///|
fn Interpreter::subst_command(
  self : Interpreter,
  args : Array[String],
  depth : Int,
) -> String raise TclError {
  if args.length() < 2 {
    raise Invalid("subst string required")
  }
  let mut variables = true
  let mut commands = true
  let mut escapes = true
  for option in args[1:args.length() - 1] {
    let matches = ["-nobackslashes", "-nocommands", "-novariables"].filter(name => {
      name.has_prefix(option)
    })
    if matches.length() != 1 {
      raise Invalid("unknown or ambiguous subst option")
    }
    match matches[0] {
      "-nobackslashes" => escapes = false
      "-nocommands" => commands = false
      _ => variables = false
    }
  }
  let result = self.substitute(
    args[args.length() - 1],
    depth,
    variables,
    commands,
    escapes,
  )
  self.state.return_options.val = []
  result
}

///|
fn Interpreter::substitute(
  self : Interpreter,
  source : String,
  depth : Int,
  variables : Bool,
  commands : Bool,
  escapes : Bool,
) -> String raise TclError {
  if source.length() > 1000000 {
    raise Invalid("substitution input size limit")
  }
  let parser = ScriptParser::new(source)
  let output = StringBuilder()
  let mut size = 0
  while parser.pos < parser.chars.length() {
    let c = parser.peek()
    let value = try {
      if c == '$' && variables {
        // A variable token must parse completely before any index substitutions.
        // Index substitutions always use all three substitution kinds.
        self.expand_parts([parser.variable(0)], depth + 1)
      } else if c == '[' && commands {
        parser.pos += 1
        self.subst_bracket(parser, depth + 1)
      } else if c == '\\' && escapes {
        let (value, next) = backslash(parser.chars, parser.pos)
        parser.pos = next
        value
      } else {
        parser.pos += 1
        c.to_string()
      }
    } catch {
      error => {
        let result = outcome(error)
        match result.actual_code() {
          1 => raise Signal(result)
          3 => return output.to_string()
          4 => ""
          _ => result.value.text
        }
      }
    }
    size += value.length()
    if size > 1000000 {
      raise Invalid("substitution result size limit")
    }
    output.write_string(value)
  }
  output.to_string()
}

///|
fn Interpreter::subst_bracket(
  self : Interpreter,
  parser : ScriptParser,
  depth : Int,
) -> String raise TclError {
  let mut value = ""
  while true {
    let commands = parser.script(true, 0, one=true)
    let closed = parser.pos > 0 && parser.chars[parser.pos - 1] == ']'
    if !commands.is_empty() {
      value = self.execute_commands(commands, depth) catch {
        error => {
          let code = outcome(error).actual_code()
          // Error and break stop immediately. Other exceptional completions
          // still require a syntactically complete remaining bracket script.
          if code != 1 && code != 3 && !closed {
            ignore(parser.script(true, 0))
          }
          raise Signal(outcome(error))
        }
      }
    }
    if closed {
      break value
    }
  } nobreak {
    ""
  }
}

///|
pub fn is_complete(source : String) -> Bool raise TclError {
  if source.length() > 1000000 {
    raise Invalid("script completeness size limit")
  }
  let parser = ScriptParser::new(source)
  try {
    ignore(parser.script(false, 0))
    !parser.trailing_continuation
  } catch {
    Invalid(message) => {
      if message.has_suffix("limit") || message.has_suffix("depth") {
        raise Invalid(message)
      }
      !message.has_prefix("missing")
    }
    error => raise error
  }
}