{"id":13670898,"url":"https://github.com/livoras/nestscript","last_synced_at":"2025-05-02T09:31:39.826Z","repository":{"id":42802298,"uuid":"271265558","full_name":"livoras/nestscript","owner":"livoras","description":"A script nested in JavaScript, dynamically run code in environment without `eval` and `new Function`.","archived":false,"fork":false,"pushed_at":"2023-03-04T23:33:43.000Z","size":3293,"stargazers_count":84,"open_issues_count":21,"forks_count":14,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-04-16T10:56:23.481Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/livoras.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-06-10T12:02:32.000Z","updated_at":"2024-02-04T16:52:18.000Z","dependencies_parsed_at":"2024-01-14T16:33:18.331Z","dependency_job_id":null,"html_url":"https://github.com/livoras/nestscript","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/livoras%2Fnestscript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/livoras%2Fnestscript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/livoras%2Fnestscript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/livoras%2Fnestscript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/livoras","download_url":"https://codeload.github.com/livoras/nestscript/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252015851,"owners_count":21680836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T09:00:52.466Z","updated_at":"2025-05-02T09:31:39.364Z","avatar_url":"https://github.com/livoras.png","language":"JavaScript","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"120\" src=\"https://github.com/livoras/nestscript/blob/master/docs/logo.png?raw=true\"\u003e\n  \u003cbr/\u003e\n\n  \u003ca href=\"https://github.com/livoras/nestscript/actions\"\u003e\n    \u003cimg src='https://github.com/livoras/nestscript/workflows/Node.js%20CI/badge.svg'\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://codecov.io/github/livoras/nestscript?branch=master\"\u003e\n    \u003cimg src=\"https://codecov.io/github/livoras/nestscript/coverage.svg?branch=master\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://david-dm.org/livoras/nestscript\"\u003e\n    \u003cimg src=\"https://david-dm.org/livoras/nestscript.svg\"\u003e\n  \u003c/a\u003e\n\n  \u003c!-- \u003ca src=\"https://badge.fury.io/js/nestscript\"\u003e\n    \u003cimg src=\"https://badge.fury.io/js/nestscript.svg\"\u003e\n  \u003c/a\u003e --\u003e\n\n\u003c!-- [![Dependency Status](https://david-dm.org/livoras/nestscript.svg)](https://david-dm.org/livoras/nestscript)\n[![npm version](https://badge.fury.io/js/nestscript.svg)](https://badge.fury.io/js/nestscript)  --\u003e\n\n\u003c/p\u003e\n\n\n# nestscript\n\nA script nested in JavaScript, dynamically runs code in environment without `eval` and `new Function`.\n\n`nestscript` 可以让你在没有 `eval` 和 `new Function` 的 JavaScript 环境中运行二进制指令文件。\n\n原理上就是把 JavaScript 先编译成 `nestscript` 的 IR 指令，然后把指令编译成二进制的文件。只要在环境中引入使用 JavaScript 编写的 `nestscript` 的虚拟机，都可以执行 `nestscript` 的二进制文件。你可以把它用在 Web 前端、微信小程序等场景。\n\n它包含三部分：\n\n1. **代码生成器**：将 JavaScript 编译成 `nestscript` 中间指令。\n2. **汇编器**：将中间指令编译成可运行在 `nestscript` 虚拟机的二进制文件。\n3. **虚拟机**：执行汇编器生成的二进制文件。\n\n理论上你可以将任意语言编译成 `nestscript` 指令集，但是目前 `nestscript` 只包含了一个代码生成器，目前支持将 JavaScript 编译成 `nestscript` 指令。\n\n\n\u003cimg src=\"https://github.com/livoras/nestscript/blob/master/docs/process.png?raw=true\"\u003e\n\n目前支持单文件 ES5 编译，并且已经成功编译并运行一些经典的 JavaScript 第三方库，例如 moment.js、lodash.js、mqtt.js。并且有日活百万级产品在生产环境使用。\n\n## Installation\n\n```bash\nnpm install nestscript\n```\n\n## 快速尝试\n\n### 新建一个文件，例如 `main.js`：\n\n```javascript\nconsole.log(\"hello world\")\n```\n\n### 编译成二进制：\n\n```bash\nnpx nsc compile main.js main\n```\n\n这会将 `main.js` 编译成 `main` 二进制文件\n\n### 通过虚拟机运行二进制：\n\n```bash\nnpx nsc run main\n```\n\n会看到终端输出 `hello world`。这个 `main` 二进制文件，可以在任何一个包含了 nestscript 虚拟机，也就是 `dist/vm.js` 文件的环境中运行。\n\n例如你可以把这个 `main` 二进制分发到 CND，然后通过网络下载到包含 `dist/vm.js` 文件的小程序中动态执行。\n\n## Example\n\n为了展示它的作用，我们编译了一个开源的的伪 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)\n\n![游戏图片](https://github.com/livoras/nestscript-demo/blob/master/demo.png?raw=true)\n\n查看源代码（[nestscript-demo](https://github.com/livoras/nestscript-demo)）可以看到，我们在网页中引入了一个虚拟机 `vm.js`。游戏的主体逻辑都通过 nestscript 编译成了一个二进制文件 `game`，然后通过 `fetch`下载这个二进制文件，然后给到虚拟机解析、运行。\n\n```html\n\u003c!-- 引入 nestscript 虚拟机 --\u003e\n\u003cscript src=\"./nestscript/dist/vm.js\"\u003e\u003c/script\u003e\n\n\u003c!-- 下载二进制文件 `game`，并且用虚拟机运行 --\u003e\n\u003cscript\u003e\n   fetch('./game').then((res) =\u003e {\n      res.arrayBuffer().then((data) =\u003e {\n         const vm = createVMFromArrayBuffer(data, window)\n         vm.run()\n      })\n   })\n\u003c/script\u003e\n```\n\n达到的效果和原来的开源的游戏效果完全一致\n\n* 原来用 JS 运行的效果：[http://codeincomplete.com/projects/racer/v4.final.html](http://codeincomplete.com/projects/racer/v4.final.html)\n* 用虚拟机运行 nestscript 二进制的效果：[https://livoras.github.io/nestscript-demo/index.html](https://livoras.github.io/nestscript-demo/index.html)\n\n### demo 原理\n\n编译的过程非常简单，只不过是把原来游戏的几个逻辑文件合并在一起：\n\n```bash\ncat common.js stats.js main.js \u003e game.js\n```\n\n然后用 nestscript 编译成二进制文件：\n\n```bash\nnsc compile game.js game\n```\n\n再在 html 中引入虚拟机 vm.js，然后通过网络请求获取 game 二进制文件，接着运行二进制：\n\n```html\n\u003c!-- 引入 nestscript 虚拟机 --\u003e\n\u003cscript src=\"./nestscript/dist/vm.js\"\u003e\u003c/script\u003e\n\n\u003c!-- 下载二进制文件 `game`，并且用虚拟机运行 --\u003e\n\u003cscript\u003e\n   fetch('./game').then((res) =\u003e {\n      res.arrayBuffer().then((data) =\u003e {\n         const vm = createVMFromArrayBuffer(data, window)\n         vm.run()\n      })\n   })\n\u003c/script\u003e\n```\n\n## API\n\n### nsc compile [source.js] [binary]\n\n把 JavaScript 文件编译成二进制，例如 `npx nsc compile game.js game`。注意，目前仅支持 ES5 的语法。\n\n### nsc run [binary]\n\n通过 nestscript 虚拟机运行编译好的二进制，例如 `npx nsc run game`\n\n### createVMFromArrayBuffer(buffer: ArrayBuffer, context: any)\n\n由 `dist/vm.js` 提供的方法，解析编译好的二进制文件，返回一个虚拟机实例，并且可以准备执行。例如:\n\n```javascript\nconst vm = createVMFromArrayBuffer(buffer, context)\n```\n\n`buffer` 指的是二进制用 JavaScript 的 ArrayBuffer 的展示形式；\n\n`context` 相当于传给虚拟机的一个全局运行环境，因为虚拟机的运行环境和外部分离开来的。它对 window、global 这些已有的 JavaScript 环境无知，所以需要手动传入一个 `context` 来告知虚拟机目前的全局环境。虚拟机的全局变量、属性都会从传入的 `contenxt` 中拿到。\n\n例如，如果代码只用到全局的 `Date` 属性，那么除了可以直接传入 `window` 对象以外，还可以这么做：\n\n```javascript\nconst vm = createVMFromArrayBuffer(buffer, { Date })\n```\n\n### VirtualMachine::run()\n\n`createVMFromArrayBuffer` 返回的虚拟机实例有 `run` 方法可以运行代码：\n\n```javascript\nconst vm = createVMFromArrayBuffer(buffer, { Date })\nvm.run()\n```\n\n## nestscript 指令集\n\n```\nMOV, ADD, SUB, MUL, DIV, MOD,\nEXP, NEG, INC, DEC,\n\nLT, GT, EQ, LE, GE, NE,\nLG_AND, LG_OR, XOR, NOT, SHL, SHR,\n\nJMP, JE, JNE, JG, JL, JIF, JF,\nJGE, JLE, PUSH, POP, CALL, PRINT,\nRET, PAUSE, EXIT,\n\nCALL_CTX, CALL_VAR, CALL_REG, MOV_CTX, MOV_PROP,\nSET_CTX,\nNEW_OBJ, NEW_ARR, SET_KEY,\nFUNC, ALLOC,\n```\n\n详情请见 [nestscript 指令集手册](https://github.com/livoras/nestscript/blob/master/docs/ir.md)。\n\n例如使用指令编写的，斐波那契数列：\n\n```javascript\nfunc @@main() {\n    CLS @fibonacci;\n    REG %r0;\n    FUNC $RET @@f0;\n    FUNC @fibonacci @@f0;\n    MOV %r0 10;\n    PUSH %r0;\n    CALL_REG @fibonacci 1 false;\n}\nfunc @@f0(.n) {\n    REG %r0;\n    REG %r1;\n    REG %r2;\n    REG %r3;\n    MOV %r0 .n;\n    MOV %r1 1;\n    LT %r0 %r1;\n    JF %r0 _l1_;\n    MOV %r1 0;\n    MOV $RET %r1;\n    RET;\n    JMP _l0_;\nLABEL _l1_:\nLABEL _l0_:\n    MOV %r0 .n;\n    MOV %r1 2;\n    LE %r0 %r1;\n    JF %r0 _l3_;\n    MOV %r1 1;\n    MOV $RET %r1;\n    RET;\n    JMP _l2_;\nLABEL _l3_:\nLABEL _l2_:\n    MOV %r2 .n;\n    MOV %r3 1;\n    SUB %r2 %r3;\n    PUSH %r2;\n    CALL_REG @fibonacci 1 false;\n    MOV %r0 $RET;\n    MOV %r2 .n;\n    MOV %r3 2;\n    SUB %r2 %r3;\n    PUSH %r2;\n    CALL_REG @fibonacci 1 false;\n    MOV %r1 $RET;\n    ADD %r0 %r1;\n    MOV $RET %r0;\n    RET;\n}\n```\n\n## TODO\n- [ ] `export`，`import` 模块支持\n- [ ] `class` 支持\n- [ ] 中间代码优化\n  - [x] 基本中间代码优化\n  - [ ] 属性访问优化\n- [ ] 文档：\n  - [ ] IR 指令手册\n  - [x] 安装文档\n  - [x] 使用手册\n  - [x] 使用 demo\n- [x] `null`, `undefined` keyword\n- [x] 正则表达式\n- [x] label 语法\n- [x] `try catch`\n  - [x] try catch block\n  - [x] error 对象获取\n- [x] ForInStatement\n- [x] 支持 function.length\n\n* * *\n\n## Change Log\n### 2020-10-10\n* 函数调用的时候延迟 new Scope 和 scope.fork 可以很好提升性能（~500ms）\n\n### 2020-10-09\n* 性能优化：不使用 `XXXBuffer.set` 从 buffer 读取指令速度更快\n\n### 2020-09-18\n* 解决 try catch 调用栈退出到 catch 的函数的地方\n\n### 2020-09-08\n* 重新设计闭包、普通变量的实现方式，使用 scope chain、block chain\n* 实现块级作用域\n* 使用块级作用域实现 `error` 参数在 `catch` 的使用\n\n### 2020-08-25\n* 闭包的形式应该是:\n  * FUNC 每次都返回一个新的函数，并且记录上一层的 closure table\n  * 调用的时候根据旧的 closure table 构建新的 closure table \n\n### 2020-08-21\n* fix 闭包生成的顺序问题\n* 编译第三方库 moment.js, moment.min.js, lodash.js, lodash.min.js 成功并把编译加入测试\n\n### 2020-08-20\n* 编译 moment.js 成功\n* fix if else 语句的顺序问题\n\n### 2020-08-19\n* ForInStatement\n\n### 2020-08-14\n* 编译 lodash 成功\n\n### 2020-08-14\n* `arguments` 参数支持\n\n### 2020-08-12\n* fix 闭包问题\n* 继续编译 lodash：发现没有 try catch 的实现\n\n### 2020-08-11\n* 继续编译 lodash，发现了运行时闭包声明顺序导致无法获取闭包的 bug\n\n### 2020-08-10\n* 编译 lodash 成功(运行失败)\n* 给函数参数增加闭包声明\n* UpdateExpression 的前后缀表达存储\n\n### 2020-08-06\n* 完成 label 语法：循环、block label\n\n### 2020-08-05\n* `null`, `undefined`\n* 正则表达式字面量\n\n### 2020-08-03\n* `while`, `do while`, `continue` codegen\n* 更多测试\n\n### 2020-07-31\n* 第一版 optimizer 完成\n\n### 2020-07-30\n* 设计代码优化器的流程图\n\n### 2020-07-29\n* 把操作数的字节数存放在类型的字节末端，让操作数的字节数量可以动态获取\n* 对于 Number 类型使用 Float64 来存储，对于其他类型的操作数用 Int32 存储\n* 可以较好地压缩二进制程序的大小.\n\n### 2020-07-27\n* 重新设计操作数的生成规则\n\n### 2020-07-23\n* 函数的调用有几种情况\n  * vm 调用自身函数\n  * vm 调用外部函数\n  * 外部调用 vm 的函数\n* 所以：\n  * vm 在调用函数的时候需要区分是那个环境的函数（函数包装 + instanceof）\n    * 如果是自身的函数，不需要对参数进行操作\n    * 如果是外部函数，需要把已经入栈的函数出栈再传给外部函数\n  * 内部函数在被调用的时候，需要区分是那个环境调用的（NumArgs 包装 + instanceof）\n    * 如果来自己的调用，不需要进行特别的操作\n    * 如果是来自外部的调用，需要把参数入栈，并且要嵌入内部循环等待虚拟机函数结束以后再返回\n* 让函数可以正确绑定 this，不管是 vm 内部还是外部的\n\n### 2020-07-22\n* 代码生成中表达式结果的处理原则：所有没有向下一层传递 s.r0 的都要处理 s.r0\n* 三目运算符 A ? B : C 的 codegen（复用 IfStatement）\n\n### 2020-07-21\n* 自动包装 @@main 函数，这样就不需要主动提供 main 函数，更接近 JS\n\n### 2020-07-20\n* 更多测试\n* 完成 +=, -=, *=, /= 操作符\n\n### 2020-07-17\n* 新增测试 \u0026 CI\n* uinary expression 的实现：+, -, ~, !, void, delete\n\n### 2020-07-16\n* 逻辑表达式 \"\u0026\u0026\" 和 \"||\" 的 codegen 和虚拟机实现\n* 完成闭包在虚拟机中的实现\n\n### 2020-07-15\n* 完成闭包变量的标记方式：内层函数“污染”外层的方式\n* 重构代码生成的方式，使用函数数组延迟代码生成，这样可以在标记完闭包变量以后再进行 codegen\n* 设计闭包变量和普通变量的标记方式“@xxx”表示闭包变量，“%xxx”表示普通自动生成的寄存器\n* 下一步设计闭包变量在汇编器和虚拟机中的生成和调取机制\n\n### 2020-07-13\n* 设计闭包的实现\n* 实现 `CALL_REG R0 3` 指令，可以把函数缓存到变量中随后进行调用\n\n### 2020-07-10\n* 支持回调函数的 codegen\n* 完成基本的 JS -\u003e 汇编的转化和运行\n* 循环 codegen\n* 三种值的更新和获取\n  * Identifier\n    * context\n    * variables\n  * Memeber\n\n### 2020-07-09\n* 完成二进制表达式的 codegen\n* 完成简单的赋值语句\n* 完成对象属性访问的 codegen\n* if 语句的 codegen\n\n### 2020-07-03\n* 确定使用动态分配 \u0026 释放寄存器方案\n* 表达式计算值存储到寄存器方案，寄存器名称外层生成、传入、释放\n\n### 2020-06-22\n* 开始使用 acorn 解析 ast，准备把 ast 编译成 IR 指令\n\n### 2020-06-19\n* `FUNC R0 sayHi`: 构建 JS 函数封装 `sayHi` 函数并存放到 R0 寄存器，可以用作 JS 的回调参数，见 `example/callback.nes`\n* `CALL_VAR R0 \"forEach\" 1`: 调用寄存器里面存值的某个方法\n* `MOV_PROP R0 R1 \"length\"`: 将 R1 寄存器的值的 \"length\" 的值放到 R0 寄存器中\n\n### 2020-06-18\n* 完成\n  * `NEW_ARR R0`: 字面量数组\n  * `NEW_OBJ R0`: 字面量对象\n  * `SET_KEY R0 \"0\" \"hello\"`: 设置某个寄存器里面的 key value 值\n  * `CALL_CTX \"console\" \"log\" 1`: 调用 ctx 里面的某个函数方法\n  * `MOV_CTX R0 \"console.log\"`: 把 ctx 某个值移动到寄存器\n\n### 2020-06-17\n* 完成基本的汇编器和虚拟机\n* 完成命令行工具 nsc，可以 `nsc compile src dest` 将文本代码 -\u003e 二进制文件，并且用 `nsc run file` 执行\n* 斐波那契数列计算例子\n* 编译打包成第三方包\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flivoras%2Fnestscript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flivoras%2Fnestscript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flivoras%2Fnestscript/lists"}