diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 6936f01..4d3702e 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -992,11 +992,11 @@ func TestBuiltins(t *testing.T) { `, expectedConstants: []interface{}{1}, expectedInstructions: []code.Instructions{ - code.Make(code.OpGetBuiltin, 5), + code.Make(code.OpGetBuiltin, 7), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpPop), - code.Make(code.OpGetBuiltin, 8), + code.Make(code.OpGetBuiltin, 10), code.Make(code.OpArray, 0), code.Make(code.OpConstant, 0), code.Make(code.OpCall, 2), @@ -1007,7 +1007,7 @@ func TestBuiltins(t *testing.T) { input: `fn() { return len([]) }`, expectedConstants: []interface{}{ []code.Instructions{ - code.Make(code.OpGetBuiltin, 5), + code.Make(code.OpGetBuiltin, 7), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpReturn), diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 6813f3c..b337242 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -411,12 +411,36 @@ func TestBuiltinFunctions(t *testing.T) { {`input()`, ""}, {`pop([])`, errors.New("cannot pop from an empty array")}, {`pop([1])`, 1}, + {`bool(1)`, true}, + {`bool(0)`, false}, + {`bool(true)`, true}, + {`bool(false)`, false}, + {`bool(null)`, false}, + {`bool("")`, false}, + {`bool("foo")`, true}, + {`bool([])`, false}, + {`bool([1, 2, 3])`, true}, + {`bool({})`, false}, + {`bool({"a": 1})`, true}, + {`int(true)`, 1}, + {`int(false)`, 0}, + {`int(1)`, 1}, + {`int("10")`, 10}, + {`str(null)`, "null"}, + {`str(true)`, "true"}, + {`str(false)`, "false"}, + {`str(10)`, "10"}, + {`str("foo")`, "foo"}, + {`str([1, 2, 3])`, "[1, 2, 3]"}, + {`str({"a": 1})`, "{\"a\": 1}"}, } for _, tt := range tests { evaluated := testEval(tt.input) switch expected := tt.expected.(type) { + case bool: + testBooleanObject(t, evaluated, expected) case int: testIntegerObject(t, evaluated, int64(expected)) case string: diff --git a/object/builtin_bool.go b/object/builtin_bool.go new file mode 100644 index 0000000..2874d2e --- /dev/null +++ b/object/builtin_bool.go @@ -0,0 +1,38 @@ +package object + +// Bool ... +func Bool(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + switch arg := args[0].(type) { + case *Null: + return &Boolean{Value: false} + case *Boolean: + return arg + case *Integer: + if arg.Value == 0 { + return &Boolean{Value: false} + } + return &Boolean{Value: true} + case *String: + if len(arg.Value) > 0 { + return &Boolean{Value: true} + } + return &Boolean{Value: false} + case *Array: + if len(arg.Elements) > 0 { + return &Boolean{Value: true} + } + return &Boolean{Value: false} + case *Hash: + if len(arg.Pairs) > 0 { + return &Boolean{Value: true} + } + return &Boolean{Value: false} + + default: + return newError("argument to `bool` not supported, got=%s", args[0].Type()) + } +} diff --git a/object/builtin_int.go b/object/builtin_int.go new file mode 100644 index 0000000..6654fe7 --- /dev/null +++ b/object/builtin_int.go @@ -0,0 +1,29 @@ +package object + +import "strconv" + +// Int ... +func Int(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + switch arg := args[0].(type) { + case *Boolean: + if arg.Value { + return &Integer{Value: 1} + } + return &Integer{Value: 0} + case *Integer: + return arg + case *String: + n, err := strconv.ParseInt(arg.Value, 10, 64) + if err != nil { + return newError("could not parse string to int: %s", err) + } + + return &Integer{Value: n} + default: + return newError("argument to `int` not supported, got=%s", args[0].Type()) + } +} diff --git a/object/builtin_str.go b/object/builtin_str.go new file mode 100644 index 0000000..cc33298 --- /dev/null +++ b/object/builtin_str.go @@ -0,0 +1,19 @@ +package object + +import ( + "fmt" +) + +// Str ... +func Str(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + arg, ok := args[0].(fmt.Stringer) + if !ok { + return newError("argument to `str` not supported, got %s", args[0].Type()) + } + + return &String{Value: arg.String()} +} diff --git a/object/builtins.go b/object/builtins.go index 8bac377..563eb4f 100644 --- a/object/builtins.go +++ b/object/builtins.go @@ -17,6 +17,9 @@ var Builtins = map[string]*Builtin{ "pop": {Name: "pop", Fn: Pop}, "exit": {Name: "exit", Fn: Exit}, "assert": {Name: "assert", Fn: Assert}, + "bool": {Name: "bool", Fn: Bool}, + "int": {Name: "int", Fn: Int}, + "str": {Name: "str", Fn: Str}, } // BuiltinsIndex ... diff --git a/vm/vm_test.go b/vm/vm_test.go index 26fb79e..290712e 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -656,6 +656,28 @@ func TestBuiltinFunctions(t *testing.T) { }, }, {`pop([1])`, 1}, + {`bool(1)`, true}, + {`bool(0)`, false}, + {`bool(true)`, true}, + {`bool(false)`, false}, + {`bool(null)`, false}, + {`bool("")`, false}, + {`bool("foo")`, true}, + {`bool([])`, false}, + {`bool([1, 2, 3])`, true}, + {`bool({})`, false}, + {`bool({"a": 1})`, true}, + {`int(true)`, 1}, + {`int(false)`, 0}, + {`int(1)`, 1}, + {`int("10")`, 10}, + {`str(null)`, "null"}, + {`str(true)`, "true"}, + {`str(false)`, "false"}, + {`str(10)`, "10"}, + {`str("foo")`, "foo"}, + {`str([1, 2, 3])`, "[1, 2, 3]"}, + {`str({"a": 1})`, "{\"a\": 1}"}, } runVmTests(t, tests)