umx_compiler

UMX virtual machine "Monkey" interpreter / bytecode compiler
git clone git://bsandro.tech/umx_compiler
Log | Files | Refs | README | LICENSE

eval_test.go (8944B)


      1 package eval
      2 
      3 import (
      4 	"umx_compiler/lexer"
      5 	"umx_compiler/object"
      6 	"umx_compiler/parser"
      7 	"testing"
      8 )
      9 
     10 func TestEvalIntegerExpression(t *testing.T) {
     11 	tests := []struct {
     12 		input    string
     13 		expected int64
     14 	}{
     15 		{"6", 6},
     16 		{"-8", -8},
     17 		{"10", 10},
     18 		{"-256", -256},
     19 		{"1+2*3", 7},
     20 		{"10/5", 2},
     21 		{"3*3*3+10", 37},
     22 		{"(5+10*2+15/3)*2+-10", 50},
     23 	}
     24 	for _, tt := range tests {
     25 		evaluated := testEval(tt.input)
     26 		testIntegerObject(t, evaluated, tt.expected)
     27 	}
     28 }
     29 
     30 func testEval(input string) object.Object {
     31 	l := lexer.New(input)
     32 	p := parser.New(l)
     33 	prg := p.ParseProgram()
     34 	ctx := object.NewContext()
     35 	return Eval(prg, ctx)
     36 }
     37 
     38 func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
     39 	result, ok := obj.(*object.Integer)
     40 	if !ok {
     41 		t.Errorf("obj is not integer (%T %+v)", obj, obj)
     42 		return false
     43 	}
     44 	if result.Value != expected {
     45 		t.Errorf("obj has wrong value (%d instead of %d)", result.Value, expected)
     46 		return false
     47 	}
     48 	return true
     49 }
     50 
     51 func TestEvalBooleanExpression(t *testing.T) {
     52 	tests := []struct {
     53 		input    string
     54 		expected bool
     55 	}{
     56 		{"true", true},
     57 		{"false", false},
     58 		{"1<2", true},
     59 		{"2>3", false},
     60 		{"1==1", true},
     61 		{"10!=10", false},
     62 		{"10==20", false},
     63 		{"5!=6", true},
     64 		{"(1<2)==true", true},
     65 		{"(1<2)==false", false},
     66 	}
     67 	for _, tt := range tests {
     68 		evaluated := testEval(tt.input)
     69 		testBooleanObject(t, evaluated, tt.expected)
     70 	}
     71 }
     72 
     73 func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
     74 	result, ok := obj.(*object.Boolean)
     75 	if !ok {
     76 		t.Errorf("obj is not boolean (%T %+v)", obj, obj)
     77 		return false
     78 	}
     79 	if result.Value != expected {
     80 		t.Errorf("obj has wrong value (%t instead of %t)", result.Value, expected)
     81 		return false
     82 	}
     83 	return true
     84 }
     85 
     86 func TestShriekOperator(t *testing.T) {
     87 	tests := []struct {
     88 		input    string
     89 		expected bool
     90 	}{
     91 		{"!true", false},
     92 		{"!false", true},
     93 		{"!5", false},
     94 		{"!!true", true},
     95 		{"!!!false", true},
     96 		{"!!8", true},
     97 	}
     98 	for _, tt := range tests {
     99 		evaluated := testEval(tt.input)
    100 		testBooleanObject(t, evaluated, tt.expected)
    101 	}
    102 }
    103 
    104 func TestIfElseExpressions(t *testing.T) {
    105 	tests := []struct {
    106 		input    string
    107 		expected interface{}
    108 	}{
    109 		{"if (true) { 10 }", 10},
    110 		{"if (false) { 20 }", nil},
    111 		{"if (1) { 30 }", 30},
    112 		{"if (1<2) { 40 }", 40},
    113 		{"if (1>2) { 11 }", nil},
    114 		{"if (1>2) { 50 } else { 60 }", 60},
    115 		{"if (1<2) { 50 } else { 60 }", 50},
    116 	}
    117 	for _, tt := range tests {
    118 		evaluated := testEval(tt.input)
    119 		integer, ok := tt.expected.(int)
    120 		if ok {
    121 			testIntegerObject(t, evaluated, int64(integer))
    122 		} else {
    123 			testNullObject(t, evaluated)
    124 		}
    125 	}
    126 }
    127 
    128 func testNullObject(t *testing.T, obj object.Object) bool {
    129 	if obj != NULL {
    130 		t.Errorf("obj is not NULL but %T (%+v)", obj, obj)
    131 		return false
    132 	}
    133 	return true
    134 }
    135 
    136 func TestReturnStatement(t *testing.T) {
    137 	tests := []struct {
    138 		input    string
    139 		expected int64
    140 	}{
    141 		{"return 10;", 10},
    142 		{"return 20; 9;", 20},
    143 		{"9; return 2*5; 20;", 10},
    144 		{`
    145 if (1<2) {
    146   return 10;
    147 }
    148 return 20;`, 10},
    149 	}
    150 	for _, tt := range tests {
    151 		evaluated := testEval(tt.input)
    152 		testIntegerObject(t, evaluated, tt.expected)
    153 	}
    154 }
    155 
    156 func TestErrorHandling(t *testing.T) {
    157 	tests := []struct {
    158 		input    string
    159 		expected string
    160 	}{
    161 		{"5+true", "type mismatch: INTEGER + BOOLEAN"},
    162 		{"5+true;5;", "type mismatch: INTEGER + BOOLEAN"},
    163 		{"-true", "unknown operator: -BOOLEAN"},
    164 		{"true+false;", "unknown operator: BOOLEAN + BOOLEAN"},
    165 		{"if (10>1) { true + false; }", "unknown operator: BOOLEAN + BOOLEAN"},
    166 		{"foo", "identifier not found: foo"},
    167 		{`"foo"-"bar"`, "unknown operator: STRING - STRING"},
    168 		{`{"foo": "bar"}[fn(x){x}]`, "unusable as hash key: FUNCTION"},
    169 	}
    170 	for _, tt := range tests {
    171 		evaluated := testEval(tt.input)
    172 		errObj, ok := evaluated.(*object.Error)
    173 		if !ok {
    174 			t.Errorf("no error object returned")
    175 			continue
    176 		}
    177 		if errObj.Message != tt.expected {
    178 			t.Errorf("wrong error message: %q instead of %q", errObj.Message, tt.expected)
    179 		}
    180 	}
    181 }
    182 
    183 func TestLetStatements(t *testing.T) {
    184 	tests := []struct {
    185 		input    string
    186 		expected int64
    187 	}{
    188 		{"let a = 5; a;", 5},
    189 		{"let a = 8*8; a;", 64},
    190 		{"let a = 8; let b=a; b;", 8},
    191 		{"let a = 5; let b=a; let c = a+b+5; c", 15},
    192 	}
    193 	for _, tt := range tests {
    194 		testIntegerObject(t, testEval(tt.input), tt.expected)
    195 	}
    196 }
    197 
    198 func TestFunctionObject(t *testing.T) {
    199 	input := "fn(x) { x + 2; };"
    200 	evaluated := testEval(input)
    201 	fn, ok := evaluated.(*object.Function)
    202 	if !ok {
    203 		t.Fatalf("object is not a function")
    204 	}
    205 	if len(fn.Parameters) != 1 {
    206 		t.Fatalf("wrong number of parameters in function")
    207 	}
    208 	if fn.Parameters[0].String() != "x" {
    209 		t.Fatalf("wrong function parameter name")
    210 	}
    211 	expectedBody := "(x + 2)"
    212 	if fn.Body.String() != expectedBody {
    213 		t.Fatalf("function body is not '%s' but '%s'", expectedBody, fn.Body.String())
    214 	}
    215 }
    216 
    217 func TestFunctionApplication(t *testing.T) {
    218 	tests := []struct {
    219 		input    string
    220 		expected int64
    221 	}{
    222 		{"let ident = fn(x) { x; }; ident(8);", 8},
    223 		{"let ident = fn(x) { return x; }; ident(8);", 8},
    224 		{"let double = fn(y) { y*2; }; double(8);", 16},
    225 		{"let sum = fn(a,b) { a+b;}; sum(10,20);", 30},
    226 		{"let sum = fn(a,b) { a+b;}; sum(10+20,sum(30,40));", 100},
    227 		{"fn(k){ k; }(32)", 32},
    228 	}
    229 	for _, tt := range tests {
    230 		testIntegerObject(t, testEval(tt.input), tt.expected)
    231 	}
    232 }
    233 
    234 func TestStringLiteral(t *testing.T) {
    235 	input := `"ehlo world"`
    236 	evaluated := testEval(input)
    237 	str, ok := evaluated.(*object.String)
    238 	if !ok {
    239 		t.Fatalf("object is not a string")
    240 	}
    241 	if str.Value != "ehlo world" {
    242 		t.Errorf("String has wrong value: %s", str.Value)
    243 	}
    244 }
    245 
    246 func TestStringsConcat(t *testing.T) {
    247 	input := `"hello" + " " + "world"`
    248 	evaluated := testEval(input)
    249 	str, ok := evaluated.(*object.String)
    250 	if !ok {
    251 		t.Fatalf("object is not a string")
    252 	}
    253 	if str.Value != "hello world" {
    254 		t.Fatalf("string value is wrong: %s", str.Value)
    255 	}
    256 }
    257 
    258 func TestBuiltinFunctions(t *testing.T) {
    259 	tests := []struct {
    260 		input    string
    261 		expected interface{}
    262 	}{
    263 		{`len("")`, 0},
    264 		{`len("four")`, 4},
    265 		{`len(1)`, "unsupported `len` argument, got INTEGER"},
    266 		{`len("one", "two")`, "wrong number of arguments (2 instead of 1)"},
    267 		{`len([1,2,3,4,5])`, 5},
    268 	}
    269 
    270 	for _, tt := range tests {
    271 		evaluated := testEval(tt.input)
    272 		switch expected := tt.expected.(type) {
    273 		case int:
    274 			testIntegerObject(t, evaluated, int64(expected))
    275 		case string:
    276 			errObj, ok := evaluated.(*object.Error)
    277 			if !ok {
    278 				t.Errorf("object is not Error")
    279 				continue
    280 			}
    281 			if errObj.Message != expected {
    282 				t.Errorf("wrong error message")
    283 			}
    284 		}
    285 	}
    286 }
    287 
    288 func TestArrayLiterals(t *testing.T) {
    289 	input := "[1,2*3,4*5]"
    290 	evaluated := testEval(input)
    291 	result, ok := evaluated.(*object.Array)
    292 	if !ok {
    293 		t.Fatalf("object is not Array, got %T (%+v)", evaluated, evaluated)
    294 	}
    295 	if len(result.Elements) != 3 {
    296 		t.Fatalf("array elements count is wrong")
    297 	}
    298 	testIntegerObject(t, result.Elements[0], 1)
    299 	testIntegerObject(t, result.Elements[1], 6)
    300 	testIntegerObject(t, result.Elements[2], 20)
    301 }
    302 
    303 func TestArrayIndexExpression(t *testing.T) {
    304 	tests := []struct {
    305 		input    string
    306 		expected interface{}
    307 	}{
    308 		{"[1,2,3][0]", 1},
    309 		{"[10,20,30][1]", 20},
    310 		{"[1,2,3][2]", 3},
    311 		{"let i=0;[1][i];", 1},
    312 		{"[1,2,3][1+1]", 3},
    313 		{"let myarray=[10,20,30]; myarray[1];", 20},
    314 		{"let myarray=[10,20,30]; myarray[0]+myarray[1]+myarray[2];", 60},
    315 		{"[10,20,30][5]", nil},
    316 		{"[10,20,30][-1]", nil},
    317 	}
    318 	for _, tt := range tests {
    319 		evaluated := testEval(tt.input)
    320 		integer, ok := tt.expected.(int)
    321 		if ok {
    322 			testIntegerObject(t, evaluated, int64(integer))
    323 		} else {
    324 			testNullObject(t, evaluated)
    325 		}
    326 	}
    327 }
    328 
    329 func TestHashLiterals(t *testing.T) {
    330 	input := `let two = "two";
    331 {
    332   "one": 10-9,
    333   two: 1+1,
    334   "thr" + "ee": 6/2,
    335   4:4,
    336   true: 5,
    337   false: 6
    338 }`
    339 
    340 	evaluated := testEval(input)
    341 	result, ok := evaluated.(*object.Hash)
    342 	if !ok {
    343 		t.Fatalf("no hash returned")
    344 	}
    345 	expected := map[object.HashKey]int64{
    346 		(&object.String{Value: "one"}).HashKey():   1,
    347 		(&object.String{Value: "two"}).HashKey():   2,
    348 		(&object.String{Value: "three"}).HashKey(): 3,
    349 		(&object.Integer{Value: 4}).HashKey():      4,
    350 		TRUE.HashKey():                             5,
    351 		FALSE.HashKey():                            6,
    352 	}
    353 	if len(result.Pairs) != len(expected) {
    354 		t.Fatalf("invalid number of pairs in the hash")
    355 	}
    356 	for k, v := range expected {
    357 		pair, ok := result.Pairs[k]
    358 		if !ok {
    359 			t.Fatalf("no pair for a given key")
    360 		}
    361 		testIntegerObject(t, pair.Value, v)
    362 	}
    363 }
    364 
    365 func TestHashEvalExpressions(t *testing.T) {
    366 	tests := []struct {
    367 		input    string
    368 		expected interface{}
    369 	}{
    370 		{`{"foo":5}["foo"]`, 5},
    371 		{`{"foo":5}["bar"]`, nil},
    372 		{`let k="foo";{"foo":5}[k]`, 5},
    373 		{`{}["foo"]`, nil},
    374 		{`{4:8}[4]`, 8},
    375 		{`{true: 10}[true]`, 10},
    376 		{`{false: 16}[false]`, 16},
    377 	}
    378 	for _, tt := range tests {
    379 		evaluated := testEval(tt.input)
    380 		integer, ok := tt.expected.(int)
    381 		if ok {
    382 			testIntegerObject(t, evaluated, int64(integer))
    383 		} else {
    384 			testNullObject(t, evaluated)
    385 		}
    386 	}
    387 }