next up previous contents
Next: Лексический анализ Up: YACC - еще один Previous: Входные спецификации   Contents

Действия

Каждому правилу можно поставить в соответствие некое действие, которое будет выполняться всякий раз, как это правило будет распознано. Действия могут возвращать значения и могут пользоваться значениями, возвращенными предыдущими действиями. Более того, лексический анализатор может возвращать значения для токенов (дополнительно), если хочется. Действие - это обычный оператор языка Си, который может выполнять ввод, вывод, вызывать подпрограммы и изменять глобальные переменные.

Действия, состоящие из нескольких операторов, необходимо заключать в фигурные скобки. Например:

A : '(' B ')' { hello( 1, "abc" ); } и

XXX : YYY ZZZ { printf("a message\n"); flag = 25; } являются грамматическими правилами с действиями.

Чтобы обеспечить связь действий с анализатором, используется спецсимвол "доллар" ($). Чтобы вернуть значение, действие обычно присваивает его псевдопеременной $$. Например, действие, которое не делает ничего, но возвращает единицу:

{ $$ = 1; } Чтобы получить значения, возвращенные предыдущими действиями и лексическим анализатором, действие может использовать псевдопеременные $1, $2 и т. д., которые соответствуют значениям, возвращенным компонентами правой части правила, считая слева направо. Таким образом, если правило имеет вид:

A : B C D ; то $2 соответствует значению, возвращенному нетерминалом C, a $3 - нетерминалом D. Более конкретный пример:

expr : '(' expr ')' ; Значением, возвращаемым этим правилом, обычно является значение выражения в скобках, что может быть записано так:

expr : '(' expr ')' { $$ = $2 ; } По умолчанию, значением правила считается значение, возвращенное первым элементом ($1). Таким образом, если правило не имеет действия, Yacc автоматически добаляет его в виде $$=$1; , благодаря чему для правила вида

A : B ; обычно не требуется самому писать действие.

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

A : B { $$ = 1; } C { x = $2; y = $3; } ; В результате разбора иксу (x) присвоится значение 1, а игреку (y) - значение, возвращенное нетерминалом C. Действие, стоящее в середине правила, считается за его компоненту, поэтому x=$2 присваивает X-у значение, возвращенное действием $$=1;

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

NEW_ACT : /* empty */ /* НОВОЕ ПРАВИЛО */ { $$ = 1; } ; A : B NEW_ACT C { x = $2; y = $3; } ; В большинстве приложений действия не выполняют ввода/вывода, а конструируют и обрабатывают в памяти структуры данных, например дерево разбора. Такие действия проще всего выполнять вызывая подпрограммы для создания и модификации структур данных. Предположим, что существует функция node, написанная так, что вызов node( L, n1, n2) создает вершину с меткой L, ветвями n1 и n2 и возвращает индекс свежесозданной вершины. Тогда дерево разбора может строиться так:

expr : expr '+' expr { $$ = node( '+', $1, $3 ); } Программист может также определить собственные переменные, доступные действиям. Их объявление и определение может быть сделано в секции объявлений, заключенное в символы %{ и %}. Такие объявления имеют глобальную область видимости, благодаря чему доступны как действиям, так и лексическому анализатору. Например:

%{ int variable = 0; %} Такие строчки, помещенные в раздел объявлений, объявляют переменную variable типа int и делают ее доступной для всех действий. Все имена внутренних переменных Yacca начинаются c двух букв y, поэтому не следует давать своим переменным имена типа yymy.



2004-06-22