///|
priv suberror ExprError {
  ExprError(String)
}

///|
fn[T] fail(message : String) -> T raise ExprError {
  raise ExprError(message)
}

///|
struct Parser {
  args : Array[String]
  mut pos : Int
}

///|
fn integer(s : String) -> Bool {
  let start = if s.has_prefix("-") { 1 } else { 0 }
  s.length() > start && s[start:].iter().all(c => c >= '0' && c <= '9')
}

///|
fn truth(s : String) -> Bool {
  s != "" && (!integer(s) || !BigInt::from_string(s).is_zero())
}

///|
fn precedence(s : String) -> Int {
  match s {
    "|" => 1
    "&" => 2
    "=" | "==" | "!=" | "<" | ">" | "<=" | ">=" => 3
    "+" | "-" => 4
    "*" | "/" | "%" => 5
    ":" => 6
    _ => 0
  }
}

///|
fn regex_match(value : String, pattern : String) -> String raise {
  let re = @bytere.compile(pattern)
  match re.find(value) {
    Some(hit) if hit.start == 0 =>
      if re.group_count() > 0 {
        hit.groups[1].unwrap_or("")
      } else {
        hit.end.to_string()
      }
    _ => if re.group_count() > 0 { "" } else { "0" }
  }
}

///|
fn Parser::atom(self : Parser, active : Bool) -> String raise {
  guard self.pos < self.args.length() else { fail("missing operand") }
  let token = self.args[self.pos]
  self.pos += 1
  match token {
    "(" => {
      let value = self.expression(1, active)
      guard self.pos < self.args.length() && self.args[self.pos] == ")" else {
        fail("missing closing parenthesis")
      }
      self.pos += 1
      value
    }
    "+" => {
      guard self.pos < self.args.length() else {
        fail("missing quoted operand")
      }
      let value = self.args[self.pos]
      self.pos += 1
      value
    }
    "length" => {
      let value = self.atom(active)
      value.length().to_string()
    }
    "index" => {
      let value = self.atom(active)
      let chars = self.atom(active)
      let mut found = 0
      for i in 0.. {
      let value = self.atom(active)
      let offset = self.atom(active)
      let length = self.atom(active)
      if !active || !integer(offset) || !integer(length) {
        return ""
      }
      let start = BigInt::from_string(offset)
      let count = BigInt::from_string(length)
      if start <= BigInt::from_int(0) ||
        count <= BigInt::from_int(0) ||
        start > BigInt::from_int(value.length()) {
        return ""
      }
      let left = start.to_int() - 1
      let available = value.length() - left
      let take = if count > BigInt::from_int(available) {
        available
      } else {
        count.to_int()
      }
      value[left:left + take].to_owned()
    }
    "match" => {
      let value = self.atom(active)
      let pattern = self.atom(active)
      if active {
        regex_match(value, pattern)
      } else {
        ""
      }
    }
    ")" => fail("unexpected closing parenthesis")
    _ => token
  }
}

///|
fn binary(op : String, left : String, right : String) -> String raise {
  if op == "|" {
    return if truth(left) { left } else if truth(right) { right } else { "0" }
  }
  if op == "&" {
    return if truth(left) && truth(right) { left } else { "0" }
  }
  if op == ":" {
    return regex_match(left, right)
  }
  if ["=", "==", "!=", "<", ">", "<=", ">="].contains(op) {
    let cmp = if integer(left) && integer(right) {
      BigInt::from_string(left).compare(BigInt::from_string(right))
    } else {
      left.compare(right)
    }
    let yes = match op {
      "=" | "==" => cmp == 0
      "!=" => cmp != 0
      "<" => cmp < 0
      ">" => cmp > 0
      "<=" => cmp <= 0
      _ => cmp >= 0
    }
    return if yes { "1" } else { "0" }
  }
  guard integer(left) && integer(right) else { fail("non-integer argument") }
  let a = BigInt::from_string(left)
  let b = BigInt::from_string(right)
  if (op == "/" || op == "%") && b.is_zero() {
    fail("division by zero")
  }
  let value = match op {
    "+" => a + b
    "-" => a - b
    "*" => a * b
    "/" => a / b
    _ => a % b
  }
  value.to_string()
}

///|
fn Parser::expression(
  self : Parser,
  minimum : Int,
  active : Bool,
) -> String raise {
  let mut left = self.atom(active)
  while self.pos < self.args.length() {
    let op = self.args[self.pos]
    let level = precedence(op)
    if level < minimum {
      break
    }
    self.pos += 1
    let use_right = active &&
      !(op == "|" && truth(left)) &&
      !(op == "&" && !truth(left))
    let right = self.expression(level + 1, use_right)
    if active {
      left = binary(op, left, right)
    }
  }
  left
}

///|
fn evaluate(args : Array[String]) -> String raise {
  let parser = { args, pos: 0, }
  let value = parser.expression(1, true)
  guard parser.pos == args.length() else {
    fail("unexpected argument: " + args[parser.pos])
  }
  value
}

///|
async fn main {
  let metadata_args = @env.args()[1:]
  if metadata_args.length() == 1 && metadata_args[0] == "--version" {
    @stdio.stdout.write("expr 0.1.1\n")
    return
  }
  let args = @env.args()[1:].to_owned()
  if args == ["--help"] {
    @stdio.stdout.write(
      "Usage: expr EXPRESSION\n      --help      display this help and exit\n      --version   display version and exit\nEvaluate integer/string expressions: | & comparisons + - * / % :\nFunctions: match, substr, index, length; + quotes one operand.\nC-locale byte strings; regex backreferences and locale collation are unsupported.\n",
    )
    return
  }
  if args == ["--version"] {
    @stdio.stdout.write("expr 0.1.1\n")
    return
  }
  if args.get(0) == Some("--") {
    ignore(args.remove(0))
  }
  let value = evaluate(args.map(@bytere.argument)) catch {
    ExprError(message) | @bytere.PatternError(message) => {
      @stdio.stderr.write("expr: \{message}\n")
      @sys.exit(2)
      return
    }
    err => {
      @stdio.stderr.write("expr: \{err}\n")
      @sys.exit(2)
      return
    }
  }
  @stdio.stdout.write(@bytere.bytes(value + "\n"))
  if !truth(value) {
    @sys.exit(1)
  }
}