Przeglądaj źródła

remove `goto` and support `finally`

blueloveTH 1 rok temu
rodzic
commit
2bca80ff7f

+ 0 - 30
docs/features/goto.md

@@ -1,30 +0,0 @@
----
-icon: dot
-title: Goto Statement
----
-
-pkpy supports goto/label just like C. You are allowed to **change the control flow unconditionally**.
-
-## Define a label
-
-```
-== <identifier> ==
-```
-
-## Goto a label
-
-```
--> <identifier>
-```
-
-## Example
-
-```python
-for i in range(10):
-  for j in range(10):
-    for k in range(10):
-      -> exit
-
-== exit ==
-print('exit')
-```

+ 0 - 1
include/pocketpy/objects/codeobject.h

@@ -75,7 +75,6 @@ typedef struct CodeObject {
     int nlocals;                        // cached varnames.size()
 
     c11_smallmap_n2i varnames_inv;
-    c11_smallmap_n2i labels;
 
     c11_vector /*T=CodeBlock*/ blocks;
     c11_vector /*T=FuncDecl_*/ func_decls;

+ 0 - 3
include/pocketpy/xmacros/opcodes.h

@@ -63,9 +63,6 @@ OPCODE(JUMP_IF_FALSE_OR_POP)
 OPCODE(SHORTCUT_IF_FALSE_OR_POP)
 OPCODE(LOOP_CONTINUE)
 OPCODE(LOOP_BREAK)
-/***/
-OPCODE(JUMP_ABSOLUTE_TOP)
-OPCODE(GOTO)
 /**************************/
 OPCODE(CALL)
 OPCODE(CALL_VARGS)

+ 24 - 41
src/compiler/compiler.c

@@ -3,11 +3,9 @@
 #include "pocketpy/objects/codeobject.h"
 #include "pocketpy/objects/sourcedata.h"
 #include "pocketpy/objects/object.h"
-#include "pocketpy/common/strname.h"
 #include "pocketpy/common/sstream.h"
 #include "pocketpy/common/memorypool.h"
 #include <assert.h>
-#include <ctype.h>
 #include <stdbool.h>
 
 /* expr.h */
@@ -82,7 +80,6 @@ static int Ctx__emit_virtual(Ctx* self, Opcode opcode, uint16_t arg, int line, b
 static void Ctx__revert_last_emit_(Ctx* self);
 static int Ctx__emit_int(Ctx* self, int64_t value, int line);
 static void Ctx__patch_jump(Ctx* self, int index);
-static bool Ctx__add_label(Ctx* self, py_Name name);
 static int Ctx__add_varname(Ctx* self, py_Name name);
 static int Ctx__add_const(Ctx* self, py_Ref);
 static int Ctx__add_const_string(Ctx* self, c11_sv);
@@ -1191,13 +1188,6 @@ static void Ctx__patch_jump(Ctx* self, int index) {
     Bytecode__set_signed_arg(&co_codes[index], target - index);
 }
 
-static bool Ctx__add_label(Ctx* self, py_Name name) {
-    bool ok = c11_smallmap_n2i__contains(&self->co->labels, name);
-    if(ok) return false;
-    c11_smallmap_n2i__set(&self->co->labels, name, self->co->codes.length);
-    return true;
-}
-
 static int Ctx__add_varname(Ctx* self, py_Name name) {
     // PK_MAX_CO_VARNAMES will be checked when pop_context(), not here
     return CodeObject__add_varname(self->co, name);
@@ -2230,9 +2220,7 @@ static Error* consume_pep695_py312(Compiler* self) {
     Error* err;
     if(match(TK_LBRACKET)) {
         consume(TK_ID);
-        if(match(TK_COLON)){
-            check(consume_type_hints(self));
-        }
+        if(match(TK_COLON)) { check(consume_type_hints(self)); }
         consume(TK_RBRACKET);
     }
     return NULL;
@@ -2445,18 +2433,25 @@ __EAT_DOTS_END:
 
 static Error* compile_try_except(Compiler* self) {
     Error* err;
+    int patches[8];
+    int patches_length = 0;
+
     Ctx__enter_block(ctx(), CodeBlockType_TRY_EXCEPT);
     Ctx__emit_(ctx(), OP_TRY_ENTER, BC_NOARG, prev()->line);
     check(compile_block_body(self, compile_stmt));
 
-    int patches[8];
-    int patches_length = 0;
-
-    patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
+    bool finally_matched = match(TK_FINALLY);
+    if(!finally_matched) {
+        patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
+    }
     Ctx__exit_block(ctx());
 
-    if(curr()->type == TK_FINALLY) {
-        return SyntaxError(self, "finally clause is not supported yet");
+    if(finally_matched) {
+        // finally only, no except block
+        compile_block_body(self, compile_stmt);
+        // re-raise if needed
+        Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
+        return NULL;
     }
 
     do {
@@ -2466,14 +2461,17 @@ static Error* compile_try_except(Compiler* self) {
         py_Name as_name = 0;
         consume(TK_EXCEPT);
         if(is_expression(self, false)) {
-            check(EXPR(self));  // push assumed type on to the stack
+            // except <expr>:
+            check(EXPR(self));
             Ctx__s_emit_top(ctx());
             Ctx__emit_(ctx(), OP_EXCEPTION_MATCH, BC_NOARG, prev()->line);
             if(match(TK_AS)) {
+                // except <expr> as <name>:
                 consume(TK_ID);
                 as_name = py_namev(Token__sv(prev()));
             }
         } else {
+            // except:
             Ctx__emit_(ctx(), OP_LOAD_TRUE, BC_NOARG, BC_KEEPLINE);
         }
         int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
@@ -2490,11 +2488,15 @@ static Error* compile_try_except(Compiler* self) {
     } while(curr()->type == TK_EXCEPT);
 
     // no match, re-raise
-    Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
+    // ...
 
-    // no exception or no match, jump to the end
+    // match one & handled, jump to the end
     for(int i = 0; i < patches_length; i++)
         Ctx__patch_jump(ctx(), patches[i]);
+
+    if(match(TK_FINALLY)) compile_block_body(self, compile_stmt);
+    // re-raise if needed
+    Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
     return NULL;
 }
 
@@ -2623,25 +2625,6 @@ static Error* compile_stmt(Compiler* self) {
             Ctx__exit_block(ctx());
         } break;
         /*************************************************/
-        case TK_EQ: {
-            consume(TK_ID);
-            if(mode() != EXEC_MODE)
-                return SyntaxError(self, "'label' is only available in EXEC_MODE");
-            c11_sv name = Token__sv(prev());
-            bool ok = Ctx__add_label(ctx(), py_namev(name));
-            if(!ok) return SyntaxError(self, "label %q already exists", name);
-            consume(TK_EQ);
-            consume_end_stmt();
-        } break;
-        case TK_ARROW:
-            consume(TK_ID);
-            if(mode() != EXEC_MODE)
-                return SyntaxError(self, "'goto' is only available in EXEC_MODE");
-            py_Name name = py_namev(Token__sv(prev()));
-            Ctx__emit_(ctx(), OP_GOTO, name, prev()->line);
-            consume_end_stmt();
-            break;
-        /*************************************************/
         // handle dangling expression or assignment
         default: {
             // do revert since we have pre-called advance() at the beginning

+ 4 - 17
src/interpreter/ceval.c

@@ -2,7 +2,6 @@
 #include "pocketpy/common/utils.h"
 #include "pocketpy/interpreter/frame.h"
 #include "pocketpy/interpreter/vm.h"
-#include "pocketpy/common/memorypool.h"
 #include "pocketpy/common/sstream.h"
 #include "pocketpy/objects/codeobject.h"
 #include "pocketpy/pocketpy.h"
@@ -622,20 +621,6 @@ FrameResult VM__run_top_frame(VM* self) {
                 int target = Frame__ip(frame) + byte.arg;
                 Frame__prepare_jump_break(frame, &self->stack, target);
                 DISPATCH_JUMP((int16_t)byte.arg);
-            }
-            case OP_JUMP_ABSOLUTE_TOP: {
-                int target = py_toint(TOP());
-                POP();
-                DISPATCH_JUMP_ABSOLUTE(target);
-            }
-            case OP_GOTO: {
-                int target = c11_smallmap_n2i__get(&frame->co->labels, byte.arg, -1);
-                if(target < 0) {
-                    RuntimeError("label '%n' not found", byte.arg);
-                    goto __ERROR;
-                }
-                Frame__prepare_jump_break(frame, &self->stack, target);
-                DISPATCH_JUMP_ABSOLUTE(target);
             }
                 /*****************************************/
             case OP_CALL: {
@@ -1002,8 +987,10 @@ FrameResult VM__run_top_frame(VM* self) {
                 goto __ERROR;
             }
             case OP_RE_RAISE: {
-                assert(self->curr_exception.type);
-                goto __ERROR_RE_RAISE;
+                if(self->curr_exception.type && !self->is_curr_exc_handled) {
+                    goto __ERROR_RE_RAISE;
+                }
+                DISPATCH();
             }
             case OP_PUSH_EXCEPTION: {
                 assert(self->curr_exception.type);

+ 0 - 4
src/modules/dis.c

@@ -1,7 +1,4 @@
 #include "pocketpy/pocketpy.h"
-
-#include "pocketpy/common/utils.h"
-#include "pocketpy/objects/object.h"
 #include "pocketpy/common/sstream.h"
 #include "pocketpy/interpreter/vm.h"
 
@@ -75,7 +72,6 @@ static void disassemble(CodeObject* co) {
                 case OP_STORE_ATTR:
                 case OP_DELETE_ATTR:
                 case OP_BEGIN_CLASS:
-                case OP_GOTO:
                 case OP_DELETE_GLOBAL:
                 case OP_STORE_CLASS_ATTR: {
                     pk_sprintf(&ss, " (%n)", byte.arg);

+ 0 - 2
src/objects/codeobject.c

@@ -125,7 +125,6 @@ void CodeObject__ctor(CodeObject* self, SourceData_ src, c11_sv name) {
     self->nlocals = 0;
 
     c11_smallmap_n2i__ctor(&self->varnames_inv);
-    c11_smallmap_n2i__ctor(&self->labels);
 
     c11_vector__ctor(&self->blocks, sizeof(CodeBlock));
     c11_vector__ctor(&self->func_decls, sizeof(FuncDecl_));
@@ -148,7 +147,6 @@ void CodeObject__dtor(CodeObject* self) {
     c11_vector__dtor(&self->varnames);
 
     c11_smallmap_n2i__dtor(&self->varnames_inv);
-    c11_smallmap_n2i__dtor(&self->labels);
 
     c11_vector__dtor(&self->blocks);
 

+ 0 - 38
tests/27_goto.py

@@ -1,38 +0,0 @@
-a = []
-
-for i in range(10):         # [0]
-    for j in range(10):     # [0-0]
-        -> test
-        print(2)
-    == test ==
-    a.append(i)
-    for k in range(5):      # [0-1]
-        for t in range(7):  # [0-1-0]
-            pass
-
-assert a == list(range(10))
-
-b = False
-
-for i in range(10):         # [1]
-    for j in range(10):     # [1-0]
-        -> out
-        b = True
-== out ==
-assert not b
-
-sum = 0
-i = 1
-
-== loop ==
-sum += i
-i += 1
-if i <= 100:
-    -> loop
-
-assert sum == 5050
-
-for i in range(4):
-    _ = 0
-# if there is no op here, the block check will fail
-while i: i-=1

+ 21 - 16
tests/28_exception.py

@@ -138,32 +138,38 @@ except Exception:
     exit(1)
 assert ok
 
-# ok = False
-# try:
-#     eval('1+')
-# except SyntaxError as e:
-#     assert type(e) is SyntaxError
-#     ok = True
-# assert ok
-
-"""
+ok = False
+try:
+    eval('1+')
+except SyntaxError as e:
+    assert type(e) is SyntaxError
+    ok = True
+assert ok
+
+
 # finally, only
 def finally_only():
     try:
         raise KeyError
     finally:
-        return True
-    
-assert finally_only() is True
+        x = 1
+
+try:
+    finally_only()
+    exit(1)
+except KeyError:
+    pass
 
 def finally_only_2():
+    x = 0
     try:
         pass
     finally:
-        return True
-    
-assert finally_only_2() is True
+        x = 1
+    return x
     
+assert finally_only_2() == 1
+
 # finally, no exception
 def finally_no_exception():
     ok = False
@@ -208,4 +214,3 @@ except KeyError:
     exit(0)
 
 exit(1)
-"""

+ 0 - 7
tests/52_context.py

@@ -27,11 +27,4 @@ assert path == ['enter', 'in', 'exit']
 
 path.clear()
 
-with A(123) as a:
-    assert path == ['enter']
-    -> end
-    path.append('in')
-
-== end ==
-assert path == ['enter']