Add tons of builtin helpers and array operations.
Some checks failed
Test / build (push) Waiting to run
Build / build (push) Has been cancelled

This commit is contained in:
Chuck Smith
2024-03-24 12:11:46 -04:00
parent c9d96236dd
commit 99cea83f57
23 changed files with 413 additions and 49 deletions

View File

@@ -879,11 +879,11 @@ func TestBuiltins(t *testing.T) {
`, `,
expectedConstants: []interface{}{1}, expectedConstants: []interface{}{1},
expectedInstructions: []code.Instructions{ expectedInstructions: []code.Instructions{
code.Make(code.OpGetBuiltin, 7), code.Make(code.OpGetBuiltin, 10),
code.Make(code.OpArray, 0), code.Make(code.OpArray, 0),
code.Make(code.OpCall, 1), code.Make(code.OpCall, 1),
code.Make(code.OpPop), code.Make(code.OpPop),
code.Make(code.OpGetBuiltin, 10), code.Make(code.OpGetBuiltin, 14),
code.Make(code.OpArray, 0), code.Make(code.OpArray, 0),
code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 0),
code.Make(code.OpCall, 2), code.Make(code.OpCall, 2),
@@ -894,7 +894,7 @@ func TestBuiltins(t *testing.T) {
input: `fn() { return len([]) }`, input: `fn() { return len([]) }`,
expectedConstants: []interface{}{ expectedConstants: []interface{}{
[]code.Instructions{ []code.Instructions{
code.Make(code.OpGetBuiltin, 7), code.Make(code.OpGetBuiltin, 10),
code.Make(code.OpArray, 0), code.Make(code.OpArray, 0),
code.Make(code.OpCall, 1), code.Make(code.OpCall, 1),
code.Make(code.OpReturn), code.Make(code.OpReturn),

View File

@@ -306,8 +306,29 @@ func evalIntegerPrefixOperatorExpression(operator string, right object.Object) o
func evalInfixExpression(operator string, left, right object.Object) object.Object { func evalInfixExpression(operator string, left, right object.Object) object.Object {
switch { 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 // [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 leftVal := left.(*object.Array).Elements
rightVal := int(right.(*object.Integer).Value) rightVal := int(right.(*object.Integer).Value)
elements := leftVal elements := leftVal
@@ -316,7 +337,7 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
} }
return &object.Array{Elements: elements} return &object.Array{Elements: elements}
// 3 * [1] // 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) leftVal := int(left.(*object.Integer).Value)
rightVal := right.(*object.Array).Elements rightVal := right.(*object.Array).Elements
elements := rightVal elements := rightVal
@@ -326,12 +347,12 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
return &object.Array{Elements: elements} return &object.Array{Elements: elements}
// " " * 4 // " " * 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 leftVal := left.(*object.String).Value
rightVal := right.(*object.Integer).Value rightVal := right.(*object.Integer).Value
return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} return &object.String{Value: strings.Repeat(leftVal, int(rightVal))}
// 4 * " " // 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 leftVal := left.(*object.Integer).Value
rightVal := right.(*object.String).Value rightVal := right.(*object.String).Value
return &object.String{Value: strings.Repeat(rightVal, int(leftVal))} return &object.String{Value: strings.Repeat(rightVal, int(leftVal))}

View File

@@ -182,27 +182,27 @@ func TestErrorHandling(t *testing.T) {
}{ }{
{ {
"5 + true;", "5 + true;",
"type mismatch: INTEGER + BOOLEAN", "type mismatch: int + bool",
}, },
{ {
"5 + true; 5;", "5 + true; 5;",
"type mismatch: INTEGER + BOOLEAN", "type mismatch: int + bool",
}, },
{ {
"-true", "-true",
"unknown operator: -BOOLEAN", "unknown operator: -bool",
}, },
{ {
"true + false;", "true + false;",
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: bool + bool",
}, },
{ {
"5; true + false; 5", "5; true + false; 5",
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: bool + bool",
}, },
{ {
"if (10 > 1) { true + false; }", "if (10 > 1) { true + false; }",
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: bool + bool",
}, },
{ {
` `
@@ -214,7 +214,7 @@ func TestErrorHandling(t *testing.T) {
return 1; return 1;
} }
`, `,
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: bool + bool",
}, },
{ {
"foobar", "foobar",
@@ -222,11 +222,11 @@ func TestErrorHandling(t *testing.T) {
}, },
{ {
`"Hello" - "World"`, `"Hello" - "World"`,
"unknown operator: STRING - STRING", "unknown operator: str - str",
}, },
{ {
`{"name": "Monkey"}[fn(x) { x }];`, `{"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("")`, 0},
{`len("four")`, 4}, {`len("four")`, 4},
{`len("hello world")`, 11}, {`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("one", "two")`, errors.New("wrong number of arguments. got=2, want=1")},
{`len("∑")`, 1}, {`len("∑")`, 1},
{`len([1, 2, 3])`, 3}, {`len([1, 2, 3])`, 3},
{`len([])`, 0}, {`len([])`, 0},
{`first([1, 2, 3])`, 1}, {`first([1, 2, 3])`, 1},
{`first([])`, nil}, {`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([1, 2, 3])`, 3},
{`last([])`, nil}, {`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([1, 2, 3])`, []int{2, 3}},
{`rest([])`, nil}, {`rest([])`, nil},
{`push([], 1)`, []int{1}}, {`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}, {`print("Hello World")`, nil},
{`input()`, ""}, {`input()`, ""},
{`pop([])`, errors.New("cannot pop from an empty array")}, {`pop([])`, errors.New("cannot pop from an empty array")},
@@ -500,6 +500,24 @@ func TestArrayDuplication(t *testing.T) {
testIntegerObject(t, result.Elements[2], 1) 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) { func TestArrayIndexExpressions(t *testing.T) {
tests := []struct { tests := []struct {
input string 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) { func TestHashSelectorExpressions(t *testing.T) {
tests := []struct { tests := []struct {
input string input string

3
foo.monkey Normal file
View File

@@ -0,0 +1,3 @@
#!./monkey-lang
print(args())

View File

@@ -70,6 +70,11 @@ func main() {
args := flag.Args() args := flag.Args()
copy(object.Arguments, args)
object.StandardInput = os.Stdin
object.StandardOutput = os.Stdout
object.ExitFunction = os.Exit
if compile { if compile {
if len(args) < 1 { if len(args) < 1 {
log.Fatal("no source file given to compile") log.Fatal("no source file given to compile")

10
object/builtin_args.go Normal file
View File

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

View File

@@ -1,17 +1,17 @@
package object package object
import "os"
// Exit ... // Exit ...
func Exit(args ...Object) Object { func Exit(args ...Object) Object {
var status int
if len(args) == 1 { if len(args) == 1 {
if args[0].Type() != INTEGER_OBJ { if args[0].Type() != INTEGER_OBJ {
return newError("argument to `exit` must be INTEGER, got %s", return newError("argument to `exit` must be INTEGER, got %s",
args[0].Type()) args[0].Type())
} }
os.Exit(int(args[0].(*Integer).Value)) status = int(args[0].(*Integer).Value)
} else {
os.Exit(0)
} }
ExitFunction(status)
return nil return nil
} }

34
object/builtin_find.go Normal file
View File

@@ -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])
}
}

View File

@@ -7,7 +7,7 @@ func First(args ...Object) Object {
len(args)) len(args))
} }
if args[0].Type() != ARRAY_OBJ { 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()) args[0].Type())
} }

27
object/builtin_join.go Normal file
View File

@@ -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])
}
}

View File

@@ -7,7 +7,7 @@ func Last(args ...Object) Object {
len(args)) len(args))
} }
if args[0].Type() != ARRAY_OBJ { 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()) args[0].Type())
} }

16
object/builtin_lower.go Normal file
View File

@@ -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])
}

View File

@@ -7,7 +7,7 @@ func Pop(args ...Object) Object {
len(args)) len(args))
} }
if args[0].Type() != ARRAY_OBJ { 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()) args[0].Type())
} }

View File

@@ -7,7 +7,7 @@ func Push(args ...Object) Object {
len(args)) len(args))
} }
if args[0].Type() != ARRAY_OBJ { 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()) args[0].Type())
} }

View File

@@ -7,7 +7,7 @@ func Rest(args ...Object) Object {
len(args)) len(args))
} }
if args[0].Type() != ARRAY_OBJ { 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()) args[0].Type())
} }

36
object/builtin_split.go Normal file
View File

@@ -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])
}
}

10
object/builtin_typeof.go Normal file
View File

@@ -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())}
}

16
object/builtin_upper.go Normal file
View File

@@ -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])
}

View File

@@ -20,6 +20,13 @@ var Builtins = map[string]*Builtin{
"bool": {Name: "bool", Fn: Bool}, "bool": {Name: "bool", Fn: Bool},
"int": {Name: "int", Fn: Int}, "int": {Name: "int", Fn: Int},
"str": {Name: "str", Fn: Str}, "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 ... // BuiltinsIndex ...

View File

@@ -12,20 +12,27 @@ import (
type ObjectType string type ObjectType string
const ( const (
INTEGER_OBJ = "INTEGER" INTEGER_OBJ = "int"
BOOLEAN_OBJ = "BOOLEAN" BOOLEAN_OBJ = "bool"
NULL_OBJ = "NULL" NULL_OBJ = "null"
RETURN_VALUE_OBJ = "RETURN_VALUE" RETURN_VALUE_OBJ = "return"
ERROR_OBJ = "ERROR" ERROR_OBJ = "error"
FUNCTION_OBJ = "FUNCTION" FUNCTION_OBJ = "fn"
STRING_OBJ = "STRING" STRING_OBJ = "str"
BUILTIN_OBJ = "BUILTIN" BUILTIN_OBJ = "builtin"
ARRAY_OBJ = "ARRAY" ARRAY_OBJ = "array"
HASH_OBJ = "HASH" HASH_OBJ = "hash"
COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION" 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 // Immutable is the interface for all immutable objects which must implement
// the Clone() method used by binding names to values. // the Clone() method used by binding names to values.
type Immutable interface { type Immutable interface {
@@ -60,6 +67,12 @@ func (i *Integer) Clone() Object {
func (i *Integer) String() string { func (i *Integer) String() string {
return i.Inspect() 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 { type Boolean struct {
Value bool Value bool
@@ -77,6 +90,12 @@ func (b *Boolean) Clone() Object {
func (b *Boolean) String() string { func (b *Boolean) String() string {
return b.Inspect() 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{} type Null struct{}
@@ -89,6 +108,10 @@ func (n *Null) Inspect() string {
func (n *Null) String() string { func (n *Null) String() string {
return n.Inspect() return n.Inspect()
} }
func (n *Null) Equal(other Object) bool {
_, ok := other.(*Null)
return ok
}
type ReturnValue struct { type ReturnValue struct {
Value Object Value Object
@@ -167,6 +190,12 @@ func (s *String) Clone() Object {
func (s *String) String() string { func (s *String) String() string {
return s.Value 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 type BuiltinFunction func(args ...Object) Object
@@ -209,6 +238,25 @@ func (ao *Array) Inspect() string {
func (ao *Array) String() string { func (ao *Array) String() string {
return ao.Inspect() 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 HashKey struct {
Type ObjectType Type ObjectType
@@ -271,6 +319,31 @@ func (h *Hash) Inspect() string {
func (h *Hash) String() string { func (h *Hash) String() string {
return h.Inspect() 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 { func (cf *CompiledFunction) Type() ObjectType {
return COMPILED_FUNCTION_OBJ return COMPILED_FUNCTION_OBJ

10
object/state.go Normal file
View File

@@ -0,0 +1,10 @@
package object
import "io"
var (
Arguments []string
StandardInput io.Reader
StandardOutput io.Writer
ExitFunction func(int)
)

View File

@@ -527,8 +527,29 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error {
switch { 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 // [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 leftVal := left.(*object.Array).Elements
rightVal := int(right.(*object.Integer).Value) rightVal := int(right.(*object.Integer).Value)
elements := leftVal elements := leftVal
@@ -537,7 +558,7 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error {
} }
return vm.push(&object.Array{Elements: elements}) return vm.push(&object.Array{Elements: elements})
// 3 * [1] // 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) leftVal := int(left.(*object.Integer).Value)
rightVal := right.(*object.Array).Elements rightVal := right.(*object.Array).Elements
elements := rightVal elements := rightVal
@@ -547,12 +568,12 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error {
return vm.push(&object.Array{Elements: elements}) return vm.push(&object.Array{Elements: elements})
// " " * 4 // " " * 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 leftVal := left.(*object.String).Value
rightVal := right.(*object.Integer).Value rightVal := right.(*object.Integer).Value
return vm.push(&object.String{Value: strings.Repeat(leftVal, int(rightVal))}) return vm.push(&object.String{Value: strings.Repeat(leftVal, int(rightVal))})
// 4 * " " // 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 leftVal := left.(*object.Integer).Value
rightVal := right.(*object.String).Value rightVal := right.(*object.String).Value
return vm.push(&object.String{Value: strings.Repeat(rightVal, int(leftVal))}) return vm.push(&object.String{Value: strings.Repeat(rightVal, int(leftVal))})

View File

@@ -354,6 +354,16 @@ func TestArrayDuplication(t *testing.T) {
runVmTests(t, tests) 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) { func TestHashLiterals(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
@@ -378,6 +388,26 @@ func TestHashLiterals(t *testing.T) {
runVmTests(t, tests) 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) { func TestSelectorExpressions(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{`{"foo": 5}.foo`, 5}, {`{"foo": 5}.foo`, 5},
@@ -677,7 +707,7 @@ func TestBuiltinFunctions(t *testing.T) {
{ {
`len(1)`, `len(1)`,
&object.Error{ &object.Error{
Message: "argument to `len` not supported, got INTEGER", Message: "argument to `len` not supported, got int",
}, },
}, },
{`len("one", "two")`, {`len("one", "two")`,
@@ -692,14 +722,14 @@ func TestBuiltinFunctions(t *testing.T) {
{`first([])`, Null}, {`first([])`, Null},
{`first(1)`, {`first(1)`,
&object.Error{ &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([1, 2, 3])`, 3},
{`last([])`, Null}, {`last([])`, Null},
{`last(1)`, {`last(1)`,
&object.Error{ &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}}, {`rest([1, 2, 3])`, []int{2, 3}},
@@ -707,7 +737,7 @@ func TestBuiltinFunctions(t *testing.T) {
{`push([], 1)`, []int{1}}, {`push([], 1)`, []int{1}},
{`push(1, 1)`, {`push(1, 1)`,
&object.Error{ &object.Error{
Message: "argument to `push` must be ARRAY, got INTEGER", Message: "argument to `push` must be array, got int",
}, },
}, },
{`input()`, ""}, {`input()`, ""},