首页 > 解决方案 > 如何检测解释型编程语言中未使用的表达式结果?

问题描述

我正在研究一种简单的过程解释脚本语言,使用 ANTLR4 用 Ja​​va 编写。只是一个爱好项目。我使用 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 进行代码生成(在我的例子中是程序集生成)时这样做会更好吗?我只是看不出最好的地方。

标签: antlrantlr4

解决方案


IMO 这不是正确语法的问题,而是您如何处理 AST/解析树的问题。是否使用结果可以通过检查兄弟姐妹(和父兄弟姐妹等)来确定。例如,赋值由左值、运算符和右值组成,因此当您确定右值时,请检查前一个树节点兄弟是否是运算符。同样,您可以检查父项是否是括号表达式(用于嵌套函数调用、分组等)。


推荐阅读