hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-12 11:41:05 +0200
committerhachem <im@hachem.wtf>2026-10-12 11:41:05 +0200
commit62636576bce8e8121a3d70f63547fae40a52f697 (patch)
tree077dfe4f04235cce63b7abb1b05bb539232ae74a
parentc0f000ea166a24781fcdbf23c080b203cd9b8ed7 (diff)
chore: refactor
-rw-r--r--examples/arithmetic.hdass1
-rw-r--r--examples/arm64/exit.hdass7
-rw-r--r--examples/arm64/sum.hdass2
-rw-r--r--examples/branch.hdass1
-rw-r--r--examples/call.hdass1
-rw-r--r--examples/circle.hdass12
-rw-r--r--examples/constants.hdass2
-rw-r--r--examples/exit_code.hdass1
-rw-r--r--examples/fibonacci.hdass2
-rw-r--r--examples/greet.hdass1
-rw-r--r--examples/load.hdass7
-rw-r--r--examples/logical.hdass13
-rw-r--r--examples/loop_sum.hdass5
-rw-r--r--examples/mandelbrot.hdass26
-rw-r--r--examples/modulo.hdass1
-rw-r--r--examples/mul_div.hdass6
-rw-r--r--examples/records.hdass14
-rw-r--r--meson.build15
-rwxr-xr-xscripts/docker_test.sh9
-rwxr-xr-xscripts/run_suite.sh32
-rwxr-xr-xscripts/test_examples.sh28
-rw-r--r--src/ast.c1
-rw-r--r--src/ast.h5
-rw-r--r--src/codegen.c185
-rw-r--r--src/diag.c8
-rw-r--r--src/parser.c2
-rw-r--r--src/sema.c30
-rw-r--r--tests/codegen_test.c689
-rw-r--r--tests/lexer_test.c111
-rw-r--r--tests/main.c16
-rw-r--r--tests/parser_test.c467
-rw-r--r--tests/sema_test.c166
-rw-r--r--tests/test.h21
-rw-r--r--tests/tests.h8
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
diff --git a/src/ast.c b/src/ast.c
index b613abe..4480b56 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -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)
diff --git a/src/ast.h b/src/ast.h
index 1db8b99..23fe438 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -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)
diff --git a/src/diag.c b/src/diag.c
index 6962046..cb1a057 100644
--- a/src/diag.c
+++ b/src/diag.c
@@ -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 '.'");
diff --git a/src/sema.c b/src/sema.c
index 01fe644..342fe44 100644
--- a/src/sema.c
+++ b/src/sema.c
@@ -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);