= new Map()
222 | const isInCandidates = (reg: string): boolean => candidates.has(reg)
223 | const isReg = (s: string): boolean => s.startsWith('%')
224 |
225 | const getValueType = (val: string): ValueType => {
226 | if (isReg(val)) {
227 | return ValueType.REGISTESR
228 | } else if (val.match(/^['"]/)) {
229 | return ValueType.STRING
230 | } else if (!isNaN(Number(val))) {
231 | return ValueType.NUMBER
232 | } else {
233 | return ValueType.OTHER
234 | }
235 | }
236 |
237 | const processReg = (reg: string): void => {
238 | const candidate = candidates.get(reg)!
239 | if (candidate.valueType === ValueType.NUMBER && candidate.usages.length > 1) { return }
240 | const { codeIndex, value, usages } = candidate
241 | codes[codeIndex] = null
242 | for (const usage of usages) {
243 | try {
244 | codes[usage.codeIndex][usage.position] = value
245 | } catch(e) {
246 | throw new Error(e)
247 | }
248 | }
249 | candidates.delete(reg)
250 | }
251 |
252 | const isIgnoreOperator = (op: string): boolean => {
253 | return ['VAR', 'REG'].includes(op)
254 | }
255 |
256 | codes.forEach((code: string[], i: number): void => {
257 | const operator = code[0]
258 | if (I[operator] === I.MOV) {
259 | const dst = code[1]
260 | const value = code[2]
261 | if (dst === value) {
262 | codes[i] = null
263 | return
264 | }
265 | if (isReg(value) && isInCandidates(value)) {
266 | const candidate = candidates.get(value)!
267 | candidate.usages.push({
268 | codeIndex: i,
269 | position: 2,
270 | })
271 | }
272 | if (!isReg(dst)) { return }
273 | if (isInCandidates(dst)) {
274 | processReg(dst)
275 | }
276 | if (isReg(value)) { return }
277 | candidates.set(dst, {
278 | codeIndex: i,
279 | operator,
280 | value,
281 | valueType: getValueType(value),
282 | usages: [],
283 | })
284 | } else {
285 | code.forEach((operant: string, j: number): void => {
286 | if (j === 0) { return }
287 | if (isIgnoreOperator(operator)) { return }
288 | if (!isReg(operant)) { return }
289 | let useType
290 | let isGetOperant
291 | try {
292 | useType = codeToUseAge[I[operator]][j - 1]
293 | isGetOperant = useType === OU.GET
294 | } catch(e) {
295 | console.log('ERROR operator --> ', operator)
296 | throw new Error(e)
297 | }
298 | if (!isInCandidates(operant)) { return }
299 | if (isGetOperant) {
300 | const candidate = candidates.get(operant)!
301 | candidate.usages.push({
302 | codeIndex: i,
303 | position: j,
304 | })
305 | } else {
306 | if (useType === OU.SET) {
307 | processReg(operant)
308 | }
309 | candidates.delete(operant)
310 | }
311 | })
312 | }
313 | })
314 |
315 | // for (const [reg] of candidates.entries()) {
316 | // processReg(reg)
317 | // }
318 |
319 | return codes
320 | }
321 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
23 |
24 |
25 |
26 |
27 | # nestscript
28 |
29 | A script nested in JavaScript, dynamically runs code in environment without `eval` and `new Function`.
30 |
31 | `nestscript` 可以让你在没有 `eval` 和 `new Function` 的 JavaScript 环境中运行二进制指令文件。
32 |
33 | 原理上就是把 JavaScript 先编译成 `nestscript` 的 IR 指令,然后把指令编译成二进制的文件。只要在环境中引入使用 JavaScript 编写的 `nestscript` 的虚拟机,都可以执行 `nestscript` 的二进制文件。你可以把它用在 Web 前端、微信小程序等场景。
34 |
35 | 它包含三部分:
36 |
37 | 1. **代码生成器**:将 JavaScript 编译成 `nestscript` 中间指令。
38 | 2. **汇编器**:将中间指令编译成可运行在 `nestscript` 虚拟机的二进制文件。
39 | 3. **虚拟机**:执行汇编器生成的二进制文件。
40 |
41 | 理论上你可以将任意语言编译成 `nestscript` 指令集,但是目前 `nestscript` 只包含了一个代码生成器,目前支持将 JavaScript 编译成 `nestscript` 指令。
42 |
43 |
44 |
45 |
46 | 目前支持单文件 ES5 编译,并且已经成功编译并运行一些经典的 JavaScript 第三方库,例如 moment.js、lodash.js、mqtt.js。并且有日活百万级产品在生产环境使用。
47 |
48 | ## Installation
49 |
50 | ```bash
51 | npm install nestscript
52 | ```
53 |
54 | ## 快速尝试
55 |
56 | ### 新建一个文件,例如 `main.js`:
57 |
58 | ```javascript
59 | console.log("hello world")
60 | ```
61 |
62 | ### 编译成二进制:
63 |
64 | ```bash
65 | npx nsc compile main.js main
66 | ```
67 |
68 | 这会将 `main.js` 编译成 `main` 二进制文件
69 |
70 | ### 通过虚拟机运行二进制:
71 |
72 | ```bash
73 | npx nsc run main
74 | ```
75 |
76 | 会看到终端输出 `hello world`。这个 `main` 二进制文件,可以在任何一个包含了 nestscript 虚拟机,也就是 `dist/vm.js` 文件的环境中运行。
77 |
78 | 例如你可以把这个 `main` 二进制分发到 CND,然后通过网络下载到包含 `dist/vm.js` 文件的小程序中动态执行。
79 |
80 | ## Example
81 |
82 | 为了展示它的作用,我们编译了一个开源的的伪 3D 游戏 [javascript-racer](https://github.com/jakesgordon/javascript-racer/)。可以通过这个网址查看效果:[https://livoras.github.io/nestscript-demo/index.html](https://livoras.github.io/nestscript-demo/index.html)
83 |
84 | 
85 |
86 | 查看源代码([nestscript-demo](https://github.com/livoras/nestscript-demo))可以看到,我们在网页中引入了一个虚拟机 `vm.js`。游戏的主体逻辑都通过 nestscript 编译成了一个二进制文件 `game`,然后通过 `fetch`下载这个二进制文件,然后给到虚拟机解析、运行。
87 |
88 | ```html
89 |
90 |
91 |
92 |
93 |
101 | ```
102 |
103 | 达到的效果和原来的开源的游戏效果完全一致
104 |
105 | * 原来用 JS 运行的效果:[http://codeincomplete.com/projects/racer/v4.final.html](http://codeincomplete.com/projects/racer/v4.final.html)
106 | * 用虚拟机运行 nestscript 二进制的效果:[https://livoras.github.io/nestscript-demo/index.html](https://livoras.github.io/nestscript-demo/index.html)
107 |
108 | ### demo 原理
109 |
110 | 编译的过程非常简单,只不过是把原来游戏的几个逻辑文件合并在一起:
111 |
112 | ```bash
113 | cat common.js stats.js main.js > game.js
114 | ```
115 |
116 | 然后用 nestscript 编译成二进制文件:
117 |
118 | ```bash
119 | nsc compile game.js game
120 | ```
121 |
122 | 再在 html 中引入虚拟机 vm.js,然后通过网络请求获取 game 二进制文件,接着运行二进制:
123 |
124 | ```html
125 |
126 |
127 |
128 |
129 |
137 | ```
138 |
139 | ## API
140 |
141 | ### nsc compile [source.js] [binary]
142 |
143 | 把 JavaScript 文件编译成二进制,例如 `npx nsc compile game.js game`。注意,目前仅支持 ES5 的语法。
144 |
145 | ### nsc run [binary]
146 |
147 | 通过 nestscript 虚拟机运行编译好的二进制,例如 `npx nsc run game`
148 |
149 | ### createVMFromArrayBuffer(buffer: ArrayBuffer, context: any)
150 |
151 | 由 `dist/vm.js` 提供的方法,解析编译好的二进制文件,返回一个虚拟机实例,并且可以准备执行。例如:
152 |
153 | ```javascript
154 | const vm = createVMFromArrayBuffer(buffer, context)
155 | ```
156 |
157 | `buffer` 指的是二进制用 JavaScript 的 ArrayBuffer 的展示形式;
158 |
159 | `context` 相当于传给虚拟机的一个全局运行环境,因为虚拟机的运行环境和外部分离开来的。它对 window、global 这些已有的 JavaScript 环境无知,所以需要手动传入一个 `context` 来告知虚拟机目前的全局环境。虚拟机的全局变量、属性都会从传入的 `contenxt` 中拿到。
160 |
161 | 例如,如果代码只用到全局的 `Date` 属性,那么除了可以直接传入 `window` 对象以外,还可以这么做:
162 |
163 | ```javascript
164 | const vm = createVMFromArrayBuffer(buffer, { Date })
165 | ```
166 |
167 | ### VirtualMachine::run()
168 |
169 | `createVMFromArrayBuffer` 返回的虚拟机实例有 `run` 方法可以运行代码:
170 |
171 | ```javascript
172 | const vm = createVMFromArrayBuffer(buffer, { Date })
173 | vm.run()
174 | ```
175 |
176 | ## nestscript 指令集
177 |
178 | ```
179 | MOV, ADD, SUB, MUL, DIV, MOD,
180 | EXP, NEG, INC, DEC,
181 |
182 | LT, GT, EQ, LE, GE, NE,
183 | LG_AND, LG_OR, XOR, NOT, SHL, SHR,
184 |
185 | JMP, JE, JNE, JG, JL, JIF, JF,
186 | JGE, JLE, PUSH, POP, CALL, PRINT,
187 | RET, PAUSE, EXIT,
188 |
189 | CALL_CTX, CALL_VAR, CALL_REG, MOV_CTX, MOV_PROP,
190 | SET_CTX,
191 | NEW_OBJ, NEW_ARR, SET_KEY,
192 | FUNC, ALLOC,
193 | ```
194 |
195 | 详情请见 [nestscript 指令集手册](https://github.com/livoras/nestscript/blob/master/docs/ir.md)。
196 |
197 | 例如使用指令编写的,斐波那契数列:
198 |
199 | ```javascript
200 | func @@main() {
201 | CLS @fibonacci;
202 | REG %r0;
203 | FUNC $RET @@f0;
204 | FUNC @fibonacci @@f0;
205 | MOV %r0 10;
206 | PUSH %r0;
207 | CALL_REG @fibonacci 1 false;
208 | }
209 | func @@f0(.n) {
210 | REG %r0;
211 | REG %r1;
212 | REG %r2;
213 | REG %r3;
214 | MOV %r0 .n;
215 | MOV %r1 1;
216 | LT %r0 %r1;
217 | JF %r0 _l1_;
218 | MOV %r1 0;
219 | MOV $RET %r1;
220 | RET;
221 | JMP _l0_;
222 | LABEL _l1_:
223 | LABEL _l0_:
224 | MOV %r0 .n;
225 | MOV %r1 2;
226 | LE %r0 %r1;
227 | JF %r0 _l3_;
228 | MOV %r1 1;
229 | MOV $RET %r1;
230 | RET;
231 | JMP _l2_;
232 | LABEL _l3_:
233 | LABEL _l2_:
234 | MOV %r2 .n;
235 | MOV %r3 1;
236 | SUB %r2 %r3;
237 | PUSH %r2;
238 | CALL_REG @fibonacci 1 false;
239 | MOV %r0 $RET;
240 | MOV %r2 .n;
241 | MOV %r3 2;
242 | SUB %r2 %r3;
243 | PUSH %r2;
244 | CALL_REG @fibonacci 1 false;
245 | MOV %r1 $RET;
246 | ADD %r0 %r1;
247 | MOV $RET %r0;
248 | RET;
249 | }
250 | ```
251 |
252 | ## TODO
253 | - [ ] `export`,`import` 模块支持
254 | - [ ] `class` 支持
255 | - [ ] 中间代码优化
256 | - [x] 基本中间代码优化
257 | - [ ] 属性访问优化
258 | - [ ] 文档:
259 | - [ ] IR 指令手册
260 | - [x] 安装文档
261 | - [x] 使用手册
262 | - [x] 使用 demo
263 | - [x] `null`, `undefined` keyword
264 | - [x] 正则表达式
265 | - [x] label 语法
266 | - [x] `try catch`
267 | - [x] try catch block
268 | - [x] error 对象获取
269 | - [x] ForInStatement
270 | - [x] 支持 function.length
271 |
272 | * * *
273 |
274 | ## Change Log
275 | ### 2020-10-10
276 | * 函数调用的时候延迟 new Scope 和 scope.fork 可以很好提升性能(~500ms)
277 |
278 | ### 2020-10-09
279 | * 性能优化:不使用 `XXXBuffer.set` 从 buffer 读取指令速度更快
280 |
281 | ### 2020-09-18
282 | * 解决 try catch 调用栈退出到 catch 的函数的地方
283 |
284 | ### 2020-09-08
285 | * 重新设计闭包、普通变量的实现方式,使用 scope chain、block chain
286 | * 实现块级作用域
287 | * 使用块级作用域实现 `error` 参数在 `catch` 的使用
288 |
289 | ### 2020-08-25
290 | * 闭包的形式应该是:
291 | * FUNC 每次都返回一个新的函数,并且记录上一层的 closure table
292 | * 调用的时候根据旧的 closure table 构建新的 closure table
293 |
294 | ### 2020-08-21
295 | * fix 闭包生成的顺序问题
296 | * 编译第三方库 moment.js, moment.min.js, lodash.js, lodash.min.js 成功并把编译加入测试
297 |
298 | ### 2020-08-20
299 | * 编译 moment.js 成功
300 | * fix if else 语句的顺序问题
301 |
302 | ### 2020-08-19
303 | * ForInStatement
304 |
305 | ### 2020-08-14
306 | * 编译 lodash 成功
307 |
308 | ### 2020-08-14
309 | * `arguments` 参数支持
310 |
311 | ### 2020-08-12
312 | * fix 闭包问题
313 | * 继续编译 lodash:发现没有 try catch 的实现
314 |
315 | ### 2020-08-11
316 | * 继续编译 lodash,发现了运行时闭包声明顺序导致无法获取闭包的 bug
317 |
318 | ### 2020-08-10
319 | * 编译 lodash 成功(运行失败)
320 | * 给函数参数增加闭包声明
321 | * UpdateExpression 的前后缀表达存储
322 |
323 | ### 2020-08-06
324 | * 完成 label 语法:循环、block label
325 |
326 | ### 2020-08-05
327 | * `null`, `undefined`
328 | * 正则表达式字面量
329 |
330 | ### 2020-08-03
331 | * `while`, `do while`, `continue` codegen
332 | * 更多测试
333 |
334 | ### 2020-07-31
335 | * 第一版 optimizer 完成
336 |
337 | ### 2020-07-30
338 | * 设计代码优化器的流程图
339 |
340 | ### 2020-07-29
341 | * 把操作数的字节数存放在类型的字节末端,让操作数的字节数量可以动态获取
342 | * 对于 Number 类型使用 Float64 来存储,对于其他类型的操作数用 Int32 存储
343 | * 可以较好地压缩二进制程序的大小.
344 |
345 | ### 2020-07-27
346 | * 重新设计操作数的生成规则
347 |
348 | ### 2020-07-23
349 | * 函数的调用有几种情况
350 | * vm 调用自身函数
351 | * vm 调用外部函数
352 | * 外部调用 vm 的函数
353 | * 所以:
354 | * vm 在调用函数的时候需要区分是那个环境的函数(函数包装 + instanceof)
355 | * 如果是自身的函数,不需要对参数进行操作
356 | * 如果是外部函数,需要把已经入栈的函数出栈再传给外部函数
357 | * 内部函数在被调用的时候,需要区分是那个环境调用的(NumArgs 包装 + instanceof)
358 | * 如果来自己的调用,不需要进行特别的操作
359 | * 如果是来自外部的调用,需要把参数入栈,并且要嵌入内部循环等待虚拟机函数结束以后再返回
360 | * 让函数可以正确绑定 this,不管是 vm 内部还是外部的
361 |
362 | ### 2020-07-22
363 | * 代码生成中表达式结果的处理原则:所有没有向下一层传递 s.r0 的都要处理 s.r0
364 | * 三目运算符 A ? B : C 的 codegen(复用 IfStatement)
365 |
366 | ### 2020-07-21
367 | * 自动包装 @@main 函数,这样就不需要主动提供 main 函数,更接近 JS
368 |
369 | ### 2020-07-20
370 | * 更多测试
371 | * 完成 +=, -=, *=, /= 操作符
372 |
373 | ### 2020-07-17
374 | * 新增测试 & CI
375 | * uinary expression 的实现:+, -, ~, !, void, delete
376 |
377 | ### 2020-07-16
378 | * 逻辑表达式 "&&" 和 "||" 的 codegen 和虚拟机实现
379 | * 完成闭包在虚拟机中的实现
380 |
381 | ### 2020-07-15
382 | * 完成闭包变量的标记方式:内层函数“污染”外层的方式
383 | * 重构代码生成的方式,使用函数数组延迟代码生成,这样可以在标记完闭包变量以后再进行 codegen
384 | * 设计闭包变量和普通变量的标记方式“@xxx”表示闭包变量,“%xxx”表示普通自动生成的寄存器
385 | * 下一步设计闭包变量在汇编器和虚拟机中的生成和调取机制
386 |
387 | ### 2020-07-13
388 | * 设计闭包的实现
389 | * 实现 `CALL_REG R0 3` 指令,可以把函数缓存到变量中随后进行调用
390 |
391 | ### 2020-07-10
392 | * 支持回调函数的 codegen
393 | * 完成基本的 JS -> 汇编的转化和运行
394 | * 循环 codegen
395 | * 三种值的更新和获取
396 | * Identifier
397 | * context
398 | * variables
399 | * Memeber
400 |
401 | ### 2020-07-09
402 | * 完成二进制表达式的 codegen
403 | * 完成简单的赋值语句
404 | * 完成对象属性访问的 codegen
405 | * if 语句的 codegen
406 |
407 | ### 2020-07-03
408 | * 确定使用动态分配 & 释放寄存器方案
409 | * 表达式计算值存储到寄存器方案,寄存器名称外层生成、传入、释放
410 |
411 | ### 2020-06-22
412 | * 开始使用 acorn 解析 ast,准备把 ast 编译成 IR 指令
413 |
414 | ### 2020-06-19
415 | * `FUNC R0 sayHi`: 构建 JS 函数封装 `sayHi` 函数并存放到 R0 寄存器,可以用作 JS 的回调参数,见 `example/callback.nes`
416 | * `CALL_VAR R0 "forEach" 1`: 调用寄存器里面存值的某个方法
417 | * `MOV_PROP R0 R1 "length"`: 将 R1 寄存器的值的 "length" 的值放到 R0 寄存器中
418 |
419 | ### 2020-06-18
420 | * 完成
421 | * `NEW_ARR R0`: 字面量数组
422 | * `NEW_OBJ R0`: 字面量对象
423 | * `SET_KEY R0 "0" "hello"`: 设置某个寄存器里面的 key value 值
424 | * `CALL_CTX "console" "log" 1`: 调用 ctx 里面的某个函数方法
425 | * `MOV_CTX R0 "console.log"`: 把 ctx 某个值移动到寄存器
426 |
427 | ### 2020-06-17
428 | * 完成基本的汇编器和虚拟机
429 | * 完成命令行工具 nsc,可以 `nsc compile src dest` 将文本代码 -> 二进制文件,并且用 `nsc run file` 执行
430 | * 斐波那契数列计算例子
431 | * 编译打包成第三方包
432 |
--------------------------------------------------------------------------------
/test/textures/lodash.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
4 | * Build: `lodash core -o ./dist/lodash.core.js`
5 | */
6 | ;(function(){function n(n){return H(n)&&pn.call(n,"callee")&&!yn.call(n,"callee")}function t(n,t){return n.push.apply(n,t),n}function r(n){return function(t){return null==t?Z:t[n]}}function e(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function u(n,t){return j(t,function(t){return n[t]})}function o(n){return n instanceof i?n:new i(n)}function i(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t}function c(n,t,r){if(typeof n!="function")throw new TypeError("Expected a function");
7 | return setTimeout(function(){n.apply(Z,r)},t)}function f(n,t){var r=true;return mn(n,function(n,e,u){return r=!!t(n,e,u)}),r}function a(n,t,r){for(var e=-1,u=n.length;++et}function b(n,t,r,e,u){return n===t||(null==n||null==t||!H(n)&&!H(t)?n!==n&&t!==t:y(n,t,r,e,b,u))}function y(n,t,r,e,u,o){var i=Nn(n),c=Nn(t),f=i?"[object Array]":hn.call(n),a=c?"[object Array]":hn.call(t),f="[object Arguments]"==f?"[object Object]":f,a="[object Arguments]"==a?"[object Object]":a,l="[object Object]"==f,c="[object Object]"==a,a=f==a;o||(o=[]);var p=An(o,function(t){return t[0]==n}),s=An(o,function(n){
9 | return n[0]==t});if(p&&s)return p[1]==t;if(o.push([n,t]),o.push([t,n]),a&&!l){if(i)r=T(n,t,r,e,u,o);else n:{switch(f){case"[object Boolean]":case"[object Date]":case"[object Number]":r=J(+n,+t);break n;case"[object Error]":r=n.name==t.name&&n.message==t.message;break n;case"[object RegExp]":case"[object String]":r=n==t+"";break n}r=false}return o.pop(),r}return 1&r||(i=l&&pn.call(n,"__wrapped__"),f=c&&pn.call(t,"__wrapped__"),!i&&!f)?!!a&&(r=B(n,t,r,e,u,o),o.pop(),r):(i=i?n.value():n,f=f?t.value():t,
10 | r=u(i,f,r,e,o),o.pop(),r)}function g(n){return typeof n=="function"?n:null==n?X:(typeof n=="object"?d:r)(n)}function _(n,t){return nt&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Array(u);++ei))return false;for(var c=-1,f=true,a=2&r?[]:Z;++cr?jn(e+r,0):r:0,r=(r||0)-1;for(var u=t===t;++rarguments.length,mn)}function G(n,t){var r;if(typeof t!="function")throw new TypeError("Expected a function");return n=Fn(n),
16 | function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=Z),r}}function J(n,t){return n===t||n!==n&&t!==t}function M(n){var t;return(t=null!=n)&&(t=n.length,t=typeof t=="number"&&-1=t),t&&!U(n)}function U(n){return!!V(n)&&(n=hn.call(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function V(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function H(n){return null!=n&&typeof n=="object"}function K(n){
17 | return typeof n=="number"||H(n)&&"[object Number]"==hn.call(n)}function L(n){return typeof n=="string"||!Nn(n)&&H(n)&&"[object String]"==hn.call(n)}function Q(n){return typeof n=="string"?n:null==n?"":n+""}function W(n){return null==n?[]:u(n,Dn(n))}function X(n){return n}function Y(n,r,e){var u=Dn(r),o=h(r,u);null!=e||V(r)&&(o.length||!u.length)||(e=r,r=n,n=this,o=h(r,Dn(r)));var i=!(V(e)&&"chain"in e&&!e.chain),c=U(n);return mn(o,function(e){var u=r[e];n[e]=u,c&&(n.prototype[e]=function(){var r=this.__chain__;
18 | if(i||r){var e=n(this.__wrapped__);return(e.__actions__=A(this.__actions__)).push({func:u,args:arguments,thisArg:n}),e.__chain__=r,e}return u.apply(n,t([this.value()],arguments))})}),n}var Z,nn=1/0,tn=/[&<>"']/g,rn=RegExp(tn.source),en=/^(?:0|[1-9]\d*)$/,un=typeof self=="object"&&self&&self.Object===Object&&self,on=typeof global=="object"&&global&&global.Object===Object&&global||un||Function("return this")(),cn=(un=typeof exports=="object"&&exports&&!exports.nodeType&&exports)&&typeof module=="object"&&module&&!module.nodeType&&module,fn=function(n){
19 | return function(t){return null==n?Z:n[t]}}({"&":"&","<":"<",">":">",'"':""","'":"'"}),an=Array.prototype,ln=Object.prototype,pn=ln.hasOwnProperty,sn=0,hn=ln.toString,vn=on._,bn=Object.create,yn=ln.propertyIsEnumerable,gn=on.isFinite,_n=function(n,t){return function(r){return n(t(r))}}(Object.keys,Object),jn=Math.max,dn=function(){function n(){}return function(t){return V(t)?bn?bn(t):(n.prototype=t,t=new n,n.prototype=Z,t):{}}}();i.prototype=dn(o.prototype),i.prototype.constructor=i;
20 | var mn=function(n,t){return function(r,e){if(null==r)return r;if(!M(r))return n(r,e);for(var u=r.length,o=t?u:-1,i=Object(r);(t?o--:++or&&(r=jn(e+r,0));n:{for(t=g(t),e=n.length,r+=-1;++re||o&&c&&a||!u&&a||!i){r=1;break n}if(!o&&r,
77 | codes: string[][],
78 | numArgs: number,
79 | localSize: number,
80 | globals: any,
81 | ip?: number,
82 | index?: number,
83 | bytecodes?: ArrayBuffer,
84 | labels?: any,
85 | }
86 |
87 |
88 | // tslint:disable-next-line: no-big-function
89 | export const parseCodeToProgram = (program: string): Buffer => {
90 | const funcsTable = {}
91 | const globalSymbols = new Map()
92 | const stringTable: string[] = []
93 | const stringIndex: any = {}
94 | const funcs = parseAssembler(optimizeCode(program))
95 | // const funcs = parseAssembler(program) // program
96 | const _symbols = new Map()
97 | let symbolsCounter = 0
98 |
99 | const getSymbolIndex = (name: string): number => {
100 | if (_symbols.has(name)) {
101 | return _symbols.get(name)!
102 | } else {
103 | _symbols.set(name, symbolsCounter++)
104 | return _symbols.get(name)!
105 | }
106 | }
107 |
108 | // .trim()
109 | // .match(/func\s[\s\S]+?\}/g) || []
110 |
111 | // console.log(funcs, '--->')
112 | // 1 pass
113 | const funcsInfo: any[] = []
114 | let globalSize: number = 0
115 | funcs.forEach((func: IParsedFunction): void => {
116 | if (!func) { return }
117 | const funcInfo = parseFunction(func)
118 | funcInfo.index = funcsInfo.length
119 | funcsInfo.push(funcInfo)
120 | funcsTable[funcInfo.name] = funcInfo
121 | funcInfo.globals.forEach((g: string): void => {
122 | globalSymbols[g] = globalSize++
123 | })
124 | })
125 |
126 | // 2 pass
127 | funcsInfo.forEach((funcInfo: IFuncInfo): void => {
128 | const symbols = funcInfo.symbols
129 | // console.log(symbols)
130 | funcInfo.codes.forEach((code: any[]): void => {
131 | const op = code[0]
132 | code[0] = I[op]
133 | if (op === 'CALL' && op) {
134 | code[1] = {
135 | type: IOperatantType.FUNCTION_INDEX,
136 | value: funcsTable[code[1]].index,
137 | }
138 | code[2] = {
139 | type: IOperatantType.ARG_COUNT,
140 | value: +code[2],
141 | }
142 | } else {
143 | code.forEach((o: any, i: number): void => {
144 | if (i === 0) { return }
145 |
146 | if (
147 | (op === 'TRY' && (i === 1 || i === 2)) ||
148 | ['JMP'].includes(op) ||
149 | (['JE', 'JNE', 'JG', 'JL', 'JGE', 'JLE'].includes(op) && i === 3) ||
150 | (['JF', 'JIF'].includes(op) && i === 2) ||
151 | (op === 'FORIN' && (i === 3 || i === 4))
152 | ) {
153 | code[i] = {
154 | type: IOperatantType.ADDRESS,
155 | value: funcInfo.labels[code[i]],
156 | }
157 | return
158 | }
159 |
160 | if (['FUNC'].includes(op) && i === 2) {
161 | code[i] = {
162 | type: IOperatantType.FUNCTION_INDEX,
163 | value: funcsTable[code[i]].index,
164 | }
165 | return
166 | }
167 |
168 | // const namemap = {
169 | // '@': IOperatantType.CLOSURE_REGISTER,
170 | // '.': IOperatantType.PARAM_REGISTER,
171 | // '%': IOperatantType.REGISTER,
172 | // }
173 |
174 | if (!['VAR', 'CLS'].includes(op)) {
175 | /** 寄存器 */
176 | let regIndex = symbols.get(o)
177 | if (regIndex !== undefined) {
178 | code[i] = {
179 | type: o[0] === IOperatantType.REGISTER,
180 | value: regIndex,
181 | }
182 | return
183 | }
184 |
185 | /** 全局 */
186 | regIndex = globalSymbols.get(o)
187 | if (regIndex !== undefined) {
188 | code[i] = {
189 | type: IOperatantType.GLOBAL,
190 | value: regIndex + 1, // 留一位给 RET
191 | }
192 | return
193 | }
194 |
195 | if (o === 'true' || o === 'false') {
196 | code[i] = {
197 | type: IOperatantType.BOOLEAN,
198 | value: o === 'true' ? 1: 0,
199 | }
200 | return
201 | }
202 |
203 | if (o === 'null') {
204 | code[i] = {
205 | type: IOperatantType.NULL,
206 | }
207 | return
208 | }
209 |
210 | if (o === 'undefined') {
211 | code[i] = {
212 | type: IOperatantType.UNDEFINED,
213 | }
214 | return
215 | }
216 |
217 | /** 返回类型 */
218 | if (o === '$RET') {
219 | code[i] = {
220 | type: IOperatantType.RETURN_VALUE,
221 | }
222 | return
223 | }
224 |
225 | /** 字符串 */
226 | if (o.match(/^\"[\s\S]*\"$/) || o.match(/^\'[\s\S]*\'$/)) {
227 | const str = o.replace(/^[\'\"]|[\'\"]$/g, '')
228 | let index = stringIndex[str]
229 | index = typeof index === 'number' ? index : void 0 // 'toString' 不就挂了?
230 | code[i] = {
231 | type: IOperatantType.STRING,
232 | value: index === undefined
233 | ? stringTable.length
234 | : index,
235 | }
236 | if (index === undefined) {
237 | stringIndex[str] = stringTable.length
238 | stringTable.push(str)
239 | }
240 | return
241 | }
242 |
243 | /** Number */
244 | if (!isNaN(+o)) {
245 | code[i] = {
246 | type: IOperatantType.NUMBER,
247 | value: +o,
248 | }
249 | return
250 | }
251 | }
252 |
253 | /** 普通变量或者闭包 */
254 | const symbolIndex = getSymbolIndex(o.replace(/^@/, ''))
255 | code[i] = {
256 | type: o.startsWith('@')
257 | ? IOperatantType.CLOSURE_REGISTER
258 | : IOperatantType.VAR_SYMBOL,
259 | value: symbolIndex,
260 | }
261 | // console.log('symbol index -> ', symbolIndex, o)
262 | })
263 | }
264 | })
265 | })
266 |
267 | // console.log('\n\n====================================')
268 | // funcsInfo[0].codes.forEach((c: any): void => {
269 | // console.log(I[c[0]], c.slice(1))
270 | // })
271 | // console.log('====================================\n\n')
272 | const stream = parseToStream(funcsInfo, stringTable, globalSize)
273 | return Buffer.from(stream)
274 | }
275 |
276 | /**
277 | * header
278 | * codes (op(1) operantType(1) value(??) oprantType value | ...)
279 | * functionTable (ip(1) | numArgs(2))
280 | * stringTable (len(4) str | len(4) str)
281 | */
282 | // tslint:disable-next-line: no-big-function
283 | const parseToStream = (funcsInfo: IFuncInfo[], strings: string[], globalsSize: number): ArrayBuffer => {
284 | const stringTable = parseStringTableToBuffer(strings)
285 |
286 | let buffer = new ArrayBuffer(0)
287 | let mainFunctionIndex: number = 0
288 | // tslint:disable-next-line: no-big-function
289 | funcsInfo.forEach((funcInfo: IFuncInfo): void => {
290 | const currentFunctionAddress = funcInfo.ip = buffer.byteLength
291 |
292 | if (funcInfo.name === '@@main') {
293 | mainFunctionIndex = funcInfo.index!
294 | }
295 |
296 | let isAddressCodeByteChanged = true
297 | const DEFAULT_ADDRESS_LEN = 0
298 | let codeAddress: number[] = []
299 | let addressCandidates: { codeIndex: number, bufferIndex: number, addressByteLen: number }[] = []
300 | let currentFuncBuffer: ArrayBuffer = new ArrayBuffer(0)
301 | const addressCodeByteMap: any = {}
302 | while(isAddressCodeByteChanged) {
303 | // console.log("////??/")
304 | isAddressCodeByteChanged = false
305 | currentFuncBuffer = funcInfo.bytecodes = new ArrayBuffer(0)
306 | codeAddress = []
307 | addressCandidates = []
308 | const appendBuffer = (buf: ArrayBuffer): void => {
309 | // buffer = concatBuffer(buffer, buf)
310 | currentFuncBuffer = funcInfo.bytecodes = concatBuffer(funcInfo.bytecodes!, buf)
311 | }
312 |
313 | funcInfo.codes.forEach((code: any, i): void => {
314 | const codeOffset = currentFunctionAddress + currentFuncBuffer.byteLength
315 | const addressByteLength: number = getByteLengthFromInt32(codeOffset)
316 | codeAddress.push(codeOffset)
317 |
318 | /* if byte length change should generate again */
319 | if ((addressCodeByteMap[i] || DEFAULT_ADDRESS_LEN) !== addressByteLength){
320 | isAddressCodeByteChanged = true
321 | addressCodeByteMap[i] = addressByteLength
322 | }
323 |
324 | /* append operator buffer */
325 | const operator = code[0]
326 | appendBuffer(Uint8Array.from([operator]).buffer)
327 |
328 | /* loop operant */
329 | code.forEach((o: { type: IOperatantType, value: any }, j: number): void => {
330 | if (j === 0) { return }
331 | if (o.type !== IOperatantType.ADDRESS) {
332 | const buf = createOperantBuffer(o.type, o.value)
333 | appendBuffer(buf)
334 | } else {
335 | const byteLength = addressCodeByteMap[o.value] || DEFAULT_ADDRESS_LEN
336 | const buf = createOperantBuffer(o.type, 0, byteLength)
337 | appendBuffer(buf)
338 | addressCandidates.push({
339 | codeIndex: o.value,
340 | bufferIndex: currentFuncBuffer.byteLength - byteLength,
341 | addressByteLen: byteLength,
342 | })
343 | }
344 | })
345 | })
346 | }
347 |
348 | addressCandidates.forEach(({ codeIndex, bufferIndex, addressByteLen }, i): void => {
349 | const address = codeAddress[codeIndex]
350 | const buf = new Uint8Array(Int32Array.from([address]).buffer)
351 | // console.log(i, '=====================================')
352 | // console.log('codeIndex -> ', codeIndex)
353 | // console.log('address -> ', address)
354 | // console.log('bufferIndex -> ', bufferIndex)
355 | // console.log('addressBytenLen -> ', addressByteLen)
356 | const functionBuffer = new Uint8Array(currentFuncBuffer)
357 | functionBuffer.set(buf.slice(0, addressByteLen), bufferIndex)
358 | })
359 |
360 | // console.log(' current -----------------> ', new Uint8Array(currentFuncBuffer)[0])
361 | buffer = concatBuffer(buffer, currentFuncBuffer)
362 | })
363 |
364 | /**
365 | * Header:
366 | *
367 | * mainFunctionIndex: 4
368 | * funcionTableBasicIndex: 4
369 | * stringTableBasicIndex: 4
370 | * globalsSize: 4
371 | */
372 | const FUNC_SIZE = 4 + 2 + 2 // ip + numArgs + localSize
373 | const funcionTableBasicIndex = 4 * 4 + buffer.byteLength
374 | const stringTableBasicIndex = funcionTableBasicIndex + FUNC_SIZE * funcsInfo.length
375 | const headerView = new Uint32Array(4)
376 | headerView[0] = mainFunctionIndex
377 | headerView[1] = funcionTableBasicIndex
378 | headerView[2] = stringTableBasicIndex
379 | headerView[3] = globalsSize
380 | buffer = concatBuffer(headerView.buffer, buffer)
381 |
382 | /** Function Table */
383 | funcsInfo.forEach((funcInfo: IFuncInfo, i: number): void => {
384 | const ipBuf = new Uint32Array(1)
385 | const numArgsAndLocal = new Uint16Array(2)
386 | ipBuf[0] = funcInfo.ip!
387 | numArgsAndLocal[0] = funcInfo.numArgs
388 | numArgsAndLocal[1] = funcInfo.localSize
389 | const funcBuf = concatBuffer(ipBuf.buffer, numArgsAndLocal.buffer)
390 | buffer = concatBuffer(buffer, funcBuf)
391 | })
392 |
393 | /** append string buffer */
394 | buffer = concatBuffer(buffer, stringTable.buffer)
395 | return buffer
396 | }
397 |
398 | const parseStringTableToBuffer = (stringTable: string[]): {
399 | buffer: ArrayBuffer,
400 | indexes: { [x in number]: number },
401 | } => {
402 | /** String Table */
403 | let strBuf = new ArrayBuffer(0)
404 | const indexes: any = {}
405 | stringTable.forEach((str: string, i: number): void => {
406 | indexes[i] = strBuf.byteLength
407 | const lenBuf = new Uint32Array(1)
408 | lenBuf[0] = str.length
409 | strBuf = concatBuffer(strBuf, lenBuf.buffer)
410 | strBuf = concatBuffer(strBuf, stringToArrayBuffer(str))
411 | })
412 | return {
413 | buffer: strBuf,
414 | indexes,
415 | }
416 | }
417 |
418 | const parseFunction = (func: IParsedFunction): IFuncInfo => {
419 | const funcName = func.functionName
420 | const args = func.params
421 | const body = func.instructions
422 |
423 | const vars = body.filter((stat: string[]): boolean => stat[0] === 'REG')
424 | const globals = body
425 | .filter((stat: string[]): boolean => stat[0] === 'GLOBAL')
426 | .map((stat: string[]): string => stat[1])
427 | const codes = body.filter((stat: string[]): boolean => stat[0] !== 'REG' && stat[0] !== 'GLOBAL')
428 | const symbols = new Map()
429 | args.forEach((arg: string, i: number): void => {
430 | symbols.set(arg, -4 - i)
431 | })
432 | let j = 0
433 | vars.forEach((v: string[], i: number): void => {
434 | const reg = v[1]
435 | // if (reg.startsWith('@c')) {
436 | // symbols[reg] = -1
437 | // } else {
438 | symbols.set(reg, j + 1)
439 | j++
440 | // }
441 | })
442 |
443 | if (funcName === '@@main') {
444 | codes.push(['EXIT'])
445 | } else if (codes.length === 0 || codes[codes.length - 1][0] !== 'RET') {
446 | codes.push(['RET'])
447 | }
448 |
449 | const labels: any = {}
450 | const codesWithoutLabel: string[][] = []
451 | codes.forEach((code: string[]): void => {
452 | if (code[0] === 'LABEL') {
453 | labels[code[1]] = codesWithoutLabel.length
454 | } else {
455 | codesWithoutLabel.push(code)
456 | }
457 | })
458 | // console.log('===>', funcName, codesWithoutLabel, labels)
459 |
460 | return {
461 | name: funcName,
462 | numArgs: args.length,
463 | symbols,
464 | codes: codesWithoutLabel,
465 | localSize: vars.length,
466 | globals,
467 | labels,
468 | }
469 | }
470 |
--------------------------------------------------------------------------------
/closure design.uxf:
--------------------------------------------------------------------------------
1 |
2 |
3 | 7
4 |
5 | UMLState
6 |
7 | 287
8 | 140
9 | 70
10 | 28
11 |
12 | UCT1
13 | bg=red
14 |
15 |
16 |
17 | Relation
18 |
19 | 350
20 | 147
21 | 91
22 | 84
23 |
24 | lt=<<.
25 | call and fork
26 | 100.0;100.0;30.0;100.0;30.0;10.0;10.0;10.0
27 |
28 |
29 | UMLState
30 |
31 | 420
32 | 203
33 | 70
34 | 28
35 |
36 | CCT2
37 | bg=blue
38 |
39 |
40 |
41 | Relation
42 |
43 | 91
44 | 140
45 | 119
46 | 28
47 |
48 | lt=<<.
49 | fork
50 | 150.0;20.0;10.0;20.0
51 |
52 |
53 | Relation
54 |
55 | 91
56 | 105
57 | 119
58 | 28
59 |
60 | lt=<<-
61 | front or back
62 | 150.0;20.0;10.0;20.0
63 |
64 |
65 | Relation
66 |
67 | 483
68 | 210
69 | 77
70 | 28
71 |
72 | lt=<<-
73 | 1
74 | 90.0;20.0;10.0;20.0
75 |
76 |
77 | UMLState
78 |
79 | 112
80 | 189
81 | 70
82 | 28
83 |
84 | CCT
85 |
86 |
87 |
88 | UMLState
89 |
90 | 546
91 | 203
92 | 70
93 | 28
94 |
95 | BCT
96 |
97 |
98 |
99 | UMLState
100 |
101 | 672
102 | 203
103 | 70
104 | 28
105 |
106 | BCT
107 |
108 |
109 |
110 | Relation
111 |
112 | 609
113 | 210
114 | 77
115 | 28
116 |
117 | lt=<<-
118 | 2
119 | 90.0;20.0;10.0;20.0
120 |
121 |
122 | UMLState
123 |
124 | 798
125 | 203
126 | 70
127 | 28
128 |
129 | BCT
130 |
131 |
132 |
133 | Relation
134 |
135 | 735
136 | 210
137 | 77
138 | 28
139 |
140 | lt=<<-
141 | 7
142 | 90.0;20.0;10.0;20.0
143 |
144 |
145 | Relation
146 |
147 | 686
148 | 224
149 | 28
150 | 84
151 |
152 | lt=<<-
153 | 3
154 | 10.0;100.0;10.0;10.0
155 |
156 |
157 | UMLState
158 |
159 | 672
160 | 294
161 | 70
162 | 28
163 |
164 | BCT
165 |
166 |
167 |
168 | UMLState
169 |
170 | 672
171 | 385
172 | 70
173 | 28
174 |
175 | BCT
176 |
177 |
178 |
179 | Relation
180 |
181 | 686
182 | 315
183 | 28
184 | 84
185 |
186 | lt=<<-
187 | 4
188 | 10.0;100.0;10.0;10.0
189 |
190 |
191 | Relation
192 |
193 | 686
194 | 406
195 | 98
196 | 154
197 |
198 | lt=<<.
199 | define and fork
200 | 120.0;200.0;10.0;200.0;10.0;10.0
201 |
202 |
203 | Relation
204 |
205 | 714
206 | 315
207 | 28
208 | 84
209 |
210 | lt=<<-
211 | 5
212 | 10.0;10.0;10.0;100.0
213 |
214 |
215 | Relation
216 |
217 | 714
218 | 224
219 | 28
220 | 84
221 |
222 | lt=<<-
223 | 6
224 | 10.0;10.0;10.0;100.0
225 |
226 |
227 | Relation
228 |
229 | 735
230 | 196
231 | 77
232 | 28
233 |
234 | lt=<<-
235 | 8
236 | 10.0;20.0;90.0;20.0
237 |
238 |
239 | Relation
240 |
241 | 609
242 | 196
243 | 77
244 | 28
245 |
246 | lt=<<-
247 | 9
248 | 10.0;20.0;90.0;20.0
249 |
250 |
251 | Relation
252 |
253 | 483
254 | 196
255 | 77
256 | 28
257 |
258 | lt=<<-
259 | 10
260 | 10.0;20.0;90.0;20.0
261 |
262 |
263 | UMLState
264 |
265 | 959
266 | 532
267 | 70
268 | 28
269 |
270 | CCT5
271 | bg=blue
272 |
273 |
274 |
275 | Relation
276 |
277 | 448
278 | 224
279 | 91
280 | 308
281 |
282 | lt=<<.
283 | define and fork
284 | 10.0;420.0;10.0;10.0
285 |
286 |
287 | UMLState
288 |
289 | 420
290 | 672
291 | 70
292 | 28
293 |
294 | BCT
295 |
296 |
297 |
298 | UMLState
299 |
300 | 420
301 | 581
302 | 70
303 | 28
304 |
305 | CCT4
306 | bg=blue
307 |
308 |
309 |
310 | Relation
311 |
312 | 434
313 | 602
314 | 28
315 | 84
316 |
317 | lt=<<-
318 | 1
319 | 10.0;100.0;10.0;10.0
320 |
321 |
322 | UMLState
323 |
324 | 420
325 | 763
326 | 70
327 | 28
328 |
329 | BCT
330 |
331 |
332 |
333 | Relation
334 |
335 | 434
336 | 693
337 | 28
338 | 84
339 |
340 | lt=<<-
341 | 2
342 | 10.0;100.0;10.0;10.0
343 |
344 |
345 | Relation
346 |
347 | 462
348 | 693
349 | 28
350 | 84
351 |
352 | lt=<<-
353 | 3
354 | 10.0;10.0;10.0;100.0
355 |
356 |
357 | Relation
358 |
359 | 462
360 | 602
361 | 28
362 | 84
363 |
364 | lt=<<-
365 | 4
366 | 10.0;10.0;10.0;100.0
367 |
368 |
369 | UMLClass
370 |
371 | 280
372 | 119
373 | 616
374 | 315
375 |
376 | fucntion 1
377 | --
378 |
379 |
380 |
381 |
382 | UMLClass
383 |
384 | 231
385 | 476
386 | 329
387 | 329
388 |
389 | function 2 defined in function1
390 | --
391 |
392 |
393 |
394 |
395 | UMLState
396 |
397 | 959
398 | 623
399 | 70
400 | 28
401 |
402 | BCT
403 |
404 |
405 |
406 | Relation
407 |
408 | 973
409 | 553
410 | 28
411 | 84
412 |
413 | lt=<<-
414 | 1
415 | 10.0;100.0;10.0;10.0
416 |
417 |
418 | Relation
419 |
420 | 1022
421 | 630
422 | 77
423 | 28
424 |
425 | lt=<<-
426 | 2
427 | 90.0;20.0;10.0;20.0
428 |
429 |
430 | UMLClass
431 |
432 | 714
433 | 483
434 | 455
435 | 280
436 |
437 | function 3 defined in function 1
438 | --
439 |
440 |
441 |
442 |
443 | UMLState
444 |
445 | 1085
446 | 623
447 | 70
448 | 28
449 |
450 | BCT
451 |
452 |
453 |
454 | UMLState
455 |
456 | 959
457 | 714
458 | 70
459 | 28
460 |
461 | BCT
462 |
463 |
464 |
465 | Relation
466 |
467 | 1022
468 | 616
469 | 77
470 | 28
471 |
472 | lt=<<-
473 | 3
474 | 10.0;20.0;90.0;20.0
475 |
476 |
477 | Relation
478 |
479 | 973
480 | 644
481 | 28
482 | 84
483 |
484 | lt=<<-
485 | 4
486 | 10.0;100.0;10.0;10.0
487 |
488 |
489 | Relation
490 |
491 | 1001
492 | 644
493 | 28
494 | 84
495 |
496 | lt=<<-
497 | 5
498 | 10.0;10.0;10.0;100.0
499 |
500 |
501 | Relation
502 |
503 | 1001
504 | 553
505 | 28
506 | 84
507 |
508 | lt=<<-
509 | 6
510 | 10.0;10.0;10.0;100.0
511 |
512 |
513 | UMLState
514 |
515 | 420
516 | 518
517 | 70
518 | 28
519 |
520 | UCT2
521 | bg=red
522 |
523 |
524 |
525 | Relation
526 |
527 | 448
528 | 539
529 | 77
530 | 56
531 |
532 | lt=<<.
533 | call and fork
534 | 10.0;60.0;10.0;10.0
535 |
536 |
537 | UMLState
538 |
539 | 770
540 | 532
541 | 70
542 | 28
543 |
544 | UCT3
545 | bg=red
546 |
547 |
548 |
549 | Relation
550 |
551 | 833
552 | 532
553 | 140
554 | 28
555 |
556 | lt=<<.
557 | call and fork
558 | 180.0;20.0;10.0;20.0
559 |
560 |
561 | UMLState
562 |
563 | 259
564 | 581
565 | 70
566 | 28
567 |
568 | CCT3
569 | bg=blue
570 |
571 |
572 |
573 | Relation
574 |
575 | 273
576 | 602
577 | 28
578 | 84
579 |
580 | lt=<<-
581 | 1
582 | 10.0;100.0;10.0;10.0
583 |
584 |
585 | Relation
586 |
587 | 301
588 | 602
589 | 28
590 | 84
591 |
592 | lt=<<-
593 | 4
594 | 10.0;10.0;10.0;100.0
595 |
596 |
597 | UMLState
598 |
599 | 259
600 | 672
601 | 70
602 | 28
603 |
604 | BCT
605 |
606 |
607 |
608 | Relation
609 |
610 | 273
611 | 693
612 | 28
613 | 84
614 |
615 | lt=<<-
616 | 2
617 | 10.0;100.0;10.0;10.0
618 |
619 |
620 | Relation
621 |
622 | 301
623 | 693
624 | 28
625 | 84
626 |
627 | lt=<<-
628 | 3
629 | 10.0;10.0;10.0;100.0
630 |
631 |
632 | UMLState
633 |
634 | 259
635 | 763
636 | 70
637 | 28
638 |
639 | BCT
640 |
641 |
642 |
643 | Relation
644 |
645 | 287
646 | 525
647 | 147
648 | 70
649 |
650 | lt=<<.
651 | call and fork
652 | 10.0;80.0;10.0;10.0;190.0;10.0
653 |
654 |
655 |
--------------------------------------------------------------------------------
/test/js.spec.ts:
--------------------------------------------------------------------------------
1 | import { createOperantBuffer, getOperatantByBuffer } from '../src/utils'
2 | import { IOperatantType } from '../src/vm/vm'
3 | import { expect } from 'chai'
4 | import { tm, makeSpy } from './utils'
5 | const chai = require('chai')
6 | const spies = require('chai-spies')
7 |
8 | chai.use(spies)
9 |
10 | describe('variable scope', (): void => {
11 | it('outer variable should not be overwritter', (): void => {
12 | tm(`
13 | const a = 1
14 | const b = () => {
15 | let a = 2
16 | expect(a).equal(2)
17 | }
18 | b()
19 | expect(a).equal(1)
20 | `)
21 | })
22 |
23 | it('outer variable can be accessed', (): void => {
24 | tm(`
25 | let a = 1
26 | const b = () => {
27 | a = 2
28 | }
29 | expect(a).equal(1)
30 | b()
31 | expect(a).equal(2)
32 | `)
33 | })
34 | })
35 |
36 | describe("uinary operators", (): void => {
37 | it('+a', (): void => {
38 | tm(`
39 | const a = '-1000'
40 | expect(+a).equal(-1000)
41 | `)
42 | })
43 | it('-a', (): void => {
44 | tm(`
45 | const a = 1
46 | expect(-a).equal(-1)
47 | expect(a).equal(1)
48 | `)
49 | })
50 | it('void 0', ():void => {
51 | tm(`
52 | const a = void 5
53 | expect(a).equal(undefined)
54 | `)
55 | })
56 | it('undefined variable', (): void => {
57 | tm(`
58 | var undefined
59 | var n = undefined
60 | if (n === undefined) {
61 | console.log(n)
62 | } else {
63 | console.log("undefined")
64 | }
65 | `)
66 | })
67 | it('~a', (): void => {
68 | tm(`
69 | const a = 7
70 | expect(~a).equal(-8)
71 | expect(a).equal(7)
72 | `)
73 | })
74 | it('!a and true & false boolean value', (): void => {
75 | tm(`
76 | const b = true
77 | const a = !b
78 | expect(b).equal(true)
79 | expect(a).equal(false)
80 | expect(!!b).equal(true)
81 | expect(!!a).equal(false)
82 | `)
83 | })
84 | it('delete object property', (): void => {
85 | tm(`
86 | const a = { a: 'good', b: 'night' }
87 | expect(a.a).equal('good')
88 | delete a.a
89 | expect(a.a).equal(void 555)
90 | expect(a.b).equal('night')
91 | `)
92 | })
93 | })
94 |
95 | describe("binary operators", (): void => {
96 | it("a = b = 1", (): void => {
97 | tm(`
98 | let a = 1
99 | expect(a).equal(1)
100 | const b = a = 3
101 | expect(a).equal(b)
102 | expect(b).equal(3)
103 | `)
104 | })
105 |
106 | it("a + b", (): void => {
107 | tm(`
108 | const a = 1
109 | const b = 2
110 | expect(a + b).equal(3)
111 | expect(a).equal(1)
112 | expect(b).equal(2)
113 | `)
114 | })
115 |
116 | it("a - b", (): void => {
117 | tm(`
118 | const a = 1
119 | const b = 2
120 | expect(a - b).equal(-1)
121 | expect(a).equal(1)
122 | expect(b).equal(2)
123 | `)
124 | })
125 |
126 | it("a * b", (): void => {
127 | tm(`
128 | const a = 5
129 | const b = 5
130 | expect(a * b).equal(25)
131 | expect(a).equal(5)
132 | expect(b).equal(5)
133 | `)
134 | })
135 |
136 | it("a / b", (): void => {
137 | tm(`
138 | const a = 25
139 | const b = 5
140 | expect(a / b).equal(5)
141 | expect(a).equal(25)
142 | expect(b).equal(5)
143 | `)
144 | })
145 |
146 | it("<, >, <=, >=", (): void => {
147 | tm(`
148 | const a = 25
149 | const b = 5
150 | expect(a > b).equal(true)
151 | expect(b < a).equal(true)
152 | expect(a < b).equal(false)
153 | expect(b > a).equal(false)
154 | expect(1 >= 5).equal(false)
155 | expect(1 >= 1).equal(true)
156 | expect(1 >= 0).equal(true)
157 | expect(1 <= 0).equal(false)
158 | expect(5 <= 5).equal(true)
159 | expect(a).equal(25)
160 | expect(b).equal(5)
161 | `)
162 | })
163 |
164 | it('a % b', (): void => {
165 | tm(`
166 | const a = 25
167 | const b = 5
168 | expect(a % b).equal(0)
169 | expect(b % a).equal(5)
170 | expect(a).equal(25)
171 | expect(b).equal(5)
172 | `)
173 | })
174 |
175 | it('a++, ++a', (): void => {
176 | tm(`
177 | let a = 1
178 | let b = 1
179 | expect(a++).equal(1)
180 | expect(++b).equal(2)
181 | `)
182 | })
183 |
184 | it('+=, -=, /=, *=, &=, |=', (): void => {
185 | tm(`
186 | let a = 5
187 | let b = 1
188 | a += b
189 | expect(a).equal(6)
190 | expect(b).equal(1)
191 | a -= b
192 | expect(a).equal(5)
193 | expect(b).equal(1)
194 | const c = 2
195 | a *= c
196 | expect(a).equal(10)
197 | expect(c).equal(2)
198 | a /= c
199 | expect(a).equal(5)
200 | expect(c).equal(2)
201 | let d = 0b001
202 | let e = 0b010
203 | d |= e
204 | expect(d).equal(0b011)
205 | d &= e
206 | expect(d).equal(0b010)
207 | `)
208 | })
209 |
210 | it("<< && >>", (): void => {
211 | tm(`
212 | const a = 1
213 | expect(a << 1).equal(2)
214 | expect(a << 2).equal(4)
215 | expect(a << 3).equal(8)
216 |
217 | const b = 16
218 | expect(b >> 1).equal(8)
219 | expect(b >> 2).equal(4)
220 | expect(b >> 3).equal(2)
221 | `)
222 | })
223 |
224 | it('&, | , ^', (): void => {
225 | tm(`
226 | const a = 1
227 | const b = 2
228 | expect(a | b | 4).equal(7)
229 | expect(a | b).equal(3)
230 | expect(a & b).equal(0)
231 | expect(a).equal(1)
232 | expect(b).equal(2)
233 | expect(0b001 & 0b010).equal(0b000)
234 | expect(0b001 | 0b010).equal(0b011)
235 | expect(0b001 ^ 0b110).equal(0b111)
236 | `)
237 | })
238 |
239 | it('||, &&', (): void => {
240 | tm(`
241 | const a = true
242 | const b = false
243 | expect(a && b).equal(false)
244 | expect(a || b).equal(true)
245 | `)
246 | })
247 |
248 | it('===', (): void => {
249 | tm(`
250 | const a = 1
251 | const b = '1'
252 | expect(a === 1).equal(true)
253 | expect(a !== 1).equal(false)
254 | expect(a === '1').equal(false)
255 | expect(a !== '1').equal(true)
256 | expect(a).equal(1)
257 | expect(b).equal('1')
258 | `)
259 | })
260 |
261 | it('in', (): void => {
262 | tm(`
263 | const a = 'name'
264 | const b = { 'name': 'Jerry' }
265 | expect(a in b).equal(true)
266 | `)
267 | })
268 |
269 | })
270 |
271 | describe('conditional expression and if else expression', (): void => {
272 | it('a ? 1: 0', (): void => {
273 | tm(`
274 | const a = 100
275 | const b = 10
276 | const c = 5
277 | const d = a > 50
278 | ? b < 10
279 | ? 1
280 | : 2
281 | : c > 3
282 | ? 4
283 | : 5
284 | expect(d).equal(2)
285 | `)
286 |
287 | })
288 |
289 | it(`if else and nested if else`, (): void => {
290 | const spy = makeSpy()
291 | tm(`
292 | function test(a, b) {
293 | if (a) {
294 | if (b) {
295 | spy()
296 | } else {
297 | throw new Error('error')
298 | }
299 | spy()
300 | if (!b) {
301 | throw new Error('error')
302 | } else {
303 | spy()
304 | }
305 | }
306 | }
307 | test(true, true)
308 | expect(spy).to.be.called.exactly(3)
309 | `, { spy, Error })
310 | })
311 | })
312 |
313 | describe('class', (): void => {
314 | it(`new and instanceof`, (): void => {
315 | tm(`
316 | const a = new Date()
317 | expect(a instanceof Date).equal(true)
318 | `)
319 | })
320 | })
321 |
322 | describe('function', (): void => {
323 | it('call function of virtual machine', (): void => {
324 | tm(`
325 | const a = (b, c) => b + c
326 | const d = a
327 | expect(a(1, 2)).equal(3)
328 | expect(d(2, 3)).equal(5)
329 | `)
330 | })
331 |
332 | it(`not passing parameter`, (): void => {
333 | tm(`
334 | const main = (a, b, c) => {
335 | expect(a).equal('ok')
336 | expect(b).equal(undefined)
337 | expect(c).equal(undefined)
338 | b = 'ok2'
339 | expect(b).equal('ok2')
340 | }
341 | console.log("OK")
342 | main('ok')
343 | `)
344 | })
345 |
346 | it('call function of raw js', (): void => {
347 | tm(`
348 | const a = console.log
349 | expect(outFunc(1, 2)).equal(-1)
350 | `, { outFunc: (a: number, b: number): number => a - b })
351 | })
352 |
353 | it('call function of virual machine from raw js', (): void => {
354 | tm(`
355 | wrapper.sub = (a, b) => a - b
356 | expect(wrapper.getResult(100, 50)).equal(50)
357 | expect(wrapper.sub(39, 20)).equal(19)
358 | `, {
359 | wrapper: {
360 | getResult(a: number, b: number): number {
361 | return this.sub(a, b)
362 | },
363 | sub(a: number, b: number): number {
364 | throw new Error('This method should be rewritten by vm')
365 | },
366 | },
367 | })
368 | })
369 |
370 | it('call function of virual machine from raw js with proper this', (): void => {
371 | tm(`
372 | wrapper.sub = function (a, b) {
373 | this && 1
374 | console.log(this, a - b + this.a, 'this is the result')
375 | return a - b + this.a + this.c
376 | }
377 | expect(wrapper.sub(39, 20)).equal(120)
378 | expect(wrapper.getResult(100, 50)).equal(151)
379 | `, {
380 | wrapper: {
381 | a: 100,
382 | c: 1,
383 | getResult(a: number, b: number): number {
384 | return this.sub(a, b)
385 | },
386 | sub(a: number, b: number): number {
387 | throw new Error('This method should be rewritten by vm')
388 | },
389 | },
390 | })
391 | })
392 |
393 | it(`arguments`, (): void => {
394 | tm(`
395 | function kk(a, b, c) {
396 | expect(arguments.length).equal(2)
397 | expect(arguments[0]).equal(1)
398 | expect(arguments[1]).equal(2)
399 | expect(arguments[2]).equal(void 555)
400 | }
401 | kk(1, 2)
402 | `)
403 | })
404 |
405 | it('call function of vm from vm with proper this', (): void => {
406 | tm(`
407 | const wrapper = {
408 | a: 100,
409 | c: 1,
410 | getResult(a, b) {
411 | return this.sub(a, b)
412 | },
413 | sub(a, b) {
414 | throw new Error('This method should be rewritten by vm')
415 | },
416 | }
417 | wrapper.sub = (a, b) => a - b + this.a + this.c
418 | expect(wrapper.sub(39, 20)).equal(120)
419 | expect(wrapper.getResult(100, 50)).equal(151)
420 | `)
421 | })
422 |
423 | it(`call function of vm nested with function of vm`, (): void => {
424 | const ctx = { wrapper: {
425 | say (): number {
426 | throw new Error('should be rewritten.')
427 | },
428 | run (): number {
429 | return this.say()
430 | },
431 | add (a: number, b: number): number {
432 | return a + b
433 | },
434 | } }
435 | tm(`
436 | wrapper.say = function() {
437 | const a = this.say2()
438 | return a + 1
439 | }
440 | wrapper.say2 = function() {
441 | return 2
442 | }
443 | const add = wrapper.add
444 | expect(wrapper.run()).equal(3)
445 | expect(add(3, 5)).equal(8)
446 | `, ctx)
447 | expect(ctx.wrapper.run()).equal(3)
448 | })
449 |
450 | it(`define function`, (): void => {
451 | tm(`
452 | const a = baseProperty("hello")
453 | console.log(a({ hello: "good" }))
454 | function baseProperty(key) {
455 | return function(object) {
456 | return object == null ? undefined : object[key];
457 | };
458 | }
459 | `)
460 | })
461 |
462 | it(`new vm function as class`, (): void => {
463 | tm(`
464 | function People(a, b) {
465 | this.a = a
466 | this.b = b
467 | }
468 | People.prototype.add = function() {
469 | return this.a + this.b
470 | }
471 | const people = new People(1, 2)
472 | expect(people.a).equal(1)
473 | expect(people.b).equal(2)
474 | expect(people.add()).equal(3)
475 | expect(people instanceof People).equal(true)
476 | `)
477 | })
478 |
479 | it(`new function with contructor running method`, (): void => {
480 | tm(`
481 | function Locale(config) {
482 | if (config != null) {
483 | this.set(config);
484 | }
485 | }
486 | const proto = Locale.prototype
487 | proto.set = function (config) {
488 | expect(this instanceof Locale).equal(true)
489 | }
490 | const l = new Locale()
491 | `)
492 |
493 | tm(`
494 | function Hash(entries) {
495 | this.name = 'jerry'
496 | new Date()
497 | }
498 | expect(new Hash().name).to.equal('jerry')
499 | `)
500 | })
501 |
502 | it(`operatant type transfer`, (): void => {
503 | expect(
504 | getOperatantByBuffer(new Uint8Array(createOperantBuffer(IOperatantType.NUMBER, -3368))),
505 | ).deep.equal(
506 | [IOperatantType.NUMBER, -3368, 3],
507 | )
508 |
509 | expect(
510 | getOperatantByBuffer(new Uint8Array(createOperantBuffer(IOperatantType.NUMBER, -3.14159))),
511 | ).deep.equal(
512 | [IOperatantType.NUMBER, -3.14159, 8],
513 | )
514 |
515 | expect(
516 | getOperatantByBuffer(new Uint8Array(createOperantBuffer(IOperatantType.REGISTER, 100))),
517 | ).deep.equal(
518 | [IOperatantType.REGISTER, 100, 1],
519 | )
520 | })
521 |
522 | it('immediatly run function', (): void => {
523 | tm(`
524 | (function () {
525 | const a = [1 ,2 ,3]
526 | function sayHi() {
527 | console.log('hi')
528 | }
529 | sayHi()
530 | })()
531 | `)
532 | })
533 |
534 | it('return new function', (): void => {
535 | tm(`
536 | const newFunc = () => {
537 | return (a, b) => {
538 | return a + b
539 | }
540 | }
541 | const a = newFunc()
542 | const b = newFunc()
543 | expect(a).not.equal(b)
544 | `)
545 | })
546 | })
547 |
548 | describe('loop', (): void => {
549 | it(`for loop`, (): void => {
550 | const spy = chai.spy((): void => {})
551 | tm(`
552 | for (let i = 0; i < 100; i++) {
553 | spy(i)
554 | }
555 | expect(spy).to.have.been.called.exactly(100);
556 | expect(spy).on.nth(38).be.called.with(37)
557 | `, { spy })
558 | })
559 |
560 | it(`nested for loop`, (): void => {
561 | const spy = chai.spy((): void => {})
562 | tm(`
563 | for (let i = 0; i < 10; i++) {
564 | for (let j = 0; j < 10; j++) {
565 | for (let k = 0; k < 10; k++) {
566 | spy(i, j, k)
567 | }
568 | }
569 | }
570 | expect(spy).to.have.been.called.exactly(1000);
571 | expect(spy).on.nth(37).be.called.with(0, 3, 6)
572 | `, { spy })
573 | })
574 |
575 | it('while loop', (): void => {
576 | const spy = chai.spy((): void => {})
577 | tm(`
578 | let i = 0
579 | while (i < 10) {
580 | spy()
581 | i++
582 | }
583 | expect(spy).to.have.been.called.exactly(10)
584 | `, { spy })
585 | })
586 |
587 | it('nested while loop', (): void => {
588 | const spy = chai.spy((): void => {})
589 | tm(`
590 | let i = 0
591 | while (i < 10) {
592 | let j = 0
593 | while (j < 10) {
594 | spy()
595 | j++
596 | }
597 | i++
598 | }
599 | expect(spy).to.have.been.called.exactly(100)
600 | `, { spy })
601 | })
602 |
603 | it('break statement', (): void => {
604 | const spy = chai.spy((): void => {})
605 | tm(`
606 | let i = 0
607 | while (i < 10) {
608 | let j = 0
609 | while (j < 10) {
610 | spy()
611 | if (j === 4) {
612 | break
613 | }
614 | j++
615 | }
616 | if (i === 4) {
617 | break
618 | }
619 | i++
620 | }
621 | expect(spy).to.have.been.called.exactly(25)
622 | `, { spy })
623 | })
624 |
625 | it('continue', (): void => {
626 | const spy = chai.spy((): void => {})
627 | tm(`
628 | let i = 0
629 | while (i < 10) {
630 | i++
631 | if (i % 3 === 0) {
632 | continue
633 | }
634 | for (let j = 0; j < 10; j++) {
635 | spy()
636 | }
637 | }
638 | expect(spy).to.have.been.called.exactly(70)
639 | `, { spy })
640 | })
641 |
642 | it('do while', (): void => {
643 | const spy = chai.spy((): void => {})
644 | tm(
645 | `
646 | let i = 0
647 | do {
648 | i++
649 | spy(i)
650 | } while (i < 10)
651 | expect(spy).to.have.been.called.exactly(10)
652 | expect(spy).on.nth(1).be.called.with(1)
653 | `, { spy })
654 | })
655 |
656 | })
657 |
658 | describe('null and undefined', (): void => {
659 | it('null', (): void => {
660 | tm(`
661 | const a = null
662 | expect(a).equal(null)
663 | `)
664 | })
665 |
666 | it('array of null', (): void => {
667 | tm(`
668 | const a = [null, 1]
669 | expect(a[0]).equal(null)
670 | expect(a[1]).equal(1)
671 | `)
672 | })
673 |
674 | it('undefined', (): void => {
675 | tm(`
676 | const a = [undefined, 1]
677 | expect(a[0]).equal(undefined)
678 | expect(a[1]).equal(1)
679 | `)
680 | })
681 |
682 | it('passing null as fucntion argument', (): void => {
683 | tm(`
684 | const a = [null, null]
685 | console.log(null, null, a)
686 | `)
687 | })
688 | })
689 |
690 | describe('regular expression', (): void => {
691 | it('normal regular expression', (): void => {
692 | tm(`
693 | const a = /\\d+/
694 | expect(a.test('1234331')).equal(true)
695 | `)
696 | })
697 |
698 | it('regular expression with flags', (): void => {
699 | tm(`
700 | const a = /HelloWorld/i
701 | expect(a.test('helloworld')).equal(true)
702 |
703 | const b = /HelloWorld/
704 | expect(b.test('helloworld')).equal(false)
705 | `)
706 | })
707 | })
708 |
709 | describe("continue and break", (): void => {
710 | it('label statment', (): void => {
711 | const spy = chai.spy((): void => {})
712 | tm(`
713 | a: {
714 | const n = 1
715 | spy()
716 | b: {
717 | if (n > 1) {
718 | break b
719 | } else {
720 | spy()
721 | break a
722 | }
723 | }
724 | spy()
725 | }
726 | expect(spy).to.have.been.called.exactly(2)
727 |
728 | d: {
729 | spy()
730 | break d
731 | spy()
732 | }
733 |
734 | expect(spy).to.have.been.called.exactly(3)
735 | `, { spy })
736 | })
737 |
738 | it('for continue', (): void => {
739 | const spy = makeSpy()
740 | tm(`
741 | for (let i = 0; i < 10; i++) {
742 | if (i % 3 === 0) { continue }
743 | spy()
744 | }
745 | expect(spy).to.have.been.called.exactly(6)
746 | `, { spy })
747 | })
748 |
749 | it(`while continue`, (): void => {
750 | const spy = makeSpy()
751 | tm(`
752 | let i = 0
753 | while (i < 10) {
754 | i++
755 | if (i % 3 === 0) { continue }
756 | spy()
757 | }
758 | expect(spy).to.have.been.called.exactly(7)
759 | `, { spy })
760 | })
761 |
762 | it(`continue with label`, (): void => {
763 | const spy = makeSpy()
764 | tm(`
765 | a:
766 | for (let i = 0; i < 10; i++) {
767 | b:
768 | for (let j = 0; j < 10; j++) {
769 | spy()
770 | if (j % 3 === 2) {
771 | continue a
772 | }
773 | }
774 | }
775 | expect(spy).to.have.been.called.exactly(30)
776 | `, { spy })
777 | })
778 |
779 | it(`while continue with label`, (): void => {
780 | const spy = makeSpy()
781 | tm(`
782 | let i = 0
783 | b:
784 | while (i < 10) {
785 | i++
786 | let j = 0
787 | c:
788 | while (j < 10) {
789 | spy()
790 | j++
791 | if (j % 3 == 0) {
792 | continue b
793 | }
794 | }
795 | }
796 | expect(spy).to.have.been.called.exactly(30)
797 | `, { spy })
798 | })
799 |
800 | it(`for in statement`, (): void => {
801 | tm(`
802 | const a = { name: 'jerry', age: 12, title: 'student' }
803 | const list = []
804 | for (var i in a) {
805 | list.push(i)
806 | }
807 | expect(list).to.be.deep.equal(['name', 'age', 'title'])
808 |
809 | const b = { name2: 'jerry', age2: 12, title2: 'student' }
810 | const list2 = []
811 | for (i in b) {
812 | if (i === 'title2') { break }
813 | list2.push(i)
814 | }
815 | expect(list2).to.be.deep.equal(['name2', 'age2'])
816 |
817 | const list3 = []
818 | for (i in b) {
819 | if (i === 'name2') { continue }
820 | list3.push(i)
821 | }
822 | expect(list3).to.be.deep.equal(['age2', 'title2'])
823 | `)
824 | })
825 |
826 | })
827 |
828 | describe('switch case and break', (): void => {
829 | const spy = makeSpy()
830 | it('switch case', (): void => {
831 | tm(`
832 | let i = 2
833 | switch(i) {
834 | case 0:
835 | console.log('ok')
836 | spy(1)
837 | break
838 | case 2:
839 | console.log(2)
840 | spy('ok')
841 | case 3:
842 | console.log(3)
843 | break
844 | default:
845 | spy('default')
846 | console.log("NOTHING")
847 | }
848 | expect(spy).on.nth(1).be.called.with('ok')
849 | `, { spy })
850 | })
851 |
852 | it('falling switch case', (): void => {
853 | tm(`
854 | const a = (i) => {
855 | switch(i) {
856 | case 1:
857 | case 2:
858 | case 3:
859 | return 'a'
860 | case 4:
861 | return 'b'
862 | }
863 | }
864 | expect(a(1)).equal('a')
865 | expect(a(2)).equal('a')
866 | expect(a(3)).equal('a')
867 | expect(a(4)).equal('b')
868 | `)
869 | })
870 | })
871 |
872 | describe("misc", (): void => {
873 | it('return sequence', (): void => {
874 | tm(`
875 | const a = () => {
876 | return (
877 | a ? "A" : "B",
878 | "C"
879 | );
880 | }
881 | expect(a()).equal("C")
882 | `)
883 | })
884 |
885 | it(`return logical`, (): void => {
886 | tm(`
887 | function pt(n, t, r) {
888 | return (t && r, n)
889 | }
890 | `)
891 | })
892 |
893 | it('access properties', (): void => {
894 | tm(`
895 | expect(typeof Function.prototype.toString).equal('function');
896 | `, { Function })
897 | })
898 |
899 | it(`isObject`, (): void => {
900 | tm(`
901 | expect(typeof isObject).equal('function')
902 | function isObject(value) {
903 | var type = typeof value;
904 | return value != null && (type == 'object' || type == 'function');
905 | }
906 | `)
907 | })
908 |
909 | it(`should passing before value`, (): void => {
910 | tm(`
911 | let a = 0
912 | expect(a += 1).equal(1)
913 | expect(a).equal(1)
914 | `)
915 | })
916 | })
917 |
918 | describe("closure", (): void => {
919 | it('call function of closure variable', (): void => {
920 | tm(`
921 | const a = (add) => {
922 | return function() {
923 | return this.b + add(this.c)
924 | }
925 | }
926 |
927 | const ret = a((n) => ++n)
928 | const num = ret.call({ b: 1, c: 2 })
929 | expect(num).equal(4)
930 | `)
931 | })
932 |
933 | it(`calling function in closure`, (): void => {
934 | tm(`
935 | const a = () => {
936 | const b = (n) => {
937 | return isObject(n) ? 'OK' : 'NO OK'
938 | }
939 | expect(b({})).equal('OK')
940 | function isObject(value) {
941 | return true
942 | }
943 | }
944 | a()
945 | `)
946 | })
947 |
948 | it(`same name argument`, (): void => {
949 | tm(`
950 | (() => {
951 | // var name = 'jerry'
952 | const a = (name) => name
953 | expect(a('lucy')).equal('lucy')
954 | })()
955 | `)
956 | })
957 |
958 | it(`?`, (): void => {
959 | tm(`
960 | (() => {
961 | const hasOwnProperty = () => {}
962 | const getRawTag = () => {
963 | expect(typeof hasOwnProperty).equal('function')
964 | hasOwnProperty.call({})
965 | }
966 | getRawTag()
967 | })()
968 | `)
969 | })
970 |
971 | it(`shallowed variable`, (): void => {
972 | tm(`
973 | (() => {
974 | var n = 1
975 | var i = 101
976 | const a = () => {
977 | console.log(i)
978 | var n = 2
979 | return b = (i) => {
980 | expect(i).equal(102)
981 | expect(n).equal(2)
982 | }
983 | }
984 | const c = () => {
985 | expect(n).equal(1)
986 | }
987 | a()(102)
988 | c()
989 | })()
990 | `)
991 | })
992 |
993 | it(`multiple call returning fucntions`, (): void => {
994 | tm(`
995 | const fn = () => {
996 | let n = 0
997 | return () => {
998 | return n++
999 | }
1000 | }
1001 | const a = fn()
1002 | const b = fn()
1003 | const c = fn()
1004 | expect(a()).equal(0)
1005 | expect(b()).equal(0)
1006 | expect(c()).equal(0)
1007 |
1008 | expect(a()).equal(1)
1009 | expect(b()).equal(1)
1010 | expect(c()).equal(1)
1011 |
1012 | expect(b()).equal(2)
1013 | expect(c()).equal(2)
1014 | `)
1015 | })
1016 |
1017 | it('closure variable should made right', (): void => {
1018 | tm(`
1019 | function a(t) {
1020 | function t(t) {
1021 | console.log("OJBK")
1022 | return t
1023 | }
1024 | return t
1025 | }
1026 | expect(typeof a(100)).equal('function')
1027 | `)
1028 | })
1029 |
1030 | it('closure variable should made right2', (): void => {
1031 | tm(`
1032 | function aa(t) {
1033 | const kk = () => t
1034 | function t() {}
1035 | return t
1036 | }
1037 | expect(typeof aa(null)).equal('function')
1038 | `)
1039 | })
1040 |
1041 | it('string indexOf with empty string', (): void => {
1042 | tm(`
1043 | const m = ''
1044 | const W = '\`'
1045 | if (-1 !== m.indexOf(W)) {
1046 | console.log("ok")
1047 | } else {
1048 | console.log("ok")
1049 | }
1050 | `)
1051 | })
1052 |
1053 | it(`assign to membership`, (): void => {
1054 | tm(`
1055 | const args = [1, 2, 3, 4]
1056 | const a = args.length
1057 |
1058 | let s
1059 | let o
1060 | for (o = new Array(a - 1), s = 0; s < o.length; ) {
1061 | o[s++] = args[s];
1062 | }
1063 | expect(o).deep.equal([2, 3, 4])
1064 | `)
1065 |
1066 | tm(`
1067 | const args = [1, 2, 3, 4]
1068 | const a = args.length
1069 |
1070 | let s
1071 | let o
1072 | for (o = new Array(a - 1), s = 0; s < o.length; ) {
1073 | o[s] = args[++s];
1074 | }
1075 | expect(o).deep.equal([2, 3, 4])
1076 | `)
1077 | })
1078 |
1079 | it(`function expression with name`, (): void => {
1080 | tm(`
1081 | (function (e) {
1082 | (function e(n) {
1083 | if (n === 1) {
1084 | expect(typeof e).equal('function')
1085 | } else {
1086 | expect(typeof e).equal('function')
1087 | e(1)
1088 | }
1089 | })()
1090 | })(1000)
1091 | `)
1092 | })
1093 |
1094 | it(`function expression name can be accessed in fuction body and not outer function body`, (): void => {
1095 | tm(`
1096 | (function(e) {
1097 | console.log('RET -> ', e);
1098 | expect(e).equal(1111);
1099 | (function e() {
1100 | expect(typeof e).equal('function')
1101 | })()
1102 | expect(e).equal(1111)
1103 | })(1111)
1104 | `)
1105 | })
1106 |
1107 | it(`function varialbe name should not reset parameter with the same name`, (): void => {
1108 | tm(`
1109 | ;(function(e) {
1110 | expect(e).equal(1)
1111 | ;(function e(e) {
1112 | var e
1113 | expect(e).equal(333)
1114 | e = 1
1115 | })(333)
1116 | expect(e).equal(1)
1117 | })(1)
1118 | `)
1119 | })
1120 | })
1121 |
1122 | describe('error handling', (): void => {
1123 | it('normal try', (): void => {
1124 | tm(`
1125 | let e = 1
1126 | try {
1127 | k.a()
1128 | throw new Error('ojbk')
1129 | } catch(e) {
1130 | expect(typeof e).equal('object')
1131 | }
1132 | expect(e).equal(1)
1133 | `)
1134 | })
1135 | })
1136 | // tslint:disable-next-line: max-file-line-count
1137 |
--------------------------------------------------------------------------------
/docs/code optimizer.uxf:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10
4 |
5 | UMLState
6 |
7 | 1630
8 | 0
9 | 100
10 | 40
11 |
12 | loop code
13 |
14 |
15 |
16 | UMLSpecialState
17 |
18 | 1590
19 | 10
20 | 20
21 | 20
22 |
23 | type=initial
24 |
25 |
26 |
27 | UMLState
28 |
29 | 2710
30 | 330
31 | 180
32 | 60
33 |
34 | valign=center
35 | halign=left
36 | [ 'MOV', R0, 1 ]
37 |
38 |
39 |
40 | Relation
41 |
42 | 1590
43 | 20
44 | 30
45 | 100
46 |
47 | lt=<-
48 | 10.0;80.0;10.0;10.0
49 |
50 |
51 | UMLSpecialState
52 |
53 | 1580
54 | 100
55 | 40
56 | 40
57 |
58 | type=decision
59 |
60 |
61 |
62 | UMLSpecialState
63 |
64 | 2050
65 | 200
66 | 20
67 | 20
68 |
69 | type=initial
70 |
71 |
72 |
73 | Relation
74 |
75 | 1590
76 | 130
77 | 70
78 | 90
79 |
80 | lt=<-
81 | if MOV
82 | 10.0;70.0;10.0;10.0
83 |
84 |
85 | Relation
86 |
87 | 1610
88 | 110
89 | 500
90 | 110
91 |
92 | lt=<-
93 | else
94 | 450.0;90.0;450.0;10.0;10.0;10.0
95 |
96 |
97 | UMLSpecialState
98 |
99 | 1590
100 | 200
101 | 20
102 | 20
103 |
104 | type=initial
105 |
106 |
107 |
108 | UMLState
109 |
110 | 2710
111 | 420
112 | 350
113 | 150
114 |
115 | valign=top
116 | halign=left
117 | {
118 | '%r1': {
119 | codeIndex: nubmer,
120 | cmd: 'MOV',
121 | value: 'xxx',
122 | type: 'string',
123 | usages: [{ codeIndex: 0, position: 0 }],
124 | }
125 | }
126 |
127 |
128 |
129 | UMLSpecialState
130 |
131 | 1580
132 | 430
133 | 40
134 | 40
135 |
136 | type=decision
137 |
138 |
139 |
140 |
141 | Relation
142 |
143 | 1590
144 | 210
145 | 30
146 | 90
147 |
148 | lt=<-
149 | 10.0;70.0;10.0;10.0
150 |
151 |
152 | Relation
153 |
154 | 1590
155 | 460
156 | 30
157 | 160
158 |
159 | lt=<-
160 | 10.0;140.0;10.0;10.0
161 |
162 |
163 | UMLClass
164 |
165 | 2580
166 | 340
167 | 110
168 | 40
169 |
170 | lt=.
171 | code
172 |
173 |
174 |
175 | UMLClass
176 |
177 | 2580
178 | 470
179 | 110
180 | 40
181 |
182 | lt=.
183 | candidates
184 |
185 |
186 |
187 | UMLState
188 |
189 | 2090
190 | 190
191 | 100
192 | 40
193 |
194 | loop operant
195 |
196 |
197 |
198 | Relation
199 |
200 | 2050
201 | 210
202 | 30
203 | 110
204 |
205 | lt=<-
206 | 10.0;90.0;10.0;10.0
207 |
208 |
209 | UMLSpecialState
210 |
211 | 2040
212 | 300
213 | 40
214 | 40
215 |
216 | type=decision
217 |
218 |
219 |
220 | Relation
221 |
222 | 1140
223 | 440
224 | 470
225 | 490
226 |
227 | lt=<-
228 | 450.0;470.0;10.0;470.0;10.0;10.0;440.0;10.0
229 |
230 |
231 | UMLState
232 |
233 | 1570
234 | 930
235 | 170
236 | 40
237 |
238 | add to candidates
239 |
240 |
241 |
242 | Relation
243 |
244 | 2050
245 | 330
246 | 30
247 | 90
248 |
249 | lt=<-
250 | 10.0;70.0;10.0;10.0
251 |
252 |
253 | Relation
254 |
255 | 2070
256 | 310
257 | 460
258 | 690
259 |
260 | lt=<-
261 | 10.0;670.0;440.0;670.0;440.0;10.0;10.0;10.0
262 |
263 |
264 | UMLSpecialState
265 |
266 | 2040
267 | 560
268 | 40
269 | 40
270 |
271 | type=decision
272 |
273 |
274 |
275 | Relation
276 |
277 | 2070
278 | 570
279 | 210
280 | 160
281 |
282 | lt=<-
283 | 190.0;140.0;190.0;10.0;10.0;10.0
284 |
285 |
286 | UMLClass
287 |
288 | 2100
289 | 300
290 | 110
291 | 40
292 |
293 | lt=.
294 | if not reg
295 |
296 |
297 |
298 | UMLClass
299 |
300 | 2210
301 | 640
302 | 110
303 | 40
304 |
305 | lt=.
306 | if both || set
307 |
308 |
309 |
310 | UMLClass
311 |
312 | 2010
313 | 640
314 | 110
315 | 40
316 |
317 | lt=.
318 | if get
319 |
320 |
321 |
322 | UMLClass
323 |
324 | 1210
325 | 420
326 | 210
327 | 40
328 |
329 | lt=.
330 | reg not in candidates
331 |
332 |
333 |
334 | Relation
335 |
336 | 2050
337 | 590
338 | 30
339 | 250
340 |
341 | lt=<-
342 | 10.0;230.0;10.0;10.0
343 |
344 |
345 | UMLClass
346 |
347 | 1970
348 | 450
349 | 180
350 | 40
351 |
352 | lt=.
353 | if candidate exists
354 |
355 |
356 |
357 | UMLSpecialState
358 |
359 | 2050
360 | 820
361 | 20
362 | 20
363 |
364 | type=initial
365 | add usage to candidates
366 |
367 |
368 |
369 | UMLState
370 |
371 | 1830
372 | 810
373 | 200
374 | 40
375 |
376 | add usage to candidate
377 |
378 |
379 |
380 | Relation
381 |
382 | 380
383 | 600
384 | 1080
385 | 170
386 |
387 | lt=.>
388 | 1060.0;10.0;10.0;10.0;10.0;150.0
389 |
390 |
391 | UMLSpecialState
392 |
393 | 1590
394 | 600
395 | 20
396 | 20
397 |
398 | type=final
399 |
400 |
401 |
402 | UMLSpecialState
403 |
404 | 370
405 | 750
406 | 40
407 | 40
408 |
409 | type=decision
410 |
411 |
412 |
413 | Relation
414 |
415 | 390
416 | 760
417 | 300
418 | 410
419 |
420 | lt=<-
421 | 10.0;390.0;280.0;390.0;280.0;10.0;20.0;10.0
422 |
423 |
424 | UMLClass
425 |
426 | 430
427 | 750
428 | 200
429 | 40
430 |
431 | lt=.
432 | if value type === 'number'
433 |
434 |
435 |
436 | UMLSpecialState
437 |
438 | 1590
439 | 900
440 | 20
441 | 20
442 |
443 | type=initial
444 | add usage to candidates
445 |
446 |
447 |
448 | UMLSpecialState
449 |
450 | 1580
451 | 1380
452 | 40
453 | 40
454 |
455 | type=decision
456 |
457 |
458 |
459 | Relation
460 |
461 | 1590
462 | 910
463 | 30
464 | 490
465 |
466 | lt=<-
467 | 10.0;470.0;10.0;10.0
468 |
469 |
470 | UMLClass
471 |
472 | 1500
473 | 1480
474 | 200
475 | 40
476 |
477 | lt=.
478 | loop code end?
479 |
480 |
481 |
482 | UMLSpecialState
483 |
484 | 2040
485 | 960
486 | 40
487 | 40
488 |
489 | type=decision
490 |
491 |
492 |
493 | Relation
494 |
495 | 2050
496 | 830
497 | 30
498 | 150
499 |
500 | lt=<-
501 | 10.0;130.0;10.0;10.0
502 |
503 |
504 | UMLClass
505 |
506 | 1970
507 | 1090
508 | 200
509 | 40
510 |
511 | lt=.
512 | loop operant end?
513 |
514 |
515 |
516 | Relation
517 |
518 | 1630
519 | 990
520 | 450
521 | 270
522 |
523 | lt=<-
524 | 10.0;250.0;430.0;250.0;430.0;10.0
525 |
526 |
527 | Relation
528 |
529 | 1770
530 | 200
531 | 300
532 | 800
533 |
534 | lt=<-
535 | 280.0;10.0;10.0;10.0;10.0;780.0;270.0;780.0
536 |
537 |
538 | Relation
539 |
540 | 1590
541 | 1410
542 | 30
543 | 200
544 |
545 | lt=<-
546 |
547 | 10.0;180.0;10.0;10.0
548 |
549 |
550 | Relation
551 |
552 | 1010
553 | 10
554 | 590
555 | 1410
556 |
557 | lt=<-
558 | 570.0;10.0;10.0;10.0;10.0;1390.0;570.0;1390.0
559 |
560 |
561 | UMLSpecialState
562 |
563 | 380
564 | 860
565 | 20
566 | 20
567 |
568 | type=initial
569 |
570 |
571 |
572 | UMLState
573 |
574 | 0
575 | 850
576 | 350
577 | 40
578 |
579 | { codeIndex, value, type, useages } = candidate
580 |
581 |
582 |
583 | Relation
584 |
585 | 380
586 | 870
587 | 30
588 | 110
589 |
590 | lt=<-
591 | 10.0;90.0;10.0;10.0
592 |
593 |
594 | UMLSpecialState
595 |
596 | 380
597 | 960
598 | 20
599 | 20
600 |
601 | type=initial
602 |
603 |
604 |
605 | UMLState
606 |
607 | 50
608 | 950
609 | 280
610 | 40
611 |
612 | code[codeIndex] = null
613 |
614 |
615 |
616 | Relation
617 |
618 | 380
619 | 970
620 | 30
621 | 100
622 |
623 | lt=<-
624 | 10.0;80.0;10.0;10.0
625 |
626 |
627 | UMLSpecialState
628 |
629 | 380
630 | 1050
631 | 20
632 | 20
633 |
634 | type=initial
635 |
636 |
637 |
638 | UMLState
639 |
640 | 50
641 | 1040
642 | 280
643 | 40
644 |
645 | every useage = value
646 |
647 |
648 |
649 | UMLSpecialState
650 |
651 | 1590
652 | 1590
653 | 20
654 | 20
655 |
656 | type=initial
657 |
658 |
659 |
660 | UMLState
661 |
662 | 1630
663 | 1580
664 | 210
665 | 50
666 |
667 | loop every candidates
668 |
669 |
670 |
671 | Relation
672 |
673 | 1590
674 | 1600
675 | 30
676 | 260
677 |
678 | lt=<-
679 |
680 | 10.0;240.0;10.0;10.0
681 |
682 |
683 | Relation
684 |
685 | 1590
686 | 610
687 | 30
688 | 310
689 |
690 | lt=<-
691 | 10.0;290.0;10.0;10.0
692 |
693 |
694 | UMLState
695 |
696 | 1440
697 | 590
698 | 140
699 | 40
700 |
701 | bg=yellow
702 | process reg
703 |
704 |
705 |
706 | UMLSpecialState
707 |
708 | 2250
709 | 710
710 | 20
711 | 20
712 |
713 | type=final
714 |
715 |
716 |
717 | UMLState
718 |
719 | 2290
720 | 700
721 | 140
722 | 40
723 |
724 | bg=yellow
725 | process reg
726 |
727 |
728 |
729 | Relation
730 |
731 | 2250
732 | 720
733 | 30
734 | 130
735 |
736 | lt=<-
737 | 10.0;110.0;10.0;10.0
738 |
739 |
740 | UMLSpecialState
741 |
742 | 2250
743 | 830
744 | 20
745 | 20
746 |
747 | type=initial
748 |
749 |
750 |
751 | UMLState
752 |
753 | 2250
754 | 810
755 | 180
756 | 50
757 |
758 | delete candidate
759 |
760 |
761 |
762 | Relation
763 |
764 | 2150
765 | 840
766 | 130
767 | 160
768 |
769 |
770 | 10.0;140.0;110.0;140.0;110.0;10.0
771 |
772 |
773 | Relation
774 |
775 | 380
776 | 780
777 | 30
778 | 100
779 |
780 | lt=<-
781 | 10.0;80.0;10.0;10.0
782 |
783 |
784 | Relation
785 |
786 | 380
787 | 1060
788 | 30
789 | 100
790 |
791 | lt=<-
792 | 10.0;80.0;10.0;10.0
793 |
794 |
795 | UMLSpecialState
796 |
797 | 380
798 | 1140
799 | 20
800 | 20
801 |
802 | type=flow_final
803 |
804 |
805 |
806 | Relation
807 |
808 | 1590
809 | 1850
810 | 30
811 | 240
812 |
813 | lt=<-
814 | 10.0;220.0;10.0;10.0
815 |
816 |
817 | UMLSpecialState
818 |
819 | 1580
820 | 2070
821 | 40
822 | 40
823 |
824 | type=decision
825 |
826 |
827 |
828 |
829 | Relation
830 |
831 | 1400
832 | 1590
833 | 210
834 | 520
835 |
836 | lt=<-
837 | 190.0;10.0;10.0;10.0;10.0;500.0;180.0;500.0
838 |
839 |
840 | Relation
841 |
842 | 1590
843 | 2100
844 | 30
845 | 140
846 |
847 | lt=<-
848 | 10.0;120.0;10.0;10.0
849 |
850 |
851 | UMLClass
852 |
853 | 1520
854 | 2130
855 | 170
856 | 30
857 |
858 | lt=.
859 | loop candidates end?
860 |
861 |
862 |
863 | UMLSpecialState
864 |
865 | 1590
866 | 2220
867 | 20
868 | 20
869 |
870 | type=initial
871 |
872 |
873 |
874 |
875 | UMLState
876 |
877 | 1630
878 | 2200
879 | 210
880 | 50
881 |
882 | filter code !== null
883 |
884 |
885 |
886 | UMLSpecialState
887 |
888 | 1590
889 | 2330
890 | 20
891 | 20
892 |
893 | type=initial
894 |
895 |
896 |
897 | Relation
898 |
899 | 1590
900 | 2230
901 | 30
902 | 120
903 |
904 | lt=<-
905 | 10.0;100.0;10.0;10.0
906 |
907 |
908 | UMLState
909 |
910 | 1630
911 | 2310
912 | 210
913 | 50
914 |
915 | join all code
916 |
917 |
918 |
919 | Relation
920 |
921 | 1590
922 | 2340
923 | 30
924 | 110
925 |
926 | lt=<-
927 | 10.0;90.0;10.0;10.0
928 |
929 |
930 | UMLSpecialState
931 |
932 | 1590
933 | 2430
934 | 20
935 | 20
936 |
937 | type=flow_final
938 |
939 |
940 |
941 | UMLSyncBarHorizontal
942 |
943 | 1560
944 | 1230
945 | 80
946 | 20
947 |
948 | lw=5
949 |
950 |
951 |
952 |
953 | UMLSpecialState
954 |
955 | 1580
956 | 280
957 | 40
958 | 40
959 |
960 | type=decision
961 |
962 |
963 |
964 | Relation
965 |
966 | 1590
967 | 310
968 | 30
969 | 140
970 |
971 | lt=<-
972 | 10.0;120.0;10.0;10.0
973 |
974 |
975 | Relation
976 |
977 | 1410
978 | 10
979 | 190
980 | 310
981 |
982 | lt=<-
983 | 10.0;10.0;10.0;290.0;170.0;290.0
984 |
985 |
986 | UMLSyncBarHorizontal
987 |
988 | 1380
989 | 10
990 | 80
991 | 20
992 |
993 | lw=5
994 |
995 |
996 |
997 |
998 | UMLClass
999 |
1000 | 1490
1001 | 350
1002 | 210
1003 | 40
1004 |
1005 | lt=.
1006 | if is reg
1007 |
1008 |
1009 |
1010 | UMLSpecialState
1011 |
1012 | 2040
1013 | 400
1014 | 40
1015 | 40
1016 |
1017 | type=decision
1018 |
1019 |
1020 |
1021 | Relation
1022 |
1023 | 2050
1024 | 430
1025 | 30
1026 | 150
1027 |
1028 | lt=<-
1029 | 10.0;130.0;10.0;10.0
1030 |
1031 |
1032 | Relation
1033 |
1034 | 2070
1035 | 410
1036 | 410
1037 | 590
1038 |
1039 |
1040 | 390.0;570.0;390.0;10.0;10.0;10.0
1041 |
1042 |
1043 | UMLClass
1044 |
1045 | 2260
1046 | 400
1047 | 110
1048 | 40
1049 |
1050 | lt=.
1051 | else
1052 |
1053 |
1054 |
1055 | UMLState
1056 |
1057 | 1640
1058 | 1830
1059 | 140
1060 | 40
1061 |
1062 | bg=yellow
1063 | process reg
1064 |
1065 |
1066 |
1067 | UMLSpecialState
1068 |
1069 | 1590
1070 | 1840
1071 | 20
1072 | 20
1073 |
1074 | type=final
1075 |
1076 |
1077 |
1078 |
--------------------------------------------------------------------------------
/src/vm/vm.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable: no-bitwise
2 | // tslint:disable: no-big-function
3 | // tslint:disable: no-dead-store
4 | import { parseStringsArray, getByProp, readUInt8, readInt16, readUInt32, readFloat64, readInt8, getOperatantByBuffer, getOperantName } from '../utils'
5 | import { Scope } from '../scope'
6 |
7 | export enum I {
8 | VAR, CLS,
9 |
10 | MOV, ADD, SUB, MUL, DIV, MOD,
11 | EXP, INC, DEC,
12 |
13 | LT, GT, EQ, LE, GE, NE, WEQ, WNE,
14 | LG_AND, LG_OR,
15 | AND, OR, XOR, SHL, SHR, ZSHR,
16 |
17 | JMP, JE, JNE, JG, JL, JIF, JF,
18 | JGE, JLE, PUSH, POP, CALL, PRINT,
19 | RET, PAUSE, EXIT,
20 |
21 | CALL_CTX, CALL_VAR, CALL_REG, MOV_CTX, MOV_PROP,
22 | SET_CTX, // SET_CTX "name" R1
23 | NEW_OBJ, NEW_ARR, NEW_REG, SET_KEY,
24 | FUNC, ALLOC,
25 |
26 | /* UnaryExpression */
27 | PLUS, // PLUS %r0 +
28 | MINUS, // MINUS %r0 -
29 | NOT, // NOT %r0 ~
30 | VOID, // VOID %r0 void
31 | DEL, // DEL %r0 %r1 delete
32 | NEG, // NEG %r0 !
33 | TYPE_OF,
34 |
35 | IN,
36 | INST_OF, // instanceof
37 | MOV_THIS, // moving this to resgister
38 |
39 | // try catch
40 | TRY, TRY_END, THROW, GET_ERR,
41 |
42 | // arguments
43 | MOV_ARGS,
44 |
45 | FORIN, FORIN_END, BREAK_FORIN, CONT_FORIN,
46 |
47 | BVAR, BLOCK, END_BLOCK, CLR_BLOCK,
48 | }
49 |
50 | const NO_RETURN_SYMBOL = Symbol()
51 |
52 | class VMRunTimeError extends Error {
53 | constructor(public error: any) {
54 | super(error)
55 | }
56 | }
57 |
58 | interface ICallingFunctionInfo {
59 | isInitClosure: boolean,
60 | closureScope: Scope,
61 | variables: Scope | null,
62 | returnValue?: any,
63 | args: any[],
64 | }
65 |
66 | export const enum IOperatantType {
67 | REGISTER = 0 << 4,
68 | CLOSURE_REGISTER = 1 << 4,
69 | GLOBAL = 2 << 4,
70 | NUMBER = 3 << 4,
71 | // tslint:disable-next-line: no-identical-expressions
72 | FUNCTION_INDEX = 4 << 4,
73 | STRING = 5 << 4,
74 | ARG_COUNT = 6 << 4,
75 | RETURN_VALUE = 7 << 4,
76 | ADDRESS = 8 << 4,
77 | BOOLEAN = 9 << 4,
78 | NULL = 10 << 4,
79 | UNDEFINED = 11 << 4,
80 | VAR_SYMBOL = 13 << 4,
81 | }
82 |
83 | export interface IOperant {
84 | type: IOperatantType,
85 | value: any,
86 | raw?: any,
87 | index?: any,
88 | }
89 |
90 | export type IClosureTable = {
91 | [x in number]: number
92 | }
93 |
94 | // tslint:disable-next-line: max-classes-per-file
95 | export class VirtualMachine {
96 | /** 指令索引 */
97 | public ip: number = 0
98 | /** 当前函数帧基索引 */
99 | public fp: number = 0
100 | /** 操作的栈顶 */
101 | public sp: number = -1
102 |
103 | /** 寄存器 */
104 | public RET: any // 函数返回寄存器
105 | public REG: any // 通用寄存器
106 |
107 | /** 函数操作栈 */
108 | public stack: any[] = []
109 |
110 | /** 闭包变量存储 */
111 | // public closureScope: Scope
112 |
113 | /** 闭包映射表 */
114 | public callingFunctionInfo: ICallingFunctionInfo = {
115 | isInitClosure: true,
116 | closureScope: new Scope(),
117 | variables: new Scope(),
118 | args: [],
119 | }
120 | public callingFunctionInfos: ICallingFunctionInfo[] = []
121 |
122 | /** this 链 */
123 | public currentThis: any
124 | public allThis: any[] = []
125 |
126 | public isRunning: boolean = false
127 | public mainFunction: CallableFunction
128 |
129 | public error: any
130 |
131 | constructor (
132 | public codes: Uint8Array,
133 | public functionsTable: FuncInfoMeta[],
134 | public entryIndx: number,
135 | public stringsTable: string[],
136 | public globalSize: number,
137 | public ctx: any,
138 | ) {
139 | const mainClosureScope = new Scope()
140 | mainClosureScope.isRestoreWhenChange = false
141 | this.mainFunction = this.parseToJsFunc(functionsTable[this.entryIndx], mainClosureScope)
142 | this.init()
143 | }
144 |
145 | public init(): void {
146 | const { globalSize, mainFunction } = this
147 | const { meta } = this.getMetaFromFunc(mainFunction)
148 | const [ip, numArgs, localSize] = meta
149 | this.stack.splice(0)
150 | const globalIndex = globalSize + 1
151 | this.fp = globalIndex // fp 指向 old fp 位置,兼容普通函数
152 | this.stack[this.fp] =-1
153 | this.sp = this.fp
154 | this.stack.length = this.sp + 1
155 | this.callingFunctionInfos = []
156 | this.allThis = []
157 | }
158 |
159 | public reset(): void {
160 | this.init()
161 | this.error = null
162 | }
163 |
164 | // tslint:disable-next-line: no-big-function
165 | public run(): void {
166 | this.callFunction(this.mainFunction, void 0, '', 0, false)
167 | this.isRunning = true
168 | while (this.isRunning) {
169 | this.fetchAndExecute()
170 | }
171 | }
172 |
173 | public setReg(dst: IOperant, src: { value: any }): void {
174 | const callingFunctionInfo = this.callingFunctionInfo
175 | if (dst.type === IOperatantType.VAR_SYMBOL) {
176 | this.checkVariableScopeAndNew()
177 | callingFunctionInfo.variables!.set(dst.index, src.value)
178 | } else if (dst.type === IOperatantType.CLOSURE_REGISTER) {
179 | this.checkClosureAndFork()
180 | this.callingFunctionInfo.closureScope.set(dst.index, src.value)
181 | } else if (dst.type === IOperatantType.REGISTER || dst.type === IOperatantType.RETURN_VALUE) {
182 | if (dst.type === IOperatantType.RETURN_VALUE) {
183 | this.callingFunctionInfo.returnValue = src.value
184 | }
185 | if (dst.raw <= -4) {
186 | this.callingFunctionInfo.args[-4 - dst.raw] = src.value
187 | } else {
188 | this.stack[dst.index] = src.value
189 | }
190 | } else {
191 | console.error(dst)
192 | throw new Error(`Cannot process register type ${dst.type}`)
193 | }
194 | }
195 |
196 | public newReg(o: IOperant): any {
197 | const callingFunctionInfo = this.callingFunctionInfo
198 | if (o.type === IOperatantType.VAR_SYMBOL) {
199 | this.checkVariableScopeAndNew()
200 | this.callingFunctionInfo.variables!.new(o.index)
201 | } else if (o.type === IOperatantType.CLOSURE_REGISTER) {
202 | this.checkClosureAndFork()
203 | this.callingFunctionInfo.closureScope.new(o.index)
204 | } else {
205 | console.error(o)
206 | throw new Error(`Cannot process register type ${o.type}`)
207 | }
208 | }
209 |
210 | public getReg(o: IOperant): any {
211 | if (o.type === IOperatantType.VAR_SYMBOL) {
212 | if (!this.callingFunctionInfo.variables) {
213 | throw new Error('variable is not declared.')
214 | }
215 | return this.callingFunctionInfo.variables.get(o.index)
216 | } else if (o.type === IOperatantType.CLOSURE_REGISTER) {
217 | return this.callingFunctionInfo.closureScope.get(o.index)
218 | } else if (o.type === IOperatantType.REGISTER || o.type === IOperatantType.RETURN_VALUE) {
219 | return this.stack[o.index]
220 | } else {
221 | throw new Error(`Cannot process register type ${o.type}`)
222 | }
223 | }
224 |
225 | // tslint:disable-next-line: no-big-function cognitive-complexity
226 | public fetchAndExecute(): [I, boolean] {
227 | if (!this.isRunning) {
228 | throw new VMRunTimeError('try to run again...')
229 | }
230 | let op = this.nextOperator()
231 | // 用来判断是否嵌套调用 vm 函数
232 | let isCallVMFunction = false
233 | // tslint:disable-next-line: max-switch-cases
234 | switch (op) {
235 | case I.VAR:
236 | case I.CLS: {
237 | const o = this.nextOperant()
238 | this.newReg(o)
239 | break
240 | }
241 |
242 | case I.PUSH: {
243 | const value = this.nextOperant().value
244 | this.push(value)
245 | break
246 | }
247 | case I.EXIT: {
248 | this.isRunning = false
249 | break
250 | }
251 | case I.RET: {
252 | this.returnCurrentFunction()
253 | break
254 | }
255 | case I.PRINT: {
256 | const val = this.nextOperant()
257 | console.log(val.value)
258 | break
259 | }
260 | case I.MOV: {
261 | const dst = this.nextOperant()
262 | const src = this.nextOperant()
263 | this.setReg(dst, src)
264 | break
265 | }
266 | case I.JMP: {
267 | const address = this.nextOperant()
268 | this.ip = address.value
269 | break
270 | }
271 | case I.JE: {
272 | this.jumpWithCondidtion((a: any, b: any): boolean => a === b)
273 | break
274 | }
275 | case I.JNE: {
276 | this.jumpWithCondidtion((a: any, b: any): boolean => a !== b)
277 | break
278 | }
279 | case I.JG: {
280 | this.jumpWithCondidtion((a: any, b: any): boolean => a > b)
281 | break
282 | }
283 | case I.JL: {
284 | this.jumpWithCondidtion((a: any, b: any): boolean => a < b)
285 | break
286 | }
287 | case I.JGE: {
288 | this.jumpWithCondidtion((a: any, b: any): boolean => a >= b)
289 | break
290 | }
291 | case I.JLE: {
292 | this.jumpWithCondidtion((a: any, b: any): boolean => a <= b)
293 | break
294 | }
295 | case I.JIF: {
296 | const cond = this.nextOperant()
297 | const address = this.nextOperant()
298 | if (cond.value) {
299 | this.ip = address.value
300 | }
301 | break
302 | }
303 | case I.JF: {
304 | const cond = this.nextOperant()
305 | const address = this.nextOperant()
306 | if (!cond.value) {
307 | this.ip = address.value
308 | }
309 | break
310 | }
311 | case I.CALL_CTX:
312 | case I.CALL_VAR: {
313 | let o
314 | if (op === I.CALL_CTX) {
315 | o = this.ctx
316 | } else {
317 | o = this.nextOperant().value
318 | }
319 | const funcName = this.nextOperant().value
320 | const numArgs = this.nextOperant().value
321 | const isNewExpression = this.nextOperant().value
322 | isCallVMFunction = this.callFunction(void 0, o, funcName, numArgs, isNewExpression)
323 | break
324 | }
325 | case I.CALL_REG: {
326 | const o = this.nextOperant()
327 | const f = o.value
328 | const numArgs = this.nextOperant().value
329 | const isNewExpression = this.nextOperant().value
330 | isCallVMFunction = this.callFunction(f, void 0, '', numArgs, isNewExpression)
331 | break
332 | }
333 | case I.MOV_CTX: {
334 | const dst = this.nextOperant()
335 | const propKey = this.nextOperant()
336 | const src = this.ctx[propKey.value] // getByProp(this.ctx, propKey.value)
337 | this.setReg(dst, { value: src })
338 | break
339 | }
340 | case I.SET_CTX: {
341 | const propKey = this.nextOperant()
342 | const val = this.nextOperant()
343 | this.ctx[propKey.value] = val.value
344 | break
345 | }
346 | case I.NEW_OBJ: {
347 | const dst = this.nextOperant()
348 | const o = {}
349 | this.setReg(dst, { value: o })
350 | break
351 | }
352 | case I.NEW_REG: {
353 | const dst = this.nextOperant()
354 | const pattern = this.nextOperant()
355 | const flags = this.nextOperant()
356 | try {
357 | this.setReg(dst, { value: new RegExp(pattern.value, flags.value) })
358 | } catch(e) {
359 | throw new VMRunTimeError(e)
360 | }
361 | break
362 | }
363 | case I.NEW_ARR: {
364 | const dst = this.nextOperant()
365 | const o: any[] = []
366 | this.setReg(dst, { value: o })
367 | break
368 | }
369 | case I.SET_KEY: {
370 | const o = this.nextOperant().value
371 | const key = this.nextOperant().value
372 | const value = this.nextOperant().value
373 | // console.log(o, key, value)
374 | o[key] = value
375 | break
376 | }
377 | /** 这是定义一个函数 */
378 | case I.FUNC: {
379 | const dst = this.nextOperant()
380 | const funcOperant = this.nextOperant()
381 | const funcInfoMeta = funcOperant.value
382 | const func = this.parseToJsFunc(funcInfoMeta, this.callingFunctionInfo.closureScope.fork())
383 | this.setReg(dst, { value: func })
384 | break
385 | }
386 | case I.MOV_PROP: {
387 | const dst = this.nextOperant()
388 | const o = this.nextOperant()
389 | const k = this.nextOperant()
390 | const v = o.value[k.value] // getByProp(o.value, k.value)
391 | this.setReg(dst, { value: v })
392 | break
393 | }
394 | case I.LT: {
395 | this.binaryExpression((a, b): any => a < b)
396 | break
397 | }
398 | case I.GT: {
399 | this.binaryExpression((a, b): any => a > b)
400 | break
401 | }
402 | case I.EQ: {
403 | this.binaryExpression((a, b): any => a === b)
404 | break
405 | }
406 | case I.NE: {
407 | this.binaryExpression((a, b): any => a !== b)
408 | break
409 | }
410 | case I.WEQ: {
411 | // tslint:disable-next-line: triple-equals
412 | this.binaryExpression((a, b): any => a == b)
413 | break
414 | }
415 | case I.WNE: {
416 | // tslint:disable-next-line: triple-equals
417 | this.binaryExpression((a, b): any => a != b)
418 | break
419 | }
420 | case I.LE: {
421 | this.binaryExpression((a, b): any => a <= b)
422 | break
423 | }
424 | case I.GE: {
425 | this.binaryExpression((a, b): any => a >= b)
426 | break
427 | }
428 | case I.ADD: {
429 | this.binaryExpression((a, b): any => a + b)
430 | break
431 | }
432 | case I.SUB: {
433 | this.binaryExpression((a, b): any => a - b)
434 | break
435 | }
436 | case I.MUL: {
437 | this.binaryExpression((a, b): any => a * b)
438 | break
439 | }
440 | case I.DIV: {
441 | this.binaryExpression((a, b): any => a / b)
442 | break
443 | }
444 | case I.MOD: {
445 | this.binaryExpression((a, b): any => a % b)
446 | break
447 | }
448 | case I.AND: {
449 | // tslint:disable-next-line: no-bitwise
450 | this.binaryExpression((a, b): any => a & b)
451 | break
452 | }
453 | case I.OR: {
454 | // tslint:disable-next-line: no-bitwise
455 | this.binaryExpression((a, b): any => a | b)
456 | break
457 | }
458 | case I.XOR: {
459 | // tslint:disable-next-line: no-bitwise
460 | this.binaryExpression((a, b): any => a ^ b)
461 | break
462 | }
463 | case I.SHL: {
464 | // tslint:disable-next-line: no-bitwise
465 | this.binaryExpression((a, b): any => a << b)
466 | break
467 | }
468 | case I.SHR: {
469 | // tslint:disable-next-line: no-bitwise
470 | this.binaryExpression((a, b): any => a >> b)
471 | break
472 | }
473 | case I.ZSHR: {
474 | // tslint:disable-next-line: no-bitwise
475 | this.binaryExpression((a, b): any => a >>> b)
476 | break
477 | }
478 | case I.LG_AND: {
479 | this.binaryExpression((a, b): any => a && b)
480 | break
481 | }
482 | case I.LG_OR: {
483 | this.binaryExpression((a, b): any => a || b)
484 | break
485 | }
486 | case I.INST_OF: {
487 | this.binaryExpression((a, b): any => {
488 | return a instanceof b
489 | })
490 | break
491 | }
492 | case I.IN: {
493 | this.binaryExpression((a, b): any => {
494 | return a in b
495 | })
496 | break
497 | }
498 | case I.ALLOC: {
499 | const dst = this.nextOperant()
500 | // this.makeClosureIndex()
501 | this.getReg(dst)
502 | break
503 | }
504 | case I.PLUS: {
505 | this.uniaryExpression((val: any): any => +val)
506 | break
507 | }
508 | case I.MINUS: {
509 | this.uniaryExpression((val: any): any => -val)
510 | break
511 | }
512 | case I.VOID: {
513 | // tslint:disable-next-line: no-unused-expression
514 | this.uniaryExpression((val: any): any => void val)
515 | break
516 | }
517 | case I.NOT: {
518 | // tslint:disable-next-line: no-bitwise
519 | this.uniaryExpression((val: any): any => ~val)
520 | break
521 | }
522 | case I.NEG: {
523 | // tslint:disable-next-line: no-bitwise
524 | this.uniaryExpression((val: any): any => !val)
525 | break
526 | }
527 | case I.TYPE_OF: {
528 | this.uniaryExpression((val: any): any => typeof val)
529 | break
530 | }
531 | case I.DEL: {
532 | const o1 = this.nextOperant().value
533 | const o2 = this.nextOperant().value
534 | delete o1[o2]
535 | break
536 | }
537 | case I.MOV_THIS: {
538 | this.setReg(this.nextOperant(), { value: this.currentThis })
539 | break
540 | }
541 | case I.TRY: {
542 | const catchAddress = this.nextOperant()
543 | const endAddress = this.nextOperant()
544 | let callCount = 1
545 | const currentFunctionInfo = this.callingFunctionInfo
546 | while (callCount > 0 && this.isRunning) {
547 | try {
548 | const [o, isCallVMFunc] = this.fetchAndExecute()
549 | op = o
550 | if (isCallVMFunc) {
551 | callCount++
552 | }
553 | if (o === I.RET) {
554 | callCount--
555 | if (callCount === 0) {
556 | break
557 | }
558 | }
559 | if (o === I.TRY_END && callCount === 1) {
560 | this.ip = endAddress.value
561 | break
562 | }
563 | } catch(e) {
564 | if (e instanceof VMRunTimeError) {
565 | throw e
566 | }
567 | this.popToFunction(currentFunctionInfo)
568 | this.error = e
569 | this.ip = catchAddress.value
570 | break
571 | }
572 | }
573 | break
574 | }
575 | case I.THROW: {
576 | const err = this.nextOperant()
577 | throw err.value
578 | // throw new VMRunTimeError(err)
579 | break
580 | }
581 | case I.TRY_END: {
582 | // throw new VMRunTimeError('Should not has `TRY_END` here.')
583 | break
584 | }
585 | case I.GET_ERR: {
586 | const o = this.nextOperant()
587 | this.setReg(o, { value: this.error })
588 | break
589 | }
590 | case I.MOV_ARGS: {
591 | const dst = this.nextOperant()
592 | this.setReg(dst, { value: this.stack[this.fp - 3] })
593 | break
594 | }
595 | case I.FORIN: {
596 | const dst = this.nextOperant()
597 | const target = this.nextOperant()
598 | const startAddress = this.nextOperant()
599 | const endAddress = this.nextOperant()
600 | forIn:
601 | // tslint:disable-next-line: forin
602 | for (const i in target.value) {
603 | this.setReg(dst, { value: i })
604 | while (true) {
605 | const o = this.fetchAndExecute()[0]
606 | if (o === I.BREAK_FORIN) {
607 | break forIn
608 | }
609 | if (o === I.FORIN_END || o === I.CONT_FORIN) {
610 | this.ip = startAddress.value
611 | continue forIn
612 | }
613 | }
614 | }
615 | this.ip = endAddress.value
616 | break
617 | }
618 | case I.FORIN_END:
619 | case I.BREAK_FORIN:
620 | case I.CONT_FORIN: {
621 | break
622 | }
623 |
624 | case I.BLOCK: {
625 | const o= this.nextOperant()
626 | this.checkClosureAndFork()
627 | this.checkVariableScopeAndNew()
628 | this.callingFunctionInfo.closureScope.front(o.value)
629 | this.callingFunctionInfo.variables!.front(o.value)
630 | break
631 | }
632 | case I.CLR_BLOCK:
633 | case I.END_BLOCK: {
634 | const o = this.nextOperant()
635 | this.callingFunctionInfo.closureScope.back(o.value)
636 | this.callingFunctionInfo.variables!.back(o.value)
637 | break
638 | }
639 |
640 | default:
641 | throw new VMRunTimeError("Unknow command " + op + " " + I[op],)
642 | }
643 |
644 | return [op, isCallVMFunction]
645 | }
646 |
647 | public checkClosureAndFork(): void {
648 | const callingFunctionInfo = this.callingFunctionInfo
649 | if (!callingFunctionInfo.isInitClosure) {
650 | callingFunctionInfo.closureScope = this.callingFunctionInfo.closureScope.fork()
651 | callingFunctionInfo.isInitClosure = true
652 | }
653 | }
654 |
655 | public checkVariableScopeAndNew(): void {
656 | if (!this.callingFunctionInfo.variables) {
657 | this.callingFunctionInfo.variables = new Scope()
658 | }
659 | }
660 |
661 | public returnCurrentFunction(): void {
662 | const stack = this.stack
663 | const fp = this.fp
664 | this.fp = stack[fp]
665 | this.ip = stack[fp - 1]
666 | // 减去参数数量,减去三个 fp ip numArgs args
667 | this.sp = fp - stack[fp - 2] - 4
668 | // 清空上一帧
669 | this.stack.splice(this.sp + 1)
670 | if (this.callingFunctionInfo.returnValue === NO_RETURN_SYMBOL) {
671 | this.stack[0] = undefined
672 | }
673 | this.allThis.pop()
674 | this.currentThis = this.allThis[this.allThis.length - 1]
675 | this.callingFunctionInfos.pop()
676 | this.callingFunctionInfo = this.callingFunctionInfos[this.callingFunctionInfos.length - 1]
677 | }
678 |
679 | public push(val: any): void {
680 | this.stack[++this.sp] = val
681 | }
682 |
683 | public nextOperator(): I {
684 | return readUInt8(this.codes, this.ip, ++this.ip)
685 | }
686 |
687 | public nextOperant(): IOperant {
688 | const [operantType, value, byteLength] = getOperatantByBuffer(this.codes, this.ip++)
689 | this.ip = this.ip + byteLength
690 | if (operantType === IOperatantType.REGISTER) {
691 | }
692 | return {
693 | type: operantType,
694 | value: this.parseValue(operantType, value),
695 | raw: value,
696 | index: operantType === IOperatantType.REGISTER ? (this.fp + value) : value,
697 | }
698 | }
699 |
700 | public parseValue(valueType: IOperatantType, value: any): any {
701 | switch (valueType) {
702 | case IOperatantType.CLOSURE_REGISTER:
703 | return this.callingFunctionInfo.closureScope.get(value) // [this.callingFunctionInfo.closureTable[value]]
704 | case IOperatantType.REGISTER:
705 | // 参数数量控制
706 | if (value <= -4) {
707 | if ((-4 - value) < this.callingFunctionInfo.args.length) {
708 | return this.callingFunctionInfo.args[-4 - value]
709 | } else {
710 | return void 0
711 | }
712 | }
713 | return this.stack[this.fp + value]
714 | case IOperatantType.ARG_COUNT:
715 | case IOperatantType.NUMBER:
716 | case IOperatantType.ADDRESS:
717 | return value
718 | case IOperatantType.GLOBAL:
719 | return this.stack[value]
720 | case IOperatantType.STRING:
721 | return this.stringsTable[value]
722 | case IOperatantType.FUNCTION_INDEX:
723 | return this.functionsTable[value]
724 | case IOperatantType.RETURN_VALUE:
725 | return this.stack[0]
726 | case IOperatantType.BOOLEAN:
727 | return !!value
728 | case IOperatantType.NULL:
729 | return null
730 | case IOperatantType.UNDEFINED:
731 | return void 0
732 | case IOperatantType.VAR_SYMBOL:
733 | if (!this.callingFunctionInfo.variables) {
734 | return undefined
735 | }
736 | return this.callingFunctionInfo.variables.get(value)
737 | default:
738 | throw new VMRunTimeError("Unknown operant " + valueType)
739 | }
740 | }
741 |
742 | public jumpWithCondidtion(cond: (a: any, b: any) => boolean): void {
743 | const op1 = this.nextOperant()
744 | const op2 = this.nextOperant()
745 | const address = this.nextOperant()
746 | if (cond(op1.value, op2.value)) {
747 | this.ip = address.value
748 | }
749 | }
750 |
751 | public uniaryExpression(exp: (a: any) => any): void {
752 | const o = this.nextOperant()
753 | const ret = exp(o.value)
754 | this.setReg(o, { value: ret })
755 | }
756 |
757 | public binaryExpression(exp: (a: any, b: any) => any): void {
758 | const o1 = this.nextOperant()
759 | const o2 = this.nextOperant()
760 | const ret = exp(o1.value, o2.value)
761 | this.setReg(o1, { value: ret })
762 | }
763 |
764 | // tslint:disable-next-line: cognitive-complexity
765 | public callFunction(
766 | func: Callable | undefined,
767 | o: any,
768 | funcName: string,
769 | numArgs: number,
770 | isNewExpression: boolean,
771 | ): boolean {
772 | const stack = this.stack
773 | const f = func || o[funcName]
774 | let isCallVMFunction = false
775 | const isNullOrUndefined = o === void 0 || o === null || o === this.ctx
776 | if ((f instanceof Callable) && !isNewExpression) {
777 | // console.log('---> THE IP IS -->', numArgs)
778 | const arg = new NumArgs(numArgs)
779 | if (!isNullOrUndefined) {
780 | if (typeof o[funcName] === 'function') {
781 | o[funcName](arg)
782 | } else {
783 | throw new VMRunTimeError(`The function ${funcName} is not a function`)
784 | }
785 | } else {
786 | f(arg)
787 | }
788 | isCallVMFunction = true
789 | } else {
790 | const args = []
791 | for (let i = 0; i < numArgs; i++) {
792 | args.unshift(stack[this.sp--])
793 | }
794 | if (!isNullOrUndefined) {
795 | stack[0] = isNewExpression
796 | ? new o[funcName](...args)
797 | : o[funcName](...args)
798 | } else {
799 | stack[0] = isNewExpression
800 | ? new f(...args)
801 | : f(...args)
802 | }
803 | this.stack.splice(this.sp + 1)
804 | }
805 | return isCallVMFunction
806 | }
807 |
808 | public popToFunction(targetFunctionInfo: ICallingFunctionInfo): void {
809 | while (this.callingFunctionInfos[this.callingFunctionInfos.length - 1] !== targetFunctionInfo) {
810 | this.returnCurrentFunction()
811 | }
812 | }
813 |
814 | // tslint:disable-next-line: cognitive-complexity
815 | public parseToJsFunc (meta: FuncInfoMeta, closureScope: Scope): any {
816 | const vm = this
817 | const func = function (this: any, ...args: any[]): any {
818 | const [ip, _, localSize] = meta
819 | vm.isRunning = true
820 | const n = args[0]
821 | const isCalledFromJs = !(n instanceof NumArgs)
822 | let numArgs = 0
823 | let allArgs = []
824 | if (isCalledFromJs) {
825 | args.forEach((arg: any): void => vm.push(arg))
826 | numArgs = args.length
827 | allArgs = [...args]
828 | } else {
829 | numArgs = n.numArgs
830 | const end = vm.sp + 1
831 | allArgs = vm.stack.slice(end - numArgs, end) // []
832 | }
833 | const currentCallingFunctionInfo: ICallingFunctionInfo = vm.callingFunctionInfo = {
834 | isInitClosure: false,
835 | closureScope,
836 | variables: null,
837 | args: allArgs,
838 | returnValue: NO_RETURN_SYMBOL,
839 | }
840 | vm.callingFunctionInfos.push(vm.callingFunctionInfo)
841 | if (vm.allThis.length === 0) {
842 | vm.currentThis = vm.ctx
843 | } else {
844 | vm.currentThis = this || vm.ctx
845 | }
846 | vm.allThis.push(vm.currentThis)
847 | const stack = vm.stack
848 | if (isCalledFromJs) {
849 | stack[0] = undefined
850 | }
851 | // console.log('call', funcInfo, numArgs)
852 | // | R3 |
853 | // | R2 |
854 | // | R1 |
855 | // | R0 |
856 | // sp -> | fp | # for restoring old fp
857 | // | ip | # for restoring old ip
858 | // | numArgs | # for restoring old sp: old sp = current sp - numArgs - 3
859 | // | arguments | # for store arguments for js `arguments` keyword
860 | // | arg1 |
861 | // | arg2 |
862 | // | arg3 |
863 | // old sp -> | .... |
864 | stack[++vm.sp] = allArgs
865 | stack[++vm.sp] = numArgs
866 | stack[++vm.sp] = vm.ip
867 | stack[++vm.sp] = vm.fp
868 | // set to new ip and fp
869 | vm.ip = ip
870 | vm.fp = vm.sp
871 | vm.sp += localSize
872 | if (isCalledFromJs) {
873 | /** 嵌套 vm 函数调 vm 函数,需要知道嵌套多少层,等到当前层完结再返回 */
874 | let callCount = 1
875 | while (callCount > 0 && vm.isRunning) {
876 | const [op, isCallVMFunction] = vm.fetchAndExecute()
877 | if (isCallVMFunction) {
878 | callCount++
879 | } else if (op === I.RET) {
880 | callCount--
881 | }
882 | }
883 | if (currentCallingFunctionInfo.returnValue !== NO_RETURN_SYMBOL) {
884 | return currentCallingFunctionInfo.returnValue
885 | }
886 | }
887 | }
888 | Object.setPrototypeOf(func, Callable.prototype)
889 | try {
890 | Object.defineProperty(func, 'length', { value: meta[1] })
891 | } catch(e) {}
892 | vm.setMetaToFunc(func, meta)
893 | return func
894 | }
895 |
896 | private setMetaToFunc(func: any, meta: FuncInfoMeta): void {
897 | Object.defineProperty(func, '__vm_func_info__', {
898 | enumerable: false,
899 | value: { meta },
900 | writable: false,
901 | })
902 | }
903 |
904 | private getMetaFromFunc(func: any): { meta: FuncInfoMeta, closureTable: any } {
905 | return func.__vm_func_info__
906 | }
907 | }
908 |
909 | /**
910 | * Header:
911 | *
912 | * mainFunctionIndex: 1
913 | * funcionTableBasicIndex: 1
914 | * stringTableBasicIndex: 1
915 | * globalsSize: 2
916 | */
917 | const createVMFromArrayBuffer = (buffer: ArrayBuffer, ctx: any = {}): VirtualMachine => {
918 | const mainFunctionIndex = readUInt32(buffer, 0, 4)
919 | const funcionTableBasicIndex = readUInt32(buffer, 4, 8)
920 | const stringTableBasicIndex = readUInt32(buffer, 8, 12)
921 | const globalsSize = readUInt32(buffer, 12, 16)
922 | // console.log(
923 | // ' =========================== Start Running =======================\n',
924 | // 'main function index', mainFunctionIndex, '\n',
925 | // 'function table basic index', funcionTableBasicIndex, '\n',
926 | // 'string table basic index', stringTableBasicIndex, '\n',
927 | // 'globals szie ', globalsSize, '\n',
928 | // '=================================================================\n',
929 | // )
930 |
931 | const stringsTable: string[] = parseStringsArray(buffer.slice(stringTableBasicIndex))
932 | const codesBuf = new Uint8Array(buffer.slice(4 * 4, funcionTableBasicIndex))
933 | const funcsBuf = buffer.slice(funcionTableBasicIndex, stringTableBasicIndex)
934 | const funcsTable: FuncInfoMeta[] = parseFunctionTable(funcsBuf)
935 | // console.log('string table', stringsTable)
936 | // console.log('function table', funcsTable)
937 | // console.log(mainFunctionIndex, 'function basic index', funcionTableBasicIndex)
938 | // console.log('total codes bytes length -->', codesBuf.byteLength)
939 | // console.log('main start index', funcsTable[mainFunctionIndex][0], stringTableBasicIndex)
940 |
941 | return new VirtualMachine(codesBuf, funcsTable, mainFunctionIndex, stringsTable, globalsSize, ctx)
942 | }
943 |
944 | type FuncInfoMeta = [number, number, number] // address, numArgs, numLocals
945 |
946 | const parseFunctionTable = (buffer: ArrayBuffer): FuncInfoMeta[] => {
947 | const funcs: FuncInfoMeta[] = []
948 | let i = 0
949 | while (i < buffer.byteLength) {
950 | const ipEnd = i + 4
951 | const ip = readUInt32(buffer, i, ipEnd)
952 | const numArgsAndLocal = new Uint16Array(buffer.slice(ipEnd, ipEnd + 2 * 2))
953 | funcs.push([ip, numArgsAndLocal[0], numArgsAndLocal[1]])
954 | i += 8
955 | }
956 | return funcs
957 | }
958 |
959 | export { createVMFromArrayBuffer }
960 |
961 | // https://hackernoon.com/creating-callable-objects-in-javascript-d21l3te1
962 | // tslint:disable-next-line: max-classes-per-file
963 | class Callable extends Function {
964 | constructor() {
965 | super()
966 | }
967 | }
968 |
969 | export { Callable }
970 |
971 |
972 | // tslint:disable-next-line: max-classes-per-file
973 | class NumArgs {
974 | constructor(public numArgs: number) {
975 | }
976 | // tslint:disable-next-line: max-file-line-count
977 | }
978 |
979 | if (typeof window !== 'undefined') {
980 | (window as any).VirtualMachine = VirtualMachine;
981 | (window as any).createVMFromArrayBuffer = createVMFromArrayBuffer
982 | }
983 |
--------------------------------------------------------------------------------