perf(number): skip the rebuild for tokens without underscores
Assisted-by: GLM 5.3
This commit is contained in:
@@ -47,6 +47,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
the input is validated UTF-8 up front, so the conversion pass and its four
|
||||
bytes per rune were pure overhead. Parsing a large array-of-tables document
|
||||
runs about a fifth faster and allocates about half the memory.
|
||||
- Numeric tokens without underscores skip the normalising rebuild: digits are
|
||||
validated in place in `joinDigits`, and a float whose token is already
|
||||
clean goes to `strconv.ParseFloat` directly. One allocation per integer
|
||||
atom and two per float atom disappear.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -94,28 +94,43 @@ func decodeFloat(tok string) (any, error) {
|
||||
if err := checkNoLeadingZero(ip); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
build := sign + ip
|
||||
|
||||
fp := ""
|
||||
if hasDot {
|
||||
fp, err := joinDigits(frac, isDecDigit)
|
||||
if err != nil {
|
||||
if fp, err = joinDigits(frac, isDecDigit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
build += "." + fp
|
||||
}
|
||||
// The ABNF requires at least one digit after the exponent marker, so a
|
||||
// trailing e or E is an error even though strconv would accept it. The
|
||||
// digits are a zero-prefixable integer, so leading zeros are fine here
|
||||
// (the corpus holds valid cases such as 1e06 and 0e00).
|
||||
esign, ed := "", ""
|
||||
if hasExp {
|
||||
esign, edigits := splitSign(exp)
|
||||
ed, err := joinDigits(edigits, isDecDigit)
|
||||
if err != nil {
|
||||
var digits string
|
||||
esign, digits = splitSign(exp)
|
||||
if ed, err = joinDigits(digits, isDecDigit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
build += "e" + esign + ed
|
||||
}
|
||||
|
||||
// The checks above validated the token's shape, and every character a
|
||||
// valid token may carry is one strconv.ParseFloat accepts in place, so
|
||||
// only a token with underscores needs the stripped rebuild.
|
||||
if !strings.ContainsRune(tok, '_') {
|
||||
f, err := strconv.ParseFloat(tok, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid float %q", tok)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
build := sign + ip
|
||||
if hasDot {
|
||||
build += "." + fp
|
||||
}
|
||||
if hasExp {
|
||||
build += "e" + esign + ed
|
||||
}
|
||||
f, err := strconv.ParseFloat(build, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid float %q", tok)
|
||||
@@ -125,11 +140,20 @@ func decodeFloat(tok string) (any, error) {
|
||||
|
||||
// joinDigits validates that every rune is a digit (per isDigit) and that each
|
||||
// underscore sits between two digits, returning the digits with underscores
|
||||
// removed.
|
||||
// removed. A token without underscores, the common case, is validated in
|
||||
// place and returned without a copy.
|
||||
func joinDigits(s string, isDigit func(byte) bool) (string, error) {
|
||||
if s == "" {
|
||||
return "", fmt.Errorf("number is missing digits")
|
||||
}
|
||||
if !strings.ContainsRune(s, '_') {
|
||||
for i := range len(s) {
|
||||
if !isDigit(s[i]) {
|
||||
return "", fmt.Errorf("invalid character %q in number", string(s[i]))
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
var b strings.Builder
|
||||
for i := range len(s) {
|
||||
c := s[i]
|
||||
|
||||
Reference in New Issue
Block a user