Предиктивный синтаксический анализ — различия между версиями
(→Общая схема построения парсеров с помощью FIRST и FOLLOW) |
|||
Строка 1: | Строка 1: | ||
{{В разработке}} | {{В разработке}} | ||
− | + | Для LL(1)-грамматик возможна автоматическая генерация парсеров, если известны множества FIRST и FOLLOW. Существуют общедоступные генераторы: [http://en.wikipedia.org/wiki/ANTLR ANTLR], [http://en.wikipedia.org/wiki/GNU_bison GNU bison], [http://en.wikipedia.org/wiki/Yacc Yacc]. | |
− | Для LL(1) грамматик возможна автоматическая генерация парсеров, если известны множества FIRST и FOLLOW. Существуют общедоступные генераторы: [http://en.wikipedia.org/wiki/ANTLR ANTLR], [http://en.wikipedia.org/wiki/GNU_bison GNU bison], [http://en.wikipedia.org/wiki/Yacc Yacc]. | + | |
+ | == Общая схема построения парсеров с помощью <tex>FIRST</tex> и <tex>FOLLOW</tex> == | ||
Пусть <tex>\Gamma</tex> {{---}} LL(1)-грамматика. Построим для нее парсер. | Пусть <tex>\Gamma</tex> {{---}} LL(1)-грамматика. Построим для нее парсер. | ||
− | Для каждого нетерминала A : <tex>A \rightarrow \alpha_1 \mid \alpha_2 \mid ... \mid \alpha_k </tex> создадим функцию A() : Node, возвращающую фрагмент дерева разбора, выведенный из нетерминала A. | + | Для каждого нетерминала <tex>A</tex> : <tex>A \rightarrow \alpha_1 \mid \alpha_2 \mid ... \mid \alpha_k </tex> создадим функцию A() : Node, возвращающую фрагмент дерева разбора, выведенный из нетерминала <tex>A</tex>. |
Здесь Node {{---}} структура вида: | Здесь Node {{---}} структура вида: | ||
Строка 17: | Строка 18: | ||
Токен {{---}} один или несколько нетерминалов, для удобства объединяемые по смыслу в одну логическую единицу. | Токен {{---}} один или несколько нетерминалов, для удобства объединяемые по смыслу в одну логическую единицу. | ||
curToken {{---}} текущий токен строки. | curToken {{---}} текущий токен строки. | ||
− | nextToken {{---}} следующий за ним токен. | + | nextToken() {{---}} записывает в curToken следующий за ним токен. |
A() : Node | A() : Node | ||
Строка 23: | Строка 24: | ||
switch (curToken) : | switch (curToken) : | ||
case : <tex>FIRST(\alpha_1) \cup ((\varepsilon \in FIRST(\alpha_1)) ? FOLLOW(A) : \varnothing)</tex> : | case : <tex>FIRST(\alpha_1) \cup ((\varepsilon \in FIRST(\alpha_1)) ? FOLLOW(A) : \varnothing)</tex> : | ||
− | // \alpha_1 = x_1x_2..x_{t_1} | + | // <tex>\alpha_1 = x_1x_2..x_{t_1}</tex> |
− | for x_1 .. x_{t_1} | + | for <tex>x_1 .. x_{t_1}</tex> |
− | if x_1 is terminal | + | if <tex>x_1</tex> is terminal |
− | consume(x_1) | + | consume(<tex>x_1</tex>) |
− | res.addChild(new Node("x_1") | + | res.addChild(new Node("<tex>x_1</tex>") |
nextToken() | nextToken() | ||
else | else | ||
− | Node t = X_1() | + | Node t = <tex>X_1()</tex> |
res.addChild(t) | res.addChild(t) | ||
break | break | ||
Строка 49: | Строка 50: | ||
== Пример == | == Пример == | ||
− | Рассмотрим построение парсера на примере | + | Рассмотрим построение парсера на примере LL(1)-грамматики арифметических выражений. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<tex> | <tex> | ||
Строка 68: | Строка 60: | ||
</tex> | </tex> | ||
− | + | Построим для нее множества FIRST и FOLLOW (их построение подробно разобрано [[Построение FIRST и FOLLOW#Пример | здесь]]). | |
{| style="background-color:#CCC;margin:0.5px" | {| style="background-color:#CCC;margin:0.5px" | ||
Строка 141: | Строка 133: | ||
return res | return res | ||
− | Функции для T и T' строятся аналогично. | + | Функции для <tex>T</tex> и <tex>T'</tex> строятся аналогично. |
{{TODO | t = Картинки примеров разбора чего-нибудь типа 1+2*3}} | {{TODO | t = Картинки примеров разбора чего-нибудь типа 1+2*3}} | ||
{{TODO | t = Построение таблицы предиктивного анализа}} | {{TODO | t = Построение таблицы предиктивного анализа}} |
Версия 17:06, 24 мая 2015
Для LL(1)-грамматик возможна автоматическая генерация парсеров, если известны множества FIRST и FOLLOW. Существуют общедоступные генераторы: ANTLR, GNU bison, Yacc.
Общая схема построения парсеров с помощью и
Пусть
— 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 (их построение подробно разобрано здесь).
Правило | 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
Функции для
и строятся аналогично.
TODO: Картинки примеров разбора чего-нибудь типа 1+2*3
TODO: Построение таблицы предиктивного анализа