// The builtin vocabulary: the names an application may head, each with the
// arity that makes application unambiguous and the semantics evaluation uses.
//
// This was `enum Pred` — five constructors, an arity match and an apply match,
// all of them closed. Nothing an author could write joined the set, which is
// why a predicate needed its own AST node (`Val::Predicate`), its own parse
// path and its own no-kind exception. Applying a NAME to arguments is the
// general shape, and the five become the first rows of a table.
//
// The table is data rather than an enum for one reason worth stating plainly:
// `Val::App` carries the NAME, so `Val` still derives `Eq` and `Debug` even
// though the behaviour here is a closure. An enum in the AST would have had to
// grow a case per user-declared predicate, which is exactly the thing that
// cannot work.

///|
/// Every builtin, by name. Built once: `builtin` is on the parse path and the
/// eval path, and neither wants to rebuild a six-entry map per call.
let builtin_table : Map[String, Builtin] = {
  // `empty?` is deliberately not `falsy?`: a `0` is falsy and is not empty,
  // and an unsized value is neither.
  "empty?": {
    name: "empty?",
    arity: 1,
    apply: args => Bool(args[0] is Null || size_of(args[0]) is Some(0)),
  },
  // `truthy?` / `falsy?` are `pred_truthy`, NOT `Value::is_truthy`: the
  // predicates treat an empty collection as falsy and plain truthiness does
  // not. The difference is deliberate and is the reason both exist.
  "truthy?": {
    name: "truthy?",
    arity: 1,
    apply: args => Bool(pred_truthy(args[0])),
  },
  "falsy?": {
    name: "falsy?",
    arity: 1,
    apply: args => Bool(!pred_truthy(args[0])),
  },
  "null?": { name: "null?", arity: 1, apply: args => Bool(args[0] is Null) },
  // The one binary member. `is` says this in the expression language, and
  // this spelling stays for as long as the views use it.
  "equals?": {
    name: "equals?",
    arity: 2,
    apply: args => Bool(args[0] == args[1]),
  },
}

///|
/// The builtin a name denotes, or None when nothing does.
pub fn builtin(name : String) -> Builtin? {
  builtin_table.get(name)
}

///|
/// Every builtin name in BYTE order — the vocabulary a "did you mean" search
/// ranges over, and the reason it is public rather than a private table.
///
/// Not `Array::sort`: MoonBit's `Compare for String` is length-first, which
/// would answer `null?, empty?, falsy?, equals?, truthy?`. That is still
/// deterministic, and it reads as unordered to the person the list is for.
pub fn builtin_names() -> Array[String] {
  let out : Array[String] = []
  for name, _ in builtin_table {
    out.push(name)
  }
  out.sort_by((a, b) => a.lexical_compare(b))
  out
}