first commit
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
|
||||
#ifndef XJSON_H
|
||||
#define XJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
enum {
|
||||
xj_INT = (1 << 0),
|
||||
xj_BOOL = (1 << 1),
|
||||
xj_NULL = (1 << 2),
|
||||
xj_FLOAT = (1 << 3),
|
||||
xj_ARRAY = (1 << 4),
|
||||
xj_OBJECT = (1 << 5),
|
||||
xj_STRING = (1 << 6),
|
||||
xj_BIG_INT = (1 << 7),
|
||||
xj_BIG_FLOAT = (1 << 8),
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
xj_E0000 = 0, // No error.
|
||||
|
||||
|
||||
// --------------------------- //
|
||||
// --- Internal errors ------- //
|
||||
// --------------------------- //
|
||||
|
||||
xj_E0001, // Maximum depth reached.
|
||||
xj_E0002, // Out of memory.
|
||||
xj_E0003, // Not implemented.
|
||||
|
||||
|
||||
// --------------------------- //
|
||||
// --- Input errors ---------- //
|
||||
// --------------------------- //
|
||||
|
||||
// (Unexpexted end of source inside object) //
|
||||
|
||||
xj_E1000, // End of source inside object,
|
||||
// right after the starting {
|
||||
|
||||
xj_E1001, // End of source inside object,
|
||||
// right after a key string. Was
|
||||
// expected :
|
||||
|
||||
xj_E1002, // End of source inside object,
|
||||
// right after a key-value separator :.
|
||||
// Was expected a value expression
|
||||
|
||||
xj_E1003, // End of source inside object,
|
||||
// right after value expression.
|
||||
// Were expected , or }
|
||||
|
||||
xj_E1004, // End of source inside object,
|
||||
// right after value-key separator ,.
|
||||
// Was expected a key string
|
||||
|
||||
// (Unexpected end of source inside array) //
|
||||
|
||||
xj_E1100, // End of source inside array,
|
||||
// right after the starting [
|
||||
|
||||
xj_E1101, // End of source inside array,
|
||||
// right after a value expression
|
||||
|
||||
xj_E1102, // End of source inside array,
|
||||
// right after value separator ,.
|
||||
// Was expected a new value
|
||||
// expression
|
||||
|
||||
// (Unexpected end of source inside number) //
|
||||
|
||||
xj_E1200, // End of source inside numeric
|
||||
// expression, right after minus
|
||||
// sign. Was expected a digit
|
||||
|
||||
// (Unexpected end of source inside string) //
|
||||
xj_E1300, // End of source inside string expression
|
||||
xj_E1301, // End of source inside string expression, right after a backslash
|
||||
|
||||
// (Unexpected character inside object) //
|
||||
xj_E2000, // Unexpected character in place of an object key string. Was expected "
|
||||
xj_E2001, // Unexpected character in place of an object key-value separator. Was expected :
|
||||
xj_E2002, // Unexpected character inside object, right after value expression. Were expected , or }
|
||||
|
||||
// (Unexpected character inside array) //
|
||||
xj_E2100, // Unexpected character inside array, right after value expression. Were expecter ] or ,
|
||||
|
||||
// (Unexpected character inside number) //
|
||||
xj_E2200, // Unexpected character inside number expression, right after minus sign. Was expected a digit after it
|
||||
|
||||
// (Unexpected character inside string) //
|
||||
xj_E2300, // Unexpected character inside string expression, right after backslash. The only escapable characters are \, /, ", n, t, r, f, b and u.
|
||||
xj_E2301, // Unexpected character inside string expression, after \u. Was expected a hex digit
|
||||
|
||||
xj_E2302, // Unexpected character inside string expression, right after unicode codepoint. Was expected an auxiliary codepoint
|
||||
xj_E2303, // Unexpected character inside string expression, right after \\. Was expected an auxiliary codepoint
|
||||
|
||||
xj_E2304, // Invalid symbol
|
||||
xj_E2305, // Invalid auxiliary symbol
|
||||
xj_E2306, // Invalid codepoint
|
||||
|
||||
// (Unexpected end of source inside value expression) //
|
||||
xj_E2400, // Unexpected character in place of a value expression. Were expected {, [, ", n, t, f or a digit.
|
||||
|
||||
// (Other) //
|
||||
xj_E2500, // Unexpected character after root value expression
|
||||
xj_E2501, // Source string is empty (null pointer)
|
||||
xj_E2502, // Source string is empty
|
||||
xj_E2503, // Duplicate key in object
|
||||
|
||||
// Decodef errors
|
||||
xj_E3000, // Bad format
|
||||
|
||||
// Query errors
|
||||
xj_E4000, // End of source after dot.
|
||||
xj_E4001, // Invalid character after dot.
|
||||
xj_E4002, // End of source after [.
|
||||
xj_E4003, // Float used as index.
|
||||
xj_E4004, // Index too big.
|
||||
xj_E4005, // Invalid character after [.
|
||||
xj_E4006, // End of source instead of closing ].
|
||||
xj_E4007, // Invalid character instead of closing ].
|
||||
xj_E4008, // Invalid character instead of dot and [.
|
||||
xj_E4009, // Missing node.
|
||||
|
||||
// (i|f|b|s|a|o)Query errors
|
||||
xj_E4100, // Queried int is too big so it's not representable.
|
||||
xj_E4101, // Queried value is not an int.
|
||||
xj_E4102, // Queried float is too big, so it's not representable.
|
||||
xj_E4103, // Queried value is not a float.
|
||||
xj_E4104, // Queried value is not a string.
|
||||
xj_E4105, // Queried value is not a bool.
|
||||
xj_E4106, // Queried value is not an array.
|
||||
xj_E4107, // Queried value is not an object.
|
||||
|
||||
xj_ECOUNT,
|
||||
} xj_code;
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char *src;
|
||||
|
||||
_Bool occurred;
|
||||
xj_code code;
|
||||
|
||||
char text[128];
|
||||
|
||||
int offset,
|
||||
lineno;
|
||||
|
||||
const char *cfile,
|
||||
*cfunc;
|
||||
int cline;
|
||||
} xj_error;
|
||||
|
||||
typedef void xj_alloc;
|
||||
|
||||
typedef struct xj_value xj_value;
|
||||
struct xj_value {
|
||||
int offset, length, type, size;
|
||||
char *key;
|
||||
union {
|
||||
long long as_int;
|
||||
_Bool as_bool;
|
||||
double as_float;
|
||||
xj_value* as_array;
|
||||
xj_value* as_object;
|
||||
char* as_string;
|
||||
};
|
||||
xj_value *next;
|
||||
};
|
||||
|
||||
xj_alloc* xj_init (unsigned int default_chunk_size);
|
||||
void xj_free (xj_alloc *alloc);
|
||||
void* xj_malloc(xj_alloc *alloc, unsigned int size);
|
||||
|
||||
void xj_set_malloc(void *(*malloc)(void*, unsigned int));
|
||||
void* xj_sys_malloc(unsigned int size);
|
||||
void xj_set_free(void (*free)(void*, void*));
|
||||
void xj_sys_free(void *ptr);
|
||||
void xj_set_userp(void *userp);
|
||||
|
||||
int xj_snprintf (char *buff, int buffsz, const char *fmt, ...);
|
||||
int xj_vsnprintf(char *buff, int buffsz, const char *fmt, va_list args);
|
||||
|
||||
xj_value* xj_query (xj_error *error, xj_value *root, const char *text, int len);
|
||||
_Bool xj_nquery(xj_error *error, xj_value *root, const char *text, int len);
|
||||
int xj_iquery(xj_error *error, xj_value *root, const char *text, int len);
|
||||
double xj_fquery(xj_error *error, xj_value *root, const char *text, int len);
|
||||
_Bool xj_bquery(xj_error *error, xj_value *root, const char *text, int len);
|
||||
char *xj_squery(xj_error *error, xj_value *root, const char *text, int len, int *count);
|
||||
xj_value *xj_aquery(xj_error *error, xj_value *root, const char *text, int len, int *count);
|
||||
xj_value *xj_oquery(xj_error *error, xj_value *root, const char *text, int len, int *count);
|
||||
|
||||
xj_value* xj_decode (xj_error *error, xj_alloc **alloc, const char *text, int len);
|
||||
xj_value* xj_decodef (xj_error *error, xj_alloc **alloc, const char *format, ...);
|
||||
xj_value* xj_vdecodef(xj_error *error, xj_alloc **alloc, const char *format, va_list args);
|
||||
|
||||
char* xj_encode (const xj_value *value, int *len);
|
||||
char* xj_sencode(const char *src, int *len);
|
||||
char* xj_aencode(const xj_value *head, int *len);
|
||||
char* xj_oencode(const xj_value *head, int *len);
|
||||
|
||||
char* xj_encode2 (const xj_value *value, char *buff, unsigned int bufflen, int *len);
|
||||
char* xj_sencode2(const char *src, char *buff, unsigned int bufflen, int *len);
|
||||
char* xj_aencode2(const xj_value *head, char *buff, unsigned int bufflen, int *len);
|
||||
char* xj_oencode2(const xj_value *head, char *buff, unsigned int bufflen, int *len);
|
||||
|
||||
_Bool xj_compare(const xj_value *a, const xj_value *b);
|
||||
xj_value* xj_get_by_name (const xj_value *parent, const char *name, int len);
|
||||
xj_value* xj_get_by_index(const xj_value *parent, unsigned int index);
|
||||
_Bool xj_is_error_internal(const xj_error *error);
|
||||
const char *xj_typename(int type);
|
||||
void xj_stats(FILE *fp);
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
USING_VALGRIND=0
|
||||
|
||||
FLAGS="-L3p/libs/ -I3p/include/ -Wall -Wextra -g -DUSING_VALGRIND=$USING_VALGRIND -DTRACE_SOURCE_COPIES=$TRACE_SOURCE_COPIES"
|
||||
|
||||
mkdir temp
|
||||
|
||||
mkdir temp/utils
|
||||
gcc -c src/utils/hash.c -o temp/utils/hash.o $FLAGS
|
||||
gcc -c src/utils/stack.c -o temp/utils/stack.o $FLAGS
|
||||
gcc -c src/utils/error.c -o temp/utils/error.o $FLAGS
|
||||
gcc -c src/utils/source.c -o temp/utils/source.o $FLAGS
|
||||
gcc -c src/utils/bpalloc.c -o temp/utils/bpalloc.o $FLAGS
|
||||
gcc -c src/utils/promise.c -o temp/utils/promise.o $FLAGS
|
||||
gcc -c src/utils/bucketlist.c -o temp/utils/bucketlist.o $FLAGS
|
||||
|
||||
mkdir temp/objects
|
||||
gcc -c src/objects/heap.c -o temp/objects/heap.o $FLAGS
|
||||
gcc -c src/objects/o_int.c -o temp/objects/o_int.o $FLAGS
|
||||
gcc -c src/objects/o_map.c -o temp/objects/o_map.o $FLAGS
|
||||
gcc -c src/objects/o_none.c -o temp/objects/o_none.o $FLAGS
|
||||
gcc -c src/objects/o_float.c -o temp/objects/o_float.o $FLAGS
|
||||
gcc -c src/objects/o_string.c -o temp/objects/o_string.o $FLAGS
|
||||
gcc -c src/objects/objects.c -o temp/objects/objects.o $FLAGS
|
||||
|
||||
mkdir temp/compiler
|
||||
gcc -c src/compiler/parse.c -o temp/compiler/parse.o $FLAGS
|
||||
gcc -c src/compiler/compile.c -o temp/compiler/compile.o $FLAGS
|
||||
gcc -c src/compiler/serialize.c -o temp/compiler/serialize.o $FLAGS
|
||||
|
||||
mkdir temp/common
|
||||
gcc -c src/common/executable.c -o temp/common/executable.o $FLAGS
|
||||
|
||||
mkdir temp/runtime
|
||||
gcc -c src/runtime/runtime_error.c -o temp/runtime/runtime_error.o $FLAGS
|
||||
gcc -c src/runtime/runtime.c -o temp/runtime/runtime.o $FLAGS
|
||||
gcc -c src/runtime/o_func.c -o temp/runtime/o_func.o $FLAGS
|
||||
|
||||
rm -rf build
|
||||
mkdir build
|
||||
|
||||
ar rcs build/libnoja-objects.a \
|
||||
temp/objects/heap.o \
|
||||
temp/objects/o_int.o \
|
||||
temp/objects/o_float.o \
|
||||
temp/objects/objects.o \
|
||||
temp/utils/error.o
|
||||
|
||||
ar rcs build/libnoja-compile.a \
|
||||
temp/compiler/parse.o \
|
||||
temp/compiler/compile.o \
|
||||
temp/compiler/serialize.o \
|
||||
temp/utils/bpalloc.o \
|
||||
temp/utils/error.o \
|
||||
temp/utils/source.o
|
||||
|
||||
ar rcs build/libnoja-runtime.a \
|
||||
temp/runtime/runtime.o \
|
||||
temp/runtime/runtime_error.o \
|
||||
temp/runtime/o_func.o \
|
||||
build/libnoja-compile.a \
|
||||
build/libnoja-objects.a
|
||||
|
||||
gcc tests/src/test-parse.c -o build/test-parse $FLAGS -Lbuild/ -lnoja-compile -lxjson
|
||||
gcc tests/src/test-objects.c -o build/test-objects $FLAGS -Lbuild/ -lnoja-objects
|
||||
|
||||
gcc src/main.c \
|
||||
temp/utils/hash.o \
|
||||
temp/utils/stack.o \
|
||||
temp/utils/source.o \
|
||||
temp/utils/promise.o \
|
||||
temp/utils/bucketlist.o \
|
||||
temp/objects/o_map.o \
|
||||
temp/objects/o_none.o \
|
||||
temp/objects/o_string.o \
|
||||
temp/runtime/runtime.o \
|
||||
temp/runtime/runtime_error.o \
|
||||
temp/common/executable.o \
|
||||
-o build/noja $FLAGS -Lbuild/ -lnoja-compile -lnoja-objects -lxjson
|
||||
|
||||
rm -rf temp
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
|
||||
stack = {count: 0, head: none};
|
||||
|
||||
fun push(stack, value) {
|
||||
|
||||
node = {prev: null, item: value};
|
||||
|
||||
node.prev = list.head;
|
||||
stack.head = node;
|
||||
stack.count += 1;
|
||||
}
|
||||
|
||||
fun pop(stack)
|
||||
{
|
||||
if stack.head is none:
|
||||
return none; # return it; ?
|
||||
|
||||
value = stack.head.item;
|
||||
stack.head = stack.head.prev;
|
||||
stack.count -= 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
push(stack, 1);
|
||||
push(stack, 2);
|
||||
push(stack, 3);
|
||||
x = pop(stack);
|
||||
y = pop(stack);
|
||||
z = pop(stack);
|
||||
w = pop(stack);
|
||||
|
||||
assert x == 3;
|
||||
assert y == 2;
|
||||
assert z == 1;
|
||||
assert w == none;
|
||||
@@ -0,0 +1,365 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "../utils/bucketlist.h"
|
||||
#include "executable.h"
|
||||
|
||||
#define MAX_OPS 3
|
||||
|
||||
typedef struct {
|
||||
Opcode opcode;
|
||||
int offset;
|
||||
int length;
|
||||
union {
|
||||
long long int as_int;
|
||||
double as_float;
|
||||
} operands[MAX_OPS];
|
||||
} Instruction;
|
||||
|
||||
struct xExecutable {
|
||||
int refs;
|
||||
int headl, bodyl;
|
||||
char *head;
|
||||
Instruction *body;
|
||||
Source *src;
|
||||
};
|
||||
|
||||
struct xExeBuilder {
|
||||
BucketList *data, *code;
|
||||
int promc;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
int stacksz;
|
||||
int opcount;
|
||||
OperandType *optypes;
|
||||
} InstrInfo;
|
||||
|
||||
static const InstrInfo instr_table[] = {
|
||||
|
||||
[OPCODE_NOPE] = {"NOPE", 0, 0, NULL},
|
||||
|
||||
[OPCODE_POS] = {"POS", 1, 0, NULL},
|
||||
[OPCODE_NEG] = {"NEG", 1, 0, NULL},
|
||||
|
||||
[OPCODE_ADD] = {"ADD", 2, 0, NULL},
|
||||
[OPCODE_SUB] = {"SUB", 2, 0, NULL},
|
||||
[OPCODE_MUL] = {"MUL", 2, 0, NULL},
|
||||
[OPCODE_DIV] = {"DIV", 2, 0, NULL},
|
||||
|
||||
[OPCODE_PUSHI] = {"PUSHI", 2, 1, (OperandType[]) {OPTP_INT}},
|
||||
[OPCODE_PUSHF] = {"PUSHF", 2, 1, (OperandType[]) {OPTP_FLOAT}},
|
||||
[OPCODE_PUSHS] = {"PUSHS", 2, 1, (OperandType[]) {OPTP_STRING}},
|
||||
[OPCODE_PUSHV] = {"PUSHV", 2, 1, (OperandType[]) {OPTP_STRING}},
|
||||
|
||||
[OPCODE_RETURN] = {"RETURN", 0, 0, NULL},
|
||||
};
|
||||
|
||||
static const char *operand_type_names[] = {
|
||||
[OPTP_INT] = "int",
|
||||
[OPTP_FLOAT] = "float",
|
||||
[OPTP_STRING] = "string",
|
||||
};
|
||||
|
||||
static const char *operand_type_arts[] = {
|
||||
[OPTP_INT] = "an",
|
||||
[OPTP_FLOAT] = "a",
|
||||
[OPTP_STRING] = "a",
|
||||
};
|
||||
|
||||
static unsigned int operand_type_sizes[] = {
|
||||
[OPTP_INT] = membersizeof(Operand, as_int),
|
||||
[OPTP_FLOAT] = membersizeof(Operand, as_float),
|
||||
[OPTP_STRING] = membersizeof(Operand, as_string),
|
||||
};
|
||||
|
||||
Executable *Executable_Copy(Executable *exe)
|
||||
{
|
||||
assert(exe != NULL);
|
||||
|
||||
exe->refs += 1;
|
||||
return exe;
|
||||
}
|
||||
|
||||
void Executable_Free(Executable *exe)
|
||||
{
|
||||
exe->refs -= 1;
|
||||
assert(exe->refs >= 0);
|
||||
|
||||
if(exe->refs == 0)
|
||||
{
|
||||
if(exe->src)
|
||||
Source_Free(exe->src);
|
||||
free(exe);
|
||||
}
|
||||
}
|
||||
|
||||
_Bool Executable_SetSource(Executable *exe, Source *src)
|
||||
{
|
||||
src = Source_Copy(src);
|
||||
|
||||
if(src == NULL)
|
||||
return 0;
|
||||
|
||||
exe->src = src;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Source *Executable_GetSource(Executable *exe)
|
||||
{
|
||||
return exe->src;
|
||||
}
|
||||
|
||||
_Bool Executable_Fetch(Executable *exe, int index, Opcode *opcode, Operand *ops, int *opc)
|
||||
{
|
||||
assert(index >= 0);
|
||||
|
||||
if(index >= exe->bodyl)
|
||||
return 0;
|
||||
|
||||
const Instruction *instr = exe->body + index;
|
||||
const InstrInfo *info = instr_table + instr->opcode;
|
||||
|
||||
if(opcode)
|
||||
*opcode = instr->opcode;
|
||||
|
||||
int i;
|
||||
|
||||
if(ops && opc)
|
||||
{
|
||||
int k = MIN(*opc, info->opcount);
|
||||
|
||||
for(i = 0; i < k; i += 1)
|
||||
{
|
||||
OperandType type = info->optypes[i];
|
||||
assert(type != OPTP_PROMISE);
|
||||
|
||||
switch(type) {
|
||||
|
||||
case OPTP_STRING:
|
||||
|
||||
int data_offset = instr->operands[i].as_int;
|
||||
|
||||
assert(data_offset < exe->headl);
|
||||
|
||||
ops[i].type = OPTP_STRING;
|
||||
ops[i].as_string = exe->head + data_offset;
|
||||
break;
|
||||
|
||||
case OPTP_INT:
|
||||
ops[i].type = OPTP_INT;
|
||||
ops[i].as_int = instr->operands[i].as_int;
|
||||
break;
|
||||
|
||||
case OPTP_FLOAT:
|
||||
ops[i].type = OPTP_FLOAT;
|
||||
ops[i].as_int = instr->operands[i].as_int;
|
||||
break;
|
||||
|
||||
case OPTP_PROMISE:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*opc = MIN(*opc, info->opcount);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
ExeBuilder *ExeBuilder_New(BPAlloc *alloc)
|
||||
{
|
||||
assert(alloc != NULL);
|
||||
|
||||
ExeBuilder *exeb = BPAlloc_Malloc(alloc, sizeof(ExeBuilder));
|
||||
|
||||
if(exeb == NULL)
|
||||
return NULL;
|
||||
|
||||
exeb->promc = 0;
|
||||
exeb->data = BucketList_New(alloc);
|
||||
exeb->code = BucketList_New(alloc);
|
||||
|
||||
if(exeb->data == NULL || exeb->code == NULL)
|
||||
return NULL;
|
||||
return exeb;
|
||||
}
|
||||
|
||||
Executable *ExeBuilder_Finalize(ExeBuilder *exeb, Error *error)
|
||||
{
|
||||
assert(exeb != NULL);
|
||||
|
||||
if(exeb->promc > 0)
|
||||
{
|
||||
Error_Report(error, 0, "There are still %d unfulfilled promises", exeb->promc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Executable *exe;
|
||||
{
|
||||
int data_size = BucketList_Size(exeb->data);
|
||||
int code_size = BucketList_Size(exeb->code);
|
||||
|
||||
assert(code_size % sizeof(Instruction) == 0);
|
||||
|
||||
void *temp = malloc(sizeof(Executable) + data_size + code_size);
|
||||
|
||||
if(temp == NULL)
|
||||
{
|
||||
Error_Report(error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
exe = temp;
|
||||
exe->headl = data_size;
|
||||
exe->bodyl = code_size / sizeof(Instruction);
|
||||
exe->body = (Instruction*) (exe + 1);
|
||||
exe->head = (char*) (exe->body + exe->bodyl);
|
||||
exe->refs = 1;
|
||||
exe->src = NULL;
|
||||
|
||||
}
|
||||
|
||||
BucketList_Copy(exeb->data, exe->head, -1);
|
||||
BucketList_Copy(exeb->code, exe->body, -1);
|
||||
return exe;
|
||||
}
|
||||
|
||||
static void promise_callback(void *userp)
|
||||
{
|
||||
assert(userp != NULL);
|
||||
|
||||
ExeBuilder *exeb = userp;
|
||||
|
||||
exeb->promc -= 1;
|
||||
assert(exeb->promc >= 0);
|
||||
}
|
||||
|
||||
_Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand *opv, int opc, int off, int len)
|
||||
{
|
||||
assert(exeb != NULL);
|
||||
assert(opc >= 0);
|
||||
|
||||
const InstrInfo *info = instr_table + opcode;
|
||||
|
||||
if(opc != info->opcount)
|
||||
{
|
||||
// ERROR: Too many operands were provided.
|
||||
Error_Report(error, 0,
|
||||
"Instruction %s expects %d operands, but %d were provided",
|
||||
info->name, opc, info->opcount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(opc <= MAX_OPS);
|
||||
|
||||
{
|
||||
Instruction *instr = BucketList_Append2(exeb->code, NULL, sizeof(Instruction));
|
||||
|
||||
if(instr == NULL)
|
||||
{
|
||||
Error_Report(error, 1, "No memory");
|
||||
return 0;
|
||||
}
|
||||
|
||||
instr->opcode = opcode;
|
||||
instr->offset = off;
|
||||
instr->length = len;
|
||||
|
||||
for(int i = 0; i < opc; i += 1)
|
||||
{
|
||||
// Check that the expected type and the provided one
|
||||
// match, or that the provided type is a promise with
|
||||
// the same size of the expected type.
|
||||
{
|
||||
OperandType expected_type = info->optypes[i];
|
||||
OperandType provided_type = opv[i].type;
|
||||
|
||||
assert(expected_type != OPTP_PROMISE);
|
||||
|
||||
if(provided_type == OPTP_PROMISE)
|
||||
{
|
||||
assert(opv[i].as_promise != NULL);
|
||||
|
||||
if(expected_type == OPTP_STRING)
|
||||
{
|
||||
Error_Report(error, 0, "Promise values can't be provided as string operands");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(Promise_Size(opv[i].as_promise) != operand_type_sizes[expected_type])
|
||||
{
|
||||
Error_Report(error, 0,
|
||||
"Provided promise has a value size of %d, "
|
||||
"but since %s %s was expected, the promise "
|
||||
"size must be %d",
|
||||
Promise_Size(opv[i].as_promise),
|
||||
operand_type_arts[expected_type],
|
||||
operand_type_names[expected_type],
|
||||
operand_type_sizes[expected_type]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(expected_type != provided_type)
|
||||
{
|
||||
// ERROR: Wrong operand type provided.
|
||||
Error_Report(error, 0,
|
||||
"Instruction %s expects %s %s as operand %d, but %s %s was provided instead",
|
||||
info->name,
|
||||
operand_type_arts[expected_type],
|
||||
operand_type_names[expected_type],
|
||||
i,
|
||||
operand_type_arts[provided_type],
|
||||
operand_type_names[provided_type]
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the copying of the operands.
|
||||
switch(opv[i].type)
|
||||
{
|
||||
case OPTP_STRING:
|
||||
|
||||
instr->operands[i].as_int = BucketList_Size(exeb->data);
|
||||
|
||||
if(!BucketList_Append(exeb->data, opv[i].as_string, strlen(opv[i].as_string)+1))
|
||||
{
|
||||
Error_Report(error, 1, "No memory");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPTP_PROMISE:
|
||||
|
||||
assert(info->optypes[i] != OPTP_STRING);
|
||||
|
||||
// This must be incremented before subscribing
|
||||
// since the counter-decrementing callback may
|
||||
// be called immediately if the promise was
|
||||
// already fulfilled.
|
||||
exeb->promc += 1;
|
||||
|
||||
if(!Promise_Subscribe2(opv[i].as_promise, &instr->operands[i], exeb, promise_callback))
|
||||
{
|
||||
Error_Report(error, 1, "No memory");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPTP_INT:
|
||||
instr->operands[i].as_int = opv[i].as_int;
|
||||
break;
|
||||
|
||||
case OPTP_FLOAT:
|
||||
instr->operands[i].as_float = opv[i].as_float;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef EXECUTABLE_H
|
||||
#define EXECUTABLE_H
|
||||
#include "../utils/source.h"
|
||||
#include "../utils/promise.h"
|
||||
|
||||
typedef enum {
|
||||
OPTP_INT,
|
||||
OPTP_FLOAT,
|
||||
OPTP_STRING,
|
||||
OPTP_PROMISE,
|
||||
} OperandType;
|
||||
|
||||
typedef struct {
|
||||
OperandType type;
|
||||
union {
|
||||
long long int as_int;
|
||||
double as_float;
|
||||
const char *as_string;
|
||||
Promise *as_promise;
|
||||
};
|
||||
} Operand;
|
||||
|
||||
typedef enum {
|
||||
OPCODE_NOPE,
|
||||
OPCODE_POS,
|
||||
OPCODE_NEG,
|
||||
OPCODE_ADD,
|
||||
OPCODE_SUB,
|
||||
OPCODE_MUL,
|
||||
OPCODE_DIV,
|
||||
OPCODE_PUSHI,
|
||||
OPCODE_PUSHF,
|
||||
OPCODE_PUSHS,
|
||||
OPCODE_PUSHV,
|
||||
OPCODE_RETURN,
|
||||
} Opcode;
|
||||
|
||||
typedef struct xExecutable Executable;
|
||||
typedef struct xExeBuilder ExeBuilder;
|
||||
|
||||
Executable *Executable_Copy(Executable *exe);
|
||||
void Executable_Free(Executable *exe);
|
||||
_Bool Executable_Fetch(Executable *exe, int index, Opcode *opcode, Operand *ops, int *opc);
|
||||
_Bool Executable_SetSource(Executable *exe, Source *src);
|
||||
Source *Executable_GetSource(Executable *exe);
|
||||
|
||||
ExeBuilder *ExeBuilder_New(BPAlloc *alloc);
|
||||
_Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand *opv, int opc, int off, int len);
|
||||
Executable *ExeBuilder_Finalize(ExeBuilder *exeb, Error *error);
|
||||
#endif
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef AST_H
|
||||
#define AST_H
|
||||
typedef struct xAST AST;
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
#ifndef ASTi_H
|
||||
#define ASTi_H
|
||||
#include "../utils/source.h"
|
||||
#include "AST.h"
|
||||
|
||||
typedef enum {
|
||||
NODE_EXPR,
|
||||
} NodeKind;
|
||||
|
||||
typedef enum {
|
||||
EXPR_POS,
|
||||
EXPR_NEG,
|
||||
EXPR_ADD,
|
||||
EXPR_SUB,
|
||||
EXPR_MUL,
|
||||
EXPR_DIV,
|
||||
EXPR_INT,
|
||||
EXPR_FLOAT,
|
||||
EXPR_STRING,
|
||||
EXPR_IDENT,
|
||||
} ExprKind;
|
||||
|
||||
typedef struct Node Node;
|
||||
|
||||
struct xAST {
|
||||
Source *src;
|
||||
Node *root;
|
||||
};
|
||||
|
||||
struct Node {
|
||||
NodeKind kind;
|
||||
Node *next;
|
||||
int offset,
|
||||
length;
|
||||
char body[];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Node base;
|
||||
ExprKind kind;
|
||||
char body[];
|
||||
} ExprNode;
|
||||
|
||||
typedef struct {
|
||||
ExprNode base;
|
||||
long long int val;
|
||||
} IntExprNode;
|
||||
|
||||
typedef struct {
|
||||
ExprNode base;
|
||||
double val;
|
||||
} FloatExprNode;
|
||||
|
||||
typedef struct {
|
||||
ExprNode base;
|
||||
char *val;
|
||||
int len;
|
||||
} StringExprNode;
|
||||
|
||||
typedef struct {
|
||||
ExprNode base;
|
||||
char *val;
|
||||
int len;
|
||||
} IdentExprNode;
|
||||
|
||||
typedef struct {
|
||||
ExprNode base;
|
||||
Node *head;
|
||||
int count;
|
||||
} OperExprNode;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,123 @@
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdlib.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "compile.h"
|
||||
#include "ASTi.h"
|
||||
|
||||
static Opcode exprkind_to_opcode(ExprKind kind)
|
||||
{
|
||||
switch(kind)
|
||||
{
|
||||
case EXPR_POS: return OPCODE_POS;
|
||||
case EXPR_NEG: return OPCODE_NEG;
|
||||
case EXPR_ADD: return OPCODE_ADD;
|
||||
case EXPR_SUB: return OPCODE_SUB;
|
||||
case EXPR_MUL: return OPCODE_MUL;
|
||||
case EXPR_DIV: return OPCODE_DIV;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error)
|
||||
{
|
||||
assert(node != NULL);
|
||||
|
||||
switch(node->kind)
|
||||
{
|
||||
case NODE_EXPR:
|
||||
{
|
||||
ExprNode *expr = (ExprNode*) node;
|
||||
switch(expr->kind)
|
||||
{
|
||||
case EXPR_POS:
|
||||
case EXPR_NEG:
|
||||
case EXPR_ADD:
|
||||
case EXPR_SUB:
|
||||
case EXPR_MUL:
|
||||
case EXPR_DIV:
|
||||
{
|
||||
OperExprNode *oper = (OperExprNode*) expr;
|
||||
|
||||
for(Node *operand = oper->head; operand; operand = operand->next)
|
||||
emit_instr_for_node(exeb, operand, error);
|
||||
|
||||
return ExeBuilder_Append(exeb, error,
|
||||
exprkind_to_opcode(expr->kind),
|
||||
NULL, 0, node->offset, node->length);
|
||||
}
|
||||
|
||||
case EXPR_INT:
|
||||
{
|
||||
IntExprNode *p = (IntExprNode*) expr;
|
||||
Operand op = { .type = OPTP_INT, .as_int = p->val };
|
||||
return ExeBuilder_Append(exeb, error, OPCODE_PUSHI, &op, 1, node->offset, node->length);
|
||||
}
|
||||
|
||||
case EXPR_FLOAT:
|
||||
{
|
||||
FloatExprNode *p = (FloatExprNode*) expr;
|
||||
Operand op = { .type = OPTP_FLOAT, .as_float = p->val };
|
||||
return ExeBuilder_Append(exeb, error, OPCODE_PUSHF, &op, 1, node->offset, node->length);
|
||||
}
|
||||
|
||||
case EXPR_STRING:
|
||||
{
|
||||
StringExprNode *p = (StringExprNode*) expr;
|
||||
Operand op = { .type = OPTP_STRING, .as_string = p->val };
|
||||
return ExeBuilder_Append(exeb, error, OPCODE_PUSHS, &op, 1, node->offset, node->length);
|
||||
}
|
||||
|
||||
case EXPR_IDENT:
|
||||
{
|
||||
IdentExprNode *p = (IdentExprNode*) expr;
|
||||
Operand op = { .type = OPTP_STRING, .as_string = p->val };
|
||||
return ExeBuilder_Append(exeb, error, OPCODE_PUSHV, &op, 1, node->offset, node->length);
|
||||
}
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Executable *compile(AST *ast, BPAlloc *alloc, Error *error)
|
||||
{
|
||||
assert(ast != NULL);
|
||||
assert(error != NULL);
|
||||
|
||||
BPAlloc *alloc2 = alloc;
|
||||
|
||||
if(alloc2 == NULL)
|
||||
{
|
||||
alloc2 = BPAlloc_Init(-1);
|
||||
|
||||
if(alloc2 == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Executable *exe = NULL;
|
||||
ExeBuilder *exeb = ExeBuilder_New(alloc2);
|
||||
|
||||
if(exeb)
|
||||
{
|
||||
if(!emit_instr_for_node(exeb, ast->root, error))
|
||||
return 0;
|
||||
|
||||
if(ExeBuilder_Append(exeb, error, OPCODE_RETURN, NULL, 0, Source_GetSize(ast->src), 0))
|
||||
exe = ExeBuilder_Finalize(exeb, error);
|
||||
}
|
||||
|
||||
if(alloc == NULL)
|
||||
BPAlloc_Free(alloc2);
|
||||
return exe;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef COMPILE_H
|
||||
#define COMPILE_H
|
||||
#include "../utils/error.h"
|
||||
#include "../utils/bpalloc.h"
|
||||
#include "../common/executable.h"
|
||||
#include "AST.h"
|
||||
Executable *compile(AST *ast, BPAlloc *alloc, Error *error);
|
||||
#endif
|
||||
@@ -0,0 +1,762 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include "parse.h"
|
||||
#include "ASTi.h"
|
||||
|
||||
#define UNREACHABLE assert(0)
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL ((void*) 0)
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
|
||||
TPOS = '+',
|
||||
TNEG = '-',
|
||||
|
||||
TADD = '+',
|
||||
TSUB = '-',
|
||||
TMUL = '*',
|
||||
TDIV = '/',
|
||||
TASS = '=',
|
||||
|
||||
TDONE = 256,
|
||||
TINT,
|
||||
TFLOAT,
|
||||
TSTRING,
|
||||
TIDENT,
|
||||
} TokenKind;
|
||||
|
||||
typedef struct Token Token;
|
||||
struct Token {
|
||||
TokenKind kind;
|
||||
Token *prev, *next;
|
||||
int offset, length;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *src;
|
||||
Token *token;
|
||||
BPAlloc *alloc;
|
||||
Error *error;
|
||||
} Context;
|
||||
|
||||
static Node *parse_statement(Context *ctx);
|
||||
static Node *parse_expression(Context *ctx);
|
||||
static Node *parse_expression_statement(Context *ctx);
|
||||
|
||||
static inline _Bool isoper(char c)
|
||||
{
|
||||
return c == '+' ||
|
||||
c == '-' ||
|
||||
c == '*' ||
|
||||
c == '/' ||
|
||||
c == '=';
|
||||
}
|
||||
|
||||
AST *parse(Source *src, BPAlloc *alloc, Error *error)
|
||||
{
|
||||
assert(src != NULL);
|
||||
assert(alloc != NULL);
|
||||
|
||||
const char *str = Source_GetBody(src);
|
||||
int len = Source_GetSize(src);
|
||||
assert(str != NULL);
|
||||
assert(len >= 0);
|
||||
|
||||
AST *ast = BPAlloc_Malloc(alloc, sizeof(AST));
|
||||
|
||||
if(ast == NULL)
|
||||
return NULL;
|
||||
|
||||
Token *head = NULL,
|
||||
*tail = NULL;
|
||||
int i = 0;
|
||||
while(1)
|
||||
{
|
||||
// Skip whitespace and comments.
|
||||
while(i < len && (isspace(str[i]) || str[i] == '#'))
|
||||
{
|
||||
while(i < len && isspace(str[i]))
|
||||
i += 1;
|
||||
|
||||
if(str[i] == '#')
|
||||
{
|
||||
i += 1;
|
||||
|
||||
while(i < len && str[i] != '\n')
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(i == len)
|
||||
break; // No more tokens left.
|
||||
|
||||
Token *tok = BPAlloc_Malloc(alloc, sizeof(Token)); // Allocate a token.
|
||||
|
||||
if(tok == NULL)
|
||||
{
|
||||
// Error: No memory.
|
||||
Error_Report(error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(isalpha(str[i]) || str[i] == '_')
|
||||
{
|
||||
tok->kind = TIDENT;
|
||||
tok->offset = i;
|
||||
|
||||
while(i < len && (isalpha(str[i]) || str[i] == '_'))
|
||||
i += 1;
|
||||
|
||||
tok->length = i - tok->offset;
|
||||
}
|
||||
else if(isdigit(str[i]))
|
||||
{
|
||||
tok->kind = TINT;
|
||||
tok->offset = i;
|
||||
|
||||
while(i < len && isdigit(str[i]))
|
||||
i += 1;
|
||||
|
||||
if(i+1 < len && str[i] == '.' && isdigit(str[i+1]))
|
||||
{
|
||||
i += 1; // Consume the dot.
|
||||
|
||||
tok->kind = TFLOAT;
|
||||
|
||||
while(i < len && isdigit(str[i]))
|
||||
i += 1;
|
||||
}
|
||||
|
||||
tok->length = i - tok->offset;
|
||||
}
|
||||
else if(str[i] == '\'' || str[i] == '"')
|
||||
{
|
||||
tok->kind = TSTRING;
|
||||
tok->offset = i;
|
||||
|
||||
char f = str[i];
|
||||
|
||||
i += 1; // Skip the starting quote.
|
||||
|
||||
while(i < len && str[i] != f)
|
||||
i += 1;
|
||||
|
||||
if(i == len)
|
||||
{
|
||||
Error_Report(error, 0, "Source ended inside string literal");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i += 1; // Consume the ' or ".
|
||||
|
||||
tok->length = i - tok->offset;
|
||||
}
|
||||
else if(isoper(str[i]))
|
||||
{
|
||||
tok->offset = i;
|
||||
|
||||
while(i < len && isoper(str[i]))
|
||||
i += 1;
|
||||
|
||||
tok->length = i - tok->offset;
|
||||
|
||||
// Determine the token
|
||||
|
||||
#define matchop(str, len, const_str) \
|
||||
(len == sizeof(const_str)-1 && !strncmp(str, const_str, sizeof(const_str)-1))
|
||||
|
||||
if(matchop(str + tok->offset, tok->length, "+"))
|
||||
{
|
||||
tok->kind = TADD;
|
||||
}
|
||||
else if(matchop(str + tok->offset, tok->length, "-"))
|
||||
{
|
||||
tok->kind = TSUB;
|
||||
}
|
||||
else if(matchop(str + tok->offset, tok->length, "*"))
|
||||
{
|
||||
tok->kind = TMUL;
|
||||
}
|
||||
else if(matchop(str + tok->offset, tok->length, "/"))
|
||||
{
|
||||
tok->kind = TDIV;
|
||||
}
|
||||
else if(matchop(str + tok->offset, tok->length, "="))
|
||||
{
|
||||
tok->kind = TASS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a known operator.
|
||||
tok->kind = str[tok->offset];
|
||||
tok->length = 1;
|
||||
i = tok->offset + 1;
|
||||
}
|
||||
|
||||
#undef matchop
|
||||
}
|
||||
else
|
||||
{
|
||||
tok->kind = str[i];
|
||||
tok->offset = i;
|
||||
tok->length = 1;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Append to the token list.
|
||||
if(head)
|
||||
tail->next = tok;
|
||||
else
|
||||
head = tok;
|
||||
tok->prev = tail;
|
||||
tok->next = NULL;
|
||||
tail = tok;
|
||||
}
|
||||
|
||||
{
|
||||
Token *tok = BPAlloc_Malloc(alloc, sizeof(Token)); // Allocate a token.
|
||||
|
||||
if(tok == NULL)
|
||||
{
|
||||
// Error: No memory.
|
||||
Error_Report(error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tok->kind = TDONE;
|
||||
tok->offset = i;
|
||||
tok->length = 0;
|
||||
|
||||
if(head)
|
||||
tail->next = tok;
|
||||
else
|
||||
head = tok;
|
||||
tok->prev = tail;
|
||||
tok->next = NULL;
|
||||
tail = tok;
|
||||
}
|
||||
|
||||
Context ctx;
|
||||
ctx.src = str;
|
||||
ctx.token = head;
|
||||
ctx.alloc = alloc;
|
||||
ctx.error = error;
|
||||
|
||||
Node *root = parse_statement(&ctx);
|
||||
|
||||
if(root == NULL)
|
||||
return NULL;
|
||||
|
||||
ast->src = src; // Not copying! Be sure to not free the source before the AST!
|
||||
ast->root = root;
|
||||
|
||||
if(ast->src == NULL)
|
||||
return NULL;
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
static Node *parse_statement(Context *ctx)
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
|
||||
switch(ctx->token->kind)
|
||||
{
|
||||
case '*':
|
||||
case '/':
|
||||
case TASS:
|
||||
case TDONE:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
|
||||
case '(':
|
||||
case '+':
|
||||
case '-':
|
||||
case TINT:
|
||||
case TFLOAT:
|
||||
case TSTRING:
|
||||
case TIDENT:
|
||||
return parse_expression_statement(ctx);
|
||||
}
|
||||
|
||||
Error_Report(ctx->error, 0, "Got token \"%.*s\" where the start of a statement was expected",
|
||||
ctx->token->length, ctx->src + ctx->token->offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Node *parse_expression_statement(Context *ctx)
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
|
||||
Node *expr = parse_expression(ctx);
|
||||
|
||||
if(expr == NULL)
|
||||
return NULL;
|
||||
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
if(ctx->token->kind == TDONE)
|
||||
// If the source ended before
|
||||
// the final ";", it's ok.
|
||||
return expr;
|
||||
|
||||
if(ctx->token->kind != ';')
|
||||
{
|
||||
// ERROR: Got something other than
|
||||
// a semicolon at the end of statement.
|
||||
Error_Report(ctx->error, 0, "Got token \"%.*s\" where \";\" was expected", ctx->token->length, ctx->src + ctx->token->offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
static Node *parse_string_primary_expression(Context *ctx)
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
assert(ctx->token->kind == TSTRING);
|
||||
|
||||
const char *src = ctx->src;
|
||||
int len = ctx->token->offset + ctx->token->length - 1;
|
||||
int i = ctx->token->offset + 1;
|
||||
|
||||
assert(src[i-1] == '"' || src[i-1] == '\'');
|
||||
assert(src[len] == '"' || src[len] == '\'');
|
||||
|
||||
char temp[4096];
|
||||
int temp_used = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int segm_off, segm_len;
|
||||
{
|
||||
segm_off = i;
|
||||
|
||||
while(i < len && src[i] != '\\')
|
||||
i += 1;
|
||||
|
||||
segm_len = i - segm_off;
|
||||
}
|
||||
|
||||
if(temp_used + segm_len >= (int) sizeof(temp))
|
||||
{
|
||||
Error_Report(ctx->error, 1, "String is too big to be rendered inside the fixed size buffer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(temp + temp_used, src + segm_off, segm_len);
|
||||
temp_used += segm_len;
|
||||
|
||||
if(src[i] == '\\')
|
||||
{
|
||||
i += 1; // Consume the \.
|
||||
|
||||
if(temp_used + 1 >= (int) sizeof(temp))
|
||||
{
|
||||
Error_Report(ctx->error, 1, "String is too big to be rendered inside the fixed size buffer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(i == len)
|
||||
{
|
||||
// Append the \ as a normal char.
|
||||
temp[temp_used++] = '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(src[i])
|
||||
{
|
||||
case 'n': temp[temp_used++] = '\n'; break;
|
||||
case 't': temp[temp_used++] = '\t'; break;
|
||||
case 'r': temp[temp_used++] = '\r'; break;
|
||||
|
||||
default:
|
||||
Error_Report(ctx->error, 0, "Invalid escape sequence \\%c", src[i]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i += 1; // Consume the char after the \.
|
||||
}
|
||||
}
|
||||
}
|
||||
while(i < len);
|
||||
|
||||
assert(temp_used < (int) sizeof(temp));
|
||||
|
||||
temp[temp_used] = '\0';
|
||||
|
||||
char *copy;
|
||||
int copyl;
|
||||
|
||||
{
|
||||
copy = BPAlloc_Malloc(ctx->alloc, temp_used + 1);
|
||||
|
||||
if(copy == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strcpy(copy, temp);
|
||||
copyl = temp_used;
|
||||
}
|
||||
|
||||
StringExprNode *node;
|
||||
{
|
||||
node = BPAlloc_Malloc(ctx->alloc, sizeof(StringExprNode));
|
||||
|
||||
if(node == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->base.base.kind = NODE_EXPR;
|
||||
node->base.base.next = NULL;
|
||||
node->base.base.offset = ctx->token->offset;
|
||||
node->base.base.length = ctx->token->length;
|
||||
node->base.kind = EXPR_STRING;
|
||||
node->val = copy;
|
||||
node->len = copyl;
|
||||
}
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
return (Node*) node;
|
||||
}
|
||||
|
||||
static Node *parse_primary_expresion(Context *ctx)
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
|
||||
switch(ctx->token->kind)
|
||||
{
|
||||
case '+':
|
||||
case '-':
|
||||
{
|
||||
Token *operator = ctx->token;
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
Node *operand = parse_primary_expresion(ctx);
|
||||
|
||||
if(operand == NULL)
|
||||
return NULL;
|
||||
|
||||
OperExprNode *temp = BPAlloc_Malloc(ctx->alloc, sizeof(OperExprNode));
|
||||
{
|
||||
if(temp == NULL)
|
||||
return NULL;
|
||||
|
||||
temp->base.base.kind = NODE_EXPR;
|
||||
temp->base.base.next = NULL;
|
||||
temp->base.base.offset = operator->offset;
|
||||
temp->base.base.length = operand->offset + operand->length - operator->offset;
|
||||
temp->base.kind = operator->kind == '+' ? EXPR_POS : EXPR_NEG;
|
||||
temp->head = operand;
|
||||
temp->count = 1;
|
||||
}
|
||||
return (Node*) temp;
|
||||
}
|
||||
|
||||
case '(':
|
||||
{
|
||||
ctx->token = ctx->token->next;
|
||||
|
||||
Node *node = parse_expression(ctx);
|
||||
|
||||
if(node == NULL)
|
||||
return NULL;
|
||||
|
||||
if(ctx->token->kind != ')')
|
||||
{
|
||||
Error_Report(ctx->error, 0, "Missing \")\", after sub-expression");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
case TINT:
|
||||
{
|
||||
char buffer[64];
|
||||
|
||||
if(ctx->token->length >= (int) sizeof(buffer))
|
||||
{
|
||||
Error_Report(ctx->error, 1, "Integer is too big");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(buffer, ctx->src + ctx->token->offset, ctx->token->length);
|
||||
buffer[ctx->token->length] = '\0';
|
||||
|
||||
errno = 0;
|
||||
|
||||
long long int val = strtoll(buffer, NULL, 10);
|
||||
|
||||
if(errno == ERANGE)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "Integer is too big");
|
||||
return NULL;
|
||||
}
|
||||
else assert(errno == 0);
|
||||
|
||||
IntExprNode *node;
|
||||
{
|
||||
node = BPAlloc_Malloc(ctx->alloc, sizeof(IntExprNode));
|
||||
|
||||
if(node == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->base.base.kind = NODE_EXPR;
|
||||
node->base.base.next = NULL;
|
||||
node->base.base.offset = ctx->token->offset;
|
||||
node->base.base.length = ctx->token->length;
|
||||
node->base.kind = EXPR_INT;
|
||||
node->val = val;
|
||||
}
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
return (Node*) node;
|
||||
}
|
||||
|
||||
case TFLOAT:
|
||||
{
|
||||
char buffer[64];
|
||||
|
||||
if(ctx->token->length >= (int) sizeof(buffer))
|
||||
{
|
||||
Error_Report(ctx->error, 1, "Floating is too big");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(buffer, ctx->src + ctx->token->offset, ctx->token->length);
|
||||
buffer[ctx->token->length] = '\0';
|
||||
|
||||
errno = 0;
|
||||
|
||||
double val = strtod(buffer, NULL);
|
||||
|
||||
if(errno == ERANGE)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "Floating is too big");
|
||||
return NULL;
|
||||
}
|
||||
else assert(errno == 0);
|
||||
|
||||
FloatExprNode *node;
|
||||
{
|
||||
node = BPAlloc_Malloc(ctx->alloc, sizeof(FloatExprNode));
|
||||
|
||||
if(node == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->base.base.kind = NODE_EXPR;
|
||||
node->base.base.next = NULL;
|
||||
node->base.base.offset = ctx->token->offset;
|
||||
node->base.base.length = ctx->token->length;
|
||||
node->base.kind = EXPR_FLOAT;
|
||||
node->val = val;
|
||||
}
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
return (Node*) node;
|
||||
}
|
||||
|
||||
case TSTRING:
|
||||
return parse_string_primary_expression(ctx);
|
||||
|
||||
case TIDENT:
|
||||
{
|
||||
char *copy;
|
||||
int copyl;
|
||||
|
||||
{
|
||||
copy = BPAlloc_Malloc(ctx->alloc, ctx->token->length + 1);
|
||||
|
||||
if(copy == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
copyl = ctx->token->length;
|
||||
memcpy(copy, ctx->src + ctx->token->offset, copyl);
|
||||
copy[copyl] = '\0';
|
||||
|
||||
}
|
||||
|
||||
IdentExprNode *node;
|
||||
{
|
||||
node = BPAlloc_Malloc(ctx->alloc, sizeof(IdentExprNode));
|
||||
|
||||
if(node == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->base.base.kind = NODE_EXPR;
|
||||
node->base.base.next = NULL;
|
||||
node->base.base.offset = ctx->token->offset;
|
||||
node->base.base.length = ctx->token->length;
|
||||
node->base.kind = EXPR_IDENT;
|
||||
node->val = copy;
|
||||
node->len = copyl;
|
||||
}
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
return (Node*) node;
|
||||
}
|
||||
|
||||
case TDONE:
|
||||
Error_Report(ctx->error, 1, "Unexpected end of source where a primary expression was expected");
|
||||
return NULL;
|
||||
|
||||
default:
|
||||
Error_Report(ctx->error, 1, "Unexpected token \"%.*s\" where a primary expression was expected", ctx->token->length, ctx->src + ctx->token->offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline _Bool isbinop(Token *tok)
|
||||
{
|
||||
assert(tok != NULL);
|
||||
|
||||
return tok->kind == '+' ||
|
||||
tok->kind == '-' ||
|
||||
tok->kind == '*' ||
|
||||
tok->kind == '/';
|
||||
}
|
||||
|
||||
static inline _Bool isrightassoc(Token *tok)
|
||||
{
|
||||
assert(tok != NULL);
|
||||
|
||||
(void) tok;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int precedenceof(Token *tok)
|
||||
{
|
||||
assert(tok != NULL);
|
||||
|
||||
switch(tok->kind)
|
||||
{
|
||||
case '+':
|
||||
case '-':
|
||||
return 1;
|
||||
|
||||
case '*':
|
||||
case '/':
|
||||
return 2;
|
||||
|
||||
default:
|
||||
return -10000000;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
return -100000000;
|
||||
}
|
||||
|
||||
static Node *parse_expression_2(Context *ctx, Node *left_expr, int min_prec)
|
||||
{
|
||||
while(isbinop(ctx->token) && precedenceof(ctx->token) >= min_prec)
|
||||
{
|
||||
Token *op = ctx->token;
|
||||
|
||||
ctx->token = ctx->token->next;
|
||||
assert(ctx->token != NULL);
|
||||
|
||||
Node *right_expr = parse_primary_expresion(ctx);
|
||||
|
||||
if(right_expr == NULL)
|
||||
return NULL;
|
||||
|
||||
while(isbinop(ctx->token) && (precedenceof(ctx->token) > precedenceof(op) || (precedenceof(ctx->token) == precedenceof(op) && isrightassoc(ctx->token))))
|
||||
{
|
||||
right_expr = parse_expression_2(ctx, right_expr, precedenceof(op) + 1);
|
||||
|
||||
if(right_expr == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OperExprNode *temp;
|
||||
{
|
||||
temp = BPAlloc_Malloc(ctx->alloc, sizeof(OperExprNode));
|
||||
|
||||
if(temp == NULL)
|
||||
{
|
||||
Error_Report(ctx->error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
temp->base.base.kind = NODE_EXPR;
|
||||
temp->base.base.next = NULL;
|
||||
temp->base.base.offset = left_expr->offset;
|
||||
temp->base.base.length = right_expr->offset + right_expr->length - left_expr->offset;
|
||||
|
||||
switch(op->kind)
|
||||
{
|
||||
case '+': temp->base.kind = EXPR_ADD; break;
|
||||
case '-': temp->base.kind = EXPR_SUB; break;
|
||||
case '*': temp->base.kind = EXPR_MUL; break;
|
||||
case '/': temp->base.kind = EXPR_DIV; break;
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
|
||||
temp->head = left_expr;
|
||||
temp->head->next = right_expr;
|
||||
temp->count = 2;
|
||||
assert(right_expr->next == NULL);
|
||||
}
|
||||
|
||||
left_expr = (Node*) temp;
|
||||
}
|
||||
|
||||
return left_expr;
|
||||
}
|
||||
|
||||
static Node *parse_expression(Context *ctx)
|
||||
{
|
||||
Node *left_expr = parse_primary_expresion(ctx);
|
||||
|
||||
if(left_expr == NULL)
|
||||
return NULL;
|
||||
|
||||
if(ctx->token->kind == TDONE)
|
||||
return left_expr;
|
||||
|
||||
return parse_expression_2(ctx, left_expr, -1000000000);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef PARSE_H
|
||||
#define PARSE_H
|
||||
#include "../utils/bpalloc.h"
|
||||
#include "../utils/source.h"
|
||||
#include "../utils/error.h"
|
||||
#include "AST.h"
|
||||
AST *parse(Source *src, BPAlloc *alloc, Error *error);
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
#include <assert.h>
|
||||
#include <xjson.h>
|
||||
#include "serialize.h"
|
||||
#include "ASTi.h"
|
||||
|
||||
#define UNREACHABLE assert(0)
|
||||
|
||||
static xj_value *convert_node(Node *node, xj_error *error, xj_alloc **alloc);
|
||||
|
||||
char *serialize(AST *ast, int *len)
|
||||
{
|
||||
xj_alloc *alloc = NULL;
|
||||
xj_error error;
|
||||
|
||||
xj_value *value = convert_node(ast->root, &error, &alloc);
|
||||
|
||||
if(value == NULL)
|
||||
{
|
||||
xj_free(alloc);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
char *serialized = xj_encode(value, len);
|
||||
xj_free(alloc);
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *expr_kind_name(ExprNode *expr)
|
||||
{
|
||||
switch(expr->kind)
|
||||
{
|
||||
case EXPR_POS: return "pos";
|
||||
case EXPR_NEG: return "neg";
|
||||
case EXPR_ADD: return "add";
|
||||
case EXPR_SUB: return "sub";
|
||||
case EXPR_MUL: return "mul";
|
||||
case EXPR_DIV: return "div";
|
||||
case EXPR_INT: return "int";
|
||||
case EXPR_FLOAT: return "float";
|
||||
case EXPR_STRING: return "string";
|
||||
case EXPR_IDENT: return "ident";
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static xj_value *convert_node_list(Node *head, xj_error *error, xj_alloc **alloc);
|
||||
|
||||
static xj_value *convert_node(Node *node, xj_error *error, xj_alloc **alloc)
|
||||
{
|
||||
switch(node->kind)
|
||||
{
|
||||
case NODE_EXPR:
|
||||
ExprNode *expr = (ExprNode*) node;
|
||||
switch(expr->kind)
|
||||
{
|
||||
case EXPR_POS:
|
||||
case EXPR_NEG:
|
||||
case EXPR_ADD:
|
||||
case EXPR_SUB:
|
||||
case EXPR_MUL:
|
||||
case EXPR_DIV:
|
||||
{
|
||||
OperExprNode *oper = (OperExprNode*) expr;
|
||||
|
||||
xj_value *operands = NULL;
|
||||
|
||||
if(oper->count > 0)
|
||||
{
|
||||
operands = convert_node_list((Node*) oper->head, error, alloc);
|
||||
|
||||
if(operands == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return xj_decodef(error, alloc,
|
||||
"{"
|
||||
"\t\"node-kind\": \"expr\", \n"
|
||||
"\t\"expr-kind\": %Q, \n"
|
||||
"\t\"operands\": %L\n"
|
||||
"}", expr_kind_name(expr), operands);
|
||||
}
|
||||
|
||||
case EXPR_INT:
|
||||
return xj_decodef(error, alloc,
|
||||
"{"
|
||||
"\t\"node-kind\": \"expr\", \n"
|
||||
"\t\"expr-kind\": %Q, \n"
|
||||
"\t\"value\": %d\n"
|
||||
"}", expr_kind_name(expr), ((IntExprNode*) expr)->val);
|
||||
|
||||
case EXPR_FLOAT:
|
||||
return xj_decodef(error, alloc,
|
||||
"{"
|
||||
"\t\"node-kind\": \"expr\", \n"
|
||||
"\t\"expr-kind\": %Q, \n"
|
||||
"\t\"value\": %f\n"
|
||||
"}", expr_kind_name(expr), ((FloatExprNode*) expr)->val);
|
||||
|
||||
case EXPR_STRING:
|
||||
return xj_decodef(error, alloc,
|
||||
"{"
|
||||
"\t\"node-kind\": \"expr\", \n"
|
||||
"\t\"expr-kind\": %Q, \n"
|
||||
"\t\"value\": %Q\n"
|
||||
"}", expr_kind_name(expr), ((StringExprNode*) expr)->val);
|
||||
|
||||
case EXPR_IDENT:
|
||||
return xj_decodef(error, alloc,
|
||||
"{"
|
||||
"\t\"node-kind\": \"expr\", \n"
|
||||
"\t\"expr-kind\": %Q, \n"
|
||||
"\t\"value\": %Q\n"
|
||||
"}", expr_kind_name(expr), ((IdentExprNode*) expr)->val);
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static xj_value *convert_node_list(Node *head, xj_error *error, xj_alloc **alloc)
|
||||
{
|
||||
xj_value *json_head = NULL;
|
||||
xj_value **json_tail = &json_head;
|
||||
|
||||
Node *curs = head;
|
||||
while(curs)
|
||||
{
|
||||
xj_value *temp = convert_node(curs, error, alloc);
|
||||
|
||||
if(temp == NULL)
|
||||
return NULL;
|
||||
|
||||
temp->next = NULL;
|
||||
|
||||
*json_tail = temp;
|
||||
json_tail = &temp->next;
|
||||
|
||||
curs = curs->next;
|
||||
}
|
||||
|
||||
return json_head;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifndef SERIALIZE_H
|
||||
#define SERIALIZE_H
|
||||
#include "AST.h"
|
||||
char *serialize(AST *ast, int *len);
|
||||
#endif
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "compiler/parse.h"
|
||||
#include "compiler/serialize.h"
|
||||
#include "compiler/compile.h"
|
||||
#include "runtime/runtime.h"
|
||||
#include "runtime/runtime_error.h"
|
||||
|
||||
/* $ noja -f <file>
|
||||
* $ noja -c <text>
|
||||
* $ noja -f <file> -o <file>
|
||||
*/
|
||||
|
||||
typedef enum { RUN, HELP, PARSE, VERSION, DISASSEMBLY } Action;
|
||||
|
||||
typedef struct {
|
||||
Action action;
|
||||
_Bool input_is_file, debug, verbose;
|
||||
const char *input, *output;
|
||||
} Options;
|
||||
|
||||
static int parse_args(Options *opts, int argc, char **argv, Error *error)
|
||||
{
|
||||
assert(argc >= 0);
|
||||
assert(opts != NULL);
|
||||
|
||||
opts->action = RUN;
|
||||
opts->input_is_file = 1;
|
||||
opts->input = NULL;
|
||||
opts->output = NULL;
|
||||
|
||||
int i;
|
||||
|
||||
for(i = 1; i < argc; i += 1)
|
||||
{
|
||||
if(!strcmp("-r", argv[i]) || !strcmp("--run", argv[i]))
|
||||
{
|
||||
opts->action = RUN;
|
||||
}
|
||||
else if(!strcmp("-p", argv[i]) || !strcmp("--parse", argv[i]))
|
||||
{
|
||||
opts->action = PARSE;
|
||||
}
|
||||
else if(!strcmp("-d", argv[i]) || !strcmp("--disassembly", argv[i]))
|
||||
{
|
||||
opts->action = DISASSEMBLY;
|
||||
}
|
||||
else if(!strcmp("-h", argv[i]) || !strcmp("--help", argv[i]))
|
||||
{
|
||||
opts->action = HELP;
|
||||
}
|
||||
else if(!strcmp("-v", argv[i]) || !strcmp("--version", argv[i]))
|
||||
{
|
||||
opts->action = VERSION;
|
||||
}
|
||||
else if(!strcmp("--debug", argv[i]))
|
||||
{
|
||||
opts->debug = 1;
|
||||
}
|
||||
else if(!strcmp("--verbose", argv[i]))
|
||||
{
|
||||
opts->verbose = 1;
|
||||
}
|
||||
else if(!strcmp("-f", argv[i]))
|
||||
{
|
||||
opts->input_is_file = 1;
|
||||
}
|
||||
else if(!strcmp("-c", argv[i]))
|
||||
{
|
||||
opts->input_is_file = 0;
|
||||
}
|
||||
else if(!strcmp("-o", argv[i]))
|
||||
{
|
||||
i += 1;
|
||||
|
||||
if(i == argc)
|
||||
{
|
||||
Error_Report(error, 0, "Option -o requires a file name after it");
|
||||
return -1;
|
||||
}
|
||||
|
||||
opts->output = argv[i];
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
if(i == argc)
|
||||
{
|
||||
Error_Report(error, 0, "Input file is missing");
|
||||
return -1;
|
||||
}
|
||||
|
||||
opts->input = argv[i];
|
||||
|
||||
i += 1;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
|
||||
// Parse command line options.
|
||||
|
||||
Options opts;
|
||||
{
|
||||
int i = parse_args(&opts, argc, argv, &error);
|
||||
|
||||
if(i < 0)
|
||||
{
|
||||
fprintf(stderr, "FATAL: %s.\n", error.message);
|
||||
Error_Free(&error);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
switch(opts.action)
|
||||
{
|
||||
case HELP:
|
||||
fprintf(stderr, "Not implemented yet.\n");
|
||||
return 1;
|
||||
|
||||
case VERSION:
|
||||
fprintf(stderr, "Not implemented yet.\n");
|
||||
return 1;
|
||||
|
||||
case RUN:
|
||||
{
|
||||
// Load
|
||||
Source *src;
|
||||
{
|
||||
if(opts.input_is_file)
|
||||
src = Source_FromFile(opts.input, &error);
|
||||
else
|
||||
src = Source_FromString(NULL, opts.input, -1, &error);
|
||||
|
||||
if(src == NULL)
|
||||
{
|
||||
fprintf(stderr, "FATAL: %s.\n", error.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Compile
|
||||
Executable *exe;
|
||||
{
|
||||
BPAlloc *alloc = BPAlloc_Init(-1);
|
||||
|
||||
if(alloc == NULL)
|
||||
return 1;
|
||||
|
||||
AST *ast = parse(src, alloc, &error);
|
||||
|
||||
if(ast == NULL)
|
||||
{
|
||||
fprintf(stderr, "PARSING ERROR: %s.\n", error.message);
|
||||
|
||||
Error_Free(&error);
|
||||
BPAlloc_Free(alloc);
|
||||
Source_Free(src);
|
||||
return 1;
|
||||
}
|
||||
|
||||
exe = compile(ast, alloc, &error);
|
||||
|
||||
if(exe == NULL)
|
||||
{
|
||||
fprintf(stderr, "COMPILATION ERROR: %s.\n", error.message);
|
||||
|
||||
Error_Free(&error);
|
||||
BPAlloc_Free(alloc);
|
||||
Source_Free(src);
|
||||
return 1;
|
||||
}
|
||||
|
||||
BPAlloc_Free(alloc);
|
||||
}
|
||||
|
||||
// Execute
|
||||
{
|
||||
Runtime *runtime = Runtime_New(-1, -1);
|
||||
|
||||
if(runtime == NULL)
|
||||
{
|
||||
fprintf(stderr, "Couldn't initialize runtime.\n");
|
||||
Executable_Free(exe);
|
||||
Source_Free(src);
|
||||
return 1;
|
||||
}
|
||||
|
||||
RuntimeError error;
|
||||
RuntimeError_Init(&error, runtime);
|
||||
|
||||
Object *result = run(runtime, (Error*) &error, exe, 0, NULL, 0);
|
||||
|
||||
if(result == NULL)
|
||||
{
|
||||
fprintf(stderr, "RUNTIME ERROR: %s.\n", error.base.message);
|
||||
|
||||
if(error.snapshot)
|
||||
Snapshot_Print(error.snapshot, stderr);
|
||||
else
|
||||
fprintf(stderr, "No snapshot available.\n");
|
||||
|
||||
Source_Free(src);
|
||||
Executable_Free(exe);
|
||||
RuntimeError_Free(&error);
|
||||
Runtime_Free(runtime);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(Object_IsInt(result))
|
||||
{
|
||||
long long int val = Object_ToInt(result, (Error*) &error);
|
||||
assert(error.base.occurred == 0);
|
||||
|
||||
fprintf(stderr, "%lld\n", val);
|
||||
}
|
||||
else if(Object_IsFloat(result))
|
||||
{
|
||||
double val = Object_ToFloat(result, (Error*) &error);
|
||||
assert(error.base.occurred == 0);
|
||||
|
||||
fprintf(stderr, "%f\n", val);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Not printing returned value since it's not an int or a float.\n");
|
||||
}
|
||||
}
|
||||
|
||||
Runtime_Free(runtime);
|
||||
}
|
||||
|
||||
Executable_Free(exe);
|
||||
Source_Free(src);
|
||||
break;
|
||||
}
|
||||
|
||||
case PARSE:
|
||||
{
|
||||
// Load
|
||||
Source *src;
|
||||
{
|
||||
if(opts.input_is_file)
|
||||
src = Source_FromFile(opts.input, &error);
|
||||
else
|
||||
src = Source_FromString(NULL, opts.input, -1, &error);
|
||||
|
||||
if(src == NULL)
|
||||
{
|
||||
fprintf(stderr, "FATAL: %s.\n", error.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
BPAlloc *alloc = BPAlloc_Init(-1);
|
||||
|
||||
if(alloc == NULL)
|
||||
return 1;
|
||||
|
||||
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
AST *ast = parse(src, alloc, &error);
|
||||
|
||||
if(ast == NULL)
|
||||
{
|
||||
fprintf(stderr, "PARSING ERROR: %s.\n", error.message);
|
||||
|
||||
Error_Free(&error);
|
||||
BPAlloc_Free(alloc);
|
||||
Source_Free(src);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int len;
|
||||
char *str = serialize(ast, &len);
|
||||
|
||||
_Bool failed = 0;
|
||||
|
||||
if(str == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to serialize\n");
|
||||
failed = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(opts.output == NULL)
|
||||
{
|
||||
fprintf(stdout, "%s\n", str);
|
||||
}
|
||||
else
|
||||
{
|
||||
FILE *fp = fopen(opts.output, "wb");
|
||||
|
||||
if(fp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Couldn't write to \"%s\".\n", opts.output);
|
||||
failed = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int k = fwrite(str, 1, len, fp);
|
||||
|
||||
if(k != len)
|
||||
{
|
||||
fprintf(stderr, "Only %d bytes out of %d could be written to \"%s\".\n", k, len, opts.output);
|
||||
failed = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(str);
|
||||
}
|
||||
|
||||
Source_Free(src);
|
||||
BPAlloc_Free(alloc);
|
||||
return failed;
|
||||
}
|
||||
|
||||
case DISASSEMBLY:
|
||||
fprintf(stderr, "Not implemented yet.\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "objects.h"
|
||||
|
||||
typedef struct OflowAlloc OflowAlloc;
|
||||
struct OflowAlloc {
|
||||
OflowAlloc *prev;
|
||||
char body[];
|
||||
};
|
||||
|
||||
struct xHeap {
|
||||
int size;
|
||||
int used;
|
||||
void *body;
|
||||
OflowAlloc *oflow;
|
||||
};
|
||||
|
||||
Heap *Heap_New(int size)
|
||||
{
|
||||
if(size < 0)
|
||||
size = 65536;
|
||||
|
||||
Heap *heap = malloc(sizeof(Heap));
|
||||
|
||||
if(heap == NULL)
|
||||
return NULL;
|
||||
|
||||
heap->size = size;
|
||||
heap->used = 0;
|
||||
heap->body = malloc(size);
|
||||
heap->oflow = 0;
|
||||
|
||||
if(heap->body == NULL)
|
||||
{
|
||||
free(heap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return heap;
|
||||
}
|
||||
|
||||
void Heap_Free(Heap *heap)
|
||||
{
|
||||
while(heap->oflow)
|
||||
{
|
||||
OflowAlloc *prev = heap->oflow->prev;
|
||||
free(heap->oflow);
|
||||
heap->oflow = prev;
|
||||
}
|
||||
|
||||
free(heap->body);
|
||||
free(heap);
|
||||
}
|
||||
|
||||
void *Heap_Malloc(Heap *heap, const Type *type, Error *err)
|
||||
{
|
||||
void *addr = Heap_RawMalloc(heap, type->size, err);
|
||||
|
||||
if(addr == NULL)
|
||||
return NULL;
|
||||
|
||||
Object *obj = addr;
|
||||
|
||||
obj->type = type;
|
||||
obj->flags = 0;
|
||||
|
||||
if(type->init && !type->init(obj, err))
|
||||
return NULL;
|
||||
|
||||
obj->type = type;
|
||||
obj->flags = 0;
|
||||
|
||||
return (Object*) addr;
|
||||
}
|
||||
|
||||
void *Heap_RawMalloc(Heap *heap, int size, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(heap);
|
||||
assert(size > -1);
|
||||
|
||||
void *addr;
|
||||
|
||||
if(heap->used + size >= heap->size)
|
||||
{
|
||||
OflowAlloc *oflow = malloc(sizeof(OflowAlloc) + size);
|
||||
|
||||
if(oflow == 0)
|
||||
return 0;
|
||||
|
||||
oflow->prev = heap->oflow;
|
||||
heap->oflow = oflow;
|
||||
|
||||
addr = oflow->body;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(heap->used & 7)
|
||||
heap->used = (heap->used & ~7) + 8;
|
||||
|
||||
addr = heap->body + heap->used;
|
||||
heap->used += size;
|
||||
}
|
||||
|
||||
assert(((intptr_t) addr) % 8 == 0);
|
||||
|
||||
return addr;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <assert.h>
|
||||
#include "objects.h"
|
||||
|
||||
static double to_float(Object *obj, Error *err);
|
||||
|
||||
typedef struct {
|
||||
Object base;
|
||||
double val;
|
||||
} FloatObject;
|
||||
|
||||
static const Type t_float = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "float",
|
||||
.size = sizeof (FloatObject),
|
||||
.atomic = ATMTP_FLOAT,
|
||||
.to_float = to_float,
|
||||
};
|
||||
|
||||
static double to_float(Object *obj, Error *err)
|
||||
{
|
||||
assert(obj);
|
||||
assert(err);
|
||||
assert(Object_GetType(obj) == &t_float);
|
||||
|
||||
(void) err;
|
||||
|
||||
return ((FloatObject*) obj)->val;
|
||||
}
|
||||
|
||||
Object *Object_FromFloat(double val, Heap *heap, Error *error)
|
||||
{
|
||||
FloatObject *obj = (FloatObject*) Heap_Malloc(heap, &t_float, error);
|
||||
|
||||
if(obj == 0)
|
||||
return 0;
|
||||
|
||||
obj->val = val;
|
||||
|
||||
return (Object*) obj;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "objects.h"
|
||||
|
||||
static long long int to_int(Object *obj, Error *err);
|
||||
|
||||
typedef struct {
|
||||
Object base;
|
||||
long long int val;
|
||||
} IntObject;
|
||||
|
||||
static const Type t_int = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "int",
|
||||
.size = sizeof (IntObject),
|
||||
.atomic = ATMTP_INT,
|
||||
.to_int = to_int,
|
||||
};
|
||||
|
||||
static long long int to_int(Object *obj, Error *err)
|
||||
{
|
||||
assert(obj);
|
||||
assert(err);
|
||||
assert(Object_GetType(obj) == &t_int);
|
||||
|
||||
(void) err;
|
||||
|
||||
return ((IntObject*) obj)->val;
|
||||
}
|
||||
|
||||
Object *Object_FromInt(long long int val, Heap *heap, Error *error)
|
||||
{
|
||||
IntObject *obj = (IntObject*) Heap_Malloc(heap, &t_int, error);
|
||||
|
||||
if(obj == 0)
|
||||
return 0;
|
||||
|
||||
obj->val = val;
|
||||
|
||||
return (Object*) obj;
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
#include <assert.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "objects.h"
|
||||
|
||||
typedef struct {
|
||||
Object base;
|
||||
int mapper_size, count;
|
||||
int *mapper;
|
||||
Object **keys;
|
||||
Object **vals;
|
||||
} MapObject;
|
||||
|
||||
static Object *select(Object *self, Object *key, Heap *heap, Error *err);
|
||||
static _Bool insert(Object *self, Object *key, Object *val, Heap *heap, Error *err);
|
||||
static int count(Object *self);
|
||||
|
||||
static const Type t_map = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "map",
|
||||
.size = sizeof (MapObject),
|
||||
.select = select,
|
||||
.insert = insert,
|
||||
.count = count,
|
||||
};
|
||||
|
||||
static inline int calc_capacity(int mapper_size)
|
||||
{
|
||||
return mapper_size * 2.0 / 3.0;
|
||||
}
|
||||
|
||||
Object *Object_NewMap(int num, Heap *heap, Error *error)
|
||||
{
|
||||
// Handle default args.
|
||||
if(num < 0)
|
||||
num = 0;
|
||||
|
||||
// Calculate initial mapper size.
|
||||
int mapper_size, capacity;
|
||||
{
|
||||
mapper_size = 8;
|
||||
while(calc_capacity(mapper_size) < num)
|
||||
mapper_size <<= 1;
|
||||
|
||||
capacity = calc_capacity(mapper_size);
|
||||
}
|
||||
|
||||
// Make the thing.
|
||||
MapObject *obj = (MapObject*) Heap_Malloc(heap, &t_map, error);
|
||||
{
|
||||
if(obj == 0)
|
||||
return 0;
|
||||
|
||||
obj->mapper_size = mapper_size;
|
||||
obj->count = 0;
|
||||
obj->mapper = Heap_RawMalloc(heap, sizeof(int) * capacity, error);
|
||||
obj->keys = Heap_RawMalloc(heap, sizeof(Object*) * capacity, error);
|
||||
obj->vals = Heap_RawMalloc(heap, sizeof(Object*) * capacity, error);
|
||||
|
||||
if(obj->mapper == NULL || obj->keys == NULL || obj->vals == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for(int i = 0; i < mapper_size; i += 1)
|
||||
obj->mapper[i] = -1;
|
||||
|
||||
return (Object*) obj;
|
||||
}
|
||||
|
||||
static Object *select(Object *self, Object *key, Heap *heap, Error *error)
|
||||
{
|
||||
assert(self != NULL);
|
||||
assert(self->type == &t_map);
|
||||
assert(key != NULL);
|
||||
assert(heap != NULL);
|
||||
assert(error != NULL);
|
||||
|
||||
MapObject *map = (MapObject*) self;
|
||||
|
||||
int mask = map->mapper_size - 1;
|
||||
int hash = Object_Hash(key, error);
|
||||
int pert = hash;
|
||||
|
||||
if(error->occurred)
|
||||
// No hash function.
|
||||
return 0;
|
||||
|
||||
int i = hash & mask;
|
||||
|
||||
while(1)
|
||||
{
|
||||
int k = map->mapper[i];
|
||||
|
||||
if(k == -1)
|
||||
{
|
||||
// Empty slot.
|
||||
// This key is not present.
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found an item.
|
||||
// Is it the right one?
|
||||
|
||||
assert(k >= 0);
|
||||
|
||||
if(Object_Compare(key, map->keys[i], error))
|
||||
// Found it!
|
||||
return map->vals[i];
|
||||
|
||||
if(error->occurred)
|
||||
// Key doesn't implement compare.
|
||||
return 0;
|
||||
|
||||
// Not the one we wanted.
|
||||
}
|
||||
|
||||
pert >>= 5;
|
||||
i = (i * 5 + pert + 1) & mask;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static _Bool grow(MapObject *map, Heap *heap, Error *error)
|
||||
{
|
||||
assert(map != NULL);
|
||||
|
||||
int new_mapper_size = map->mapper_size << 1;
|
||||
int new_capacity = calc_capacity(new_mapper_size);
|
||||
|
||||
int *mapper = Heap_RawMalloc(heap, sizeof(int) * new_mapper_size, error);
|
||||
Object **keys = Heap_RawMalloc(heap, sizeof(Object*) * new_capacity, error);
|
||||
Object **vals = Heap_RawMalloc(heap, sizeof(Object*) * new_capacity, error);
|
||||
|
||||
if(mapper == NULL || keys == NULL || vals == NULL)
|
||||
return 0;
|
||||
|
||||
for(int i = 0; i < map->count; i += 1)
|
||||
{
|
||||
keys[i] = map->keys[i];
|
||||
vals[i] = map->vals[i];
|
||||
}
|
||||
|
||||
for(int i = 0; i < new_mapper_size; i += 1)
|
||||
mapper[i] = -1;
|
||||
|
||||
// Rehash everything.
|
||||
for(int i = 0; i < map->count; i += 1)
|
||||
{
|
||||
// This won't trigger an error because the key
|
||||
// surely has a hash method since we already
|
||||
// hashed it once.
|
||||
int hash = Object_Hash(keys[i], error);
|
||||
assert(error->occurred == 0);
|
||||
|
||||
int mask = new_mapper_size - 1;
|
||||
int pert = hash;
|
||||
|
||||
int j = hash & mask;
|
||||
|
||||
while(1)
|
||||
{
|
||||
if(mapper[j] == -1)
|
||||
{
|
||||
// No collision.
|
||||
// Insert here.
|
||||
mapper[j] = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Collided. Find a new place.
|
||||
pert >>= 5;
|
||||
j = (j * 5 + pert + 1) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Done.
|
||||
map->mapper = mapper;
|
||||
map->mapper_size = new_mapper_size;
|
||||
map->keys = keys;
|
||||
map->vals = vals;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static _Bool insert(Object *self, Object *key, Object *val, Heap *heap, Error *error)
|
||||
{
|
||||
assert(error != NULL);
|
||||
assert(key != NULL);
|
||||
assert(val != NULL);
|
||||
assert(heap != NULL);
|
||||
assert(self != NULL);
|
||||
assert(self->type == &t_map);
|
||||
|
||||
MapObject *map = (MapObject*) self;
|
||||
|
||||
if(map->count == calc_capacity(map->mapper_size))
|
||||
if(!grow(map, heap, error))
|
||||
return 0;
|
||||
|
||||
int mask = map->mapper_size - 1;
|
||||
int hash = Object_Hash(key, error);
|
||||
int pert = hash;
|
||||
|
||||
if(error->occurred)
|
||||
// No hash function.
|
||||
return 0;
|
||||
|
||||
int i = hash & mask;
|
||||
|
||||
while(1)
|
||||
{
|
||||
int k = map->mapper[i];
|
||||
|
||||
if(k == -1)
|
||||
{
|
||||
// Empty slot. We can insert it here.
|
||||
Object *key_copy = Object_Copy(key, heap, error);
|
||||
|
||||
if(key_copy == NULL)
|
||||
return NULL;
|
||||
|
||||
map->mapper[i] = map->count;
|
||||
map->keys[map->count] = key_copy;
|
||||
map->vals[map->count] = val;
|
||||
map->count += 1;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(k >= 0);
|
||||
|
||||
if(Object_Compare(key, map->keys[i], error))
|
||||
{
|
||||
// Already inserted.
|
||||
// Overwrite the value.
|
||||
map->vals[i] = val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(error->occurred)
|
||||
// Key doesn't implement compare.
|
||||
return 0;
|
||||
|
||||
// Collision.
|
||||
}
|
||||
|
||||
pert >>= 5;
|
||||
i = (i * 5 + pert + 1) & mask;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int count(Object *self)
|
||||
{
|
||||
MapObject *map = (MapObject*) self;
|
||||
|
||||
return map->count;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "objects.h"
|
||||
|
||||
static const Type t_none = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "none",
|
||||
.size = sizeof (Object),
|
||||
};
|
||||
|
||||
static Object the_none_object = {
|
||||
.type = &t_none,
|
||||
.flags = Object_STATIC,
|
||||
};
|
||||
|
||||
Object *Object_NewNone(Heap *heap, Error *error)
|
||||
{
|
||||
(void) heap;
|
||||
(void) error;
|
||||
return &the_none_object;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "../utils/hash.h"
|
||||
#include "objects.h"
|
||||
|
||||
typedef struct {
|
||||
Object base;
|
||||
int size;
|
||||
char *body;
|
||||
} StringObject;
|
||||
|
||||
static int hash(Object *self);
|
||||
static int count(Object *self);
|
||||
|
||||
static const Type t_string = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "string",
|
||||
.size = sizeof (StringObject),
|
||||
.hash = hash,
|
||||
.count = count,
|
||||
};
|
||||
|
||||
Object *Object_FromString(const char *str, int len, Heap *heap, Error *error)
|
||||
{
|
||||
assert(str != NULL);
|
||||
assert(heap != NULL);
|
||||
assert(error != NULL);
|
||||
|
||||
if(len < 0)
|
||||
len = strlen(str);
|
||||
|
||||
StringObject *strobj = Heap_Malloc(heap, &t_string, error);
|
||||
|
||||
if(strobj == NULL)
|
||||
return NULL;
|
||||
|
||||
strobj->body = Heap_RawMalloc(heap, len+1, error);
|
||||
strobj->size = len;
|
||||
|
||||
if(strobj->body == NULL)
|
||||
return NULL;
|
||||
|
||||
memcpy(strobj->body, str, len);
|
||||
|
||||
strobj->body[len] = '\0';
|
||||
|
||||
return (Object*) strobj;
|
||||
}
|
||||
|
||||
static int count(Object *self)
|
||||
{
|
||||
assert(self != NULL);
|
||||
assert(self->type == &t_string);
|
||||
|
||||
StringObject *strobj = (StringObject*) self;
|
||||
|
||||
return strobj->size;
|
||||
}
|
||||
|
||||
static int hash(Object *self)
|
||||
{
|
||||
assert(self != NULL);
|
||||
assert(self->type == &t_string);
|
||||
|
||||
StringObject *strobj = (StringObject*) self;
|
||||
|
||||
return hashbytes((unsigned char*) strobj->body, strobj->size);
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
#include <assert.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "objects.h"
|
||||
|
||||
const Type t_type = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "type",
|
||||
.size = sizeof (Type),
|
||||
};
|
||||
|
||||
const char *Object_GetName(const Object *obj)
|
||||
{
|
||||
assert(obj);
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
const char *name = type->name;
|
||||
assert(name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
const Type *Object_GetType(const Object *obj)
|
||||
{
|
||||
assert(obj);
|
||||
assert(obj->type);
|
||||
return obj->type;
|
||||
}
|
||||
|
||||
unsigned int Object_GetSize(const Object *obj, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(obj);
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
return type->size;
|
||||
}
|
||||
|
||||
unsigned int Object_GetDeepSize(const Object *obj, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(obj);
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
if(type->deepsize == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return type->deepsize(obj);
|
||||
}
|
||||
|
||||
int Object_Hash(Object *obj, Error *err)
|
||||
{
|
||||
assert(obj);
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
if(type->hash == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return type->hash(obj);
|
||||
}
|
||||
|
||||
Object *Object_Copy(Object *obj, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(obj);
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
if(type->copy == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return type->copy(obj, heap, err);
|
||||
}
|
||||
|
||||
Object *Object_Call(Object *obj, Object **argv, unsigned int argc, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(obj);
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
if(type->call == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return type->call(obj, argv, argc, heap, err);
|
||||
}
|
||||
|
||||
Object *Object_Select(Object *coll, Object *key, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(key);
|
||||
assert(coll);
|
||||
|
||||
const Type *type = Object_GetType(coll);
|
||||
assert(type);
|
||||
|
||||
if(type->select == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(coll), __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return type->select(coll, key, heap, err);
|
||||
}
|
||||
|
||||
Object *Object_Delete(Object *coll, Object *key, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(key);
|
||||
assert(coll);
|
||||
|
||||
const Type *type = Object_GetType(coll);
|
||||
assert(type);
|
||||
|
||||
if(type->delete == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(coll), __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return type->delete(coll, key, heap, err);
|
||||
}
|
||||
|
||||
_Bool Object_Insert(Object *coll, Object *key, Object *val, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(key);
|
||||
assert(coll);
|
||||
|
||||
const Type *type = Object_GetType(coll);
|
||||
assert(type);
|
||||
|
||||
if(type->insert == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(coll), __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return type->insert(coll, key, val, heap, err);
|
||||
}
|
||||
|
||||
int Object_Count(Object *coll, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(coll);
|
||||
|
||||
const Type *type = Object_GetType(coll);
|
||||
assert(type);
|
||||
|
||||
if(type->count == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(coll), __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return type->count(coll);
|
||||
}
|
||||
|
||||
Object *Object_Next(Object *iter, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(iter);
|
||||
|
||||
const Type *type = Object_GetType(iter);
|
||||
assert(type);
|
||||
|
||||
if(type->next == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(iter), __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return type->next(iter, heap, err);
|
||||
}
|
||||
|
||||
Object *Object_Prev(Object *iter, Heap *heap, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(iter);
|
||||
|
||||
const Type *type = Object_GetType(iter);
|
||||
assert(type);
|
||||
|
||||
if(type->prev == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(iter), __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return type->prev(iter, heap, err);
|
||||
}
|
||||
|
||||
_Bool Object_IsInt(Object *obj)
|
||||
{
|
||||
assert(obj != NULL);
|
||||
assert(obj->type != NULL);
|
||||
return obj->type->atomic == ATMTP_INT;
|
||||
}
|
||||
|
||||
_Bool Object_IsFloat(Object *obj)
|
||||
{
|
||||
assert(obj != NULL);
|
||||
assert(obj->type != NULL);
|
||||
return obj->type->atomic == ATMTP_FLOAT;
|
||||
}
|
||||
|
||||
long long int Object_ToInt(Object *obj, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(obj);
|
||||
|
||||
if(!Object_IsInt(obj))
|
||||
{
|
||||
Error_Report(err, 0, "Object is not an integer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
if(type->to_int == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return type->to_int(obj, err);
|
||||
}
|
||||
|
||||
double Object_ToFloat(Object *obj, Error *err)
|
||||
{
|
||||
assert(err);
|
||||
assert(obj);
|
||||
|
||||
if(!Object_IsFloat(obj))
|
||||
{
|
||||
Error_Report(err, 0, "Object is not a floating");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Type *type = Object_GetType(obj);
|
||||
assert(type);
|
||||
|
||||
if(type->to_float == NULL)
|
||||
{
|
||||
Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return type->to_float(obj, err);
|
||||
}
|
||||
|
||||
_Bool Object_Compare(Object *obj1, Object *obj2, Error *error)
|
||||
{
|
||||
if(obj1->type != obj2->type)
|
||||
return 0;
|
||||
|
||||
if(obj1->type->op_eql == NULL)
|
||||
{
|
||||
Error_Report(error, "Object %s doesn't implement %s", Object_GetName(obj1), __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return obj1->type->op_eql(obj1, obj2);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#ifndef OBJECT_H
|
||||
#define OBJECT_H
|
||||
|
||||
#include "../utils/error.h"
|
||||
|
||||
typedef struct Type Type;
|
||||
typedef struct Object Object;
|
||||
typedef struct xHeap Heap;
|
||||
|
||||
struct Object {
|
||||
const Type *type;
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
ATMTP_NOTATOMIC = 0,
|
||||
ATMTP_INT,
|
||||
ATMTP_FLOAT,
|
||||
} AtomicType;
|
||||
|
||||
struct Type {
|
||||
|
||||
Object base;
|
||||
|
||||
// Any.
|
||||
const char *name;
|
||||
unsigned int size;
|
||||
AtomicType atomic;
|
||||
|
||||
_Bool (*init)(Object *self, Error *err);
|
||||
_Bool (*free)(Object *self, Error *err);
|
||||
int (*hash)(Object *self);
|
||||
Object* (*copy)(Object *self, Heap *heap, Error *err);
|
||||
Object* (*call)(Object *self, Object **argv, unsigned int argc, Heap *heap, Error *err);
|
||||
unsigned int (*deepsize)(const Object *self);
|
||||
|
||||
// Collections.
|
||||
Object *(*select)(Object *self, Object *key, Heap *heap, Error *err);
|
||||
Object *(*delete)(Object *self, Object *key, Heap *heap, Error *err);
|
||||
_Bool (*insert)(Object *self, Object *key, Object *val, Heap *heap, Error *err);
|
||||
int (*count)(Object *self);
|
||||
|
||||
// Iterators.
|
||||
Object *(*next)(Object *self, Heap *heap, Error *err);
|
||||
Object *(*prev)(Object *self, Heap *heap, Error *err);
|
||||
|
||||
// Some.
|
||||
union {
|
||||
long long int (*to_int)(Object *self, Error *err);
|
||||
double (*to_float)(Object *self, Error *err);
|
||||
char *(*to_string)(Object *self, int *size, Heap *heap, Error *err);
|
||||
};
|
||||
|
||||
_Bool (*op_eql)(Object *self, Object *other);
|
||||
};
|
||||
|
||||
enum {
|
||||
Object_STATIC = 1,
|
||||
};
|
||||
|
||||
Heap* Heap_New(int size);
|
||||
void Heap_Free(Heap *heap);
|
||||
void* Heap_Malloc (Heap *heap, const Type *type, Error *err);
|
||||
void* Heap_RawMalloc(Heap *heap, int size, Error *err);
|
||||
|
||||
const Type* Object_GetType(const Object *obj);
|
||||
const char* Object_GetName(const Object *obj);
|
||||
unsigned int Object_GetSize(const Object *obj, Error *err);
|
||||
unsigned int Object_GetDeepSize(const Object *obj, Error *err);
|
||||
int Object_Hash (Object *obj, Error *err);
|
||||
Object* Object_Copy (Object *obj, Heap *heap, Error *err);
|
||||
Object* Object_Call (Object *obj, Object **argv, unsigned int argc, Heap *heap, Error *err);
|
||||
Object* Object_Select(Object *coll, Object *key, Heap *heap, Error *err);
|
||||
Object* Object_Delete(Object *coll, Object *key, Heap *heap, Error *err);
|
||||
_Bool Object_Insert(Object *coll, Object *key, Object *val, Heap *heap, Error *err);
|
||||
int Object_Count (Object *coll, Error *err);
|
||||
Object* Object_Next (Object *iter, Heap *heap, Error *err);
|
||||
Object* Object_Prev (Object *iter, Heap *heap, Error *err);
|
||||
|
||||
Object* Object_NewMap(int num, Heap *heap, Error *error);
|
||||
Object* Object_NewNone(Heap *heap, Error *error);
|
||||
|
||||
Object* Object_FromInt (long long int val, Heap *heap, Error *error);
|
||||
Object* Object_FromFloat (double val, Heap *heap, Error *error);
|
||||
Object* Object_FromString(const char *str, int len, Heap *heap, Error *error);
|
||||
|
||||
_Bool Object_IsInt (Object *obj);
|
||||
_Bool Object_IsFloat(Object *obj);
|
||||
|
||||
long long int Object_ToInt (Object *obj, Error *err);
|
||||
double Object_ToFloat(Object *obj, Error *err);
|
||||
|
||||
_Bool Object_Compare(Object *obj1, Object *obj2, Error *error);
|
||||
|
||||
extern const Type t_type;
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <assert.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "../objects/objects.h"
|
||||
#include "runtime.h"
|
||||
|
||||
typedef struct {
|
||||
Object base;
|
||||
Runtime *runtime;
|
||||
Executable *exe;
|
||||
int index;
|
||||
} FunctionObject;
|
||||
|
||||
static Object *call(Object *self, Object **argv, unsigned int argc, Heap *heap, Error *error);
|
||||
|
||||
static const Type t_func = {
|
||||
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
||||
.name = "function",
|
||||
.size = sizeof (FunctionObject),
|
||||
.call = call,
|
||||
};
|
||||
|
||||
static Object *call(Object *self, Object **argv, unsigned int argc, Heap *heap, Error *error)
|
||||
{
|
||||
assert(self != NULL);
|
||||
assert(heap != NULL);
|
||||
assert(error != NULL);
|
||||
|
||||
FunctionObject *func = (FunctionObject*) self;
|
||||
|
||||
assert(func->exe != NULL);
|
||||
assert(func->index >= 0);
|
||||
|
||||
return run(func->runtime, error, func->exe, func->index, argv, argc);
|
||||
}
|
||||
|
||||
Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, Heap *heap, Error *error)
|
||||
{
|
||||
assert(runtime != NULL);
|
||||
assert(exe != NULL);
|
||||
assert(index >= 0);
|
||||
assert(heap != NULL);
|
||||
assert(error != NULL);
|
||||
|
||||
FunctionObject *func = (FunctionObject*) Heap_Malloc(heap, &t_func, error);
|
||||
|
||||
if(func == NULL)
|
||||
return NULL;
|
||||
|
||||
func->runtime = runtime;
|
||||
func->exe = exe;
|
||||
func->index = index;
|
||||
return (Object*) func;
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
#include <stdlib.h>
|
||||
#include "../utils/defs.h"
|
||||
#include "../utils/stack.h"
|
||||
#include "runtime.h"
|
||||
|
||||
#define MAX_FRAME_STACK 16
|
||||
|
||||
typedef struct Frame Frame;
|
||||
|
||||
struct Frame {
|
||||
Frame *prev;
|
||||
Object *vars;
|
||||
Executable *exe;
|
||||
int index, used;
|
||||
};
|
||||
|
||||
struct xRuntime {
|
||||
int depth;
|
||||
Frame *frame;
|
||||
Stack *stack;
|
||||
Heap *heap;
|
||||
};
|
||||
|
||||
Runtime *Runtime_New(int stack_size, int heap_size)
|
||||
{
|
||||
if(stack_size < 0)
|
||||
stack_size = 1024;
|
||||
|
||||
if(heap_size < 0)
|
||||
heap_size = 65536;
|
||||
|
||||
Runtime *runtime;
|
||||
|
||||
{
|
||||
runtime = malloc(sizeof(Runtime));
|
||||
|
||||
if(runtime == NULL)
|
||||
return NULL;
|
||||
|
||||
runtime->heap = Heap_New(heap_size);
|
||||
|
||||
if(runtime->heap == NULL)
|
||||
{
|
||||
free(runtime);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
runtime->stack = Stack_New(stack_size);
|
||||
|
||||
if(runtime->stack == NULL)
|
||||
{
|
||||
Heap_Free(runtime->heap);
|
||||
free(runtime);
|
||||
}
|
||||
|
||||
runtime->frame = NULL;
|
||||
runtime->depth = 0;
|
||||
}
|
||||
|
||||
return runtime;
|
||||
}
|
||||
|
||||
void Runtime_Free(Runtime *runtime)
|
||||
{
|
||||
Heap_Free(runtime->heap);
|
||||
Stack_Free(runtime->stack);
|
||||
free(runtime);
|
||||
}
|
||||
|
||||
_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj)
|
||||
{
|
||||
assert(runtime != NULL);
|
||||
assert(error != NULL);
|
||||
assert(obj != NULL);
|
||||
|
||||
if(runtime->depth == 0)
|
||||
{
|
||||
Error_Report(error, 0, "There are no frames on the stack");
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(runtime->frame->used <= MAX_FRAME_STACK);
|
||||
|
||||
if(runtime->frame->used == MAX_FRAME_STACK)
|
||||
{
|
||||
Error_Report(error, 0, "Frames stack limit of %d reached", MAX_FRAME_STACK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(!Stack_Push(runtime->stack, obj))
|
||||
{
|
||||
Error_Report(error, 0, "Out of stack");
|
||||
return 0;
|
||||
}
|
||||
|
||||
runtime->frame->used += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n)
|
||||
{
|
||||
assert(runtime != NULL);
|
||||
assert(error != NULL);
|
||||
|
||||
if(runtime->depth == 0)
|
||||
{
|
||||
Error_Report(error, 0, "There are no frames on the stack");
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(runtime->frame->used >= 0);
|
||||
|
||||
if((unsigned int) runtime->frame->used < n)
|
||||
{
|
||||
Error_Report(error, 0, "Frames has not enough values on the stack");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The frame has something on the stack,
|
||||
// this means that the stack isn't empty
|
||||
// and popping won't fail.
|
||||
(void) Stack_Pop(runtime->stack, n);
|
||||
|
||||
runtime->frame->used -= n;
|
||||
|
||||
assert(runtime->frame->used >= 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Executable *exe;
|
||||
int index;
|
||||
} SnapshotNode;
|
||||
|
||||
struct xSnapshot {
|
||||
int depth;
|
||||
SnapshotNode nodes[];
|
||||
};
|
||||
|
||||
Snapshot *Snapshot_New(Runtime *runtime)
|
||||
{
|
||||
assert(runtime->depth >= 0);
|
||||
|
||||
Snapshot *snapshot = malloc(sizeof(Snapshot) + sizeof(SnapshotNode) * runtime->depth);
|
||||
|
||||
if(snapshot == NULL)
|
||||
return NULL;
|
||||
|
||||
{
|
||||
Frame *f = runtime->frame;
|
||||
|
||||
snapshot->depth = 0;
|
||||
|
||||
while(snapshot->depth < runtime->depth)
|
||||
{
|
||||
assert(f != NULL);
|
||||
|
||||
SnapshotNode *node = snapshot->nodes + snapshot->depth;
|
||||
|
||||
node->exe = Executable_Copy(f->exe);
|
||||
node->index = f->index;
|
||||
|
||||
if(node->exe == NULL)
|
||||
goto abort;
|
||||
|
||||
f = f->prev;
|
||||
snapshot->depth += 1;
|
||||
}
|
||||
|
||||
assert(f == NULL);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
|
||||
abort:
|
||||
Snapshot_Free(snapshot);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Snapshot_Free(Snapshot *snapshot)
|
||||
{
|
||||
for(int i = 0; i < snapshot->depth; i += 1)
|
||||
{
|
||||
Executable *exe = snapshot->nodes[i].exe;
|
||||
|
||||
Executable_Free(exe);
|
||||
}
|
||||
free(snapshot);
|
||||
}
|
||||
|
||||
void Snapshot_Print(Snapshot *snapshot, FILE *fp)
|
||||
{
|
||||
assert(snapshot != NULL);
|
||||
assert(fp != NULL);
|
||||
|
||||
fprintf(fp, " (Snapshot can't be printed yet)\n");
|
||||
}
|
||||
|
||||
static Object *do_math_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error)
|
||||
{
|
||||
assert(lop != NULL);
|
||||
assert(rop != NULL);
|
||||
|
||||
#define APPLY(x, y, z, id) \
|
||||
switch(opcode) \
|
||||
{ \
|
||||
case OPCODE_ADD: (z) = (x) + (y); break; \
|
||||
case OPCODE_SUB: (z) = (x) - (y); break; \
|
||||
case OPCODE_MUL: (z) = (x) * (y); break; \
|
||||
case OPCODE_DIV: (z) = (x) / (y); break; \
|
||||
default: assert(0); break; \
|
||||
}
|
||||
|
||||
Object *res;
|
||||
|
||||
if(Object_IsInt(lop))
|
||||
{
|
||||
long long int raw_lop = Object_ToInt(lop, error);
|
||||
|
||||
if(error->occurred)
|
||||
return NULL;
|
||||
|
||||
if(Object_IsInt(rop))
|
||||
{
|
||||
// int + int
|
||||
long long int raw_rop = Object_ToInt(rop, error);
|
||||
|
||||
if(error->occurred)
|
||||
return NULL;
|
||||
|
||||
long long int raw_res;
|
||||
|
||||
APPLY(raw_lop, raw_rop, raw_res, id)
|
||||
|
||||
res = Object_FromInt(raw_res, heap, error);
|
||||
}
|
||||
else if(Object_IsFloat(rop))
|
||||
{
|
||||
// int + float
|
||||
double raw_rop = Object_ToFloat(rop, error);
|
||||
|
||||
if(error->occurred)
|
||||
return NULL;
|
||||
|
||||
double raw_res;
|
||||
|
||||
APPLY((double) raw_lop, raw_rop, raw_res, id)
|
||||
|
||||
res = Object_FromFloat(raw_res, heap, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Error_Report(error, 0, "Arithmetic operation on a non-numeric object");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if(Object_IsFloat(lop))
|
||||
{
|
||||
double raw_lop = Object_ToFloat(lop, error);
|
||||
|
||||
if(error->occurred)
|
||||
return NULL;
|
||||
|
||||
if(Object_IsInt(rop))
|
||||
{
|
||||
// float + int
|
||||
long long int raw_rop = Object_ToInt(rop, error);
|
||||
|
||||
if(error->occurred)
|
||||
return NULL;
|
||||
|
||||
double raw_res;
|
||||
|
||||
APPLY(raw_lop, (double) raw_rop, raw_res, id)
|
||||
|
||||
res = Object_FromFloat(raw_res, heap, error);
|
||||
}
|
||||
else if(Object_IsFloat(rop))
|
||||
{
|
||||
// float + float
|
||||
double raw_rop = Object_ToFloat(rop, error);
|
||||
|
||||
if(error->occurred)
|
||||
return NULL;
|
||||
|
||||
double raw_res;
|
||||
|
||||
APPLY(raw_lop, raw_rop, raw_res, id)
|
||||
|
||||
res = Object_FromFloat(raw_res, heap, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Error_Report(error, 0, "Arithmetic operation on a non-numeric object");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error_Report(error, 0, "Arithmetic operation on a non-numeric object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef APPLY
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static _Bool step(Runtime *runtime, Error *error)
|
||||
{
|
||||
assert(runtime != NULL);
|
||||
|
||||
Opcode opcode;
|
||||
Operand ops[3];
|
||||
int opc = sizeof(ops) / sizeof(ops[0]);
|
||||
|
||||
if(!Executable_Fetch(runtime->frame->exe, runtime->frame->index, &opcode, ops, &opc))
|
||||
{
|
||||
Error_Report(error, 1, "Invalid instruction index");
|
||||
return 0;
|
||||
}
|
||||
|
||||
runtime->frame->index += 1;
|
||||
|
||||
switch(opcode)
|
||||
{
|
||||
case OPCODE_NOPE:
|
||||
// Do nothing.
|
||||
return 1;
|
||||
|
||||
case OPCODE_POS:
|
||||
Error_Report(error, 1, "POS not implemented");
|
||||
return 0;
|
||||
|
||||
case OPCODE_NEG:
|
||||
Error_Report(error, 1, "NEG not implemented");
|
||||
return 0;
|
||||
|
||||
case OPCODE_ADD:
|
||||
case OPCODE_SUB:
|
||||
case OPCODE_MUL:
|
||||
case OPCODE_DIV:
|
||||
{
|
||||
Object *rop = Stack_Top(runtime->stack, 0);
|
||||
Object *lop = Stack_Top(runtime->stack, -1);
|
||||
|
||||
if(!Runtime_Pop(runtime, error, 2))
|
||||
return 0;
|
||||
|
||||
// We managed to pop rop and lop,
|
||||
// so we know they're not NULL.
|
||||
assert(rop != NULL);
|
||||
assert(lop != NULL);
|
||||
|
||||
Object *res = do_math_op(lop, rop, opcode, runtime->heap, error);
|
||||
|
||||
if(res == NULL)
|
||||
return 0;
|
||||
|
||||
if(!Runtime_Push(runtime, error, res))
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PUSHI:
|
||||
{
|
||||
assert(opc == 1);
|
||||
assert(ops[0].type == OPTP_INT);
|
||||
|
||||
Object *obj = Object_FromInt(ops[0].as_int, runtime->heap, error);
|
||||
|
||||
if(obj == NULL)
|
||||
return 0;
|
||||
|
||||
if(!Runtime_Push(runtime, error, obj))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
case OPCODE_PUSHF:
|
||||
{
|
||||
assert(opc == 1);
|
||||
assert(ops[0].type == OPTP_FLOAT);
|
||||
|
||||
Object *obj = Object_FromFloat(ops[0].as_float, runtime->heap, error);
|
||||
|
||||
if(obj == NULL)
|
||||
return 0;
|
||||
|
||||
if(!Runtime_Push(runtime, error, obj))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
case OPCODE_PUSHS:
|
||||
{
|
||||
assert(opc == 1);
|
||||
assert(ops[0].type == OPTP_STRING);
|
||||
|
||||
Object *obj = Object_FromString(ops[0].as_string, -1, runtime->heap, error);
|
||||
|
||||
if(obj == NULL)
|
||||
return 0;
|
||||
|
||||
if(!Runtime_Push(runtime, error, obj))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
case OPCODE_PUSHV:
|
||||
{
|
||||
assert(opc == 1);
|
||||
assert(ops[0].type == OPTP_STRING);
|
||||
|
||||
Object *key = Object_FromString(ops[0].as_string, -1, runtime->heap, error);
|
||||
|
||||
if(key == NULL)
|
||||
return 0;
|
||||
|
||||
Object *obj = Object_Select(runtime->frame->vars, key, runtime->heap, error);
|
||||
|
||||
if(obj == NULL)
|
||||
{
|
||||
if(error->occurred == 0)
|
||||
// There's no such variable.
|
||||
Error_Report(error, 1, "Reference to undefined variable \"%s\"", ops[0].as_string);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
case OPCODE_RETURN:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
UNREACHABLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
Object *run(Runtime *runtime, Error *error, Executable *exe, int index, Object **argv, int argc)
|
||||
{
|
||||
assert(runtime != NULL);
|
||||
assert(error != NULL);
|
||||
assert(exe != NULL);
|
||||
assert(index >= 0);
|
||||
assert(argc >= 0);
|
||||
|
||||
// Initialize the frame.
|
||||
Frame frame;
|
||||
{
|
||||
frame.prev = NULL;
|
||||
frame.vars = Object_NewMap(-1, runtime->heap, error);
|
||||
frame.exe = Executable_Copy(exe);
|
||||
frame.index = index;
|
||||
frame.used = 0;
|
||||
|
||||
if(frame.vars == NULL)
|
||||
return NULL;
|
||||
|
||||
if(frame.exe == NULL)
|
||||
{
|
||||
Error_Report(error, 1, "Failed to copy executable");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add the frame to the runtime.
|
||||
frame.prev = runtime->frame;
|
||||
runtime->frame = &frame;
|
||||
runtime->depth += 1;
|
||||
}
|
||||
|
||||
// Push the initial values of the frame.
|
||||
for(int i = 0; i < argc; i += 1)
|
||||
if(!Runtime_Push(runtime, error, argv[i]))
|
||||
goto cleanup;
|
||||
|
||||
// Run the code.
|
||||
while(step(runtime, error));
|
||||
|
||||
// This is what the function will return.
|
||||
Object *result = NULL;
|
||||
|
||||
// If an error occurred, we want to return NULL.
|
||||
if(error->occurred == 0)
|
||||
{
|
||||
// If the step function left something
|
||||
// on the stack, we return that. If it
|
||||
// didn't, we return some other default
|
||||
// value like "none".
|
||||
if(frame.used == 0)
|
||||
{
|
||||
// Nothing to return on the stack. Set to none.
|
||||
result = Object_NewNone(runtime->heap, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Stack_Top(runtime->stack, 0);
|
||||
assert(result != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
// Remove the frame-owned items from the stack.
|
||||
// This can't fail.
|
||||
(void) Stack_Pop(runtime->stack, frame.used);
|
||||
|
||||
// Deinitialize the frame.
|
||||
{
|
||||
// Remove the frame from the runtime.
|
||||
runtime->frame = runtime->frame->prev;
|
||||
runtime->depth -= 1;
|
||||
|
||||
// Deallocate the fields.
|
||||
Executable_Free(frame.exe);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef RUNTIME_H
|
||||
#define RUNTIME_H
|
||||
#include <stdio.h> // meh.. just for the definition of FILE.
|
||||
#include "../utils/error.h"
|
||||
#include "../objects/objects.h"
|
||||
#include "../common/executable.h"
|
||||
|
||||
typedef struct xRuntime Runtime;
|
||||
Runtime* Runtime_New(int stack_size, int heap_size);
|
||||
void Runtime_Free(Runtime *runtime);
|
||||
_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n);
|
||||
_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj);
|
||||
|
||||
typedef struct xSnapshot Snapshot;
|
||||
Snapshot *Snapshot_New(Runtime *runtime);
|
||||
void Snapshot_Free(Snapshot *snapshot);
|
||||
void Snapshot_Print(Snapshot *snapshot, FILE *fp);
|
||||
|
||||
Object *run(Runtime *runtime, Error *error, Executable *exe, int index, Object **argv, int argc);
|
||||
|
||||
Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int offset, Heap *heap, Error *error);
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <assert.h>
|
||||
#include "runtime_error.h"
|
||||
|
||||
static void on_report(Error *error)
|
||||
{
|
||||
assert(error != NULL);
|
||||
|
||||
RuntimeError *error2 = (RuntimeError*) error;
|
||||
|
||||
if(error2->runtime != NULL)
|
||||
error2->snapshot = Snapshot_New(error2->runtime);
|
||||
}
|
||||
|
||||
void RuntimeError_Init(RuntimeError *error, Runtime *runtime)
|
||||
{
|
||||
assert(error != NULL);
|
||||
|
||||
Error_Init2(&error->base, on_report);
|
||||
|
||||
error->runtime = runtime;
|
||||
error->snapshot = NULL;
|
||||
}
|
||||
|
||||
void RuntimeError_Free(RuntimeError *error)
|
||||
{
|
||||
if(error->snapshot)
|
||||
Snapshot_Free(error->snapshot);
|
||||
Error_Free(&error->base);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef RUNTIME_ERROR_H
|
||||
#define RUNTIME_ERROR_H
|
||||
#include "../utils/error.h"
|
||||
#include "runtime.h"
|
||||
|
||||
typedef struct {
|
||||
Error base;
|
||||
Runtime *runtime;
|
||||
Snapshot *snapshot;
|
||||
} RuntimeError;
|
||||
|
||||
void RuntimeError_Init(RuntimeError *error, Runtime *runtime);
|
||||
void RuntimeError_Free(RuntimeError *error);
|
||||
#endif
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "defs.h"
|
||||
#include "bpalloc.h"
|
||||
|
||||
#if USING_VALGRIND
|
||||
#include <valgrind/memcheck.h>
|
||||
#endif
|
||||
|
||||
#define CHUNK_SIZE 4096
|
||||
#define PADDING 8
|
||||
|
||||
typedef struct BPAllocChunk BPAllocChunk;
|
||||
struct BPAllocChunk {
|
||||
BPAllocChunk *prev;
|
||||
char body[];
|
||||
};
|
||||
|
||||
struct xBPAlloc {
|
||||
int flags;
|
||||
void *userp;
|
||||
void *(*fn_malloc)(void *userp, int size);
|
||||
void (*fn_free )(void *userp, void *addr);
|
||||
int minsize, size, used;
|
||||
BPAllocChunk *tail;
|
||||
};
|
||||
|
||||
enum {
|
||||
FG_STATIC = 1,
|
||||
};
|
||||
|
||||
static void *default_fn_malloc(void *userp, int size);
|
||||
static void default_fn_free (void *userp, void *addr);
|
||||
|
||||
BPAlloc *BPAlloc_Init(int chunk_size)
|
||||
{
|
||||
return BPAlloc_Init2(-1, chunk_size, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
BPAlloc *BPAlloc_Init2(int first_size, int chunk_size,
|
||||
void *userp,
|
||||
void *(*fn_malloc)(void *userp, int size),
|
||||
void (*fn_free )(void *userp, void *addr))
|
||||
{
|
||||
if(chunk_size < 0)
|
||||
chunk_size = CHUNK_SIZE;
|
||||
|
||||
if(first_size < 0)
|
||||
first_size = chunk_size;
|
||||
|
||||
if(fn_malloc == NULL)
|
||||
{
|
||||
userp = NULL;
|
||||
fn_malloc = default_fn_malloc;
|
||||
fn_free = default_fn_free;
|
||||
}
|
||||
|
||||
void *temp = fn_malloc(userp, sizeof(BPAlloc) + sizeof(BPAllocChunk) + first_size + PADDING);
|
||||
|
||||
if(temp == NULL)
|
||||
return NULL;
|
||||
|
||||
BPAlloc *alloc = temp;
|
||||
|
||||
BPAllocChunk *chunk = (BPAllocChunk*) (alloc + 1);
|
||||
chunk->prev = NULL;
|
||||
|
||||
alloc->flags = 0;
|
||||
alloc->used = 0;
|
||||
alloc->size = first_size;
|
||||
alloc->tail = chunk;
|
||||
alloc->minsize = chunk_size;
|
||||
alloc->userp = userp;
|
||||
alloc->fn_malloc = fn_malloc;
|
||||
alloc->fn_free = fn_free;
|
||||
|
||||
#if USING_VALGRIND
|
||||
VALGRIND_CREATE_MEMPOOL(alloc, PADDING, 0);
|
||||
#endif
|
||||
|
||||
return alloc;
|
||||
}
|
||||
|
||||
BPAlloc *BPAlloc_Init3(void *mem, int mem_size, int chunk_size,
|
||||
void *userp,
|
||||
void *(*fn_malloc)(void *userp, int size),
|
||||
void (*fn_free )(void *userp, void *addr))
|
||||
{
|
||||
assert(mem != NULL);
|
||||
assert(mem_size >= 0);
|
||||
|
||||
if(chunk_size < 0)
|
||||
chunk_size = CHUNK_SIZE;
|
||||
|
||||
if(fn_malloc == NULL)
|
||||
{
|
||||
userp = NULL;
|
||||
fn_malloc = default_fn_malloc;
|
||||
fn_free = default_fn_free;
|
||||
}
|
||||
|
||||
int required = sizeof(BPAlloc)
|
||||
+ sizeof(BPAllocChunk)
|
||||
+ PADDING;
|
||||
|
||||
if(mem_size < required)
|
||||
// Not enough memory was provided.
|
||||
return NULL;
|
||||
|
||||
BPAlloc *alloc = mem;
|
||||
|
||||
BPAllocChunk *chunk = (BPAllocChunk*) (alloc + 1);
|
||||
chunk->prev = NULL;
|
||||
|
||||
alloc->flags = FG_STATIC;
|
||||
alloc->used = 0;
|
||||
alloc->size = mem_size - sizeof(BPAlloc) - sizeof(BPAllocChunk);
|
||||
alloc->tail = chunk;
|
||||
alloc->minsize = chunk_size;
|
||||
alloc->userp = userp;
|
||||
alloc->fn_malloc = fn_malloc;
|
||||
alloc->fn_free = fn_free;
|
||||
|
||||
#if USING_VALGRIND
|
||||
VALGRIND_CREATE_MEMPOOL(alloc, PADDING, 0);
|
||||
#endif
|
||||
|
||||
return alloc;
|
||||
}
|
||||
|
||||
void BPAlloc_Free(BPAlloc *alloc)
|
||||
{
|
||||
assert(alloc != NULL);
|
||||
|
||||
#if USING_VALGRIND
|
||||
VALGRIND_DESTROY_MEMPOOL(alloc);
|
||||
#endif
|
||||
|
||||
BPAllocChunk *chunk = alloc->tail;
|
||||
|
||||
while(chunk->prev)
|
||||
{
|
||||
BPAllocChunk *prev = chunk->prev;
|
||||
|
||||
if(alloc->fn_free)
|
||||
alloc->fn_free(alloc->userp, chunk);
|
||||
|
||||
chunk = prev;
|
||||
}
|
||||
|
||||
if(!(alloc->flags & FG_STATIC))
|
||||
if(alloc->fn_free)
|
||||
alloc->fn_free(alloc->userp, alloc);
|
||||
}
|
||||
|
||||
void *BPAlloc_Malloc(BPAlloc *alloc, int req_size)
|
||||
{
|
||||
assert(alloc != NULL);
|
||||
assert(req_size >= 0);
|
||||
|
||||
alloc->used += PADDING;
|
||||
|
||||
if(alloc->used & 7)
|
||||
alloc->used = (alloc->used & ~7) + 8;
|
||||
|
||||
if(alloc->used + req_size > alloc->size)
|
||||
{
|
||||
// If the chunk size is lower than the
|
||||
// requested size, then set the chunk
|
||||
// size to the requested size.
|
||||
int chunk_size = MAX(alloc->minsize, req_size + PADDING);
|
||||
|
||||
assert(alloc->fn_malloc != NULL);
|
||||
BPAllocChunk *chunk = alloc->fn_malloc(alloc->userp, sizeof(BPAllocChunk) + chunk_size);
|
||||
|
||||
if(chunk == NULL)
|
||||
return NULL;
|
||||
|
||||
chunk->prev = alloc->tail;
|
||||
alloc->tail = chunk;
|
||||
alloc->size = chunk_size;
|
||||
alloc->used = PADDING;
|
||||
|
||||
if(alloc->used & 7)
|
||||
alloc->used = (alloc->used & ~7) + 8;
|
||||
}
|
||||
|
||||
void *addr = alloc->tail->body + alloc->used;
|
||||
|
||||
assert(((intptr_t) addr) % 8 == 0);
|
||||
|
||||
alloc->used += req_size;
|
||||
|
||||
#if USING_VALGRIND
|
||||
VALGRIND_MEMPOOL_ALLOC(alloc, addr, req_size);
|
||||
// VALGRIND_MAKE_MEM_NOACCESS((char*) addr - PADDING, PADDING);
|
||||
// VALGRIND_MAKE_MEM_NOACCESS((char*) addr + req_size, PADDING);
|
||||
#endif
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static void *default_fn_malloc(void *userp, int size)
|
||||
{
|
||||
assert(userp == NULL);
|
||||
assert(size >= 0);
|
||||
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
static void default_fn_free(void *userp, void *addr)
|
||||
{
|
||||
assert(userp == NULL);
|
||||
|
||||
free(addr);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef BPALLOC_H
|
||||
#define BPALLOC_H
|
||||
typedef struct xBPAlloc BPAlloc;
|
||||
BPAlloc* BPAlloc_Init(int chunk_size);
|
||||
BPAlloc* BPAlloc_Init2(int first_size, int chunk_size, void *userp, void *(*fn_malloc)(void *userp, int size), void (*fn_free )(void *userp, void *addr));
|
||||
BPAlloc* BPAlloc_Init3(void *mem, int mem_size, int chunk_size, void *userp, void *(*fn_malloc)(void *userp, int size), void (*fn_free )(void *userp, void *addr));
|
||||
void BPAlloc_Free(BPAlloc *alloc);
|
||||
void* BPAlloc_Malloc(BPAlloc *alloc, int req_size);
|
||||
#endif
|
||||
@@ -0,0 +1,177 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "bucketlist.h"
|
||||
#include "defs.h"
|
||||
|
||||
#define MIN_BUCKET_SIZE 4096
|
||||
|
||||
typedef struct Bucket Bucket;
|
||||
struct Bucket {
|
||||
Bucket* next;
|
||||
int size,
|
||||
used,
|
||||
aidx;
|
||||
char body[];
|
||||
};
|
||||
|
||||
struct xBucketList {
|
||||
BPAlloc *alloc;
|
||||
Bucket *head,
|
||||
*tail;
|
||||
int size;
|
||||
};
|
||||
|
||||
BucketList *BucketList_New(BPAlloc *alloc)
|
||||
{
|
||||
assert(alloc != NULL);
|
||||
|
||||
BucketList *blist = BPAlloc_Malloc(alloc, sizeof(BucketList) + sizeof(Bucket) + MIN_BUCKET_SIZE);
|
||||
|
||||
if(blist == NULL)
|
||||
return NULL;
|
||||
|
||||
Bucket *head = (Bucket*) (blist + 1);
|
||||
head->next = NULL;
|
||||
head->size = MIN_BUCKET_SIZE;
|
||||
head->used = 0;
|
||||
head->aidx = 0;
|
||||
|
||||
blist->alloc = alloc;
|
||||
blist->head = head;
|
||||
blist->tail = head;
|
||||
blist->size = 0;
|
||||
return blist;
|
||||
}
|
||||
|
||||
int BucketList_Size(BucketList *blist)
|
||||
{
|
||||
return blist->size;
|
||||
}
|
||||
|
||||
static Bucket *make_bucket(BPAlloc *alloc, int size)
|
||||
{
|
||||
Bucket *new_bucket = BPAlloc_Malloc(alloc, sizeof(Bucket) + size);
|
||||
|
||||
if(new_bucket == NULL)
|
||||
return NULL;
|
||||
|
||||
// Initialize it.
|
||||
new_bucket->next = NULL;
|
||||
new_bucket->size = size;
|
||||
new_bucket->used = 0;
|
||||
new_bucket->aidx = -1;
|
||||
return new_bucket;
|
||||
}
|
||||
|
||||
static void append_bucket(BucketList *blist, Bucket *new_bucket)
|
||||
{
|
||||
new_bucket->aidx = blist->size;
|
||||
blist->tail->next = new_bucket;
|
||||
blist->tail = new_bucket;
|
||||
}
|
||||
|
||||
_Bool BucketList_Append(BucketList *blist, const void *data, int size)
|
||||
{
|
||||
assert(blist != NULL);
|
||||
assert(size >= 0);
|
||||
|
||||
int not_copied_yet = size;
|
||||
|
||||
while(not_copied_yet > 0)
|
||||
{
|
||||
// Copy until there's nothing left
|
||||
// or until the current bucket is
|
||||
// full. If the bucket is already
|
||||
// full, add another one.
|
||||
|
||||
int left_in_bucket = blist->tail->size - blist->tail->used;
|
||||
|
||||
if(left_in_bucket == 0)
|
||||
{
|
||||
Bucket *new_bucket = make_bucket(blist->alloc, MIN_BUCKET_SIZE);
|
||||
|
||||
if(new_bucket == NULL)
|
||||
return 0;
|
||||
|
||||
append_bucket(blist, new_bucket);
|
||||
}
|
||||
|
||||
// Decide how much to copy.
|
||||
int copying = MIN(not_copied_yet, left_in_bucket);
|
||||
|
||||
// Copy into the bucket.
|
||||
{
|
||||
char *dst = blist->tail->body + blist->tail->used;
|
||||
|
||||
if(data == NULL)
|
||||
memset(dst, 0, copying);
|
||||
else
|
||||
memcpy(dst, data + size - not_copied_yet, copying);
|
||||
|
||||
blist->tail->used += copying;
|
||||
}
|
||||
|
||||
not_copied_yet -= copying;
|
||||
}
|
||||
|
||||
blist->size += size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *BucketList_Append2(BucketList *blist, const void *data, int size)
|
||||
{
|
||||
assert(blist != NULL);
|
||||
assert(size >= 0);
|
||||
|
||||
// If the data doesn't fit inside the
|
||||
// current bucket, add another one with
|
||||
// enough space.
|
||||
if(blist->tail->used + size > blist->tail->size)
|
||||
{
|
||||
int bucket_size = MAX(MIN_BUCKET_SIZE, size);
|
||||
|
||||
Bucket *new_bucket = make_bucket(blist->alloc, bucket_size);
|
||||
|
||||
if(new_bucket == NULL)
|
||||
return 0;
|
||||
|
||||
append_bucket(blist, new_bucket);
|
||||
}
|
||||
|
||||
void *addr = blist->tail->body + blist->tail->used;
|
||||
|
||||
// Do the copying.
|
||||
if(data == NULL)
|
||||
memset(addr, 0, size);
|
||||
else
|
||||
memcpy(addr, data, size);
|
||||
|
||||
blist->tail->used += size;
|
||||
blist->size += size;
|
||||
return addr;
|
||||
}
|
||||
|
||||
void BucketList_Copy(BucketList *blist, void *dest, int len)
|
||||
{
|
||||
assert(blist != NULL);
|
||||
assert(dest != NULL);
|
||||
|
||||
if(len < 0)
|
||||
len = blist->size;
|
||||
|
||||
int copied = 0;
|
||||
|
||||
Bucket *bucket = blist->head;
|
||||
|
||||
while(bucket && copied < len)
|
||||
{
|
||||
int copying = MIN(len - copied, bucket->used);
|
||||
assert(copying >= 0);
|
||||
|
||||
memcpy((char*) dest + copied, bucket->body, copying);
|
||||
|
||||
copied += copying;
|
||||
bucket = bucket->next;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef BUCKETLIST_H
|
||||
#define BUCKETLIST_H
|
||||
#include "bpalloc.h"
|
||||
typedef struct xBucketList BucketList;
|
||||
BucketList *BucketList_New(BPAlloc *alloc);
|
||||
int BucketList_Size(BucketList *blist);
|
||||
void BucketList_Copy(BucketList *blist, void *dest, int len);
|
||||
_Bool BucketList_Append (BucketList *blist, const void *data, int size);
|
||||
void *BucketList_Append2(BucketList *blist, const void *data, int size);
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL ((void*) 0)
|
||||
#endif
|
||||
|
||||
#define UNREACHABLE assert(0);
|
||||
|
||||
#define membersizeof(type, member) (sizeof(((type*) 0)->member))
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "error.h"
|
||||
|
||||
void Error_Init(Error *err)
|
||||
{
|
||||
memset(err, 0, sizeof (Error));
|
||||
}
|
||||
|
||||
void Error_Init2(Error *err, void (*on_report)(Error *err))
|
||||
{
|
||||
memset(err, 0, sizeof (Error));
|
||||
err->on_report = on_report;
|
||||
}
|
||||
|
||||
void Error_Free(Error *err)
|
||||
{
|
||||
if(err->message2 != err->message)
|
||||
free(err->message);
|
||||
memset(err, 0, sizeof (Error));
|
||||
}
|
||||
|
||||
void _Error_Report(Error *err, _Bool internal,
|
||||
const char *file, const char *func, int line,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
_Error_Report2(err, internal, file, func, line, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void _Error_Report2(Error *err, _Bool internal,
|
||||
const char *file, const char *func, int line,
|
||||
const char *fmt, va_list va)
|
||||
{
|
||||
assert(err);
|
||||
assert(file);
|
||||
assert(func);
|
||||
assert(line > 0);
|
||||
assert(fmt);
|
||||
assert(err->occurred == 0);
|
||||
|
||||
err->occurred = 1;
|
||||
err->internal = internal;
|
||||
err->file = file;
|
||||
err->func = func;
|
||||
err->line = line;
|
||||
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
|
||||
int p = vsnprintf(err->message2, sizeof(err->message2), fmt, va);
|
||||
|
||||
assert(p > -1);
|
||||
|
||||
if((unsigned int) p > sizeof(err->message2)-1)
|
||||
{
|
||||
char *temp = malloc(p+1);
|
||||
|
||||
if(temp == NULL)
|
||||
{
|
||||
err->truncated = 1;
|
||||
err->message = err->message2;
|
||||
err->length = sizeof(err->message2)-1;
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(temp, p+1, fmt, va2);
|
||||
err->truncated = 0;
|
||||
err->message = temp;
|
||||
err->length = p;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
err->truncated = 0;
|
||||
err->message = err->message2;
|
||||
err->length = p;
|
||||
}
|
||||
|
||||
va_end(va2);
|
||||
|
||||
if(err->on_report)
|
||||
err->on_report(err);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef ERROR_H
|
||||
#define ERROR_H
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef struct Error Error;
|
||||
|
||||
struct Error {
|
||||
void (*on_report)(Error *err);
|
||||
_Bool occurred,
|
||||
internal,
|
||||
truncated;
|
||||
int length;
|
||||
char* message;
|
||||
char message2[256];
|
||||
const char *file,
|
||||
*func;
|
||||
int line;
|
||||
};
|
||||
|
||||
void Error_Init(Error *err);
|
||||
void Error_Init2(Error *err, void (*on_report)(Error *err));
|
||||
void Error_Free(Error *err);
|
||||
#define Error_Report(err, internal, fmt, ...) _Error_Report(err, internal, __FILE__, __func__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
void _Error_Report (Error *err, _Bool internal, const char *file, const char *func, int line, const char *fmt, ...);
|
||||
void _Error_Report2(Error *err, _Bool internal, const char *file, const char *func, int line, const char *fmt, va_list va);
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <stdint.h>
|
||||
#include "hash.h"
|
||||
|
||||
int hashbytes(unsigned char *str, int len)
|
||||
{
|
||||
int x = (intptr_t) str; // just to not use 0.
|
||||
|
||||
x ^= *str << 7;
|
||||
|
||||
for(int i = 0; i < len; i += 1)
|
||||
x = (1000003UL * x) ^ *str++;
|
||||
|
||||
x ^= len;
|
||||
|
||||
if(x == -1)
|
||||
x = -2;
|
||||
|
||||
return x;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef HASH_H
|
||||
#define HASH_H
|
||||
int hashbytes(unsigned char *str, int len);
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "promise.h"
|
||||
#include "defs.h"
|
||||
|
||||
typedef struct Gap Gap;
|
||||
struct Gap {
|
||||
Gap *next;
|
||||
void *dest;
|
||||
void *userp;
|
||||
void (*callback)(void*);
|
||||
};
|
||||
|
||||
struct xPromise {
|
||||
BPAlloc *alloc;
|
||||
_Bool set;
|
||||
Gap *gaps;
|
||||
int size;
|
||||
char body[];
|
||||
};
|
||||
|
||||
Promise *Promise_New(BPAlloc *alloc, int size)
|
||||
{
|
||||
assert(alloc != NULL);
|
||||
assert(size >= 0);
|
||||
|
||||
Promise *promise = BPAlloc_Malloc(alloc, sizeof(Promise) + size);
|
||||
|
||||
if(promise == NULL)
|
||||
return NULL;
|
||||
|
||||
promise->alloc = alloc;
|
||||
promise->set = 0;
|
||||
promise->gaps = NULL;
|
||||
promise->size = size;
|
||||
return promise;
|
||||
}
|
||||
|
||||
unsigned int Promise_Size(Promise *promise)
|
||||
{
|
||||
return promise->size;
|
||||
}
|
||||
|
||||
void Promise_Delete(Promise *promise)
|
||||
{
|
||||
assert(promise->set == 1);
|
||||
}
|
||||
|
||||
void Promise_Resolve(Promise *promise, const void *data, int size)
|
||||
{
|
||||
assert(size >= 0);
|
||||
assert(size == promise->size);
|
||||
assert(promise->set == 0);
|
||||
|
||||
memcpy(promise->body, data, size);
|
||||
promise->set = 1;
|
||||
|
||||
Gap *gap = promise->gaps;
|
||||
while(gap)
|
||||
{
|
||||
memcpy(gap->dest, data, size);
|
||||
|
||||
if(gap->callback)
|
||||
gap->callback(gap->userp);
|
||||
|
||||
gap = gap->next;
|
||||
}
|
||||
|
||||
promise->gaps = NULL;
|
||||
}
|
||||
|
||||
_Bool Promise_Subscribe(Promise *promise, void *dest)
|
||||
{
|
||||
assert(promise != NULL);
|
||||
assert(dest != NULL);
|
||||
return Promise_Subscribe2(promise, dest, NULL, NULL);
|
||||
}
|
||||
|
||||
_Bool Promise_Subscribe2(Promise *promise, void *dest, void *userp, void (*callback)(void*))
|
||||
{
|
||||
assert(promise != NULL);
|
||||
assert(dest != NULL);
|
||||
|
||||
if(promise->set == 0)
|
||||
{
|
||||
Gap *gap = BPAlloc_Malloc(promise->alloc, sizeof(Gap));
|
||||
|
||||
if(gap == NULL)
|
||||
return 0;
|
||||
|
||||
gap->next = promise->gaps;
|
||||
gap->dest = dest;
|
||||
gap->userp = userp;
|
||||
gap->callback = callback;
|
||||
promise->gaps = gap;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(dest, promise->body, promise->size);
|
||||
|
||||
if(callback)
|
||||
callback(userp);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef PROMISE_H
|
||||
#define PROMISE_H
|
||||
#include "bpalloc.h"
|
||||
typedef struct xPromise Promise;
|
||||
Promise *Promise_New(BPAlloc *alloc, int size);
|
||||
unsigned int Promise_Size(Promise *promise);
|
||||
void Promise_Delete(Promise *promise);
|
||||
void Promise_Resolve(Promise *promise, const void *data, int size);
|
||||
_Bool Promise_Subscribe(Promise *promise, void *dest);
|
||||
_Bool Promise_Subscribe2(Promise *promise, void *dest, void *userp, void (*callback)(void*));
|
||||
#endif
|
||||
@@ -0,0 +1,160 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include "source.h"
|
||||
|
||||
struct xSource {
|
||||
char *name;
|
||||
char *body;
|
||||
int size;
|
||||
int refs;
|
||||
};
|
||||
|
||||
Source *Source_Copy(Source *s)
|
||||
{
|
||||
s->refs += 1;
|
||||
return s;
|
||||
}
|
||||
|
||||
void Source_Free(Source *s)
|
||||
{
|
||||
s->refs -= 1;
|
||||
assert(s->refs >= 0);
|
||||
|
||||
if(s->refs == 0)
|
||||
free(s);
|
||||
}
|
||||
|
||||
const char *Source_GetName(const Source *s)
|
||||
{
|
||||
return s->name;
|
||||
}
|
||||
|
||||
const char *Source_GetBody(const Source *s)
|
||||
{
|
||||
return s->body;
|
||||
}
|
||||
|
||||
unsigned int Source_GetSize(const Source *s)
|
||||
{
|
||||
return s->size;
|
||||
}
|
||||
|
||||
Source *Source_FromFile(const char *file, Error *error)
|
||||
{
|
||||
assert(file != NULL);
|
||||
|
||||
// Open the file and get it's size.
|
||||
// at the end of the block, the file
|
||||
// cursor will point at the start of
|
||||
// the file.
|
||||
FILE *fp;
|
||||
int size;
|
||||
{
|
||||
fp = fopen(file, "rb");
|
||||
|
||||
if(fp == NULL)
|
||||
{
|
||||
if(errno == ENOENT)
|
||||
Error_Report(error, 0, "File \"%s\" doesn't exist", file);
|
||||
else
|
||||
Error_Report(error, 1, "Call to fopen failed (%s, errno = %d)", strerror(errno), errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(fseek(fp, 0, SEEK_END))
|
||||
{
|
||||
Error_Report(error, 1, "Call to fseek failed (%s, errno = %d)", strerror(errno), errno);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = ftell(fp);
|
||||
|
||||
if(size < 0)
|
||||
{
|
||||
Error_Report(error, 1, "Call to ftell failed (%s, errno = %d)", strerror(errno), errno);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(fseek(fp, 0, SEEK_END))
|
||||
{
|
||||
Error_Report(error, 1, "Call to fseek failed (%s, errno = %d)", strerror(errno), errno);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the source structure.
|
||||
Source *s;
|
||||
{
|
||||
int namel = strlen(file);
|
||||
|
||||
s = malloc(sizeof(Source) + namel + size + 2);
|
||||
|
||||
if(s == NULL)
|
||||
{
|
||||
Error_Report(error, 1, "No memory");
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->name = (char*) (s + 1);
|
||||
s->body = s->name + namel + 1;
|
||||
}
|
||||
|
||||
// Copy the name into it.
|
||||
strcpy(s->name, file);
|
||||
s->size = size;
|
||||
s->refs = 1;
|
||||
|
||||
// Now copy the file contents into it.
|
||||
{
|
||||
int p = fread(s->body, 1, size, fp);
|
||||
|
||||
if(p != size)
|
||||
{
|
||||
Error_Report(error, 1, "Call to fread failed, %d bytes out of %d were read (%s, errno = %d)", p, size, strerror(errno), errno);
|
||||
fclose(fp);
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->body[s->size] = '\0';
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return s;
|
||||
}
|
||||
|
||||
Source *Source_FromString(const char *name, const char *body, int size, Error *error)
|
||||
{
|
||||
assert(body != NULL);
|
||||
|
||||
if(size < 0)
|
||||
size = strlen(body);
|
||||
|
||||
int namel = name ? strlen(name) : 0;
|
||||
|
||||
void *memory = malloc(sizeof(Source) + namel + size + 2);
|
||||
|
||||
if(memory == NULL)
|
||||
{
|
||||
Error_Report(error, 1, "No memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Source *s = memory;
|
||||
s->name = (char*) (s + 1);
|
||||
s->body = s->name + namel + 1;
|
||||
s->size = size;
|
||||
s->refs = 1;
|
||||
|
||||
if(name)
|
||||
strcpy(s->name, name);
|
||||
strncpy(s->body, body, size);
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef SOURCE_H
|
||||
#define SOURCE_H
|
||||
#include "error.h"
|
||||
typedef struct xSource Source;
|
||||
Source *Source_Copy(Source *s);
|
||||
void Source_Free(Source *s);
|
||||
const char *Source_GetName(const Source *s);
|
||||
const char *Source_GetBody(const Source *s);
|
||||
unsigned int Source_GetSize(const Source *s);
|
||||
Source *Source_FromFile(const char *file, Error *error);
|
||||
Source *Source_FromString(const char *name, const char *body, int size, Error *error);
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <stdlib.h>
|
||||
#include "stack.h"
|
||||
#include "defs.h"
|
||||
|
||||
struct xStack {
|
||||
unsigned int size, used;
|
||||
void *body[];
|
||||
};
|
||||
|
||||
void *Stack_New(int size)
|
||||
{
|
||||
if(size < 0)
|
||||
size = 1024;
|
||||
|
||||
Stack *s = malloc(sizeof(Stack) + sizeof(void*) * size);
|
||||
|
||||
if(s == NULL)
|
||||
return NULL;
|
||||
|
||||
s->size = size;
|
||||
s->used = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
void *Stack_Top(Stack *s, int n)
|
||||
{
|
||||
assert(n <= 0);
|
||||
|
||||
if(s->used == 0)
|
||||
return NULL;
|
||||
|
||||
if((int) s->used + n - 1 < 0)
|
||||
return NULL;
|
||||
|
||||
return s->body[s->used + n - 1];
|
||||
}
|
||||
|
||||
_Bool Stack_Pop(Stack *s, unsigned int n)
|
||||
{
|
||||
if(s->used < n)
|
||||
return 0;
|
||||
|
||||
s->used -= n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
_Bool Stack_Push(Stack *s, void *item)
|
||||
{
|
||||
assert(s != NULL);
|
||||
assert(item != NULL);
|
||||
|
||||
if(s->used == s->size)
|
||||
return 0;
|
||||
|
||||
s->body[s->used] = item;
|
||||
s->used += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Stack_Free(Stack *s)
|
||||
{
|
||||
free(s);
|
||||
}
|
||||
|
||||
unsigned int Stack_Size(Stack *s)
|
||||
{
|
||||
return s->used;
|
||||
}
|
||||
|
||||
unsigned int Stack_Capacity(Stack *s)
|
||||
{
|
||||
return s->size;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef STACK_H
|
||||
#define STACK_H
|
||||
typedef struct xStack Stack;
|
||||
void *Stack_New(int size);
|
||||
void *Stack_Top(Stack *s, int n);
|
||||
_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);
|
||||
unsigned int Stack_Capacity(Stack *s);
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
./build/test-parse tests/aux/parsing-tests.json
|
||||
./build/test-objects
|
||||
@@ -0,0 +1,104 @@
|
||||
[
|
||||
{
|
||||
"source": "1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "int",
|
||||
"value": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "1.1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "float",
|
||||
"value": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "1+1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "add",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1},
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "1-1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "sub",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1},
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "1*1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "mul",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1},
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "1-+1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "sub",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1},
|
||||
{
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "pos",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "+1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "pos",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "-1",
|
||||
"expect": {
|
||||
"node-kind": "expr",
|
||||
"expr-kind": "neg",
|
||||
"operands": [
|
||||
{"node-kind": "expr", "expr-kind": "int", "value": 1}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "hello",
|
||||
"expect": {"node-kind": "expr", "expr-kind": "ident", "value": "hello"}
|
||||
},
|
||||
{
|
||||
"source": "'hello'",
|
||||
"expect": {"node-kind": "expr", "expr-kind": "string", "value": "hello"}
|
||||
},
|
||||
{
|
||||
"source": "-'hello'",
|
||||
"expect": {"node-kind": "expr", "expr-kind": "neg", "operands": [{"node-kind": "expr", "expr-kind": "string", "value": "hello"}]}
|
||||
},
|
||||
{
|
||||
"source": "-'hel\\nlo'",
|
||||
"expect": {"node-kind": "expr", "expr-kind": "neg", "operands": [{"node-kind": "expr", "expr-kind": "string", "value": "hel\\nlo"}]}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "../../src/objects/objects.h"
|
||||
|
||||
#define tassert(exp) if(!(exp)) goto done;
|
||||
#define tdone done: if(1)
|
||||
|
||||
static _Bool test_int()
|
||||
{
|
||||
_Bool passed = 0;
|
||||
|
||||
Heap *heap = Heap_New(-1);
|
||||
assert(heap != NULL);
|
||||
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
// Test [Object_FromInt], [Object_ToInt].
|
||||
|
||||
int n = -3; // Just any value.
|
||||
|
||||
Object *obj = Object_FromInt(n, heap, &error);
|
||||
|
||||
tassert(obj != 0);
|
||||
tassert(error.occurred == 0);
|
||||
|
||||
int k = Object_ToInt(obj, &error);
|
||||
|
||||
tassert(k == n);
|
||||
tassert(error.occurred == 0);
|
||||
|
||||
passed = 1;
|
||||
|
||||
tdone {
|
||||
Error_Free(&error);
|
||||
Heap_Free(heap);
|
||||
return passed;
|
||||
}
|
||||
}
|
||||
|
||||
static _Bool test_float()
|
||||
{
|
||||
_Bool passed = 0;
|
||||
|
||||
Heap *heap = Heap_New(-1);
|
||||
assert(heap != NULL);
|
||||
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
// Test [Object_FromFloat], [Object_ToFloat].
|
||||
|
||||
double n = -3.4; // Just any value.
|
||||
|
||||
Object *obj = Object_FromFloat(n, heap, &error);
|
||||
|
||||
if(obj == 0)
|
||||
goto done;
|
||||
|
||||
double k = Object_ToFloat(obj, &error);
|
||||
|
||||
if(k != n)
|
||||
goto done;
|
||||
|
||||
// ...
|
||||
|
||||
passed = 1;
|
||||
done:
|
||||
Error_Free(&error);
|
||||
Heap_Free(heap);
|
||||
return passed;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
typedef _Bool (*test_t)();
|
||||
static const test_t tests[] = {
|
||||
test_int,
|
||||
test_float,
|
||||
};
|
||||
|
||||
for(int i = 0; i < (int) (sizeof(tests) / sizeof(tests[0])); i += 1)
|
||||
printf("TEST %d: %s\n", i, test_int() ? "Passed" : "Failed");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <xjson.h>
|
||||
#include "../../src/compiler/parse.h"
|
||||
#include "../../src/compiler/serialize.h"
|
||||
|
||||
typedef enum { PASSED, FAILED, IGNORED } result_t;
|
||||
|
||||
static _Bool compare(const char *s, xj_value *v)
|
||||
{
|
||||
xj_alloc *alloc = NULL;
|
||||
xj_error error;
|
||||
xj_value *doc;
|
||||
|
||||
doc = xj_decode(&error, &alloc, s, -1);
|
||||
assert(doc != NULL);
|
||||
|
||||
_Bool same = xj_compare(doc, v);
|
||||
|
||||
xj_free(alloc);
|
||||
return same;
|
||||
}
|
||||
|
||||
result_t runtest(xj_value *value, int testno)
|
||||
{
|
||||
int raw_sourcel;
|
||||
char *raw_source;
|
||||
xj_value *expect;
|
||||
|
||||
{
|
||||
xj_error error;
|
||||
xj_value *source2 = xj_query(&error, value, ".source", -1);
|
||||
|
||||
if(source2 == NULL)
|
||||
{
|
||||
if(error.code == xj_E4009)
|
||||
fprintf(stderr, "WARNING: Field \"source\" is missing\n");
|
||||
else
|
||||
fprintf(stderr, "WARNING: Internal failure (%s)\n", error.text);
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
if(source2->type != xj_STRING)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Field \"source\" is not a string\n");
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
raw_source = source2->as_string;
|
||||
raw_sourcel = source2->size;
|
||||
|
||||
expect = xj_query(&error, value, ".expect", -1);
|
||||
|
||||
if(expect == NULL)
|
||||
{
|
||||
if(error.code == xj_E4009)
|
||||
fprintf(stderr, "WARNING: Field \"expect\" is missing\n");
|
||||
else
|
||||
fprintf(stderr, "WARNING: Internal failure (%s)\n", error.text);
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
if(expect->type != xj_OBJECT)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Field \"expect\" is not an object\n");
|
||||
return IGNORED;
|
||||
}
|
||||
}
|
||||
|
||||
Source *source;
|
||||
{
|
||||
Error error;
|
||||
Error_Init(&error);
|
||||
|
||||
char temp[64];
|
||||
int k = snprintf(temp, sizeof(temp), "(Test %d's source)", testno);
|
||||
|
||||
assert(k >= 0);
|
||||
assert((unsigned int) k < sizeof(temp));
|
||||
|
||||
source = Source_FromString(temp, raw_source, raw_sourcel, &error);
|
||||
|
||||
if(source == NULL)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Internal failure (%s)\n", error.message);
|
||||
return IGNORED;
|
||||
}
|
||||
}
|
||||
|
||||
_Bool passed;
|
||||
{
|
||||
Error error;
|
||||
BPAlloc *alloc;
|
||||
char buffer[65536];
|
||||
|
||||
Error_Init(&error);
|
||||
|
||||
alloc = BPAlloc_Init3(buffer, sizeof(buffer), -1, NULL, NULL, NULL);
|
||||
|
||||
if(alloc == NULL)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Internal failure (Failed to instanciate BPAlloc)\n");
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
AST *ast = parse(source, alloc, &error);
|
||||
|
||||
if(ast == NULL)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Internal failure (Failed to compile: %s; reported in %s at %s:%d)\n", error.message, error.func, error.file, error.line);
|
||||
|
||||
Error_Free(&error);
|
||||
BPAlloc_Free(alloc);
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
char *serialized = serialize(ast, NULL);
|
||||
|
||||
if(serialized == NULL)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Internal failure (Failed to serialize AST)\n");
|
||||
|
||||
Error_Free(&error);
|
||||
BPAlloc_Free(alloc);
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
/*
|
||||
printf("SERIALIZED :: %s\n", serialized);
|
||||
{
|
||||
char *temp = xj_encode(expect, NULL);
|
||||
printf("SERIALIZED 2 :: %s\n", temp);
|
||||
xj_sys_free(temp);
|
||||
}
|
||||
*/
|
||||
|
||||
passed = compare(serialized, expect);
|
||||
|
||||
Error_Free(&error);
|
||||
BPAlloc_Free(alloc);
|
||||
xj_sys_free(serialized);
|
||||
}
|
||||
|
||||
return passed ? PASSED : FAILED;
|
||||
}
|
||||
|
||||
static char *loadfile(const char *file, int *size);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if(argc == 1)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Missing file name\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int filesize;
|
||||
char *filename = argv[1];
|
||||
char *filetext = loadfile(filename, &filesize);
|
||||
|
||||
if(filetext == NULL)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Couldn't open \"%s\"\n", filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
xj_alloc *alloc = NULL;
|
||||
xj_error error;
|
||||
xj_value *document;
|
||||
|
||||
document = xj_decode(&error, &alloc, filetext, filesize);
|
||||
|
||||
free(filetext);
|
||||
|
||||
if(document == NULL)
|
||||
{
|
||||
assert(error.occurred == 1);
|
||||
fprintf(stderr, "ERROR: Failed to parse \"%s\" as JSON: %s\n",
|
||||
filename, error.text);
|
||||
xj_free(alloc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(document->type != xj_ARRAY)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Was expected an array as JSON document root\n");
|
||||
xj_free(alloc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
xj_value *cursor = document->as_array;
|
||||
|
||||
int passed = 0, ignored = 0;
|
||||
int total = document->size;
|
||||
int index = 1;
|
||||
|
||||
while(cursor)
|
||||
{
|
||||
result_t result = runtest(cursor, index);
|
||||
|
||||
#define KNRM "\x1B[0m"
|
||||
#define KRED "\x1B[31m"
|
||||
#define KGRN "\x1B[32m"
|
||||
#define KYEL "\x1B[33m"
|
||||
#define KBLU "\x1B[34m"
|
||||
#define KMAG "\x1B[35m"
|
||||
#define KCYN "\x1B[36m"
|
||||
#define KWHT "\x1B[37m"
|
||||
#define RESET "\033[0m"
|
||||
|
||||
switch(result)
|
||||
{
|
||||
case PASSED:
|
||||
printf("TEST %-2d :: " KGRN "PASSED" RESET "\n", index);
|
||||
passed += 1;
|
||||
break;
|
||||
|
||||
case FAILED:
|
||||
printf("TEST %-2d :: " KRED "FAILED" RESET "\n", index);
|
||||
break;
|
||||
|
||||
case IGNORED:
|
||||
printf("TEST %-2d :: " KYEL "IGNORED" RESET "\n", index);
|
||||
ignored += 1;
|
||||
break;
|
||||
|
||||
default: assert(0);
|
||||
}
|
||||
|
||||
index += 1;
|
||||
cursor = cursor->next;
|
||||
}
|
||||
|
||||
printf("\n ... %d passed, %d failed and %d ignored.\n", passed, total - passed - ignored, ignored);
|
||||
|
||||
xj_free(alloc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *loadfile(const char *file, int *size)
|
||||
{
|
||||
char *body = NULL;
|
||||
int _size;
|
||||
|
||||
FILE *fp = fopen(file, "rb");
|
||||
|
||||
if(fp == NULL)
|
||||
return NULL;
|
||||
|
||||
if(fseek(fp, 0, SEEK_END))
|
||||
goto done;
|
||||
|
||||
_size = ftell(fp);
|
||||
|
||||
if(_size < 0)
|
||||
goto done;
|
||||
|
||||
if(size)
|
||||
*size = _size;
|
||||
|
||||
body = malloc(_size + 1);
|
||||
|
||||
if(body == NULL)
|
||||
goto done;
|
||||
|
||||
if(fseek(fp, 0, SEEK_SET))
|
||||
goto done;
|
||||
|
||||
int k = fread(body, 1, _size, fp);
|
||||
|
||||
if(k != _size)
|
||||
goto done;
|
||||
|
||||
body[_size] = '\0';
|
||||
|
||||
done:
|
||||
fclose(fp);
|
||||
return body;
|
||||
}
|
||||
Reference in New Issue
Block a user