///|
enum Expr {
  Value(String)
  Substitute(Array[Part])
  Unary(String, Expr)
  Binary(String, Expr, Expr)
  Conditional(Expr, Expr, Expr)
  Function(String, Array[Expr])
} derive(Debug)

///|
fn ScriptParser::expr_space(self : ScriptParser) -> Unit {
  while list_space(self.peek()) {
    self.pos += 1
  }
}

///|
fn ScriptParser::expression(
  self : ScriptParser,
  minimum : Int,
  depth : Int,
) -> Expr raise TclError {
  if depth > 64 {
    raise Invalid("expression nesting limit")
  }
  self.expr_space()
  let mut left = self.operand(depth + 1)
  while true {
    self.expr_space()
    if self.peek() == '?' && minimum <= 1 {
      self.pos += 1
      let yes = self.expression(1, depth + 1)
      self.expr_space()
      if self.peek() != ':' {
        raise Invalid("missing expression colon")
      }
      self.pos += 1
      left = Conditional(left, yes, self.expression(1, depth + 1))
      continue
    }
    let mut found = ("", 0)
    for
      (op, precedence) in [
        ("||", 2),
        ("&&", 3),
        ("|", 4),
        ("^", 5),
        ("&", 6),
        ("in", 7),
        ("ni", 7),
        ("eq", 8),
        ("ne", 8),
        ("==", 9),
        ("!=", 9),
        ("<=", 10),
        (">=", 10),
        ("<<", 11),
        (">>", 11),
        ("<", 10),
        (">", 10),
        ("+", 12),
        ("-", 12),
        ("**", 14),
        ("*", 13),
        ("/", 13),
        ("%", 13),
      ] {
      let cs = op.to_array()
      if self.pos + cs.length() <= self.chars.length() &&
        self.chars[self.pos:self.pos + cs.length()].to_owned() == cs {
        if namechar(cs[0]) && namechar(self.peek(offset=cs.length())) {
          continue
        }
        found = (op, precedence)
        break
      }
    }
    let (op, precedence) = found
    if precedence == 0 || precedence < minimum {
      break
    }
    self.pos += op.length()
    left = Binary(
      op,
      left,
      self.expression(
        if op == "**" {
          precedence
        } else {
          precedence + 1
        },
        depth + 1,
      ),
    )
  }
  left
}

///|
fn ScriptParser::operand(
  self : ScriptParser,
  depth : Int,
) -> Expr raise TclError {
  if depth > 64 {
    raise Invalid("expression nesting limit")
  }
  self.expr_space()
  let c = self.peek()
  if ['+', '-', '!', '~'].contains(c) {
    self.pos += 1
    return Unary(c.to_string(), self.operand(depth + 1))
  }
  if c == '(' {
    self.pos += 1
    let value = self.expression(1, depth + 1)
    self.expr_space()
    if self.peek() != ')' {
      raise Invalid("missing expression close-parenthesis")
    }
    self.pos += 1
    return value
  }
  if c == '{' {
    return Value(self.braces())
  }
  if c == '"' {
    self.pos += 1
    return Substitute(self.parts('"', false, depth + 1))
  }
  if c == '$' {
    return Substitute([self.variable(depth + 1)])
  }
  if c == '[' {
    self.pos += 1
    return Substitute([Command(self.script(true, depth + 1))])
  }
  let start = self.pos
  while self.pos < self.chars.length() {
    let x = self.peek()
    if list_space(x) ||
      [
        '(', ')', ',', '?', ':', '*', '/', '%', '<', '>', '=', '!', '&', '^', '|',
        '~',
      ].contains(x) {
      break
    }
    if x == '+' || x == '-' {
      let hex = self.pos >= start + 2 &&
        self.chars[start] == '0' &&
        ['x', 'X'].contains(self.chars[start + 1])
      if self.pos == start ||
        hex ||
        !['e', 'E'].contains(self.chars[self.pos - 1]) {
        break
      }
    }
    self.pos += 1
  }
  if start == self.pos {
    raise Invalid("expected expression operand")
  }
  let text = String::from_array(self.chars[start:self.pos])
  self.expr_space()
  if self.peek() == '(' {
    self.pos += 1
    self.expr_space()
    let args = []
    if self.peek() != ')' {
      while true {
        args.push(self.expression(1, depth + 1))
        self.expr_space()
        if self.peek() != ',' {
          break
        }
        self.pos += 1
      }
    }
    if self.peek() != ')' {
      raise Invalid("missing math function close-parenthesis")
    }
    self.pos += 1
    return Function(text, args)
  }
  if number(text) is None &&
    !(try {
      ignore(boolean(text))
      true
    } catch {
      _ => false
    }) {
    raise Invalid("invalid bareword " + text)
  }
  Value(text)
}

///|
fn Interpreter::eval_expr(
  self : Interpreter,
  expr : Expr,
  depth : Int,
) -> TclValue raise TclError {
  self.tick()
  if depth > 64 {
    raise Invalid("expression evaluation depth")
  }
  match expr {
    Value(value) => text_value(value)
    Substitute(parts) => self.expand_values(parts, depth + 1)
    Conditional(condition, yes, no) =>
      self.eval_expr(
        if self.eval_expr(condition, depth + 1).truth() {
          yes
        } else {
          no
        },
        depth + 1,
      )
    Unary(op, value) => {
      let v = self.eval_expr(value, depth + 1)
      if op == "!" {
        return text_value(boolean_text(!v.truth()))
      }
      if op == "~" {
        return number_value(Whole(-v.as_whole() - 1N))
      }
      let n = match v.as_number() {
        Some(n) => n
        None => raise Invalid("expected numeric operand")
      }
      match n {
        Small(x) => number_value(Small(if op == "-" { -x } else { x }))
        Whole(x) => number_value(Whole(if op == "-" { -x } else { x }))
        Real(x) => number_value(Real(if op == "-" { -x } else { x }))
      }
    }
    Binary(op, a, b) => {
      let left = self.eval_expr(a, depth + 1)
      if op == "&&" {
        return text_value(
          boolean_text(left.truth() && self.eval_expr(b, depth + 1).truth()),
        )
      }
      if op == "||" {
        return text_value(
          boolean_text(left.truth() || self.eval_expr(b, depth + 1).truth()),
        )
      }
      let right = self.eval_expr(b, depth + 1)
      if op == "/" {
        numeric_divide(left, right)
      } else {
        numeric_binary(op, left, right)
      }
    }
    Function(name, args) => {
      let values = args.map(arg => self.eval_expr(arg, depth + 1))
      self.math_function(name, values, depth + 1)
    }
  }
}

///|
fn Interpreter::expression_object(
  self : Interpreter,
  source : String,
  depth : Int,
) -> TclValue raise TclError {
  let expr = self.cached_expression(source)
  let value = self.eval_expr(expr, depth + 1)
  match value.as_number() {
    Some(n) => number_value(n)
    None => value
  }
}

///|
fn Interpreter::math(
  self : Interpreter,
  source : String,
  depth : Int,
) -> Int raise TclError {
  if boolean(self.expression_value(source, depth)) {
    1
  } else {
    0
  }
}

///|
fn Interpreter::math_function(
  self : Interpreter,
  name : String,
  args : Array[TclValue],
  depth : Int,
) -> TclValue raise TclError {
  if self.find_command("tcl::mathfunc::" + name) is Some(command) {
    return self.command_value([text_value(command.name)] + args, depth)
  }
  if name == "bool" {
    if args.length() != 1 {
      raise Invalid("math function arity")
    }
    return text_value(boolean_text(args[0].truth()))
  }
  let values = args.map(v => {
    match v.as_number() {
      Some(n) => n
      None => raise Invalid("math function requires number")
    }
  })
  if name == "min" || name == "max" {
    if values.is_empty() {
      raise Invalid("math function arity")
    }
    let mut best = args[0]
    for value in args[1:] {
      if numeric_binary(if name == "min" { "<" } else { ">" }, value, best).text ==
        "1" {
        best = value
      }
    }
    return match best.as_number() {
      Some(n) => number_value(n)
      None => best
    }
  }
  let two = ["pow", "atan2", "hypot", "fmod"].contains(name)
  if values.length() != (if two { 2 } else { 1 }) {
    raise Invalid("math function arity")
  }
  let x = values[0].double()
  let y = if two { values[1].double() } else { 0.0 }
  if name == "abs" {
    return match values[0] {
      Small(n) => number_value(Small(n.abs()))
      Whole(n) => number_value(Whole(if n < 0N { -n } else { n }))
      Real(n) => number_value(Real(n.abs()))
    }
  }
  if ["int", "wide", "entier", "round"].contains(name) {
    let n = match values[0] {
      Small(n) => @bigint.BigInt::from_int(n)
      Whole(n) => n
      Real(_) => {
        if x.is_inf() || x.is_nan() {
          raise Invalid("cannot convert non-finite integer")
        }
        let value = if name == "round" {
          if x < 0.0 {
            -(x.abs() + 0.5).floor()
          } else {
            (x + 0.5).floor()
          }
        } else if x < 0.0 {
          x.ceil()
        } else {
          x.floor()
        }
        double_integer(value)
      }
    }
    if name == "int" || name == "wide" {
      let modulus = 1N << 64
      let masked = n & (modulus - 1N)
      return number_value(
        Whole(if masked >= 1N << 63 { masked - modulus } else { masked }),
      )
    }
    return number_value(Whole(n))
  }
  let value = match name {
    "double" => x
    "ceil" => x.ceil()
    "floor" => x.floor()
    "sqrt" => x.sqrt()
    "sin" => @math.sin(x)
    "cos" => @math.cos(x)
    "tan" => @math.tan(x)
    "asin" => @math.asin(x)
    "acos" => @math.acos(x)
    "atan" => @math.atan(x)
    "sinh" => @math.sinh(x)
    "cosh" => @math.cosh(x)
    "tanh" => @math.tanh(x)
    "exp" => @math.exp(x)
    "log" => @math.ln(x)
    "log10" => @math.log10(x)
    "pow" => @math.pow(x, y)
    "atan2" => @math.atan2(x, y)
    "hypot" => @math.hypot(x, y)
    "fmod" =>
      x - y * (if x / y < 0.0 { (x / y).ceil() } else { (x / y).floor() })
    _ => raise Invalid("unknown math function " + name)
  }
  number_value(Real(value))
}

///|
fn double_integer(value : Double) -> @bigint.BigInt raise TclError {
  if value.is_nan() || value.is_inf() {
    raise Invalid("cannot convert non-finite integer")
  }
  let bits = value.reinterpret_as_uint64()
  let exponent = ((bits >> 52) & 2047UL).to_int() - 1023
  if exponent < 0 {
    return 0N
  }
  let mantissa = (bits & 4503599627370495UL) | 4503599627370496UL
  let n = @strconv.parse_bigint(mantissa.to_string()) catch {
    _ => raise Invalid("integer conversion")
  }
  let magnitude = if exponent >= 52 {
    n << (exponent - 52)
  } else {
    n >> (52 - exponent)
  }
  if value < 0.0 {
    -magnitude
  } else {
    magnitude
  }
}

///|
fn Interpreter::expression_value(
  self : Interpreter,
  source : String,
  depth : Int,
) -> String raise TclError {
  self.expression_object(source, depth).text
}