named_rule.pony

use "collections"

class NamedRule[
  S: (Any #read & Equatable[S]),
  D: Any #share = None,
  V: Any #share = None]
  is RuleNodeWithBody[S, D, V]
  """
  Represents a named grammar rule.  Memoization and left-recursion handling happens per named `Rule`.
  """

  let name: String
  let memoize: Bool
  var _body: (RuleNode[S, D, V] | None)
  var _action: (Action[S, D, V] | None)

  var left_recursive: Bool = false

  new create(
    name': String,
    body': (RuleNode[S, D, V] | None) = None,
    action': (Action[S, D, V] | None) = None,
    memoize': Bool = false)
  =>
    name = name'
    memoize = memoize'
    _body = body'
    _action = action'
    set_left_recursive()

  fun action(): (Action[S, D, V] | None) =>
    _action

  fun body(): (this->(RuleNode[S, D, V]) | None) =>
    _body

  fun has_body(): Bool =>
    _body isnt None

  fun ref set_body(
    body': RuleNode[S, D, V],
    action': (Action[S, D, V] | None) = None)
  =>
    _body = body'
    if action' isnt None then
      _action = action'
    end
    set_left_recursive()

  fun ref set_left_recursive() =>
    let named_rules = SetIs[NamedRule[S, D, V]]
    _collect(this, named_rules)
    for nr in named_rules.values() do
      _set_lr(nr, SetIs[NamedRule[S, D, V]])
    end

  fun tag _collect(node: RuleNode[S, D, V], seen: SetIs[NamedRule[S, D, V]]) =>
    match node
    | let named_rule: NamedRule[S, D, V] =>
      if not seen.contains(named_rule) then
        seen.set(named_rule)
        match named_rule.body()
        | let body': RuleNode[S, D, V] =>
          _collect(body', seen)
        end
      end
    | let with_children: RuleNodeWithChildren[S, D, V] =>
      for child in with_children.children().values() do
        _collect(child, seen)
      end
    | let with_body: RuleNodeWithBody[S, D, V] =>
      match with_body.body()
      | let body': RuleNode[S, D, V] =>
        _collect(body', seen)
      end
    end

  fun tag _set_lr(node: RuleNode[S, D, V], seen: SetIs[NamedRule[S, D, V]]) =>
    match node
    | let named_rule: NamedRule[S, D, V] =>
      if seen.contains(named_rule) then
        named_rule.left_recursive = true
      else
        seen.set(named_rule)
        match named_rule.body()
        | let body': RuleNode[S, D, V] =>
          _set_lr(body', seen)
        end
      end
    | let conj: Conj[S, D, V] =>
      try
        _set_lr(conj.children()(0)?, seen)
      end
    | let disj: Disj[S, D, V] =>
      for child in disj.children().values() do
        _set_lr(child, seen)
      end
    | let with_body: RuleNodeWithBody[S, D, V] =>
      match with_body.body()
      | let body': RuleNode[S, D, V] =>
        _set_lr(body', seen)
      end
    end

  fun call(depth: USize, loc: Loc[S]): _RuleFrame[S, D, V] =>
    _NamedRuleFrame[S, D, V](this, depth, loc, _body)

class _NamedRuleFrame[
  S: (Any #read & Equatable[S]),
  D: Any #share,
  V: Any #share]
  is _Frame[S, D, V]

  let rule: NamedRule[S, D, V] box
  let depth: USize
  let loc: Loc[S]
  let body: (RuleNode[S, D, V] box | None)

  new create(
    rule': NamedRule[S, D, V] box,
    depth': USize,
    loc': Loc[S],
    body': (RuleNode[S, D, V] box | None))
  =>
    rule = rule'
    depth = depth'
    loc = loc'
    body = body'

  fun ref run(child_result: (Result[S, D, V] | None)): _FrameResult[S, D, V] =>
    // can't happen; parser should not call this
    Failure[S, D, V](rule, loc, "NamedRuleFrame.run() should not be called")