aboutsummaryrefslogtreecommitdiffstats
path: root/ccl.go
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-10-30 11:04:23 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-10-30 11:35:15 -0700
commitb1e37119da439b52a2c1f1aca4df2fdc80462f97 (patch)
tree7eece3cb89901ebb01402745c002f4f2ff1c34ae /ccl.go
parentAdd benchmark (diff)
downloadccl-b1e37119da439b52a2c1f1aca4df2fdc80462f97.tar.zst
Remove the regexes
Wow it doesn't look good for regex goos: linux goarch: amd64 pkg: roseh.moe/pkg/ccl cpu: AMD Ryzen 9 5900X 12-Core Processor │ baseline.txt │ regexn't.txt │ │ sec/op │ sec/op vs base │ Lex-24 8.625µ ± 1% 1.429µ ± 0% -83.43% (p=0.000 n=20) Parse-24 18.305µ ± 0% 8.856µ ± 0% -51.62% (p=0.000 n=20) geomean 12.57µ 3.557µ -71.69%
Diffstat (limited to 'ccl.go')
-rw-r--r--ccl.go271
1 files changed, 145 insertions, 126 deletions
diff --git a/ccl.go b/ccl.go
index c4c660e..521fbba 100644
--- a/ccl.go
+++ b/ccl.go
@@ -167,7 +167,6 @@ import (
"iter"
"math"
"reflect"
- "regexp"
"strconv"
"strings"
"unicode/utf8"
@@ -284,121 +283,159 @@ func (p *parser) next() ([]byte, error) {
return tok, nil
}
-var (
- numRE = regexp.MustCompile(`^[-+]?(0[xX][0-9a-fA-F]+|((0|[1-9][0-9]*)(\.[0-9]*)?|\.[0-9]+)([eE][-+]?[0-9]+)?)$`)
- hexRE = regexp.MustCompile(`^([-+]?)0[xX]`)
-)
+func checkNum(b []byte) bool {
+ if bytes.Equal(b, []byte("0")) {
+ return true
+ }
+ if len(b) == 0 || !(b[0] == '.' || '1' <= b[0] && b[0] <= '9') {
+ return false
+ }
+ haveDigits := false
+ for ; len(b) > 0 && '0' <= b[0] && b[0] <= '9'; b = b[1:] {
+ haveDigits = true
+ }
+ if len(b) > 0 && b[0] == '.' {
+ b = b[1:]
+ for ; len(b) > 0 && '0' <= b[0] && b[0] <= '9'; b = b[1:] {
+ haveDigits = true
+ }
+ }
+ if !haveDigits {
+ return false
+ }
+ if len(b) == 0 || !(b[0] == 'e' || b[0] == 'E') {
+ return true
+ }
+ b = b[1:]
+ if len(b) > 0 && b[0] == '-' || b[0] == '+' {
+ b = b[1:]
+ }
+ if len(b) == 0 {
+ return false
+ }
+ for ; len(b) > 0 && '0' <= b[0] && b[0] <= '9'; b = b[1:] {
+ }
+ return len(b) == 0
+}
+
+type integer struct {
+ n uint64
+ sgn int8
+}
func (p *parser) parseNum(numBytes []byte) (any, error) {
- if !numRE.Match(numBytes) {
- return nil, p.error("invalid number")
+ n := numBytes
+ var sgn int8 = 1
+ switch numBytes[0] {
+ case '-':
+ sgn = -1
+ n = numBytes[1:]
+ case '+':
+ n = numBytes[1:]
}
- if hex := hexRE.FindSubmatch(numBytes); hex != nil {
- if string(hex[1]) == "-" {
- n, err := strconv.ParseInt(string(numBytes[len(hex[0]):]), 16, 64)
- if err != nil {
- return nil, p.error("invalid number")
- }
- return -n, nil
- } else {
- n, err := strconv.ParseUint(string(numBytes[len(hex[0]):]), 16, 64)
- if err != nil {
- return nil, p.error("invalid number")
- }
- return n, nil
+ if len(n) > 2 && n[0] == '0' && (n[1] == 'x' || n[1] == 'X') {
+ n, err := strconv.ParseUint(string(n[2:]), 16, 64)
+ if err != nil {
+ return nil, p.error("invalid hex number: %s", err)
}
+ return &integer{n, sgn}, nil
+ }
+ if !checkNum(n) {
+ return nil, p.error("invalid number")
}
- if bytes.ContainsAny(numBytes, ".eE") {
+ if bytes.ContainsAny(n, ".eE") {
n, err := strconv.ParseFloat(string(numBytes), 64)
if err != nil {
- return nil, p.error("invalid number")
+ return nil, p.error("invalid number (unreachable)")
}
return n, nil
}
- if bytes.HasPrefix(numBytes, []byte("-")) {
- n, err := strconv.ParseInt(string(numBytes), 10, 64)
- if err != nil {
- return nil, p.error("invalid number")
- }
- return n, nil
- } else {
- n, err := strconv.ParseUint(string(bytes.TrimPrefix(numBytes, []byte("+"))), 10, 64)
- if err != nil {
- return nil, p.error("invalid number")
- }
- return n, nil
+ un, err := strconv.ParseUint(string(n), 10, 64)
+ if err != nil {
+ return nil, p.error("invalid number (unreachable)")
}
-}
-
-var escapesRE = regexp.MustCompile(`(?s)\\(.|\r\n|[0-7]{3}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})`)
-
-func init() {
- escapesRE.Longest()
+ return &integer{un, sgn}, nil
}
func (p *parser) unescape(rawStr []byte) ([]byte, error) {
- var err error
- escaped := escapesRE.ReplaceAllFunc(rawStr, func(escape []byte) []byte {
- switch string(escape) {
- case `\'`:
- return []byte("'")
- case `\"`:
- return []byte(`"`)
- case `\?`:
- return []byte("?")
- case `\\`:
- return []byte(`\`)
- case `\a`:
- return []byte("\a")
- case `\b`:
- return []byte("\b")
- case `\f`:
- return []byte("\f")
- case `\n`:
- return []byte("\n")
- case `\r`:
- return []byte("\r")
- case `\t`:
- return []byte("\t")
- case `\v`:
- return []byte("\v")
- case "\\\n", "\\\r\n":
- return nil
+ var escaped []byte
+ for i := 0; i < len(rawStr); i++ {
+ if rawStr[i] != '\\' {
+ escaped = append(escaped, rawStr[i])
+ continue
}
- switch {
- case bytes.HasPrefix(escape, []byte(`\x`)):
- var n uint64
- if n, err = strconv.ParseUint(string(escape[2:]), 16, 8); err != nil {
- err = p.error("invalid hex escape %q: %s", escape, err)
- return nil
+ i++
+ var b []byte
+ switch rawStr[i] {
+ case '\'':
+ b = []byte("'")
+ case '"':
+ b = []byte(`"`)
+ case '?':
+ b = []byte("?")
+ case '\\':
+ b = []byte(`\`)
+ case 'a':
+ b = []byte("\a")
+ case 'b':
+ b = []byte("\b")
+ case 'f':
+ b = []byte("\f")
+ case 'n':
+ b = []byte("\n")
+ case 'r':
+ b = []byte("\r")
+ case 't':
+ b = []byte("\t")
+ case 'v':
+ b = []byte("\v")
+ case '\n':
+ b = nil
+ case '\r':
+ i++
+ if i < len(rawStr) && rawStr[i] == '\n' {
+ b = nil
+ } else {
+ return nil, fmt.Errorf("invalid escape sequence %q", rawStr[i-2:min(i+1, len(rawStr))])
}
- return []byte{byte(n)}
- case bytes.HasPrefix(escape, []byte(`\u`)), bytes.HasPrefix(escape, []byte(`\U`)):
- var n int64
- if n, err = strconv.ParseInt(string(escape[2:]), 16, 32); err != nil {
- err = p.error("invalid unicode escape %q: %s", escape, err)
- return nil
+ case 'x':
+ i++
+ if i+2 > len(rawStr) {
+ return nil, fmt.Errorf("invalid hex escape %q", rawStr[i-2:min(i+2, len(rawStr))])
}
- return utf8.AppendRune(nil, rune(n))
- default:
- if len(escape) != 4 {
- err = p.error("invalid string escape %q", escape)
- return nil
+ n, err := strconv.ParseUint(string(rawStr[i:i+2]), 16, 8)
+ if err != nil {
+ return nil, fmt.Errorf("invalid hex escape %q: %s", rawStr[i-2:i+2], err)
}
- var n int64
- if n, err = strconv.ParseInt(string(escape[1:]), 8, 32); err != nil {
- err = p.error("invalid string escape %q", escape)
- return nil
+ i++
+ b = []byte{byte(n)}
+ case 'u', 'U':
+ nBytes := 4
+ if rawStr[i] == 'U' {
+ nBytes = 8
}
- if n > 255 {
- err = p.error("invalid octal escape %q %d > 255", escape, n)
- return nil
+ i++
+ if i+nBytes > len(rawStr) {
+ return nil, fmt.Errorf("invalid unicode escape %q", rawStr[i-2:min(i+nBytes, len(rawStr))])
}
- return []byte{byte(n)}
+ n, err := strconv.ParseUint(string(rawStr[i:i+nBytes]), 16, 31)
+ if err != nil {
+ return nil, fmt.Errorf("invalid hex escape %q: %s", rawStr[i-2:i+2], err)
+ }
+ i += nBytes - 1
+ b = utf8.AppendRune(nil, rune(n))
+ default:
+ if i+3 > len(rawStr) {
+ return nil, fmt.Errorf("invalid string escape %q", rawStr[i-1:i+1])
+ }
+ n, err := strconv.ParseUint(string(rawStr[i:i+3]), 8, 8)
+ if err != nil {
+ return nil, fmt.Errorf("invalid octal escape %q: %s", rawStr[i:i+3], err)
+ }
+ i += 2
+ b = []byte{byte(n)}
}
- })
- if err != nil {
- return nil, err
+ escaped = append(escaped, b...)
}
if !utf8.Valid(escaped) {
return nil, p.error("syntax error: string %q is not UTF-8 encoded", escaped)
@@ -499,7 +536,7 @@ func appendAny(prevVal any, newVal any) any {
var l []any
if ll, ok := prevVal.([]any); ok {
l = ll
- } else if prevVal != nil {
+ } else {
l = []any{prevVal}
}
if ll, ok := newVal.([]any); ok {
@@ -555,18 +592,18 @@ func (p *parser) parse() (map[string]any, error) {
}
}
-func intLimits(kind reflect.Kind) (min int64, max uint64, ok bool) {
+func intLimits(kind reflect.Kind) (min, max uint64, ok bool) {
switch kind {
case reflect.Int:
- return math.MinInt, math.MaxInt, true
+ return -math.MinInt, math.MaxInt, true
case reflect.Int8:
- return math.MinInt8, math.MaxInt8, true
+ return -math.MinInt8, math.MaxInt8, true
case reflect.Int16:
- return math.MinInt16, math.MaxInt16, true
+ return -math.MinInt16, math.MaxInt16, true
case reflect.Int32:
- return math.MinInt32, math.MaxInt32, true
+ return -math.MinInt32, math.MaxInt32, true
case reflect.Int64:
- return math.MinInt64, math.MaxInt64, true
+ return -math.MinInt64, math.MaxInt64, true
case reflect.Uint:
return 0, math.MaxUint, true
case reflect.Uint8:
@@ -603,41 +640,23 @@ func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, fi
default:
return fmt.Errorf("field %q should have type bool", field)
}
- case uint64:
- switch fieldVal.Kind() {
- case reflect.Float32, reflect.Float64:
- fieldVal.SetFloat(float64(val))
- return nil
- }
- min, max, ok := intLimits(fieldVal.Kind())
- if !ok {
- return fmt.Errorf("field %q should have type int", field)
- }
- if val > max {
- return fmt.Errorf("number %d is out of range for %s", val, fieldVal.Kind())
- }
- if min == 0 { // unsigned
- fieldVal.SetUint(val)
- } else {
- fieldVal.SetInt(int64(val))
- }
- case int64:
+ case *integer:
switch fieldVal.Kind() {
case reflect.Float32, reflect.Float64:
- fieldVal.SetFloat(float64(val))
+ fieldVal.SetFloat(float64(val.sgn) * float64(val.n))
return nil
}
min, max, ok := intLimits(fieldVal.Kind())
if !ok {
return fmt.Errorf("field %q should have type int", field)
}
- if val < min || val > 0 && uint64(val) > max {
+ if val.sgn < 0 && val.n > min || val.sgn > 0 && val.n > max {
return fmt.Errorf("number %d is out of range for %s", val, fieldVal.Kind())
}
if min == 0 { // unsigned
- fieldVal.SetUint(uint64(val))
+ fieldVal.SetUint(val.n)
} else {
- fieldVal.SetInt(val)
+ fieldVal.SetInt(int64(val.sgn) * int64(val.n))
}
case float64:
switch fieldVal.Kind() {
@@ -684,7 +703,7 @@ func unpackVal(fieldVal reflect.Value, fieldMap map[structField]int, val any, fi
case []any:
return fmt.Errorf("invalid repeated field")
default:
- return fmt.Errorf("unexpected AST node (internal failure)")
+ return fmt.Errorf("unexpected AST node (unreachable)")
}
return nil
}