Skip to content

Commit

Permalink
* add array of magic methods to class struct.
Browse files Browse the repository at this point in the history
* current magic methods: _init, _getter, _setter, _str, _repr, _call.
* optimize method calling for magic methods.
* add _call so that object can be callable.
* add Object.getattr and Object.setattr so that attribs can be accessed via
  dynamic name.
* getter/setter can be called in script, no only in native code.
  • Loading branch information
khchen committed Jul 21, 2022
1 parent e316d53 commit 7a736f1
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 97 deletions.
2 changes: 1 addition & 1 deletion src/core/compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -2802,7 +2802,7 @@ static void compileFunction(Compiler* compiler, FuncType fn_type) {
global_index = compilerAddVariable(compiler, name, name_length, name_line);
}

if (fn_type == FUNC_METHOD && strncmp(name, CTOR_NAME, name_length) == 0) {
if (fn_type == FUNC_METHOD && strncmp(name, LITS__init, name_length) == 0) {
fn_type = FUNC_CONSTRUCTOR;
}

Expand Down
123 changes: 88 additions & 35 deletions src/core/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,15 @@ String* varToString(PKVM* vm, Var self, bool repr) {
// from GC).
Closure* closure = NULL;

bool has_method = false;
if (!repr) {
String* name = newString(vm, LITS__str); // TODO: static vm string?.
vmPushTempRef(vm, &name->_super); // name.
has_method = hasMethod(vm, self, name, &closure);
vmPopTempRef(vm); // name.
closure = getMagicMethod(getClass(vm, self), METHOD_STR);
}

if (!has_method) {
String* name = newString(vm, LITS__repr); // TODO: static vm string?.
vmPushTempRef(vm, &name->_super); // name.
has_method = hasMethod(vm, self, name, &closure);
vmPopTempRef(vm); // name.
if (closure == NULL) {
closure = getMagicMethod(getClass(vm, self), METHOD_REPR);
}

if (has_method) {
if (closure != NULL) {
Var ret = VAR_NULL;
PkResult result = vmCallMethod(vm, self, closure, 0, NULL, &ret);
if (result != PK_RESULT_SUCCESS) return NULL;
Expand Down Expand Up @@ -976,6 +969,32 @@ DEF(_objRepr,
RET(VAR_OBJ(toRepr(vm, SELF)));
}

DEF(_objGetattr,
"Object.getattr(name:String[, skipGetter: bool]) -> Var",
"Returns the value of the named attribute of an object.") {

if (!pkCheckArgcRange(vm, ARGC, 1, 2)) return;

String* name;
if (!validateArgString(vm, 1, &name)) return;

bool skipGetter = (ARGC >= 2 ? toBool(ARG(2)) : false);
RET(varGetAttrib(vm, SELF, name, skipGetter));
}

DEF(_objSetattr,
"Object.setattr(name:String, value:Var[, skipSetter: bool]) -> Null",
"Sets the value of the attribute of an object.") {

if (!pkCheckArgcRange(vm, ARGC, 2, 3)) return;

String* name;
if (!validateArgString(vm, 1, &name)) return;

bool skipSetter = (ARGC >= 3 ? toBool(ARG(3)) : false);
varSetAttrib(vm, SELF, name, ARG(2), skipSetter);
}

DEF(_numberTimes,
"Number.times(f:Closure)",
"Iterate the function [f] n times. Here n is the integral value of the "
Expand Down Expand Up @@ -1422,7 +1441,8 @@ static void initializePrimitiveClasses(PKVM* vm) {
fn->native = ptr; \
fn->arity = arity_; \
vmPushTempRef(vm, &fn->_super); /* fn. */ \
vm->builtin_classes[type]->ctor = newClosure(vm, fn); \
vm->builtin_classes[type]->magic_methods[METHOD_INIT] = \
newClosure(vm, fn); \
vmPopTempRef(vm); /* fn. */ \
} while (false)

Expand Down Expand Up @@ -1453,6 +1473,9 @@ static void initializePrimitiveClasses(PKVM* vm) {
ADD_METHOD(PK_OBJECT, "typename", _objTypeName, 0);
ADD_METHOD(PK_OBJECT, "_repr", _objRepr, 0);

ADD_METHOD(PK_OBJECT, "getattr", _objGetattr, -1);
ADD_METHOD(PK_OBJECT, "setattr", _objSetattr, -1);

ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1);
ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0);
ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0);
Expand Down Expand Up @@ -1536,6 +1559,49 @@ Var preConstructSelf(PKVM* vm, Class* cls) {
return VAR_NULL;
}

void bindMethod(PKVM* vm, Class* cls, Closure* method) {
// TODO: check hash instead of using strcmp?
if (strcmp(method->fn->name, LITS__init) == 0) {
cls->magic_methods[METHOD_INIT] = method;
} else if (strcmp(method->fn->name, LITS__str) == 0) {
cls->magic_methods[METHOD_STR] = method;
} else if (strcmp(method->fn->name, LITS__repr) == 0) {
cls->magic_methods[METHOD_REPR] = method;
} else if (strcmp(method->fn->name, LITS__getter) == 0) {
cls->magic_methods[METHOD_GETTER] = method;
} else if (strcmp(method->fn->name, LITS__setter) == 0) {
cls->magic_methods[METHOD_SETTER] = method;
} else if (strcmp(method->fn->name, LITS__call) == 0) {
cls->magic_methods[METHOD_CALL] = method;
}

pkClosureBufferWrite(&cls->methods, vm, method);
}

Closure* getMagicMethod(Class* cls, MagicMethod m) {
ASSERT(cls != NULL, OOPS);

// magic method
// -1: find the method from ancestor
// NULL: not found and don't find again
if (cls->magic_methods[m] == (Closure*) -1) {
cls->magic_methods[m] = NULL;

Class* super = cls->super_class;
while (super != NULL) {
if (super->magic_methods[m] != NULL &&
super->magic_methods[m] != (Closure*) -1) {

cls->magic_methods[m] = super->magic_methods[m];
break;
}
super = super->super_class;
}
}
// printf("%d %p\n", m, cls->magic_methods[m]);
return cls->magic_methods[m];
}

Class* getClass(PKVM* vm, Var instance) {
PkVarType type = getVarType(instance);
if (0 <= type && type < PK_INSTANCE) {
Expand Down Expand Up @@ -1587,7 +1653,7 @@ Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) {

// If the attribute not found it'll set an error.
if (is_method) *is_method = false;
return varGetAttrib(vm, self, name);
return varGetAttrib(vm, self, name, false);
}

Closure* getSuperMethod(PKVM* vm, Var self, String* name) {
Expand Down Expand Up @@ -1961,7 +2027,7 @@ bool varIsType(PKVM* vm, Var inst, Var type) {
return false;
}

Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
Var varGetAttrib(PKVM* vm, Var on, String* attrib, bool skipGetter) {

#define ERR_NO_ATTRIB(vm, on, attrib) \
VM_SET_ERROR(vm, stringFormat(vm, "'$' object has no attribute named '$'.", \
Expand Down Expand Up @@ -2130,16 +2196,9 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
Instance* inst = (Instance*)obj;
Var value = VAR_NULL;

if (inst->native != NULL) {

Closure* getter;
// TODO: static vm string?
String* getter_name = newString(vm, GETTER_NAME);
vmPushTempRef(vm, &getter_name->_super); // getter_name.
bool has_getter = hasMethod(vm, on, getter_name, &getter);
vmPopTempRef(vm); // getter_name.

if (has_getter) {
if (!skipGetter) {
Closure* getter = getMagicMethod(inst->cls, METHOD_GETTER);
if (getter != NULL) {
Var attrib_name = VAR_OBJ(attrib);
vmCallMethod(vm, on, getter, 1, &attrib_name, &value);
return value; // If any error occure, it was already set.
Expand All @@ -2165,7 +2224,8 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
#undef ERR_NO_ATTRIB
}

void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value, bool skipSetter)
{

// Set error for accessing non-existed attribute.
#define ERR_NO_ATTRIB(vm, on, attrib) \
Expand Down Expand Up @@ -2197,18 +2257,11 @@ void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
}

case OBJ_INST: {

Instance* inst = (Instance*)obj;
if (inst->native != NULL) {
Closure* setter;
// TODO: static vm string?
String* setter_name = newString(vm, SETTER_NAME);
vmPushTempRef(vm, &setter_name->_super); // setter_name.
bool has_setter = hasMethod(vm, VAR_OBJ(inst), setter_name, &setter);
vmPopTempRef(vm); // setter_name.

if (has_setter) {

if (!skipSetter) {
Closure* setter = getMagicMethod(inst->cls, METHOD_SETTER);
if (setter != NULL) {
// FIXME:
// Once we retreive values from directly the stack we can pass the
// args pointer, pointing in the VM stack, instead of creating a temp
Expand Down
22 changes: 11 additions & 11 deletions src/core/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
#define LITS__init "_init"
#define LITS__str "_str"
#define LITS__repr "_repr"
#define LITS__getter "_getter"
#define LITS__setter "_setter"
#define LITS__call "_call"

// Functions, methods, classes and other names which are intrenal / special to
// pocketlang are starts with the following character (ex: @main, @literalFn).
Expand All @@ -33,15 +36,6 @@
// they're uniquely identified by their index in the script's function buffer.
#define LITERAL_FN_NAME "@func"

// Name of a constructor function.
#define CTOR_NAME LITS__init

// Getter/Setter method names used by the native instance to get/ set value.
// Script instance's values doesn't support methods but they use vanila
// '.attrib', '.attrib=' operators.
#define GETTER_NAME "@getter"
#define SETTER_NAME "@setter"

// Initialize core language, builtin function and core libs.
void initializeCore(PKVM* vm);

Expand Down Expand Up @@ -78,6 +72,12 @@ void moduleAddFunctionInternal(PKVM* vm, Module* module,
// For other classes the return value will be an Instance.
Var preConstructSelf(PKVM* vm, Class* cls);

// Bind a method to a class and deal with magic methods.
void bindMethod(PKVM* vm, Class* cls, Closure* method);

// Get the specified magic method or NULL. Cache the method if possible.
Closure* getMagicMethod(Class* cls, MagicMethod mm);

// Returns the class of the [instance].
Class* getClass(PKVM* vm, Var instance);

Expand Down Expand Up @@ -137,11 +137,11 @@ bool varContains(PKVM* vm, Var elem, Var container);
bool varIsType(PKVM* vm, Var inst, Var type);

// Returns the attribute named [attrib] on the variable [on].
Var varGetAttrib(PKVM* vm, Var on, String* attrib);
Var varGetAttrib(PKVM* vm, Var on, String* attrib, bool skipGetter);

// Set the attribute named [attrib] on the variable [on] with the given
// [value].
void varSetAttrib(PKVM* vm, Var on, String* name, Var value);
void varSetAttrib(PKVM* vm, Var on, String* name, Var value, bool skipSetter);

// Returns the subscript value (ie. on[key]).
Var varGetSubscript(PKVM* vm, Var on, Var key);
Expand Down
19 changes: 5 additions & 14 deletions src/core/public.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,9 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls,

Closure* method = newClosure(vm, fn);
vmPopTempRef(vm); // fn.

vmPushTempRef(vm, &method->_super); // method.
{
pkClosureBufferWrite(&class_->methods, vm, method);
if (!strcmp(name, CTOR_NAME)) class_->ctor = method;
}
bindMethod(vm, class_, method);
vmPopTempRef(vm); // method.
}

Expand Down Expand Up @@ -457,7 +455,6 @@ static inline bool isStringEmpty(const char* line) {
// This function will get the main function from the module to run it in the
// repl mode.
Closure* moduleGetMainFunction(PKVM* vm, Module* module) {

int main_index = moduleGetGlobalIndex(module, IMPLICIT_MAIN_NAME,
(uint32_t) strlen(IMPLICIT_MAIN_NAME));
if (main_index == -1) return NULL;
Expand Down Expand Up @@ -842,7 +839,7 @@ bool pkSetAttribute(PKVM* vm, int instance, const char* name, int value) {

String* sname = newString(vm, name);
vmPushTempRef(vm, &sname->_super); // sname.
varSetAttrib(vm, SLOT(instance), sname, SLOT(value));
varSetAttrib(vm, SLOT(instance), sname, SLOT(value), false);
vmPopTempRef(vm); // sname.

return !VM_HAS_ERROR(vm);
Expand All @@ -857,7 +854,7 @@ bool pkGetAttribute(PKVM* vm, int instance, const char* name,

String* sname = newString(vm, name);
vmPushTempRef(vm, &sname->_super); // sname.
SET_SLOT(index, varGetAttrib(vm, SLOT(instance), sname));
SET_SLOT(index, varGetAttrib(vm, SLOT(instance), sname, false));
vmPopTempRef(vm); // sname.

return !VM_HAS_ERROR(vm);
Expand All @@ -869,13 +866,7 @@ static Var _newInstance(PKVM* vm, Class* cls, int argc, Var* argv) {

if (IS_OBJ(instance)) vmPushTempRef(vm, AS_OBJ(instance)); // instance.

Closure* ctor = cls->ctor;
while (ctor == NULL) {
cls = cls->super_class;
if (cls == NULL) break;
ctor = cls->ctor;
}

Closure* ctor = getMagicMethod(cls, METHOD_INIT);
if (ctor != NULL) vmCallMethod(vm, instance, ctor, argc, argv, NULL);
if (IS_OBJ(instance)) vmPopTempRef(vm); // instance.

Expand Down
7 changes: 6 additions & 1 deletion src/core/value.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
Class* cls = (Class*)obj;
vm->bytes_allocated += sizeof(Class);
markObject(vm, &cls->owner->_super);
markObject(vm, &cls->ctor->_super);
markObject(vm, &cls->name->_super);
markObject(vm, &cls->static_attribs->_super);
// don't need to mark magic_methods, they are all in cls->methods.

This comment has been minimized.

Copy link
@rdbyk

rdbyk Jan 25, 2024

Is that really true, even for builtin classes like List?

I am trying to use this magic_methods stuff and I am experiencing
problems (crashes) ...

Any comments would be highly appreciated.


markClosureBuffer(vm, &cls->methods);
vm->bytes_allocated += sizeof(Closure) * cls->methods.capacity;
Expand Down Expand Up @@ -527,6 +527,11 @@ Class* newClass(PKVM* vm, const char* name, int length,
cls->super_class = super;
cls->docstring = docstring;

// Initialize to -1 as undefined
for (int i = 0; i < MAX_MAGIC_METHODS; i++) {
cls->magic_methods[i] = (Closure*)-1;
}

// Builtin types doesn't belongs to a module.
if (module != NULL) {
cls->name = moduleAddString(module, vm, name, length, NULL);
Expand Down
14 changes: 12 additions & 2 deletions src/core/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,16 @@ struct Fiber {
String* error;
};

typedef enum {
METHOD_INIT,
METHOD_STR,
METHOD_REPR,
METHOD_GETTER,
METHOD_SETTER,
METHOD_CALL,
MAX_MAGIC_METHODS,
} MagicMethod;

struct Class {
Object _super;

Expand All @@ -538,7 +548,8 @@ struct Class {
// builtin type's class.
PkVarType class_of;

Closure* ctor; //< The constructor function.
// Magic methods, ctor/getter/setter etc.
Closure* magic_methods[MAX_MAGIC_METHODS];

// A buffer of methods of the class.
pkClosureBuffer methods;
Expand All @@ -550,7 +561,6 @@ struct Class {
// For script/ builtin types it'll be NULL.
pkNewInstanceFn new_fn;
pkDeleteInstanceFn delete_fn;

};

typedef struct {
Expand Down
Loading

1 comment on commit 7a736f1

@rdbyk
Copy link

@rdbyk rdbyk commented on 7a736f1 Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have a look at my comment 7a736f1#r137787262

Thanks

Please sign in to comment.