all repos — nand2tetris @ c52dc71dd10d530e038cbed1aa273c206fbd3326

my nand2tetris progress

Start assembler rewrite, misc. tidying
x1phosura x1phosura@x1phosura.zone
Thu, 02 Feb 2023 01:43:08 -0800
commit

c52dc71dd10d530e038cbed1aa273c206fbd3326

parent

92ae841904b31a66bacce132fe7d53a868c21c6b

M README.mdREADME.md

@@ -4,5 +4,5 @@ I've been going through nand2tetris for fun and self study.

---------------------------------------------------------------- -Note: I may have accidentally encountered some data loss of some select files form the last couple of months, including the previous project directory for my nand2tetris progress. As such, there will be a bunch of commits close together as I re-do chapters 1-4 from sketches I made on a notebook (except chapter 4, where I forgot to draw out the flowcharts for each assembly language program). +Note: I may have accidentally encountered some data loss of some select files from the last couple of months, including the previous project directory for my nand2tetris progress. As such, there will be a bunch of commits close together as I re-do chapters 1-4 from sketches I made on a notebook (except chapter 4, where I forgot to draw out the flowcharts for each assembly language program).
M projects/06/Makefileprojects/06/Makefile

@@ -1,11 +1,14 @@

CFLAGS = -std=c99 -Wall -Wextra -all: bin/assembler1 +all: bin/assembler1 bin/assembler2 # TODO: clean up, make more Makefile-idiomatic bin/assembler1: assembler1/assembler1.c + $(CC) $(CFLAGS) -o $@ $< + +bin/assembler2: assembler2/assembler2.c $(CC) $(CFLAGS) -o $@ $< clean:
M projects/06/assembler1/assembler1.cprojects/06/assembler1/assembler1.c

@@ -8,7 +8,8 @@ #define DBGLOG(...) printf(__VA_ARGS__)

//#define DBGLOG(...) #define error(...) fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "%lu | %s\n", \ - g_asm_line_number, g_asm_line); + g_asm_line_number, g_asm_line); \ + g_error_encountered = true; #define valid_symbol_char(c) (('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') \ || ('a' <= c && c <= 'z') || c == '_' || c == '.' \

@@ -19,6 +20,8 @@

#define MAX_SYMBOL_STR_LEN MAX_LINE_LEN - 2 #define RESERVED_LABEL_NUM 23 #define MAX_SYMBOLS 32768 + +bool g_error_encountered = false; char *g_asm_line; // currently-read line for convenience size_t g_asm_line_number; // current line number

@@ -67,13 +70,13 @@ }

printf("%s", binary_string); } -static uint32_t myatoi(const char *a_field_str) +static uint32_t myatoi(const char *str) { size_t i; uint32_t ret = 0; - for (i = 0; i < 5 && '0' <= a_field_str[i] && a_field_str[i] <= '9'; ++i) { - ret = (ret * 10) + (a_field_str[i] - 0x30); + for (i = 0; i < 5 && '0' <= str[i] && str[i] <= '9'; ++i) { + ret = (ret * 10) + (str[i] - 0x30); } return ret;

@@ -256,8 +259,8 @@ return false;

} if (line[label_len + 1] != ')') { - error("line[label_len = %lu] value is '%c'\n", label_len, line[label_len]) - error("syntax error: no matching ')' found for label"); + error("syntax error: no matching ')' found for label, found " + "value '%c' in label\n", line[label_len + 1]); return false; }

@@ -796,23 +799,22 @@

++file_line; } - - if (fclose(fp)) { - fprintf(stderr, "Error closing file %s. Aborting...\n", in_file_path); + // skip second pass if error found in first pass + if (g_error_encountered) { + fprintf(stderr, "error during first pass label parsing, " + "aborting...\n"); + exit(-1); } - // TODO: skip second pass if error found in first pass - - // TODO: keep file open, restart read from beginning - fp = fopen(in_file_path, "r"); - if (fp == NULL) { - fprintf(stderr, "failed to open file %s for reading\n", + if (fseek(fp, 0, SEEK_SET)) { + fprintf(stderr, "failed to re-read file %s from beginning\n", in_file_path); exit(-1); } file_line = 1; while (fgets(in_line, MAX_LINE_LEN, fp) != NULL) { // parse loop + fprintf(stderr, "line %lu: %s", file_line, in_line); for (i = 0; in_line[i] != '\0'; ++i) { // remove newlines if (in_line[i] == '\n' || in_line[i] == '\r') { in_line[i] = '\0';
A projects/06/assembler2/assembler2.c

@@ -0,0 +1,285 @@

+#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define DBGLOG printf + +#define valid_symbol_char(c) (('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') \ + || ('a' <= c && c <= 'z') || c == '_' || c == '.' \ + || c == '$' || c == ':') + +#define error(...) (fprintf(stderr, __VA_ARGS__), -1) + +#define MAX_LINE_LEN 256 // TODO: in/excludes NULL terminator? +#define MAX_SYMBOL_LEN MAX_LINE_LEN - 2 + 1 // + 1 for NULL terminator +#define MAX_SYMBOLS 32768 // TODO remove me + +char *g_asm_line; // currently-read line for convenience +size_t g_asm_line_number; // current line number +size_t g_instruction_number = 0; // instruction offset + +struct symbol_t { + char *symbolstr; + uint16_t value; +}; + +char rom[32768]; +size_t rom_index = 0; + +char symbol_strs[32768 + 23][MAX_SYMBOL_LEN]; // TODO: double-check 2D array +uint16_t symbol_vals[32768]; +uint16_t symbol_index; + +#define RESERVED_LABEL_NUM 23 // TODO: remove me + +// label and variable symbols will effectively be treated the same; the only +// difference is in their use (in a hack program) +struct symbol_t *g_symbol_list[32768 + RESERVED_LABEL_NUM]; +size_t g_symbol_list_len = 0; +size_t g_current_variable_address = 16; // address for next variable symbol + + +void print_binary_word16(uint16_t w) +{ + unsigned char i, msb; + char binary_string[17]; + binary_string[16] = '\0'; + + for (i = 0; i < 16; ++i) { + msb = (char)(w >> 15); + + if (msb != 0) binary_string[i] = '1'; + else binary_string[i] = '0'; + w <<= 1; + } + printf("%s", binary_string); +} + +static uint32_t myatoi(const char *str) +{ + size_t i; + uint32_t ret = 0; + + for (i = 0; i < 5 && '0' <= str[i] && str[i] <= '9'; ++i) { + ret = (ret * 10) + (str[i] - 0x30); + } + + return ret; +} + +size_t skip_whitespace(const char *line, size_t n) { + size_t i = 0; + char c = line[i]; + + for (i; (c == ' ' || c == '\t') && c != '\0' && i < n; ++i); + return i; // TODO double-check above loop logic +} + +// TODO: will still need to handle multi-line-comments +bool is_single_line_comment(const char *line) { + size_t i; + + i = skip_whitespace(line, MAX_LINE_LEN); + if (line[i] == '/') + if (line[i + 1] == '/' && !(i >= MAX_LINE_LEN)) + return true; + return false; +} + +// TODO FIX! +void debug_dump_all_symbols() +{ + size_t i; + + DBGLOG("-------- DEBUG SYMBOL DUMP --------\n"); + for (i = 0; i < symbol_index; ++i) { + DBGLOG("symbol (%s, %hu)\n", symbol_strs[i], symbol_vals[i]); + } + DBGLOG("-------- END SYMBOL DUMP --------\n"); +} + +bool lookup_symbol(const char *str, uint16_t *val) +{ + size_t i; + + for (i = 0; i < sizeof(symbol_strs) / sizeof(symbol_strs[0]); ++i) { + if (strncmp(symbol_strs[i], str, MAX_SYMBOL_LEN) == 0) { + *val = symbol_vals[i]; + return true; + } + } + + return false; +} + +bool add_symbol(const char *str, uint16_t val) +{ + size_t i; + uint16_t tmp; + + if (symbol_index > 32767) { + // TODO: print appropriate error message + return false; + } + + if (lookup_symbol(str, &tmp)) { + // TODO: print appropriate error message + return false; + } + + strncpy(symbol_strs[symbol_index], str, MAX_SYMBOL_LEN); + symbol_vals[symbol_index] = val; + ++symbol_index; + + return true; +} + +// pre-fill symbol lists with 'reserved' symbols and values +bool init_symbol_list() +{ + size_t i; + + char *reserved_strs[] = {"R0", "R1", "R2", "R3", "R4", "R5", + "R6", "R7", "R8", "R9", "R10", "R11", + "R12", "R13", "R14", "R15", "SP", "LCL", + "ARG", "THIS", "THAT", "SCREEN", "KBD"}; + uint16_t reserved_vals[] = {0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 0, 1, + 2, 3, 4, 0x4000, 0x6000}; + + for (i = 0; i < sizeof(reserved_strs) / sizeof(reserved_strs[0]); ++i) { + if (!add_symbol(reserved_strs[i], reserved_vals[i])) + return false; + } + + return true; +} + +bool pass2(FILE *in_file) +{ + bool result = false; + uint16_t instruction; + char in_line[MAX_LINE_LEN]; + size_t i, file_line, in_line_len; + + file_line = 1; + while (fgets(in_line, MAX_LINE_LEN, in_file) != NULL) { // parse loop + for (i = 0; in_line[i] != '\0'; ++i) { // remove newlines + if (in_line[i] == '\n' || in_line[i] == '\r') { + in_line[i] = '\0'; + break; + } + ++in_line_len; + } + + if (in_line_len == 0 || in_line_len == 1) + continue; + + g_asm_line = in_line; + g_asm_line_number = file_line; + + printf("line %lu: %s\n", file_line, in_line); + result = true; + /* + result = parse_line(in_line, &instruction); + + if (result) { + print_binary_word16(instruction); + putchar('\n'); + //TODO: put instruction in ROM + } + */ + + ++file_line; + } + + return result; +} + +bool pass1(FILE *in_file) +{ + bool result = false; + uint16_t instruction; + char in_line[MAX_LINE_LEN]; + size_t i, file_line, in_line_len; + + if (!init_symbol_list()) { + return false; + } + + file_line = 1; + // first pass to read labels and associate with values + while (fgets(in_line, MAX_LINE_LEN, in_file) != NULL) { // parse loop + for (i = 0; in_line[i] != '\0'; ++i) { // remove newlines + if (in_line[i] == '\n' || in_line[i] == '\r') { + in_line[i] = '\0'; + break; + } + ++in_line_len; + } + + if (in_line_len == 0 || in_line_len == 1) + continue; + + if (in_line_len >= 2) + if (in_line[0] == '/' && in_line[1] == '/') + continue; // comment found + + g_asm_line = in_line; + g_asm_line_number = file_line; + printf("line %lu. If this was real, it would call " + "parse_line_for_label()\n", file_line); + result = true; + ////result = parse_line_for_label(in_line); + //if (result) + // DBGLOG("DEBUG: label found in line %s\n", in_line); + + ++file_line; + } + + debug_dump_all_symbols(); + + return result; +} + + +char *usage_msg = "Usage: assembler1 [path/to/file.asm]\n"; + +int main(int argc, char *argv[]) +{ + FILE *in_file, *out_file; + char *in_file_path, *out_file_path; + if (argc != 3) // requires 2 arguments + return error(usage_msg); // TODO: eventually support STDOUT + + in_file_path = argv[1]; + out_file_path = argv[2]; + + in_file = fopen(in_file_path, "r"); + if (in_file == NULL) + return error("failed to open assembly file for reading\n"); + + if(!pass1(in_file)) + return error("failed to parse labels/variables in file\n"); + + if (fseek(in_file, 0, SEEK_SET)) + return error("failed to re-read file from beginning\n"); + + if(!pass2(in_file)) + return error("failed to parse assembly in file\n"); + + out_file = fopen(out_file_path, "wb"); + if (out_file == NULL) + return error("failed to open output file for writing\n"); + + //fwrite(assembled, sizeof(assembled), 1, out_fp); + if (fclose(in_file)) + return error("failed to close assembly file\n"); + if (fclose(out_file)) + return error("failed to close output file\n"); + + return 0; +}