Hacky implementation of @staticmethod, @property, mb even @classmethod

This commit is contained in:
K. Lange 2021-01-23 19:30:07 +09:00
parent 824cbc59eb
commit 50e4902170
12 changed files with 187 additions and 20 deletions

View File

@ -1291,6 +1291,41 @@ print(doesANestedThing())
_**Note:** The implementation of `with` blocks is incomplete; exceptions raised from within a `with` that are not caught within the block will cause `__exit__` to not be called._ _**Note:** The implementation of `with` blocks is incomplete; exceptions raised from within a `with` that are not caught within the block will cause `__exit__` to not be called._
### Special Decorators
The compiler implements special handling when the decorators `@staticmethod` and `@property` are used with methods of a class.
`@staticmethod` will mark the decorated method as a regular function - it will not receive an implicit "self" and it will be attached to the `fields` table of the class and instances of that class, rather than the `methods` table.
`@property` will mark the decorated method as a property object. When it is retrieved or assigned to from the class or instance through a dot-accessor (eg. `foo.bar`), the wrapped method will be called intead.
Properties work differently from in Python:
```py
class Foo():
def __init__(self):
self._bar = 0
@property
def bar(self, *setter):
if setter:
print("Setting bar:", setter[0])
self._bar = setter[0]
else:
print("Getting bar.")
return self._bar
let f = Foo()
print(f.bar)
f.bar = 42
print(f.bar)
# → Getting bar.
# 0
# Setting bar: 42
# Getting bar.
# 42
```
_**Note:** Special handling when using `del` on a property is not yet implemented._
## About the REPL ## About the REPL
Kuroko's repl provides an interactive environment for executing code and seeing results. Kuroko's repl provides an interactive environment for executing code and seeing results.

View File

@ -83,6 +83,8 @@ typedef enum {
OP_INVOKE_DELETE, OP_INVOKE_DELETE,
OP_IMPORT_FROM, OP_IMPORT_FROM,
OP_CREATE_PROPERTY,
OP_CONSTANT_LONG = 128, OP_CONSTANT_LONG = 128,
OP_DEFINE_GLOBAL_LONG, OP_DEFINE_GLOBAL_LONG,
OP_GET_GLOBAL_LONG, OP_GET_GLOBAL_LONG,

View File

@ -92,6 +92,8 @@ typedef enum {
TYPE_METHOD, TYPE_METHOD,
TYPE_INIT, TYPE_INIT,
TYPE_LAMBDA, TYPE_LAMBDA,
TYPE_STATIC,
TYPE_PROPERTY,
} FunctionType; } FunctionType;
typedef struct Compiler { typedef struct Compiler {
@ -133,6 +135,10 @@ static KrkChunk * currentChunk() {
#define EMIT_CONSTANT_OP(opc, arg) do { if (arg < 256) { emitBytes(opc, arg); } \ #define EMIT_CONSTANT_OP(opc, arg) do { if (arg < 256) { emitBytes(opc, arg); } \
else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0) else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0)
static int isMethod(int type) {
return type == TYPE_METHOD || type == TYPE_INIT || type == TYPE_PROPERTY;
}
static void initCompiler(Compiler * compiler, FunctionType type) { static void initCompiler(Compiler * compiler, FunctionType type) {
compiler->enclosing = current; compiler->enclosing = current;
current = compiler; current = compiler;
@ -159,7 +165,7 @@ static void initCompiler(Compiler * compiler, FunctionType type) {
current->function->name = krk_copyString(parser.previous.start, parser.previous.length); current->function->name = krk_copyString(parser.previous.start, parser.previous.length);
} }
if (type == TYPE_INIT || type == TYPE_METHOD) { if (isMethod(type)) {
Local * local = &current->locals[current->localCount++]; Local * local = &current->locals[current->localCount++];
local->depth = 0; local->depth = 0;
local->isCaptured = 0; local->isCaptured = 0;
@ -876,7 +882,7 @@ static void function(FunctionType type, size_t blockWidth) {
beginScope(); beginScope();
if (type == TYPE_METHOD || type == TYPE_INIT) current->function->requiredArgs = 1; if (isMethod(type)) current->function->requiredArgs = 1;
int hasCollectors = 0; int hasCollectors = 0;
@ -885,7 +891,7 @@ static void function(FunctionType type, size_t blockWidth) {
if (!check(TOKEN_RIGHT_PAREN)) { if (!check(TOKEN_RIGHT_PAREN)) {
do { do {
if (match(TOKEN_SELF)) { if (match(TOKEN_SELF)) {
if (type != TYPE_INIT && type != TYPE_METHOD) { if (!isMethod(type)) {
error("Invalid use of `self` as a function paramenter."); error("Invalid use of `self` as a function paramenter.");
} }
continue; continue;
@ -995,6 +1001,9 @@ static void method(size_t blockWidth) {
if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) { if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) {
errorAtCurrent("Expected end of line after class attribut declaration"); errorAtCurrent("Expected end of line after class attribut declaration");
} }
} else if (match(TOKEN_PASS)) {
/* bah */
consume(TOKEN_EOL, "Expected linefeed after 'pass' in class body.");
} else { } else {
consume(TOKEN_DEF, "expected a definition, got nothing"); consume(TOKEN_DEF, "expected a definition, got nothing");
consume(TOKEN_IDENTIFIER, "expected method name"); consume(TOKEN_IDENTIFIER, "expected method name");
@ -1134,8 +1143,33 @@ static KrkToken decorator(size_t level, FunctionType type) {
size_t blockWidth = (parser.previous.type == TOKEN_INDENTATION) ? parser.previous.length : 0; size_t blockWidth = (parser.previous.type == TOKEN_INDENTATION) ? parser.previous.length : 0;
advance(); /* Collect the `@` */ advance(); /* Collect the `@` */
/* Collect an identifier */ KrkToken funcName = {0};
expression(); int haveCallable = 0;
/* hol'up, let's special case some stuff */
KrkToken at_staticmethod = syntheticToken("staticmethod");
KrkToken at_property = syntheticToken("property");
if (identifiersEqual(&at_staticmethod, &parser.current)) {
if (level != 0 || type != TYPE_METHOD) {
error("Invalid use of @staticmethod, which must be the top decorator of a class method.");
return funcName;
}
advance();
type = TYPE_STATIC;
emitBytes(OP_DUP, 0); /* SET_PROPERTY will pop class */
} else if (identifiersEqual(&at_property, &parser.current)) {
if (level != 0 || type != TYPE_METHOD) {
error("Invalid use of @property, which must be the top decorator of a class method.");
return funcName;
}
advance();
type = TYPE_PROPERTY;
emitBytes(OP_DUP, 0);
} else {
/* Collect an identifier */
expression();
haveCallable = 1;
}
consume(TOKEN_EOL, "Expected line feed after decorator."); consume(TOKEN_EOL, "Expected line feed after decorator.");
if (blockWidth) { if (blockWidth) {
@ -1143,7 +1177,6 @@ static KrkToken decorator(size_t level, FunctionType type) {
if (parser.previous.length != blockWidth) error("Expected next line after decorator to have same indentation."); if (parser.previous.length != blockWidth) error("Expected next line after decorator to have same indentation.");
} }
KrkToken funcName = {0};
if (check(TOKEN_DEF)) { if (check(TOKEN_DEF)) {
/* We already checked for block level */ /* We already checked for block level */
advance(); advance();
@ -1160,7 +1193,8 @@ static KrkToken decorator(size_t level, FunctionType type) {
return funcName; return funcName;
} }
emitBytes(OP_CALL, 1); if (haveCallable)
emitBytes(OP_CALL, 1);
if (level == 0) { if (level == 0) {
if (type == TYPE_FUNCTION) { if (type == TYPE_FUNCTION) {
@ -1168,6 +1202,15 @@ static KrkToken decorator(size_t level, FunctionType type) {
declareVariable(); declareVariable();
size_t ind = (current->scopeDepth > 0) ? 0 : identifierConstant(&funcName); size_t ind = (current->scopeDepth > 0) ? 0 : identifierConstant(&funcName);
defineVariable(ind); defineVariable(ind);
} else if (type == TYPE_STATIC) {
size_t ind = identifierConstant(&funcName);
EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind);
emitByte(OP_POP);
} else if (type == TYPE_PROPERTY) {
emitByte(OP_CREATE_PROPERTY);
size_t ind = identifierConstant(&funcName);
EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind);
emitByte(OP_POP);
} else { } else {
size_t ind = identifierConstant(&funcName); size_t ind = identifierConstant(&funcName);
EMIT_CONSTANT_OP(OP_METHOD, ind); EMIT_CONSTANT_OP(OP_METHOD, ind);

View File

@ -122,6 +122,7 @@ size_t krk_disassembleInstruction(FILE * f, KrkFunction * func, size_t offset) {
SIMPLE(OP_FINALIZE) SIMPLE(OP_FINALIZE)
SIMPLE(OP_IS) SIMPLE(OP_IS)
SIMPLE(OP_POW) SIMPLE(OP_POW)
SIMPLE(OP_CREATE_PROPERTY)
OPERANDB(OP_DUP,(void)0) OPERANDB(OP_DUP,(void)0)
OPERANDB(OP_EXPAND_ARGS,EXPAND_ARGS_MORE) OPERANDB(OP_EXPAND_ARGS,EXPAND_ARGS_MORE)
CONSTANT(OP_DEFINE_GLOBAL,(void)0) CONSTANT(OP_DEFINE_GLOBAL,(void)0)

View File

@ -87,6 +87,10 @@ static void freeObject(KrkObj * object) {
FREE(KrkBytes, bytes); FREE(KrkBytes, bytes);
break; break;
} }
case OBJ_PROPERTY: {
FREE(KrkProperty, object);
break;
}
} }
} }
@ -178,6 +182,11 @@ static void blackenObject(KrkObj * object) {
markArray(&tuple->values); markArray(&tuple->values);
break; break;
} }
case OBJ_PROPERTY: {
KrkProperty * property = (KrkProperty *)object;
krk_markValue(property->method);
break;
}
case OBJ_NATIVE: case OBJ_NATIVE:
case OBJ_STRING: case OBJ_STRING:
case OBJ_BYTES: case OBJ_BYTES:

View File

@ -248,6 +248,12 @@ KrkUpvalue * krk_newUpvalue(int slot) {
return upvalue; return upvalue;
} }
KrkProperty * krk_newProperty(KrkValue method) {
KrkProperty * property = ALLOCATE_OBJECT(KrkProperty, OBJ_PROPERTY);
property->method = method;
return property;
}
KrkClass * krk_newClass(KrkString * name, KrkClass * baseClass) { KrkClass * krk_newClass(KrkString * name, KrkClass * baseClass) {
KrkClass * _class = ALLOCATE_OBJECT(KrkClass, OBJ_CLASS); KrkClass * _class = ALLOCATE_OBJECT(KrkClass, OBJ_CLASS);
_class->name = name; _class->name = name;

View File

@ -28,6 +28,8 @@
#define IS_TUPLE(value) isObjType(value, OBJ_TUPLE) #define IS_TUPLE(value) isObjType(value, OBJ_TUPLE)
#define AS_TUPLE(value) ((KrkTuple *)AS_OBJECT(value)) #define AS_TUPLE(value) ((KrkTuple *)AS_OBJECT(value))
#define IS_PROPERTY(value) isObjType(value, OBJ_PROPERTY)
#define AS_PROPERTY(value) ((KrkProperty *)AS_OBJECT(value))
typedef enum { typedef enum {
OBJ_FUNCTION, OBJ_FUNCTION,
@ -40,6 +42,7 @@ typedef enum {
OBJ_BOUND_METHOD, OBJ_BOUND_METHOD,
OBJ_TUPLE, OBJ_TUPLE,
OBJ_BYTES, OBJ_BYTES,
OBJ_PROPERTY,
} ObjType; } ObjType;
struct Obj { struct Obj {
@ -173,6 +176,11 @@ typedef struct {
KrkValueArray values; KrkValueArray values;
} KrkTuple; } KrkTuple;
typedef struct {
KrkObj obj;
KrkValue method;
} KrkProperty;
typedef struct { typedef struct {
KrkInstance inst; KrkInstance inst;
KrkValueArray values; KrkValueArray values;
@ -200,6 +208,7 @@ extern KrkClass * krk_newClass(KrkString * name, KrkClass * base);
extern KrkInstance * krk_newInstance(KrkClass * _class); extern KrkInstance * krk_newInstance(KrkClass * _class);
extern KrkBoundMethod * krk_newBoundMethod(KrkValue receiver, KrkObj * method); extern KrkBoundMethod * krk_newBoundMethod(KrkValue receiver, KrkObj * method);
extern KrkTuple * krk_newTuple(size_t length); extern KrkTuple * krk_newTuple(size_t length);
extern KrkProperty * krk_newProperty(KrkValue method);
extern void * krk_unicodeString(KrkString * string); extern void * krk_unicodeString(KrkString * string);
extern uint32_t krk_unicodeCodepoint(KrkString * string, size_t index); extern uint32_t krk_unicodeCodepoint(KrkString * string, size_t index);

View File

@ -1411,6 +1411,7 @@ KrkValue krk_callSimple(KrkValue value, int argCount, int isMethod) {
} else if (result == 1) { } else if (result == 1) {
return krk_runNext(); return krk_runNext();
} }
if (!IS_NONE(vm.currentException)) return NONE_VAL();
return krk_runtimeError(vm.exceptions.typeError, "Invalid internal method call: %d ('%s')", result, krk_typeName(value)); return krk_runtimeError(vm.exceptions.typeError, "Invalid internal method call: %d ('%s')", result, krk_typeName(value));
} }
@ -4133,6 +4134,10 @@ static int valueGetProperty(KrkString * name) {
if (IS_INSTANCE(krk_peek(0))) { if (IS_INSTANCE(krk_peek(0))) {
KrkInstance * instance = AS_INSTANCE(krk_peek(0)); KrkInstance * instance = AS_INSTANCE(krk_peek(0));
if (krk_tableGet(&instance->fields, OBJECT_VAL(name), &value)) { if (krk_tableGet(&instance->fields, OBJECT_VAL(name), &value)) {
if (IS_PROPERTY(value)) {
krk_push(krk_callSimple(AS_PROPERTY(value)->method, 1, 0));
return 1;
}
krk_pop(); krk_pop();
krk_push(value); krk_push(value);
return 1; return 1;
@ -4142,6 +4147,10 @@ static int valueGetProperty(KrkString * name) {
KrkClass * _class = AS_CLASS(krk_peek(0)); KrkClass * _class = AS_CLASS(krk_peek(0));
if (krk_tableGet(&_class->fields, OBJECT_VAL(name), &value) || if (krk_tableGet(&_class->fields, OBJECT_VAL(name), &value) ||
krk_tableGet(&_class->methods, OBJECT_VAL(name), &value)) { krk_tableGet(&_class->methods, OBJECT_VAL(name), &value)) {
if (IS_PROPERTY(value)) {
krk_push(krk_callSimple(AS_PROPERTY(value)->method, 1, 0));
return 1;
}
krk_pop(); krk_pop();
krk_push(value); krk_push(value);
return 1; return 1;
@ -4584,12 +4593,17 @@ static KrkValue run() {
case OP_SET_PROPERTY_LONG: case OP_SET_PROPERTY_LONG:
case OP_SET_PROPERTY: { case OP_SET_PROPERTY: {
KrkString * name = READ_STRING(operandWidth); KrkString * name = READ_STRING(operandWidth);
if (IS_INSTANCE(krk_peek(1))) { KrkTable * table = NULL;
KrkInstance * instance = AS_INSTANCE(krk_peek(1)); if (IS_INSTANCE(krk_peek(1))) table = &AS_INSTANCE(krk_peek(1))->fields;
krk_tableSet(&instance->fields, OBJECT_VAL(name), krk_peek(0)); else if (IS_CLASS(krk_peek(1))) table = &AS_CLASS(krk_peek(1))->fields;
} else if (IS_CLASS(krk_peek(1))) { if (table) {
KrkClass * _class = AS_CLASS(krk_peek(1)); KrkValue previous;
krk_tableSet(&_class->fields, OBJECT_VAL(name), krk_peek(0)); if (krk_tableGet(table, OBJECT_VAL(name), &previous) && IS_PROPERTY(previous)) {
krk_push(krk_callSimple(AS_PROPERTY(previous)->method, 2, 0));
break;
} else {
krk_tableSet(table, OBJECT_VAL(name), krk_peek(0));
}
} else { } else {
krk_runtimeError(vm.exceptions.attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars); krk_runtimeError(vm.exceptions.attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars);
goto _finishException; goto _finishException;
@ -4754,6 +4768,12 @@ static KrkValue run() {
krk_push(handler); krk_push(handler);
break; break;
} }
case OP_CREATE_PROPERTY: {
KrkProperty * newProperty = krk_newProperty(krk_peek(0));
krk_pop();
krk_push(OBJECT_VAL(newProperty));
break;
}
} }
if (likely(!(vm.flags & KRK_HAS_EXCEPTION))) continue; if (likely(!(vm.flags & KRK_HAS_EXCEPTION))) continue;
_finishException: _finishException:

View File

@ -1,14 +1,9 @@
def staticmethod(func):
def _wrapper(*args,**kwargs):
return func(None,*args,**kwargs)
return _wrapper
class Foo(): class Foo():
def amethod(): def amethod():
print("If this were Python, I'd be impossible to call.") print("If this were Python, I'd be impossible to call.")
@staticmethod @staticmethod
def astatic(): def astatic():
print("Since `None` is always implicit, it's been set by @staticmethod here to None:", self) print("these are special now")
# This doesn't work because amethod is unbound and needs an instance. # This doesn't work because amethod is unbound and needs an instance.
try: try:

View File

@ -1,3 +1,3 @@
amethod() takes exactly 1 argument (0 given) amethod() takes exactly 1 argument (0 given)
If this were Python, I'd be impossible to call. 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 these are special now

View File

@ -0,0 +1,39 @@
def classmethod(func):
def bindClassMethod(callee, *args):
if args: raise ValueError("can not reassign class method")
let cls = callee if isinstance(callee, type) else callee.__class__
def _bound(*args, **kwargs):
return func(None, cls, *args, **kwargs)
return _bound
return bindClassMethod
class Foo(object):
myBar = 42
@staticmethod
def foo():
print("No args!")
@property
def bar(*setter):
if setter:
print("Called as a setter:", setter)
self.myBar = setter[0]
return self.myBar
@property
@classmethod
def fromString(cls, string):
print(cls, string)
class Bar(Foo):
Foo.foo()
print(Foo().bar)
let f = Foo()
f.myBar = 48
print(f.bar)
f.bar = 102
print(f.bar)
Foo.fromString("test")
f.fromString("test")
Bar.fromString("test")

View File

@ -0,0 +1,8 @@
No args!
42
48
Called as a setter: [102]
102
<type 'Foo'> test
<type 'Foo'> test
<type 'Bar'> test