From b2ef112f50ca1ce652d3032da413235fb2ac029d Mon Sep 17 00:00:00 2001 From: mappu Date: Sat, 11 Apr 2020 12:51:09 +1200 Subject: [PATCH] node: implement shorthand assignment operators, callable rvalues --- node.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 9 deletions(-) diff --git a/node.go b/node.go index 58a7f89..d8e0bab 100644 --- a/node.go +++ b/node.go @@ -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 } }