我正在尝试在lexing / parsing语法中建立一些技巧。我正在回顾我为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
和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中使用可选或重复的标记来表示语法?
答案 0 :(得分:3)
当你编写所有小件时,你应该得到你想要的东西。而不是:
(INNER | RIGHT | LEFT )
你刚才
inner_right_left
并将其定义为这三个关键字的并集。
您还可以在词法分析器中定义联合。在你定义令牌的方式中,或者使用camlp4,我在那方面做得不多,所以我不建议你采取这些路线。而且我认为他们不会为你工作,也不会随处可见。
编辑:
因此,对于camlp4,您可以查看Camlp4 Grammar module和tutorial以及better tutorial。这不是完全你想要什么,请注意,但它非常接近。文档非常糟糕,如recent discussion on the ocaml groups所述,但对于这个特定领域,我认为你不会有太多问题。我做了一点,可以提出更多问题。
答案 1 :(得分:3)
Menhir允许通过其他符号对非终结符号进行参数化,并提供标准快捷键库,如选项和列表,您可以创建自己的符号。例如:
option(X): x=X { Some x}
| { None }
还有一些语法糖,'令牌?'相当于'option(token)','token +'到'nonempty_list(token)'。
所有这些都真正缩短了语法定义。它也受ocamlbuild支持,可以直接替代ocamlyacc。强烈推荐!
好笑,我用它来解析SQL:)