all repos — xyw @ 6d231ac4e0e107fe363109c9f8f584cf7b1fd1a6

A minimal virtual machine and assembler for terminals.

Enhanced error reporting.
h3rald h3rald@h3rald.com
Thu, 30 Jul 2026 11:00:42 +0200
commit

6d231ac4e0e107fe363109c9f8f584cf7b1fd1a6

parent

318137f8fb9361cc206126381f7c23d87791b33b

7 files changed, 61 insertions(+), 26 deletions(-)

jump to
M CHANGELOG.mdCHANGELOG.md

@@ -2,9 +2,10 @@ ## Changelog

### v320-1 — TBD -#### New functionality +#### New Features - Now detecting if an audio backend is present. If not, beeper errors out when used. +- Enhanced error reporting: now printing a user-friendly debug message in case of every error and returning xyw error code as program error. #### Fixes
M devices/beeper.cdevices/beeper.c

@@ -1,3 +1,4 @@

+#include <stdio.h> #include <stdlib.h> #include <string.h> #include "../xyw.h"

@@ -92,6 +93,7 @@ {

(void)frequency_hz; (void)duration_ms; (void)volume; + XYW_DBG("Error: Beeper device not available\n"); *error = BEEPER_ERROR_DEVICE; }

@@ -110,6 +112,7 @@ size_t total = 44 + data_bytes;

unsigned char *wav = (unsigned char *)malloc(total); if (!wav) { + XYW_DBG("Error: Unable to allocate memory for beeper device\n"); *error = BEEPER_ERROR_MEMORY; return; }

@@ -134,6 +137,7 @@

// SND_SYNC: PlaySound doesn't return until the clip has finished. if (!PlaySoundA((LPCSTR)wav, NULL, SND_MEMORY | SND_SYNC)) { + XYW_DBG("Error: Unable to play beeper tone\n"); *error = BEEPER_ERROR_PLAYBACK; } free(wav);

@@ -150,6 +154,7 @@ size_t frames;

short *buf = generate_square_wave(frequency_hz, duration_ms, volume, &frames); if (!buf) { + XYW_DBG("Error: Unable to allocate memory for beeper device\n"); *error = BEEPER_ERROR_MEMORY; return; }

@@ -187,6 +192,7 @@

AudioQueueRef queue; if (AudioQueueNewOutput(&asbd, noop_callback, NULL, NULL, NULL, 0, &queue) != noErr) { + XYW_DBG("Error: Beeper device not available\n"); *error = BEEPER_ERROR_DEVICE; return; }

@@ -195,6 +201,7 @@ AudioQueueBufferRef buf;

size_t bytes = frames * sizeof(short); if (AudioQueueAllocateBuffer(queue, (UInt32)bytes, &buf) != noErr) { + XYW_DBG("Error: Beeper device not available\n"); *error = BEEPER_ERROR_DEVICE; AudioQueueDispose(queue, true); return;

@@ -205,6 +212,7 @@

if (AudioQueueEnqueueBuffer(queue, buf, 0, NULL) != noErr || AudioQueueStart(queue, NULL) != noErr) { + XYW_DBG("Error: Unable to play beeper tone\n"); *error = BEEPER_ERROR_PLAYBACK; AudioQueueDispose(queue, true); return;

@@ -226,6 +234,7 @@ size_t frames;

short *buf = generate_square_wave(frequency_hz, duration_ms, volume, &frames); if (!buf) { + XYW_DBG("Error: Unable to allocate memory for beeper device\n"); *error = BEEPER_ERROR_MEMORY; return; }

@@ -243,6 +252,7 @@ {

snd_pcm_t *pcm; if (snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) { + XYW_DBG("Error: Beeper device not available\n"); *error = BEEPER_ERROR_DEVICE; return; }

@@ -250,6 +260,7 @@

if (snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, sample_rate, 1, 500000) < 0) { + XYW_DBG("Error: Beeper device not available\n"); *error = BEEPER_ERROR_DEVICE; snd_pcm_close(pcm); return;

@@ -263,6 +274,7 @@ if (n < 0)

{ if (snd_pcm_recover(pcm, (int)n, 1) < 0) { + XYW_DBG("Error: Unable to play beeper tone\n"); *error = BEEPER_ERROR_PLAYBACK; break; }

@@ -285,6 +297,7 @@ size_t frames;

short *buf = generate_square_wave(frequency_hz, duration_ms, volume, &frames); if (!buf) { + XYW_DBG("Error: Unable to allocate memory for beeper device\n"); *error = BEEPER_ERROR_MEMORY; return; }

@@ -305,6 +318,7 @@ {

int fd = open("/dev/dsp", O_WRONLY); if (fd < 0) { + XYW_DBG("Error: Beeper device not available\n"); *error = BEEPER_ERROR_DEVICE; return; }

@@ -321,6 +335,7 @@ }

if (write(fd, samples, frames * sizeof(short)) < 0) { + XYW_DBG("Error: Unable to play beeper tone\n"); *error = BEEPER_ERROR_PLAYBACK; } ioctl(fd, SNDCTL_DSP_SYNC, 0); // blocks until the driver has drained the buffer

@@ -338,6 +353,7 @@ size_t frames;

short *buf = generate_square_wave(frequency_hz, duration_ms, volume, &frames); if (!buf) { + XYW_DBG("Error: Unable to allocate memory for beeper device\n"); *error = BEEPER_ERROR_MEMORY; return; }
M devices/file.cdevices/file.c

@@ -147,7 +147,7 @@ XYW_POKEW(&data[FILE_SUCCESS], 0);

if (path_addr == 0 || buffer_addr == 0 || length == 0) { - XYW_DBG(" Invalid parameters for file_read\n"); + XYW_DBG("Error: Invalid parameters for file_read\n"); *error = FILE_ERROR_INVALID_OP; return; }

@@ -182,7 +182,7 @@ hFind = FindFirstFileA(searchPath, &findData);

if (hFind == INVALID_HANDLE_VALUE) { - XYW_DBG(" Failed to open directory: %s\n", path); + XYW_DBG("Error: Unable to access directory '%s'\n", path); *error = FILE_ERROR_ACCESS_DENIED; return; }

@@ -237,7 +237,7 @@ char full_path[FILE_MAX_PATH * 2 + 2];

if (!dir) { - XYW_DBG(" Failed to open directory: %s\n", path); + XYW_DBG("Error: Unable to access directory '%s'\n", path); *error = FILE_ERROR_ACCESS_DENIED; return; }

@@ -314,7 +314,7 @@ file_close();

file_handle = fopen(path, "rb"); if (!file_handle) { - XYW_DBG(" Failed to open file: %s\n", path); + XYW_DBG("Error: Failed to open file '%s'\n", path); *error = FILE_ERROR_NOT_FOUND; return; }

@@ -327,7 +327,7 @@

// Seek to cursor position, only if not already there if (ftell(file_handle) != file_cursor && fseek(file_handle, file_cursor, SEEK_SET) != 0) { - XYW_DBG(" Failed to seek in file: %s\n", path); + XYW_DBG("Error: Failed to seek in file '%s'\n", path); *error = FILE_ERROR_IO_ERROR; return; }

@@ -361,7 +361,7 @@ XYW_POKEW(&data[FILE_SUCCESS], 0);

if (path_addr == 0 || buffer_addr == 0 || length == 0) { - XYW_DBG(" Invalid parameters for file_write\n"); + XYW_DBG("Error: Invalid parameters for file_write\n"); *error = FILE_ERROR_INVALID_OP; return; }

@@ -407,7 +407,7 @@ }

if (!file_handle) { - XYW_DBG(" Failed to open file for writing: %s (errno=%d: %s)\n", path, errno, strerror(errno)); + XYW_DBG("Error: Failed to open file '%s' for writing (errno=%d: %s)\n", path, errno, strerror(errno)); *error = FILE_ERROR_ACCESS_DENIED; return; }

@@ -473,7 +473,7 @@ XYW_POKEW(&data[FILE_SUCCESS], 0);

if (path_addr == 0) { - XYW_DBG(" Invalid path address for file_delete\n"); + XYW_DBG("Error: Invalid path address for file_delete\n"); *error = FILE_ERROR_INVALID_OP; return; }

@@ -494,7 +494,7 @@ XYW_DBG(" Deleting file: %s\n", path);

if (remove(path) != 0) { - XYW_DBG(" Failed to delete file: %s\n", path); + XYW_DBG("Error: Failed to delete file '%s'\n", path); *error = FILE_ERROR_ACCESS_DENIED; return; }

@@ -513,7 +513,9 @@ xyw_word path_addr = XYW_PEEKW(&data[FILE_PATH]);

char combined[FILE_MAX_PATH * 2]; if (path_addr == 0) + { return -1; + } if (get_path_string(path_addr, combined, sizeof(combined)) != 0) {

@@ -541,6 +543,7 @@ XYW_POKEW(&data[FILE_SUCCESS], 0);

if (split_path_pair(data, src, dst, FILE_MAX_PATH) != 0) { + XYW_DBG("Error: Incorrect path pair specified\n"); *error = FILE_ERROR_INVALID_OP; return; }

@@ -554,6 +557,7 @@

FILE *in = fopen(src, "rb"); if (!in) { + XYW_DBG("Error: Failed to open source file '%s'\n", src); *error = FILE_ERROR_NOT_FOUND; return; }

@@ -562,6 +566,7 @@ FILE *out = fopen(dst, "wb");

if (!out) { fclose(in); + XYW_DBG("Error: Failed to access destination file '%s'\n", dst); *error = FILE_ERROR_ACCESS_DENIED; return; }

@@ -596,6 +601,7 @@ XYW_POKEW(&data[FILE_SUCCESS], 0);

if (split_path_pair(data, src, dst, FILE_MAX_PATH) != 0) { + XYW_DBG("Error: Incorrect path pair specified\n"); *error = FILE_ERROR_INVALID_OP; return; }

@@ -676,6 +682,7 @@ break;

default: if (op != 0) { + XYW_DBG("Error: Invalid file operation code: %2X", op); *error = FILE_ERROR_INVALID_OP; } break;
M devices/system.cdevices/system.c

@@ -89,6 +89,7 @@ exec_pending = 0;

if (exec_stack_depth >= MAX_IMAGE_EXEC_DEPTH) { + XYW_DBG("Error: Maximum image execution depth exceeded\n"); data[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_EXEC_DEPTH_EXCEEDED; return; }
M devices/terminal.cdevices/terminal.c

@@ -162,13 +162,14 @@ struct termios raw;

if (!isatty(STDIN_FILENO)) { + XYW_DBG("Error: Terminal is not a TTY\n"); *error = TERMINAL_ERROR_NOT_A_TTY; return; } if (tcgetattr(STDIN_FILENO, &terminal_orig_termios) != 0) { - XYW_DBG("terminal: tcgetattr failed, raw mode not enabled\n"); + XYW_DBG("Error: tcgetattr failed, terminal raw mode not enabled\n"); *error = TERMINAL_ERROR_NOT_A_TTY; return; }
M xyw.cxyw.c

@@ -115,9 +115,10 @@ break;

} else if (ext && strcmp(ext, ".xim") == 0) { - if (xyw_run(input_file) != 0) + xyw_byte result = xyw_run(input_file); + if (result != 0) { - fprintf(stderr, "Execution failed for file: %s\n", input_file); + fprintf(stderr, "Execution failed for file: %s (Error: $%2X)\n", input_file, result); return -1; } break;
M xywrun.cxywrun.c

@@ -120,6 +120,7 @@ static void ss_push_frame(xyw_word return_addr)

{ if (s >= SYSTEM_MAX_FRAMES) { + XYW_DBG("Error: System stack overflow\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_SYSTEM_STACK_OVERFLOW; return; }

@@ -139,6 +140,7 @@ static xyw_word ss_pop_frame()

{ if (s < 1) { + XYW_DBG("Error: System stack underflow\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_SYSTEM_STACK_UNDERFLOW; return -1; }

@@ -156,7 +158,7 @@ }

//// Internal VM state -static int XYW_SYSTEM_ERROR_state() +static int system_error_state() { return xyw_memory[SYSTEM_STATE] & XYW_STATE_ERROR; }

@@ -195,7 +197,7 @@ }

int xyw_vm_is_running(void) { return system_running_state(); } int xyw_vm_is_waiting(void) { return system_waiting_state(); } -int xyw_vm_is_error_state(void) { return XYW_SYSTEM_ERROR_state(); } +int xyw_vm_is_error_state(void) { return system_error_state(); } void xyw_vm_reset(void) {

@@ -219,6 +221,7 @@ static void us_push(xyw_byte val)

{ if (u > XYW_USER_STACK_SIZE - 1) { + XYW_DBG("Error: User stack overflow\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_OVERFLOW; return; }

@@ -229,6 +232,7 @@ static void us_pushw(xyw_word val)

{ if (u > XYW_USER_STACK_SIZE - 2) { + XYW_DBG("Error: User stack overflow\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_OVERFLOW; return; }

@@ -241,6 +245,7 @@ static xyw_byte us_pop()

{ if (u < 1) { + XYW_DBG("Error: User stack underflow\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_UNDERFLOW; return -1; }

@@ -254,6 +259,7 @@ static xyw_word us_popw()

{ if (u < 2) { + XYW_DBG("Error: User stack underflow\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_UNDERFLOW; return -1; }

@@ -426,10 +432,10 @@ {

FILE *fp = fopen(path, "rb"); if (!fp) { - fprintf(stderr, "Error - Could not open file [%s]\n", path); + XYW_DBG("Error: Image file '%s' not found\n", path); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_NOT_FOUND; xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; - return -1; + return XYW_SYSTEM_ERROR_IMAGE_NOT_FOUND; } fseek(fp, 0, SEEK_END); long file_size = ftell(fp);

@@ -437,29 +443,29 @@ fseek(fp, 0, SEEK_SET);

if (file_size < 0) { - fprintf(stderr, "Error - Could not determine file size [%s]\n", path); + XYW_DBG("Error: Unable to determine size of image file '%s'\n", path); fclose(fp); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_READ_ERROR; xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; - return -1; + return XYW_SYSTEM_ERROR_IMAGE_READ_ERROR; } if ((size_t)file_size > XYW_MEMORY_SIZE) { - fprintf(stderr, "Error - Image exceeds %d-byte memory size [%s]\n", XYW_MEMORY_SIZE, path); + XYW_DBG("Error: Image file '%s' is too large (%ld bytes)\n", path, file_size); fclose(fp); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_TOO_LARGE; xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; - return -1; + return XYW_SYSTEM_ERROR_IMAGE_TOO_LARGE; } size_t bytes_read = fread(&xyw_memory[0x000], 1, XYW_MEMORY_SIZE - 0x000, fp); fclose(fp); if (bytes_read == 0) { - fprintf(stderr, "Error - Could not read file contents [%s]\n", path); + XYW_DBG("Error: Unable to read image file '%s'\n", path); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_READ_ERROR; xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; - return -1; + return XYW_SYSTEM_ERROR_IMAGE_READ_ERROR; } xyw_memory[SYSTEM_STATE] |= XYW_STATE_RUNNING;

@@ -624,7 +630,7 @@ /* SHR */ OPC_SR1r(0x0B, xyw_byte val = us_pop(); SETX(ARGX >> val); SETY(ARGY >> val); );

/* ADD */ OPC_SR2(0x0C, PUSH(a + b)); /* SUB */ OPC_SR2(0x0D, PUSH(a - b)); /* MUL */ OPC_SR2(0x0E, PUSH(a * b)); - /* DIV */ OPC_SR2(0x0F, if (b) { PUSH(a % b); PUSH(a / b); } else { xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_DIVISION_BY_ZERO; } ); + /* DIV */ OPC_SR2(0x0F, if (b) { PUSH(a % b); PUSH(a / b); } else { XYW_DBG("Error: Division by zero\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_DIVISION_BY_ZERO; } ); /* EQU */ OPC_SR2(0x10, us_push(a == b)); /* NEQ */ OPC_SR2(0x11, us_push(a != b)); /* GTH */ OPC_SR2(0x12, us_push(a > b));

@@ -646,6 +652,7 @@ /* RTS */ case 0x1F:

*pc = ss_pop_frame()-1; break; default: + XYW_DBG("Error: Invalid opcode\n"); xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_INVALID_OPCODE; break; // clang-format on

@@ -685,9 +692,10 @@ atexit(xyw_teardown_devices);

signal(SIGINT, xyw_signal_exit); signal(SIGTERM, xyw_signal_exit); - if (xyw_vm_load_image(input, NULL) != 0) + xyw_byte result = xyw_vm_load_image(input, NULL); + if (result != 0) { - return -1; + return result; } XYW_DBG("Loaded image into memory\n");