Implement 'finally'

This commit is contained in:
K. Lange 2021-03-18 19:52:30 +09:00
parent baad0997fb
commit 8aed1368ea
14 changed files with 310 additions and 40 deletions

View File

@ -66,7 +66,9 @@ typedef enum {
OP_BREAKPOINT, /* NEVER output this instruction in the compiler or bad things can happen */
OP_YIELD,
OP_ANNOTATE,
/* current highest: 44 */
OP_BEGIN_FINALLY,
OP_END_FINALLY,
/* current highest: 45 */
OP_CALL = 64,
OP_CLASS,

View File

@ -257,6 +257,7 @@ static KrkToken classDeclaration();
static void declareVariable();
static void namedVariable(KrkToken name, int canAssign);
static size_t addLocal(KrkToken name);
static size_t renameLocal(size_t ind, KrkToken name);
static void string(int canAssign);
static KrkToken decorator(size_t level, FunctionType type);
static void call(int canAssign);
@ -907,7 +908,8 @@ static void endScope() {
while (current->localCount > 0 &&
current->locals[current->localCount - 1].depth > (ssize_t)current->scopeDepth) {
for (size_t i = 0; i < current->codeobject->localNameCount; i++) {
if (current->codeobject->localNames[i].id == current->localCount - 1) {
if (current->codeobject->localNames[i].id == current->localCount - 1 &&
current->codeobject->localNames[i].deathday == 0) {
current->codeobject->localNames[i].deathday = (size_t)currentChunk()->count;
}
}
@ -1730,18 +1732,27 @@ static void tryStatement() {
/* Make sure we are in a local scope so this ends up on the stack */
beginScope();
int tryJump = emitJump(OP_PUSH_TRY);
/* We'll rename this later, but it needs to be on the stack now as it represents the exception handler */
size_t localNameCount = current->codeobject->localNameCount;
size_t exceptionObject = addLocal(syntheticToken(""));
defineVariable(0);
markInitialized();
addLocal(syntheticToken("")); /* Try */
markInitialized();
beginScope();
block(blockWidth,"try");
endScope();
int successJump = emitJump(OP_JUMP);
#define EXIT_JUMP_MAX 32
int exitJumps = 1;
int exitJumpOffsets[EXIT_JUMP_MAX] = {0};
exitJumpOffsets[0] = emitJump(OP_JUMP);
patchJump(tryJump);
int nextJump = -1;
_anotherExcept:
if (blockWidth == 0 || (check(TOKEN_INDENTATION) && (parser.current.length == blockWidth))) {
KrkToken previous;
if (blockWidth) {
@ -1749,11 +1760,19 @@ static void tryStatement() {
advance();
}
if (match(TOKEN_EXCEPT)) {
if (nextJump != -1) {
patchJump(nextJump);
emitByte(OP_POP);
}
/* Match filter expression (should be class or tuple) */
if (!check(TOKEN_COLON) && !check(TOKEN_AS)) {
expression();
emitByte(OP_FILTER_EXCEPT);
} else {
emitByte(OP_NONE);
}
emitByte(OP_FILTER_EXCEPT);
nextJump = emitJump(OP_JUMP_IF_FALSE);
emitByte(OP_POP);
/* Match 'as' to rename exception */
if (match(TOKEN_AS)) {
@ -1763,14 +1782,42 @@ static void tryStatement() {
/* XXX Should we remove this now? */
current->locals[exceptionObject].name = syntheticToken("exception");
}
/* Make sure we update the local name for debugging */
current->codeobject->localNames[localNameCount].birthday = currentChunk()->count;
current->codeobject->localNames[localNameCount].name = krk_copyString(current->locals[exceptionObject].name.start, current->locals[exceptionObject].name.length);
size_t nameInd = renameLocal(exceptionObject, current->locals[exceptionObject].name);
consume(TOKEN_COLON, "Expect ':' after except.");
beginScope();
block(blockWidth,"except");
endScope();
current->codeobject->localNames[nameInd].deathday = (size_t)currentChunk()->count;
if (exitJumps < EXIT_JUMP_MAX) {
exitJumpOffsets[exitJumps++] = emitJump(OP_JUMP);
} else {
error("too many except clauses");
return;
}
goto _anotherExcept;
} else if (match(TOKEN_FINALLY)) {
consume(TOKEN_COLON, "expected : after 'finally'");
for (int i = 0; i < exitJumps; ++i) {
patchJump(exitJumpOffsets[i]);
}
size_t nameInd = renameLocal(exceptionObject, syntheticToken("__tmp"));
emitByte(OP_BEGIN_FINALLY);
exitJumps = 0;
if (nextJump != -1) {
patchJump(nextJump);
emitByte(OP_POP);
}
beginScope();
block(blockWidth,"finally");
endScope();
nextJump = -2;
current->codeobject->localNames[nameInd].deathday = (size_t)currentChunk()->count;
emitByte(OP_END_FINALLY);
} else if (!check(TOKEN_EOL) && !check(TOKEN_EOF)) {
krk_ungetToken(parser.current);
parser.current = parser.previous;
@ -1782,7 +1829,18 @@ static void tryStatement() {
}
}
patchJump(successJump);
for (int i = 0; i < exitJumps; ++i) {
patchJump(exitJumpOffsets[i]);
}
if (nextJump >= 0) {
emitByte(OP_BEGIN_FINALLY);
emitByte(OP_NONE);
patchJump(nextJump);
emitByte(OP_POP);
emitByte(OP_END_FINALLY);
}
endScope(); /* will pop the exception handler */
}
@ -2818,6 +2876,19 @@ static ssize_t resolveLocal(Compiler * compiler, KrkToken * name) {
return -1;
}
static size_t renameLocal(size_t ind, KrkToken name) {
if (current->codeobject->localNameCount + 1 > current->localNameCapacity) {
size_t old = current->localNameCapacity;
current->localNameCapacity = GROW_CAPACITY(old);
current->codeobject->localNames = GROW_ARRAY(KrkLocalEntry, current->codeobject->localNames, old, current->localNameCapacity);
}
current->codeobject->localNames[current->codeobject->localNameCount].id = ind;
current->codeobject->localNames[current->codeobject->localNameCount].birthday = currentChunk()->count;
current->codeobject->localNames[current->codeobject->localNameCount].deathday = 0;
current->codeobject->localNames[current->codeobject->localNameCount].name = krk_copyString(name.start, name.length);
return current->codeobject->localNameCount++;
}
static size_t addLocal(KrkToken name) {
if (current->localCount + 1 > current->localsSpace) {
size_t old = current->localsSpace;
@ -2830,16 +2901,10 @@ static size_t addLocal(KrkToken name) {
local->depth = -1;
local->isCaptured = 0;
if (current->codeobject->localNameCount + 1 > current->localNameCapacity) {
size_t old = current->localNameCapacity;
current->localNameCapacity = GROW_CAPACITY(old);
current->codeobject->localNames = GROW_ARRAY(KrkLocalEntry, current->codeobject->localNames, old, current->localNameCapacity);
if (name.length) {
renameLocal(out, name);
}
current->codeobject->localNames[current->codeobject->localNameCount].id = current->localCount-1;
current->codeobject->localNames[current->codeobject->localNameCount].birthday = currentChunk()->count;
current->codeobject->localNames[current->codeobject->localNameCount].deathday = 0;
current->codeobject->localNames[current->codeobject->localNameCount].name = krk_copyString(name.start, name.length);
current->codeobject->localNameCount++;
return out;
}

View File

@ -37,6 +37,7 @@ void krk_debug_dumpStack(FILE * file, KrkCallFrame * frame) {
if (relative == f->closure->function->localNames[j].id
/* Only display this name if it's currently valid */
&& f->closure->function->localNames[j].birthday <= (size_t)(f->ip - f->closure->function->chunk.code)
&& f->closure->function->localNames[j].deathday >= (size_t)(f->ip - f->closure->function->chunk.code)
) {
fprintf(file, "%s=", f->closure->function->localNames[j].name->chars);
found = 1;

View File

@ -40,6 +40,8 @@ SIMPLE(OP_FILTER_EXCEPT)
SIMPLE(OP_BREAKPOINT)
SIMPLE(OP_YIELD)
SIMPLE(OP_ANNOTATE)
SIMPLE(OP_BEGIN_FINALLY)
SIMPLE(OP_END_FINALLY)
CONSTANT(OP_DEFINE_GLOBAL,(void)0)
CONSTANT(OP_CONSTANT,(void)0)
CONSTANT(OP_GET_GLOBAL,(void)0)

View File

@ -553,7 +553,7 @@ char * syn_krk_keywords[] = {
"and","class","def","else","for","if","in","import","del",
"let","not","or","return","while","try","except","raise",
"continue","break","as","from","elif","lambda","with","is",
"pass","assert","yield",
"pass","assert","yield","finally",
NULL
};

View File

@ -224,6 +224,7 @@ static KrkTokenType identifierType() {
case 'x': return checkKeyword(2, "cept", TOKEN_EXCEPT);
} break;
case 'f': if (MORE(1)) switch(scanner.start[1]) {
case 'i': return checkKeyword(2, "nally", TOKEN_FINALLY);
case 'o': return checkKeyword(2, "r", TOKEN_FOR);
case 'r': return checkKeyword(2, "om", TOKEN_FROM);
} else if (scanner.start[1] == '\'' || scanner.start[1] == '"') return TOKEN_PREFIX_F;

View File

@ -68,6 +68,7 @@ typedef enum {
TOKEN_DEL,
TOKEN_ELSE,
TOKEN_FALSE,
TOKEN_FINALLY,
TOKEN_FOR,
TOKEN_IF,
TOKEN_IMPORT,

View File

@ -53,7 +53,16 @@ void krk_printValueSafe(FILE * f, KrkValue printable) {
case KRK_VAL_BOOLEAN: fprintf(f, "%s", AS_BOOLEAN(printable) ? "True" : "False"); break;
case KRK_VAL_FLOATING: fprintf(f, "%.16g", AS_FLOATING(printable)); break;
case KRK_VAL_NONE: fprintf(f, "None"); break;
case KRK_VAL_HANDLER: fprintf(f, "{%s->%d}", AS_HANDLER(printable).type == OP_PUSH_TRY ? "try" : "with", (int)AS_HANDLER(printable).target); break;
case KRK_VAL_HANDLER:
switch (AS_HANDLER(printable).type) {
case OP_PUSH_TRY: fprintf(f, "{try->%d}", (int)AS_HANDLER(printable).target); break;
case OP_PUSH_WITH: fprintf(f, "{with->%d}", (int)AS_HANDLER(printable).target); break;
case OP_RAISE: fprintf(f, "{raise<-%d}", (int)AS_HANDLER(printable).target); break;
case OP_FILTER_EXCEPT: fprintf(f, "{except<-%d}", (int)AS_HANDLER(printable).target); break;
case OP_BEGIN_FINALLY: fprintf(f, "{finally<-%d}", (int)AS_HANDLER(printable).target); break;
case OP_RETURN: fprintf(f, "{return<-%d}", (int)AS_HANDLER(printable).target); break;
}
break;
case KRK_VAL_KWARGS: {
if (AS_INTEGER(printable) == LONG_MAX) {
fprintf(f, "{unpack single}");

View File

@ -207,4 +207,6 @@ extern int krk_valuesSame(KrkValue a, KrkValue b);
#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)
#define IS_RAISE_HANDLER(value) (IS_HANDLER(value) && AS_HANDLER(value).type == OP_RAISE)
#define IS_EXCEPT_HANDLER(value) (IS_HANDLER(value) && AS_HANDLER(value).type == OP_FILTER_EXCEPT)

View File

@ -1431,7 +1431,11 @@ MAKE_COMPARATOR(gt, >)
static int handleException() {
int stackOffset, frameOffset;
int exitSlot = (krk_currentThread.exitOnFrame >= 0) ? krk_currentThread.frames[krk_currentThread.exitOnFrame].outSlots : 0;
for (stackOffset = (int)(krk_currentThread.stackTop - krk_currentThread.stack - 1); stackOffset >= exitSlot && !IS_TRY_HANDLER(krk_currentThread.stack[stackOffset]); stackOffset--);
for (stackOffset = (int)(krk_currentThread.stackTop - krk_currentThread.stack - 1);
stackOffset >= exitSlot &&
!IS_TRY_HANDLER(krk_currentThread.stack[stackOffset]) &&
!IS_EXCEPT_HANDLER(krk_currentThread.stack[stackOffset])
; stackOffset--);
if (stackOffset < exitSlot) {
if (exitSlot == 0) {
/*
@ -1982,21 +1986,32 @@ _resumeHook: (void)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 */
krk_pop(); /* contextManager */
} /* fallthrough */
case OP_RETURN: {
_finishReturn: (void)0;
KrkValue result = krk_pop();
closeUpvalues(frame->slots);
/* See if this frame had a thing */
int stackOffset;
for (stackOffset = (int)(krk_currentThread.stackTop - krk_currentThread.stack - 1); stackOffset >= (int)frame->slots && !IS_WITH_HANDLER(krk_currentThread.stack[stackOffset]); stackOffset--);
for (stackOffset = (int)(krk_currentThread.stackTop - krk_currentThread.stack - 1);
stackOffset >= (int)frame->slots &&
!IS_WITH_HANDLER(krk_currentThread.stack[stackOffset]) &&
!IS_TRY_HANDLER(krk_currentThread.stack[stackOffset]) &&
!IS_EXCEPT_HANDLER(krk_currentThread.stack[stackOffset])
; stackOffset--);
if (stackOffset >= (int)frame->slots) {
krk_currentThread.stackTop = &krk_currentThread.stack[stackOffset + 1];
frame->ip = frame->closure->function->chunk.code + AS_HANDLER(krk_peek(0)).target;
int wasWith = (IS_WITH_HANDLER(krk_peek(0)));
AS_HANDLER(krk_currentThread.stackTop[-1]).type = OP_RETURN;
krk_push(result);
krk_swap(2);
krk_swap(1);
frame->ip = frame->closure->function->chunk.code + AS_HANDLER(krk_peek(0)).target;
AS_HANDLER(krk_currentThread.stackTop[-1]).type = OP_RETURN;
if (wasWith) {
krk_swap(1);
} else {
krk_pop();
}
break;
}
krk_currentThread.frameCount--;
@ -2185,24 +2200,52 @@ _resumeHook: (void)0;
break;
case OP_FILTER_EXCEPT: {
int isMatch = 0;
if (IS_CLASS(krk_peek(0)) && krk_isInstanceOf(krk_peek(1), AS_CLASS(krk_peek(0)))) {
if (AS_HANDLER(krk_peek(1)).type == OP_RETURN) {
isMatch = 0;
} else if (AS_HANDLER(krk_peek(1)).type == OP_END_FINALLY) {
isMatch = 0;
} else if (IS_CLASS(krk_peek(0)) && krk_isInstanceOf(krk_peek(2), AS_CLASS(krk_peek(0)))) {
isMatch = 1;
} if (IS_TUPLE(krk_peek(0))) {
} else if (IS_TUPLE(krk_peek(0))) {
for (size_t i = 0; i < AS_TUPLE(krk_peek(0))->values.count; ++i) {
if (IS_CLASS(AS_TUPLE(krk_peek(0))->values.values[i]) && krk_isInstanceOf(krk_peek(1), AS_CLASS(AS_TUPLE(krk_peek(0))->values.values[i]))) {
if (IS_CLASS(AS_TUPLE(krk_peek(0))->values.values[i]) && krk_isInstanceOf(krk_peek(2), AS_CLASS(AS_TUPLE(krk_peek(0))->values.values[i]))) {
isMatch = 1;
break;
}
}
} else if (IS_NONE(krk_peek(0))) {
isMatch = !IS_NONE(krk_peek(2));
}
if (!isMatch) {
/* Restore and re-raise the exception if it didn't match. */
krk_currentThread.currentException = krk_peek(1);
krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION;
goto _finishException;
if (isMatch) {
AS_HANDLER(krk_currentThread.stackTop[-2]).type = OP_FILTER_EXCEPT;
}
/* Else pop the filter value */
krk_pop();
krk_push(BOOLEAN_VAL(isMatch));
break;
}
case OP_BEGIN_FINALLY: {
if (IS_HANDLER(krk_peek(0))) {
if (AS_HANDLER(krk_peek(0)).type == OP_PUSH_TRY) {
AS_HANDLER(krk_currentThread.stackTop[-1]).type = OP_BEGIN_FINALLY;
} else if (AS_HANDLER(krk_peek(0)).type == OP_FILTER_EXCEPT) {
AS_HANDLER(krk_currentThread.stackTop[-1]).type = OP_BEGIN_FINALLY;
}
}
break;
}
case OP_END_FINALLY: {
KrkValue handler = krk_peek(0);
if (IS_HANDLER(handler)) {
if (AS_HANDLER(handler).type == OP_RAISE || AS_HANDLER(handler).type == OP_END_FINALLY) {
krk_pop(); /* handler */
krk_currentThread.currentException = krk_pop();
krk_currentThread.flags |= KRK_THREAD_HAS_EXCEPTION;
goto _finishException;
} else if (AS_HANDLER(handler).type == OP_RETURN) {
krk_push(krk_peek(1));
goto _finishReturn;
}
}
break;
}
case OP_BREAKPOINT: {
@ -2258,6 +2301,7 @@ _resumeHook: (void)0;
}
case OP_PUSH_TRY: {
uint16_t tryTarget = OPERAND + (frame->ip - frame->closure->function->chunk.code);
krk_push(NONE_VAL());
KrkValue handler = HANDLER_VAL(OP_PUSH_TRY, tryTarget);
krk_push(handler);
break;
@ -2623,9 +2667,13 @@ _finishException:
if (!handleException()) {
frame = &krk_currentThread.frames[krk_currentThread.frameCount - 1];
frame->ip = frame->closure->function->chunk.code + AS_HANDLER(krk_peek(0)).target;
/* Replace the exception handler with the exception */
krk_pop();
krk_push(krk_currentThread.currentException);
/* Stick the exception into the exception slot */
if (AS_HANDLER(krk_currentThread.stackTop[-1]).type == OP_FILTER_EXCEPT) {
AS_HANDLER(krk_currentThread.stackTop[-1]).type = OP_END_FINALLY;
} else {
AS_HANDLER(krk_currentThread.stackTop[-1]).type = OP_RAISE;
}
krk_currentThread.stackTop[-2] = krk_currentThread.currentException;
krk_currentThread.currentException = NONE_VAL();
} else {
return NONE_VAL();

73
test/testFinally.krk Normal file
View File

@ -0,0 +1,73 @@
def f():
try:
print("in try")
return 'try'
print("???")
except:
print("exception?")
finally:
print("in finally")
return 'finally'
print(f())
def f():
try:
print("in try")
raise Exception()
except:
print("exception?")
return "exception"
finally:
print("in finally")
return 'finally'
print(f())
def f():
try:
print("in try")
raise Exception()
except:
print("exception?")
return "exception"
finally:
print("in finally")
print(f())
def f():
try:
print("in try")
raise Exception()
finally:
print("in finally")
return 42
print(f())
def f():
try:
print("in try")
raise Exception()
finally:
print("in finally")
try:
print(f())
except:
print("Raised exception.")
def f():
try:
print("in try")
raise Exception()
except:
raise ValueError()
finally:
print("in finally")
try:
print(f())
except Exception as e:
print("Raised",type(e))

View File

@ -0,0 +1,20 @@
in try
in finally
finally
in try
exception?
in finally
finally
in try
exception?
in finally
exception
in try
in finally
42
in try
in finally
Raised exception.
in try
in finally
Raised <class 'ValueError'>

View File

@ -0,0 +1,27 @@
class SpecialException(ValueError):
pass
def exceptionFilter(exc):
print('with',repr(exc))
try:
if exc: raise exc()
print("None")
except TypeError:
print("TypeError")
except ValueError:
print("ValueError")
finally:
print("Running finally")
print("Function exit")
exceptionFilter(None)
exceptionFilter(TypeError)
exceptionFilter(ValueError)
exceptionFilter(SpecialException)
try:
exceptionFilter(NameError)
except Exception as e:
print("NameError was not caught:", repr(e))

View File

@ -0,0 +1,19 @@
with None
None
Running finally
Function exit
with <class 'TypeError'>
TypeError
Running finally
Function exit
with <class 'ValueError'>
ValueError
Running finally
Function exit
with <class '__main__.SpecialException'>
ValueError
Running finally
Function exit
with <class 'NameError'>
Running finally
NameError was not caught: NameError(None)