refactor objects
Some checks failed
Publish Image / publish (push) Waiting to run
Test / build (push) Waiting to run
Build / build (push) Has been cancelled

This commit is contained in:
2024-04-01 17:34:10 -04:00
parent 803f330e82
commit 99f7553d67
15 changed files with 101 additions and 94 deletions

View File

@@ -14,8 +14,8 @@ func HashOf(args ...object.Object) object.Object {
return newError(err.Error()) return newError(err.Error())
} }
if hash, ok := args[0].(object.Hashable); ok { if hash, ok := args[0].(object.Hasher); ok {
return object.Integer{Value: int64(hash.HashKey().Value)} return object.Integer{Value: int64(hash.Hash().Value)}
} }
return newError("TypeError: hash() expected argument #1 to be hashable") return newError("TypeError: hash() expected argument #1 to be hashable")

View File

@@ -61,8 +61,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
} }
if ident, ok := node.Left.(*ast.Identifier); ok { if ident, ok := node.Left.(*ast.Identifier); ok {
if immutable, ok := value.(object.Immutable); ok { if obj, ok := value.(object.Copyable); ok {
env.Set(ident.Value, immutable.Clone()) env.Set(ident.Value, obj.Copy())
} else { } else {
env.Set(ident.Value, value) env.Set(ident.Value, value)
} }
@@ -105,8 +105,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
if isError(key) { if isError(key) {
return key return key
} }
if hashKey, ok := key.(object.Hashable); ok { if hashKey, ok := key.(object.Hasher); ok {
hashed := hashKey.HashKey() hashed := hashKey.Hash()
hash.Pairs[hashed] = object.HashPair{Key: key, Value: value} hash.Pairs[hashed] = object.HashPair{Key: key, Value: value}
} else { } else {
return newError("cannot index hash with %T", key) 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 { func evalHashIndexExpression(hash, index object.Object) object.Object {
hashObject := hash.(*object.Hash) hashObject := hash.(*object.Hash)
key, ok := index.(object.Hashable) key, ok := index.(object.Hasher)
if !ok { if !ok {
return newError("unusable as hash key: %s", index.Type()) return newError("unusable as hash key: %s", index.Type())
} }
pair, ok := hashObject.Pairs[key.HashKey()] pair, ok := hashObject.Pairs[key.Hash()]
if !ok { if !ok {
return NULL return NULL
} }
@@ -664,7 +664,7 @@ func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Obje
return key return key
} }
hashKey, ok := key.(object.Hashable) hashKey, ok := key.(object.Hasher)
if !ok { if !ok {
return newError("unusable as hash key: %s", key.Type()) 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 return value
} }
hashed := hashKey.HashKey() hashed := hashKey.Hash()
pairs[hashed] = object.HashPair{ pairs[hashed] = object.HashPair{
Key: key, Key: key,
Value: value, Value: value,

View File

@@ -640,12 +640,12 @@ func TestHashLiterals(t *testing.T) {
} }
expected := map[object.HashKey]int64{ expected := map[object.HashKey]int64{
(object.String{Value: "one"}).HashKey(): 1, (object.String{Value: "one"}).Hash(): 1,
(object.String{Value: "two"}).HashKey(): 2, (object.String{Value: "two"}).Hash(): 2,
(object.String{Value: "three"}).HashKey(): 3, (object.String{Value: "three"}).Hash(): 3,
(object.Integer{Value: 4}).HashKey(): 4, (object.Integer{Value: 4}).Hash(): 4,
TRUE.HashKey(): 5, TRUE.Hash(): 5,
FALSE.HashKey(): 6, FALSE.Hash(): 6,
} }
if len(result.Pairs) != len(expected) { if len(result.Pairs) != len(expected) {
@@ -671,8 +671,8 @@ func TestHashMerging(t *testing.T) {
} }
expected := map[object.HashKey]int64{ expected := map[object.HashKey]int64{
(object.String{Value: "a"}).HashKey(): 1, (object.String{Value: "a"}).Hash(): 1,
(object.String{Value: "b"}).HashKey(): 2, (object.String{Value: "b"}).Hash(): 2,
} }
if len(result.Pairs) != len(expected) { if len(result.Pairs) != len(expected) {

View File

@@ -32,10 +32,24 @@ func (b Boolean) Inspect() string {
return fmt.Sprintf("%t", b.Value) 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} 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 { func (b Boolean) String() string {
return b.Inspect() return b.Inspect()
} }

View File

@@ -4,24 +4,31 @@ import "unicode"
func NewEnclosedEnvironment(outer *Environment) *Environment { func NewEnclosedEnvironment(outer *Environment) *Environment {
env := NewEnvironment() env := NewEnvironment()
env.outer = outer env.parent = outer
return env return env
} }
func NewEnvironment() *Environment { func NewEnvironment() *Environment {
s := make(map[string]Object) s := make(map[string]Object)
return &Environment{store: s, outer: nil} return &Environment{store: s, parent: nil}
} }
type Environment struct { type Environment struct {
store map[string]Object store map[string]Object
outer *Environment 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) { func (e *Environment) Get(name string) (Object, bool) {
obj, ok := e.store[name] obj, ok := e.store[name]
if !ok && e.outer != nil { if !ok && e.parent != nil {
obj, ok = e.outer.Get(name) obj, ok = e.parent.Get(name)
} }
return obj, ok return obj, ok
} }
@@ -40,7 +47,7 @@ func (e *Environment) ExportedHash() *Hash {
for k, v := range e.store { for k, v := range e.store {
if unicode.IsUpper(rune(k[0])) { if unicode.IsUpper(rune(k[0])) {
s := String{Value: k} s := String{Value: k}
pairs[s.HashKey()] = HashPair{Key: s, Value: v} pairs[s.Hash()] = HashPair{Key: s, Value: v}
} }
} }
return &Hash{Pairs: pairs} return &Hash{Pairs: pairs}

View File

@@ -18,7 +18,8 @@ func (e Error) Inspect() string {
return "Error: " + e.Message return "Error: " + e.Message
} }
func (e Error) Clone() Object { // Copy implements the Copyable interface
func (e Error) Copy() Object {
return Error{Message: e.Message} return Error{Message: e.Message}
} }

View File

@@ -3,7 +3,6 @@ package object
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"hash/fnv"
"strings" "strings"
) )
@@ -12,29 +11,6 @@ type HashKey struct {
Value uint64 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 { type HashPair struct {
Key Object Key Object
Value Object Value Object
@@ -91,12 +67,12 @@ func (h *Hash) Add(other Object) (Object, error) {
} }
func (h *Hash) Get(index Object) (Object, error) { func (h *Hash) Get(index Object) (Object, error) {
key, ok := index.(Hashable) key, ok := index.(Hasher)
if !ok { if !ok {
return nil, fmt.Errorf("invalid hash key %s", index.Type()) return nil, fmt.Errorf("invalid hash key %s", index.Type())
} }
pair, found := h.Pairs[key.HashKey()] pair, found := h.Pairs[key.Hash()]
if !found { if !found {
return Null{}, nil return Null{}, nil
} }
@@ -105,12 +81,12 @@ func (h *Hash) Get(index Object) (Object, error) {
} }
func (h *Hash) Set(index, other Object) error { func (h *Hash) Set(index, other Object) error {
key, ok := index.(Hashable) key, ok := index.(Hasher)
if !ok { if !ok {
return fmt.Errorf("invalid hash key %s", index.Type()) 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 return nil
} }
@@ -122,8 +98,8 @@ func (h *Hash) Compare(other Object) int {
} }
for _, pair := range h.Pairs { for _, pair := range h.Pairs {
left := pair.Value left := pair.Value
hashed := left.(Hashable) hashed := left.(Hasher)
right, ok := obj.Pairs[hashed.HashKey()] right, ok := obj.Pairs[hashed.Hash()]
if !ok { if !ok {
return -1 return -1
} }

View File

@@ -18,10 +18,15 @@ func (i Integer) Inspect() string {
return fmt.Sprintf("%d", i.Value) return fmt.Sprintf("%d", i.Value)
} }
func (i Integer) Clone() Object { func (i Integer) Copy() Object {
return Integer{Value: i.Value} 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 { func (i Integer) String() string {
return i.Inspect() return i.Inspect()
} }

View File

@@ -25,12 +25,12 @@ func (m Module) Inspect() string {
} }
func (m Module) Get(index Object) (Object, error) { func (m Module) Get(index Object) (Object, error) {
key, ok := index.(Hashable) key, ok := index.(Hasher)
if !ok { if !ok {
return nil, fmt.Errorf("invalid module attribute %s", index.Type()) 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 { if !found {
return Null{}, nil return Null{}, nil
} }

View File

@@ -54,17 +54,14 @@ func (t Type) String() string {
} }
} }
// Comparable is the interface for comparing two Object and their underlying // Comparable is the interface for objects to implement suitable comparisons
// values. It is the responsibility of the caller (left) to check for types.
// Returns `true` iif the types and values are identical, `false` otherwise.
type Comparable interface { type Comparable interface {
Compare(other Object) int Compare(other Object) int
} }
// Immutable is the interface for all immutable objects which must implement // Copyable is the interface for creating copies of objects
// the Clone() method used by binding names to values. type Copyable interface {
type Immutable interface { Copy() Object
Clone() Object
} }
// Object represents a value and implementations are expected to implement // Object represents a value and implementations are expected to implement
@@ -76,10 +73,9 @@ type Object interface {
Inspect() string Inspect() string
} }
// Hashable is the interface for all hashable objects which must implement // Hasher is the interface for objects to provide suitable hash keys
// the HashKey() method which returns a HashKey result. type Hasher interface {
type Hashable interface { Hash() HashKey
HashKey() HashKey
} }
// BuiltinFunction represents the builtin function type // BuiltinFunction represents the builtin function type

View File

@@ -8,15 +8,15 @@ func TestStringHashKey(t *testing.T) {
diff1 := String{Value: "My name is johnny"} diff1 := String{Value: "My name is johnny"}
diff2 := 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") 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") 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") t.Errorf("string with different content have same hash keys")
} }
} }

View File

@@ -2,6 +2,7 @@ package object
import ( import (
"fmt" "fmt"
"hash/fnv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
@@ -26,10 +27,18 @@ func (s String) Inspect() string {
return fmt.Sprintf("%#v", s.Value) return fmt.Sprintf("%#v", s.Value)
} }
func (s String) Clone() Object { func (s String) Copy() Object {
return String{Value: s.Value} 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 { func (s String) String() string {
return s.Value return s.Value
} }

View File

@@ -102,7 +102,7 @@ func (s *VMState) ExportedHash() *object.Hash {
if symbol.Scope == compiler.GlobalScope { if symbol.Scope == compiler.GlobalScope {
obj := s.Globals[symbol.Index] obj := s.Globals[symbol.Index]
s := object.String{Value: name} 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() globalIndex := vm.currentFrame().ReadUint16()
ref := vm.pop() ref := vm.pop()
if immutable, ok := ref.(object.Immutable); ok { if obj, ok := ref.(object.Copyable); ok {
vm.state.Globals[globalIndex] = immutable.Clone() vm.state.Globals[globalIndex] = obj.Copy()
} else { } else {
vm.state.Globals[globalIndex] = ref vm.state.Globals[globalIndex] = ref
} }
@@ -223,8 +223,8 @@ func (vm *VM) executeSetLocal() error {
localIndex := vm.currentFrame().ReadUint8() localIndex := vm.currentFrame().ReadUint8()
ref := vm.pop() ref := vm.pop()
if immutable, ok := ref.(object.Immutable); ok { if obj, ok := ref.(object.Copyable); ok {
vm.stack[vm.currentFrame().basePointer+int(localIndex)] = immutable.Clone() vm.stack[vm.currentFrame().basePointer+int(localIndex)] = obj.Copy()
} else { } else {
vm.stack[vm.currentFrame().basePointer+int(localIndex)] = ref 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} pair := object.HashPair{Key: key, Value: value}
hashKey, ok := key.(object.Hashable) hashKey, ok := key.(object.Hasher)
if !ok { if !ok {
return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) 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 return &object.Hash{Pairs: hashedPairs}, nil

View File

@@ -379,15 +379,15 @@ func TestHashLiterals(t *testing.T) {
{ {
"{1: 2, 2: 3}", "{1: 2, 2: 3}",
map[object.HashKey]int64{ map[object.HashKey]int64{
(object.Integer{Value: 1}).HashKey(): 2, (object.Integer{Value: 1}).Hash(): 2,
(object.Integer{Value: 2}).HashKey(): 3, (object.Integer{Value: 2}).Hash(): 3,
}, },
}, },
{ {
"{1 + 1: 2 * 2, 3 + 3: 4 * 4}", "{1 + 1: 2 * 2, 3 + 3: 4 * 4}",
map[object.HashKey]int64{ map[object.HashKey]int64{
(object.Integer{Value: 2}).HashKey(): 4, (object.Integer{Value: 2}).Hash(): 4,
(object.Integer{Value: 6}).HashKey(): 16, (object.Integer{Value: 6}).Hash(): 16,
}, },
}, },
} }
@@ -400,14 +400,14 @@ func TestHashMerging(t *testing.T) {
{ {
`{} + {"a": 1}`, `{} + {"a": 1}`,
map[object.HashKey]int64{ map[object.HashKey]int64{
(object.String{Value: "a"}).HashKey(): 1, (object.String{Value: "a"}).Hash(): 1,
}, },
}, },
{ {
`{"a": 1} + {"b": 2}`, `{"a": 1} + {"b": 2}`,
map[object.HashKey]int64{ map[object.HashKey]int64{
(object.String{Value: "a"}).HashKey(): 1, (object.String{Value: "a"}).Hash(): 1,
(object.String{Value: "b"}).HashKey(): 2, (object.String{Value: "b"}).Hash(): 2,
}, },
}, },
} }

View File

@@ -6,15 +6,14 @@ import (
"flag" "flag"
"fmt" "fmt"
"log" "log"
"monkey/internal/compiler"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/vm"
"os" "os"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"strings" "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 = ` const defaultCode = `