├── .editorconfig ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── .yarnrc ├── 20220709-235015.gif ├── origin.jpg ├── package.json ├── src ├── compiler.ts ├── constrains.ts ├── index.ts ├── parser.ts ├── traverse.ts └── virtual-machine.ts ├── target.png ├── test-code.js ├── test-miniprogram ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── pages │ ├── index │ │ ├── constrains.js │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── virtual-machine.js │ └── logs │ │ ├── logs.js │ │ ├── logs.json │ │ ├── logs.wxml │ │ └── logs.wxss ├── project.config.json ├── sitemap.json └── utils │ └── util.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.ts] 3 | indent_style = space 4 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.lock 4 | *.log 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | // "preLaunchTask": "npm: build", 15 | "program": "${workspaceFolder}/src/index.ts", 16 | "outFiles": [ 17 | "${workspaceFolder}/**/*.js" 18 | ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.yarnpkg.com" -------------------------------------------------------------------------------- /20220709-235015.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramblex/jsjs-vm-demo/b34e02e293b45f0f8a9bad88777b7fb587fb94d8/20220709-235015.gif -------------------------------------------------------------------------------- /origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramblex/jsjs-vm-demo/b34e02e293b45f0f8a9bad88777b7fb587fb94d8/origin.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@types/estree": "^0.0.51", 4 | "@types/lodash": "^4.14.182", 5 | "@types/node": "^17.0.23", 6 | "@types/source-map": "^0.5.7", 7 | "acron": "^1.0.5", 8 | "astring": "^1.8.1", 9 | "ava": "^4.1.0", 10 | "jimp": "^0.16.1", 11 | "lodash": "^4.17.21", 12 | "static-server": "^2.2.1", 13 | "typescript": "^4.6.3" 14 | }, 15 | "scripts": { 16 | "build": "tsc", 17 | "server": "static-server", 18 | "generate": "node dist/index.js" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/compiler.ts: -------------------------------------------------------------------------------- 1 | import { Node, Program, Function, Expression, Statement, Identifier, Property, MemberExpression } from 'estree'; 2 | import { OpCode } from './constrains'; 3 | import { Parser } from "./parser"; 4 | import { VirtualMachine } from './virtual-machine'; 5 | 6 | export class UniqueId { 7 | private nextId = 1; 8 | clear() { 9 | this.nextId = 1; 10 | } 11 | get() { 12 | return (this.nextId++).toString(16); 13 | } 14 | } 15 | 16 | type AsmInst 17 | = { type: 'label', name: string } 18 | | { type: 'reference', label: string } 19 | | { type: 'opcode', opcode: OpCode, comment?: string } 20 | | { type: 'data', data: number[], rawData: string | number } 21 | | { type: 'comment', comment: string }; 22 | 23 | export class Compiler { 24 | private uniqueId: UniqueId = new UniqueId(); 25 | private parser: Parser = new Parser(this.uniqueId); 26 | private instructions: AsmInst[] = []; 27 | private controlBlockStack: { continue?: string, break?: string }[] = []; 28 | 29 | // 初始化编译器 30 | private clear() { 31 | this.instructions = [] 32 | this.uniqueId.clear(); 33 | this.controlBlockStack = []; 34 | } 35 | 36 | // 创建 label 37 | createLabelName(name: string): string { 38 | return `.${name}_${this.uniqueId.get()}`; 39 | } 40 | 41 | // 基本输出 42 | private writeLabel(name: string) { 43 | this.instructions.push({ type: 'label', name }); 44 | } 45 | 46 | private writeReference(name: string) { 47 | this.writeOp(OpCode.ADDR); 48 | this.instructions.push({ type: 'reference', label: name }); 49 | } 50 | 51 | private writeOp(code: OpCode, comment?: string) { 52 | this.instructions.push({ type: 'opcode', opcode: code, comment }); 53 | } 54 | 55 | private writeNumber(nu: number) { 56 | const inst: AsmInst = { type: 'data', rawData: nu, data: [] }; 57 | const dv = new DataView(new ArrayBuffer(8)); 58 | dv.setFloat64(0, nu); 59 | for (let i = 0; i < 8; i++) { 60 | inst.data.push(dv.getUint8(i)); 61 | } 62 | this.writeOp(OpCode.NUM); 63 | this.instructions.push(inst); 64 | } 65 | 66 | private writeString(str: string) { 67 | const inst: AsmInst = { type: 'data', rawData: str, data: [] }; 68 | const dv = new DataView(new ArrayBuffer(2)); 69 | for (const c of str) { 70 | dv.setUint16(0, c.charCodeAt(0)); 71 | inst.data.push(dv.getUint8(0)); 72 | inst.data.push(dv.getUint8(1)); 73 | } 74 | inst.data.push(0); 75 | inst.data.push(0); 76 | this.writeOp(OpCode.STR); 77 | this.instructions.push(inst); 78 | } 79 | 80 | private writeComment(comment: string) { 81 | this.instructions.push({ type: 'comment', comment }); 82 | } 83 | 84 | private compileStatement(stat: Statement) { 85 | switch (stat.type) { 86 | case 'EmptyStatement': 87 | case 'DebuggerStatement': { 88 | break; 89 | } 90 | case 'BlockStatement': { 91 | for (const _stat of stat.body) { 92 | this.compileStatement(_stat); 93 | } 94 | break; 95 | } 96 | case 'BreakStatement': { 97 | for (let i = this.controlBlockStack.length - 1; i >= 0; i--) { 98 | const { break: breakLabel } = this.controlBlockStack[i]; 99 | if (breakLabel) { 100 | this.writeReference(breakLabel); 101 | break; 102 | } 103 | } 104 | this.writeOp(OpCode.JUMP); 105 | break; 106 | } 107 | case 'ContinueStatement': { 108 | for (let i = this.controlBlockStack.length - 1; i >= 0; i--) { 109 | const { continue: breakLabel } = this.controlBlockStack[i]; 110 | if (breakLabel) { 111 | this.writeReference(breakLabel); 112 | break; 113 | } 114 | } 115 | this.writeOp(OpCode.JUMP); 116 | break; 117 | } 118 | case 'IfStatement': { 119 | const endLabel = this.createLabelName('cond_end'); 120 | const altLabel = this.createLabelName('cond_alt'); 121 | 122 | const { test, consequent, alternate } = stat; 123 | 124 | this.compileExpression(test); 125 | this.writeReference(alternate ? altLabel : endLabel); 126 | this.writeOp(OpCode.JUMPNOT); 127 | this.compileStatement(consequent); 128 | 129 | if (alternate) { 130 | this.writeReference(endLabel); 131 | this.writeOp(OpCode.JUMP); 132 | this.writeLabel(altLabel); 133 | this.compileStatement(alternate); 134 | } 135 | 136 | this.writeLabel(endLabel); 137 | break; 138 | } 139 | 140 | case 'SwitchStatement': { 141 | const caseLabel = this.createLabelName('switch_case'); 142 | const defaultLabel = this.createLabelName('switch_default'); 143 | const endLabel = this.createLabelName('switch_end') 144 | 145 | this.controlBlockStack.push({ break: endLabel }); 146 | 147 | const { discriminant, cases } = stat; 148 | this.compileExpression(discriminant); 149 | for (let i = 0; i < cases.length; i++) { 150 | const c = cases[i]; 151 | if (c.test) { 152 | this.writeOp(OpCode.TOP); 153 | this.compileExpression(c.test); 154 | this.writeOp(OpCode.SEQ); 155 | this.writeReference(`${caseLabel}_${i}`); 156 | this.writeOp(OpCode.JUMPIF); 157 | } else { 158 | this.writeReference(defaultLabel); 159 | this.writeOp(OpCode.JUMP); 160 | } 161 | } 162 | for (let i = 0; i < cases.length; i++) { 163 | const c = cases[i]; 164 | if (c.test) { 165 | this.writeLabel(`${caseLabel}_${i}`); 166 | } else { 167 | this.writeLabel(defaultLabel); 168 | } 169 | for (const _stat of c.consequent) { 170 | this.compileStatement(_stat) 171 | } 172 | } 173 | this.writeOp(OpCode.POP); 174 | this.writeLabel(endLabel); 175 | 176 | this.controlBlockStack.pop; 177 | break; 178 | } 179 | 180 | case 'WhileStatement': { 181 | const startLabel = this.createLabelName('while_start'); 182 | const endLabel = this.createLabelName('while_end'); 183 | this.controlBlockStack.push({ continue: startLabel, break: endLabel }); 184 | this.writeLabel(startLabel); 185 | this.compileExpression(stat.test); 186 | this.writeReference(endLabel); 187 | this.writeOp(OpCode.JUMPNOT); 188 | this.compileStatement(stat.body); 189 | this.writeLabel(endLabel); 190 | this.controlBlockStack.pop(); 191 | break; 192 | } 193 | 194 | case 'DoWhileStatement': { 195 | const startLabel = this.createLabelName('do_while_start'); 196 | const testLabel = this.createLabelName('do_while_test'); 197 | const endLabel = this.createLabelName('do_while_end'); 198 | 199 | this.controlBlockStack.push({ continue: testLabel, break: endLabel }); 200 | this.writeLabel(startLabel); 201 | this.compileStatement(stat.body); 202 | this.writeLabel(testLabel); 203 | this.compileExpression(stat.test); 204 | this.writeReference(startLabel); 205 | this.writeOp(OpCode.JUMPIF); 206 | this.writeLabel(endLabel); 207 | this.controlBlockStack.pop(); 208 | break; 209 | } 210 | 211 | case 'ForStatement': { 212 | const startLabel = this.createLabelName('for_start') 213 | const updateLabel = this.createLabelName('for_update'); 214 | const endLabel = this.createLabelName('for_end'); 215 | this.controlBlockStack.push({ continue: updateLabel, break: endLabel }); 216 | 217 | if (stat.init) { 218 | const init: Statement = /Expression$/.test(stat.init.type) 219 | ? { type: 'ExpressionStatement', expression: stat.init as Expression } 220 | : (stat.init as Statement) 221 | this.compileStatement(init); 222 | } 223 | 224 | this.writeLabel(startLabel); 225 | this.compileStatement(stat.body); 226 | this.writeLabel(updateLabel); 227 | 228 | if (stat.update) { 229 | this.compileStatement({ 230 | type: 'ExpressionStatement', 231 | expression: stat.update 232 | }); 233 | } 234 | 235 | if (stat.test) { 236 | this.compileExpression(stat.test) 237 | this.writeReference(startLabel); 238 | this.writeOp(OpCode.JUMPIF); 239 | } else { 240 | this.writeReference(startLabel); 241 | this.writeOp(OpCode.JUMP); 242 | } 243 | 244 | this.writeLabel(endLabel); 245 | this.controlBlockStack.pop(); 246 | break; 247 | } 248 | 249 | // case 'ForInStatement': { 250 | // break; 251 | // } 252 | 253 | case 'VariableDeclaration': { 254 | this.compileStatement({ 255 | type: 'ExpressionStatement', 256 | expression: { 257 | type: 'SequenceExpression', 258 | expressions: stat.declarations.filter(v => v.init).map(v => ({ 259 | type: 'AssignmentExpression', 260 | operator: '=', 261 | left: { type: 'Identifier', name: (v.id as Identifier).name }, 262 | right: v.init as Expression 263 | })) 264 | }, 265 | }); 266 | break; 267 | } 268 | case 'FunctionDeclaration': { 269 | this.writeOp(OpCode.NULL); 270 | this.writeNumber(stat.params.length); 271 | this.writeReference(stat.label); 272 | this.writeOp(OpCode.FUNC); 273 | this.writeString((stat.id as Identifier).name); 274 | this.writeOp(OpCode.OUT); 275 | this.writeOp(OpCode.POP) 276 | break; 277 | } 278 | case 'ReturnStatement': { 279 | if (stat.argument) { 280 | this.compileExpression(stat.argument); 281 | } else { 282 | this.writeOp(OpCode.UNDEF); 283 | } 284 | this.writeOp(OpCode.RET); 285 | break; 286 | } 287 | case 'ExpressionStatement': { 288 | this.compileExpression(stat.expression); 289 | this.writeOp(OpCode.POP); 290 | break; 291 | } 292 | // case 'ThrowStatement': 293 | // case 'TryStatement': 294 | // case 'LabeledStatement': 295 | default: 296 | throw new Error(`Unsupported statement type: ${stat.type}`); 297 | } 298 | } 299 | 300 | private compileMemberAndProperty(expr: MemberExpression) { 301 | this.compileExpression(expr.object as Expression); 302 | if (expr.computed) { 303 | this.compileExpression(expr.property as Expression); 304 | } else { 305 | this.writeString((expr.property as Identifier).name); 306 | } 307 | } 308 | 309 | private compileExpression(expr: Expression) { 310 | switch (expr.type) { 311 | case 'Identifier': { 312 | switch (expr.name) { 313 | case 'undefined': this.writeOp(OpCode.UNDEF); break; 314 | case 'null': this.writeOp(OpCode.NULL); break; 315 | default: 316 | this.writeString(expr.name); 317 | this.writeOp(OpCode.LOAD); 318 | } 319 | break; 320 | } 321 | case 'Literal': { 322 | if (expr.value === null) { 323 | this.writeOp(OpCode.NULL); break; 324 | } else if (typeof expr.value === 'number') { 325 | this.writeNumber(expr.value); 326 | } else if (typeof expr.value === 'string') { 327 | this.writeString(expr.value); 328 | } else if (typeof expr.value === 'boolean') { 329 | this.writeOp(expr.value ? OpCode.TRUE : OpCode.FALSE); 330 | } else { 331 | throw new Error(`Unsupported literal type: ${expr.value}`); 332 | } 333 | break; 334 | } 335 | case 'ThisExpression': { 336 | this.writeString('this'); 337 | this.writeOp(OpCode.LOAD); 338 | break; 339 | } 340 | case 'ArrayExpression': { 341 | this.writeOp(OpCode.ARR); 342 | for (let i = 0; i < expr.elements.length; i++) { 343 | const element = expr.elements[i]; 344 | this.writeOp(OpCode.TOP); 345 | this.writeNumber(i); 346 | if (element) { 347 | this.compileExpression(element as Expression); 348 | } else { 349 | this.writeOp(OpCode.NULL); 350 | } 351 | this.writeOp(OpCode.SET); 352 | this.writeOp(OpCode.POP); 353 | } 354 | break; 355 | } 356 | case 'ObjectExpression': { 357 | this.writeOp(OpCode.OBJ); 358 | for (const prop of expr.properties as Property[]) { 359 | this.writeOp(OpCode.TOP); 360 | if (prop.computed) { 361 | this.compileExpression(prop.key as Expression); 362 | } else { 363 | this.writeString((prop.key as Identifier).name); 364 | } 365 | this.compileExpression(prop.value as Expression); 366 | this.writeOp(OpCode.SET); 367 | this.writeOp(OpCode.POP); 368 | } 369 | break; 370 | } 371 | case 'UnaryExpression': { 372 | switch (expr.operator) { 373 | case '+': 374 | this.writeNumber(0); 375 | this.compileExpression(expr.argument); 376 | this.writeOp(OpCode.ADD); break; 377 | case '-': 378 | this.writeNumber(0); 379 | this.compileExpression(expr.argument); 380 | this.writeOp(OpCode.SUB); 381 | break; 382 | case '!': 383 | this.compileExpression(expr.argument); 384 | this.writeOp(OpCode.NOT); 385 | break; 386 | case '~': 387 | this.compileExpression(expr.argument); 388 | this.writeOp(OpCode.BNOT); 389 | break; 390 | case 'typeof': 391 | this.compileExpression(expr.argument); 392 | this.writeOp(OpCode.TYPEOF); 393 | break; 394 | case 'void': 395 | this.compileExpression(expr.argument); 396 | this.writeOp(OpCode.POP); 397 | this.writeOp(OpCode.UNDEF); 398 | break; 399 | case 'delete': { 400 | const { argument } = expr; 401 | if (argument.type === 'MemberExpression') { 402 | this.compileMemberAndProperty(argument); 403 | this.writeOp(OpCode.DELETE); 404 | } else { 405 | this.writeOp(OpCode.TRUE); 406 | } 407 | break; 408 | } 409 | } 410 | break; 411 | } 412 | case 'BinaryExpression': { 413 | this.compileExpression(expr.left); 414 | this.compileExpression(expr.right); 415 | switch (expr.operator) { 416 | case '==': this.writeOp(OpCode.EQ); break; 417 | case '!=': this.writeOp(OpCode.NEQ); break; 418 | case '===': this.writeOp(OpCode.SEQ); break; 419 | case '!==': this.writeOp(OpCode.SNEQ); break; 420 | case '<': this.writeOp(OpCode.LT); break; 421 | case '<=': this.writeOp(OpCode.LTE); break; 422 | case '>': this.writeOp(OpCode.GT); break; 423 | case '>=': this.writeOp(OpCode.GTE); break; 424 | case '<<': this.writeOp(OpCode.LSHIFT); break; 425 | case '>>': this.writeOp(OpCode.RSHIFT); break; 426 | case '>>>': this.writeOp(OpCode.URSHIFT); break; 427 | case '+': this.writeOp(OpCode.ADD); break; 428 | case '-': this.writeOp(OpCode.SUB); break; 429 | case '*': this.writeOp(OpCode.MUL); break; 430 | case '**': this.writeOp(OpCode.EXP); break; 431 | case '/': this.writeOp(OpCode.DIV); break; 432 | case '%': this.writeOp(OpCode.MOD); break; 433 | case '|': this.writeOp(OpCode.BOR); break; 434 | case '^': this.writeOp(OpCode.BXOR); break; 435 | case '&': this.writeOp(OpCode.BAND); break; 436 | case 'in': this.writeOp(OpCode.IN); break; 437 | case 'instanceof': this.writeOp(OpCode.INSOF); break; 438 | } 439 | break; 440 | } 441 | case 'UpdateExpression': { 442 | const { argument } = expr; 443 | let op: OpCode = expr.operator == '++' ? OpCode.ADD : OpCode.SUB; 444 | 445 | if (argument.type === 'Identifier') { 446 | this.compileExpression(argument); 447 | this.writeNumber(1); 448 | this.writeOp(op); 449 | this.writeString(argument.name); 450 | this.writeOp(OpCode.OUT); 451 | } else if (argument.type === 'MemberExpression') { 452 | this.compileMemberAndProperty(argument); 453 | this.writeOp(OpCode.TOP2); 454 | this.writeOp(OpCode.GET); 455 | this.writeNumber(1); 456 | this.writeOp(op); 457 | this.writeOp(OpCode.SET); 458 | } 459 | 460 | if (!expr.prefix) { 461 | this.writeNumber(0); 462 | this.writeOp(op === OpCode.ADD ? OpCode.SUB : OpCode.ADD); 463 | } 464 | break; 465 | } 466 | case 'AssignmentExpression': { 467 | const { left, right, operator } = expr; 468 | const op = { 469 | '=': OpCode.NOP, 470 | '+=': OpCode.ADD, 471 | '-=': OpCode.SUB, 472 | '*=': OpCode.MUL, 473 | '**=': OpCode.EXP, 474 | '/=': OpCode.DIV, 475 | '%=': OpCode.MOD, 476 | '<<=': OpCode.LSHIFT, 477 | '>>=': OpCode.RSHIFT, 478 | '>>>=': OpCode.URSHIFT, 479 | '|=': OpCode.BOR, 480 | '^=': OpCode.BXOR, 481 | '&=': OpCode.BAND, 482 | }[operator]; 483 | 484 | if (left.type === 'Identifier') { 485 | this.compileExpression(right); 486 | if (operator === '=') { 487 | this.writeString(left.name); 488 | this.writeOp(OpCode.OUT); 489 | } else { 490 | this.compileExpression(left); 491 | this.writeOp(op); 492 | this.writeOp(OpCode.OUT); 493 | } 494 | } else if (left.type === 'MemberExpression') { 495 | this.compileMemberAndProperty(left); 496 | if (operator === '=') { 497 | this.compileExpression(right); 498 | this.writeOp(OpCode.SET); 499 | } else { 500 | this.writeOp(OpCode.TOP2); 501 | this.writeOp(OpCode.GET); 502 | this.compileExpression(right); 503 | this.writeOp(op); 504 | this.writeOp(OpCode.SET); 505 | } 506 | } 507 | break; 508 | } 509 | case 'LogicalExpression': { 510 | const label = this.createLabelName('logic_end'); 511 | const { left, right, operator } = expr; 512 | this.compileExpression(left); 513 | this.writeOp(OpCode.TOP); 514 | this.writeReference(label); 515 | if (operator === '&&') { 516 | this.writeOp(OpCode.JUMPIF); 517 | this.compileExpression(right); 518 | this.writeOp(OpCode.AND); 519 | } else if (operator === '||') { 520 | this.writeOp(OpCode.JUMPNOT); 521 | this.compileExpression(right); 522 | this.writeOp(OpCode.OR); 523 | } 524 | this.writeLabel(label); 525 | break; 526 | } 527 | case 'MemberExpression': { 528 | this.compileMemberAndProperty(expr); 529 | this.writeOp(OpCode.GET); 530 | break; 531 | } 532 | case 'ConditionalExpression': { 533 | const endLabel = this.createLabelName('cond_end'); 534 | const altLabel = this.createLabelName('cond_alt'); 535 | const { test, consequent, alternate } = expr; 536 | this.compileExpression(test); 537 | this.writeLabel(altLabel); 538 | this.writeOp(OpCode.JUMPNOT); 539 | this.compileExpression(consequent); 540 | this.writeReference(endLabel); 541 | this.writeOp(OpCode.JUMP); 542 | this.writeLabel(altLabel); 543 | this.compileExpression(alternate); 544 | this.writeLabel(endLabel); 545 | break; 546 | } 547 | case 'CallExpression': { 548 | const { callee, arguments: args } = expr; 549 | if (callee.type === 'MemberExpression') { 550 | const { object, property, computed } = callee; 551 | this.compileExpression(object as Expression); 552 | this.writeOp(OpCode.TOP); 553 | if (computed) { 554 | this.compileExpression(property as Expression); 555 | this.writeOp(OpCode.GET); 556 | } else { 557 | this.writeString((property as Identifier).name); 558 | this.writeOp(OpCode.GET); 559 | } 560 | } else { 561 | this.writeOp(OpCode.NULL); 562 | this.compileExpression(callee as Expression); 563 | } 564 | this.compileExpression({ 565 | type: 'ArrayExpression', 566 | elements: args as Expression[], 567 | }); 568 | this.writeOp(OpCode.CALL); 569 | break; 570 | } 571 | case 'NewExpression': { 572 | const { callee, arguments: args } = expr; 573 | this.compileExpression(callee as Expression); 574 | this.compileExpression({ 575 | type: 'ArrayExpression', 576 | elements: args as Expression[], 577 | }); 578 | this.writeOp(OpCode.NEW); 579 | break; 580 | } 581 | case 'SequenceExpression': { 582 | for (let i = 0; i < expr.expressions.length; i++) { 583 | const expression = expr.expressions[i]; 584 | this.compileExpression(expression); 585 | if (i < arguments.length - 1) { 586 | this.writeOp(OpCode.POP); 587 | } 588 | } 589 | break; 590 | } 591 | case 'FunctionExpression': { 592 | if (expr.id) { 593 | this.writeString(expr.id.name); 594 | } else { 595 | this.writeOp(OpCode.NULL); 596 | } 597 | this.writeNumber(expr.params.length); 598 | this.writeReference(expr.label); 599 | this.writeOp(OpCode.FUNC); 600 | break; 601 | } 602 | // case 'YieldExpression': 603 | // case 'AwaitExpression': 604 | default: { 605 | throw new Error(`Unsupported expression type: ${expr.type}`); 606 | } 607 | } 608 | } 609 | 610 | private compileBlock(block: Program | Function) { 611 | this.writeLabel(block.label); 612 | 613 | switch (block.type) { 614 | case 'Program': { 615 | 616 | for (const name of block.declarations) { 617 | this.writeString(name); 618 | this.writeOp(OpCode.VAR); 619 | } 620 | for (const stat of block.body) { 621 | this.compileStatement(stat as Statement); 622 | } 623 | this.writeOp(OpCode.RET); 624 | break; 625 | } 626 | case 'FunctionExpression': 627 | case 'FunctionDeclaration': { 628 | for (let i = 0; i < block.params.length; i++) { 629 | const id = block.params[i] as Identifier; 630 | // 声明参数 631 | this.writeString(id.name); 632 | this.writeOp(OpCode.VAR); 633 | // 初始化参数 634 | this.writeOp(OpCode.TOP); 635 | this.writeNumber(i); 636 | this.writeOp(OpCode.GET); 637 | this.writeString(id.name); 638 | this.writeOp(OpCode.OUT); 639 | this.writeOp(OpCode.POP); 640 | } 641 | this.writeOp(OpCode.POP); 642 | for (const name of block.declarations) { 643 | this.writeString(name); 644 | this.writeOp(OpCode.VAR); 645 | } 646 | this.compileStatement(block.body); 647 | this.writeOp(OpCode.UNDEF); 648 | this.writeOp(OpCode.RET); 649 | break; 650 | } 651 | } 652 | } 653 | 654 | compile(source: string) { 655 | this.clear(); 656 | const blocks = this.parser.parse(source); 657 | 658 | this.compile 659 | 660 | for (const block of blocks) { 661 | this.compileBlock(block); 662 | } 663 | } 664 | 665 | show() { 666 | const lines: string[] = []; 667 | for (const inst of this.instructions) { 668 | switch (inst.type) { 669 | case 'label': { 670 | lines.push(`${inst.name}:`); 671 | break; 672 | } 673 | case 'reference': { 674 | lines.push(`\t${inst.label}`); 675 | break; 676 | } 677 | case 'opcode': { 678 | lines.push(`\t${OpCode[inst.opcode]}(${inst.opcode.toString(16).padStart(2, '0')})${inst.comment ? ` # ${inst.comment}` : ''}`); 679 | break; 680 | } 681 | case 'data': { 682 | lines.push(`\t${JSON.stringify(inst.rawData)} (${inst.data.map(d => d.toString(16).padStart(2, '0')).join(' ')})`); 683 | break; 684 | } 685 | case 'comment': { 686 | lines.push(`\t# ${inst.comment}`); 687 | } 688 | } 689 | } 690 | console.log(lines.join('\n')); 691 | } 692 | 693 | toNumberArray(): number[] { 694 | const labelMap = new Map(); 698 | const codes: number[] = []; 699 | 700 | for (const inst of this.instructions) { 701 | switch (inst.type) { 702 | case 'label': { 703 | const label = labelMap.get(inst.name) || { address: 0, references: [] }; 704 | label.address = codes.length; 705 | labelMap.set(inst.name, label); 706 | break; 707 | } 708 | case 'reference': { 709 | const label = labelMap.get(inst.label) || { address: 0, references: [] }; 710 | label.references.push(codes.length); 711 | codes.push(0, 0, 0, 0); 712 | labelMap.set(inst.label, label); 713 | break; 714 | } 715 | case 'opcode': { 716 | codes.push(inst.opcode); 717 | break; 718 | } 719 | case 'data': { 720 | codes.push(...inst.data); 721 | } 722 | } 723 | } 724 | 725 | for (const [name, { address, references }] of labelMap) { 726 | const dv = new DataView(new ArrayBuffer(4)); 727 | dv.setUint32(0, address); 728 | for (const offset of references) { 729 | for (let i = 0; i < 4; i++) { 730 | codes[offset + i] = dv.getUint8(i); 731 | } 732 | } 733 | } 734 | 735 | return codes; 736 | } 737 | 738 | toArrayBuffer() { 739 | const codes = this.toNumberArray(); 740 | const buffer = new ArrayBuffer(codes.length); 741 | const dv = new DataView(buffer); 742 | for (let i = 0; i < codes.length; i++) { 743 | dv.setUint8(i, codes[i]); 744 | } 745 | return buffer; 746 | } 747 | } 748 | -------------------------------------------------------------------------------- /src/constrains.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 二进制文件: 3 | */ 4 | 5 | // 操作码 8 位 6 | export enum OpCode { 7 | // 空指令,占位用 8 | NOP = 0x00, 9 | 10 | // push 基本数据 11 | UNDEF = 0x01, 12 | NULL = 0x02, 13 | OBJ = 0x03, 14 | ARR = 0x04, 15 | TRUE = 0x05, 16 | FALSE = 0x06, 17 | 18 | NUM = 0x07, // 后面跟 64 位 double 数字字面量 19 | ADDR = 0x08, 20 | STR = 0x09, // 后面跟 16 位为一个字符长度 0x0000 表示结尾的字符串字面量 21 | 22 | // 基本栈操作 23 | POP = 0x0A, 24 | // SWP = 0x0B, 25 | // CLEAN = 0x0C, 26 | TOP = 0x0D, 27 | TOP2 = 0x0E, 28 | 29 | // 数据存储 30 | VAR = 0x10, 31 | LOAD = 0x11, 32 | OUT = 0x12, 33 | 34 | // 分支跳转 35 | JUMP = 0x20, 36 | JUMPIF = 0x21, 37 | JUMPNOT = 0x22, 38 | 39 | // 函数 40 | FUNC = 0x30, 41 | CALL = 0x31, 42 | NEW = 0x32, 43 | RET = 0x33, 44 | 45 | // 对象操作 46 | GET = 0x40, 47 | SET = 0x41, 48 | // KEYS = 0x42, 49 | IN = 0x43, // in 50 | DELETE = 0x44, // delete 51 | 52 | // 表运算 53 | EQ = 0x50, // == 54 | NEQ = 0x51, // != 55 | SEQ = 0x52, // === 56 | SNEQ = 0x53, // !== 57 | LT = 0x54, // < 58 | LTE = 0x55, // <= 59 | GT = 0x56, // > 60 | GTE = 0x57, // >= 61 | 62 | // 数学运算 63 | ADD = 0x60, // + 64 | SUB = 0x61, // - 65 | MUL = 0x62, // * 66 | EXP = 0x63, // ** 67 | DIV = 0x64, // / 68 | MOD = 0x65, // % 69 | 70 | // 位运算 71 | BNOT = 0x70, // ~ 72 | BOR = 0x71, // | 73 | BXOR = 0x72, // ^ 74 | BAND = 0x73,// & 75 | LSHIFT = 0x73, // << 76 | RSHIFT = 0x75, // >> 77 | URSHIFT = 0x76,// >>> 78 | 79 | // 逻辑运算 80 | OR = 0x80, // || 81 | AND = 0x81, // && 82 | NOT = 0x82, // ! 83 | 84 | // 类型运算 85 | INSOF = 0x90, // instanceof 86 | TYPEOF = 0x91, // typeof 87 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import * as Jimp from 'jimp'; 4 | import { chunk } from 'lodash'; 5 | 6 | import { Compiler } from './compiler'; 7 | import { GlobalScope, VirtualMachine } from './virtual-machine'; 8 | 9 | async function __main__() { 10 | 11 | const compiler = new Compiler(); 12 | 13 | const testCode = fs.readFileSync(path.join(__dirname, '../test-code.js'), 'utf8'); 14 | compiler.compile(testCode); 15 | 16 | const codes = compiler.toNumberArray(); 17 | compiler.show(); 18 | 19 | // const globalScope = new GlobalScope(global); 20 | // const vm = new VirtualMachine(globalScope, codes); 21 | // vm.run() 22 | 23 | const image = await Jimp.read('./origin.jpg'); 24 | const buffer = image.bitmap.data; 25 | 26 | const highBit = 0xFF; 27 | const lowBit = 0xF0; 28 | 29 | for (let i = 0; i < codes.length; i++) { 30 | const code = codes[i].toString(2).padStart(8, '0').split('').map(x => x === '1' ? highBit : lowBit); 31 | const offset = i * 8 * 4; 32 | 33 | for (let i = 0; i < code.length; i++) { 34 | const bit = code[i]; 35 | const bitIndex = offset + (i * 4) + 3; 36 | buffer[bitIndex] = bit; 37 | } 38 | } 39 | 40 | await image.quality(100).writeAsync('./target.png'); 41 | 42 | const targetImage = await Jimp.read('./target.png'); 43 | const targetBuffer = targetImage.bitmap.data; 44 | const targetCodes = []; 45 | 46 | for (let i = 0; i < targetBuffer.length; i += 32) { 47 | const offset = i; 48 | 49 | let byte = 0; 50 | for (let j = 0; j < 8; j++) { 51 | const bitIndex = offset + (j * 4) + 3; 52 | const bit = targetBuffer[bitIndex] > 0xf8 ? 1 : 0; 53 | byte = byte << 1; 54 | byte = byte | bit; 55 | } 56 | targetCodes.push(byte); 57 | } 58 | console.log(targetCodes); 59 | 60 | for (let i = 0; i < codes.length; i++) { 61 | if (codes[i] !== targetCodes[i]) { 62 | debugger; 63 | } 64 | } 65 | 66 | // for (let i = 0; i < 1e3; i = i + 4) { 67 | // console.log([0, 1, 2, 3].map(n => targetBuffer[i + n].toString(16).padStart(2, '0'))); 68 | // debugger; 69 | // } 70 | 71 | // const width = Math.ceil(Math.sqrt(codes.length / 3)); 72 | // new Jimp(width, width, 0x000000ff, (err, img) => { 73 | // const buffer = img.bitmap.data; 74 | // const bitmap = chunk(codes, 3); 75 | 76 | // for (let i = 0; i < bitmap.length; i++) { 77 | // let offset = i * 4; 78 | // const [r, g, b] = bitmap[i]; 79 | // buffer[offset] = r || 0 80 | // buffer[offset + 1] = g || 0 81 | // buffer[offset + 2] = b || 0 82 | // } 83 | 84 | // img.quality(100).quality(100).write('codes.png'); 85 | // }) 86 | 87 | // const buffer = compiler.toArrayBuffer(); 88 | // fs.writeFileSync(path.join(__dirname, '../codes.bin'), new DataView(buffer)); 89 | } 90 | 91 | __main__() 92 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as acorn from 'acorn'; 2 | import { Node, Program, Function, Identifier } from 'estree'; 3 | import { UniqueId } from './compiler'; 4 | 5 | import { traverse } from './traverse'; 6 | 7 | declare module 'estree' { 8 | interface Program { 9 | declarations: Set, 10 | label: string, 11 | } 12 | 13 | interface BaseFunction { 14 | declarations: Set, 15 | label: string, 16 | } 17 | } 18 | 19 | export class Parser { 20 | private uniqueId: UniqueId; 21 | 22 | constructor(uniqueId: UniqueId) { 23 | this.uniqueId = uniqueId; 24 | } 25 | 26 | parse(code: string): (Program | Function)[] { 27 | const program = acorn.parse(code, { 28 | ecmaVersion: 5 29 | }) as Node; 30 | 31 | let blocks: (Program | Function)[] = []; 32 | 33 | traverse((node, scopeNode, next) => { 34 | switch (node.type) { 35 | case 'LabeledStatement': 36 | case 'ThrowStatement': 37 | case 'TryStatement': 38 | case 'ForInStatement': 39 | throw new Error(`Unsupported Syntax: ${node.type}`); 40 | } 41 | 42 | switch (node.type) { 43 | case 'Program': 44 | node.declarations = new Set(); 45 | node.label = '.main_' + this.uniqueId.get(); 46 | blocks.push(node); 47 | return next(node, node); 48 | 49 | case 'FunctionExpression': { 50 | node.declarations = new Set(); 51 | node.label = `.${(node.id?.name || 'anonymous')}_${this.uniqueId.get()}`; 52 | blocks.push(node); 53 | return next(node, node); 54 | } 55 | 56 | case 'FunctionDeclaration': { 57 | scopeNode.declarations.add((node.id as Identifier).name); 58 | node.declarations = new Set(); 59 | node.label = `.${(node.id?.name || 'anonymous')}_${this.uniqueId.get()}`; 60 | blocks.push(node); 61 | return next(node, node); 62 | } 63 | 64 | case 'VariableDeclaration': { 65 | for (const declarator of node.declarations) { 66 | scopeNode.declarations.add(((declarator.id as Identifier).name)) 67 | } 68 | break; 69 | } 70 | } 71 | 72 | return next(node, scopeNode); 73 | })(program, program as Program); 74 | 75 | return blocks; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/traverse.ts: -------------------------------------------------------------------------------- 1 | import { Node } from "estree"; 2 | 3 | const isNode = (target: any): target is Node => 4 | target && typeof target.type === 'string'; 5 | 6 | const isNodeArray = (target: any): target is Node[] => 7 | Array.isArray(target) && target[0] && isNode(target[0]); 8 | 9 | const isChildNode = (target: any): target is Node | Node[] => 10 | isNodeArray(target) || isNode(target); 11 | 12 | const getChildrenKeys = (node: Node): (keyof Node)[] => 13 | (Object.keys(node) as (keyof Node)[]).filter(key => isChildNode(node[key])); 14 | 15 | const traverseChildren = (func: (node: Node, ctx: T) => Node) => (node: Node, ctx: T) => { 16 | if (isNode(node)) { 17 | for (const key of getChildrenKeys(node)) { 18 | const child = node[key] as any as Node | Node[]; 19 | if (isNodeArray(child)) { 20 | for (let i = 0; i < child.length; i++) { 21 | child[i] = child[i] && func(child[i], ctx); 22 | } 23 | } else { 24 | (node as any)[key] = func((node as any)[key], ctx); 25 | } 26 | } 27 | } 28 | return node; 29 | } 30 | 31 | export const traverse = (func: (node: Node, ctx: T, next: (node: Node, ctx: T) => Node) => Node) => { 32 | const _traverse = (node: Node, ctx: T): Node => func(node, ctx, _traverseChildren); 33 | const _traverseChildren = traverseChildren(_traverse); 34 | return _traverse; 35 | }; -------------------------------------------------------------------------------- /src/virtual-machine.ts: -------------------------------------------------------------------------------- 1 | import { OpCode } from "./constrains"; 2 | 3 | export class Scope { 4 | private parent?: Scope; 5 | private content: Map = new Map(); 6 | 7 | constructor(parent?: Scope) { 8 | this.parent = parent; 9 | } 10 | 11 | var(name: string) { 12 | this.content.set(name, undefined); 13 | } 14 | 15 | load(name: string): unknown { 16 | if (this.content.has(name)) { 17 | return this.content.get(name); 18 | } else if (this.parent) { 19 | return this.parent.load(name); 20 | } 21 | throw new Error(`Variable ${name} not found`); 22 | } 23 | 24 | out(name: string, value: unknown): unknown { 25 | if (this.content.has(name)) { 26 | this.content.set(name, value); 27 | return value; 28 | } else if (this.parent) { 29 | return this.parent.out(name, value); 30 | } 31 | throw new Error(`Variable ${name} not found`); 32 | } 33 | } 34 | 35 | export class GlobalScope extends Scope { 36 | private global: any; 37 | constructor(global: any) { 38 | super() 39 | this.global = global; 40 | } 41 | 42 | load(name: string) { 43 | try { return super.load(name) } catch (e) { } 44 | if (this.global.hasOwnProperty(name)) { 45 | return this.global[name]; 46 | } 47 | throw new Error(`Variable ${name} not found`); 48 | } 49 | 50 | out(name: string, value: unknown) { 51 | try { return super.out(name, value) } catch (e) { } 52 | this.global[name] = value; 53 | } 54 | } 55 | 56 | export class VirtualMachine { 57 | 58 | private scope: any; 59 | private codes: number[]; 60 | private stack: unknown[]; 61 | private pc: number; 62 | 63 | constructor(scope: any, codes: number[], pc: number = 0, stack: unknown[] = []) { 64 | this.scope = scope; 65 | this.codes = codes; 66 | this.pc = pc; 67 | this.stack = stack; 68 | } 69 | 70 | private loadAddress() { 71 | const dv = new DataView(new ArrayBuffer(8)); 72 | for (let i = 0; i < 4; i++) { 73 | dv.setUint8(i, this.codes[this.pc + i]); 74 | } 75 | this.pc += 4; 76 | this.stack.push(dv.getUint32(0)); 77 | } 78 | 79 | private loadNumber() { 80 | const dv = new DataView(new ArrayBuffer(8)); 81 | for (let i = 0; i < 8; i++) { 82 | dv.setUint8(i, this.codes[this.pc + i]); 83 | } 84 | this.pc += 8; 85 | this.stack.push(dv.getFloat64(0)); 86 | } 87 | 88 | private loadString() { 89 | const dv = new DataView(new ArrayBuffer(2)); 90 | let str = ''; 91 | for (let i = 0; true; i += 2) { 92 | dv.setUint16(0, this.codes[this.pc + i] << 8 | this.codes[this.pc + i + 1]); 93 | const charCode = dv.getUint16(0); 94 | if (charCode) { 95 | str += String.fromCharCode(charCode); 96 | } else { 97 | this.pc = this.pc + i + 2; 98 | this.stack.push(str); 99 | return; 100 | } 101 | } 102 | } 103 | 104 | run() { 105 | while (true) { 106 | const code = this.codes[this.pc++]; 107 | switch (code) { 108 | case OpCode.NOP: break; 109 | 110 | case OpCode.UNDEF: this.stack.push(undefined); break; 111 | case OpCode.NULL: this.stack.push(null); break; 112 | case OpCode.OBJ: this.stack.push({}); break; 113 | case OpCode.ARR: this.stack.push([]); break; 114 | case OpCode.TRUE: this.stack.push(true); break; 115 | case OpCode.FALSE: this.stack.push(false); break; 116 | 117 | case OpCode.NUM: this.loadNumber(); break; 118 | case OpCode.ADDR: this.loadAddress(); break; 119 | case OpCode.STR: this.loadString(); break; 120 | 121 | case OpCode.POP: this.stack.pop(); break; 122 | case OpCode.TOP: this.stack.push(this.stack[this.stack.length - 1]); break; 123 | case OpCode.TOP2: this.stack.push( 124 | this.stack[this.stack.length - 2], 125 | this.stack[this.stack.length - 1], 126 | ); break; 127 | 128 | case OpCode.VAR: this.scope.var(this.stack.pop()); break; 129 | case OpCode.LOAD: this.stack.push(this.scope.load(this.stack.pop())); break; 130 | case OpCode.OUT: this.stack.push(this.scope.out(this.stack.pop(), this.stack.pop())); break; 131 | 132 | case OpCode.JUMP: this.pc = this.stack.pop() as number; break; 133 | case OpCode.JUMPIF: { 134 | const addr = this.stack.pop(); 135 | const test = this.stack.pop(); 136 | if (test) { this.pc = addr as number }; 137 | break; 138 | } 139 | case OpCode.JUMPNOT: { 140 | const addr = this.stack.pop(); 141 | const test = this.stack.pop(); 142 | if (!test) { this.pc = addr as number }; 143 | break; 144 | } 145 | 146 | // 函数 147 | case OpCode.FUNC: { 148 | const addr = this.stack.pop(); 149 | const len = this.stack.pop(); 150 | const name = this.stack.pop(); 151 | const _this = this; 152 | 153 | const func = function (this: any, ...args: unknown[]) { 154 | const scope = new Scope(_this.scope); 155 | scope.var('this'); 156 | scope.out('this', this); 157 | if (name) { 158 | scope.var(name as string); 159 | scope.out(name as string, func); 160 | } 161 | const vm = new VirtualMachine(scope, _this.codes, addr as number, [args]); 162 | return vm.run(); 163 | } 164 | 165 | Object.defineProperty(func, 'name', { value: name }); 166 | Object.defineProperty(func, 'length', { value: len }); 167 | 168 | this.stack.push(func); 169 | break; 170 | } 171 | case OpCode.CALL: { 172 | const args = this.stack.pop(); 173 | const func = this.stack.pop() as Function; 174 | const _this = this.stack.pop(); 175 | this.stack.push(func.apply(_this, args)); 176 | break; 177 | } 178 | case OpCode.NEW: { 179 | const args = this.stack.pop() as unknown[]; 180 | const func = this.stack.pop() as Function; 181 | this.stack.push(new (func.bind.apply(func, [null, ...args]))); 182 | break; 183 | } 184 | case OpCode.RET: return this.stack.pop(); 185 | 186 | case OpCode.GET: { 187 | const key = this.stack.pop() as string; 188 | const object = this.stack.pop() as any; 189 | this.stack.push(object[key]) 190 | break; 191 | } 192 | case OpCode.SET: { 193 | const value = this.stack.pop(); 194 | const key = this.stack.pop() as string; 195 | const object = this.stack.pop() as any; 196 | this.stack.push(object[key] = value) 197 | break; 198 | } 199 | case OpCode.IN: { 200 | const key = this.stack.pop() as string; 201 | const object = this.stack.pop() as any; 202 | this.stack.push(key in object); 203 | break; 204 | } 205 | case OpCode.DELETE: { 206 | const key = this.stack.pop() as string; 207 | const object = this.stack.pop() as any; 208 | this.stack.push(delete object[key]); 209 | } 210 | // 表运算 211 | case OpCode.EQ: { 212 | const right = this.stack.pop(); 213 | const left = this.stack.pop(); 214 | this.stack.push(left == right); 215 | break; 216 | } 217 | case OpCode.NEQ: { 218 | const right = this.stack.pop(); 219 | const left = this.stack.pop(); 220 | this.stack.push(left != right); 221 | break; 222 | } 223 | case OpCode.SEQ: { 224 | const right = this.stack.pop(); 225 | const left = this.stack.pop(); 226 | this.stack.push(left === right); 227 | break; 228 | } 229 | case OpCode.SNEQ: { 230 | const right = this.stack.pop(); 231 | const left = this.stack.pop(); 232 | this.stack.push(left !== right); 233 | break; 234 | } 235 | case OpCode.LT: { 236 | const right = this.stack.pop() as any; 237 | const left = this.stack.pop() as any; 238 | this.stack.push(left < right); 239 | break; 240 | } 241 | case OpCode.LTE: { 242 | const right = this.stack.pop() as any; 243 | const left = this.stack.pop() as any; 244 | this.stack.push(left <= right); 245 | break; 246 | } 247 | case OpCode.GT: { 248 | const right = this.stack.pop() as any; 249 | const left = this.stack.pop() as any; 250 | this.stack.push(left > right); 251 | break; 252 | } 253 | case OpCode.GTE: { 254 | const right = this.stack.pop() as any; 255 | const left = this.stack.pop() as any; 256 | this.stack.push(left >= right); 257 | break; 258 | } 259 | 260 | // 数学运算 261 | case OpCode.ADD: { 262 | const right = this.stack.pop() as any; 263 | const left = this.stack.pop() as any; 264 | this.stack.push(left + right); 265 | break; 266 | } 267 | case OpCode.SUB: { 268 | const right = this.stack.pop() as any; 269 | const left = this.stack.pop() as any; 270 | this.stack.push(left - right); 271 | break; 272 | } 273 | case OpCode.MUL: { 274 | const right = this.stack.pop() as any; 275 | const left = this.stack.pop() as any; 276 | this.stack.push(left * right); 277 | break; 278 | } 279 | case OpCode.EXP: { 280 | const right = this.stack.pop() as any; 281 | const left = this.stack.pop() as any; 282 | this.stack.push(left ** right); 283 | break; 284 | } 285 | case OpCode.DIV: { 286 | const right = this.stack.pop() as any; 287 | const left = this.stack.pop() as any; 288 | this.stack.push(left / right); 289 | break; 290 | } 291 | case OpCode.MOD: { 292 | const right = this.stack.pop() as any; 293 | const left = this.stack.pop() as any; 294 | this.stack.push(left % right); 295 | break; 296 | } 297 | 298 | // 位运算 299 | case OpCode.BNOT: { 300 | const arg = this.stack.pop() as any; 301 | this.stack.push(~arg); 302 | break; 303 | } 304 | case OpCode.BOR:{ 305 | const right = this.stack.pop() as any; 306 | const left = this.stack.pop() as any; 307 | this.stack.push(left | right); 308 | break; 309 | } 310 | case OpCode.BXOR:{ 311 | const right = this.stack.pop() as any; 312 | const left = this.stack.pop() as any; 313 | this.stack.push(left ^ right); 314 | break; 315 | } 316 | case OpCode.BAND:{ 317 | const right = this.stack.pop() as any; 318 | const left = this.stack.pop() as any; 319 | this.stack.push(left & right); 320 | break; 321 | } 322 | case OpCode.LSHIFT:{ 323 | const right = this.stack.pop() as any; 324 | const left = this.stack.pop() as any; 325 | this.stack.push(left << right); 326 | break; 327 | } 328 | case OpCode.RSHIFT:{ 329 | const right = this.stack.pop() as any; 330 | const left = this.stack.pop() as any; 331 | this.stack.push(left >> right); 332 | break; 333 | } 334 | case OpCode.URSHIFT:{ 335 | const right = this.stack.pop() as any; 336 | const left = this.stack.pop() as any; 337 | this.stack.push(left >>> right); 338 | break; 339 | } 340 | 341 | // 逻辑运算 342 | case OpCode.OR:{ 343 | const right = this.stack.pop() as any; 344 | const left = this.stack.pop() as any; 345 | this.stack.push(left || right); 346 | break; 347 | } 348 | case OpCode.AND:{ 349 | const right = this.stack.pop() as any; 350 | const left = this.stack.pop() as any; 351 | this.stack.push(left && right); 352 | break; 353 | } 354 | case OpCode.NOT:{ 355 | const arg = this.stack.pop() as any; 356 | this.stack.push(!arg); 357 | break; 358 | } 359 | 360 | // 类型运算 361 | case OpCode.INSOF: { 362 | const right = this.stack.pop() as any; 363 | const left = this.stack.pop() as any; 364 | this.stack.push(left instanceof right); 365 | break; 366 | } 367 | case OpCode.TYPEOF:{ 368 | const arg = this.stack.pop() as any; 369 | this.stack.push(typeof arg); 370 | break; 371 | } 372 | 373 | default: 374 | throw new Error(`Unexpected code ${code.toString(16).padStart(2, '0')}`); 375 | } 376 | } 377 | } 378 | } -------------------------------------------------------------------------------- /target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramblex/jsjs-vm-demo/b34e02e293b45f0f8a9bad88777b7fb587fb94d8/target.png -------------------------------------------------------------------------------- /test-code.js: -------------------------------------------------------------------------------- 1 | 2 | // var quickSort = function (arr) { 3 | // if (arr.length <= 1) { return arr; } 4 | // var pivotIndex = Math.floor(arr.length / 2); 5 | // var pivot = arr.splice(pivotIndex, 1)[0]; 6 | // var left = []; 7 | // var right = []; 8 | // for (var i = 0; i < arr.length; i++) { 9 | // if (arr[i] < pivot) { 10 | // left.push(arr[i]); 11 | // } else { 12 | // right.push(arr[i]); 13 | // } 14 | // } 15 | // return quickSort(left).concat([pivot], quickSort(right)); 16 | // }; 17 | // console.log(quickSort([3, 6, 8, 2, 1, 9, 5, 7, 4])); 18 | 19 | wx.showModal({ 20 | title: '这是一段隐藏在图片中的代码', 21 | content: '这是一段隐藏在图片中的代码', 22 | success: function (res) { 23 | if (res.confirm) { 24 | page.setData({ motto: '用户点击确定' }); 25 | } else if (res.cancel) { 26 | page.setData({ motto: '用户点击取消' }); 27 | } 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /test-miniprogram/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /test-miniprogram/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({ 3 | onLaunch() { 4 | // 展示本地存储能力 5 | const logs = wx.getStorageSync('logs') || [] 6 | logs.unshift(Date.now()) 7 | wx.setStorageSync('logs', logs) 8 | 9 | // 登录 10 | wx.login({ 11 | success: res => { 12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 13 | } 14 | }) 15 | }, 16 | globalData: { 17 | userInfo: null 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /test-miniprogram/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index", 4 | "pages/logs/logs" 5 | ], 6 | "window":{ 7 | "backgroundTextStyle":"light", 8 | "navigationBarBackgroundColor": "#fff", 9 | "navigationBarTitleText": "Weixin", 10 | "navigationBarTextStyle":"black" 11 | }, 12 | "style": "v2", 13 | "sitemapLocation": "sitemap.json" 14 | } 15 | -------------------------------------------------------------------------------- /test-miniprogram/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /test-miniprogram/pages/index/constrains.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | 二进制文件: 4 | */ 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.OpCode = void 0; 7 | // 操作码 8 位 8 | var OpCode; 9 | (function (OpCode) { 10 | // 空指令,占位用 11 | OpCode[OpCode["NOP"] = 0] = "NOP"; 12 | // push 基本数据 13 | OpCode[OpCode["UNDEF"] = 1] = "UNDEF"; 14 | OpCode[OpCode["NULL"] = 2] = "NULL"; 15 | OpCode[OpCode["OBJ"] = 3] = "OBJ"; 16 | OpCode[OpCode["ARR"] = 4] = "ARR"; 17 | OpCode[OpCode["TRUE"] = 5] = "TRUE"; 18 | OpCode[OpCode["FALSE"] = 6] = "FALSE"; 19 | OpCode[OpCode["NUM"] = 7] = "NUM"; 20 | OpCode[OpCode["ADDR"] = 8] = "ADDR"; 21 | OpCode[OpCode["STR"] = 9] = "STR"; 22 | // 基本栈操作 23 | OpCode[OpCode["POP"] = 10] = "POP"; 24 | // SWP = 0x0B, 25 | // CLEAN = 0x0C, 26 | OpCode[OpCode["TOP"] = 13] = "TOP"; 27 | OpCode[OpCode["TOP2"] = 14] = "TOP2"; 28 | // 数据存储 29 | OpCode[OpCode["VAR"] = 16] = "VAR"; 30 | OpCode[OpCode["LOAD"] = 17] = "LOAD"; 31 | OpCode[OpCode["OUT"] = 18] = "OUT"; 32 | // 分支跳转 33 | OpCode[OpCode["JUMP"] = 32] = "JUMP"; 34 | OpCode[OpCode["JUMPIF"] = 33] = "JUMPIF"; 35 | OpCode[OpCode["JUMPNOT"] = 34] = "JUMPNOT"; 36 | // 函数 37 | OpCode[OpCode["FUNC"] = 48] = "FUNC"; 38 | OpCode[OpCode["CALL"] = 49] = "CALL"; 39 | OpCode[OpCode["NEW"] = 50] = "NEW"; 40 | OpCode[OpCode["RET"] = 51] = "RET"; 41 | // 对象操作 42 | OpCode[OpCode["GET"] = 64] = "GET"; 43 | OpCode[OpCode["SET"] = 65] = "SET"; 44 | // KEYS = 0x42, 45 | OpCode[OpCode["IN"] = 67] = "IN"; 46 | OpCode[OpCode["DELETE"] = 68] = "DELETE"; 47 | // 表运算 48 | OpCode[OpCode["EQ"] = 80] = "EQ"; 49 | OpCode[OpCode["NEQ"] = 81] = "NEQ"; 50 | OpCode[OpCode["SEQ"] = 82] = "SEQ"; 51 | OpCode[OpCode["SNEQ"] = 83] = "SNEQ"; 52 | OpCode[OpCode["LT"] = 84] = "LT"; 53 | OpCode[OpCode["LTE"] = 85] = "LTE"; 54 | OpCode[OpCode["GT"] = 86] = "GT"; 55 | OpCode[OpCode["GTE"] = 87] = "GTE"; 56 | // 数学运算 57 | OpCode[OpCode["ADD"] = 96] = "ADD"; 58 | OpCode[OpCode["SUB"] = 97] = "SUB"; 59 | OpCode[OpCode["MUL"] = 98] = "MUL"; 60 | OpCode[OpCode["EXP"] = 99] = "EXP"; 61 | OpCode[OpCode["DIV"] = 100] = "DIV"; 62 | OpCode[OpCode["MOD"] = 101] = "MOD"; 63 | // 位运算 64 | OpCode[OpCode["BNOT"] = 112] = "BNOT"; 65 | OpCode[OpCode["BOR"] = 113] = "BOR"; 66 | OpCode[OpCode["BXOR"] = 114] = "BXOR"; 67 | OpCode[OpCode["BAND"] = 115] = "BAND"; 68 | OpCode[OpCode["LSHIFT"] = 115] = "LSHIFT"; 69 | OpCode[OpCode["RSHIFT"] = 117] = "RSHIFT"; 70 | OpCode[OpCode["URSHIFT"] = 118] = "URSHIFT"; 71 | // 逻辑运算 72 | OpCode[OpCode["OR"] = 128] = "OR"; 73 | OpCode[OpCode["AND"] = 129] = "AND"; 74 | OpCode[OpCode["NOT"] = 130] = "NOT"; 75 | // 类型运算 76 | OpCode[OpCode["INSOF"] = 144] = "INSOF"; 77 | OpCode[OpCode["TYPEOF"] = 145] = "TYPEOF"; 78 | })(OpCode = exports.OpCode || (exports.OpCode = {})); 79 | //# sourceMappingURL=constrains.js.map -------------------------------------------------------------------------------- /test-miniprogram/pages/index/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | // 获取应用实例 3 | const { Scope, GlobalScope, VirtualMachine } = require('./virtual-machine'); 4 | const app = getApp() 5 | 6 | Page({ 7 | data: { 8 | motto: '', 9 | url: 'http://localhost:9080/target.png', 10 | }, 11 | 12 | onReady() { 13 | }, 14 | 15 | load() { 16 | const page = this; 17 | wx.downloadFile({ 18 | url: this.data.url, 19 | success({ tempFilePath }) { 20 | var ctx = wx.createCanvasContext('myCanvas'); 21 | 22 | const width = 319; 23 | const height = 205; 24 | 25 | ctx.drawImage(tempFilePath, 0, 0, width, height); 26 | ctx.draw(false, () => { 27 | wx.canvasGetImageData({ 28 | canvasId: 'myCanvas', 29 | x: 0, 30 | y: 0, 31 | width, 32 | height, 33 | success: (res) => { 34 | const targetBuffer = res.data; 35 | const targetCodes = []; 36 | 37 | for (let i = 0; i < targetBuffer.length; i += 32) { 38 | const offset = i; 39 | 40 | let byte = 0; 41 | for (let j = 0; j < 8; j++) { 42 | const bitIndex = offset + (j * 4) + 3; 43 | const bit = targetBuffer[bitIndex] > 0xf8 ? 1 : 0; 44 | byte = byte << 1; 45 | byte = byte | bit; 46 | } 47 | targetCodes.push(byte); 48 | } 49 | const globalScope = new GlobalScope({ 50 | console, 51 | Math, 52 | page, 53 | wx, 54 | }); 55 | const vm = new VirtualMachine(globalScope, targetCodes); 56 | vm.run() 57 | }, 58 | fail: (res) => { 59 | debugger; 60 | } 61 | }, page); 62 | }); 63 | 64 | 65 | } 66 | }) 67 | } 68 | 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /test-miniprogram/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /test-miniprogram/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 图片 url: 6 | 7 | 8 | {{motto}} 9 | 10 | -------------------------------------------------------------------------------- /test-miniprogram/pages/index/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramblex/jsjs-vm-demo/b34e02e293b45f0f8a9bad88777b7fb587fb94d8/test-miniprogram/pages/index/index.wxss -------------------------------------------------------------------------------- /test-miniprogram/pages/index/virtual-machine.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.VirtualMachine = exports.GlobalScope = exports.Scope = void 0; 4 | const constrains_1 = require("./constrains"); 5 | class Scope { 6 | parent; 7 | content = new Map(); 8 | constructor(parent) { 9 | this.parent = parent; 10 | } 11 | var(name) { 12 | this.content.set(name, undefined); 13 | } 14 | load(name) { 15 | if (this.content.has(name)) { 16 | return this.content.get(name); 17 | } 18 | else if (this.parent) { 19 | return this.parent.load(name); 20 | } 21 | throw new Error(`Variable ${name} not found`); 22 | } 23 | out(name, value) { 24 | if (this.content.has(name)) { 25 | this.content.set(name, value); 26 | return value; 27 | } 28 | else if (this.parent) { 29 | return this.parent.out(name, value); 30 | } 31 | throw new Error(`Variable ${name} not found`); 32 | } 33 | } 34 | exports.Scope = Scope; 35 | class GlobalScope extends Scope { 36 | global; 37 | constructor(global) { 38 | super(); 39 | this.global = global; 40 | } 41 | load(name) { 42 | try { 43 | return super.load(name); 44 | } 45 | catch (e) { } 46 | if (this.global.hasOwnProperty(name)) { 47 | return this.global[name]; 48 | } 49 | throw new Error(`Variable ${name} not found`); 50 | } 51 | out(name, value) { 52 | try { 53 | return super.out(name, value); 54 | } 55 | catch (e) { } 56 | this.global[name] = value; 57 | } 58 | } 59 | exports.GlobalScope = GlobalScope; 60 | class VirtualMachine { 61 | scope; 62 | codes; 63 | stack; 64 | pc; 65 | constructor(scope, codes, pc = 0, stack = []) { 66 | this.scope = scope; 67 | this.codes = codes; 68 | this.pc = pc; 69 | this.stack = stack; 70 | } 71 | loadAddress() { 72 | const dv = new DataView(new ArrayBuffer(8)); 73 | for (let i = 0; i < 4; i++) { 74 | dv.setUint8(i, this.codes[this.pc + i]); 75 | } 76 | this.pc += 4; 77 | this.stack.push(dv.getUint32(0)); 78 | } 79 | loadNumber() { 80 | const dv = new DataView(new ArrayBuffer(8)); 81 | for (let i = 0; i < 8; i++) { 82 | dv.setUint8(i, this.codes[this.pc + i]); 83 | } 84 | this.pc += 8; 85 | this.stack.push(dv.getFloat64(0)); 86 | } 87 | loadString() { 88 | const dv = new DataView(new ArrayBuffer(2)); 89 | let str = ''; 90 | for (let i = 0; true; i += 2) { 91 | dv.setUint16(0, this.codes[this.pc + i] << 8 | this.codes[this.pc + i + 1]); 92 | const charCode = dv.getUint16(0); 93 | if (charCode) { 94 | str += String.fromCharCode(charCode); 95 | } 96 | else { 97 | this.pc = this.pc + i + 2; 98 | this.stack.push(str); 99 | return; 100 | } 101 | } 102 | } 103 | run() { 104 | while (true) { 105 | const code = this.codes[this.pc++]; 106 | switch (code) { 107 | case constrains_1.OpCode.NOP: break; 108 | case constrains_1.OpCode.UNDEF: 109 | this.stack.push(undefined); 110 | break; 111 | case constrains_1.OpCode.NULL: 112 | this.stack.push(null); 113 | break; 114 | case constrains_1.OpCode.OBJ: 115 | this.stack.push({}); 116 | break; 117 | case constrains_1.OpCode.ARR: 118 | this.stack.push([]); 119 | break; 120 | case constrains_1.OpCode.TRUE: 121 | this.stack.push(true); 122 | break; 123 | case constrains_1.OpCode.FALSE: 124 | this.stack.push(false); 125 | break; 126 | case constrains_1.OpCode.NUM: 127 | this.loadNumber(); 128 | break; 129 | case constrains_1.OpCode.ADDR: 130 | this.loadAddress(); 131 | break; 132 | case constrains_1.OpCode.STR: 133 | this.loadString(); 134 | break; 135 | case constrains_1.OpCode.POP: 136 | this.stack.pop(); 137 | break; 138 | case constrains_1.OpCode.TOP: 139 | this.stack.push(this.stack[this.stack.length - 1]); 140 | break; 141 | case constrains_1.OpCode.TOP2: 142 | this.stack.push(this.stack[this.stack.length - 2], this.stack[this.stack.length - 1]); 143 | break; 144 | case constrains_1.OpCode.VAR: 145 | this.scope.var(this.stack.pop()); 146 | break; 147 | case constrains_1.OpCode.LOAD: 148 | this.stack.push(this.scope.load(this.stack.pop())); 149 | break; 150 | case constrains_1.OpCode.OUT: 151 | this.stack.push(this.scope.out(this.stack.pop(), this.stack.pop())); 152 | break; 153 | case constrains_1.OpCode.JUMP: 154 | this.pc = this.stack.pop(); 155 | break; 156 | case constrains_1.OpCode.JUMPIF: { 157 | const addr = this.stack.pop(); 158 | const test = this.stack.pop(); 159 | if (test) { 160 | this.pc = addr; 161 | } 162 | ; 163 | break; 164 | } 165 | case constrains_1.OpCode.JUMPNOT: { 166 | const addr = this.stack.pop(); 167 | const test = this.stack.pop(); 168 | if (!test) { 169 | this.pc = addr; 170 | } 171 | ; 172 | break; 173 | } 174 | // 函数 175 | case constrains_1.OpCode.FUNC: { 176 | const addr = this.stack.pop(); 177 | const len = this.stack.pop(); 178 | const name = this.stack.pop(); 179 | const _this = this; 180 | const func = function (...args) { 181 | const scope = new Scope(_this.scope); 182 | scope.var('this'); 183 | scope.out('this', this); 184 | if (name) { 185 | scope.var(name); 186 | scope.out(name, func); 187 | } 188 | const vm = new VirtualMachine(scope, _this.codes, addr, [args]); 189 | return vm.run(); 190 | }; 191 | Object.defineProperty(func, 'name', { value: name }); 192 | Object.defineProperty(func, 'length', { value: len }); 193 | this.stack.push(func); 194 | break; 195 | } 196 | case constrains_1.OpCode.CALL: { 197 | const args = this.stack.pop(); 198 | const func = this.stack.pop(); 199 | const _this = this.stack.pop(); 200 | this.stack.push(func.apply(_this, args)); 201 | break; 202 | } 203 | case constrains_1.OpCode.NEW: { 204 | const args = this.stack.pop(); 205 | const func = this.stack.pop(); 206 | this.stack.push(new (func.bind.apply(func, [null, ...args]))); 207 | break; 208 | } 209 | case constrains_1.OpCode.RET: return this.stack.pop(); 210 | case constrains_1.OpCode.GET: { 211 | const key = this.stack.pop(); 212 | const object = this.stack.pop(); 213 | this.stack.push(object[key]); 214 | break; 215 | } 216 | case constrains_1.OpCode.SET: { 217 | const value = this.stack.pop(); 218 | const key = this.stack.pop(); 219 | const object = this.stack.pop(); 220 | this.stack.push(object[key] = value); 221 | break; 222 | } 223 | case constrains_1.OpCode.IN: { 224 | const key = this.stack.pop(); 225 | const object = this.stack.pop(); 226 | this.stack.push(key in object); 227 | break; 228 | } 229 | case constrains_1.OpCode.DELETE: { 230 | const key = this.stack.pop(); 231 | const object = this.stack.pop(); 232 | this.stack.push(delete object[key]); 233 | } 234 | // 表运算 235 | case constrains_1.OpCode.EQ: { 236 | const right = this.stack.pop(); 237 | const left = this.stack.pop(); 238 | this.stack.push(left == right); 239 | break; 240 | } 241 | case constrains_1.OpCode.NEQ: { 242 | const right = this.stack.pop(); 243 | const left = this.stack.pop(); 244 | this.stack.push(left != right); 245 | break; 246 | } 247 | case constrains_1.OpCode.SEQ: { 248 | const right = this.stack.pop(); 249 | const left = this.stack.pop(); 250 | this.stack.push(left === right); 251 | break; 252 | } 253 | case constrains_1.OpCode.SNEQ: { 254 | const right = this.stack.pop(); 255 | const left = this.stack.pop(); 256 | this.stack.push(left !== right); 257 | break; 258 | } 259 | case constrains_1.OpCode.LT: { 260 | const right = this.stack.pop(); 261 | const left = this.stack.pop(); 262 | this.stack.push(left < right); 263 | break; 264 | } 265 | case constrains_1.OpCode.LTE: { 266 | const right = this.stack.pop(); 267 | const left = this.stack.pop(); 268 | this.stack.push(left <= right); 269 | break; 270 | } 271 | case constrains_1.OpCode.GT: { 272 | const right = this.stack.pop(); 273 | const left = this.stack.pop(); 274 | this.stack.push(left > right); 275 | break; 276 | } 277 | case constrains_1.OpCode.GTE: { 278 | const right = this.stack.pop(); 279 | const left = this.stack.pop(); 280 | this.stack.push(left >= right); 281 | break; 282 | } 283 | // 数学运算 284 | case constrains_1.OpCode.ADD: { 285 | const right = this.stack.pop(); 286 | const left = this.stack.pop(); 287 | this.stack.push(left + right); 288 | break; 289 | } 290 | case constrains_1.OpCode.SUB: { 291 | const right = this.stack.pop(); 292 | const left = this.stack.pop(); 293 | this.stack.push(left - right); 294 | break; 295 | } 296 | case constrains_1.OpCode.MUL: { 297 | const right = this.stack.pop(); 298 | const left = this.stack.pop(); 299 | this.stack.push(left * right); 300 | break; 301 | } 302 | case constrains_1.OpCode.EXP: { 303 | const right = this.stack.pop(); 304 | const left = this.stack.pop(); 305 | this.stack.push(left ** right); 306 | break; 307 | } 308 | case constrains_1.OpCode.DIV: { 309 | const right = this.stack.pop(); 310 | const left = this.stack.pop(); 311 | this.stack.push(left / right); 312 | break; 313 | } 314 | case constrains_1.OpCode.MOD: { 315 | const right = this.stack.pop(); 316 | const left = this.stack.pop(); 317 | this.stack.push(left % right); 318 | break; 319 | } 320 | // 位运算 321 | case constrains_1.OpCode.BNOT: { 322 | const arg = this.stack.pop(); 323 | this.stack.push(~arg); 324 | break; 325 | } 326 | case constrains_1.OpCode.BOR: { 327 | const right = this.stack.pop(); 328 | const left = this.stack.pop(); 329 | this.stack.push(left | right); 330 | break; 331 | } 332 | case constrains_1.OpCode.BXOR: { 333 | const right = this.stack.pop(); 334 | const left = this.stack.pop(); 335 | this.stack.push(left ^ right); 336 | break; 337 | } 338 | case constrains_1.OpCode.BAND: { 339 | const right = this.stack.pop(); 340 | const left = this.stack.pop(); 341 | this.stack.push(left & right); 342 | break; 343 | } 344 | case constrains_1.OpCode.LSHIFT: { 345 | const right = this.stack.pop(); 346 | const left = this.stack.pop(); 347 | this.stack.push(left << right); 348 | break; 349 | } 350 | case constrains_1.OpCode.RSHIFT: { 351 | const right = this.stack.pop(); 352 | const left = this.stack.pop(); 353 | this.stack.push(left >> right); 354 | break; 355 | } 356 | case constrains_1.OpCode.URSHIFT: { 357 | const right = this.stack.pop(); 358 | const left = this.stack.pop(); 359 | this.stack.push(left >>> right); 360 | break; 361 | } 362 | // 逻辑运算 363 | case constrains_1.OpCode.OR: { 364 | const right = this.stack.pop(); 365 | const left = this.stack.pop(); 366 | this.stack.push(left || right); 367 | break; 368 | } 369 | case constrains_1.OpCode.AND: { 370 | const right = this.stack.pop(); 371 | const left = this.stack.pop(); 372 | this.stack.push(left && right); 373 | break; 374 | } 375 | case constrains_1.OpCode.NOT: { 376 | const arg = this.stack.pop(); 377 | this.stack.push(!arg); 378 | break; 379 | } 380 | // 类型运算 381 | case constrains_1.OpCode.INSOF: { 382 | const right = this.stack.pop(); 383 | const left = this.stack.pop(); 384 | this.stack.push(left instanceof right); 385 | break; 386 | } 387 | case constrains_1.OpCode.TYPEOF: { 388 | const arg = this.stack.pop(); 389 | this.stack.push(typeof arg); 390 | break; 391 | } 392 | default: 393 | throw new Error(`Unexpected code ${code.toString(16).padStart(2, '0')}`); 394 | } 395 | } 396 | } 397 | } 398 | exports.VirtualMachine = VirtualMachine; 399 | //# sourceMappingURL=virtual-machine.js.map -------------------------------------------------------------------------------- /test-miniprogram/pages/logs/logs.js: -------------------------------------------------------------------------------- 1 | // logs.js 2 | const util = require('../../utils/util.js') 3 | 4 | Page({ 5 | data: { 6 | logs: [] 7 | }, 8 | onLoad() { 9 | this.setData({ 10 | logs: (wx.getStorageSync('logs') || []).map(log => { 11 | return { 12 | date: util.formatTime(new Date(log)), 13 | timeStamp: log 14 | } 15 | }) 16 | }) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /test-miniprogram/pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志", 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /test-miniprogram/pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{index + 1}}. {{log.date}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /test-miniprogram/pages/logs/logs.wxss: -------------------------------------------------------------------------------- 1 | .log-list { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 40rpx; 5 | } 6 | .log-item { 7 | margin: 10rpx; 8 | } 9 | -------------------------------------------------------------------------------- /test-miniprogram/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [ 5 | { 6 | "type": "file", 7 | "value": ".eslintrc.js" 8 | } 9 | ] 10 | }, 11 | "setting": { 12 | "urlCheck": false, 13 | "es6": true, 14 | "enhance": true, 15 | "postcss": true, 16 | "preloadBackgroundData": false, 17 | "minified": true, 18 | "newFeature": false, 19 | "coverView": true, 20 | "nodeModules": false, 21 | "autoAudits": false, 22 | "showShadowRootInWxmlPanel": true, 23 | "scopeDataCheck": false, 24 | "uglifyFileName": false, 25 | "checkInvalidKey": true, 26 | "checkSiteMap": true, 27 | "uploadWithSourceMap": true, 28 | "compileHotReLoad": false, 29 | "lazyloadPlaceholderEnable": false, 30 | "useMultiFrameRuntime": true, 31 | "useApiHook": true, 32 | "useApiHostProcess": true, 33 | "babelSetting": { 34 | "ignore": [], 35 | "disablePlugins": [], 36 | "outputPath": "" 37 | }, 38 | "enableEngineNative": false, 39 | "useIsolateContext": true, 40 | "userConfirmedBundleSwitch": false, 41 | "packNpmManually": false, 42 | "packNpmRelationList": [], 43 | "minifyWXSS": true, 44 | "disableUseStrict": false, 45 | "minifyWXML": true, 46 | "showES6CompileOption": false, 47 | "useCompilerPlugins": false 48 | }, 49 | "compileType": "miniprogram", 50 | "libVersion": "2.19.4", 51 | "appid": "wx6763d5b63c467eb4", 52 | "projectname": "miniprogram-1", 53 | "debugOptions": { 54 | "hidedInDevtools": [] 55 | }, 56 | "scripts": {}, 57 | "staticServerOptions": { 58 | "baseURL": "", 59 | "servePath": "" 60 | }, 61 | "isGameTourist": false, 62 | "condition": { 63 | "search": { 64 | "list": [] 65 | }, 66 | "conversation": { 67 | "list": [] 68 | }, 69 | "game": { 70 | "list": [] 71 | }, 72 | "plugin": { 73 | "list": [] 74 | }, 75 | "gamePlugin": { 76 | "list": [] 77 | }, 78 | "miniprogram": { 79 | "list": [] 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /test-miniprogram/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /test-miniprogram/utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}` 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : `0${n}` 15 | } 16 | 17 | module.exports = { 18 | formatTime 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "allowJs": false, 7 | "outDir": "dist", 8 | 9 | "noImplicitAny": true, 10 | "strict": true, 11 | "alwaysStrict": true, 12 | }, 13 | "include": [ 14 | "src/**/*.ts" 15 | ] 16 | } --------------------------------------------------------------------------------