aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-10-30 18:45:11 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-10-30 18:50:19 -0700
commitaa8a935f620a053cc9c3c65587b433f3e8d3a6e9 (patch)
treea786b3174c1a901f19fc10485c4856d60ef93d6b
parent74235daa0ae145c96b63c980e98c9c169f5c0007 (diff)
downloadccl-aa8a935f620a053cc9c3c65587b433f3e8d3a6e9.tar.zst
Use a classic struct iterator instead of iter.Pull
iter.Pull doesn't provide much value here. Actually the lexer is probably simpler this way, and iter.Pull also destroys stack traces for panics in the lexer. Ouch goos: linux goarch: amd64 pkg: roseh.moe/pkg/ccl cpu: AMD Ryzen 9 5900X 12-Core Processor │ baseline.txt │ itern't.txt │ │ sec/op │ sec/op vs base │ Lex-24 1.436µ ± 0% 1.320µ ± 0% -8.05% (p=0.000 n=20) Parse-24 7.846µ ± 0% 4.801µ ± 0% -38.82% (p=0.000 n=20) geomean 3.356µ 2.517µ -24.99%
-rw-r--r--ccl.go13
-rw-r--r--ccl_test.go7
-rw-r--r--lexer.go112
3 files changed, 54 insertions, 78 deletions
diff --git a/ccl.go b/ccl.go
index 3a444c1..6d97152 100644
--- a/ccl.go
+++ b/ccl.go
@@ -164,7 +164,6 @@ import (
"encoding/base64"
"errors"
"fmt"
- "iter"
"math"
"reflect"
"strconv"
@@ -243,7 +242,7 @@ func fieldMap(out map[structField]int, types map[reflect.Type]bool, s reflect.Ty
}
type parser struct {
- nextTok func() (token, error, bool)
+ nextTok func() (token, error)
tok []byte
err error
data []byte
@@ -261,11 +260,7 @@ func (p *parser) peek() ([]byte, error) {
if p.err != nil || p.tok != nil {
return p.tok, p.err
}
- tok, err, ok := p.nextTok()
- if !ok {
- p.err = errEOF
- return nil, p.err
- }
+ tok, err := p.nextTok()
if err != nil {
p.err = err
return nil, p.err
@@ -747,7 +742,5 @@ func Unmarshal(data []byte, v any) error {
if err := fieldMap(fields, make(map[reflect.Type]bool), val.Type().Elem()); err != nil {
return err
}
- nextToken, stop := iter.Pull2(tokens(data))
- defer stop()
- return (&parser{nextTok: nextToken, data: data, fieldMap: fields}).parse(val.Elem())
+ return (&parser{nextTok: newLexer(data).next, data: data, fieldMap: fields}).parse(val.Elem())
}
diff --git a/ccl_test.go b/ccl_test.go
index ab4833f..53b2a24 100644
--- a/ccl_test.go
+++ b/ccl_test.go
@@ -684,8 +684,13 @@ func BenchmarkLex(b *testing.B) {
repeated: [5, 6]
`)
for b.Loop() {
- for _, err := range tokens(msg) {
+ l := newLexer(msg)
+ for {
+ _, err := l.next()
if err != nil {
+ if err == errEOF {
+ break
+ }
b.Fatal(err)
}
}
diff --git a/lexer.go b/lexer.go
index c7c0631..6561e38 100644
--- a/lexer.go
+++ b/lexer.go
@@ -2,7 +2,6 @@ package ccl
import (
"bytes"
- "iter"
"unicode"
"unicode/utf8"
)
@@ -13,21 +12,18 @@ type token struct {
}
type lexer struct {
- data []byte
- i int
- yieldTok func(token, error) bool
+ data []byte
+ i int
}
-func (l *lexer) error(reason string, args ...any) {
- l.yieldTok(token{}, newSyntaxError(l.data, l.i, reason, args...))
+func (l *lexer) error(reason string, args ...any) error {
+ return newSyntaxError(l.data, l.i, reason, args...)
}
-func (l *lexer) yield(n int) bool {
- if !l.yieldTok(token{l.i, l.data[l.i : l.i+n]}, nil) {
- return false
- }
+func (l *lexer) yield(n int) token {
+ t := token{l.i, l.data[l.i : l.i+n]}
l.i += n
- return true
+ return t
}
func (l *lexer) skipSpace() {
@@ -75,67 +71,49 @@ func fieldTailByte(b byte) bool {
'0' <= b && b <= '9'
}
-func (l *lexer) tokens() {
- for l.i = 0; ; {
- l.skipSpace()
- if l.i == len(l.data) {
- break
- }
- switch l.data[l.i] {
- case
- '{',
- '}',
- '[',
- ']',
- ':',
- ',':
+func (l *lexer) next() (token, error) {
+ l.skipSpace()
+ if l.i == len(l.data) {
+ return token{}, errEOF
+ }
+ switch l.data[l.i] {
+ case
+ '{',
+ '}',
+ '[',
+ ']',
+ ':',
+ ',':
- if !l.yield(1) {
- return
- }
- continue
- case '\'', '"':
- q := l.data[l.i]
- i := l.i + 1
- for ; i < len(l.data) && l.data[i] != q; i++ {
- if l.data[i] == '\\' {
- i++
- }
+ return l.yield(1), nil
+ case '\'', '"':
+ q := l.data[l.i]
+ i := l.i + 1
+ for ; i < len(l.data) && l.data[i] != q; i++ {
+ if l.data[i] == '\\' {
+ i++
}
- if i >= len(l.data) {
- l.error("unterminated string")
- return
- }
- if !l.yield(i + 1 - l.i) {
- return
- }
- continue
}
- switch b := l.data[l.i]; {
- case numFirstByte(b):
- i := l.i + 1
- for ; i < len(l.data) && numTailByte(l.data[i]); i++ {
- }
- if !l.yield(i - l.i) {
- return
- }
- continue
- case fieldFirstByte(b):
- i := l.i + 1
- for ; i < len(l.data) && fieldTailByte(l.data[i]); i++ {
- }
- if !l.yield(i - l.i) {
- return
- }
- continue
+ if i >= len(l.data) {
+ return token{}, l.error("unterminated string")
}
- l.error("invalid lexeme")
- return
+ return l.yield(i + 1 - l.i), nil
}
+ switch b := l.data[l.i]; {
+ case numFirstByte(b):
+ i := l.i + 1
+ for ; i < len(l.data) && numTailByte(l.data[i]); i++ {
+ }
+ return l.yield(i - l.i), nil
+ case fieldFirstByte(b):
+ i := l.i + 1
+ for ; i < len(l.data) && fieldTailByte(l.data[i]); i++ {
+ }
+ return l.yield(i - l.i), nil
+ }
+ return token{}, l.error("invalid lexeme")
}
-func tokens(data []byte) iter.Seq2[token, error] {
- return func(yield func(token, error) bool) {
- (&lexer{data: data, yieldTok: yield}).tokens()
- }
+func newLexer(data []byte) *lexer {
+ return &lexer{data: data}
}