├── nsc ├── bin │ ├── run.cmd │ └── run ├── lib │ ├── index.ts │ └── commands │ │ ├── codegen.ts │ │ ├── run.ts │ │ └── compile.ts ├── .gitignore ├── .editorconfig ├── tsconfig.json └── package.json ├── docs ├── ir.md ├── logo.png ├── process.png ├── parseFunction.png ├── closure design.png ├── code optimizer.png ├── logo.svg └── code optimizer.uxf ├── example ├── main ├── test.js ├── p.ts ├── page.nes ├── fib.nes ├── callback.nes ├── tmp.js ├── test.nes └── js.nes ├── src ├── index.ts ├── js.ts ├── scope.ts ├── block-chain.ts ├── parser.ts ├── utils.ts ├── optimizer.ts ├── assembler.ts └── vm │ └── vm.ts ├── Makefile ├── test ├── utils.ts ├── textures │ └── lodash.min.js └── js.spec.ts ├── tsconfig.json ├── .github └── workflows │ └── node.js.yml ├── webpack.config.js ├── tslint.json ├── LICENSE ├── package.json ├── .gitignore ├── README.md └── closure design.uxf /nsc/bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /nsc/lib/index.ts: -------------------------------------------------------------------------------- 1 | export {run} from '@oclif/command' 2 | -------------------------------------------------------------------------------- /docs/ir.md: -------------------------------------------------------------------------------- 1 | nestscript 指令集手册 2 | ========================= 3 | TODO 4 | -------------------------------------------------------------------------------- /example/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livoras/nestscript/HEAD/example/main -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livoras/nestscript/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livoras/nestscript/HEAD/docs/process.png -------------------------------------------------------------------------------- /docs/parseFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livoras/nestscript/HEAD/docs/parseFunction.png -------------------------------------------------------------------------------- /docs/closure design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livoras/nestscript/HEAD/docs/closure design.png -------------------------------------------------------------------------------- /docs/code optimizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livoras/nestscript/HEAD/docs/code optimizer.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./assembler" 2 | export * from "./vm/vm" 3 | export * from "./codegen" 4 | -------------------------------------------------------------------------------- /nsc/.gitignore: -------------------------------------------------------------------------------- 1 | *-debug.log 2 | *-error.log 3 | /.nyc_output 4 | /dist 5 | /tmp 6 | /yarn.lock 7 | node_modules 8 | .DS_Store -------------------------------------------------------------------------------- /nsc/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ts-node 2 | 3 | require('@oclif/command').run() 4 | .then(require('@oclif/command/flush')) 5 | .catch(require('@oclif/errors/handle')) 6 | -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | const a = () => {} 2 | 3 | Object.defineProperty(a, 'toString', { 4 | value: () => { 5 | return 'OJBK' 6 | }, 7 | }) 8 | 9 | console.log('-----------', a) 10 | -------------------------------------------------------------------------------- /nsc/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /nsc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "importHelpers": true, 5 | "module": "commonjs", 6 | "rootDir": "lib", 7 | "strict": true, 8 | "target": "es2017" 9 | }, 10 | "include": [ 11 | "lib/**/*" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /example/p.ts: -------------------------------------------------------------------------------- 1 | class Variable { 2 | constructor(public v: any) { 3 | } 4 | 5 | public get(): any { 6 | 7 | } 8 | 9 | public set(v: any): void { 10 | this.v = v 11 | } 12 | } 13 | 14 | const f1 = (a: Variable, b: Variable, c: Variable) => { 15 | console.log(a.get() + b.get()) 16 | return () => { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/js.ts: -------------------------------------------------------------------------------- 1 | import { VirtualMachine, createVMFromArrayBuffer } from './vm/vm' 2 | import { generateAssemblyFromJs } from './codegen' 3 | import { parseCodeToProgram } from './assembler' 4 | 5 | export const createVMFromJavaScript = (jsCode: string, ctx: any = {}): VirtualMachine => { 6 | const asmCodes = generateAssemblyFromJs(jsCode) 7 | const buffer = parseCodeToProgram(asmCodes).buffer 8 | return createVMFromArrayBuffer(buffer, ctx) 9 | } 10 | -------------------------------------------------------------------------------- /example/page.nes: -------------------------------------------------------------------------------- 1 | func main() { 2 | VAR R0; 3 | VAR R1; 4 | PUSH "DJH"; 5 | CALL sayHi 1; 6 | 7 | PRINT "HELLO WORLD 2"; 8 | NEW_OBJ R0; 9 | SET_KEY R0 "title" $RET; 10 | SET_KEY R0 "duration" 1000; 11 | 12 | PRINT R0; 13 | PUSH R0; 14 | CALL_CTX "wx" "showToast" 1; 15 | } 16 | 17 | func sayHi(a) { 18 | VAR R0; 19 | PRINT "HELL WORLD"; 20 | PRINT a; 21 | MOV R0 "软羊"; 22 | ADD R0 a; 23 | PRINT R0; 24 | MOV $RET R0; 25 | } 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | # ./nsc/bin/run compile ./example/js.nes ./example/main 3 | ./nsc/bin/run run ./example/main 4 | build-dist: 5 | npx tsc 6 | npx webpack 7 | watch: 8 | npx nodemon -e nes --exec "make run" example 9 | js: 10 | ./nsc/bin/run codegen ./example/main.js ./example/js.nes 11 | ./nsc/bin/run compile ./example/js.nes ./example/main 12 | make run 13 | lodash: 14 | ./nsc/bin/run codegen ./example/lodash.min.js ./example/lodash.nes 15 | ./nsc/bin/run compile ./example/lodash.nes ./example/main 16 | make run 17 | cov: 18 | npm run cov 19 | CODECOV_TOKEN="fd02f663-150e-479f-925d-6f6c2aa634ff" bash <(curl -s https://codecov.io/bash) 20 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { createVMFromJavaScript } from '../src/js' 2 | const chai = require('chai') 3 | const spies = require('chai-spies') 4 | 5 | export const makeSpy = (): any => chai.spy((): void => {}) 6 | 7 | export const tm = (codes: string, ctx: any = {}): any => { 8 | const c = { expect: chai.expect, Date, console, ...ctx, Error, 9 | Array, 10 | Function, 11 | RegExp, 12 | Object, 13 | String, 14 | TypeError, 15 | ArrayBuffer, 16 | DataView, 17 | Infinity, 18 | Math, 19 | Set, 20 | Map, 21 | Boolean, 22 | Buffer, 23 | Number, 24 | Uint8Array, 25 | } 26 | const vm = createVMFromJavaScript(codes, c) 27 | vm.run() 28 | return c 29 | } 30 | -------------------------------------------------------------------------------- /example/fib.nes: -------------------------------------------------------------------------------- 1 | func fib(a) { 2 | PUSH "斐波那契数列"; 3 | CALL_CTX "console" "time" 1; 4 | VAR r0; 5 | VAR r1; 6 | VAR r2; 7 | VAR tmp; 8 | MOV r1 1; 9 | MOV r2 1; 10 | MOV r0 0; 11 | 12 | JL a 3 l2; 13 | SUB a 2; 14 | JMP l1; 15 | 16 | LABEL l0: 17 | MOV tmp r2; 18 | ADD r2 r1; 19 | MOV r1 tmp; 20 | ADD r0 1; 21 | 22 | LABEL l1: 23 | PRINT r2; 24 | JL r0 a l0; 25 | MOV $RET r2; 26 | JMP l3; 27 | 28 | LABEL l2: 29 | PRINT "DIRECT"; 30 | MOV $RET 1; 31 | 32 | LABEL l3: 33 | MOV tmp $RET; 34 | PUSH "斐波那契数列"; 35 | CALL_CTX "console" "timeEnd" 1; 36 | MOV $RET tmp; 37 | } 38 | 39 | func main() { 40 | PUSH "THE RESULT IS"; 41 | CALL_CTX "console" "log" 1; 42 | PUSH 5; 43 | CALL fib 1; 44 | PRINT $RET; 45 | } 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "exclude": ["nsc"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "typeRoots": ["typings/wx", "node_modules/@types"], 7 | "paths": { "*": ["types/*"] }, 8 | "outDir": "build", 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "skipDefaultLibCheck": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "removeComments": true, 14 | "moduleResolution": "node", 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "noEmitHelpers": true, 18 | "importHelpers": true, 19 | "downlevelIteration": true, 20 | "module": "commonjs", 21 | "target": "es6", 22 | "lib": [ 23 | "es2017", 24 | "dom" 25 | ] 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /example/callback.nes: -------------------------------------------------------------------------------- 1 | func main () { 2 | PRINT "HELLO WORLD"; 3 | VAR R0; 4 | VAR R1; 5 | 6 | FUNC R0 sayHi; 7 | PUSH "LOG FROM CONSOLE 1"; 8 | PUSH "LOG FROM CONSOLE 2"; 9 | CALL_CTX "console" "log" 2; 10 | 11 | NEW_ARR R1; 12 | SET_KEY R1 "0" "good"; 13 | SET_KEY R1 "1" "night"; 14 | PUSH R0; 15 | CALL_VAR R1 "forEach" 1; 16 | } 17 | 18 | func sayHi(a, b, c) { 19 | PRINT "RESULT --> WHAT?"; 20 | PRINT a; 21 | PRINT b; 22 | PUSH a; 23 | CALL say 1; 24 | } 25 | 26 | func say(d) { 27 | PRINT "!~~~~~~~~~~~"; 28 | VAR R0; 29 | VAR R1; 30 | VAR LEN; 31 | 32 | MOV R0 0; 33 | MOV_PROP LEN d "length" ; 34 | JMP l1; 35 | LABEL l0: 36 | MOV_PROP R1 d R0; 37 | PRINT R1; 38 | ADD R0 1; 39 | LABEL l1: 40 | JL R0 LEN l0; 41 | PRINT d; 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm i 28 | - run: npm run cov 29 | - run: bash <(curl -s https://codecov.io/bash) 30 | env: 31 | CODECOV_TOKEN: fd02f663-150e-479f-925d-6f6c2aa634ff 32 | CI: true 33 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | 3 | module.exports = { 4 | entry: { 5 | "vm": "./build/src/vm/vm.js", 6 | }, 7 | optimization: { 8 | minimize: false, 9 | // splitChunks: { 10 | // cacheGroups: { 11 | // commons: { 12 | // name: "commons", 13 | // chunks: "initial", 14 | // minChunks: 2, 15 | // minSize: 0 16 | // } 17 | // } 18 | // }, 19 | // chunkIds: "named" // To keep filename consistent between different modes (for example building only) 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.tsx?$/, 25 | use: 'ts-loader', 26 | exclude: /node_modules/, 27 | }, 28 | ], 29 | }, 30 | output: { 31 | path: path.resolve(__dirname, 'dist'), 32 | libraryTarget: "commonjs" 33 | }, 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-eslint-rules", "tslint-sonarts"], 3 | "rules": { 4 | "no-empty": false, 5 | "no-console": false, 6 | "semicolon": [true, "never"], 7 | "object-literal-sort-keys": false, 8 | "ordered-imports": false, 9 | "no-var-requires": false, 10 | "no-implicit-dependencies": false, 11 | "no-ignored-return": false, 12 | "no-dead-store": {"severity": "warning"}, 13 | "object-curly-spacing": [true, "always"], 14 | "max-line-length": [true, {"limit": 120, "ignore-pattern": "^import |^export {(.*?)}"}], 15 | "trailing-comma": [true, {"multiline": "always"}], 16 | "max-file-line-count": [true, 600], 17 | "no-big-function": [true, 200], 18 | "ter-indent": [true, 2], 19 | "no-commented-code": false, 20 | "no-redundant-boolean": true, 21 | "typedef": [true, "call-signature", "arrow-call-signature", "parameter", "property-declaration"] 22 | }, 23 | "linterOptions": { 24 | "exclude": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Livoras 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/tmp.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name typeform 3 | // @namespace http://tampermonkey.net/ 4 | // @version 0.1 5 | // @description try to take over the world! 6 | // @author You 7 | // @match https://*.typeform.com/* 8 | // @grant none 9 | // ==/UserScript== 10 | 11 | (function () { 12 | 'use strict'; 13 | const $id = (i) => document.querySelector(i) 14 | const root = $id('#root') 15 | 16 | const makeEnter = () => { 17 | try { 18 | const buttons = document.querySelectorAll('button') 19 | for (let i = 0; i < buttons.length; i++) { 20 | buttons[1].click() 21 | } 22 | } catch(e) {} 23 | } 24 | 25 | function makeInput1() { 26 | const inputs = document.querySelectorAll('input') 27 | inputs[0].value = 'x9dkdk20xkd9fak2k' 28 | inputs[0].dispatchEvent(new Event('change')) 29 | } 30 | 31 | window.addEventListener('load', () => { 32 | console.log('load') 33 | const timer = setInterval(() => { 34 | if (root.style.cssText) { 35 | clearInterval(timer) 36 | makeEnter() 37 | setTimeout(makeInput1) 38 | } 39 | }, 100) 40 | }) 41 | 42 | })(); -------------------------------------------------------------------------------- /example/test.nes: -------------------------------------------------------------------------------- 1 | func bar(c, b) { 2 | MOV G2 "1"; 3 | VAR R0; 4 | MOV R0 b; 5 | SUB R0 c; 6 | MOV $RET R0; 7 | RET; 8 | } 9 | 10 | func foo(a, b) { 11 | MOV G1 "loop"; 12 | VAR R0; 13 | MOV R0 a; 14 | ADD R0 b; 15 | PUSH R0; 16 | PUSH 3; 17 | CALL bar 2; 18 | } 19 | 20 | func tow(s1, s2) { 21 | MOV $RET s1; 22 | ADD $RET s2; 23 | ADD G1 G2; 24 | RET; 25 | } 26 | 27 | func main() { 28 | GLOBAL G1; 29 | GLOBAL G2; 30 | 31 | VAR R0; 32 | PUSH 2; 33 | PUSH 2; 34 | CALL foo 2; 35 | 36 | PRINT $RET; 37 | PUSH 2; 38 | PUSH 2; 39 | CALL tow 2; 40 | 41 | MOV R0 $RET; 42 | MOV G2 1; 43 | MOV G1 1; 44 | JNE G1 G2 l2; 45 | 46 | LABEL l3: 47 | PRINT R0; 48 | ADD R0 1; 49 | MOV R0 0; 50 | PUSH "check time"; 51 | CALL_CTX "console" "time" 1; 52 | JMP l5; 53 | LABEL l4: 54 | PRINT R0; 55 | MOV_CTX G1 "name.age"; 56 | PUSH "LOG SOMETHING ..."; 57 | CALL_CTX "console" "log" 1; 58 | PUSH "LOG SOMETHING ...2"; 59 | PUSH "LOG SOMETHING ...3"; 60 | CALL_CTX "console" "log" 2; 61 | PRINT G1; 62 | ADD R0 1; 63 | LABEL l5: 64 | JL R0 1000 l4; 65 | PUSH "check time"; 66 | CALL_CTX "console" "timeEnd" 1; 67 | } 68 | -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | NestScript 2 | -------------------------------------------------------------------------------- /nsc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nsc", 3 | "version": "0.0.0", 4 | "author": "Livoras Dai", 5 | "bin": { 6 | "nsc": "./bin/run" 7 | }, 8 | "bugs": "https://github.com/nestscript/nsc/issues", 9 | "dependencies": { 10 | "@oclif/command": "^1.6.1", 11 | "@oclif/config": "^1.15.1", 12 | "@oclif/plugin-help": "^3.1.0", 13 | "tslib": "^1.13.0" 14 | }, 15 | "devDependencies": { 16 | "@oclif/dev-cli": "^1.22.2", 17 | "@types/node": "^10.17.26", 18 | "globby": "^10.0.2", 19 | "ts-node": "^8.10.2", 20 | "typescript": "^3.9.5" 21 | }, 22 | "engines": { 23 | "node": ">=8.0.0" 24 | }, 25 | "files": [ 26 | "/src/*", 27 | "/bin", 28 | "/lib", 29 | "/npm-shrinkwrap.json", 30 | "/oclif.manifest.json", 31 | "tsconfig.json" 32 | ], 33 | "homepage": "https://github.com/nestscript/nsc", 34 | "keywords": [ 35 | "oclif" 36 | ], 37 | "license": "MIT", 38 | "main": "lib/index.js", 39 | "oclif": { 40 | "commands": "./lib/commands", 41 | "bin": "nsc", 42 | "plugins": [ 43 | "@oclif/plugin-help" 44 | ] 45 | }, 46 | "repository": "nestscript/nsc", 47 | "scripts": { 48 | "postpack": "rm -f oclif.manifest.json", 49 | "prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme", 50 | "test": "echo NO TESTS", 51 | "version": "oclif-dev readme && git add README.md" 52 | }, 53 | "types": "lib/index.d.ts" 54 | } 55 | -------------------------------------------------------------------------------- /nsc/lib/commands/codegen.ts: -------------------------------------------------------------------------------- 1 | import { Command, flags } from '@oclif/command' 2 | import path = require("path") 3 | import fs = require("fs") 4 | import { generateAssemblyFromJs } from '../../../src/codegen' 5 | import { parseCodeToProgram } from '../../../src/assembler' 6 | 7 | export default class Codegen extends Command { 8 | static description = 'compile javascript file to nestscript assembly.' 9 | 10 | static examples = [ 11 | `$ nsc codegen main.js main.nes`, 12 | ] 13 | 14 | static flags = { 15 | help: flags.help({ char: 'h' }), 16 | // flag with a value (-n, --name=VALUE) 17 | name: flags.string({ char: 'n', description: 'name to print' }), 18 | // flag with no value (-f, --force) 19 | force: flags.boolean({ char: 'f' }), 20 | } 21 | 22 | static args = [{ name: 'file' }, { name: 'target' }] 23 | 24 | async run() { 25 | const { args, flags } = this.parse(Codegen) 26 | if (!args.file) { 27 | console.log("Please specify source file.") 28 | return 29 | } 30 | 31 | if (!args.target) { 32 | console.log("Please specify target file.") 33 | return 34 | } 35 | 36 | const src = this.getAbsPath(args.file) 37 | const dst = this.getAbsPath(args.target) 38 | const code = fs.readFileSync(src, 'utf-8') 39 | const buffer = generateAssemblyFromJs(code) 40 | fs.writeFileSync(dst, buffer) 41 | console.log("Compile done!") 42 | } 43 | 44 | public getAbsPath(p: string): string { 45 | return path.resolve(path.join(process.cwd(), p)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /nsc/lib/commands/run.ts: -------------------------------------------------------------------------------- 1 | import { Command, flags } from '@oclif/command' 2 | import path = require("path") 3 | import fs = require("fs") 4 | import { createVMFromArrayBuffer } from '../../../src/vm/vm' 5 | 6 | export default class Run extends Command { 7 | static description = 'run executable binary file' 8 | 9 | static examples = [ 10 | `$ nsc compile eg.asm target`, 11 | ] 12 | 13 | static flags = { 14 | help: flags.help({ char: 'h' }), 15 | // flag with a value (-n, --name=VALUE) 16 | name: flags.string({ char: 'n', description: 'name to print' }), 17 | // flag with no value (-f, --force) 18 | force: flags.boolean({ char: 'f' }), 19 | } 20 | 21 | static args = [{ name: 'file' }] 22 | 23 | async run() { 24 | const { args, flags } = this.parse(Run) 25 | if (!args.file) { 26 | console.log("Please specify executable file.") 27 | return 28 | } 29 | const exe = this.getAbsPath(args.file) 30 | const vm = createVMFromArrayBuffer((new Uint8Array(fs.readFileSync(exe)).buffer), { 31 | Array, 32 | Function, 33 | RegExp, 34 | Object, 35 | String, 36 | TypeError, 37 | ArrayBuffer, 38 | DataView, 39 | Uint8Array, 40 | Error, 41 | Set, 42 | Map, 43 | Boolean, 44 | Infinity, 45 | Math, 46 | console, 47 | Buffer, 48 | Number, 49 | process, 50 | Date, 51 | }) 52 | vm.run() 53 | } 54 | 55 | public getAbsPath(p: string): string { 56 | return path.resolve(path.join(process.cwd(), p)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /nsc/lib/commands/compile.ts: -------------------------------------------------------------------------------- 1 | import { Command, flags } from '@oclif/command' 2 | import path = require("path") 3 | import fs = require("fs") 4 | import { generateAssemblyFromJs } from '../../../src/codegen' 5 | import { parseCodeToProgram } from '../../../src/assembler' 6 | 7 | export default class Compile extends Command { 8 | static description = 'compile source javascript file to executable binary file' 9 | 10 | static examples = [ 11 | `$ nsc compile main.js main`, 12 | ] 13 | 14 | static flags = { 15 | help: flags.help({ char: 'h' }), 16 | // flag with a value (-n, --name=VALUE) 17 | name: flags.string({ char: 'n', description: 'name to print' }), 18 | // flag with no value (-f, --force) 19 | force: flags.boolean({ char: 'f' }), 20 | } 21 | 22 | static args = [{ name: 'file' }, { name: 'target' }] 23 | 24 | async run() { 25 | const { args, flags } = this.parse(Compile) 26 | if (!args.file) { 27 | console.log("Please specify source file.") 28 | return 29 | } 30 | 31 | if (!args.target) { 32 | console.log("Please specify target file.") 33 | return 34 | } 35 | 36 | const src = this.getAbsPath(args.file) 37 | const dst = this.getAbsPath(args.target) 38 | const code = fs.readFileSync(src, 'utf-8') 39 | const asm = generateAssemblyFromJs(code) 40 | const buffer = parseCodeToProgram(asm) 41 | fs.writeFileSync(dst, buffer) 42 | console.log("Compile done!") 43 | } 44 | 45 | public getAbsPath(p: string): string { 46 | return path.resolve(path.join(process.cwd(), p)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestscript", 3 | "version": "1.0.5", 4 | "description": "A script nested in JavaScript, dynamically run code in environment without `eval` and `new Function`.", 5 | "main": "src/index.js", 6 | "bin": { 7 | "nsc": "./nsc/bin/run" 8 | }, 9 | "scripts": { 10 | "start": "npx nodemon --watch example/main.js --exec 'make js'", 11 | "cov": "nyc -r lcov -e .ts -x \"*.test.ts\" npm run test", 12 | "test": "mocha --timeout 60000 -r ts-node/register test/**/*.spec.ts", 13 | "watch": "nodemon --watch src --watch test -e ts --exec 'npm test'" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/livoras/nestscript.git" 18 | }, 19 | "author": "daijiahua", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/livoras/nestscript/issues" 23 | }, 24 | "homepage": "https://github.com/livoras/nestscript#readme", 25 | "devDependencies": { 26 | "@types/chai": "^4.2.11", 27 | "@types/estree": "0.0.44", 28 | "@types/mocha": "^8.0.0", 29 | "@types/node": "^14.0.13", 30 | "chai": "^4.2.0", 31 | "chai-spies": "^1.0.0", 32 | "mocha": "7.1.1", 33 | "nodemon": "^2.0.4", 34 | "nyc": "^15.1.0", 35 | "tslib": "^2.0.0", 36 | "tslint": "^6.1.2", 37 | "tslint-eslint-rules": "^5.4.0", 38 | "tslint-sonarts": "^1.9.0", 39 | "webpack": "^4.43.0", 40 | "webpack-cli": "^3.3.12" 41 | }, 42 | "dependencies": { 43 | "acorn": "^8.0.1", 44 | "acorn-walk": "^8.0.0", 45 | "typescript": "^3.9.7", 46 | "@oclif/command": "^1.8.0", 47 | "ts-node": "^8.10.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/scope.ts: -------------------------------------------------------------------------------- 1 | // (window as any).newCount = 0 2 | 3 | export class Scope { 4 | constructor( 5 | public scope: any = {}, 6 | public heap: any[] = [], 7 | public isRestoreWhenChange: boolean = true) { 8 | // (window as any).newCount++ 9 | this.scope.blockNameMap = new Map() 10 | } 11 | 12 | public front(blockName: any): void { 13 | this.scope = Object.setPrototypeOf({ len: this.heap.length }, this.scope) 14 | this.scope.blockName = blockName // 用来销毁用 15 | this.scope.blockNameMap.set(blockName, this.scope) 16 | } 17 | 18 | public back(blockName: any): void { 19 | const targetScope = this.scope.blockNameMap.get(blockName) 20 | // 这里可能有内存泄漏问题,map 里面还存在着中间的 scope 对象 21 | // 但是函数的执行结束以后就会回收掉,似乎不需要考虑太多? 22 | if (this.isRestoreWhenChange) { 23 | const len = targetScope.len 24 | this.heap.splice(len) 25 | } 26 | this.scope = Object.getPrototypeOf(targetScope) 27 | } 28 | 29 | public fork(): Scope { 30 | const scope = Object.setPrototypeOf({ len: this.heap.length }, this.scope) 31 | return new Scope(scope, this.heap, this.isRestoreWhenChange) 32 | } 33 | 34 | public new(key: any): void { 35 | const index = this.heap.length 36 | this.scope[key] = index 37 | this.heap.push(void 0) 38 | } 39 | 40 | public set(key: any, value: any): void { 41 | if (!(key in this.scope)) { 42 | throw new Error('variable is used before decleration') 43 | } 44 | const index = this.scope[key] 45 | this.heap[index] = value 46 | } 47 | 48 | public get(key: any): any { 49 | const index = this.scope[key] 50 | return this.heap[index] 51 | } 52 | 53 | public printScope(): string { 54 | let scope = this.scope 55 | const scopes = [] 56 | while (scope.len !== undefined) { 57 | scopes.push(JSON.stringify(scope)) 58 | scope = Object.getPrototypeOf(scope) 59 | } 60 | return `len ${scopes.length}: ` + scopes.join(' <- ') 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | bin.png 105 | .vscode 106 | build 107 | opt.nes -------------------------------------------------------------------------------- /src/block-chain.ts: -------------------------------------------------------------------------------- 1 | import { VariableType } from './codegen' 2 | 3 | export interface IBlock { 4 | variables: Map 5 | closures: Map 6 | isForceBlock: boolean 7 | } 8 | 9 | export interface IFuncBlock extends IBlock { 10 | params: Map 11 | blockNameIndex: number 12 | } 13 | 14 | export class BlockChain { 15 | public closureCounter: number = 0 16 | private static globalBlockNameIndex: number = 0 17 | 18 | constructor( 19 | public chain: (IBlock | IFuncBlock)[], 20 | public currentFuncBlock?: IFuncBlock, 21 | ) { 22 | if (this.chain.length === 0) { 23 | this.closureCounter = 0 24 | } 25 | } 26 | 27 | public newBlock(variables?: Map): BlockChain { 28 | const block = { 29 | variables: variables || new Map(), 30 | closures: new Map(), 31 | isForceBlock: false, 32 | } 33 | return new BlockChain([ ...this.chain, block], this.currentFuncBlock) 34 | } 35 | 36 | public newFuncBlock(params?: Map): BlockChain { 37 | const funcBlock = { 38 | variables: new Map(), 39 | closures: new Map(), 40 | params: params || new Map(), 41 | blockNameIndex: 0, 42 | isForceBlock: false, 43 | } 44 | return new BlockChain([...this.chain, funcBlock], funcBlock) 45 | } 46 | 47 | // tslint:disable-next-line: cognitive-complexity 48 | public accessName(name: string): boolean { 49 | let i = this.chain.length 50 | // if (name === 'i') { 51 | // console.log(this.chain) 52 | // } 53 | if (name.startsWith('@@f')) { 54 | return false 55 | } 56 | const currentFuncBlock = this.currentFuncBlock 57 | let shouldMakeClosure = false 58 | const isBlockHasName = (b: IBlock | IFuncBlock, n: string): boolean => { 59 | if (b.variables.has(n)) { 60 | return true 61 | } 62 | if (this.isFuncBlock(b) && b.params.has(n)) { 63 | return true 64 | } 65 | return false 66 | } 67 | while (i-- > 0) { 68 | const block = this.chain[i] 69 | // console.log('make fucking closure', name, block, i, this.chain.length) 70 | if (!shouldMakeClosure) { 71 | if (isBlockHasName(block, name)) { 72 | return false 73 | } 74 | if (this.isFuncBlock(block) && block === currentFuncBlock) { 75 | shouldMakeClosure = true 76 | } 77 | continue 78 | } 79 | 80 | const makeClosure = (n: string): void => { 81 | block.closures.set(n, `@c${this.closureCounter++}`) 82 | } 83 | 84 | if (block.variables.has(name)) { 85 | block.variables.set(name, VariableType.CLOSURE) 86 | makeClosure(name) 87 | return true 88 | } 89 | 90 | if (this.isFuncBlock(block) && block.params.has(name)) { 91 | block.params.set(name, VariableType.CLOSURE) 92 | makeClosure(name) 93 | return true 94 | } 95 | } 96 | return false 97 | } 98 | 99 | public isFuncBlock(block: IFuncBlock | IBlock | undefined): block is IFuncBlock { 100 | return !!block && !!(block as any).params 101 | } 102 | 103 | public newName(name: string, kind: 'var' | 'const' | 'let', isForce: boolean = false): void { 104 | const block = this.chain.length === 1 105 | ? this.chain[0] 106 | : kind === 'var' 107 | ? this.currentFuncBlock 108 | : this.chain[this.chain.length - 1] 109 | 110 | // console.log(isForce, '---->', name) 111 | if (this.isFuncBlock(block) && block.params.has(name)) { 112 | if (isForce) { 113 | block.params.delete(name) 114 | } else { 115 | block.variables.delete(name) 116 | return 117 | } 118 | } 119 | block?.variables.set(name, VariableType.VARIABLE) 120 | } 121 | 122 | public newGlobal(name: string, type: VariableType): void { 123 | const block = this.chain[0] 124 | if (!block) { 125 | throw new Error('Root block is not assigned.') 126 | } 127 | block.variables.set(name, type) 128 | } 129 | 130 | public getNameType(name: string): VariableType { 131 | let i = this.chain.length 132 | while (i-- > 0) { 133 | const block = this.chain[i] 134 | let varType = block.variables.get(name) 135 | if (!varType && this.isFuncBlock(block)) { 136 | varType = block.params.get(name) 137 | } 138 | if (varType > 0) { 139 | return varType 140 | } 141 | } 142 | return VariableType.NO_EXIST 143 | } 144 | 145 | public hasName(name: string): boolean { 146 | return this.getNameType(name) > 0 147 | } 148 | 149 | // tslint:disable-next-line: cognitive-complexity 150 | public getName(name: string): string { 151 | let i = this.chain.length 152 | while (i-- > 0) { 153 | const block = this.chain[i] 154 | let varType = block.variables.get(name) 155 | let isParam = false 156 | if (!varType && this.isFuncBlock(block)) { 157 | varType = block.params.get(name) 158 | isParam = true 159 | } 160 | if (!varType) { continue } 161 | if (varType === VariableType.VARIABLE ) { 162 | // if (i === this.chain.length - 1) { 163 | return (isParam ? '.' : '') + name 164 | // } 165 | // else { 166 | // throw new Error(`Variable ${name} should be closure but got normal variable type.`) 167 | // } 168 | } 169 | if (varType === VariableType.CLOSURE) { 170 | if (block.closures.has(name)) { 171 | return '@' + name 172 | // return block.closures.get(name) 173 | } else { 174 | throw new Error(`Closure for ${name} is not allocated.`) 175 | } 176 | } 177 | } 178 | return name 179 | } 180 | 181 | public getCurrentBlock(): IFuncBlock | IBlock { 182 | return this.chain[this.chain.length - 1] 183 | } 184 | 185 | public newBlockName(): any { 186 | if (this.currentFuncBlock) { 187 | return this.currentFuncBlock.blockNameIndex++ 188 | } else { 189 | return BlockChain.globalBlockNameIndex++ 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /example/js.nes: -------------------------------------------------------------------------------- 1 | func @@main() { 2 | VAR %r0; 3 | FUNC rotatingHash @@f0; 4 | FUNC _encryptKey @@f1; 5 | FUNC getRandom @@f2; 6 | FUNC judegeWx @@f3; 7 | FUNC init @@f4; 8 | GLOBAL isWx; 9 | MOV isWx false; 10 | GLOBAL rotatingHash; 11 | GLOBAL _encryptKey; 12 | GLOBAL getRandom; 13 | GLOBAL judegeWx; 14 | GLOBAL init; 15 | CALL_REG init 0 false; 16 | MOV %r0 _encryptKey; 17 | SET_CTX "encryptKey" %r0; 18 | } 19 | func @@f0(string) { 20 | VAR hash; 21 | VAR len; 22 | VAR i; 23 | VAR %r0; 24 | VAR %r1; 25 | VAR %r2; 26 | VAR %r3; 27 | VAR %r4; 28 | MOV %r0 string; 29 | MOV %r1 "length"; 30 | MOV_PROP hash %r0 %r1; 31 | MOV %r0 string; 32 | MOV %r1 "length"; 33 | MOV_PROP len %r0 %r1; 34 | MOV i 0; 35 | LABEL _l0_: 36 | MOV %r0 i; 37 | MOV %r1 len; 38 | LT %r0 %r1; 39 | JF %r0 _l1_; 40 | LABEL _l3_: 41 | MOV %r1 hash; 42 | MOV %r4 7; 43 | SHL %r1 %r4; 44 | MOV %r3 hash; 45 | MOV %r4 18; 46 | SHR %r3 %r4; 47 | XOR %r1 %r3; 48 | MOV %r3 i; 49 | PUSH %r3; 50 | MOV %r3 string; 51 | MOV %r4 "charCodeAt"; 52 | CALL_VAR %r3 %r4 1 false; 53 | MOV %r2 $RET; 54 | XOR %r1 %r2; 55 | MOV hash %r1; 56 | MOV %r2 isWx; 57 | NEG %r2; 58 | JF %r2 _l5_; 59 | MOV %r1 hash; 60 | MOV %r3 1; 61 | ADD %r1 %r3; 62 | JMP _l4_; 63 | LABEL _l5_: 64 | MOV %r1 hash; 65 | LABEL _l4_: 66 | MOV hash %r1; 67 | MOV %r2 isWx; 68 | JF %r2 _l7_; 69 | MOV %r1 hash; 70 | JMP _l6_; 71 | LABEL _l7_: 72 | MOV %r1 hash; 73 | MOV %r3 1; 74 | SHL %r1 %r3; 75 | LABEL _l6_: 76 | MOV hash %r1; 77 | LABEL _l2_: 78 | MOV %r1 i; 79 | ADD %r1 1; 80 | MOV i %r1; 81 | JMP _l0_; 82 | LABEL _l1_: 83 | MOV %r1 hash; 84 | PUSH %r1; 85 | MOV_CTX %r1 "Math"; 86 | MOV %r2 "abs"; 87 | CALL_VAR %r1 %r2 1 false; 88 | MOV %r0 $RET; 89 | MOV $RET %r0; 90 | RET; 91 | } 92 | func @@f1(key) { 93 | VAR tempKey; 94 | VAR md5Timer; 95 | VAR i; 96 | VAR %r0; 97 | VAR %r1; 98 | VAR %r2; 99 | VAR %r3; 100 | MOV tempKey key; 101 | MOV %r1 key; 102 | PUSH %r1; 103 | CALL_REG rotatingHash 1 false; 104 | MOV %r0 $RET; 105 | MOV tempKey %r0; 106 | MOV md5Timer tempKey; 107 | MOV %r1 10; 108 | MOD md5Timer %r1; 109 | MOV %r0 1; 110 | OR md5Timer %r0; 111 | MOV i 0; 112 | LABEL _l8_: 113 | MOV %r0 i; 114 | MOV %r1 md5Timer; 115 | LT %r0 %r1; 116 | JF %r0 _l9_; 117 | LABEL _l11_: 118 | MOV %r3 tempKey; 119 | PUSH %r3; 120 | CALL_CTX 'md5' 1 false; 121 | MOV %r1 $RET; 122 | MOV %r2 ""; 123 | ADD %r1 %r2; 124 | MOV tempKey %r1; 125 | LABEL _l10_: 126 | MOV %r1 i; 127 | ADD %r1 1; 128 | MOV i %r1; 129 | JMP _l8_; 130 | LABEL _l9_: 131 | MOV %r0 tempKey; 132 | MOV $RET %r0; 133 | RET; 134 | } 135 | func @@f2(num) { 136 | VAR %r0; 137 | VAR %r1; 138 | VAR %r2; 139 | VAR %r3; 140 | VAR %r4; 141 | MOV_CTX %r3 "Math"; 142 | MOV %r4 "random"; 143 | CALL_VAR %r3 %r4 0 false; 144 | MOV %r0 $RET; 145 | MOV %r2 num; 146 | MUL %r0 %r2; 147 | MOV %r1 0; 148 | SHR %r0 %r1; 149 | MOV $RET %r0; 150 | RET; 151 | } 152 | func @@f3() { 153 | VAR %r0; 154 | VAR %r1; 155 | VAR %r2; 156 | VAR %r3; 157 | VAR %r4; 158 | VAR %r5; 159 | VAR %r6; 160 | VAR %r7; 161 | VAR %r8; 162 | VAR %r9; 163 | VAR %r10; 164 | VAR %r11; 165 | VAR %r12; 166 | MOV_CTX %r7 "a"; 167 | MOV %r6 %r7; 168 | JF %r7 _l18_; 169 | MOV_CTX %r9 "a"; 170 | MOV %r10 "aldstat"; 171 | MOV_PROP %r8 %r9 %r10; 172 | LG_AND %r6 %r8; 173 | LABEL _l18_: 174 | MOV %r5 %r6; 175 | JF %r6 _l17_; 176 | MOV_CTX %r11 "a"; 177 | MOV %r12 "aldstat"; 178 | MOV_PROP %r9 %r11 %r12; 179 | MOV %r10 "app"; 180 | MOV_PROP %r7 %r9 %r10; 181 | MOV_CTX %r8 "a"; 182 | EQ %r7 %r8; 183 | LG_AND %r5 %r7; 184 | LABEL _l17_: 185 | MOV %r4 %r5; 186 | JF %r5 _l16_; 187 | MOV_CTX %r10 "a"; 188 | MOV %r11 "ignoreUrlList"; 189 | MOV_PROP %r8 %r10 %r11; 190 | MOV %r9 1; 191 | MOV_PROP %r6 %r8 %r9; 192 | MOV %r7 "/authorization/authorization"; 193 | EQ %r6 %r7; 194 | LG_AND %r4 %r6; 195 | LABEL _l16_: 196 | MOV %r3 %r4; 197 | JF %r4 _l15_; 198 | MOV_CTX %r9 "a"; 199 | MOV %r10 "utils"; 200 | MOV_PROP %r7 %r9 %r10; 201 | MOV %r8 "app"; 202 | MOV_PROP %r5 %r7 %r8; 203 | MOV_CTX %r6 "a"; 204 | EQ %r5 %r6; 205 | LG_AND %r3 %r5; 206 | LABEL _l15_: 207 | MOV %r2 %r3; 208 | JF %r3 _l14_; 209 | MOV_CTX %r8 "a"; 210 | MOV %r9 "utils"; 211 | MOV_PROP %r6 %r8 %r9; 212 | MOV %r7 "aliMonitor"; 213 | MOV_PROP %r4 %r6 %r7; 214 | MOV_CTX %r6 "a"; 215 | MOV %r7 "monitor"; 216 | MOV_PROP %r5 %r6 %r7; 217 | EQ %r4 %r5; 218 | LG_AND %r2 %r4; 219 | LABEL _l14_: 220 | MOV %r1 %r2; 221 | JF %r2 _l13_; 222 | MOV_CTX %r5 "a"; 223 | MOV %r6 "getCurrentPageUrlWithArgs"; 224 | MOV_PROP %r3 %r5 %r6; 225 | TYPE_OF %r3; 226 | MOV %r4 "function"; 227 | EQ %r3 %r4; 228 | LG_AND %r1 %r3; 229 | LABEL _l13_: 230 | MOV %r0 %r1; 231 | JF %r1 _l12_; 232 | MOV %r4 "pages"; 233 | PUSH %r4; 234 | MOV_CTX %r6 "a"; 235 | MOV %r7 "getCurrentPageUrlWithArgs"; 236 | CALL_VAR %r6 %r7 0 false; 237 | MOV %r4 $RET; 238 | MOV %r5 "indexOf"; 239 | CALL_VAR %r4 %r5 1 false; 240 | MOV %r2 $RET; 241 | MOV %r3 1; 242 | MINUS %r3; 243 | NE %r2 %r3; 244 | LG_AND %r0 %r2; 245 | LABEL _l12_: 246 | MOV isWx %r0; 247 | RET; 248 | } 249 | func @@f4() { 250 | VAR forTime; 251 | VAR judgeTimer; 252 | VAR i; 253 | VAR %r0; 254 | VAR %r1; 255 | VAR %r2; 256 | MOV %r1 100; 257 | PUSH %r1; 258 | CALL_REG getRandom 1 false; 259 | MOV forTime $RET; 260 | MOV %r0 10; 261 | ADD forTime %r0; 262 | MOV %r0 forTime; 263 | PUSH %r0; 264 | CALL_REG getRandom 1 false; 265 | MOV judgeTimer $RET; 266 | MOV %r0 0; 267 | MOV i %r0; 268 | LABEL _l19_: 269 | MOV %r0 i; 270 | MOV %r1 forTime; 271 | LT %r0 %r1; 272 | JF %r0 _l20_; 273 | LABEL _l22_: 274 | MOV %r1 judgeTimer; 275 | MOV %r2 i; 276 | WEQ %r1 %r2; 277 | JF %r1 _l24_; 278 | CALL_REG judegeWx 0 false; 279 | JMP _l23_; 280 | LABEL _l24_: 281 | LABEL _l23_: 282 | LABEL _l21_: 283 | MOV %r1 i; 284 | ADD %r1 1; 285 | MOV i %r1; 286 | JMP _l19_; 287 | LABEL _l20_: 288 | } 289 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | export const parseCode = (cd: string): string[][] => { 4 | const code = cd.trim() 5 | let operants: string[] = [] 6 | let i = 0 7 | let val = "" 8 | let isInString = false 9 | let oldStringStart: string = '' 10 | const codes: string[][] = [] 11 | 12 | while (i < code.length) { 13 | const c = code[i++] 14 | if ((c === '"' || c === "'") && !isInString) { 15 | oldStringStart = c 16 | isInString = true 17 | val = c 18 | continue 19 | } 20 | 21 | if (isInString) { 22 | if (c === oldStringStart) { 23 | val += c 24 | operants.push(val) 25 | isInString = false 26 | val = '' 27 | } else { 28 | val += c 29 | } 30 | continue 31 | } 32 | 33 | if (c === ';' || c === ':') { 34 | if (val !== "") { 35 | operants.push(val) 36 | val = "" 37 | } 38 | if (operants.length > 0) { 39 | codes.push(operants) 40 | } 41 | operants = [] 42 | continue 43 | } 44 | 45 | if (c.match(/\s/)) { 46 | if (val.length === 0) { 47 | continue 48 | } else { 49 | if (val !== "") { 50 | operants.push(val) 51 | } 52 | val = "" 53 | continue 54 | } 55 | } 56 | 57 | val += c 58 | } 59 | 60 | if (operants.length > 0) { 61 | codes.push(operants) 62 | } 63 | 64 | return codes 65 | } 66 | 67 | export interface IParsedFunction { 68 | instructions: string[][], 69 | params: string[], 70 | functionName: string, 71 | } 72 | 73 | const enum TokenizingState { 74 | INIT, 75 | FUNCTION_NAME, 76 | PARAMING, 77 | PARAMING_ENCLOSING, 78 | FUNCTION_BODY, 79 | } 80 | 81 | export const parseAssembler = (code: string, isRawString: boolean = false): IParsedFunction[] => { 82 | code = code.trim() 83 | let operants: string[] = [] 84 | 85 | let i = 0 86 | let val = "" 87 | let isInString = false 88 | let oldStringStart: string = '' 89 | 90 | let codes: string[][] = [] 91 | const funcs: IParsedFunction[] = [] 92 | let token = '' 93 | let tokenizingState: TokenizingState = TokenizingState.INIT 94 | let currentFunctionInfo: IParsedFunction | null = null 95 | const NO_SET_FUNCTION_INFO_ERROR = 'current function info is not set.' 96 | 97 | const isEmpyty = (c: string): boolean => /\s/.test(c) 98 | let j = 0 99 | let k = 0 100 | let isEscape = false 101 | 102 | while (i < code.length) { 103 | // console.log(tokenizingState, '===>', i, code[i]) 104 | const c = code[i++] 105 | 106 | if (tokenizingState === TokenizingState.INIT) { 107 | if (isEmpyty(c)) { 108 | if (!token) { continue } 109 | if (token !== 'func') { 110 | console.log('->', operants) 111 | throw new Error('INIT Unexpected token ' + token) 112 | } 113 | token = '' 114 | tokenizingState = TokenizingState.FUNCTION_NAME 115 | k++ 116 | // console.log(k, 'coount ') 117 | currentFunctionInfo = { functionName: '', params: [], instructions: [] } 118 | codes = currentFunctionInfo.instructions 119 | } else { 120 | token += c 121 | } 122 | continue 123 | } 124 | 125 | if (tokenizingState === TokenizingState.FUNCTION_NAME) { 126 | if (isEmpyty(c)) { continue } 127 | if (c === '(') { 128 | if (!token) { 129 | throw new Error('Function should have function name') 130 | } else { 131 | if (!currentFunctionInfo) { throw new Error(NO_SET_FUNCTION_INFO_ERROR) } 132 | currentFunctionInfo.functionName = token 133 | token = '' 134 | tokenizingState = TokenizingState.PARAMING 135 | // console.log(tokenizingState, currentFunctionInfo) 136 | } 137 | } else { 138 | token += c 139 | } 140 | continue 141 | } 142 | 143 | if (tokenizingState === TokenizingState.PARAMING) { 144 | if (isEmpyty(c)) { continue } 145 | if (c === ',' || c === ')') { 146 | if (!currentFunctionInfo) { throw new Error(NO_SET_FUNCTION_INFO_ERROR) } 147 | if (c === ',' && !token) { 148 | throw new Error('parameter name should not be empty') 149 | } 150 | if (token) { 151 | currentFunctionInfo.params.push(token) 152 | } 153 | token = '' 154 | if (c === ')') { 155 | tokenizingState = TokenizingState.PARAMING_ENCLOSING 156 | // console.log('--- paraming..', tokenizingState, currentFunctionInfo) 157 | } 158 | } else { 159 | token += c 160 | } 161 | continue 162 | } 163 | 164 | if (tokenizingState === TokenizingState.PARAMING_ENCLOSING) { 165 | if (isEmpyty(c)) { continue } 166 | if (c === '{') { 167 | tokenizingState = TokenizingState.FUNCTION_BODY 168 | } else { 169 | throw new Error("PARAMING_ENCLOSING Unexpected token " + c) 170 | } 171 | continue 172 | } 173 | 174 | // console.log("bodyig --> ", isInString, tokenizingState) 175 | 176 | if ((c === '"' || c === "'") && !isInString) { 177 | oldStringStart = c 178 | isInString = true 179 | val = c 180 | continue 181 | } 182 | 183 | if (isInString) { 184 | if (c === '\\') { 185 | if (isEscape) { 186 | isEscape = false 187 | if (isRawString) { 188 | val += c 189 | } 190 | } else { 191 | isEscape = true 192 | val += c 193 | } 194 | continue 195 | } 196 | 197 | if (c === '"' && isEscape) { 198 | // console.log('--->', val) 199 | val = val.substring(0, val.length - (isRawString ? 0 : 1)) + '"' 200 | isEscape = false 201 | continue 202 | } 203 | 204 | if (c === oldStringStart) { 205 | val += c 206 | operants.push(val) 207 | // console.log("<-----", val) 208 | isInString = false 209 | val = '' 210 | } else { 211 | val += c 212 | } 213 | isEscape = false 214 | continue 215 | } 216 | 217 | if (c === ';' || c === ':') { 218 | if (val !== "") { 219 | operants.push(val) 220 | val = "" 221 | } 222 | if (operants.length > 0) { 223 | codes.push(operants) 224 | } 225 | operants = [] 226 | continue 227 | } 228 | 229 | if (c.match(/\s/)) { 230 | if (val.length === 0) { 231 | continue 232 | } else { 233 | if (val !== "") { 234 | operants.push(val) 235 | } 236 | val = "" 237 | continue 238 | } 239 | } 240 | 241 | if (c === '}') { 242 | j++ 243 | if (operants.length > 0) { 244 | codes.push(operants) 245 | } 246 | if (!currentFunctionInfo) { throw new Error(NO_SET_FUNCTION_INFO_ERROR) } 247 | funcs.push(currentFunctionInfo) 248 | tokenizingState = TokenizingState.INIT 249 | currentFunctionInfo = null 250 | // console.log('functions ====>', funcs) 251 | continue 252 | } 253 | 254 | // console.log(operants) 255 | val += c 256 | } 257 | 258 | return funcs 259 | } 260 | 261 | const test = (): void => { 262 | const c = fs.readFileSync(__dirname + "/../example/js.nes", 'utf-8') 263 | const funcs = parseAssembler(c) 264 | funcs.forEach((f: IParsedFunction): void => { 265 | console.log(f, '-->') 266 | }) 267 | } 268 | 269 | // test() 270 | // console.log(parseCode(`PUSH "DIE WORLD"`)) 271 | // console.log(parseCode(`PUSH "HELLO WORLD"`)) 272 | // console.log(parseCode('MOV R0 1')) 273 | // console.log(parseCode(testProgram)) 274 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable: no-bitwise 2 | // const b = new ArrayBuffer(16) 3 | // const a = new Float64Array(b) 4 | // a[0] = -0.001 5 | // a[1] = -0.002 6 | // const c = new Float64Array(b) 7 | // console.log(a, b, c[0]) 8 | 9 | import { IOperatantType, I } from './vm/vm' 10 | 11 | // const d = new Float64Array(b.slice(8)) 12 | // a[1] = 0.03 13 | // console.log(c, d[0]) 14 | 15 | export const concatBuffer = (buffer1: ArrayBuffer, buffer2: ArrayBuffer): ArrayBuffer => { 16 | const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) 17 | tmp.set(new Uint8Array(buffer1), 0) 18 | tmp.set(new Uint8Array(buffer2), buffer1.byteLength) 19 | return tmp.buffer 20 | } 21 | 22 | /** 23 | * Converts an ArrayBuffer to a String. 24 | * 25 | * @param buffer - Buffer to convert. 26 | * @returns String. 27 | */ 28 | export const arrayBufferToString = (buffer: ArrayBuffer): string => { 29 | return String.fromCharCode.apply(null, Array.from(new Uint16Array(buffer))) 30 | } 31 | 32 | /** 33 | * Converts a String to an ArrayBuffer. 34 | * 35 | * @param str - String to convert. 36 | * @returns ArrayBuffer. 37 | */ 38 | export const stringToArrayBuffer = (str: string): ArrayBuffer => { 39 | const stringLength = str.length 40 | const buffer = new ArrayBuffer(stringLength * 2) 41 | const bufferView = new Uint16Array(buffer) 42 | for (let i = 0; i < stringLength; i++) { 43 | bufferView[i] = str.charCodeAt(i) 44 | } 45 | return buffer 46 | } 47 | 48 | export const getByProp = (obj: any, prop: any): any => { 49 | if (typeof prop === 'string') { 50 | return String(prop).split('.').reduce((o: any, p: string): any => o[p], obj) 51 | } else { 52 | return obj[prop] 53 | } 54 | } 55 | 56 | // const u = { name: { age: "TOMY" } } 57 | // console.time("check") 58 | // for (let i = 0; i < 10000; i++) { 59 | // console.log(u.name.age) 60 | // console.log("LOG SOMETHING...") 61 | // console.log("LOG SOMETHING...3", "LOG SOMETHING...2") 62 | // } 63 | // console.timeEnd("check") 64 | 65 | export const parseStringsArray = (buffer: ArrayBuffer): string[] => { 66 | const strings: string[] = [] 67 | let i = 0 68 | while(i < buffer.byteLength) { 69 | const lentOffset = i + 4 70 | const len = readUInt32(buffer, i, lentOffset) 71 | const start = lentOffset 72 | const end = lentOffset + len * 2 73 | const str = readString(buffer, start, end) 74 | strings.push(str) 75 | i = end 76 | } 77 | return strings 78 | } 79 | 80 | export const readFloat64 = (buffer: ArrayBuffer, from: number, to: number): number => { 81 | return (new Float64Array(buffer.slice(from, to)))[0] 82 | } 83 | 84 | export const readUInt8 = (buffer: ArrayBuffer, from: number, to: number): number => { 85 | // console.log(from, to) 86 | return buffer[from] 87 | // return buffer.slice(from, to)[0] 88 | } 89 | 90 | export const readInt8 = (buffer: ArrayBuffer, from: number, to: number): number => { 91 | return (new Int8Array(buffer.slice(from, to)))[0] 92 | } 93 | 94 | export const readInt16 = (buffer: ArrayBuffer, from: number, to: number): number => { 95 | return (new Int16Array(buffer.slice(from, to)))[0] 96 | } 97 | 98 | export const readUInt16 = (buffer: ArrayBuffer, from: number, to: number): number => { 99 | return (new Uint16Array(buffer.slice(from, to)))[0] 100 | } 101 | 102 | export const readUInt32 = (buffer: ArrayBuffer, from: number, to: number): number => { 103 | return (new Uint32Array(buffer.slice(from, to)))[0] 104 | } 105 | 106 | export const readString = (buffer: ArrayBuffer, from: number, to: number): string => { 107 | return arrayBufferToString(buffer.slice(from, to)) 108 | } 109 | 110 | 111 | const OPERANT_TYPE_MASK = 0b11110000 112 | const OPERANT_BYTE_LEN_MASK = ~OPERANT_TYPE_MASK 113 | 114 | export const createFloat64OperantBuff = (ot: IOperatantType, value: number, forceLength?: number): ArrayBuffer => { 115 | const numBuf = value !== void 0 116 | ? new Float64Array([value]).buffer 117 | : new ArrayBuffer(0) 118 | const byteLength = forceLength || getByteLengthFromFloat64(value) 119 | // console.log('---> byteLength', byteLength) 120 | const head = createOperantHead(ot, byteLength) 121 | return concatBuffer(head, numBuf.slice(8 - byteLength)) 122 | // console.log('-> head', head) 123 | // console.log('-> value', new Uint8Array(numBuf)) 124 | // console.log('-> operant', 'length -> ', numBuf.byteLength, new Uint8Array(operantBuf)) 125 | // return operantBuf 126 | } 127 | 128 | export const createInt32OperantBuff = (ot: IOperatantType, value: number, forceLength?: number): ArrayBuffer => { 129 | const numBuf = value !== void 0 130 | ? new Uint32Array([value]).buffer 131 | : new ArrayBuffer(0) 132 | const byteLength = forceLength || getByteLengthFromInt32(value) 133 | const head = createOperantHead(ot, byteLength) 134 | return concatBuffer(head, numBuf.slice(0, byteLength)) 135 | } 136 | 137 | export const createOperantBuffer = (ot: IOperatantType, value: number, forceLength?: number): ArrayBuffer => { 138 | if (ot === IOperatantType.NUMBER) { 139 | return createFloat64OperantBuff(ot, value, forceLength) 140 | } else { 141 | // return createInt32OperantBuff(ot, value, forceLength) 142 | return createInt32OperantBuff(ot, value, forceLength) 143 | } 144 | } 145 | 146 | export const getOperatantByBuffer = (arrayBuffer: Uint8Array, i: number = 0): [IOperatantType, number, number] => { 147 | const buffer = arrayBuffer 148 | const head = buffer[i++] 149 | const [ot, byteLength] = getOperantTypeAndByteLengthByNum(head) 150 | // console.log('head -> ', head, buffer.slice(i, i + byteLength)) 151 | // const value = getFloat64OperatantValueByBuffer(buffer, i, byteLength) 152 | const value = ot === IOperatantType.NUMBER 153 | ? getFloat64OperatantValueByBuffer(buffer, i, byteLength) 154 | : getInt32OperatantValueByBuffer(buffer, i, byteLength) 155 | return [ot, value, byteLength] 156 | } 157 | 158 | const createOperantHead = (ot: IOperatantType, byteLength: number): ArrayBuffer => { 159 | return new Uint8Array([ot | byteLength]).buffer 160 | } 161 | 162 | // const saveNumberToMinimalByteBuffer = ( 163 | // largeBuf: Float64Array | Int32Array, 164 | // forceLength?: number, 165 | // ): ArrayBuffer => { 166 | // const uint8buf = new Uint8Array(largeBuf.buffer) 167 | // const i = (largeBuf instanceof Float64Array) 168 | // ? getByteLengthFromFloat64() 169 | // // largeBuf[0] = num 170 | // // let i = 0 171 | // // while (uint8buf[i] === 0) { 172 | // // i++ 173 | // // } 174 | // // i = forceLength 175 | // // ? uint8buf.length - forceLength 176 | // // : i 177 | // return uint8buf.slice(i).buffer 178 | // } 179 | 180 | const num64Buf = new Float64Array(1) 181 | const num8For64Buf = new Uint8Array(num64Buf.buffer) 182 | export const getFloat64OperatantValueByBuffer = ( 183 | buffer: Uint8Array, i: number = 0, byteLength: number): number => { 184 | num64Buf[0] = 0 185 | /** setting in this way is faster than `XXBuffer.set` function */ 186 | const s = num8For64Buf.length - byteLength 187 | for (let j = 0; j < byteLength; j++) { 188 | num8For64Buf[s + j] = buffer[i + j] 189 | } 190 | // const b = buffer.slice(i, i + byteLength) 191 | // num8For64Buf.set(b, num8For64Buf.length - byteLength) 192 | return num64Buf[0] 193 | } 194 | 195 | const num32Buf = new Int32Array(1) 196 | const num8For32Buf = new Uint8Array(num32Buf.buffer) 197 | export const getInt32OperatantValueByBuffer = ( 198 | buffer: Uint8Array, i: number = 0, byteLength: number): number => { 199 | num32Buf[0] = 0 200 | for (let j = 0; j < byteLength; j++) { 201 | num8For32Buf[j] = buffer[j + i] 202 | } 203 | // const b = buffer.slice(i, i + byteLength) 204 | return num32Buf[0] 205 | } 206 | 207 | export const getOperantTypeAndByteLengthByNum = (head: number): [IOperatantType, number] => { 208 | const ot = head & OPERANT_TYPE_MASK 209 | const byteLength = head & OPERANT_BYTE_LEN_MASK 210 | return [ot, byteLength] 211 | } 212 | 213 | export const getByteLengthFromInt32 = (num: number): number => { 214 | const n32Buf = new Int32Array([num]) 215 | const n8For32Buf = new Uint8Array(n32Buf.buffer) 216 | let i = n8For32Buf.length 217 | while (i-- > 0) { 218 | if (n8For32Buf[i] > 0) { 219 | break 220 | } 221 | } 222 | return i + 1 223 | } 224 | 225 | export const getByteLengthFromFloat64 = (num: number): number => { 226 | const n64Buf = new Float64Array([num]) 227 | const n8For64Buf = new Uint8Array(n64Buf.buffer) 228 | let i = 0 229 | while (n8For64Buf[i] === 0) { 230 | i++ 231 | } 232 | return 8 - i 233 | } 234 | 235 | export const getOperantName = (o: I): string => { 236 | return I[o] 237 | } 238 | 239 | export class CategoriesPriorityQueue { 240 | public categories: { [x in number]: T[] } = {} 241 | constructor() {} 242 | 243 | public push(item: T, p: number = 100): void { 244 | const list: T[] = this.categories[p] || [] 245 | list.push(item) 246 | this.categories[p] = list 247 | } 248 | 249 | public clear(): void { 250 | this.categories = {} 251 | } 252 | 253 | *[Symbol.iterator] (): IterableIterator { 254 | const ll: T[][] = Object 255 | .entries(this.categories) 256 | .sort(([x], [y]): number => Number(x) - Number[y]) 257 | .map(([_, list]): any => list) 258 | for (const l of ll) { 259 | for (const i of l) { 260 | yield i 261 | } 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/optimizer.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable: max-classes-per-file 2 | 3 | import { parseAssembler, IParsedFunction } from './parser' 4 | import { I, IOperatantType } from './vm/vm' 5 | const fs = require('fs') 6 | 7 | class CodeBlock { 8 | public toCodeBlocks = new Set() 9 | constructor( 10 | public codes: string[][] = [], 11 | public index: number = 0, 12 | ) {} 13 | } 14 | 15 | class CodeBlockGraph { 16 | public blockMap: Map = new Map() 17 | public codes: string[][] = [] 18 | public functionName: string = '' 19 | public params: string[] = [] 20 | 21 | constructor(public func: IParsedFunction) { 22 | this.functionName = func.functionName 23 | this.codes = func.instructions 24 | this.params = func.params 25 | this.parse() 26 | } 27 | 28 | public parse(): void { 29 | let codeBlock = new CodeBlock([], 0) 30 | let oldCodeBlock = codeBlock 31 | const labelsMap = new Map() 32 | this.codes.forEach((code: string[], i): void => { 33 | if (code[0].match(/LABEL/i)) { 34 | labelsMap.set(code[1], i) 35 | } 36 | }) 37 | this.blockMap.set(0, codeBlock) 38 | 39 | this.codes.forEach((code: string[], i): void => { 40 | const op = code[0] 41 | const isJumpCode: boolean = ['JMP', 'JE', 'JNE', 'JG', 'JL', 'JGE', 'JLE', 'JF', 'JIF'].includes(op) 42 | const isLabelCode: boolean = !!op.match(/LABEL/i) 43 | if (isLabelCode) { 44 | if (codeBlock.codes.length !== 0) { 45 | oldCodeBlock = codeBlock 46 | codeBlock = new CodeBlock([], i) 47 | this.blockMap.set(codeBlock.index, codeBlock) 48 | } else { 49 | codeBlock.index = i 50 | } 51 | oldCodeBlock.toCodeBlocks.add(i) 52 | codeBlock.codes.push(code) 53 | } else if (isJumpCode) { 54 | codeBlock.codes.push(code) 55 | const label = code[code.length - 1] 56 | if (!labelsMap.has(label)) { 57 | throw new Error(`cannot find label ${label}`) 58 | } 59 | codeBlock.toCodeBlocks.add(labelsMap.get(label)!) 60 | codeBlock.toCodeBlocks.add(i + 1) 61 | oldCodeBlock = codeBlock 62 | codeBlock = new CodeBlock([], i + 1) 63 | this.blockMap.set(codeBlock.index, codeBlock) 64 | } else { 65 | codeBlock.codes.push(code) 66 | } 67 | }) 68 | } 69 | 70 | public print(): void { 71 | for (const [_, cb] of this.blockMap.entries()) { 72 | console.log('--->', cb.index, cb.codes, cb.toCodeBlocks) 73 | } 74 | } 75 | 76 | public optimize(): void { 77 | [...this.blockMap.entries()].forEach(([i, codeBlock]): void => { 78 | codeBlock.codes = optimizeCodes(codeBlock.codes) 79 | }) 80 | } 81 | 82 | public output(): any { 83 | const codeString = [...this.blockMap.entries()] 84 | .sort(([i]: any, [j]: any): any => i - j) 85 | .reduce((ret: string[][], [i, codeBlock]): string[][] => { 86 | return [...ret, ...codeBlock.codes] 87 | }, []) 88 | .filter((c): boolean => !!c) 89 | .map((c: string[]): string => { 90 | if (c[0] === 'LABEL') { 91 | return c.join(' ') + ':\n' 92 | } 93 | return ' ' + c.join(' ') + ';\n' 94 | }).join('') 95 | return `func ${this.functionName} (${this.params.join(', ')}) { 96 | ${codeString.trimRight()}\n}` 97 | } 98 | } 99 | 100 | export const optimizeCode = (code: string): string => { 101 | // return code 102 | const ret = parseAssembler(code, true).reduce((r: string, func: IParsedFunction): string => { 103 | const cb = new CodeBlockGraph(func) 104 | cb.optimize() 105 | return r + '\n' + cb.output() 106 | }, '').trim() 107 | // fs.writeFileSync('opt.nes', ret, 'utf-8') 108 | return ret 109 | // console.log(funcs) 110 | // console.log(func.instructions.join('\n')) 111 | } 112 | 113 | const enum OU { 114 | SET, 115 | GET, 116 | BOTH, 117 | } 118 | 119 | /** 120 | * 'code string'.split(',').map((s) => s.trim()).filter((s) => !!s).map((s) => `[I.${s}]: []`).join(',\n') 121 | */ 122 | const SET_GET = [OU.SET, OU.GET] 123 | const BOTH_GET = [OU.BOTH, OU.GET] 124 | const THREE_GET = [OU.GET, OU.GET, OU.GET] 125 | const TWO_GET = [OU.GET, OU.GET] 126 | const codeToUseAge: { [x in any]: OU[] } = { 127 | [I.MOV]: SET_GET, 128 | [I.ADD]: BOTH_GET, 129 | [I.SUB]: BOTH_GET, 130 | [I.MUL]: BOTH_GET, 131 | [I.DIV]: BOTH_GET, 132 | [I.MOD]: BOTH_GET, 133 | [I.EXP]: BOTH_GET, 134 | [I.INC]: [OU.BOTH], 135 | [I.DEC]: [OU.BOTH], 136 | [I.LT]: BOTH_GET, 137 | [I.GT]: BOTH_GET, 138 | [I.EQ]: BOTH_GET, 139 | [I.LE]: BOTH_GET, 140 | [I.GE]: BOTH_GET, 141 | [I.NE]: BOTH_GET, 142 | [I.WEQ]: BOTH_GET, 143 | [I.WNE]: BOTH_GET, 144 | [I.LG_AND]: BOTH_GET, 145 | [I.LG_OR]: BOTH_GET, 146 | [I.AND]: BOTH_GET, 147 | [I.OR]: BOTH_GET, 148 | [I.XOR]: BOTH_GET, 149 | [I.SHL]: BOTH_GET, 150 | [I.SHR]: BOTH_GET, 151 | [I.ZSHR]: BOTH_GET, 152 | [I.JMP]: [OU.GET], 153 | [I.JE]: THREE_GET, 154 | [I.JNE]: THREE_GET, 155 | [I.JG]: THREE_GET, 156 | [I.JL]: THREE_GET, 157 | [I.JIF]: TWO_GET, 158 | [I.JF]: TWO_GET, 159 | [I.JGE]: THREE_GET, 160 | [I.JLE]: THREE_GET, 161 | [I.PUSH]: [OU.GET], 162 | [I.POP]: [], 163 | [I.CALL]: [], // ignore 164 | [I.PRINT]: [OU.GET], 165 | [I.RET]: [], 166 | [I.PAUSE]: [], 167 | [I.EXIT]: [], 168 | [I.CALL_CTX]: THREE_GET, 169 | [I.CALL_VAR]: [...THREE_GET, OU.GET], 170 | [I.CALL_REG]: THREE_GET, 171 | [I.MOV_CTX]: SET_GET, 172 | [I.MOV_PROP]: [OU.SET, OU.GET, OU.GET], 173 | [I.SET_CTX]: [OU.GET, OU.GET], 174 | [I.NEW_OBJ]: [OU.SET], 175 | [I.NEW_ARR]: [OU.SET], 176 | [I.SET_KEY]: [OU.GET, OU.GET, OU.GET], 177 | [I.FUNC]: SET_GET, 178 | [I.ALLOC]: [OU.GET], 179 | [I.PLUS]: [OU.BOTH], 180 | [I.MINUS]: [OU.BOTH], 181 | [I.NOT]: [OU.BOTH], 182 | [I.VOID]: [OU.BOTH], 183 | [I.DEL]: [OU.BOTH], 184 | [I.NEG]: [OU.BOTH], 185 | [I.TYPE_OF]: [OU.BOTH], 186 | [I.INST_OF]: BOTH_GET, 187 | [I.IN]: BOTH_GET, 188 | [I.MOV_THIS]: [OU.SET], 189 | [I.NEW_REG]: [OU.SET, OU.GET, OU.GET], 190 | [I.TRY]: [OU.GET], 191 | [I.TRY_END]: [], 192 | [I.THROW]: [OU.GET], 193 | [I.FORIN]: [OU.BOTH, OU.GET], 194 | [I.FORIN_END]: [], 195 | [I.BREAK_FORIN]: [], 196 | [I.CONT_FORIN]: [], 197 | [I.MOV_ARGS]: [OU.SET], 198 | } 199 | 200 | const IGNORE_INS = [ 201 | I.NEW_ARR, 202 | I.NEW_OBJ, 203 | ] 204 | 205 | const enum ValueType { 206 | NUMBER, 207 | REGISTESR, 208 | STRING, 209 | OTHER, 210 | } 211 | 212 | interface ICandidate { 213 | codeIndex: number, 214 | operator: string, 215 | value: string, 216 | valueType: ValueType, 217 | usages: { codeIndex: number, position: number }[], 218 | } 219 | 220 | const optimizeCodes = (codes: any[]): any[] => { 221 | const candidates: Map = 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 | ![游戏图片](https://github.com/livoras/nestscript-demo/blob/master/demo.png?raw=true) 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 | --------------------------------------------------------------------------------