diff --git a/Makefile b/Makefile index b991ab7..f3bb83b 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,8 @@ CHDR=\ src/cvm.h\ src/cobj.h\ src/cbaselib.h\ + src/cdump.h\ + src/cundump.h\ CSRC=\ src/cchunk.c\ @@ -33,6 +35,8 @@ CSRC=\ src/cvm.c\ src/cobj.c\ src/cbaselib.c\ + src/cdump.c\ + src/cundump.c\ main.c\ COBJ=$(CSRC:.c=.o) diff --git a/main.c b/main.c index 51f45b5..e216ffc 100644 --- a/main.c +++ b/main.c @@ -1,21 +1,29 @@ -#include "cosmo.h" +#include "cbaselib.h" #include "cchunk.h" #include "cdebug.h" -#include "cvm.h" -#include "cparse.h" -#include "cbaselib.h" - +#include "cdump.h" #include "cmem.h" +#include "cosmo.h" +#include "cparse.h" +#include "cundump.h" +#include "cvm.h" + +#include +#include +#include +#include static bool _ACTIVE = false; -int cosmoB_quitRepl(CState *state, int nargs, CValue *args) { +int cosmoB_quitRepl(CState *state, int nargs, CValue *args) +{ _ACTIVE = false; return 0; // we don't return anything } -int cosmoB_input(CState *state, int nargs, CValue *args) { +int cosmoB_input(CState *state, int nargs, CValue *args) +{ // input() accepts the same params as print()! for (int i = 0; i < nargs; i++) { CObjString *str = cosmoV_toString(state, args[i]); @@ -26,16 +34,18 @@ int cosmoB_input(CState *state, int nargs, CValue *args) { char line[1024]; fgets(line, sizeof(line), stdin); - cosmoV_pushRef(state, (CObj*)cosmoO_copyString(state, line, strlen(line)-1)); // -1 for the \n + cosmoV_pushRef(state, + (CObj *)cosmoO_copyString(state, line, strlen(line) - 1)); // -1 for the \n return 1; // 1 return value } -static bool interpret(CState *state, const char *script, const char *mod) { +static bool interpret(CState *state, const char *script, const char *mod) +{ bool ret; // cosmoV_compileString pushes the result onto the stack (COBJ_ERROR or COBJ_CLOSURE) - if (cosmoV_compileString(state, script, mod)) { + if (cosmoV_compileString(state, script, mod)) { COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected if (res == COSMOVM_RUNTIME_ERR) @@ -50,15 +60,11 @@ static bool interpret(CState *state, const char *script, const char *mod) { return !ret; } -static void repl() { +static void repl(CState *state) +{ char line[1024]; _ACTIVE = true; - CState *state = cosmoV_newState(); - cosmoB_loadLibrary(state); - cosmoB_loadOSLib(state); - cosmoB_loadVM(state); - // add our custom REPL functions cosmoV_pushString(state, "quit"); cosmoV_pushCFunction(state, cosmoB_quitRepl); @@ -78,12 +84,11 @@ static void repl() { interpret(state, line, "REPL"); } - - cosmoV_freeState(state); } -static char *readFile(const char* path) { - FILE* file = fopen(path, "rb"); +static char *readFile(const char *path) +{ + FILE *file = fopen(path, "rb"); if (file == NULL) { fprintf(stderr, "Could not open file \"%s\".\n", path); exit(74); @@ -94,7 +99,7 @@ static char *readFile(const char* path) { size_t fileSize = ftell(file); rewind(file); - char *buffer = (char*)malloc(fileSize + 1); // make room for the null byte + char *buffer = (char *)malloc(fileSize + 1); // make room for the null byte if (buffer == NULL) { fprintf(stderr, "failed to allocate!"); exit(1); @@ -106,7 +111,7 @@ static char *readFile(const char* path) { printf("failed to read file \"%s\"!\n", path); exit(74); } - + buffer[bytesRead] = '\0'; // place our null terminator // close the file handler and return the script buffer @@ -114,12 +119,10 @@ static char *readFile(const char* path) { return buffer; } -static bool runFile(const char* fileName) { +static bool runFile(CState *state, const char *fileName) +{ bool ret; - char* script = readFile(fileName); - CState *state = cosmoV_newState(); - cosmoB_loadLibrary(state); - cosmoB_loadOSLib(state); + char *script = readFile(fileName); // add our input() function to the global table cosmoV_pushString(state, "input"); @@ -129,21 +132,119 @@ static bool runFile(const char* fileName) { ret = interpret(state, script, fileName); - cosmoV_freeState(state); free(script); return ret; // let the caller know if the script failed } -int main(int argc, const char *argv[]) { - if (argc == 1) { - repl(); - } else if (argc >= 2) { // they passed a file (or more lol) - for (int i = 1; i < argc; i++) { - if (!runFile(argv[i])) { +int fileWriter(CState *state, const void *data, size_t size, const void *ud) +{ + return !fwrite(data, size, 1, (FILE *)ud); +} + +int fileReader(CState *state, void *data, size_t size, const void *ud) +{ + return fread(data, size, 1, (FILE *)ud) != 1; +} + +void compileScript(CState *state, const char *in, const char *out) +{ + char *script = readFile(in); + + FILE *fout = fopen(out, "wb"); + + if (cosmoV_compileString(state, script, in)) { + CObjFunction *func = cosmoV_readClosure(*cosmoV_getTop(state, 0))->function; + cosmoD_dump(state, func, fileWriter, (void *)fout); + } else { + cosmoV_pop(state); // pop the error off the stack + cosmoV_printError(state, state->error); + } + + free(script); + fclose(fout); + + printf("[!] compiled %s to %s successfully!\n", in, out); +} + +void loadScript(CState *state, const char *in) +{ + FILE *file; + + file = fopen(in, "rb"); + if (!cosmoV_undump(state, fileReader, file)) { + cosmoV_pop(state); // pop the error off the stack + cosmoV_printError(state, state->error); + return; + }; + + printf("[!] loaded %s!\n", in); + COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected + + if (res == COSMOVM_RUNTIME_ERR) + cosmoV_printError(state, state->error); + + fclose(file); +} + +void printUsage(const char *name) +{ + printf("Usage: %s [-clsr] [args]\n\n", name); + printf("available options are:\n" + "-c \tcompile and dump to \n" + "-l \t\tload dump from \n" + "-s \tcompile and run script(s)\n" + "-r\t\tstart the repl\n\n"); +} + +int main(int argc, char *const argv[]) +{ + CState *state = cosmoV_newState(); + cosmoB_loadLibrary(state); + cosmoB_loadOSLib(state); + cosmoB_loadVM(state); + + int opt; + bool isValid = false; + while ((opt = getopt(argc, argv, "clsr")) != -1) { + switch (opt) { + case 'c': + if (optind >= argc - 1) { + printf("Usage: %s -c \n", argv[0]); exit(EXIT_FAILURE); + } else { + compileScript(state, argv[optind], argv[optind + 1]); } + isValid = true; + break; + case 'l': + if (optind >= argc) { + printf("Usage: %s -l \n", argv[0]); + exit(EXIT_FAILURE); + } else { + loadScript(state, argv[optind]); + } + isValid = true; + break; + case 's': + for (int i = optind; i < argc; i++) { + if (!runFile(state, argv[i])) { + printf("failed to run %s!\n", argv[i]); + exit(EXIT_FAILURE); + } + } + isValid = true; + break; + case 'r': + repl(state); + isValid = true; + break; } } + if (!isValid) { + printUsage(argv[0]); + } + + cosmoV_freeState(state); return 0; } diff --git a/src/cbaselib.c b/src/cbaselib.c index 069a944..0a2e769 100644 --- a/src/cbaselib.c +++ b/src/cbaselib.c @@ -980,6 +980,4 @@ void cosmoB_loadVM(CState *state) // register "vm" to the global table cosmoV_register(state, 1); - - printf("[WARNING] the vm.* library has been loaded!\n"); } diff --git a/src/cdump.c b/src/cdump.c new file mode 100644 index 0000000..ebc2187 --- /dev/null +++ b/src/cdump.c @@ -0,0 +1,204 @@ +#include "cdump.h" + +#include "cdebug.h" +#include "cmem.h" +#include "cobj.h" +#include "cvalue.h" +#include "cvm.h" + +typedef struct +{ + CState *state; + const void *userData; + cosmo_Writer writer; + int writerStatus; +} DumpState; + +static bool writeCValue(DumpState *dstate, CValue val); + +#define check(e) \ + if (!e) { \ + return false; \ + } + +static void initDumpState(CState *state, DumpState *dstate, cosmo_Writer writer, + const void *userData) +{ + dstate->state = state; + dstate->userData = userData; + dstate->writer = writer; + dstate->writerStatus = 0; +} + +static bool writeBlock(DumpState *dstate, const void *data, size_t size) +{ + if (dstate->writerStatus == 0) { + dstate->writerStatus = dstate->writer(dstate->state, data, size, dstate->userData); + } + + return dstate->writerStatus == 0; +} + +static bool writeu8(DumpState *dstate, uint8_t d) +{ + return writeBlock(dstate, &d, sizeof(uint8_t)); +} + +static bool writeu32(DumpState *dstate, uint32_t d) +{ + return writeBlock(dstate, &d, sizeof(uint32_t)); +} + +static bool writeSize(DumpState *dstate, size_t d) +{ + return writeBlock(dstate, &d, sizeof(size_t)); +} + +static bool writeVector(DumpState *dstate, const void *data, size_t size, size_t count) +{ + check(writeSize(dstate, count)); + check(writeBlock(dstate, data, size * count)); + + return true; +} + +static bool writeHeader(DumpState *dstate) +{ + check(writeBlock(dstate, COSMO_MAGIC, COSMO_MAGIC_LEN)); + + /* after the magic, we write some platform information */ + check(writeu8(dstate, cosmoD_isBigEndian())); + check(writeu8(dstate, sizeof(cosmo_Number))); + check(writeu8(dstate, sizeof(size_t))); + check(writeu8(dstate, sizeof(int))); + + return true; +} + +static bool writeCObjString(DumpState *dstate, CObjString *obj) +{ + if (obj == NULL) { /* this is in case cobjfunction's name or module strings are null */ + check(writeu32(dstate, 0)); + return true; + } + + /* write string length */ + check(writeu32(dstate, obj->length)); + + /* write string data */ + check(writeBlock(dstate, obj->str, obj->length)); + + return true; +} + +static bool writeCObjFunction(DumpState *dstate, CObjFunction *obj) +{ + check(writeCObjString(dstate, obj->name)); + check(writeCObjString(dstate, obj->module)); + + check(writeu32(dstate, obj->args)); + check(writeu32(dstate, obj->upvals)); + check(writeu8(dstate, obj->variadic)); + + /* write chunk info */ + check(writeVector(dstate, obj->chunk.buf, sizeof(uint8_t), obj->chunk.count)); + + /* write line info */ + check(writeVector(dstate, obj->chunk.lineInfo, sizeof(int), obj->chunk.count)); + + /* write constants */ + check(writeSize(dstate, obj->chunk.constants.count)); + for (int i = 0; i < obj->chunk.constants.count; i++) { + check(writeCValue(dstate, obj->chunk.constants.values[i])); + } + + return true; +} + +static bool writeCObj(DumpState *dstate, CObj *obj) +{ + /* + we can kind of cheat here since our parser only emits a few very limited CObjs... + CChunks will only ever have the following CObj's in their constant table: + - COBJ_STRING + - COBJ_FUNCTION + + the rest of the objects are created during runtime. yay! + */ + CObjType t = cosmoO_readType(obj); + + /* write cobj type */ + writeu8(dstate, t); + + /* write object payload/body */ + switch (t) { + case COBJ_STRING: + check(writeCObjString(dstate, (CObjString *)obj)); + break; + case COBJ_FUNCTION: + check(writeCObjFunction(dstate, (CObjFunction *)obj)); + break; + default: + cosmoV_error(dstate->state, "invalid cobj type: %d", t); + return false; + } + + return true; +} + +#define WRITE_VAR(dstate, type, expression) \ + { \ + type _tmp = expression; \ + check(writeBlock(dstate, &_tmp, sizeof(_tmp))); \ + break; \ + } + +static bool writeCValue(DumpState *dstate, CValue val) +{ + CosmoType t = GET_TYPE(val); + + /* write value type */ + writeu8(dstate, t); + + /* write value payload/body */ + switch (t) { + case COSMO_TNUMBER: + WRITE_VAR(dstate, cosmo_Number, cosmoV_readNumber(val)) + case COSMO_TBOOLEAN: + WRITE_VAR(dstate, bool, cosmoV_readBoolean(val)) + case COSMO_TREF: + check(writeCObj(dstate, cosmoV_readRef(val))); + break; + case COSMO_TNIL: /* no body */ + break; + default: + cosmoV_error(dstate->state, "invalid value type: %d", t); + return false; + } + + return true; +} + +#undef WRITE_VAR + +bool cosmoD_isBigEndian() +{ + union + { + uint32_t i; + uint8_t c[4]; + } _indxint = {0xDEADB33F}; + + return _indxint.c[0] == 0xDE; +} + +int cosmoD_dump(CState *state, CObjFunction *func, cosmo_Writer writer, const void *userData) +{ + DumpState dstate; + initDumpState(state, &dstate, writer, userData); + + check(writeHeader(&dstate)); + check(writeCObjFunction(&dstate, func)); + + return dstate.writerStatus; +} diff --git a/src/cdump.h b/src/cdump.h new file mode 100644 index 0000000..15bb745 --- /dev/null +++ b/src/cdump.h @@ -0,0 +1,17 @@ +#ifndef COSMO_DUMP_H +#define COSMO_DUMP_H + +#include "cobj.h" +#include "cosmo.h" + +#include + +#define COSMO_MAGIC "COS\x12" +#define COSMO_MAGIC_LEN 4 + +bool cosmoD_isBigEndian(); + +/* returns non-zero on error */ +int cosmoD_dump(CState *state, CObjFunction *func, cosmo_Writer writer, const void *userData); + +#endif \ No newline at end of file diff --git a/src/cosmo.h b/src/cosmo.h index f4878e9..8134ff0 100644 --- a/src/cosmo.h +++ b/src/cosmo.h @@ -45,6 +45,9 @@ typedef struct CObjClosure CObjClosure; typedef uint8_t INSTRUCTION; +typedef int (*cosmo_Reader)(CState *state, void *data, size_t size, const void *ud); +typedef int (*cosmo_Writer)(CState *state, const void *data, size_t size, const void *ud); + #define COSMOMAX_UPVALS 80 #define FRAME_MAX 64 #define STACK_MAX (256 * FRAME_MAX) diff --git a/src/cundump.c b/src/cundump.c new file mode 100644 index 0000000..3165546 --- /dev/null +++ b/src/cundump.c @@ -0,0 +1,219 @@ +#include "cundump.h" + +#include "cchunk.h" +#include "cdump.h" +#include "cmem.h" +#include "cvm.h" + +typedef struct +{ + CState *state; + const void *userData; + cosmo_Reader reader; + int readerStatus; +} UndumpState; + +static bool readCValue(UndumpState *udstate, CValue *val); + +#define check(e) \ + if (!e) { \ + return false; \ + } + +static void initUndumpState(CState *state, UndumpState *udstate, cosmo_Reader reader, + const void *userData) +{ + udstate->state = state; + udstate->userData = userData; + udstate->reader = reader; + udstate->readerStatus = 0; +} + +static bool readBlock(UndumpState *udstate, void *data, size_t size) +{ + if (udstate->readerStatus == 0) { + /* if reader returns 1, we expect an error was thrown */ + udstate->readerStatus = udstate->reader(udstate->state, data, size, udstate->userData); + } + + return udstate->readerStatus == 0; +} + +static bool readu8(UndumpState *udstate, uint8_t *d) +{ + return readBlock(udstate, d, sizeof(uint8_t)); +} + +static bool readu32(UndumpState *udstate, uint32_t *d) +{ + return readBlock(udstate, d, sizeof(uint32_t)); +} + +static bool readSize(UndumpState *udstate, size_t *d) +{ + return readBlock(udstate, d, sizeof(size_t)); +} + +static bool readVector(UndumpState *udstate, void **data, size_t size, size_t *count) +{ + check(readSize(udstate, count)); + *data = cosmoM_xmalloc(udstate->state, (*count) * size); + return readBlock(udstate, *data, (*count) * size); +} + +#define checku8(udstate, d, tmp) \ + check(readu8(udstate, &tmp)); \ + if (d != tmp) { \ + cosmoV_error(udstate->state, "bad header!"); \ + return false; \ + } + +static bool checkHeader(UndumpState *udstate) +{ + char magic[COSMO_MAGIC_LEN]; + uint8_t tmp; + + /* check header */ + check(readBlock(udstate, magic, COSMO_MAGIC_LEN)); + if (memcmp(magic, COSMO_MAGIC, COSMO_MAGIC_LEN) != 0) { + cosmoV_error(udstate->state, "bad header!"); + return false; + } + + /* after the magic, we read some platform information */ + checku8(udstate, cosmoD_isBigEndian(), tmp); + checku8(udstate, sizeof(cosmo_Number), tmp); + checku8(udstate, sizeof(size_t), tmp); + checku8(udstate, sizeof(int), tmp); + + return true; +} + +#undef checku8 + +static bool readCObjString(UndumpState *udstate, CObjString **str) +{ + uint32_t size; + char *data; + + check(readu32(udstate, (uint32_t *)&size)); + if (size == 0) { /* empty string */ + *str = NULL; + return true; + } + + data = cosmoM_xmalloc(udstate->state, size + 1); + check(readBlock(udstate, (void *)data, size)); + data[size] = '\0'; /* add NULL-terminator */ + + *str = cosmoO_takeString(udstate->state, data, size); + return true; +} + +static bool readCObjFunction(UndumpState *udstate, CObjFunction **func) +{ + size_t constants; + CValue val; + + *func = cosmoO_newFunction(udstate->state); + + /* make sure our GC can see that we're currently using this function (and the values it uses) */ + cosmoV_pushRef(udstate->state, (CObj *)*func); + + check(readCObjString(udstate, &(*func)->name)); + check(readCObjString(udstate, &(*func)->module)); + + check(readu32(udstate, (uint32_t *)&(*func)->args)); + check(readu32(udstate, (uint32_t *)&(*func)->upvals)); + check(readu8(udstate, (uint8_t *)&(*func)->variadic)); + + /* read chunk info */ + check( + readVector(udstate, (void **)&(*func)->chunk.buf, sizeof(uint8_t), &(*func)->chunk.count)); + check( + readVector(udstate, (void **)&(*func)->chunk.lineInfo, sizeof(int), &(*func)->chunk.count)); + + /* read constants */ + check(readSize(udstate, &constants)); + for (int i = 0; i < constants; i++) { + check(readCValue(udstate, &val)); + addConstant(udstate->state, &(*func)->chunk, val); + } + + /* pop function off stack */ + cosmoV_pop(udstate->state); + return true; +} + +static bool readCObj(UndumpState *udstate, CObj **obj) +{ + uint8_t type; + check(readu8(udstate, &type)); + + switch (type) { + case COBJ_STRING: + return readCObjString(udstate, (CObjString **)obj); + case COBJ_FUNCTION: + return readCObjFunction(udstate, (CObjFunction **)obj); + default: + cosmoV_error(udstate->state, "unknown object type!"); + return false; + } + + return true; +} + +#define READ_VAR(udstate, val, type, creator) \ + { \ + type _tmp; \ + check(readBlock(udstate, &_tmp, sizeof(type))); \ + *val = creator(_tmp); \ + break; \ + } + +static bool readCValue(UndumpState *udstate, CValue *val) +{ + uint8_t t; + check(readu8(udstate, &t)); + + switch (t) { + case COSMO_TNUMBER: + READ_VAR(udstate, val, cosmo_Number, cosmoV_newNumber) + case COSMO_TBOOLEAN: + READ_VAR(udstate, val, bool, cosmoV_newBoolean) + case COSMO_TREF: { + CObj *obj; + check(readCObj(udstate, (CObj **)&obj)); + *val = cosmoV_newRef(obj); + break; + } + case COSMO_TNIL: + *val = cosmoV_newNil(); + break; + default: + cosmoV_error(udstate->state, "invalid value type: %d", t); + return false; + } + + return true; +} + +#undef READ_VAR +#undef check + +int cosmoD_undump(CState *state, cosmo_Reader reader, const void *userData, CObjFunction **func) +{ + UndumpState udstate; + initUndumpState(state, &udstate, reader, userData); + + if (!checkHeader(&udstate)) { + return 1; + } + + if (!readCObjFunction(&udstate, func)) { + return 1; + } + + cosmoV_pushRef(state, (CObj *)*func); + return udstate.readerStatus; +} \ No newline at end of file diff --git a/src/cundump.h b/src/cundump.h new file mode 100644 index 0000000..763be70 --- /dev/null +++ b/src/cundump.h @@ -0,0 +1,12 @@ +#ifndef COSMO_UNDUMP_H +#define COSMO_UNDUMP_H + +#include "cobj.h" +#include "cosmo.h" + +#include + +/* returns non-zero on error */ +int cosmoD_undump(CState *state, cosmo_Reader reader, const void *userData, CObjFunction **func); + +#endif \ No newline at end of file diff --git a/src/cvm.c b/src/cvm.c index b3735ff..5b248b0 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -4,6 +4,7 @@ #include "cmem.h" #include "cparse.h" #include "cstate.h" +#include "cundump.h" #include #include @@ -30,6 +31,28 @@ COSMO_API void cosmo_insert(CState *state, int indx, CValue val) state->top++; } +COSMO_API bool cosmoV_undump(CState *state, cosmo_Reader reader, const void *ud) +{ + CObjFunction *func; + + if (cosmoD_undump(state, reader, ud, &func)) { + // fail recovery + state->panic = false; + cosmoV_pushRef(state, (CObj *)state->error); + return false; + }; + +#ifdef VM_DEBUG + disasmChunk(&func->chunk, func->name ? func->name->str : UNNAMEDCHUNK, 0); +#endif + + // push function onto the stack so it doesn't it cleaned up by the GC, at the same stack + // location put our closure + cosmoV_pushRef(state, (CObj *)func); + *(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func)); + return true; +} + COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name) { CObjFunction *func; @@ -46,7 +69,7 @@ COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char * return true; } - // fail + // fail recovery state->panic = false; cosmoV_pushRef(state, (CObj *)state->error); return false; diff --git a/src/cvm.h b/src/cvm.h index 6e1fe52..6e3953c 100644 --- a/src/cvm.h +++ b/src/cvm.h @@ -38,7 +38,7 @@ COSMO_API void cosmo_insert(CState *state, int indx, CValue val); COSMO_API bool cosmoV_registerProtoObject(CState *state, CObjType objType, CObjObject *obj); /* - compiles string into a , if successful, will be pushed onto the stack + compiles string into a . if successful, will be pushed onto the stack otherwise the will be pushed. returns: @@ -47,6 +47,16 @@ COSMO_API bool cosmoV_registerProtoObject(CState *state, CObjType objType, CObjO */ COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name); +/* + loads a from a dump. if successful, will be pushed onto the stack + otherwise the will be pushed. + + returns: + false : is at the top of the stack + true : is at the top of the stack +*/ +COSMO_API bool cosmoV_undump(CState *state, cosmo_Reader reader, const void *ud); + /* expects object to be pushed, then the key.