Skip to content

Commit

Permalink
Make error messages nicer
Browse files Browse the repository at this point in the history
  • Loading branch information
arnav-ag committed Apr 19, 2024
1 parent adfffbc commit 22e2a90
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 49 deletions.
4 changes: 2 additions & 2 deletions src/server/runOogaLang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
}
);
Expand Down
87 changes: 58 additions & 29 deletions src/tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand All @@ -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
);
Expand Down Expand Up @@ -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++ {
Expand All @@ -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++ {
Expand All @@ -1949,29 +1946,44 @@ 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 {
case <-x:
print("This won't happen");
}
10;
`, 'Stuck forever!', '', defaultNumWords);
`,
'Stuck forever!',
'',
defaultNumWords
);

testProgram(`
testProgram(
`
x := make(chan int); // unbuffered
go func() {
Expand All @@ -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() {
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
);
4 changes: 2 additions & 2 deletions src/vm/oogavm-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
4 changes: 2 additions & 2 deletions src/vm/oogavm-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ export const builtinMappings = {
getTime: () => {
// Get unix time in millis
return TSValueToAddress(Date.now());
}
},
};

class Builtin {
Expand Down Expand Up @@ -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:
Expand Down
55 changes: 42 additions & 13 deletions src/vm/oogavm-typechecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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.
Expand All @@ -257,39 +285,39 @@ 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();
return new IntegerType();
},
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();
return new FloatType();
},
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();
return new BooleanType();
},
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();
return new StringType();
},
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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) +
', ' +
Expand Down

0 comments on commit 22e2a90

Please sign in to comment.