Я работаю над простым процедурным интерпретируемым языком сценариев, написанным на Java с использованием ANTLR4. Просто хобби проект. Я написал несколько DSL с использованием ANTLR4, и лексер и парсер не представили реальных проблем. Я получил немного работы с языком, интерпретируя напрямую из дерева разбора, но эта стратегия, кроме медленной, начала разрушаться, когда я начал добавлять функции.
Итак, я создал виртуальную машину на основе стека, основанную на главе 10 «Шаблоны языковой реализации: создание собственных доменных и общих языков программирования». У меня есть ассемблер для виртуальной машины, который работает хорошо, и сейчас я пытаюсь заставить язык сценариев генерировать ассемблер через AST.
Что-то, чего я не могу понять, это как определить, когда выражение или результат функции не используется, чтобы я мог сгенерировать инструкцию POP, чтобы отбросить значение из верхней части стека операндов.
Я хочу, чтобы такие выражения, как операторы присваивания, были выражениями, чтобы я мог делать такие вещи, как:
x = y = 1;
В AST узел назначения обозначается символом (lvalue), а значение r получается из посещения дочерних элементов узла назначения. В конце посещения узла назначения значение r сохраняется в lvalue и перезагружается обратно в стек операндов, чтобы его можно было использовать в качестве результата выражения.
Это генерирует (для 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 для генерации кода (генерация сборки в моем случае)? Я просто не вижу, где это лучше всего сделать.