1 | const {
|
2 | Expr,
|
3 | Token,
|
4 | Setter,
|
5 | Expression,
|
6 | SetterExpression,
|
7 | SpliceSetterExpression,
|
8 | TokenTypeData,
|
9 | Clone
|
10 | } = require('./lang');
|
11 | const _ = require('lodash');
|
12 | const SimpleCompiler = require('./simple-compiler');
|
13 | const {topologicalSortGetters, pathMatches} = require('./expr-tagging');
|
14 |
|
15 | class OptimizingCompiler extends SimpleCompiler {
|
16 | get template() {
|
17 | return require('./templates/optimizing.js');
|
18 | }
|
19 |
|
20 | topLevelOverrides() {
|
21 | return Object.assign({}, super.topLevelOverrides(), {
|
22 | RESET: `$first = false;
|
23 | $tainted = new WeakSet();
|
24 | `,
|
25 | DERIVED: 'updateDerived()'
|
26 | });
|
27 | }
|
28 |
|
29 | allExpressions() {
|
30 | const realGetters = []
|
31 | Object.keys(this.getters).forEach(name => {
|
32 | const index = this.topLevelToIndex(name);
|
33 | if (typeof index === 'number') {
|
34 | realGetters[index] = name;
|
35 | }
|
36 | });
|
37 | const countTopLevels = realGetters.length;
|
38 |
|
39 | return `
|
40 | const $topLevel = new Array(${countTopLevels}).fill(null);
|
41 | ${super.allExpressions()}
|
42 | ${
|
43 | this.mergeTemplate(this.template.updateDerived, {
|
44 | COUNT_GETTERS: () => countTopLevels,
|
45 | BUILDER_FUNCS: () => realGetters.map(name => `$${name}Build`).join(','),
|
46 | BUILDER_NAMES: () => realGetters.map(name => this.options.debug || name.indexOf('$') !== 0 ? JSON.stringify(name) : '""').join(',')
|
47 | })
|
48 | }
|
49 | `
|
50 | }
|
51 |
|
52 | byTokenTypesPlaceHolders(expr) {
|
53 | const currentToken = expr instanceof Expression ? expr[0] : expr;
|
54 | const tokenType = currentToken.$type;
|
55 | switch (tokenType) {
|
56 | case 'object':
|
57 | return {
|
58 | ARGS: () => _.range(1, expr.length, 2)
|
59 | .map(i => `'${expr[i]}'`)
|
60 | .join(',')
|
61 | };
|
62 | }
|
63 | return {};
|
64 | }
|
65 |
|
66 | exprTemplatePlaceholders(expr, funcName) {
|
67 | const currentToken = expr instanceof Expression ? expr[0] : expr;
|
68 | const tokenType = currentToken.$type;
|
69 | return Object.assign(
|
70 | {},
|
71 | super.exprTemplatePlaceholders(expr, funcName),
|
72 | {
|
73 | TOP_LEVEL_INDEX: () => `${this.getters[funcName] ? this.topLevelToIndex(funcName) : -1}`,
|
74 | TRACKING: () => this.tracking(expr),
|
75 | PRETRACKING: () => {
|
76 | if (expr[0].$path && expr[0].$path.size) {
|
77 | const conditionals = expr[0].$trackedExpr ?
|
78 | Array.from(expr[0].$trackedExpr.values()).map(cond => `let $cond_${cond} = 0;`) :
|
79 | [];
|
80 | return `${conditionals.join('')}`;
|
81 | }
|
82 | return '';
|
83 | },
|
84 | INVALIDATES: () => !!this.invalidates(expr),
|
85 | FN_ARGS: () => expr[0].$type === 'func' ? `${expr.slice(2).map(t => `,${t.$type}`).join('')} ` : ' '
|
86 | },
|
87 | this.byTokenTypesPlaceHolders(expr)
|
88 | );
|
89 | }
|
90 |
|
91 | wrapExprCondPart(expr, indexInExpr) {
|
92 | if (!expr[0].$tracked) {
|
93 | return `(${this.generateExpr(expr[indexInExpr])})`;
|
94 | }
|
95 | return `(($cond_${expr[0].$id} = ${indexInExpr}) && ${this.generateExpr(expr[indexInExpr])})`;
|
96 | }
|
97 |
|
98 | uniqueId(expr, extra = '') {
|
99 | return `${extra ? '-' : ''}${expr[0].$id}`;
|
100 | }
|
101 |
|
102 | generateExpr(expr) {
|
103 | const currentToken = expr instanceof Expression ? expr[0] : expr;
|
104 | const tokenType = currentToken.$type;
|
105 | switch (tokenType) {
|
106 | case 'get':
|
107 | if (expr[2] instanceof Token && expr[2].$type === 'topLevel') {
|
108 | const commentStr = this.options.debug ? ` /*${this.generateExpr(expr[1])}*/` : '';
|
109 | return `${this.generateExpr(expr[2])}[${this.topLevelToIndex(expr[1])}${commentStr}]`;
|
110 | }
|
111 | return super.generateExpr(expr)
|
112 | case 'topLevel':
|
113 | return '$topLevel';
|
114 | case 'and':
|
115 | return (
|
116 | `(${
|
117 | expr
|
118 | .slice(1)
|
119 | .map((t, index) => this.wrapExprCondPart(expr, index + 1))
|
120 | .join('&&')
|
121 | })`
|
122 | );
|
123 | case 'or':
|
124 | return (
|
125 | `(${
|
126 | expr
|
127 | .slice(1)
|
128 | .map((t, index) => this.wrapExprCondPart(expr, index + 1))
|
129 | .join('||')
|
130 | })`
|
131 | );
|
132 | case 'ternary':
|
133 | return `((${this.generateExpr(expr[1])})?${this.wrapExprCondPart(expr, 2)}:(${this.wrapExprCondPart(
|
134 | expr,
|
135 | 3
|
136 | )}))`;
|
137 | case 'object':
|
138 | return `object($tracked,[${
|
139 | _.range(2, expr.length, 2).map(idx => this.generateExpr(expr[idx])).join(',')
|
140 | }], ${this.uniqueId(expr)}, object$${
|
141 | expr[0].$duplicate ? expr[0].$duplicate : expr[0].$id
|
142 | }Args)`;
|
143 | case 'array':
|
144 | return `array($tracked,${super.generateExpr(expr)}, ${this.uniqueId(expr)}, ${expr.length -
|
145 | 1})`;
|
146 | case 'call':
|
147 | return `call($tracked,[${expr
|
148 | .slice(1)
|
149 | .map(subExpr => this.generateExpr(subExpr))
|
150 | .join(',')}], ${this.uniqueId(expr)}, ${expr.length - 1})`;
|
151 | case 'bind':
|
152 | return `bind($tracked,[${expr
|
153 | .slice(1)
|
154 | .map(subExpr => this.generateExpr(subExpr))
|
155 | .join(',')}], ${this.uniqueId(expr)}, ${expr.length - 1})`;
|
156 | case 'keys':
|
157 | case 'values':
|
158 | case 'sum':
|
159 | case 'flatten':
|
160 | case 'size':
|
161 | case 'assign':
|
162 | case 'defaults':
|
163 | return `${tokenType}Opt($tracked, ${this.generateExpr(expr[1])}, ${this.uniqueId(expr)})`;
|
164 | case 'range':
|
165 | return `range($tracked, ${this.generateExpr(expr[1])}, ${
|
166 | expr.length > 2 ? this.generateExpr(expr[2]) : '0'
|
167 | }, ${expr.length > 3 ? this.generateExpr(expr[3]) : '1'}, ${this.uniqueId(expr)})`;
|
168 | case 'filterBy':
|
169 | case 'mapValues':
|
170 | case 'groupBy':
|
171 | case 'map':
|
172 | case 'filter':
|
173 | case 'mapKeys':
|
174 | case 'any':
|
175 | case 'keyBy':
|
176 | case 'anyValues':
|
177 | case 'recursiveMap':
|
178 | case 'recursiveMapValues':
|
179 | return `${tokenType}Opt($tracked, ${this.uniqueId(expr)}, ${this.generateExpr(expr[1])}, ${this.generateExpr(
|
180 | expr[2]
|
181 | )}, ${
|
182 | typeof expr[3] === 'undefined' || expr[3] instanceof Token && expr[3].$type === 'null' ?
|
183 | null :
|
184 | `array($tracked,[${this.generateExpr(expr[3])}],${this.uniqueId(expr, 'arr')},1)`
|
185 | })`;
|
186 | case 'context':
|
187 | return 'context[0]';
|
188 | case 'recur':
|
189 | return `${this.generateExpr(expr[1])}.recursiveSteps(${this.generateExpr(expr[2])}, $tracked)`;
|
190 | case 'func':
|
191 | return expr[0].$duplicate ? expr[0].$duplicate : expr[0].$funcId;
|
192 | case 'invoke':
|
193 | return `${expr[1]}($tracked${expr.slice(2).map(t => `,${t.$type}`).join('')})`
|
194 | default:
|
195 | return super.generateExpr(expr);
|
196 | }
|
197 | }
|
198 |
|
199 | isStaticObject(expr) {
|
200 | return _.range(1, expr.length, 2)
|
201 | .every(idx => typeof expr[idx] === 'string')
|
202 | }
|
203 |
|
204 | buildExprFunctionsByTokenType(acc, expr) {
|
205 | const tokenType = expr[0].$type;
|
206 | if (expr[0].$duplicate) {
|
207 | return;
|
208 | }
|
209 | switch (tokenType) {
|
210 | case 'object':
|
211 | if (this.isStaticObject(expr)) {
|
212 | this.appendExpr(acc, tokenType, expr, `${tokenType}$${expr[0].$id}`);
|
213 | }
|
214 | break;
|
215 | default:
|
216 | super.buildExprFunctionsByTokenType(acc, expr);
|
217 | }
|
218 | }
|
219 |
|
220 | invalidates(expr) {
|
221 | return expr[0].$invalidates;
|
222 | }
|
223 |
|
224 | pathOfExpr(expr) {
|
225 | return [new Token('topLevel'), expr[0].$rootName].concat(
|
226 | new Array(Math.min(expr[0].$depth, 1)).fill(new Token('key'))
|
227 | );
|
228 | }
|
229 |
|
230 | tracking(expr) {
|
231 | const tracks = [];
|
232 |
|
233 | const pathsThatInvalidate = expr[0].$path;
|
234 | if (pathsThatInvalidate) {
|
235 |
|
236 | pathsThatInvalidate.forEach((cond, invalidatedPath) => {
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | const precond = cond && cond !== true ? `(${this.generateExpr(cond)} ) && ` : '';
|
243 | if (invalidatedPath[0].$type === 'context') {
|
244 | const activePath = [0].concat(invalidatedPath.slice(1));
|
245 | tracks.push(
|
246 | `${precond} trackPath($tracked, [context, ${activePath
|
247 | .map(fragment => this.generateExpr(fragment))
|
248 | .join(',')}]);`
|
249 | );
|
250 | } else if (invalidatedPath.length > 1 && invalidatedPath[0].$type === 'topLevel') {
|
251 | tracks.push(
|
252 | `${precond} trackPath($tracked, [${invalidatedPath
|
253 | .map((fragment, index) => index === 1 ? this.topLevelToIndex(fragment) : this.generateExpr(fragment))
|
254 | .join(',')}]);`
|
255 | );
|
256 | } else if (invalidatedPath.length > 1 && invalidatedPath[0] instanceof Expression && invalidatedPath[0][0].$type === 'get' && invalidatedPath[0][2].$type === 'topLevel') {
|
257 | tracks.push(
|
258 | `${precond} trackPath($tracked, [${invalidatedPath
|
259 | .map(fragment => this.generateExpr(fragment))
|
260 | .join(',')}]);`
|
261 | );
|
262 | } else if (invalidatedPath.length > 1 &&
|
263 | (invalidatedPath[0] instanceof Expression && invalidatedPath[0][0].$type === 'invoke')) {
|
264 | tracks.push(
|
265 | `${precond} trackPath($tracked, [${invalidatedPath
|
266 | .map(fragment => this.generateExpr(fragment))
|
267 | .join(',')}]);`
|
268 | );
|
269 | } else if (invalidatedPath[0].$type === 'root' && invalidatedPath.length > 1) {
|
270 | const settersMatched = Object.values(this.setters).filter(setter => pathMatches(invalidatedPath, setter));
|
271 | if (settersMatched.length) {
|
272 |
|
273 | tracks.push(
|
274 | `${precond} trackPath($tracked, [${invalidatedPath
|
275 | .map(fragment => this.generateExpr(fragment))
|
276 | .join(',')}]);`
|
277 | );
|
278 | }
|
279 | }
|
280 |
|
281 | });
|
282 | }
|
283 | return tracks.join('\n');
|
284 | }
|
285 | }
|
286 |
|
287 | module.exports = OptimizingCompiler;
|