main.pony 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. use "collections"
  2. use "files"
  3. use "format"
  4. use "itertools"
  5. interface Value
  6. """
  7. Values referred to by opcodes in instructions should all be things
  8. that implement this interface
  9. """
  10. fun value(): I64
  11. interface Target
  12. fun value(): I64
  13. fun addr(): USize
  14. class Position
  15. """
  16. A positional operand (i.e. one whose value is fetched from a memory cell
  17. """
  18. let _addr: I64
  19. let _value: I64
  20. new create(__addr: I64, __value: I64) =>
  21. _addr = __addr
  22. _value = __value
  23. fun addr(): USize => _addr.usize()
  24. fun value(): I64 => _value
  25. class Immediate
  26. """
  27. An immediate operand (i.e. one whose value is a verbatim argument)
  28. """
  29. let _value: I64
  30. new create(__value: I64) =>
  31. _value = __value
  32. fun value(): I64 => _value
  33. class Relative
  34. """
  35. A realtive operand (i.e. one whose value is fetched from memory relative to a register)
  36. """
  37. let _addr: USize
  38. let _value: I64
  39. new create(__addr: USize, __value: I64) =>
  40. _addr = __addr
  41. _value = __value
  42. fun value(): I64 => _value
  43. fun addr(): USize => _addr
  44. class Operands
  45. """
  46. The operands to a typical binary operator: two values and a target
  47. that will always be in positional (i.e. non-immediate) mode
  48. """
  49. let _lhs: Value
  50. let _rhs: Value
  51. let _tgt: Target
  52. new create(__lhs: Value, __rhs: Value, __tgt: Target) =>
  53. _lhs = __lhs
  54. _rhs = __rhs
  55. _tgt = __tgt
  56. fun val1(): I64 => _lhs.value()
  57. fun val2(): I64 => _rhs.value()
  58. fun tgt(): USize => _tgt.addr()
  59. trait Error
  60. """
  61. The errors we can raise will all implement this so we can give
  62. nice informative error messages
  63. """
  64. fun message(): String
  65. class val OutOfBounds is Error
  66. let _loc: USize
  67. new val create(loc: USize) => _loc = loc
  68. fun message(): String =>
  69. "Memory access out of bounds: " + Format.int[USize](_loc)
  70. class val UnknownOperand is Error
  71. let _op: I64
  72. new val create(op: I64) => _op = op
  73. fun message(): String =>
  74. "Unknown operation: " + Format.int[I64](_op)
  75. class val NotEnoughInput is Error
  76. let _pc: USize
  77. new val create(pc: USize) => _pc = pc
  78. fun message(): String =>
  79. "Not enough input when running instruction at " + Format.int[USize](_pc)
  80. // I should wrap this but I haven't, so, uh, yeah
  81. type Opcode is I64
  82. // a Mode is true if immediate, false if positional
  83. type Modes is (Mode, Mode, Mode)
  84. primitive ModeImm
  85. fun sigil(): String => ""
  86. primitive ModePos
  87. fun sigil(): String => "*"
  88. primitive ModeRel
  89. fun sigil(): String => "~"
  90. type Mode is (ModeImm | ModePos | ModeRel)
  91. class val OutputRequest
  92. let _o: I64
  93. new val create(_o': I64) => _o = _o'
  94. fun output(): I64 => _o
  95. fun string(): String =>
  96. "OutputRequest(" + _o.string() + ")"
  97. primitive InputRequest
  98. fun string(): String => "InputRequest"
  99. primitive Halted
  100. fun string(): String => "Halted"
  101. type ProgramResult is (OutputRequest | InputRequest | Halted)
  102. class Program
  103. """
  104. All the logic we need for running programs
  105. """
  106. // an array of memory cells
  107. var storage: Array[I64]
  108. // the current instruction we're running
  109. var pc: USize = 0
  110. //
  111. var input: Array[I64] = Array[I64]()
  112. //
  113. var rel_base: I64 = 0
  114. // a list of errors that may or may not have happened. (right now,
  115. // we'll only ever have one, but it's worth planning ahead, eh?)
  116. var errors: Array[Error val] = Array[Error val]()
  117. // a queue of debug messages
  118. var debug: Array[String] = Array[String]()
  119. new create(s: Array[I64]) =>
  120. """
  121. Create a Program from a verbatim storage and input array
  122. """
  123. storage = s
  124. new from_file(file: File) =>
  125. """
  126. Create a Program by walking input from a file. This just skips any
  127. errors if they happen (e.g. if there are non-numbers), which
  128. might not be what we want!
  129. """
  130. storage = Array[I64](0)
  131. for line in file.lines() do
  132. for chunk in line.split(",").values() do
  133. try
  134. storage.push(chunk.i64()?)
  135. end
  136. end
  137. end
  138. fun ref send_input(i: I64) =>
  139. input.push(i)
  140. fun print(env: Env) =>
  141. """
  142. Print out the current state of memory
  143. """
  144. env.out.write("[")
  145. for x in storage.values() do
  146. env.out.write(Format.int[I64](x))
  147. env.out.write(" ")
  148. end
  149. env.out.print("]")
  150. fun ref dbg(msg: String) =>
  151. """
  152. Log a debug message
  153. """
  154. debug.push(msg)
  155. fun ref read_storage(addr: USize): I64 ? =>
  156. """
  157. Attempt to read a word from storage, logging an error message
  158. if we fail to do so
  159. """
  160. while addr > (storage.size() - 1) do
  161. storage.push(0)
  162. end
  163. try
  164. storage(addr)?
  165. else
  166. errors.push(OutOfBounds(addr))
  167. error
  168. end
  169. fun ref write_storage(addr: USize, value: I64) ? =>
  170. """
  171. Attempt to write a word to storage, logging an error message
  172. if we fail to do so
  173. """
  174. while addr > (storage.size() - 1) do
  175. storage.push(0)
  176. end
  177. try
  178. storage(addr)? = value
  179. else
  180. errors.push(OutOfBounds(addr))
  181. error
  182. end
  183. fun ref get_immediate(addr_loc: USize): Immediate ? =>
  184. """
  185. Read a single immediate value at the provided loc
  186. """
  187. let value = read_storage(addr_loc)?
  188. Immediate(value)
  189. fun ref get_position(addr_loc: USize): Position ? =>
  190. """
  191. Read a positional value whose address is stored at the provided loc
  192. """
  193. let addr = read_storage(addr_loc)?
  194. let value = read_storage(addr.usize())?
  195. Position(addr, value)
  196. fun ref get_relative(addr_loc: USize): Relative ? =>
  197. """
  198. Read a positional value whose address is stored at the provided loc
  199. """
  200. let offset = read_storage(addr_loc)?
  201. let addr = offset + rel_base
  202. let value = read_storage(addr.usize())?
  203. dbg(" (rel: base " + rel_base.string() + " delta " + offset.string() + ")")
  204. Relative(addr.usize(), value)
  205. fun ref get_value(addr_loc: USize, mode: Mode): Value ? =>
  206. """
  207. Read a value at the provided loc based on the mode: if true, then
  208. treat it as an immediate, otherwise treat it as a positional
  209. """
  210. match mode
  211. | ModeImm => get_immediate(addr_loc)?
  212. | ModePos => get_position(addr_loc)?
  213. | ModeRel => get_relative(addr_loc)?
  214. end
  215. fun ref get_target(addr_loc: USize, mode: Mode): Target ? =>
  216. """
  217. Read a value at the provided loc based on the mode: if true, then
  218. treat it as an immediate, otherwise treat it as a positional
  219. """
  220. match mode
  221. | ModeImm => error
  222. | ModePos => get_position(addr_loc)?
  223. | ModeRel => get_relative(addr_loc)?
  224. end
  225. fun ref get_op_data(modes: Modes): Operands ? =>
  226. """
  227. Get the data that a binary op needs based on the provided modes
  228. """
  229. (let lm, let rm, let tm) = modes
  230. Operands(
  231. get_value(pc+1, lm)?,
  232. get_value(pc+2, rm)?,
  233. get_target(pc+3, tm)?
  234. )
  235. fun ref mk_mode(n: I64): Mode =>
  236. match n
  237. | 0 => ModePos
  238. | 1 => ModeImm
  239. | 2 => ModeRel
  240. else
  241. ModeImm
  242. end
  243. fun ref parse_op(n: I64): (Opcode, Modes) =>
  244. """
  245. Parse out an opcode and set of modes. This hard-codes modes
  246. for three operands; it might be worth it in the future to generalize
  247. to any N operands
  248. """
  249. let opcode = n % 100
  250. let mode1 = (n / 100) % 10
  251. let mode2 = (n / 1000) % 10
  252. let mode3 = (n / 10000) % 10
  253. (opcode, (mk_mode(mode1), mk_mode(mode2), mk_mode(mode3)))
  254. fun ref do_binop(modes: Modes, op: {(I64, I64): I64}, op_name: String) ? =>
  255. """
  256. Get the values of the first two operands by the provided modes, run
  257. the function `op` on them, and store the result of that at the
  258. provided location
  259. """
  260. let data = get_op_data(modes)?
  261. dbg(" writing " + data.val1().string() + op_name + data.val2().string()
  262. + " = " + op(data.val1(), data.val2()).string() + " to *" + data.tgt().string())
  263. write_storage(data.tgt(), op(data.val1(), data.val2()))?
  264. pc = pc + 4
  265. fun ref do_adjust_base(modes: Modes) ? =>
  266. (let m, _, _) = modes
  267. let delta = get_value(pc+1, m)?.value()
  268. rel_base = rel_base + delta.i64()
  269. pc = pc + 2
  270. fun ref do_jump(modes: Modes, jump_cond: {(I64): Bool}) ? =>
  271. """
  272. Get the value indicated by the modes at [pc+1], and the function
  273. `jump_cond` returns true on that value, then jump to the location
  274. indicated by [pc+2], otherwise move ahead as usual
  275. """
  276. (let cond, let tgt, _) = modes
  277. if jump_cond(get_value(pc+1, cond)?.value()) then
  278. pc = get_value(pc+2, tgt)?.value().usize()
  279. else
  280. pc = pc + 3
  281. end
  282. fun ref do_input(modes: Modes) ? =>
  283. """
  284. Read a value from the input and put it into storage. This logs an
  285. error if there are not enough values in the input queue.
  286. """
  287. let i = try
  288. input.shift()?
  289. else
  290. errors.push(NotEnoughInput(pc))
  291. error
  292. end
  293. (let m, _, _) = modes
  294. let addr = match get_value(pc+1, m)?
  295. | let r: Relative => r.addr().usize()
  296. | let p: Position => p.addr().usize()
  297. else
  298. error
  299. end
  300. dbg(" writing " + i.string() + " to *" + addr.string())
  301. write_storage(addr, i)?
  302. pc = pc + 2
  303. fun ref do_output(modes: Modes): OutputRequest ? =>
  304. """
  305. Write the value indicated according to the mode to the output queue.
  306. """
  307. (let m, _, _) = modes
  308. let r = get_value(pc+1, m)?.value()
  309. pc = pc + 2
  310. OutputRequest(r)
  311. fun ref at_pc(): USize => pc
  312. fun ref fmt_op(opcode: I64, modes: Modes): String =>
  313. (let a, let b, let c) = modes
  314. let op1 = try read_storage(pc+1)? else -99 end
  315. let op2 = try read_storage(pc+2)? else -99 end
  316. let op3 = try read_storage(pc+3)? else -99 end
  317. let arg1 = a.sigil() + op1.string()
  318. let arg2 = b.sigil() + op2.string()
  319. let arg3 = c.sigil() + op3.string()
  320. match opcode
  321. | 1 => "add " + arg1 + " " + arg2 + " -> " + arg3
  322. | 2 => "mul " + arg1 + " " + arg2 + " -> " + arg3
  323. | 3 => "input " + arg1
  324. | 4 => "output " + arg1
  325. | 5 => "jnz " + arg1 + " to " + arg2
  326. | 6 => "jez " + arg1 + " to " + arg2
  327. | 7 => "lt " + arg1 + " " + arg2 + " -> " + arg3
  328. | 8 => "eq " + arg1 + " " + arg2 + " -> " + arg3
  329. | 9 => "adj_base " + arg1
  330. | 99 => "halt"
  331. else
  332. "unknown op(" + opcode.string() + ")"
  333. end
  334. fun ref run_loop(env: Env): ProgramResult ? =>
  335. """
  336. This continues running the VM until it encounters a halt instruction
  337. or it encounters some other error
  338. """
  339. while true do
  340. (let opcode, let modes) = parse_op(read_storage(pc)?)
  341. env.out.print(" " + pc.string() + "/" + rel_base.string() + " -> " + fmt_op(opcode, modes))
  342. match opcode
  343. | 1 => do_binop(modes, {(x, y) => x + y}, "+")?
  344. | 2 => do_binop(modes, {(x, y) => x * y}, "*")?
  345. | 3 => try do_input(modes)? else return InputRequest end
  346. | 4 => return do_output(modes)?
  347. | 5 => do_jump(modes, {(x) => x != 0})?
  348. | 6 => do_jump(modes, {(x) => x == 0})?
  349. | 7 => do_binop(modes, {(x, y) => if x < y then 1 else 0 end}, "<")?
  350. | 8 => do_binop(modes, {(x, y) => if x == y then 1 else 0 end}, "==")?
  351. | 9 => do_adjust_base(modes)?
  352. | 99 => return Halted
  353. | let x: I64 =>
  354. errors.push(UnknownOperand(x))
  355. error
  356. end
  357. end
  358. Halted
  359. fun ref run_with(env: Env, i: Array[I64]): Array[I64] =>
  360. """
  361. This runs the VM and prints output: if an error occurs, it prints
  362. the error message(s) and any debug messages that happened during
  363. program execution, and then it prints everything printed via an
  364. output instruction.
  365. """
  366. input.append(i)
  367. var output = Array[I64]()
  368. var is_done = false
  369. try
  370. repeat
  371. env.out.print("...")
  372. match run_loop(env)?
  373. | Halted => is_done = true
  374. | InputRequest => env.err.print("Not enough input for program"); is_done = true
  375. | let o: OutputRequest => output.push(o.output())
  376. end
  377. for n in debug.values() do
  378. env.out.print("[debug] " + n)
  379. end
  380. until is_done end
  381. else
  382. for err in errors.values() do
  383. env.out.print(err.message())
  384. end
  385. for n in debug.values() do
  386. env.out.print("[debug] " + n)
  387. end
  388. end
  389. for n in output.values() do
  390. env.out.print("> " + Format.int[I64](n))
  391. end
  392. output
  393. fun ref run_step(env: Env): ProgramResult =>
  394. try
  395. env.out.print("restarting from " + pc.string())
  396. let r = run_loop(env)?
  397. for msg in debug.values() do
  398. env.out.print(msg)
  399. end
  400. debug.clear()
  401. r
  402. else
  403. for err in errors.values() do
  404. env.out.print(err.message())
  405. end
  406. Halted
  407. end
  408. fun ref clone(): Program =>
  409. Program(storage.clone())
  410. actor Main
  411. new create(env: Env) =>
  412. let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
  413. let target_file = try env.args(1)? else "input.txt" end
  414. try
  415. with
  416. file = OpenFile(FilePath(env.root as AmbientAuth, target_file, caps)?) as File
  417. do
  418. let program = Program.from_file(file)
  419. program.run_with(env, [2])
  420. end
  421. else
  422. // if something failed, then print an error message of some kind and exit
  423. env.err.print("Couldn't read expected file `" + target_file + "`")
  424. env.exitcode(99)
  425. end