我想为简单的脚本语言制作一个简单的解析器,它有文本块和脚本块,在这些脚本块中,我希望能够定义一个函数,以及执行任何类型的通用语句。
我真的不需要知道或关心什么被归类为“声明”,但我确实需要解析函数声明。所以即使它看起来像一个 while 循环而且我没有 while 循环的规则,我可以匹配一个“通用语句规则”并以某种方式获取内容吗?
使用包罗万象的规则,我可以很好地完成“通用文本”部分,但在脚本模式下我不太成功,我尝试取消嵌套模式,在其中设置“功能中”模式,但一直遇到路障.
例如,当在我的
statement
内的 functionDeclaration
内时,我如何匹配所有内容直到 end function
。此外,我怎样才能只匹配“通用”语句,这样我就不需要像emptyStatement
或assignmentStatement
这样的语句类型。即使它只是变成一个大的“脚本代码块”,我也无所谓。
到目前为止我在哪里:
我的语法:
parser grammar ExprParser;
options { tokenVocab=ExprLexer; }
file
: block* EOF
;
block
: textBlock+
| script
;
textBlock
: HtmlDtd
| GenericText
| ScriptEnd
;
script
: topStatement+
| statement
;
topStatement
: functionDeclaration
;
functionDeclaration
: FunctionStart Ident L_PAREN R_PAREN statement* FunctionEnd
;
statement
: assignmentStatement
| emptyStatement
;
assignmentStatement
: Ident ASSIGNTO Ident SEMICOLON
;
emptyStatement
: SEMICOLON
;
我的词法分析器
lexer grammar ExprLexer;
channels { Comments, SkipChannel }
SeaWhitespace: [ \t\r\n\f]+ -> channel(HIDDEN);
HtmlDtd: '<!' .*? '>';
ScriptStart: SCRIPT_START_FRAGMENT -> channel(SkipChannel), pushMode(SCRIPT);
// Catch all text
GenericText : . ;
mode SCRIPT;
ScriptEnd :'%' '>' -> channel(SkipChannel), popMode;
ScriptWhitespace : [ \t\r\n\f]+ -> channel(SkipChannel);
// Comments begin with single quote
ScriptSingleLineComment: '\'' -> channel(SkipChannel), pushMode(SingleLineCommentMode);
FunctionStart : FUNCTION_START_FRAGMENT;
FunctionEnd : FUNCTION_END_FRAGMENT;
Ident : ID;
COMMA : ',';
SEMICOLON : ';';
L_PAREN : '(';
R_PAREN : ')';
ASSIGNTO : '=';
mode SingleLineCommentMode;
Comment: ~[\r\n?]+ -> channel(Comments);
CommentEnd: [\r\n] -> channel(SkipChannel), popMode; // exit from comment.
// Fragments
fragment ID: [a-zA-Z0-9_\u0080-\ufffe]+;
fragment NameString: [a-zA-Z_\u0080-\ufffe][a-zA-Z0-9_\u0080-\ufffe]*;
fragment SCRIPT_START_FRAGMENT : '<%';
fragment SCRIPT_END_FRAGMENT : '%>';
fragment FUNCTION_START_FRAGMENT : 'function';
fragment FUNCTION_END_FRAGMENT : 'end function'; // Space is required here
一些测试字符串
<! tagsIknow >
<tagsIdontknowbutwant>
<%
function xxx() 'this is a comment
x = y;
a = 1;
;
;
end function
a = 1;
b = 2;
%>
randomtext
<%
'another script
x = 3; 'inline comment again
%>
我想使用的脚本类型
blah
<%
function xxx()
while (true) ' notice I have no rule for a while loop
get me everything in here verbatim except for comments ' this ideally is trimmed
endwhile
end function ' I want everything until the 'end function' keyword, basically
%>
more generic text
编辑:
我的目标是这样输入
text1
<%
arbitrary script lines1
arbitrary script lines2
function x(a,b)
arbitrary script body containing anything
end function
arbitrary script lines3 again
%>
plain text
<%
arbitrary script lines4 again
function y()
different function body
end function
%>
所以我明白了:
PLAIN_TEXT_BLOB (matching TEXT1)
SCRIPT_BLOB (matching script lines 1 & 2 together)
FUNCTION
name: x
params: [a, b]
body: SCRIPT_BLOB (containing the body)
SCRIPT_BLOB (matching line 3)
PLAIN_TEXT_BLOB (matching 'plain text')
SCRIPT_BLOB (matching line 4)
FUNCTION
name: y
params: []
body: SCRIPT_BLOB (containing the body)
EOF
所以理论上只有三种“类型”,纯文本,脚本对象(多行)和函数(它们本身包含一些参数和一个脚本对象)
鉴于上述对象,我可以维护我遇到的顺序并适当处理,将“PLAIN TEXT”推出原始,按顺序运行“非函数脚本”,并按顺序声明函数。
问题是当我有一个贪婪规则时我似乎无法捕获函数名称或参数之类的东西(这是由于 ANTLR 用最贪婪的规则覆盖了这些规则),所以我不能有一个参数规则来确认它们适合一个标识符,同时有一个'.+'规则来收集函数体。
折衷方案是将函数作为一个整体收集(
function
和 end function
内的所有内容)并对该块进行第二次解析以解析函数头(名称 + 参数),尽量避免。
另一个想法是有一个额外的模式,一旦遇到
R_PAREN
就会进入“FUNCTION_BODY_MODE”,一旦找到 end function
就会弹出(两次)。这样,R_PAREN 和 end function
之间的任何东西都是函数的主体,在更高级别的模式中我可以有一个贪婪的规则。
有点像
FunctionStart: FUNCTION_START_FRAGMENT-> channel(SkipChannel), pushMode(IN_FUNCTION);
mode IN_FUNCTION;
FunctionBodyStart: R_PAREN_FRAGMENT -> channel(SkipChannel), pushMode(IN_FUNCTION_BODY);
mode IN_FUNCTION_BODY;
FunctionBodyAndFunctionEnd : FUNCTION_END_FRAGMENT -> channel(SkipChannel), popMode, popMode; // double pop
ALL_TEXT : . ; // will consume everything
我对上面的问题是它听起来非常违反直觉,而且我对 ANTLR 解析器非常陌生,所以只是试图获得最好的建议来做适合我的目的。
我不使用推送模式,而是使用
mode(...)
切换到另一种模式。这意味着您不需要弹出模式,从而更容易理解正在发生的事情。
我会选择这样的东西:
lexer grammar ExprLexer;
ScriptStart : '<%' -> mode(Script);
GenericText : . ;
fragment Spaces : [ \r\n\t]+;
fragment Id : [a-zA-Z0-9_\u0080-\ufffe]+;
mode Script;
ScriptEnd : '%>' -> mode(DEFAULT_MODE);
Comment : '\'' ~[\r\n]* -> skip;
Function : 'function' -> mode(FunctionDeclaration);
ScriptText : . ;
mode FunctionDeclaration;
FunctionName : Id;
DeclarationSpaces : Spaces+ -> skip;
OPar : '(' -> mode(FunctionParameter);
mode FunctionParameter;
ParameterName : Id;
ParameterSpaces : Spaces+ -> skip;
Comma : ',';
CPar : ')' -> mode(InFunction);
mode InFunction;
EndFunction : 'end' Spaces 'function' -> mode(Script);
FunctionSpaces : Spaces+ -> skip;
FunctionText : . ;
parser grammar ExprParser;
options { tokenVocab=ExprLexer; }
file
: block* EOF
;
block
: plainText
| ScriptStart script* ScriptEnd
;
plainText
: GenericText+
;
script
: ScriptText+
| function
;
function
: Function FunctionName OPar parameters? CPar functionBody EndFunction
;
functionBody
: FunctionText*
;
parameters
: ParameterName ( Comma ParameterName )*
;
这将解析您的输入:
text1
<%
arbitrary script lines1
arbitrary script lines2
function x(a,b)
arbitrary script body containing anything
end function
arbitrary script lines3 again
%>
plain text
<%
arbitrary script lines4 again
function y()
different function body
end function
%>
MU
像这样:
(file
(block
(plainText t e x t 1 \n))
(block <%
(script \n a r b i t r a r y s c r i p t l i n e s 1 \n a r b i t r a r y s c r i p t l i n e s 2 \n \n)
(script
(function function x ( (parameters a , b) )
(functionBody a r b i t r a r y s c r i p t b o d y c o n t a i n i n g a n y t h i n g) end function))
(script \n \n a r b i t r a r y s c r i p t l i n e s 3 a g a i n \n) %>)
(block
(plainText \n p l a i n t e x t \n))
(block <%
(script \n a r b i t r a r y s c r i p t l i n e s 4 a g a i n \n \n)
(script
(function function y ( )
(functionBody d i f f e r e n t f u n c t i o n b o d y) end function))
(script \n) %>)
(block
(plainText \n M U)) <EOF>)