├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json ├── src ├── abstractcontext.js ├── anonymous-function.js ├── environment.js ├── evalerror.js ├── evaluator.js ├── identifiererror.js ├── namespace.js ├── recursion-function.js ├── s-lex.js ├── s-parser.js ├── scope.js ├── syntexerror.js └── user-define-function.js └── test ├── test-evaluator.js ├── test-s-lex.js ├── test-s-parser.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Hema Shushu (hippospark@gmail.com) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (Practice) Toy LISP - JS 2 | 3 | 4 | 5 | 6 | 7 | - [(Practice) Toy LISP - JS](#practice-toy-lisp-js) 8 | - [单元测试](#单元测试) 9 | - [程序示例](#程序示例) 10 | - [一般函数的调用](#一般函数的调用) 11 | - [匿名函数](#匿名函数) 12 | - [带尾部调用优化的函数调用](#带尾部调用优化的函数调用) 13 | - [语法](#语法) 14 | 15 | 16 | 17 | 练习单纯使用 JS 编写简单的 _玩具 LISP_ 解析器。 18 | 19 | > 注:本项目是阅读和学习《Building an Interpreter from scratch》时的随手练习,并无实际用途。程序的原理、讲解和代码的原始出处请移步 http://dmitrysoshnikov.com/courses/essentials-of-interpretation/ (这个教程的作者非常用心,有能力的请多多支持作者) 20 | 21 | ## 单元测试 22 | 23 | ```bash 24 | $ npm test 25 | ``` 26 | 27 | 或者 28 | 29 | ```bash 30 | $ node test/test.js 31 | ``` 32 | 33 | ## 程序示例 34 | 35 | 程序的运行方法可以参考测试文件 `test/test-evaluator.js`。 36 | 37 | ### 一般函数的调用 38 | 39 | 计算斐波那契数: 40 | 41 | ```clojure 42 | (defn fib (i) 43 | (if (native.i64.eq i 1) 44 | 1 45 | (if (native.i64.eq i 2) 46 | 2 47 | (native.i64.add (fib (native.i64.sub i 1)) (fib (native.i64.sub i 2))) 48 | ) 49 | ) 50 | ) 51 | (fib 8) 52 | ``` 53 | 54 | ### 匿名函数 55 | 56 | ```clojure 57 | (defn accumulate (count) 58 | (do 59 | (let internalLoop 60 | (fn (i result) 61 | (if (native.i64.eq i 0) 62 | result 63 | (internalLoop (native.i64.sub i 1) (native.i64.add i result)) 64 | ) 65 | ) 66 | ) 67 | (internalLoop count 0) 68 | ) 69 | ) 70 | (accumulate 100) 71 | ``` 72 | 73 | ### 带尾部调用优化的函数调用 74 | 75 | ```clojure 76 | (defnr sumToOneHundred (i accu) 77 | (if (native.i64.gt_s i 100) 78 | (break accu) 79 | (do 80 | (let next (native.i64.add i 1)) 81 | (let sum (native.i64.add accu i)) 82 | (recur next sum) 83 | ) 84 | ) 85 | ) 86 | (sumToOneHundred 1 0) 87 | ``` 88 | 89 | ## 语法 90 | 91 | ::TODO -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { SLex } from './src/s-lex.js'; 2 | import { SParser } from './src/s-parser.js'; 3 | 4 | import { EvalError } from './src/evalerror.js'; 5 | import { SyntaxError } from './src/syntexerror.js'; 6 | import { IdentifierError } from './src/identifiererror.js'; 7 | 8 | import { UserDefineFunction } from './src/user-define-function.js'; 9 | import { AnonymousFunction } from './src/anonymous-function.js'; 10 | import { RecursionFunction } from './src/recursion-function.js'; 11 | 12 | import { Environment } from './src/environment.js'; 13 | import { AbstractContext } from './src/abstractcontext.js'; 14 | import { Namespace } from './src/namespace.js'; 15 | import { Scope } from './src/scope.js'; 16 | 17 | import { Evaluator } from './src/evaluator.js'; 18 | 19 | export { 20 | SLex, 21 | SParser, 22 | 23 | EvalError, 24 | SyntaxError, 25 | IdentifierError, 26 | 27 | UserDefineFunction, 28 | AnonymousFunction, 29 | RecursionFunction, 30 | 31 | Environment, 32 | AbstractContext, 33 | Namespace, 34 | Scope, 35 | 36 | Evaluator, 37 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xiaoxuan-lang-ir-interpreter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "node ./test/test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/hemashushu/xiaoxuan-lang-ir-interpreter-js.git" 13 | }, 14 | "author": "", 15 | "license": "Apache-2.0", 16 | "bugs": { 17 | "url": "https://github.com/hemashushu/xiaoxuan-lang-ir-interpreter-js/issues" 18 | }, 19 | "homepage": "https://github.com/hemashushu/xiaoxuan-lang-ir-interpreter-js#readme" 20 | } -------------------------------------------------------------------------------- /src/abstractcontext.js: -------------------------------------------------------------------------------- 1 | import { IdentifierError } from './identifiererror.js'; 2 | 3 | /** 4 | * 标识符容器有两种: 5 | * - namespace 6 | * 标识符就是用户自定义函数的名称,对于一些解析器,常量 7 | * 也会作为标识符挂靠在命名空间里,而对于编译成字节码或者本地代码的编译器, 8 | * 常量会在编译的预处理阶段处理,并不会挂靠在命名空间里。 9 | * - 表达式块,或者匿名函数的上下文 10 | * 标识符就是局部变量。 11 | */ 12 | class AbstractContext { 13 | constructor() { 14 | this.identifiers = new Map(); 15 | } 16 | 17 | /** 18 | * 19 | * @param {*} name 标识符的名称 20 | * @param {*} value 值的可能值有: 21 | * - 字面量,即 i32/i64/f32/f64 22 | * - 本地函数,比如 add/sub,是一个 JavaScript `Function` 对象 23 | * - 用户函数,是一个 JavaScript Object,结构:{parameters, bodyExp, context} 24 | * 其中 context 是定义该函数时的上下文(对于用户自定义函数来说,上下文就是其所在的 namespace)。 25 | * @returns 26 | */ 27 | defineIdentifier(name, value) { 28 | // 在同一个命名空间之内不允许重名的标识符 29 | if (this.exist(name)) { 30 | throw new IdentifierError( 31 | 'IDENTIFIER_ALREADY_EXIST', 32 | { name: name }, 33 | `Identifier "${name}" has already exists`); 34 | } 35 | 36 | this.identifiers.set(name, value); 37 | return value; 38 | } 39 | 40 | exist(name) { 41 | // 42 | } 43 | 44 | getIdentifier(name) { 45 | // 46 | } 47 | } 48 | 49 | export { AbstractContext }; -------------------------------------------------------------------------------- /src/anonymous-function.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 匿名函数(也叫做 Lambda) 3 | */ 4 | class AnonymousFunction { 5 | constructor(parameters, bodyExp, context) { 6 | this.parameters = parameters; 7 | this.bodyExp = bodyExp; 8 | this.context = context; // 定义函数时所在的作用域 9 | } 10 | } 11 | 12 | export { AnonymousFunction }; -------------------------------------------------------------------------------- /src/environment.js: -------------------------------------------------------------------------------- 1 | import { SyntaxError } from './syntexerror.js'; 2 | import { IdentifierError } from './identifiererror.js'; 3 | import { Namespace } from './namespace.js'; 4 | 5 | 6 | /** 7 | * 运行环境 8 | * 9 | * 存储和管理 namespace 10 | * 11 | * - namePath 是 namespace 的路径,其中 namePath 的第一部分是 module 名称。 12 | * - fullName 是 identifier 的全称,fullName = namePath + '.' + identifierName 13 | */ 14 | class Environment { 15 | constructor() { 16 | this.namespaces = new Map(); 17 | } 18 | 19 | /** 20 | * 创建并返回 namespace 对象 21 | * 22 | * 如果指定的 namespace 已存在,则返回已有的 namespace 对象。 23 | * 24 | * @param {*} namePath 25 | */ 26 | createNamespace(namePath) { 27 | let namespace = this.namespaces.get(namePath); 28 | if (namespace === undefined) { 29 | // 创建 Namespace 对象 30 | namespace = new Namespace() 31 | this.namespaces.set(namePath, namespace); 32 | } 33 | return namespace; 34 | } 35 | 36 | /** 37 | * 通过指定的 namePath 获取对应的 namespace 对象 38 | * 39 | * 如果指定的 namespace 不存在,则抛出 IdentifierError 40 | * 41 | * @param {*} namePath 42 | */ 43 | getNamespace(namePath) { 44 | let namespace = this.namespaces.get(namePath); 45 | if (namespace === undefined) { 46 | throw new IdentifierError( 47 | 'NAMESPACE_NOT_FOUND', 48 | { namePath }, 49 | 'Namespace not found'); 50 | } 51 | return namespace; 52 | } 53 | 54 | /** 55 | * 通过标识符全称获取标识符 56 | * 57 | * 如果指定的标识符所在的 namespace 不存在,则抛出 IdentifierError.NAMESPACE_NOT_FOUND 58 | * 如果指定的标识符不存在,则抛出 IdentifierError.IDENTIFIER_ALREADY_EXIST 59 | * 60 | * @param {*} fullName 61 | */ 62 | getIdentifierByFullName(fullName) { 63 | let { namePath, identifierName } = Environment.getNamePathAndIdentifierName(fullName); 64 | let namespace = this.getNamespace(namePath); 65 | return namespace.getIdentifier(identifierName); 66 | } 67 | 68 | /** 69 | * 解析出标识符全称的 namePath 和 identifierName 部分。 70 | * 71 | * @param {*} fullName 72 | * @returns 73 | */ 74 | static getNamePathAndIdentifierName(fullName) { 75 | let pos = fullName.lastIndexOf('.'); 76 | if (pos <= 0 || pos === fullName.length - 1) { 77 | throw new SyntaxError('Invalid identifier fullname.'); 78 | } 79 | 80 | let namePath = fullName.substring(0, pos); 81 | let identifierName = fullName.substring(pos + 1); 82 | return { namePath, identifierName }; 83 | } 84 | 85 | /** 86 | * 将下面两种路径解析为一般的绝对路径。 87 | * 88 | * - `use` 语句当中的 fullName 或者 namePath 89 | * - 带有相对路径的标识符 fullName 90 | * 91 | * @param {*} fullName 92 | * @param {*} moduleName 93 | * @param {*} currentNamePath 当前 namespace 的 namePath 94 | */ 95 | static normalizeFullname(fullName, moduleName, currentNamespaceNamePath) { 96 | if (fullName.startsWith('module.')) { 97 | return moduleName + fullName.substring('module'.length); 98 | 99 | } else if (fullName.startsWith('current.')) { 100 | return currentNamespaceNamePath + fullName.substring('current'.length); 101 | 102 | } else if (fullName.startsWith('parent.')) { 103 | let remainPath = fullName; 104 | let prefixPath = currentNamespaceNamePath; 105 | do { 106 | remainPath = remainPath.substring('parent.'.length); 107 | let lastDotPos = prefixPath.lastIndexOf('.'); 108 | if (lastDotPos < 0) { 109 | throw new SyntaxError('RELATIVE_PATH_ERROR', { relativePath: remainPath }, 'Relative path out of range.'); 110 | } 111 | prefixPath = prefixPath.substring(0, lastDotPos); 112 | } while (remainPath.startsWith('parent.')) 113 | return prefixPath + '.' + remainPath; 114 | 115 | } else { 116 | return fullName; 117 | } 118 | } 119 | } 120 | 121 | export { Environment }; -------------------------------------------------------------------------------- /src/evalerror.js: -------------------------------------------------------------------------------- 1 | class EvalError extends Error { 2 | constructor(code, data, message) { 3 | super(message); 4 | this.code = code; 5 | this.data = data; 6 | } 7 | } 8 | 9 | export { EvalError }; -------------------------------------------------------------------------------- /src/evaluator.js: -------------------------------------------------------------------------------- 1 | import { SLex } from './s-lex.js'; 2 | import { SParser } from './s-parser.js'; 3 | 4 | import { EvalError } from './evalerror.js'; 5 | import { SyntaxError } from './syntexerror.js'; 6 | import { IdentifierError } from './identifiererror.js'; 7 | 8 | import { UserDefineFunction } from './user-define-function.js'; 9 | import { AnonymousFunction } from './anonymous-function.js'; 10 | import { RecursionFunction } from './recursion-function.js'; 11 | 12 | import { Environment } from './environment.js'; 13 | import { AbstractContext } from './abstractcontext.js'; 14 | import { Namespace } from './namespace.js'; 15 | import { Scope } from './scope.js'; 16 | 17 | class Evaluator { 18 | constructor() { 19 | let environment = new Environment(); 20 | Evaluator.addNativeFunctionNamespace(environment); 21 | Evaluator.addBuiltinFunctionNamespace(environment) 22 | 23 | // 设置 eval() 方法的默认命名空间为 `user` 24 | this.defaultNamespace = Evaluator.addDefaultNamespace(environment); 25 | 26 | this.environment = environment; 27 | } 28 | 29 | /** 30 | * 加载一个模块的文本代码 31 | * 32 | * - 支持 `use` 语句 33 | * - 支持相对路径 34 | * 35 | * @param {*} text 36 | */ 37 | loadModuleFromString(moduleName, text) { 38 | let tokens = SLex.fromString(text); 39 | let list = SParser.parse(tokens); 40 | // TODO:: 41 | // 处理 `use` 表达式 42 | } 43 | 44 | /** 45 | * 解析一条表达式的文本,并执行,然后返回表达式的值 46 | * 47 | * - 不支持 `use` 语句 48 | * - 不支持相对路径 49 | * 50 | * 默认命名空间 `user`。 51 | * 52 | * @param {*} singleExpText 53 | * @returns 54 | */ 55 | evalFromString(singleExpText) { 56 | let tokens = SLex.fromString(singleExpText); 57 | let list = SParser.parse(tokens); 58 | return this.eval(list, this.defaultNamespace); 59 | } 60 | 61 | evalFromStringMultiExps(multiExpsText) { 62 | let tokens = SLex.fromString('(' + multiExpsText + ')'); 63 | let lists = SParser.parse(tokens); 64 | 65 | let result; 66 | for(let list of lists) { 67 | result = this.eval(list, this.defaultNamespace); 68 | } 69 | return result; 70 | } 71 | 72 | /** 73 | * 执行一个表达式,返回表达式的值 74 | * 75 | * @param {*} exp 76 | * @param {*} context 77 | * @returns 78 | */ 79 | eval(exp, context) { 80 | // 字面量 81 | // 只支持 i32/i64/f32/f64 82 | // 83 | // 123 84 | // 3.14 85 | // 6.626e-34 86 | if (Evaluator.isNumber(exp)) { 87 | return exp; 88 | } 89 | 90 | // 标识符 91 | // 不包含空格的字符串 92 | // 93 | // foo 94 | // filter 95 | if (Evaluator.isIdentifier(exp)) { 96 | return this.getIdentifier(exp, context); 97 | } 98 | 99 | // 表达式的第一个元素,一般是函数名称或者关键字 100 | let op = exp[0]; 101 | 102 | // 定义常量 103 | // 返回值:标识符的值 104 | // 105 | // 语法: 106 | // (const identifier-name value) 107 | // 108 | // 常量仅可以在 namespace 里定义 109 | if (op === 'const') { 110 | Evaluator.assertNumberOfParameters('const', exp.length - 1, 2); 111 | 112 | if (!(context instanceof Namespace)) { 113 | throw new SyntaxError( 114 | 'INVALID_CONST_EXPRESSION_PLACE', 115 | {}, 116 | 'Const expressions can only be defined in namespaces'); 117 | } 118 | 119 | let [_, name, value] = exp; 120 | return context.defineIdentifier( 121 | name, 122 | this.eval(value, context)); 123 | } 124 | 125 | // 定义局部标识符(局部变量) 126 | // 返回值:标识符的值 127 | // 128 | // 语法: 129 | // (let identifier-name value) 130 | // 131 | // 变量只可以在 scope 里定义 132 | if (op === 'let') { 133 | Evaluator.assertNumberOfParameters('let', exp.length - 1, 2); 134 | 135 | if (!(context instanceof Scope)) { 136 | throw new SyntaxError( 137 | 'INVALID_LET_EXPRESSION_PLACE', 138 | {}, 139 | 'Let expressions can only be defined in scopes'); 140 | } 141 | 142 | let [_, name, value] = exp; 143 | return context.defineIdentifier( 144 | name, 145 | this.eval(value, context)); 146 | } 147 | 148 | if (op === 'set') { 149 | Evaluator.assertNumberOfParameters('set', exp.length - 1, 2); 150 | 151 | if (!(context instanceof Scope)) { 152 | throw new SyntaxError( 153 | 'INVALID_SET_EXPRESSION_PLACE', 154 | {}, 155 | 'Set expressions can only be used in scopes'); 156 | } 157 | 158 | let [_, name, value] = exp; 159 | return context.updateIdentifierValue( 160 | name, 161 | this.eval(value, context)); 162 | } 163 | 164 | // 命名空间表达式 165 | // 返回值:命名空间的 namespace 对象 166 | // 167 | // 语法: 168 | // (namespace name 169 | // (...) 170 | // (...) 171 | // ) 172 | if (op === 'namespace') { 173 | // 为 namespace 创建一个单独的上下文 174 | // 如此一来平行的多个命名空间就可以不相互影响 175 | let [_, namePath, ...exps] = exp; 176 | 177 | const childContext = this.environment.createNamespace(namePath); 178 | return this.evalExps(exps, childContext); 179 | } 180 | 181 | 182 | // 表达式块 183 | // 返回值:最后一个表达式的值 184 | // 185 | // 语法: 186 | // (do 187 | // (...) 188 | // (...) 189 | // ) 190 | if (op === 'do') { 191 | // 为语句块创建一个单独的作用域, 192 | // 如此一来平行的多个语句块就可以不相互影响 193 | const childContext = new Scope(context); 194 | let exps = exp.slice(1); 195 | return this.evalExps(exps, childContext); 196 | } 197 | 198 | // 流程控制 ———— 条件表达式 199 | // 返回值:条件值为 true 时的表达式的值 200 | // 201 | // 语法: 202 | // (if condition value-when-true value-when-false) 203 | if (op === 'if') { 204 | Evaluator.assertNumberOfParameters('if', exp.length - 1, 3); 205 | let [_, condition, trueExp, falseExp] = exp; 206 | // 约定以整数 1 作为逻辑 true 207 | if (this.eval(condition, context) === 1) { 208 | return this.eval(trueExp, context); 209 | } else { 210 | return this.eval(falseExp, context); 211 | } 212 | } 213 | 214 | // 循环表达式 215 | // 216 | // 语法: 217 | // (loop (param1 param2 ...) (init_value1 init_value2 ...) (...)) 218 | // 219 | // loop 后面跟着一个变量列表和一个初始值列表,然后是循环主体。 220 | // 变量的数量可以是零个。 221 | // 222 | // 当循环主体返回的值为: 223 | // * (1 value1 value2 ...) 时表示使用后面的值更新变量列表,然后再执行一次循环主体 224 | // 如果循环的变量数为零,则在数字 1 后面不需要其他数值。 225 | // * (0 value) 时表示退出循环主体,并以后面的值作为循环表达式的返回值 226 | // 为了确保表达式始终有返回值,数字 0 后面必须有一个数值。 227 | // 228 | // 为了明确目的,在语法上规定需要使用 `recur` 和 `break` 两个表达式构建返回值。 229 | // - (recur value1 value2 ...) `recur` 表达式返回 (1 value1 value2 ...) 230 | // recur 表达式需要零个或多个参数 231 | // - (break value) `break` 表达式返回 (0 value) 232 | // break 表达式需要一个参数 233 | // 234 | // 注意: 235 | // * 如果使用 `defnr` 定义函数(相对于 defn),也会隐含一个 loop 结构,即函数本身就是一个 loop 结构; 236 | // * `break` 和 `recur` 只能存在 `defnr` 和 `loop` 表达式之内的最后一句,在加载源码时会有语法检查。 237 | // * 如果循环主体里有 `if` 分支表达式,需要确保每一个分支的最后一句 238 | // 必须是 `recur` 或者 `break`,在加载源码时会有语法检查。; 239 | // * 循环表达式隐含地创建了一个自己的作用域 240 | // 241 | // `loop...recur` 语句借鉴了 Clojure loop 语句 (https://clojuredocs.org/clojure.core/loop) 242 | // 不过因为 Clojure 是用户级语言,它在非循环分支里不需要明写类似 break 的语句。 243 | 244 | if (op === 'loop') { 245 | Evaluator.assertNumberOfParameters('loop', exp.length - 1, 3); 246 | 247 | let [_, parameters, initialValues, bodyExp] = exp; 248 | return this.evalLoop(bodyExp, parameters, initialValues, context); 249 | } 250 | 251 | // 返回第一个元素的值为 0 的列表 252 | // 253 | // 语法: 254 | // (break loop-expression-return-value) 255 | if (op === 'break') { 256 | Evaluator.assertNumberOfParameters('break', exp.length - 1, 1) 257 | let returnValue = this.eval(exp[1], context); 258 | return [0, returnValue]; 259 | } 260 | 261 | // 返回第一个元素的值为 1 的列表 262 | // 263 | // 语法: 264 | // (recur value1, value2, ...) 265 | if (op === 'recur') { 266 | let argExps = exp.slice(1); 267 | let args = argExps.map(argExp => { 268 | return this.eval(argExp, context); 269 | }); 270 | return [1, ...args]; 271 | } 272 | 273 | // 用户自定义函数(也就是一般函数) 274 | // 275 | // 语法: 276 | // (defn name (param1 param2 ...) (...)) 277 | // 278 | // * 用户自定义函数只能在 namespace 里定义 279 | // * 用户自定义函数隐含地创建了自己的作用域 280 | 281 | if (op === 'defn') { 282 | Evaluator.assertNumberOfParameters('defn', exp.length - 1, 3); 283 | 284 | if (!(context instanceof Namespace)) { 285 | throw new SyntaxError( 286 | 'INVALID_DEFN_EXPRESSION_PLACE', 287 | {}, 288 | 'Defn expressions can only be defined in namespaces'); 289 | } 290 | 291 | let [_, name, parameters, bodyExp] = exp; 292 | 293 | // 因为闭包(closure)的需要,函数对象需要包含当前的上下文 294 | let userDefineFunc = new UserDefineFunction( 295 | name, 296 | parameters, 297 | bodyExp, 298 | context 299 | ); 300 | 301 | return context.defineIdentifier(name, userDefineFunc); 302 | } 303 | 304 | // 含有递归调用的用户自定义函数 305 | // 306 | // 语法: 307 | // (defnr name (param1 param2 ...) (...)) 308 | // 309 | // `defnr` 表达式的结构跟 `loop` 一样,即: 310 | // - 函数的最后一句必须是 `break` 或者 `recur` 311 | // - 如果函数有多个分支,必须保证每个分支的最后一句必须是 `break` 或者 `recur`, 312 | // 在加载源码时会有语法检查。 313 | // 314 | // * 用户自定义函数只能在 namespace 里定义 315 | // * 用户自定义函数隐含地创建了自己的作用域 316 | 317 | if (op === 'defnr') { 318 | Evaluator.assertNumberOfParameters('defnr', exp.length - 1, 3); 319 | 320 | if (!(context instanceof Namespace)) { 321 | throw new SyntaxError( 322 | 'INVALID_DEFN_EXPRESSION_PLACE', 323 | {}, 324 | 'Defnr expressions can only be defined in namespaces'); 325 | } 326 | 327 | let [_, name, parameters, bodyExp] = exp; 328 | 329 | // 因为闭包(closure)的需要,函数对象需要包含当前的上下文 330 | let recursionFunction = new RecursionFunction( 331 | name, 332 | parameters, 333 | bodyExp, 334 | context 335 | ); 336 | 337 | return context.defineIdentifier(name, recursionFunction); 338 | } 339 | 340 | // 匿名函数(即所谓 Lambda) 341 | // 匿名函数在一般函数内部定义,可以作为函数的返回值,或者作为参数传递给另外一个函数。 342 | // 343 | // 语法: 344 | // (fn (param1 param2 ...) (...)) 345 | // 346 | // * 匿名函数隐含地创建了自己的作用域 347 | if (op === 'fn') { 348 | Evaluator.assertNumberOfParameters('fn', exp.length - 1, 2); 349 | let [_, parameters, bodyExp] = exp; 350 | 351 | // 因为闭包(closure)的需要,函数对象需要包含当前的上下文 352 | let anonymousFunc = new AnonymousFunction( 353 | parameters, 354 | bodyExp, 355 | context 356 | ); 357 | 358 | return anonymousFunc; 359 | } 360 | 361 | // 函数调用 362 | if (Array.isArray(exp)) { 363 | // 列表的第一个元素可能是一个函数名称,也可能是一个子列表,先对第一个元素求值 364 | let opValue = this.eval(op, context) 365 | 366 | if (typeof opValue === 'function') { // 本地函数 367 | let args = exp.slice(1); 368 | Evaluator.assertNumberOfParameters(op, args.length, opValue.length); 369 | 370 | let argValues = args.map(a => { 371 | return this.eval(a, context); 372 | }); 373 | 374 | return opValue(...argValues); 375 | 376 | } else if (opValue instanceof UserDefineFunction) { // 用户定义函数 377 | let args = exp.slice(1); 378 | let { name, parameters, bodyExp, context: functionContext } = opValue; 379 | return this.evalFunction(name, bodyExp, parameters, args, functionContext, context); 380 | 381 | } else if (opValue instanceof AnonymousFunction) { // 匿名函数 382 | let args = exp.slice(1); 383 | let { parameters, bodyExp, context: functionContext } = opValue; 384 | return this.evalFunction('lambda', bodyExp, parameters, args, functionContext, context); 385 | 386 | } else if (opValue instanceof RecursionFunction) { // 递归函数 387 | let args = exp.slice(1); 388 | let { name, parameters, bodyExp, context: functionContext } = opValue; 389 | return this.evalRecursionFunction(name, bodyExp, parameters, args, functionContext, context); 390 | 391 | } else { 392 | throw new EvalError( 393 | 'IDENTIFIER_NOT_A_FUNCTION', 394 | { name: op }, 395 | `The specified identifier is not a function: "${op}"`); 396 | } 397 | } 398 | 399 | // 未知的表达式 400 | throw new SyntaxError( 401 | 'INVALID_EXPRESSION', 402 | { exp: exp }, 403 | `Invalid expression`); 404 | } 405 | 406 | /** 407 | * 执行多个表达式,返回最后一个表达式的值 408 | * 409 | * @param {*} exps 410 | * @param {*} context 411 | * @returns 412 | */ 413 | evalExps(exps, context) { 414 | let result; 415 | exps.forEach(exp => { 416 | result = this.eval(exp, context); 417 | }); 418 | return result; 419 | } 420 | 421 | evalLoop(exp, parameters, args, context) { 422 | while (true) { 423 | Evaluator.assertNumberOfLoopArgs(parameters.length, args.length); 424 | 425 | // 循环表达式隐含地创建了一个自己的作用域 426 | const childContext = new Scope(context); 427 | 428 | // 添加实参 429 | parameters.forEach((name, idx) => { 430 | childContext.defineIdentifier( 431 | name, 432 | this.eval(args[idx], context)); 433 | }); 434 | 435 | let result = this.eval(exp, childContext); 436 | 437 | if (result[0] === 0) { 438 | // break the loop 439 | if (result.length !== 2) { 440 | throw new SyntaxError('REQUIRE_LOOP_RETURN_ONE_VALUE', 441 | { actual: result.length - 1, expect: 1 }, 442 | `Require loop return one value`); 443 | } 444 | 445 | // 因为语法规定返回值必须由 `break` 或者 `recur` 语句构建,而这两个 446 | // 语句已经求值了各个参数,所以这里不需要再次求值。 447 | return result[1]; 448 | 449 | } else { 450 | // recur the loop 451 | // update the args and run the loop body again 452 | 453 | // 因为语法规定返回值必须由 `break` 或者 `recur` 语句构建,而这两个 454 | // 语句已经求值了各个参数,所以这里不需要再次求值。 455 | args = result.slice(1); 456 | } 457 | } 458 | } 459 | 460 | evalFunction(name, exp, parameters, args, functionContext, context) { 461 | Evaluator.assertNumberOfParameters(name, args.length, parameters.length); 462 | 463 | // 函数隐含地创建了一个自己的作用域 464 | let activationContext = new Scope( 465 | //context // 动态环境,函数内的外部变量的值会从调用栈开始层层往上查找 466 | functionContext // 静态环境,函数内的外部变量的值只跟定义该函数时的上下文有关 467 | ) 468 | 469 | // 添加实参 470 | parameters.forEach((name, idx) => { 471 | activationContext.defineIdentifier( 472 | name, 473 | this.eval(args[idx], context)); 474 | }); 475 | 476 | // 函数的 body 有可能是一个普通表达式也可能是一个 `do` 语句块表达式, 477 | // 为了避免遇到 `do` 语句块表达式又创建一个作用域(虽然也没有错误), 478 | // 可以在这里提前检测是否为 `do` 表达式,然后直接调用 `evalBlock` 方法。 479 | return this.eval(exp, activationContext); 480 | } 481 | 482 | evalRecursionFunction(name, exp, parameters, args, functionContext, context) { 483 | while (true) { 484 | let result = this.evalFunction(name, exp, parameters, args, functionContext, context); 485 | 486 | if (result[0] === 0) { 487 | // break the loop 488 | if (result.length !== 2) { 489 | throw new SyntaxError('REQUIRE_RECURSION_FUNCTION_RETURN_ONE_VALUE', 490 | { actual: result.length - 1, expect: 1 }, 491 | `Require recursion function return one value`); 492 | } 493 | 494 | // 因为语法规定返回值必须由 `break` 或者 `recur` 语句构建,而这两个 495 | // 语句已经求值了各个参数,所以这里不需要再次求值。 496 | return result[1]; 497 | 498 | } else { 499 | // recur the loop 500 | // update the args and run the loop body again 501 | 502 | // 因为语法规定返回值必须由 `break` 或者 `recur` 语句构建,而这两个 503 | // 语句已经求值了各个参数,所以这里不需要再次求值。 504 | args = result.slice(1); 505 | } 506 | } 507 | } 508 | 509 | /** 510 | * 创建 `native` 命名空间,并加入虚拟机直接支持的指令 511 | * 函数名称保持与 WASM VM 指令名称一致 512 | * 513 | * 对于 i64/i32/f32/f64 各个数据类型都有它们对应的子命名空间: 514 | * 515 | * - native.i64.add 是 i64 加法 516 | * - native.i32.add 是 i32 加法 517 | * - native.f32.add 是 f32 加法 518 | * - native.f64.add 是 f64 加法 519 | * 520 | */ 521 | static addNativeFunctionNamespace(environment) { 522 | 523 | /** 524 | * i64 算术运算 525 | * 526 | * - add 加 527 | * - sub 减 528 | * - mul 乘 529 | * - div_s 除 530 | * - div_u 无符号除 531 | * - rem_s 余 532 | * - rem_u 无符号余 533 | * 534 | * i64 位运算 535 | * 536 | * - and 位与 537 | * - or 位或 538 | * - xor 位异或 539 | * - shl 位左移 540 | * - shr_s 位右移 541 | * - shr_u 逻辑位右移 542 | * 543 | * i64 比较运算,条件成立返回 1,不成立返回 0 544 | * - eq 等于,返回 0/1 545 | * - ne 不等于,返回 0/1 546 | * - lt_s 小于,返回 0/1 547 | * - lt_u 无符号小于,返回 0/1 548 | * - gt_s 大于,返回 0/1 549 | * - gt_u 无符号大于,返回 0/1 550 | * - le_s 小于等于,返回 0/1 551 | * - le_u 无符号小于等于,返回 0/1 552 | * - ge_s 大于等于,返回 0/1 553 | * - ge_u 无符号大于等于,返回 0/1 554 | * 555 | */ 556 | 557 | let nsi64 = environment.createNamespace('native.i64'); 558 | 559 | // 算术运算 560 | 561 | nsi64.defineIdentifier('add', (lh, rh) => { 562 | return lh + rh; 563 | }); 564 | 565 | nsi64.defineIdentifier('sub', (lh, rh) => { 566 | return lh - rh; 567 | }); 568 | 569 | nsi64.defineIdentifier('mul', (lh, rh) => { 570 | return lh * rh; 571 | }); 572 | 573 | nsi64.defineIdentifier('div_s', (lh, rh) => { 574 | return lh / rh; 575 | }); 576 | 577 | nsi64.defineIdentifier('div_u', (lh, rh) => { 578 | // not implement yet 579 | throw new EvalError('NOT_IMPLEMENT'); 580 | }); 581 | 582 | nsi64.defineIdentifier('rem_s', (lh, rh) => { 583 | return lh % rh; 584 | }); 585 | 586 | nsi64.defineIdentifier('rem_u', (lh, rh) => { 587 | // not implement yet 588 | throw new EvalError('NOT_IMPLEMENT'); 589 | }); 590 | 591 | // 位运算 592 | 593 | nsi64.defineIdentifier('and', (lh, rh) => { 594 | return lh & rh; 595 | }); 596 | 597 | nsi64.defineIdentifier('or', (lh, rh) => { 598 | return lh | rh; 599 | }); 600 | 601 | nsi64.defineIdentifier('xor', (lh, rh) => { 602 | return lh ^ rh; 603 | }); 604 | 605 | nsi64.defineIdentifier('shl', (lh, rh) => { 606 | return lh << rh; 607 | }); 608 | 609 | nsi64.defineIdentifier('shr_s', (lh, rh) => { 610 | return lh >> rh; 611 | }); 612 | 613 | nsi64.defineIdentifier('shr_u', (lh, rh) => { 614 | return lh >>> rh; 615 | }); 616 | 617 | // 比较运算 618 | 619 | nsi64.defineIdentifier('eq', (lh, rh) => { 620 | return lh === rh ? 1 : 0; 621 | }); 622 | 623 | nsi64.defineIdentifier('ne', (lh, rh) => { 624 | return lh !== rh ? 1 : 0; 625 | }); 626 | 627 | nsi64.defineIdentifier('lt_s', (lh, rh) => { 628 | return lh < rh ? 1 : 0; 629 | }); 630 | 631 | nsi64.defineIdentifier('lt_u', (lh, rh) => { 632 | // not implement yet 633 | throw new EvalError('NOT_IMPLEMENT'); 634 | }); 635 | 636 | nsi64.defineIdentifier('gt_s', (lh, rh) => { 637 | return lh > rh ? 1 : 0; 638 | }); 639 | 640 | nsi64.defineIdentifier('gt_u', (lh, rh) => { 641 | // not implement yet 642 | throw new EvalError('NOT_IMPLEMENT'); 643 | }); 644 | 645 | nsi64.defineIdentifier('le_s', (lh, rh) => { 646 | return lh <= rh ? 1 : 0; 647 | }); 648 | 649 | nsi64.defineIdentifier('le_u', (lh, rh) => { 650 | // not implement yet 651 | throw new EvalError('NOT_IMPLEMENT'); 652 | }); 653 | 654 | nsi64.defineIdentifier('ge_s', (lh, rh) => { 655 | return lh >= rh ? 1 : 0; 656 | }); 657 | 658 | nsi64.defineIdentifier('ge_u', (lh, rh) => { 659 | // not implement yet 660 | throw new EvalError('NOT_IMPLEMENT'); 661 | }); 662 | 663 | /** 664 | * i32 算术运算 665 | * 666 | * - add 加 667 | * - sub 减 668 | * - mul 乘 669 | * - div_s 除 670 | * - div_u 无符号除 671 | * - rem_s 余 672 | * - rem_u 无符号余 673 | * 674 | * i32 位运算 675 | * 676 | * - and 位与 677 | * - or 位或 678 | * - xor 位异或 679 | * - shl 位左移 680 | * - shr_s 位右移 681 | * - shr_u 逻辑位右移 682 | * 683 | * i32 比较运算,条件成立返回 1,不成立返回 0 684 | * - eq 等于,返回 0/1 685 | * - ne 不等于,返回 0/1 686 | * - lt_s 小于,返回 0/1 687 | * - lt_u 无符号小于,返回 0/1 688 | * - gt_s 大于,返回 0/1 689 | * - gt_u 无符号大于,返回 0/1 690 | * - le_s 小于等于,返回 0/1 691 | * - le_u 无符号小于等于,返回 0/1 692 | * - ge_s 大于等于,返回 0/1 693 | * - ge_u 无符号大于等于,返回 0/1 694 | * 695 | */ 696 | 697 | let nsi32 = environment.createNamespace('native.i32'); 698 | // TODO:: nsi32 全部都未实现 699 | 700 | /** 701 | * f64 算术运算 702 | * - add 加 703 | * - sub 减 704 | * - mul 乘 705 | * - div 除 706 | * 707 | * f64 比较运算 708 | * - eq 等于,返回 0/1 709 | * - ne 不等于,返回 0/1 710 | * - lt 小于,返回 0/1 711 | * - gt 大于,返回 0/1 712 | * - le 小于等于,返回 0/1 713 | * - ge 大于等于,返回 0/1 714 | * 715 | * f64 数学函数 716 | * - abs 绝对值 717 | * - neg 取反 718 | * - ceil 向上取整 719 | * - floor 向下取整 720 | * - trunc 截断取整 721 | * - nearest 就近取整(对应一般数学函数 round) 722 | * - sqrt 平方根 723 | */ 724 | 725 | let nsf64 = environment.createNamespace('native.f64'); 726 | // TODO:: 只实现了数学函数部分 727 | 728 | // 数学函数 729 | 730 | nsf64.defineIdentifier('abs', (val) => { 731 | return Math.abs(val); 732 | }); 733 | 734 | nsf64.defineIdentifier('neg', (val) => { 735 | return -val; 736 | }); 737 | 738 | nsf64.defineIdentifier('ceil', (val) => { 739 | return Math.ceil(val); 740 | }); 741 | 742 | nsf64.defineIdentifier('floor', (val) => { 743 | return Math.floor(val); 744 | }); 745 | 746 | nsf64.defineIdentifier('trunc', (val) => { 747 | return Math.trunc(val); 748 | }); 749 | 750 | nsf64.defineIdentifier('nearest', (val) => { 751 | return Math.round(val); 752 | }); 753 | 754 | nsf64.defineIdentifier('sqrt', (val) => { 755 | return Math.sqrt(val); 756 | }); 757 | 758 | /** 759 | * f32 算术运算 760 | * - add 加 761 | * - sub 减 762 | * - mul 乘 763 | * - div 除 764 | * 765 | * f32 比较运算 766 | * - eq 等于,返回 0/1 767 | * - ne 不等于,返回 0/1 768 | * - lt 小于,返回 0/1 769 | * - gt 大于,返回 0/1 770 | * - le 小于等于,返回 0/1 771 | * - ge 大于等于,返回 0/1 772 | * 773 | * f32 数学函数 774 | * - abs 绝对值 775 | * - neg 取反 776 | * - ceil 向上取整 777 | * - floor 向下取整 778 | * - trunc 截断取整 779 | * - nearest 就近取整(对应一般数学函数 round) 780 | * - sqrt 平方根 781 | */ 782 | 783 | let nsf32 = environment.createNamespace('native.f32'); 784 | // TODO:: nsf32 全部都未实现 785 | 786 | /** 787 | * - 整数(i32, i64)之间转换 788 | * - 浮点数(f32, f64)之间转换 789 | * - 整数(i32, i64)与浮点数(f32, f64)之间转换 790 | * 791 | * 部分函数 792 | * 793 | * - 整数提升 794 | * + i64.extend_i32_s(i32) -> i64 795 | * + i64.extend_i32_u(i32) -> i64 796 | * - 整数截断 797 | * + i32.wrap(i64) -> i32 798 | * 799 | * - 浮点数精度提升 800 | * + f64.promote(f32) -> f32 801 | * 802 | * - 浮点数精度下降 803 | * + f32.demote(f64) -> f32 804 | * 805 | * - 浮点转整数 806 | * + i32.trunc_f32_s (f32) -> i32 807 | * + i32.trunc_f32_u (f32) -> i32 808 | * + i64.trunc_f32_s (f32) -> i64 809 | * + i64.trunc_f32_u (f32) -> i64 810 | * 811 | * + i32.trunc_f64_s (f64) -> i32 812 | * + i32.trunc_f64_u (f64) -> i32 813 | * + i64.trunc_f64_s (f64) -> i64 814 | * + i64.trunc_f64_u (f64) -> i64 815 | * 816 | * - 整数转浮点 817 | * + f32.convert_i32_s (i32) -> f32 818 | * + f32.convert_i32_u (i32) -> f32 819 | * + f64.convert_i32_s (i32) -> f64 820 | * + f64.convert_i32_u (i32) -> f64 821 | * 822 | * + f32.convert_i64_s (i64) -> f32 823 | * + f32.convert_i64_u (i64) -> f32 824 | * + f64.convert_i64_s (i64) -> f64 825 | * + f64.convert_i64_u (i64) -> f64 826 | * 827 | */ 828 | // TODO:: 全部都未实现 829 | } 830 | 831 | /** 832 | * 创建 `builtin` 命名空间,并加入内置函数 833 | * 834 | * @param {*} environment 835 | */ 836 | static addBuiltinFunctionNamespace(environment) { 837 | 838 | let nsbuiltin = environment.createNamespace('builtin'); 839 | 840 | // 求值函数 841 | // 返回值:标识符或者字面量的值 842 | // 843 | // 语法: 844 | // - (val identifier) 845 | // - (val literal) 846 | nsbuiltin.defineIdentifier('val', (val) => { 847 | return val; 848 | }); 849 | 850 | /** 851 | * 逻辑运算 852 | * 约定 0 为 false,1 为 true,内部使用位运算实现。 853 | * - and 逻辑与,返回 0/1 854 | * - or 逻辑或,返回 0/1 855 | * - not 逻辑非,返回 0/1 856 | * */ 857 | 858 | // 逻辑运算 859 | 860 | nsbuiltin.defineIdentifier('and', (lh, rh) => { 861 | return (lh & rh) !== 0 ? 1 : 0; 862 | }); 863 | 864 | nsbuiltin.defineIdentifier('or', (lh, rh) => { 865 | return (lh | rh) === 0 ? 0 : 1; 866 | }); 867 | 868 | nsbuiltin.defineIdentifier('not', (val) => { 869 | return val === 0 ? 1 : 0; 870 | }); 871 | } 872 | 873 | static addDefaultNamespace(environment) { 874 | let namespace = environment.createNamespace('user'); 875 | return namespace; 876 | } 877 | 878 | getIdentifier(identifierNameOrFullName, context) { 879 | if (identifierNameOrFullName.indexOf('.') > 0) { 880 | return this.environment.getIdentifierByFullName(identifierNameOrFullName); 881 | } else { 882 | return context.getIdentifier(identifierNameOrFullName); 883 | } 884 | } 885 | 886 | static isNumber(element) { 887 | return typeof element === 'number'; 888 | } 889 | 890 | static isIdentifier(element) { 891 | return typeof element === 'string'; 892 | } 893 | 894 | static assertNumberOfParameters(name, actual, expect) { 895 | if (actual !== expect) { 896 | throw new SyntaxError('INCORRECT_NUMBER_OF_PARAMETERS', 897 | { name: name, actual: actual, expect: expect }, 898 | `Incorrect number of parameters for function: "${name}"`); 899 | } 900 | } 901 | 902 | static assertNumberOfLoopArgs(numberOfParameters, numberOfArgs) { 903 | if (numberOfParameters !== numberOfArgs) { 904 | throw new SyntaxError('INCORRECT_NUMBER_OF_LOOP_ARGS', 905 | { actual: numberOfArgs, expect: numberOfParameters }, 906 | `Incorrect number of args for loop`); 907 | } 908 | } 909 | } 910 | 911 | export { Evaluator }; -------------------------------------------------------------------------------- /src/identifiererror.js: -------------------------------------------------------------------------------- 1 | import { EvalError } from './evalerror.js'; 2 | 3 | class IdentifierError extends EvalError { 4 | } 5 | 6 | export { IdentifierError }; -------------------------------------------------------------------------------- /src/namespace.js: -------------------------------------------------------------------------------- 1 | import { IdentifierError } from './identifiererror.js'; 2 | import { AbstractContext } from './abstractcontext.js'; 3 | 4 | /** 5 | * 命名空间用于存储用户自定义函数、内置函数及常量 6 | * 7 | * 命名空间之间没有继承关系,所有命名空间都从属于 Environment. 8 | * 9 | */ 10 | class Namespace extends AbstractContext { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | exist(name) { 16 | // 命名空间对象之间没有继承关系 17 | return this.identifiers.has(name); 18 | } 19 | 20 | getIdentifier(name) { 21 | let value = this.identifiers.get(name); 22 | if (value === undefined) { 23 | throw new IdentifierError( 24 | 'IDENTIFIER_NOT_FOUND', 25 | { name: name }, 26 | `Identifier "${name}" not found`); 27 | 28 | } else { 29 | return value; 30 | } 31 | } 32 | } 33 | 34 | export { Namespace }; -------------------------------------------------------------------------------- /src/recursion-function.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 含尾部调用的用户自定义函数 3 | * 4 | * - 函数的最后一句必须是 `break` 或者 `recur` 5 | * - 如果函数有多个分支,必须保证每个分支的最后一句必须是 `break` 或者 `recur`, 6 | * 在加载源码时会有语法检查。 7 | */ 8 | class RecursionFunction { 9 | constructor(name, parameters, bodyExp, context) { 10 | this.name = name; 11 | this.parameters = parameters; 12 | this.bodyExp = bodyExp; 13 | this.context = context; // 定义函数时所在的作用域 14 | } 15 | } 16 | 17 | export { RecursionFunction }; -------------------------------------------------------------------------------- /src/s-lex.js: -------------------------------------------------------------------------------- 1 | /* S 表达式文本示例: 2 | (add 1 2) 3 | (add 1 (div 4 2)) 4 | (if -1 5 | (print 33) 6 | (print 34) 7 | ) 8 | */ 9 | 10 | const S_LEFT_PAREN = '('; 11 | const S_RIGHT_PAREN = ')'; 12 | 13 | const S_PARENS = [ 14 | S_LEFT_PAREN, 15 | S_RIGHT_PAREN, 16 | ]; 17 | 18 | const S_WHITESPACE = [' ', '\t', '\n', '\r']; 19 | 20 | // const S_NUMBER = ['-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e']; 21 | 22 | class SLex { 23 | static fromString(str) { 24 | let tokens = []; 25 | while (str.length > 0) { 26 | if (SLex.oneOf(str[0], S_WHITESPACE)) { 27 | str = str.slice(1); 28 | continue; 29 | } 30 | 31 | // '(' or ')' 32 | if (SLex.oneOf(str[0], S_PARENS)) { 33 | tokens.push(str[0]); 34 | str = str.slice(1); 35 | continue; 36 | } 37 | 38 | // symbol 39 | let { sSymbol, restString: restAfterSymbol } = SLex.lexSymbol(str); 40 | if (sSymbol !== undefined) { 41 | tokens.push(sSymbol); 42 | str = restAfterSymbol; 43 | continue; 44 | } 45 | 46 | throw new Error(`Invalid char ${str[0]}`); 47 | } 48 | 49 | return tokens; 50 | } 51 | 52 | static lexSymbol(str) { 53 | for (let idx = 0; idx < str.length; idx++) { 54 | let nextChar = str[idx]; 55 | if (SLex.oneOf(nextChar, S_WHITESPACE) || 56 | nextChar === S_RIGHT_PAREN) { 57 | let sSymbol = str.substring(0, idx); 58 | let restString = str.substring(idx); 59 | return { sSymbol, restString }; 60 | } 61 | } 62 | 63 | return { sSymbol: str, restString: '' }; 64 | } 65 | 66 | static oneOf(char, array) { 67 | return array.includes(char); 68 | } 69 | } 70 | 71 | export { SLex }; -------------------------------------------------------------------------------- /src/s-parser.js: -------------------------------------------------------------------------------- 1 | const S_LEFT_PAREN = '('; 2 | const S_RIGHT_PAREN = ')'; 3 | 4 | class SParser { 5 | static parseList(tokens) { 6 | let elements = []; 7 | 8 | if (tokens[0] === S_RIGHT_PAREN) { 9 | return { value: elements, restTokens: tokens.slice(1) }; 10 | } 11 | 12 | while (true) { 13 | let { value, restTokens } = SParser.parseValue(tokens); 14 | 15 | elements.push(value); 16 | 17 | if (restTokens[0] === S_RIGHT_PAREN) { 18 | return { value: elements, restTokens: restTokens.slice(1) }; 19 | } 20 | 21 | tokens = restTokens; 22 | } 23 | } 24 | 25 | static parseValue(tokens) { 26 | if (tokens[0] === S_LEFT_PAREN) { 27 | return SParser.parseList(tokens.slice(1)); 28 | } else { 29 | let currentToken = tokens[0]; 30 | let num = Number(currentToken); 31 | if (Number.isNaN(num)) { 32 | return { value: currentToken, restTokens: tokens.slice(1) }; 33 | } else { 34 | return { value: num, restTokens: tokens.slice(1) }; 35 | } 36 | } 37 | } 38 | 39 | static parse(tokens) { 40 | let {value, restTokens} = SParser.parseValue(tokens); 41 | return value; 42 | } 43 | } 44 | 45 | export { SParser }; -------------------------------------------------------------------------------- /src/scope.js: -------------------------------------------------------------------------------- 1 | import { IdentifierError } from './identifiererror.js'; 2 | import { AbstractContext } from './abstractcontext.js'; 3 | 4 | /** 5 | * 表达式块,或者匿名函数的上下文 6 | * 7 | * 用于创建一个作用域,维护作用域内的局部变量 8 | * Scope 仅可以在函数内定义 9 | */ 10 | class Scope extends AbstractContext { 11 | /** 12 | * 13 | * @param {*} parentContext 父上下文 14 | * - 对于表达式块,父上下文就是其所在的函数的命名空间,或者父表达式块的上下文 15 | * - 对于匿名函数,父上下文就是其所在的函数的命名空间,或者父匿名函数的上下文 16 | */ 17 | constructor(parentContext = null) { 18 | super(); 19 | this.parentContext = parentContext; 20 | } 21 | 22 | /** 23 | * 更新局部变量的值 24 | * 注意 Scope 一路从用户自定义函数的 Namespace 继承下来,但只有 Scope 里面 25 | * 的标识符才是局部变量。 26 | * 27 | * 返回值本身。 28 | * @param {*} name 29 | * @param {*} value 30 | */ 31 | updateIdentifierValue(name, value) { 32 | if (this.identifiers.has(name)) { 33 | this.identifiers.set(name, value); 34 | return value; 35 | 36 | }else if (this.parentContext !== null && 37 | this.parentContext instanceof Scope) { 38 | return this.parentContext.updateIdentifierValue(name, value); 39 | 40 | }else { 41 | throw new IdentifierError( 42 | 'IDENTIFIER_NOT_FOUND', 43 | { name: name }, 44 | `Local identifier "${name}" not found`); 45 | } 46 | } 47 | 48 | exist(name) { 49 | // 上下文具有继承关系 50 | return this.identifiers.has(name) || 51 | (this.parentContext !== null && this.parentContext.exist(name)); 52 | } 53 | 54 | getIdentifier(name) { 55 | // 上下文具有继承关系 56 | let value = this.identifiers.get(name); 57 | if (value === undefined) { 58 | if (this.parentContext !== null) { 59 | return this.parentContext.getIdentifier(name); 60 | 61 | } else { 62 | throw new IdentifierError( 63 | 'IDENTIFIER_NOT_FOUND', 64 | { name: name }, 65 | `Identifier "${name}" not found`); 66 | } 67 | 68 | } else { 69 | return value; 70 | } 71 | } 72 | } 73 | 74 | export { Scope }; -------------------------------------------------------------------------------- /src/syntexerror.js: -------------------------------------------------------------------------------- 1 | import { EvalError } from './evalerror.js'; 2 | 3 | class SyntaxError extends EvalError { }; 4 | 5 | export { SyntaxError }; -------------------------------------------------------------------------------- /src/user-define-function.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户自定义函数,即一般函数 3 | */ 4 | class UserDefineFunction { 5 | constructor(name, parameters, bodyExp, context) { 6 | this.name = name; 7 | this.parameters = parameters; 8 | this.bodyExp = bodyExp; 9 | this.context = context; // 定义函数时所在的作用域 10 | } 11 | } 12 | 13 | export { UserDefineFunction }; -------------------------------------------------------------------------------- /test/test-evaluator.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert'; 2 | 3 | import { 4 | Evaluator, 5 | EvalError, 6 | SyntaxError, 7 | IdentifierError 8 | } from "../index.js"; 9 | 10 | /** 11 | * Evaluator 实例创建后,默认在 `user` namespace 之中 12 | * 13 | * 测试环境里支持直接调用函数的表达式,以及 14 | * - `namespace` 15 | * - `do` 16 | * - `if` 17 | * - `loop`(及其子表达式 `break` 和 `recur`) 18 | * 等表达式,也可以直接对字面量和数值类型的标识符求值。 19 | * 20 | */ 21 | class TestEvaluator { 22 | 23 | static testLiteral() { 24 | let evaluator = new Evaluator(); 25 | 26 | assert.equal(evaluator.evalFromString('123'), 123); 27 | } 28 | 29 | static testArithmeticOperatorFunction() { 30 | let evaluator = new Evaluator(); 31 | 32 | assert.equal(evaluator.evalFromString('(native.i64.add 1 2)'), 3); 33 | assert.equal(evaluator.evalFromString('(native.i64.sub 1 2)'), -1); 34 | assert.equal(evaluator.evalFromString('(native.i64.sub 3 1)'), 2); 35 | assert.equal(evaluator.evalFromString('(native.i64.mul 2 3)'), 6); 36 | assert.equal(evaluator.evalFromString('(native.i64.div_s 6 2)'), 3); 37 | assert.equal(evaluator.evalFromString('(native.i64.div_s 1 2)'), 0.5); 38 | assert.equal(evaluator.evalFromString('(native.i64.div_s -6 2)'), -3); 39 | assert.equal(evaluator.evalFromString('(native.i64.rem_s 10 4)'), 2); 40 | assert.equal(evaluator.evalFromString('(native.i64.rem_s -10 4)'), -2); 41 | } 42 | 43 | static testBitwiseOperatorFunction() { 44 | let evaluator = new Evaluator(); 45 | 46 | assert.equal(evaluator.evalFromString('(native.i64.and 3 5)'), 3 & 5); 47 | assert.equal(evaluator.evalFromString('(native.i64.and -7 9)'), -7 & 9); 48 | 49 | assert.equal(evaluator.evalFromString('(native.i64.or 3 5)'), 3 | 5); 50 | assert.equal(evaluator.evalFromString('(native.i64.or -7 9)'), -7 | 9); 51 | 52 | assert.equal(evaluator.evalFromString('(native.i64.xor 3 5)'), 3 ^ 5); 53 | assert.equal(evaluator.evalFromString('(native.i64.xor -7 9)'), -7 ^ 9); 54 | 55 | assert.equal(evaluator.evalFromString('(native.i64.shl 3 5)'), 3 << 5); 56 | assert.equal(evaluator.evalFromString('(native.i64.shl -3 5)'), -3 << 5); 57 | 58 | assert.equal(evaluator.evalFromString('(native.i64.shr_s 3 5)'), 3 >> 5); 59 | assert.equal(evaluator.evalFromString('(native.i64.shr_s -3 5)'), -3 >> 5); 60 | 61 | assert.equal(evaluator.evalFromString('(native.i64.shr_u 3 5)'), 3 >>> 5); 62 | assert.equal(evaluator.evalFromString('(native.i64.shr_u -3 5)'), -3 >>> 5); 63 | } 64 | 65 | static testComparisonOperatorFunction() { 66 | let evaluator = new Evaluator(); 67 | 68 | assert.equal(evaluator.evalFromString('(native.i64.eq 2 3)'), 0); 69 | assert.equal(evaluator.evalFromString('(native.i64.eq 2 2)'), 1); 70 | 71 | assert.equal(evaluator.evalFromString('(native.i64.ne 2 3)'), 1); 72 | assert.equal(evaluator.evalFromString('(native.i64.ne 2 2)'), 0); 73 | 74 | assert.equal(evaluator.evalFromString('(native.i64.lt_s 3 2)'), 0); 75 | assert.equal(evaluator.evalFromString('(native.i64.lt_s 2 3)'), 1); 76 | 77 | assert.equal(evaluator.evalFromString('(native.i64.gt_s 2 3)'), 0); 78 | assert.equal(evaluator.evalFromString('(native.i64.gt_s 3 2)'), 1); 79 | 80 | assert.equal(evaluator.evalFromString('(native.i64.le_s 2 1)'), 0); 81 | assert.equal(evaluator.evalFromString('(native.i64.le_s 2 2)'), 1); 82 | assert.equal(evaluator.evalFromString('(native.i64.le_s 2 3)'), 1); 83 | 84 | assert.equal(evaluator.evalFromString('(native.i64.ge_s 2 3)'), 0); 85 | assert.equal(evaluator.evalFromString('(native.i64.ge_s 2 2)'), 1); 86 | assert.equal(evaluator.evalFromString('(native.i64.ge_s 2 1)'), 1); 87 | } 88 | 89 | static testMathFunction() { 90 | let evaluator = new Evaluator(); 91 | 92 | assert.equal(evaluator.evalFromString('(native.f64.abs 2.7)'), 2.7); 93 | assert.equal(evaluator.evalFromString('(native.f64.abs -2.7)'), 2.7); 94 | assert.equal(evaluator.evalFromString('(native.f64.neg 2.7)'), -2.7); 95 | assert.equal(evaluator.evalFromString('(native.f64.neg -2.7)'), 2.7); 96 | assert.equal(evaluator.evalFromString('(native.f64.ceil 2.7)'), 3); 97 | assert.equal(evaluator.evalFromString('(native.f64.ceil -2.7)'), -2); 98 | assert.equal(evaluator.evalFromString('(native.f64.floor 2.7)'), 2); 99 | assert.equal(evaluator.evalFromString('(native.f64.floor -2.7)'), -3); 100 | assert.equal(evaluator.evalFromString('(native.f64.trunc 2.7)'), 2); 101 | assert.equal(evaluator.evalFromString('(native.f64.trunc -2.7)'), -2); 102 | assert.equal(evaluator.evalFromString('(native.f64.nearest 2.7)'), 3); 103 | assert.equal(evaluator.evalFromString('(native.f64.nearest -2.7)'), -3); 104 | assert.equal(evaluator.evalFromString('(native.f64.sqrt 9.0)'), 3.0); 105 | } 106 | 107 | static testBuiltinFunctionsLogicalOperator() { 108 | let evaluator = new Evaluator(); 109 | 110 | assert.equal(evaluator.evalFromString('(builtin.and 0 0)'), 0); 111 | assert.equal(evaluator.evalFromString('(builtin.and 0 1)'), 0); 112 | assert.equal(evaluator.evalFromString('(builtin.and 1 1)'), 1); 113 | 114 | assert.equal(evaluator.evalFromString('(builtin.or 0 0)'), 0); 115 | assert.equal(evaluator.evalFromString('(builtin.or 0 1)'), 1); 116 | assert.equal(evaluator.evalFromString('(builtin.or 1 1)'), 1); 117 | 118 | assert.equal(evaluator.evalFromString('(builtin.not 0)'), 1); 119 | assert.equal(evaluator.evalFromString('(builtin.not 1)'), 0); 120 | } 121 | 122 | static testConstant() { 123 | let evaluator = new Evaluator(); 124 | 125 | assert.equal(evaluator.evalFromString('(const a 2)'), 2); 126 | assert.equal(evaluator.evalFromString('a'), 2) 127 | assert.equal(evaluator.evalFromString('(const b 5))'), 5); 128 | assert.equal(evaluator.evalFromString('user.b'), 5); // 默认命名空间是 `user` 129 | 130 | try { 131 | evaluator.evalFromString('(const b 1)'); 132 | } catch (err) { 133 | assert(err instanceof IdentifierError); 134 | assert.equal(err.code, 'IDENTIFIER_ALREADY_EXIST'); 135 | assert.deepEqual(err.data, { name: 'b' }); 136 | } 137 | 138 | try { 139 | evaluator.evalFromString('c'); 140 | } catch (err) { 141 | assert(err instanceof IdentifierError); 142 | assert.equal(err.code, 'IDENTIFIER_NOT_FOUND'); 143 | assert.deepEqual(err.data, { name: 'c' }); 144 | } 145 | 146 | } 147 | 148 | static testNamespace() { 149 | let evaluator = new Evaluator(); 150 | 151 | evaluator.evalFromString('(const a 1)'); // 默认命名空间是 `user` 152 | evaluator.evalFromString('(namespace foo (const a 10))'); 153 | evaluator.evalFromString('(namespace foo.bar (const a 100))'); 154 | 155 | assert.equal(evaluator.evalFromString('a'), 1) 156 | assert.equal(evaluator.evalFromString('user.a'), 1) 157 | assert.equal(evaluator.evalFromString('foo.a'), 10) 158 | assert.equal(evaluator.evalFromString('foo.bar.a'), 100) 159 | } 160 | 161 | static testDo() { 162 | let evaluator = new Evaluator(); 163 | 164 | // check block return value 165 | assert.equal(evaluator.evalFromString( 166 | `(do 7 8 9)` 167 | ), 9) 168 | 169 | // check cascaded block return value 170 | assert.equal(evaluator.evalFromString( 171 | `(do (do 8))` 172 | ), 8) 173 | 174 | assert.equal(evaluator.evalFromString( 175 | `(do 1 2 3 (do 4 5 6))` 176 | ), 6) 177 | 178 | // check multi expressions 179 | assert.equal(evaluator.evalFromString( 180 | `(do 181 | (let a 1) 182 | a 183 | )` 184 | ), 1); 185 | 186 | assert.equal(evaluator.evalFromString( 187 | `(do 188 | (let a 1) 189 | (let b 2) 190 | (native.i64.add a b) 191 | )` 192 | ), 3); 193 | 194 | // lookup parent block variable 195 | assert.equal(evaluator.evalFromString( 196 | `(do 197 | (let m 1) 198 | (do 199 | (let n 2) 200 | (native.i64.add m n) 201 | ) 202 | )` 203 | ), 3); 204 | 205 | // try lookup child block variable 206 | try { 207 | evaluator.evalFromString( 208 | `(do 209 | (do 210 | (let i 2) 211 | ) 212 | i 213 | )` 214 | ); 215 | } catch (err) { 216 | assert(err instanceof IdentifierError); 217 | assert.equal(err.code, 'IDENTIFIER_NOT_FOUND'); 218 | assert.deepEqual(err.data, { name: 'i' }); 219 | } 220 | 221 | // try lookup another block variable 222 | try { 223 | evaluator.evalFromString( 224 | ` 225 | (do 226 | (let i 2) 227 | ) 228 | (do 229 | i 230 | )` 231 | ); 232 | } catch (err) { 233 | assert(err instanceof IdentifierError); 234 | assert.equal(err.code, 'IDENTIFIER_NOT_FOUND'); 235 | assert.deepEqual(err.data, { name: 'i' }); 236 | } 237 | 238 | } 239 | 240 | static testVariable() { 241 | let evaluator = new Evaluator(); 242 | 243 | assert.equal(evaluator.evalFromString( 244 | `(do 245 | (let a 2) 246 | (set a 3) 247 | )` 248 | ), 3); 249 | 250 | assert.equal(evaluator.evalFromString( 251 | `(do 252 | (let a 2) 253 | (set a 3) 254 | a 255 | )` 256 | ), 3); 257 | 258 | // 设置父有效域的变量的值 259 | assert.equal(evaluator.evalFromString( 260 | `(do 261 | (let a 2) 262 | (do 263 | (set a 3) 264 | ) 265 | a 266 | )` 267 | ), 3); 268 | 269 | try { 270 | evaluator.evalFromString( 271 | `(do 272 | (let a 2) 273 | (set c 3) 274 | )` 275 | ); 276 | } catch (err) { 277 | assert(err instanceof IdentifierError); 278 | assert.equal(err.code, 'IDENTIFIER_NOT_FOUND'); 279 | assert.deepEqual(err.data, { name: 'c' }); 280 | } 281 | 282 | try { 283 | evaluator.evalFromString( 284 | `(do 285 | (let a 2) 286 | (do 287 | (let b 3) 288 | ) 289 | (set b 3) 290 | )` 291 | ); 292 | } catch (err) { 293 | assert(err instanceof IdentifierError); 294 | assert.equal(err.code, 'IDENTIFIER_NOT_FOUND'); 295 | assert.deepEqual(err.data, { name: 'b' }); 296 | } 297 | } 298 | 299 | static testConditionControlFlow() { 300 | let evaluator = new Evaluator(); 301 | 302 | assert.equal(evaluator.evalFromString( 303 | `(if (native.i64.gt_s 2 1) 10 20)` 304 | ), 10); 305 | 306 | assert.equal(evaluator.evalFromString( 307 | `(do 308 | (let a 61) 309 | (if (native.i64.gt_s a 90) 310 | 2 311 | (if (native.i64.gt_s a 60) 312 | 1 313 | 0 314 | ) 315 | ) 316 | )` 317 | ), 1) 318 | } 319 | 320 | static testLoopControlFlow() { 321 | let evaluator = new Evaluator(); 322 | 323 | assert.equal(evaluator.evalFromString( 324 | `(do 325 | (loop (i) (1) 326 | (if (native.i64.lt_s i 10) 327 | (recur (native.i64.add i 1)) 328 | (break i) 329 | ) 330 | ) 331 | )` 332 | ), 10); 333 | 334 | assert.equal(evaluator.evalFromString( 335 | `(do 336 | (loop (i accu) (1 0) 337 | (if (native.i64.gt_s i 100) 338 | (break accu) 339 | (do 340 | (let next (native.i64.add i 1)) 341 | (let sum (native.i64.add accu i)) 342 | (recur next sum) 343 | ) 344 | ) 345 | ) 346 | )` 347 | ), 5050); 348 | } 349 | 350 | static testUserDefineFunction() { 351 | let evaluator = new Evaluator(); 352 | 353 | // test define and invoke 354 | assert.equal(evaluator.evalFromStringMultiExps( 355 | ` 356 | (defn five1 () 5) 357 | (five1)` 358 | ), 5); 359 | 360 | assert.equal(evaluator.evalFromStringMultiExps( 361 | ` 362 | (defn five2 () (native.i64.add 2 3)) 363 | (five2)` 364 | ), 5); 365 | 366 | assert.equal(evaluator.evalFromStringMultiExps( 367 | ` 368 | (defn plusOne (i) (native.i64.add i 1)) 369 | (plusOne 2)` 370 | ), 3); 371 | 372 | // test lookup constant 373 | assert.equal(evaluator.evalFromStringMultiExps( 374 | ` 375 | (const THREE 3) 376 | (defn inc (x) (native.i64.add THREE x)) 377 | (inc 2)` 378 | ), 5); 379 | 380 | assert.equal(evaluator.evalFromStringMultiExps( 381 | ` 382 | (defn incAndDouble (y) (do 383 | (let tmp (inc y)) 384 | (native.i64.mul tmp y) 385 | ) 386 | ) 387 | (incAndDouble 2)` 388 | ), 10); 389 | 390 | // test function recursion (i.e. call function itself) 391 | // 1,2,3,5,8,13,21,34 (result) 392 | // 1 2 3 4 5 6 7 8 (index) 393 | assert.equal(evaluator.evalFromStringMultiExps( 394 | ` 395 | (defn fib (i) 396 | (if (native.i64.eq i 1) 397 | 1 398 | (if (native.i64.eq i 2) 399 | 2 400 | (native.i64.add (fib (native.i64.sub i 1)) (fib (native.i64.sub i 2))) 401 | ) 402 | ) 403 | ) 404 | (fib 8)` 405 | ), 34); 406 | } 407 | 408 | static testAnonymousFunction() { 409 | let evaluator = new Evaluator(); 410 | 411 | // test invoke anonymous function directly 412 | assert.equal(evaluator.evalFromString( 413 | `((fn (i) (native.i64.mul i 3)) 9)` 414 | ), 27); 415 | 416 | // test assign an anonymous function to a variable 417 | assert.equal(evaluator.evalFromString( 418 | ` 419 | (do 420 | (let f (fn (i) (native.i64.mul i 3))) 421 | (f 7) 422 | )` 423 | ), 21); 424 | 425 | // 在普通函数里定义匿名函数 426 | assert.equal(evaluator.evalFromStringMultiExps( 427 | ` 428 | (defn foo (i) 429 | (do 430 | (let bar (fn () 3)) 431 | (native.i64.add i (bar)) 432 | ) 433 | ) 434 | (foo 2)` 435 | ), 5); 436 | 437 | // test anonymous function as return value 438 | assert.equal(evaluator.evalFromStringMultiExps( 439 | ` 440 | (defn makeInc 441 | (much) 442 | (fn (base) (native.i64.add base much)) 443 | ) 444 | 445 | (do 446 | (let incTwo (makeInc 2)) 447 | (incTwo 6) 448 | )` 449 | ), 8); 450 | 451 | // test anonymous function as arg 452 | assert.equal(evaluator.evalFromStringMultiExps( 453 | ` 454 | (defn execFun 455 | (i f) 456 | (f i) 457 | ) 458 | (execFun 459 | 3 460 | (fn (i) (native.i64.mul i 2)) 461 | )` 462 | ), 6); 463 | 464 | // test loop by anonymous function recursion 465 | assert.equal(evaluator.evalFromStringMultiExps( 466 | ` 467 | (defn accumulate (count) 468 | (do 469 | (let internalLoop 470 | (fn (i result) 471 | (if (native.i64.eq i 0) 472 | result 473 | (internalLoop (native.i64.sub i 1) (native.i64.add i result)) 474 | ) 475 | ) 476 | ) 477 | (internalLoop count 0) 478 | ) 479 | ) 480 | (accumulate 100) 481 | ` 482 | ), 5050); 483 | 484 | } 485 | 486 | static testRecursionFunction() { 487 | let evaluator = new Evaluator(); 488 | 489 | assert.equal(evaluator.evalFromStringMultiExps( 490 | ` 491 | (defnr countToTen (i) 492 | (if (native.i64.lt_s i 10) 493 | (recur (native.i64.add i 1)) 494 | (break i) 495 | ) 496 | ) 497 | (countToTen 1)` 498 | ), 10); 499 | 500 | assert.equal(evaluator.evalFromStringMultiExps( 501 | ` 502 | (defnr sumToOneHundred (i accu) 503 | (if (native.i64.gt_s i 100) 504 | (break accu) 505 | (do 506 | (let next (native.i64.add i 1)) 507 | (let sum (native.i64.add accu i)) 508 | (recur next sum) 509 | ) 510 | ) 511 | ) 512 | (sumToOneHundred 1 0)` 513 | ), 5050); 514 | } 515 | 516 | static testFunctionError() { 517 | let evaluator = new Evaluator(); 518 | 519 | // incorrent number of parameters 520 | try { 521 | evaluator.evalFromString('(native.i64.add 1)'); 522 | } catch (err) { 523 | assert(err instanceof SyntaxError); 524 | assert.equal(err.code, 'INCORRECT_NUMBER_OF_PARAMETERS'); 525 | assert.deepEqual(err.data, { name: 'native.i64.add', actual: 1, expect: 2 }); 526 | } 527 | 528 | // call non-exist function 529 | try { 530 | evaluator.evalFromString('(noThisFunction)'); 531 | } catch (err) { 532 | assert(err instanceof EvalError); 533 | assert.equal(err.code, 'IDENTIFIER_NOT_FOUND'); 534 | assert.deepEqual(err.data, { name: 'noThisFunction' }); 535 | } 536 | 537 | // invoke a variable 538 | evaluator.evalFromString(`(const foo 2)`); 539 | 540 | try { 541 | evaluator.evalFromString(`(foo 1 2)`); 542 | } catch (err) { 543 | assert(err instanceof EvalError); 544 | assert.equal(err.code, 'IDENTIFIER_NOT_A_FUNCTION'); 545 | assert.deepEqual(err.data, { name: 'foo' }); 546 | } 547 | } 548 | 549 | static testEvaluator() { 550 | TestEvaluator.testLiteral(); 551 | 552 | TestEvaluator.testArithmeticOperatorFunction(); 553 | TestEvaluator.testBitwiseOperatorFunction(); 554 | TestEvaluator.testComparisonOperatorFunction(); 555 | TestEvaluator.testMathFunction(); 556 | TestEvaluator.testBuiltinFunctionsLogicalOperator(); 557 | 558 | TestEvaluator.testConstant(); 559 | TestEvaluator.testNamespace(); 560 | TestEvaluator.testDo(); 561 | TestEvaluator.testVariable(); 562 | 563 | TestEvaluator.testConditionControlFlow(); 564 | TestEvaluator.testLoopControlFlow(); 565 | 566 | TestEvaluator.testUserDefineFunction(); 567 | TestEvaluator.testAnonymousFunction(); 568 | TestEvaluator.testRecursionFunction(); 569 | TestEvaluator.testFunctionError(); 570 | 571 | console.log('Evaluator passed'); 572 | } 573 | } 574 | 575 | export { TestEvaluator }; -------------------------------------------------------------------------------- /test/test-s-lex.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert'; 2 | 3 | import { SLex } from '../index.js'; 4 | 5 | class TestSLex { 6 | static testLiteral() { 7 | let s1 = SLex.fromString('123'); 8 | assert.deepEqual(s1, ['123']); 9 | 10 | let s2 = SLex.fromString('foo'); 11 | assert.deepEqual(s2, ['foo']); 12 | } 13 | 14 | static testList() { 15 | let s1 = SLex.fromString('(foo)'); 16 | assert.deepEqual(s1, ['(', 'foo', ')']); 17 | 18 | let s2 = SLex.fromString('(add 123 456)'); 19 | assert.deepEqual(s2, ['(', 'add', '123', '456', ')']); 20 | } 21 | 22 | static testCascadedList() { 23 | let s1 = SLex.fromString( 24 | '(add (mul 1 2) 3)'); 25 | assert.deepEqual(s1, ['(', 26 | 'add', 27 | '(', 'mul', '1', '2', ')', 28 | '3', ')']); 29 | 30 | let s2 = SLex.fromString( 31 | '(let ((a 1) (b 2)) (add a b))'); 32 | assert.deepEqual(s2, ['(', 33 | 'let', 34 | '(', '(', 'a', '1', ')', 35 | '(', 'b', '2', ')', ')', 36 | '(', 'add', 'a', 'b', ')', 37 | ')']); 38 | } 39 | 40 | static testSLex() { 41 | TestSLex.testLiteral(); 42 | // TestSLex.testList(); 43 | // TestSLex.testCascadedList(); 44 | console.log('SLex passed.') 45 | } 46 | } 47 | 48 | export { TestSLex }; -------------------------------------------------------------------------------- /test/test-s-parser.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert'; 2 | 3 | import { SLex, SParser } from '../index.js'; 4 | 5 | class TestSParser { 6 | static testLiteral() { 7 | let token1 = SLex.fromString('123'); 8 | let a1 = SParser.parse(token1); 9 | assert.deepEqual(a1, 123); 10 | 11 | let token2 = SLex.fromString('foo'); 12 | let a2 = SParser.parse(token2); 13 | assert.deepEqual(a2, 'foo'); 14 | } 15 | 16 | static testSimpleList() { 17 | let token1 = SLex.fromString('(foo)'); 18 | let a1 = SParser.parse(token1); 19 | assert.deepEqual(a1, ['foo']); 20 | 21 | let token2 = SLex.fromString('(add 123 456)'); 22 | let a2 = SParser.parse(token2); 23 | assert.deepEqual(a2, ['add', 123, 456]); 24 | } 25 | 26 | static testCascadedList() { 27 | let token1 = SLex.fromString( 28 | '(add (mul 1 2) 3)'); 29 | let a1 = SParser.parse(token1); 30 | 31 | assert.deepEqual(a1, ['add', ['mul', 1, 2], 3]); 32 | 33 | let token2 = SLex.fromString( 34 | '(let ((a 1) (b 2)) (add a b))'); 35 | let a2 = SParser.parse(token2); 36 | 37 | assert.deepEqual(a2, ['let', 38 | [['a', 1], ['b', 2]], 39 | ['add', 'a', 'b'] 40 | ]); 41 | 42 | } 43 | 44 | static testSParser() { 45 | TestSParser.testLiteral(); 46 | TestSParser.testSimpleList(); 47 | TestSParser.testCascadedList(); 48 | 49 | console.log('SParser passed.') 50 | } 51 | } 52 | 53 | export { TestSParser }; 54 | 55 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import { TestSLex } from './test-s-lex.js'; 2 | import { TestSParser } from './test-s-parser.js'; 3 | import { TestEvaluator } from './test-evaluator.js'; 4 | 5 | function testAll() { 6 | TestSLex.testSLex(); 7 | TestSParser.testSParser(); 8 | TestEvaluator.testEvaluator(); 9 | console.log('All passed.'); 10 | } 11 | 12 | testAll(); --------------------------------------------------------------------------------