builtins
This commit is contained in:
@@ -37,6 +37,7 @@ const (
|
|||||||
OpReturn
|
OpReturn
|
||||||
OpGetLocal
|
OpGetLocal
|
||||||
OpSetLocal
|
OpSetLocal
|
||||||
|
OpGetBuiltin
|
||||||
)
|
)
|
||||||
|
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
@@ -71,6 +72,7 @@ var definitions = map[Opcode]*Definition{
|
|||||||
OpReturn: {"OpReturn", []int{}},
|
OpReturn: {"OpReturn", []int{}},
|
||||||
OpGetLocal: {"OpGetLocal", []int{1}},
|
OpGetLocal: {"OpGetLocal", []int{1}},
|
||||||
OpSetLocal: {"OpSetLocal", []int{1}},
|
OpSetLocal: {"OpSetLocal", []int{1}},
|
||||||
|
OpGetBuiltin: {"OpGetBuiltin", []int{1}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lookup(op byte) (*Definition, error) {
|
func Lookup(op byte) (*Definition, error) {
|
||||||
|
|||||||
@@ -34,9 +34,16 @@ func New() *Compiler {
|
|||||||
lastInstruction: EmittedInstruction{},
|
lastInstruction: EmittedInstruction{},
|
||||||
previousInstruction: EmittedInstruction{},
|
previousInstruction: EmittedInstruction{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symbolTable := NewSymbolTable()
|
||||||
|
|
||||||
|
for i, v := range object.Builtins {
|
||||||
|
symbolTable.DefineBuiltin(i, v.Name)
|
||||||
|
}
|
||||||
|
|
||||||
return &Compiler{
|
return &Compiler{
|
||||||
constants: []object.Object{},
|
constants: []object.Object{},
|
||||||
symbolTable: NewSymbolTable(),
|
symbolTable: symbolTable,
|
||||||
scopes: []CompilationScope{mainScope},
|
scopes: []CompilationScope{mainScope},
|
||||||
scopeIndex: 0,
|
scopeIndex: 0,
|
||||||
}
|
}
|
||||||
@@ -207,11 +214,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||||||
return fmt.Errorf("undefined varible %s", node.Value)
|
return fmt.Errorf("undefined varible %s", node.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if symbol.Scope == GlobalScope {
|
c.loadSymbol(symbol)
|
||||||
c.emit(code.OpGetGlobal, symbol.Index)
|
|
||||||
} else {
|
|
||||||
c.emit(code.OpGetLocal, symbol.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.StringLiteral:
|
case *ast.StringLiteral:
|
||||||
str := &object.String{Value: node.Value}
|
str := &object.String{Value: node.Value}
|
||||||
@@ -425,3 +428,14 @@ type Bytecode struct {
|
|||||||
Instructions code.Instructions
|
Instructions code.Instructions
|
||||||
Constants []object.Object
|
Constants []object.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) loadSymbol(s Symbol) {
|
||||||
|
switch s.Scope {
|
||||||
|
case GlobalScope:
|
||||||
|
c.emit(code.OpGetGlobal, s.Index)
|
||||||
|
case LocalScope:
|
||||||
|
c.emit(code.OpGetLocal, s.Index)
|
||||||
|
case BuiltinScope:
|
||||||
|
c.emit(code.OpGetBuiltin, s.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -832,3 +832,43 @@ func testStringObject(expected string, actual object.Object) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuiltins(t *testing.T) {
|
||||||
|
tests := []compilerTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
len([]);
|
||||||
|
push([], 1);
|
||||||
|
`,
|
||||||
|
expectedConstants: []interface{}{1},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
code.Make(code.OpGetBuiltin, 0),
|
||||||
|
code.Make(code.OpArray, 0),
|
||||||
|
code.Make(code.OpCall, 1),
|
||||||
|
code.Make(code.OpPop),
|
||||||
|
code.Make(code.OpGetBuiltin, 5),
|
||||||
|
code.Make(code.OpArray, 0),
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
code.Make(code.OpCall, 2),
|
||||||
|
code.Make(code.OpPop),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `fn() { len([]) }`,
|
||||||
|
expectedConstants: []interface{}{
|
||||||
|
[]code.Instructions{
|
||||||
|
code.Make(code.OpGetBuiltin, 0),
|
||||||
|
code.Make(code.OpArray, 0),
|
||||||
|
code.Make(code.OpCall, 1),
|
||||||
|
code.Make(code.OpReturnValue),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
code.Make(code.OpPop),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCompilerTests(t, tests)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package compiler
|
|||||||
type SymbolScope string
|
type SymbolScope string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LocalScope SymbolScope = "LOCAL"
|
LocalScope SymbolScope = "LOCAL"
|
||||||
GlobalScope SymbolScope = "GLOBAL"
|
GlobalScope SymbolScope = "GLOBAL"
|
||||||
|
BuiltinScope SymbolScope = "BUILTIN"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Symbol struct {
|
type Symbol struct {
|
||||||
@@ -53,3 +54,9 @@ func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
|||||||
|
|
||||||
return obj, ok
|
return obj, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
|
||||||
|
symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
|
||||||
|
s.store[name] = symbol
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
|
|||||||
@@ -166,3 +166,34 @@ func TestResolveNestedLocal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefineResolveBuiltins(t *testing.T) {
|
||||||
|
global := NewSymbolTable()
|
||||||
|
firstLocal := NewEnclosedSymbolTable(global)
|
||||||
|
secondLocal := NewEnclosedSymbolTable(firstLocal)
|
||||||
|
|
||||||
|
expected := []Symbol{
|
||||||
|
Symbol{Name: "a", Scope: BuiltinScope, Index: 0},
|
||||||
|
Symbol{Name: "c", Scope: BuiltinScope, Index: 1},
|
||||||
|
Symbol{Name: "e", Scope: BuiltinScope, Index: 2},
|
||||||
|
Symbol{Name: "f", Scope: BuiltinScope, Index: 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range expected {
|
||||||
|
global.DefineBuiltin(i, v.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range []*SymbolTable{global, firstLocal, secondLocal} {
|
||||||
|
for _, sym := range expected {
|
||||||
|
result, ok := table.Resolve(sym.Name)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("name %s not resolvable", sym.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if result != sym {
|
||||||
|
t.Errorf("expected %s to resolve to %+v, got=%+v",
|
||||||
|
sym.Name, sym, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,119 +1,14 @@
|
|||||||
package evaluator
|
package evaluator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var builtins = map[string]*object.Builtin{
|
var builtins = map[string]*object.Builtin{
|
||||||
"len": &object.Builtin{
|
"len": object.GetBuiltinByName("len"),
|
||||||
Fn: func(args ...object.Object) object.Object {
|
"first": object.GetBuiltinByName("first"),
|
||||||
if len(args) != 1 {
|
"last": object.GetBuiltinByName("last"),
|
||||||
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
"rest": object.GetBuiltinByName("rest"),
|
||||||
}
|
"push": object.GetBuiltinByName("push"),
|
||||||
|
"puts": object.GetBuiltinByName("puts"),
|
||||||
switch arg := args[0].(type) {
|
|
||||||
case *object.Array:
|
|
||||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
|
||||||
case *object.String:
|
|
||||||
return &object.Integer{Value: int64(utf8.RuneCountInString(arg.Value))}
|
|
||||||
default:
|
|
||||||
return newError("argument to `len` not supported, got %s", args[0].Type())
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"first": &object.Builtin{
|
|
||||||
Fn: func(args ...object.Object) object.Object {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
|
||||||
}
|
|
||||||
if args[0].Type() != object.ARRAY_OBJ {
|
|
||||||
return newError("argument to `first` must be ARRAY, got %s", args[0].Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
arr := args[0].(*object.Array)
|
|
||||||
if len(arr.Elements) > 0 {
|
|
||||||
return arr.Elements[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"last": &object.Builtin{
|
|
||||||
Fn: func(args ...object.Object) object.Object {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
|
||||||
}
|
|
||||||
if args[0].Type() != object.ARRAY_OBJ {
|
|
||||||
return newError("argument to `last` must be ARRAY, got %s", args[0].Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
arr := args[0].(*object.Array)
|
|
||||||
length := len(arr.Elements)
|
|
||||||
if length > 0 {
|
|
||||||
return arr.Elements[length-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"rest": &object.Builtin{
|
|
||||||
Fn: func(args ...object.Object) object.Object {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return newError("wrong number of arguments. got=%d, want=1",
|
|
||||||
len(args))
|
|
||||||
}
|
|
||||||
if args[0].Type() != object.ARRAY_OBJ {
|
|
||||||
return newError("argument to `rest` must be ARRAY, got %s",
|
|
||||||
args[0].Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
arr := args[0].(*object.Array)
|
|
||||||
length := len(arr.Elements)
|
|
||||||
if length > 0 {
|
|
||||||
newElements := make([]object.Object, length-1, length-1)
|
|
||||||
copy(newElements, arr.Elements[1:length])
|
|
||||||
return &object.Array{Elements: newElements}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"push": &object.Builtin{
|
|
||||||
Fn: func(args ...object.Object) object.Object {
|
|
||||||
if len(args) != 2 {
|
|
||||||
return newError("wrong number of arguments. got=%d, want=2",
|
|
||||||
len(args))
|
|
||||||
}
|
|
||||||
if args[0].Type() != object.ARRAY_OBJ {
|
|
||||||
return newError("argument to `push` must be ARRAY, got %s",
|
|
||||||
args[0].Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
arr := args[0].(*object.Array)
|
|
||||||
length := len(arr.Elements)
|
|
||||||
|
|
||||||
newElements := make([]object.Object, length+1, length+1)
|
|
||||||
copy(newElements, arr.Elements)
|
|
||||||
newElements[length] = args[1]
|
|
||||||
|
|
||||||
return &object.Array{Elements: newElements}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"puts": &object.Builtin{
|
|
||||||
Fn: func(args ...object.Object) object.Object {
|
|
||||||
for _, arg := range args {
|
|
||||||
fmt.Println(arg.Inspect())
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,7 +319,10 @@ func applyFunction(fn object.Object, args []object.Object) object.Object {
|
|||||||
return unwrapReturnValue(evaluated)
|
return unwrapReturnValue(evaluated)
|
||||||
|
|
||||||
case *object.Builtin:
|
case *object.Builtin:
|
||||||
return fn.Fn(args...)
|
if result := fn.Fn(args...); result != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return NULL
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return newError("not a function: %s", fn.Type())
|
return newError("not a function: %s", fn.Type())
|
||||||
|
|||||||
139
object/builtins.go
Normal file
139
object/builtins.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var Builtins = []struct {
|
||||||
|
Name string
|
||||||
|
Builtin *Builtin
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"len",
|
||||||
|
&Builtin{Fn: func(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 *Array:
|
||||||
|
return &Integer{Value: int64(len(arg.Elements))}
|
||||||
|
case *String:
|
||||||
|
return &Integer{Value: int64(len(arg.Value))}
|
||||||
|
default:
|
||||||
|
return newError("argument to `len` not supported, got %s",
|
||||||
|
args[0].Type())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"puts",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
for _, arg := range args {
|
||||||
|
fmt.Println(arg.Inspect())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"first",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
||||||
|
}
|
||||||
|
if args[0].Type() != ARRAY_OBJ {
|
||||||
|
return newError("argument to `first` must be ARRAY, got %s", args[0].Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*Array)
|
||||||
|
if len(arr.Elements) > 0 {
|
||||||
|
return arr.Elements[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"last",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
||||||
|
}
|
||||||
|
if args[0].Type() != ARRAY_OBJ {
|
||||||
|
return newError("argument to `last` must be ARRAY, got %s", args[0].Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*Array)
|
||||||
|
length := len(arr.Elements)
|
||||||
|
if length > 0 {
|
||||||
|
return arr.Elements[length-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rest",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return newError("wrong number of arguments. got=%d, want=1",
|
||||||
|
len(args))
|
||||||
|
}
|
||||||
|
if args[0].Type() != ARRAY_OBJ {
|
||||||
|
return newError("argument to `rest` must be ARRAY, got %s",
|
||||||
|
args[0].Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*Array)
|
||||||
|
length := len(arr.Elements)
|
||||||
|
if length > 0 {
|
||||||
|
newElements := make([]Object, length-1, length-1)
|
||||||
|
copy(newElements, arr.Elements[1:length])
|
||||||
|
return &Array{Elements: newElements}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) != 2 {
|
||||||
|
return newError("wrong number of arguments. got=%d, want=2",
|
||||||
|
len(args))
|
||||||
|
}
|
||||||
|
if args[0].Type() != ARRAY_OBJ {
|
||||||
|
return newError("argument to `push` must be ARRAY, got %s",
|
||||||
|
args[0].Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*Array)
|
||||||
|
length := len(arr.Elements)
|
||||||
|
|
||||||
|
newElements := make([]Object, length+1, length+1)
|
||||||
|
copy(newElements, arr.Elements)
|
||||||
|
newElements[length] = args[1]
|
||||||
|
|
||||||
|
return &Array{Elements: newElements}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newError(format string, a ...interface{}) *Error {
|
||||||
|
return &Error{Message: fmt.Sprintf(format, a...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBuiltinByName(name string) *Builtin {
|
||||||
|
for _, def := range Builtins {
|
||||||
|
if def.Name == name {
|
||||||
|
return def.Builtin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ func Start(in io.Reader, out io.Writer) {
|
|||||||
constants := []object.Object{}
|
constants := []object.Object{}
|
||||||
globals := make([]object.Object, vm.GlobalsSize)
|
globals := make([]object.Object, vm.GlobalsSize)
|
||||||
symbolTable := compiler.NewSymbolTable()
|
symbolTable := compiler.NewSymbolTable()
|
||||||
|
for i, v := range object.Builtins {
|
||||||
|
symbolTable.DefineBuiltin(i, v.Name)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
fmt.Fprintf(out, PROMPT)
|
fmt.Fprintf(out, PROMPT)
|
||||||
|
|||||||
49
vm/vm.go
49
vm/vm.go
@@ -206,7 +206,7 @@ func (vm *VM) Run() error {
|
|||||||
numArgs := code.ReadUint8(ins[ip+1:])
|
numArgs := code.ReadUint8(ins[ip+1:])
|
||||||
vm.currentFrame().ip += 1
|
vm.currentFrame().ip += 1
|
||||||
|
|
||||||
err := vm.callFunction(int(numArgs))
|
err := vm.executeCall(int(numArgs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -249,6 +249,17 @@ func (vm *VM) Run() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case code.OpGetBuiltin:
|
||||||
|
builtinIndex := code.ReadUint8(ins[ip+1:])
|
||||||
|
vm.currentFrame().ip += 1
|
||||||
|
|
||||||
|
definition := object.Builtins[builtinIndex]
|
||||||
|
|
||||||
|
err := vm.push(definition.Builtin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,12 +476,19 @@ func (vm *VM) executeMinusOperator() error {
|
|||||||
return vm.push(&object.Integer{Value: -value})
|
return vm.push(&object.Integer{Value: -value})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VM) callFunction(numArgs int) error {
|
func (vm *VM) executeCall(numArgs int) error {
|
||||||
fn, ok := vm.stack[vm.sp-1-numArgs].(*object.CompiledFunction)
|
callee := vm.stack[vm.sp-1-numArgs]
|
||||||
if !ok {
|
switch callee := callee.(type) {
|
||||||
return fmt.Errorf("calling non-function")
|
case *object.CompiledFunction:
|
||||||
|
return vm.callFunction(callee, numArgs)
|
||||||
|
case *object.Builtin:
|
||||||
|
return vm.callBuiltin(callee, numArgs)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("calling non-function and non-built-in")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VM) callFunction(fn *object.CompiledFunction, numArgs int) error {
|
||||||
if numArgs != fn.NumParameters {
|
if numArgs != fn.NumParameters {
|
||||||
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", fn.NumParameters, numArgs)
|
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", fn.NumParameters, numArgs)
|
||||||
}
|
}
|
||||||
@@ -482,6 +500,27 @@ func (vm *VM) callFunction(numArgs int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error {
|
||||||
|
args := vm.stack[vm.sp-numArgs : vm.sp]
|
||||||
|
|
||||||
|
result := builtin.Fn(args...)
|
||||||
|
vm.sp = vm.sp - numArgs - 1
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
err := vm.push(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := vm.push(Null)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||||
if input {
|
if input {
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -106,6 +106,16 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object
|
|||||||
if actual != Null {
|
if actual != Null {
|
||||||
t.Errorf("object is not Null: %T (%+v)", actual, actual)
|
t.Errorf("object is not Null: %T (%+v)", actual, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *object.Error:
|
||||||
|
errObj, ok := actual.(*object.Error)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("object is not Error: %T (%+v)", actual, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errObj.Message != expected.Message {
|
||||||
|
t.Errorf("wrong error message. expected=%q, got=%q", expected.Message, errObj.Message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,3 +565,49 @@ func TestCallingFunctionsWithWrongArguments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuiltinFunctions(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{`len("")`, 0},
|
||||||
|
{`len("four")`, 4},
|
||||||
|
{`len("hello world")`, 11},
|
||||||
|
{
|
||||||
|
`len(1)`,
|
||||||
|
&object.Error{
|
||||||
|
Message: "argument to `len` not supported, got INTEGER",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{`len("one", "two")`,
|
||||||
|
&object.Error{
|
||||||
|
Message: "wrong number of arguments. got=2, want=1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{`len([1, 2, 3])`, 3},
|
||||||
|
{`len([])`, 0},
|
||||||
|
{`puts("hello", "world!")`, Null},
|
||||||
|
{`first([1, 2, 3])`, 1},
|
||||||
|
{`first([])`, Null},
|
||||||
|
{`first(1)`,
|
||||||
|
&object.Error{
|
||||||
|
Message: "argument to `first` must be ARRAY, got INTEGER",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{`last([1, 2, 3])`, 3},
|
||||||
|
{`last([])`, Null},
|
||||||
|
{`last(1)`,
|
||||||
|
&object.Error{
|
||||||
|
Message: "argument to `last` must be ARRAY, got INTEGER",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||||
|
{`rest([])`, Null},
|
||||||
|
{`push([], 1)`, []int{1}},
|
||||||
|
{`push(1, 1)`,
|
||||||
|
&object.Error{
|
||||||
|
Message: "argument to `push` must be ARRAY, got INTEGER",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user