node: implement shorthand assignment operators, callable rvalues

This commit is contained in:
mappu 2020-04-11 12:51:09 +12:00
parent 86266ae676
commit b2ef112f50
1 changed files with 94 additions and 9 deletions

103
node.go
View File

@ -644,7 +644,7 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
// Assignment expressions can take on better error-handling behaviour
// when we know the assignment is a top-level expression
if a, ok := n.Expr.(*assign.Assign); ok {
return this.convertAssignment(a, true)
return this.convertAssignAssign(a, true)
}
// Non-assignment expression
@ -909,7 +909,71 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
//
case *assign.Assign:
return this.convertAssignment(n, false /* no reason to believe we are a top-level statement */)
return this.convertAssignAssign(n, false /* no reason to believe we are a top-level statement */)
// Go has a limited set of assignment shorthands:
//
// ```
// assign_op = [ add_op | mul_op ] "=" .
//
// [...]
//
// add_op = "+" | "-" | "|" | "^" .
// mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
// ```
case *assign.BitwiseAnd:
return this.convertAssignment(n, n.Variable, n.Expression, false, `&=`)
case *assign.BitwiseOr:
return this.convertAssignment(n, n.Variable, n.Expression, false, `|=`)
case *assign.BitwiseXor:
return this.convertAssignment(n, n.Variable, n.Expression, false, `^=`)
case *assign.Coalesce:
// Null coaslecing assignment expression - $x ??= $y;
// We don't have a natural equivalent for the coalescing operator
// We don't have an implementation of *binary.Coalesce yet either
// But when we do this will need an rvalue transformation to use it
return this.convert(assign.NewAssign(n.Variable, binary.NewCoalesce(n.Variable, n.Expression))) // FIXME also handle position/freefloating
case *assign.Concat:
return this.convertAssignment(n, n.Variable, n.Expression, false, `+=`)
case *assign.Div:
return this.convertAssignment(n, n.Variable, n.Expression, false, `/=`)
case *assign.Minus:
return this.convertAssignment(n, n.Variable, n.Expression, false, `-=`)
//case *assign.Mod:
// TODO
case *assign.Mul:
return this.convertAssignment(n, n.Variable, n.Expression, false, `*=`)
case *assign.Plus:
return this.convertAssignment(n, n.Variable, n.Expression, false, `+=`)
//case *assign.Pow:
// TODO
//case *assign.Reference:
case *assign.ShiftLeft:
return this.convertAssignment(n, n.Variable, n.Expression, false, `<<=`)
case *assign.ShiftRight:
return this.convertAssignment(n, n.Variable, n.Expression, false, `>>=`)
//
// name
//
case *name.Name:
return this.resolveName(n)
//
// expr
@ -919,7 +983,11 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
// All our generated functions return err, but this AST node may be in a single-rvalue context
// TODO do something more intelligent here
// We can't necessarily hoist the whole call, in case we are on the right-hand side of a && operator
funcName, err := this.resolveName(n.Function)
// We might be calling by name, or, we might be calling by an rvalue expression
// e.g. foo()()() or $x()
// Use full conversion logic for name resolution
funcName, err := this.convert(n.Function)
if err != nil {
return "", parseErr{n, err}
}
@ -1526,14 +1594,23 @@ func (this *conversionState) resolveName(n node.Node) (string, error) {
return ret, nil
}
func (this *conversionState) convertAssignment(n *assign.Assign, isTopLevelStatement bool) (string, error) {
func (this *conversionState) convertAssignAssign(n *assign.Assign, isTopLevelStatement bool) (string, error) {
return this.convertAssignment(n, n.Variable, n.Expression, isTopLevelStatement, `=`)
}
rvalue, err := this.convert(n.Expression)
func (this *conversionState) convertAssignment(n, nLValue, nRValue node.Node, isTopLevelStatement bool, operator string) (string, error) {
rvalue, err := this.convert(nRValue)
if err != nil {
return "", parseErr{n, err}
}
if dimf, ok := n.Variable.(*expr.ArrayDimFetch); ok && dimf.Dim == nil {
if dimf, ok := nLValue.(*expr.ArrayDimFetch); ok && dimf.Dim == nil {
if operator != `=` {
return "", parseErr{n, fmt.Errorf("can't yet handle complex shorthand append assignment operator statement")}
}
// Special handling for the case of foo[] = bar
// Transform into append()
arrayVar, err := this.convert(dimf.Variable)
@ -1551,7 +1628,7 @@ func (this *conversionState) convertAssignment(n *assign.Assign, isTopLevelState
// Normal assignment
lvalue, err := this.convert(n.Variable) // might be a more complicated lvalue
lvalue, err := this.convert(nLValue) // might be a more complicated lvalue
if err != nil {
return "", parseErr{n, err}
}
@ -1563,8 +1640,16 @@ func (this *conversionState) convertAssignment(n *assign.Assign, isTopLevelState
// If this is a simple expression (func call; indirect/method call; assignment of func/method call) then
// we need to propagate errors
switch n.Expression.(type) {
switch nRValue.(type) {
case *expr.FunctionCall, *expr.StaticCall, *expr.New:
if operator != `=` {
// We need to perform the variable assignment + err check, but we are in shorthand assignment context
// and can't just mess with the left hand of the expression
// Need to create a temporary accumulator variable
return "", parseErr{n, fmt.Errorf("can't yet handle err value extraction for function call in shorthand append assignment operator statement")}
}
ret := lvalue + ", err = " + rvalue + "\n"
ret += "if err != nil {\n"
ret += this.currentErrHandler
@ -1577,7 +1662,7 @@ func (this *conversionState) convertAssignment(n *assign.Assign, isTopLevelState
}
} else {
return lvalue + " = " + rvalue, nil // Just the basic assignment - without trailing NL
return lvalue + " " + operator + " " + rvalue, nil // Just the basic assignment - without trailing NL
}
}