Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/user-interface/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Parsing 解析sexp的优雅方法_Parsing_Ocaml_S Expression - Fatal编程技术网

Parsing 解析sexp的优雅方法

Parsing 解析sexp的优雅方法,parsing,ocaml,s-expression,Parsing,Ocaml,S Expression,sexp是这样的:类型sexp=Atom of string | sexp列表,例如,“((ab)((cd)e)f)” 我已经编写了一个解析器,将sexp字符串解析为以下类型: let of_string s = let len = String.length s in let empty_buf () = Buffer.create 16 in let rec parse_atom buf i = if i >= len then failwith "cannot p

sexp是这样的:
类型sexp=Atom of string | sexp列表
,例如,
“((ab)((cd)e)f)”

我已经编写了一个解析器,将sexp字符串解析为以下类型:

let of_string s =
  let len = String.length s in
  let empty_buf () = Buffer.create 16 in
  let rec parse_atom buf i =
    if i >= len then failwith "cannot parse"
    else 
      match s.[i] with 
      | '(' -> failwith "cannot parse"
      | ')' -> Atom (Buffer.contents buf), i-1
      | ' ' -> Atom (Buffer.contents buf), i
      | c when i = len-1 -> (Buffer.add_char buf c; Atom (Buffer.contents buf), i)
      | c -> (Buffer.add_char buf c; parse_atom buf (i+1))
  and parse_list acc i =
    if i >= len || (i = len-1 && s.[i] <> ')') then failwith "cannot parse"
    else 
      match s.[i] with
      | ')' -> List (List.rev acc), i
      | '(' -> 
        let list, j = parse_list [] (i+1) in
        parse_list (list::acc) (j+1)
      | c -> 
        let atom, j = parse_atom (empty_buf()) i in
        parse_list (atom::acc) (j+1)
  in 
  if s.[0] <> '(' then
    let atom, j = parse_atom (empty_buf()) 0 in
    if j = len-1 then atom
    else failwith "cannot parse"
  else 
    let list, j = parse_list [] 1 in
    if j = len-1 then list
    else failwith "cannot parse"
let of_字符串=
设len=String.length s in
让empty_buf()=Buffer.create 16 in
让rec解析_原子buf i=
如果i>=len,则失败为“无法解析”
其他的
将s[i]与
|“(”->failwith“cannot parse”
|')'->Atom(Buffer.contents buf),i-1
|''->Atom(Buffer.contents buf),i
|当i=len-1->(Buffer.add_char buf c;Atom(Buffer.contents buf),i)
|c->(Buffer.add_char buf c;parse_atom buf(i+1))
和解析列表acc i=
如果i>=len | |(i=len-1&&s[i]')),则失败为“无法解析”
其他的
将s[i]与
|“)”->List(List.rev acc),i
| '(' -> 
let list,j=parse_list[](i+1)in
解析列表(列表::acc)(j+1)
|c->
设atom,j=parse_atom(empty_buf())i in
解析列表(atom::acc)(j+1)
在里面
如果s[0]'('则
让atom,j=parse_atom(empty_buf())0 in
如果j=len-1,那么原子
else失败,带有“无法解析”
其他的
let list,j=parse_list[]1 in
如果j=len-1,则列出
else失败,带有“无法解析”
但我觉得它太冗长和丑陋了

有人能帮我用一种优雅的方式来编写这样一个解析器吗

事实上,我在编写解析器的代码时总是遇到问题,我所能做的就是编写这样一个难看的代码


这种解析有什么诀窍吗?如何有效地处理符号,例如
,这意味着递归解析?

您可以使用lexer+解析器规程来分离词汇语法的细节(主要是跳过空格)从实际的语法结构来看,对于这样一个简单的语法来说,这似乎有些过分,但事实上,只要您解析的数据有一点点出错的机会,就更好了:您确实想要错误位置(而不是自己实现它们)

一种简单且提供简短解析器的技术是使用流解析器(使用书中描述的Camlp4扩展);您甚至可以通过使用该模块免费获得lexer

如果您真的想手动执行,如上面的示例所示,我建议您使用一个好的解析器结构。使用相互递归的解析器,每个语法类别对应一个解析器,并具有以下接口:

  • 解析器将开始解析的索引作为输入
  • 它们返回一对解析后的值和不属于该值的第一个索引
  • 没别的了
<>你的代码不尊重这个结构。例如,如果原子分析器看到一个<代码>(),它将失败。这不是他的作用和责任:它应该简单地认为这个字符不是原子的一部分,并返回到目前为止解析的原子,表明这个位置不再在原子中。< /P> 下面是一个语法上使用这种风格的代码示例。我将解析器和累加器分成三个部分(
start\u foo
parse\u foo
finish\u foo
),以分解多个起始点或返回点,但这只是一个实现细节

我使用了4.02的一个新特性只是为了好玩,而不是显式地测试字符串的结尾

最后,如果有效表达式在输入结束之前结束,则当前解析器不会失败,它只会返回输入端的边。这对测试很有帮助,但在“生产”中,无论这意味着什么,您都会以不同的方式执行

let of_string str =
  let rec parse i =
    match str.[i] with
      | exception _ -> failwith "unfinished input"
      | ')' -> failwith "extraneous ')'"
      | ' ' -> parse (i+1)
      | '(' -> start_list (i+1)
      | _ -> start_atom i
  and start_list i = parse_list [] i
  and parse_list acc i =
    match str.[i] with
      | exception _ -> failwith "unfinished list"
      | ')' -> finish_list acc (i+1)
      | ' ' -> parse_list acc (i+1)
      | _ ->
        let elem, j = parse i in
        parse_list (elem :: acc) j
  and finish_list acc i =
    List (List.rev acc), i
  and start_atom i = parse_atom (Buffer.create 3) i
  and parse_atom acc i =
    match str.[i] with
      | exception _ -> finish_atom acc i
      | ')' | ' ' -> finish_atom acc i
      | _ -> parse_atom (Buffer.add_char acc str.[i]; acc) (i + 1)
  and finish_atom acc i =
    Atom (Buffer.contents acc), i
  in
  let result, rest = parse 0 in
  result, String.sub str rest (String.length str - rest)
请注意,在解析有效表达式(您必须至少读取了一个atom或list)或解析列表(您必须遇到右括号)时,到达输入末尾是错误的,但它在atom末尾是有效的


此解析器不返回位置信息。所有现实世界的解析器都应该返回位置信息,这就足以作为使用lexer/parser方法(或您首选的单元解析器库)的理由但是,在这里返回位置信息并不十分困难,只需将
i
参数一方面复制到当前已解析字符的索引中,另一方面复制到用于当前AST节点的第一个索引中;每当生成结果时,位置就是该对(
第一个索引
最后一个有效索引
)。

如果我被迫手动执行,我应该模拟lex+解析的过程吗?我的意思是,例如,对于sexp解析器,我应该首先将它们转换为令牌,然后对令牌进行解析吗?请看一下?