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
|
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
|
bytes per rune were pure overhead. Parsing a large array-of-tables document
|
||||||
runs about a fifth faster and allocates about half the memory.
|
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
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -94,28 +94,43 @@ func decodeFloat(tok string) (any, error) {
|
|||||||
if err := checkNoLeadingZero(ip); err != nil {
|
if err := checkNoLeadingZero(ip); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
build := sign + ip
|
|
||||||
|
|
||||||
|
fp := ""
|
||||||
if hasDot {
|
if hasDot {
|
||||||
fp, err := joinDigits(frac, isDecDigit)
|
if fp, err = joinDigits(frac, isDecDigit); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
build += "." + fp
|
|
||||||
}
|
}
|
||||||
// The ABNF requires at least one digit after the exponent marker, so a
|
// 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
|
// 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
|
// digits are a zero-prefixable integer, so leading zeros are fine here
|
||||||
// (the corpus holds valid cases such as 1e06 and 0e00).
|
// (the corpus holds valid cases such as 1e06 and 0e00).
|
||||||
|
esign, ed := "", ""
|
||||||
if hasExp {
|
if hasExp {
|
||||||
esign, edigits := splitSign(exp)
|
var digits string
|
||||||
ed, err := joinDigits(edigits, isDecDigit)
|
esign, digits = splitSign(exp)
|
||||||
if err != nil {
|
if ed, err = joinDigits(digits, isDecDigit); err != nil {
|
||||||
return nil, err
|
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)
|
f, err := strconv.ParseFloat(build, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid float %q", tok)
|
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
|
// joinDigits validates that every rune is a digit (per isDigit) and that each
|
||||||
// underscore sits between two digits, returning the digits with underscores
|
// 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) {
|
func joinDigits(s string, isDigit func(byte) bool) (string, error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return "", fmt.Errorf("number is missing digits")
|
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
|
var b strings.Builder
|
||||||
for i := range len(s) {
|
for i := range len(s) {
|
||||||
c := s[i]
|
c := s[i]
|
||||||
|
|||||||
Reference in New Issue
Block a user