From 60d27f09d764df59c3e9e49a32e825699c8a4c0b Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Tue, 19 Mar 2024 08:15:11 -0400 Subject: [PATCH] assert and test changes --- compiler/compiler.go | 22 ++++--- compiler/compiler_test.go | 125 +++++++++++++++++--------------------- examples/demo.monkey | 2 +- examples/fact.monkey | 2 +- object/builtins.go | 42 ++++++++++--- vm/vm.go | 6 +- vm/vm_test.go | 107 ++++++++++++++++---------------- 7 files changed, 164 insertions(+), 142 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 7709fd6..e15f3fd 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -91,9 +91,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - if !c.lastInstructionIs(code.OpNoop) { - c.emit(code.OpPop) - } + c.emit(code.OpPop) case *ast.InfixExpression: if node.Operator == "<" || node.Operator == "<=" { @@ -182,9 +180,6 @@ func (c *Compiler) Compile(node ast.Node) error { } case *ast.IfExpression: - if c.lastInstructionIs(code.OpPop) { - c.removeLastPop() - } c.l++ err := c.Compile(node.Condition) c.l-- @@ -240,6 +235,14 @@ func (c *Compiler) Compile(node ast.Node) error { } c.l-- + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } else { + if !c.lastInstructionIs(code.OpReturn) { + c.emit(code.OpNull) + } + } + case *ast.AssignmentStatement: symbol, ok := c.symbolTable.Resolve(node.Name.Value) if !ok { @@ -366,8 +369,13 @@ func (c *Compiler) Compile(node ast.Node) error { if c.lastInstructionIs(code.OpPop) { c.replaceLastPopWithReturn() } + + // If the function doesn't end with a return statement add one with a + // `return null;` and also handle the edge-case of empty functions. if !c.lastInstructionIs(code.OpReturn) { - c.emit(code.OpNull) + if !c.lastInstructionIs(code.OpNull) { + c.emit(code.OpNull) + } c.emit(code.OpReturn) } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 7869afb..1251e47 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -231,15 +231,15 @@ func TestConditionals(t *testing.T) { code.Make(code.OpJumpNotTruthy, 10), // 0004 code.Make(code.OpConstant, 0), - // 0007 + // 0008 code.Make(code.OpJump, 13), - // 0010 + // 0011 code.Make(code.OpConstant, 1), // 0013 code.Make(code.OpPop), // 0014 code.Make(code.OpConstant, 2), - // 0017 + // 0018 code.Make(code.OpPop), }, }, @@ -256,28 +256,34 @@ func TestConditionals(t *testing.T) { // 0006 code.Make(code.OpTrue), // 0007 - code.Make(code.OpJumpNotTruthy, 19), + code.Make(code.OpJumpNotTruthy, 20), // 0010 code.Make(code.OpConstant, 1), // 0013 code.Make(code.OpAssignGlobal, 0), // 0016 - code.Make(code.OpJump, 20), - // 0019 code.Make(code.OpNull), + // 0017 + code.Make(code.OpJump, 21), // 0020 - code.Make(code.OpFalse), - // 0021 - code.Make(code.OpJumpNotTruthy, 33), - // 0024 - code.Make(code.OpConstant, 2), - // 0027 - code.Make(code.OpAssignGlobal, 0), - // 0030 - code.Make(code.OpJump, 34), - // 0033 code.Make(code.OpNull), - // 0034 + // 0021 + code.Make(code.OpPop), + // 0022 + code.Make(code.OpFalse), + // 0023 + code.Make(code.OpJumpNotTruthy, 36), + // 0025 + code.Make(code.OpConstant, 2), + // 0029 + code.Make(code.OpAssignGlobal, 0), + // 0032 + code.Make(code.OpNull), + // 0033 + code.Make(code.OpJump, 37), + // 0036 + code.Make(code.OpNull), + // 0037 code.Make(code.OpPop), }, }, @@ -487,7 +493,7 @@ func TestIndexExpressions(t *testing.T) { func TestFunctions(t *testing.T) { tests := []compilerTestCase{ { - input: "fn() { return 5 + 10 }", + input: `fn() { return 5 + 10 }`, expectedConstants: []interface{}{ 5, 10, @@ -504,24 +510,7 @@ func TestFunctions(t *testing.T) { }, }, { - input: `fn() { 5 + 10 }`, - expectedConstants: []interface{}{ - 5, - 10, - []code.Instructions{ - code.Make(code.OpConstant, 0), - code.Make(code.OpConstant, 1), - code.Make(code.OpAdd), - code.Make(code.OpReturn), - }, - }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 2, 0), - code.Make(code.OpPop), - }, - }, - { - input: `fn() { 1; 2 }`, + input: `fn() { 1; return 2 }`, expectedConstants: []interface{}{ 1, 2, @@ -566,8 +555,8 @@ func TestClosures(t *testing.T) { tests := []compilerTestCase{ { input: `fn(a) { - fn(b) { - a + b + return fn(b) { + return a + b } } `, @@ -592,9 +581,9 @@ func TestClosures(t *testing.T) { { input: ` fn(a) { - fn(b) { - fn(c) { - a + b + c + return fn(b) { + return fn(c) { + return a + b + c } } }; @@ -632,13 +621,13 @@ func TestClosures(t *testing.T) { fn() { let a = 66; - fn() { + return fn() { let b = 77; - fn() { + return fn() { let c = 88; - global + a + b + c; + return global + a + b + c; } } } @@ -737,7 +726,7 @@ func TestCompilerScopes(t *testing.T) { func TestFunctionCalls(t *testing.T) { tests := []compilerTestCase{ { - input: `fn() { 24 }();`, + input: `fn() { return 24 }();`, expectedConstants: []interface{}{ 24, []code.Instructions{ @@ -753,7 +742,7 @@ func TestFunctionCalls(t *testing.T) { }, { input: ` - let noArg = fn() { 24 }; + let noArg = fn() { return 24 }; noArg(); `, expectedConstants: []interface{}{ @@ -773,7 +762,7 @@ func TestFunctionCalls(t *testing.T) { }, { input: ` - let oneArg = fn(a) { a }; + let oneArg = fn(a) { return a }; oneArg(24); `, expectedConstants: []interface{}{ @@ -794,7 +783,7 @@ func TestFunctionCalls(t *testing.T) { }, { input: ` - let manyArg = fn(a, b, c) { a; b; c }; + let manyArg = fn(a, b, c) { a; b; return c }; manyArg(24, 25, 26); `, expectedConstants: []interface{}{ @@ -881,7 +870,7 @@ func TestLetStatementScopes(t *testing.T) { { input: ` let num = 55; - fn() { num } + fn() { return num } `, expectedConstants: []interface{}{ 55, @@ -901,7 +890,7 @@ func TestLetStatementScopes(t *testing.T) { input: ` fn() { let num = 55; - num + return num } `, expectedConstants: []interface{}{ @@ -923,7 +912,7 @@ func TestLetStatementScopes(t *testing.T) { fn() { let a = 55; let b = 77; - a + b + return a + b } `, expectedConstants: []interface{}{ @@ -977,11 +966,11 @@ func TestBuiltins(t *testing.T) { `, expectedConstants: []interface{}{1}, expectedInstructions: []code.Instructions{ - code.Make(code.OpGetBuiltin, 4), + code.Make(code.OpGetBuiltin, 5), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpPop), - code.Make(code.OpGetBuiltin, 7), + code.Make(code.OpGetBuiltin, 8), code.Make(code.OpArray, 0), code.Make(code.OpConstant, 0), code.Make(code.OpCall, 2), @@ -989,10 +978,10 @@ func TestBuiltins(t *testing.T) { }, }, { - input: `fn() { len([]) }`, + input: `fn() { return len([]) }`, expectedConstants: []interface{}{ []code.Instructions{ - code.Make(code.OpGetBuiltin, 4), + code.Make(code.OpGetBuiltin, 5), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpReturn), @@ -1012,9 +1001,9 @@ func TestRecursiveFunctions(t *testing.T) { tests := []compilerTestCase{ { input: ` - let countDown = fn(x) { countDown(x - 1); }; - countDown(1); - `, + let countDown = fn(x) { return countDown(x - 1); }; + countDown(1); + `, expectedConstants: []interface{}{ 1, []code.Instructions{ @@ -1038,12 +1027,12 @@ func TestRecursiveFunctions(t *testing.T) { }, { input: ` - let wrapper = fn() { - let countDown = fn(x) { countDown(x - 1); }; - countDown(1); - }; - wrapper(); - `, + let wrapper = fn() { + let countDown = fn(x) { return countDown(x - 1); }; + return countDown(1); + }; + wrapper(); + `, expectedConstants: []interface{}{ 1, []code.Instructions{ @@ -1088,15 +1077,15 @@ func TestIteration(t *testing.T) { // 0000 code.Make(code.OpTrue), // 0001 - code.Make(code.OpJumpNotTruthy, 11), + code.Make(code.OpJumpNotTruthy, 10), // 0004 code.Make(code.OpConstant, 0), // 0007 - code.Make(code.OpPop), - // 0008 code.Make(code.OpJump, 0), - // 0011 + // 0010 code.Make(code.OpNoop), + // 0011 + code.Make(code.OpPop), }, }, } diff --git a/examples/demo.monkey b/examples/demo.monkey index 8ebe304..4a06e8d 100644 --- a/examples/demo.monkey +++ b/examples/demo.monkey @@ -40,4 +40,4 @@ let map = fn(arr, f) { }; let numbers = [1, 1 + 1, 4 - 1, 2 * 2, 2 + 3, 12 / 2]; -//map(numbers, fibonacci); \ No newline at end of file +map(numbers, fibonacci); \ No newline at end of file diff --git a/examples/fact.monkey b/examples/fact.monkey index ba5153c..498a135 100644 --- a/examples/fact.monkey +++ b/examples/fact.monkey @@ -5,4 +5,4 @@ let fact = fn(n) { return n * fact(n - 1) } -print(fact(5) \ No newline at end of file +assert(fact(5) == 120, "fact(5) != 120") \ No newline at end of file diff --git a/object/builtins.go b/object/builtins.go index 9d09751..b88e36f 100644 --- a/object/builtins.go +++ b/object/builtins.go @@ -11,15 +11,16 @@ import ( // Builtins ... var Builtins = map[string]*Builtin{ - "len": {Name: "len", Fn: Len}, - "input": {Name: "input", Fn: Input}, - "print": {Name: "print", Fn: Print}, - "first": {Name: "first", Fn: First}, - "last": {Name: "last", Fn: Last}, - "rest": {Name: "rest", Fn: Rest}, - "push": {Name: "push", Fn: Push}, - "pop": {Name: "pop", Fn: Pop}, - "exit": {Name: "exit", Fn: Exit}, + "len": {Name: "len", Fn: Len}, + "input": {Name: "input", Fn: Input}, + "print": {Name: "print", Fn: Print}, + "first": {Name: "first", Fn: First}, + "last": {Name: "last", Fn: Last}, + "rest": {Name: "rest", Fn: Rest}, + "push": {Name: "push", Fn: Push}, + "pop": {Name: "pop", Fn: Pop}, + "exit": {Name: "exit", Fn: Exit}, + "assert": {Name: "assert", Fn: Assert}, } // BuiltinsIndex ... @@ -213,3 +214,26 @@ func Exit(args ...Object) Object { } return nil } + +// Assert ... +func Assert(args ...Object) Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != BOOLEAN_OBJ { + return newError("argument #1 to `assert` must be BOOLEAN, got %s", + args[0].Type()) + } + if args[1].Type() != STRING_OBJ { + return newError("argument #2 to `assert` must be STRING, got %s", + args[0].Type()) + } + + if !args[0].(*Boolean).Value { + fmt.Printf("Assertion Error: %s", args[1].(*String).Value) + os.Exit(1) + } + + return nil +} diff --git a/vm/vm.go b/vm/vm.go index d82d627..8141e3b 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -119,11 +119,7 @@ func (vm *VM) Run() error { } case code.OpPop: - // This makes things like this work: - // >> let x = 1; if (x == 1) { x = 2 } - if vm.sp > 0 { - vm.pop() - } + vm.pop() case code.OpTrue: err := vm.push(True) diff --git a/vm/vm_test.go b/vm/vm_test.go index 778d2cd..65b75b5 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -259,7 +259,12 @@ func TestConditionals(t *testing.T) { {"if (1 > 2) { 10 } else { 20 }", 20}, {"if (1 > 2) { 10 }", Null}, {"if (false) { 10 }", Null}, - //{"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + {"if (true) { let a = 5; }", Null}, + {"if (true) { 10; let a = 5; }", Null}, + {"if (false) { 10 } else { let b = 5; }", Null}, + {"if (false) { 10 } else { 10; let b = 5; }", Null}, + {"if (true) { let a = 5; } else { 10 }", Null}, {"let x = 0; if (true) { x = 1; }; if (false) { x = 2; }; x", 1}, } @@ -346,24 +351,24 @@ func TestCallingFunctionsWithoutArguments(t *testing.T) { tests := []vmTestCase{ { input: ` - let fivePlusTen = fn() { 5 + 10; }; + let fivePlusTen = fn() { return 5 + 10; }; fivePlusTen(); `, expected: 15, }, { input: ` - let one = fn() { 1; }; - let two = fn() { 2; }; + let one = fn() { return 1; }; + let two = fn() { return 2; }; one() + two() `, expected: 3, }, { input: ` - let a = fn() { 1 }; - let b = fn() { a() + 1 }; - let c = fn() { b() + 1 }; + let a = fn() { return 1 }; + let b = fn() { return a() + 1 }; + let c = fn() { return b() + 1 }; c(); `, expected: 3, @@ -421,8 +426,8 @@ func TestFirstClassFunctions(t *testing.T) { tests := []vmTestCase{ { input: ` - let returnsOne = fn() { 1; }; - let returnsOneReturner = fn() { returnsOne; }; + let returnsOne = fn() { return 1; }; + let returnsOneReturner = fn() { return returnsOne; }; returnsOneReturner()(); `, expected: 1, @@ -430,7 +435,7 @@ func TestFirstClassFunctions(t *testing.T) { { input: ` let returnsOneReturner = fn() { - let returnsOne = fn() { 1; }; + let returnsOne = fn() { return 1; }; returnsOne; }; returnsOneReturner()(); @@ -444,32 +449,32 @@ func TestFirstClassFunctions(t *testing.T) { func TestCallingFunctionsWithBindings(t *testing.T) { tests := []vmTestCase{ - //{ - // input: ` - //let one = fn() { let one = 1; one }; - //one(); - //`, - // expected: 1, - //}, { input: ` - let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + let one = fn() { let one = 1; return one }; + one(); + `, + expected: 1, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; return one + two; }; oneAndTwo(); `, expected: 3, }, { input: ` - let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; - let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; + let oneAndTwo = fn() { let one = 1; let two = 2; return one + two; }; + let threeAndFour = fn() { let three = 3; let four = 4; return three + four; }; oneAndTwo() + threeAndFour(); `, expected: 10, }, { input: ` - let firstFoobar = fn() { let foobar = 50; foobar; }; - let secondFoobar = fn() { let foobar = 100; foobar; }; + let firstFoobar = fn() { let foobar = 50; return foobar; }; + let secondFoobar = fn() { let foobar = 100; return foobar; }; firstFoobar() + secondFoobar(); `, expected: 150, @@ -479,11 +484,11 @@ func TestCallingFunctionsWithBindings(t *testing.T) { let globalSeed = 50; let minusOne = fn() { let num = 1; - globalSeed - num; + return globalSeed - num; } let minusTwo = fn() { let num = 2; - globalSeed - num; + return globalSeed - num; } minusOne() + minusTwo(); `, @@ -498,14 +503,14 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { tests := []vmTestCase{ { input: ` - let identity = fn(a) { a; }; + let identity = fn(a) { return a; }; identity(4); `, expected: 4, }, { input: ` - let sum = fn(a, b) { a + b; }; + let sum = fn(a, b) { return a + b; }; sum(1, 2); `, expected: 3, @@ -514,7 +519,7 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { input: ` let sum = fn(a, b) { let c = a + b; - c; + return c; }; sum(1, 2); `, @@ -524,7 +529,7 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { input: ` let sum = fn(a, b) { let c = a + b; - c; + return c; }; sum(1, 2) + sum(3, 4);`, expected: 10, @@ -533,10 +538,10 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { input: ` let sum = fn(a, b) { let c = a + b; - c; + return c; }; let outer = fn() { - sum(1, 2) + sum(3, 4); + return sum(1, 2) + sum(3, 4); }; outer(); `, @@ -548,11 +553,11 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { let sum = fn(a, b) { let c = a + b; - c + globalNum; + return c + globalNum; }; let outer = fn() { - sum(1, 2) + sum(3, 4) + globalNum; + return sum(1, 2) + sum(3, 4) + globalNum; }; outer() + globalNum; @@ -567,15 +572,15 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { func TestCallingFunctionsWithWrongArguments(t *testing.T) { tests := []vmTestCase{ { - input: `fn() { 1; }(1);`, + input: `fn() { return 1; }(1);`, expected: `wrong number of arguments: want=0, got=1`, }, { - input: `fn(a) { a; }();`, + input: `fn(a) { return a; }();`, expected: `wrong number of arguments: want=1, got=0`, }, { - input: `fn(a, b) { a + b; }(1);`, + input: `fn(a, b) { return a + b; }(1);`, expected: `wrong number of arguments: want=2, got=1`, }, } @@ -658,7 +663,7 @@ func TestClosures(t *testing.T) { { input: ` let newClosure = fn(a) { - fn() { a; }; + fn() { return a; }; }; let closure = newClosure(99); closure(); @@ -668,7 +673,7 @@ func TestClosures(t *testing.T) { { input: ` let newAdder = fn(a, b) { - fn(c) { a + b + c }; + fn(c) { return a + b + c }; }; let adder = newAdder(1, 2); adder(8); @@ -679,7 +684,7 @@ func TestClosures(t *testing.T) { input: ` let newAdder = fn(a, b) { let c = a + b; - fn(d) { c + d }; + fn(d) { return c + d }; }; let adder = newAdder(1, 2); adder(8); @@ -690,9 +695,9 @@ func TestClosures(t *testing.T) { input: ` let newAdderOuter = fn(a, b) { let c = a + b; - fn(d) { + return fn(d) { let e = d + c; - fn(f) { e + f; }; + return fn(f) { return e + f; }; }; }; let newAdderInner = newAdderOuter(1, 2) @@ -705,8 +710,8 @@ func TestClosures(t *testing.T) { input: ` let a = 1; let newAdderOuter = fn(b) { - fn(c) { - fn(d) { a + b + c + d }; + return fn(c) { + return fn(d) { return a + b + c + d }; }; }; let newAdderInner = newAdderOuter(2) @@ -718,9 +723,9 @@ func TestClosures(t *testing.T) { { input: ` let newClosure = fn(a, b) { - let one = fn() { a; }; - let two = fn() { b; }; - fn() { one() + two(); }; + let one = fn() { return a; }; + let two = fn() { return b; }; + return fn() { return one() + two(); }; }; let closure = newClosure(9, 90); closure(); @@ -740,7 +745,7 @@ func TestRecursiveFunctions(t *testing.T) { if (x == 0) { return 0; } else { - countDown(x - 1); + return countDown(x - 1); } }; countDown(1); @@ -753,11 +758,11 @@ func TestRecursiveFunctions(t *testing.T) { if (x == 0) { return 0; } else { - countDown(x - 1); + return countDown(x - 1); } }; let wrapper = fn() { - countDown(1); + return countDown(1); }; wrapper(); `, @@ -770,10 +775,10 @@ func TestRecursiveFunctions(t *testing.T) { if (x == 0) { return 0; } else { - countDown(x - 1); + return countDown(x - 1); } }; - countDown(1); + return countDown(1); }; wrapper(); `, @@ -795,7 +800,7 @@ func TestRecursiveFibonacci(t *testing.T) { if (x == 1) { return 1; } else { - fibonacci(x - 1) + fibonacci(x - 2); + return fibonacci(x - 1) + fibonacci(x - 2); } } };