Merge pull request #206 from ThakeeNathees/class2

[WIP] class implementation
This commit is contained in:
Thakee Nathees 2022-04-22 07:58:29 +05:30 committed by GitHub
commit 020f9e2eab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1527 additions and 1450 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")

View File

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

View File

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

View File

@ -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]")

View File

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

View File

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

View File

@ -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
View 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')

View File

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

View File

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

View File

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

View 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())

View File

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

View File

@ -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",
),