class compilation implementation

This commit is contained in:
Thakee Nathees 2022-04-21 17:23:03 +05:30
parent 1951b3d5f7
commit 179026294d
6 changed files with 203 additions and 82 deletions

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;
}
@ -1444,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);
@ -1469,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
@ -1534,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,
@ -1698,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) {
@ -2035,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;
@ -2204,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;
@ -2318,14 +2357,12 @@ 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);
TODO; //< compileClass Function is in-compilete.
// 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;
@ -2336,23 +2373,58 @@ static void compileClass(Compiler* compiler) {
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);
// Compile all the methods and constructors.
TODO;
skipNewLines(compiler);
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);
consume(compiler, TK_END, "Expected 'end' after a class declaration end.");
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);
// TODO: check if the constructor or method already exists and report
// error. Make sure the error report line match the name token's line.
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.
}
// 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);
}
compiler->parser.parsing_class = false;
vmPopTempRef(_vm); // cls.
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;
@ -2367,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);
@ -2378,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.
@ -2422,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);
@ -2439,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);
@ -2449,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.
@ -3013,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
@ -3076,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);
@ -3130,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));

View File

@ -1289,8 +1289,10 @@ static void initializePrimitiveClasses(PKVM* vm) {
Class* super = NULL;
if (i != 0) super = vm->builtin_classes[PK_OBJECT];
const char* name = getPkVarTypeName((PkVarType)i);
vm->builtin_classes[i] = newClass(vm, name, (int)strlen(name),
super, NULL, NULL, NULL);
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_) \
@ -1347,46 +1349,41 @@ Var preConstructSelf(PKVM* vm, Class* cls) {
VM_SET_ERROR(vm, newString(vm, \
"Class '" type_name "' cannot be instanciated."))
for (int i = 0; i < PK_INSTANCE; i++) {
if (vm->builtin_classes[i] == cls) {
switch (cls->class_of) {
case PK_OBJECT:
NO_INSTANCE("Object");
return VAR_NULL;
switch ((PkVarType)i) {
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_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_MODULE:
NO_INSTANCE("Module");
return VAR_NULL;
case PK_CLOSURE:
NO_INSTANCE("Closure");
return VAR_NULL;
case PK_CLOSURE:
NO_INSTANCE("Closure");
return VAR_NULL;
case PK_FIBER:
return VAR_NULL;
case PK_FIBER:
return VAR_NULL;
case PK_CLASS:
NO_INSTANCE("Class");
return VAR_NULL;
case PK_CLASS:
NO_INSTANCE("Class");
return VAR_NULL;
case PK_INSTANCE:
UNREACHABLE();
return VAR_NULL;
}
}
case PK_INSTANCE:
return VAR_OBJ(newInstance(vm, cls));
}
return VAR_OBJ(newInstance(vm, cls));
UNREACHABLE();
return VAR_NULL;
}
Class* getClass(PKVM* vm, Var instance) {

View File

@ -288,7 +288,7 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
case OBJ_INST:
{
Instance* inst = (Instance*)obj;
markObject(vm, &inst->attribs._super);
markObject(vm, &inst->attribs->_super);
markObject(vm, &inst->cls->_super);
vm->bytes_allocated += sizeof(Instance);
} break;
@ -515,6 +515,7 @@ Class* newClass(PKVM* vm, const char* name, int length,
pkClosureBufferInit(&cls->methods);
cls->class_of = PK_INSTANCE;
cls->owner = NULL;
cls->super_class = super;
cls->docstring = NULL;
@ -538,28 +539,23 @@ Class* newClass(PKVM* vm, const char* name, int length,
Instance* newInstance(PKVM* vm, Class* cls) {
#ifdef DEBUG
bool _builtin_class = false;
for (int i = 0; i < PK_INSTANCE; i++) {
if (vm->builtin_classes[i] == cls) {
_builtin_class = true;
break;
}
}
ASSERT(!_builtin_class, "Cannot create an instace of builtin class "
"with newInstance() function.");
#endif // DEBUG
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->cls = cls;
inst->attribs = newMap(vm);
if (cls->new_fn != NULL) {
vmPushTempRef(vm, &inst->_super); // inst.
inst->native = cls->new_fn();
vmPopTempRef(vm); // inst.
} else {
inst->native = NULL;
}
vmPopTempRef(vm); // inst.
return inst;
}
@ -1170,10 +1166,10 @@ bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) {
ASSERT((inst != NULL) && (attrib != NULL) && (value != NULL), OOPS);
if (inst->native != NULL) {
TODO;
}
TODO;
Var value_ = mapGet(&inst->attribs, VAR_OBJ(attrib));
Var value_ = mapGet(inst->attribs, VAR_OBJ(attrib));
if (IS_UNDEF(value_)) return false;
*value = value_;
@ -1194,7 +1190,7 @@ bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) {
return true;
}
mapSet(vm, &inst->attribs, VAR_OBJ(attrib), value);
mapSet(vm, inst->attribs, VAR_OBJ(attrib), value);
return true;
}
@ -1591,9 +1587,10 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
{
const Instance* inst = (const Instance*)obj;
pkByteBufferWrite(buff, vm, '[');
pkByteBufferWrite(buff, vm, '\'');
pkByteBufferAddString(buff, vm, inst->cls->name->data,
inst->cls->name->length);
pkByteBufferWrite(buff, vm, ':');
pkByteBufferAddString(buff, vm, "' instance at ", 14);
char buff_addr[STR_HEX_BUFF_SIZE];
char* ptr = (char*)buff_addr;

View File

@ -501,6 +501,11 @@ 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.
// A buffer of methods of the class.
@ -531,7 +536,7 @@ struct Instance {
void* native;
// Dynamic attributes of an instance.
Map attribs;
Map* attribs;
};
/*****************************************************************************/

View File

@ -551,7 +551,7 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) {
frame->closure = closure;
frame->ip = closure->fn->fn->opcodes.data;
// Eat the self.
// Capture self.
frame->self = vm->fiber->self;
vm->fiber->self = VAR_UNDEFINED;
}
@ -567,7 +567,10 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) {
CallFrame* frame = fb->frames + fb->frame_count - 1;
frame->closure = closure;
frame->ip = closure->fn->fn->opcodes.data;
frame->self = VAR_UNDEFINED;
// Capture self.
frame->self = vm->fiber->self;
vm->fiber->self = VAR_UNDEFINED;
ASSERT(*frame->rbp == VAR_NULL, OOPS);

View File

@ -1,9 +1,33 @@
## Note that classes are being implemented and temproarly
## the classes cannot be compiled.
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
end
v1 = Vec2(1, 2); assert(v1.x == 1 and v1.y == 2)
print("v1 = ${v1.to_string()}")
v2 = Vec2(3, 4); assert(v2.x == 3 and v2.y == 4)
print("v2 = ${v2.to_string()}")
v3 = v1.add(v2); assert(v3.x == 4 and v3.y == 6)
print("v3 = ${v3.to_string()}")
print('ALL TESTS PASSED')