| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- 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 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
- }
|