aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/quasilyte/gogrep/parse.go
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2022-09-05 14:27:54 +0200
committerGitHub <noreply@github.com>2022-09-05 12:27:54 +0000
commitb2f2446b46bf02821d90ebedadae2bf7ae0e880e (patch)
tree923cf42842918d6bebca1d6bbdc08abed54d274d /vendor/github.com/quasilyte/gogrep/parse.go
parente6654faff4bcca4be92e9a8596fd4b77f747c39e (diff)
go.mod, vendor: update (#3358)
* go.mod, vendor: remove unnecessary dependencies Commands: 1. go mod tidy 2. go mod vendor * go.mod, vendor: update cloud.google.com/go Commands: 1. go get -u cloud.google.com/go 2. go mod tidy 3. go mod vendor * go.mod, vendor: update cloud.google.com/* Commands: 1. go get -u cloud.google.com/storage cloud.google.com/logging 2. go mod tidy 3. go mod vendor * go.mod, .golangci.yml, vendor: update *lint* Commands: 1. go get -u golang.org/x/tools github.com/golangci/golangci-lint@v1.47.0 2. go mod tidy 3. go mod vendor 4. edit .golangci.yml to suppress new errors (resolved in the same PR later) * all: fix lint errors hash.go: copy() recommended by gosimple parse.go: ent is never nil verifier.go: signal.Notify() with unbuffered channel is bad. Have no idea why. * .golangci.yml: adjust godot rules check-all is deprecated, but still work if you're hesitating too - I'll remove this commit
Diffstat (limited to 'vendor/github.com/quasilyte/gogrep/parse.go')
-rw-r--r--vendor/github.com/quasilyte/gogrep/parse.go397
1 files changed, 397 insertions, 0 deletions
diff --git a/vendor/github.com/quasilyte/gogrep/parse.go b/vendor/github.com/quasilyte/gogrep/parse.go
new file mode 100644
index 000000000..f70c4a8f4
--- /dev/null
+++ b/vendor/github.com/quasilyte/gogrep/parse.go
@@ -0,0 +1,397 @@
+// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
+// See LICENSE for licensing information
+
+package gogrep
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "strings"
+ "text/template"
+)
+
+func transformSource(expr string) (string, []posOffset, error) {
+ toks, err := tokenize([]byte(expr))
+ if err != nil {
+ return "", nil, fmt.Errorf("cannot tokenize expr: %v", err)
+ }
+ var offs []posOffset
+ lbuf := lineColBuffer{line: 1, col: 1}
+ lastLit := false
+ for _, t := range toks {
+ if lbuf.offs >= t.pos.Offset && lastLit && t.lit != "" {
+ _, _ = lbuf.WriteString(" ")
+ }
+ for lbuf.offs < t.pos.Offset {
+ _, _ = lbuf.WriteString(" ")
+ }
+ if t.lit == "" {
+ _, _ = lbuf.WriteString(t.tok.String())
+ lastLit = false
+ continue
+ }
+ _, _ = lbuf.WriteString(t.lit)
+ lastLit = strings.TrimSpace(t.lit) != ""
+ }
+ // trailing newlines can cause issues with commas
+ return strings.TrimSpace(lbuf.String()), offs, nil
+}
+
+func parseExpr(fset *token.FileSet, expr string) (ast.Node, error) {
+ exprStr, offs, err := transformSource(expr)
+ if err != nil {
+ return nil, err
+ }
+ node, err := parseDetectingNode(fset, exprStr)
+ if err != nil {
+ err = subPosOffsets(err, offs...)
+ return nil, fmt.Errorf("cannot parse expr: %v", err)
+ }
+ return node, nil
+}
+
+type lineColBuffer struct {
+ bytes.Buffer
+ line, col, offs int
+}
+
+func (l *lineColBuffer) WriteString(s string) (n int, err error) {
+ for _, r := range s {
+ if r == '\n' {
+ l.line++
+ l.col = 1
+ } else {
+ l.col++
+ }
+ l.offs++
+ }
+ return l.Buffer.WriteString(s)
+}
+
+var tmplDecl = template.Must(template.New("").Parse(`` +
+ `package p; {{ . }}`))
+
+var tmplBlock = template.Must(template.New("").Parse(`` +
+ `package p; func _() { if true {{ . }} else {} }`))
+
+var tmplExprs = template.Must(template.New("").Parse(`` +
+ `package p; var _ = []interface{}{ {{ . }}, }`))
+
+var tmplStmts = template.Must(template.New("").Parse(`` +
+ `package p; func _() { {{ . }} }`))
+
+var tmplType = template.Must(template.New("").Parse(`` +
+ `package p; var _ {{ . }}`))
+
+var tmplValSpec = template.Must(template.New("").Parse(`` +
+ `package p; var {{ . }}`))
+
+func execTmpl(tmpl *template.Template, src string) string {
+ var buf bytes.Buffer
+ if err := tmpl.Execute(&buf, src); err != nil {
+ panic(err)
+ }
+ return buf.String()
+}
+
+func noBadNodes(node ast.Node) bool {
+ any := false
+ ast.Inspect(node, func(n ast.Node) bool {
+ if any {
+ return false
+ }
+ switch n.(type) {
+ case *ast.BadExpr, *ast.BadDecl:
+ any = true
+ }
+ return true
+ })
+ return !any
+}
+
+func parseType(fset *token.FileSet, src string) (ast.Expr, *ast.File, error) {
+ asType := execTmpl(tmplType, src)
+ f, err := parser.ParseFile(fset, "", asType, 0)
+ if err != nil {
+ err = subPosOffsets(err, posOffset{1, 1, 17})
+ return nil, nil, err
+ }
+ vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
+ return vs.Type, f, nil
+}
+
+// parseDetectingNode tries its best to parse the ast.Node contained in src, as
+// one of: *ast.File, ast.Decl, ast.Expr, ast.Stmt, *ast.ValueSpec.
+// It also returns the *ast.File used for the parsing, so that the returned node
+// can be easily type-checked.
+func parseDetectingNode(fset *token.FileSet, src string) (ast.Node, error) {
+ file := fset.AddFile("", fset.Base(), len(src))
+ scan := scanner.Scanner{}
+ scan.Init(file, []byte(src), nil, 0)
+ if _, tok, _ := scan.Scan(); tok == token.EOF {
+ return nil, fmt.Errorf("empty source code")
+ }
+ var mainErr error
+
+ // Some adhoc patterns first.
+ if strings.HasPrefix(src, "range ") {
+ e, err := parser.ParseExpr(src[len("range "):])
+ if err == nil && noBadNodes(e) {
+ return &rangeClause{X: e}, nil
+ }
+ }
+ if strings.HasPrefix(src, "for ") && !strings.HasSuffix(src, "}") {
+ asStmts := execTmpl(tmplStmts, src+"{}")
+ f, err := parser.ParseFile(fset, "", asStmts, 0)
+ if err == nil && noBadNodes(f) {
+ bl := f.Decls[0].(*ast.FuncDecl).Body
+ if len(bl.List) == 1 {
+ return &rangeHeader{Node: bl.List[0].(*ast.RangeStmt)}, nil
+ }
+ }
+ }
+
+ // try as a block; otherwise blocks might be mistaken for composite
+ // literals further below
+ asBlock := execTmpl(tmplBlock, src)
+ if f, err := parser.ParseFile(fset, "", asBlock, 0); err == nil && noBadNodes(f) {
+ bl := f.Decls[0].(*ast.FuncDecl).Body
+ if len(bl.List) == 1 {
+ ifs := bl.List[0].(*ast.IfStmt)
+ return ifs.Body, nil
+ }
+ }
+
+ // then as value expressions
+ asExprs := execTmpl(tmplExprs, src)
+ if f, err := parser.ParseFile(fset, "", asExprs, 0); err == nil && noBadNodes(f) {
+ vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
+ cl := vs.Values[0].(*ast.CompositeLit)
+ if len(cl.Elts) == 1 {
+ return cl.Elts[0], nil
+ }
+ return ExprSlice(cl.Elts), nil
+ }
+
+ // then try as statements
+ asStmts := execTmpl(tmplStmts, src)
+ f, err := parser.ParseFile(fset, "", asStmts, 0)
+ if err == nil && noBadNodes(f) {
+ bl := f.Decls[0].(*ast.FuncDecl).Body
+ if len(bl.List) == 1 {
+ return bl.List[0], nil
+ }
+ return stmtSlice(bl.List), nil
+ }
+ // Statements is what covers most cases, so it will give
+ // the best overall error message. Show positions
+ // relative to where the user's code is put in the
+ // template.
+ mainErr = subPosOffsets(err, posOffset{1, 1, 22})
+
+ // try as a single declaration, or many
+ asDecl := execTmpl(tmplDecl, src)
+ if f, err := parser.ParseFile(fset, "", asDecl, 0); err == nil && noBadNodes(f) {
+ if len(f.Decls) == 1 {
+ return f.Decls[0], nil
+ }
+ return declSlice(f.Decls), nil
+ }
+
+ // try as a whole file
+ if f, err := parser.ParseFile(fset, "", src, 0); err == nil && noBadNodes(f) {
+ return f, nil
+ }
+
+ // type expressions not yet picked up, for e.g. chans and interfaces
+ if typ, f, err := parseType(fset, src); err == nil && noBadNodes(f) {
+ return typ, nil
+ }
+
+ // value specs
+ asValSpec := execTmpl(tmplValSpec, src)
+ if f, err := parser.ParseFile(fset, "", asValSpec, 0); err == nil && noBadNodes(f) {
+ decl := f.Decls[0].(*ast.GenDecl)
+ if len(decl.Specs) != 0 {
+ vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
+ return vs, nil
+ }
+ }
+
+ return nil, mainErr
+}
+
+type posOffset struct {
+ atLine, atCol int
+ offset int
+}
+
+func subPosOffsets(err error, offs ...posOffset) error {
+ list, ok := err.(scanner.ErrorList)
+ if !ok {
+ return err
+ }
+ for i, err := range list {
+ for _, off := range offs {
+ if err.Pos.Line != off.atLine {
+ continue
+ }
+ if err.Pos.Column < off.atCol {
+ continue
+ }
+ err.Pos.Column -= off.offset
+ }
+ list[i] = err
+ }
+ return list
+}
+
+type fullToken struct {
+ pos token.Position
+ tok token.Token
+ lit string
+}
+
+type caseStatus uint
+
+const (
+ caseNone caseStatus = iota
+ caseNeedBlock
+ caseHere
+)
+
+func tokenize(src []byte) ([]fullToken, error) {
+ var s scanner.Scanner
+ fset := token.NewFileSet()
+ file := fset.AddFile("", fset.Base(), len(src))
+
+ var err error
+ onError := func(pos token.Position, msg string) {
+ switch msg { // allow certain extra chars
+ case `illegal character U+0024 '$'`:
+ case `illegal character U+007E '~'`:
+ default:
+ err = fmt.Errorf("%v: %s", pos, msg)
+ }
+ }
+
+ // we will modify the input source under the scanner's nose to
+ // enable some features such as regexes.
+ s.Init(file, src, onError, scanner.ScanComments)
+
+ next := func() fullToken {
+ pos, tok, lit := s.Scan()
+ return fullToken{fset.Position(pos), tok, lit}
+ }
+
+ caseStat := caseNone
+
+ var toks []fullToken
+ for t := next(); t.tok != token.EOF; t = next() {
+ switch t.lit {
+ case "$": // continues below
+ case "switch", "select", "case":
+ if t.lit == "case" {
+ caseStat = caseNone
+ } else {
+ caseStat = caseNeedBlock
+ }
+ fallthrough
+ default: // regular Go code
+ if t.tok == token.LBRACE && caseStat == caseNeedBlock {
+ caseStat = caseHere
+ }
+ toks = append(toks, t)
+ continue
+ }
+ wt, err := tokenizeWildcard(t.pos, next)
+ if err != nil {
+ return nil, err
+ }
+ if caseStat == caseHere {
+ toks = append(toks, fullToken{wt.pos, token.IDENT, "case"})
+ }
+ toks = append(toks, wt)
+ if caseStat == caseHere {
+ toks = append(toks,
+ fullToken{wt.pos, token.COLON, ""},
+ fullToken{wt.pos, token.IDENT, "gogrep_body"})
+ }
+ }
+ return toks, err
+}
+
+type varInfo struct {
+ Name string
+ Seq bool
+}
+
+func tokenizeWildcard(pos token.Position, next func() fullToken) (fullToken, error) {
+ t := next()
+ any := false
+ if t.tok == token.MUL {
+ t = next()
+ any = true
+ }
+ wildName := encodeWildName(t.lit, any)
+ wt := fullToken{pos, token.IDENT, wildName}
+ if t.tok != token.IDENT {
+ return wt, fmt.Errorf("%v: $ must be followed by ident, got %v",
+ t.pos, t.tok)
+ }
+ return wt, nil
+}
+
+const wildSeparator = "ᐸᐳ"
+
+func isWildName(s string) bool {
+ return strings.HasPrefix(s, wildSeparator)
+}
+
+func encodeWildName(name string, any bool) string {
+ suffix := "v"
+ if any {
+ suffix = "a"
+ }
+ return wildSeparator + name + wildSeparator + suffix
+}
+
+func decodeWildName(s string) varInfo {
+ s = s[len(wildSeparator):]
+ nameEnd := strings.Index(s, wildSeparator)
+ name := s[:nameEnd+0]
+ s = s[nameEnd:]
+ s = s[len(wildSeparator):]
+ kind := s
+ return varInfo{Name: name, Seq: kind == "a"}
+}
+
+func decodeWildNode(n ast.Node) varInfo {
+ switch n := n.(type) {
+ case *ast.ExprStmt:
+ return decodeWildNode(n.X)
+ case *ast.Ident:
+ if isWildName(n.Name) {
+ return decodeWildName(n.Name)
+ }
+ }
+ return varInfo{}
+}
+
+type rangeClause struct {
+ X ast.Expr
+}
+
+type rangeHeader struct {
+ Node *ast.RangeStmt
+}
+
+func (*rangeClause) Pos() token.Pos { return 0 }
+func (*rangeClause) End() token.Pos { return 0 }
+
+func (*rangeHeader) Pos() token.Pos { return 0 }
+func (*rangeHeader) End() token.Pos { return 0 }