From 9d47486aac9cd1a4422b78ee273b7a27bf7b733a Mon Sep 17 00:00:00 2001 From: hachem Date: Tue, 8 Sep 2026 12:00:49 +0200 Subject: refactor: flatten src and migrate to meson --- .gitignore | 11 +- Dockerfile | 11 +- README.md | 25 +- meson.build | 79 +++ premake5.lua | 178 ------- scripts/run.sh | 6 +- scripts/run_suite.sh | 6 +- scripts/test_examples.sh | 4 +- src/args.c | 119 +++++ src/args.h | 26 + src/ast.c | 265 ++++++++++ src/ast.h | 245 +++++++++ src/cli/args.c | 119 ----- src/cli/args.h | 26 - src/codegen/nasm.c | 1252 ---------------------------------------------- src/codegen/nasm.h | 7 - src/diag.c | 63 +++ src/diag.h | 12 + src/diag/diag.c | 63 --- src/diag/diag.h | 12 - src/file.c | 69 +++ src/file.h | 13 + src/io/file.c | 69 --- src/io/file.h | 13 - src/lexer.c | 311 ++++++++++++ src/lexer.h | 82 +++ src/lexer/lexer.c | 311 ------------ src/lexer/lexer.h | 82 --- src/main.c | 14 +- src/nasm.c | 1252 ++++++++++++++++++++++++++++++++++++++++++++++ src/nasm.h | 7 + src/parser.c | 787 +++++++++++++++++++++++++++++ src/parser.h | 8 + src/parser/ast.c | 265 ---------- src/parser/ast.h | 245 --------- src/parser/parser.c | 787 ----------------------------- src/parser/parser.h | 8 - src/sema.c | 527 +++++++++++++++++++ src/sema.h | 8 + src/sema/sema.c | 527 ------------------- src/sema/sema.h | 8 - tests/codegen_test.c | 4 +- tests/lexer_test.c | 2 +- tests/parser_test.c | 2 +- tests/sema_test.c | 4 +- 45 files changed, 3913 insertions(+), 4021 deletions(-) create mode 100644 meson.build delete mode 100644 premake5.lua create mode 100644 src/args.c create mode 100644 src/args.h create mode 100644 src/ast.c create mode 100644 src/ast.h delete mode 100644 src/cli/args.c delete mode 100644 src/cli/args.h delete mode 100644 src/codegen/nasm.c delete mode 100644 src/codegen/nasm.h create mode 100644 src/diag.c create mode 100644 src/diag.h delete mode 100644 src/diag/diag.c delete mode 100644 src/diag/diag.h create mode 100644 src/file.c create mode 100644 src/file.h delete mode 100644 src/io/file.c delete mode 100644 src/io/file.h create mode 100644 src/lexer.c create mode 100644 src/lexer.h delete mode 100644 src/lexer/lexer.c delete mode 100644 src/lexer/lexer.h create mode 100644 src/nasm.c create mode 100644 src/nasm.h create mode 100644 src/parser.c create mode 100644 src/parser.h delete mode 100644 src/parser/ast.c delete mode 100644 src/parser/ast.h delete mode 100644 src/parser/parser.c delete mode 100644 src/parser/parser.h create mode 100644 src/sema.c create mode 100644 src/sema.h delete mode 100644 src/sema/sema.c delete mode 100644 src/sema/sema.h diff --git a/.gitignore b/.gitignore index 843c7fa..a850c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,8 @@ -bin/ -bin-int/ - -Makefile -*.make -*.sln -*.vcxproj -*.vcxproj.* +build/ +build-*/ compile_commands.json compile_flags.txt .clangd .DS_Store - diff --git a/Dockerfile b/Dockerfile index 2fccad9..e8f37e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,11 @@ FROM --platform=linux/amd64 debian:trixie-slim -ARG PREMAKE_VERSION=5.0.0-beta8 - RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ nasm \ binutils \ - make \ - curl \ - ca-certificates \ + meson \ + ninja-build \ && rm -rf /var/lib/apt/lists/* -RUN curl -fsSL "https://github.com/premake/premake-core/releases/download/v${PREMAKE_VERSION}/premake-${PREMAKE_VERSION}-linux.tar.gz" \ - | tar -xz -C /usr/local/bin \ - && chmod +x /usr/local/bin/premake5 - WORKDIR /hdass diff --git a/README.md b/README.md index 53e0579..c3e942e 100644 --- a/README.md +++ b/README.md @@ -37,18 +37,23 @@ The program still directly controls the registers used for the system calls. Not The full language reference, shit like directives, declarations, statements, expressions, registers and extensions, is explained in [docs/language.md](docs/language.md). ## Building -The build is driven by [premake5](https://premake.github.io/). Generate the makefiles and build the compiler: +The build is driven by [Meson](https://mesonbuild.com/). Configure a build directory and compile the compiler: ```bash -premake5 gmake -make config=debug +meson setup build +meson compile -C build ``` -This produces the `hdass` binary at `bin/-/hdass` (for example `bin/debug-macosx/hdass` or `bin/debug-linux/hdass`). The available configurations are `debug`, `release` and `dist`. To run the unit tests: +This produces the `hdass` binary at `build/hdass`. The default build turns on the address and undefined-behaviour sanitizers; for an optimised build without them, configure a separate directory: ```bash -make config=debug -./bin/-/tests +meson setup build-release --buildtype=release -Db_sanitize=none +meson compile -C build-release ``` +To run the unit tests: +```bash +meson test -C build +``` +If [cppcheck](https://cppcheck.sourceforge.io/) is installed, `ninja -C build cppcheck` runs static analysis over the sources. -hdass emits x86-64 assembly, so to actually assemble and run its output you need an x86-64 Linux toolchain. The bundled Docker environment provides a consistent one on any host, including Apple Silicon, where the amd64 image runs under emulation. The image is a Debian base with `nasm`, `ld` (binutils), a C toolchain and `premake5` preinstalled. +hdass emits x86-64 assembly, so to actually assemble and run its output you need an x86-64 Linux toolchain. The bundled Docker environment provides a consistent one on any host, including Apple Silicon, where the amd64 image runs under emulation. The image is a Debian base with `nasm`, `ld` (binutils), a C toolchain and Meson/Ninja preinstalled. Start the container (this builds the image the first time): ```bash @@ -62,8 +67,8 @@ docker compose exec hdass bash From inside the container you can build the compiler and take a program all the way to a running executable: ```bash -premake5 gmake && make config=debug -./bin/debug-linux/hdass examples/hello_world.hdass -o hello.asm +meson setup build-linux && meson compile -C build-linux +./build-linux/hdass examples/hello_world.hdass -o hello.asm nasm -f elf64 hello.asm -o hello.o ld -e main hello.o -o hello ./hello @@ -71,7 +76,7 @@ ld -e main hello.o -o hello To transpile, assemble, link and run every program in [examples/](examples/) and check its output, use the end-to-end test script (also from inside the container): ```bash -make config=debug && ./scripts/test_examples.sh +meson setup build-linux && meson compile -C build-linux && ./scripts/test_examples.sh ``` Or, from the host, run the whole suite (unit tests plus the example tests) in one shot, bringing the container up if needed: diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..b445e94 --- /dev/null +++ b/meson.build @@ -0,0 +1,79 @@ +project( + 'hdass', + 'c', + version: '0.1.0', + license: 'MIT', + meson_version: '>=1.0.0', + default_options: [ + 'c_std=c17', + 'warning_level=3', + 'b_sanitize=address,undefined', + 'b_lundef=false', + ], +) + +cc = meson.get_compiler('c') + +add_project_arguments( + cc.get_supported_arguments( + '-Wconversion', + '-Wsign-conversion', + '-Wshadow', + '-Wstrict-prototypes', + '-Wmissing-prototypes', + '-fno-omit-frame-pointer', + ), + language: 'c', +) + +src_inc = include_directories('src') + +core = static_library( + 'hdass_core', + files( + 'src/args.c', + 'src/ast.c', + 'src/diag.c', + 'src/file.c', + 'src/lexer.c', + 'src/nasm.c', + 'src/parser.c', + 'src/sema.c', + ), + include_directories: src_inc, +) + +executable( + 'hdass', + 'src/main.c', + include_directories: src_inc, + link_with: core, +) + +tests = executable( + 'tests', + files( + 'tests/main.c', + 'tests/codegen_test.c', + 'tests/lexer_test.c', + 'tests/parser_test.c', + 'tests/sema_test.c', + ), + include_directories: [src_inc, include_directories('tests')], + link_with: core, +) + +test('unit', tests) + +cppcheck = find_program('cppcheck', required: false) +if cppcheck.found() + run_target( + 'cppcheck', + command: [ + cppcheck, + '--project=@0@'.format(meson.project_build_root() / 'compile_commands.json'), + '--enable=warning,style,performance,portability', + '--error-exitcode=1', + ], + ) +endif diff --git a/premake5.lua b/premake5.lua deleted file mode 100644 index 178c730..0000000 --- a/premake5.lua +++ /dev/null @@ -1,178 +0,0 @@ -output_dir = "%{cfg.buildcfg}-%{cfg.system}" - -function setup_target() - targetdir ("bin/" .. output_dir) - objdir ("bin-int/" .. output_dir .. "/%{prj.name}") - staticruntime "On" -end - -function setup_c_target() - setup_target() - - filter { "system:macosx or system:linux" } - buildoptions { - "-Wall", - "-Wextra", - "-Wpedantic", - "-Wconversion", - "-Wsign-conversion", - "-Wshadow", - "-Wstrict-prototypes", - "-Wmissing-prototypes", - } - filter {} - - filter "configurations:debug" - runtime "Debug" - symbols "On" - - filter { "system:macosx or system:linux" } - buildoptions { - "-g", - "-fno-omit-frame-pointer", - "-fsanitize=address,undefined", - } - - linkoptions { - "-fsanitize=address,undefined", - } - filter {} - - filter { "configurations:release", "configurations:dist" } - runtime "Release" - optimize "Speed" - filter {} - - filter "configurations:dist" - symbols "Off" - filter {} -end - -newaction { - trigger = "clean", - description = "Remove build output and generated project files", - - execute = function() - os.rmdir("bin") - os.rmdir("bin-int") - os.remove("Makefile") - - for _, file in ipairs(os.matchfiles("*.make")) do - os.remove(file) - end - - print("Cleaned build output and generated project files") - end -} - -newaction { - trigger = "check", - description = "Run static analysis with cppcheck", - - execute = function() - local result = os.execute("make clean") - - if result ~= true and result ~= 0 then - error("Failed to clean project") - end - - result = os.execute( - "bear -- make config=debug" - ) - - if result ~= true and result ~= 0 then - error("Failed to generate compile_commands.json") - end - - result = os.execute( - "cppcheck " .. - "--project=compile_commands.json " .. - "--file-filter=src/** " .. - "--file-filter=tests/** " .. - "--enable=warning,style,performance,portability " .. - "--error-exitcode=1" - ) - - if result ~= true and result ~= 0 then - error("cppcheck found issues") - end - end -} - -workspace "hdass" - architecture "x64" - startproject "hdass" - multiprocessorcompile "On" - - configurations { - "debug", - "release", - "dist", - } - - filter "system:windows" - defines "HDASS_WINDOWS" - - filter "system:linux" - defines "HDASS_LINUX" - - filter "system:macosx" - architecture "ARM64" - defines "HDASS_MACOS" - - filter {} - -project "hdass" - kind "ConsoleApp" - language "C" - cdialect "C17" - - setup_c_target() - - files { - "src/**.h", - "src/**.c", - } - - includedirs "src" - - filter "system:windows" - systemversion "latest" - - filter "configurations:debug" - defines "HDASS_DEBUG" - - filter "configurations:release" - defines "HDASS_RELEASE" - - filter "configurations:dist" - defines "HDASS_DIST" - - filter {} - -project "tests" - kind "ConsoleApp" - language "C" - cdialect "C17" - - setup_c_target() - - files { - "src/**.h", - "src/**.c", - "tests/**.h", - "tests/**.c", - } - - removefiles "src/main.c" - - includedirs { - "src", - "tests", - } - - filter "system:windows" - systemversion "latest" - - filter {} - diff --git a/scripts/run.sh b/scripts/run.sh index 01d3882..2bdb450 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -17,9 +17,9 @@ docker compose up -d >/dev/null docker compose exec -T -e SRC="$file" -e NAME="$name" hdass bash -c ' set -e cd /hdass - premake5 gmake >/dev/null - make config=debug >/dev/null - ./bin/debug-linux/hdass "$SRC" -o "/tmp/$NAME.asm" + [ -d build-linux ] || meson setup build-linux >/dev/null + meson compile -C build-linux >/dev/null + ./build-linux/hdass "$SRC" -o "/tmp/$NAME.asm" echo cat "/tmp/$NAME.asm" echo diff --git a/scripts/run_suite.sh b/scripts/run_suite.sh index 48cf67f..38598ad 100755 --- a/scripts/run_suite.sh +++ b/scripts/run_suite.sh @@ -10,7 +10,7 @@ else fi printf '%s━━ building compiler ━━%s\n' "$bold" "$reset" -if ! build=$(premake5 gmake 2>&1 && make config=debug 2>&1); then +if ! build=$({ [ -d build-linux ] || meson setup build-linux; } 2>&1 && meson compile -C build-linux 2>&1); then printf '%s\n' "$build" printf ' %s✘ build failed%s\n' "$red" "$reset" exit 1 @@ -18,11 +18,11 @@ fi printf ' %s✔ compiler built%s\n\n' "$green" "$reset" printf '%s━━ unit tests ━━%s\n' "$bold" "$reset" -if units=$(./bin/debug-linux/tests 2>/dev/null); then +if units=$(./build-linux/tests 2>/dev/null); then printf ' %s✔%s %s\n\n' "$green" "$reset" "$(printf '%s' "$units" | tail -1)" else printf '%s\n' "$units" - ./bin/debug-linux/tests || true + ./build-linux/tests || true printf ' %s✘ unit tests failed%s\n' "$red" "$reset" exit 1 fi diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh index c13ee1d..f8d1462 100755 --- a/scripts/test_examples.sh +++ b/scripts/test_examples.sh @@ -10,10 +10,10 @@ else bold=; dim=; red=; green=; reset= fi -hdass="${HDASS:-./bin/debug-linux/hdass}" +hdass="${HDASS:-./build-linux/hdass}" if [ ! -x "$hdass" ]; then echo "${red}error:${reset} '$hdass' not found; build it first with:" >&2 - echo " premake5 gmake && make config=debug" >&2 + echo " meson setup build-linux && meson compile -C build-linux" >&2 exit 1 fi diff --git a/src/args.c b/src/args.c new file mode 100644 index 0000000..b31def5 --- /dev/null +++ b/src/args.c @@ -0,0 +1,119 @@ +#include +#include +#include + +#include "args.h" + +#define HDASS_VERSION "0.1.0" + +static bool match_assembler(const char* name, enum Assembler* out) +{ + if (strcmp(name, "nasm") == 0) + { + *out = ASSEMBLER_NASM; + return true; + } + if (strcmp(name, "fasm") == 0) + { + *out = ASSEMBLER_FASM; + return true; + } + if (strcmp(name, "masm") == 0) + { + *out = ASSEMBLER_MASM; + return true; + } + return false; +} + +void print_version(void) +{ + printf("hdass %s\n", HDASS_VERSION); +} + +void print_usage(const char* program) +{ + printf("hachem's dumb assembly super set\n\n"); + printf("usage: %s [options]\n\n", program); + printf("options:\n"); + printf(" -o, --output write output to (default: stdout)\n"); + printf(" -t, --target target assembler: nasm, fasm, masm (default: nasm)\n"); + printf(" -h, --help print this help and exit\n"); + printf(" -v, --version print version and exit\n"); +} + +enum ParseResult parse_args(int argc, char** argv, struct Args* args) +{ + const char* program = argv[0]; + + args->input_path = NULL; + args->output_path = NULL; + args->target = ASSEMBLER_NASM; + + for (int i = 1; i < argc; i += 1) + { + const char* arg = argv[i]; + + if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) + { + print_usage(program); + return PARSE_EXIT; + } + + if (strcmp(arg, "-v") == 0 || strcmp(arg, "--version") == 0) + { + print_version(); + return PARSE_EXIT; + } + + if (strcmp(arg, "-o") == 0 || strcmp(arg, "--output") == 0) + { + i += 1; + if (i >= argc) + { + fprintf(stderr, "error: '%s' requires an argument\n", arg); + return PARSE_ERROR; + } + args->output_path = argv[i]; + continue; + } + + if (strcmp(arg, "-t") == 0 || strcmp(arg, "--target") == 0) + { + i += 1; + if (i >= argc) + { + fprintf(stderr, "error: '%s' requires an argument\n", arg); + return PARSE_ERROR; + } + if (!match_assembler(argv[i], &args->target)) + { + fprintf(stderr, "error: unknown target '%s'\n", argv[i]); + return PARSE_ERROR; + } + continue; + } + + if (arg[0] == '-') + { + fprintf(stderr, "error: unknown option '%s'\n", arg); + return PARSE_ERROR; + } + + if (args->input_path != NULL) + { + fprintf(stderr, "error: multiple input files given ('%s' and '%s')\n", args->input_path, arg); + return PARSE_ERROR; + } + + args->input_path = arg; + } + + if (args->input_path == NULL) + { + fprintf(stderr, "error: no input file given\n"); + return PARSE_ERROR; + } + + return PARSE_OK; +} diff --git a/src/args.h b/src/args.h new file mode 100644 index 0000000..f80b736 --- /dev/null +++ b/src/args.h @@ -0,0 +1,26 @@ +#pragma once + +enum Assembler +{ + ASSEMBLER_NASM, + ASSEMBLER_FASM, + ASSEMBLER_MASM, +}; + +enum ParseResult +{ + PARSE_OK, + PARSE_EXIT, + PARSE_ERROR, +}; + +struct Args +{ + const char* input_path; + const char* output_path; + enum Assembler target; +}; + +enum ParseResult parse_args(int argc, char** argv, struct Args* args); +void print_usage(const char* program); +void print_version(void); diff --git a/src/ast.c b/src/ast.c new file mode 100644 index 0000000..28bb98e --- /dev/null +++ b/src/ast.c @@ -0,0 +1,265 @@ +#include + +#include "ast.h" + +void free_expr(struct Expr* expr) +{ + if (expr == NULL) + return; + + switch (expr->kind) + { + case EXPR_PRIMARY: + break; + case EXPR_UNARY: + free_expr(expr->unary.operand); + break; + case EXPR_BINARY: + free_expr(expr->binary.left); + free_expr(expr->binary.right); + break; + case EXPR_MEMBER: + free_expr(expr->member.object); + break; + case EXPR_DEREF: + free_expr(expr->deref.address); + break; + } + + free(expr); +} + +void free_statement(struct Statement* statement) +{ + switch (statement->kind) + { + case STATEMENT_ASSIGN: + free_expr(statement->assign.value); + break; + case STATEMENT_IF: + free_expr(statement->branch.left); + free_expr(statement->branch.right); + for (size_t i = 0; i < statement->branch.body_count; i += 1) + free_statement(&statement->branch.body[i]); + free(statement->branch.body); + for (size_t i = 0; i < statement->branch.else_count; i += 1) + free_statement(&statement->branch.else_body[i]); + free(statement->branch.else_body); + break; + case STATEMENT_CALL: + for (size_t i = 0; i < statement->call.arg_count; i += 1) + free_expr(statement->call.args[i]); + free(statement->call.args); + break; + case STATEMENT_STACK: + free_expr(statement->stack.size); + break; + default: + break; + } +} + +void free_proc(struct ProcDecl* proc) +{ + free(proc->params); + + for (size_t i = 0; i < proc->body_count; i += 1) + free_statement(&proc->body[i]); + free(proc->body); +} + +struct Program create_program(void) +{ + struct Program program; + program.config.bits = 64; + program.config.has_entry = false; + program.config.logical_registers = false; + program.consts = NULL; + program.const_count = 0; + program.const_capacity = 0; + program.data_decls = NULL; + program.data_count = 0; + program.data_capacity = 0; + program.enums = NULL; + program.enum_count = 0; + program.enum_capacity = 0; + program.structs = NULL; + program.struct_count = 0; + program.struct_capacity = 0; + program.procs = NULL; + program.proc_count = 0; + program.proc_capacity = 0; + return program; +} + +void free_program(struct Program* program) +{ + for (size_t i = 0; i < program->const_count; i += 1) + free_expr(program->consts[i].value); + free(program->consts); + free(program->data_decls); + + for (size_t i = 0; i < program->enum_count; i += 1) + free(program->enums[i].members); + free(program->enums); + + for (size_t i = 0; i < program->struct_count; i += 1) + free(program->structs[i].fields); + free(program->structs); + + for (size_t i = 0; i < program->proc_count; i += 1) + free_proc(&program->procs[i]); + free(program->procs); + + program->consts = NULL; + program->const_count = 0; + program->const_capacity = 0; + program->data_decls = NULL; + program->data_count = 0; + program->data_capacity = 0; + program->enums = NULL; + program->enum_count = 0; + program->enum_capacity = 0; + program->structs = NULL; + program->struct_count = 0; + program->struct_capacity = 0; + program->procs = NULL; + program->proc_count = 0; + program->proc_capacity = 0; +} + +// TODO: generalize this growable-array boilerplate once a third list appears +void add_const(struct Program* program, struct ConstDecl decl) +{ + if (program->const_count == program->const_capacity) + { + size_t capacity = program->const_capacity < 8 ? 8 : program->const_capacity * 2; + program->consts = realloc(program->consts, capacity * sizeof(struct ConstDecl)); + program->const_capacity = capacity; + } + + program->consts[program->const_count] = decl; + program->const_count += 1; +} + +void add_data(struct Program* program, struct DataDecl decl) +{ + if (program->data_count == program->data_capacity) + { + size_t capacity = program->data_capacity < 8 ? 8 : program->data_capacity * 2; + program->data_decls = realloc(program->data_decls, capacity * sizeof(struct DataDecl)); + program->data_capacity = capacity; + } + + program->data_decls[program->data_count] = decl; + program->data_count += 1; +} + +struct EnumDecl create_enum(void) +{ + struct EnumDecl decl = { 0 }; + return decl; +} + +void add_enum_member(struct EnumDecl* decl, struct Token member) +{ + if (decl->member_count == decl->member_capacity) + { + size_t capacity = decl->member_capacity < 8 ? 8 : decl->member_capacity * 2; + decl->members = realloc(decl->members, capacity * sizeof(struct Token)); + decl->member_capacity = capacity; + } + + decl->members[decl->member_count] = member; + decl->member_count += 1; +} + +void add_enum(struct Program* program, struct EnumDecl decl) +{ + if (program->enum_count == program->enum_capacity) + { + size_t capacity = program->enum_capacity < 8 ? 8 : program->enum_capacity * 2; + program->enums = realloc(program->enums, capacity * sizeof(struct EnumDecl)); + program->enum_capacity = capacity; + } + + program->enums[program->enum_count] = decl; + program->enum_count += 1; +} + +struct StructDecl create_struct(void) +{ + struct StructDecl decl = { 0 }; + return decl; +} + +void add_struct_field(struct StructDecl* decl, struct StructField field) +{ + if (decl->field_count == decl->field_capacity) + { + size_t capacity = decl->field_capacity < 8 ? 8 : decl->field_capacity * 2; + decl->fields = realloc(decl->fields, capacity * sizeof(struct StructField)); + decl->field_capacity = capacity; + } + + decl->fields[decl->field_count] = field; + decl->field_count += 1; +} + +void add_struct(struct Program* program, struct StructDecl decl) +{ + if (program->struct_count == program->struct_capacity) + { + size_t capacity = program->struct_capacity < 8 ? 8 : program->struct_capacity * 2; + program->structs = realloc(program->structs, capacity * sizeof(struct StructDecl)); + program->struct_capacity = capacity; + } + + program->structs[program->struct_count] = decl; + program->struct_count += 1; +} + +struct ProcDecl create_proc(void) +{ + struct ProcDecl proc = { 0 }; + return proc; +} + +void add_param(struct ProcDecl* proc, struct Param param) +{ + if (proc->param_count == proc->param_capacity) + { + size_t capacity = proc->param_capacity < 4 ? 4 : proc->param_capacity * 2; + proc->params = realloc(proc->params, capacity * sizeof(struct Param)); + proc->param_capacity = capacity; + } + + proc->params[proc->param_count] = param; + proc->param_count += 1; +} + +void add_statement(struct ProcDecl* proc, struct Statement statement) +{ + if (proc->body_count == proc->body_capacity) + { + size_t capacity = proc->body_capacity < 8 ? 8 : proc->body_capacity * 2; + proc->body = realloc(proc->body, capacity * sizeof(struct Statement)); + proc->body_capacity = capacity; + } + + proc->body[proc->body_count] = statement; + proc->body_count += 1; +} + +void add_proc(struct Program* program, struct ProcDecl decl) +{ + if (program->proc_count == program->proc_capacity) + { + size_t capacity = program->proc_capacity < 8 ? 8 : program->proc_capacity * 2; + program->procs = realloc(program->procs, capacity * sizeof(struct ProcDecl)); + program->proc_capacity = capacity; + } + + program->procs[program->proc_count] = decl; + program->proc_count += 1; +} diff --git a/src/ast.h b/src/ast.h new file mode 100644 index 0000000..074c9b2 --- /dev/null +++ b/src/ast.h @@ -0,0 +1,245 @@ +#pragma once + +#include +#include +#include + +#include "lexer.h" + +enum StoreSize +{ + STORE_SIZE_NONE, + STORE_SIZE_BYTE, + STORE_SIZE_WORD, + STORE_SIZE_DWORD, + STORE_SIZE_QWORD, +}; + +struct ConstDecl +{ + struct Token name; + struct Expr* value; +}; + +struct DataDecl +{ + struct Token name; + struct Token value; +}; + +struct EnumDecl +{ + struct Token name; + struct Token* members; + size_t member_count; + size_t member_capacity; +}; + +struct StructField +{ + struct Token name; + enum StoreSize size; +}; + +struct StructDecl +{ + struct Token name; + struct StructField* fields; + size_t field_count; + size_t field_capacity; +}; + +struct Param +{ + struct Token name; + struct Token reg; +}; + +enum ExprKind +{ + EXPR_PRIMARY, + EXPR_UNARY, + EXPR_BINARY, + EXPR_MEMBER, + EXPR_DEREF, +}; + +struct PrimaryExpr +{ + struct Token token; +}; + +struct UnaryExpr +{ + struct Token op; + struct Expr* operand; +}; + +struct BinaryExpr +{ + struct Expr* left; + struct Token op; + struct Expr* right; +}; + +struct MemberExpr +{ + struct Expr* object; + struct Token member; +}; + +struct DerefExpr +{ + bool is_signed; + enum StoreSize size; + struct Expr* address; +}; + +struct Expr +{ + enum ExprKind kind; + union + { + struct PrimaryExpr primary; + struct UnaryExpr unary; + struct BinaryExpr binary; + struct MemberExpr member; + struct DerefExpr deref; + }; +}; + +enum StatementKind +{ + STATEMENT_ASSIGN, + STATEMENT_LABEL, + STATEMENT_GOTO, + STATEMENT_SYSCALL, + STATEMENT_IF, + STATEMENT_CALL, + STATEMENT_STACK, +}; + +struct AssignStatement +{ + bool target_deref; + enum StoreSize store_size; + struct Token target; + struct Token op; + struct Expr* value; +}; + +struct LabelStatement +{ + struct Token name; +}; + +struct GotoStatement +{ + struct Token label; +}; + +struct IfStatement +{ + struct Expr* left; + struct Token comparison; + struct Expr* right; + struct Statement* body; + size_t body_count; + struct Statement* else_body; + size_t else_count; +}; + +struct CallStatement +{ + struct Token name; + struct Expr** args; + size_t arg_count; + size_t arg_capacity; +}; + +struct StackStatement +{ + struct Token name; + struct Expr* size; +}; + +struct Statement +{ + enum StatementKind kind; + union + { + struct AssignStatement assign; + struct LabelStatement label; + struct GotoStatement jump; + struct IfStatement branch; + struct CallStatement call; + struct StackStatement stack; + }; +}; + +struct ProcDecl +{ + struct Token name; + struct Param* params; + size_t param_count; + size_t param_capacity; + + struct Statement* body; + size_t body_count; + size_t body_capacity; +}; + +struct Config +{ + uint32_t bits; + bool has_entry; + struct Token entry; + bool logical_registers; +}; + +struct Program +{ + struct Config config; + + struct ConstDecl* consts; + size_t const_count; + size_t const_capacity; + + struct DataDecl* data_decls; + size_t data_count; + size_t data_capacity; + + struct EnumDecl* enums; + size_t enum_count; + size_t enum_capacity; + + struct StructDecl* structs; + size_t struct_count; + size_t struct_capacity; + + struct ProcDecl* procs; + size_t proc_count; + size_t proc_capacity; +}; + +struct Program create_program(void); +void free_program(struct Program* program); +void add_const(struct Program* program, struct ConstDecl decl); +void add_data(struct Program* program, struct DataDecl decl); + +struct EnumDecl create_enum(void); +void add_enum_member(struct EnumDecl* decl, struct Token member); +void add_enum(struct Program* program, struct EnumDecl decl); + +struct StructDecl create_struct(void); +void add_struct_field(struct StructDecl* decl, struct StructField field); +void add_struct(struct Program* program, struct StructDecl decl); + +struct ProcDecl create_proc(void); +void free_proc(struct ProcDecl* proc); +void add_param(struct ProcDecl* proc, struct Param param); +void add_statement(struct ProcDecl* proc, struct Statement statement); +void add_proc(struct Program* program, struct ProcDecl decl); + +void free_expr(struct Expr* expr); +void free_statement(struct Statement* statement); diff --git a/src/cli/args.c b/src/cli/args.c deleted file mode 100644 index 501406f..0000000 --- a/src/cli/args.c +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include - -#include "cli/args.h" - -#define HDASS_VERSION "0.1.0" - -static bool match_assembler(const char* name, enum Assembler* out) -{ - if (strcmp(name, "nasm") == 0) - { - *out = ASSEMBLER_NASM; - return true; - } - if (strcmp(name, "fasm") == 0) - { - *out = ASSEMBLER_FASM; - return true; - } - if (strcmp(name, "masm") == 0) - { - *out = ASSEMBLER_MASM; - return true; - } - return false; -} - -void print_version(void) -{ - printf("hdass %s\n", HDASS_VERSION); -} - -void print_usage(const char* program) -{ - printf("hachem's dumb assembly super set\n\n"); - printf("usage: %s [options]\n\n", program); - printf("options:\n"); - printf(" -o, --output write output to (default: stdout)\n"); - printf(" -t, --target target assembler: nasm, fasm, masm (default: nasm)\n"); - printf(" -h, --help print this help and exit\n"); - printf(" -v, --version print version and exit\n"); -} - -enum ParseResult parse_args(int argc, char** argv, struct Args* args) -{ - const char* program = argv[0]; - - args->input_path = NULL; - args->output_path = NULL; - args->target = ASSEMBLER_NASM; - - for (int i = 1; i < argc; i += 1) - { - const char* arg = argv[i]; - - if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) - { - print_usage(program); - return PARSE_EXIT; - } - - if (strcmp(arg, "-v") == 0 || strcmp(arg, "--version") == 0) - { - print_version(); - return PARSE_EXIT; - } - - if (strcmp(arg, "-o") == 0 || strcmp(arg, "--output") == 0) - { - i += 1; - if (i >= argc) - { - fprintf(stderr, "error: '%s' requires an argument\n", arg); - return PARSE_ERROR; - } - args->output_path = argv[i]; - continue; - } - - if (strcmp(arg, "-t") == 0 || strcmp(arg, "--target") == 0) - { - i += 1; - if (i >= argc) - { - fprintf(stderr, "error: '%s' requires an argument\n", arg); - return PARSE_ERROR; - } - if (!match_assembler(argv[i], &args->target)) - { - fprintf(stderr, "error: unknown target '%s'\n", argv[i]); - return PARSE_ERROR; - } - continue; - } - - if (arg[0] == '-') - { - fprintf(stderr, "error: unknown option '%s'\n", arg); - return PARSE_ERROR; - } - - if (args->input_path != NULL) - { - fprintf(stderr, "error: multiple input files given ('%s' and '%s')\n", args->input_path, arg); - return PARSE_ERROR; - } - - args->input_path = arg; - } - - if (args->input_path == NULL) - { - fprintf(stderr, "error: no input file given\n"); - return PARSE_ERROR; - } - - return PARSE_OK; -} diff --git a/src/cli/args.h b/src/cli/args.h deleted file mode 100644 index f80b736..0000000 --- a/src/cli/args.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -enum Assembler -{ - ASSEMBLER_NASM, - ASSEMBLER_FASM, - ASSEMBLER_MASM, -}; - -enum ParseResult -{ - PARSE_OK, - PARSE_EXIT, - PARSE_ERROR, -}; - -struct Args -{ - const char* input_path; - const char* output_path; - enum Assembler target; -}; - -enum ParseResult parse_args(int argc, char** argv, struct Args* args); -void print_usage(const char* program); -void print_version(void); diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c deleted file mode 100644 index 198cb5c..0000000 --- a/src/codegen/nasm.c +++ /dev/null @@ -1,1252 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "codegen/nasm.h" - -static void emit_const_expr(struct Expr* expr, FILE* out) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start); - break; - case EXPR_UNARY: - fprintf(out, "%.*s", (int)expr->unary.op.length, expr->unary.op.start); - emit_const_expr(expr->unary.operand, out); - break; - case EXPR_BINARY: - emit_const_expr(expr->binary.left, out); - fprintf(out, " %.*s ", (int)expr->binary.op.length, expr->binary.op.start); - emit_const_expr(expr->binary.right, out); - break; - default: - break; - } -} - -static void emit_consts(struct Program* program, FILE* out) -{ - for (size_t i = 0; i < program->const_count; i += 1) - { - struct ConstDecl decl = program->consts[i]; - fprintf(out, "%%define %.*s (", (int)decl.name.length, decl.name.start); - emit_const_expr(decl.value, out); - fprintf(out, ")\n"); - } -} - -static void emit_data(struct Program* program, FILE* out) -{ - fprintf(out, "section .data\n"); - - for (size_t i = 0; i < program->data_count; i += 1) - { - struct DataDecl decl = program->data_decls[i]; - - // the value lexeme keeps its surrounding double quotes; NASM backtick - // strings interpret the same escapes, so re-wrap the inner content - fprintf(out, "%.*s: db `%.*s`\n", - (int)decl.name.length, decl.name.start, - (int)(decl.value.length - 2), decl.value.start + 1); - fprintf(out, ".len equ $ - %.*s\n", - (int)decl.name.length, decl.name.start); - } -} - -static const char* assign_mnemonic(enum TokenType op) -{ - switch (op) - { - case TOKEN_EQUAL: return "mov"; - case TOKEN_PLUS_EQUAL: return "add"; - case TOKEN_MINUS_EQUAL: return "sub"; - case TOKEN_STAR_EQUAL: return "imul"; - default: return NULL; - } -} - -struct FloatTable -{ - struct Token* items; - size_t count; - size_t capacity; -}; - -struct Emitter -{ - struct Program* program; - struct ProcDecl* proc; - struct FloatTable* floats; - FILE* out; - uint32_t label_id; -}; - -static bool is_float_register(struct Token token) -{ - if (token.length < 4 || memcmp(token.start, "xmm", 3) != 0) - return false; - - for (size_t i = 3; i < token.length; i += 1) - if (token.start[i] < '0' || token.start[i] > '9') - return false; - - return true; -} - -static size_t float_index(const struct FloatTable* floats, struct Token literal) -{ - for (size_t i = 0; i < floats->count; i += 1) - if (floats->items[i].length == literal.length - && memcmp(floats->items[i].start, literal.start, literal.length) == 0) - return i; - - return floats->count; -} - -static const char* sized_register(struct Token reg, enum StoreSize size); - -static struct Token resolve_token(struct Emitter* emitter, struct Token token) -{ - if (emitter->proc == NULL) - return token; - - for (size_t i = 0; i < emitter->proc->param_count; i += 1) - { - struct Param param = emitter->proc->params[i]; - if (param.name.length == token.length && memcmp(param.name.start, token.start, token.length) == 0) - return param.reg; - } - - return token; -} - -static struct Token text_token(const char* text) -{ - struct Token token; - token.type = TOKEN_IDENTIFIER; - token.start = text; - token.length = strlen(text); - token.line = 0; - return token; -} - -// with the logical_registers extension, r1..r14 name the general-purpose -// registers; rsp/rbp and the instruction pointer keep their dedicated names. -static const char* logical_register_base(struct Token token) -{ - static const char* registers[] = { - "rax", "rbx", "rcx", "rdx", "rsi", "rdi", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", - }; - - if (token.length < 2 || token.start[0] != 'r') - return NULL; - - uint32_t index = 0; - for (size_t i = 1; i < token.length; i += 1) - { - char digit = token.start[i]; - if (digit < '0' || digit > '9') - return NULL; - index = index * 10 + (uint32_t)(digit - '0'); - } - - if (index < 1 || index > sizeof(registers) / sizeof(registers[0])) - return NULL; - - return registers[index - 1]; -} - -static struct Token resolve_register(struct Emitter* emitter, struct Token token) -{ - struct Token resolved = resolve_token(emitter, token); - - if (emitter->program->config.logical_registers) - { - const char* base = logical_register_base(resolved); - if (base != NULL) - return text_token(base); - } - - return resolved; -} - -static uint64_t token_to_u64(struct Token token) -{ - if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'x' || token.start[1] == 'X')) - { - uint64_t value = 0; - for (size_t i = 2; i < token.length; i += 1) - { - char digit = token.start[i]; - uint64_t nibble = digit <= '9' ? (uint64_t)(digit - '0') - : (uint64_t)((digit | 0x20) - 'a' + 10); - value = value * 16 + nibble; - } - return value; - } - - if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'b' || token.start[1] == 'B')) - { - uint64_t value = 0; - for (size_t i = 2; i < token.length; i += 1) - value = value * 2 + (uint64_t)(token.start[i] - '0'); - return value; - } - - uint64_t value = 0; - for (size_t i = 0; i < token.length; i += 1) - value = value * 10 + (uint64_t)(token.start[i] - '0'); - - return value; -} - -static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out); - -static bool buffer_offset(struct Emitter* emitter, struct Token name, uint64_t* out_offset) -{ - struct ProcDecl* proc = emitter->proc; - uint64_t cumulative = 0; - for (size_t i = 0; i < proc->body_count; i += 1) - { - struct Statement* statement = &proc->body[i]; - if (statement->kind != STATEMENT_STACK) - continue; - - uint64_t size = 0; - fold_const(emitter->program, statement->stack.size, &size); - cumulative += size; - if (statement->stack.name.length == name.length - && memcmp(statement->stack.name.start, name.start, name.length) == 0) - { - *out_offset = cumulative; - return true; - } - } - - return false; -} - -static bool is_buffer_name(struct Emitter* emitter, struct Token token) -{ - uint64_t offset; - return emitter->proc != NULL && buffer_offset(emitter, token, &offset); -} - -static enum StoreSize size_from_int(struct Token token) -{ - switch (token_to_u64(token)) - { - case 8: return STORE_SIZE_BYTE; - case 16: return STORE_SIZE_WORD; - case 32: return STORE_SIZE_DWORD; - case 64: return STORE_SIZE_QWORD; - default: return STORE_SIZE_NONE; - } -} - -static bool tokens_equal(struct Token a, struct Token b) -{ - return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; -} - -static bool token_matches(struct Token token, const char* text) -{ - size_t length = strlen(text); - return token.length == length && memcmp(token.start, text, length) == 0; -} - -static struct EnumDecl* find_enum(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->enum_count; i += 1) - if (tokens_equal(program->enums[i].name, name)) - return &program->enums[i]; - - return NULL; -} - -static struct StructDecl* find_struct(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->struct_count; i += 1) - if (tokens_equal(program->structs[i].name, name)) - return &program->structs[i]; - - return NULL; -} - -static uint64_t store_size_bytes(enum StoreSize size) -{ - switch (size) - { - case STORE_SIZE_BYTE: return 1; - case STORE_SIZE_WORD: return 2; - case STORE_SIZE_DWORD: return 4; - default: return 8; - } -} - -static uint64_t char_literal_value(struct Token token) -{ - if (token.length >= 4 && token.start[1] == '\\') - { - switch (token.start[2]) - { - case 'n': return 10; - case 't': return 9; - case 'r': return 13; - case '0': return 0; - case '\\': return 92; - case '\'': return 39; - default: return (unsigned char)token.start[2]; - } - } - - return (unsigned char)token.start[1]; -} - -static bool fold_member(struct Program* program, struct Expr* object, struct Token member, uint64_t* out) -{ - if (object->kind != EXPR_PRIMARY) - return false; - struct Token name = object->primary.token; - - const struct EnumDecl* enumeration = find_enum(program, name); - if (enumeration != NULL) - { - for (size_t i = 0; i < enumeration->member_count; i += 1) - if (tokens_equal(enumeration->members[i], member)) - { - *out = i; - return true; - } - return false; - } - - const struct StructDecl* layout = find_struct(program, name); - if (layout != NULL) - { - uint64_t offset = 0; - for (size_t i = 0; i < layout->field_count; i += 1) - { - if (tokens_equal(layout->fields[i].name, member)) - { - *out = offset; - return true; - } - offset += store_size_bytes(layout->fields[i].size); - } - if (token_matches(member, "size")) - { - *out = offset; - return true; - } - } - - return false; -} - -// evaluates a compile-time constant expression: integer/char literals, other -// constants, enum values and struct offsets, and + - * / over them -static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - { - struct Token token = expr->primary.token; - if (token.type == TOKEN_INTEGER) - { - *out = token_to_u64(token); - return true; - } - if (token.type == TOKEN_CHAR) - { - *out = char_literal_value(token); - return true; - } - if (token.type == TOKEN_IDENTIFIER) - for (size_t i = 0; i < program->const_count; i += 1) - if (tokens_equal(program->consts[i].name, token)) - return fold_const(program, program->consts[i].value, out); - return false; - } - case EXPR_UNARY: - { - uint64_t value; - if (!fold_const(program, expr->unary.operand, &value)) - return false; - *out = 0 - value; - return true; - } - case EXPR_BINARY: - { - uint64_t left; - uint64_t right; - if (!fold_const(program, expr->binary.left, &left) - || !fold_const(program, expr->binary.right, &right)) - return false; - - switch (expr->binary.op.type) - { - case TOKEN_PLUS: *out = left + right; return true; - case TOKEN_MINUS: *out = left - right; return true; - case TOKEN_STAR: *out = left * right; return true; - case TOKEN_SLASH: *out = right != 0 ? left / right : 0; return true; - default: return false; - } - } - case EXPR_MEMBER: - return fold_member(program, expr->member.object, expr->member.member, out); - case EXPR_DEREF: - return false; - } - - return false; -} - -// an enum member folds to its 0-based index; a struct member folds to its byte -// offset (or the total size for `.size`) -static bool emit_named_member(struct Emitter* emitter, struct Token object, struct Token member) -{ - const struct EnumDecl* enumeration = find_enum(emitter->program, object); - if (enumeration != NULL) - { - for (size_t i = 0; i < enumeration->member_count; i += 1) - if (tokens_equal(enumeration->members[i], member)) - { - fprintf(emitter->out, "%zu", i); - return true; - } - } - - const struct StructDecl* layout = find_struct(emitter->program, object); - if (layout != NULL) - { - uint64_t offset = 0; - for (size_t i = 0; i < layout->field_count; i += 1) - { - if (tokens_equal(layout->fields[i].name, member)) - { - fprintf(emitter->out, "%llu", (unsigned long long)offset); - return true; - } - offset += store_size_bytes(layout->fields[i].size); - } - if (token_matches(member, "size")) - { - fprintf(emitter->out, "%llu", (unsigned long long)offset); - return true; - } - } - - return false; -} - -static bool emit_operand(struct Emitter* emitter, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - { - struct Token token = resolve_register(emitter, expr->primary.token); - fprintf(emitter->out, "%.*s", (int)token.length, token.start); - return true; - } - case EXPR_MEMBER: - { - // a register size suffix: r1.64 -> rax, r1.8 -> al - if (expr->member.member.type == TOKEN_INTEGER && - expr->member.object->kind == EXPR_PRIMARY) - { - enum StoreSize size = size_from_int(expr->member.member); - struct Token base = resolve_register(emitter, expr->member.object->primary.token); - const char* sized = sized_register(base, size); - if (sized != NULL) - fprintf(emitter->out, "%s", sized); - else - fprintf(emitter->out, "%.*s", (int)base.length, base.start); - return true; - } - - // enum value or struct offset - if (expr->member.object->kind == EXPR_PRIMARY - && emit_named_member(emitter, expr->member.object->primary.token, expr->member.member)) - return true; - - if (!emit_operand(emitter, expr->member.object)) - return false; - fprintf(emitter->out, ".%.*s", (int)expr->member.member.length, expr->member.member.start); - return true; - } - case EXPR_UNARY: - { - uint64_t value; - if (!fold_const(emitter->program, expr, &value)) - return false; - fprintf(emitter->out, "%lld", (long long)value); - return true; - } - case EXPR_BINARY: - case EXPR_DEREF: - return false; - } - - return false; -} - -// idiv divides rdx:rax by its operand and leaves the quotient in rax, so a -// division computes `dst = dst / divisor` through rax (clobbering rax and rdx). -static void emit_division(struct Emitter* emitter, const char* dst, struct Expr* divisor) -{ - bool dst_is_rax = strcmp(dst, "rax") == 0; - - if (!dst_is_rax) - fprintf(emitter->out, "\tmov rax, %s\n", dst); - fprintf(emitter->out, "\tcqo\n"); - fprintf(emitter->out, "\tidiv "); - emit_operand(emitter, divisor); - fprintf(emitter->out, "\n"); - if (!dst_is_rax) - fprintf(emitter->out, "\tmov %s, rax\n", dst); -} - -// idiv leaves the remainder in rdx, so a modulo takes its result from there -static void emit_modulo(struct Emitter* emitter, const char* dst, struct Expr* divisor) -{ - if (strcmp(dst, "rax") != 0) - fprintf(emitter->out, "\tmov rax, %s\n", dst); - fprintf(emitter->out, "\tcqo\n"); - fprintf(emitter->out, "\tidiv "); - emit_operand(emitter, divisor); - fprintf(emitter->out, "\n"); - if (strcmp(dst, "rdx") != 0) - fprintf(emitter->out, "\tmov %s, rdx\n", dst); -} - -static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign) -{ - if (assign->target_deref) - { - fprintf(emitter->out, "\t; TODO: unsupported division\n"); - return; - } - - struct Token target = resolve_register(emitter, assign->target); - char dst[32]; - snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); - - if (assign->op.type == TOKEN_PERCENT_EQUAL) - emit_modulo(emitter, dst, assign->value); - else - emit_division(emitter, dst, assign->value); -} - -// an expression can be evaluated into a register when it is a single term -// (primary or member), or a left-associative chain of binary operators whose -// right operands are plain operands (never a buffer or a nested binary) -static bool expr_supported(struct Emitter* emitter, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - case EXPR_MEMBER: - case EXPR_DEREF: - return true; - case EXPR_UNARY: - { - uint64_t value; - return fold_const(emitter->program, expr, &value); - } - case EXPR_BINARY: - if (expr->binary.right->kind != EXPR_PRIMARY && - expr->binary.right->kind != EXPR_MEMBER && - expr->binary.right->kind != EXPR_UNARY) - return false; - if (expr->binary.right->kind == EXPR_UNARY) - { - uint64_t value; - if (!fold_const(emitter->program, expr->binary.right, &value)) - return false; - } - if (expr->binary.right->kind == EXPR_PRIMARY && - is_buffer_name(emitter, expr->binary.right->primary.token)) - return false; - return expr_supported(emitter, expr->binary.left); - } - - return false; -} - -// dst = [address], extending narrower loads into the full register. Unsigned -// loads zero-extend (movzx / a 32-bit mov); signed loads sign-extend (movsx, -// or movsxd for a dword). -static void emit_load(struct Emitter* emitter, const char* dst, struct DerefExpr* deref) -{ - FILE* out = emitter->out; - if (deref->is_signed) - { - switch (deref->size) - { - case STORE_SIZE_BYTE: - fprintf(out, "\tmovsx %s, byte [", dst); - break; - case STORE_SIZE_WORD: - fprintf(out, "\tmovsx %s, word [", dst); - break; - case STORE_SIZE_DWORD: - fprintf(out, "\tmovsxd %s, dword [", dst); - break; - default: - fprintf(out, "\tmov %s, [", dst); - break; - } - - emit_operand(emitter, deref->address); - fprintf(out, "]\n"); - return; - } - - switch (deref->size) - { - case STORE_SIZE_BYTE: - fprintf(out, "\tmovzx %s, byte [", dst); - break; - case STORE_SIZE_WORD: - fprintf(out, "\tmovzx %s, word [", dst); - break; - case STORE_SIZE_DWORD: - { - const char* dword = sized_register(text_token(dst), STORE_SIZE_DWORD); - fprintf(out, "\tmov %s, [", dword != NULL ? dword : dst); - break; - } - default: - fprintf(out, "\tmov %s, [", dst); - break; - } - - emit_operand(emitter, deref->address); - fprintf(out, "]\n"); -} - -static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr* expr) -{ - if (expr->kind == EXPR_DEREF) - { - emit_load(emitter, dst, &expr->deref); - return; - } - - if (expr->kind == EXPR_BINARY) - { - emit_expr_into(emitter, dst, expr->binary.left); - - if (expr->binary.op.type == TOKEN_SLASH) - { - emit_division(emitter, dst, expr->binary.right); - return; - } - if (expr->binary.op.type == TOKEN_PERCENT) - { - emit_modulo(emitter, dst, expr->binary.right); - return; - } - - const char* mnemonic = - expr->binary.op.type == TOKEN_PLUS ? "add" : - expr->binary.op.type == TOKEN_MINUS ? "sub" : "imul"; - fprintf(emitter->out, "\t%s %s, ", mnemonic, dst); - emit_operand(emitter, expr->binary.right); - fprintf(emitter->out, "\n"); - return; - } - - if (expr->kind == EXPR_PRIMARY && is_buffer_name(emitter, expr->primary.token)) - { - uint64_t offset; - buffer_offset(emitter, expr->primary.token, &offset); - fprintf(emitter->out, "\tlea %s, [rbp - %llu]\n", dst, (unsigned long long)offset); - return; - } - - fprintf(emitter->out, "\tmov %s, ", dst); - emit_operand(emitter, expr); - fprintf(emitter->out, "\n"); -} - -static const char* store_size_keyword(enum StoreSize size) -{ - switch (size) - { - case STORE_SIZE_BYTE: return "byte "; - case STORE_SIZE_WORD: return "word "; - case STORE_SIZE_DWORD: return "dword "; - case STORE_SIZE_QWORD: return "qword "; - default: return ""; - } -} - -// maps a full 64-bit register to its byte/word/dword sub-register for a sized -// store, so `^byte rsi = rdx` writes `dl` rather than the whole register. -// returns NULL when the token is not a full register, or no resizing applies. -static const char* sized_register(struct Token reg, enum StoreSize size) -{ - if (size == STORE_SIZE_NONE || size == STORE_SIZE_QWORD) - return NULL; - - static const struct RegisterSizes - { - const char* quad; - const char* dword; - const char* word; - const char* byte; - } registers[] = - { - { "rax", "eax", "ax", "al" }, - { "rbx", "ebx", "bx", "bl" }, - { "rcx", "ecx", "cx", "cl" }, - { "rdx", "edx", "dx", "dl" }, - { "rsi", "esi", "si", "sil" }, - { "rdi", "edi", "di", "dil" }, - { "rbp", "ebp", "bp", "bpl" }, - { "rsp", "esp", "sp", "spl" }, - { "r8", "r8d", "r8w", "r8b" }, - { "r9", "r9d", "r9w", "r9b" }, - { "r10", "r10d", "r10w", "r10b" }, - { "r11", "r11d", "r11w", "r11b" }, - { "r12", "r12d", "r12w", "r12b" }, - { "r13", "r13d", "r13w", "r13b" }, - { "r14", "r14d", "r14w", "r14b" }, - { "r15", "r15d", "r15w", "r15b" }, - }; - - for (size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); i += 1) - { - const struct RegisterSizes* entry = ®isters[i]; - size_t length = strlen(entry->quad); - if (reg.length != length || memcmp(reg.start, entry->quad, length) != 0) - continue; - - switch (size) - { - case STORE_SIZE_DWORD: return entry->dword; - case STORE_SIZE_WORD: return entry->word; - case STORE_SIZE_BYTE: return entry->byte; - default: return NULL; - } - } - - return NULL; -} - -static const char* float_mnemonic(enum TokenType op) -{ - switch (op) - { - case TOKEN_EQUAL: return "movsd"; - case TOKEN_PLUS_EQUAL: return "addsd"; - case TOKEN_MINUS_EQUAL: return "subsd"; - case TOKEN_STAR_EQUAL: return "mulsd"; - case TOKEN_SLASH_EQUAL: return "divsd"; - default: return NULL; - } -} - -static bool value_is_float(struct Emitter* emitter, const struct Expr* expr) -{ - if (expr->kind != EXPR_PRIMARY) - return false; - if (expr->primary.token.type == TOKEN_FLOAT) - return true; - - return is_float_register(resolve_register(emitter, expr->primary.token)); -} - -// floating point: xmm moves and arithmetic, conversions to/from general-purpose -// registers, and float literals loaded from their .data slot -static bool emit_float_assign(struct Emitter* emitter, struct AssignStatement* assign, - struct Token target, bool target_float) -{ - struct Expr* value = assign->value; - - // float store: ^ptr = xmm -> movsd [ptr], xmm - if (assign->target_deref) - { - if (assign->op.type != TOKEN_EQUAL || value->kind != EXPR_PRIMARY) - return false; - struct Token source = resolve_register(emitter, value->primary.token); - if (!is_float_register(source)) - return false; - fprintf(emitter->out, "\tmovsd [%.*s], %.*s\n", - (int)target.length, target.start, (int)source.length, source.start); - return true; - } - - // float load: xmm = ^ptr -> movsd xmm, [ptr] - if (value->kind == EXPR_DEREF) - { - if (!target_float || assign->op.type != TOKEN_EQUAL) - return false; - fprintf(emitter->out, "\tmovsd %.*s, [", (int)target.length, target.start); - emit_operand(emitter, value->deref.address); - fprintf(emitter->out, "]\n"); - return true; - } - - if (value->kind == EXPR_PRIMARY && value->primary.token.type == TOKEN_FLOAT) - { - if (!target_float || assign->op.type != TOKEN_EQUAL) - return false; - size_t index = float_index(emitter->floats, value->primary.token); - fprintf(emitter->out, "\tmovsd %.*s, [__float%zu]\n", - (int)target.length, target.start, index); - return true; - } - - if (value->kind != EXPR_PRIMARY) - return false; - - struct Token source = resolve_register(emitter, value->primary.token); - bool source_float = is_float_register(source); - - if (target_float && source_float) - { - const char* mnemonic = float_mnemonic(assign->op.type); - if (mnemonic == NULL) - return false; - fprintf(emitter->out, "\t%s %.*s, %.*s\n", mnemonic, - (int)target.length, target.start, (int)source.length, source.start); - return true; - } - - if (assign->op.type != TOKEN_EQUAL) - return false; - - if (target_float) - fprintf(emitter->out, "\tcvtsi2sd %.*s, %.*s\n", - (int)target.length, target.start, (int)source.length, source.start); - else - fprintf(emitter->out, "\tcvttsd2si %.*s, %.*s\n", - (int)target.length, target.start, (int)source.length, source.start); - return true; -} - -static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) -{ - struct Token float_target = resolve_register(emitter, assign->target); - if (is_float_register(float_target) || value_is_float(emitter, assign->value)) - { - if (!emit_float_assign(emitter, assign, float_target, is_float_register(float_target))) - fprintf(emitter->out, "\t; TODO: unsupported float assignment\n"); - return; - } - - if (assign->op.type == TOKEN_SLASH_EQUAL || assign->op.type == TOKEN_PERCENT_EQUAL) - { - emit_divide(emitter, assign); - return; - } - - struct Token target = resolve_register(emitter, assign->target); - - if (assign->op.type == TOKEN_EQUAL && !assign->target_deref) - { - if (!expr_supported(emitter, assign->value)) - { - fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); - return; - } - - char dst[32]; - snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); - emit_expr_into(emitter, dst, assign->value); - return; - } - - // deref store or compound assignment: needs a plain operand, not a buffer or binary - const char* mnemonic = assign_mnemonic(assign->op.type); - bool value_is_buffer = assign->value->kind == EXPR_PRIMARY - && is_buffer_name(emitter, assign->value->primary.token); - if (mnemonic == NULL || assign->value->kind == EXPR_BINARY - || assign->value->kind == EXPR_DEREF || value_is_buffer) - { - fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); - return; - } - - // adding or subtracting a constant zero (e.g. a struct field at offset 0) is a no-op - uint64_t folded; - if (!assign->target_deref - && (assign->op.type == TOKEN_PLUS_EQUAL || assign->op.type == TOKEN_MINUS_EQUAL) - && fold_const(emitter->program, assign->value, &folded) && folded == 0) - return; - - if (assign->target_deref) - { - fprintf(emitter->out, "\t%s %s[%.*s], ", mnemonic, - store_size_keyword(assign->store_size), (int)target.length, target.start); - - const char* sized = NULL; - if (assign->value->kind == EXPR_PRIMARY) - { - struct Token value = resolve_register(emitter, assign->value->primary.token); - sized = sized_register(value, assign->store_size); - if (sized != NULL) - fprintf(emitter->out, "%s", sized); - } - - if (sized == NULL) - emit_operand(emitter, assign->value); - } - else - { - fprintf(emitter->out, "\t%s %.*s, ", mnemonic, (int)target.length, target.start); - emit_operand(emitter, assign->value); - } - - fprintf(emitter->out, "\n"); -} - -static const char* jump_if_false(enum TokenType comparison) -{ - switch (comparison) - { - case TOKEN_EQUAL_EQUAL: return "jne"; - case TOKEN_BANG_EQUAL: return "je"; - case TOKEN_LESS: return "jge"; - case TOKEN_LESS_EQUAL: return "jg"; - case TOKEN_GREATER: return "jle"; - case TOKEN_GREATER_EQUAL: return "jl"; - default: return NULL; - } -} - -static struct ProcDecl* find_proc(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->proc_count; i += 1) - { - struct ProcDecl* proc = &program->procs[i]; - if (proc->name.length == name.length && memcmp(proc->name.start, name.start, name.length) == 0) - return proc; - } - - return NULL; -} - -static void emit_call(struct Emitter* emitter, struct CallStatement* call) -{ - const struct ProcDecl* callee = find_proc(emitter->program, call->name); - if (callee == NULL || callee->param_count != call->arg_count) - { - fprintf(emitter->out, "\t; TODO: unsupported call\n"); - return; - } - - for (size_t i = 0; i < call->arg_count; i += 1) - { - if (call->args[i]->kind == EXPR_BINARY || call->args[i]->kind == EXPR_DEREF) - { - fprintf(emitter->out, "\t; TODO: unsupported call argument\n"); - continue; - } - - struct Token reg = resolve_register(emitter, callee->params[i].reg); - fprintf(emitter->out, "\tmov %.*s, ", (int)reg.length, reg.start); - emit_operand(emitter, call->args[i]); - fprintf(emitter->out, "\n"); - } - - fprintf(emitter->out, "\tcall %.*s\n", (int)call->name.length, call->name.start); -} - -static void emit_statement(struct Emitter* emitter, struct Statement* statement); - -static void emit_block(struct Emitter* emitter, struct Statement* body, size_t count) -{ - for (size_t i = 0; i < count; i += 1) - emit_statement(emitter, &body[i]); -} - -// ucomisd sets the flags like an unsigned compare, so float branches use the -// unsigned jump family (ja/jae/jb/jbe) rather than the signed one -static const char* float_jump_if_false(enum TokenType comparison) -{ - switch (comparison) - { - case TOKEN_EQUAL_EQUAL: return "jne"; - case TOKEN_BANG_EQUAL: return "je"; - case TOKEN_LESS: return "jae"; - case TOKEN_LESS_EQUAL: return "ja"; - case TOKEN_GREATER: return "jbe"; - case TOKEN_GREATER_EQUAL: return "jb"; - default: return NULL; - } -} - -static void emit_float_operand(struct Emitter* emitter, const struct Expr* expr) -{ - if (expr->kind == EXPR_PRIMARY && expr->primary.token.type == TOKEN_FLOAT) - { - fprintf(emitter->out, "[__float%zu]", float_index(emitter->floats, expr->primary.token)); - return; - } - - struct Token token = resolve_register(emitter, expr->primary.token); - fprintf(emitter->out, "%.*s", (int)token.length, token.start); -} - -static void emit_if(struct Emitter* emitter, struct IfStatement* branch) -{ - bool is_float = value_is_float(emitter, branch->left) || value_is_float(emitter, branch->right); - bool has_else = branch->else_count > 0; - - uint32_t id = emitter->label_id; - emitter->label_id += 1; - - const char* target = has_else ? ".if_else_" : ".if_end_"; - - if (is_float) - { - const char* jump = float_jump_if_false(branch->comparison.type); - bool left_reg = branch->left->kind == EXPR_PRIMARY - && is_float_register(resolve_register(emitter, branch->left->primary.token)); - if (jump == NULL || !left_reg || !value_is_float(emitter, branch->right)) - { - fprintf(emitter->out, "\t; TODO: unsupported if\n"); - return; - } - - fprintf(emitter->out, "\tucomisd "); - emit_float_operand(emitter, branch->left); - fprintf(emitter->out, ", "); - emit_float_operand(emitter, branch->right); - fprintf(emitter->out, "\n\t%s %s%u\n", jump, target, id); - } - else - { - const char* jump = jump_if_false(branch->comparison.type); - if (jump == NULL - || branch->left->kind == EXPR_BINARY || branch->left->kind == EXPR_DEREF - || branch->right->kind == EXPR_BINARY || branch->right->kind == EXPR_DEREF) - { - fprintf(emitter->out, "\t; TODO: unsupported if\n"); - return; - } - - fprintf(emitter->out, "\tcmp "); - emit_operand(emitter, branch->left); - fprintf(emitter->out, ", "); - emit_operand(emitter, branch->right); - fprintf(emitter->out, "\n\t%s %s%u\n", jump, target, id); - } - - emit_block(emitter, branch->body, branch->body_count); - - if (has_else) - { - fprintf(emitter->out, "\tjmp .if_end_%u\n", id); - fprintf(emitter->out, ".if_else_%u:\n", id); - emit_block(emitter, branch->else_body, branch->else_count); - } - - fprintf(emitter->out, ".if_end_%u:\n", id); -} - -static void emit_statement(struct Emitter* emitter, struct Statement* statement) -{ - FILE* out = emitter->out; - switch (statement->kind) - { - case STATEMENT_ASSIGN: - emit_assign(emitter, &statement->assign); - break; - case STATEMENT_LABEL: - fprintf(out, "%.*s:\n", (int)statement->label.name.length, statement->label.name.start); - break; - case STATEMENT_GOTO: - fprintf(out, "\tjmp %.*s\n", (int)statement->jump.label.length, statement->jump.label.start); - break; - case STATEMENT_SYSCALL: - fprintf(out, "\tsyscall\n"); - break; - case STATEMENT_IF: - emit_if(emitter, &statement->branch); - break; - case STATEMENT_CALL: - emit_call(emitter, &statement->call); - break; - case STATEMENT_STACK: - break; - default: - fprintf(out, "\t; TODO: unsupported statement\n"); - break; - } -} - -static uint64_t proc_stack_size(struct Program* program, struct ProcDecl* proc) -{ - uint64_t total = 0; - for (size_t i = 0; i < proc->body_count; i += 1) - { - struct Statement* statement = &proc->body[i]; - if (statement->kind == STATEMENT_STACK) - { - uint64_t size = 0; - fold_const(program, statement->stack.size, &size); - total += size; - } - } - - if (total % 16 != 0) - total += 16 - (total % 16); - - return total; -} - -static void collect_float(struct FloatTable* floats, struct Token token) -{ - if (token.type != TOKEN_FLOAT || float_index(floats, token) != floats->count) - return; - - if (floats->count == floats->capacity) - { - size_t capacity = floats->capacity < 8 ? 8 : floats->capacity * 2; - floats->items = realloc(floats->items, capacity * sizeof(struct Token)); - floats->capacity = capacity; - } - - floats->items[floats->count] = token; - floats->count += 1; -} - -static void collect_floats_expr(struct FloatTable* floats, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - collect_float(floats, expr->primary.token); - break; - case EXPR_UNARY: - collect_floats_expr(floats, expr->unary.operand); - break; - case EXPR_BINARY: - collect_floats_expr(floats, expr->binary.left); - collect_floats_expr(floats, expr->binary.right); - break; - case EXPR_MEMBER: - collect_floats_expr(floats, expr->member.object); - break; - case EXPR_DEREF: - collect_floats_expr(floats, expr->deref.address); - break; - } -} - -static void collect_floats_statement(struct FloatTable* floats, struct Statement* statement) -{ - switch (statement->kind) - { - case STATEMENT_ASSIGN: - collect_floats_expr(floats, statement->assign.value); - break; - case STATEMENT_IF: - collect_floats_expr(floats, statement->branch.left); - collect_floats_expr(floats, statement->branch.right); - for (size_t i = 0; i < statement->branch.body_count; i += 1) - collect_floats_statement(floats, &statement->branch.body[i]); - for (size_t i = 0; i < statement->branch.else_count; i += 1) - collect_floats_statement(floats, &statement->branch.else_body[i]); - break; - case STATEMENT_CALL: - for (size_t i = 0; i < statement->call.arg_count; i += 1) - collect_floats_expr(floats, statement->call.args[i]); - break; - default: - break; - } -} - -static struct FloatTable collect_floats(struct Program* program) -{ - struct FloatTable floats = { NULL, 0, 0 }; - for (size_t i = 0; i < program->proc_count; i += 1) - for (size_t j = 0; j < program->procs[i].body_count; j += 1) - collect_floats_statement(&floats, &program->procs[i].body[j]); - - return floats; -} - -static void emit_float_data(const struct FloatTable* floats, FILE* out) -{ - for (size_t i = 0; i < floats->count; i += 1) - fprintf(out, "__float%zu: dq %.*s\n", i, - (int)floats->items[i].length, floats->items[i].start); -} - -static void emit_proc(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, FILE* out) -{ - struct Emitter emitter; - emitter.program = program; - emitter.proc = proc; - emitter.floats = floats; - emitter.out = out; - emitter.label_id = 0; - - struct Config config = program->config; - bool is_entry = config.has_entry - && proc->name.length == config.entry.length - && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; - - fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start); - - uint64_t stack_size = proc_stack_size(program, proc); - if (stack_size > 0) - { - fprintf(out, "\tpush rbp\n"); - fprintf(out, "\tmov rbp, rsp\n"); - fprintf(out, "\tsub rsp, %llu\n", (unsigned long long)stack_size); - } - - for (size_t i = 0; i < proc->body_count; i += 1) - emit_statement(&emitter, &proc->body[i]); - - if (!is_entry) - { - if (stack_size > 0) - fprintf(out, "\tleave\n"); - fprintf(out, "\tret\n"); - } -} - -void generate_nasm(struct Program* program, FILE* out) -{ - struct FloatTable floats = collect_floats(program); - - fprintf(out, "bits %u\n\n", program->config.bits); - - if (program->const_count > 0) - { - emit_consts(program, out); - fprintf(out, "\n"); - } - - emit_data(program, out); - emit_float_data(&floats, out); - fprintf(out, "\n"); - - fprintf(out, "section .text\n"); - if (program->config.has_entry) - fprintf(out, "global %.*s\n", (int)program->config.entry.length, program->config.entry.start); - - for (size_t i = 0; i < program->proc_count; i += 1) - { - fprintf(out, "\n"); - emit_proc(program, &floats, &program->procs[i], out); - } - - free(floats.items); -} diff --git a/src/codegen/nasm.h b/src/codegen/nasm.h deleted file mode 100644 index b9c2c90..0000000 --- a/src/codegen/nasm.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -#include "parser/ast.h" - -void generate_nasm(struct Program* program, FILE* out); diff --git a/src/diag.c b/src/diag.c new file mode 100644 index 0000000..6962046 --- /dev/null +++ b/src/diag.c @@ -0,0 +1,63 @@ +#include +#include +#include + +#include "diag.h" + +static bool colored(void) +{ + return getenv("NO_COLOR") == NULL; +} + +void report_error(struct Source source, struct Token token, const char* message) +{ + const char* name = source.name != NULL ? source.name : ""; + const char* text = source.text; + + const char* line_start = token.start; + while (line_start > text && line_start[-1] != '\n') + line_start -= 1; + + const char* line_end = token.start; + while (*line_end != '\0' && *line_end != '\n') + line_end += 1; + + uint32_t column = (uint32_t)(token.start - line_start) + 1; + + size_t span = token.length > 0 ? token.length : 1; + if ((size_t)(line_end - token.start) < span) + span = (size_t)(line_end - token.start); + if (span == 0) + span = 1; + + const char* bold = colored() ? "\033[1m" : ""; + const char* red = colored() ? "\033[31m" : ""; + const char* blue = colored() ? "\033[34m" : ""; + const char* reset = colored() ? "\033[0m" : ""; + + int gutter = snprintf(NULL, 0, "%u", token.line); + + fprintf(stderr, "%s%serror:%s %s%s%s\n", bold, red, reset, bold, message, reset); + fprintf(stderr, "%*s %s-->%s %s:%u:%u\n", gutter, "", blue, reset, name, token.line, column); + fprintf(stderr, "%*s %s|%s\n", gutter, "", blue, reset); + fprintf(stderr, "%s%u%s %s|%s %.*s\n", blue, token.line, reset, blue, reset, + (int)(line_end - line_start), line_start); + fprintf(stderr, "%*s %s|%s ", gutter, "", blue, reset); + + for (const char* character = line_start; character < token.start; character += 1) + fputc(*character == '\t' ? '\t' : ' ', stderr); + + fprintf(stderr, "%s", red); + for (size_t i = 0; i < span; i += 1) + fputc('^', stderr); + fprintf(stderr, "%s\n", reset); +} + +void report_error_message(const char* message) +{ + const char* bold = colored() ? "\033[1m" : ""; + const char* red = colored() ? "\033[31m" : ""; + const char* reset = colored() ? "\033[0m" : ""; + + fprintf(stderr, "%s%serror:%s %s%s%s\n", bold, red, reset, bold, message, reset); +} diff --git a/src/diag.h b/src/diag.h new file mode 100644 index 0000000..e79b465 --- /dev/null +++ b/src/diag.h @@ -0,0 +1,12 @@ +#pragma once + +#include "lexer.h" + +struct Source +{ + const char* name; + const char* text; +}; + +void report_error(struct Source source, struct Token token, const char* message); +void report_error_message(const char* message); diff --git a/src/diag/diag.c b/src/diag/diag.c deleted file mode 100644 index cc61e89..0000000 --- a/src/diag/diag.c +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include - -#include "diag/diag.h" - -static bool colored(void) -{ - return getenv("NO_COLOR") == NULL; -} - -void report_error(struct Source source, struct Token token, const char* message) -{ - const char* name = source.name != NULL ? source.name : ""; - const char* text = source.text; - - const char* line_start = token.start; - while (line_start > text && line_start[-1] != '\n') - line_start -= 1; - - const char* line_end = token.start; - while (*line_end != '\0' && *line_end != '\n') - line_end += 1; - - uint32_t column = (uint32_t)(token.start - line_start) + 1; - - size_t span = token.length > 0 ? token.length : 1; - if ((size_t)(line_end - token.start) < span) - span = (size_t)(line_end - token.start); - if (span == 0) - span = 1; - - const char* bold = colored() ? "\033[1m" : ""; - const char* red = colored() ? "\033[31m" : ""; - const char* blue = colored() ? "\033[34m" : ""; - const char* reset = colored() ? "\033[0m" : ""; - - int gutter = snprintf(NULL, 0, "%u", token.line); - - fprintf(stderr, "%s%serror:%s %s%s%s\n", bold, red, reset, bold, message, reset); - fprintf(stderr, "%*s %s-->%s %s:%u:%u\n", gutter, "", blue, reset, name, token.line, column); - fprintf(stderr, "%*s %s|%s\n", gutter, "", blue, reset); - fprintf(stderr, "%s%u%s %s|%s %.*s\n", blue, token.line, reset, blue, reset, - (int)(line_end - line_start), line_start); - fprintf(stderr, "%*s %s|%s ", gutter, "", blue, reset); - - for (const char* character = line_start; character < token.start; character += 1) - fputc(*character == '\t' ? '\t' : ' ', stderr); - - fprintf(stderr, "%s", red); - for (size_t i = 0; i < span; i += 1) - fputc('^', stderr); - fprintf(stderr, "%s\n", reset); -} - -void report_error_message(const char* message) -{ - const char* bold = colored() ? "\033[1m" : ""; - const char* red = colored() ? "\033[31m" : ""; - const char* reset = colored() ? "\033[0m" : ""; - - fprintf(stderr, "%s%serror:%s %s%s%s\n", bold, red, reset, bold, message, reset); -} diff --git a/src/diag/diag.h b/src/diag/diag.h deleted file mode 100644 index 7a99266..0000000 --- a/src/diag/diag.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "lexer/lexer.h" - -struct Source -{ - const char* name; - const char* text; -}; - -void report_error(struct Source source, struct Token token, const char* message); -void report_error_message(const char* message); diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..a122468 --- /dev/null +++ b/src/file.c @@ -0,0 +1,69 @@ +#include +#include + +#include "diag.h" +#include "file.h" + +static void report_file_error(const char* verb, const char* path) +{ + char message[512]; + snprintf(message, sizeof(message), "%s '%s'", verb, path); + report_error_message(message); +} + +bool read_file(const char* path, struct File* out) +{ + FILE* stream = fopen(path, "rb"); + if (stream == NULL) + { + report_file_error("could not open", path); + return false; + } + + if (fseek(stream, 0, SEEK_END) != 0) + { + report_file_error("could not read", path); + fclose(stream); + return false; + } + + long length = ftell(stream); + if (length < 0) + { + report_file_error("could not read", path); + fclose(stream); + return false; + } + rewind(stream); + + size_t size = (size_t)length; + char* data = malloc(size + 1); + if (data == NULL) + { + report_file_error("out of memory reading", path); + fclose(stream); + return false; + } + + if (fread(data, 1, size, stream) != size) + { + report_file_error("could not read", path); + free(data); + fclose(stream); + return false; + } + + data[size] = '\0'; + fclose(stream); + + out->data = data; + out->size = size; + return true; +} + +void free_file(struct File* file) +{ + free(file->data); + file->data = NULL; + file->size = 0; +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..7d15bd0 --- /dev/null +++ b/src/file.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +struct File +{ + char* data; + size_t size; +}; + +bool read_file(const char* path, struct File* out); +void free_file(struct File* file); diff --git a/src/io/file.c b/src/io/file.c deleted file mode 100644 index 3d7083c..0000000 --- a/src/io/file.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include - -#include "diag/diag.h" -#include "io/file.h" - -static void report_file_error(const char* verb, const char* path) -{ - char message[512]; - snprintf(message, sizeof(message), "%s '%s'", verb, path); - report_error_message(message); -} - -bool read_file(const char* path, struct File* out) -{ - FILE* stream = fopen(path, "rb"); - if (stream == NULL) - { - report_file_error("could not open", path); - return false; - } - - if (fseek(stream, 0, SEEK_END) != 0) - { - report_file_error("could not read", path); - fclose(stream); - return false; - } - - long length = ftell(stream); - if (length < 0) - { - report_file_error("could not read", path); - fclose(stream); - return false; - } - rewind(stream); - - size_t size = (size_t)length; - char* data = malloc(size + 1); - if (data == NULL) - { - report_file_error("out of memory reading", path); - fclose(stream); - return false; - } - - if (fread(data, 1, size, stream) != size) - { - report_file_error("could not read", path); - free(data); - fclose(stream); - return false; - } - - data[size] = '\0'; - fclose(stream); - - out->data = data; - out->size = size; - return true; -} - -void free_file(struct File* file) -{ - free(file->data); - file->data = NULL; - file->size = 0; -} diff --git a/src/io/file.h b/src/io/file.h deleted file mode 100644 index 7d15bd0..0000000 --- a/src/io/file.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include - -struct File -{ - char* data; - size_t size; -}; - -bool read_file(const char* path, struct File* out); -void free_file(struct File* file); diff --git a/src/lexer.c b/src/lexer.c new file mode 100644 index 0000000..0d26f9a --- /dev/null +++ b/src/lexer.c @@ -0,0 +1,311 @@ +#include +#include + +#include "lexer.h" + +static bool is_alpha(char character) +{ + return (character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z') || + character == '_'; +} + +static bool is_digit(char character) +{ + return character >= '0' && character <= '9'; +} + +static bool is_hex_digit(char character) +{ + return is_digit(character) + || (character >= 'a' && character <= 'f') + || (character >= 'A' && character <= 'F'); +} + +static enum TokenType identifier_type(const char* start, size_t length) +{ + static const struct Keyword + { + const char* text; + size_t length; + enum TokenType type; + } keywords[] = + { + { "const", 5, TOKEN_CONST }, + { "data", 4, TOKEN_DATA }, + { "proc", 4, TOKEN_PROC }, + { "enum", 4, TOKEN_ENUM }, + { "struct", 6, TOKEN_STRUCT }, + { "stack", 5, TOKEN_STACK }, + { "if", 2, TOKEN_IF }, + { "else", 4, TOKEN_ELSE }, + { "goto", 4, TOKEN_GOTO }, + { "syscall", 7, TOKEN_SYSCALL }, + { "byte", 4, TOKEN_BYTE }, + { "word", 4, TOKEN_WORD }, + { "dword", 5, TOKEN_DWORD }, + { "qword", 5, TOKEN_QWORD }, + { "signed", 6, TOKEN_SIGNED }, + }; + + for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i += 1) + { + const struct Keyword* keyword = &keywords[i]; + if (keyword->length == length && memcmp(start, keyword->text, length) == 0) + return keyword->type; + } + + return TOKEN_IDENTIFIER; +} + +static char peek(struct Lexer* lexer) +{ + return *lexer->current; +} + +static char advance(struct Lexer* lexer) +{ + char c = *lexer->current; + lexer->current += 1; + return c; +} + +static bool match(struct Lexer* lexer, char expected) +{ + if (*lexer->current != expected) + return false; + lexer->current += 1; + return true; +} + +static void skip_whitespace(struct Lexer* lexer) +{ + for (;;) + { + char c = peek(lexer); + if (c == ' ' || c == '\t' || c == '\r') + advance(lexer); + else if (c == '\n') + { + lexer->line += 1; + advance(lexer); + } + else if (c == '/' && lexer->current[1] == '/') + { + while (peek(lexer) != '\n' && peek(lexer) != '\0') + advance(lexer); + } + else if (c == '/' && lexer->current[1] == '*') + { + advance(lexer); + advance(lexer); + while (!(peek(lexer) == '*' && lexer->current[1] == '/') && peek(lexer) != '\0') + { + if (peek(lexer) == '\n') + lexer->line += 1; + advance(lexer); + } + if (peek(lexer) != '\0') + { + advance(lexer); + advance(lexer); + } + } + else + return; + } +} + +static struct Token make_token(const struct Lexer* lexer, enum TokenType type, const char* start) +{ + struct Token token; + token.type = type; + token.start = start; + token.length = (size_t)(lexer->current - start); + token.line = lexer->line; + return token; +} + +struct Lexer create_lexer(const char* source) +{ + struct Lexer lexer; + lexer.name = NULL; + lexer.source = source; + lexer.current = source; + lexer.line = 1; + return lexer; +} + +static struct Token scan_string(struct Lexer* lexer, const char* start) +{ + while (peek(lexer) != '"') + { + char character = peek(lexer); + if (character == '\0') + return make_token(lexer, TOKEN_UNKNOWN, start); + if (character == '\n') + lexer->line += 1; + if (character == '\\' && lexer->current[1] != '\0') + advance(lexer); + advance(lexer); + } + + advance(lexer); + return make_token(lexer, TOKEN_STRING, start); +} + +static struct Token scan_char(struct Lexer* lexer, const char* start) +{ + while (peek(lexer) != '\'') + { + char character = peek(lexer); + if (character == '\0' || character == '\n') + return make_token(lexer, TOKEN_UNKNOWN, start); + if (character == '\\' && lexer->current[1] != '\0') + advance(lexer); + advance(lexer); + } + + advance(lexer); + return make_token(lexer, TOKEN_CHAR, start); +} + +struct Token scan_token(struct Lexer* lexer) +{ + skip_whitespace(lexer); + + const char* start = lexer->current; + if (peek(lexer) == '\0') + return make_token(lexer, TOKEN_EOF, start); + char character = advance(lexer); + + if (is_alpha(character)) + { + while (is_alpha(peek(lexer)) || is_digit(peek(lexer))) + advance(lexer); + return make_token(lexer, identifier_type(start, (size_t)(lexer->current - start)), start); + } + + if (is_digit(character)) + { + if (character == '0' && (peek(lexer) == 'x' || peek(lexer) == 'X')) + { + advance(lexer); + while (is_hex_digit(peek(lexer))) + advance(lexer); + return make_token(lexer, TOKEN_INTEGER, start); + } + + if (character == '0' && (peek(lexer) == 'b' || peek(lexer) == 'B')) + { + advance(lexer); + while (peek(lexer) == '0' || peek(lexer) == '1') + advance(lexer); + return make_token(lexer, TOKEN_INTEGER, start); + } + + while (is_digit(peek(lexer))) + advance(lexer); + + if (peek(lexer) == '.' && is_digit(lexer->current[1])) + { + advance(lexer); + while (is_digit(peek(lexer))) + advance(lexer); + return make_token(lexer, TOKEN_FLOAT, start); + } + + return make_token(lexer, TOKEN_INTEGER, start); + } + + if (character == '"') + return scan_string(lexer, start); + if (character == '\'') + return scan_char(lexer, start); + + switch (character) + { + case '=': return make_token(lexer, match(lexer, '=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL, start); + case '!': return make_token(lexer, match(lexer, '=') ? TOKEN_BANG_EQUAL : TOKEN_BANG, start); + case '<': return make_token(lexer, match(lexer, '=') ? TOKEN_LESS_EQUAL : TOKEN_LESS, start); + case '>': return make_token(lexer, match(lexer, '=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER, start); + case '+': return make_token(lexer, match(lexer, '=') ? TOKEN_PLUS_EQUAL : TOKEN_PLUS, start); + case '-': return make_token(lexer, match(lexer, '=') ? TOKEN_MINUS_EQUAL : TOKEN_MINUS, start); + case '*': return make_token(lexer, match(lexer, '=') ? TOKEN_STAR_EQUAL : TOKEN_STAR, start); + case '/': return make_token(lexer, match(lexer, '=') ? TOKEN_SLASH_EQUAL : TOKEN_SLASH, start); + case '%': return make_token(lexer, match(lexer, '=') ? TOKEN_PERCENT_EQUAL : TOKEN_PERCENT, start); + + case '^': return make_token(lexer, TOKEN_CARET, start); + case '.': return make_token(lexer, TOKEN_DOT, start); + case ',': return make_token(lexer, TOKEN_COMMA, start); + case ':': return make_token(lexer, TOKEN_COLON, start); + case '(': return make_token(lexer, TOKEN_LEFT_PAREN, start); + case ')': return make_token(lexer, TOKEN_RIGHT_PAREN, start); + case '[': return make_token(lexer, TOKEN_LEFT_BRACKET, start); + case ']': return make_token(lexer, TOKEN_RIGHT_BRACKET, start); + case '{': return make_token(lexer, TOKEN_LEFT_BRACE, start); + case '}': return make_token(lexer, TOKEN_RIGHT_BRACE, start); + } + + return make_token(lexer, TOKEN_UNKNOWN, start); +} + +const char* token_type_name(enum TokenType type) +{ + switch (type) + { + case TOKEN_EOF: return "eof"; + case TOKEN_IDENTIFIER: return "identifier"; + case TOKEN_INTEGER: return "integer"; + case TOKEN_FLOAT: return "float"; + case TOKEN_STRING: return "string"; + case TOKEN_CHAR: return "char"; + case TOKEN_CONST: return "const"; + case TOKEN_DATA: return "data"; + case TOKEN_PROC: return "proc"; + case TOKEN_ENUM: return "enum"; + case TOKEN_STRUCT: return "struct"; + case TOKEN_STACK: return "stack"; + case TOKEN_IF: return "if"; + case TOKEN_ELSE: return "else"; + case TOKEN_GOTO: return "goto"; + case TOKEN_SYSCALL: return "syscall"; + case TOKEN_BYTE: return "byte"; + case TOKEN_WORD: return "word"; + case TOKEN_DWORD: return "dword"; + case TOKEN_QWORD: return "qword"; + case TOKEN_SIGNED: return "signed"; + case TOKEN_EQUAL: return "equal"; + case TOKEN_PLUS: return "plus"; + case TOKEN_MINUS: return "minus"; + case TOKEN_STAR: return "star"; + case TOKEN_SLASH: return "slash"; + case TOKEN_PERCENT: return "percent"; + case TOKEN_CARET: return "caret"; + case TOKEN_BANG: return "bang"; + case TOKEN_LESS: return "less"; + case TOKEN_GREATER: return "greater"; + case TOKEN_DOT: return "dot"; + case TOKEN_COMMA: return "comma"; + case TOKEN_COLON: return "colon"; + case TOKEN_LEFT_PAREN: return "left_paren"; + case TOKEN_RIGHT_PAREN: return "right_paren"; + case TOKEN_LEFT_BRACKET: return "left_bracket"; + case TOKEN_RIGHT_BRACKET: return "right_bracket"; + case TOKEN_LEFT_BRACE: return "left_brace"; + case TOKEN_RIGHT_BRACE: return "right_brace"; + case TOKEN_EQUAL_EQUAL: return "equal_equal"; + case TOKEN_BANG_EQUAL: return "bang_equal"; + case TOKEN_PLUS_EQUAL: return "plus_equal"; + case TOKEN_MINUS_EQUAL: return "minus_equal"; + case TOKEN_STAR_EQUAL: return "star_equal"; + case TOKEN_SLASH_EQUAL: return "slash_equal"; + case TOKEN_PERCENT_EQUAL: return "percent_equal"; + case TOKEN_LESS_EQUAL: return "less_equal"; + case TOKEN_GREATER_EQUAL: return "greater_equal"; + case TOKEN_UNKNOWN: return "unknown"; + } + + return "unknown"; +} + diff --git a/src/lexer.h b/src/lexer.h new file mode 100644 index 0000000..73de02f --- /dev/null +++ b/src/lexer.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +enum TokenType +{ + TOKEN_EOF, + TOKEN_IDENTIFIER, + TOKEN_INTEGER, + TOKEN_FLOAT, + TOKEN_STRING, + TOKEN_CHAR, + + TOKEN_CONST, + TOKEN_DATA, + TOKEN_PROC, + TOKEN_ENUM, + TOKEN_STRUCT, + TOKEN_STACK, + TOKEN_IF, + TOKEN_ELSE, + TOKEN_GOTO, + TOKEN_SYSCALL, + TOKEN_BYTE, + TOKEN_WORD, + TOKEN_DWORD, + TOKEN_QWORD, + TOKEN_SIGNED, + + TOKEN_EQUAL, + TOKEN_PLUS, + TOKEN_MINUS, + TOKEN_STAR, + TOKEN_SLASH, + TOKEN_PERCENT, + TOKEN_CARET, + TOKEN_BANG, + TOKEN_LESS, + TOKEN_GREATER, + TOKEN_DOT, + TOKEN_COMMA, + TOKEN_COLON, + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LEFT_BRACE, + TOKEN_RIGHT_BRACE, + + TOKEN_EQUAL_EQUAL, + TOKEN_BANG_EQUAL, + TOKEN_PLUS_EQUAL, + TOKEN_MINUS_EQUAL, + TOKEN_STAR_EQUAL, + TOKEN_SLASH_EQUAL, + TOKEN_PERCENT_EQUAL, + TOKEN_LESS_EQUAL, + TOKEN_GREATER_EQUAL, + + TOKEN_UNKNOWN, +}; + +struct Token +{ + enum TokenType type; + const char* start; + size_t length; + uint32_t line; +}; + +struct Lexer +{ + const char* name; + const char* source; + const char* current; + uint32_t line; +}; + +struct Lexer create_lexer(const char* source); +struct Token scan_token(struct Lexer* lexer); +const char* token_type_name(enum TokenType type); diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c deleted file mode 100644 index 4d468e6..0000000 --- a/src/lexer/lexer.c +++ /dev/null @@ -1,311 +0,0 @@ -#include -#include - -#include "lexer/lexer.h" - -static bool is_alpha(char character) -{ - return (character >= 'a' && character <= 'z') || - (character >= 'A' && character <= 'Z') || - character == '_'; -} - -static bool is_digit(char character) -{ - return character >= '0' && character <= '9'; -} - -static bool is_hex_digit(char character) -{ - return is_digit(character) - || (character >= 'a' && character <= 'f') - || (character >= 'A' && character <= 'F'); -} - -static enum TokenType identifier_type(const char* start, size_t length) -{ - static const struct Keyword - { - const char* text; - size_t length; - enum TokenType type; - } keywords[] = - { - { "const", 5, TOKEN_CONST }, - { "data", 4, TOKEN_DATA }, - { "proc", 4, TOKEN_PROC }, - { "enum", 4, TOKEN_ENUM }, - { "struct", 6, TOKEN_STRUCT }, - { "stack", 5, TOKEN_STACK }, - { "if", 2, TOKEN_IF }, - { "else", 4, TOKEN_ELSE }, - { "goto", 4, TOKEN_GOTO }, - { "syscall", 7, TOKEN_SYSCALL }, - { "byte", 4, TOKEN_BYTE }, - { "word", 4, TOKEN_WORD }, - { "dword", 5, TOKEN_DWORD }, - { "qword", 5, TOKEN_QWORD }, - { "signed", 6, TOKEN_SIGNED }, - }; - - for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i += 1) - { - const struct Keyword* keyword = &keywords[i]; - if (keyword->length == length && memcmp(start, keyword->text, length) == 0) - return keyword->type; - } - - return TOKEN_IDENTIFIER; -} - -static char peek(struct Lexer* lexer) -{ - return *lexer->current; -} - -static char advance(struct Lexer* lexer) -{ - char c = *lexer->current; - lexer->current += 1; - return c; -} - -static bool match(struct Lexer* lexer, char expected) -{ - if (*lexer->current != expected) - return false; - lexer->current += 1; - return true; -} - -static void skip_whitespace(struct Lexer* lexer) -{ - for (;;) - { - char c = peek(lexer); - if (c == ' ' || c == '\t' || c == '\r') - advance(lexer); - else if (c == '\n') - { - lexer->line += 1; - advance(lexer); - } - else if (c == '/' && lexer->current[1] == '/') - { - while (peek(lexer) != '\n' && peek(lexer) != '\0') - advance(lexer); - } - else if (c == '/' && lexer->current[1] == '*') - { - advance(lexer); - advance(lexer); - while (!(peek(lexer) == '*' && lexer->current[1] == '/') && peek(lexer) != '\0') - { - if (peek(lexer) == '\n') - lexer->line += 1; - advance(lexer); - } - if (peek(lexer) != '\0') - { - advance(lexer); - advance(lexer); - } - } - else - return; - } -} - -static struct Token make_token(const struct Lexer* lexer, enum TokenType type, const char* start) -{ - struct Token token; - token.type = type; - token.start = start; - token.length = (size_t)(lexer->current - start); - token.line = lexer->line; - return token; -} - -struct Lexer create_lexer(const char* source) -{ - struct Lexer lexer; - lexer.name = NULL; - lexer.source = source; - lexer.current = source; - lexer.line = 1; - return lexer; -} - -static struct Token scan_string(struct Lexer* lexer, const char* start) -{ - while (peek(lexer) != '"') - { - char character = peek(lexer); - if (character == '\0') - return make_token(lexer, TOKEN_UNKNOWN, start); - if (character == '\n') - lexer->line += 1; - if (character == '\\' && lexer->current[1] != '\0') - advance(lexer); - advance(lexer); - } - - advance(lexer); - return make_token(lexer, TOKEN_STRING, start); -} - -static struct Token scan_char(struct Lexer* lexer, const char* start) -{ - while (peek(lexer) != '\'') - { - char character = peek(lexer); - if (character == '\0' || character == '\n') - return make_token(lexer, TOKEN_UNKNOWN, start); - if (character == '\\' && lexer->current[1] != '\0') - advance(lexer); - advance(lexer); - } - - advance(lexer); - return make_token(lexer, TOKEN_CHAR, start); -} - -struct Token scan_token(struct Lexer* lexer) -{ - skip_whitespace(lexer); - - const char* start = lexer->current; - if (peek(lexer) == '\0') - return make_token(lexer, TOKEN_EOF, start); - char character = advance(lexer); - - if (is_alpha(character)) - { - while (is_alpha(peek(lexer)) || is_digit(peek(lexer))) - advance(lexer); - return make_token(lexer, identifier_type(start, (size_t)(lexer->current - start)), start); - } - - if (is_digit(character)) - { - if (character == '0' && (peek(lexer) == 'x' || peek(lexer) == 'X')) - { - advance(lexer); - while (is_hex_digit(peek(lexer))) - advance(lexer); - return make_token(lexer, TOKEN_INTEGER, start); - } - - if (character == '0' && (peek(lexer) == 'b' || peek(lexer) == 'B')) - { - advance(lexer); - while (peek(lexer) == '0' || peek(lexer) == '1') - advance(lexer); - return make_token(lexer, TOKEN_INTEGER, start); - } - - while (is_digit(peek(lexer))) - advance(lexer); - - if (peek(lexer) == '.' && is_digit(lexer->current[1])) - { - advance(lexer); - while (is_digit(peek(lexer))) - advance(lexer); - return make_token(lexer, TOKEN_FLOAT, start); - } - - return make_token(lexer, TOKEN_INTEGER, start); - } - - if (character == '"') - return scan_string(lexer, start); - if (character == '\'') - return scan_char(lexer, start); - - switch (character) - { - case '=': return make_token(lexer, match(lexer, '=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL, start); - case '!': return make_token(lexer, match(lexer, '=') ? TOKEN_BANG_EQUAL : TOKEN_BANG, start); - case '<': return make_token(lexer, match(lexer, '=') ? TOKEN_LESS_EQUAL : TOKEN_LESS, start); - case '>': return make_token(lexer, match(lexer, '=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER, start); - case '+': return make_token(lexer, match(lexer, '=') ? TOKEN_PLUS_EQUAL : TOKEN_PLUS, start); - case '-': return make_token(lexer, match(lexer, '=') ? TOKEN_MINUS_EQUAL : TOKEN_MINUS, start); - case '*': return make_token(lexer, match(lexer, '=') ? TOKEN_STAR_EQUAL : TOKEN_STAR, start); - case '/': return make_token(lexer, match(lexer, '=') ? TOKEN_SLASH_EQUAL : TOKEN_SLASH, start); - case '%': return make_token(lexer, match(lexer, '=') ? TOKEN_PERCENT_EQUAL : TOKEN_PERCENT, start); - - case '^': return make_token(lexer, TOKEN_CARET, start); - case '.': return make_token(lexer, TOKEN_DOT, start); - case ',': return make_token(lexer, TOKEN_COMMA, start); - case ':': return make_token(lexer, TOKEN_COLON, start); - case '(': return make_token(lexer, TOKEN_LEFT_PAREN, start); - case ')': return make_token(lexer, TOKEN_RIGHT_PAREN, start); - case '[': return make_token(lexer, TOKEN_LEFT_BRACKET, start); - case ']': return make_token(lexer, TOKEN_RIGHT_BRACKET, start); - case '{': return make_token(lexer, TOKEN_LEFT_BRACE, start); - case '}': return make_token(lexer, TOKEN_RIGHT_BRACE, start); - } - - return make_token(lexer, TOKEN_UNKNOWN, start); -} - -const char* token_type_name(enum TokenType type) -{ - switch (type) - { - case TOKEN_EOF: return "eof"; - case TOKEN_IDENTIFIER: return "identifier"; - case TOKEN_INTEGER: return "integer"; - case TOKEN_FLOAT: return "float"; - case TOKEN_STRING: return "string"; - case TOKEN_CHAR: return "char"; - case TOKEN_CONST: return "const"; - case TOKEN_DATA: return "data"; - case TOKEN_PROC: return "proc"; - case TOKEN_ENUM: return "enum"; - case TOKEN_STRUCT: return "struct"; - case TOKEN_STACK: return "stack"; - case TOKEN_IF: return "if"; - case TOKEN_ELSE: return "else"; - case TOKEN_GOTO: return "goto"; - case TOKEN_SYSCALL: return "syscall"; - case TOKEN_BYTE: return "byte"; - case TOKEN_WORD: return "word"; - case TOKEN_DWORD: return "dword"; - case TOKEN_QWORD: return "qword"; - case TOKEN_SIGNED: return "signed"; - case TOKEN_EQUAL: return "equal"; - case TOKEN_PLUS: return "plus"; - case TOKEN_MINUS: return "minus"; - case TOKEN_STAR: return "star"; - case TOKEN_SLASH: return "slash"; - case TOKEN_PERCENT: return "percent"; - case TOKEN_CARET: return "caret"; - case TOKEN_BANG: return "bang"; - case TOKEN_LESS: return "less"; - case TOKEN_GREATER: return "greater"; - case TOKEN_DOT: return "dot"; - case TOKEN_COMMA: return "comma"; - case TOKEN_COLON: return "colon"; - case TOKEN_LEFT_PAREN: return "left_paren"; - case TOKEN_RIGHT_PAREN: return "right_paren"; - case TOKEN_LEFT_BRACKET: return "left_bracket"; - case TOKEN_RIGHT_BRACKET: return "right_bracket"; - case TOKEN_LEFT_BRACE: return "left_brace"; - case TOKEN_RIGHT_BRACE: return "right_brace"; - case TOKEN_EQUAL_EQUAL: return "equal_equal"; - case TOKEN_BANG_EQUAL: return "bang_equal"; - case TOKEN_PLUS_EQUAL: return "plus_equal"; - case TOKEN_MINUS_EQUAL: return "minus_equal"; - case TOKEN_STAR_EQUAL: return "star_equal"; - case TOKEN_SLASH_EQUAL: return "slash_equal"; - case TOKEN_PERCENT_EQUAL: return "percent_equal"; - case TOKEN_LESS_EQUAL: return "less_equal"; - case TOKEN_GREATER_EQUAL: return "greater_equal"; - case TOKEN_UNKNOWN: return "unknown"; - } - - return "unknown"; -} - diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h deleted file mode 100644 index 73de02f..0000000 --- a/src/lexer/lexer.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include - -enum TokenType -{ - TOKEN_EOF, - TOKEN_IDENTIFIER, - TOKEN_INTEGER, - TOKEN_FLOAT, - TOKEN_STRING, - TOKEN_CHAR, - - TOKEN_CONST, - TOKEN_DATA, - TOKEN_PROC, - TOKEN_ENUM, - TOKEN_STRUCT, - TOKEN_STACK, - TOKEN_IF, - TOKEN_ELSE, - TOKEN_GOTO, - TOKEN_SYSCALL, - TOKEN_BYTE, - TOKEN_WORD, - TOKEN_DWORD, - TOKEN_QWORD, - TOKEN_SIGNED, - - TOKEN_EQUAL, - TOKEN_PLUS, - TOKEN_MINUS, - TOKEN_STAR, - TOKEN_SLASH, - TOKEN_PERCENT, - TOKEN_CARET, - TOKEN_BANG, - TOKEN_LESS, - TOKEN_GREATER, - TOKEN_DOT, - TOKEN_COMMA, - TOKEN_COLON, - TOKEN_LEFT_PAREN, - TOKEN_RIGHT_PAREN, - TOKEN_LEFT_BRACKET, - TOKEN_RIGHT_BRACKET, - TOKEN_LEFT_BRACE, - TOKEN_RIGHT_BRACE, - - TOKEN_EQUAL_EQUAL, - TOKEN_BANG_EQUAL, - TOKEN_PLUS_EQUAL, - TOKEN_MINUS_EQUAL, - TOKEN_STAR_EQUAL, - TOKEN_SLASH_EQUAL, - TOKEN_PERCENT_EQUAL, - TOKEN_LESS_EQUAL, - TOKEN_GREATER_EQUAL, - - TOKEN_UNKNOWN, -}; - -struct Token -{ - enum TokenType type; - const char* start; - size_t length; - uint32_t line; -}; - -struct Lexer -{ - const char* name; - const char* source; - const char* current; - uint32_t line; -}; - -struct Lexer create_lexer(const char* source); -struct Token scan_token(struct Lexer* lexer); -const char* token_type_name(enum TokenType type); diff --git a/src/main.c b/src/main.c index 71e8918..4153a6e 100644 --- a/src/main.c +++ b/src/main.c @@ -1,12 +1,12 @@ #include -#include "io/file.h" -#include "cli/args.h" -#include "diag/diag.h" -#include "sema/sema.h" -#include "lexer/lexer.h" -#include "codegen/nasm.h" -#include "parser/parser.h" +#include "file.h" +#include "args.h" +#include "diag.h" +#include "sema.h" +#include "lexer.h" +#include "nasm.h" +#include "parser.h" int main(int argc, char** argv) { diff --git a/src/nasm.c b/src/nasm.c new file mode 100644 index 0000000..15f5f01 --- /dev/null +++ b/src/nasm.c @@ -0,0 +1,1252 @@ +#include +#include +#include +#include +#include +#include + +#include "nasm.h" + +static void emit_const_expr(struct Expr* expr, FILE* out) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start); + break; + case EXPR_UNARY: + fprintf(out, "%.*s", (int)expr->unary.op.length, expr->unary.op.start); + emit_const_expr(expr->unary.operand, out); + break; + case EXPR_BINARY: + emit_const_expr(expr->binary.left, out); + fprintf(out, " %.*s ", (int)expr->binary.op.length, expr->binary.op.start); + emit_const_expr(expr->binary.right, out); + break; + default: + break; + } +} + +static void emit_consts(struct Program* program, FILE* out) +{ + for (size_t i = 0; i < program->const_count; i += 1) + { + struct ConstDecl decl = program->consts[i]; + fprintf(out, "%%define %.*s (", (int)decl.name.length, decl.name.start); + emit_const_expr(decl.value, out); + fprintf(out, ")\n"); + } +} + +static void emit_data(struct Program* program, FILE* out) +{ + fprintf(out, "section .data\n"); + + for (size_t i = 0; i < program->data_count; i += 1) + { + struct DataDecl decl = program->data_decls[i]; + + // the value lexeme keeps its surrounding double quotes; NASM backtick + // strings interpret the same escapes, so re-wrap the inner content + fprintf(out, "%.*s: db `%.*s`\n", + (int)decl.name.length, decl.name.start, + (int)(decl.value.length - 2), decl.value.start + 1); + fprintf(out, ".len equ $ - %.*s\n", + (int)decl.name.length, decl.name.start); + } +} + +static const char* assign_mnemonic(enum TokenType op) +{ + switch (op) + { + case TOKEN_EQUAL: return "mov"; + case TOKEN_PLUS_EQUAL: return "add"; + case TOKEN_MINUS_EQUAL: return "sub"; + case TOKEN_STAR_EQUAL: return "imul"; + default: return NULL; + } +} + +struct FloatTable +{ + struct Token* items; + size_t count; + size_t capacity; +}; + +struct Emitter +{ + struct Program* program; + struct ProcDecl* proc; + struct FloatTable* floats; + FILE* out; + uint32_t label_id; +}; + +static bool is_float_register(struct Token token) +{ + if (token.length < 4 || memcmp(token.start, "xmm", 3) != 0) + return false; + + for (size_t i = 3; i < token.length; i += 1) + if (token.start[i] < '0' || token.start[i] > '9') + return false; + + return true; +} + +static size_t float_index(const struct FloatTable* floats, struct Token literal) +{ + for (size_t i = 0; i < floats->count; i += 1) + if (floats->items[i].length == literal.length + && memcmp(floats->items[i].start, literal.start, literal.length) == 0) + return i; + + return floats->count; +} + +static const char* sized_register(struct Token reg, enum StoreSize size); + +static struct Token resolve_token(struct Emitter* emitter, struct Token token) +{ + if (emitter->proc == NULL) + return token; + + for (size_t i = 0; i < emitter->proc->param_count; i += 1) + { + struct Param param = emitter->proc->params[i]; + if (param.name.length == token.length && memcmp(param.name.start, token.start, token.length) == 0) + return param.reg; + } + + return token; +} + +static struct Token text_token(const char* text) +{ + struct Token token; + token.type = TOKEN_IDENTIFIER; + token.start = text; + token.length = strlen(text); + token.line = 0; + return token; +} + +// with the logical_registers extension, r1..r14 name the general-purpose +// registers; rsp/rbp and the instruction pointer keep their dedicated names. +static const char* logical_register_base(struct Token token) +{ + static const char* registers[] = { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + }; + + if (token.length < 2 || token.start[0] != 'r') + return NULL; + + uint32_t index = 0; + for (size_t i = 1; i < token.length; i += 1) + { + char digit = token.start[i]; + if (digit < '0' || digit > '9') + return NULL; + index = index * 10 + (uint32_t)(digit - '0'); + } + + if (index < 1 || index > sizeof(registers) / sizeof(registers[0])) + return NULL; + + return registers[index - 1]; +} + +static struct Token resolve_register(struct Emitter* emitter, struct Token token) +{ + struct Token resolved = resolve_token(emitter, token); + + if (emitter->program->config.logical_registers) + { + const char* base = logical_register_base(resolved); + if (base != NULL) + return text_token(base); + } + + return resolved; +} + +static uint64_t token_to_u64(struct Token token) +{ + if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'x' || token.start[1] == 'X')) + { + uint64_t value = 0; + for (size_t i = 2; i < token.length; i += 1) + { + char digit = token.start[i]; + uint64_t nibble = digit <= '9' ? (uint64_t)(digit - '0') + : (uint64_t)((digit | 0x20) - 'a' + 10); + value = value * 16 + nibble; + } + return value; + } + + if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'b' || token.start[1] == 'B')) + { + uint64_t value = 0; + for (size_t i = 2; i < token.length; i += 1) + value = value * 2 + (uint64_t)(token.start[i] - '0'); + return value; + } + + uint64_t value = 0; + for (size_t i = 0; i < token.length; i += 1) + value = value * 10 + (uint64_t)(token.start[i] - '0'); + + return value; +} + +static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out); + +static bool buffer_offset(struct Emitter* emitter, struct Token name, uint64_t* out_offset) +{ + struct ProcDecl* proc = emitter->proc; + uint64_t cumulative = 0; + for (size_t i = 0; i < proc->body_count; i += 1) + { + struct Statement* statement = &proc->body[i]; + if (statement->kind != STATEMENT_STACK) + continue; + + uint64_t size = 0; + fold_const(emitter->program, statement->stack.size, &size); + cumulative += size; + if (statement->stack.name.length == name.length + && memcmp(statement->stack.name.start, name.start, name.length) == 0) + { + *out_offset = cumulative; + return true; + } + } + + return false; +} + +static bool is_buffer_name(struct Emitter* emitter, struct Token token) +{ + uint64_t offset; + return emitter->proc != NULL && buffer_offset(emitter, token, &offset); +} + +static enum StoreSize size_from_int(struct Token token) +{ + switch (token_to_u64(token)) + { + case 8: return STORE_SIZE_BYTE; + case 16: return STORE_SIZE_WORD; + case 32: return STORE_SIZE_DWORD; + case 64: return STORE_SIZE_QWORD; + default: return STORE_SIZE_NONE; + } +} + +static bool tokens_equal(struct Token a, struct Token b) +{ + return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; +} + +static bool token_matches(struct Token token, const char* text) +{ + size_t length = strlen(text); + return token.length == length && memcmp(token.start, text, length) == 0; +} + +static struct EnumDecl* find_enum(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->enum_count; i += 1) + if (tokens_equal(program->enums[i].name, name)) + return &program->enums[i]; + + return NULL; +} + +static struct StructDecl* find_struct(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->struct_count; i += 1) + if (tokens_equal(program->structs[i].name, name)) + return &program->structs[i]; + + return NULL; +} + +static uint64_t store_size_bytes(enum StoreSize size) +{ + switch (size) + { + case STORE_SIZE_BYTE: return 1; + case STORE_SIZE_WORD: return 2; + case STORE_SIZE_DWORD: return 4; + default: return 8; + } +} + +static uint64_t char_literal_value(struct Token token) +{ + if (token.length >= 4 && token.start[1] == '\\') + { + switch (token.start[2]) + { + case 'n': return 10; + case 't': return 9; + case 'r': return 13; + case '0': return 0; + case '\\': return 92; + case '\'': return 39; + default: return (unsigned char)token.start[2]; + } + } + + return (unsigned char)token.start[1]; +} + +static bool fold_member(struct Program* program, struct Expr* object, struct Token member, uint64_t* out) +{ + if (object->kind != EXPR_PRIMARY) + return false; + struct Token name = object->primary.token; + + const struct EnumDecl* enumeration = find_enum(program, name); + if (enumeration != NULL) + { + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (tokens_equal(enumeration->members[i], member)) + { + *out = i; + return true; + } + return false; + } + + const struct StructDecl* layout = find_struct(program, name); + if (layout != NULL) + { + uint64_t offset = 0; + for (size_t i = 0; i < layout->field_count; i += 1) + { + if (tokens_equal(layout->fields[i].name, member)) + { + *out = offset; + return true; + } + offset += store_size_bytes(layout->fields[i].size); + } + if (token_matches(member, "size")) + { + *out = offset; + return true; + } + } + + return false; +} + +// evaluates a compile-time constant expression: integer/char literals, other +// constants, enum values and struct offsets, and + - * / over them +static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + { + struct Token token = expr->primary.token; + if (token.type == TOKEN_INTEGER) + { + *out = token_to_u64(token); + return true; + } + if (token.type == TOKEN_CHAR) + { + *out = char_literal_value(token); + return true; + } + if (token.type == TOKEN_IDENTIFIER) + for (size_t i = 0; i < program->const_count; i += 1) + if (tokens_equal(program->consts[i].name, token)) + return fold_const(program, program->consts[i].value, out); + return false; + } + case EXPR_UNARY: + { + uint64_t value; + if (!fold_const(program, expr->unary.operand, &value)) + return false; + *out = 0 - value; + return true; + } + case EXPR_BINARY: + { + uint64_t left; + uint64_t right; + if (!fold_const(program, expr->binary.left, &left) + || !fold_const(program, expr->binary.right, &right)) + return false; + + switch (expr->binary.op.type) + { + case TOKEN_PLUS: *out = left + right; return true; + case TOKEN_MINUS: *out = left - right; return true; + case TOKEN_STAR: *out = left * right; return true; + case TOKEN_SLASH: *out = right != 0 ? left / right : 0; return true; + default: return false; + } + } + case EXPR_MEMBER: + return fold_member(program, expr->member.object, expr->member.member, out); + case EXPR_DEREF: + return false; + } + + return false; +} + +// an enum member folds to its 0-based index; a struct member folds to its byte +// offset (or the total size for `.size`) +static bool emit_named_member(struct Emitter* emitter, struct Token object, struct Token member) +{ + const struct EnumDecl* enumeration = find_enum(emitter->program, object); + if (enumeration != NULL) + { + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (tokens_equal(enumeration->members[i], member)) + { + fprintf(emitter->out, "%zu", i); + return true; + } + } + + const struct StructDecl* layout = find_struct(emitter->program, object); + if (layout != NULL) + { + uint64_t offset = 0; + for (size_t i = 0; i < layout->field_count; i += 1) + { + if (tokens_equal(layout->fields[i].name, member)) + { + fprintf(emitter->out, "%llu", (unsigned long long)offset); + return true; + } + offset += store_size_bytes(layout->fields[i].size); + } + if (token_matches(member, "size")) + { + fprintf(emitter->out, "%llu", (unsigned long long)offset); + return true; + } + } + + return false; +} + +static bool emit_operand(struct Emitter* emitter, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + { + struct Token token = resolve_register(emitter, expr->primary.token); + fprintf(emitter->out, "%.*s", (int)token.length, token.start); + return true; + } + case EXPR_MEMBER: + { + // a register size suffix: r1.64 -> rax, r1.8 -> al + if (expr->member.member.type == TOKEN_INTEGER && + expr->member.object->kind == EXPR_PRIMARY) + { + enum StoreSize size = size_from_int(expr->member.member); + struct Token base = resolve_register(emitter, expr->member.object->primary.token); + const char* sized = sized_register(base, size); + if (sized != NULL) + fprintf(emitter->out, "%s", sized); + else + fprintf(emitter->out, "%.*s", (int)base.length, base.start); + return true; + } + + // enum value or struct offset + if (expr->member.object->kind == EXPR_PRIMARY + && emit_named_member(emitter, expr->member.object->primary.token, expr->member.member)) + return true; + + if (!emit_operand(emitter, expr->member.object)) + return false; + fprintf(emitter->out, ".%.*s", (int)expr->member.member.length, expr->member.member.start); + return true; + } + case EXPR_UNARY: + { + uint64_t value; + if (!fold_const(emitter->program, expr, &value)) + return false; + fprintf(emitter->out, "%lld", (long long)value); + return true; + } + case EXPR_BINARY: + case EXPR_DEREF: + return false; + } + + return false; +} + +// idiv divides rdx:rax by its operand and leaves the quotient in rax, so a +// division computes `dst = dst / divisor` through rax (clobbering rax and rdx). +static void emit_division(struct Emitter* emitter, const char* dst, struct Expr* divisor) +{ + bool dst_is_rax = strcmp(dst, "rax") == 0; + + if (!dst_is_rax) + fprintf(emitter->out, "\tmov rax, %s\n", dst); + fprintf(emitter->out, "\tcqo\n"); + fprintf(emitter->out, "\tidiv "); + emit_operand(emitter, divisor); + fprintf(emitter->out, "\n"); + if (!dst_is_rax) + fprintf(emitter->out, "\tmov %s, rax\n", dst); +} + +// idiv leaves the remainder in rdx, so a modulo takes its result from there +static void emit_modulo(struct Emitter* emitter, const char* dst, struct Expr* divisor) +{ + if (strcmp(dst, "rax") != 0) + fprintf(emitter->out, "\tmov rax, %s\n", dst); + fprintf(emitter->out, "\tcqo\n"); + fprintf(emitter->out, "\tidiv "); + emit_operand(emitter, divisor); + fprintf(emitter->out, "\n"); + if (strcmp(dst, "rdx") != 0) + fprintf(emitter->out, "\tmov %s, rdx\n", dst); +} + +static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign) +{ + if (assign->target_deref) + { + fprintf(emitter->out, "\t; TODO: unsupported division\n"); + return; + } + + struct Token target = resolve_register(emitter, assign->target); + char dst[32]; + snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); + + if (assign->op.type == TOKEN_PERCENT_EQUAL) + emit_modulo(emitter, dst, assign->value); + else + emit_division(emitter, dst, assign->value); +} + +// an expression can be evaluated into a register when it is a single term +// (primary or member), or a left-associative chain of binary operators whose +// right operands are plain operands (never a buffer or a nested binary) +static bool expr_supported(struct Emitter* emitter, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + case EXPR_MEMBER: + case EXPR_DEREF: + return true; + case EXPR_UNARY: + { + uint64_t value; + return fold_const(emitter->program, expr, &value); + } + case EXPR_BINARY: + if (expr->binary.right->kind != EXPR_PRIMARY && + expr->binary.right->kind != EXPR_MEMBER && + expr->binary.right->kind != EXPR_UNARY) + return false; + if (expr->binary.right->kind == EXPR_UNARY) + { + uint64_t value; + if (!fold_const(emitter->program, expr->binary.right, &value)) + return false; + } + if (expr->binary.right->kind == EXPR_PRIMARY && + is_buffer_name(emitter, expr->binary.right->primary.token)) + return false; + return expr_supported(emitter, expr->binary.left); + } + + return false; +} + +// dst = [address], extending narrower loads into the full register. Unsigned +// loads zero-extend (movzx / a 32-bit mov); signed loads sign-extend (movsx, +// or movsxd for a dword). +static void emit_load(struct Emitter* emitter, const char* dst, struct DerefExpr* deref) +{ + FILE* out = emitter->out; + if (deref->is_signed) + { + switch (deref->size) + { + case STORE_SIZE_BYTE: + fprintf(out, "\tmovsx %s, byte [", dst); + break; + case STORE_SIZE_WORD: + fprintf(out, "\tmovsx %s, word [", dst); + break; + case STORE_SIZE_DWORD: + fprintf(out, "\tmovsxd %s, dword [", dst); + break; + default: + fprintf(out, "\tmov %s, [", dst); + break; + } + + emit_operand(emitter, deref->address); + fprintf(out, "]\n"); + return; + } + + switch (deref->size) + { + case STORE_SIZE_BYTE: + fprintf(out, "\tmovzx %s, byte [", dst); + break; + case STORE_SIZE_WORD: + fprintf(out, "\tmovzx %s, word [", dst); + break; + case STORE_SIZE_DWORD: + { + const char* dword = sized_register(text_token(dst), STORE_SIZE_DWORD); + fprintf(out, "\tmov %s, [", dword != NULL ? dword : dst); + break; + } + default: + fprintf(out, "\tmov %s, [", dst); + break; + } + + emit_operand(emitter, deref->address); + fprintf(out, "]\n"); +} + +static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr* expr) +{ + if (expr->kind == EXPR_DEREF) + { + emit_load(emitter, dst, &expr->deref); + return; + } + + if (expr->kind == EXPR_BINARY) + { + emit_expr_into(emitter, dst, expr->binary.left); + + if (expr->binary.op.type == TOKEN_SLASH) + { + emit_division(emitter, dst, expr->binary.right); + return; + } + if (expr->binary.op.type == TOKEN_PERCENT) + { + emit_modulo(emitter, dst, expr->binary.right); + return; + } + + const char* mnemonic = + expr->binary.op.type == TOKEN_PLUS ? "add" : + expr->binary.op.type == TOKEN_MINUS ? "sub" : "imul"; + fprintf(emitter->out, "\t%s %s, ", mnemonic, dst); + emit_operand(emitter, expr->binary.right); + fprintf(emitter->out, "\n"); + return; + } + + if (expr->kind == EXPR_PRIMARY && is_buffer_name(emitter, expr->primary.token)) + { + uint64_t offset; + buffer_offset(emitter, expr->primary.token, &offset); + fprintf(emitter->out, "\tlea %s, [rbp - %llu]\n", dst, (unsigned long long)offset); + return; + } + + fprintf(emitter->out, "\tmov %s, ", dst); + emit_operand(emitter, expr); + fprintf(emitter->out, "\n"); +} + +static const char* store_size_keyword(enum StoreSize size) +{ + switch (size) + { + case STORE_SIZE_BYTE: return "byte "; + case STORE_SIZE_WORD: return "word "; + case STORE_SIZE_DWORD: return "dword "; + case STORE_SIZE_QWORD: return "qword "; + default: return ""; + } +} + +// maps a full 64-bit register to its byte/word/dword sub-register for a sized +// store, so `^byte rsi = rdx` writes `dl` rather than the whole register. +// returns NULL when the token is not a full register, or no resizing applies. +static const char* sized_register(struct Token reg, enum StoreSize size) +{ + if (size == STORE_SIZE_NONE || size == STORE_SIZE_QWORD) + return NULL; + + static const struct RegisterSizes + { + const char* quad; + const char* dword; + const char* word; + const char* byte; + } registers[] = + { + { "rax", "eax", "ax", "al" }, + { "rbx", "ebx", "bx", "bl" }, + { "rcx", "ecx", "cx", "cl" }, + { "rdx", "edx", "dx", "dl" }, + { "rsi", "esi", "si", "sil" }, + { "rdi", "edi", "di", "dil" }, + { "rbp", "ebp", "bp", "bpl" }, + { "rsp", "esp", "sp", "spl" }, + { "r8", "r8d", "r8w", "r8b" }, + { "r9", "r9d", "r9w", "r9b" }, + { "r10", "r10d", "r10w", "r10b" }, + { "r11", "r11d", "r11w", "r11b" }, + { "r12", "r12d", "r12w", "r12b" }, + { "r13", "r13d", "r13w", "r13b" }, + { "r14", "r14d", "r14w", "r14b" }, + { "r15", "r15d", "r15w", "r15b" }, + }; + + for (size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); i += 1) + { + const struct RegisterSizes* entry = ®isters[i]; + size_t length = strlen(entry->quad); + if (reg.length != length || memcmp(reg.start, entry->quad, length) != 0) + continue; + + switch (size) + { + case STORE_SIZE_DWORD: return entry->dword; + case STORE_SIZE_WORD: return entry->word; + case STORE_SIZE_BYTE: return entry->byte; + default: return NULL; + } + } + + return NULL; +} + +static const char* float_mnemonic(enum TokenType op) +{ + switch (op) + { + case TOKEN_EQUAL: return "movsd"; + case TOKEN_PLUS_EQUAL: return "addsd"; + case TOKEN_MINUS_EQUAL: return "subsd"; + case TOKEN_STAR_EQUAL: return "mulsd"; + case TOKEN_SLASH_EQUAL: return "divsd"; + default: return NULL; + } +} + +static bool value_is_float(struct Emitter* emitter, const struct Expr* expr) +{ + if (expr->kind != EXPR_PRIMARY) + return false; + if (expr->primary.token.type == TOKEN_FLOAT) + return true; + + return is_float_register(resolve_register(emitter, expr->primary.token)); +} + +// floating point: xmm moves and arithmetic, conversions to/from general-purpose +// registers, and float literals loaded from their .data slot +static bool emit_float_assign(struct Emitter* emitter, struct AssignStatement* assign, + struct Token target, bool target_float) +{ + struct Expr* value = assign->value; + + // float store: ^ptr = xmm -> movsd [ptr], xmm + if (assign->target_deref) + { + if (assign->op.type != TOKEN_EQUAL || value->kind != EXPR_PRIMARY) + return false; + struct Token source = resolve_register(emitter, value->primary.token); + if (!is_float_register(source)) + return false; + fprintf(emitter->out, "\tmovsd [%.*s], %.*s\n", + (int)target.length, target.start, (int)source.length, source.start); + return true; + } + + // float load: xmm = ^ptr -> movsd xmm, [ptr] + if (value->kind == EXPR_DEREF) + { + if (!target_float || assign->op.type != TOKEN_EQUAL) + return false; + fprintf(emitter->out, "\tmovsd %.*s, [", (int)target.length, target.start); + emit_operand(emitter, value->deref.address); + fprintf(emitter->out, "]\n"); + return true; + } + + if (value->kind == EXPR_PRIMARY && value->primary.token.type == TOKEN_FLOAT) + { + if (!target_float || assign->op.type != TOKEN_EQUAL) + return false; + size_t index = float_index(emitter->floats, value->primary.token); + fprintf(emitter->out, "\tmovsd %.*s, [__float%zu]\n", + (int)target.length, target.start, index); + return true; + } + + if (value->kind != EXPR_PRIMARY) + return false; + + struct Token source = resolve_register(emitter, value->primary.token); + bool source_float = is_float_register(source); + + if (target_float && source_float) + { + const char* mnemonic = float_mnemonic(assign->op.type); + if (mnemonic == NULL) + return false; + fprintf(emitter->out, "\t%s %.*s, %.*s\n", mnemonic, + (int)target.length, target.start, (int)source.length, source.start); + return true; + } + + if (assign->op.type != TOKEN_EQUAL) + return false; + + if (target_float) + fprintf(emitter->out, "\tcvtsi2sd %.*s, %.*s\n", + (int)target.length, target.start, (int)source.length, source.start); + else + fprintf(emitter->out, "\tcvttsd2si %.*s, %.*s\n", + (int)target.length, target.start, (int)source.length, source.start); + return true; +} + +static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) +{ + struct Token float_target = resolve_register(emitter, assign->target); + if (is_float_register(float_target) || value_is_float(emitter, assign->value)) + { + if (!emit_float_assign(emitter, assign, float_target, is_float_register(float_target))) + fprintf(emitter->out, "\t; TODO: unsupported float assignment\n"); + return; + } + + if (assign->op.type == TOKEN_SLASH_EQUAL || assign->op.type == TOKEN_PERCENT_EQUAL) + { + emit_divide(emitter, assign); + return; + } + + struct Token target = resolve_register(emitter, assign->target); + + if (assign->op.type == TOKEN_EQUAL && !assign->target_deref) + { + if (!expr_supported(emitter, assign->value)) + { + fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); + return; + } + + char dst[32]; + snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); + emit_expr_into(emitter, dst, assign->value); + return; + } + + // deref store or compound assignment: needs a plain operand, not a buffer or binary + const char* mnemonic = assign_mnemonic(assign->op.type); + bool value_is_buffer = assign->value->kind == EXPR_PRIMARY + && is_buffer_name(emitter, assign->value->primary.token); + if (mnemonic == NULL || assign->value->kind == EXPR_BINARY + || assign->value->kind == EXPR_DEREF || value_is_buffer) + { + fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); + return; + } + + // adding or subtracting a constant zero (e.g. a struct field at offset 0) is a no-op + uint64_t folded; + if (!assign->target_deref + && (assign->op.type == TOKEN_PLUS_EQUAL || assign->op.type == TOKEN_MINUS_EQUAL) + && fold_const(emitter->program, assign->value, &folded) && folded == 0) + return; + + if (assign->target_deref) + { + fprintf(emitter->out, "\t%s %s[%.*s], ", mnemonic, + store_size_keyword(assign->store_size), (int)target.length, target.start); + + const char* sized = NULL; + if (assign->value->kind == EXPR_PRIMARY) + { + struct Token value = resolve_register(emitter, assign->value->primary.token); + sized = sized_register(value, assign->store_size); + if (sized != NULL) + fprintf(emitter->out, "%s", sized); + } + + if (sized == NULL) + emit_operand(emitter, assign->value); + } + else + { + fprintf(emitter->out, "\t%s %.*s, ", mnemonic, (int)target.length, target.start); + emit_operand(emitter, assign->value); + } + + fprintf(emitter->out, "\n"); +} + +static const char* jump_if_false(enum TokenType comparison) +{ + switch (comparison) + { + case TOKEN_EQUAL_EQUAL: return "jne"; + case TOKEN_BANG_EQUAL: return "je"; + case TOKEN_LESS: return "jge"; + case TOKEN_LESS_EQUAL: return "jg"; + case TOKEN_GREATER: return "jle"; + case TOKEN_GREATER_EQUAL: return "jl"; + default: return NULL; + } +} + +static struct ProcDecl* find_proc(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->proc_count; i += 1) + { + struct ProcDecl* proc = &program->procs[i]; + if (proc->name.length == name.length && memcmp(proc->name.start, name.start, name.length) == 0) + return proc; + } + + return NULL; +} + +static void emit_call(struct Emitter* emitter, struct CallStatement* call) +{ + const struct ProcDecl* callee = find_proc(emitter->program, call->name); + if (callee == NULL || callee->param_count != call->arg_count) + { + fprintf(emitter->out, "\t; TODO: unsupported call\n"); + return; + } + + for (size_t i = 0; i < call->arg_count; i += 1) + { + if (call->args[i]->kind == EXPR_BINARY || call->args[i]->kind == EXPR_DEREF) + { + fprintf(emitter->out, "\t; TODO: unsupported call argument\n"); + continue; + } + + struct Token reg = resolve_register(emitter, callee->params[i].reg); + fprintf(emitter->out, "\tmov %.*s, ", (int)reg.length, reg.start); + emit_operand(emitter, call->args[i]); + fprintf(emitter->out, "\n"); + } + + fprintf(emitter->out, "\tcall %.*s\n", (int)call->name.length, call->name.start); +} + +static void emit_statement(struct Emitter* emitter, struct Statement* statement); + +static void emit_block(struct Emitter* emitter, struct Statement* body, size_t count) +{ + for (size_t i = 0; i < count; i += 1) + emit_statement(emitter, &body[i]); +} + +// ucomisd sets the flags like an unsigned compare, so float branches use the +// unsigned jump family (ja/jae/jb/jbe) rather than the signed one +static const char* float_jump_if_false(enum TokenType comparison) +{ + switch (comparison) + { + case TOKEN_EQUAL_EQUAL: return "jne"; + case TOKEN_BANG_EQUAL: return "je"; + case TOKEN_LESS: return "jae"; + case TOKEN_LESS_EQUAL: return "ja"; + case TOKEN_GREATER: return "jbe"; + case TOKEN_GREATER_EQUAL: return "jb"; + default: return NULL; + } +} + +static void emit_float_operand(struct Emitter* emitter, const struct Expr* expr) +{ + if (expr->kind == EXPR_PRIMARY && expr->primary.token.type == TOKEN_FLOAT) + { + fprintf(emitter->out, "[__float%zu]", float_index(emitter->floats, expr->primary.token)); + return; + } + + struct Token token = resolve_register(emitter, expr->primary.token); + fprintf(emitter->out, "%.*s", (int)token.length, token.start); +} + +static void emit_if(struct Emitter* emitter, struct IfStatement* branch) +{ + bool is_float = value_is_float(emitter, branch->left) || value_is_float(emitter, branch->right); + bool has_else = branch->else_count > 0; + + uint32_t id = emitter->label_id; + emitter->label_id += 1; + + const char* target = has_else ? ".if_else_" : ".if_end_"; + + if (is_float) + { + const char* jump = float_jump_if_false(branch->comparison.type); + bool left_reg = branch->left->kind == EXPR_PRIMARY + && is_float_register(resolve_register(emitter, branch->left->primary.token)); + if (jump == NULL || !left_reg || !value_is_float(emitter, branch->right)) + { + fprintf(emitter->out, "\t; TODO: unsupported if\n"); + return; + } + + fprintf(emitter->out, "\tucomisd "); + emit_float_operand(emitter, branch->left); + fprintf(emitter->out, ", "); + emit_float_operand(emitter, branch->right); + fprintf(emitter->out, "\n\t%s %s%u\n", jump, target, id); + } + else + { + const char* jump = jump_if_false(branch->comparison.type); + if (jump == NULL + || branch->left->kind == EXPR_BINARY || branch->left->kind == EXPR_DEREF + || branch->right->kind == EXPR_BINARY || branch->right->kind == EXPR_DEREF) + { + fprintf(emitter->out, "\t; TODO: unsupported if\n"); + return; + } + + fprintf(emitter->out, "\tcmp "); + emit_operand(emitter, branch->left); + fprintf(emitter->out, ", "); + emit_operand(emitter, branch->right); + fprintf(emitter->out, "\n\t%s %s%u\n", jump, target, id); + } + + emit_block(emitter, branch->body, branch->body_count); + + if (has_else) + { + fprintf(emitter->out, "\tjmp .if_end_%u\n", id); + fprintf(emitter->out, ".if_else_%u:\n", id); + emit_block(emitter, branch->else_body, branch->else_count); + } + + fprintf(emitter->out, ".if_end_%u:\n", id); +} + +static void emit_statement(struct Emitter* emitter, struct Statement* statement) +{ + FILE* out = emitter->out; + switch (statement->kind) + { + case STATEMENT_ASSIGN: + emit_assign(emitter, &statement->assign); + break; + case STATEMENT_LABEL: + fprintf(out, "%.*s:\n", (int)statement->label.name.length, statement->label.name.start); + break; + case STATEMENT_GOTO: + fprintf(out, "\tjmp %.*s\n", (int)statement->jump.label.length, statement->jump.label.start); + break; + case STATEMENT_SYSCALL: + fprintf(out, "\tsyscall\n"); + break; + case STATEMENT_IF: + emit_if(emitter, &statement->branch); + break; + case STATEMENT_CALL: + emit_call(emitter, &statement->call); + break; + case STATEMENT_STACK: + break; + default: + fprintf(out, "\t; TODO: unsupported statement\n"); + break; + } +} + +static uint64_t proc_stack_size(struct Program* program, struct ProcDecl* proc) +{ + uint64_t total = 0; + for (size_t i = 0; i < proc->body_count; i += 1) + { + struct Statement* statement = &proc->body[i]; + if (statement->kind == STATEMENT_STACK) + { + uint64_t size = 0; + fold_const(program, statement->stack.size, &size); + total += size; + } + } + + if (total % 16 != 0) + total += 16 - (total % 16); + + return total; +} + +static void collect_float(struct FloatTable* floats, struct Token token) +{ + if (token.type != TOKEN_FLOAT || float_index(floats, token) != floats->count) + return; + + if (floats->count == floats->capacity) + { + size_t capacity = floats->capacity < 8 ? 8 : floats->capacity * 2; + floats->items = realloc(floats->items, capacity * sizeof(struct Token)); + floats->capacity = capacity; + } + + floats->items[floats->count] = token; + floats->count += 1; +} + +static void collect_floats_expr(struct FloatTable* floats, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + collect_float(floats, expr->primary.token); + break; + case EXPR_UNARY: + collect_floats_expr(floats, expr->unary.operand); + break; + case EXPR_BINARY: + collect_floats_expr(floats, expr->binary.left); + collect_floats_expr(floats, expr->binary.right); + break; + case EXPR_MEMBER: + collect_floats_expr(floats, expr->member.object); + break; + case EXPR_DEREF: + collect_floats_expr(floats, expr->deref.address); + break; + } +} + +static void collect_floats_statement(struct FloatTable* floats, struct Statement* statement) +{ + switch (statement->kind) + { + case STATEMENT_ASSIGN: + collect_floats_expr(floats, statement->assign.value); + break; + case STATEMENT_IF: + collect_floats_expr(floats, statement->branch.left); + collect_floats_expr(floats, statement->branch.right); + for (size_t i = 0; i < statement->branch.body_count; i += 1) + collect_floats_statement(floats, &statement->branch.body[i]); + for (size_t i = 0; i < statement->branch.else_count; i += 1) + collect_floats_statement(floats, &statement->branch.else_body[i]); + break; + case STATEMENT_CALL: + for (size_t i = 0; i < statement->call.arg_count; i += 1) + collect_floats_expr(floats, statement->call.args[i]); + break; + default: + break; + } +} + +static struct FloatTable collect_floats(struct Program* program) +{ + struct FloatTable floats = { NULL, 0, 0 }; + for (size_t i = 0; i < program->proc_count; i += 1) + for (size_t j = 0; j < program->procs[i].body_count; j += 1) + collect_floats_statement(&floats, &program->procs[i].body[j]); + + return floats; +} + +static void emit_float_data(const struct FloatTable* floats, FILE* out) +{ + for (size_t i = 0; i < floats->count; i += 1) + fprintf(out, "__float%zu: dq %.*s\n", i, + (int)floats->items[i].length, floats->items[i].start); +} + +static void emit_proc(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, FILE* out) +{ + struct Emitter emitter; + emitter.program = program; + emitter.proc = proc; + emitter.floats = floats; + emitter.out = out; + emitter.label_id = 0; + + struct Config config = program->config; + bool is_entry = config.has_entry + && proc->name.length == config.entry.length + && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; + + fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start); + + uint64_t stack_size = proc_stack_size(program, proc); + if (stack_size > 0) + { + fprintf(out, "\tpush rbp\n"); + fprintf(out, "\tmov rbp, rsp\n"); + fprintf(out, "\tsub rsp, %llu\n", (unsigned long long)stack_size); + } + + for (size_t i = 0; i < proc->body_count; i += 1) + emit_statement(&emitter, &proc->body[i]); + + if (!is_entry) + { + if (stack_size > 0) + fprintf(out, "\tleave\n"); + fprintf(out, "\tret\n"); + } +} + +void generate_nasm(struct Program* program, FILE* out) +{ + struct FloatTable floats = collect_floats(program); + + fprintf(out, "bits %u\n\n", program->config.bits); + + if (program->const_count > 0) + { + emit_consts(program, out); + fprintf(out, "\n"); + } + + emit_data(program, out); + emit_float_data(&floats, out); + fprintf(out, "\n"); + + fprintf(out, "section .text\n"); + if (program->config.has_entry) + fprintf(out, "global %.*s\n", (int)program->config.entry.length, program->config.entry.start); + + for (size_t i = 0; i < program->proc_count; i += 1) + { + fprintf(out, "\n"); + emit_proc(program, &floats, &program->procs[i], out); + } + + free(floats.items); +} diff --git a/src/nasm.h b/src/nasm.h new file mode 100644 index 0000000..1a93b31 --- /dev/null +++ b/src/nasm.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "ast.h" + +void generate_nasm(struct Program* program, FILE* out); diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..4c01c86 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,787 @@ +#include +#include +#include + +#include "diag.h" +#include "parser.h" + +struct Parser +{ + struct Lexer* lexer; + struct Source source; + struct Token current; + struct Token previous; + bool had_error; +}; + +static void advance_parser(struct Parser* parser) +{ + parser->previous = parser->current; + parser->current = scan_token(parser->lexer); +} + +static bool check(struct Parser* parser, enum TokenType type) +{ + return parser->current.type == type; +} + +static void error_at(struct Parser* parser, struct Token token, const char* message) +{ + report_error(parser->source, token, message); + parser->had_error = true; +} + +static bool match_token(struct Parser* parser, enum TokenType type) +{ + if (!check(parser, type)) + return false; + + advance_parser(parser); + return true; +} + +static bool consume(struct Parser* parser, enum TokenType type, const char* message) +{ + if (check(parser, type)) + { + advance_parser(parser); + return true; + } + + error_at(parser, parser->current, message); + return false; +} + +static struct Expr* parse_expression(struct Parser* parser); +static enum StoreSize parse_store_size(struct Parser* parser); + +static bool parse_const(struct Parser* parser, struct Program* program) +{ + struct ConstDecl decl; + + if (!consume(parser, TOKEN_IDENTIFIER, "expected constant name after 'const'")) + return false; + decl.name = parser->previous; + + if (!consume(parser, TOKEN_EQUAL, "expected '=' after constant name")) + return false; + + decl.value = parse_expression(parser); + if (decl.value == NULL) + return false; + + add_const(program, decl); + return true; +} + +static bool parse_data(struct Parser* parser, struct Program* program) +{ + struct DataDecl decl; + + if (!consume(parser, TOKEN_IDENTIFIER, "expected data name after 'data'")) + return false; + decl.name = parser->previous; + + if (!consume(parser, TOKEN_EQUAL, "expected '=' after data name")) + return false; + + if (!consume(parser, TOKEN_STRING, "expected string value after '='")) + return false; + decl.value = parser->previous; + + add_data(program, decl); + return true; +} + +static bool parse_enum(struct Parser* parser, struct Program* program) +{ + struct EnumDecl decl = create_enum(); + + if (!consume(parser, TOKEN_IDENTIFIER, "expected enum name after 'enum'")) + goto error; + decl.name = parser->previous; + + if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' after enum name")) + goto error; + + while (!check(parser, TOKEN_RIGHT_BRACE)) + { + if (check(parser, TOKEN_EOF)) + { + error_at(parser, parser->current, "unterminated enum"); + goto error; + } + + if (!consume(parser, TOKEN_IDENTIFIER, "expected an enum member name")) + goto error; + add_enum_member(&decl, parser->previous); + + match_token(parser, TOKEN_COMMA); + } + advance_parser(parser); + + add_enum(program, decl); + return true; + +error: + free(decl.members); + return false; +} + +static bool parse_struct(struct Parser* parser, struct Program* program) +{ + struct StructDecl decl = create_struct(); + + if (!consume(parser, TOKEN_IDENTIFIER, "expected struct name after 'struct'")) + goto error; + decl.name = parser->previous; + + if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' after struct name")) + goto error; + + while (!check(parser, TOKEN_RIGHT_BRACE)) + { + if (check(parser, TOKEN_EOF)) + { + error_at(parser, parser->current, "unterminated struct"); + goto error; + } + + struct StructField field; + if (!consume(parser, TOKEN_IDENTIFIER, "expected a field name")) + goto error; + field.name = parser->previous; + + field.size = STORE_SIZE_QWORD; + if (match_token(parser, TOKEN_COLON)) + { + field.size = parse_store_size(parser); + if (field.size == STORE_SIZE_NONE) + { + error_at(parser, parser->current, "expected a size (byte, word, dword, qword) after ':'"); + goto error; + } + } + + add_struct_field(&decl, field); + match_token(parser, TOKEN_COMMA); + } + advance_parser(parser); + + add_struct(program, decl); + return true; + +error: + free(decl.fields); + return false; +} + +static bool parse_params(struct Parser* parser, struct ProcDecl* proc) +{ + if (check(parser, TOKEN_RIGHT_PAREN)) + return true; + + do + { + struct Param param; + + if (!consume(parser, TOKEN_IDENTIFIER, "expected parameter name")) + return false; + param.name = parser->previous; + + if (!consume(parser, TOKEN_COLON, "expected ':' after parameter name")) + return false; + + if (!consume(parser, TOKEN_IDENTIFIER, "expected register after ':'")) + return false; + param.reg = parser->previous; + + add_param(proc, param); + } + while (match_token(parser, TOKEN_COMMA)); + + return true; +} + +static bool is_assign_op(enum TokenType type) +{ + return type == TOKEN_EQUAL + || type == TOKEN_PLUS_EQUAL + || type == TOKEN_MINUS_EQUAL + || type == TOKEN_STAR_EQUAL + || type == TOKEN_SLASH_EQUAL + || type == TOKEN_PERCENT_EQUAL; +} + +static struct Expr* alloc_expr(enum ExprKind kind) +{ + struct Expr* expr = malloc(sizeof(*expr)); + if (expr != NULL) + expr->kind = kind; + return expr; +} + +static enum StoreSize parse_store_size(struct Parser* parser) +{ + if (match_token(parser, TOKEN_BYTE)) + return STORE_SIZE_BYTE; + if (match_token(parser, TOKEN_WORD)) + return STORE_SIZE_WORD; + if (match_token(parser, TOKEN_DWORD)) + return STORE_SIZE_DWORD; + if (match_token(parser, TOKEN_QWORD)) + return STORE_SIZE_QWORD; + return STORE_SIZE_NONE; +} + +static struct Expr* parse_primary(struct Parser* parser) +{ + if (match_token(parser, TOKEN_CARET)) + { + bool is_signed = match_token(parser, TOKEN_SIGNED); + enum StoreSize size = parse_store_size(parser); + + struct Expr* address = parse_primary(parser); + if (address == NULL) + return NULL; + + struct Expr* deref = alloc_expr(EXPR_DEREF); + if (deref == NULL) + { + free_expr(address); + return NULL; + } + deref->deref.is_signed = is_signed; + deref->deref.size = size; + deref->deref.address = address; + return deref; + } + + if (check(parser, TOKEN_IDENTIFIER) || check(parser, TOKEN_INTEGER) + || check(parser, TOKEN_FLOAT) || check(parser, TOKEN_CHAR)) + { + advance_parser(parser); + + struct Expr* expr = alloc_expr(EXPR_PRIMARY); + expr->primary.token = parser->previous; + return expr; + } + + error_at(parser, parser->current, "expected an expression"); + return NULL; +} + +static struct Expr* parse_postfix(struct Parser* parser) +{ + struct Expr* expr = parse_primary(parser); + if (expr == NULL) + return NULL; + + while (match_token(parser, TOKEN_DOT)) + { + // an identifier is a member (data.len); an integer is a register size + // suffix (r1.64), meaningful with the logical_registers extension + if (!check(parser, TOKEN_IDENTIFIER) && !check(parser, TOKEN_INTEGER)) + { + error_at(parser, parser->current, "expected a member name or size after '.'"); + free_expr(expr); + return NULL; + } + advance_parser(parser); + + struct Expr* member = alloc_expr(EXPR_MEMBER); + member->member.object = expr; + member->member.member = parser->previous; + expr = member; + } + + return expr; +} + +static struct Expr* parse_unary(struct Parser* parser) +{ + if (match_token(parser, TOKEN_MINUS)) + { + struct Token op = parser->previous; + + struct Expr* operand = parse_unary(parser); + if (operand == NULL) + return NULL; + + struct Expr* expr = alloc_expr(EXPR_UNARY); + if (expr == NULL) + { + free_expr(operand); + return NULL; + } + expr->unary.op = op; + expr->unary.operand = operand; + return expr; + } + + return parse_postfix(parser); +} + +static struct Expr* parse_binary(struct Parser* parser, struct Expr* (*operand)(struct Parser*), + enum TokenType a, enum TokenType b, enum TokenType c) +{ + struct Expr* left = operand(parser); + if (left == NULL) + return NULL; + + while (check(parser, a) || check(parser, b) || check(parser, c)) + { + advance_parser(parser); + struct Token op = parser->previous; + + struct Expr* right = operand(parser); + if (right == NULL) + { + free_expr(left); + return NULL; + } + + struct Expr* binary = alloc_expr(EXPR_BINARY); + binary->binary.left = left; + binary->binary.op = op; + binary->binary.right = right; + left = binary; + } + + return left; +} + +static struct Expr* parse_multiplicative(struct Parser* parser) +{ + return parse_binary(parser, parse_unary, TOKEN_STAR, TOKEN_SLASH, TOKEN_PERCENT); +} + +static struct Expr* parse_expression(struct Parser* parser) +{ + return parse_binary(parser, parse_multiplicative, TOKEN_PLUS, TOKEN_MINUS, TOKEN_MINUS); +} + +static bool is_compare_op(enum TokenType type) +{ + return type == TOKEN_EQUAL_EQUAL + || type == TOKEN_BANG_EQUAL + || type == TOKEN_LESS + || type == TOKEN_LESS_EQUAL + || type == TOKEN_GREATER + || type == TOKEN_GREATER_EQUAL; +} + +static bool parse_call(struct Parser* parser, struct Token name, struct Statement* out) +{ + struct Expr** args = NULL; + size_t count = 0; + size_t capacity = 0; + + if (!check(parser, TOKEN_RIGHT_PAREN)) + { + do + { + struct Expr* arg = parse_expression(parser); + if (arg == NULL) + goto error; + + if (count == capacity) + { + capacity = capacity < 4 ? 4 : capacity * 2; + struct Expr** grown = realloc(args, capacity * sizeof(struct Expr*)); + if (grown == NULL) + { + free_expr(arg); + goto error; + } + args = grown; + } + args[count] = arg; + count += 1; + } + while (match_token(parser, TOKEN_COMMA)); + } + + if (!consume(parser, TOKEN_RIGHT_PAREN, "expected ')' after arguments")) + goto error; + + out->kind = STATEMENT_CALL; + out->call.name = name; + out->call.args = args; + out->call.arg_count = count; + out->call.arg_capacity = capacity; + return true; + +error: + for (size_t i = 0; i < count; i += 1) + free_expr(args[i]); + free(args); + return false; +} + +static bool parse_statement(struct Parser* parser, struct Statement* out); + +// A branch body is either a braced block or a single bare statement, always +// returned as a list so codegen and freeing treat both the same way. +static bool parse_block(struct Parser* parser, struct Statement** out_body, size_t* out_count) +{ + if (!match_token(parser, TOKEN_LEFT_BRACE)) + { + struct Statement* body = malloc(sizeof(*body)); + if (body == NULL) + return false; + + if (!parse_statement(parser, body)) + { + free(body); + return false; + } + + *out_body = body; + *out_count = 1; + return true; + } + + struct Statement* body = NULL; + size_t count = 0; + size_t capacity = 0; + + while (!check(parser, TOKEN_RIGHT_BRACE)) + { + if (check(parser, TOKEN_EOF)) + { + error_at(parser, parser->current, "unterminated block"); + goto error; + } + + if (count == capacity) + { + size_t grown_capacity = capacity == 0 ? 4 : capacity * 2; + struct Statement* grown = realloc(body, grown_capacity * sizeof(*grown)); + if (grown == NULL) + goto error; + body = grown; + capacity = grown_capacity; + } + + if (!parse_statement(parser, &body[count])) + goto error; + count += 1; + } + advance_parser(parser); + + *out_body = body; + *out_count = count; + return true; + +error: + for (size_t i = 0; i < count; i += 1) + free_statement(&body[i]); + free(body); + return false; +} + +static bool parse_if(struct Parser* parser, struct Statement* out) +{ + struct Expr* left = parse_expression(parser); + if (left == NULL) + return false; + + if (!is_compare_op(parser->current.type)) + { + error_at(parser, parser->current, "expected a comparison operator"); + free_expr(left); + return false; + } + advance_parser(parser); + struct Token comparison = parser->previous; + + struct Expr* right = parse_expression(parser); + if (right == NULL) + { + free_expr(left); + return false; + } + + struct Statement* body; + size_t body_count; + if (!parse_block(parser, &body, &body_count)) + { + free_expr(left); + free_expr(right); + return false; + } + + struct Statement* else_body = NULL; + size_t else_count = 0; + if (match_token(parser, TOKEN_ELSE) && !parse_block(parser, &else_body, &else_count)) + { + for (size_t i = 0; i < body_count; i += 1) + free_statement(&body[i]); + free(body); + free_expr(left); + free_expr(right); + return false; + } + + out->kind = STATEMENT_IF; + out->branch.left = left; + out->branch.comparison = comparison; + out->branch.right = right; + out->branch.body = body; + out->branch.body_count = body_count; + out->branch.else_body = else_body; + out->branch.else_count = else_count; + return true; +} + +static bool parse_statement(struct Parser* parser, struct Statement* out) +{ + if (match_token(parser, TOKEN_IF)) + return parse_if(parser, out); + + if (match_token(parser, TOKEN_SYSCALL)) + { + out->kind = STATEMENT_SYSCALL; + return true; + } + + if (match_token(parser, TOKEN_STACK)) + { + if (!consume(parser, TOKEN_IDENTIFIER, "expected buffer name after 'stack'")) + return false; + struct Token name = parser->previous; + + if (!consume(parser, TOKEN_LEFT_BRACKET, "expected '[' after buffer name")) + return false; + + struct Expr* size = parse_expression(parser); + if (size == NULL) + return false; + + if (!consume(parser, TOKEN_RIGHT_BRACKET, "expected ']' after buffer size")) + { + free_expr(size); + return false; + } + + out->kind = STATEMENT_STACK; + out->stack.name = name; + out->stack.size = size; + return true; + } + + if (match_token(parser, TOKEN_GOTO)) + { + if (!consume(parser, TOKEN_IDENTIFIER, "expected label after 'goto'")) + return false; + + out->kind = STATEMENT_GOTO; + out->jump.label = parser->previous; + return true; + } + + bool deref = match_token(parser, TOKEN_CARET); + enum StoreSize store_size = deref ? parse_store_size(parser) : STORE_SIZE_NONE; + + if (!consume(parser, TOKEN_IDENTIFIER, "expected a statement")) + return false; + struct Token name = parser->previous; + + if (!deref && match_token(parser, TOKEN_LEFT_PAREN)) + return parse_call(parser, name, out); + + if (!deref && match_token(parser, TOKEN_COLON)) + { + out->kind = STATEMENT_LABEL; + out->label.name = name; + return true; + } + + if (!is_assign_op(parser->current.type)) + { + error_at(parser, parser->current, "expected an assignment operator"); + return false; + } + + advance_parser(parser); + struct Token op = parser->previous; + + struct Expr* value = parse_expression(parser); + if (value == NULL) + return false; + + out->kind = STATEMENT_ASSIGN; + out->assign.target_deref = deref; + out->assign.store_size = store_size; + out->assign.target = name; + out->assign.op = op; + out->assign.value = value; + return true; +} + +static bool parse_proc(struct Parser* parser, struct Program* program) +{ + struct ProcDecl decl = create_proc(); + + if (!consume(parser, TOKEN_IDENTIFIER, "expected procedure name after 'proc'")) + goto error; + decl.name = parser->previous; + + if (match_token(parser, TOKEN_LEFT_PAREN)) + { + if (!parse_params(parser, &decl)) + goto error; + if (!consume(parser, TOKEN_RIGHT_PAREN, "expected ')' after parameters")) + goto error; + } + + if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' to begin procedure body")) + goto error; + + while (!check(parser, TOKEN_RIGHT_BRACE)) + { + if (check(parser, TOKEN_EOF)) + { + error_at(parser, parser->current, "unterminated procedure body"); + goto error; + } + + struct Statement statement; + if (!parse_statement(parser, &statement)) + goto error; + + add_statement(&decl, statement); + } + advance_parser(parser); + + add_proc(program, decl); + return true; + +error: + free_proc(&decl); + return false; +} + +static bool token_text_is(struct Token token, const char* text) +{ + size_t length = strlen(text); + return token.length == length && memcmp(token.start, text, length) == 0; +} + +static bool parse_directive(struct Parser* parser, struct Program* program) +{ + if (!consume(parser, TOKEN_IDENTIFIER, "expected directive name after '['")) + return false; + struct Token key = parser->previous; + + if (!consume(parser, TOKEN_COLON, "expected ':' after directive name")) + return false; + + if (!check(parser, TOKEN_IDENTIFIER) && !check(parser, TOKEN_INTEGER)) + { + error_at(parser, parser->current, "expected a directive value"); + return false; + } + advance_parser(parser); + struct Token value = parser->previous; + + if (!consume(parser, TOKEN_RIGHT_BRACKET, "expected ']' to close directive")) + return false; + + if (token_text_is(key, "bits")) + { + if (value.type != TOKEN_INTEGER || (!token_text_is(value, "64") && !token_text_is(value, "32"))) + { + error_at(parser, value, "bits must be 32 or 64"); + return false; + } + program->config.bits = token_text_is(value, "64") ? 64 : 32; + return true; + } + + if (token_text_is(key, "entry")) + { + if (value.type != TOKEN_IDENTIFIER) + { + error_at(parser, value, "entry must be a procedure name"); + return false; + } + program->config.has_entry = true; + program->config.entry = value; + return true; + } + + if (token_text_is(key, "enable")) + { + if (value.type == TOKEN_IDENTIFIER && token_text_is(value, "logical_registers")) + { + program->config.logical_registers = true; + return true; + } + error_at(parser, value, "unknown extension"); + return false; + } + + error_at(parser, key, "unknown directive"); + return false; +} + +bool parse_program(struct Lexer* lexer, struct Program* out) +{ + struct Parser parser = { 0 }; + parser.lexer = lexer; + parser.source.name = lexer->name; + parser.source.text = lexer->source; + parser.had_error = false; + advance_parser(&parser); + + *out = create_program(); + + while (!check(&parser, TOKEN_EOF)) + { + if (check(&parser, TOKEN_LEFT_BRACKET)) + { + advance_parser(&parser); + if (!parse_directive(&parser, out)) + return false; + } + else if (check(&parser, TOKEN_CONST)) + { + advance_parser(&parser); + if (!parse_const(&parser, out)) + return false; + } + else if (check(&parser, TOKEN_DATA)) + { + advance_parser(&parser); + if (!parse_data(&parser, out)) + return false; + } + else if (check(&parser, TOKEN_ENUM)) + { + advance_parser(&parser); + if (!parse_enum(&parser, out)) + return false; + } + else if (check(&parser, TOKEN_STRUCT)) + { + advance_parser(&parser); + if (!parse_struct(&parser, out)) + return false; + } + else if (check(&parser, TOKEN_PROC)) + { + advance_parser(&parser); + if (!parse_proc(&parser, out)) + return false; + } + else + { + error_at(&parser, parser.current, "expected a top-level declaration"); + return false; + } + } + + return !parser.had_error; +} diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..82a3019 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "ast.h" +#include "lexer.h" + +bool parse_program(struct Lexer* lexer, struct Program* out); diff --git a/src/parser/ast.c b/src/parser/ast.c deleted file mode 100644 index 63d8a08..0000000 --- a/src/parser/ast.c +++ /dev/null @@ -1,265 +0,0 @@ -#include - -#include "parser/ast.h" - -void free_expr(struct Expr* expr) -{ - if (expr == NULL) - return; - - switch (expr->kind) - { - case EXPR_PRIMARY: - break; - case EXPR_UNARY: - free_expr(expr->unary.operand); - break; - case EXPR_BINARY: - free_expr(expr->binary.left); - free_expr(expr->binary.right); - break; - case EXPR_MEMBER: - free_expr(expr->member.object); - break; - case EXPR_DEREF: - free_expr(expr->deref.address); - break; - } - - free(expr); -} - -void free_statement(struct Statement* statement) -{ - switch (statement->kind) - { - case STATEMENT_ASSIGN: - free_expr(statement->assign.value); - break; - case STATEMENT_IF: - free_expr(statement->branch.left); - free_expr(statement->branch.right); - for (size_t i = 0; i < statement->branch.body_count; i += 1) - free_statement(&statement->branch.body[i]); - free(statement->branch.body); - for (size_t i = 0; i < statement->branch.else_count; i += 1) - free_statement(&statement->branch.else_body[i]); - free(statement->branch.else_body); - break; - case STATEMENT_CALL: - for (size_t i = 0; i < statement->call.arg_count; i += 1) - free_expr(statement->call.args[i]); - free(statement->call.args); - break; - case STATEMENT_STACK: - free_expr(statement->stack.size); - break; - default: - break; - } -} - -void free_proc(struct ProcDecl* proc) -{ - free(proc->params); - - for (size_t i = 0; i < proc->body_count; i += 1) - free_statement(&proc->body[i]); - free(proc->body); -} - -struct Program create_program(void) -{ - struct Program program; - program.config.bits = 64; - program.config.has_entry = false; - program.config.logical_registers = false; - program.consts = NULL; - program.const_count = 0; - program.const_capacity = 0; - program.data_decls = NULL; - program.data_count = 0; - program.data_capacity = 0; - program.enums = NULL; - program.enum_count = 0; - program.enum_capacity = 0; - program.structs = NULL; - program.struct_count = 0; - program.struct_capacity = 0; - program.procs = NULL; - program.proc_count = 0; - program.proc_capacity = 0; - return program; -} - -void free_program(struct Program* program) -{ - for (size_t i = 0; i < program->const_count; i += 1) - free_expr(program->consts[i].value); - free(program->consts); - free(program->data_decls); - - for (size_t i = 0; i < program->enum_count; i += 1) - free(program->enums[i].members); - free(program->enums); - - for (size_t i = 0; i < program->struct_count; i += 1) - free(program->structs[i].fields); - free(program->structs); - - for (size_t i = 0; i < program->proc_count; i += 1) - free_proc(&program->procs[i]); - free(program->procs); - - program->consts = NULL; - program->const_count = 0; - program->const_capacity = 0; - program->data_decls = NULL; - program->data_count = 0; - program->data_capacity = 0; - program->enums = NULL; - program->enum_count = 0; - program->enum_capacity = 0; - program->structs = NULL; - program->struct_count = 0; - program->struct_capacity = 0; - program->procs = NULL; - program->proc_count = 0; - program->proc_capacity = 0; -} - -// TODO: generalize this growable-array boilerplate once a third list appears -void add_const(struct Program* program, struct ConstDecl decl) -{ - if (program->const_count == program->const_capacity) - { - size_t capacity = program->const_capacity < 8 ? 8 : program->const_capacity * 2; - program->consts = realloc(program->consts, capacity * sizeof(struct ConstDecl)); - program->const_capacity = capacity; - } - - program->consts[program->const_count] = decl; - program->const_count += 1; -} - -void add_data(struct Program* program, struct DataDecl decl) -{ - if (program->data_count == program->data_capacity) - { - size_t capacity = program->data_capacity < 8 ? 8 : program->data_capacity * 2; - program->data_decls = realloc(program->data_decls, capacity * sizeof(struct DataDecl)); - program->data_capacity = capacity; - } - - program->data_decls[program->data_count] = decl; - program->data_count += 1; -} - -struct EnumDecl create_enum(void) -{ - struct EnumDecl decl = { 0 }; - return decl; -} - -void add_enum_member(struct EnumDecl* decl, struct Token member) -{ - if (decl->member_count == decl->member_capacity) - { - size_t capacity = decl->member_capacity < 8 ? 8 : decl->member_capacity * 2; - decl->members = realloc(decl->members, capacity * sizeof(struct Token)); - decl->member_capacity = capacity; - } - - decl->members[decl->member_count] = member; - decl->member_count += 1; -} - -void add_enum(struct Program* program, struct EnumDecl decl) -{ - if (program->enum_count == program->enum_capacity) - { - size_t capacity = program->enum_capacity < 8 ? 8 : program->enum_capacity * 2; - program->enums = realloc(program->enums, capacity * sizeof(struct EnumDecl)); - program->enum_capacity = capacity; - } - - program->enums[program->enum_count] = decl; - program->enum_count += 1; -} - -struct StructDecl create_struct(void) -{ - struct StructDecl decl = { 0 }; - return decl; -} - -void add_struct_field(struct StructDecl* decl, struct StructField field) -{ - if (decl->field_count == decl->field_capacity) - { - size_t capacity = decl->field_capacity < 8 ? 8 : decl->field_capacity * 2; - decl->fields = realloc(decl->fields, capacity * sizeof(struct StructField)); - decl->field_capacity = capacity; - } - - decl->fields[decl->field_count] = field; - decl->field_count += 1; -} - -void add_struct(struct Program* program, struct StructDecl decl) -{ - if (program->struct_count == program->struct_capacity) - { - size_t capacity = program->struct_capacity < 8 ? 8 : program->struct_capacity * 2; - program->structs = realloc(program->structs, capacity * sizeof(struct StructDecl)); - program->struct_capacity = capacity; - } - - program->structs[program->struct_count] = decl; - program->struct_count += 1; -} - -struct ProcDecl create_proc(void) -{ - struct ProcDecl proc = { 0 }; - return proc; -} - -void add_param(struct ProcDecl* proc, struct Param param) -{ - if (proc->param_count == proc->param_capacity) - { - size_t capacity = proc->param_capacity < 4 ? 4 : proc->param_capacity * 2; - proc->params = realloc(proc->params, capacity * sizeof(struct Param)); - proc->param_capacity = capacity; - } - - proc->params[proc->param_count] = param; - proc->param_count += 1; -} - -void add_statement(struct ProcDecl* proc, struct Statement statement) -{ - if (proc->body_count == proc->body_capacity) - { - size_t capacity = proc->body_capacity < 8 ? 8 : proc->body_capacity * 2; - proc->body = realloc(proc->body, capacity * sizeof(struct Statement)); - proc->body_capacity = capacity; - } - - proc->body[proc->body_count] = statement; - proc->body_count += 1; -} - -void add_proc(struct Program* program, struct ProcDecl decl) -{ - if (program->proc_count == program->proc_capacity) - { - size_t capacity = program->proc_capacity < 8 ? 8 : program->proc_capacity * 2; - program->procs = realloc(program->procs, capacity * sizeof(struct ProcDecl)); - program->proc_capacity = capacity; - } - - program->procs[program->proc_count] = decl; - program->proc_count += 1; -} diff --git a/src/parser/ast.h b/src/parser/ast.h deleted file mode 100644 index 288bddf..0000000 --- a/src/parser/ast.h +++ /dev/null @@ -1,245 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "lexer/lexer.h" - -enum StoreSize -{ - STORE_SIZE_NONE, - STORE_SIZE_BYTE, - STORE_SIZE_WORD, - STORE_SIZE_DWORD, - STORE_SIZE_QWORD, -}; - -struct ConstDecl -{ - struct Token name; - struct Expr* value; -}; - -struct DataDecl -{ - struct Token name; - struct Token value; -}; - -struct EnumDecl -{ - struct Token name; - struct Token* members; - size_t member_count; - size_t member_capacity; -}; - -struct StructField -{ - struct Token name; - enum StoreSize size; -}; - -struct StructDecl -{ - struct Token name; - struct StructField* fields; - size_t field_count; - size_t field_capacity; -}; - -struct Param -{ - struct Token name; - struct Token reg; -}; - -enum ExprKind -{ - EXPR_PRIMARY, - EXPR_UNARY, - EXPR_BINARY, - EXPR_MEMBER, - EXPR_DEREF, -}; - -struct PrimaryExpr -{ - struct Token token; -}; - -struct UnaryExpr -{ - struct Token op; - struct Expr* operand; -}; - -struct BinaryExpr -{ - struct Expr* left; - struct Token op; - struct Expr* right; -}; - -struct MemberExpr -{ - struct Expr* object; - struct Token member; -}; - -struct DerefExpr -{ - bool is_signed; - enum StoreSize size; - struct Expr* address; -}; - -struct Expr -{ - enum ExprKind kind; - union - { - struct PrimaryExpr primary; - struct UnaryExpr unary; - struct BinaryExpr binary; - struct MemberExpr member; - struct DerefExpr deref; - }; -}; - -enum StatementKind -{ - STATEMENT_ASSIGN, - STATEMENT_LABEL, - STATEMENT_GOTO, - STATEMENT_SYSCALL, - STATEMENT_IF, - STATEMENT_CALL, - STATEMENT_STACK, -}; - -struct AssignStatement -{ - bool target_deref; - enum StoreSize store_size; - struct Token target; - struct Token op; - struct Expr* value; -}; - -struct LabelStatement -{ - struct Token name; -}; - -struct GotoStatement -{ - struct Token label; -}; - -struct IfStatement -{ - struct Expr* left; - struct Token comparison; - struct Expr* right; - struct Statement* body; - size_t body_count; - struct Statement* else_body; - size_t else_count; -}; - -struct CallStatement -{ - struct Token name; - struct Expr** args; - size_t arg_count; - size_t arg_capacity; -}; - -struct StackStatement -{ - struct Token name; - struct Expr* size; -}; - -struct Statement -{ - enum StatementKind kind; - union - { - struct AssignStatement assign; - struct LabelStatement label; - struct GotoStatement jump; - struct IfStatement branch; - struct CallStatement call; - struct StackStatement stack; - }; -}; - -struct ProcDecl -{ - struct Token name; - struct Param* params; - size_t param_count; - size_t param_capacity; - - struct Statement* body; - size_t body_count; - size_t body_capacity; -}; - -struct Config -{ - uint32_t bits; - bool has_entry; - struct Token entry; - bool logical_registers; -}; - -struct Program -{ - struct Config config; - - struct ConstDecl* consts; - size_t const_count; - size_t const_capacity; - - struct DataDecl* data_decls; - size_t data_count; - size_t data_capacity; - - struct EnumDecl* enums; - size_t enum_count; - size_t enum_capacity; - - struct StructDecl* structs; - size_t struct_count; - size_t struct_capacity; - - struct ProcDecl* procs; - size_t proc_count; - size_t proc_capacity; -}; - -struct Program create_program(void); -void free_program(struct Program* program); -void add_const(struct Program* program, struct ConstDecl decl); -void add_data(struct Program* program, struct DataDecl decl); - -struct EnumDecl create_enum(void); -void add_enum_member(struct EnumDecl* decl, struct Token member); -void add_enum(struct Program* program, struct EnumDecl decl); - -struct StructDecl create_struct(void); -void add_struct_field(struct StructDecl* decl, struct StructField field); -void add_struct(struct Program* program, struct StructDecl decl); - -struct ProcDecl create_proc(void); -void free_proc(struct ProcDecl* proc); -void add_param(struct ProcDecl* proc, struct Param param); -void add_statement(struct ProcDecl* proc, struct Statement statement); -void add_proc(struct Program* program, struct ProcDecl decl); - -void free_expr(struct Expr* expr); -void free_statement(struct Statement* statement); diff --git a/src/parser/parser.c b/src/parser/parser.c deleted file mode 100644 index 97b8569..0000000 --- a/src/parser/parser.c +++ /dev/null @@ -1,787 +0,0 @@ -#include -#include -#include - -#include "diag/diag.h" -#include "parser/parser.h" - -struct Parser -{ - struct Lexer* lexer; - struct Source source; - struct Token current; - struct Token previous; - bool had_error; -}; - -static void advance_parser(struct Parser* parser) -{ - parser->previous = parser->current; - parser->current = scan_token(parser->lexer); -} - -static bool check(struct Parser* parser, enum TokenType type) -{ - return parser->current.type == type; -} - -static void error_at(struct Parser* parser, struct Token token, const char* message) -{ - report_error(parser->source, token, message); - parser->had_error = true; -} - -static bool match_token(struct Parser* parser, enum TokenType type) -{ - if (!check(parser, type)) - return false; - - advance_parser(parser); - return true; -} - -static bool consume(struct Parser* parser, enum TokenType type, const char* message) -{ - if (check(parser, type)) - { - advance_parser(parser); - return true; - } - - error_at(parser, parser->current, message); - return false; -} - -static struct Expr* parse_expression(struct Parser* parser); -static enum StoreSize parse_store_size(struct Parser* parser); - -static bool parse_const(struct Parser* parser, struct Program* program) -{ - struct ConstDecl decl; - - if (!consume(parser, TOKEN_IDENTIFIER, "expected constant name after 'const'")) - return false; - decl.name = parser->previous; - - if (!consume(parser, TOKEN_EQUAL, "expected '=' after constant name")) - return false; - - decl.value = parse_expression(parser); - if (decl.value == NULL) - return false; - - add_const(program, decl); - return true; -} - -static bool parse_data(struct Parser* parser, struct Program* program) -{ - struct DataDecl decl; - - if (!consume(parser, TOKEN_IDENTIFIER, "expected data name after 'data'")) - return false; - decl.name = parser->previous; - - if (!consume(parser, TOKEN_EQUAL, "expected '=' after data name")) - return false; - - if (!consume(parser, TOKEN_STRING, "expected string value after '='")) - return false; - decl.value = parser->previous; - - add_data(program, decl); - return true; -} - -static bool parse_enum(struct Parser* parser, struct Program* program) -{ - struct EnumDecl decl = create_enum(); - - if (!consume(parser, TOKEN_IDENTIFIER, "expected enum name after 'enum'")) - goto error; - decl.name = parser->previous; - - if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' after enum name")) - goto error; - - while (!check(parser, TOKEN_RIGHT_BRACE)) - { - if (check(parser, TOKEN_EOF)) - { - error_at(parser, parser->current, "unterminated enum"); - goto error; - } - - if (!consume(parser, TOKEN_IDENTIFIER, "expected an enum member name")) - goto error; - add_enum_member(&decl, parser->previous); - - match_token(parser, TOKEN_COMMA); - } - advance_parser(parser); - - add_enum(program, decl); - return true; - -error: - free(decl.members); - return false; -} - -static bool parse_struct(struct Parser* parser, struct Program* program) -{ - struct StructDecl decl = create_struct(); - - if (!consume(parser, TOKEN_IDENTIFIER, "expected struct name after 'struct'")) - goto error; - decl.name = parser->previous; - - if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' after struct name")) - goto error; - - while (!check(parser, TOKEN_RIGHT_BRACE)) - { - if (check(parser, TOKEN_EOF)) - { - error_at(parser, parser->current, "unterminated struct"); - goto error; - } - - struct StructField field; - if (!consume(parser, TOKEN_IDENTIFIER, "expected a field name")) - goto error; - field.name = parser->previous; - - field.size = STORE_SIZE_QWORD; - if (match_token(parser, TOKEN_COLON)) - { - field.size = parse_store_size(parser); - if (field.size == STORE_SIZE_NONE) - { - error_at(parser, parser->current, "expected a size (byte, word, dword, qword) after ':'"); - goto error; - } - } - - add_struct_field(&decl, field); - match_token(parser, TOKEN_COMMA); - } - advance_parser(parser); - - add_struct(program, decl); - return true; - -error: - free(decl.fields); - return false; -} - -static bool parse_params(struct Parser* parser, struct ProcDecl* proc) -{ - if (check(parser, TOKEN_RIGHT_PAREN)) - return true; - - do - { - struct Param param; - - if (!consume(parser, TOKEN_IDENTIFIER, "expected parameter name")) - return false; - param.name = parser->previous; - - if (!consume(parser, TOKEN_COLON, "expected ':' after parameter name")) - return false; - - if (!consume(parser, TOKEN_IDENTIFIER, "expected register after ':'")) - return false; - param.reg = parser->previous; - - add_param(proc, param); - } - while (match_token(parser, TOKEN_COMMA)); - - return true; -} - -static bool is_assign_op(enum TokenType type) -{ - return type == TOKEN_EQUAL - || type == TOKEN_PLUS_EQUAL - || type == TOKEN_MINUS_EQUAL - || type == TOKEN_STAR_EQUAL - || type == TOKEN_SLASH_EQUAL - || type == TOKEN_PERCENT_EQUAL; -} - -static struct Expr* alloc_expr(enum ExprKind kind) -{ - struct Expr* expr = malloc(sizeof(*expr)); - if (expr != NULL) - expr->kind = kind; - return expr; -} - -static enum StoreSize parse_store_size(struct Parser* parser) -{ - if (match_token(parser, TOKEN_BYTE)) - return STORE_SIZE_BYTE; - if (match_token(parser, TOKEN_WORD)) - return STORE_SIZE_WORD; - if (match_token(parser, TOKEN_DWORD)) - return STORE_SIZE_DWORD; - if (match_token(parser, TOKEN_QWORD)) - return STORE_SIZE_QWORD; - return STORE_SIZE_NONE; -} - -static struct Expr* parse_primary(struct Parser* parser) -{ - if (match_token(parser, TOKEN_CARET)) - { - bool is_signed = match_token(parser, TOKEN_SIGNED); - enum StoreSize size = parse_store_size(parser); - - struct Expr* address = parse_primary(parser); - if (address == NULL) - return NULL; - - struct Expr* deref = alloc_expr(EXPR_DEREF); - if (deref == NULL) - { - free_expr(address); - return NULL; - } - deref->deref.is_signed = is_signed; - deref->deref.size = size; - deref->deref.address = address; - return deref; - } - - if (check(parser, TOKEN_IDENTIFIER) || check(parser, TOKEN_INTEGER) - || check(parser, TOKEN_FLOAT) || check(parser, TOKEN_CHAR)) - { - advance_parser(parser); - - struct Expr* expr = alloc_expr(EXPR_PRIMARY); - expr->primary.token = parser->previous; - return expr; - } - - error_at(parser, parser->current, "expected an expression"); - return NULL; -} - -static struct Expr* parse_postfix(struct Parser* parser) -{ - struct Expr* expr = parse_primary(parser); - if (expr == NULL) - return NULL; - - while (match_token(parser, TOKEN_DOT)) - { - // an identifier is a member (data.len); an integer is a register size - // suffix (r1.64), meaningful with the logical_registers extension - if (!check(parser, TOKEN_IDENTIFIER) && !check(parser, TOKEN_INTEGER)) - { - error_at(parser, parser->current, "expected a member name or size after '.'"); - free_expr(expr); - return NULL; - } - advance_parser(parser); - - struct Expr* member = alloc_expr(EXPR_MEMBER); - member->member.object = expr; - member->member.member = parser->previous; - expr = member; - } - - return expr; -} - -static struct Expr* parse_unary(struct Parser* parser) -{ - if (match_token(parser, TOKEN_MINUS)) - { - struct Token op = parser->previous; - - struct Expr* operand = parse_unary(parser); - if (operand == NULL) - return NULL; - - struct Expr* expr = alloc_expr(EXPR_UNARY); - if (expr == NULL) - { - free_expr(operand); - return NULL; - } - expr->unary.op = op; - expr->unary.operand = operand; - return expr; - } - - return parse_postfix(parser); -} - -static struct Expr* parse_binary(struct Parser* parser, struct Expr* (*operand)(struct Parser*), - enum TokenType a, enum TokenType b, enum TokenType c) -{ - struct Expr* left = operand(parser); - if (left == NULL) - return NULL; - - while (check(parser, a) || check(parser, b) || check(parser, c)) - { - advance_parser(parser); - struct Token op = parser->previous; - - struct Expr* right = operand(parser); - if (right == NULL) - { - free_expr(left); - return NULL; - } - - struct Expr* binary = alloc_expr(EXPR_BINARY); - binary->binary.left = left; - binary->binary.op = op; - binary->binary.right = right; - left = binary; - } - - return left; -} - -static struct Expr* parse_multiplicative(struct Parser* parser) -{ - return parse_binary(parser, parse_unary, TOKEN_STAR, TOKEN_SLASH, TOKEN_PERCENT); -} - -static struct Expr* parse_expression(struct Parser* parser) -{ - return parse_binary(parser, parse_multiplicative, TOKEN_PLUS, TOKEN_MINUS, TOKEN_MINUS); -} - -static bool is_compare_op(enum TokenType type) -{ - return type == TOKEN_EQUAL_EQUAL - || type == TOKEN_BANG_EQUAL - || type == TOKEN_LESS - || type == TOKEN_LESS_EQUAL - || type == TOKEN_GREATER - || type == TOKEN_GREATER_EQUAL; -} - -static bool parse_call(struct Parser* parser, struct Token name, struct Statement* out) -{ - struct Expr** args = NULL; - size_t count = 0; - size_t capacity = 0; - - if (!check(parser, TOKEN_RIGHT_PAREN)) - { - do - { - struct Expr* arg = parse_expression(parser); - if (arg == NULL) - goto error; - - if (count == capacity) - { - capacity = capacity < 4 ? 4 : capacity * 2; - struct Expr** grown = realloc(args, capacity * sizeof(struct Expr*)); - if (grown == NULL) - { - free_expr(arg); - goto error; - } - args = grown; - } - args[count] = arg; - count += 1; - } - while (match_token(parser, TOKEN_COMMA)); - } - - if (!consume(parser, TOKEN_RIGHT_PAREN, "expected ')' after arguments")) - goto error; - - out->kind = STATEMENT_CALL; - out->call.name = name; - out->call.args = args; - out->call.arg_count = count; - out->call.arg_capacity = capacity; - return true; - -error: - for (size_t i = 0; i < count; i += 1) - free_expr(args[i]); - free(args); - return false; -} - -static bool parse_statement(struct Parser* parser, struct Statement* out); - -// A branch body is either a braced block or a single bare statement, always -// returned as a list so codegen and freeing treat both the same way. -static bool parse_block(struct Parser* parser, struct Statement** out_body, size_t* out_count) -{ - if (!match_token(parser, TOKEN_LEFT_BRACE)) - { - struct Statement* body = malloc(sizeof(*body)); - if (body == NULL) - return false; - - if (!parse_statement(parser, body)) - { - free(body); - return false; - } - - *out_body = body; - *out_count = 1; - return true; - } - - struct Statement* body = NULL; - size_t count = 0; - size_t capacity = 0; - - while (!check(parser, TOKEN_RIGHT_BRACE)) - { - if (check(parser, TOKEN_EOF)) - { - error_at(parser, parser->current, "unterminated block"); - goto error; - } - - if (count == capacity) - { - size_t grown_capacity = capacity == 0 ? 4 : capacity * 2; - struct Statement* grown = realloc(body, grown_capacity * sizeof(*grown)); - if (grown == NULL) - goto error; - body = grown; - capacity = grown_capacity; - } - - if (!parse_statement(parser, &body[count])) - goto error; - count += 1; - } - advance_parser(parser); - - *out_body = body; - *out_count = count; - return true; - -error: - for (size_t i = 0; i < count; i += 1) - free_statement(&body[i]); - free(body); - return false; -} - -static bool parse_if(struct Parser* parser, struct Statement* out) -{ - struct Expr* left = parse_expression(parser); - if (left == NULL) - return false; - - if (!is_compare_op(parser->current.type)) - { - error_at(parser, parser->current, "expected a comparison operator"); - free_expr(left); - return false; - } - advance_parser(parser); - struct Token comparison = parser->previous; - - struct Expr* right = parse_expression(parser); - if (right == NULL) - { - free_expr(left); - return false; - } - - struct Statement* body; - size_t body_count; - if (!parse_block(parser, &body, &body_count)) - { - free_expr(left); - free_expr(right); - return false; - } - - struct Statement* else_body = NULL; - size_t else_count = 0; - if (match_token(parser, TOKEN_ELSE) && !parse_block(parser, &else_body, &else_count)) - { - for (size_t i = 0; i < body_count; i += 1) - free_statement(&body[i]); - free(body); - free_expr(left); - free_expr(right); - return false; - } - - out->kind = STATEMENT_IF; - out->branch.left = left; - out->branch.comparison = comparison; - out->branch.right = right; - out->branch.body = body; - out->branch.body_count = body_count; - out->branch.else_body = else_body; - out->branch.else_count = else_count; - return true; -} - -static bool parse_statement(struct Parser* parser, struct Statement* out) -{ - if (match_token(parser, TOKEN_IF)) - return parse_if(parser, out); - - if (match_token(parser, TOKEN_SYSCALL)) - { - out->kind = STATEMENT_SYSCALL; - return true; - } - - if (match_token(parser, TOKEN_STACK)) - { - if (!consume(parser, TOKEN_IDENTIFIER, "expected buffer name after 'stack'")) - return false; - struct Token name = parser->previous; - - if (!consume(parser, TOKEN_LEFT_BRACKET, "expected '[' after buffer name")) - return false; - - struct Expr* size = parse_expression(parser); - if (size == NULL) - return false; - - if (!consume(parser, TOKEN_RIGHT_BRACKET, "expected ']' after buffer size")) - { - free_expr(size); - return false; - } - - out->kind = STATEMENT_STACK; - out->stack.name = name; - out->stack.size = size; - return true; - } - - if (match_token(parser, TOKEN_GOTO)) - { - if (!consume(parser, TOKEN_IDENTIFIER, "expected label after 'goto'")) - return false; - - out->kind = STATEMENT_GOTO; - out->jump.label = parser->previous; - return true; - } - - bool deref = match_token(parser, TOKEN_CARET); - enum StoreSize store_size = deref ? parse_store_size(parser) : STORE_SIZE_NONE; - - if (!consume(parser, TOKEN_IDENTIFIER, "expected a statement")) - return false; - struct Token name = parser->previous; - - if (!deref && match_token(parser, TOKEN_LEFT_PAREN)) - return parse_call(parser, name, out); - - if (!deref && match_token(parser, TOKEN_COLON)) - { - out->kind = STATEMENT_LABEL; - out->label.name = name; - return true; - } - - if (!is_assign_op(parser->current.type)) - { - error_at(parser, parser->current, "expected an assignment operator"); - return false; - } - - advance_parser(parser); - struct Token op = parser->previous; - - struct Expr* value = parse_expression(parser); - if (value == NULL) - return false; - - out->kind = STATEMENT_ASSIGN; - out->assign.target_deref = deref; - out->assign.store_size = store_size; - out->assign.target = name; - out->assign.op = op; - out->assign.value = value; - return true; -} - -static bool parse_proc(struct Parser* parser, struct Program* program) -{ - struct ProcDecl decl = create_proc(); - - if (!consume(parser, TOKEN_IDENTIFIER, "expected procedure name after 'proc'")) - goto error; - decl.name = parser->previous; - - if (match_token(parser, TOKEN_LEFT_PAREN)) - { - if (!parse_params(parser, &decl)) - goto error; - if (!consume(parser, TOKEN_RIGHT_PAREN, "expected ')' after parameters")) - goto error; - } - - if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' to begin procedure body")) - goto error; - - while (!check(parser, TOKEN_RIGHT_BRACE)) - { - if (check(parser, TOKEN_EOF)) - { - error_at(parser, parser->current, "unterminated procedure body"); - goto error; - } - - struct Statement statement; - if (!parse_statement(parser, &statement)) - goto error; - - add_statement(&decl, statement); - } - advance_parser(parser); - - add_proc(program, decl); - return true; - -error: - free_proc(&decl); - return false; -} - -static bool token_text_is(struct Token token, const char* text) -{ - size_t length = strlen(text); - return token.length == length && memcmp(token.start, text, length) == 0; -} - -static bool parse_directive(struct Parser* parser, struct Program* program) -{ - if (!consume(parser, TOKEN_IDENTIFIER, "expected directive name after '['")) - return false; - struct Token key = parser->previous; - - if (!consume(parser, TOKEN_COLON, "expected ':' after directive name")) - return false; - - if (!check(parser, TOKEN_IDENTIFIER) && !check(parser, TOKEN_INTEGER)) - { - error_at(parser, parser->current, "expected a directive value"); - return false; - } - advance_parser(parser); - struct Token value = parser->previous; - - if (!consume(parser, TOKEN_RIGHT_BRACKET, "expected ']' to close directive")) - return false; - - if (token_text_is(key, "bits")) - { - if (value.type != TOKEN_INTEGER || (!token_text_is(value, "64") && !token_text_is(value, "32"))) - { - error_at(parser, value, "bits must be 32 or 64"); - return false; - } - program->config.bits = token_text_is(value, "64") ? 64 : 32; - return true; - } - - if (token_text_is(key, "entry")) - { - if (value.type != TOKEN_IDENTIFIER) - { - error_at(parser, value, "entry must be a procedure name"); - return false; - } - program->config.has_entry = true; - program->config.entry = value; - return true; - } - - if (token_text_is(key, "enable")) - { - if (value.type == TOKEN_IDENTIFIER && token_text_is(value, "logical_registers")) - { - program->config.logical_registers = true; - return true; - } - error_at(parser, value, "unknown extension"); - return false; - } - - error_at(parser, key, "unknown directive"); - return false; -} - -bool parse_program(struct Lexer* lexer, struct Program* out) -{ - struct Parser parser = { 0 }; - parser.lexer = lexer; - parser.source.name = lexer->name; - parser.source.text = lexer->source; - parser.had_error = false; - advance_parser(&parser); - - *out = create_program(); - - while (!check(&parser, TOKEN_EOF)) - { - if (check(&parser, TOKEN_LEFT_BRACKET)) - { - advance_parser(&parser); - if (!parse_directive(&parser, out)) - return false; - } - else if (check(&parser, TOKEN_CONST)) - { - advance_parser(&parser); - if (!parse_const(&parser, out)) - return false; - } - else if (check(&parser, TOKEN_DATA)) - { - advance_parser(&parser); - if (!parse_data(&parser, out)) - return false; - } - else if (check(&parser, TOKEN_ENUM)) - { - advance_parser(&parser); - if (!parse_enum(&parser, out)) - return false; - } - else if (check(&parser, TOKEN_STRUCT)) - { - advance_parser(&parser); - if (!parse_struct(&parser, out)) - return false; - } - else if (check(&parser, TOKEN_PROC)) - { - advance_parser(&parser); - if (!parse_proc(&parser, out)) - return false; - } - else - { - error_at(&parser, parser.current, "expected a top-level declaration"); - return false; - } - } - - return !parser.had_error; -} diff --git a/src/parser/parser.h b/src/parser/parser.h deleted file mode 100644 index 82d306a..0000000 --- a/src/parser/parser.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -#include "lexer/lexer.h" -#include "parser/ast.h" - -bool parse_program(struct Lexer* lexer, struct Program* out); diff --git a/src/sema.c b/src/sema.c new file mode 100644 index 0000000..12f2689 --- /dev/null +++ b/src/sema.c @@ -0,0 +1,527 @@ +#include +#include +#include +#include + +#include "sema.h" + +static bool token_is(struct Token token, const char* text) +{ + size_t length = strlen(text); + return token.length == length && memcmp(token.start, text, length) == 0; +} + +static bool names_equal(struct Token a, struct Token b) +{ + return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; +} + +static struct ProcDecl* find_proc(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->proc_count; i += 1) + if (names_equal(program->procs[i].name, name)) + return &program->procs[i]; + + return NULL; +} + +static bool check_duplicate_names(struct Source source, struct Program* program) +{ + size_t count = program->const_count + program->data_count + + program->enum_count + program->struct_count + program->proc_count; + if (count == 0) + return true; + + struct Token* names = malloc(count * sizeof(*names)); + if (names == NULL) + return true; + size_t n = 0; + for (size_t i = 0; i < program->const_count; i += 1) + { + names[n] = program->consts[i].name; + n += 1; + } + for (size_t i = 0; i < program->data_count; i += 1) + { + names[n] = program->data_decls[i].name; + n += 1; + } + for (size_t i = 0; i < program->enum_count; i += 1) + { + names[n] = program->enums[i].name; + n += 1; + } + for (size_t i = 0; i < program->struct_count; i += 1) + { + names[n] = program->structs[i].name; + n += 1; + } + for (size_t i = 0; i < program->proc_count; i += 1) + { + names[n] = program->procs[i].name; + n += 1; + } + + bool ok = true; + for (size_t i = 0; i < count; i += 1) + for (size_t j = 0; j < i; j += 1) + if (names_equal(names[i], names[j])) + { + char message[128]; + snprintf(message, sizeof(message), "'%.*s' is already defined", + (int)names[i].length, names[i].start); + report_error(source, names[i], message); + ok = false; + } + + free(names); + return ok; +} + +static bool check_entry_point(struct Source source, struct Program* program) +{ + if (!program->config.has_entry) + return true; + + if (find_proc(program, program->config.entry) != NULL) + return true; + + struct Token entry = program->config.entry; + char message[128]; + snprintf(message, sizeof(message), "entry point '%.*s' is not defined", + (int)entry.length, entry.start); + report_error(source, entry, message); + return false; +} + +static bool is_program_const(const struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->const_count; i += 1) + if (names_equal(program->consts[i].name, name)) + return true; + + return false; +} + +static struct Token first_token(const struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_UNARY: return expr->unary.op; + case EXPR_BINARY: return first_token(expr->binary.left); + case EXPR_MEMBER: return first_token(expr->member.object); + case EXPR_DEREF: return first_token(expr->deref.address); + default: return expr->primary.token; + } +} + +static bool check_const_value(struct Source source, struct Program* program, struct Expr* expr) +{ + if (expr->kind == EXPR_UNARY) + return check_const_value(source, program, expr->unary.operand); + + if (expr->kind == EXPR_BINARY) + { + bool left = check_const_value(source, program, expr->binary.left); + bool right = check_const_value(source, program, expr->binary.right); + return left && right; + } + + if (expr->kind == EXPR_PRIMARY) + { + struct Token token = expr->primary.token; + if (token.type == TOKEN_INTEGER || token.type == TOKEN_CHAR) + return true; + + if (token.type == TOKEN_IDENTIFIER && is_program_const(program, token)) + return true; + + char message[128]; + snprintf(message, sizeof(message), "'%.*s' is not a constant", + (int)token.length, token.start); + report_error(source, token, message); + return false; + } + + report_error(source, first_token(expr), "constant must be an integer expression"); + return false; +} + +static bool check_const_values(struct Source source, struct Program* program) +{ + bool ok = true; + for (size_t i = 0; i < program->const_count; i += 1) + if (!check_const_value(source, program, program->consts[i].value)) + ok = false; + + return ok; +} + +struct RefCheck +{ + struct Source source; + struct Program* program; + struct ProcDecl* proc; + bool ok; +}; + +static void ref_error(struct RefCheck* check, struct Token token, const char* format, ...) +{ + char message[256]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + report_error(check->source, token, message); + check->ok = false; +} + +static bool is_arch_register(struct Token token) +{ + static const char* names[] = { + "rax", "eax", "ax", "al", "ah", + "rbx", "ebx", "bx", "bl", "bh", + "rcx", "ecx", "cx", "cl", "ch", + "rdx", "edx", "dx", "dl", "dh", + "rsi", "esi", "si", "sil", + "rdi", "edi", "di", "dil", + "rbp", "ebp", "bp", "bpl", + "rsp", "esp", "sp", "spl", + "r8", "r8d", "r8w", "r8b", + "r9", "r9d", "r9w", "r9b", + "r10", "r10d", "r10w", "r10b", + "r11", "r11d", "r11w", "r11b", + "r12", "r12d", "r12w", "r12b", + "r13", "r13d", "r13w", "r13b", + "r14", "r14d", "r14w", "r14b", + "r15", "r15d", "r15w", "r15b", + "rip", + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", + }; + + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i += 1) + if (token_is(token, names[i])) + return true; + + return false; +} + +static bool is_logical_register(struct Token token) +{ + if (token.length < 2 || token.start[0] != 'r') + return false; + + uint32_t index = 0; + for (size_t i = 1; i < token.length; i += 1) + { + char digit = token.start[i]; + if (digit < '0' || digit > '9') + return false; + index = index * 10 + (uint32_t)(digit - '0'); + } + + return index >= 1 && index <= 14; +} + +static bool is_register(struct RefCheck* check, struct Token token) +{ + if (is_arch_register(token)) + return true; + + return check->program->config.logical_registers && is_logical_register(token); +} + +static bool is_param(const struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->proc->param_count; i += 1) + if (names_equal(check->proc->params[i].name, token)) + return true; + + return false; +} + +static bool is_const(const struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->program->const_count; i += 1) + if (names_equal(check->program->consts[i].name, token)) + return true; + + return false; +} + +static bool is_data(const struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->program->data_count; i += 1) + if (names_equal(check->program->data_decls[i].name, token)) + return true; + + return false; +} + +static struct EnumDecl* find_enum(struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->program->enum_count; i += 1) + if (names_equal(check->program->enums[i].name, token)) + return &check->program->enums[i]; + + return NULL; +} + +static struct StructDecl* find_struct(struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->program->struct_count; i += 1) + if (names_equal(check->program->structs[i].name, token)) + return &check->program->structs[i]; + + return NULL; +} + +static bool is_stack_buffer(struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->proc->body_count; i += 1) + { + const struct Statement* statement = &check->proc->body[i]; + if (statement->kind == STATEMENT_STACK && names_equal(statement->stack.name, token)) + return true; + } + + return false; +} + +static bool is_label(struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->proc->body_count; i += 1) + { + const struct Statement* statement = &check->proc->body[i]; + if (statement->kind == STATEMENT_LABEL && names_equal(statement->label.name, token)) + return true; + } + + return false; +} + +static void check_value_name(struct RefCheck* check, struct Token name) +{ + if (is_register(check, name) || is_param(check, name) || is_const(check, name) + || is_data(check, name) || is_stack_buffer(check, name)) + return; + + ref_error(check, name, "undefined name '%.*s'", (int)name.length, name.start); +} + +static void check_expr(struct RefCheck* check, const struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + if (expr->primary.token.type == TOKEN_IDENTIFIER) + check_value_name(check, expr->primary.token); + break; + case EXPR_BINARY: + check_expr(check, expr->binary.left); + check_expr(check, expr->binary.right); + break; + case EXPR_UNARY: + check_expr(check, expr->unary.operand); + break; + case EXPR_DEREF: + { + const struct Expr* address = expr->deref.address; + if (expr->deref.is_signed + && expr->deref.size != STORE_SIZE_BYTE + && expr->deref.size != STORE_SIZE_WORD + && expr->deref.size != STORE_SIZE_DWORD) + ref_error(check, first_token(expr), "signed load requires a byte, word, or dword size"); + + if (address->kind == EXPR_PRIMARY + && (is_register(check, address->primary.token) || is_param(check, address->primary.token))) + break; + + if (address->kind == EXPR_PRIMARY) + ref_error(check, address->primary.token, "dereference address must be a register"); + else + check_expr(check, address); + break; + } + case EXPR_MEMBER: + { + const struct Expr* object = expr->member.object; + struct Token member = expr->member.member; + + if (member.type == TOKEN_INTEGER) + { + if (object->kind != EXPR_PRIMARY || !is_register(check, object->primary.token)) + ref_error(check, member, "size suffix requires a register"); + break; + } + + if (object->kind == EXPR_PRIMARY) + { + const struct EnumDecl* enumeration = find_enum(check, object->primary.token); + if (enumeration != NULL) + { + bool found = false; + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (names_equal(enumeration->members[i], member)) + found = true; + if (!found) + ref_error(check, member, "enum '%.*s' has no member '%.*s'", + (int)object->primary.token.length, object->primary.token.start, + (int)member.length, member.start); + break; + } + + const struct StructDecl* layout = find_struct(check, object->primary.token); + if (layout != NULL) + { + bool found = token_is(member, "size"); + for (size_t i = 0; i < layout->field_count; i += 1) + if (names_equal(layout->fields[i].name, member)) + found = true; + if (!found) + ref_error(check, member, "struct '%.*s' has no field '%.*s'", + (int)object->primary.token.length, object->primary.token.start, + (int)member.length, member.start); + break; + } + + if (is_data(check, object->primary.token)) + { + if (!token_is(member, "len")) + ref_error(check, member, "unknown member '%.*s'", (int)member.length, member.start); + break; + } + } + + check_expr(check, object); + break; + } + } +} + +static void check_target(struct RefCheck* check, struct Token target) +{ + if (is_register(check, target) || is_param(check, target)) + return; + + ref_error(check, target, "cannot assign to '%.*s': not a register", (int)target.length, target.start); +} + +// a stack size must be a compile-time constant: integer/char literals, other +// constants, enum values, struct sizes/offsets, and arithmetic over them +static void check_stack_size(struct RefCheck* check, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_BINARY: + check_stack_size(check, expr->binary.left); + check_stack_size(check, expr->binary.right); + break; + case EXPR_UNARY: + check_stack_size(check, expr->unary.operand); + break; + case EXPR_MEMBER: + { + const struct Expr* object = expr->member.object; + if (object->kind == EXPR_PRIMARY + && (find_enum(check, object->primary.token) != NULL + || find_struct(check, object->primary.token) != NULL)) + check_expr(check, expr); + else + ref_error(check, first_token(expr), "stack size must be a constant"); + break; + } + case EXPR_PRIMARY: + { + struct Token token = expr->primary.token; + if (token.type == TOKEN_INTEGER || token.type == TOKEN_CHAR) + break; + if (token.type == TOKEN_IDENTIFIER && is_const(check, token)) + break; + ref_error(check, token, "stack size must be a constant"); + break; + } + default: + ref_error(check, first_token(expr), "stack size must be a constant"); + break; + } +} + +static void check_statement(struct RefCheck* check, struct Statement* statement) +{ + switch (statement->kind) + { + case STATEMENT_ASSIGN: + check_target(check, statement->assign.target); + check_expr(check, statement->assign.value); + break; + case STATEMENT_GOTO: + if (!is_label(check, statement->jump.label)) + ref_error(check, statement->jump.label, "undefined label '%.*s'", + (int)statement->jump.label.length, statement->jump.label.start); + break; + case STATEMENT_IF: + check_expr(check, statement->branch.left); + check_expr(check, statement->branch.right); + for (size_t i = 0; i < statement->branch.body_count; i += 1) + check_statement(check, &statement->branch.body[i]); + for (size_t i = 0; i < statement->branch.else_count; i += 1) + check_statement(check, &statement->branch.else_body[i]); + break; + case STATEMENT_CALL: + { + struct CallStatement* call = &statement->call; + struct ProcDecl* callee = find_proc(check->program, call->name); + if (callee == NULL) + ref_error(check, call->name, "undefined procedure '%.*s'", + (int)call->name.length, call->name.start); + else if (callee->param_count != call->arg_count) + ref_error(check, call->name, "'%.*s' expects %zu argument(s), got %zu", + (int)call->name.length, call->name.start, callee->param_count, call->arg_count); + + for (size_t i = 0; i < call->arg_count; i += 1) + check_expr(check, call->args[i]); + break; + } + case STATEMENT_STACK: + check_stack_size(check, statement->stack.size); + break; + case STATEMENT_LABEL: + case STATEMENT_SYSCALL: + break; + } +} + +static bool check_references(struct Source source, struct Program* program) +{ + bool ok = true; + for (size_t i = 0; i < program->proc_count; i += 1) + { + struct RefCheck check = { source, program, &program->procs[i], true }; + for (size_t j = 0; j < program->procs[i].body_count; j += 1) + check_statement(&check, &program->procs[i].body[j]); + + if (!check.ok) + ok = false; + } + + return ok; +} + +bool analyze_program(struct Source source, struct Program* program) +{ + bool ok = true; + + if (!check_duplicate_names(source, program)) + ok = false; + if (!check_entry_point(source, program)) + ok = false; + if (!check_const_values(source, program)) + ok = false; + if (!check_references(source, program)) + ok = false; + + return ok; +} diff --git a/src/sema.h b/src/sema.h new file mode 100644 index 0000000..f28c49c --- /dev/null +++ b/src/sema.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "ast.h" +#include "diag.h" + +bool analyze_program(struct Source source, struct Program* program); diff --git a/src/sema/sema.c b/src/sema/sema.c deleted file mode 100644 index 748ccbe..0000000 --- a/src/sema/sema.c +++ /dev/null @@ -1,527 +0,0 @@ -#include -#include -#include -#include - -#include "sema/sema.h" - -static bool token_is(struct Token token, const char* text) -{ - size_t length = strlen(text); - return token.length == length && memcmp(token.start, text, length) == 0; -} - -static bool names_equal(struct Token a, struct Token b) -{ - return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; -} - -static struct ProcDecl* find_proc(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->proc_count; i += 1) - if (names_equal(program->procs[i].name, name)) - return &program->procs[i]; - - return NULL; -} - -static bool check_duplicate_names(struct Source source, struct Program* program) -{ - size_t count = program->const_count + program->data_count - + program->enum_count + program->struct_count + program->proc_count; - if (count == 0) - return true; - - struct Token* names = malloc(count * sizeof(*names)); - if (names == NULL) - return true; - size_t n = 0; - for (size_t i = 0; i < program->const_count; i += 1) - { - names[n] = program->consts[i].name; - n += 1; - } - for (size_t i = 0; i < program->data_count; i += 1) - { - names[n] = program->data_decls[i].name; - n += 1; - } - for (size_t i = 0; i < program->enum_count; i += 1) - { - names[n] = program->enums[i].name; - n += 1; - } - for (size_t i = 0; i < program->struct_count; i += 1) - { - names[n] = program->structs[i].name; - n += 1; - } - for (size_t i = 0; i < program->proc_count; i += 1) - { - names[n] = program->procs[i].name; - n += 1; - } - - bool ok = true; - for (size_t i = 0; i < count; i += 1) - for (size_t j = 0; j < i; j += 1) - if (names_equal(names[i], names[j])) - { - char message[128]; - snprintf(message, sizeof(message), "'%.*s' is already defined", - (int)names[i].length, names[i].start); - report_error(source, names[i], message); - ok = false; - } - - free(names); - return ok; -} - -static bool check_entry_point(struct Source source, struct Program* program) -{ - if (!program->config.has_entry) - return true; - - if (find_proc(program, program->config.entry) != NULL) - return true; - - struct Token entry = program->config.entry; - char message[128]; - snprintf(message, sizeof(message), "entry point '%.*s' is not defined", - (int)entry.length, entry.start); - report_error(source, entry, message); - return false; -} - -static bool is_program_const(const struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->const_count; i += 1) - if (names_equal(program->consts[i].name, name)) - return true; - - return false; -} - -static struct Token first_token(const struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_UNARY: return expr->unary.op; - case EXPR_BINARY: return first_token(expr->binary.left); - case EXPR_MEMBER: return first_token(expr->member.object); - case EXPR_DEREF: return first_token(expr->deref.address); - default: return expr->primary.token; - } -} - -static bool check_const_value(struct Source source, struct Program* program, struct Expr* expr) -{ - if (expr->kind == EXPR_UNARY) - return check_const_value(source, program, expr->unary.operand); - - if (expr->kind == EXPR_BINARY) - { - bool left = check_const_value(source, program, expr->binary.left); - bool right = check_const_value(source, program, expr->binary.right); - return left && right; - } - - if (expr->kind == EXPR_PRIMARY) - { - struct Token token = expr->primary.token; - if (token.type == TOKEN_INTEGER || token.type == TOKEN_CHAR) - return true; - - if (token.type == TOKEN_IDENTIFIER && is_program_const(program, token)) - return true; - - char message[128]; - snprintf(message, sizeof(message), "'%.*s' is not a constant", - (int)token.length, token.start); - report_error(source, token, message); - return false; - } - - report_error(source, first_token(expr), "constant must be an integer expression"); - return false; -} - -static bool check_const_values(struct Source source, struct Program* program) -{ - bool ok = true; - for (size_t i = 0; i < program->const_count; i += 1) - if (!check_const_value(source, program, program->consts[i].value)) - ok = false; - - return ok; -} - -struct RefCheck -{ - struct Source source; - struct Program* program; - struct ProcDecl* proc; - bool ok; -}; - -static void ref_error(struct RefCheck* check, struct Token token, const char* format, ...) -{ - char message[256]; - va_list args; - va_start(args, format); - vsnprintf(message, sizeof(message), format, args); - va_end(args); - - report_error(check->source, token, message); - check->ok = false; -} - -static bool is_arch_register(struct Token token) -{ - static const char* names[] = { - "rax", "eax", "ax", "al", "ah", - "rbx", "ebx", "bx", "bl", "bh", - "rcx", "ecx", "cx", "cl", "ch", - "rdx", "edx", "dx", "dl", "dh", - "rsi", "esi", "si", "sil", - "rdi", "edi", "di", "dil", - "rbp", "ebp", "bp", "bpl", - "rsp", "esp", "sp", "spl", - "r8", "r8d", "r8w", "r8b", - "r9", "r9d", "r9w", "r9b", - "r10", "r10d", "r10w", "r10b", - "r11", "r11d", "r11w", "r11b", - "r12", "r12d", "r12w", "r12b", - "r13", "r13d", "r13w", "r13b", - "r14", "r14d", "r14w", "r14b", - "r15", "r15d", "r15w", "r15b", - "rip", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", - }; - - for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i += 1) - if (token_is(token, names[i])) - return true; - - return false; -} - -static bool is_logical_register(struct Token token) -{ - if (token.length < 2 || token.start[0] != 'r') - return false; - - uint32_t index = 0; - for (size_t i = 1; i < token.length; i += 1) - { - char digit = token.start[i]; - if (digit < '0' || digit > '9') - return false; - index = index * 10 + (uint32_t)(digit - '0'); - } - - return index >= 1 && index <= 14; -} - -static bool is_register(struct RefCheck* check, struct Token token) -{ - if (is_arch_register(token)) - return true; - - return check->program->config.logical_registers && is_logical_register(token); -} - -static bool is_param(const struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->proc->param_count; i += 1) - if (names_equal(check->proc->params[i].name, token)) - return true; - - return false; -} - -static bool is_const(const struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->program->const_count; i += 1) - if (names_equal(check->program->consts[i].name, token)) - return true; - - return false; -} - -static bool is_data(const struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->program->data_count; i += 1) - if (names_equal(check->program->data_decls[i].name, token)) - return true; - - return false; -} - -static struct EnumDecl* find_enum(struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->program->enum_count; i += 1) - if (names_equal(check->program->enums[i].name, token)) - return &check->program->enums[i]; - - return NULL; -} - -static struct StructDecl* find_struct(struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->program->struct_count; i += 1) - if (names_equal(check->program->structs[i].name, token)) - return &check->program->structs[i]; - - return NULL; -} - -static bool is_stack_buffer(struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->proc->body_count; i += 1) - { - const struct Statement* statement = &check->proc->body[i]; - if (statement->kind == STATEMENT_STACK && names_equal(statement->stack.name, token)) - return true; - } - - return false; -} - -static bool is_label(struct RefCheck* check, struct Token token) -{ - for (size_t i = 0; i < check->proc->body_count; i += 1) - { - const struct Statement* statement = &check->proc->body[i]; - if (statement->kind == STATEMENT_LABEL && names_equal(statement->label.name, token)) - return true; - } - - return false; -} - -static void check_value_name(struct RefCheck* check, struct Token name) -{ - if (is_register(check, name) || is_param(check, name) || is_const(check, name) - || is_data(check, name) || is_stack_buffer(check, name)) - return; - - ref_error(check, name, "undefined name '%.*s'", (int)name.length, name.start); -} - -static void check_expr(struct RefCheck* check, const struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - if (expr->primary.token.type == TOKEN_IDENTIFIER) - check_value_name(check, expr->primary.token); - break; - case EXPR_BINARY: - check_expr(check, expr->binary.left); - check_expr(check, expr->binary.right); - break; - case EXPR_UNARY: - check_expr(check, expr->unary.operand); - break; - case EXPR_DEREF: - { - const struct Expr* address = expr->deref.address; - if (expr->deref.is_signed - && expr->deref.size != STORE_SIZE_BYTE - && expr->deref.size != STORE_SIZE_WORD - && expr->deref.size != STORE_SIZE_DWORD) - ref_error(check, first_token(expr), "signed load requires a byte, word, or dword size"); - - if (address->kind == EXPR_PRIMARY - && (is_register(check, address->primary.token) || is_param(check, address->primary.token))) - break; - - if (address->kind == EXPR_PRIMARY) - ref_error(check, address->primary.token, "dereference address must be a register"); - else - check_expr(check, address); - break; - } - case EXPR_MEMBER: - { - const struct Expr* object = expr->member.object; - struct Token member = expr->member.member; - - if (member.type == TOKEN_INTEGER) - { - if (object->kind != EXPR_PRIMARY || !is_register(check, object->primary.token)) - ref_error(check, member, "size suffix requires a register"); - break; - } - - if (object->kind == EXPR_PRIMARY) - { - const struct EnumDecl* enumeration = find_enum(check, object->primary.token); - if (enumeration != NULL) - { - bool found = false; - for (size_t i = 0; i < enumeration->member_count; i += 1) - if (names_equal(enumeration->members[i], member)) - found = true; - if (!found) - ref_error(check, member, "enum '%.*s' has no member '%.*s'", - (int)object->primary.token.length, object->primary.token.start, - (int)member.length, member.start); - break; - } - - const struct StructDecl* layout = find_struct(check, object->primary.token); - if (layout != NULL) - { - bool found = token_is(member, "size"); - for (size_t i = 0; i < layout->field_count; i += 1) - if (names_equal(layout->fields[i].name, member)) - found = true; - if (!found) - ref_error(check, member, "struct '%.*s' has no field '%.*s'", - (int)object->primary.token.length, object->primary.token.start, - (int)member.length, member.start); - break; - } - - if (is_data(check, object->primary.token)) - { - if (!token_is(member, "len")) - ref_error(check, member, "unknown member '%.*s'", (int)member.length, member.start); - break; - } - } - - check_expr(check, object); - break; - } - } -} - -static void check_target(struct RefCheck* check, struct Token target) -{ - if (is_register(check, target) || is_param(check, target)) - return; - - ref_error(check, target, "cannot assign to '%.*s': not a register", (int)target.length, target.start); -} - -// a stack size must be a compile-time constant: integer/char literals, other -// constants, enum values, struct sizes/offsets, and arithmetic over them -static void check_stack_size(struct RefCheck* check, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_BINARY: - check_stack_size(check, expr->binary.left); - check_stack_size(check, expr->binary.right); - break; - case EXPR_UNARY: - check_stack_size(check, expr->unary.operand); - break; - case EXPR_MEMBER: - { - const struct Expr* object = expr->member.object; - if (object->kind == EXPR_PRIMARY - && (find_enum(check, object->primary.token) != NULL - || find_struct(check, object->primary.token) != NULL)) - check_expr(check, expr); - else - ref_error(check, first_token(expr), "stack size must be a constant"); - break; - } - case EXPR_PRIMARY: - { - struct Token token = expr->primary.token; - if (token.type == TOKEN_INTEGER || token.type == TOKEN_CHAR) - break; - if (token.type == TOKEN_IDENTIFIER && is_const(check, token)) - break; - ref_error(check, token, "stack size must be a constant"); - break; - } - default: - ref_error(check, first_token(expr), "stack size must be a constant"); - break; - } -} - -static void check_statement(struct RefCheck* check, struct Statement* statement) -{ - switch (statement->kind) - { - case STATEMENT_ASSIGN: - check_target(check, statement->assign.target); - check_expr(check, statement->assign.value); - break; - case STATEMENT_GOTO: - if (!is_label(check, statement->jump.label)) - ref_error(check, statement->jump.label, "undefined label '%.*s'", - (int)statement->jump.label.length, statement->jump.label.start); - break; - case STATEMENT_IF: - check_expr(check, statement->branch.left); - check_expr(check, statement->branch.right); - for (size_t i = 0; i < statement->branch.body_count; i += 1) - check_statement(check, &statement->branch.body[i]); - for (size_t i = 0; i < statement->branch.else_count; i += 1) - check_statement(check, &statement->branch.else_body[i]); - break; - case STATEMENT_CALL: - { - struct CallStatement* call = &statement->call; - struct ProcDecl* callee = find_proc(check->program, call->name); - if (callee == NULL) - ref_error(check, call->name, "undefined procedure '%.*s'", - (int)call->name.length, call->name.start); - else if (callee->param_count != call->arg_count) - ref_error(check, call->name, "'%.*s' expects %zu argument(s), got %zu", - (int)call->name.length, call->name.start, callee->param_count, call->arg_count); - - for (size_t i = 0; i < call->arg_count; i += 1) - check_expr(check, call->args[i]); - break; - } - case STATEMENT_STACK: - check_stack_size(check, statement->stack.size); - break; - case STATEMENT_LABEL: - case STATEMENT_SYSCALL: - break; - } -} - -static bool check_references(struct Source source, struct Program* program) -{ - bool ok = true; - for (size_t i = 0; i < program->proc_count; i += 1) - { - struct RefCheck check = { source, program, &program->procs[i], true }; - for (size_t j = 0; j < program->procs[i].body_count; j += 1) - check_statement(&check, &program->procs[i].body[j]); - - if (!check.ok) - ok = false; - } - - return ok; -} - -bool analyze_program(struct Source source, struct Program* program) -{ - bool ok = true; - - if (!check_duplicate_names(source, program)) - ok = false; - if (!check_entry_point(source, program)) - ok = false; - if (!check_const_values(source, program)) - ok = false; - if (!check_references(source, program)) - ok = false; - - return ok; -} diff --git a/src/sema/sema.h b/src/sema/sema.h deleted file mode 100644 index 468d1be..0000000 --- a/src/sema/sema.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -#include "diag/diag.h" -#include "parser/ast.h" - -bool analyze_program(struct Source source, struct Program* program); diff --git a/tests/codegen_test.c b/tests/codegen_test.c index e2969db..1da711c 100644 --- a/tests/codegen_test.c +++ b/tests/codegen_test.c @@ -1,9 +1,9 @@ #include #include -#include "parser/parser.h" -#include "codegen/nasm.h" +#include "nasm.h" #include "tests.h" +#include "parser.h" static void generate_to_buffer(struct Program* program, char* buffer, size_t size) { diff --git a/tests/lexer_test.c b/tests/lexer_test.c index a44b1f0..0810c3b 100644 --- a/tests/lexer_test.c +++ b/tests/lexer_test.c @@ -1,7 +1,7 @@ #include #include -#include "lexer/lexer.h" +#include "lexer.h" #include "tests.h" static bool token_matches(struct Token token, enum TokenType type, const char* text) diff --git a/tests/parser_test.c b/tests/parser_test.c index e1199eb..b51093e 100644 --- a/tests/parser_test.c +++ b/tests/parser_test.c @@ -1,8 +1,8 @@ #include #include -#include "parser/parser.h" #include "tests.h" +#include "parser.h" static bool text_is(struct Token token, const char* text) { diff --git a/tests/sema_test.c b/tests/sema_test.c index 643ffdf..3c12001 100644 --- a/tests/sema_test.c +++ b/tests/sema_test.c @@ -1,8 +1,8 @@ #include -#include "parser/parser.h" -#include "sema/sema.h" +#include "sema.h" #include "tests.h" +#include "parser.h" static bool analyze_source(const char* source) { -- cgit v1.3