diff options
34 files changed, 110 insertions, 1785 deletions
diff --git a/examples/arithmetic.hdass b/examples/arithmetic.hdass index 1534146..f3da8ba 100644 --- a/examples/arithmetic.hdass +++ b/examples/arithmetic.hdass @@ -1,6 +1,5 @@ [entry: main] -// Computes (5 + 3) * 2 - 1 = 15 and returns it as the exit status. const SYS_EXIT = 60 proc main diff --git a/examples/arm64/exit.hdass b/examples/arm64/exit.hdass index a86e5e2..a7e89df 100644 --- a/examples/arm64/exit.hdass +++ b/examples/arm64/exit.hdass @@ -1,14 +1,11 @@ [entry: main] [enable: logical_registers] -// Exits with status 42 on AArch64 Linux. Logical registers map r1 -> x0 and -// r9 -> x8, so the exit code goes in x0 and the syscall number in x8; `syscall` -// lowers to `svc #0`. const SYS_EXIT = 93 proc main { - r1 = 42 // x0 = exit code - r9 = SYS_EXIT // x8 = syscall number + r1 = 42 + r9 = SYS_EXIT syscall } diff --git a/examples/arm64/sum.hdass b/examples/arm64/sum.hdass index 85820c9..6e93133 100644 --- a/examples/arm64/sum.hdass +++ b/examples/arm64/sum.hdass @@ -1,8 +1,6 @@ [entry: main] [enable: logical_registers] -// Sums 5 + 4 + 3 + 2 + 1 = 15 with a named countdown loop, returns it as the -// exit status. Shows three-operand arithmetic (r1 = r1 + r2 -> add x0, x0, x1). const SYS_EXIT = 93 proc main diff --git a/examples/branch.hdass b/examples/branch.hdass index 15f8d1b..9a31463 100644 --- a/examples/branch.hdass +++ b/examples/branch.hdass @@ -1,6 +1,5 @@ [entry: main] -// Returns the larger of two values (max(3, 8) = 8) as the exit status. const SYS_EXIT = 60 proc main diff --git a/examples/call.hdass b/examples/call.hdass index bf0c515..63eb957 100644 --- a/examples/call.hdass +++ b/examples/call.hdass @@ -1,6 +1,5 @@ [entry: main] -// Passes an argument through a proc parameter, which exits with that value. const SYS_EXIT = 60 proc exit_with(code: rdi) diff --git a/examples/circle.hdass b/examples/circle.hdass index 8a4edaa..71b204b 100644 --- a/examples/circle.hdass +++ b/examples/circle.hdass @@ -1,17 +1,15 @@ [entry: main] -// Floating-point math on the SSE registers. Computes the area of a circle -// (pi * r * r) with r = 3, then exits with the truncated result (28). const SYS_EXIT = 60 proc main { - xmm0 = 3.0 // radius - xmm0 *= xmm0 // r * r = 9.0 - xmm1 = 3.14159 // pi - xmm0 *= xmm1 // area ~= 28.27 + xmm0 = 3.0 // radius + xmm0 *= xmm0 // r * r = 9.0 + xmm1 = 3.14159 // pi + xmm0 *= xmm1 // area ~= 28.27 - rdi = xmm0 // truncate to int -> 28 + rdi = xmm0 // truncate rax = SYS_EXIT syscall } diff --git a/examples/constants.hdass b/examples/constants.hdass index 1bb85eb..227c83b 100644 --- a/examples/constants.hdass +++ b/examples/constants.hdass @@ -1,7 +1,5 @@ [entry: main] -// The exit status is folded from constant arithmetic, and SYS_EXIT is a hex -// literal. STATUS = 6 * 7 = 42. const A = 6 const B = 7 const STATUS = A * B diff --git a/examples/exit_code.hdass b/examples/exit_code.hdass index cabe634..0d6706b 100644 --- a/examples/exit_code.hdass +++ b/examples/exit_code.hdass @@ -1,6 +1,5 @@ [entry: main] -// Exits with status 42 and no output. const SYS_EXIT = 60 proc main diff --git a/examples/fibonacci.hdass b/examples/fibonacci.hdass index 47224fb..7f93c12 100644 --- a/examples/fibonacci.hdass +++ b/examples/fibonacci.hdass @@ -43,7 +43,7 @@ proc main { r12 = 0 r13 = 1 - r15 = 10 // syscall clobbers rcx/r11, so keep the counter in r15 + r15 = 10 while .sequence r15 > 0 { diff --git a/examples/greet.hdass b/examples/greet.hdass index da9f790..ebcf642 100644 --- a/examples/greet.hdass +++ b/examples/greet.hdass @@ -1,6 +1,5 @@ [entry: main] -// Writes a fixed string to stdout, then exits cleanly. const SYS_WRITE = 1 const SYS_EXIT = 60 diff --git a/examples/load.hdass b/examples/load.hdass index 8d29358..8f027ea 100644 --- a/examples/load.hdass +++ b/examples/load.hdass @@ -1,6 +1,5 @@ [entry: main] -// Stores 7 into a stack cell, loads it back through a pointer, and exits with it. const SYS_EXIT = 60 proc main @@ -8,9 +7,9 @@ proc main stack cell[8] rbx = 7 - rsi = cell // address of the cell - ^rsi = rbx // store - rdi = ^rsi // load it back + rsi = cell + ^rsi = rbx + rdi = ^rsi rax = SYS_EXIT syscall diff --git a/examples/logical.hdass b/examples/logical.hdass index 5fb27fc..1b7dd2a 100644 --- a/examples/logical.hdass +++ b/examples/logical.hdass @@ -1,19 +1,16 @@ [entry: main] [enable: logical_registers] -// With the logical_registers extension, r1..r14 name the general-purpose -// registers (r1 = rax, r2 = rbx, ...) instead of the architecture names. -// Computes (4 + 3) * 3 = 21 and returns it as the exit status. const SYS_EXIT = 60 proc main { - r1 = 4 // rax - r2 = 3 // rbx - r1 += r2 // 7 - r1 *= r2 // 21 + r1 = 4 + r2 = 3 + r1 += r2 + r1 *= r2 - r6 = r1 // rdi = 21 (the exit status) + r6 = r1 r1 = SYS_EXIT syscall } diff --git a/examples/loop_sum.hdass b/examples/loop_sum.hdass index 130f055..7d9acdd 100644 --- a/examples/loop_sum.hdass +++ b/examples/loop_sum.hdass @@ -1,12 +1,11 @@ [entry: main] -// Sums 5 + 4 + 3 + 2 + 1 = 15 with a countdown loop, returns it as the status. const SYS_EXIT = 60 proc main { - rbx = 0 // running total - rcx = 5 // counter + rbx = 0 + rcx = 5 while .countdown rcx > 0 { diff --git a/examples/mandelbrot.hdass b/examples/mandelbrot.hdass index 9054c15..9a1fd45 100644 --- a/examples/mandelbrot.hdass +++ b/examples/mandelbrot.hdass @@ -1,11 +1,6 @@ [entry: main] [enable: logical_registers] -// Renders the Mandelbrot set as ASCII art. The evolving z value is kept in a -// Complex struct on the stack (accessed through its field offsets), while the -// per-pixel constant c stays in xmm2/xmm3. Uses double-precision floats and the -// logical_registers extension (r1..r14 name the general-purpose registers). - const SYS_WRITE = 1 const SYS_EXIT = 60 const STDOUT = 1 @@ -27,7 +22,7 @@ proc main stack z[Complex.size] stack row[W + 1] - r11 = 0 // py + r11 = 0 // py row_loop: // ci = py * 0.08 - 1.2 @@ -37,7 +32,7 @@ row_loop: xmm7 = 1.2 xmm3 -= xmm7 - r12 = 0 // px + r12 = 0 // px col_loop: // cr = px * 0.04375 - 2.5 @@ -51,12 +46,12 @@ col_loop: xmm0 = 0.0 r5 = z r5 += Complex.re - ^r5 = xmm0 // z.re = 0 + ^r5 = xmm0 // z.re = 0 r5 = z r5 += Complex.im - ^r5 = xmm0 // z.im = 0 + ^r5 = xmm0 // z.im = 0 - r2 = 0 // iteration count + r2 = 0 // iteration count iter_loop: // load z into xmm0 (zr) and xmm1 (zi) @@ -79,12 +74,12 @@ iter_loop: if xmm6 > 4.0 goto plot - // new_zi = 2*zr*zi + ci (uses the old zr and zi) + // new_zi = 2*zr*zi + ci xmm6 = xmm0 xmm6 *= xmm1 xmm6 += xmm6 xmm6 += xmm3 - xmm7 = xmm6 // stash new_zi + xmm7 = xmm6 // new_zr = zr2 - zi2 + cr xmm4 -= xmm5 @@ -93,17 +88,17 @@ iter_loop: // store the new z back into the struct r5 = z r5 += Complex.re - ^r5 = xmm4 // z.re = new_zr + ^r5 = xmm4 // z.re = new_zr r5 = z r5 += Complex.im - ^r5 = xmm7 // z.im = new_zi + ^r5 = xmm7 // z.im = new_zi r2 += 1 if r2 < MAX_ITER goto iter_loop plot: - // index = iter * 9 / MAX_ITER, then char = palette[index] + // index = iter * 9 / MAX_ITER r1 = r2 r1 *= 9 r3 = MAX_ITER @@ -121,7 +116,6 @@ plot: if r12 < W goto col_loop - // terminate the row with a newline and write it r6 = row r6 += W r1 = 10 diff --git a/examples/modulo.hdass b/examples/modulo.hdass index 44c290d..a437080 100644 --- a/examples/modulo.hdass +++ b/examples/modulo.hdass @@ -1,6 +1,5 @@ [entry: main] -// Modulo: 2024 % 100 = 24, returned as the exit status. const SYS_EXIT = 60 proc main diff --git a/examples/mul_div.hdass b/examples/mul_div.hdass index 8c813b6..43a1357 100644 --- a/examples/mul_div.hdass +++ b/examples/mul_div.hdass @@ -1,17 +1,15 @@ [entry: main] -// (7 * 6) / 5 = 8 with a value-expression multiply and a non-rax division, -// returned as the exit status. const SYS_EXIT = 60 proc main { rbx = 7 rcx = 6 - rbx = rbx * rcx // 42 + rbx = rbx * rcx rcx = 5 - rbx /= rcx // 8 + rbx /= rcx rdi = rbx rax = SYS_EXIT diff --git a/examples/records.hdass b/examples/records.hdass index 8e87a4c..a6d5659 100644 --- a/examples/records.hdass +++ b/examples/records.hdass @@ -1,7 +1,5 @@ [entry: main] -// An enum of status codes and a struct describing a memory layout. Stores two -// fields into a stack instance, reads them back, and exits with their sum. enum Status { Ok, @@ -24,20 +22,20 @@ proc main rsi = pair rsi += Pair.a rbx = 40 - ^rsi = rbx // pair.a + ^rsi = rbx rsi = pair rsi += Pair.b - rcx = Status.Fail // 2 - ^rsi = rcx // pair.b + rcx = Status.Fail + ^rsi = rcx rsi = pair rsi += Pair.a - rdi = ^rsi // load a = 40 + rdi = ^rsi rsi = pair rsi += Pair.b - rax = ^rsi // load b = 2 - rdi += rax // 42 + rax = ^rsi + rdi += rax rax = SYS_EXIT syscall diff --git a/meson.build b/meson.build index 75226c3..54f43c2 100644 --- a/meson.build +++ b/meson.build @@ -50,21 +50,6 @@ executable( 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( diff --git a/scripts/docker_test.sh b/scripts/docker_test.sh deleted file mode 100755 index 5730e1d..0000000 --- a/scripts/docker_test.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -root="$(cd "$(dirname "$0")/.." && pwd)" -cd "$root" - -docker compose up -d >/dev/null - -docker compose exec -T -e "NO_COLOR=${NO_COLOR:-}" hdass bash /hdass/scripts/run_suite.sh </dev/null diff --git a/scripts/run_suite.sh b/scripts/run_suite.sh deleted file mode 100755 index 56854e5..0000000 --- a/scripts/run_suite.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd "$(cd "$(dirname "$0")/.." && pwd)" - -if [ -z "${NO_COLOR:-}" ]; then - bold=$'\033[1m'; red=$'\033[31m'; green=$'\033[32m'; reset=$'\033[0m' -else - bold=; red=; green=; reset= -fi - -printf '%s━━ building compiler ━━%s\n' "$bold" "$reset" -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 -fi -printf ' %s✔ compiler built%s\n\n' "$green" "$reset" - -printf '%s━━ unit tests ━━%s\n' "$bold" "$reset" -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" - ./build-linux/tests || true - printf ' %s✘ unit tests failed%s\n' "$red" "$reset" - exit 1 -fi - -ASSEMBLER=nasm bash ./scripts/test_examples.sh || exit 1 -printf '\n' -ASSEMBLER=fasm bash ./scripts/test_examples.sh diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh index ab4f184..f76b837 100755 --- a/scripts/test_examples.sh +++ b/scripts/test_examples.sh @@ -84,20 +84,20 @@ check() printf '%s━━ example programs (%s) ━━%s\n' "$bold" "$assembler" "$reset" -check hello_world "writes a greeting to stdout" examples/hello_world.hdass 0 "Hello, World!" -check greet "writes a fixed string" examples/greet.hdass 0 "hdass works!" -check exit_code "exits with a status code" examples/exit_code.hdass 42 "" -check arithmetic "integer compound-assignment math" examples/arithmetic.hdass 15 "" -check loop_sum "sums 1..5 with a countdown loop" examples/loop_sum.hdass 15 "" -check branch "selects the larger of two values" examples/branch.hdass 8 "" -check call "passes an argument through a proc" examples/call.hdass 21 "" -check logical "logical register names (r1..r14)" examples/logical.hdass 21 "" -check mul_div "multiply and non-rax division" examples/mul_div.hdass 8 "" -check modulo "the modulo operator (%)" examples/modulo.hdass 24 "" -check load "stores then loads through a pointer" examples/load.hdass 7 "" -check constants "hex literals and constant folding" examples/constants.hdass 42 "" -check records "enum values and struct field offsets" examples/records.hdass 42 "" -check circle "floating-point math on SSE registers" examples/circle.hdass 28 "" +check hello_world "writes a greeting to stdout" examples/hello_world.hdass 0 "Hello, World!" +check greet "writes a fixed string" examples/greet.hdass 0 "hdass works!" +check exit_code "exits with a status code" examples/exit_code.hdass 42 "" +check arithmetic "integer compound-assignment math" examples/arithmetic.hdass 15 "" +check loop_sum "sums 1..5 with a countdown loop" examples/loop_sum.hdass 15 "" +check branch "selects the larger of two values" examples/branch.hdass 8 "" +check call "passes an argument through a proc" examples/call.hdass 21 "" +check logical "logical register names (r1..r14)" examples/logical.hdass 21 "" +check mul_div "multiply and non-rax division" examples/mul_div.hdass 8 "" +check modulo "the modulo operator (%)" examples/modulo.hdass 24 "" +check load "stores then loads through a pointer" examples/load.hdass 7 "" +check constants "hex literals and constant folding" examples/constants.hdass 42 "" +check records "enum values and struct field offsets" examples/records.hdass 42 "" +check circle "floating-point math on SSE registers" examples/circle.hdass 28 "" check fibonacci "prints the first ten Fibonacci numbers" examples/fibonacci.hdass 0 "0 1 1 @@ -149,7 +149,6 @@ void free_program(struct Program* program) 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) @@ -177,9 +177,6 @@ struct StackStatement struct Expr* size; }; -// a bare instruction: a mnemonic and its operands emitted verbatim, the escape -// hatch for anything outside the assignment / control-flow model (int, hlt, -// lgdt, in/out, ...) struct InstructionStatement { struct Token mnemonic; @@ -188,8 +185,6 @@ struct InstructionStatement size_t operand_capacity; }; -// a branchless conditional move: `target = if_value if left cmp right else -// else_value`. Lowers to csel on AArch64 and cmov on x86. struct SelectStatement { struct Token target; diff --git a/src/codegen.c b/src/codegen.c index c0e67be..b111fb8 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -32,11 +32,11 @@ static const char* assign_mnemonic(enum TokenType op) { switch (op) { - case TOKEN_EQUAL: return "mov"; + 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; + default: return NULL; } } @@ -60,11 +60,9 @@ 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; } @@ -74,7 +72,6 @@ static size_t float_index(const struct FloatTable* floats, struct Token literal) if (floats->items[i].length == literal.length && memcmp(floats->items[i].start, literal.start, literal.length) == 0) return i; - return floats->count; } @@ -105,8 +102,6 @@ static struct Token text_token(const char* text) 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[] = { @@ -135,14 +130,12 @@ static const char* logical_register_base(struct Token token) 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; } @@ -320,8 +313,6 @@ static bool fold_member(struct Program* program, struct Expr* object, struct Tok 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) @@ -379,8 +370,6 @@ static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out 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); @@ -429,7 +418,6 @@ static bool emit_operand(struct Emitter* emitter, struct Expr* expr) } 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) { @@ -442,10 +430,8 @@ static bool emit_operand(struct Emitter* emitter, struct Expr* expr) 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)) + 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)) @@ -479,14 +465,9 @@ static bool is_gp_register(struct Token token) for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i += 1) if (token_matches(token, names[i])) return true; - return false; } -// idiv can divide by a general-purpose register directly, but not by rax or rdx -// (it overwrites both with the quotient and remainder) nor by an immediate (it -// has no immediate form). Those divisors are copied into r11 first — already a -// volatile register here, clobbered by syscall just like rcx. static bool divisor_uses_scratch(struct Emitter* emitter, const struct Expr* divisor) { if (divisor->kind == EXPR_PRIMARY) @@ -497,14 +478,9 @@ static bool divisor_uses_scratch(struct Emitter* emitter, const struct Expr* div return true; } - // enum/struct members and unary minus fold to immediates; a binary or deref - // divisor is left to the direct path (unsupported, as before) return divisor->kind == EXPR_MEMBER || divisor->kind == EXPR_UNARY; } -// idiv divides rdx:rax by its operand, leaving the quotient in rax and the -// remainder in rdx. The dividend arrives in dst; this loads it into rax, routes -// an awkward divisor through r11, and runs idiv. It clobbers rax, rdx and r11. static void emit_idiv(struct Emitter* emitter, const char* dst, struct Expr* divisor) { FILE* out = emitter->out; @@ -519,9 +495,8 @@ static void emit_idiv(struct Emitter* emitter, const char* dst, struct Expr* div return; } - // a divisor in rax must be saved before the dividend overwrites rax - bool divisor_in_rax = divisor->kind == EXPR_PRIMARY - && token_matches(resolve_register(emitter, divisor->primary.token), "rax"); + bool divisor_in_rax = divisor->kind == EXPR_PRIMARY && + token_matches(resolve_register(emitter, divisor->primary.token), "rax"); if (divisor_in_rax && strcmp(dst, "rax") != 0) { @@ -574,9 +549,6 @@ static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign) 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) @@ -610,9 +582,6 @@ static bool expr_supported(struct Emitter* emitter, struct Expr* expr) 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; @@ -719,9 +688,6 @@ static const char* store_size_keyword(enum StoreSize size) } } -// 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) @@ -795,14 +761,10 @@ static bool value_is_float(struct Emitter* emitter, const struct Expr* expr) 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) +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) @@ -815,7 +777,6 @@ static bool emit_float_assign(struct Emitter* emitter, struct AssignStatement* a return true; } - // float load: xmm = ^ptr -> movsd xmm, [ptr] if (value->kind == EXPR_DEREF) { if (!target_float || assign->op.type != TOKEN_EQUAL) @@ -896,7 +857,6 @@ static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) 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); @@ -907,11 +867,11 @@ static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) 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) + 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) @@ -993,15 +953,12 @@ static void emit_call(struct Emitter* emitter, struct CallStatement* call) } 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) @@ -1028,10 +985,6 @@ static void emit_float_operand(struct Emitter* emitter, const struct Expr* expr) fprintf(emitter->out, "%.*s", (int)token.length, token.start); } -// Emits the comparison for `left cmp right` and a jump to `target` taken when -// the condition is false, so the code that follows runs when it is true. Both -// if and while build on this. Returns false (after a TODO note) for a form that -// isn't supported yet. static bool emit_branch_test(struct Emitter* emitter, struct Expr* left, struct Token comparison, struct Expr* right, const char* target) { @@ -1127,9 +1080,6 @@ static void emit_while(struct Emitter* emitter, struct WhileStatement* loop) fprintf(emitter->out, "%s:\n", end); } -// renders a raw instruction's operand: registers, immediates, constants and -// members reuse emit_operand; memory (^x -> [x], with an optional size) and -// address math are handled here so operands like `^byte si` and `gdt + 2` work static void emit_instruction_operand(struct Emitter* emitter, struct Expr* operand) { switch (operand->kind) @@ -1162,7 +1112,6 @@ static void emit_instruction(struct Emitter* emitter, struct InstructionStatemen fprintf(emitter->out, "\n"); } -// the x86 condition-move suffix for a comparison, true or inverted static const char* cmov_cc(enum TokenType comparison, bool when_true) { switch (comparison) @@ -1177,23 +1126,21 @@ static const char* cmov_cc(enum TokenType comparison, bool when_true) } } -// target = if_value if L cmp R else else_value, branchlessly via cmov. The -// destination defaults to one source and conditionally takes the other, choosing -// which to default so the target can alias either source safely. static void emit_select(struct Emitter* emitter, struct SelectStatement* select) { FILE* out = emitter->out; - if (select->if_value->kind != EXPR_PRIMARY || select->else_value->kind != EXPR_PRIMARY - || cmov_cc(select->comparison.type, true) == NULL) + if (select->if_value->kind != EXPR_PRIMARY || + select->else_value->kind != EXPR_PRIMARY || + cmov_cc(select->comparison.type, true) == NULL) { fprintf(out, "\t; TODO: unsupported select\n"); return; } struct Token dst = resolve_register(emitter, select->target); - struct Token a = resolve_register(emitter, select->if_value->primary.token); - struct Token b = resolve_register(emitter, select->else_value->primary.token); - bool dst_is_a = tokens_equal(dst, a); + struct Token a = resolve_register(emitter, select->if_value->primary.token); + struct Token b = resolve_register(emitter, select->else_value->primary.token); + bool dst_is_a = tokens_equal(dst, a); if (!dst_is_a && !tokens_equal(dst, b)) fprintf(out, "\tmov %.*s, %.*s\n", (int)dst.length, dst.start, (int)b.length, b.start); @@ -1204,11 +1151,9 @@ static void emit_select(struct Emitter* emitter, struct SelectStatement* select) emit_operand(emitter, select->right); fprintf(out, "\n"); - // if the destination already holds if_value, keep it when the condition is - // false and move else_value in otherwise; otherwise the normal true form struct Token source = dst_is_a ? b : a; fprintf(out, "\tcmov%s %.*s, %.*s\n", cmov_cc(select->comparison.type, !dst_is_a), - (int)dst.length, dst.start, (int)source.length, source.start); + (int)dst.length, dst.start, (int)source.length, source.start); } static void emit_statement(struct Emitter* emitter, struct Statement* statement) @@ -1267,7 +1212,6 @@ static uint64_t proc_stack_size(struct Program* program, struct ProcDecl* proc) if (total % 16 != 0) total += 16 - (total % 16); - return total; } @@ -1346,17 +1290,16 @@ static struct FloatTable collect_floats(struct Program* program) 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_proc_x86(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, bool is_entry, FILE* out) { struct Emitter emitter; - emitter.program = program; - emitter.proc = proc; - emitter.floats = floats; - emitter.out = out; + emitter.program = program; + emitter.proc = proc; + emitter.floats = floats; + emitter.out = out; emitter.label_id = 0; fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start); @@ -1380,19 +1323,12 @@ static void emit_proc_x86(struct Program* program, struct FloatTable* floats, st } } -// --------------------------------------------------------------------------- -// AArch64 target -// -// A separate instruction selector: the register model (logical rN -> xN-1, or -// native x0.., w0.., sp, lr), 3-operand arithmetic, ldr/str memory, cmp + b.cond -// control flow and svc #0 syscalls are all its own. Shares only the arch-neutral -// helpers above (fold_const, resolve_token, the AST). -// --------------------------------------------------------------------------- - static bool is_a64_register(struct Token token) { - // logical rN (mapped to xN-1) - if (token.length >= 2 && token.start[0] == 'r' && token.start[1] >= '0' && token.start[1] <= '9') + if (token.length >= 2 && + token.start[0] == 'r' && + token.start[1] >= '0' && + token.start[1] <= '9') { for (size_t i = 1; i < token.length; i += 1) if (token.start[i] < '0' || token.start[i] > '9') @@ -1400,14 +1336,18 @@ static bool is_a64_register(struct Token token) return true; } - // native names x0..x30 / w0..w30 - if ((token.start[0] == 'x' || token.start[0] == 'w') && token.length >= 2 - && token.start[1] >= '0' && token.start[1] <= '9') + if ((token.start[0] == 'x' || + token.start[0] == 'w') && + token.length >= 2 && + token.start[1] >= '0' && + token.start[1] <= '9') return true; - return token_matches(token, "sp") || token_matches(token, "lr") - || token_matches(token, "fp") || token_matches(token, "xzr") - || token_matches(token, "wzr"); + return token_matches(token, "sp") || + token_matches(token, "lr") || + token_matches(token, "fp") || + token_matches(token, "xzr") || + token_matches(token, "wzr"); } static void emit_a64_reg(struct Emitter* emitter, struct Token token) @@ -1424,8 +1364,6 @@ static void emit_a64_reg(struct Emitter* emitter, struct Token token) fprintf(emitter->out, "%.*s", (int)r.length, r.start); } -// an operand in register or immediate position: a register maps through, and -// anything that folds to a constant becomes an #immediate static void emit_a64_operand(struct Emitter* emitter, struct Expr* expr) { if (expr->kind == EXPR_PRIMARY && is_a64_register(resolve_token(emitter, expr->primary.token))) @@ -1459,7 +1397,6 @@ static const char* a64_binop(enum TokenType op) } } -// branch taken when the comparison is false (to skip the guarded body) static const char* a64_jump_if_false(enum TokenType comparison) { switch (comparison) @@ -1475,7 +1412,6 @@ static const char* a64_jump_if_false(enum TokenType comparison) } static void emit_a64_statement(struct Emitter* emitter, struct Statement* statement); - static void emit_a64_block(struct Emitter* emitter, struct Statement* body, size_t count) { for (size_t i = 0; i < count; i += 1) @@ -1486,7 +1422,6 @@ static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* ass { FILE* out = emitter->out; - // store through a pointer: ^[size] p = value if (assign->target_deref) { const char* store = assign->store_size == STORE_SIZE_BYTE ? "strb" @@ -1501,7 +1436,6 @@ static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* ass struct Expr* value = assign->value; - // load through a pointer: dst = ^[size] p if (assign->op.type == TOKEN_EQUAL && value->kind == EXPR_DEREF && value->deref.address->kind == EXPR_PRIMARY) { @@ -1515,7 +1449,6 @@ static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* ass return; } - // three-operand arithmetic: dst = a op b if (assign->op.type == TOKEN_EQUAL && value->kind == EXPR_BINARY) { const char* mnemonic = a64_binop(value->binary.op.type); @@ -1534,7 +1467,6 @@ static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* ass return; } - // compound assignment: dst op= value -> op dst, dst, value if (assign->op.type != TOKEN_EQUAL) { const char* mnemonic = a64_binop(assign->op.type); @@ -1553,7 +1485,6 @@ static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* ass return; } - // plain move: dst = <register | immediate | symbol/address> if (value->kind == EXPR_PRIMARY && is_a64_register(resolve_token(emitter, value->primary.token))) { fprintf(out, "\tmov "); @@ -1573,7 +1504,6 @@ static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* ass return; } - // a data label or other symbol: load its address/value through the pool if (value->kind == EXPR_PRIMARY) { fprintf(out, "\tldr "); @@ -1693,7 +1623,6 @@ static void emit_a64_instruction(struct Emitter* emitter, struct InstructionStat fprintf(emitter->out, "\n"); } -// the AArch64 condition code for a true comparison (used by csel) static const char* a64_cond(enum TokenType comparison) { switch (comparison) @@ -1708,8 +1637,6 @@ static const char* a64_cond(enum TokenType comparison) } } -// target = if_value if L cmp R else else_value -> cmp; csel (one instruction, -// no aliasing hazard) static void emit_a64_select(struct Emitter* emitter, struct SelectStatement* select) { FILE* out = emitter->out; @@ -1790,23 +1717,16 @@ static void emit_proc_aarch64(struct Program* program, struct FloatTable* floats fprintf(out, "\tret\n"); } -// Instruction selection lives behind the Arch seam: turning a procedure's -// statements into a target's instructions (register model, mnemonics, stack -// frames) is all an architecture decides. The Backend below is the orthogonal -// axis — the assembler *syntax* (framing, data, labels) for a given arch. struct Arch { - void (*emit_proc)(struct Program* program, struct FloatTable* floats, - struct ProcDecl* proc, bool is_entry, FILE* out); + void (*emit_proc)(struct Program* program, + struct FloatTable* floats, + struct ProcDecl* proc, + bool is_entry, FILE* out); }; -static const struct Arch x86_arch = { - emit_proc_x86, -}; - -static const struct Arch aarch64_arch = { - emit_proc_aarch64, -}; +static const struct Arch x86_arch = { emit_proc_x86, }; +static const struct Arch aarch64_arch = { emit_proc_aarch64, }; struct Backend { @@ -1842,8 +1762,6 @@ static void nasm_data_section(FILE* out) static void nasm_string_data(struct DataDecl decl, FILE* out) { - // the value lexeme keeps its 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); @@ -1865,7 +1783,6 @@ static void nasm_global(struct Token name, FILE* out) fprintf(out, "global %.*s\n", (int)name.length, name.start); } -// pad to 510 bytes and append the 0x55AA boot signature (little-endian dw) static void nasm_boot_signature(FILE* out) { fprintf(out, "times 510-($-$$) db 0\n"); @@ -1894,9 +1811,7 @@ static void fasm_prologue(const struct Program* program, FILE* out) fprintf(out, "use%u\n", config.bits); } else - { fprintf(out, "format ELF%s\n", config.bits == 64 ? "64" : ""); - } } static void fasm_constant(struct ConstDecl decl, FILE* out) @@ -1911,8 +1826,6 @@ static void fasm_data_section(FILE* out) fprintf(out, "section '.data' writeable\n"); } -// fasm string literals are taken verbatim, so the escapes NASM would interpret -// are expanded here into the byte values fasm expects (db "run", 10, "run"). static void fasm_string_data(struct DataDecl decl, FILE* out) { fprintf(out, "%.*s db ", (int)decl.name.length, decl.name.start); @@ -2002,8 +1915,6 @@ static const struct Backend fasm_backend = { fasm_boot_signature, }; -// GNU as (the assembler for the ARM targets): different directives from the -// Intel-syntax assemblers, but the same framing shape. static void gas_prologue(const struct Program* program, FILE* out) { (void)program; @@ -2024,8 +1935,6 @@ static void gas_data_section(FILE* out) static void gas_string_data(struct DataDecl decl, FILE* out) { - // GNU as .ascii interprets the same C escapes NASM's backtick strings do, - // so the inner content passes through unchanged (no trailing NUL, matching) fprintf(out, "%.*s: .ascii \"%.*s\"\n", (int)decl.name.length, decl.name.start, (int)(decl.value.length - 2), decl.value.start + 1); @@ -2051,7 +1960,6 @@ static void gas_global(struct Token name, FILE* out) static void gas_boot_signature(FILE* out) { - // boot sectors are an x86/BIOS concept; not meaningful for the ARM targets (void)out; } @@ -2066,22 +1974,18 @@ static const struct Backend gas_backend = { gas_boot_signature, }; -// the entry procedure drops its trailing `ret`. It is the [entry: NAME] proc if -// given; otherwise a flat binary starts at its first proc. static bool proc_is_entry(struct Program* program, size_t index) { struct Config config = program->config; struct ProcDecl* proc = &program->procs[index]; if (config.has_entry) - return proc->name.length == config.entry.length - && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; - + return proc->name.length == config.entry.length && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; return config.format == OUTPUT_BIN && index == 0; } static void emit_data_block(struct Program* program, struct FloatTable* floats, - const struct Backend* backend, FILE* out) + const struct Backend* backend, FILE* out) { for (size_t i = 0; i < program->data_count; i += 1) backend->string_data(program->data_decls[i], out); @@ -2105,7 +2009,6 @@ static void generate(struct Program* program, FILE* out, const struct Arch* arch if (program->config.format == OUTPUT_BIN) { - // a flat binary executes from its origin, so code comes first, then data for (size_t i = 0; i < program->proc_count; i += 1) { if (i > 0) @@ -30,10 +30,10 @@ void report_error(struct Source source, struct Token token, const char* message) if (span == 0) span = 1; - const char* bold = colored() ? "\033[1m" : ""; + const char* bold = colored() ? "\033[1m" : ""; const char* red = colored() ? "\033[31m" : ""; const char* blue = colored() ? "\033[34m" : ""; - const char* reset = colored() ? "\033[0m" : ""; + const char* reset = colored() ? "\033[0m" : ""; int gutter = snprintf(NULL, 0, "%u", token.line); @@ -55,9 +55,9 @@ void report_error(struct Source source, struct Token token, const char* message) void report_error_message(const char* message) { - const char* bold = colored() ? "\033[1m" : ""; + const char* bold = colored() ? "\033[1m" : ""; const char* red = colored() ? "\033[31m" : ""; - const char* reset = colored() ? "\033[0m" : ""; + 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/parser.c b/src/parser.c index b0e049c..1815562 100644 --- a/src/parser.c +++ b/src/parser.c @@ -279,8 +279,6 @@ static struct Expr* parse_postfix(struct Parser* parser) 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 '.'"); @@ -180,14 +180,14 @@ static void ref_error(struct RefCheck* check, struct Token token, const char* fo 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", + "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", @@ -411,8 +411,6 @@ static void check_target(struct RefCheck* check, struct Token target) 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) @@ -427,9 +425,9 @@ static void check_stack_size(struct RefCheck* check, struct Expr* expr) 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)) + 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"); @@ -500,9 +498,7 @@ static void check_statement(struct RefCheck* check, struct Statement* statement) case STATEMENT_SYSCALL: break; case STATEMENT_INSTRUCTION: - // a raw instruction is an escape hatch; its operands may name asm - // symbols or registers hdass does not model, so leave them to the - // assembler rather than flagging them as undefined + // escape hatch break; case STATEMENT_SELECT: check_target(check, statement->select.target); @@ -533,7 +529,6 @@ static bool check_references(struct Source source, struct Program* program) 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)) @@ -542,6 +537,5 @@ bool analyze_program(struct Source source, struct Program* program) ok = false; if (!check_references(source, program)) ok = false; - return ok; } diff --git a/tests/codegen_test.c b/tests/codegen_test.c deleted file mode 100644 index 0a4a841..0000000 --- a/tests/codegen_test.c +++ /dev/null @@ -1,689 +0,0 @@ -#include <stdio.h> -#include <string.h> - -#include "tests.h" -#include "parser.h" -#include "codegen.h" - -static void generate_to_buffer(struct Program* program, char* buffer, size_t size) -{ - FILE* out = tmpfile(); - if (out == NULL) - { - buffer[0] = '\0'; - return; - } - - generate_nasm(program, out); - fflush(out); - rewind(out); - - size_t read = fread(buffer, 1, size - 1, out); - buffer[read] = '\0'; - fclose(out); -} - -static void generate_fasm_to_buffer(struct Program* program, char* buffer, size_t size) -{ - FILE* out = tmpfile(); - if (out == NULL) - { - buffer[0] = '\0'; - return; - } - - generate_fasm(program, out); - fflush(out); - rewind(out); - - size_t read = fread(buffer, 1, size - 1, out); - buffer[read] = '\0'; - fclose(out); -} - -static void generate_aarch64_to_buffer(struct Program* program, char* buffer, size_t size) -{ - FILE* out = tmpfile(); - if (out == NULL) - { - buffer[0] = '\0'; - return; - } - - generate_aarch64(program, out); - fflush(out); - rewind(out); - - size_t read = fread(buffer, 1, size - 1, out); - buffer[read] = '\0'; - fclose(out); -} - -static void test_generate_consts_and_data(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("const N = 5\ndata msg = \"hi\"\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "%define N (5)") != NULL); - check(context, strstr(buffer, "section .data") != NULL); - check(context, strstr(buffer, "msg: db `hi`") != NULL); - check(context, strstr(buffer, ".len equ $ - msg") != NULL); - check(context, strstr(buffer, "section .text") != NULL); - - free_program(&program); -} - -static void test_generate_fasm(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "[entry: main]\nconst N = 5\ndata msg = \"hi\\n\"\nproc main\n{\nrax = N\nsyscall\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_fasm_to_buffer(&program, buffer, sizeof(buffer)); - - // fasm framing differs from nasm - check(context, strstr(buffer, "format ELF64") != NULL); - check(context, strstr(buffer, "N = 5") != NULL); - check(context, strstr(buffer, "section '.data' writeable") != NULL); - check(context, strstr(buffer, "msg db \"hi\", 10") != NULL); // escape expanded to a byte - check(context, strstr(buffer, ".len = $ - msg") != NULL); - check(context, strstr(buffer, "section '.text' executable") != NULL); - check(context, strstr(buffer, "public main") != NULL); - // instruction bodies are identical to nasm - check(context, strstr(buffer, "mov rax, N") != NULL); - // nasm-only forms must be absent - check(context, strstr(buffer, "%define") == NULL); - check(context, strstr(buffer, "bits 64") == NULL); - - free_program(&program); -} - -static void test_generate_select(struct TestContext* context) -{ - // destination distinct from both sources: default-move else, then cmov - struct Lexer lexer = create_lexer("proc main\n{\nrax = rbx if rcx < rdx else rsi\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, rsi") != NULL); - check(context, strstr(buffer, "cmp rcx, rdx") != NULL); - check(context, strstr(buffer, "cmovl rax, rbx") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_select_aarch64(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "[enable: logical_registers]\nproc main\n{\nr1 = r2 if r3 < r4 else r5\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_aarch64_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "cmp x2, x3") != NULL); - check(context, strstr(buffer, "csel x0, x1, x4, lt") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_aarch64(struct TestContext* context) -{ - // logical registers map r1 -> x0, r2 -> x1; three-operand arithmetic and svc - struct Lexer lexer = create_lexer( - "[enable: logical_registers]\nproc main\n{\nr1 = 5\nr1 = r1 + r2\n" - "while r2 > 0\nr2 = r2 - 1\nsyscall\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_aarch64_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, ".text") != NULL); - check(context, strstr(buffer, "mov x0, #5") != NULL); // r1 = 5 - check(context, strstr(buffer, "add x0, x0, x1") != NULL); // r1 = r1 + r2 - check(context, strstr(buffer, "cmp x1, #0") != NULL); // while r2 > 0 - check(context, strstr(buffer, "sub x1, x1, #1") != NULL); // r2 = r2 - 1 - check(context, strstr(buffer, "svc #0") != NULL); // syscall - // no x86 leaked in - check(context, strstr(buffer, "rax") == NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_text(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nrax = SYS_WRITE\nrsi = message\nrdx = message.len\n^rdi = rax\nloop:\nsyscall\ngoto loop\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "main:") != NULL); - check(context, strstr(buffer, "mov rax, SYS_WRITE") != NULL); - check(context, strstr(buffer, "mov rsi, message") != NULL); - check(context, strstr(buffer, "mov rdx, message.len") != NULL); - check(context, strstr(buffer, "mov [rdi], rax") != NULL); - check(context, strstr(buffer, "loop:") != NULL); - check(context, strstr(buffer, "syscall") != NULL); - check(context, strstr(buffer, "jmp loop") != NULL); - - free_program(&program); -} - -static void test_generate_if(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nif rcx != 0\ngoto loop\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "cmp rcx, 0") != NULL); - check(context, strstr(buffer, "je .if_end_0") != NULL); - check(context, strstr(buffer, "jmp loop") != NULL); - check(context, strstr(buffer, ".if_end_0:") != NULL); - - free_program(&program); -} - -static void test_generate_if_else(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nif rax > 3\n{\nrdi = 1\n}\nelse\n{\nrdi = 0\n}\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "jle .if_else_0") != NULL); - check(context, strstr(buffer, "mov rdi, 1") != NULL); - check(context, strstr(buffer, "jmp .if_end_0") != NULL); - check(context, strstr(buffer, ".if_else_0:") != NULL); - check(context, strstr(buffer, "mov rdi, 0") != NULL); - check(context, strstr(buffer, ".if_end_0:") != NULL); - - free_program(&program); -} - -static void test_generate_while(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nwhile rcx > 0\n{\nrbx += rcx\nrcx -= 1\n}\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, ".while_0:") != NULL); - check(context, strstr(buffer, "cmp rcx, 0") != NULL); - check(context, strstr(buffer, "jle .while_end_0") != NULL); - check(context, strstr(buffer, "add rbx, rcx") != NULL); - check(context, strstr(buffer, "jmp .while_0") != NULL); - check(context, strstr(buffer, ".while_end_0:") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_while_named(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nwhile .drain rcx > 0\n{\nrcx -= 1\n}\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, ".drain:") != NULL); - check(context, strstr(buffer, "jle .drain_end") != NULL); - check(context, strstr(buffer, "jmp .drain") != NULL); - check(context, strstr(buffer, ".drain_end:") != NULL); - check(context, strstr(buffer, ".while_0") == NULL); - - free_program(&program); -} - -static void test_generate_negative(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "const N = -8\nproc main\n{\nrax = -5\nrbx = rax + -3\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "%define N (-8)") != NULL); - check(context, strstr(buffer, "mov rax, -5") != NULL); - check(context, strstr(buffer, "add rbx, -3") != NULL); - - free_program(&program); -} - -static void test_generate_call(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc print_number(value: rdi)\n{\nsyscall\n}\nproc main\n{\nprint_number(r12)\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rdi, r12") != NULL); - check(context, strstr(buffer, "call print_number") != NULL); - check(context, strstr(buffer, "\tret\n") != NULL); - - free_program(&program); -} - -static void test_generate_param_substitution(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc print_number(value: rdi)\n{\nrax = value\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, rdi") != NULL); - check(context, strstr(buffer, "value") == NULL); - - free_program(&program); -} - -static void test_generate_divide(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nrax /= rbx\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "cqo") != NULL); - check(context, strstr(buffer, "idiv rbx") != NULL); - - free_program(&program); -} - -static void test_generate_multiply(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nrax = rbx * rcx\nrbx = rcx * 3 + rdx\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, rbx") != NULL); - check(context, strstr(buffer, "imul rax, rcx") != NULL); - check(context, strstr(buffer, "imul rbx, 3") != NULL); - check(context, strstr(buffer, "add rbx, rdx") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_modulo(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nrbx %= rcx\nrax = rsi % rdi\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - // modulo takes idiv's remainder from rdx - check(context, strstr(buffer, "mov rax, rbx") != NULL); - check(context, strstr(buffer, "idiv rcx") != NULL); - check(context, strstr(buffer, "mov rbx, rdx") != NULL); - check(context, strstr(buffer, "mov rax, rdx") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_divide_nonrax(struct TestContext* context) -{ - // division into a register other than rax routes through rax:rdx - struct Lexer lexer = create_lexer("proc main\n{\nrbx /= rcx\nrsi = rdi / rcx\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, rbx") != NULL); - check(context, strstr(buffer, "idiv rcx") != NULL); - check(context, strstr(buffer, "mov rbx, rax") != NULL); - check(context, strstr(buffer, "mov rsi, rax") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_divide_scratch(struct TestContext* context) -{ - // an immediate or rax/rdx divisor is routed through the r11 scratch - struct Lexer lexer = create_lexer( - "proc main\n{\nrbx /= 10\nrcx /= rax\nrsi = rdi / rdx\nr11 /= rax\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov r11, 10") != NULL); // immediate - check(context, strstr(buffer, "mov r11, rax") != NULL); // rax divisor saved - check(context, strstr(buffer, "mov r11, rdx") != NULL); // rdx divisor saved - check(context, strstr(buffer, "xchg rax, r11") != NULL); // dst is the scratch - check(context, strstr(buffer, "idiv r11") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_stack_frame(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc work\n{\nstack buffer[32]\nsyscall\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "push rbp") != NULL); - check(context, strstr(buffer, "mov rbp, rsp") != NULL); - check(context, strstr(buffer, "sub rsp, 32") != NULL); - check(context, strstr(buffer, "leave") != NULL); - - free_program(&program); -} - -static void test_generate_address_expr(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc work\n{\nstack buffer[32]\nrsi = buffer + 31\nrdx = buffer + 32 - rsi\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "lea rsi, [rbp - 32]") != NULL); - check(context, strstr(buffer, "add rsi, 31") != NULL); - check(context, strstr(buffer, "lea rdx, [rbp - 32]") != NULL); - check(context, strstr(buffer, "add rdx, 32") != NULL); - check(context, strstr(buffer, "sub rdx, rsi") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_sized_store(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\n^byte rsi = rdx\n^dword rsi = rax\n^byte rsi = 5\n^rsi = rbx\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov byte [rsi], dl") != NULL); - check(context, strstr(buffer, "mov dword [rsi], eax") != NULL); - check(context, strstr(buffer, "mov byte [rsi], 5") != NULL); - check(context, strstr(buffer, "mov [rsi], rbx") != NULL); - - free_program(&program); -} - -static void test_generate_entry_and_bits(struct TestContext* context) -{ - struct Lexer with_entry = create_lexer("[bits: 64]\n[entry: main]\nproc main\n{\nsyscall\n}\nproc helper\n{\nsyscall\n}\n"); - struct Program program; - check(context, parse_program(&with_entry, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "bits 64") != NULL); - check(context, strstr(buffer, "global main") != NULL); - check(context, strstr(buffer, "main:") != NULL); - check(context, strstr(buffer, "_start") == NULL); - // the entry proc does not return; the non-entry helper does - check(context, strstr(buffer, "helper:\n\tsyscall\n\tret") != NULL); - - free_program(&program); -} - -static void test_generate_no_entry(struct TestContext* context) -{ - struct Lexer no_entry = create_lexer("proc main\n{\nsyscall\n}\n"); - struct Program program; - check(context, parse_program(&no_entry, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "global") == NULL); - check(context, strstr(buffer, "main:") != NULL); - - free_program(&program); -} - -static void test_generate_logical_registers(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "[enable: logical_registers]\nproc main\n{\nr1 = 5\nr4 = r10\nr6 = r1.8\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, 5") != NULL); // r1 -> rax - check(context, strstr(buffer, "mov rdx, r11") != NULL); // r4 -> rdx, r10 -> r11 - check(context, strstr(buffer, "mov rdi, al") != NULL); // r6 -> rdi, r1.8 -> al - - free_program(&program); -} - -static void test_generate_logical_disabled(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nr1 = 5\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - // without the extension, r1 is passed through untouched - check(context, strstr(buffer, "mov r1, 5") != NULL); - - free_program(&program); -} - -static void test_generate_load(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nrax = ^rsi\nrbx = ^byte rsi\nrcx = ^dword rsi\nrdx = ^rsi + 4\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, [rsi]") != NULL); - check(context, strstr(buffer, "movzx rbx, byte [rsi]") != NULL); - check(context, strstr(buffer, "mov ecx, [rsi]") != NULL); - check(context, strstr(buffer, "mov rdx, [rsi]") != NULL); - check(context, strstr(buffer, "add rdx, 4") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_signed_load(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nrax = ^signed byte rsi\nrbx = ^signed word rsi\nrcx = ^signed dword rsi\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "movsx rax, byte [rsi]") != NULL); - check(context, strstr(buffer, "movsx rbx, word [rsi]") != NULL); - check(context, strstr(buffer, "movsxd rcx, dword [rsi]") != NULL); - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_enum_struct(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "enum Color\n{\nRed,\nGreen,\nBlue\n}\n" - "struct Point\n{\nx: qword\ny: qword\nflag: byte\n}\n" - "proc main\n{\nrax = Color.Blue\nrbx = Point.y\nrcx = Point.flag\nrdx = Point.size\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "mov rax, 2") != NULL); // Color.Blue -> 2 - check(context, strstr(buffer, "mov rbx, 8") != NULL); // Point.y -> 8 - check(context, strstr(buffer, "mov rcx, 16") != NULL); // Point.flag -> 16 - check(context, strstr(buffer, "mov rdx, 17") != NULL); // Point.size -> 17 - - free_program(&program); -} - -static void test_generate_floats(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nxmm0 = 3.5\nxmm0 *= xmm1\nrax = 4\nxmm2 = rax\nrbx = xmm0\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "__float0: dq 3.5") != NULL); - check(context, strstr(buffer, "movsd xmm0, [__float0]") != NULL); - check(context, strstr(buffer, "mulsd xmm0, xmm1") != NULL); // float arithmetic - check(context, strstr(buffer, "cvtsi2sd xmm2, rax") != NULL); // int -> float - check(context, strstr(buffer, "cvttsd2si rbx, xmm0") != NULL); // float -> int - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_float_compare(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nif xmm0 > 4.0\ngoto done\ndone:\nsyscall\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "ucomisd xmm0, [__float0]") != NULL); - check(context, strstr(buffer, "jbe .if_end") != NULL); // '>' skips when <= - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -static void test_generate_add_zero_peephole(struct TestContext* context) -{ - // `+= 0` / `-= 0` (e.g. a struct field at offset 0) is dropped; `*= 0` is not - struct Lexer lexer = create_lexer("proc main\n{\nrax += 0\nrbx -= 0\nrcx *= 0\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "add rax, 0") == NULL); - check(context, strstr(buffer, "sub rbx, 0") == NULL); - check(context, strstr(buffer, "imul rcx, 0") != NULL); - - free_program(&program); -} - -static void test_generate_float_memory(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\n^rsi = xmm0\nxmm1 = ^rsi\n}\n"); - struct Program program; - check(context, parse_program(&lexer, &program)); - - char buffer[1024]; - generate_to_buffer(&program, buffer, sizeof(buffer)); - - check(context, strstr(buffer, "movsd [rsi], xmm0") != NULL); // float store - check(context, strstr(buffer, "movsd xmm1, [rsi]") != NULL); // float load - check(context, strstr(buffer, "; TODO") == NULL); - - free_program(&program); -} - -void run_codegen_tests(struct TestContext* context) -{ - test_generate_floats(context); - test_generate_float_compare(context); - test_generate_float_memory(context); - test_generate_add_zero_peephole(context); - test_generate_enum_struct(context); - test_generate_load(context); - test_generate_signed_load(context); - test_generate_logical_registers(context); - test_generate_logical_disabled(context); - test_generate_entry_and_bits(context); - test_generate_no_entry(context); - test_generate_consts_and_data(context); - test_generate_fasm(context); - test_generate_aarch64(context); - test_generate_select(context); - test_generate_select_aarch64(context); - test_generate_text(context); - test_generate_if(context); - test_generate_if_else(context); - test_generate_while(context); - test_generate_while_named(context); - test_generate_negative(context); - test_generate_call(context); - test_generate_param_substitution(context); - test_generate_divide(context); - test_generate_multiply(context); - test_generate_modulo(context); - test_generate_divide_nonrax(context); - test_generate_divide_scratch(context); - test_generate_stack_frame(context); - test_generate_address_expr(context); - test_generate_sized_store(context); -} diff --git a/tests/lexer_test.c b/tests/lexer_test.c deleted file mode 100644 index 0810c3b..0000000 --- a/tests/lexer_test.c +++ /dev/null @@ -1,111 +0,0 @@ -#include <string.h> -#include <stdbool.h> - -#include "lexer.h" -#include "tests.h" - -static bool token_matches(struct Token token, enum TokenType type, const char* text) -{ - size_t length = strlen(text); - return token.type == type - && token.length == length - && memcmp(token.start, text, length) == 0; -} - -static void test_identifiers_and_integers(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("rax 42"); - check(context, token_matches(scan_token(&lexer), TOKEN_IDENTIFIER, "rax")); - check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "42")); - check(context, scan_token(&lexer).type == TOKEN_EOF); -} - -static void test_number_bases(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("42 0x1F 0b1010"); - check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "42")); - check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "0x1F")); - check(context, token_matches(scan_token(&lexer), TOKEN_INTEGER, "0b1010")); -} - -static void test_float_literals(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("3.14 1.0 42"); - check(context, token_matches(scan_token(&lexer), TOKEN_FLOAT, "3.14")); - check(context, token_matches(scan_token(&lexer), TOKEN_FLOAT, "1.0")); - check(context, scan_token(&lexer).type == TOKEN_INTEGER); -} - -static void test_operators(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("= == += != /"); - check(context, scan_token(&lexer).type == TOKEN_EQUAL); - check(context, scan_token(&lexer).type == TOKEN_EQUAL_EQUAL); - check(context, scan_token(&lexer).type == TOKEN_PLUS_EQUAL); - check(context, scan_token(&lexer).type == TOKEN_BANG_EQUAL); - check(context, scan_token(&lexer).type == TOKEN_SLASH); -} - -static void test_literals(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("\"hi\\n\" '0'"); - check(context, token_matches(scan_token(&lexer), TOKEN_STRING, "\"hi\\n\"")); - check(context, token_matches(scan_token(&lexer), TOKEN_CHAR, "'0'")); -} - -static void test_keywords(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("const proc mainly"); - check(context, scan_token(&lexer).type == TOKEN_CONST); - check(context, scan_token(&lexer).type == TOKEN_PROC); - check(context, scan_token(&lexer).type == TOKEN_IDENTIFIER); -} - -static void test_size_keywords(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("byte word dword qword"); - check(context, scan_token(&lexer).type == TOKEN_BYTE); - check(context, scan_token(&lexer).type == TOKEN_WORD); - check(context, scan_token(&lexer).type == TOKEN_DWORD); - check(context, scan_token(&lexer).type == TOKEN_QWORD); -} - -static void test_enum_struct_keywords(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("enum struct"); - check(context, scan_token(&lexer).type == TOKEN_ENUM); - check(context, scan_token(&lexer).type == TOKEN_STRUCT); -} - -static void test_line_counting(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("a\nb\nc"); - check(context, scan_token(&lexer).line == 1); - check(context, scan_token(&lexer).line == 2); - check(context, scan_token(&lexer).line == 3); -} - -static void test_comments(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("rax // line comment\n/* block\ncomment */ rbx"); - check(context, token_matches(scan_token(&lexer), TOKEN_IDENTIFIER, "rax")); - - struct Token second = scan_token(&lexer); - check(context, token_matches(second, TOKEN_IDENTIFIER, "rbx")); - check(context, second.line == 3); - check(context, scan_token(&lexer).type == TOKEN_EOF); -} - -void run_lexer_tests(struct TestContext* context) -{ - test_identifiers_and_integers(context); - test_number_bases(context); - test_float_literals(context); - test_operators(context); - test_literals(context); - test_keywords(context); - test_size_keywords(context); - test_enum_struct_keywords(context); - test_line_counting(context); - test_comments(context); -} diff --git a/tests/main.c b/tests/main.c deleted file mode 100644 index dfd3ed9..0000000 --- a/tests/main.c +++ /dev/null @@ -1,16 +0,0 @@ -#include <stdio.h> - -#include "tests.h" - -int main(void) -{ - struct TestContext context = { 0, 0 }; - - run_lexer_tests(&context); - run_parser_tests(&context); - run_sema_tests(&context); - run_codegen_tests(&context); - - printf("\n%d checks, %d failure(s)\n", context.checks, context.failures); - return context.failures == 0 ? 0 : 1; -} diff --git a/tests/parser_test.c b/tests/parser_test.c deleted file mode 100644 index eecc9b9..0000000 --- a/tests/parser_test.c +++ /dev/null @@ -1,467 +0,0 @@ -#include <string.h> -#include <stdbool.h> - -#include "tests.h" -#include "parser.h" - -static bool 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 primary_is(const struct Expr* expr, const char* text) -{ - return expr->kind == EXPR_PRIMARY && text_is(expr->primary.token, text); -} - -static void test_parse_consts(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("const A = 1\nconst B = 60\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.const_count == 2); - check(context, text_is(program.consts[0].name, "A")); - check(context, primary_is(program.consts[0].value, "1")); - check(context, text_is(program.consts[1].name, "B")); - check(context, primary_is(program.consts[1].value, "60")); - - free_program(&program); -} - -static void test_parse_const_expr(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("const A = 1\nconst B = A + 2 * 3\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - const struct Expr* value = program.consts[1].value; - check(context, value->kind == EXPR_BINARY); - check(context, text_is(value->binary.op, "+")); - check(context, primary_is(value->binary.left, "A")); - check(context, value->binary.right->kind == EXPR_BINARY); - - free_program(&program); -} - -static void test_parse_unary(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("const N = -8\nproc main\n{\nrax = -5\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - const struct Expr* konst = program.consts[0].value; - check(context, konst->kind == EXPR_UNARY); - check(context, text_is(konst->unary.op, "-")); - check(context, primary_is(konst->unary.operand, "8")); - - const struct Expr* value = program.procs[0].body[0].assign.value; - check(context, value->kind == EXPR_UNARY); - check(context, primary_is(value->unary.operand, "5")); - - free_program(&program); -} - -static void test_parse_data(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("data msg = \"hi\"\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.data_count == 1); - check(context, text_is(program.data_decls[0].name, "msg")); - check(context, text_is(program.data_decls[0].value, "\"hi\"")); - - free_program(&program); -} - -static void test_parse_proc_params(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc f(a: rdi, b: rsi)\n{\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.proc_count == 1); - check(context, text_is(program.procs[0].name, "f")); - check(context, program.procs[0].param_count == 2); - check(context, text_is(program.procs[0].params[0].name, "a")); - check(context, text_is(program.procs[0].params[0].reg, "rdi")); - check(context, text_is(program.procs[0].params[1].name, "b")); - check(context, text_is(program.procs[0].params[1].reg, "rsi")); - - free_program(&program); -} - -static void test_parse_proc_body(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nrax = 1\nrcx -= 1\n^rsi = rdx\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.proc_count == 1); - check(context, program.procs[0].body_count == 3); - - struct Statement first = program.procs[0].body[0]; - check(context, first.kind == STATEMENT_ASSIGN); - check(context, !first.assign.target_deref); - check(context, text_is(first.assign.target, "rax")); - check(context, text_is(first.assign.op, "=")); - check(context, primary_is(first.assign.value, "1")); - - struct Statement second = program.procs[0].body[1]; - check(context, text_is(second.assign.target, "rcx")); - check(context, text_is(second.assign.op, "-=")); - - struct Statement third = program.procs[0].body[2]; - check(context, third.assign.target_deref); - check(context, text_is(third.assign.target, "rsi")); - check(context, primary_is(third.assign.value, "rdx")); - - free_program(&program); -} - -static void test_parse_simple_statements(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nloop:\nsyscall\ngoto loop\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 3); - - struct Statement label = program.procs[0].body[0]; - check(context, label.kind == STATEMENT_LABEL); - check(context, text_is(label.label.name, "loop")); - - check(context, program.procs[0].body[1].kind == STATEMENT_SYSCALL); - - struct Statement jump = program.procs[0].body[2]; - check(context, jump.kind == STATEMENT_GOTO); - check(context, text_is(jump.jump.label, "loop")); - - free_program(&program); -} - -static void test_parse_expressions(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nrsi = buffer + 31\nrdx = message.len\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 2); - - const struct Expr* sum = program.procs[0].body[0].assign.value; - check(context, sum->kind == EXPR_BINARY); - check(context, text_is(sum->binary.op, "+")); - check(context, primary_is(sum->binary.left, "buffer")); - check(context, primary_is(sum->binary.right, "31")); - - const struct Expr* member = program.procs[0].body[1].assign.value; - check(context, member->kind == EXPR_MEMBER); - check(context, primary_is(member->member.object, "message")); - check(context, text_is(member->member.member, "len")); - - free_program(&program); -} - -static void test_parse_if(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nif rax != 0\ngoto loop\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 1); - - struct Statement branch = program.procs[0].body[0]; - check(context, branch.kind == STATEMENT_IF); - check(context, primary_is(branch.branch.left, "rax")); - check(context, text_is(branch.branch.comparison, "!=")); - check(context, primary_is(branch.branch.right, "0")); - check(context, branch.branch.body_count == 1); - check(context, branch.branch.body->kind == STATEMENT_GOTO); - check(context, text_is(branch.branch.body->jump.label, "loop")); - check(context, branch.branch.else_count == 0); - - free_program(&program); -} - -static void test_parse_if_block(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nif rax > 3\n{\nrdi = 1\nrbx = 2\n}\nelse\n{\nrdi = 0\n}\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 1); - - struct Statement branch = program.procs[0].body[0]; - check(context, branch.kind == STATEMENT_IF); - check(context, branch.branch.body_count == 2); - check(context, branch.branch.body[0].kind == STATEMENT_ASSIGN); - check(context, text_is(branch.branch.body[1].assign.target, "rbx")); - check(context, branch.branch.else_count == 1); - check(context, text_is(branch.branch.else_body[0].assign.target, "rdi")); - - free_program(&program); -} - -static void test_parse_else_if(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nif rax == 0\nrdi = 1\nelse if rax == 1\nrdi = 2\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - struct Statement branch = program.procs[0].body[0]; - check(context, branch.kind == STATEMENT_IF); - check(context, branch.branch.body_count == 1); - check(context, branch.branch.else_count == 1); - check(context, branch.branch.else_body[0].kind == STATEMENT_IF); - check(context, primary_is(branch.branch.else_body[0].branch.right, "1")); - - free_program(&program); -} - -static void test_parse_while(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "proc main\n{\nwhile rcx > 0\n{\nrbx += rcx\nrcx -= 1\n}\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 1); - - struct Statement loop = program.procs[0].body[0]; - check(context, loop.kind == STATEMENT_WHILE); - check(context, primary_is(loop.loop.left, "rcx")); - check(context, text_is(loop.loop.comparison, ">")); - check(context, primary_is(loop.loop.right, "0")); - check(context, loop.loop.body_count == 2); - check(context, text_is(loop.loop.body[1].assign.target, "rcx")); - check(context, !loop.loop.named); - - free_program(&program); -} - -static void test_parse_select(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nr1 = r2 if r3 < r4 else r5\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - struct Statement s = program.procs[0].body[0]; - check(context, s.kind == STATEMENT_SELECT); - check(context, text_is(s.select.target, "r1")); - check(context, primary_is(s.select.if_value, "r2")); - check(context, primary_is(s.select.left, "r3")); - check(context, text_is(s.select.comparison, "<")); - check(context, primary_is(s.select.right, "r4")); - check(context, primary_is(s.select.else_value, "r5")); - - free_program(&program); -} - -static void test_parse_while_named(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nwhile .drain rcx > 0\n{\nrcx -= 1\n}\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - struct Statement loop = program.procs[0].body[0]; - check(context, loop.kind == STATEMENT_WHILE); - check(context, loop.loop.named); - check(context, text_is(loop.loop.name, "drain")); - check(context, primary_is(loop.loop.left, "rcx")); - - free_program(&program); -} - -static void test_parse_call(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nprint_number(r12)\nf(a, b)\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 2); - - struct CallStatement first = program.procs[0].body[0].call; - check(context, program.procs[0].body[0].kind == STATEMENT_CALL); - check(context, text_is(first.name, "print_number")); - check(context, first.arg_count == 1); - check(context, primary_is(first.args[0], "r12")); - - struct CallStatement second = program.procs[0].body[1].call; - check(context, second.arg_count == 2); - check(context, primary_is(second.args[0], "a")); - check(context, primary_is(second.args[1], "b")); - - free_program(&program); -} - -static void test_parse_stack(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nstack buffer[32]\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 1); - - struct Statement statement = program.procs[0].body[0]; - check(context, statement.kind == STATEMENT_STACK); - check(context, text_is(statement.stack.name, "buffer")); - check(context, primary_is(statement.stack.size, "32")); - - free_program(&program); -} - -static void test_parse_sized_deref(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\n^byte rsi = rdx\n^rdi = rax\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.procs[0].body_count == 2); - - struct AssignStatement sized = program.procs[0].body[0].assign; - check(context, sized.target_deref); - check(context, sized.store_size == STORE_SIZE_BYTE); - check(context, text_is(sized.target, "rsi")); - - struct AssignStatement plain = program.procs[0].body[1].assign; - check(context, plain.target_deref); - check(context, plain.store_size == STORE_SIZE_NONE); - - free_program(&program); -} - -static void test_parse_signed_deref(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nrax = ^signed byte rsi\nrbx = ^byte rsi\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - const struct Expr* signed_load = program.procs[0].body[0].assign.value; - check(context, signed_load->kind == EXPR_DEREF); - check(context, signed_load->deref.is_signed); - check(context, signed_load->deref.size == STORE_SIZE_BYTE); - - const struct Expr* plain_load = program.procs[0].body[1].assign.value; - check(context, plain_load->kind == EXPR_DEREF); - check(context, !plain_load->deref.is_signed); - - free_program(&program); -} - -static void test_parse_directives(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("[bits: 32]\n[entry: kmain]\n[enable: logical_registers]\nproc kmain\n{\nsyscall\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.config.bits == 32); - check(context, program.config.has_entry); - check(context, text_is(program.config.entry, "kmain")); - check(context, program.config.logical_registers); - - free_program(&program); -} - -static void test_parse_bad_directive(struct TestContext* context) -{ - struct Lexer bad_bits = create_lexer("[bits: 8]\nproc main\n{\nsyscall\n}\n"); - struct Program program; - check(context, !parse_program(&bad_bits, &program)); - free_program(&program); - - struct Lexer bad_key = create_lexer("[target: nasm]\nproc main\n{\nsyscall\n}\n"); - check(context, !parse_program(&bad_key, &program)); - free_program(&program); -} - -static void test_parse_register_size_suffix(struct TestContext* context) -{ - struct Lexer lexer = create_lexer("proc main\n{\nr1 = r2.8\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - - const struct Expr* value = program.procs[0].body[0].assign.value; - check(context, value->kind == EXPR_MEMBER); - check(context, value->member.member.type == TOKEN_INTEGER); - check(context, text_is(value->member.member, "8")); - check(context, value->member.object->kind == EXPR_PRIMARY); - check(context, text_is(value->member.object->primary.token, "r2")); - - free_program(&program); -} - -static void test_parse_enum_struct(struct TestContext* context) -{ - struct Lexer lexer = create_lexer( - "enum Color\n{\nRed,\nGreen,\nBlue\n}\nstruct Point\n{\nx\ny: byte\n}\n"); - struct Program program; - - check(context, parse_program(&lexer, &program)); - check(context, program.enum_count == 1); - check(context, text_is(program.enums[0].name, "Color")); - check(context, program.enums[0].member_count == 3); - check(context, text_is(program.enums[0].members[1], "Green")); - - check(context, program.struct_count == 1); - check(context, text_is(program.structs[0].name, "Point")); - check(context, program.structs[0].field_count == 2); - check(context, program.structs[0].fields[0].size == STORE_SIZE_QWORD); - check(context, program.structs[0].fields[1].size == STORE_SIZE_BYTE); - - free_program(&program); -} - -static void test_parse_errors(struct TestContext* context) -{ - struct Program program; - - struct Lexer missing_name = create_lexer("const = 1\n"); - check(context, !parse_program(&missing_name, &program)); - free_program(&program); - - struct Lexer wrong_value = create_lexer("data x = 5\n"); - check(context, !parse_program(&wrong_value, &program)); - free_program(&program); - - struct Lexer unterminated = create_lexer("proc main\n{\nrax = 1\n"); - check(context, !parse_program(&unterminated, &program)); - free_program(&program); -} - -void run_parser_tests(struct TestContext* context) -{ - test_parse_consts(context); - test_parse_const_expr(context); - test_parse_unary(context); - test_parse_data(context); - test_parse_proc_params(context); - test_parse_proc_body(context); - test_parse_simple_statements(context); - test_parse_expressions(context); - test_parse_if(context); - test_parse_if_block(context); - test_parse_else_if(context); - test_parse_while(context); - test_parse_while_named(context); - test_parse_select(context); - test_parse_call(context); - test_parse_stack(context); - test_parse_sized_deref(context); - test_parse_signed_deref(context); - test_parse_directives(context); - test_parse_bad_directive(context); - test_parse_register_size_suffix(context); - test_parse_enum_struct(context); - test_parse_errors(context); -} diff --git a/tests/sema_test.c b/tests/sema_test.c deleted file mode 100644 index 3c12001..0000000 --- a/tests/sema_test.c +++ /dev/null @@ -1,166 +0,0 @@ -#include <stdbool.h> - -#include "sema.h" -#include "tests.h" -#include "parser.h" - -static bool analyze_source(const char* source) -{ - struct Lexer lexer = create_lexer(source); - struct Program program; - if (!parse_program(&lexer, &program)) - { - free_program(&program); - return false; - } - - struct Source diagnostics = { "<test>", source }; - bool ok = analyze_program(diagnostics, &program); - free_program(&program); - return ok; -} - -static void test_valid_program(struct TestContext* context) -{ - check(context, analyze_source("const N = 1\ndata msg = \"hi\"\nproc main\n{\nsyscall\n}\n")); -} - -static void test_no_entry_is_ok(struct TestContext* context) -{ - // without an [entry: ...] directive there is no required entry point - check(context, analyze_source("proc helper\n{\nsyscall\n}\n")); -} - -static void test_defined_entry(struct TestContext* context) -{ - check(context, analyze_source("[entry: start]\nproc start\n{\nsyscall\n}\n")); -} - -static void test_undefined_entry(struct TestContext* context) -{ - check(context, !analyze_source("[entry: main]\nproc helper\n{\nsyscall\n}\n")); -} - -static void test_duplicate_const(struct TestContext* context) -{ - check(context, !analyze_source("const X = 1\nconst X = 2\nproc main\n{\nsyscall\n}\n")); -} - -static void test_duplicate_across_kinds(struct TestContext* context) -{ - check(context, !analyze_source("data foo = \"a\"\nproc foo\n{\nsyscall\n}\nproc main\n{\nsyscall\n}\n")); -} - -static void test_undefined_name(struct TestContext* context) -{ - check(context, !analyze_source("proc main\n{\nrax = MISSING\n}\n")); -} - -static void test_undefined_label(struct TestContext* context) -{ - check(context, !analyze_source("proc main\n{\ngoto nowhere\n}\n")); -} - -static void test_undefined_call(struct TestContext* context) -{ - check(context, !analyze_source("proc main\n{\nnope(rax)\n}\n")); -} - -static void test_call_arg_count(struct TestContext* context) -{ - check(context, !analyze_source("proc f(a: rdi)\n{\nsyscall\n}\nproc main\n{\nf(rax, rbx)\n}\n")); -} - -static void test_assign_to_const(struct TestContext* context) -{ - check(context, !analyze_source("const K = 5\nproc main\n{\nK = 1\n}\n")); -} - -static void test_const_expr_ok(struct TestContext* context) -{ - check(context, analyze_source("const A = 1\nconst B = A + 2 * 3\nproc main\n{\nsyscall\n}\n")); -} - -static void test_const_expr_rejects_register(struct TestContext* context) -{ - check(context, !analyze_source("const X = rax + 1\nproc main\n{\nsyscall\n}\n")); -} - -static void test_const_expr_rejects_data(struct TestContext* context) -{ - check(context, !analyze_source("data d = \"x\"\nconst X = d\nproc main\n{\nsyscall\n}\n")); -} - -static void test_deref_needs_register(struct TestContext* context) -{ - check(context, !analyze_source("proc main\n{\nrax = ^MISSING\n}\n")); - check(context, analyze_source("proc main\n{\nrax = ^rsi\n}\n")); -} - -static void test_signed_load_needs_size(struct TestContext* context) -{ - check(context, analyze_source("proc main\n{\nrax = ^signed byte rsi\n}\n")); - check(context, !analyze_source("proc main\n{\nrax = ^signed rsi\n}\n")); - check(context, !analyze_source("proc main\n{\nrax = ^signed qword rsi\n}\n")); -} - -static void test_stack_size_constant(struct TestContext* context) -{ - check(context, analyze_source( - "struct P\n{\nx\ny\n}\nconst N = 4\n" - "proc main\n{\nstack a[P.size]\nstack b[N * 2]\nsyscall\n}\n")); - check(context, !analyze_source("proc main\n{\nstack a[rax]\n}\n")); -} - -static void test_enum_struct_members(struct TestContext* context) -{ - check(context, analyze_source( - "enum Color\n{\nRed,\nGreen\n}\nstruct Point\n{\nx\ny\n}\n" - "proc main\n{\nrax = Color.Green\nrbx = Point.y\nrcx = Point.size\n}\n")); - check(context, !analyze_source( - "enum Color\n{\nRed\n}\nproc main\n{\nrax = Color.Nope\n}\n")); - check(context, !analyze_source( - "struct Point\n{\nx\n}\nproc main\n{\nrax = Point.z\n}\n")); -} - -static void test_references_resolve(struct TestContext* context) -{ - // registers, params, consts, data (+ .len), stack buffers, labels, calls - check(context, analyze_source( - "const N = 1\n" - "data msg = \"hi\"\n" - "proc helper(value: rdi)\n{\nrax = value\n}\n" - "proc main\n{\n" - "stack buf[8]\n" - "rax = N\n" - "rsi = msg\n" - "rdx = msg.len\n" - "rdi = buf + 1\n" - "helper(rax)\n" - "loop:\n" - "goto loop\n" - "}\n")); -} - -void run_sema_tests(struct TestContext* context) -{ - test_valid_program(context); - test_no_entry_is_ok(context); - test_defined_entry(context); - test_undefined_entry(context); - test_duplicate_const(context); - test_duplicate_across_kinds(context); - test_undefined_name(context); - test_undefined_label(context); - test_undefined_call(context); - test_call_arg_count(context); - test_assign_to_const(context); - test_const_expr_ok(context); - test_const_expr_rejects_register(context); - test_const_expr_rejects_data(context); - test_deref_needs_register(context); - test_signed_load_needs_size(context); - test_stack_size_constant(context); - test_enum_struct_members(context); - test_references_resolve(context); -} diff --git a/tests/test.h b/tests/test.h deleted file mode 100644 index c5e2e73..0000000 --- a/tests/test.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include <stdio.h> - -struct TestContext -{ - int checks; - int failures; -}; - -#define check(context, condition) \ - do \ - { \ - (context)->checks += 1; \ - if (!(condition)) \ - { \ - (context)->failures += 1; \ - printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ - } \ - } \ - while (0) diff --git a/tests/tests.h b/tests/tests.h deleted file mode 100644 index 2caf9b3..0000000 --- a/tests/tests.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "test.h" - -void run_lexer_tests(struct TestContext* context); -void run_parser_tests(struct TestContext* context); -void run_sema_tests(struct TestContext* context); -void run_codegen_tests(struct TestContext* context); |
