From 1eb3ba3077c1e3e97903220068cba82a4e663446 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Sat, 6 Jan 2024 15:51:12 +0800 Subject: [PATCH] add support for `try..finally` --- README.md | 2 +- docs/features/basic.md | 2 +- include/pocketpy/common.h | 2 +- include/pocketpy/opcodes.h | 1 + src/ceval.cpp | 7 ++-- src/compiler.cpp | 66 +++++++++++++++++++++++++------------- src/vm.cpp | 2 +- tests/41_exception.py | 62 +++++++++++++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 1c6129ff..42afed62 100644 --- a/README.md +++ b/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` | ✅ | diff --git a/docs/features/basic.md b/docs/features/basic.md index baccf82f..1731c54f 100644 --- a/docs/features/basic.md +++ b/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 | diff --git a/include/pocketpy/common.h b/include/pocketpy/common.h index 9b09c2c3..6405ecf5 100644 --- a/include/pocketpy/common.h +++ b/include/pocketpy/common.h @@ -23,7 +23,7 @@ #include #include -#define PK_VERSION "1.3.6" +#define PK_VERSION "1.3.7" #include "config.h" #include "export.h" diff --git a/include/pocketpy/opcodes.h b/include/pocketpy/opcodes.h index 02f3ef9a..de0fe9df 100644 --- a/include/pocketpy/opcodes.h +++ b/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) diff --git a/src/ceval.cpp b/src/ceval.cpp index db8ff190..221982dd 100644 --- a/src/ceval.cpp +++ b/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 } diff --git a/src/compiler.cpp b/src/compiler.cpp index 6c1ce899..459af202 100644 --- a/src/compiler.cpp +++ b/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(){ diff --git a/src/vm.cpp b/src/vm.cpp index f0bc4ab5..e1519c79 100644 --- a/src/vm.cpp +++ b/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; diff --git a/tests/41_exception.py b/tests/41_exception.py index ab85fbf0..0e1fff50 100644 --- a/tests/41_exception.py +++ b/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)