mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
Merge pull request #206 from ThakeeNathees/class2
[WIP] class implementation
This commit is contained in:
commit
020f9e2eab
@ -13,10 +13,9 @@
|
||||
/* STD MODULE SOURCES */
|
||||
/*****************************************************************************/
|
||||
|
||||
#include "modules/modules.c"
|
||||
|
||||
#include "modules/std_file.c"
|
||||
#include "modules/std_io.c"
|
||||
#include "modules/std_path.c"
|
||||
#include "modules/std_math.c"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* THIRDPARTY SOURCES */
|
||||
|
@ -129,11 +129,6 @@ static PKVM* intializePocketVM() {
|
||||
config.write_fn = writeFunction;
|
||||
config.read_fn = readFunction;
|
||||
|
||||
config.inst_free_fn = freeObj;
|
||||
config.inst_name_fn = getObjName;
|
||||
config.inst_get_attrib_fn = objGetAttrib;
|
||||
config.inst_set_attrib_fn = objSetAttrib;
|
||||
|
||||
config.load_script_fn = loadScript;
|
||||
config.resolve_path_fn = resolvePath;
|
||||
|
||||
@ -193,7 +188,7 @@ int main(int argc, const char** argv) {
|
||||
user_data.repl_mode = false;
|
||||
pkSetUserData(vm, &user_data);
|
||||
|
||||
registerModules(vm);
|
||||
REGISTER_ALL_MODULES(vm);
|
||||
|
||||
PkCompileOptions options = pkNewCompilerOptions();
|
||||
options.debug = debug;
|
||||
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2022 Thakee Nathees
|
||||
* Copyright (c) 2021-2022 Pocketlang Contributors
|
||||
* Distributed Under The MIT License
|
||||
*/
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
// Note: Everything here is for testing the native API, and will have to
|
||||
// refactor everything.
|
||||
|
||||
// Allocate a new module object of type [Ty].
|
||||
#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty))
|
||||
|
||||
// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj
|
||||
// callback.
|
||||
#define FREE_OBJ(ptr) free(ptr)
|
||||
|
||||
/*****************************************************************************/
|
||||
/* MODULE FUNCTIONS DECLARATION */
|
||||
/*****************************************************************************/
|
||||
|
||||
void fileGetAttrib(PKVM* vm, File* file, const char* attrib);
|
||||
bool fileSetAttrib(PKVM* vm, File* file, const char* attrib);
|
||||
void fileClean(PKVM* vm, File* file);
|
||||
|
||||
void registerModuleFile(PKVM* vm);
|
||||
void registerModulePath(PKVM* vm);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* MODULE PUBLIC FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
void initObj(Obj* obj, ObjType type) {
|
||||
obj->type = type;
|
||||
}
|
||||
|
||||
void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) {
|
||||
Obj* obj = (Obj*)instance;
|
||||
ASSERT(obj->type == (ObjType)id, OOPS);
|
||||
|
||||
switch (obj->type) {
|
||||
case OBJ_FILE:
|
||||
fileGetAttrib(vm, (File*)obj, attrib.string);
|
||||
return;
|
||||
}
|
||||
STATIC_ASSERT(_OBJ_MAX_ == 2);
|
||||
}
|
||||
|
||||
bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) {
|
||||
Obj* obj = (Obj*)instance;
|
||||
ASSERT(obj->type == (ObjType)id, OOPS);
|
||||
|
||||
switch (obj->type) {
|
||||
case OBJ_FILE:
|
||||
return fileSetAttrib(vm, (File*)obj, attrib.string);
|
||||
}
|
||||
STATIC_ASSERT(_OBJ_MAX_ == 2);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void freeObj(PKVM* vm, void* instance, uint32_t id) {
|
||||
Obj* obj = (Obj*)instance;
|
||||
ASSERT(obj->type == (ObjType)id, OOPS);
|
||||
|
||||
switch (obj->type) {
|
||||
case OBJ_FILE:
|
||||
fileClean(vm, (File*)obj);
|
||||
}
|
||||
STATIC_ASSERT(_OBJ_MAX_ == 2);
|
||||
|
||||
FREE_OBJ(obj);
|
||||
}
|
||||
|
||||
const char* getObjName(uint32_t id) {
|
||||
switch ((ObjType)id) {
|
||||
case OBJ_FILE: return "File";
|
||||
}
|
||||
STATIC_ASSERT(_OBJ_MAX_ == 2);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* REGISTER MODULES */
|
||||
/*****************************************************************************/
|
||||
|
||||
void registerModules(PKVM* vm) {
|
||||
registerModuleFile(vm);
|
||||
registerModulePath(vm);
|
||||
}
|
@ -14,73 +14,38 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/*****************************************************************************/
|
||||
/* MODULE OBJECTS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Str | If already exists | If does not exist |
|
||||
// -----+-------------------+-------------------|
|
||||
// 'r' | read from start | failure to open |
|
||||
// 'w' | destroy contents | create new |
|
||||
// 'a' | write to end | create new |
|
||||
// 'r+' | read from start | error |
|
||||
// 'w+' | destroy contents | create new |
|
||||
// 'a+' | write to end | create new |
|
||||
typedef enum {
|
||||
FMODE_READ = (1 << 0),
|
||||
FMODE_WRITE = (1 << 1),
|
||||
FMODE_APPEND = (1 << 2),
|
||||
_FMODE_EXT = (1 << 3),
|
||||
FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ),
|
||||
FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE),
|
||||
FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND),
|
||||
} FileAccessMode;
|
||||
|
||||
typedef enum {
|
||||
OBJ_FILE = 1,
|
||||
|
||||
_OBJ_MAX_
|
||||
} ObjType;
|
||||
|
||||
typedef struct {
|
||||
ObjType type;
|
||||
} Obj;
|
||||
|
||||
typedef struct {
|
||||
Obj _super;
|
||||
FILE* fp; // C file poinnter.
|
||||
FileAccessMode mode; // Access mode of the file.
|
||||
bool closed; // True if the file isn't closed yet.
|
||||
} File;
|
||||
|
||||
/*****************************************************************************/
|
||||
/* MODULE PUBLIC FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Initialize the native module object with it's default values.
|
||||
void initObj(Obj* obj, ObjType type);
|
||||
|
||||
// A function callback called by pocket VM to get attribute of a native
|
||||
// instance. The value of the attributes will be returned with pkReturn...()
|
||||
// functions and if the attribute doesn't exists on the instance we're
|
||||
// shouldn't return anything, PKVM will know it and set error (or use some
|
||||
// common attributes like "as_string", "as_repr", etc).
|
||||
void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib);
|
||||
|
||||
// A function callback called by pocket VM to set attribute of a native
|
||||
// instance.
|
||||
bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib);
|
||||
|
||||
// The free callback of the object, that'll called by pocketlang when a
|
||||
// pocketlang native instance garbage collected.
|
||||
void freeObj(PKVM* vm, void* instance, uint32_t id);
|
||||
|
||||
// The native instance get_name callback used to get the name of a native
|
||||
// instance from pocketlang. Here the id we're using is the ObjType enum.
|
||||
const char* getObjName(uint32_t id);
|
||||
void registerModuleIO(PKVM* vm);
|
||||
void registerModulePath(PKVM* vm);
|
||||
void registerModuleMath(PKVM* vm);
|
||||
|
||||
// Registers all the cli modules.
|
||||
void registerModules(PKVM* vm);
|
||||
#define REGISTER_ALL_MODULES(vm) \
|
||||
do { \
|
||||
registerModuleIO(vm); \
|
||||
registerModulePath(vm); \
|
||||
registerModuleMath(vm); \
|
||||
} while (false)
|
||||
|
||||
/*****************************************************************************/
|
||||
/* MODULES INTERNAL */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Allocate a new module object of type [Ty].
|
||||
#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty))
|
||||
|
||||
// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj
|
||||
// callback.
|
||||
#define FREE_OBJ(ptr) free(ptr)
|
||||
|
||||
// Returns the docstring of the function, which is a static const char* defined
|
||||
// just above the function by the DEF() macro below.
|
||||
#define DOCSTRING(fn) __doc_##fn
|
||||
|
||||
// A macro to declare a function, with docstring, which is defined as
|
||||
// ___doc_<fn> = docstring; That'll used to generate function help text.
|
||||
#define DEF(fn, docstring) \
|
||||
static const char* DOCSTRING(fn) = docstring; \
|
||||
static void fn(PKVM* vm)
|
||||
|
||||
/*****************************************************************************/
|
||||
/* SHARED FUNCTIONS */
|
||||
|
@ -7,32 +7,56 @@
|
||||
#include "modules.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* FILE OBJECT OPERATORS */
|
||||
/* FILE CLASS */
|
||||
/*****************************************************************************/
|
||||
|
||||
void fileGetAttrib(PKVM* vm, File* file, const char* attrib) {
|
||||
if (strcmp(attrib, "closed") == 0) {
|
||||
pkReturnBool(vm, file->closed);
|
||||
return;
|
||||
}
|
||||
// Str | If already exists | If does not exist |
|
||||
// -----+-------------------+-------------------|
|
||||
// 'r' | read from start | failure to open |
|
||||
// 'w' | destroy contents | create new |
|
||||
// 'a' | write to end | create new |
|
||||
// 'r+' | read from start | error |
|
||||
// 'w+' | destroy contents | create new |
|
||||
// 'a+' | write to end | create new |
|
||||
typedef enum {
|
||||
FMODE_NONE = 0,
|
||||
FMODE_READ = (1 << 0),
|
||||
FMODE_WRITE = (1 << 1),
|
||||
FMODE_APPEND = (1 << 2),
|
||||
_FMODE_EXT = (1 << 3),
|
||||
FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ),
|
||||
FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE),
|
||||
FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND),
|
||||
} FileAccessMode;
|
||||
|
||||
typedef struct {
|
||||
FILE* fp; // C file poinnter.
|
||||
FileAccessMode mode; // Access mode of the file.
|
||||
bool closed; // True if the file isn't closed yet.
|
||||
} File;
|
||||
|
||||
void* _newFile() {
|
||||
File* file = NEW_OBJ(File);
|
||||
file->closed = true;
|
||||
file->mode = FMODE_NONE;
|
||||
file->fp = NULL;
|
||||
return file;
|
||||
}
|
||||
|
||||
bool fileSetAttrib(PKVM* vm, File* file, const char* attrib) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void fileClean(PKVM* vm, File* file) {
|
||||
void _deleteFile(void* ptr) {
|
||||
File* file = (File*)ptr;
|
||||
if (!file->closed) {
|
||||
if (fclose(file->fp) != 0) { /* TODO: error! */ }
|
||||
file->closed = true;
|
||||
}
|
||||
FREE_OBJ(file);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* FILE MODULE FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void _fileOpen(PKVM* vm) {
|
||||
DEF(_fileOpen, "") {
|
||||
|
||||
int argc = pkGetArgc(vm);
|
||||
if (!pkCheckArgcRange(vm, argc, 1, 2)) return;
|
||||
@ -62,25 +86,29 @@ static void _fileOpen(PKVM* vm) {
|
||||
} while (false);
|
||||
}
|
||||
|
||||
// This TODO is just a blockade from running the bellow code, complete the
|
||||
// native interface and test before removing it.
|
||||
TODO;
|
||||
|
||||
FILE* fp = fopen(path, mode_str);
|
||||
|
||||
if (fp != NULL) {
|
||||
File* file = NEW_OBJ(File);
|
||||
initObj(&file->_super, OBJ_FILE);
|
||||
file->fp = fp;
|
||||
file->mode = mode;
|
||||
file->closed = false;
|
||||
|
||||
pkReturnInstNative(vm, (void*)file, OBJ_FILE);
|
||||
File* self = (File*)pkGetSelf(vm);
|
||||
self->fp = fp;
|
||||
self->mode = mode;
|
||||
self->closed = false;
|
||||
|
||||
} else {
|
||||
pkReturnNull(vm);
|
||||
pkSetRuntimeError(vm, "Error opening the file.");
|
||||
}
|
||||
}
|
||||
|
||||
static void _fileRead(PKVM* vm) {
|
||||
File* file;
|
||||
if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return;
|
||||
DEF(_fileRead, "") {
|
||||
// This TODO is just a blockade from running the bellow code, complete the
|
||||
// native interface and test before removing it.
|
||||
TODO;
|
||||
|
||||
File* file = (File*)pkGetSelf(vm);
|
||||
|
||||
if (file->closed) {
|
||||
pkSetRuntimeError(vm, "Cannot read from a closed file.");
|
||||
@ -98,11 +126,14 @@ static void _fileRead(PKVM* vm) {
|
||||
pkReturnString(vm, (const char*)buff);
|
||||
}
|
||||
|
||||
static void _fileWrite(PKVM* vm) {
|
||||
File* file;
|
||||
DEF(_fileWrite, "") {
|
||||
// This TODO is just a blockade from running the bellow code, complete the
|
||||
// native interface and test before removing it.
|
||||
TODO;
|
||||
|
||||
File* file = (File*)pkGetSelf(vm);
|
||||
const char* text; uint32_t length;
|
||||
if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return;
|
||||
if (!pkGetArgString(vm, 2, &text, &length)) return;
|
||||
if (!pkGetArgString(vm, 1, &text, &length)) return;
|
||||
|
||||
if (file->closed) {
|
||||
pkSetRuntimeError(vm, "Cannot write to a closed file.");
|
||||
@ -117,9 +148,12 @@ static void _fileWrite(PKVM* vm) {
|
||||
fwrite(text, sizeof(char), (size_t)length, file->fp);
|
||||
}
|
||||
|
||||
static void _fileClose(PKVM* vm) {
|
||||
File* file;
|
||||
if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return;
|
||||
DEF(_fileClose, "") {
|
||||
// This TODO is just a blockade from running the bellow code, complete the
|
||||
// native interface and test before removing it.
|
||||
TODO;
|
||||
|
||||
File* file = (File*)pkGetSelf(vm);
|
||||
|
||||
if (file->closed) {
|
||||
pkSetRuntimeError(vm, "File already closed.");
|
||||
@ -133,14 +167,16 @@ static void _fileClose(PKVM* vm) {
|
||||
file->closed = true;
|
||||
}
|
||||
|
||||
void registerModuleFile(PKVM* vm) {
|
||||
PkHandle* file = pkNewModule(vm, "File");
|
||||
void registerModuleIO(PKVM* vm) {
|
||||
PkHandle* io = pkNewModule(vm, "io");
|
||||
|
||||
pkModuleAddFunction(vm, file, "open", _fileOpen, -1);
|
||||
pkModuleAddFunction(vm, file, "read", _fileRead, 1);
|
||||
pkModuleAddFunction(vm, file, "write", _fileWrite, 2);
|
||||
pkModuleAddFunction(vm, file, "close", _fileClose, 1);
|
||||
PkHandle* cls_file = pkNewClass(vm, "File", NULL, io, _newFile, _deleteFile);
|
||||
pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1);
|
||||
pkClassAddMethod(vm, cls_file, "read", _fileRead, 0);
|
||||
pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1);
|
||||
pkClassAddMethod(vm, cls_file, "close", _fileClose, 0);
|
||||
pkReleaseHandle(vm, cls_file);
|
||||
|
||||
pkRegisterModule(vm, file);
|
||||
pkReleaseHandle(vm, file);
|
||||
pkRegisterModule(vm, io);
|
||||
pkReleaseHandle(vm, io);
|
||||
}
|
218
cli/modules/std_math.c
Normal file
218
cli/modules/std_math.c
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2022 Thakee Nathees
|
||||
* Copyright (c) 2021-2022 Pocketlang Contributors
|
||||
* Distributed Under The MIT License
|
||||
*/
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
// M_PI is non standard. The macro _USE_MATH_DEFINES defining before importing
|
||||
// <math.h> will define the constants for MSVC. But for a portable solution,
|
||||
// we're defining it ourselves if it isn't already.
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
DEF(stdMathFloor,
|
||||
"floor(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
pkReturnNumber(vm, floor(num));
|
||||
}
|
||||
|
||||
DEF(stdMathCeil,
|
||||
"ceil(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
pkReturnNumber(vm, ceil(num));
|
||||
}
|
||||
|
||||
DEF(stdMathPow,
|
||||
"pow(value:num) -> num\n") {
|
||||
|
||||
double num, ex;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
if (!pkGetArgNumber(vm, 2, &ex)) return;
|
||||
pkReturnNumber(vm, pow(num, ex));
|
||||
}
|
||||
|
||||
DEF(stdMathSqrt,
|
||||
"sqrt(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
pkReturnNumber(vm, sqrt(num));
|
||||
}
|
||||
|
||||
DEF(stdMathAbs,
|
||||
"abs(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
if (num < 0) num = -num;
|
||||
pkReturnNumber(vm, num);
|
||||
}
|
||||
|
||||
DEF(stdMathSign,
|
||||
"sign(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
if (num < 0) num = -1;
|
||||
else if (num > 0) num = +1;
|
||||
else num = 0;
|
||||
pkReturnNumber(vm, num);
|
||||
}
|
||||
|
||||
DEF(stdMathSine,
|
||||
"sin(rad:num) -> num\n"
|
||||
"Return the sine value of the argument [rad] which is an angle expressed "
|
||||
"in radians.") {
|
||||
|
||||
double rad;
|
||||
if (!pkGetArgNumber(vm, 1, &rad)) return;
|
||||
pkReturnNumber(vm, sin(rad));
|
||||
}
|
||||
|
||||
DEF(stdMathCosine,
|
||||
"cos(rad:num) -> num\n"
|
||||
"Return the cosine value of the argument [rad] which is an angle expressed "
|
||||
"in radians.") {
|
||||
|
||||
double rad;
|
||||
if (!pkGetArgNumber(vm, 1, &rad)) return;
|
||||
pkReturnNumber(vm, cos(rad));
|
||||
}
|
||||
|
||||
DEF(stdMathTangent,
|
||||
"tan(rad:num) -> num\n"
|
||||
"Return the tangent value of the argument [rad] which is an angle expressed "
|
||||
"in radians.") {
|
||||
|
||||
double rad;
|
||||
if (!pkGetArgNumber(vm, 1, &rad)) return;
|
||||
pkReturnNumber(vm, tan(rad));
|
||||
}
|
||||
|
||||
DEF(stdMathSinh,
|
||||
"sinh(val) -> val\n"
|
||||
"Return the hyperbolic sine value of the argument [val].") {
|
||||
|
||||
double val;
|
||||
if (!pkGetArgNumber(vm, 1, &val)) return;
|
||||
pkReturnNumber(vm, sinh(val));
|
||||
}
|
||||
|
||||
DEF(stdMathCosh,
|
||||
"cosh(val) -> val\n"
|
||||
"Return the hyperbolic cosine value of the argument [val].") {
|
||||
|
||||
double val;
|
||||
if (!pkGetArgNumber(vm, 1, &val)) return;
|
||||
pkReturnNumber(vm, cosh(val));
|
||||
}
|
||||
|
||||
DEF(stdMathTanh,
|
||||
"tanh(val) -> val\n"
|
||||
"Return the hyperbolic tangent value of the argument [val].") {
|
||||
|
||||
double val;
|
||||
if (!pkGetArgNumber(vm, 1, &val)) return;
|
||||
pkReturnNumber(vm, tanh(val));
|
||||
}
|
||||
|
||||
DEF(stdMathArcSine,
|
||||
"asin(num) -> num\n"
|
||||
"Return the arcsine value of the argument [num] which is an angle "
|
||||
"expressed in radians.") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
|
||||
if (num < -1 || 1 < num) {
|
||||
pkSetRuntimeError(vm, "Argument should be between -1 and +1");
|
||||
}
|
||||
|
||||
pkReturnNumber(vm, asin(num));
|
||||
}
|
||||
|
||||
DEF(stdMathArcCosine,
|
||||
"acos(num) -> num\n"
|
||||
"Return the arc cosine value of the argument [num] which is "
|
||||
"an angle expressed in radians.") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
|
||||
if (num < -1 || 1 < num) {
|
||||
pkSetRuntimeError(vm, "Argument should be between -1 and +1");
|
||||
}
|
||||
|
||||
pkReturnNumber(vm, acos(num));
|
||||
}
|
||||
|
||||
DEF(stdMathArcTangent,
|
||||
"atan(num) -> num\n"
|
||||
"Return the arc tangent value of the argument [num] which is "
|
||||
"an angle expressed in radians.") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
pkReturnNumber(vm, atan(num));
|
||||
}
|
||||
|
||||
DEF(stdMathLog10,
|
||||
"log10(value:num) -> num\n"
|
||||
"Return the logarithm to base 10 of argument [value]") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
pkReturnNumber(vm, log10(num));
|
||||
}
|
||||
|
||||
DEF(stdMathRound,
|
||||
"round(value:num) -> num\n"
|
||||
"Round to nearest integer, away from zero and return the number.") {
|
||||
|
||||
double num;
|
||||
if (!pkGetArgNumber(vm, 1, &num)) return;
|
||||
pkReturnNumber(vm, round(num));
|
||||
}
|
||||
|
||||
void registerModuleMath(PKVM* vm) {
|
||||
|
||||
PkHandle* math = pkNewModule(vm, "math");
|
||||
|
||||
pkModuleAddFunction(vm, math, "floor", stdMathFloor, 1);
|
||||
pkModuleAddFunction(vm, math, "ceil", stdMathCeil, 1);
|
||||
pkModuleAddFunction(vm, math, "pow", stdMathPow, 2);
|
||||
pkModuleAddFunction(vm, math, "sqrt", stdMathSqrt, 1);
|
||||
pkModuleAddFunction(vm, math, "abs", stdMathAbs, 1);
|
||||
pkModuleAddFunction(vm, math, "sign", stdMathSign, 1);
|
||||
pkModuleAddFunction(vm, math, "sin", stdMathSine, 1);
|
||||
pkModuleAddFunction(vm, math, "cos", stdMathCosine, 1);
|
||||
pkModuleAddFunction(vm, math, "tan", stdMathTangent, 1);
|
||||
pkModuleAddFunction(vm, math, "sinh", stdMathSinh, 1);
|
||||
pkModuleAddFunction(vm, math, "cosh", stdMathCosh, 1);
|
||||
pkModuleAddFunction(vm, math, "tanh", stdMathTanh, 1);
|
||||
pkModuleAddFunction(vm, math, "asin", stdMathArcSine, 1);
|
||||
pkModuleAddFunction(vm, math, "acos", stdMathArcCosine, 1);
|
||||
pkModuleAddFunction(vm, math, "atan", stdMathArcTangent, 1);
|
||||
pkModuleAddFunction(vm, math, "log10", stdMathLog10, 1);
|
||||
pkModuleAddFunction(vm, math, "round", stdMathRound, 1);
|
||||
|
||||
// FIXME:
|
||||
// Refactor native type interface and add PI as a global to the module.
|
||||
//
|
||||
// Note that currently it's mutable (since it's a global variable, not
|
||||
// constant and pocketlang doesn't support constant) so the user shouldn't
|
||||
// modify the PI, like in python.
|
||||
//pkModuleAddGlobal(vm, math, "PI", Handle-Of-PI);
|
||||
|
||||
pkRegisterModule(vm, math);
|
||||
pkReleaseHandle(vm, math);
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
* Distributed Under The MIT License
|
||||
*/
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
#include "thirdparty/cwalk/cwalk.h"
|
||||
#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
|
||||
#include "thirdparty/dirent/dirent.h"
|
||||
@ -90,13 +92,13 @@ static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) {
|
||||
/* PATH MODULE FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void _pathSetStyleUnix(PKVM* vm) {
|
||||
DEF(_pathSetStyleUnix, "") {
|
||||
bool value;
|
||||
if (!pkGetArgBool(vm, 1, &value)) return;
|
||||
cwk_path_set_style((value) ? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS);
|
||||
}
|
||||
|
||||
static void _pathGetCWD(PKVM* vm) {
|
||||
DEF(_pathGetCWD, "") {
|
||||
char cwd[FILENAME_MAX];
|
||||
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
|
||||
// TODO: Handle error.
|
||||
@ -104,7 +106,7 @@ static void _pathGetCWD(PKVM* vm) {
|
||||
pkReturnString(vm, cwd);
|
||||
}
|
||||
|
||||
static void _pathAbspath(PKVM* vm) {
|
||||
DEF(_pathAbspath, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
|
||||
@ -113,7 +115,7 @@ static void _pathAbspath(PKVM* vm) {
|
||||
pkReturnStringLength(vm, abspath, len);
|
||||
}
|
||||
|
||||
static void _pathRelpath(PKVM* vm) {
|
||||
DEF(_pathRelpath, "") {
|
||||
const char* from, * path;
|
||||
if (!pkGetArgString(vm, 1, &from, NULL)) return;
|
||||
if (!pkGetArgString(vm, 2, &path, NULL)) return;
|
||||
@ -130,7 +132,7 @@ static void _pathRelpath(PKVM* vm) {
|
||||
pkReturnStringLength(vm, result, len);
|
||||
}
|
||||
|
||||
static void _pathJoin(PKVM* vm) {
|
||||
DEF(_pathJoin, "") {
|
||||
const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL.
|
||||
int argc = pkGetArgc(vm);
|
||||
|
||||
@ -150,7 +152,7 @@ static void _pathJoin(PKVM* vm) {
|
||||
pkReturnStringLength(vm, result, len);
|
||||
}
|
||||
|
||||
static void _pathNormalize(PKVM* vm) {
|
||||
DEF(_pathNormalize, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
|
||||
@ -159,7 +161,7 @@ static void _pathNormalize(PKVM* vm) {
|
||||
pkReturnStringLength(vm, result, len);
|
||||
}
|
||||
|
||||
static void _pathBaseName(PKVM* vm) {
|
||||
DEF(_pathBaseName, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
|
||||
@ -169,7 +171,7 @@ static void _pathBaseName(PKVM* vm) {
|
||||
pkReturnString(vm, base_name);
|
||||
}
|
||||
|
||||
static void _pathDirName(PKVM* vm) {
|
||||
DEF(_pathDirName, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
|
||||
@ -178,14 +180,14 @@ static void _pathDirName(PKVM* vm) {
|
||||
pkReturnStringLength(vm, path, length);
|
||||
}
|
||||
|
||||
static void _pathIsPathAbs(PKVM* vm) {
|
||||
DEF(_pathIsPathAbs, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
|
||||
pkReturnBool(vm, cwk_path_is_absolute(path));
|
||||
}
|
||||
|
||||
static void _pathGetExtension(PKVM* vm) {
|
||||
DEF(_pathGetExtension, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
|
||||
@ -198,19 +200,19 @@ static void _pathGetExtension(PKVM* vm) {
|
||||
}
|
||||
}
|
||||
|
||||
static void _pathExists(PKVM* vm) {
|
||||
DEF(_pathExists, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
pkReturnBool(vm, pathIsExists(path));
|
||||
}
|
||||
|
||||
static void _pathIsFile(PKVM* vm) {
|
||||
DEF(_pathIsFile, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
pkReturnBool(vm, pathIsFileExists(path));
|
||||
}
|
||||
|
||||
static void _pathIsDir(PKVM* vm) {
|
||||
DEF(_pathIsDir, "") {
|
||||
const char* path;
|
||||
if (!pkGetArgString(vm, 1, &path, NULL)) return;
|
||||
pkReturnBool(vm, pathIsDirectoryExists(path));
|
||||
|
@ -55,8 +55,9 @@ extern "C" {
|
||||
#define PK_PUBLIC
|
||||
#endif
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG TYPES */
|
||||
/* POCKETLANG TYPEDEFS & CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// PocketLang Virtual Machine. It'll contain the state of the execution, stack,
|
||||
@ -74,57 +75,13 @@ typedef struct PkHandle PkHandle;
|
||||
// alive use `pkNewHandle()`.
|
||||
typedef void* PkVar;
|
||||
|
||||
// Type enum of the pocketlang's first class types. Note that Object isn't
|
||||
// instanciable (as of now) but they're considered first calss.
|
||||
typedef enum {
|
||||
PK_OBJECT = 0,
|
||||
|
||||
PK_NULL,
|
||||
PK_BOOL,
|
||||
PK_NUMBER,
|
||||
PK_STRING,
|
||||
PK_LIST,
|
||||
PK_MAP,
|
||||
PK_RANGE,
|
||||
PK_MODULE,
|
||||
PK_CLOSURE,
|
||||
PK_FIBER,
|
||||
PK_CLASS,
|
||||
PK_INSTANCE,
|
||||
} PkVarType;
|
||||
|
||||
typedef enum PkVarType PkVarType;
|
||||
typedef enum PkErrorType PkErrorType;
|
||||
typedef enum PkResult PkResult;
|
||||
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.
|
||||
typedef enum {
|
||||
PK_ERROR_COMPILE = 0, // Compile time errors.
|
||||
PK_ERROR_RUNTIME, // Runtime error message.
|
||||
PK_ERROR_STACKTRACE, // One entry of a runtime error stack.
|
||||
} PkErrorType;
|
||||
|
||||
// Result that pocketlang will return after a compilation or running a script
|
||||
// or a function or evaluating an expression.
|
||||
typedef enum {
|
||||
PK_RESULT_SUCCESS = 0, // Successfully finished the execution.
|
||||
|
||||
// Unexpected EOF while compiling the source. This is another compile time
|
||||
// error that will ONLY be returned if we're compiling with the REPL mode set
|
||||
// in the compile options. We need this specific error to indicate the host
|
||||
// application to add another line to the last input. If REPL is not enabled,
|
||||
// this will be PK_RESULT_COMPILE_ERROR.
|
||||
PK_RESULT_UNEXPECTED_EOF,
|
||||
|
||||
PK_RESULT_COMPILE_ERROR, // Compilation failed.
|
||||
PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime.
|
||||
} PkResult;
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG FUNCTION POINTERS & CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// C function pointer which is callable from pocketLang by native module
|
||||
// functions.
|
||||
typedef void (*pkNativeFn)(PKVM* vm);
|
||||
@ -155,35 +112,6 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text);
|
||||
// contain a line ending (\n or \r\n).
|
||||
typedef PkStringPtr (*pkReadFn) (PKVM* vm);
|
||||
|
||||
// A function callback, that'll be called when a native instance (wrapper) is
|
||||
// freed by by the garbage collector, to indicate that pocketlang is done with
|
||||
// the native instance.
|
||||
typedef void (*pkInstFreeFn) (PKVM* vm, void* instance, uint32_t id);
|
||||
|
||||
// A function callback to get the type name of the native instance from
|
||||
// pocketlang, using it's [id]. The returned string won't be copied by
|
||||
// pocketlang so it's expected to be alived since the instance is alive and
|
||||
// recomended to return a C literal string.
|
||||
typedef const char* (*pkInstNameFn) (uint32_t id);
|
||||
|
||||
// A get arribute callback, called by pocket VM when trying to get an attribute
|
||||
// from a native type. to return the value of the attribute use 'pkReturn...()'
|
||||
// functions. DON'T set an error to the VM if the attribute not exists. Example
|
||||
// if the '.as_string' attribute doesn't exists, pocket VM will use a default
|
||||
// to string value.
|
||||
typedef void (*pkInstGetAttribFn) (PKVM* vm, void* instance, uint32_t id,
|
||||
PkStringPtr attrib);
|
||||
|
||||
// Use pkGetArg...(vm, 0, ptr) function to get the value of the attribute
|
||||
// and use 0 as the argument index, using any other arg index value cause UB.
|
||||
//
|
||||
// If the attribute dones't exists DON'T set an error, instead return false.
|
||||
// Pocket VM will handle it, On success update the native instance and return
|
||||
// true. And DON'T ever use 'pkReturn...()' in the attribute setter It's is a
|
||||
// void return function.
|
||||
typedef bool (*pkInstSetAttribFn) (PKVM* vm, void* instance, uint32_t id,
|
||||
PkStringPtr attrib);
|
||||
|
||||
// A function callback symbol for clean/free the pkStringResult.
|
||||
typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result);
|
||||
|
||||
@ -199,6 +127,105 @@ typedef PkStringPtr (*pkResolvePathFn) (PKVM* vm, const char* from,
|
||||
// to indicate if it's failed to load the script.
|
||||
typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path);
|
||||
|
||||
// A function callback to allocate and return a new instance of the registered
|
||||
// class. Which will be called when the instance is constructed. The returned/
|
||||
// data is expected to be alive till the delete callback occurs.
|
||||
typedef void* (*pkNewInstanceFn) ();
|
||||
|
||||
// A function callback to de-allocate the aloocated native instance of the
|
||||
// registered class.
|
||||
typedef void (*pkDeleteInstanceFn) (void*);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG TYPES */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Type enum of the pocketlang's first class types. Note that Object isn't
|
||||
// instanciable (as of now) but they're considered first calss.
|
||||
enum PkVarType {
|
||||
PK_OBJECT = 0,
|
||||
|
||||
PK_NULL,
|
||||
PK_BOOL,
|
||||
PK_NUMBER,
|
||||
PK_STRING,
|
||||
PK_LIST,
|
||||
PK_MAP,
|
||||
PK_RANGE,
|
||||
PK_MODULE,
|
||||
PK_CLOSURE,
|
||||
PK_FIBER,
|
||||
PK_CLASS,
|
||||
PK_INSTANCE,
|
||||
};
|
||||
|
||||
// Type of the error message that pocketlang will provide with the pkErrorFn
|
||||
// callback.
|
||||
enum PkErrorType {
|
||||
PK_ERROR_COMPILE = 0, // Compile time errors.
|
||||
PK_ERROR_RUNTIME, // Runtime error message.
|
||||
PK_ERROR_STACKTRACE, // One entry of a runtime error stack.
|
||||
};
|
||||
|
||||
// Result that pocketlang will return after a compilation or running a script
|
||||
// or a function or evaluating an expression.
|
||||
enum PkResult {
|
||||
PK_RESULT_SUCCESS = 0, // Successfully finished the execution.
|
||||
|
||||
// Unexpected EOF while compiling the source. This is another compile time
|
||||
// error that will ONLY be returned if we're compiling with the REPL mode set
|
||||
// in the compile options. We need this specific error to indicate the host
|
||||
// application to add another line to the last input. If REPL is not enabled,
|
||||
// this will be PK_RESULT_COMPILE_ERROR.
|
||||
PK_RESULT_UNEXPECTED_EOF,
|
||||
|
||||
PK_RESULT_COMPILE_ERROR, // Compilation failed.
|
||||
PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime.
|
||||
};
|
||||
|
||||
// A string pointer wrapper to pass c string between host application and
|
||||
// pocket VM. With a on_done() callback to clean it when the pocket VM is done
|
||||
// with the string.
|
||||
struct PkStringPtr {
|
||||
const char* string; //< The string result.
|
||||
pkResultDoneFn on_done; //< Called once vm done with the string.
|
||||
void* user_data; //< User related data.
|
||||
|
||||
// These values are provided by the pocket VM to the host application, you're
|
||||
// not expected to set this when provideing string to the pocket VM.
|
||||
uint32_t length; //< Length of the string.
|
||||
uint32_t hash; //< Its 32 bit FNV-1a hash.
|
||||
};
|
||||
|
||||
struct PkConfiguration {
|
||||
|
||||
// The callback used to allocate, reallocate, and free. If the function
|
||||
// pointer is NULL it defaults to the VM's realloc(), free() wrappers.
|
||||
pkReallocFn realloc_fn;
|
||||
|
||||
pkErrorFn error_fn;
|
||||
pkWriteFn write_fn;
|
||||
pkReadFn read_fn;
|
||||
|
||||
pkResolvePathFn resolve_path_fn;
|
||||
pkLoadScriptFn load_script_fn;
|
||||
|
||||
// User defined data associated with VM.
|
||||
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;
|
||||
|
||||
// Set to true if compiling in REPL mode, This will print repr version of
|
||||
// each evaluated non-null values.
|
||||
bool repl_mode;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG PUBLIC API */
|
||||
/*****************************************************************************/
|
||||
@ -250,7 +277,7 @@ PK_PUBLIC PkHandle* pkModuleGetGlobal(PKVM* vm, PkHandle* module,
|
||||
const char* name);
|
||||
|
||||
// Add a native function to the given module. If [arity] is -1 that means
|
||||
// The function has variadic parameters and use pkGetArgc() to get the argc.
|
||||
// the function has variadic parameters and use pkGetArgc() to get the argc.
|
||||
// Note that the function will be added as a global variable of the module,
|
||||
// to retrieve the function use pkModuleGetGlobal().
|
||||
PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
|
||||
@ -261,6 +288,30 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
|
||||
// it's statements are wrapped around an implicit main function.
|
||||
PK_PUBLIC PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module);
|
||||
|
||||
// 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);
|
||||
|
||||
// Register the module to the PKVM's modules map, once after it can be
|
||||
// imported in other modules.
|
||||
PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module);
|
||||
|
||||
// Create a new class on the [module] with the [name] and return it.
|
||||
// If the [base_class] is NULL by default it'll set to "Object" class.
|
||||
PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, const char* name,
|
||||
PkHandle* base_class, PkHandle* module,
|
||||
pkNewInstanceFn new_fn,
|
||||
pkDeleteInstanceFn delete_fn);
|
||||
|
||||
// Add a native method to the given class. If the [arity] is -1 that means
|
||||
// the method has variadic parameters and use pkGetArgc() to get the argc.
|
||||
PK_PUBLIC void pkClassAddMethod(PKVM* vm, PkHandle* cls,
|
||||
const char* name,
|
||||
pkNativeFn fptr, int arity);
|
||||
|
||||
// Create and return a new fiber around the function [fn].
|
||||
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
|
||||
|
||||
// Compile the [module] with the provided [source]. Set the compiler options
|
||||
// with the the [options] argument or set to NULL for default options.
|
||||
PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module,
|
||||
@ -289,59 +340,6 @@ PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
|
||||
// yielded or returned value use the pkFiberGetReturnValue() function.
|
||||
PK_PUBLIC PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG PUBLIC TYPE DEFINES */
|
||||
/*****************************************************************************/
|
||||
|
||||
// A string pointer wrapper to pass c string between host application and
|
||||
// pocket VM. With a on_done() callback to clean it when the pocket VM is done
|
||||
// with the string.
|
||||
struct PkStringPtr {
|
||||
const char* string; //< The string result.
|
||||
pkResultDoneFn on_done; //< Called once vm done with the string.
|
||||
void* user_data; //< User related data.
|
||||
|
||||
// These values are provided by the pocket VM to the host application, you're
|
||||
// not expected to set this when provideing string to the pocket VM.
|
||||
uint32_t length; //< Length of the string.
|
||||
uint32_t hash; //< Its 32 bit FNV-1a hash.
|
||||
};
|
||||
|
||||
struct PkConfiguration {
|
||||
|
||||
// The callback used to allocate, reallocate, and free. If the function
|
||||
// pointer is NULL it defaults to the VM's realloc(), free() wrappers.
|
||||
pkReallocFn realloc_fn;
|
||||
|
||||
pkErrorFn error_fn;
|
||||
pkWriteFn write_fn;
|
||||
pkReadFn read_fn;
|
||||
|
||||
pkInstFreeFn inst_free_fn;
|
||||
pkInstNameFn inst_name_fn;
|
||||
pkInstGetAttribFn inst_get_attrib_fn;
|
||||
pkInstSetAttribFn inst_set_attrib_fn;
|
||||
|
||||
pkResolvePathFn resolve_path_fn;
|
||||
pkLoadScriptFn load_script_fn;
|
||||
|
||||
// User defined data associated with VM.
|
||||
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;
|
||||
|
||||
// Set to true if compiling in REPL mode, This will print repr version of
|
||||
// each evaluated non-null values.
|
||||
bool repl_mode;
|
||||
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* NATIVE FUNCTION API */
|
||||
/*****************************************************************************/
|
||||
@ -352,6 +350,9 @@ PK_PUBLIC void pkSetRuntimeError(PKVM* vm, const char* message);
|
||||
// TODO: Set a runtime error to VM, with the formated string.
|
||||
//PK_PUBLIC void pkSetRuntimeErrorFmt(PKVM* vm, const char* fmt, ...);
|
||||
|
||||
// Returns native [self] of the current method as a void*.
|
||||
PK_PUBLIC void* pkGetSelf(const PKVM* vm);
|
||||
|
||||
// Return the type of the [value] this will help to get the type of the
|
||||
// variable that was extracted from pkGetArg() earlier.
|
||||
PK_PUBLIC PkVarType pkGetValueType(const PkVar value);
|
||||
@ -383,7 +384,6 @@ PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value);
|
||||
PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value);
|
||||
PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg,
|
||||
const char** value, uint32_t* length);
|
||||
PK_PUBLIC bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value);
|
||||
PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value);
|
||||
|
||||
// The functions follow are used to set the return value of the current native
|
||||
@ -397,8 +397,6 @@ PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len);
|
||||
PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value);
|
||||
PK_PUBLIC void pkReturnHandle(PKVM* vm, PkHandle* handle);
|
||||
|
||||
PK_PUBLIC void pkReturnInstNative(PKVM* vm, void* data, uint32_t id);
|
||||
|
||||
// Returns the cstring pointer of the given string. Make sure if the [value] is
|
||||
// a string before calling this function, otherwise it'll fail an assertion.
|
||||
PK_PUBLIC const char* pkStringGetData(const PkVar value);
|
||||
@ -424,23 +422,6 @@ PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len);
|
||||
PK_PUBLIC PkHandle* pkNewList(PKVM* vm);
|
||||
PK_PUBLIC PkHandle* pkNewMap(PKVM* vm);
|
||||
|
||||
// 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);
|
||||
|
||||
// Register the module to the PKVM's modules map, once after it can be
|
||||
// imported in other modules.
|
||||
PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module);
|
||||
|
||||
// Create and return a new fiber around the function [fn].
|
||||
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
|
||||
|
||||
// Create and return a native instance around the [data]. The [id] is the
|
||||
// unique id of the instance, this would be used to check if two instances are
|
||||
// equal and used to get the name of the instance using NativeTypeNameFn
|
||||
// callback.
|
||||
PK_PUBLIC PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id);
|
||||
|
||||
// TODO: Create a primitive (non garbage collected) variable buffer (or a
|
||||
// fixed size array) to store them and make the handle points to the variable
|
||||
// in that buffer, this will prevent us from invoking an allocation call for
|
||||
|
@ -125,6 +125,8 @@ typedef enum {
|
||||
TK_NOT, // not / !
|
||||
TK_TRUE, // true
|
||||
TK_FALSE, // false
|
||||
TK_SELF, // self
|
||||
// TODO: TK_SUPER
|
||||
|
||||
TK_DO, // do
|
||||
TK_THEN, // then
|
||||
@ -186,6 +188,7 @@ static _Keyword _keywords[] = {
|
||||
{ "not", 3, TK_NOT },
|
||||
{ "true", 4, TK_TRUE },
|
||||
{ "false", 5, TK_FALSE },
|
||||
{ "self", 4, TK_SELF },
|
||||
{ "do", 2, TK_DO },
|
||||
{ "then", 4, TK_THEN },
|
||||
{ "while", 5, TK_WHILE },
|
||||
@ -241,6 +244,14 @@ typedef enum {
|
||||
DEPTH_LOCAL, //< Local scope. Increase with inner scope.
|
||||
} Depth;
|
||||
|
||||
typedef enum {
|
||||
FUNC_MAIN, // The body function of the script.
|
||||
FUNC_TOPLEVEL,
|
||||
FUNC_LITERAL,
|
||||
FUNC_METHOD,
|
||||
FUNC_CONSTRUCTOR,
|
||||
} FuncType;
|
||||
|
||||
typedef struct {
|
||||
const char* name; //< Directly points into the source string.
|
||||
uint32_t length; //< Length of the name.
|
||||
@ -314,6 +325,9 @@ typedef struct sUpvalueInfo {
|
||||
|
||||
typedef struct sFunc {
|
||||
|
||||
// Type of the current function.
|
||||
FuncType type;
|
||||
|
||||
// Scope of the function. -2 for module body function, -1 for top level
|
||||
// function and literal functions will have the scope where it declared.
|
||||
int depth;
|
||||
@ -396,8 +410,9 @@ typedef struct sParser {
|
||||
ForwardName forwards[MAX_FORWARD_NAMES];
|
||||
int forwards_count;
|
||||
|
||||
bool repl_mode; //< True if compiling for REPL.
|
||||
bool has_errors; //< True if any syntex error occurred at.
|
||||
bool repl_mode;
|
||||
bool parsing_class;
|
||||
bool has_errors;
|
||||
bool need_more_lines; //< True if we need more lines in REPL mode.
|
||||
|
||||
} Parser;
|
||||
@ -507,6 +522,7 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler,
|
||||
parser->forwards_count = 0;
|
||||
|
||||
parser->repl_mode = !!(compiler->options && compiler->options->repl_mode);
|
||||
parser->parsing_class = false;
|
||||
parser->has_errors = false;
|
||||
parser->need_more_lines = false;
|
||||
}
|
||||
@ -1257,6 +1273,19 @@ static int findBuiltinFunction(const PKVM* vm,
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find the builtin classes name and returns it's index in the VM's builtin
|
||||
// classes array, if not found returns -1.
|
||||
static int findBuiltinClass(const PKVM* vm,
|
||||
const char* name, uint32_t length) {
|
||||
for (int i = 0; i < PK_INSTANCE; i++) {
|
||||
uint32_t bfn_length = vm->builtin_classes[i]->name->length;
|
||||
if (IS_CSTR_EQ(vm->builtin_classes[i]->name, name, length)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find the local with the [name] in the given function [func] and return
|
||||
// it's index, if not found returns -1.
|
||||
static int findLocal(Func* func, const char* name, uint32_t length) {
|
||||
@ -1337,7 +1366,8 @@ typedef enum {
|
||||
NAME_LOCAL_VAR, //< Including parameter.
|
||||
NAME_UPVALUE, //< Local to an enclosing function.
|
||||
NAME_GLOBAL_VAR,
|
||||
NAME_BUILTIN_FN, //< Native builtin function.
|
||||
NAME_BUILTIN_FN, //< Native builtin function.
|
||||
NAME_BUILTIN_TY, //< Builtin primitive type classes.
|
||||
} NameDefnType;
|
||||
|
||||
// Identifier search result.
|
||||
@ -1394,6 +1424,13 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
|
||||
return result;
|
||||
}
|
||||
|
||||
index = findBuiltinClass(compiler->parser.vm, name, length);
|
||||
if (index != -1) {
|
||||
result.type = NAME_BUILTIN_TY;
|
||||
result.index = index;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1423,7 +1460,7 @@ static void compilerChangeStack(Compiler* compiler, int num);
|
||||
|
||||
// Forward declaration of grammar functions.
|
||||
static void parsePrecedence(Compiler* compiler, Precedence precedence);
|
||||
static void compileFunction(Compiler* compiler, bool is_literal);
|
||||
static int compileFunction(Compiler* compiler, FuncType fn_type);
|
||||
static void compileExpression(Compiler* compiler);
|
||||
|
||||
static void exprLiteral(Compiler* compiler);
|
||||
@ -1448,6 +1485,8 @@ static void exprSubscript(Compiler* compiler);
|
||||
// true, false, null, self.
|
||||
static void exprValue(Compiler* compiler);
|
||||
|
||||
static void exprSelf(Compiler* compiler);
|
||||
|
||||
#define NO_RULE { NULL, NULL, PREC_NONE }
|
||||
#define NO_INFIX PREC_NONE
|
||||
|
||||
@ -1513,6 +1552,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
|
||||
/* TK_NOT */ { exprUnaryOp, NULL, PREC_UNARY },
|
||||
/* TK_TRUE */ { exprValue, NULL, NO_INFIX },
|
||||
/* TK_FALSE */ { exprValue, NULL, NO_INFIX },
|
||||
/* TK_FALSE */ { exprSelf, NULL, NO_INFIX },
|
||||
/* TK_DO */ NO_RULE,
|
||||
/* TK_THEN */ NO_RULE,
|
||||
/* TK_WHILE */ NO_RULE,
|
||||
@ -1573,6 +1613,11 @@ static void emitPushName(Compiler* compiler, NameDefnType type, int index) {
|
||||
emitOpcode(compiler, OP_PUSH_BUILTIN_FN);
|
||||
emitByte(compiler, index);
|
||||
return;
|
||||
|
||||
case NAME_BUILTIN_TY:
|
||||
emitOpcode(compiler, OP_PUSH_BUILTIN_TY);
|
||||
emitByte(compiler, index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1584,6 +1629,7 @@ static void emitStoreName(Compiler* compiler, NameDefnType type, int index) {
|
||||
switch (type) {
|
||||
case NAME_NOT_DEFINED:
|
||||
case NAME_BUILTIN_FN:
|
||||
case NAME_BUILTIN_TY:
|
||||
UNREACHABLE();
|
||||
|
||||
case NAME_LOCAL_VAR:
|
||||
@ -1671,7 +1717,7 @@ static void exprInterpolation(Compiler* compiler) {
|
||||
}
|
||||
|
||||
static void exprFunction(Compiler* compiler) {
|
||||
compileFunction(compiler, true);
|
||||
compileFunction(compiler, FUNC_LITERAL);
|
||||
}
|
||||
|
||||
static void exprName(Compiler* compiler) {
|
||||
@ -1701,7 +1747,9 @@ static void exprName(Compiler* compiler) {
|
||||
// like python does) and it's recommented to define all the globals
|
||||
// before entering a local scope.
|
||||
|
||||
if (result.type == NAME_NOT_DEFINED || result.type == NAME_BUILTIN_FN) {
|
||||
if (result.type == NAME_NOT_DEFINED ||
|
||||
result.type == NAME_BUILTIN_FN ||
|
||||
result.type == NAME_BUILTIN_TY ) {
|
||||
name_type = (compiler->scope_depth == DEPTH_GLOBAL)
|
||||
? NAME_GLOBAL_VAR
|
||||
: NAME_LOCAL_VAR;
|
||||
@ -1897,7 +1945,11 @@ static void exprMap(Compiler* compiler) {
|
||||
consume(compiler, TK_RBRACE, "Expected '}' after map elements.");
|
||||
}
|
||||
|
||||
static void exprCall(Compiler* compiler) {
|
||||
// This function is reused between calls and method calls. if the [call_type]
|
||||
// is OP_METHOD_CALL the [method] should refer a string in the module's
|
||||
// constant pool, otherwise it's ignored.
|
||||
static void _compileCall(Compiler* compiler, Opcode call_type, int method) {
|
||||
ASSERT((call_type == OP_CALL) || (call_type == OP_METHOD_CALL), OOPS);
|
||||
|
||||
// Compile parameters.
|
||||
int argc = 0;
|
||||
@ -1911,14 +1963,24 @@ static void exprCall(Compiler* compiler) {
|
||||
consume(compiler, TK_RPARAN, "Expected ')' after parameter list.");
|
||||
}
|
||||
|
||||
emitOpcode(compiler, OP_CALL);
|
||||
emitOpcode(compiler, call_type);
|
||||
|
||||
emitByte(compiler, argc);
|
||||
|
||||
if (call_type == OP_METHOD_CALL) {
|
||||
ASSERT_INDEX(method, (int)compiler->module->constants.count);
|
||||
emitShort(compiler, method);
|
||||
}
|
||||
|
||||
// After the call the arguments will be popped and the callable
|
||||
// will be replaced with the return value.
|
||||
compilerChangeStack(compiler, -argc);
|
||||
}
|
||||
|
||||
static void exprCall(Compiler* compiler) {
|
||||
_compileCall(compiler, OP_CALL, -1);
|
||||
}
|
||||
|
||||
static void exprAttrib(Compiler* compiler) {
|
||||
consume(compiler, TK_NAME, "Expected an attribute name after '.'.");
|
||||
const char* name = compiler->parser.previous.start;
|
||||
@ -1929,6 +1991,12 @@ static void exprAttrib(Compiler* compiler) {
|
||||
moduleAddString(compiler->module, compiler->parser.vm,
|
||||
name, length, &index);
|
||||
|
||||
// Check if it's a method call.
|
||||
if (match(compiler, TK_LPARAN)) {
|
||||
_compileCall(compiler, OP_METHOD_CALL, index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (compiler->l_value && matchAssignment(compiler)) {
|
||||
TokenType assignment = compiler->parser.previous.type;
|
||||
skipNewLines(compiler);
|
||||
@ -1986,6 +2054,25 @@ static void exprValue(Compiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
static void exprSelf(Compiler* compiler) {
|
||||
|
||||
if (compiler->func->type == FUNC_CONSTRUCTOR ||
|
||||
compiler->func->type == FUNC_METHOD) {
|
||||
emitOpcode(compiler, OP_PUSH_SELF);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reach here 'self' is used in either non method or a closure
|
||||
// inside a method.
|
||||
|
||||
if (!compiler->parser.parsing_class) {
|
||||
parseError(compiler, "Invalid use of 'self'.");
|
||||
} else {
|
||||
// FIXME:
|
||||
parseError(compiler, "TODO: Closures cannot capture 'self' for now.");
|
||||
}
|
||||
}
|
||||
|
||||
static void parsePrecedence(Compiler* compiler, Precedence precedence) {
|
||||
lexToken(&(compiler->parser));
|
||||
GrammarFn prefix = getRule(compiler->parser.previous.type)->prefix;
|
||||
@ -2155,7 +2242,8 @@ static void compilerExitBlock(Compiler* compiler) {
|
||||
}
|
||||
|
||||
static void compilerPushFunc(Compiler* compiler, Func* fn,
|
||||
Function* func) {
|
||||
Function* func, FuncType type) {
|
||||
fn->type = type;
|
||||
fn->outer_func = compiler->func;
|
||||
fn->local_count = 0;
|
||||
fn->stack_size = 0;
|
||||
@ -2269,99 +2357,74 @@ static void compileStatement(Compiler* compiler);
|
||||
static void compileBlockBody(Compiler* compiler, BlockType type);
|
||||
|
||||
// Compile a class and return it's index in the module's types buffer.
|
||||
static void compileClass(Compiler* compiler) {
|
||||
static int compileClass(Compiler* compiler) {
|
||||
|
||||
ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS);
|
||||
|
||||
// Consume the name of the type.
|
||||
consume(compiler, TK_NAME, "Expected a type name.");
|
||||
consume(compiler, TK_NAME, "Expected a class name.");
|
||||
const char* name = compiler->parser.previous.start;
|
||||
int name_len = compiler->parser.previous.length;
|
||||
int name_line = compiler->parser.previous.line;
|
||||
|
||||
// Create a new class.
|
||||
int cls_index, ctor_index;
|
||||
Class* cls = newClass(compiler->parser.vm, compiler->module,
|
||||
name, (uint32_t)name_len, &cls_index, &ctor_index);
|
||||
cls->ctor->fn->arity = 0;
|
||||
|
||||
// FIXME:
|
||||
// Temproary patch for moving functions and classes to constant buffer.
|
||||
ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS);
|
||||
int index = compilerAddVariable(compiler,
|
||||
compiler->parser.previous.start,
|
||||
compiler->parser.previous.length,
|
||||
compiler->parser.previous.line);
|
||||
moduleSetGlobal(compiler->module, index, VAR_OBJ(cls));
|
||||
int cls_index;
|
||||
PKVM* _vm = compiler->parser.vm;
|
||||
Class* cls = newClass(_vm, name, name_len,
|
||||
_vm->builtin_classes[PK_OBJECT], compiler->module,
|
||||
NULL, &cls_index);
|
||||
vmPushTempRef(_vm, &cls->_super); // cls.
|
||||
compiler->parser.parsing_class = true;
|
||||
|
||||
// Check count exceeded.
|
||||
checkMaxConstantsReached(compiler, cls_index);
|
||||
checkMaxConstantsReached(compiler, ctor_index);
|
||||
|
||||
// Compile the constructor function.
|
||||
ASSERT(compiler->func->ptr == compiler->module->body->fn, OOPS);
|
||||
Func curr_fn;
|
||||
compilerPushFunc(compiler, &curr_fn, cls->ctor->fn);
|
||||
compilerEnterBlock(compiler);
|
||||
|
||||
// Push an instance on the stack.
|
||||
emitOpcode(compiler, OP_PUSH_INSTANCE);
|
||||
emitShort(compiler, cls_index);
|
||||
|
||||
skipNewLines(compiler);
|
||||
TokenType next = peek(compiler);
|
||||
while (next != TK_END && next != TK_EOF) {
|
||||
while (!match(compiler, TK_END)) {
|
||||
// At the top level the stack size should be 0, before and after compiling
|
||||
// a top level statement, since there aren't any locals at the top level.
|
||||
ASSERT(compiler->parser.has_errors ||
|
||||
compiler->func->stack_size == 0, OOPS);
|
||||
|
||||
// Compile field name.
|
||||
consume(compiler, TK_NAME, "Expected a type name.");
|
||||
const char* f_name = compiler->parser.previous.start;
|
||||
int f_len = compiler->parser.previous.length;
|
||||
consume(compiler, TK_DEF, "Expected method definition.");
|
||||
int fn_index = compileFunction(compiler, FUNC_METHOD);
|
||||
Var fn_var = compiler->module->constants.data[fn_index];
|
||||
ASSERT(IS_OBJ_TYPE(fn_var, OBJ_FUNC), OOPS);
|
||||
|
||||
int f_index = 0;
|
||||
String* new_name = moduleAddString(compiler->module, compiler->parser.vm,
|
||||
f_name, f_len, &f_index);
|
||||
// TODO: check if the constructor or method already exists and report
|
||||
// error. Make sure the error report line match the name token's line.
|
||||
|
||||
for (uint32_t i = 0; i < cls->field_names.count; i++) {
|
||||
String* prev = moduleGetStringAt(compiler->module,
|
||||
cls->field_names.data[i]);
|
||||
ASSERT(prev != NULL, OOPS);
|
||||
if (IS_STR_EQ(new_name, prev)) {
|
||||
parseError(compiler, "Class field with name '%s' already exists.",
|
||||
new_name->data);
|
||||
}
|
||||
Closure* method = newClosure(_vm, (Function*)AS_OBJ(fn_var));
|
||||
if (strcmp(method->fn->name, "_init") == 0) {
|
||||
cls->ctor = method;
|
||||
|
||||
} else {
|
||||
vmPushTempRef(_vm, &method->_super); // method.
|
||||
pkClosureBufferWrite(&cls->methods, _vm, method);
|
||||
vmPopTempRef(_vm); // method.
|
||||
}
|
||||
|
||||
pkUintBufferWrite(&cls->field_names, compiler->parser.vm, f_index);
|
||||
|
||||
// Consume the assignment expression.
|
||||
consume(compiler, TK_EQ, "Expected an assignment after field name.");
|
||||
compileExpression(compiler); // Assigned value.
|
||||
consumeEndStatement(compiler);
|
||||
|
||||
// At this point the stack top would be the expression.
|
||||
emitOpcode(compiler, OP_INST_APPEND);
|
||||
// At the top level the stack size should be 0, before and after compiling
|
||||
// a top level statement, since there aren't any locals at the top level.
|
||||
ASSERT(compiler->parser.has_errors ||
|
||||
compiler->func->stack_size == 0, OOPS);
|
||||
|
||||
skipNewLines(compiler);
|
||||
next = peek(compiler);
|
||||
}
|
||||
consume(compiler, TK_END, "Expected 'end' after a class declaration end.");
|
||||
|
||||
// The instance pushed by the OP_PUSH_INSTANCE instruction is at the top
|
||||
// of the stack, return it (Constructor will return the instance). Note that
|
||||
// the emitFunctionEnd function will also add a return instruction but that's
|
||||
// for functions which doesn't return anything explicitly. This return won't
|
||||
// change compiler's stack size because it won't pop the return value.
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
compiler->parser.parsing_class = false;
|
||||
vmPopTempRef(_vm); // cls.
|
||||
|
||||
compilerExitBlock(compiler);
|
||||
emitFunctionEnd(compiler);
|
||||
compilerPopFunc(compiler);
|
||||
return cls_index;
|
||||
}
|
||||
|
||||
// Compile a function and return it's index in the module's function buffer.
|
||||
static void compileFunction(Compiler* compiler, bool is_literal) {
|
||||
static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
|
||||
const char* name;
|
||||
int name_length;
|
||||
|
||||
if (!is_literal) {
|
||||
if (fn_type != FUNC_LITERAL) {
|
||||
consume(compiler, TK_NAME, "Expected a function name.");
|
||||
name = compiler->parser.previous.start;
|
||||
name_length = compiler->parser.previous.length;
|
||||
@ -2376,7 +2439,7 @@ static void compileFunction(Compiler* compiler, bool is_literal) {
|
||||
compiler->module, false, NULL, &fn_index);
|
||||
checkMaxConstantsReached(compiler, fn_index);
|
||||
|
||||
if (!is_literal) {
|
||||
if (fn_type != FUNC_LITERAL) {
|
||||
ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS);
|
||||
int name_line = compiler->parser.previous.line;
|
||||
int g_index = compilerAddVariable(compiler, name, name_length, name_line);
|
||||
@ -2387,8 +2450,12 @@ static void compileFunction(Compiler* compiler, bool is_literal) {
|
||||
vmPopTempRef(compiler->parser.vm); // func.
|
||||
}
|
||||
|
||||
if (fn_type == FUNC_METHOD && strncmp(name, "_init", name_length) == 0) {
|
||||
fn_type = FUNC_CONSTRUCTOR;
|
||||
}
|
||||
|
||||
Func curr_fn;
|
||||
compilerPushFunc(compiler, &curr_fn, func);
|
||||
compilerPushFunc(compiler, &curr_fn, func, fn_type);
|
||||
|
||||
int argc = 0;
|
||||
compilerEnterBlock(compiler); // Parameter depth.
|
||||
@ -2431,6 +2498,11 @@ static void compileFunction(Compiler* compiler, bool is_literal) {
|
||||
|
||||
compileBlockBody(compiler, BLOCK_FUNC);
|
||||
|
||||
if (fn_type == FUNC_CONSTRUCTOR) {
|
||||
emitOpcode(compiler, OP_PUSH_SELF);
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
}
|
||||
|
||||
consume(compiler, TK_END, "Expected 'end' after function definition end.");
|
||||
compilerExitBlock(compiler); // Parameter depth.
|
||||
emitFunctionEnd(compiler);
|
||||
@ -2448,7 +2520,7 @@ static void compileFunction(Compiler* compiler, bool is_literal) {
|
||||
// function of this function, and the bellow emit calls will write to the
|
||||
// outer function. If it's a literal function, we need to push a closure
|
||||
// of it on the stack.
|
||||
if (is_literal) {
|
||||
if (fn_type == FUNC_LITERAL) {
|
||||
emitOpcode(compiler, OP_PUSH_CLOSURE);
|
||||
emitShort(compiler, fn_index);
|
||||
|
||||
@ -2458,6 +2530,8 @@ static void compileFunction(Compiler* compiler, bool is_literal) {
|
||||
emitByte(compiler, curr_fn.upvalues[i].index);
|
||||
}
|
||||
}
|
||||
|
||||
return fn_index;
|
||||
}
|
||||
|
||||
// Finish a block body.
|
||||
@ -3022,10 +3096,22 @@ static void compileStatement(Compiler* compiler) {
|
||||
}
|
||||
|
||||
if (matchEndStatement(compiler)) {
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
|
||||
// Constructors will return self.
|
||||
if (compiler->func->type == FUNC_CONSTRUCTOR) {
|
||||
emitOpcode(compiler, OP_PUSH_SELF);
|
||||
} else {
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
}
|
||||
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
|
||||
} else {
|
||||
|
||||
if (compiler->func->type == FUNC_CONSTRUCTOR) {
|
||||
parseError(compiler, "Cannor 'return' a value from constructor.");
|
||||
}
|
||||
|
||||
compileExpression(compiler); //< Return value is at stack top.
|
||||
|
||||
// If the last expression parsed with compileExpression() is a call
|
||||
@ -3085,7 +3171,7 @@ static void compileTopLevelStatement(Compiler* compiler) {
|
||||
compileClass(compiler);
|
||||
|
||||
} else if (match(compiler, TK_DEF)) {
|
||||
compileFunction(compiler, false);
|
||||
compileFunction(compiler, FUNC_TOPLEVEL);
|
||||
|
||||
} else if (match(compiler, TK_FROM)) {
|
||||
compileFromImport(compiler);
|
||||
@ -3139,7 +3225,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source,
|
||||
uint32_t globals_count = module->globals.count;
|
||||
|
||||
Func curr_fn;
|
||||
compilerPushFunc(compiler, &curr_fn, module->body->fn);
|
||||
compilerPushFunc(compiler, &curr_fn, module->body->fn, FUNC_MAIN);
|
||||
|
||||
// Lex initial tokens. current <-- next.
|
||||
lexToken(&(compiler->parser));
|
||||
@ -3197,7 +3283,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source,
|
||||
}
|
||||
|
||||
#if DUMP_BYTECODE
|
||||
dumpFunctionCode(compiler->parser.vm, module->body);
|
||||
dumpFunctionCode(compiler->parser.vm, module->body->fn);
|
||||
#endif
|
||||
|
||||
// Return the compilation result.
|
||||
|
694
src/pk_core.c
694
src/pk_core.c
@ -15,13 +15,6 @@
|
||||
#include "pk_utils.h"
|
||||
#include "pk_vm.h"
|
||||
|
||||
// M_PI is non standard. The macro _USE_MATH_DEFINES defining before importing
|
||||
// <math.h> will define the constants for MSVC. But for a portable solution,
|
||||
// we're defining it ourselves if it isn't already.
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
// Returns the docstring of the function, which is a static const char* defined
|
||||
// just above the function by the DEF() macro below.
|
||||
#define DOCSTRING(fn) _pk_doc_##fn
|
||||
@ -69,8 +62,67 @@ void pkRegisterModule(PKVM* vm, PkHandle* module) {
|
||||
vmRegisterModule(vm, module_, module_->name);
|
||||
}
|
||||
|
||||
PkHandle* pkNewClass(PKVM* vm, const char* name,
|
||||
PkHandle* base_class, PkHandle* module,
|
||||
pkNewInstanceFn new_fn,
|
||||
pkDeleteInstanceFn delete_fn) {
|
||||
CHECK_NULL(module);
|
||||
CHECK_NULL(name);
|
||||
CHECK_TYPE(module, OBJ_MODULE);
|
||||
|
||||
Class* super = vm->builtin_classes[PK_OBJECT];
|
||||
if (base_class != NULL) {
|
||||
CHECK_TYPE(base_class, OBJ_CLASS);
|
||||
super = (Class*)AS_OBJ(base_class->value);
|
||||
}
|
||||
|
||||
Class* class_ = newClass(vm, name, (int)strlen(name),
|
||||
super, (Module*)AS_OBJ(module->value),
|
||||
NULL, NULL);
|
||||
class_->new_fn = new_fn;
|
||||
class_->delete_fn = delete_fn;
|
||||
|
||||
return vmNewHandle(vm, VAR_OBJ(class_));
|
||||
}
|
||||
|
||||
void pkClassAddMethod(PKVM* vm, PkHandle* cls,
|
||||
const char* name,
|
||||
pkNativeFn fptr, int arity) {
|
||||
CHECK_NULL(cls);
|
||||
CHECK_NULL(fptr);
|
||||
CHECK_TYPE(cls, OBJ_CLASS);
|
||||
|
||||
Class* class_ = (Class*)AS_OBJ(cls->value);
|
||||
|
||||
Function* fn = newFunction(vm, name, (int)strlen(name),
|
||||
class_->owner, true, NULL, NULL);
|
||||
|
||||
// No need to push the function to temp references of the VM
|
||||
// since it's written to the constant pool of the module and the module
|
||||
// won't be garbage collected (class handle has reference to the module).
|
||||
|
||||
Closure* method = newClosure(vm, fn);
|
||||
|
||||
// FIXME: name "_init" is literal everywhere.
|
||||
if (strcmp(name, "_init") == 0) {
|
||||
class_->ctor = method;
|
||||
|
||||
} else {
|
||||
vmPushTempRef(vm, &method->_super); // method.
|
||||
pkClosureBufferWrite(&class_->methods, vm, method);
|
||||
vmPopTempRef(vm); // method.
|
||||
}
|
||||
}
|
||||
|
||||
void* pkGetSelf(const PKVM* vm) {
|
||||
ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS);
|
||||
Instance* inst = (Instance*)AS_OBJ(vm->fiber->self);
|
||||
ASSERT(inst->native != NULL, OOPS);
|
||||
return inst->native;
|
||||
}
|
||||
|
||||
void pkModuleAddGlobal(PKVM* vm, PkHandle* module,
|
||||
const char* name, PkHandle* value) {
|
||||
const char* name, PkHandle* value) {
|
||||
CHECK_TYPE(module, OBJ_MODULE);
|
||||
CHECK_NULL(value);
|
||||
|
||||
@ -230,33 +282,6 @@ bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value) {
|
||||
CHECK_GET_ARG_API_ERRORS();
|
||||
|
||||
Var val = ARG(arg);
|
||||
bool is_native_instance = false;
|
||||
|
||||
if (IS_OBJ_TYPE(val, OBJ_INST)) {
|
||||
Instance* inst = ((Instance*)AS_OBJ(val));
|
||||
if (inst->is_native && inst->native_id == id) {
|
||||
*value = inst->native;
|
||||
is_native_instance = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_native_instance) {
|
||||
const char* ty_name = "$(?)";
|
||||
if (vm->config.inst_name_fn != NULL) {
|
||||
ty_name = vm->config.inst_name_fn(id);
|
||||
}
|
||||
|
||||
ERR_INVALID_ARG_TYPE(ty_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) {
|
||||
CHECK_GET_ARG_API_ERRORS();
|
||||
|
||||
@ -300,10 +325,6 @@ void pkReturnHandle(PKVM* vm, PkHandle* handle) {
|
||||
RET(handle->value);
|
||||
}
|
||||
|
||||
void pkReturnInstNative(PKVM* vm, void* data, uint32_t id) {
|
||||
RET(VAR_OBJ(newInstanceNative(vm, data, id)));
|
||||
}
|
||||
|
||||
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.");
|
||||
@ -514,7 +535,7 @@ DEF(coreAssert,
|
||||
|
||||
DEF(coreBin,
|
||||
"bin(value:num) -> string\n"
|
||||
"Returns as a binary value string with '0x' prefix.") {
|
||||
"Returns as a binary value string with '0b' prefix.") {
|
||||
|
||||
int64_t value;
|
||||
if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return;
|
||||
@ -592,6 +613,35 @@ DEF(coreToString,
|
||||
RET(VAR_OBJ(toString(vm, ARG(1))));
|
||||
}
|
||||
|
||||
DEF(coreChr,
|
||||
"chr(value:num) -> string\n"
|
||||
"Returns the ASCII string value of the integer argument.") {
|
||||
|
||||
int64_t num;
|
||||
if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return;
|
||||
|
||||
if (!IS_NUM_BYTE(num)) {
|
||||
RET_ERR(newString(vm, "The number is not in a byte range."));
|
||||
}
|
||||
|
||||
char c = (char)num;
|
||||
RET(VAR_OBJ(newStringLength(vm, &c, 1)));
|
||||
}
|
||||
|
||||
DEF(coreOrd,
|
||||
"ord(value:string) -> num\n"
|
||||
"Returns integer value of the given ASCII character.") {
|
||||
|
||||
String* c;
|
||||
if (!validateArgString(vm, 1, &c)) return;
|
||||
if (c->length != 1) {
|
||||
RET_ERR(newString(vm, "Expected a string of length 1."));
|
||||
|
||||
} else {
|
||||
RET(VAR_NUM((double)c->data[0]));
|
||||
}
|
||||
}
|
||||
|
||||
DEF(corePrint,
|
||||
"print(...) -> void\n"
|
||||
"Write each argument as space seperated, to the stdout and ends with a "
|
||||
@ -679,35 +729,6 @@ DEF(coreStrSub,
|
||||
RET(VAR_OBJ(newStringLength(vm, str->data + pos, (uint32_t)len)));
|
||||
}
|
||||
|
||||
DEF(coreStrChr,
|
||||
"str_chr(value:num) -> string\n"
|
||||
"Returns the ASCII string value of the integer argument.") {
|
||||
|
||||
int64_t num;
|
||||
if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return;
|
||||
|
||||
if (!IS_NUM_BYTE(num)) {
|
||||
RET_ERR(newString(vm, "The number is not in a byte range."));
|
||||
}
|
||||
|
||||
char c = (char)num;
|
||||
RET(VAR_OBJ(newStringLength(vm, &c, 1)));
|
||||
}
|
||||
|
||||
DEF(coreStrOrd,
|
||||
"str_ord(value:string) -> num\n"
|
||||
"Returns integer value of the given ASCII character.") {
|
||||
|
||||
String* c;
|
||||
if (!validateArgString(vm, 1, &c)) return;
|
||||
if (c->length != 1) {
|
||||
RET_ERR(newString(vm, "Expected a string of length 1."));
|
||||
|
||||
} else {
|
||||
RET(VAR_NUM((double)c->data[0]));
|
||||
}
|
||||
}
|
||||
|
||||
// List functions.
|
||||
// ---------------
|
||||
|
||||
@ -785,14 +806,17 @@ static void initializeBuiltinFunctions(PKVM* vm) {
|
||||
INITIALIZE_BUILTIN_FN("hex", coreHex, 1);
|
||||
INITIALIZE_BUILTIN_FN("yield", coreYield, -1);
|
||||
INITIALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
||||
INITIALIZE_BUILTIN_FN("chr", coreChr, 1);
|
||||
INITIALIZE_BUILTIN_FN("ord", coreOrd, 1);
|
||||
INITIALIZE_BUILTIN_FN("print", corePrint, -1);
|
||||
INITIALIZE_BUILTIN_FN("input", coreInput, -1);
|
||||
INITIALIZE_BUILTIN_FN("exit", coreExit, -1);
|
||||
|
||||
// FIXME:
|
||||
// move this functions as methods. and make "append()" a builtin.
|
||||
|
||||
// String functions.
|
||||
INITIALIZE_BUILTIN_FN("str_sub", coreStrSub, 3);
|
||||
INITIALIZE_BUILTIN_FN("str_chr", coreStrChr, 1);
|
||||
INITIALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1);
|
||||
|
||||
// List functions.
|
||||
INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2);
|
||||
@ -913,254 +937,6 @@ DEF(stdLangWrite,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move math to cli as it's not part of the pocketlang core.
|
||||
//
|
||||
// 'math' library methods.
|
||||
// -----------------------
|
||||
|
||||
DEF(stdMathFloor,
|
||||
"floor(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(floor(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathCeil,
|
||||
"ceil(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(ceil(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathPow,
|
||||
"pow(value:num) -> num\n") {
|
||||
|
||||
double num, ex;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
if (!validateNumeric(vm, ARG(2), &ex, "Argument 2")) return;
|
||||
RET(VAR_NUM(pow(num, ex)));
|
||||
}
|
||||
|
||||
DEF(stdMathSqrt,
|
||||
"sqrt(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(sqrt(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathAbs,
|
||||
"abs(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
if (num < 0) num = -num;
|
||||
RET(VAR_NUM(num));
|
||||
}
|
||||
|
||||
DEF(stdMathSign,
|
||||
"sign(value:num) -> num\n") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
if (num < 0) num = -1;
|
||||
else if (num > 0) num = +1;
|
||||
else num = 0;
|
||||
RET(VAR_NUM(num));
|
||||
}
|
||||
|
||||
DEF(stdMathHash,
|
||||
"hash(value:var) -> num\n"
|
||||
"Return the hash value of the variable, if it's not hashable it'll "
|
||||
"return null.") {
|
||||
|
||||
if (IS_OBJ(ARG(1))) {
|
||||
if (!isObjectHashable(AS_OBJ(ARG(1))->type)) {
|
||||
RET(VAR_NULL);
|
||||
}
|
||||
}
|
||||
RET(VAR_NUM((double)varHashValue(ARG(1))));
|
||||
}
|
||||
|
||||
DEF(stdMathSine,
|
||||
"sin(rad:num) -> num\n"
|
||||
"Return the sine value of the argument [rad] which is an angle expressed "
|
||||
"in radians.") {
|
||||
|
||||
double rad;
|
||||
if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return;
|
||||
RET(VAR_NUM(sin(rad)));
|
||||
}
|
||||
|
||||
DEF(stdMathCosine,
|
||||
"cos(rad:num) -> num\n"
|
||||
"Return the cosine value of the argument [rad] which is an angle expressed "
|
||||
"in radians.") {
|
||||
|
||||
double rad;
|
||||
if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return;
|
||||
RET(VAR_NUM(cos(rad)));
|
||||
}
|
||||
|
||||
DEF(stdMathTangent,
|
||||
"tan(rad:num) -> num\n"
|
||||
"Return the tangent value of the argument [rad] which is an angle expressed "
|
||||
"in radians.") {
|
||||
|
||||
double rad;
|
||||
if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return;
|
||||
RET(VAR_NUM(tan(rad)));
|
||||
}
|
||||
|
||||
DEF(stdMathSinh,
|
||||
"sinh(val) -> val\n"
|
||||
"Return the hyperbolic sine value of the argument [val].") {
|
||||
|
||||
double val;
|
||||
if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return;
|
||||
RET(VAR_NUM(sinh(val)));
|
||||
}
|
||||
|
||||
DEF(stdMathCosh,
|
||||
"cosh(val) -> val\n"
|
||||
"Return the hyperbolic cosine value of the argument [val].") {
|
||||
|
||||
double val;
|
||||
if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return;
|
||||
RET(VAR_NUM(cosh(val)));
|
||||
}
|
||||
|
||||
DEF(stdMathTanh,
|
||||
"tanh(val) -> val\n"
|
||||
"Return the hyperbolic tangent value of the argument [val].") {
|
||||
|
||||
double val;
|
||||
if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return;
|
||||
RET(VAR_NUM(tanh(val)));
|
||||
}
|
||||
|
||||
DEF(stdMathArcSine,
|
||||
"asin(num) -> num\n"
|
||||
"Return the arcsine value of the argument [num] which is an angle "
|
||||
"expressed in radians.") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
|
||||
if (num < -1 || 1 < num) {
|
||||
RET_ERR(newString(vm, "Argument should be between -1 and +1"));
|
||||
}
|
||||
|
||||
RET(VAR_NUM(asin(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathArcCosine,
|
||||
"acos(num) -> num\n"
|
||||
"Return the arc cosine value of the argument [num] which is "
|
||||
"an angle expressed in radians.") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
|
||||
if (num < -1 || 1 < num) {
|
||||
RET_ERR(newString(vm, "Argument should be between -1 and +1"));
|
||||
}
|
||||
|
||||
RET(VAR_NUM(acos(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathArcTangent,
|
||||
"atan(num) -> num\n"
|
||||
"Return the arc tangent value of the argument [num] which is "
|
||||
"an angle expressed in radians.") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(atan(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathLog10,
|
||||
"log10(value:num) -> num\n"
|
||||
"Return the logarithm to base 10 of argument [value]") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(log10(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathRound,
|
||||
"round(value:num) -> num\n"
|
||||
"Round to nearest integer, away from zero and return the number.") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(round(num)));
|
||||
}
|
||||
|
||||
// 'Fiber' module methods.
|
||||
// -----------------------
|
||||
|
||||
DEF(stdFiberNew,
|
||||
"new(fn:Closure) -> fiber\n"
|
||||
"Create and return a new fiber from the given function [fn].") {
|
||||
|
||||
Closure* closure;
|
||||
if (!validateArgClosure(vm, 1, &closure)) return;
|
||||
RET(VAR_OBJ(newFiber(vm, closure)));
|
||||
}
|
||||
|
||||
DEF(stdFiberRun,
|
||||
"run(fb:Fiber, ...) -> var\n"
|
||||
"Runs the fiber's function with the provided arguments and returns it's "
|
||||
"return value or the yielded value if it's yielded.") {
|
||||
|
||||
int argc = ARGC;
|
||||
if (argc == 0) // Missing the fiber argument.
|
||||
RET_ERR(newString(vm, "Missing argument - fiber."));
|
||||
|
||||
Fiber* fb;
|
||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||
|
||||
// Buffer of argument to call vmPrepareFiber().
|
||||
Var* args[MAX_ARGC];
|
||||
|
||||
// ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc).
|
||||
for (int i = 1; i < argc; i++) {
|
||||
args[i - 1] = &ARG(i + 1);
|
||||
}
|
||||
|
||||
// Switch fiber and start execution.
|
||||
if (vmPrepareFiber(vm, fb, argc - 1, args)) {
|
||||
ASSERT(fb == vm->fiber, OOPS);
|
||||
fb->state = FIBER_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
DEF(stdFiberResume,
|
||||
"resume(fb:Fiber) -> var\n"
|
||||
"Resumes a yielded function from a previous call of fiber_run() function. "
|
||||
"Return it's return value or the yielded value if it's yielded.") {
|
||||
|
||||
int argc = ARGC;
|
||||
if (argc == 0) // Missing the fiber argument.
|
||||
RET_ERR(newString(vm, "Expected at least 1 argument(s)."));
|
||||
if (argc > 2) // Can only accept 1 argument for resume.
|
||||
RET_ERR(newString(vm, "Expected at most 2 argument(s)."));
|
||||
|
||||
Fiber* fb;
|
||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||
|
||||
Var value = (argc == 1) ? VAR_NULL : ARG(2);
|
||||
|
||||
// Switch fiber and resume execution.
|
||||
if (vmSwitchFiber(vm, fb, &value)) {
|
||||
ASSERT(fb == vm->fiber, OOPS);
|
||||
fb->state = FIBER_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
static void initializeCoreModules(PKVM* vm) {
|
||||
#define MODULE_ADD_FN(module, name, fn, argc) \
|
||||
moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn))
|
||||
@ -1180,56 +956,260 @@ static void initializeCoreModules(PKVM* vm) {
|
||||
MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0);
|
||||
#endif
|
||||
|
||||
NEW_MODULE(math, "math");
|
||||
MODULE_ADD_FN(math, "floor", stdMathFloor, 1);
|
||||
MODULE_ADD_FN(math, "ceil", stdMathCeil, 1);
|
||||
MODULE_ADD_FN(math, "pow", stdMathPow, 2);
|
||||
MODULE_ADD_FN(math, "sqrt", stdMathSqrt, 1);
|
||||
MODULE_ADD_FN(math, "abs", stdMathAbs, 1);
|
||||
MODULE_ADD_FN(math, "sign", stdMathSign, 1);
|
||||
MODULE_ADD_FN(math, "hash", stdMathHash, 1);
|
||||
MODULE_ADD_FN(math, "sin", stdMathSine, 1);
|
||||
MODULE_ADD_FN(math, "cos", stdMathCosine, 1);
|
||||
MODULE_ADD_FN(math, "tan", stdMathTangent, 1);
|
||||
MODULE_ADD_FN(math, "sinh", stdMathSinh, 1);
|
||||
MODULE_ADD_FN(math, "cosh", stdMathCosh, 1);
|
||||
MODULE_ADD_FN(math, "tanh", stdMathTanh, 1);
|
||||
MODULE_ADD_FN(math, "asin", stdMathArcSine, 1);
|
||||
MODULE_ADD_FN(math, "acos", stdMathArcCosine, 1);
|
||||
MODULE_ADD_FN(math, "atan", stdMathArcTangent, 1);
|
||||
MODULE_ADD_FN(math, "log10", stdMathLog10, 1);
|
||||
MODULE_ADD_FN(math, "round", stdMathRound, 1);
|
||||
|
||||
// Note that currently it's mutable (since it's a global variable, not
|
||||
// constant and pocketlang doesn't support constant) so the user shouldn't
|
||||
// modify the PI, like in python.
|
||||
moduleAddGlobal(vm, math, "PI", 2, VAR_NUM(M_PI));
|
||||
|
||||
NEW_MODULE(fiber, "Fiber");
|
||||
MODULE_ADD_FN(fiber, "new", stdFiberNew, 1);
|
||||
MODULE_ADD_FN(fiber, "run", stdFiberRun, -1);
|
||||
MODULE_ADD_FN(fiber, "resume", stdFiberResume, -1);
|
||||
|
||||
#undef MODULE_ADD_FN
|
||||
#undef NEW_MODULE
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* BUILTIN CLASS CONSTRUCTORS */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void _ctorNull(PKVM* vm) {
|
||||
RET(VAR_NULL);
|
||||
}
|
||||
|
||||
static void _ctorBool(PKVM* vm) {
|
||||
RET(toBool(ARG(1)));
|
||||
}
|
||||
|
||||
static void _ctorNumber(PKVM* vm) {
|
||||
double value;
|
||||
if (!validateNumeric(vm, ARG(1), &value, "Argument 1")) return;
|
||||
RET(VAR_NUM(value));
|
||||
}
|
||||
|
||||
static void _ctorString(PKVM* vm) {
|
||||
if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return;
|
||||
if (ARGC == 0) {
|
||||
RET(VAR_OBJ(newStringLength(vm, NULL, 0)));
|
||||
return;
|
||||
}
|
||||
RET(VAR_OBJ(toString(vm, ARG(1))));
|
||||
}
|
||||
|
||||
static void _ctorList(PKVM* vm) {
|
||||
List* list = newList(vm, ARGC);
|
||||
vmPushTempRef(vm, &list->_super); // list.
|
||||
for (int i = 0; i < ARGC; i++) {
|
||||
listAppend(vm, list, ARG(i + 1));
|
||||
}
|
||||
vmPopTempRef(vm); // list.
|
||||
RET(VAR_OBJ(list));
|
||||
}
|
||||
|
||||
static void _ctorMap(PKVM* vm) {
|
||||
RET(VAR_OBJ(newMap(vm)));
|
||||
}
|
||||
|
||||
static void _ctorRange(PKVM* vm) {
|
||||
double from, to;
|
||||
if (!validateNumeric(vm, ARG(1), &from, "Argument 1")) return;
|
||||
if (!validateNumeric(vm, ARG(2), &to, "Argument 2")) return;
|
||||
|
||||
RET(VAR_OBJ(newRange(vm, from, to)));
|
||||
}
|
||||
|
||||
static void _ctorFiber(PKVM* vm) {
|
||||
Closure* closure;
|
||||
if (!validateArgClosure(vm, 1, &closure)) return;
|
||||
RET(VAR_OBJ(newFiber(vm, closure)));
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* BUILTIN CLASS METHODS */
|
||||
/*****************************************************************************/
|
||||
|
||||
#define SELF (vm->fiber->self)
|
||||
|
||||
DEF(_listAppend,
|
||||
"List.append(value:var) -> List\n"
|
||||
"Append the [value] to the list and return the list.") {
|
||||
|
||||
ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS);
|
||||
|
||||
listAppend(vm, ((List*)AS_OBJ(SELF)), ARG(1));
|
||||
RET(SELF);
|
||||
}
|
||||
|
||||
DEF(_fiberRun,
|
||||
"Fiber.run(...) -> var\n"
|
||||
"Runs the fiber's function with the provided arguments and returns it's "
|
||||
"return value or the yielded value if it's yielded.") {
|
||||
|
||||
ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS);
|
||||
Fiber* self = (Fiber*)AS_OBJ(SELF);
|
||||
|
||||
// Buffer of argument to call vmPrepareFiber().
|
||||
Var* args[MAX_ARGC];
|
||||
|
||||
for (int i = 0; i < ARGC; i++) {
|
||||
args[i] = &ARG(i + 1);
|
||||
}
|
||||
|
||||
// Switch fiber and start execution.
|
||||
if (vmPrepareFiber(vm, self, ARGC, args)) {
|
||||
self->state = FIBER_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
DEF(_fiberResume,
|
||||
"Fiber.resume() -> var\n"
|
||||
"Resumes a yielded function from a previous call of fiber_run() function. "
|
||||
"Return it's return value or the yielded value if it's yielded.") {
|
||||
|
||||
ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS);
|
||||
Fiber* self = (Fiber*)AS_OBJ(SELF);
|
||||
|
||||
if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return;
|
||||
|
||||
Var value = (ARGC == 1) ? ARG(1) : VAR_NULL;
|
||||
|
||||
// Switch fiber and resume execution.
|
||||
if (vmSwitchFiber(vm, self, &value)) {
|
||||
self->state = FIBER_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
#undef SELF
|
||||
|
||||
/*****************************************************************************/
|
||||
/* BUILTIN CLASS INITIALIZATION */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void initializePrimitiveClasses(PKVM* vm) {
|
||||
for (int i = 0; i < PK_INSTANCE; i++) {
|
||||
Class* super = NULL;
|
||||
if (i != 0) super = vm->builtin_classes[PK_OBJECT];
|
||||
const char* name = getPkVarTypeName((PkVarType)i);
|
||||
Class* cls = newClass(vm, name, (int)strlen(name),
|
||||
super, NULL, NULL, NULL);
|
||||
vm->builtin_classes[i] = cls;
|
||||
cls->class_of = (PkVarType)i;
|
||||
}
|
||||
|
||||
#define ADD_CTOR(type, name, ptr, arity_) \
|
||||
do { \
|
||||
Function* fn = newFunction(vm, name, (int)strlen(name), \
|
||||
NULL, true, NULL, NULL); \
|
||||
fn->native = ptr; \
|
||||
fn->arity = arity_; \
|
||||
vmPushTempRef(vm, &fn->_super); /* fn. */ \
|
||||
vm->builtin_classes[type]->ctor = newClosure(vm, fn); \
|
||||
vmPopTempRef(vm); /* fn. */ \
|
||||
} while (false)
|
||||
|
||||
ADD_CTOR(PK_NULL, "@ctorNull", _ctorNull, 0);
|
||||
ADD_CTOR(PK_BOOL, "@ctorBool", _ctorBool, 1);
|
||||
ADD_CTOR(PK_NUMBER, "@ctorNumber", _ctorNumber, 1);
|
||||
ADD_CTOR(PK_STRING, "@ctorString", _ctorString, -1);
|
||||
ADD_CTOR(PK_LIST, "@ctorList", _ctorList, -1);
|
||||
ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0);
|
||||
ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1);
|
||||
|
||||
#undef ADD_CTOR
|
||||
|
||||
#define ADD_METHOD(type, name, ptr, arity_) \
|
||||
do { \
|
||||
Function* fn = newFunction(vm, name, (int)strlen(name), \
|
||||
NULL, true, DOCSTRING(ptr), NULL); \
|
||||
fn->native = ptr; \
|
||||
fn->arity = arity_; \
|
||||
vmPushTempRef(vm, &fn->_super); /* fn. */ \
|
||||
pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \
|
||||
vm, newClosure(vm, fn)); \
|
||||
vmPopTempRef(vm); /* fn. */ \
|
||||
} while (false)
|
||||
|
||||
ADD_METHOD(PK_LIST, "append", _listAppend, 1);
|
||||
ADD_METHOD(PK_FIBER, "run", _fiberRun, -1);
|
||||
ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1);
|
||||
|
||||
#undef ADD_METHOD
|
||||
}
|
||||
|
||||
#undef IS_NUM_BYTE
|
||||
#undef DOCSTRING
|
||||
#undef DEF
|
||||
|
||||
/*****************************************************************************/
|
||||
/* PRIMITIVE TYPES CLASS */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void initializePrimitiveClasses(PKVM* vm) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* OPERATORS */
|
||||
/*****************************************************************************/
|
||||
|
||||
Var preConstructSelf(PKVM* vm, Class* cls) {
|
||||
|
||||
#define NO_INSTANCE(type_name) \
|
||||
VM_SET_ERROR(vm, newString(vm, \
|
||||
"Class '" type_name "' cannot be instanciated."))
|
||||
|
||||
switch (cls->class_of) {
|
||||
case PK_OBJECT:
|
||||
NO_INSTANCE("Object");
|
||||
return VAR_NULL;
|
||||
|
||||
case PK_NULL:
|
||||
case PK_BOOL:
|
||||
case PK_NUMBER:
|
||||
case PK_STRING:
|
||||
case PK_LIST:
|
||||
case PK_MAP:
|
||||
case PK_RANGE:
|
||||
return VAR_NULL; // Constructor will override the null.
|
||||
|
||||
case PK_MODULE:
|
||||
NO_INSTANCE("Module");
|
||||
return VAR_NULL;
|
||||
|
||||
case PK_CLOSURE:
|
||||
NO_INSTANCE("Closure");
|
||||
return VAR_NULL;
|
||||
|
||||
case PK_FIBER:
|
||||
return VAR_NULL;
|
||||
|
||||
case PK_CLASS:
|
||||
NO_INSTANCE("Class");
|
||||
return VAR_NULL;
|
||||
|
||||
case PK_INSTANCE:
|
||||
return VAR_OBJ(newInstance(vm, cls));
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Class* getClass(PKVM* vm, Var instance) {
|
||||
PkVarType type = getVarType(instance);
|
||||
if (0 <= type && type < PK_INSTANCE) {
|
||||
return vm->builtin_classes[type];
|
||||
}
|
||||
ASSERT(IS_OBJ_TYPE(instance, OBJ_INST), OOPS);
|
||||
Instance* inst = (Instance*)AS_OBJ(instance);
|
||||
return inst->cls;
|
||||
}
|
||||
|
||||
Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) {
|
||||
|
||||
Class* cls = getClass(vm, self);
|
||||
ASSERT(cls != NULL, OOPS);
|
||||
|
||||
Class* cls_ = cls;
|
||||
do {
|
||||
for (int i = 0; i < (int)cls_->methods.count; i++) {
|
||||
Closure* method = cls_->methods.data[i];
|
||||
if (IS_CSTR_EQ(name, method->fn->name, name->length)) {
|
||||
if (is_method) *is_method = true;
|
||||
return VAR_OBJ(method);
|
||||
}
|
||||
}
|
||||
cls_ = cls_->super_class;
|
||||
} while (cls_ != NULL);
|
||||
|
||||
// If the attribute not found it'll set an error.
|
||||
if (is_method) *is_method = false;
|
||||
return varGetAttrib(vm, self, name);
|
||||
}
|
||||
|
||||
#define UNSUPPORTED_OPERAND_TYPES(op) \
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Unsupported operand types for " \
|
||||
"operator '" op "' $ and $", varTypeName(v1), varTypeName(v2)))
|
||||
|
@ -17,6 +17,24 @@ void initializeCore(PKVM* vm);
|
||||
/* OPERATORS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// This method is called just before constructing a type to initialize self
|
||||
// and after that the constructor will be called. For builtin types this
|
||||
// function will return VAR_NULL and the constructor will override self to
|
||||
// it's instance (because for some classes we cannot create without argument
|
||||
// example Fiber(fn), Range(from, to) etc). If the class cannot be
|
||||
// instanciated (ex: Class 'Module') it'll set an error and return VAR_NULL.
|
||||
// For other classes the return value will be an Instance.
|
||||
Var preConstructSelf(PKVM* vm, Class* cls);
|
||||
|
||||
// Returns the class of the [instance].
|
||||
Class* getClass(PKVM* vm, Var instance);
|
||||
|
||||
// Returns the method (closure) in the instance [self]. If it's not an method
|
||||
// but just an attribute the [is_method] pointer will be set to false and
|
||||
// returns the value.
|
||||
// If the method / attribute not found, it'll set a runtime error on the VM.
|
||||
Var getMethod(PKVM* vm, Var self, String* name, bool* is_method);
|
||||
|
||||
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.
|
||||
|
@ -126,25 +126,16 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
NO_ARGS();
|
||||
break;
|
||||
|
||||
case OP_PUSH_LIST: SHORT_ARG(); break;
|
||||
case OP_PUSH_INSTANCE:
|
||||
{
|
||||
int cls_index = READ_SHORT();
|
||||
ASSERT_INDEX((uint32_t)cls_index, func->owner->constants.count);
|
||||
Var constant = func->owner->constants.data[cls_index];
|
||||
ASSERT(IS_OBJ_TYPE(constant, OBJ_CLASS), OOPS);
|
||||
|
||||
// Prints: %5d [Class:%s]\n
|
||||
PRINT_INT(cls_index);
|
||||
PRINT(" [Class:");
|
||||
PRINT(func->owner->name->data);
|
||||
PRINT("]\n");
|
||||
case OP_PUSH_LIST:
|
||||
SHORT_ARG();
|
||||
break;
|
||||
|
||||
case OP_PUSH_MAP:
|
||||
case OP_PUSH_SELF:
|
||||
case OP_LIST_APPEND:
|
||||
case OP_MAP_INSERT:
|
||||
NO_ARGS();
|
||||
break;
|
||||
}
|
||||
case OP_PUSH_MAP: NO_ARGS(); break;
|
||||
case OP_LIST_APPEND: NO_ARGS(); break;
|
||||
case OP_MAP_INSERT: NO_ARGS(); break;
|
||||
case OP_INST_APPEND: NO_ARGS(); break;
|
||||
|
||||
case OP_PUSH_LOCAL_0:
|
||||
case OP_PUSH_LOCAL_1:
|
||||
@ -236,6 +227,19 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_PUSH_BUILTIN_TY:
|
||||
{
|
||||
int index = READ_BYTE();
|
||||
ASSERT_INDEX(index, PK_INSTANCE);
|
||||
const char* name = vm->builtin_classes[index]->name->data;
|
||||
// Prints: %5d [Fn:%s]\n
|
||||
PRINT_INT(index);
|
||||
PRINT(" [Class:");
|
||||
PRINT(name);
|
||||
PRINT("]\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_PUSH_UPVALUE:
|
||||
case OP_STORE_UPVALUE:
|
||||
{
|
||||
@ -278,6 +282,24 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_METHOD_CALL:
|
||||
{
|
||||
int argc = READ_BYTE();
|
||||
int index = READ_SHORT();
|
||||
String* name = moduleGetStringAt(func->owner, index);
|
||||
ASSERT(name != NULL, OOPS);
|
||||
|
||||
// Prints: %5d (argc) %d '%s'\n
|
||||
PRINT_INT(argc);
|
||||
PRINT(" (argc) ");
|
||||
|
||||
_PRINT_INT(index, 0);
|
||||
PRINT(" '");
|
||||
PRINT(name->data);
|
||||
PRINT("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_CALL:
|
||||
// Prints: %5d (argc)\n
|
||||
PRINT_INT(READ_BYTE());
|
||||
|
@ -40,9 +40,8 @@ OPCODE(PUSH_LIST, 2, 1)
|
||||
// Push a new map to construct from literal.
|
||||
OPCODE(PUSH_MAP, 0, 1)
|
||||
|
||||
// Push a new instance to the stack.
|
||||
// param: 1 byte index.
|
||||
OPCODE(PUSH_INSTANCE, 1, 1)
|
||||
// Push the self of the current method on the stack.
|
||||
OPCODE(PUSH_SELF, 0, 1)
|
||||
|
||||
// Pop the value on the stack the next stack top would be a list. Append the
|
||||
// value to the list. Used in literal array construction.
|
||||
@ -52,10 +51,6 @@ OPCODE(LIST_APPEND, 0, -1)
|
||||
// Insert the key value pairs to the map. Used in literal map construction.
|
||||
OPCODE(MAP_INSERT, 0, -2)
|
||||
|
||||
// Pop the value on the stack, the next stack top would be an instance. Append
|
||||
// the value to the instance. Used in instance construction.
|
||||
OPCODE(INST_APPEND, 0, -1)
|
||||
|
||||
// Push stack local on top of the stack. Locals at 0 to 8 marked explicitly
|
||||
// since it's performance critical.
|
||||
// params: PUSH_LOCAL_N -> 1 byte count value.
|
||||
@ -97,6 +92,10 @@ OPCODE(STORE_GLOBAL, 1, 0)
|
||||
// params: 1 bytes index.
|
||||
OPCODE(PUSH_BUILTIN_FN, 1, 1)
|
||||
|
||||
// Push a built in class.
|
||||
// params: 1 bytes index.
|
||||
OPCODE(PUSH_BUILTIN_TY, 1, 1)
|
||||
|
||||
// Push an upvalue of the current closure at the index which is the first one
|
||||
// byte argument.
|
||||
// params: 1 byte index.
|
||||
@ -124,6 +123,11 @@ OPCODE(POP, 0, -1)
|
||||
// params: 2 byte name index.
|
||||
OPCODE(IMPORT, 2, 1)
|
||||
|
||||
// Call a method on the variable at the stack top. See opcode CALL for detail.
|
||||
// params: 2 bytes method name index in the constant pool.
|
||||
// 1 byte argc.
|
||||
OPCODE(METHOD_CALL, 3, -0) //< Stack size will be calculated at compile time.
|
||||
|
||||
// Calls a function using stack's top N values as the arguments and once it
|
||||
// done the stack top should be stored otherwise it'll be disregarded. The
|
||||
// function should set the 0 th argment to return value.
|
||||
|
338
src/pk_value.c
338
src/pk_value.c
@ -75,14 +75,6 @@ PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id) {
|
||||
Instance* inst = newInstanceNative(vm, data, id);
|
||||
vmPushTempRef(vm, &inst->_super); // inst
|
||||
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(inst));
|
||||
vmPopTempRef(vm); // inst
|
||||
return handle;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VAR INTERNALS */
|
||||
/*****************************************************************************/
|
||||
@ -102,6 +94,7 @@ DEFINE_BUFFER(Uint, uint32_t)
|
||||
DEFINE_BUFFER(Byte, uint8_t)
|
||||
DEFINE_BUFFER(Var, Var)
|
||||
DEFINE_BUFFER(String, String*)
|
||||
DEFINE_BUFFER(Closure, Closure*)
|
||||
|
||||
void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str,
|
||||
uint32_t length) {
|
||||
@ -154,6 +147,13 @@ void markStringBuffer(PKVM* vm, pkStringBuffer* self) {
|
||||
}
|
||||
}
|
||||
|
||||
void markClosureBuffer(PKVM* vm, pkClosureBuffer* self) {
|
||||
if (self == NULL) return;
|
||||
for (uint32_t i = 0; i < self->count; i++) {
|
||||
markObject(vm, &self->data[i]->_super);
|
||||
}
|
||||
}
|
||||
|
||||
static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
|
||||
// TODO: trace here.
|
||||
|
||||
@ -263,6 +263,7 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
|
||||
// Mark call frames.
|
||||
for (int i = 0; i < fiber->frame_count; i++) {
|
||||
markObject(vm, (Object*)&fiber->frames[i].closure->_super);
|
||||
markValue(vm, fiber->frames[i].self);
|
||||
}
|
||||
vm->bytes_allocated += sizeof(CallFrame) * fiber->frame_capacity;
|
||||
|
||||
@ -278,17 +279,18 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
|
||||
markObject(vm, &cls->owner->_super);
|
||||
markObject(vm, &cls->ctor->_super);
|
||||
markObject(vm, &cls->name->_super);
|
||||
vm->bytes_allocated += sizeof(uint32_t) * cls->field_names.capacity;
|
||||
|
||||
markClosureBuffer(vm, &cls->methods);
|
||||
vm->bytes_allocated += sizeof(Closure) * cls->methods.capacity;
|
||||
|
||||
} break;
|
||||
|
||||
case OBJ_INST:
|
||||
{
|
||||
Instance* inst = (Instance*)obj;
|
||||
if (!inst->is_native) {
|
||||
Inst* ins = inst->ins;
|
||||
vm->bytes_allocated += sizeof(Inst);
|
||||
vm->bytes_allocated += sizeof(Var*) * ins->fields.capacity;
|
||||
}
|
||||
markObject(vm, &inst->attribs->_super);
|
||||
markObject(vm, &inst->cls->_super);
|
||||
vm->bytes_allocated += sizeof(Instance);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@ -493,6 +495,7 @@ Fiber* newFiber(PKVM* vm, Closure* closure) {
|
||||
}
|
||||
|
||||
fiber->open_upvalues = NULL;
|
||||
fiber->self = VAR_UNDEFINED;
|
||||
|
||||
// Initialize the return value to null (doesn't really have to do that here
|
||||
// but if we're trying to debut it may crash when dumping the return value).
|
||||
@ -501,80 +504,58 @@ Fiber* newFiber(PKVM* vm, Closure* closure) {
|
||||
return fiber;
|
||||
}
|
||||
|
||||
Class* newClass(PKVM* vm, Module* module, const char* name, uint32_t length,
|
||||
int* cls_index, int* ctor_index) {
|
||||
Class* newClass(PKVM* vm, const char* name, int length,
|
||||
Class* super, Module* module,
|
||||
const char* docstring, int* cls_index) {
|
||||
|
||||
Class* cls = ALLOCATE(vm, Class);
|
||||
varInitObject(&cls->_super, vm, OBJ_CLASS);
|
||||
|
||||
vmPushTempRef(vm, &cls->_super); // class.
|
||||
|
||||
uint32_t _cls_index = moduleAddConstant(vm, module, VAR_OBJ(cls));
|
||||
if (cls_index) *cls_index = (int)_cls_index;
|
||||
pkClosureBufferInit(&cls->methods);
|
||||
|
||||
pkUintBufferInit(&cls->field_names);
|
||||
cls->owner = module;
|
||||
cls->class_of = PK_INSTANCE;
|
||||
cls->owner = NULL;
|
||||
cls->super_class = super;
|
||||
cls->docstring = NULL;
|
||||
cls->name = moduleAddString(module, vm, name, length, NULL);
|
||||
cls->ctor = NULL;
|
||||
cls->new_fn = NULL;
|
||||
cls->delete_fn = NULL;
|
||||
|
||||
// Since characters '@' and '$' are special in stringFormat, and they
|
||||
// currently cannot be escaped (TODO), a string (char array) created
|
||||
// for that character and passed as C string format.
|
||||
char special[2] = { SPECIAL_NAME_CHAR, '\0' };
|
||||
String* ctor_name = stringFormat(vm, "$(Ctor:@)", special, cls->name);
|
||||
|
||||
// Constructor.
|
||||
vmPushTempRef(vm, &ctor_name->_super); // ctor_name.
|
||||
{
|
||||
Function* ctor_fn = newFunction(vm, ctor_name->data, ctor_name->length,
|
||||
module, false, NULL, ctor_index);
|
||||
vmPushTempRef(vm, &ctor_fn->_super); // ctor_fn.
|
||||
cls->ctor = newClosure(vm, ctor_fn);
|
||||
vmPopTempRef(vm); // ctor_fn.
|
||||
// Builtin types doesn't belongs to a module.
|
||||
if (module != NULL) {
|
||||
cls->name = moduleAddString(module, vm, name, length, NULL);
|
||||
int _cls_index = moduleAddConstant(vm, module, VAR_OBJ(cls));
|
||||
if (cls_index) *cls_index = _cls_index;
|
||||
moduleAddGlobal(vm, module, name, length, VAR_OBJ(cls));
|
||||
} else {
|
||||
cls->name = newStringLength(vm, name, (uint32_t)length);
|
||||
}
|
||||
vmPopTempRef(vm); // ctor_name.
|
||||
|
||||
vmPopTempRef(vm); // class.
|
||||
return cls;
|
||||
}
|
||||
|
||||
Instance* newInstance(PKVM* vm, Class* cls, bool initialize) {
|
||||
Instance* newInstance(PKVM* vm, Class* cls) {
|
||||
|
||||
ASSERT(cls->class_of == PK_INSTANCE, "Cannot create an instace of builtin "
|
||||
"class with newInstance() function.");
|
||||
|
||||
Instance* inst = ALLOCATE(vm, Instance);
|
||||
varInitObject(&inst->_super, vm, OBJ_INST);
|
||||
|
||||
vmPushTempRef(vm, &inst->_super); // inst.
|
||||
|
||||
inst->ty_name = cls->name->data;
|
||||
inst->is_native = false;
|
||||
inst->cls = cls;
|
||||
inst->attribs = newMap(vm);
|
||||
|
||||
Inst* ins = ALLOCATE(vm, Inst);
|
||||
inst->ins = ins;
|
||||
ins->type = cls;
|
||||
pkVarBufferInit(&ins->fields);
|
||||
|
||||
if (initialize && cls->field_names.count != 0) {
|
||||
pkVarBufferFill(&ins->fields, vm, VAR_NULL, cls->field_names.count);
|
||||
if (cls->new_fn != NULL) {
|
||||
inst->native = cls->new_fn();
|
||||
} else {
|
||||
inst->native = NULL;
|
||||
}
|
||||
|
||||
vmPopTempRef(vm); // inst.
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id) {
|
||||
Instance* inst = ALLOCATE(vm, Instance);
|
||||
varInitObject(&inst->_super, vm, OBJ_INST);
|
||||
inst->is_native = true;
|
||||
inst->native_id = id;
|
||||
|
||||
if (vm->config.inst_name_fn != NULL) {
|
||||
inst->ty_name = vm->config.inst_name_fn(id);
|
||||
} else {
|
||||
inst->ty_name = "$(?)";
|
||||
}
|
||||
|
||||
inst->native = data;
|
||||
return inst;
|
||||
}
|
||||
|
||||
@ -798,7 +779,8 @@ List* listJoin(PKVM* vm, List* l1, List* l2) {
|
||||
return list;
|
||||
}
|
||||
|
||||
// Return a hash value for the object.
|
||||
// Return a hash value for the object. Only String and Range objects can be
|
||||
// hashable.
|
||||
static uint32_t _hashObject(Object* obj) {
|
||||
|
||||
ASSERT(isObjectHashable(obj->type),
|
||||
@ -809,28 +791,10 @@ static uint32_t _hashObject(Object* obj) {
|
||||
case OBJ_STRING:
|
||||
return ((String*)obj)->hash;
|
||||
|
||||
case OBJ_LIST:
|
||||
case OBJ_MAP:
|
||||
goto L_unhashable;
|
||||
|
||||
case OBJ_RANGE:
|
||||
{
|
||||
case OBJ_RANGE: {
|
||||
Range* range = (Range*)obj;
|
||||
return utilHashNumber(range->from) ^ utilHashNumber(range->to);
|
||||
}
|
||||
|
||||
case OBJ_MODULE:
|
||||
case OBJ_FUNC:
|
||||
case OBJ_FIBER:
|
||||
case OBJ_CLASS:
|
||||
case OBJ_INST:
|
||||
TODO;
|
||||
UNREACHABLE();
|
||||
|
||||
default:
|
||||
L_unhashable:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
@ -1067,27 +1031,15 @@ void freeObject(PKVM* vm, Object* self) {
|
||||
|
||||
case OBJ_CLASS: {
|
||||
Class* cls = (Class*)self;
|
||||
pkUintBufferClear(&cls->field_names, vm);
|
||||
pkClosureBufferClear(&cls->methods, vm);
|
||||
} break;
|
||||
|
||||
case OBJ_INST:
|
||||
{
|
||||
case OBJ_INST: {
|
||||
Instance* inst = (Instance*)self;
|
||||
|
||||
if (inst->is_native) {
|
||||
if (vm->config.inst_free_fn != NULL) {
|
||||
// TODO: Allow user to set error when freeing the object.
|
||||
vm->config.inst_free_fn(vm, inst->native, inst->native_id);
|
||||
}
|
||||
|
||||
} else {
|
||||
Inst* ins = inst->ins;
|
||||
pkVarBufferClear(&ins->fields, vm);
|
||||
DEALLOCATE(vm, ins);
|
||||
if (inst->cls->delete_fn != NULL) {
|
||||
inst->cls->delete_fn(inst->native);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
DEALLOCATE(vm, self);
|
||||
@ -1194,129 +1146,35 @@ void moduleAddMain(PKVM* vm, Module* module) {
|
||||
}
|
||||
|
||||
bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) {
|
||||
ASSERT(inst != NULL, OOPS);
|
||||
ASSERT(attrib != NULL, OOPS);
|
||||
ASSERT(value != NULL, OOPS);
|
||||
ASSERT((inst != NULL) && (attrib != NULL) && (value != NULL), OOPS);
|
||||
|
||||
// This function should only be called at runtime.
|
||||
ASSERT(vm->fiber != NULL, OOPS);
|
||||
|
||||
if (inst->is_native) {
|
||||
|
||||
if (vm->config.inst_get_attrib_fn) {
|
||||
// Temproarly change the fiber's "return address" to points to the
|
||||
// below var 'val' so that the users can use 'pkReturn...()' function
|
||||
// to return the attribute as well.
|
||||
Var* temp = vm->fiber->ret;
|
||||
Var val = VAR_UNDEFINED;
|
||||
|
||||
vm->fiber->ret = &val;
|
||||
PkStringPtr attr = { attrib->data, NULL, NULL,
|
||||
attrib->length, attrib->hash };
|
||||
vm->config.inst_get_attrib_fn(vm, inst->native, inst->native_id, attr);
|
||||
vm->fiber->ret = temp;
|
||||
|
||||
if (IS_UNDEF(val)) {
|
||||
|
||||
// FIXME: add a list of attribute overrides.
|
||||
if ((CHECK_HASH("as_string", 0xbdef4147) == attrib->hash) &&
|
||||
IS_CSTR_EQ(attrib, "as_string", 9)) {
|
||||
*value = VAR_OBJ(toRepr(vm, VAR_OBJ(inst)));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we reached here, the native instance don't have the attribute
|
||||
// and no overriden attributes found, return false to indicate that the
|
||||
// attribute doesn't exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attribute [val] updated by the hosting application.
|
||||
*value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the hosting application doesn't provided a getter function, we treat
|
||||
// it as if the instance don't has the attribute.
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Optimize this with binary search.
|
||||
Class* cls = inst->ins->type;
|
||||
for (uint32_t i = 0; i < cls->field_names.count; i++) {
|
||||
ASSERT_INDEX(i, cls->field_names.count);
|
||||
String* f_name = moduleGetStringAt(cls->owner, cls->field_names.data[i]);
|
||||
ASSERT(f_name != NULL, OOPS);
|
||||
if (IS_STR_EQ(f_name, attrib)) {
|
||||
*value = inst->ins->fields.data[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find the attribute in it's type class, return false.
|
||||
return false;
|
||||
if (inst->native != NULL) {
|
||||
TODO;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
Var value_ = mapGet(inst->attribs, VAR_OBJ(attrib));
|
||||
if (IS_UNDEF(value_)) return false;
|
||||
|
||||
*value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) {
|
||||
ASSERT((inst != NULL) && (attrib != NULL), OOPS);
|
||||
|
||||
if (inst->is_native) {
|
||||
if (inst->native != NULL) {
|
||||
// Try setting the attribute from the native interface, and if success, we
|
||||
// should return. otherwise the code will "fall through" and set on it's
|
||||
// dynamic attributes map.
|
||||
TODO;
|
||||
|
||||
if (vm->config.inst_set_attrib_fn) {
|
||||
// Temproarly change the fiber's "return address" to points to the
|
||||
// below var 'attrib_ptr' so that the users can use 'pkGetArg...()'
|
||||
// function to validate and get the attribute (users should use 0 as the
|
||||
// index of the argument since it's at the return address and we cannot
|
||||
// ensure fiber->ret[1] will be in bounds).
|
||||
Var* temp = vm->fiber->ret;
|
||||
Var attrib_ptr = value;
|
||||
|
||||
vm->fiber->ret = &attrib_ptr;
|
||||
PkStringPtr attr = { attrib->data, NULL, NULL,
|
||||
attrib->length, attrib->hash };
|
||||
bool exists = vm->config.inst_set_attrib_fn(vm, inst->native,
|
||||
inst->native_id, attr);
|
||||
vm->fiber->ret = temp;
|
||||
|
||||
// If the type is incompatible there'll be an error by now, return false
|
||||
// and the user of this function has to check VM_HAS_ERROR() as well.
|
||||
if (VM_HAS_ERROR(vm)) return false;
|
||||
|
||||
// If the attribute exists on the native type, the host application would
|
||||
// returned true by now, return it.
|
||||
return exists;
|
||||
}
|
||||
|
||||
// If the host application doesn't provided a setter we treat it as it
|
||||
// doesn't has the attribute.
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Optimize this with binary search.
|
||||
Class* ty = inst->ins->type;
|
||||
for (uint32_t i = 0; i < ty->field_names.count; i++) {
|
||||
ASSERT_INDEX(i, ty->field_names.count);
|
||||
String* f_name = moduleGetStringAt(ty->owner, ty->field_names.data[i]);
|
||||
ASSERT(f_name != NULL, OOPS);
|
||||
if (f_name->hash == attrib->hash &&
|
||||
f_name->length == attrib->length &&
|
||||
memcmp(f_name->data, attrib->data, attrib->length) == 0) {
|
||||
inst->ins->fields.data[i] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find the attribute in it's type class, return false.
|
||||
return false;
|
||||
// FIXME:
|
||||
// Only return true if attribute have been set.
|
||||
return true;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
mapSet(vm, inst->attribs, VAR_OBJ(attrib), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -1407,6 +1265,16 @@ const char* varTypeName(Var v) {
|
||||
return getObjectTypeName(obj->type);
|
||||
}
|
||||
|
||||
PkVarType getVarType(Var v) {
|
||||
if (IS_NULL(v)) return PK_NULL;
|
||||
if (IS_BOOL(v)) return PK_BOOL;
|
||||
if (IS_NUM(v)) return PK_NUMBER;
|
||||
|
||||
ASSERT(IS_OBJ(v), OOPS);
|
||||
Object* obj = AS_OBJ(v);
|
||||
return getObjPkVarType(obj->type);
|
||||
}
|
||||
|
||||
bool isValuesSame(Var v1, Var v2) {
|
||||
#if VAR_NAN_TAGGING
|
||||
// Bit representation of each values are unique so just compare the bits.
|
||||
@ -1461,8 +1329,8 @@ bool isValuesEqual(Var v1, Var v2) {
|
||||
}
|
||||
|
||||
bool isObjectHashable(ObjectType type) {
|
||||
// Only list and map are un-hashable.
|
||||
return type != OBJ_LIST && type != OBJ_MAP;
|
||||
// Only String and Range are hashable (since they're immutable).
|
||||
return type == OBJ_STRING || type == OBJ_RANGE;
|
||||
}
|
||||
|
||||
// This will prevent recursive list/map from crash when calling to_string, by
|
||||
@ -1702,35 +1570,17 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
|
||||
{
|
||||
const Instance* inst = (const Instance*)obj;
|
||||
pkByteBufferWrite(buff, vm, '[');
|
||||
pkByteBufferAddString(buff, vm, inst->ty_name,
|
||||
(uint32_t)strlen(inst->ty_name));
|
||||
pkByteBufferWrite(buff, vm, ':');
|
||||
|
||||
if (!inst->is_native) {
|
||||
const Class* cls = inst->ins->type;
|
||||
const Inst* ins = inst->ins;
|
||||
ASSERT(ins->fields.count == cls->field_names.count, OOPS);
|
||||
|
||||
for (uint32_t i = 0; i < cls->field_names.count; i++) {
|
||||
if (i != 0) pkByteBufferWrite(buff, vm, ',');
|
||||
|
||||
pkByteBufferWrite(buff, vm, ' ');
|
||||
String* f_name = moduleGetStringAt(cls->owner,
|
||||
cls->field_names.data[i]);
|
||||
pkByteBufferAddString(buff, vm, f_name->data, f_name->length);
|
||||
pkByteBufferWrite(buff, vm, '=');
|
||||
_toStringInternal(vm, ins->fields.data[i], buff, outer, repr);
|
||||
}
|
||||
} else {
|
||||
|
||||
char buff_addr[STR_HEX_BUFF_SIZE];
|
||||
char* ptr = (char*)buff_addr;
|
||||
(*ptr++) = '0'; (*ptr++) = 'x';
|
||||
const int len = snprintf(ptr, sizeof(buff_addr) - 2,
|
||||
"%08x", (unsigned int)(uintptr_t)inst->native);
|
||||
pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len);
|
||||
}
|
||||
pkByteBufferWrite(buff, vm, '\'');
|
||||
pkByteBufferAddString(buff, vm, inst->cls->name->data,
|
||||
inst->cls->name->length);
|
||||
pkByteBufferAddString(buff, vm, "' instance at ", 14);
|
||||
|
||||
char buff_addr[STR_HEX_BUFF_SIZE];
|
||||
char* ptr = (char*)buff_addr;
|
||||
(*ptr++) = '0'; (*ptr++) = 'x';
|
||||
const int len = snprintf(ptr, sizeof(buff_addr) - 2,
|
||||
"%08x", (unsigned int)(uintptr_t)inst);
|
||||
pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len);
|
||||
pkByteBufferWrite(buff, vm, ']');
|
||||
return;
|
||||
}
|
||||
|
@ -200,6 +200,7 @@ DECLARE_BUFFER(Uint, uint32_t)
|
||||
DECLARE_BUFFER(Byte, uint8_t)
|
||||
DECLARE_BUFFER(Var, Var)
|
||||
DECLARE_BUFFER(String, String*)
|
||||
DECLARE_BUFFER(Closure, Closure*)
|
||||
|
||||
// Add all the characters to the buffer, byte buffer can also be used as a
|
||||
// buffer to write string (like a string stream). Note that this will not
|
||||
@ -428,6 +429,7 @@ typedef struct {
|
||||
const uint8_t* ip; //< Pointer to the next instruction byte code.
|
||||
const Closure* closure; //< Closure of the frame.
|
||||
Var* rbp; //< Stack base pointer. (%rbp)
|
||||
Var self; //< Self reference of the current method.
|
||||
} CallFrame;
|
||||
|
||||
typedef enum {
|
||||
@ -464,6 +466,13 @@ struct Fiber {
|
||||
// overflowed.
|
||||
Var* ret;
|
||||
|
||||
// The self pointer to of the current method. It'll be updated before
|
||||
// calling a native method. (Because native methods doesn't have a call
|
||||
// frame we're doing it this way). Also updated just before calling a
|
||||
// script method, and will be captured by the next allocated callframe
|
||||
// and reset to VAR_UNDEFINED.
|
||||
Var self;
|
||||
|
||||
// Heap allocated array of call frames will grow as needed.
|
||||
CallFrame* frames;
|
||||
int frame_capacity; //< Capacity of the frames array.
|
||||
@ -479,6 +488,9 @@ struct Fiber {
|
||||
struct Class {
|
||||
Object _super;
|
||||
|
||||
// The base class of this class.
|
||||
Class* super_class;
|
||||
|
||||
// The module that owns this class.
|
||||
Module* owner;
|
||||
|
||||
@ -489,9 +501,21 @@ struct Class {
|
||||
// entry in it's owner module's constant pool.
|
||||
const char* docstring;
|
||||
|
||||
// For builtin type it'll be it's enum (ex: PK_STRING, PK_NUMBER, ...) for
|
||||
// every other classes it'll be PK_INSTANCE to indicate that it's not a
|
||||
// builtin type's class.
|
||||
PkVarType class_of;
|
||||
|
||||
Closure* ctor; //< The constructor function.
|
||||
pkUintBuffer field_names; //< Buffer of field names.
|
||||
// TODO: ordered names buffer for binary search.
|
||||
|
||||
// A buffer of methods of the class.
|
||||
pkClosureBuffer methods;
|
||||
|
||||
// Allocater and de-allocator functions for native types.
|
||||
// For script/ builtin types it'll be NULL.
|
||||
pkNewInstanceFn new_fn;
|
||||
pkDeleteInstanceFn delete_fn;
|
||||
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@ -502,15 +526,17 @@ typedef struct {
|
||||
struct Instance {
|
||||
Object _super;
|
||||
|
||||
const char* ty_name; //< Name of the type it belongs to.
|
||||
Class* cls; //< Class of the instance.
|
||||
|
||||
bool is_native; //< True if it's a native type instance.
|
||||
uint32_t native_id; //< Unique ID of this native instance.
|
||||
// If the instance is native, the [native] pointer points to the user data
|
||||
// (generally a heap allocated struct of that type) that contains it's
|
||||
// attributes. We'll use it to access an attribute first with setters and
|
||||
// getters and if the attribute not exists we'll continue search in the
|
||||
// bellow attribs map.
|
||||
void* native;
|
||||
|
||||
union {
|
||||
void* native; //< C struct pointer. // TODO:
|
||||
Inst* ins; //< Module instance pointer.
|
||||
};
|
||||
// Dynamic attributes of an instance.
|
||||
Map* attribs;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -544,6 +570,12 @@ Range* newRange(PKVM* vm, double from, double to);
|
||||
|
||||
Module* newModule(PKVM* vm);
|
||||
|
||||
Closure* newClosure(PKVM* vm, Function* fn);
|
||||
|
||||
Upvalue* newUpvalue(PKVM* vm, Var* value);
|
||||
|
||||
Fiber* newFiber(PKVM* vm, Closure* closure);
|
||||
|
||||
// FIXME:
|
||||
// The docstring should be allocated and stored in the module's constants
|
||||
// as a string if it's not a native function. (native function's docs are
|
||||
@ -555,29 +587,15 @@ Function* newFunction(PKVM* vm, const char* name, int length,
|
||||
bool is_native, const char* docstring,
|
||||
int* fn_index);
|
||||
|
||||
Closure* newClosure(PKVM* vm, Function* fn);
|
||||
// If the module is not NULL, the name and the class object will be added to
|
||||
// the module's constant pool. The class will be added to the modules global
|
||||
// as well.
|
||||
Class* newClass(PKVM* vm, const char* name, int length,
|
||||
Class* super, Module* module,
|
||||
const char* docstring, int* cls_index);
|
||||
|
||||
Upvalue* newUpvalue(PKVM* vm, Var* value);
|
||||
|
||||
Fiber* newFiber(PKVM* vm, Closure* closure);
|
||||
|
||||
// FIXME:
|
||||
// Same fix has to applied as newFunction() (see above).
|
||||
//
|
||||
// Allocate new Class object and return Class* with name [name].
|
||||
Class* newClass(PKVM* vm, Module* scr, const char* name, uint32_t length,
|
||||
int* cls_index, int* ctor_index);
|
||||
|
||||
// Allocate new instance with of the base [type]. Note that if [initialize] is
|
||||
// false, the field value buffer of the instance would be un initialized (ie.
|
||||
// the buffer count = 0). Otherwise they'll be set to VAR_NULL.
|
||||
Instance* newInstance(PKVM* vm, Class* cls, bool initialize);
|
||||
|
||||
// Allocate new native instance and with [data] as the native type handle and
|
||||
// return Instance*. The [id] is the unique id of the instance, this would be
|
||||
// used to check if two instances are equal and used to get the name of the
|
||||
// instance using NativeTypeNameFn callback.
|
||||
Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id);
|
||||
// Allocate new instance with of the base [type].
|
||||
Instance* newInstance(PKVM* vm, Class* cls);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* METHODS */
|
||||
@ -738,6 +756,9 @@ const char* getObjectTypeName(ObjectType type);
|
||||
// Returns the type name of the var [v].
|
||||
const char* varTypeName(Var v);
|
||||
|
||||
// Returns the PkVarType of the first class varaible [v].
|
||||
PkVarType getVarType(Var v);
|
||||
|
||||
// Returns true if both variables are the same (ie v1 is v2).
|
||||
bool isValuesSame(Var v1, Var v2);
|
||||
|
||||
|
126
src/pk_vm.c
126
src/pk_vm.c
@ -31,11 +31,6 @@ PkConfiguration pkNewConfiguration(void) {
|
||||
config.write_fn = NULL;
|
||||
config.read_fn = NULL;
|
||||
|
||||
config.inst_free_fn = NULL;
|
||||
config.inst_name_fn = NULL;
|
||||
config.inst_get_attrib_fn = NULL;
|
||||
config.inst_set_attrib_fn = NULL;
|
||||
|
||||
config.load_script_fn = NULL;
|
||||
config.resolve_path_fn = NULL;
|
||||
config.user_data = NULL;
|
||||
@ -555,6 +550,10 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) {
|
||||
frame->rbp = rbp;
|
||||
frame->closure = closure;
|
||||
frame->ip = closure->fn->fn->opcodes.data;
|
||||
|
||||
// Capture self.
|
||||
frame->self = vm->fiber->self;
|
||||
vm->fiber->self = VAR_UNDEFINED;
|
||||
}
|
||||
|
||||
static inline void reuseCallFrame(PKVM* vm, const Closure* closure) {
|
||||
@ -569,6 +568,10 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) {
|
||||
frame->closure = closure;
|
||||
frame->ip = closure->fn->fn->opcodes.data;
|
||||
|
||||
// Capture self.
|
||||
frame->self = vm->fiber->self;
|
||||
vm->fiber->self = VAR_UNDEFINED;
|
||||
|
||||
ASSERT(*frame->rbp == VAR_NULL, OOPS);
|
||||
|
||||
// Move all the argument(s) to the base of the current frame.
|
||||
@ -709,6 +712,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber_) {
|
||||
register const uint8_t* ip;
|
||||
|
||||
register Var* rbp; //< Stack base pointer register.
|
||||
register Var* self; //< Points to the self in the current call frame.
|
||||
register CallFrame* frame; //< Current call frame.
|
||||
register Module* module; //< Currently executing module.
|
||||
register Fiber* fiber = fiber_;
|
||||
@ -770,6 +774,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber_) {
|
||||
frame = &fiber->frames[fiber->frame_count-1]; \
|
||||
ip = frame->ip; \
|
||||
rbp = frame->rbp; \
|
||||
self = &frame->self; \
|
||||
module = frame->closure->fn->owner; \
|
||||
} while (false)
|
||||
|
||||
@ -793,12 +798,18 @@ L_vm_main_loop:
|
||||
// defined, the next line become a declaration (Opcode instruction;).
|
||||
NO_OP;
|
||||
|
||||
#define _DUMP_STACK() \
|
||||
do { \
|
||||
system("cls"); /* FIXME: */ \
|
||||
dumpGlobalValues(vm); \
|
||||
dumpStackFrame(vm); \
|
||||
DEBUG_BREAK(); \
|
||||
} while (false)
|
||||
|
||||
#if DUMP_STACK
|
||||
system("cls"); // FIXME:
|
||||
dumpGlobalValues(vm);
|
||||
dumpStackFrame(vm);
|
||||
DEBUG_BREAK();
|
||||
_DUMP_STACK();
|
||||
#endif
|
||||
#undef _DUMP_STACK
|
||||
|
||||
SWITCH() {
|
||||
|
||||
@ -848,14 +859,9 @@ L_vm_main_loop:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_INSTANCE):
|
||||
OPCODE(PUSH_SELF):
|
||||
{
|
||||
uint8_t index = READ_SHORT();
|
||||
ASSERT_INDEX(index, module->constants.count);
|
||||
ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_CLASS), OOPS);
|
||||
Instance* inst = newInstance(vm,
|
||||
(Class*)AS_OBJ(module->constants.data[index]), false);
|
||||
PUSH(VAR_OBJ(inst));
|
||||
PUSH(*self);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
@ -889,21 +895,6 @@ L_vm_main_loop:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(INST_APPEND):
|
||||
{
|
||||
Var value = PEEK(-1); // Don't pop yet, we need the reference for gc.
|
||||
Var inst = PEEK(-2);
|
||||
ASSERT(IS_OBJ_TYPE(inst, OBJ_INST), OOPS);
|
||||
|
||||
Instance* inst_p = (Instance*)AS_OBJ(inst);
|
||||
ASSERT(!inst_p->is_native, OOPS);
|
||||
Inst* ins = inst_p->ins;
|
||||
pkVarBufferWrite(&ins->fields, vm, value);
|
||||
DROP(); // value
|
||||
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_LOCAL_0):
|
||||
OPCODE(PUSH_LOCAL_1):
|
||||
OPCODE(PUSH_LOCAL_2):
|
||||
@ -971,6 +962,15 @@ L_vm_main_loop:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_BUILTIN_TY):
|
||||
{
|
||||
uint8_t index = READ_BYTE();
|
||||
ASSERT_INDEX(index, PK_INSTANCE);
|
||||
Class* cls = vm->builtin_classes[index];
|
||||
PUSH(VAR_OBJ(cls));
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_UPVALUE):
|
||||
{
|
||||
uint8_t index = READ_BYTE();
|
||||
@ -1058,32 +1058,66 @@ L_vm_main_loop:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t argc;
|
||||
Var callable;
|
||||
const Closure* closure;
|
||||
|
||||
OPCODE(METHOD_CALL):
|
||||
argc = READ_BYTE();
|
||||
fiber->ret = (fiber->sp - argc - 1);
|
||||
fiber->self = *fiber->ret; //< Self for the next call.
|
||||
|
||||
uint16_t index = READ_SHORT();
|
||||
bool is_method;
|
||||
String* name = moduleGetStringAt(module, (int)index);
|
||||
callable = getMethod(vm, fiber->self, name, &is_method);
|
||||
CHECK_ERROR();
|
||||
goto L_do_call;
|
||||
|
||||
OPCODE(CALL):
|
||||
OPCODE(TAIL_CALL):
|
||||
{
|
||||
const uint8_t argc = READ_BYTE();
|
||||
Var* callable = fiber->sp - argc - 1;
|
||||
|
||||
const Closure* closure = NULL;
|
||||
argc = READ_BYTE();
|
||||
fiber->ret = fiber->sp - argc - 1;
|
||||
callable = *fiber->ret;
|
||||
|
||||
L_do_call:
|
||||
// Raw functions cannot be on the stack, since they're not first class
|
||||
// citizens.
|
||||
ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS);
|
||||
ASSERT(!IS_OBJ_TYPE(callable, OBJ_FUNC), OOPS);
|
||||
|
||||
if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) {
|
||||
closure = (const Closure*)AS_OBJ(*callable);
|
||||
if (IS_OBJ_TYPE(callable, OBJ_CLOSURE)) {
|
||||
closure = (const Closure*)AS_OBJ(callable);
|
||||
|
||||
} else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) {
|
||||
closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor;
|
||||
} else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) {
|
||||
Class* cls = (Class*)AS_OBJ(callable);
|
||||
|
||||
// Allocate / create a new self before calling constructor on it.
|
||||
fiber->self = preConstructSelf(vm, cls);
|
||||
CHECK_ERROR();
|
||||
|
||||
closure = (const Closure*)(cls)->ctor;
|
||||
|
||||
// No constructor is defined on the class. Just return self.
|
||||
if (closure == NULL) {
|
||||
if (argc != 0) {
|
||||
String* msg = stringFormat(vm, "Expected exactly 0 argument(s).");
|
||||
RUNTIME_ERROR(msg);
|
||||
}
|
||||
|
||||
*fiber->ret = fiber->self;
|
||||
fiber->self = VAR_UNDEFINED;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
} else {
|
||||
RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to "
|
||||
"call, instead got",
|
||||
varTypeName(*callable), toString(vm, *callable)));
|
||||
DISPATCH();
|
||||
varTypeName(callable), toString(vm, callable)));
|
||||
}
|
||||
|
||||
// If we reached here it's a valid callable.
|
||||
ASSERT(closure != NULL, OOPS);
|
||||
|
||||
// -1 argument means multiple number of args.
|
||||
if (closure->fn->arity != -1 && closure->fn->arity != argc) {
|
||||
@ -1093,8 +1127,6 @@ L_vm_main_loop:
|
||||
RUNTIME_ERROR(msg);
|
||||
}
|
||||
|
||||
// Next call frame starts here. (including return value).
|
||||
fiber->ret = callable;
|
||||
*(fiber->ret) = VAR_NULL; //< Set the return value to null.
|
||||
|
||||
if (closure->fn->is_native) {
|
||||
@ -1129,9 +1161,9 @@ L_vm_main_loop:
|
||||
|
||||
} else {
|
||||
|
||||
if (instruction == OP_CALL) {
|
||||
if (instruction == OP_CALL || instruction == OP_METHOD_CALL) {
|
||||
UPDATE_FRAME(); //< Update the current frame's ip.
|
||||
pushCallFrame(vm, closure, callable);
|
||||
pushCallFrame(vm, closure, fiber->ret);
|
||||
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
|
||||
|
||||
} else {
|
||||
|
@ -67,7 +67,7 @@ def execute(expr)
|
||||
|
||||
## output the byte at the data pointer.
|
||||
else if c == '.'
|
||||
write(str_chr(mem[ptr]))
|
||||
write(chr(mem[ptr]))
|
||||
|
||||
else if c == ','
|
||||
assert(false, "Currently input isn't supported in pocketlang.")
|
||||
|
@ -3,7 +3,11 @@
|
||||
##
|
||||
## PI/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ...
|
||||
|
||||
from math import abs, PI
|
||||
from math import abs
|
||||
|
||||
## Temproarly we cannot register variables on native modules
|
||||
## will be implemented soon. So defining the PI here.
|
||||
PI = 3.14159265358979323846
|
||||
|
||||
pi_by_4 = 0; sign = -1
|
||||
for i in 1..100000
|
||||
|
@ -37,11 +37,9 @@ assert(!('foo' in {'bar':'baz'}))
|
||||
## Builtin functions tests.
|
||||
assert(to_string(42) == '42')
|
||||
|
||||
## Core module test.
|
||||
import math
|
||||
h1 = math.hash("testing"); h2 = math.hash("test" + "ing")
|
||||
assert(h1 == h2)
|
||||
assert(math.ceil(1.1) == math.floor(2.9))
|
||||
## FIXME: add hash function.
|
||||
##h1 = math.hash("testing"); h2 = math.hash("test" + "ing")
|
||||
##assert(h1 == h2)
|
||||
|
||||
## Logical statement test
|
||||
val = 0; a = false; b = true
|
||||
|
@ -1,36 +1,33 @@
|
||||
|
||||
## TODO: Implement ctor with va arg to
|
||||
## initialize, fields.
|
||||
class Vec2
|
||||
def _init(x, y)
|
||||
self.x = x
|
||||
self.y = y
|
||||
end
|
||||
|
||||
def add(other)
|
||||
return Vec2(self.x + other.x,
|
||||
self.y + other.y)
|
||||
end
|
||||
|
||||
## Note that operator overloading / friend functions
|
||||
## haven't implemented at this point (to_string won't actually
|
||||
## override it).
|
||||
def to_string
|
||||
return "[${self.x}, ${self.y}]"
|
||||
end
|
||||
|
||||
class _Vec
|
||||
x = 0
|
||||
y = 0
|
||||
end
|
||||
|
||||
def Vec(x, y)
|
||||
ret = _Vec()
|
||||
ret.x = x; ret.y = y
|
||||
return ret
|
||||
end
|
||||
v1 = Vec2(1, 2); assert(v1.x == 1 and v1.y == 2)
|
||||
print("v1 = ${v1.to_string()}")
|
||||
|
||||
def vecAdd(v1, v2)
|
||||
return Vec(v1.x + v2.x,
|
||||
v1.y + v2.y)
|
||||
end
|
||||
v2 = Vec2(3, 4); assert(v2.x == 3 and v2.y == 4)
|
||||
print("v2 = ${v2.to_string()}")
|
||||
|
||||
v1 = Vec(1, 2); assert(v1.x == 1 and v1.y == 2)
|
||||
v2 = Vec(3, 4); assert(v2.x == 3 and v2.y == 4)
|
||||
v3 = v1.add(v2); assert(v3.x == 4 and v3.y == 6)
|
||||
print("v3 = ${v3.to_string()}")
|
||||
|
||||
v3 = vecAdd(v1, v2)
|
||||
assert(v3.x == 4 and v3.y == 6)
|
||||
print('ALL TESTS PASSED')
|
||||
|
||||
class Test
|
||||
fn = null
|
||||
val = Vec(12, 32)
|
||||
end
|
||||
|
||||
test = Test()
|
||||
test.fn = to_string
|
||||
res = test.fn(test.val)
|
||||
assert(res == "[_Vec: x=12, y=32]")
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
## Core builtin functions and attribute tests.
|
||||
|
||||
## Math functions
|
||||
from math import *
|
||||
|
||||
assert(hex(12648430) == '0xc0ffee')
|
||||
assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3')
|
||||
assert(hex(-4294967295) == '-0xffffffff') ## the largest.
|
||||
@ -48,61 +45,6 @@ assert(r.as_list == [1, 2, 3, 4])
|
||||
assert(r.first == 1)
|
||||
assert(r.last == 5)
|
||||
|
||||
assert(sin(0) == 0)
|
||||
assert(sin(PI/2) == 1)
|
||||
|
||||
threshold = 0.0000000000001
|
||||
|
||||
assert(abs(cos(PI/3) - 0.5) < threshold )
|
||||
assert(abs(tan(PI/4) - 1.0) < threshold )
|
||||
for i in 0..1000
|
||||
assert(abs(sin(i) / cos(i) - tan(i)) < threshold)
|
||||
end
|
||||
|
||||
assert((cosh(.5) - 1.1276259652063807) < threshold)
|
||||
assert((tanh(0.5) - 1.127625965206) < threshold)
|
||||
for i in 0..100
|
||||
assert(abs(sinh(i) / cosh(i) - tanh(i)) < threshold)
|
||||
end
|
||||
|
||||
assert(abs(acos(PI/4) - 0.5) < 0.35)
|
||||
assert(abs(atan(PI/4) - 0.5) < 0.2)
|
||||
|
||||
assert((acos(0.5) - 1.1276259652063807) < threshold)
|
||||
assert((atan(0.3) - 1.1276259652063807) < threshold)
|
||||
|
||||
x = -1; interval = 1000
|
||||
for i in 0..interval-1
|
||||
x += 2/interval
|
||||
assert(abs(sin(asin(x)) - x) < threshold)
|
||||
assert(abs(cos(acos(x)) - x) < threshold)
|
||||
assert(abs(tan(atan(x)) - x) < threshold)
|
||||
end
|
||||
|
||||
assert(abs(log10(2) - 0.3010299956639812) < threshold)
|
||||
assert(round(1.4) == 1)
|
||||
assert(round(1.5) == 2)
|
||||
assert(round(-1.5) == -2)
|
||||
|
||||
## Note that these mathe functions are removed temproarly from the core
|
||||
## For more information see PR #201
|
||||
##
|
||||
##assert(abs(log2(2) - 1) < threshold)
|
||||
##assert(abs(log2(1) - 0) < threshold)
|
||||
##assert(abs(log2(5) - 2.321928094887362) < threshold)
|
||||
##
|
||||
##assert(abs(hypot(1,1) - 1.414213562373095) < threshold)
|
||||
##assert(abs(hypot(3,5) - 5.830951894845301) < threshold)
|
||||
##
|
||||
##assert(abs(cbrt(27) - 3) < threshold)
|
||||
##assert(abs(cbrt(9) - 2.080083823051904) < threshold)
|
||||
##
|
||||
##assert(abs(gamma(5) - 24) < threshold)
|
||||
##assert(abs(gamma(2.2) - 1.101802490879713) < threshold)
|
||||
|
||||
|
||||
|
||||
|
||||
# If we got here, that means all test were passed.
|
||||
print('All TESTS PASSED')
|
||||
|
||||
|
@ -3,10 +3,8 @@ def f0()
|
||||
yield("yield value")
|
||||
end
|
||||
|
||||
import Fiber
|
||||
|
||||
fiber = Fiber.new(f0)
|
||||
yield_value = Fiber.run(fiber)
|
||||
fiber = Fiber(f0)
|
||||
yield_value = fiber.run()
|
||||
assert(yield_value == "yield value")
|
||||
assert(!fiber.is_done)
|
||||
|
||||
@ -14,9 +12,10 @@ def f1()
|
||||
assert(yield("y") == "r")
|
||||
yield()
|
||||
end
|
||||
fiber = Fiber.new(f1)
|
||||
assert(Fiber.run(fiber) == "y")
|
||||
Fiber.resume(fiber, "r")
|
||||
|
||||
fiber = Fiber(f1)
|
||||
assert(fiber.run() == "y")
|
||||
fiber.resume("r")
|
||||
assert(!fiber.is_done)
|
||||
|
||||
def f2(p1, p2, p3)
|
||||
@ -27,11 +26,11 @@ def f2(p1, p2, p3)
|
||||
return p1 + p2 * p3
|
||||
end
|
||||
|
||||
fiber = Fiber.new(f2)
|
||||
p3 = Fiber.run(fiber, 1, 2, 3); assert(p3 == 3)
|
||||
p2 = Fiber.resume(fiber, 'r1'); assert(p2 == 2)
|
||||
p1 = Fiber.resume(fiber, 'r2'); assert(p1 == 1)
|
||||
pa = Fiber.resume(fiber, 'r3'); assert(pa == 7)
|
||||
fiber = Fiber(f2)
|
||||
p3 = fiber.run(1, 2, 3); assert(p3 == 3)
|
||||
p2 = fiber.resume('r1'); assert(p2 == 2)
|
||||
p1 = fiber.resume('r2'); assert(p1 == 1)
|
||||
pa = fiber.resume('r3'); assert(pa == 7)
|
||||
assert(fiber.is_done)
|
||||
|
||||
# If we got here, that means all test were passed.
|
||||
|
@ -13,10 +13,6 @@ import "basics.pk" ## will import all
|
||||
import "controlflow.pk" as if_test
|
||||
from "functions.pk" import f1, f2 as fn2, f3
|
||||
|
||||
import "class.pk" as class_
|
||||
assert(class_.Vec(42, 3.14).y == 3.14)
|
||||
(vec = class_._Vec()).x = 'a'; assert(vec.x == 'a')
|
||||
|
||||
## If it has a module name it'll bind to that name.
|
||||
import 'import/module.pk'
|
||||
assert(module_name.get_module_name() == 'module_name')
|
||||
|
64
tests/modules/math.pk
Normal file
64
tests/modules/math.pk
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
from math import *
|
||||
|
||||
assert(ceil(1.1) == floor(2.9))
|
||||
|
||||
## FIXME:
|
||||
## temproarly modules cannot define globals via native interface
|
||||
## and it'll be fixed soon.
|
||||
PI = 3.14159265358979323846
|
||||
|
||||
assert(sin(0) == 0)
|
||||
assert(sin(PI/2) == 1)
|
||||
|
||||
threshold = 0.0000000000001
|
||||
|
||||
assert(abs(cos(PI/3) - 0.5) < threshold )
|
||||
assert(abs(tan(PI/4) - 1.0) < threshold )
|
||||
for i in 0..1000
|
||||
assert(abs(sin(i) / cos(i) - tan(i)) < threshold)
|
||||
end
|
||||
|
||||
assert((cosh(.5) - 1.1276259652063807) < threshold)
|
||||
assert((tanh(0.5) - 1.127625965206) < threshold)
|
||||
for i in 0..100
|
||||
assert(abs(sinh(i) / cosh(i) - tanh(i)) < threshold)
|
||||
end
|
||||
|
||||
assert(abs(acos(PI/4) - 0.5) < 0.35)
|
||||
assert(abs(atan(PI/4) - 0.5) < 0.2)
|
||||
|
||||
assert((acos(0.5) - 1.1276259652063807) < threshold)
|
||||
assert((atan(0.3) - 1.1276259652063807) < threshold)
|
||||
|
||||
x = -1; interval = 1000
|
||||
for i in 0..interval-1
|
||||
x += 2/interval
|
||||
assert(abs(sin(asin(x)) - x) < threshold)
|
||||
assert(abs(cos(acos(x)) - x) < threshold)
|
||||
assert(abs(tan(atan(x)) - x) < threshold)
|
||||
end
|
||||
|
||||
assert(abs(log10(2) - 0.3010299956639812) < threshold)
|
||||
assert(round(1.4) == 1)
|
||||
assert(round(1.5) == 2)
|
||||
assert(round(-1.5) == -2)
|
||||
|
||||
## Note that these mathe functions are removed temproarly from the core
|
||||
## For more information see PR #201
|
||||
##
|
||||
##assert(abs(log2(2) - 1) < threshold)
|
||||
##assert(abs(log2(1) - 0) < threshold)
|
||||
##assert(abs(log2(5) - 2.321928094887362) < threshold)
|
||||
##
|
||||
##assert(abs(hypot(1,1) - 1.414213562373095) < threshold)
|
||||
##assert(abs(hypot(3,5) - 5.830951894845301) < threshold)
|
||||
##
|
||||
##assert(abs(cbrt(27) - 3) < threshold)
|
||||
##assert(abs(cbrt(9) - 2.080083823051904) < threshold)
|
||||
##
|
||||
##assert(abs(gamma(5) - 24) < threshold)
|
||||
##assert(abs(gamma(2.2) - 1.101802490879713) < threshold)
|
||||
|
||||
# If we got here, that means all test were passed.
|
||||
print('All TESTS PASSED')
|
@ -1,7 +1,7 @@
|
||||
## Example on how to integrate pocket VM with in your application.
|
||||
|
||||
- Including this example this repository contains several examples on how to integrate
|
||||
pocket VM with your application
|
||||
- Including this example this repository contains several examples on how to
|
||||
integrate pocket VM with your application
|
||||
- These examples (currently 2 examples)
|
||||
- The `cli/` application
|
||||
- The `docs/try/main.c` web assembly version of pocketlang
|
||||
|
@ -10,17 +10,17 @@
|
||||
|
||||
// The pocket script we're using to test.
|
||||
static const char* code =
|
||||
" from YourModule import variableToC \n"
|
||||
" a = 42 \n"
|
||||
" b = variableToC(a) \n"
|
||||
" print('[pocket] b =', b) \n"
|
||||
" from my_module import cFunction \n"
|
||||
" a = 42 \n"
|
||||
" b = cFunction(a) \n"
|
||||
" print('[pocket] b = $b') \n"
|
||||
;
|
||||
|
||||
/*****************************************************************************/
|
||||
/* MODULE FUNCTION */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void variableToC(PKVM* vm) {
|
||||
static void cFunction(PKVM* vm) {
|
||||
|
||||
// Get the parameter from pocket VM.
|
||||
double a;
|
||||
@ -36,15 +36,7 @@ static void variableToC(PKVM* vm) {
|
||||
/* POCKET VM CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Error report callback.
|
||||
static void reportError(PKVM* vm, PkErrorType type,
|
||||
const char* file, int line,
|
||||
const char* message) {
|
||||
fprintf(stderr, "Error: %s\n", message);
|
||||
}
|
||||
|
||||
// print() callback to write stdout.
|
||||
static void stdoutWrite(PKVM* vm, const char* text) {
|
||||
static void stdoutCallback(PKVM* vm, const char* text) {
|
||||
fprintf(stdout, "%s", text);
|
||||
}
|
||||
|
||||
@ -56,21 +48,20 @@ int main(int argc, char** argv) {
|
||||
|
||||
// Pocket VM configuration.
|
||||
PkConfiguration config = pkNewConfiguration();
|
||||
config.error_fn = reportError;
|
||||
config.write_fn = stdoutWrite;
|
||||
//config.read_fn = stdinRead;
|
||||
config.write_fn = stdoutCallback;
|
||||
|
||||
// Create a new pocket VM.
|
||||
PKVM* vm = pkNewVM(&config);
|
||||
|
||||
// Register your module.
|
||||
PkHandle* your_module = pkNewModule(vm, "YourModule");
|
||||
pkModuleAddFunction(vm, your_module, "variableToC", variableToC, 1);
|
||||
pkReleaseHandle(vm, your_module);
|
||||
// Registering a native module.
|
||||
PkHandle* my_module = pkNewModule(vm, "my_module");
|
||||
pkModuleAddFunction(vm, my_module, "cFunction", cFunction, 1);
|
||||
pkRegisterModule(vm, my_module);
|
||||
pkReleaseHandle(vm, my_module);
|
||||
|
||||
// The path and the source code.
|
||||
PkStringPtr source = { code, NULL, NULL, 0, 0 };
|
||||
PkStringPtr path = { "./some/path/", NULL, NULL, 0, 0 };
|
||||
PkStringPtr source = { .string = code };
|
||||
PkStringPtr path = { .string = "./some/path/" };
|
||||
|
||||
// Run the code.
|
||||
PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/);
|
||||
|
@ -3,174 +3,85 @@
|
||||
* Distributed Under The MIT License
|
||||
*/
|
||||
|
||||
#error Native interface is being refactored and will be completed soon.
|
||||
|
||||
// This is an example on how to write your own custom type (Vector here) and
|
||||
// bind it with with the pocket VM.
|
||||
|
||||
#include <pocketlang.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h> /* For malloc */
|
||||
#include <stdio.h> /* For printf */
|
||||
#include <string.h> /* For strncmp */
|
||||
#include <math.h> /* For sqrt */
|
||||
|
||||
// The script we're using to test the native Vector type.
|
||||
static const char* code =
|
||||
" import Vector # The native module. \n"
|
||||
" print('Module =', Vector) \n"
|
||||
" \n"
|
||||
" vec1 = Vector.new(1, 2) # Calling native method. \n"
|
||||
" print('vec1 =', 'Vector.new(1, 2)') \n"
|
||||
" print() \n"
|
||||
" \n"
|
||||
" # Using the native getter. \n"
|
||||
" print('vec1.x =', vec1.x) \n"
|
||||
" print('vec1.y =', vec1.y) \n"
|
||||
" print('vec1.length =', vec1.length) \n"
|
||||
" print() \n"
|
||||
" \n"
|
||||
" # Using the native setter. \n"
|
||||
" vec1.x = 3; vec1.y = 4; \n"
|
||||
" print('vec1.x =', vec1.x) \n"
|
||||
" print('vec1.y =', vec1.y) \n"
|
||||
" print('vec1.length =', vec1.length) \n"
|
||||
" print() \n"
|
||||
" \n"
|
||||
" vec2 = Vector.new(5, 6) \n"
|
||||
" vec3 = Vector.add(vec1, vec2) \n"
|
||||
" print('vec3 =', 'Vector.add(vec1, vec2)') \n"
|
||||
" print('vec3.x =', vec3.x) \n"
|
||||
" print('vec3.y =', vec3.y) \n"
|
||||
" \n"
|
||||
" from vector import Vec2 \n"
|
||||
" print('Class = $Vec2') \n"
|
||||
" \n"
|
||||
" v1 = Vec2(1, 2) \n"
|
||||
" print('v1 = $v1') \n"
|
||||
" print() \n"
|
||||
" \n"
|
||||
" print('v1.x = ${v1.x}') \n"
|
||||
" print('v1.y = ${v1.y}') \n"
|
||||
" print('v1.length = ${v1.length}') \n"
|
||||
" print() \n"
|
||||
" \n"
|
||||
" v1.x = 3; v1.y = 4; \n"
|
||||
" print('v1.x = ${v1.x}') \n"
|
||||
" print('v1.y = ${v1.y}') \n"
|
||||
" print('v1.length = ${v1.length}') \n"
|
||||
" print() \n"
|
||||
" \n"
|
||||
" v2 = Vec2(5, 6) \n"
|
||||
" v3 = v1.add(v2) \n"
|
||||
" print('v3 = ${v3}') \n"
|
||||
" print('v3.x = ${v3.x}') \n"
|
||||
" print('v3.y = ${v3.y}') \n"
|
||||
" \n"
|
||||
;
|
||||
|
||||
/*****************************************************************************/
|
||||
/* NATIVE TYPE DEFINES & CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// An enum value of native object, used as unique of the type in pocketlang.
|
||||
typedef enum {
|
||||
OBJ_VECTOR = 0,
|
||||
} ObjType;
|
||||
|
||||
typedef struct {
|
||||
double x, y; // Vector variables.
|
||||
} Vector;
|
||||
|
||||
// Get name callback, will called from pocketlang to get the type name from
|
||||
// the ID (the enum value).
|
||||
const char* getObjName(uint32_t id) {
|
||||
switch ((ObjType)id) {
|
||||
case OBJ_VECTOR: return "Vector";
|
||||
}
|
||||
return NULL; // Unreachable.
|
||||
}
|
||||
|
||||
// Instance getter callback to get a value from the native instance.
|
||||
// The hash value and the length of the string are provided with the
|
||||
// argument [attrib].
|
||||
void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) {
|
||||
|
||||
switch ((ObjType)id) {
|
||||
case OBJ_VECTOR: {
|
||||
Vector* vector = ((Vector*)instance);
|
||||
|
||||
if (strcmp(attrib.string, "x") == 0) {
|
||||
pkReturnNumber(vm, vector->x);
|
||||
return;
|
||||
|
||||
} else if (strcmp(attrib.string, "y") == 0) {
|
||||
pkReturnNumber(vm, vector->y);
|
||||
return;
|
||||
|
||||
} else if (strcmp(attrib.string, "length") == 0) {
|
||||
double length = sqrt(pow(vector->x, 2) + pow(vector->y, 2));
|
||||
pkReturnNumber(vm, length);
|
||||
return;
|
||||
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
// If we reached here that means the attribute doesn't exists.
|
||||
// Since we haven't used pkReturn...() function, pocket VM already
|
||||
// know that the attribute doesn't exists. just return.
|
||||
return;
|
||||
}
|
||||
|
||||
// Instance setter callback to set the value to the native instance.
|
||||
// The hash value and the length of the string are provided with the
|
||||
// argument [attrib].
|
||||
bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) {
|
||||
|
||||
switch ((ObjType)id) {
|
||||
case OBJ_VECTOR: {
|
||||
Vector* vector = ((Vector*)instance);
|
||||
|
||||
if (strcmp(attrib.string, "x") == 0) {
|
||||
double x; // Get the number x.
|
||||
if (!pkGetArgNumber(vm, 0, &x)) return false;
|
||||
vector->x = x;
|
||||
return true;
|
||||
|
||||
} else if (strcmp(attrib.string, "y") == 0) {
|
||||
double y; // Get the number x.
|
||||
if (!pkGetArgNumber(vm, 0, &y)) return false;
|
||||
vector->y = y;
|
||||
return true;
|
||||
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
// If we reached here that means the attribute doesn't exists.
|
||||
// Return false to indicate it.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The free object callback, called just before the native instance, garbage
|
||||
// collect.
|
||||
void freeObj(PKVM* vm, void* instance, uint32_t id) {
|
||||
free((void*)instance); // Your cleanups.
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VECTOR MODULE FUNCTIONS REGISTER */
|
||||
/*****************************************************************************/
|
||||
|
||||
// The Vector.new(x, y) function.
|
||||
void _vecNew(PKVM* vm) {
|
||||
double x, y; // The args.
|
||||
|
||||
// Get the args from the stack, If it's not number, return.
|
||||
if (!pkGetArgNumber(vm, 1, &x)) return;
|
||||
if (!pkGetArgNumber(vm, 2, &y)) return;
|
||||
|
||||
// Create a new vector.
|
||||
typedef struct {
|
||||
double x, y;
|
||||
} Vector;
|
||||
|
||||
// Native instance allocation callback.
|
||||
void* _newVec() {
|
||||
Vector* vec = (Vector*)malloc(sizeof(Vector));
|
||||
vec->x = x, vec->y = y;
|
||||
pkReturnInstNative(vm, (void*)vec, OBJ_VECTOR);
|
||||
vec->x = 0;
|
||||
vec->y = 0;
|
||||
return vec;
|
||||
}
|
||||
|
||||
// The Vector.length(vec) function.
|
||||
void _vecAdd(PKVM* vm) {
|
||||
Vector *v1, *v2;
|
||||
if (!pkGetArgInst(vm, 1, OBJ_VECTOR, (void**)&v1)) return;
|
||||
if (!pkGetArgInst(vm, 2, OBJ_VECTOR, (void**)&v2)) return;
|
||||
|
||||
// Create a new vector.
|
||||
Vector* v3 = (Vector*)malloc(sizeof(Vector));
|
||||
v3->x = v1->x + v2->x;
|
||||
v3->y = v1->y + v2->y;
|
||||
// Native instance de-allocatoion callback.
|
||||
void _deleteVec(void* vector) {
|
||||
free(vector);
|
||||
}
|
||||
|
||||
pkReturnInstNative(vm, (void*)v3, OBJ_VECTOR);
|
||||
// Vec2 'add' method.
|
||||
void _vec2Add(PKVM* vm) {
|
||||
Vector* self = (Vector*)pkGetSelf(vm);
|
||||
// FIXME:
|
||||
// Temproarly it's not possible to get vector from the args since the native
|
||||
// interface is being refactored. Will be implemented soon.
|
||||
}
|
||||
|
||||
// Register the 'Vector' module and it's functions.
|
||||
void registerVector(PKVM* vm) {
|
||||
PkHandle* vector = pkNewModule(vm, "Vector");
|
||||
PkHandle* vector = pkNewModule(vm, "vector");
|
||||
|
||||
pkModuleAddFunction(vm, vector, "new", _vecNew, 2);
|
||||
pkModuleAddFunction(vm, vector, "add", _vecAdd, 2);
|
||||
PkHandle* Vec2 = pkNewClass(vm, "Vec2", NULL /*Base Class*/,
|
||||
vector, _newVec, _deleteVec);
|
||||
pkClassAddMethod(vm, Vec2, "add", _vec2Add, 1);
|
||||
pkReleaseHandle(vm, Vec2);
|
||||
|
||||
pkRegisterModule(vm, vector);
|
||||
pkReleaseHandle(vm, vector);
|
||||
}
|
||||
|
||||
@ -178,35 +89,20 @@ void registerVector(PKVM* vm) {
|
||||
/* POCKET VM CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Error report callback.
|
||||
void reportError(PKVM* vm, PkErrorType type,
|
||||
const char* file, int line,
|
||||
const char* message) {
|
||||
fprintf(stderr, "Error: %s\n", message);
|
||||
}
|
||||
|
||||
// print() callback to write stdout.
|
||||
void stdoutWrite(PKVM* vm, const char* text) {
|
||||
void stdoutCallback(PKVM* vm, const char* text) {
|
||||
fprintf(stdout, "%s", text);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
PkConfiguration config = pkNewConfiguration();
|
||||
config.error_fn = reportError;
|
||||
config.write_fn = stdoutWrite;
|
||||
//config.read_fn = stdinRead;
|
||||
config.inst_free_fn = freeObj;
|
||||
config.inst_name_fn = getObjName;
|
||||
config.inst_get_attrib_fn = objGetAttrib;
|
||||
config.inst_set_attrib_fn = objSetAttrib;
|
||||
config.write_fn = stdoutCallback;
|
||||
|
||||
PKVM* vm = pkNewVM(&config);
|
||||
registerVector(vm);
|
||||
|
||||
PkStringPtr source = { code, NULL, NULL, 0, 0 };
|
||||
PkStringPtr path = { "./some/path/", NULL, NULL, 0, 0 };
|
||||
PkStringPtr source = { .string = code };
|
||||
PkStringPtr path = { .string = "./some/path/" };
|
||||
|
||||
PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/);
|
||||
pkFreeVM(vm);
|
||||
|
67
tests/random/linked_list.pk
Normal file
67
tests/random/linked_list.pk
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
class Node
|
||||
def _init(val)
|
||||
self.val = val
|
||||
self.next = null
|
||||
end
|
||||
|
||||
def _to_string()
|
||||
return "(${self.val})"
|
||||
end
|
||||
end
|
||||
|
||||
class LinkedList
|
||||
def _init()
|
||||
self.head = null
|
||||
end
|
||||
|
||||
def append(node)
|
||||
if self.head == null
|
||||
self.head = node
|
||||
else
|
||||
last = self.head
|
||||
while last.next
|
||||
last = last.next
|
||||
end
|
||||
last.next = node
|
||||
end
|
||||
end
|
||||
|
||||
def reverse()
|
||||
curr = self.head
|
||||
prev = null; next = null
|
||||
while curr
|
||||
next = curr.next
|
||||
curr.next = prev
|
||||
prev = curr
|
||||
curr = next
|
||||
end
|
||||
self.head = prev
|
||||
end
|
||||
|
||||
def _to_string()
|
||||
ret = ""
|
||||
next = self.head
|
||||
while next
|
||||
ret += next._to_string()
|
||||
ret += " --> "
|
||||
next = next.next
|
||||
end
|
||||
ret += "null"
|
||||
return ret
|
||||
end
|
||||
end
|
||||
|
||||
ll = LinkedList()
|
||||
ll.append(Node(4))
|
||||
ll.append(Node(6))
|
||||
ll.append(Node(3))
|
||||
ll.append(Node(9))
|
||||
|
||||
## FIXME: No override supported at the moment.
|
||||
print(ll._to_string())
|
||||
|
||||
ll.reverse()
|
||||
|
||||
print(ll._to_string())
|
||||
|
@ -65,7 +65,7 @@ def eval(expr, ind)
|
||||
return [r[1] / r[2], r[3]]
|
||||
|
||||
else if isnum(c)
|
||||
val = str_ord(c) - str_ord('0')
|
||||
val = ord(c) - ord('0')
|
||||
assert(0 <= val and val < 10)
|
||||
return [val, ind]
|
||||
|
||||
@ -93,7 +93,7 @@ end
|
||||
|
||||
## Return true if c in numeric.
|
||||
def isnum(c)
|
||||
k = str_ord(c) - str_ord('0')
|
||||
k = ord(c) - ord('0')
|
||||
## TODO: k in 0..10
|
||||
return (0 <= k and k < 10)
|
||||
end
|
||||
|
@ -26,8 +26,13 @@ TEST_SUITE = {
|
||||
"lang/functions.pk",
|
||||
"lang/import.pk",
|
||||
),
|
||||
|
||||
"Modules Test" : (
|
||||
"modules/math.pk",
|
||||
),
|
||||
|
||||
"Random Scripts" : (
|
||||
"random/linked_list.pk",
|
||||
"random/lisp_eval.pk",
|
||||
"random/string_algo.pk",
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user