Каков процедурный способ упорядочить файлы lex и parse? - PullRequest
0 голосов
/ 24 июня 2018

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

Итак, когда вы пишете простую функцию в source.txt, лексер находится в файле C, читает исходный файл и разбивает простую функцию на токены?

В DrКнига Доббса, есть файл, который содержит список уже определенных токенов, который записывается в файл def, если предыдущий вопрос верен, куда вписывается этот предопределенный список токенов?

Как только исходный файл прочитан lex, и токены разделены, как синтаксический анализатор получает информацию токена, которую выводит lex, и затем создает грамматику?

Я видел структуру typedef, использовавшуюся для того, что казалось небольшим разрывом предопределенных токенов и некоторой части лекса внутри.Так есть ли токены в 1 файле, лекс в 1 и парсер в 1?Или токены в 1, но lex и parse вместе вызывают этот файл токенов?

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

Ответы [ 2 ]

0 голосов
/ 24 июня 2018

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

Сначала вам понадобится определение токена, произведенного лексером. Это почти всегда структура с перечислением, указывающим, какой тип токена, и тип объединения для переноса любого значения, которое может иметь тип токена:

 struct Token {
     enum  // distinguishes token types
          { EndOfFile, Integer, String, Float, Identifier
            Semicolon, Colon, Plus, Minus, Times, Divide, LefParen, RightParen,
            KeywordBegin, KeywordDeclare, KeywordEnd, ...
          } tokentype
      union {
         long numeric_value; // holds numeric value of integer-valued token
         char* string_value; // holds ptr to string body of identifiers or string literals
         float float_value; // holds numeric value of floating-point valued token
            } tokenvalue
      }

Вы захотите построить абстрактное синтаксическое дерево. Для этого вам понадобится тип TreeNode. Как и токены, это почти всегда перечисление, указывающее, какой тип узла, и объединение для хранения различных свойств типа узла, и, наконец, список указателей на дочерние элементы:

      struct TreeNode {
          enum // distiguishes tree node types
             { Goal, Function, StatementList, Statement, LeftHandSide, Expression,
               Add, Subtract, Times, Divide, Identifier, FunctionCall, ...
             } nodetype
          children* TreeNode[4];  // a small constant here is almost always enough
          union // hold values specific to node type, often includes a copy of lexer union
             { long numeric_value; // holds:
                       // numeric value of integer-valued token
                       // index of built-in function number
                       // actual number of children if it varies
                       // ...
               char* string_value; // holds ptr to string body of identifiers or string literals
               float float_value; // holds numeric value of floating-point valued token
            } nodevalue
        }

MyCompiler.c содержит следующий код:

     int main() {
            filehandle Handle = OpenSourceFile(&FileName);
            ASTrootnode TreeNode = Parser(Handle);
            CodeGenerator(ASTrootnode);
            exit();
     }

Parser.c содержит следующий код:

     TreeNode Parser(filehandle Handle) {
            <parsingmachinery>
            nexttoken Token=Lexer(filehandle);
            <moreparsingmachinery to build tree nodes>
            return toplevel_TreeNode;
     }

Lexer.c содержит следующий код:

    Token Lexer(filehandle Handle) {
         token Token;
         <process characters in buffer>
         if bufferp=end_of_buffer
            fileread(filehandle,&bufferbody,bufferlength);
         <process characters in buffer>
         token.tokentype=<typeofrecognizedtoken>
         token.tokenvalue.long=<valueofnumerictoken>
         ...
         return Token;
    }

Очевидно, что вы захотите поместить объявления Token и TreeNode в заголовочные файлы, которые можно будет использовать в исходных файлах вашего компилятора.

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

Если вы хотите увидеть конкретные подробности о том, как создать синтаксический анализатор, который создает AST, см. Мой SO-ответ по созданию синтаксических анализаторов с рекурсивным спуском: https://stackoverflow.com/a/2336769/120163

0 голосов
/ 24 июня 2018

Да, второй пример и понимание, которое вы приводите, довольно типичны с одной настройкой.

Да, заранее определенный список токенов (обычно в перечислении, если разрешено) существует в заголовке или аналогичном. И лекс, и парсер могут ссылаться на этот заголовочный файл.

Но токены, фактически обнаруженные с помощью 'lex', передаются парсеру во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...