1

好吧,根据我已经发布的问题数量,我经常觉得自己是使用 ANTLR 的最愚蠢的人,但在这里我再次寻求帮助。

我最终尝试重写现有策略以简化它,只是让“简化”的策略决定轰炸应该发送到 HIDDEN 通道的空白(skip() 也不起作用)。它可能只是乱序的 Lexer 令牌,但我很难过(也许我对如何指定顺序没有很好的理解)。

无论如何,这是整个(经过一定程度消毒的)政策:

grammar ValidatingPolicy;

options {
  language = Java;
    backtrack = true; 
}

// package and imports for the parser
@parser::header {
package org.jason.manager.impl;

import org.jason.manager.RecognitionRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
}

// package and imports for the lexer
@lexer::header {
package org.jason.manager.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
}

// member functions and fields for the parser
@parser::members {

 private static final Logger log = LoggerFactory.getLogger(ValidatingPolicyParser.class);
 @Override
 protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow) throws RecognitionException {
   throw new MismatchedTokenException(ttype, input);
 }

  @Override
 public Object recoverFromMismatchedSet(IntStream input, RecognitionException e, BitSet follow) throws RecognitionException {
   throw e;
 }

 @Override
 public String getErrorMessage(RecognitionException e, String[] tokenNames) {
  // wrap in a runtime exception to escape ANTLR's dungeon
  throw new RecognitionRuntimeException(e);
 }
}

// member functions and fields for the lexer
@lexer::members {
  private static final Logger log = LoggerFactory.getLogger(ValidatingPolicyLexer.class);
}

// validate a group of SHOW constructs
showGroup
  : show+ EOF
  ;

// validate a construct WITHOUT show (MINQ, MOS, etc)
noShow
  : simpleIfStatement+ EOF
  ;

// validate a SHOW construct (COMP or ELIG validation)
show
  : SHOW STRING FOR simpleIfStatement+
  ;

// handle an if statement
simpleIfStatement
  // basic if statement
  : IF chainedOperation THEN operationGroup (ELSE operationGroup)? ENDIF
  // if statement with recursive if statement in THEN or ELSE block
  | IF chainedOperation THEN simpleIfStatement (ELSE simpleIfStatement)? ENDIF
  | operationGroup
  ;

// aggregate multiple operations. When evaluated, there is an implicit AND
// when there are multiple groups
operationGroup
  : chainedOperation+
  ;

// chain an operation together optionally with AND/OR
chainedOperation
  @init {
    log.info("Entered chainedOperation");
  }
  : operation (AND operation | OR operation)*
  ;

// aggregate into a single rule that can be referenced up the chain
operation
  @init {
    log.info("Entered operation");
  }
  // legal operation
  : (booleanLogical | stringLogical | integerLogical | dateLogical | datePeriodLogical)
  ;

// LOGICAL OPERATIONS
// Logical operators do not have a pass through, but may have limits
// on which particular operators can be used

// compare DATE/DATE_FIELD to DATE/DATE_FIELD
dateLogical
  @init {
    log.info("Entered dateLogical");
  }
  : dateOp (EQ|NE|LT|LE|GT|GE) dateOp
  ;

// compare DATE_PERIOD/DATE_PERIOD_CONSTANT/DATE_PERIOD_FIELD
datePeriodLogical
  @init {
    log.info("Entered datePeriodLogical");
  }
  : datePeriodOp (EQ|NE|LT|LE|GT|GE) datePeriodOp
  ;

// compare INTEGER_FIELD/INTEGER
integerLogical
  @init {
    log.info("Entered integerLogical");
  }
  : integerOp (EQ|NE|LT|LE|GT|GE) integerOp
  ;

// compare BOOLEAN_FIELD/BOOLEAN_CONSTANT
booleanLogical
  : booleanOp (EQ|NE) booleanOp
  ;

// compare STRING_FIELD/STRING
stringLogical
  : stringOp (EQ|NE|LT|LE|GT|GE) stringOp
  {
    System.out.println("stringLogical: matched rule 1");
  }
  ;

dateOp
  @init {
    log.info("Entered dateOp");
  }
  // pass through if no math op needs to be performed
  : DATE_FIELD|DATE|DATE_CONSTANT
  // match a legal math op
  | DATE_FIELD|DATE|DATE_CONSTANT ((PLUS|MINUS) DATE_FIELD|DATE|DATE_CONSTANT|DATE_PERIOD_FIELD|DATE_PERIOD_CONSTANT (' ' DATE_PERIOD_CONSTANT)*)*
  ;

datePeriodOp
  // pass through if no math op needs to be performed
  : DATE_PERIOD_FIELD|DATE_PERIOD_CONSTANT
  // match a legal math op
  | DATE_PERIOD_FIELD ((PLUS|MINUS) DATE_FIELD|DATE|DATE_CONSTANT|DATE_PERIOD_FIELD|DATE_PERIOD_CONSTANT+)*
  ;

integerOp
  @init {
    log.info("Entered integerOp");
  }
  // pass through if no math op needs to be performed
  : INTEGER_FIELD | INTEGER
  // match a legal math op
  | INTEGER_FIELD (PLUS|MINUS INTEGER_FIELD|INTEGER)*
  ;

// booleanOp, stringOp, and waiverOp don't do anything since + and - ops are not
// supported for them
booleanOp
  : BOOLEAN_FIELD | BOOLEAN_CONSTANT
  ;

stringOp
  : STRING_FIELD | STRING
  ;

// these items are not directly referenced by parser rules, so they
// can be fragments

fragment DIGIT: ('0'..'9');
fragment DATE: ;
fragment DATE_PERIOD_CONSTANT: DIGIT+ ' '+ (YEAR | MONTH | WEEK | DAY);
YEAR: ('YEAR'|'YEARS');
MONTH: ('MONTH'|'MONTHS');
WEEK: ('WEEK'|'WEEKS');
DAY: ('DAY'|'DAYS');

DATE_FIELD:('DOB'|'TEST_DATE');
DATE_PERIOD_FIELD:('EMPLOYMENT_PERIOD');
BOOLEAN_FIELD:('CERTIFIED');
INTEGER_FIELD:('AGE'|'OPTION');
STRING_FIELD:('STATE'|'UF_USERID'|'USER_LEVEL');

// various tokens can't be fragments since they are directly referenced by parser rules
COMMENT_START: ';';
BOOLEAN_CONSTANT: ('TRUE'|'FALSE'|'"Y"'|'"N"');
DATE_CONSTANT:('TODAY'|'YESTERDAY'|'TOMMOROW');
SHOW: 'SHOW';
FOR: 'FOR';
IF: 'IF';
THEN: 'THEN';
ELSE: 'ELSE';
ENDIF: 'ENDIF';
AND: 'AND';
OR: 'OR';
EQ: '=';
NE: '<>';
LT: '<';
LE: '<=';
GT: '>';
GE: '>=';
NOT: 'NOT';
HAS: 'HAS';
PLUS: '+';
MINUS: '-';

// Commented ifs seem to take more than one line, even if comments are
// only supposed to be a single line
COMMENTED_IF: COMMENT_START WS* IF (options {greedy=false;} : .)* ENDIF '\r\n'
{
  log.info("Lexer: matched COMMENTED IF" + getText());
  $channel=HIDDEN;
  //skip();
};

// Handle an empty comment such as "; "
EMPTY_COMMENT: COMMENT_START WS* '\r\n'
{
  log.info("Lexer: matched EMPTY_COMMENT: " + getText());
  $channel=HIDDEN;
};

// Handle a single-line comment. Policies often end with a comment, so be ready for it
SINGLE_COMMENT: COMMENT_START ~('\r'|'\n')*  (('\r\n')+| EOF)
{
  log.info("Lexer: matched SINGLE_COMMENT: " + getText());
  $channel=HIDDEN;
};

INTEGER
  // Bart Kiers on SO helped me with this one, basically handle a date period such as
  // 4 WEEKS, 1 YEAR 6 MONTHS 2 WEEKS 8 DAYS, etc
 : (DATE_PERIOD_CONSTANT)=> DATE_PERIOD_CONSTANT ((' '+ DATE_PERIOD_CONSTANT)=> ' '+ DATE_PERIOD_CONSTANT)*
   {
      // manually switch the type from INTEGER to DATE_PERIOD_CONSTANT
     $type=DATE_PERIOD_CONSTANT;
     log.info("Matched DATE_PERIOD_CONSTANT: " + getText());
   }
 | DIGIT+
   {
      // match a 6-digit or 8-digit date format (20120101 or 201201)
     if ($text.matches("(19|20|21)[0-9]{2}[0-1]\\d{3}") || $text.matches("(19|20|21)\\d{2}(0[1-9]|1[0-2])")) {
      log.info("Matched DATE pattern: " + getText());
       $type = DATE;
     } else {
      log.info("Matched INTEGER: " + getText());
     }
   }   
 ;

STRING
  : '"' ID  (' ' ID)* '"'
  ;

ID: ('A'..'Z'|'a'..'z'|DIGIT|','|'!'|'?'|':')+;

WS: (' '+|'\r'|'\n'|'\t') 
{
  //skip();
  $channel=HIDDEN;
};

“show” 构造应该如下所示:

SHOW "DOES NOT MEET AGE REQUIREMENTS FOR EMPLOYMENT" FOR
  AGE < 18

SHOW "TOO YOUNG FOR CERTIFICATION IN KY" FOR
   IF STATE="KY" THEN AGE > 21 ENDIF

它在我删除空格时起作用,例如字符串周围或运算符周围等。

此外,如果有人在语法中看到任何其他愚蠢之处,我会很高兴听到它们。

杰森

4

1 回答 1

1

您的词法分析器正在一个隐式的、未命名的词法分析器规则中匹配空格。此词法分析器规则在解析器规则中引用dateOp

dateOp
  //...
  // pass through if no math op needs to be performed
  : DATE_FIELD|DATE|DATE_CONSTANT
  // match a legal math op
  | DATE_FIELD|DATE|DATE_CONSTANT 
     ((PLUS|MINUS) DATE_FIELD|DATE|DATE_CONSTANT|DATE_PERIOD_FIELD|DATE_PERIOD_CONSTANT 
       (' ' DATE_PERIOD_CONSTANT)* //<--- ' ' becomes a new lexer rule
     )*
  ;

它的行为就像一个普通的词法分析器规则,所以使用这个输入:

SHOW "DOES NOT MEET AGE REQUIREMENTS FOR EMPLOYMENT" FOR
  AGE < 18

词法分析器产生这些标记:

[SHOW : SHOW] [' ' :  ] [STRING : "DOES NOT MEET AGE REQUIREMENTS FOR EMPLOYMENT"] 
[' ' :  ] [FOR : FOR] [INTEGER_FIELD : AGE] [' ' :  ] [LT : <] [' ' :  ] 
[INTEGER : 18] 

注意[' ' : ]令牌。这些是隐含的词法分析器规则。解析器不期望这些标记超出dateOp规则,因此解析噱头。

删除' 'from parser ruledateOp后,上面的输入按预期生成以下标记:

[SHOW : SHOW] [STRING : "DOES NOT MEET AGE REQUIREMENTS FOR EMPLOYMENT"] 
[FOR : FOR] [INTEGER_FIELD : AGE] [LT : <]
[INTEGER : 18] 

我不知道删除' 'fromdateOp在您的语法中是否可以接受。如果需要显式测试空格,请考虑重写您可以将空格测试向下移动到词法分析器中的内容。或者,解析器可以提前查看下一个标记是否是隐藏的 WS 标记。dateOp不过,对于初学者来说,我建议尽可能地清理干净,看看东西落在哪里。

于 2013-03-02T05:55:58.257 回答