Это более общий вопрос, так как я не знаю, с чего начать. По сути, я должен использовать Yacc для интерпретации / перевода выдуманного языка, называемого CALC, который может выполнять арифметические операции над переменными, а затем распечатывать их. У меня уже работает интерпретация, но вторая часть моего проекта — написать файл C ++, содержащий все инструкции в оригинальном переведенном файле.
У меня вопрос, как мне это сделать? Я пишу в файл внутри кода для моих правил грамматики, или я делаю это в основном? Я пытался сделать это внутри кода правила грамматики, но я обнаружил, что данные записываются в файл в обратном направлении (то есть, если я объявляю четыре переменные, программа выводит последнюю, объявленную в оригинале сначала программа, и так далее).
Ну, Yacc делает анализ снизу вверх.
У вас есть два варианта:
Вы можете выводить результаты в том виде, в каком они есть сейчас, когда ввод приводит к уменьшению, и присоединенный код выполняется, но тогда вам придется все время переупорядочивать.
Вы можете построить абстрактное синтаксическое дерево в ваших правилах, а затем пройти его по порядку. Это обычный подход.
Если у вас есть проблемы с использованием Yacc, вы должны взять на себя этот учебник: Пишите текстовые парсеры с помощью yacc и lex
Я бы сделал это в два этапа: используйте yacc для создания дерева разбора, а затем пройдитесь по дереву, чтобы вывести все необходимое для вывода.
данные записываются в файл задом наперед
Этот конкретный бит звучит так, как будто вы выбрали другую конструкцию, чем хотели. Yacc допускает как левую, так и правую рекурсию, что позволяет (например) анализировать список в порядке, в котором вы видите каждый элемент (левая рекурсия), или в обратном порядке. Вот пример правильной рекурсии, которую вы, вероятно, делаете сейчас:
list : ITEM
| ITEM list
Обратите внимание, что когда эта подграмма видит элемент ITEM во входных данных, она не знает, какое из этих двух производств использовать, основываясь только на этом — они оба начинаются с ITEM. Итак, парсер смотрит вперед на один токен. Если следующий токен не является ПУНКТОМ, то он знает, как использовать первую продукцию. Но если следующий токен — это ПУНКТ, то он должен обработать второе производство. Но это производство имеет нетерминальный list
в нем, что означает, что синтаксический анализатор на самом деле вообще не сокращается, а помещает первый элемент в стек и ищет другой list
,
В результате, если вы дадите этой подпрограмме список токенов ITEM, она будет просто помещать их в стек до тех пор, пока, наконец, не увидит ITEM, за которым не последует другой ITEM. На этом этапе конечный пункт будет сокращен на первое производство (и, следовательно, превратился в list
и все остальные ПУНКТЫ будут сокращены один за другим на втором производстве. На левой рекурсии.
list : ITEM /* matches first ITEM in list */
| list ITEM /* matches all other ITEMs in list */
На этот раз, когда анализатор видит пункт, только один из list
производство начинается с ПУНКТА, поэтому оно сразу же сокращается к первому производству, превращая этот ПУНКТ в list
, Теперь, если есть другой пункт во входном потоке, он будет пытаться сопоставить list
затем пункт, и это точно соответствует второй продукции.
Это общие идиомы для анализа простых списков. Обратите внимание, что если ваш список «ноль или более» вместо «один или несколько», то вы просто использовали бы пустой продукт вместо того, который содержит один пункт. С левой рекурсией (что обычно требуется), наличие отдельного производства, соответствующего первому элементу, дает вам удобное место для размещения действия, которое, например, инициализирует массив, который будет содержать все элементы, которые (могут) следовать.