diff --git a/apps/bim.c b/apps/bim.c index daf535e5..be403974 100644 --- a/apps/bim.c +++ b/apps/bim.c @@ -16,15 +16,10 @@ */ #define ENABLE_THREADING #include "bim.h" -#ifdef __toaru__ -#include -#include "vm.h" -#include "debug.h" -#else #include #include #include -#endif +#include global_config_t global_config = { /* State */ @@ -80,6 +75,7 @@ global_config_t global_config = { .smart_complete = 0, .has_terminal = 0, .search_wraps = 1, + .had_error = 0, /* Integer config values */ .cursor_padding = 4, .split_percent = 50, @@ -3183,6 +3179,7 @@ void render_error(char * message, ...) { /* Draw the message */ printf("%s", buf); + global_config.had_error = 1; } else { printf("bim: error during startup: %s\n", buf); } @@ -9938,7 +9935,7 @@ struct CommandDef { int process_krk_command(const char * cmd, KrkValue * outVal) { place_cursor(global_config.term_width, global_config.term_height); - fprintf(stdout,"\n"); + fprintf(stdout, "\n"); /* By resetting, we're at 0 frames. */ krk_resetStack(); /* Push something so we're not at the bottom of the stack when an @@ -9998,7 +9995,8 @@ int process_krk_command(const char * cmd, KrkValue * outVal) { } } global_config.break_from_selection = 1; - redraw_all(); + if (!global_config.had_error) redraw_all(); + global_config.had_error = 0; return retval; } @@ -10184,11 +10182,11 @@ static KrkValue krk_bim_register_syntax(int argc, KrkValue argv[], int hasKw) { if (argc < 1 || !IS_CLASS(argv[0]) || !checkClass(AS_CLASS(argv[0]), syntaxStateClass)) return krk_runtimeError(vm.exceptions->typeError, "Can not register '%s' as a syntax highlighter, expected subclass of SyntaxState.", krk_typeName(argv[0])); - KrkValue name = NONE_VAL(), extensions = NONE_VAL(), spaces = BOOLEAN_VAL(0), calculate = NONE_VAL(); - krk_tableGet(&AS_CLASS(argv[0])->fields, OBJECT_VAL(S("name")), &name); - krk_tableGet(&AS_CLASS(argv[0])->fields, OBJECT_VAL(S("extensions")), &extensions); - krk_tableGet(&AS_CLASS(argv[0])->fields, OBJECT_VAL(S("spaces")), &spaces); - krk_tableGet(&AS_CLASS(argv[0])->methods, OBJECT_VAL(S("calculate")), &calculate); + KrkValue name = krk_valueGetAttribute_default(argv[0], "name", NONE_VAL()); + KrkValue extensions = krk_valueGetAttribute_default(argv[0], "extensions", NONE_VAL()); + KrkValue spaces = krk_valueGetAttribute_default(argv[0], "spaces", BOOLEAN_VAL(0)); + KrkValue calculate = krk_valueGetAttribute_default(argv[0], "calculate", NONE_VAL()); + if (!IS_STRING(name)) return krk_runtimeError(vm.exceptions->typeError, "%s.name must be str", AS_CLASS(argv[0])->name->chars); if (!IS_TUPLE(extensions)) @@ -10527,7 +10525,7 @@ void import_directory(char * dirName) { if (!dirp) { /* Try one last fallback */ if (dirpath) free(dirpath); - dirpath = strdup("/usr/share/bim"); + dirpath = strdup("/usr/local/share/bim"); sprintf(file, "%s/%s", dirpath, dirName); extra = "/"; dirp = opendir(file); @@ -10620,6 +10618,37 @@ BIM_COMMAND(reload,"reload","Reloads all the Kuroko stuff.") { return 0; } +static KrkValue krk_bim_getDocumentText(int argc, KrkValue argv[], int hasKw) { + struct StringBuilder sb = {0}; + + int i, j; + for (i = 0; i < env->line_count; ++i) { + line_t * line = env->lines[i]; + for (j = 0; j < line->actual; j++) { + char_t c = line->text[j]; + if (c.codepoint == 0) { + pushStringBuilder(&sb, 0); + } else { + char tmp[8] = {0}; + int len = to_eight(c.codepoint, tmp); + pushStringBuilderStr(&sb, tmp, len); + } + } + pushStringBuilder(&sb, '\n'); + } + + return finishStringBuilder(&sb); +} + +static KrkValue krk_bim_renderError(int argc, KrkValue argv[], int hasKw) { + if (argc != 1 || !IS_STRING(argv[0])) return TYPE_ERROR(str,argv[0]); + if (AS_STRING(argv[0])->length == 0) + redraw_commandline(); + else + render_error(AS_CSTRING(argv[0])); + return NONE_VAL(); +} + /** * Run global initialization tasks */ @@ -10671,6 +10700,9 @@ void initialize(void) { krk_bim_syntax_dict = krk_dict_of(0,NULL,0); krk_attachNamedValue(&bimModule->fields, "highlighters", krk_bim_syntax_dict); + krk_defineNative(&bimModule->fields, "getDocumentText", krk_bim_getDocumentText); + krk_defineNative(&bimModule->fields, "renderError", krk_bim_renderError); + makeClass(bimModule, &ActionDef, "Action", vm.baseClasses->objectClass); ActionDef->allocSize = sizeof(struct ActionDef); krk_defineNative(&ActionDef->methods, ".__call__", bim_krk_action_call); @@ -10718,21 +10750,21 @@ void initialize(void) { krk_defineNative(&syntaxStateClass->methods, ".matchAndPaint", bim_krk_state_matchAndPaint); krk_defineNative(&syntaxStateClass->methods, ".commentBuzzwords", bim_krk_state_commentBuzzwords); krk_defineNative(&syntaxStateClass->methods, ".rewind", bim_krk_state_rewind); - krk_defineNative(&syntaxStateClass->methods, ".__get__", bim_krk_state_get); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_NONE", INTEGER_VAL(FLAG_NONE)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_KEYWORD", INTEGER_VAL(FLAG_KEYWORD)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_STRING", INTEGER_VAL(FLAG_STRING)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_COMMENT", INTEGER_VAL(FLAG_COMMENT)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_TYPE", INTEGER_VAL(FLAG_TYPE)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_PRAGMA", INTEGER_VAL(FLAG_PRAGMA)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_NUMERAL", INTEGER_VAL(FLAG_NUMERAL)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_ERROR", INTEGER_VAL(FLAG_ERROR)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_DIFFPLUS", INTEGER_VAL(FLAG_DIFFPLUS)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_DIFFMINUS", INTEGER_VAL(FLAG_DIFFMINUS)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_NOTICE", INTEGER_VAL(FLAG_NOTICE)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_BOLD", INTEGER_VAL(FLAG_BOLD)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_LINK", INTEGER_VAL(FLAG_LINK)); - krk_attachNamedValue(&syntaxStateClass->fields, "FLAG_ESCAPE", INTEGER_VAL(FLAG_ESCAPE)); + krk_defineNative(&syntaxStateClass->methods, ".__getitem__", bim_krk_state_get); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_NONE", INTEGER_VAL(FLAG_NONE)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_KEYWORD", INTEGER_VAL(FLAG_KEYWORD)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_STRING", INTEGER_VAL(FLAG_STRING)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_COMMENT", INTEGER_VAL(FLAG_COMMENT)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_TYPE", INTEGER_VAL(FLAG_TYPE)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_PRAGMA", INTEGER_VAL(FLAG_PRAGMA)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_NUMERAL", INTEGER_VAL(FLAG_NUMERAL)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_ERROR", INTEGER_VAL(FLAG_ERROR)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_DIFFPLUS", INTEGER_VAL(FLAG_DIFFPLUS)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_DIFFMINUS", INTEGER_VAL(FLAG_DIFFMINUS)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_NOTICE", INTEGER_VAL(FLAG_NOTICE)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_BOLD", INTEGER_VAL(FLAG_BOLD)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_LINK", INTEGER_VAL(FLAG_LINK)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_ESCAPE", INTEGER_VAL(FLAG_ESCAPE)); krk_finalizeClass(syntaxStateClass); diff --git a/apps/bim.h b/apps/bim.h index 13a5c942..2b8a42b5 100644 --- a/apps/bim.h +++ b/apps/bim.h @@ -22,11 +22,7 @@ #include #include #include -#ifdef __toaru__ -#include "vm.h" -#else #include -#endif #ifdef __DATE__ # define BIM_BUILD_DATE " built " __DATE__ " at " __TIME__ @@ -213,6 +209,7 @@ typedef struct { unsigned int smart_complete:1; unsigned int has_terminal:1; unsigned int search_wraps:1; + unsigned int had_error:1; int cursor_padding; int split_percent; diff --git a/apps/kuroko.c b/apps/kuroko.c index 3fe97886..8508b621 100644 --- a/apps/kuroko.c +++ b/apps/kuroko.c @@ -29,23 +29,120 @@ #include "memory.h" #include "scanner.h" #include "compiler.h" +#include "util.h" #define PROMPT_MAIN ">>> " #define PROMPT_BLOCK " > " static int enableRline = 1; static int exitRepl = 0; -static KrkValue exitFunc(int argc, KrkValue argv[], int hasKw) { +static int pasteEnabled = 0; + +KRK_FUNC(exit,{ + FUNCTION_TAKES_NONE(); exitRepl = 1; +}) + +KRK_FUNC(paste,{ + FUNCTION_TAKES_AT_MOST(1); + if (argc) { + CHECK_ARG(0,bool,int,enabled); + pasteEnabled = enabled; + } else { + pasteEnabled = !pasteEnabled; + } + fprintf(stderr, "Pasting is %s.\n", pasteEnabled ? "enabled" : "disabled"); +}) + +static int doRead(char * buf, size_t bufSize) { +#ifndef NO_RLINE + if (enableRline) + return rline(buf, bufSize); + else +#endif + return read(STDIN_FILENO, buf, bufSize); +} + +static KrkValue readLine(char * prompt, int promptWidth, char * syntaxHighlighter) { + struct StringBuilder sb = {0}; + +#ifndef NO_RLINE + if (enableRline) { + rline_exit_string = ""; + rline_exp_set_prompts(prompt, "", promptWidth, 0); + rline_exp_set_syntax(syntaxHighlighter); + rline_exp_set_tab_complete_func(NULL); + } else +#endif + { + fprintf(stdout, "%s", prompt); + fflush(stdout); + } + + /* Read a line of input using a method that we can guarantee will be + * interrupted by signal delivery. */ + while (1) { + char buf[4096]; + ssize_t bytesRead = doRead(buf, 4096); + if (krk_currentThread.flags & KRK_THREAD_SIGNALLED) goto _exit; + if (bytesRead < 0) { + krk_runtimeError(vm.exceptions->ioError, "%s", strerror(errno)); + goto _exit; + } else if (bytesRead == 0 && !sb.length) { + krk_runtimeError(vm.exceptions->baseException, "EOF"); + goto _exit; + } else { + pushStringBuilderStr(&sb, buf, bytesRead); + } + /* Was there a linefeed? Then we can exit. */ + if (sb.length && sb.bytes[sb.length-1] == '\n') { + sb.length--; + break; + } + } + + return finishStringBuilder(&sb); + +_exit: + discardStringBuilder(&sb); return NONE_VAL(); } -static int pasteEnabled = 0; -static KrkValue paste(int argc, KrkValue argv[], int hasKw) { - pasteEnabled = !pasteEnabled; - fprintf(stderr, "Pasting is %s.\n", pasteEnabled ? "enabled" : "disabled"); - return NONE_VAL(); -} +/** + * @brief Read a line of input. + * + * In an interactive session, presents the configured prompt without + * a trailing linefeed. + */ +KRK_FUNC(input,{ + FUNCTION_TAKES_AT_MOST(1); + + char * prompt = ""; + int promptLength = 0; + char * syntaxHighlighter = NULL; + + if (argc) { + CHECK_ARG(0,str,KrkString*,_prompt); + prompt = _prompt->chars; + promptLength = _prompt->codesLength; + } + + if (hasKw) { + KrkValue promptwidth; + if (krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("promptwidth")), &promptwidth)) { + if (!IS_INTEGER(promptwidth)) return TYPE_ERROR(int,promptwidth); + promptLength = AS_INTEGER(promptwidth); + } + + KrkValue syntax; + if (krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("syntax")), &syntax)) { + if (!IS_STRING(syntax)) return TYPE_ERROR(str,syntax); + syntaxHighlighter = AS_CSTRING(syntax); + } + } + + return readLine(prompt, promptLength, syntaxHighlighter); +}) #ifndef NO_RLINE /** @@ -358,7 +455,7 @@ static int debuggerHook(KrkCallFrame * frame) { } else { size_t frameCount = krk_currentThread.frameCount; /* Compile statement */ - KrkFunction * expression = krk_compile(arg,""); + KrkCodeObject * expression = krk_compile(arg,""); if (expression) { /* Make sure stepping is disabled first. */ krk_debug_disableSingleStep(); @@ -429,7 +526,7 @@ static int debuggerHook(KrkCallFrame * frame) { } if (!strcmp(arg,"breakpoints")) { - KrkFunction * codeObject = NULL; + KrkCodeObject * codeObject = NULL; size_t offset = 0; int flags = 0; int enabled = 0; @@ -521,11 +618,13 @@ _dbgQuit: } static void handleSigint(int sigNum) { - if (krk_currentThread.frameCount) { - krk_currentThread.flags |= KRK_THREAD_SIGNALLED; - } + /* Don't set the signal flag if the VM is not running */ + if (!krk_currentThread.frameCount) return; + krk_currentThread.flags |= KRK_THREAD_SIGNALLED; +} - signal(sigNum, handleSigint); +static void bindSignalHandlers(void) { + signal(SIGINT, handleSigint); } static void findInterpreter(char * argv[]) { @@ -566,6 +665,7 @@ static int runString(char * argv[], int flags, char * string) { findInterpreter(argv); krk_initVM(flags); krk_startModule("__main__"); + krk_attachNamedValue(&krk_currentThread.module->fields,"__doc__", NONE_VAL()); krk_interpret(string, ""); krk_freeVM(); return 0; @@ -595,7 +695,7 @@ static int compileFile(char * argv[], int flags, char * fileName) { krk_startModule("__main__"); /* Call the compiler directly. */ - KrkFunction * func = krk_compile(buf, fileName); + KrkCodeObject * func = krk_compile(buf, fileName); /* See if there was an exception. */ if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { @@ -715,7 +815,7 @@ _finishArgs: for (int arg = optind; arg < argc + (optind == argc); ++arg) krk_pop(); /* Bind interrupt signal */ - signal(SIGINT, handleSigint); + bindSignalHandlers(); #ifdef BUNDLE_LIBS /* Add any other modules you want to include that are normally built as shared objects. */ @@ -724,6 +824,24 @@ _finishArgs: KrkValue result = INTEGER_VAL(0); + /** + * Add general builtins that aren't part of the core VM. + * This is where we provide @c input in particular. + */ + KRK_DOC(BIND_FUNC(vm.builtins,input), "@brief Read a line of input.\n" + "@arguments [prompt], promptwidth=None, syntax=None\n\n" + "Read a line of input from @c stdin. If the @c rline library is available, " + "it will be used to gather input. Input reading stops on end-of file or when " + "a read ends with a line feed, which will be removed from the returned string. " + "If a prompt is provided, it will be printed without a line feed before requesting " + "input. If @c rline is available, the prompt will be passed to the library as the " + "left-hand prompt string. If not provided, @p promptwidth will default to the width " + "of @p prompt in codepoints; if you are providing a prompt with escape characters or " + "characters with multi-column East-Asian Character Width be sure to pass a value " + "for @p promptwidth that reflects the display width of your prompt. " + "If provided, @p syntax specifies the name of an @c rline syntax module to " + "provide color highlighting of the input line."); + if (moduleAsMain) { krk_push(OBJECT_VAL(krk_copyString("__main__",8))); int out = !krk_importModule( @@ -736,8 +854,14 @@ _finishArgs: return out; } else if (optind == argc) { /* Add builtins for the repl, but hide them from the globals() list. */ - krk_defineNative(&vm.builtins->fields, "exit", exitFunc); - krk_defineNative(&vm.builtins->fields, "paste", paste); + KRK_DOC(BIND_FUNC(vm.builtins,exit), "@brief Exit the interactive repl.\n\n" + "Only available from the interactive interpreter; exits the repl."); + KRK_DOC(BIND_FUNC(vm.builtins,paste), "@brief Toggle paste mode.\n" + "@arguments enabled=None\n\n" + "Toggles paste-safe mode, disabling automatic indentation in the repl. " + "If @p enabled is specified, the mode can be directly specified, otherwise " + "it will be set to the opposite of the current mode. The new mode will be " + "printed to stderr."); /* The repl runs in the context of a top-level module so each input * line can share a globals state with the others. */ diff --git a/base/usr/include/kuroko/chunk.h b/base/usr/include/kuroko/chunk.h new file mode 100644 index 00000000..0d10c17b --- /dev/null +++ b/base/usr/include/kuroko/chunk.h @@ -0,0 +1,219 @@ +#pragma once +/** + * @file chunk.h + * @brief Structures and enums for bytecode chunks. + */ +#include "kuroko.h" +#include "value.h" + +/** + * @brief Instruction opcode values + * + * The instruction opcode table is divided in four parts. The high two bits of each + * opcode encodes the number of operands to pull from the codeobject and thus the + * size (generally) of the instruction (note that OP_CLOSURE(_LONG) has additional + * arguments depending on the function it points to). + * + * 0-operand opcodes are "simple" instructions that generally only deal with stack + * values and require no additional arguments. + * + * 1- and 3- operand opcodes are paired as 'short' and 'long'. While the VM does not + * currently depend on these instructions having the same values in the lower 6 bits, + * it is recommended that this property remain true. + * + * 2-operand opcodes are generally jump instructions. + */ +typedef enum { + OP_ADD = 1, + OP_BITAND, + OP_BITNEGATE, + OP_BITOR, + OP_BITXOR, + OP_CLEANUP_WITH, + OP_CLOSE_UPVALUE, + OP_DIVIDE, + OP_DOCSTRING, + OP_EQUAL, + OP_FALSE, + OP_FINALIZE, + OP_GREATER, + OP_INHERIT, + OP_INVOKE_DELETE, + OP_INVOKE_DELSLICE, + OP_INVOKE_GETSLICE, + OP_INVOKE_GETTER, + OP_INVOKE_SETSLICE, + OP_INVOKE_SETTER, + OP_IS, + OP_LESS, + OP_MODULO, + OP_MULTIPLY, + OP_NEGATE, + OP_NONE, + OP_NOT, + OP_POP, + OP_POW, + OP_RAISE, + OP_RETURN, + OP_SHIFTLEFT, + OP_SHIFTRIGHT, + OP_SUBTRACT, + OP_SWAP, + OP_TRUE, + OP_FILTER_EXCEPT, + OP_INVOKE_ITER, + OP_INVOKE_CONTAINS, + OP_BREAKPOINT, /* NEVER output this instruction in the compiler or bad things can happen */ + OP_YIELD, + OP_ANNOTATE, + /* current highest: 44 */ + + OP_CALL = 64, + OP_CLASS, + OP_CLOSURE, + OP_CONSTANT, + OP_DEFINE_GLOBAL, + OP_DEL_GLOBAL, + OP_DEL_PROPERTY, + OP_DUP, + OP_EXPAND_ARGS, + OP_GET_GLOBAL, + OP_GET_LOCAL, + OP_GET_PROPERTY, + OP_GET_SUPER, + OP_GET_UPVALUE, + OP_IMPORT, + OP_IMPORT_FROM, + OP_INC, + OP_KWARGS, + OP_CLASS_PROPERTY, + OP_SET_GLOBAL, + OP_SET_LOCAL, + OP_SET_PROPERTY, + OP_SET_UPVALUE, + OP_TUPLE, + OP_UNPACK, + OP_LIST_APPEND, + OP_DICT_SET, + OP_SET_ADD, + OP_MAKE_LIST, + OP_MAKE_DICT, + OP_MAKE_SET, + OP_REVERSE, + + OP_JUMP_IF_FALSE = 128, + OP_JUMP_IF_TRUE, + OP_JUMP, + OP_LOOP, + OP_PUSH_TRY, + OP_PUSH_WITH, + + OP_CALL_LONG = 192, + OP_CLASS_LONG, + OP_CLOSURE_LONG, + OP_CONSTANT_LONG, + OP_DEFINE_GLOBAL_LONG, + OP_DEL_GLOBAL_LONG, + OP_DEL_PROPERTY_LONG, + OP_DUP_LONG, + OP_EXPAND_ARGS_LONG, + OP_GET_GLOBAL_LONG, + OP_GET_LOCAL_LONG, + OP_GET_PROPERTY_LONG, + OP_GET_SUPER_LONG, + OP_GET_UPVALUE_LONG, + OP_IMPORT_LONG, + OP_IMPORT_FROM_LONG, + OP_INC_LONG, + OP_KWARGS_LONG, + OP_CLASS_PROPERTY_LONG, + OP_SET_GLOBAL_LONG, + OP_SET_LOCAL_LONG, + OP_SET_PROPERTY_LONG, + OP_SET_UPVALUE_LONG, + OP_TUPLE_LONG, + OP_UNPACK_LONG, + OP_LIST_APPEND_LONG, + OP_DICT_SET_LONG, + OP_SET_ADD_LONG, + OP_MAKE_LIST_LONG, + OP_MAKE_DICT_LONG, + OP_MAKE_SET_LONG, + OP_REVERSE_LONG, +} KrkOpCode; + +/** + * @brief Map entry of instruction offsets to line numbers. + * + * Each code object contains an array of line mappings, indicating + * the start offset of each line. Since a line typically maps to + * multiple opcodes, and spans of many lines may map to no opcodes + * in the case of blank lines or docstrings, this array is stored + * as a sequence of pairs rather than a simple + * array of one or the other. + */ +typedef struct { + size_t startOffset; + size_t line; +} KrkLineMap; + +/** + * @brief Opcode chunk of a code object. + * + * Opcode chunks are internal to code objects and I'm not really + * sure why we're still separating them from the KrkCodeObjects. + * + * Stores four flexible arrays using three different formats: + * - Code, representing opcodes and operands. + * - Lines, representing offset-to-line mappings. + * - Filename, the string name of the source file. + * - Constants, an array of values referenced by the code object. + */ +typedef struct { + size_t count; + size_t capacity; + uint8_t * code; + + size_t linesCount; + size_t linesCapacity; + KrkLineMap * lines; + + KrkString * filename; + KrkValueArray constants; +} KrkChunk; + +/** + * @brief Initialize an opcode chunk. + * @memberof KrkChunk + */ +extern void krk_initChunk(KrkChunk * chunk); + +/** + * @memberof KrkChunk + * @brief Append a byte to an opcode chunk. + */ +extern void krk_writeChunk(KrkChunk * chunk, uint8_t byte, size_t line); + +/** + * @brief Release the resources allocated to an opcode chunk. + * @memberof KrkChunk + */ +extern void krk_freeChunk(KrkChunk * chunk); + +/** + * @brief Add a new constant value to an opcode chunk. + * @memberof KrkChunk + */ +extern size_t krk_addConstant(KrkChunk * chunk, KrkValue value); + +/** + * @brief Write an OP_CONSTANT(_LONG) instruction. + * @memberof KrkChunk + */ +extern void krk_emitConstant(KrkChunk * chunk, size_t ind, size_t line); + +/** + * @brief Add a new constant and write an instruction for it. + * @memberof KrkChunk + */ +extern size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line); diff --git a/base/usr/include/kuroko/compiler.h b/base/usr/include/kuroko/compiler.h new file mode 100644 index 00000000..82baaec2 --- /dev/null +++ b/base/usr/include/kuroko/compiler.h @@ -0,0 +1,22 @@ +#pragma once +/** + * @file compiler.h + * @brief Exported methods for the source compiler. + */ +#include "object.h" + +/** + * @brief Compile a string to a code object. + * + * Compiles the source string 'src' into a code object. + * + * @param src Source code string to compile. + * @param fileName Path name of the source file or a representative string like "" + * @return The code object resulting from the compilation, or NULL if compilation failed. + */ +extern KrkCodeObject * krk_compile(const char * src, char * fileName); + +/** + * @brief Mark objects owned by the compiler as in use. + */ +extern void krk_markCompilerRoots(void); diff --git a/base/usr/include/kuroko/debug.h b/base/usr/include/kuroko/debug.h new file mode 100644 index 00000000..9c6e1e8d --- /dev/null +++ b/base/usr/include/kuroko/debug.h @@ -0,0 +1,248 @@ +#pragma once +/** + * @file debug.h + * @brief Functions for debugging bytecode execution. + * + * This header provides functions for disassembly bytecode to + * readable instruction traces, mapping bytecode offsets to + * source code lines, and handling breakpoint instructions. + * + * Several of these functions are also exported to user code + * in the @ref mod_dis module. + * + * Note that these functions are not related to manage code + * exception handling, but instead inteaded to provide a low + * level interface to the VM's execution process and allow + * for the implementation of debuggers and debugger extensions. + */ +#include +#include "vm.h" +#include "chunk.h" +#include "object.h" + +/** + * @brief Print a disassembly of 'func' to the stream 'f'. + * + * Generates and prints a bytecode disassembly of the code object 'func', + * writing it to the requested stream. + * + * @param f Stream to write to. + * @param func Code object to disassemble. + * @param name Function name to display in disassembly output. + */ +extern void krk_disassembleCodeObject(FILE * f, KrkCodeObject * func, const char * name); + +/** + * @brief Print a disassembly of a single opcode instruction. + * + * Generates and prints a bytecode disassembly for one instruction from + * the code object 'func' at byte offset 'offset', printing the result to + * the requested stream and returning the size of the instruction. + * + * @param f Stream to write to. + * @param func Code object to disassemble. + * @param offset Byte offset of the instruction to disassemble. + * @return The size of the instruction in bytes. + */ +extern size_t krk_disassembleInstruction(FILE * f, KrkCodeObject * func, size_t offset); + +/** + * @brief Obtain the line number for a byte offset into a bytecode chunk. + * + * Scans the line mapping table for the given chunk to find the + * correct line number from the original source file for the instruction + * at byte index 'offset'. + * + * @param chunk Bytecode chunk containing the instruction. + * @param offset Byte offset of the instruction to locate. + * @return Line number, 1-indexed. + */ +extern size_t krk_lineNumber(KrkChunk * chunk, size_t offset); + +/* Internal stuff */ +extern void _createAndBind_disMod(void); + +/** + * @brief Called by the VM when a breakpoint is encountered. + * + * Internal method, should not generally be called. + */ +extern int krk_debugBreakpointHandler(void); + +/** + * @brief Called by the VM on single step. + * + * Handles calling the registered debugger hook, if one has + * been set, and performing the requested response action. + * Also takes care of re-enabling REPEAT breakpoints. + * + * Internal method, should not generally be called. + */ +extern int krk_debuggerHook(KrkCallFrame * frame); + +/** + * @brief Function pointer for a debugger hook. + * @ref krk_debug_registerCallback() + */ +typedef int (*KrkDebugCallback)(KrkCallFrame *frame); + +/** + * @brief Register a debugger callback. + * + * The registered function @p hook will be called when an + * OP_BREAKPOINT instruction is encountered. The debugger + * is provided a pointer to the active frame and can use + * functions from the krk_debug_* suite to examine the + * thread state, execute more code, and resume execution. + * + * The debugger hook will be called from the thread that + * encountered the breakpoint, and should return one of + * the KRK_DEBUGGER_ status values to inform the VM of + * what action to take. + * + * @param hook The hook function to attach. + * @return 0 if the hook was registered; 1 if a hook was + * already registered, in which case the new hook + * has not been registered. + */ +extern int krk_debug_registerCallback(KrkDebugCallback hook); + +/** + * @brief Add a breakpoint to the given line of a file. + * + * The interpreter will scan all code objects and attach + * a breakpoint instruction to the first such object that + * has a match filename and contains an instruction with + * a matching line mapping. Breakpoints can only be added + * to code which has already been compiled. + * + * @param filename KrkString * representation of a source + * code filename to look for. + * @param line The line to set the breakpoint at. + * @param flags Allows configuring the disposition of the breakpoint. + * @return A breakpoint identifier handle on success, or -1 on failure. + */ +extern int krk_debug_addBreakpointFileLine(KrkString * filename, size_t line, int flags); + +/** + * @brief Add a breakpoint to the given code object. + * + * A new breakpoint is added to the breakpoint table and + * the code object's bytecode is overwritten to insert + * the OP_BREAKPOINT instruction. + * + * Callers must ensure that @p offset points to the opcode + * portion of an instruction, and not an operand, or the + * attempt to add a breakpoint can corrupt the bytecode. + * + * @param codeObject KrkCodeObject* for the code object to break on. + * @param offset Bytecode offset to insert the breakpoint at. + * @param flags Allows configuring the disposition of the breakpoint. + * @return A breakpoint identifier handle on success, or -1 on failure. + */ +extern int krk_debug_addBreakpointCodeOffset(KrkCodeObject * codeObject, size_t offset, int flags); + +/** + * @brief Remove a breakpoint from the breakpoint table. + * + * Removes the breakpoint @p breakpointId from the breakpoint + * table, restoring the bytecode instruction for it if it + * was enabled. + * + * @param breakpointId The breakpoint to remove. + * @return 0 on success, 1 if the breakpoint identifier is invalid. + */ +extern int krk_debug_removeBreakpoint(int breakpointId); + +/** + * @brief Enable a breakpoint. + * + * Writes the OP_BREAKPOINT instruction into the function + * bytecode chunk for the given breakpoint. + * + * @param breakpointId The breakpoint to enable. + * @return 0 on success, 1 if the breakpoint identifier is invalid. + */ +extern int krk_debug_enableBreakpoint(int breakpointId); + +/** + * @brief Disable a breakpoint. + * + * Restores the bytecode instructions for the given breakpoint + * if it is currently enabled. + * + * No error is returned if the breakpoint was already disabled. + * + * @param breakpointId The breakpoint to disable. + * @return 0 on success, 1 if the breakpoint identifier is invalid. + */ +extern int krk_debug_disableBreakpoint(int breakpointId); + +/** + * @brief Enable single stepping in the current thread. + */ +extern void krk_debug_enableSingleStep(void); + +/** + * @brief Disable single stepping in the current thread. + */ +extern void krk_debug_disableSingleStep(void); + +/** + * @brief Safely dump a traceback to stderr. + * + * Wraps @ref krk_dumpTraceback() so it can be safely + * called from a debugger. + */ +extern void krk_debug_dumpTraceback(void); + +/** + * @brief Retreive information on a breakpoint. + * + * Can be called by debuggers to examine the details of a breakpoint by its handle. + * Information is returned through the pointers provided as parameters. + * + * @param breakIndex Breakpoint handle to examine. + * @param funcOut (Out) The code object this breakpoint is in. + * @param offsetOut (Out) The bytecode offset within the code object where the breakpoint is located. + * @param flagsOut (Out) The configuration flags for the breakpoint. + * @param enabledOut (Out) Whether the breakpoint is enabled or not. + * @return 0 on success, -1 on out of range, -2 if the selected slot was removed. + */ +extern int krk_debug_examineBreakpoint(int breakIndex, KrkCodeObject ** funcOut, size_t * offsetOut, int * flagsOut, int *enabledOut); + +/** + * @brief Print the elements on the stack. + * + * Prints the elements on the stack for the current thread to @p file, + * highlighting @p frame as the activate call point and indicating + * where its arguments start. + */ +extern void krk_debug_dumpStack(FILE * f, KrkCallFrame * frame); + +/** + * @def KRK_BREAKPOINT_NORMAL + * + * This breakpoint should fire once and then remain in the table + * to be re-enabled later. + * + * @def KRK_BREAKPOINT_ONCE + * + * This breakpoint should fire once and then be removed from the + * breakpoint table. + * + * @def KRK_BREAKPOINT_REPEAT + * + * After this breakpoint has fired and execution resumes, the + * interpreter should re-enable it to fire again until it is + * removed by a call to @ref krk_debug_removeBreakpoint() + */ +#define KRK_BREAKPOINT_NORMAL 0 +#define KRK_BREAKPOINT_ONCE 1 +#define KRK_BREAKPOINT_REPEAT 2 + +#define KRK_DEBUGGER_CONTINUE 0 +#define KRK_DEBUGGER_ABORT 1 +#define KRK_DEBUGGER_STEP 2 +#define KRK_DEBUGGER_RAISE 3 +#define KRK_DEBUGGER_QUIT 4 diff --git a/base/usr/include/kuroko/kuroko.h b/base/usr/include/kuroko/kuroko.h new file mode 100644 index 00000000..0ed6dd50 --- /dev/null +++ b/base/usr/include/kuroko/kuroko.h @@ -0,0 +1,37 @@ +#pragma once +/** + * @file kuroko.h + * @brief Top-level header with configuration macros. + */ +#include +#include +#include + +#if defined(__EMSCRIPTEN__) +typedef long long krk_integer_type; +# define PRIkrk_int "%lld" +# define PRIkrk_hex "%llx" +# define parseStrInt strtoll +#elif defined(_WIN32) +typedef long long krk_integer_type; +# define PRIkrk_int "%I64d" +# define PRIkrk_hex "%I64x" +# define parseStrInt strtoll +# define ENABLE_THREADING +# else +typedef long krk_integer_type; +# define PRIkrk_int "%ld" +# define PRIkrk_hex "%lx" +# define parseStrInt strtol +# define ENABLE_THREADING +#endif + +#define ENABLE_THREADING + +#ifdef DEBUG +#define ENABLE_DISASSEMBLY +#define ENABLE_TRACING +#define ENABLE_SCAN_TRACING +#define ENABLE_STRESS_GC +#endif + diff --git a/base/usr/include/kuroko/memory.h b/base/usr/include/kuroko/memory.h new file mode 100644 index 00000000..0efecce7 --- /dev/null +++ b/base/usr/include/kuroko/memory.h @@ -0,0 +1,80 @@ +#pragma once +/** + * @file memory.h + * @brief Functions for dealing with garbage collection and memory allocation. + */ +#include "kuroko.h" +#include "object.h" +#include "table.h" + +#define GROW_CAPACITY(c) ((c) < 8 ? 8 : (c) * 2) +#define GROW_ARRAY(t,p,o,n) (t*)krk_reallocate(p,sizeof(t)*o,sizeof(t)*n) + +#define FREE_ARRAY(t,a,c) krk_reallocate(a,sizeof(t) * c, 0) +#define FREE(t,p) krk_reallocate(p,sizeof(t),0) + +#define ALLOCATE(type, count) (type*)krk_reallocate(NULL,0,sizeof(type)*(count)) + +/** + * @brief Resize an allocated heap object. + * + * Allocates or reallocates the heap object 'ptr', tracking changes + * in sizes from 'old' to 'new'. If 'ptr' is NULL, 'old' should be 0, + * and a new pointer will be allocated of size 'new'. + * + * @param ptr Heap object to resize. + * @param old Current size of the object. + * @param new New size of the object. + * @return New pointer for heap object. + */ +extern void * krk_reallocate(void * ptr, size_t old, size_t new); + +/** + * @brief Release all objects. + * + * Generally called automatically by krk_freeVM(); releases all of + * the GC-tracked heap objects. + */ +extern void krk_freeObjects(void); + +/** + * @brief Run a cycle of the garbage collector. + * + * Runs one scan-sweep cycle of the garbage collector, potentially + * freeing unused resources and advancing potentially-unused + * resources to the next stage of removal. + * + * @return The number of bytes released by this collection cycle. + */ +extern size_t krk_collectGarbage(void); + +/** + * @brief During a GC scan cycle, mark a value as used. + * + * When defining a new type in a C extension, this function should + * be used by the type's _ongcscan callback to mark any values not + * already tracked by the garbage collector. + * + * @param value The value to mark. + */ +extern void krk_markValue(KrkValue value); + +/** + * @brief During a GC scan cycle, mark an object as used. + * + * Equivalent to krk_markValue but operates directly on an object. + * + * @param object The object to mark. + */ +extern void krk_markObject(KrkObj * object); + +/** + * @brief During a GC scan cycle, mark the contents of a table as used. + * + * Marks all keys and values in a table as used. Generally applied + * to the internal storage of mapping types. + * + * @param table The table to mark. + */ +extern void krk_markTable(KrkTable * table); + diff --git a/base/usr/include/kuroko/object.h b/base/usr/include/kuroko/object.h new file mode 100644 index 00000000..df137c28 --- /dev/null +++ b/base/usr/include/kuroko/object.h @@ -0,0 +1,411 @@ +#pragma once +/** + * @file object.h + * @brief Struct definitions for core object types. + */ +#include +#include "kuroko.h" +#include "value.h" +#include "chunk.h" +#include "table.h" + +#ifdef ENABLE_THREADING +#include +#endif + +typedef enum { + KRK_OBJ_CODEOBJECT, + KRK_OBJ_NATIVE, + KRK_OBJ_CLOSURE, + KRK_OBJ_STRING, + KRK_OBJ_UPVALUE, + KRK_OBJ_CLASS, + KRK_OBJ_INSTANCE, + KRK_OBJ_BOUND_METHOD, + KRK_OBJ_TUPLE, + KRK_OBJ_BYTES, +} KrkObjType; + +#undef KrkObj +/** + * @brief The most basic object type. + * + * This is the base of all object types and contains + * the core structures for garbage collection. + */ +typedef struct KrkObj { + KrkObjType type; + unsigned char isMarked:1; + unsigned char inRepr:1; + unsigned char generation:2; + unsigned char isImmortal:1; + uint32_t hash; + struct KrkObj * next; +} KrkObj; + +typedef enum { + KRK_STRING_ASCII = 0, + KRK_STRING_UCS1 = 1, + KRK_STRING_UCS2 = 2, + KRK_STRING_UCS4 = 4, + KRK_STRING_INVALID = 5, +} KrkStringType; + +#undef KrkString +/** + * @brief Immutable sequence of Unicode codepoints. + * @extends KrkObj + */ +typedef struct KrkString { + KrkObj obj; + KrkStringType type; + size_t length; + size_t codesLength; + char * chars; + void * codes; +} KrkString; + +/** + * @brief Immutable sequence of bytes. + * @extends KrkObj + */ +typedef struct { + KrkObj obj; + size_t length; + uint8_t * bytes; +} KrkBytes; + +/** + * @brief Storage for values referenced from nested functions. + * @extends KrkObj + */ +typedef struct KrkUpvalue { + KrkObj obj; + int location; + KrkValue closed; + struct KrkUpvalue * next; + struct KrkThreadState * owner; +} KrkUpvalue; + +/** + * @brief Metadata on a local variable name in a function. + * + * This is used by the disassembler to print the names of + * locals when they are referenced by instructions. + */ +typedef struct { + size_t id; + size_t birthday; + size_t deathday; + KrkString * name; +} KrkLocalEntry; + +struct KrkInstance; + +/** + * @brief Code object. + * @extends KrkObj + * + * Contains the static data associated with a chunk of bytecode. + */ +typedef struct { + KrkObj obj; + short requiredArgs; + short keywordArgs; + size_t upvalueCount; + KrkChunk chunk; + KrkString * name; + KrkString * docstring; + KrkValueArray requiredArgNames; + KrkValueArray keywordArgNames; + size_t localNameCapacity; + size_t localNameCount; + KrkLocalEntry * localNames; + unsigned char collectsArguments:1; + unsigned char collectsKeywords:1; + unsigned char isGenerator:1; + struct KrkInstance * globalsContext; + KrkString * qualname; +} KrkCodeObject; + +/** + * @brief Function object. + * @extends KrkObj + * + * Not to be confused with code objects, a closure is a single instance of a function. + */ +typedef struct { + KrkObj obj; + KrkCodeObject * function; + KrkUpvalue ** upvalues; + size_t upvalueCount; + unsigned char isClassMethod:1; + unsigned char isStaticMethod:1; + KrkValue annotations; + KrkTable fields; +} KrkClosure; + +typedef void (*KrkCleanupCallback)(struct KrkInstance *); + +/** + * @brief Type object. + * @extends KrkObj + * + * Represents classes defined in user code as well as classes defined + * by C extensions to represent method tables for new types. + */ +typedef struct KrkClass { + KrkObj obj; + KrkString * name; + KrkString * filename; + KrkString * docstring; + struct KrkClass * base; + KrkTable methods; + size_t allocSize; + KrkCleanupCallback _ongcscan; + KrkCleanupCallback _ongcsweep; + + /* Quick access for common stuff */ + KrkObj * _getter; + KrkObj * _setter; + KrkObj * _getslice; + KrkObj * _reprer; + KrkObj * _tostr; + KrkObj * _call; + KrkObj * _init; + KrkObj * _eq; + KrkObj * _len; + KrkObj * _enter; + KrkObj * _exit; + KrkObj * _delitem; + KrkObj * _iter; + KrkObj * _getattr; + KrkObj * _dir; + KrkObj * _setslice; + KrkObj * _delslice; + KrkObj * _contains; + KrkObj * _descget; + KrkObj * _descset; + KrkObj * _classgetitem; +} KrkClass; + +/** + * @brief An object of a class. + * @extends KrkObj + * + * Created by class initializers, instances are the standard type of objects + * built by managed code. Not all objects are instances, but all instances are + * objects, and all instances have well-defined class. + */ +typedef struct KrkInstance { + KrkObj obj; + KrkClass * _class; + KrkTable fields; +} KrkInstance; + +/** + * @brief A function that has been attached to an object to serve as a method. + * @extends KrkObj + * + * When a bound method is called, its receiver is implicitly extracted as + * the first argument. Bound methods are created whenever a method is retreived + * from the class of a value. + */ +typedef struct { + KrkObj obj; + KrkValue receiver; + KrkObj * method; +} KrkBoundMethod; + +typedef KrkValue (*NativeFn)(int argCount, KrkValue* args, int hasKwargs); + +/** + * @brief Managed binding to a C function. + * @extends KrkObj + * + * Represents a C function that has been exposed to managed code. + */ +typedef struct { + KrkObj obj; + NativeFn function; + const char * name; + const char * doc; + int isMethod; +} KrkNative; + +/** + * @brief Immutable sequence of arbitrary values. + * @extends KrkObj + * + * Tuples are fixed-length non-mutable collections of values intended + * for use in situations where the flexibility of a list is not needed. + */ +typedef struct { + KrkObj obj; + KrkValueArray values; +} KrkTuple; + +/** + * @brief Mutable array of values. + * @extends KrkInstance + * + * A list is a flexible array of values that can be extended, cleared, + * sorted, rearranged, iterated over, etc. + */ +typedef struct { + KrkInstance inst; + KrkValueArray values; +#ifdef ENABLE_THREADING + pthread_rwlock_t rwlock; +#endif +} KrkList; + +/** + * @brief Flexible mapping type. + * @extends KrkInstance + * + * Provides key-to-value mappings as a first-class object type. + */ +typedef struct { + KrkInstance inst; + KrkTable entries; +} KrkDict; + +struct DictItems { + KrkInstance inst; + KrkValue dict; + size_t i; +}; + +struct DictKeys { + KrkInstance inst; + KrkValue dict; + size_t i; +}; + +/** + * @brief Yield ownership of a C string to the GC and obtain a string object. + * @memberof KrkString + * + * Creates a string object represented by the characters in 'chars' and of + * length 'length'. The source string must be nil-terminated and must + * remain valid for the lifetime of the object, as its ownership is yielded + * to the GC. Useful for strings which were allocated on the heap by + * other mechanisms. + * + * 'chars' must be a nil-terminated C string representing a UTF-8 + * character sequence. + * + * @param chars C string to take ownership of. + * @param length Length of the C string. + * @return A string object. + */ +extern KrkString * krk_takeString(char * chars, size_t length); + +/** + * @brief Obtain a string object representation of the given C string. + * @memberof KrkString + * + * Converts the C string 'chars' into a string object by checking the + * string table for it. If the string table does not have an equivalent + * string, a new one will be created by copying 'chars'. + * + * 'chars' must be a nil-terminated C string representing a UTF-8 + * character sequence. + * + * @param chars C string to convert to a string object. + * @param length Length of the C string. + * @return A string object. + */ +extern KrkString * krk_copyString(const char * chars, size_t length); + +/** + * @brief Ensure that a codepoint representation of a string is available. + * @memberof KrkString + * + * Obtain an untyped pointer to the codepoint representation of a string. + * If the string does not have a codepoint representation allocated, it will + * be generated by this function and remain with the string for the duration + * of its lifetime. + * + * @param string String to obtain the codepoint representation of. + * @return A pointer to the bytes of the codepoint representation. + */ +extern void * krk_unicodeString(KrkString * string); + +/** + * @brief Obtain the codepoint at a given index in a string. + * @memberof KrkString + * + * This is a convenience function which ensures that a Unicode codepoint + * representation has been generated and returns the codepoint value at + * the requested index. If you need to find multiple codepoints, it + * is recommended that you use the KRK_STRING_FAST macro after calling + * krk_unicodeString instead. + * + * @note This function does not perform any bounds checking. + * + * @param string String to index into. + * @param index Offset of the codepoint to obtain. + * @return Integer representation of the codepoint at the requested index. + */ +extern uint32_t krk_unicodeCodepoint(KrkString * string, size_t index); + +/** + * @brief Convert an integer codepoint to a UTF-8 byte representation. + * @memberof KrkString + * + * Converts a single codepoint to a sequence of bytes containing the + * UTF-8 representation. 'out' must be allocated by the caller. + * + * @param value Codepoint to encode. + * @param out Array to write UTF-8 sequence into. + * @return The length of the UTF-8 sequence, in bytes. + */ +extern size_t krk_codepointToBytes(krk_integer_type value, unsigned char * out); + +/* Internal stuff. */ +extern NativeFn KrkGenericAlias; +extern KrkCodeObject * krk_newCodeObject(void); +extern KrkNative * krk_newNative(NativeFn function, const char * name, int type); +extern KrkClosure * krk_newClosure(KrkCodeObject * function); +extern KrkUpvalue * krk_newUpvalue(int slot); +extern KrkClass * krk_newClass(KrkString * name, KrkClass * base); +extern KrkInstance * krk_newInstance(KrkClass * _class); +extern KrkBoundMethod * krk_newBoundMethod(KrkValue receiver, KrkObj * method); +extern KrkTuple * krk_newTuple(size_t length); +extern KrkBytes * krk_newBytes(size_t length, uint8_t * source); +extern void krk_bytesUpdateHash(KrkBytes * bytes); +extern void krk_tupleUpdateHash(KrkTuple * self); + +#define KRK_STRING_FAST(string,offset) (uint32_t)\ + (string->type <= 1 ? ((uint8_t*)string->codes)[offset] : \ + (string->type == 2 ? ((uint16_t*)string->codes)[offset] : \ + ((uint32_t*)string->codes)[offset])) + +#define CODEPOINT_BYTES(cp) (cp < 0x80 ? 1 : (cp < 0x800 ? 2 : (cp < 0x10000 ? 3 : 4))) + +#define krk_isObjType(v,t) (IS_OBJECT(v) && (AS_OBJECT(v)->type == (t))) +#define OBJECT_TYPE(value) (AS_OBJECT(value)->type) +#define IS_STRING(value) krk_isObjType(value, KRK_OBJ_STRING) +#define AS_STRING(value) ((KrkString *)AS_OBJECT(value)) +#define AS_CSTRING(value) (((KrkString *)AS_OBJECT(value))->chars) +#define IS_BYTES(value) krk_isObjType(value, KRK_OBJ_BYTES) +#define AS_BYTES(value) ((KrkBytes*)AS_OBJECT(value)) +#define IS_NATIVE(value) krk_isObjType(value, KRK_OBJ_NATIVE) +#define AS_NATIVE(value) ((KrkNative *)AS_OBJECT(value)) +#define IS_CLOSURE(value) krk_isObjType(value, KRK_OBJ_CLOSURE) +#define AS_CLOSURE(value) ((KrkClosure *)AS_OBJECT(value)) +#define IS_CLASS(value) krk_isObjType(value, KRK_OBJ_CLASS) +#define AS_CLASS(value) ((KrkClass *)AS_OBJECT(value)) +#define IS_INSTANCE(value) krk_isObjType(value, KRK_OBJ_INSTANCE) +#define AS_INSTANCE(value) ((KrkInstance *)AS_OBJECT(value)) +#define IS_BOUND_METHOD(value) krk_isObjType(value, KRK_OBJ_BOUND_METHOD) +#define AS_BOUND_METHOD(value) ((KrkBoundMethod*)AS_OBJECT(value)) +#define IS_TUPLE(value) krk_isObjType(value, KRK_OBJ_TUPLE) +#define AS_TUPLE(value) ((KrkTuple *)AS_OBJECT(value)) +#define AS_LIST(value) (&((KrkList *)AS_OBJECT(value))->values) +#define AS_DICT(value) (&((KrkDict *)AS_OBJECT(value))->entries) + +#define IS_codeobject(value) krk_isObjType(value, KRK_OBJ_CODEOBJECT) +#define AS_codeobject(value) ((KrkCodeObject *)AS_OBJECT(value)) diff --git a/base/usr/include/kuroko/opcodes.h b/base/usr/include/kuroko/opcodes.h new file mode 100644 index 00000000..12e19dff --- /dev/null +++ b/base/usr/include/kuroko/opcodes.h @@ -0,0 +1,80 @@ +SIMPLE(OP_RETURN) +SIMPLE(OP_ADD) +SIMPLE(OP_SUBTRACT) +SIMPLE(OP_MULTIPLY) +SIMPLE(OP_DIVIDE) +SIMPLE(OP_NEGATE) +SIMPLE(OP_MODULO) +SIMPLE(OP_NONE) +SIMPLE(OP_TRUE) +SIMPLE(OP_FALSE) +SIMPLE(OP_NOT) +SIMPLE(OP_EQUAL) +SIMPLE(OP_GREATER) +SIMPLE(OP_LESS) +SIMPLE(OP_POP) +SIMPLE(OP_INHERIT) +SIMPLE(OP_RAISE) +SIMPLE(OP_CLOSE_UPVALUE) +SIMPLE(OP_DOCSTRING) +SIMPLE(OP_BITOR) +SIMPLE(OP_BITXOR) +SIMPLE(OP_BITAND) +SIMPLE(OP_SHIFTLEFT) +SIMPLE(OP_SHIFTRIGHT) +SIMPLE(OP_BITNEGATE) +SIMPLE(OP_INVOKE_GETTER) +SIMPLE(OP_INVOKE_SETTER) +SIMPLE(OP_INVOKE_DELETE) +SIMPLE(OP_INVOKE_GETSLICE) +SIMPLE(OP_INVOKE_SETSLICE) +SIMPLE(OP_INVOKE_DELSLICE) +SIMPLE(OP_INVOKE_ITER) +SIMPLE(OP_INVOKE_CONTAINS) +SIMPLE(OP_SWAP) +SIMPLE(OP_FINALIZE) +SIMPLE(OP_IS) +SIMPLE(OP_POW) +SIMPLE(OP_CLEANUP_WITH) +SIMPLE(OP_FILTER_EXCEPT) +SIMPLE(OP_BREAKPOINT) +SIMPLE(OP_YIELD) +SIMPLE(OP_ANNOTATE) +CONSTANT(OP_DEFINE_GLOBAL,(void)0) +CONSTANT(OP_CONSTANT,(void)0) +CONSTANT(OP_GET_GLOBAL,(void)0) +CONSTANT(OP_SET_GLOBAL,(void)0) +CONSTANT(OP_DEL_GLOBAL,(void)0) +CONSTANT(OP_CLASS,(void)0) +CONSTANT(OP_GET_PROPERTY, (void)0) +CONSTANT(OP_SET_PROPERTY, (void)0) +CONSTANT(OP_DEL_PROPERTY,(void)0) +CONSTANT(OP_CLASS_PROPERTY, (void)0) +CONSTANT(OP_CLOSURE, CLOSURE_MORE) +CONSTANT(OP_IMPORT, (void)0) +CONSTANT(OP_IMPORT_FROM, (void)0) +CONSTANT(OP_GET_SUPER, (void)0) +OPERAND(OP_KWARGS, (void)0) +OPERAND(OP_SET_LOCAL, LOCAL_MORE) +OPERAND(OP_GET_LOCAL, LOCAL_MORE) +OPERAND(OP_SET_UPVALUE, (void)0) +OPERAND(OP_GET_UPVALUE, (void)0) +OPERAND(OP_CALL, (void)0) +OPERAND(OP_INC, (void)0) +OPERAND(OP_TUPLE, (void)0) +OPERAND(OP_UNPACK, (void)0) +OPERAND(OP_DUP,(void)0) +OPERAND(OP_EXPAND_ARGS,EXPAND_ARGS_MORE) +OPERAND(OP_LIST_APPEND, (void)0) +OPERAND(OP_DICT_SET, (void)0) +OPERAND(OP_SET_ADD, (void)0) +OPERAND(OP_MAKE_LIST, (void)0) +OPERAND(OP_MAKE_DICT, (void)0) +OPERAND(OP_MAKE_SET, (void)0) +OPERAND(OP_REVERSE, (void)0) +JUMP(OP_JUMP,+) +JUMP(OP_JUMP_IF_FALSE,+) +JUMP(OP_JUMP_IF_TRUE,+) +JUMP(OP_LOOP,-) +JUMP(OP_PUSH_TRY,+) +JUMP(OP_PUSH_WITH,+) diff --git a/base/usr/include/kuroko/scanner.h b/base/usr/include/kuroko/scanner.h new file mode 100644 index 00000000..d713adec --- /dev/null +++ b/base/usr/include/kuroko/scanner.h @@ -0,0 +1,186 @@ +#pragma once +/** + * @file scanner.h + * @brief Definitions used by the token scanner. + */ + +typedef enum { + TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, + TOKEN_LEFT_SQUARE, TOKEN_RIGHT_SQUARE, + TOKEN_COLON, + TOKEN_COMMA, + TOKEN_DOT, + TOKEN_MINUS, + TOKEN_PLUS, + TOKEN_SEMICOLON, + TOKEN_SOLIDUS, + TOKEN_ASTERISK, + TOKEN_POW, + TOKEN_MODULO, + TOKEN_AT, + TOKEN_CARET, /* ^ (xor) */ + TOKEN_AMPERSAND, /* & (and) */ + TOKEN_PIPE, /* | (or) */ + TOKEN_TILDE, /* ~ (negate) */ + TOKEN_LEFT_SHIFT, /* << */ + TOKEN_RIGHT_SHIFT,/* >> */ + TOKEN_BANG, + TOKEN_GREATER, + TOKEN_LESS, + TOKEN_ARROW, /* -> */ + TOKEN_WALRUS, /* := */ + + /* Comparisons */ + TOKEN_GREATER_EQUAL, + TOKEN_LESS_EQUAL, + TOKEN_BANG_EQUAL, + TOKEN_EQUAL_EQUAL, + + /* Assignments */ + TOKEN_EQUAL, + TOKEN_LSHIFT_EQUAL, /* <<= */ + TOKEN_RSHIFT_EQUAL, /* >>= */ + TOKEN_PLUS_EQUAL, /* += */ + TOKEN_MINUS_EQUAL, /* -= */ + TOKEN_PLUS_PLUS, /* ++ */ + TOKEN_MINUS_MINUS, /* -- */ + TOKEN_CARET_EQUAL, + TOKEN_PIPE_EQUAL, + TOKEN_AMP_EQUAL, + TOKEN_SOLIDUS_EQUAL, + TOKEN_ASTERISK_EQUAL, + TOKEN_POW_EQUAL, + TOKEN_MODULO_EQUAL, + + TOKEN_STRING, + TOKEN_BIG_STRING, + TOKEN_NUMBER, + + /* + * Everything after this, up to indentation, + * consists of alphanumerics. + */ + TOKEN_IDENTIFIER, + TOKEN_AND, + TOKEN_CLASS, + TOKEN_DEF, + TOKEN_DEL, + TOKEN_ELSE, + TOKEN_FALSE, + TOKEN_FOR, + TOKEN_IF, + TOKEN_IMPORT, + TOKEN_IN, + TOKEN_IS, + TOKEN_LET, + TOKEN_NONE, + TOKEN_NOT, + TOKEN_OR, + TOKEN_ELIF, + TOKEN_PASS, + TOKEN_RETURN, + TOKEN_SELF, + TOKEN_SUPER, + TOKEN_TRUE, + TOKEN_WHILE, + TOKEN_TRY, + TOKEN_EXCEPT, + TOKEN_RAISE, + TOKEN_BREAK, + TOKEN_CONTINUE, + TOKEN_AS, + TOKEN_FROM, + TOKEN_LAMBDA, + TOKEN_ASSERT, + TOKEN_YIELD, + TOKEN_WITH, + + TOKEN_PREFIX_B, + TOKEN_PREFIX_F, + + TOKEN_INDENTATION, + + TOKEN_EOL, + TOKEN_RETRY, + TOKEN_ERROR, + TOKEN_EOF, +} KrkTokenType; + +/** + * @brief A token from the scanner. + * + * Represents a single scanned item from the scanner, such as a keyword, + * string literal, numeric literal, identifier, etc. + */ +typedef struct { + KrkTokenType type; + const char * start; + size_t length; + size_t line; + const char * linePtr; + size_t col; + size_t literalWidth; +} KrkToken; + +/** + * @brief Token scanner state. + * + * Stores the state of the compiler's scanner, reading from a source + * character string and tracking the current line. + */ +typedef struct { + const char * start; + const char * cur; + const char * linePtr; + size_t line; + int startOfLine; + int hasUnget; + KrkToken unget; +} KrkScanner; + +/** + * @brief Initialize the compiler to scan tokens from 'src'. + * + * FIXME: There is currently only a single static scanner state; + * along with making the compiler re-entrant, the scanner + * needs to also be re-entrant; there's really no reason + * these can't all just take a KrkScanner* argument. + */ +extern void krk_initScanner(const char * src); + +/** + * @brief Read the next token from the scanner. + * + * FIXME: Or, maybe the scanner shouldn't even be available outside + * of the compiler, that would make some sense as well, as it's + * a low-level detail, but we use it for tab completion in the + * main repl, so I'm not sure that's feasible right now. + */ +extern KrkToken krk_scanToken(void); + +/** + * @brief Push a token back to the scanner to be reprocessed. + * + * Pushes a previously-scanned token back to the scanner. + * Used to implement small backtracking operations at the + * end of block constructs like 'if' and 'try'. + */ +extern void krk_ungetToken(KrkToken token); + +/** + * @brief Rewind the scanner to a previous state. + * + * Resets the current scanner to the state in 'to'. Used by + * the compiler to implement comprehensions, which would otherwise + * not be possible in a single-pass compiler. + */ +extern void krk_rewindScanner(KrkScanner to); + +/** + * @brief Retreive a copy of the current scanner state. + * + * Used with krk_rewindScanner() to implement rescanning + * for comprehensions. + */ +extern KrkScanner krk_tellScanner(void); diff --git a/base/usr/include/kuroko/table.h b/base/usr/include/kuroko/table.h new file mode 100644 index 00000000..8ac343ab --- /dev/null +++ b/base/usr/include/kuroko/table.h @@ -0,0 +1,153 @@ +#pragma once +/** + * @file table.h + * @brief Implementation of a generic hash table. + * + * I was going to just use the ToaruOS hashmap library, but to make following + * the book easier, let's just start from their Table implementation; it has + * an advantage of using stored entries and fixed arrays, so it has some nice + * properties despite being chained internally... + */ + +#include +#include "kuroko.h" +#include "value.h" +#include "threads.h" + +/** + * @brief One (key,value) pair in a table. + */ +typedef struct { + KrkValue key; + KrkValue value; +} KrkTableEntry; + +/** + * @brief Simple hash table of arbitrary keys to values. + */ +typedef struct { + size_t count; + size_t capacity; + KrkTableEntry * entries; +} KrkTable; + +/** + * @brief Initialize a hash table. + * @memberof KrkTable + * + * This should be called for any new hash table, especially ones + * initialized in heap or stack space, to set up the capacity, count + * and initial entries pointer. + * + * @param table Hash table to initialize. + */ +extern void krk_initTable(KrkTable * table); + +/** + * @brief Release resources associated with a hash table. + * @memberof KrkTable + * + * Frees the entries array for the table and resets count and capacity. + * + * @param table Hash table to release. + */ +extern void krk_freeTable(KrkTable * table); + +/** + * @brief Add all key-value pairs from 'from' into 'to'. + * @memberof KrkTable + * + * Copies each key-value pair from one hash table to another. If a key + * from 'from' already exists in 'to', the existing value in 'to' will be + * overwritten with the value from 'from'. + * + * @param from Source table. + * @param to Destination table. + */ +extern void krk_tableAddAll(KrkTable * from, KrkTable * to); + +/** + * @brief Find a character sequence in the string interning table. + * @memberof KrkTable + * + * Scans through the entries in a given table - usually vm.strings - to find + * an entry equivalent to the string specified by the 'chars' and 'length' + * parameters, using the 'hash' parameter to speed up lookup. + * + * @param table Should always be @c &vm.strings + * @param chars C array of chars representing the string. + * @param length Length of the string. + * @param hash Precalculated hash value for the string. + * @return If the string was found, the string object representation, else NULL. + */ +extern KrkString * krk_tableFindString(KrkTable * table, const char * chars, size_t length, uint32_t hash); + +/** + * @brief Assign a value to a key in a table. + * @memberof KrkTable + * + * Inserts the key-value pair specified by 'key' and 'value' into the hash + * table 'table', replacing any value that was already preseng with the + * same key. + * + * @param table Table to assign to. + * @param key Key to assign. + * @param value Value to assign to the key. + * @return 0 if the key was already present and has been overwritten, 1 if the key is new to the table. + */ +extern int krk_tableSet(KrkTable * table, KrkValue key, KrkValue value); + +/** + * @brief Obtain the value associated with a key in a table. + * @memberof KrkTable + * + * Scans the table 'table' for the key 'key' and, if found, outputs + * the associated value to *value. If the key is not found, then + * *value will not be changed. + * + * @param table Table to look up. + * @param key Key to look for. + * @param value Output pointer to place resulting value in. + * @return 0 if the key was not found, 1 if it was. + */ +extern int krk_tableGet(KrkTable * table, KrkValue key, KrkValue * value); + +/** + * @brief Remove a key from a hash table. + * @memberof KrkTable + * + * Scans the table 'table' for the key 'key' and, if found, removes + * the entry, replacing it with a tombstone value. + * + * @param table Table to delete from. + * @param key Key to delete. + * @return 1 if the value was found and deleted, 0 if it was not present. + */ +extern int krk_tableDelete(KrkTable * table, KrkValue key); + +/** + * @brief Internal table scan function. + * @memberof KrkTable + * + * Scans through the the entry array 'entries' to find the appropriate entry + * for 'key', return a pointer to the entry, which may be or may not have + * an associated pair. + * + * @param entries Table entry array to scan. + * @param capacity Size of the table entry array, in entries. + * @param key Key to locate. + * @return A pointer to the entry for 'key'. + */ +extern KrkTableEntry * krk_findEntry(KrkTableEntry * entries, size_t capacity, KrkValue key); + +/** + * @brief Calculate the hash for a value. + * @memberof KrkValue + * + * Retreives or calculates the hash value for 'value'. + * + * @param value Value to hash. + * @return An unsigned 32-bit hash value. + */ +extern uint32_t krk_hashValue(KrkValue value); + diff --git a/base/usr/include/kuroko/threads.h b/base/usr/include/kuroko/threads.h new file mode 100644 index 00000000..ede5e91b --- /dev/null +++ b/base/usr/include/kuroko/threads.h @@ -0,0 +1,41 @@ +#pragma once +/** + * @file threads.h + * @brief Convience header for providing atomic operations to threads. + */ + +#ifdef ENABLE_THREADING +#include +#include + +#ifdef _WIN32 +#include +#include +#define sched_yield() YieldProcessor() +#endif + +static inline void _krk_internal_spin_lock(int volatile * lock) { + while(__sync_lock_test_and_set(lock, 0x01)) { + sched_yield(); + } +} + +static inline void _krk_internal_spin_unlock(int volatile * lock) { + __sync_lock_release(lock); +} + +#define _obtain_lock(v) _krk_internal_spin_lock(&v); +#define _release_lock(v) _krk_internal_spin_unlock(&v); + +#else + +#define _obtain_lock(v) +#define _release_lock(v) + +#define pthread_rwlock_init(a,b) +#define pthread_rwlock_wrlock(a) +#define pthread_rwlock_rdlock(a) +#define pthread_rwlock_unlock(a) + +#endif + diff --git a/base/usr/include/kuroko/util.h b/base/usr/include/kuroko/util.h new file mode 100644 index 00000000..c78c904b --- /dev/null +++ b/base/usr/include/kuroko/util.h @@ -0,0 +1,296 @@ +#pragma once +/** + * @file util.h + * @brief Utilities for creating native bindings. + * + * This is intended for use in C extensions to provide a uniform interface + * for defining extension methods and ensuring they have consistent argument + * and keyword argument usage. + */ +#include "object.h" +#include "vm.h" +#include "memory.h" + +/* Quick macro for turning string constants into KrkString*s */ +#define S(c) (krk_copyString(c,sizeof(c)-1)) + +#define likely(cond) __builtin_expect(!!(cond), 1) +#define unlikely(cond) __builtin_expect(!!(cond), 0) +#ifndef _WIN32 +#define _noexport __attribute__((visibility("hidden"))) +#else +#define _noexport +#endif + +/** + * Binding macros. + * + * These macros are intended to be used together to define functions for a class. + */ +static inline const char * _method_name(const char * func) { + const char * out = func; + if (*out == '_') out++; + while (*out && *out != '_') out++; + if (*out == '_') out++; + return out; +} + +#define ADD_BASE_CLASS(obj, name, baseClass) krk_makeClass(vm.builtins, &obj, name, baseClass) + +/* _method_name works for this, but let's skip the inlined function call where possible */ +#define _function_name(f) (f+5) + +#define ATTRIBUTE_NOT_ASSIGNABLE() do { if (argc != 1) return krk_runtimeError(vm.exceptions->attributeError, "attribute '%s' is not assignable", \ + _method_name(__func__)); } while (0) + +#define METHOD_TAKES_NONE() do { if (argc != 1) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes no arguments (%d given)", \ + _method_name(__func__), (argc-1)); } while (0) + +#define METHOD_TAKES_EXACTLY(n) do { if (argc != (n+1)) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)", \ + _method_name(__func__), "exactly", n, (n != 1) ? "s" : "", (argc-1)); } while (0) + +#define METHOD_TAKES_AT_LEAST(n) do { if (argc < (n+1)) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)", \ + _method_name(__func__), "at least", n, (n != 1) ? "s" : "", (argc-1)); } while (0) + +#define METHOD_TAKES_AT_MOST(n) do { if (argc > (n+1)) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)", \ + _method_name(__func__), "at most", n, (n != 1) ? "s" : "", (argc-1)); } while (0) + +#define FUNCTION_TAKES_NONE() do { if (argc != 0) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes no arguments (%d given)", \ + _function_name(__func__), (argc)); } while (0) + +#define FUNCTION_TAKES_EXACTLY(n) do { if (argc != n) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)", \ + _function_name(__func__), "exactly", n, (n != 1) ? "s" : "", (argc)); } while (0) + +#define FUNCTION_TAKES_AT_LEAST(n) do { if (argc < n) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)", \ + _function_name(__func__), "at least", n, (n != 1) ? "s" : "", (argc)); } while (0) + +#define FUNCTION_TAKES_AT_MOST(n) do { if (argc > n) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %s %d argument%s (%d given)", \ + _function_name(__func__), "at most", n, (n != 1) ? "s" : "", (argc)); } while (0) + +#define TYPE_ERROR(expected,value) krk_runtimeError(vm.exceptions->typeError, "%s() expects %s, not '%s'", \ + /* Function name */ _method_name(__func__), /* expected type */ #expected, krk_typeName(value)) + +#define NOT_ENOUGH_ARGS() krk_runtimeError(vm.exceptions->argumentError, "%s() missing required positional argument", \ + /* Function name */ _method_name(__func__)) + +#define CHECK_ARG(i, type, ctype, name) \ + if (argc < (i+1)) return NOT_ENOUGH_ARGS(); \ + if (!IS_ ## type (argv[i])) return TYPE_ERROR(type,argv[i]); \ + ctype name __attribute__((unused)) = AS_ ## type (argv[i]) + +#define FUNC_NAME(klass, name) _ ## klass ## _ ## name +#define FUNC_SIG(klass, name) _noexport KrkValue FUNC_NAME(klass,name) (int argc, KrkValue argv[], int hasKw) +#define KRK_METHOD(klass, name, body) FUNC_SIG(klass, name) { \ + CHECK_ARG(0,klass,CURRENT_CTYPE,CURRENT_NAME); \ + body; return NONE_VAL(); } + +#define KRK_FUNC(name,body) static KrkValue _krk_ ## name (int argc, KrkValue argv[], int hasKw) { \ + body; return NONE_VAL(); } + +/* This assumes you have a KrkInstance called `module` in the current scope. */ +#define MAKE_CLASS(klass) do { krk_makeClass(module,&klass,#klass,vm.baseClasses->objectClass); klass ->allocSize = sizeof(struct klass); } while (0) +#define BIND_METHOD(klass,method) krk_defineNative(&klass->methods, "." #method, _ ## klass ## _ ## method) +#define BIND_FIELD(klass,method) krk_defineNative(&klass->methods, ":" #method, _ ## klass ## _ ## method) +#define BIND_PROP(klass,method) krk_defineNativeProperty(&klass->methods, #method, _ ## klass ## _ ## method) +#define BIND_FUNC(module,func) krk_defineNative(&module->fields, #func, _krk_ ## func) + +/** + * @brief Inline flexible string array. + */ +struct StringBuilder { + size_t capacity; + size_t length; + char * bytes; +}; + +/** + * @brief Add a character to the end of a string builder. + * + * @param sb String builder to append to. + * @param c Character to append. + */ +static inline void pushStringBuilder(struct StringBuilder * sb, char c) { + if (sb->capacity < sb->length + 1) { + size_t old = sb->capacity; + sb->capacity = GROW_CAPACITY(old); + sb->bytes = GROW_ARRAY(char, sb->bytes, old, sb->capacity); + } + sb->bytes[sb->length++] = c; +} + +/** + * @brief Append a string to the end of a string builder. + * + * @param sb String builder to append to. + * @param str C string to add. + * @param len Length of the C string. + */ +static inline void pushStringBuilderStr(struct StringBuilder * sb, char *str, size_t len) { + if (sb->capacity < sb->length + len) { + while (sb->capacity < sb->length + len) { + size_t old = sb->capacity; + sb->capacity = GROW_CAPACITY(old); + } + sb->bytes = realloc(sb->bytes, sb->capacity); + } + for (size_t i = 0; i < len; ++i) { + sb->bytes[sb->length++] = *(str++); + } +} + +/** + * @brief Finalize a string builder into a string object. + * + * Creates a string object from the contents of the string builder and + * frees the space allocated for the builder, returning a value representing + * the newly created string object. + * + * @param sb String builder to finalize. + * @return A value representing a string object. + */ +static inline KrkValue finishStringBuilder(struct StringBuilder * sb) { + KrkValue out = OBJECT_VAL(krk_copyString(sb->bytes, sb->length)); + FREE_ARRAY(char,sb->bytes, sb->capacity); + return out; +} + +/** + * @brief Finalize a string builder in a bytes object. + * + * Converts the contents of a string builder into a bytes object and + * frees the space allocated for the builder. + * + * @param sb String builder to finalize. + * @return A value representing a bytes object. + */ +static inline KrkValue finishStringBuilderBytes(struct StringBuilder * sb) { + KrkValue out = OBJECT_VAL(krk_newBytes(sb->length, (uint8_t*)sb->bytes)); + FREE_ARRAY(char,sb->bytes, sb->capacity); + return out; +} + +/** + * @brief Discard the contents of a string builder. + * + * Frees the resources allocated for the string builder without converting + * it to a string or bytes object. Call this when an error has been encountered + * and the contents of a string builder are no longer needed. + * + * @param sb String builder to discard. + * @return None, as a convenience. + */ +static inline KrkValue discardStringBuilder(struct StringBuilder * sb) { + FREE_ARRAY(char,sb->bytes, sb->capacity); + return NONE_VAL(); +} + +#define IS_int(o) (IS_INTEGER(o)) +#define AS_int(o) (AS_INTEGER(o)) + +#define IS_bool(o) (IS_BOOLEAN(o)) +#define AS_bool(o) (AS_BOOLEAN(o)) + +#define IS_float(o) (IS_FLOATING(o)) +#define AS_float(o) (AS_FLOATING(o)) + +#define IS_list(o) krk_isInstanceOf(o,vm.baseClasses->listClass) +#define AS_list(o) (KrkList*)AS_OBJECT(o) + +#define IS_listiterator(o) krk_isInstanceOf(o,vm.baseClasses->listiteratorClass) +#define AS_listiterator(o) AS_INSTANCE(o) + +#define IS_str(o) (IS_STRING(o)||krk_isInstanceOf(o,vm.baseClasses->strClass)) +#define AS_str(o) (KrkString*)AS_OBJECT(o) + +#define IS_striterator(o) (krk_isInstanceOf(o,vm.baseClasses->striteratorClass)) +#define AS_striterator(o) (AS_INSTANCE(o)) + +#define IS_dict(o) krk_isInstanceOf(o,vm.baseClasses->dictClass) +#define AS_dict(o) (KrkDict*)AS_OBJECT(o) + +#define IS_dictitems(o) krk_isInstanceOf(o,vm.baseClasses->dictitemsClass) +#define AS_dictitems(o) ((struct DictItems*)AS_OBJECT(o)) + +#define IS_dictkeys(o) krk_isInstanceOf(o,vm.baseClasses->dictkeysClass) +#define AS_dictkeys(o) ((struct DictKeys*)AS_OBJECT(o)) + +#define IS_bytesiterator(o) (krk_isInstanceOf(o,vm.baseClasses->bytesiteratorClass)) +#define AS_bytesiterator(o) (AS_INSTANCE(o)) + +#ifndef unpackError +#define unpackError(fromInput) return krk_runtimeError(vm.exceptions->typeError, "'%s' object is not iterable", krk_typeName(fromInput)); +#endif + +#define unpackIterable(fromInput) do { \ + KrkClass * type = krk_getType(fromInput); \ + if (type->_iter) { \ + size_t stackOffset = krk_currentThread.stackTop - krk_currentThread.stack; \ + krk_push(fromInput); \ + krk_push(krk_callSimple(OBJECT_VAL(type->_iter), 1, 0)); \ + do { \ + krk_push(krk_currentThread.stack[stackOffset]); \ + krk_push(krk_callSimple(krk_peek(0), 0, 1)); \ + if (krk_valuesSame(krk_currentThread.stack[stackOffset], krk_peek(0))) { \ + krk_pop(); \ + krk_pop(); \ + break; \ + } \ + unpackArray(1,krk_peek(0)); \ + krk_pop(); \ + } while (1); \ + } else { \ + unpackError(fromInput); \ + } \ +} while (0) + +#define unpackIterableFast(fromInput) do { \ + KrkValue iterableValue = (fromInput); \ + if (IS_TUPLE(iterableValue)) { \ + unpackArray(AS_TUPLE(iterableValue)->values.count, AS_TUPLE(iterableValue)->values.values[i]); \ + } else if (IS_INSTANCE(iterableValue) && AS_INSTANCE(iterableValue)->_class == vm.baseClasses->listClass) { \ + unpackArray(AS_LIST(iterableValue)->count, AS_LIST(iterableValue)->values[i]); \ + } else if (IS_INSTANCE(iterableValue) && AS_INSTANCE(iterableValue)->_class == vm.baseClasses->dictClass) { \ + unpackArray(AS_DICT(iterableValue)->count, krk_dict_nth_key_fast(AS_DICT(iterableValue)->capacity, AS_DICT(iterableValue)->entries, i)); \ + } else if (IS_STRING(iterableValue)) { \ + unpackArray(AS_STRING(iterableValue)->codesLength, krk_string_get(2,(KrkValue[]){iterableValue,INTEGER_VAL(i)},0)); \ + } else { \ + unpackIterable(iterableValue); \ + } \ +} while (0) + +static inline void _setDoc_class(KrkClass * thing, const char * text, size_t size) { + thing->docstring = krk_copyString(text, size); +} +static inline void _setDoc_instance(KrkInstance * thing, const char * text, size_t size) { + krk_attachNamedObject(&thing->fields, "__doc__", (KrkObj*)krk_copyString(text, size)); +} +static inline void _setDoc_native(KrkNative * thing, const char * text, size_t size) { + (void)size; + thing->doc = text; +} + +/** + * @def KRK_DOC(thing,text) + * @brief Attach documentation to a thing of various types. + * + * Classes store their docstrings directly, rather than in their attribute tables. + * Instances use the attribute table and store strings with the name @c \__doc__. + * Native functions store direct C string pointers for documentation. + * + * This macro provides a generic interface for applying documentation strings to + * any of the above types, and handles not attaching documentation when built + * with KRK_NO_DOCUMENTATION. + */ +#ifdef KRK_NO_DOCUMENTATION +# define KRK_DOC(thing, text) (thing); +#else +# define KRK_DOC(thing, text) \ + _Generic(&((thing)[0]), \ + KrkClass*: _setDoc_class, \ + KrkInstance*: _setDoc_instance, \ + KrkNative*: _setDoc_native \ + )(thing,text,sizeof(text)-1) +#endif + +#define BUILTIN_FUNCTION(name, func, docStr) KRK_DOC(krk_defineNative(&vm.builtins->fields, name, func), docStr) + diff --git a/base/usr/include/kuroko/value.h b/base/usr/include/kuroko/value.h new file mode 100644 index 00000000..79587ae1 --- /dev/null +++ b/base/usr/include/kuroko/value.h @@ -0,0 +1,210 @@ +#pragma once +/** + * @file value.h + * @brief Definitions for primitive stack references. + */ +#include +#include "kuroko.h" + +/** + * @brief Base structure of all heap objects. + * + * KrkObj is the base type of all objects stored on the heap and + * managed by the garbage collector. + */ +typedef struct KrkObj KrkObj; +typedef struct KrkString KrkString; + +/** + * @brief Tag enum for basic value types. + * + * Value types are tagged unions of a handful of small + * types represented directly on the stack: Integers, + * double-precision floating point values, booleans, + * exception handler references, complex function argument + * processing sentinels, object reference pointers, and None. + */ +typedef enum { + KRK_VAL_NONE, + KRK_VAL_BOOLEAN, + KRK_VAL_INTEGER, + KRK_VAL_FLOATING, + KRK_VAL_HANDLER, + KRK_VAL_OBJECT, + KRK_VAL_KWARGS, +} KrkValueType; + +/** + * @brief Stack value representation of a 'with' or 'try' block. + * + * When a 'with' or 'try' block is entered, a handler value is + * created on the stack representing the type (with, try) and the + * jump target to leave the block (entering the 'except' block of + * a 'try', if present, or calling the __exit__ method of an object + * __enter__'d by a 'with' block). When the relevant conditions are + * triggered in the VM, the stack will be scanned from top to bottom + * to look for these values. + */ +typedef struct { + unsigned short type; + unsigned short target; +} KrkJumpTarget; + +/** + * @brief Stack reference or primative value. + * + * This type stores a stack reference to an object, or the contents of + * a primitive type. Each VM thread's stack consists of an array of + * these values, and they are generally passed around in the VM through + * direct copying rather than as pointers, avoiding the need to track + * memory used by them. + * + * Each value is a tagged union with a type (see the enum KrkValueType) + * and its contents. + */ +typedef struct { + KrkValueType type; + union { + char boolean; + krk_integer_type integer; + double floating; + KrkJumpTarget handler; + KrkObj * object; + } as; +} KrkValue; + +/** + * @brief Flexible vector of stack references. + * + * Value Arrays provide a resizable collection of values and are the + * backbone of lists and tuples. + */ +typedef struct { + size_t capacity; /**< Available allocated space. */ + size_t count; /**< Current number of used slots. */ + KrkValue * values; /**< Pointer to heap-allocated storage. */ +} KrkValueArray; + +/** + * @brief Initialize a value array. + * @memberof KrkValueArray + * + * This should be called for any new value array, especially ones + * initialized in heap or stack space, to set up the capacity, count + * and initial value pointer. + * + * @param array Value array to initialize. + */ +extern void krk_initValueArray(KrkValueArray * array); + +/** + * @brief Add a value to a value array. + * @memberof KrkValueArray + * + * Appends 'value' to the end of the given array, adjusting count values + * and resizing as necessary. + * + * @param array Array to append to. + * @param value Value to append to array. + */ +extern void krk_writeValueArray(KrkValueArray * array, KrkValue value); + +/** + * @brief Release relesources used by a value array. + * @memberof KrkValueArray + * + * Frees the storage associated with a given value array and resets + * its capacity and count. Does not directly free resources associated + * with heap objects referenced by the values in this array: The GC + * is responsible for taking care of that. + * + * @param array Array to release. + */ +extern void krk_freeValueArray(KrkValueArray * array); + +/** + * @brief Print a string representation of a value. + * @memberof KrkValue + * + * Print a string representation of 'value' to the stream 'f'. + * For primitives, performs appropriate formatting. For objects, + * this will call __str__ on the object's representative type. + * If the type does not have a __str__ method, __repr__ will be + * tried before falling back to krk_typeName to directly print + * the name of the class with no information on the value. + * + * This function provides the backend for the print() built-in. + * + * @param f Stream to write to. + * @param value Value to display. + */ +extern void krk_printValue(FILE * f, KrkValue value); + +/** + * @brief Print a value without calling the VM. + * @memberof KrkValue + * + * Print a string representation of 'value' to the stream 'f', + * avoiding calls to managed code by using simplified representations + * where necessary. This is intended for use in debugging code, such + * as during disassembly, or when printing values in an untrusted context. + * + * @note This function will truncate long strings and print them in a form + * closer to the 'repr()' representation, with escaped bytes, rather + * than directly printing them to the stream. + * + * @param f Stream to write to. + * @param value Value to display. + */ +extern void krk_printValueSafe(FILE * f, KrkValue value); + +/** + * @brief Compare two values for equality. + * @memberof KrkValue + * + * Performs a relaxed equality comparison between two values, + * check for equivalence by contents. This may call managed + * code to run __eq__ methods. + * + * @return 1 if values are equivalent, 0 otherwise. + */ +extern int krk_valuesEqual(KrkValue a, KrkValue b); + +/** + * @brief Compare two values by identity. + * @memberof KrkValue + * + * Performs a strict comparison between two values, comparing + * their identities. For primitive values, this is generally + * the same as comparing by equality. For objects, this compares + * pointer values directly. + * + * @return 1 if values represent the same object or value, 0 otherwise. + */ +extern int krk_valuesSame(KrkValue a, KrkValue b); + +#define BOOLEAN_VAL(value) ((KrkValue){KRK_VAL_BOOLEAN, {.integer = value}}) +#define NONE_VAL(value) ((KrkValue){KRK_VAL_NONE, {.integer = 0}}) +#define INTEGER_VAL(value) ((KrkValue){KRK_VAL_INTEGER, {.integer = value}}) +#define FLOATING_VAL(value) ((KrkValue){KRK_VAL_FLOATING,{.floating = value}}) +#define HANDLER_VAL(ty,ta) ((KrkValue){KRK_VAL_HANDLER, {.handler = (KrkJumpTarget){.type = ty, .target = ta}}}) +#define OBJECT_VAL(value) ((KrkValue){KRK_VAL_OBJECT, {.object = (KrkObj*)value}}) +#define KWARGS_VAL(value) ((KrkValue){KRK_VAL_KWARGS, {.integer = value}}) + +#define AS_BOOLEAN(value) ((value).as.integer) +#define AS_INTEGER(value) ((value).as.integer) +#define AS_FLOATING(value) ((value).as.floating) +#define AS_HANDLER(value) ((value).as.handler) +#define AS_OBJECT(value) ((value).as.object) + +#define IS_BOOLEAN(value) ((value).type == KRK_VAL_BOOLEAN) +#define IS_NONE(value) ((value).type == KRK_VAL_NONE) +#define IS_INTEGER(value) (((value).type == KRK_VAL_INTEGER) || ((value.type) == KRK_VAL_BOOLEAN)) +#define IS_FLOATING(value) ((value).type == KRK_VAL_FLOATING) +#define IS_HANDLER(value) ((value).type == KRK_VAL_HANDLER) +#define IS_OBJECT(value) ((value).type == KRK_VAL_OBJECT) +#define IS_KWARGS(value) ((value).type == KRK_VAL_KWARGS) + +#define IS_TRY_HANDLER(value) (IS_HANDLER(value) && AS_HANDLER(value).type == OP_PUSH_TRY) +#define IS_WITH_HANDLER(value) (IS_HANDLER(value) && AS_HANDLER(value).type == OP_PUSH_WITH) + diff --git a/base/usr/include/kuroko/vm.h b/base/usr/include/kuroko/vm.h new file mode 100644 index 00000000..41ea3983 --- /dev/null +++ b/base/usr/include/kuroko/vm.h @@ -0,0 +1,820 @@ +#pragma once +/** + * @file vm.h + * @brief Core API for the bytecode virtual machine. + * + * Functions and structures declared here make up the bulk of the public C API + * for Kuroko, including initializing the VM and passing code to be interpreted. + */ +#include +#include +#include "kuroko.h" +#include "value.h" +#include "table.h" +#include "object.h" + +/** + * @def KRK_CALL_FRAMES_MAX + * @brief Maximum depth of the call stack in managed-code function calls. + */ +#define KRK_CALL_FRAMES_MAX 64 + +/** + * @def KRK_THREAD_SCRATCH_SIZE + * @brief Extra space for each thread to store a set of working values safe from the GC. + * + * Various operations require threads to remove values from the stack but ensure + * they are not lost to garbage collection. This space allows each thread to keep + * a few things around during those operations. + */ +#define KRK_THREAD_SCRATCH_SIZE 3 + +/** + * @brief Represents a managed call state in a VM thread. + * + * For every managed function call, including the top-level module, + * a call frame is added to the stack to track the running function, + * the current opcode instruction, the offset into the stack, and + * the valid globals table. + * + * Call frames are used directly by the VM as the source of + * opcodes and operands during execution, and are used by the exception + * handler to roll back execution to the appropriate environment. + */ +typedef struct { + KrkClosure * closure; /**< Pointer to the function object containing the code object for this frame */ + uint8_t * ip; /**< Instruction pointer within the code object's bytecode data */ + size_t slots; /**< Offset into the stack at which this function call's arguments begin */ + size_t outSlots; /**< Offset into the stack at which stackTop will be reset upon return */ + KrkTable * globals; /**< Pointer to the attribute table containing valud global vairables for this call */ +} KrkCallFrame; + +/** + * @brief Index numbers for always-available interned strings representing important method and member names. + * + * The VM must look up many methods and members by fixed names. To avoid + * continuously having to box and unbox these from C strings to the appropriate + * interned @c KrkString, we keep an array of the @c KrkString pointers in the global VM state. + * + * These values are the offsets into that index for each of the relevant + * function names (generally with extra underscores removed). For example + * @c METHOD_INIT is the offset for the string value for @c "__init__". + */ +typedef enum { + METHOD_INIT, + METHOD_STR, + METHOD_REPR, + METHOD_GET, + METHOD_SET, + METHOD_CLASS, + METHOD_NAME, + METHOD_FILE, + METHOD_INT, + METHOD_FLOAT, + METHOD_CHR, + METHOD_LEN, + METHOD_DOC, + METHOD_BASE, + METHOD_GETSLICE, + METHOD_ORD, + METHOD_CALL, + METHOD_EQ, + METHOD_ENTER, + METHOD_EXIT, + METHOD_DELITEM, + METHOD_ITER, + METHOD_GETATTR, + METHOD_DIR, + METHOD_SETSLICE, + METHOD_DELSLICE, + METHOD_CONTAINS, + METHOD_DESCGET, + METHOD_DESCSET, + METHOD_CLASSGETITEM, + + METHOD__MAX, +} KrkSpecialMethods; + +/** + * @brief Table of basic exception types. + * + * These are the core exception types, available in managed code + * from the builtin namespace. A single instance of this struct + * is attached to the global VM state so that C code can quickly + * access these exception types for use with krk_runtimeException. + * + * @see krk_runtimeException + */ +struct Exceptions { + KrkClass * baseException; /**< @exception Exception The base exception type. */ + KrkClass * typeError; /**< @exception TypeError An argument or value was not of the expected type. */ + KrkClass * argumentError; /**< @exception ArgumentException The number of arguments passed to a function was not as expected. */ + KrkClass * indexError; /**< @exception IndexError An attempt was made to reference an invalid array index. */ + KrkClass * keyError; /**< @exception KeyError An attempt was made to reference an invalid mapping key. */ + KrkClass * attributeError; /**< @exception AttributeError An attempt was made to reference an invalid object property. */ + KrkClass * nameError; /**< @exception NameError An attempt was made to reference an undeclared global variable. */ + KrkClass * importError; /**< @exception ImportError An error was encountered when attempting to import a module. */ + KrkClass * ioError; /**< @exception IOError An error was encountered in operating system's IO library. */ + KrkClass * valueError; /**< @exception ValueError The value of a parameter or variable is not valid. */ + KrkClass * keyboardInterrupt; /**< @exception KeyboardInterrupt An interrupt signal was received. */ + KrkClass * zeroDivisionError; /**< @exception ZeroDivisionError A mathematical function attempted to divide by zero. */ + KrkClass * notImplementedError; /**< @exception NotImplementedError The method is not implemented, either for the given arguments or in general. */ + KrkClass * syntaxError; /**< @exception SyntaxError The compiler encountered an unrecognized or invalid source code input. */ + KrkClass * assertionError; /**< @exception AssertionError An @c assert statement failed. */ +}; + +/** + * @brief Table of classes for built-in object types. + * + * For use by C modules and within the VM, an instance of this struct + * is attached to the global VM state. At VM initialization, each + * built-in class is attached to this table, and the class values + * stored here are used for integrated type checking with krk_isInstanceOf. + * + * @note As this and other tables are used directly by embedders, do not + * reorder the layout of the individual class pointers, even if + * it looks nicer. The ordering here is part of our library ABI. + */ +struct BaseClasses { + KrkClass * objectClass; /**< The base of all classes within the type tree. */ + KrkClass * moduleClass; /**< A class for representing imported modules, both managed and C. */ + KrkClass * typeClass; /**< Classes themselves are of this class. */ + KrkClass * intClass; /**< Primitive integer type. */ + KrkClass * floatClass; /**< Primitive double-precision floating-point type. */ + KrkClass * boolClass; /**< Primitive boolean type. */ + KrkClass * noneTypeClass; /**< The class of the None value. */ + KrkClass * strClass; /**< Built-in Unicode string type. */ + KrkClass * functionClass; /**< Represents a function object (KrkClosure) or native bind (KrkNative) */ + KrkClass * methodClass; /**< Represents a bound method (KrkBoundMethod) */ + KrkClass * tupleClass; /**< An immutable collection of arbitrary values. */ + KrkClass * bytesClass; /**< An immutable sequence of bytes. */ + KrkClass * listiteratorClass; /**< Iterator over lists */ + KrkClass * rangeClass; /**< An object representing a start and end point for a sequence of integers. */ + KrkClass * rangeiteratorClass; /**< Iterator over a range of values */ + KrkClass * striteratorClass; /**< Iterator over characters (by codepoint) in a string */ + KrkClass * tupleiteratorClass; /**< Iterator over values in a tuple */ + KrkClass * listClass; /**< Mutable collection of arbitrary values. */ + KrkClass * dictClass; /**< Mutable mapping of hashable keys to arbitrary values. */ + KrkClass * dictitemsClass; /**< Iterator over the (key,value) pairs of a dict */ + KrkClass * dictkeysClass; /**< Iterator over the keys of a dict */ + KrkClass * bytesiteratorClass; /**< Iterator over the integer byte values of a bytes object. */ + KrkClass * propertyClass; /**< Magic object that calls a function when accessed from an instance through the dot operator. */ + KrkClass * codeobjectClass; /**< Static compiled bytecode container (KrkCodeObject) */ + KrkClass * generatorClass; /**< Generator object. */ +}; + +/** + * @brief Execution state of a VM thread. + * + * Each thread in the VM has its own local thread state, which contains + * the thread's stack, stack pointer, call frame stack, a thread-specific + * VM flags bitarray, and an exception state. + * + * @see krk_currentThread + */ +typedef struct KrkThreadState { + struct KrkThreadState * next; /**< Invasive list pointer to next thread. */ + + KrkCallFrame * frames; /**< Call frame stack for this thread, max KRK_CALL_FRAMES_MAX */ + size_t frameCount; /**< Number of active call frames. */ + size_t stackSize; /**< Size of the allocated stack space for this thread. */ + KrkValue * stack; /**< Pointer to the bottom of the stack for this thread. */ + KrkValue * stackTop; /**< Pointer to the top of the stack. */ + KrkUpvalue * openUpvalues; /**< Flexible array of unclosed upvalues. */ + ssize_t exitOnFrame; /**< When called in a nested context, the frame offset to exit the VM dispatch loop on. */ + + KrkInstance * module; /**< The current module execution context. */ + KrkValue currentException; /**< When an exception is thrown, it is stored here. */ + int flags; /**< Thread-local VM flags; each thread inherits the low byte of the global VM flags. */ + long watchdog; /**< Decrementing watchdog timer for embedding. */ + + KrkValue scratchSpace[KRK_THREAD_SCRATCH_SIZE]; /**< A place to store a few values to keep them from being prematurely GC'd. */ +} KrkThreadState; + +/** + * @brief Global VM state. + * + * This state is shared by all VM threads and stores the + * path to the VM binary, global execution flags, the + * string and module tables, tables of builtin types, + * and the state of the (shared) garbage collector. + */ +typedef struct KrkVM { + int globalFlags; /**< Global VM state flags */ + char * binpath; /**< A string representing the name of the interpreter binary. */ + KrkTable strings; /**< Strings table */ + KrkTable modules; /**< Module cache */ + KrkInstance * builtins; /**< '\__builtins__' module */ + KrkInstance * system; /**< 'kuroko' module */ + KrkValue * specialMethodNames; /**< Cached strings of important method and function names */ + struct BaseClasses * baseClasses; /**< Pointer to a (static) namespacing struct for the KrkClass*'s of built-in object types */ + struct Exceptions * exceptions; /**< Pointer to a (static) namespacing struct for the KrkClass*'s of basic exception types */ + + /* Garbage collector state */ + KrkObj * objects; /**< Linked list of all objects in the GC */ + size_t bytesAllocated; /**< Running total of bytes allocated */ + size_t nextGC; /**< Point at which we should sweep again */ + size_t grayCount; /**< Count of objects marked by scan. */ + size_t grayCapacity; /**< How many objects we can fit in the scan list. */ + KrkObj** grayStack; /**< Scan list */ + + KrkThreadState * threads; /**< Invasive linked list of all VM threads. */ +} KrkVM; + +/* Thread-specific flags */ +#define KRK_THREAD_ENABLE_TRACING (1 << 0) +#define KRK_THREAD_ENABLE_DISASSEMBLY (1 << 1) +#define KRK_THREAD_ENABLE_SCAN_TRACING (1 << 2) +#define KRK_THREAD_HAS_EXCEPTION (1 << 3) +#define KRK_THREAD_SINGLE_STEP (1 << 4) +#define KRK_THREAD_SIGNALLED (1 << 5) + +/* Global flags */ +#define KRK_GLOBAL_ENABLE_STRESS_GC (1 << 8) +#define KRK_GLOBAL_GC_PAUSED (1 << 9) +#define KRK_GLOBAL_CLEAN_OUTPUT (1 << 10) + +#ifdef ENABLE_THREADING +# define threadLocal __thread +#else +# define threadLocal +#endif + +/** + * @brief Thread-local VM state. + * + * See @c KrkThreadState for more information. + */ +#if defined(_WIN32) && !defined(KRKINLIB) +#define krk_currentThread (*krk_getCurrentThread()) +#else +extern threadLocal KrkThreadState krk_currentThread; +#endif + +/** + * @brief Singleton instance of the shared VM state. + */ +extern KrkVM krk_vm; + +/** + * @def vm + * @brief Convenience macro for namespacing. + */ +#define vm krk_vm + +/** + * @brief Initialize the VM at program startup. + * @memberof KrkVM + * + * All library users must call this exactly once on startup to create + * the built-in types, modules, and functions for the VM and prepare + * the string and module tables. Optionally, callers may set `vm.binpath` + * before calling krk_initVM to allow the VM to locate the interpreter + * binary and establish the default module paths. + * + * @param flags Combination of global VM flags and initial thread flags. + */ +extern void krk_initVM(int flags); + +/** + * @brief Release resources from the VM. + * @memberof KrkVM + * + * Generally, it is desirable to call this once before the hosting program exits. + * If a fresh VM state is needed, krk_freeVM should be called before a further + * call to krk_initVM is made. The resources released here can include allocated + * heap memory, FILE pointers or descriptors, or various other things which were + * initialized by C extension modules. + */ +extern void krk_freeVM(void); + +/** + * @brief Reset the current thread's stack state to the top level. + * + * In a repl, this should be called before or after each iteration to clean up any + * remnant stack entries from an uncaught exception. It should not be called + * during normal execution by C extensions. Values on the stack may be lost + * to garbage collection after a call to @c krk_resetStack . + */ +extern void krk_resetStack(void); + +/** + * @brief Compile and execute a source code input. + * + * This is the lowest level call for most usecases, including execution + * of commands from a REPL or when executing a file. + * + * @param src Source code to compile and run. + * @param fromFile Path to the source file, or a representative string like "". + * @return The value of the executed code, which is either the value of an explicit 'return' + * statement, or the last expression value from an executed statement. If an uncaught + * exception occurred, this will be @c None and @c krk_currentThread.flags should + * indicate @c KRK_THREAD_HAS_EXCEPTION and @c krk_currentThread.currentException + * should contain the raised exception value. + */ +extern KrkValue krk_interpret(const char * src, char * fromFile); + +/** + * @brief Load and run a source file and return when execution completes. + * + * Loads and runs a source file. Can be used by interpreters to run scripts, + * either in the context of a new a module or as if they were continuations + * of the current module state (eg. as if they were lines entered on a repl) + * + * @param fileName Path to the source file to read and execute. + * @param fromFile Value to assign to @c \__file__ + * @return As with @c krk_interpret, an object representing the newly created module, + * or the final return value of the VM execution. + */ +extern KrkValue krk_runfile(const char * fileName, char * fromFile); + +/** + * @brief Load and run a file as a module. + * + * Similar to @c krk_runfile but ensures that execution of the VM returns to the caller + * after the file is run. This should be run after calling @c krk_startModule to initialize + * a new module context and is used internally by the import mechanism. + * + * @param fileName Path to the source file to read and execute. + * @param fromFile Value to assign to @c \__file__ + * @return The object representing the module, or None if execution of the file failed. + */ +extern KrkValue krk_callfile(const char * fileName, char * fromFile); + +/** + * @brief Push a stack value. + * + * Pushes a value onto the current thread's stack, triggering a + * stack resize if there is not enough space to hold the new value. + * + * @param value Value to push. + */ +extern void krk_push(KrkValue value); + +/** + * @brief Pop the top of the stack. + * + * Removes and returns the value at the top of current thread's stack. + * Generally, it is preferably to leave values on the stack and use + * krk_peek if the value is desired, as removing a value from the stack + * may result in it being garbage collected. + * + * @return The value previously at the top of the stack. + */ +extern KrkValue krk_pop(void); + +/** + * @brief Peek down from the top of the stack. + * + * Obtains a value from the current thread's stack without modifying the stack. + * + * @param distance How far down from the top of the stack to peek (0 = the top) + * @return The value from the stack. + */ +extern KrkValue krk_peek(int distance); + +/** + * @brief Swap the top of the stack of the value @p distance slots down. + * + * Exchanges the values at the top of the stack and @p distance slots from the top + * without removing or shuffling anything in between. + * + * @param distance How from down from the top of the stack to swap (0 = the top) + */ +extern void krk_swap(int distance); + +/** + * @brief Get the name of the type of a value. + * @memberof KrkValue + * + * Obtains the C string representing the name of the class + * associated with the given value. Useful for crafting + * exception messages, such as those describing TypeErrors. + * + * @param value Value to examine + * @return Nul-terminated C string of the type of @p value + */ +extern const char * krk_typeName(KrkValue value); + +/** + * @brief Attach a native C function to an attribute table. + * @memberof KrkTable + * + * Attaches the given native function pointer to an attribute table + * while managing the stack shuffling and boxing of both the name and + * the function object. If @p name begins with a '.', the native function + * is marked as a method. If @p name begins with a ':', the native function + * is marked as a dynamic property. + * + * @param table Attribute table to attach to, such as @c &someInstance->fields + * @param name Nil-terminated C string with the name to assign + * @param function Native function pointer to attach + * @return A pointer to the object representing the attached function. + */ +extern KrkNative * krk_defineNative(KrkTable * table, const char * name, NativeFn function); + +/** + * @brief Attach a native dynamic property to an attribute table. + * @memberof KrkTable + * + * Mostly the same as @c krk_defineNative, but ensures the creation of a dynamic property. + * The intention of this function is to replace uses of defineNative with ":" names, + * and replace specialized methods with @c KrkProperty* objects. + * + * @param table Attribute table to attach to, such as @c &someInstance->fields + * @param name Nil-terminated C string with the name to assign + * @param func Native function pointer to attach + * @return A pointer to the property object created. + */ +extern KrkNative * krk_defineNativeProperty(KrkTable * table, const char * name, NativeFn func); + +/** + * @brief Attach a value to an attribute table. + * @memberof KrkTable + * + * Manages the stack shuffling and boxing of the name string when attaching + * a value to an attribute table. Rather than using @c krk_tableSet, this is + * the preferred method of supplying fields to objects from C code. + * + * @param table Attribute table to attach to, such as @c &someInstance->fields + * @param name Nil-terminated C string with the name to assign + * @param obj Value to attach. + */ +extern void krk_attachNamedValue(KrkTable * table, const char name[], KrkValue obj); + +/** + * @brief Attach an object to an attribute table. + * @memberof KrkTable + * + * Manages the stack shuffling and boxing of the name string when attaching + * an object to an attribute table. Rather than using @c krk_tableSet, this is + * the preferred method of supplying fields to objects from C code. + * + * This is a convenience wrapper around @c krk_attachNamedValue. + * + * @param table Attribute table to attach to, such as @c &someInstance->fields + * @param name Nil-terminated C string with the name to assign + * @param obj Object to attach. + */ +extern void krk_attachNamedObject(KrkTable * table, const char name[], KrkObj * obj); + +/** + * @brief Raise an exception. + * + * Creates an instance of the given exception type, passing a formatted + * string to the initializer. All of the core exception types take an option + * string value to attach to the exception, but third-party exception types + * may have different initializer signatures and need separate initialization. + * + * The created exception object is attached to the current thread state and + * the @c KRK_THREAD_HAS_EXCEPTION flag is set. + * + * @param type Class pointer for the exception type, eg. @c vm.exceptions->valueError + * @param fmt Format string. + * @return As a convenience to C extension authors, returns @c None + */ +extern KrkValue krk_runtimeError(KrkClass * type, const char * fmt, ...); + +/** + * @brief Get a pointer to the current thread state. + * + * Generally equivalent to @c &krk_currentThread, though @c krk_currentThread + * itself may be implemented as a macro that calls this function depending + * on the platform's thread support. + * + * @return Pointer to current thread's thread state. + */ +extern KrkThreadState * krk_getCurrentThread(void); + +/** + * @brief Continue VM execution until the next exit trigger. + * + * Resumes the VM dispatch loop, returning to the caller when + * the next exit trigger event happens. Generally, callers will + * want to set the current thread's exitOnFrame before calling + * @c krk_runNext. Alternatively, see @c krk_callValue which manages + * exit triggers automatically when calling function objects. + * + * @return Value returned by the exit trigger, generally the value + * returned by the inner function before the VM returned + * to the exit frame. + */ +extern KrkValue krk_runNext(void); + +/** + * @brief Get the class representing a value. + * @memberof KrkValue + * + * Returns the class object representing the type of a value. + * This may be the direct class of an instance, or a pseudoclass + * for other value types. + * + * @param value Reference value to examine. + * @return A pointer to the value's type's class object. + */ +extern KrkClass * krk_getType(KrkValue value); + +/** + * @brief Determine if a class is an instance or subclass of a given type. + * @memberof KrkValue + * + * Searches the class hierarchy of the given value to determine if it is + * a subtype of @p type. As this chains through the inheritence tree, the + * more deeply subclassed @p obj is, the longer it may take to determine + * if it is a subtype of @p type. All types should eventually be subtypes + * of the @ref object type, so this condition should not be checked. For + * some types, convenience macros are available. If you need to check if + * @p obj is a specific type, exclusive of subtypes, look at + * @c krk_getType() instead of using this function. + * + * @param obj Value to examine. + * @param type Class object to test for membership of. + * @return 1 if @p obj is an instance of @p type or of a subclass of @p type + */ +extern int krk_isInstanceOf(KrkValue obj, KrkClass * type); + +/** + * @brief Perform method binding on the stack. + * @memberof KrkClass + * + * Performs attribute lookup from the class @p _class for @p name. + * If @p name is not a valid method, the binding fails. + * If @p name is a valid method, the method will be retrieved and + * bound to the instance on the top of the stack, replacing it + * with a @ref BoundMethod object. + * + * @param _class Class object to resolve methods from. + * @param name String object with the name of the method to resolve. + * @return 1 if the method has been bound, 0 if binding failed. + */ +extern int krk_bindMethod(KrkClass * _class, KrkString * name); + +/** + * @brief Call a callable value in the current stack context. + * @memberof KrkValue + * + * Executes the given callable object (function, bound method, object + * with a @c \__call__() method, etc.) using @p argCount arguments from the stack. + * + * @param callee Value referencing a callable object. + * @param argCount Arguments to retreive from stack. + * @param extra Whether extra arguments below argCount should be + * considered as part of this call frame. Generally, + * when this is 1, the value below the arguments is + * the callable object. Most library users will want + * to leave this as 0 when calling normal functions, + * bound method objects, or ubound methods when the + * instance is included in the arguments already. + * @return An indicator of how the result should be obtained: + * 1: The VM must be resumed to run managed code. + * 2: The callable was a native function and result should be popped now. + * Else: The call failed. An exception may have already been set. + */ +extern int krk_callValue(KrkValue callee, int argCount, int extra); + +/** + * @brief Create a list object. + * @memberof KrkList + * + * This is the native function bound to @c listOf + */ +extern KrkValue krk_list_of(int argc, KrkValue argv[], int hasKw); + +/** + * @brief Create a dict object. + * @memberof KrkDict + * + * This is the native function bound to @c dictOf + */ +extern KrkValue krk_dict_of(int argc, KrkValue argv[], int hasKw); + +/** + * @brief Create a tuple object. + * @memberof KrkTuple + * + * This is the native function bound to @c tupleOf + */ +extern KrkValue krk_tuple_of(int argc, KrkValue argv[], int hasKw); + +/** + * @brief Create a set object. + * @memberof Set + * + * This is the native function bound to @c setOf + */ +extern KrkValue krk_set_of(int argc, KrkValue argv[], int hasKw); + +/** + * @brief Call a callable and manage VM state to obtain the return value. + * @memberof KrkValue + * + * This is a wrapper around various mechanisms including krk_callValue + * intended for use by C extensions to call arbitrary functions without + * knowledge of their implementation details. See the notes for + * @c krk_callValue 's @c extra paramater for details on how @p isMethod is used. + * + * @param value Callable object reference. + * @param argCount Arguments to collect from the stack. + * @param isMethod This should almost always be 0. + * @return The return value of the function. + */ +extern KrkValue krk_callSimple(KrkValue value, int argCount, int isMethod); + +/** + * @brief Convenience function for creating new types. + * @memberof KrkClass + * + * Creates a class object, output to @p _class, setting its name to @p name, inheriting + * from @p base, and attaching it with its name to the fields table of the given @p module. + * + * @param module Pointer to an instance for a module to attach to, or @c NULL to skip attaching. + * @param _class Output pointer to assign the new class object to. + * @param name Name of the new class. + * @param base Pointer to class object to inherit from. + * @return A pointer to the class object, equivalent to the value assigned to @p _class. + */ +extern KrkClass * krk_makeClass(KrkInstance * module, KrkClass ** _class, const char * name, KrkClass * base); + +/** + * @brief Finalize a class by collecting pointers to core methods. + * @memberof KrkClass + * + * Scans through the methods table of a class object to find special + * methods and assign them to the class object's pointer table so they + * can be referenced directly without performing hash lookups. + * + * @param _class Class object to finalize. + */ +extern void krk_finalizeClass(KrkClass * _class); + +/** + * @brief If there is an active exception, print a traceback to @c stderr + * + * This function is exposed as a convenience for repl developers. Normally, + * the VM will call @c krk_dumpTraceback() itself if an exception is unhandled and no + * exit trigger is current set. The traceback is obtained from the exception + * object. If the exception object does not have a traceback, only the + * exception itself will be printed. The traceback printer will attempt to + * open source files to print faulting lines and may call into the VM if the + * exception object has a managed implementation of @c \__str__. + */ +extern void krk_dumpTraceback(); + +/** + * @brief Set up a new module object in the current thread. + * + * Creates a new instance of the module type and attaches a @c \__builtins__ + * reference to its fields. The module becomes the current thread's + * main module, but is not directly attached to the module table. + * + * @param name Name of the module, which is assigned to @c \__name__ + * @return The instance object representing the module. + */ +extern KrkInstance * krk_startModule(const char * name); + +/** + * @brief Obtain a list of properties for an object. + * + * This is the native function bound to @c object.__dir__ + */ +extern KrkValue krk_dirObject(int argc, KrkValue argv[], int hasKw); + +/** + * @brief Load a module from a file with a specified name. + * + * This is generally called by the import mechanisms to load a single module and + * will establish a module context internally to load the new module into, return + * a KrkValue representing that module context through the @p moduleOut parameter. + * + * @param path Dotted path of the module, used for file lookup. + * @param moduleOut Receives a value with the module object. + * @param runAs Name to attach to @c \__name__ for this module, different from @p path + * @return 1 if the module was loaded, 0 if an @ref ImportError occurred. + */ +extern int krk_loadModule(KrkString * path, KrkValue * moduleOut, KrkString * runAs); + +/** + * @brief Load a module by a dotted name. + * + * Given a package identifier, attempt to the load module into the module table. + * This is a thin wrapper around `krk_importModule()`. + * + * @param name String object of the dot-separated package path to import. + * @return 1 if the module was loaded, 0 if an @ref ImportError occurred. + */ +extern int krk_doRecursiveModuleLoad(KrkString * name); + +/** + * @brief Load the dotted name @p name with the final element as @p runAs + * + * If @p name was imported previously with a name different from @p runAs, + * it will be imported again with the new name; this may result in + * unexpected behaviour. Generally, @p runAs is used to specify that the + * module should be run as `__main__`. + * + * @param name Dotted path name of a module. + * @param runAs Alternative name to attach to @c \__name__ for the module. + * @return 1 on success, 0 on failure. + */ +extern int krk_importModule(KrkString * name, KrkString * runAs); + +/** + * @brief Determine the truth of a value. + * @memberof KrkValue + * + * Determines if a value represents a "falsey" value. + * Empty collections, 0-length strings, False, numeric 0, + * None, etc. are "falsey". Other values are generally "truthy". + * + * @param value Value to examine. + * @return 1 if falsey, 0 if truthy + */ +extern int krk_isFalsey(KrkValue value); + +/** + * @brief Obtain a property of an object by name. + * @memberof KrkValue + * + * This is a convenience function that works in essentially the + * same way as the OP_GET_PROPERTY instruction. + * + * @param value Value to examine. + * @param name C-string of the property name to query. + * @return The requested property, or None with an @ref AttributeError + * exception set in the current thread if the attribute was + * not found. + */ +extern KrkValue krk_valueGetAttribute(KrkValue value, char * name); + +/** + * @brief See @ref krk_valueGetAttribute + */ +extern KrkValue krk_valueGetAttribute_default(KrkValue value, char * name, KrkValue defaultVal); + +/** + * @brief Set a property of an object by name. + * @memberof KrkValue + * + * This is a convenience function that works in essentially the + * same way as the OP_SET_PROPERTY instruction. + * + * @param owner The owner of the property to modify. + * @param name C-string of the property name to modify. + * @param to The value to assign. + * @return The set value, or None with an @ref AttributeError + * exception set in the current thread if the object can + * not have a property set. + */ +extern KrkValue krk_valueSetAttribute(KrkValue owner, char * name, KrkValue to); + +/** + * @brief Concatenate two strings. + * + * This is a convenience function which calls @c str.__add__ on the top stack + * values. Generally, this should be avoided - use @c StringBuilder instead. + */ +extern void krk_addObjects(void); + +/* + * All of the remaining stuff is internal and shouldn't be used by library users or embedders. + * FIXME This stuff needs to be moved to another header! FIXME + */ + +extern KrkValue _str___getitem__(int argc, KrkValue argv[], int hasKw); +#define krk_string_get _str___getitem__ +extern KrkValue _str___int__(int argc, KrkValue argv[], int hasKw); +#define krk_string_int _str___int__ +extern KrkValue _str___float__(int argc, KrkValue argv[], int hasKw); +#define krk_string_float _str___float__ +extern KrkValue _str_split(int argc, KrkValue argv[], int hasKw); +#define krk_string_split _str_split +extern KrkValue _str_format(int argc, KrkValue argv[], int hasKw); +#define krk_string_format _str_format + +extern KrkValue krk_dict_nth_key_fast(size_t capacity, KrkTableEntry * entries, size_t index); + +extern void _createAndBind_numericClasses(void); +extern void _createAndBind_strClass(void); +extern void _createAndBind_listClass(void); +extern void _createAndBind_tupleClass(void); +extern void _createAndBind_bytesClass(void); +extern void _createAndBind_dictClass(void); +extern void _createAndBind_functionClass(void); +extern void _createAndBind_rangeClass(void); +extern void _createAndBind_setClass(void); +extern void _createAndBind_builtins(void); +extern void _createAndBind_type(void); +extern void _createAndBind_exceptions(void); +extern void _createAndBind_gcMod(void); +extern void _createAndBind_timeMod(void); +extern void _createAndBind_osMod(void); +extern void _createAndBind_fileioMod(void); +#ifdef ENABLE_THREADING +extern void _createAndBind_threadsMod(void); +#endif + +extern KrkValue krk_operator_lt(KrkValue,KrkValue); +extern KrkValue krk_operator_gt(KrkValue,KrkValue); + +extern void _createAndBind_generatorClass(void); +extern KrkInstance * krk_buildGenerator(KrkClosure *, KrkValue *, size_t); diff --git a/kuroko b/kuroko index 34c8d687..ca7d0dae 160000 --- a/kuroko +++ b/kuroko @@ -1 +1 @@ -Subproject commit 34c8d68720ea5ee736b49094b8b1836a267604a9 +Subproject commit ca7d0dae2ec78a7746b49ecaae1c8836488a811b diff --git a/lib/kuroko/_yutani.c b/lib/kuroko/_yutani.c index cf5782dc..629aba5a 100644 --- a/lib/kuroko/_yutani.c +++ b/lib/kuroko/_yutani.c @@ -1186,7 +1186,7 @@ KrkValue krk_module_onload__yutani(void) { Message->allocSize = sizeof(struct MessageClass); Message->_ongcsweep = _message_sweep; /* All the MSG_ constants */ -#define TYPE(type) krk_attachNamedValue(&Message->fields, "MSG_" #type, INTEGER_VAL(YUTANI_MSG_ ## type)) +#define TYPE(type) krk_attachNamedValue(&Message->methods, "MSG_" #type, INTEGER_VAL(YUTANI_MSG_ ## type)) TYPE(HELLO); TYPE(WINDOW_NEW); TYPE(FLIP); TYPE(KEY_EVENT); TYPE(MOUSE_EVENT); TYPE(WINDOW_MOVE); TYPE(WINDOW_CLOSE); TYPE(WINDOW_SHOW); TYPE(WINDOW_HIDE); TYPE(WINDOW_STACK); TYPE(WINDOW_FOCUS_CHANGE); TYPE(WINDOW_MOUSE_EVENT); @@ -1347,7 +1347,7 @@ KrkValue krk_module_onload__yutani(void) { " Calculate the rendered width of the given string when drawn with this font."; krk_defineNative(&YutaniFont->methods, ":size", _font_size); /* Some static values */ -#define ATTACH_FONT(name) krk_attachNamedValue(&YutaniFont->fields, #name, INTEGER_VAL(SDF_ ## name)) +#define ATTACH_FONT(name) krk_attachNamedValue(&YutaniFont->methods, #name, INTEGER_VAL(SDF_ ## name)) ATTACH_FONT(FONT_THIN); ATTACH_FONT(FONT_BOLD); ATTACH_FONT(FONT_MONO); @@ -1387,11 +1387,11 @@ KrkValue krk_module_onload__yutani(void) { krk_finalizeClass(MenuEntrySeparatorClass); Decorator = krk_createClass(module, "Decorator", NULL); - krk_defineNative(&Decorator->fields, "get_bounds", _decor_get_bounds); - krk_defineNative(&Decorator->fields, "render", _decor_render); - krk_defineNative(&Decorator->fields, "handle_event", _decor_handle_event); - krk_defineNative(&Decorator->fields, "show_default_menu", _decor_show_default_menu); -#define ATTACH_CONSTANT(name) krk_attachNamedValue(&Decorator->fields, #name, INTEGER_VAL(name)) + krk_defineNative(&Decorator->methods, "get_bounds", _decor_get_bounds); + krk_defineNative(&Decorator->methods, "render", _decor_render); + krk_defineNative(&Decorator->methods, "handle_event", _decor_handle_event); + krk_defineNative(&Decorator->methods, "show_default_menu", _decor_show_default_menu); +#define ATTACH_CONSTANT(name) krk_attachNamedValue(&Decorator->methods, #name, INTEGER_VAL(name)) ATTACH_CONSTANT(DECOR_OTHER); ATTACH_CONSTANT(DECOR_CLOSE); ATTACH_CONSTANT(DECOR_RESIZE); diff --git a/util/auto-dep.py b/util/auto-dep.py index d0477679..f82f3cba 100755 --- a/util/auto-dep.py +++ b/util/auto-dep.py @@ -37,6 +37,7 @@ class Classifier(object): '': (None, '-ltoaru_button', ['','', '']), # Kuroko '': ('../../../kuroko/src', '-lkuroko', []), + '': (None, '-lkuroko', []), # OPTIONAL third-party libraries, for extensions / ports '': ('freetype2', '-lfreetype', []), '': ('pixman-1', '-lpixman-1', []),