class implemented

This commit is contained in:
Thakee Nathees 2021-06-20 20:58:31 +05:30
parent 3f1d0e9380
commit 41ed7dd991
18 changed files with 684 additions and 155 deletions

View File

@ -1,9 +1,9 @@
# ## Copyright (c) 2020-2021 Thakee Nathees
# ## Distributed Under The MIT License
## Copyright (c) 2020-2021 Thakee Nathees
## Distributed Under The MIT License
CC = gcc
CFLAGS = -fPIC -Wno-int-to-pointer-cast
CFLAGS = -fPIC
DEBUG_CFLAGS = -D DEBUG -g3 -Og
RELEASE_CFLAGS = -g -O3
LDFLAGS = -lm

View File

@ -68,7 +68,7 @@ except for a c99 compatible compiler. It can be compiled with the following comm
#### GCC / MinGw / Clang (alias with gcc)
```
gcc -o pocket cli/*.c src/*.c -Isrc/include -lm -Wno-int-to-pointer-cast
gcc -o pocket cli/*.c src/*.c -Isrc/include -lm
```
#### MSVC

View File

@ -130,7 +130,7 @@ PkStringPtr loadScript(PKVM* vm, const char* path) {
int main(int argc, char** argv) {
const char* usage = "usage: pocket [-c cmd | file]\n";
//const char* usage = "usage: pocket [-c cmd | file]\n";
// TODO: implement arg parse, REPL.

View File

@ -126,10 +126,10 @@ static void _pathRelpath(PKVM* vm) {
if (!pkGetArgString(vm, 2, &path)) return;
char abs_from[FILENAME_MAX];
size_t len_from = pathAbs(from, abs_from, sizeof(abs_from));
pathAbs(from, abs_from, sizeof(abs_from));
char abs_path[FILENAME_MAX];
size_t len_path = pathAbs(path, abs_path, sizeof(abs_path));
pathAbs(path, abs_path, sizeof(abs_path));
char result[FILENAME_MAX];
size_t len = cwk_path_get_relative(abs_from, abs_path,

View File

@ -33,7 +33,7 @@ const char* read_line(uint32_t* length) {
// Read a line from stdin and returns it without the line ending.
static void readLine(ByteBuffer* buff) {
do {
char c = fgetc(stdin);
char c = (char)fgetc(stdin);
if (c == EOF || c == '\n') break;
byteBufferWrite(buff, (uint8_t)c);

View File

@ -6,6 +6,8 @@
// To implement.
- no __file__ for core modules.
- def f(x)
f(x) ## not tco, unless return f(x)
end

View File

@ -89,6 +89,8 @@ typedef enum {
PK_SCRIPT,
PK_FUNCTION,
PK_FIBER,
PK_CLASS,
PK_INST,
} PkVarType;
typedef struct PkStringPtr PkStringPtr;
@ -387,7 +389,6 @@ PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
//PK_PUBLIC PkVar pkPushBool(PKVM* vm, bool value);
//PK_PUBLIC PkVar pkPushNumber(PKVM* vm, double value);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -77,6 +77,14 @@
/* COMMON MACROS */
/*****************************************************************************/
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#elif defined(__clang__)
#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#include <stdio.h> //< Only needed here for ASSERT() macro and for release mode
//< TODO; macro use this to print a crash report.

View File

@ -17,9 +17,13 @@
#define MAX_VARIABLES 256
// The maximum number of functions a script could contain. Also it's limited by
// it's opcode which is using a single byte value to identify the local.
// it's opcode which is using a single byte value to identify.
#define MAX_FUNCTIONS 256
// The maximum number of classes a script could contain. Also it's limited by
// it's opcode which is using a single byte value to identify.
#define MAX_CLASSES 255
// The maximum number of names that were used before defined. Its just the size
// of the Forward buffer of the compiler. Feel free to increase it if it
// require more.
@ -107,6 +111,7 @@ typedef enum {
// Keywords.
TK_MODULE, // module
TK_CLASS, // class
TK_FROM, // from
TK_IMPORT, // import
TK_AS, // as
@ -170,6 +175,7 @@ typedef struct {
// List of keywords mapped into their identifiers.
static _Keyword _keywords[] = {
{ "module", 6, TK_MODULE },
{ "class", 5, TK_CLASS },
{ "from", 4, TK_FROM },
{ "import", 6, TK_IMPORT },
{ "as", 2, TK_AS },
@ -973,6 +979,7 @@ typedef enum {
NAME_LOCAL_VAR, //< Including parameter.
NAME_GLOBAL_VAR,
NAME_FUNCTION,
NAME_CLASS,
NAME_BUILTIN, //< Native builtin function.
} NameDefnType;
@ -1025,6 +1032,14 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
return result;
}
// Search through classes.
index = scriptGetClass(compiler->script, name, length);
if (index != -1) {
result.type = NAME_CLASS;
result.index = index;
return result;
}
// Search through functions.
index = scriptGetFunc(compiler->script, name, length);
if (index != -1) {
@ -1140,6 +1155,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
/* TK_SRIGHT */ { NULL, exprBinaryOp, PREC_BITWISE_SHIFT },
/* TK_SLEFT */ { NULL, exprBinaryOp, PREC_BITWISE_SHIFT },
/* TK_MODULE */ NO_RULE,
/* TK_CLASS */ NO_RULE,
/* TK_FROM */ NO_RULE,
/* TK_IMPORT */ NO_RULE,
/* TK_AS */ NO_RULE,
@ -1293,6 +1309,11 @@ static void exprName(Compiler* compiler) {
emitByte(compiler, result.index);
break;
case NAME_CLASS:
emitOpcode(compiler, OP_PUSH_TYPE);
emitByte(compiler, result.index);
break;
case NAME_BUILTIN:
emitOpcode(compiler, OP_PUSH_BUILTIN_FN);
emitByte(compiler, result.index);
@ -1776,6 +1797,19 @@ static void compilerExitBlock(Compiler* compiler) {
compiler->scope_depth--;
}
static void compilerPushFunc(Compiler* compiler, Func* fn,
Function* func, int index) {
fn->outer_func = compiler->func;
fn->ptr = func;
fn->depth = compiler->scope_depth;
fn->index = index;
compiler->func = fn;
}
static void compilerPopFunc(Compiler* compiler) {
compiler->func = compiler->func->outer_func;
}
/*****************************************************************************/
/* COMPILING (EMIT BYTECODE) */
/*****************************************************************************/
@ -1864,6 +1898,94 @@ typedef enum {
static void compileStatement(Compiler* compiler);
static void compileBlockBody(Compiler* compiler, BlockType type);
// Compile a type and return it's index in the script's types buffer.
static int compileType(Compiler* compiler) {
// Consume the name of the type.
consume(compiler, TK_NAME, "Expected a type name.");
const char* name = compiler->previous.start;
int name_len = compiler->previous.length;
NameSearchResult result = compilerSearchName(compiler, name, name_len);
if (result.type != NAME_NOT_DEFINED) {
parseError(compiler, "Name '%.*s' already exists.", name_len, name);
}
// Create a new type.
Class* type = newClass(compiler->vm, compiler->script,
name, (uint32_t)name_len);
type->ctor->arity = 0;
// Check count exceeded.
int fn_index = (int)compiler->script->functions.count - 1;
if (fn_index == MAX_FUNCTIONS) {
parseError(compiler, "A script should contain at most %d functions.",
MAX_FUNCTIONS);
}
int ty_index = (int)(compiler->script->classes.count - 1);
if (ty_index == MAX_CLASSES) {
parseError(compiler, "A script should contain at most %d types.",
MAX_CLASSES);
}
// Compile the constructor function.
ASSERT(compiler->func->ptr == compiler->script->body, OOPS);
Func curr_fn;
compilerPushFunc(compiler, &curr_fn, type->ctor, fn_index);
compilerEnterBlock(compiler);
// Push an instance on the stack.
emitOpcode(compiler, OP_PUSH_INSTANCE);
emitByte(compiler, ty_index);
skipNewLines(compiler);
TokenType next = peek(compiler);
while (next != TK_END && next != TK_EOF) {
// Compile field name.
consume(compiler, TK_NAME, "Expected a type name.");
const char* f_name = compiler->previous.start;
int f_len = compiler->previous.length;
uint32_t f_index = scriptAddName(compiler->script, compiler->vm,
f_name, f_len);
// TODO: Add a string compare macro.
String* new_name = compiler->script->names.data[f_index];
for (uint32_t i = 0; i < type->field_names.count; i++) {
String* prev = compiler->script->names.data[type->field_names.data[i]];
if (new_name->hash == prev->hash && new_name->length == prev->length &&
memcmp(new_name->data, prev->data, prev->length) == 0) {
parseError(compiler, "Class field with name '%s' already exists.",
new_name->data);
}
}
pkUintBufferWrite(&type->field_names, compiler->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);
skipNewLines(compiler);
next = peek(compiler);
}
consume(compiler, TK_END, "Expected 'end' after type declaration end.");
compilerExitBlock(compiler);
// At this point, the stack top would be the constructed instance. Return it.
emitFunctionEnd(compiler);
compilerPopFunc(compiler);
return -1; // TODO;
}
// Compile a function and return it's index in the script's function buffer.
static int compileFunction(Compiler* compiler, FuncType fn_type) {
@ -1892,13 +2014,8 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
MAX_FUNCTIONS);
}
Func curr_func;
curr_func.outer_func = compiler->func;
curr_func.ptr = func;
curr_func.depth = compiler->scope_depth;
curr_func.index = fn_index;
compiler->func = &curr_func;
Func curr_fn;
compilerPushFunc(compiler, &curr_fn, func, fn_index);
int argc = 0;
compilerEnterBlock(compiler); // Parameter depth.
@ -1968,7 +2085,8 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
printf("%s", buff.data);
pkByteBufferClear(&buff, compiler->vm);
#endif
compiler->func = compiler->func->outer_func;
compilerPopFunc(compiler);
return fn_index;
}
@ -2156,6 +2274,7 @@ static int compilerImportName(Compiler* compiler, int line,
return result.index;
case NAME_FUNCTION:
case NAME_CLASS:
case NAME_BUILTIN:
parseError(compiler, "Name '%.*s' already exists.", length, name);
return -1;
@ -2196,8 +2315,12 @@ static void compilerImportAll(Compiler* compiler, Script* script) {
ASSERT(script != NULL, OOPS);
ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS);
bool done = false; //< A flag to jump out of the loop.
pkUintBuffer* name_buff = NULL; //< The string buffer to iterate through.
// Import all types.
for (uint32_t i = 0; i < script->classes.count; i++) {
uint32_t name_ind = script->classes.data[i]->name;
String* name = script->names.data[name_ind];
compilerImportSingleEntry(compiler, name->data, name->length);
}
// Import all functions.
for (uint32_t i = 0; i < script->functions.count; i++) {
@ -2581,7 +2704,10 @@ static void compileTopLevelStatement(Compiler* compiler) {
// If the statement is call, this will be set to true.
compiler->is_last_call = false;
if (match(compiler, TK_NATIVE)) {
if (match(compiler, TK_CLASS)) {
compileType(compiler);
} else if (match(compiler, TK_NATIVE)) {
compileFunction(compiler, FN_NATIVE);
} else if (match(compiler, TK_DEF)) {
@ -2624,10 +2750,11 @@ PkResult compile(PKVM* vm, Script* script, const char* source,
ASSERT(script->body != NULL, OOPS);
pkByteBufferClear(&script->body->fn->opcodes, vm);
// Remember the count of the globals and functions, If the compilation failed
// discard all the globals and functions added by the compilation.
// Remember the count of the globals, functions and types, If the compilation
// failed discard all the globals and functions added by the compilation.
uint32_t globals_count = script->globals.count;
uint32_t functions_count = script->functions.count;
uint32_t types_count = script->classes.count;
Func curr_fn;
curr_fn.depth = DEPTH_SCRIPT;
@ -2688,6 +2815,7 @@ PkResult compile(PKVM* vm, Script* script, const char* source,
if (compiler->has_errors) {
script->globals.count = script->global_names.count = globals_count;
script->functions.count = functions_count;
script->classes.count = types_count;
}
#if DEBUG_DUMP_COMPILED_CODE

View File

@ -815,12 +815,14 @@ DEF(stdLangDisas,
RET(VAR_OBJ(dump));
}
#ifdef DEBUG
DEF(stdLangDebugBreak,
"debug_break() -> null\n"
"A debug function for development (will be removed).") {
DEBUG_BREAK();
}
#endif
DEF(stdLangWrite,
"write(...) -> null\n"
@ -1080,7 +1082,8 @@ Var varAdd(PKVM* vm, Var v1, Var v2) {
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
case OBJ_CLASS:
case OBJ_INST:
break;
}
}
@ -1339,6 +1342,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
// map = { "foo" : 42, "can't access" : 32 }
// val = map.foo ## 42
TODO;
UNREACHABLE();
}
case OBJ_RANGE:
@ -1371,8 +1375,15 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
{
Script* scr = (Script*)obj;
// Search in types.
int index = scriptGetClass(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX((uint32_t)index, scr->classes.count);
return VAR_OBJ(scr->classes.data[index]);
}
// Search in functions.
int index = scriptGetFunc(scr, attrib->data, attrib->length);
index = scriptGetFunc(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX((uint32_t)index, scr->functions.count);
return VAR_OBJ(scr->functions.data[index]);
@ -1408,8 +1419,38 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
}
case OBJ_FIBER:
case OBJ_USER:
TODO;
UNREACHABLE();
case OBJ_CLASS:
TODO;
UNREACHABLE();
case OBJ_INST:
{
Instance* inst = (Instance*)obj;
if (inst->is_native) {
TODO;
} 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);
ASSERT_INDEX(ty->field_names.data[i], ty->owner->names.count);
String* f_name = ty->owner->names.data[ty->field_names.data[i]];
if (f_name->hash == attrib->hash &&
f_name->length == attrib->length &&
memcmp(f_name->data, attrib->data, attrib->length) == 0) {
return inst->ins->fields.data[i];
}
}
}
ERR_NO_ATTRIB(vm, on, attrib);
return VAR_NULL;
}
default:
UNREACHABLE();
@ -1485,6 +1526,15 @@ do { \
return;
}
index = scriptGetClass(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX((uint32_t)index, scr->classes.count);
ASSERT_INDEX(scr->classes.data[index]->name, scr->names.count);
String* name = scr->names.data[scr->classes.data[index]->name];
ATTRIB_IMMUTABLE(name->data);
return;
}
ERR_NO_ATTRIB(vm, on, attrib);
return;
}
@ -1499,10 +1549,39 @@ do { \
ERR_NO_ATTRIB(vm, on, attrib);
return;
case OBJ_USER:
TODO; //ERR_NO_ATTRIB(vm, on, attrib);
case OBJ_CLASS:
ERR_NO_ATTRIB(vm, on, attrib);
return;
case OBJ_INST:
{
Instance* inst = (Instance*)obj;
if (inst->is_native) {
TODO;
return;
} 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);
ASSERT_INDEX(ty->field_names.data[i], ty->owner->names.count);
String* f_name = ty->owner->names.data[ty->field_names.data[i]];
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;
}
}
ERR_NO_ATTRIB(vm, on, attrib);
return;
}
UNREACHABLE();
}
default:
UNREACHABLE();
}
@ -1574,8 +1653,11 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
case OBJ_CLASS:
case OBJ_INST:
TODO;
UNREACHABLE();
default:
UNREACHABLE();
}
@ -1621,8 +1703,11 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
case OBJ_CLASS:
case OBJ_INST:
TODO;
UNREACHABLE();
default:
UNREACHABLE();
}

View File

@ -96,7 +96,7 @@ void dumpFunctionCode(PKVM* vm, Function* func, pkByteBuffer* buff) {
const char* op_name = op_names[opcodes[i]];
uint32_t op_length = (uint32_t)strlen(op_name);
pkByteBufferAddString(buff, vm, op_name, op_length);
for (uint32_t i = 0; i < 16 - op_length; i++) { // Padding.
for (uint32_t j = 0; j < 16 - op_length; j++) { // Padding.
ADD_CHAR(vm, buff, ' ');
}
@ -125,9 +125,25 @@ void dumpFunctionCode(PKVM* vm, Function* func, pkByteBuffer* buff) {
break;
case OP_PUSH_LIST: SHORT_ARG(); break;
case OP_PUSH_INSTANCE:
{
int ty_index = READ_BYTE();
ASSERT_INDEX((uint32_t)ty_index, func->owner->classes.count);
uint32_t name_ind = func->owner->classes.data[ty_index]->name;
ASSERT_INDEX(name_ind, func->owner->names.count);
String* ty_name = func->owner->names.data[name_ind];
// Prints: %5d [Ty:%s]\n
ADD_INTEGER(vm, buff, ty_index, INT_WIDTH);
pkByteBufferAddString(buff, vm, STR_AND_LEN(" [Ty:"));
pkByteBufferAddString(buff, vm, ty_name->data, ty_name->length);
pkByteBufferAddString(buff, vm, STR_AND_LEN("]\n"));
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:
@ -148,7 +164,7 @@ void dumpFunctionCode(PKVM* vm, Function* func, pkByteBuffer* buff) {
} else {
arg = (int)(op - OP_PUSH_LOCAL_0);
for (int i = 0; i < INT_WIDTH; i++) ADD_CHAR(vm, buff, ' ');
for (int j = 0; j < INT_WIDTH; j++) ADD_CHAR(vm, buff, ' ');
}
if (arg < func->arity) {
@ -181,7 +197,7 @@ void dumpFunctionCode(PKVM* vm, Function* func, pkByteBuffer* buff) {
} else {
arg = (int)(op - OP_STORE_LOCAL_0);
for (int i = 0; i < INT_WIDTH; i++) ADD_CHAR(vm, buff, ' ');
for (int j = 0; j < INT_WIDTH; j++) ADD_CHAR(vm, buff, ' ');
}
if (arg < func->arity) {
@ -224,6 +240,23 @@ void dumpFunctionCode(PKVM* vm, Function* func, pkByteBuffer* buff) {
break;
}
case OP_PUSH_TYPE:
{
int ty_index = READ_BYTE();
ASSERT_INDEX((uint32_t)ty_index, func->owner->classes.count);
uint32_t name_ind = func->owner->classes.data[ty_index]->name;
ASSERT_INDEX(name_ind, func->owner->names.count);
String* ty_name = func->owner->names.data[name_ind];
// Prints: %5d [Ty:%s]\n
ADD_INTEGER(vm, buff, ty_index, INT_WIDTH);
pkByteBufferAddString(buff, vm, STR_AND_LEN(" [Ty:"));
pkByteBufferAddString(buff, vm, ty_name->data, ty_name->length);
pkByteBufferAddString(buff, vm, STR_AND_LEN("]\n"));
break;
}
case OP_PUSH_BUILTIN_FN:
{
int index = READ_BYTE();

View File

@ -39,6 +39,10 @@ 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)
// 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.
OPCODE(LIST_APPEND, 0, -1)
@ -47,6 +51,10 @@ 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.
@ -89,6 +97,10 @@ OPCODE(STORE_GLOBAL, 1, 0)
// params: 1 byte index.
OPCODE(PUSH_FN, 1, 1)
// Push the script's type on the stack.
// params: 1 byte index
OPCODE(PUSH_TYPE, 1, 1)
// Push a built in function.
// params: 1 bytes index.
OPCODE(PUSH_BUILTIN_FN, 1, 1)

View File

@ -34,7 +34,8 @@ PkVarType pkGetValueType(const PkVar value) {
case OBJ_SCRIPT: return PK_SCRIPT;
case OBJ_FUNC: return PK_FUNCTION;
case OBJ_FIBER: return PK_FIBER;
case OBJ_USER: TODO; break;
case OBJ_CLASS: return PK_CLASS;
case OBJ_INST: return PK_INST;
}
UNREACHABLE();
@ -83,6 +84,7 @@ DEFINE_BUFFER(Byte, uint8_t)
DEFINE_BUFFER(Var, Var)
DEFINE_BUFFER(String, String*)
DEFINE_BUFFER(Function, Function*)
DEFINE_BUFFER(Class, Class*)
void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str,
uint32_t length) {
@ -138,6 +140,7 @@ void markVarBuffer(PKVM* vm, pkVarBuffer* self) {
MARK_OBJ_BUFFER(String)
MARK_OBJ_BUFFER(Function)
MARK_OBJ_BUFFER(Class)
#undef MARK_OBJ_BUFFER
@ -192,6 +195,9 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
markFunctionBuffer(vm, &scr->functions);
vm->bytes_allocated += sizeof(Function*) * scr->functions.capacity;
markClassBuffer(vm, &scr->classes);
vm->bytes_allocated += sizeof(Class*) * scr->classes.count;
markStringBuffer(vm, &scr->names);
vm->bytes_allocated += sizeof(String*) * scr->names.capacity;
@ -207,8 +213,10 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
if (!func->is_native) {
Fn* fn = func->fn;
vm->bytes_allocated += sizeof(Fn);
vm->bytes_allocated += sizeof(uint8_t)* fn->opcodes.capacity;
vm->bytes_allocated += sizeof(int) * fn->oplines.capacity;
vm->bytes_allocated += sizeof(uint32_t) * fn->oplines.capacity;
}
} break;
@ -237,9 +245,24 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
} break;
case OBJ_USER:
TODO;
break;
case OBJ_CLASS:
{
Class* type = (Class*)obj;
vm->bytes_allocated += sizeof(Class);
markObject(vm, &type->owner->_super);
markObject(vm, &type->ctor->_super);
vm->bytes_allocated += sizeof(uint32_t) * type->field_names.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;
}
} break;
}
}
@ -329,6 +352,7 @@ Script* newScript(PKVM* vm, String* path) {
pkUintBufferInit(&script->global_names);
pkVarBufferInit(&script->literals);
pkFunctionBufferInit(&script->functions);
pkClassBufferInit(&script->classes);
pkStringBufferInit(&script->names);
vmPushTempRef(vm, &script->_super);
@ -434,6 +458,59 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
return fiber;
}
Class* newClass(PKVM* vm, Script* scr, const char* name, uint32_t length) {
Class* type = ALLOCATE(vm, Class);
varInitObject(&type->_super, vm, OBJ_CLASS);
vmPushTempRef(vm, &type->_super); // type.
pkClassBufferWrite(&scr->classes, vm, type);
type->owner = scr;
type->name = scriptAddName(scr, vm, name, length);
pkUintBufferInit(&type->field_names);
// Can't use '$' in string format. (TODO)
String* ty_name = scr->names.data[type->name];
String* dollar = newStringLength(vm, "$", 1);
vmPushTempRef(vm, &dollar->_super); // dollar
String* ctor_name = stringFormat(vm, "@(Ctor:@)", dollar, ty_name);
vmPopTempRef(vm); // dollar
// Constructor.
vmPushTempRef(vm, &ctor_name->_super); // ctor_name
type->ctor = newFunction(vm, ctor_name->data, ctor_name->length,
scr, false, NULL);
vmPopTempRef(vm); // ctor_name
vmPopTempRef(vm); // type.
return type;
}
Instance* newInstance(PKVM* vm, Class* ty, bool initialize) {
Instance* inst = ALLOCATE(vm, Instance);
varInitObject(&inst->_super, vm, OBJ_INST);
vmPushTempRef(vm, &inst->_super); // inst.
ASSERT(ty->name < ty->owner->names.count, OOPS);
inst->name = ty->owner->names.data[ty->name]->data;
inst->is_native = false;
Inst* ins = ALLOCATE(vm, Inst);
inst->ins = ins;
ins->type = ty;
pkVarBufferInit(&ins->fields);
if (initialize && ty->field_names.count != 0) {
pkVarBufferFill(&ins->fields, vm, VAR_NULL, ty->field_names.count);
}
vmPopTempRef(vm); // inst.
return inst;
}
List* rangeAsList(PKVM* vm, Range* self) {
List* list;
if (self->from < self->to) {
@ -678,8 +755,10 @@ static uint32_t _hashObject(Object* obj) {
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
case OBJ_CLASS:
case OBJ_INST:
TODO;
UNREACHABLE();
default:
L_unhashable:
@ -898,6 +977,7 @@ void freeObject(PKVM* vm, Object* self) {
pkUintBufferClear(&scr->global_names, vm);
pkVarBufferClear(&scr->literals, vm);
pkFunctionBufferClear(&scr->functions, vm);
pkClassBufferClear(&scr->classes, vm);
pkStringBufferClear(&scr->names, vm);
} break;
@ -906,6 +986,7 @@ void freeObject(PKVM* vm, Object* self) {
if (!func->is_native) {
pkByteBufferClear(&func->fn->opcodes, vm);
pkUintBufferClear(&func->fn->oplines, vm);
DEALLOCATE(vm, func->fn);
}
} break;
@ -915,10 +996,27 @@ void freeObject(PKVM* vm, Object* self) {
DEALLOCATE(vm, fiber->frames);
} break;
case OBJ_USER:
TODO; // Remove OBJ_USER.
case OBJ_CLASS: {
Class* type = (Class*)self;
pkUintBufferClear(&type->field_names, vm);
} break;
case OBJ_INST:
{
Instance* inst = (Instance*)self;
if (inst->is_native) {
TODO;
} else {
Inst* ins = inst->ins;
pkVarBufferClear(&ins->fields, vm);
DEALLOCATE(vm, ins);
}
break;
}
}
DEALLOCATE(vm, self);
}
@ -943,23 +1041,36 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
return self->names.count - 1;
}
uint32_t scriptGetFunc(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->functions.count; i++) {
const char* fn_name = script->functions.data[i]->name;
uint32_t fn_length = (uint32_t)strlen(fn_name);
if (fn_length == length && strncmp(fn_name, name, length) == 0) {
return i;
int scriptGetClass(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->classes.count; i++) {
uint32_t name_ind = script->classes.data[i]->name;
ASSERT(name_ind < script->names.count, OOPS);
String* ty_name = script->names.data[name_ind];
if (ty_name->length == length &&
strncmp(ty_name->data, name, length) == 0) {
return (int)i;
}
}
return -1;
}
uint32_t scriptGetGlobals(Script* script, const char* name, uint32_t length) {
int scriptGetFunc(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->functions.count; i++) {
const char* fn_name = script->functions.data[i]->name;
uint32_t fn_length = (uint32_t)strlen(fn_name);
if (fn_length == length && strncmp(fn_name, name, length) == 0) {
return (int)i;
}
}
return -1;
}
int scriptGetGlobals(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->global_names.count; i++) {
uint32_t name_index = script->global_names.data[i];
String* g_name = script->names.data[name_index];
if (g_name->length == length && strncmp(g_name->data, name, length) == 0) {
return i;
return (int)i;
}
}
return -1;
@ -970,9 +1081,9 @@ uint32_t scriptAddGlobal(PKVM* vm, Script* script,
Var value) {
// If already exists update the value.
uint32_t var_ind = scriptGetGlobals(script, name, length);
int var_ind = scriptGetGlobals(script, name, length);
if (var_ind != -1) {
ASSERT(var_ind < script->globals.count, OOPS);
ASSERT(var_ind < (int)script->globals.count, OOPS);
script->globals.data[var_ind] = value;
return var_ind;
}
@ -985,13 +1096,15 @@ uint32_t scriptAddGlobal(PKVM* vm, Script* script,
return script->globals.count - 1;
}
// Utility functions //////////////////////////////////////////////////////////
/*****************************************************************************/
/* UTILITY FUNCTIONS */
/*****************************************************************************/
const char* getPkVarTypeName(PkVarType type) {
switch (type) {
case PK_NULL: return "null";
case PK_BOOL: return "bool";
case PK_NUMBER: return "number";
case PK_NULL: return "Null";
case PK_BOOL: return "Bool";
case PK_NUMBER: return "Number";
case PK_STRING: return "String";
case PK_LIST: return "List";
case PK_MAP: return "Map";
@ -999,6 +1112,8 @@ const char* getPkVarTypeName(PkVarType type) {
case PK_SCRIPT: return "Script";
case PK_FUNCTION: return "Function";
case PK_FIBER: return "Fiber";
case PK_CLASS: return "Class";
case PK_INST: return "Inst";
}
UNREACHABLE();
@ -1013,15 +1128,16 @@ const char* getObjectTypeName(ObjectType type) {
case OBJ_SCRIPT: return "Script";
case OBJ_FUNC: return "Func";
case OBJ_FIBER: return "Fiber";
case OBJ_USER: return "UserObj";
case OBJ_CLASS: return "Class";
case OBJ_INST: return "Inst";
}
UNREACHABLE();
}
const char* varTypeName(Var v) {
if (IS_NULL(v)) return "null";
if (IS_BOOL(v)) return "bool";
if (IS_NUM(v)) return "number";
if (IS_NULL(v)) return "Null";
if (IS_BOOL(v)) return "Bool";
if (IS_NUM(v)) return "Number";
ASSERT(IS_OBJ(v), OOPS);
Object* obj = AS_OBJ(v);
@ -1299,9 +1415,40 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
return;
}
case OBJ_USER: {
// TODO:
pkByteBufferAddString(buff, vm, "[UserObj]", 9);
case OBJ_CLASS: {
const Class* ty = (const Class*)obj;
pkByteBufferAddString(buff, vm, "[Class:", 7);
String* ty_name = ty->owner->names.data[ty->name];
pkByteBufferAddString(buff, vm, ty_name->data, ty_name->length);
pkByteBufferWrite(buff, vm, ']');
return;
}
case OBJ_INST:
{
const Instance* inst = (const Instance*)obj;
pkByteBufferWrite(buff, vm, '[');
pkByteBufferAddString(buff, vm, inst->name,
(uint32_t)strlen(inst->name));
if (!inst->is_native) {
const Class* ty = inst->ins->type;
const Inst* ins = inst->ins;
ASSERT(ins->fields.count == ty->field_names.count, OOPS);
pkByteBufferWrite(buff, vm, ':');
for (uint32_t i = 0; i < ty->field_names.count; i++) {
if (i != 0) pkByteBufferWrite(buff, vm, ',');
pkByteBufferWrite(buff, vm, ' ');
String* f_name = ty->owner->names.data[ty->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);
}
}
pkByteBufferWrite(buff, vm, ']');
return;
}
}
@ -1351,7 +1498,8 @@ bool toBool(Var v) {
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
case OBJ_CLASS:
case OBJ_INST:
return true;
}

View File

@ -187,6 +187,8 @@ typedef struct Range Range;
typedef struct Script Script;
typedef struct Function Function;
typedef struct Fiber Fiber;
typedef struct Class Class;
typedef struct Instance Instance;
// Declaration of buffer objects of different types.
DECLARE_BUFFER(Uint, uint32_t)
@ -194,6 +196,7 @@ DECLARE_BUFFER(Byte, uint8_t)
DECLARE_BUFFER(Var, Var)
DECLARE_BUFFER(String, String*)
DECLARE_BUFFER(Function, Function*)
DECLARE_BUFFER(Class, Class*)
// 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
@ -209,16 +212,14 @@ typedef enum {
OBJ_SCRIPT,
OBJ_FUNC,
OBJ_FIBER,
// TODO:
OBJ_USER,
OBJ_CLASS,
OBJ_INST,
} ObjectType;
// Base struct for all heap allocated objects.
struct Object {
ObjectType type; //< Type of the object in \ref var_Object_Type.
bool is_marked; //< Marked when garbage collection's marking phase.
//Class* is; //< The class the object IS. // No OOP in PK.
Object* next; //< Next object in the heap allocated link list.
};
@ -269,24 +270,11 @@ struct Script {
String* module; //< Module name of the script.
String* path; //< Path of the script.
/*
names: ["v1", "fn1", "v2", "fn2", ...]
0 1 2 3
g_names: [ 1, 3 ] <-- function name
0 1 <-- it's index
globals: [ fn1, fn2 ]
0 1
*/
// TODO: (maybe) join the function buffer and variable buffers.
// and make if possible to override functions. (also have to allow builtin).
pkVarBuffer globals; //< Script level global variables.
pkUintBuffer global_names; //< Name map to index in globals.
pkFunctionBuffer functions; //< Script level functions.
pkFunctionBuffer functions; //< Functions of the script.
pkClassBuffer classes; //< Classes of the script.
pkStringBuffer names; //< Name literals, attribute names, etc.
pkVarBuffer literals; //< Script literal constant values.
@ -369,6 +357,33 @@ struct Fiber {
String* error;
};
struct Class {
Object _super;
Script* owner; //< The script it belongs to.
uint32_t name; //< Index of the type's name in the script's name buffer.
Function* ctor; //< The constructor function.
pkUintBuffer field_names; //< Buffer of field names.
// TODO: ordered names buffer for binary search.
};
typedef struct {
Class* type; //< Class this instance belongs to.
pkVarBuffer fields; //< Var buffer of the instance.
} Inst;
struct Instance {
Object _super;
const char* name; //< Name of the type it belongs to.
bool is_native; //< True if it's a native type instance.
union {
void* native; //< C struct pointer. // TODO:
Inst* ins; //< Module instance pointer.
};
};
/*****************************************************************************/
/* "CONSTRUCTORS" */
/*****************************************************************************/
@ -392,7 +407,7 @@ String* newStringLength(PKVM* vm, const char* text, uint32_t length);
#else // Macro implementation.
// Allocate new string using the cstring [text].
#define newString(vm, text) \
newStringLength(vm, text, (text == NULL) ? 0 : (uint32_t)strlen(text))
newStringLength(vm, text, (!text) ? 0 : (uint32_t)strlen(text))
#endif
// Allocate new List and return List*.
@ -419,6 +434,14 @@ Function* newFunction(PKVM* vm, const char* name, int length, Script* owner,
// Allocate new Fiber object around the function [fn] and return Fiber*.
Fiber* newFiber(PKVM* vm, Function* fn);
// Allocate new Class object and return Class* with name [name].
Class* newClass(PKVM* vm, Script* scr, const char* name, uint32_t length);
// 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* ty, bool initialize);
/*****************************************************************************/
/* METHODS */
/*****************************************************************************/
@ -520,13 +543,17 @@ bool fiberHasError(Fiber* fiber);
uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
uint32_t length);
// Search for the type name in the script and return it's index in it's
// [classes] buffer. If not found returns -1.
int scriptGetClass(Script* script, const char* name, uint32_t length);
// Search for the function name in the script and return it's index in it's
// [functions] buffer. If not found returns -1.
uint32_t scriptGetFunc(Script* script, const char* name, uint32_t length);
int scriptGetFunc(Script* script, const char* name, uint32_t length);
// Search for the global variable name in the script and return it's index in
// it's [globals] buffer. If not found returns -1.
uint32_t scriptGetGlobals(Script* script, const char* name, uint32_t length);
int scriptGetGlobals(Script* script, const char* name, uint32_t length);
// Add a global [value] to the [scrpt] and return its index.
uint32_t scriptAddGlobal(PKVM* vm, Script* script,

View File

@ -755,6 +755,15 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
DISPATCH();
}
OPCODE(PUSH_INSTANCE):
{
uint8_t index = READ_BYTE();
ASSERT_INDEX(index, script->classes.count);
Instance* inst = newInstance(vm, script->classes.data[index], false);
PUSH(VAR_OBJ(inst));
DISPATCH();
}
OPCODE(LIST_APPEND):
{
Var elem = PEEK(-1); // Don't pop yet, we need the reference for gc.
@ -785,6 +794,21 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
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):
@ -830,7 +854,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
OPCODE(PUSH_GLOBAL):
{
uint8_t index = READ_BYTE();
ASSERT(index < script->globals.count, OOPS);
ASSERT_INDEX(index, script->globals.count);
PUSH(script->globals.data[index]);
DISPATCH();
}
@ -838,7 +862,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
OPCODE(STORE_GLOBAL):
{
uint8_t index = READ_BYTE();
ASSERT(index < script->globals.count, OOPS);
ASSERT_INDEX(index, script->globals.count);
script->globals.data[index] = PEEK(-1);
DISPATCH();
}
@ -846,12 +870,21 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
OPCODE(PUSH_FN):
{
uint8_t index = READ_BYTE();
ASSERT(index < script->functions.count, OOPS);
ASSERT_INDEX(index, script->functions.count);
Function* fn = script->functions.data[index];
PUSH(VAR_OBJ(fn));
DISPATCH();
}
OPCODE(PUSH_TYPE):
{
uint8_t index = READ_BYTE();
ASSERT_INDEX(index, script->classes.count);
Class* ty = script->classes.data[index];
PUSH(VAR_OBJ(ty));
DISPATCH();
}
OPCODE(PUSH_BUILTIN_FN):
{
uint8_t index = READ_BYTE();
@ -890,8 +923,22 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
Fiber* call_fiber = vm->fiber;
Var* callable = call_fiber->sp - argc - 1;
const Function* fn = NULL;
if (IS_OBJ_TYPE(*callable, OBJ_FUNC)) {
const Function* fn = (const Function*)AS_OBJ(*callable);
fn = (const Function*)AS_OBJ(*callable);
} else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) {
fn = (const Function*)((Class*)AS_OBJ(*callable))->ctor;
} else {
RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a function in "
"call, instead got",
varTypeName(*callable), toString(vm, *callable)));
DISPATCH();
}
// If we reached here it's a valid callable.
// -1 argument means multiple number of args.
if (fn->arity != -1 && fn->arity != argc) {
@ -945,11 +992,6 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
}
}
} else {
RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a function in "
"call, instead got", varTypeName(*callable), toString(vm, *callable)));
}
DISPATCH();
}
@ -1051,7 +1093,8 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
case OBJ_CLASS:
case OBJ_INST:
TODO; break;
default:
UNREACHABLE();
@ -1433,6 +1476,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
OPCODE(IN):
// TODO: Implement bool varContaines(vm, on, value);
TODO;
UNREACHABLE();
OPCODE(REPL_PRINT):
{
@ -1447,7 +1491,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
}
OPCODE(END):
TODO;
UNREACHABLE();
break;
default:
@ -1455,5 +1499,5 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
}
return PK_RESULT_SUCCESS;
UNREACHABLE(); //return PK_RESULT_SUCCESS;
}

36
tests/lang/class.pk Normal file
View File

@ -0,0 +1,36 @@
## TODO: Implement ctor with va arg to
## initialize, fields.
class _Vec
x = 0
y = 0
end
def Vec(x, y)
ret = _Vec()
ret.x = x; ret.y = y
return ret
end
def vecAdd(v1, v2)
return Vec(v1.x + v2.x,
v1.y + v2.y)
end
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 = vecAdd(v1, v2)
assert(v3.x == 4 and v3.y == 6)
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

@ -13,6 +13,10 @@ import "basics.pk" ## will import all
import "controlflow.pk" as if_test
from "functions.pk" import fn1, fn2 as f2, fn3
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')

View File

@ -16,6 +16,7 @@ THIS_PATH = abspath(dirname(__file__))
TEST_SUITE = {
"Unit Tests": (
"lang/basics.pk",
"lang/class.pk",
"lang/core.pk",
"lang/controlflow.pk",
"lang/fibers.pk",