mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-11 07:00:58 +08:00
Merge pull request #207 from ThakeeNathees/pretty-error
error pretty print (print lines) implemented
This commit is contained in:
commit
13b70d6512
@ -18,6 +18,16 @@
|
|||||||
|
|
||||||
#include "modules/common.h"
|
#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 \
|
#define CLI_NOTICE \
|
||||||
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \
|
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \
|
||||||
"Copyright (c) 2020 - 2021 ThakeeNathees\n" \
|
"Copyright (c) 2020 - 2021 ThakeeNathees\n" \
|
||||||
|
61
cli/main.c
61
cli/main.c
@ -21,32 +21,15 @@ void onResultDone(PKVM* vm, PkStringPtr result) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line,
|
void stderrWrite(PKVM* vm, const char* text) {
|
||||||
const char* message) {
|
fprintf(stderr, "%s", text);
|
||||||
|
|
||||||
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 writeFunction(PKVM* vm, const char* text) {
|
void stdoutWrite(PKVM* vm, const char* text) {
|
||||||
fprintf(stdout, "%s", text);
|
fprintf(stdout, "%s", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
PkStringPtr readFunction(PKVM* vm) {
|
PkStringPtr stdinRead(PKVM* vm) {
|
||||||
PkStringPtr result;
|
PkStringPtr result;
|
||||||
result.string = read_line(NULL);
|
result.string = read_line(NULL);
|
||||||
result.on_done = onResultDone;
|
result.on_done = onResultDone;
|
||||||
@ -110,10 +93,14 @@ PkStringPtr loadScript(PKVM* vm, const char* path) {
|
|||||||
|
|
||||||
// Read source to buffer.
|
// Read source to buffer.
|
||||||
char* buff = (char*)malloc((size_t)(file_size) + 1);
|
char* buff = (char*)malloc((size_t)(file_size) + 1);
|
||||||
|
|
||||||
// Using read instead of file_size is because "\r\n" is read as '\n' in
|
// Using read instead of file_size is because "\r\n" is read as '\n' in
|
||||||
// windows.
|
// windows.
|
||||||
size_t read = fread(buff, sizeof(char), file_size, file);
|
if (buff != NULL) {
|
||||||
buff[read] = '\0';
|
size_t read = fread(buff, sizeof(char), file_size, file);
|
||||||
|
buff[read] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
result.string = buff;
|
result.string = buff;
|
||||||
@ -122,16 +109,38 @@ PkStringPtr loadScript(PKVM* vm, const char* path) {
|
|||||||
return result;
|
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.
|
// Create new pocket VM and set it's configuration.
|
||||||
static PKVM* intializePocketVM() {
|
static PKVM* intializePocketVM() {
|
||||||
|
|
||||||
PkConfiguration config = pkNewConfiguration();
|
PkConfiguration config = pkNewConfiguration();
|
||||||
config.error_fn = errorFunction;
|
config.stderr_write = stderrWrite;
|
||||||
config.write_fn = writeFunction;
|
config.stdout_write = stdoutWrite;
|
||||||
config.read_fn = readFunction;
|
config.stdin_read = stdinRead;
|
||||||
|
|
||||||
config.load_script_fn = loadScript;
|
config.load_script_fn = loadScript;
|
||||||
config.resolve_path_fn = resolvePath;
|
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);
|
return pkNewVM(&config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,21 +10,13 @@
|
|||||||
mergeInto(LibraryManager.library, {
|
mergeInto(LibraryManager.library, {
|
||||||
/** js_func_name : function() {...} */
|
/** js_func_name : function() {...} */
|
||||||
|
|
||||||
js_errorPrint : function(type, line, message) {
|
js_errorPrint : function(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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
var out = document.getElementById("code-output");
|
var out = document.getElementById("code-output");
|
||||||
// To Indicate error (should be removed before each run request).
|
// To Indicate error (should be removed before each run request).
|
||||||
out.classList.add("has-error");
|
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) {
|
js_writeFunction : function(message) {
|
||||||
|
@ -8,14 +8,13 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
// IO api functions.
|
// 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);
|
extern void js_writeFunction(const char* message);
|
||||||
|
|
||||||
void errorPrint(PKVM* vm, PkErrorType type, const char* file, int line,
|
void errorPrint(PKVM* vm, const char* message) {
|
||||||
const char* message) {
|
|
||||||
// No need to pass the file (since there is only script that'll ever run on
|
// No need to pass the file (since there is only script that'll ever run on
|
||||||
// the browser.
|
// the browser.
|
||||||
js_errorPrint((int)type, line, message);
|
js_errorPrint(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeFunction(PKVM* vm, const char* text) {
|
void writeFunction(PKVM* vm, const char* text) {
|
||||||
@ -26,16 +25,39 @@ EMSCRIPTEN_KEEPALIVE
|
|||||||
int runSource(const char* source) {
|
int runSource(const char* source) {
|
||||||
|
|
||||||
PkConfiguration config = pkNewConfiguration();
|
PkConfiguration config = pkNewConfiguration();
|
||||||
config.error_fn = errorPrint;
|
config.stderr_write = errorPrint;
|
||||||
config.write_fn = writeFunction;
|
config.stdout_write = writeFunction;
|
||||||
config.load_script_fn = NULL;
|
config.load_script_fn = NULL;
|
||||||
config.resolve_path_fn = NULL;
|
config.resolve_path_fn = NULL;
|
||||||
|
|
||||||
PKVM* vm = pkNewVM(&config);
|
PKVM* vm = pkNewVM(&config);
|
||||||
|
|
||||||
PkStringPtr src = { source, NULL, NULL };
|
// FIXME:
|
||||||
PkStringPtr module = { "@(TRY)", NULL, NULL };
|
// The bellow blocks of code can be simplified with
|
||||||
PkResult result = pkInterpretSource(vm, src, module, NULL);
|
//
|
||||||
|
// 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);
|
pkFreeVM(vm);
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type,
|
|||||||
const char* file, int line,
|
const char* file, int line,
|
||||||
const char* message);
|
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);
|
typedef void (*pkWriteFn) (PKVM* vm, const char* text);
|
||||||
|
|
||||||
// A function callback to read a line from stdin. The returned string shouldn't
|
// A function callback to read a line from stdin. The returned string shouldn't
|
||||||
@ -159,14 +159,6 @@ enum PkVarType {
|
|||||||
PK_INSTANCE,
|
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
|
// Result that pocketlang will return after a compilation or running a script
|
||||||
// or a function or evaluating an expression.
|
// or a function or evaluating an expression.
|
||||||
enum PkResult {
|
enum PkResult {
|
||||||
@ -203,13 +195,16 @@ struct PkConfiguration {
|
|||||||
// pointer is NULL it defaults to the VM's realloc(), free() wrappers.
|
// pointer is NULL it defaults to the VM's realloc(), free() wrappers.
|
||||||
pkReallocFn realloc_fn;
|
pkReallocFn realloc_fn;
|
||||||
|
|
||||||
pkErrorFn error_fn;
|
pkWriteFn stderr_write;
|
||||||
pkWriteFn write_fn;
|
pkWriteFn stdout_write;
|
||||||
pkReadFn read_fn;
|
pkReadFn stdin_read;
|
||||||
|
|
||||||
pkResolvePathFn resolve_path_fn;
|
pkResolvePathFn resolve_path_fn;
|
||||||
pkLoadScriptFn load_script_fn;
|
pkLoadScriptFn load_script_fn;
|
||||||
|
|
||||||
|
// If true stderr calls will use ansi color codes.
|
||||||
|
bool use_ansi_color;
|
||||||
|
|
||||||
// User defined data associated with VM.
|
// User defined data associated with VM.
|
||||||
void* user_data;
|
void* user_data;
|
||||||
};
|
};
|
||||||
|
@ -297,12 +297,8 @@ typedef struct sForwardName {
|
|||||||
// The function where the name is used, and the instruction is belongs to.
|
// The function where the name is used, and the instruction is belongs to.
|
||||||
Fn* func;
|
Fn* func;
|
||||||
|
|
||||||
// The name string's pointer in the source.
|
// Name token that was lexed for this name.
|
||||||
const char* name;
|
Token tkname;
|
||||||
int length;
|
|
||||||
|
|
||||||
// Line number of the name used (required for error message).
|
|
||||||
int line;
|
|
||||||
|
|
||||||
} ForwardName;
|
} ForwardName;
|
||||||
|
|
||||||
@ -412,9 +408,15 @@ typedef struct sParser {
|
|||||||
|
|
||||||
bool repl_mode;
|
bool repl_mode;
|
||||||
bool parsing_class;
|
bool parsing_class;
|
||||||
bool has_errors;
|
|
||||||
bool need_more_lines; //< True if we need more lines in REPL mode.
|
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;
|
} Parser;
|
||||||
|
|
||||||
struct Compiler {
|
struct Compiler {
|
||||||
@ -509,7 +511,10 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler,
|
|||||||
parser->current_char = parser->source;
|
parser->current_char = parser->source;
|
||||||
parser->current_line = 1;
|
parser->current_line = 1;
|
||||||
|
|
||||||
|
parser->previous.type = TK_ERROR;
|
||||||
|
parser->current.type = TK_ERROR;
|
||||||
parser->next.type = TK_ERROR;
|
parser->next.type = TK_ERROR;
|
||||||
|
|
||||||
parser->next.start = NULL;
|
parser->next.start = NULL;
|
||||||
parser->next.length = 0;
|
parser->next.length = 0;
|
||||||
parser->next.line = 1;
|
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->repl_mode = !!(compiler->options && compiler->options->repl_mode);
|
||||||
parser->parsing_class = false;
|
parser->parsing_class = false;
|
||||||
parser->has_errors = false;
|
parser->has_errors = false;
|
||||||
|
parser->has_syntax_error = false;
|
||||||
parser->need_more_lines = 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->new_local = false;
|
||||||
compiler->is_last_call = 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.
|
// Cache the required built functions.
|
||||||
compiler->bifn_list_join = findBuiltinFunction(vm, "list_join", 9);
|
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.
|
// 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) {
|
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;
|
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,
|
// If the source is incomplete we're not printing an error message,
|
||||||
// instead return PK_RESULT_UNEXPECTED_EOF to the host.
|
// instead return PK_RESULT_UNEXPECTED_EOF to the host.
|
||||||
if (parser->need_more_lines) {
|
if (parser->need_more_lines) {
|
||||||
@ -572,51 +583,47 @@ static void reportError(Parser* parser, const char* file, int line,
|
|||||||
return;
|
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
|
// Error caused when parsing. The associated token assumed to be last consumed
|
||||||
// which is [parser->previous].
|
// 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.
|
parser->has_syntax_error = true;
|
||||||
if (token->type == TK_ERROR) return;
|
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_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
reportError(&(compiler->parser), compiler->parser.file_path,
|
reportError(parser, tk, fmt, args);
|
||||||
token->line, fmt, args);
|
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error caused when trying to resolve forward names (maybe more in the
|
// 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
|
// future), Which will be called once after compiling the module and thus we
|
||||||
// need to pass the line number the error originated from.
|
// 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_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
reportError(&(compiler->parser),
|
reportError(parser, tk, fmt, args);
|
||||||
compiler->parser.file_path,
|
|
||||||
line, fmt, args);
|
|
||||||
va_end(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) {
|
static void checkMaxConstantsReached(Compiler* compiler, int index) {
|
||||||
ASSERT(index >= 0, OOPS);
|
ASSERT(index >= 0, OOPS);
|
||||||
if (index >= MAX_CONSTANTS) {
|
if (index >= MAX_CONSTANTS) {
|
||||||
parseError(compiler, "A module should contain at most %d "
|
semanticError(compiler, compiler->parser.previous,
|
||||||
"unique constants.", MAX_CONSTANTS);
|
"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.
|
// Forward declaration of lexer methods.
|
||||||
|
|
||||||
|
static Token makeErrToken(Parser* parser);
|
||||||
static char peekChar(Parser* parser);
|
static char peekChar(Parser* parser);
|
||||||
static char peekNextChar(Parser* parser);
|
static char peekNextChar(Parser* parser);
|
||||||
static char eatChar(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 void setNextToken(Parser* parser, TokenType type);
|
||||||
static bool matchChar(Parser* parser, char c);
|
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;
|
pkByteBuffer buff;
|
||||||
pkByteBufferInit(&buff);
|
pkByteBufferInit(&buff);
|
||||||
|
|
||||||
@ -658,7 +668,8 @@ static void eatString(Parser* parser, bool single_quote) {
|
|||||||
if (c == quote) break;
|
if (c == quote) break;
|
||||||
|
|
||||||
if (c == '\0') {
|
if (c == '\0') {
|
||||||
lexError(parser, "Non terminated string.");
|
syntaxError(compiler, makeErrToken(parser), "Non terminated string.");
|
||||||
|
return;
|
||||||
|
|
||||||
// Null byte is required by TK_EOF.
|
// Null byte is required by TK_EOF.
|
||||||
parser->current_char--;
|
parser->current_char--;
|
||||||
@ -678,7 +689,9 @@ static void eatString(Parser* parser, bool single_quote) {
|
|||||||
|
|
||||||
} else { // Name Interpolation.
|
} else { // Name Interpolation.
|
||||||
if (!utilIsName(c)) {
|
if (!utilIsName(c)) {
|
||||||
lexError(parser, "Expected '{' or identifier after '$'.");
|
syntaxError(compiler, makeErrToken(parser),
|
||||||
|
"Expected '{' or identifier after '$'.");
|
||||||
|
return;
|
||||||
|
|
||||||
} else { // Name interpolation (ie. "Hello $name!").
|
} else { // Name interpolation (ie. "Hello $name!").
|
||||||
|
|
||||||
@ -695,8 +708,9 @@ static void eatString(Parser* parser, bool single_quote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
lexError(parser, "Maximum interpolation level reached (can only "
|
semanticError(compiler, makeErrToken(parser),
|
||||||
"interpolate upto depth %d).", MAX_STR_INTERP_DEPTH);
|
"Maximum interpolation level reached (can only "
|
||||||
|
"interpolate upto depth %d).", MAX_STR_INTERP_DEPTH);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -714,8 +728,9 @@ static void eatString(Parser* parser, bool single_quote) {
|
|||||||
case '$': pkByteBufferWrite(&buff, parser->vm, '$'); break;
|
case '$': pkByteBufferWrite(&buff, parser->vm, '$'); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
lexError(parser, "Error: invalid escape character");
|
syntaxError(compiler, makeErrToken(parser),
|
||||||
break;
|
"Invalid escape character.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pkByteBufferWrite(&buff, parser->vm, c);
|
pkByteBufferWrite(&buff, parser->vm, c);
|
||||||
@ -776,7 +791,8 @@ static void eatName(Parser* parser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Complete lexing a number literal.
|
// Complete lexing a number literal.
|
||||||
static void eatNumber(Parser* parser) {
|
static void eatNumber(Compiler* compiler) {
|
||||||
|
Parser* parser = &compiler->parser;
|
||||||
|
|
||||||
#define IS_HEX_CHAR(c) \
|
#define IS_HEX_CHAR(c) \
|
||||||
(('0' <= (c) && (c) <= '9') || \
|
(('0' <= (c) && (c) <= '9') || \
|
||||||
@ -794,7 +810,8 @@ static void eatNumber(Parser* parser) {
|
|||||||
uint64_t bin = 0;
|
uint64_t bin = 0;
|
||||||
c = peekChar(parser);
|
c = peekChar(parser);
|
||||||
if (!IS_BIN_CHAR(c)) {
|
if (!IS_BIN_CHAR(c)) {
|
||||||
lexError(parser, "Invalid binary literal.");
|
syntaxError(compiler, makeErrToken(parser), "Invalid binary literal.");
|
||||||
|
return;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
@ -806,7 +823,8 @@ static void eatNumber(Parser* parser) {
|
|||||||
// Check the length of the binary literal.
|
// Check the length of the binary literal.
|
||||||
int length = (int)(parser->current_char - parser->token_start);
|
int length = (int)(parser->current_char - parser->token_start);
|
||||||
if (length > STR_BIN_BUFF_SIZE - 2) { // -2: '-\0' 0b is in both side.
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,7 +843,8 @@ static void eatNumber(Parser* parser) {
|
|||||||
|
|
||||||
// The first digit should be either hex digit.
|
// The first digit should be either hex digit.
|
||||||
if (!IS_HEX_CHAR(c)) {
|
if (!IS_HEX_CHAR(c)) {
|
||||||
lexError(parser, "Invalid hex literal.");
|
syntaxError(compiler, makeErrToken(parser), "Invalid hex literal.");
|
||||||
|
return;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
@ -837,7 +856,8 @@ static void eatNumber(Parser* parser) {
|
|||||||
// Check the length of the binary literal.
|
// Check the length of the binary literal.
|
||||||
int length = (int)(parser->current_char - parser->token_start);
|
int length = (int)(parser->current_char - parser->token_start);
|
||||||
if (length > STR_HEX_BUFF_SIZE - 2) { // -2: '-\0' 0x is in both side.
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -872,7 +892,8 @@ static void eatNumber(Parser* parser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!utilIsDigit(peekChar(parser))) {
|
if (!utilIsDigit(peekChar(parser))) {
|
||||||
lexError(parser, "Invalid number literal.");
|
syntaxError(compiler, makeErrToken(parser), "Invalid number literal.");
|
||||||
|
return;
|
||||||
|
|
||||||
} else { // Eat the exponent.
|
} else { // Eat the exponent.
|
||||||
while (utilIsDigit(peekChar(parser))) eatChar(parser);
|
while (utilIsDigit(peekChar(parser))) eatChar(parser);
|
||||||
@ -884,7 +905,8 @@ static void eatNumber(Parser* parser) {
|
|||||||
if (errno == ERANGE) {
|
if (errno == ERANGE) {
|
||||||
const char* start = parser->token_start;
|
const char* start = parser->token_start;
|
||||||
int len = (int)(parser->current_char - 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);
|
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.
|
// Initialize the next token as the type.
|
||||||
static void setNextToken(Parser* parser, TokenType type) {
|
static void setNextToken(Parser* parser, TokenType type) {
|
||||||
Token* next = &parser->next;
|
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.
|
// 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->previous = parser->current;
|
||||||
parser->current = parser->next;
|
parser->current = parser->next;
|
||||||
|
|
||||||
@ -957,7 +991,7 @@ static void lexToken(Parser* parser) {
|
|||||||
if (parser->si_name_end != NULL) {
|
if (parser->si_name_end != NULL) {
|
||||||
if (parser->current_char == parser->si_name_end) {
|
if (parser->current_char == parser->si_name_end) {
|
||||||
parser->si_name_end = NULL;
|
parser->si_name_end = NULL;
|
||||||
eatString(parser, parser->si_name_quote == '\'');
|
eatString(compiler, parser->si_name_quote == '\'');
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
ASSERT(parser->current_char < parser->si_name_end, OOPS);
|
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];
|
char quote = parser->si_quote[parser->si_depth - 1];
|
||||||
parser->si_depth--; //< Exit the depth.
|
parser->si_depth--; //< Exit the depth.
|
||||||
eatString(parser, quote == '\'');
|
eatString(compiler, quote == '\'');
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} else { // Decrease the open brace at the current depth.
|
} else { // Decrease the open brace at the current depth.
|
||||||
@ -1043,7 +1077,8 @@ static void lexToken(Parser* parser) {
|
|||||||
setNextToken(parser, TK_DOTDOT); // '..'
|
setNextToken(parser, TK_DOTDOT); // '..'
|
||||||
} else if (utilIsDigit(peekChar(parser))) {
|
} else if (utilIsDigit(peekChar(parser))) {
|
||||||
eatChar(parser); // Consume the decimal point.
|
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 {
|
} else {
|
||||||
setNextToken(parser, TK_DOT); // '.'
|
setNextToken(parser, TK_DOT); // '.'
|
||||||
}
|
}
|
||||||
@ -1103,33 +1138,37 @@ static void lexToken(Parser* parser) {
|
|||||||
setNextTwoCharToken(parser, '=', TK_FSLASH, TK_DIVEQ);
|
setNextTwoCharToken(parser, '=', TK_FSLASH, TK_DIVEQ);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case '"': eatString(parser, false); return;
|
case '"': eatString(compiler, false); return;
|
||||||
|
|
||||||
case '\'': eatString(parser, true); return;
|
case '\'': eatString(compiler, true); return;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
|
||||||
if (utilIsDigit(c)) {
|
if (utilIsDigit(c)) {
|
||||||
eatNumber(parser);
|
eatNumber(compiler);
|
||||||
|
if (parser->has_syntax_error) return;
|
||||||
|
|
||||||
} else if (utilIsName(c)) {
|
} else if (utilIsName(c)) {
|
||||||
eatName(parser);
|
eatName(parser);
|
||||||
|
|
||||||
} else {
|
} 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);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser->token_start = parser->current_char;
|
||||||
setNextToken(parser, TK_EOF);
|
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.
|
// and return true otherwise return false.
|
||||||
static bool match(Compiler* compiler, TokenType expected) {
|
static bool match(Compiler* compiler, TokenType expected) {
|
||||||
if (peek(compiler) != expected) return false;
|
if (peek(compiler) != expected) return false;
|
||||||
lexToken(&(compiler->parser));
|
|
||||||
|
lexToken(compiler);
|
||||||
|
if (compiler->parser.has_syntax_error) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1154,15 +1196,13 @@ static bool match(Compiler* compiler, TokenType expected) {
|
|||||||
static void consume(Compiler* compiler, TokenType expected,
|
static void consume(Compiler* compiler, TokenType expected,
|
||||||
const char* err_msg) {
|
const char* err_msg) {
|
||||||
|
|
||||||
lexToken(&(compiler->parser));
|
lexToken(compiler);
|
||||||
if (compiler->parser.previous.type != expected) {
|
if (compiler->parser.has_syntax_error) return;
|
||||||
parseError(compiler, "%s", err_msg);
|
|
||||||
|
|
||||||
// If the next token is expected discard the current to minimize
|
Token *prev = &compiler->parser.previous;
|
||||||
// cascaded errors and continue parsing.
|
if (prev->type != expected) {
|
||||||
if (peek(compiler) == expected) {
|
syntaxError(compiler, *prev, "%s", err_msg);
|
||||||
lexToken(&(compiler->parser));
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1172,8 +1212,10 @@ static bool matchLine(Compiler* compiler) {
|
|||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
|
||||||
if (peek(compiler) == TK_LINE) {
|
if (peek(compiler) == TK_LINE) {
|
||||||
while (peek(compiler) == TK_LINE)
|
while (peek(compiler) == TK_LINE) {
|
||||||
lexToken(&(compiler->parser));
|
lexToken(compiler);
|
||||||
|
if (compiler->parser.has_syntax_error) return false;
|
||||||
|
}
|
||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1213,7 +1255,9 @@ static bool matchEndStatement(Compiler* compiler) {
|
|||||||
// Consume semi collon, multiple new lines or peek 'end' keyword.
|
// Consume semi collon, multiple new lines or peek 'end' keyword.
|
||||||
static void consumeEndStatement(Compiler* compiler) {
|
static void consumeEndStatement(Compiler* compiler) {
|
||||||
if (!matchEndStatement(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;
|
const char* msg;
|
||||||
if (delimiter == TK_DO) msg = "Expected enter block with newline or 'do'.";
|
if (delimiter == TK_DO) msg = "Expected enter block with newline or 'do'.";
|
||||||
else msg = "Expected enter block with newline or 'then'.";
|
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) {
|
if (func->ptr->upvalue_count == MAX_UPVALUES) {
|
||||||
parseError(compiler, "A function cannot capture more thatn %d upvalues.",
|
semanticError(compiler, compiler->parser.previous,
|
||||||
MAX_UPVALUES);
|
"A function cannot capture more thatn %d upvalues.", MAX_UPVALUES);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1455,7 +1500,7 @@ static int compilerAddConstant(Compiler* compiler, Var value);
|
|||||||
static int compilerAddVariable(Compiler* compiler, const char* name,
|
static int compilerAddVariable(Compiler* compiler, const char* name,
|
||||||
uint32_t length, int line);
|
uint32_t length, int line);
|
||||||
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
|
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);
|
static void compilerChangeStack(Compiler* compiler, int num);
|
||||||
|
|
||||||
// Forward declaration of grammar functions.
|
// Forward declaration of grammar functions.
|
||||||
@ -1587,8 +1632,12 @@ static void emitPushName(Compiler* compiler, NameDefnType type, int index) {
|
|||||||
ASSERT(index >= 0, OOPS);
|
ASSERT(index >= 0, OOPS);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NAME_NOT_DEFINED:
|
case NAME_NOT_DEFINED: {
|
||||||
|
if (compiler->parser.has_errors) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
case NAME_LOCAL_VAR:
|
case NAME_LOCAL_VAR:
|
||||||
if (index < 9) { //< 0..8 locals have single opcode.
|
if (index < 9) { //< 0..8 locals have single opcode.
|
||||||
@ -1629,8 +1678,10 @@ static void emitStoreName(Compiler* compiler, NameDefnType type, int index) {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case NAME_NOT_DEFINED:
|
case NAME_NOT_DEFINED:
|
||||||
case NAME_BUILTIN_FN:
|
case NAME_BUILTIN_FN:
|
||||||
case NAME_BUILTIN_TY:
|
case NAME_BUILTIN_TY: {
|
||||||
|
if (compiler->parser.has_errors) return;
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
case NAME_LOCAL_VAR:
|
case NAME_LOCAL_VAR:
|
||||||
if (index < 9) { //< 0..8 locals have single opcode.
|
if (index < 9) { //< 0..8 locals have single opcode.
|
||||||
@ -1722,9 +1773,11 @@ static void exprFunction(Compiler* compiler) {
|
|||||||
|
|
||||||
static void exprName(Compiler* compiler) {
|
static void exprName(Compiler* compiler) {
|
||||||
|
|
||||||
const char* start = compiler->parser.previous.start;
|
Token tkname = compiler->parser.previous;
|
||||||
int length = compiler->parser.previous.length;
|
|
||||||
int line = compiler->parser.previous.line;
|
const char* start = tkname.start;
|
||||||
|
int length = tkname.length;
|
||||||
|
int line = tkname.line;
|
||||||
NameSearchResult result = compilerSearchName(compiler, start, length);
|
NameSearchResult result = compilerSearchName(compiler, start, length);
|
||||||
|
|
||||||
if (compiler->l_value && matchAssignment(compiler)) {
|
if (compiler->l_value && matchAssignment(compiler)) {
|
||||||
@ -1767,8 +1820,14 @@ static void exprName(Compiler* compiler) {
|
|||||||
compileExpression(compiler);
|
compileExpression(compiler);
|
||||||
|
|
||||||
} else { // name += / -= / *= ... = (expr);
|
} else { // name += / -= / *= ... = (expr);
|
||||||
|
|
||||||
if (result.type == NAME_NOT_DEFINED) {
|
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.
|
// Push the named value.
|
||||||
@ -1806,11 +1865,12 @@ static void exprName(Compiler* compiler) {
|
|||||||
// a local depth.
|
// a local depth.
|
||||||
if (result.type == NAME_NOT_DEFINED) {
|
if (result.type == NAME_NOT_DEFINED) {
|
||||||
if (compiler->scope_depth == DEPTH_GLOBAL) {
|
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 {
|
} else {
|
||||||
emitOpcode(compiler, OP_PUSH_GLOBAL);
|
emitOpcode(compiler, OP_PUSH_GLOBAL);
|
||||||
int index = emitByte(compiler, 0xff);
|
int index = emitByte(compiler, 0xff);
|
||||||
compilerAddForward(compiler, index, _FN, start, length, line);
|
compilerAddForward(compiler, index, _FN, &tkname);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitPushName(compiler, result.type, result.index);
|
emitPushName(compiler, result.type, result.index);
|
||||||
@ -2066,19 +2126,24 @@ static void exprSelf(Compiler* compiler) {
|
|||||||
// inside a method.
|
// inside a method.
|
||||||
|
|
||||||
if (!compiler->parser.parsing_class) {
|
if (!compiler->parser.parsing_class) {
|
||||||
parseError(compiler, "Invalid use of 'self'.");
|
semanticError(compiler, compiler->parser.previous,
|
||||||
|
"Invalid use of 'self'.");
|
||||||
} else {
|
} else {
|
||||||
// FIXME:
|
// 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) {
|
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;
|
GrammarFn prefix = getRule(compiler->parser.previous.type)->prefix;
|
||||||
|
|
||||||
if (prefix == NULL) {
|
if (prefix == NULL) {
|
||||||
parseError(compiler, "Expected an expression.");
|
syntaxError(compiler, compiler->parser.previous,
|
||||||
|
"Expected an expression.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2092,7 +2157,8 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
|
|||||||
compiler->is_last_call = false;
|
compiler->is_last_call = false;
|
||||||
|
|
||||||
while (getRule(compiler->parser.current.type)->precedence >= precedence) {
|
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;
|
TokenType op = compiler->parser.previous.type;
|
||||||
GrammarFn infix = getRule(op)->infix;
|
GrammarFn infix = getRule(op)->infix;
|
||||||
@ -2131,8 +2197,8 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (max_vars_reached) {
|
if (max_vars_reached) {
|
||||||
parseError(compiler, "A module should contain at most %d %s.",
|
semanticError(compiler, compiler->parser.previous,
|
||||||
MAX_VARIABLES, var_type);
|
"A module should contain at most %d %s.", MAX_VARIABLES, var_type);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2156,10 +2222,10 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
|
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) {
|
if (compiler->parser.forwards_count == MAX_FORWARD_NAMES) {
|
||||||
parseError(compiler, "A module should contain at most %d implicit forward "
|
semanticError(compiler, *tkname, "A module should contain at most %d "
|
||||||
"function declarations.", MAX_FORWARD_NAMES);
|
"implicit forward function declarations.", MAX_FORWARD_NAMES);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2167,9 +2233,7 @@ static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
|
|||||||
compiler->parser.forwards_count++];
|
compiler->parser.forwards_count++];
|
||||||
forward->instruction = instruction;
|
forward->instruction = instruction;
|
||||||
forward->func = fn;
|
forward->func = fn;
|
||||||
forward->name = name;
|
forward->tkname = *tkname;
|
||||||
forward->length = length;
|
|
||||||
forward->line = line;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a literal constant to module literals and return it's index.
|
// 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.
|
vmPushTempRef(_vm, &cls->_super); // cls.
|
||||||
compiler->parser.parsing_class = true;
|
compiler->parser.parsing_class = true;
|
||||||
|
|
||||||
// Check count exceeded.
|
|
||||||
checkMaxConstantsReached(compiler, cls_index);
|
checkMaxConstantsReached(compiler, cls_index);
|
||||||
|
|
||||||
skipNewLines(compiler);
|
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
|
// 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.
|
// a top level statement, since there aren't any locals at the top level.
|
||||||
ASSERT(compiler->parser.has_errors ||
|
ASSERT(compiler->parser.has_errors ||
|
||||||
compiler->func->stack_size == 0, OOPS);
|
compiler->func->stack_size == 0, OOPS);
|
||||||
|
|
||||||
consume(compiler, TK_DEF, "Expected method definition.");
|
consume(compiler, TK_DEF, "Expected method definition.");
|
||||||
|
if (compiler->parser.has_syntax_error) break;
|
||||||
|
|
||||||
int fn_index = compileFunction(compiler, FUNC_METHOD);
|
int fn_index = compileFunction(compiler, FUNC_METHOD);
|
||||||
|
if (compiler->parser.has_syntax_error) break;
|
||||||
|
|
||||||
Var fn_var = compiler->module->constants.data[fn_index];
|
Var fn_var = compiler->module->constants.data[fn_index];
|
||||||
ASSERT(IS_OBJ_TYPE(fn_var, OBJ_FUNC), OOPS);
|
ASSERT(IS_OBJ_TYPE(fn_var, OBJ_FUNC), OOPS);
|
||||||
|
|
||||||
@ -2410,6 +2477,7 @@ static int compileClass(Compiler* compiler) {
|
|||||||
compiler->func->stack_size == 0, OOPS);
|
compiler->func->stack_size == 0, OOPS);
|
||||||
|
|
||||||
skipNewLines(compiler);
|
skipNewLines(compiler);
|
||||||
|
if (compiler->parser.has_syntax_error) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
compiler->parser.parsing_class = false;
|
compiler->parser.parsing_class = false;
|
||||||
@ -2437,6 +2505,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
|||||||
int fn_index;
|
int fn_index;
|
||||||
Function* func = newFunction(compiler->parser.vm, name, name_length,
|
Function* func = newFunction(compiler->parser.vm, name, name_length,
|
||||||
compiler->module, false, NULL, &fn_index);
|
compiler->module, false, NULL, &fn_index);
|
||||||
|
|
||||||
checkMaxConstantsReached(compiler, fn_index);
|
checkMaxConstantsReached(compiler, fn_index);
|
||||||
|
|
||||||
if (fn_type != FUNC_LITERAL) {
|
if (fn_type != FUNC_LITERAL) {
|
||||||
@ -2482,7 +2551,8 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (predefined) {
|
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,
|
compilerAddVariable(compiler, param_name, param_len,
|
||||||
@ -2585,8 +2655,10 @@ static Module* importFile(Compiler* compiler, const char* path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resolved.string == NULL) {
|
if (resolved.string == NULL) {
|
||||||
parseError(compiler, "Cannot resolve path '%s' from '%s'", path,
|
semanticError(compiler, compiler->parser.previous,
|
||||||
compiler->module->path->data);
|
"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.
|
// 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
|
// The script not exists in the VM, make sure we have the script loading
|
||||||
// api function.
|
// api function.
|
||||||
if (vm->config.load_script_fn == NULL) {
|
if (vm->config.load_script_fn == NULL) {
|
||||||
parseError(compiler, "Cannot import. The hosting application haven't "
|
semanticError(compiler, compiler->parser.previous,
|
||||||
"registered the script loading API");
|
"Cannot import. The hosting application haven't registered "
|
||||||
|
"the script loading API");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the script at the path.
|
// Load the script at the path.
|
||||||
PkStringPtr source = vm->config.load_script_fn(vm, path_->data);
|
PkStringPtr source = vm->config.load_script_fn(vm, path_->data);
|
||||||
if (source.string == NULL) {
|
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;
|
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 (source.on_done != NULL) source.on_done(vm, source);
|
||||||
|
|
||||||
if (result != PK_RESULT_SUCCESS) {
|
if (result != PK_RESULT_SUCCESS) {
|
||||||
parseError(compiler, "Compilation of imported script '%s' failed",
|
semanticError(compiler, compiler->parser.previous,
|
||||||
path_->data);
|
"Compilation of imported script '%s' failed", path_->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
@ -2677,7 +2751,8 @@ static Module* importCoreLib(Compiler* compiler, const char* name_start,
|
|||||||
|
|
||||||
Module* imported = vmGetModule(compiler->parser.vm, module_name);
|
Module* imported = vmGetModule(compiler->parser.vm, module_name);
|
||||||
if (imported == NULL) {
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2707,7 +2782,8 @@ static inline Module* compilerImport(Compiler* compiler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invalid token after import/from keyword.
|
// 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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2725,6 +2801,7 @@ static int compilerImportName(Compiler* compiler, int line,
|
|||||||
return compilerAddVariable(compiler, name, length, line);
|
return compilerAddVariable(compiler, name, length, line);
|
||||||
|
|
||||||
case NAME_LOCAL_VAR:
|
case NAME_LOCAL_VAR:
|
||||||
|
case NAME_UPVALUE:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
|
||||||
case NAME_GLOBAL_VAR:
|
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
|
// should pass) and allow imported entries to have the same name of
|
||||||
// builtin functions.
|
// builtin functions.
|
||||||
case NAME_BUILTIN_FN:
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3061,7 +3140,8 @@ static void compileStatement(Compiler* compiler) {
|
|||||||
|
|
||||||
if (match(compiler, TK_BREAK)) {
|
if (match(compiler, TK_BREAK)) {
|
||||||
if (compiler->loop == NULL) {
|
if (compiler->loop == NULL) {
|
||||||
parseError(compiler, "Cannot use 'break' outside a loop.");
|
syntaxError(compiler, compiler->parser.previous,
|
||||||
|
"Cannot use 'break' outside a loop.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3078,7 +3158,8 @@ static void compileStatement(Compiler* compiler) {
|
|||||||
|
|
||||||
} else if (match(compiler, TK_CONTINUE)) {
|
} else if (match(compiler, TK_CONTINUE)) {
|
||||||
if (compiler->loop == NULL) {
|
if (compiler->loop == NULL) {
|
||||||
parseError(compiler, "Cannot use 'continue' outside a loop.");
|
syntaxError(compiler, compiler->parser.previous,
|
||||||
|
"Cannot use 'continue' outside a loop.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3091,7 +3172,8 @@ static void compileStatement(Compiler* compiler) {
|
|||||||
} else if (match(compiler, TK_RETURN)) {
|
} else if (match(compiler, TK_RETURN)) {
|
||||||
|
|
||||||
if (compiler->scope_depth == DEPTH_GLOBAL) {
|
if (compiler->scope_depth == DEPTH_GLOBAL) {
|
||||||
parseError(compiler, "Invalid 'return' outside a function.");
|
syntaxError(compiler, compiler->parser.previous,
|
||||||
|
"Invalid 'return' outside a function.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3109,7 +3191,8 @@ static void compileStatement(Compiler* compiler) {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (compiler->func->type == FUNC_CONSTRUCTOR) {
|
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.
|
compileExpression(compiler); //< Return value is at stack top.
|
||||||
@ -3180,8 +3263,8 @@ static void compileTopLevelStatement(Compiler* compiler) {
|
|||||||
compileRegularImport(compiler);
|
compileRegularImport(compiler);
|
||||||
|
|
||||||
} else if (match(compiler, TK_MODULE)) {
|
} else if (match(compiler, TK_MODULE)) {
|
||||||
parseError(compiler, "Module name must be the first statement "
|
syntaxError(compiler, compiler->parser.previous,
|
||||||
"of the script.");
|
"Module name must be the first statement of the script.");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
compileStatement(compiler);
|
compileStatement(compiler);
|
||||||
@ -3196,6 +3279,8 @@ static void compileTopLevelStatement(Compiler* compiler) {
|
|||||||
PkResult compile(PKVM* vm, Module* module, const char* source,
|
PkResult compile(PKVM* vm, Module* module, const char* source,
|
||||||
const PkCompileOptions* options) {
|
const PkCompileOptions* options) {
|
||||||
|
|
||||||
|
ASSERT(module != NULL, OOPS);
|
||||||
|
|
||||||
// Skip utf8 BOM if there is any.
|
// Skip utf8 BOM if there is any.
|
||||||
if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
|
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
|
// the native api function (pkNewModule() that'll return a module without a
|
||||||
// main function) so just create and add the function here.
|
// main function) so just create and add the function here.
|
||||||
if (module->body == NULL) moduleAddMain(vm, module);
|
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
|
// 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.
|
// 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);
|
compilerPushFunc(compiler, &curr_fn, module->body->fn, FUNC_MAIN);
|
||||||
|
|
||||||
// Lex initial tokens. current <-- next.
|
// Lex initial tokens. current <-- next.
|
||||||
lexToken(&(compiler->parser));
|
lexToken(compiler);
|
||||||
lexToken(&(compiler->parser));
|
lexToken(compiler);
|
||||||
skipNewLines(compiler);
|
skipNewLines(compiler);
|
||||||
|
|
||||||
if (match(compiler, TK_MODULE)) {
|
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
|
// application module attribute might already set. In that case make it
|
||||||
// Compile error.
|
// Compile error.
|
||||||
if (module->name != NULL) {
|
if (module->name != NULL) {
|
||||||
parseError(compiler, "Module name already defined.");
|
syntaxError(compiler, compiler->parser.previous,
|
||||||
|
"Module name already defined.");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
consume(compiler, TK_NAME, "Expected a name for the module.");
|
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);
|
compileTopLevelStatement(compiler);
|
||||||
skipNewLines(compiler);
|
skipNewLines(compiler);
|
||||||
}
|
}
|
||||||
@ -3257,20 +3344,23 @@ PkResult compile(PKVM* vm, Module* module, const char* source,
|
|||||||
emitFunctionEnd(compiler);
|
emitFunctionEnd(compiler);
|
||||||
|
|
||||||
// Resolve forward names (function names that are used before defined).
|
// Resolve forward names (function names that are used before defined).
|
||||||
for (int i = 0; i < compiler->parser.forwards_count; i++) {
|
if (!compiler->parser.has_syntax_error) {
|
||||||
ForwardName* forward = &compiler->parser.forwards[i];
|
for (int i = 0; i < compiler->parser.forwards_count; i++) {
|
||||||
const char* name = forward->name;
|
ForwardName* forward = &compiler->parser.forwards[i];
|
||||||
int length = forward->length;
|
const char* name = forward->tkname.start;
|
||||||
int index = moduleGetGlobalIndex(compiler->module, name, (uint32_t)length);
|
int length = forward->tkname.length;
|
||||||
if (index != -1) {
|
int index = moduleGetGlobalIndex(compiler->module, name,
|
||||||
patchForward(compiler, forward->func, forward->instruction, index);
|
(uint32_t)length);
|
||||||
} else {
|
if (index != -1) {
|
||||||
// need_more_lines is only true for unexpected EOF errors. For syntax
|
patchForward(compiler, forward->func, forward->instruction, index);
|
||||||
// errors it'll be false by now but. Here it's a semantic errors, so
|
} else {
|
||||||
// we're overriding it to false.
|
// need_more_lines is only true for unexpected EOF errors. For syntax
|
||||||
compiler->parser.need_more_lines = false;
|
// errors it'll be false by now but. Here it's a semantic errors, so
|
||||||
resolveError(compiler, forward->line, "Name '%.*s' is not defined.",
|
// we're overriding it to false.
|
||||||
length, name);
|
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;
|
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,
|
PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source,
|
||||||
const PkCompileOptions* options) {
|
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.");
|
"Given handle is not a module.");
|
||||||
|
|
||||||
Module* module = (Module*)AS_OBJ(module_handle->value);
|
Module* module = (Module*)AS_OBJ(module_handle->value);
|
||||||
|
|
||||||
PkResult result = compile(vm, module, source.string, options);
|
PkResult result = compile(vm, module, source.string, options);
|
||||||
|
@ -480,8 +480,8 @@ DEF(coreHelp,
|
|||||||
|
|
||||||
if (argc == 0) {
|
if (argc == 0) {
|
||||||
// If there ins't an io function callback, we're done.
|
// 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);
|
||||||
vm->config.write_fn(vm, "TODO: print help here\n");
|
vm->config.stdout_write(vm, "TODO: print help here\n");
|
||||||
|
|
||||||
} else if (argc == 1) {
|
} else if (argc == 1) {
|
||||||
|
|
||||||
@ -492,15 +492,15 @@ DEF(coreHelp,
|
|||||||
if (!validateArgClosure(vm, 1, &closure)) return;
|
if (!validateArgClosure(vm, 1, &closure)) return;
|
||||||
|
|
||||||
// If there ins't an io function callback, we're done.
|
// 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) {
|
if (closure->fn->docstring != NULL) {
|
||||||
vm->config.write_fn(vm, closure->fn->docstring);
|
vm->config.stdout_write(vm, closure->fn->docstring);
|
||||||
vm->config.write_fn(vm, "\n\n");
|
vm->config.stdout_write(vm, "\n\n");
|
||||||
} else {
|
} else {
|
||||||
vm->config.write_fn(vm, "function '");
|
vm->config.stdout_write(vm, "function '");
|
||||||
vm->config.write_fn(vm, closure->fn->name);
|
vm->config.stdout_write(vm, closure->fn->name);
|
||||||
vm->config.write_fn(vm, "()' doesn't have a docstring.\n");
|
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
|
// If the host application doesn't provide any write function, discard the
|
||||||
// output.
|
// output.
|
||||||
if (vm->config.write_fn == NULL) return;
|
if (vm->config.stdout_write == NULL) return;
|
||||||
|
|
||||||
for (int i = 1; i <= ARGC; i++) {
|
for (int i = 1; i <= ARGC; i++) {
|
||||||
if (i != 1) vm->config.write_fn(vm, " ");
|
if (i != 1) vm->config.stdout_write(vm, " ");
|
||||||
vm->config.write_fn(vm, toString(vm, ARG(i))->data);
|
vm->config.stdout_write(vm, toString(vm, ARG(i))->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
vm->config.write_fn(vm, "\n");
|
vm->config.stdout_write(vm, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
DEF(coreInput,
|
DEF(coreInput,
|
||||||
@ -670,13 +670,13 @@ DEF(coreInput,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the host application doesn't provide any write function, return.
|
// 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) {
|
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);
|
String* line = newString(vm, result.string);
|
||||||
if (result.on_done) result.on_done(vm, result);
|
if (result.on_done) result.on_done(vm, result);
|
||||||
RET(VAR_OBJ(line));
|
RET(VAR_OBJ(line));
|
||||||
@ -920,7 +920,7 @@ DEF(stdLangWrite,
|
|||||||
|
|
||||||
// If the host application doesn't provide any write function, discard the
|
// If the host application doesn't provide any write function, discard the
|
||||||
// output.
|
// output.
|
||||||
if (vm->config.write_fn == NULL) return;
|
if (vm->config.stdout_write == NULL) return;
|
||||||
|
|
||||||
String* str; //< Will be cleaned by garbage collector;
|
String* str; //< Will be cleaned by garbage collector;
|
||||||
|
|
||||||
@ -933,7 +933,7 @@ DEF(stdLangWrite,
|
|||||||
str = toString(vm, arg);
|
str = toString(vm, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
vm->config.write_fn(vm, str->data);
|
vm->config.stdout_write(vm, str->data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
211
src/pk_debug.c
211
src/pk_debug.c
@ -11,6 +11,209 @@
|
|||||||
#include "pk_value.h"
|
#include "pk_value.h"
|
||||||
#include "pk_vm.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.
|
// Opcode names array.
|
||||||
static const char* op_names[] = {
|
static const char* op_names[] = {
|
||||||
#define OPCODE(name, params, stack) #name,
|
#define OPCODE(name, params, stack) #name,
|
||||||
@ -19,20 +222,20 @@ static const char* op_names[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void dumpValue(PKVM* vm, Var value) {
|
static void dumpValue(PKVM* vm, Var value) {
|
||||||
if (!vm->config.write_fn) return;
|
if (!vm->config.stdout_write) return;
|
||||||
String* repr = toRepr(vm, value);
|
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.
|
// String repr will be garbage collected - No need to clean.
|
||||||
}
|
}
|
||||||
|
|
||||||
void dumpFunctionCode(PKVM* vm, Function* func) {
|
void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||||
|
|
||||||
if (!vm->config.write_fn) return;
|
if (!vm->config.stdout_write) return;
|
||||||
|
|
||||||
#define _INDENTATION " "
|
#define _INDENTATION " "
|
||||||
#define _INT_WIDTH 5 // Width of the integer string to print.
|
#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 NEWLINE() PRINT("\n")
|
||||||
|
|
||||||
#define _PRINT_INT(value, width) \
|
#define _PRINT_INT(value, width) \
|
||||||
|
@ -10,6 +10,14 @@
|
|||||||
#include "pk_internal.h"
|
#include "pk_internal.h"
|
||||||
#include "pk_value.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.
|
// Dump opcodes of the given function to the stdout.
|
||||||
void dumpFunctionCode(PKVM* vm, Function* func);
|
void dumpFunctionCode(PKVM* vm, Function* func);
|
||||||
|
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
#elif defined(__clang__)
|
#elif defined(__clang__)
|
||||||
#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
|
#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
|
||||||
#pragma clang diagnostic ignored "-Wunused-parameter"
|
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#pragma warning(disable:26812)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
@ -1630,6 +1630,8 @@ bool toBool(Var v) {
|
|||||||
case OBJ_RANGE: // [[FALLTHROUGH]]
|
case OBJ_RANGE: // [[FALLTHROUGH]]
|
||||||
case OBJ_MODULE:
|
case OBJ_MODULE:
|
||||||
case OBJ_FUNC:
|
case OBJ_FUNC:
|
||||||
|
case OBJ_CLOSURE:
|
||||||
|
case OBJ_UPVALUE:
|
||||||
case OBJ_FIBER:
|
case OBJ_FIBER:
|
||||||
case OBJ_CLASS:
|
case OBJ_CLASS:
|
||||||
case OBJ_INST:
|
case OBJ_INST:
|
||||||
|
30
src/pk_vm.c
30
src/pk_vm.c
@ -27,12 +27,14 @@ PkConfiguration pkNewConfiguration(void) {
|
|||||||
PkConfiguration config;
|
PkConfiguration config;
|
||||||
config.realloc_fn = defaultRealloc;
|
config.realloc_fn = defaultRealloc;
|
||||||
|
|
||||||
config.error_fn = NULL;
|
config.stdout_write = NULL;
|
||||||
config.write_fn = NULL;
|
config.stderr_write = NULL;
|
||||||
config.read_fn = NULL;
|
config.stdin_read = NULL;
|
||||||
|
|
||||||
config.load_script_fn = NULL;
|
config.load_script_fn = NULL;
|
||||||
config.resolve_path_fn = NULL;
|
config.resolve_path_fn = NULL;
|
||||||
|
|
||||||
|
config.use_ansi_color = false;
|
||||||
config.user_data = NULL;
|
config.user_data = NULL;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
@ -674,25 +676,15 @@ static void closeUpvalues(Fiber* fiber, Var* top) {
|
|||||||
|
|
||||||
fiber->open_upvalues = upvalue->next;
|
fiber->open_upvalues = upvalue->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reportError(PKVM* vm) {
|
static void reportError(PKVM* vm) {
|
||||||
ASSERT(VM_HAS_ERROR(vm), "runtimeError() should be called after an error.");
|
ASSERT(VM_HAS_ERROR(vm), "runtimeError() should be called after an error.");
|
||||||
|
|
||||||
// TODO: pass the error to the caller of the fiber.
|
// TODO: pass the error to the caller of the fiber.
|
||||||
|
|
||||||
// Print the Error message and stack trace.
|
if (vm->config.stderr_write == NULL) return;
|
||||||
if (vm->config.error_fn == NULL) return;
|
reportRuntimeError(vm, vm->fiber);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
@ -1701,11 +1693,11 @@ L_do_call:
|
|||||||
|
|
||||||
OPCODE(REPL_PRINT):
|
OPCODE(REPL_PRINT):
|
||||||
{
|
{
|
||||||
if (vm->config.write_fn != NULL) {
|
if (vm->config.stdout_write != NULL) {
|
||||||
Var tmp = PEEK(-1);
|
Var tmp = PEEK(-1);
|
||||||
if (!IS_NULL(tmp)) {
|
if (!IS_NULL(tmp)) {
|
||||||
vm->config.write_fn(vm, toRepr(vm, tmp)->data);
|
vm->config.stdout_write(vm, toRepr(vm, tmp)->data);
|
||||||
vm->config.write_fn(vm, "\n");
|
vm->config.stdout_write(vm, "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
|
@ -48,7 +48,7 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
// Pocket VM configuration.
|
// Pocket VM configuration.
|
||||||
PkConfiguration config = pkNewConfiguration();
|
PkConfiguration config = pkNewConfiguration();
|
||||||
config.write_fn = stdoutCallback;
|
config.stdout_write = stdoutCallback;
|
||||||
|
|
||||||
// Create a new pocket VM.
|
// Create a new pocket VM.
|
||||||
PKVM* vm = pkNewVM(&config);
|
PKVM* vm = pkNewVM(&config);
|
||||||
|
@ -96,7 +96,7 @@ void stdoutCallback(PKVM* vm, const char* text) {
|
|||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
PkConfiguration config = pkNewConfiguration();
|
PkConfiguration config = pkNewConfiguration();
|
||||||
config.write_fn = stdoutCallback;
|
config.stdout_write = stdoutCallback;
|
||||||
|
|
||||||
PKVM* vm = pkNewVM(&config);
|
PKVM* vm = pkNewVM(&config);
|
||||||
registerVector(vm);
|
registerVector(vm);
|
||||||
|
Loading…
Reference in New Issue
Block a user