github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/compile_lvalue.go (about) 1 package eval 2 3 import ( 4 "errors" 5 6 "github.com/markusbkk/elvish/pkg/diag" 7 "github.com/markusbkk/elvish/pkg/eval/errs" 8 "github.com/markusbkk/elvish/pkg/eval/vals" 9 "github.com/markusbkk/elvish/pkg/eval/vars" 10 "github.com/markusbkk/elvish/pkg/parse" 11 ) 12 13 // Parsed group of lvalues. 14 type lvaluesGroup struct { 15 lvalues []lvalue 16 // Index of the rest variable within lvalues. If there is no rest variable, 17 // the index is -1. 18 rest int 19 } 20 21 // Parsed lvalue. 22 type lvalue struct { 23 diag.Ranging 24 ref *varRef 25 indexOps []valuesOp 26 ends []int 27 } 28 29 type lvalueFlag uint 30 31 const ( 32 setLValue lvalueFlag = 1 << iota 33 newLValue 34 ) 35 36 func (cp *compiler) parseCompoundLValues(ns []*parse.Compound, f lvalueFlag) lvaluesGroup { 37 g := lvaluesGroup{nil, -1} 38 for _, n := range ns { 39 if len(n.Indexings) != 1 { 40 cp.errorpf(n, "lvalue may not be composite expressions") 41 } 42 more := cp.parseIndexingLValue(n.Indexings[0], f) 43 if more.rest == -1 { 44 g.lvalues = append(g.lvalues, more.lvalues...) 45 } else if g.rest != -1 { 46 cp.errorpf(n, "at most one rest variable is allowed") 47 } else { 48 g.rest = len(g.lvalues) + more.rest 49 g.lvalues = append(g.lvalues, more.lvalues...) 50 } 51 } 52 return g 53 } 54 55 func (cp *compiler) parseIndexingLValue(n *parse.Indexing, f lvalueFlag) lvaluesGroup { 56 if n.Head.Type == parse.Braced { 57 // Braced list of lvalues may not have indices. 58 if len(n.Indices) > 0 { 59 cp.errorpf(n, "braced list may not have indices when used as lvalue") 60 } 61 return cp.parseCompoundLValues(n.Head.Braced, f) 62 } 63 // A basic lvalue. 64 if !parse.ValidLHSVariable(n.Head, true) { 65 cp.errorpf(n.Head, "lvalue must be valid literal variable names") 66 } 67 varUse := n.Head.Value 68 sigil, qname := SplitSigil(varUse) 69 70 var ref *varRef 71 if f&setLValue != 0 { 72 ref = resolveVarRef(cp, qname, n) 73 if ref != nil && len(ref.subNames) == 0 && ref.info.readOnly { 74 cp.errorpf(n, "variable $%s is read-only", qname) 75 } 76 } 77 if ref == nil { 78 if f&newLValue == 0 { 79 cp.errorpf(n, "cannot find variable $%s", qname) 80 } 81 if len(n.Indices) > 0 { 82 cp.errorpf(n, "name for new variable must not have indices") 83 } 84 segs := SplitQNameSegs(qname) 85 if len(segs) == 1 { 86 // Unqualified name - implicit local 87 name := segs[0] 88 ref = &varRef{localScope, 89 staticVarInfo{name, false, false}, cp.thisScope().add(name), nil} 90 } else { 91 cp.errorpf(n, "cannot create variable $%s; new variables can only be created in the local scope", qname) 92 } 93 } 94 95 ends := make([]int, len(n.Indices)+1) 96 ends[0] = n.Head.Range().To 97 for i, idx := range n.Indices { 98 ends[i+1] = idx.Range().To 99 } 100 lv := lvalue{n.Range(), ref, cp.arrayOps(n.Indices), ends} 101 restIndex := -1 102 if sigil == "@" { 103 restIndex = 0 104 } 105 // TODO: Support % (and other sigils?) if https://b.elv.sh/584 is implemented for map explosion. 106 return lvaluesGroup{[]lvalue{lv}, restIndex} 107 } 108 109 type assignOp struct { 110 diag.Ranging 111 lhs lvaluesGroup 112 rhs valuesOp 113 temp bool 114 } 115 116 func (op *assignOp) exec(fm *Frame) Exception { 117 variables := make([]vars.Var, len(op.lhs.lvalues)) 118 for i, lvalue := range op.lhs.lvalues { 119 variable, err := derefLValue(fm, lvalue) 120 if err != nil { 121 return fm.errorp(op.lhs.lvalues[i], err) 122 } 123 variables[i] = variable 124 } 125 126 values, exc := op.rhs.exec(fm) 127 if exc != nil { 128 return exc 129 } 130 131 rest, temp := op.lhs.rest, op.temp 132 if rest == -1 { 133 if len(variables) != len(values) { 134 return fm.errorp(op, errs.ArityMismatch{What: "assignment right-hand-side", 135 ValidLow: len(variables), ValidHigh: len(variables), Actual: len(values)}) 136 } 137 for i, variable := range variables { 138 exc := set(fm, op.lhs.lvalues[i], temp, variable, values[i]) 139 if exc != nil { 140 return exc 141 } 142 } 143 } else { 144 if len(values) < len(variables)-1 { 145 return fm.errorp(op, errs.ArityMismatch{What: "assignment right-hand-side", 146 ValidLow: len(variables) - 1, ValidHigh: -1, Actual: len(values)}) 147 } 148 for i := 0; i < rest; i++ { 149 exc := set(fm, op.lhs.lvalues[i], temp, variables[i], values[i]) 150 if exc != nil { 151 return exc 152 } 153 } 154 restOff := len(values) - len(variables) 155 exc := set(fm, op.lhs.lvalues[rest], temp, 156 variables[rest], vals.MakeList(values[rest:rest+restOff+1]...)) 157 if exc != nil { 158 return exc 159 } 160 for i := rest + 1; i < len(variables); i++ { 161 exc := set(fm, op.lhs.lvalues[i], temp, variables[i], values[i+restOff]) 162 if exc != nil { 163 return exc 164 } 165 } 166 } 167 return nil 168 } 169 170 func set(fm *Frame, r diag.Ranger, temp bool, variable vars.Var, value interface{}) Exception { 171 if temp { 172 saved := variable.Get() 173 err := variable.Set(value) 174 if err != nil { 175 return fm.errorp(r, err) 176 } 177 fm.addDefer(func(fm *Frame) Exception { 178 err := variable.Set(saved) 179 if err != nil { 180 return fm.errorpf(r, "restore variable: %w", err) 181 } 182 return nil 183 }) 184 return nil 185 } 186 err := variable.Set(value) 187 if err != nil { 188 return fm.errorp(r, err) 189 } 190 return nil 191 } 192 193 // NoSuchVariable returns an error representing that a variable can't be found. 194 func NoSuchVariable(name string) error { 195 return noSuchVariableError{name} 196 } 197 198 type noSuchVariableError struct{ name string } 199 200 func (er noSuchVariableError) Error() string { return "no variable $" + er.name } 201 202 func derefLValue(fm *Frame, lv lvalue) (vars.Var, error) { 203 variable := deref(fm, lv.ref) 204 if variable == nil { 205 return nil, NoSuchVariable(fm.srcMeta.Code[lv.From:lv.To]) 206 } 207 if len(lv.indexOps) == 0 { 208 return variable, nil 209 } 210 indices := make([]interface{}, len(lv.indexOps)) 211 for i, op := range lv.indexOps { 212 values, exc := op.exec(fm) 213 if exc != nil { 214 return nil, exc 215 } 216 // TODO: Implement multi-indexing. 217 if len(values) != 1 { 218 return nil, errors.New("multi indexing not implemented") 219 } 220 indices[i] = values[0] 221 } 222 elemVar, err := vars.MakeElement(variable, indices) 223 if err != nil { 224 level := vars.ElementErrorLevel(err) 225 if level < 0 { 226 return nil, fm.errorp(lv, err) 227 } 228 return nil, fm.errorp(diag.Ranging{From: lv.From, To: lv.ends[level]}, err) 229 } 230 return elemVar, nil 231 }