两次传递以标记语法树中的出现次数?

时间:2014-02-11 16:05:53

标签: functional-programming ocaml

假设你有一个语法树,其中包含代表标识符的节点。类似的东西:

type node = ...
| Ident of string
...

我想走树,并用标识符的出现次数标记所有Ident节点。例如,如果标识符“x”在树中出现3次,则每个Ident“x”节点将被标记为3。

显然,这需要修改类型decl以适应新数据(有些像“String of string * ref int”)。我的问题是这样做的最佳方法是什么?看起来像是一个进行计数的两遍方法,一个做“标记”的方法可能是最直接的,但即便如此,似乎跟踪给定标识符的节点也很尴尬。

连连呢?

1 个答案:

答案 0 :(得分:0)

这是一个纯粹的功能性解决方案:

type 'a exp = Ident of 'a | Const of int | Add of 'a exp * 'a exp

(* pass 1: count occurrences of identifiers *)
let count : string exp -> (string * int) list =
  let rec record x = function
    | [] -> [(x, 1)]
    | (y, n) :: counts when x = y -> (y, n + 1) :: counts
    | count :: counts -> count :: record x counts
  in
  let rec visit acc = function
    | Ident x -> record x acc
    | Const _ -> acc
    | Add (e1, e2) -> visit (visit acc e1) e2
  in
  visit []

(* pass 2: mark all identifiers with their occurrence counts *)
let mark (counts : (string * int) list) : string exp -> (string * int) exp =
  let rec lookup x = function
    | [] -> invalid_arg "lookup"
    | (y, n) :: _ when x = y -> n
    | _ :: counts -> lookup x counts
  in
  let rec visit = function
    | Ident x -> Ident (x, lookup x counts)
    | Const n -> Const n
    | Add (e1, e2) -> Add (visit e1, visit e2)
  in
  visit

(* combining the two passes *)
let mark_with_counts e = mark (count e) e

请注意,这两个过程可以合并为一个访问函数,有效地避免了重复的模式匹配:

let mark_with_counts' e =
  let rec record x = function
    | [] -> [(x, 1)]
    | (y, n) :: counts when x = y -> (y, n + 1) :: counts
    | count :: counts -> count :: record x counts
  in
  let rec lookup x = function
    | [] -> invalid_arg "lookup"
    | (y, n) :: _ when x = y -> n
    | _ :: counts -> lookup x counts
  in
  let rec visit acc = function
    | Ident x -> (record x acc, fun counts -> Ident (x, lookup x counts))
    | Const n -> (acc, fun _ -> Const n)
    | Add (e1, e2) ->
        let (acc, syn1) = visit acc e1 in
        let (acc, syn2) = visit acc e2 in
        (acc, fun counts -> Add (syn1 counts, syn2 counts))
  in
  let (counts, syn) = visit [] e in
  syn counts