feat: scaffold interpres project with TOML parser and CI
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
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')
|
||||
}
|
||||
Reference in New Issue
Block a user