all repos — xyw @ b8e7f36d2932cc5502d2abf075430b45109d74bd

A minimal virtual machine and assembler for terminals.

Simplified event management, fixed arg processing
h3rald h3rald@h3rald.com
Fri, 24 Jul 2026 11:06:10 +0200
commit

b8e7f36d2932cc5502d2abf075430b45109d74bd

parent

997e522eaeebf929ad5387a964ba611d287b1923

5 files changed, 141 insertions(+), 164 deletions(-)

jump to
M devices/terminal.cdevices/terminal.c

@@ -67,24 +67,7 @@ (void)error;

switch (addr) { case TERMINAL_INPUT: - { - xyw_word handler; - if (!xyw_argv_processed && XYW_PEEKW(&data[TERMINAL_ON_ARGUMENT])) - { - // Process input as arguments (separated by XYW_ARGUMENT_SEPARATOR) until XYW_END_OF_ARGUMENTS - handler = XYW_PEEKW(&data[TERMINAL_ON_ARGUMENT]); - } - else - { - // Process stdin keypress - handler = XYW_PEEKW(&data[TERMINAL_ON_KEYPRESS]); - } - if (handler) - { - xyw_eval(handler); - } break; - } case TERMINAL_OUTPUT: fputc(data[addr], stdout); fflush(stdout);
M examples/launcher.xywexamples/launcher.xyw

@@ -1,20 +1,20 @@

.include "lib/macros.xyw" -executing puts JSRw fileops.xim puts JSRw NL PUTC -fileops.xim system.image_exec STW +executing puts JSRw print-args.xim puts JSRw NL +print-args.xim system.image_exec STW -executing puts JSRw helloworld.xim puts JSRw NL PUTC +executing puts JSRw helloworld.xim puts JSRw NL helloworld.xim system.image_exec STW -done puts JSRw NL PUTC +done puts JSRw NL HLT .label executing .string "Executing: " .label done .string "Done!" -.label fileops.xim - .string "fileops.xim" +.label print-args.xim + .string "print-args.xim hello world" .label helloworld.xim .string "helloworld.xim"
M test.shtest.sh

@@ -10,7 +10,7 @@ "run_test print-args.xyw test1 test2 -- assert_output_equals \$'print-args.xim, test1, test2\\n'"

"run_test factorial8.xyw assert_output_equals \$'120\\n'" "run_test factorial16.xyw assert_output_equals \$'5040\\n'" "run_test fileops.xyw assert_output_equals \$'This is a test file.\\nIt has multiple lines.\\n'" - "run_test launcher.xyw assert_output_equals \$'Executing: fileops.xim\\n This is a test file.\\nIt has multiple lines.\\nExecuting: helloworld.xim\\n Hello, World!\\nDone!\\n'" + "run_test launcher.xyw assert_output_equals \$'Executing: print-args.xim hello world\\nprint-args.xim, hello, world\\nExecuting: helloworld.xim\\nHello, World!\\nDone!\\n'" ) ### Helper functions
M xyw.hxyw.h

@@ -33,14 +33,6 @@ xyw_output output;

xyw_teardown teardown; } xyw_device; -typedef struct -{ - xyw_word handler; - int pending; -} xyw_vector_slot; - -void xyw_request_vector(xyw_word handler); - // Main Globals extern xyw_device xyw_devices[XYW_TOTAL_DEVICES]; extern const char xyw_instructions[][4];
M xywrun.cxywrun.c

@@ -14,7 +14,6 @@

#define USER_STACK_SIZE 128 #define SYSTEM_FRAME_SIZE 8 #define SYSTEM_MAX_FRAMES 48 -#define MAX_VECTORS 4 #define USER_MEMORY_START 0x000 #define DEVICE_AREA_START 0xff00

@@ -91,9 +90,6 @@ static xyw_word yw_storage = 0; // 16-bit Y register storage

xyw_word *pc = &pc_storage; xyw_word *xw = &xw_storage; xyw_word *yw = &yw_storage; - -//// Vector slots -static xyw_vector_slot vector_slots[MAX_VECTORS]; //// Helpers to retrieve device number and address within the device static inline int get_device(xyw_word addr)

@@ -203,39 +199,6 @@ for (int i = 0; i < SYSTEM_FRAME_SIZE; i++) base[i] = 0;

return return_addr; } -//// Vector dispatch (deferred, non-recursive event handling) - -// Request a vector -void xyw_request_vector(xyw_word handler) -{ - for (int i = 0; i < MAX_VECTORS; i++) - { - if (!vector_slots[i].pending) - { - vector_slots[i].handler = handler; - vector_slots[i].pending = 1; - return; - } - } - XYW_DBG("Vector table full, dropping request for handler %04X\n", handler); -} - -// Dispatch requested vectors (called once per xyw_eval iteration) -static void dispatch_vectors(void) -{ - for (int i = 0; i < MAX_VECTORS; i++) - { - if (vector_slots[i].pending) - { - vector_slots[i].pending = 0; - XYW_DBG("Dispatching vector to handler %04X\n", vector_slots[i].handler); - ss_push_frame(*pc); - *pc = vector_slots[i].handler - 1; - return; - } - } -} - //// User stack management static void us_push(xyw_byte val)

@@ -424,17 +387,6 @@ // No error handler, halt execution

return xyw_memory[SYSTEM_ERROR]; } } - // Timer elapsed handling - xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); - if (on_timer_handler != 0) - { - if (readb(CLOCK_ON_TIMER_ELAPSED)) - { - XYW_DBG("Timer elapsed, requesting vector for handler at %04X\n", on_timer_handler); - xyw_request_vector(on_timer_handler); - } - } - return 0; }

@@ -446,7 +398,6 @@ xyw_byte user_stack[USER_STACK_SIZE];

xyw_byte system_stack[SYSTEM_MAX_FRAMES * SYSTEM_FRAME_SIZE]; xyw_byte s, u, x_val, y_val; xyw_word pc_val, xw_val, yw_val; - xyw_vector_slot vector_slots[MAX_VECTORS]; xyw_byte error_handler_entry_s; int argv_processed; } xyw_image_snapshot;

@@ -498,11 +449,16 @@ y_val = 0;

xw_storage = 0; yw_storage = 0; pc_storage = 0; - memset(vector_slots, 0, sizeof(vector_slots)); error_handler_entry_s = 0; xyw_argv_processed = 0; } +// Sub-image argument feeding state +#define MAX_SUBIMAGE_ARGS 256 +static char subimage_args_buf[MAX_SUBIMAGE_ARGS]; +static int subimage_args_active = 0; +static int subimage_args_pos = 0; + static int load_and_launch(const char *path, char *argstart) { FILE *fp = fopen(path, "rb");

@@ -529,21 +485,17 @@

xyw_memory[SYSTEM_PAGE] = 0xff; *pc = 0x0000; - if (argstart && XYW_PEEKW(&xyw_memory[TERMINAL_ON_ARGUMENT])) + // Store sub-image arguments for deferred feeding via feed_next_event + // Include image path as first argument (matching CLI argv convention) + if (argstart) + { + snprintf(subimage_args_buf, MAX_SUBIMAGE_ARGS, "%s %s", path, argstart); + subimage_args_active = 1; + subimage_args_pos = 0; + } + else { - char *tok = strtok(argstart, " "); - int first = 1; - while (tok) - { - if (!first) - writeb(TERMINAL_INPUT, XYW_ARGUMENT_SEPARATOR); - for (size_t j = 0; tok[j] != '\0'; j++) - writeb(TERMINAL_INPUT, (xyw_byte)tok[j]); - first = 0; - tok = strtok(NULL, " "); - } - writeb(TERMINAL_INPUT, XYW_END_OF_ARGUMENTS); - xyw_argv_processed = 1; + subimage_args_active = 0; } return 0; }

@@ -561,7 +513,6 @@ snap->y_val = y_val;

snap->pc_val = *pc; snap->xw_val = *xw; snap->yw_val = *yw; - memcpy(snap->vector_slots, vector_slots, sizeof(vector_slots)); snap->error_handler_entry_s = error_handler_entry_s; snap->argv_processed = xyw_argv_processed; }

@@ -579,7 +530,6 @@ y_val = snap->y_val;

*pc = snap->pc_val; *xw = snap->xw_val; *yw = snap->yw_val; - memcpy(vector_slots, snap->vector_slots, sizeof(vector_slots)); error_handler_entry_s = snap->error_handler_entry_s; xyw_argv_processed = snap->argv_processed;

@@ -613,6 +563,106 @@ reset_vm_state();

load_and_launch(path, argstart); } +//// Event dispatch (WAITING state) + +// Argument feeding state (CLI args) +static int arg_index = 1; +static int arg_char_pos = 0; + +// Feed the next external event and dispatch its handler. +// Returns 1 if an event was dispatched, 0 if no more events. +static int feed_next_event(void) +{ + // Feed command line arguments + xyw_word on_arg_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_ARGUMENT]); + if (!xyw_argv_processed && on_arg_handler) + { + if (subimage_args_active) + { + // Feed from sub-image argument string (space-separated) + char c = subimage_args_buf[subimage_args_pos]; + if (c == ' ') + { + xyw_memory[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; + subimage_args_pos++; + } + else if (c != '\0') + { + xyw_memory[TERMINAL_INPUT] = (xyw_byte)c; + subimage_args_pos++; + } + else + { + xyw_memory[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + subimage_args_active = 0; + } + } + else if (arg_index < xyw_argc) + { + // Feed from CLI argv + char c = xyw_argv[arg_index][arg_char_pos]; + if (c != '\0') + { + xyw_memory[TERMINAL_INPUT] = (xyw_byte)c; + arg_char_pos++; + } + else if (arg_index < xyw_argc - 1) + { + xyw_memory[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; + arg_index++; + arg_char_pos = 0; + } + else + { + xyw_memory[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + } + } + else + { + // No arguments, just send end marker + xyw_memory[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + } + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; + // Sentinel return: handler's RTS returns to device space, cleanly ending dispatch + ss_push_frame(DEVICE_AREA_START); + *pc = on_arg_handler; + return 1; + } + + // Keypress handling + xyw_word on_keypress_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_KEYPRESS]); + if (on_keypress_handler) + { + int c = getch(); + if (c == 3 || c == 4) return 0; // CTRL+C or CTRL+D + xyw_memory[TERMINAL_INPUT] = (xyw_byte)c; + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; + // Sentinel return: handler's RTS returns to device space, cleanly ending dispatch + ss_push_frame(DEVICE_AREA_START); + *pc = on_keypress_handler; + return 1; + } + + // Timer handling (readb blocks until the timer elapses if active) + xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); + if (on_timer_handler) + { + if (readb(CLOCK_ON_TIMER_ELAPSED)) + { + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; + // Sentinel return: handler's RTS returns to device space, cleanly ending dispatch + ss_push_frame(DEVICE_AREA_START); + *pc = on_timer_handler; + return 1; + } + } + + return 0; +} + //// Main evaluation function - runs from current pc until BRK int xyw_eval(xyw_word start_pc) {

@@ -636,6 +686,14 @@ xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING;

} if (!system_running_state()) { + if (system_waiting_state()) + { + if (feed_next_event()) + { + continue; // handler was dispatched, go execute it + } + xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_WAITING; + } if (exec_stack_depth > 0) { restore_snapshot(&exec_stack[--exec_stack_depth]);

@@ -645,7 +703,6 @@ break; // no parent -- genuinely done

} process_events(); - dispatch_vectors(); handle_exec_requests(); if (!system_running_state()) {

@@ -655,7 +712,15 @@ 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 - /* HLT */ case 0x00: xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; break; + /* HLT */ case 0x00: + xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; + if (XYW_PEEKW(&xyw_memory[TERMINAL_ON_ARGUMENT]) || + XYW_PEEKW(&xyw_memory[TERMINAL_ON_KEYPRESS]) || + XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED])) + { + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_WAITING; + } + break; /* NOP */ case 0x01: break; /* PSH */ OPC_SR1(0x02, , if (rx||ry) { PUSH(ARG); } else { (*pc)++; PUSH(READ(*pc)); if (w) { (*pc)++; } }) /* PSH */ OPC_SR1r(0x02, PUSH(a); PUSH(b); )

@@ -762,69 +827,6 @@ // Set direct page to device page by default

xyw_memory[SYSTEM_PAGE] = 0xff; int eval_result = xyw_eval(0x0000); - if (!eval_result) - { - // Handle command line arguments - if (!xyw_argv_processed && !(system_error_state()) && XYW_PEEKW(&xyw_memory[TERMINAL_ON_ARGUMENT])) - { - for (int i = 1; i < xyw_argc; i++) - { - XYW_DBG("Processing argument %d of %d: %s\n", i, xyw_argc, xyw_argv[i]); - const char *arg = xyw_argv[i]; - for (size_t j = 0; arg[j] != '\0'; j++) - { - writeb(TERMINAL_INPUT, (xyw_byte)arg[j]); - } - if (i < xyw_argc - 1) - { - writeb(TERMINAL_INPUT, XYW_ARGUMENT_SEPARATOR); - } - } - writeb(TERMINAL_INPUT, XYW_END_OF_ARGUMENTS); - xyw_argv_processed = 1; - } - - // Check if we need to enter an event loop (keypress handler or timer) - xyw_word on_keypress_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_KEYPRESS]); - xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); - if (on_keypress_handler) - { - XYW_DBG("terminal.on_keypress handler set to %04X, entering input loop\n", on_keypress_handler); - while (system_running_state() || system_waiting_state()) - { - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_WAITING; - int c = getch(); - // if CTRL+C or CTRL+D is pressed, exit - if (c == 3 || c == 4) - { - return 0; - } - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; - writeb(TERMINAL_INPUT, (xyw_byte)c); - } - } - else if (on_timer_handler) - { - XYW_DBG("clock.on_timer_elapsed handler set to %04X, entering timer loop\n", on_timer_handler); - while (system_running_state() || system_waiting_state()) - { - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_WAITING; - if (readb(CLOCK_ON_TIMER_ELAPSED)) - { - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; - XYW_DBG("Timer elapsed, calling handler at %04X\n", on_timer_handler); - xyw_eval(on_timer_handler); - // Refresh handler in case it was changed - on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); - if (!on_timer_handler) - { - break; - } - } - } - } - } - - return 0; + return eval_result; }