[содержание] [назад] [пред] [вверх] [след] [вперед] 6. алгоритм анализатора bisonкогда bison читает лексемы, он помещает их в стек вместе с их семантическими значениями. стек называется стеком анализатора. помещение лексемы в стек традиционно называется сдвигом. например, предположим, что инфиксный калькулятор прочёл `1 + 5 *', а далее во входном тексте следует `3'. стек будет содержать четыре элемента, по одному на каждую лексему, для которой был выполнен сдвиг. но стек не всегда содержит по элементу для каждой прочитанной лексемы. когда последние n лексем и групп, для которых был выполнен сдвиг, соответствуют компонентам правила грамматики, они могут быть объединены в соответствии с этим правилом. это называется свёрткой. эти лексемы и группы заменяются в стеке одной группой, символ которой является результатом (левой частью) этого правила. выполнение действия правила -- часть процесса свёртки, потому что именно тогда вычисляется семантическое значение полученной группы. например, если стек анализатора инфиксного калькулятора содержит: 1 + 5 * 3 и следующая лексема -- это литера новой строки, то последние три элемента могут быть свёрнуты до 15 правилом: expr: expr '*' expr; после этого стек будет содержать только три элемента: 1 + 15 в этот момент можно произвести ещё одну свёртку, получая единственное значение 16. затем можно выполнить сдвиг для лексемы новой строки. анализатор пытается сдвигами и свёртками свернуть весь входной текст до единственной группы, символом которой является начальный символ грамматики (см. раздел 2.1 языки и контекстно-свободные грамматики). этот тип анализаторов известен в литературе как анализатор снизу вверх. 6.1 предпросмотренные лексемыанализатор bison не всегда выполняет свёртку немедленно, как только последние n лексем и групп соответствуют правилу. причина этого в том, что такая простая стратегия не подоходит для обработки большинства языков. вместо этого, когда свёртка возможна, анализатор иногда "смотрит вперёд" на следующую лексему, чтобы решить, что делать. когда лексема прочитана, сдвиг не выполняется сразу же, сначала она становится предпросмотренной лексемой, которая не помещена в стек. теперь анализатор может выполнить одну или более свёрток лексем и групп в стеке, в то время, как предпросмотренная лексема остаётся за его пределами. когда больше свёрток выполнять не следует, предпросмотренная лексема сдвигается в стек. это не означает, что произведены все возможные свёртки, это зависит от типа предпросмотренной лексемы, некоторые правила могут предпочесть отложить своё применение. вот простой случай, когда нужен предпросмотр. эти три правила определяют выражения, содержащие бинарную операцию сложения и постфиксную унарную операцию факториала (`!'), а также допускают группировку скобками. expr: term '+' expr | term ; term: '(' expr ')' | term '!' | number ;
предположим, что прочитаны и сдвинуты лексемы `1 + 2', что следует
делать дальше? если далее следует лексемы `)', то первые три лексемы
должны быть свёрнуты до
если же далее следует лексема `!', она должна быть немедленно сдвинута,
чтобы `2 !' можно было свернуть до
текущая предпросмотренная лексема находится в переменной 6.2 конфликты сдвиг/свёрткапредположим, мы разбираем язык, имеющий операторы if-then и if-then-else, с помощью такой пары правил: if_stmt: if expr then stmt | if expr then stmt else stmt ;
здесь мы предполагаем, что
когда лексемы такая ситуация, когда допустимы как сдвиг, так и свёртка, называется конфликтом сдвиг/свёртка. bison разработан так, что он разрешает эти конфликты, выбирая сдвиг, за ислючением случаев, когда объявления приоритета операций указывают обратное. чтобы понять причину этого решения, сравним его с другой возможной альтернативой.
поскольку анализатор предпочитает сдвинуть if x then if y then win (); else lose; if x then do; if y then win (); else lose; end; но если анализатор выбирал при возможности свёртку, а не сдвиг, в результате предложение else относилось бы к самому внешнему оператору if, что сделает эквивалентными следующие два входных текста: if x then if y then win (); else lose; if x then do; if y then win (); end; else lose;
конфликт возникает потому что грамматика написана неоднозначно: правомерен
любой способ разбор простого вложенного оператора if. общепринятое соглашение
состоит в том, что такие неоднозначности разрешаются отнесением предложения
else к самому внутреннему оператору if. именно это делает bison, выбирая сдвиг,
а не свёртку (идеальным было бы написать однозначную грамматику, но в данном
случае это сделать очень трудно). эта конкретная неоднозначность впервые
встретилась в спецификации algol 60 и называется неоднозначностью "кочующего
чтобы избежать предупреждений bison о предсказуемых, законных конфликтах
сдвиг/свёртка используйте объявление
определение %token if then else variable %% stmt: expr | if_stmt ; if_stmt: if expr then stmt | if expr then stmt else stmt ; expr: variable ; 6.3 приоритет операцийдругая ситуация, когда возникают конфликты сдвиг/свёртка, -- это в арифметических выражениях. здесь сдвиг -- не всегда предпочтительное решение, объявления bison приоритета операций позволят вам указать, когда выполняется сдвиг, а когда свёртка. 6.3.1 когда необходим приоритетрассмотрим следующий фрагмент неоднозначной грамматики (неоднозначной, потому что входной текст `1 - 2 * 3' может юбыть разобран двумя разными способами): expr: expr '-' expr | expr '*' expr | expr '<' expr | '(' expr ')' ... ; предположим, что анализатор рассмотрел лексемы `1', `-' и `2'. должен ли он выполнить свёртку по правилу для операции вычитания? это зависит от следующей лексемы. конечно, если далее следует лексема `)', мы должны выполнить свёртку, сдвиг будет неверным решением, потому что ни одно правило не может свернуть ни последовательность лексем `- 2 )', ни что-либо, начинающееся с неё. но если следующая лексема -- `*' или `<', у нас есть выбор: как сдвиг, так и свёртка позволят удачно завершить разбор, но с разными результатами. чтобы решить, что должен делать bison, мы должны рассмотреть результаты. если сдвинуть следующую лексему знака операции op, она должна быть свёрнута первой, чтобы дать возможность свернуть разность. результатом (на самом деле) будет `1 - (2 op 3'. с другой стороны, если вычитание свернуть до сдвига op, результатом будет `(1 - 2) op 3'. поэтому ясно, что выбор сдвига или свёртки должен зависеть от относительного приоритета операций `-' и op: `*' должна быть сначала сдвинута, а `<' -- нет. а что можно сказать о таком входном тексте, как `1 - 2 - 5', должен ли он означать `(1 - 2) - 5' или `1 - (2 - 5'? для большинства операций мы предпочтаем первый вариант, называемый левой ассоциативностью. второй вариант -- правая ассоциативность желателен для операций присваивания. выбор левой или правой ассоциативности -- это вопрос о том, будет анализатор выбирать сдвиг или свёртку, когда стек содержит `1 - 2' и предпросмотренная лексема -- `-'. сдвиг даёт правую ассоциативность. 6.3.2 задание приоритета операций
bison позволяет вам указать требуемый выбор с помощью объявлений приоритета
операций
относительный приоритет различных операций управляется порядком, в котором они
объявляются. первое в файле объявление 6.3.3 примеры приоритетав нашем примере нам нужны были следующие объявления: %left '<' %left '-' %left '*'
в более законченном примере, поддерживающем также другие операции, нам
следует объявлять их группами равного приоритета. например, %left '<' '>' '=' ne le ge %left '+' '-' %left '*' '/'
(здесь 6.3.4 как работает приоритетво-первых, объявление приоритета присваивает уровни приоритета объявленным терминальным символам. во-вторых, присваиваются уровни приоритета определённым правилам: каждое правило получает приоритет, равный приоритету последнего терминального символа среди его компонентов (вы можете также явно задать приоритет правила. см. раздел 6.4 контекстно-зависимый приоритет.) наконец, конфликт разрешается сравнением приоритета рассматриваемого правила с приоритетом предпросмотренной лексемы. если приоритет лексемы выше, выполняется сдвиг, если ниже -- свёртка. если приоритеты одинаковы, выбор делается исходя из ассоциативности этого уровня приоритета. подробный выходной файл, создаваемый при использовании параметра `-v' (см. раздел 10. вызов bison), объясняет, как был разрешён каждый конфликт. не все правила и не все лексемы имеют приоритет. если у правила или у предпросмотренной лексемы нет приоритета, по умолчанию производится сдвиг. 6.4 контекстно-зависимый приоритетчасто приоритет операции зависит от контекста. на первый взгляд, это звучит странно, но на самом деле это очень распространённый случай. например, знак "минус" обычно имеет очень высокий приоритет как унарная операция, и несколько меньший (меньший, чем умножение) как бинарная операция.
объявления приоритета bison --
модификатор %prec терминальный_символ он ставится после всех компонентов правила. в результате правилу вместо приоритета, который должен быть присвоен ему обычным способом, присваивается приоритет символа терминальный_символ. затем изменённый приоритет правила влияет на разрешение конфликтов с участием этого правила (см. раздел 6.3 приоритет операций).
вот как ... %left '+' '-' %left '*' %left uminus
теперь приоритет exp: ... | exp '-' exp ... | '-' exp %prec uminus 6.5 состояния анализатора
функция каждый раз, когда читается предпросмотренная лексема, текущее состояние анализатора и тип предпросмотренной лексемы ищутся в таблице. эта ячейка таблицы может говорить, например: "выполнить сдвиг для предпросмотренной лексемы". в этом случае она также задаёт новое состояние анализатора, помещаемое на вершину стека. или же она может говорить: "выполнить свёртку, используя правило номер n". это означает, что определённое количество лексем и групп снимаются с вершины стека и заменяются одной группой. другими словами, из стека вынимаются несколько состояний, и в него помещается одно новое. есть ещё одна возможность -- таблица может сказать, что предпросмотренная лексема в текущем состоянии недопустима. это запускает процесс обработки ошибок (см. раздел 7. восстановление после ошибок). 6.6 конфликты свёртка/свёрткаконфликт свёртка/свёртка возникает, когда есть два правила или более, применимых к одной последовательности входного текста. это обычно свидетельствует о серьёзной ошибке в грамматике.
например, вот ошибочная попытка определить последовательность нуля или более
групп sequence: /* пусто */ { printf ("пустая последовательность\n"); } | maybeword | sequence word { printf ("добавлено слово %s\n", $2); } ; maybeword: /* пусто */ { printf ("maybeword пусто\n"); } | word { printf ("одиночное слово %s\n", $1); } ;
ошибка состоит в неоднозначности: есть более одного способа разобрать
одиночное слово
есть также более одного способа свёртки "совсем ничего" в вы можете думать, что это безразлично, потому что это не влияет на то, является какой-либо входной текст правильным, или нет. но это влияет на то, какие правила выполняются. один порядок разбора выполнит действие второго правила, другой -- действия первого и третьего правила. в этом примере изменится вывод программы.
bison разрешает конфликты свёртка/свёртка, выбирая правило, появляющееся в
грамматике раньше, но полагаться на это очень рискованно. каждый конфликт
свёртка/свёртка должен быть изучен и обычно исключён. вот правильный способ
определения sequence: /* пусто */ { printf ("пустая последовательность\n"); } | sequence word { printf ("добавлено слово %s\n", $2); } ; вот ещё одна распространённая ошибка, приводящая к конфликтам свёртка/свёртка: sequence: /* пусто */ | sequence words | sequence redirects ; words: /* пусто */ | words word ; redirects:/* пусто */ | redirects redirect ;
здесь сделана попытка определить последовательность, которая может содержать
либо группы
а именно: "совсем ничто" может быть есть два способа исправить эти правила. во-первых, сделать последовательность одноуровнневой: sequence: /* пусто */ | sequence word | sequence redirect ;
во-вторых, запретить, чтобы sequence: /* пусто */ | sequence words | sequence redirects ; words: word | words word ; redirects:redirect | redirects redirect ; 6.7 загадочные конфликты свёртка/свёрткаиногда могут возникать конфликты свёртка/свёртка, выглядящие неоправданными. вот пример: %token id %% def: param_spec return_spec ',' ; param_spec: type | name_list ':' type ; return_spec: type | name ':' type ; type: id ; name: id ; name_list: name | name ',' name_list ;
может показаться, что эта грамматика может быть разобрана с предпросмотром
только на одну лексему: если прочитано
тем не менее, bison, как и большинство генераторов анализаторов, на самом деле
может обрабатывать не все lr(1)-грамматики. в этой грамматике два контекста --
после в общем, лучше устранить недостатки, чем документировать их. но этот конкретный недостаток по его природе трудно устранить -- генератор анализаторов, который может обрабатывать lr(1)-грамматики трудно написать, и он будет создавать очень большие анализаторы. на практике bison более полезен в том виде, в котором он существует сейчас.
когда возникает эта проблема, вы часто можете решить её, выявив два состояния
анализатора, которые были смешаны, и добавляя что-нибудь, чтобы они выглядели
по-разному. в вышеприведённом примере добавление одного правила к
%token bogus ... %% ... return_spec: type | name ':' type /* это правило никогда не используется. */ | id bogus ;
это решит проблему, поскольку появляется возможность использования ещё одного
правила в контексте после
в этом конкретном примере есть и другой способ решения проблемы:
переписать правило для param_spec: type | name_list ':' type ; return_spec: type | id ':' type ; 6.8 переполнение стека и как его избежать
стек анализатора bison может переполниться, если для слишком многих лексем
выполнен сдвиг и не выполнена свёртка. когда это происходит: функция
анализатора
определяя макрос
не обязательно выделять всё допустимое для стека пространство. если вы
задаёте большое значение
значение
вы можете управлять размером стека, выделяемого при инициализации,
определяя макрос [содержание] [назад] [пред] [вверх] [след] [вперед] |