Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement dict to receive Object as key #119

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions py/dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,81 @@ func init() {
}
return nil, ExceptionNewf(KeyError, "%v", args[0])
}, 0, "gets(key, default) -> If there is a val corresponding to key, return val, otherwise default")

DictType.Dict["items"] = MustNewMethod("items", func(self Object, args Tuple) (Object, error) {
dict := self.(Dict)
o := make([]Object, 0, len(dict.keys))
for _, item := range dict.items {
if item[0] != nil {
o = append(o, Tuple{item[0], item[1]})
}
}
return NewIterator(o), nil
}, 0, "items() -> list of D's (key, value) pairs, as 2-tuples")

DictType.Dict["get"] = MustNewMethod("get", func(self Object, args Tuple) (Object, error) {
var length = len(args)
switch {
case length == 0:
return nil, ExceptionNewf(TypeError, "%s expected at least 1 arguments, got %d", "items()", length)
case length > 2:
return nil, ExceptionNewf(TypeError, "%s expected at most 2 arguments, got %d", "items()", length)
}
dict := self.(Dict)
if res, ok := dict.keys[args[0]]; ok {
return dict.items[res][1], nil
}

if length == 2 {
return args[1], nil
}
return None, nil
}, 0, "gets(key, default) -> If there is a val corresponding to key, return val, otherwise default")
}

// String to object dictionary
//
// Used for variables etc where the keys can only be strings
type StringDict map[string]Object

type Dict struct {
keys map[Object]int
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right representation alas...

Putting the Object as the key means that you are relying on how Go compares interfaces. It does this with a shallow compare. An interface is basically

  • pointer to type
  • pointer to data

so comparing two interfaces just compares the pointer to type and pointer to data - it doesn't compare the data.

This means that you could insert two objects eg py.String("hello") and py.String("hello") and these would both get inserted into the dictionary.

I think what we want as the key is

  • python type
  • hash of object

And the values need to be a []Object (because different objects can have the same hash).

Copy link
Contributor Author

@HyeockJinKim HyeockJinKim Oct 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no hash implementation, so I've got Object as key now.

We only receive hash values as key values. so I'm going to get int as the key.
What do you think about this way?

type Dict struct {
	keys  map[int]int  // int key

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe we should try the really simple version first, then we can make it more efficient later

type dictEntry struct {
    key Object
    value Object
}
type Dict struct {
    entries []dictEntry 
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ncw, to be nit-picky, the way py.String is currently implemented, I believe this would work.
see:
https://play.golang.org/p/bDvOEmkim6r

(ie: py.String just contains values and no pointers.)

but ok with going with the KISS version first.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! The problem comes when the py.Object interface is satisfied with a pointer: https://play.golang.org/p/vf4dGmh0Gb4

items [][2]Object // key-value pair
}

// Type of this StringDict object
func (o StringDict) Type() *Type {
return StringDictType
}

// Type of this Dict object
func (o Dict) Type() *Type {
return DictType
}

// Make a new dictionary
func NewStringDict() StringDict {
return make(StringDict)
}

// Make a new dictionary
func NewDict() *Dict {
return &Dict{}
}

// Make a new dictionary with reservation for n entries
func NewStringDictSized(n int) StringDict {
return make(StringDict, n)
}

// Make a new dictionary with reservation for n entries
func NewDictSized(n int) *Dict {
return &Dict{
keys: make(map[Object]int, n),
items: make([][2]Object, n),
}
}

// Checks that obj is exactly a dictionary and returns an error if not
func DictCheckExact(obj Object) (StringDict, error) {
dict, ok := obj.(StringDict)
Expand All @@ -91,12 +144,27 @@ func DictCheckExact(obj Object) (StringDict, error) {
return dict, nil
}

// Checks that obj is exactly a dictionary and returns an error if not
func _DictCheckExact(obj Object) (*Dict, error) {
dict, ok := obj.(Dict)
if !ok {
return nil, expectingDict
}
return &dict, nil
}

// Checks that obj is exactly a dictionary and returns an error if not
func DictCheck(obj Object) (StringDict, error) {
// FIXME should be checking subclasses
return DictCheckExact(obj)
}

// Checks that obj is exactly a dictionary and returns an error if not
func _DictCheck(obj Object) (*Dict, error) {
// FIXME should be checking subclasses
return _DictCheckExact(obj)
}

// Copy a dictionary
func (d StringDict) Copy() StringDict {
e := make(StringDict, len(d))
Expand All @@ -106,10 +174,26 @@ func (d StringDict) Copy() StringDict {
return e
}

// Copy a dictionary
func (d Dict) Copy() *Dict {
e := NewDictSized(len(d.keys))
for k, v := range d.keys {
e.keys[k] = v
e.items[v][0] = d.items[v][0]
e.items[v][1] = d.items[v][1]
}
return e
}

func (a StringDict) M__str__() (Object, error) {
return a.M__repr__()
}


func (a Dict) M__str__() (Object, error) {
return a.M__repr__()
}

func (a StringDict) M__repr__() (Object, error) {
var out bytes.Buffer
out.WriteRune('{')
Expand All @@ -135,6 +219,33 @@ func (a StringDict) M__repr__() (Object, error) {
return String(out.String()), nil
}

func (a Dict) M__repr__() (Object, error) {
var out bytes.Buffer
out.WriteRune('{')
spacer := false
for _, item := range a.items {
if item[0] != nil {
if spacer {
out.WriteString(", ")
}
keyStr, err := ReprAsString(item[0])
if err != nil {
return nil, err
}
valueStr, err := ReprAsString(item[1])
if err != nil {
return nil, err
}
out.WriteString(keyStr)
out.WriteString(": ")
out.WriteString(valueStr)
spacer = true
}
}
out.WriteRune('}')
return String(out.String()), nil
}

// Returns a list of keys from the dict
func (d StringDict) M__iter__() (Object, error) {
o := make([]Object, 0, len(d))
Expand All @@ -144,6 +255,17 @@ func (d StringDict) M__iter__() (Object, error) {
return NewIterator(o), nil
}

// Returns a list of keys from the dict
func (d Dict) M__iter__() (Object, error) {
o := make([]Object, 0, len(d.keys))
for _, item := range d.items {
if item[0] != nil {
o = append(o, item[0])
}
}
return NewIterator(o), nil
}

func (d StringDict) M__getitem__(key Object) (Object, error) {
str, ok := key.(String)
if ok {
Expand All @@ -155,6 +277,15 @@ func (d StringDict) M__getitem__(key Object) (Object, error) {
return nil, ExceptionNewf(KeyError, "%v", key)
}

func (d Dict) M__getitem__(key Object) (Object, error) {
// FIXME should be checking hash of Object
res, ok := d.keys[key]
if ok {
return d.items[res][1], nil
}
return nil, ExceptionNewf(KeyError, "%v", key)
}

func (d StringDict) M__setitem__(key, value Object) (Object, error) {
str, ok := key.(String)
if !ok {
Expand All @@ -164,6 +295,13 @@ func (d StringDict) M__setitem__(key, value Object) (Object, error) {
return None, nil
}

func (d Dict) M__setitem__(key, value Object) (Object, error) {
// FIXME should be checking hash of Object
d.keys[key] = len(d.items)
d.items = append(d.items, [2]Object{key, value})
return None, nil
}

func (a StringDict) M__eq__(other Object) (Object, error) {
b, ok := other.(StringDict)
if !ok {
Expand All @@ -188,6 +326,41 @@ func (a StringDict) M__eq__(other Object) (Object, error) {
return True, nil
}

func (a Dict) M__eq__(other Object) (Object, error) {
b, ok := other.(Dict)
if !ok {
return NotImplemented, nil
}
if len(a.keys) != len(b.keys) {
return False, nil
}
for k, ai := range a.keys {
// FIXME should be checking hash of Object
bi, ok := b.keys[k]
if !ok || len(a.keys) < ai || len(b.keys) < bi {
return False, nil
}
aitem := a.items[ai]
bitem := b.items[bi]

res, err := Eq(aitem[0], bitem[0])
if err != nil {
return nil, err
}
if res == False {
return False, nil
}
res, err = Eq(aitem[1], bitem[1])
if err != nil {
return nil, err
}
if res == False {
return False, nil
}
}
return True, nil
}

func (a StringDict) M__ne__(other Object) (Object, error) {
res, err := a.M__eq__(other)
if err != nil {
Expand All @@ -202,6 +375,10 @@ func (a StringDict) M__ne__(other Object) (Object, error) {
return True, nil
}

func (a Dict) M__ne__(other Object) (Object, error) {
return notEq(a.M__eq__(other))
}

func (a StringDict) M__contains__(other Object) (Object, error) {
key, ok := other.(String)
if !ok {
Expand All @@ -213,3 +390,11 @@ func (a StringDict) M__contains__(other Object) (Object, error) {
}
return False, nil
}

func (a Dict) M__contains__(other Object) (Object, error) {
// FIXME should be checking hash of Object
if i, ok := a.keys[other]; ok {
return Eq(other, a.items[i][0])
}
return False, nil
}