grawkit/play/vendor/github.com/benhoyt/goawk/interp/value.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'
}