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') }