Improvements to exceptions

- Exceptions get repr'd to print for better flexibility and no more weird
  if/else tree in dumpTraceback to handle other cases.
- Parser / compiler errors are now SyntaxError's.
- Try to read filenames when printing tracebacks.
- Fixup formatting to look more like CPython.
This commit is contained in:
K. Lange 2021-01-17 18:14:08 +09:00
parent f97d8cd562
commit 97922d3922
3 changed files with 130 additions and 68 deletions

View File

@ -189,44 +189,30 @@ static void string(int canAssign);
static KrkToken decorator(size_t level, FunctionType type);
static void call(int canAssign);
static void errorAt(KrkToken * token, const char * message) {
if (parser.panicMode) return;
parser.panicMode = 1;
static void finishError(KrkToken * token) {
size_t i = (token->col - 1);
while (token->linePtr[i] && token->linePtr[i] != '\n') i++;
const char fancyError[] = "Parse error in \"%s\" on line %d col %d (%s): %s\n"
" %.*s\033[31m%.*s\033[39m%.*s\n"
" %-*s\033[31m^\033[39m\n";
const char plainError[] = "Parse error in \"%s\" on line %d col %d (%s): %s\n"
" %.*s%.*s%.*s\n"
" %-*s^\n";
fprintf(stderr, (vm.flags & KRK_NO_ESCAPE) ? plainError: fancyError,
currentChunk()->filename->chars,
(int)token->line,
(int)token->col,
getRule(token->type)->name,
message,
(int)(token->col - 1),
token->linePtr,
(int)(token->literalWidth),
token->linePtr + (token->col - 1),
(int)(i - (token->col - 1 + token->literalWidth)),
token->linePtr + (token->col - 1 + token->literalWidth),
(int)token->col-1,
""
);
krk_attachNamedObject(&AS_INSTANCE(vm.currentException)->fields, "line", (KrkObj*)krk_copyString(token->linePtr, i));
krk_attachNamedObject(&AS_INSTANCE(vm.currentException)->fields, "file", (KrkObj*)currentChunk()->filename);
krk_attachNamedValue (&AS_INSTANCE(vm.currentException)->fields, "lineno", INTEGER_VAL(token->line));
krk_attachNamedValue (&AS_INSTANCE(vm.currentException)->fields, "colno", INTEGER_VAL(token->col));
krk_attachNamedValue (&AS_INSTANCE(vm.currentException)->fields, "width", INTEGER_VAL(token->literalWidth));
if (current->function->name) {
krk_attachNamedObject(&AS_INSTANCE(vm.currentException)->fields, "func", (KrkObj*)current->function->name);
} else {
KrkValue name = NONE_VAL();
krk_tableGet(&vm.module->fields, vm.specialMethodNames[METHOD_NAME], &name);
krk_attachNamedValue(&AS_INSTANCE(vm.currentException)->fields, "func", name);
}
parser.panicMode = 1;
parser.hadError = 1;
}
static void error(const char * message) {
errorAt(&parser.previous, message);
}
static void errorAtCurrent(const char * message) {
errorAt(&parser.current, message);
}
#define error(...) do { if (parser.panicMode) break; krk_runtimeError(vm.exceptions.syntaxError, __VA_ARGS__); finishError(&parser.previous); } while (0)
#define errorAtCurrent(...) do { if (parser.panicMode) break; krk_runtimeError(vm.exceptions.syntaxError, __VA_ARGS__); finishError(&parser.current); } while (0)
static void advance() {
parser.previous = parser.current;
@ -2243,7 +2229,7 @@ static void declareVariable() {
Local * local = &current->locals[i];
if (local->depth != -1 && local->depth < (ssize_t)current->scopeDepth) break;
if (identifiersEqual(name, &local->name)) {
error("Duplicate definition");
error("Duplicate definition for local '%.*s' in this scope.", (int)name->literalWidth, name->start);
}
}
addLocal(*name);

141
vm.c
View File

@ -49,7 +49,9 @@ KrkVM vm;
static KrkValue run();
static KrkValue krk_isinstance(int argc, KrkValue argv[]);
static void addObjects();
/* We use these directly sometimes */
static KrkValue _string_get(int argc, KrkValue argv[]);
static KrkValue _string_format(int argc, KrkValue argv[], int hasKw);
/* Embedded script for extensions to builtin-ins; see builtins.c/builtins.krk */
extern const char krk_builtinsSrc[];
@ -123,46 +125,56 @@ static void dumpStack(CallFrame * frame) {
#endif
/**
* Display a traceback by working through call frames.
* Called when no exception handler was available and
* an exception was thrown. If there the exception value
* is not None, it will also be printed using safe methods.
* Display a traceback by scanning up the stack / call frames.
* The format of the output here is modeled after the output
* given by CPython, so we display the outermost call first
* and then move inwards; on each call frame we try to open
* the source file and print the corresponding line.
*/
void krk_dumpTraceback() {
if (vm.frameCount) {
fprintf(stderr, "Traceback, most recent last:\n");
fprintf(stderr, "Traceback (most recent call last):\n");
for (size_t i = 0; i <= vm.frameCount - 1; i++) {
CallFrame * frame = &vm.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 : "?"),
(int)krk_lineNumber(&function->chunk, instruction),
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(vm.currentException,NONE_VAL())) {
if (IS_STRING(vm.currentException)) {
/* Make sure strings are printed without quotes */
fprintf(stderr, "%s", AS_CSTRING(vm.currentException));
} else if (AS_BOOLEAN(krk_isinstance(2, (KrkValue[]){vm.currentException, OBJECT_VAL(vm.exceptions.baseException)}))) {
/* ErrorClass: arg... */
fprintf(stderr, "%s: ", AS_INSTANCE(vm.currentException)->_class->name->chars);
KrkValue exceptionArg;
krk_tableGet(&AS_INSTANCE(vm.currentException)->fields, OBJECT_VAL(S("arg")), &exceptionArg);
if (IS_STRING(exceptionArg)) {
/* Make sure strings are printed without quotes */
fprintf(stderr, "%s", AS_CSTRING(exceptionArg));
} else {
krk_printValueSafe(stderr, exceptionArg);
}
} else {
/* Whatever, just print it. */
krk_printValueSafe(stderr, vm.currentException);
}
fprintf(stderr, "\n");
krk_push(vm.currentException);
KrkValue result = krk_callSimple(OBJECT_VAL(AS_CLASS(krk_typeOf(1,&vm.currentException))->_reprer), 1, 0);
fprintf(stderr, "%s\n", AS_CSTRING(result));
}
}
@ -182,11 +194,9 @@ void krk_runtimeError(KrkClass * type, const char * fmt, ...) {
/* Try to allocate an instance of __builtins__. */
KrkInstance * exceptionObject = krk_newInstance(type);
krk_push(OBJECT_VAL(exceptionObject));
KrkString * strArg = S("arg");
krk_push(OBJECT_VAL(strArg));
KrkString * strVal = krk_copyString(buf, len);
krk_push(OBJECT_VAL(strVal));
krk_tableSet(&exceptionObject->fields, OBJECT_VAL(strArg), OBJECT_VAL(strVal));
krk_push(OBJECT_VAL(S("arg")));
krk_push(OBJECT_VAL(krk_copyString(buf, len)));
krk_tableSet(&exceptionObject->fields, krk_peek(1), krk_peek(0));
krk_pop();
krk_pop();
krk_pop();
@ -1411,12 +1421,66 @@ static KrkValue krk_initException(int argc, KrkValue argv[]) {
if (argc > 0) {
krk_attachNamedValue(&self->fields, "arg", argv[1]);
} else {
krk_attachNamedValue(&self->fields, "arg", OBJECT_VAL(S("")));
krk_attachNamedValue(&self->fields, "arg", NONE_VAL());
}
return argv[0];
}
static KrkValue _exception_repr(int argc, KrkValue argv[]) {
KrkInstance * self = AS_INSTANCE(argv[0]);
/* .arg */
KrkValue arg;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("arg")), &arg) || IS_NONE(arg)) {
return OBJECT_VAL(self->_class->name);
} else {
krk_push(OBJECT_VAL(self->_class->name));
krk_push(OBJECT_VAL(S(": ")));
addObjects();
krk_push(arg);
addObjects();
return krk_pop();
}
}
static KrkValue _syntaxerror_repr(int argc, KrkValue argv[]) {
KrkInstance * self = AS_INSTANCE(argv[0]);
/* .arg */
KrkValue file, line, lineno, colno, width, arg, func;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("file")), &file) || !IS_STRING(file)) goto _badSyntaxError;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("line")), &line) || !IS_STRING(line)) goto _badSyntaxError;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("lineno")), &lineno) || !IS_INTEGER(lineno)) goto _badSyntaxError;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("colno")), &colno) || !IS_INTEGER(colno)) goto _badSyntaxError;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("width")), &width) || !IS_INTEGER(width)) goto _badSyntaxError;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("arg")), &arg) || !IS_STRING(arg)) goto _badSyntaxError;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("func")), &func)) goto _badSyntaxError;
krk_push(OBJECT_VAL(S(" File \"{}\", line {}{}\n {}\n {}^\n{}: {}")));
char * tmp = malloc(AS_INTEGER(colno));
memset(tmp,' ',AS_INTEGER(colno));
tmp[AS_INTEGER(colno)-1] = '\0';
krk_push(OBJECT_VAL(krk_takeString(tmp,AS_INTEGER(colno)-1)));
krk_push(OBJECT_VAL(self->_class->name));
if (IS_STRING(func)) {
krk_push(OBJECT_VAL(S(" in ")));
krk_push(func);
addObjects();
} else {
krk_push(OBJECT_VAL(S("")));
}
KrkValue formattedString = _string_format(8,
(KrkValue[]){krk_peek(3), file, lineno, krk_peek(0), line, krk_peek(2), krk_peek(1), arg}, 0);
krk_pop(); /* instr */
krk_pop(); /* class */
krk_pop(); /* spaces */
krk_pop(); /* format string */
return formattedString;
_badSyntaxError:
return OBJECT_VAL(S("SyntaxError: invalid syntax"));
}
static KrkValue _string_init(int argc, KrkValue argv[]) {
/* Ignore argument which would have been an instance */
if (argc < 2) {
@ -1491,6 +1555,7 @@ static KrkValue _string_add(int argc, KrkValue argv[]) {
obj->base = baseClass; \
krk_tableAddAll(&baseClass->methods, &obj->methods); \
krk_tableAddAll(&baseClass->fields, &obj->fields); \
krk_finalizeClass(obj); \
} while (0)
#define BUILTIN_FUNCTION(name, func) do { \
@ -3193,6 +3258,8 @@ void krk_initVM(int flags) {
ADD_EXCEPTION_CLASS(vm.exceptions.baseException, "Exception", vm.objectClass);
/* base exception class gets an init that takes an optional string */
krk_defineNative(&vm.exceptions.baseException->methods, ".__init__", krk_initException);
krk_defineNative(&vm.exceptions.baseException->methods, ".__repr__", _exception_repr);
krk_finalizeClass(vm.exceptions.baseException);
ADD_EXCEPTION_CLASS(vm.exceptions.typeError, "TypeError", vm.exceptions.baseException);
ADD_EXCEPTION_CLASS(vm.exceptions.argumentError, "ArgumentError", vm.exceptions.baseException);
ADD_EXCEPTION_CLASS(vm.exceptions.indexError, "IndexError", vm.exceptions.baseException);
@ -3205,6 +3272,9 @@ void krk_initVM(int flags) {
ADD_EXCEPTION_CLASS(vm.exceptions.keyboardInterrupt, "KeyboardInterrupt", vm.exceptions.baseException);
ADD_EXCEPTION_CLASS(vm.exceptions.zeroDivisionError, "ZeroDivisionError", vm.exceptions.baseException);
ADD_EXCEPTION_CLASS(vm.exceptions.notImplementedError, "NotImplementedError", vm.exceptions.baseException);
ADD_EXCEPTION_CLASS(vm.exceptions.syntaxError, "SyntaxError", vm.exceptions.baseException);
krk_defineNative(&vm.exceptions.syntaxError->methods, ".__repr__", _syntaxerror_repr);
krk_finalizeClass(vm.exceptions.syntaxError);
/* Build classes for basic types */
ADD_BASE_CLASS(vm.baseClasses.typeClass, "type", vm.objectClass);
@ -3630,8 +3700,10 @@ int krk_loadModule(KrkString * name, KrkValue * moduleOut, KrkString * runAs) {
*moduleOut = krk_runfile(fileName,1,runAs->chars,fileName);
vm.exitOnFrame = previousExitFrame;
if (!IS_OBJECT(*moduleOut)) {
if (!(vm.flags & KRK_HAS_EXCEPTION)) {
krk_runtimeError(vm.exceptions.importError,
"Failed to load module '%s' from '%s'", name->chars, fileName);
}
return 0;
}
@ -4322,7 +4394,10 @@ KrkValue krk_interpret(const char * src, int newScope, char * fromName, char * f
if (newScope) krk_startModule(fromName);
KrkFunction * function = krk_compile(src, 0, fromFile);
if (!function) return NONE_VAL();
if (!function) {
if (!vm.frameCount) handleException();
return NONE_VAL();
}
krk_attachNamedObject(&vm.module->fields, "__file__", (KrkObj*)function->chunk.filename);

1
vm.h
View File

@ -87,6 +87,7 @@ typedef struct {
KrkClass * keyboardInterrupt;
KrkClass * zeroDivisionError;
KrkClass * notImplementedError;
KrkClass * syntaxError;
} exceptions;
struct {