Implementing assembler.
h3rald h3rald@h3rald.com
Tue, 25 Nov 2025 12:04:22 +0100
5 files changed,
488 insertions(+),
3 deletions(-)
M
Makefile
→
Makefile
@@ -1,14 +1,19 @@
CC = gcc CFLAGS = -Wall -Wextra -g LDFLAGS = +SOURCES = xyw.c xywasm.c +OBJS = xyw.o xywasm.o .PHONY: clean -xyw: xyw.o - $(CC) $(CFLAGS) $(LDFLAGS) xyw.o -o xyw +xyw: $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o xyw xyw.o: xyw.c xyw.h $(CC) $(CFLAGS) -c xyw.c -o xyw.o +xywasm.o: xywasm.c + $(CC) $(CFLAGS) -c xywasm.c -o xywasm.o + clean: - rm -f xyw xyw.exe xyw.o + rm -f xyw xyw.exe $(OBJS)
M
xyw.c
→
xyw.c
@@ -1,4 +1,5 @@
#include <stdio.h> +#include <string.h> #include "xyw.h" #define MEMORY_SIZE 0xffff@@ -109,4 +110,24 @@ int main(int argc, char *argv[])
{ (void)argc; (void)argv; + // If first argument is a file ending in .xyw, assemble it + if (argc >= 2) + { + const char *input_file = argv[1]; + // Output file is the same as input but with .rom extension + char output_file[256]; + snprintf(output_file, sizeof(output_file), "%.*s.rom", (int)(strlen(input_file) - 4), input_file); + if (xyw_assemble(input_file, output_file) != 0) + { + // TODO: Standardize errors + fprintf(stderr, "Assembly failed for file: %s\n", input_file); + return -1; + } + printf("Assembled %s to %s successfully.\n", input_file, output_file); + } + else + { + fprintf(stderr, "Usage: %s <input_file.xyw>\n", argv[0]); + return -1; + } }
A
xywasm.c
@@ -0,0 +1,456 @@
+#include <stdio.h> +#include "xyw.h" + +#define TOKEN_MAX_LENGTH 32 +#define DIRECTIVE_MAX_LENGTH 256 +#define COMMENT_MAX_LENGTH 256 + +#define MODE_X 0x20 +#define MODE_Y 0x40 +#define MODE_W 0x80 + +#define error(msg) fprintf(stderr, "Error - %s [File: %s]\n", msg, ctx->file) +#define token_error(msg) fprintf(stderr, "Error - %s: %s [File: %s, Line: %d, Column %d: ]\n", token, msg, ctx->file, ctx->line, ctx->column) + +typedef struct +{ + int line; + int column; + char *file; + FILE *fp; +} xyw_context; + +// Instruction mnemonics +static char instructions[][4] = { + "BRK", "PSH", "POP", "NOT", + "LDR", "STR", "LDD", "STD", + "ILD", "LDI", "DLD", "LDD", + "ADD", "SUB", "MUL", "DIV", + "EQU", "NEQ", "GTH", "LTH", + "AND", "IOR", "XOR", "SFT", + "SWP", "DUP", "OVR", "ROT", + "JMP", "JCN", "JSR", "RTS"}; + +// The token being processed +static char token[TOKEN_MAX_LENGTH]; +static unsigned int token_length = 0; + +// Bytecode to be written +xyw_byte data[0xffff - 0x0100]; + +//// Utilities + +static int strcmpn(const char *s1, const char *s2, unsigned int n) +{ + for (unsigned int i = 0; i < n; i++) + { + if (s1[i] != s2[i]) + { + return s1[i] - s2[i]; + } + if (s1[i] == '\0') + { + // Matches up to specified length + return 0; + } + } + return 0; +} + +void byte_to_binary(xyw_byte byte, char *str) +{ + int i; + for (i = 7; i >= 0; i--) + { + str[7 - i] = ((byte >> i) & 1) ? '1' : '0'; + } + str[8] = '\0'; +} + +//// Parsing functions + +static int parse_comment(xyw_context *ctx) +{ + char c; + char comment_data[COMMENT_MAX_LENGTH]; + unsigned int comment_length = 0; + // Process single-line comment + while (fread(&c, 1, 1, ctx->fp) == 1) + { + if (c == '\n') + { + ctx->line++; + ctx->column = 1; + break; + } + ctx->column++; + if (comment_length < COMMENT_MAX_LENGTH - 1) + { + comment_data[comment_length++] = c; + comment_data[comment_length] = '\0'; + } + else + { + token_error("Comment too long"); + return -1; + } + } + // TODO: Remove + printf("Comment:\t%s%s\n", token, comment_data); + return 0; +} + +static int parse_directive(xyw_context *ctx) +{ + // Parse labels starting with . until end of line + char c; + char directive_data[DIRECTIVE_MAX_LENGTH]; + unsigned int directive_length = 0; + while (fread(&c, 1, 1, ctx->fp) == 1) + { + if (c == '\n') + { + ctx->line++; + ctx->column = 1; + break; + } + else + { + if (directive_length < DIRECTIVE_MAX_LENGTH - 1) + { + directive_data[directive_length++] = c; + directive_data[directive_length] = '\0'; + } + else + { + token_error("Directive too long"); + return -1; + } + } + ctx->column++; + } + // TODO: Remove + printf("Directive:\t%s %s\n", token, directive_data); + // TODO: Process directive + return 0; +} + +static int parse_hex_number(xyw_context *ctx) +{ + // Token is a hex number starting with $ + xyw_short value = 0; + for (unsigned int i = 1; i < token_length; i++) + { + char c = token[i]; + ctx->column++; + value <<= 4; + if (c >= '0' && c <= '9') + { + value |= (c - '0'); + } + else if (c >= 'A' && c <= 'F') + { + value |= (c - 'A' + 10); + } + else if (c >= 'a' && c <= 'f') + { + value |= (c - 'a' + 10); + } + else + { + token_error("Invalid hex digit"); + return -1; + } + } + // TODO: Remove + printf("Hex number:\t%s (%04X)\n", token, value); + // TODO: Store value + return 0; +} + +static int parse_binary_number(xyw_context *ctx) +{ + // Token is a binary number starting with % + xyw_short value = 0; + for (unsigned int i = 1; i < token_length; i++) + { + char c = token[i]; + ctx->column++; + value <<= 1; + if (c == '0') + { + // do nothing + } + else if (c == '1') + { + value |= 1; + } + else + { + token_error("Invalid binary digit"); + return -1; + } + } + // TODO: Remove + printf("Binary number:\t%s (%04X)\n", token, value); + // TODO: Store value + return 0; +} + +static int parse_decimal_number(xyw_context *ctx) +{ + // Token is a decimal number (may or may not start with -) + xyw_short value = 0; + unsigned int start_index = 0; + int is_negative = 0; + if (token[0] == '-') + { + is_negative = 1; + start_index = 1; + } + for (unsigned int i = start_index; i < token_length; i++) + { + char c = token[i]; + ctx->column++; + if (c >= '0' && c <= '9') + { + value = value * 10 + (c - '0'); + } + else + { + token_error("Invalid decimal digit"); + return -1; + } + } + if (is_negative) + { + value = -value; + } + // TODO: Remove + printf("Decimal number:\t%s (%04X)\n", token, value); + // TODO: Store value + return 0; +} + +static int parse_instruction(xyw_context *ctx) +{ + xyw_byte opcode = 0; + // Match token against instruction mnemonics + for (unsigned int i = 0; i < sizeof(instructions) / sizeof(instructions[0]); i++) + { + if (strcmpn(token, instructions[i], 3) == 0) + { + opcode = (xyw_byte)i; + ctx->column += 3; + // There could be 0 to 3 modes (x y z) after the mnemonic in any order + if (token_length > 3) + { + for (unsigned int j = 3; j < token_length; j++) + { + char mode_char = token[j]; + ctx->column++; + int mode_found = 0; + if (mode_char == 'x' || mode_char == 'X') + { + opcode |= MODE_X; + mode_found = 1; + } + else if (mode_char == 'y' || mode_char == 'Y') + { + opcode |= MODE_Y; + mode_found = 1; + } + else if (mode_char == 'w' || mode_char == 'W') + { + opcode |= MODE_W; + mode_found = 1; + } + if (!mode_found) + { + token_error("Invalid mode character"); + return -1; + } + } + } + // TODO: Remove + char bits[9]; + byte_to_binary(opcode, bits); + printf("Instruction:\t%s\t(%02X - %s)\n", token, opcode, bits); + // TODO: Store opcode + return 0; + } + } + return -1; +} + +static int parse_symbol(xyw_context *ctx) +{ + // Symbols must start with a letter or underscore, + // and they can contain letters, numbers, underscores, dashes, or dots. + if (!((token[0] >= 'A' && token[0] <= 'Z') || (token[0] >= 'a' && token[0] <= 'z') || token[0] == '_')) + { + token_error("Invalid symbol start character"); + return -1; + } + for (unsigned int i = 1; i < token_length; i++) + { + char c = token[i]; + ctx->column++; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.')) + { + token_error("Invalid symbol character"); + return -1; + } + } + // TODO: Remove + printf("Symbol:\t\t%s\n", token); + // TODO: Process symbol + return 0; +} + +static int parse_token(xyw_context *ctx) +{ + if (token[0] == ';') + { + return parse_comment(ctx); + } + else if (token[0] == '.') + { + return parse_directive(ctx); + } + else if (token[0] == '$') + { + return parse_hex_number(ctx); + } + else if (token[0] == '%') + { + return parse_binary_number(ctx); + } + else if ((token[0] >= '0' && token[0] <= '9') || token[0] == '-') + { + return parse_decimal_number(ctx); + } + else if ((token[0] >= 'A' && token[0] <= 'Z') || (token[0] >= 'a' && token[0] <= 'z') || token[0] == '_') + { + if (parse_instruction(ctx) == 0) + { + return 0; + } + return parse_symbol(ctx); + } + token_error("Unrecognized token"); + return -1; +} + +static int process_file(xyw_context *ctx) +{ + ctx->fp = fopen(ctx->file, "r"); + if (!ctx->fp) + { + error("Could not open file"); + return -1; + } + + unsigned char c; + while (fread(&c, 1, 1, ctx->fp) == 1) + { + if (c > 0x20 && c != 0x7f) // printable non-space + { + if (token_length < TOKEN_MAX_LENGTH - 1) + { + token[token_length++] = (char)c; + token[token_length] = '\0'; + ctx->column++; + } + else + { + token_error("Token too long"); + fclose(ctx->fp); + return -1; + } + } + else if (c == ' ' || c == '\t') + { + // whitespace separator: finalize token if present + if (token_length > 0) + { + if (parse_token(ctx) != 0) + { + fclose(ctx->fp); + return -1; + } + token_length = 0; + token[0] = '\0'; + } + ctx->column++; + } + else if (c == '\n') + { + if (token_length > 0) + { + if (parse_token(ctx) != 0) + { + fclose(ctx->fp); + return -1; + } + token_length = 0; + token[0] = '\0'; + } + ctx->line++; + ctx->column = 1; + } + else if (c == '\r') + { + // Treat CR as line break (handle CRLF gracefully) + if (token_length > 0) + { + if (parse_token(ctx) != 0) + { + fclose(ctx->fp); + return -1; + } + token_length = 0; + token[0] = '\0'; + } + ctx->line++; + ctx->column = 1; + } + else + { + token_error("Invalid character encountered"); + fclose(ctx->fp); + return -1; + } + } + + // Final token at EOF + if (token_length > 0) + { + if (parse_token(ctx) != 0) + { + fclose(ctx->fp); + return -1; + } + } + + fclose(ctx->fp); + return 0; +} + +///// Public API + +int xyw_assemble(const char *input, const char *output) +{ + xyw_context ctx; + ctx.file = (char *)input; + ctx.line = 1; + ctx.column = 1; + + if (process_file(&ctx) != 0) + { + return -1; + } + + (void)output; // TODO: write to output file + return 0; +}