Parsing OCaml中的Packrat解析(通过惰性进行记忆)

Parsing OCaml中的Packrat解析(通过惰性进行记忆),parsing,functional-programming,ocaml,lazy-evaluation,memoization,Parsing,Functional Programming,Ocaml,Lazy Evaluation,Memoization,我正在OCaml中实现一个packrat解析器,因为我的解析器应该接收一个表示语言语法的数据结构,并解析给定的符号序列 我被记忆部分难住了。最初的论文使用Haskell的惰性评估来实现线性时间复杂度。我想在OCaml中实现这一点(通过懒惰记忆),但不知道如何实现 那么,如何通过OCaml中的惰性求值来记忆函数呢 编辑:我知道什么是惰性评估,以及如何在OCaml中利用它。问题是如何使用它来记忆函数 编辑:我编写的表示语法的数据结构是: type ('a, 'b, 'c) expr = | Empt

我正在OCaml中实现一个packrat解析器,因为我的解析器应该接收一个表示语言语法的数据结构,并解析给定的符号序列

我被记忆部分难住了。最初的论文使用Haskell的惰性评估来实现线性时间复杂度。我想在OCaml中实现这一点(通过懒惰记忆),但不知道如何实现

那么,如何通过OCaml中的惰性求值来记忆函数呢

编辑:我知道什么是惰性评估,以及如何在OCaml中利用它。问题是如何使用它来记忆函数

编辑:我编写的表示语法的数据结构是:

type ('a, 'b, 'c) expr =
| Empty of 'c
| Term of 'a * ('a -> 'c)
| NTerm of 'b
| Juxta of ('a, 'b, 'c) expr * ('a, 'b, 'c) expr * ('c -> 'c -> 'c)
| Alter of ('a, 'b, 'c) expr * ('a, 'b, 'c) expr
| Pred of ('a, 'b, 'c) expr * 'c
| NPred of ('a, 'b, 'c) expr * 'c
type ('a, 'b, 'c) grammar = ('a * ('a, 'b, 'c) expr) list
解析符号列表的(非记忆)函数是:

let rec parse g v xs = parse' g (List.assoc v g) xs
and parse' g e xs =
  match e with
  | Empty y -> Parsed (y, xs)
  | Term (x, f) ->
     begin
       match xs with
       | x' :: xs when x = x' -> Parsed (f x, xs)
       | _ -> NoParse
     end
  | NTerm v' -> parse g v' xs
  | Juxta (e1, e2, f) ->
     begin
       match parse' g e1 xs with
       | Parsed (y, xs) ->
          begin
            match parse' g e2 xs with
            | Parsed (y', xs) -> Parsed (f y y', xs)
            | p -> p
          end
       | p -> p
     end
( and so on )
其中,parse的返回值的类型由

type ('a, 'c) result = Parsed of 'c * ('a list) | NoParse
例如,基本算术表达式的语法可以指定为
g
,具体如下:

type nt = Add | Mult | Prim | Dec | Expr
let zero _  = 0
let g =
  [(Expr, Juxta (NTerm Add, Term ('$', zero), fun x _ -> x));
   (Add, Alter (Juxta (NTerm Mult, Juxta (Term ('+', zero), NTerm Add, fun _ x -> x), (+)), NTerm Mult));
   (Mult, Alter (Juxta (NTerm Prim, Juxta (Term ('*', zero), NTerm Mult, fun _ x -> x), ( * )), NTerm Prim));
   (Prim, Alter (Juxta (Term ('<', zero), Juxta (NTerm Dec, Term ('>', zero), fun x _ -> x), fun _ x -> x), NTerm Dec));
   (Dec, List.fold_left (fun acc d -> Alter (Term (d, (fun c -> int_of_char c - 48)), acc)) (Term ('0', zero)) ['1';'2';'3';])]
type nt=Add | Mult | Prim | Dec | Expr
设零=0
让g=
[(Expr,并置(NTerm-Add,Term('$',零),fun x->x));
(添加、更改(并列(NTerm Mult,并列(Term(+),zero),NTerm Add,fun x->x),(+),NTerm Mult));
(Mult,Alter(并置(NTerm Prim,并置(Term('*',zero),NTerm Mult,fun x->x),(*),NTerm Prim));
(原音,变音(并列(术语('',零),乐趣x->x,乐趣x->x),中间音);
(Dec,List.fold_left(fun acc d->Alter(Term(d,(fun c->int_of_char c-48)),acc))(Term('0',zero))['1';'2';'3';])]
关键字

你可以找到一些很好的例子


如果它适合您的用例,您也可以使用OCaml流,而不是手动生成Thunk。

为什么要记忆函数?我相信,您想要记住的是给定(解析)表达式和输入流中给定位置的解析结果。例如,您可以使用Ocaml的哈希表来实现这一点。

使用lazyness进行记忆的想法不是使用函数,而是使用数据结构进行记忆。懒散意味着当你在某个表达式中编写
let x=foo时,
foo
不会立即被评估,而只会在
某个表达式需要时才被评估,但
某个表达式中不同出现的
x
将共享同一主干:只要其中一个表达式强制计算,结果对所有人都是可用的

这对函数不起作用:如果您在某个表达式中编写
让fx=foo,并在
某个表达式中多次调用
f
,那么,每个调用都将独立计算,没有共享的thunk来存储结果

因此,您可以通过使用数据结构而不是函数来实现记忆。通常,这是使用关联数据结构完成的:不计算
a->b
函数,而是计算
表a b
,其中
是从参数到结果的一些映射。一个例子是斐波那契的Haskell表示:

fib n = fibTable !! n
fibTable = [0,1] ++ map (\n -> fib (n - 1) + fib (n - 2)) [2..]
(你也可以用
tail
zip
来写,但这并不能让这一点更清楚。)

请注意,您不需要记忆一个函数,而是一个列表:执行记忆的是列表
fibTable
。您也可以在OCaml中编写,例如使用库的LazyList模块:

此示例可能选择不当,因为您需要一个动态结构来存储缓存。在packrat解析器案例中,您将对已知输入文本进行列表解析,因此可以使用纯数组(按语法规则索引):对于每个规则,您将有一个
('a,'c)result option
数组,其大小与输入长度相同,并初始化为
None
。例如,
juxta.(n)
表示从输入位置
n
尝试规则
juxta
的结果,或者
None
(如果尚未尝试)


懒散是呈现这种记忆的一种很好的方式,但并不总是足够有表现力:例如,如果您需要部分释放结果缓存以降低内存使用率,那么如果您从一个懒散的呈现开始,您将遇到困难。关于这一点,请参阅。

我认为parckrat解析是一种众所周知的技术,它的发明者的硕士论文也是如此。但似乎并非如此:-)
open Batteries
module LL = LazyList

let from_2 = LL.seq 2 ((+) 1) (fun _ -> true)

let rec fib n = LL.at fib_table (n - 1) + LL.at fib_table (n - 2)
and fib_table = lazy (LL.Cons (0, LL.cons 1 <| LL.map fib from_2))
open Batteries

let fib =
  let fib_table = DynArray.of_list [0; 1] in
  let get_fib n = DynArray.get fib_table n in
  fun n ->
    for i = DynArray.length fib_table to n do
      DynArray.add fib_table (get_fib (i - 1) + get_fib (i - 2))
    done;
    get_fib n