1
2
3
4
5
6
7
8
9
10 package constraint
11
12 import (
13 "errors"
14 "strings"
15 "unicode"
16 "unicode/utf8"
17 )
18
19
20
21 const maxSize = 1000
22
23
24
25 type Expr interface {
26
27
28 String() string
29
30
31
32
33 Eval(ok func(tag string) bool) bool
34
35
36
37 isExpr()
38 }
39
40
41 type TagExpr struct {
42 Tag string
43 }
44
45 func (x *TagExpr) isExpr() {}
46
47 func (x *TagExpr) Eval(ok func(tag string) bool) bool {
48 return ok(x.Tag)
49 }
50
51 func (x *TagExpr) String() string {
52 return x.Tag
53 }
54
55 func tag(tag string) Expr { return &TagExpr{tag} }
56
57
58 type NotExpr struct {
59 X Expr
60 }
61
62 func (x *NotExpr) isExpr() {}
63
64 func (x *NotExpr) Eval(ok func(tag string) bool) bool {
65 return !x.X.Eval(ok)
66 }
67
68 func (x *NotExpr) String() string {
69 s := x.X.String()
70 switch x.X.(type) {
71 case *AndExpr, *OrExpr:
72 s = "(" + s + ")"
73 }
74 return "!" + s
75 }
76
77 func not(x Expr) Expr { return &NotExpr{x} }
78
79
80 type AndExpr struct {
81 X, Y Expr
82 }
83
84 func (x *AndExpr) isExpr() {}
85
86 func (x *AndExpr) Eval(ok func(tag string) bool) bool {
87
88 xok := x.X.Eval(ok)
89 yok := x.Y.Eval(ok)
90 return xok && yok
91 }
92
93 func (x *AndExpr) String() string {
94 return andArg(x.X) + " && " + andArg(x.Y)
95 }
96
97 func andArg(x Expr) string {
98 s := x.String()
99 if _, ok := x.(*OrExpr); ok {
100 s = "(" + s + ")"
101 }
102 return s
103 }
104
105 func and(x, y Expr) Expr {
106 return &AndExpr{x, y}
107 }
108
109
110 type OrExpr struct {
111 X, Y Expr
112 }
113
114 func (x *OrExpr) isExpr() {}
115
116 func (x *OrExpr) Eval(ok func(tag string) bool) bool {
117
118 xok := x.X.Eval(ok)
119 yok := x.Y.Eval(ok)
120 return xok || yok
121 }
122
123 func (x *OrExpr) String() string {
124 return orArg(x.X) + " || " + orArg(x.Y)
125 }
126
127 func orArg(x Expr) string {
128 s := x.String()
129 if _, ok := x.(*AndExpr); ok {
130 s = "(" + s + ")"
131 }
132 return s
133 }
134
135 func or(x, y Expr) Expr {
136 return &OrExpr{x, y}
137 }
138
139
140 type SyntaxError struct {
141 Offset int
142 Err string
143 }
144
145 func (e *SyntaxError) Error() string {
146 return e.Err
147 }
148
149 var errNotConstraint = errors.New("not a build constraint")
150
151
152
153 func Parse(line string) (Expr, error) {
154 if text, ok := splitGoBuild(line); ok {
155 return parseExpr(text)
156 }
157 if text, ok := splitPlusBuild(line); ok {
158 return parsePlusBuildExpr(text)
159 }
160 return nil, errNotConstraint
161 }
162
163
164
165 func IsGoBuild(line string) bool {
166 _, ok := splitGoBuild(line)
167 return ok
168 }
169
170
171
172 func splitGoBuild(line string) (expr string, ok bool) {
173
174 if len(line) > 0 && line[len(line)-1] == '\n' {
175 line = line[:len(line)-1]
176 }
177 if strings.Contains(line, "\n") {
178 return "", false
179 }
180
181 if !strings.HasPrefix(line, "//go:build") {
182 return "", false
183 }
184
185 line = strings.TrimSpace(line)
186 line = line[len("//go:build"):]
187
188
189
190
191
192 trim := strings.TrimSpace(line)
193 if len(line) == len(trim) && line != "" {
194 return "", false
195 }
196
197 return trim, true
198 }
199
200
201 type exprParser struct {
202 s string
203 i int
204
205 tok string
206 isTag bool
207 pos int
208
209 size int
210 }
211
212
213 func parseExpr(text string) (x Expr, err error) {
214 defer func() {
215 if e := recover(); e != nil {
216 if e, ok := e.(*SyntaxError); ok {
217 err = e
218 return
219 }
220 panic(e)
221 }
222 }()
223
224 p := &exprParser{s: text}
225 x = p.or()
226 if p.tok != "" {
227 panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok})
228 }
229 return x, nil
230 }
231
232
233
234
235 func (p *exprParser) or() Expr {
236 x := p.and()
237 for p.tok == "||" {
238 x = or(x, p.and())
239 }
240 return x
241 }
242
243
244
245
246 func (p *exprParser) and() Expr {
247 x := p.not()
248 for p.tok == "&&" {
249 x = and(x, p.not())
250 }
251 return x
252 }
253
254
255
256
257 func (p *exprParser) not() Expr {
258 p.size++
259 if p.size > maxSize {
260 panic(&SyntaxError{Offset: p.pos, Err: "build expression too large"})
261 }
262 p.lex()
263 if p.tok == "!" {
264 p.lex()
265 if p.tok == "!" {
266 panic(&SyntaxError{Offset: p.pos, Err: "double negation not allowed"})
267 }
268 return not(p.atom())
269 }
270 return p.atom()
271 }
272
273
274
275
276 func (p *exprParser) atom() Expr {
277
278 if p.tok == "(" {
279 pos := p.pos
280 defer func() {
281 if e := recover(); e != nil {
282 if e, ok := e.(*SyntaxError); ok && e.Err == "unexpected end of expression" {
283 e.Err = "missing close paren"
284 }
285 panic(e)
286 }
287 }()
288 x := p.or()
289 if p.tok != ")" {
290 panic(&SyntaxError{Offset: pos, Err: "missing close paren"})
291 }
292 p.lex()
293 return x
294 }
295
296 if !p.isTag {
297 if p.tok == "" {
298 panic(&SyntaxError{Offset: p.pos, Err: "unexpected end of expression"})
299 }
300 panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok})
301 }
302 tok := p.tok
303 p.lex()
304 return tag(tok)
305 }
306
307
308
309
310
311
312
313 func (p *exprParser) lex() {
314 p.isTag = false
315 for p.i < len(p.s) && (p.s[p.i] == ' ' || p.s[p.i] == '\t') {
316 p.i++
317 }
318 if p.i >= len(p.s) {
319 p.tok = ""
320 p.pos = p.i
321 return
322 }
323 switch p.s[p.i] {
324 case '(', ')', '!':
325 p.pos = p.i
326 p.i++
327 p.tok = p.s[p.pos:p.i]
328 return
329
330 case '&', '|':
331 if p.i+1 >= len(p.s) || p.s[p.i+1] != p.s[p.i] {
332 panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(rune(p.s[p.i]))})
333 }
334 p.pos = p.i
335 p.i += 2
336 p.tok = p.s[p.pos:p.i]
337 return
338 }
339
340 tag := p.s[p.i:]
341 for i, c := range tag {
342 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
343 tag = tag[:i]
344 break
345 }
346 }
347 if tag == "" {
348 c, _ := utf8.DecodeRuneInString(p.s[p.i:])
349 panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(c)})
350 }
351
352 p.pos = p.i
353 p.i += len(tag)
354 p.tok = p.s[p.pos:p.i]
355 p.isTag = true
356 }
357
358
359
360 func IsPlusBuild(line string) bool {
361 _, ok := splitPlusBuild(line)
362 return ok
363 }
364
365
366
367 func splitPlusBuild(line string) (expr string, ok bool) {
368
369 if len(line) > 0 && line[len(line)-1] == '\n' {
370 line = line[:len(line)-1]
371 }
372 if strings.Contains(line, "\n") {
373 return "", false
374 }
375
376 if !strings.HasPrefix(line, "//") {
377 return "", false
378 }
379 line = line[len("//"):]
380
381 line = strings.TrimSpace(line)
382
383 if !strings.HasPrefix(line, "+build") {
384 return "", false
385 }
386 line = line[len("+build"):]
387
388
389
390
391
392 trim := strings.TrimSpace(line)
393 if len(line) == len(trim) && line != "" {
394 return "", false
395 }
396
397 return trim, true
398 }
399
400
401 func parsePlusBuildExpr(text string) (Expr, error) {
402
403
404
405 const maxOldSize = 100
406 size := 0
407
408 var x Expr
409 for _, clause := range strings.Fields(text) {
410 var y Expr
411 for _, lit := range strings.Split(clause, ",") {
412 var z Expr
413 var neg bool
414 if strings.HasPrefix(lit, "!!") || lit == "!" {
415 z = tag("ignore")
416 } else {
417 if strings.HasPrefix(lit, "!") {
418 neg = true
419 lit = lit[len("!"):]
420 }
421 if isValidTag(lit) {
422 z = tag(lit)
423 } else {
424 z = tag("ignore")
425 }
426 if neg {
427 z = not(z)
428 }
429 }
430 if y == nil {
431 y = z
432 } else {
433 if size++; size > maxOldSize {
434 return nil, errComplex
435 }
436 y = and(y, z)
437 }
438 }
439 if x == nil {
440 x = y
441 } else {
442 if size++; size > maxOldSize {
443 return nil, errComplex
444 }
445 x = or(x, y)
446 }
447 }
448 if x == nil {
449 x = tag("ignore")
450 }
451 return x, nil
452 }
453
454
455
456
457 func isValidTag(word string) bool {
458 if word == "" {
459 return false
460 }
461 for _, c := range word {
462 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
463 return false
464 }
465 }
466 return true
467 }
468
469 var errComplex = errors.New("expression too complex for // +build lines")
470
471
472
473 func PlusBuildLines(x Expr) ([]string, error) {
474
475
476
477 x = pushNot(x, false)
478
479
480 var split [][][]Expr
481 for _, or := range appendSplitAnd(nil, x) {
482 var ands [][]Expr
483 for _, and := range appendSplitOr(nil, or) {
484 var lits []Expr
485 for _, lit := range appendSplitAnd(nil, and) {
486 switch lit.(type) {
487 case *TagExpr, *NotExpr:
488 lits = append(lits, lit)
489 default:
490 return nil, errComplex
491 }
492 }
493 ands = append(ands, lits)
494 }
495 split = append(split, ands)
496 }
497
498
499
500
501 maxOr := 0
502 for _, or := range split {
503 if maxOr < len(or) {
504 maxOr = len(or)
505 }
506 }
507 if maxOr == 1 {
508 var lits []Expr
509 for _, or := range split {
510 lits = append(lits, or[0]...)
511 }
512 split = [][][]Expr{{lits}}
513 }
514
515
516 var lines []string
517 for _, or := range split {
518 line := "// +build"
519 for _, and := range or {
520 clause := ""
521 for i, lit := range and {
522 if i > 0 {
523 clause += ","
524 }
525 clause += lit.String()
526 }
527 line += " " + clause
528 }
529 lines = append(lines, line)
530 }
531
532 return lines, nil
533 }
534
535
536
537
538 func pushNot(x Expr, not bool) Expr {
539 switch x := x.(type) {
540 default:
541
542 return x
543 case *NotExpr:
544 if _, ok := x.X.(*TagExpr); ok && !not {
545 return x
546 }
547 return pushNot(x.X, !not)
548 case *TagExpr:
549 if not {
550 return &NotExpr{X: x}
551 }
552 return x
553 case *AndExpr:
554 x1 := pushNot(x.X, not)
555 y1 := pushNot(x.Y, not)
556 if not {
557 return or(x1, y1)
558 }
559 if x1 == x.X && y1 == x.Y {
560 return x
561 }
562 return and(x1, y1)
563 case *OrExpr:
564 x1 := pushNot(x.X, not)
565 y1 := pushNot(x.Y, not)
566 if not {
567 return and(x1, y1)
568 }
569 if x1 == x.X && y1 == x.Y {
570 return x
571 }
572 return or(x1, y1)
573 }
574 }
575
576
577
578 func appendSplitAnd(list []Expr, x Expr) []Expr {
579 if x, ok := x.(*AndExpr); ok {
580 list = appendSplitAnd(list, x.X)
581 list = appendSplitAnd(list, x.Y)
582 return list
583 }
584 return append(list, x)
585 }
586
587
588
589 func appendSplitOr(list []Expr, x Expr) []Expr {
590 if x, ok := x.(*OrExpr); ok {
591 list = appendSplitOr(list, x.X)
592 list = appendSplitOr(list, x.Y)
593 return list
594 }
595 return append(list, x)
596 }
597
View as plain text