1 | const _ = require('lodash');
|
2 | const {flattenExpression, searchExpressions, searchExpressionsWithoutInnerFunctions, getAllFunctions, flattenExpressionWithoutInnerFunctions} = require('./expr-search');
|
3 | const {memoizeExprFunc, memoize} = require('./memoize');
|
4 | const {exprHash, hashString} = require('./expr-hash');
|
5 | const {TokenTypeData, Expression, Get, Expr, TopLevel, Token, Invoke, FuncArg, Func} = require('./lang');
|
6 | const {generateName, generateNameFromTag} = require('./expr-names');
|
7 | const objectHash = require('object-hash');
|
8 |
|
9 | const countTokens = memoizeExprFunc(expr => _.sum(expr.map(countTokens)), () => 1)
|
10 |
|
11 | function tryToHoist(expr) {
|
12 | return TokenTypeData[expr[0].$type].tryToHoist;
|
13 | }
|
14 |
|
15 | const isStaticExpression = memoize(expr => {
|
16 | const res = true;
|
17 | const areChildrenStatic = expr.map(token => {
|
18 | if (token instanceof Expression) {
|
19 | return isStaticExpression(token);
|
20 | } else if (token.$type === 'val' || token.$type === 'key' || token.$type === 'context') {
|
21 | return false;
|
22 | }
|
23 | return true;
|
24 | });
|
25 | return _.every(areChildrenStatic, (isChildStatic, index) => isChildStatic || expr[index] instanceof Expression && expr[index][0].$type === 'func');
|
26 | });
|
27 |
|
28 | const getRewriteUsingTopLevels = namesByExpr => {
|
29 | const rewriteUsingTopLevels = memoizeExprFunc(
|
30 | expr => {
|
31 | const str = exprHash(expr);
|
32 | if (namesByExpr[str]) {
|
33 | return Expr(Get, namesByExpr[str], TopLevel);
|
34 | }
|
35 | return expr.map(child => rewriteUsingTopLevels(child));
|
36 | },
|
37 | token => token
|
38 | );
|
39 | return rewriteUsingTopLevels;
|
40 | };
|
41 |
|
42 |
|
43 | function rewriteStaticsToTopLevels(getters) {
|
44 | const allExpressions = flattenExpression(...Object.values(getters));
|
45 | const allStaticExpressions = _.filter(allExpressions, isStaticExpression);
|
46 | const allStaticAsStrings = allStaticExpressions.reduce((acc, e) => {
|
47 | acc[exprHash(e)] = e;
|
48 | return acc;
|
49 | }, {});
|
50 | const namesByExpr = _(getters)
|
51 | .mapValues(e => exprHash(e))
|
52 | .invert()
|
53 | .value();
|
54 | let nodeIndex = 1;
|
55 | _.forEach(allStaticAsStrings, (e, s) => {
|
56 | if (!namesByExpr[s] && tryToHoist(e)) {
|
57 | namesByExpr[s] = `$${e[0].$type}${generateName(namesByExpr, e)}${nodeIndex++}`;
|
58 | }
|
59 | });
|
60 | const rewriteUsingTopLevels = getRewriteUsingTopLevels(namesByExpr);
|
61 | const newGetters = {};
|
62 | _.forEach(namesByExpr, (name, hash) => {
|
63 | newGetters[name] = allStaticAsStrings[hash].map(rewriteUsingTopLevels);
|
64 | });
|
65 | _.forEach(getters, (expr, name) => {
|
66 | if (!newGetters[name]) {
|
67 | newGetters[name] = Expr(Get, namesByExpr[exprHash(expr)], TopLevel);
|
68 | }
|
69 | })
|
70 | return newGetters;
|
71 | }
|
72 |
|
73 | function rewriteLocalsToFunctions(getters) {
|
74 | const exprs = flattenExpression(...Object.values(getters));
|
75 |
|
76 |
|
77 | const parentMap = new Map();
|
78 | searchExpressions(expr => {
|
79 | if (expr[0].$type !== 'func') {
|
80 | expr.forEach(child => {
|
81 | if (child instanceof Expression) {
|
82 | if (!parentMap.has(child)) {
|
83 | parentMap.set(child, []);
|
84 | }
|
85 | parentMap.get(child).push(expr);
|
86 | }
|
87 | })
|
88 | }
|
89 | }, Object.values(getters))
|
90 | const countIdenticals = {};
|
91 | exprs.forEach(e => {
|
92 | const parents = parentMap.get(e);
|
93 |
|
94 | if (e instanceof Expression && e[0].$type !== 'func' && parents && parents.length > 1) {
|
95 | const hash = exprHash(e);
|
96 | const children = flattenExpressionWithoutInnerFunctions(e);
|
97 |
|
98 | countIdenticals[hash] = {counter: parents.length, children}
|
99 | }
|
100 | });
|
101 |
|
102 | const newGetters = {};
|
103 | const namesByHash = {};
|
104 | const localTokens = {
|
105 | val: true,
|
106 | key: true,
|
107 | context: true,
|
108 | loop: true
|
109 | }
|
110 |
|
111 | function rewriteExpr(e) {
|
112 | if (e instanceof Expression) {
|
113 | const hash = exprHash(e);
|
114 | const found = countIdenticals[hash];
|
115 | if (found && found.counter > 2 && found.children.length > 4) {
|
116 | const name = namesByHash[hash] ? namesByHash[hash] : `$$${generateNameFromTag(e)}${hash}`;
|
117 | if (!namesByHash[name]) {
|
118 | const tokens = _(found.children)
|
119 | .flatten()
|
120 | .filter(t => t instanceof Token && localTokens[t.$type])
|
121 | .map(t => t.$type)
|
122 | .uniq()
|
123 | .map(t => new Token(t))
|
124 | .value()
|
125 | found.tokens = tokens;
|
126 | namesByHash[hash] = name;
|
127 | newGetters[name] = Expr(Func, Expr(...e.map(rewriteExpr)), ...found.tokens);
|
128 | }
|
129 | return Expr(Invoke, name, ...found.tokens.map(t => new Token(t.$type)));
|
130 | }
|
131 | return Expr(...e.map(rewriteExpr));
|
132 | }
|
133 | return e;
|
134 | }
|
135 |
|
136 | Object.assign(newGetters, _.mapValues(getters, rewriteExpr))
|
137 | return newGetters;
|
138 | }
|
139 |
|
140 | function rewriteUniqueByHash(getters) {
|
141 | const exprs = flattenExpression(Object.values(getters));
|
142 | const allHashes = {};
|
143 | exprs.forEach(e => {
|
144 | const hash = exprHash(e);
|
145 | allHashes[hash] = allHashes[hash] || [];
|
146 | allHashes[hash].push(e)
|
147 | })
|
148 | const canonical = {};
|
149 | function getCanoncial(expr) {
|
150 | if (expr instanceof Expression) {
|
151 | const hash = exprHash(expr);
|
152 | if (!canonical.hasOwnProperty(hash)) {
|
153 | canonical[hash] = Expr(...expr.map(getCanoncial));
|
154 | }
|
155 | return canonical[hash];
|
156 | }
|
157 | return expr;
|
158 | }
|
159 | const newGetters = _.mapValues(getters, getCanoncial)
|
160 | return newGetters;
|
161 | }
|
162 |
|
163 | module.exports = {
|
164 | rewriteLocalsToFunctions,
|
165 | rewriteStaticsToTopLevels,
|
166 | rewriteUniqueByHash
|
167 | } |
\ | No newline at end of file |