Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Transform for F# Pipeline #1

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,56 @@
import { types as t } from "@babel/core";

// tries to optimize sequence expressions in the format
// (a = b, ((c) => d + e)(a))
// to
// (a = b, a + e)
const buildOptimizedSequenceExpression = ({ assign, call, path }) => {
const { left: placeholderNode, right: pipelineLeft } = assign;
const { callee: pipelineRight } = call;

let optimizeArrow =
t.isArrowFunctionExpression(pipelineRight) &&
t.isExpression(pipelineRight.body) &&
!pipelineRight.async &&
!pipelineRight.generator;
let param;

if (optimizeArrow) {
const { params } = pipelineRight;
if (params.length === 1 && t.isIdentifier(params[0])) {
param = params[0];
} else if (params.length > 0) {
optimizeArrow = false;
}
} else if (t.isIdentifier(pipelineRight, { name: "eval" })) {
const evalSequence = t.sequenceExpression([
t.numericLiteral(0),
pipelineRight,
]);

call.callee = evalSequence;

return t.sequenceExpression([assign, call]);
} else if (
(t.isIdentifier(pipelineRight) &&
path.scope.hasBinding(pipelineRight.name)) ||
t.isImmutable(pipelineLeft)
) {
return t.callExpression(pipelineRight, [pipelineLeft]);
}

if (optimizeArrow && !param) {
// Arrow function with 0 arguments
return t.sequenceExpression([pipelineLeft, pipelineRight.body]);
}

if (param) {
path.get("right").scope.rename(param.name, placeholderNode.name);

return t.sequenceExpression([assign, pipelineRight.body]);
}

return t.sequenceExpression([assign, call]);
};

export default buildOptimizedSequenceExpression;
@@ -0,0 +1,26 @@
import { types as t } from "@babel/core";
import buildOptimizedSequenceExpression from "./buildOptimizedSequenceExpression";

const fsharpVisitor = {
BinaryExpression(path) {
const { scope, node } = path;
const { operator, left, right } = node;
if (operator !== "|>") return;

const placeholder = scope.generateUidIdentifierBasedOnNode(left);
scope.push({ id: placeholder });

const call =
right.type === "AwaitExpression"
? t.awaitExpression(t.cloneNode(placeholder))
: t.callExpression(right, [t.cloneNode(placeholder)]);
const sequence = buildOptimizedSequenceExpression({
assign: t.assignmentExpression("=", t.cloneNode(placeholder), left),
call,
path,
});
path.replaceWith(sequence);
},
};

export default fsharpVisitor;
2 changes: 2 additions & 0 deletions packages/babel-plugin-proposal-pipeline-operator/src/index.js
Expand Up @@ -2,10 +2,12 @@ import { declare } from "@babel/helper-plugin-utils";
import syntaxPipelineOperator from "@babel/plugin-syntax-pipeline-operator";
import minimalVisitor from "./minimalVisitor";
import smartVisitor from "./smartVisitor";
import fsharpVisitor from "./fsharpVisitor";

const visitorsPerProposal = {
minimal: minimalVisitor,
smart: smartVisitor,
fsharp: fsharpVisitor,
};

export default declare((api, options) => {
Expand Down
@@ -1,51 +1,22 @@
import { types as t } from "@babel/core";
import buildOptimizedSequenceExpression from "./buildOptimizedSequenceExpression";

const minimalVisitor = {
BinaryExpression(path) {
const { scope } = path;
const { node } = path;
const { operator, left } = node;
let { right } = node;
const { scope, node } = path;
const { operator, left, right } = node;
if (operator !== "|>") return;

let optimizeArrow =
t.isArrowFunctionExpression(right) &&
t.isExpression(right.body) &&
!right.async &&
!right.generator;
let param;

if (optimizeArrow) {
const { params } = right;
if (params.length === 1 && t.isIdentifier(params[0])) {
param = params[0];
} else if (params.length > 0) {
optimizeArrow = false;
}
} else if (t.isIdentifier(right, { name: "eval" })) {
right = t.sequenceExpression([t.numericLiteral(0), right]);
}

if (optimizeArrow && !param) {
// Arrow function with 0 arguments
path.replaceWith(t.sequenceExpression([left, right.body]));
return;
}

const placeholder = scope.generateUidIdentifierBasedOnNode(param || left);
const placeholder = scope.generateUidIdentifierBasedOnNode(left);
scope.push({ id: placeholder });
if (param) {
path.get("right").scope.rename(param.name, placeholder.name);
}

const call = optimizeArrow
? right.body
: t.callExpression(right, [t.cloneNode(placeholder)]);
const call = t.callExpression(right, [t.cloneNode(placeholder)]);
path.replaceWith(
t.sequenceExpression([
t.assignmentExpression("=", t.cloneNode(placeholder), left),
buildOptimizedSequenceExpression({
assign: t.assignmentExpression("=", t.cloneNode(placeholder), left),
call,
]),
path,
}),
);
},
};
Expand Down
@@ -0,0 +1,27 @@
const y = 2;

const f = (x) => (x |> (y) => y + 1)
|> (z) => z * y

const _f = (x) => x
|> (y) => y + 1
|> (z) => z * y

const g = (x) => x
|> (y) => (y + 1 |> (z) => z * y)

const _g = (x) => x
|> (y => (y + 1 |> (z) => z * y))

const __g = (x) => x
|> (
y => {
return (y + 1 |> (z) => z * y);
}
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this random indentation makes it really hard to review this file 😛

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hard for me too :-p

I'm still getting the hang of how to indent pipelines. I'll see what I can do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see 4811f08 and let me know what you think.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah they look better. As a general rule, I think that if the pipeline body is multiline, ) should aligned with the | of |> and there should be a line break after (. SImilar to

return (
  x => y
);

Let's hope Prettier implements them soon 😛


expect( f(1)).toBe(4);
expect( _f(1)).toBe(4);
expect( g(1)).toBe(2);
expect( _g(1)).toBe(2);
expect(__g(1)).toBe(2);
@@ -0,0 +1,14 @@
var result = [5,10]
|> (_ => _.map(x => x * 2))
|> (_ => _.reduce( (a,b) => a + b ))
|> (sum => sum + 1)

expect(result).toBe(31);


var inc = (x) => x + 1;
var double = (x) => x * 2;

var result2 = [4, 9].map( x => x |> inc |> double )

expect(result2).toEqual([10, 20]);
@@ -0,0 +1,14 @@
var result = [5,10]
|> (_ => _.map(x => x * 2))
|> (_ => _.reduce( (a,b) => a + b ))
|> (sum => sum + 1)

expect(result).toBe(31);
thiagoarrais marked this conversation as resolved.
Show resolved Hide resolved


var inc = (x) => x + 1;
var double = (x) => x * 2;

var result2 = [4, 9].map( x => x |> inc |> double )

expect(result2).toEqual([10, 20]);
@@ -0,0 +1,15 @@
var _ref, _ref2, _ref3;

var result = (_ref = (_ref2 = (_ref3 = [5, 10], _ref3.map(x => x * 2)), _ref2.reduce((a, b) => a + b)), _ref + 1);
expect(result).toBe(31);

var inc = x => x + 1;

var double = x => x * 2;

var result2 = [4, 9].map(x => {
var _ref4, _x;

return double(inc(x));
});
expect(result2).toEqual([10, 20]);
@@ -0,0 +1,13 @@
const triple = (x) => x * 3;

async function myFunction(n) {
return n
|> Math.abs
|> Promise.resolve
|> await
|> triple;
}

return myFunction(-7).then(result => {
expect(result).toBe(21);
});
@@ -0,0 +1,6 @@
async function myFunction(n) {
return n
|> Math.abs
|> Promise.resolve
|> await;
}
thiagoarrais marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,9 @@
{
"plugins": [
["proposal-pipeline-operator", { "proposal": "fsharp" }]
],
"parserOpts": {
"allowReturnOutsideFunction": true
},
"minNodeVersion": "8.0.0"
}
@@ -0,0 +1,5 @@
async function myFunction(n) {
var _ref, _ref2, _n;

return _ref = (_ref2 = (_n = n, Math.abs(_n)), Promise.resolve(_ref2)), await _ref;
}
@@ -0,0 +1,3 @@
var inc = (x) => x + 1

expect(10 |> inc).toBe(11);
@@ -0,0 +1,3 @@
var inc = (x) => x + 1

expect(10 |> inc).toBe(11);
@@ -0,0 +1,5 @@
var _;

var inc = x => x + 1;

expect(inc(10)).toBe(11);
@@ -0,0 +1,7 @@
(function() {
'use strict';
var result = '(function() { return this; })()'
|> eval;

expect(result).not.toBeUndefined();
})();
@@ -0,0 +1,7 @@
(function() {
'use strict';
var result = '(function() { return this; })()'
|> eval;

expect(result).not.toBeUndefined();
})();
@@ -0,0 +1,8 @@
(function () {
'use strict';

var _functionReturn;

var result = (_functionReturn = '(function() { return this; })()', (0, eval)(_functionReturn));
expect(result).not.toBeUndefined();
})();
@@ -0,0 +1,5 @@
var array = [10,20,30];

var last = array |> (a => a[a.length-1]);

expect(last).toBe(30);
@@ -0,0 +1,5 @@
var _array;

var array = [10, 20, 30];
var last = (_array = array, _array[_array.length - 1]);
expect(last).toBe(30);
@@ -0,0 +1,8 @@
var a = 1,
b = 2,
c = 3;
var result = a
|> (() => b)
|> (() => c);

expect(result).toBe(c);
@@ -0,0 +1,8 @@
var a = 1,
b = 2,
c = 3;
var result = a
|> (() => b)
|> (() => c);

expect(result).toBe(c);
@@ -0,0 +1,7 @@
var _ref, _a;

var a = 1,
b = 2,
c = 3;
var result = ((a, b), c);
expect(result).toBe(c);
@@ -0,0 +1,8 @@
{
"plugins": [
["proposal-pipeline-operator", { "proposal": "fsharp" }]
],
"parserOpts": {
"allowReturnOutsideFunction": true
}
}
@@ -0,0 +1,20 @@
const y = 2;
const f = (x) => x
|> (y => y + 1)
|> (z => z * y)

const g = (x) => x
|> (y =>
y + 1
|> (z => z * y)
)

const h = (x) => x
|> (y => (
y + 1
|> (z => z * y)
))

expect(f(1)).toBe(4);
expect(g(1)).toBe(2);
expect(h(1)).toBe(2);
@@ -0,0 +1,20 @@
const y = 2;
const f = (x) => x
|> (y => y + 1)
|> (z => z * y)

const g = (x) => x
|> (y =>
y + 1
|> (z => z * y)
)

const h = (x) => x
|> (y => (
y + 1
|> (z => z * y)
))

expect(f(1)).toBe(4);
expect(g(1)).toBe(2);
expect(h(1)).toBe(2);