Class fields and access to class member methods
This commit is contained in:
parent
3141678fa0
commit
eb27158173
16
README.md
16
README.md
@ -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
|
||||
|
10
compiler.c
10
compiler.c
@ -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");
|
||||
|
2
object.c
2
object.c
@ -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;
|
||||
}
|
||||
|
1
object.h
1
object.h
@ -100,6 +100,7 @@ typedef struct KrkClass {
|
||||
KrkString * docstring;
|
||||
struct KrkClass * base;
|
||||
KrkTable methods;
|
||||
KrkTable fields;
|
||||
|
||||
/* Quick access for common stuff */
|
||||
KrkObj * _getter;
|
||||
|
@ -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();
|
||||
|
||||
|
1
src/os.c
1
src/os.c
@ -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
27
test/testClassFields.krk
Normal 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()
|
6
test/testClassFields.krk.expect
Normal file
6
test/testClassFields.krk.expect
Normal 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
23
test/testClassMethod.krk
Normal 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()
|
||||
|
||||
|
3
test/testClassMethod.krk.expect
Normal file
3
test/testClassMethod.krk.expect
Normal 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
46
vm.c
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user