Дутов писал:
Или я мог бы каждый раз повторять все строки, но:
это будет медленно
есть инструкции, которые я не хочу запускать дважды
Можно ли это сделать с помощью ANTLR, или, если нет, с чем-то другим?
Да, ANTLR может сделать это. Возможно, не из коробки, но с небольшим количеством специального кода, это возможно. Вам также не нужно повторно анализировать весь поток токенов для этого.
Допустим, вы хотите построчно анализировать очень простой язык, где каждая строка является либо декларацией program
, либо декларацией uses
, либо statement
.
Он всегда должен начинаться с объявления program
, за которым следуют ноль или более uses
объявлений, за которыми следует ноль или более statement
с. uses
объявления не могут идти после statement
с, и не может быть более одного program
объявления.
Для простоты statement
- это просто простое назначение: a = 4
или b = a
.
Грамматика ANTLR для такого языка может выглядеть следующим образом:
grammar REPL;
parse
: programDeclaration EOF
| usesDeclaration EOF
| statement EOF
;
programDeclaration
: PROGRAM ID
;
usesDeclaration
: USES idList
;
statement
: ID '=' (INT | ID)
;
idList
: ID (',' ID)*
;
PROGRAM : 'program';
USES : 'uses';
ID : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
INT : '0'..'9'+;
SPACE : (' ' | '\t' | '\r' | '\n') {skip();};
Но нам, конечно, нужно добавить пару проверок. Кроме того, по умолчанию парсер принимает поток токенов в своем конструкторе, но поскольку мы планируем перетекать токены в парсер построчно, нам необходимо создать новый конструктор в нашем парсере. Вы можете добавлять пользовательские элементы в классы лексеров или анализаторов, помещая их в раздел @parser::members { ... }
или @lexer::members { ... }
соответственно. Мы также добавим пару логических флагов, чтобы отслеживать, было ли уже объявлено объявление program
и разрешены ли объявления uses
. Наконец, мы добавим метод process(String source)
, который для каждой новой строки создает лексер, который передается парсеру.
Все это будет выглядеть так:
@parser::members {
boolean programDeclDone;
boolean usesDeclAllowed;
public REPLParser() {
super(null);
programDeclDone = false;
usesDeclAllowed = true;
}
public void process(String source) throws Exception {
ANTLRStringStream in = new ANTLRStringStream(source);
REPLLexer lexer = new REPLLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
super.setTokenStream(tokens);
this.parse(); // the entry point of our parser
}
}
Теперь внутри нашей грамматики мы собираемся проверить пару стробированных семантических предикатов , анализируем ли мы объявления в правильном порядке. И после анализа определенного объявления или оператора мы захотим перевернуть некоторые логические флаги, чтобы разрешить или запретить объявление с этого момента. Переключение этих логических флагов выполняется через секцию @after { ... }
каждого правила, которая выполняется (не удивительно) после , с которыми совпадают токены из этого правила синтаксического анализатора.
Ваш окончательный файл грамматики теперь выглядит следующим образом (включая некоторые System.out.println
для целей отладки):
grammar REPL;
@parser::members {
boolean programDeclDone;
boolean usesDeclAllowed;
public REPLParser() {
super(null);
programDeclDone = false;
usesDeclAllowed = true;
}
public void process(String source) throws Exception {
ANTLRStringStream in = new ANTLRStringStream(source);
REPLLexer lexer = new REPLLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
super.setTokenStream(tokens);
this.parse();
}
}
parse
: programDeclaration EOF
| {programDeclDone}? (usesDeclaration | statement) EOF
;
programDeclaration
@after{
programDeclDone = true;
}
: {!programDeclDone}? PROGRAM ID {System.out.println("\t\t\t program <- " + $ID.text);}
;
usesDeclaration
: {usesDeclAllowed}? USES idList {System.out.println("\t\t\t uses <- " + $idList.text);}
;
statement
@after{
usesDeclAllowed = false;
}
: left=ID '=' right=(INT | ID) {System.out.println("\t\t\t " + $left.text + " <- " + $right.text);}
;
idList
: ID (',' ID)*
;
PROGRAM : 'program';
USES : 'uses';
ID : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
INT : '0'..'9'+;
SPACE : (' ' | '\t' | '\r' | '\n') {skip();};
, который можно проверить в следующем классе:
import org.antlr.runtime.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
REPLParser parser = new REPLParser();
while(true) {
System.out.print("\n> ");
String input = keyboard.nextLine();
if(input.equals("quit")) {
break;
}
parser.process(input);
}
System.out.println("\nBye!");
}
}
Чтобы запустить этот тестовый класс, сделайте следующее:
# generate a lexer and parser:
java -cp antlr-3.2.jar org.antlr.Tool REPL.g
# compile all .java source files:
javac -cp antlr-3.2.jar *.java
# run the main class on Windows:
java -cp .;antlr-3.2.jar Main
# or on Linux/Mac:
java -cp .:antlr-3.2.jar Main
Как видите, вы можете объявить program
только один раз:
> program A
program <- A
> program B
line 1:0 rule programDeclaration failed predicate: {!programDeclDone}?
uses
не может прийти после statement
s:
> program X
program <- X
> uses a,b,c
uses <- a,b,c
> a = 666
a <- 666
> uses d,e
line 1:0 rule usesDeclaration failed predicate: {usesDeclAllowed}?
и вы должны начать с program
объявления:
> uses foo
line 1:0 rule parse failed predicate: {programDeclDone}?