View on GitHub

Toy

A toy programming language.

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 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.

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.

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.

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:

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.

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.

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.

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.

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).

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:

Toy_Literal foobarIdn = foobar;
if (TOY_IS_IDENTIFIER(foobar) && Toy_parseIdentifierToValue(interpreter, &foobar)) {
	freeLiteral(foobarIdn); //remember to free the identifier
}

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:

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.

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:

static void assertWrapper(const char* output) {
	fprintf(stderr, "Assertion failure: %s\n", output);
}

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:

static void errorWrapper(const char* output) {
	fprintf(stderr, "%s", output); //no newline
}