test/lower/lower-test.rad 7.2 KiB raw
1
//! Standalone lowering pass test runner.
2
//!
3
//! Runs a single test that compares generated IL against expected output.
4
//! The test path should be a .rad source file; the expected .ril file is
5
//! derived by replacing the extension.
6
7
use std::io;
8
use std::mem;
9
use std::sys;
10
use std::sys::unix;
11
use std::lang::alloc;
12
use std::lang::ast;
13
use std::lang::il;
14
use std::lang::il::printer;
15
use std::lang::parser;
16
use std::lang::scanner;
17
use std::lang::module;
18
use std::lang::resolver;
19
use std::lang::strings;
20
use std::lang::lower;
21
22
/// Buffer size for reading source files (4 KB).
23
const SOURCE_BUF_SIZE: u32 = 4096;
24
/// Buffer size for reading expected IL files (4 KB).
25
const EXPECTED_BUF_SIZE: u32 = 4096;
26
/// Buffer size for generated IL output (4 KB).
27
const OUTPUT_BUF_SIZE: u32 = 4096;
28
/// Arena size for AST/IL allocations (64 KB).
29
const ARENA_SIZE: u32 = 65536;
30
/// Maximum path length for expected IL file path.
31
const MAX_PATH_LEN: u32 = 256;
32
33
/// String pool.
34
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
35
36
/// Strip comment from a line. Returns sub-slice up to ';', trimmed of trailing whitespace.
37
fn stripLine(line: *[u8]) -> *[u8] {
38
    let mut end: u32 = 0;
39
    let mut i: u32 = 0;
40
41
    while i < line.len and line[i] != ';' {
42
        if line[i] != ' ' and line[i] != '\t' {
43
            end = i + 1;
44
        }
45
        i += 1;
46
    }
47
    return &line[..end];
48
}
49
50
/// Get next line from string at offset. Returns the line and updates offset past newline.
51
fn nextLine(s: *[u8], offset: *mut u32) -> *[u8] {
52
    let start = *offset;
53
    let mut i = start;
54
55
    while i < s.len and s[i] != '\n' {
56
        i += 1;
57
    }
58
    let line = &s[start..i];
59
    if i < s.len {
60
        *offset = i + 1;
61
    } else {
62
        *offset = i;
63
    }
64
    return line;
65
}
66
67
/// Compare two strings ignoring comments, line by line.
68
fn stringsEqual(a: *[u8], b: *[u8]) -> bool {
69
    let mut ai: u32 = 0;
70
    let mut bi: u32 = 0;
71
72
    while ai < a.len or bi < b.len {
73
        let mut aLine = "";
74
        while ai < a.len and aLine.len == 0 {
75
            aLine = stripLine(nextLine(a, &mut ai));
76
        }
77
        let mut bLine = "";
78
        while bi < b.len and bLine.len == 0 {
79
            bLine = stripLine(nextLine(b, &mut bi));
80
        }
81
        if not mem::eq(aLine, bLine) {
82
            return false;
83
        }
84
    }
85
    return true;
86
}
87
88
/// Print diff between expected and actual output.
89
fn printDiff(testName: *[u8], expected: *[u8], actual: *[u8]) {
90
    io::printLn("\n;; Expected");
91
    io::print(expected);
92
    io::print("\n");
93
94
    io::printLn(";; Actual");
95
    io::print(actual);
96
    io::print("\n");
97
}
98
99
/// Derive the expected .ril path from a .rad source path. Replaces the .rad
100
/// extension with .ril. The output is null-terminated for use with syscalls.
101
fn deriveExpectedPath(sourcePath: *[u8], buf: *mut [u8]) -> ?*[u8] {
102
    let len = sourcePath.len;
103
    if len < 4 {
104
        return nil;
105
    }
106
    // Check for .rad extension.
107
    let extStart = len - 4; // TODO: language support for additions in ranges.
108
    if not mem::eq(&sourcePath[extStart..len], ".rad") {
109
        return nil;
110
    }
111
    if len + 1 > buf.len {
112
        return nil;
113
    }
114
    // Copy full path then overwrite extension with null terminator.
115
    try mem::copy(buf, sourcePath) catch {
116
        return nil;
117
    };
118
    // TODO: Rewrite this once we fix the compiler bug and add support for
119
    // additions in ranges.
120
    buf[len - 3] = 'r';
121
    buf[len - 2] = 'i';
122
    buf[len - 1] = 'l';
123
    buf[len] = 0;
124
125
    return &buf[..len];
126
}
127
128
/// Run a single lowering test case. Returns `true` on success.
129
fn runTest(sourcePath: *[u8]) -> bool {
130
    // Buffers for file I/O.
131
    let mut sourceBuf: [u8; SOURCE_BUF_SIZE] = undefined;
132
    let mut expectedBuf: [u8; EXPECTED_BUF_SIZE] = undefined;
133
    let mut outputBuf: [u8; OUTPUT_BUF_SIZE] = undefined;
134
    let mut expectedPathBuf: [u8; MAX_PATH_LEN] = undefined;
135
136
    // Parser and resolver storage.
137
    let mut astArenaStorage: [u8; ARENA_SIZE] = undefined;
138
    let mut ilArenaStorage: [u8; ARENA_SIZE] = undefined;
139
    let mut printArenaStorage: [u8; ARENA_SIZE] = undefined;
140
    let mut resolverArenaStorage: [u8; ARENA_SIZE] = undefined;
141
    let mut moduleArenaStorage: [u8; 1024] = undefined;
142
    let mut nodeDataStorage: [resolver::NodeData; 256] = undefined;
143
    let mut errorStorage: [resolver::Error; 4] = undefined;
144
    let mut pkgScope: resolver::Scope = undefined;
145
    let mut moduleEntries: [module::ModuleEntry; 4] = undefined;
146
147
    // Derive expected path from source path.
148
    let expectedPath = deriveExpectedPath(sourcePath, &mut expectedPathBuf[..]) else {
149
        io::print("error: Invalid source path (must end in .rad): ");
150
        io::printLn(sourcePath);
151
        return false;
152
    };
153
    // Read source file.
154
    let source = unix::readFile(sourcePath, &mut sourceBuf[..]) else {
155
        io::print("error: Could not read source: ");
156
        io::printLn(sourcePath);
157
        return false;
158
    };
159
    // Read expected IL.
160
    let expected = unix::readFile(expectedPath, &mut expectedBuf[..]) else {
161
        io::print("error: Could not read expected: ");
162
        io::printLn(expectedPath);
163
        return false;
164
    };
165
    // Parse source.
166
    let mut astArena = ast::nodeArena(&mut astArenaStorage[..]);
167
    let root = try parser::parse(scanner::SourceLoc::String, source, &mut astArena, &mut STRING_POOL) catch {
168
        io::printLn("error: Parsing failed");
169
        return false;
170
    };
171
    // Run resolver.
172
    let mut moduleArena = ast::nodeArena(&mut moduleArenaStorage[..]);
173
    let mut moduleGraph = module::moduleGraph(&mut moduleEntries[..], &mut STRING_POOL, &mut moduleArena);
174
    let storage = resolver::ResolverStorage {
175
        arena: alloc::new(&mut resolverArenaStorage[..]),
176
        nodeData: &mut nodeDataStorage[..],
177
        pkgScope: &mut pkgScope,
178
        errors: &mut errorStorage[..],
179
    };
180
    let config = resolver::Config { buildTest: false };
181
    let mut res = resolver::resolver(storage, config);
182
    let diag = try resolver::resolveModuleRoot(&mut res, root) catch {
183
        io::printLn("error: Resolver failed");
184
        return false;
185
    };
186
    if not resolver::success(&diag) {
187
        io::printLn("error: Resolver failed");
188
        return false;
189
    }
190
    // Lower to IL.
191
    let mut ilArena = alloc::new(&mut ilArenaStorage[..]);
192
    let program = try lower::lower(&res, root, "test", &mut ilArena) catch err {
193
        io::print("error: Lowering failed: ");
194
        lower::printError(err);
195
        io::printLn("");
196
        return false;
197
    };
198
    // Print IL to buffer.
199
    let mut printArena = alloc::new(&mut printArenaStorage[..]);
200
    let actual = printer::printProgramToBuffer(&program, &mut printArena, &mut outputBuf[..]);
201
202
    // Compare ignoring comments.
203
    if not stringsEqual(actual, expected) {
204
        io::printLn("FAILED");
205
        printDiff(sourcePath, expected, actual);
206
        return false;
207
    }
208
    io::printLn("ok");
209
210
    return true;
211
}
212
213
/// Run a single test specified as an argument.
214
@default fn main(env: *sys::Env) -> i32 {
215
    let args = env.args;
216
217
    if args.len != 2 {
218
        io::printError("error: expected test name and file path");
219
        return 1;
220
    }
221
    let sourcePath = args[1];
222
    io::print("test ");
223
    io::print(sourcePath);
224
    io::print(" ... ");
225
226
    if runTest(sourcePath) {
227
        return 0;
228
    } else {
229
        return 1;
230
    }
231
}