///|
fn atom(token : Token) -> Expr raise ConfigError {
  if token.quoted {
    return Constant(Text(token.text))
  }
  match token.text {
    "n" => Constant(Logic(N))
    "m" => Constant(Logic(M))
    "y" => Constant(Logic(Y))
    s => {
      if s.is_empty() {
        raise Invalid("empty atom")
      }
      let c = s[0]
      if (c >= 48 && c <= 57) || c == 45 || c == 43 {
        NumericLiteral(s, parse_number(s)) catch {
          _ => SymbolRef(s)
        }
      } else if identifier(s) {
        SymbolRef(s)
      } else {
        raise Invalid("invalid expression atom " + s)
      }
    }
  }
}

///|
fn precedence(op : String) -> Int32 {
  match op {
    "||" => 1
    "&&" => 2
    "=" | "!=" | "<" | ">" | "<=" | ">=" => 4
    _ => 0
  }
}

///|
fn expression_tokens(
  tokens : Array[Token],
  cursor : Ref[Int32],
  min : Int32,
  depth : Int32,
) -> Expr raise ConfigError {
  if depth > 128 {
    raise Invalid("expression nesting exceeds 128")
  }
  if cursor.val >= tokens.length() {
    raise Invalid("missing expression operand")
  }
  let t = tokens[cursor.val]
  cursor.val += 1
  let mut lhs = if !t.quoted && t.text == "!" {
    Negation(expression_tokens(tokens, cursor, 3, depth + 1))
  } else if !t.quoted && t.text == "(" {
    let inner = expression_tokens(tokens, cursor, 1, depth + 1)
    if cursor.val >= tokens.length() || tokens[cursor.val].text != ")" {
      raise Invalid("unclosed expression parenthesis")
    }
    cursor.val += 1
    inner
  } else {
    atom(t)
  }
  while cursor.val < tokens.length() {
    let op = tokens[cursor.val]
    let p = if op.quoted { 0 } else { precedence(op.text) }
    if p < min || p == 0 {
      break
    }
    cursor.val += 1
    let rhs = expression_tokens(tokens, cursor, p + 1, depth + 1)
    lhs = match op.text {
      "&&" => Conjunction(lhs, rhs)
      "||" => Disjunction(lhs, rhs)
      s => Comparison(s, lhs, rhs)
    }
  }
  lhs
}

///|
pub fn parse_expression(text : String) -> Expr raise ConfigError {
  parse_expression_tokens(tokenize(text))
}

///|
fn parse_expression_tokens(tokens : Array[Token]) -> Expr raise ConfigError {
  if tokens.length() > 256 {
    raise Invalid("expression exceeds 256 tokens")
  }
  let cursor = Ref(0)
  let expr = expression_tokens(tokens, cursor, 1, 0)
  if cursor.val != tokens.length() {
    raise Invalid("unexpected expression token " + tokens[cursor.val].text)
  }
  expr
}

///|
fn truth() -> Expr {
  Constant(Logic(Y))
}

///|
fn conditional(
  tokens : Array[Token],
  start : Int32,
) -> (Array[Token], Expr) raise ConfigError {
  let left : Array[Token] = []
  let right : Array[Token] = []
  let mut found = false
  for i in start.. (Expr, Expr) raise ConfigError {
  let (value, cond) = conditional(tokenize(text), 0)
  (parse_expression_tokens(value), cond)
}