1// Copyright 2021 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package main 6 7import ( 8 "fmt" 9 "strings" 10) 11 12// exprParser is a //go:build expression parser and evaluator. 13// The parser is a trivial precedence-based parser which is still 14// almost overkill for these very simple expressions. 15type exprParser struct { 16 x string 17 t exprToken // upcoming token 18} 19 20// val is the value type result of parsing. 21// We don't keep a parse tree, just the value of the expression. 22type val bool 23 24// exprToken describes a single token in the input. 25// Prefix operators define a prefix func that parses the 26// upcoming value. Binary operators define an infix func 27// that combines two values according to the operator. 28// In that case, the parsing loop parses the two values. 29type exprToken struct { 30 tok string 31 prec int 32 prefix func(*exprParser) val 33 infix func(val, val) val 34} 35 36var exprTokens []exprToken 37 38func init() { // init to break init cycle 39 exprTokens = []exprToken{ 40 {tok: "&&", prec: 1, infix: func(x, y val) val { return x && y }}, 41 {tok: "||", prec: 2, infix: func(x, y val) val { return x || y }}, 42 {tok: "!", prec: 3, prefix: (*exprParser).not}, 43 {tok: "(", prec: 3, prefix: (*exprParser).paren}, 44 {tok: ")"}, 45 } 46} 47 48// matchexpr parses and evaluates the //go:build expression x. 49func matchexpr(x string) (matched bool, err error) { 50 defer func() { 51 if e := recover(); e != nil { 52 matched = false 53 err = fmt.Errorf("parsing //go:build line: %v", e) 54 } 55 }() 56 57 p := &exprParser{x: x} 58 p.next() 59 v := p.parse(0) 60 if p.t.tok != "end of expression" { 61 panic("unexpected " + p.t.tok) 62 } 63 return bool(v), nil 64} 65 66// parse parses an expression, including binary operators at precedence >= prec. 67func (p *exprParser) parse(prec int) val { 68 if p.t.prefix == nil { 69 panic("unexpected " + p.t.tok) 70 } 71 v := p.t.prefix(p) 72 for p.t.prec >= prec && p.t.infix != nil { 73 t := p.t 74 p.next() 75 v = t.infix(v, p.parse(t.prec+1)) 76 } 77 return v 78} 79 80// not is the prefix parser for a ! token. 81func (p *exprParser) not() val { 82 p.next() 83 return !p.parse(100) 84} 85 86// paren is the prefix parser for a ( token. 87func (p *exprParser) paren() val { 88 p.next() 89 v := p.parse(0) 90 if p.t.tok != ")" { 91 panic("missing )") 92 } 93 p.next() 94 return v 95} 96 97// next advances the parser to the next token, 98// leaving the token in p.t. 99func (p *exprParser) next() { 100 p.x = strings.TrimSpace(p.x) 101 if p.x == "" { 102 p.t = exprToken{tok: "end of expression"} 103 return 104 } 105 for _, t := range exprTokens { 106 if strings.HasPrefix(p.x, t.tok) { 107 p.x = p.x[len(t.tok):] 108 p.t = t 109 return 110 } 111 } 112 113 i := 0 114 for i < len(p.x) && validtag(p.x[i]) { 115 i++ 116 } 117 if i == 0 { 118 panic(fmt.Sprintf("syntax error near %#q", rune(p.x[i]))) 119 } 120 tag := p.x[:i] 121 p.x = p.x[i:] 122 p.t = exprToken{ 123 tok: "tag", 124 prefix: func(p *exprParser) val { 125 p.next() 126 return val(matchtag(tag)) 127 }, 128 } 129} 130 131func validtag(c byte) bool { 132 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '.' || c == '_' 133} 134