Parsing 使用OCaml解析语法

Parsing 使用OCaml解析语法,parsing,ocaml,grammar,Parsing,Ocaml,Grammar,我的任务是使用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 -

我的任务是使用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"; "+"];;
我要寻找的是一个规则列表,它是解析一个片段的结果,比如frag1[“4”;“+”;“3”]:


限制是不使用除列表之外的任何OCaml库…:/

我不确定您是否特别需要派生树,或者这只是解析的第一步。我假设是后者

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

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
然后我将实现一个递归下降解析器。当然,如果您可以将
与预处理器
camlp4of
结合使用会更好


顺便说一句,OCaml文档中有一个关于算术表达式的小例子。

好的,所以您应该首先编写一个词法分析器。这就是问题所在 接受“原始”输入的函数,如
[“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   ]
这将使编写解析器的工作更容易,因为您不必这样做 担心识别什么是整数、什么是运算符等

这是第一步,不太困难,因为令牌已经分离。 lexer所要做的就是识别它们


完成后,您可以编写一个更真实的词法分析器,类型为
string->token list
,它接受实际的原始输入,例如
“3-(4+2)”
,并将其转换为一个token list。

这里是一个粗略的示意图-直接进入语法并按顺序尝试每个分支。可能的优化:分支中单个非终端的尾部递归

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

谢谢,你说得对-我所描述的是创建匹配器过程中的第一步,该匹配器找到与语法匹配的前缀,然后将其传递给接受程序…我正在编写进行解析所需的递归函数。。。到目前为止,这是相当痛苦的。那么,ocamllexx和ocamlyacc是不可能的?差不多10年后……你有没有可能发现这一点?我还负责编写一个没有解析器生成器或流的ocaml解析器,不需要lexer,因为要解析的片段已经表示为列表。语法是左因数的,所以直接使用输入列表递归下降。@ygrek:但是使用模式匹配编写解析器会更容易。让匹配者理解
“342”
“+”
(它们都是字符串)之间的区别比理解
TokInt
TokBinOp
之间的区别要痛苦得多。此外,OP可能希望有一天解析一个字符串而不是一个列表。看看语法——“342”是不允许的,所以终端只是按原样进行比较。请注意,当从上到下递减时,解析器不需要区分标记“342”和“+”——它只会尝试按顺序将当前输入与当前分支中的所有then终端匹配。对我来说,单独的lexer在这里是一个不必要的复杂问题。我刚刚在一个CS课堂上做了一个非常类似的作业(使用ocaml),我花了好几天的时间绞尽脑汁,直到最后通过你的简单算法看到了曙光!非常感谢。
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