Hacky implementation of @staticmethod, @property, mb even @classmethod
This commit is contained in:
parent
824cbc59eb
commit
50e4902170
35
README.md
35
README.md
@ -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._
|
||||
|
||||
### 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
|
||||
|
||||
Kuroko's repl provides an interactive environment for executing code and seeing results.
|
||||
|
@ -83,6 +83,8 @@ typedef enum {
|
||||
OP_INVOKE_DELETE,
|
||||
OP_IMPORT_FROM,
|
||||
|
||||
OP_CREATE_PROPERTY,
|
||||
|
||||
OP_CONSTANT_LONG = 128,
|
||||
OP_DEFINE_GLOBAL_LONG,
|
||||
OP_GET_GLOBAL_LONG,
|
||||
|
@ -92,6 +92,8 @@ typedef enum {
|
||||
TYPE_METHOD,
|
||||
TYPE_INIT,
|
||||
TYPE_LAMBDA,
|
||||
TYPE_STATIC,
|
||||
TYPE_PROPERTY,
|
||||
} FunctionType;
|
||||
|
||||
typedef struct Compiler {
|
||||
@ -133,6 +135,10 @@ static KrkChunk * currentChunk() {
|
||||
#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)
|
||||
|
||||
static int isMethod(int type) {
|
||||
return type == TYPE_METHOD || type == TYPE_INIT || type == TYPE_PROPERTY;
|
||||
}
|
||||
|
||||
static void initCompiler(Compiler * compiler, FunctionType type) {
|
||||
compiler->enclosing = current;
|
||||
current = compiler;
|
||||
@ -159,7 +165,7 @@ static void initCompiler(Compiler * compiler, FunctionType type) {
|
||||
current->function->name = krk_copyString(parser.previous.start, parser.previous.length);
|
||||
}
|
||||
|
||||
if (type == TYPE_INIT || type == TYPE_METHOD) {
|
||||
if (isMethod(type)) {
|
||||
Local * local = ¤t->locals[current->localCount++];
|
||||
local->depth = 0;
|
||||
local->isCaptured = 0;
|
||||
@ -876,7 +882,7 @@ static void function(FunctionType type, size_t blockWidth) {
|
||||
|
||||
beginScope();
|
||||
|
||||
if (type == TYPE_METHOD || type == TYPE_INIT) current->function->requiredArgs = 1;
|
||||
if (isMethod(type)) current->function->requiredArgs = 1;
|
||||
|
||||
int hasCollectors = 0;
|
||||
|
||||
@ -885,7 +891,7 @@ static void function(FunctionType type, size_t blockWidth) {
|
||||
if (!check(TOKEN_RIGHT_PAREN)) {
|
||||
do {
|
||||
if (match(TOKEN_SELF)) {
|
||||
if (type != TYPE_INIT && type != TYPE_METHOD) {
|
||||
if (!isMethod(type)) {
|
||||
error("Invalid use of `self` as a function paramenter.");
|
||||
}
|
||||
continue;
|
||||
@ -995,6 +1001,9 @@ static void method(size_t blockWidth) {
|
||||
if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) {
|
||||
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 {
|
||||
consume(TOKEN_DEF, "expected a definition, got nothing");
|
||||
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;
|
||||
advance(); /* Collect the `@` */
|
||||
|
||||
KrkToken funcName = {0};
|
||||
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.");
|
||||
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.");
|
||||
}
|
||||
|
||||
KrkToken funcName = {0};
|
||||
if (check(TOKEN_DEF)) {
|
||||
/* We already checked for block level */
|
||||
advance();
|
||||
@ -1160,6 +1193,7 @@ static KrkToken decorator(size_t level, FunctionType type) {
|
||||
return funcName;
|
||||
}
|
||||
|
||||
if (haveCallable)
|
||||
emitBytes(OP_CALL, 1);
|
||||
|
||||
if (level == 0) {
|
||||
@ -1168,6 +1202,15 @@ static KrkToken decorator(size_t level, FunctionType type) {
|
||||
declareVariable();
|
||||
size_t ind = (current->scopeDepth > 0) ? 0 : identifierConstant(&funcName);
|
||||
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 {
|
||||
size_t ind = identifierConstant(&funcName);
|
||||
EMIT_CONSTANT_OP(OP_METHOD, ind);
|
||||
|
@ -122,6 +122,7 @@ size_t krk_disassembleInstruction(FILE * f, KrkFunction * func, size_t offset) {
|
||||
SIMPLE(OP_FINALIZE)
|
||||
SIMPLE(OP_IS)
|
||||
SIMPLE(OP_POW)
|
||||
SIMPLE(OP_CREATE_PROPERTY)
|
||||
OPERANDB(OP_DUP,(void)0)
|
||||
OPERANDB(OP_EXPAND_ARGS,EXPAND_ARGS_MORE)
|
||||
CONSTANT(OP_DEFINE_GLOBAL,(void)0)
|
||||
|
@ -87,6 +87,10 @@ static void freeObject(KrkObj * object) {
|
||||
FREE(KrkBytes, bytes);
|
||||
break;
|
||||
}
|
||||
case OBJ_PROPERTY: {
|
||||
FREE(KrkProperty, object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +182,11 @@ static void blackenObject(KrkObj * object) {
|
||||
markArray(&tuple->values);
|
||||
break;
|
||||
}
|
||||
case OBJ_PROPERTY: {
|
||||
KrkProperty * property = (KrkProperty *)object;
|
||||
krk_markValue(property->method);
|
||||
break;
|
||||
}
|
||||
case OBJ_NATIVE:
|
||||
case OBJ_STRING:
|
||||
case OBJ_BYTES:
|
||||
|
@ -248,6 +248,12 @@ KrkUpvalue * krk_newUpvalue(int slot) {
|
||||
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 * _class = ALLOCATE_OBJECT(KrkClass, OBJ_CLASS);
|
||||
_class->name = name;
|
||||
|
@ -28,6 +28,8 @@
|
||||
|
||||
#define IS_TUPLE(value) isObjType(value, OBJ_TUPLE)
|
||||
#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 {
|
||||
OBJ_FUNCTION,
|
||||
@ -40,6 +42,7 @@ typedef enum {
|
||||
OBJ_BOUND_METHOD,
|
||||
OBJ_TUPLE,
|
||||
OBJ_BYTES,
|
||||
OBJ_PROPERTY,
|
||||
} ObjType;
|
||||
|
||||
struct Obj {
|
||||
@ -173,6 +176,11 @@ typedef struct {
|
||||
KrkValueArray values;
|
||||
} KrkTuple;
|
||||
|
||||
typedef struct {
|
||||
KrkObj obj;
|
||||
KrkValue method;
|
||||
} KrkProperty;
|
||||
|
||||
typedef struct {
|
||||
KrkInstance inst;
|
||||
KrkValueArray values;
|
||||
@ -200,6 +208,7 @@ extern KrkClass * krk_newClass(KrkString * name, KrkClass * base);
|
||||
extern KrkInstance * krk_newInstance(KrkClass * _class);
|
||||
extern KrkBoundMethod * krk_newBoundMethod(KrkValue receiver, KrkObj * method);
|
||||
extern KrkTuple * krk_newTuple(size_t length);
|
||||
extern KrkProperty * krk_newProperty(KrkValue method);
|
||||
|
||||
extern void * krk_unicodeString(KrkString * string);
|
||||
extern uint32_t krk_unicodeCodepoint(KrkString * string, size_t index);
|
||||
|
32
src/vm.c
32
src/vm.c
@ -1411,6 +1411,7 @@ KrkValue krk_callSimple(KrkValue value, int argCount, int isMethod) {
|
||||
} else if (result == 1) {
|
||||
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));
|
||||
}
|
||||
|
||||
@ -4133,6 +4134,10 @@ static int valueGetProperty(KrkString * name) {
|
||||
if (IS_INSTANCE(krk_peek(0))) {
|
||||
KrkInstance * instance = AS_INSTANCE(krk_peek(0));
|
||||
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_push(value);
|
||||
return 1;
|
||||
@ -4142,6 +4147,10 @@ static int valueGetProperty(KrkString * name) {
|
||||
KrkClass * _class = AS_CLASS(krk_peek(0));
|
||||
if (krk_tableGet(&_class->fields, 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_push(value);
|
||||
return 1;
|
||||
@ -4584,12 +4593,17 @@ static KrkValue run() {
|
||||
case OP_SET_PROPERTY_LONG:
|
||||
case OP_SET_PROPERTY: {
|
||||
KrkString * name = READ_STRING(operandWidth);
|
||||
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));
|
||||
KrkTable * table = NULL;
|
||||
if (IS_INSTANCE(krk_peek(1))) table = &AS_INSTANCE(krk_peek(1))->fields;
|
||||
else if (IS_CLASS(krk_peek(1))) table = &AS_CLASS(krk_peek(1))->fields;
|
||||
if (table) {
|
||||
KrkValue previous;
|
||||
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 {
|
||||
krk_runtimeError(vm.exceptions.attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars);
|
||||
goto _finishException;
|
||||
@ -4754,6 +4768,12 @@ static KrkValue run() {
|
||||
krk_push(handler);
|
||||
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;
|
||||
_finishException:
|
||||
|
@ -1,14 +1,9 @@
|
||||
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)
|
||||
print("these are special now")
|
||||
|
||||
# This doesn't work because amethod is unbound and needs an instance.
|
||||
try:
|
||||
|
@ -1,3 +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
|
||||
these are special now
|
||||
|
39
test/testSpecialDecorators.krk
Normal file
39
test/testSpecialDecorators.krk
Normal 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")
|
8
test/testSpecialDecorators.krk.expect
Normal file
8
test/testSpecialDecorators.krk.expect
Normal file
@ -0,0 +1,8 @@
|
||||
No args!
|
||||
42
|
||||
48
|
||||
Called as a setter: [102]
|
||||
102
|
||||
<type 'Foo'> test
|
||||
<type 'Foo'> test
|
||||
<type 'Bar'> test
|
Loading…
Reference in New Issue
Block a user