165 lines
3.8 KiB
Go
165 lines
3.8 KiB
Go
package interpres
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// decodeNumber parses a bare numeric token under strict TOML rules: no leading
|
|
// zeros, underscores only between digits, prefixed radixes without a sign, and
|
|
// floats with explicit fraction/exponent digits.
|
|
func decodeNumber(tok string) (any, error) {
|
|
switch tok {
|
|
case "inf", "+inf":
|
|
return math.Inf(1), nil
|
|
case "-inf":
|
|
return math.Inf(-1), nil
|
|
case "nan", "+nan", "-nan":
|
|
return math.NaN(), nil
|
|
}
|
|
|
|
if len(tok) >= 2 && tok[0] == '0' && (tok[1] == 'x' || tok[1] == 'o' || tok[1] == 'b') {
|
|
return decodeRadix(tok)
|
|
}
|
|
if strings.ContainsAny(tok, ".eE") {
|
|
return decodeFloat(tok)
|
|
}
|
|
return decodeDecimalInt(tok)
|
|
}
|
|
|
|
func decodeDecimalInt(tok string) (any, error) {
|
|
sign, body := splitSign(tok)
|
|
digits, err := joinDigits(body, isDecDigit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkNoLeadingZero(digits); err != nil {
|
|
return nil, err
|
|
}
|
|
i, err := strconv.ParseInt(sign+digits, 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("integer %q out of range", tok)
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func decodeRadix(tok string) (any, error) {
|
|
var base int
|
|
var isDigit func(byte) bool
|
|
switch tok[1] {
|
|
case 'x':
|
|
base, isDigit = 16, isHexDigit
|
|
case 'o':
|
|
base, isDigit = 8, isOctDigit
|
|
case 'b':
|
|
base, isDigit = 2, isBinDigit
|
|
}
|
|
digits, err := joinDigits(tok[2:], isDigit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
i, err := strconv.ParseInt(digits, base, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("integer %q out of range", tok)
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func decodeFloat(tok string) (any, error) {
|
|
sign, s := splitSign(tok)
|
|
|
|
mantissa, exp := s, ""
|
|
if i := strings.IndexAny(s, "eE"); i >= 0 {
|
|
mantissa, exp = s[:i], s[i+1:]
|
|
}
|
|
|
|
intPart, frac, hasDot := mantissa, "", false
|
|
if i := strings.IndexByte(mantissa, '.'); i >= 0 {
|
|
intPart, frac, hasDot = mantissa[:i], mantissa[i+1:], true
|
|
}
|
|
if !hasDot && exp == "" {
|
|
return nil, fmt.Errorf("invalid float %q", tok)
|
|
}
|
|
|
|
ip, err := joinDigits(intPart, isDecDigit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkNoLeadingZero(ip); err != nil {
|
|
return nil, err
|
|
}
|
|
build := sign + ip
|
|
|
|
if hasDot {
|
|
fp, err := joinDigits(frac, isDecDigit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
build += "." + fp
|
|
}
|
|
if exp != "" {
|
|
esign, edigits := splitSign(exp)
|
|
ed, err := joinDigits(edigits, isDecDigit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
build += "e" + esign + ed
|
|
}
|
|
|
|
f, err := strconv.ParseFloat(build, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid float %q", tok)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// joinDigits validates that every rune is a digit (per isDigit) and that each
|
|
// underscore sits between two digits, returning the digits with underscores
|
|
// removed.
|
|
func joinDigits(s string, isDigit func(byte) bool) (string, error) {
|
|
if s == "" {
|
|
return "", fmt.Errorf("number is missing digits")
|
|
}
|
|
var b strings.Builder
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if c == '_' {
|
|
if i == 0 || i == len(s)-1 || !isDigit(s[i-1]) || !isDigit(s[i+1]) {
|
|
return "", fmt.Errorf("misplaced underscore in number %q", s)
|
|
}
|
|
continue
|
|
}
|
|
if !isDigit(c) {
|
|
return "", fmt.Errorf("invalid character %q in number", string(c))
|
|
}
|
|
b.WriteByte(c)
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
func checkNoLeadingZero(digits string) error {
|
|
if len(digits) > 1 && digits[0] == '0' {
|
|
return fmt.Errorf("leading zeros are not allowed in numbers")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func splitSign(tok string) (sign, rest string) {
|
|
if tok != "" && (tok[0] == '+' || tok[0] == '-') {
|
|
if tok[0] == '-' {
|
|
return "-", tok[1:]
|
|
}
|
|
return "", tok[1:]
|
|
}
|
|
return "", tok
|
|
}
|
|
|
|
func isDecDigit(c byte) bool { return c >= '0' && c <= '9' }
|
|
func isOctDigit(c byte) bool { return c >= '0' && c <= '7' }
|
|
func isBinDigit(c byte) bool { return c == '0' || c == '1' }
|
|
func isHexDigit(c byte) bool {
|
|
return isDecDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
|
|
}
|