commit 7a5d0d1dcb86cb04d8fa3e57e5c942c3767dbec4 Author: cozis Date: Sun Oct 31 04:17:57 2021 +0000 first commit diff --git a/3p/include/xjson.h b/3p/include/xjson.h new file mode 100644 index 0000000..30eadcf --- /dev/null +++ b/3p/include/xjson.h @@ -0,0 +1,217 @@ + +#ifndef XJSON_H +#define XJSON_H + +#include +#include + +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 \ No newline at end of file diff --git a/3p/libs/libxjson.a b/3p/libs/libxjson.a new file mode 100644 index 0000000..cd3e287 Binary files /dev/null and b/3p/libs/libxjson.a differ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d4db02d --- /dev/null +++ b/build.sh @@ -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 \ No newline at end of file diff --git a/build/libnoja-compile.a b/build/libnoja-compile.a new file mode 100644 index 0000000..e8f3e9b Binary files /dev/null and b/build/libnoja-compile.a differ diff --git a/build/libnoja-objects.a b/build/libnoja-objects.a new file mode 100644 index 0000000..f65e059 Binary files /dev/null and b/build/libnoja-objects.a differ diff --git a/build/libnoja-runtime.a b/build/libnoja-runtime.a new file mode 100644 index 0000000..9a94873 Binary files /dev/null and b/build/libnoja-runtime.a differ diff --git a/build/noja b/build/noja new file mode 100755 index 0000000..3d7a06c Binary files /dev/null and b/build/noja differ diff --git a/build/test-objects b/build/test-objects new file mode 100755 index 0000000..0b5ea66 Binary files /dev/null and b/build/test-objects differ diff --git a/build/test-parse b/build/test-parse new file mode 100755 index 0000000..e35df71 Binary files /dev/null and b/build/test-parse differ diff --git a/include/noja/compile.h b/include/noja/compile.h new file mode 100644 index 0000000..e69de29 diff --git a/include/noja/objects.h b/include/noja/objects.h new file mode 100644 index 0000000..e69de29 diff --git a/samples/gang.noja b/samples/gang.noja new file mode 100644 index 0000000..f710f37 --- /dev/null +++ b/samples/gang.noja @@ -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; \ No newline at end of file diff --git a/src/common/executable.c b/src/common/executable.c new file mode 100644 index 0000000..449dbd2 --- /dev/null +++ b/src/common/executable.c @@ -0,0 +1,365 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/src/common/executable.h b/src/common/executable.h new file mode 100644 index 0000000..228cb6c --- /dev/null +++ b/src/common/executable.h @@ -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 \ No newline at end of file diff --git a/src/compiler/AST.h b/src/compiler/AST.h new file mode 100644 index 0000000..82b755b --- /dev/null +++ b/src/compiler/AST.h @@ -0,0 +1,4 @@ +#ifndef AST_H +#define AST_H +typedef struct xAST AST; +#endif \ No newline at end of file diff --git a/src/compiler/ASTi.h b/src/compiler/ASTi.h new file mode 100644 index 0000000..3b294f9 --- /dev/null +++ b/src/compiler/ASTi.h @@ -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 \ No newline at end of file diff --git a/src/compiler/compile.c b/src/compiler/compile.c new file mode 100644 index 0000000..9544af4 --- /dev/null +++ b/src/compiler/compile.c @@ -0,0 +1,123 @@ +#include +#include +#include +#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; +} \ No newline at end of file diff --git a/src/compiler/compile.h b/src/compiler/compile.h new file mode 100644 index 0000000..7530eff --- /dev/null +++ b/src/compiler/compile.h @@ -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 \ No newline at end of file diff --git a/src/compiler/parse.c b/src/compiler/parse.c new file mode 100644 index 0000000..a07a14c --- /dev/null +++ b/src/compiler/parse.c @@ -0,0 +1,762 @@ +#include +#include +#include +#include +#include +#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); +} \ No newline at end of file diff --git a/src/compiler/parse.h b/src/compiler/parse.h new file mode 100644 index 0000000..99020ce --- /dev/null +++ b/src/compiler/parse.h @@ -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 \ No newline at end of file diff --git a/src/compiler/serialize.c b/src/compiler/serialize.c new file mode 100644 index 0000000..9059713 --- /dev/null +++ b/src/compiler/serialize.c @@ -0,0 +1,161 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/src/compiler/serialize.h b/src/compiler/serialize.h new file mode 100644 index 0000000..f691054 --- /dev/null +++ b/src/compiler/serialize.h @@ -0,0 +1,5 @@ +#ifndef SERIALIZE_H +#define SERIALIZE_H +#include "AST.h" +char *serialize(AST *ast, int *len); +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1841e77 --- /dev/null +++ b/src/main.c @@ -0,0 +1,335 @@ +#include +#include +#include +#include +#include "compiler/parse.h" +#include "compiler/serialize.h" +#include "compiler/compile.h" +#include "runtime/runtime.h" +#include "runtime/runtime_error.h" + +/* $ noja -f + * $ noja -c + * $ noja -f -o + */ + +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; +} + diff --git a/src/objects/heap.c b/src/objects/heap.c new file mode 100644 index 0000000..7288352 --- /dev/null +++ b/src/objects/heap.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#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; +} \ No newline at end of file diff --git a/src/objects/o_float.c b/src/objects/o_float.c new file mode 100644 index 0000000..81a5e4c --- /dev/null +++ b/src/objects/o_float.c @@ -0,0 +1,40 @@ +#include +#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; +} diff --git a/src/objects/o_int.c b/src/objects/o_int.c new file mode 100644 index 0000000..2219336 --- /dev/null +++ b/src/objects/o_int.c @@ -0,0 +1,41 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/src/objects/o_map.c b/src/objects/o_map.c new file mode 100644 index 0000000..d9307d2 --- /dev/null +++ b/src/objects/o_map.c @@ -0,0 +1,261 @@ +#include +#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; +} \ No newline at end of file diff --git a/src/objects/o_none.c b/src/objects/o_none.c new file mode 100644 index 0000000..16fa570 --- /dev/null +++ b/src/objects/o_none.c @@ -0,0 +1,21 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/src/objects/o_string.c b/src/objects/o_string.c new file mode 100644 index 0000000..a96e761 --- /dev/null +++ b/src/objects/o_string.c @@ -0,0 +1,69 @@ +#include +#include +#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); +} \ No newline at end of file diff --git a/src/objects/objects.c b/src/objects/objects.c new file mode 100644 index 0000000..862557d --- /dev/null +++ b/src/objects/objects.c @@ -0,0 +1,286 @@ +#include +#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); +} \ No newline at end of file diff --git a/src/objects/objects.h b/src/objects/objects.h new file mode 100644 index 0000000..c126aba --- /dev/null +++ b/src/objects/objects.h @@ -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 \ No newline at end of file diff --git a/src/runtime/o_func.c b/src/runtime/o_func.c new file mode 100644 index 0000000..3dc18b1 --- /dev/null +++ b/src/runtime/o_func.c @@ -0,0 +1,53 @@ +#include +#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; +} \ No newline at end of file diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c new file mode 100644 index 0000000..1121801 --- /dev/null +++ b/src/runtime/runtime.c @@ -0,0 +1,522 @@ +#include +#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; +} \ No newline at end of file diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h new file mode 100644 index 0000000..5267e2c --- /dev/null +++ b/src/runtime/runtime.h @@ -0,0 +1,22 @@ +#ifndef RUNTIME_H +#define RUNTIME_H +#include // 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 \ No newline at end of file diff --git a/src/runtime/runtime_error.c b/src/runtime/runtime_error.c new file mode 100644 index 0000000..84928bb --- /dev/null +++ b/src/runtime/runtime_error.c @@ -0,0 +1,29 @@ +#include +#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); +} \ No newline at end of file diff --git a/src/runtime/runtime_error.h b/src/runtime/runtime_error.h new file mode 100644 index 0000000..2059bd3 --- /dev/null +++ b/src/runtime/runtime_error.h @@ -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 \ No newline at end of file diff --git a/src/utils/bpalloc.c b/src/utils/bpalloc.c new file mode 100644 index 0000000..8bb5500 --- /dev/null +++ b/src/utils/bpalloc.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include "defs.h" +#include "bpalloc.h" + +#if USING_VALGRIND +#include +#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); +} diff --git a/src/utils/bpalloc.h b/src/utils/bpalloc.h new file mode 100644 index 0000000..6c44d9a --- /dev/null +++ b/src/utils/bpalloc.h @@ -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 \ No newline at end of file diff --git a/src/utils/bucketlist.c b/src/utils/bucketlist.c new file mode 100644 index 0000000..62b1ad7 --- /dev/null +++ b/src/utils/bucketlist.c @@ -0,0 +1,177 @@ +#include +#include +#include +#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; + } +} \ No newline at end of file diff --git a/src/utils/bucketlist.h b/src/utils/bucketlist.h new file mode 100644 index 0000000..66e9a63 --- /dev/null +++ b/src/utils/bucketlist.h @@ -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 \ No newline at end of file diff --git a/src/utils/defs.h b/src/utils/defs.h new file mode 100644 index 0000000..c077a38 --- /dev/null +++ b/src/utils/defs.h @@ -0,0 +1,17 @@ +#include + +#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)) \ No newline at end of file diff --git a/src/utils/error.c b/src/utils/error.c new file mode 100644 index 0000000..062d870 --- /dev/null +++ b/src/utils/error.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#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); +} \ No newline at end of file diff --git a/src/utils/error.h b/src/utils/error.h new file mode 100644 index 0000000..d2f478d --- /dev/null +++ b/src/utils/error.h @@ -0,0 +1,26 @@ +#ifndef ERROR_H +#define ERROR_H +#include + +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 \ No newline at end of file diff --git a/src/utils/hash.c b/src/utils/hash.c new file mode 100644 index 0000000..cd20650 --- /dev/null +++ b/src/utils/hash.c @@ -0,0 +1,19 @@ +#include +#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; +} \ No newline at end of file diff --git a/src/utils/hash.h b/src/utils/hash.h new file mode 100644 index 0000000..220ea4b --- /dev/null +++ b/src/utils/hash.h @@ -0,0 +1,4 @@ +#ifndef HASH_H +#define HASH_H +int hashbytes(unsigned char *str, int len); +#endif \ No newline at end of file diff --git a/src/utils/promise.c b/src/utils/promise.c new file mode 100644 index 0000000..8720e25 --- /dev/null +++ b/src/utils/promise.c @@ -0,0 +1,106 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/src/utils/promise.h b/src/utils/promise.h new file mode 100644 index 0000000..9d2c64f --- /dev/null +++ b/src/utils/promise.h @@ -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 \ No newline at end of file diff --git a/src/utils/source.c b/src/utils/source.c new file mode 100644 index 0000000..a7a999b --- /dev/null +++ b/src/utils/source.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#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; +} \ No newline at end of file diff --git a/src/utils/source.h b/src/utils/source.h new file mode 100644 index 0000000..8e32456 --- /dev/null +++ b/src/utils/source.h @@ -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 \ No newline at end of file diff --git a/src/utils/stack.c b/src/utils/stack.c new file mode 100644 index 0000000..1fc2e97 --- /dev/null +++ b/src/utils/stack.c @@ -0,0 +1,73 @@ +#include +#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; +} \ No newline at end of file diff --git a/src/utils/stack.h b/src/utils/stack.h new file mode 100644 index 0000000..e440f77 --- /dev/null +++ b/src/utils/stack.h @@ -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 \ No newline at end of file diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..57d89dc --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +./build/test-parse tests/aux/parsing-tests.json +./build/test-objects \ No newline at end of file diff --git a/tests/aux/parsing-tests.json b/tests/aux/parsing-tests.json new file mode 100644 index 0000000..a11d780 --- /dev/null +++ b/tests/aux/parsing-tests.json @@ -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"}]} + } +] \ No newline at end of file diff --git a/tests/src/test-objects.c b/tests/src/test-objects.c new file mode 100644 index 0000000..2e85472 --- /dev/null +++ b/tests/src/test-objects.c @@ -0,0 +1,86 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/tests/src/test-parse.c b/tests/src/test-parse.c new file mode 100644 index 0000000..0d16718 --- /dev/null +++ b/tests/src/test-parse.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#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; +} \ No newline at end of file