main.pony 14 KB

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