main.pony 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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 Program
  71. """
  72. All the logic we need for running programs
  73. """
  74. // an array of memory cells
  75. var storage: Array[I64]
  76. // the current instruction we're running
  77. var pc: USize = 0
  78. // a list of errors that may or may not have happened. (right now,
  79. // we'll only ever have one, but it's worth planning ahead, eh?)
  80. var errors: Array[Error val] = Array[Error val]()
  81. // the queue of input values from the user
  82. var input: Array[I64]
  83. // the queue of lines we intend to output
  84. var output: Array[I64] = Array[I64]()
  85. // a queue of debug messages
  86. var debug: Array[String] = Array[String]()
  87. new create(s: Array[I64], i: Array[I64] = Array[I64]()) =>
  88. """
  89. Create a Program from a verbatim storage and input array
  90. """
  91. storage = s
  92. input = i
  93. new from_file(file: File, i: Array[I64] = Array[I64]()) =>
  94. """
  95. Create a Program by walking input from a file. This just skips any
  96. errors if they happen (e.g. if there are non-numbers), which
  97. might not be what we want!
  98. """
  99. storage = Array[I64](0)
  100. for line in file.lines() do
  101. for chunk in line.split(",").values() do
  102. try
  103. storage.push(chunk.i64()?)
  104. end
  105. end
  106. end
  107. input = i
  108. fun print(env: Env) =>
  109. """
  110. Print out the current state of memory
  111. """
  112. env.out.write("[")
  113. for x in storage.values() do
  114. env.out.write(Format.int[I64](x))
  115. env.out.write(" ")
  116. end
  117. env.out.print("]")
  118. fun ref dbg(msg: String) =>
  119. """
  120. Log a debug message
  121. """
  122. debug.push(msg)
  123. fun ref read_storage(addr: USize): I64 ? =>
  124. """
  125. Attempt to read a word from storage, logging an error message
  126. if we fail to do so
  127. """
  128. try
  129. storage(addr)?
  130. else
  131. errors.push(OutOfBounds(addr))
  132. error
  133. end
  134. fun ref write_storage(addr: USize, value: I64) ? =>
  135. """
  136. Attempt to write a word to storage, logging an error message
  137. if we fail to do so
  138. """
  139. try
  140. storage(addr)? = value
  141. else
  142. errors.push(OutOfBounds(addr))
  143. error
  144. end
  145. fun ref get_immediate(addr_loc: USize): Immediate ? =>
  146. """
  147. Read a single immediate value at the provided loc
  148. """
  149. let value = read_storage(addr_loc)?
  150. Immediate(value)
  151. fun ref get_position(addr_loc: USize): Position ? =>
  152. """
  153. Read a positional value whose address is stored at the provided loc
  154. """
  155. let addr = read_storage(addr_loc)?
  156. let value = read_storage(addr.usize())?
  157. Position(addr, value)
  158. fun ref get_value(addr_loc: USize, mode: Bool): Value ? =>
  159. """
  160. Read a value at the provided loc based on the mode: if true, then
  161. treat it as an immediate, otherwise treat it as a positional
  162. """
  163. if mode then
  164. get_immediate(addr_loc)?
  165. else
  166. get_position(addr_loc)?
  167. end
  168. fun ref get_op_data(modes: Modes): Operands ? =>
  169. """
  170. Get the data that a binary op needs based on the provided modes
  171. """
  172. (let lm, let rm, _) = modes
  173. Operands(
  174. get_value(pc+1, lm)?,
  175. get_value(pc+2, rm)?,
  176. read_storage(pc+3)?
  177. )
  178. fun ref parse_op(n: I64): (Opcode, Modes) =>
  179. """
  180. Parse out an opcode and set of modes. This hard-codes modes
  181. for three operands; it might be worth it in the future to generalize
  182. to any N operands
  183. """
  184. let opcode = n % 100
  185. let mode1 = (n / 100) % 10
  186. let mode2 = (n / 1000) % 10
  187. let mode3 = (n / 10000) % 10
  188. (opcode, (mode1 == 1, mode2 == 1, mode3 == 1))
  189. fun ref do_binop(modes: Modes, op: {(I64, I64): I64}) ? =>
  190. """
  191. Get the values of the first two operands by the provided modes, run
  192. the function `op` on them, and store the result of that at the
  193. provided location
  194. """
  195. let data = get_op_data(modes)?
  196. write_storage(data.tgt(), op(data.val1(), data.val2()))?
  197. pc = pc + 4
  198. fun ref do_jump(modes: Modes, jump_cond: {(I64): Bool}) ? =>
  199. """
  200. Get the value indicated by the modes at [pc+1], and the function
  201. `jump_cond` returns true on that value, then jump to the location
  202. indicated by [pc+2], otherwise move ahead as usual
  203. """
  204. (let cond, let tgt, _) = modes
  205. if jump_cond(get_value(pc+1, cond)?.value()) then
  206. pc = get_value(pc+2, tgt)?.value().usize()
  207. else
  208. pc = pc + 3
  209. end
  210. fun ref do_input(modes: Modes) ? =>
  211. """
  212. Read a value from the input and put it into storage. This logs an
  213. error if there are not enough values in the input queue.
  214. """
  215. let i = try
  216. input.shift()?
  217. else
  218. errors.push(NotEnoughInput(pc))
  219. error
  220. end
  221. write_storage(read_storage(pc+1)?.usize(), i)?
  222. pc = pc + 2
  223. fun ref do_output(modes: Modes) ? =>
  224. """
  225. Write the value indicated according to the mode to the output queue.
  226. """
  227. (let m, _, _) = modes
  228. output.push(get_value(pc+1, m)?.value())
  229. pc = pc + 2
  230. fun ref run_loop() ? =>
  231. """
  232. This continues running the VM until it encounters a halt instruction
  233. or it encounters some other error
  234. """
  235. while true do
  236. (let opcode, let modes) = parse_op(read_storage(pc)?)
  237. dbg("running op " + Format.int[I64](opcode))
  238. match opcode
  239. | 1 => do_binop(modes, {(x, y) => x + y})?
  240. | 2 => do_binop(modes, {(x, y) => x * y})?
  241. | 3 => do_input(modes)?
  242. | 4 => do_output(modes)?
  243. | 5 => do_jump(modes, {(x) => x != 0})?
  244. | 6 => do_jump(modes, {(x) => x == 0})?
  245. | 7 => do_binop(modes, {(x, y) => if x < y then 1 else 0 end})?
  246. | 8 => do_binop(modes, {(x, y) => if x == y then 1 else 0 end})?
  247. | 99 => return None
  248. | let x: I64 =>
  249. errors.push(UnknownOperand(x))
  250. error
  251. end
  252. end
  253. fun ref run(env: Env): Array[I64] =>
  254. """
  255. This runs the VM and prints output: if an error occurs, it prints
  256. the error message(s) and any debug messages that happened during
  257. program execution, and then it prints everything printed via an
  258. output instruction.
  259. """
  260. try
  261. run_loop()?
  262. else
  263. for err in errors.values() do
  264. env.out.print(err.message())
  265. end
  266. for n in debug.values() do
  267. env.out.print("[debug] " + n)
  268. end
  269. end
  270. for n in output.values() do
  271. env.out.print("> " + Format.int[I64](n))
  272. end
  273. output
  274. fun ref clone(): Program =>
  275. Program(storage, input)
  276. fun ref set_input(i: Array[I64]): Program =>
  277. input = i
  278. this
  279. actor Main
  280. new create(env: Env) =>
  281. let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
  282. let target_file = try env.args(1)? else "input.txt" end
  283. try
  284. with
  285. file = OpenFile(FilePath(env.root as AmbientAuth, target_file, caps)?) as File
  286. do
  287. let program = Program.from_file(file, [0;1])
  288. var max: (I64 | None) = None
  289. var choice = Array[I64]()
  290. for p in permutations([0;1;2;3;4]).values() do
  291. env.out.print("\ntesting")
  292. let res = test_phases(env, program, p)
  293. if try (res as I64) > (max as I64) else true end then
  294. max = res
  295. choice = p
  296. end
  297. end
  298. env.out.print("maximum is " + max.string())
  299. env.out.write("permutation was")
  300. for i in choice.values() do
  301. env.out.write(" " + i.string())
  302. end
  303. env.out.print("")
  304. end
  305. else
  306. // if something failed, then print an error message of some kind and exit
  307. env.err.print("Couldn't read expected file `input.txt'")
  308. env.exitcode(99)
  309. end
  310. fun test_phases(env: Env, program: Program, phases: Array[I64]): (I64 | None) =>
  311. var input: I64 = 0
  312. for i in phases.values() do
  313. let output = program.clone().set_input([i; input]).run(env)
  314. input = try output(0)? else
  315. env.out.print("Not enough output returned")
  316. return None
  317. end
  318. None
  319. end
  320. input
  321. fun permutations(array: Array[I64]): Array[Array[I64]] =>
  322. if array.size() == 0 then
  323. [[]]
  324. else
  325. let res = Array[Array[I64]]()
  326. for a in array.values() do
  327. let sm = Array[I64]()
  328. Iter[I64](array.values()).filter({(x: I64) => x != a}).collect(sm)
  329. let ps = permutations(sm)
  330. for p in ps.values() do
  331. p.unshift(a)
  332. res.push(p)
  333. end
  334. end
  335. res
  336. end