Ответ 1
Создание AST с использованием JavaCC очень похоже на создание "нормального" парсера (определенного в файле jj
). Если у вас уже есть рабочая грамматика, она (относительно) проста:)
Вот шаги, необходимые для создания AST:
- переименуйте файл грамматики
jj
вjjt
- украсить его корневыми метками (курсивом являются мои собственные терминология...)
- invoke
jjtree
на вашей грамматикеjjt
, которая создаст для васjj
файл - вызывать
javacc
в вашей сгенерированной грамматикеjj
- скомпилировать сгенерированные исходные файлы
java
- проверить его
В этом случае пошаговое руководство, предполагающее, что вы используете MacOS или * nix, имеет файл javacc.jar
в том же каталоге, в котором находятся ваши грамматический файл и java
и javac
. ваша система PATH:
1
Предполагая, что ваш грамматический файл jj
называется TestParser.jj
, переименуйте его:
mv TestParser.jj TestParser.jjt
2
Теперь сложная часть: украшение вашей грамматики, чтобы создать правильную структуру АСТ. Вы украшаете AST (или node, или производственное правило (все равно)), добавляя #
, за которым следует идентификатор после него (и до :
). В вашем первоначальном вопросе у вас много #void
в разных постановках, а это значит, что вы создаете один и тот же тип АСТ для разных производственных правил: это не то, что вы хотите.
Если вы не украшаете свое производство, имя продукта используется как тип node (поэтому вы можете удалить #void
):
void decl() :
{}
{
var_decl()
| const_decl()
}
Теперь правило просто возвращает то, что AST возвращает правило var_decl()
или const_decl()
.
Теперь рассмотрим правило (упрощенное) var_decl
:
void var_decl() #VAR :
{}
{
<VAR> id() <COL> id() <EQ> expr() <SCOL>
}
void id() #ID :
{}
{
<ID>
}
void expr() #EXPR :
{}
{
<ID>
}
который я украсил типом #VAR
. Теперь это означает, что это правило вернет следующую древовидную структуру:
VAR
/ | \
/ | \
ID ID EXPR
Как вы можете видеть, терминалы отбрасываются из AST! Это также означает, что правила id
и expr
теряют текст, который соответствует их терминалу <ID>
. Конечно, это не то, что вы хотите. Для правил, которые должны содержать внутренний текст, согласованный с терминалом, необходимо явно указать .value
дерева на .image
совпадающего терминала:
void id() #ID :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void expr() #EXPR :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
в результате чего вход "var x : int = i;"
будет выглядеть следующим образом:
VAR
|
.---+------.
/ | \
/ | \
ID["x"] ID["int"] EXPR["i"]
Вот как вы создаете правильную структуру для своего АСТ. Ниже следует небольшая грамматика, которая является очень простой версией вашей собственной грамматики, включая небольшой метод main
для тестирования всего:
// TestParser.jjt
PARSER_BEGIN(TestParser)
public class TestParser {
public static void main(String[] args) throws ParseException {
TestParser parser = new TestParser(new java.io.StringReader(args[0]));
SimpleNode root = parser.program();
root.dump("");
}
}
PARSER_END(TestParser)
TOKEN :
{
< OPAR : "(" >
| < CPAR : ")" >
| < OBR : "{" >
| < CBR : "}" >
| < COL : ":" >
| < SCOL : ";" >
| < COMMA : "," >
| < VAR : "var" >
| < EQ : "=" >
| < CONST : "const" >
| < ID : ("_" | <LETTER>) ("_" | <ALPHANUM>)* >
}
TOKEN :
{
< #DIGIT : ["0"-"9"] >
| < #LETTER : ["a"-"z","A"-"Z"] >
| < #ALPHANUM : <LETTER> | <DIGIT> >
}
SKIP : { " " | "\t" | "\r" | "\n" }
SimpleNode program() #PROGRAM :
{}
{
(decl())* (function())* <EOF> {return jjtThis;}
}
void decl() :
{}
{
var_decl()
| const_decl()
}
void var_decl() #VAR :
{}
{
<VAR> id() <COL> id() <EQ> expr() <SCOL>
}
void const_decl() #CONST :
{}
{
<CONST> id() <COL> id() <EQ> expr() <SCOL>
}
void function() #FUNCTION :
{}
{
type() id() <OPAR> params() <CPAR> <OBR> /* ... */ <CBR>
}
void type() #TYPE :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void id() #ID :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void params() #PARAMS :
{}
{
(param() (<COMMA> param())*)?
}
void param() #PARAM :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void expr() #EXPR :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
3
Пусть класс jjtree
(включен в javacc.jar
) создает для вас файл jj
:
java -cp javacc.jar jjtree TestParser.jjt
4
Предыдущий шаг создал файл TestParser.jj
(если все прошло хорошо). Пусть javacc
(также присутствующий в javacc.jar
) обрабатывает его:
java -cp javacc.jar javacc TestParser.jj
5
Чтобы скомпилировать все исходные файлы, выполните:
javac -cp .:javacc.jar *.java
(в Windows, do: javac -cp .;javacc.jar *.java
)
6
Наступил момент истины: посмотрим, действительно ли все работает! Чтобы обработчик обработал вход:
var n : int = I;
const x : bool = B;
double f(a,b,c)
{
}
выполните следующее:
java -cp . TestParser "var n : int = I; const x : bool = B; double f(a,b,c) { }"
и на вашей консоли должно быть указано следующее:
PROGRAM decl VAR ID ID EXPR decl CONST ID ID EXPR FUNCTION TYPE ID PARAMS PARAM PARAM PARAM
Обратите внимание, что текст id
не отображается, но, поверьте, они там. Метод dump()
просто не показывает его.
НТН
ИЗМЕНИТЬ
Для рабочей грамматики, включая выражения, вы можете посмотреть на мой оценщик оценки: https://github.com/bkiers/Curta (грамматика находится в src/grammar
). Возможно, вы захотите посмотреть, как создавать корневые узлы в случае двоичных выражений.