Refactored terminal.
@@ -1,5 +1,6 @@
#include <stdio.h> -#include "../xyw.h" +#include <string.h> +#include "terminal.h" #ifndef _WIN32 #include <termios.h>@@ -15,15 +16,150 @@ #define TERMINAL_OUTPUT 0x1
#define TERMINAL_ON_KEYPRESS 0x2 #define TERMINAL_ON_ARGUMENT 0x4 +#define POLL_INTERVAL_MS 100 + +//// Platform-specific non-blocking key input + +#ifdef _WIN32 +#include <conio.h> +#include <windows.h> +static int getch_timeout(int timeout_ms) +{ + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (WaitForSingleObject(h, timeout_ms) == WAIT_OBJECT_0 && _kbhit()) + return _getch(); + return 0; +} +#else +static int getch_timeout(int timeout_ms) +{ + (void)timeout_ms; // bounded by VMIN=0/VTIME in terminal_init() + int c = getchar(); + return (c == EOF) ? 0 : c; +} +#endif + +//// Argument feeding state + +static int arg_index = 1; +static int arg_char_pos = 0; +static int exec_args_pos = 0; + +//// Generic save/restore + +void terminal_save(xyw_byte *data, xyw_byte *state) +{ + (void)data; + memcpy(&state[0], &arg_index, sizeof(arg_index)); + memcpy(&state[4], &arg_char_pos, sizeof(arg_char_pos)); + memcpy(&state[8], &exec_args_pos, sizeof(exec_args_pos)); +} + +void terminal_restore(xyw_byte *data, const xyw_byte *state) +{ + (void)data; + memcpy(&arg_index, &state[0], sizeof(arg_index)); + memcpy(&arg_char_pos, &state[4], sizeof(arg_char_pos)); + memcpy(&exec_args_pos, &state[8], sizeof(exec_args_pos)); +} + +//// Poll: argument feeding and keypress handling + +int terminal_poll(xyw_byte *data) +{ + // Feed command line arguments + xyw_word on_arg_handler = XYW_PEEKW(&data[TERMINAL_ON_ARGUMENT]); + if (!xyw_argv_processed && on_arg_handler) + { + if (xyw_exec_args_active) + { + char c = xyw_exec_args[exec_args_pos]; + if (c == ' ') + { + data[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; + exec_args_pos++; + } + else if (c != '\0') + { + data[TERMINAL_INPUT] = (xyw_byte)c; + exec_args_pos++; + } + else + { + data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + xyw_exec_args_active = 0; + } + } + else if (arg_index < xyw_argc) + { + char c = xyw_argv[arg_index][arg_char_pos]; + if (c != '\0') + { + data[TERMINAL_INPUT] = (xyw_byte)c; + arg_char_pos++; + } + else if (arg_index < xyw_argc - 1) + { + data[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; + arg_index++; + arg_char_pos = 0; + } + else + { + data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + } + } + else + { + data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + } + xyw_vm_set_running(1); + xyw_vm_push_frame(XYW_DEVICE_AREA_START); + *pc = on_arg_handler; + return 1; + } + + // Keypress handling + xyw_word on_keypress_handler = XYW_PEEKW(&data[TERMINAL_ON_KEYPRESS]); + if (on_keypress_handler) + { + int c = getch_timeout(POLL_INTERVAL_MS); + if (c > 0) + { + if (c == 3 || c == 4) + { + xyw_vm_set_waiting(0); + return 0; + } + data[TERMINAL_INPUT] = (xyw_byte)c; + xyw_vm_set_running(1); + xyw_vm_push_frame(XYW_DEVICE_AREA_START); + *pc = on_keypress_handler; + return 1; + } + return -1; // active handler, no event yet (keep polling) + } + + return 0; +} + +//// Device init/teardown/IO + void terminal_init(xyw_byte *data) { (void)data; + // Reset arg feeding state for fresh image + arg_index = 1; + arg_char_pos = 0; + exec_args_pos = 0; #ifndef _WIN32 struct termios raw; if (!isatty(STDIN_FILENO)) { - // Not a real terminal (e.g. piped input/tests) — nothing to save or set return; }@@ -35,7 +171,6 @@ }
raw = terminal_orig_termios; raw.c_lflag &= ~(ICANON | ECHO | ISIG); - // Non-blocking read: return after 100ms even if no input (for polling) raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 1;
@@ -3,4 +3,7 @@
void terminal_init(xyw_byte *data); void terminal_output(xyw_byte *data, xyw_byte addr, xyw_byte *error); xyw_byte terminal_input(xyw_byte *data, xyw_byte addr, xyw_byte *error); -void terminal_teardown(xyw_byte *data);+void terminal_teardown(xyw_byte *data); +int terminal_poll(xyw_byte *data); +void terminal_save(xyw_byte *data, xyw_byte *state); +void terminal_restore(xyw_byte *data, const xyw_byte *state);
@@ -6,6 +6,7 @@ #define XYW_MODE_Y 0x40
#define XYW_MODE_W 0x20 #define XYW_TOTAL_INSTRUCTIONS 0x20 #define XYW_TOTAL_DEVICES 0x10 +#define XYW_DEVICE_AREA_START 0xff00 #define XYW_ARGUMENT_SEPARATOR 0x1E #define XYW_END_OF_ARGUMENTS 0x1D@@ -23,7 +24,11 @@ typedef xyw_byte (*xyw_input)(xyw_byte *data, xyw_byte addr, xyw_byte *error);
typedef void (*xyw_output)(xyw_byte *data, xyw_byte addr, xyw_byte *error); typedef void (*xyw_setup)(xyw_byte *data); typedef void (*xyw_teardown)(xyw_byte *data); -typedef int (*xyw_poll)(xyw_byte *data); +typedef int (*xyw_poll)(xyw_byte *data); // returns: 1=dispatched, 0=inactive, -1=active but no event +typedef void (*xyw_save)(xyw_byte *data, xyw_byte *state); +typedef void (*xyw_restore)(xyw_byte *data, const xyw_byte *state); + +#define XYW_DEVICE_STATE_SIZE 64 typedef struct xyw_device {@@ -33,6 +38,8 @@ xyw_input input;
xyw_output output; xyw_teardown teardown; xyw_poll poll; + xyw_save save; + xyw_restore restore; } xyw_device; // Main Globals@@ -42,6 +49,11 @@ extern int xyw_debug;
extern int xyw_argc; extern char *xyw_argv[]; extern int xyw_argv_processed; + +// Exec args: set by VM exec logic, read by terminal during poll +#define XYW_EXEC_ARGS_SIZE 256 +extern char xyw_exec_args[XYW_EXEC_ARGS_SIZE]; +extern int xyw_exec_args_active; // Main public API int xyw_assemble(const char *input, const char *output);
@@ -21,8 +21,6 @@
#define MAX_IMAGE_EXEC_DEPTH 8 #define MAX_IMAGE_EXEC_PATH_LENGTH 256 -#define POLL_INTERVAL_MS 100 - /* system device addresses */ #define SYSTEM_STATE 0xff00 #define SYSTEM_ERROR 0xff01@@ -39,25 +37,6 @@ /* Clock device addresses */
#define CLOCK_TIMER 0xff28 #define CLOCK_ON_TIMER_ELAPSED 0xff2a -#ifdef _WIN32 -#include <conio.h> -#include <windows.h> -int getch_timeout(int timeout_ms) -{ - HANDLE h = GetStdHandle(STD_INPUT_HANDLE); - if (WaitForSingleObject(h, timeout_ms) == WAIT_OBJECT_0 && _kbhit()) - return _getch(); - return 0; -} -#else -#include <unistd.h> -int getch_timeout(int timeout_ms) -{ - (void)timeout_ms; // bounded by VMIN=0/VTIME in terminal_init() - int c = getchar(); - return (c == EOF) ? 0 : c; -} -#endif typedef enum { XYW_ERROR_NONE = 0,@@ -76,6 +55,10 @@ #define SYSTEM_STATE_ERROR 0x80
/// Main memory xyw_byte xyw_memory[MEMORY_SIZE]; + +/// Exec args (set by exec logic, consumed by terminal poll) +char xyw_exec_args[XYW_EXEC_ARGS_SIZE]; +int xyw_exec_args_active = 0; /// Stacks (not part of addressable memory) static xyw_byte user_stack[USER_STACK_SIZE];@@ -266,6 +249,7 @@ yw_storage = 0;
pc_storage = 0; error_handler_entry_s = 0; xyw_argv_processed = 0; + xyw_exec_args_active = 0; } //// User stack management@@ -403,29 +387,19 @@ //// Initialize devices
xyw_device xyw_devices[XYW_TOTAL_DEVICES] = { // System device - {&xyw_memory[0xff00], system_setup, system_input, system_output, 0, 0}, + {&xyw_memory[0xff00], system_setup, system_input, system_output, 0, 0, 0, 0}, // Terminal device - {&xyw_memory[0xff10], terminal_init, terminal_input, terminal_output, terminal_teardown, 0}, + {&xyw_memory[0xff10], terminal_init, terminal_input, terminal_output, terminal_teardown, terminal_poll, terminal_save, terminal_restore}, // Clock device - {&xyw_memory[0xff20], clock_setup, clock_input, clock_output, 0, 0}, + {&xyw_memory[0xff20], clock_setup, clock_input, clock_output, 0, 0, 0, 0}, // File device - {&xyw_memory[0xff30], file_setup, file_input, file_output, 0, 0}, + {&xyw_memory[0xff30], file_setup, file_input, file_output, 0, 0, 0, 0}, // Beeper device - {&xyw_memory[0xff40], 0, beeper_input, beeper_output, beeper_teardown, 0}, + {&xyw_memory[0xff40], 0, beeper_input, beeper_output, beeper_teardown, 0, 0, 0}, // ... }; // clang-format on -// Argument feeding state (CLI args) -static int arg_index = 1; -static int arg_char_pos = 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 process_events() { // Error handling@@ -462,11 +436,9 @@ xyw_byte s, u, x_val, y_val;
xyw_word pc_val, xw_val, yw_val; xyw_byte error_handler_entry_s; int argv_processed; - int arg_index; - int arg_char_pos; - char subimage_args_buf[MAX_SUBIMAGE_ARGS]; - int subimage_args_active; - int subimage_args_pos; + char exec_args[XYW_EXEC_ARGS_SIZE]; + int exec_args_active; + xyw_byte device_state[XYW_TOTAL_DEVICES][XYW_DEVICE_STATE_SIZE]; } xyw_image_snapshot; static xyw_image_snapshot exec_stack[MAX_IMAGE_EXEC_DEPTH];@@ -518,6 +490,7 @@ yw_storage = 0;
pc_storage = 0; error_handler_entry_s = 0; xyw_argv_processed = 0; + xyw_exec_args_active = 0; } static int load_and_launch(const char *path, char *argstart)@@ -548,17 +521,15 @@
xyw_memory[SYSTEM_PAGE] = 0xff; *pc = 0x0000; - // Store sub-image arguments for deferred feeding via feed_next_event - // Include image path as first argument (matching CLI argv convention) + // Store sub-image arguments for deferred feeding via terminal poll if (argstart) { - snprintf(subimage_args_buf, MAX_SUBIMAGE_ARGS, "%s %s", path, argstart); - subimage_args_active = 1; - subimage_args_pos = 0; + snprintf(xyw_exec_args, XYW_EXEC_ARGS_SIZE, "%s %s", path, argstart); + xyw_exec_args_active = 1; } else { - subimage_args_active = 0; + xyw_exec_args_active = 0; } return 0; }@@ -578,11 +549,11 @@ snap->xw_val = *xw;
snap->yw_val = *yw; snap->error_handler_entry_s = error_handler_entry_s; snap->argv_processed = xyw_argv_processed; - snap->arg_index = arg_index; - snap->arg_char_pos = arg_char_pos; - memcpy(snap->subimage_args_buf, subimage_args_buf, sizeof(subimage_args_buf)); - snap->subimage_args_active = subimage_args_active; - snap->subimage_args_pos = subimage_args_pos; + memcpy(snap->exec_args, xyw_exec_args, sizeof(xyw_exec_args)); + snap->exec_args_active = xyw_exec_args_active; + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + if (xyw_devices[i].save) + xyw_devices[i].save(xyw_devices[i].data, snap->device_state[i]); } static void restore_snapshot(xyw_image_snapshot *snap)@@ -600,17 +571,15 @@ *xw = snap->xw_val;
*yw = snap->yw_val; error_handler_entry_s = snap->error_handler_entry_s; xyw_argv_processed = snap->argv_processed; - arg_index = snap->arg_index; - arg_char_pos = snap->arg_char_pos; - memcpy(subimage_args_buf, snap->subimage_args_buf, sizeof(subimage_args_buf)); - subimage_args_active = snap->subimage_args_active; - subimage_args_pos = snap->subimage_args_pos; - - // Devices with state outside xyw_memory (file cursor, clock timer target, - // beeper device) don't roll back automatically — re-sync to a clean slate + memcpy(xyw_exec_args, snap->exec_args, sizeof(xyw_exec_args)); + xyw_exec_args_active = snap->exec_args_active; for (int i = 0; i < XYW_TOTAL_DEVICES; i++) - if (xyw_devices[i].setup) + { + if (xyw_devices[i].restore) + xyw_devices[i].restore(xyw_devices[i].data, snap->device_state[i]); + else if (xyw_devices[i].setup) xyw_devices[i].setup(xyw_devices[i].data); + } } static void handle_exec_requests(void)@@ -659,107 +628,26 @@ return 1;
return 0; } -// Feed the next external event and dispatch its handler. +// Feed legacy event (timer only — terminal is handled by terminal_poll). // 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 (non-blocking: returns 0 on timeout) xyw_word on_keypress_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_KEYPRESS]); - - // Timer handling xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); - // Polling loop: keep checking until an event fires or exit is requested - while (on_keypress_handler || on_timer_handler) + if (on_timer_handler) { - if (on_keypress_handler) - { - int c = getch_timeout(POLL_INTERVAL_MS); - if (c > 0) - { - 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; - } - // c == 0: no key this cycle — fall through to timer check - } - - if (on_timer_handler) + // If keypress handler is also active, do non-blocking check + // (terminal_poll provides the sleep via getch_timeout) + int elapsed = on_keypress_handler + ? (readw(CLOCK_TIMER) == 0 && readb(CLOCK_ON_TIMER_ELAPSED)) + : readb(CLOCK_ON_TIMER_ELAPSED); // blocking: timer-only program + if (elapsed) { - int elapsed = on_keypress_handler - ? (readw(CLOCK_TIMER) == 0 && readb(CLOCK_ON_TIMER_ELAPSED)) - : readb(CLOCK_ON_TIMER_ELAPSED); // blocking: timer-only program - if (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; - } + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; + ss_push_frame(DEVICE_AREA_START); + *pc = on_timer_handler; + return 1; } }@@ -791,7 +679,26 @@ if (!system_running_state())
{ if (system_waiting_state()) { - if (feed_next_event()) + // Poll devices for events, then fall back to legacy handler. + // poll returns: 1=dispatched, 0=no active handler, -1=active but no event yet + int dispatched = 0; + while (system_waiting_state()) + { + int active = 0; + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + { + if (!xyw_devices[i].poll) continue; + int r = xyw_devices[i].poll(xyw_devices[i].data); + if (r > 0) { dispatched = 1; break; } + if (r < 0) active = 1; + } + if (dispatched) break; + // Legacy: timer handling (will be moved to clock_poll in step 4) + if (feed_next_event()) { dispatched = 1; break; } + // If no device is actively waiting, exit + if (!active) break; + } + if (dispatched) { continue; // handler was dispatched, go execute it }