///|
priv struct SourceFrame {
  data : Bytes
  address : Int
  mut cursor : Int
} derive(Debug)

///|
fn Machine::source_frame(self : Machine) -> SourceFrame raise ForthError {
  match self.sources.last() {
    Some(frame) => frame
    None => raise Invalid("no active input source")
  }
}

///|
fn SourceFrame::name(self : SourceFrame) -> String {
  while self.cursor < self.data.length() &&
        self.data[self.cursor].to_int() <= 32 {
    self.cursor += 1
  }
  let start = self.cursor
  while self.cursor < self.data.length() && self.data[self.cursor].to_int() > 32 {
    self.cursor += 1
  }
  let end = self.cursor
  if self.cursor < self.data.length() {
    self.cursor += 1
  }
  @utf8.decode_lossy(self.data[start:end])
}

///|
fn Machine::source_name(self : Machine) -> String raise ForthError {
  let name = self.source_frame().name()
  if name.is_empty() {
    raise Invalid("missing input name")
  }
  name
}

///|
fn Machine::parse_source(
  self : Machine,
  delimiter : Int,
  skip : Bool,
) -> (Int, Int) raise ForthError {
  if delimiter < 0 || delimiter > 255 {
    raise Invalid("delimiter must be a byte")
  }
  let frame = self.source_frame()
  if skip {
    while frame.cursor < frame.data.length() &&
          frame.data[frame.cursor].to_int() == delimiter {
      frame.cursor += 1
    }
  }
  let start = frame.cursor
  while frame.cursor < frame.data.length() &&
        frame.data[frame.cursor].to_int() != delimiter {
    frame.cursor += 1
  }
  let length = frame.cursor - start
  if frame.cursor < frame.data.length() {
    frame.cursor += 1
  }
  (frame.address + start, length)
}

///|
fn Machine::delimited(
  self : Machine,
  delimiter : Int,
) -> String raise ForthError {
  let frame = self.source_frame()
  let start = frame.cursor
  let (_, length) = self.parse_source(delimiter, false)
  if start + length == frame.data.length() {
    raise Invalid("unterminated string or comment")
  }
  @utf8.decode_lossy(frame.data[start:start + length])
}

///|
fn Machine::next_source_word(self : Machine) -> String raise ForthError {
  while true {
    let original = self.source_frame().name()
    let word = if original.length() == 3 &&
      original[0] == '\'' &&
      original[2] == '\'' {
      original
    } else {
      original.to_lower()
    }
    if word == "(" {
      ignore(self.delimited(41))
      continue
    }
    if word == "\\" {
      ignore(self.parse_source(10, false))
      continue
    }
    return word
  } nobreak {
    ""
  }
}

///|
fn Machine::intern_string(
  self : Machine,
  text : String,
  counted : Bool,
) -> (Int, Int) raise ForthError {
  self.intern_bytes(@utf8.encode(text).to_array(), counted)
}

///|
fn Machine::intern_bytes(
  self : Machine,
  bytes : Array[Byte],
  counted : Bool,
) -> (Int, Int) raise ForthError {
  let builder = StringBuilder()
  builder.write_string(if counted { "c" } else { "s" })
  for byte in bytes {
    builder.write_char(byte.to_char())
  }
  let key = builder.to_string()
  if self.string_cache.get(key) is Some(pair) {
    return pair
  }
  if counted && bytes.length() > 255 {
    raise Invalid("counted string exceeds 255 bytes")
  }
  if self.strings.length() + bytes.length() + (if counted { 2 } else { 1 }) >
    16000000 {
    raise Invalid("string storage limit")
  }
  let address = 1048576 + self.strings.length()
  if counted {
    self.strings.push(bytes.length().to_byte())
  }
  for byte in bytes {
    self.strings.push(byte)
  }
  self.strings.push(0)
  let pair = (address, bytes.length())
  self.string_cache[key] = pair
  pair
}

///|
fn Machine::string_code(
  self : Machine,
  word : String,
) -> Array[String] raise ForthError {
  if word == "s\\\"" {
    let (address, length) = self.intern_bytes(self.escaped_string(), false)
    return [address.to_string(), length.to_string()]
  }
  let text = self.delimited(if word == ".(" { 41 } else { 34 })
  if word == ".(" {
    self.write_text(text)
    return []
  }
  let (address, length) = self.intern_string(text, word == "c\"")
  if word == "c\"" {
    return [address.to_string()]
  }
  let code = [address.to_string(), length.to_string()]
  if word == ".\"" {
    code.push("\u0001type")
  }
  if word == "abort\"" {
    code.push("\u0001abort-message")
  }
  code
}

///|
fn string_word(word : String) -> Bool {
  ["s\"", ".\"", "c\"", ".(", "s\\\"", "abort\""].contains(word)
}

///|
fn Machine::source_control(
  self : Machine,
  first : String,
) -> Array[String] raise ForthError {
  let code = [first]
  let mut nesting = 1
  while nesting > 0 {
    self.fuel -= 1
    if self.fuel < 0 {
      raise Invalid("execution budget exhausted")
    }
    let word = self.next_source_word()
    if word.is_empty() {
      raise Invalid("unclosed source control structure")
    }
    if string_word(word) {
      for token in self.string_code(word) {
        code.push(token)
      }
      continue
    }
    if ["if", "begin", "do", "?do"].contains(word) {
      nesting += 1
    }
    if ["then", "until", "again", "repeat", "loop", "+loop"].contains(word) {
      nesting -= 1
    }
    code.push(
      if self.read_number(word) is Some(n) {
        n.to_string()
      } else {
        word
      },
    )
  }
  validate_structure(code)
  code
}

///|
fn Machine::interpret_token(
  self : Machine,
  word : String,
  depth : Int,
) -> Unit raise ForthError {
  if string_word(word) {
    self.execute(self.string_code(word), depth)
  } else if ["if", "begin", "do", "?do"].contains(word) {
    self.execute(self.source_control(word), depth)
  } else if !self.words.contains(word) &&
    !primitive_word(word) &&
    self.read_number(word) is Some(n) {
    self.execute([n.to_string()], depth)
  } else {
    self.execute([word], depth)
  }
}

///|
fn Machine::consume_source(
  self : Machine,
  word : String,
  depth : Int,
) -> Unit raise ForthError {
  if self.active_compiler is Some(compiler) {
    self.fuel -= 1
    if self.fuel < 0 {
      raise Invalid("execution budget exhausted")
    }
    if word == ";" {
      if !self.compilation_state {
        raise Invalid("missing closing bracket")
      }
      self.finish_compilation(compiler)
    } else if !self.compilation_state {
      if word == "]" {
        compiler.bracket_stack = None
        self.compilation_state = true
      } else if word == "[" {
        raise Invalid("nested interpretation bracket")
      } else {
        self.interpret_token(word, depth)
      }
    } else {
      self.compile_token(compiler, word, depth)
    }
  } else {
    self.interpret_token(word, depth)
  }
}

///|
fn Machine::interpret_source(
  self : Machine,
  source : String,
  depth : Int,
) -> Unit raise ForthError {
  if self.sources.length() >= 64 || depth > 64 || source.length() > 1000000 {
    raise Invalid("input source limit")
  }
  let data = @utf8.encode(source)
  for byte in data {
    if byte.to_int() < 3 {
      raise Invalid("reserved source character")
    }
  }
  let frame = SourceFrame::{
    data,
    address: 1000000000 + self.sources.length() * 4000000,
    cursor: 0,
  }
  self.sources.push(frame)
  defer ignore(self.sources.pop())
  while true {
    let word = self.next_source_word()
    if word.is_empty() {
      break
    }
    self.consume_source(word, depth)
  }
}