From c59ce311b03e1978e99d93763a5c369f77b65d46 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Mon, 18 Mar 2024 17:08:36 -0400 Subject: [PATCH] clean up builtins --- compiler/compiler.go | 4 +- compiler/compiler_test.go | 6 +- evaluator/builtins.go | 12 +- object/builtins.go | 372 +++++++++++++++++++------------------- repl/repl.go | 4 +- vm/vm.go | 4 +- 6 files changed, 191 insertions(+), 211 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index bc8cfac..2c0774d 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -42,8 +42,8 @@ func New() *Compiler { symbolTable := NewSymbolTable() - for i, v := range object.Builtins { - symbolTable.DefineBuiltin(i, v.Name) + for i, builtin := range object.BuiltinsIndex { + symbolTable.DefineBuiltin(i, builtin.Name) } return &Compiler{ diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 119b6ce..7869afb 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -977,11 +977,11 @@ func TestBuiltins(t *testing.T) { `, expectedConstants: []interface{}{1}, expectedInstructions: []code.Instructions{ - code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpGetBuiltin, 4), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpPop), - code.Make(code.OpGetBuiltin, 6), + code.Make(code.OpGetBuiltin, 7), code.Make(code.OpArray, 0), code.Make(code.OpConstant, 0), code.Make(code.OpCall, 2), @@ -992,7 +992,7 @@ func TestBuiltins(t *testing.T) { input: `fn() { len([]) }`, expectedConstants: []interface{}{ []code.Instructions{ - code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpGetBuiltin, 4), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpReturn), diff --git a/evaluator/builtins.go b/evaluator/builtins.go index f6632cf..a9db8f9 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -4,14 +4,4 @@ import ( "monkey/object" ) -var builtins = map[string]*object.Builtin{ - "len": object.GetBuiltinByName("len"), - "input": object.GetBuiltinByName("input"), - "print": object.GetBuiltinByName("print"), - "first": object.GetBuiltinByName("first"), - "last": object.GetBuiltinByName("last"), - "rest": object.GetBuiltinByName("rest"), - "push": object.GetBuiltinByName("push"), - "pop": object.GetBuiltinByName("pop"), - "exit": object.GetBuiltinByName("exit"), -} +var builtins = object.Builtins diff --git a/object/builtins.go b/object/builtins.go index 01fe481..28224b7 100644 --- a/object/builtins.go +++ b/object/builtins.go @@ -5,210 +5,200 @@ import ( "fmt" "io" "os" + "sort" "unicode/utf8" ) -var Builtins = []struct { - Name string - Builtin *Builtin -}{ - { - "len", - &Builtin{Name: "len", Fn: func(args ...Object) Object { - if len(args) != 1 { - return newError("wrong number of arguments. got=%d, want=1", - len(args)) - } +var Builtins = map[string]*Builtin{ + "len": {Name: "len", Fn: Len}, + "input": {Name: "input", Fn: Input}, + "print": {Name: "print", Fn: Print}, + "first": {Name: "first", Fn: First}, + "last": {Name: "last", Fn: Last}, + "rest": {Name: "rest", Fn: Rest}, + "push": {Name: "push", Fn: Push}, + "pop": {Name: "pop", Fn: Pop}, + "exit": {Name: "exit", Fn: Exit}, +} - switch arg := args[0].(type) { - case *Array: - return &Integer{Value: int64(len(arg.Elements))} - case *String: - return &Integer{Value: int64(utf8.RuneCountInString(arg.Value))} - default: - return newError("argument to `len` not supported, got %s", - args[0].Type()) - } - }, - }, - }, - { - "input", - &Builtin{Name: "input", Fn: func(args ...Object) Object { - if len(args) > 0 { - obj, ok := args[0].(*String) - if !ok { - return newError( - "argument to `input` not supported, got %s", - args[0].Type(), - ) - } - fmt.Fprintf(os.Stdout, obj.Value) - } +var BuiltinsIndex []*Builtin - buffer := bufio.NewReader(os.Stdin) +func init() { + var keys []string + for k := range Builtins { + keys = append(keys, k) + } + sort.Strings(keys) - line, _, err := buffer.ReadLine() - if err != nil && err != io.EOF { - return newError(fmt.Sprintf("error reading input from stdin: %s", err)) - } - return &String{Value: string(line)} - }}, - }, - { - "print", - &Builtin{Name: "print", Fn: func(args ...Object) Object { - for _, arg := range args { - fmt.Println(arg.Inspect()) - } - - return nil - }, - }, - }, - { - "first", - &Builtin{Name: "first", 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{Name: "last", 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{Name: "rest", 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{Name: "push", 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) - if immutable, ok := args[1].(Immutable); ok { - newElements[length] = immutable.Clone() - } else { - newElements[length] = args[1] - } - - return &Array{Elements: newElements} - }, - }, - }, - { - "pop", - &Builtin{Name: "pop", 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 `pop` must be ARRAY, got %s", - args[0].Type()) - } - - arr := args[0].(*Array) - length := len(arr.Elements) - - if length == 0 { - return newError("cannot pop from an empty array") - } - - element := arr.Elements[length-1] - arr.Elements = arr.Elements[:length-1] - - return element - }, - }, - }, - { - "exit", - &Builtin{Name: "exit", Fn: func(args ...Object) Object { - 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) - } - return nil - }, - }, - }, + for _, k := range keys { + BuiltinsIndex = append(BuiltinsIndex, Builtins[k]) + } } 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 +func Len(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(utf8.RuneCountInString(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } +} + +func Input(args ...Object) Object { + if len(args) > 0 { + obj, ok := args[0].(*String) + if !ok { + return newError( + "argument to `input` not supported, got %s", + args[0].Type(), + ) } + fmt.Fprintf(os.Stdout, obj.Value) + } + + buffer := bufio.NewReader(os.Stdin) + + line, _, err := buffer.ReadLine() + if err != nil && err != io.EOF { + return newError(fmt.Sprintf("error reading input from stdin: %s", err)) + } + return &String{Value: string(line)} +} + +func Print(args ...Object) Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return nil +} + +func First(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 +} + +func Last(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 +} + +func Rest(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 +} + +func Push(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) + copy(newElements, arr.Elements) + if immutable, ok := args[1].(Immutable); ok { + newElements[length] = immutable.Clone() + } else { + newElements[length] = args[1] + } + + return &Array{Elements: newElements} +} + +func Pop(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 `pop` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + + if length == 0 { + return newError("cannot pop from an empty array") + } + + element := arr.Elements[length-1] + arr.Elements = arr.Elements[:length-1] + + return element +} + +func Exit(args ...Object) Object { + 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) } return nil } diff --git a/repl/repl.go b/repl/repl.go index 7d86cd4..6fd5a26 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -50,8 +50,8 @@ type VMState struct { func NewVMState() *VMState { symbolTable := compiler.NewSymbolTable() - for i, v := range object.Builtins { - symbolTable.DefineBuiltin(i, v.Name) + for i, builtin := range object.BuiltinsIndex { + symbolTable.DefineBuiltin(i, builtin.Name) } return &VMState{ diff --git a/vm/vm.go b/vm/vm.go index bb821a6..c139179 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -281,9 +281,9 @@ func (vm *VM) Run() error { builtinIndex := code.ReadUint8(ins[ip+1:]) vm.currentFrame().ip += 1 - definition := object.Builtins[builtinIndex] + builtin := object.BuiltinsIndex[builtinIndex] - err := vm.push(definition.Builtin) + err := vm.push(builtin) if err != nil { return err }