diff options
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | examples/arithmetic.hdass | 2 | ||||
| -rw-r--r-- | examples/branch.hdass | 2 | ||||
| -rw-r--r-- | examples/call.hdass | 2 | ||||
| -rw-r--r-- | examples/exit_code.hdass | 2 | ||||
| -rw-r--r-- | examples/fibonacci.hdasm | 8 | ||||
| -rw-r--r-- | examples/greet.hdass | 2 | ||||
| -rw-r--r-- | examples/hello_world.hdass | 2 | ||||
| -rw-r--r-- | examples/loop_sum.hdass | 2 | ||||
| -rwxr-xr-x | scripts/docker_test.sh | 14 | ||||
| -rwxr-xr-x | scripts/run_suite.sh | 35 | ||||
| -rwxr-xr-x | scripts/test_examples.sh | 77 | ||||
| -rw-r--r-- | src/codegen/nasm.c | 16 | ||||
| -rw-r--r-- | src/parser/ast.c | 3 | ||||
| -rw-r--r-- | src/parser/ast.h | 11 | ||||
| -rw-r--r-- | src/parser/parser.c | 73 | ||||
| -rw-r--r-- | src/sema/sema.c | 12 | ||||
| -rw-r--r-- | tests/codegen_test.c | 53 | ||||
| -rw-r--r-- | tests/parser_test.c | 28 | ||||
| -rw-r--r-- | tests/sema_test.c | 19 |
20 files changed, 312 insertions, 55 deletions
@@ -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); } |
