Подтвердить что ты не робот

Как реализовать JJTree по грамматике

У меня есть назначение использовать JavaCC для создания анализатора сверху вниз с помощью семантического анализа для языка, предоставленного лектором. У меня есть правила производства, и ошибок нет. Я полностью зациклен на том, как использовать JJTree для моего кода, и мои часы очистки Интернета для учебных пособий не доставили меня никуда. Просто интересно, может ли кто-нибудь потратить некоторое время, чтобы объяснить, как реализовать JJTree в коде? Или, если есть скрытый пошаговый учебник, где-то там была бы большая помощь!

Вот некоторые из моих правил производства, если они помогают. Спасибо заранее!

void program() : {}
{
  (decl())* (function())* main_prog()
}

void decl() #void : {}
{
  (
    var_decl() | const_decl()
   )
}

void var_decl() #void : {}
{
  <VAR> ident_list() <COLON> type()
 (<COMMA> ident_list() <COLON> type())* <SEMIC>
}

void const_decl()  #void : {}
{
  <CONSTANT> identifier() <COLON> type() <EQUAL> expression()
 ( <COMMA> identifier() <COLON> type() <EQUAL > expression())* <SEMIC>
} 

void function() #void : {}
{
  type() identifier() <LBR> param_list() <RBR>
  <CBL>
  (decl())*
  (statement() <SEMIC> )*
  returnRule() (expression() | {} )<SEMIC>
  <CBR>
}
4b9b3361

Ответ 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). Возможно, вы захотите посмотреть, как создавать корневые узлы в случае двоичных выражений.