diff --git a/.gitignore b/.gitignore index a6ce85f..a9a9f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -78,8 +78,13 @@ fabric.properties .idea/caches/build_file_checksums.ser *~ +*-e +*.so *.bak *.txt +*.swo +*.swn +.DS_Store /dist /monkey-lang \ No newline at end of file diff --git a/builtins/args.go b/builtins/args.go new file mode 100644 index 0000000..9c92379 --- /dev/null +++ b/builtins/args.go @@ -0,0 +1,12 @@ +package builtins + +import "monkey/object" + +// Args ... +func Args(args ...object.Object) object.Object { + elements := make([]object.Object, len(object.Arguments)) + for i, arg := range object.Arguments { + elements[i] = &object.String{Value: arg} + } + return &object.Array{Elements: elements} +} diff --git a/object/builtin_assert.go b/builtins/assert.go similarity index 55% rename from object/builtin_assert.go rename to builtins/assert.go index 0b88af7..5f52e54 100644 --- a/object/builtin_assert.go +++ b/builtins/assert.go @@ -1,4 +1,6 @@ -package object +package builtins + +import "monkey/object" import ( "fmt" @@ -6,22 +8,22 @@ import ( ) // Assert ... -func Assert(args ...Object) Object { +func Assert(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() != BOOLEAN_OBJ { + if args[0].Type() != object.BOOLEAN_OBJ { return newError("argument #1 to `assert` must be BOOLEAN, got %s", args[0].Type()) } - if args[1].Type() != STRING_OBJ { + if args[1].Type() != object.STRING_OBJ { return newError("argument #2 to `assert` must be STRING, got %s", args[0].Type()) } - if !args[0].(*Boolean).Value { - fmt.Printf("Assertion Error: %s", args[1].(*String).Value) + if !args[0].(*object.Boolean).Value { + fmt.Printf("Assertion Error: %s", args[1].(*object.String).Value) os.Exit(1) } diff --git a/builtins/bool.go b/builtins/bool.go new file mode 100644 index 0000000..8dc6cdb --- /dev/null +++ b/builtins/bool.go @@ -0,0 +1,40 @@ +package builtins + +import "monkey/object" + +// Bool ... +func Bool(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + switch arg := args[0].(type) { + case *object.Null: + return &object.Boolean{Value: false} + case *object.Boolean: + return arg + case *object.Integer: + if arg.Value == 0 { + return &object.Boolean{Value: false} + } + return &object.Boolean{Value: true} + case *object.String: + if len(arg.Value) > 0 { + return &object.Boolean{Value: true} + } + return &object.Boolean{Value: false} + case *object.Array: + if len(arg.Elements) > 0 { + return &object.Boolean{Value: true} + } + return &object.Boolean{Value: false} + case *object.Hash: + if len(arg.Pairs) > 0 { + return &object.Boolean{Value: true} + } + return &object.Boolean{Value: false} + + default: + return newError("argument to `bool` not supported, got=%s", args[0].Type()) + } +} diff --git a/object/builtins.go b/builtins/builtins.go similarity index 77% rename from object/builtins.go rename to builtins/builtins.go index f063a66..5ecfeda 100644 --- a/object/builtins.go +++ b/builtins/builtins.go @@ -1,12 +1,13 @@ -package object +package builtins import ( "fmt" + "monkey/object" "sort" ) // Builtins ... -var Builtins = map[string]*Builtin{ +var Builtins = map[string]*object.Builtin{ "len": {Name: "len", Fn: Len}, "input": {Name: "input", Fn: Input}, "print": {Name: "print", Fn: Print}, @@ -20,7 +21,7 @@ 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}, + "type": {Name: "type", Fn: TypeOf}, "args": {Name: "args", Fn: Args}, "lower": {Name: "lower", Fn: Lower}, "upper": {Name: "upper", Fn: Upper}, @@ -29,10 +30,11 @@ var Builtins = map[string]*Builtin{ "find": {Name: "find", Fn: Find}, "read": {Name: "read", Fn: Read}, "write": {Name: "write", Fn: Write}, + "ffi": {Name: "ffi", Fn: FFI}, } // BuiltinsIndex ... -var BuiltinsIndex []*Builtin +var BuiltinsIndex []*object.Builtin func init() { var keys []string @@ -46,6 +48,6 @@ func init() { } } -func newError(format string, a ...interface{}) *Error { - return &Error{Message: fmt.Sprintf(format, a...)} +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} } diff --git a/builtins/exit.go b/builtins/exit.go new file mode 100644 index 0000000..0bcf42c --- /dev/null +++ b/builtins/exit.go @@ -0,0 +1,19 @@ +package builtins + +import "monkey/object" + +// Exit ... +func Exit(args ...object.Object) object.Object { + var status int + if len(args) == 1 { + if args[0].Type() != object.INTEGER_OBJ { + return newError("argument to `exit` must be INTEGER, got %s", + args[0].Type()) + } + status = int(args[0].(*object.Integer).Value) + } + + object.ExitFunction(status) + + return nil +} diff --git a/builtins/ffi.go b/builtins/ffi.go new file mode 100644 index 0000000..c237abd --- /dev/null +++ b/builtins/ffi.go @@ -0,0 +1,40 @@ +package builtins + +import "monkey/object" + +import ( + "fmt" + "plugin" +) + +// FFI ... +func FFI(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + + arg, ok := args[0].(*object.String) + if !ok { + return newError("argument #1 to `ffi` expected to be `str` got=%T", args[0].Type()) + } + name := arg.Value + + arg, ok = args[1].(*object.String) + if !ok { + return newError("argument #2 to `ffi` expected to be `str` got=%T", args[0].Type()) + } + symbol := arg.Value + + p, err := plugin.Open(fmt.Sprintf("%s.so", name)) + if err != nil { + return newError("error loading plugin: %s", err) + } + + v, err := p.Lookup(symbol) + if err != nil { + return newError("error finding symbol: %s", err) + } + + return &object.Builtin{Name: symbol, Fn: v.(func(...object.Object) object.Object)} +} diff --git a/object/builtin_find.go b/builtins/find.go similarity index 54% rename from object/builtin_find.go rename to builtins/find.go index 422b466..7664989 100644 --- a/object/builtin_find.go +++ b/builtins/find.go @@ -1,33 +1,35 @@ -package object +package builtins + +import "monkey/object" import ( "strings" ) // Find ... -func Find(args ...Object) Object { +func Find(args ...object.Object) 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 { + if haystack, ok := args[0].(*object.String); ok { + if needle, ok := args[1].(*object.String); ok { index := strings.Index(haystack.Value, needle.Value) - return &Integer{Value: int64(index)} + return &object.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 { + } else if haystack, ok := args[0].(*object.Array); ok { needle := args[1] index := -1 for i, el := range haystack.Elements { - if cmp, ok := el.(Comparable); ok && cmp.Equal(needle) { + if cmp, ok := el.(object.Comparable); ok && cmp.Equal(needle) { index = i break } } - return &Integer{Value: int64(index)} + return &object.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/builtins/first.go similarity index 62% rename from object/builtin_first.go rename to builtins/first.go index 959f250..b25bac3 100644 --- a/object/builtin_first.go +++ b/builtins/first.go @@ -1,17 +1,19 @@ -package object +package builtins + +import "monkey/object" // First ... -func First(args ...Object) Object { +func First(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() != ARRAY_OBJ { + if args[0].Type() != object.ARRAY_OBJ { return newError("argument to `first` must be array, got %s", args[0].Type()) } - arr := args[0].(*Array) + arr := args[0].(*object.Array) if len(arr.Elements) > 0 { return arr.Elements[0] } diff --git a/object/builtin_input.go b/builtins/input.go similarity index 70% rename from object/builtin_input.go rename to builtins/input.go index 4be0858..f75a449 100644 --- a/object/builtin_input.go +++ b/builtins/input.go @@ -1,4 +1,6 @@ -package object +package builtins + +import "monkey/object" import ( "bufio" @@ -8,9 +10,9 @@ import ( ) // Input ... -func Input(args ...Object) Object { +func Input(args ...object.Object) object.Object { if len(args) > 0 { - obj, ok := args[0].(*String) + obj, ok := args[0].(*object.String) if !ok { return newError( "argument to `input` not supported, got %s", @@ -26,5 +28,5 @@ func Input(args ...Object) Object { if err != nil && err != io.EOF { return newError(fmt.Sprintf("error reading input from stdin: %s", err)) } - return &String{Value: string(line)} + return &object.String{Value: string(line)} } diff --git a/object/builtin_int.go b/builtins/int.go similarity index 61% rename from object/builtin_int.go rename to builtins/int.go index 6654fe7..d0cc2a5 100644 --- a/object/builtin_int.go +++ b/builtins/int.go @@ -1,28 +1,30 @@ -package object +package builtins + +import "monkey/object" import "strconv" // Int ... -func Int(args ...Object) Object { +func Int(args ...object.Object) 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: + case *object.Boolean: if arg.Value { - return &Integer{Value: 1} + return &object.Integer{Value: 1} } - return &Integer{Value: 0} - case *Integer: + return &object.Integer{Value: 0} + case *object.Integer: return arg - case *String: + case *object.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} + return &object.Integer{Value: n} default: return newError("argument to `int` not supported, got=%s", args[0].Type()) } diff --git a/object/builtin_join.go b/builtins/join.go similarity index 63% rename from object/builtin_join.go rename to builtins/join.go index 0afe533..22b879d 100644 --- a/object/builtin_join.go +++ b/builtins/join.go @@ -1,23 +1,25 @@ -package object +package builtins + +import "monkey/object" import ( "strings" ) // Join ... -func Join(args ...Object) Object { +func Join(args ...object.Object) 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 { + if arr, ok := args[0].(*object.Array); ok { + if sep, ok := args[1].(*object.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)} + return &object.String{Value: strings.Join(a, sep.Value)} } else { return newError("expected arg #2 to be `str` got got=%T", args[1]) } diff --git a/object/builtin_last.go b/builtins/last.go similarity index 64% rename from object/builtin_last.go rename to builtins/last.go index e929d38..c04b1aa 100644 --- a/object/builtin_last.go +++ b/builtins/last.go @@ -1,17 +1,19 @@ -package object +package builtins + +import "monkey/object" // Last ... -func Last(args ...Object) Object { +func Last(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() != ARRAY_OBJ { + if args[0].Type() != object.ARRAY_OBJ { return newError("argument to `last` must be array, got %s", args[0].Type()) } - arr := args[0].(*Array) + arr := args[0].(*object.Array) length := len(arr.Elements) if length > 0 { return arr.Elements[length-1] diff --git a/object/builtin_len.go b/builtins/len.go similarity index 50% rename from object/builtin_len.go rename to builtins/len.go index 1813290..03c9667 100644 --- a/object/builtin_len.go +++ b/builtins/len.go @@ -1,19 +1,21 @@ -package object +package builtins + +import "monkey/object" import "unicode/utf8" // Len ... -func Len(args ...Object) Object { +func Len(args ...object.Object) 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))} + 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()) diff --git a/object/builtin_lower.go b/builtins/lower.go similarity index 51% rename from object/builtin_lower.go rename to builtins/lower.go index be41ac2..de8babf 100644 --- a/object/builtin_lower.go +++ b/builtins/lower.go @@ -1,16 +1,18 @@ -package object +package builtins + +import "monkey/object" import "strings" // Lower ... -func Lower(args ...Object) Object { +func Lower(args ...object.Object) 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)} + if str, ok := args[0].(*object.String); ok { + return &object.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/builtins/pop.go similarity index 70% rename from object/builtin_pop.go rename to builtins/pop.go index 3710c24..cbfaab6 100644 --- a/object/builtin_pop.go +++ b/builtins/pop.go @@ -1,17 +1,19 @@ -package object +package builtins + +import "monkey/object" // Pop ... -func Pop(args ...Object) Object { +func Pop(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() != ARRAY_OBJ { + if args[0].Type() != object.ARRAY_OBJ { return newError("argument to `pop` must be array, got %s", args[0].Type()) } - arr := args[0].(*Array) + arr := args[0].(*object.Array) length := len(arr.Elements) if length == 0 { diff --git a/object/builtin_print.go b/builtins/print.go similarity index 52% rename from object/builtin_print.go rename to builtins/print.go index c1250a5..6a4257e 100644 --- a/object/builtin_print.go +++ b/builtins/print.go @@ -1,9 +1,11 @@ -package object +package builtins + +import "monkey/object" import "fmt" // Print ... -func Print(args ...Object) Object { +func Print(args ...object.Object) object.Object { for _, arg := range args { fmt.Println(arg.String()) } diff --git a/object/builtin_push.go b/builtins/push.go similarity index 53% rename from object/builtin_push.go rename to builtins/push.go index 22691cb..9e7b93e 100644 --- a/object/builtin_push.go +++ b/builtins/push.go @@ -1,26 +1,28 @@ -package object +package builtins + +import "monkey/object" // Push ... -func Push(args ...Object) Object { +func Push(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() != ARRAY_OBJ { + if args[0].Type() != object.ARRAY_OBJ { return newError("argument to `push` must be array, got %s", args[0].Type()) } - arr := args[0].(*Array) + arr := args[0].(*object.Array) length := len(arr.Elements) - newElements := make([]Object, length+1) + newElements := make([]object.Object, length+1) copy(newElements, arr.Elements) - if immutable, ok := args[1].(Immutable); ok { + if immutable, ok := args[1].(object.Immutable); ok { newElements[length] = immutable.Clone() } else { newElements[length] = args[1] } - return &Array{Elements: newElements} + return &object.Array{Elements: newElements} } diff --git a/object/builtin_read.go b/builtins/read.go similarity index 67% rename from object/builtin_read.go rename to builtins/read.go index 9386d1b..b996025 100644 --- a/object/builtin_read.go +++ b/builtins/read.go @@ -1,17 +1,19 @@ -package object +package builtins + +import "monkey/object" import ( "os" ) // Read ... -func Read(args ...Object) Object { +func Read(args ...object.Object) object.Object { if len(args) != 1 { return newError("wrong number of arguments. got=%d, want=1", len(args)) } - arg, ok := args[0].(*String) + arg, ok := args[0].(*object.String) if !ok { return newError("argument to `read` expected to be `str` got=%T", args[0].Type()) } @@ -22,5 +24,5 @@ func Read(args ...Object) Object { return newError("error reading file: %s", err) } - return &String{Value: string(data)} + return &object.String{Value: string(data)} } diff --git a/object/builtin_rest.go b/builtins/rest.go similarity index 53% rename from object/builtin_rest.go rename to builtins/rest.go index 9a8b584..15f730a 100644 --- a/object/builtin_rest.go +++ b/builtins/rest.go @@ -1,22 +1,24 @@ -package object +package builtins + +import "monkey/object" // Rest ... -func Rest(args ...Object) Object { +func Rest(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() != ARRAY_OBJ { + if args[0].Type() != object.ARRAY_OBJ { return newError("argument to `rest` must be array, got %s", args[0].Type()) } - arr := args[0].(*Array) + arr := args[0].(*object.Array) length := len(arr.Elements) if length > 0 { - newElements := make([]Object, length-1, length-1) + newElements := make([]object.Object, length-1, length-1) copy(newElements, arr.Elements[1:length]) - return &Array{Elements: newElements} + return &object.Array{Elements: newElements} } return nil diff --git a/object/builtin_split.go b/builtins/split.go similarity index 58% rename from object/builtin_split.go rename to builtins/split.go index 566eb1a..fb05c76 100644 --- a/object/builtin_split.go +++ b/builtins/split.go @@ -1,23 +1,25 @@ -package object +package builtins + +import "monkey/object" import ( "strings" ) // Split ... -func Split(args ...Object) Object { +func Split(args ...object.Object) 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 { + if obj, ok := args[0].(*object.String); ok { var sep string s := obj.Value if len(args) == 2 { - if obj, ok := args[1].(*String); ok { + if obj, ok := args[1].(*object.String); ok { sep = obj.Value } else { return newError("expected arg #2 to be `str` got=%T", args[1]) @@ -25,11 +27,11 @@ func Split(args ...Object) Object { } tokens := strings.Split(s, sep) - elements := make([]Object, len(tokens)) + elements := make([]object.Object, len(tokens)) for i, token := range tokens { - elements[i] = &String{Value: token} + elements[i] = &object.String{Value: token} } - return &Array{Elements: elements} + return &object.Array{Elements: elements} } else { return newError("expected arg #1 to be `str` got got=%T", args[0]) } diff --git a/object/builtin_str.go b/builtins/str.go similarity index 65% rename from object/builtin_str.go rename to builtins/str.go index cc33298..8c77a8f 100644 --- a/object/builtin_str.go +++ b/builtins/str.go @@ -1,11 +1,13 @@ -package object +package builtins + +import "monkey/object" import ( "fmt" ) // Str ... -func Str(args ...Object) Object { +func Str(args ...object.Object) object.Object { if len(args) != 1 { return newError("wrong number of arguments. got=%d, want=1", len(args)) } @@ -15,5 +17,5 @@ func Str(args ...Object) Object { return newError("argument to `str` not supported, got %s", args[0].Type()) } - return &String{Value: arg.String()} + return &object.String{Value: arg.String()} } diff --git a/builtins/typeof.go b/builtins/typeof.go new file mode 100644 index 0000000..c093254 --- /dev/null +++ b/builtins/typeof.go @@ -0,0 +1,12 @@ +package builtins + +import "monkey/object" + +// TypeOf ... +func TypeOf(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + return &object.String{Value: string(args[0].Type())} +} diff --git a/object/builtin_upper.go b/builtins/upper.go similarity index 51% rename from object/builtin_upper.go rename to builtins/upper.go index b1e9e5f..45c0880 100644 --- a/object/builtin_upper.go +++ b/builtins/upper.go @@ -1,16 +1,18 @@ -package object +package builtins + +import "monkey/object" import "strings" // Upper ... -func Upper(args ...Object) Object { +func Upper(args ...object.Object) 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)} + if str, ok := args[0].(*object.String); ok { + return &object.String{Value: strings.ToUpper(str.Value)} } return newError("expected `str` argument to `upper` got=%T", args[0]) } diff --git a/object/builtin_write.go b/builtins/write.go similarity index 72% rename from object/builtin_write.go rename to builtins/write.go index 2e1eb6a..c2ff56f 100644 --- a/object/builtin_write.go +++ b/builtins/write.go @@ -1,23 +1,25 @@ -package object +package builtins + +import "monkey/object" import ( "os" ) // Write ... -func Write(args ...Object) Object { +func Write(args ...object.Object) object.Object { if len(args) != 2 { return newError("wrong number of arguments. got=%d, want=2", len(args)) } - arg, ok := args[0].(*String) + arg, ok := args[0].(*object.String) if !ok { return newError("argument #1 to `write` expected to be `str` got=%T", args[0].Type()) } filename := arg.Value - arg, ok = args[1].(*String) + arg, ok = args[1].(*object.String) if !ok { return newError("argument #2 to `write` expected to be `str` got=%T", args[1].Type()) } @@ -28,5 +30,5 @@ func Write(args ...Object) Object { return newError("error writing file: %s", err) } - return &Null{} + return &object.Null{} } diff --git a/compiler/compiler.go b/compiler/compiler.go index 1382f4f..f3b823b 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "monkey/ast" + "monkey/builtins" "monkey/code" "monkey/object" "sort" @@ -42,7 +43,7 @@ func New() *Compiler { symbolTable := NewSymbolTable() - for i, builtin := range object.BuiltinsIndex { + for i, builtin := range builtins.BuiltinsIndex { symbolTable.DefineBuiltin(i, builtin.Name) } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index a725f0c..cc920dc 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, 10), + code.Make(code.OpGetBuiltin, 11), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpPop), - code.Make(code.OpGetBuiltin, 14), + code.Make(code.OpGetBuiltin, 15), 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, 10), + code.Make(code.OpGetBuiltin, 11), code.Make(code.OpArray, 0), code.Make(code.OpCall, 1), code.Make(code.OpReturn), diff --git a/evaluator/builtins.go b/evaluator/builtins.go deleted file mode 100644 index a9db8f9..0000000 --- a/evaluator/builtins.go +++ /dev/null @@ -1,7 +0,0 @@ -package evaluator - -import ( - "monkey/object" -) - -var builtins = object.Builtins diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 63a1e60..a4c27eb 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -3,6 +3,7 @@ package evaluator import ( "fmt" "monkey/ast" + "monkey/builtins" "monkey/object" "strings" ) @@ -487,7 +488,7 @@ func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object return val } - if builtin, ok := builtins[node.Value]; ok { + if builtin, ok := builtins.Builtins[node.Value]; ok { return builtin } diff --git a/object/array.go b/object/array.go new file mode 100644 index 0000000..48adf00 --- /dev/null +++ b/object/array.go @@ -0,0 +1,51 @@ +package object + +import ( + "bytes" + "strings" +) + +// Array is the array literal type that holds a slice of Object(s) +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { + return ARRAY_OBJ +} +func (ao *Array) Inspect() string { + var out bytes.Buffer + + var elements []string + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.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 +} diff --git a/object/bool.go b/object/bool.go new file mode 100644 index 0000000..55fc12d --- /dev/null +++ b/object/bool.go @@ -0,0 +1,28 @@ +package object + +import ( + "fmt" +) + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { + return BOOLEAN_OBJ +} +func (b *Boolean) Inspect() string { + return fmt.Sprintf("%t", b.Value) +} +func (b *Boolean) Clone() Object { + return &Boolean{Value: b.Value} +} +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 +} diff --git a/object/builtin.go b/object/builtin.go new file mode 100644 index 0000000..c443ac0 --- /dev/null +++ b/object/builtin.go @@ -0,0 +1,18 @@ +package object + +import "fmt" + +type Builtin struct { + Name string + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { + return BUILTIN_OBJ +} +func (b *Builtin) Inspect() string { + return fmt.Sprintf("", b.Name) +} +func (b *Builtin) String() string { + return b.Inspect() +} diff --git a/object/builtin_args.go b/object/builtin_args.go deleted file mode 100644 index 4e66137..0000000 --- a/object/builtin_args.go +++ /dev/null @@ -1,10 +0,0 @@ -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_bool.go b/object/builtin_bool.go deleted file mode 100644 index 2874d2e..0000000 --- a/object/builtin_bool.go +++ /dev/null @@ -1,38 +0,0 @@ -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_exit.go b/object/builtin_exit.go deleted file mode 100644 index d9502e3..0000000 --- a/object/builtin_exit.go +++ /dev/null @@ -1,17 +0,0 @@ -package object - -// 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()) - } - status = int(args[0].(*Integer).Value) - } - - ExitFunction(status) - - return nil -} diff --git a/object/builtin_typeof.go b/object/builtin_typeof.go deleted file mode 100644 index 6382e53..0000000 --- a/object/builtin_typeof.go +++ /dev/null @@ -1,10 +0,0 @@ -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/closure.go b/object/closure.go new file mode 100644 index 0000000..3375539 --- /dev/null +++ b/object/closure.go @@ -0,0 +1,37 @@ +package object + +import ( + "fmt" + "monkey/code" +) + +type CompiledFunction struct { + Instructions code.Instructions + NumLocals int + NumParameters int +} + +func (cf *CompiledFunction) Type() ObjectType { + return COMPILED_FUNCTION_OBJ +} +func (cf *CompiledFunction) Inspect() string { + return fmt.Sprintf("CompiledFunction[%p]", cf) +} +func (cf *CompiledFunction) String() string { + return cf.Inspect() +} + +type Closure struct { + Fn *CompiledFunction + Free []Object +} + +func (c *Closure) Type() ObjectType { + return CLOSURE_OBJ +} +func (c *Closure) Inspect() string { + return fmt.Sprintf("Closure[%p]", c) +} +func (c *Closure) String() string { + return c.Inspect() +} diff --git a/object/error.go b/object/error.go new file mode 100644 index 0000000..d50e0b1 --- /dev/null +++ b/object/error.go @@ -0,0 +1,18 @@ +package object + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { + return ERROR_OBJ +} +func (e *Error) Inspect() string { + return "Error: " + e.Message +} +func (e *Error) Clone() Object { + return &Error{Message: e.Message} +} +func (e *Error) String() string { + return e.Message +} diff --git a/object/function.go b/object/function.go new file mode 100644 index 0000000..7677e95 --- /dev/null +++ b/object/function.go @@ -0,0 +1,51 @@ +package object + +import ( + "bytes" + "monkey/ast" + "strings" +) + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { + return FUNCTION_OBJ +} +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} +func (f *Function) String() string { + return f.Inspect() +} + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { + return RETURN_VALUE_OBJ +} +func (rv *ReturnValue) Inspect() string { + return rv.Value.Inspect() +} +func (rv *ReturnValue) String() string { + return rv.Inspect() +} diff --git a/object/hash.go b/object/hash.go new file mode 100644 index 0000000..b0b420c --- /dev/null +++ b/object/hash.go @@ -0,0 +1,91 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "strings" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { + return HASH_OBJ +} +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.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 +} diff --git a/object/int.go b/object/int.go new file mode 100644 index 0000000..fdd7354 --- /dev/null +++ b/object/int.go @@ -0,0 +1,26 @@ +package object + +import "fmt" + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { + return INTEGER_OBJ +} +func (i *Integer) Inspect() string { + return fmt.Sprintf("%d", i.Value) +} +func (i *Integer) Clone() Object { + return &Integer{Value: i.Value} +} +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 +} diff --git a/object/null.go b/object/null.go new file mode 100644 index 0000000..e693fea --- /dev/null +++ b/object/null.go @@ -0,0 +1,17 @@ +package object + +type Null struct{} + +func (n *Null) Type() ObjectType { + return NULL_OBJ +} +func (n *Null) Inspect() string { + return "null" +} +func (n *Null) String() string { + return n.Inspect() +} +func (n *Null) Equal(other Object) bool { + _, ok := other.(*Null) + return ok +} diff --git a/object/object.go b/object/object.go index 53ef9e3..a58c931 100644 --- a/object/object.go +++ b/object/object.go @@ -1,14 +1,6 @@ package object -import ( - "bytes" - "fmt" - "hash/fnv" - "monkey/ast" - "monkey/code" - "strings" -) - +// Type represents the type of an object type ObjectType string const ( @@ -39,333 +31,19 @@ type Immutable interface { Clone() Object } +// Object represents a value and implementations are expected to implement +// `Type()` and `Inspect()` functions type Object interface { Type() ObjectType String() string Inspect() string } -type Integer struct { - Value int64 -} - -type CompiledFunction struct { - Instructions code.Instructions - NumLocals int - NumParameters int -} - -func (i *Integer) Type() ObjectType { - return INTEGER_OBJ -} -func (i *Integer) Inspect() string { - return fmt.Sprintf("%d", i.Value) -} -func (i *Integer) Clone() Object { - return &Integer{Value: i.Value} -} -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 -} - -func (b *Boolean) Type() ObjectType { - return BOOLEAN_OBJ -} -func (b *Boolean) Inspect() string { - return fmt.Sprintf("%t", b.Value) -} -func (b *Boolean) Clone() Object { - return &Boolean{Value: b.Value} -} -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{} - -func (n *Null) Type() ObjectType { - return NULL_OBJ -} -func (n *Null) Inspect() string { - return "null" -} -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 -} - -func (rv *ReturnValue) Type() ObjectType { - return RETURN_VALUE_OBJ -} -func (rv *ReturnValue) Inspect() string { - return rv.Value.Inspect() -} -func (rv *ReturnValue) String() string { - return rv.Inspect() -} - -type Error struct { - Message string -} - -func (e *Error) Type() ObjectType { - return ERROR_OBJ -} -func (e *Error) Inspect() string { - return "Error: " + e.Message -} -func (e *Error) Clone() Object { - return &Error{Message: e.Message} -} -func (e *Error) String() string { - return e.Message -} - -type Function struct { - Parameters []*ast.Identifier - Body *ast.BlockStatement - Env *Environment -} - -func (f *Function) Type() ObjectType { - return FUNCTION_OBJ -} -func (f *Function) Inspect() string { - var out bytes.Buffer - - params := []string{} - for _, p := range f.Parameters { - params = append(params, p.String()) - } - - out.WriteString("fn") - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") {\n") - out.WriteString(f.Body.String()) - out.WriteString("\n}") - - return out.String() -} -func (f *Function) String() string { - return f.Inspect() -} - -type String struct { - Value string -} - -func (s *String) Type() ObjectType { - return STRING_OBJ -} -func (s *String) Inspect() string { - return fmt.Sprintf("%#v", s.Value) -} -func (s *String) Clone() Object { - return &String{Value: s.Value} -} -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 - -type Builtin struct { - Name string - Fn BuiltinFunction -} - -func (b *Builtin) Type() ObjectType { - return BUILTIN_OBJ -} -func (b *Builtin) Inspect() string { - return fmt.Sprintf("", b.Name) -} -func (b *Builtin) String() string { - return b.Inspect() -} - -type Array struct { - Elements []Object -} - -func (ao *Array) Type() ObjectType { - return ARRAY_OBJ -} -func (ao *Array) Inspect() string { - var out bytes.Buffer - - elements := []string{} - for _, e := range ao.Elements { - elements = append(elements, e.Inspect()) - } - - out.WriteString("[") - out.WriteString(strings.Join(elements, ", ")) - out.WriteString("]") - - return out.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 - Value uint64 -} - -func (b *Boolean) HashKey() HashKey { - var value uint64 - - if b.Value { - value = 1 - } else { - value = 0 - } - - return HashKey{Type: b.Type(), Value: value} -} - +// Hashable is the interface for all hashable objects which must implement +// the HashKey() method which returns a HashKey result. type Hashable interface { HashKey() HashKey } -func (i *Integer) HashKey() HashKey { - return HashKey{Type: i.Type(), Value: uint64(i.Value)} -} - -func (s *String) HashKey() HashKey { - h := fnv.New64a() - h.Write([]byte(s.Value)) - - return HashKey{Type: s.Type(), Value: h.Sum64()} -} - -type HashPair struct { - Key Object - Value Object -} - -type Hash struct { - Pairs map[HashKey]HashPair -} - -func (h *Hash) Type() ObjectType { - return HASH_OBJ -} -func (h *Hash) Inspect() string { - var out bytes.Buffer - - pairs := []string{} - for _, pair := range h.Pairs { - pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) - } - - out.WriteString("{") - out.WriteString(strings.Join(pairs, ", ")) - out.WriteString("}") - - return out.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 -} -func (cf *CompiledFunction) Inspect() string { - return fmt.Sprintf("CompiledFunction[%p]", cf) -} -func (cf *CompiledFunction) String() string { - return cf.Inspect() -} - -type Closure struct { - Fn *CompiledFunction - Free []Object -} - -func (c *Closure) Type() ObjectType { - return CLOSURE_OBJ -} -func (c *Closure) Inspect() string { - return fmt.Sprintf("Closure[%p]", c) -} -func (c *Closure) String() string { - return c.Inspect() -} +// BuiltinFunction represents the builtin function type +type BuiltinFunction func(args ...Object) Object diff --git a/object/str.go b/object/str.go new file mode 100644 index 0000000..815903d --- /dev/null +++ b/object/str.go @@ -0,0 +1,26 @@ +package object + +import "fmt" + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { + return STRING_OBJ +} +func (s *String) Inspect() string { + return fmt.Sprintf("%#v", s.Value) +} +func (s *String) Clone() Object { + return &String{Value: s.Value} +} +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 +} diff --git a/plugins/hello.go b/plugins/hello.go new file mode 100644 index 0000000..0360bea --- /dev/null +++ b/plugins/hello.go @@ -0,0 +1,8 @@ +package main + +import "monkey/object" + +// Hello ... +func Hello(args ...object.Object) object.Object { + return &object.String{Value: "Hello World!"} +} diff --git a/repl/repl.go b/repl/repl.go index f9228cf..e3876e8 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "log" + "monkey/builtins" "monkey/compiler" "monkey/evaluator" "monkey/lexer" @@ -49,7 +50,7 @@ type VMState struct { func NewVMState() *VMState { symbolTable := compiler.NewSymbolTable() - for i, builtin := range object.BuiltinsIndex { + for i, builtin := range builtins.BuiltinsIndex { symbolTable.DefineBuiltin(i, builtin.Name) } diff --git a/vm/vm.go b/vm/vm.go index d989ab0..d1a9841 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -3,6 +3,7 @@ package vm import ( "fmt" "log" + "monkey/builtins" "monkey/code" "monkey/compiler" "monkey/object" @@ -323,7 +324,7 @@ func (vm *VM) Run() error { builtinIndex := code.ReadUint8(ins[ip+1:]) vm.currentFrame().ip += 1 - builtin := object.BuiltinsIndex[builtinIndex] + builtin := builtins.BuiltinsIndex[builtinIndex] err := vm.push(builtin) if err != nil {