UNPKG

9.92 kBJavaScriptView Raw
1const {
2 Expr,
3 Token,
4 Setter,
5 Expression,
6 SetterExpression,
7 SpliceSetterExpression,
8 TokenTypeData,
9 Clone
10} = require('./lang');
11const _ = require('lodash');
12const SimpleCompiler = require('./simple-compiler');
13const {topologicalSortGetters, pathMatches} = require('./expr-tagging');
14
15class 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 // tracks.push(`// invalidates - ${this.invalidates(expr)}`)
233 const pathsThatInvalidate = expr[0].$path;
234 if (pathsThatInvalidate) {
235 //console.log(pathsThatInvalidate);
236 pathsThatInvalidate.forEach((cond, invalidatedPath) => {
237 // tracks.push(
238 // `// invalidatedPath: ${JSON.stringify(invalidatedPath)}, ${JSON.stringify(cond)}, ${
239 // invalidatedPath[invalidatedPath.length - 1].$type
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 // settersMatched.forEach(setter => tracks.push(`// path matched ${JSON.stringify(setter)}`));
273 tracks.push(
274 `${precond} trackPath($tracked, [${invalidatedPath
275 .map(fragment => this.generateExpr(fragment))
276 .join(',')}]);`
277 );
278 }
279 }
280 //tracks.push(`// tracking ${JSON.stringify(invalidatedPath)}`);
281 });
282 }
283 return tracks.join('\n');
284 }
285}
286
287module.exports = OptimizingCompiler;