diff --git a/edit/api.go b/edit/api.go index 59bd076e..1d1cb1b5 100644 --- a/edit/api.go +++ b/edit/api.go @@ -8,9 +8,11 @@ import ( "os" "strings" "sync" + "unsafe" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" + "github.com/xiaq/persistent/hash" ) // This file implements types and functions for interactions with the @@ -41,6 +43,10 @@ func (bf *BuiltinFn) Equal(a interface{}) bool { return bf == a } +func (bf *BuiltinFn) Hash() uint32 { + return hash.Pointer(unsafe.Pointer(bf)) +} + // Repr returns the representation of a builtin function as a variable name. func (bf *BuiltinFn) Repr(int) string { return "$" + bf.name diff --git a/edit/arg_completers.go b/edit/arg_completers.go index 1b43a272..76aea7be 100644 --- a/edit/arg_completers.go +++ b/edit/arg_completers.go @@ -2,8 +2,10 @@ package edit import ( "errors" + "unsafe" "github.com/elves/elvish/eval" + "github.com/xiaq/persistent/hash" ) // For an overview of completion, see the comment in completers.go. @@ -123,6 +125,10 @@ func (bac *builtinArgCompleter) Equal(a interface{}) bool { return bac == a } +func (bac *builtinArgCompleter) Hash() uint32 { + return hash.Pointer(unsafe.Pointer(bac)) +} + func (bac *builtinArgCompleter) Repr(int) string { return "$edit:&" + bac.name } diff --git a/edit/binding.go b/edit/binding.go index dc9211dd..9dd7910f 100644 --- a/edit/binding.go +++ b/edit/binding.go @@ -4,6 +4,7 @@ import ( "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" + "github.com/xiaq/persistent/hash" ) func getBinding(mode string, k ui.Key) eval.CallableValue { @@ -45,6 +46,17 @@ func (bt BindingTable) Equal(a interface{}) bool { return true } +func (bt BindingTable) Hash() uint32 { + h := hash.DJBInit + for k, v := range bt.inner { + // TODO(xiaq): Use a more efficient implementation to derive a hash from + // ui.Key. + h = hash.DJBCombine(h, hash.String(k.String())) + h = hash.DJBCombine(h, v.Hash()) + } + return h +} + // Repr returns the representation of the binding table as if it were an // ordinary map. func (bt BindingTable) Repr(indent int) string { diff --git a/edit/candidate.go b/edit/candidate.go index d2fd2443..c702f156 100644 --- a/edit/candidate.go +++ b/edit/candidate.go @@ -8,6 +8,7 @@ import ( "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" + "github.com/xiaq/persistent/hash" ) type candidate struct { @@ -29,10 +30,11 @@ func (cs rawCandidates) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } func (cs rawCandidates) Less(i, j int) bool { return cs[i].text() < cs[j].text() } // plainCandidate is a minimal implementation of rawCandidate. -type plainCandidate string +type plainCandidate eval.String func (plainCandidate) Kind() string { return "string" } func (p plainCandidate) Equal(a interface{}) bool { return p == a } +func (p plainCandidate) Hash() uint32 { return hash.String(string(p)) } func (p plainCandidate) Repr(l int) string { return eval.String(p).Repr(l) } func (p plainCandidate) text() string { return string(p) } @@ -47,6 +49,7 @@ type noQuoteCandidate string func (noQuoteCandidate) Kind() string { return "string" } func (nq noQuoteCandidate) Equal(a interface{}) bool { return nq == a } +func (nq noQuoteCandidate) Hash() uint32 { return hash.String(string(nq)) } func (nq noQuoteCandidate) Repr(l int) string { return eval.String(nq).Repr(l) } func (nq noQuoteCandidate) text() string { return string(nq) } @@ -71,6 +74,15 @@ func (c *complexCandidate) Equal(a interface{}) bool { return ok && c.stem == rhs.stem && c.codeSuffix == rhs.codeSuffix && c.displaySuffix == rhs.displaySuffix && c.style.Eq(rhs.style) } +func (c *complexCandidate) Hash() uint32 { + h := hash.DJBInit + h = hash.DJBCombine(h, hash.String(c.stem)) + h = hash.DJBCombine(h, hash.String(c.codeSuffix)) + h = hash.DJBCombine(h, hash.String(c.displaySuffix)) + h = hash.DJBCombine(h, c.style.Hash()) + return h +} + func (c *complexCandidate) Repr(indent int) string { // TODO(xiaq): Pretty-print when indent >= 0 return fmt.Sprintf("(edit:complex-candidate %s &code-suffix=%s &display-suffix=%s style=%s)", diff --git a/edit/history_value.go b/edit/history_value.go index 26717a5d..78601764 100644 --- a/edit/history_value.go +++ b/edit/history_value.go @@ -25,6 +25,12 @@ func (hv History) Equal(a interface{}) bool { return ok } +func (hv History) Hash() uint32 { + // TODO(xiaq): Make a global registry of singleton hashes to avoid + // collision. + return 100 +} + func (hv History) Repr(int) string { return "$le:history" } diff --git a/edit/ui/styled.go b/edit/ui/styled.go index 1d8fb53d..a47bf7f8 100644 --- a/edit/ui/styled.go +++ b/edit/ui/styled.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/elves/elvish/parse" + "github.com/xiaq/persistent/hash" ) // Styled is a piece of text with style. @@ -72,6 +73,13 @@ func (s *Styled) Equal(a interface{}) bool { return s.Text == rhs.Text && s.Styles.Eq(rhs.Styles) } +func (s *Styled) Hash() uint32 { + h := hash.DJBInit + h = hash.DJBCombine(h, hash.String(s.Text)) + h = hash.DJBCombine(h, s.Styles.Hash()) + return h +} + func (s *Styled) String() string { return "\033[" + s.Styles.String() + "m" + s.Text + "\033[m" } @@ -94,6 +102,14 @@ func (ss Styles) Eq(rhs Styles) bool { return true } +func (ss Styles) Hash() uint32 { + h := hash.DJBInit + for _, s := range ss { + h = hash.DJBCombine(h, hash.String(s)) + } + return h +} + func JoinStyles(so Styles, st ...Styles) Styles { for _, v := range st { so = append(so, v...) diff --git a/eval/bool.go b/eval/bool.go index 73e9cd8e..79d61457 100644 --- a/eval/bool.go +++ b/eval/bool.go @@ -11,6 +11,13 @@ func (b Bool) Equal(rhs interface{}) bool { return b == rhs } +func (b Bool) Hash() uint32 { + if b { + return 1 + } + return 0 +} + func (b Bool) Repr(int) string { if b { return "$true" diff --git a/eval/builtin_fn.go b/eval/builtin_fn.go index ccd4b26b..2f60a80e 100644 --- a/eval/builtin_fn.go +++ b/eval/builtin_fn.go @@ -23,11 +23,13 @@ import ( "syscall" "time" "unicode/utf8" + "unsafe" "github.com/elves/elvish/parse" "github.com/elves/elvish/store/storedefs" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" + "github.com/xiaq/persistent/hash" ) var builtinFns []*BuiltinFn @@ -52,6 +54,10 @@ func (b *BuiltinFn) Equal(rhs interface{}) bool { return b == rhs } +func (b *BuiltinFn) Hash() uint32 { + return hash.Pointer(unsafe.Pointer(b)) +} + // Repr returns an opaque representation "". func (b *BuiltinFn) Repr(int) string { return "" diff --git a/eval/closure.go b/eval/closure.go index 56791080..488ea723 100644 --- a/eval/closure.go +++ b/eval/closure.go @@ -3,6 +3,9 @@ package eval import ( "errors" "fmt" + "unsafe" + + "github.com/xiaq/persistent/hash" ) // ErrArityMismatch is thrown by a closure when the number of arguments the user @@ -36,6 +39,10 @@ func (c *Closure) Equal(rhs interface{}) bool { return c == rhs } +func (c *Closure) Hash() uint32 { + return hash.Pointer(unsafe.Pointer(c)) +} + // Repr returns an opaque representation "". func (c *Closure) Repr(int) string { return fmt.Sprintf("", c) diff --git a/eval/env_path_list.go b/eval/env_path_list.go index 66e4fc79..2a2fea10 100644 --- a/eval/env_path_list.go +++ b/eval/env_path_list.go @@ -5,6 +5,8 @@ import ( "os" "strings" "sync" + + "github.com/xiaq/persistent/hash" ) // Errors @@ -70,6 +72,14 @@ func (epl *EnvPathList) Equal(a interface{}) bool { return epl == a || eqListLike(epl, a) } +func (epl *EnvPathList) Hash() uint32 { + h := hash.DJBInit + for _, p := range epl.get() { + h = hash.DJBCombine(h, hash.String(p)) + } + return h +} + // Repr returns the representation of an EnvPathList, as if it were an ordinary // list. func (epl *EnvPathList) Repr(indent int) string { diff --git a/eval/exception.go b/eval/exception.go index 807d208a..d5c8884b 100644 --- a/eval/exception.go +++ b/eval/exception.go @@ -6,9 +6,11 @@ import ( "strconv" "strings" "syscall" + "unsafe" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" + "github.com/xiaq/persistent/hash" ) // Exception represents an elvish exception. It is both a Value accessible to @@ -76,6 +78,10 @@ func (exc *Exception) Equal(rhs interface{}) bool { return exc == rhs } +func (exc *Exception) Hash() uint32 { + return hash.Pointer(unsafe.Pointer(exc)) +} + func (exc *Exception) Bool() bool { return exc.Cause == nil } diff --git a/eval/external_cmd.go b/eval/external_cmd.go index 7fad68cd..612de22d 100644 --- a/eval/external_cmd.go +++ b/eval/external_cmd.go @@ -8,6 +8,7 @@ import ( "github.com/elves/elvish/parse" "github.com/elves/elvish/util" + "github.com/xiaq/persistent/hash" ) // FdNil is a special impossible fd value used for "close fd" in @@ -32,6 +33,10 @@ func (e ExternalCmd) Equal(a interface{}) bool { return e == a } +func (e ExternalCmd) Hash() uint32 { + return hash.String(e.Name) +} + func (e ExternalCmd) Repr(int) string { return "" } diff --git a/eval/file.go b/eval/file.go index 519df525..704f4c82 100644 --- a/eval/file.go +++ b/eval/file.go @@ -5,6 +5,7 @@ import ( "os" "github.com/elves/elvish/parse" + "github.com/xiaq/persistent/hash" ) type File struct { @@ -21,6 +22,10 @@ func (f File) Equal(rhs interface{}) bool { return f == rhs } +func (f File) Hash() uint32 { + return hash.UIntPtr(f.inner.Fd()) +} + func (f File) Repr(int) string { return fmt.Sprintf("", parse.Quote(f.inner.Name()), f.inner) } diff --git a/eval/glob.go b/eval/glob.go index c95ae80c..83e6b17e 100644 --- a/eval/glob.go +++ b/eval/glob.go @@ -64,6 +64,11 @@ func (gp GlobPattern) Equal(a interface{}) bool { return reflect.DeepEqual(gp, a) } +func (gp GlobPattern) Hash() uint32 { + // GlobPattern is not a first-class value. + return 0 +} + func (gp GlobPattern) Repr(int) string { return fmt.Sprintf("", gp) } diff --git a/eval/list.go b/eval/list.go index 84753165..ea26789e 100644 --- a/eval/list.go +++ b/eval/list.go @@ -50,6 +50,10 @@ func (l List) Equal(rhs interface{}) bool { return eqListLike(l, rhs) } +func (l List) Hash() uint32 { + return hashListLike(l) +} + func (l List) Repr(indent int) string { var b ListReprBuilder b.Indent = indent diff --git a/eval/listlike.go b/eval/listlike.go index 603b5ca5..ac3d7cab 100644 --- a/eval/listlike.go +++ b/eval/listlike.go @@ -1,5 +1,7 @@ package eval +import "github.com/xiaq/persistent/hash" + type ListLike interface { Lener Iterable @@ -16,3 +18,12 @@ func eqListLike(lhs ListLike, r interface{}) bool { } return true } + +func hashListLike(l ListLike) uint32 { + h := hash.DJBInit + l.Iterate(func(v Value) bool { + // h = hash.DJBCombine(h, v.Hash()) + return true + }) + return h +} diff --git a/eval/map.go b/eval/map.go index 7f6f9024..811a323f 100644 --- a/eval/map.go +++ b/eval/map.go @@ -30,6 +30,10 @@ func (m Map) Equal(a interface{}) bool { return m == a || eqMapLike(m, a) } +func (m Map) Hash() uint32 { + return hashMapLike(m) +} + func (m Map) MarshalJSON() ([]byte, error) { // XXX Not the most efficient way. mm := map[string]Value{} diff --git a/eval/maplike.go b/eval/maplike.go index e4d9286c..708b7c34 100644 --- a/eval/maplike.go +++ b/eval/maplike.go @@ -1,5 +1,7 @@ package eval +import "github.com/xiaq/persistent/hash" + type MapLike interface { Lener IndexOneer @@ -26,3 +28,13 @@ func eqMapLike(lhs MapLike, a interface{}) bool { }) return eq } + +func hashMapLike(m MapLike) uint32 { + h := hash.DJBInit + m.IteratePair(func(k, v Value) bool { + // h = hash.DJBCombine(h, k.Hash()) + // h = hash.DJBCombine(h, v.Hash()) + return true + }) + return h +} diff --git a/eval/pipe.go b/eval/pipe.go index 9374aef3..41a92461 100644 --- a/eval/pipe.go +++ b/eval/pipe.go @@ -3,6 +3,8 @@ package eval import ( "fmt" "os" + + "github.com/xiaq/persistent/hash" ) type Pipe struct { @@ -19,6 +21,13 @@ func (p Pipe) Equal(rhs interface{}) bool { return p == rhs } +func (p Pipe) Hash() uint32 { + h := hash.DJBInit + h = hash.DJBCombine(h, hash.UIntPtr(p.r.Fd())) + h = hash.DJBCombine(h, hash.UIntPtr(p.w.Fd())) + return h +} + func (p Pipe) Repr(int) string { return fmt.Sprintf("", p.r.Fd(), p.w.Fd()) } diff --git a/eval/rat.go b/eval/rat.go index 268ef757..dddfb16e 100644 --- a/eval/rat.go +++ b/eval/rat.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "math/big" + + "github.com/xiaq/persistent/hash" ) var ErrOnlyStrOrRat = errors.New("only str or rat may be converted to rat") @@ -30,6 +32,11 @@ func (r Rat) Equal(a interface{}) bool { return r.b.Cmp(r2.b) == 0 } +func (r Rat) Hash() uint32 { + // TODO(xiaq): Use a more efficient implementation. + return hash.String(r.String()) +} + func (r Rat) Repr(int) string { return "(rat " + r.String() + ")" } diff --git a/eval/string.go b/eval/string.go index 94e9884e..945c2a55 100644 --- a/eval/string.go +++ b/eval/string.go @@ -4,6 +4,7 @@ import ( "unicode/utf8" "github.com/elves/elvish/parse" + "github.com/xiaq/persistent/hash" ) // String is just a string. @@ -26,6 +27,10 @@ func (s String) Equal(rhs interface{}) bool { return s == rhs } +func (s String) Hash() uint32 { + return hash.String(string(s)) +} + func (s String) String() string { return string(s) } diff --git a/eval/struct.go b/eval/struct.go index 642fdc20..b4e74cec 100644 --- a/eval/struct.go +++ b/eval/struct.go @@ -31,6 +31,10 @@ func (s *Struct) Equal(rhs interface{}) bool { return s == rhs || eqMapLike(s, rhs) } +func (s *Struct) Hash() uint32 { + return hashMapLike(s) +} + func (s *Struct) Repr(indent int) string { var builder MapReprBuilder builder.Indent = indent diff --git a/eval/value.go b/eval/value.go index e86ae125..bac20685 100644 --- a/eval/value.go +++ b/eval/value.go @@ -17,6 +17,7 @@ const ( type Value interface { Kinder Equaler + Hasher Reprer }