#pragma once #include "stddef.h" #include "stdint.h" /** * Represents the natively defined type of integers */ #define INK_INTEGER 0 /** * Represents the natively defined type of natively defined functions */ #define INK_NATIVE_FUNCTION 1 /** * Represents the natively defined type of functions defined from within ink */ #define INK_FUNCTION 2 /** * Represents the special coroutine state that means that it was disposed of and is ready for reuse */ #define INK_ROUTINE_CAN_REUSE 32 /** * Represents the special coroutine state that means that it was terminated without errors */ #define INK_ROUTINE_SUCCESS 1 #ifdef __cplusplus extern "C" { #endif /** * Represents arbitrary values within ink */ struct elem { int type; int value; }; /** * Represents a stack-frame within the execution context */ struct stack_frame { struct elem executing; int index; }; /** * Represents a function within ink, defined in ink, using the homoiconic representation of ink */ struct fn { char *name; struct elem *things; int size; }; struct context; /** * Represents natively defined words within ink */ struct native_fn { char *name; void (*value)(struct context *); }; struct parse_error { int is_set; const char* error_message; size_t offset; }; struct runtime_error { int is_set; const char* error_message; }; /** * Represents the narrow execution context of a single thread of execution within ink. */ struct ink_routine { int panic; struct parse_error parse_error; struct runtime_error runtime_error; struct elem *stack; int capacity; int top; struct stack_frame *function_stack; int function_stack_capacity; int function_stack_top; /** * This user data can be set to any value convenient for the user to track a state local to the routine that is executing */ void *routine_userdata; }; /** * Contains a list of element on which garbage collection is to not be performed */ struct ink_collection_list { struct elem* elements; int count; }; /** * Contains the data and metadata of an embedded element including the pointer to the element */ struct element_slab { void* data; int is_protected; int uses; int in_use; }; /** * Contains all the data for every element of any type and its garbage collection information. */ struct ink_type { const char* name; /**< The name of the type */ int element_size; /**< The size of individual elements of the type, 0 for int adjacent, negative for unmanaged size */ struct element_slab* elements; /**< The elements that are still live */ int elements_top; /**< The top of the elements list */ int elements_capacity; /**< The allocated capacity of the elements list */ void (*collect)(struct context*,void*); /**< The "destructor" of the object */ struct ink_collection_list (*gc)(struct context*,void*); /**< A function that returns an in-interpreter allocated list of elem references within the object */ }; /** * Represents a complete execution context for the ink interpreter */ struct context { int panic; void *(*inner_malloc)(struct context*,size_t); void *(*inner_realloc)(struct context*,void *, size_t); void (*inner_free)(struct context*,void *); void *(*malloc)(struct context*,size_t); void *(*realloc)(struct context*,void *, size_t); void (*free)(struct context*,void *); int (*putchar)(struct context*,int); struct ink_routine *routines; int routines_capacity; int routines_top; struct ink_type *types; int types_capacity; int types_top; /** * Contains the id of the routine that is currently being manipulated */ int routine_current; struct native_fn *native_words; int native_words_capacity; int native_words_top; struct fn *words; int words_capacity; int words_top; char **lex_reserved_words; int lex_reserved_words_capacity; int lex_reserved_words_top; unsigned int steps; unsigned int collections; /** * Can be set to any data that is convenient to the user to track and use within natively defined functions */ void *persistent_userdata; }; /** * Creates a routine to execute within the context * @param ctx The context in which to create the routine * @warning Does not set the `routine_current` of the context to the newly created routine * @return either a negative error value or the id of the created routine */ int ink_make_routine(struct context *ctx); /** * Cleans the targeted routine id data from the context * @param ctx The context to operate in * @param routine The id of the routine to destroy * @return 0 if nothing could or needed to be performed, 1 otherwise */ int ink_kill_routine(struct context *ctx, int routine); /** * Declares and defines a native function within the context * @param ctx The context tpo operate in * @param name The name to give to the newly declared word * @param value A pointer to a valid word-function * @return a negative value in case of error, 0 otherwise */ int ink_add_native(struct context *ctx, const char *name, void(*value)(struct context *)); /** * Pushes a value on the current routine's value stack * @param ctx The context to manipulate * @param value The value to push * @return 0 on success, a negative value on failure */ int ink_push(struct context *ctx, struct elem value); /** * Pushes a function on the execution stack of the current routine of the context * @param ctx The context to operate in * @param value the function that will be pushed. in the state one wants it to run from * @return 0 on success, a negative value on failure */ int ink_push_fn(struct context *ctx, struct stack_frame value); /** * Create a context to execute routines * @param malloc the memory allocation function, with a signature similar to the standard malloc * @param realloc the memory allocation function, with a signature similar to the standard realloc * @param free the memory allocation function, with a signature similar to the standard free * @param putchar a function to print to the output character by character * @return a pointer to a context allocated within the malloc function itself. */ struct context* ink_make_context(void*(*malloc)(struct context*, size_t), void*(*realloc)(struct context*, void*, size_t), void(*free)(struct context*, void*), int(*putchar)(struct context*, int)); /** * Create a context to execute routines in-place * @param location a pointer to where the context should be built, userdata can be set in advance * @param malloc the memory allocation function, with a signature similar to the standard malloc * @param realloc the memory allocation function, with a signature similar to the standard realloc * @param free the memory allocation function, with a signature similar to the standard free * @param putchar a function to print to the output character by character * @return a pointer to a context allocated within the malloc function itself. */ void ink_make_context_inplace(struct context* location, void*(*malloc)(struct context*, size_t), void*(*realloc)(struct context*, void*, size_t), void(*free)(struct context*, void*), int(*putchar)(struct context*, int)); #ifndef NOSTDLIB /** * Creates a context that includes the standard library of ink, as well as uses the C standard library to operate * @return a pointer to a context allocated with malloc and with predefined functions added */ struct context* ink_make_default_context(void); #endif /** * Steps the current routine by one execution step * @param pContext The context of the routine to advance * @return A negative value in case of error, 0 if execution is finished, a positive value if more steps are required to execute */ int ink_step(struct context *pContext); /** * Examine the context to see if any routines can execute * @param pContext the context * @return 0 if no coroutines are available, 1 otherwise */ int ink_can_run(struct context *pContext); /** * Step every routine that can be executed. * @param pContext The context, the `routine_current` value may be modified. * @return 0 */ int ink_step_everyone(struct context *pContext); /** * Compiles the code and starts a main routine to execute it * @param pContext The context to execute the code in * @param buffer The buffer that contains the source as a NULL terminated string * @return the routine that was created when compiling the code or -1 in case an error occured */ int ink_compile(struct context *pContext, const char *buffer); /** * Includes the standard library in the specified context * @param ctx The context * @return 0 */ int ink_std_library(struct context *ctx); /** * Removes the top element of the function stack of the current routine of the specified context * @param ctx the context */ void ink_pop_fn(struct context *ctx); /** * Removes the top element of the stack of the current routine of the specified context * @param ctx the context */ void ink_pop(struct context *ctx); /** * Declares a new type that can be stored within the interpreter * @param ctx The context in which to add the file * @param type_name The name of the type we want to add * @param size The size in bytes of the type to add, size of 0 mean no size, in which case the type is adjacent to C int, negative size means that the memory is not managed by the interpreter. * @param collect A "destructor" function for the data * @param gc A function that returns a list (allocated with the `inner_malloc`) of all the elements this element holds references to * @return if positive, a new type id, if negative an error value * @internal user defined type ids minimal value is 15, we keep the first 16 types as reserved, just like negative type ids */ int ink_new_type( struct context* ctx, const char* type_name, int size, void (*collect)(struct context*,void*), struct ink_collection_list (*gc)(struct context*,void*) ); /** * * @param ctx The context of the interpreter * @param ref The in-interpreter reference * @return A pointer to the created value or NULL if it can't be found or has a size of 0 */ void* ink_get_value(struct context* ctx, struct elem ref); /** * Builds a native type from the provided memory by copying it using memcpy * @param ctx The context in which we operate * @param type_id The type_id to use * @param ptr The pointer from which to copy * @return The in-interpreter reference of the newly created element */ struct elem ink_make_native(struct context* ctx, int type_id, void* ptr); /** * Builds a transparent type from the provided pointer * @param ctx The context in which we operate * @param type_id The type_id to use * @param ptr The pointer * @return The in-interpreter reference of the newly created element */ struct elem ink_make_transparent(struct context* ctx, int type_id, void* ptr); /** * Launch the mark and sweep garbage collection * @param ctx The context to clean */ void ink_gc(struct context* ctx); /** * Cleans up coroutine stacks before reuse * @param ctx */ void ink_clean_routines(struct context* ctx); /** * Obtains the type id from the declared name of the type * @param ctx The context where we want to detect the type * @param name The name of the type * @return the type id if it exists, -1 otherwise */ int get_type_by_name(struct context* ctx, const char* name); /** * The internal representation of arrays in ink */ struct ink_array { int top; int capacity; struct elem* elements; uint16_t flags; }; /** * Pushes a value in an array. in case of failure, panics the routine * * @param ctx the working context * @param currentRoutine the routine that will panic * @param ary the array to be incremented * @param value the value to push */ void array_push(struct context* ctx, struct ink_routine* currentRoutine, struct ink_array* ary, struct elem value); #ifdef __cplusplus }; #endif