Support 'return' from within a 'with' block
This commit is contained in:
parent
e6997418cb
commit
ff7dcbb92a
30
README.md
30
README.md
@ -1026,7 +1026,35 @@ print(f)
|
|||||||
# → <closed file 'README.md' ...>
|
# → <closed file 'README.md' ...>
|
||||||
```
|
```
|
||||||
|
|
||||||
_**Note:** The implementation of `with` blocks is incomplete; exceptions raised from within a `with` that are not caught within the block will cause `__exit__` to not be called. The same is also true of `return` statements within a `with` block._
|
If an early return is encountered inside of a `with` block, the `__exit__` method for the context manager will be called before the function returns.
|
||||||
|
|
||||||
|
```py
|
||||||
|
class ContextManager:
|
||||||
|
def __init__(title):
|
||||||
|
self.title = title
|
||||||
|
def __enter__():
|
||||||
|
print("Enter context manager", self.title)
|
||||||
|
def __exit__():
|
||||||
|
print("Exit context manager", self.title)
|
||||||
|
def doesANestedThing():
|
||||||
|
with ContextManager('outer'):
|
||||||
|
with ContextManager('inner'):
|
||||||
|
with ContextManager('triple'):
|
||||||
|
return 42
|
||||||
|
print('Should not print')
|
||||||
|
print('Should not print')
|
||||||
|
print('Should not print')
|
||||||
|
print(doesANestedThing())
|
||||||
|
# → Enter context manager outer
|
||||||
|
# Enter context manager inner
|
||||||
|
# Enter context manager triple
|
||||||
|
# Exit context manager triple
|
||||||
|
# Exit context manager inner
|
||||||
|
# Exit context manager outer
|
||||||
|
# 42
|
||||||
|
```
|
||||||
|
|
||||||
|
_**Note:** The implementation of `with` blocks is incomplete; exceptions raised from within a `with` that are not caught within the block will cause `__exit__` to not be called._
|
||||||
|
|
||||||
## About the REPL
|
## About the REPL
|
||||||
|
|
||||||
|
2
chunk.h
2
chunk.h
@ -72,6 +72,8 @@ typedef enum {
|
|||||||
OP_FINALIZE,
|
OP_FINALIZE,
|
||||||
OP_TUPLE,
|
OP_TUPLE,
|
||||||
OP_UNPACK_TUPLE,
|
OP_UNPACK_TUPLE,
|
||||||
|
OP_PUSH_WITH,
|
||||||
|
OP_CLEANUP_WITH,
|
||||||
|
|
||||||
OP_CONSTANT_LONG = 128,
|
OP_CONSTANT_LONG = 128,
|
||||||
OP_DEFINE_GLOBAL_LONG,
|
OP_DEFINE_GLOBAL_LONG,
|
||||||
|
26
compiler.c
26
compiler.c
@ -1067,33 +1067,22 @@ static void withStatement() {
|
|||||||
defineVariable(ind);
|
defineVariable(ind);
|
||||||
} else {
|
} else {
|
||||||
/* Otherwise we want an unnamed local; TODO: Wait, can't we do this for iterable counts? */
|
/* Otherwise we want an unnamed local; TODO: Wait, can't we do this for iterable counts? */
|
||||||
Local * local = ¤t->locals[current->localCount++];
|
addLocal(syntheticToken(""));
|
||||||
local->depth = current->scopeDepth;
|
markInitialized();
|
||||||
local->isCaptured = 0;
|
|
||||||
local->name.start = "";
|
|
||||||
local->name.length = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(TOKEN_COLON, "Expected ':' after with statement");
|
consume(TOKEN_COLON, "Expected ':' after with statement");
|
||||||
|
|
||||||
/* Call enter */
|
addLocal(syntheticToken(""));
|
||||||
emitBytes(OP_DUP, 0);
|
int withJump = emitJump(OP_PUSH_WITH);
|
||||||
KrkToken _enter = syntheticToken("__enter__");
|
markInitialized();
|
||||||
ssize_t ind = identifierConstant(&_enter);
|
|
||||||
EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind);
|
|
||||||
emitBytes(OP_CALL, 0);
|
|
||||||
emitByte(OP_POP); /* We don't care about the result */
|
|
||||||
|
|
||||||
beginScope();
|
beginScope();
|
||||||
block(blockWidth,"with");
|
block(blockWidth,"with");
|
||||||
endScope();
|
endScope();
|
||||||
|
|
||||||
emitBytes(OP_DUP, 0);
|
patchJump(withJump);
|
||||||
KrkToken _exit = syntheticToken("__exit__");
|
emitByte(OP_CLEANUP_WITH);
|
||||||
ind = identifierConstant(&_exit);
|
|
||||||
EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind);
|
|
||||||
emitBytes(OP_CALL, 0);
|
|
||||||
emitByte(OP_POP); /* We don't care about the result */
|
|
||||||
|
|
||||||
/* Scope exit pops context manager */
|
/* Scope exit pops context manager */
|
||||||
endScope();
|
endScope();
|
||||||
@ -1235,7 +1224,6 @@ static void forStatement() {
|
|||||||
int exitJump;
|
int exitJump;
|
||||||
|
|
||||||
if (match(TOKEN_IN)) {
|
if (match(TOKEN_IN)) {
|
||||||
defineVariable(loopInd);
|
|
||||||
|
|
||||||
/* ITERABLE.__iter__() */
|
/* ITERABLE.__iter__() */
|
||||||
beginScope();
|
beginScope();
|
||||||
|
2
debug.c
2
debug.c
@ -146,6 +146,8 @@ size_t krk_disassembleInstruction(FILE * f, KrkFunction * func, size_t offset) {
|
|||||||
JUMP(OP_JUMP_IF_TRUE,+)
|
JUMP(OP_JUMP_IF_TRUE,+)
|
||||||
JUMP(OP_LOOP,-)
|
JUMP(OP_LOOP,-)
|
||||||
JUMP(OP_PUSH_TRY,+)
|
JUMP(OP_PUSH_TRY,+)
|
||||||
|
JUMP(OP_PUSH_WITH,+)
|
||||||
|
SIMPLE(OP_CLEANUP_WITH)
|
||||||
default:
|
default:
|
||||||
fprintf(f, "Unknown opcode: %02x", opcode);
|
fprintf(f, "Unknown opcode: %02x", opcode);
|
||||||
}
|
}
|
||||||
|
2
object.h
2
object.h
@ -111,6 +111,8 @@ typedef struct KrkClass {
|
|||||||
KrkObj * _init;
|
KrkObj * _init;
|
||||||
KrkObj * _eq;
|
KrkObj * _eq;
|
||||||
KrkObj * _len;
|
KrkObj * _len;
|
||||||
|
KrkObj * _enter;
|
||||||
|
KrkObj * _exit;
|
||||||
} KrkClass;
|
} KrkClass;
|
||||||
|
|
||||||
typedef struct KrkInstance {
|
typedef struct KrkInstance {
|
||||||
|
35
test/testContextManagerEarlyReturn.krk
Normal file
35
test/testContextManagerEarlyReturn.krk
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from fileio import open
|
||||||
|
|
||||||
|
class ContextManager:
|
||||||
|
def __init__(title):
|
||||||
|
self.title = title
|
||||||
|
def __enter__():
|
||||||
|
print("Enter context manager", self.title)
|
||||||
|
def __exit__():
|
||||||
|
print("Exit context manager", self.title)
|
||||||
|
|
||||||
|
|
||||||
|
def doesThing():
|
||||||
|
print("Creating CM")
|
||||||
|
let cm = ContextManager('simple')
|
||||||
|
|
||||||
|
with cm:
|
||||||
|
print("In context manager")
|
||||||
|
return 42
|
||||||
|
|
||||||
|
print("Not exiting correctly?")
|
||||||
|
return "oh no"
|
||||||
|
|
||||||
|
print(doesThing())
|
||||||
|
|
||||||
|
|
||||||
|
def doesANestedThing():
|
||||||
|
with ContextManager('outer'):
|
||||||
|
with ContextManager('inner'):
|
||||||
|
with ContextManager('triple'):
|
||||||
|
return 42
|
||||||
|
print('Should not print')
|
||||||
|
print('Should not print')
|
||||||
|
print('Should not print')
|
||||||
|
|
||||||
|
print(doesANestedThing())
|
12
test/testContextManagerEarlyReturn.krk.expect
Normal file
12
test/testContextManagerEarlyReturn.krk.expect
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Creating CM
|
||||||
|
Enter context manager simple
|
||||||
|
In context manager
|
||||||
|
Exit context manager simple
|
||||||
|
42
|
||||||
|
Enter context manager outer
|
||||||
|
Enter context manager inner
|
||||||
|
Enter context manager triple
|
||||||
|
Exit context manager triple
|
||||||
|
Exit context manager inner
|
||||||
|
Exit context manager outer
|
||||||
|
42
|
2
value.c
2
value.c
@ -34,7 +34,7 @@ void krk_printValue(FILE * f, KrkValue printable) {
|
|||||||
case VAL_BOOLEAN: fprintf(f, "%s", AS_BOOLEAN(printable) ? "True" : "False"); break;
|
case VAL_BOOLEAN: fprintf(f, "%s", AS_BOOLEAN(printable) ? "True" : "False"); break;
|
||||||
case VAL_FLOATING: fprintf(f, "%g", AS_FLOATING(printable)); break;
|
case VAL_FLOATING: fprintf(f, "%g", AS_FLOATING(printable)); break;
|
||||||
case VAL_NONE: fprintf(f, "None"); break;
|
case VAL_NONE: fprintf(f, "None"); break;
|
||||||
case VAL_HANDLER: fprintf(f, "{try->%ld}", AS_HANDLER(printable)); break;
|
case VAL_HANDLER: fprintf(f, "{%s->%d}", AS_HANDLER(printable).type == OP_PUSH_TRY ? "try" : "with", (int)AS_HANDLER(printable).target); break;
|
||||||
case VAL_KWARGS: {
|
case VAL_KWARGS: {
|
||||||
if (AS_INTEGER(printable) == LONG_MAX) {
|
if (AS_INTEGER(printable) == LONG_MAX) {
|
||||||
fprintf(f, "{unpack single}");
|
fprintf(f, "{unpack single}");
|
||||||
|
12
value.h
12
value.h
@ -17,13 +17,18 @@ typedef enum {
|
|||||||
/* More here later */
|
/* More here later */
|
||||||
} KrkValueType;
|
} KrkValueType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned short type;
|
||||||
|
unsigned short target;
|
||||||
|
} KrkJumpTarget;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
KrkValueType type;
|
KrkValueType type;
|
||||||
union {
|
union {
|
||||||
char boolean;
|
char boolean;
|
||||||
long integer;
|
long integer;
|
||||||
double floating;
|
double floating;
|
||||||
long handler;
|
KrkJumpTarget handler;
|
||||||
KrkObj * object;
|
KrkObj * object;
|
||||||
} as;
|
} as;
|
||||||
} KrkValue;
|
} KrkValue;
|
||||||
@ -32,7 +37,7 @@ typedef struct {
|
|||||||
#define NONE_VAL(value) ((KrkValue){VAL_NONE, {.integer = 0}})
|
#define NONE_VAL(value) ((KrkValue){VAL_NONE, {.integer = 0}})
|
||||||
#define INTEGER_VAL(value) ((KrkValue){VAL_INTEGER, {.integer = value}})
|
#define INTEGER_VAL(value) ((KrkValue){VAL_INTEGER, {.integer = value}})
|
||||||
#define FLOATING_VAL(value) ((KrkValue){VAL_FLOATING,{.floating = value}})
|
#define FLOATING_VAL(value) ((KrkValue){VAL_FLOATING,{.floating = value}})
|
||||||
#define HANDLER_VAL(value) ((KrkValue){VAL_HANDLER, {.handler = value}})
|
#define HANDLER_VAL(ty,ta) ((KrkValue){VAL_HANDLER, {.handler = (KrkJumpTarget){.type = ty, .target = ta}}})
|
||||||
#define OBJECT_VAL(value) ((KrkValue){VAL_OBJECT, {.object = (KrkObj*)value}})
|
#define OBJECT_VAL(value) ((KrkValue){VAL_OBJECT, {.object = (KrkObj*)value}})
|
||||||
#define KWARGS_VAL(value) ((KrkValue){VAL_KWARGS, {.integer = value}})
|
#define KWARGS_VAL(value) ((KrkValue){VAL_KWARGS, {.integer = value}})
|
||||||
|
|
||||||
@ -50,6 +55,9 @@ typedef struct {
|
|||||||
#define IS_OBJECT(value) ((value).type == VAL_OBJECT)
|
#define IS_OBJECT(value) ((value).type == VAL_OBJECT)
|
||||||
#define IS_KWARGS(value) ((value).type == VAL_KWARGS)
|
#define IS_KWARGS(value) ((value).type == VAL_KWARGS)
|
||||||
|
|
||||||
|
#define IS_TRY_HANDLER(value) (IS_HANDLER(value) && AS_HANDLER(value).type == OP_PUSH_TRY)
|
||||||
|
#define IS_WITH_HANDLER(value) (IS_HANDLER(value) && AS_HANDLER(value).type == OP_PUSH_WITH)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
size_t capacity;
|
size_t capacity;
|
||||||
size_t count;
|
size_t count;
|
||||||
|
49
vm.c
49
vm.c
@ -298,6 +298,8 @@ void krk_finalizeClass(KrkClass * _class) {
|
|||||||
{&_class->_init, METHOD_INIT},
|
{&_class->_init, METHOD_INIT},
|
||||||
{&_class->_eq, METHOD_EQ},
|
{&_class->_eq, METHOD_EQ},
|
||||||
{&_class->_len, METHOD_LEN},
|
{&_class->_len, METHOD_LEN},
|
||||||
|
{&_class->_enter, METHOD_ENTER},
|
||||||
|
{&_class->_exit, METHOD_EXIT},
|
||||||
{NULL, 0},
|
{NULL, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2637,6 +2639,8 @@ void krk_initVM(int flags) {
|
|||||||
vm.specialMethodNames[METHOD_DICT_INT] = OBJECT_VAL(S("__dict"));
|
vm.specialMethodNames[METHOD_DICT_INT] = OBJECT_VAL(S("__dict"));
|
||||||
vm.specialMethodNames[METHOD_INREPR] = OBJECT_VAL(S("__inrepr"));
|
vm.specialMethodNames[METHOD_INREPR] = OBJECT_VAL(S("__inrepr"));
|
||||||
vm.specialMethodNames[METHOD_EQ] = OBJECT_VAL(S("__eq__"));
|
vm.specialMethodNames[METHOD_EQ] = OBJECT_VAL(S("__eq__"));
|
||||||
|
vm.specialMethodNames[METHOD_ENTER] = OBJECT_VAL(S("__enter__"));
|
||||||
|
vm.specialMethodNames[METHOD_EXIT] = OBJECT_VAL(S("__exit__"));
|
||||||
|
|
||||||
/* Create built-in class `object` */
|
/* Create built-in class `object` */
|
||||||
vm.objectClass = krk_newClass(S("object"));
|
vm.objectClass = krk_newClass(S("object"));
|
||||||
@ -3011,7 +3015,7 @@ static void addObjects() {
|
|||||||
static int handleException() {
|
static int handleException() {
|
||||||
int stackOffset, frameOffset;
|
int stackOffset, frameOffset;
|
||||||
int exitSlot = (vm.exitOnFrame >= 0) ? vm.frames[vm.exitOnFrame].outSlots : 0;
|
int exitSlot = (vm.exitOnFrame >= 0) ? vm.frames[vm.exitOnFrame].outSlots : 0;
|
||||||
for (stackOffset = (int)(vm.stackTop - vm.stack - 1); stackOffset >= exitSlot && !IS_HANDLER(vm.stack[stackOffset]); stackOffset--);
|
for (stackOffset = (int)(vm.stackTop - vm.stack - 1); stackOffset >= exitSlot && !IS_TRY_HANDLER(vm.stack[stackOffset]); stackOffset--);
|
||||||
if (stackOffset < exitSlot) {
|
if (stackOffset < exitSlot) {
|
||||||
if (exitSlot == 0) {
|
if (exitSlot == 0) {
|
||||||
/*
|
/*
|
||||||
@ -3288,9 +3292,33 @@ static KrkValue run() {
|
|||||||
int operandWidth = (opcode & (1 << 7)) ? 3 : 1;
|
int operandWidth = (opcode & (1 << 7)) ? 3 : 1;
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
case OP_CLEANUP_WITH: {
|
||||||
|
/* Top of stack is a HANDLER that should have had something loaded into it if it was still valid */
|
||||||
|
KrkValue handler = krk_peek(0);
|
||||||
|
KrkValue contextManager = krk_peek(1);
|
||||||
|
KrkClass * type = AS_CLASS(krk_typeOf(1, &contextManager));
|
||||||
|
krk_push(contextManager);
|
||||||
|
krk_callSimple(OBJECT_VAL(type->_exit), 1, 0);
|
||||||
|
/* Top of stack is now either someone else's problem or a return value */
|
||||||
|
if (AS_HANDLER(handler).type != OP_RETURN) break;
|
||||||
|
krk_pop(); /* handler */
|
||||||
|
krk_pop(); /* context manager */
|
||||||
|
} /* fallthrough */
|
||||||
case OP_RETURN: {
|
case OP_RETURN: {
|
||||||
KrkValue result = krk_pop();
|
KrkValue result = krk_pop();
|
||||||
closeUpvalues(frame->slots);
|
closeUpvalues(frame->slots);
|
||||||
|
/* See if this frame had a thing */
|
||||||
|
int stackOffset;
|
||||||
|
for (stackOffset = (int)(vm.stackTop - vm.stack - 1); stackOffset >= (int)frame->slots && !IS_WITH_HANDLER(vm.stack[stackOffset]); stackOffset--);
|
||||||
|
if (stackOffset >= (int)frame->slots) {
|
||||||
|
vm.stackTop = &vm.stack[stackOffset + 1];
|
||||||
|
krk_push(result);
|
||||||
|
krk_swap(2);
|
||||||
|
krk_swap(1);
|
||||||
|
frame->ip = frame->closure->function->chunk.code + AS_HANDLER(krk_peek(0)).target;
|
||||||
|
AS_HANDLER(vm.stackTop[-1]).type = OP_RETURN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
vm.frameCount--;
|
vm.frameCount--;
|
||||||
if (vm.frameCount == 0) {
|
if (vm.frameCount == 0) {
|
||||||
krk_pop();
|
krk_pop();
|
||||||
@ -3423,7 +3451,7 @@ static KrkValue run() {
|
|||||||
}
|
}
|
||||||
case OP_PUSH_TRY: {
|
case OP_PUSH_TRY: {
|
||||||
uint16_t tryTarget = readBytes(frame, 2) + (frame->ip - frame->closure->function->chunk.code);
|
uint16_t tryTarget = readBytes(frame, 2) + (frame->ip - frame->closure->function->chunk.code);
|
||||||
KrkValue handler = HANDLER_VAL(tryTarget);
|
KrkValue handler = HANDLER_VAL(OP_PUSH_TRY, tryTarget);
|
||||||
krk_push(handler);
|
krk_push(handler);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -3647,12 +3675,27 @@ static KrkValue run() {
|
|||||||
vm.stackTop[-count] = AS_TUPLE(tuple)->values.values[0];
|
vm.stackTop[-count] = AS_TUPLE(tuple)->values.values[0];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OP_PUSH_WITH: {
|
||||||
|
uint16_t cleanupTarget = readBytes(frame, 2) + (frame->ip - frame->closure->function->chunk.code);
|
||||||
|
KrkValue contextManager = krk_peek(0);
|
||||||
|
KrkClass * type = AS_CLASS(krk_typeOf(1, &contextManager));
|
||||||
|
if (!type->_enter || !type->_exit) {
|
||||||
|
krk_runtimeError(vm.exceptions.attributeError, "Can not use '%s' as context manager.", krk_typeName(contextManager));
|
||||||
|
goto _finishException;
|
||||||
|
}
|
||||||
|
krk_push(contextManager);
|
||||||
|
krk_callSimple(OBJECT_VAL(type->_enter), 1, 0);
|
||||||
|
/* Ignore result; don't need to pop */
|
||||||
|
KrkValue handler = HANDLER_VAL(OP_PUSH_WITH, cleanupTarget);
|
||||||
|
krk_push(handler);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!(vm.flags & KRK_HAS_EXCEPTION)) continue;
|
if (!(vm.flags & KRK_HAS_EXCEPTION)) continue;
|
||||||
_finishException:
|
_finishException:
|
||||||
if (!handleException()) {
|
if (!handleException()) {
|
||||||
frame = &vm.frames[vm.frameCount - 1];
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
frame->ip = frame->closure->function->chunk.code + AS_HANDLER(krk_peek(0));
|
frame->ip = frame->closure->function->chunk.code + AS_HANDLER(krk_peek(0)).target;
|
||||||
/* Replace the exception handler with the exception */
|
/* Replace the exception handler with the exception */
|
||||||
krk_pop();
|
krk_pop();
|
||||||
krk_push(vm.currentException);
|
krk_push(vm.currentException);
|
||||||
|
Loading…
Reference in New Issue
Block a user