From d2d1c98a1e13c01edba4d66b3fe255a088d8f8d6 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Thu, 14 Jan 2021 16:00:43 +0900 Subject: [PATCH] Add 'del' statement. --- chunk.h | 6 ++++ compiler.c | 46 +++++++++++++++++++++--- debug.c | 3 ++ object.h | 1 + rline.c | 2 +- scanner.c | 7 +++- scanner.h | 1 + test/testDel.krk | 49 +++++++++++++++++++++++++ test/testDel.krk.expect | 16 +++++++++ vm.c | 79 +++++++++++++++++++++++++++++++++++++++++ vm.h | 1 + 11 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 test/testDel.krk create mode 100644 test/testDel.krk.expect diff --git a/chunk.h b/chunk.h index d46097d..6896591 100644 --- a/chunk.h +++ b/chunk.h @@ -77,6 +77,10 @@ typedef enum { OP_IS, + OP_DEL_GLOBAL, + OP_DEL_PROPERTY, + OP_INVOKE_DELETE, + OP_CONSTANT_LONG = 128, OP_DEFINE_GLOBAL_LONG, OP_GET_GLOBAL_LONG, @@ -97,6 +101,8 @@ typedef enum { OP_KWARGS_LONG, OP_TUPLE_LONG, OP_UNPACK_TUPLE_LONG, + OP_DEL_GLOBAL_LONG, + OP_DEL_PROPERTY_LONG, } KrkOpCode; typedef struct { diff --git a/compiler.c b/compiler.c index e1ed8bb..dc5ccc9 100644 --- a/compiler.c +++ b/compiler.c @@ -124,6 +124,7 @@ typedef struct ClassCompiler { static Parser parser; static Compiler * current = NULL; static ClassCompiler * currentClass = NULL; +static int inDel = 0; static KrkChunk * currentChunk() { return ¤t->function->chunk; @@ -477,6 +478,10 @@ static int matchAssignment(void) { match(TOKEN_PLUS_PLUS) || match(TOKEN_MINUS_MINUS); } +static int matchEndOfDel(void) { + return check(TOKEN_COMMA) || match(TOKEN_EOL) || match(TOKEN_EOF); +} + static void assignmentValue(void) { switch (parser.previous.type) { case TOKEN_PLUS_EQUAL: @@ -521,7 +526,9 @@ static void get_(int canAssign) { consume(TOKEN_RIGHT_SQUARE, "Expected ending square bracket after slice."); } if (canAssign && matchAssignment()) { - error("Can not assign to slice."); + error("Slice assignment not implemented."); + } else if (inDel && matchEndOfDel()) { + error("Slice deletion not implemented."); } else { emitByte(OP_INVOKE_GETSLICE); } @@ -536,6 +543,13 @@ static void get_(int canAssign) { emitByte(OP_INVOKE_GETTER); /* o e v */ assignmentValue(); /* o e v a */ emitByte(OP_INVOKE_SETTER); /* r */ + } else if (inDel && matchEndOfDel()) { + if (!canAssign || inDel != 1) { + error("Invalid del target"); + } else if (canAssign) { + emitByte(OP_INVOKE_DELETE); + inDel = 2; + } } else { emitByte(OP_INVOKE_GETTER); } @@ -553,6 +567,13 @@ static void dot(int canAssign) { EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind); assignmentValue(); EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind); + } else if (inDel && matchEndOfDel()) { + if (!canAssign || inDel != 1) { + error("Invalid del target"); + } else { + EMIT_CONSTANT_OP(OP_DEL_PROPERTY, ind); + inDel = 2; + } } else { EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind); } @@ -1449,6 +1470,13 @@ static void statement() { tryStatement(); } else if (check(TOKEN_WITH)) { withStatement(); + } else if (match(TOKEN_DEL)) { + do { + inDel = 1; + expression(); + } while (match(TOKEN_COMMA)); + inDel = 0; + /* del already eats linefeeds */ } else { if (match(TOKEN_RAISE)) { raiseStatement(); @@ -1663,7 +1691,8 @@ static ssize_t resolveUpvalue(Compiler * compiler, KrkToken * name) { return -1; } -#define DO_VARIABLE(opset,opget) do { \ +#define OP_NONE_LONG -1 +#define DO_VARIABLE(opset,opget,opdel) do { \ if (canAssign && match(TOKEN_EQUAL)) { \ expression(); \ EMIT_CONSTANT_OP(opset, arg); \ @@ -1671,6 +1700,9 @@ static ssize_t resolveUpvalue(Compiler * compiler, KrkToken * name) { EMIT_CONSTANT_OP(opget, arg); \ assignmentValue(); \ EMIT_CONSTANT_OP(opset, arg); \ + } else if (inDel && matchEndOfDel()) {\ + if (opdel == OP_NONE || !canAssign || inDel != 1) { error("Invalid del target"); } else { \ + EMIT_CONSTANT_OP(opdel, arg); inDel = 2; } \ } else { \ EMIT_CONSTANT_OP(opget, arg); \ } } while (0) @@ -1678,12 +1710,12 @@ static ssize_t resolveUpvalue(Compiler * compiler, KrkToken * name) { static void namedVariable(KrkToken name, int canAssign) { ssize_t arg = resolveLocal(current, &name); if (arg != -1) { - DO_VARIABLE(OP_SET_LOCAL, OP_GET_LOCAL); + DO_VARIABLE(OP_SET_LOCAL, OP_GET_LOCAL, OP_NONE); } else if ((arg = resolveUpvalue(current, &name)) != -1) { - DO_VARIABLE(OP_SET_UPVALUE, OP_GET_UPVALUE); + DO_VARIABLE(OP_SET_UPVALUE, OP_GET_UPVALUE, OP_NONE); } else { arg = identifierConstant(&name); - DO_VARIABLE(OP_SET_GLOBAL, OP_GET_GLOBAL); + DO_VARIABLE(OP_SET_GLOBAL, OP_GET_GLOBAL, OP_DEL_GLOBAL); } } #undef DO_VARIABLE @@ -1953,6 +1985,7 @@ ParseRule krk_parseRules[] = { RULE(TOKEN_FALSE, literal, NULL, PREC_NONE), RULE(TOKEN_FOR, NULL, NULL, PREC_NONE), RULE(TOKEN_DEF, NULL, NULL, PREC_NONE), + RULE(TOKEN_DEL, NULL, NULL, PREC_NONE), RULE(TOKEN_IF, NULL, ternary,PREC_TERNARY), RULE(TOKEN_IN, NULL, in_, PREC_COMPARISON), RULE(TOKEN_LET, NULL, NULL, PREC_NONE), @@ -2044,6 +2077,9 @@ static void parsePrecedence(Precedence precedence) { if (canAssign && matchAssignment()) { error("invalid assignment target"); } + if (inDel == 1 && matchEndOfDel()) { + error("invalid del target"); + } } static ssize_t identifierConstant(KrkToken * name) { diff --git a/debug.c b/debug.c index 69620e5..9d02480 100644 --- a/debug.c +++ b/debug.c @@ -117,6 +117,7 @@ size_t krk_disassembleInstruction(FILE * f, KrkFunction * func, size_t offset) { SIMPLE(OP_INVOKE_GETTER) SIMPLE(OP_INVOKE_SETTER) SIMPLE(OP_INVOKE_GETSLICE) + SIMPLE(OP_INVOKE_DELETE) SIMPLE(OP_SWAP) SIMPLE(OP_FINALIZE) SIMPLE(OP_IS) @@ -126,9 +127,11 @@ size_t krk_disassembleInstruction(FILE * f, KrkFunction * func, size_t offset) { CONSTANT(OP_CONSTANT,(void)0) CONSTANT(OP_GET_GLOBAL,(void)0) CONSTANT(OP_SET_GLOBAL,(void)0) + CONSTANT(OP_DEL_GLOBAL,(void)0) CONSTANT(OP_CLASS,(void)0) CONSTANT(OP_GET_PROPERTY, (void)0) CONSTANT(OP_SET_PROPERTY, (void)0) + CONSTANT(OP_DEL_PROPERTY,(void)0) CONSTANT(OP_METHOD, (void)0) CONSTANT(OP_CLOSURE, CLOSURE_MORE) CONSTANT(OP_IMPORT, (void)0) diff --git a/object.h b/object.h index c6ed08c..df94823 100644 --- a/object.h +++ b/object.h @@ -134,6 +134,7 @@ typedef struct KrkClass { KrkObj * _len; KrkObj * _enter; KrkObj * _exit; + KrkObj * _delitem; } KrkClass; typedef struct KrkInstance { diff --git a/rline.c b/rline.c index 3e2d9c9..e5d0e48 100644 --- a/rline.c +++ b/rline.c @@ -541,7 +541,7 @@ void paint_krk_string(struct syntax_state * state, int type) { } char * syn_krk_keywords[] = { - "and","class","def","else","for","if","in","import", + "and","class","def","else","for","if","in","import","del", "let","not","or","return","while","try","except","raise", "continue","break","as","from","elif","lambda","with","is", NULL diff --git a/scanner.c b/scanner.c index a9ee204..996048a 100644 --- a/scanner.c +++ b/scanner.c @@ -203,7 +203,12 @@ static KrkTokenType identifierType() { case 'l': return checkKeyword(2, "ass", TOKEN_CLASS); case 'o': return checkKeyword(2, "ntinue", TOKEN_CONTINUE); } break; - case 'd': return checkKeyword(1, "ef", TOKEN_DEF); + case 'd': if (MORE(1)) switch(scanner.start[1]) { + case 'e': if (MORE(2)) switch (scanner.start[2]) { + case 'f': return checkKeyword(3, "", TOKEN_DEF); + case 'l': return checkKeyword(3, "", TOKEN_DEL); + } break; + } break; case 'e': if (MORE(1)) switch(scanner.start[1]) { case 'l': if (MORE(2)) switch(scanner.start[2]) { case 's': return checkKeyword(3,"e", TOKEN_ELSE); diff --git a/scanner.h b/scanner.h index fbdf8b0..9a03129 100644 --- a/scanner.h +++ b/scanner.h @@ -42,6 +42,7 @@ typedef enum { TOKEN_AND, TOKEN_CLASS, TOKEN_DEF, + TOKEN_DEL, TOKEN_ELSE, TOKEN_FALSE, TOKEN_FOR, diff --git a/test/testDel.krk b/test/testDel.krk new file mode 100644 index 0000000..117954b --- /dev/null +++ b/test/testDel.krk @@ -0,0 +1,49 @@ +let foo, bar, baz + +del foo + +print("foo" in globals()) # False +print("bar" in globals()) # True +print("baz" in globals()) # True + +del bar, baz + +print("foo" in globals()) # False +print("bar" in globals()) # False +print("baz" in globals()) # False + +let l = [1,2,3,4,5] + +del l[1] +print(l) # [1, 3, 4, 5] +del l[3] +print(l) # [1, 3, 4] +try: + del l[3] +except: + print(exception.arg) # List index out of range + +let o = object() +o.foo = object() +o.foo.bar = object() +o.foo.bar.baz = 42 +o.foo.bar.qux = "hi" + +print(dir(o.foo.bar)) +print(o.foo.bar.baz) +del o.foo.bar.baz +print(dir(o.foo.bar)) +print(o.foo.bar.qux) +try: + print(o.foo.bar.baz) +except: + print(exception.arg) # AttributeError +del o.foo.bar +print(dir(o.foo)) +try: + print(o.foo.bar) +except: + print(exception.arg) # AttributeError +del o.foo +del o + diff --git a/test/testDel.krk.expect b/test/testDel.krk.expect new file mode 100644 index 0000000..00f4482 --- /dev/null +++ b/test/testDel.krk.expect @@ -0,0 +1,16 @@ +False +True +True +False +False +False +[1, 3, 4, 5] +[1, 3, 4] +list index out of range: 3 +['__class__', '__str__', '__dir__', '__repr__', 'baz', 'qux'] +42 +['__class__', '__str__', '__dir__', '__repr__', 'qux'] +hi +'object' object has no attribute 'baz' +['__class__', '__str__', '__dir__', '__repr__'] +'object' object has no attribute 'bar' diff --git a/vm.c b/vm.c index 2c9731c..dcfa78c 100644 --- a/vm.c +++ b/vm.c @@ -301,6 +301,7 @@ void krk_finalizeClass(KrkClass * _class) { {&_class->_len, METHOD_LEN}, {&_class->_enter, METHOD_ENTER}, {&_class->_exit, METHOD_EXIT}, + {&_class->_delitem, METHOD_DELITEM}, {NULL, 0}, }; @@ -357,6 +358,30 @@ static KrkValue _dict_set(int argc, KrkValue argv[]) { return NONE_VAL(); } +/** + * dict.__delitem__ + */ +static KrkValue _dict_delitem(int argc, KrkValue argv[]) { + if (argc < 2) { + krk_runtimeError(vm.exceptions.argumentError, "wrong number of arguments"); + return NONE_VAL(); + } + KrkValue _dict_internal = OBJECT_VAL(AS_INSTANCE(argv[0])->_internal); + if (!krk_tableDelete(AS_DICT(_dict_internal), argv[1])) { + KrkClass * type = AS_CLASS(krk_typeOf(1,&argv[1])); + if (type->_reprer) { + krk_push(argv[1]); + KrkValue asString = krk_callSimple(OBJECT_VAL(type->_reprer), 1, 0); + if (IS_STRING(asString)) { + krk_runtimeError(vm.exceptions.keyError, "%s", AS_CSTRING(asString)); + return NONE_VAL(); + } + } + krk_runtimeError(vm.exceptions.keyError, "(Unrepresentable value)"); + } + return NONE_VAL(); +} + /** * dict.__len__() */ @@ -3067,6 +3092,7 @@ void krk_initVM(int flags) { vm.specialMethodNames[METHOD_EQ] = OBJECT_VAL(S("__eq__")); vm.specialMethodNames[METHOD_ENTER] = OBJECT_VAL(S("__enter__")); vm.specialMethodNames[METHOD_EXIT] = OBJECT_VAL(S("__exit__")); + vm.specialMethodNames[METHOD_DELITEM] = OBJECT_VAL(S("__delitem__")); /* delitem */ /* Create built-in class `object` */ vm.objectClass = krk_newClass(S("object")); @@ -3301,6 +3327,7 @@ void krk_initVM(int flags) { krk_defineNative(&_class->methods, ".__init__", _list_init); krk_defineNative(&_class->methods, ".__get__", _list_get); krk_defineNative(&_class->methods, ".__set__", _list_set); + krk_defineNative(&_class->methods, ".__delitem__", _list_pop); krk_defineNative(&_class->methods, ".__len__", _list_len); krk_defineNative(&_class->methods, ".__contains__", _list_contains); krk_defineNative(&_class->methods, ".__getslice__", _list_slice); @@ -3315,6 +3342,7 @@ void krk_initVM(int flags) { krk_defineNative(&_class->methods, ".__init__", _dict_init); krk_defineNative(&_class->methods, ".__get__", _dict_get); krk_defineNative(&_class->methods, ".__set__", _dict_set); + krk_defineNative(&_class->methods, ".__delitem__", _dict_delitem); krk_defineNative(&_class->methods, ".__len__", _dict_len); krk_defineNative(&_class->methods, ".__contains__", _dict_contains); krk_finalizeClass(_class); @@ -3674,6 +3702,26 @@ static int valueGetProperty(KrkString * name) { return 0; } +static int valueDelProperty(KrkString * name) { + if (IS_INSTANCE(krk_peek(0))) { + KrkInstance* instance = AS_INSTANCE(krk_peek(0)); + if (!krk_tableDelete(&instance->fields, OBJECT_VAL(name))) { + return 0; + } + krk_pop(); /* the original value */ + return 1; + } else if (IS_CLASS(krk_peek(0))) { + KrkClass * _class = AS_CLASS(krk_peek(0)); + if (!krk_tableDelete(&_class->fields, OBJECT_VAL(name))) { + return 0; + } + krk_pop(); /* the original value */ + return 1; + } + /* TODO del on values? */ + return 0; +} + #define READ_BYTE() (*frame->ip++) #define BINARY_OP(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); krk_push(operator_ ## op (a,b)); break; } #define BINARY_OP_CHECK_ZERO(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); \ @@ -3859,6 +3907,15 @@ static KrkValue run() { } break; } + case OP_DEL_GLOBAL_LONG: + case OP_DEL_GLOBAL: { + KrkString * name = READ_STRING(operandWidth); + if (!krk_tableDelete(frame->globals, OBJECT_VAL(name))) { + krk_runtimeError(vm.exceptions.nameError, "Undefined variable '%s'.", name->chars); + goto _finishException; + } + break; + } case OP_IMPORT_LONG: case OP_IMPORT: { KrkString * name = READ_STRING(operandWidth); @@ -3995,6 +4052,15 @@ static KrkValue run() { } break; } + case OP_DEL_PROPERTY_LONG: + case OP_DEL_PROPERTY: { + KrkString * name = READ_STRING(operandWidth); + if (!valueDelProperty(name)) { + krk_runtimeError(vm.exceptions.attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars); + goto _finishException; + } + break; + } case OP_INVOKE_GETTER: { KrkClass * type = AS_CLASS(krk_typeOf(1,(KrkValue[]){krk_peek(1)})); if (type->_getter) { @@ -4026,6 +4092,19 @@ static KrkValue run() { } break; } + case OP_INVOKE_DELETE: { + KrkClass * type = AS_CLASS(krk_typeOf(1,(KrkValue[]){krk_peek(1)})); + if (type->_delitem) { + krk_callSimple(OBJECT_VAL(type->_delitem), 2, 0); + } else { + if (type->_getter) { + krk_runtimeError(vm.exceptions.attributeError, "'%s' object is not mutable", krk_typeName(krk_peek(1))); + } else { + krk_runtimeError(vm.exceptions.attributeError, "'%s' object is not subscriptable", krk_typeName(krk_peek(1))); + } + } + break; + } case OP_SET_PROPERTY_LONG: case OP_SET_PROPERTY: { KrkString * name = READ_STRING(operandWidth); diff --git a/vm.h b/vm.h index 2099087..32c274d 100644 --- a/vm.h +++ b/vm.h @@ -41,6 +41,7 @@ typedef enum { METHOD_EQ, METHOD_ENTER, METHOD_EXIT, + METHOD_DELITEM, METHOD__MAX, } KrkSpecialMethods;