Here is my solution for the self hosting problem, inspired by CakeEaterGames' version.
It takes 893 cycles and 193 bytes. His solution is faster (848 cycles), but reads more memory (206 bytes). It's probably still sub-optimal, so any improvement suggestion is welcome.
As specified, the solution can handle programs up to 21 bytes, but 40 bytes of memory are un-used, so I think it could actually handle programs up to 61 bytes if pre-allocated.
I wish I could write a test for that. Now that the game is published on Steam, having puzzles with player written test cases to publish on their community workshop would be fantastic.
; Parse a program containing .data directives and subleq ; instructions, then execute that program. ; ; All addresses used by the input program should be isolated from ; your program's address space. ; ; As in any other program, if the program writes to address 254 ; (@OUT; signed value: -2), that value should be directly written out. ; ; If the program branches to address 255 (@HALT; signed value: -1), ; then the program is done. Start over from scratch with the next ; input program. ; ; The compiled size of each input program is <= 21 bytes. ; ; As in previous tasks, the .data directive will always have exactly ; 1 value and subleq instructions will specify exactly 3 addresses ; (separated by spaces only). ; ; Additionally, the input programs will not declare or use any labels ; or variables. The only built-in addresses that will be used will be ; referenced by address instead of label (e.g. "254" will be used but ; "@OUT" will not). ; ; allocate 21 bytes of memory for the program @program: subleq 0, 0, @start subleq 0, 0, 0 subleq 0, 0, 0 subleq 0, 0, 0 subleq 0, 0, 0 subleq 0, 0, 0 subleq 0, 0, 0 ; @start: subleq @input, @IN subleq @value, @value subleq @value, @input, @program ; execute if end of string subleq @input, @input subleq @value, @break, @start ; skip empty lines subleq @number, @number ; read useless characters subleq @IN, @IN subleq @IN, @IN subleq @IN, @IN subleq @IN, @IN subleq @input, @IN subleq @input, @space, @next ; skip a char (subleq has one more char than data) subleq @IN, @IN @next: subleq @target+2, @target+2 subleq @input, @input subleq @input, @IN subleq @value, @value subleq @value, @input ; testing chars from lower to greater ascii code by their diff subleq @value, @break, @push_line ; '\n' (10) subleq @value, @_22, @push_space ; ' ' (22) subleq @value, @_13, @negative ; ' '-' (45) subleq @value, @three ; '0' (48) ; AB = 10A + B ; 'A' might be greater than 10 ; it's guaranteed no out of range number will be input subleq @input, @input subleq @input, @number subleq @count, @count subleq @count, @eight @loop: subleq @number, @input subleq @count, @n_one, @loop subleq @number, @value subleq @input, @input, @next ; @negative: subleq @n, @n_one subleq @input, @input, @next ; @push_line: subleq @target+2, @start_address subleq @input, @input, @push ; @push_space: subleq @target+2, @next_address subleq @input, @input, @push ; @push: ; clear the address before writing ; it's guaranteed no unallocated memory will be read or written by the input programs subleq @program, @program subleq @value, @value subleq @value, @number subleq @n, @OUT, @skip_negative ; @OUT reads as zero ; negative subleq @n, @n subleq @count, @count subleq @count, @value subleq @number, @count subleq @number, @count ; short-circuit to @push2? faster, but costs more memory @skip_negative: subleq @value, @n_one, @maybe255 @push2: subleq @program, @number @shift: ; right shift writing addresses by one byte subleq @push, @n_one subleq @push+1, @n_one subleq @push2, @n_one @target: ; if called from @push_line, jumps to @start ; if called from @push_space, jumps to @next subleq @number, @number, @next ; @maybe255: subleq @value, @n_one, @push2 subleq @number, @number ; replace @HALT (255) with address of @halt (labels are case sensitive) ; it's guaranteed 255 will only be input as subleq's arg3 ; .data, arg1 and arg2 will never be 255 subleq @number, @halt_address subleq @value, @value, @push2 ; @halt: ; resets pointers subleq @push, @push subleq @push+1, @push+1 subleq @push2, @push2 subleq @input, @input, @start ; ; variables @input: .data 0 @n: .data 0 @number: .data 0 @count: .data 0 @value: .data 0 ; ; constants @halt_address: .data @halt ; positive because is higher than 127 @start_address: .data -@start @next_address: .data -@next @n_one: .data -1 @eight: .data 8 @three: .data 3 ; '0' (48) - '-' (45) @break: .data '\n' ; 10 @_13: .data 13 ; '-' (45) - ' ' (32) @_22: .data 22 ; ' ' (32) - '\n' (10) @space: .data ' ' ; 32 ;