mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-11 07:00:58 +08:00
refactor for repl support [2/3]
This commit is contained in:
parent
3092118008
commit
fdf685731d
@ -44,7 +44,7 @@ if not defined INCLUDE goto :MSVC_INIT
|
||||
goto :START
|
||||
|
||||
:MSVC_INIT
|
||||
call :ColorText 0f "Not running on MSVM prompt, searching for one..."
|
||||
call :ColorText 0f "Not running on MSVC prompt, searching for one..."
|
||||
echo.
|
||||
|
||||
:: Find vswhere
|
||||
@ -170,6 +170,7 @@ goto :END
|
||||
:FAIL
|
||||
call :ColorText 0c "Build failed. See the error messages."
|
||||
echo.
|
||||
exit /b 1
|
||||
goto :END
|
||||
|
||||
:END
|
||||
|
10
cli/TODO.txt
10
cli/TODO.txt
@ -1,5 +1,15 @@
|
||||
|
||||
// To implement.
|
||||
|
||||
- refactor build.bat batch script to powershell
|
||||
refactor makefile and setup a github action.
|
||||
|
||||
- change or add => to_string() to value.as_string
|
||||
and add as_repr, as_bool.
|
||||
str_lower("UPPER") to "UPPER".lower
|
||||
|
||||
- support new line just after '=' (and other places)
|
||||
|
||||
- Implement argparse.
|
||||
- -v --version
|
||||
- emit opcodes
|
||||
|
51
cli/main.c
51
cli/main.c
@ -9,7 +9,7 @@
|
||||
|
||||
#include <pocketlang.h>
|
||||
|
||||
// FIXME: everything below here is temproary and for testing.
|
||||
// FIXME: everything below here is temproary and for testing.
|
||||
|
||||
void registerModules(PKVM* vm);
|
||||
|
||||
@ -23,7 +23,14 @@ size_t pathJoin(const char* from, const char* path, char* buffer,
|
||||
|
||||
// ---------------------------------------
|
||||
|
||||
void errorPrint(PKVM* vm, PkErrorType type, const char* file, int line,
|
||||
void onResultDone(PKVM* vm, PkStringPtr result) {
|
||||
|
||||
if ((bool)result.user_data) {
|
||||
free((void*)result.string);
|
||||
}
|
||||
}
|
||||
|
||||
void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line,
|
||||
const char* message) {
|
||||
if (type == PK_ERROR_COMPILE) {
|
||||
fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line);
|
||||
@ -40,11 +47,24 @@ void writeFunction(PKVM* vm, const char* text) {
|
||||
fprintf(stdout, "%s", text);
|
||||
}
|
||||
|
||||
void onResultDone(PKVM* vm, PkStringPtr result) {
|
||||
static const char* read_line() {
|
||||
// FIXME: use fgetc char by char till reach a new line.
|
||||
const int size = 1024;
|
||||
char* mem = (char*) malloc(size);
|
||||
fgets(mem, size, stdin);
|
||||
size_t len = strlen(mem);
|
||||
|
||||
if ((bool)result.user_data) {
|
||||
free((void*)result.string);
|
||||
}
|
||||
// FIXME: handle \r\n, this is temp.
|
||||
mem[len - 1] = '\0';
|
||||
return mem;
|
||||
}
|
||||
|
||||
PkStringPtr readFunction(PKVM* vm) {
|
||||
PkStringPtr result;
|
||||
result.string = read_line();
|
||||
result.on_done = onResultDone;
|
||||
result.user_data = (void*)true;
|
||||
return result;
|
||||
}
|
||||
|
||||
PkStringPtr resolvePath(PKVM* vm, const char* from, const char* path) {
|
||||
@ -135,22 +155,31 @@ int main(int argc, char** argv) {
|
||||
pathInit();
|
||||
|
||||
PkConfiguration config = pkNewConfiguration();
|
||||
config.error_fn = errorPrint;
|
||||
config.error_fn = errorFunction;
|
||||
config.write_fn = writeFunction;
|
||||
config.read_fn = readFunction;
|
||||
config.load_script_fn = loadScript;
|
||||
config.resolve_path_fn = resolvePath;
|
||||
|
||||
PkCompileOptions options = pkNewCompilerOptions();
|
||||
options.debug = true; // TODO: update this with cli args.
|
||||
|
||||
PKVM* vm = pkNewVM(&config);
|
||||
registerModules(vm);
|
||||
PkInterpretResult result;
|
||||
PkResult result;
|
||||
|
||||
// FIXME: this is temp till arg parse implemented.
|
||||
if (argc >= 3 && strcmp(argv[1], "-c") == 0) {
|
||||
|
||||
if (argc == 1) {
|
||||
// TODO:
|
||||
//PkHandle* module = pkNewModule(vm, "$(REPL)");
|
||||
|
||||
} if (argc >= 3 && strcmp(argv[1], "-c") == 0) {
|
||||
|
||||
PkStringPtr source = { argv[2], NULL, NULL };
|
||||
PkStringPtr path = { "$(Source)", NULL, NULL };
|
||||
|
||||
result = pkInterpretSource(vm, source, path);
|
||||
result = pkInterpretSource(vm, source, path, NULL);
|
||||
pkFreeVM(vm);
|
||||
return result;
|
||||
}
|
||||
@ -159,7 +188,7 @@ int main(int argc, char** argv) {
|
||||
PkStringPtr source = loadScript(vm, resolved.string);
|
||||
|
||||
if (source.string != NULL) {
|
||||
result = pkInterpretSource(vm, source, resolved);
|
||||
result = pkInterpretSource(vm, source, resolved, &options);
|
||||
|
||||
} else {
|
||||
result = PK_RESULT_COMPILE_ERROR;
|
||||
|
@ -164,9 +164,4 @@
|
||||
// + 1 for null byte '\0'
|
||||
#define STR_INT_BUFF_SIZE 12
|
||||
|
||||
/*****************************************************************************/
|
||||
/* INTERNAL TYPE DEFINES */
|
||||
/*****************************************************************************/
|
||||
|
||||
|
||||
#endif //PK_COMMON_H
|
||||
|
140
src/compiler.c
140
src/compiler.c
@ -40,6 +40,9 @@
|
||||
// available in C++98.
|
||||
#define ERROR_MESSAGE_SIZE 256
|
||||
|
||||
// The name of a literal function.
|
||||
#define LITERAL_FN_NAME "$(LiteralFn)"
|
||||
|
||||
/*****************************************************************************
|
||||
* TOKENS *
|
||||
*****************************************************************************/
|
||||
@ -328,6 +331,8 @@ struct Compiler {
|
||||
Token previous, current, next; //< Currently parsed tokens.
|
||||
bool has_errors; //< True if any syntex error occured at.
|
||||
|
||||
const PkCompileOptions* options; //< To configure the compilation.
|
||||
|
||||
// Current depth the compiler in (-1 means top level) 0 means function
|
||||
// level and > 0 is inner scope.
|
||||
int scope_depth;
|
||||
@ -479,7 +484,7 @@ static void eatString(Compiler* compiler, bool single_quote) {
|
||||
|
||||
// '\0' will be added by varNewSring();
|
||||
Var string = VAR_OBJ(newStringLength(compiler->vm, (const char*)buff.data,
|
||||
(uint32_t)buff.count));
|
||||
(uint32_t)buff.count));
|
||||
|
||||
byteBufferClear(&buff, compiler->vm);
|
||||
|
||||
@ -879,7 +884,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
|
||||
int index; // For storing the search result below.
|
||||
|
||||
// Search through globals.
|
||||
index = scriptSearchGlobals(compiler->script, name, length);
|
||||
index = scriptGetGlobals(compiler->script, name, length);
|
||||
if (index != -1) {
|
||||
result.type = NAME_GLOBAL_VAR;
|
||||
result.index = index;
|
||||
@ -887,7 +892,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
|
||||
}
|
||||
|
||||
// Search through functions.
|
||||
index = scriptSearchFunc(compiler->script, name, length);
|
||||
index = scriptGetFunc(compiler->script, name, length);
|
||||
if (index != -1) {
|
||||
result.type = NAME_FUNCTION;
|
||||
result.index = index;
|
||||
@ -1453,7 +1458,7 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
|
||||
*****************************************************************************/
|
||||
|
||||
static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
|
||||
Script* script) {
|
||||
Script* script, const PkCompileOptions* options) {
|
||||
|
||||
compiler->vm = vm;
|
||||
compiler->next_compiler = NULL;
|
||||
@ -1462,6 +1467,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
|
||||
compiler->script = script;
|
||||
compiler->token_start = source;
|
||||
compiler->has_errors = false;
|
||||
compiler->options = options;
|
||||
|
||||
compiler->current_char = source;
|
||||
compiler->current_line = 1;
|
||||
@ -1689,7 +1695,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
}
|
||||
|
||||
} else {
|
||||
name = "$(LiteralFn)";
|
||||
name = LITERAL_FN_NAME;
|
||||
name_length = (int)strlen(name);
|
||||
}
|
||||
|
||||
@ -1860,8 +1866,14 @@ static Script* importFile(Compiler* compiler, const char* path) {
|
||||
emitOpcode(compiler, OP_IMPORT);
|
||||
emitShort(compiler, index);
|
||||
|
||||
// Option for the compilation, even if we're running on repl mode the
|
||||
// imported script cannot run on repl mode.
|
||||
PkCompileOptions options = pkNewCompilerOptions();
|
||||
if (compiler->options) options = *compiler->options;
|
||||
options.repl_mode = false;
|
||||
|
||||
// Compile the source to the script and clean the source.
|
||||
bool success = compile(vm, scr, source.string);
|
||||
bool success = compile(vm, scr, source.string, &options);
|
||||
if (source.on_done != NULL) source.on_done(vm, source);
|
||||
|
||||
if (!success) parseError(compiler, "Compilation of imported script "
|
||||
@ -2247,6 +2259,11 @@ static void compileForStatement(Compiler* compiler) {
|
||||
// variable declaration, which will be handled.
|
||||
static void compileStatement(Compiler* compiler) {
|
||||
|
||||
// is_temproary will be set to true if the statement is an temporary
|
||||
// expression, it'll used to be pop from the stack. If running REPL mode used
|
||||
// to print it's value.
|
||||
bool is_temproary = false;
|
||||
|
||||
if (match(compiler, TK_BREAK)) {
|
||||
if (compiler->loop == NULL) {
|
||||
parseError(compiler, "Cannot use 'break' outside a loop.");
|
||||
@ -2304,22 +2321,55 @@ static void compileStatement(Compiler* compiler) {
|
||||
compiler->new_local = false;
|
||||
compileExpression(compiler);
|
||||
consumeEndStatement(compiler);
|
||||
if (!compiler->new_local) {
|
||||
// Pop the temp.
|
||||
emitOpcode(compiler, OP_POP);
|
||||
}
|
||||
if (!compiler->new_local) is_temproary = true;
|
||||
compiler->new_local = false;
|
||||
}
|
||||
|
||||
// If running REPL mode, print the expression's evaluvated value. Python
|
||||
// does print local depth expression too. (it's just a design decision).
|
||||
if (compiler->options && compiler->options->repl_mode &&
|
||||
is_temproary /*&& compiler->scope_depth == DEPTH_GLOBAL*/) {
|
||||
emitOpcode(compiler, OP_REPL_PRINT);
|
||||
}
|
||||
|
||||
if (is_temproary) emitOpcode(compiler, OP_POP);
|
||||
|
||||
}
|
||||
|
||||
// Compile statements that are only valid at the top level of the script. Such
|
||||
// as import statement, function define, and if we're running REPL mode top
|
||||
// level expression's evaluvated value will be printed.
|
||||
static void compileTopLevelStatement(Compiler* compiler) {
|
||||
if (match(compiler, TK_NATIVE)) {
|
||||
compileFunction(compiler, FN_NATIVE);
|
||||
|
||||
} else if (match(compiler, TK_DEF)) {
|
||||
compileFunction(compiler, FN_SCRIPT);
|
||||
|
||||
} else if (match(compiler, TK_FROM)) {
|
||||
compileFromImport(compiler);
|
||||
|
||||
} else if (match(compiler, TK_IMPORT)) {
|
||||
compileRegularImport(compiler);
|
||||
|
||||
} else if (match(compiler, TK_MODULE)) {
|
||||
parseError(compiler, "Module name must be the first statement "
|
||||
"of the script.");
|
||||
|
||||
} else {
|
||||
compileStatement(compiler);
|
||||
}
|
||||
}
|
||||
|
||||
bool compile(PKVM* vm, Script* script, const char* source) {
|
||||
bool compile(PKVM* vm, Script* script, const char* source,
|
||||
const PkCompileOptions* options) {
|
||||
|
||||
// Skip utf8 BOM if there is any.
|
||||
if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
|
||||
|
||||
Compiler _compiler;
|
||||
Compiler* compiler = &_compiler; //< Compiler pointer for quick access.
|
||||
compilerInit(compiler, vm, source, script);
|
||||
compilerInit(compiler, vm, source, script, options);
|
||||
|
||||
// If compiling for an imported script the vm->compiler would be the compiler
|
||||
// of the script that imported this script. Add the all the compilers into a
|
||||
@ -2327,6 +2377,12 @@ bool compile(PKVM* vm, Script* script, const char* source) {
|
||||
compiler->next_compiler = vm->compiler;
|
||||
vm->compiler = compiler;
|
||||
|
||||
// Remember the count of the (valid) code of the provided script body. For
|
||||
// new scripts it'll be null, but if we're compiling it multiple times we
|
||||
// already have some code before. And if the compilation failed we discard
|
||||
// all the compiled opcodes and jump back to the valid_code.
|
||||
uint32_t valid_code = script->body->fn->opcodes.count;
|
||||
|
||||
Func curr_fn;
|
||||
curr_fn.depth = DEPTH_SCRIPT;
|
||||
curr_fn.ptr = script->body;
|
||||
@ -2356,40 +2412,25 @@ bool compile(PKVM* vm, Script* script, const char* source) {
|
||||
}
|
||||
|
||||
while (!match(compiler, TK_EOF)) {
|
||||
|
||||
if (match(compiler, TK_NATIVE)) {
|
||||
compileFunction(compiler, FN_NATIVE);
|
||||
|
||||
} else if (match(compiler, TK_DEF)) {
|
||||
compileFunction(compiler, FN_SCRIPT);
|
||||
|
||||
} else if (match(compiler, TK_FROM)) {
|
||||
compileFromImport(compiler);
|
||||
|
||||
} else if (match(compiler, TK_IMPORT)) {
|
||||
compileRegularImport(compiler);
|
||||
|
||||
} else if (match(compiler, TK_MODULE)) {
|
||||
parseError(compiler, "Module name must be the first statement "
|
||||
"of the script.");
|
||||
|
||||
} else {
|
||||
compileStatement(compiler);
|
||||
}
|
||||
|
||||
compileTopLevelStatement(compiler);
|
||||
skipNewLines(compiler);
|
||||
}
|
||||
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
emitOpcode(compiler, OP_END);
|
||||
if (compiler->options == NULL || !compiler->options->repl_mode) {
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
emitOpcode(compiler, OP_END);
|
||||
|
||||
} else {
|
||||
emitOpcode(compiler, OP_YIELD);
|
||||
}
|
||||
|
||||
// Resolve forward names (function names that are used before defined).
|
||||
for (int i = 0; i < compiler->forwards_count; i++) {
|
||||
ForwardName* forward = &compiler->forwards[i];
|
||||
const char* name = forward->name;
|
||||
int length = forward->length;
|
||||
int index = scriptSearchFunc(script, name, (uint32_t)length);
|
||||
int index = scriptGetFunc(script, name, (uint32_t)length);
|
||||
if (index != -1) {
|
||||
patchForward(compiler, forward->func, forward->instruction, index);
|
||||
} else {
|
||||
@ -2404,8 +2445,29 @@ bool compile(PKVM* vm, Script* script, const char* source) {
|
||||
dumpFunctionCode(vm, script->body);
|
||||
#endif
|
||||
|
||||
// Return true if success.
|
||||
return !(compiler->has_errors);
|
||||
// If the compilation failed discard all the compiled invalid code.
|
||||
// TODO: May be shrink the buffer.
|
||||
if (compiler->has_errors) {
|
||||
script->body->fn->opcodes.count = valid_code;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we reach here, it means the compilation is success and return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source,
|
||||
const PkCompileOptions* options) {
|
||||
__ASSERT(module != NULL, "Argument module was NULL.");
|
||||
Var scr = module->value;
|
||||
__ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module");
|
||||
Script* script = (Script*)AS_OBJ(scr);
|
||||
|
||||
bool success = compile(vm, script, source.string, options);
|
||||
if (source.on_done) source.on_done(vm, source);
|
||||
|
||||
if (!success) return PK_RESULT_COMPILE_ERROR;
|
||||
return PK_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void compilerMarkObjects(PKVM* vm, Compiler* compiler) {
|
||||
|
@ -25,8 +25,9 @@ typedef enum {
|
||||
typedef struct Compiler Compiler;
|
||||
|
||||
// This will take source code as a cstring, compiles it to pocketlang bytecodes
|
||||
// and append them to the script's implicit main (the $SourceBody function).
|
||||
bool compile(PKVM* vm, Script* script, const char* source);
|
||||
// and append them to the script's implicit main function ("$(SourceBody)").
|
||||
bool compile(PKVM* vm, Script* script, const char* source,
|
||||
const PkCompileOptions* options);
|
||||
|
||||
// Mark the heap allocated ojbects of the compiler at the garbage collection
|
||||
// called at the marking phase of vmCollectGarbage().
|
||||
|
114
src/core.c
114
src/core.c
@ -14,7 +14,7 @@
|
||||
#include "vm.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* PUBLIC API */
|
||||
/* CORE PUBLIC API */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Create a new module with the given [name] and returns as a Script* for
|
||||
@ -36,15 +36,44 @@ PkHandle* pkNewModule(PKVM* vm, const char* name) {
|
||||
void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
|
||||
pkNativeFn fptr, int arity) {
|
||||
__ASSERT(module != NULL, "Argument module was NULL.");
|
||||
|
||||
Var scr = module->value;
|
||||
__ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module");
|
||||
|
||||
moduleAddFunctionInternal(vm, (Script*)AS_OBJ(scr), name, fptr, arity);
|
||||
}
|
||||
|
||||
PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
|
||||
const char* name) {
|
||||
__ASSERT(module != NULL, "Argument module was NULL.");
|
||||
Var scr = module->value;
|
||||
__ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module");
|
||||
Script* script = (Script*)AS_OBJ(scr);
|
||||
|
||||
// TODO: Currently it's O(n) and could be optimized to O(log(n)) but does it
|
||||
// worth it?
|
||||
//
|
||||
// 'function_names' buffer is un-necessary since the function itself has the
|
||||
// reference to the function name and it can be refactored into a index buffer
|
||||
// in an "increasing-name" order which can be used to binary search. Similer
|
||||
// for 'global_names' refactor them from VarBuffer to GlobalVarBuffer where
|
||||
// GlobalVar is struct { const char* name, Var value };
|
||||
//
|
||||
// "nicreasing-name" order index buffer:
|
||||
// A buffer of int where each is an index in the function buffer and each
|
||||
// points to different functions in an "increasing-name" (could be hash
|
||||
// value) order. If we have more than some threshold number of function
|
||||
// use binary search. (remember to skip literal functions).
|
||||
for (uint32_t i = 0; i < script->functions.count; i++) {
|
||||
const char* fn_name = script->functions.data[i]->name;
|
||||
if (strcmp(name, fn_name) == 0) {
|
||||
return vmNewHandle(vm, VAR_OBJ(script->functions.data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// A convinent macro to get the nth (1 based) argument of the current function.
|
||||
#define ARG(n) vm->fiber->ret[n]
|
||||
#define ARG(n) (vm->fiber->ret[n])
|
||||
|
||||
// Convinent macros to get the 1st, 2nd, 3rd arguments.
|
||||
#define ARG1 ARG(1)
|
||||
@ -260,7 +289,7 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
|
||||
VALIDATE_ARG_OBJ(Fiber, OBJ_FIBER, "fiber")
|
||||
|
||||
/*****************************************************************************/
|
||||
/* BUILTIN FUNCTIONS API */
|
||||
/* SHARED FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// findBuiltinFunction implementation (see core.h for description).
|
||||
@ -299,12 +328,12 @@ Script* getCoreLib(const PKVM* vm, String* name) {
|
||||
/*****************************************************************************/
|
||||
|
||||
#define FN_IS_PRIMITE_TYPE(name, check) \
|
||||
void coreIs##name(PKVM* vm) { \
|
||||
static void coreIs##name(PKVM* vm) { \
|
||||
RET(VAR_BOOL(check(ARG1))); \
|
||||
}
|
||||
|
||||
#define FN_IS_OBJ_TYPE(name, _enum) \
|
||||
void coreIs##name(PKVM* vm) { \
|
||||
static void coreIs##name(PKVM* vm) { \
|
||||
Var arg1 = ARG1; \
|
||||
if (IS_OBJ_TYPE(arg1, _enum)) { \
|
||||
RET(VAR_TRUE); \
|
||||
@ -370,20 +399,7 @@ PK_DOC(coreYield,
|
||||
RET_ERR(newString(vm, "Invalid argument count."));
|
||||
}
|
||||
|
||||
Fiber* caller = vm->fiber->caller;
|
||||
|
||||
// Return the yield value to the caller fiber.
|
||||
if (caller != NULL) {
|
||||
if (argc == 0) *caller->ret = VAR_NULL;
|
||||
else *caller->ret = ARG1;
|
||||
}
|
||||
|
||||
// Can be resumed by another caller fiber.
|
||||
vm->fiber->caller = NULL;
|
||||
vm->fiber->state = FIBER_YIELDED;
|
||||
vm->fiber = caller;
|
||||
|
||||
return;
|
||||
vmYieldFiber(vm, (argc == 1) ? &ARG1 : NULL);
|
||||
}
|
||||
|
||||
PK_DOC(coreToString,
|
||||
@ -400,24 +416,36 @@ PK_DOC(corePrint,
|
||||
// output.
|
||||
if (vm->config.write_fn == NULL) return;
|
||||
|
||||
String* str; //< Will be cleaned by garbage collector;
|
||||
|
||||
for (int i = 1; i <= ARGC; i++) {
|
||||
Var arg = ARG(i);
|
||||
// If it's already a string don't allocate a new string instead use it.
|
||||
if (IS_OBJ_TYPE(arg, OBJ_STRING)) {
|
||||
str = (String*)AS_OBJ(arg);
|
||||
} else {
|
||||
str = toString(vm, arg);
|
||||
}
|
||||
|
||||
if (i != 1) vm->config.write_fn(vm, " ");
|
||||
vm->config.write_fn(vm, str->data);
|
||||
vm->config.write_fn(vm, toString(vm, ARG(i))->data);
|
||||
}
|
||||
|
||||
vm->config.write_fn(vm, "\n");
|
||||
}
|
||||
|
||||
PK_DOC(coreInput,
|
||||
"input([msg:var]) -> string\n"
|
||||
"Read a line from stdin and returns it without the line ending. Accepting "
|
||||
"an optional argument [msg] and prints it before reading.") {
|
||||
int argc = ARGC;
|
||||
if (argc != 1 && argc != 2) {
|
||||
RET_ERR(newString(vm, "Invalid argument count."));
|
||||
}
|
||||
|
||||
// If the host appliaction donesn't provide any write function, return.
|
||||
if (vm->config.read_fn == NULL) return;
|
||||
|
||||
if (argc == 1) {
|
||||
vm->config.write_fn(vm, toString(vm, ARG1)->data);
|
||||
}
|
||||
|
||||
PkStringPtr result = vm->config.read_fn(vm);
|
||||
String* line = newString(vm, result.string);
|
||||
if (result.on_done) result.on_done(vm, result);
|
||||
RET(VAR_OBJ(line));
|
||||
}
|
||||
|
||||
// String functions.
|
||||
// -----------------
|
||||
PK_DOC(coreStrLower,
|
||||
@ -558,6 +586,8 @@ PK_DOC(coreFiberRun,
|
||||
Fiber* fb;
|
||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||
|
||||
ASSERT(fb->func->arity >= -1 , OOPS " (Forget to initialize arity.)");
|
||||
|
||||
if (argc - 1 != fb->func->arity) {
|
||||
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fb->func->arity);
|
||||
RET_ERR(stringFormat(vm, "Expected excatly $ argument(s).", buff));
|
||||
@ -691,13 +721,13 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script,
|
||||
int arity) {
|
||||
|
||||
// Check if function with the same name already exists.
|
||||
if (scriptSearchFunc(script, name, (uint32_t)strlen(name)) != -1) {
|
||||
if (scriptGetFunc(script, name, (uint32_t)strlen(name)) != -1) {
|
||||
__ASSERT(false, stringFormat(vm, "A function named '$' already esists "
|
||||
"on module '@'", name, script->moudle)->data);
|
||||
}
|
||||
|
||||
// Check if a global variable with the same name already exists.
|
||||
if (scriptSearchGlobals(script, name, (uint32_t)strlen(name)) != -1) {
|
||||
if (scriptGetGlobals(script, name, (uint32_t)strlen(name)) != -1) {
|
||||
__ASSERT(false, stringFormat(vm, "A global variable named '$' already "
|
||||
"esists on module '@'", name, script->moudle)->data);
|
||||
}
|
||||
@ -707,6 +737,8 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script,
|
||||
fn->arity = arity;
|
||||
}
|
||||
|
||||
// TODO: make the below module functions as PK_DOC(name, doc);
|
||||
|
||||
// 'lang' library methods.
|
||||
// -----------------------
|
||||
|
||||
@ -835,6 +867,11 @@ void initializeCore(PKVM* vm) {
|
||||
INITALIZE_BUILTIN_FN("type_name", coreTypeName, 1);
|
||||
|
||||
// TOOD: (maybe remove is_*() functions) suspend by type_name.
|
||||
// and add is keyword with modules for builtin types
|
||||
// ex: val is Num; val is null; val is List; val is Range
|
||||
// List.append(l, e) # List is implicitly imported core module.
|
||||
// String.lower(s)
|
||||
|
||||
INITALIZE_BUILTIN_FN("is_null", coreIsNull, 1);
|
||||
INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1);
|
||||
INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1);
|
||||
@ -851,6 +888,7 @@ void initializeCore(PKVM* vm) {
|
||||
INITALIZE_BUILTIN_FN("yield", coreYield, -1);
|
||||
INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
||||
INITALIZE_BUILTIN_FN("print", corePrint, -1);
|
||||
INITALIZE_BUILTIN_FN("input", coreInput, -1);
|
||||
|
||||
// String functions.
|
||||
INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1);
|
||||
@ -1101,14 +1139,14 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
Script* scr = (Script*)obj;
|
||||
|
||||
// Search in functions.
|
||||
uint32_t index = scriptSearchFunc(scr, attrib->data, attrib->length);
|
||||
uint32_t index = scriptGetFunc(scr, attrib->data, attrib->length);
|
||||
if (index != -1) {
|
||||
ASSERT_INDEX(index, scr->functions.count);
|
||||
return VAR_OBJ(scr->functions.data[index]);
|
||||
}
|
||||
|
||||
// Search in globals.
|
||||
index = scriptSearchGlobals(scr, attrib->data, attrib->length);
|
||||
index = scriptGetGlobals(scr, attrib->data, attrib->length);
|
||||
if (index != -1) {
|
||||
ASSERT_INDEX(index, scr->globals.count);
|
||||
return scr->globals.data[index];
|
||||
@ -1173,7 +1211,7 @@ do { \
|
||||
Script* scr = (Script*)obj;
|
||||
|
||||
// Check globals.
|
||||
uint32_t index = scriptSearchGlobals(scr, attrib->data, attrib->length);
|
||||
uint32_t index = scriptGetGlobals(scr, attrib->data, attrib->length);
|
||||
if (index != -1) {
|
||||
ASSERT_INDEX(index, scr->globals.count);
|
||||
scr->globals.data[index] = value;
|
||||
@ -1181,7 +1219,7 @@ do { \
|
||||
}
|
||||
|
||||
// Check function (Functions are immutable).
|
||||
index = scriptSearchFunc(scr, attrib->data, attrib->length);
|
||||
index = scriptGetFunc(scr, attrib->data, attrib->length);
|
||||
if (index != -1) {
|
||||
ASSERT_INDEX(index, scr->functions.count);
|
||||
ATTRIB_IMMUTABLE(scr->functions.data[index]->name);
|
||||
|
@ -33,11 +33,11 @@ Script* getCoreLib(const PKVM* vm, String* name);
|
||||
Var varAdd(PKVM* vm, Var v1, Var v2); // Returns v1 + v2.
|
||||
Var varSubtract(PKVM* vm, Var v1, Var v2); // Returns v1 - v2.
|
||||
Var varMultiply(PKVM* vm, Var v1, Var v2); // Returns v1 * v2.
|
||||
Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2.
|
||||
Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2.
|
||||
Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2.
|
||||
Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2.
|
||||
|
||||
bool varGreater(Var v1, Var v2); // Returns v1 > v2.
|
||||
bool varLesser(Var v1, Var v2); // Returns v1 < v2.
|
||||
bool varLesser(Var v1, Var v2); // Returns v1 < v2.
|
||||
|
||||
// Returns the attribute named [attrib] on the variable [on].
|
||||
Var varGetAttrib(PKVM* vm, Var on, String* attrib);
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include "core.h"
|
||||
#include "var.h"
|
||||
#include "vm.h"
|
||||
|
||||
// To limit maximum elements to be dumpin in a map or a list.
|
||||
@ -313,6 +314,8 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
case OP_GTEQ:
|
||||
case OP_RANGE:
|
||||
case OP_IN:
|
||||
case OP_REPL_PRINT:
|
||||
case OP_YIELD:
|
||||
case OP_END:
|
||||
NO_ARGS();
|
||||
break;
|
||||
|
@ -63,7 +63,11 @@ extern "C" {
|
||||
// }
|
||||
//
|
||||
#define PK_DOC(func, doc) \
|
||||
/* TODO: static char __pkdoc__##func[] = doc;*/ void func(PKVM* vm)
|
||||
/* TODO: static char __pkdoc__##func[] = doc;*/ static void func(PKVM* vm)
|
||||
|
||||
// Name of the implicit function for a module. When a module is parsed all of
|
||||
// it's statements are wrapped around an implicit function with this name.
|
||||
#define PK_BODY_FN_NAME "$(SourceBody)"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG TYPES */
|
||||
@ -100,6 +104,7 @@ typedef enum {
|
||||
|
||||
typedef struct PkStringPtr PkStringPtr;
|
||||
typedef struct PkConfiguration PkConfiguration;
|
||||
typedef struct PkCompileOptions PkCompileOptions;
|
||||
|
||||
// Type of the error message that pocketlang will provide with the pkErrorFn
|
||||
// callback.
|
||||
@ -109,13 +114,13 @@ typedef enum {
|
||||
PK_ERROR_STACKTRACE, // One entry of a runtime error stack.
|
||||
} PkErrorType;
|
||||
|
||||
// Result that pocketlang will return after running a script or a function
|
||||
// or evaluvating an expression.
|
||||
// Result that pocketlang will return after a compilation or running a script
|
||||
// or a function or evaluvating an expression.
|
||||
typedef enum {
|
||||
PK_RESULT_SUCCESS = 0, // Successfully finished the execution.
|
||||
PK_RESULT_COMPILE_ERROR, // Compilation failed.
|
||||
PK_RESULT_RUNTIME_ERROR, // An error occured at runtime.
|
||||
} PkInterpretResult;
|
||||
} PkResult;
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG FUNCTION POINTERS & CALLBACKS */
|
||||
@ -136,15 +141,20 @@ typedef void (*pkNativeFn)(PKVM* vm);
|
||||
// function will return NULL.
|
||||
typedef void* (*pkReallocFn)(void* memory, size_t new_size, void* user_data);
|
||||
|
||||
// Error callback function pointer. for runtime error it'll call first with
|
||||
// Error callback function pointer. For runtime error it'll call first with
|
||||
// PK_ERROR_RUNTIME followed by multiple callbacks with PK_ERROR_STACKTRACE.
|
||||
// The error messages should be written to stderr.
|
||||
typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type,
|
||||
const char* file, int line,
|
||||
const char* message);
|
||||
|
||||
// A function callback used by `print()` statement.
|
||||
// A function callback to write [text] to stdout.
|
||||
typedef void (*pkWriteFn) (PKVM* vm, const char* text);
|
||||
|
||||
// A function callback to read a line from stdin. The returned string shouldn't
|
||||
// contain a line ending (\n or \r\n).
|
||||
typedef PkStringPtr (*pkReadFn) (PKVM* vm);
|
||||
|
||||
// A function callback symbol for clean/free the pkStringResult.
|
||||
typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result);
|
||||
|
||||
@ -169,6 +179,11 @@ typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path);
|
||||
// application.
|
||||
PK_PUBLIC PkConfiguration pkNewConfiguration();
|
||||
|
||||
// Create a new pkCompilerOptions with the default values and return it.
|
||||
// Override those default configuration to adopt to another hosting
|
||||
// application.
|
||||
PK_PUBLIC PkCompileOptions pkNewCompilerOptions();
|
||||
|
||||
// Allocate initialize and returns a new VM
|
||||
PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config);
|
||||
|
||||
@ -195,21 +210,36 @@ PK_PUBLIC PkVar pkGetHandleValue(const PkHandle* handle);
|
||||
// this for every handles before freeing the VM.
|
||||
PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle);
|
||||
|
||||
// Add a new module named [name] to the [vm]. Note that the module shouldn't
|
||||
// already existed, otherwise an assertion will fail to indicate that.
|
||||
PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name);
|
||||
|
||||
// Add a native function to the given script. If [arity] is -1 that means
|
||||
// The function has variadic parameters and use pkGetArgc() to get the argc.
|
||||
PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
|
||||
const char* name,
|
||||
pkNativeFn fptr, int arity);
|
||||
|
||||
// Returns the function from the [module] as a handle, if not found it'll
|
||||
// return NULL.
|
||||
PK_PUBLIC PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
|
||||
const char* name);
|
||||
|
||||
// Compile the [module] with the provided [source] and return true if the
|
||||
// compilation is success. Set the compiler options with the the [options]
|
||||
// argument or it can be set to NULL for default options.
|
||||
PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module,
|
||||
PkStringPtr source,
|
||||
const PkCompileOptions* options);
|
||||
|
||||
// Interpret the source and return the result. Once It's done with the source
|
||||
// and path 'on_done' will be called to clean the string if it's not NULL.
|
||||
PK_PUBLIC PkInterpretResult pkInterpretSource(PKVM* vm,
|
||||
PkStringPtr source,
|
||||
PkStringPtr path);
|
||||
// Set the compiler options with the the [options] argument or it can be set to
|
||||
// NULL for default options.
|
||||
PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
|
||||
PkStringPtr source,
|
||||
PkStringPtr path,
|
||||
const PkCompileOptions* options);
|
||||
|
||||
|
||||
//PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
|
||||
// int argc, PkHandle** argv);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG PUBLIC TYPE DEFINES */
|
||||
@ -232,6 +262,7 @@ struct PkConfiguration {
|
||||
|
||||
pkErrorFn error_fn;
|
||||
pkWriteFn write_fn;
|
||||
pkReadFn read_fn;
|
||||
|
||||
pkResolvePathFn resolve_path_fn;
|
||||
pkLoadScriptFn load_script_fn;
|
||||
@ -240,6 +271,29 @@ struct PkConfiguration {
|
||||
void* user_data;
|
||||
};
|
||||
|
||||
// The options to configure the compilation provided by the command line
|
||||
// arguments (or other ways the host application provides).
|
||||
struct PkCompileOptions {
|
||||
|
||||
// Compile debug version of the source.
|
||||
bool debug;
|
||||
|
||||
// TODO: don't use FILE* pointer or any of <stdio.h> functions here.
|
||||
// instead add a stream option to vm.config.write_fn callback.
|
||||
//
|
||||
// Dump the compiled opcodes to the given [dump_stream] FILE* could be stdio,
|
||||
// stderr, or a file pointer.
|
||||
//bool dump_opcodes;
|
||||
//FILE* dump_stream;
|
||||
|
||||
// Set to true if compiling in REPL mode, This will print repr version of
|
||||
// each evaluvated non-null values. Note that if [repl_mode] is true the
|
||||
// [expression] should also be true otherwise it's incompatible, (will fail
|
||||
// an assertion).
|
||||
bool repl_mode;
|
||||
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* NATIVE FUNCTION API */
|
||||
/*****************************************************************************/
|
||||
@ -257,7 +311,7 @@ PK_PUBLIC int pkGetArgc(const PKVM* vm);
|
||||
|
||||
// Return the [arg] th argument as a PkVar. This pointer will only be
|
||||
// valid till the current function ends, because it points to the var at the
|
||||
// stack and it'll popped when the current call frame ended. Use handlers to
|
||||
// stack and it'll popped when the current call frame ended. Use handles to
|
||||
// keep the var alive even after that.
|
||||
PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg);
|
||||
|
||||
@ -294,8 +348,14 @@ PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len);
|
||||
PK_PUBLIC PkHandle* pkNewList(PKVM* vm);
|
||||
PK_PUBLIC PkHandle* pkNewMap(PKVM* vm);
|
||||
|
||||
PK_PUBLIC const char* pkStringGetData(const PkVar value);
|
||||
// Add a new module named [name] to the [vm]. Note that the module shouldn't
|
||||
// already existed, otherwise an assertion will fail to indicate that.
|
||||
PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name);
|
||||
|
||||
// Create and return a new fiber around the function [fn].
|
||||
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
|
||||
|
||||
PK_PUBLIC const char* pkStringGetData(const PkVar value);
|
||||
|
||||
// TODO:
|
||||
// The below functions will push the primitive values on the stack and return
|
||||
|
@ -188,6 +188,13 @@ OPCODE(GTEQ, 0, -1)
|
||||
OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push.
|
||||
OPCODE(IN, 0, -1)
|
||||
|
||||
// Print the repr string of the value at the stack top, used in REPL mode.
|
||||
// This will not pop the value.
|
||||
OPCODE(REPL_PRINT, 0, 0)
|
||||
|
||||
// Yield the current fiber. Used in REPL mode.
|
||||
OPCODE(YIELD, 0, 0)
|
||||
|
||||
// A sudo instruction which will never be called. A function's last opcode
|
||||
// used for debugging.
|
||||
OPCODE(END, 0, 0)
|
||||
|
70
src/var.c
70
src/var.c
@ -10,7 +10,7 @@
|
||||
#include "vm.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* PUBLIC API */
|
||||
/* VAR PUBLIC API */
|
||||
/*****************************************************************************/
|
||||
|
||||
PkVarType pkGetValueType(const PkVar value) {
|
||||
@ -55,6 +55,13 @@ PkHandle* pkNewMap(PKVM* vm) {
|
||||
return vmNewHandle(vm, VAR_OBJ(newMap(vm)));
|
||||
}
|
||||
|
||||
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
|
||||
__ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function.");
|
||||
|
||||
Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value));
|
||||
return vmNewHandle(vm, VAR_OBJ(fiber));
|
||||
}
|
||||
|
||||
const char* pkStringGetData(const PkVar value) {
|
||||
const Var str = (*(const Var*)value);
|
||||
__ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string.");
|
||||
@ -137,7 +144,7 @@ void grayVarBuffer(PKVM* vm, VarBuffer* self) {
|
||||
GRAY_OBJ_BUFFER(String)
|
||||
GRAY_OBJ_BUFFER(Function)
|
||||
|
||||
static void blackenObject(Object* obj, PKVM* vm) {
|
||||
static void _blackenObject(Object* obj, PKVM* vm) {
|
||||
// TODO: trace here.
|
||||
|
||||
switch (obj->type) {
|
||||
@ -242,12 +249,11 @@ static void blackenObject(Object* obj, PKVM* vm) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void blackenObjects(PKVM* vm) {
|
||||
while (vm->gray_list_count > 0) {
|
||||
// Pop the gray object from the list.
|
||||
Object* gray = vm->gray_list[--vm->gray_list_count];
|
||||
blackenObject(gray, vm);
|
||||
_blackenObject(gray, vm);
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,8 +340,9 @@ Script* newScript(PKVM* vm, String* path) {
|
||||
stringBufferInit(&script->names);
|
||||
|
||||
vmPushTempRef(vm, &script->_super);
|
||||
const char* fn_name = "$(SourceBody)";
|
||||
const char* fn_name = PK_BODY_FN_NAME;
|
||||
script->body = newFunction(vm, fn_name, (int)strlen(fn_name), script, false);
|
||||
script->body->arity = 0; // TODO: should it be 1 (ARGV)?.
|
||||
vmPopTempRef(vm);
|
||||
|
||||
return script;
|
||||
@ -847,8 +854,10 @@ struct OuterSequence {
|
||||
};
|
||||
typedef struct OuterSequence OuterSequence;
|
||||
|
||||
static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
|
||||
OuterSequence* outer) {
|
||||
static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff,
|
||||
OuterSequence* outer, bool repr) {
|
||||
ASSERT(outer == NULL || repr, OOPS);
|
||||
|
||||
if (IS_NULL(v)) {
|
||||
byteBufferAddString(buff, vm, "null", 4);
|
||||
return;
|
||||
@ -887,9 +896,27 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
|
||||
case OBJ_STRING:
|
||||
{
|
||||
const String* str = (const String*)obj;
|
||||
if (outer == NULL) {
|
||||
if (outer == NULL && !repr) {
|
||||
byteBufferAddString(buff, vm, str->data, str->length);
|
||||
return;
|
||||
} else {
|
||||
// If recursive return with quotes (ex: [42, "hello", 0..10]).
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
for (const char* c = str->data; *c != '\0'; c++) {
|
||||
switch (*c) {
|
||||
case '"': byteBufferAddString(buff, vm, "\\\"", 2); break;
|
||||
case '\\': byteBufferAddString(buff, vm, "\\\\", 2); break;
|
||||
case '\n': byteBufferAddString(buff, vm, "\\n", 2); break;
|
||||
case '\r': byteBufferAddString(buff, vm, "\\r", 2); break;
|
||||
case '\t': byteBufferAddString(buff, vm, "\\t", 2); break;
|
||||
|
||||
default:
|
||||
byteBufferWrite(buff, vm, *c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
return;
|
||||
}
|
||||
|
||||
// If recursive return with quotes (ex: [42, "hello", 0..10]).
|
||||
@ -924,7 +951,7 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
|
||||
byteBufferWrite(buff, vm, '[');
|
||||
for (uint32_t i = 0; i < list->elements.count; i++) {
|
||||
if (i != 0) byteBufferAddString(buff, vm, ", ", 2);
|
||||
toStringInternal(vm, list->elements.data[i], buff, &seq_list);
|
||||
_toStringInternal(vm, list->elements.data[i], buff, &seq_list, true);
|
||||
}
|
||||
byteBufferWrite(buff, vm, ']');
|
||||
return;
|
||||
@ -971,9 +998,9 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
|
||||
byteBufferAddString(buff, vm, ", ", 2);
|
||||
_first = false;
|
||||
}
|
||||
toStringInternal(vm, map->entries[i].key, buff, &seq_map);
|
||||
_toStringInternal(vm, map->entries[i].key, buff, &seq_map, true);
|
||||
byteBufferWrite(buff, vm, ':');
|
||||
toStringInternal(vm, map->entries[i].value, buff, &seq_map);
|
||||
_toStringInternal(vm, map->entries[i].value, buff, &seq_map, true);
|
||||
i++;
|
||||
} while (i < map->capacity);
|
||||
|
||||
@ -1044,10 +1071,23 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
|
||||
return;
|
||||
}
|
||||
|
||||
String* toString(PKVM* vm, Var v) {
|
||||
String* toString(PKVM* vm, const Var value) {
|
||||
|
||||
// If it's already a string don't allocate a new string.
|
||||
if (IS_OBJ_TYPE(value, OBJ_STRING)) {
|
||||
return (String*)AS_OBJ(value);
|
||||
}
|
||||
|
||||
ByteBuffer buff;
|
||||
byteBufferInit(&buff);
|
||||
toStringInternal(vm, v, &buff, NULL);
|
||||
_toStringInternal(vm, value, &buff, NULL, false);
|
||||
return newStringLength(vm, (const char*)buff.data, buff.count);
|
||||
}
|
||||
|
||||
String* toRepr(PKVM* vm, const Var value) {
|
||||
ByteBuffer buff;
|
||||
byteBufferInit(&buff);
|
||||
_toStringInternal(vm, value, &buff, NULL, true);
|
||||
return newStringLength(vm, (const char*)buff.data, buff.count);
|
||||
}
|
||||
|
||||
@ -1168,7 +1208,7 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
|
||||
return self->names.count - 1;
|
||||
}
|
||||
|
||||
int scriptSearchFunc(Script* script, const char* name, uint32_t length) {
|
||||
int scriptGetFunc(Script* script, const char* name, uint32_t length) {
|
||||
for (uint32_t i = 0; i < script->function_names.count; i++) {
|
||||
uint32_t name_index = script->function_names.data[i];
|
||||
String* fn_name = script->names.data[name_index];
|
||||
@ -1180,7 +1220,7 @@ int scriptSearchFunc(Script* script, const char* name, uint32_t length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int scriptSearchGlobals(Script* script, const char* name, uint32_t length) {
|
||||
int scriptGetGlobals(Script* script, const char* name, uint32_t length) {
|
||||
for (uint32_t i = 0; i < script->global_names.count; i++) {
|
||||
uint32_t name_index = script->global_names.data[i];
|
||||
String* g_name = script->names.data[name_index];
|
||||
|
16
src/var.h
16
src/var.h
@ -138,7 +138,7 @@
|
||||
#define IS_INT(value) ((value & _MASK_INTEGER) == _MASK_INTEGER)
|
||||
#define IS_NUM(value) ((value & _MASK_QNAN) != _MASK_QNAN)
|
||||
#define IS_OBJ(value) ((value & _MASK_OBJECT) == _MASK_OBJECT)
|
||||
#define IS_OBJ_TYPE(obj, obj_type) IS_OBJ(obj) && AS_OBJ(obj)->type == obj_type
|
||||
#define IS_OBJ_TYPE(var, obj_type) IS_OBJ(var) && AS_OBJ(var)->type == obj_type
|
||||
|
||||
// Decode types.
|
||||
#define AS_BOOL(value) ((value) == VAR_TRUE)
|
||||
@ -408,7 +408,7 @@ Script* newScript(PKVM* vm, String* path);
|
||||
// would be builtin function. For builtin function arity and the native
|
||||
// function pointer would be initialized after calling this function.
|
||||
Function* newFunction(PKVM* vm, const char* name, int length, Script* owner,
|
||||
bool is_native);
|
||||
bool is_native);
|
||||
|
||||
// Allocate new Fiber object around the function [fn] and return Fiber*.
|
||||
Fiber* newFiber(PKVM* vm, Function* fn);
|
||||
@ -500,8 +500,12 @@ uint32_t varHashValue(Var v);
|
||||
// Return true if the object type is hashable.
|
||||
bool isObjectHashable(ObjectType type);
|
||||
|
||||
// Returns the string version of the value.
|
||||
String* toString(PKVM* vm, Var v);
|
||||
// Returns the string version of the [value].
|
||||
String* toString(PKVM* vm, const Var value);
|
||||
|
||||
// Returns the representation version of the [value], similer of python's
|
||||
// __repr__() method.
|
||||
String * toRepr(PKVM * vm, const Var value);
|
||||
|
||||
// Returns the truthy value of the var.
|
||||
bool toBool(Var v);
|
||||
@ -523,10 +527,10 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
|
||||
|
||||
// Search for the function name in the script and return it's index in it's
|
||||
// [functions] buffer. If not found returns -1.
|
||||
int scriptSearchFunc(Script* script, const char* name, uint32_t length);
|
||||
int scriptGetFunc(Script* script, const char* name, uint32_t length);
|
||||
|
||||
// Search for the global variable name in the script and return it's index in
|
||||
// it's [globals] buffer. If not found returns -1.
|
||||
int scriptSearchGlobals(Script* script, const char* name, uint32_t length);
|
||||
int scriptGetGlobals(Script* script, const char* name, uint32_t length);
|
||||
|
||||
#endif // VAR_H
|
||||
|
163
src/vm.c
163
src/vm.c
@ -21,12 +21,17 @@
|
||||
// if the host doesn't provided any allocators for us.
|
||||
static void* defaultRealloc(void* memory, size_t new_size, void* user_data);
|
||||
|
||||
// Runs the [fiber] if it's at yielded state, this will resume the execution
|
||||
// till the next yield or return statement, and return result.
|
||||
static PkResult runFiber(PKVM* vm, Fiber* fiber);
|
||||
|
||||
PkConfiguration pkNewConfiguration() {
|
||||
PkConfiguration config;
|
||||
config.realloc_fn = defaultRealloc;
|
||||
|
||||
config.error_fn = NULL;
|
||||
config.write_fn = NULL;
|
||||
config.read_fn = NULL;
|
||||
|
||||
config.load_script_fn = NULL;
|
||||
config.resolve_path_fn = NULL;
|
||||
@ -35,6 +40,16 @@ PkConfiguration pkNewConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
PkCompileOptions pkNewCompilerOptions() {
|
||||
PkCompileOptions options;
|
||||
options.debug = false;
|
||||
// TODO:
|
||||
//options.dump_opcodes = false;
|
||||
//options.dump_stream = stdout;
|
||||
options.repl_mode = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
PKVM* pkNewVM(PkConfiguration* config) {
|
||||
|
||||
PkConfiguration default_config = pkNewConfiguration();
|
||||
@ -112,6 +127,49 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
|
||||
DEALLOCATE(vm, handle);
|
||||
}
|
||||
|
||||
// This function is responsible to call on_done function if it's done with the
|
||||
// provided string pointers.
|
||||
PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path,
|
||||
const PkCompileOptions* options) {
|
||||
|
||||
String* path_name = newString(vm, path.string);
|
||||
if (path.on_done) path.on_done(vm, path);
|
||||
vmPushTempRef(vm, &path_name->_super); // path_name.
|
||||
|
||||
// TODO: Should I clean the script if it already exists before compiling it?
|
||||
|
||||
// Load a new script to the vm's scripts cache.
|
||||
Script* scr = vmGetScript(vm, path_name);
|
||||
if (scr == NULL) {
|
||||
scr = newScript(vm, path_name);
|
||||
vmPushTempRef(vm, &scr->_super); // scr.
|
||||
mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr));
|
||||
vmPopTempRef(vm); // scr.
|
||||
}
|
||||
vmPopTempRef(vm); // path_name.
|
||||
|
||||
// Compile the source.
|
||||
bool success = compile(vm, scr, source.string, options);
|
||||
if (source.on_done) source.on_done(vm, source);
|
||||
|
||||
if (!success) return PK_RESULT_COMPILE_ERROR;
|
||||
|
||||
// Set script initialized to true before the execution ends to prevent cyclic
|
||||
// inclusion cause a crash.
|
||||
scr->initialized = true;
|
||||
|
||||
return runFiber(vm, newFiber(vm, scr->body));
|
||||
}
|
||||
|
||||
void pkSetRuntimeError(PKVM* vm, const char* message) {
|
||||
__ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
|
||||
vm->fiber->error = newString(vm, message);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* SHARED FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
PkHandle* vmNewHandle(PKVM* vm, Var value) {
|
||||
PkHandle* handle = (PkHandle*)ALLOCATE(vm, PkHandle);
|
||||
handle->value = value;
|
||||
@ -122,7 +180,6 @@ PkHandle* vmNewHandle(PKVM* vm, Var value) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) {
|
||||
|
||||
// TODO: Debug trace allocations here.
|
||||
@ -152,6 +209,13 @@ void vmPopTempRef(PKVM* vm) {
|
||||
vm->temp_reference_count--;
|
||||
}
|
||||
|
||||
Script* vmGetScript(PKVM* vm, String* path) {
|
||||
Var scr = mapGet(vm->scripts, VAR_OBJ(path));
|
||||
if (IS_UNDEF(scr)) return NULL;
|
||||
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
|
||||
return (Script*)AS_OBJ(scr);
|
||||
}
|
||||
|
||||
void vmCollectGarbage(PKVM* vm) {
|
||||
|
||||
// Reset VM's bytes_allocated value and count it again so that we don't
|
||||
@ -217,6 +281,22 @@ void vmCollectGarbage(PKVM* vm) {
|
||||
if (vm->next_gc < vm->min_heap_size) vm->next_gc = vm->min_heap_size;
|
||||
}
|
||||
|
||||
void vmYieldFiber(PKVM* vm, Var* value) {
|
||||
|
||||
Fiber* caller = vm->fiber->caller;
|
||||
|
||||
// Return the yield value to the caller fiber.
|
||||
if (caller != NULL) {
|
||||
if (value == NULL) *caller->ret = VAR_NULL;
|
||||
else *caller->ret = *value;
|
||||
}
|
||||
|
||||
// Can be resumed by another caller fiber.
|
||||
vm->fiber->caller = NULL;
|
||||
vm->fiber->state = FIBER_YIELDED;
|
||||
vm->fiber = caller;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VM INTERNALS */
|
||||
/*****************************************************************************/
|
||||
@ -231,13 +311,6 @@ static void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
|
||||
return realloc(memory, new_size);
|
||||
}
|
||||
|
||||
static inline Script* getScript(PKVM* vm, String* path) {
|
||||
Var scr = mapGet(vm->scripts, VAR_OBJ(path));
|
||||
if (IS_UNDEF(scr)) return NULL;
|
||||
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
|
||||
return (Script*)AS_OBJ(scr);
|
||||
}
|
||||
|
||||
// If failed to resolve it'll return false. Parameter [result] should be points
|
||||
// to the string which is the path that has to be resolved and once it resolved
|
||||
// the provided result's string's on_done() will be called and, it's string
|
||||
@ -353,12 +426,7 @@ static inline void pushCallFrame(PKVM* vm, const Function* fn) {
|
||||
frame->ip = fn->fn->opcodes.data;
|
||||
}
|
||||
|
||||
void pkSetRuntimeError(PKVM* vm, const char* message) {
|
||||
__ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
|
||||
vm->fiber->error = newString(vm, message);
|
||||
}
|
||||
|
||||
void vmReportError(PKVM* vm) {
|
||||
static void reportError(PKVM* vm) {
|
||||
ASSERT(HAS_ERROR(), "runtimeError() should be called after an error.");
|
||||
// TODO: pass the error to the caller of the fiber.
|
||||
|
||||
@ -376,44 +444,11 @@ void vmReportError(PKVM* vm) {
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible to call on_done function if it's done with the
|
||||
// provided string pointers.
|
||||
PkInterpretResult pkInterpretSource(PKVM* vm, PkStringPtr source,
|
||||
PkStringPtr path) {
|
||||
String* path_name = newString(vm, path.string);
|
||||
if (path.on_done) path.on_done(vm, path);
|
||||
vmPushTempRef(vm, &path_name->_super); // path_name.
|
||||
|
||||
// TODO: Should I clean the script if it already exists before compiling it?
|
||||
|
||||
// Load a new script to the vm's scripts cache.
|
||||
Script* scr = getScript(vm, path_name);
|
||||
if (scr == NULL) {
|
||||
scr = newScript(vm, path_name);
|
||||
vmPushTempRef(vm, &scr->_super); // scr.
|
||||
mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr));
|
||||
vmPopTempRef(vm); // scr.
|
||||
}
|
||||
vmPopTempRef(vm); // path_name.
|
||||
|
||||
// Compile the source.
|
||||
bool success = compile(vm, scr, source.string);
|
||||
if (source.on_done) source.on_done(vm, source);
|
||||
|
||||
if (!success) return PK_RESULT_COMPILE_ERROR;
|
||||
|
||||
// Set script initialized to true before the execution ends to prevent cyclic
|
||||
// inclusion cause a crash.
|
||||
scr->initialized = true;
|
||||
|
||||
return vmRunFiber(vm, newFiber(vm, scr->body));
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* RUNTIME *
|
||||
*****************************************************************************/
|
||||
|
||||
PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
|
||||
static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
|
||||
// Set the fiber as the vm's current fiber (another root object) to prevent
|
||||
// it from garbage collection and get the reference from native functions.
|
||||
@ -423,9 +458,6 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
|
||||
fiber->state = FIBER_RUNNING;
|
||||
|
||||
// The instruction pointer.
|
||||
// Note: sing 'uint8_t** ip' as reference to the instruction pointer in the
|
||||
// call frame seems a bit slower because of the dereferencing (~0.1 sec for
|
||||
// 100 million calls).
|
||||
register const uint8_t* ip;
|
||||
|
||||
register Var* rbp; //< Stack base pointer register.
|
||||
@ -444,7 +476,7 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
|
||||
do { \
|
||||
if (HAS_ERROR()) { \
|
||||
UPDATE_FRAME(); \
|
||||
vmReportError(vm); \
|
||||
reportError(vm); \
|
||||
return PK_RESULT_RUNTIME_ERROR; \
|
||||
} \
|
||||
} while (false)
|
||||
@ -454,7 +486,7 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
|
||||
do { \
|
||||
vm->fiber->error = err_msg; \
|
||||
UPDATE_FRAME(); \
|
||||
vmReportError(vm); \
|
||||
reportError(vm); \
|
||||
return PK_RESULT_RUNTIME_ERROR; \
|
||||
} while (false)
|
||||
|
||||
@ -1150,6 +1182,31 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
|
||||
// TODO: Implement bool varContaines(vm, on, value);
|
||||
TODO;
|
||||
|
||||
OPCODE(REPL_PRINT):
|
||||
{
|
||||
if (vm->config.write_fn != NULL) {
|
||||
Var tmp = PEEK(-1);
|
||||
vm->config.write_fn(vm, toRepr(vm, tmp)->data);
|
||||
vm->config.write_fn(vm, "\n");
|
||||
}
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(YIELD):
|
||||
{
|
||||
Fiber* call_fiber = vm->fiber;
|
||||
|
||||
UPDATE_FRAME();
|
||||
vmYieldFiber(vm, NULL);
|
||||
if (vm->fiber == NULL) return PK_RESULT_SUCCESS;
|
||||
LOAD_FRAME();
|
||||
|
||||
// Pop function arguments except for the return value of the call fiber.
|
||||
call_fiber->sp = call_fiber->ret + 1;
|
||||
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(END):
|
||||
TODO;
|
||||
break;
|
||||
|
10
src/vm.h
10
src/vm.h
@ -176,8 +176,12 @@ void vmPushTempRef(PKVM* vm, Object* obj);
|
||||
// Pop the top most object from temporary reference stack.
|
||||
void vmPopTempRef(PKVM* vm);
|
||||
|
||||
// Runs the [fiber] if it's at yielded state, this will resume the execution
|
||||
// till the next yield or return statement, and return result.
|
||||
PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber);
|
||||
// Returns the scrpt with the resolved [path] (also the key) in the vm's script
|
||||
// cache. If not found itll return NULL.
|
||||
Script* vmGetScript(PKVM* vm, String* path);
|
||||
|
||||
// Yield from the current fiber. If the [value] isn't NULL it'll set it as the
|
||||
// yield value.
|
||||
void vmYieldFiber(PKVM* vm, Var* value);
|
||||
|
||||
#endif // VM_H
|
||||
|
Loading…
Reference in New Issue
Block a user