// Copyright 2020 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 constraint implements parsing and evaluation of build constraint lines.// See https://golang.org/cmd/go/#hdr-Build_constraints for documentation about build constraints themselves.//// This package parses both the original “// +build” syntax and the “//go:build” syntax that was added in Go 1.17.// See https://golang.org/design/draft-gobuild for details about the “//go:build” syntax.
package constraintimport ()// An Expr is a build tag constraint expression.// The underlying concrete type is *[AndExpr], *[OrExpr], *[NotExpr], or *[TagExpr].typeExprinterface {// String returns the string form of the expression, // using the boolean syntax used in //go:build lines.String() string// Eval reports whether the expression evaluates to true. // It calls ok(tag) as needed to find out whether a given build tag // is satisfied by the current build configuration.Eval(ok func(tag string) bool) bool// The presence of an isExpr method explicitly marks the type as an Expr. // Only implementations in this package should be used as Exprs. isExpr()}// A TagExpr is an [Expr] for the single tag Tag.typeTagExprstruct { Tag string// for example, “linux” or “cgo”}func ( *TagExpr) () {}func ( *TagExpr) ( func( string) bool) bool {return (.Tag)}func ( *TagExpr) () string {return .Tag}func tag( string) Expr { return &TagExpr{} }// A NotExpr represents the expression !X (the negation of X).typeNotExprstruct { X Expr}func ( *NotExpr) () {}func ( *NotExpr) ( func( string) bool) bool {return !.X.Eval()}func ( *NotExpr) () string { := .X.String()switch .X.(type) {case *AndExpr, *OrExpr: = "(" + + ")" }return"!" + }func not( Expr) Expr { return &NotExpr{} }// An AndExpr represents the expression X && Y.typeAndExprstruct { X, Y Expr}func ( *AndExpr) () {}func ( *AndExpr) ( func( string) bool) bool {// Note: Eval both, to make sure ok func observes all tags. := .X.Eval() := .Y.Eval()return && }func ( *AndExpr) () string {returnandArg(.X) + " && " + andArg(.Y)}func andArg( Expr) string { := .String()if , := .(*OrExpr); { = "(" + + ")" }return}func and(, Expr) Expr {return &AndExpr{, }}// An OrExpr represents the expression X || Y.typeOrExprstruct { X, Y Expr}func ( *OrExpr) () {}func ( *OrExpr) ( func( string) bool) bool {// Note: Eval both, to make sure ok func observes all tags. := .X.Eval() := .Y.Eval()return || }func ( *OrExpr) () string {returnorArg(.X) + " || " + orArg(.Y)}func orArg( Expr) string { := .String()if , := .(*AndExpr); { = "(" + + ")" }return}func or(, Expr) Expr {return &OrExpr{, }}// A SyntaxError reports a syntax error in a parsed build expression.typeSyntaxErrorstruct { Offset int// byte offset in input where error was detected Err string// description of error}func ( *SyntaxError) () string {return .Err}var errNotConstraint = errors.New("not a build constraint")// Parse parses a single build constraint line of the form “//go:build ...” or “// +build ...”// and returns the corresponding boolean expression.func ( string) (Expr, error) {if , := splitGoBuild(); {returnparseExpr() }if , := splitPlusBuild(); {returnparsePlusBuildExpr(), nil }returnnil, errNotConstraint}// IsGoBuild reports whether the line of text is a “//go:build” constraint.// It only checks the prefix of the text, not that the expression itself parses.func ( string) bool { , := splitGoBuild()return}// splitGoBuild splits apart the leading //go:build prefix in line from the build expression itself.// It returns "", false if the input is not a //go:build line or if the input contains multiple lines.func splitGoBuild( string) ( string, bool) {// A single trailing newline is OK; otherwise multiple lines are not.iflen() > 0 && [len()-1] == '\n' { = [:len()-1] }ifstrings.Contains(, "\n") {return"", false }if !strings.HasPrefix(, "//go:build") {return"", false } = strings.TrimSpace() = [len("//go:build"):]// If strings.TrimSpace finds more to trim after removing the //go:build prefix, // it means that the prefix was followed by a space, making this a //go:build line // (as opposed to a //go:buildsomethingelse line). // If line is empty, we had "//go:build" by itself, which also counts. := strings.TrimSpace()iflen() == len() && != "" {return"", false }return , true}// An exprParser holds state for parsing a build expression.type exprParser struct { s string// input string i int// next read location in s tok string// last token read isTag bool pos int// position (start) of last token}// parseExpr parses a boolean build tag expression.func parseExpr( string) ( Expr, error) {deferfunc() {if := recover(); != nil {if , := .(*SyntaxError); { = return }panic() // unreachable unless parser has a bug } }() := &exprParser{s: } = .or()if .tok != "" {panic(&SyntaxError{Offset: .pos, Err: "unexpected token " + .tok}) }return , nil}// or parses a sequence of || expressions.// On entry, the next input token has not yet been lexed.// On exit, the next input token has been lexed and is in p.tok.func ( *exprParser) () Expr { := .and()for .tok == "||" { = or(, .and()) }return}// and parses a sequence of && expressions.// On entry, the next input token has not yet been lexed.// On exit, the next input token has been lexed and is in p.tok.func ( *exprParser) () Expr { := .not()for .tok == "&&" { = and(, .not()) }return}// not parses a ! expression.// On entry, the next input token has not yet been lexed.// On exit, the next input token has been lexed and is in p.tok.func ( *exprParser) () Expr { .lex()if .tok == "!" { .lex()if .tok == "!" {panic(&SyntaxError{Offset: .pos, Err: "double negation not allowed"}) }returnnot(.atom()) }return .atom()}// atom parses a tag or a parenthesized expression.// On entry, the next input token HAS been lexed.// On exit, the next input token has been lexed and is in p.tok.func ( *exprParser) () Expr {// first token already in p.tokif .tok == "(" { := .posdeferfunc() {if := recover(); != nil {if , := .(*SyntaxError); && .Err == "unexpected end of expression" { .Err = "missing close paren" }panic() } }() := .or()if .tok != ")" {panic(&SyntaxError{Offset: , Err: "missing close paren"}) } .lex()return }if !.isTag {if .tok == "" {panic(&SyntaxError{Offset: .pos, Err: "unexpected end of expression"}) }panic(&SyntaxError{Offset: .pos, Err: "unexpected token " + .tok}) } := .tok .lex()returntag()}// lex finds and consumes the next token in the input stream.// On return, p.tok is set to the token text,// p.isTag reports whether the token was a tag,// and p.pos records the byte offset of the start of the token in the input stream.// If lex reaches the end of the input, p.tok is set to the empty string.// For any other syntax error, lex panics with a SyntaxError.func ( *exprParser) () { .isTag = falsefor .i < len(.s) && (.s[.i] == ' ' || .s[.i] == '\t') { .i++ }if .i >= len(.s) { .tok = "" .pos = .ireturn }switch .s[.i] {case'(', ')', '!': .pos = .i .i++ .tok = .s[.pos:.i]returncase'&', '|':if .i+1 >= len(.s) || .s[.i+1] != .s[.i] {panic(&SyntaxError{Offset: .i, Err: "invalid syntax at " + string(rune(.s[.i]))}) } .pos = .i .i += 2 .tok = .s[.pos:.i]return } := .s[.i:]for , := range {if !unicode.IsLetter() && !unicode.IsDigit() && != '_' && != '.' { = [:]break } }if == "" { , := utf8.DecodeRuneInString(.s[.i:])panic(&SyntaxError{Offset: .i, Err: "invalid syntax at " + string()}) } .pos = .i .i += len() .tok = .s[.pos:.i] .isTag = true}// IsPlusBuild reports whether the line of text is a “// +build” constraint.// It only checks the prefix of the text, not that the expression itself parses.func ( string) bool { , := splitPlusBuild()return}// splitPlusBuild splits apart the leading // +build prefix in line from the build expression itself.// It returns "", false if the input is not a // +build line or if the input contains multiple lines.func splitPlusBuild( string) ( string, bool) {// A single trailing newline is OK; otherwise multiple lines are not.iflen() > 0 && [len()-1] == '\n' { = [:len()-1] }ifstrings.Contains(, "\n") {return"", false }if !strings.HasPrefix(, "//") {return"", false } = [len("//"):]// Note the space is optional; "//+build" is recognized too. = strings.TrimSpace()if !strings.HasPrefix(, "+build") {return"", false } = [len("+build"):]// If strings.TrimSpace finds more to trim after removing the +build prefix, // it means that the prefix was followed by a space, making this a +build line // (as opposed to a +buildsomethingelse line). // If line is empty, we had "// +build" by itself, which also counts. := strings.TrimSpace()iflen() == len() && != "" {return"", false }return , true}// parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”).func parsePlusBuildExpr( string) Expr {varExprfor , := rangestrings.Fields() {varExprfor , := rangestrings.Split(, ",") {varExprvarboolifstrings.HasPrefix(, "!!") || == "!" { = tag("ignore") } else {ifstrings.HasPrefix(, "!") { = true = [len("!"):] }ifisValidTag() { = tag() } else { = tag("ignore") }if { = not() } }if == nil { = } else { = and(, ) } }if == nil { = } else { = or(, ) } }if == nil { = tag("ignore") }return}// isValidTag reports whether the word is a valid build tag.// Tags must be letters, digits, underscores or dots.// Unlike in Go identifiers, all digits are fine (e.g., "386").func isValidTag( string) bool {if == "" {returnfalse }for , := range {if !unicode.IsLetter() && !unicode.IsDigit() && != '_' && != '.' {returnfalse } }returntrue}var errComplex = errors.New("expression too complex for // +build lines")// PlusBuildLines returns a sequence of “// +build” lines that evaluate to the build expression x.// If the expression is too complex to convert directly to “// +build” lines, PlusBuildLines returns an error.func ( Expr) ([]string, error) {// Push all NOTs to the expression leaves, so that //go:build !(x && y) can be treated as !x || !y. // This rewrite is both efficient and commonly needed, so it's worth doing. // Essentially all other possible rewrites are too expensive and too rarely needed. = pushNot(, false)// Split into AND of ORs of ANDs of literals (tag or NOT tag).var [][][]Exprfor , := rangeappendSplitAnd(nil, ) {var [][]Exprfor , := rangeappendSplitOr(nil, ) {var []Exprfor , := rangeappendSplitAnd(nil, ) {switch .(type) {case *TagExpr, *NotExpr: = append(, )default:returnnil, errComplex } } = append(, ) } = append(, ) }// If all the ORs have length 1 (no actual OR'ing going on), // push the top-level ANDs to the bottom level, so that we get // one // +build line instead of many. := 0for , := range {if < len() { = len() } }if == 1 {var []Exprfor , := range { = append(, [0]...) } = [][][]Expr{{}} }// Prepare the +build lines.var []stringfor , := range { := "// +build"for , := range { := ""for , := range {if > 0 { += "," } += .String() } += " " + } = append(, ) }return , nil}// pushNot applies DeMorgan's law to push negations down the expression,// so that only tags are negated in the result.// (It applies the rewrites !(X && Y) => (!X || !Y) and !(X || Y) => (!X && !Y).)func pushNot( Expr, bool) Expr {switch x := .(type) {default:// unreachablereturncase *NotExpr:if , := .X.(*TagExpr); && ! {return }return (.X, !)case *TagExpr:if {return &NotExpr{X: } }returncase *AndExpr: := (.X, ) := (.Y, )if {returnor(, ) }if == .X && == .Y {return }returnand(, )case *OrExpr: := (.X, ) := (.Y, )if {returnand(, ) }if == .X && == .Y {return }returnor(, ) }}// appendSplitAnd appends x to list while splitting apart any top-level && expressions.// For example, appendSplitAnd({W}, X && Y && Z) = {W, X, Y, Z}.func appendSplitAnd( []Expr, Expr) []Expr {if , := .(*AndExpr); { = (, .X) = (, .Y)return }returnappend(, )}// appendSplitOr appends x to list while splitting apart any top-level || expressions.// For example, appendSplitOr({W}, X || Y || Z) = {W, X, Y, Z}.func appendSplitOr( []Expr, Expr) []Expr {if , := .(*OrExpr); { = (, .X) = (, .Y)return }returnappend(, )}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.