Class fields and access to class member methods

This commit is contained in:
K. Lange 2021-01-11 16:31:34 +09:00
parent 3141678fa0
commit eb27158173
11 changed files with 129 additions and 7 deletions

View File

@ -326,6 +326,22 @@ o.printFoo()
_**Note:** As `self` is implicit, it can not be renamed; other argument names listed in a method signature will refer to additional arguments._
Classes can also define fields, which can be accessed from the class or through an instance.
```py
class Foo():
bar = "baz"
def printBar(self):
print(self.bar)
let o = Foo()
o.printBar()
# → baz
print(Foo.bar)
# → baz
```
_**Note:** Instances receive a copy of their class's fields upon creation. If a class field is mutable, the instance's copy will refer to the same underlying object. Assignments to the instance's copy of a field will refer to a new object. If a new field is added to a class after instances have been created, the existing instances will not be able to reference the new field._
When a class is instantiated, if it has an `__init__` method it will be called automatically. `__init__` may take arguments as well.
```py

View File

@ -862,6 +862,16 @@ static void method(size_t blockWidth) {
* as a redundant thing, just to make more Python stuff work with changes. */
if (check(TOKEN_AT)) {
decorator(0, TYPE_METHOD);
} else if (match(TOKEN_IDENTIFIER)) {
emitBytes(OP_DUP, 0); /* SET_PROPERTY will pop class */
size_t ind = identifierConstant(&parser.previous);
consume(TOKEN_EQUAL, "Class field must have value.");
expression();
EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind);
emitByte(OP_POP); /* Value of expression replaces dup of class*/
if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) {
errorAtCurrent("Expected end of line after class attribut declaration");
}
} else {
consume(TOKEN_DEF, "expected a definition, got nothing");
consume(TOKEN_IDENTIFIER, "expected method name");

View File

@ -114,6 +114,7 @@ KrkClass * krk_newClass(KrkString * name) {
_class->docstring = NULL;
_class->base = NULL;
krk_initTable(&_class->methods);
krk_initTable(&_class->fields);
_class->_getter = NULL;
_class->_setter = NULL;
@ -131,6 +132,7 @@ KrkInstance * krk_newInstance(KrkClass * _class) {
KrkInstance * instance = ALLOCATE_OBJECT(KrkInstance, OBJ_INSTANCE);
instance->_class = _class;
krk_initTable(&instance->fields);
krk_tableAddAll(&_class->fields, &instance->fields);
instance->_internal = NULL; /* To be used by C-defined types to track internal objects. */
return instance;
}

View File

@ -100,6 +100,7 @@ typedef struct KrkClass {
KrkString * docstring;
struct KrkClass * base;
KrkTable methods;
KrkTable fields;
/* Quick access for common stuff */
KrkObj * _getter;

View File

@ -288,6 +288,7 @@ KrkValue krk_module_onload_fileio(void) {
krk_attachNamedObject(&module->fields,chr_File,(KrkObj*)FileClass);
/* Inherit from object */
krk_tableAddAll(&vm.objectClass->methods, &FileClass->methods);
krk_tableAddAll(&vm.objectClass->fields, &FileClass->fields);
krk_pop();
krk_pop();

View File

@ -56,6 +56,7 @@ static void _loadEnviron(KrkInstance * module) {
KrkValue dictClass;
krk_tableGet(&vm.builtins->fields,OBJECT_VAL(S("dict")), &dictClass);
krk_tableAddAll(&AS_CLASS(dictClass)->methods, &environClass->methods);
krk_tableAddAll(&AS_CLASS(dictClass)->fields, &environClass->fields);
environClass->base = AS_CLASS(dictClass);
/* Add our set method that should also call dict's set method */

27
test/testClassFields.krk Normal file
View File

@ -0,0 +1,27 @@
class Foo():
a = None
b = 42
c = "string"
l = []
def test(self):
return (self.a,self.b,self.c,self.l)
class Bar(Foo):
c = 96
let f = Foo()
let b = Foo()
def testAll():
print(Foo.test(Foo), f.test(), b.test(), Bar.test(Bar))
testAll()
f.a = 42
testAll()
f.l.append("hi")
testAll()
f.l = []
testAll()
f.l.append("bacon")
testAll()
b.b = "derp"
testAll()

View File

@ -0,0 +1,6 @@
(None, 42, 'string', []) (None, 42, 'string', []) (None, 42, 'string', []) (None, 42, 96, [])
(None, 42, 'string', []) (42, 42, 'string', []) (None, 42, 'string', []) (None, 42, 96, [])
(None, 42, 'string', ['hi']) (42, 42, 'string', ['hi']) (None, 42, 'string', ['hi']) (None, 42, 96, ['hi'])
(None, 42, 'string', ['hi']) (42, 42, 'string', []) (None, 42, 'string', ['hi']) (None, 42, 96, ['hi'])
(None, 42, 'string', ['hi']) (42, 42, 'string', ['bacon']) (None, 42, 'string', ['hi']) (None, 42, 96, ['hi'])
(None, 42, 'string', ['hi']) (42, 42, 'string', ['bacon']) (None, 'derp', 'string', ['hi']) (None, 42, 96, ['hi'])

23
test/testClassMethod.krk Normal file
View File

@ -0,0 +1,23 @@
def staticmethod(func):
def _wrapper(*args,**kwargs):
return func(None,*args,**kwargs)
return _wrapper
class Foo():
def amethod():
print("If this were Python, I'd be impossible to call.")
@staticmethod
def astatic():
print("Since `None` is always implicit, it's been set by @staticmethod here to None:", self)
# This doesn't work because amethod is unbound and needs an instance.
try:
Foo.amethod()
except:
print(exception.arg)
# This works
Foo.amethod(Foo())
# This should too?
Foo.astatic()

View File

@ -0,0 +1,3 @@
amethod() takes exactly 1 argument (0 given)
If this were Python, I'd be impossible to call.
Since `None` is always implicit, it's been set by @staticmethod here to None: None

46
vm.c
View File

@ -733,6 +733,21 @@ KrkValue krk_dirObject(int argc, KrkValue argv[]) {
}
}
} else {
if (IS_CLASS(argv[0])) {
KrkClass * _class = AS_CLASS(argv[0]);
for (size_t i = 0; i < _class->methods.capacity; ++i) {
if (_class->methods.entries[i].key.type != VAL_KWARGS) {
krk_writeValueArray(AS_LIST(_list_internal),
_class->methods.entries[i].key);
}
}
for (size_t i = 0; i < _class->fields.capacity; ++i) {
if (_class->fields.entries[i].key.type != VAL_KWARGS) {
krk_writeValueArray(AS_LIST(_list_internal),
_class->fields.entries[i].key);
}
}
}
KrkClass * type = AS_CLASS(krk_typeOf(1, (KrkValue[]){argv[0]}));
for (size_t i = 0; i < type->methods.capacity; ++i) {
@ -1368,6 +1383,7 @@ static KrkValue _string_init(int argc, KrkValue argv[]) {
krk_attachNamedObject(&vm.builtins->fields, name, (KrkObj*)obj); \
obj->base = baseClass; \
krk_tableAddAll(&baseClass->methods, &obj->methods); \
krk_tableAddAll(&baseClass->fields, &obj->fields); \
} while (0)
#define ADD_EXCEPTION_CLASS(obj, name, baseClass) do { \
@ -1375,6 +1391,7 @@ static KrkValue _string_init(int argc, KrkValue argv[]) {
krk_attachNamedObject(&vm.builtins->fields, name, (KrkObj*)obj); \
obj->base = baseClass; \
krk_tableAddAll(&baseClass->methods, &obj->methods); \
krk_tableAddAll(&baseClass->fields, &obj->fields); \
} while (0)
#define BUILTIN_FUNCTION(name, func) do { \
@ -2680,6 +2697,7 @@ void krk_initVM(int flags) {
vm.moduleClass = krk_newClass(S("module"));
vm.moduleClass->base = vm.objectClass;
krk_tableAddAll(&vm.objectClass->methods, &vm.moduleClass->methods);
krk_tableAddAll(&vm.objectClass->fields, &vm.moduleClass->fields);
/* Attach new repr/str */
krk_defineNative(&vm.moduleClass->methods, ".__repr__", _module_repr);
@ -3240,15 +3258,24 @@ int krk_loadModule(KrkString * name, KrkValue * moduleOut) {
*/
static int valueGetProperty(KrkString * name) {
KrkClass * objectClass;
KrkValue value;
if (IS_INSTANCE(krk_peek(0))) {
KrkInstance * instance = AS_INSTANCE(krk_peek(0));
KrkValue value;
if (krk_tableGet(&instance->fields, OBJECT_VAL(name), &value)) {
krk_pop();
krk_push(value);
return 1;
}
objectClass = instance->_class;
} else if (IS_CLASS(krk_peek(0))) {
KrkClass * _class = AS_CLASS(krk_peek(0));
if (krk_tableGet(&_class->fields, OBJECT_VAL(name), &value) ||
krk_tableGet(&_class->methods, OBJECT_VAL(name), &value)) {
krk_pop();
krk_push(value);
return 1;
}
objectClass = AS_CLASS(krk_typeOf(1, (KrkValue[]){krk_peek(0)}));
} else {
objectClass = AS_CLASS(krk_typeOf(1, (KrkValue[]){krk_peek(0)}));
}
@ -3570,6 +3597,7 @@ static KrkValue run() {
_class->filename = frame->closure->function->chunk.filename;
_class->base = vm.objectClass;
krk_tableAddAll(&vm.objectClass->methods, &_class->methods);
krk_tableAddAll(&vm.objectClass->fields, &_class->fields);
break;
}
case OP_GET_PROPERTY_LONG:
@ -3615,15 +3643,18 @@ static KrkValue run() {
case OP_SET_PROPERTY_LONG:
case OP_SET_PROPERTY: {
KrkString * name = READ_STRING(operandWidth);
if (!IS_INSTANCE(krk_peek(1))) {
if (IS_INSTANCE(krk_peek(1))) {
KrkInstance * instance = AS_INSTANCE(krk_peek(1));
krk_tableSet(&instance->fields, OBJECT_VAL(name), krk_peek(0));
} else if (IS_CLASS(krk_peek(1))) {
KrkClass * _class = AS_CLASS(krk_peek(1));
krk_tableSet(&_class->fields, OBJECT_VAL(name), krk_peek(0));
} else {
krk_runtimeError(vm.exceptions.attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars);
goto _finishException;
}
KrkInstance * instance = AS_INSTANCE(krk_peek(1));
krk_tableSet(&instance->fields, OBJECT_VAL(name), krk_peek(0));
KrkValue value = krk_pop();
krk_pop(); /* instance */
krk_push(value); /* Moves value in */
krk_swap(1);
krk_pop();
break;
}
case OP_METHOD_LONG:
@ -3651,6 +3682,7 @@ static KrkValue run() {
KrkClass * subclass = AS_CLASS(krk_peek(0));
subclass->base = AS_CLASS(superclass);
krk_tableAddAll(&AS_CLASS(superclass)->methods, &subclass->methods);
krk_tableAddAll(&AS_CLASS(superclass)->fields, &subclass->fields);
krk_pop();
break;
}