exp_if.pony

use json = "../json"

primitive IfExp
primitive IfDef
primitive IfType

type IfKind is (IfExp | IfDef | IfType)

class val ExpIf is NodeData
  """
    An `if` expression.
    - `kind`: `if`, `ifdef`, or `iftype`.
  """

  let kind: IfKind
  let conditions: NodeSeqWith[IfCondition]
  let else_block: (NodeWith[Expression] | None)

  new val create(
    kind': IfKind,
    conditions': NodeSeqWith[IfCondition],
    else_block': (NodeWith[Expression] | None))
  =>
    kind = kind'
    conditions = conditions'
    else_block = else_block'

  fun name(): String => "ExpIf"

  fun val clone(updates: ChildUpdateMap): NodeData =>
    ExpIf(
      kind,
      _map[IfCondition](conditions, updates),
      _map_or_none[Expression](else_block, updates))

  fun add_json_props(node: Node box, props: Array[(String, json.Item)]) =>
    let kind_str =
      match kind
      | IfExp => "IfExp"
      | IfDef => "IfDef"
      | IfType => "IfType"
      end
    props.push(("kind", kind_str))
    props.push(("conditions", node.child_refs(conditions)))
    match else_block
    | let block: Node =>
      props.push(("else_block", node.child_ref(block)))
    end

primitive ParseExpIf
  fun apply(obj: json.Object val, children: NodeSeq): (ExpIf | String) =>
    let kind =
      match try obj("kind")? end
      | let str: String box =>
        match str
        | "IfExp" =>
          IfExp
        | "IfDef" =>
          IfDef
        | "IfType" =>
          IfType
        else
          return "ExpIf.kind must be one of (IfExp | IfDef | IfType)"
        end
      else
        return "ExpIf.kind must be a string"
      end
    let conditions =
      match ParseNode._get_seq_with[IfCondition](
        obj,
        children,
        "conditions",
        "ExpIf.conditions must be a sequence of IfCondition")
      | let seq: NodeSeqWith[IfCondition] =>
        seq
      | let err: String =>
        return err
      end
    let else_block =
      match ParseNode._get_child_with[Expression](
        obj,
        children,
        "else_block",
        "ExpIf.else_block must be an Expression",
        false)
      | let node: NodeWith[Expression] =>
        node
      | let err: String =>
        return err
      end
    ExpIf(kind, conditions, else_block)

class val IfCondition is NodeData
  """
    A condition and then-block in an `if` expression (i.e. the initial `if` and
    `then` block; or subsequent `elseif` and `then` blocks).
  """
  let if_true: NodeWith[Expression]
  let then_block: NodeWith[Expression]

  new val create(
    if_true': NodeWith[Expression],
    then_block': NodeWith[Expression])
  =>
    if_true = if_true'
    then_block = then_block'

  fun name(): String => "IfCondition"

  fun val clone(updates: ChildUpdateMap): NodeData =>
    IfCondition(
      _map_with[Expression](if_true, updates),
      _map_with[Expression](then_block, updates))

  fun add_json_props(node: Node box, props: Array[(String, json.Item)]) =>
    props.push(("if_true", node.child_ref(if_true)))
    props.push(("then_block", node.child_ref(then_block)))

primitive ParseIfCondition
  fun apply(obj: json.Object val, children: NodeSeq): (IfCondition | String) =>
    let if_true =
      match ParseNode._get_child_with[Expression](
        obj,
        children,
        "if_true",
        "IfCondition.if_true must be an Expression")
      | let node: NodeWith[Expression] =>
        node
      | let err: String =>
        return err
      else
        return "IfCondition.if_true must be an Expression"
      end
    let then_block =
      match ParseNode._get_child_with[Expression](
        obj,
        children,
        "then_block",
        "IfCondition.then_block must be an Expression")
      | let node: NodeWith[Expression] =>
        node
      | let err: String =>
        return err
      else
        return "IfCondition.then_block must be an Expression"
      end
    IfCondition(if_true, then_block)