فهرست منبع

add support for `try..finally`

blueloveTH 2 سال پیش
والد
کامیت
1eb3ba3077
8فایلهای تغییر یافته به همراه116 افزوده شده و 28 حذف شده
  1. 1 1
      README.md
  2. 1 1
      docs/features/basic.md
  3. 1 1
      include/pocketpy/common.h
  4. 1 0
      include/pocketpy/opcodes.h
  5. 5 2
      src/ceval.cpp
  6. 44 22
      src/compiler.cpp
  7. 1 1
      src/vm.cpp
  8. 62 0
      tests/41_exception.py

+ 1 - 1
README.md

@@ -127,7 +127,7 @@ for a quick overview of the supported features.
 | F-String        | `f'value is {x}'`               | ✅       |
 | Unpacking       | `a, b = 1, 2`                   | ✅       |
 | Star Unpacking  | `a, *b = [1, 2, 3]`             | ✅       |
-| Exception       | `raise/try..catch`              | ✅       |
+| Exception       | `raise/try..catch..finally`     | ✅       |
 | Dynamic Code    | `eval()/exec()`                 | ✅       |
 | Reflection      | `hasattr()/getattr()/setattr()` | ✅       |
 | Import          | `import/from..import`           | ✅       |

+ 1 - 1
docs/features/basic.md

@@ -24,7 +24,7 @@ The features marked with `YES` are supported, and the features marked with `NO`
 | 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`              | YES       |
+| Exception       | `raise/try..catch..finally`     | YES       |
 | Dynamic Code    | `eval()/exec()`                 | YES       |
 | Reflection      | `hasattr()/getattr()/setattr()` | YES       |
 | Import          | `import/from..import`           | YES       |

+ 1 - 1
include/pocketpy/common.h

@@ -23,7 +23,7 @@
 #include <bitset>
 #include <deque>
 
-#define PK_VERSION				"1.3.6"
+#define PK_VERSION				"1.3.7"
 
 #include "config.h"
 #include "export.h"

+ 1 - 0
include/pocketpy/opcodes.h

@@ -82,6 +82,7 @@ OPCODE(IS_OP)
 OPCODE(CONTAINS_OP)
 /**************************/
 OPCODE(JUMP_ABSOLUTE)
+OPCODE(JUMP_ABSOLUTE_TOP)
 OPCODE(POP_JUMP_IF_FALSE)
 OPCODE(POP_JUMP_IF_TRUE)
 OPCODE(JUMP_IF_TRUE_OR_POP)

+ 5 - 2
src/ceval.cpp

@@ -533,6 +533,9 @@ __NEXT_STEP:;
     TARGET(JUMP_ABSOLUTE)
         frame->jump_abs(byte.arg);
         DISPATCH();
+    TARGET(JUMP_ABSOLUTE_TOP)
+        frame->jump_abs(_CAST(uint16_t, POPX()));
+        DISPATCH();
     TARGET(POP_JUMP_IF_FALSE){
         PyObject* _0 = POPX();
         if(_0==False || !py_bool(_0)) frame->jump_abs(byte.arg);
@@ -843,8 +846,8 @@ __NEXT_STEP:;
     } DISPATCH();
 
 #if !PK_ENABLE_COMPUTED_GOTO
-        static_assert(OP_DEC_GLOBAL == 111);
-        case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: case 127: case 128: case 129: case 130: case 131: case 132: case 133: case 134: case 135: case 136: case 137: case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: case 147: case 148: case 149: case 150: case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159: case 160: case 161: case 162: case 163: case 164: case 165: case 166: case 167: case 168: case 169: case 170: case 171: case 172: case 173: case 174: case 175: case 176: case 177: case 178: case 179: case 180: case 181: case 182: case 183: case 184: case 185: case 186: case 187: case 188: case 189: case 190: case 191: case 192: case 193: case 194: case 195: case 196: case 197: case 198: case 199: case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: case 208: case 209: case 210: case 211: case 212: case 213: case 214: case 215: case 216: case 217: case 218: case 219: case 220: case 221: case 222: case 223: case 224: case 225: case 226: case 227: case 228: case 229: case 230: case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239: case 240: case 241: case 242: case 243: case 244: case 245: case 246: case 247: case 248: case 249: case 250: case 251: case 252: case 253: case 254: case 255: PK_UNREACHABLE() break;
+        static_assert(OP_DEC_GLOBAL == 112);
+        case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: case 127: case 128: case 129: case 130: case 131: case 132: case 133: case 134: case 135: case 136: case 137: case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: case 147: case 148: case 149: case 150: case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159: case 160: case 161: case 162: case 163: case 164: case 165: case 166: case 167: case 168: case 169: case 170: case 171: case 172: case 173: case 174: case 175: case 176: case 177: case 178: case 179: case 180: case 181: case 182: case 183: case 184: case 185: case 186: case 187: case 188: case 189: case 190: case 191: case 192: case 193: case 194: case 195: case 196: case 197: case 198: case 199: case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: case 208: case 209: case 210: case 211: case 212: case 213: case 214: case 215: case 216: case 217: case 218: case 219: case 220: case 221: case 222: case 223: case 224: case 225: case 226: case 227: case 228: case 229: case 230: case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239: case 240: case 241: case 242: case 243: case 244: case 245: case 246: case 247: case 248: case 249: case 250: case 251: case 252: case 253: case 254: case 255: PK_UNREACHABLE() break;
     }
 #endif
 }

+ 44 - 22
src/compiler.cpp

@@ -673,34 +673,56 @@ __EAT_DOTS_END:
             ctx()->emit_(OP_JUMP_ABSOLUTE, BC_NOARG, BC_KEEPLINE)
         };
         ctx()->exit_block();
-        do {
-            StrName as_name;
-            consume(TK("except"));
-            if(is_expression()){
-                EXPR(false);      // push assumed type on to the stack
-                ctx()->emit_(OP_EXCEPTION_MATCH, BC_NOARG, prev().line);
-                if(match(TK("as"))){
-                    consume(TK("@id"));
-                    as_name = StrName(prev().sv());
+
+        int finally_entry = -1;
+        if(curr().type != TK("finally")){
+            do {
+                StrName as_name;
+                consume(TK("except"));
+                if(is_expression()){
+                    EXPR(false);      // push assumed type on to the stack
+                    ctx()->emit_(OP_EXCEPTION_MATCH, BC_NOARG, prev().line);
+                    if(match(TK("as"))){
+                        consume(TK("@id"));
+                        as_name = StrName(prev().sv());
+                    }
+                }else{
+                    ctx()->emit_(OP_LOAD_TRUE, BC_NOARG, BC_KEEPLINE);
                 }
-            }else{
-                ctx()->emit_(OP_LOAD_TRUE, BC_NOARG, BC_KEEPLINE);
-            }
-            int patch = ctx()->emit_(OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
-            // on match
-            if(!as_name.empty()){
-                ctx()->emit_(OP_DUP_TOP, BC_NOARG, BC_KEEPLINE);
-                ctx()->emit_store_name(name_scope(), as_name, BC_KEEPLINE);
-            }
-            // pop the exception 
-            ctx()->emit_(OP_POP_EXCEPTION, BC_NOARG, BC_KEEPLINE);
+                int patch = ctx()->emit_(OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
+                // on match
+                if(!as_name.empty()){
+                    ctx()->emit_(OP_DUP_TOP, BC_NOARG, BC_KEEPLINE);
+                    ctx()->emit_store_name(name_scope(), as_name, BC_KEEPLINE);
+                }
+                // pop the exception 
+                ctx()->emit_(OP_POP_EXCEPTION, BC_NOARG, BC_KEEPLINE);
+                compile_block_body();
+                patches.push_back(ctx()->emit_(OP_JUMP_ABSOLUTE, BC_NOARG, BC_KEEPLINE));
+                ctx()->patch_jump(patch);
+            }while(curr().type == TK("except"));
+        }
+
+        if(match(TK("finally"))){
+            int patch = ctx()->emit_(OP_JUMP_ABSOLUTE, BC_NOARG, BC_KEEPLINE);
+            finally_entry = ctx()->co->codes.size();
             compile_block_body();
-            patches.push_back(ctx()->emit_(OP_JUMP_ABSOLUTE, BC_NOARG, BC_KEEPLINE));
+            ctx()->emit_(OP_JUMP_ABSOLUTE_TOP, BC_NOARG, BC_KEEPLINE);
             ctx()->patch_jump(patch);
-        }while(curr().type == TK("except"));
+        }
         // no match, re-raise
+        if(finally_entry != -1){
+            ctx()->emit_(OP_LOAD_INTEGER, (uint16_t)ctx()->co->codes.size()+2, BC_KEEPLINE);
+            ctx()->emit_(OP_JUMP_ABSOLUTE, finally_entry, BC_KEEPLINE);
+        }
         ctx()->emit_(OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
+
+        // no exception or no match, jump to the end
         for (int patch : patches) ctx()->patch_jump(patch);
+        if(finally_entry != -1){
+            ctx()->emit_(OP_LOAD_INTEGER, (uint16_t)ctx()->co->codes.size()+2, BC_KEEPLINE);
+            ctx()->emit_(OP_JUMP_ABSOLUTE, finally_entry, BC_KEEPLINE);
+        }
     }
 
     void Compiler::compile_decorated(){

+ 1 - 1
src/vm.cpp

@@ -584,7 +584,7 @@ static std::string _opcode_argstr(VM* vm, Bytecode byte, const CodeObject* co){
             break;
         case OP_LOAD_NAME: case OP_LOAD_GLOBAL: case OP_LOAD_NONLOCAL: case OP_STORE_GLOBAL:
         case OP_LOAD_ATTR: case OP_LOAD_METHOD: case OP_STORE_ATTR: case OP_DELETE_ATTR:
-        case OP_BEGIN_CLASS: case OP_RAISE: case OP_GOTO:
+        case OP_BEGIN_CLASS: case OP_GOTO:
         case OP_DELETE_GLOBAL: case OP_INC_GLOBAL: case OP_DEC_GLOBAL: case OP_STORE_CLASS_ATTR:
             argStr += fmt(" (", StrName(byte.arg).sv(), ")").sv();
             break;

+ 62 - 0
tests/41_exception.py

@@ -122,3 +122,65 @@ 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
+
+def finally_only_2():
+    try:
+        pass
+    finally:
+        return True
+    
+assert finally_only_2() is True
+    
+# finally, no exception
+def finally_no_exception():
+    ok = False
+    try:
+        pass
+    except KeyError:
+        exit(1)
+    finally:
+        ok = True
+    return ok
+
+assert finally_no_exception()
+
+# finally, match
+def finally_match():
+    ok = False
+    try:
+        raise KeyError
+    except KeyError:
+        pass
+    finally:
+        ok = True
+    return ok
+
+assert finally_match()
+
+# finally, no match
+ok = False
+def finally_no_match():
+    global ok
+    try:
+        raise KeyError
+    except IndexError:
+        exit(1)
+    finally:
+        ok = True
+
+try:
+    finally_no_match()
+except KeyError:
+    assert ok
+    exit(0)
+
+exit(1)