From b0c1fe5b135dc2809e8db8a09b1046090529dfca Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 19 May 2025 11:15:44 +0800 Subject: [PATCH 1/8] fix doc according to https://github.com/pocketpy/pocketpy/pull/366 --- include/pocketpy/pocketpy.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index e99402f5..dff6af1d 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -90,7 +90,8 @@ enum py_CompileMode { EXEC_MODE, EVAL_MODE, SINGLE_MODE }; /// Initialize pocketpy and the default VM. PK_API void py_initialize(); -/// Finalize pocketpy and free all VMs. +/// Finalize pocketpy and free all VMs. This opearation is irreversible. +/// After this call, you cannot use any function from this header anymore. PK_API void py_finalize(); /// Get the current VM index. PK_API int py_currentvm(); From ae13cc6ea9778b7a6251bced73be194d14f7daf1 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 19 May 2025 14:03:18 +0800 Subject: [PATCH 2/8] fix https://github.com/pocketpy/pocketpy/issues/338 --- README.md | 1 + docs/features/basic.md | 42 +++++----- docs/features/differences.md | 2 +- docs/retype.yml | 2 +- include/pocketpy/compiler/lexer.h | 130 +++++++++++++++++++++++------ include/pocketpy/config.h | 6 +- include/pocketpy/xmacros/opcodes.h | 1 + src/compiler/compiler.c | 100 ++++++++++++++++++---- src/compiler/lexer.c | 12 +-- src/interpreter/ceval.c | 7 ++ tests/15_controlflow.py | 56 +++++++++++-- 11 files changed, 278 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 093f5832..85602abe 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ __ERROR: | Type Annotation | `def f(a:int, b:float=1)` | ✅ | | Generator | `yield i` | ✅ | | Decorator | `@cache` | ✅ | +| Match Case | `match code: case 200:` | ✅ | ## Performance diff --git a/docs/features/basic.md b/docs/features/basic.md index c9af4ff7..0ece257a 100644 --- a/docs/features/basic.md +++ b/docs/features/basic.md @@ -5,30 +5,30 @@ order: 100 --- The following table shows the basic features of pkpy with respect to [cpython](https://github.com/python/cpython). -The features marked with `YES` are supported, and the features marked with `NO` are not supported. | Name | Example | Supported | | --------------- | ------------------------------- | --------- | -| If Else | `if..else..elif` | YES | -| Loop | `for/while/break/continue` | YES | -| Function | `def f(x,*args,y=1):` | YES | -| Subclass | `class A(B):` | YES | -| List | `[1, 2, 'a']` | YES | -| ListComp | `[i for i in range(5)]` | YES | -| Slice | `a[1:2], a[:2], a[1:]` | YES | -| Tuple | `(1, 2, 'a')` | YES | -| Dict | `{'a': 1, 'b': 2}` | YES | -| F-String | `f'value is {x}'` | YES | -| Unpacking | `a, b = 1, 2` | YES | -| Star Unpacking | `a, *b = [1, 2, 3]` | YES | -| Exception | `raise/try..catch..finally` | YES | -| Dynamic Code | `eval()/exec()` | YES | -| Reflection | `hasattr()/getattr()/setattr()` | YES | -| Import | `import/from..import` | YES | -| Context Block | `with as :` | YES | -| Type Annotation | `def f(a:int, b:float=1)` | YES | -| Generator | `yield i` | YES | -| Decorator | `@cache` | YES | +| If Else | `if..else..elif` | ✅ | +| Loop | `for/while/break/continue` | ✅ | +| Function | `def f(x,*args,y=1):` | ✅ | +| Subclass | `class A(B):` | ✅ | +| List | `[1, 2, 'a']` | ✅ | +| ListComp | `[i for i in range(5)]` | ✅ | +| Slice | `a[1:2], a[:2], a[1:]` | ✅ | +| Tuple | `(1, 2, 'a')` | ✅ | +| Dict | `{'a': 1, 'b': 2}` | ✅ | +| F-String | `f'value is {x}'` | ✅ | +| Unpacking | `a, b = 1, 2` | ✅ | +| Star Unpacking | `a, *b = [1, 2, 3]` | ✅ | +| Exception | `raise/try..catch..finally` | ✅ | +| Dynamic Code | `eval()/exec()` | ✅ | +| Reflection | `hasattr()/getattr()/setattr()` | ✅ | +| Import | `import/from..import` | ✅ | +| Context Block | `with as :` | ✅ | +| Type Annotation | `def f(a:int, b:float=1)` | ✅ | +| Generator | `yield i` | ✅ | +| Decorator | `@cache` | ✅ | +| Match Case | `match code: case 200:` | ✅ | ## Supported magic methods diff --git a/docs/features/differences.md b/docs/features/differences.md index 5be19b7a..a5b691ac 100644 --- a/docs/features/differences.md +++ b/docs/features/differences.md @@ -36,4 +36,4 @@ The easiest way to test a feature is to [try it on your browser](https://pocketp 5. In a starred unpacked assignment, e.g. `a, b, *c = x`, the starred variable can only be presented in the last position. `a, *b, c = x` is not supported. 6. A `Tab` is equivalent to 4 spaces. You can mix `Tab` and spaces in indentation, but it is not recommended. 7. A return, break, continue in try/except/with block will make the finally block not executed. - +8. `match` is a keyword and `match..case` is equivalent to `if..elif..else`. diff --git a/docs/retype.yml b/docs/retype.yml index e4f41e54..89232f96 100644 --- a/docs/retype.yml +++ b/docs/retype.yml @@ -3,7 +3,7 @@ output: .retype url: https://pocketpy.dev branding: title: pocketpy - label: v2.0.9 + label: v2.1.0 logo: "./static/logo.png" favicon: "./static/logo.png" meta: diff --git a/include/pocketpy/compiler/lexer.h b/include/pocketpy/compiler/lexer.h index 1cb00e2d..52bb2b04 100644 --- a/include/pocketpy/compiler/lexer.h +++ b/include/pocketpy/compiler/lexer.h @@ -1,39 +1,118 @@ #pragma once #include "pocketpy/common/str.h" -#include "pocketpy/common/vector.h" #include "pocketpy/objects/sourcedata.h" #include "pocketpy/objects/error.h" #include extern const char* TokenSymbols[]; -typedef enum TokenIndex{ - TK_EOF, TK_EOL, TK_SOF, - TK_ID, TK_NUM, TK_STR, TK_FSTR_BEGIN, TK_FSTR_CPNT, TK_FSTR_SPEC, TK_FSTR_END, TK_BYTES, TK_IMAG, - TK_INDENT, TK_DEDENT, +typedef enum TokenIndex { + TK_EOF, + TK_EOL, + TK_SOF, + TK_ID, + TK_NUM, + TK_STR, + TK_FSTR_BEGIN, + TK_FSTR_CPNT, + TK_FSTR_SPEC, + TK_FSTR_END, + TK_BYTES, + TK_IMAG, + TK_INDENT, + TK_DEDENT, /***************/ - TK_IS_NOT, TK_NOT_IN, TK_YIELD_FROM, + TK_IS_NOT, + TK_NOT_IN, + TK_YIELD_FROM, /***************/ - TK_ADD, TK_IADD, TK_SUB, TK_ISUB, - TK_MUL, TK_IMUL, TK_DIV, TK_IDIV, TK_FLOORDIV, TK_IFLOORDIV, TK_MOD, TK_IMOD, - TK_AND, TK_IAND, TK_OR, TK_IOR, TK_XOR, TK_IXOR, - TK_LSHIFT, TK_ILSHIFT, TK_RSHIFT, TK_IRSHIFT, + TK_ADD, + TK_IADD, + TK_SUB, + TK_ISUB, + TK_MUL, + TK_IMUL, + TK_DIV, + TK_IDIV, + TK_FLOORDIV, + TK_IFLOORDIV, + TK_MOD, + TK_IMOD, + TK_AND, + TK_IAND, + TK_OR, + TK_IOR, + TK_XOR, + TK_IXOR, + TK_LSHIFT, + TK_ILSHIFT, + TK_RSHIFT, + TK_IRSHIFT, /***************/ - TK_LPAREN, TK_RPAREN, TK_LBRACKET, TK_RBRACKET, TK_LBRACE, TK_RBRACE, - TK_DOT, TK_DOTDOT, TK_DOTDOTDOT, TK_COMMA, TK_COLON, TK_SEMICOLON, - TK_POW, TK_ARROW, TK_HASH, TK_DECORATOR, - TK_GT, TK_LT, TK_ASSIGN, TK_EQ, TK_NE, TK_GE, TK_LE, TK_INVERT, + TK_LPAREN, + TK_RPAREN, + TK_LBRACKET, + TK_RBRACKET, + TK_LBRACE, + TK_RBRACE, + TK_DOT, + TK_DOTDOT, + TK_DOTDOTDOT, + TK_COMMA, + TK_COLON, + TK_SEMICOLON, + TK_POW, + TK_ARROW, + TK_HASH, + TK_DECORATOR, + TK_GT, + TK_LT, + TK_ASSIGN, + TK_EQ, + TK_NE, + TK_GE, + TK_LE, + TK_INVERT, /***************/ - TK_FALSE, TK_NONE, TK_TRUE, TK_AND_KW, TK_AS, TK_ASSERT, TK_BREAK, TK_CLASS, TK_CONTINUE, - TK_DEF, TK_DEL, TK_ELIF, TK_ELSE, TK_EXCEPT, TK_FINALLY, TK_FOR, TK_FROM, TK_GLOBAL, - TK_IF, TK_IMPORT, TK_IN, TK_IS, TK_LAMBDA, TK_NOT_KW, TK_OR_KW, TK_PASS, TK_RAISE, TK_RETURN, - TK_TRY, TK_WHILE, TK_WITH, TK_YIELD, + TK_FALSE, + TK_NONE, + TK_TRUE, + TK_AND_KW, + TK_AS, + TK_ASSERT, + TK_BREAK, + TK_CLASS, + TK_CONTINUE, + TK_DEF, + TK_DEL, + TK_ELIF, + TK_ELSE, + TK_EXCEPT, + TK_FINALLY, + TK_FOR, + TK_FROM, + TK_GLOBAL, + TK_IF, + TK_IMPORT, + TK_IN, + TK_IS, + TK_LAMBDA, + TK_MATCH, + TK_NOT_KW, + TK_OR_KW, + TK_PASS, + TK_RAISE, + TK_RETURN, + TK_TRY, + TK_WHILE, + TK_WITH, + TK_YIELD, /***************/ TK__COUNT__ } TokenIndex; -enum TokenValueIndex{ +enum TokenValueIndex { TokenValue_EMPTY = 0, TokenValue_I64 = 1, TokenValue_F64 = 2, @@ -42,10 +121,11 @@ enum TokenValueIndex{ typedef struct TokenValue { enum TokenValueIndex index; // 0: empty + union { - int64_t _i64; // 1 - double _f64; // 2 - c11_string* _str; // 3 + int64_t _i64; // 1 + double _f64; // 2 + c11_string* _str; // 3 }; } TokenValue; @@ -69,7 +149,8 @@ enum Precedence { /* https://docs.python.org/3/reference/expressions.html#comparisons * Unlike C, all comparison operations in Python have the same priority, * which is lower than that of any arithmetic, shifting or bitwise operation. - * Also unlike C, expressions like a < b < c have the interpretation that is conventional in mathematics. + * Also unlike C, expressions like a < b < c have the interpretation that is conventional in + * mathematics. */ PREC_COMPARISION, // < > <= >= != ==, in / is / is not / not in PREC_BITWISE_OR, // | @@ -86,4 +167,5 @@ enum Precedence { Error* Lexer__process(SourceData_ src, Token** out_tokens, int* out_length); -#define Token__sv(self) (c11_sv){(self)->start, (self)->length} +#define Token__sv(self) \ + (c11_sv) { (self)->start, (self)->length } diff --git a/include/pocketpy/config.h b/include/pocketpy/config.h index 43af92a5..18ca5d68 100644 --- a/include/pocketpy/config.h +++ b/include/pocketpy/config.h @@ -1,10 +1,10 @@ #pragma once // clang-format off -#define PK_VERSION "2.0.9" +#define PK_VERSION "2.1.0" #define PK_VERSION_MAJOR 2 -#define PK_VERSION_MINOR 0 -#define PK_VERSION_PATCH 9 +#define PK_VERSION_MINOR 1 +#define PK_VERSION_PATCH 0 /*************** feature settings ***************/ // Whether to compile os-related modules or not diff --git a/include/pocketpy/xmacros/opcodes.h b/include/pocketpy/xmacros/opcodes.h index b98592b0..d27a66d1 100644 --- a/include/pocketpy/xmacros/opcodes.h +++ b/include/pocketpy/xmacros/opcodes.h @@ -56,6 +56,7 @@ OPCODE(IS_OP) OPCODE(CONTAINS_OP) /**************************/ OPCODE(JUMP_FORWARD) +OPCODE(POP_JUMP_IF_NOT_MATCH) OPCODE(POP_JUMP_IF_FALSE) OPCODE(POP_JUMP_IF_TRUE) OPCODE(JUMP_IF_TRUE_OR_POP) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 188850f1..53e4fd03 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -1,9 +1,9 @@ #include "pocketpy/compiler/compiler.h" +#include "pocketpy/common/vector.h" #include "pocketpy/compiler/lexer.h" #include "pocketpy/objects/base.h" #include "pocketpy/objects/codeobject.h" #include "pocketpy/objects/sourcedata.h" -#include "pocketpy/objects/object.h" #include "pocketpy/common/sstream.h" #include #include @@ -1377,6 +1377,15 @@ static bool is_expression(Compiler* self, bool allow_slice) { #define match(expected) (curr()->type == expected ? (++self->i) : 0) +static bool match_id_by_str(Compiler* self, const char* name) { + if(curr()->type == TK_ID) { + bool ok = c11__sveq2(Token__sv(curr()), name); + if(ok) advance(); + return ok; + } + return false; +} + static bool match_newlines_impl(Compiler* self) { bool consumed = false; if(curr()->type == TK_EOL) { @@ -1969,10 +1978,10 @@ static Error* consume_type_hints_sv(Compiler* self, c11_sv* out) { static Error* compile_stmt(Compiler* self); -static Error* compile_block_body(Compiler* self, PrattCallback callback) { +static Error* compile_block_body(Compiler* self) { Error* err; - assert(callback != NULL); consume(TK_COLON); + if(curr()->type != TK_EOL && curr()->type != TK_EOF) { while(true) { check(compile_stmt(self)); @@ -1988,7 +1997,7 @@ static Error* compile_block_body(Compiler* self, PrattCallback callback) { consume(TK_INDENT); while(curr()->type != TK_DEDENT) { match_newlines(); - check(callback(self)); + check(compile_stmt(self)); match_newlines(); } consume(TK_DEDENT); @@ -2000,7 +2009,7 @@ static Error* compile_if_stmt(Compiler* self) { check(EXPR(self)); // condition Ctx__s_emit_top(ctx()); int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, prev()->line); - err = compile_block_body(self, compile_stmt); + err = compile_block_body(self); if(err) return err; if(match(TK_ELIF)) { int exit_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, prev()->line); @@ -2010,7 +2019,7 @@ static Error* compile_if_stmt(Compiler* self) { } else if(match(TK_ELSE)) { int exit_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, prev()->line); Ctx__patch_jump(ctx(), patch); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__patch_jump(ctx(), exit_patch); } else { Ctx__patch_jump(ctx(), patch); @@ -2018,6 +2027,56 @@ static Error* compile_if_stmt(Compiler* self) { return NULL; } +static Error* compile_match_case(Compiler* self, c11_vector* patches) { + Error* err; + bool is_case_default = false; + + check(EXPR(self)); // condition + Ctx__s_emit_top(ctx()); + + consume(TK_COLON); + + bool consumed = match_newlines(); + if(!consumed) return SyntaxError(self, "expected a new line after ':'"); + + consume(TK_INDENT); + while(curr()->type != TK_DEDENT) { + match_newlines(); + + if(match_id_by_str(self, "case")) { + if(is_case_default) return SyntaxError(self, "case _: must be the last one"); + is_case_default = match_id_by_str(self, "_"); + + if(!is_case_default) { + Ctx__emit_(ctx(), OP_DUP_TOP, BC_NOARG, prev()->line); + check(EXPR(self)); // expr + Ctx__s_emit_top(ctx()); + int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_NOT_MATCH, BC_NOARG, prev()->line); + check(compile_block_body(self)); + int break_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, prev()->line); + c11_vector__push(int, patches, break_patch); + Ctx__patch_jump(ctx(), patch); + } else { + check(compile_block_body(self)); + int break_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, prev()->line); + c11_vector__push(int, patches, break_patch); + } + } + + match_newlines(); + } + consume(TK_DEDENT); + + if(patches->length == 0) return SyntaxError(self, "invalid syntax"); + + for(int i = 0; i < patches->length; i++) { + int patch = c11__getitem(int, patches, i); + Ctx__patch_jump(ctx(), patch); + } + Ctx__emit_(ctx(), OP_POP_TOP, BC_NOARG, prev()->line); + return NULL; +} + static Error* compile_while_loop(Compiler* self) { Error* err; int block = Ctx__enter_block(ctx(), CodeBlockType_WHILE_LOOP); @@ -2025,13 +2084,13 @@ static Error* compile_while_loop(Compiler* self) { check(EXPR(self)); // condition Ctx__s_emit_top(ctx()); int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, prev()->line); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__emit_jump(ctx(), block_start, BC_KEEPLINE); Ctx__patch_jump(ctx(), patch); Ctx__exit_block(ctx()); // optional else clause if(match(TK_ELSE)) { - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); CodeBlock* p_block = c11__at(CodeBlock, &ctx()->co->blocks, block); p_block->end2 = ctx()->co->codes.length; } @@ -2054,12 +2113,12 @@ static Error* compile_for_loop(Compiler* self) { // this error occurs in `vars` instead of this line, but...nevermind return SyntaxError(self, "invalid syntax"); } - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__emit_jump(ctx(), block_start, BC_KEEPLINE); Ctx__exit_block(ctx()); // optional else clause if(match(TK_ELSE)) { - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); CodeBlock* p_block = c11__at(CodeBlock, &ctx()->co->blocks, block); p_block->end2 = ctx()->co->codes.length; } @@ -2289,7 +2348,7 @@ static Error* compile_function(Compiler* self, int decorators) { consume(TK_RPAREN); } if(match(TK_ARROW)) check(consume_type_hints(self)); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); check(pop_context(self)); if(decl->code.codes.length >= 2) { @@ -2354,7 +2413,7 @@ static Error* compile_class(Compiler* self, int decorators) { if(it->is_compiling_class) return SyntaxError(self, "nested class is not allowed"); } ctx()->is_compiling_class = true; - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); ctx()->is_compiling_class = false; Ctx__s_emit_decorators(ctx(), decorators); @@ -2489,7 +2548,7 @@ static Error* compile_try_except(Compiler* self) { Ctx__enter_block(ctx(), CodeBlockType_TRY); Ctx__emit_(ctx(), OP_TRY_ENTER, BC_NOARG, prev()->line); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); // https://docs.python.org/3/reference/compound_stmts.html#finally-clause /* If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, @@ -2515,7 +2574,7 @@ static Error* compile_try_except(Compiler* self) { Ctx__emit_(ctx(), OP_BEGIN_FINALLY, BC_NOARG, prev()->line); // finally only, no except block Ctx__enter_block(ctx(), CodeBlockType_FINALLY); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__exit_block(ctx()); Ctx__emit_(ctx(), OP_END_FINALLY, BC_NOARG, BC_KEEPLINE); // re-raise if needed @@ -2551,7 +2610,7 @@ static Error* compile_try_except(Compiler* self) { Ctx__emit_store_name(ctx(), name_scope(self), as_name, BC_KEEPLINE); } Ctx__enter_block(ctx(), CodeBlockType_EXCEPT); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__exit_block(ctx()); Ctx__emit_(ctx(), OP_END_EXC_HANDLING, BC_NOARG, BC_KEEPLINE); patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE); @@ -2568,7 +2627,7 @@ static Error* compile_try_except(Compiler* self) { if(match(TK_FINALLY)) { Ctx__emit_(ctx(), OP_BEGIN_FINALLY, BC_NOARG, prev()->line); Ctx__enter_block(ctx(), CodeBlockType_FINALLY); - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__exit_block(ctx()); Ctx__emit_(ctx(), OP_END_FINALLY, BC_NOARG, BC_KEEPLINE); } @@ -2629,6 +2688,13 @@ static Error* compile_stmt(Compiler* self) { break; /*************************************************/ case TK_IF: check(compile_if_stmt(self)); break; + case TK_MATCH: { + c11_vector patches; + c11_vector__ctor(&patches, sizeof(int)); + check(compile_match_case(self, &patches)); + c11_vector__dtor(&patches); + break; + } case TK_WHILE: check(compile_while_loop(self)); break; case TK_FOR: check(compile_for_loop(self)); break; case TK_IMPORT: check(compile_normal_import(self)); break; @@ -2701,7 +2767,7 @@ static Error* compile_stmt(Compiler* self) { // discard `__enter__()`'s return value Ctx__emit_(ctx(), OP_POP_TOP, BC_NOARG, BC_KEEPLINE); } - check(compile_block_body(self, compile_stmt)); + check(compile_block_body(self)); Ctx__emit_(ctx(), OP_WITH_EXIT, BC_NOARG, prev()->line); Ctx__exit_block(ctx()); } break; diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index 85101c90..4359aa4e 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -1,4 +1,3 @@ -#include "pocketpy/common/smallmap.h" #include "pocketpy/common/sstream.h" #include "pocketpy/common/vector.h" #include "pocketpy/compiler/lexer.h" @@ -280,20 +279,20 @@ static Error* _eat_string(Lexer* self, c11_sbuf* buff, char quote, enum StringTy case 'x': { char hex[3] = {eatchar(self), eatchar(self), '\0'}; int code; - if (sscanf(hex, "%x", &code) != 1 || code > 0xFF) { + if(sscanf(hex, "%x", &code) != 1 || code > 0xFF) { return LexerError(self, "invalid hex escape"); } - if (type == NORMAL_BYTES) { + if(type == NORMAL_BYTES) { // Bytes literals: write raw byte c11_sbuf__write_char(buff, (char)code); } else { // Regular strings: encode as UTF-8 - if (code <= 0x7F) { + if(code <= 0x7F) { c11_sbuf__write_char(buff, (char)code); } else { // Encode as 2-byte UTF-8 for code points 0x80-0xFF - c11_sbuf__write_char(buff, 0xC0 | (code >> 6)); // Leading byte - c11_sbuf__write_char(buff, 0x80 | (code & 0x3F)); // Continuation byte + c11_sbuf__write_char(buff, 0xC0 | (code >> 6)); // Leading byte + c11_sbuf__write_char(buff, 0x80 | (code & 0x3F)); // Continuation byte } } } break; @@ -712,6 +711,7 @@ const char* TokenSymbols[] = { "in", "is", "lambda", + "match", "not", "or", "pass", diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index d96e4274..90ebfd49 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -619,6 +619,13 @@ FrameResult VM__run_top_frame(VM* self) { } /*****************************************/ case OP_JUMP_FORWARD: DISPATCH_JUMP((int16_t)byte.arg); + case OP_POP_JUMP_IF_NOT_MATCH: { + int res = py_equal(SECOND(), TOP()); + if(res < 0) goto __ERROR; + STACK_SHRINK(2); + if(!res) DISPATCH_JUMP((int16_t)byte.arg); + DISPATCH(); + } case OP_POP_JUMP_IF_FALSE: { int res = py_bool(TOP()); if(res < 0) goto __ERROR; diff --git a/tests/15_controlflow.py b/tests/15_controlflow.py index 672d91c6..6837df08 100644 --- a/tests/15_controlflow.py +++ b/tests/15_controlflow.py @@ -124,11 +124,51 @@ else: x = 3 assert x == 2 -# t = 0 -# for i in range(5): -# try: -# break -# except: -# pass -# t = 1 -# assert t == 0 \ No newline at end of file +# match case + +case, _ = 1, 2 +assert case == 1 and _ == 2 + +match (404 * 1): + case 200: + assert False + case 404: + assert True + case _: assert False + +match (555 * 1): + case 200: + assert False + case 404: assert False + case _: + assert True + +match (555 * 1): + case 200: + assert False + case 404: + assert False + # no default case + + +def f(case): + match case: + case 200: + return True + case 404: + return False + case _: + return False + +assert f(200) == True + +# extras + +t = 0 +for i in range(5): + try: + break + except: + pass + t = 1 +assert t == 0 From e473fe7f050ab050c096f108fcceff3e89d582ee Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 19 May 2025 14:06:20 +0800 Subject: [PATCH 3/8] fix a bug --- src/compiler/compiler.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 53e4fd03..c2189c09 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -2061,14 +2061,14 @@ static Error* compile_match_case(Compiler* self, c11_vector* patches) { int break_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, prev()->line); c11_vector__push(int, patches, break_patch); } + } else { + return SyntaxError(self, "expected 'case', got '%s'", TokenSymbols[curr()->type]); } match_newlines(); } consume(TK_DEDENT); - if(patches->length == 0) return SyntaxError(self, "invalid syntax"); - for(int i = 0; i < patches->length; i++) { int patch = c11__getitem(int, patches, i); Ctx__patch_jump(ctx(), patch); From fac83607f06d9c0cd22791ed535d8bcdcabf7977 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 19 May 2025 14:11:48 +0800 Subject: [PATCH 4/8] fix a bug --- src/compiler/compiler.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index c2189c09..f97e5f70 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -2058,8 +2058,6 @@ static Error* compile_match_case(Compiler* self, c11_vector* patches) { Ctx__patch_jump(ctx(), patch); } else { check(compile_block_body(self)); - int break_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, prev()->line); - c11_vector__push(int, patches, break_patch); } } else { return SyntaxError(self, "expected 'case', got '%s'", TokenSymbols[curr()->type]); From c540e7ac9cf3e3e1baee5a33a5163cb61c65a40f Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 20 May 2025 11:30:24 +0800 Subject: [PATCH 5/8] improve options --- CMakeLists.txt | 41 ++++++++++++++++------------------------- CMakeOptions.txt | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 CMakeOptions.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index df0a57f2..a8dc8156 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,19 +5,14 @@ project(pocketpy) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) -# use UNITY_BUILD if CMake version >= 3.16 -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16") - option(PK_BUILD_WITH_UNITY "" TRUE) -else() - option(PK_BUILD_WITH_UNITY "" FALSE) -endif() +include(CMakeOptions.txt) if(WIN32) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() if(MSVC) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /jumptablerdata /GS-") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /jumptablerdata") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /experimental:c11atomics") add_compile_options(/wd4267 /wd4244) @@ -25,6 +20,10 @@ if(MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Ox") add_definitions(-DNDEBUG) endif() + + if(PK_ENABLE_DETERMINISM) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Oi-") + endif() else() if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") @@ -35,44 +34,32 @@ else() if(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-shorten-64-to-32") endif() + + if(PK_ENABLE_DETERMINISM) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexcess-precision=standard -ffp-contract=off") + endif() endif() include_directories(${CMAKE_CURRENT_LIST_DIR}/include) file(GLOB_RECURSE POCKETPY_SRC ${CMAKE_CURRENT_LIST_DIR}/src/*.c) -option(PK_ENABLE_OS "" OFF) + if(PK_ENABLE_OS) add_definitions(-DPK_ENABLE_OS=1) endif() -option(PK_BUILD_MODULE_LZ4 "" OFF) if(PK_BUILD_MODULE_LZ4) add_subdirectory(3rd/lz4) include_directories(3rd/lz4) add_definitions(-DPK_BUILD_MODULE_LZ4) endif() -option(PK_BUILD_MODULE_LIBHV "" OFF) if(PK_BUILD_MODULE_LIBHV) add_subdirectory(3rd/libhv) include_directories(3rd/libhv/include) add_definitions(-DPK_BUILD_MODULE_LIBHV) endif() -# PK_IS_MAIN determines whether the project is being used from root -# or if it is added as a dependency (through add_subdirectory for example). -if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") - set(PK_IS_MAIN TRUE) - option(PK_BUILD_SHARED_LIB "Build shared library" OFF) - option(PK_BUILD_STATIC_LIB "Build static library" OFF) -else() - set(PK_IS_MAIN FALSE) - option(PK_BUILD_SHARED_LIB "Build shared library" OFF) - option(PK_BUILD_STATIC_LIB "Build static library" ON) -endif() - -option(PK_BUILD_STATIC_MAIN "Build static main" OFF) - if(PK_BUILD_SHARED_LIB) message(">> Building shared library") add_library(${PROJECT_NAME} SHARED ${POCKETPY_SRC}) @@ -92,7 +79,11 @@ else() endif() if(UNIX AND NOT APPLE) - target_link_libraries(${PROJECT_NAME} m) + if(NOT PK_ENABLE_DETERMINISM) + # use platform libm + target_link_libraries(${PROJECT_NAME} m) + endif() + if(PK_ENABLE_OS) target_link_libraries(${PROJECT_NAME} dl) endif() diff --git a/CMakeOptions.txt b/CMakeOptions.txt new file mode 100644 index 00000000..d9cefc70 --- /dev/null +++ b/CMakeOptions.txt @@ -0,0 +1,29 @@ +# use UNITY_BUILD if CMake version >= 3.16 +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16") + option(PK_BUILD_WITH_UNITY "" TRUE) +else() + option(PK_BUILD_WITH_UNITY "" FALSE) +endif() + +# system features +option(PK_ENABLE_OS "" OFF) +option(PK_ENABLE_DETERMINISM "" FALSE) + +# modules +option(PK_BUILD_MODULE_LZ4 "" OFF) +option(PK_BUILD_MODULE_LIBHV "" OFF) + + +# PK_IS_MAIN determines whether the project is being used from root +# or if it is added as a dependency (through add_subdirectory for example). +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + set(PK_IS_MAIN TRUE) + option(PK_BUILD_SHARED_LIB "Build shared library" OFF) + option(PK_BUILD_STATIC_LIB "Build static library" OFF) +else() + set(PK_IS_MAIN FALSE) + option(PK_BUILD_SHARED_LIB "Build shared library" OFF) + option(PK_BUILD_STATIC_LIB "Build static library" ON) +endif() + +option(PK_BUILD_STATIC_MAIN "Build static main" OFF) \ No newline at end of file From f9320f8a3e36c73c14d3a23377080bf7d0973ed5 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 20 May 2025 18:16:11 +0800 Subject: [PATCH 6/8] add `PK_ENABLE_WATCHDOG` --- CMakeLists.txt | 4 +++ CMakeOptions.txt | 1 + include/pocketpy/config.h | 4 +++ include/pocketpy/interpreter/vm.h | 6 ++++ include/pocketpy/pocketpy.h | 12 ++++++++ include/typings/pkpy.pyi | 13 +++++++++ src/interpreter/ceval.c | 13 +++++++++ src/interpreter/vm.c | 1 + src/modules/pkpy.c | 47 ++++++++++++++++++++++++++++++- 9 files changed, 100 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8dc8156..68afcf7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,10 @@ if(PK_ENABLE_OS) add_definitions(-DPK_ENABLE_OS=1) endif() +if(PK_ENABLE_WATCHDOG) + add_definitions(-DPK_ENABLE_WATCHDOG=1) +endif() + if(PK_BUILD_MODULE_LZ4) add_subdirectory(3rd/lz4) include_directories(3rd/lz4) diff --git a/CMakeOptions.txt b/CMakeOptions.txt index d9cefc70..4e21ab44 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -8,6 +8,7 @@ endif() # system features option(PK_ENABLE_OS "" OFF) option(PK_ENABLE_DETERMINISM "" FALSE) +option(PK_ENABLE_WATCHDOG "" OFF) # modules option(PK_BUILD_MODULE_LZ4 "" OFF) diff --git a/include/pocketpy/config.h b/include/pocketpy/config.h index 18ca5d68..12fcbb10 100644 --- a/include/pocketpy/config.h +++ b/include/pocketpy/config.h @@ -12,6 +12,10 @@ #define PK_ENABLE_OS 1 #endif +#ifndef PK_ENABLE_WATCHDOG // can be overridden by cmake +#define PK_ENABLE_WATCHDOG 0 +#endif + // GC min threshold #ifndef PK_GC_MIN_THRESHOLD // can be overridden by cmake #define PK_GC_MIN_THRESHOLD 32768 diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index 8a13dcb7..2dabd1d4 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -23,6 +23,11 @@ typedef struct TraceInfo { py_TraceFunc func; } TraceInfo; +typedef struct WatchdogInfo { + py_i64 timeout; + py_i64 last_reset_time; +} WatchdogInfo; + typedef struct VM { py_Frame* top_frame; @@ -48,6 +53,7 @@ typedef struct VM { py_StackRef curr_class; py_StackRef curr_decl_based_function; TraceInfo trace_info; + WatchdogInfo watchdog_info; py_TValue vectorcall_buffer[PK_MAX_CO_VARNAMES]; InternedNames names; diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index dff6af1d..a61f023b 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -111,6 +111,16 @@ PK_API void py_sys_settrace(py_TraceFunc func); /// Setup the callbacks for the current VM. PK_API py_Callbacks* py_callbacks(); +/// Begin the watchdog with a timeout in milliseconds. +/// `PK_ENABLE_WATCHDOG` must be defined to `1` to use this feature. +/// You need to call `py_watchdog_reset()` periodically to keep the watchdog alive. +/// If the timeout is reached, `TimeoutError` will be raised. +PK_API void py_watchdog_begin(py_i64 timeout); +/// Reset the watchdog. +PK_API void py_watchdog_reset(); +/// End the watchdog. +PK_API void py_watchdog_end(); + /// Get the current source location of the frame. PK_API const char* py_Frame_sourceloc(py_Frame* frame, int* lineno); /// Python equivalent to `globals()` with respect to the given frame. @@ -553,6 +563,7 @@ PK_API void py_clearexc(py_StackRef p0); #define NameError(n) py_exception(tp_NameError, "name '%n' is not defined", (n)) #define TypeError(...) py_exception(tp_TypeError, __VA_ARGS__) #define RuntimeError(...) py_exception(tp_RuntimeError, __VA_ARGS__) +#define TimeoutError(...) py_exception(tp_TimeoutError, __VA_ARGS__) #define OSError(...) py_exception(tp_OSError, __VA_ARGS__) #define ValueError(...) py_exception(tp_ValueError, __VA_ARGS__) #define IndexError(...) py_exception(tp_IndexError, __VA_ARGS__) @@ -757,6 +768,7 @@ enum py_PredefinedType { tp_IndexError, tp_ValueError, tp_RuntimeError, + tp_TimeoutError, tp_ZeroDivisionError, tp_NameError, tp_UnboundLocalError, diff --git a/include/typings/pkpy.pyi b/include/typings/pkpy.pyi index 793897d3..69b9c691 100644 --- a/include/typings/pkpy.pyi +++ b/include/typings/pkpy.pyi @@ -25,6 +25,19 @@ def currentvm() -> int: """Return the current VM index.""" +def watchdog_begin(timeout: int): + """ + Begin the watchdog with a timeout in milliseconds. + `PK_ENABLE_WATCHDOG` must be defined to `1` to use this feature. + You need to call `watchdog_reset()` periodically to keep the watchdog alive. + If the timeout is reached, `TimeoutError` will be raised. + """ +def watchdog_reset(): + """Reset the watchdog.""" +def watchdog_end(): + """End the watchdog.""" + + class ComputeThread: def __init__(self, vm_index: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]): ... diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 90ebfd49..3f889bae 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -7,6 +7,7 @@ #include "pocketpy/pocketpy.h" #include "pocketpy/objects/error.h" #include +#include static bool stack_format_object(VM* self, c11_sv spec); @@ -107,6 +108,18 @@ FrameResult VM__run_top_frame(VM* self) { } } +#if PK_ENABLE_WATCHDOG + if(self->watchdog_info.timeout > 0){ + py_i64 now = clock() / (CLOCKS_PER_SEC / 1000); + py_i64 delta = now - self->watchdog_info.last_reset_time; + if(delta > self->watchdog_info.timeout) { + self->watchdog_info.last_reset_time = now; + TimeoutError("watchdog timeout"); + goto __ERROR; + } + } +#endif + #ifndef NDEBUG pk_print_stack(self, frame, byte); #endif diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index cbd94ed9..e9b5b6e3 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -170,6 +170,7 @@ void VM__ctor(VM* self) { INJECT_BUILTIN_EXC(IndexError, tp_Exception); INJECT_BUILTIN_EXC(ValueError, tp_Exception); INJECT_BUILTIN_EXC(RuntimeError, tp_Exception); + INJECT_BUILTIN_EXC(TimeoutError, tp_Exception); INJECT_BUILTIN_EXC(ZeroDivisionError, tp_Exception); INJECT_BUILTIN_EXC(NameError, tp_Exception); INJECT_BUILTIN_EXC(UnboundLocalError, tp_Exception); diff --git a/src/modules/pkpy.c b/src/modules/pkpy.c index 66999c99..62c34533 100644 --- a/src/modules/pkpy.c +++ b/src/modules/pkpy.c @@ -3,7 +3,6 @@ #include "pocketpy/pocketpy.h" #include "pocketpy/common/utils.h" -#include "pocketpy/objects/object.h" #include "pocketpy/common/sstream.h" #include "pocketpy/interpreter/vm.h" @@ -79,6 +78,46 @@ static bool pkpy_currentvm(int argc, py_Ref argv) { return true; } +#if PK_ENABLE_WATCHDOG +void py_watchdog_begin(py_i64 timeout) { + WatchdogInfo* info = &pk_current_vm->watchdog_info; + info->timeout = timeout; + py_watchdog_reset(); +} + +void py_watchdog_reset() { + WatchdogInfo* info = &pk_current_vm->watchdog_info; + info->last_reset_time = clock() / (CLOCKS_PER_SEC / 1000); +} + +void py_watchdog_end() { + WatchdogInfo* info = &pk_current_vm->watchdog_info; + info->timeout = 0; +} + +static bool pkpy_watchdog_begin(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + PY_CHECK_ARG_TYPE(0, tp_int); + py_watchdog_begin(py_toint(argv)); + py_newnone(py_retval()); + return true; +} + +static bool pkpy_watchdog_reset(int argc, py_Ref argv) { + PY_CHECK_ARGC(0); + py_watchdog_reset(); + py_newnone(py_retval()); + return true; +} + +static bool pkpy_watchdog_end(int argc, py_Ref argv) { + PY_CHECK_ARGC(0); + py_watchdog_end(); + py_newnone(py_retval()); + return true; +} +#endif + typedef struct c11_ComputeThread c11_ComputeThread; typedef struct { @@ -467,6 +506,12 @@ void pk__add_module_pkpy() { py_bindfunc(mod, "currentvm", pkpy_currentvm); +#if PK_ENABLE_WATCHDOG + py_bindfunc(mod, "watchdog_begin", pkpy_watchdog_begin); + py_bindfunc(mod, "watchdog_reset", pkpy_watchdog_reset); + py_bindfunc(mod, "watchdog_end", pkpy_watchdog_end); +#endif + pk_ComputeThread__register(mod); } From 29cd6fe59b3a99dff098da60ac496492abf96f37 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Wed, 21 May 2025 11:45:40 +0800 Subject: [PATCH 7/8] improve code --- include/pocketpy/config.h | 6 ++++++ include/pocketpy/pocketpy.h | 2 +- src/interpreter/frame.c | 4 ++-- src/modules/pickle.c | 6 +++--- src/modules/vmath.c | 7 +++---- src/public/modules.c | 4 ++-- src/public/py_dict.c | 2 +- src/public/py_list.c | 5 ++--- src/public/py_slice.c | 3 +-- src/public/py_str.c | 6 +++--- src/public/py_tuple.c | 2 +- src/public/values.c | 22 +++++++++++----------- 12 files changed, 36 insertions(+), 33 deletions(-) diff --git a/include/pocketpy/config.h b/include/pocketpy/config.h index 12fcbb10..746fe53e 100644 --- a/include/pocketpy/config.h +++ b/include/pocketpy/config.h @@ -55,3 +55,9 @@ #else #define PK_PLATFORM_SEP '/' #endif + +#ifdef __cplusplus + #ifndef restrict + #define restrict + #endif +#endif diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index a61f023b..afe38808 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -47,7 +47,7 @@ typedef py_TValue* py_GlobalRef; typedef py_TValue* py_StackRef; /// An item reference to a container object. It invalidates when the container is modified. typedef py_TValue* py_ItemRef; -/// An output reference for returning a value. +/// An output reference for returning a value. Only use this for function arguments. typedef py_TValue* py_OutRef; typedef struct py_Frame py_Frame; diff --git a/src/interpreter/frame.c b/src/interpreter/frame.c index 396f239d..40ecfb2a 100644 --- a/src/interpreter/frame.c +++ b/src/interpreter/frame.c @@ -192,7 +192,7 @@ const char* py_Frame_sourceloc(py_Frame* self, int* lineno) { return loc.src->filename->data; } -void py_Frame_newglobals(py_Frame* frame, py_Ref out) { +void py_Frame_newglobals(py_Frame* frame, py_OutRef out) { if(!frame) { pk_mappingproxy__namedict(out, &pk_current_vm->main); return; @@ -204,7 +204,7 @@ void py_Frame_newglobals(py_Frame* frame, py_Ref out) { } } -void py_Frame_newlocals(py_Frame* frame, py_Ref out) { +void py_Frame_newlocals(py_Frame* frame, py_OutRef out) { if(!frame) { py_newdict(out); return; diff --git a/src/modules/pickle.c b/src/modules/pickle.c index b7159a9c..cc0112c9 100644 --- a/src/modules/pickle.c +++ b/src/modules/pickle.c @@ -580,7 +580,7 @@ bool py_pickle_loads_body(const unsigned char* p, int memo_length, c11_smallmap_ } case PKL_BUILD_LIST: { int length = pkl__read_int(&p); - py_OutRef val = py_retval(); + py_Ref val = py_retval(); py_newlistn(val, length); for(int i = length - 1; i >= 0; i--) { py_StackRef item = py_peek(-1); @@ -592,7 +592,7 @@ bool py_pickle_loads_body(const unsigned char* p, int memo_length, c11_smallmap_ } case PKL_BUILD_TUPLE: { int length = pkl__read_int(&p); - py_OutRef val = py_retval(); + py_Ref val = py_retval(); py_Ref p = py_newtuple(val, length); for(int i = length - 1; i >= 0; i--) { p[i] = *py_peek(-1); @@ -603,7 +603,7 @@ bool py_pickle_loads_body(const unsigned char* p, int memo_length, c11_smallmap_ } case PKL_BUILD_DICT: { int length = pkl__read_int(&p); - py_OutRef val = py_pushtmp(); + py_Ref val = py_pushtmp(); py_newdict(val); py_StackRef begin = py_peek(-1) - 2 * length; py_StackRef end = py_peek(-1); diff --git a/src/modules/vmath.c b/src/modules/vmath.c index 92e7cdf9..e0badc05 100644 --- a/src/modules/vmath.c +++ b/src/modules/vmath.c @@ -4,7 +4,6 @@ #include "pocketpy/common/sstream.h" #include "pocketpy/common/utils.h" #include "pocketpy/interpreter/vm.h" -#include "pocketpy/objects/object.h" #include static bool isclose(float a, float b) { return fabs(a - b) < 1e-4; } @@ -544,7 +543,7 @@ static bool mat3x3__eq__(int argc, py_Ref argv) { DEFINE_BOOL_NE(mat3x3, mat3x3__eq__) -static void matmul(const c11_mat3x3* lhs, const c11_mat3x3* rhs, c11_mat3x3* out) { +static void matmul(const c11_mat3x3* lhs, const c11_mat3x3* rhs, c11_mat3x3* restrict out) { out->_11 = lhs->_11 * rhs->_11 + lhs->_12 * rhs->_21 + lhs->_13 * rhs->_31; out->_12 = lhs->_11 * rhs->_12 + lhs->_12 * rhs->_22 + lhs->_13 * rhs->_32; out->_13 = lhs->_11 * rhs->_13 + lhs->_12 * rhs->_23 + lhs->_13 * rhs->_33; @@ -562,7 +561,7 @@ static float determinant(const c11_mat3x3* m) { m->_13 * (m->_21 * m->_32 - m->_22 * m->_31); } -static bool inverse(const c11_mat3x3* m, c11_mat3x3* out) { +static bool inverse(const c11_mat3x3* m, c11_mat3x3* restrict out) { float det = determinant(m); if(isclose(det, 0)) return false; float invdet = 1.0f / det; @@ -578,7 +577,7 @@ static bool inverse(const c11_mat3x3* m, c11_mat3x3* out) { return true; } -static void trs(c11_vec2 t, float r, c11_vec2 s, c11_mat3x3* out) { +static void trs(c11_vec2 t, float r, c11_vec2 s, c11_mat3x3* restrict out) { float cr = cosf(r); float sr = sinf(r); // clang-format off diff --git a/src/public/modules.c b/src/public/modules.c index a379d41b..bd47e3b3 100644 --- a/src/public/modules.c +++ b/src/public/modules.c @@ -500,12 +500,12 @@ static bool builtins_locals(int argc, py_Ref argv) { return true; } -void py_newglobals(py_Ref out) { +void py_newglobals(py_OutRef out) { py_Frame* frame = pk_current_vm->top_frame; py_Frame_newglobals(frame, out); } -void py_newlocals(py_Ref out) { +void py_newlocals(py_OutRef out) { py_Frame* frame = pk_current_vm->top_frame; py_Frame_newlocals(frame, out); } diff --git a/src/public/py_dict.c b/src/public/py_dict.c index 50906a48..6ed723bf 100644 --- a/src/public/py_dict.c +++ b/src/public/py_dict.c @@ -266,7 +266,7 @@ static bool dict__new__(int argc, py_Ref argv) { return true; } -void py_newdict(py_Ref out) { +void py_newdict(py_OutRef out) { Dict* ud = py_newobject(out, tp_dict, 0, sizeof(Dict)); Dict__ctor(ud, 7, 8); } diff --git a/src/public/py_list.c b/src/public/py_list.c index d33585cf..cb0ac7f8 100644 --- a/src/public/py_list.c +++ b/src/public/py_list.c @@ -1,16 +1,15 @@ #include "pocketpy/pocketpy.h" #include "pocketpy/common/utils.h" -#include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/sstream.h" -void py_newlist(py_Ref out) { +void py_newlist(py_OutRef out) { List* ud = py_newobject(out, tp_list, 0, sizeof(List)); c11_vector__ctor(ud, sizeof(py_TValue)); } -void py_newlistn(py_Ref out, int n) { +void py_newlistn(py_OutRef out, int n) { py_newlist(out); List* ud = py_touserdata(out); c11_vector__reserve(ud, n); diff --git a/src/public/py_slice.c b/src/public/py_slice.c index de96f24e..5d0fe627 100644 --- a/src/public/py_slice.c +++ b/src/public/py_slice.c @@ -1,11 +1,10 @@ #include "pocketpy/pocketpy.h" -#include "pocketpy/common/utils.h" #include "pocketpy/common/sstream.h" #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" -void py_newslice(py_Ref out) { +void py_newslice(py_OutRef out) { VM* vm = pk_current_vm; PyObject* obj = ManagedHeap__gcnew(&vm->heap, tp_slice, 3, 0); out->type = tp_slice; diff --git a/src/public/py_str.c b/src/public/py_str.c index 03d217da..06175ec2 100644 --- a/src/public/py_str.c +++ b/src/public/py_str.c @@ -6,9 +6,9 @@ #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/sstream.h" -void py_newstr(py_Ref out, const char* data) { py_newstrv(out, (c11_sv){data, strlen(data)}); } +void py_newstr(py_OutRef out, const char* data) { py_newstrv(out, (c11_sv){data, strlen(data)}); } -char* py_newstrn(py_Ref out, int size) { +char* py_newstrn(py_OutRef out, int size) { if(size < 8) { out->type = tp_str; out->is_ptr = false; @@ -42,7 +42,7 @@ void py_newfstr(py_OutRef out, const char* fmt, ...) { c11_sbuf__py_submit(&buf, out); } -unsigned char* py_newbytes(py_Ref out, int size) { +unsigned char* py_newbytes(py_OutRef out, int size) { ManagedHeap* heap = &pk_current_vm->heap; // 4 bytes size + data PyObject* obj = ManagedHeap__gcnew(heap, tp_bytes, 0, sizeof(c11_bytes) + size); diff --git a/src/public/py_tuple.c b/src/public/py_tuple.c index 1c0f97b2..0471bd84 100644 --- a/src/public/py_tuple.c +++ b/src/public/py_tuple.c @@ -5,7 +5,7 @@ #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" -py_ObjectRef py_newtuple(py_Ref out, int n) { +py_ObjectRef py_newtuple(py_OutRef out, int n) { VM* vm = pk_current_vm; PyObject* obj = ManagedHeap__gcnew(&vm->heap, tp_tuple, n, 0); out->type = tp_tuple; diff --git a/src/public/values.c b/src/public/values.c index 5db42560..edb86f4e 100644 --- a/src/public/values.c +++ b/src/public/values.c @@ -7,42 +7,42 @@ #include "pocketpy/interpreter/vm.h" #include "pocketpy/compiler/compiler.h" -void py_newint(py_Ref out, int64_t val) { +void py_newint(py_OutRef out, py_i64 val) { out->type = tp_int; out->is_ptr = false; out->_i64 = val; } -void py_newfloat(py_Ref out, double val) { +void py_newfloat(py_OutRef out, py_f64 val) { out->type = tp_float; out->is_ptr = false; out->_f64 = val; } -void py_newbool(py_Ref out, bool val) { +void py_newbool(py_OutRef out, bool val) { out->type = tp_bool; out->is_ptr = false; out->_bool = val; } -void py_newnone(py_Ref out) { +void py_newnone(py_OutRef out) { out->type = tp_NoneType; out->is_ptr = false; } -void py_newnotimplemented(py_Ref out) { +void py_newnotimplemented(py_OutRef out) { out->type = tp_NotImplementedType; out->is_ptr = false; } -void py_newellipsis(py_Ref out) { +void py_newellipsis(py_OutRef out) { out->type = tp_ellipsis; out->is_ptr = false; } -void py_newnil(py_Ref out) { out->type = 0; } +void py_newnil(py_OutRef out) { out->type = 0; } -void py_newnativefunc(py_Ref out, py_CFunction f) { +void py_newnativefunc(py_OutRef out, py_CFunction f) { out->type = tp_nativefunc; out->is_ptr = false; out->_cfunc = f; @@ -90,7 +90,7 @@ void py_bind(py_Ref obj, const char* sig, py_CFunction f) { } py_Name - py_newfunction(py_Ref out, const char* sig, py_CFunction f, const char* docstring, int slots) { + py_newfunction(py_OutRef out, const char* sig, py_CFunction f, const char* docstring, int slots) { char buffer[256]; snprintf(buffer, sizeof(buffer), "def %s: pass", sig); // fn(a, b, *c, d=1) -> None @@ -118,13 +118,13 @@ py_Name return decl_name; } -void py_newboundmethod(py_Ref out, py_Ref self, py_Ref func) { +void py_newboundmethod(py_OutRef out, py_Ref self, py_Ref func) { py_newobject(out, tp_boundmethod, 2, 0); py_setslot(out, 0, self); py_setslot(out, 1, func); } -void* py_newobject(py_Ref out, py_Type type, int slots, int udsize) { +void* py_newobject(py_OutRef out, py_Type type, int slots, int udsize) { ManagedHeap* heap = &pk_current_vm->heap; PyObject* obj = ManagedHeap__gcnew(heap, type, slots, udsize); out->type = type; From 340309b5d4b61b85b9ce4003fdf65ac1ab4991c6 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Wed, 21 May 2025 19:33:49 +0800 Subject: [PATCH 8/8] fix https://github.com/pocketpy/pocketpy/issues/367 --- src/compiler/compiler.c | 10 ++-------- tests/95_bugs.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index f97e5f70..29c93e60 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -479,13 +479,7 @@ bool TupleExpr__emit_store(Expr* self_, Ctx* ctx) { } if(starred_i == -1) { - Bytecode* prev = c11__at(Bytecode, &ctx->co->codes, ctx->co->codes.length - 1); - if(prev->op == OP_BUILD_TUPLE && prev->arg == self->itemCount) { - // build tuple and unpack it is meaningless - Ctx__revert_last_emit_(ctx); - } else { - Ctx__emit_(ctx, OP_UNPACK_SEQUENCE, self->itemCount, self->line); - } + Ctx__emit_(ctx, OP_UNPACK_SEQUENCE, self->itemCount, self->line); } else { // starred assignment target must be in a tuple if(self->itemCount == 1) return false; @@ -2177,7 +2171,7 @@ Error* try_compile_assignment(Compiler* self, bool* is_assign) { } case TK_ASSIGN: { consume(TK_ASSIGN); - int n = 0; + int n = 0; // assignment count if(match(TK_YIELD_FROM)) { check(compile_yield_from(self, prev()->line)); diff --git a/tests/95_bugs.py b/tests/95_bugs.py index 1916c34d..ea341717 100644 --- a/tests/95_bugs.py +++ b/tests/95_bugs.py @@ -148,4 +148,13 @@ assert A.x == [1, 2, 3] a = [(0, 1), (1, 1), (1, 2)] b = sorted(a, key=lambda x: x[0]) if b != [(0, 1), (1, 1), (1, 2)]: - assert False, b \ No newline at end of file + assert False, b + +# https://github.com/pocketpy/pocketpy/issues/367 +a = 10 if False else 5 +assert a == 5 + +a, b, c = (1, 2, 3) if True else (4, 5, 6) +assert a == 1 +assert b == 2 +assert c == 3 \ No newline at end of file