// Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "strings" ) // exprParser is a //go:build expression parser and evaluator. // The parser is a trivial precedence-based parser which is still // almost overkill for these very simple expressions. type exprParser struct { x string t exprToken // upcoming token } // val is the value type result of parsing. // We don't keep a parse tree, just the value of the expression. type val bool // exprToken describes a single token in the input. // Prefix operators define a prefix func that parses the // upcoming value. Binary operators define an infix func // that combines two values according to the operator. // In that case, the parsing loop parses the two values. type exprToken struct { tok string prec int prefix func(*exprParser) val infix func(val, val) val } var exprTokens []exprToken func init() { // init to break init cycle exprTokens = []exprToken{ {tok: "&&", prec: 1, infix: func(x, y val) val { return x && y }}, {tok: "||", prec: 2, infix: func(x, y val) val { return x || y }}, {tok: "!", prec: 3, prefix: (*exprParser).not}, {tok: "(", prec: 3, prefix: (*exprParser).paren}, {tok: ")"}, } } // matchexpr parses and evaluates the //go:build expression x. func matchexpr(x string) (matched bool, err error) { defer func() { if e := recover(); e != nil { matched = false err = fmt.Errorf("parsing //go:build line: %v", e) } }() p := &exprParser{x: x} p.next() v := p.parse(0) if p.t.tok != "end of expression" { panic("unexpected " + p.t.tok) } return bool(v), nil } // parse parses an expression, including binary operators at precedence >= prec. func (p *exprParser) parse(prec int) val { if p.t.prefix == nil { panic("unexpected " + p.t.tok) } v := p.t.prefix(p) for p.t.prec >= prec && p.t.infix != nil { t := p.t p.next() v = t.infix(v, p.parse(t.prec+1)) } return v } // not is the prefix parser for a ! token. func (p *exprParser) not() val { p.next() return !p.parse(100) } // paren is the prefix parser for a ( token. func (p *exprParser) paren() val { p.next() v := p.parse(0) if p.t.tok != ")" { panic("missing )") } p.next() return v } // next advances the parser to the next token, // leaving the token in p.t. func (p *exprParser) next() { p.x = strings.TrimSpace(p.x) if p.x == "" { p.t = exprToken{tok: "end of expression"} return } for _, t := range exprTokens { if strings.HasPrefix(p.x, t.tok) { p.x = p.x[len(t.tok):] p.t = t return } } i := 0 for i < len(p.x) && validtag(p.x[i]) { i++ } if i == 0 { panic(fmt.Sprintf("syntax error near %#q", rune(p.x[i]))) } tag := p.x[:i] p.x = p.x[i:] p.t = exprToken{ tok: "tag", prefix: func(p *exprParser) val { p.next() return val(matchtag(tag)) }, } } func validtag(c byte) bool { return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '.' || c == '_' }