diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-10-30 18:45:11 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-10-30 18:50:19 -0700 |
| commit | aa8a935f620a053cc9c3c65587b433f3e8d3a6e9 (patch) | |
| tree | a786b3174c1a901f19fc10485c4856d60ef93d6b | |
| parent | 74235daa0ae145c96b63c980e98c9c169f5c0007 (diff) | |
| download | ccl-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.go | 13 | ||||
| -rw-r--r-- | ccl_test.go | 7 | ||||
| -rw-r--r-- | lexer.go | 112 |
3 files changed, 54 insertions, 78 deletions
@@ -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) } } @@ -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} } |
