使用antlr4的词法分析器模式解析内联和多行语句

时间:2016-08-10 15:21:54

标签: antlr antlr4

我目前正在使用Island语法解析器来解析同一文件中的两种编程语言(DSL)。 第二种编程语言的语句总是以特殊的char(*)开头,但它们可以采用两种形式:内联语句或多行。

如果是内联语句,则该行以*开头,并以换行符(\ r?\ n)结束。

如果有多行,则语句以*开头,语句可能会扩展为多行,后跟分号。

我很难使用Antlr4的lexer模式来实现这一目标。有人能指出我正确的方向吗?

我在下面给出了我的语法。 解析器显示以下示例的两个错误

line 5:21 extraneous input '\n' expecting {<EOF>, ID, SWITCH_CHAR}
line 8:31 extraneous input '\n' expecting {<EOF>, ID, SWITCH_CHAR}

示例:

first programming language 
*example one second programming language inline statement ending with semicolon;
*example two another valid second programming language inline statement ending with newline
*example three second programming language may expand to the next line
until semicolon char;
*example four second programming language example may expand 
to a number of lines
too ending with semicolon char;
first programming language again

词法:

lexer grammar ComplexLanguageLexer;
/*** SEA ****/
ID: [a-z]+;
WS: [ \t\f]+ -> skip;
SWITCH_CHAR: '*' -> pushMode(inline_mode), pushMode(multiline_mode);
NEWLINE:  '\r'? '\n' -> skip;

/***ISLANDS****/
mode multiline_mode;
MULTILINE_SWITCH_CHAR: ';' -> popMode;  //seek until ';'
MULTILINE_ID: [a-z]+;
MULTILINE_WS: [ \t\f]+ -> skip;
MULTILINE_NEWLINE:  '\r'? '\n' -> skip; //just skip newlines in the multiline mode

mode inline_mode;
INLINE_NEWLINE:  '\r'? '\n' -> type(NEWLINE), popMode;
INLINE_SEMICOLONCHAR: ';' ; //just match semicolonchar
INLINE_ID: [a-z]+;
INLINE_WS: [ \t\f]+ -> skip;

语法:

parser grammar ComplexLanguageParser;
options { tokenVocab = ComplexLanguageLexer ; }

startRule:   programStatement+;

programStatement:
    word | inlineStatement| multilineStatement
;

word: ID;

inlineStatement:
    SWITCH_CHAR INLINE_ID+ INLINE_SEMICOLONCHAR? NEWLINE
;

multilineStatement:
    SWITCH_CHAR MULTILINE_ID+ MULTILINE_SWITCH_CHAR
;

更新

我按照@ GRosenberg的说明更新了词法分析器/解析器语法:

词法

lexer grammar ComplexLanguageLexer;

SWITCH_CHAR: STAR -> pushMode(second_mode) ;
ID1         : ID ;
WS1         : WS -> skip ;
NL1         : NL -> skip ;

fragment STAR : '*' ;
fragment ID   : [a-z]+ ;
fragment WS   : [ \t\f]+ ;
fragment NL   : '\r'? '\n' ;

mode second_mode ;
    TERM1 : ( WS | NL )* SEMI -> popMode ;
    TERM2 : WS* NL -> popMode ;
    ID2   : ID ;
    WS2   : WS+ -> skip ;
    NL2   : NL;
    SEMI : ';';

语法

parser grammar ComplexLanguageParser;
options { tokenVocab = ComplexLanguageLexer ; }

startRule:  programStatement+;
programStatement:   firstLanguageStatement | secondLanguageStatment ;
firstLanguageStatement:    word ;
secondLanguageStatment:    SWITCH_CHAR (inlineStatement| multilineStatement)     ;
word: ID1;
multilineStatement:    (ID2|NL2)+ TERM1;
inlineStatement:   ID2+ TERM2;

它正在用于内联语句,但仍然不适用于多行语句。不确定我在这里做错了什么?

e.g。

first language            -> ok
*second language inline   -> ok 
*multi line;              -> ok
*multi line expands to 
 next line;                ->  token recognition error at ';'
*multi line
;                          -> ok
first language again       -> ok

1 个答案:

答案 0 :(得分:1)

使用单个堆栈实现pushModepopMode命令。所以,规则

SWITCH_CHAR: '*' -> pushMode(inline_mode), pushMode(multiline_mode);

应该导致词法分析器评估multiline_mode规则。在弹出窗口中,词法分析器将评估inline_mode规则。不太可能的是什么。

最好实现一个正确处理所有第二语言语句的词法分析器模式。基本理念是:

SWITCH_CHAR : STAR -> pushMode(second_mode) ;

mode second_mode ;
    STMT1 : ( ID | WS | NL )+ SEMI -> popMode() ;
    STMT2 : ( ID | WS )+ NL -> popMode() ;

未经测试,但应该提供的工作ID不包括STARSEMI

<强>更新

要将ID公开给解析器,只需将其从语句规则中删除:

SWITCH_CHAR: STAR -> pushMode(second_mode) ;
ID1         : ID ;
WS1         : WS -> skip ;
NL1         : NL -> skip ;

fragment STAR : '*' ;
fragment ID   : [a-z]+ ;
fragment WS   : [ \t\f]+ ;
fragment NL   : '\r'? '\n' ;

mode second_mode ;
    TERM1 : ( WS | NL )* SEMI -> popMode() ;
    TERM2 : WS+ NL -> popMode() ;
    ID2   : ID ;
    WS2   : WS+ -> skip ;

然而,这允许含糊不清:

 *example two inline statement ending with newline
 first programming language again (including a semicolon)

如果这是有效的,那么没有使用本机代码就没有足够的结构来消除歧义。

在去那里之前,可能更好的设计选择是将第一种语言和第二种语言之间的区别推迟到解析器,或者更好地分析生成的解析树。