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  }