all repos — xyw @ 0b2088be17b577856b2e5ed21f97c4c4143e0299

A minimal virtual machine and assembler for terminals.

Added factorial example, fixed a few bugs in macros and SWP.
h3rald h3rald@h3rald.com
Thu, 05 Feb 2026 15:38:14 +0100
commit

0b2088be17b577856b2e5ed21f97c4c4143e0299

parent

d5462eb6b0080f193c6df100231f3753477974bd

3 files changed, 59 insertions(+), 17 deletions(-)

jump to
M examples/factorial.xywexamples/factorial.xyw

@@ -1,12 +1,53 @@

-; main program -$5 fact JSRw -$0 system.state STBw +.include "devices.xyw" -; a8 -- +; ;ain program: calculate and print factorial of 5 +$5 fact JSRw +print8 JSRw +$0A terminal.output STB +end JMPw + +; Factorial subroutine +; a8 -- | [xy] .label fact - $1 EQU fact.end JCNw - INC MUL - fact JMPw - .label fact.end - ; TODO: print - RTS+ ; Save argument to both registers + POPxy + .label fact.loop + PSHy $0 EQU fact.end0 JCNw + PSHy $1 EQU fact.end1 JCNw + ; Decrement y and multiply + DECy PSHx PSHy MULx POPx + fact.loop JMPw + .label fact.end0 + ; Edge case: factorial of 0 is 1 + $1 + CLRxy + RTS + .label fact.end1 + ; Return result + PSHx + CLRxy + RTS + +; Printing subroutine +; a8 -- | [xy] +.label print8 + POPx + ; If value is less than 10, print single digit + PSHx $A LTH print8.single JCNw + ; Divide by 10: stack becomes [remainder, quotient] + PSHx $A DIV + ; Save remainder to y for later + SWP POPy + ; Call print8 recursively with quotient (still on stack) + print8 JSRw + ; Print the saved remainder + PSHy $30 ADD terminal.output STB + CLRxy + RTS + .label print8.single + ; Print single digit + PSHx $30 ADD terminal.output STB + CLRx + RTS + +.label end
M test.shtest.sh

@@ -7,6 +7,7 @@ "run_test helloworld.xyw assert_output_equals \$'Hello, World!\\n'"

"run_test d6.xyw assert_output_in_range 1 6" "run_test on-error.xyw assert_output_equals \$'Error: Division by zero.\\nError: Unknown error.\\n'" "run_test print-args.xyw test1 test2 -- assert_output_equals \$'print-args.xim, test1, test2\\n'" + "run_test factorial.xyw assert_output_equals \$'120\\n'" ) ### Helper functions
M xywrun.cxywrun.c

@@ -226,10 +226,10 @@ // clang-format off

//// Operations that support stack and all registers #define OPC_SR2(opc, body) { \ - case opc: { const int w=0, rx=0, ry=0; xyw_byte b = us_pop(), a = us_pop(); body; (void)(rx); (void)(ry); } break; \ - case XYW_MODE_X|opc: { const int w=0, rx=1, ry=0; xyw_byte b = *x, a = us_pop(); body; (void)(rx); (void)(ry); } break; \ - case XYW_MODE_Y|opc: { const int w=0, rx=0, ry=1; xyw_byte b = *y, a = us_pop(); body; (void)(rx); (void)(ry); } break; \ - case XYW_MODE_X|XYW_MODE_Y|opc: { const int w=0, rx=1, ry=1; xyw_byte a = *x, b = *y; body; (void)(rx); (void)(ry); } break; \ + case opc: { const int w=0, rx=0, ry=0; xyw_byte b = us_pop(); xyw_byte a = us_pop(); body; (void)(rx); (void)(ry); } break; \ + case XYW_MODE_X|opc: { const int w=0, rx=1, ry=0; xyw_byte b = *x; xyw_byte a = us_pop(); body; (void)(rx); (void)(ry); } break; \ + case XYW_MODE_Y|opc: { const int w=0, rx=0, ry=1; xyw_byte b = *y; xyw_byte a = us_pop(); body; (void)(rx); (void)(ry); } break; \ + case XYW_MODE_X|XYW_MODE_Y|opc: { const int w=0, rx=1, ry=1; xyw_byte a = *x; xyw_byte b = *y; body; (void)(rx); (void)(ry); } break; \ case XYW_MODE_W|opc: { const int w=1, rx=0, ry=0; xyw_word b = us_popw(); xyw_word a = us_popw(); body; (void)(rx); (void)(ry); } break; \ case XYW_MODE_X|XYW_MODE_W|opc: { const int w=1, rx=1, ry=0; xyw_word b = *xw; xyw_word a = us_popw(); body; (void)(rx); (void)(ry); } break; \ case XYW_MODE_Y|XYW_MODE_W|opc: { const int w=1, rx=0, ry=1; xyw_word b = *yw; xyw_word a = us_popw(); body; (void)(rx); (void)(ry); } break; \

@@ -253,7 +253,7 @@ case XYW_MODE_X|XYW_MODE_Y|XYW_MODE_W|opc: { const int w=1, rx=1, ry=1; xyw_word a = *xw; xyw_word b = *yw; body;(void)(w);(void)(a);(void)(b);(void)(rx);(void)(ry); } break; \

} // Create address based on direct page or full address -#define ADDR(ad) w ? ad : (DIRECT_PAGE | ad) +#define ADDR(ad) (w ? (ad) : (DIRECT_PAGE | (ad))) // Push a value to stack #define PUSH(n) do { if (w) { us_pushw(n); } else { us_push(n); } } while(0) // Pop value from stack

@@ -354,7 +354,7 @@ }

while ((xyw_memory[SYSTEM_STATE] & SYSTEM_STATE_RUNNING) && *pc < SYSTEM_MEMORY_START) { process_events(); - xyw_dbg("PC: %04X -> %02X (%s) [U:%02X|S:%02X|X:%02X|Y:%02X|XW:%04X|YW:%04X]\n", *pc, xyw_memory[*pc], xyw_instructions[xyw_memory[*pc] & 0x1F], *u, *s, *x, *y, *xw, *yw); + xyw_dbg(" -- PC: %04X -> %02X (%s) [U:%02X|S:%02X|X:%02X|Y:%02X|XW:%04X|YW:%04X]\n", *pc, xyw_memory[*pc], xyw_instructions[xyw_memory[*pc] & 0x1F], *u, *s, *x, *y, *xw, *yw); switch (xyw_memory[*pc]) { // clang-format off

@@ -394,7 +394,7 @@ /* IOR */ OPC_SR2(0x15, PUSH(a | b));

/* XOR */ OPC_SR2(0x16, PUSH(a ^ b)); /* NOT */ OPC_SR1(0x17, , SET(~ARG)); /* NOT */ OPC_SR1r(0x17, SET(~ARGX); SET(~ARGY); ); - /* SWP */ OPC_SR2(0x18, if ((rx||ry)) { SET(b); } else { PUSH(a); PUSH(b); } ); + /* SWP */ OPC_SR2(0x18, if ((rx||ry)) { SET(b); } else { PUSH(b); PUSH(a); } ); /* DUP */ OPC_SR1(0x19, , SET(READ(USER_STACK_START + (*u) - (w ? 2 : 1)))); /* DUP */ OPC_SR1r(0x19, SETX(READ(USER_STACK_START + (*u) - (w ? 2 : 1))); SETY(READ(USER_STACK_START + (*u) - (w ? 2 : 1))); ); /* OVR */ OPC_SR2(0x1A, PUSH(b); PUSH(a); PUSH(b));