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 }