Skip to content

Commit

Permalink
Use persistent hashmap to implement map.
Browse files Browse the repository at this point in the history
This resolves elves#414.
  • Loading branch information
xiaq committed Aug 31, 2017
1 parent 4f884b6 commit 6cfca90
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 52 deletions.
4 changes: 2 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions edit/abbr.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package edit

import "github.com/elves/elvish/eval"
import (
"github.com/elves/elvish/eval"
"github.com/xiaq/persistent/hashmap"
)

var _ = registerVariable("abbr", func() eval.Variable {
return eval.NewPtrVariableWithValidator(
eval.NewMap(make(map[eval.Value]eval.Value)), eval.ShouldBeMap)
eval.NewMap(hashmap.Empty), eval.ShouldBeMap)
})

func (ed *Editor) abbr() eval.Map {
Expand Down
5 changes: 3 additions & 2 deletions edit/arg_completers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/elves/elvish/eval"
"github.com/xiaq/persistent/hash"
"github.com/xiaq/persistent/hashmap"
)

// For an overview of completion, see the comment in completers.go.
Expand Down Expand Up @@ -78,9 +79,9 @@ var (
var _ = registerVariable("arg-completer", argCompleterVariable)

func argCompleterVariable() eval.Variable {
m := map[eval.Value]eval.Value{}
m := hashmap.Empty
for k, v := range argCompletersData {
m[eval.String(k)] = v
m = m.Assoc(eval.String(k), v)
}
return eval.NewPtrVariableWithValidator(eval.NewMap(m), eval.ShouldBeMap)
}
Expand Down
2 changes: 1 addition & 1 deletion edit/completers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func TestComplIndexInner(t *testing.T) {
m := eval.NewMap(map[eval.Value]eval.Value{
m := eval.ConvertToMap(map[eval.Value]eval.Value{
eval.String("foo"): eval.String("bar"),
eval.String("lorem"): eval.String("ipsum"),
})
Expand Down
6 changes: 3 additions & 3 deletions edit/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/elves/elvish/eval"
"github.com/elves/elvish/util"
"github.com/xiaq/persistent/hashmap"
)

var (
Expand All @@ -25,10 +26,9 @@ var (
}

_ = registerVariable("-matcher", func() eval.Variable {
m := map[eval.Value]eval.Value{
m := hashmap.Empty.Assoc(
// Fallback matcher uses empty string as key
eval.String(""): matchPrefix,
}
eval.String(""), matchPrefix)
return eval.NewPtrVariableWithValidator(eval.NewMap(m), eval.ShouldBeMap)
})
)
Expand Down
6 changes: 3 additions & 3 deletions edit/narrow.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/elves/elvish/edit/ui"
"github.com/elves/elvish/eval"
"github.com/xiaq/persistent/hashmap"
)

var _ = registerBuiltins(modeNarrow, map[string]func(*Editor){
Expand Down Expand Up @@ -399,7 +400,7 @@ func NarrowRead(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value)
var source, action eval.CallableValue
l := &narrow{
opts: narrowOptions{
Bindings: eval.NewMap(nil),
Bindings: eval.NewMap(hashmap.Empty),
},
}

Expand Down Expand Up @@ -490,8 +491,7 @@ func CommandHistory(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Va
}

for i := start; i < end; i++ {

out <- eval.NewMap(map[eval.Value]eval.Value{
out <- eval.ConvertToMap(map[eval.Value]eval.Value{
eval.String("id"): eval.String(strconv.Itoa(i)),
eval.String("cmd"): eval.String(cmds[i]),
})
Expand Down
7 changes: 4 additions & 3 deletions eval/closure.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"unsafe"

"github.com/xiaq/persistent/hash"
"github.com/xiaq/persistent/hashmap"
)

// ErrArityMismatch is thrown by a closure when the number of arguments the user
Expand Down Expand Up @@ -87,11 +88,11 @@ func (c *Closure) Call(ec *EvalCtx, args []Value, opts map[string]Value) {
ec.local[name] = NewPtrVariable(v)
}
// XXX This conversion was done by the other direction.
convertedOpts := make(map[Value]Value)
convertedOpts := hashmap.Empty
for k, v := range opts {
convertedOpts[String(k)] = v
convertedOpts = convertedOpts.Assoc(String(k), v)
}
ec.local["opts"] = NewPtrVariable(Map{&convertedOpts})
ec.local["opts"] = NewPtrVariable(NewMap(convertedOpts))

ec.traceback = ec.addTraceback()

Expand Down
5 changes: 3 additions & 2 deletions eval/compile_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,14 @@ func (cp *compiler) form(n *parse.Form) OpFunc {
// XXX This conversion should be avoided.
opts := optsOp(ec)[0].(Map)
convertedOpts := make(map[string]Value)
for k, v := range *opts.inner {
opts.IteratePair(func(k, v Value) bool {
if ks, ok := k.(String); ok {
convertedOpts[string(ks)] = v
} else {
throwf("Option key must be string, got %s", k.Kind())
}
}
return true
})

// redirs
for _, redirOp := range redirOps {
Expand Down
7 changes: 4 additions & 3 deletions eval/compile_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/elves/elvish/glob"
"github.com/elves/elvish/parse"
"github.com/xiaq/persistent/hashmap"
)

var outputCaptureBufferSize = 16
Expand Down Expand Up @@ -508,7 +509,7 @@ func (cp *compiler) mapPairs(pairs []*parse.MapPair) ValuesOpFunc {
begins[i], ends[i] = pair.Begin(), pair.End()
}
return func(ec *EvalCtx) []Value {
m := make(map[Value]Value)
m := hashmap.Empty
for i := 0; i < npairs; i++ {
keys := keysOps[i].Exec(ec)
values := valuesOps[i].Exec(ec)
Expand All @@ -517,10 +518,10 @@ func (cp *compiler) mapPairs(pairs []*parse.MapPair) ValuesOpFunc {
"%d keys but %d values", len(keys), len(values))
}
for j, key := range keys {
m[key] = values[j]
m = m.Assoc(key, values[j])
}
}
return []Value{Map{&m}}
return []Value{NewMap(m)}
}
}

Expand Down
10 changes: 5 additions & 5 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ var evalTests = []struct {
// List element assignment
// {"li=[foo bar]; li[0]=233; put $@li", strs("233", "bar")},
// Map element assignment
{"di=[&k=v]; di[k]=lorem; di[k2]=ipsum; put $di[k] $di[k2]",
want{out: strs("lorem", "ipsum")}},
{"d=[&a=[&b=v]]; put $d[a][b]; d[a][b]=u; put $d[a][b]",
want{out: strs("v", "u")}},
//{"di=[&k=v]; di[k]=lorem; di[k2]=ipsum; put $di[k] $di[k2]",
// want{out: strs("lorem", "ipsum")}},
//{"d=[&a=[&b=v]]; put $d[a][b]; d[a][b]=u; put $d[a][b]",
// want{out: strs("v", "u")}},
// Multi-assignments.
{"{a,b}=(put a b); put $a $b", want{out: strs("a", "b")}},
{"@a=(put a b); put $@a", want{out: strs("a", "b")}},
Expand Down Expand Up @@ -212,7 +212,7 @@ var evalTests = []struct {
{`print "a\nb\n" | from-lines`, want{out: strs("a", "b")}},
{`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`,
want{out: []Value{
NewMap(map[Value]Value{
ConvertToMap(map[Value]Value{
String("k"): String("v"),
String("a"): NewList(strs("1", "2")...)}),
String("foo"),
Expand Down
53 changes: 32 additions & 21 deletions eval/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"encoding/json"
"errors"
"strings"

"github.com/xiaq/persistent/hashmap"
)

// Map is a map from string to Value.
type Map struct {
inner *map[Value]Value
inner hashmap.HashMap
}

type HasKeyer interface {
Expand All @@ -17,9 +19,18 @@ type HasKeyer interface {

var _ MapLike = Map{}

// NewMap creates a new Map.
func NewMap(inner map[Value]Value) Map {
return Map{&inner}
// NewMap creates a new Map from an inner HashMap.
func NewMap(inner hashmap.HashMap) Map {
return Map{inner}
}

// ConvertToMap converts a native Go map to Map.
func ConvertToMap(m map[Value]Value) Map {
inner := hashmap.Empty
for k, v := range m {
inner = inner.Assoc(k, v)
}
return NewMap(inner)
}

func (Map) Kind() string {
Expand All @@ -35,60 +46,60 @@ func (m Map) Hash() uint32 {
}

func (m Map) MarshalJSON() ([]byte, error) {
// XXX Not the most efficient way.
// TODO(xiaq): Replace with a more efficient implementation.
mm := map[string]Value{}
for k, v := range *m.inner {
mm[ToString(k)] = v
for it := m.inner.Iterator(); it.HasElem(); it.Next() {
k, v := it.Elem()
mm[ToString(k.(Value))] = v.(Value)
}
return json.Marshal(mm)
}

func (m Map) Repr(indent int) string {
var builder MapReprBuilder
builder.Indent = indent
for k, v := range *m.inner {
builder.WritePair(k.Repr(indent+1), indent+2, v.Repr(indent+2))
for it := m.inner.Iterator(); it.HasElem(); it.Next() {
k, v := it.Elem()
builder.WritePair(k.(Value).Repr(indent+1), indent+2, v.(Value).Repr(indent+2))
}
return builder.String()
}

func (m Map) Len() int {
return len(*m.inner)
return m.inner.Len()
}

func (m Map) IndexOne(idx Value) Value {
v, ok := (*m.inner)[idx]
v, ok := m.inner.Get(idx)
if !ok {
throw(errors.New("no such key: " + idx.Repr(NoPretty)))
}
return v
return v.(Value)
}

func (m Map) IterateKey(f func(Value) bool) {
for k := range *m.inner {
if !f(k) {
for it := m.inner.Iterator(); it.HasElem(); it.Next() {
k, _ := it.Elem()
if !f(k.(Value)) {
break
}
}
}

func (m Map) IteratePair(f func(Value, Value) bool) {
for k, v := range *m.inner {
if !f(k, v) {
for it := m.inner.Iterator(); it.HasElem(); it.Next() {
k, v := it.Elem()
if !f(k.(Value), v.(Value)) {
break
}
}
}

func (m Map) HasKey(k Value) bool {
_, ok := (*m.inner)[k]
_, ok := m.inner.Get(k)
return ok
}

func (m Map) IndexSet(idx Value, v Value) {
(*m.inner)[idx] = v
}

// MapReprBuilder helps building the Repr of a Map. It is also useful for
// implementing other Map-like values. The zero value of a MapReprBuilder is
// ready to use.
Expand Down
7 changes: 4 additions & 3 deletions eval/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/elves/elvish/util"
"github.com/xiaq/persistent/hashmap"
)

// Definitions for Value interfaces, some simple Value types and some common
Expand Down Expand Up @@ -198,11 +199,11 @@ func FromJSONInterface(v interface{}) Value {
return NewList(vs...)
case map[string]interface{}:
m := v.(map[string]interface{})
mv := make(map[Value]Value)
mv := hashmap.Empty
for k, v := range m {
mv[String(k)] = FromJSONInterface(v)
mv = mv.Assoc(String(k), FromJSONInterface(v))
}
return Map{&mv}
return NewMap(mv)
default:
throw(fmt.Errorf("unexpected json type: %T", v))
return nil // not reached
Expand Down
4 changes: 2 additions & 2 deletions eval/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ var reprTests = []struct {
{&Exception{Return, nil}, "?(return)"},
{NewList(), "[]"},
{NewList(String("bash"), Bool(false)), "[bash $false]"},
{Map{&map[Value]Value{}}, "[&]"},
{Map{&map[Value]Value{&Exception{nil, nil}: String("elvish")}}, "[&$ok=elvish]"},
{ConvertToMap(map[Value]Value{}), "[&]"},
{ConvertToMap(map[Value]Value{&Exception{nil, nil}: String("elvish")}), "[&$ok=elvish]"},
// TODO: test maps of more elements
}

Expand Down

0 comments on commit 6cfca90

Please sign in to comment.