Overhaul exceptions with tracebacks; 'except Type...'

This commit is contained in:
K. Lange 2021-02-18 11:04:59 +09:00
parent b527561b53
commit 9bbb0a1d6e
9 changed files with 188 additions and 41 deletions

View File

@ -49,6 +49,7 @@ typedef enum {
OP_SUBTRACT,
OP_SWAP,
OP_TRUE,
OP_FILTER_EXCEPT,
OP_CALL = 64,
OP_CLASS,

View File

@ -191,7 +191,7 @@ static void and_(int canAssign);
static KrkToken classDeclaration();
static void declareVariable();
static void namedVariable(KrkToken name, int canAssign);
static void addLocal(KrkToken name);
static Local * addLocal(KrkToken name);
static void string(int canAssign);
static KrkToken decorator(size_t level, FunctionType type);
static void call(int canAssign);
@ -1536,7 +1536,8 @@ 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);
addLocal(syntheticToken("exception"));
/* We'll rename this later, but it needs to be on the stack now as it represents the exception handler */
Local * exceptionObject = addLocal(syntheticToken(""));
defineVariable(0);
beginScope();
@ -1553,6 +1554,16 @@ static void tryStatement() {
advance();
}
if (match(TOKEN_EXCEPT)) {
if (!check(TOKEN_COLON) && !check(TOKEN_AS)) {
expression();
emitByte(OP_FILTER_EXCEPT);
}
if (match(TOKEN_AS)) {
consume(TOKEN_IDENTIFIER, "Expected identifier after 'as'");
exceptionObject->name = parser.previous;
} else {
exceptionObject->name = syntheticToken("exception");
}
consume(TOKEN_COLON, "Expect ':' after except.");
beginScope();
block(blockWidth,"except");
@ -2428,7 +2439,7 @@ static ssize_t resolveLocal(Compiler * compiler, KrkToken * name) {
return -1;
}
static void addLocal(KrkToken name) {
static Local * addLocal(KrkToken name) {
if (current->localCount + 1 > current->localsSpace) {
size_t old = current->localsSpace;
current->localsSpace = GROW_CAPACITY(old);
@ -2449,6 +2460,7 @@ static void addLocal(KrkToken name) {
current->function->localNames[current->function->localNameCount].deathday = 0;
current->function->localNames[current->function->localNameCount].name = krk_copyString(name.start, name.length);
current->function->localNameCount++;
return local;
}
static void declareVariable() {

View File

@ -3,6 +3,7 @@
#include "value.h"
#include "memory.h"
#include "util.h"
#include "debug.h"
/* function.__doc__ */
static KrkValue _closure_get_doc(int argc, KrkValue argv[], int hasKw) {
@ -24,13 +25,15 @@ static KrkValue _bound_get_doc(int argc, KrkValue argv[], int hasKw) {
/* Check for and return the name of a native function as a string object */
static KrkValue nativeFunctionName(KrkValue func) {
const char * string = ((KrkNative*)AS_OBJECT(func))->name;
if (!string) return OBJECT_VAL(S("<unnamed>"));
size_t len = strlen(string);
return OBJECT_VAL(krk_copyString(string,len));
}
/* function.__name__ */
static KrkValue _closure_get_name(int argc, KrkValue argv[], int hasKw) {
if (!IS_CLOSURE(argv[0])) return nativeFunctionName(argv[0]);
if (IS_NATIVE(argv[0])) return nativeFunctionName(argv[0]);
else if (!IS_CLOSURE(argv[0])) return krk_runtimeError(vm.exceptions->typeError, "'%s' is neither a closure nor a native", krk_typeName(argv[0]));
return AS_CLOSURE(argv[0])->function->name ? OBJECT_VAL(AS_CLOSURE(argv[0])->function->name) : OBJECT_VAL(S(""));
}
@ -40,9 +43,22 @@ static KrkValue _bound_get_name(int argc, KrkValue argv[], int hasKw) {
return _closure_get_name(1, (KrkValue[]){OBJECT_VAL(boundMethod->method)}, hasKw);
}
static KrkValue _closure_ip_to_line(int argc, KrkValue argv[], int hasKw) {
if (argc < 2) return krk_runtimeError(vm.exceptions->argumentError, "%s() expects exactly 2 arguments", "ip_to_line");
if (!IS_CLOSURE(argv[0])) return NONE_VAL();
if (!IS_INTEGER(argv[1])) return TYPE_ERROR(int,argv[1]);
return INTEGER_VAL(krk_lineNumber(&AS_CLOSURE(argv[0])->function->chunk, AS_INTEGER(argv[1])));
}
static KrkValue _bound_ip_to_line(int argc, KrkValue argv[], int hasKw) {
KrkBoundMethod * boundMethod = AS_BOUND_METHOD(argv[0]);
return _closure_ip_to_line(1, (KrkValue[]){OBJECT_VAL(boundMethod->method)}, hasKw);
}
/* function.__str__ / function.__repr__ */
static KrkValue _closure_str(int argc, KrkValue argv[], int hasKw) {
KrkValue s = _closure_get_name(argc, argv, hasKw);
if (!IS_STRING(s)) return NONE_VAL();
krk_push(s);
size_t len = AS_STRING(s)->length + sizeof("<function >");
@ -114,6 +130,7 @@ void _createAndBind_functionClass(void) {
krk_defineNative(&vm.baseClasses->functionClass->methods, ":__name__", _closure_get_name);
krk_defineNative(&vm.baseClasses->functionClass->methods, ":__file__", _closure_get_file);
krk_defineNative(&vm.baseClasses->functionClass->methods, ":__args__", _closure_get_argnames);
krk_defineNative(&vm.baseClasses->functionClass->methods, "_ip_to_line", _closure_ip_to_line);
krk_finalizeClass(vm.baseClasses->functionClass);
ADD_BASE_CLASS(vm.baseClasses->methodClass, "method", vm.baseClasses->objectClass);
@ -123,5 +140,6 @@ void _createAndBind_functionClass(void) {
krk_defineNative(&vm.baseClasses->methodClass->methods, ":__name__", _bound_get_name);
krk_defineNative(&vm.baseClasses->methodClass->methods, ":__file__", _bound_get_file);
krk_defineNative(&vm.baseClasses->methodClass->methods, ":__args__", _bound_get_argnames);
krk_defineNative(&vm.baseClasses->methodClass->methods, "_ip_to_line", _bound_ip_to_line);
krk_finalizeClass(vm.baseClasses->methodClass);
}

View File

@ -36,6 +36,7 @@ SIMPLE(OP_IS)
SIMPLE(OP_POW)
SIMPLE(OP_CREATE_PROPERTY)
SIMPLE(OP_CLEANUP_WITH)
SIMPLE(OP_FILTER_EXCEPT)
CONSTANT(OP_DEFINE_GLOBAL,(void)0)
CONSTANT(OP_CONSTANT,(void)0)
CONSTANT(OP_GET_GLOBAL,(void)0)

148
src/vm.c
View File

@ -167,47 +167,66 @@ static void dumpStack(CallFrame * frame) {
* the source file and print the corresponding line.
*/
void krk_dumpTraceback() {
if (krk_currentThread.frameCount) {
fprintf(stderr, "Traceback (most recent call last):\n");
for (size_t i = 0; i <= krk_currentThread.frameCount - 1; i++) {
CallFrame * frame = &krk_currentThread.frames[i];
KrkFunction * function = frame->closure->function;
size_t instruction = frame->ip - function->chunk.code - 1;
int lineNo = (int)krk_lineNumber(&function->chunk, instruction);
fprintf(stderr, " File \"%s\", line %d, in %s\n",
(function->chunk.filename ? function->chunk.filename->chars : "?"),
lineNo,
(function->name ? function->name->chars : "(unnamed)"));
if (function->chunk.filename) {
FILE * f = fopen(function->chunk.filename->chars, "r");
if (f) {
int line = 1;
do {
char c = fgetc(f);
if (c < -1) break;
if (c == '\n') {
line++;
continue;
}
if (line == lineNo) {
fprintf(stderr," ");
while (c == ' ') c = fgetc(f);
do {
fputc(c, stderr);
c = fgetc(f);
} while (!feof(f) && c > 0 && c != '\n');
fprintf(stderr, "\n");
break;
}
} while (!feof(f));
fclose(f);
if (!krk_valuesEqual(krk_currentThread.currentException,NONE_VAL())) {
krk_push(krk_currentThread.currentException);
KrkValue tracebackEntries;
if (IS_INSTANCE(krk_currentThread.currentException)
&& krk_tableGet(&AS_INSTANCE(krk_currentThread.currentException)->fields, OBJECT_VAL(S("traceback")), &tracebackEntries)
&& IS_list(tracebackEntries) && AS_LIST(tracebackEntries)->count > 0) {
/* This exception has a traceback we can print. */
fprintf(stderr, "Traceback (most recent call last):\n");
for (size_t i = 0; i < AS_LIST(tracebackEntries)->count; ++i) {
/* Quietly skip invalid entries as we don't want to bother printing explanatory text for them */
if (!IS_TUPLE(AS_LIST(tracebackEntries)->values[i])) continue;
KrkTuple * entry = AS_TUPLE(AS_LIST(tracebackEntries)->values[i]);
if (entry->values.count != 2) continue;
if (!IS_CLOSURE(entry->values.values[0])) continue;
if (!IS_INTEGER(entry->values.values[1])) continue;
/* Get the function and instruction index from this traceback entry */
KrkClosure * closure = AS_CLOSURE(entry->values.values[0]);
KrkFunction * function = closure->function;
size_t instruction = AS_INTEGER(entry->values.values[1]);
/* Calculate the line number */
int lineNo = (int)krk_lineNumber(&function->chunk, instruction);
/* Print the simple stuff that we already know */
fprintf(stderr, " File \"%s\", line %d, in %s\n",
(function->chunk.filename ? function->chunk.filename->chars : "?"),
lineNo,
(function->name ? function->name->chars : "(unnamed)"));
/* Try to open the file */
if (function->chunk.filename) {
FILE * f = fopen(function->chunk.filename->chars, "r");
if (f) {
int line = 1;
do {
char c = fgetc(f);
if (c < -1) break;
if (c == '\n') {
line++;
continue;
}
if (line == lineNo) {
fprintf(stderr," ");
while (c == ' ') c = fgetc(f);
do {
fputc(c, stderr);
c = fgetc(f);
} while (!feof(f) && c > 0 && c != '\n');
fprintf(stderr, "\n");
break;
}
} while (!feof(f));
fclose(f);
}
}
}
}
}
if (!krk_valuesEqual(krk_currentThread.currentException,NONE_VAL())) {
krk_push(krk_currentThread.currentException);
/* Is this a SyntaxError? Handle those specially. */
if (krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->syntaxError)) {
KrkValue result = krk_callSimple(OBJECT_VAL(krk_getType(krk_currentThread.currentException)->_tostr), 1, 0);
@ -228,6 +247,37 @@ void krk_dumpTraceback() {
}
}
/**
* Attach a traceback to the current exception object, if it doesn't already have one.
*/
static void attachTraceback(void) {
if (IS_INSTANCE(krk_currentThread.currentException)) {
KrkInstance * theException = AS_INSTANCE(krk_currentThread.currentException);
/* If there already is a traceback, don't add a new one; this exception was re-raised. */
KrkValue existing;
if (krk_tableGet(&theException->fields, OBJECT_VAL(S("traceback")), &existing)) return;
KrkValue tracebackList = krk_list_of(0,NULL,0);
krk_push(tracebackList);
/* Build the traceback object */
if (krk_currentThread.frameCount) {
for (size_t i = 0; i < krk_currentThread.frameCount; i++) {
CallFrame * frame = &krk_currentThread.frames[i];
KrkTuple * tbEntry = krk_newTuple(2);
krk_push(OBJECT_VAL(tbEntry));
tbEntry->values.values[tbEntry->values.count++] = OBJECT_VAL(frame->closure);
tbEntry->values.values[tbEntry->values.count++] = INTEGER_VAL(frame->ip - frame->closure->function->chunk.code - 1);
krk_tupleUpdateHash(tbEntry);
krk_writeValueArray(AS_LIST(tracebackList), OBJECT_VAL(tbEntry));
krk_pop();
}
}
krk_attachNamedValue(&theException->fields, "traceback", tracebackList);
krk_pop();
} /* else: probably a legacy 'raise str', just don't bother. */
}
/**
* Raise an exception. Creates an exception object of the requested type
* and formats a message string to attach to it. Exception classes are
@ -254,6 +304,7 @@ KrkValue krk_runtimeError(KrkClass * type, const char * fmt, ...) {
/* Set the current exception to be picked up by handleException */
krk_currentThread.currentException = OBJECT_VAL(exceptionObject);
attachTraceback();
return NONE_VAL();
}
@ -1831,6 +1882,7 @@ static KrkValue run() {
case OP_POP: krk_pop(); break;
case OP_RAISE: {
krk_currentThread.currentException = krk_pop();
attachTraceback();
krk_currentThread.flags |= KRK_HAS_EXCEPTION;
goto _finishException;
}
@ -1945,6 +1997,28 @@ static KrkValue run() {
krk_push(OBJECT_VAL(newProperty));
break;
}
case OP_FILTER_EXCEPT: {
int isMatch = 0;
if (IS_CLASS(krk_peek(0)) && krk_isInstanceOf(krk_peek(1), AS_CLASS(krk_peek(0)))) {
isMatch = 1;
} 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]))) {
isMatch = 1;
break;
}
}
}
if (!isMatch) {
/* Restore and re-raise the exception if it didn't match. */
krk_currentThread.currentException = krk_peek(1);
krk_currentThread.flags |= KRK_HAS_EXCEPTION;
goto _finishException;
}
/* Else pop the filter value */
krk_pop();
break;
}
/*
* Two-byte operands

View File

@ -0,0 +1,17 @@
def doTheThing(excp):
try:
try:
raise excp
except (TypeError, ValueError):
print("Caught a", repr(exception))
for i in exception.traceback:
let func, instr = i
print(f" File '{func.__file__}', line {func._ip_to_line(instr)}, in {func.__name__}")
except NameError:
print("That's a name error!")
doTheThing(TypeError("A type error"))
doTheThing(ValueError("A value error"))
doTheThing(NameError("A name error"))

View File

@ -0,0 +1,7 @@
Caught a TypeError('A type error')
File 'test/testExceptionTracebacks.krk', line 14, in __main__
File 'test/testExceptionTracebacks.krk', line 4, in doTheThing
Caught a ValueError('A value error')
File 'test/testExceptionTracebacks.krk', line 15, in __main__
File 'test/testExceptionTracebacks.krk', line 4, in doTheThing
That's a name error!

View File

@ -0,0 +1,14 @@
def doTheThing(excp):
try:
try:
raise excp
except (TypeError, ValueError) as e:
print("Caught a", repr(e))
except NameError:
print("That's a name error!")
doTheThing(TypeError("A type error"))
doTheThing(ValueError("A value error"))
doTheThing(NameError("A name error"))

View File

@ -0,0 +1,3 @@
Caught a TypeError('A type error')
Caught a ValueError('A value error')
That's a name error!