Предиктивный синтаксический анализ — различия между версиями
(→Нерекурсивный нисходящий парсер) |
(→Нерекурсивный нисходящий парсер) |
||
Строка 150: | Строка 150: | ||
== Нерекурсивный нисходящий парсер == | == Нерекурсивный нисходящий парсер == | ||
− | [[Файл:Parse_table.png|350px | + | [[Файл:Parse_table.png|350px|right]] |
Рекурсивные разборщики можно генерировать автоматически, зная множества FIRST и FOLLOW, так как они имеют достаточно прозрачный шаблон построения. Альтернативным способом осуществления нисходящего синтаксического анализа является построение нерекурсивного нисходящего парсера. Его можно построить с помощью явного использования стека (вместо неявного при рекурсивных вызовах). Такое анализатор имитирует левое порождение. | Рекурсивные разборщики можно генерировать автоматически, зная множества FIRST и FOLLOW, так как они имеют достаточно прозрачный шаблон построения. Альтернативным способом осуществления нисходящего синтаксического анализа является построение нерекурсивного нисходящего парсера. Его можно построить с помощью явного использования стека (вместо неявного при рекурсивных вызовах). Такое анализатор имитирует левое порождение. | ||
Строка 187: | Строка 187: | ||
[[Файл:Parse_stack.png|500px|thumb|right]] | [[Файл:Parse_stack.png|500px|thumb|right]] | ||
+ | |||
+ | <tex> | ||
+ | (1) \ E \to TE' \\ | ||
+ | (2) \ E' \to +TE' \\ | ||
+ | (3) \ E' \to \varepsilon \\ | ||
+ | (4) \ T \to FT' \\ | ||
+ | (5) \ T' \to \times FT' \\ | ||
+ | (6) \ T' \to \varepsilon \\ | ||
+ | (7) \ F \to n \\ | ||
+ | (8) \ F \to (E) | ||
+ | </tex> | ||
+ | |||
+ | {| style="background-color:#CCC;margin:0.5px" | ||
+ | !style="background-color:#EEE"| | ||
+ | !style="background-color:#EEE"| n | ||
+ | !style="background-color:#EEE"| ( | ||
+ | !style="background-color:#EEE"| ) | ||
+ | !style="background-color:#EEE"| + | ||
+ | !style="background-color:#EEE"| * | ||
+ | !style="background-color:#EEE"| $ | ||
+ | |- | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>E</tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>1 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>1 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |- | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>E'</tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>3 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>2 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>3 </tex> | ||
+ | |- | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>T</tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>4 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>4 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |- | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>T'</tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>6 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>6 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>5 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>6 </tex> | ||
+ | |- | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>F</tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>7 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>8 </tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |- | ||
+ | |style="background-color:#FFF;padding:2px 30px"| <tex>end</tex> | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |style="background-color:#FFF;padding:2px 30px"| | ||
+ | |} |
Версия 00:38, 25 мая 2015
Для LL(1)-грамматик возможна автоматическая генерация парсеров, если известны множества FIRST и FOLLOW. Существуют общедоступные генераторы: ANTLR, GNU bison, Yacc.
Содержание
Общая схема построения рекурсивных парсеров с помощью FIRST и FOLLOW
Пусть
— LL(1)-грамматика. Построим для нее парсер.Для каждого нетерминала
: создадим функцию A() : Node, возвращающую фрагмент дерева разбора, выведенный из нетерминала .Здесь Node — структура вида:
Node children : list<Node> value : string // имя нетерминала или текст терминала addChild(Node) // функция, подвешивающая поддерево к данному узлу
Токен — один или несколько нетерминалов, для удобства объединяемые по смыслу в одну логическую единицу.
curToken — текущий токен строки.
nextToken() — записывает в curToken следующий за ним токен.
A() : Node res = Node("A") switch (curToken) : case: // for if is terminal consume( ) res.addChild(new Node(" ") nextToken() else Node t = res.addChild(t) break case : ... break ... default : error("unexpected char") return res
consume(char c) if (curToken != c) error("expected" + c) nextToken()
Такой парсер не только разбирает строку, но и находит ошибки в неудовлетворяющих грамматике выражениях.
Пример
Рассмотрим построение парсера на примере LL(1)-грамматики арифметических выражений.
Построим для нее множества здесь).
и (их построение подробно разобраноПравило | FIRST | FOLLOW |
---|---|---|
Псевдокоды
Построим функции обработки некоторых нетерминалов.
E() res = Node("E") switch(curToken) case 'n', '(' : res.addChild(T()) res.addChild(E'()) break default : error("unexpected char") return res
E'() res = Node("E'") switch(curToken) case '+' : consume('+') res.addChild(Node("+")) res.addChild(T()) res.addChild(E'()) break case '$', ')' : break default : error("unexpected char") return res
F() res = Node("F") switch(curToken) case 'n' : consume('n') res.addChild(Node("n")) break case '(' : consume('(') res.addChild(Node("(")) res.addChild(E()) consume(')') res.addChild(Node(")")) default : error("unexpected char") return res
Функции для
и строятся аналогично.Дерево разбора
Рассмотрим дерево разбора для выражения (1 + 2) * 3 и несколько первых шагов алгоритма рекурсивного разбора. Сначала вызывается функция стартового нетерминала грамматики, то есть
. Так как первым токеном является '(', то будет использовано первое правило разбора . Поэтому к вершине с меткой добавятся два ребёнка: и . А рекурсивный разборщик перейдёт к нетерминалу . По-прежнему curToken равен '(', поэтому в сработает второй case, первым ребёнком добавится '(', curToken станет равен , а разборщик перейдёт к нетерминалу . После того как выражение после '(', которое выводится из , будет полностью разобрано, функция рекурсивного разбора для добавит ')' последним сыном к этому нетерминалу.Продолжая в том же духе, мы построим всё дерево разбора данного выражения.
Нерекурсивный нисходящий парсер
Рекурсивные разборщики можно генерировать автоматически, зная множества FIRST и FOLLOW, так как они имеют достаточно прозрачный шаблон построения. Альтернативным способом осуществления нисходящего синтаксического анализа является построение нерекурсивного нисходящего парсера. Его можно построить с помощью явного использования стека (вместо неявного при рекурсивных вызовах). Такое анализатор имитирует левое порождение.
Нерекурсивный предиктивный синтаксический анализатор содержит дополнительно стек, содержащий последовательность терминалов и нетерминалов, и таблицу синтаксического анализа. На стеке располагается последовательность символов грамматики с маркером конца строки $ на дне. В начале процесса анализа строки стек содержит стартовый нетерминал грамматики непосредственно над символом $. Таблица синтаксического анализа представляет собой двухмерный массив М[X, а], где X — нетерминал, а — терминал или символ $.
Нерекурсивный синтаксический анализатор смотрит на текущий токен строки a и на символ на вершине стека X, а затем принимает решение в зависимости от одного из возникающих ниже случаев:
- если Х=curToken=$, синтаксический анализатор прекращает работу, так как разбор строки завершён,
- eсли Х=curToken≠$, синтаксический анализатор снимает со стека X и перемещает указатель входного потока к следующему токену (то есть вызывает nextToken),
- eсли X представляет собой нетерминал, программа рассматривает запись M[Х,а] таблицы разбора М. Эта запись представляет собой либо X-продукцию грамматики, либо запись об ошибке. Если, например, М[Х,а] = {X → UVW}, синтаксический анализатор замещает X на вершине стека на WVU (с U на вершине стека). В кач-ве выхода синтаксический анализатор просто выводит использованную продукцию. Если M[Х,а] = error, синтаксический анализатор вызывает программу восстановления после ошибки.
Псевдокод
function nonRecursiveParser(w : String): s : Stack s.push(bottom) s.push(Start) do X = s.top() if X == c c = nextToken() s.pop() else if X in Sigma error("unexpected symbol") else if M[X, c] == undefined // запись об ошибке error("unexpected symbol") else if M[X, c] == X -> Y_1 Y_2 ... Y_k s.pop() for i = k downto 1 s.push(Y_i) while s.top() != bottom
Пример
n | ( | ) | + | * | $ | |
---|---|---|---|---|---|---|