github.com/benhoyt/goawk@v1.8.1/interp/value.go (about) 1 // GoAWK interpreter value type (not exported). 2 3 package interp 4 5 import ( 6 "fmt" 7 "math" 8 "strconv" 9 10 "github.com/benhoyt/goawk/internal/strutil" 11 ) 12 13 type valueType uint8 14 15 const ( 16 typeNull valueType = iota 17 typeStr 18 typeNum 19 typeNumStr 20 ) 21 22 // An AWK value (these are passed around by value) 23 type value struct { 24 typ valueType // Type of value 25 s string // String value (for typeStr) 26 n float64 // Numeric value (for typeNum and typeNumStr) 27 } 28 29 // Create a new null value 30 func null() value { 31 return value{} 32 } 33 34 // Create a new number value 35 func num(n float64) value { 36 return value{typ: typeNum, n: n} 37 } 38 39 // Create a new string value 40 func str(s string) value { 41 return value{typ: typeStr, s: s} 42 } 43 44 // Create a new value for a "numeric string" context, converting the 45 // string to a number if possible. 46 func numStr(s string) value { 47 f, err := strconv.ParseFloat(strutil.TrimSpace(s), 64) 48 if err != nil { 49 // Doesn't parse as number, make it a "true string" 50 return value{typ: typeStr, s: s} 51 } 52 return value{typ: typeNumStr, s: s, n: f} 53 } 54 55 // Create a numeric value from a Go bool 56 func boolean(b bool) value { 57 if b { 58 return num(1) 59 } 60 return num(0) 61 } 62 63 // Return true if value is a "true string" (string but not a "numeric 64 // string") 65 func (v value) isTrueStr() bool { 66 return v.typ == typeStr 67 } 68 69 // Return Go bool value of AWK value. For numbers or numeric strings, 70 // zero is false and everything else is true. For strings, empty 71 // string is false and everything else is true. 72 func (v value) boolean() bool { 73 if v.isTrueStr() { 74 return v.s != "" 75 } else { 76 return v.n != 0 77 } 78 } 79 80 // Return value's string value, or convert to a string using given 81 // format if a number value. Integers are a special case and don't 82 // use floatFormat. 83 func (v value) str(floatFormat string) string { 84 if v.typ == typeNum { 85 switch { 86 case math.IsNaN(v.n): 87 return "nan" 88 case math.IsInf(v.n, 0): 89 if v.n < 0 { 90 return "-inf" 91 } else { 92 return "inf" 93 } 94 case v.n == float64(int(v.n)): 95 return strconv.Itoa(int(v.n)) 96 default: 97 return fmt.Sprintf(floatFormat, v.n) 98 } 99 } 100 // For typeStr and typeStrNum we already have the string, for 101 // typeNull v.s == "". 102 return v.s 103 } 104 105 // Return value's number value, converting from string if necessary 106 func (v value) num() float64 { 107 if v.typ == typeStr { 108 // Ensure string starts with a float and convert it 109 return parseFloatPrefix(v.s) 110 } 111 // Handle case for typeNum and typeStrNum. If it's a numeric 112 // string, we already have the float value from the numStr() 113 // call. For typeNull v.n == 0. 114 return v.n 115 } 116 117 // Like strconv.ParseFloat, but parses at the start of string and 118 // allows things like "1.5foo" 119 func parseFloatPrefix(s string) float64 { 120 // Skip whitespace at start 121 i := 0 122 for i < len(s) && strutil.IsASCIISpace(s[i]) { 123 i++ 124 } 125 start := i 126 127 // Parse mantissa: optional sign, initial digit(s), optional '.', 128 // then more digits 129 gotDigit := false 130 if i < len(s) && (s[i] == '+' || s[i] == '-') { 131 i++ 132 } 133 for i < len(s) && s[i] >= '0' && s[i] <= '9' { 134 gotDigit = true 135 i++ 136 } 137 if i < len(s) && s[i] == '.' { 138 i++ 139 } 140 for i < len(s) && s[i] >= '0' && s[i] <= '9' { 141 gotDigit = true 142 i++ 143 } 144 if !gotDigit { 145 return 0 146 } 147 148 // Parse exponent ("1e" and similar are allowed, but ParseFloat 149 // rejects them) 150 end := i 151 if i < len(s) && (s[i] == 'e' || s[i] == 'E') { 152 i++ 153 if i < len(s) && (s[i] == '+' || s[i] == '-') { 154 i++ 155 } 156 for i < len(s) && s[i] >= '0' && s[i] <= '9' { 157 i++ 158 end = i 159 } 160 } 161 162 floatStr := s[start:end] 163 f, _ := strconv.ParseFloat(floatStr, 64) 164 return f // Returns infinity in case of "value out of range" error 165 }