// This function will take the expression "expr" and expand it into multiple // statements such that no single statement contains more than one side- // effect. It will also scan for property getter invocations and opIndex // that are treated as lvalues. When it finds them, it will rewrite them // such that the corresponding setter or opIndexAssign is guaranteed to be // called. // // To provide some insight into how prolog and epilog work, here is what they // look like at the outermost level: // [prolog] // auto t0 = a; // [prolog and epilog grow towards the center] // a++; // a = t0; // [epilog] // (That would be made from "a++;" if 'a' were a property.) // // Assumptions: // * expr's structure is consistent with operator precedence. // * Operator overloads have already been expanded into function calls. // Expression doPropertyRewriteGreedily( Expression expr, BlockStatement prolog, BlockStatement epilog, bool hasSideEffects // Does the parent expression have side-effects? ) { VariableExpression temporary = expr; if ( expr == opPostInc || expr == opPostDec ) { // Generate any temporaries that we depend on but don't know about. // Also, find out what the child is made of. auto incoming = doPropertyRewriteGreedily(expr.child,prolog,epilog,true); if ( incoming.isVariableExpression() ) { // The rewrite gave us a variable or a temporary, so making // yet another temporary is unnecessary. temporary = incoming; } else { // Make a new temporary to hold the returned expression. temporary = new VariableExpression(makeUniqueTemporaryName()); // Check for duplicate getter calls before injecting ours. if ( ! prolog.contains("auto {anything} = {incoming};"); prolog.append("auto {temporary} = {incoming};"); } // Check for duplicate setter calls before injecting ours. if ( ! epilog.contains("{incoming} = {anything};") ) epilog.prepend("{incoming} = {temporary};"); // This looks out-of-order, but since we are prepending, // this will actually be executed before the setter is called. expr.child = temporary; epilog.prepend("{expr};"); } else if ( expr == oneOf("~,!,-,+=,-=,*=,/=,%=,|=,&=,~=,>>=,>>>=,<<=") || expr == impureFunctionCall ) { // lhs = left-hand-side. rhs = right-hand-side. // For the unary ~, !, and -, the lhs is the argument // and the rhs has zero expressions. auto lhs = expr.lhs; // Generate any temporaries that we depend on but don't know about. // Also, find out what the left-hand-side is made of. auto incoming = doPropertyRewriteGreedily(lhs,prolog,epilog,true); if ( incoming.isVariableExpression() ) { // The rewrite gave us a variable or a temporary, so making // yet another temporary is unnecessary. temporary = incoming; } else { // Make a new temporary to hold the returned expression. temporary = new VariableExpression(makeUniqueTemporaryName()); // Check for duplicate getter calls before injecting ours. if ( ! prolog.contains("auto {anything} = {incoming};" ) prolog.append("auto {temporary} = {incoming};"); } // Substitute arguments with temporaries as needed. // These particular temporaries will be created by the recursive calls // to doPropertyRewriteGreedily, so we don't need to set them up. foreach( ref e; rhs ) e = doPropertyRewriteGreedily(e,prolog,epilog,false); // Replace expr's guys and stick it here. expr.lhs = temporary; prolog.append("{expr}"); // Check for duplicate setter calls before injecting ours. if ( ! epilog.contains("{incoming} = {anything};") ) epilog.prepend("{incoming} = {temporary};"); } else if ( expr == "=" && (expr.lhs == writeProperty || expr.lhs == opIndexAssignCall) ) { // ... // optionally rewrite expr.lhs as a function call. // ... // Rewrite the subexpressions as needed. expr.lhs = doPropertyRewriteGreedily( expr.lhs, prolog, epilog, true ); expr.rhs = doPropertyRewriteGreedily( expr.rhs, prolog, epilog, false ); } // This assumes that an expression like a.b.c is considered a readProperty if c is a property. // In other words, the recursive call /must/ include all of a.b.c, not just c itself. // (such troubles are possible if c is a leaf of a dot operator or somesuch.) else if ( hasSideEffects && (expr == readProperty || expr == opIndexCall) ) { // Generate any temporaries that we depend on but don't know about. // Given a.b.c, expr.lhs is a.b // Given s[i], expr.lhs is s expr.lhs = doPropertyRewriteGreedily(expr.lhs,prolog,epilog,true); if ( expr == opIndexCall ) // Given s[i++], expr.rhs is i++ expr.rhs = doPropertyRewriteGreedily(expr.rhs,prolog,epilog,true); // ... // optionally rewrite expr (the property/indexOp) as a function call. // ... temporary = new VariableExpression(makeUniqueTemporaryName()); // Check for duplicate getter calls before injecting ours. if ( ! prolog.contains("auto {anything} = {expr};" ) prolog.append("auto {temporary} = {expr};"); // Check for duplicate setter calls before injecting ours. if ( ! epilog.contains("{incoming} = {anything};") ) epilog.prepend("{incoming} = {temporary};"); } else if ( expr != null ) // Other binary operators, ?: operator, pure functions, etc... { // Recursion terminates when expr.children is empty. foreach( ref e; expr.children ) e = doPropertyRewriteGreedily( e, prolog, epilog, hasSideEffects ); } return temporary; } // A wrapper around the greedy rewrite. // This does the same thing as the greedy counterpart, but only when a property // getter invocation is found in a subexpression of a side-effect. Otherwise // it does not expand the expression into multiple statements, and the // returned expression may contain more than one side-effect. // This ensures that if a rewrite doesn't need to happen, then it won't happen. // The greedy version may be called directly and both results will be correct, // but the lazy version's result might be faster executing. // // Assumptions: // * expr's structure is consistent with operator precedence. // * Operator overloads have already been expanded into function calls. // Expression doPropertyRewriteLazily( Expression expr, ref Expression farthestAncestorSideEffect, // This must be a reference to a reference. BlockStatement prolog, BlockStatement epilog ) { if ( expr == oneOf("++,--,~,!,-,+=,-=,*=,/=,%=,|=,&=,~=,>>=,>>>=,<<=,=") || expr == impureFunctionCall ) // if ( expr.hasSideEffects() ) { foreach( ref e; expr.children ) e = doPropertyRewriteLazily( e, expr, prolog, epilog ); } else if ( farthestAncestorSideEffect != null && (expr == readProperty || expr == opIndexCall) ) { farthestAncestorSideEffect = doPropertyRewriteGreedily( farthestAncestorSideEffect, prolog, epilog, false ); } else if ( expr != null ) { foreach( ref e; expr.children ) e = doPropertyRewriteLazily( e, farthestAncestorSideEffect, prolog, epilog ); } return expr; } // This is a wrapper for doPropertyRewriteLazily or doPropertyRewriteGreedy. // See one of those for more details. // // Assumptions: // * exprStatement is a child of parent. // * exprStatement's expression structure is consistent with operator precedence. // * Operator overloads have already been expanded into function calls. // void doPropertyRewrite( BlockStatement parent, ExpressionStatement exprStatement ) { auto prolog = new BlockStatement(); auto epilog = new BlockStatement(); // The greedy version can be called if the lazy one doesn't work right. exprStatement.expression = doPropertyRewriteLazily(exprStatement.expression, null, prolog, epilog); prolog.append(exprStatement); prolog.append(epilog); // replace the expression statement with its expansion. parent.replace(exprStatement, prolog); }