mirror of
https://github.com/deuill/grawkit.git
synced 2024-09-28 00:12:45 +00:00
Alex Palaistras
e1208d67bd
This updates to the latest Go version for building the default OCI container for the Grawkit playground, and switches to using the `embed` package for serving static content.
295 lines
6.5 KiB
Go
295 lines
6.5 KiB
Go
// GoAWK interpreter value type (not exported).
|
|
|
|
package interp
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type valueType uint8
|
|
|
|
const (
|
|
typeNull valueType = iota
|
|
typeStr
|
|
typeNum
|
|
typeNumStr
|
|
)
|
|
|
|
// An AWK value (these are passed around by value)
|
|
type value struct {
|
|
typ valueType // Type of value
|
|
s string // String value (for typeStr and typeNumStr)
|
|
n float64 // Numeric value (for typeNum)
|
|
}
|
|
|
|
// Create a new null value
|
|
func null() value {
|
|
return value{}
|
|
}
|
|
|
|
// Create a new number value
|
|
func num(n float64) value {
|
|
return value{typ: typeNum, n: n}
|
|
}
|
|
|
|
// Create a new string value
|
|
func str(s string) value {
|
|
return value{typ: typeStr, s: s}
|
|
}
|
|
|
|
// Create a new value to represent a "numeric string" from an input field
|
|
func numStr(s string) value {
|
|
return value{typ: typeNumStr, s: s}
|
|
}
|
|
|
|
// Create a numeric value from a Go bool
|
|
func boolean(b bool) value {
|
|
if b {
|
|
return num(1)
|
|
}
|
|
return num(0)
|
|
}
|
|
|
|
// String returns a string representation of v for debugging.
|
|
func (v value) String() string {
|
|
switch v.typ {
|
|
case typeStr:
|
|
return fmt.Sprintf("str(%q)", v.s)
|
|
case typeNum:
|
|
return fmt.Sprintf("num(%s)", v.str("%.6g"))
|
|
case typeNumStr:
|
|
return fmt.Sprintf("numStr(%q)", v.s)
|
|
default:
|
|
return "null()"
|
|
}
|
|
}
|
|
|
|
// Return true if value is a "true string" (a string or a "numeric string"
|
|
// from an input field that can't be converted to a number). If false,
|
|
// also return the (possibly converted) number.
|
|
func (v value) isTrueStr() (float64, bool) {
|
|
switch v.typ {
|
|
case typeStr:
|
|
return 0, true
|
|
case typeNumStr:
|
|
f, err := parseFloat(v.s)
|
|
if err != nil {
|
|
return 0, true
|
|
}
|
|
return f, false
|
|
default: // typeNum, typeNull
|
|
return v.n, false
|
|
}
|
|
}
|
|
|
|
// Return Go bool value of AWK value. For numbers or numeric strings,
|
|
// zero is false and everything else is true. For strings, empty
|
|
// string is false and everything else is true.
|
|
func (v value) boolean() bool {
|
|
switch v.typ {
|
|
case typeStr:
|
|
return v.s != ""
|
|
case typeNumStr:
|
|
f, err := parseFloat(v.s)
|
|
if err != nil {
|
|
return v.s != ""
|
|
}
|
|
return f != 0
|
|
default: // typeNum, typeNull
|
|
return v.n != 0
|
|
}
|
|
}
|
|
|
|
// Like strconv.ParseFloat, but allow hex floating point without exponent, and
|
|
// allow "+nan" and "-nan" (though they both return math.NaN()). Also disallow
|
|
// underscore digit separators.
|
|
func parseFloat(s string) (float64, error) {
|
|
s = strings.TrimSpace(s)
|
|
if len(s) > 1 && (s[0] == '+' || s[0] == '-') {
|
|
if len(s) == 4 && hasNaNPrefix(s[1:]) {
|
|
// ParseFloat doesn't handle "nan" with sign prefix, so handle it here.
|
|
return math.NaN(), nil
|
|
}
|
|
if len(s) > 3 && hasHexPrefix(s[1:]) && strings.IndexByte(s, 'p') < 0 {
|
|
s += "p0"
|
|
}
|
|
} else if len(s) > 2 && hasHexPrefix(s) && strings.IndexByte(s, 'p') < 0 {
|
|
s += "p0"
|
|
}
|
|
n, err := strconv.ParseFloat(s, 64)
|
|
if err == nil && strings.IndexByte(s, '_') >= 0 {
|
|
// Underscore separators aren't supported by AWK.
|
|
return 0, strconv.ErrSyntax
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// Return value's string value, or convert to a string using given
|
|
// format if a number value. Integers are a special case and don't
|
|
// use floatFormat.
|
|
func (v value) str(floatFormat string) string {
|
|
if v.typ == typeNum {
|
|
switch {
|
|
case math.IsNaN(v.n):
|
|
return "nan"
|
|
case math.IsInf(v.n, 0):
|
|
if v.n < 0 {
|
|
return "-inf"
|
|
} else {
|
|
return "inf"
|
|
}
|
|
case v.n == float64(int(v.n)):
|
|
return strconv.Itoa(int(v.n))
|
|
default:
|
|
if floatFormat == "%.6g" {
|
|
return strconv.FormatFloat(v.n, 'g', 6, 64)
|
|
}
|
|
return fmt.Sprintf(floatFormat, v.n)
|
|
}
|
|
}
|
|
// For typeStr and typeNumStr we already have the string, for
|
|
// typeNull v.s == "".
|
|
return v.s
|
|
}
|
|
|
|
// Return value's number value, converting from string if necessary
|
|
func (v value) num() float64 {
|
|
switch v.typ {
|
|
case typeStr, typeNumStr:
|
|
// Ensure string starts with a float and convert it
|
|
return parseFloatPrefix(v.s)
|
|
default: // typeNum, typeNull
|
|
return v.n
|
|
}
|
|
}
|
|
|
|
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
|
|
|
|
// Like strconv.ParseFloat, but parses at the start of string and
|
|
// allows things like "1.5foo"
|
|
func parseFloatPrefix(s string) float64 {
|
|
// Skip whitespace at start
|
|
i := 0
|
|
for i < len(s) && asciiSpace[s[i]] != 0 {
|
|
i++
|
|
}
|
|
start := i
|
|
|
|
// Parse optional sign and check for NaN and Inf.
|
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
|
i++
|
|
}
|
|
if i+3 <= len(s) {
|
|
if hasNaNPrefix(s[i:]) {
|
|
return math.NaN()
|
|
}
|
|
if hasInfPrefix(s[i:]) {
|
|
if s[start] == '-' {
|
|
return math.Inf(-1)
|
|
}
|
|
return math.Inf(1)
|
|
}
|
|
}
|
|
|
|
// Parse mantissa: initial digit(s), optional '.', then more digits
|
|
if i+2 < len(s) && hasHexPrefix(s[i:]) {
|
|
return parseHexFloatPrefix(s, start, i+2)
|
|
}
|
|
gotDigit := false
|
|
for i < len(s) && isDigit(s[i]) {
|
|
gotDigit = true
|
|
i++
|
|
}
|
|
if i < len(s) && s[i] == '.' {
|
|
i++
|
|
}
|
|
for i < len(s) && isDigit(s[i]) {
|
|
gotDigit = true
|
|
i++
|
|
}
|
|
if !gotDigit {
|
|
return 0
|
|
}
|
|
|
|
// Parse exponent ("1e" and similar are allowed, but ParseFloat
|
|
// rejects them)
|
|
end := i
|
|
if i < len(s) && (s[i] == 'e' || s[i] == 'E') {
|
|
i++
|
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
|
i++
|
|
}
|
|
for i < len(s) && isDigit(s[i]) {
|
|
i++
|
|
end = i
|
|
}
|
|
}
|
|
|
|
floatStr := s[start:end]
|
|
f, _ := strconv.ParseFloat(floatStr, 64)
|
|
return f // Returns infinity in case of "value out of range" error
|
|
}
|
|
|
|
func hasHexPrefix(s string) bool {
|
|
return s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
|
}
|
|
|
|
func hasNaNPrefix(s string) bool {
|
|
return (s[0] == 'n' || s[0] == 'N') && (s[1] == 'a' || s[1] == 'A') && (s[2] == 'n' || s[2] == 'N')
|
|
}
|
|
|
|
func hasInfPrefix(s string) bool {
|
|
return (s[0] == 'i' || s[0] == 'I') && (s[1] == 'n' || s[1] == 'N') && (s[2] == 'f' || s[2] == 'F')
|
|
}
|
|
|
|
// Helper used by parseFloatPrefix to handle hexadecimal floating point.
|
|
func parseHexFloatPrefix(s string, start, i int) float64 {
|
|
gotDigit := false
|
|
for i < len(s) && isHexDigit(s[i]) {
|
|
gotDigit = true
|
|
i++
|
|
}
|
|
if i < len(s) && s[i] == '.' {
|
|
i++
|
|
}
|
|
for i < len(s) && isHexDigit(s[i]) {
|
|
gotDigit = true
|
|
i++
|
|
}
|
|
if !gotDigit {
|
|
return 0
|
|
}
|
|
|
|
gotExponent := false
|
|
end := i
|
|
if i < len(s) && (s[i] == 'p' || s[i] == 'P') {
|
|
i++
|
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
|
i++
|
|
}
|
|
for i < len(s) && isDigit(s[i]) {
|
|
gotExponent = true
|
|
i++
|
|
end = i
|
|
}
|
|
}
|
|
|
|
floatStr := s[start:end]
|
|
if !gotExponent {
|
|
floatStr += "p0" // AWK allows "0x12", ParseFloat requires "0x12p0"
|
|
}
|
|
f, _ := strconv.ParseFloat(floatStr, 64)
|
|
return f // Returns infinity in case of "value out of range" error
|
|
}
|
|
|
|
func isDigit(c byte) bool {
|
|
return c >= '0' && c <= '9'
|
|
}
|
|
|
|
func isHexDigit(c byte) bool {
|
|
return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
|
|
}
|