1
2
3
4
5
6
7
8
9
10
11
12 package constraint
13
14 import (
15 "errors"
16 "strings"
17 "unicode"
18 "unicode/utf8"
19 )
20
21
22
23 type Expr interface {
24
25
26 String() string
27
28
29
30
31 Eval(ok func(tag string) bool) bool
32
33
34
35 isExpr()
36 }
37
38
39 type TagExpr struct {
40 Tag string
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
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
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
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
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
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
138 type SyntaxError struct {
139 Offset int
140 Err string
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
150
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
162
163 func IsGoBuild(line string) bool {
164 _, ok := splitGoBuild(line)
165 return ok
166 }
167
168
169
170 func splitGoBuild(line string) (expr string, ok bool) {
171
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
187
188
189
190 trim := strings.TrimSpace(line)
191 if len(line) == len(trim) && line != "" {
192 return "", false
193 }
194
195 return trim, true
196 }
197
198
199 type exprParser struct {
200 s string
201 i int
202
203 tok string
204 isTag bool
205 pos int
206 }
207
208
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)
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
229
230
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
240
241
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
251
252
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
266
267
268 func (p *exprParser) atom() Expr {
269
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
300
301
302
303
304
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
352
353 func IsPlusBuild(line string) bool {
354 _, ok := splitPlusBuild(line)
355 return ok
356 }
357
358
359
360 func splitPlusBuild(line string) (expr string, ok bool) {
361
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
374 line = strings.TrimSpace(line)
375
376 if !strings.HasPrefix(line, "+build") {
377 return "", false
378 }
379 line = line[len("+build"):]
380
381
382
383
384
385 trim := strings.TrimSpace(line)
386 if len(line) == len(trim) && line != "" {
387 return "", false
388 }
389
390 return trim, true
391 }
392
393
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
436
437
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
453
454 func PlusBuildLines(x Expr) ([]string, error) {
455
456
457
458 x = pushNot(x, false)
459
460
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
480
481
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
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
517
518
519 func pushNot(x Expr, not bool) Expr {
520 switch x := x.(type) {
521 default:
522
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
558
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
569
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