cleanups and more test cases

This commit is contained in:
cozis
2022-08-12 05:30:41 +02:00
parent f630830b3c
commit 18e936f0ad
145 changed files with 1445 additions and 234 deletions
-8
View File
@@ -1,8 +0,0 @@
all: tester
libnoja.a:
make -B -C .. tests/libnoja.a BUILD_MODE=RELEASE OUTDIR=tests
tester: tester.c libnoja.a
gcc $^ -o $@ -Wall -Wextra -lm -g
-201
View File
@@ -1,201 +0,0 @@
from random import randint
NODE_KINDS = ['expr', 'if-else', 'while', 'do-while', 'return', 'comp', 'func']
EXPR_KINDS = ['int', 'float', 'string', 'array', 'object']
def generate_ident():
return 'Apple'
def generate_string_expr_node():
return 'Hello, world!'
def generate_node(kind=None, depth=0):
if kind == None and depth > 3:
return {
'kind': 'comp',
'body': []
}
if kind == None:
kind = NODE_KINDS[randint(0, len(NODE_KINDS)-1)]
assert kind in NODE_KINDS
if kind == 'expr':
return generate_expr_node(None, depth+1)
elif kind == 'if-else':
branches = randint(1, 2)
assert branches in [1, 2]
if branches == 1:
return {
'kind': 'if-else',
'cond': generate_expr_node(None, depth+1),
'if-case': generate_node(None, depth+1)
}
else:
return {
'kind': 'if-else',
'cond': generate_expr_node(None, depth+1),
'if-case': generate_node(None, depth+1),
'else-case': generate_node(None, depth+1)
}
elif kind == 'while':
return {
'kind': 'while',
'cond': generate_expr_node(None, depth+1),
'body': generate_node(None, depth+1)
}
elif kind == 'do-while':
return {
'kind': 'do-while',
'cond': generate_expr_node(None, depth+1),
'body': generate_node(None, depth+1)
}
elif kind == 'return':
return {
'kind': 'return',
'value': generate_expr_node(None, depth+1)
}
elif kind == 'comp':
n = randint(0, 3)
return {
'kind': 'comp',
'body': [generate_node(None, depth+1) for i in range(n)]
}
elif kind == 'func':
argc = randint(0, 3)
return {
'kind': 'func',
'name': generate_ident(),
'args': [generate_ident() for i in range(argc)],
'body': generate_node(None, depth+1)
}
def generate_expr_node(expr_kind=None, depth=0):
if expr_kind == None:
expr_kind = EXPR_KINDS[randint(0, len(EXPR_KINDS)-1)]
assert expr_kind in EXPR_KINDS
if depth > 3:
expr_kind = ['int', 'float', 'string'][randint(0, 2)]
if expr_kind == 'int':
return {
'kind': 'expr',
'expr-kind': 'int',
'value': randint(0, 100)
}
elif expr_kind == 'float':
return {
'kind': 'expr',
'expr-kind': 'float',
'value': float(str(randint(0, 100)) + '.' + str(randint(0, 100)))
}
elif expr_kind == 'string':
return {
'kind': 'expr',
'expr-kind': 'string',
'value': generate_string_expr_node()
}
elif expr_kind == 'array':
n = randint(0, 3)
return {
'kind': 'expr',
'expr-kind': 'array',
'items': [generate_expr_node(None, depth+1) for i in range(n)]
}
elif expr_kind == 'object':
n = randint(0, 3)
return {
'kind': 'expr',
'expr-kind': 'object',
'items': [(generate_expr_node(None, depth+1), generate_expr_node(None, depth+1)) for i in range(n)]
}
def serialize_expr(node):
if node['expr-kind'] == 'int':
return str(node['value'])
elif node['expr-kind'] == 'float':
return str(node['value'])
elif node['expr-kind'] == 'string':
return '"' + node['value'] + '"'
elif node['expr-kind'] == 'array':
return '[' + ', '.join([serialize_expr(item) for item in node['items']]) + ']'
elif node['expr-kind'] == 'object':
return '{' + ', '.join([serialize_expr(item[0]) + ': ' + serialize_expr(item[1]) for item in node['items']]) + '}'
def serialize(node):
if node['kind'] == 'expr':
return serialize_expr(node) + ';'
elif node['kind'] == 'if-else':
if 'else-case' in node:
return 'if ' + serialize_expr(node['cond']) + ': ' + serialize(node['if-case']) + ' else ' + serialize(node['else-case'])
else:
return 'if ' + serialize_expr(node['cond']) + ': ' + serialize(node['if-case'])
elif node['kind'] == 'while':
return 'while ' + serialize_expr(node['cond']) + ': ' + serialize(node['body'])
elif node['kind'] == 'do-while':
return 'do ' + serialize(node['body']) + ' while ' + serialize_expr(node['cond']) + ';'
elif node['kind'] == 'return':
return 'return ' + serialize_expr(node['value']) + ';'
elif node['kind'] == 'comp':
return '{' + ''.join([serialize(stmt) for stmt in node['body']]) + '}'
elif node['kind'] == 'func':
return 'fun ' + node['name'] + '(' + ', '.join(node['args']) + ') ' + serialize(node['body'])
print(serialize(generate_node()))
+10
View File
@@ -0,0 +1,10 @@
#source
return none;
#bytecode
PUSHNNE;
RETURN 1;
RETURN 0;
+8
View File
@@ -0,0 +1,8 @@
#source
{}
#bytecode
RETURN 0;
+11
View File
@@ -0,0 +1,11 @@
#source
-1;
#bytecode
PUSHINT 1;
NEG;
POP 1;
RETURN 0;
+11
View File
@@ -0,0 +1,11 @@
#source
+1;
#bytecode
PUSHINT 1;
POS;
POP 1;
RETURN 0;
+11
View File
@@ -0,0 +1,11 @@
#source
not 1;
#bytecode
PUSHINT 1;
NOT;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 == 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
EQL;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 != 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
NQL;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 < 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
LSS;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 > 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
GRT;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 <= 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
LEQ;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 >= 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
GEQ;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 and 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
AND;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
1 or 2;
#bytecode
PUSHINT 1;
PUSHINT 2;
OR;
POP 1;
RETURN 0;
+16
View File
@@ -0,0 +1,16 @@
#source
1 + 2 * 3;
#bytecode
PUSHINT 1;
PUSHINT 2;
PUSHINT 3;
MUL;
ADD;
POP 1;
RETURN 0;
+16
View File
@@ -0,0 +1,16 @@
#source
1 * 2 + 3;
#bytecode
PUSHINT 1;
PUSHINT 2;
MUL;
PUSHINT 3;
ADD;
POP 1;
RETURN 0;
+16
View File
@@ -0,0 +1,16 @@
#source
(1 + 2) * 3;
#bytecode
PUSHINT 1;
PUSHINT 2;
ADD;
PUSHINT 3;
MUL;
POP 1;
RETURN 0;
+14
View File
@@ -0,0 +1,14 @@
#source
1 + (2 * 3);
#bytecode
PUSHINT 1;
PUSHINT 2;
PUSHINT 3;
MUL;
ADD;
POP 1;
RETURN 0;
+19
View File
@@ -0,0 +1,19 @@
#source
1 * 2 * 3 * 4;
#bytecode
PUSHINT 1;
PUSHINT 2;
MUL;
PUSHINT 3;
MUL;
PUSHINT 4;
MUL;
POP 1;
RETURN 0;
+16
View File
@@ -0,0 +1,16 @@
#source
1 * (2 * (3 * 4));
#bytecode
PUSHINT 1;
PUSHINT 2;
PUSHINT 3;
PUSHINT 4;
MUL;
MUL;
MUL;
POP 1;
RETURN 0;
+16
View File
@@ -0,0 +1,16 @@
#source
1 + 2 * 3 + 4;
#bytecode
PUSHINT 1;
PUSHINT 2;
PUSHINT 3;
MUL;
ADD;
PUSHINT 4;
ADD;
POP 1;
RETURN 0;
+19
View File
@@ -0,0 +1,19 @@
#source
1 * 2 + 3 * 4;
#bytecode
PUSHINT 1;
PUSHINT 2;
MUL;
PUSHINT 3;
PUSHINT 4;
MUL;
ADD;
POP 1;
RETURN 0;
+23
View File
@@ -0,0 +1,23 @@
#source
A - B - C;
A - B * C;
#bytecode
PUSHVAR "A";
PUSHVAR "B";
SUB;
PUSHVAR "C";
SUB;
POP 1;
PUSHVAR "A";
PUSHVAR "B";
PUSHVAR "C";
MUL;
SUB;
POP 1;
RETURN 0;
+17
View File
@@ -0,0 +1,17 @@
#source
A and B or C and D;
#bytecode
PUSHVAR "A";
PUSHVAR "B";
AND;
PUSHVAR "C";
PUSHVAR "D";
AND;
OR;
POP 1;
RETURN 0;
+17
View File
@@ -0,0 +1,17 @@
#source
X or Y and Z or W;
#bytecode
PUSHVAR "X";
PUSHVAR "Y";
PUSHVAR "Z";
AND;
OR;
PUSHVAR "W";
OR;
POP 1;
RETURN 0;
+46
View File
@@ -0,0 +1,46 @@
#source
1 + 2 > 3 + 4;
1 + 2 < 3 + 4;
1 + 2 >= 3 + 4;
1 + 2 <= 3 + 4;
#bytecode
PUSHINT 1;
PUSHINT 2;
ADD;
PUSHINT 3;
PUSHINT 4;
ADD;
GRT;
POP 1;
PUSHINT 1;
PUSHINT 2;
ADD;
PUSHINT 3;
PUSHINT 4;
ADD;
LSS;
POP 1;
PUSHINT 1;
PUSHINT 2;
ADD;
PUSHINT 3;
PUSHINT 4;
ADD;
GEQ;
POP 1;
PUSHINT 1;
PUSHINT 2;
ADD;
PUSHINT 3;
PUSHINT 4;
ADD;
LEQ;
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
[1];
#bytecode
PUSHLST 1;
PUSHINT 0;
PUSHINT 1;
INSERT;
POP 1;
RETURN 0;
+19
View File
@@ -0,0 +1,19 @@
#source
[true, false];
#bytecode
PUSHLST 2;
PUSHINT 0;
PUSHTRU;
INSERT;
PUSHINT 1;
PUSHFLS;
INSERT;
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
({'name': 'Francesco'});
#bytecode
PUSHMAP 1;
PUSHSTR "name";
PUSHSTR "Francesco";
INSERT;
POP 1;
RETURN 0;
+19
View File
@@ -0,0 +1,19 @@
#source
({'name': 'Francesco', 'age': 25});
#bytecode
PUSHMAP 2;
PUSHSTR "name";
PUSHSTR "Francesco";
INSERT;
PUSHSTR "age";
PUSHINT 25;
INSERT;
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
({name: 'Francesco'});
#bytecode
PUSHMAP 1;
PUSHSTR "name";
PUSHSTR "Francesco";
INSERT;
POP 1;
RETURN 0;
+19
View File
@@ -0,0 +1,19 @@
#source
({name: 'Francesco', age: 25});
#bytecode
PUSHMAP 2;
PUSHSTR "name";
PUSHSTR "Francesco";
INSERT;
PUSHSTR "age";
PUSHINT 25;
INSERT;
POP 1;
RETURN 0;
+14
View File
@@ -0,0 +1,14 @@
#source
({+name: 'Francesco'});
#bytecode
PUSHMAP 1;
PUSHVAR "name";
POS;
PUSHSTR "Francesco";
INSERT;
POP 1;
RETURN 0;
+14
View File
@@ -0,0 +1,14 @@
#source
({+name: 'Francesco'});
#bytecode
PUSHMAP 1;
PUSHVAR "name";
POS;
PUSHSTR "Francesco";
INSERT;
POP 1;
RETURN 0;
+11
View File
@@ -0,0 +1,11 @@
#source
var = none;
#bytecode
PUSHNNE;
ASS "var";
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
var[key] = none;
#bytecode
PUSHNNE;
PUSHVAR "var";
PUSHVAR "key";
INSERT2;
POP 1;
RETURN 0;
+17
View File
@@ -0,0 +1,17 @@
#source
A, B = func();
#bytecode
PUSHVAR "func";
CALL 0, 2;
ASS "B";
POP 1;
ASS "A";
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
var.key = none;
#bytecode
PUSHNNE;
PUSHVAR "var";
PUSHSTR "key";
INSERT2;
POP 1;
RETURN 0;
+11
View File
@@ -0,0 +1,11 @@
#source
func();
#bytecode
PUSHVAR "func";
CALL 0, 1;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
func(none);
#bytecode
PUSHNNE;
PUSHVAR "func";
CALL 1, 1;
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
func(true, false);
#bytecode
PUSHFLS;
PUSHTRU;
PUSHVAR "func";
CALL 2, 1;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
set[key];
#bytecode
PUSHVAR "set";
PUSHVAR "key";
SELECT;
POP 1;
RETURN 0;
+25
View File
@@ -0,0 +1,25 @@
#source
set["dog", "cow", "cat"];
#bytecode
PUSHVAR "set";
PUSHLST 3;
PUSHINT 0;
PUSHSTR "dog";
INSERT;
PUSHINT 1;
PUSHSTR "cow";
INSERT;
PUSHINT 2;
PUSHSTR "cat";
INSERT;
SELECT;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
set.key;
#bytecode
PUSHVAR "set";
PUSHSTR "key";
SELECT;
POP 1;
RETURN 0;
+12
View File
@@ -0,0 +1,12 @@
#source
if true:
{}
#bytecode
PUSHTRU;
JUMPIFNOTANDPOP end;
end:
RETURN 0;
+14
View File
@@ -0,0 +1,14 @@
#source
if true:
none;
#bytecode
PUSHTRU;
JUMPIFNOTANDPOP end;
PUSHNNE;
POP 1;
end:
RETURN 0;
+17
View File
@@ -0,0 +1,17 @@
#source
if true:
{}
else
{}
#bytecode
PUSHTRU;
JUMPIFNOTANDPOP end;
JUMP end;
end:
RETURN 0;
+20
View File
@@ -0,0 +1,20 @@
#source
if none:
true;
else
false;
#bytecode
PUSHNNE;
JUMPIFNOTANDPOP else;
PUSHTRU;
POP 1;
JUMP end;
else:
PUSHFLS;
POP 1;
end:
RETURN 0;
+14
View File
@@ -0,0 +1,14 @@
#source
while none:
{}
#bytecode
begin:
PUSHNNE;
JUMPIFNOTANDPOP end;
JUMP begin;
end:
RETURN 0;
+19
View File
@@ -0,0 +1,19 @@
#source
while none:
true;
false;
#bytecode
begin:
PUSHNNE;
JUMPIFNOTANDPOP end;
PUSHTRU;
POP 1;
JUMP begin;
end:
PUSHFLS;
POP 1;
RETURN 0;
+23
View File
@@ -0,0 +1,23 @@
#source
while none: {
true;
"Hello, world!";
}
false;
#bytecode
begin:
PUSHNNE;
JUMPIFNOTANDPOP end;
PUSHTRU;
POP 1;
PUSHSTR "Hello, world!";
POP 1;
JUMP begin;
end:
PUSHFLS;
POP 1;
RETURN 0;
+25
View File
@@ -0,0 +1,25 @@
#source
while none: {
true;
"Hello, world!";
break;
}
false;
#bytecode
begin:
PUSHNNE;
JUMPIFNOTANDPOP end;
PUSHTRU;
POP 1;
PUSHSTR "Hello, world!";
POP 1;
JUMP end;
JUMP begin;
end:
PUSHFLS;
POP 1;
RETURN 0;
+18
View File
@@ -0,0 +1,18 @@
#source
while none:
break;
false;
#bytecode
begin:
PUSHNNE;
JUMPIFNOTANDPOP end;
JUMP end;
JUMP begin;
end:
PUSHFLS;
POP 1;
RETURN 0;
+13
View File
@@ -0,0 +1,13 @@
#source
do
{}
while none;
#bytecode
begin:
PUSHNNE;
JUMPIFANDPOP begin;
RETURN 0;
+18
View File
@@ -0,0 +1,18 @@
#source
do
true;
while none;
false;
#bytecode
begin:
PUSHTRU;
POP 1;
PUSHNNE;
JUMPIFANDPOP begin;
PUSHFLS;
POP 1;
RETURN 0;
+21
View File
@@ -0,0 +1,21 @@
#source
do {
true;
"Hello, world!";
} while none;
false;
#bytecode
begin:
PUSHTRU;
POP 1;
PUSHSTR "Hello, world!";
POP 1;
PUSHNNE;
JUMPIFANDPOP begin;
PUSHFLS;
POP 1;
RETURN 0;
+24
View File
@@ -0,0 +1,24 @@
#source
do {
true;
"Hello, world!";
break;
} while none;
false;
#bytecode
begin:
PUSHTRU;
POP 1;
PUSHSTR "Hello, world!";
POP 1;
JUMP end;
PUSHNNE;
JUMPIFANDPOP begin;
end:
PUSHFLS;
POP 1;
RETURN 0;
+18
View File
@@ -0,0 +1,18 @@
#source
do
break;
while none;
false;
#bytecode
begin:
JUMP end;
PUSHNNE;
JUMPIFANDPOP begin;
end:
PUSHFLS;
POP 1;
RETURN 0;
+15
View File
@@ -0,0 +1,15 @@
#source
fun X() {}
#bytecode
PUSHFUN fun, 0;
ASS "X";
POP 1;
JUMP end;
fun:
RETURN 0;
end:
RETURN 0;
+17
View File
@@ -0,0 +1,17 @@
#source
fun X() "Hello, world!";
#bytecode
PUSHFUN fun, 0;
ASS "X";
POP 1;
JUMP end;
fun:
PUSHSTR "Hello, world!";
POP 1;
RETURN 0;
end:
RETURN 0;
+22
View File
@@ -0,0 +1,22 @@
#source
fun X() {
"Hello, world!";
return false;
}
#bytecode
PUSHFUN fun, 0;
ASS "X";
POP 1;
JUMP end;
fun:
PUSHSTR "Hello, world!";
POP 1;
PUSHFLS;
RETURN 1;
RETURN 0;
end:
RETURN 0;
+25
View File
@@ -0,0 +1,25 @@
#source
fun add(a, b) {
return a + b;
}
#bytecode
PUSHFUN fun, 2;
ASS "add";
POP 1;
JUMP end;
fun:
ASS "b";
POP 1;
ASS "a";
POP 1;
PUSHVAR "a";
PUSHVAR "b";
ADD;
RETURN 1;
RETURN 0;
end:
RETURN 0;
BIN
View File
Binary file not shown.
-555
View File
@@ -1,555 +0,0 @@
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <ctype.h>
#include "../src/noja/utils/source.h"
#include "../src/noja/compiler/parse.h"
#include "../src/noja/compiler/compile.h"
#include "../src/noja/common/executable.h"
#include "../src/noja/assembler/assemble.h"
//Regular text
#define BLK "\e[0;30m"
#define RED "\e[0;31m"
#define GRN "\e[0;32m"
#define YEL "\e[0;33m"
#define BLU "\e[0;34m"
#define MAG "\e[0;35m"
#define CYN "\e[0;36m"
#define WHT "\e[0;37m"
//Regular bold text
#define BBLK "\e[1;30m"
#define BRED "\e[1;31m"
#define BGRN "\e[1;32m"
#define BYEL "\e[1;33m"
#define BBLU "\e[1;34m"
#define BMAG "\e[1;35m"
#define BCYN "\e[1;36m"
#define BWHT "\e[1;37m"
//Regular underline text
#define UBLK "\e[4;30m"
#define URED "\e[4;31m"
#define UGRN "\e[4;32m"
#define UYEL "\e[4;33m"
#define UBLU "\e[4;34m"
#define UMAG "\e[4;35m"
#define UCYN "\e[4;36m"
#define UWHT "\e[4;37m"
//Regular background
#define BLKB "\e[40m"
#define REDB "\e[41m"
#define GRNB "\e[42m"
#define YELB "\e[43m"
#define BLUB "\e[44m"
#define MAGB "\e[45m"
#define CYNB "\e[46m"
#define WHTB "\e[47m"
//High intensty background
#define BLKHB "\e[0;100m"
#define REDHB "\e[0;101m"
#define GRNHB "\e[0;102m"
#define YELHB "\e[0;103m"
#define BLUHB "\e[0;104m"
#define MAGHB "\e[0;105m"
#define CYNHB "\e[0;106m"
#define WHTHB "\e[0;107m"
//High intensty text
#define HBLK "\e[0;90m"
#define HRED "\e[0;91m"
#define HGRN "\e[0;92m"
#define HYEL "\e[0;93m"
#define HBLU "\e[0;94m"
#define HMAG "\e[0;95m"
#define HCYN "\e[0;96m"
#define HWHT "\e[0;97m"
//Bold high intensity text
#define BHBLK "\e[1;90m"
#define BHRED "\e[1;91m"
#define BHGRN "\e[1;92m"
#define BHYEL "\e[1;93m"
#define BHBLU "\e[1;94m"
#define BHMAG "\e[1;95m"
#define BHCYN "\e[1;96m"
#define BHWHT "\e[1;97m"
//Reset
#define reset "\e[0m"
#define CRESET "\e[0m"
#define COLOR_RESET "\e[0m"
// NOTE: From https://gist.github.com/RabaDabaDoba/145049536f815903c79944599c6f952a
static void print_error(const char *type, Error *error)
{
if(type == NULL)
fprintf(stderr, RED "Error" CRESET);
else if(error->internal)
fprintf(stderr, RED "Internal Error" CRESET);
else
fprintf(stderr, RED "%s Error" CRESET, type);
fprintf(stderr, " :: %s.", error->message);
#ifdef DEBUG
if(error->file != NULL)
{
if(error->line > 0 && error->func != NULL)
fprintf(stderr, " (Reported in %s:%d in %s)", error->file, error->line, error->func);
else if(error->line > 0 && error->func == NULL)
fprintf(stderr, " (Reported in %s:%d)", error->file, error->line);
else if(error->line < 1 && error->func != NULL)
fprintf(stderr, " (Reported in %s in %s)", error->file, error->func);
}
#endif
fprintf(stderr, "\n");
}
typedef enum {
TokenType_END,
TokenType_TEXT,
TokenType_DIRECTIVE_SOURCE,
TokenType_DIRECTIVE_BYTECODE,
} TokenType;
typedef struct {
TokenType type;
size_t offset;
size_t length;
} Token;
static bool startsWithSourceDirective(const char *str, size_t len)
{
return (((7 < len && !isalpha(str[7])) || 7 == len)
&& str[0] == '#' && str[1] == 's'
&& str[2] == 'o' && str[3] == 'u'
&& str[4] == 'r' && str[5] == 'c'
&& str[6] == 'e');
}
static bool startsWithBytecodeDirective(const char *str, size_t len)
{
return (((9 < len && !isalpha(str[9])) || 9 == len)
&& str[0] == '#' && str[1] == 'b'
&& str[2] == 'y' && str[3] == 't'
&& str[4] == 'e' && str[5] == 'c'
&& str[6] == 'o' && str[7] == 'd'
&& str[8] == 'e');
}
static bool startsWithDirective(const char *str, size_t len)
{
return startsWithSourceDirective(str, len)
|| startsWithBytecodeDirective(str, len);
}
static Token nextToken(const char *str, size_t len, size_t *cur_)
{
size_t cur = *cur_;
while(cur < len && isspace(str[cur]))
cur += 1;
Token token;
if(cur == len) {
token.type = TokenType_END;
token.offset = cur;
token.length = 0;
} else if(startsWithSourceDirective(str + cur, len - cur)) {
token.type = TokenType_DIRECTIVE_SOURCE;
token.offset = cur;
cur += strlen("#source");
token.length = cur - token.offset;
} else if(startsWithBytecodeDirective(str + cur, len - cur)) {
token.type = TokenType_DIRECTIVE_BYTECODE;
token.offset = cur;
cur += strlen("#bytecode");
token.length = cur - token.offset;
} else {
token.type = TokenType_TEXT;
token.offset = cur;
while(1) {
// Consume all characters until
// the next '#'.
while(cur < len && str[cur] != '#')
cur += 1;
if(cur == len)
break;
// If it's the start of a directive,
// stop the loop. If it was not, skip
// the '#'.
if(startsWithDirective(str + cur, len - cur))
break;
cur += 1; // Skip the '#'
}
token.length = cur - token.offset;
}
*cur_ = cur;
return token;
}
typedef struct {
Source *source;
Source *bytecode;
} TestCase;
static bool parseTestCaseSource(Source *source, TestCase *testcase)
{
const char *str = Source_GetBody(source);
size_t len = Source_GetSize(source);
assert(str != NULL);
bool no_source = true;
size_t source_offset;
size_t source_length;
bool no_bytecode = true;
size_t bytecode_offset;
size_t bytecode_length;
size_t cur = 0;
while(1) {
Token token = nextToken(str, len, &cur);
if(token.type == TokenType_END)
break;
if(token.type == TokenType_TEXT) {
fprintf(stderr, RED "Error" CRESET " :: Expected a directive at offset %ld\n", token.offset);
return false;
}
assert(token.type == TokenType_DIRECTIVE_SOURCE
|| token.type == TokenType_DIRECTIVE_BYTECODE);
Token directive = token;
size_t directive_body_offset;
size_t directive_body_length;
size_t backup = cur;
token = nextToken(str, len, &cur);
switch(token.type) {
case TokenType_END:
case TokenType_DIRECTIVE_SOURCE:
case TokenType_DIRECTIVE_BYTECODE:
// Right after the previous directive
// there's either the end of the file
// or another directive. We assume
// that the directive is followed by
// and empty string.
directive_body_offset = token.offset;
directive_body_length = 0;
// Put the cursor back so that this
// token is tokenized again at the
// start of the next iteration.
cur = backup;
break;
case TokenType_TEXT:
directive_body_offset = token.offset;
directive_body_length = token.length;
break;
}
if(directive.type == TokenType_DIRECTIVE_SOURCE) {
if(no_source == false) {
fprintf(stderr, RED "Error" CRESET " :: #source directive specified twice.\n");
return false;
}
no_source = false;
source_offset = directive_body_offset;
source_length = directive_body_length;
} else {
assert(directive.type == TokenType_DIRECTIVE_BYTECODE);
if(no_bytecode == false) {
fprintf(stderr, RED "Error" CRESET " :: #bytecode directive specified twice.\n");
return false;
}
no_bytecode = false;
bytecode_offset = directive_body_offset;
bytecode_length = directive_body_length;
}
}
if(no_source == true && no_bytecode == true) {
fprintf(stderr, RED "Error" CRESET " :: Missing both #source and #bytecode directives.\n");
return false;
}
if(no_source == true) {
fprintf(stderr, RED "Error" CRESET " :: Missing #source directive.\n");
return false;
}
if(no_bytecode == true) {
fprintf(stderr, RED "Error" CRESET " :: Missing #bytecode directive.\n");
return false;
}
{
Error error;
Error_Init(&error);
testcase->source = Source_FromString("<input>", str + source_offset, source_length, &error);
if(testcase->source == NULL) {
print_error(NULL, &error);
Error_Free(&error);
return false;
}
testcase->bytecode = Source_FromString("<output>", str + bytecode_offset, bytecode_length, &error);
if(testcase->bytecode == NULL) {
print_error(NULL, &error);
Source_Free(testcase->source);
Error_Free(&error);
return false;
}
Error_Free(&error);
}
return true;
}
static Executable *build(Source *src)
{
Executable *exe;
// Create a bump-pointer allocator to hold the AST.
BPAlloc *alloc = BPAlloc_Init(-1);
if(alloc == NULL)
{
fprintf(stderr, RED "Internal Error" CRESET " :: Couldn't allocate bump-pointer allocator to hold the AST.\n");
return 0;
}
Error error;
Error_Init(&error);
// NOTE: The AST is stored in the BPAlloc. It's
// lifetime is the same as the pool.
AST *ast = parse(src, alloc, &error);
if(ast == NULL)
{
assert(error.occurred);
print_error("Parsing", &error);
Error_Free(&error);
BPAlloc_Free(alloc);
return 0;
}
exe = compile(ast, alloc, &error);
// We're done with the AST, independently from
// the compilation result.
BPAlloc_Free(alloc);
if(exe == NULL)
{
assert(error.occurred);
print_error("Compilation", &error);
Error_Free(&error);
return 0;
}
Error_Free(&error);
return exe;
}
typedef enum {
TestResult_PASSED,
TestResult_FAILED,
TestResult_ABORT,
} TestResult;
static TestResult runTest(const char *file)
{
Source *source;
{
Error error;
Error_Init(&error);
source = Source_FromFile(file, &error);
if(source == NULL) {
print_error(NULL, &error);
Error_Free(&error);
return TestResult_ABORT;
}
Error_Free(&error);
}
TestCase testcase;
{
if(!parseTestCaseSource(source, &testcase)) {
Source_Free(source);
return TestResult_ABORT;
}
}
Source_Free(source);
Executable *exe1 = build(testcase.source);
if(exe1 == NULL) {
Source_Free(testcase.source);
Source_Free(testcase.bytecode);
return TestResult_ABORT;
}
Executable *exe2;
{
Error error;
Error_Init(&error);
exe2 = assemble(testcase.bytecode, &error);
if(exe2 == NULL) {
print_error("Assemblation", &error);
Error_Free(&error);
Executable_Free(exe1);
Source_Free(testcase.source);
Source_Free(testcase.bytecode);
return TestResult_ABORT;
}
Error_Free(&error);
}
bool passed = Executable_Equiv(exe1, exe2, stderr, " " WHT "Log" CRESET " :: ");
Executable_Free(exe1);
Executable_Free(exe2);
Source_Free(testcase.source);
Source_Free(testcase.bytecode);
return passed
? TestResult_PASSED
: TestResult_FAILED;
}
static void runTestSuite(const char *folder)
{
DIR *handle = opendir(folder);
if(handle == NULL) {
fprintf(stderr, RED "Error" CRESET " :: Failed to open folder '%s'.\n", folder);
return;
}
size_t total_tests = 0;
size_t passed_tests = 0;
size_t failed_tests = 0;
size_t aborted_tests = 0;
size_t skipped_files = 0;
size_t folder_len = strlen(folder);
// If the folder path ends with a "/",
// but it's not the first character,
// then pop it.
if(folder_len > 0 && folder[folder_len-1] == '/')
folder_len -= 1;
// Now we know for sure that the provided
// folder needs a trailing "/".
char test_path[1024];
// Make sure the buffer can contain the
// folder's name (plus the trailing "/"
// and the null byte).
if(folder_len+2 > sizeof(test_path)) {
fprintf(stderr, RED "Error" CRESET " :: Suite folder name is too long\n");
return;
}
memcpy(test_path, folder, folder_len);
test_path[folder_len] = '/';
test_path[folder_len+1] = '\0';
// Now count the "/".
folder_len += 1;
struct dirent *dir;
while((dir = readdir(handle)) != NULL) {
const char *name = dir->d_name;
size_t name_len = strlen(name);
if(dir->d_type != DT_REG) {
fprintf(stderr, " " WHT "Log" CRESET " :: Skipping '%s' (not a regular file).\n", name);
skipped_files += 1;
continue;
}
if(folder_len + name_len >= sizeof(test_path)) {
fprintf(stderr, " " WHT "Log" CRESET " :: Skipping '%s' (path is too long).\n", name);
skipped_files += 1;
continue;
}
// Remove the previous file's name
// by truncating the parent folder
// name with a null byte.
test_path[folder_len] = '\0';
strcat(test_path, name);
TestResult res = runTest(test_path);
switch(res) {
case TestResult_PASSED:
fprintf(stdout, " " BLU "Test" CRESET " :: Passed (%s)\n", name);
passed_tests += 1;
break;
case TestResult_FAILED:
fprintf(stdout, " " YEL "Test" CRESET " :: Failed (%s)\n", name);
failed_tests += 1;
break;
case TestResult_ABORT:
fprintf(stdout, " " RED "Test" CRESET " :: Aborted (%s)\n", name);
aborted_tests += 1;
break;
}
total_tests += 1;
}
fprintf(stdout, "\n");
fprintf(stdout, "@-----SUMMARY-----@\n");
fprintf(stdout, "| Total : %-3ld |\n", total_tests);
fprintf(stdout, "| Passed : %-3ld |\n", passed_tests);
fprintf(stdout, "| Failed : %-3ld |\n", failed_tests);
fprintf(stdout, "| Aborted : %-3ld |\n", aborted_tests);
fprintf(stdout, "@-----------------@\n");
closedir(handle);
}
int main(int argc, char **argv)
{
if(argc < 2) {
fprintf(stderr, RED "Error" CRESET " :: No test suite folder name was provided.\n");
fprintf(stderr, WHT "Usage" CRESET " :: %s <path>\n", argv[0]);
return -1;
}
const char *folder = argv[1];
runTestSuite(folder);
return 0;
}