new testing infrastructure
This commit is contained in:
Executable → Regular
@@ -1,4 +1,4 @@
|
||||
make -B BUILD_MODE=COVERAGE CC=gcc
|
||||
./test tests/suite
|
||||
./test tests/compiler/ tests/runtime/
|
||||
make report
|
||||
firefox report/index.html
|
||||
@@ -107,12 +107,57 @@ static bool parseStringOperand(Context *ctx, Error *error, BPAlloc *alloc, Opera
|
||||
assert(ctx->cur < ctx->len && ctx->str[ctx->cur] == '"');
|
||||
ctx->cur += 1;
|
||||
|
||||
size_t literal_offset = ctx->cur;
|
||||
char buffer[1024];
|
||||
size_t used = 0;
|
||||
|
||||
// For now do a dumb copy into the buffer
|
||||
// without considering special characters.
|
||||
while(ctx->cur < ctx->len && ctx->str[ctx->cur] != '"')
|
||||
ctx->cur += 1;
|
||||
do {
|
||||
|
||||
size_t offset = ctx->cur;
|
||||
while (ctx->cur < ctx->len && ctx->str[ctx->cur] != '\\' && ctx->str[ctx->cur] != '"')
|
||||
ctx->cur++;
|
||||
size_t length = ctx->cur - offset;
|
||||
|
||||
if (used + length > sizeof(buffer)) {
|
||||
*ctx->error_offset = ctx->cur;
|
||||
Error_Report(error, ErrorType_INTERNAL, "Buffer is too short to hold string literal");
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer + used, ctx->str + offset, length);
|
||||
used += length;
|
||||
|
||||
if (ctx->cur < ctx->len && ctx->str[ctx->cur] == '\\') {
|
||||
|
||||
ctx->cur++;
|
||||
if (ctx->cur == ctx->len) {
|
||||
*ctx->error_offset = ctx->cur;
|
||||
Error_Report(error, ErrorType_SYNTAX, "End of source inside a string literal");
|
||||
return false;
|
||||
}
|
||||
|
||||
char c;
|
||||
switch (ctx->str[ctx->cur]) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case '"': c = '"'; break;
|
||||
case '\\': c = '\\'; break;
|
||||
default:
|
||||
*ctx->error_offset = ctx->cur;
|
||||
Error_Report(error, ErrorType_SYNTAX, "End of source inside a string literal");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (used + 1 > sizeof(buffer)) {
|
||||
*ctx->error_offset = ctx->cur;
|
||||
Error_Report(error, ErrorType_INTERNAL, "Buffer is too short to hold string literal");
|
||||
return false;
|
||||
}
|
||||
buffer[used++] = c;
|
||||
|
||||
ctx->cur++;
|
||||
}
|
||||
|
||||
} while (ctx->cur < ctx->len && ctx->str[ctx->cur] != '"');
|
||||
|
||||
if(ctx->cur == ctx->len) {
|
||||
*ctx->error_offset = ctx->cur;
|
||||
@@ -120,21 +165,18 @@ static bool parseStringOperand(Context *ctx, Error *error, BPAlloc *alloc, Opera
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t literal_length = ctx->cur - literal_offset;
|
||||
|
||||
// Skip the ending double quotes.
|
||||
assert(ctx->cur < ctx->len && ctx->str[ctx->cur] == '"');
|
||||
ctx->cur += 1;
|
||||
|
||||
char *copy = BPAlloc_Malloc(alloc, literal_length+1);
|
||||
char *copy = BPAlloc_Malloc(alloc, used+1);
|
||||
if(copy == NULL) {
|
||||
*ctx->error_offset = ctx->cur;
|
||||
Error_Report(error, ErrorType_INTERNAL, "No memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(copy, ctx->str + literal_offset, literal_length);
|
||||
copy[literal_length] = '\0';
|
||||
memcpy(copy, buffer, used);
|
||||
copy[used] = '\0';
|
||||
|
||||
op->type = OPTP_STRING;
|
||||
op->as_string = copy;
|
||||
|
||||
@@ -115,7 +115,7 @@ static int bin_print(Runtime *runtime, Object **argv, unsigned int argc, Object
|
||||
UNUSED(error);
|
||||
|
||||
for(int i = 0; i < (int) argc; i += 1)
|
||||
Object_Print(argv[i], stdout);
|
||||
Object_Print(argv[i], Runtime_GetOutputStream(runtime));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ static int bin_input(Runtime *runtime, Object **argv, unsigned int argc, Object
|
||||
|
||||
while(1)
|
||||
{
|
||||
char c = getc(stdin);
|
||||
char c = getc(Runtime_GetInputStream(runtime));
|
||||
|
||||
if(c == '\n')
|
||||
break;
|
||||
|
||||
+27
-1
@@ -82,6 +82,10 @@ struct xRuntime {
|
||||
Heap *heap;
|
||||
TimingTable *timing;
|
||||
|
||||
FILE *stdin;
|
||||
FILE *stdout;
|
||||
FILE *stderr;
|
||||
|
||||
FailedFrame failed_frame;
|
||||
};
|
||||
|
||||
@@ -112,12 +116,15 @@ RuntimeConfig Runtime_GetDefaultConfigs()
|
||||
.stack = 1024,
|
||||
.callback = { .func = NULL, .data = NULL },
|
||||
.time = false,
|
||||
.stdin = stdin,
|
||||
.stdout = stdout,
|
||||
.stderr = stderr,
|
||||
};
|
||||
}
|
||||
|
||||
Stack *Runtime_GetStack(Runtime *runtime)
|
||||
{
|
||||
return Stack_Copy(runtime->stack, 1);
|
||||
return runtime->stack;
|
||||
}
|
||||
|
||||
Heap *Runtime_GetHeap(Runtime *runtime)
|
||||
@@ -353,6 +360,10 @@ Runtime *Runtime_New(RuntimeConfig config)
|
||||
runtime->frame = NULL;
|
||||
runtime->depth = 0;
|
||||
|
||||
runtime->stdin = config.stdin;
|
||||
runtime->stderr = config.stderr;
|
||||
runtime->stdout = config.stdout;
|
||||
|
||||
return runtime;
|
||||
}
|
||||
|
||||
@@ -367,6 +378,21 @@ void Runtime_Free(Runtime *runtime)
|
||||
free(runtime);
|
||||
}
|
||||
|
||||
FILE *Runtime_GetErrorStream(Runtime *runtime)
|
||||
{
|
||||
return runtime->stderr;
|
||||
}
|
||||
|
||||
FILE *Runtime_GetOutputStream(Runtime *runtime)
|
||||
{
|
||||
return runtime->stdout;
|
||||
}
|
||||
|
||||
FILE *Runtime_GetInputStream(Runtime *runtime)
|
||||
{
|
||||
return runtime->stdin;
|
||||
}
|
||||
|
||||
_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj)
|
||||
{
|
||||
ASSERT(runtime != NULL);
|
||||
|
||||
+6
-1
@@ -51,12 +51,17 @@ typedef struct {
|
||||
bool time;
|
||||
size_t heap;
|
||||
size_t stack;
|
||||
FILE *stdin;
|
||||
FILE *stderr;
|
||||
FILE *stdout;
|
||||
RuntimeCallback callback;
|
||||
} RuntimeConfig;
|
||||
|
||||
Runtime* Runtime_New(RuntimeConfig config);
|
||||
void Runtime_Free(Runtime *runtime);
|
||||
|
||||
FILE *Runtime_GetErrorStream(Runtime *runtime);
|
||||
FILE *Runtime_GetInputStream(Runtime *runtime);
|
||||
FILE *Runtime_GetOutputStream(Runtime *runtime);
|
||||
bool Runtime_plugDefaultBuiltins(Runtime *runtime, Error *error);
|
||||
bool Runtime_plugBuiltinsFromString(Runtime *runtime, const char *string, Error *error);
|
||||
bool Runtime_plugBuiltinsFromFile(Runtime *runtime, const char *file, Error *error);
|
||||
|
||||
+2
-52
@@ -40,11 +40,6 @@ struct xStack {
|
||||
void *body[];
|
||||
};
|
||||
|
||||
_Bool Stack_IsReadOnlyCopy(Stack *s)
|
||||
{
|
||||
return (uintptr_t) s & (uintptr_t) 1;
|
||||
}
|
||||
|
||||
void *Stack_New(int size)
|
||||
{
|
||||
if(size < 0)
|
||||
@@ -63,18 +58,10 @@ void *Stack_New(int size)
|
||||
return s;
|
||||
}
|
||||
|
||||
static Stack *unmark(Stack *s)
|
||||
{
|
||||
return (Stack*) ((intptr_t) s & ~ (intptr_t) 1);
|
||||
}
|
||||
|
||||
void *Stack_Top(Stack *s, int n)
|
||||
{
|
||||
ASSERT(n <= 0);
|
||||
|
||||
// Remove readonly bit.
|
||||
s = unmark(s);
|
||||
|
||||
if(s->used == 0)
|
||||
return NULL;
|
||||
|
||||
@@ -88,12 +75,6 @@ void **Stack_TopRef(Stack *s, int n)
|
||||
{
|
||||
ASSERT(n <= 0);
|
||||
|
||||
if(Stack_IsReadOnlyCopy(s))
|
||||
return NULL;
|
||||
|
||||
// Remove readonly bit.
|
||||
s = unmark(s);
|
||||
|
||||
if(s->used == 0)
|
||||
return NULL;
|
||||
|
||||
@@ -106,9 +87,6 @@ void **Stack_TopRef(Stack *s, int n)
|
||||
|
||||
_Bool Stack_Pop(Stack *s, unsigned int n)
|
||||
{
|
||||
if(Stack_IsReadOnlyCopy(s))
|
||||
return 0;
|
||||
|
||||
if(s->used < n)
|
||||
return 0;
|
||||
|
||||
@@ -121,9 +99,6 @@ _Bool Stack_Push(Stack *s, void *item)
|
||||
ASSERT(s != NULL);
|
||||
ASSERT(item != NULL);
|
||||
|
||||
if(Stack_IsReadOnlyCopy(s))
|
||||
return 0;
|
||||
|
||||
if(s->used == s->size)
|
||||
return 0;
|
||||
|
||||
@@ -132,51 +107,26 @@ _Bool Stack_Push(Stack *s, void *item)
|
||||
return 1;
|
||||
}
|
||||
|
||||
Stack *Stack_Copy(Stack *s, _Bool readonly)
|
||||
Stack *Stack_Copy(Stack *s)
|
||||
{
|
||||
if(Stack_IsReadOnlyCopy(s))
|
||||
{
|
||||
// Reference is readonly,
|
||||
// so the copy must be
|
||||
// readonly.
|
||||
readonly = 1;
|
||||
|
||||
// Remove readonly bit.
|
||||
s = unmark(s);
|
||||
}
|
||||
|
||||
s->refs += 1;
|
||||
|
||||
if(readonly)
|
||||
return (Stack*) ((uintptr_t) s | (intptr_t) 1);
|
||||
else
|
||||
return s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void Stack_Free(Stack *s)
|
||||
{
|
||||
// Remove readonly bit.
|
||||
s = unmark(s);
|
||||
|
||||
s->refs -= 1;
|
||||
ASSERT(s->refs >= 0);
|
||||
|
||||
if(s->refs == 0)
|
||||
free(s);
|
||||
}
|
||||
|
||||
unsigned int Stack_Size(Stack *s)
|
||||
{
|
||||
// Remove readonly bit.
|
||||
s = unmark(s);
|
||||
|
||||
return s->used;
|
||||
}
|
||||
|
||||
unsigned int Stack_Capacity(Stack *s)
|
||||
{
|
||||
// Remove readonly bit.
|
||||
s = unmark(s);
|
||||
|
||||
return s->size;
|
||||
}
|
||||
@@ -37,8 +37,7 @@ _Bool Stack_Pop(Stack *s, unsigned int n);
|
||||
_Bool Stack_Push(Stack *s, void *item);
|
||||
void Stack_Free(Stack *s);
|
||||
unsigned int Stack_Size(Stack *s);
|
||||
Stack *Stack_Copy(Stack *s, _Bool readonly);
|
||||
Stack *Stack_Copy(Stack *s);
|
||||
void **Stack_TopRef(Stack *s, int n);
|
||||
unsigned int Stack_Capacity(Stack *s);
|
||||
_Bool Stack_IsReadOnlyCopy(Stack *s);
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "error.h"
|
||||
|
||||
const char *getErrorString(ErrorID error)
|
||||
{
|
||||
switch (error) {
|
||||
case ErrorID_VOID: return "No error";
|
||||
case ErrorID_OUTOFMEMORY: return "Out of memory";
|
||||
case ErrorID_DUPLFIELD: return "Duplicate field";
|
||||
case ErrorID_BADSYNTAX_NOAT: return "Missing '@' character";
|
||||
case ErrorID_BADSYNTAX_NONAME: return "Missing field name";
|
||||
case ErrorID_BADSYNTAX_NOFIELD: return "Missing field";
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef TEST_ERROR_H
|
||||
#define TEST_ERROR_H
|
||||
|
||||
typedef enum {
|
||||
ErrorID_VOID,
|
||||
ErrorID_OUTOFMEMORY,
|
||||
ErrorID_DUPLFIELD,
|
||||
ErrorID_BADSYNTAX_NONAME,
|
||||
ErrorID_BADSYNTAX_NOAT,
|
||||
ErrorID_BADSYNTAX_NOFIELD,
|
||||
} ErrorID;
|
||||
|
||||
const char *getErrorString(ErrorID error);
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,140 @@
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "field.h"
|
||||
|
||||
static void freeFieldName(NojaTestField *field)
|
||||
{
|
||||
if (field->name != field->name_maybe)
|
||||
free(field->name);
|
||||
}
|
||||
|
||||
static void freeFieldBody(NojaTestField *field)
|
||||
{
|
||||
if (field->body != field->body_maybe)
|
||||
free(field->body);
|
||||
}
|
||||
|
||||
void freeField(NojaTestField *field)
|
||||
{
|
||||
freeFieldName(field);
|
||||
freeFieldBody(field);
|
||||
}
|
||||
|
||||
static ErrorID setFieldName(NojaTestField *field, const char *str, size_t len)
|
||||
{
|
||||
if (len < sizeof(field->name_maybe))
|
||||
field->name = field->name_maybe;
|
||||
else {
|
||||
field->name = malloc(len+1);
|
||||
if (field->name == NULL)
|
||||
return ErrorID_OUTOFMEMORY;
|
||||
}
|
||||
memcpy(field->name, str, len);
|
||||
field->name[len] = '\0';
|
||||
return ErrorID_VOID;
|
||||
}
|
||||
|
||||
static ErrorID setFieldBody(NojaTestField *field, const char *str, size_t len)
|
||||
{
|
||||
if (len < sizeof(field->body_maybe))
|
||||
field->body = field->body_maybe;
|
||||
else {
|
||||
field->body = malloc(len+1);
|
||||
if (field->body == NULL)
|
||||
return ErrorID_OUTOFMEMORY;
|
||||
}
|
||||
memcpy(field->body, str, len);
|
||||
field->body[len] = '\0';
|
||||
return ErrorID_VOID;
|
||||
}
|
||||
|
||||
static int isFieldNameCharacter(int c)
|
||||
{
|
||||
return isalpha(c) || c == '_';
|
||||
}
|
||||
|
||||
static ErrorID parseFieldName(NojaTestScanner *scanner, NojaTestField *field)
|
||||
{
|
||||
size_t offset;
|
||||
size_t length;
|
||||
|
||||
offset = scanner->cur;
|
||||
consumeCharactersThat(scanner, isFieldNameCharacter);
|
||||
length = scanner->cur - offset;
|
||||
|
||||
if (length == 0)
|
||||
return ErrorID_BADSYNTAX_NONAME; // Missing field name token after '@'
|
||||
|
||||
ErrorID error = setFieldName(field, scanner->src + offset, length);
|
||||
if (error != ErrorID_VOID) return error;
|
||||
|
||||
return ErrorID_VOID;
|
||||
}
|
||||
|
||||
static bool parseFieldBody(NojaTestScanner *scanner, NojaTestField *field)
|
||||
{
|
||||
size_t offset;
|
||||
size_t length;
|
||||
|
||||
consumeCharactersThat(scanner, isspace);
|
||||
|
||||
bool no_term;
|
||||
char term;
|
||||
|
||||
if (ifFollowsConsumeCharacter(scanner, '[')) {
|
||||
no_term = false;
|
||||
term = ']';
|
||||
} else if (ifFollowsConsumeCharacter(scanner, '{')) {
|
||||
no_term = false;
|
||||
term = '}';
|
||||
} else
|
||||
no_term = true;
|
||||
|
||||
offset = scanner->cur;
|
||||
while (!noMoreCharacters(scanner) && !followsCharacter(scanner, '@') && (no_term || !followsCharacter(scanner, term)))
|
||||
scanner->cur++;
|
||||
length = scanner->cur - offset;
|
||||
if (!no_term)
|
||||
ifFollowsConsumeCharacter(scanner, term);
|
||||
|
||||
return setFieldBody(field, scanner->src + offset, length);
|
||||
}
|
||||
|
||||
ErrorID findField(NojaTestScanner *scanner)
|
||||
{
|
||||
consumeCharactersThat(scanner, isspace);
|
||||
bool at_follows = ifFollowsConsumeCharacter(scanner, '@');
|
||||
if (!at_follows) {
|
||||
if (noMoreCharacters(scanner))
|
||||
return ErrorID_BADSYNTAX_NOFIELD;
|
||||
return ErrorID_BADSYNTAX_NOAT;
|
||||
}
|
||||
return ErrorID_VOID;
|
||||
}
|
||||
|
||||
// Returns:
|
||||
// 0 if there are no more fields
|
||||
// -1 on error
|
||||
// 1 if a field was parsed
|
||||
ErrorID parseField(NojaTestScanner *scanner, NojaTestField *field)
|
||||
{
|
||||
ErrorID error;
|
||||
|
||||
error = findField(scanner);
|
||||
if (error != ErrorID_VOID)
|
||||
return error;
|
||||
|
||||
error = parseFieldName(scanner, field);
|
||||
if (error != ErrorID_VOID)
|
||||
return error;
|
||||
|
||||
error = parseFieldBody(scanner, field);
|
||||
if (error != ErrorID_VOID) {
|
||||
freeFieldName(field);
|
||||
return error;
|
||||
}
|
||||
|
||||
return ErrorID_VOID;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef TEST_FIELD_H
|
||||
#define TEST_FIELD_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "error.h"
|
||||
#include "scanner.h"
|
||||
|
||||
typedef struct {
|
||||
char name_maybe[128];
|
||||
char body_maybe[512];
|
||||
char *name;
|
||||
char *body;
|
||||
size_t name_len;
|
||||
size_t body_len;
|
||||
} NojaTestField;
|
||||
|
||||
void freeField(NojaTestField *field);
|
||||
ErrorID parseField(NojaTestScanner *scanner, NojaTestField *field);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
+128
-485
@@ -1,497 +1,140 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include "main.h"
|
||||
#include "run.h"
|
||||
#include "../lib/run.h"
|
||||
#include "../lib/runtime.h"
|
||||
#include "../lib/executable.h"
|
||||
#include "../lib/utils/source.h"
|
||||
#include "../lib/compiler/compile.h"
|
||||
#include "../lib/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
|
||||
|
||||
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) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, stderr);
|
||||
Error_Free(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
testcase->bytecode = Source_FromString("<output>", str + bytecode_offset, bytecode_length, &error);
|
||||
if(testcase->bytecode == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, stderr);
|
||||
Source_Free(testcase->source);
|
||||
Error_Free(&error);
|
||||
return false;
|
||||
}
|
||||
Error_Free(&error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Executable *compile_source_and_print_error_on_failure(Source *src)
|
||||
{
|
||||
int error_offset;
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
Executable *exe = compile(src, &error, &error_offset);
|
||||
if(exe == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, stderr);
|
||||
Error_Free(&error);
|
||||
return NULL;
|
||||
}
|
||||
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) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, stderr);
|
||||
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 = compile_source_and_print_error_on_failure(testcase.source);
|
||||
if(exe1 == NULL) {
|
||||
Source_Free(testcase.source);
|
||||
Source_Free(testcase.bytecode);
|
||||
return TestResult_ABORT;
|
||||
}
|
||||
|
||||
Executable *exe2;
|
||||
{
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
int error_offset;
|
||||
exe2 = assemble(testcase.bytecode, &error, &error_offset);
|
||||
if(exe2 == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, stderr);
|
||||
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, "| Skipped : %-3ld |\n", skipped_files);
|
||||
fprintf(stdout, "@-----------------@\n");
|
||||
|
||||
closedir(handle);
|
||||
}
|
||||
static TestResult runCompilerTest(const char *inputs[static 2], FILE *log_stream);
|
||||
static TestResult runRuntimeTest(const char *inputs[static 2], FILE *log_stream);
|
||||
|
||||
static const TestType test_types[] = {
|
||||
{.name="compiler", .routine=runCompilerTest, .fields=(const char*[]){"source", "bytecode", NULL}},
|
||||
{.name="runtime", .routine=runRuntimeTest, .fields=(const char*[]){"bytecode", "output", NULL}},
|
||||
{.name=NULL, .fields=NULL, .routine=NULL},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
TestBatchResults results = {.passed=0, .failed=0, .aborted=0, .batch_aborted=false};
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
||||
const char *folder = argv[1];
|
||||
runTestSuite(folder);
|
||||
const char *dir_name = argv[i];
|
||||
|
||||
TestBatchResults temp = runTestDirectory(test_types, dir_name, stderr);
|
||||
if (temp.batch_aborted)
|
||||
fprintf(stderr, "Error: Failed to run test batch\n");
|
||||
else {
|
||||
results.passed += temp.passed;
|
||||
results.failed += temp.failed;
|
||||
results.aborted += temp.aborted;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "SUMMARY: %ld passed, %ld failed, %ld aborted\n",
|
||||
results.passed, results.failed, results.aborted);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static TestResult runCompilerTest(const char *inputs[static 2], FILE *log_stream)
|
||||
{
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
Source *srcA = Source_FromString("<test>", inputs[0], -1, &error);
|
||||
if (srcA == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, log_stream);
|
||||
Error_Free(&error);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
Source *srcB = Source_FromString("<test>", inputs[1], -1, &error);
|
||||
if (srcA == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, log_stream);
|
||||
Error_Free(&error);
|
||||
Source_Free(srcA);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
int error_offset;
|
||||
Executable *exeA = compile(srcA, &error, &error_offset);
|
||||
if (exeA == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, log_stream);
|
||||
Error_Free(&error);
|
||||
Source_Free(srcA);
|
||||
Source_Free(srcB);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
Executable *exeB = assemble(srcB, &error, &error_offset);
|
||||
if (exeB == NULL) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, log_stream);
|
||||
Error_Free(&error);
|
||||
Source_Free(srcA);
|
||||
Source_Free(srcB);
|
||||
Executable_Free(exeA);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
bool passed = Executable_Equiv(exeA, exeB, log_stream, "Info: ");
|
||||
|
||||
Source_Free(srcA);
|
||||
Source_Free(srcB);
|
||||
Executable_Free(exeA);
|
||||
Executable_Free(exeB);
|
||||
return passed ? TestResult_PASSED : TestResult_FAILED;
|
||||
}
|
||||
|
||||
static TestResult runRuntimeTest(const char *inputs[static 2], FILE *log_stream)
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
FILE *output_stream = fmemopen(buffer, sizeof(buffer), "wb");
|
||||
if (output_stream == NULL)
|
||||
return TestResult_ABORTED;
|
||||
|
||||
RuntimeConfig config = Runtime_GetDefaultConfigs();
|
||||
config.stdout = output_stream;
|
||||
|
||||
Runtime *runtime = Runtime_New(config);
|
||||
if (runtime == NULL) {
|
||||
fclose(output_stream);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
if (!Runtime_plugDefaultBuiltins(runtime, &error)) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, log_stream);
|
||||
Error_Free(&error);
|
||||
Runtime_PrintStackTrace(runtime, log_stream);
|
||||
Runtime_Free(runtime);
|
||||
fclose(output_stream);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
if (!runBytecodeString(runtime, inputs[0], &error)) {
|
||||
Error_Print(&error, ErrorType_UNSPECIFIED, log_stream);
|
||||
Error_Free(&error);
|
||||
Runtime_PrintStackTrace(runtime, log_stream);
|
||||
Runtime_Free(runtime);
|
||||
fclose(output_stream);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
fclose(output_stream);
|
||||
|
||||
TestResult result;
|
||||
if (!strcmp(buffer, inputs[1])) // No bounds checks
|
||||
result = TestResult_PASSED;
|
||||
else {
|
||||
result = TestResult_FAILED;
|
||||
fprintf(stderr, "Error: Expected output [%s] but got [%s]\n", inputs[1], buffer);
|
||||
}
|
||||
|
||||
Error_Free(&error);
|
||||
Runtime_Free(runtime);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
typedef enum {
|
||||
TestResult_ABORTED,
|
||||
TestResult_FAILED,
|
||||
TestResult_PASSED,
|
||||
} TestResult;
|
||||
#endif
|
||||
Binary file not shown.
+210
@@ -0,0 +1,210 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include "run.h"
|
||||
#include "test.h"
|
||||
|
||||
static const TestType *findTestType(const TestType *test_types, const char *name)
|
||||
{
|
||||
for (size_t i = 0; test_types[i].name != NULL; i++)
|
||||
if (!strcmp(name, test_types[i].name))
|
||||
return test_types + i;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TestResult runTestString(const TestType *test_types, const char *src, FILE *log_stream)
|
||||
{
|
||||
NojaTest test;
|
||||
ErrorID error = parseTest(src, &test);
|
||||
if (error != ErrorID_VOID) {
|
||||
fprintf(log_stream, "Error: %s\n", getErrorString(error));
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
const char *type = queryTest(&test, "type");
|
||||
if (type == NULL) {
|
||||
fprintf(log_stream, "Error: Test is missing the \"type\" field\n");
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
const TestType *info = findTestType(test_types, type);
|
||||
if (info == NULL) {
|
||||
fprintf(log_stream, "Error: Invalid test type \"%s\"\n", type);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
bool uses_type = false;
|
||||
const char *ordered_fields[8];
|
||||
|
||||
size_t i = 0;
|
||||
while (info->fields[i] != NULL) {
|
||||
|
||||
uses_type = uses_type || !strcmp(info->fields[i], "type");
|
||||
|
||||
const char *field = queryTest(&test, info->fields[i]);
|
||||
if (field == NULL) {
|
||||
fprintf(log_stream, "Error: Test is missing the \"%s\" field, necessary for \"%s\" tests\n", info->fields[i], info->name);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
if (i == sizeof(ordered_fields)/sizeof(ordered_fields[0]))
|
||||
abort();
|
||||
|
||||
ordered_fields[i] = field;
|
||||
i++;
|
||||
}
|
||||
size_t used_fields = i;
|
||||
|
||||
size_t unused_fields = test.fields_count - used_fields;
|
||||
if (!uses_type) unused_fields--;
|
||||
|
||||
if (unused_fields > 0)
|
||||
fprintf(log_stream, "Warning: %ld field of the test weren't used\n", unused_fields);
|
||||
|
||||
TestResult result = info->routine(ordered_fields, log_stream);
|
||||
|
||||
freeTest(&test);
|
||||
return result;
|
||||
}
|
||||
|
||||
static char *getStreamData(FILE *file_stream, FILE *log_stream)
|
||||
{
|
||||
size_t copied = 0, just_copied;
|
||||
size_t capacity = 512;
|
||||
char *content = NULL;
|
||||
do {
|
||||
|
||||
capacity *= 2;
|
||||
|
||||
content = realloc(content, capacity);
|
||||
if (content == NULL) {
|
||||
fprintf(log_stream, "Error: Out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
just_copied = fread(content + copied, 1, capacity - copied, file_stream);
|
||||
copied += just_copied;
|
||||
|
||||
} while (just_copied > 0);
|
||||
|
||||
if (ferror(file_stream)) {
|
||||
free(content);
|
||||
fprintf(log_stream, "Error: Couldn't read file contents\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (copied+1 >= capacity) {
|
||||
content = realloc(content, capacity+1);
|
||||
if (content == NULL) {
|
||||
fprintf(log_stream, "Error: Out of memory");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
content[copied] = '\0';
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
TestResult runTestFile(const TestType *test_types, const char *file, FILE *log_stream)
|
||||
{
|
||||
FILE *file_stream = fopen(file, "rb");
|
||||
if (file_stream == NULL) {
|
||||
fprintf(log_stream, "Error: Couldn't open file \"%s\" (%s)\n", file, strerror(errno));
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
char *data = getStreamData(file_stream, log_stream);
|
||||
if (data == NULL) {
|
||||
fclose(file_stream);
|
||||
return TestResult_ABORTED;
|
||||
}
|
||||
|
||||
TestResult result = runTestString(test_types, data, log_stream);
|
||||
|
||||
free(data);
|
||||
fclose(file_stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *getExtension(const char *file)
|
||||
{
|
||||
int dot = -1;
|
||||
for (int i = 0; file[i] != '\0'; i++)
|
||||
if (file[i] == '.')
|
||||
dot = i;
|
||||
|
||||
return (dot == -1) ? "" : file + dot;
|
||||
}
|
||||
|
||||
TestBatchResults runTestDirectory(const TestType *test_types, const char *dir_name, FILE *log_stream)
|
||||
{
|
||||
TestBatchResults results = {.passed=0, .failed=0, .aborted=0, .batch_aborted=false};
|
||||
|
||||
DIR *d = opendir(dir_name);
|
||||
if (d == NULL) {
|
||||
results.batch_aborted = true;
|
||||
return results;
|
||||
}
|
||||
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
|
||||
if (ent->d_name[0] == '.') {
|
||||
//fprintf(log_stream, "Info: Ignoring file \"%s\"\n", ent->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
char full[1024];
|
||||
strcpy(full, dir_name);
|
||||
if (dir_name[strlen(dir_name)-1] != '/')
|
||||
strcat(full, "/");
|
||||
strcat(full, ent->d_name);
|
||||
|
||||
if (ent->d_type == DT_DIR) {
|
||||
TestBatchResults sub_results = runTestDirectory(test_types, full, log_stream);
|
||||
if (!sub_results.batch_aborted) {
|
||||
results.passed += sub_results.passed;
|
||||
results.failed += sub_results.failed;
|
||||
results.aborted += sub_results.aborted;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent->d_type == DT_UNKNOWN) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (ent->d_type != DT_REG) {
|
||||
//fprintf(log_stream, "Info: Ignoring file \"%s\"\n", ent->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *ext = getExtension(ent->d_name);
|
||||
if (strcmp(ext, ".noja-test")) {
|
||||
//fprintf(log_stream, "Info: Ignoring file \"%s\"\n", ent->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
TestResult result = runTestFile(test_types, full, log_stream);
|
||||
switch (result) {
|
||||
case TestResult_PASSED:
|
||||
fprintf(log_stream, "Test %s .. PASSED\n", full);
|
||||
results.passed++;
|
||||
break;
|
||||
|
||||
case TestResult_FAILED:
|
||||
fprintf(log_stream, "Test %s .. FAILED\n", full);
|
||||
results.failed++;
|
||||
break;
|
||||
|
||||
case TestResult_ABORTED:
|
||||
fprintf(log_stream, "Test %s .. ABORTED\n", full);
|
||||
results.aborted++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
return results;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include "main.h"
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char **fields;
|
||||
TestResult (*routine)(const char **fields, FILE *log_stream);
|
||||
} TestType;
|
||||
|
||||
typedef struct {
|
||||
size_t passed;
|
||||
size_t failed;
|
||||
size_t aborted;
|
||||
bool batch_aborted;
|
||||
} TestBatchResults;
|
||||
|
||||
TestResult runTestFile(const TestType *test_types, const char *file, FILE *log_stream);
|
||||
TestResult runTestString(const TestType *test_types, const char *src, FILE *log_stream);
|
||||
TestBatchResults runTestDirectory(const TestType *test_types, const char *dir_name, FILE *log_stream);
|
||||
Binary file not shown.
@@ -0,0 +1,65 @@
|
||||
#include "scanner.h"
|
||||
|
||||
bool followsCharacterThat(NojaTestScanner *scanner, int (*testfn)(int c))
|
||||
{
|
||||
return scanner->cur < scanner->len && testfn(scanner->src[scanner->cur]);
|
||||
}
|
||||
|
||||
bool followsCharacter(NojaTestScanner *scanner, char c)
|
||||
{
|
||||
return scanner->cur < scanner->len && scanner->src[scanner->cur] == c;
|
||||
}
|
||||
|
||||
bool noMoreCharacters(NojaTestScanner *scanner)
|
||||
{
|
||||
return scanner->cur == scanner->len;
|
||||
}
|
||||
|
||||
bool ifFollowsConsumeCharacter(NojaTestScanner *scanner, char c)
|
||||
{
|
||||
bool follows = followsCharacter(scanner, c);
|
||||
if (follows)
|
||||
scanner->cur++;
|
||||
return follows;
|
||||
}
|
||||
|
||||
void consumeCharactersThat(NojaTestScanner *scanner, int (*testfn)(int c))
|
||||
{
|
||||
while (followsCharacterThat(scanner, testfn))
|
||||
scanner->cur++;
|
||||
}
|
||||
/*
|
||||
#ifndef NDEBUG
|
||||
|
||||
bool noMoreCharacters__wrapped(NojaTestScanner *scanner, const char *file, size_t line)
|
||||
{
|
||||
fprintf(stderr, "noMoreCharacters(%p) - %s:%ld\n", scanner, file, line);
|
||||
return noMoreCharacters(scanner);
|
||||
}
|
||||
|
||||
bool followsCharacter__wrapped(NojaTestScanner *scanner, char c, const char *file, size_t line)
|
||||
{
|
||||
fprintf(stderr, "followsCharacter(%p, '%c') - %s:%ld\n", scanner, c, file, line);
|
||||
return followsCharacter(scanner, c);
|
||||
}
|
||||
|
||||
bool followsCharacterThat__wrapped(NojaTestScanner *scanner, int (*testfn)(int c), const char *file, size_t line)
|
||||
{
|
||||
fprintf(stderr, "followsCharacterThat(%p, %p) - %s:%ld\n", scanner, testfn, file, line);
|
||||
return followsCharacterThat(scanner);
|
||||
}
|
||||
|
||||
void consumeCharactersThat__wrapped(NojaTestScanner *scanner, int (*testfn)(int c), const char *file, size_t line)
|
||||
{
|
||||
fprintf(stderr, "consumeCharactersThat(%p, %p) - %s:%ld\n", scanner, testfn, file, line);
|
||||
return consumeCharactersThat(scanner);
|
||||
}
|
||||
|
||||
bool ifFollowsConsumeCharacter__wrapped(NojaTestScanner *scanner, char c, const char *file, size_t line)
|
||||
{
|
||||
fprintf(stderr, "ifFollowsConsumeCharacter(%p, '%c') - %s:%ld\n", scanner, c, file, line);
|
||||
return ifFollowsConsumeCharacter(scanner);
|
||||
}
|
||||
|
||||
#endif
|
||||
*/
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef TEST_SCANNER_H
|
||||
#define TEST_SCANNER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
const char *src;
|
||||
size_t cur, len;
|
||||
} NojaTestScanner;
|
||||
|
||||
bool noMoreCharacters(NojaTestScanner *scanner);
|
||||
bool followsCharacter(NojaTestScanner *scanner, char c);
|
||||
bool followsCharacterThat(NojaTestScanner *scanner, int (*testfn)(int c));
|
||||
void consumeCharactersThat(NojaTestScanner *scanner, int (*testfn)(int c));
|
||||
bool ifFollowsConsumeCharacter(NojaTestScanner *scanner, char c);
|
||||
/*
|
||||
#ifndef NDEBUG
|
||||
bool noMoreCharacters__wrapped(NojaTestScanner *scanner, const char *file, size_t line);
|
||||
bool followsCharacter__wrapped(NojaTestScanner *scanner, char c, const char *file, size_t line);
|
||||
bool followsCharacterThat__wrapped(NojaTestScanner *scanner, int (*testfn)(int c), const char *file, size_t line);
|
||||
void consumeCharactersThat__wrapped(NojaTestScanner *scanner, int (*testfn)(int c), const char *file, size_t line);
|
||||
bool ifFollowsConsumeCharacter__wrapped(NojaTestScanner *scanner, char c, const char *file, size_t line);
|
||||
|
||||
#define noMoreCharacters(scanner) noMoreCharacters__wrapped(scanner, __FILE__, __LINE__)
|
||||
#define followsCharacter(scanner, c) followsCharacter__wrapped(scanner, c, __FILE__, __LINE__)
|
||||
#define followsCharacterThat(scanner, testfn) followsCharacterThat__wrapped(scanner, testfn, __FILE__, __LINE__)
|
||||
#define consumeCharactersThat(scanner, testfn) consumeCharacterThat__wrapped(scanner, testfn, __FILE__, __LINE__)
|
||||
#define ifFollowsConsumeCharacter(scanner, c) ifFollowsConsumeCharacter__wrapped(scanner, c, __FILE__, __LINE__)
|
||||
#endif
|
||||
*/
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "test.h"
|
||||
#include "error.h"
|
||||
#include "field.h"
|
||||
|
||||
static bool growFieldsArray(NojaTest *test)
|
||||
{
|
||||
size_t new_capacity = 2 * test->fields_capacity;
|
||||
|
||||
NojaTestField *new_fields;
|
||||
if (test->fields == test->fields_maybe) {
|
||||
new_fields = malloc(new_capacity * sizeof(NojaTestField));
|
||||
if (new_fields != NULL)
|
||||
memcpy(new_fields, test->fields, test->fields_count * sizeof(NojaTestField));
|
||||
} else
|
||||
new_fields = realloc(test->fields, new_capacity * sizeof(NojaTestField));
|
||||
|
||||
if (new_fields == NULL)
|
||||
return false;
|
||||
|
||||
test->fields = new_fields;
|
||||
test->fields_capacity = new_capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
void freeTest(NojaTest *test)
|
||||
{
|
||||
for (size_t i = 0; i < test->fields_count; i++)
|
||||
freeField(test->fields + i);
|
||||
if (test->fields != test->fields_maybe)
|
||||
free(test->fields);
|
||||
}
|
||||
|
||||
const char *queryTest(NojaTest *test, const char *name)
|
||||
{
|
||||
for (size_t i = 0; i < test->fields_count; i++)
|
||||
if (!strcmp(name, test->fields[i].name))
|
||||
return test->fields[i].body;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ErrorID parseTest(const char *src, NojaTest *test)
|
||||
{
|
||||
NojaTestScanner scanner = {.src=src, .len=strlen(src), .cur=0};
|
||||
|
||||
test->fields = test->fields_maybe;
|
||||
test->fields_count = 0;
|
||||
test->fields_capacity = sizeof(test->fields_maybe)/sizeof(test->fields_maybe[0]);
|
||||
|
||||
bool done = false;
|
||||
do {
|
||||
|
||||
if (test->fields_count == test->fields_capacity)
|
||||
if (!growFieldsArray(test)) {
|
||||
freeTest(test);
|
||||
return ErrorID_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
NojaTestField *field = test->fields + test->fields_count;
|
||||
|
||||
ErrorID error = parseField(&scanner, field);
|
||||
switch (error) {
|
||||
|
||||
case ErrorID_VOID:
|
||||
// Before acknowledging the insertion
|
||||
// by incrementing the field counter,
|
||||
// check that the new field doesn't have
|
||||
// a duplicate name
|
||||
if (queryTest(test, field->name))
|
||||
return ErrorID_DUPLFIELD;
|
||||
test->fields_count++;
|
||||
break;
|
||||
|
||||
case ErrorID_BADSYNTAX_NOFIELD:
|
||||
done = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
freeTest(test);
|
||||
return error;
|
||||
}
|
||||
} while (!done);
|
||||
return ErrorID_VOID;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "field.h"
|
||||
#include "error.h"
|
||||
|
||||
typedef struct {
|
||||
NojaTestField fields_maybe[8];
|
||||
NojaTestField *fields;
|
||||
size_t fields_count;
|
||||
size_t fields_capacity;
|
||||
} NojaTest;
|
||||
ErrorID parseTest(const char *src, NojaTest *test);
|
||||
const char *queryTest(NojaTest *test, const char *name);
|
||||
void freeTest(NojaTest *test);
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -1,11 +1,12 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
do
|
||||
{}
|
||||
while none;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
begin:
|
||||
PUSHNNE;
|
||||
@@ -1,12 +1,13 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
do
|
||||
true;
|
||||
while none;
|
||||
false;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
begin:
|
||||
PUSHTRU;
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
do {
|
||||
true;
|
||||
@@ -7,7 +8,7 @@
|
||||
} while none;
|
||||
false;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
begin:
|
||||
PUSHTRU;
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
do {
|
||||
true;
|
||||
@@ -8,7 +9,7 @@
|
||||
} while none;
|
||||
false;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
begin:
|
||||
PUSHTRU;
|
||||
@@ -1,12 +1,13 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
do
|
||||
break;
|
||||
while none;
|
||||
false;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
begin:
|
||||
JUMP end;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 + 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 - 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 * 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 / 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1.0 + 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLT 1.0;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1.0 - 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLT 1.0;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1.0 * 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLT 1.0;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1.0 / 7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLT 1.0;
|
||||
PUSHINT 7;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 + 7.0;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHFLT 7.0;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 - 7.0;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHFLT 7.0;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 * 7.0;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHFLT 7.0;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 / 7.0;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHFLT 7.0;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 + 2 * 3;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 * 2 + 3;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
(1 + 2) * 3;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 + (2 * 3);
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 * 2 * 3 * 4;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 * (2 * (3 * 4));
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 + 2 * 3 + 4;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 * 2 + 3 * 4;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
A - B - C;
|
||||
A - B * C;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "A";
|
||||
PUSHVAR "B";
|
||||
@@ -0,0 +1,16 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
# This comment is because there
|
||||
# can't be a '[' right before a
|
||||
# tag.
|
||||
[1];
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHLST 1;
|
||||
PUSHINT 0;
|
||||
PUSHINT 1;
|
||||
INSERT;
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
#source
|
||||
|
||||
@type [compiler]
|
||||
@source
|
||||
# This comment is because there
|
||||
# can't be a '[' right before a
|
||||
# tag.
|
||||
[true, false];
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHLST 2;
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
var = none;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHNNE;
|
||||
ASS "var";
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
var[key] = none;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHNNE;
|
||||
PUSHVAR "var";
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
A, B = func();
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "func";
|
||||
CALL 0, 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
var.key = none;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHNNE;
|
||||
PUSHVAR "var";
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 and 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
JUMPIFNOTANDPOP false;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 or 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
JUMPIFANDPOP true;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
A and B or C and D;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "A";
|
||||
JUMPIFNOTANDPOP false_0;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
X or Y and Z or W;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "X";
|
||||
JUMPIFANDPOP true_0;
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({'name': 'Francesco'});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 1;
|
||||
PUSHSTR "name";
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({'name': 'Francesco', 'age': 25});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 2;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({name: 'Francesco'});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 1;
|
||||
PUSHSTR "name";
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({name: 'Francesco', age: 25});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 2;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({+name: 'Francesco'});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 1;
|
||||
PUSHVAR "name";
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({+name: 'Francesco'});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 1;
|
||||
PUSHVAR "name";
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
func();
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "func";
|
||||
CALL 0, 1;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
func(none);
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHNNE;
|
||||
PUSHVAR "func";
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
func(true, false);
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLS;
|
||||
PUSHTRU;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
set[key];
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "set";
|
||||
PUSHVAR "key";
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
set["dog", "cow", "cat"];
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "set";
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
set.key;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHVAR "set";
|
||||
PUSHSTR "key";
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
-1;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
NEG;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
+1;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
POS;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
not 1;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
NOT;
|
||||
@@ -0,0 +1,9 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
1;
|
||||
|
||||
@bytecode
|
||||
PUSHINT 1;
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -1,9 +1,10 @@
|
||||
@type [compiler]
|
||||
|
||||
#source
|
||||
@source
|
||||
|
||||
6.7;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLT 6.7;
|
||||
POP 1;
|
||||
@@ -1,9 +1,10 @@
|
||||
@type [compiler]
|
||||
|
||||
#source
|
||||
@source
|
||||
|
||||
'Hello, world!';
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "Hello, world!";
|
||||
POP 1;
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
true;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHTRU;
|
||||
POP 1;
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
false;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHFLS;
|
||||
POP 1;
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
none;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHNNE;
|
||||
POP 1;
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
({});
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHMAP 0;
|
||||
POP 1;
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
# This comment is because there
|
||||
# can't be a '[' right before a
|
||||
# tag.
|
||||
[];
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHLST 0;
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
{}
|
||||
|
||||
@bytecode
|
||||
|
||||
EXIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
|
||||
'This \\ is a slash!';
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "This \\ is a slash!";
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
|
||||
'This \n is a newline!';
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "This \n is a newline!";
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
|
||||
'This \t is an horizontal tab!';
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "This \t is an horizontal tab!";
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
|
||||
'This \r is a special character!';
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "This \r is a special character!";
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
|
||||
'Look at this \'!';
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "Look at this '!";
|
||||
POP 1;
|
||||
EXIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
@type [compiler]
|
||||
|
||||
@source
|
||||
|
||||
"Look at this \"!";
|
||||
|
||||
@bytecode
|
||||
|
||||
PUSHSTR "Look at this \"!";
|
||||
POP 1;
|
||||
EXIT;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 == 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 != 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 < 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 > 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 <= 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 >= 2;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
|
||||
#source
|
||||
@type [compiler]
|
||||
@source
|
||||
|
||||
1 + 2 > 3 + 4;
|
||||
1 + 2 < 3 + 4;
|
||||
1 + 2 >= 3 + 4;
|
||||
1 + 2 <= 3 + 4;
|
||||
|
||||
#bytecode
|
||||
@bytecode
|
||||
|
||||
PUSHINT 1;
|
||||
PUSHINT 2;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user