diff --git a/pattern/lexer.go b/pattern/lexer.go
index ec7735db3..8ab8f5696 100644
--- a/pattern/lexer.go
+++ b/pattern/lexer.go
@@ -3,10 +3,24 @@ package pattern
import (
"fmt"
"go/token"
+ "iter"
"unicode"
"unicode/utf8"
)
+// lex returns the sequence of tokens in the input.
+func lex(f *token.File, input string) iter.Seq[item] {
+ return func(yield func(item) bool) {
+ lex := &lexer{
+ f: f,
+ input: input,
+ yield: yield,
+ }
+ lex.run()
+ }
+}
+
+// lexer holds the state of a single [lex] iteration.
type lexer struct {
f *token.File
@@ -14,7 +28,8 @@ type lexer struct {
start int
pos int
width int
- items chan item
+
+ yield func(item) bool
}
type itemType int
@@ -79,40 +94,55 @@ func (l *lexer) run() {
for state := lexStart; state != nil; {
state = state(l)
}
- close(l.items)
}
-func (l *lexer) emitValue(t itemType, value string) {
- l.items <- item{t, value, l.start}
+func (l *lexer) emitValue(t itemType, value string) bool {
+ ok := l.yield(item{t, value, l.start})
l.start = l.pos
+ return ok
}
-func (l *lexer) emit(t itemType) {
- l.items <- item{t, l.input[l.start:l.pos], l.start}
+func (l *lexer) emit(t itemType) bool {
+ ok := l.yield(item{t, l.input[l.start:l.pos], l.start})
l.start = l.pos
+ return ok
}
func lexStart(l *lexer) stateFn {
switch r := l.next(); {
case r == eof:
- l.emit(itemEOF)
+ _ = l.emit(itemEOF)
return nil
case unicode.IsSpace(r):
l.ignore()
case r == '(':
- l.emit(itemLeftParen)
+ if !l.emit(itemLeftParen) {
+ return nil
+ }
case r == ')':
- l.emit(itemRightParen)
+ if !l.emit(itemRightParen) {
+ return nil
+ }
case r == '[':
- l.emit(itemLeftBracket)
+ if !l.emit(itemLeftBracket) {
+ return nil
+ }
case r == ']':
- l.emit(itemRightBracket)
+ if !l.emit(itemRightBracket) {
+ return nil
+ }
case r == '@':
- l.emit(itemAt)
+ if !l.emit(itemAt) {
+ return nil
+ }
case r == ':':
- l.emit(itemColon)
+ if !l.emit(itemColon) {
+ return nil
+ }
case r == '_':
- l.emit(itemBlank)
+ if !l.emit(itemBlank) {
+ return nil
+ }
case r == '"':
l.backup()
return lexString
@@ -154,11 +184,11 @@ func (l *lexer) backup() {
func (l *lexer) errorf(format string, args ...any) stateFn {
// TODO(dh): emit position information in errors
- l.items <- item{
+ _ = l.yield(item{
itemError,
fmt.Sprintf(format, args...),
l.start,
- }
+ })
return nil
}
@@ -179,7 +209,9 @@ func lexString(l *lexer) stateFn {
return l.errorf("unterminated string")
case '"':
if !escape {
- l.emitValue(itemString, string(runes))
+ if !l.emitValue(itemString, string(runes)) {
+ return nil
+ }
return lexStart
} else {
runes = append(runes, '"')
@@ -203,7 +235,9 @@ func lexType(l *lexer) stateFn {
for {
if !isAlphaNumeric(l.next()) {
l.backup()
- l.emit(itemTypeName)
+ if !l.emit(itemTypeName) {
+ return nil
+ }
return lexStart
}
}
@@ -214,7 +248,9 @@ func lexVariable(l *lexer) stateFn {
for {
if !isAlphaNumeric(l.next()) {
l.backup()
- l.emit(itemVariable)
+ if !l.emit(itemVariable) {
+ return nil
+ }
return lexStart
}
}
diff --git a/pattern/match.go b/pattern/match.go
index d90bcae6c..5ccb8d321 100644
--- a/pattern/match.go
+++ b/pattern/match.go
@@ -6,8 +6,6 @@ import (
"go/token"
"go/types"
"reflect"
-
- "golang.org/x/tools/go/ast/astutil"
)
var tokensByString = map[string]Token{
@@ -569,9 +567,8 @@ func (fn Symbol) Match(m *Matcher, node any) (any, bool) {
case *ast.IndexListExpr:
fun = idx.X
}
- fun = astutil.Unparen(fun)
- switch fun := fun.(type) {
+ switch fun := ast.Unparen(fun).(type) {
case *ast.Ident:
obj = m.TypesInfo.ObjectOf(fun)
case *ast.SelectorExpr:
diff --git a/pattern/parser.go b/pattern/parser.go
index 26214babf..27d7ca977 100644
--- a/pattern/parser.go
+++ b/pattern/parser.go
@@ -5,8 +5,11 @@ import (
"fmt"
"go/ast"
"go/token"
+ "iter"
+ "log"
"reflect"
"strings"
+ "time"
)
type Pattern struct {
@@ -339,10 +342,10 @@ type Parser struct {
// Allow nodes that rely on type information
AllowTypeInfo bool
- lex *lexer
- cur item
- last *item
- items chan item
+ f *token.File
+ cur item
+ last *item
+ nextItem func() (item, bool)
bindings map[string]int
}
@@ -360,26 +363,27 @@ func (p *Parser) bindingIndex(name string) int {
}
func (p *Parser) Parse(s string) (Pattern, error) {
+ f := token.NewFileSet().AddFile("", -1, len(s))
+
+ // Run the lexer iterator as a coroutine.
+ // The parser will call 'next' to consume each item.
+ // After the parser returns, we must call 'stop' to
+ // terminate the coroutine.
+ next, stop := iter.Pull(lex(f, s))
+ defer stop()
+
p.cur = item{}
p.last = nil
- p.items = nil
+ p.f = f
+ p.nextItem = next
- fset := token.NewFileSet()
- p.lex = &lexer{
- f: fset.AddFile("", -1, len(s)),
- input: s,
- items: make(chan item),
- }
- go p.lex.run()
- p.items = p.lex.items
+ // Parse.
root, err := p.node()
if err != nil {
- // drain lexer if parsing failed
- for range p.lex.items {
- }
return Pattern{}, err
}
- if item := <-p.lex.items; item.typ != itemEOF {
+ // Consume final EOF token.
+ if item, ok := next(); !ok || item.typ != itemEOF {
return Pattern{}, fmt.Errorf("unexpected token %s after end of pattern", item.typ)
}
@@ -417,7 +421,7 @@ func (p *Parser) next() item {
return n
}
var ok bool
- p.cur, ok = <-p.items
+ p.cur, ok = p.nextItem()
if !ok {
p.cur = item{typ: eof}
}
@@ -455,7 +459,7 @@ func (p *Parser) unexpectedToken(valid string) error {
got = "'" + p.cur.typ.String() + "'"
}
- pos := p.lex.f.Position(token.Pos(p.cur.pos))
+ pos := p.f.Position(token.Pos(p.cur.pos))
return fmt.Errorf("%s: expected %s, found %s", pos, valid, got)
}
diff --git a/pattern/parser_test.go b/pattern/parser_test.go
index f15743275..eb025d0db 100644
--- a/pattern/parser_test.go
+++ b/pattern/parser_test.go
@@ -245,3 +245,40 @@ func TestCollectSymbols(t *testing.T) {
}
}
}
+
+func BenchmarkParser(b *testing.B) {
+ const input = `
+ (CallExpr
+ (Symbol
+ name@(Or
+ "math/rand.Int31n"
+ "math/rand.Int63n"
+ "math/rand.Intn"
+ "(*math/rand.Rand).Int31n"
+ "(*math/rand.Rand).Int63n"
+ "(*math/rand.Rand).Intn"
+
+ "math/rand/v2.Int32N"
+ "math/rand/v2.Int64N"
+ "math/rand/v2.IntN"
+ "math/rand/v2.N"
+ "math/rand/v2.Uint32N"
+ "math/rand/v2.Uint64N"
+ "math/rand/v2.UintN"
+
+ "(*math/rand/v2.Rand).Int32N"
+ "(*math/rand/v2.Rand).Int64N"
+ "(*math/rand/v2.Rand).IntN"
+ "(*math/rand/v2.Rand).Uint32N"
+ "(*math/rand/v2.Rand).Uint64N"
+ "(*math/rand/v2.Rand).UintN"))
+ [(IntegerLiteral "1")])`
+
+ for range b.N {
+ p := &Parser{AllowTypeInfo: true}
+ _, err := p.Parse(input)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/quickfix/qf1012/qf1012.go b/quickfix/qf1012/qf1012.go
index eb89e65df..45784d94e 100644
--- a/quickfix/qf1012/qf1012.go
+++ b/quickfix/qf1012/qf1012.go
@@ -66,7 +66,7 @@ func run(pass *analysis.Pass) (any, error) {
// is a named non-interface type, since the pointer
// has a larger method set (https://staticcheck.dev/issues/1097).
// We assume the receiver expression is addressable
- // since otherwise thre code wouldn't compile.
+ // since otherwise the code wouldn't compile.
if _, ok := types.Unalias(recvT).(*types.Named); ok && !types.IsInterface(recvT) {
recvT = types.NewPointer(recvT)
recv = &ast.UnaryExpr{Op: token.AND, X: recv}