Browse Source

solve first part before going to wacky parallel stuff

Getty Ritter 4 years ago
parent
commit
1eaec0dc44
1 changed files with 388 additions and 0 deletions
  1. 388 0
      07/main.pony

+ 388 - 0
07/main.pony

@@ -0,0 +1,388 @@
+use "collections"
+use "files"
+use "format"
+use "itertools"
+
+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
+
+  new create(__addr: I64, __value: I64) =>
+    _addr = __addr
+    _value = __value
+
+  fun addr(): I64 => _addr
+  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) =>
+    _value = __value
+
+  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
+
+  new create(__lhs: Value, __rhs: Value, __tgt: I64) =>
+    _lhs = __lhs
+    _rhs = __rhs
+    _tgt = __tgt
+
+  fun val1(): I64 => _lhs.value()
+  fun val2(): I64 => _rhs.value()
+  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
+  let _loc: USize
+  new val create(loc: USize) => _loc = loc
+  fun message(): String =>
+    "Memory access out of bounds: " + Format.int[USize](_loc)
+
+class val UnknownOperand is Error
+  let _op: I64
+  new val create(op: I64) => _op = op
+  fun message(): String =>
+    "Unknown operation: " + Format.int[I64](_op)
+
+class val NotEnoughInput is Error
+  let _pc: USize
+  new val create(pc: USize) => _pc = pc
+  fun message(): String =>
+    "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] = Array[I64]()) =>
+    """
+    Create a Program from a verbatim storage and input array
+    """
+    storage = s
+    input = i
+
+  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
+        try
+          storage.push(chunk.i64()?)
+        end
+      end
+    end
+    input = i
+
+
+  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))
+      env.out.write(" ")
+    end
+    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
+      errors.push(OutOfBounds(addr))
+      error
+    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
+      errors.push(OutOfBounds(addr))
+      error
+    end
+
+
+  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
+      get_position(addr_loc)?
+    end
+
+
+  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
+    Operands(
+      get_value(pc+1, lm)?,
+      get_value(pc+2, rm)?,
+      read_storage(pc+3)?
+    )
+
+  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
+    let mode3 = (n / 10000) % 10
+    (opcode, (mode1 == 1, mode2 == 1, mode3 == 1))
+
+
+  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()
+    else
+      pc = pc + 3
+    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
+      errors.push(NotEnoughInput(pc))
+      error
+    end
+    write_storage(read_storage(pc+1)?.usize(), i)?
+    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_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))
+      match opcode
+        | 1 => do_binop(modes, {(x, y) => x + y})?
+        | 2 => do_binop(modes, {(x, y) => x * y})?
+        | 3 => do_input(modes)?
+        | 4 => do_output(modes)?
+        | 5 => do_jump(modes, {(x) => x != 0})?
+        | 6 => do_jump(modes, {(x) => x == 0})?
+        | 7 => do_binop(modes, {(x, y) => if x < y then 1 else 0 end})?
+        | 8 => do_binop(modes, {(x, y) => if x == y then 1 else 0 end})?
+        | 99 => return None
+        | let x: I64 =>
+          errors.push(UnknownOperand(x))
+          error
+      end
+    end
+
+  fun ref run(env: Env): Array[I64] =>
+    """
+    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_loop()?
+    else
+      for err in errors.values() do
+        env.out.print(err.message())
+      end
+      for n in debug.values() do
+        env.out.print("[debug] " + n)
+      end
+    end
+    for n in output.values() do
+      env.out.print("> " + Format.int[I64](n))
+    end
+    output
+
+  fun ref clone(): Program =>
+    Program(storage, input)
+
+  fun ref set_input(i: Array[I64]): Program =>
+    input = i
+    this
+
+actor Main
+  new create(env: Env) =>
+    let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
+
+    let target_file = try env.args(1)? else "input.txt" end
+    try
+      with
+        file = OpenFile(FilePath(env.root as AmbientAuth, target_file, caps)?) as File
+      do
+        let program = Program.from_file(file, [0;1])
+        var max: (I64 | None) = None
+        var choice = Array[I64]()
+        for p in permutations([0;1;2;3;4]).values() do
+          env.out.print("\ntesting")
+          let res = test_phases(env, program, p)
+          if try (res as I64) > (max as I64) else true end then
+            max = res
+            choice = p
+          end
+        end
+        env.out.print("maximum is " + max.string())
+        env.out.write("permutation was")
+        for i in choice.values() do
+          env.out.write(" " + i.string())
+        end
+        env.out.print("")
+      end
+    else
+      // if something failed, then print an error message of some kind and exit
+      env.err.print("Couldn't read expected file `input.txt'")
+      env.exitcode(99)
+    end
+
+  fun test_phases(env: Env, program: Program, phases: Array[I64]): (I64 | None) =>
+    var input: I64 = 0
+    for i in phases.values() do
+      let output = program.clone().set_input([i; input]).run(env)
+      input = try output(0)? else
+        env.out.print("Not enough output returned")
+        return None
+      end
+      None
+    end
+    input
+
+  fun permutations(array: Array[I64]): Array[Array[I64]] =>
+    if array.size() == 0 then
+      [[]]
+    else
+      let res = Array[Array[I64]]()
+      for a in array.values() do
+        let sm = Array[I64]()
+        Iter[I64](array.values()).filter({(x: I64) => x != a}).collect(sm)
+        let ps = permutations(sm)
+        for p in ps.values() do
+          p.unshift(a)
+          res.push(p)
+        end
+      end
+      res
+    end