Because IIFEs aren’t adequate for either smart pipelines, we need to create helper functions that allow us to “flatten” statements that contain them, extracting them into preceding constant declarations. This would not only allow us to implement smart pipelines properly, this would also allow us to implement do expressions away from using IIFEs, which do not work with await and yield (see babel/babel#3780).
-
Add a
getChildOfInnermostStatementmethod to paths, so that calling the method on a path toxin…function f () { return Promise.all(g(x)); }
…would return a path pointing to the
Promise.all(g(x))expression. The same thing would happen if this method is called on paths tog(x)orPromise.all(g(x))itself. (Calling it on a path to statements such as thereturnstatement will returnundefined.)Similarly, calling this method on a path to
xin…if (f(x)) g(y); else h(z);
…would return a path pointing to the
f(x)expression. The same thing would happen if this method is called on a path tof(x)itself. Calling the method on paths toyor tog(y)would return a path tog(y). The same would go for paths tozor toh(z), returning a path toh(z). -
Add a
replaceTopicReferencesfunction that, given any main expression and a variable, replaces all instances ofPipelinePrimaryTopicReferencein the main expression (except references contained within an inner pipeline’s body) with that variable. For instance, using this function on# + (# |> # * 2) + 1with an$0variable will return$0 + ($0 |> # * 2) + 1. -
Create an
isPureExpressionfunction that, given any type of expression or statement, returns a boolean for whether the expression is pure or impure (i.e., whether its evaluation may cause any observable side effects, including runtime errors). The order in which a program’s impure expressions are evaluated must be preserved by any code transformation, but the order of pure expressions’ evaluation may be rearranged without affecting the program’s behavior. The function’s behavior is such that:- It would return
truefor any literal boolean/number/string, liketrue,false,1,"string". - It would return
truefor any variable reference, likex. - It would return
truefor any array literal whose elements are all pure expressions, like[0, 1],[[0, 1], 2],[f(0, 1), 2]. - It would return
truefor any object literal whose keys and values are all pure expressions, like{x: 0, y: 1},{a: {x: 0, y: 1}, b: 2},{x: f(0, 1), y: 2}. - It would return
truefor any function-definition expression. - It would return
truefor any comma expression or logical-not/and/or/conditional expression whose child subexpression(s) are themselves pure. - It would return
falsefor any other expression, including pipeline expressions, function/constructor calls, assignment expressions, arithmetic/bitwise expressions (which can raise runtime errors), member access,awaitexpressions,yieldexpressions, anddoexpressions.
- It would return
-
Create a
flattenExpressionOncefunction that, given any type of expression, returns a shallowly flattened version of that expression – along with a series of helper declarations for lexically hygienic new helper constants or helper variables. If the expression cannot be flattened (if it is already flat), then it returnsnullinstead.Category Original Flattened Helper declarations A 0Already flat None B await xAlready flat None C await f()await $0const $0 = f();C g(f(), 0)g($0, 0)const $0 = f();C [f(), 0][$0, 0]const $0 = f();C {[a]: b, [f()]: g()}{[a]: b, [$0]: $1}const $0 = f(); const $1 = g();C {[x |> # + 1]: y}{[$0]: y}const $0 = x |> # + 1;D x |> ff(x)None E x |> f |> gg($0)const $0 = x |> f;E x |> # + 1 |> gg($0)const $0 = x |> # + 1;F x |> # + 1x + 1None F x |> [f(), # + 1, # + 2][f(), x + 1, x + 2]None F x |> [f(), # |> # + 1][f(), x |> # + 1]None G g(x) |> [f(), # + 1, # + 2][f(), $0 + 1, $0 + 2]const $0 = g(x);G g(x) |> [f(), # |> # + 1][f(), $0 |> # + 1]const $0 = g();G x |> [f(), # + 1] |> [g(), h(#), i(#)][g(), h($0), i($0)]const $0 = x |> [f(), # + 1];H do { if (f()) g(); else h(); }$0;var $0; if (f()) $0 = g(); else $0 = h();The examples above follow these rules:
-
A. When the expression contains no subexpression, then it is already flat and the function returns
null. -
B. When the expression contains child subexpressions and none of those child subexpressions is impure, then it is already flat and the function returns
null. -
C. When the expression contains child subexpressions and any one of those child subexpressions is impure, then its flattened version is the expression but with all of those impure child subexpressions replaced by lexically hygienic new helper constants.
-
D. When the expression is a bare-style pipeline whose head is a pure expression, then its flattened version is simply a function call on the pipeline’s body with the pipeline’s head. In this case, there are no helper declarations.
-
E. When the expression is a bare-style pipeline whose head is an impure expression, then its flattened version is a function call on a lexically hygienic new helper constant. The helper constant is defined as the the pipeline’s head expression.
-
F. When the expression is a topic-style pipeline whose head is a pure expression, then its flattened version is the result of calling
replaceTopicReferenceson the pipeline’s body expression with the pipeline’s head. In this case, there are no new helper declarations. -
G. When the expression is a topic-style pipeline whose head is an impure expression, then its flattened version is the result of first calling
replaceTopicReferenceson the pipeline’s body expression with a lexically hygienic new helper constant. The new helper constant is defined as the pipeline’s head expression. -
H. When the expression is a
doexpression, then its flattened version is a lexically hygienic new helper variable. The new helper variable’s declaration is followed by the child statements of the originaldoexpression’s block, except that the final statement of every branch from the block is assigned to the helper variable. The logic for adding such assignments to a block of statements is already implemented inreplaceExpressionWithStatements, but, unlike withreplaceExpressionWithStatements, the resulting statements would not be wrapped in an arrow function.
-
-
Create a
flattenExpressionfunction that, given any type of expression, returns a deeply flattened version of that expression – along with a series of helper declarations for lexically hygienic new helper constants or helper variables. If the expression cannot be flattened (if it is already flat), then it returnsnullinstead.This occurs by iterating first over the expression until it is flat (that is, until
flattenExpressionOncereturnsnull). Each time this occurs, any new helper declarations are appended to a growing list. Then,flattenExpressionis recursively applied to each helper declaration's binding expression. If it does not returnnull, then a new helper declaration using the newly flattened binding expression replaces the old helper declaration in the list. Any additional helper declarations that this recursiveflattenExpressioncall created are also inserted into the list, immediately before the current helper declaration. -
Create a
flattenAndReplaceChildOfStatementfunction that requires a path to any expression whose parent is a statement (seegetChildOfInnermostStatementabove); otherwise it will throw an error. The function then replaces that expression with the result offlattenExpression. It then inserts any new constant declarations in the appropriate location (usually, but not always, immediately before the replaced expression’s parent statement).For instance, the array expression in
return [f(), x, y |> # + 1 |> g(#, 2)];is a child of a statement (thereturnstatement). And callingflattenExpressionon the expression would result in[$0, $1]and the new declarationsconst $0 = f(); const $1 = y |> # + 1 |> g(#, 2);. So then callingflattenAndReplaceChildOfStatementon that array expression will effectively replace itsreturnstatement withconst $0 = f(); const $1 = y |> # + 1 |> g(#, 2); return [$0, x, $1];.Now consider the pipeline expression
x |> # + 1 |> g(#, 2)in that new code. It is now also a child of a statement (aconststatement). CallingflattenStatementon that pipeline expression would result inconst $1 = g($2, 1);and the new declarationconst $2 = x |> # + 1;. So then callingflattenAndReplaceStatementon the pipeline expression will replace itsconststatement withconst $2 = x |> # + 1; const $1 = g($2, 1);.-
In general, when calling
flattenAndReplaceChildOfStatementon any expression that is a child of a statement, then firstflattenExpressionis called on that expression. (flattenStatementreturns a new version of that statement along with any new declarations that it uses.) The original statement is replaced inline with the new version returned byflattenStatement. Then the new declarations that it also returned will be inserted immediately before the statement. Several exceptions to this rule follow. -
If the parent expression is directly the antecedent or consequent child of an
ifstatement – or if the expression is directly the body child of an arrow function or of afor,for–in,for–of,while,do–while,with, or label statement – then the replacement statement must first be wrapped in a block statement. The new declarations will then be inserted within the block before the statement. For instance, flattening the pipeline expression in the antecedent statement of:if (f()) g() |> h(#, #+1) |> i; else j();
…would result in this:
if (f()) { const $0 = g(); const $1 = h($0, $0+1); i($1); } else j();
-
If the original expression was the
testchild of awhile (<test>) <body>statement or ado <body> while (<test>) statement, then firstflattenExpressionis called on thetestexpression. This results in a replacementtestexpression, and a list of new declarations for the replacementtest. A declaration for an additional lexically hygienic new variable (called$testbelow, which is initially bound to the replacementtest` expression) is also created. These parts are then combined such that the final statement looks like this:<testHelperDeclarations> var $test = <replacementTestExpression>; while ($test) { <body> <testHelperDeclarations> $test = <replacementTestExpression>; }
For instance, flattening the test expression in:
while (i |> f(#, #+1) |> g |> # < n) console.log(i);
…would result in this:
const $0 = f(i, i+1); const $1 = g($0); var $test = $1 < n; while ($test) { console.log(i); const $0 = f(i, i+1); const $1 = g($0); $test = $1 < n; }
-
If the original expression was the
testchild of a classicfor (<init>; <test>; <update>) <body>statement, then firstflattenExpressionis called on thetestexpression. This results in a replacementtestexpression, and a list of new declarations for the replacementtest. A declaration for an additional lexically hygienic new variable (called$testbelow, which is initially bound to the replacementtestexpression) is also created. These parts are then combined such that the finalforstatement looks like this:{ <initClause> <testHelperDeclarations> var $test = <replacementTestExpression>; for (; $test; <update>) { <body> <testHelperDeclarations> $test = <replacementTestExpression>; } }
For instance, flattening the test expression in:
for (var i = 0; i |> f(#, #+1) |> g |> # < n; i++) console.log(i);
…would result in this:
{ var i = 0; const $0 = f(i, i+1); const $1 = g($0); var $test = $1 < n; for (; $test; i++) { console.log(i); const $0 = f(i, i+1); const $1 = g($0); $test = $1 < n; } }
-
Once flattenAndReplaceExpression is implemented, a visitor searching for pipeline expressions will be able to call flattenAndReplaceExpression on the child of the innermost statement containing that pipeline expression (as determined by the getChildOfInnermostStatement path method mentioned way above).
The visitor will then jump back to the path returned by flattenAndReplaceStatement, which is the earliest statement that was just-now modified (and which will either be a new const declaration or the innermost statement containing the pipeline itself). There, the visitor will restart its search for more pipeline expressions.
The visitor should perform this transformation inside-out: that is, upon leaving pipeline expressions, rather than when entering them.
do expressions could similarly be implemented using this same code, rather than using IIFEs.