aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-31 00:19:10 +0200
committerhachem <im@hachem.wtf>2026-08-31 00:19:10 +0200
commit3e20038bbf7206e2270655418d7884ccfe91a4e4 (patch)
treedfc7bf806a00c1dbec6c889351c108702a0e9d40
parent8eac30c13cfa4f47a8a0aa0772b96f0813fe33a5 (diff)
add directives and directive based entry points
-rw-r--r--README.md4
-rw-r--r--examples/arithmetic.hdass2
-rw-r--r--examples/branch.hdass2
-rw-r--r--examples/call.hdass2
-rw-r--r--examples/exit_code.hdass2
-rw-r--r--examples/fibonacci.hdasm8
-rw-r--r--examples/greet.hdass2
-rw-r--r--examples/hello_world.hdass2
-rw-r--r--examples/loop_sum.hdass2
-rwxr-xr-xscripts/docker_test.sh14
-rwxr-xr-xscripts/run_suite.sh35
-rwxr-xr-xscripts/test_examples.sh77
-rw-r--r--src/codegen/nasm.c16
-rw-r--r--src/parser/ast.c3
-rw-r--r--src/parser/ast.h11
-rw-r--r--src/parser/parser.c73
-rw-r--r--src/sema/sema.c12
-rw-r--r--tests/codegen_test.c53
-rw-r--r--tests/parser_test.c28
-rw-r--r--tests/sema_test.c19
20 files changed, 312 insertions, 55 deletions
diff --git a/README.md b/README.md
index 4a69904..4cf4444 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,8 @@ This is where hdass comes in. It's not quite high-level enough to be a C-like la
## Examples
Here's a simple "Hello, World!" world program written using hdass' syntax:
```hdass
+[entry: main]
+
const SYS_WRITE = 1
const SYS_EXIT = 60
@@ -60,7 +62,7 @@ From inside the container you can build the compiler and take a program all the
premake5 gmake && make config=debug
./bin/debug-linux/hdass examples/hello_world.hdass -o hello.asm
nasm -f elf64 hello.asm -o hello.o
-ld hello.o -o hello
+ld -e main hello.o -o hello
./hello
```
diff --git a/examples/arithmetic.hdass b/examples/arithmetic.hdass
index 359653e..1534146 100644
--- a/examples/arithmetic.hdass
+++ b/examples/arithmetic.hdass
@@ -1,3 +1,5 @@
+[entry: main]
+
// Computes (5 + 3) * 2 - 1 = 15 and returns it as the exit status.
const SYS_EXIT = 60
diff --git a/examples/branch.hdass b/examples/branch.hdass
index 1218773..15f8d1b 100644
--- a/examples/branch.hdass
+++ b/examples/branch.hdass
@@ -1,3 +1,5 @@
+[entry: main]
+
// Returns the larger of two values (max(3, 8) = 8) as the exit status.
const SYS_EXIT = 60
diff --git a/examples/call.hdass b/examples/call.hdass
index 239a26b..bf0c515 100644
--- a/examples/call.hdass
+++ b/examples/call.hdass
@@ -1,3 +1,5 @@
+[entry: main]
+
// Passes an argument through a proc parameter, which exits with that value.
const SYS_EXIT = 60
diff --git a/examples/exit_code.hdass b/examples/exit_code.hdass
index 7c44df9..cabe634 100644
--- a/examples/exit_code.hdass
+++ b/examples/exit_code.hdass
@@ -1,3 +1,5 @@
+[entry: main]
+
// Exits with status 42 and no output.
const SYS_EXIT = 60
diff --git a/examples/fibonacci.hdasm b/examples/fibonacci.hdasm
index 58f71fe..95a59c8 100644
--- a/examples/fibonacci.hdasm
+++ b/examples/fibonacci.hdasm
@@ -1,3 +1,5 @@
+[entry: main]
+
const SYS_WRITE = 1
const SYS_EXIT = 60
@@ -41,7 +43,7 @@ proc main
{
r12 = 0
r13 = 1
- rcx = 10
+ r15 = 10 // syscall clobbers rcx/r11, so keep the counter in r15
loop:
print_number(r12)
@@ -50,8 +52,8 @@ loop:
r12 = r13
r13 += rax
- rcx -= 1
- if rcx != 0
+ r15 -= 1
+ if r15 != 0
goto loop
rax = SYS_EXIT
diff --git a/examples/greet.hdass b/examples/greet.hdass
index a253b5e..da9f790 100644
--- a/examples/greet.hdass
+++ b/examples/greet.hdass
@@ -1,3 +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/hello_world.hdass b/examples/hello_world.hdass
index 59dac80..da6b2b5 100644
--- a/examples/hello_world.hdass
+++ b/examples/hello_world.hdass
@@ -1,3 +1,5 @@
+[entry: main]
+
const SYS_WRITE = 1
const SYS_EXIT = 60
diff --git a/examples/loop_sum.hdass b/examples/loop_sum.hdass
index bb8d115..5976914 100644
--- a/examples/loop_sum.hdass
+++ b/examples/loop_sum.hdass
@@ -1,3 +1,5 @@
+[entry: main]
+
// Sums 5 + 4 + 3 + 2 + 1 = 15 with a countdown loop, returns it as the status.
const SYS_EXIT = 60
diff --git a/scripts/docker_test.sh b/scripts/docker_test.sh
index 93f0bca..2ded903 100755
--- a/scripts/docker_test.sh
+++ b/scripts/docker_test.sh
@@ -2,17 +2,15 @@
# Convenience wrapper: build hdass and run the full test suite (unit tests plus
# the end-to-end example tests) inside the amd64 Linux Docker environment.
# Run this from the host; it brings the container up if it is not already.
+#
+# Colours are on by default; set NO_COLOR to disable them.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
cd "$root"
-docker compose up -d
+docker compose up -d >/dev/null
-docker compose exec -T hdass bash -c '
- set -e
- premake5 gmake
- make config=debug
- ./bin/debug-linux/tests
- ./scripts/test_examples.sh
-'
+# stdin is redirected from /dev/null so `docker compose exec -T` does not hang
+# waiting on the stream after the suite finishes.
+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
new file mode 100755
index 0000000..9d40e24
--- /dev/null
+++ b/scripts/run_suite.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# Build hdass and run the full test suite (unit tests plus the end-to-end
+# example tests). Expects the amd64 Linux environment; it is invoked inside the
+# container by scripts/docker_test.sh, but can also be run directly there.
+#
+# Colours are on by default; set NO_COLOR to disable them.
+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=$(premake5 gmake 2>&1 && make config=debug 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=$(./bin/debug-linux/tests 2>/dev/null); then
+ printf ' %s✔%s %s\n\n' "$green" "$reset" "$(printf '%s' "$units" | tail -1)"
+else
+ printf '%s\n' "$units"
+ ./bin/debug-linux/tests || true
+ printf ' %s✘ unit tests failed%s\n' "$red" "$reset"
+ exit 1
+fi
+
+exec bash ./scripts/test_examples.sh
diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh
index d5b5491..5ebba65 100755
--- a/scripts/test_examples.sh
+++ b/scripts/test_examples.sh
@@ -5,21 +5,29 @@
#
# The generated programs use Linux x86-64 syscalls, so this must run in the
# amd64 Linux environment (see the Docker setup in the README), not on macOS.
+#
+# Colours are on by default; set NO_COLOR to disable them.
set -u
root="$(cd "$(dirname "$0")/.." && pwd)"
cd "$root"
+if [ -z "${NO_COLOR:-}" ]; then
+ bold=$'\033[1m'; dim=$'\033[2m'; red=$'\033[31m'; green=$'\033[32m'; reset=$'\033[0m'
+else
+ bold=; dim=; red=; green=; reset=
+fi
+
hdass="${HDASS:-./bin/debug-linux/hdass}"
if [ ! -x "$hdass" ]; then
- echo "error: '$hdass' not found; build it first with:" >&2
+ echo "${red}error:${reset} '$hdass' not found; build it first with:" >&2
echo " premake5 gmake && make config=debug" >&2
exit 1
fi
for tool in nasm ld; do
if ! command -v "$tool" >/dev/null 2>&1; then
- echo "error: '$tool' not found; run this inside the Docker environment" >&2
+ echo "${red}error:${reset} '$tool' not found; run this inside the Docker environment" >&2
exit 1
fi
done
@@ -30,47 +38,59 @@ trap 'rm -rf "$work"' EXIT
pass=0
fail=0
-# check <name> <source> <expected_exit> <expected_stdout>
+# check <name> <description> <source> <expected_exit> <expected_stdout>
# expected_stdout is compared after trailing newlines are stripped (as $() does).
check()
{
- local name="$1" source="$2" expected_exit="$3" expected_stdout="$4"
- local asm="$work/$name.asm" obj="$work/$name.o" bin="$work/$name"
+ local name="$1" desc="$2" source="$3" expected_exit="$4" expected_stdout="$5"
+ local asm="$work/$name.asm" obj="$work/$name.o" bin="$work/$name" stage=""
if ! "$hdass" "$source" -o "$asm" 2>"$work/err"; then
- echo "FAIL $name (transpile)"; cat "$work/err"; fail=$((fail + 1)); return
+ stage="transpile"
+ elif ! nasm -f elf64 "$asm" -o "$obj" 2>"$work/err"; then
+ stage="assemble"
+ elif ! ld -e main "$obj" -o "$bin" 2>"$work/err"; then
+ stage="link"
fi
- if ! nasm -f elf64 "$asm" -o "$obj" 2>"$work/err"; then
- echo "FAIL $name (assemble)"; cat "$work/err"; fail=$((fail + 1)); return
- fi
- if ! ld "$obj" -o "$bin" 2>"$work/err"; then
- echo "FAIL $name (link)"; cat "$work/err"; fail=$((fail + 1)); return
+
+ if [ -n "$stage" ]; then
+ printf ' %s✘%s %-12s %s%s%s\n' "$red" "$reset" "$name" "$dim" "$desc" "$reset"
+ printf ' %sfailed to %s%s\n' "$red" "$stage" "$reset"
+ sed 's/^/ /' "$work/err"
+ fail=$((fail + 1))
+ return
fi
local actual_stdout actual_exit
- actual_stdout="$("$bin")"
+ actual_stdout="$(timeout 10 "$bin")"
actual_exit=$?
if [ "$actual_exit" != "$expected_exit" ] || [ "$actual_stdout" != "$expected_stdout" ]; then
- echo "FAIL $name"
- echo " expected: exit=$expected_exit stdout=$(printf '%q' "$expected_stdout")"
- echo " actual: exit=$actual_exit stdout=$(printf '%q' "$actual_stdout")"
+ printf ' %s✘%s %-12s %s%s%s\n' "$red" "$reset" "$name" "$dim" "$desc" "$reset"
+ if [ "$actual_exit" = 124 ]; then
+ printf ' %stimed out (likely an infinite loop)%s\n' "$red" "$reset"
+ fi
+ printf ' expected: exit %s stdout=%s\n' "$expected_exit" "$(printf '%q' "$expected_stdout")"
+ printf ' actual: exit %s stdout=%s\n' "$actual_exit" "$(printf '%q' "$actual_stdout")"
fail=$((fail + 1))
return
fi
- echo "PASS $name"
+ printf ' %s✔%s %-12s %s%-42s%s %sexit %s%s\n' \
+ "$green" "$reset" "$name" "$dim" "$desc" "$reset" "$dim" "$expected_exit" "$reset"
pass=$((pass + 1))
}
-check hello_world examples/hello_world.hdass 0 "Hello, World!"
-check greet examples/greet.hdass 0 "hdass works!"
-check exit_code examples/exit_code.hdass 42 ""
-check arithmetic examples/arithmetic.hdass 15 ""
-check loop_sum examples/loop_sum.hdass 15 ""
-check branch examples/branch.hdass 8 ""
-check call examples/call.hdass 21 ""
-check fibonacci examples/fibonacci.hdasm 0 "0
+printf '%s━━ example programs ━━%s\n' "$bold" "$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 fibonacci "prints the first ten Fibonacci numbers" examples/fibonacci.hdasm 0 "0
1
1
2
@@ -81,6 +101,11 @@ check fibonacci examples/fibonacci.hdasm 0 "0
21
34"
-echo
-echo "$pass passed, $fail failed"
+printf '\n'
+if [ "$fail" -eq 0 ]; then
+ printf ' %s✔ all %d examples passed%s\n' "$green" "$pass" "$reset"
+else
+ printf ' %s✘ %d of %d examples failed%s\n' "$red" "$fail" "$((pass + fail))" "$reset"
+fi
+
[ "$fail" -eq 0 ]
diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c
index 323adfc..adae143 100644
--- a/src/codegen/nasm.c
+++ b/src/codegen/nasm.c
@@ -449,11 +449,12 @@ static void emit_proc(struct Program* program, struct ProcDecl* proc, FILE* out)
emitter.out = out;
emitter.label_id = 0;
- bool is_entry = proc->name.length == 4 && memcmp(proc->name.start, "main", 4) == 0;
- if (is_entry)
- fprintf(out, "_start:\n");
- else
- fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start);
+ struct Config config = program->config;
+ bool is_entry = config.has_entry
+ && proc->name.length == config.entry.length
+ && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0;
+
+ fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start);
uint64_t stack_size = proc_stack_size(proc);
if (stack_size > 0)
@@ -476,6 +477,8 @@ static void emit_proc(struct Program* program, struct ProcDecl* proc, FILE* out)
void generate_nasm(struct Program* program, FILE* out)
{
+ fprintf(out, "bits %u\n\n", program->config.bits);
+
if (program->const_count > 0)
{
emit_consts(program, out);
@@ -486,7 +489,8 @@ void generate_nasm(struct Program* program, FILE* out)
fprintf(out, "\n");
fprintf(out, "section .text\n");
- fprintf(out, "global _start\n");
+ if (program->config.has_entry)
+ fprintf(out, "global %.*s\n", (int)program->config.entry.length, program->config.entry.start);
for (size_t i = 0; i < program->proc_count; i += 1)
{
diff --git a/src/parser/ast.c b/src/parser/ast.c
index 1aa1d38..96ee479 100644
--- a/src/parser/ast.c
+++ b/src/parser/ast.c
@@ -58,6 +58,9 @@ void free_proc(struct ProcDecl* proc)
struct Program create_program(void)
{
struct Program program;
+ program.config.bits = 64;
+ program.config.has_entry = false;
+ program.config.logical_registers = false;
program.consts = NULL;
program.const_count = 0;
program.const_capacity = 0;
diff --git a/src/parser/ast.h b/src/parser/ast.h
index 6071c4a..a98f43b 100644
--- a/src/parser/ast.h
+++ b/src/parser/ast.h
@@ -1,6 +1,7 @@
#pragma once
#include <stddef.h>
+#include <stdint.h>
#include <stdbool.h>
#include "lexer/lexer.h"
@@ -146,8 +147,18 @@ struct ProcDecl
size_t body_capacity;
};
+struct Config
+{
+ uint32_t bits;
+ bool has_entry;
+ struct Token entry;
+ bool logical_registers;
+};
+
struct Program
{
+ struct Config config;
+
struct ConstDecl* consts;
size_t const_count;
size_t const_capacity;
diff --git a/src/parser/parser.c b/src/parser/parser.c
index ca14023..8603c46 100644
--- a/src/parser/parser.c
+++ b/src/parser/parser.c
@@ -1,5 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include "parser/parser.h"
@@ -436,6 +437,70 @@ error:
return false;
}
+static bool token_text_is(struct Token token, const char* text)
+{
+ size_t length = strlen(text);
+ return token.length == length && memcmp(token.start, text, length) == 0;
+}
+
+static bool parse_directive(struct Parser* parser, struct Program* program)
+{
+ if (!consume(parser, TOKEN_IDENTIFIER, "expected directive name after '['"))
+ return false;
+ struct Token key = parser->previous;
+
+ if (!consume(parser, TOKEN_COLON, "expected ':' after directive name"))
+ return false;
+
+ if (!check(parser, TOKEN_IDENTIFIER) && !check(parser, TOKEN_INTEGER))
+ {
+ error_at(parser, parser->current, "expected a directive value");
+ return false;
+ }
+ advance_parser(parser);
+ struct Token value = parser->previous;
+
+ if (!consume(parser, TOKEN_RIGHT_BRACKET, "expected ']' to close directive"))
+ return false;
+
+ if (token_text_is(key, "bits"))
+ {
+ if (value.type != TOKEN_INTEGER || (!token_text_is(value, "64") && !token_text_is(value, "32")))
+ {
+ error_at(parser, value, "bits must be 32 or 64");
+ return false;
+ }
+ program->config.bits = token_text_is(value, "64") ? 64 : 32;
+ return true;
+ }
+
+ if (token_text_is(key, "entry"))
+ {
+ if (value.type != TOKEN_IDENTIFIER)
+ {
+ error_at(parser, value, "entry must be a procedure name");
+ return false;
+ }
+ program->config.has_entry = true;
+ program->config.entry = value;
+ return true;
+ }
+
+ if (token_text_is(key, "enable"))
+ {
+ if (value.type == TOKEN_IDENTIFIER && token_text_is(value, "logical_registers"))
+ {
+ program->config.logical_registers = true;
+ return true;
+ }
+ error_at(parser, value, "unknown extension");
+ return false;
+ }
+
+ error_at(parser, key, "unknown directive");
+ return false;
+}
+
bool parse_program(struct Lexer* lexer, struct Program* out)
{
struct Parser parser;
@@ -447,7 +512,13 @@ bool parse_program(struct Lexer* lexer, struct Program* out)
while (!check(&parser, TOKEN_EOF))
{
- if (check(&parser, TOKEN_CONST))
+ if (check(&parser, TOKEN_LEFT_BRACKET))
+ {
+ advance_parser(&parser);
+ if (!parse_directive(&parser, out))
+ return false;
+ }
+ else if (check(&parser, TOKEN_CONST))
{
advance_parser(&parser);
if (!parse_const(&parser, out))
diff --git a/src/sema/sema.c b/src/sema/sema.c
index 99ec4f1..55f4a93 100644
--- a/src/sema/sema.c
+++ b/src/sema/sema.c
@@ -49,14 +49,16 @@ static bool check_duplicate_names(struct Program* program)
static bool check_entry_point(struct Program* program)
{
+ if (!program->config.has_entry)
+ return true;
+
+ struct Token entry = program->config.entry;
for (size_t i = 0; i < program->proc_count; i += 1)
- {
- struct Token name = program->procs[i].name;
- if (name.length == 4 && memcmp(name.start, "main", 4) == 0)
+ if (names_equal(program->procs[i].name, entry))
return true;
- }
- fprintf(stderr, "error: no 'main' procedure defined\n");
+ fprintf(stderr, "error: line %u: entry point '%.*s' is not defined\n",
+ entry.line, (int)entry.length, entry.start);
return false;
}
diff --git a/tests/codegen_test.c b/tests/codegen_test.c
index 64a422d..5236268 100644
--- a/tests/codegen_test.c
+++ b/tests/codegen_test.c
@@ -26,7 +26,6 @@ static void test_generate_consts_and_data(struct TestContext* context)
check(context, strstr(buffer, "msg: db `hi`") != NULL);
check(context, strstr(buffer, ".len equ $ - msg") != NULL);
check(context, strstr(buffer, "section .text") != NULL);
- check(context, strstr(buffer, "global _start") != NULL);
free_program(&program);
}
@@ -48,7 +47,7 @@ static void test_generate_text(struct TestContext* context)
buffer[read] = '\0';
fclose(out);
- check(context, strstr(buffer, "_start:") != NULL);
+ 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);
@@ -228,8 +227,58 @@ static void test_generate_sized_store(struct TestContext* context)
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));
+
+ FILE* out = tmpfile();
+ generate_nasm(&program, out);
+ fflush(out);
+ rewind(out);
+
+ char buffer[1024];
+ size_t read = fread(buffer, 1, sizeof(buffer) - 1, out);
+ buffer[read] = '\0';
+ fclose(out);
+
+ 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));
+
+ FILE* out = tmpfile();
+ generate_nasm(&program, out);
+ fflush(out);
+ rewind(out);
+
+ char buffer[1024];
+ size_t read = fread(buffer, 1, sizeof(buffer) - 1, out);
+ buffer[read] = '\0';
+ fclose(out);
+
+ check(context, strstr(buffer, "global") == NULL);
+ check(context, strstr(buffer, "main:") != NULL);
+
+ free_program(&program);
+}
+
void run_codegen_tests(struct TestContext* context)
{
+ test_generate_entry_and_bits(context);
+ test_generate_no_entry(context);
test_generate_consts_and_data(context);
test_generate_text(context);
test_generate_if(context);
diff --git a/tests/parser_test.c b/tests/parser_test.c
index 61f8c37..3b60261 100644
--- a/tests/parser_test.c
+++ b/tests/parser_test.c
@@ -208,6 +208,32 @@ static void test_parse_sized_deref(struct TestContext* context)
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: 16]\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_errors(struct TestContext* context)
{
struct Program program;
@@ -237,5 +263,7 @@ void run_parser_tests(struct TestContext* context)
test_parse_call(context);
test_parse_stack(context);
test_parse_sized_deref(context);
+ test_parse_directives(context);
+ test_parse_bad_directive(context);
test_parse_errors(context);
}
diff --git a/tests/sema_test.c b/tests/sema_test.c
index a1946d9..b7d3805 100644
--- a/tests/sema_test.c
+++ b/tests/sema_test.c
@@ -24,9 +24,20 @@ 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_missing_main(struct TestContext* context)
+static void test_no_entry_is_ok(struct TestContext* context)
{
- check(context, !analyze_source("proc helper\n{\nsyscall\n}\n"));
+ // 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)
@@ -42,7 +53,9 @@ static void test_duplicate_across_kinds(struct TestContext* context)
void run_sema_tests(struct TestContext* context)
{
test_valid_program(context);
- test_missing_main(context);
+ test_no_entry_is_ok(context);
+ test_defined_entry(context);
+ test_undefined_entry(context);
test_duplicate_const(context);
test_duplicate_across_kinds(context);
}