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._
### 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.

View File

@ -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,

View File

@ -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 = &current->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 `@` */
/* Collect an identifier */
expression();
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,7 +1193,8 @@ static KrkToken decorator(size_t level, FunctionType type) {
return funcName;
}
emitBytes(OP_CALL, 1);
if (haveCallable)
emitBytes(OP_CALL, 1);
if (level == 0) {
if (type == TYPE_FUNCTION) {
@ -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);

View File

@ -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)

View File

@ -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:

View File

@ -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;

View File

@ -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);

View File

@ -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:

View File

@ -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:

View File

@ -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

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