2 _ This file extends barem4.txt, which demonstrates how the m4 langauge
3 _ is Turing complete with JUST the define builtin. (It also uses the
4 _ `dnl' builtin, spelled as `_' for legibility, but the concept still
5 _ works even if you strip all comments out.) This is an implementation
6 _ of the core engine for an Intcode virtual machine, from Advent of
7 _ Code 2019 https://adventofcode.com/2019 (see days 2, 5, 7, and 9
8 _ for the machine definition, and then odd days 11-25 for more uses).
9 _ In general, you will still need to write some more code in barem4
10 _ in order to manage execution of this engine, since some of the Advent
11 _ of Code puzzles require repeated executions.
13 _ barem4.txt was written by Douglas McIlroy, and can be found at
14 _ www.cs.dartmouth.edu/~doug/barem4.txt
16 _ I'm tempted to redefine the various switch macros to properly quote
17 _ arguments, by using DOL(@). But for now, premature quote stripping
18 _ doesn't break anything in this file.
20 _ Although this file implements the intcode engine, you still need to
21 _ load your program and input separately. The initial program is loaded
22 _ by defining macros M_<base2> where the machine will start executing at
23 _ instruction M_0. The input instruction, opcode 3, defaults to consuming
24 _ integers defined in the I<base2> namespace, although this can be changed
25 _ by redefining `input'. Note that I've also written encode.m4, which
26 _ can be used to convert an input file containing an intcode program
27 _ consisting of a comma-separated list of decimal numbers into a series
28 _ of macro definitions, for easier loading of a program for this engine:
30 _ m4 -Dfile=dayN.input encode.m4 | m4 barem4.txt - intcode.barem4 dayN.barem4
32 _ For some slight optimizations, inline several constants rather than
33 _ repeating use of multiple succ() each time they are reused. And add a
34 _ couple more common constants.
36 define(`five', succ(four))_
37 define(`six', succ(five))_
38 define(`seven', succ(six))_
39 define(`eight', succ(seven))_
40 define(`nine', succ(eight))_
41 define(`ten', succ(nine))_
42 define(`sixteen', square(four))_
43 define(`thirty_two', mul(four, eight))_
44 define(`sixty_four', square(eight))_
45 define(`hundred', square(ten))_
47 _ There are several places where it is necessary to compute a macro name
48 _ to then invoke, such as when combining a namespace with an index. While
49 _ `if_T' and `_head' do the same thing (at least, as long as `if' is not
50 _ coded with switch), it's nicer to have a name that expresses intent.
52 define(`expand', `$1')_
54 _ Define one more logic operator, xor.
56 define(`xor', `if(`$1', `not(`$2')', `$2')')_
58 _ Extend the logic operators into bitwise versions. The bitwise operators
59 _ only operate on positive numbers, and the result extends to the larger
60 _ of the two inputs, which may be non-canonical until using trim. This is
61 _ another situation where it is easier to use a second-level defining setup.
62 _ This even works for unary not into bnot, since m4 ignores excess args.
63 _ The `l2b' helper converts logical values into bits.
66 _ band((0,(1,(0,(1,(1,()))))), (0,(0,(1,(1,()))))) ==> (0,(0,(0,(1,(0,())))))
67 _ bor((0,(1,(0,(1,())))), (0,(0,(1,(1,(0,())))))) ==> (0,(1,(1,(1,(0,())))))
68 _ bxor((0,(1,(0,(1,())))), (0,(0,(1,(1,()))))) ==> (0,(1,(1,(0,()))))
69 _ bnot((0,(1,(0,(1,()))))) ==> (1,(0,(1,(0,()))))
71 define(`l2b', `(l2b_$1,$2)')_
76 __`define(`b$1', `_b$1(_split$'`1, _split$'`2)')'_
77 __`define(`_b$1', `b$1_$'`1_$'`3($'`2, $'`4)')'_
78 __`case(`b$1__', `()')'_
79 __`case(`b$1__0', `l2b($1(`F', `F'),b$1((), $'`2))')'_
80 __`case(`b$1__1', `l2b($1(`F', `T'),b$1((), $'`2))')'_
81 __`case(`b$1_0_', `l2b($1(`F', `F'),b$1($'`1, ()))')'_
82 __`case(`b$1_0_0', `l2b($1(`F', `F'),b$1($'`1, $'`2))')'_
83 __`case(`b$1_0_1', `l2b($1(`F', `T'),b$1($'`1, $'`2))')'_
84 __`case(`b$1_1_', `l2b($1(`T', `F'),b$1($'`1, ()))')'_
85 __`case(`b$1_1_0', `l2b($1(`T', `F'),b$1($'`1, $'`2))')'_
86 __`case(`b$1_1_1', `l2b($1(`T', `T'),b$1($'`1, $'`2))')')_
93 _ Extend the arithmetic operations to handle negative numbers. This is
94 _ done by using a sign-magnitude form: positive numbers remain as-is, and
95 _ the operations now understand a list with a leading N as a negative.
97 define(`neg_one', `(N,(1,()))')_
99 _ The sign of a number is N, 0, or 1 for negative, zero, positive.
101 define(`sign', `if(iszero($1), `0',
102 `if(eq(head($1),N), N, 1)')')_
104 _ Negating a number is easy.
106 switch1of1(`neg', `sign($1)')_
108 case(`neg_1', `(N,$1)')_
109 case(`neg_N', `tail($1)')_
111 _ Same with absolute value.
113 switch1of1(`abs', `sign($1)')_
116 case(`abs_N', `tail($1)')_
118 _ Sign-magnitude with arbitrary-width integers is the preferred mode of
119 _ computation, but a typical task in modern computing is to use a fixed-width
120 _ word with sign extension and twos-complement inversion, with common
121 _ widths of 8, 16, 32, and 64, already defined as useful constants.
123 _ The function `invert(<value>)' preserves sign, but computes the
124 _ twos-complement magnitude of <value> occupying the same number of bits.
127 _ invert(zero) ==> ()
128 _ invert((0,(0,()))) ==> (0,(0,()))
129 _ invert((1,(0,(0,())))) ==> (1,(1,(1,())))
130 _ invert((1,(1,(1,())))) ==> (1,(0,(0,())))
131 _ invert((N,(1,(0,(1,(0,())))))) ==> (N,(1,(1,(0,(1,())))))
133 switch1of1(`invert', `sign($1)')_
134 case(`invert_0', `$1')_
135 case(`invert_1', `succ(bnot($1))')_
136 case(`invert_N', `(N,succ(bnot(tail($1))))')_
138 _ The function `extend(<value>, <width>)' returns a non-canonical positive
139 _ number with <width> bits, as follows. If <value> was already non-negative,
140 _ but shorter than <width>, then insignificant zeros are inserted. If <value>
141 _ was positive but larger than <width>, bits beyond <width> are discarded.
142 _ If <value> was negative, then the twos complement inversion is performed.
143 _ The helper `_extend' iterates until the counter reaches zero.
146 _ extend(one, eight) ==> (1,(0,(0,(0,(0,(0,(0,(0,()))))))))
147 _ extend(five, two) ==> (1,(0,()))
148 _ extend(neg_one, four)
149 _ ==> invert(_extend(tail((N,(1,()))),(0,(0,(1,())))))
150 _ ==> invert((1,(0,(0,(0,())))))
151 _ ==> succ(bnot((1,(0,(0,(0,()))))))
152 _ ==> succ((0,(1,(1,(1,())))))
153 _ ==> (1,(1,(1,(1,()))))
155 define(`extend', `if(eq(head($1),N), `invert(_extend(tail($1), $2))',
156 `_extend($1, $2)')')_
157 define(`_extend', `if(iszero($2), `()',
158 `l2b(eq(head($1),1),_extend(tail($1), _dim($2,'one`)))')')_
160 _ Create a true subtraction. While dim requires positive inputs and clamps
161 _ at zero, sub can handle negative inputs as well as output. Note that
162 _ the result may not be canonical.
164 switch2of2(`sub', `sign($1)', `sign($2)')_
165 case(`sub_0_0', `()')_
166 case(`sub_0_1', `(N,$2)')_
167 case(`sub_0_N', `tail($2)')_
168 case(`sub_1_0', `$1')_
169 case(`sub_1_1', `_sub($1, $2)')_
170 case(`sub_1_N', `add($1, tail($2))')_
171 case(`sub_N_0', `$1')_
172 case(`sub_N_1', `(N,add(tail($1), $2))')_
173 case(`sub_N_N', `_sub(tail($2), tail($1))')_
175 define(`_sub', `if(lt($1, $2), `(N,_dim($2, $1))', `_dim($1, $2)')')_
177 _ The predecessor and decr functions are also useful shorthands comparable to
178 _ succ and incr, although on loop bounds that can have non-significant zeroes,
179 _ it is faster still to directly count down via _dim($1,'one`).
181 define(`pred', `sub($1, 'one`)')_
182 define(`decr', `define(`$1', pred($1))')_
184 _ Extend other existing operators to handle negative inputs. Functions
185 _ defined by switch are easy: add new cases to the existing call.
186 _ This extended `succ' continues to produce canonical values, although it
187 _ means it is slower on negatives than on positives.
189 define(`succ', `_succ(_split$1)')_
190 case(`_succ_1', `(0,_succ(_split$2))')_
191 case(`succ_N', `if(et($1, 'neg_one`), `()',
192 `(N,trim(_dim(tail($1),''one``)))')')_
194 case(`add__N', `$2')_
195 case(`add_0_N', `trim(_sub($1, tail($2)))')_
196 case(`add_1_N', `trim(_sub($1, tail($2)))')_
197 case(`add_N_', `$1')_
198 case(`add_N_0', `trim(_sub($2, tail($1)))')_
199 case(`add_N_1', `trim(_sub($2, tail($1)))')_
200 case(`add_N_N', `(N,add(tail($1), tail($2)))')_
202 case(`compare__N', `if(iszero($2), `EQ', `GT')')_
203 case(`compare_0_N', `if(and(iszero($1), iszero($2)), `EQ', `GT')')_
204 case(`compare_1_N', `GT')_
205 case(`compare_N_', `if(iszero($1), `EQ', `LT')')_
206 case(`compare_N_0', `if(and(iszero($1), iszero($2)), `EQ', `LT')')_
207 case(`compare_N_1', `LT')_
208 case(`compare_N_N', `compare(tail($2), tail($1))')_
210 _ That said, `succ', `add', `_dim', and `compare' are used frequently, and can
211 _ be made more efficient by rewriting with split; which requires rewriting
212 _ the bulk of the cases without tail. Now, for `succ' and `add', when a
213 _ negative value is involved, the result might not be canonical. (These
214 _ rewrites for speed could be commented out if the earlier canonical behavior
217 case(`succ_N', `if(et($1,'one`), 'zero`, `(N,_dim($1,''one``))')')_
219 define(`add', `_add(_split$1, _split$2)')_
220 define(`_add', `add_$1_$3($2, $4)')_
221 case(`add__0', `(0,$2)')_
222 case(`add__1', `(1,$2)')_
223 case(`add__N', `(N,$2)')_
224 case(`add_0_', `(0,$1)')_
225 case(`add_0_0', `(0,add($1,$2))')_
226 case(`add_0_1', `(1,add($1,$2))')_
227 case(`add_0_N', `_sub((0,$1),$2)')_
228 case(`add_1_', `(1,$1)')_
229 case(`add_1_0', `(1,add($1,$2))')_
230 case(`add_1_1', `(0,succ(add($1,$2)))')_
231 case(`add_1_N', `_sub((1,$1),$2)')_
232 case(`add_N_', `(N,$1)')_
233 case(`add_N_0', `_sub((0,$2), $1)')_
234 case(`add_N_1', `_sub((1,$2), $1)')_
235 case(`add_N_N', `(N,add($1,$2))')_
237 define(`_dim', `_dim_(_split$1, _split$2)')_
238 define(`_dim_', `dim_$1_$3($2, $4)')_
240 case(`dim__0', zero)_ only possible with non-canonical input
241 _ dim__1 not possible, because dim filtered by le.
242 case(`dim_0_', `(0,$1)')_
243 case(`dim_1_', `(1,$1)')_
244 case(`dim_0_0', `(0,_dim($1, $2))')_
245 case(`dim_1_1', `(0,_dim($1, $2))')_
246 case(`dim_1_0', `(1,_dim($1, $2))')_
247 case(`dim_0_1', `(1,_dim($1, succ($2)))')_
249 define(`compare', `_compare_(_split$1, _split$2)')_
250 define(`_compare_', `compare_$1_$3($2, $4)')_
251 case(`compare_0_0', `compare($1, $2)')_
252 case(`compare_0_1', `_switch1(`_compare', compare($1, $2))(`LT')')_
253 case(`compare_1_0', `_switch1(`_compare', compare($1, $2))(`GT')')_
254 case(`compare_1_1', `compare($1, $2)')_
255 case(`compare_N_N', `compare($2, $1)')_
257 case(`_compare_LT', `LT')_
258 case(`_compare_EQ', `$1')_
259 case(`_compare_GT', `GT')_
261 _ Meanwhile, functions defined by open-coded if require a re-write.
262 _ First up, `isempty' can be rewritten for no ghost defines, by exploiting
263 _ the fact that a recognized macro can inject a comma changing the length
264 _ of a list, and thereby the second element of that list, while an
265 _ unrecognized name "expands" to itself and does not munge the list.
267 define(`isempty', `_tail(expand(`_isempty'_head$1)`T', `F')')_
268 define(`_isempty', `,')_
270 _ Similarly, it's nice to know when a list is present, including when it
273 define(`islist', `_tail(expand(`_islist'$1)`T',`F')')_
274 define(`_islist', `,')_
276 _ iszero is called frequently. In addition to adding support for negatives,
277 _ the switch to split results in less work per recursion.
279 define(`iszero', `_iszero(_split$1)')_
280 define(`_iszero', `iszero_$1($2)')_
281 case(`iszero_', `T')_
282 case(`iszero_0', `iszero($1)')_
283 case(`iszero_1', `F')_
284 case(`iszero_N', `iszero($1)')_
286 switch1of2(`mul', `sign($2)')_
287 case(`mul_N', `neg(_mul($1, tail($2)))')_
289 case(`mul_1', `_mul($1, $2)')_
290 case(`_mul_N', `(N,_mul(tail($1), $2))')_
292 _ Relying on canonical numbers requires lots of `trim' calls; it is often
293 _ better to rework things to not depend on trim, but when it is needed,
294 _ it should be fast. Remember, iszero can stop iterating at the first 1,
295 _ but trim must iterate all the way to the end of the number.
297 define(`trim', `if(iszero($1), `()',
298 `_trim(_split$1)')')_
299 define(`_trim', `($1,trim($2))')_
301 _ Note that base2(<negative>) does not produce a valid macro suffix; intcode
302 _ memory and inputs have non-negative indices. But for debug, displaying
303 _ a negative with a prefix `-' rather than a suffix N is preferable.
304 _ Also, being able to ignore non-significant trailing zeroes while still
305 _ producing 0 for the empty string is important; but checking iszero
306 _ must be avoided to minimize extra scans of the full number, except when
307 _ handling non-canonical negatives. This is done by having the helper
308 _ `_base2(<head>, <tail>, <defer>, <seed>)'. `base2' supplies `0' for <seed>,
309 _ and `base2_' ends recursion and prints <seed> (empty if any other digits
310 _ were seen, otherwise the original number was zero). `base2_0' prints
311 _ nothing, but adds a 0 to <defer> and passes <seed> along. All other numbers
312 _ (in this case `base2_1', but the concept works for `base10' as well)
313 _ print themselves plus the current <defer>, then drop <defer> and <seed>.
316 _ base2(zero) ==> _base2(_split(), `', `0') ==> _base2(`',`', `', `0')
317 _ ==> base2_(`', `', `0') => 0
318 _ base2(one) ==> _base2(1, (), `', `0') ==> base2_1((), `', `0')
319 _ => _base2(_split(), `')1 => base2_(`', `')1 => 1
320 _ base2((0,(1,(0,())))) ==> base2_0((1,(0,())), `', `0')
321 _ ==> _base2(_split(1,(0,())), `0') ==> base2_1((0,()), `0')
322 _ ==> _base2(_split(0,()), `')10 ==> base2_0((), `')10
323 _ ==> _base2(_split(), `0')10 ==> base2_(`', `0')10 => 10
325 define(`base2', `_base2(_split$1, `', `0')')_
326 define(`_base2', `base2_$1($2, $3, $4)')_
327 case(`base2_', `$3')_
328 case(`base2_0', `_base2(_split$1, 0$2, $3)')_
329 case(`base2_1', `_base2(_split$1)1$2')_
330 case(`base2_N', `if(iszero($1), 0, ``-'base2($1)')')_
332 _ At this time, teaching pow, div, mod, or qr to handle negatives is overkill.
333 _ However, the existing qr is inefficient: is uses `mul(two,<exp>)' twice
334 _ instead of `add(<exp>,<exp>)' (which preserves canonical numbers) or
335 _ `(0,<exp>)' (which can create trailing zeroes, but is faster). But since I
336 _ taught `base2' to deal with trailing zeroes, there is no need to spend time
337 _ on `trim' here. And `_qrnorm' calls `dim' which makes a duplicate `lt' call.
338 _ More importantly, `qr' outputs a list rather than two items, which means more
339 _ macro calls to break that list back into components. It is not too hard
340 _ to rewrite things to use a different return convention; where `qr'
341 _ returns `(q,r)', my `remquo' returns `r,q'.
344 `if(lt($1, $2), `$1,'zero,
345 `_remquo(head($1), $2, remquo(tail($1), $2))')')_
346 define(`_remquo', `_norm($2, (0,$4), ($1,$3))')_
347 define(`_norm', `if(lt($3,$1), `$3,$2', `_dim($3,$1),succ($2)')')_
348 define(`div',`_tail(remquo($1, $2))')_
349 define(`mod',`_head(remquo($1, $2))')_
351 _ Since intcode is typically displayed in signed decimal (even though that
352 _ form cannot be directly parsed by barem4), it is worth having a conversion
355 switch1of1(`base10', `sign($1)')_
356 case(`base10_0', `0')_
357 case(`base10_1', `_base10(remquo($1, 'ten`))')_
358 case(`base10_N', ``-'_base10(remquo(tail($1), 'ten`))')_
360 define(`_base10', `if(iszero($2), `',
361 `_base10(remquo($2,'ten`))')_switch1(`dig', base2($1))`'')_
363 _ Display is easier when mapping from values to digits.
365 switch1of1(`dig', `base2($1)')_
370 case(`dig_100', `4')_
371 case(`dig_101', `5')_
372 case(`dig_110', `6')_
373 case(`dig_111', `7')_
374 case(`dig_1000', `8')_
375 case(`dig_1001', `9')_
377 _ The converse direction is also useful. Note that while barem4 numbers
378 _ are written lsb first, this utility function accepts decimal digits with
379 _ the most significant digit first. That is:
381 _ dec2num((1,(2,()))) ==> (0,(0,(1,(1,()))))
383 define(`dec2num', `_dec($1,'zero`)')_
384 switch1of2(`_dec', `head($1)')_
386 define(`mkdec', `case(`_dec_'base10($1),
387 `_dec(tail('DOL(1)`), add(mul('DOL(2)`,''ten``),$1))')')_
399 _ The intcode VM halts on opcode 99. This is thus a useful constant.
401 define(`ninety_nine', dec2num((9,(9,()))))_
403 _ Several Intcode puzzles produce output that is encoded in (a subset of)
404 _ ASCII. As m4 does not natively have any way to map integers to characters,
405 _ I get to open-code a table for this. (GNU m4 comes with format(%c), but
406 _ this being barem4, that's out of the question). Note that without
407 _ changequote, it is impossible to output a backtick; and outputting an
408 _ apostrophe relies on luck (while you can define a macro to expand to
409 _ apostrophe, using it inside other macros tends to prematurely end what was
410 _ supposed to be a longer string). Output of other characters like `(', `,',
411 _ and `)' is possible with sufficient quoting. At any rate, since not
412 _ everything can be output, I've chosen to instead output escape sequences
413 _ for anything difficult, with \\ for backslash, \x60 and \x27 for `', and \xXX
414 _ for anything else less than space that is not newline or tab. NUL, and
415 _ any negative values or values > 127, fail the `isasc' predicate (at least
416 _ one Intcode puzzle has a solution as the first non-ASCII output). This is
417 _ made easier with a base16() function; which can be implemented with repeated
418 _ tail instead of remquo. Since this will be used for \x01-\x1f, this function
419 _ does not eliminate trailing zeroes that might have been placed by
420 _ extend(<value>, eight); but true 0 is always compacted to `0'.
422 switch1of1(`base16', `sign($1)')_
423 case(`base16_0', `0')_
424 case(`base16_1', `_base16d_1(_split$1)')_
425 case(`base16_N', ``-'base16_1(tail($1))')_
427 define(`_base16d_1', `_base16d_2($1, _split$2)')_
428 define(`_base16d_2', `_base16d_3($1, $2, _split$3)')_
429 define(`_base16d_3', `_base16d_4($1, $2, $3, _split$4)')_
430 define(`_base16d_4', `if(isempty($5), `', `base16_1($5)')dig_$4$3$2$1`'')_
432 _ Some additional digits. This includes cases where a non-trimmed value
433 _ results from how base16d splits lists into digits.
435 case(`dig_0000', `0')_
436 case(`dig_000', `0')_
438 case(`dig_0001', `1')_
439 case(`dig_001', `1')_
441 case(`dig_0010', `2')_
442 case(`dig_010', `2')_
443 case(`dig_0011', `3')_
444 case(`dig_011', `3')_
445 case(`dig_0100', `4')_
446 case(`dig_0101', `5')_
447 case(`dig_0110', `6')_
448 case(`dig_0111', `7')_
449 case(`dig_1010', ``a'')_
450 case(`dig_1011', ``b'')_
451 case(`dig_1100', ``c'')_
452 case(`dig_1101', ``d'')_
453 case(`dig_1110', ``e'')_
454 case(`dig_1111', ``f'')_
456 _ Now for the ASCII table. Do a first pass that assigns escape sequences for
457 _ everything, then a second pass of specific characters. Use of `pred' for
458 _ looping preserves non-significant zeroes. Define both `asc_1' and `asc_01'
459 _ for convenience, through varied `mkasc' definitions. `safeasc' is suitable
460 _ as a formatter for use with `dump' if NUL or large values might be mixed in.
464 define(`isasc', `and(gt($1, 'zero`), lt($1, 'mul(eight,sixteen)`))')_
465 define(`asc', `_switch1(`asc', base16($1))`'')_
466 define(`safeasc', `if(isasc($1), `asc($1)', `\{base10($1)}')')_
468 define(`mkasc', `if(iszero($1), `', `define(`asc_'base16($1),
469 `\x0'base16($1))mkasc(pred($1))')')_
470 mkasc(mul(three, five))_
471 define(`mkasc', `if(iszero($1), `', `define(`asc_'base16($1),
472 `\x'base16($1))mkasc(pred($1))')')_
473 mkasc(pred(mul(eight, sixteen)))_
474 define(`mkasc', `define(`asc_$1', ``$2'')')_
475 mkasc(`9', ` ')mkasc(`09', ` ')mkasc(`a', nl)mkasc(`0a', nl)_
476 mkasc(`20', ` ')mkasc(`21', `!')mkasc(`22', `"')mkasc(`23', `#')_
477 mkasc(`24', `$')mkasc(`25', `%')mkasc(`26', `&')mkasc(`28', `(')_
478 mkasc(`29', `)')mkasc(`2a', `*')mkasc(`2b', `+')mkasc(`2c', `,')_
479 mkasc(`2d', `-')mkasc(`2e', `.')mkasc(`2f', `/')mkasc(`30', `0')_
480 mkasc(`31', `1')mkasc(`32', `2')mkasc(`33', `3')mkasc(`34', `4')_
481 mkasc(`35', `5')mkasc(`36', `6')mkasc(`37', `7')mkasc(`38', `8')_
482 mkasc(`39', `9')mkasc(`3a', `:')mkasc(`3b', `;')mkasc(`3c', `<')_
483 mkasc(`3d', `=')mkasc(`3e', `>')mkasc(`3f', `?')mkasc(`40', `@')_
484 mkasc(`41', `A')mkasc(`42', `B')mkasc(`43', `C')mkasc(`44', `D')_
485 mkasc(`45', `E')mkasc(`46', `F')mkasc(`47', `G')mkasc(`48', `H')_
486 mkasc(`49', `I')mkasc(`4a', `J')mkasc(`4b', `K')mkasc(`4c', `L')_
487 mkasc(`4d', `M')mkasc(`4e', `N')mkasc(`4f', `O')mkasc(`50', `P')_
488 mkasc(`51', `Q')mkasc(`52', `R')mkasc(`53', `S')mkasc(`54', `T')_
489 mkasc(`55', `U')mkasc(`56', `V')mkasc(`57', `W')mkasc(`58', `X')_
490 mkasc(`59', `Y')mkasc(`5a', `Z')mkasc(`5b', `[')mkasc(`5c', `\\')_
491 mkasc(`5d', `]')mkasc(`5e', `^')mkasc(`5f', `_')_
492 mkasc(`61', `a')mkasc(`62', `b')mkasc(`63', `c')mkasc(`64', `d')_
493 mkasc(`65', `e')mkasc(`66', `f')mkasc(`67', `g')mkasc(`68', `h')_
494 mkasc(`69', `i')mkasc(`6a', `j')mkasc(`6b', `k')mkasc(`6c', `l')_
495 mkasc(`6d', `m')mkasc(`6e', `n')mkasc(`6f', `o')mkasc(`70', `p')_
496 mkasc(`71', `q')mkasc(`72', `r')mkasc(`73', `s')mkasc(`74', `t')_
497 mkasc(`75', `u')mkasc(`76', `v')mkasc(`77', `w')mkasc(`78', `x')_
498 mkasc(`79', `y')mkasc(`7a', `z')mkasc(`7b', `{')mkasc(`7c', `|')_
499 mkasc(`7d', `}')mkasc(`7e', `~')_
501 _ A VM has three status bits, `halt<ns>_' (opcode 99 has been executed),
502 _ `pause<ns>_' (temporary pause related to op 3 and 4 doing I/O), and
503 _ `fatal<ns>_' (program was invalid, unable to continue), each defaulting
504 _ to F. `run' stops looping if any is set to T, but each call to run
505 _ clears `pause<ns>'. For convenience, `halt(<ns>)' sets the halt bit,
506 _ ishalt(<ns>) queries it, and so on (this lets me experiment with other
507 _ representations, such as an integer bimask instead of separate bits).
509 define(`halt', `define(`halt$1_', T)')_
510 define(`ishalt', `halt$1_()')_
511 define(`pause', `define(`pause$1_', T)')_
512 define(`ispause', `pause$1_()')_
513 define(`fatal', `define(`fatal$1_', T)')_
514 define(`isfatal', `fatal$1_()')_
516 _ A common pattern in the engine is accessing an index from an offset in a
517 _ namespace; common namespaces are `M' for memory, `I' for inputs, `O' for
518 _ outputs, and `_OP' for operations. For safety, `fetch' expands to 99 if
519 _ the index is negative, or to 0 if the index is undefined in the namespace.
520 _ This task is easier with a helper predicate `isundef(<namespace>, <index>)'
521 _ that determines if a potential macro name produces a list with an empty
522 _ third element (true for all barem4 integers), while anything else is
523 _ nonempty. Similar to isempty above, abuse the knowledge that any
524 _ defined memory location will have a head of `', `0', `1', or `N' which
525 _ will produce a comma to shift arguments; an undefined macro will not
526 _ expand. In fact, isundef even reports T for any negative <value>.
528 define(`isundef', `_tail(_switch1(`_isundef',
529 head(_switch1(`$1', base2($2))))`F', `T')')_
530 define(`_isundef_', `,')_
531 define(`_isundef_0', `,')_
532 define(`_isundef_1', `,')_
533 define(`_isundef_N', `,')_
535 _ Fetch an argument from a namespaced array, or a safe alternative if the
536 _ offset is not yet defined. It is actually more efficient to evaluate the
537 _ assumed macro name first, and then massage the results on failure, than to
538 _ run base2() multiple times for both isundef and the actual fetch.
540 define(`fetch', `_switch1(`fetch', _head$2)(`$1', base2($2))')_
541 case(`fetch_N', `ninety_nine')_
542 case(`fetch_', `_fetch($1_$2)')_
543 case(`fetch_0', `_fetch($1_$2)')_
544 case(`fetch_1', `_fetch($1_$2)')_
545 define(`_fetch', `_tail(expand(`_islist'$1)`$1','zero`)')_
547 _ In an instruction, the source for an argument depends on the mode.
548 _ `get(<mode>, <value>, <ns>)' can read from M<ns>_<value>, use <value> as-is,
549 _ or read from M<ns>_<add(<value>,rel<ns>)>. The sink for an argument,
550 _ `put(<mode>, <addr>, <value>, <ns>)' likewise depends on the <mode>, although
551 _ an immediate cannot be a sink. The <ns> argument is optional; most
552 _ days only run one machine at once. But days 7 and 23 are easiest to
553 _ solve if they run parallel copies of the VM, each with a consistent
554 _ <ns> suffix used in `M', `I', `O', `pc', `halt', `pause', `fatal', and
555 _ `rel'. `put' does not check that <addr> is non-negative. For large programs,
556 _ it can be more efficient to have `get' try M<ns>_<value> and fall back to
557 _ M_<value> before defaulting to 0, than to have to pre-copy all of `M'
558 _ over to M<ns> up front.
560 define(`get', `get_$1($2, `$3')')_
561 case(`get_0', `_get(base2($1), `$2')')_
563 case(`get_2', `_get(base2(add($1, rel$2)), `$2')')_
564 define(`_get', `_get_($1, M$2_$1)')_
565 define(`_get_', `_tail(expand(`_islist'$2)`$2', `_fetch(M_$1)')')_
567 define(`put', `put_$1($2, $3, `$4')')_
568 case(`put_0', `define(`M$3_'base2($1), `$2')')_
569 case(`put_1', `fatal(`$3')')_
570 case(`put_2', `define(`M$3_'base2(add($1, rel$3)), `$2')')_
572 _ An intcode instruction can consist of up to four fields. In decimal
573 _ representation, the value 12001 decodes to op 01 (add), using mode
574 _ 0 for its first operand (absolute address), mode 1 for its second
575 _ operand (literal), and mode 2 for its third (relative address).
576 _ Performing division at runtime is expensive; better is to create a
577 _ lookup table of all 27 variants of each opcode, to eventually call
578 _ into OP<n>(<mode1>, <mode2>, <mode3>, <ns>). Even though instructions are
579 _ variable width (based on how many operands they consume), it does not hurt
580 _ to pass unused modes to an operand. Although a well-formed intcode
581 _ instruction will never invoke an invalid command, this engine sets the
582 _ fatal flag when it detects such attempts, done by by defining `_OP<n>'
583 _ with the decode of known combos, and undefined otherwise.
585 define(`decode', `_switch1(`_OP', base2($1))')_
587 _ Define the actual operations, with the helper `mkop'. Each
588 _ OP<n>(<mode1>, <mode2>, <mode3>, <ns>) must output the new value of pc<ns>,
589 _ in addition to any other side effects it may have. Defining a macro
590 _ requires defining each of its 27 _OP variants, for decoding.
592 define(`mkop', `_mkop($1, base2($1), `$2')')_
593 define(`_mkop', `_mkop1($1, `$2,0')_mkop1(add('hundred`, $1),
594 `$2,1')_mkop1(add('(0,hundred)`, $1), `$2,2')define(`OP$2', `$3')')_
595 define(`_mkop1', `_mkop2($1, `$2,0')_mkop2(add('mul(ten,hundred)`, $1),
596 `$2,1')_mkop2(add('(0,mul(ten,hundred))`, $1), `$2,2')')_
597 define(`_mkop2', `_mkop3($1, `$2,0')_mkop3(add('square(hundred)`, $1),
598 `$2,1')_mkop3(add('(0,square(hundred))`, $1), `$2,2')')_
599 define(`_mkop3', `define(`_OP_'base2($1), `$2')')_
601 _ OP 1: Add ARG1 ARG2 DST
602 _ Store ARG1 + ARG2 into DST
604 mkop(one, `put_$3(get_0(add(pc$4, 'three`), `$4'),
605 add(get_$1(get_0(succ(pc$4), `$4'), `$4'),
606 get_$2(get_0(succ(succ(pc$4)), `$4'), `$4')), `$4')add(pc$4, 'four`)')_
608 _ OP 2: Mul ARG1 ARG2 DST
609 _ Store ARG1 * ARG2 into DST
611 mkop(two, `put_$3(get_0(add(pc$4, 'three`), `$4'),
612 mul(get_$1(get_0(succ(pc$4), `$4'), `$4'),
613 get_$2(get_0(succ(succ(pc$4)), `$4'), `$4')), `$4')add(pc$4, 'four`)')_
616 _ Store input into DST. This command relies on `read(<ns>)' macro returning up
617 _ to two values <good,data>. If good is T, data is stored and execution
618 _ continues by incrementing pc<ns>. If good is F, value is ignored, execution
619 _ is paused, and pc<ns> left unchanged so more input can be supplied before the
620 _ next `run' to resume. The default `read' macro consumes data from the
621 _ `I<ns>' namespace at offset `i<ns>off', returning F when it hits the end of
622 _ the array; but wrappers can redefine it to take on other input effects.
624 mkop(three, `_input($1, `$4', read(`$4'))')_
625 define(`_input', `if($3,
626 `put_$1(get_0(succ(pc$2), `$4'), $4, `$2')succ(succ(pc$2))',
627 `pause(`$2')pc$2')')_
629 define(`read', `_read($1, _switch1(`I$1', base2(i$1off)))')_
630 define(`_read', `if(islist($2), `T,$2incr(`i$1off')', `F')')_
633 _ Use the value at SRC as output. This command relies on the `write(<val>,
634 _ <ns>)' macro doing something useful, returning T to continue or F to pause
635 _ execution; either way, the next run resumes on the next instruction. The
636 _ default implementation stores val into the next slot of the `O<ns>'
637 _ namespace, using `o<ns>off', and returns T; but wrappers can redefine it to
638 _ take on other output effects (such as copying data between parallel
639 _ machines, printing a base10 representation to stdout, or even mapping
640 _ numbers into corresponding ASCII output to stdout). Since `write' is
641 _ called as the condition to an `if', the caller cannot use it to output
642 _ any data to the screen. If that is desired, define a one-shot `hook' to
643 _ do whatever is desired after the op finishes. `hook' auto-resets to empty.
645 mkop(four, `if(write(get_$1(get_0(succ(pc$4), `$4'), `$4'), `$4'),
646 `', `pause(`$4')')succ(succ(pc$4))')_
648 define(`write', `define(`O$2_'base2(o$2off), trim($1))incr(`o$2off')T')_
651 _ OP 5: Jump-If-True SRC DST
652 _ If SRC is non-zero, jump to DST instead of continuing.
654 mkop(five, `if(iszero(get_$1(get_0(succ(pc$4), `$4'), `$4')),
655 `add(pc$4, three)', `get_$2(get_0(succ(succ(pc$4)), `$4'), `$4')')')_
657 _ OP 6: Jump-If-False SRC DST
658 _ If SRC is zero, jump to DST instead of continuing.
660 mkop(six, `if(not(iszero(get_$1(get_0(succ(pc$4), `$4'), `$4'))),
661 `add(pc$4, three)', `get_$2(get_0(succ(succ(pc$4)), `$4'), `$4')')')_
663 _ OP 7: Less-Than ARG1 ARG2 DST
664 _ Store ARG1 < ARG2 into DST
666 mkop(seven, `put_$3(get_0(add(pc$4, 'three`), `$4'),
667 if(lt(get_$1(get_0(succ(pc$4), `$4'), `$4'),
668 get_$2(get_0(succ(succ(pc$4)), `$4'), `$4')), 'one`, 'zero`),
669 `$4')add(pc$4, 'four`)')_
671 _ OP 8: Equals ARG1 ARG2 DST
672 _ Store ARG1 + ARG2 into DST
674 mkop(eight, `put_$3(get_0(add(pc$4, 'three`), `$4'),
675 if(et(get_$1(get_0(succ(pc$4), `$4'), `$4'),
676 get_$2(get_0(succ(succ(pc$4)), `$4'), `$4')), 'one`, 'zero`),
677 `$4')add(pc$4, 'four`)')_
679 _ OP 9: RelAdjust ARG
680 _ Adjust `rel' by ARG, affecting all mode-2 loads and stores.
682 mkop(nine, `define(`rel$4', add(rel$4, get_$1(get_0(succ(pc$4), `$4'),
683 `$4')))succ(succ(pc$4))')_
686 mkop(ninety_nine, `halt(`$4')pc$4')_
688 _ Time for some meta-programming. `curry(`<name>', `<args>...')(<extra>)'
689 _ results in a call to <name>(<args>, <extra>). If <args> is to supply more
690 _ than one argument, at least the commas must be quoted; the quoting will
691 _ be stripped during the final call to <name>. This implementation allows up
692 _ to three <extra> parameters (more would be possible by using DOL(@), but
693 _ since barem4 lacks shift, I'm avoiding that). The main use case for
694 _ currying is to emulate partial application: a function that requires more
695 _ than one argument (such as `isundef'), but where the early arguments can
696 _ be fixed, can be called in a context that normally only provides one
697 _ argument (such as `map'), letting me implement `copy' on top of `map'.
699 define(`curry', `$1($2,_curry')_
700 define(`_curry', ``$1',`$2',`$3')')_
702 _ `compose(`<outer>', `<inner>', <args>...)' results in a nested call to
703 _ `<outer>(<inner>(<args...))'. As with `curry', this implementation
704 _ only supports up to three <args>. This is useful in situations where
705 _ arguments from one source must be transformed before consumption by another
706 _ function. `ccompose(`<outer>', `<inner>')(<args>...)' is a curried compose.
708 define(`compose', `$1($2(`$3', `$4', `$5'))')_
709 define(`ccompose', `curry(`compose', ``$1', `$2'')')_
711 _ This is a workhorse iterator over a namespace. `map_cond(<start>, <cond>,
712 _ <func>, <ns>)' checks `<cond>(<value>)', starting at <start>. <cond> must
713 _ return T or F; if it returns F, `map_cond' then calls `<func>(<ns>, <value>)'
714 _ outside of argument collection (unless `map_cond' itself was called during
715 _ a macro argument), so that <func> can provide output to stdout; if <cond>
716 _ was good enough for its side effects, <func> can be `nop'. Iteration
717 _ continues with successively larger values from <start>, until <cond>
718 _ returns T to end iteration; <func> is not called for the <value> that
722 define(`map_cond', `if($2($1), `',
723 `$3(`$4', $1)`'map_cond(succ($1), `$2', `$3', `$4')')')_
725 _ `map(`<condfunc>', `<ns>')' is a simpler wrapper around `map_cond'; it
726 _ always starts at zero, and has only a single function as `<condfunc>(<ns>,
727 _ <value>)', which must return F to continue and T to halt, and where output
728 _ to stdout is not possible.
730 define(`map', `map_cond('zero`, `curry(`$1', ``$2'')', `nop', `$2')')_
732 _ Several Advent of Code puzzles want to run the virtual machine more than
733 _ once, resetting it back to the original state between runs before making
734 _ some other tweak. For example, the day 2 puzzle is about optimizing the
735 _ final value of M_0 after setting M_1 and M_10 two various values between
736 _ decimal 0 and 99 inclusive. This is made easier by having a function
737 _ that copies all values into a destination namespace from a source,
738 _ starting from index 0, and ending when predicate(<ns>, <val>) returns T.
739 _ There are two common predicates: `isundef' declared above which stops at
740 _ the first <val> that has never been assigned, and `limit(<val>)' which
741 _ produces a function that returns T upon reaching M_<val>. With the latter,
742 _ unassigned locations in the source are assigned `zero' in the destination.
744 _ Example: starting with `M_0' => one, `M_1' => three, and `M_10' not yet
746 _ copy(`B', `M', `isundef') ==> `B_0' => one, `B_1' => three
748 _ copy(`B', `M', `limit(three)') ==> as above, plus `B_10' => zero
750 _ The definition of `limit' is interesting: on each invocation, it updates
751 _ the definition of a helper macro that will determine the desired condition,
752 _ then produces the name of that helper. Thus, `limit(three)(`M', three)'
753 _ will ultimately result in T, while `limit(three)(`M', zero)' is F.
755 define(`limit', `define(`_limit', `le($1, 'DOL(2)`)')_limit')_
757 _ `copy(<dst_ns>, <src_ns>, <cond>)' copies items from <src_ns> to <dst_ns>,
758 _ starting at zero, until `<cond>(<src_ns>, <value>)' returns T. This
759 _ relies on the helper `_copy(<cond>, <dst_ns>, <src_ns>, <value>)'.
761 define(`copy', `map(`curry(`_copy', ``$3',`$1'')', `$2')')_
762 define(`_copy', `if($1(`$3', $4), `T',
763 `define(`$2_'base2($4), fetch(`$3', $4))F')')_
765 _ Another common desire is to display a namespace, whether with a `comma'
766 _ or `nl' separator. `dump(<format>, <sep>, <cond>, <ns>)' calls
767 _ `<format>(fetch(<ns>, <value>))' for each <value> from 0 until <cond>
768 _ returns T, and calling `<sep>`'' between items after the first.
770 define(`dump', `if($3(`$4', 'zero`), `', `$1(fetch(`$4', ''zero``))`'map_cond(
771 ''one``, `curry(`$3', ``$4'')', `$2`'ccompose(`$1', `fetch')',
773 define(`comma', `,')_
775 _ An intcode machine tracks the non-negative program counter, `pc'. Relative
776 _ addressing also relies on a `rel' offset, which can be negative, but the sum
777 _ must still be non-negative. There are also three status bits, described
778 _ earlier. Then there is `ioff', used by the default `read' for reading the
779 _ `I' namespace, and `ooff', used used by the default `write' for writing to
780 _ the `O' namespace. The function `reset' puts all of these values back to
781 _ their starting value of zero or F. As mentioned above, all of these take
782 _ an optional namespace suffix, to facilitate tracking state of parallel VMs.
785 `define(`pc$1', 'zero`)define(`rel$1', 'zero`)define(`halt$1_',
786 F)define(`pause$1_', F)define(`fatal$1_', F)define(`i$1off',
787 'zero`)define(`o$1off', 'zero`)')_
790 _ `step' is the heart of the engine, which loads, decodes, and executes a
791 _ single instruction. `run' is a wrapper that resets the pause bit in status,
792 _ uses `step', and then repeats if all status bits are still zero. Individual
793 _ instructions are responsible for updating `pc' as one of their side effects
794 _ (since intcode operations are variable width, and because of jump commands),
795 _ and may also update status to affect whether operation continues. Note
796 _ that this file does not call step or run; it is assumed that a wrapper
797 _ setup will fine-tune the machine (such as using `copy' to save or restore
798 _ pristine memory, alter initial values, tweak the behaviors of input or
799 _ output to match the program's needs) and then call `run' one or more times.
800 _ `op' also runs any `hook' (such as when `write' wants to display a prompt).
802 define(`step', `op(`$1', decode(get_0(pc$1, `$1')))')_
803 define(`op', `if(isempty($3), `fatal($1)',
804 `define(`pc$1', OP$2($3, $4, $5, `$1'))hook()define(`hook', `')')')_
805 define(`run', `define(`pause$1_', F)step(`$1')if(or(halt$1_,
806 or(pause$1_, fatal$1_)), `', `run(`$1')')')_
808 _ And with that, have fun!