Antlr4正则表达式语法,NFA转换表的数据结构

时间:2017-08-02 14:12:47

标签: java antlr4

我为极长的解释道歉,但我现在已经坚持了一个月,我真的无法弄清楚如何解决这个问题。 作为一个项目,我必须得到一个带有antlr4的编译器,用于生成一个程序(JAVA),该程序能够区分属于由用作antlr4编译器输入的正则表达式生成的语言的单词。 我们必须使用的语法就是这个:

RE ::= union | simpleRE
union ::= simpleRE + RE
simpleRE ::= concatenation | basicRE
concatenation ::= basicRE simpleRE
basicRE ::= group | any | char
group ::= (RE) | (RE)∗ | (RE)+
any ::= ?
char ::= a | b | c | ··· | z | A | B | C | ··· | Z | 0 | 1 | 2 | ··· | 9 | . | − | _

从那以后,我把这个语法给了atrl4

Regexp.g4

grammar Regxp;

start_rule              
    : re                            # start
    ;

re
    :    union                      
    | simpleRE                      
    ;

union 
    :    simpleRE '+' re            # unionOfREs
    ;

simpleRE
    :    concatenation                      
    | basicRE                               
    ;

concatenation
    :    basicRE simpleRE                   #concatOfREs
    ;

basicRE
    :    group                      
    | any                               
    | cHAR                              
    ;


group
    :  LPAREN re RPAREN '*'             # star
    |  LPAREN re RPAREN '+'             # plus
    |  LPAREN re RPAREN                 # singleWithParenthesis
    ;


any
    :   '?'                             
    ;


cHAR
    : CHAR              #singleChar
    ;

WS : [ \t\r\n]+ -> skip ;
LPAREN : '(' ;
RPAREN : ')' ;
CHAR : LETTER | DIGIT | DOT | D | UNDERSCORE
    ;
/* tokens */
fragment LETTER:    [a-zA-Z]
    ;
fragment DIGIT: [0-9]
    ;
fragment DOT:  '.'
    ;
fragment D:  '-'
    ;
fragment UNDERSCORE: '_'
    ;

然后我用antlr4和访问者生成了java文件。 据我理解项目的逻辑,当访问者遍历解析树时,它必须生成代码行以填充在输入正则表达式上应用Thompson规则而派生的NFA的转换表。 然后将这些代码行保存为.java文本文件,并编译为一个程序,该程序接受输入字符串(单词)并告诉该单词是否属于正则表达式生成的语言。 结果应该是这样的:

RE      word    Result
a+b       a       OK
          b       OK
         ac       KO

a∗b     aab       OK
         b        OK
       aaaab      OK
        abb       KO

所以我问,如何以一种方式表示转换表,以便在访问解析树期间填充它,然后导出以便由实现接受算法的简单java程序使用NFA? (我正在考虑这个伪代码):

S = ε−closure(s0);
c = nextChar();
while (c ≠ eof) do
S = ε−closure(move(S,c));
c = nextChar();
end while
if (S ∩ F ≠ ∅) then return “yes”;
else return “no”;
end if

截至目前,我设法做到这一点,例如,当访问者在unionOfREs规则中时,它将执行以下操作:

MyVisitor.java

private List<String> generatedCode = new ArrayList<String>();

/* ... */
@Override 
public String visitUnionOfREs(RegxpParser.UnionOfREsContext ctx) { 
    System.out.println("unionOfRExps");
    String char1 = visit(ctx.simpleRE());
    String char2 = visit(ctx.re());
    generatedCode.add("tTable.addUnion("+char1+","+char2+");");
    //then this line of code will populate the transition table
    return char1+"+"+char2;
}
/* ... */

addUnion它位于一个java文件中,该文件将包含填充转换表的所有方法。我为联盟编写了代码,但我不喜欢它,因为它就像编写NFA的转换表一样,就像你在纸上写的那样:example。 当我注意到通过迭代构建表时,我可以在表上定义2个“指针”,currentBeginning和currentEnd,告诉你在哪里再次展开写在表上的字符,接下来的规则是访问者在解析树上找到。因为这个角色可以是另一个角色,也可以只是一个角色。在链接上,它代表了书面纸上的例子,说服我使用这种方法。

TransitionTable.java

/* ... */
public void addUnion(String char1, String char2) {
    if (transitionTable.isEmpty()) {
    List<List<Integer>> lc1 = Arrays.asList(Arrays.asList(null)
            ,Arrays.asList(currentBeginning+3)
            ,Arrays.asList(null)
            ,Arrays.asList(null)
            ,Arrays.asList(null)
            ,Arrays.asList(null));
    List<List<Integer>> lc2 = Arrays.asList(Arrays.asList(null)
            ,Arrays.asList(null)
            ,Arrays.asList(currentBeginning+4)
            ,Arrays.asList(null)
            ,Arrays.asList(null)
            ,Arrays.asList(null));
    List<List<Integer>> le = Arrays.asList(Arrays.asList(currentBeginning+1,currentBeginning+2)
            ,Arrays.asList(null)
            ,Arrays.asList(null)
            ,Arrays.asList(currentBeginning+5)
            ,Arrays.asList(currentBeginning+5)
            ,Arrays.asList(null));

        transitionTable.put(char1, lc1);
        transitionTable.put(char2, lc2);
        transitionTable.put("epsilon", le);
        //currentBeginning += 2;
        //currentEnd = transitionTable.get(char2).get(currentBeginning).get(0);
        currentEnd = transitionTable.get("epsilon").size()-1;//il 5
        } else { //not the first time it encounters this rule, beginning and end changed
            //needs to add 2 less states
        }
    }
/* ... */

目前我正在表示转换表,因为HashMap<String, List<List<Integer>>>字符串用于NFA和List<List<Integer>>边缘的字符,因为它是非确定性的,它需要表示来自单个字符串的更多转换州。 但是这样,对于像this这样的解析树,我将获得联合的这行代码:"tTable.addUnion("tTable.addConcat(a,b)","+char2+");"

我在这里被阻止,我不知道如何解决这个问题,我真的想不出一种不同的方式来表示转换表或者在访问解析树时填充它。

谢谢。

1 个答案:

答案 0 :(得分:0)

使用Thompson的构造,每个常规(子)表达式都会生成NFA,并且每个正则表达式运算符(union,cat,*)都可以通过添加几个状态并将它们连接到已存在的状态来实现。参见:

https://en.wikipedia.org/wiki/Thompson%27s_construction

因此,在解析正则表达式时,每个终端或非终端生产应该将所需的状态和转换添加到NFA,并将其开始和结束状态返回到包含的生产。非终端制作将结合他们的孩子并返回他们自己的开始和结束状态,以便你的NFA可以从正则表达式的叶子构建。

状态表的表示对于构建并不重要。 Thompson的构造永远不会要求您修改之前构建的状态或转换,因此您只需要能够添加新的状态或转换。您也永远不需要从同一角色的状态转换,甚至不需要一个以上的非epsilon转换。实际上,如果所有运算符都是二进制的,那么一个状态就不需要超过2个转换。通常,该表示旨在使后续步骤变得容易,例如DFA生成或针对字符串直接执行NFA。

例如,像这样的类可以完全代表一个状态:

class State
{
    public char matchChar;
    public State matchState; //where to go if you match matchChar, or null
    public State epsilon1; //or null
    public State epsilon2; //or null
}

这实际上是直接执行NFA的非常合理的表示。但是,如果您已经拥有直接执行NFA的代码,那么您应该只是构建它所使用的任何内容,这样您就不必再进行另一次转换。