From 13bbc3ae2d35172854e75d68a513bcc1d93f3d20 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Thu, 14 Jan 2021 21:16:48 +0900 Subject: [PATCH] Change 'let' semantics to do unpacking; support unpacking more things --- .gitignore | 1 + README.md | 13 +++++-- chunk.h | 4 +-- compiler.c | 91 ++++++++++++++++++++++++++++++++++++++--------- debug.c | 2 +- test/day3.krk | 2 +- vm.c | 97 +++++++++++++++++++++++++++++++-------------------- vm.h | 4 +++ 8 files changed, 153 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 97c337f..581cced 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.o *.so kuroko +wasm/ diff --git a/README.md b/README.md index 164c67c..7164b0f 100644 --- a/README.md +++ b/README.md @@ -253,14 +253,23 @@ print(foo) # → 1 ``` -You may declare and define multiple variables on a single line: +You may declare and define multiple variables on a single line as follows: ```py -let a = 1, b = "test", c = object() +let a, b, c = 1, "test", object() print(a,b,c) # → 1 test ``` +The `let` statement can also be used to unpack some sequence types: + +```py +let t = (1, 2, 3) +let a, b, c = t +print(a,b,c) +# → 1 2 3 +``` + _**Note:** Identifier names, including for variables, functions, and classes, can be Unicode sequences. All non-ASCII codepoints are accepted as identifier characters._ ### Assignments diff --git a/chunk.h b/chunk.h index 6896591..ef1fa2e 100644 --- a/chunk.h +++ b/chunk.h @@ -71,7 +71,7 @@ typedef enum { OP_EXPAND_ARGS, OP_FINALIZE, OP_TUPLE, - OP_UNPACK_TUPLE, + OP_UNPACK, OP_PUSH_WITH, OP_CLEANUP_WITH, @@ -100,7 +100,7 @@ typedef enum { OP_INC_LONG, OP_KWARGS_LONG, OP_TUPLE_LONG, - OP_UNPACK_TUPLE_LONG, + OP_UNPACK_LONG, OP_DEL_GLOBAL_LONG, OP_DEL_PROPERTY_LONG, } KrkOpCode; diff --git a/compiler.c b/compiler.c index dc5ccc9..4a5e60d 100644 --- a/compiler.c +++ b/compiler.c @@ -614,16 +614,69 @@ static void expression() { parsePrecedence(PREC_ASSIGNMENT); } -static void varDeclaration() { - ssize_t ind = parseVariable("Expected variable name."); +static void letDeclaration(void) { + size_t argCount = 0; + size_t argSpace = 1; + ssize_t * args = GROW_ARRAY(ssize_t,NULL,0,1); + + do { + if (argSpace < argCount + 1) { + size_t old = argSpace; + argSpace = GROW_CAPACITY(old); + args = GROW_ARRAY(ssize_t,args,old,argSpace); + } + ssize_t ind = parseVariable("Expected variable name."); + if (current->scopeDepth > 0) { + /* Need locals space */ + args[argCount++] = current->localCount - 1; + emitByte(OP_NONE); + defineVariable(ind); + } else { + args[argCount++] = ind; + } + } while (match(TOKEN_COMMA)); if (match(TOKEN_EQUAL)) { - expression(); - } else { - emitByte(OP_NONE); + size_t expressionCount = 0; + do { + expressionCount++; + expression(); + } while (match(TOKEN_COMMA)); + if (expressionCount == 1 && argCount > 1) { + EMIT_CONSTANT_OP(OP_UNPACK, argCount); + } else if (expressionCount == 1 && argCount == 1) { + /* Do nothing */ + } else if (expressionCount == argCount) { + /* This is stupid but it flips the stuff around */ + EMIT_CONSTANT_OP(OP_TUPLE, argCount); + EMIT_CONSTANT_OP(OP_UNPACK, argCount); + } else if (expressionCount > 1 && argCount == 1) { + EMIT_CONSTANT_OP(OP_TUPLE, expressionCount); + } else { + error("Invalid sequence unpack in 'let' statement"); + } + if (current->scopeDepth > 0) { + for (size_t i = argCount; i > 0; i--) { + EMIT_CONSTANT_OP(OP_SET_LOCAL, args[i-1]); + emitByte(OP_POP); + } + } + } else if (current->scopeDepth == 0) { + /* Need to nil it */ + for (size_t i = 0; i < argCount; ++i) { + emitByte(OP_NONE); + } } - defineVariable(ind); + if (current->scopeDepth == 0) { + for (size_t i = argCount; i > 0; i--) { + defineVariable(args[i-1]); + } + } + + if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) { + error("Expected end of line after 'let' statement."); + } } static void synchronize() { @@ -651,12 +704,7 @@ static void declaration() { if (check(TOKEN_DEF)) { defDeclaration(); } else if (match(TOKEN_LET)) { - do { - varDeclaration(); - } while (match(TOKEN_COMMA)); - if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) { - error("Expected EOL after variable declaration.\n"); - } + letDeclaration(); } else if (check(TOKEN_CLASS)) { classDeclaration(); } else if (check(TOKEN_AT)) { @@ -1257,15 +1305,23 @@ static void forStatement() { ssize_t loopInd = current->localCount; ssize_t varCount = 0; + int matchedEquals = 0; do { - varDeclaration(); + ssize_t ind = parseVariable("Expected name for loop iterator."); + if (match(TOKEN_EQUAL)) { + matchedEquals = 1; + expression(); + } else { + emitByte(OP_NONE); + } + defineVariable(ind); varCount++; } while (match(TOKEN_COMMA)); int loopStart; int exitJump; - if (match(TOKEN_IN)) { + if (!matchedEquals && match(TOKEN_IN)) { /* ITERABLE.__iter__() */ beginScope(); @@ -1303,7 +1359,7 @@ static void forStatement() { if (varCount > 1) { EMIT_CONSTANT_OP(OP_GET_LOCAL, loopInd); - EMIT_CONSTANT_OP(OP_UNPACK_TUPLE, varCount); + EMIT_CONSTANT_OP(OP_UNPACK, varCount); for (ssize_t i = loopInd + varCount - 1; i >= loopInd; i--) { EMIT_CONSTANT_OP(OP_SET_LOCAL, i); emitByte(OP_POP); @@ -1790,7 +1846,8 @@ static void list(int canAssign) { ssize_t loopInd = current->localCount; ssize_t varCount = 0; do { - varDeclaration(); + defineVariable(parseVariable("Expected name for iteration variable.")); + emitByte(OP_NONE); defineVariable(loopInd); varCount++; } while (match(TOKEN_COMMA)); @@ -1838,7 +1895,7 @@ static void list(int canAssign) { /* Unpack tuple */ if (varCount > 1) { EMIT_CONSTANT_OP(OP_GET_LOCAL, loopInd); - EMIT_CONSTANT_OP(OP_UNPACK_TUPLE, varCount); + EMIT_CONSTANT_OP(OP_UNPACK, varCount); for (ssize_t i = loopInd + varCount - 1; i >= loopInd; i--) { EMIT_CONSTANT_OP(OP_SET_LOCAL, i); emitByte(OP_POP); diff --git a/debug.c b/debug.c index 9d02480..1ee18c6 100644 --- a/debug.c +++ b/debug.c @@ -144,7 +144,7 @@ size_t krk_disassembleInstruction(FILE * f, KrkFunction * func, size_t offset) { OPERAND(OP_CALL, (void)0) OPERAND(OP_INC, (void)0) OPERAND(OP_TUPLE, (void)0) - OPERAND(OP_UNPACK_TUPLE, (void)0) + OPERAND(OP_UNPACK, (void)0) JUMP(OP_JUMP,+) JUMP(OP_JUMP_IF_FALSE,+) JUMP(OP_JUMP_IF_TRUE,+) diff --git a/test/day3.krk b/test/day3.krk index 7800f63..5495e76 100644 --- a/test/day3.krk +++ b/test/day3.krk @@ -4,7 +4,7 @@ let lines = f.read().split('\n')[:-1] def count_trees(lines, x_off, y_off): let modulo = len(lines[0]) - let x = 0, y = 0, trees = 0 + let x, y, trees = 0, 0, 0 while y < len(lines): if lines[y][x % modulo] == '#': trees += 1 diff --git a/vm.c b/vm.c index dcfa78c..8611c96 100644 --- a/vm.c +++ b/vm.c @@ -443,6 +443,16 @@ static KrkValue _dict_key_at_index(int argc, KrkValue argv[]) { return OBJECT_VAL(outValue); } +static KrkValue _dict_nth_key_fast(size_t capacity, KrkTableEntry * entries, size_t index) { + size_t found = 0; + for (size_t i = 0; i < capacity; ++i) { + if (IS_KWARGS(entries[i].key)) continue; + if (found == index) return entries[i].key; + found++; + } + return NONE_VAL(); +} + /** * list.__init__() */ @@ -3323,33 +3333,31 @@ void krk_initVM(int flags) { * the list and dict types by pulling them out of the global namespace, * as they were exported by builtins.krk */ krk_tableGet(&vm.builtins->fields,OBJECT_VAL(S("list")),&val); - KrkClass * _class = AS_CLASS(val); - 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); - krk_defineNative(&_class->methods, ".__iter__", _list_iter); - krk_defineNative(&_class->methods, ".append", _list_append); - krk_defineNative(&_class->methods, ".pop", _list_pop); - krk_defineNative(&_class->methods, "._extend_fast", _list_extend_fast); - krk_finalizeClass(_class); + vm.baseClasses.listClass = AS_CLASS(val); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__init__", _list_init); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__get__", _list_get); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__set__", _list_set); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__delitem__", _list_pop); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__len__", _list_len); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__contains__", _list_contains); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__getslice__", _list_slice); + krk_defineNative(&vm.baseClasses.listClass->methods, ".__iter__", _list_iter); + krk_defineNative(&vm.baseClasses.listClass->methods, ".append", _list_append); + krk_defineNative(&vm.baseClasses.listClass->methods, ".pop", _list_pop); + krk_defineNative(&vm.baseClasses.listClass->methods, "._extend_fast", _list_extend_fast); + krk_finalizeClass(vm.baseClasses.listClass); krk_tableGet(&vm.builtins->fields,OBJECT_VAL(S("dict")),&val); - _class = AS_CLASS(val); - 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); - - /* These are used to for dict.keys() to create the iterators. */ - krk_defineNative(&_class->methods, ".capacity", _dict_capacity); - krk_defineNative(&_class->methods, "._key_at_index", _dict_key_at_index); + vm.baseClasses.dictClass = AS_CLASS(val); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".__init__", _dict_init); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".__get__", _dict_get); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".__set__", _dict_set); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".__delitem__", _dict_delitem); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".__len__", _dict_len); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".__contains__", _dict_contains); + krk_defineNative(&vm.baseClasses.dictClass->methods, ".capacity", _dict_capacity); + krk_defineNative(&vm.baseClasses.dictClass->methods, "._key_at_index", _dict_key_at_index); + krk_finalizeClass(vm.baseClasses.dictClass); } /* The VM is now ready to start executing code. */ @@ -4192,22 +4200,35 @@ static KrkValue run() { } break; } - case OP_UNPACK_TUPLE_LONG: - case OP_UNPACK_TUPLE: { + case OP_UNPACK_LONG: + case OP_UNPACK: { size_t count = readBytes(frame, operandWidth); - KrkValue tuple = krk_peek(0); - if (!IS_TUPLE(tuple)) { - krk_runtimeError(vm.exceptions.typeError, "Can not unpack non-tuple '%s'", krk_typeName(tuple)); - goto _finishException; - } else if (AS_TUPLE(tuple)->values.count != count) { - krk_runtimeError(vm.exceptions.valueError, "Wrong number of values to unpack (wanted %d, got %d)", (int)count, (int)AS_TUPLE(tuple)->values.count); + KrkValue sequence = krk_peek(0); + /* First figure out what it is and if we can unpack it. */ +#define unpackArray(counter, indexer) do { \ + if (counter != count) { \ + krk_runtimeError(vm.exceptions.valueError, "Wrong number of values to unpack (wanted %d, got %d)", (int)count, (int)counter); \ + } \ + for (size_t i = 1; i < counter; ++i) { \ + krk_push(indexer); \ + } \ + size_t i = 0; \ + vm.stackTop[-count] = indexer; \ + } while (0) + if (IS_TUPLE(sequence)) { + unpackArray(AS_TUPLE(sequence)->values.count, AS_TUPLE(sequence)->values.values[i]); + } else if (IS_INSTANCE(sequence) && AS_INSTANCE(sequence)->_class == vm.baseClasses.listClass) { + KrkValue _list_internal = OBJECT_VAL(AS_INSTANCE(sequence)->_internal); + unpackArray(AS_LIST(_list_internal)->count, AS_LIST(_list_internal)->values[i]); + } else if (IS_INSTANCE(sequence) && AS_INSTANCE(sequence)->_class == vm.baseClasses.dictClass) { + KrkValue _dict_internal = OBJECT_VAL(AS_INSTANCE(sequence)->_internal); + unpackArray(AS_DICT(_dict_internal)->count, _dict_nth_key_fast(AS_DICT(_dict_internal)->capacity, AS_DICT(_dict_internal)->entries, i)); + } else if (IS_STRING(sequence)) { + unpackArray(AS_STRING(sequence)->codesLength, _string_get(2,(KrkValue[]){sequence,INTEGER_VAL(i)})); + } else { + krk_runtimeError(vm.exceptions.notImplementedError, "Can not iterate '%s' in unpack.", krk_typeName(sequence)); goto _finishException; } - /* Unpack from 1 to end, then unpack 0 into bottom slot */ - for (size_t i = 1; i < AS_TUPLE(tuple)->values.count; ++i) { - krk_push(AS_TUPLE(tuple)->values.values[i]); - } - vm.stackTop[-count] = AS_TUPLE(tuple)->values.values[0]; break; } case OP_PUSH_WITH: { diff --git a/vm.h b/vm.h index 32c274d..2ba7605 100644 --- a/vm.h +++ b/vm.h @@ -104,6 +104,10 @@ typedef struct { KrkClass * rangeiteratorClass; KrkClass * striteratorClass; KrkClass * tupleiteratorClass; + + /* These are actually defined in builtins.krk and are real instances */ + KrkClass * listClass; + KrkClass * dictClass; } baseClasses; KrkValue currentException;