disj.pony

class Disj[
  S: (Any #read & Equatable[S]),
  D: Any #share = None,
  V: Any #share = None]
  is RuleNodeWithChildren[S, D, V]
  """
  Matches one out of a list of possible alternatives.  Tries each alternative in
  order.  If one alternative fails, but an outer rule later fails, will *not*
  backtrack to another alternative.
  """

  let _children: Array[RuleNode[S, D, V]]
  let _action: (Action[S, D, V] | None)

  new create(
    children': Array[RuleNode[S, D, V]],
    action': (Action[S, D, V] | None) = None)
  =>
    _children = children'
    _action = action'

  fun children(): this->Seq[RuleNode[S, D, V]] =>
    _children

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

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

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

  let _rule: RuleNode[S, D, V] box
  let _depth: USize
  let _loc: Loc[S]
  let _children: ReadSeq[RuleNode[S, D, V] box]
  var _child_index: USize

  new create(
    rule: RuleNode[S, D, V] box,
    depth: USize,
    loc: Loc[S],
    children: ReadSeq[RuleNode[S, D, V] box])
  =>
    _rule = rule
    _depth = depth
    _loc = loc
    _children = children
    _child_index = 0

  fun ref run(child_result: (Result[S, D, V] | None)): _FrameResult[S, D, V] =>
    match child_result
    | let success: Success[S, D, V] =>
      let result = Success[S, D, V](_rule, _loc, success.next, [ success ])
      _Dbg() and _Dbg.out(_depth, "= " + result.string())
      return result
    | let failure: Failure[S, D, V] =>
      _child_index = _child_index + 1
      if _child_index == _children.size() then
        var message = ErrorMsg.disjunction_none()

        // a common pattern is to have an Error node last in a disjunction
        // in this case bubble up the error message
        var rightmost = failure
        while true do
          if rightmost.from_error then
            message = rightmost.get_message()
            break
          end
          match rightmost.inner
          | let inner': Failure[S, D, V] =>
            rightmost = inner'
          else
            break
          end
        end

        let result = Failure[S, D, V](_rule, _loc, message)
        _Dbg() and _Dbg.out(_depth, "= " + result.string())
        return result
      end
      // fall through
    end

    try
      if _child_index == 0 then
        _Dbg() and _Dbg.out(_depth, "DISJ @" + _loc.string())
      end
      _children(_child_index)?.call(_depth + 1, _loc)
    else
      _Dbg() and _Dbg.out(_depth, "= invalid child index")
      Failure[S, D, V](_rule, _loc, ErrorMsg.disjunction_failed())
    end