| |
Каждому правилу можно поставить в соответствие некое действие, которое будет выполняться всякий раз, как это правило будет распознано. Действия могут возвращать значения и могут пользоваться значениями, возвращенными предыдущими действиями. Более того, лексический анализатор может возвращать значения для токенов (дополнительно), если хочется. Действие - это обычный оператор языка Си, который может выполнять ввод, вывод, вызывать подпрограммы и изменять глобальные переменные.
Действия, состоящие из нескольких операторов, необходимо заключать в фигурные скобки. Например:
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
.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |