Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 55 additions & 19 deletions pattern/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ 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

input string
start int
pos int
width int
items chan item

yield func(item) bool
}

type itemType int
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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, '"')
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand Down
5 changes: 1 addition & 4 deletions pattern/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"go/token"
"go/types"
"reflect"

"golang.org/x/tools/go/ast/astutil"
)

var tokensByString = map[string]Token{
Expand Down Expand Up @@ -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:
Expand Down
42 changes: 23 additions & 19 deletions pattern/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
"fmt"
"go/ast"
"go/token"
"iter"
"log"

Check failure on line 9 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (windows-latest, 1.23.x, gotypesalias=1)

"log" imported and not used

Check failure on line 9 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (ubuntu-latest, 1.23.x, gotypesalias=0)

"log" imported and not used

Check failure on line 9 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (windows-latest, 1.23.x, gotypesalias=0)

"log" imported and not used

Check failure on line 9 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (ubuntu-latest, 1.23.x, gotypesalias=1)

"log" imported and not used
"reflect"
"strings"
"time"

Check failure on line 12 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (windows-latest, 1.23.x, gotypesalias=1)

"time" imported and not used

Check failure on line 12 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (ubuntu-latest, 1.23.x, gotypesalias=0)

"time" imported and not used

Check failure on line 12 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (windows-latest, 1.23.x, gotypesalias=0)

"time" imported and not used

Check failure on line 12 in pattern/parser.go

View workflow job for this annotation

GitHub Actions / Run CI (ubuntu-latest, 1.23.x, gotypesalias=1)

"time" imported and not used
)

type Pattern struct {
Expand Down Expand Up @@ -339,10 +342,10 @@
// 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
}
Expand All @@ -360,26 +363,27 @@
}

func (p *Parser) Parse(s string) (Pattern, error) {
f := token.NewFileSet().AddFile("<input>", -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("<input>", -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)
}

Expand Down Expand Up @@ -417,7 +421,7 @@
return n
}
var ok bool
p.cur, ok = <-p.items
p.cur, ok = p.nextItem()
if !ok {
p.cur = item{typ: eof}
}
Expand Down Expand Up @@ -455,7 +459,7 @@
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)
}

Expand Down
37 changes: 37 additions & 0 deletions pattern/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
2 changes: 1 addition & 1 deletion quickfix/qf1012/qf1012.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Loading