#pragma once /*! # toy_interpreter.h This header defines the interpreter structure, which is the beating heart of Toy. `Toy_Interpreter` is a stack-based, bytecode-driven interpreter with a number of customisation options, including "hooks"; native C functions wrapped in `Toy_Literal` instances, injected into the interpreter in order to give the Toy scripts access to libraries via the `import` keyword. The hooks, when invoked this way, can then inject further native functions into the interpreter's current scope. Exactly which hooks are made available varies by host program, but `standard` is the most commonly included one. Another useful customisation feature is the ability to redicrect output from the `print` and `assert` keywords, as well as any internal errors that occur. This can allow you to add in a logging system, or even hook the `print` statement up to some kind of HUD. ## Defined Interfaces Note: These interfaces are *actually* defined in [toy_literal.h](toy_literal_h.md) but are documented here, because this is where it matters most. ### typedef void (*Toy_PrintFn)(const char*) This is the interface used by "print functions" - that is, functions used to print messages from the `print` and `assert` keywords, as well as internal interpreter errors. ### typedef int (*Toy_NativeFn)(struct Toy_Interpreter* interpreter, struct Toy_LiteralArray* arguments) This is the interface used by "native functions" - that is, functions written in C which can be called directly by Toy scripts. The arguments to the function are passed in as a `Toy_LiteralArray`. ### typedef int (*Toy_HookFn)(struct Toy_Interpreter* interpreter, struct Toy_Literal identifier, struct Toy_Literal alias) This is the interface used by "hook functions" - that is, functions written in C which are invoked by using the `import` keyword, and are intended to inject other native functions into the current scope. While hook functions are capable of doing other things, this is greatly discouraged. The identifier of the library (its name) is passed in as a `Toy_Literal`, as is any given alias; if no alias is given, then `alias` will be a null literal. Here, the identifier is `standard`, while the alias is `std`. ``` import standard as std; ``` Conventionally, when an alias is given, all of the functions should instead be inserted into a `Toy_LiteralDictionary` which is then inserted into the scope with the alias as its identifier. !*/ #include "toy_common.h" #include "toy_literal.h" #include "toy_literal_array.h" #include "toy_literal_dictionary.h" #include "toy_scope.h" //the interpreter acts depending on the bytecode instructions typedef struct Toy_Interpreter { //input const unsigned char* bytecode; int length; int count; int codeStart; //BUGFIX: for jumps, must be initialized to -1 Toy_LiteralArray literalCache; //read-only - built from the bytecode, refreshed each time new bytecode is provided //operation Toy_Scope* scope; Toy_LiteralArray stack; //Library APIs Toy_LiteralDictionary* hooks; //debug outputs Toy_PrintFn printOutput; Toy_PrintFn assertOutput; Toy_PrintFn errorOutput; int depth; //don't overflow bool panic; } Toy_Interpreter; /*! ## Defined Functions !*/ /*! ### void Toy_initInterpreter(Toy_Interpreter* interpreter) This function initializes the interpreter. It allocates memory for internal systems such as the stack, and zeroes-out systems that have yet to be invoked. Internally, it also invokes `Toy_resetInterpreter` to initialize the environment. !*/ TOY_API void Toy_initInterpreter(Toy_Interpreter* interpreter); //start of program /*! ### void Toy_runInterpreter(Toy_Interpreter* interpreter, const unsigned char* bytecode, size_t length) This function takes a `Toy_Interpreter` and `bytecode` (as well as the `length` of the bytecode), checks its version information, parses and un-flattens the literal cache, and executes the compiled program stored in the bytecode. This function also consumes the bytecode, so the `bytecode` argument is no longer valid after calls. If the given bytecode's embedded version is not compatible with the current interpreter, then this function will refuse to execute. Re-using a `Toy_Interpreter` instance without first resetting it is possible (that's how the repl works), however doing so may have unintended consequences if the scripts are not intended to be used in such a way. Any variables declared will persist. !*/ TOY_API void Toy_runInterpreter(Toy_Interpreter* interpreter, const unsigned char* bytecode, size_t length); /*! ### void Toy_resetInterpreter(Toy_Interpreter* interpreter) This function frees any scopes that the scripts have built up, and generates a new one. It also injects several globally available functions: * set * get * push * pop * length * clear !*/ TOY_API void Toy_resetInterpreter(Toy_Interpreter* interpreter); /*! ### void Toy_freeInterpreter(Toy_Interpreter* interpreter) This function frees a `Toy_Interpreter`, clearing all of the memory used within. That interpreter is no longer valid for use, and must be re-initialized. !*/ TOY_API void Toy_freeInterpreter(Toy_Interpreter* interpreter); /*! ### bool Toy_injectNativeFn(Toy_Interpreter* interpreter, const char* name, Toy_NativeFn func) This function will inject the given native function `func` into the `Toy_Interpreter`'s current scope, with the identifer as `name`. Both the name and function will be converted into literals internally before being stored. It will return true on success, otherwise it will return false. The primary use of this function is within hooks. !*/ TOY_API bool Toy_injectNativeFn(Toy_Interpreter* interpreter, const char* name, Toy_NativeFn func); /*! ### bool Toy_injectNativeHook(Toy_Interpreter* interpreter, const char* name, Toy_HookFn hook) This function will inject the given native function `hook` into the `Toy_Interpreter`'s hook cache, with the identifier as `name`. Both the name and the function will be converted into literals internally before being stored. It will return true on success, otherwise it will return false. Hooks are invoked with the `import` keyword within Toy's scripts. !*/ TOY_API bool Toy_injectNativeHook(Toy_Interpreter* interpreter, const char* name, Toy_HookFn hook); /*! ### bool Toy_callLiteralFn(Toy_Interpreter* interpreter, Toy_Literal func, Toy_LiteralArray* arguments, Toy_LiteralArray* returns) This function calls a `Toy_Literal` which contains a function, with the arguments to that function passed in as `arguments` and the results stored in `returns`. It returns true on success, otherwise it returns false. The literal `func` can be either a native function or a Toy function, but it won't execute a hook. !*/ TOY_API bool Toy_callLiteralFn(Toy_Interpreter* interpreter, Toy_Literal func, Toy_LiteralArray* arguments, Toy_LiteralArray* returns); /*! ### bool Toy_callFn(Toy_Interpreter* interpreter, const char* name, Toy_LiteralArray* arguments, Toy_LiteralArray* returns) This utility function will find a `Toy_literal` within the `Toy_Interpreter`'s scope with an identifier that matches `name`, and will invoke it using `Toy_callLiteralFn` (passing in `arguments` and `returns` as expected). !*/ TOY_API bool Toy_callFn(Toy_Interpreter* interpreter, const char* name, Toy_LiteralArray* arguments, Toy_LiteralArray* returns); /*! ### bool Toy_parseIdentifierToValue(Toy_Interpreter* interpreter, Toy_Literal* literalPtr) Sometimes, native functions will receive `Toy_Literal` identifiers instead of the values - the correct values can be retreived from the given interpreter's scope using the following pattern: ```c Toy_Literal foobarIdn = foobar; if (TOY_IS_IDENTIFIER(foobar) && Toy_parseIdentifierToValue(interpreter, &foobar)) { freeLiteral(foobarIdn); //remember to free the identifier } ``` !*/ TOY_API bool Toy_parseIdentifierToValue(Toy_Interpreter* interpreter, Toy_Literal* literalPtr); /*! ### void Toy_setInterpreterPrint(Toy_Interpreter* interpreter, Toy_PrintFn printOutput) This function sets the function called by the `print` keyword. By default, the following wrapper is used: ```c static void printWrapper(const char* output) { printf("%s\n", output); } ``` Note: The above is a very minor lie - in reality there are some preprocessor directives to allow the repl's `-n` flag to work. !*/ TOY_API void Toy_setInterpreterPrint(Toy_Interpreter* interpreter, Toy_PrintFn printOutput); /*! ### void Toy_setInterpreterAssert(Toy_Interpreter* interpreter, Toy_PrintFn assertOutput) This function sets the function called by the `assert` keyword on failure. By default, the following wrapper is used: ```c static void assertWrapper(const char* output) { fprintf(stderr, "Assertion failure: %s\n", output); } ``` !*/ TOY_API void Toy_setInterpreterAssert(Toy_Interpreter* interpreter, Toy_PrintFn assertOutput); /*! ### void Toy_setInterpreterError(Toy_Interpreter* interpreter, Toy_PrintFn errorOutput) This function sets the function called when an error occurs within the interpreter. By default, the following wrapper is used: ```c static void errorWrapper(const char* output) { fprintf(stderr, "%s", output); //no newline } ``` !*/ TOY_API void Toy_setInterpreterError(Toy_Interpreter* interpreter, Toy_PrintFn errorOutput);