kmyaccはyaccやbisonと同じLALRパーサージェネレータです。yaccと互換性があり、生成される表が小さく、ホスト言語としてC以外にJava, JavaScript,Perlも使うことができます。
なおこの文書の内容は、予告なく変更される可能性があることをあらかじめ御了承ください。
最新のソースコード(ベータ版)はkmyacc-4.1.4.tar.gzです。ソースのみでバイナリは含まれていません。
コンパイルするには、ANSI Cコンパイラ(gccで十分)が必要です。
以下のコマンド
gzip -d kmyacc-4.1.4.tar.gz | tar xvf -
でアーカイブを展開するとkmyacc-4.1.4というディレクトリができるので、この中に移動します。
この中のMakefile中の
BIN = /usr/local/bin PARLIB = /usr/local/lib
を適切なインストール先に変更してください。BINはkmyaccバイナリが置かれる場所、PARLIBはパーサーのプロトタイプファイルが置かれる場所です。
そして、
make make install
でコンパイル・インストールが完了します。
kmyaccの起動の書式は以下の通りです。
kmyacc [ -dvltani ] [ -b X ] [ -p XX ] [ -m MODEL ] [ -L LANG ] grammarfile
grammerfileにはyaccソースを指定します。kmyaccはこのファイルの拡張子から、以下のルールに従ってホスト言語を推定します。
yacc拡張子 | ホスト言語 | 出力ファイル拡張子 |
---|---|---|
.y | C | y.tab.c |
.jy | Java | .java |
.jsy | JavaScript | .js |
.ply | Perl | .pl |
出力されるパーサープログラムのファイル名は、C言語の場合はy.tab.cに固定されていますが、それ以外の言語では元のgrammarfile名から作られます。
kmyacc foo.y → y.tab.c kmyacc Foo.jy → Foo.java kmyacc Foo.jsy → Foo.js kmyacc foo.ply → foo.pl
オプションとして以下のものが指定できます。
-DYYDEBUG
を指定すれば同じ効果が得られる。
/usr/local/lib/kmyacc.
LANG.parser
というファイルが使われる(LANGはホスト言語の拡張子)。
基本的にはyaccと同じですが、以下に違いを示します。
項目 | 意味 | yaccとの違い |
---|---|---|
%token | 終端記号の定義 | - |
%left | 左結合終端記号の定義 | - |
%right | 右結合終端記号の定義 | - |
%nonassoc | 非結合終端記号の定義 | - |
%{ ... %} | コードの挿入 | Javaではimportを書く |
%union | 意味スタックの型定義 | C言語のみ使用可能 |
%type | 終端記号、非終端記号の型定義 | C言語では%unionで宣言したフィールド名、 Javaではクラス名 |
%start | 開始記号定義 | - |
%expect N | shift/reduce conflictの数を指定 | 拡張 |
%pure_parser | reentrantなパーサーを生成 | 拡張 |
$$, $1, $2... | semantic valueの参照 | 名前による参照も可能 |
最後の項目「semantic valueの参照」について補足します。
kmyaccでは、アクション中のsemantic valueを、$$, $1, $2...の代わりに文法記号の名前で参照することができます(Cでは-nオプションの指定が必要)。
以下に例を示します。
foo: bar '+' baz { foo = bar + baz; }
上のアクションは、{ $$ = $1 + $3; }と同じです。
同じ文法記号が規則中に複数回現われる場合は、どの要素を指しているのかが曖昧なため、そのまま文法記号で参照するとエラーになってしまいます。
こういう場合には文法記号の前に「名前@」を前置することにより、その名前でsemantic valueを参照することができます。
result@expr: addendum@expr '+' addenda@expr { result = addendum + addenda; }
以下、文法定義ファイルの書き方を各言語別に説明します(Cは省略)。
Java版のパーサーは、単一のクラスとして生成されます。
クラス名は、ソースファイルの名前から拡張子を除いて作られます。例えば、ソースがFoo.jyならクラス名はFooとなります。
文法定義の次の%%以降の部分は、このクラス内にそのままコピーされますが、%{ ... %}の中に記述したコードは、クラス定義の外側に展開されます。従って、package宣言やimportはこの中に書く必要があります。
Java版では、意味スタックはObjectとして宣言されており、ある記号に対し%typeによって型が指定されている場合、その型へのキャストが自動的に生成されます。
例えば以下の文法定義を考えてみます。
%type <Integer> expr %% expr : expr '+' expr { $$ = new Integer($1.intValue() + $3.intValue()); }
この場合、{}内のアクションは以下のようなJavaプログラムに展開されます。
{ yyval = new Integer(((Integer)yyastk[yysp-2]).intValue() + ((Integer)yyastk[yysp-0]).intValue()); }
以下に、アクション中でエラーの処理を行うための機能を説明します。C版ではマクロで提供されている機能ですが、Javaではマクロがないため、直接制御変数を書き換えたり参照したりする必要があります。参考までに括弧内にC版でのマクロ名を示しておきます。
{ yyparseerror = true; }
{ yyerrflag = 0; }
{ yychar = -1; }
{ if (yyerrflag) hoge(); }
生成されるパーサーのクラスメソッドは以下の通りです。
また、yyparse中からは以下のメソッドを呼出します。ユーザーはこれらのメソッドを%%以下のコード中で定義する必要があります。
字句解析のインターフェースです。トークンを読み出してその値を返します。返す値は、リテラルトークン(例:'+')かまたは%tokenで宣言されたトークンの値です。また、semantic valueは変数yylvalにセットします。
字句解析クラスを独立したクラスとして定義する場合は、このメソッドでインターフェースの調整を行ってください。
なお、トークンの値は、public static final intとして定義されます。従って、これらの値をクラスの外部からFoo.IDENTIFIERのように参照することができます。
生成されるパーサはJavaScriptのプログラムの形となっており、<script>タグは付加されていません。HTMLファイルとして使用可能な形にするためには、以下のような適切なヘッダ
%{ <html> <head> <title>Parser</title> <script type="text/javascript"> <!-- %}
を、末尾にそれを閉じる以下のようなタグ
// --></script></head> <body>...</body> </html>
を置く必要があります。
JavaScriptは静的な型のない言語ですので、%typeは意味をもちません。
生成されたパーサーはCと同様、function yyparse()が入口となります。
ユーザは、function yylex()とyyerror(msg)を用意する必要があります。
yylex()は入力トークンのsemantic valueを変数yylvalにセットし、その値を整数値で返します。
yyerror(msg)は、エラーメッセージの表示を行う関数です。渡されたmsgの内容を表示します。
Perlは静的な型のない言語ですので、%typeは意味をもちません。
アクション中のsemantic valueは、$$, $1, $2...の代わりにgrammar symbolの名前で参照します。従って、$で始まるPerlの変数は普通に使うことができます(前出の例を参照)。
生成されたパーサは、Cと同様サブルーチンyyparse()がパーサの入口となります。
ユーザはサブルーチンyylex()とyyerror(msg)を用意する必要があります。
yylex()はCと同様にsemantic valueを$yylvalにセットし、トークンの値を整数値で返します。
yyerror(msg)は、エラーメッセージの表示を行う関数です。渡されたmsgの内容を表示します。
kmyaccでは、-mオプションで任意のプロトタイプファイルを指定することができます。
プロトタイプファイルは、パーサーの原型となるソースプログラムで、ホスト言語ごとに別々のものが用意されており、言語の違いはほとんどこのファイルで吸収しています(一部はkmyacc本体で対応)。
プロトタイプファイルの中では、表や定数を生成したり、条件によって別のコードを生成したりするためのマクロ機能が使われています。これはC言語のプリプロセッサとは別のもので、kmyaccによって処理されます。
以下、このマクロ機能について説明します。
プロトタイプ中のマクロ機能には、行単位のものと、文字単位のものがあります。
行単位のものは、1行すべてがマクロ呼出しであり、他のコードは含むことができません。一方、文字単位のものはテキスト中の任意の場所に書くことができます。
また、実装上の都合で、$includeと$semvalは書ける場所に制限があります。
事前に定義されたスカラー変数の値を参照する機能です。文字単位でソースのどの位置にも現われることができ、以下の形式をしています。
$(変数名)
変数名としては、以下のものがあります。
YYSTATES, YYNLSTATES, YYINTERRTOK, YYUNEXPECTED, YYDEFAULT, YYTERMS, YYNONTERMS, YYBADCH, YYMAXLEX, YYLAST, YYGLAST, YY2TBLSTATE, CLASSNAME, -p
$(-p)は、-pオプションに記述されたプリフィックス値に展開されます。
$listvarは、配列変数の値を展開する機能です。
$listvar 配列名
行単位で、','で区切られた変数の値のリストに展開されます。
配列名には、以下のものが指定できます。
yytranslate, yyaction, yycheck, yybase, yydefault, yygoto, yygcheck, yygbase, yygdefault, yylhs, yylen, terminals, nonterminals
$TYPEOFは、配列変数の型(char, short)に展開される機能です。
$TYPEOF(配列名)
行の中の任意の位置に書くことができ、Cならばcharもしくはshortに、Javaならばbyteもしくはshortに展開されます。
配列名には、以下のものが指定できます。
yytranslate, yycheck, yygcheck, yylhs, yylen
$ifは、条件が成り立っているかどうかを調べ、成り立っているときのみ$endifまでのコードを生成します。$ifnotはその逆です。
行単位であり、ネストは許されません。
$if 条件 ... $endif
現在のところ記述できる条件は、オプション-t, -a, -pだけです。
$reduceは、yaccのアクションの生成パターンを記述する行単位マクロです。
$reduce ... [$noact ... ] $endreduce
$reduceと$endreduceの間(または$noactの間)のコードが、アクションの数だけ生成されます。また、%nと書くとアクション番号に、%bと書くとアクションコード本体に展開されます。
また、$noactと$endreduceの間のコードは、ユーザ定義のアクションがなかった場合に生成されるコードです。
例:
switch (yyn) { $reduce case %n: {%b} break; $endreduce }
$metaは、プロトタイプファイル中のマクロ機能呼出しを示すメタキャラクタ'$'を他の文字に変えます。
$meta new-metacharacter
$metaの次の最初の空白でない文字が、新しいメタキャラクタとして$の代わりに使われます。
以下の例では、!が新しくメタキャラクタとして使われます。
$meta ! !include
なお、このメタキャラクタは、アクション中のsemantic valueの参照を示す$とは関係ありません。$meta指定があっても、アクション中でのsemantic valueの参照には$を使う必要があります。
$semvalは、アクション中のsemantic value($$, $1, $2...)をどのように展開するかを定義する機能です。これは必須であり、これを書かないとアクションは正常に展開されません。
$semval($) body-for-$$-without-type $semval($,%t) body-for-$$-with-type $semval(%n) body-for-$n-without-type $semval(%n,%t) body-for-$n-with-type
semantic valueには$$と$数字の二種類があり、さらにそれぞれが%typeによる型指定の有無によって二つに分けられ、全体で4種類のバリエーションがあります。この4種類すべてを定義しなくてはなりません。
$semval($)は、型なしの$$の展開形式を定義します。
$semval($,%t)は、型つきの$$の展開形式を定義します。
$semval(%n)は、型なしの$数字に対する展開形式を定義します。
$semval(%n,%t)は、型つきの$数字に対する展開形式を定義します。
どの定義でも、定義中に%tと書くと、それは型名(フィールド名)に置き換えられ、%nと書くと、それは$の後の数字に置き換えられます。
$semvalは、$includeよりも先に現れなくてはいけません。
C言語での例:
$semval($) yyval $semval($,%t) yyval.%t $semval(%n) YYASP(%n-%l) $semval(%n,%t) YYASP(%n-%l).%t
$includeは%{ ... %}の中身に展開される行単位マクロです。
$include
$includeは$semvalマクロを除くすべてのマクロ呼出しに先行して書かなくてはいけません。また、$includeがないとパーサーコード全体が正常に作成されません。
$tailcodeは、文法定義ファイル中の最後の%%以降の部分に展開される行単位マクロです。
$tailcode
$unionは、%union { ... }の中身に展開される行単位マクロです。
$union
$tokenvalは、終端記号の値定義を生成するための行単位マクロです。
$tokenval ... $endtokenval
$tokenvalと$endtokenvalの間が、文字定数以外の終端記号の個数分だけ繰り返し生成されます。そのさい、%nは終端記号の値の十進表記に、%sは終端記号の名前に展開されます。
例えば
$tokenval public static final int %s = %n; $endtokenval
は、終端記号IDENTIFIERとNUMBERが定義されているとき、以下のように展開されます。
public static final int IDENTIFIER = 256; public static final int NUMBER = 257;