| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- package vm
- import (
- "fmt"
- "github/runnignwater/monkey/ast"
- "github/runnignwater/monkey/compiler"
- "github/runnignwater/monkey/lexer"
- "github/runnignwater/monkey/object"
- "github/runnignwater/monkey/parser"
- "testing"
- )
- type vmTestCase struct {
- input string
- expected interface{}
- }
- func TestIntegerArithmetic(t *testing.T) {
- tests := []vmTestCase{
- {"1", 1},
- {"2", 2},
- {"1 + 2", 3},
- {"1 - 2", -1},
- {"1 * 2", 2},
- {"4 / 2", 2},
- {"50 / 2 * 2 + 10 - 5", 55},
- {"5 * (2 + 10)", 60},
- {"5 + 5 + 5 + 5 - 10", 10},
- {"2 * 2 * 2 * 2 * 2", 32},
- {"5 * 2 + 10", 20},
- {"5 + 2 * 10", 25},
- {"5 * (2 + 10)", 60},
- {"-5", -5},
- {"-10", -10},
- {"-50 + 100 + -50", 0},
- {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
- {"if ((if(false) {10})){10} else {20}", 20},
- }
- runVmTests(t, tests)
- }
- func TestBooleanExpression(t *testing.T) {
- tests := []vmTestCase{
- {"true", true},
- {"false", false},
- {"1 < 2", true},
- {"1 > 2", false},
- {"1 < 1", false},
- {"1 > 1", false},
- {"1 == 1", true},
- {"1 != 1", false},
- {"true == true", true},
- {"false == false", true},
- {"true == false", false},
- {"false != true", true},
- {"(1 < 2) == true", true},
- {"(1 < 2) == false", false},
- {"!true", false},
- {"!false", true},
- {"!5", false},
- {"!!true", true},
- {"!!false", false},
- {"!!5", true},
- {"if (true) { 10 };", 10},
- {"if (true) { 10 } else { 20 };", 10},
- {"if (false) { 10 } else { 20 };", 20},
- {"if (1) {10}", 10},
- {"if (1 < 2) {10}", 10},
- {"if (1 < 2) {10} else {20}", 10},
- {"if (1 > 2) {10} else {20}", 20},
- {"if (false) {10;}", Null},
- {"if (1 > 2) {10;}", Null},
- {"!(if(false) {5;})", true},
- }
- runVmTests(t, tests)
- }
- func TestGlobalLetStatement(t *testing.T) {
- tests := []vmTestCase{
- {"let one = 1; one", 1},
- {"let one = 1; let two = 2; one + two", 3},
- {"let one = 1; let two = one + one; one + two", 3},
- }
- runVmTests(t, tests)
- }
- func TestStringExpression(t *testing.T) {
- tests := []vmTestCase{
- {`"monkey"`, "monkey"},
- {`"mon" + "key"`, "monkey"},
- {`"mon" + "key" + "banana"`, "monkeybanana"},
- }
- runVmTests(t, tests)
- }
- func TestArrayLiterals(t *testing.T) {
- tests := []vmTestCase{
- {"[]", []int{}},
- {"[1, 2, 3]", []int{1, 2, 3}},
- {"[1+2,3*4,5+6]", []int{3, 12, 11}},
- }
- runVmTests(t, tests)
- }
- func TestHashLiterals(t *testing.T) {
- test := []vmTestCase{
- {"{}", map[object.HashKey]int64{}},
- {"{1:2, 2:3}", map[object.HashKey]int64{
- (&object.Integer{Value: 1}).HashKey(): 2,
- (&object.Integer{Value: 2}).HashKey(): 3,
- }},
- {"{1+1:2*2, 3+3:4*4}", map[object.HashKey]int64{
- (&object.Integer{Value: 2}).HashKey(): 4,
- (&object.Integer{Value: 6}).HashKey(): 16,
- }},
- }
- runVmTests(t, test)
- }
- func TestIndexExpressions(t *testing.T) {
- tests := []vmTestCase{
- {"[1,2,3][1]", 2},
- {"[1,2,3][0+2]", 3},
- {"[[1,1,1]][0][0]", 1},
- {"[][0]", Null},
- {"[1,2,3][99]", Null},
- {"[1][-1]", Null},
- {"{1:1, 2:2}[1]", 1},
- {"{1:1, 2:2}[2]", 2},
- {"{1:1}[0]", Null},
- {"{}[0]", Null},
- }
- runVmTests(t, tests)
- }
- func TestCallingFunctionsWithoutArguments(t *testing.T) {
- tests := []vmTestCase{
- {
- input: `
- let fivePlusTen = fn () { 5 + 10; };
- fivePlusTen();
- `,
- expected: 15,
- },
- {
- input: `
- let earlyExit = fn () {return 99; 100;};
- earlyExit();
- `,
- expected: 99,
- },
- }
- runVmTests(t, tests)
- }
- func TestFunctionWithoutReturnValue(t *testing.T) {
- tests := []vmTestCase{
- {
- input: `let noReturn = fn() {};noReturn()`,
- expected: Null,
- },
- {
- input: `let noReturn = fn() {};let noReturnTwo=fn(){noReturn();};noReturn(); noReturnTwo();`,
- expected: Null,
- },
- }
- runVmTests(t, tests)
- }
- func TestFirstClassFunctions(t *testing.T) {
- tests := []vmTestCase{
- {
- input: `
- let returnsOne = fn() { 1; };
- let returnsOneReturner = fn() { returnsOne; };
- returnsOneReturner()();
- `,
- expected: 1,
- },
- }
- runVmTests(t, tests)
- }
- 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;};
- 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; };
- oneAndTwo() + threeAndFour();
- `,
- expected: 10,
- },
- {
- input: `
- let firstFoobar = fn() { let foobar = 50; foobar; };
- let secondFoobar = fn() { let foobar = 100; foobar; };
- firstFoobar() + secondFoobar();
- `,
- expected: 150,
- },
- {
- input: `
- let globalSeed = 50;
- let minusOne = fn () { let num = 1; globalSeed - num; };
- let minusTwo = fn () { let num = 2; globalSeed - num; };
- minusOne() + minusTwo();
- `,
- expected: 97,
- },
- }
- runVmTests(t, tests)
- }
- func runVmTests(t *testing.T, tests []vmTestCase) {
- t.Helper()
- for _, tt := range tests {
- program := parse(tt.input)
- comp := compiler.New()
- err := comp.Compile(program)
- if err != nil {
- t.Fatalf("compler error: %s", err)
- }
- vm := New(comp.ByteCode())
- err = vm.Run()
- if err != nil {
- t.Fatalf("vm error: %s", err)
- }
- stackElem := vm.LastPopStackElem()
- testExpectedObject(t, tt.expected, stackElem)
- }
- }
- func parse(input string) *ast.Program {
- l := lexer.New(input)
- p := parser.New(l)
- return p.ParseProgram()
- }
- func testExpectedObject(
- t *testing.T,
- expected interface{},
- actual object.Object,
- ) {
- t.Helper()
- switch expected := expected.(type) {
- case int:
- err := testIntegerObject(int64(expected), actual)
- if err != nil {
- t.Errorf("testIntegerObject failed: %s", err)
- }
- case bool:
- err := testBooleanObject(expected, actual)
- if err != nil {
- t.Errorf("testBooleanObject failed: %s", err)
- }
- case string:
- err := testStringObject(expected, actual)
- if err != nil {
- t.Errorf("testStringObject failed: %s", err)
- }
- case []int:
- array, ok := actual.(*object.Array)
- if !ok {
- t.Errorf("object not Array: %T (%+v)", actual, actual)
- return
- }
- if len(array.Elements) != len(expected) {
- t.Errorf("wrong num of elements. want=%d, got=%d", len(expected), len(array.Elements))
- return
- }
- for i, expectedElem := range expected {
- err := testIntegerObject(int64(expectedElem), array.Elements[i])
- if err != nil {
- t.Errorf("testIntegerObject failed: %s", err)
- }
- }
- case map[object.HashKey]int64:
- hash, ok := actual.(*object.Hash)
- if !ok {
- t.Errorf("object is not Hash. got=%T (%+v)", actual, actual)
- return
- }
- if len(hash.Pairs) != len(expected) {
- t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", len(expected), len(hash.Pairs))
- return
- }
- for expectedKey, expectedValue := range expected {
- pair, ok := hash.Pairs[expectedKey]
- if !ok {
- t.Errorf("no pair for given key in Pairs")
- }
- err := testIntegerObject(expectedValue, pair.Value)
- if err != nil {
- t.Errorf("testIntegerObject failed: %s", err)
- }
- }
- }
- }
- func testStringObject(expected string, actual object.Object) error {
- result, ok := actual.(*object.String)
- if !ok {
- return fmt.Errorf("object is not String. got=%T (%+v)", actual, actual)
- }
- if result.Value != expected {
- return fmt.Errorf("object has wrong value. got=%q, want=%q", result.Value, expected)
- }
- return nil
- }
- func testIntegerObject(expected int64, actual object.Object) error {
- result, ok := actual.(*object.Integer)
- if !ok {
- return fmt.Errorf("object is not Integer. got=%T (%+v)", actual, actual)
- }
- if result.Value != expected {
- return fmt.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected)
- }
- return nil
- }
- func testBooleanObject(expected bool, actual object.Object) error {
- result, ok := actual.(*object.Boolean)
- if !ok {
- return fmt.Errorf("object is not Boolean. got=%T (%+v)", actual, actual)
- }
- if result.Value != expected {
- return fmt.Errorf("object has wrong value. got=%t, want=%t", result.Value, expected)
- }
- return nil
- }
|