Merge pull request #207 from ThakeeNathees/pretty-error

error pretty print (print lines) implemented
This commit is contained in:
Thakee Nathees 2022-04-23 06:50:29 +05:30 committed by GitHub
commit 13b70d6512
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 581 additions and 247 deletions

View File

@ -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" \

View File

@ -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 <Windows.h>
#include <io.h>
#else
#include <unistd.h>
#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);
}

View File

@ -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 += `<span class="error-line">${err_text}</span>\n`;
let msg = AsciiToString(message);
out.innerHTML += `<span class="error-line">${msg}</span>`;
},
js_writeFunction : function(message) {

View File

@ -8,14 +8,13 @@
#include <string.h>
// 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);

View File

@ -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;
};

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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) \

View File

@ -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);

View File

@ -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
/*****************************************************************************/

View File

@ -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:

View File

@ -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();

View File

@ -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);

View File

@ -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);