2009-10-18 73 views
8

我有一个任务,使用OCaml为(玩具)语法编写(玩具)解析器,并且不知道如何开始(并继续)这个问题。使用OCaml解析语法

这里有一个样品在awk语法:

type ('nonterm, 'term) symbol = N of 'nonterm | T of 'term;; 

type awksub_nonterminals = Expr | Term | Lvalue | Incrop | Binop | Num;; 

let awksub_grammar = 
    (Expr, 
    function 
    | Expr -> 
     [[N Term; N Binop; N Expr]; 
      [N Term]] 
    | Term -> 
    [[N Num]; 
     [N Lvalue]; 
     [N Incrop; N Lvalue]; 
     [N Lvalue; N Incrop]; 
     [T"("; N Expr; T")"]] 
    | Lvalue -> 
    [[T"$"; N Expr]] 
    | Incrop -> 
    [[T"++"]; 
     [T"--"]] 
    | Binop -> 
    [[T"+"]; 
     [T"-"]] 
    | Num -> 
    [[T"0"]; [T"1"]; [T"2"]; [T"3"]; [T"4"]; 
     [T"5"]; [T"6"]; [T"7"]; [T"8"]; [T"9"]]);; 

和这里的一些片断解析:

let frag1 = ["4"; "+"; "3"];; 
let frag2 = ["9"; "+"; "$"; "1"; "+"];; 

我正在寻找的是一个rulelist那就是解析片段的结果,比如这个用于frag1 [“4”; “+”; “3”]:

[(Expr, [N Term; N Binop; N Expr]); 
    (Term, [N Num]); 
    (Num, [T "3"]); 
    (Binop, [T "+"]); 
    (Expr, [N Term]); 
    (Term, [N Num]); 
    (Num, [T "4"])] 

的限制是不使用比列表中的其他任何OCaml的库...:/

+0

那么,ocamllexx和ocamlyacc是不可能的? – nlucaroni 2009-10-19 15:06:44

回答

3

我不知道,如果你明确要求派生树,或者如果这是这只是解析的第一步。我假设后者。

您可以通过定义类型来定义生成的抽象语法树的结构。它可能是这样的:

type expr = 
    | Operation of term * binop * term 
    | Term of term 
and term = 
    | Num of num 
    | Lvalue of expr 
    | Incrop of incrop * expression 
and incrop = Incr | Decr 
and binop = Plus | Minus 
and num = int 

然后,我会实现一个递归下降解析器。当然,它会好得多,如果你可以使用streams与预处理camlp4of结合...

顺便说一句,有大约OCaml的文档here在算术表达式中的一个小例子。

+0

谢谢你,我说的是创建一个匹配器的过程中的第一步,该匹配器找到与语法匹配的前缀,然后将它传递给接受者... – 2009-10-19 05:37:22

+0

我正在编写递归函数解析所必需的......到目前为止,这是非常痛苦的。 – 2009-10-19 07:26:49

9

好吧,所以第一个想你应该做的就是编写一个词法分析器。这是 函数采用'原始'输入,如["3"; "-"; "("; "4"; "+"; "2"; ")"], ,并将其拆分为令牌列表(即终端符号的表示形式)。

可以定义一个令牌是

type token = 
    | TokInt of int   (* an integer *) 
    | TokBinOp of binop  (* a binary operator *) 
    | TokOParen    (* an opening parenthesis *) 
    | TokCParen    (* a closing parenthesis *)  
and binop = Plus | Minus 

的类型lexer功能将string list -> token list

lexer ["3"; "-"; "("; "4"; "+"; "2"; ")"] 

的输出中会像

[ TokInt 3; TokBinOp Minus; TokOParen; TokInt 4; 
    TBinOp Plus; TokInt 2; TokCParen ] 

这将使解析器的编写工作变得更容易,因为你不会必须 担心识别什么是整数,什么是运营商等。

这是第一个,不是太困难的步骤,因为令牌已经分开。 词法分析器所要做的就是识别它们。

完成此操作后,您可以编写一个类型为string -> token list的更现实的词法分析器,它需要实际的原始输入(例如"3-(4+2)")并将其转换为令牌列表。

+0

谢谢,我会尽快给您尝试并更新! – 2009-10-20 02:31:21

+0

不需要词法分析器,因为要解析的片段已被表示为列表。语法是左考虑的,所以递归地使用输入列表直接下降。 – ygrek 2009-10-20 09:08:28

+0

@ygrek:但使用模式匹配编写解析器会更容易。使匹配器理解''342“'和'”++“'(它们都是字符串)之间的区别比'TokInt'和'TokBinOp'之间的区别更加痛苦。另外,OP可能想要在某天解析字符串而不是列表。 – jdb 2009-10-20 16:22:42

12

这是一个粗略的草图 - 直接下降到语法并尝试每个分支的顺序。可能的优化:分支中单个非终端的尾递归。

exception Backtrack 

let parse l = 
    let rules = snd awksub_grammar in 
    let rec descend gram l = 
    let rec loop = function 
     | [] -> raise Backtrack 
     | x::xs -> try attempt x l with Backtrack -> loop xs 
    in 
    loop (rules gram) 
    and attempt branch (path,tokens) = 
    match branch, tokens with 
    | T x :: branch' , h::tokens' when h = x -> 
     attempt branch' ((T x :: path),tokens') 
    | N n :: branch' , _ -> 
     let (path',tokens) = descend n ((N n :: path),tokens) in 
     attempt branch' (path', tokens) 
    | [], _ -> path,tokens 
    | _, _ -> raise Backtrack 
    in 
    let (path,tail) = descend (fst awksub_grammar) ([],l) in 
    tail, List.rev path 
+1

ygrek:我希望我能+1000这个答案。我刚刚在CS课上做了一个非常类似的任务(使用ocaml),我花了几天甚至几天的时间将自己的大脑打碎,直到通过简单的算法看到光线!谢谢 – kaveman 2011-10-18 09:47:17