Проверка того, что ваш код flex и yacc работает, путем генерации исходного кода для сравнения с оригинальным - PullRequest
1 голос
/ 09 января 2012

Я работаю над проектом по преобразованию сценариев, написанных на языке LSL Second Life, в Lua. Я изучаю flex и btyacc для этого проекта. На самом деле, это гораздо больший проект, это только часть его. http://www.sqlite.org/cgi/src В качестве первого шага я хочу убедиться, что производимый мной AST является точным отражением входных данных. Поэтому моя идея состоит в том, чтобы создать новый файл из этого AST, а затем сравнить их. Это означает, что мне нужно включить пробел и комментарии в AST, чтобы при использовании его для гееренирования файла результатов он содержал точно такой же пробел и комментарии.

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

Я бы подумал, что это стандартная вещь, но я просто не могу найти никаких примеров. Кто-нибудь знает примеры подобных вещей?

Исходный код находится на github, если кто-то хочет просмотреть его и предложить подход.

https://github.com/onefang/SledjHamr/blob/experimental/LuaSL/src LuaSL_yaccer.l, LuaSL_yaccer.y, LuaSL_LSL_tree.h и LuaSL_LSL_tree.c

У гибкой линии, которая распознает пробелы, действие закомментировано. Если я откомментирую, я получаю ошибку разбора.

РЕШЕНИЕ

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

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

struct AST_node
{
    struct AST_node  *left;
    struct AST_node  *right;
    char             *white_space;
    struct AST_token *token;    /* common() stashes a pointer to a token structure here.  */
    int               line, column;
    union
    {
        /* The various types of symbols are stored here as per usual, including this one - */
        int operationValue;
    } value;
}

struct AST_node *lval = calloc(1, sizeof(struct AST_node);

while((yv = yylex(lval)) != 0)
{
    Parse(pParser, yv, lval);
    lval = calloc(1, sizeof(struct AST_node));
}

В лексере у меня есть функция common (), которая вызывается из каждого раздела действий с регулярными выражениями. Помимо прочего, если это комментарий или пробел, я сохраняю текст в статическом аккумуляторе. Если это не комментарий или пробел, я сохраняю аккумулятор (если он существует) в структуре yylval и очищаю аккумулятор. Этот аккумулятор собирает пробелы и комментарии, поэтому любой из них в yylval может содержать оба.

Лексер не возвращает символ, если это просто пробел / комментарии, поэтому он накапливает их до тех пор, пока не дойдет до испускания действительного символа.

В лимоне все терминалы и нетерминалы являются типом, используемым для yylval, поэтому я могу передать это коду C в разделах действия. Например -

expr(A) ::= expr(B) LSL_ADD(D) expr(C).  { A = addOperation(D, LSL_ADD, B, C); }

LSL_ADD - это символ, излучаемый лексером, а D - это его значение, то есть yylval, который я создал в главном цикле для передачи лексеру. В этом случае addOperation () добавляет указатели на левый и правый узлы AST (B и C) в структуру AST (D) и помещает туда символ (так что я позже узнаю, что эта конкретная операция является дополнением) .

struct AST_node *addOperation(struct AST_node *lval, int type, struct AST_node *left, struct AST_node *right)
{
    lval->left = left;
    lval->right = right;
    lval->value.operationValue = type;
    return lval;
}

Позже, когда я восстанавливаю исходный код из AST, я просто выводю пробел / комментарий перед выводом символа в том же узле AST.

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

1 Ответ

3 голосов
/ 09 января 2012

Редко когда AST является точным, обратимым преобразованием исходного источника. Например, круглые скобки могут быть потеряны в процессе анализа (используется только для старшинства, но исключены в окончательном AST), или могут быть удалены пробелы.

Весьма распространено хранить смещения строк и / или символов для токенов, чтобы сообщения об ошибках могли ссылаться на их происхождение, но этого недостаточно для полной обратимости.

Я бы рекомендовал не иметь полностью обратимого AST, а вместо этого иметь набор тестов с известными результатами AST и входными данными, которые их производят. Но если нужно, вы можете хранить все пробелы вместе с терминальными токенами - например, если у вас есть код вроде:

1 + /* this is a comment */ 2

тогда узел AST, соответствующий +, будет содержать один пробел перед +, а узел для 2 будет содержать /* this is a comment */ в качестве дополнительных пробельных данных. Затем, когда вы отмените преобразование, вы можете восстановить этот пробел по ходу дела. Естественно, это потребует явного кодирования таких синтаксических функций, как скобки.

С помощью lex / yacc вы можете реализовать это, поддерживая отдельный накопитель пробелов / комментариев (или индексов во входном буфере); когда вы видите пробелы или комментарии, обновите этот аккумулятор, но не генерирует токен . Когда вы нажимаете любой другой токен, включая EOF, перемещаете эти данные в другой аккумулятор и сбрасываете основной аккумулятор. Как только вы вернетесь в yacc, ваши терминалы yacc смогут проверить этот вторичный аккумулятор и спрятать их в любую структуру данных, которую вы назначите терминалу.

...