///|
impl Default for CoreForm with fn default() {
  SelfEval(Nil)
}

///|
pub impl ToSexp for CoreForm with fn to_sexp(self) {
  let s = Value::symbol
  match self {
    SelfEval(v) => v
    Var(name) => Symbol(name)
    Quote(v) => to_sexp([s("quote"), v])
    Lambda(parms, Begin(bodys, bodye)) =>
      to_sexp((s("lambda"), (parms, [..bodys, bodye])))
    Lambda(parms, body) => //
      to_sexp((s("lambda"), (parms, (body, Nil))))
    Begin(es, ee) => to_sexp((s("begin"), [..es, ee]))
    If(e1, e2, Some(e3)) => to_sexp((s("if"), [e1, e2, e3]))
    If(e1, e2, None) => to_sexp((s("if"), [e1, e2]))
    Apply(f, args) => to_sexp((f, args))
    Set(name, e) => to_sexp((s("set!"), (name, [e])))
    Define(name, e) => to_sexp((s("define"), (name, [e])))
  }
}

///|
pub impl FromSexp for CoreForm with fn from_sexp(self) {
  self.to_core_form()
}

///|
pub fn Value::to_core_form(self : Value) -> CoreForm raise FromSexpError {
  let to_quote = fn(r) raise FromSexpError {
    match r {
      Pair(r1, Nil) => Quote(r1)
      _ => raise FromSexpError(self)
    }
  }
  let to_if = fn(r) raise FromSexpError {
    match r {
      Pair(e1, Pair(e2, Nil)) => If(e1.to_core_form(), e2.to_core_form(), None)
      Pair(e1, Pair(e2, Pair(e3, Nil))) =>
        If(e1.to_core_form(), e2.to_core_form(), Some(e3.to_core_form()))
      _ => raise FromSexpError(self)
    }
  }
  let to_set = fn(r) -> CoreForm raise FromSexpError {
    match r {
      Pair(Symbol(name), Pair(e1, Nil)) => Set(name, e1.to_core_form())
      _ => raise FromSexpError(self)
    }
  }
  let to_define = fn(r) -> CoreForm raise FromSexpError {
    for r = r {
      match r {
        Pair(Pair(fname, parms), bodys) =>
          continue Pair(
              fname,
              Pair(Pair(Value::symbol("lambda"), Pair(parms, bodys)), Nil),
            )
        Pair(Symbol(name), Pair(e1, Nil)) =>
          break Define(name, e1.to_core_form())
        _ => raise FromSexpError(self)
      }
    }
  }
  let proper_list_length = fn(ls : Value) raise FromSexpError {
    guard ls.list_tail_and_length() is (Nil, length) else {
      raise FromSexpError(self)
    }
    length
  }
  let to_seq = fn(r) raise FromSexpError {
    let length = proper_list_length(r)
    guard length > 0 else { raise FromSexpError(self) }
    guard! to_core_from_list(r, length - 1) is (bodys, Pair(bodye, _))
    Begin(bodys, bodye.to_core_form())
  }
  let to_parms = fn(parms_list) raise FromSexpError {
    let length = proper_list_length(parms_list)
    let res = FixedArray::make(length, Symbol::default())
    for i = 0, p = parms_list; p is Pair(l, r); {
      guard l is Symbol(s) else { raise FromSexpError(self) }
      for x in res[:i] {
        guard x != s else { raise FromSexpError(self) }
      }
      res[i] = s
      continue i + 1, r
    }
    res
  }
  let to_lambda = fn(r) raise FromSexpError {
    guard r is Pair(parms_list, bodys) else { raise FromSexpError(self) }
    Lambda(to_parms(parms_list), to_seq(bodys))
  }
  let to_args = fn(r) raise FromSexpError {
    let length = proper_list_length(r)
    to_core_from_list(r, length).0
  }
  let expand_self = self.expand() catch {
    SyntaxError => raise FromSexpError(self)
  }
  match expand_self {
    Symbol(s) => Var(s)
    Pair(l, r) =>
      match l {
        Symbol(s) =>
          match s.to_string() {
            "quote" => to_quote(r)
            "lambda" => to_lambda(r)
            "begin" => to_seq(r)
            "if" => to_if(r)
            "set!" => to_set(r)
            "define" => to_define(r)
            _ => Apply(Var(s), to_args(r))
          }
        _ => Apply(l.to_core_form(), to_args(r))
      }
    Int(_) | Double(_) | True | False | String(_) as e => SelfEval(e)
    _ => raise FromSexpError(self)
  }
}

///|
fn to_core_from_list(
  ls : Value,
  length : Int,
) -> (FixedArray[CoreForm], Value) raise FromSexpError {
  let dist = FixedArray::make(length, Default::default())
  for i = 0, r = ls; i < length; {
    guard! r is Pair(l, r)
    dist[i] = l.to_core_form()
    continue i + 1, r
  } nobreak {
    (dist, r)
  }
}

///|
suberror SyntaxError {
  SyntaxError
}

///|
pub let expand_table : @hashmap.HashMap[
  Symbol,
  (Value) -> Value raise SyntaxError,
] = @hashmap.from_array([
  (
    Symbol::of("let"),
    body => {
      guard body is Pair(nvs, body) else { raise SyntaxError }
      fn split(nvs) raise SyntaxError {
        match nvs {
          Pair(Pair(n, Pair(v, Nil)), r) => {
            let (ns, vs) = split(r)
            (Pair(n, ns), Pair(v, vs))
          }
          Nil => (Nil, Nil)
          _ => raise SyntaxError
        }
      }

      let (names, values) = split(nvs)
      let lam = Pair(Value::symbol("lambda"), Pair(names, body))
      Pair(lam, values)
    },
  ),
  (
    Symbol::of("cond"),
    {
      fn expand_clauses(r) raise SyntaxError {
        match r {
          Pair(Pair(Symbol(s), Pair(texp, Nil)), r) if s == Symbol::of("else") => {
            guard r is Nil else { raise SyntaxError }
            texp
          }
          Pair(Pair(pred, Pair(texp, Nil)), Nil) =>
            Value::list([Value::symbol("if"), pred, texp])
          Pair(Pair(pred, Pair(texp, Nil)), r) =>
            Value::list([Value::symbol("if"), pred, texp, expand_clauses(r)])
          _ => raise SyntaxError
        }
      }

      expand_clauses
    },
  ),
  (
    Symbol::of("and"),
    body => {
      match body {
        Nil => True
        Pair(l, Nil) => l
        Pair(l, r) =>
          Value::list([
            Value::symbol("if"),
            l,
            Pair(Value::symbol("and"), r),
            False,
          ])
        _ => raise SyntaxError
      }
    },
  ),
  (
    Symbol::of("or"),
    body => {
      match body {
        Nil => False
        Pair(l, Nil) => l
        Pair(l, r) => {
          let var_l = Symbol(Symbol::generate())
          Value::list([
            Value::symbol("let"),
            Value::list([Value::list([var_l, l])]),
            Value::list([
              Value::symbol("if"),
              var_l,
              var_l,
              Pair(Value::symbol("or"), r),
            ]),
          ])
        }
        _ => raise SyntaxError
      }
    },
  ),
])

///|
fn Value::expand(exp : Value) -> Value raise SyntaxError {
  if exp is Pair(Symbol(s), body) && expand_table.get(s) is Some(expander) {
    expander(body).expand()
  } else {
    exp
  }
}