Стиль зубров: плохо ли использовать мой собственный стек?Глобалы плохие? - PullRequest
2 голосов
/ 17 февраля 2012

Мой вопрос в основном "что представляет собой хороший стиль в YACC / Bison?"и, соответственно, позволяю ли я Bison делать то, что у него хорошо получается.

Например, я обнаружил, что моя программа Bison в большей степени опирается на глобальные переменные, чем мой исходный код.Рассмотрим следующее:

 prog : 
      vents{ /*handle semantics...*/ } 
      unity{ /*handle semantics...*/ } 
      defs;

Если я хочу передать информацию между двумя фигурными скобками, разделенными скобками, после «vents» и «unity», я думаю, что использую глобальную переменную (технически переменную с файлом-уровень и внутренняя связь) - лучшее, что я могу сделать с точки зрения сокрытия информации.Любая переменная, которую я объявляю в этих блоках, является локальной для ее блока (я думаю ...), а другие обозначенные места, в которые я могу поместить объявления C ++, приводят к области действия на уровне файла.

Если бы я мог внедрить объявление переменнойв функцию "yyparse ()", это будет лучше соответствовать моим потребностям.Есть ли ловушка для такого рода кода или какой-то другой способ ввести такую ​​переменную?Или глобалы являются просто принятой частью использования Bison?

Мне также пришло в голову, что, возможно, я даже не собираюсь передавать информацию между этими разделами таким образом.Но мне все сложно обойти, используя только $$, $ 1, $ 2 и т. Д.Я просто не «понимаю»?

Я считаю, что одна из моих глобальных переменных особенно сомнительна, даже если я принимаю остальные из них.Он имеет тип std :: stack и относится к поддержке языка ввода для условных выражений.

Когда я сталкиваюсь с условием ("if / else") на входе моего компилятора, это приводит к возможному выбросу трех меток на ассемблере, состоящих из текстовой строки, за которой следует число, извлеченное из последовательности.

Итак, я получаю порядковый номер, когда впервые сталкиваюсь с «if», помещаю его в стек (поскольку структуры «if» можно вкладывать), а затем использую его позже (через «peeks» или«pops») для создания необходимых меток и переходов, например, после моего состояния, моего блока if и моего блока else.

Я попытался заставить эту работу использовать что-то вроде $ -2,но обнаружил, что этот идентификатор относится не к началу моего условного, а к концу любого блока, который был только что скомпилирован.Система, абстрагированная от $, похоже, относится к коду, читаемому слева направо, без какой-либо концепции о том, как вложены в нее структуры.

Я не ожидаю, что вы все сделаете эту работу за меня... но был ли я хотя бы на правильном пути, пытаясь использовать $$, $ 1, $ -1 и так далее?Вполне возможно, что я просто сдался слишком рано, и / или мне было бы полезно воспользоваться подходом чистого листа, то есть выкинуть мой старый ad hoc код в целом.

Это тот случай?Или мой общий подход с его std: stack и его глобальными переменными в порядке?

Ответы [ 3 ]

2 голосов
/ 17 февраля 2012

Я не вижу трудности в том, чтобы избегать использования глобальных переменных, я лишь использую их для сигнализации об ошибках или подобных вещах.

Подумайте о парсере, что он должен производить?Абстрактное синтаксическое дерево ..

Как это сделано?Это n-арное дерево, в котором каждый узел содержит некоторую информацию и только его дочерние элементы, поэтому глобальные переменные не нужны.

Я покажу вам язык, который я пишу, просто чтобы дать вамидея:

bexp:
  bexp T_PLUS bexp { $$ = new ASTBExp($2,$1,$3); }
  | bexp T_MINUS bexp { $$ = new ASTBExp($2,$1,$3); }
  | bexp T_TIMES bexp { $$ = new ASTBExp($2,$1,$3); }
  | bexp T_DIV bexp { $$ = new ASTBExp($2,$1,$3); }

uexp:
  raw_value { $$ = $1; }
  | UOP_NOT uexp { $$ = new ASTUExp($1,$2); }
  | T_LPAREN bexp T_LPAREN { $$ = $2; }
  | var_ref { $$ = new ASTVarRef((ASTIdentifier*)$1); }
  | call { $$ = $1; }

Как видите, каждый проанализированный узел создается с потомком грамматики, которые также являются семантически потомками абстрактного синтаксического дерева и возвращаются в $$

Корневой элемент - это что-то вроде

start: root { Compiler::instance()->setAST((ASTRoot*)$1); }
;

root:
  function_list { $$ = new ASTRoot($1); }
;

, в котором я просто получаю все дерево и передаю его экземпляру моего Compiler класса.

Теперь, если вы посмотрите на функцию, котораязвонки yyparse()

bool parseSource()
{
  //yydebug = 1;
  freopen(fileName, "r", stdin);
  yyparse();

  return !failed;
}

Я просто открываю файл и вызываю процедуру разбора.Эта функция вызывается классом Compiler здесь:

  bool compile()
  {
    if (!parseSource())
      return false;

    if (!populateFunctionsTable())
      return false;

    ast->recursivePrint(0);
    Utils::switchStdout(binaryFile);
    ast->generateASM();
    Utils::revertStdout();

    assemble();

    return true;
  }

Как вы можете видеть, здесь вызывается процедура синтаксического анализа, которая создает целое дерево и затем устанавливает его внутри класса Compiler.Рекурсивное посещение дерева (функция generateASM) делает грязную работу.

Надеюсь, это немного прояснит, как вы должны использовать свой синтаксический анализатор, дайте мне знать, если вам нужна дополнительная информация.не нужно делать всю работу в парсере.Просто выполните там синтаксический анализ, все остальное можно решить с помощью рекурсивных вызовов через ваше абстрактное синтаксическое дерево.

Другой практический пример - оператор if / else, о котором вы говорите, в грамматике он определяется как

if_stat:
  KW_IF T_LPAREN exp T_RPAREN block %prec LOWER_THAN_ELSE { $$ = new ASTIfStat($3, $5); }
  | KW_IF T_LPAREN exp T_RPAREN block KW_ELSE block { $$ = new ASTIfStat($3, $5, $7); }
;

Создается специальный узел для управления конструкцией if / else, который затем работает просто, имея эту generateASM функцию:

 void generateASM()
  { 
    if (m_fbody == NULL)
    {
      m_condition->generateASM();
      printf("NOT\n");
      printf("JUMPC iflabel%u\n", labelCounter);
      m_tbody->generateASM();
      printf("iflabel%u:\r\n", labelCounter);

      ++labelCounter;
    }
    else
    {
      u32 c = labelCounter++;
      u32 d = labelCounter++;

      m_condition->generateASM();
      printf("JUMPC iflabel%u\n", c);
      m_fbody->generateASM();
      printf("JUMP iflabel%u\n", d);
      printf("iflabel%u:\n", c);
      m_tbody->generateASM();
      printf("iflabel%u:\n", d);
    }
  }
0 голосов
/ 20 февраля 2012

Вы можете объявить дополнительные данные для передачи, используя директиву %parse-param . Это позволяет вам скрывать дополнительные данные немного лучше, хотя вам также придется передавать их в функцию синтаксического анализа.

0 голосов
/ 17 февраля 2012

Правилам среднего уровня может быть присвоено значение в стеке.

Если у вас есть

rule
    : A B { ... } C

Бизон автоматически конвертирует это в

some_identifier
    : /* empty */ { ... }

rule
    : A B some_identifier C

и к его значению можно получить доступ именно так. В этом случае семантическое действие среднего правила имело значение, хранящееся в стеке Bison, а затем снова вызывалось в том же правиле.

Обычно эти функции являются рекурсивными. Рассмотрим простой следующий фрагмент

// C++
class Statement { public: virtual ~Statement() {} };
class Expression : public Statement {};
class IfStatement : public Statement { Statement* if_true; Expression* condition; }

// Bison
%type if_statement if_stmt
%type statement stmt
%union {
    IfStatement* if_stmt;
    Statement* stmt;
}

if_statement
    : if { $$ = new IfStatement(); } 
      '(' expression { $2->condition = $4; } 
      ')' statement { $2->if_true = $7; $$ = $2; }

statement
    : if_statement { $$ = $1; }
    | ...

Нет необходимости во внешнем стеке для выполнения рекурсивных функций, подобных этой.

...