@@ -404,81 +404,97 @@ func readIssues(pcfg ProgramConfig) ([]*Issue, error) {
404
404
for _ , issue := range issues {
405
405
block := findPredicateBlock (issue .Body )
406
406
if block != "" {
407
- expr , err := parser . ParseExpr (block )
407
+ pred , err := parsePredicate (block )
408
408
if err != nil {
409
409
log .Printf ("invalid predicate in issue #%d: %v\n <<%s>>" ,
410
410
issue .Number , err , block )
411
411
continue
412
412
}
413
- var validate func (ast.Expr ) error
414
- validate = func (e ast.Expr ) error {
415
- switch e := e .(type ) {
416
- case * ast.UnaryExpr :
417
- if e .Op != token .NOT {
418
- return fmt .Errorf ("invalid op: %s" , e .Op )
419
- }
420
- return validate (e .X )
421
-
422
- case * ast.BinaryExpr :
423
- if e .Op != token .LAND && e .Op != token .LOR {
424
- return fmt .Errorf ("invalid op: %s" , e .Op )
425
- }
426
- if err := validate (e .X ); err != nil {
427
- return err
428
- }
429
- return validate (e .Y )
413
+ issue .predicateText = block
414
+ issue .predicate = pred
415
+ }
416
+ }
430
417
431
- case * ast. ParenExpr :
432
- return validate ( e . X )
418
+ return issues , nil
419
+ }
433
420
434
- case * ast.BasicLit :
435
- if e .Kind != token .STRING {
436
- return fmt .Errorf ("invalid literal (%s)" , e .Kind )
437
- }
438
- if _ , err := strconv .Unquote (e .Value ); err != nil {
439
- return err
440
- }
421
+ // parsePredicate parses a predicate expression, returning a function that evaluates
422
+ // the predicate on a stack.
423
+ // The expression must match this grammar:
424
+ //
425
+ // expr = "string literal"
426
+ // | ( expr )
427
+ // | ! expr
428
+ // | expr && expr
429
+ // | expr || expr
430
+ func parsePredicate (s string ) (func (string ) bool , error ) {
431
+ expr , err := parser .ParseExpr (s )
432
+ if err != nil {
433
+ return nil , fmt .Errorf ("parse error: %w" , err )
434
+ }
435
+ var validate func (ast.Expr ) error
436
+ validate = func (e ast.Expr ) error {
437
+ switch e := e .(type ) {
438
+ case * ast.UnaryExpr :
439
+ if e .Op != token .NOT {
440
+ return fmt .Errorf ("invalid op: %s" , e .Op )
441
+ }
442
+ return validate (e .X )
441
443
442
- default :
443
- return fmt .Errorf ("syntax error (%T)" , e )
444
- }
445
- return nil
444
+ case * ast.BinaryExpr :
445
+ if e .Op != token .LAND && e .Op != token .LOR {
446
+ return fmt .Errorf ("invalid op: %s" , e .Op )
446
447
}
447
- if err := validate (expr ); err != nil {
448
- log .Printf ("invalid predicate in issue #%d: %v\n <<%s>>" ,
449
- issue .Number , err , block )
450
- continue
448
+ if err := validate (e .X ); err != nil {
449
+ return err
451
450
}
452
- issue .predicateText = block
453
- issue .predicate = func (stack string ) bool {
454
- var eval func (ast.Expr ) bool
455
- eval = func (e ast.Expr ) bool {
456
- switch e := e .(type ) {
457
- case * ast.UnaryExpr :
458
- return ! eval (e .X )
459
-
460
- case * ast.BinaryExpr :
461
- if e .Op == token .LAND {
462
- return eval (e .X ) && eval (e .Y )
463
- } else {
464
- return eval (e .X ) || eval (e .Y )
465
- }
451
+ return validate (e .Y )
466
452
467
- case * ast.ParenExpr :
468
- return eval (e .X )
453
+ case * ast.ParenExpr :
454
+ return validate (e .X )
469
455
470
- case * ast.BasicLit :
471
- substr , _ := strconv .Unquote (e .Value )
472
- return strings .Contains (stack , substr )
473
- }
474
- panic ("unreachable" )
475
- }
476
- return eval (expr )
456
+ case * ast.BasicLit :
457
+ if e .Kind != token .STRING {
458
+ return fmt .Errorf ("invalid literal (%s)" , e .Kind )
459
+ }
460
+ if _ , err := strconv .Unquote (e .Value ); err != nil {
461
+ return err
477
462
}
463
+
464
+ default :
465
+ return fmt .Errorf ("syntax error (%T)" , e )
478
466
}
467
+ return nil
468
+ }
469
+ if err := validate (expr ); err != nil {
470
+ return nil , err
479
471
}
480
472
481
- return issues , nil
473
+ return func (stack string ) bool {
474
+ var eval func (ast.Expr ) bool
475
+ eval = func (e ast.Expr ) bool {
476
+ switch e := e .(type ) {
477
+ case * ast.UnaryExpr :
478
+ return ! eval (e .X )
479
+
480
+ case * ast.BinaryExpr :
481
+ if e .Op == token .LAND {
482
+ return eval (e .X ) && eval (e .Y )
483
+ } else {
484
+ return eval (e .X ) || eval (e .Y )
485
+ }
486
+
487
+ case * ast.ParenExpr :
488
+ return eval (e .X )
489
+
490
+ case * ast.BasicLit :
491
+ substr , _ := strconv .Unquote (e .Value )
492
+ return strings .Contains (stack , substr )
493
+ }
494
+ panic ("unreachable" )
495
+ }
496
+ return eval (expr )
497
+ }, nil
482
498
}
483
499
484
500
// claimStack maps each stack ID to its issue (if any).
0 commit comments