///|
pub suberror ForthError {
Invalid(String)
Thrown(Int)
} derive(Debug)
///|
pub struct Machine {
priv stack : Array[Int]
priv words : Map[String, Array[String]]
priv immediate_words : Map[String, Bool]
priv mut latest_name : String?
priv mut compile_output : Array[String]?
priv mut compilation_state : Bool
priv mut compile_eval : Bool
priv data_addresses : Map[String, Int]
priv mut latest_created : Array[String]?
priv sources : Array[SourceFrame]
priv strings : Array[Byte]
priv string_cache : Map[String, (Int, Int)]
priv scratch : Array[Byte]
priv terminal : Array[Byte]
priv mut base : Int
priv mut catch_depth : Int
priv mut active_compiler : Compiler?
priv execution_tokens : Array[Array[String]]
priv mut binding_id : Int
priv output : Array[String]
priv memory : Array[Byte]
priv returns : Array[Int]
priv mut return_base : Int
priv loop_indices : Array[Int]
priv mut exiting : Bool
priv mut loop_base : Int
priv mut in_word : Bool
priv mut leaving : Bool
priv mut fuel : Int
} derive(Debug)
///|
pub fn Machine::new() -> Machine {
{
stack: [],
words: {},
binding_id: 0,
execution_tokens: [],
immediate_words: {},
latest_name: None,
compile_output: None,
compilation_state: false,
compile_eval: false,
data_addresses: {},
latest_created: None,
sources: [],
strings: [],
string_cache: {},
scratch: Array::make(1024, b'\x00'),
terminal: [],
base: 10,
catch_depth: 0,
active_compiler: None,
output: [],
memory: [],
returns: [],
return_base: 0,
loop_indices: [],
exiting: false,
loop_base: 0,
in_word: false,
leaving: false,
fuel: 0,
}
}
///|
pub fn Machine::values(self : Machine) -> Array[Int] {
self.stack.copy()
}
///|
pub fn Machine::printed(self : Machine) -> String {
self.output.join(" ")
}
///|
fn Machine::pop(self : Machine) -> Int raise ForthError {
match self.stack.pop() {
Some(v) => v
None => raise Invalid("stack underflow")
}
}
///|
fn number(s : String) -> Int? {
if s == "" || s == "-" {
return None
}
let neg = s.has_prefix("-")
let mut n : Int64 = 0L
for i in (if neg { 1 } else { 0 }).. 9 {
return None
}
n = n * 10L + d.to_int64()
if n > (if neg { 2147483648L } else { 2147483647L }) {
return None
}
}
(if neg { -n } else { n }).to_int() |> Some
}
///|
fn Machine::execute(
self : Machine,
code : Array[String],
depth : Int,
) -> Unit raise ForthError {
if depth > 64 {
raise Invalid("call depth exceeded")
}
let mut pc = 0
while pc < code.length() {
self.fuel -= 1
if self.fuel < 0 {
raise Invalid("execution budget exhausted")
}
let raw = code[pc]
let builtin = raw.has_prefix("\u0001")
let word = if builtin { raw[1:].to_owned() } else { raw }
pc += 1
if raw.has_prefix("\u0002") {
self.emit_postponed(raw[1:].to_owned())
} else if word == "'" {
let (name, next) = self.runtime_name(code, pc, depth)
pc = next
self.stack.push(self.capture_xt(name))
} else if word == "variable" || word == "constant" || word == "create" {
if self.compile_eval {
raise Invalid("defining words during compilation are unsupported")
}
let (name, next) = self.runtime_name(code, pc, depth)
pc = next
if number(name) is Some(_) ||
[":", ";", "if", "else", "then"].contains(name) {
raise Invalid("invalid data word name")
}
let value = if word == "constant" {
self.pop()
} else {
self.allocate((4 - self.memory.length() % 4) % 4)
let address = self.memory.length()
if word == "variable" {
self.allocate(4)
}
address
}
let data_body = [value.to_string()]
if word == "create" {
let identity = self.binding_key()
self.words[identity] = []
self.data_addresses[identity] = value
data_body.push(identity)
self.latest_created = Some(data_body)
} else {
self.latest_created = None
}
self.words[name] = data_body
self.latest_name = Some(name)
self.immediate_words[name] = false
} else if word == "does>" {
self.attach_does(code[pc:].to_owned())
return
} else if word == ":" || word == ":noname" {
if self.compile_eval {
raise Invalid("nested compilation")
}
let anonymous = word == ":noname"
let name = if anonymous {
self.binding_key()
} else {
self.source_name().to_lower()
}
if self.read_number(name) is Some(_) ||
[";", ":", ":noname"].contains(name) {
raise Invalid("invalid definition name")
}
self.compile_word(name, depth)
} else if word == "do" || word == "?do" {
let (body, plus, next) = counted_body(code, pc)
pc = next
let start = self.pop()
let limit = self.pop()
if word == "do" || start != limit {
self.counted_loop(body, start, limit, plus, depth + 1)
}
} else if word == "begin" {
let (body, tail, ending, next) = begin_body(code, pc)
pc = next
while true {
self.fuel -= 1
if self.fuel < 0 {
raise Invalid("execution budget exhausted")
}
self.execute(body, depth + 1)
if self.leaving || self.exiting {
return
}
if ending == "until" {
if self.pop() != 0 {
break
}
} else if ending == "repeat" {
if self.pop() == 0 {
break
}
self.execute(tail, depth + 1)
if self.leaving || self.exiting {
return
}
}
}
} else if word == "if" {
let condition = self.pop()
let yes = []
let no = []
let mut nesting = 1
let mut alternative = false
while pc < code.length() {
let t = code[pc]
pc += 1
if t == "if" {
nesting += 1
}
if t == "then" {
nesting -= 1
}
if nesting == 0 {
break
}
if t == "else" && nesting == 1 {
if alternative {
raise Invalid("duplicate else")
}
alternative = true
} else if alternative {
no.push(t)
} else {
yes.push(t)
}
}
if nesting != 0 {
raise Invalid("missing then")
}
self.execute(if condition != 0 { yes } else { no }, depth + 1)
} else if [
";", "else", "then", "while", "repeat", "until", "again", "loop", "+loop",
].contains(word) {
raise Invalid("unexpected control word: " + word)
} else if !builtin && self.words.get(word) is Some(body) {
self.execute_frame(body, depth + 1, true)
} else if number(word) is Some(n) {
self.stack.push(n)
} else {
match word {
"dup" => {
let a = self.pop()
self.stack.push(a)
self.stack.push(a)
}
"drop" => ignore(self.pop())
"swap" => {
let b = self.pop()
let a = self.pop()
self.stack.push(b)
self.stack.push(a)
}
"over" => {
let b = self.pop()
let a = self.pop()
self.stack.push(a)
self.stack.push(b)
self.stack.push(a)
}
"rot" => {
let c = self.pop()
let b = self.pop()
let a = self.pop()
self.stack.push(b)
self.stack.push(c)
self.stack.push(a)
}
"nip" => {
let b = self.pop()
ignore(self.pop())
self.stack.push(b)
}
"tuck" => {
let b = self.pop()
let a = self.pop()
self.stack.push(b)
self.stack.push(a)
self.stack.push(b)
}
"2dup" => {
let b = self.pop()
let a = self.pop()
self.stack.push(a)
self.stack.push(b)
self.stack.push(a)
self.stack.push(b)
}
"2drop" => {
ignore(self.pop())
ignore(self.pop())
}
"2swap" => {
let d = self.pop()
let c = self.pop()
let b = self.pop()
let a = self.pop()
self.stack.push(c)
self.stack.push(d)
self.stack.push(a)
self.stack.push(b)
}
"here" => self.stack.push(self.memory.length())
"allot" => {
let count = self.pop()
self.allocate(count)
}
"align" => self.allocate((4 - self.memory.length() % 4) % 4)
"aligned" => {
let n = self.pop()
self.stack.push((n + 3) & -4)
}
"cells" => {
let n = self.pop()
self.stack.push(n * 4)
}
"cell+" => {
let n = self.pop()
self.stack.push(n + 4)
}
"@" | "c@" => {
let address = self.pop()
self.stack.push(self.load(address, word == "@"))
}
"!" | "c!" | "+!" => {
let address = self.pop()
let value = self.pop()
let next = if word == "+!" {
self.load(address, true) + value
} else {
value
}
self.save(address, next, word != "c!")
}
"," | "c," => {
let value = self.pop()
let address = self.memory.length()
if word == "," && address % 4 != 0 {
raise Invalid("unaligned comma")
}
self.allocate(if word == "," { 4 } else { 1 })
self.save(address, value, word == ",")
}
"fill" | "erase" => {
let value = if word == "fill" {
self.pop().to_byte()
} else {
b'\x00'
}
let count = self.pop()
let address = self.pop()
self.memory_range(address, count)
for i in 0.. {
let count = self.pop()
let dest = self.pop()
let source = self.pop()
self.memory_range(source, count)
self.memory_range(dest, count)
let copy = self.read_bytes(source, count)
for i in 0.." | "1+" | "1-" => {
let value = self.pop()
self.stack.push(
match word {
"0=" => if value == 0 { -1 } else { 0 }
"0<" => if value < 0 { -1 } else { 0 }
"0>" => if value > 0 { -1 } else { 0 }
"1+" => value + 1
_ => value - 1
},
)
}
"i" | "j" => {
let back = if word == "i" { 1 } else { 2 }
if self.loop_indices.length() < back {
raise Invalid("loop index unavailable")
}
self.stack.push(self.loop_indices[self.loop_indices.length() - back])
}
"exit" => {
if !self.in_word {
raise Invalid("EXIT outside definition")
}
if self.loop_indices.length() != self.loop_base {
raise Invalid("EXIT requires UNLOOP for active loops")
}
if self.returns.length() != self.return_base {
raise Invalid("EXIT requires balanced return stack")
}
self.exiting = true
return
}
"unloop" => {
if !self.in_word || self.loop_indices.length() <= self.loop_base {
raise Invalid("UNLOOP outside current word loop")
}
ignore(self.loop_indices.pop())
}
"leave" => {
if self.loop_indices.is_empty() {
raise Invalid("LEAVE outside counted loop")
}
self.leaving = true
return
}
">r" | "r>" | "r@" | "2>r" | "2r>" | "2r@" => self.return_word(word)
"state" => self.stack.push(-4)
"immediate" => {
if self.compile_eval {
raise Invalid("IMMEDIATE during active compilation is unsupported")
}
match self.latest_name {
Some(name) => self.immediate_words[name] = true
None => raise Invalid("IMMEDIATE requires a named definition")
}
}
"compile," => {
let output = match self.compile_output {
Some(output) => output
None => raise Invalid("COMPILE, outside compilation")
}
let xt = self.pop()
if xt < 1 || xt > self.execution_tokens.length() {
raise Invalid("invalid execution token")
}
if output.length() >= 65534 {
raise Invalid("compiled definition limit")
}
output.push(xt.to_string())
output.push("\u0001execute")
}
">body" => {
let xt = self.pop()
self.stack.push(self.body_address(xt))
}
"execute" => {
let xt = self.pop()
if xt < 1 || xt > self.execution_tokens.length() {
raise Invalid("invalid execution token")
}
self.execute_frame(self.execution_tokens[xt - 1], depth + 1, true)
}
"depth" => self.stack.push(self.stack.length())
"." => {
let text = self.format_integer(self.pop(), false)
self.output.push(text)
self.write_text(text + " ")
}
"abs"
| "min"
| "max"
| "/mod"
| "*/"
| "*/mod"
| "2*"
| "2/"
| "invert"
| "xor"
| "lshift"
| "rshift"
| "u<"
| "u>"
| "within"
| "<>"
| "0<>" => self.integer_word(word)
"negate" => {
let a = self.pop()
self.stack.push(-a)
}
"+" | "-" | "*" | "/" | "mod" | "=" | "<" | ">" | "and" | "or" => {
let b = self.pop()
let a = self.pop()
if (word == "/" || word == "mod") &&
(b == 0 || (a == -2147483648 && b == -1)) {
raise Invalid("invalid division")
}
self.stack.push(
match word {
"+" => a + b
"-" => a - b
"*" => a * b
"/" => a / b
"mod" => a % b
"=" => if a == b { -1 } else { 0 }
"<" => if a < b { -1 } else { 0 }
">" => if a > b { -1 } else { 0 }
"and" => a & b
_ => a | b
},
)
}
_ => self.text_word(word, depth)
}
}
if self.leaving || self.exiting {
return
}
if self.stack.length() > 4096 {
raise Invalid("stack limit exceeded")
}
}
}
///|
/// State changes before an error are retained. Integer arithmetic uses 32-bit cells.
pub fn Machine::eval(
self : Machine,
source : String,
budget? : Int = 10000,
) -> Unit raise ForthError {
ignore(self.eval_chunk(source, budget, false))
}
///|
/// Feed a complete input buffer, keeping an unfinished definition for the next call.
pub fn Machine::feed(
self : Machine,
source : String,
budget? : Int = 10000,
) -> Bool raise ForthError {
self.eval_chunk(source, budget, true)
}
///|
pub fn Machine::is_compiling(self : Machine) -> Bool {
self.active_compiler is Some(_)
}
///|
fn Machine::eval_chunk(
self : Machine,
source : String,
budget : Int,
allow_pending : Bool,
) -> Bool raise ForthError {
if budget < 1 || source.length() > 1000000 {
raise Invalid("invalid input limit")
}
self.fuel = budget
let return_base = self.returns.length()
defer (while self.returns.length() > return_base {
ignore(self.returns.pop())
})
errdefer {
self.abort_compilation()
self.exiting = false
self.leaving = false
}
self.interpret_source(source, 1)
if self.returns.length() != return_base {
raise Invalid("unbalanced return stack at eval boundary")
}
if !allow_pending && self.active_compiler is Some(_) {
raise Invalid("unterminated definition")
}
self.is_compiling()
}