# Muri: a Minimalist Assembler for Nga This is a small assembler used to build the initial image for RETRO. The implementation here uses the runtime variant included in the core RETRO system. See the glossary entries for `i`, `d`, `r`, `as{`, and `}as` for details on these. The full assembler has a postfix notation. Syntax is: Directives are a single character. Muri recognizes: * **i** for instructions * **d** for numeric data * **s** for string data * **:** for creating a label * **r** for references to labels Instructions are packed up to four instructions per location. You can specify them using the first two characters of the instruction name. For a non operation, use '..' instead of 'no'. 0 nop 5 push 10 ret 15 fetch 20 div 25 zret 1 lit 6 pop 11 eq 16 store 21 and 26 halt 2 dup 7 jump 12 neq 17 add 22 or 27 ienum 3 drop 8 call 13 lt 18 sub 23 xor 28 iquery 4 swap 9 ccall 14 gt 19 mul 24 shift 29 iinvoke E.g., for a sequence of dup, multiply, no-op, drop: i dumu..dr An example of a small program: i liju.... r main : square i dumure.. : main i lilica.. d 12 r square i ha...... As mentioned earlier this requires knowledge of Nga architecture. While you can pack up to four instructions per location, you should not place anything after an instruction that modifies the instruction pointer. These are: ju, ca, cc, re, and zr. ~~~ :file:operation #4 io:scan-for io:invoke ; #0 'file:R const #1 'file:W const #2 'file:A const #3 'file:R+ const :file:open (sm-h) #0 file:operation ; :file:close (h-) #1 file:operation ; :file:read (h-c) #2 file:operation ; :file:write (ch-) #3 file:operation ; {{ 'FID var 'Size var 'Action var 'Buffer var :-eof? (-f) @FID file:tell @Size lt? ; :preserve (q-) &FID [ &Size &call v:preserve ] v:preserve ; ---reveal--- :file:read-line (f-s) !FID [ here dup !Buffer buffer:set [ @FID file:read dup buffer:add [ ASCII:CR eq? ] [ ASCII:LF eq? ] [ ASCII:NUL eq? ] tri or or ] until buffer:get drop ] buffer:preserve @Buffer ; :file:for-each-line (sq-) [ !Action file:open-for-reading !FID !Size [ @FID file:read-line @Action call -eof? ] while @FID file:close ] preserve ; }} ~~~ ## Unu This is documented in *example/retro-unu.forth*, but basically it provides a combinator that runs a quote for each line in a file, provided that the lines are in fenced blocks starting and ending with `~~~`. The RETRO sources are written in this style, so I include Unu here to simplify the later workflow. ~~~ {{ 'Fenced var :toggle-fence @Fenced not !Fenced ; :fenced? (-f) @Fenced ; :handle-line (s-) fenced? [ over call ] [ drop ] choose ; ---reveal--- :unu (sq-) swap [ dup '~~~ s:eq? [ drop toggle-fence ] [ handle-line ] choose ] file:for-each-line drop ; }} ~~~ ## Muri Now for the assembler. I create a couple of data structures: a buffer for the assembled image and a pointer into this. ~~~ 'Image d:create #8192 allot 'AP var ~~~ I then use these to implement `I,`, a word which stores a value into the image buffer and increment the pointer. ~~~ :I, (n-) &Image @AP + store &AP v:inc ; ~~~ ### Pass 1 Muri is a two pass assembler. The first pass handles most of the work. It processes instrution bundles, data, strings, and creates labels pointing to specific addresses in the image. References are compiled as dummy values, to be resolved later. ~~~ #0 !AP 'retro.muri [ dup s:length n:zero? [ drop #0 ] if 0; fetch-next &n:inc dip $i [ i here n:dec fetch I, ] case $d [ s:to-number I, ] case $r [ drop #-1 I, ] case $: [ @AP swap 'muri! s:prepend const ] case $s [ &I, s:for-each #0 I, ] case 'ERROR s:put nl ] unu ~~~ ### Pass 2 The second pass skips over everything except references, which get resolved and filled in. This allows for forward references. ~~~ #0 !AP 'retro.muri [ dup s:length n:zero? [ drop #0 ] if 0; fetch-next &n:inc dip $i [ drop &AP v:inc ] case $d [ drop &AP v:inc ] case $r [ 'muri! s:prepend d:lookup d:xt fetch I, ] case $: [ drop ] case $s [ s:length n:inc &AP v:inc-by ] case 'ERROR s:put nl ] unu ~~~ ### Save Image Saving the image is pretty straightforward. For each cell, convert to bytes and write them to the output file. ~~~ 'FID var :write-byte (n-) @FID file:write ; :mask (n-) #255 and ; :write-cell (n-) dup mask write-byte #8 shift dup mask write-byte #8 shift dup mask write-byte #8 shift mask write-byte ; :image:save (s-) file:W file:open !FID &Image @AP [ fetch-next write-cell ] times drop @FID file:close ; 'retro.nga image:save ~~~