the interpreter implementation is now hidden behing the src/libnoja/noja.h facade. Also I wrote a disassembler that lets you build noja executables by specifying the bytecode directly in a text format

This commit is contained in:
cozis
2022-08-11 16:26:56 +02:00
parent 784914c7e6
commit f8cd57da43
68 changed files with 1260 additions and 263 deletions
View File
View File
+1
View File
@@ -0,0 +1 @@
ADD
+32
View File
@@ -0,0 +1,32 @@
0: PUSHLST 4
1: PUSHINT 0
2: PUSHINT 1
3: INSERT
4: PUSHINT 1
5: PUSHINT 2
6: INSERT
7: PUSHINT 2
8: PUSHINT 3
9: INSERT
10: PUSHINT 3
11: PUSHINT 4
12: INSERT
13: ASS [l]
14: POP 1
15: PUSHSTR [ items.
]
16: PUSHVAR [l]
17: PUSHVAR [count]
18: CALL 1 1
19: PUSHSTR [The list contains ]
20: PUSHVAR [print]
21: CALL 3 1
22: POP 1
23: PUSHSTR [.
]
24: PUSHVAR [l]
25: PUSHSTR [The list is: ]
26: PUSHVAR [print]
27: CALL 3 1
28: POP 1
29: RETURN 0
+1
View File
@@ -0,0 +1 @@
MUL
+1
View File
@@ -0,0 +1 @@
NOPE
+289
View File
@@ -0,0 +1,289 @@
#include <stdlib.h>
#include <stdbool.h>
#include "../src/utils/bpalloc.h"
#include "../src/assembler/assembler.h"
static void stopTesting_(const char *file, int line)
{
fprintf(stderr, "\n%s:%d :: Failed to set up test environment\n", file, line);
abort();
}
#define stopTestingIf(exp) \
do { \
if(exp) { \
stopTesting_(__FILE__, __LINE__); \
} \
} while(0);
static void testCase(int line, bool exp, const char *msg)
{
if(exp == false)
fprintf(stdout, "Test case failed (line %d) :: %s\n", line, msg);
else
fprintf(stdout, "Test case passed (line %d)\n", line);
}
typedef struct {
Opcode opcode;
int operand_count;
Operand *operands;
} StaticInstruction;
typedef struct {
int instr_count;
StaticInstruction *instr_list;
} StaticExecutable;
static Executable *buildExpectedExecutable(BPAlloc *alloc, StaticExecutable *static_exp_exe)
{
ExeBuilder *builder = ExeBuilder_New(alloc);
stopTestingIf(builder == NULL);
Error error;
Error_Init(&error);
for(int i = 0; i < static_exp_exe->instr_count; i += 1) {
StaticInstruction instr = static_exp_exe->instr_list[i];
bool ok = ExeBuilder_Append(builder, &error, instr.opcode, instr.operands, instr.operand_count, -1, -1);
stopTestingIf(ok == false);
}
Executable *exe = ExeBuilder_Finalize(builder, &error);
stopTestingIf(exe == NULL);
Error_Free(&error);
return exe;
}
int main()
{
struct {
int line;
bool fail;
const char *text;
StaticExecutable expected;
} tests[] = {
// Test the assembly of an empty string
// into an executable with no instructions.
{
.line = __LINE__,
.text = "",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 0,
.instr_list = NULL,
},
},
// Test the assembly of the simplest
// executable possible
{
.line = __LINE__,
.text = "POS",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 1,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
}
},
},
},
// Invalid opcode
{
.line = __LINE__,
.text = "GUCCI",
.fail = true,
},
// Label but no opcode
{
.line = __LINE__,
.text = "label:",
.fail = true,
},
// Basic executable with a label
{
.line = __LINE__,
.text = "label: POS",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 1,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
}
},
},
},
{
.line = __LINE__,
.text = " label : POS ",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 1,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
}
},
},
},
{
.line = __LINE__,
.text = " label : POS ; ",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 1,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
}
},
},
},
{
.line = __LINE__,
.text = " label0 : POS ; "
" label1 : NEG ; ",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 2,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
},
{
.opcode = OPCODE_NEG,
.operands = NULL,
.operand_count = 0,
},
},
},
},
{
.line = __LINE__,
.text = " label0 : POS ; "
" label1 : NEG ",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 2,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
},
{
.opcode = OPCODE_NEG,
.operands = NULL,
.operand_count = 0,
},
},
},
},
{
.line = __LINE__,
.text = " POS ; "
" label1 : NEG ",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 2,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
},
{
.opcode = OPCODE_NEG,
.operands = NULL,
.operand_count = 0,
},
},
},
},
{
.line = __LINE__,
.text = " label0 : POS ; "
" NEG ",
.fail = false,
.expected = (StaticExecutable) {
.instr_count = 2,
.instr_list = (StaticInstruction[]) {
{
.opcode = OPCODE_POS,
.operands = NULL,
.operand_count = 0,
},
{
.opcode = OPCODE_NEG,
.operands = NULL,
.operand_count = 0,
},
},
},
},
};
Error error;
char pool[4096];
for(size_t i = 0; i < sizeof(tests)/sizeof(tests[0]); i += 1) {
// Use an allocator backed by a static
// buffer to avoid reallocating a pool
// for each test.
BPAlloc *alloc = BPAlloc_Init3(pool, sizeof(pool), -1, NULL, NULL, NULL);
stopTestingIf(alloc == NULL);
Error_Init(&error);
Source *src = Source_FromString(NULL, tests[i].text, -1, &error);
stopTestingIf(src == NULL);
Executable *got_exe = assemble(src, &error);
if(tests[i].fail == true) {
testCase(tests[i].line, got_exe == NULL, "Managed to assemble an invalid source");
} else {
Executable *exp_exe = buildExpectedExecutable(alloc, &tests[i].expected);
testCase(tests[i].line, got_exe != NULL, "Failed to assemble a valid source");
/*
fprintf(stderr, "== Generated ==\n");
Executable_Dump(got_exe);
fprintf(stderr, "\n");
fprintf(stderr, "== Expected ==\n");
Executable_Dump(exp_exe);
*/
testCase(tests[i].line, Executable_Equiv(got_exe, exp_exe, stderr, "Executable diff :: "), "Invalid assemblation result");
Executable_Free(got_exe);
Executable_Free(exp_exe);
}
Source_Free(src);
Error_Free(&error);
BPAlloc_Free(alloc);
}
return 0;
}