///|
priv struct Compiler {
  own : String
  name : String
  old_binding : Int
  old_tokens : Int
  stack_depth : Int
  mut bracket_stack : Array[Int]?
  output : Array[String]
  captures : Map[String, String]
  pending : Map[String, Array[String]]
  mut after_does : Bool
} derive(Debug)

///|
fn Machine::compile_token(
  self : Machine,
  compiler : Compiler,
  word : String,
  depth : Int,
) -> Unit raise ForthError {
  let output = compiler.output
  if output.length() >= 65536 {
    raise Invalid("compiled definition limit")
  }
  if string_word(word) {
    for token in self.string_code(word) {
      output.push(token)
    }
  } else if word == "[char]" {
    output.push(@utf8.encode(self.source_name())[0].to_int().to_string())
  } else if word == "postpone" {
    self.postpone_into(self.source_name().to_lower(), output)
  } else if word == "[" {
    compiler.bracket_stack = Some(self.stack.copy())
    self.compilation_state = false
  } else if word == "literal" {
    output.push(self.pop().to_string())
  } else if word == "does>" {
    compiler.after_does = true
    output.push("\u0001does>")
  } else if word == "[']" {
    output.push(self.capture_xt(self.source_name().to_lower()).to_string())
  } else if word == "recurse" {
    if compiler.after_does {
      raise Invalid("RECURSE after DOES> is unsupported")
    }
    output.push(compiler.own)
  } else if control_word(word) {
    output.push(word)
  } else if self.words.get(word) is Some(target) {
    if self.immediate_words.get(word) == Some(true) {
      self.execute_frame(target, depth + 1, true)
    } else {
      let key = match compiler.captures.get(word) {
        Some(key) => key
        None => {
          let key = self.binding_key()
          compiler.pending[key] = target
          compiler.captures[word] = key
          key
        }
      }
      output.push(key)
    }
  } else if primitive_word(word) {
    output.push("\u0001" + word)
  } else if self.read_number(word) is Some(n) {
    output.push(n.to_string())
  } else {
    raise Invalid("unknown or unsupported compile-time word: " + word)
  }
}

///|
fn Machine::compile_word(
  self : Machine,
  name : String,
  _depth : Int,
) -> Unit raise ForthError {
  if self.active_compiler is Some(_) {
    raise Invalid("nested compilation")
  }
  let old_binding = self.binding_id
  let own = self.binding_key()
  let compiler = Compiler::{
    own,
    name,
    old_binding,
    old_tokens: self.execution_tokens.length(),
    stack_depth: self.stack.length(),
    bracket_stack: None,
    output: [],
    captures: Map([]),
    pending: Map([]),
    after_does: false,
  }
  self.compilation_state = true
  self.compile_output = Some(compiler.output)
  self.active_compiler = Some(compiler)
  self.compile_eval = true
}

///|
fn Machine::abort_compilation(self : Machine) -> Unit {
  if self.active_compiler is Some(compiler) {
    self.binding_id = compiler.old_binding
    while self.execution_tokens.length() > compiler.old_tokens {
      ignore(self.execution_tokens.pop())
    }
    if compiler.bracket_stack is Some(saved) {
      self.stack.clear()
      for value in saved {
        self.stack.push(value)
      }
    }
  }
  self.active_compiler = None
  self.compile_output = None
  self.compilation_state = false
  self.compile_eval = false
}

///|
fn Machine::finish_compilation(
  self : Machine,
  compiler : Compiler,
) -> Unit raise ForthError {
  if self.stack.length() != compiler.stack_depth {
    raise Thrown(-2)
  }
  validate_structure(
    compiler.output.map(word => {
      if word == "\u0001does>" {
        "does>"
      } else {
        word
      }
    }),
  )
  for key, target in compiler.pending {
    self.words[key] = target
  }
  self.words[compiler.own] = compiler.output
  self.words[compiler.name] = compiler.output
  self.latest_created = None
  let anonymous = compiler.name.has_prefix("\u0000")
  self.latest_name = if anonymous { None } else { Some(compiler.name) }
  self.immediate_words[compiler.name] = false
  self.active_compiler = None
  self.compile_output = None
  self.compilation_state = false
  self.compile_eval = false
  if anonymous {
    self.stack.push(self.capture_xt(compiler.name))
  }
}