|
@@ -3,10 +3,17 @@ use "files"
|
|
|
use "format"
|
|
|
|
|
|
interface Value
|
|
|
+ """
|
|
|
+ Values referred to by opcodes in instructions should all be things
|
|
|
+ that implement this interface
|
|
|
+ """
|
|
|
fun value(): I64
|
|
|
|
|
|
|
|
|
class Position
|
|
|
+ """
|
|
|
+ A positional operand (i.e. one whose value is fetched from a memory cell
|
|
|
+ """
|
|
|
let _addr: I64
|
|
|
let _value: I64
|
|
|
|
|
@@ -18,6 +25,9 @@ class Position
|
|
|
fun value(): I64 => _value
|
|
|
|
|
|
class Immediate
|
|
|
+ """
|
|
|
+ An immediate operand (i.e. one whose value is a verbatim argument)
|
|
|
+ """
|
|
|
let _value: I64
|
|
|
|
|
|
new create(__value: I64) =>
|
|
@@ -26,6 +36,10 @@ class Immediate
|
|
|
fun value(): I64 => _value
|
|
|
|
|
|
class Operands
|
|
|
+ """
|
|
|
+ The operands to a typical binary operator: two values and a target
|
|
|
+ that will always be in positional (i.e. non-immediate) mode
|
|
|
+ """
|
|
|
let _lhs: Value
|
|
|
let _rhs: Value
|
|
|
let _tgt: I64
|
|
@@ -40,6 +54,10 @@ class Operands
|
|
|
fun tgt(): USize => _tgt.usize()
|
|
|
|
|
|
trait Error
|
|
|
+ """
|
|
|
+ The errors we can raise will all implement this so we can give
|
|
|
+ nice informative error messages
|
|
|
+ """
|
|
|
fun message(): String
|
|
|
|
|
|
class val OutOfBounds is Error
|
|
@@ -61,24 +79,46 @@ class val NotEnoughInput is Error
|
|
|
"Not enough input when running instruction at " + Format.int[USize](_pc)
|
|
|
|
|
|
|
|
|
+// I should wrap this but I haven't, so, uh, yeah
|
|
|
type Opcode is I64
|
|
|
+// a Mode is true if immediate, false if positional
|
|
|
type Modes is (Bool, Bool, Bool)
|
|
|
|
|
|
|
|
|
class Program
|
|
|
+ """
|
|
|
+ All the logic we need for running programs
|
|
|
+ """
|
|
|
+
|
|
|
+ // an array of memory cells
|
|
|
var storage: Array[I64]
|
|
|
+ // the current instruction we're running
|
|
|
var pc: USize = 0
|
|
|
+
|
|
|
+ // a list of errors that may or may not have happened. (right now,
|
|
|
+ // we'll only ever have one, but it's worth planning ahead, eh?)
|
|
|
var errors: Array[Error val] = Array[Error val]()
|
|
|
|
|
|
+ // the queue of input values from the user
|
|
|
var input: Array[I64]
|
|
|
+ // the queue of lines we intend to output
|
|
|
var output: Array[I64] = Array[I64]()
|
|
|
+ // a queue of debug messages
|
|
|
var debug: Array[String] = Array[String]()
|
|
|
|
|
|
- new create(s: Array[I64], i: Array[I64]) =>
|
|
|
+ new create(s: Array[I64], i: Array[I64] = Array[I64]()) =>
|
|
|
+ """
|
|
|
+ Create a Program from a verbatim storage and input array
|
|
|
+ """
|
|
|
storage = s
|
|
|
input = i
|
|
|
|
|
|
- new from_file(file: File, i: Array[I64]) =>
|
|
|
+ new from_file(file: File, i: Array[I64] = Array[I64]()) =>
|
|
|
+ """
|
|
|
+ Create a Program by walking input from a file. This just skips any
|
|
|
+ errors if they happen (e.g. if there are non-numbers), which
|
|
|
+ might not be what we want!
|
|
|
+ """
|
|
|
storage = Array[I64](0)
|
|
|
for line in file.lines() do
|
|
|
for chunk in line.split(",").values() do
|
|
@@ -91,6 +131,9 @@ class Program
|
|
|
|
|
|
|
|
|
fun print(env: Env) =>
|
|
|
+ """
|
|
|
+ Print out the current state of memory
|
|
|
+ """
|
|
|
env.out.write("[")
|
|
|
for x in storage.values() do
|
|
|
env.out.write(Format.int[I64](x))
|
|
@@ -99,10 +142,17 @@ class Program
|
|
|
env.out.print("]")
|
|
|
|
|
|
fun ref dbg(msg: String) =>
|
|
|
+ """
|
|
|
+ Log a debug message
|
|
|
+ """
|
|
|
debug.push(msg)
|
|
|
|
|
|
|
|
|
fun ref read_storage(addr: USize): I64 ? =>
|
|
|
+ """
|
|
|
+ Attempt to read a word from storage, logging an error message
|
|
|
+ if we fail to do so
|
|
|
+ """
|
|
|
try
|
|
|
storage(addr)?
|
|
|
else
|
|
@@ -111,6 +161,10 @@ class Program
|
|
|
end
|
|
|
|
|
|
fun ref write_storage(addr: USize, value: I64) ? =>
|
|
|
+ """
|
|
|
+ Attempt to write a word to storage, logging an error message
|
|
|
+ if we fail to do so
|
|
|
+ """
|
|
|
try
|
|
|
storage(addr)? = value
|
|
|
else
|
|
@@ -120,15 +174,25 @@ class Program
|
|
|
|
|
|
|
|
|
fun ref get_immediate(addr_loc: USize): Immediate ? =>
|
|
|
+ """
|
|
|
+ Read a single immediate value at the provided loc
|
|
|
+ """
|
|
|
let value = read_storage(addr_loc)?
|
|
|
Immediate(value)
|
|
|
|
|
|
fun ref get_position(addr_loc: USize): Position ? =>
|
|
|
+ """
|
|
|
+ Read a positional value whose address is stored at the provided loc
|
|
|
+ """
|
|
|
let addr = read_storage(addr_loc)?
|
|
|
let value = read_storage(addr.usize())?
|
|
|
Position(addr, value)
|
|
|
|
|
|
fun ref get_value(addr_loc: USize, mode: Bool): Value ? =>
|
|
|
+ """
|
|
|
+ Read a value at the provided loc based on the mode: if true, then
|
|
|
+ treat it as an immediate, otherwise treat it as a positional
|
|
|
+ """
|
|
|
if mode then
|
|
|
get_immediate(addr_loc)?
|
|
|
else
|
|
@@ -137,6 +201,9 @@ class Program
|
|
|
|
|
|
|
|
|
fun ref get_op_data(modes: Modes): Operands ? =>
|
|
|
+ """
|
|
|
+ Get the data that a binary op needs based on the provided modes
|
|
|
+ """
|
|
|
(let lm, let rm, _) = modes
|
|
|
let lhs = get_value(pc+1, lm)?
|
|
|
let rhs = get_value(pc+2, rm)?
|
|
@@ -144,6 +211,11 @@ class Program
|
|
|
Operands(lhs, rhs, tgt)
|
|
|
|
|
|
fun ref parse_op(n: I64): (Opcode, Modes) =>
|
|
|
+ """
|
|
|
+ Parse out an opcode and set of modes. This hard-codes modes
|
|
|
+ for three operands; it might be worth it in the future to generalize
|
|
|
+ to any N operands
|
|
|
+ """
|
|
|
let opcode = n % 100
|
|
|
let mode1 = (n / 100) % 10
|
|
|
let mode2 = (n / 1000) % 10
|
|
@@ -152,11 +224,21 @@ class Program
|
|
|
|
|
|
|
|
|
fun ref do_binop(modes: Modes, op: {(I64, I64): I64}) ? =>
|
|
|
+ """
|
|
|
+ Get the values of the first two operands by the provided modes, run
|
|
|
+ the function `op` on them, and store the result of that at the
|
|
|
+ provided location
|
|
|
+ """
|
|
|
let data = get_op_data(modes)?
|
|
|
write_storage(data.tgt(), op(data.val1(), data.val2()))?
|
|
|
pc = pc + 4
|
|
|
|
|
|
fun ref do_jump(modes: Modes, jump_cond: {(I64): Bool}) ? =>
|
|
|
+ """
|
|
|
+ Get the value indicated by the modes at [pc+1], and the function
|
|
|
+ `jump_cond` returns true on that value, then jump to the location
|
|
|
+ indicated by [pc+2], otherwise move ahead as usual
|
|
|
+ """
|
|
|
(let cond, let tgt, _) = modes
|
|
|
if jump_cond(get_value(pc+1, cond)?.value()) then
|
|
|
pc = get_value(pc+2, tgt)?.value().usize()
|
|
@@ -165,6 +247,10 @@ class Program
|
|
|
end
|
|
|
|
|
|
fun ref do_input(modes: Modes) ? =>
|
|
|
+ """
|
|
|
+ Read a value from the input and put it into storage. This logs an
|
|
|
+ error if there are not enough values in the input queue.
|
|
|
+ """
|
|
|
let i = try
|
|
|
input.shift()?
|
|
|
else
|
|
@@ -175,12 +261,20 @@ class Program
|
|
|
pc = pc + 2
|
|
|
|
|
|
fun ref do_output(modes: Modes) ? =>
|
|
|
+ """
|
|
|
+ Write the value indicated according to the mode to the output queue.
|
|
|
+ """
|
|
|
(let m, _, _) = modes
|
|
|
output.push(get_value(pc+1, m)?.value())
|
|
|
pc = pc + 2
|
|
|
|
|
|
|
|
|
- fun ref run() ? =>
|
|
|
+ fun ref run_loop() ? =>
|
|
|
+ """
|
|
|
+ This continues running the VM until it encounters a halt instruction
|
|
|
+ or it encounters some other error
|
|
|
+ """
|
|
|
+
|
|
|
while true do
|
|
|
(let opcode, let modes) = parse_op(read_storage(pc)?)
|
|
|
dbg("running op " + Format.int[I64](opcode))
|
|
@@ -200,9 +294,16 @@ class Program
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- fun ref run_trial(env: Env) =>
|
|
|
+ fun ref run(env: Env) =>
|
|
|
+ """
|
|
|
+ This runs the VM and prints output: if an error occurs, it prints
|
|
|
+ the error message(s) and any debug messages that happened during
|
|
|
+ program execution, and then it prints everything printed via an
|
|
|
+ output instruction.
|
|
|
+ """
|
|
|
+
|
|
|
try
|
|
|
- run()?
|
|
|
+ run_loop()?
|
|
|
else
|
|
|
for err in errors.values() do
|
|
|
env.out.print(err.message())
|
|
@@ -224,7 +325,7 @@ actor Main
|
|
|
file = OpenFile(FilePath(env.root as AmbientAuth, "input.txt", caps)?) as File
|
|
|
do
|
|
|
let program = Program.from_file(file, [5])
|
|
|
- program.run_trial(env)
|
|
|
+ program.run(env)
|
|
|
program.print(env)
|
|
|
end
|
|
|
else
|