UNPKG

9.9 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 'assign':
161 case 'defaults':
162 return `${tokenType}Opt($tracked, ${this.generateExpr(expr[1])}, ${this.uniqueId(expr)})`;
163 case 'range':
164 return `range($tracked, ${this.generateExpr(expr[1])}, ${
165 expr.length > 2 ? this.generateExpr(expr[2]) : '0'
166 }, ${expr.length > 3 ? this.generateExpr(expr[3]) : '1'}, ${this.uniqueId(expr)})`;
167 case 'filterBy':
168 case 'mapValues':
169 case 'groupBy':
170 case 'map':
171 case 'filter':
172 case 'mapKeys':
173 case 'any':
174 case 'keyBy':
175 case 'anyValues':
176 case 'recursiveMap':
177 case 'recursiveMapValues':
178 return `${tokenType}Opt($tracked, ${this.uniqueId(expr)}, ${this.generateExpr(expr[1])}, ${this.generateExpr(
179 expr[2]
180 )}, ${
181 typeof expr[3] === 'undefined' || expr[3] instanceof Token && expr[3].$type === 'null' ?
182 null :
183 `array($tracked,[${this.generateExpr(expr[3])}],${this.uniqueId(expr, 'arr')},1)`
184 })`;
185 case 'context':
186 return 'context[0]';
187 case 'recur':
188 return `${this.generateExpr(expr[1])}.recursiveSteps(${this.generateExpr(expr[2])}, $tracked)`;
189 case 'func':
190 return expr[0].$duplicate ? expr[0].$duplicate : expr[0].$funcId;
191 case 'invoke':
192 return `${expr[1]}($tracked${expr.slice(2).map(t => `,${t.$type}`).join('')})`
193 default:
194 return super.generateExpr(expr);
195 }
196 }
197
198 isStaticObject(expr) {
199 return _.range(1, expr.length, 2)
200 .every(idx => typeof expr[idx] === 'string')
201 }
202
203 buildExprFunctionsByTokenType(acc, expr) {
204 const tokenType = expr[0].$type;
205 if (expr[0].$duplicate) {
206 return;
207 }
208 switch (tokenType) {
209 case 'object':
210 if (this.isStaticObject(expr)) {
211 this.appendExpr(acc, tokenType, expr, `${tokenType}$${expr[0].$id}`);
212 }
213 break;
214 default:
215 super.buildExprFunctionsByTokenType(acc, expr);
216 }
217 }
218
219 invalidates(expr) {
220 return expr[0].$invalidates;
221 }
222
223 pathOfExpr(expr) {
224 return [new Token('topLevel'), expr[0].$rootName].concat(
225 new Array(Math.min(expr[0].$depth, 1)).fill(new Token('key'))
226 );
227 }
228
229 tracking(expr) {
230 const tracks = [];
231 // tracks.push(`// invalidates - ${this.invalidates(expr)}`)
232 const pathsThatInvalidate = expr[0].$path;
233 if (pathsThatInvalidate) {
234 //console.log(pathsThatInvalidate);
235 pathsThatInvalidate.forEach((cond, invalidatedPath) => {
236 // tracks.push(
237 // `// invalidatedPath: ${JSON.stringify(invalidatedPath)}, ${JSON.stringify(cond)}, ${
238 // invalidatedPath[invalidatedPath.length - 1].$type
239 // }`
240 // );
241 const precond = cond && cond !== true ? `(${this.generateExpr(cond)} ) && ` : '';
242 if (invalidatedPath[0].$type === 'context') {
243 const activePath = [0].concat(invalidatedPath.slice(1));
244 tracks.push(
245 `${precond} trackPath($tracked, [context, ${activePath
246 .map(fragment => this.generateExpr(fragment))
247 .join(',')}]);`
248 );
249 } else if (invalidatedPath.length > 1 && invalidatedPath[0].$type === 'topLevel') {
250 tracks.push(
251 `${precond} trackPath($tracked, [${invalidatedPath
252 .map((fragment, index) => index === 1 ? this.topLevelToIndex(fragment) : this.generateExpr(fragment))
253 .join(',')}]);`
254 );
255 } else if (invalidatedPath.length > 1 && invalidatedPath[0] instanceof Expression && invalidatedPath[0][0].$type === 'get' && invalidatedPath[0][2].$type === 'topLevel') {
256 tracks.push(
257 `${precond} trackPath($tracked, [${invalidatedPath
258 .map(fragment => this.generateExpr(fragment))
259 .join(',')}]);`
260 );
261 } else if (invalidatedPath.length > 1 &&
262 (invalidatedPath[0] instanceof Expression && invalidatedPath[0][0].$type === 'invoke')) {
263 tracks.push(
264 `${precond} trackPath($tracked, [${invalidatedPath
265 .map(fragment => this.generateExpr(fragment))
266 .join(',')}]);`
267 );
268 } else if (invalidatedPath[0].$type === 'root' && invalidatedPath.length > 1) {
269 const settersMatched = Object.values(this.setters).filter(setter => pathMatches(invalidatedPath, setter));
270 if (settersMatched.length) {
271 // settersMatched.forEach(setter => tracks.push(`// path matched ${JSON.stringify(setter)}`));
272 tracks.push(
273 `${precond} trackPath($tracked, [${invalidatedPath
274 .map(fragment => this.generateExpr(fragment))
275 .join(',')}]);`
276 );
277 }
278 }
279 //tracks.push(`// tracking ${JSON.stringify(invalidatedPath)}`);
280 });
281 }
282 return tracks.join('\n');
283 }
284}
285
286module.exports = OptimizingCompiler;