github.com/elves/elvish@v0.15.0/pkg/eval/vars/element.go (about) 1 package vars 2 3 import ( 4 "github.com/elves/elvish/pkg/eval/vals" 5 ) 6 7 type elem struct { 8 variable Var 9 assocers []interface{} 10 indices []interface{} 11 setValue interface{} 12 } 13 14 func (ev *elem) Set(v0 interface{}) error { 15 var err error 16 v := v0 17 // Evaluate the actual new value from inside out. See comments in 18 // MakeElement for how element assignment works. 19 for i := len(ev.assocers) - 1; i >= 0; i-- { 20 v, err = vals.Assoc(ev.assocers[i], ev.indices[i], v) 21 if err != nil { 22 return err 23 } 24 } 25 err = ev.variable.Set(v) 26 // TODO(xiaq): Remember the set value for use in Get. 27 ev.setValue = v0 28 return err 29 } 30 31 func (ev *elem) Get() interface{} { 32 // TODO(xiaq): This is only called from fixNilVariables. We don't want to 33 // waste time accessing the variable, so we simply return the value that was 34 // set. 35 return ev.setValue 36 } 37 38 // MakeElement returns a variable, that when set, simulates the mutation of an 39 // element. 40 func MakeElement(v Var, indices []interface{}) (Var, error) { 41 // Assignment of indexed variables actually assigns the variable, with 42 // the right hand being a nested series of Assocs. As the simplest 43 // example, `a[0] = x` is equivalent to `a = (assoc $a 0 x)`. A more 44 // complex example is that `a[0][1][2] = x` is equivalent to 45 // `a = (assoc $a 0 (assoc $a[0] 1 (assoc $a[0][1] 2 x)))`. 46 // Note that in each assoc form, the first two arguments can be 47 // determined now, while the last argument is only known when the 48 // right-hand-side is known. So here we evaluate the first two arguments 49 // of each assoc form and put them in two slices, assocers and indices. 50 // In the previous example, the two slices will contain: 51 // 52 // assocers: $a $a[0] $a[0][1] 53 // indices: 0 1 2 54 // 55 // When the right-hand side of the assignment becomes available, the new 56 // value for $a is evaluated by doing Assoc from inside out. 57 assocers := make([]interface{}, len(indices)) 58 varValue := v.Get() 59 assocers[0] = varValue 60 for i, index := range indices[:len(indices)-1] { 61 lastAssocer := assocers[i] 62 v, err := vals.Index(lastAssocer, index) 63 if err != nil { 64 return nil, err 65 } 66 assocers[i+1] = v 67 } 68 return &elem{v, assocers, indices, nil}, nil 69 } 70 71 // DelElement deletes an element. It uses a similar process to MakeElement, 72 // except that the last level of container needs to be Dissoc-able instead of 73 // Assoc-able. 74 func DelElement(variable Var, indices []interface{}) error { 75 var err error 76 // In "del a[0][1][2]", 77 // 78 // indices: 0 1 2 79 // assocers: $a $a[0] 80 // dissocer: $a[0][1] 81 assocers := make([]interface{}, len(indices)-1) 82 container := variable.Get() 83 for i, index := range indices[:len(indices)-1] { 84 assocers[i] = container 85 86 var err error 87 container, err = vals.Index(container, index) 88 if err != nil { 89 return err 90 } 91 } 92 93 v := vals.Dissoc(container, indices[len(indices)-1]) 94 if v == nil { 95 return elemErr{len(indices), "value does not support element removal"} 96 } 97 98 for i := len(assocers) - 1; i >= 0; i-- { 99 v, err = vals.Assoc(assocers[i], indices[i], v) 100 if err != nil { 101 return err 102 } 103 } 104 return variable.Set(v) 105 } 106 107 type elemErr struct { 108 level int 109 msg string 110 } 111 112 func (err elemErr) Error() string { 113 return err.msg 114 } 115 116 // HeadOfElement gets the underlying head variable of an element variable, or 117 // nil if the argument is not an element variable. 118 func HeadOfElement(v Var) Var { 119 if ev, ok := v.(*elem); ok { 120 return ev.variable 121 } 122 return nil 123 } 124 125 // ElementErrorLevel returns the level of an error returned by MakeElement or 126 // DelElement. Level 0 represents that the error is about the variable itself. 127 // If the argument was not returned from MakeVariable, -1 is returned. 128 func ElementErrorLevel(err error) int { 129 if err, ok := err.(elemErr); ok { 130 return err.level 131 } 132 return -1 133 }