Предиктивный синтаксический анализ — различия между версиями
(→Общая схема построения парсеров с помощью FIRST и FOLLOW) |
(→Общая схема построения парсеров с помощью FIRST и FOLLOW) |
||
| Строка 9: | Строка 9: | ||
Здесь Node {{---}} структура вида: | Здесь Node {{---}} структура вида: | ||
Node | Node | ||
| − | children : list<Node> | + | children : list<Node> // список детей данного узла |
| − | value : string | + | value : string // терминал или не терминал, записанный в данном узле дерева |
| + | addChild(Node) // функция, подвешивающая поддерево к данному узлу | ||
Тут картинка про строку. | Тут картинка про строку. | ||
| + | |||
| + | Токен {{---}} один или несколько нетерминалов, для удобства объединяемые по смыслу в одну логическую единицу. | ||
| + | curToken {{---}} текущий токен строки. | ||
| + | nextToken {{---}} следующий за ним токен. | ||
A() : Node | A() : Node | ||
| Строка 40: | Строка 45: | ||
error("expected" + c) | error("expected" + c) | ||
nextToken() | nextToken() | ||
| + | |||
| + | Такой парсер не только разбирает строку, но и находит ошибки в неудовлетворяющих грамматике выражениях. | ||
== Пример == | == Пример == | ||
Версия 16:28, 24 мая 2015
Общая схема построения парсеров с помощью FIRST и FOLLOW
Для LL(1) грамматик возможна автоматическая генерация парсеров, если известны множества FIRST и FOLLOW. Существуют общедоступные генераторы: ANTLR, GNU bison, Yacc.
Пусть — LL(1)-грамматика. Построим для нее парсер.
Для каждого нетерминала A : создадим функцию A() : Node, возвращающую фрагмент дерева разбора, выведенный из нетерминала A.
Здесь Node — структура вида:
Node
children : list<Node> // список детей данного узла
value : string // терминал или не терминал, записанный в данном узле дерева
addChild(Node) // функция, подвешивающая поддерево к данному узлу
Тут картинка про строку.
Токен — один или несколько нетерминалов, для удобства объединяемые по смыслу в одну логическую единицу. curToken — текущий токен строки. nextToken — следующий за ним токен.
A() : Node
res = Node("A")
switch (curToken) :
case : :
// \alpha_1 = x_1x_2..x_{t_1}
for x_1 .. x_{t_1}
if x_1 is terminal
consume(x_1)
res.addChild(new Node("x_1")
nextToken()
else
Node t = X_1()
res.addChild(t)
break
case :
...
break
...
default :
error("unexpected char")
return res
consume(char c)
if (curToken != c)
error("expected" + c)
nextToken()
Такой парсер не только разбирает строку, но и находит ошибки в неудовлетворяющих грамматике выражениях.
Пример
Рассмотрим построение парсера на примере грамматики арифметических выражений. Запишем грамматику.
Данная грамматика не является LL(1)-грамматикой, так как содержит правое ветвление, от него нужно избавиться перед построением парсера:
Теперь грамматика стала LL(1)-грамматикой, построим для нее множества FIRST и FOLLOW (их построение подробно разобрано здесь).
| Правило | 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
Функции для T и T' строятся аналогично.
TODO: Картинки примеров разбора чего-нибудь типа 1+2*3
TODO: Построение таблицы предиктивного анализа