diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 771aa2f..a725f0c 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -879,11 +879,11 @@ func TestBuiltins(t *testing.T) { `, expectedConstants: []interface{}{1}, expectedInstructions: []code.Instructions{ - code.Make(code.OpGetBuiltin, 7), + code.Make(code.OpGetBuiltin, 10), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpPop), - code.Make(code.OpGetBuiltin, 10), + code.Make(code.OpGetBuiltin, 14), code.Make(code.OpArray, 0), code.Make(code.OpConstant, 0), code.Make(code.OpCall, 2), @@ -894,7 +894,7 @@ func TestBuiltins(t *testing.T) { input: `fn() { return len([]) }`, expectedConstants: []interface{}{ []code.Instructions{ - code.Make(code.OpGetBuiltin, 7), + code.Make(code.OpGetBuiltin, 10), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpReturn), diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index a350f19..63a1e60 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -306,8 +306,29 @@ func evalIntegerPrefixOperatorExpression(operator string, right object.Object) o func evalInfixExpression(operator string, left, right object.Object) object.Object { switch { + // {"a": 1} + {"b": 2} + case operator == "+" && left.Type() == object.HASH_OBJ && right.Type() == object.HASH_OBJ: + leftVal := left.(*object.Hash).Pairs + rightVal := right.(*object.Hash).Pairs + pairs := make(map[object.HashKey]object.HashPair) + for k, v := range leftVal { + pairs[k] = v + } + for k, v := range rightVal { + pairs[k] = v + } + return &object.Hash{Pairs: pairs} + + // [1] + [2] + case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := right.(*object.Array).Elements + elements := make([]object.Object, len(leftVal)+len(rightVal)) + elements = append(leftVal, rightVal...) + return &object.Array{Elements: elements} + // [1] * 3 - case left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: + case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: leftVal := left.(*object.Array).Elements rightVal := int(right.(*object.Integer).Value) elements := leftVal @@ -316,7 +337,7 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje } return &object.Array{Elements: elements} // 3 * [1] - case left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: leftVal := int(left.(*object.Integer).Value) rightVal := right.(*object.Array).Elements elements := rightVal @@ -326,12 +347,12 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje return &object.Array{Elements: elements} // " " * 4 - case left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: + case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: leftVal := left.(*object.String).Value rightVal := right.(*object.Integer).Value return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} // 4 * " " - case left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: leftVal := left.(*object.Integer).Value rightVal := right.(*object.String).Value return &object.String{Value: strings.Repeat(rightVal, int(leftVal))} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 0cccf77..51347fa 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -182,27 +182,27 @@ func TestErrorHandling(t *testing.T) { }{ { "5 + true;", - "type mismatch: INTEGER + BOOLEAN", + "type mismatch: int + bool", }, { "5 + true; 5;", - "type mismatch: INTEGER + BOOLEAN", + "type mismatch: int + bool", }, { "-true", - "unknown operator: -BOOLEAN", + "unknown operator: -bool", }, { "true + false;", - "unknown operator: BOOLEAN + BOOLEAN", + "unknown operator: bool + bool", }, { "5; true + false; 5", - "unknown operator: BOOLEAN + BOOLEAN", + "unknown operator: bool + bool", }, { "if (10 > 1) { true + false; }", - "unknown operator: BOOLEAN + BOOLEAN", + "unknown operator: bool + bool", }, { ` @@ -214,7 +214,7 @@ func TestErrorHandling(t *testing.T) { return 1; } `, - "unknown operator: BOOLEAN + BOOLEAN", + "unknown operator: bool + bool", }, { "foobar", @@ -222,11 +222,11 @@ func TestErrorHandling(t *testing.T) { }, { `"Hello" - "World"`, - "unknown operator: STRING - STRING", + "unknown operator: str - str", }, { `{"name": "Monkey"}[fn(x) { x }];`, - "unusable as hash key: FUNCTION", + "unusable as hash key: fn", }, } @@ -395,21 +395,21 @@ func TestBuiltinFunctions(t *testing.T) { {`len("")`, 0}, {`len("four")`, 4}, {`len("hello world")`, 11}, - {`len(1)`, errors.New("argument to `len` not supported, got INTEGER")}, + {`len(1)`, errors.New("argument to `len` not supported, got int")}, {`len("one", "two")`, errors.New("wrong number of arguments. got=2, want=1")}, {`len("∑")`, 1}, {`len([1, 2, 3])`, 3}, {`len([])`, 0}, {`first([1, 2, 3])`, 1}, {`first([])`, nil}, - {`first(1)`, errors.New("argument to `first` must be ARRAY, got INTEGER")}, + {`first(1)`, errors.New("argument to `first` must be array, got int")}, {`last([1, 2, 3])`, 3}, {`last([])`, nil}, - {`last(1)`, errors.New("argument to `last` must be ARRAY, got INTEGER")}, + {`last(1)`, errors.New("argument to `last` must be array, got int")}, {`rest([1, 2, 3])`, []int{2, 3}}, {`rest([])`, nil}, {`push([], 1)`, []int{1}}, - {`push(1, 1)`, errors.New("argument to `push` must be ARRAY, got INTEGER")}, + {`push(1, 1)`, errors.New("argument to `push` must be array, got int")}, {`print("Hello World")`, nil}, {`input()`, ""}, {`pop([])`, errors.New("cannot pop from an empty array")}, @@ -500,6 +500,24 @@ func TestArrayDuplication(t *testing.T) { testIntegerObject(t, result.Elements[2], 1) } +func TestArrayMerging(t *testing.T) { + input := "[1] + [2]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 2 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 2) +} + func TestArrayIndexExpressions(t *testing.T) { tests := []struct { input string @@ -599,6 +617,33 @@ func TestHashLiterals(t *testing.T) { } } +func TestHashMerging(t *testing.T) { + input := `{"a": 1} + {"b": 2}` + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "a"}).HashKey(): 1, + (&object.String{Value: "b"}).HashKey(): 2, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + func TestHashSelectorExpressions(t *testing.T) { tests := []struct { input string diff --git a/foo.monkey b/foo.monkey new file mode 100644 index 0000000..234a305 --- /dev/null +++ b/foo.monkey @@ -0,0 +1,3 @@ +#!./monkey-lang + +print(args()) \ No newline at end of file diff --git a/main.go b/main.go index b6ae08b..0b60b1d 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,11 @@ func main() { args := flag.Args() + copy(object.Arguments, args) + object.StandardInput = os.Stdin + object.StandardOutput = os.Stdout + object.ExitFunction = os.Exit + if compile { if len(args) < 1 { log.Fatal("no source file given to compile") diff --git a/object/builtin_args.go b/object/builtin_args.go new file mode 100644 index 0000000..4e66137 --- /dev/null +++ b/object/builtin_args.go @@ -0,0 +1,10 @@ +package object + +// Args ... +func Args(args ...Object) Object { + elements := make([]Object, len(Arguments)) + for i, arg := range Arguments { + elements[i] = &String{Value: arg} + } + return &Array{Elements: elements} +} diff --git a/object/builtin_exit.go b/object/builtin_exit.go index cdf7112..d9502e3 100644 --- a/object/builtin_exit.go +++ b/object/builtin_exit.go @@ -1,17 +1,17 @@ package object -import "os" - // Exit ... func Exit(args ...Object) Object { + var status int if len(args) == 1 { if args[0].Type() != INTEGER_OBJ { return newError("argument to `exit` must be INTEGER, got %s", args[0].Type()) } - os.Exit(int(args[0].(*Integer).Value)) - } else { - os.Exit(0) + status = int(args[0].(*Integer).Value) } + + ExitFunction(status) + return nil } diff --git a/object/builtin_find.go b/object/builtin_find.go new file mode 100644 index 0000000..422b466 --- /dev/null +++ b/object/builtin_find.go @@ -0,0 +1,34 @@ +package object + +import ( + "strings" +) + +// Find ... +func Find(args ...Object) Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + + if haystack, ok := args[0].(*String); ok { + if needle, ok := args[1].(*String); ok { + index := strings.Index(haystack.Value, needle.Value) + return &Integer{Value: int64(index)} + } else { + return newError("expected arg #2 to be `str` got got=%T", args[1]) + } + } else if haystack, ok := args[0].(*Array); ok { + needle := args[1] + index := -1 + for i, el := range haystack.Elements { + if cmp, ok := el.(Comparable); ok && cmp.Equal(needle) { + index = i + break + } + } + return &Integer{Value: int64(index)} + } else { + return newError("expected arg #1 to be `str` or `array` got got=%T", args[0]) + } +} diff --git a/object/builtin_first.go b/object/builtin_first.go index 3685033..959f250 100644 --- a/object/builtin_first.go +++ b/object/builtin_first.go @@ -7,7 +7,7 @@ func First(args ...Object) Object { len(args)) } if args[0].Type() != ARRAY_OBJ { - return newError("argument to `first` must be ARRAY, got %s", + return newError("argument to `first` must be array, got %s", args[0].Type()) } diff --git a/object/builtin_join.go b/object/builtin_join.go new file mode 100644 index 0000000..0afe533 --- /dev/null +++ b/object/builtin_join.go @@ -0,0 +1,27 @@ +package object + +import ( + "strings" +) + +// Join ... +func Join(args ...Object) Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + if arr, ok := args[0].(*Array); ok { + if sep, ok := args[1].(*String); ok { + a := make([]string, len(arr.Elements)) + for i, el := range arr.Elements { + a[i] = el.String() + } + return &String{Value: strings.Join(a, sep.Value)} + } else { + return newError("expected arg #2 to be `str` got got=%T", args[1]) + } + } else { + return newError("expected arg #1 to be `array` got got=%T", args[0]) + } +} diff --git a/object/builtin_last.go b/object/builtin_last.go index 74c32eb..e929d38 100644 --- a/object/builtin_last.go +++ b/object/builtin_last.go @@ -7,7 +7,7 @@ func Last(args ...Object) Object { len(args)) } if args[0].Type() != ARRAY_OBJ { - return newError("argument to `last` must be ARRAY, got %s", + return newError("argument to `last` must be array, got %s", args[0].Type()) } diff --git a/object/builtin_lower.go b/object/builtin_lower.go new file mode 100644 index 0000000..be41ac2 --- /dev/null +++ b/object/builtin_lower.go @@ -0,0 +1,16 @@ +package object + +import "strings" + +// Lower ... +func Lower(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + if str, ok := args[0].(*String); ok { + return &String{Value: strings.ToLower(str.Value)} + } + return newError("expected `str` argument to `lower` got=%T", args[0]) +} diff --git a/object/builtin_pop.go b/object/builtin_pop.go index ca9674c..3710c24 100644 --- a/object/builtin_pop.go +++ b/object/builtin_pop.go @@ -7,7 +7,7 @@ func Pop(args ...Object) Object { len(args)) } if args[0].Type() != ARRAY_OBJ { - return newError("argument to `pop` must be ARRAY, got %s", + return newError("argument to `pop` must be array, got %s", args[0].Type()) } diff --git a/object/builtin_push.go b/object/builtin_push.go index fd932e5..22691cb 100644 --- a/object/builtin_push.go +++ b/object/builtin_push.go @@ -7,7 +7,7 @@ func Push(args ...Object) Object { len(args)) } if args[0].Type() != ARRAY_OBJ { - return newError("argument to `push` must be ARRAY, got %s", + return newError("argument to `push` must be array, got %s", args[0].Type()) } diff --git a/object/builtin_rest.go b/object/builtin_rest.go index 29a5383..9a8b584 100644 --- a/object/builtin_rest.go +++ b/object/builtin_rest.go @@ -7,7 +7,7 @@ func Rest(args ...Object) Object { len(args)) } if args[0].Type() != ARRAY_OBJ { - return newError("argument to `rest` must be ARRAY, got %s", + return newError("argument to `rest` must be array, got %s", args[0].Type()) } diff --git a/object/builtin_split.go b/object/builtin_split.go new file mode 100644 index 0000000..566eb1a --- /dev/null +++ b/object/builtin_split.go @@ -0,0 +1,36 @@ +package object + +import ( + "strings" +) + +// Split ... +func Split(args ...Object) Object { + if len(args) < 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + if obj, ok := args[0].(*String); ok { + var sep string + + s := obj.Value + + if len(args) == 2 { + if obj, ok := args[1].(*String); ok { + sep = obj.Value + } else { + return newError("expected arg #2 to be `str` got=%T", args[1]) + } + } + + tokens := strings.Split(s, sep) + elements := make([]Object, len(tokens)) + for i, token := range tokens { + elements[i] = &String{Value: token} + } + return &Array{Elements: elements} + } else { + return newError("expected arg #1 to be `str` got got=%T", args[0]) + } +} diff --git a/object/builtin_typeof.go b/object/builtin_typeof.go new file mode 100644 index 0000000..6382e53 --- /dev/null +++ b/object/builtin_typeof.go @@ -0,0 +1,10 @@ +package object + +// TypeOf ... +func TypeOf(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + return &String{Value: string(args[0].Type())} +} diff --git a/object/builtin_upper.go b/object/builtin_upper.go new file mode 100644 index 0000000..b1e9e5f --- /dev/null +++ b/object/builtin_upper.go @@ -0,0 +1,16 @@ +package object + +import "strings" + +// Upper ... +func Upper(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + if str, ok := args[0].(*String); ok { + return &String{Value: strings.ToUpper(str.Value)} + } + return newError("expected `str` argument to `upper` got=%T", args[0]) +} diff --git a/object/builtins.go b/object/builtins.go index 563eb4f..da2d473 100644 --- a/object/builtins.go +++ b/object/builtins.go @@ -20,6 +20,13 @@ var Builtins = map[string]*Builtin{ "bool": {Name: "bool", Fn: Bool}, "int": {Name: "int", Fn: Int}, "str": {Name: "str", Fn: Str}, + "typeof": {Name: "typeof", Fn: TypeOf}, + "args": {Name: "args", Fn: Args}, + "lower": {Name: "lower", Fn: Lower}, + "upper": {Name: "upper", Fn: Upper}, + "join": {Name: "join", Fn: Join}, + "split": {Name: "split", Fn: Split}, + "find": {Name: "find", Fn: Find}, } // BuiltinsIndex ... diff --git a/object/object.go b/object/object.go index d82cda7..53ef9e3 100644 --- a/object/object.go +++ b/object/object.go @@ -12,20 +12,27 @@ import ( type ObjectType string const ( - INTEGER_OBJ = "INTEGER" - BOOLEAN_OBJ = "BOOLEAN" - NULL_OBJ = "NULL" - RETURN_VALUE_OBJ = "RETURN_VALUE" - ERROR_OBJ = "ERROR" - FUNCTION_OBJ = "FUNCTION" - STRING_OBJ = "STRING" - BUILTIN_OBJ = "BUILTIN" - ARRAY_OBJ = "ARRAY" - HASH_OBJ = "HASH" + INTEGER_OBJ = "int" + BOOLEAN_OBJ = "bool" + NULL_OBJ = "null" + RETURN_VALUE_OBJ = "return" + ERROR_OBJ = "error" + FUNCTION_OBJ = "fn" + STRING_OBJ = "str" + BUILTIN_OBJ = "builtin" + ARRAY_OBJ = "array" + HASH_OBJ = "hash" COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION" - CLOSURE_OBJ = "CLOSURE" + CLOSURE_OBJ = "closure" ) +// Comparable is the interface for comparing two Object and their underlying +// values. It is the responsibility of the caller (left) to check for types. +// Returns `true` iif the types and values are identical, `false` otherwise. +type Comparable interface { + Equal(other Object) bool +} + // Immutable is the interface for all immutable objects which must implement // the Clone() method used by binding names to values. type Immutable interface { @@ -60,6 +67,12 @@ func (i *Integer) Clone() Object { func (i *Integer) String() string { return i.Inspect() } +func (i *Integer) Equal(other Object) bool { + if obj, ok := other.(*Integer); ok { + return i.Value == obj.Value + } + return false +} type Boolean struct { Value bool @@ -77,6 +90,12 @@ func (b *Boolean) Clone() Object { func (b *Boolean) String() string { return b.Inspect() } +func (b *Boolean) Equal(other Object) bool { + if obj, ok := other.(*Boolean); ok { + return b.Value == obj.Value + } + return false +} type Null struct{} @@ -89,6 +108,10 @@ func (n *Null) Inspect() string { func (n *Null) String() string { return n.Inspect() } +func (n *Null) Equal(other Object) bool { + _, ok := other.(*Null) + return ok +} type ReturnValue struct { Value Object @@ -167,6 +190,12 @@ func (s *String) Clone() Object { func (s *String) String() string { return s.Value } +func (s *String) Equal(other Object) bool { + if obj, ok := other.(*String); ok { + return s.Value == obj.Value + } + return false +} type BuiltinFunction func(args ...Object) Object @@ -209,6 +238,25 @@ func (ao *Array) Inspect() string { func (ao *Array) String() string { return ao.Inspect() } +func (ao *Array) Equal(other Object) bool { + if obj, ok := other.(*Array); ok { + if len(ao.Elements) != len(obj.Elements) { + return false + } + for i, el := range ao.Elements { + cmp, ok := el.(Comparable) + if !ok { + return false + } + if !cmp.Equal(obj.Elements[i]) { + return false + } + } + + return true + } + return false +} type HashKey struct { Type ObjectType @@ -271,6 +319,31 @@ func (h *Hash) Inspect() string { func (h *Hash) String() string { return h.Inspect() } +func (h *Hash) Equal(other Object) bool { + if obj, ok := other.(*Hash); ok { + if len(h.Pairs) != len(obj.Pairs) { + return false + } + for _, pair := range h.Pairs { + left := pair.Value + hashed := left.(Hashable) + right, ok := obj.Pairs[hashed.HashKey()] + if !ok { + return false + } + cmp, ok := left.(Comparable) + if !ok { + return false + } + if !cmp.Equal(right.Value) { + return false + } + } + + return true + } + return false +} func (cf *CompiledFunction) Type() ObjectType { return COMPILED_FUNCTION_OBJ diff --git a/object/state.go b/object/state.go new file mode 100644 index 0000000..857952e --- /dev/null +++ b/object/state.go @@ -0,0 +1,10 @@ +package object + +import "io" + +var ( + Arguments []string + StandardInput io.Reader + StandardOutput io.Writer + ExitFunction func(int) +) diff --git a/vm/vm.go b/vm/vm.go index 4226e5f..d989ab0 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -527,8 +527,29 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error { switch { + // {"a": 1} + {"b": 2} + case op == code.OpAdd && left.Type() == object.HASH_OBJ && right.Type() == object.HASH_OBJ: + leftVal := left.(*object.Hash).Pairs + rightVal := right.(*object.Hash).Pairs + pairs := make(map[object.HashKey]object.HashPair) + for k, v := range leftVal { + pairs[k] = v + } + for k, v := range rightVal { + pairs[k] = v + } + return vm.push(&object.Hash{Pairs: pairs}) + + // [1] + [2] + case op == code.OpAdd && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := right.(*object.Array).Elements + elements := make([]object.Object, len(leftVal)+len(rightVal)) + elements = append(leftVal, rightVal...) + return vm.push(&object.Array{Elements: elements}) + // [1] * 3 - case left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: + case op == code.OpMul && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: leftVal := left.(*object.Array).Elements rightVal := int(right.(*object.Integer).Value) elements := leftVal @@ -537,7 +558,7 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error { } return vm.push(&object.Array{Elements: elements}) // 3 * [1] - case left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: + case op == code.OpMul && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: leftVal := int(left.(*object.Integer).Value) rightVal := right.(*object.Array).Elements elements := rightVal @@ -547,12 +568,12 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error { return vm.push(&object.Array{Elements: elements}) // " " * 4 - case left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: + case op == code.OpMul && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: leftVal := left.(*object.String).Value rightVal := right.(*object.Integer).Value return vm.push(&object.String{Value: strings.Repeat(leftVal, int(rightVal))}) // 4 * " " - case left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: + case op == code.OpMul && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: leftVal := left.(*object.Integer).Value rightVal := right.(*object.String).Value return vm.push(&object.String{Value: strings.Repeat(rightVal, int(leftVal))}) diff --git a/vm/vm_test.go b/vm/vm_test.go index 82f1388..169ccbc 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -354,6 +354,16 @@ func TestArrayDuplication(t *testing.T) { runVmTests(t, tests) } +func TestArrayMerging(t *testing.T) { + tests := []vmTestCase{ + {"[] + [1]", []int{1}}, + {"[1] + [2]", []int{1, 2}}, + {"[1, 2] + [3, 4]", []int{1, 2, 3, 4}}, + } + + runVmTests(t, tests) +} + func TestHashLiterals(t *testing.T) { tests := []vmTestCase{ { @@ -378,6 +388,26 @@ func TestHashLiterals(t *testing.T) { runVmTests(t, tests) } +func TestHashMerging(t *testing.T) { + tests := []vmTestCase{ + { + `{} + {"a": 1}`, + map[object.HashKey]int64{ + (&object.String{Value: "a"}).HashKey(): 1, + }, + }, + { + `{"a": 1} + {"b": 2}`, + map[object.HashKey]int64{ + (&object.String{Value: "a"}).HashKey(): 1, + (&object.String{Value: "b"}).HashKey(): 2, + }, + }, + } + + runVmTests(t, tests) +} + func TestSelectorExpressions(t *testing.T) { tests := []vmTestCase{ {`{"foo": 5}.foo`, 5}, @@ -677,7 +707,7 @@ func TestBuiltinFunctions(t *testing.T) { { `len(1)`, &object.Error{ - Message: "argument to `len` not supported, got INTEGER", + Message: "argument to `len` not supported, got int", }, }, {`len("one", "two")`, @@ -692,14 +722,14 @@ func TestBuiltinFunctions(t *testing.T) { {`first([])`, Null}, {`first(1)`, &object.Error{ - Message: "argument to `first` must be ARRAY, got INTEGER", + Message: "argument to `first` must be array, got int", }, }, {`last([1, 2, 3])`, 3}, {`last([])`, Null}, {`last(1)`, &object.Error{ - Message: "argument to `last` must be ARRAY, got INTEGER", + Message: "argument to `last` must be array, got int", }, }, {`rest([1, 2, 3])`, []int{2, 3}}, @@ -707,7 +737,7 @@ func TestBuiltinFunctions(t *testing.T) { {`push([], 1)`, []int{1}}, {`push(1, 1)`, &object.Error{ - Message: "argument to `push` must be ARRAY, got INTEGER", + Message: "argument to `push` must be array, got int", }, }, {`input()`, ""},