我正在研究一种简单的过程解释脚本语言,使用ANTLR4用Java编写。只是一个爱好项目。我已经使用ANTLR4编写了一些DSL,词法器和解析器没有出现真正的问题。通过直接从解析树进行解释,我得到了相当多的语言工作,但是当我开始添加函数时,这种策略除了速度慢之外,开始崩溃。
因此,我基于"语言实现模式:创建自己的特定于域的通用编程语言"的第 10 章创建了一个基于堆栈的虚拟机。我有一个运行良好的 VM 汇编程序,我现在正在尝试使脚本语言通过 AST 生成程序集。
我不太明白的是,如何检测表达式或函数结果何时未使用,以便我可以生成 POP 指令以丢弃操作数堆栈顶部的值。
我希望赋值语句之类的东西是表达式,这样我就可以做这样的事情:
x = y = 1;
在 AST 中,赋值节点用符号(左值(进行批注,右值来自访问赋值节点的子节点。在赋值节点访问结束时,右值将存储到左值中,并将其重新加载回操作数堆栈中,以便可以将其用作表达式结果。
这将生成(对于x = y = 1
(:
CLOAD 1 ; Push constant value
GSTOR y ; Store into global y and pop
GLOAD y ; Push value of y
GSTOR x ; Store into global x and pop
GLOAD x ; Push value of x
但它需要在最后使用 POP 指令来丢弃结果,否则操作数堆栈会随着这些未使用的结果而开始增长。我看不出最好的方法。
我想我的语法可能有缺陷,这阻止了我在这里看到解决方案。
grammar g;
// ----------------------------------------------------------------------------
// Parser
// ----------------------------------------------------------------------------
parse
: (functionDefinition | compoundStatement)*
;
functionDefinition
: FUNCTION ID parameterSpecification compoundStatement
;
parameterSpecification
: '(' (ID (',' ID)*)? ')'
;
compoundStatement
: '{' compoundStatement* '}'
| conditionalStatement
| iterationStatement
| statement ';'
;
statement
: declaration
| expression
| exitStatement
| printStatement
| returnStatement
;
declaration
: LET ID ASSIGN expression # ConstantDeclaration
| VAR ID ASSIGN expression # VariableDeclaration
;
conditionalStatement
: ifStatement
;
ifStatement
: IF expression compoundStatement (ELSE compoundStatement)?
;
exitStatement
: EXIT
;
iterationStatement
: WHILE expression compoundStatement # WhileStatement
| DO compoundStatement WHILE expression # DoStatement
| FOR ID IN expression TO expression (STEP expression)? compoundStatement # ForStatement
;
printStatement
: PRINT '(' (expression (',' expression)*)? ')' # SimplePrintStatement
| PRINTF '(' STRING (',' expression)* ')' # PrintFormatStatement
;
returnStatement
: RETURN expression?
;
expression
: expression '[' expression ']' # Indexed
| ID DEFAULT expression # DefaultValue
| ID op=(INC | DEC) # Postfix
| op=(ADD | SUB | NOT) expression # Unary
| op=(INC | DEC) ID # Prefix
| expression op=(MUL | DIV | MOD) expression # Multiplicative
| expression op=(ADD | SUB) expression # Additive
| expression op=(GT | GE | LT | LE) expression # Relational
| expression op=(EQ | NE) expression # Equality
| expression AND expression # LogicalAnd
| expression OR expression # LogicalOr
| expression IF expression ELSE expression # Ternary
| ID '(' (expression (',' expression)*)? ')' # FunctionCall
| '(' expression ')' # Parenthesized
| '[' (expression (',' expression)* )? ']' # LiteralArray
| ID # Identifier
| NUMBER # LiteralNumber
| STRING # LiteralString
| BOOLEAN # LiteralBoolean
| ID ASSIGN expression # SimpleAssignment
| ID op=(CADD | CSUB | CMUL | CDIV) expression # CompoundAssignment
| ID '[' expression ']' ASSIGN expression # IndexedAssignment
;
// ----------------------------------------------------------------------------
// Lexer
// ----------------------------------------------------------------------------
fragment
IDCHR : [A-Za-z_$];
fragment
DIGIT : [0-9];
fragment
ESC : '\' ["\];
COMMENT : '#' .*? 'n' -> skip;
// ----------------------------------------------------------------------------
// Keywords
// ----------------------------------------------------------------------------
DO : 'do';
ELSE : 'else';
EXIT : 'exit';
FOR : 'for';
FUNCTION : 'function';
IF : 'if';
IN : 'in';
LET : 'let';
PRINT : 'print';
PRINTF : 'printf';
RETURN : 'return';
STEP : 'step';
TO : 'to';
VAR : 'var';
WHILE : 'while';
// ----------------------------------------------------------------------------
// Operators
// ----------------------------------------------------------------------------
ADD : '+';
DIV : '/';
MOD : '%';
MUL : '*';
SUB : '-';
DEC : '--';
INC : '++';
ASSIGN : '=';
CADD : '+=';
CDIV : '/=';
CMUL : '*=';
CSUB : '-=';
GE : '>=';
GT : '>';
LE : '<=';
LT : '<';
AND : '&&';
EQ : '==';
NE : '!=';
NOT : '!';
OR : '||';
DEFAULT : '??';
// ----------------------------------------------------------------------------
// Literals and identifiers
// ----------------------------------------------------------------------------
BOOLEAN : ('true'|'false');
NUMBER : DIGIT+ ('.' DIGIT+)?;
STRING : '"' (ESC | .)*? '"';
ID : IDCHR (IDCHR | DIGIT)*;
WHITESPACE : [ trn] -> skip;
ANYCHAR : . ;
所以我的问题是检测未使用的表达式结果的通常位置在哪里,即当表达式用作纯语句时?我应该在解析过程中检测到它,然后注释 AST 节点吗?还是在访问 AST 进行代码生成(在我的情况下为程序集生成(时这样做更好?我只是看不出在哪里最好。
IMO 这不是正确的语法问题,而是如何处理 AST/parse 树的问题。是否使用结果的事实可以通过检查兄弟姐妹(和父母兄弟姐妹等(来确定。例如,由左值、运算符和右值进行赋值,因此当您确定右值时,请检查上一个树节点同级是否是运算符。同样,您可以检查父项是否是括号表达式(用于嵌套函数调用、分组等(。
statement
: ...
| expression
如果使用# ExpressionStatement
标记此大小写,则可以通过覆盖侦听器中的exitExpressionStatement()
或访问者中的visitExpressionStatement
,在每个表达式语句之后生成弹出