antlr - 如何检测解释型编程语言中未使用的表达式结果?
问题描述
我正在研究一种简单的过程解释脚本语言,使用 ANTLR4 用 Java 编写。只是一个爱好项目。我使用 ANTLR4 编写了一些 DSL,词法分析器和解析器没有出现真正的问题。通过直接从解析树进行解释,我得到了相当多的语言工作,但是当我开始添加函数时,这种策略除了速度很慢之外开始崩溃。
所以我创建了一个基于堆栈的虚拟机,基于“语言实现模式:创建自己的特定领域和通用编程语言”的第 10 章。我有一个运行良好的 VM 汇编器,我现在正试图让脚本语言通过 AST 生成汇编。
我不太明白的是如何检测表达式或函数结果何时未被使用,以便我可以生成一个 POP 指令来丢弃操作数堆栈顶部的值。
我希望赋值语句之类的东西成为表达式,以便我可以执行以下操作:
x = y = 1;
在 AST 中,赋值节点用符号(左值)注释,右值来自访问赋值节点的子节点。在赋值节点的访问结束时,将右值存储到左值中,并将其重新加载回操作数堆栈中,以便将其用作表达式结果。
这会生成 ( for 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 : [ \t\r\n] -> skip;
ANYCHAR : . ;
所以我的问题是检测未使用的表达式结果的通常位置在哪里,即当表达式用作普通语句时?我应该在解析过程中检测到它,然后注释 AST 节点吗?或者在访问 AST 进行代码生成(在我的例子中是程序集生成)时这样做会更好吗?我只是看不出最好的地方。
解决方案
IMO 这不是正确语法的问题,而是您如何处理 AST/解析树的问题。是否使用结果可以通过检查兄弟姐妹(和父兄弟姐妹等)来确定。例如,赋值由左值、运算符和右值组成,因此当您确定右值时,请检查前一个树节点兄弟是否是运算符。同样,您可以检查父项是否是括号表达式(用于嵌套函数调用、分组等)。
推荐阅读
- javascript - 在 HTML 表单中添加 Spotify 链接
- c# - 在我尝试将记录插入我的 SQL Server 时,列名“T001”无效
- ios - Amazon s3 传输实用程序无法在后台运行
- css - 使用 HTML 为按钮创建边框的问题
- docker - 在 Jupyter docker 容器上运行的 Py2neo 函数无法连接到 neo4j docker 容器
- scheme - 计划中的汽车实施
- powershell - 如何使这个powershell命令在一个循环中批量工作?
- javascript - 转换 Javascript 多维数组格式
- c# - 有没有办法创建一个可以改变变量数据类型的函数?
- elasticsearch - Elasticsearch:如何在日期范围之间的 url 查询中获取 store.size 和 pri.store.size 每天的大小