aboutsummaryrefslogtreecommitdiff
path: root/tests/sema_test.c
blob: 095321deebffcc9dec97b6bd547a4ac1e926a49c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <stdbool.h>

#include "parser/parser.h"
#include "sema/sema.h"
#include "tests.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_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_references_resolve(context);
}