diff --git a/assets/scripts/init.toy b/assets/scripts/init.toy new file mode 100644 index 0000000..1696089 --- /dev/null +++ b/assets/scripts/init.toy @@ -0,0 +1,5 @@ +import engine; + +initWindow("Airport Game", 800, 600, false); + +loadRootNode("assets/scripts/root.toy"); diff --git a/assets/scripts/root.toy b/assets/scripts/root.toy new file mode 100644 index 0000000..8ee348e --- /dev/null +++ b/assets/scripts/root.toy @@ -0,0 +1,14 @@ +//root node can load the whole scene, and essentially act as the scene object +fn onInit() { + print "root.toy:onInit() called"; +} + +fn onStep() { + import standard; + print "root.toy:onStep() called"; + print clock(); +} + +fn onFree() { + print "root.toy:onFree() called"; +} diff --git a/core/engine.c b/core/engine.c index 47316e4..855507b 100644 --- a/core/engine.c +++ b/core/engine.c @@ -1,66 +1,160 @@ #include "engine.h" +#include "lib_engine.h" +#include "lib_standard.h" + +#include "memory.h" +#include "lexer.h" +#include "parser.h" +#include "compiler.h" +#include "interpreter.h" + +#include "console_colors.h" + #include +#include +#include + +//define the engine object +Engine engine; //errors here should be fatal -static void error(Engine* engine, char* message) { +static void fatalError(char* message) { fprintf(stderr, message); exit(-1); } +//compilation functions +//TODO: move these to their own file +static char* readFile(char* path, size_t* fileSize) { + FILE* file = fopen(path, "rb"); + + if (file == NULL) { + fprintf(stderr, ERROR "Could not open file \"%s\"\n" RESET, path); + exit(-1); + } + + fseek(file, 0L, SEEK_END); + *fileSize = ftell(file); + rewind(file); + + char* buffer = (char*)malloc(*fileSize + 1); + + if (buffer == NULL) { + fprintf(stderr, ERROR "Not enough memory to read \"%s\"\n" RESET, path); + exit(-1); + } + + size_t bytesRead = fread(buffer, sizeof(char), *fileSize, file); + + buffer[*fileSize] = '\0'; //NOTE: fread doesn't append this + + if (bytesRead < *fileSize) { + fprintf(stderr, ERROR "Could not read file \"%s\"\n" RESET, path); + exit(-1); + } + + fclose(file); + + return buffer; +} + +static unsigned char* compileString(char* source, size_t* size) { + Lexer lexer; + Parser parser; + Compiler compiler; + + initLexer(&lexer, source); + initParser(&parser, &lexer); + initCompiler(&compiler); + + //run the parser until the end of the source + ASTNode* node = scanParser(&parser); + while(node != NULL) { + //pack up and leave + if (node->type == AST_NODEERROR) { + printf(ERROR "error node detected\n" RESET); + freeNode(node); + freeCompiler(&compiler); + freeParser(&parser); + return NULL; + } + + writeCompiler(&compiler, node); + freeNode(node); + node = scanParser(&parser); + } + + //get the bytecode dump + unsigned char* tb = collateCompiler(&compiler, (int*)(size)); + + //cleanup + freeCompiler(&compiler); + freeParser(&parser); + //no lexer to clean up + + //finally + return tb; +} + //exposed functions -void initEngine(Engine* engine) { +void initEngine() { //clear - engine->root = NULL; - engine->running = true; + engine.rootNode = NULL; + engine.running = false; + engine.window = NULL; + engine.renderer = NULL; //init SDL if (SDL_Init(0) != 0) { - error(engine, "Failed to initialize SDL2"); + fatalError("Failed to initialize SDL2"); } - //init the window - engine->window = SDL_CreateWindow( - "Caption", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - engine->screenWidth, - engine->screenHeight, - SDL_WINDOW_RESIZABLE - ); + //init Toy + initInterpreter(&engine.interpreter); + injectNativeHook(&engine.interpreter, "engine", hookEngine); + injectNativeHook(&engine.interpreter, "standard", hookStandard); - if (engine->window == NULL) { - error(engine, "Failed to initialize the window"); - } + size_t size = 0; + char* source = readFile("./assets/scripts/init.toy", &size); + unsigned char* tb = compileString(source, &size); + free((void*)source); - //init the renderer - engine->renderer = SDL_CreateRenderer(engine->window, -1, 0); - - if (engine->renderer == NULL) { - error(engine, "Failed to initialize the renderer"); - } - - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); - SDL_RenderSetLogicalSize(engine->renderer, engine->screenWidth, engine->screenHeight); + runInterpreter(&engine.interpreter, tb, size); } -void freeEngine(Engine* engine) { - SDL_DestroyRenderer(engine->renderer); - SDL_DestroyWindow(engine->window); +void freeEngine() { + SDL_DestroyRenderer(engine.renderer); + SDL_DestroyWindow(engine.window); SDL_Quit(); - engine->renderer = NULL; - engine->window = NULL; + //clear existing root node + if (engine.rootNode != NULL) { + callEngineNode(engine.rootNode, &engine.interpreter, "onFree"); + + freeEngineNode(engine.rootNode); + FREE(EngineNode, engine.rootNode); + + engine.rootNode = NULL; + } + + freeInterpreter(&engine.interpreter); + + engine.renderer = NULL; + engine.window = NULL; } -static void execStep(Engine* engine) { - //DEBUG: for now, just poll events +static void execStep() { + //call onStep + callEngineNode(engine.rootNode, &engine.interpreter, "onStep"); + + //poll events SDL_Event event; while (SDL_PollEvent(&event)) { switch(event.type) { //quit case SDL_QUIT: { - engine->running = false; + engine.running = false; } break; @@ -68,9 +162,9 @@ static void execStep(Engine* engine) { case SDL_WINDOWEVENT: { switch(event.window.event) { case SDL_WINDOWEVENT_RESIZED: - engine->screenWidth = event.window.data1; - engine->screenHeight = event.window.data2; - SDL_RenderSetLogicalSize(engine->renderer, engine->screenWidth, engine->screenHeight); + engine.screenWidth = event.window.data1; + engine.screenHeight = event.window.data2; + SDL_RenderSetLogicalSize(engine.renderer, engine.screenWidth, engine.screenHeight); break; } } @@ -82,25 +176,29 @@ static void execStep(Engine* engine) { } //the heart of the engine -void execEngine(Engine* engine) { +void execEngine() { + if (!engine.running) { + fatalError("Can't execute the engine (did you forget to initialize the screen?)"); + } + //set up time - gettimeofday(&engine->realTime, NULL); - engine->simTime = engine->realTime; + gettimeofday(&engine.realTime, NULL); + engine.simTime = engine.realTime; struct timeval delta = { .tv_sec = 0, .tv_usec = 1000 * 1000 / 60 }; //60 frames per second - while (engine->running) { + while (engine.running) { //calc the time passed - gettimeofday(&engine->realTime, NULL); + gettimeofday(&engine.realTime, NULL); //if not enough time has passed - if (engine->simTime.tv_sec < engine->realTime.tv_sec && engine->simTime.tv_usec < engine->realTime.tv_usec) { + if (engine.simTime.tv_sec < engine.realTime.tv_sec && engine.simTime.tv_usec < engine.realTime.tv_usec) { //while not enough time has passed - while(engine->simTime.tv_sec < engine->realTime.tv_sec && engine->simTime.tv_usec < engine->realTime.tv_usec) { + while(engine.simTime.tv_sec < engine.realTime.tv_sec && engine.simTime.tv_usec < engine.realTime.tv_usec) { //simulate the world - execStep(engine); + execStep(); //calc the time simulation - timeradd(&delta, &engine->simTime, &engine->simTime); + timeradd(&delta, &engine.simTime, &engine.simTime); } } else { @@ -108,8 +206,8 @@ void execEngine(Engine* engine) { } //render the world - SDL_SetRenderDrawColor(engine->renderer, 0, 0, 0, 255); //NOTE: This line can be disabled later - SDL_RenderClear(engine->renderer); //NOTE: This line can be disabled later - SDL_RenderPresent(engine->renderer); + SDL_SetRenderDrawColor(engine.renderer, 0, 0, 0, 255); //NOTE: This line can be disabled later + SDL_RenderClear(engine.renderer); //NOTE: This line can be disabled later + SDL_RenderPresent(engine.renderer); } } diff --git a/core/engine.h b/core/engine.h index 5ffa14d..c0b7a78 100644 --- a/core/engine.h +++ b/core/engine.h @@ -2,6 +2,7 @@ #include "common.h" #include "engine_node.h" +#include "interpreter.h" #include @@ -10,11 +11,14 @@ //the base engine object, which represents the state of the game typedef struct _engine { //engine stuff - EngineNode* root; + EngineNode* rootNode; struct timeval simTime; struct timeval realTime; bool running; + //Toy stuff + Interpreter interpreter; + //SDL stuff SDL_Window* window; SDL_Renderer* renderer; @@ -22,9 +26,11 @@ typedef struct _engine { int screenHeight; } Engine; -//APIs for initializing the engine -CORE_API void initEngine(Engine* engine); -CORE_API void freeEngine(Engine* engine); +//extern singleton +extern Engine engine; -CORE_API void execEngine(Engine* engine); +//APIs for running the engine in main() +CORE_API void initEngine(); +CORE_API void execEngine(); +CORE_API void freeEngine(); diff --git a/core/lib_engine.c b/core/lib_engine.c new file mode 100644 index 0000000..d53f8a4 --- /dev/null +++ b/core/lib_engine.c @@ -0,0 +1,258 @@ +#include "lib_engine.h" + +#include "engine.h" + +#include "memory.h" +#include "literal_array.h" + +//errors here should be fatal +static void fatalError(char* message) { + fprintf(stderr, message); + exit(-1); +} + +//compilation functions +//TODO: move these to their own file +#include "console_colors.h" +#include "lexer.h" +#include "parser.h" +#include "compiler.h" +#include "interpreter.h" + +static char* readFile(char* path, size_t* fileSize) { + FILE* file = fopen(path, "rb"); + + if (file == NULL) { + fprintf(stderr, ERROR "Could not open file \"%s\"\n" RESET, path); + exit(-1); + } + + fseek(file, 0L, SEEK_END); + *fileSize = ftell(file); + rewind(file); + + char* buffer = (char*)malloc(*fileSize + 1); + + if (buffer == NULL) { + fprintf(stderr, ERROR "Not enough memory to read \"%s\"\n" RESET, path); + exit(-1); + } + + size_t bytesRead = fread(buffer, sizeof(char), *fileSize, file); + + buffer[*fileSize] = '\0'; //NOTE: fread doesn't append this + + if (bytesRead < *fileSize) { + fprintf(stderr, ERROR "Could not read file \"%s\"\n" RESET, path); + exit(-1); + } + + fclose(file); + + return buffer; +} + +static unsigned char* compileString(char* source, size_t* size) { + Lexer lexer; + Parser parser; + Compiler compiler; + + initLexer(&lexer, source); + initParser(&parser, &lexer); + initCompiler(&compiler); + + //run the parser until the end of the source + ASTNode* node = scanParser(&parser); + while(node != NULL) { + //pack up and leave + if (node->type == AST_NODEERROR) { + printf(ERROR "error node detected\n" RESET); + freeNode(node); + freeCompiler(&compiler); + freeParser(&parser); + return NULL; + } + + writeCompiler(&compiler, node); + freeNode(node); + node = scanParser(&parser); + } + + //get the bytecode dump + unsigned char* tb = collateCompiler(&compiler, (int*)(size)); + + //cleanup + freeCompiler(&compiler); + freeParser(&parser); + //no lexer to clean up + + //finally + return tb; +} + +//native functions to be called +static int nativeInitWindow(Interpreter* interpreter, LiteralArray* arguments) { + if (engine.window != NULL) { + fatalError("Can't re-initialize the window\n"); + } + + if (arguments->count != 4) { + fatalError("Incorrect number of arguments passed to initEngine\n"); + } + + //extract the arguments + Literal fscreen = popLiteralArray(arguments); + Literal screenHeight = popLiteralArray(arguments); + Literal screenWidth = popLiteralArray(arguments); + Literal caption = popLiteralArray(arguments); + + //check argument types + if (!IS_STRING(caption) || !IS_INTEGER(screenWidth) || !IS_INTEGER(screenHeight) || !IS_BOOLEAN(fscreen)) { + fatalError("Incorrect argument type passed to initEngine\n"); + } + + //init the window + engine.window = SDL_CreateWindow( + AS_STRING(caption), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + engine.screenWidth = AS_INTEGER(screenWidth), + engine.screenHeight = AS_INTEGER(screenHeight), + IS_TRUTHY(fscreen) ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE + ); + + if (engine.window == NULL) { + fatalError("Failed to initialize the window\n"); + } + + //init the renderer + engine.renderer = SDL_CreateRenderer(engine.window, -1, 0); + + if (engine.renderer == NULL) { + fatalError("Failed to initialize the renderer\n"); + } + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); + SDL_RenderSetLogicalSize(engine.renderer, engine.screenWidth, engine.screenHeight); + + //only run with a window + engine.running = true; + + return 0; +} + +static int nativeLoadRootNode(Interpreter* interpreter, LiteralArray* arguments) { + if (arguments->count != 1) { + interpreter->errorOutput("Incorrect number of arguments passed to loadRootNode\n"); + return -1; + } + + //extract the arguments + Literal fname = popLiteralArray(arguments); + + //check argument types + if (!IS_STRING(fname)) { + interpreter->errorOutput("Incorrect argument type passed to loadRootNode\n"); + freeLiteral(fname); + return -1; + } + + //clear existing root node + if (engine.rootNode != NULL) { + callEngineNode(engine.rootNode, &engine.interpreter, "onFree"); + + freeEngineNode(engine.rootNode); + FREE(EngineNode, engine.rootNode); + + engine.rootNode = NULL; + } + + //load the new root node + size_t size = 0; + char* source = readFile(AS_STRING(fname), &size); + unsigned char* tb = compileString(source, &size); + free((void*)source); + + engine.rootNode = ALLOCATE(EngineNode, 1); + + //BUGFIX: use an inner-interpreter here, otherwise it'll mess up the original's length value by calling run within a native function + Interpreter inner; + initInterpreter(&inner); + + initEngineNode(engine.rootNode, &inner, tb, size); + + freeInterpreter(&inner); + + //init the new node + callEngineNode(engine.rootNode, &engine.interpreter, "onInit"); + + //cleanup + freeLiteral(fname); + + return 0; +} + +//call the hook +typedef struct Natives { + char* name; + NativeFn fn; +} Natives; + +int hookEngine(Interpreter* interpreter, Literal identifier, Literal alias) { + //build the natives list + Natives natives[] = { + {"initWindow", nativeInitWindow}, + {"loadRootNode", nativeLoadRootNode}, + {NULL, NULL} + }; + + //store the library in an aliased dictionary + if (!IS_NULL(alias)) { + //make sure the name isn't taken + if (isDelcaredScopeVariable(interpreter->scope, alias)) { + interpreter->errorOutput("Can't override an existing variable\n"); + freeLiteral(alias); + return false; + } + + //create the dictionary to load up with functions + LiteralDictionary* dictionary = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(dictionary); + + //load the dict with functions + for (int i = 0; natives[i].name; i++) { + Literal name = TO_STRING_LITERAL(copyString(natives[i].name, strlen(natives[i].name)), strlen(natives[i].name)); + Literal func = TO_FUNCTION_LITERAL((void*)natives[i].fn, 0); + func.type = LITERAL_FUNCTION_NATIVE; + + setLiteralDictionary(dictionary, name, func); + + freeLiteral(name); + freeLiteral(func); + } + + //build the type + Literal type = TO_TYPE_LITERAL(LITERAL_DICTIONARY, true); + Literal strType = TO_TYPE_LITERAL(LITERAL_STRING, true); + Literal fnType = TO_TYPE_LITERAL(LITERAL_FUNCTION_NATIVE, true); + TYPE_PUSH_SUBTYPE(&type, strType); + TYPE_PUSH_SUBTYPE(&type, fnType); + + //set scope + Literal dict = TO_DICTIONARY_LITERAL(dictionary); + declareScopeVariable(interpreter->scope, alias, type); + setScopeVariable(interpreter->scope, alias, dict, false); + + //cleanup + freeLiteral(dict); + freeLiteral(type); + return 0; + } + + //default + for (int i = 0; natives[i].name; i++) { + injectNativeFn(interpreter, natives[i].name, natives[i].fn); + } + + return 0; +} diff --git a/core/lib_engine.h b/core/lib_engine.h new file mode 100644 index 0000000..db6a5d5 --- /dev/null +++ b/core/lib_engine.h @@ -0,0 +1,6 @@ +#pragma once + +#include "interpreter.h" + +int hookEngine(Interpreter* interpreter, Literal identifier, Literal alias); + diff --git a/core/lib_standard.c b/core/lib_standard.c new file mode 100644 index 0000000..5ad4bee --- /dev/null +++ b/core/lib_standard.c @@ -0,0 +1,94 @@ +#include "lib_standard.h" + +#include "memory.h" + +#include + +static int nativeClock(Interpreter* interpreter, LiteralArray* arguments) { + //no arguments + if (arguments->count != 0) { + interpreter->errorOutput("Incorrect number of arguments to clock\n"); + return -1; + } + + //get the time from C (what a pain) + time_t rawtime = time(NULL); + struct tm* timeinfo = localtime( &rawtime ); + char* timestr = asctime(timeinfo); + + //push to the stack + int len = strlen(timestr) - 1; //-1 for the newline + Literal timeLiteral = TO_STRING_LITERAL(copyString(timestr, len), len); + + //push to the stack + pushLiteralArray(&interpreter->stack, timeLiteral); + + //cleanup + freeLiteral(timeLiteral); + + return 1; +} + +//call the hook +typedef struct Natives { + char* name; + NativeFn fn; +} Natives; + +int hookStandard(Interpreter* interpreter, Literal identifier, Literal alias) { + //build the natives list + Natives natives[] = { + {"clock", nativeClock}, + {NULL, NULL} + }; + + //store the library in an aliased dictionary + if (!IS_NULL(alias)) { + //make sure the name isn't taken + if (isDelcaredScopeVariable(interpreter->scope, alias)) { + interpreter->errorOutput("Can't override an existing variable\n"); + freeLiteral(alias); + return false; + } + + //create the dictionary to load up with functions + LiteralDictionary* dictionary = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(dictionary); + + //load the dict with functions + for (int i = 0; natives[i].name; i++) { + Literal name = TO_STRING_LITERAL(copyString(natives[i].name, strlen(natives[i].name)), strlen(natives[i].name)); + Literal func = TO_FUNCTION_LITERAL((void*)natives[i].fn, 0); + func.type = LITERAL_FUNCTION_NATIVE; + + setLiteralDictionary(dictionary, name, func); + + freeLiteral(name); + freeLiteral(func); + } + + //build the type + Literal type = TO_TYPE_LITERAL(LITERAL_DICTIONARY, true); + Literal strType = TO_TYPE_LITERAL(LITERAL_STRING, true); + Literal fnType = TO_TYPE_LITERAL(LITERAL_FUNCTION_NATIVE, true); + TYPE_PUSH_SUBTYPE(&type, strType); + TYPE_PUSH_SUBTYPE(&type, fnType); + + //set scope + Literal dict = TO_DICTIONARY_LITERAL(dictionary); + declareScopeVariable(interpreter->scope, alias, type); + setScopeVariable(interpreter->scope, alias, dict, false); + + //cleanup + freeLiteral(dict); + freeLiteral(type); + return 0; + } + + //default + for (int i = 0; natives[i].name; i++) { + injectNativeFn(interpreter, natives[i].name, natives[i].fn); + } + + return 0; +} diff --git a/core/lib_standard.h b/core/lib_standard.h new file mode 100644 index 0000000..46c146d --- /dev/null +++ b/core/lib_standard.h @@ -0,0 +1,6 @@ +#pragma once + +#include "interpreter.h" + +int hookStandard(Interpreter* interpreter, Literal identifier, Literal alias); + diff --git a/source/main.c b/source/main.c index 2bf2c9a..0264f3f 100644 --- a/source/main.c +++ b/source/main.c @@ -1,11 +1,8 @@ #include "engine.h" int main(int argc, char* argv[]) { - Engine engine = { .screenWidth = 800, .screenHeight = 600 }; - - initEngine(&engine); - execEngine(&engine); - freeEngine(&engine); - + initEngine(); + execEngine(); + freeEngine(); return 0; }