...

Source file src/go/build/constraint/expr.go

Documentation: go/build/constraint

		 1  // Copyright 2020 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  
		 5  // Package constraint implements parsing and evaluation of build constraint lines.
		 6  // See https://golang.org/cmd/go/#hdr-Build_constraints for documentation about build constraints themselves.
		 7  //
		 8  // This package parses both the original “// +build” syntax and the “//go:build” syntax that will be added in Go 1.17.
		 9  // The parser is being included in Go 1.16 to allow tools that need to process Go 1.17 source code
		10  // to still be built against the Go 1.16 release.
		11  // See https://golang.org/design/draft-gobuild for details about the “//go:build” syntax.
		12  package constraint
		13  
		14  import (
		15  	"errors"
		16  	"strings"
		17  	"unicode"
		18  	"unicode/utf8"
		19  )
		20  
		21  // An Expr is a build tag constraint expression.
		22  // The underlying concrete type is *AndExpr, *OrExpr, *NotExpr, or *TagExpr.
		23  type Expr interface {
		24  	// String returns the string form of the expression,
		25  	// using the boolean syntax used in //go:build lines.
		26  	String() string
		27  
		28  	// Eval reports whether the expression evaluates to true.
		29  	// It calls ok(tag) as needed to find out whether a given build tag
		30  	// is satisfied by the current build configuration.
		31  	Eval(ok func(tag string) bool) bool
		32  
		33  	// The presence of an isExpr method explicitly marks the type as an Expr.
		34  	// Only implementations in this package should be used as Exprs.
		35  	isExpr()
		36  }
		37  
		38  // A TagExpr is an Expr for the single tag Tag.
		39  type TagExpr struct {
		40  	Tag string // for example, “linux” or “cgo”
		41  }
		42  
		43  func (x *TagExpr) isExpr() {}
		44  
		45  func (x *TagExpr) Eval(ok func(tag string) bool) bool {
		46  	return ok(x.Tag)
		47  }
		48  
		49  func (x *TagExpr) String() string {
		50  	return x.Tag
		51  }
		52  
		53  func tag(tag string) Expr { return &TagExpr{tag} }
		54  
		55  // A NotExpr represents the expression !X (the negation of X).
		56  type NotExpr struct {
		57  	X Expr
		58  }
		59  
		60  func (x *NotExpr) isExpr() {}
		61  
		62  func (x *NotExpr) Eval(ok func(tag string) bool) bool {
		63  	return !x.X.Eval(ok)
		64  }
		65  
		66  func (x *NotExpr) String() string {
		67  	s := x.X.String()
		68  	switch x.X.(type) {
		69  	case *AndExpr, *OrExpr:
		70  		s = "(" + s + ")"
		71  	}
		72  	return "!" + s
		73  }
		74  
		75  func not(x Expr) Expr { return &NotExpr{x} }
		76  
		77  // An AndExpr represents the expression X && Y.
		78  type AndExpr struct {
		79  	X, Y Expr
		80  }
		81  
		82  func (x *AndExpr) isExpr() {}
		83  
		84  func (x *AndExpr) Eval(ok func(tag string) bool) bool {
		85  	// Note: Eval both, to make sure ok func observes all tags.
		86  	xok := x.X.Eval(ok)
		87  	yok := x.Y.Eval(ok)
		88  	return xok && yok
		89  }
		90  
		91  func (x *AndExpr) String() string {
		92  	return andArg(x.X) + " && " + andArg(x.Y)
		93  }
		94  
		95  func andArg(x Expr) string {
		96  	s := x.String()
		97  	if _, ok := x.(*OrExpr); ok {
		98  		s = "(" + s + ")"
		99  	}
	 100  	return s
	 101  }
	 102  
	 103  func and(x, y Expr) Expr {
	 104  	return &AndExpr{x, y}
	 105  }
	 106  
	 107  // An OrExpr represents the expression X || Y.
	 108  type OrExpr struct {
	 109  	X, Y Expr
	 110  }
	 111  
	 112  func (x *OrExpr) isExpr() {}
	 113  
	 114  func (x *OrExpr) Eval(ok func(tag string) bool) bool {
	 115  	// Note: Eval both, to make sure ok func observes all tags.
	 116  	xok := x.X.Eval(ok)
	 117  	yok := x.Y.Eval(ok)
	 118  	return xok || yok
	 119  }
	 120  
	 121  func (x *OrExpr) String() string {
	 122  	return orArg(x.X) + " || " + orArg(x.Y)
	 123  }
	 124  
	 125  func orArg(x Expr) string {
	 126  	s := x.String()
	 127  	if _, ok := x.(*AndExpr); ok {
	 128  		s = "(" + s + ")"
	 129  	}
	 130  	return s
	 131  }
	 132  
	 133  func or(x, y Expr) Expr {
	 134  	return &OrExpr{x, y}
	 135  }
	 136  
	 137  // A SyntaxError reports a syntax error in a parsed build expression.
	 138  type SyntaxError struct {
	 139  	Offset int		// byte offset in input where error was detected
	 140  	Err		string // description of error
	 141  }
	 142  
	 143  func (e *SyntaxError) Error() string {
	 144  	return e.Err
	 145  }
	 146  
	 147  var errNotConstraint = errors.New("not a build constraint")
	 148  
	 149  // Parse parses a single build constraint line of the form “//go:build ...” or “// +build ...”
	 150  // and returns the corresponding boolean expression.
	 151  func Parse(line string) (Expr, error) {
	 152  	if text, ok := splitGoBuild(line); ok {
	 153  		return parseExpr(text)
	 154  	}
	 155  	if text, ok := splitPlusBuild(line); ok {
	 156  		return parsePlusBuildExpr(text), nil
	 157  	}
	 158  	return nil, errNotConstraint
	 159  }
	 160  
	 161  // IsGoBuild reports whether the line of text is a “//go:build” constraint.
	 162  // It only checks the prefix of the text, not that the expression itself parses.
	 163  func IsGoBuild(line string) bool {
	 164  	_, ok := splitGoBuild(line)
	 165  	return ok
	 166  }
	 167  
	 168  // splitGoBuild splits apart the leading //go:build prefix in line from the build expression itself.
	 169  // It returns "", false if the input is not a //go:build line or if the input contains multiple lines.
	 170  func splitGoBuild(line string) (expr string, ok bool) {
	 171  	// A single trailing newline is OK; otherwise multiple lines are not.
	 172  	if len(line) > 0 && line[len(line)-1] == '\n' {
	 173  		line = line[:len(line)-1]
	 174  	}
	 175  	if strings.Contains(line, "\n") {
	 176  		return "", false
	 177  	}
	 178  
	 179  	if !strings.HasPrefix(line, "//go:build") {
	 180  		return "", false
	 181  	}
	 182  
	 183  	line = strings.TrimSpace(line)
	 184  	line = line[len("//go:build"):]
	 185  
	 186  	// If strings.TrimSpace finds more to trim after removing the //go:build prefix,
	 187  	// it means that the prefix was followed by a space, making this a //go:build line
	 188  	// (as opposed to a //go:buildsomethingelse line).
	 189  	// If line is empty, we had "//go:build" by itself, which also counts.
	 190  	trim := strings.TrimSpace(line)
	 191  	if len(line) == len(trim) && line != "" {
	 192  		return "", false
	 193  	}
	 194  
	 195  	return trim, true
	 196  }
	 197  
	 198  // An exprParser holds state for parsing a build expression.
	 199  type exprParser struct {
	 200  	s string // input string
	 201  	i int		// next read location in s
	 202  
	 203  	tok	 string // last token read
	 204  	isTag bool
	 205  	pos	 int // position (start) of last token
	 206  }
	 207  
	 208  // parseExpr parses a boolean build tag expression.
	 209  func parseExpr(text string) (x Expr, err error) {
	 210  	defer func() {
	 211  		if e := recover(); e != nil {
	 212  			if e, ok := e.(*SyntaxError); ok {
	 213  				err = e
	 214  				return
	 215  			}
	 216  			panic(e) // unreachable unless parser has a bug
	 217  		}
	 218  	}()
	 219  
	 220  	p := &exprParser{s: text}
	 221  	x = p.or()
	 222  	if p.tok != "" {
	 223  		panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok})
	 224  	}
	 225  	return x, nil
	 226  }
	 227  
	 228  // or parses a sequence of || expressions.
	 229  // On entry, the next input token has not yet been lexed.
	 230  // On exit, the next input token has been lexed and is in p.tok.
	 231  func (p *exprParser) or() Expr {
	 232  	x := p.and()
	 233  	for p.tok == "||" {
	 234  		x = or(x, p.and())
	 235  	}
	 236  	return x
	 237  }
	 238  
	 239  // and parses a sequence of && expressions.
	 240  // On entry, the next input token has not yet been lexed.
	 241  // On exit, the next input token has been lexed and is in p.tok.
	 242  func (p *exprParser) and() Expr {
	 243  	x := p.not()
	 244  	for p.tok == "&&" {
	 245  		x = and(x, p.not())
	 246  	}
	 247  	return x
	 248  }
	 249  
	 250  // not parses a ! expression.
	 251  // On entry, the next input token has not yet been lexed.
	 252  // On exit, the next input token has been lexed and is in p.tok.
	 253  func (p *exprParser) not() Expr {
	 254  	p.lex()
	 255  	if p.tok == "!" {
	 256  		p.lex()
	 257  		if p.tok == "!" {
	 258  			panic(&SyntaxError{Offset: p.pos, Err: "double negation not allowed"})
	 259  		}
	 260  		return not(p.atom())
	 261  	}
	 262  	return p.atom()
	 263  }
	 264  
	 265  // atom parses a tag or a parenthesized expression.
	 266  // On entry, the next input token HAS been lexed.
	 267  // On exit, the next input token has been lexed and is in p.tok.
	 268  func (p *exprParser) atom() Expr {
	 269  	// first token already in p.tok
	 270  	if p.tok == "(" {
	 271  		pos := p.pos
	 272  		defer func() {
	 273  			if e := recover(); e != nil {
	 274  				if e, ok := e.(*SyntaxError); ok && e.Err == "unexpected end of expression" {
	 275  					e.Err = "missing close paren"
	 276  				}
	 277  				panic(e)
	 278  			}
	 279  		}()
	 280  		x := p.or()
	 281  		if p.tok != ")" {
	 282  			panic(&SyntaxError{Offset: pos, Err: "missing close paren"})
	 283  		}
	 284  		p.lex()
	 285  		return x
	 286  	}
	 287  
	 288  	if !p.isTag {
	 289  		if p.tok == "" {
	 290  			panic(&SyntaxError{Offset: p.pos, Err: "unexpected end of expression"})
	 291  		}
	 292  		panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok})
	 293  	}
	 294  	tok := p.tok
	 295  	p.lex()
	 296  	return tag(tok)
	 297  }
	 298  
	 299  // lex finds and consumes the next token in the input stream.
	 300  // On return, p.tok is set to the token text,
	 301  // p.isTag reports whether the token was a tag,
	 302  // and p.pos records the byte offset of the start of the token in the input stream.
	 303  // If lex reaches the end of the input, p.tok is set to the empty string.
	 304  // For any other syntax error, lex panics with a SyntaxError.
	 305  func (p *exprParser) lex() {
	 306  	p.isTag = false
	 307  	for p.i < len(p.s) && (p.s[p.i] == ' ' || p.s[p.i] == '\t') {
	 308  		p.i++
	 309  	}
	 310  	if p.i >= len(p.s) {
	 311  		p.tok = ""
	 312  		p.pos = p.i
	 313  		return
	 314  	}
	 315  	switch p.s[p.i] {
	 316  	case '(', ')', '!':
	 317  		p.pos = p.i
	 318  		p.i++
	 319  		p.tok = p.s[p.pos:p.i]
	 320  		return
	 321  
	 322  	case '&', '|':
	 323  		if p.i+1 >= len(p.s) || p.s[p.i+1] != p.s[p.i] {
	 324  			panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(rune(p.s[p.i]))})
	 325  		}
	 326  		p.pos = p.i
	 327  		p.i += 2
	 328  		p.tok = p.s[p.pos:p.i]
	 329  		return
	 330  	}
	 331  
	 332  	tag := p.s[p.i:]
	 333  	for i, c := range tag {
	 334  		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
	 335  			tag = tag[:i]
	 336  			break
	 337  		}
	 338  	}
	 339  	if tag == "" {
	 340  		c, _ := utf8.DecodeRuneInString(p.s[p.i:])
	 341  		panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(c)})
	 342  	}
	 343  
	 344  	p.pos = p.i
	 345  	p.i += len(tag)
	 346  	p.tok = p.s[p.pos:p.i]
	 347  	p.isTag = true
	 348  	return
	 349  }
	 350  
	 351  // IsPlusBuild reports whether the line of text is a “// +build” constraint.
	 352  // It only checks the prefix of the text, not that the expression itself parses.
	 353  func IsPlusBuild(line string) bool {
	 354  	_, ok := splitPlusBuild(line)
	 355  	return ok
	 356  }
	 357  
	 358  // splitPlusBuild splits apart the leading // +build prefix in line from the build expression itself.
	 359  // It returns "", false if the input is not a // +build line or if the input contains multiple lines.
	 360  func splitPlusBuild(line string) (expr string, ok bool) {
	 361  	// A single trailing newline is OK; otherwise multiple lines are not.
	 362  	if len(line) > 0 && line[len(line)-1] == '\n' {
	 363  		line = line[:len(line)-1]
	 364  	}
	 365  	if strings.Contains(line, "\n") {
	 366  		return "", false
	 367  	}
	 368  
	 369  	if !strings.HasPrefix(line, "//") {
	 370  		return "", false
	 371  	}
	 372  	line = line[len("//"):]
	 373  	// Note the space is optional; "//+build" is recognized too.
	 374  	line = strings.TrimSpace(line)
	 375  
	 376  	if !strings.HasPrefix(line, "+build") {
	 377  		return "", false
	 378  	}
	 379  	line = line[len("+build"):]
	 380  
	 381  	// If strings.TrimSpace finds more to trim after removing the +build prefix,
	 382  	// it means that the prefix was followed by a space, making this a +build line
	 383  	// (as opposed to a +buildsomethingelse line).
	 384  	// If line is empty, we had "// +build" by itself, which also counts.
	 385  	trim := strings.TrimSpace(line)
	 386  	if len(line) == len(trim) && line != "" {
	 387  		return "", false
	 388  	}
	 389  
	 390  	return trim, true
	 391  }
	 392  
	 393  // parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”).
	 394  func parsePlusBuildExpr(text string) Expr {
	 395  	var x Expr
	 396  	for _, clause := range strings.Fields(text) {
	 397  		var y Expr
	 398  		for _, lit := range strings.Split(clause, ",") {
	 399  			var z Expr
	 400  			var neg bool
	 401  			if strings.HasPrefix(lit, "!!") || lit == "!" {
	 402  				z = tag("ignore")
	 403  			} else {
	 404  				if strings.HasPrefix(lit, "!") {
	 405  					neg = true
	 406  					lit = lit[len("!"):]
	 407  				}
	 408  				if isValidTag(lit) {
	 409  					z = tag(lit)
	 410  				} else {
	 411  					z = tag("ignore")
	 412  				}
	 413  				if neg {
	 414  					z = not(z)
	 415  				}
	 416  			}
	 417  			if y == nil {
	 418  				y = z
	 419  			} else {
	 420  				y = and(y, z)
	 421  			}
	 422  		}
	 423  		if x == nil {
	 424  			x = y
	 425  		} else {
	 426  			x = or(x, y)
	 427  		}
	 428  	}
	 429  	if x == nil {
	 430  		x = tag("ignore")
	 431  	}
	 432  	return x
	 433  }
	 434  
	 435  // isValidTag reports whether the word is a valid build tag.
	 436  // Tags must be letters, digits, underscores or dots.
	 437  // Unlike in Go identifiers, all digits are fine (e.g., "386").
	 438  func isValidTag(word string) bool {
	 439  	if word == "" {
	 440  		return false
	 441  	}
	 442  	for _, c := range word {
	 443  		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
	 444  			return false
	 445  		}
	 446  	}
	 447  	return true
	 448  }
	 449  
	 450  var errComplex = errors.New("expression too complex for // +build lines")
	 451  
	 452  // PlusBuildLines returns a sequence of “// +build” lines that evaluate to the build expression x.
	 453  // If the expression is too complex to convert directly to “// +build” lines, PlusBuildLines returns an error.
	 454  func PlusBuildLines(x Expr) ([]string, error) {
	 455  	// Push all NOTs to the expression leaves, so that //go:build !(x && y) can be treated as !x || !y.
	 456  	// This rewrite is both efficient and commonly needed, so it's worth doing.
	 457  	// Essentially all other possible rewrites are too expensive and too rarely needed.
	 458  	x = pushNot(x, false)
	 459  
	 460  	// Split into AND of ORs of ANDs of literals (tag or NOT tag).
	 461  	var split [][][]Expr
	 462  	for _, or := range appendSplitAnd(nil, x) {
	 463  		var ands [][]Expr
	 464  		for _, and := range appendSplitOr(nil, or) {
	 465  			var lits []Expr
	 466  			for _, lit := range appendSplitAnd(nil, and) {
	 467  				switch lit.(type) {
	 468  				case *TagExpr, *NotExpr:
	 469  					lits = append(lits, lit)
	 470  				default:
	 471  					return nil, errComplex
	 472  				}
	 473  			}
	 474  			ands = append(ands, lits)
	 475  		}
	 476  		split = append(split, ands)
	 477  	}
	 478  
	 479  	// If all the ORs have length 1 (no actual OR'ing going on),
	 480  	// push the top-level ANDs to the bottom level, so that we get
	 481  	// one // +build line instead of many.
	 482  	maxOr := 0
	 483  	for _, or := range split {
	 484  		if maxOr < len(or) {
	 485  			maxOr = len(or)
	 486  		}
	 487  	}
	 488  	if maxOr == 1 {
	 489  		var lits []Expr
	 490  		for _, or := range split {
	 491  			lits = append(lits, or[0]...)
	 492  		}
	 493  		split = [][][]Expr{{lits}}
	 494  	}
	 495  
	 496  	// Prepare the +build lines.
	 497  	var lines []string
	 498  	for _, or := range split {
	 499  		line := "// +build"
	 500  		for _, and := range or {
	 501  			clause := ""
	 502  			for i, lit := range and {
	 503  				if i > 0 {
	 504  					clause += ","
	 505  				}
	 506  				clause += lit.String()
	 507  			}
	 508  			line += " " + clause
	 509  		}
	 510  		lines = append(lines, line)
	 511  	}
	 512  
	 513  	return lines, nil
	 514  }
	 515  
	 516  // pushNot applies DeMorgan's law to push negations down the expression,
	 517  // so that only tags are negated in the result.
	 518  // (It applies the rewrites !(X && Y) => (!X || !Y) and !(X || Y) => (!X && !Y).)
	 519  func pushNot(x Expr, not bool) Expr {
	 520  	switch x := x.(type) {
	 521  	default:
	 522  		// unreachable
	 523  		return x
	 524  	case *NotExpr:
	 525  		if _, ok := x.X.(*TagExpr); ok && !not {
	 526  			return x
	 527  		}
	 528  		return pushNot(x.X, !not)
	 529  	case *TagExpr:
	 530  		if not {
	 531  			return &NotExpr{X: x}
	 532  		}
	 533  		return x
	 534  	case *AndExpr:
	 535  		x1 := pushNot(x.X, not)
	 536  		y1 := pushNot(x.Y, not)
	 537  		if not {
	 538  			return or(x1, y1)
	 539  		}
	 540  		if x1 == x.X && y1 == x.Y {
	 541  			return x
	 542  		}
	 543  		return and(x1, y1)
	 544  	case *OrExpr:
	 545  		x1 := pushNot(x.X, not)
	 546  		y1 := pushNot(x.Y, not)
	 547  		if not {
	 548  			return and(x1, y1)
	 549  		}
	 550  		if x1 == x.X && y1 == x.Y {
	 551  			return x
	 552  		}
	 553  		return or(x1, y1)
	 554  	}
	 555  }
	 556  
	 557  // appendSplitAnd appends x to list while splitting apart any top-level && expressions.
	 558  // For example, appendSplitAnd({W}, X && Y && Z) = {W, X, Y, Z}.
	 559  func appendSplitAnd(list []Expr, x Expr) []Expr {
	 560  	if x, ok := x.(*AndExpr); ok {
	 561  		list = appendSplitAnd(list, x.X)
	 562  		list = appendSplitAnd(list, x.Y)
	 563  		return list
	 564  	}
	 565  	return append(list, x)
	 566  }
	 567  
	 568  // appendSplitOr appends x to list while splitting apart any top-level || expressions.
	 569  // For example, appendSplitOr({W}, X || Y || Z) = {W, X, Y, Z}.
	 570  func appendSplitOr(list []Expr, x Expr) []Expr {
	 571  	if x, ok := x.(*OrExpr); ok {
	 572  		list = appendSplitOr(list, x.X)
	 573  		list = appendSplitOr(list, x.Y)
	 574  		return list
	 575  	}
	 576  	return append(list, x)
	 577  }
	 578  

View as plain text