From 22e2a90f4d1e19b8fc5e26b23d4ea9a83a00af6e Mon Sep 17 00:00:00 2001 From: arnav-ag Date: Fri, 19 Apr 2024 16:29:52 +0800 Subject: [PATCH] Make error messages nicer --- src/server/runOogaLang.ts | 4 +- src/server/server.ts | 2 +- src/tests/test.ts | 87 ++++++++++++++++++++++++------------ src/vm/oogavm-compiler.ts | 4 +- src/vm/oogavm-machine.ts | 4 +- src/vm/oogavm-typechecker.ts | 55 +++++++++++++++++------ 6 files changed, 107 insertions(+), 49 deletions(-) diff --git a/src/server/runOogaLang.ts b/src/server/runOogaLang.ts index 33ca6af..27e588c 100644 --- a/src/server/runOogaLang.ts +++ b/src/server/runOogaLang.ts @@ -16,8 +16,8 @@ const standardSource = readFileSync('std/ooga-std.ooga', 'utf8'); export function runOogaLangCode( code: string ): Promise<{ capturedOutput: string; heaps: any[]; stacks: any[] }> { - // debug.disable(); - // debug.enable('ooga:runOogaLang'); + debug.disable(); + debug.enable('ooga:runOogaLang'); log(code); resetHeapsAndStacks(); return new Promise((resolve, reject) => { diff --git a/src/server/server.ts b/src/server/server.ts index 830eceb..cf8534f 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -29,7 +29,7 @@ app.post( const { capturedOutput, heaps, stacks } = await runOogaLangCode(code); // This will run the code and catch errors internally res.json({ success: true, output: capturedOutput, heaps, stacks }); } catch (error) { - res.json({ success: false, error: error.message }); + res.json({ success: false, error: error }); } } ); diff --git a/src/tests/test.ts b/src/tests/test.ts index 468566d..61eb610 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -1499,11 +1499,7 @@ func foo(x int) int { foo(5); `, - 'type error in return statement; expected return type: {\n' + - ' "name": "Integer"\n' + - '}, actual return type: {\n' + - ' "name": "Boolean"\n' + - '}', + 'Type error in return statement; expected return type: int, actual return type: bool', '', defaultNumWords ); @@ -1520,11 +1516,7 @@ func foo(x int) int { foo(5); `, - 'type error in function declaration; declared return type: {\n' + - ' "name": "Integer"\n' + - '}, actual return type: {\n' + - ' "name": "Null"\n' + - '}', + 'Type error in function declaration; declared return type: int, actual return type: nil', '', defaultNumWords ); @@ -1924,7 +1916,8 @@ var j int = i(2) ); // Testing slice assignment -testProgram(` +testProgram( + ` arr := make([]int, 5, 10); for i := 0; i < len(arr); i++ { @@ -1933,10 +1926,14 @@ for i := 0; i < len(arr); i++ { print(arr[i]); } arr[4]; -`, 4, '0\n0\n0\n1\n0\n2\n0\n3\n0\n4\n', defaultNumWords); - +`, + 4, + '0\n0\n0\n1\n0\n2\n0\n3\n0\n4\n', + defaultNumWords +); -testProgram(` +testProgram( + ` arr := make([]int, 5, 10); l := len(arr); for i := 0; i < l; i++ { @@ -1949,19 +1946,29 @@ for i := 0; i < len(arr); i++ { print(arr[i]); } 0; -`, 0, '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n', defaultNumWords); +`, + 0, + '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n', + defaultNumWords +); // Test for division by zero -testProgram(` +testProgram( + ` func foo() int { return 0; } 5 / foo(); -`, 'Division by 0 error!', '', defaultNumWords); +`, + 'Division by 0 error!', + '', + defaultNumWords +); // Test that select blocks if no cases match -testProgram(` +testProgram( + ` x := make(chan int); // unbuffered select { @@ -1969,9 +1976,14 @@ case <-x: print("This won't happen"); } 10; -`, 'Stuck forever!', '', defaultNumWords); +`, + 'Stuck forever!', + '', + defaultNumWords +); -testProgram(` +testProgram( + ` x := make(chan int); // unbuffered go func() { @@ -1984,9 +1996,14 @@ go func() { for i := 0; i < 100; i++ { } 10; -`, 10, '', defaultNumWords); +`, + 10, + '', + defaultNumWords +); -testProgram(` +testProgram( + ` x := make(chan int); // unbuffered go func() { @@ -2001,11 +2018,15 @@ go func() { for i := 0; i < 100; i++ { } 10; -`, 10, '"Print once"', defaultNumWords); - +`, + 10, + '"Print once"', + defaultNumWords +); // Test binary sync.Semaphore -testProgram(` +testProgram( + ` s := sync.NewSemaphore(1); x := 0; @@ -2029,11 +2050,15 @@ for i := 0; i < 100; i++ { } 10; -`, 10, '"x in Thread 1 is 1"\n"x in Thread 2 is 2"', defaultNumWords); - +`, + 10, + '"x in Thread 1 is 1"\n"x in Thread 2 is 2"', + defaultNumWords +); // Test sync.Semaphore with more than 1 count -testProgram(` +testProgram( + ` s := sync.NewSemaphore(2); x := 0; @@ -2064,4 +2089,8 @@ for i := 0; i < 100; i++ { } 10; -`, 10, '"x in Thread 1 is 1"\n"x in Thread 2 is 2"\n"x in Thread 3 is 4"', defaultNumWords); +`, + 10, + '"x in Thread 1 is 1"\n"x in Thread 2 is 2"\n"x in Thread 3 is 4"', + defaultNumWords +); diff --git a/src/vm/oogavm-compiler.ts b/src/vm/oogavm-compiler.ts index 0b5aa3b..271db29 100644 --- a/src/vm/oogavm-compiler.ts +++ b/src/vm/oogavm-compiler.ts @@ -400,12 +400,12 @@ const compileComp = { // jump to the next select case if possible jof.addr = wc; } else if (compCase.tag === 'SelectDefaultCase') { - console.log("Default case"); + console.log('Default case'); hasDefault = true; compile(compCase.body, ce); instrs[wc++] = { tag: Opcodes.END_ATOMIC }; jumps.push(wc); - instrs[wc++] = { tag: Opcodes.GOTO, addr: 0}; + instrs[wc++] = { tag: Opcodes.GOTO, addr: 0 }; } else { throw new CompilerError('Unsupported select case in SelectStatement'); } diff --git a/src/vm/oogavm-machine.ts b/src/vm/oogavm-machine.ts index 779c7ef..ea5aef6 100644 --- a/src/vm/oogavm-machine.ts +++ b/src/vm/oogavm-machine.ts @@ -310,7 +310,7 @@ export const builtinMappings = { getTime: () => { // Get unix time in millis return TSValueToAddress(Date.now()); - } + }, }; class Builtin { @@ -355,7 +355,7 @@ function apply_binop(sym: string, left: any, right: any) { return left * right; case '/': if (right === 0) { - throw new RuntimeError("Division by 0 error!"); + throw new RuntimeError('Division by 0 error!'); } return left / right; default: diff --git a/src/vm/oogavm-typechecker.ts b/src/vm/oogavm-typechecker.ts index 64c7aad..4c05c50 100644 --- a/src/vm/oogavm-typechecker.ts +++ b/src/vm/oogavm-typechecker.ts @@ -43,10 +43,6 @@ function is_string(x) { return typeof x === 'string'; } -const unparse_types = t => { - return JSON.stringify(t, null, 2); -}; - const unary_arith_type = [ new FunctionType([new IntegerType()], new IntegerType()), new FunctionType([new FloatType()], new FloatType()), @@ -129,7 +125,7 @@ let global_struct_environment = pair({}, empty_type_environment); const lookup_type = (x, e): Type => { if (e === null) { - throw new TypecheckError('unbound name: ' + x); + throw new TypecheckError('Variable not declared in scope: ' + x); } if (head(e).hasOwnProperty(x)) { return head(e)[x]; @@ -248,6 +244,38 @@ function getType(t, struct_te): Type { throw new TypecheckError('Unknown type: ' + t.type); } + +function unparse_types(t) { + log('UnparseTypes: ', t); + // These are the go types + if (is_type(t, IntegerType)) { + return 'int'; + } else if (is_type(t, FloatType)) { + return 'float64'; + } else if (is_type(t, BooleanType)) { + return 'bool'; + } else if (is_type(t, StringType)) { + return 'string'; + } else if (is_type(t, NullType)) { + return 'nil'; + } else if (is_type(t, AnyType)) { + return 'any'; + } else if (is_type(t, FunctionType)) { + return 'func(' + t.args.map(a => unparse_types(a)).join(', ') + ') ' + unparse_types(t.ret); + } else if (is_type(t, StructType)) { + return t.name; + } else if (is_type(t, ArrayType)) { + return '[]' + unparse_types(t.elem_type); + } else if (is_type(t, ChanType)) { + return 'chan ' + unparse_types(t.elem_type); + } else if (is_type(t, ReturnType)) { + return 'return ' + unparse_types(t.type); + } else if (is_type(t, MethodType)) { + return 'method ' + t.name + '(' + t.args.map(a => unparse_types(a)).join(', ') + ')'; + } else { + throw new TypecheckError('Unknown type: ' + t); + } +} /** * Represents a collection of type checking functions for different AST node types. * Each property of this object corresponds to a specific AST node type, and its value is a type checking function for that node type. @@ -257,7 +285,7 @@ function getType(t, struct_te): Type { const type_comp = { Integer: (comp, te, struct_te) => { if (!is_integer(comp.value)) { - throw new TypecheckError('expected integer, got ' + comp.value); + throw new TypecheckError('Expected int value, got ' + comp.value); } comp.type = new IntegerType(); @@ -265,7 +293,7 @@ const type_comp = { }, Float: (comp, te, struct_te) => { if (!is_float(comp.value)) { - throw new TypecheckError('expected float, got ' + comp.value); + throw new TypecheckError('Expected float value, got ' + comp.value); } comp.type = new FloatType(); @@ -273,7 +301,7 @@ const type_comp = { }, Boolean: (comp, te, struct_te) => { if (!is_boolean(comp.value)) { - throw new TypecheckError('expected boolean, got ' + comp.value); + throw new TypecheckError('Expected boolean value, got ' + comp.value); } comp.type = new BooleanType(); @@ -281,7 +309,7 @@ const type_comp = { }, String: (comp, te, struct_te) => { if (!is_string(comp.value)) { - throw new TypecheckError('expected string, got ' + comp.value); + throw new TypecheckError('Expected string value, got ' + comp.value); } comp.type = new StringType(); @@ -289,7 +317,7 @@ const type_comp = { }, Null: (comp, te, struct_te) => { if (!is_null(comp.value)) { - throw new TypecheckError('expected null, got ' + comp.value); + throw new TypecheckError('Expected null value, got ' + comp.value); } comp.type = new NullType(); @@ -459,7 +487,7 @@ const type_comp = { const t0 = type(comp.test, te, struct_te); // log('IfStatement: t0', t0); if (!is_type(t0, BooleanType)) - throw new TypecheckError('expected predicate type: Boolean, got ' + t0); + throw new TypecheckError('Expected predicate type: Boolean, got ' + unparse_types(t0)); const t1 = type(comp.consequent, te, struct_te); // log('IfStatement: t1', t1); const t2 = type(comp.alternate, te, struct_te); @@ -503,6 +531,7 @@ const type_comp = { log('SwitchCase'); log(unparse(comp)); const t0 = comp.test ? type(comp.test, te, struct_te) : new NullType(); + // We don't have to check the type of t0, it can be any type - this can be checked in the runtime const t1 = type(comp.consequent, te, struct_te); log('Exiting SwitchCase, returning', t1); @@ -537,7 +566,7 @@ const type_comp = { if (!equal_type(ret_type.type, expected_ret)) { throw new TypecheckError( - 'type error in function declaration; declared return type: ' + + 'Type error in function declaration; declared return type: ' + unparse_types(expected_ret) + ', actual return type: ' + unparse_types(ret_type.type) @@ -824,7 +853,7 @@ const type_comp = { if (in_func) { if (!equal_type(ret_type, expected_ret)) { throw new TypecheckError( - 'type error in return statement; ' + + 'Type error in return statement; ' + 'expected return type: ' + unparse_types(expected_ret) + ', ' +