main.pony 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. use "collections"
  2. use "files"
  3. use "format"
  4. interface Value
  5. """
  6. Values referred to by opcodes in instructions should all be things
  7. that implement this interface
  8. """
  9. fun value(): I64
  10. class Position
  11. """
  12. A positional operand (i.e. one whose value is fetched from a memory cell
  13. """
  14. let _addr: I64
  15. let _value: I64
  16. new create(__addr: I64, __value: I64) =>
  17. _addr = __addr
  18. _value = __value
  19. fun addr(): I64 => _addr
  20. fun value(): I64 => _value
  21. class Immediate
  22. """
  23. An immediate operand (i.e. one whose value is a verbatim argument)
  24. """
  25. let _value: I64
  26. new create(__value: I64) =>
  27. _value = __value
  28. fun value(): I64 => _value
  29. class Operands
  30. """
  31. The operands to a typical binary operator: two values and a target
  32. that will always be in positional (i.e. non-immediate) mode
  33. """
  34. let _lhs: Value
  35. let _rhs: Value
  36. let _tgt: I64
  37. new create(__lhs: Value, __rhs: Value, __tgt: I64) =>
  38. _lhs = __lhs
  39. _rhs = __rhs
  40. _tgt = __tgt
  41. fun val1(): I64 => _lhs.value()
  42. fun val2(): I64 => _rhs.value()
  43. fun tgt(): USize => _tgt.usize()
  44. trait Error
  45. """
  46. The errors we can raise will all implement this so we can give
  47. nice informative error messages
  48. """
  49. fun message(): String
  50. class val OutOfBounds is Error
  51. let _loc: USize
  52. new val create(loc: USize) => _loc = loc
  53. fun message(): String =>
  54. "Memory access out of bounds: " + Format.int[USize](_loc)
  55. class val UnknownOperand is Error
  56. let _op: I64
  57. new val create(op: I64) => _op = op
  58. fun message(): String =>
  59. "Unknown operation: " + Format.int[I64](_op)
  60. class val NotEnoughInput is Error
  61. let _pc: USize
  62. new val create(pc: USize) => _pc = pc
  63. fun message(): String =>
  64. "Not enough input when running instruction at " + Format.int[USize](_pc)
  65. // I should wrap this but I haven't, so, uh, yeah
  66. type Opcode is I64
  67. // a Mode is true if immediate, false if positional
  68. type Modes is (Bool, Bool, Bool)
  69. class Program
  70. """
  71. All the logic we need for running programs
  72. """
  73. // an array of memory cells
  74. var storage: Array[I64]
  75. // the current instruction we're running
  76. var pc: USize = 0
  77. // a list of errors that may or may not have happened. (right now,
  78. // we'll only ever have one, but it's worth planning ahead, eh?)
  79. var errors: Array[Error val] = Array[Error val]()
  80. // the queue of input values from the user
  81. var input: Array[I64]
  82. // the queue of lines we intend to output
  83. var output: Array[I64] = Array[I64]()
  84. // a queue of debug messages
  85. var debug: Array[String] = Array[String]()
  86. new create(s: Array[I64], i: Array[I64] = Array[I64]()) =>
  87. """
  88. Create a Program from a verbatim storage and input array
  89. """
  90. storage = s
  91. input = i
  92. new from_file(file: File, i: Array[I64] = Array[I64]()) =>
  93. """
  94. Create a Program by walking input from a file. This just skips any
  95. errors if they happen (e.g. if there are non-numbers), which
  96. might not be what we want!
  97. """
  98. storage = Array[I64](0)
  99. for line in file.lines() do
  100. for chunk in line.split(",").values() do
  101. try
  102. storage.push(chunk.i64()?)
  103. end
  104. end
  105. end
  106. input = i
  107. fun print(env: Env) =>
  108. """
  109. Print out the current state of memory
  110. """
  111. env.out.write("[")
  112. for x in storage.values() do
  113. env.out.write(Format.int[I64](x))
  114. env.out.write(" ")
  115. end
  116. env.out.print("]")
  117. fun ref dbg(msg: String) =>
  118. """
  119. Log a debug message
  120. """
  121. debug.push(msg)
  122. fun ref read_storage(addr: USize): I64 ? =>
  123. """
  124. Attempt to read a word from storage, logging an error message
  125. if we fail to do so
  126. """
  127. try
  128. storage(addr)?
  129. else
  130. errors.push(OutOfBounds(addr))
  131. error
  132. end
  133. fun ref write_storage(addr: USize, value: I64) ? =>
  134. """
  135. Attempt to write a word to storage, logging an error message
  136. if we fail to do so
  137. """
  138. try
  139. storage(addr)? = value
  140. else
  141. errors.push(OutOfBounds(addr))
  142. error
  143. end
  144. fun ref get_immediate(addr_loc: USize): Immediate ? =>
  145. """
  146. Read a single immediate value at the provided loc
  147. """
  148. let value = read_storage(addr_loc)?
  149. Immediate(value)
  150. fun ref get_position(addr_loc: USize): Position ? =>
  151. """
  152. Read a positional value whose address is stored at the provided loc
  153. """
  154. let addr = read_storage(addr_loc)?
  155. let value = read_storage(addr.usize())?
  156. Position(addr, value)
  157. fun ref get_value(addr_loc: USize, mode: Bool): Value ? =>
  158. """
  159. Read a value at the provided loc based on the mode: if true, then
  160. treat it as an immediate, otherwise treat it as a positional
  161. """
  162. if mode then
  163. get_immediate(addr_loc)?
  164. else
  165. get_position(addr_loc)?
  166. end
  167. fun ref get_op_data(modes: Modes): Operands ? =>
  168. """
  169. Get the data that a binary op needs based on the provided modes
  170. """
  171. (let lm, let rm, _) = modes
  172. let lhs = get_value(pc+1, lm)?
  173. let rhs = get_value(pc+2, rm)?
  174. let tgt = read_storage(pc+3)?
  175. Operands(lhs, rhs, tgt)
  176. fun ref parse_op(n: I64): (Opcode, Modes) =>
  177. """
  178. Parse out an opcode and set of modes. This hard-codes modes
  179. for three operands; it might be worth it in the future to generalize
  180. to any N operands
  181. """
  182. let opcode = n % 100
  183. let mode1 = (n / 100) % 10
  184. let mode2 = (n / 1000) % 10
  185. let mode3 = (n / 10000) % 10
  186. (opcode, (mode1 == 1, mode2 == 1, mode3 == 1))
  187. fun ref do_binop(modes: Modes, op: {(I64, I64): I64}) ? =>
  188. """
  189. Get the values of the first two operands by the provided modes, run
  190. the function `op` on them, and store the result of that at the
  191. provided location
  192. """
  193. let data = get_op_data(modes)?
  194. write_storage(data.tgt(), op(data.val1(), data.val2()))?
  195. pc = pc + 4
  196. fun ref do_jump(modes: Modes, jump_cond: {(I64): Bool}) ? =>
  197. """
  198. Get the value indicated by the modes at [pc+1], and the function
  199. `jump_cond` returns true on that value, then jump to the location
  200. indicated by [pc+2], otherwise move ahead as usual
  201. """
  202. (let cond, let tgt, _) = modes
  203. if jump_cond(get_value(pc+1, cond)?.value()) then
  204. pc = get_value(pc+2, tgt)?.value().usize()
  205. else
  206. pc = pc + 3
  207. end
  208. fun ref do_input(modes: Modes) ? =>
  209. """
  210. Read a value from the input and put it into storage. This logs an
  211. error if there are not enough values in the input queue.
  212. """
  213. let i = try
  214. input.shift()?
  215. else
  216. errors.push(NotEnoughInput(pc))
  217. error
  218. end
  219. write_storage(read_storage(pc+1)?.usize(), i)?
  220. pc = pc + 2
  221. fun ref do_output(modes: Modes) ? =>
  222. """
  223. Write the value indicated according to the mode to the output queue.
  224. """
  225. (let m, _, _) = modes
  226. output.push(get_value(pc+1, m)?.value())
  227. pc = pc + 2
  228. fun ref run_loop() ? =>
  229. """
  230. This continues running the VM until it encounters a halt instruction
  231. or it encounters some other error
  232. """
  233. while true do
  234. (let opcode, let modes) = parse_op(read_storage(pc)?)
  235. dbg("running op " + Format.int[I64](opcode))
  236. match opcode
  237. | 1 => do_binop(modes, {(x, y) => x + y})?
  238. | 2 => do_binop(modes, {(x, y) => x * y})?
  239. | 3 => do_input(modes)?
  240. | 4 => do_output(modes)?
  241. | 5 => do_jump(modes, {(x) => x != 0})?
  242. | 6 => do_jump(modes, {(x) => x == 0})?
  243. | 7 => do_binop(modes, {(x, y) => if x < y then 1 else 0 end})?
  244. | 8 => do_binop(modes, {(x, y) => if x == y then 1 else 0 end})?
  245. | 99 => return None
  246. | let x: I64 =>
  247. errors.push(UnknownOperand(x))
  248. error
  249. end
  250. end
  251. fun ref run(env: Env) =>
  252. """
  253. This runs the VM and prints output: if an error occurs, it prints
  254. the error message(s) and any debug messages that happened during
  255. program execution, and then it prints everything printed via an
  256. output instruction.
  257. """
  258. try
  259. run_loop()?
  260. else
  261. for err in errors.values() do
  262. env.out.print(err.message())
  263. end
  264. for n in debug.values() do
  265. env.out.print("[debug] " + n)
  266. end
  267. end
  268. for n in output.values() do
  269. env.out.print("> " + Format.int[I64](n))
  270. end
  271. actor Main
  272. new create(env: Env) =>
  273. let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
  274. try
  275. with
  276. file = OpenFile(FilePath(env.root as AmbientAuth, "input.txt", caps)?) as File
  277. do
  278. let program = Program.from_file(file, [5])
  279. program.run(env)
  280. program.print(env)
  281. end
  282. else
  283. // if something failed, then print an error message of some kind and exit
  284. env.err.print("Couldn't read expected file `input.txt'")
  285. env.exitcode(99)
  286. end