我有一个任务是使用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”]:
[(Expr, [N Term; N Binop; N Expr]);
(Term, [N Num]);
(Num, [T "3"]);
(Binop, [T "+"]);
(Expr, [N Term]);
(Term, [N Num]);
(Num, [T "4"])]
限制是不使用除List ...之外的任何OCaml库:/
答案 0 :(得分: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 :(得分: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 ]
这将使编写解析器的工作变得更容易,因为您不必这样做 担心识别什么是整数,什么是运算符等等。
这是第一个,不是太困难的一步,因为代币已经分开了。 lexer所要做的就是识别它们。
完成此操作后,您可以编写一个类型为string -> token list
的更真实的词法分析器,它接受一个实际的原始输入,例如"3-(4+2)"
并将其转换为令牌列表。
答案 2 :(得分: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中有一个关于算术表达式的小例子。