文法の字句解析/構文解析のスキルを身に付けようとしています。SQL用に作成した単純なパーサーを振り返っていますが、完全に満足しているわけではありません。パーサーを作成するためのより簡単な方法があったはずです。
SQLにはオプションのトークンと繰り返しがたくさんあるので、私はつまずきました。例えば:
SELECT *
FROM t1
INNER JOIN t2
INNER JOIN t3
WHERE t1.ID = t2.ID and t1.ID = t3.ID
と同等です:
SELECT *
FROM t1
INNER JOIN t2 ON t1.ID = t2.ID
INNER JOIN t3 on t1.ID = t3.ID
ON
and句はオプションであり、WHERE
複数回発生する可能性があります。私はこれらをパーサーで次のように処理しました。
%{
open AST
%}
// ...
%token <string> ID
%token AND OR COMMA
%token EQ LT LE GT GE
%token JOIN INNER LEFT RIGHT ON
// ...
%%
op: EQ { Eq } | LT { Lt } | LE { Le } | GT { Gt } | GE { Ge }
// WHERE clause is optional
whereClause:
| { None }
| WHERE whereList { Some($2) }
whereList:
| ID op ID { Cond($1, $2, $3) }
| ID op ID AND whereList { And(Cond($1, $2, $3), $5) }
| ID op ID OR whereList { Or(Cond($1, $2, $3), $5) }
// Join clause is optional
joinList:
| { [] }
| joinClause { [$1] }
| joinClause joinList { $1 :: $2 }
joinClause:
| INNER JOIN ID joinOnClause { $3, Inner, $4 }
| LEFT JOIN ID joinOnClause { $3, Left, $4 }
| RIGHT JOIN ID joinOnClause { $3, Right, $4 }
// "On" clause is optional
joinOnClause:
| { None }
| ON whereList { Some($2) }
// ...
%%
つまり、オプションの構文を個別のルールに分割して処理し、再帰を使用して繰り返しを処理しました。これは機能しますが、解析が小さなサブルーチンの束に分割され、文法が実際に何を表しているかを確認するのは非常に困難です。
角かっこ内にオプションの構文を指定し、*または+で繰り返しを指定できれば、はるかに簡単に記述できると思います。これにより、whereClauseとjoinListのルールが次のようになります。
whereClause:
| { None }
// $1 $2, I envision $2 being return as an (ID * op * ID * cond) list
| WHERE [ ID op ID { (AND | OR) }]+
{ Some([for name1, op, name2, _ in $1 -> name1, op, name2]) }
joinClause:
| { None }
// $1, returned as (joinType
// * JOIN
// * ID
// * ((ID * op * ID) list) option) list
| [ (INNER | LEFT | RIGHT) JOIN ID [ON whereClause] ]*
{ let joinType, _, table, onClause = $1;
Some(table, joinType, onClause) }
このフォームははるかに読みやすく、キャプチャしようとしている文法をより直感的に表現していると思います。残念ながら、この表記法または同様のものをサポートするOcamlまたはF#のドキュメントには何も見つかりません。
OcamlYaccまたはFsYaccでオプションまたは反復トークンを使用して文法を表す簡単な方法はありますか?