///|
fn decimal_char_code(text : String) -> Int? {
  let mut value = 0
  let mut saw_digit = false
  for code in text {
    guard code >= '0' && code <= '9' else { break }
    saw_digit = true
    let digit = code.to_int() - ('0' : Char).to_int()
    value = if value > (2147483647 - digit) / 10 {
      2147483647
    } else {
      value * 10 + digit
    }
  }

  guard saw_digit else { None }
  Some(value)
}

///|
fn char_argument_text(
  arg : ParseNode,
  func_name : String,
) -> String raise ParseFailure {
  guard arg is OrdGroup(body~, ..) else {
    raise InternalInvariant(message="Expected group argument for " + func_name)
  }
  let builder = StringBuilder()
  for node in body {
    guard node is TextOrd(text~, ..) else {
      raise InternalInvariant(
        message="Expected text character for " + func_name,
      )
    }
    builder.write_string(text)
  }
  builder.to_string()
}

///|
fn char_spec() -> FunctionSpec {
  FunctionSpec::make(["\\@char"], 1, allowed_in_text=true, handler=char_handler)
}

///|
fn char_handler(
  context : FunctionContext,
  args : Array[ParseNode],
  _opt_args : Array[ParseNode?],
) -> ParseNode raise ParseFailure {
  let number = char_argument_text(
    require_function_arg(args, 0, context.func_name),
    context.func_name,
  )
  guard decimal_char_code(number) is Some(code) else {
    raise InvalidArgument(
      message="\\@char has non-numeric argument " + number,
      loc=None,
    )
  }
  guard code >= 0 && code < 0x10ffff else {
    raise InvalidArgument(
      message="\\@char with invalid code point " + number,
      loc=None,
    )
  }
  guard code.to_char() is Some(character) else {
    raise InternalInvariant(
      message="Unable to convert validated character code",
    )
  }
  TextOrd(mode=context.mode, loc=None, text=String::from_array([character]))
}