///|
/// Run up to `budget` instructions. Returns "halted", "line", "char", or "budget".
/// A budget prevents a hostile or looping story from monopolising its host.
pub fn Machine::run(
self : Machine,
budget~ : Int32 = 100000,
) -> String raise Fault {
if budget < 0 {
raise Invalid("negative instruction budget")
}
for _ in 0.. raise Invalid("\{message} (instruction at \{at})")
}
}
if self.halted {
"halted"
} else if self.waiting == 1 {
"line"
} else if self.waiting == 2 {
"char"
} else if self.waiting == 3 {
"save"
} else if self.waiting == 4 {
"restore"
} else {
"budget"
}
}
///|
fn Machine::execute(self : Machine, ins : Instruction) -> Unit raise Fault {
let a = ins.args
let x = if a.length() > 0 { a[0] } else { 0 }
let y = if a.length() > 1 { a[1] } else { 0 }
let z = if a.length() > 2 { a[2] } else { 0 }
match ins.form {
0 =>
match ins.opcode {
0 => self.ret(1)
1 => self.ret(0)
2 => self.pc = self.print_text(self.pc)
3 => {
self.pc = self.print_text(self.pc)
self.emit(13)
self.ret(1)
}
4 => ()
5 | 6 => {
if self.version != 3 {
raise Invalid("v3 save opcode in v5")
}
self.waiting = if ins.opcode == 5 { 3 } else { 4 }
}
7 => self.restart()
8 => self.ret(self.variable(0))
9 =>
if self.version == 3 {
let _ = self.variable(0)
} else {
self.store(self.frames.length() - 1)
}
10 => self.halted = true
11 => self.emit(13)
12 => () // v3 status line belongs to the host screen model.
13 => {
let scale = if self.version == 3 { 2 } else { 4 }
let n = (
self.original[26].to_int() * 256 + self.original[27].to_int()
) *
scale
let mut sum = 0
if n >= 64 && n <= self.original.length() {
for i in 64..= 64 && n <= self.original.length() && sum == expected)
}
15 => {
if self.version != 5 {
raise Invalid("piracy requires v5")
}
self.branch(true)
}
_ => raise Invalid("unknown 0OP opcode \{ins.opcode}")
}
1 => {
if a.length() != 1 {
raise Invalid("invalid 1OP operand count")
}
match ins.opcode {
0 => self.branch(x == 0)
1 | 2 => {
let value = self.relation(x, if ins.opcode == 1 { 1 } else { 2 })
self.store(value)
self.branch(value != 0)
}
3 => self.store(self.relation(x, 0))
4 => self.store(self.property_length(x))
5 | 6 => {
let value = self.variable(x, peek=true) +
(if ins.opcode == 5 { 1 } else { -1 })
self.assign(x, value, replace=true)
}
7 => {
let _ = self.print_text(x)
}
8 => {
if self.version != 5 {
raise Invalid("call_1s requires v5")
}
self.call(a, true)
}
9 => self.remove_object(x)
10 => {
let table = self.property_table(x)
if self.byte(table) != 0 {
let _ = self.print_text(table + 1)
}
}
11 => self.ret(x)
12 => self.pc += signed(x) - 2
13 => {
let _ = self.print_text(x * (if self.version == 3 { 2 } else { 4 }))
}
14 => self.store(self.variable(x, peek=true))
15 =>
if self.version == 3 {
self.store(x ^ 65535)
} else {
self.call(a, false)
}
_ => raise Invalid("unknown 1OP opcode")
}
}
2 => {
if a.length() < 2 || (ins.opcode != 1 && a.length() != 2) {
raise Invalid("invalid 2OP operand count")
}
match ins.opcode {
1 => {
let mut same = false
for i in 1.. self.branch(signed(x) < signed(y))
3 => self.branch(signed(x) > signed(y))
4 | 5 => {
let value = (
self.variable(x, peek=true) +
(if ins.opcode == 4 { -1 } else { 1 })
) &
65535
self.assign(x, value, replace=true)
self.branch(
if ins.opcode == 4 {
signed(value) < signed(y)
} else {
signed(value) > signed(y)
},
)
}
6 => self.branch(self.relation(x, 0) == y)
7 => self.branch((x & y) == y)
8 => self.store(x | y)
9 => self.store(x & y)
10 => self.branch(self.attribute(x, y, 0))
11 => {
let _ = self.attribute(x, y, 1)
}
12 => {
let _ = self.attribute(x, y, 2)
}
13 => self.assign(x, y, replace=true)
14 => self.insert_object(x, y)
15 => self.store(self.word((x + 2 * y) & 65535))
16 => self.store(self.byte((x + y) & 65535))
17 => self.store(self.get_property(x, y))
18 => {
let (data, _) = self.find_property(x, y)
self.store(data)
}
19 => self.store(self.next_property(x, y))
20 => self.store(x + y)
21 => self.store(x - y)
22 => self.store(signed(x) * signed(y))
23 | 24 => {
if y == 0 {
raise Invalid("division by zero")
}
self.store(
if ins.opcode == 23 {
signed(x) / signed(y)
} else {
signed(x) % signed(y)
},
)
}
25 | 26 => {
if self.version != 5 {
raise Invalid("call_2 requires v5")
}
self.call(a, ins.opcode == 25)
}
27 => if self.version != 5 { raise Invalid("set_colour requires v5") }
28 => {
if self.version != 5 || y < 1 || y >= self.frames.length() {
raise Invalid("invalid catch token")
}
while self.frames.length() > y + 1 {
let _ = self.frames.pop()
}
self.ret(x)
}
_ => raise Invalid("unknown 2OP opcode \{ins.opcode}")
}
}
3 =>
match ins.opcode {
0 | 12 | 25 | 26 => self.call(a, ins.opcode == 0 || ins.opcode == 12)
1 => {
self.require(a, 3)
self.put_word((x + y * 2) & 65535, z)
}
2 => {
self.require(a, 3)
self.put_byte((x + y) & 65535, z)
}
3 => {
self.require(a, 3)
let (addr, size) = self.find_property(x, y)
if addr == 0 || size > 2 {
raise Invalid("put_prop requires existing one or two byte property")
}
if size == 1 {
self.put_byte(addr, z)
} else {
self.put_word(addr, z)
}
}
4 => {
self.require(a, 2)
if a.length() > 2 && z != 0 {
raise Invalid("timed input not supported")
}
self.input_buffer = x
self.parse_buffer = y
self.input_store = if self.version == 5 { self.fetch() } else { -1 }
self.waiting = 1
}
5 => {
self.require(a, 1)
self.emit(x)
}
6 => {
self.require(a, 1)
for c in signed(x).to_string().to_array() {
self.emit(c.to_int())
}
}
7 => {
self.require(a, 1)
let range = signed(x)
if range <= 0 {
self.rng = if range == 0 {
(self.seed_source)() & 65535
} else {
-range
}
self.store(0)
} else {
let limit = 65536 - 65536 % range
while true {
self.rng = (self.rng * 25173 + 13849) & 65535
if self.rng < limit {
self.store(self.rng % range + 1)
break
}
}
}
}
8 => {
self.require(a, 1)
self.assign(0, x)
}
9 => {
self.require(a, 1)
let value = self.variable(0)
self.assign(x, value, replace=true)
}
10 => {
self.require(a, 1)
self.split_window(x)
}
11 => {
self.require(a, 1)
self.select_window(x)
}
13 => {
self.require(a, 1)
self.erase_window(x)
}
14 => {
self.require(a, 1)
if x == 1 && self.window == 1 && self.rows[1] <= self.split {
for col in (self.columns[1] - 1)..<80 {
self.upper[(self.rows[1] - 1) * 80 + col] = 32
}
}
}
15 => {
self.require(a, 2)
if self.window == 1 {
if x < 1 || x > self.split || y < 1 || y > 80 {
raise Invalid("cursor outside upper window")
}
self.rows[1] = x
self.columns[1] = y
}
}
16 => {
self.require(a, 1)
self.put_word(x, self.rows[self.window])
self.put_word(x + 2, self.columns[self.window])
}
17 | 18 => () // No styles are advertised in header Flags 1.
19 => {
self.require(a, 1)
match signed(x) {
1 => self.screen = true
-1 => self.screen = false
3 => {
self.require(a, 2)
if self.streams.length() >= 16 {
raise Invalid("output stream nesting limit")
}
self.put_word(y, 0)
self.streams.push(y)
}
-3 => {
if self.streams.is_empty() {
raise Invalid("unbalanced output stream 3")
}
let _ = self.streams.pop()
}
2 | -2 | 4 | -4 => ()
_ => raise Invalid("invalid output stream")
}
}
20 => {
self.require(a, 1)
if x != 0 {
raise Invalid("file input stream unavailable")
}
}
21 => ()
22 => {
self.require(a, 1)
if x != 1 {
raise Invalid("read_char requires keyboard device 1")
}
if a.length() > 1 && y != 0 {
raise Invalid("timed character input unavailable")
}
self.input_store = self.fetch()
self.waiting = 2
}
23 => {
self.require(a, 3)
let form = if a.length() >= 4 { a[3] } else { 0x82 }
let size = form & 127
if size == 0 {
raise Invalid("scan_table has zero entry size")
}
let mut found = 0
for i in 0.. {
self.require(a, 1)
self.store(x ^ 65535)
}
27 => {
self.require(a, 2)
self.tokenise(
x,
y,
if a.length() > 2 {
z
} else {
0
},
a.length() > 3 && a[3] != 0,
)
}
28 => {
self.require(a, 4)
let data = Array::makei(y, i => self.byte(x + z + i))
self.encode_dictionary(data, a[3])
}
29 => {
self.require(a, 3)
let n = signed(z)
let count = if n < 0 { -n } else { n }
if y == 0 {
for i in 0..= 0; i = i - 1 {
self.put_byte(y + i, self.byte(x + i))
}
}
}
30 => {
self.require(a, 2)
let height = if a.length() >= 3 { z } else { 1 }
let skip = if a.length() >= 4 { a[3] } else { 0 }
for row in 0.. 0 {
self.emit(13)
}
for col in 0.. {
self.require(a, 1)
self.branch(self.frame().argc >= x)
}
_ => raise Invalid("unknown VAR opcode \{ins.opcode}")
}
4 =>
match ins.opcode {
0 | 1 =>
if a.is_empty() {
self.waiting = if ins.opcode == 0 { 3 } else { 4 }
} else {
self.store(0)
} // Auxiliary files are not available through this host API.
9 => {
if self.undo.length() >= 16 {
self.undo.remove(0) |> ignore
}
self.undo.push(self.capture())
self.store(1)
}
10 =>
if self.undo.is_empty() {
self.store(0)
} else {
self.apply_saved(self.undo.pop().unwrap())
self.store(2)
}
2 | 3 => {
self.require(a, 2)
let shift = signed(y)
let value = if ins.opcode == 2 { x } else { signed(x) }
self.store(
if shift >= 16 {
0
} else if shift <= -16 {
if value < 0 {
-1
} else {
0
}
} else if shift >= 0 {
value << shift
} else {
value >> -shift
},
)
}
4 => {
self.require(a, 1)
let old = self.font
if x == 0 {
self.store(old)
} else if x == 1 || x == 4 {
self.font = x
self.store(old)
} else {
self.store(0)
}
}
11 => {
self.require(a, 1)
self.emit_unicode(x)
}
12 => {
self.require(a, 1)
self.store(if x >= 32 && x < 0xd800 { 3 } else { 0 })
}
13 => ()
_ => raise Invalid("unsupported EXT opcode \{ins.opcode}")
}
_ => raise Invalid("invalid instruction form")
}
}
///|
fn Machine::require(
_self : Machine,
args : Array[Int32],
count : Int32,
) -> Unit raise Fault {
if args.length() < count {
raise Invalid("missing instruction operands")
}
}
///|
fn Machine::restart(self : Machine) -> Unit raise Fault {
let fresh = Machine::new(self.original, seed_source=self.seed_source)
self.apply_saved(fresh.capture())
self.streams.clear()
self.undo.clear()
self.screen = true
self.upper.fill(32)
self.rows.fill(1)
self.columns.fill(1)
self.window = 0
self.split = 0
self.font = fresh.font
self.rng = fresh.rng
}