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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
#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_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_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_stack_size_constant(context);
test_enum_struct_members(context);
test_references_resolve(context);
}
|