github.com/elves/elvish@v0.15.0/pkg/eval/compile_lvalue.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/elves/elvish/pkg/diag"
     7  	"github.com/elves/elvish/pkg/eval/errs"
     8  	"github.com/elves/elvish/pkg/eval/vals"
     9  	"github.com/elves/elvish/pkg/eval/vars"
    10  	"github.com/elves/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  func (cp *compiler) parseCompoundLValues(ns []*parse.Compound) lvaluesGroup {
    30  	g := lvaluesGroup{nil, -1}
    31  	for _, n := range ns {
    32  		if len(n.Indexings) != 1 {
    33  			cp.errorpf(n, "lvalue may not be composite expressions")
    34  		}
    35  		more := cp.parseIndexingLValue(n.Indexings[0])
    36  		if more.rest == -1 {
    37  			g.lvalues = append(g.lvalues, more.lvalues...)
    38  		} else if g.rest != -1 {
    39  			cp.errorpf(n, "at most one rest variable is allowed")
    40  		} else {
    41  			g.rest = len(g.lvalues) + more.rest
    42  			g.lvalues = append(g.lvalues, more.lvalues...)
    43  		}
    44  	}
    45  	return g
    46  }
    47  
    48  func (cp *compiler) parseIndexingLValue(n *parse.Indexing) lvaluesGroup {
    49  	if n.Head.Type == parse.Braced {
    50  		// Braced list of lvalues may not have indices.
    51  		if len(n.Indicies) > 0 {
    52  			cp.errorpf(n, "braced list may not have indices when used as lvalue")
    53  		}
    54  		return cp.parseCompoundLValues(n.Head.Braced)
    55  	}
    56  	// A basic lvalue.
    57  	if !parse.ValidLHSVariable(n.Head, true) {
    58  		cp.errorpf(n.Head, "lvalue must be valid literal variable names")
    59  	}
    60  	varUse := n.Head.Value
    61  	sigil, qname := SplitSigil(varUse)
    62  	var ref *varRef
    63  	if len(n.Indicies) == 0 {
    64  		ref = resolveVarRef(cp, qname, nil)
    65  		if ref == nil {
    66  			segs := SplitQNameSegs(qname)
    67  			if len(segs) == 1 {
    68  				// Unqualified name - implicit local
    69  				ref = &varRef{localScope, cp.thisScope().addInner(segs[0]), nil}
    70  			} else if len(segs) == 2 && (segs[0] == "local:" || segs[0] == ":") {
    71  				// Qualified local name
    72  				ref = &varRef{localScope, cp.thisScope().addInner(segs[1]), nil}
    73  			} else {
    74  				cp.errorpf(n, "cannot create variable $%s; new variables can only be created in the local scope", qname)
    75  			}
    76  		}
    77  	} else {
    78  		ref = resolveVarRef(cp, qname, n)
    79  		if ref == nil {
    80  			cp.errorpf(n, "cannot find variable $%s", qname)
    81  		}
    82  	}
    83  	ends := make([]int, len(n.Indicies)+1)
    84  	ends[0] = n.Head.Range().To
    85  	for i, idx := range n.Indicies {
    86  		ends[i+1] = idx.Range().To
    87  	}
    88  	lv := lvalue{n.Range(), ref, cp.arrayOps(n.Indicies), ends}
    89  	restIndex := -1
    90  	if sigil == "@" {
    91  		restIndex = 0
    92  	}
    93  	// TODO: Deal with other sigils when they exist.
    94  	return lvaluesGroup{[]lvalue{lv}, restIndex}
    95  }
    96  
    97  type assignOp struct {
    98  	diag.Ranging
    99  	lhs lvaluesGroup
   100  	rhs valuesOp
   101  }
   102  
   103  func (op *assignOp) exec(fm *Frame) Exception {
   104  	variables := make([]vars.Var, len(op.lhs.lvalues))
   105  	for i, lvalue := range op.lhs.lvalues {
   106  		variable, err := derefLValue(fm, lvalue)
   107  		if err != nil {
   108  			return fm.errorp(op, err)
   109  		}
   110  		variables[i] = variable
   111  	}
   112  
   113  	values, exc := op.rhs.exec(fm)
   114  	if exc != nil {
   115  		return exc
   116  	}
   117  
   118  	if op.lhs.rest == -1 {
   119  		if len(variables) != len(values) {
   120  			return fm.errorp(op, errs.ArityMismatch{
   121  				What:     "assignment right-hand-side",
   122  				ValidLow: len(variables), ValidHigh: len(variables), Actual: len(values)})
   123  		}
   124  		for i, variable := range variables {
   125  			err := variable.Set(values[i])
   126  			if err != nil {
   127  				return fm.errorp(op, err)
   128  			}
   129  		}
   130  	} else {
   131  		if len(values) < len(variables)-1 {
   132  			return fm.errorp(op, errs.ArityMismatch{
   133  				What:     "assignment right-hand-side",
   134  				ValidLow: len(variables) - 1, ValidHigh: -1, Actual: len(values)})
   135  		}
   136  		rest := op.lhs.rest
   137  		for i := 0; i < rest; i++ {
   138  			err := variables[i].Set(values[i])
   139  			if err != nil {
   140  				return fm.errorp(op, err)
   141  			}
   142  		}
   143  		restOff := len(values) - len(variables)
   144  		err := variables[rest].Set(vals.MakeList(values[rest : rest+restOff+1]...))
   145  		if err != nil {
   146  			return fm.errorp(op, err)
   147  		}
   148  		for i := rest + 1; i < len(variables); i++ {
   149  			err := variables[i].Set(values[i+restOff])
   150  			if err != nil {
   151  				return fm.errorp(op, err)
   152  			}
   153  		}
   154  	}
   155  	return nil
   156  }
   157  
   158  // NoSuchVariable returns an error representing that a variable can't be found.
   159  func NoSuchVariable(name string) error {
   160  	return noSuchVariableError{name}
   161  }
   162  
   163  type noSuchVariableError struct{ name string }
   164  
   165  func (er noSuchVariableError) Error() string { return "no variable $" + er.name }
   166  
   167  func derefLValue(fm *Frame, lv lvalue) (vars.Var, error) {
   168  	variable := deref(fm, lv.ref)
   169  	if variable == nil {
   170  		return nil, NoSuchVariable(fm.srcMeta.Code[lv.From:lv.To])
   171  	}
   172  	if len(lv.indexOps) == 0 {
   173  		return variable, nil
   174  	}
   175  	indices := make([]interface{}, len(lv.indexOps))
   176  	for i, op := range lv.indexOps {
   177  		values, exc := op.exec(fm)
   178  		if exc != nil {
   179  			return nil, exc
   180  		}
   181  		// TODO: Implement multi-indexing.
   182  		if len(values) != 1 {
   183  			return nil, errors.New("multi indexing not implemented")
   184  		}
   185  		indices[i] = values[0]
   186  	}
   187  	elemVar, err := vars.MakeElement(variable, indices)
   188  	if err != nil {
   189  		level := vars.ElementErrorLevel(err)
   190  		if level < 0 {
   191  			return nil, fm.errorp(lv, err)
   192  		}
   193  		return nil, fm.errorp(diag.Ranging{From: lv.From, To: lv.ends[level]}, err)
   194  	}
   195  	return elemVar, nil
   196  }