1 | const {Expr, Token, TrackPath, Get, Expression} = require('./lang');
|
2 | const _ = require('lodash');
|
3 | const SimpleCompiler = require('./simple-compiler');
|
4 | const {searchExpressions} = require('./expr-search');
|
5 | const {exprHash} = require('./expr-hash');
|
6 |
|
7 | const enums = require('../bytecode/bytecode-enums');
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | const maxInlineNumber = 32767;
|
14 | const minInlineNumber = 0;
|
15 |
|
16 | function embeddedVal(type, val) {
|
17 | if (typeof type !== 'number' || typeof val !== 'number' || type < 0 || type > enums.nonVerbs) {
|
18 | throw new Error(`illegal value, ${type}, ${val}`);
|
19 | }
|
20 | return (val << 5) + type;
|
21 | }
|
22 |
|
23 | function canInlineNumber(val) {
|
24 | return val >= minInlineNumber && val < maxInlineNumber;
|
25 | }
|
26 |
|
27 | function setToMap(src) {
|
28 | const res = new Map();
|
29 | src.forEach(val => res.set(val, res.size));
|
30 | return res;
|
31 | }
|
32 |
|
33 | function str2ab_array(str) {
|
34 | if (str.length % 2 === 1) {
|
35 | str += ' ';
|
36 | }
|
37 | const buf = new ArrayBuffer(str.length * 2);
|
38 | const bufView = new Uint16Array(buf);
|
39 | for (let i = 0; i < str.length; i++) {
|
40 | bufView[i] = str.charCodeAt(i);
|
41 | }
|
42 | return bufView;
|
43 | }
|
44 |
|
45 | function concatBuffers(...buffers) {
|
46 |
|
47 | const offsetsSize = 4 * (buffers.length + 1);
|
48 | const totalSize = _.sum(buffers.map(buf => buf.byteLength)) + offsetsSize;
|
49 |
|
50 | const out = new Buffer(totalSize);
|
51 |
|
52 | let offset = 4;
|
53 | let totalLength = offsetsSize;
|
54 | out.writeUInt32LE(offsetsSize, 0);
|
55 | for (let i = 0; i < buffers.length; i++) {
|
56 | totalLength += buffers[i].byteLength;
|
57 | out.writeUInt32LE(totalLength, offset);
|
58 | offset += 4;
|
59 | }
|
60 | buffers.forEach(buf => {
|
61 | for (let i = 0; i < buf.length; i++) {
|
62 | if (buf instanceof Uint32Array) {
|
63 | out.writeUInt32LE(buf[i], offset);
|
64 | offset += 4;
|
65 | } else {
|
66 | out.writeUInt16LE(buf[i], offset);
|
67 | offset += 2;
|
68 | }
|
69 | }
|
70 | });
|
71 | return out;
|
72 | }
|
73 |
|
74 | class BytecodeCompiler extends SimpleCompiler {
|
75 | constructor(model, options) {
|
76 | options = {...options, disableHelperFunctions: true};
|
77 | super(model, options);
|
78 | this.realGetters = [];
|
79 | this.exprsFromHash = {};
|
80 | this.tagToHash = {};
|
81 | this.exprsHashToIndex = new Map();
|
82 | }
|
83 | extractConsts() {
|
84 | const stringsSet = new Set();
|
85 | stringsSet.add('');
|
86 | const numbersSet = new Set();
|
87 | const addConst = t => {
|
88 | if (typeof t === 'string') {
|
89 | stringsSet.add(t);
|
90 | } else if (typeof t === 'number' && !canInlineNumber(t)) {
|
91 | numbersSet.add(t);
|
92 | }
|
93 | };
|
94 | Object.keys(this.getters).forEach(t => {
|
95 | if (this.options.debug || t[0] !== '$') {
|
96 | stringsSet.add(t);
|
97 | }
|
98 | });
|
99 | Object.keys(this.setters).forEach(t => stringsSet.add(t));
|
100 | Object.values(this.setters).forEach(setter => setter.forEach(addConst));
|
101 | searchExpressions(e => {
|
102 | if (e[0].$type === 'get' && e[2] instanceof Token && e[2].$type === 'topLevel') {
|
103 | e[1] = this.topLevelToIndex(e[1]);
|
104 | }
|
105 | e.forEach(addConst);
|
106 | }, Object.values(this.getters));
|
107 | Object.values(this.setters).forEach(s => s.forEach(addConst));
|
108 | this.stringsMap = setToMap(stringsSet);
|
109 | this.numbersMap = setToMap(numbersSet);
|
110 | this.stringsSet = stringsSet;
|
111 | this.numbersSet = numbersSet;
|
112 | }
|
113 | convertToEmbeddedValue(val) {
|
114 | if (typeof val === 'string') {
|
115 | return embeddedVal(enums.$stringRef, this.stringsMap.get(val));
|
116 | } else if (typeof val === 'number') {
|
117 | return canInlineNumber(val) ?
|
118 | embeddedVal(enums.$numberInline, val) :
|
119 | embeddedVal(enums.$numberRef, this.numbersMap.get(val));
|
120 | } else if (typeof val === 'boolean') {
|
121 | return embeddedVal(enums.$booleanInline, val ? 1 : 0);
|
122 | } else if (val instanceof Token) {
|
123 | return embeddedVal(enums[`$${val.$type}`], 0);
|
124 | } else if (val instanceof Expression) {
|
125 | if (val[0].$type === 'cond') {
|
126 | return embeddedVal(enums.$condRef, this.expressionsOffsets[this.expressionsHashToIndex[val[1]]]);
|
127 | }
|
128 | return embeddedVal(enums.$expressionRef, this.expressionsOffsets[this.expressionsHashToIndex[exprHash(val)]]);
|
129 | }
|
130 | }
|
131 | rewriteCondsToHash(expr) {
|
132 | if (!(expr instanceof Expression)) {
|
133 | return expr;
|
134 | }
|
135 | if (expr[0].$type === 'cond') {
|
136 | expr[1] = this.tagToHash.hasOwnProperty(expr[1]) ? this.tagToHash[expr[1]] : expr[1];
|
137 | return expr;
|
138 | }
|
139 | return Expr(...expr.map(t => this.rewriteCondsToHash(t)));
|
140 | }
|
141 | addExpressionsToHashes(exprs) {
|
142 | searchExpressions(e => {
|
143 | if (!(e instanceof Expression) || e[0].$type === 'cond') {
|
144 | return;
|
145 | }
|
146 | const hash = exprHash(e);
|
147 | e[0].$hash = hash;
|
148 | this.exprsFromHash[hash] = this.exprsFromHash[hash] || e;
|
149 | if (e[0].hasOwnProperty('$id')) {
|
150 | this.tagToHash[e[0].$id] = hash;
|
151 | }
|
152 |
|
153 | }, exprs);
|
154 | }
|
155 | compile() {
|
156 | Object.keys(this.getters).forEach(name => {
|
157 | const index = this.topLevelToIndex(name);
|
158 | if (typeof index === 'number') {
|
159 | this.realGetters[index] = name;
|
160 | }
|
161 | });
|
162 | const countTopLevels = this.realGetters.length;
|
163 |
|
164 | this.addExpressionsToHashes(Object.values(this.getters));
|
165 |
|
166 | this.extractConsts();
|
167 |
|
168 | const trackedPaths = {root: true, context: true, topLevel: true};
|
169 |
|
170 | searchExpressions(e => {
|
171 | if (!(e instanceof Expression)) {
|
172 | return;
|
173 | }
|
174 | if (!e[0].$path) {
|
175 | return;
|
176 | }
|
177 | const trackParts = Array.from(e[0].$path.entries())
|
178 | .filter(([path, cond]) =>
|
179 |
|
180 | trackedPaths[path[0].$type] || path.length > 1 && path[0] instanceof Expression && path[0][0].$type === 'get' && path[0][2].$type === 'topLevel'
|
181 | )
|
182 | .map(([path, cond]) => {
|
183 | const pathAsExpr = path.slice(1).reduce((acc, t) => Expr(Get, t, acc), path[0]);
|
184 | const pathHash = exprHash(pathAsExpr);
|
185 |
|
186 |
|
187 | return [this.rewriteCondsToHash(cond), pathAsExpr];
|
188 | });
|
189 | e[0].$path = Expr(TrackPath, ...[].concat(...trackParts));
|
190 |
|
191 | this.addExpressionsToHashes([e[0].$path]);
|
192 | if (e[0].$type === 'func') {
|
193 | e.push(e[0].$path);
|
194 | }
|
195 | }, Object.values(this.getters));
|
196 |
|
197 | Object.keys(this.exprsFromHash).forEach(hash => {
|
198 | this.exprsHashToIndex.set(hash, this.exprsHashToIndex.size);
|
199 | });
|
200 |
|
201 |
|
202 | this.expressionsHashToIndex = {};
|
203 | Object.keys(this.exprsFromHash).forEach((hash, index) => this.expressionsHashToIndex[hash] = index);
|
204 |
|
205 | const stringsAndNumbers = JSON.stringify({
|
206 | $strings: Array.from(this.stringsSet),
|
207 | $numbers: Array.from(this.numbersSet)
|
208 | });
|
209 | const constsBuffer = str2ab_array(stringsAndNumbers);
|
210 | const countOfTopLevels = Object.keys(this.getters).length;
|
211 | const countOfExpressions = Object.keys(this.exprsFromHash).length;
|
212 | const lengthOfAllExpressions = _.sum(Object.values(this.exprsFromHash).map(e => e.length));
|
213 | const header = new Uint32Array(1);
|
214 | header[0] = countOfTopLevels;
|
215 | const topLevelNames = new Uint32Array(countOfTopLevels);
|
216 | const topLevelExpressions = new Uint32Array(countOfTopLevels);
|
217 | const topLevelTracking = new Uint32Array(countOfTopLevels);
|
218 |
|
219 | let exprOffset = 0;
|
220 | this.expressionsOffsets = new Uint32Array(countOfExpressions);
|
221 | Object.keys(this.exprsFromHash).forEach((hash, index) => {
|
222 | const e = this.exprsFromHash[hash];
|
223 | this.expressionsOffsets[index] = exprOffset;
|
224 | exprOffset += e.length;
|
225 | });
|
226 | _.range(countTopLevels).forEach(i => {
|
227 | let name = '';
|
228 | if (this.options.debug || this.realGetters[i][0] !== '$') {
|
229 | name = this.realGetters[i];
|
230 | }
|
231 | topLevelNames[i] = this.stringsMap.get(name);
|
232 | const expr = this.getters[this.realGetters[i]];
|
233 | topLevelExpressions[i] = this.expressionsOffsets[this.exprsHashToIndex.get(exprHash(expr))];
|
234 |
|
235 | topLevelTracking[i] = this.expressionsOffsets[this.exprsHashToIndex.get(exprHash(expr[0].$path))];
|
236 | });
|
237 | const expressions = new Uint32Array(lengthOfAllExpressions);
|
238 |
|
239 |
|
240 | Object.keys(this.exprsFromHash).forEach((hash, index) => {
|
241 | exprOffset = this.expressionsOffsets[index];
|
242 | const e = this.exprsFromHash[hash];
|
243 | const verb = enums[`$${e[0].$type}`] << 16;
|
244 | expressions[exprOffset] = verb + e.length;
|
245 |
|
246 | e.slice(1)
|
247 | .map(t => this.convertToEmbeddedValue(t))
|
248 | .forEach((val, indexInExpr) => {
|
249 | expressions[exprOffset + 1 + indexInExpr] = val;
|
250 | });
|
251 | });
|
252 |
|
253 | const settersSize = _.sum(Object.keys(this.setters).map(key => this.setters[key].length + 2));
|
254 | const settersBuffer = new Uint32Array(settersSize);
|
255 | let settersOffset = 0;
|
256 | Object.keys(this.setters).forEach(key => {
|
257 | const setter = this.setters[key];
|
258 | const type = enums[`$${setter.setterType()}`] << 16;
|
259 | settersBuffer[settersOffset++] = type + setter.length;
|
260 | settersBuffer[settersOffset++] = this.stringsMap.get(key);
|
261 | setter
|
262 | .map(v => this.convertToEmbeddedValue(v))
|
263 | .forEach(val => {
|
264 | settersBuffer[settersOffset++] = val;
|
265 | });
|
266 | });
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | const outputArray = concatBuffers(
|
278 | header,
|
279 | topLevelExpressions,
|
280 | topLevelNames,
|
281 | topLevelTracking,
|
282 | expressions,
|
283 | settersBuffer,
|
284 | constsBuffer
|
285 | );
|
286 | return outputArray;
|
287 | }
|
288 | }
|
289 |
|
290 | module.exports = BytecodeCompiler;
|