Merge pull request #6 from mdgriffith/modernize-js-and-fixes

added arrow func transformation and native object merge
This commit is contained in:
Matthew Griffith 2020-07-28 08:41:49 -04:00 committed by GitHub
commit 3d73f2ffe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 505 additions and 635 deletions

View File

@ -15,6 +15,11 @@ import {
createInlineListFromArrayTransformer, createInlineListFromArrayTransformer,
} from './experiments/inlineListFromArray'; } from './experiments/inlineListFromArray';
import {
replaceUtilsUpdateWithObjectSpread,
convertFunctionExpressionsToArrowFuncs,
} from './experiments/modernizeJS';
// Compile examples in `testcases/*` folder as js // Compile examples in `testcases/*` folder as js
// Run whatever transformations we want on them, saving steps as `elm.{transformation}.js` // Run whatever transformations we want on them, saving steps as `elm.{transformation}.js`
compile(['Main.elm'], { compile(['Main.elm'], {
@ -54,9 +59,9 @@ const customTypeTransformer = createCustomTypesTransformer(
Mode.Prod Mode.Prod
); );
const collectedSplits: FuncSplit[] = []; const collectedSplits = new Map<string, FuncSplit>();
const splitTransformer = createSplitFunctionDeclarationsTransformer(split => const splitTransformer = createSplitFunctionDeclarationsTransformer(
collectedSplits.push(split) collectedSplits
); );
const funcInlineTransformer = createFuncInlineTransformer(collectedSplits); const funcInlineTransformer = createFuncInlineTransformer(collectedSplits);
@ -70,6 +75,8 @@ const [result] = ts.transform(source, [
splitTransformer, splitTransformer,
funcInlineTransformer, funcInlineTransformer,
inlineListFromArrayCalls, inlineListFromArrayCalls,
replaceUtilsUpdateWithObjectSpread,
convertFunctionExpressionsToArrowFuncs,
]).transformed; ]).transformed;
const printer = ts.createPrinter(); const printer = ts.createPrinter();

View File

@ -1,7 +1,6 @@
import ts from 'typescript'; import ts from 'typescript';
export type FuncSplit = { export type FuncSplit = {
originalName: string;
rawLambdaName: string; rawLambdaName: string;
arity: number; arity: number;
}; };
@ -12,12 +11,18 @@ const deriveRawLambdaName = (wrappedName: string): string =>
const wrapperRegex = /F(?<arity>[1-9]+[0-9]*)/; const wrapperRegex = /F(?<arity>[1-9]+[0-9]*)/;
export const createSplitFunctionDeclarationsTransformer = ( export const createSplitFunctionDeclarationsTransformer = (
reportSplit: (split: FuncSplit) => void splits: Map<string, FuncSplit>
): ts.TransformerFactory<ts.SourceFile> => context => { ): ts.TransformerFactory<ts.SourceFile> => context => {
return sourceFile => { return sourceFile => {
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => { const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
// detects "var a" // detects "var a"
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
if (node.initializer && ts.isIdentifier(node.initializer)) {
const existingSplit = splits.get(node.initializer.text);
if (existingSplit) {
splits.set(node.name.text, existingSplit);
}
}
// detects "var a = [exp](..)" // detects "var a = [exp](..)"
if (node.initializer && ts.isCallExpression(node.initializer)) { if (node.initializer && ts.isCallExpression(node.initializer)) {
const callExpression = node.initializer.expression; const callExpression = node.initializer.expression;
@ -31,6 +36,16 @@ export const createSplitFunctionDeclarationsTransformer = (
if (args.length === 1) { if (args.length === 1) {
const [maybeFuncExpression] = args; const [maybeFuncExpression] = args;
const arity = Number(maybeMatch.groups.arity);
const originalName = node.name.text;
if (ts.isIdentifier(maybeFuncExpression)) {
splits.set(originalName, {
arity,
rawLambdaName: maybeFuncExpression.text,
});
}
// and it is a function // and it is a function
// detects "var a = F123( function (a) {return a})" // detects "var a = F123( function (a) {return a})"
// or "var a = F123( a => a)" // or "var a = F123( a => a)"
@ -39,11 +54,12 @@ export const createSplitFunctionDeclarationsTransformer = (
ts.isFunctionExpression(maybeFuncExpression) ts.isFunctionExpression(maybeFuncExpression)
) { ) {
// TODO typecheck? // TODO typecheck?
const arity = Number(maybeMatch.groups.arity);
const originalName = node.name.text;
const rawLambdaName = deriveRawLambdaName(originalName); const rawLambdaName = deriveRawLambdaName(originalName);
reportSplit({ arity, originalName, rawLambdaName }); splits.set(originalName, {
arity,
rawLambdaName,
});
const lambdaDeclaration = ts.createVariableDeclaration( const lambdaDeclaration = ts.createVariableDeclaration(
rawLambdaName, rawLambdaName,
@ -78,7 +94,7 @@ export const createSplitFunctionDeclarationsTransformer = (
const invocationRegex = /A(?<arity>[1-9]+[0-9]*)/; const invocationRegex = /A(?<arity>[1-9]+[0-9]*)/;
export const createFuncInlineTransformer = ( export const createFuncInlineTransformer = (
splits: FuncSplit[] splits: Map<string, FuncSplit>
): ts.TransformerFactory<ts.SourceFile> => context => { ): ts.TransformerFactory<ts.SourceFile> => context => {
return sourceFile => { return sourceFile => {
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => { const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
@ -103,17 +119,17 @@ export const createFuncInlineTransformer = (
if (args.length !== arity) { if (args.length !== arity) {
throw new Error( throw new Error(
`somerhing went wrong, expected number of arguments=${arity} but got ${args.length} for ${funcName.text}` `something went wrong, expected number of arguments=${arity} but got ${args.length} for ${funcName.text}`
); );
} }
const split = splits.find(s => s.originalName === funcName.text); const split = splits.get(funcName.text);
if (split && split.arity === arity) { if (split && split.arity === arity) {
return ts.createCall( return ts.createCall(
ts.createIdentifier(split.rawLambdaName), ts.createIdentifier(split.rawLambdaName),
undefined, undefined,
args args.map(arg => ts.visitNode(arg, visitor))
); );
} }
} }

View File

@ -0,0 +1,106 @@
import ts from 'typescript';
export const replaceUtilsUpdateWithObjectSpread: ts.TransformerFactory<ts.SourceFile> = context => {
return sourceFile => {
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
// detects function f(..){..}
if (
ts.isFunctionDeclaration(node) &&
node.name?.text === '_Utils_update'
) {
return ts.createVariableStatement(
undefined,
ts.createVariableDeclarationList(
[
ts.createVariableDeclaration(
ts.createIdentifier('_Utils_update'),
undefined,
ts.createArrowFunction(
undefined,
undefined,
[
ts.createParameter(
undefined,
undefined,
undefined,
ts.createIdentifier('oldRecord'),
undefined,
undefined,
undefined
),
ts.createParameter(
undefined,
undefined,
undefined,
ts.createIdentifier('updatedFields'),
undefined,
undefined,
undefined
),
],
undefined,
undefined,
ts.createObjectLiteral(
[
ts.createSpreadAssignment(
ts.createIdentifier('oldRecord')
),
ts.createSpreadAssignment(
ts.createIdentifier('updatedFields')
),
],
false
)
)
),
],
ts.NodeFlags.Const
)
);
}
return ts.visitEachChild(node, visitor, context);
};
return ts.visitNode(sourceFile, visitor);
};
};
export const convertFunctionExpressionsToArrowFuncs: ts.TransformerFactory<ts.SourceFile> = context => {
return sourceFile => {
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
// console.log(
// `Visiting: ${ts.SyntaxKind[node.kind]} with name ${
// (node as any).name?.text
// }`
// );
if (
ts.isFunctionExpression(node) &&
node.name === undefined &&
node.body.statements.length === 1
) {
// console.log('$$body', node.body.getText());
const [returnStatement] = node.body.statements;
if (
ts.isReturnStatement(returnStatement) &&
returnStatement.expression !== undefined
) {
return ts.createArrowFunction(
undefined,
undefined,
node.parameters,
undefined,
undefined,
ts.visitNode(returnStatement.expression, visitor)
// returnStatement.expression
);
}
}
return ts.visitEachChild(node, visitor, context);
};
return ts.visitNode(sourceFile, visitor);
};
};

View File

@ -11,6 +11,7 @@ import {
createInlineListFromArrayTransformer, createInlineListFromArrayTransformer,
InlineMode, InlineMode,
} from './experiments/inlineListFromArray'; } from './experiments/inlineListFromArray';
import { convertFunctionExpressionsToArrowFuncs } from './experiments/modernizeJS';
const elmOutput = ` const elmOutput = `
var $elm$core$Maybe$Nothing = {$: 'Nothing'}; var $elm$core$Maybe$Nothing = {$: 'Nothing'};
@ -27,6 +28,16 @@ var $author$project$Main$Three = F3(
var _v1 = A3($author$project$Main$Three, a, b, c); var _v1 = A3($author$project$Main$Three, a, b, c);
_List_fromArray(['a', 'b', 'c']); _List_fromArray(['a', 'b', 'c']);
function _List_Cons(hd, tl) {
return { $: 1, a: hd, b: tl };
}
var _List_cons = F2(_List_Cons);
var $elm$core$List$cons = _List_cons;
A2($elm$core$List$cons, key, keyList);
$elm$core$String$join_raw("\n\n", A2($elm$core$List$cons, introduction, A2($elm$core$List$indexedMap, $elm$json$Json$Decode$errorOneOf, errors)));
`; `;
const source = ts.createSourceFile('elm.js', elmOutput, ts.ScriptTarget.ES2018); const source = ts.createSourceFile('elm.js', elmOutput, ts.ScriptTarget.ES2018);
@ -71,9 +82,9 @@ console.log(printer.printFile(source));
console.log(printer.printFile(newFile)); console.log(printer.printFile(newFile));
console.log('----------AFTER SPLIT TRANSFORM ----------------'); console.log('----------AFTER SPLIT TRANSFORM ----------------');
const collectedSplits: FuncSplit[] = []; const collectedSplits = new Map<string, FuncSplit>();
const splitTransformer = createSplitFunctionDeclarationsTransformer(split => const splitTransformer = createSplitFunctionDeclarationsTransformer(
collectedSplits.push(split) collectedSplits
); );
const [sourceWithSplittedFunctions] = ts.transform(newFile, [ const [sourceWithSplittedFunctions] = ts.transform(newFile, [
splitTransformer, splitTransformer,
@ -103,3 +114,22 @@ const [sourceWithInlinedListFromArr] = ts.transform(
).transformed; ).transformed;
console.log(printer.printFile(sourceWithInlinedListFromArr)); console.log(printer.printFile(sourceWithInlinedListFromArr));
const funcSource = ts.createSourceFile(
'elm.js',
`
function F3(fun) {
return F(3, fun, function (a) {
return function (b) { return function (c) { return fun(a, b, c); }; };
});
}
`,
ts.ScriptTarget.ES2018
);
console.log('---------- Arrow Transformation ----------------');
const [fRes] = ts.transform(funcSource, [
convertFunctionExpressionsToArrowFuncs,
]).transformed;
console.log(printer.printFile(fRes));

View File

@ -37,7 +37,13 @@ addMyType mine sum =
main = main =
let let
f x =
addMyType x
g =
f
sum = sum =
List.foldl addMyType 0 many List.foldl g 0 many
in in
Html.text (String.fromInt sum) Html.text (String.fromInt sum)

View File

@ -4522,7 +4522,11 @@ var $author$project$Main$many = $elm$core$List$concat(
var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text; var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text;
var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text; var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text;
var $author$project$Main$main = function () { var $author$project$Main$main = function () {
var sum = A3($elm$core$List$foldl, $author$project$Main$addMyType, 0, $author$project$Main$many); var f = function (x) {
return $author$project$Main$addMyType(x);
};
var g = f;
var sum = A3($elm$core$List$foldl, g, 0, $author$project$Main$many);
return $elm$html$Html$text( return $elm$html$Html$text(
$elm$core$String$fromInt(sum)); $elm$core$String$fromInt(sum));
}(); }();

View File

@ -3741,12 +3741,11 @@
var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text; var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text;
var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text; var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text;
var $author$project$Main$main = (function() { var $author$project$Main$main = (function() {
var sum = A3( var f = function(x) {
$elm$core$List$foldl, return $author$project$Main$addMyType(x);
$author$project$Main$addMyType, };
0, var g = f;
$author$project$Main$many var sum = A3($elm$core$List$foldl, g, 0, $author$project$Main$many);
);
return $elm$html$Html$text($elm$core$String$fromInt(sum)); return $elm$html$Html$text($elm$core$String$fromInt(sum));
})(); })();
_Platform_export({ _Platform_export({

File diff suppressed because it is too large Load Diff