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  }