ユーザーは、規則が認識されたときに実行するアクションを各構文規則に関連付けることができます。アクションは、値を返したり、他のアクションによって返された値を取得できます。また、字句アナライザは、必要であれば、トークンの値を返すことができます。
アクションは、C 言語の文として入力および出力を行なったり、サブルーチンを呼び出したり、配列や変数を変更できます。アクションは、{ と } で囲まれた 1 つ以上の文によって指定されます。アクションを持った構文規則の例を以下に 2 つ示します。
A : '(' B ')' { hello( 1, "abc" ); }
XXX : YYY ZZZ { (void) printf("a message¥n"); flag = 25; }
$ 記号は、アクションとパーサーの間の伝達を容易にするために使用されます。擬似変数 $$ は、完了したアクションによって返される値を表します。
たとえば、以下のアクションは値 1 を返します。実際、このアクションが行うことはそれだけです。
{ $$ = 1; }
アクションは擬似変数 $1、$2、... $n を使用して、他のアクションと字句アナライザによって返された値を取得できます。これらの擬似変数は、規則の右側にある要素 1 〜 n によって返された値を参照します。要素には、左から右の順に番号が割り当てられます。以下のような規則の場合には、$2 は C によって返された値、$3 は D によって返された値を持つことになります。
A : B C D ;
以下の規則で、一般的な例を示します。
expr : '(' expr ')' ;
この規則で値は、括弧内の expr の値に意味があります。アクションの最初の要素はリテラルの左括弧なので、目的の論理的な結果は以下の記述によって表すことができます。
expr : '(' expr ')' { $$ = $2 ; }
デフォルトでは、規則の値はその規則内の最初の要素の値 ($1) になります。したがって、以下の形式の構文規則では、明示的なアクションを持つ必要性はほとんどありません。
A : B ;
前述の例では、アクションは規則の終わりにすべて記述されています。場合によっては、規則が完全に構文解析される前に制御を行う必要があります。yacc では、規則の終了部だけでなく、規則の途中にもアクションを記述できます。
このアクションは、通常の $ メカニズムでアクセスできる値がこのアクションの中の記述によって返されると仮定しています。同様に、このアクションは、その左側のシンボルによって返された値にアクセスできます。したがって、以下の規則の場合には、x は 1 に設定され、y は C によって返される値に設定されます。
A : B { $$ = 1; } C { x = $2; y = $3; } ;
規則を終端していないアクションは、yacc によって扱われます。yacc は、新しい非終端記号の名前と、その名前を空の文字列に一致させる新しい規則を作成します。内部のアクションは、この追加された規則を認識することによって起動されるアクションです。
yacc は、以下のように記述されているかのように上記の例を取り扱います。
$ACT : /* 空 */ { $$ = 1; } ; A : B $ACT C { x = $2; y = $3; } ;
$ACT は空のアクションです。
多くのアプリケーションでは、出力はアクションの直接的な結果ではありません。構文解析ツリーなどのデータ構造がメモリー内で構築され、そのデータ構造に変換処理が適用されてから出力が生成されます。ツリー構造の作成と保守を行うために必要なルーチンは用意されているので、構文解析ツリーは非常に簡単に構築できます。
たとえば、以下のようにして呼び出した場合に、ラベル L とその派生 n1 および n2 を持ったノードを作成し、その新しく作成したノードのインデックスを返す node という C 関数があると仮定します。
node( L, n1, n2 )
その場合、構文解析ツリーは、以下のような仕様のアクションを用意することによって構築できます。
expr : expr '+' expr { $$ = node( '+', $1, $3 ); }
ユーザーは、アクションで使用する他の変数を定義できます。宣言と定義は、宣言セクションで %{ と %} の間に記述します。これらの宣言と定義の適用範囲はグローバルスコープであるため、アクション文に認識されます。また、字句アナライザにも認識されるようにすることも可能です。たとえば、以下の定義を宣言セクションに配置すれば、すべてのアクションで変数 (variable) にアクセスできるようになります。
%{ int variable = 0; %}
yacc パーサーは yy で始まる名前だけを使用しているので、yy で始まる名前は避ける必要があります。また、これまでに示した例では値がすべて整数であることに注意してください。
値については、「高度なトピック」で説明しています。最後に、以下のように定義した場合には、yacc は %{ の後からコピーを開始して、最初に検出した %}、つまり printf() の中の %}でコピーを終了するので注意してください。逆に、printf() の中で %{ を検出した場合には、その %{ からコピーが開始されます。
%{ int i; printf("%}"); %}