diff --git a/cli/internal.h b/cli/internal.h index c802ddb..2cce2a9 100644 --- a/cli/internal.h +++ b/cli/internal.h @@ -18,6 +18,16 @@ #include "modules/common.h" +#if defined(__GNUC__) + #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + #pragma GCC diagnostic ignored "-Wunused-parameter" +#elif defined(__clang__) + #pragma clang diagnostic ignored "-Wint-to-pointer-cast" + #pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(_MSC_VER) + #pragma warning(disable:26812) +#endif + #define CLI_NOTICE \ "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ "Copyright (c) 2020 - 2021 ThakeeNathees\n" \ diff --git a/cli/main.c b/cli/main.c index 09ff9e4..17beff9 100644 --- a/cli/main.c +++ b/cli/main.c @@ -21,32 +21,15 @@ void onResultDone(PKVM* vm, PkStringPtr result) { } } -void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line, - const char* message) { - - VmUserData* ud = (VmUserData*)pkGetUserData(vm); - bool repl = (ud) ? ud->repl_mode : false; - - if (type == PK_ERROR_COMPILE) { - - if (repl) fprintf(stderr, "Error: %s\n", message); - else fprintf(stderr, "Error: %s\n at \"%s\":%i\n", - message, file, line); - - } else if (type == PK_ERROR_RUNTIME) { - fprintf(stderr, "Error: %s\n", message); - - } else if (type == PK_ERROR_STACKTRACE) { - if (repl) fprintf(stderr, " %s() [line:%i]\n", message, line); - else fprintf(stderr, " %s() [\"%s\":%i]\n", message, file, line); - } +void stderrWrite(PKVM* vm, const char* text) { + fprintf(stderr, "%s", text); } -void writeFunction(PKVM* vm, const char* text) { +void stdoutWrite(PKVM* vm, const char* text) { fprintf(stdout, "%s", text); } -PkStringPtr readFunction(PKVM* vm) { +PkStringPtr stdinRead(PKVM* vm) { PkStringPtr result; result.string = read_line(NULL); result.on_done = onResultDone; @@ -110,10 +93,14 @@ PkStringPtr loadScript(PKVM* vm, const char* path) { // Read source to buffer. char* buff = (char*)malloc((size_t)(file_size) + 1); + // Using read instead of file_size is because "\r\n" is read as '\n' in // windows. - size_t read = fread(buff, sizeof(char), file_size, file); - buff[read] = '\0'; + if (buff != NULL) { + size_t read = fread(buff, sizeof(char), file_size, file); + buff[read] = '\0'; + } + fclose(file); result.string = buff; @@ -122,16 +109,38 @@ PkStringPtr loadScript(PKVM* vm, const char* path) { return result; } +// FIXME: +// Included for isatty(). This should be moved to somewhere. and I'm not sure +// about the portability of these headers. and isatty() function. +#ifdef _WIN32 + #include + #include +#else + #include +#endif + // Create new pocket VM and set it's configuration. static PKVM* intializePocketVM() { + PkConfiguration config = pkNewConfiguration(); - config.error_fn = errorFunction; - config.write_fn = writeFunction; - config.read_fn = readFunction; + config.stderr_write = stderrWrite; + config.stdout_write = stdoutWrite; + config.stdin_read = stdinRead; config.load_script_fn = loadScript; config.resolve_path_fn = resolvePath; +// FIXME: + if (!!isatty(fileno(stderr))) { +#ifdef _WIN32 + DWORD outmode = 0; + HANDLE handle = GetStdHandle(STD_ERROR_HANDLE); + GetConsoleMode(handle, &outmode); + SetConsoleMode(handle, outmode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); +#endif + config.use_ansi_color = true; + } + return pkNewVM(&config); } diff --git a/docs/wasm/io_api.js b/docs/wasm/io_api.js index c0d8569..0624627 100644 --- a/docs/wasm/io_api.js +++ b/docs/wasm/io_api.js @@ -10,21 +10,13 @@ mergeInto(LibraryManager.library, { /** js_func_name : function() {...} */ - js_errorPrint : function(type, line, message) { - var err_text = '' - const msg = AsciiToString(message); - if (type == 0 /*PK_ERROR_COMPILE*/) { - err_text = `[Error at:${line}] ${msg}`; - } else if (type == 1 /*PK_ERROR_RUNTIME*/) { - err_text = `Error: ${msg}`; - } else if (type == 2 /*PK_ERROR_STACKTRACE*/) { - err_text = ` [at:${line}] ${msg}`; - } - + js_errorPrint : function(message) { var out = document.getElementById("code-output"); // To Indicate error (should be removed before each run request). out.classList.add("has-error"); - out.innerHTML += `${err_text}\n`; + + let msg = AsciiToString(message); + out.innerHTML += `${msg}`; }, js_writeFunction : function(message) { diff --git a/docs/wasm/main.c b/docs/wasm/main.c index 430a037..f91dc75 100644 --- a/docs/wasm/main.c +++ b/docs/wasm/main.c @@ -8,14 +8,13 @@ #include // IO api functions. -extern void js_errorPrint(int type, int line, const char* message); +extern void js_errorPrint(const char* message); extern void js_writeFunction(const char* message); -void errorPrint(PKVM* vm, PkErrorType type, const char* file, int line, - const char* message) { +void errorPrint(PKVM* vm, const char* message) { // No need to pass the file (since there is only script that'll ever run on // the browser. - js_errorPrint((int)type, line, message); + js_errorPrint(message); } void writeFunction(PKVM* vm, const char* text) { @@ -26,16 +25,39 @@ EMSCRIPTEN_KEEPALIVE int runSource(const char* source) { PkConfiguration config = pkNewConfiguration(); - config.error_fn = errorPrint; - config.write_fn = writeFunction; + config.stderr_write = errorPrint; + config.stdout_write = writeFunction; config.load_script_fn = NULL; config.resolve_path_fn = NULL; PKVM* vm = pkNewVM(&config); - PkStringPtr src = { source, NULL, NULL }; - PkStringPtr module = { "@(TRY)", NULL, NULL }; - PkResult result = pkInterpretSource(vm, src, module, NULL); + // FIXME: + // The bellow blocks of code can be simplified with + // + // PkResult result = pkInterpretSource(vm, src, path, NULL); + // + // But the path is printed on the error messages as "@(TRY)" + // If we create a module and compile it, then it won't have a + // path and the error message is simplified. This behavior needs + // to be changed in the error report function. + + PkResult result; + + PkHandle* module = pkNewModule(vm, "@(TRY)"); + do { + PkStringPtr src = { .string = source }; + result = pkCompileModule(vm, module, src, NULL); + if (result != PK_RESULT_SUCCESS) break; + + PkHandle* _main = pkModuleGetMainFunction(vm, module); + PkHandle* fiber = pkNewFiber(vm, _main); + result = pkRunFiber(vm, fiber, 0, NULL); + pkReleaseHandle(vm, _main); + pkReleaseHandle(vm, fiber); + + } while (false); + pkReleaseHandle(vm, module); pkFreeVM(vm); diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 10690df..0b27742 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -105,7 +105,7 @@ typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type, const char* file, int line, const char* message); -// A function callback to write [text] to stdout. +// Function callback to write [text] to stdout or stderr. typedef void (*pkWriteFn) (PKVM* vm, const char* text); // A function callback to read a line from stdin. The returned string shouldn't @@ -159,14 +159,6 @@ enum PkVarType { PK_INSTANCE, }; -// Type of the error message that pocketlang will provide with the pkErrorFn -// callback. -enum PkErrorType { - PK_ERROR_COMPILE = 0, // Compile time errors. - PK_ERROR_RUNTIME, // Runtime error message. - PK_ERROR_STACKTRACE, // One entry of a runtime error stack. -}; - // Result that pocketlang will return after a compilation or running a script // or a function or evaluating an expression. enum PkResult { @@ -203,13 +195,16 @@ struct PkConfiguration { // pointer is NULL it defaults to the VM's realloc(), free() wrappers. pkReallocFn realloc_fn; - pkErrorFn error_fn; - pkWriteFn write_fn; - pkReadFn read_fn; + pkWriteFn stderr_write; + pkWriteFn stdout_write; + pkReadFn stdin_read; pkResolvePathFn resolve_path_fn; pkLoadScriptFn load_script_fn; + // If true stderr calls will use ansi color codes. + bool use_ansi_color; + // User defined data associated with VM. void* user_data; }; diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 4b38e0f..2feca34 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -297,12 +297,8 @@ typedef struct sForwardName { // The function where the name is used, and the instruction is belongs to. Fn* func; - // The name string's pointer in the source. - const char* name; - int length; - - // Line number of the name used (required for error message). - int line; + // Name token that was lexed for this name. + Token tkname; } ForwardName; @@ -412,9 +408,15 @@ typedef struct sParser { bool repl_mode; bool parsing_class; - bool has_errors; bool need_more_lines; //< True if we need more lines in REPL mode. + // [has_errors] is for all kinds of errors, If it's set we don't terminate + // the compilation since we can cascade more errors by continuing. But + // [has_syntax_error] will set to true if we encounter one and this will + // terminatie the compilation. + bool has_syntax_error; + bool has_errors; + } Parser; struct Compiler { @@ -509,7 +511,10 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler, parser->current_char = parser->source; parser->current_line = 1; + parser->previous.type = TK_ERROR; + parser->current.type = TK_ERROR; parser->next.type = TK_ERROR; + parser->next.start = NULL; parser->next.length = 0; parser->next.line = 1; @@ -524,6 +529,7 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler, parser->repl_mode = !!(compiler->options && compiler->options->repl_mode); parser->parsing_class = false; parser->has_errors = false; + parser->has_syntax_error = false; parser->need_more_lines = false; } @@ -543,7 +549,14 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, compiler->new_local = false; compiler->is_last_call = false; - parserInit(&compiler->parser, vm, compiler, source, module->path->data); + const char* source_path = "@??"; + if (module->path != NULL) { + source_path = module->path->data; + } else if (options->repl_mode) { + source_path = "@REPL"; + } + + parserInit(&compiler->parser, vm, compiler, source, source_path); // Cache the required built functions. compiler->bifn_list_join = findBuiltinFunction(vm, "list_join", 9); @@ -555,16 +568,14 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, /*****************************************************************************/ // Internal error report function for lexing and parsing. -static void reportError(Parser* parser, const char* file, int line, +static void reportError(Parser* parser, Token tk, const char* fmt, va_list args) { - // On REPL mode only the first error is reported. - if (parser->repl_mode && parser->has_errors) { - return; - } - parser->has_errors = true; + PKVM* vm = parser->vm; + if (vm->config.stderr_write == NULL) return; + // If the source is incomplete we're not printing an error message, // instead return PK_RESULT_UNEXPECTED_EOF to the host. if (parser->need_more_lines) { @@ -572,51 +583,47 @@ static void reportError(Parser* parser, const char* file, int line, return; } - if (parser->vm->config.error_fn == NULL) return; + reportCompileTimeError(vm, parser->file_path, tk.line, parser->source, + tk.start, tk.length, fmt, args); - // TODO: fix the buffer size. A non terminated large string could cause this - // crash. - - char message[ERROR_MESSAGE_SIZE]; - int length = vsnprintf(message, sizeof(message), fmt, args); - __ASSERT(length >= 0, "Error message buffer failed at vsnprintf()."); - parser->vm->config.error_fn(parser->vm, PK_ERROR_COMPILE, - file, line, message); -} - -// Error caused at the middle of lexing (and TK_ERROR will be lexed instead). -static void lexError(Parser* parser, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - reportError(parser, parser->file_path, parser->current_line, fmt, args); - va_end(args); } // Error caused when parsing. The associated token assumed to be last consumed // which is [parser->previous]. -static void parseError(Compiler* compiler, const char* fmt, ...) { +static void syntaxError(Compiler* compiler, Token tk, const char* fmt, ...) { + Parser* parser = &compiler->parser; - Token* token = &(compiler->parser.previous); + // Only one syntax error is reported. + if (parser->has_syntax_error) return; - // Lex errors would reported earlier by lexError and lexed a TK_ERROR token. - if (token->type == TK_ERROR) return; + parser->has_syntax_error = true; + va_list args; + va_start(args, fmt); + reportError(parser, tk, fmt, args); + va_end(args); +} + +static void semanticError(Compiler* compiler, Token tk, const char* fmt, ...) { + Parser* parser = &compiler->parser; + + // If the parser has synax errors, semantic errors are not reported. + if (parser->has_syntax_error) return; va_list args; va_start(args, fmt); - reportError(&(compiler->parser), compiler->parser.file_path, - token->line, fmt, args); + reportError(parser, tk, fmt, args); va_end(args); } // Error caused when trying to resolve forward names (maybe more in the // future), Which will be called once after compiling the module and thus we // need to pass the line number the error originated from. -static void resolveError(Compiler* compiler, int line, const char* fmt, ...) { +static void resolveError(Compiler* compiler, Token tk, const char* fmt, ...) { + Parser* parser = &compiler->parser; + va_list args; va_start(args, fmt); - reportError(&(compiler->parser), - compiler->parser.file_path, - line, fmt, args); + reportError(parser, tk, fmt, args); va_end(args); } @@ -625,8 +632,8 @@ static void resolveError(Compiler* compiler, int line, const char* fmt, ...) { static void checkMaxConstantsReached(Compiler* compiler, int index) { ASSERT(index >= 0, OOPS); if (index >= MAX_CONSTANTS) { - parseError(compiler, "A module should contain at most %d " - "unique constants.", MAX_CONSTANTS); + semanticError(compiler, compiler->parser.previous, + "A module should contain at most %d unique constants.", MAX_CONSTANTS); } } @@ -636,6 +643,7 @@ static void checkMaxConstantsReached(Compiler* compiler, int index) { // Forward declaration of lexer methods. +static Token makeErrToken(Parser* parser); static char peekChar(Parser* parser); static char peekNextChar(Parser* parser); static char eatChar(Parser* parser); @@ -643,7 +651,9 @@ static void setNextValueToken(Parser* parser, TokenType type, Var value); static void setNextToken(Parser* parser, TokenType type); static bool matchChar(Parser* parser, char c); -static void eatString(Parser* parser, bool single_quote) { +static void eatString(Compiler* compiler, bool single_quote) { + Parser* parser = &compiler->parser; + pkByteBuffer buff; pkByteBufferInit(&buff); @@ -658,7 +668,8 @@ static void eatString(Parser* parser, bool single_quote) { if (c == quote) break; if (c == '\0') { - lexError(parser, "Non terminated string."); + syntaxError(compiler, makeErrToken(parser), "Non terminated string."); + return; // Null byte is required by TK_EOF. parser->current_char--; @@ -678,7 +689,9 @@ static void eatString(Parser* parser, bool single_quote) { } else { // Name Interpolation. if (!utilIsName(c)) { - lexError(parser, "Expected '{' or identifier after '$'."); + syntaxError(compiler, makeErrToken(parser), + "Expected '{' or identifier after '$'."); + return; } else { // Name interpolation (ie. "Hello $name!"). @@ -695,8 +708,9 @@ static void eatString(Parser* parser, bool single_quote) { } } else { - lexError(parser, "Maximum interpolation level reached (can only " - "interpolate upto depth %d).", MAX_STR_INTERP_DEPTH); + semanticError(compiler, makeErrToken(parser), + "Maximum interpolation level reached (can only " + "interpolate upto depth %d).", MAX_STR_INTERP_DEPTH); } break; } @@ -714,8 +728,9 @@ static void eatString(Parser* parser, bool single_quote) { case '$': pkByteBufferWrite(&buff, parser->vm, '$'); break; default: - lexError(parser, "Error: invalid escape character"); - break; + syntaxError(compiler, makeErrToken(parser), + "Invalid escape character."); + return; } } else { pkByteBufferWrite(&buff, parser->vm, c); @@ -776,7 +791,8 @@ static void eatName(Parser* parser) { } // Complete lexing a number literal. -static void eatNumber(Parser* parser) { +static void eatNumber(Compiler* compiler) { + Parser* parser = &compiler->parser; #define IS_HEX_CHAR(c) \ (('0' <= (c) && (c) <= '9') || \ @@ -794,7 +810,8 @@ static void eatNumber(Parser* parser) { uint64_t bin = 0; c = peekChar(parser); if (!IS_BIN_CHAR(c)) { - lexError(parser, "Invalid binary literal."); + syntaxError(compiler, makeErrToken(parser), "Invalid binary literal."); + return; } else { do { @@ -806,7 +823,8 @@ static void eatNumber(Parser* parser) { // Check the length of the binary literal. int length = (int)(parser->current_char - parser->token_start); if (length > STR_BIN_BUFF_SIZE - 2) { // -2: '-\0' 0b is in both side. - lexError(parser, "Binary literal is too long."); + semanticError(compiler, makeErrToken(parser), + "Binary literal is too long."); break; } @@ -825,7 +843,8 @@ static void eatNumber(Parser* parser) { // The first digit should be either hex digit. if (!IS_HEX_CHAR(c)) { - lexError(parser, "Invalid hex literal."); + syntaxError(compiler, makeErrToken(parser), "Invalid hex literal."); + return; } else { do { @@ -837,7 +856,8 @@ static void eatNumber(Parser* parser) { // Check the length of the binary literal. int length = (int)(parser->current_char - parser->token_start); if (length > STR_HEX_BUFF_SIZE - 2) { // -2: '-\0' 0x is in both side. - lexError(parser, "Hex literal is too long."); + semanticError(compiler, makeErrToken(parser), + "Hex literal is too long."); break; } @@ -872,7 +892,8 @@ static void eatNumber(Parser* parser) { } if (!utilIsDigit(peekChar(parser))) { - lexError(parser, "Invalid number literal."); + syntaxError(compiler, makeErrToken(parser), "Invalid number literal."); + return; } else { // Eat the exponent. while (utilIsDigit(peekChar(parser))) eatChar(parser); @@ -884,7 +905,8 @@ static void eatNumber(Parser* parser) { if (errno == ERANGE) { const char* start = parser->token_start; int len = (int)(parser->current_char - start); - lexError(parser, "Number literal is too large (%.*s).", len, start); + semanticError(compiler, makeErrToken(parser), + "Number literal is too large (%.*s).", len, start); value = VAR_NUM(0); } } @@ -923,6 +945,16 @@ static void setNextTwoCharToken(Parser* parser, char c, TokenType one, } } +// Returns an error token from the current position for reporting error. +static Token makeErrToken(Parser* parser) { + Token tk; + tk.type = TK_ERROR; + tk.start = parser->token_start; + tk.length = (int)(parser->current_char - parser->token_start); + tk.line = parser->current_line; + return tk; +} + // Initialize the next token as the type. static void setNextToken(Parser* parser, TokenType type) { Token* next = &parser->next; @@ -939,7 +971,9 @@ static void setNextValueToken(Parser* parser, TokenType type, Var value) { } // Lex the next token and set it as the next token. -static void lexToken(Parser* parser) { +static void lexToken(Compiler* compiler) { + Parser* parser = &compiler->parser; + parser->previous = parser->current; parser->current = parser->next; @@ -957,7 +991,7 @@ static void lexToken(Parser* parser) { if (parser->si_name_end != NULL) { if (parser->current_char == parser->si_name_end) { parser->si_name_end = NULL; - eatString(parser, parser->si_name_quote == '\''); + eatString(compiler, parser->si_name_quote == '\''); return; } else { ASSERT(parser->current_char < parser->si_name_end, OOPS); @@ -987,7 +1021,7 @@ static void lexToken(Parser* parser) { char quote = parser->si_quote[parser->si_depth - 1]; parser->si_depth--; //< Exit the depth. - eatString(parser, quote == '\''); + eatString(compiler, quote == '\''); return; } else { // Decrease the open brace at the current depth. @@ -1043,7 +1077,8 @@ static void lexToken(Parser* parser) { setNextToken(parser, TK_DOTDOT); // '..' } else if (utilIsDigit(peekChar(parser))) { eatChar(parser); // Consume the decimal point. - eatNumber(parser); // Consume the rest of the number + eatNumber(compiler); // Consume the rest of the number + if (parser->has_syntax_error) return; } else { setNextToken(parser, TK_DOT); // '.' } @@ -1103,33 +1138,37 @@ static void lexToken(Parser* parser) { setNextTwoCharToken(parser, '=', TK_FSLASH, TK_DIVEQ); return; - case '"': eatString(parser, false); return; + case '"': eatString(compiler, false); return; - case '\'': eatString(parser, true); return; + case '\'': eatString(compiler, true); return; default: { if (utilIsDigit(c)) { - eatNumber(parser); + eatNumber(compiler); + if (parser->has_syntax_error) return; } else if (utilIsName(c)) { eatName(parser); } else { - if (c >= 32 && c <= 126) { - lexError(parser, "Invalid character '%c'", c); - } else { - lexError(parser, "Invalid byte 0x%x", (uint8_t)c); - } setNextToken(parser, TK_ERROR); + + if (c >= 32 && c <= 126) { + syntaxError(compiler, parser->next, + "Invalid character '%c'", c); + } else { + syntaxError(compiler, parser->next, + "Invalid byte 0x%x", (uint8_t)c); + } } return; } } } + parser->token_start = parser->current_char; setNextToken(parser, TK_EOF); - parser->next.start = parser->current_char; } /*****************************************************************************/ @@ -1145,7 +1184,10 @@ static TokenType peek(Compiler* compiler) { // and return true otherwise return false. static bool match(Compiler* compiler, TokenType expected) { if (peek(compiler) != expected) return false; - lexToken(&(compiler->parser)); + + lexToken(compiler); + if (compiler->parser.has_syntax_error) return false; + return true; } @@ -1154,15 +1196,13 @@ static bool match(Compiler* compiler, TokenType expected) { static void consume(Compiler* compiler, TokenType expected, const char* err_msg) { - lexToken(&(compiler->parser)); - if (compiler->parser.previous.type != expected) { - parseError(compiler, "%s", err_msg); + lexToken(compiler); + if (compiler->parser.has_syntax_error) return; - // If the next token is expected discard the current to minimize - // cascaded errors and continue parsing. - if (peek(compiler) == expected) { - lexToken(&(compiler->parser)); - } + Token *prev = &compiler->parser.previous; + if (prev->type != expected) { + syntaxError(compiler, *prev, "%s", err_msg); + return; } } @@ -1172,8 +1212,10 @@ static bool matchLine(Compiler* compiler) { bool consumed = false; if (peek(compiler) == TK_LINE) { - while (peek(compiler) == TK_LINE) - lexToken(&(compiler->parser)); + while (peek(compiler) == TK_LINE) { + lexToken(compiler); + if (compiler->parser.has_syntax_error) return false; + } consumed = true; } @@ -1213,7 +1255,9 @@ static bool matchEndStatement(Compiler* compiler) { // Consume semi collon, multiple new lines or peek 'end' keyword. static void consumeEndStatement(Compiler* compiler) { if (!matchEndStatement(compiler)) { - parseError(compiler, "Expected statement end with '\\n' or ';'."); + syntaxError(compiler, compiler->parser.current, + "Expected statement end with '\\n' or ';'."); + return; } } @@ -1234,7 +1278,8 @@ static void consumeStartBlock(Compiler* compiler, TokenType delimiter) { const char* msg; if (delimiter == TK_DO) msg = "Expected enter block with newline or 'do'."; else msg = "Expected enter block with newline or 'then'."; - parseError(compiler, msg); + syntaxError(compiler, compiler->parser.previous, msg); + return; } } @@ -1313,8 +1358,8 @@ static int addUpvalue(Compiler* compiler, Func* func, } if (func->ptr->upvalue_count == MAX_UPVALUES) { - parseError(compiler, "A function cannot capture more thatn %d upvalues.", - MAX_UPVALUES); + semanticError(compiler, compiler->parser.previous, + "A function cannot capture more thatn %d upvalues.", MAX_UPVALUES); return -1; } @@ -1455,7 +1500,7 @@ static int compilerAddConstant(Compiler* compiler, Var value); static int compilerAddVariable(Compiler* compiler, const char* name, uint32_t length, int line); static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, - const char* name, int length, int line); + Token* tkname); static void compilerChangeStack(Compiler* compiler, int num); // Forward declaration of grammar functions. @@ -1587,8 +1632,12 @@ static void emitPushName(Compiler* compiler, NameDefnType type, int index) { ASSERT(index >= 0, OOPS); switch (type) { - case NAME_NOT_DEFINED: + case NAME_NOT_DEFINED: { + if (compiler->parser.has_errors) { + return; + } UNREACHABLE(); + } case NAME_LOCAL_VAR: if (index < 9) { //< 0..8 locals have single opcode. @@ -1629,8 +1678,10 @@ static void emitStoreName(Compiler* compiler, NameDefnType type, int index) { switch (type) { case NAME_NOT_DEFINED: case NAME_BUILTIN_FN: - case NAME_BUILTIN_TY: + case NAME_BUILTIN_TY: { + if (compiler->parser.has_errors) return; UNREACHABLE(); + } case NAME_LOCAL_VAR: if (index < 9) { //< 0..8 locals have single opcode. @@ -1722,9 +1773,11 @@ static void exprFunction(Compiler* compiler) { static void exprName(Compiler* compiler) { - const char* start = compiler->parser.previous.start; - int length = compiler->parser.previous.length; - int line = compiler->parser.previous.line; + Token tkname = compiler->parser.previous; + + const char* start = tkname.start; + int length = tkname.length; + int line = tkname.line; NameSearchResult result = compilerSearchName(compiler, start, length); if (compiler->l_value && matchAssignment(compiler)) { @@ -1767,8 +1820,14 @@ static void exprName(Compiler* compiler) { compileExpression(compiler); } else { // name += / -= / *= ... = (expr); + if (result.type == NAME_NOT_DEFINED) { - parseError(compiler, "Name '%.*s' is not defined.", length, start); + + // TODO: + // Add to forward names. Create result.type as NAME_FORWARD + // and use emitPushName, emitStoreName for here and bellow. + semanticError(compiler, tkname, + "Name '%.*s' is not defined.", length, start); } // Push the named value. @@ -1806,11 +1865,12 @@ static void exprName(Compiler* compiler) { // a local depth. if (result.type == NAME_NOT_DEFINED) { if (compiler->scope_depth == DEPTH_GLOBAL) { - parseError(compiler, "Name '%.*s' is not defined.", length, start); + semanticError(compiler, tkname, + "Name '%.*s' is not defined.", length, start); } else { emitOpcode(compiler, OP_PUSH_GLOBAL); int index = emitByte(compiler, 0xff); - compilerAddForward(compiler, index, _FN, start, length, line); + compilerAddForward(compiler, index, _FN, &tkname); } } else { emitPushName(compiler, result.type, result.index); @@ -2066,19 +2126,24 @@ static void exprSelf(Compiler* compiler) { // inside a method. if (!compiler->parser.parsing_class) { - parseError(compiler, "Invalid use of 'self'."); + semanticError(compiler, compiler->parser.previous, + "Invalid use of 'self'."); } else { // FIXME: - parseError(compiler, "TODO: Closures cannot capture 'self' for now."); + semanticError(compiler, compiler->parser.previous, + "TODO: Closures cannot capture 'self' for now."); } } static void parsePrecedence(Compiler* compiler, Precedence precedence) { - lexToken(&(compiler->parser)); + lexToken(compiler); + if (compiler->parser.has_syntax_error) return; + GrammarFn prefix = getRule(compiler->parser.previous.type)->prefix; if (prefix == NULL) { - parseError(compiler, "Expected an expression."); + syntaxError(compiler, compiler->parser.previous, + "Expected an expression."); return; } @@ -2092,7 +2157,8 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) { compiler->is_last_call = false; while (getRule(compiler->parser.current.type)->precedence >= precedence) { - lexToken(&(compiler->parser)); + lexToken(compiler); + if (compiler->parser.has_syntax_error) return; TokenType op = compiler->parser.previous.type; GrammarFn infix = getRule(op)->infix; @@ -2131,8 +2197,8 @@ static int compilerAddVariable(Compiler* compiler, const char* name, } } if (max_vars_reached) { - parseError(compiler, "A module should contain at most %d %s.", - MAX_VARIABLES, var_type); + semanticError(compiler, compiler->parser.previous, + "A module should contain at most %d %s.", MAX_VARIABLES, var_type); return -1; } @@ -2156,10 +2222,10 @@ static int compilerAddVariable(Compiler* compiler, const char* name, } static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, - const char* name, int length, int line) { + Token* tkname) { if (compiler->parser.forwards_count == MAX_FORWARD_NAMES) { - parseError(compiler, "A module should contain at most %d implicit forward " - "function declarations.", MAX_FORWARD_NAMES); + semanticError(compiler, *tkname, "A module should contain at most %d " + "implicit forward function declarations.", MAX_FORWARD_NAMES); return; } @@ -2167,9 +2233,7 @@ static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, compiler->parser.forwards_count++]; forward->instruction = instruction; forward->func = fn; - forward->name = name; - forward->length = length; - forward->line = line; + forward->tkname = *tkname; } // Add a literal constant to module literals and return it's index. @@ -2376,18 +2440,21 @@ static int compileClass(Compiler* compiler) { vmPushTempRef(_vm, &cls->_super); // cls. compiler->parser.parsing_class = true; - // Check count exceeded. checkMaxConstantsReached(compiler, cls_index); skipNewLines(compiler); - while (!match(compiler, TK_END)) { + while (!match(compiler, TK_END) && !match(compiler, TK_EOF)) { // At the top level the stack size should be 0, before and after compiling // a top level statement, since there aren't any locals at the top level. ASSERT(compiler->parser.has_errors || compiler->func->stack_size == 0, OOPS); consume(compiler, TK_DEF, "Expected method definition."); + if (compiler->parser.has_syntax_error) break; + int fn_index = compileFunction(compiler, FUNC_METHOD); + if (compiler->parser.has_syntax_error) break; + Var fn_var = compiler->module->constants.data[fn_index]; ASSERT(IS_OBJ_TYPE(fn_var, OBJ_FUNC), OOPS); @@ -2410,6 +2477,7 @@ static int compileClass(Compiler* compiler) { compiler->func->stack_size == 0, OOPS); skipNewLines(compiler); + if (compiler->parser.has_syntax_error) break; } compiler->parser.parsing_class = false; @@ -2437,6 +2505,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { int fn_index; Function* func = newFunction(compiler->parser.vm, name, name_length, compiler->module, false, NULL, &fn_index); + checkMaxConstantsReached(compiler, fn_index); if (fn_type != FUNC_LITERAL) { @@ -2482,7 +2551,8 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { } } if (predefined) { - parseError(compiler, "Multiple definition of a parameter."); + semanticError(compiler, compiler->parser.previous, + "Multiple definition of a parameter."); } compilerAddVariable(compiler, param_name, param_len, @@ -2585,8 +2655,10 @@ static Module* importFile(Compiler* compiler, const char* path) { } if (resolved.string == NULL) { - parseError(compiler, "Cannot resolve path '%s' from '%s'", path, - compiler->module->path->data); + semanticError(compiler, compiler->parser.previous, + "Cannot resolve path '%s' from '%s'", path, + compiler->module->path->data); + return NULL; } // Create new string for the resolved path. And free the resolved path. @@ -2611,15 +2683,17 @@ static Module* importFile(Compiler* compiler, const char* path) { // The script not exists in the VM, make sure we have the script loading // api function. if (vm->config.load_script_fn == NULL) { - parseError(compiler, "Cannot import. The hosting application haven't " - "registered the script loading API"); + semanticError(compiler, compiler->parser.previous, + "Cannot import. The hosting application haven't registered " + "the script loading API"); return NULL; } // Load the script at the path. PkStringPtr source = vm->config.load_script_fn(vm, path_->data); if (source.string == NULL) { - parseError(compiler, "Error loading script at \"%s\"", path_->data); + semanticError(compiler, compiler->parser.previous, + "Error loading script at \"%s\"", path_->data); return NULL; } @@ -2656,8 +2730,8 @@ static Module* importFile(Compiler* compiler, const char* path) { if (source.on_done != NULL) source.on_done(vm, source); if (result != PK_RESULT_SUCCESS) { - parseError(compiler, "Compilation of imported script '%s' failed", - path_->data); + semanticError(compiler, compiler->parser.previous, + "Compilation of imported script '%s' failed", path_->data); } return module; @@ -2677,7 +2751,8 @@ static Module* importCoreLib(Compiler* compiler, const char* name_start, Module* imported = vmGetModule(compiler->parser.vm, module_name); if (imported == NULL) { - parseError(compiler, "No module named '%s' exists.", module_name->data); + semanticError(compiler, compiler->parser.previous, + "No module named '%s' exists.", module_name->data); return NULL; } @@ -2707,7 +2782,8 @@ static inline Module* compilerImport(Compiler* compiler) { } // Invalid token after import/from keyword. - parseError(compiler, "Expected a module name or path to import."); + syntaxError(compiler, compiler->parser.previous, + "Expected a module name or path to import."); return NULL; } @@ -2725,6 +2801,7 @@ static int compilerImportName(Compiler* compiler, int line, return compilerAddVariable(compiler, name, length, line); case NAME_LOCAL_VAR: + case NAME_UPVALUE: UNREACHABLE(); case NAME_GLOBAL_VAR: @@ -2735,7 +2812,9 @@ static int compilerImportName(Compiler* compiler, int line, // should pass) and allow imported entries to have the same name of // builtin functions. case NAME_BUILTIN_FN: - parseError(compiler, "Name '%.*s' already exists.", length, name); + case NAME_BUILTIN_TY: + semanticError(compiler, compiler->parser.previous, + "Name '%.*s' already exists.", length, name); return -1; } @@ -3061,7 +3140,8 @@ static void compileStatement(Compiler* compiler) { if (match(compiler, TK_BREAK)) { if (compiler->loop == NULL) { - parseError(compiler, "Cannot use 'break' outside a loop."); + syntaxError(compiler, compiler->parser.previous, + "Cannot use 'break' outside a loop."); return; } @@ -3078,7 +3158,8 @@ static void compileStatement(Compiler* compiler) { } else if (match(compiler, TK_CONTINUE)) { if (compiler->loop == NULL) { - parseError(compiler, "Cannot use 'continue' outside a loop."); + syntaxError(compiler, compiler->parser.previous, + "Cannot use 'continue' outside a loop."); return; } @@ -3091,7 +3172,8 @@ static void compileStatement(Compiler* compiler) { } else if (match(compiler, TK_RETURN)) { if (compiler->scope_depth == DEPTH_GLOBAL) { - parseError(compiler, "Invalid 'return' outside a function."); + syntaxError(compiler, compiler->parser.previous, + "Invalid 'return' outside a function."); return; } @@ -3109,7 +3191,8 @@ static void compileStatement(Compiler* compiler) { } else { if (compiler->func->type == FUNC_CONSTRUCTOR) { - parseError(compiler, "Cannor 'return' a value from constructor."); + syntaxError(compiler, compiler->parser.previous, + "Cannor 'return' a value from constructor."); } compileExpression(compiler); //< Return value is at stack top. @@ -3180,8 +3263,8 @@ static void compileTopLevelStatement(Compiler* compiler) { compileRegularImport(compiler); } else if (match(compiler, TK_MODULE)) { - parseError(compiler, "Module name must be the first statement " - "of the script."); + syntaxError(compiler, compiler->parser.previous, + "Module name must be the first statement of the script."); } else { compileStatement(compiler); @@ -3196,6 +3279,8 @@ static void compileTopLevelStatement(Compiler* compiler) { PkResult compile(PKVM* vm, Module* module, const char* source, const PkCompileOptions* options) { + ASSERT(module != NULL, OOPS); + // Skip utf8 BOM if there is any. if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; @@ -3213,6 +3298,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, // the native api function (pkNewModule() that'll return a module without a // main function) so just create and add the function here. if (module->body == NULL) moduleAddMain(vm, module); + ASSERT(module->body != NULL, OOPS); // If we're compiling for a module that was already compiled (when running // REPL or evaluating an expression) we don't need the old main anymore. @@ -3228,8 +3314,8 @@ PkResult compile(PKVM* vm, Module* module, const char* source, compilerPushFunc(compiler, &curr_fn, module->body->fn, FUNC_MAIN); // Lex initial tokens. current <-- next. - lexToken(&(compiler->parser)); - lexToken(&(compiler->parser)); + lexToken(compiler); + lexToken(compiler); skipNewLines(compiler); if (match(compiler, TK_MODULE)) { @@ -3238,7 +3324,8 @@ PkResult compile(PKVM* vm, Module* module, const char* source, // application module attribute might already set. In that case make it // Compile error. if (module->name != NULL) { - parseError(compiler, "Module name already defined."); + syntaxError(compiler, compiler->parser.previous, + "Module name already defined."); } else { consume(compiler, TK_NAME, "Expected a name for the module."); @@ -3249,7 +3336,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, } } - while (!match(compiler, TK_EOF)) { + while (!match(compiler, TK_EOF) && !compiler->parser.has_syntax_error) { compileTopLevelStatement(compiler); skipNewLines(compiler); } @@ -3257,20 +3344,23 @@ PkResult compile(PKVM* vm, Module* module, const char* source, emitFunctionEnd(compiler); // Resolve forward names (function names that are used before defined). - for (int i = 0; i < compiler->parser.forwards_count; i++) { - ForwardName* forward = &compiler->parser.forwards[i]; - const char* name = forward->name; - int length = forward->length; - int index = moduleGetGlobalIndex(compiler->module, name, (uint32_t)length); - if (index != -1) { - patchForward(compiler, forward->func, forward->instruction, index); - } else { - // need_more_lines is only true for unexpected EOF errors. For syntax - // errors it'll be false by now but. Here it's a semantic errors, so - // we're overriding it to false. - compiler->parser.need_more_lines = false; - resolveError(compiler, forward->line, "Name '%.*s' is not defined.", - length, name); + if (!compiler->parser.has_syntax_error) { + for (int i = 0; i < compiler->parser.forwards_count; i++) { + ForwardName* forward = &compiler->parser.forwards[i]; + const char* name = forward->tkname.start; + int length = forward->tkname.length; + int index = moduleGetGlobalIndex(compiler->module, name, + (uint32_t)length); + if (index != -1) { + patchForward(compiler, forward->func, forward->instruction, index); + } else { + // need_more_lines is only true for unexpected EOF errors. For syntax + // errors it'll be false by now but. Here it's a semantic errors, so + // we're overriding it to false. + compiler->parser.need_more_lines = false; + resolveError(compiler, forward->tkname, "Name '%.*s' is not defined.", + length, name); + } } } @@ -3296,11 +3386,20 @@ PkResult compile(PKVM* vm, Module* module, const char* source, return PK_RESULT_SUCCESS; } +// FIXME: +// move this to pk_core or create new pk_public.c to implement all +// public functions. PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source, const PkCompileOptions* options) { - __ASSERT(module_handle != NULL, "Argument module was NULL."); - __ASSERT(IS_OBJ_TYPE(module_handle->value, OBJ_MODULE), + + ASSERT(source.string != NULL, OOPS); + + // FIXME: + // CHECK_NULL, CHECK_TYPE marcros can be use after this function is moved. + ASSERT(module_handle != NULL, "Argument module was NULL."); + ASSERT(IS_OBJ_TYPE(module_handle->value, OBJ_MODULE), "Given handle is not a module."); + Module* module = (Module*)AS_OBJ(module_handle->value); PkResult result = compile(vm, module, source.string, options); diff --git a/src/pk_core.c b/src/pk_core.c index cf94153..1ed7228 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -480,8 +480,8 @@ DEF(coreHelp, if (argc == 0) { // If there ins't an io function callback, we're done. - if (vm->config.write_fn == NULL) RET(VAR_NULL); - vm->config.write_fn(vm, "TODO: print help here\n"); + if (vm->config.stdout_write == NULL) RET(VAR_NULL); + vm->config.stdout_write(vm, "TODO: print help here\n"); } else if (argc == 1) { @@ -492,15 +492,15 @@ DEF(coreHelp, if (!validateArgClosure(vm, 1, &closure)) return; // If there ins't an io function callback, we're done. - if (vm->config.write_fn == NULL) RET(VAR_NULL); + if (vm->config.stdout_write == NULL) RET(VAR_NULL); if (closure->fn->docstring != NULL) { - vm->config.write_fn(vm, closure->fn->docstring); - vm->config.write_fn(vm, "\n\n"); + vm->config.stdout_write(vm, closure->fn->docstring); + vm->config.stdout_write(vm, "\n\n"); } else { - vm->config.write_fn(vm, "function '"); - vm->config.write_fn(vm, closure->fn->name); - vm->config.write_fn(vm, "()' doesn't have a docstring.\n"); + vm->config.stdout_write(vm, "function '"); + vm->config.stdout_write(vm, closure->fn->name); + vm->config.stdout_write(vm, "()' doesn't have a docstring.\n"); } } } @@ -649,14 +649,14 @@ DEF(corePrint, // If the host application doesn't provide any write function, discard the // output. - if (vm->config.write_fn == NULL) return; + if (vm->config.stdout_write == NULL) return; for (int i = 1; i <= ARGC; i++) { - if (i != 1) vm->config.write_fn(vm, " "); - vm->config.write_fn(vm, toString(vm, ARG(i))->data); + if (i != 1) vm->config.stdout_write(vm, " "); + vm->config.stdout_write(vm, toString(vm, ARG(i))->data); } - vm->config.write_fn(vm, "\n"); + vm->config.stdout_write(vm, "\n"); } DEF(coreInput, @@ -670,13 +670,13 @@ DEF(coreInput, } // If the host application doesn't provide any write function, return. - if (vm->config.read_fn == NULL) return; + if (vm->config.stdin_read == NULL) return; if (argc == 1) { - vm->config.write_fn(vm, toString(vm, ARG(1))->data); + vm->config.stdout_write(vm, toString(vm, ARG(1))->data); } - PkStringPtr result = vm->config.read_fn(vm); + PkStringPtr result = vm->config.stdin_read(vm); String* line = newString(vm, result.string); if (result.on_done) result.on_done(vm, result); RET(VAR_OBJ(line)); @@ -920,7 +920,7 @@ DEF(stdLangWrite, // If the host application doesn't provide any write function, discard the // output. - if (vm->config.write_fn == NULL) return; + if (vm->config.stdout_write == NULL) return; String* str; //< Will be cleaned by garbage collector; @@ -933,7 +933,7 @@ DEF(stdLangWrite, str = toString(vm, arg); } - vm->config.write_fn(vm, str->data); + vm->config.stdout_write(vm, str->data); } } diff --git a/src/pk_debug.c b/src/pk_debug.c index 2525731..2806081 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -11,6 +11,209 @@ #include "pk_value.h" #include "pk_vm.h" +// FIXME: +// Refactor this. Maybe move to a module, Rgb values are hardcoded ?! +// Should check stderr/stdout etc. +static void _printRed(PKVM* vm, const char* msg) { + if (vm->config.use_ansi_color) { + vm->config.stderr_write(vm, "\033[38;2;220;100;100m"); + vm->config.stderr_write(vm, msg); + vm->config.stderr_write(vm, "\033[0m"); + } else { + vm->config.stderr_write(vm, msg); + } +} + +void reportCompileTimeError(PKVM* vm, const char* path, int line, + const char* source, const char* at, int length, + const char* fmt, va_list args) { + + pkWriteFn writefn = vm->config.stderr_write; + if (writefn == NULL) return; + + pkByteBuffer buff; + pkByteBufferInit(&buff); + { + // Initial size set to 512. Will grow if needed. + pkByteBufferReserve(&buff, vm, 512); + + buff.count = 0; + writefn(vm, path); + writefn(vm, ":"); + snprintf((char*)buff.data, buff.capacity, "%d", line); + writefn(vm, (char*)buff.data); + _printRed(vm, " error: "); + + // Print the error message. + buff.count = 0; + int size = vsnprintf(NULL, 0, fmt, args) + 1; + ASSERT(size >= 0, "vnsprintf() failed."); + pkByteBufferReserve(&buff, vm, size); + vsnprintf((char*)buff.data, size, fmt, args); + writefn(vm, (char*)buff.data); + writefn(vm, "\n"); + + // Print the lines. (TODO: Optimize it). + + int start = line - 2; // First line. + if (start < 1) start = 1; + int end = start + 5; // Exclisive last line. + + int line_number_width = 5; + + int curr_line = line; + const char* c = at; + + // Get the first character of the [start] line. + if (c != source) { + do { + c--; + if (*c == '\n') curr_line--; + if (c == source) break; + } while (curr_line >= start); + } + + curr_line = start; + if (c != source) { + ASSERT(*c == '\n', OOPS); + c++; // Enter the line. + } + + // Print each lines. + while (curr_line < end) { + + buff.count = 0; + snprintf((char*)buff.data, buff.capacity, + "%*d", line_number_width, curr_line); + writefn(vm, (char*)buff.data); + writefn(vm, " | "); + + if (curr_line != line) { + // Run to the line end. + const char* line_start = c; + while (*c != '\0' && *c != '\n') c++; + + buff.count = 0; + pkByteBufferAddString(&buff, vm, line_start, + (uint32_t)(c - line_start)); + pkByteBufferWrite(&buff, vm, '\0'); + writefn(vm, (char*)buff.data); + writefn(vm, "\n"); + + } else { + + const char* line_start = c; + + // Print line till error. + buff.count = 0; + pkByteBufferAddString(&buff, vm, line_start, + (uint32_t)(at - line_start)); + pkByteBufferWrite(&buff, vm, '\0'); + writefn(vm, (char*)buff.data); + + // Print error token - if the error token is a new line ignore it. + if (*at != '\n') { + buff.count = 0; + pkByteBufferAddString(&buff, vm, at, length); + pkByteBufferWrite(&buff, vm, '\0'); + _printRed(vm, (char*)buff.data); + + // Run to the line end. Note that tk.length is not reliable and + // sometimes longer than the actual string which will cause a + // buffer overflow. + const char* tail_start = at; + for (int i = 0; i < length; i++) { + if (*tail_start == '\0') break; + tail_start++; + } + + c = tail_start; + while (*c != '\0' && *c != '\n') c++; + + // Print rest of the line. + if (c != tail_start) { + buff.count = 0; + pkByteBufferAddString(&buff, vm, tail_start, + (uint32_t)(c - tail_start)); + pkByteBufferWrite(&buff, vm, '\0'); + writefn(vm, (char*)buff.data); + } + } else { + c = at; // Run 'c' to the end of the line. + } + writefn(vm, "\n"); + + // White space before error token. + buff.count = 0; + pkByteBufferFill(&buff, vm, ' ', line_number_width); + pkByteBufferAddString(&buff, vm, " | ", 3); + + for (const char* c2 = line_start; c2 < at; c2++) { + char white_space = (*c2 == '\t') ? '\t' : ' '; + pkByteBufferWrite(&buff, vm, white_space); + } + + pkByteBufferWrite(&buff, vm, '\0'); + writefn(vm, (char*)buff.data); + + // Error token underline. + buff.count = 0; + pkByteBufferFill(&buff, vm, '~', (uint32_t)(length ? length : 1)); + pkByteBufferWrite(&buff, vm, '\0'); + _printRed(vm, (char*)buff.data); + writefn(vm, "\n"); + + } + + if (*c == '\0') break; + curr_line++; c++; + } + } + pkByteBufferClear(&buff, vm); +} + +void reportRuntimeError(PKVM* vm, Fiber* fiber) { + + pkWriteFn writefn = vm->config.stderr_write; + if (writefn == NULL) return; + + // Error message. + _printRed(vm, "Error: "); + writefn(vm, fiber->error->data); + writefn(vm, "\n"); + + // Stack trace. + for (int i = fiber->frame_count - 1; i >= 0; i--) { + CallFrame* frame = &fiber->frames[i]; + const Function* fn = frame->closure->fn; + ASSERT(!fn->is_native, OOPS); + int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1]; + + if (fn->owner->path == NULL) { + + writefn(vm, " [at:"); + char buff[STR_INT_BUFF_SIZE]; + sprintf(buff, "%2d", line); + writefn(vm, buff); + writefn(vm, "] "); + writefn(vm, fn->name); + writefn(vm, "()\n"); + + } else { + writefn(vm, " "); + writefn(vm, fn->name); + writefn(vm, "() ["); + writefn(vm, fn->owner->path->data); + writefn(vm, ":"); + char buff[STR_INT_BUFF_SIZE]; + sprintf(buff, "%d", line); + writefn(vm, buff); + writefn(vm, "]\n"); + } + + } +} + // Opcode names array. static const char* op_names[] = { #define OPCODE(name, params, stack) #name, @@ -19,20 +222,20 @@ static const char* op_names[] = { }; static void dumpValue(PKVM* vm, Var value) { - if (!vm->config.write_fn) return; + if (!vm->config.stdout_write) return; String* repr = toRepr(vm, value); - vm->config.write_fn(vm, repr->data); + vm->config.stdout_write(vm, repr->data); // String repr will be garbage collected - No need to clean. } void dumpFunctionCode(PKVM* vm, Function* func) { - if (!vm->config.write_fn) return; + if (!vm->config.stdout_write) return; #define _INDENTATION " " #define _INT_WIDTH 5 // Width of the integer string to print. -#define PRINT(str) vm->config.write_fn(vm, str) +#define PRINT(str) vm->config.stdout_write(vm, str) #define NEWLINE() PRINT("\n") #define _PRINT_INT(value, width) \ diff --git a/src/pk_debug.h b/src/pk_debug.h index 39703ae..879f724 100644 --- a/src/pk_debug.h +++ b/src/pk_debug.h @@ -10,6 +10,14 @@ #include "pk_internal.h" #include "pk_value.h" +// Pretty print compile time error. +void reportCompileTimeError(PKVM* vm, const char* path, int line, + const char* source, const char* at, int length, + const char* fmt, va_list args); + +// Pretty print runtime error. +void reportRuntimeError(PKVM* vm, Fiber* fiber); + // Dump opcodes of the given function to the stdout. void dumpFunctionCode(PKVM* vm, Function* func); diff --git a/src/pk_internal.h b/src/pk_internal.h index 1ae739a..db1f9e0 100644 --- a/src/pk_internal.h +++ b/src/pk_internal.h @@ -36,6 +36,8 @@ #elif defined(__clang__) #pragma clang diagnostic ignored "-Wint-to-pointer-cast" #pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(_MSC_VER) + #pragma warning(disable:26812) #endif /*****************************************************************************/ diff --git a/src/pk_value.c b/src/pk_value.c index b388137..52c0a4c 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -1630,6 +1630,8 @@ bool toBool(Var v) { case OBJ_RANGE: // [[FALLTHROUGH]] case OBJ_MODULE: case OBJ_FUNC: + case OBJ_CLOSURE: + case OBJ_UPVALUE: case OBJ_FIBER: case OBJ_CLASS: case OBJ_INST: diff --git a/src/pk_vm.c b/src/pk_vm.c index a9ab792..0471a89 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -27,12 +27,14 @@ PkConfiguration pkNewConfiguration(void) { PkConfiguration config; config.realloc_fn = defaultRealloc; - config.error_fn = NULL; - config.write_fn = NULL; - config.read_fn = NULL; + config.stdout_write = NULL; + config.stderr_write = NULL; + config.stdin_read = NULL; config.load_script_fn = NULL; config.resolve_path_fn = NULL; + + config.use_ansi_color = false; config.user_data = NULL; return config; @@ -674,25 +676,15 @@ static void closeUpvalues(Fiber* fiber, Var* top) { fiber->open_upvalues = upvalue->next; } - } static void reportError(PKVM* vm) { ASSERT(VM_HAS_ERROR(vm), "runtimeError() should be called after an error."); + // TODO: pass the error to the caller of the fiber. - // Print the Error message and stack trace. - if (vm->config.error_fn == NULL) return; - Fiber* fiber = vm->fiber; - vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error->data); - for (int i = fiber->frame_count - 1; i >= 0; i--) { - CallFrame* frame = &fiber->frames[i]; - const Function* fn = frame->closure->fn; - ASSERT(!fn->is_native, OOPS); - int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1]; - vm->config.error_fn(vm, PK_ERROR_STACKTRACE, fn->owner->path->data, line, - fn->name); - } + if (vm->config.stderr_write == NULL) return; + reportRuntimeError(vm, vm->fiber); } /****************************************************************************** @@ -1701,11 +1693,11 @@ L_do_call: OPCODE(REPL_PRINT): { - if (vm->config.write_fn != NULL) { + if (vm->config.stdout_write != NULL) { Var tmp = PEEK(-1); if (!IS_NULL(tmp)) { - vm->config.write_fn(vm, toRepr(vm, tmp)->data); - vm->config.write_fn(vm, "\n"); + vm->config.stdout_write(vm, toRepr(vm, tmp)->data); + vm->config.stdout_write(vm, "\n"); } } DISPATCH(); diff --git a/tests/native/example1.c b/tests/native/example1.c index 12446fa..43c7cb5 100644 --- a/tests/native/example1.c +++ b/tests/native/example1.c @@ -48,7 +48,7 @@ int main(int argc, char** argv) { // Pocket VM configuration. PkConfiguration config = pkNewConfiguration(); - config.write_fn = stdoutCallback; + config.stdout_write = stdoutCallback; // Create a new pocket VM. PKVM* vm = pkNewVM(&config); diff --git a/tests/native/example2.c b/tests/native/example2.c index 2d0b238..2082648 100644 --- a/tests/native/example2.c +++ b/tests/native/example2.c @@ -96,7 +96,7 @@ void stdoutCallback(PKVM* vm, const char* text) { int main(int argc, char** argv) { PkConfiguration config = pkNewConfiguration(); - config.write_fn = stdoutCallback; + config.stdout_write = stdoutCallback; PKVM* vm = pkNewVM(&config); registerVector(vm);