Skip to content

Commit cd36a9f

Browse files
authored
Merge pull request #50 from yaacov/review-round2-fixes
fixes
2 parents 1889d06 + 48cb61c commit cd36a9f

33 files changed

Lines changed: 826 additions & 120 deletions

v6/pkg/parser/ast.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ func (op OpType) String() string {
146146

147147
// Node represents a generic AST node
148148
type Node struct {
149-
Kind NodeKind
149+
Kind NodeKind
150+
// Value must be an immutable type (string, float64, bool, time.Time, or nil).
151+
// Clone performs a shallow copy of this field.
150152
Value interface{}
151153
Operator OpType
152154
Left *Node

v6/pkg/parser/bench_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package parser
2+
3+
import "testing"
4+
5+
func BenchmarkParseSimple(b *testing.B) {
6+
for i := 0; i < b.N; i++ {
7+
if _, err := Parse("name = 'alice'"); err != nil {
8+
b.Fatal(err)
9+
}
10+
}
11+
}
12+
13+
func BenchmarkParseComplex(b *testing.B) {
14+
for i := 0; i < b.N; i++ {
15+
if _, err := Parse("(age > 25 and city = 'rome') or (status in ['active', 'pending'] and price between 10 and 100)"); err != nil {
16+
b.Fatal(err)
17+
}
18+
}
19+
}
20+
21+
func BenchmarkLexerSimple(b *testing.B) {
22+
for i := 0; i < b.N; i++ {
23+
l := NewLexer("name = 'alice'")
24+
if err := l.Tokenize(); err != nil {
25+
b.Fatal(err)
26+
}
27+
}
28+
}
29+
30+
func BenchmarkLexerComplex(b *testing.B) {
31+
for i := 0; i < b.N; i++ {
32+
l := NewLexer("(age > 25 and city = 'rome') or (status in ['active', 'pending'] and price between 10 and 100)")
33+
if err := l.Tokenize(); err != nil {
34+
b.Fatal(err)
35+
}
36+
}
37+
}

v6/pkg/parser/fuzz_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package parser
2+
3+
import "testing"
4+
5+
func FuzzParse(f *testing.F) {
6+
f.Add("name = 'alice'")
7+
f.Add("age > 25 and city = 'rome'")
8+
f.Add("status in ['active', 'pending']")
9+
f.Add("price between 10 and 100")
10+
f.Add("not (deleted = true)")
11+
f.Add("title like '%book%'")
12+
f.Add("created_at > 2023-01-01")
13+
f.Add("(a = 1 or b = 2) and c = 3")
14+
f.Add("")
15+
f.Add(" ")
16+
f.Add("名前 = 'test'")
17+
f.Add("name = '🎉'")
18+
f.Add("[1, 2, 3]")
19+
f.Add("a ~= '.*'")
20+
21+
f.Fuzz(func(t *testing.T, input string) {
22+
// Should not panic regardless of input
23+
_, _ = Parse(input)
24+
})
25+
}

v6/pkg/parser/lexer.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ const (
2121

2222
// Lexer represents a lexical analyzer for TSL
2323
type Lexer struct {
24-
input string
25-
pos int // current position
26-
start int // start of current token
24+
input string // original input string
25+
runes []rune // input as runes for proper UTF-8 handling
26+
pos int // current position (rune index)
27+
start int // start of current token (rune index)
2728
tokens []Token
2829
current int // current token index
2930
}
@@ -58,6 +59,7 @@ var (
5859
func NewLexer(input string) *Lexer {
5960
return &Lexer{
6061
input: input,
62+
runes: []rune(input),
6163
pos: 0,
6264
start: 0,
6365
tokens: make([]Token, 0),
@@ -80,38 +82,38 @@ func (l *Lexer) Tokenize() error {
8082

8183
// isAtEnd checks if we're at the end of input
8284
func (l *Lexer) isAtEnd() bool {
83-
return l.pos >= len(l.input)
85+
return l.pos >= len(l.runes)
8486
}
8587

8688
// peek returns the current character without advancing
8789
func (l *Lexer) peek() rune {
8890
if l.isAtEnd() {
8991
return 0
9092
}
91-
return rune(l.input[l.pos])
93+
return l.runes[l.pos]
9294
}
9395

9496
// peekNext returns the next character without advancing
9597
func (l *Lexer) peekNext() rune {
96-
if l.pos+1 >= len(l.input) {
98+
if l.pos+1 >= len(l.runes) {
9799
return 0
98100
}
99-
return rune(l.input[l.pos+1])
101+
return l.runes[l.pos+1]
100102
}
101103

102104
// advance consumes and returns the current character
103105
func (l *Lexer) advance() rune {
104106
if l.isAtEnd() {
105107
return 0
106108
}
107-
c := rune(l.input[l.pos])
109+
c := l.runes[l.pos]
108110
l.pos++
109111
return c
110112
}
111113

112114
// match checks if current character matches expected and advances if so
113115
func (l *Lexer) match(expected rune) bool {
114-
if l.isAtEnd() || rune(l.input[l.pos]) != expected {
116+
if l.isAtEnd() || l.runes[l.pos] != expected {
115117
return false
116118
}
117119
l.pos++
@@ -227,11 +229,11 @@ func (l *Lexer) scanToken() error {
227229
// isDateTimePattern checks if the current position starts a date or time pattern
228230
func (l *Lexer) isDateTimePattern() bool {
229231
// Look ahead to see if this matches a date or RFC3339 pattern
230-
remaining := l.input[l.pos:]
232+
remaining := l.runes[l.pos:]
231233

232234
// Try to match date pattern (YYYY-MM-DD)
233235
if len(remaining) >= 10 {
234-
dateCandidate := remaining[:10]
236+
dateCandidate := string(remaining[:10])
235237
if datePattern.MatchString(dateCandidate) {
236238
// Check if it continues as RFC3339 (has T after date)
237239
if len(remaining) > 10 && remaining[10] == 'T' {
@@ -240,12 +242,12 @@ func (l *Lexer) isDateTimePattern() bool {
240242
c := remaining[i]
241243
if c == ' ' || c == '\t' || c == '\n' || c == ')' || c == ',' {
242244
// End of potential timestamp
243-
candidate := remaining[:i]
245+
candidate := string(remaining[:i])
244246
return rfc3339Pattern.MatchString(candidate)
245247
}
246248
}
247249
// Check the whole remaining string
248-
return rfc3339Pattern.MatchString(remaining)
250+
return rfc3339Pattern.MatchString(string(remaining))
249251
}
250252
return true // Just a date
251253
}
@@ -267,7 +269,7 @@ func (l *Lexer) scanDateTime() error {
267269
l.advance()
268270
}
269271

270-
value := l.input[start:l.pos]
272+
value := string(l.runes[start:l.pos])
271273

272274
// Check if it's a date or RFC3339 format
273275
if rfc3339Pattern.MatchString(value) {
@@ -391,7 +393,7 @@ func (l *Lexer) scanNumber() error {
391393
}
392394
}
393395

394-
value := l.input[start:l.pos]
396+
value := string(l.runes[start:l.pos])
395397
l.addToken(NUMERIC_LITERAL, value)
396398
return nil
397399
}
@@ -430,7 +432,7 @@ func (l *Lexer) scanIdentifier() error {
430432
}
431433
}
432434

433-
value := l.input[start:l.pos]
435+
value := string(l.runes[start:l.pos])
434436

435437
// Check if it's a keyword (case-insensitive)
436438
lowerValue := strings.ToLower(value)
@@ -446,7 +448,7 @@ func (l *Lexer) scanIdentifier() error {
446448
// NextToken returns the next token for the parser
447449
func (l *Lexer) NextToken() Token {
448450
if l.current >= len(l.tokens) {
449-
return Token{Type: EOF, Value: "", Position: len(l.input)}
451+
return Token{Type: EOF, Value: "", Position: len(l.runes)}
450452
}
451453
token := l.tokens[l.current]
452454
l.current++
@@ -456,7 +458,7 @@ func (l *Lexer) NextToken() Token {
456458
// Peek returns the current token without advancing
457459
func (l *Lexer) PeekToken() Token {
458460
if l.current >= len(l.tokens) {
459-
return Token{Type: EOF, Value: "", Position: len(l.input)}
461+
return Token{Type: EOF, Value: "", Position: len(l.runes)}
460462
}
461463
return l.tokens[l.current]
462464
}

0 commit comments

Comments
 (0)