代码之家  ›  专栏  ›  技术社区  ›  Juliet

用OcamlYacc/FsYacc表示可选语法和重复

  •  3
  • Juliet  · 技术社区  · 16 年前

    我正在努力培养一些语法的词法分析技巧。我回顾了我为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中,是否有一种用可选或重复的标记表示语法的简单方法?

    2 回复  |  直到 8 年前
        1
  •  3
  •   nlucaroni    16 年前

    当你写下所有的小片段时,你应该得到你想要的东西。而不是:

    (INNER | RIGHT | LEFT ) 
    

    你只是

    inner_right_left
    

    也可以在lexer中定义联合。在你定义代币的方式上,或者使用camlp4,我在这方面做得不多,所以我不能建议你走那些路线。我不认为他们会为你工作,就像到处都是小碎片一样。

    编辑:

    Camlp4 Grammar module 以及 tutorial 以及 better tutorial . 这不是 确切地 recent discussion on the ocaml groups ,但对于这一特定领域,我认为你不会有太多问题。我做了一点,可以提出更多的问题。

        2
  •  3
  •   ygrek    15 年前

    Menhir 允许通过其他符号对非终结符进行参数化,并提供标准快捷方式库,如选项和列表,您可以创建自己的快捷方式库。例子:

    option(X): x=X { Some x} 
             | { None }
    

    还有一些语法甜点,“token?”相当于“option(token)”、“token+”和“nonempty_list(token)”。

    所有这些都缩短了语法定义。它还受ocamlbuild支持,可以作为ocamlyacc的替代品。强烈推荐!