diff --git a/internal/builtins/hash.go b/internal/builtins/hash.go index 072be42..eb84d6a 100644 --- a/internal/builtins/hash.go +++ b/internal/builtins/hash.go @@ -14,8 +14,8 @@ func HashOf(args ...object.Object) object.Object { return newError(err.Error()) } - if hash, ok := args[0].(object.Hashable); ok { - return object.Integer{Value: int64(hash.HashKey().Value)} + if hash, ok := args[0].(object.Hasher); ok { + return object.Integer{Value: int64(hash.Hash().Value)} } return newError("TypeError: hash() expected argument #1 to be hashable") diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go index 75752b2..1e156e1 100644 --- a/internal/evaluator/evaluator.go +++ b/internal/evaluator/evaluator.go @@ -61,8 +61,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { } if ident, ok := node.Left.(*ast.Identifier); ok { - if immutable, ok := value.(object.Immutable); ok { - env.Set(ident.Value, immutable.Clone()) + if obj, ok := value.(object.Copyable); ok { + env.Set(ident.Value, obj.Copy()) } else { env.Set(ident.Value, value) } @@ -105,8 +105,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if isError(key) { return key } - if hashKey, ok := key.(object.Hashable); ok { - hashed := hashKey.HashKey() + if hashKey, ok := key.(object.Hasher); ok { + hashed := hashKey.Hash() hash.Pairs[hashed] = object.HashPair{Key: key, Value: value} } else { return newError("cannot index hash with %T", key) @@ -630,12 +630,12 @@ func evalStringIndexExpression(str, index object.Object) object.Object { func evalHashIndexExpression(hash, index object.Object) object.Object { hashObject := hash.(*object.Hash) - key, ok := index.(object.Hashable) + key, ok := index.(object.Hasher) if !ok { return newError("unusable as hash key: %s", index.Type()) } - pair, ok := hashObject.Pairs[key.HashKey()] + pair, ok := hashObject.Pairs[key.Hash()] if !ok { return NULL } @@ -664,7 +664,7 @@ func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Obje return key } - hashKey, ok := key.(object.Hashable) + hashKey, ok := key.(object.Hasher) if !ok { return newError("unusable as hash key: %s", key.Type()) } @@ -674,7 +674,7 @@ func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Obje return value } - hashed := hashKey.HashKey() + hashed := hashKey.Hash() pairs[hashed] = object.HashPair{ Key: key, Value: value, diff --git a/internal/evaluator/evaluator_test.go b/internal/evaluator/evaluator_test.go index 6260be7..4c5d78a 100644 --- a/internal/evaluator/evaluator_test.go +++ b/internal/evaluator/evaluator_test.go @@ -640,12 +640,12 @@ func TestHashLiterals(t *testing.T) { } expected := map[object.HashKey]int64{ - (object.String{Value: "one"}).HashKey(): 1, - (object.String{Value: "two"}).HashKey(): 2, - (object.String{Value: "three"}).HashKey(): 3, - (object.Integer{Value: 4}).HashKey(): 4, - TRUE.HashKey(): 5, - FALSE.HashKey(): 6, + (object.String{Value: "one"}).Hash(): 1, + (object.String{Value: "two"}).Hash(): 2, + (object.String{Value: "three"}).Hash(): 3, + (object.Integer{Value: 4}).Hash(): 4, + TRUE.Hash(): 5, + FALSE.Hash(): 6, } if len(result.Pairs) != len(expected) { @@ -671,8 +671,8 @@ func TestHashMerging(t *testing.T) { } expected := map[object.HashKey]int64{ - (object.String{Value: "a"}).HashKey(): 1, - (object.String{Value: "b"}).HashKey(): 2, + (object.String{Value: "a"}).Hash(): 1, + (object.String{Value: "b"}).Hash(): 2, } if len(result.Pairs) != len(expected) { diff --git a/internal/object/bool.go b/internal/object/bool.go index 7c06f35..6ad3977 100644 --- a/internal/object/bool.go +++ b/internal/object/bool.go @@ -32,10 +32,24 @@ func (b Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } -func (b Boolean) Clone() Object { +// Copy implements the Copyable interface +func (b Boolean) Copy() Object { return Boolean{Value: b.Value} } +// Hash implements the Hasher interface +func (b Boolean) Hash() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + func (b Boolean) String() string { return b.Inspect() } diff --git a/internal/object/environment.go b/internal/object/environment.go index 7d98b4c..60491d1 100644 --- a/internal/object/environment.go +++ b/internal/object/environment.go @@ -4,24 +4,31 @@ import "unicode" func NewEnclosedEnvironment(outer *Environment) *Environment { env := NewEnvironment() - env.outer = outer + env.parent = outer return env } func NewEnvironment() *Environment { s := make(map[string]Object) - return &Environment{store: s, outer: nil} + return &Environment{store: s, parent: nil} } type Environment struct { - store map[string]Object - outer *Environment + store map[string]Object + parent *Environment +} + +// New creates a new copy of the environment with the current environment as parent +func (e *Environment) New() *Environment { + env := NewEnvironment() + env.parent = e + return env } func (e *Environment) Get(name string) (Object, bool) { obj, ok := e.store[name] - if !ok && e.outer != nil { - obj, ok = e.outer.Get(name) + if !ok && e.parent != nil { + obj, ok = e.parent.Get(name) } return obj, ok } @@ -40,7 +47,7 @@ func (e *Environment) ExportedHash() *Hash { for k, v := range e.store { if unicode.IsUpper(rune(k[0])) { s := String{Value: k} - pairs[s.HashKey()] = HashPair{Key: s, Value: v} + pairs[s.Hash()] = HashPair{Key: s, Value: v} } } return &Hash{Pairs: pairs} diff --git a/internal/object/error.go b/internal/object/error.go index c06d632..33d64c5 100644 --- a/internal/object/error.go +++ b/internal/object/error.go @@ -18,7 +18,8 @@ func (e Error) Inspect() string { return "Error: " + e.Message } -func (e Error) Clone() Object { +// Copy implements the Copyable interface +func (e Error) Copy() Object { return Error{Message: e.Message} } diff --git a/internal/object/hash.go b/internal/object/hash.go index 6ef3a39..e0d24c4 100644 --- a/internal/object/hash.go +++ b/internal/object/hash.go @@ -3,7 +3,6 @@ package object import ( "bytes" "fmt" - "hash/fnv" "strings" ) @@ -12,29 +11,6 @@ type HashKey struct { 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 @@ -91,12 +67,12 @@ func (h *Hash) Add(other Object) (Object, error) { } func (h *Hash) Get(index Object) (Object, error) { - key, ok := index.(Hashable) + key, ok := index.(Hasher) if !ok { return nil, fmt.Errorf("invalid hash key %s", index.Type()) } - pair, found := h.Pairs[key.HashKey()] + pair, found := h.Pairs[key.Hash()] if !found { return Null{}, nil } @@ -105,12 +81,12 @@ func (h *Hash) Get(index Object) (Object, error) { } func (h *Hash) Set(index, other Object) error { - key, ok := index.(Hashable) + key, ok := index.(Hasher) if !ok { return fmt.Errorf("invalid hash key %s", index.Type()) } - h.Pairs[key.HashKey()] = HashPair{Key: index, Value: other} + h.Pairs[key.Hash()] = HashPair{Key: index, Value: other} return nil } @@ -122,8 +98,8 @@ func (h *Hash) Compare(other Object) int { } for _, pair := range h.Pairs { left := pair.Value - hashed := left.(Hashable) - right, ok := obj.Pairs[hashed.HashKey()] + hashed := left.(Hasher) + right, ok := obj.Pairs[hashed.Hash()] if !ok { return -1 } diff --git a/internal/object/int.go b/internal/object/int.go index 2720028..3ffcd7c 100644 --- a/internal/object/int.go +++ b/internal/object/int.go @@ -18,10 +18,15 @@ func (i Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } -func (i Integer) Clone() Object { +func (i Integer) Copy() Object { return Integer{Value: i.Value} } +// Hash implements the Hasher interface +func (i Integer) Hash() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + func (i Integer) String() string { return i.Inspect() } diff --git a/internal/object/module.go b/internal/object/module.go index 67a068b..217798e 100644 --- a/internal/object/module.go +++ b/internal/object/module.go @@ -25,12 +25,12 @@ func (m Module) Inspect() string { } func (m Module) Get(index Object) (Object, error) { - key, ok := index.(Hashable) + key, ok := index.(Hasher) if !ok { return nil, fmt.Errorf("invalid module attribute %s", index.Type()) } - attr, found := m.Attrs.(*Hash).Pairs[key.HashKey()] + attr, found := m.Attrs.(*Hash).Pairs[key.Hash()] if !found { return Null{}, nil } diff --git a/internal/object/object.go b/internal/object/object.go index 1ee0abb..614f683 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -54,17 +54,14 @@ func (t Type) String() string { } } -// 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. +// Comparable is the interface for objects to implement suitable comparisons type Comparable interface { Compare(other Object) int } -// Immutable is the interface for all immutable objects which must implement -// the Clone() method used by binding names to values. -type Immutable interface { - Clone() Object +// Copyable is the interface for creating copies of objects +type Copyable interface { + Copy() Object } // Object represents a value and implementations are expected to implement @@ -76,10 +73,9 @@ type Object interface { Inspect() string } -// Hashable is the interface for all hashable objects which must implement -// the HashKey() method which returns a HashKey result. -type Hashable interface { - HashKey() HashKey +// Hasher is the interface for objects to provide suitable hash keys +type Hasher interface { + Hash() HashKey } // BuiltinFunction represents the builtin function type diff --git a/internal/object/object_test.go b/internal/object/object_test.go index b74aa87..191c225 100644 --- a/internal/object/object_test.go +++ b/internal/object/object_test.go @@ -8,15 +8,15 @@ func TestStringHashKey(t *testing.T) { diff1 := String{Value: "My name is johnny"} diff2 := String{Value: "My name is johnny"} - if hello1.HashKey() != hello2.HashKey() { + if hello1.Hash() != hello2.Hash() { t.Errorf("string with same content have different hash keys") } - if diff1.HashKey() != diff2.HashKey() { + if diff1.Hash() != diff2.Hash() { t.Errorf("string with same content have different hash keys") } - if hello1.HashKey() == diff1.HashKey() { + if hello1.Hash() == diff1.Hash() { t.Errorf("string with different content have same hash keys") } } diff --git a/internal/object/str.go b/internal/object/str.go index 1faccca..dc27118 100644 --- a/internal/object/str.go +++ b/internal/object/str.go @@ -2,6 +2,7 @@ package object import ( "fmt" + "hash/fnv" "strings" "unicode/utf8" ) @@ -26,10 +27,18 @@ func (s String) Inspect() string { return fmt.Sprintf("%#v", s.Value) } -func (s String) Clone() Object { +func (s String) Copy() Object { return String{Value: s.Value} } +// Hash implements the Hasher interface +func (s String) Hash() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + func (s String) String() string { return s.Value } diff --git a/internal/vm/vm.go b/internal/vm/vm.go index bea2809..b8f8fb5 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -102,7 +102,7 @@ func (s *VMState) ExportedHash() *object.Hash { if symbol.Scope == compiler.GlobalScope { obj := s.Globals[symbol.Index] s := object.String{Value: name} - pairs[s.HashKey()] = object.HashPair{Key: s, Value: obj} + pairs[s.Hash()] = object.HashPair{Key: s, Value: obj} } } } @@ -210,8 +210,8 @@ func (vm *VM) executeSetGlobal() error { globalIndex := vm.currentFrame().ReadUint16() ref := vm.pop() - if immutable, ok := ref.(object.Immutable); ok { - vm.state.Globals[globalIndex] = immutable.Clone() + if obj, ok := ref.(object.Copyable); ok { + vm.state.Globals[globalIndex] = obj.Copy() } else { vm.state.Globals[globalIndex] = ref } @@ -223,8 +223,8 @@ func (vm *VM) executeSetLocal() error { localIndex := vm.currentFrame().ReadUint8() ref := vm.pop() - if immutable, ok := ref.(object.Immutable); ok { - vm.stack[vm.currentFrame().basePointer+int(localIndex)] = immutable.Clone() + if obj, ok := ref.(object.Copyable); ok { + vm.stack[vm.currentFrame().basePointer+int(localIndex)] = obj.Copy() } else { vm.stack[vm.currentFrame().basePointer+int(localIndex)] = ref } @@ -241,12 +241,12 @@ func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { pair := object.HashPair{Key: key, Value: value} - hashKey, ok := key.(object.Hashable) + hashKey, ok := key.(object.Hasher) if !ok { return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) } - hashedPairs[hashKey.HashKey()] = pair + hashedPairs[hashKey.Hash()] = pair } return &object.Hash{Pairs: hashedPairs}, nil diff --git a/internal/vm/vm_test.go b/internal/vm/vm_test.go index b0b32b5..b4af1e8 100644 --- a/internal/vm/vm_test.go +++ b/internal/vm/vm_test.go @@ -379,15 +379,15 @@ func TestHashLiterals(t *testing.T) { { "{1: 2, 2: 3}", map[object.HashKey]int64{ - (object.Integer{Value: 1}).HashKey(): 2, - (object.Integer{Value: 2}).HashKey(): 3, + (object.Integer{Value: 1}).Hash(): 2, + (object.Integer{Value: 2}).Hash(): 3, }, }, { "{1 + 1: 2 * 2, 3 + 3: 4 * 4}", map[object.HashKey]int64{ - (object.Integer{Value: 2}).HashKey(): 4, - (object.Integer{Value: 6}).HashKey(): 16, + (object.Integer{Value: 2}).Hash(): 4, + (object.Integer{Value: 6}).Hash(): 16, }, }, } @@ -400,14 +400,14 @@ func TestHashMerging(t *testing.T) { { `{} + {"a": 1}`, map[object.HashKey]int64{ - (object.String{Value: "a"}).HashKey(): 1, + (object.String{Value: "a"}).Hash(): 1, }, }, { `{"a": 1} + {"b": 2}`, map[object.HashKey]int64{ - (object.String{Value: "a"}).HashKey(): 1, - (object.String{Value: "b"}).HashKey(): 2, + (object.String{Value: "a"}).Hash(): 1, + (object.String{Value: "b"}).Hash(): 2, }, }, } diff --git a/profile.go b/profile.go index d184810..f67510d 100644 --- a/profile.go +++ b/profile.go @@ -6,15 +6,14 @@ import ( "flag" "fmt" "log" + "monkey/internal/compiler" + "monkey/internal/object" + "monkey/internal/parser" + "monkey/internal/vm" "os" "runtime" "runtime/pprof" "strings" - - "git.mills.io/prologic/monkey-lang/internal/compiler" - "git.mills.io/prologic/monkey-lang/internal/object" - "git.mills.io/prologic/monkey-lang/internal/parser" - "git.mills.io/prologic/monkey-lang/internal/vm" ) const defaultCode = `