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

Array creation #64

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions src/transforms/elmArray/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// @ts-nocheck

/* Taken from the compiled Array creation code from:
* - Elm 0.19.1
* - elm/core v1.0.5
* - compiled by elm-optimize-level-2 v0.2.3
*
* Changes made:
* - Removed AX/FX wrapping
* - Removed unused functions
* - Replaced `$elm$core$Elm$JsArray$length(jsArray)` by `jsArray.length`
* - Replaced call to and comparison with `_Utils_cmp` by a `<` operation
*/

function _Utils_Tuple2(a, b) { return { a: a, b: b }; }
var $elm$core$Array$branchFactor = 32;


var _JsArray_initializeFromList_fn = function (max, ls) {
var result = new Array(max);
for (var i = 0; i < max && ls.b; i++) {
result[i] = ls.a;
ls = ls.b;
}
result.length = i;
return _Utils_Tuple2(result, ls);
}

var $elm$core$Array$Array_elm_builtin_fn = function (a, b, c, d) {
return { $: 0, a: a, b: b, c: c, d: d };
};
var _Basics_ceiling = Math.ceil;
var _Basics_floor = Math.floor;
var _Basics_log = Math.log;
var $elm$core$Basics$ceiling = _Basics_ceiling;
var $elm$core$Basics$floor = _Basics_floor;
var $elm$core$Basics$logBase_fn = function (base, number) {
return _Basics_log(number) / _Basics_log(base);
};
var $elm$core$Basics$max_fn = function (x, y) {
return x > y ? x : y;
}

var _List_Nil = { $: 0, a: null, b: null };
function _List_Cons(hd, tl) { return { $: 1, a: hd, b: tl }; }
var _List_cons = _List_Cons;
var $elm$core$List$cons = _List_cons;
function _List_fromArray(arr) {
var out = _List_Nil;
for (var i = arr.length; i--;) {
out = _List_Cons(arr[i], out);
}
return out;
}
var $elm$core$List$foldl_fn = function (func, acc, list) {
foldl: while (true) {
if (!list.b) {
return acc;
}
else {
var x = list.a;
var xs = list.b;
var $temp$func = func, $temp$acc = func(x, acc), $temp$list = xs;
func = $temp$func;
acc = $temp$acc;
list = $temp$list;
continue foldl;
}
}
}
var $elm$core$List$reverse = function (list) {
return $elm$core$List$foldl_fn($elm$core$List$cons, _List_Nil, list);
};

var _JsArray_empty = [];
var $elm$core$Elm$JsArray$empty = _JsArray_empty;
var $elm$core$Array$shiftStep = $elm$core$Basics$ceiling($elm$core$Basics$logBase_fn(2, $elm$core$Array$branchFactor));
var $elm$core$Array$SubTree = function (a) {
return { $: 0, a: a };
}
var $elm$core$Array$Leaf = function (a) {
return { $: 1, a: a };
}
var $elm$core$Array$compressNodes_fn = function (nodes, acc) {
compressNodes: while (true) {
var _v0 = _JsArray_initializeFromList_fn($elm$core$Array$branchFactor, nodes);
var node = _v0.a;
var remainingNodes = _v0.b;
var newAcc = _List_Cons($elm$core$Array$SubTree(node), acc);
if (!remainingNodes.b) {
return $elm$core$List$reverse(newAcc);
}
else {
var $temp$nodes = remainingNodes, $temp$acc = newAcc;
nodes = $temp$nodes;
acc = $temp$acc;
continue compressNodes;
}
}
}
var $elm$core$Array$treeFromBuilder_fn = function (nodeList, nodeListSize) {
treeFromBuilder: while (true) {
var newNodeSize = $elm$core$Basics$ceiling(nodeListSize / $elm$core$Array$branchFactor);
if (newNodeSize === 1) {
return _JsArray_initializeFromList_fn($elm$core$Array$branchFactor, nodeList).a;
}
else {
var $temp$nodeList = $elm$core$Array$compressNodes_fn(nodeList, _List_Nil), $temp$nodeListSize = newNodeSize;
nodeList = $temp$nodeList;
nodeListSize = $temp$nodeListSize;
continue treeFromBuilder;
}
}
}
var $elm$core$Array$builderToArray_fn = function (reverseNodeList, builder) {
if (!builder.k) {
return $elm$core$Array$Array_elm_builtin_fn(builder.n.length, $elm$core$Array$shiftStep, $elm$core$Elm$JsArray$empty, builder.n);
}
else {
var treeLen = builder.k * $elm$core$Array$branchFactor;
var depth = $elm$core$Basics$floor($elm$core$Basics$logBase_fn($elm$core$Array$branchFactor, treeLen - 1));
var correctNodeList = reverseNodeList ? $elm$core$List$reverse(builder.o) : builder.o;
var tree = $elm$core$Array$treeFromBuilder_fn(correctNodeList, builder.k);
return $elm$core$Array$Array_elm_builtin_fn(builder.n.length + treeLen, $elm$core$Basics$max_fn(5, depth * $elm$core$Array$shiftStep), tree, builder.n);
}
}

var $elm$core$Array$fromListHelp_fn = function (list, nodeList, nodeListSize) {
fromListHelp: while (true) {
var _v0 = _JsArray_initializeFromList_fn($elm$core$Array$branchFactor, list);
var jsArray = _v0.a;
var remainingItems = _v0.b;
if (jsArray.length < $elm$core$Array$branchFactor) {
return $elm$core$Array$builderToArray_fn(true, { o: nodeList, k: nodeListSize, n: jsArray });
}
else {
var $temp$list = remainingItems, $temp$nodeList = _List_Cons($elm$core$Array$Leaf(jsArray), nodeList), $temp$nodeListSize = nodeListSize + 1;
list = $temp$list;
nodeList = $temp$nodeList;
nodeListSize = $temp$nodeListSize;
continue fromListHelp;
}
}
};
var $elm$core$Array$empty = $elm$core$Array$Array_elm_builtin_fn(0, $elm$core$Array$shiftStep, $elm$core$Elm$JsArray$empty, $elm$core$Elm$JsArray$empty);
var $elm$core$Array$fromList = function (list) {
if (!list.b) {
return $elm$core$Array$empty;
}
else {
return $elm$core$Array$fromListHelp_fn(list, _List_Nil, 0);
}
};

/** End of copied code **/

export function toArray(list) {
return $elm$core$Array$fromList(_List_fromArray(list));
}
86 changes: 86 additions & 0 deletions src/transforms/inlineArrayFromListWithLiteral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import ts from 'typescript';
import { toArray } from './elmArray';

/* Pre-compute array constructed from a List literal, so that Arrays have no or little creation penalty.



initial

$elm$core$Array$fromList(
_List_fromArray(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));

transformed

{ $: 0, a: 10, b: 5, c: [], d: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] };

---

initial

$elm$core$Array$fromList(_List_Nil);

transformed

$elm$core$Array$empty;

*/

const LIST_EMPTY = '_List_Nil';
const LIST_FROM_ARRAY_F_NAME = '_List_fromArray';
const ARRAY_EMPTY = '$elm$core$Array$empty';
const ARRAY_FROM_LIST = "$elm$core$Array$fromList";

export const createArrayFromLiteralTransformer : ts.TransformerFactory<ts.SourceFile> = (context) => {
return (sourceFile) => {
const visitor = (originalNode: ts.Node): ts.VisitResult<ts.Node> => {
const node = ts.visitEachChild(originalNode, visitor, context);

if (ts.isCallExpression(node)
&& ts.isIdentifier(node.expression)
&& node.expression.text == ARRAY_FROM_LIST
) {
const [arg] = node.arguments;

if (ts.isCallExpression(arg)
&& ts.isIdentifier(arg.expression)
&& arg.expression.text == LIST_FROM_ARRAY_F_NAME
&& ts.isArrayLiteralExpression(arg.arguments[0])
) {
// @ts-ignore - Can't figure out why TypeScript is complaining here
return arrayToAst(toArray(arg.arguments[0].elements));
}
else if (ts.isIdentifier(arg)
&& arg.text == LIST_EMPTY) {
return ts.createIdentifier(ARRAY_EMPTY)
}
}
return node;
};

return ts.visitNode(sourceFile, visitor);
};
};

function arrayToAst(array: any) {
return ts.createObjectLiteral([
ts.createPropertyAssignment('$', ts.createLiteral(array.$)),
ts.createPropertyAssignment('a', ts.createLiteral(array.a)),
ts.createPropertyAssignment('b', ts.createLiteral(array.b)),
ts.createPropertyAssignment('c', ts.createArrayLiteral(array.c.map(treeToAst))),
ts.createPropertyAssignment('d', ts.createArrayLiteral(array.d)),
]);
}

function treeToAst(node: any) {
const subElements =
node.$ == 0
? ts.createArrayLiteral(node.a.map(treeToAst))
: ts.createArrayLiteral(node.a);

return ts.createObjectLiteral([
ts.createPropertyAssignment('$', ts.createLiteral(node.$)),
ts.createPropertyAssignment('a', subElements)
]);
}
104 changes: 104 additions & 0 deletions test/inlineArrayFromListWithLiteral.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import ts from 'typescript';

import { createArrayFromLiteralTransformer } from '../src/transforms/inlineArrayFromListWithLiteral';

test('it can replace an Array created with a literal List with a pre-computed array (10 items)', () => {
const initialCode = `
var $author$project$Api$someValue = $elm$core$Array$fromList(_List_fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
`;

const expectedOutputCode = `
var $author$project$Api$someValue = { $: 0, a: 10, b: 5, c: [], d: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] };
`;

const { actual, expected } = transformCode(
initialCode,
expectedOutputCode,
createArrayFromLiteralTransformer
);

expect(actual).toBe(expected);
});

test('it can replace an Array created with a literal List with a pre-computed array (40 items, above the 32 limit)', () => {
const initialCode = `
var $author$project$Api$someValue = $elm$core$Array$fromList(_List_fromArray([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]));
`;

const expectedOutputCode = `
var $author$project$Api$someValue = { $: 0, a: 40, b: 5, c: [{ $: 1, a: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32] }], d: [33, 34, 35, 36, 37, 38, 39, 40] };
`;

const { actual, expected } = transformCode(
initialCode,
expectedOutputCode,
createArrayFromLiteralTransformer
);

expect(actual).toBe(expected);
});

test('it can replace an Array created with a literal List with a pre-computed array (100 items, multiple levels)', () => {
const initialCode = `
var $author$project$Api$someValue = $elm$core$Array$fromList(_List_fromArray([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100]));
`;

const expectedOutputCode = `
var $author$project$Api$someValue = { $: 0, a: 100, b: 5, c: [{ $: 1, a: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32] }, { $: 1, a: [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64] }, { $: 1, a: [65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96] }], d: [97, 98, 99, 100] };`;

const { actual, expected } = transformCode(
initialCode,
expectedOutputCode,
createArrayFromLiteralTransformer
);

expect(actual).toBe(expected);
});

test('it can replace an Array on empty list by an empty array', () => {
const initialCode = `
var $author$project$Api$someValue = $elm$core$Array$fromList(_List_Nil);
`;

const expectedOutputCode = `
var $author$project$Api$someValue = $elm$core$Array$empty`;

const { actual, expected } = transformCode(
initialCode,
expectedOutputCode,
createArrayFromLiteralTransformer
);

expect(actual).toBe(expected);
});


export function transformCode(
initialCode: string,
expectedCode: string,
transformer: ts.TransformerFactory<ts.SourceFile>
): {
actual: string;
expected: string;
} {
const source = ts.createSourceFile(
'elm.js',
initialCode,
ts.ScriptTarget.ES2018
);

const printer = ts.createPrinter();

const [output] = ts.transform(source, [transformer]).transformed;

const expectedOutput = printer.printFile(
ts.createSourceFile('elm.js', expectedCode, ts.ScriptTarget.ES2018)
);

const printedOutput = printer.printFile(output);

return {
actual: printedOutput,
expected: expectedOutput,
};
}