src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/compile_lvalue.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  
     6  	"src.elv.sh/pkg/diag"
     7  	"src.elv.sh/pkg/eval/errs"
     8  	"src.elv.sh/pkg/eval/vals"
     9  	"src.elv.sh/pkg/eval/vars"
    10  	"src.elv.sh/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  			break
    42  		}
    43  		more := cp.parseIndexingLValue(n.Indexings[0], f)
    44  		if more.rest == -1 {
    45  			g.lvalues = append(g.lvalues, more.lvalues...)
    46  		} else if g.rest != -1 {
    47  			cp.errorpf(n, "at most one rest variable is allowed")
    48  		} else {
    49  			g.rest = len(g.lvalues) + more.rest
    50  			g.lvalues = append(g.lvalues, more.lvalues...)
    51  		}
    52  	}
    53  	return g
    54  }
    55  
    56  var dummyLValuesGroup = lvaluesGroup{[]lvalue{{}}, -1}
    57  
    58  func (cp *compiler) parseIndexingLValue(n *parse.Indexing, f lvalueFlag) lvaluesGroup {
    59  	if n.Head.Type == parse.Braced {
    60  		// Braced list of lvalues may not have indices.
    61  		if len(n.Indices) > 0 {
    62  			cp.errorpf(n, "braced list may not have indices when used as lvalue")
    63  			return dummyLValuesGroup
    64  		}
    65  		return cp.parseCompoundLValues(n.Head.Braced, f)
    66  	}
    67  	// A basic lvalue.
    68  	if !parse.ValidLHSVariable(n.Head, true) {
    69  		cp.errorpf(n.Head, "lvalue must be valid literal variable names")
    70  		return dummyLValuesGroup
    71  	}
    72  	varUse := n.Head.Value
    73  	sigil, qname := SplitSigil(varUse)
    74  	if qname == "" {
    75  		cp.errorpf(n, "variable name must not be empty")
    76  		return dummyLValuesGroup
    77  	}
    78  
    79  	var ref *varRef
    80  	if f&setLValue != 0 {
    81  		ref = resolveVarRef(cp, qname, n)
    82  		if ref != nil && len(ref.subNames) == 0 && ref.info.readOnly {
    83  			cp.errorpf(n, "variable $%s is read-only", parse.Quote(qname))
    84  			return dummyLValuesGroup
    85  		}
    86  	}
    87  	if ref == nil {
    88  		if f&newLValue == 0 {
    89  			cp.autofixUnresolvedVar(qname)
    90  			cp.errorpf(n, "cannot find variable $%s", parse.Quote(qname))
    91  			return dummyLValuesGroup
    92  		}
    93  		if len(n.Indices) > 0 {
    94  			cp.errorpf(n, "new variable $%s must not have indices", parse.Quote(qname))
    95  			return dummyLValuesGroup
    96  		}
    97  		segs := SplitQNameSegs(qname)
    98  		if len(segs) == 1 {
    99  			// Unqualified name - implicit local
   100  			name := segs[0]
   101  			ref = &varRef{localScope,
   102  				staticVarInfo{name, false, false}, cp.thisScope().add(name), nil}
   103  		} else {
   104  			cp.errorpf(n, "cannot create variable $%s; "+
   105  				"new variables can only be created in the current scope",
   106  				parse.Quote(qname))
   107  			return dummyLValuesGroup
   108  		}
   109  	}
   110  
   111  	ends := make([]int, len(n.Indices)+1)
   112  	ends[0] = n.Head.Range().To
   113  	for i, idx := range n.Indices {
   114  		ends[i+1] = idx.Range().To
   115  	}
   116  	lv := lvalue{n.Range(), ref, cp.arrayOps(n.Indices), ends}
   117  	restIndex := -1
   118  	if sigil == "@" {
   119  		restIndex = 0
   120  	}
   121  	// TODO: Support % (and other sigils?) if https://b.elv.sh/584 is implemented for map explosion.
   122  	return lvaluesGroup{[]lvalue{lv}, restIndex}
   123  }
   124  
   125  type assignOp struct {
   126  	diag.Ranging
   127  	lhs  lvaluesGroup
   128  	rhs  valuesOp
   129  	temp bool
   130  }
   131  
   132  func (op *assignOp) exec(fm *Frame) Exception {
   133  	variables := make([]vars.Var, len(op.lhs.lvalues))
   134  	for i, lvalue := range op.lhs.lvalues {
   135  		variable, err := derefLValue(fm, lvalue)
   136  		if err != nil {
   137  			return fm.errorp(op.lhs.lvalues[i], err)
   138  		}
   139  		variables[i] = variable
   140  	}
   141  
   142  	values, exc := op.rhs.exec(fm)
   143  	if exc != nil {
   144  		return exc
   145  	}
   146  
   147  	rest, temp := op.lhs.rest, op.temp
   148  	if rest == -1 {
   149  		if len(variables) != len(values) {
   150  			return fm.errorp(op, errs.ArityMismatch{What: "assignment right-hand-side",
   151  				ValidLow: len(variables), ValidHigh: len(variables), Actual: len(values)})
   152  		}
   153  		for i, variable := range variables {
   154  			exc := set(fm, op.lhs.lvalues[i], temp, variable, values[i])
   155  			if exc != nil {
   156  				return exc
   157  			}
   158  		}
   159  	} else {
   160  		if len(values) < len(variables)-1 {
   161  			return fm.errorp(op, errs.ArityMismatch{What: "assignment right-hand-side",
   162  				ValidLow: len(variables) - 1, ValidHigh: -1, Actual: len(values)})
   163  		}
   164  		for i := 0; i < rest; i++ {
   165  			exc := set(fm, op.lhs.lvalues[i], temp, variables[i], values[i])
   166  			if exc != nil {
   167  				return exc
   168  			}
   169  		}
   170  		restOff := len(values) - len(variables)
   171  		exc := set(fm, op.lhs.lvalues[rest], temp,
   172  			variables[rest], vals.MakeList(values[rest:rest+restOff+1]...))
   173  		if exc != nil {
   174  			return exc
   175  		}
   176  		for i := rest + 1; i < len(variables); i++ {
   177  			exc := set(fm, op.lhs.lvalues[i], temp, variables[i], values[i+restOff])
   178  			if exc != nil {
   179  				return exc
   180  			}
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  func set(fm *Frame, r diag.Ranger, temp bool, variable vars.Var, value any) Exception {
   187  	if temp {
   188  		saved := variable.Get()
   189  
   190  		needUnset := false
   191  		unsettable, ok := variable.(vars.UnsettableVar)
   192  		if ok {
   193  			needUnset = !unsettable.IsSet()
   194  		}
   195  
   196  		err := variable.Set(value)
   197  		if err != nil {
   198  			return fm.errorp(r, err)
   199  		}
   200  		fm.addDefer(func(fm *Frame) Exception {
   201  			if needUnset {
   202  				if err := unsettable.Unset(); err != nil {
   203  					return fm.errorpf(r, "unset variable: %w", err)
   204  				}
   205  				return nil
   206  			}
   207  
   208  			err := variable.Set(saved)
   209  			if err != nil {
   210  				return fm.errorpf(r, "restore variable: %w", err)
   211  			}
   212  			return nil
   213  		})
   214  		return nil
   215  	}
   216  	err := variable.Set(value)
   217  	if err != nil {
   218  		return fm.errorp(r, err)
   219  	}
   220  	return nil
   221  }
   222  
   223  // NoSuchVariable returns an error representing that a variable can't be found.
   224  func NoSuchVariable(name string) error {
   225  	return noSuchVariableError{name}
   226  }
   227  
   228  type noSuchVariableError struct{ name string }
   229  
   230  func (er noSuchVariableError) Error() string { return "no variable $" + er.name }
   231  
   232  func derefLValue(fm *Frame, lv lvalue) (vars.Var, error) {
   233  	variable := deref(fm, lv.ref)
   234  	if variable == nil {
   235  		return nil, NoSuchVariable(fm.srcMeta.Code[lv.From:lv.To])
   236  	}
   237  	if len(lv.indexOps) == 0 {
   238  		return variable, nil
   239  	}
   240  	indices := make([]any, len(lv.indexOps))
   241  	for i, op := range lv.indexOps {
   242  		values, exc := op.exec(fm)
   243  		if exc != nil {
   244  			return nil, exc
   245  		}
   246  		// TODO: Implement multi-indexing.
   247  		if len(values) != 1 {
   248  			return nil, errors.New("multi indexing not implemented")
   249  		}
   250  		indices[i] = values[0]
   251  	}
   252  	elemVar, err := vars.MakeElement(variable, indices)
   253  	if err != nil {
   254  		level := vars.ElementErrorLevel(err)
   255  		if level < 0 {
   256  			return nil, fm.errorp(lv, err)
   257  		}
   258  		return nil, fm.errorp(diag.Ranging{From: lv.From, To: lv.ends[level]}, err)
   259  	}
   260  	return elemVar, nil
   261  }