UNPKG

15.6 kBJavaScriptView Raw
1/* eslint-disable prefer-const */
2const {TokenTypeData, Token, Expr} = require('./lang');
3const NaiveCompiler = require('./naive-compiler');
4const _ = require('lodash');
5const fs = require('fs');
6const path = require('path');
7
8const babelParser = require('@babel/parser');
9const walk = require('babylon-walk');
10const generate = require('babel-generator');
11const optLibrary = require('./templates/optimizing').library.toString();
12
13const verbs = Object.keys(TokenTypeData)
14 .filter(token => !TokenTypeData[token].nonVerb)
15 .filter(token => token !== 'abstract')
16 .filter(token => token !== 'cond');
17const lazyVerbs = new Set(['and', 'or', 'ternary']);
18const eagerVerbs = verbs.filter(token => !lazyVerbs.has(token));
19const verbsLazyFirst = Array.from(lazyVerbs).concat(eagerVerbs);
20const verbsSet = new Set(verbsLazyFirst);
21const nonVerbs = Object.keys(TokenTypeData).filter(token => TokenTypeData[token].nonVerb);
22
23const valueTypes = ['numberInline', 'booleanInline', 'stringRef', 'numberRef', 'expressionRef', 'condRef'];
24
25const setterTypes = ['setter', 'splice', 'push'];
26
27const defineEnum = (name, values) => `
28${values.map((key, index) => `module.exports.$${key} = ${index};`).join('\n')}
29module.exports.${name}Count = ${values.length};
30module.exports.${name} = {
31${values.map((key, index) => ` $${key}: ${index}`).join(',\n')}
32};
33
34`;
35
36const enums = `
37${defineEnum('Verbs', verbsLazyFirst)}
38${defineEnum('nonVerbs', valueTypes.concat(nonVerbs))}
39${defineEnum('setterTypes', setterTypes)}
40
41// Values are uint32
42// VVVVVVVVVVVVVVVVVVVVVVVVVVVVVTTT
43// TTT - signifies type
44// 000 - number inline
45// 001 - boolean inline
46// 010 - string ref
47// 011 - number ref
48// 100 - token
49// 101 - expression ref
50// the rest of the bits are the value 2^29 possible values
51
52// table Expression {
53// token: Verbs;
54// values: [uint32] (required);
55// }
56
57// table Bytecode {
58// topLevels:[uint32] (required);
59// topLevelsNames:[uint32] (required);
60// expressions: [Expression] (required);
61// constant_numbers:[float64] (required);
62// constant_strings:[string] (required);
63// }
64
65// root_type Bytecode;
66//
67
68`;
69fs.writeFileSync(path.join(__dirname, '..', 'bytecode', 'bytecode-enums.js'), enums);
70
71const verbsNotAutoGenerated = new Set(['recur', 'func', 'array', 'object', 'recursiveMapValues', 'recursiveMap']);
72
73const naiveCompiler = new NaiveCompiler({}, {});
74const helperFunctions = eagerVerbs
75 .map(verb => {
76 if (
77 verbsNotAutoGenerated.has(verb) ||
78 TokenTypeData[verb].len[0] !== TokenTypeData[verb].len[1] ||
79 TokenTypeData[verb].collectionVerb
80 ) {
81 return `// ${verb} skipped`;
82 }
83 const args = new Array(TokenTypeData[verb].len[0] - 1).fill().map((_, i) => new Token(`arg${i}`));
84 const expr = Expr(new Token(verb), ...args);
85 return `
86module.exports.$${verb} = function $${verb}($offset, $len) {
87${args
88 .map(naiveCompiler.generateExpr)
89 .map(
90 (id, index) =>
91 ` this.processValue(this.$expressions[++$offset])
92 const ${id} = this.$stack.pop();`
93 )
94 .join('\n')}
95 this.$stack.push(${naiveCompiler.generateExpr(expr)});
96}`;
97 })
98 .join('\n');
99
100const ast = babelParser.parse(optLibrary, {plugins: []});
101const statements = ast.program.body[0].body.body;
102const functions = statements.filter(t => t.type === 'FunctionDeclaration');
103const functionNames = new Set(functions.map(t => t.id.name));
104const constFuncs = statements
105 .filter(t => t.type === 'VariableDeclaration')
106 .filter(t => t.declarations[0].init.type === 'ArrowFunctionExpression');
107
108const constFuncsNames = new Set(constFuncs.map(t => t.declarations[0].id.name));
109
110const constFuncSources = Array.from(constFuncs)
111 .filter(f => f.declarations[0].id.name !== 'recursiveCacheFunc')
112 .map(f => generate.default(f).code)
113 .join('\n');
114
115const constValues = statements
116 .filter(t => t.type === 'VariableDeclaration')
117 .filter(t => t.declarations[0].init.type !== 'ArrowFunctionExpression');
118
119const constValuesNames = new Set(
120 constValues.map(t => t.declarations[0].id.name).concat(['$res', '$funcLib', '$funcLibRaw'])
121);
122
123function rewriteAncestorBlockStatement(ancestors, deleteCount, ...newItems) {
124 const innerMostBlockStatementIndex = _.findLastIndex(ancestors, {type: 'BlockStatement'});
125 const block = ancestors[innerMostBlockStatementIndex];
126 const currentIndex = _.findIndex(block.body, ancestors[innerMostBlockStatementIndex + 1]);
127 if (deleteCount > 0 && newItems.length) {
128 ancestors[innerMostBlockStatementIndex + 1] = newItems[0];
129 }
130 return block.body.splice(currentIndex, deleteCount, ...newItems);
131}
132
133const visitorFuncDeclStatements = {
134 ReturnStatement(node, state, ancestors) {
135 if (ancestors.length > 3) {
136 return;
137 }
138 Object.assign(node, {
139 arguments: [node.argument],
140 type: 'CallExpression',
141 callee: {
142 type: 'MemberExpression',
143 object: {
144 type: 'MemberExpression',
145 object: {
146 type: 'ThisExpression'
147 },
148 property: {
149 type: 'Identifier',
150 name: '$stack'
151 },
152 computed: false
153 },
154 property: {
155 type: 'Identifier',
156 name: 'push'
157 },
158 computed: false
159 }
160 });
161 delete node.argument;
162 },
163 CallExpression(node, state, ancestors) {
164 if (node.callee.name === 'getEmptyArray' || node.callee.name === 'getEmptyObject') {
165 node.arguments = [
166 {
167 type: 'UnaryExpression',
168 operator: '-',
169 prefix: true,
170 argument: {
171 type: 'Identifier',
172 name: '$offset'
173 }
174 }
175 ];
176 } else if (node.callee.name === 'initOutput') {
177 state.initOutput = true
178 node.arguments.splice(0, 2);
179 node.arguments[0] = {
180 type: 'BinaryExpression',
181 left: {
182 type: 'Identifier',
183 name: '$offset'
184 },
185 operator: '-',
186 right: {
187 type: 'Identifier',
188 name: '$length'
189 }
190 };
191 } else if (node.callee.name === 'func') {
192 const arg = node.arguments[1];
193 node.callee = {
194 type: 'MemberExpression',
195 object: {
196 type: 'MemberExpression',
197 object: {
198 type: 'ThisExpression'
199 },
200 property: {
201 type: 'Identifier',
202 name: '$stack'
203 },
204 computed: false
205 },
206 property: {
207 type: 'Identifier',
208 name: 'pop'
209 },
210 computed: false
211 };
212 node.arguments = [];
213 rewriteAncestorBlockStatement(
214 ancestors,
215 0,
216 {
217 type: 'ExpressionStatement',
218 expression: {
219 type: 'CallExpression',
220 callee: {
221 type: 'MemberExpression',
222 object: {
223 type: 'MemberExpression',
224 object: {
225 type: 'ThisExpression'
226 },
227 property: {
228 type: 'Identifier',
229 name: '$keys'
230 },
231 computed: false
232 },
233 property: {
234 type: 'Identifier',
235 name: 'push'
236 },
237 computed: false
238 },
239 arguments: [arg]
240 }
241 },
242 {
243 type: 'ExpressionStatement',
244 expression: {
245 type: 'CallExpression',
246 callee: {
247 type: 'MemberExpression',
248 object: {
249 type: 'ThisExpression'
250 },
251 property: {
252 type: 'Identifier',
253
254 name: 'collectionFunction'
255 },
256 computed: false
257 },
258 arguments: []
259 }
260 }
261 );
262 }
263 // c(node.callee);
264 // node.arguments.forEach(c);
265 }
266};
267
268const visitorsPointFunctionsToThis = {
269 Identifier(node, state, ancestors) {
270 if (
271 constValuesNames.has(node.name) &&
272 (ancestors[ancestors.length - 2].type !== 'MemberExpression' ||
273 ancestors[ancestors.length - 2].object.type !== 'ThisExpression')
274 ) {
275 Object.assign(node, {
276 type: 'MemberExpression',
277 object: {
278 type: 'ThisExpression'
279 },
280 property: {
281 type: 'Identifier',
282 name: node.name
283 },
284 computed: false
285 });
286 delete node.name;
287 }
288 },
289 CallExpression(node, state, ancestors) {
290 if (
291 node.callee.name &&
292 functionNames.has(node.callee.name) &&
293 (ancestors[ancestors.length - 2].type !== 'MemberExpression' ||
294 ancestors[ancestors.length - 2].object.type !== 'ThisExpression')
295 ) {
296 node.callee = {
297 type: 'MemberExpression',
298 object: {
299 type: 'ThisExpression'
300 },
301 property: {
302 type: 'Identifier',
303 name: node.callee.name
304 },
305 computed: false
306 };
307 }
308 }
309};
310
311const functionsById = functions.reduce((acc, f) => ({...acc, [f.id.name]: f}), {});
312const verbFunctions = Object.keys(functionsById).reduce((acc, name) => {
313 if (verbsSet.has(name) || verbsSet.has(name.replace('Opt', ''))) {
314 return {...acc, [verbsSet.has(name) ? name : name.replace('Opt', '')]: functionsById[name]};
315 }
316 return acc;
317}, {});
318
319const verbsIgnoredInOptimizing = new Set(['recur', 'func', 'recursiveMapValues', 'recursiveMap']);
320
321const snippets = _.mapValues(
322 {
323 srcPre: ($offset, $length) => {
324 this.processValue(this.$expressions[++$offset]);
325 },
326 src: ($offset, $length) => {
327 let src = this.$stack.pop();
328 this.$collections.push(src);
329 },
330 srcEnd: ($offset, $length) => {
331 this.$collections.pop();
332 },
333 initOutputEnd: ($offset, $length) => {
334 this.$currentSets.pop();
335 },
336 contextPre: ($offset, $length) => {
337 if ($length === 3) {
338 this.$stack.push(null);
339 } else {
340 this.processValue(this.$expressions[++$offset]);
341 }
342 },
343 context: ($offset, $length) => {
344 if ($length === 3) {
345 this.$contexts.push(this.$stack.pop());
346 } else {
347 const contextArray = this.getEmptyArray(~$offset);
348 if (contextArray.length) {
349 this.setOnArray(contextArray, 0, this.$stack.pop(), false);
350 } else {
351 contextArray[0] = this.$stack.pop();
352 }
353 this.$contexts.push(contextArray);
354 }
355 },
356 contextEnd: ($offset, $length) => {
357 this.$contexts.pop();
358 },
359 func: ($offset, $length) => {
360 // eslint-disable-next-line no-undef
361 this.$functions.push(func);
362 },
363 funcPre: ($offset, $length) => {
364 const func = this.$expressions[++$offset];
365 },
366 funcEnd: ($offset, $length) => {
367 this.$functions.pop();
368 },
369 endPre: ($offset, $length) => {
370 this.processValue(this.$expressions[++$offset]);
371 },
372 end: ($offset, $length) => {
373 const end = this.$stack.pop();
374 },
375 startPre: ($offset, $length) => {
376 if ($length > 2) {
377 this.processValue(this.$expressions[++$offset]);
378 } else {
379 this.$stack.push(0);
380 }
381 },
382 start: ($offset, $length) => {
383 const start = this.$stack.pop();
384 },
385 stepPre: ($offset, $length) => {
386 if ($length > 3) {
387 this.processValue(this.$expressions[++$offset]);
388 } else {
389 this.$stack.push(1);
390 }
391 },
392 step: ($offset, $length) => {
393 const step = this.$stack.pop();
394 },
395 len: ($offset, $length) => {
396 const len = $length - 1;
397 },
398 newValPre: ($offset, $length) => {
399 const newVal = [];
400 for (let i = 1; i < $length; i++) {
401 this.processValue(this.$expressions[++$offset]);
402 newVal.push(this.$stack.pop());
403 }
404 },
405 keysListPre: ($offset, $length) => {
406 let keysList = this.$globals.get($offset);
407 if (!keysList) {
408 keysList = [];
409 for (let i = 1; i < $length; i += 2) {
410 this.processValue(this.$expressions[$offset + i]);
411 keysList.push(this.$stack.pop());
412 }
413 this.$globals.set($offset, keysList);
414 }
415 },
416 valsListPre: ($offset, $length) => {
417 const valsList = [];
418 for (let i = 2; i < $length; i += 2) {
419 this.processValue(this.$expressions[$offset + i]);
420 valsList.push(this.$stack.pop());
421 }
422 }
423 },
424 f => {
425 const snippetAst = babelParser.parse(f.toString(), {plugins: []});
426 return snippetAst.program.body[0].expression.body.body;
427 }
428);
429
430const extractedFuncs = Object.entries(verbFunctions)
431 .map(([name, f]) => {
432 if (verbsIgnoredInOptimizing.has(name)) {
433 return `// ${name} skipped from optimizing`;
434 }
435 f.id.name = name;
436 const paramsPre = _.flatten(
437 f.params
438 .map(t => `${t.name}Pre`)
439 .map(t => snippets[t])
440 .filter(Boolean)
441 );
442 const params = _.flatten(
443 _.reverse(f.params
444 .map(t => t.name)
445 .map(t => snippets[t])
446 .filter(Boolean)
447 )
448 );
449 const paramNamesEnds = f.params
450 .map(t => `${t.name}End`)
451 ;
452 const state = {initOutput: false}
453 walk.ancestor(f, visitorFuncDeclStatements, state);
454 walk.ancestor(f, visitorsPointFunctionsToThis, []);
455 f.body.body.splice(0, 0, ...paramsPre, ...params);
456 if (state.initOutput) {
457 paramNamesEnds.push('initOutputEnd')
458 }
459 const paramEnds = _.flatten(paramNamesEnds
460 .map(t => snippets[t])
461 .filter(Boolean))
462 f.body.body.push(...paramEnds);
463
464 // console.log('func', name, JSON.stringify(f, null, 2));
465 f.params = [
466 {
467 type: 'Identifier',
468 name: '$offset'
469 },
470 {
471 type: 'Identifier',
472 name: '$length'
473 }
474 ];
475 f = {
476 type: 'ExpressionStatement',
477 expression: {
478 type: 'AssignmentExpression',
479 operator: '=',
480 left: {
481 type: 'MemberExpression',
482 object: {
483 type: 'MemberExpression',
484 object: {
485 type: 'Identifier',
486 name: 'module'
487 },
488 property: {
489 type: 'Identifier',
490 name: 'exports'
491 },
492 computed: false
493 },
494 property: {
495 type: 'Identifier',
496 name: `$${name}`
497 },
498 computed: false
499 },
500 right: f
501 }
502 };
503 return generate.default(f).code;
504 })
505 .join('\n');
506
507
508const keepNonVerbFunctions = ['untrack', 'invalidate', 'setOnObject', 'deleteOnObject', 'setOnArray', 'truncateArray', 'track', 'trackPath', 'triggerInvalidations', 'initOutput', 'getEmptyArray', 'getEmptyObject', 'invalidatePath', 'set', 'splice']
509const nonVerbFunctions = keepNonVerbFunctions
510 .map(name => functionsById[name])
511 .map(f => {
512 walk.ancestor(f, visitorsPointFunctionsToThis, []);
513 return generate.default(f).code.replace(/^function /, '');
514 });
515
516fs.writeFileSync(
517 path.join(__dirname, '..', 'bytecode', 'bytecode-functions.js'),
518 `
519${helperFunctions}
520${constFuncSources}
521${extractedFuncs}
522
523/*
524// constantValues
525${constValues
526 .map(
527 t =>
528 generate.default({
529 type: 'ExpressionStatement',
530 expression: {
531 type: 'AssignmentExpression',
532 operator: '=',
533 left: {
534 type: 'MemberExpression',
535 object: {
536 type: 'ThisExpression'
537 },
538 property: {
539 type: 'Identifier',
540 name: t.declarations[0].id.name
541 },
542 computed: false
543 },
544 right: t.declarations[0].init
545 }
546 }).code
547 )
548 .join('\n')}
549${nonVerbFunctions.join('\n')}
550*/
551
552`
553);