├── tests ├── cases │ ├── should-work-with-null.feeny │ ├── should-work-with-global-variable.feeny │ ├── should-work-with-type-null.feeny │ ├── should-work-with-type-integer.feeny │ ├── should-work-with-variable-reference.feeny │ ├── should-work-with-arrays.feeny │ ├── should-work-with-printf.feeny │ ├── should-work-with-function.feeny │ ├── should-work-with-get-shorthand.feeny │ ├── should-work-with-integer.feeny │ ├── should-work-with-function-call.feeny │ ├── should-work-with-set-shorthand.feeny │ ├── should-work-with-type-arrays.feeny │ ├── should-work-with-type-typedef.feeny │ ├── should-work-with-variable-assignment.feeny │ ├── should-work-with-local-variable.feeny │ ├── should-work-with-type-variable-slot.feeny │ ├── should-work-with-if-expression-with-if-only.feeny │ ├── should-work-with-type-function-parameter.feeny │ ├── should-work-with-method-call.feeny │ ├── should-work-with-type-function-statement.feeny │ ├── should-work-with-while-expression.feeny │ ├── should-work-with-function-expression.feeny │ ├── should-work-with-if-expression.feeny │ ├── should-work-with-object-without-extends.feeny │ ├── should-work-with-type-function-expression.feeny │ ├── should-work-with-type-method-signature.feeny │ ├── should-work-with-type-function-call.feeny │ ├── should-work-with-type-variable-reference.feeny │ ├── should-work-with-objects.feeny │ ├── should-work-with-type-method-slot.feeny │ ├── should-work-with-nested-function.feeny │ ├── should-work-with-objects-and-slots.feeny │ ├── should-work-with-binary-shorthand.feeny │ ├── should-work-with-slot-lookup.feeny │ ├── should-work-with-slot-assignment.feeny │ └── should-work-with-break-and-continue.feeny ├── .DS_Store ├── demo │ ├── hello.feeny │ ├── hello2.feeny │ ├── hello9.feeny │ ├── hello3.feeny │ ├── hello4.feeny │ ├── hello8.feeny │ ├── hello5.feeny │ ├── hello7.feeny │ ├── hello6.feeny │ ├── inheritance.feeny │ ├── fibonacci.feeny │ ├── bsearch.feeny │ ├── cplx.feeny │ ├── lists.feeny │ ├── vector.feeny │ ├── sudoku.feeny │ └── sudoku2.feeny ├── parser.spec.ts ├── scanner.spec.ts ├── binder.spec.ts ├── checker.spec.ts ├── intepreter.spec.ts ├── demo.spec.ts ├── __snapshots__ │ ├── language-service.spec.ts.snap │ ├── intepreter.spec.ts.snap │ └── binder.spec.ts.snap ├── language-service.spec.ts └── utils.ts ├── .npmrc ├── cli └── bin.js ├── extension ├── images │ ├── icon.png │ └── github-dark-default.png ├── CHANGELOG.md ├── scripts │ └── build.js ├── .vscode │ └── launch.json ├── README.md ├── language-configuration.json ├── LICENSE ├── src │ ├── client.ts │ └── server.ts ├── package.json └── syntaxes │ └── feeny.tmLanguage.json ├── jest.config.js ├── prettier.config.js ├── src ├── index.ts ├── interpreter │ ├── utils.ts │ ├── types.ts │ ├── builtins.ts │ ├── values.ts │ └── interpreter.ts ├── cli.ts ├── language-service.ts ├── binder.ts ├── visitor.ts ├── scanner.ts ├── factory.ts ├── utils.ts └── types.ts ├── tsconfig.json ├── scripts └── build.js ├── .github └── workflows │ └── build.yml ├── package.json ├── .vscode └── launch.json ├── LICENSE ├── README.md └── .gitignore /tests/cases/should-work-with-null.feeny: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry="https://registry.yarnpkg.com" -------------------------------------------------------------------------------- /cli/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/cli') -------------------------------------------------------------------------------- /tests/cases/should-work-with-global-variable.feeny: -------------------------------------------------------------------------------- 1 | var x = 42 -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-null.feeny: -------------------------------------------------------------------------------- 1 | var a: null = 1 -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-integer.feeny: -------------------------------------------------------------------------------- 1 | var a: integer = 1 -------------------------------------------------------------------------------- /tests/cases/should-work-with-variable-reference.feeny: -------------------------------------------------------------------------------- 1 | var x = 1 2 | x -------------------------------------------------------------------------------- /tests/cases/should-work-with-arrays.feeny: -------------------------------------------------------------------------------- 1 | array (10, 0) 2 | array (10) -------------------------------------------------------------------------------- /tests/cases/should-work-with-printf.feeny: -------------------------------------------------------------------------------- 1 | printf (" My age is ~.\ n " , 8) -------------------------------------------------------------------------------- /tests/cases/should-work-with-function.feeny: -------------------------------------------------------------------------------- 1 | defn f(x, y, z ): 2 | x + y -------------------------------------------------------------------------------- /tests/cases/should-work-with-get-shorthand.feeny: -------------------------------------------------------------------------------- 1 | var a = array(2) 2 | 3 | a[1] -------------------------------------------------------------------------------- /tests/cases/should-work-with-integer.feeny: -------------------------------------------------------------------------------- 1 | 1 2 | 42 3 | 2333333333 4 | -1 5 | 0 -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingwl/feeny-ts/HEAD/tests/.DS_Store -------------------------------------------------------------------------------- /tests/cases/should-work-with-function-call.feeny: -------------------------------------------------------------------------------- 1 | defn f(v): 2 | v 3 | 4 | f(10) -------------------------------------------------------------------------------- /tests/cases/should-work-with-set-shorthand.feeny: -------------------------------------------------------------------------------- 1 | var a = array(10) 2 | 3 | a[1] = 0 -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-arrays.feeny: -------------------------------------------------------------------------------- 1 | var a: array(integer) = array(10, 0) -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-typedef.feeny: -------------------------------------------------------------------------------- 1 | typedef foo: 2 | var a: integer -------------------------------------------------------------------------------- /tests/cases/should-work-with-variable-assignment.feeny: -------------------------------------------------------------------------------- 1 | var x = 1 2 | 3 | x = 42 -------------------------------------------------------------------------------- /tests/demo/hello.feeny: -------------------------------------------------------------------------------- 1 | defn main () : 2 | printf("Hello World\n") 3 | 4 | main() -------------------------------------------------------------------------------- /tests/cases/should-work-with-local-variable.feeny: -------------------------------------------------------------------------------- 1 | defn foo(): 2 | var x = 42 3 | -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-variable-slot.feeny: -------------------------------------------------------------------------------- 1 | var a = object: 2 | var foo: integer = 1 -------------------------------------------------------------------------------- /extension/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingwl/feeny-ts/HEAD/extension/images/icon.png -------------------------------------------------------------------------------- /tests/demo/hello2.feeny: -------------------------------------------------------------------------------- 1 | defn main () : 2 | printf("Hello ") 3 | printf("World\n") 4 | 5 | main() -------------------------------------------------------------------------------- /tests/cases/should-work-with-if-expression-with-if-only.feeny: -------------------------------------------------------------------------------- 1 | var x = 1 2 | 3 | if x: 4 | printf("A") -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-function-parameter.feeny: -------------------------------------------------------------------------------- 1 | defn foo(a: integer) -> integer: 2 | a + 1 -------------------------------------------------------------------------------- /tests/cases/should-work-with-method-call.feeny: -------------------------------------------------------------------------------- 1 | var o = object: 2 | method f(x): 3 | x 4 | 5 | o.f(10) -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-function-statement.feeny: -------------------------------------------------------------------------------- 1 | defn foo() -> null: 2 | printf("foo") 3 | null -------------------------------------------------------------------------------- /tests/cases/should-work-with-while-expression.feeny: -------------------------------------------------------------------------------- 1 | var x = 3 2 | var y = -1 3 | 4 | while x: 5 | x = x + y -------------------------------------------------------------------------------- /tests/cases/should-work-with-function-expression.feeny: -------------------------------------------------------------------------------- 1 | var fn = defn foo(x): 2 | x + 1 3 | 4 | printf("", fn(2)) 5 | -------------------------------------------------------------------------------- /tests/cases/should-work-with-if-expression.feeny: -------------------------------------------------------------------------------- 1 | var x = 1 2 | 3 | if x: 4 | printf("A") 5 | else: 6 | printf("B") -------------------------------------------------------------------------------- /tests/cases/should-work-with-object-without-extends.feeny: -------------------------------------------------------------------------------- 1 | object: 2 | var x = 10 3 | method f(): 4 | this.x -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-function-expression.feeny: -------------------------------------------------------------------------------- 1 | var a = defn foo() -> integer: 2 | printf("foo") 3 | 42 -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-method-signature.feeny: -------------------------------------------------------------------------------- 1 | typedef foo: 2 | var a: integer 3 | method f() -> integer 4 | -------------------------------------------------------------------------------- /extension/images/github-dark-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kingwl/feeny-ts/HEAD/extension/images/github-dark-default.png -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-function-call.feeny: -------------------------------------------------------------------------------- 1 | defn add(x: integer, y: integer) -> integer : 2 | x + y 3 | 4 | var a = add(1, 2) -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-variable-reference.feeny: -------------------------------------------------------------------------------- 1 | typedef foo: 2 | var a: integer 3 | 4 | var a: foo = object: 5 | var a = 1 6 | -------------------------------------------------------------------------------- /tests/demo/hello9.feeny: -------------------------------------------------------------------------------- 1 | defn main () : 2 | var i = 0 3 | while i : 4 | printf("Do one iteration\n") 5 | i = null 6 | 7 | main() -------------------------------------------------------------------------------- /tests/cases/should-work-with-objects.feeny: -------------------------------------------------------------------------------- 1 | var p = object: 2 | var y = 42 3 | 4 | object(p): 5 | var x = 10 6 | method f(): 7 | this.x -------------------------------------------------------------------------------- /extension/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.0.1] 4 | 5 | * Initial release with syntax highlighting, brace completion, comment toggling, and indentation. -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /tests/cases/should-work-with-type-method-slot.feeny: -------------------------------------------------------------------------------- 1 | var a = object: 2 | var foo: integer = 1 3 | method bar(x: integer) -> integer: 4 | this.foo + x + 1 -------------------------------------------------------------------------------- /tests/cases/should-work-with-nested-function.feeny: -------------------------------------------------------------------------------- 1 | var m = 1 2 | defn foo(x): 3 | var y = 1 4 | defn bar(z): 5 | m + x + y + z + 1 6 | bar 7 | printf("", foo(1)(1)) -------------------------------------------------------------------------------- /tests/cases/should-work-with-objects-and-slots.feeny: -------------------------------------------------------------------------------- 1 | var p = object: 2 | var y = 42 3 | 4 | object(p): 5 | var x = 10 6 | method f(x, y, z): 7 | this.x + x + y + z -------------------------------------------------------------------------------- /tests/demo/hello3.feeny: -------------------------------------------------------------------------------- 1 | defn hello () : 2 | printf("Hello ") 3 | 4 | defn world () : 5 | printf("World\n") 6 | 7 | defn main () : 8 | hello() 9 | world() 10 | 11 | main() -------------------------------------------------------------------------------- /tests/cases/should-work-with-binary-shorthand.feeny: -------------------------------------------------------------------------------- 1 | var x = 1 2 | var y = 2 3 | 4 | x + y 5 | x - y 6 | x * y 7 | x / y 8 | x % y 9 | x < y 10 | x > y 11 | x <= y 12 | x >= y 13 | x == y 14 | -------------------------------------------------------------------------------- /tests/demo/hello4.feeny: -------------------------------------------------------------------------------- 1 | defn hello (i) : 2 | printf("Hello ~ ", i) 3 | 4 | defn world (j, k) : 5 | printf("World ~ ~\n", j, k) 6 | 7 | defn main () : 8 | hello(3) 9 | world(4, 5) 10 | 11 | main() -------------------------------------------------------------------------------- /tests/cases/should-work-with-slot-lookup.feeny: -------------------------------------------------------------------------------- 1 | var o = object: 2 | var field = 2 3 | 4 | var a = object: 5 | var b = object: 6 | var c = object: 7 | var d = 42 8 | 9 | o.field 10 | 11 | a.b.c.d -------------------------------------------------------------------------------- /tests/demo/hello8.feeny: -------------------------------------------------------------------------------- 1 | defn main () : 2 | if null : 3 | printf("Hello") 4 | else : 5 | printf("World") 6 | 7 | if 0 : 8 | printf("Hello\n") 9 | else : 10 | printf("World\n") 11 | 12 | main() -------------------------------------------------------------------------------- /tests/cases/should-work-with-slot-assignment.feeny: -------------------------------------------------------------------------------- 1 | var o = object: 2 | var field = 2 3 | 4 | var a = object: 5 | var b = object: 6 | var c = object: 7 | var d = 2 8 | 9 | o.field = 42 10 | 11 | a.b.c.d = 42 -------------------------------------------------------------------------------- /tests/demo/hello5.feeny: -------------------------------------------------------------------------------- 1 | defn three () : 2 | 3 3 | 4 | defn hello (i) : 5 | printf("Hello ~ ", i) 6 | 7 | defn world (j, k) : 8 | printf("World ~ ~\n", j, k) 9 | 10 | defn main () : 11 | hello(three()) 12 | world(4, three()) 13 | 14 | main() -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | module.exports = { 5 | parser: 'typescript', 6 | semi: true, 7 | singleQuote: true, 8 | trailingComma: 'none', 9 | endOfLine: 'crlf', 10 | arrowParens: 'avoid' 11 | } -------------------------------------------------------------------------------- /tests/cases/should-work-with-break-and-continue.feeny: -------------------------------------------------------------------------------- 1 | var x = 4 2 | while x: 3 | x = x - 1 4 | printf("while start", x) 5 | if x == 2: 6 | continue 7 | if x == 1: 8 | break 9 | printf("while end") 10 | 11 | printf("after", x) 12 | -------------------------------------------------------------------------------- /tests/demo/hello7.feeny: -------------------------------------------------------------------------------- 1 | defn three () : 2 | 3 3 | 4 | defn hello (i) : 5 | printf("Hello ~ ", i) 6 | 7 | defn world (j, k) : 8 | printf("World ~ ~\n", j, k) 9 | 10 | defn main () : 11 | var i = three() 12 | hello(i) 13 | i = 10 14 | world(4, i) 15 | 16 | main() -------------------------------------------------------------------------------- /tests/demo/hello6.feeny: -------------------------------------------------------------------------------- 1 | defn three () : 2 | 3 3 | 4 | defn hello (i) : 5 | printf("Hello ~ ", i) 6 | 7 | defn world (j, k) : 8 | printf("World ~ ~\n", j, k) 9 | 10 | defn main () : 11 | var i = three() 12 | hello(i) 13 | var j = 10 14 | world(4, j) 15 | 16 | main() -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './scanner'; 3 | export * from './parser'; 4 | export * from './factory'; 5 | export * from './binder'; 6 | export * from './visitor'; 7 | export * from './checker'; 8 | export * from './language-service' 9 | export * from './interpreter/interpreter'; 10 | -------------------------------------------------------------------------------- /tests/parser.spec.ts: -------------------------------------------------------------------------------- 1 | import type {} from 'jest'; 2 | import { forEachCases, parseCode } from './utils'; 3 | 4 | describe('Parser', () => { 5 | forEachCases((baseName, content) => { 6 | it(baseName, () => { 7 | const file = parseCode(content); 8 | expect(file).toMatchSnapshot(); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/scanner.spec.ts: -------------------------------------------------------------------------------- 1 | import type {} from 'jest'; 2 | import { forEachCases, scanCode } from './utils'; 3 | 4 | describe('Scanner', () => { 5 | forEachCases((baseName, content) => { 6 | it(baseName, () => { 7 | const tokens = scanCode(content); 8 | expect(tokens).toMatchSnapshot(); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "moduleResolution": "node", 5 | "module": "esnext", 6 | "target": "esnext", 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "emitDeclarationOnly": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true 12 | }, 13 | "include": [ 14 | "src" 15 | ] 16 | } -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild'); 2 | 3 | esbuild.build({ 4 | entryPoints: [ 5 | './src/index.ts' 6 | ], 7 | bundle: true, 8 | format: 'cjs', 9 | sourcemap: true, 10 | outdir: './dist', 11 | }) 12 | 13 | esbuild.build({ 14 | entryPoints: [ 15 | './src/cli.ts' 16 | ], 17 | format: 'cjs', 18 | sourcemap: true, 19 | outdir: './dist', 20 | platform: 'node' 21 | }) 22 | -------------------------------------------------------------------------------- /extension/scripts/build.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild'); 2 | const path = require('path'); 3 | 4 | esbuild.build({ 5 | entryPoints: [ 6 | path.resolve(__dirname, '../src/server.ts'), 7 | path.resolve(__dirname, '../src/client.ts') 8 | ], 9 | bundle: true, 10 | platform: 'node', 11 | format: 'cjs', 12 | outdir: path.resolve(__dirname, '../dist'), 13 | external: [ 14 | 'vscode' 15 | ] 16 | }) -------------------------------------------------------------------------------- /tests/binder.spec.ts: -------------------------------------------------------------------------------- 1 | import { forEachCases, bindCode } from './utils'; 2 | 3 | describe('Binder', () => { 4 | forEachCases((baseName, content) => { 5 | const { localsResult, membersResult } = bindCode(content); 6 | 7 | it(`${baseName} - locals`, () => { 8 | expect(localsResult).toMatchSnapshot(); 9 | }); 10 | 11 | it(`${baseName} - members`, () => { 12 | expect(membersResult).toMatchSnapshot(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Yarn install 15 | run: yarn 16 | - name: Yarn build 17 | run: yarn build 18 | - name: Yarn lint 19 | run: yarn lint 20 | - name: Yarn test 21 | run: yarn test 22 | -------------------------------------------------------------------------------- /tests/checker.spec.ts: -------------------------------------------------------------------------------- 1 | import type {} from 'jest'; 2 | import { forEachCases, checkCode } from './utils'; 3 | 4 | describe('Checker', () => { 5 | forEachCases((baseName, content) => { 6 | const [result, diagnostics] = checkCode(content); 7 | it(`types - ${baseName}`, () => { 8 | expect(result).toMatchSnapshot(); 9 | }); 10 | 11 | it(`diagnostics - ${baseName}`, () => { 12 | expect(diagnostics).toMatchSnapshot(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/intepreter.spec.ts: -------------------------------------------------------------------------------- 1 | import { forEachCases, runWithStdoutHook } from './utils'; 2 | 3 | describe('Interpreter', () => { 4 | it('Should work with hello world', () => { 5 | const code = `printf("hello world")`; 6 | const stdout = runWithStdoutHook(code); 7 | expect(stdout).toMatchSnapshot(); 8 | }); 9 | 10 | forEachCases((baseName, content) => { 11 | it(baseName, () => { 12 | const stdout = runWithStdoutHook(content); 13 | expect(stdout).toMatchSnapshot(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /extension/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "args": [ 9 | "--extensionDevelopmentPath=${workspaceFolder}" 10 | ], 11 | "name": "Launch Extension", 12 | "outFiles": [ 13 | "${workspaceFolder}/dist/**/*.js" 14 | ], 15 | "request": "launch", 16 | "type": "pwa-extensionHost" 17 | } 18 | 19 | ] 20 | } -------------------------------------------------------------------------------- /tests/demo.spec.ts: -------------------------------------------------------------------------------- 1 | import { forEachDemo, parseCode, runWithStdoutHook, scanCode } from './utils'; 2 | 3 | describe('Should work with demo', () => { 4 | forEachDemo((baseName, content) => { 5 | it(`Scanner - should work with ${baseName}`, () => { 6 | const tokens = scanCode(content); 7 | expect(tokens).toMatchSnapshot(); 8 | }); 9 | 10 | it(`Parser - should work with ${baseName}`, () => { 11 | const file = parseCode(content); 12 | expect(file).toMatchSnapshot(); 13 | }); 14 | 15 | it(`Interpreter - should work with ${baseName}`, () => { 16 | const output = runWithStdoutHook(content); 17 | expect(output).toMatchSnapshot(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feeny-ts", 3 | "version": "0.0.4", 4 | "main": "./dist/index.js", 5 | "types": "./dist/index.d.ts", 6 | "bin": "./cli/bin.js", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "@types/jest": "^27.0.3", 10 | "esbuild": "^0.14.1", 11 | "jest": "^27.4.3", 12 | "prettier": "^2.5.1", 13 | "ts-jest": "^27.0.7", 14 | "typescript": "^4.5.2" 15 | }, 16 | "scripts": { 17 | "build:dts": "tsc", 18 | "build:bundle": "node ./scripts/build.js", 19 | "build": "yarn build:bundle && yarn build:dts", 20 | "lint": "prettier ./src/**/*.ts ./tests/**/*.ts -c", 21 | "prettier": "yarn lint -w", 22 | "test": "jest" 23 | }, 24 | "files": [ 25 | "dist", 26 | "cli" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/cli.js", 15 | "args": [ 16 | "${file}" 17 | ], 18 | "outFiles": [ 19 | "${workspaceFolder}/dist/**/*.js" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /tests/demo/inheritance.feeny: -------------------------------------------------------------------------------- 1 | defn base () : 2 | object : 3 | method do-op (x, y) : 4 | printf("Do a binary operation on ~ and ~.\n", x, y) 5 | printf("Result = ~.\n", this.op(x, y)) 6 | 7 | defn adder () : 8 | object(base()) : 9 | method op (x, y) : 10 | x + y 11 | 12 | defn multiplier () : 13 | object(base()) : 14 | method op (x, y) : 15 | x * y 16 | 17 | var a = adder() 18 | var m = multiplier() 19 | a.do-op(11, 7) 20 | m.do-op(11, 7) 21 | 22 | 23 | 24 | ;============================================================ 25 | ;====================== OUTPUT ============================== 26 | ;============================================================ 27 | ; 28 | ;Do a binary operation on 11 and 7. 29 | ;Result = 18. 30 | ;Do a binary operation on 11 and 7. 31 | ;Result = 77. -------------------------------------------------------------------------------- /extension/README.md: -------------------------------------------------------------------------------- 1 | # Feeny 2 | 3 | This package provides basic language support for Feeny. 4 | Feeny is a small programming languages for learning how to implement dynamic languages and their runtimes. 5 | 6 | Feeny was introduced in the U.C. Berkeley course [*Virtual Machines and Managed Runtimes*](http://www.wolczko.com/CS294/index.html), taught by Mario Wolczko and Patrick S. Li. 7 | Its syntax is largely inspired by another language called [Stanza](http://lbstanza.org/). 8 | 9 | You can read more about Feeny [on the course materials home page](http://www.wolczko.com/CS294/index.html), or on [the implementation's GitHub repository](https://github.com/CuppoJava/Feeny). 10 | 11 | ## Features 12 | 13 | This package includes support for 14 | 15 | * Syntax highlighting 16 | * Brace completion 17 | * Smart indentation 18 | * Comment toggling 19 | 20 | ![Feeny syntax highlighting under the GitHub Dark Default theme.](./images/github-dark-default.png) 21 | -------------------------------------------------------------------------------- /src/interpreter/utils.ts: -------------------------------------------------------------------------------- 1 | import { assertDef } from '../utils'; 2 | import { VarValues, CodeValues } from './types'; 3 | import { BaseValue } from './values'; 4 | 5 | export function assertThisValue( 6 | value: T | undefined 7 | ): asserts value is NonNullable { 8 | assertDef(value, "'This' cannot be null"); 9 | } 10 | 11 | export function assertArgumentsLength(expected: number, actual: number) { 12 | if (expected !== actual) { 13 | throw new Error( 14 | `Arguments mis match, expected ${expected}, actual: ${actual}` 15 | ); 16 | } 17 | } 18 | 19 | export function isVarValues(value: BaseValue): value is VarValues { 20 | return ( 21 | value.isInteger() || 22 | value.isNull() || 23 | value.isObject() || 24 | value.isArray() || 25 | value.isString() 26 | ); 27 | } 28 | 29 | export function isCodeValues(value: BaseValue): value is CodeValues { 30 | return value.isFunction(); 31 | } 32 | -------------------------------------------------------------------------------- /tests/demo/fibonacci.feeny: -------------------------------------------------------------------------------- 1 | defn fib (n) : 2 | if n == 0 : 3 | 1 4 | else if n == 1 : 5 | 1 6 | else : 7 | var a = 1 8 | var b = 1 9 | while n >= 2 : 10 | var c = a + b 11 | a = b 12 | b = c 13 | n = n - 1 14 | b 15 | 16 | defn main () : 17 | var i = 0 18 | while i < 20 : 19 | printf("Fib(~) = ~\n", i, fib(i)) 20 | i = i + 1 21 | 22 | main() 23 | 24 | 25 | 26 | ;============================================================ 27 | ;====================== OUTPUT ============================== 28 | ;============================================================ 29 | ; 30 | ;Fib(0) = 1 31 | ;Fib(1) = 1 32 | ;Fib(2) = 2 33 | ;Fib(3) = 3 34 | ;Fib(4) = 5 35 | ;Fib(5) = 8 36 | ;Fib(6) = 13 37 | ;Fib(7) = 21 38 | ;Fib(8) = 34 39 | ;Fib(9) = 55 40 | ;Fib(10) = 89 41 | ;Fib(11) = 144 42 | ;Fib(12) = 233 43 | ;Fib(13) = 377 44 | ;Fib(14) = 610 45 | ;Fib(15) = 987 46 | ;Fib(16) = 1597 47 | ;Fib(17) = 2584 48 | ;Fib(18) = 4181 49 | ;Fib(19) = 6765 -------------------------------------------------------------------------------- /extension/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. 4 | "lineComment": ";" 5 | }, 6 | // Symbols used as brackets. 7 | "brackets": [ 8 | ["[", "]"], 9 | ["(", ")"] 10 | ], 11 | // Symbols that are auto closed when typing. 12 | "autoClosingPairs": [ 13 | ["[", "]"], 14 | ["(", ")"], 15 | ["\"", "\""] 16 | ], 17 | // Symbols that can be used to surround a selection. 18 | "surroundingPairs": [ 19 | ["[", "]"], 20 | ["(", ")"], 21 | ["\"", "\""] 22 | ], 23 | // Indent after any ':' that's not part of a comment. 24 | "onEnterRules": [ 25 | { 26 | "beforeText": "^[^;]*:\\s*(?:;.+)?$", 27 | "action": { 28 | "indent": "indent" 29 | } 30 | } 31 | ], 32 | // Lots of valid identifiers in Feeny - but not a lot worth auto-completing. 33 | "wordPattern": "[a-zA-Z][a-zA-Z\\+\\-\\*\\?/%<>=]*", 34 | } -------------------------------------------------------------------------------- /tests/demo/bsearch.feeny: -------------------------------------------------------------------------------- 1 | defn bshelper (xs, i, n, v) : 2 | if n == 1 : 3 | if xs[i] == v : i 4 | else : -1 5 | else : 6 | var n1 = n / 2 7 | var a = xs[i + n1 - 1] 8 | if a < v : bshelper(xs, i + n1, n - n1, v) 9 | else : bshelper(xs, i, n1, v) 10 | 11 | defn bs (xs, v) : 12 | if xs.length() == 0 : -1 13 | else : bshelper(xs, 0, xs.length(), v) 14 | 15 | 16 | var a = array(10, 0) 17 | a[0] = 4 18 | a[1] = 6 19 | a[2] = 9 20 | a[3] = 13 21 | a[4] = 15 22 | a[5] = 17 23 | a[6] = 18 24 | a[7] = 24 25 | a[8] = 29 26 | a[9] = 35 27 | 28 | defn find (v) : 29 | printf("The index of ~ in a is ~.\n", v, bs(a, v)) 30 | 31 | find(13) 32 | find(14) 33 | find(15) 34 | find(29) 35 | find(30) 36 | 37 | 38 | ;============================================================ 39 | ;====================== OUTPUT ============================== 40 | ;============================================================ 41 | ;The index of 13 in a is 3. 42 | ;The index of 14 in a is -1. 43 | ;The index of 15 in a is 4. 44 | ;The index of 29 in a is 8. 45 | ;The index of 30 in a is -1. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Wenlu Wang 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. -------------------------------------------------------------------------------- /extension/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Rosenwasser 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 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { createParser, createInterpreter } from './index'; 4 | import { Context } from './interpreter/types'; 5 | 6 | function main() { 7 | if (process.argv.length < 3) { 8 | console.error('Input missing.'); 9 | return; 10 | } 11 | 12 | const file = process.argv[2]; 13 | if (!file) { 14 | console.error(`Invalid input '${file}'.`); 15 | return; 16 | } 17 | 18 | const filePath = path.resolve(file); 19 | if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { 20 | console.error(`File '${filePath}' not existed.`); 21 | return; 22 | } 23 | 24 | const content = fs.readFileSync(filePath).toString(); 25 | if (!content) { 26 | console.error(`File '${filePath}' is empty.`); 27 | return; 28 | } 29 | 30 | const parser = createParser(content); 31 | const sourceFile = parser.parse(); 32 | const context: Context = { 33 | stdout: text => process.stdout.write(text, 'utf-8') 34 | } 35 | const interpreter = createInterpreter(sourceFile, context); 36 | interpreter.evaluate(); 37 | } 38 | 39 | main(); 40 | -------------------------------------------------------------------------------- /tests/__snapshots__/language-service.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Language service 1 Definition of 47 1`] = `"bar"`; 4 | 5 | exports[`Language service 1 Definition of 51 1`] = `"a"`; 6 | 7 | exports[`Language service 1 Definition of 57 1`] = `"b"`; 8 | 9 | exports[`Language service 2 Definition of 71 1`] = `"bar"`; 10 | 11 | exports[`Language service 2 Definition of 75 1`] = `"a"`; 12 | 13 | exports[`Language service 2 Definition of 81 1`] = `"b"`; 14 | 15 | exports[`Language service 2 Definition of 89 1`] = `"c"`; 16 | 17 | exports[`Language service 3 Definition of 110 1`] = `"baz"`; 18 | 19 | exports[`Language service 3 Definition of 114 1`] = `"a"`; 20 | 21 | exports[`Language service 3 Definition of 120 1`] = `"b"`; 22 | 23 | exports[`Language service 3 Definition of 128 1`] = `"c"`; 24 | 25 | exports[`Language service 3 Definition of 134 1`] = `"c"`; 26 | 27 | exports[`Language service Definition of 71 1`] = `"foo"`; 28 | 29 | exports[`Language service Definition of 77 1`] = `"bar"`; 30 | 31 | exports[`Language service Definition of 81 1`] = `"a"`; 32 | 33 | exports[`Language service Definition of 87 1`] = `"b"`; 34 | -------------------------------------------------------------------------------- /tests/demo/cplx.feeny: -------------------------------------------------------------------------------- 1 | defn cplx (r, i) : 2 | object : 3 | var real = r 4 | var imag = i 5 | method add (c) : 6 | cplx(this.real + c.real, 7 | this.imag + c.imag) 8 | method sub (c) : 9 | cplx(this.real - c.real, 10 | this.imag - c.imag) 11 | method mul (c) : 12 | cplx(this.real * c.real - this.imag * c.imag, 13 | this.real * c.imag + this.imag * c.real) 14 | method div (c) : 15 | var d = c.real * c.real + c.imag * c.imag 16 | cplx((this.real * c.real + this.imag * c.imag) / d, 17 | (this.imag * c.real - this.real * c.imag) / d) 18 | method print () : 19 | if this.imag < 0 : 20 | printf("~ - ~i", this.real, 0 - this.imag) 21 | else if this.imag == 0 : 22 | printf("~", this.real) 23 | else : 24 | printf("~ + ~i", this.real, this.imag) 25 | 26 | defn main () : 27 | var a = cplx(2, 5) 28 | var b = cplx(-4, 2) 29 | 30 | printf("a / b = ") 31 | (a / b).print() 32 | printf("\n") 33 | 34 | main() 35 | 36 | 37 | 38 | ;============================================================ 39 | ;====================== OUTPUT ============================== 40 | ;============================================================ 41 | ; 42 | ;a = 2 + 5i 43 | ;b = -4 + 2i 44 | ;a + b = -2 + 7i 45 | ;a - b = 6 + 3i 46 | ;a * b = -18 - 16i 47 | ;a / b = 0 - 1i -------------------------------------------------------------------------------- /extension/src/client.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { workspace, ExtensionContext } from 'vscode'; 3 | 4 | import { 5 | LanguageClient, 6 | LanguageClientOptions, 7 | ServerOptions, 8 | TransportKind 9 | } from 'vscode-languageclient/node'; 10 | 11 | let client: LanguageClient; 12 | 13 | export function activate(context: ExtensionContext) { 14 | const serverModule = context.asAbsolutePath(path.join( 'dist', 'server.js')); 15 | const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; 16 | 17 | const serverOptions: ServerOptions = { 18 | run: { module: serverModule, transport: TransportKind.ipc }, 19 | debug: { 20 | module: serverModule, 21 | transport: TransportKind.ipc, 22 | options: debugOptions 23 | } 24 | }; 25 | 26 | const clientOptions: LanguageClientOptions = { 27 | documentSelector: [{ scheme: 'file', language: 'feeny' }], 28 | synchronize: { 29 | fileEvents: workspace.createFileSystemWatcher('**/.feeny') 30 | } 31 | }; 32 | 33 | client = new LanguageClient( 34 | 'feeny', 35 | 'Feeny language features', 36 | serverOptions, 37 | clientOptions 38 | ); 39 | 40 | client.start(); 41 | } 42 | 43 | export function deactivate(): Thenable | undefined { 44 | if (!client) { 45 | return undefined; 46 | } 47 | return client.stop(); 48 | } 49 | -------------------------------------------------------------------------------- /extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feeny", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "main": "./dist/client.js", 6 | "activationEvents": [ 7 | "onLanguage:feeny" 8 | ], 9 | "description": "Basic syntax support for Feeny - a programming language for teaching purposes.", 10 | "author": { 11 | "name": "Daniel Rosenwasser", 12 | "url": "https://github.com/DanielRosenwasser" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/DanielRosenwasser/Feeny-VSCode" 17 | }, 18 | "scripts": { 19 | "package": "vsce package", 20 | "build": "node ./scripts/build.js" 21 | }, 22 | "devDependencies": { 23 | "esbuild": "^0.14.2", 24 | "typescript": "^4.5.3", 25 | "vsce": "^1.100.1" 26 | }, 27 | "displayName": "Feeny", 28 | "icon": "images/icon.png", 29 | "publisher": "DanielRosenwasser", 30 | "engines": { 31 | "vscode": "^1.60.0" 32 | }, 33 | "categories": [ 34 | "Programming Languages" 35 | ], 36 | "contributes": { 37 | "languages": [ 38 | { 39 | "id": "feeny", 40 | "aliases": [ 41 | "Feeny", 42 | "feeny" 43 | ], 44 | "extensions": [ 45 | "feeny" 46 | ], 47 | "configuration": "./language-configuration.json" 48 | } 49 | ], 50 | "grammars": [ 51 | { 52 | "language": "feeny", 53 | "scopeName": "source.feeny", 54 | "path": "./syntaxes/feeny.tmLanguage.json" 55 | } 56 | ] 57 | }, 58 | "dependencies": { 59 | "feeny-ts": "link:../", 60 | "vscode-languageclient": "^7.0.0", 61 | "vscode-languageserver": "^7.0.0", 62 | "vscode-languageserver-textdocument": "^1.0.3" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/demo/lists.feeny: -------------------------------------------------------------------------------- 1 | var nil = object : 2 | method nil? () : 3 | 1 4 | method print () : 5 | printf("()") 6 | method length () : 7 | 0 8 | method append (b) : 9 | b 10 | method reverse () : 11 | this 12 | 13 | defn cons (a, b) : 14 | object : 15 | var head = a 16 | var tail = b 17 | method nil? () : 18 | 0 19 | 20 | method print () : 21 | printf("(~", this.head) 22 | var rest = this.tail 23 | while rest.nil?() == 0 : 24 | printf(" ~", rest.head) 25 | rest = rest.tail 26 | printf(")") 27 | 28 | method length () : 29 | 1 + this.tail.length() 30 | 31 | method append (b) : 32 | cons(this.head, this.tail.append(b)) 33 | 34 | method reverse () : 35 | this.tail.reverse().append(cons(this.head, nil)) 36 | 37 | defn nl () : 38 | printf("\n") 39 | 40 | defn main () : 41 | printf("List A\n") 42 | var a = cons(1, cons(2, cons(3, cons(4, nil)))) 43 | a.print() 44 | nl() 45 | 46 | printf("List B\n") 47 | var b = cons(10, cons(9, cons(8, cons(7, cons(6, cons(5, nil)))))) 48 | b.print() 49 | nl() 50 | 51 | printf("a.length() = ~\n", a.length()) 52 | printf("b.length() = ~\n", b.length()) 53 | 54 | var c = a.append(b) 55 | printf("a.append(b) = ") 56 | c.print() 57 | nl() 58 | 59 | var ar = a.reverse() 60 | var br = b.reverse() 61 | printf("a reversed = ") 62 | ar.print() 63 | nl() 64 | printf("b reversed = ") 65 | br.print() 66 | nl() 67 | 68 | main() 69 | 70 | 71 | 72 | 73 | ;============================================================ 74 | ;====================== Output ============================== 75 | ;============================================================ 76 | ; 77 | ;List A 78 | ;(1 2 3 4) 79 | ;List B 80 | ;(10 9 8 7 6 5) 81 | ;a.length() = 4 82 | ;b.length() = 6 83 | ;a.append(b) = (1 2 3 4 10 9 8 7 6 5) 84 | ;a reversed = (4 3 2 1) 85 | ;b reversed = (5 6 7 8 9 10) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feeny-ts 2 | 3 | ## Summary 4 | 5 | Feeny is a small programming language for learning how to implement dynamic languages and their runtimes. 6 | 7 | Feeny was introduced in the U.C. Berkeley course [Virtual Machines and Managed Runtimes](http://www.wolczko.com/CS294/index.html), taught by Mario Wolczko and Patrick S. Li. Its syntax is largely inspired by another language called Stanza. 8 | 9 | This package provides `parsing tools` and an `interpreter` written in pure `TypeScript`. We can use it in the web toolchains. Obviously, It can run in either browser or node. Or some other JavaScript Runtime. 10 | 11 | And we have extended Feeny to add some syntax (`break/continue`, `closure`, etc.) and a simple type checker(and also type annitations). It's too simple and even not have a type system yet. But I think it will be better in the future. 12 | 13 | And we also provide `language service toolchains` and a `VSCode extension` that based on above features. It only supports `diagnostics` (for type check result) and `goToDefinition` for now. 14 | 15 | The codebase is inspired by TypeScript compiler codebase. So you may can see many familiar code if you are also familiar with TypeScript. 16 | 17 | The VSCode extension is based on Daniel's [Feeny-VSCode](https://github.com/DanielRosenwasser/Feeny-VSCode) which is also a brilliant package. 18 | 19 | Happy hacking! 20 | 21 | ## Features 22 | 23 | - Lexer 24 | - Parser 25 | - Parsing 26 | - Syntax extend 27 | - Function expression 28 | - Closure 29 | - Break / Continue expression 30 | 31 | - Binder 32 | - Symbol tables 33 | - Get symbol from node 34 | 35 | - Checker 36 | - Type annotations 37 | - Basic type check 38 | - ~~Type system~~ 39 | 40 | - AST Interpreter 41 | - Feeny features 42 | - Closure 43 | - Break / Continue 44 | 45 | - Cli 46 | - Run feeny files 47 | 48 | - Language service api 49 | - Get diagnostics 50 | - GoToDefinition 51 | 52 | - VSCode extension 53 | - All features in [Feeny-VSCode](https://github.com/DanielRosenwasser/Feeny-VSCode) 54 | - Client / Server 55 | - Diagnostics 56 | - GoToDefinition 57 | -------------------------------------------------------------------------------- /src/interpreter/types.ts: -------------------------------------------------------------------------------- 1 | import { isVarValues, isCodeValues } from './utils'; 2 | import { 3 | IntegerValue, 4 | ArrayValue, 5 | ObjectValue, 6 | NullValue, 7 | BooleanValue, 8 | FunctionValue, 9 | BuiltinFunction, 10 | BaseValue 11 | } from './values'; 12 | 13 | export interface Context { 14 | stdout: (str: string) => void; 15 | } 16 | 17 | export enum ValueType { 18 | Null, 19 | Boolean, 20 | Array, 21 | Object, 22 | Function, 23 | Integer, 24 | String 25 | } 26 | 27 | export type VarValues = 28 | | IntegerValue 29 | | ArrayValue 30 | | ObjectValue 31 | | NullValue 32 | | BooleanValue; 33 | export type CodeValues = FunctionValue | BuiltinFunction; 34 | export type AllValues = VarValues | CodeValues; 35 | 36 | export interface CallFrame { 37 | thisValue: BaseValue | undefined; 38 | name: string; 39 | envStack: Environment[]; 40 | parent: CallFrame | undefined; 41 | insideLoop: boolean; 42 | } 43 | 44 | export class Environment { 45 | private varValues = new Map(); 46 | private codeValues = new Map(); 47 | 48 | constructor(private parent?: Environment) {} 49 | 50 | private setValues(name: string, value: BaseValue) { 51 | if (isVarValues(value)) { 52 | this.varValues.set(name, value); 53 | } else if (isCodeValues(value)) { 54 | this.codeValues.set(name, value); 55 | } else { 56 | throw new Error('Invalid value type'); 57 | } 58 | } 59 | 60 | hasBinding(name: string): boolean { 61 | return this.varValues.has(name) || this.codeValues.has(name); 62 | } 63 | 64 | addBinding(name: string, value: BaseValue) { 65 | this.setValues(name, value); 66 | } 67 | 68 | setBinding(name: string, value: BaseValue) { 69 | if (!this.hasBinding(name)) { 70 | this.parent?.setBinding(name, value); 71 | } else { 72 | this.setValues(name, value); 73 | } 74 | } 75 | 76 | getBinding(name: string): BaseValue | undefined { 77 | return ( 78 | this.varValues.get(name) ?? 79 | this.codeValues.get(name) ?? 80 | this.parent?.getBinding(name) 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /.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 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | yarn.lock 107 | 108 | .DS_Store -------------------------------------------------------------------------------- /tests/demo/vector.feeny: -------------------------------------------------------------------------------- 1 | defn copy-array (dst, src, i, n) : 2 | var j = 0 3 | while j < n : 4 | dst[i + j] = src[i + j] 5 | j = j + 1 6 | dst 7 | 8 | defn max (a, b) : 9 | if a < b : b 10 | else : a 11 | 12 | defn ensure-capacity (v, c) : 13 | var n = v.array.length() 14 | if n < c : 15 | var sz = max(c, n * 2) 16 | var a = array(sz, 0) 17 | copy-array(a, v.array, 0, v.size) 18 | v.array = a 19 | 20 | defn vector () : 21 | object : 22 | var array = array(4, 0) 23 | var size = 0 24 | method add (x) : 25 | ensure-capacity(this, this.size + 1) 26 | this.array[this.size] = x 27 | this.size = this.size + 1 28 | method get (i) : 29 | this.array[i] 30 | method set (i, x) : 31 | if i == this.size : 32 | this.add(x) 33 | else : 34 | this.array[i] = x 35 | method length () : 36 | this.size 37 | method print () : 38 | if this.size == 0 : 39 | printf("[]") 40 | else : 41 | printf("[~", this.array[0]) 42 | var i = 1 43 | while i < this.size : 44 | printf(", ~", this.array[i]) 45 | i = i + 1 46 | printf("]") 47 | 48 | defn main () : 49 | printf("Create empty vector.\n") 50 | var v = vector() 51 | v.print() 52 | printf("\n") 53 | 54 | printf("Add some elements.\n") 55 | v.add(2) 56 | v.add(10) 57 | v.add(22) 58 | v.add(17) 59 | v.add(23) 60 | v.add(2) 61 | v.add(7) 62 | v.print() 63 | printf("\n") 64 | 65 | printf("Retrieving some elements.\n") 66 | printf("v[~] = ~.\n", 2, v[2]) 67 | printf("v[~] = ~.\n", 4, v[4]) 68 | printf("v[~] = ~.\n", 1, v[1]) 69 | 70 | printf("Setting first 3 elements to 0.\n") 71 | v[0] = 0 72 | v[1] = 0 73 | v[2] = 0 74 | v.print() 75 | printf("\n") 76 | 77 | main() 78 | 79 | 80 | ;============================================================ 81 | ;====================== Output ============================== 82 | ;============================================================ 83 | ; 84 | ;Create empty vector. 85 | ;[] 86 | ;Add some elements. 87 | ;[2, 10, 22, 17, 23, 2, 7] 88 | ;Retrieving some elements. 89 | ;v[2] = 22. 90 | ;v[4] = 23. 91 | ;v[1] = 10. 92 | ;Setting first 3 elements to 0. 93 | ;[0, 0, 0, 17, 23, 2, 7] -------------------------------------------------------------------------------- /src/language-service.ts: -------------------------------------------------------------------------------- 1 | import { Declaration,ObjectsExpression, FunctionStatement, MethodSlot, MethodSlotSignatureDeclaration, ParameterDeclaration, SyntaxKind, TypeDefDeclaration, VariableSlot, VariableSlotSignatureDeclaration, VariableStatement } from "./types"; 2 | import { createBinder } from "./binder"; 3 | import { createChecker } from "./checker"; 4 | import { createParser } from "./parser"; 5 | import { assertKind, findCurrentToken } from "./utils"; 6 | 7 | function getDeclarationAnchor (decl: Declaration) { 8 | switch (decl.kind) { 9 | case SyntaxKind.VariableStatement: 10 | case SyntaxKind.VariableSlot: 11 | case SyntaxKind.FunctionStatement: 12 | case SyntaxKind.MethodSlot: 13 | case SyntaxKind.ParameterDeclaration: 14 | case SyntaxKind.TypeDefDeclaration: 15 | case SyntaxKind.VariableSlotSignatureDeclaration: 16 | case SyntaxKind.MethodSlotSignatureDeclaration: 17 | assertKind(decl) 18 | return decl.name 19 | case SyntaxKind.ObjectsExpression: 20 | assertKind(decl) 21 | return decl.objectToken 22 | default: 23 | return decl 24 | } 25 | } 26 | 27 | export function createLanguageService (text: string) { 28 | const parser = createParser(text); 29 | const file = parser.parse(); 30 | const binder = createBinder(file); 31 | binder.bindFile(); 32 | const checker = createChecker(file, binder.createBuiltinSymbol); 33 | 34 | return { 35 | getDiagnostics, 36 | goToDefinition, 37 | getCurrentToken 38 | } 39 | 40 | function getDiagnostics () { 41 | return checker.diagnostics 42 | } 43 | 44 | function getCurrentToken (pos: number) { 45 | const currentToken = findCurrentToken(file, pos); 46 | return currentToken; 47 | } 48 | 49 | function goToDefinition(pos: number) { 50 | const currentToken = findCurrentToken(file, pos); 51 | if (!currentToken) { 52 | return undefined; 53 | } 54 | const symbol = checker.getSymbolAtNode(currentToken); 55 | if (!symbol?.declaration) { 56 | return undefined 57 | } 58 | return getDeclarationAnchor(symbol.declaration); 59 | } 60 | } -------------------------------------------------------------------------------- /extension/src/server.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createConnection, 3 | TextDocuments, 4 | DiagnosticSeverity, 5 | ProposedFeatures, 6 | InitializeParams, 7 | TextDocumentSyncKind, 8 | InitializeResult, 9 | Range 10 | } from 'vscode-languageserver/node'; 11 | 12 | import { TextDocument } from 'vscode-languageserver-textdocument'; 13 | 14 | import { createLanguageService, TextSpan } from 'feeny-ts' 15 | 16 | let connection = createConnection(ProposedFeatures.all); 17 | 18 | let documents: TextDocuments = new TextDocuments(TextDocument); 19 | 20 | connection.onInitialize((params: InitializeParams) => { 21 | const result: InitializeResult = { 22 | capabilities: { 23 | textDocumentSync: TextDocumentSyncKind.Full, 24 | definitionProvider: true, 25 | } 26 | }; 27 | return result; 28 | }); 29 | 30 | let server: ReturnType | undefined 31 | 32 | function textSpanToRange (doc: TextDocument, span: TextSpan) { 33 | const startPosition = doc.positionAt(span.pos) 34 | const endPosition = doc.positionAt(span.end) 35 | 36 | return Range.create( 37 | startPosition, 38 | endPosition 39 | ) 40 | } 41 | 42 | function createLsForDoc (doc: TextDocument) { 43 | server = createLanguageService(doc.getText()) 44 | const diagnostics = server.getDiagnostics() 45 | connection.sendDiagnostics({ 46 | uri: doc.uri, 47 | diagnostics: diagnostics.map(d => { 48 | return { 49 | range: textSpanToRange(doc, d.span), 50 | severity: DiagnosticSeverity.Error, 51 | message: d.message 52 | } 53 | }) 54 | }) 55 | } 56 | 57 | documents.onDidOpen(doc => { 58 | createLsForDoc(doc.document) 59 | }) 60 | 61 | documents.onDidChangeContent(change => { 62 | createLsForDoc(change.document) 63 | }) 64 | 65 | connection.onDidChangeWatchedFiles(_change => { 66 | connection.console.log('We received a file change event'); 67 | }); 68 | 69 | connection.onDefinition(async (params) => { 70 | if (!server) { 71 | return undefined; 72 | } 73 | 74 | const uri = params.textDocument.uri; 75 | const doc = documents.get(uri); 76 | const pos = doc.offsetAt(params.position); 77 | const result = server.goToDefinition(pos); 78 | if (!result) { 79 | return undefined; 80 | } 81 | 82 | const range = textSpanToRange(doc, result) 83 | result.pos 84 | return [ 85 | { 86 | uri, 87 | range 88 | } 89 | ]; 90 | }) 91 | 92 | documents.listen(connection); 93 | 94 | connection.listen(); 95 | -------------------------------------------------------------------------------- /extension/syntaxes/feeny.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Feeny", 4 | "patterns": [ 5 | { 6 | "include": "#comments" 7 | }, 8 | { 9 | "include": "#keywords" 10 | }, 11 | { 12 | "include": "#integers" 13 | }, 14 | { 15 | "include": "#square-brackets" 16 | }, 17 | { 18 | "include": "#parentheses" 19 | }, 20 | { 21 | "include": "#slot-access-operator" 22 | }, 23 | { 24 | "include": "#shorthand-operators" 25 | }, 26 | { 27 | "include": "#variables" 28 | }, 29 | { 30 | "include": "#strings" 31 | } 32 | ], 33 | "repository": { 34 | "comments": { 35 | "name": "comment.line", 36 | "match": ";.*$" 37 | }, 38 | "keywords": { 39 | "patterns": [{ 40 | "name": "keyword.control.feeny", 41 | "match": "\\b(if|else|while|var|object|array|defn|method|this|null)\\b" 42 | }] 43 | }, 44 | "integers": { 45 | "name": "constant.numeric", 46 | "match": "(?=])-?\\d+\\b" 47 | }, 48 | "square-brackets": { 49 | "name": "meta.brace.square.feeny", 50 | "match": "\\[|\\]" 51 | }, 52 | "parentheses": { 53 | "name": "meta.brace.round.feeny", 54 | "match": "\\(|\\)" 55 | }, 56 | "slot-access-operator": { 57 | "name": "punctuation.accessor.feeny", 58 | "match": "\\." 59 | }, 60 | "shorthand-operators": { 61 | "name": "keyword.operator", 62 | "comment": "It seems like in Feeny, operators are tokenized the same as identifiers - but it would be nice if they appeared distinct.", 63 | "match": "(?:(?=])(?:\\+|-|\\*|\\?|/|%|<|>|<=|>=|==|=)(?![a-zA-Z\\d\\+\\-\\*\\?/%<>=]))|:" 64 | }, 65 | "variables": { 66 | "name": "variable.name", 67 | "match": "(:?(:?[\\-][a-zA-Z\\+\\-\\*\\?/%<>=])|[a-zA-Z\\+\\*\\?/%<>=])[a-zA-Z\\d\\+\\-\\*\\?/%<>=]*" 68 | }, 69 | "strings": { 70 | "name": "string.quoted.double.feeny", 71 | "begin": "\"", 72 | "end": "\"", 73 | "patterns": [ 74 | { 75 | "name": "constant.character.escape.feeny", 76 | "match": "\\\\." 77 | }, 78 | { 79 | "name": "string.interpolated.feeny", 80 | "match": "~" 81 | } 82 | ] 83 | } 84 | }, 85 | "scopeName": "source.feeny" 86 | } -------------------------------------------------------------------------------- /tests/language-service.spec.ts: -------------------------------------------------------------------------------- 1 | import { createLanguageService } from '../src'; 2 | 3 | describe('Language service', () => { 4 | const code = ` 5 | defn foo(): 6 | object: 7 | var a = 1 8 | var b = 2 9 | var bar = foo() 10 | bar.a 11 | bar.b 12 | `; 13 | 14 | const posList = [ 15 | code.indexOf(`bar.`), 16 | code.indexOf(`bar.a`) + 'bar.'.length, 17 | code.indexOf(`bar.b`) + 'bar.'.length, 18 | code.indexOf(`= foo()`) + '= '.length 19 | ]; 20 | const ls = createLanguageService(code); 21 | posList.forEach(pos => { 22 | const decl = ls.goToDefinition(pos); 23 | it(`Definition of ${pos}`, () => { 24 | expect(decl?.__debugText).toMatchSnapshot(); 25 | }); 26 | }); 27 | }); 28 | 29 | describe('Language service 1', () => { 30 | const code = ` 31 | var bar = object: 32 | var a = 1 33 | var b = 2 34 | bar.a 35 | bar.b 36 | `; 37 | 38 | const posList = [ 39 | code.indexOf(`bar.`), 40 | code.indexOf(`bar.a`) + 'bar.'.length, 41 | code.indexOf(`bar.b`) + 'bar.'.length 42 | ]; 43 | const ls = createLanguageService(code); 44 | posList.forEach(pos => { 45 | const decl = ls.goToDefinition(pos); 46 | it(`Definition of ${pos}`, () => { 47 | expect(decl?.__debugText).toMatchSnapshot(); 48 | }); 49 | }); 50 | }); 51 | 52 | describe('Language service 2', () => { 53 | const code = ` 54 | var bar = object: 55 | var a = 1 56 | var b = object: 57 | var c = 2 58 | bar.a 59 | bar.b 60 | bar.b.c 61 | `; 62 | 63 | const posList = [ 64 | code.indexOf(`bar.`), 65 | code.indexOf(`bar.a`) + 'bar.'.length, 66 | code.indexOf(`bar.b`) + 'bar.'.length, 67 | code.indexOf(`bar.b.c`) + 'bar.b.'.length 68 | ]; 69 | const ls = createLanguageService(code); 70 | posList.forEach(pos => { 71 | const decl = ls.goToDefinition(pos); 72 | it(`Definition of ${pos}`, () => { 73 | expect(decl?.__debugText).toMatchSnapshot(); 74 | }); 75 | }); 76 | }); 77 | 78 | describe('Language service 3', () => { 79 | const code = ` 80 | var bar = object: 81 | var a = 1 82 | var b = object: 83 | var c = 2 84 | 85 | var baz = object(bar): 86 | var c = 2 87 | 88 | baz.a 89 | baz.b 90 | baz.b.c 91 | baz.c 92 | `; 93 | 94 | const posList = [ 95 | code.indexOf(`baz.`), 96 | code.indexOf(`baz.a`) + 'baz.'.length, 97 | code.indexOf(`baz.b`) + 'baz.'.length, 98 | code.indexOf(`baz.b.c`) + 'baz.b.'.length, 99 | code.indexOf(`baz.c`) + 'baz.'.length 100 | ]; 101 | const ls = createLanguageService(code); 102 | posList.forEach(pos => { 103 | const decl = ls.goToDefinition(pos); 104 | it(`Definition of ${pos}`, () => { 105 | expect(decl?.__debugText).toMatchSnapshot(); 106 | }); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /tests/__snapshots__/intepreter.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Interpreter Should work with hello world 1`] = ` 4 | Array [ 5 | "hello world", 6 | ] 7 | `; 8 | 9 | exports[`Interpreter should-work-with-arrays 1`] = `Array []`; 10 | 11 | exports[`Interpreter should-work-with-binary-shorthand 1`] = `Array []`; 12 | 13 | exports[`Interpreter should-work-with-break-and-continue 1`] = ` 14 | Array [ 15 | "while start", 16 | "3", 17 | "while end", 18 | "while start", 19 | "2", 20 | "while start", 21 | "1", 22 | "after", 23 | "1", 24 | ] 25 | `; 26 | 27 | exports[`Interpreter should-work-with-function 1`] = `Array []`; 28 | 29 | exports[`Interpreter should-work-with-function-call 1`] = `Array []`; 30 | 31 | exports[`Interpreter should-work-with-function-expression 1`] = ` 32 | Array [ 33 | "", 34 | "3", 35 | ] 36 | `; 37 | 38 | exports[`Interpreter should-work-with-get-shorthand 1`] = `Array []`; 39 | 40 | exports[`Interpreter should-work-with-global-variable 1`] = `Array []`; 41 | 42 | exports[`Interpreter should-work-with-if-expression 1`] = ` 43 | Array [ 44 | "A", 45 | ] 46 | `; 47 | 48 | exports[`Interpreter should-work-with-if-expression-with-if-only 1`] = ` 49 | Array [ 50 | "A", 51 | ] 52 | `; 53 | 54 | exports[`Interpreter should-work-with-integer 1`] = `Array []`; 55 | 56 | exports[`Interpreter should-work-with-local-variable 1`] = `Array []`; 57 | 58 | exports[`Interpreter should-work-with-method-call 1`] = `Array []`; 59 | 60 | exports[`Interpreter should-work-with-nested-function 1`] = ` 61 | Array [ 62 | "", 63 | "5", 64 | ] 65 | `; 66 | 67 | exports[`Interpreter should-work-with-null 1`] = `Array []`; 68 | 69 | exports[`Interpreter should-work-with-object-without-extends 1`] = `Array []`; 70 | 71 | exports[`Interpreter should-work-with-objects 1`] = `Array []`; 72 | 73 | exports[`Interpreter should-work-with-objects-and-slots 1`] = `Array []`; 74 | 75 | exports[`Interpreter should-work-with-printf 1`] = ` 76 | Array [ 77 | " My age is ~.\\\\ n ", 78 | "8", 79 | ] 80 | `; 81 | 82 | exports[`Interpreter should-work-with-set-shorthand 1`] = `Array []`; 83 | 84 | exports[`Interpreter should-work-with-slot-assignment 1`] = `Array []`; 85 | 86 | exports[`Interpreter should-work-with-slot-lookup 1`] = `Array []`; 87 | 88 | exports[`Interpreter should-work-with-type-arrays 1`] = `Array []`; 89 | 90 | exports[`Interpreter should-work-with-type-function-call 1`] = `Array []`; 91 | 92 | exports[`Interpreter should-work-with-type-function-expression 1`] = `Array []`; 93 | 94 | exports[`Interpreter should-work-with-type-function-parameter 1`] = `Array []`; 95 | 96 | exports[`Interpreter should-work-with-type-function-statement 1`] = `Array []`; 97 | 98 | exports[`Interpreter should-work-with-type-integer 1`] = `Array []`; 99 | 100 | exports[`Interpreter should-work-with-type-method-signature 1`] = `Array []`; 101 | 102 | exports[`Interpreter should-work-with-type-method-slot 1`] = `Array []`; 103 | 104 | exports[`Interpreter should-work-with-type-null 1`] = `Array []`; 105 | 106 | exports[`Interpreter should-work-with-type-typedef 1`] = `Array []`; 107 | 108 | exports[`Interpreter should-work-with-type-variable-reference 1`] = `Array []`; 109 | 110 | exports[`Interpreter should-work-with-type-variable-slot 1`] = `Array []`; 111 | 112 | exports[`Interpreter should-work-with-variable-assignment 1`] = `Array []`; 113 | 114 | exports[`Interpreter should-work-with-variable-reference 1`] = `Array []`; 115 | 116 | exports[`Interpreter should-work-with-while-expression 1`] = `Array []`; 117 | -------------------------------------------------------------------------------- /tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createScanner, 3 | createBinder, 4 | createChecker, 5 | Token, 6 | TokenSyntaxKind, 7 | createParser, 8 | createInterpreter, 9 | forEachChild, 10 | ASTNode, 11 | Symbol 12 | } from '../src'; 13 | import * as path from 'path'; 14 | import * as fs from 'fs'; 15 | import { Context } from '../src/interpreter/types'; 16 | import { isDeclaration } from '../src/utils'; 17 | 18 | export const casesPath = path.resolve(__dirname, 'cases'); 19 | export const demoPath = path.resolve(__dirname, 'demo'); 20 | 21 | export function forEachFeeny( 22 | basePath: string, 23 | callback: (baseName: string, content: string) => void 24 | ) { 25 | const fileNames = fs.readdirSync(basePath); 26 | 27 | fileNames.forEach(fileName => { 28 | const baseName = path.basename(fileName, '.feeny'); 29 | const fileNamePath = path.join(basePath, fileName); 30 | const content = fs.readFileSync(fileNamePath, 'utf8').toString(); 31 | callback(baseName, content); 32 | }); 33 | } 34 | 35 | export function forEachCases( 36 | callback: (baseName: string, content: string) => void 37 | ) { 38 | return forEachFeeny(casesPath, callback); 39 | } 40 | 41 | export function forEachDemo( 42 | callback: (baseName: string, content: string) => void 43 | ) { 44 | return forEachFeeny(demoPath, callback); 45 | } 46 | 47 | export function scanCode(text: string) { 48 | const scanner = createScanner(text); 49 | const tokens: Token[] = []; 50 | while (!scanner.isEOF()) { 51 | const token = scanner.nextToken(); 52 | tokens.push(token); 53 | } 54 | return tokens; 55 | } 56 | 57 | export function parseCode(text: string) { 58 | const parser = createParser(text); 59 | const file = parser.parse(); 60 | return file; 61 | } 62 | 63 | interface SymbolSignature { 64 | id: number; 65 | declarationPos: number; 66 | name?: string; 67 | flags: string | number; 68 | } 69 | 70 | interface SymbolContainer { 71 | pos: number; 72 | name?: string; 73 | kind: string | number; 74 | symbols: SymbolSignature[]; 75 | } 76 | 77 | export function bindCode(text: string) { 78 | const parser = createParser(text); 79 | const file = parser.parse(); 80 | const binder = createBinder(file); 81 | binder.bindFile(); 82 | 83 | const localsResult: SymbolContainer[] = []; 84 | const membersResult: SymbolContainer[] = []; 85 | 86 | visitor(file); 87 | 88 | return { 89 | localsResult, 90 | membersResult 91 | }; 92 | 93 | function visitor(node: ASTNode) { 94 | if (node.locals) { 95 | localsResult.push({ 96 | pos: node.pos, 97 | kind: node.__debugKind ?? node.kind, 98 | symbols: Array.from(node.locals.values()).map(symbolToSignature) 99 | }); 100 | } 101 | 102 | if (isDeclaration(node)) { 103 | if (node.symbol?.members) { 104 | membersResult.push({ 105 | pos: node.pos, 106 | name: node.symbol.name, 107 | kind: node.__debugKind ?? node.kind, 108 | symbols: Array.from(node.symbol.members.values()).map( 109 | symbolToSignature 110 | ) 111 | }); 112 | } 113 | } 114 | 115 | forEachChild(node, visitor); 116 | } 117 | 118 | function symbolToSignature(symbol: Symbol): SymbolSignature { 119 | return { 120 | id: symbol.id, 121 | declarationPos: symbol.declaration?.pos ?? -1, 122 | name: symbol.name, 123 | flags: symbol._debugFlags ?? symbol.flags 124 | }; 125 | } 126 | } 127 | 128 | export function createNodeContext(): Context { 129 | return { 130 | stdout: text => process.stdout.write(text, 'utf-8') 131 | }; 132 | } 133 | 134 | export function runCode(text: string) { 135 | const parser = createParser(text); 136 | const file = parser.parse(); 137 | const context = createNodeContext(); 138 | const interpreter = createInterpreter(file, context); 139 | interpreter.evaluate(); 140 | } 141 | 142 | interface CheckResult { 143 | id: number; 144 | pos: number; 145 | type: string | number; 146 | kind: string | number; 147 | } 148 | 149 | export function checkCode(text: string) { 150 | const parser = createParser(text); 151 | const file = parser.parse(); 152 | const binder = createBinder(file); 153 | binder.bindFile(); 154 | const checker = createChecker(file, binder.createBuiltinSymbol); 155 | 156 | const result: CheckResult[] = []; 157 | 158 | visitor(file); 159 | 160 | return [result, checker.diagnostics] as const; 161 | 162 | function visitor(node: ASTNode) { 163 | const type = checker.check(node); 164 | if (!checker.isNeverType(type)) { 165 | result.push({ 166 | id: type.id, 167 | pos: node.pos, 168 | type: type._debugKind ?? type.kind, 169 | kind: node.__debugKind ?? node.kind 170 | }); 171 | } 172 | 173 | forEachChild(node, visitor); 174 | } 175 | } 176 | 177 | export function runWithStdoutHook(text: string) { 178 | const result: string[] = []; 179 | 180 | const parser = createParser(text); 181 | const file = parser.parse(); 182 | 183 | const context: Context = { 184 | stdout: content => result.push(content) 185 | }; 186 | const interpreter = createInterpreter(file, context); 187 | interpreter.evaluate(); 188 | return result; 189 | } 190 | -------------------------------------------------------------------------------- /tests/demo/sudoku.feeny: -------------------------------------------------------------------------------- 1 | defn and (a, b) : 2 | if a : b 3 | else : a 4 | 5 | defn or (a, b) : 6 | if a : a 7 | else : b 8 | 9 | defn not (x) : 10 | if x : null 11 | else : 0 12 | 13 | var check-array = array(10, 0) 14 | defn begin-check () : 15 | var i = 0 16 | while i < 10 : 17 | check-array[i] = 0 18 | i = i + 1 19 | defn check (x) : 20 | check-array[x] = check-array[x] + 1 21 | defn check-good? () : 22 | var good = 1 23 | var i = 1 24 | while i < 10 : 25 | good = and(good, check-array[i] <= 1) 26 | i = i + 1 27 | good 28 | 29 | defn solvehelper (b, i) : 30 | if i == 81 : 31 | 0 32 | else if b.pos(i) == 0 : 33 | var s = null 34 | var n = 1 35 | while n <= 9 : 36 | if not(s) : 37 | b.set-pos(i, n) 38 | if b.good?() : 39 | s = or(s, solvehelper(b, i + 1)) 40 | n = n + 1 41 | if s : 42 | s 43 | else : 44 | b.set-pos(i, 0) 45 | null 46 | else : 47 | solvehelper(b, i + 1) 48 | 49 | defn board () : 50 | object : 51 | var array = array(9 * 9, 0) 52 | method pos (i) : 53 | this.array[i] 54 | method set-pos (i, x) : 55 | this.array[i] = x 56 | method get (r, c) : 57 | this.array[r * 9 + c] 58 | method set (r, c, x) : 59 | this.array[r * 9 + c] = x 60 | method print () : 61 | var r = 0 62 | while r < 9 : 63 | var c = 0 64 | while c < 9 : 65 | if c > 0 : printf(" ") 66 | if this[r, c] == 0 : printf("_") 67 | else : printf("~", this[r,c]) 68 | c = c + 1 69 | printf("\n") 70 | r = r + 1 71 | method good? () : 72 | var good? = 1 73 | 74 | ;Rows 75 | var r = 0 76 | while r < 9 : 77 | begin-check() 78 | var c = 0 79 | while c < 9 : 80 | check(this[r,c]) 81 | c = c + 1 82 | good? = and(good?, check-good?()) 83 | r = r + 1 84 | 85 | ;Columns 86 | var c = 0 87 | while c < 9 : 88 | begin-check() 89 | var r = 0 90 | while r < 9 : 91 | check(this[r,c]) 92 | r = r + 1 93 | good? = and(good?, check-good?()) 94 | c = c + 1 95 | 96 | ;Cells 97 | var cell = 0 98 | while cell < 9 : 99 | var r = (cell / 3) * 3 100 | var c = (cell % 3) * 3 101 | begin-check() 102 | var ri = r 103 | while ri < r + 3 : 104 | var ci = c 105 | while ci < c + 3 : 106 | check(this[ri,ci]) 107 | ci = ci + 1 108 | ri = ri + 1 109 | good? = and(good?, check-good?()) 110 | cell = cell + 1 111 | 112 | good? 113 | method solve () : 114 | solvehelper(this, 0) 115 | 116 | defn main () : 117 | var b = board() 118 | b[0,0] = 8 119 | b[0,1] = 0 120 | b[0,2] = 0 121 | b[0,3] = 1 122 | b[0,4] = 0 123 | b[0,5] = 3 124 | b[0,6] = 4 125 | b[0,7] = 0 126 | b[0,8] = 0 127 | b[1,0] = 0 128 | b[1,1] = 3 129 | b[1,2] = 5 130 | b[1,3] = 7 131 | b[1,4] = 8 132 | b[1,5] = 0 133 | b[1,6] = 0 134 | b[1,7] = 6 135 | b[1,8] = 2 136 | b[2,0] = 4 137 | b[2,1] = 7 138 | b[2,2] = 0 139 | b[2,3] = 0 140 | b[2,4] = 0 141 | b[2,5] = 6 142 | b[2,6] = 0 143 | b[2,7] = 9 144 | b[2,8] = 0 145 | b[3,0] = 0 146 | b[3,1] = 0 147 | b[3,2] = 0 148 | b[3,3] = 0 149 | b[3,4] = 0 150 | b[3,5] = 0 151 | b[3,6] = 0 152 | b[3,7] = 2 153 | b[3,8] = 4 154 | b[4,0] = 0 155 | b[4,1] = 1 156 | b[4,2] = 0 157 | b[4,3] = 3 158 | b[4,4] = 0 159 | b[4,5] = 5 160 | b[4,6] = 0 161 | b[4,7] = 8 162 | b[4,8] = 0 163 | b[5,0] = 2 164 | b[5,1] = 8 165 | b[5,2] = 0 166 | b[5,3] = 0 167 | b[5,4] = 0 168 | b[5,5] = 0 169 | b[5,6] = 0 170 | b[5,7] = 0 171 | b[5,8] = 0 172 | b[6,0] = 0 173 | b[6,1] = 2 174 | b[6,2] = 0 175 | b[6,3] = 6 176 | b[6,4] = 0 177 | b[6,5] = 0 178 | b[6,6] = 0 179 | b[6,7] = 3 180 | b[6,8] = 9 181 | b[7,0] = 1 182 | b[7,1] = 9 183 | b[7,2] = 0 184 | b[7,3] = 0 185 | b[7,4] = 7 186 | b[7,5] = 2 187 | b[7,6] = 6 188 | b[7,7] = 4 189 | b[7,8] = 0 190 | b[8,0] = 0 191 | b[8,1] = 0 192 | b[8,2] = 8 193 | b[8,3] = 5 194 | b[8,4] = 0 195 | b[8,5] = 9 196 | b[8,6] = 0 197 | b[8,7] = 0 198 | b[8,8] = 1 199 | 200 | printf("=== Given Puzzle ===\n") 201 | b.print() 202 | 203 | printf("\n=== Solution ===\n") 204 | b.solve() 205 | b.print() 206 | 207 | main() 208 | 209 | 210 | ;============================================================ 211 | ;======================== OUTPUT ============================ 212 | ;============================================================ 213 | ; 214 | ;=== Given Puzzle === 215 | ;8 _ _ 1 _ 3 4 _ _ 216 | ;_ 3 5 7 8 _ _ 6 2 217 | ;4 7 _ _ _ 6 _ 9 _ 218 | ;_ _ _ _ _ _ _ 2 4 219 | ;_ 1 _ 3 _ 5 _ 8 _ 220 | ;2 8 _ _ _ _ _ _ _ 221 | ;_ 2 _ 6 _ _ _ 3 9 222 | ;1 9 _ _ 7 2 6 4 _ 223 | ;_ _ 8 5 _ 9 _ _ 1 224 | ; 225 | ;=== Solution === 226 | ;8 6 2 1 9 3 4 5 7 227 | ;9 3 5 7 8 4 1 6 2 228 | ;4 7 1 2 5 6 3 9 8 229 | ;3 5 6 9 1 8 7 2 4 230 | ;7 1 4 3 2 5 9 8 6 231 | ;2 8 9 4 6 7 5 1 3 232 | ;5 2 7 6 4 1 8 3 9 233 | ;1 9 3 8 7 2 6 4 5 234 | ;6 4 8 5 3 9 2 7 1 -------------------------------------------------------------------------------- /tests/demo/sudoku2.feeny: -------------------------------------------------------------------------------- 1 | defn and (a, b) : 2 | if a : b 3 | else : a 4 | 5 | defn or (a, b) : 6 | if a : a 7 | else : b 8 | 9 | defn not (x) : 10 | if x : null 11 | else : 0 12 | 13 | var check-array = array(10, 0) 14 | defn begin-check () : 15 | var i = 0 16 | while i < 10 : 17 | check-array[i] = 0 18 | i = i + 1 19 | defn check (x) : 20 | check-array[x] = check-array[x] + 1 21 | defn check-good? () : 22 | var good = 1 23 | var i = 1 24 | while i < 10 : 25 | good = and(good, check-array[i] <= 1) 26 | i = i + 1 27 | good 28 | 29 | defn solvehelper (b, i) : 30 | if i == 81 : 31 | 0 32 | else if b.pos(i) == 0 : 33 | var s = null 34 | var n = 1 35 | while n <= 9 : 36 | if not(s) : 37 | b.set-pos(i, n) 38 | if b.good?() : 39 | s = or(s, solvehelper(b, i + 1)) 40 | n = n + 1 41 | if s : 42 | s 43 | else : 44 | b.set-pos(i, 0) 45 | null 46 | else : 47 | solvehelper(b, i + 1) 48 | 49 | defn board () : 50 | object : 51 | var array = array(9 * 9, 0) 52 | method pos (i) : 53 | this.array[i] 54 | method set-pos (i, x) : 55 | this.array[i] = x 56 | method get (r, c) : 57 | this.array[r * 9 + c] 58 | method set (r, c, x) : 59 | this.array[r * 9 + c] = x 60 | method print () : 61 | var r = 0 62 | while r < 9 : 63 | var c = 0 64 | while c < 9 : 65 | if c > 0 : printf(" ") 66 | if this[r, c] == 0 : printf("_") 67 | else : printf("~", this[r,c]) 68 | c = c + 1 69 | printf("\n") 70 | r = r + 1 71 | method good? () : 72 | var good? = 1 73 | 74 | ;Rows 75 | var r = 0 76 | while r < 9 : 77 | begin-check() 78 | var c = 0 79 | while c < 9 : 80 | check(this[r,c]) 81 | c = c + 1 82 | good? = and(good?, check-good?()) 83 | r = r + 1 84 | 85 | ;Columns 86 | var c = 0 87 | while c < 9 : 88 | begin-check() 89 | var r = 0 90 | while r < 9 : 91 | check(this[r,c]) 92 | r = r + 1 93 | good? = and(good?, check-good?()) 94 | c = c + 1 95 | 96 | ;Cells 97 | var cell = 0 98 | while cell < 9 : 99 | var r = (cell / 3) * 3 100 | var c = (cell % 3) * 3 101 | begin-check() 102 | var ri = r 103 | while ri < r + 3 : 104 | var ci = c 105 | while ci < c + 3 : 106 | check(this[ri,ci]) 107 | ci = ci + 1 108 | ri = ri + 1 109 | good? = and(good?, check-good?()) 110 | cell = cell + 1 111 | 112 | good? 113 | method solve () : 114 | solvehelper(this, 0) 115 | 116 | defn main () : 117 | var b = board() 118 | b[0,0] = 8 119 | b[0,1] = 0 120 | b[0,2] = 0 121 | b[0,3] = 1 122 | b[0,4] = 0 123 | b[0,5] = 3 124 | b[0,6] = 4 125 | b[0,7] = 0 126 | b[0,8] = 0 127 | b[1,0] = 0 128 | b[1,1] = 3 129 | b[1,2] = 5 130 | b[1,3] = 7 131 | b[1,4] = 8 132 | b[1,5] = 0 133 | b[1,6] = 0 134 | b[1,7] = 6 135 | b[1,8] = 2 136 | b[2,0] = 4 137 | b[2,1] = 7 138 | b[2,2] = 0 139 | b[2,3] = 0 140 | b[2,4] = 0 141 | b[2,5] = 6 142 | b[2,6] = 0 143 | b[2,7] = 9 144 | b[2,8] = 0 145 | b[3,0] = 0 146 | b[3,1] = 0 147 | b[3,2] = 0 148 | b[3,3] = 0 149 | b[3,4] = 0 150 | b[3,5] = 0 151 | b[3,6] = 0 152 | b[3,7] = 2 153 | b[3,8] = 4 154 | b[4,0] = 0 155 | b[4,1] = 1 156 | b[4,2] = 0 157 | b[4,3] = 3 158 | b[4,4] = 0 159 | b[4,5] = 5 160 | b[4,6] = 0 161 | b[4,7] = 8 162 | b[4,8] = 0 163 | b[5,0] = 2 164 | b[5,1] = 8 165 | b[5,2] = 0 166 | b[5,3] = 0 167 | b[5,4] = 0 168 | b[5,5] = 0 169 | b[5,6] = 0 170 | b[5,7] = 0 171 | b[5,8] = 0 172 | b[6,0] = 0 173 | b[6,1] = 2 174 | b[6,2] = 0 175 | b[6,3] = 6 176 | b[6,4] = 0 177 | b[6,5] = 0 178 | b[6,6] = 0 179 | b[6,7] = 3 180 | b[6,8] = 9 181 | b[7,0] = 1 182 | b[7,1] = 9 183 | b[7,2] = 0 184 | b[7,3] = 0 185 | b[7,4] = 7 186 | b[7,5] = 2 187 | b[7,6] = 6 188 | b[7,7] = 4 189 | b[7,8] = 0 190 | b[8,0] = 0 191 | b[8,1] = 0 192 | b[8,2] = 8 193 | b[8,3] = 5 194 | b[8,4] = 0 195 | b[8,5] = 9 196 | b[8,6] = 0 197 | b[8,7] = 0 198 | b[8,8] = 1 199 | 200 | ;printf("=== Given Puzzle ===\n") 201 | ;b.print() 202 | 203 | ;printf("\n=== Solution ===\n") 204 | b.solve() 205 | ;b.print() 206 | 207 | var i = 0 208 | while i < 200 : 209 | printf("Puzzle ~\n", i) 210 | main() 211 | i = i + 1 212 | 213 | 214 | ;============================================================ 215 | ;======================== OUTPUT ============================ 216 | ;============================================================ 217 | ; 218 | ;=== Given Puzzle === 219 | ;8 _ _ 1 _ 3 4 _ _ 220 | ;_ 3 5 7 8 _ _ 6 2 221 | ;4 7 _ _ _ 6 _ 9 _ 222 | ;_ _ _ _ _ _ _ 2 4 223 | ;_ 1 _ 3 _ 5 _ 8 _ 224 | ;2 8 _ _ _ _ _ _ _ 225 | ;_ 2 _ 6 _ _ _ 3 9 226 | ;1 9 _ _ 7 2 6 4 _ 227 | ;_ _ 8 5 _ 9 _ _ 1 228 | ; 229 | ;=== Solution === 230 | ;8 6 2 1 9 3 4 5 7 231 | ;9 3 5 7 8 4 1 6 2 232 | ;4 7 1 2 5 6 3 9 8 233 | ;3 5 6 9 1 8 7 2 4 234 | ;7 1 4 3 2 5 9 8 6 235 | ;2 8 9 4 6 7 5 1 3 236 | ;5 2 7 6 4 1 8 3 9 237 | ;1 9 3 8 7 2 6 4 5 238 | ;6 4 8 5 3 9 2 7 1 -------------------------------------------------------------------------------- /src/interpreter/builtins.ts: -------------------------------------------------------------------------------- 1 | import { assertThisValue, assertArgumentsLength } from './utils'; 2 | import { 3 | BaseValue, 4 | BuiltinFunction, 5 | IntegerValue, 6 | BooleanValue, 7 | NullValue, 8 | ArrayValue 9 | } from './values'; 10 | 11 | function createIntegerValueBuiltinFunction( 12 | cb: (a: number, b: number) => BaseValue 13 | ) { 14 | return (thisValue: BaseValue | undefined, args: BaseValue[]) => { 15 | assertThisValue(thisValue); 16 | 17 | assertArgumentsLength(1, args.length); 18 | const [right] = args; 19 | if (!thisValue.isInteger()) { 20 | throw new TypeError('Invalid this value, expected integers.'); 21 | } 22 | if (!right.isInteger()) { 23 | throw new TypeError('Invalid arguments, expected integers.'); 24 | } 25 | 26 | return cb(thisValue.value, right.value); 27 | }; 28 | } 29 | 30 | export function setupBuiltin() { 31 | const integerBuiltinFunctionAdd = new BuiltinFunction( 32 | 'add', 33 | ['b'], 34 | createIntegerValueBuiltinFunction((a, b) => { 35 | return new IntegerValue(a + b); 36 | }) 37 | ); 38 | 39 | const integerBuiltinFunctionSub = new BuiltinFunction( 40 | 'sub', 41 | ['b'], 42 | createIntegerValueBuiltinFunction((a, b) => { 43 | return new IntegerValue(a - b); 44 | }) 45 | ); 46 | 47 | const integerBuiltinFunctionMul = new BuiltinFunction( 48 | 'mul', 49 | ['b'], 50 | createIntegerValueBuiltinFunction((a, b) => { 51 | return new IntegerValue(a * b); 52 | }) 53 | ); 54 | 55 | const integerBuiltinFunctionDiv = new BuiltinFunction( 56 | 'div', 57 | ['b'], 58 | createIntegerValueBuiltinFunction((a, b) => { 59 | return new IntegerValue(Math.trunc(a / b)); 60 | }) 61 | ); 62 | 63 | const integerBuiltinFunctionMod = new BuiltinFunction( 64 | 'mod', 65 | ['b'], 66 | createIntegerValueBuiltinFunction((a, b) => { 67 | return new IntegerValue(a % b); 68 | }) 69 | ); 70 | 71 | const integerBuiltinFunctionLt = new BuiltinFunction( 72 | 'lt', 73 | ['b'], 74 | createIntegerValueBuiltinFunction((a, b) => { 75 | return new BooleanValue(a < b); 76 | }) 77 | ); 78 | 79 | const integerBuiltinFunctionGt = new BuiltinFunction( 80 | 'gt', 81 | ['b'], 82 | createIntegerValueBuiltinFunction((a, b) => { 83 | return new BooleanValue(a > b); 84 | }) 85 | ); 86 | 87 | const integerBuiltinFunctionLe = new BuiltinFunction( 88 | 'le', 89 | ['b'], 90 | createIntegerValueBuiltinFunction((a, b) => { 91 | return new BooleanValue(a <= b); 92 | }) 93 | ); 94 | 95 | const integerBuiltinFunctionGe = new BuiltinFunction( 96 | 'ge', 97 | ['b'], 98 | createIntegerValueBuiltinFunction((a, b) => { 99 | return new BooleanValue(a >= b); 100 | }) 101 | ); 102 | 103 | const integerBuiltinFunctionEq = new BuiltinFunction( 104 | 'eq', 105 | ['b'], 106 | createIntegerValueBuiltinFunction((a, b) => { 107 | return new BooleanValue(a === b); 108 | }) 109 | ); 110 | 111 | IntegerValue.Env.addBinding('add', integerBuiltinFunctionAdd); 112 | IntegerValue.Env.addBinding('sub', integerBuiltinFunctionSub); 113 | IntegerValue.Env.addBinding('mul', integerBuiltinFunctionMul); 114 | IntegerValue.Env.addBinding('div', integerBuiltinFunctionDiv); 115 | IntegerValue.Env.addBinding('mod', integerBuiltinFunctionMod); 116 | IntegerValue.Env.addBinding('lt', integerBuiltinFunctionLt); 117 | IntegerValue.Env.addBinding('gt', integerBuiltinFunctionGt); 118 | IntegerValue.Env.addBinding('le', integerBuiltinFunctionLe); 119 | IntegerValue.Env.addBinding('ge', integerBuiltinFunctionGe); 120 | IntegerValue.Env.addBinding('eq', integerBuiltinFunctionEq); 121 | 122 | const arraysBuiltinFunctionGet = new BuiltinFunction( 123 | 'get', 124 | ['index'], 125 | (thisValue: BaseValue | undefined, args: BaseValue[]) => { 126 | assertThisValue(thisValue); 127 | 128 | if (!thisValue.isArray()) { 129 | throw new TypeError('Invalid this value, expected Array'); 130 | } 131 | 132 | assertArgumentsLength(1, args.length); 133 | const [indexValue] = args; 134 | if (!indexValue.isInteger()) { 135 | throw new TypeError('Invalid arguments, expected Integer'); 136 | } 137 | 138 | const index = indexValue.value; 139 | if (index < 0 || index >= thisValue.length) { 140 | throw new Error('Index out of range'); 141 | } 142 | 143 | const result = thisValue.env.getBinding(`${index}`); 144 | return result ?? NullValue.Instance; 145 | } 146 | ); 147 | 148 | const arraysBuiltinFunctionSet = new BuiltinFunction( 149 | 'set', 150 | ['index', 'value'], 151 | (thisValue: BaseValue | undefined, args: BaseValue[]) => { 152 | assertThisValue(thisValue); 153 | 154 | if (!thisValue.isArray()) { 155 | throw new TypeError('Invalid this value, expected Array'); 156 | } 157 | 158 | assertArgumentsLength(2, args.length); 159 | const [indexValue, value] = args; 160 | if (!indexValue.isInteger()) { 161 | throw new TypeError('Invalid arguments, expected Integer'); 162 | } 163 | 164 | const index = indexValue.value; 165 | thisValue.env.addBinding(`${index}`, value); 166 | return NullValue.Instance; 167 | } 168 | ); 169 | 170 | const arraysBuiltinFunctionLength = new BuiltinFunction( 171 | 'length', 172 | [], 173 | (thisValue: BaseValue | undefined, args: BaseValue[]) => { 174 | assertThisValue(thisValue); 175 | 176 | if (!thisValue.isArray()) { 177 | throw new TypeError('Invalid this value, expected Array'); 178 | } 179 | 180 | return new IntegerValue(thisValue.length); 181 | } 182 | ); 183 | 184 | ArrayValue.Env.addBinding('get', arraysBuiltinFunctionGet); 185 | ArrayValue.Env.addBinding('set', arraysBuiltinFunctionSet); 186 | ArrayValue.Env.addBinding('length', arraysBuiltinFunctionLength); 187 | } 188 | -------------------------------------------------------------------------------- /src/interpreter/values.ts: -------------------------------------------------------------------------------- 1 | import { Environment, ValueType } from './types'; 2 | 3 | export abstract class BaseValue { 4 | abstract get type(): ValueType; 5 | 6 | abstract print(): string; 7 | 8 | isNull(): this is NullValue { 9 | return false; 10 | } 11 | 12 | isBoolean(): this is BooleanValue { 13 | return false; 14 | } 15 | 16 | isArray(): this is ArrayValue { 17 | return false; 18 | } 19 | 20 | isString(): this is StringValue { 21 | return false; 22 | } 23 | 24 | isObject(): this is ObjectValue { 25 | return false; 26 | } 27 | 28 | isFunction(): this is FunctionValue { 29 | return false; 30 | } 31 | 32 | isInteger(): this is IntegerValue { 33 | return false; 34 | } 35 | 36 | isEnvValue(): this is EnvValue { 37 | return false; 38 | } 39 | } 40 | 41 | export class NullValue extends BaseValue { 42 | get type() { 43 | return ValueType.Null; 44 | } 45 | 46 | static Instance = new NullValue(); 47 | 48 | print(): string { 49 | return 'null'; 50 | } 51 | 52 | isNull(): true { 53 | return true; 54 | } 55 | } 56 | 57 | export abstract class EnvValue extends BaseValue { 58 | abstract get env(): Environment; 59 | 60 | isEnvValue(): true { 61 | return true; 62 | } 63 | } 64 | 65 | export class BooleanValue extends EnvValue { 66 | get type() { 67 | return ValueType.Boolean; 68 | } 69 | 70 | static True = new BooleanValue(true); 71 | static False = new BooleanValue(false); 72 | 73 | static Env = new Environment(); 74 | private _instanceEnv = new Environment(BooleanValue.Env); 75 | 76 | get env() { 77 | return this._instanceEnv; 78 | } 79 | 80 | constructor(public value: boolean) { 81 | super(); 82 | } 83 | 84 | isBoolean(): true { 85 | return true; 86 | } 87 | 88 | print(): string { 89 | return `${this.value}`; 90 | } 91 | } 92 | 93 | export class ArrayValue extends EnvValue { 94 | get type() { 95 | return ValueType.Array; 96 | } 97 | 98 | static Env = new Environment(); 99 | private _instanceEnv = new Environment(ArrayValue.Env); 100 | 101 | get env() { 102 | return this._instanceEnv; 103 | } 104 | 105 | constructor(public length: number, defaultValue?: BaseValue) { 106 | super(); 107 | 108 | if (defaultValue) { 109 | for (let i = 0; i < length; i++) { 110 | this.env.addBinding(`${i}`, defaultValue); 111 | } 112 | } 113 | } 114 | 115 | print(): string { 116 | const list: BaseValue[] = []; 117 | for (let i = 0; i < this.length; i++) { 118 | const value = this.env.getBinding(`${i}`); 119 | if (value) { 120 | list.push(value); 121 | } else { 122 | list.push(NullValue.Instance); 123 | } 124 | } 125 | return `[${list.map(v => v.print()).join(', ')}]`; 126 | } 127 | 128 | isArray(): true { 129 | return true; 130 | } 131 | } 132 | 133 | export class ObjectValue extends EnvValue { 134 | get type() { 135 | return ValueType.Object; 136 | } 137 | 138 | private _instanceEnv: Environment; 139 | 140 | get env() { 141 | return this._instanceEnv; 142 | } 143 | 144 | constructor(parent?: BaseValue) { 145 | super(); 146 | 147 | if (parent) { 148 | if (!parent.isEnvValue() && !parent.isNull()) { 149 | throw new TypeError('Invalid extends'); 150 | } 151 | 152 | this._instanceEnv = new Environment( 153 | parent.isNull() ? undefined : parent.env 154 | ); 155 | } else { 156 | this._instanceEnv = new Environment(); 157 | } 158 | } 159 | 160 | print(): string { 161 | return `{[Object object]}`; 162 | } 163 | 164 | isObject(): true { 165 | return true; 166 | } 167 | } 168 | 169 | export class IntegerValue extends EnvValue { 170 | get type() { 171 | return ValueType.Integer; 172 | } 173 | 174 | static Env = new Environment(); 175 | private _instanceEnv = new Environment(IntegerValue.Env); 176 | 177 | get env() { 178 | return this._instanceEnv; 179 | } 180 | 181 | constructor(public value: number) { 182 | super(); 183 | } 184 | 185 | print(): string { 186 | return `${this.value}`; 187 | } 188 | 189 | isInteger(): true { 190 | return true; 191 | } 192 | } 193 | 194 | export class StringValue extends BaseValue { 195 | get type() { 196 | return ValueType.String; 197 | } 198 | 199 | constructor(public value: string) { 200 | super(); 201 | } 202 | 203 | print(): string { 204 | return this.value; 205 | } 206 | 207 | isString(): true { 208 | return true; 209 | } 210 | } 211 | 212 | export abstract class FunctionValue extends BaseValue { 213 | get type() { 214 | return ValueType.Function; 215 | } 216 | 217 | constructor(public name: string, public params: string[]) { 218 | super(); 219 | } 220 | 221 | print(): string { 222 | return `{[Function function]}`; 223 | } 224 | 225 | isFunction(): true { 226 | return true; 227 | } 228 | 229 | isBuiltin(): this is BuiltinFunction { 230 | return false; 231 | } 232 | 233 | isRuntime(): this is RuntimeFunction { 234 | return false; 235 | } 236 | } 237 | 238 | export class RuntimeFunction extends FunctionValue { 239 | constructor( 240 | name: string, 241 | params: string[], 242 | public body: () => BaseValue, 243 | public closureEnv: Environment 244 | ) { 245 | super(name, params); 246 | } 247 | 248 | isRuntime(): true { 249 | return true; 250 | } 251 | } 252 | 253 | export class BuiltinFunction extends FunctionValue { 254 | constructor( 255 | name: string, 256 | params: string[], 257 | public fn: ( 258 | thisValue: BaseValue | undefined, 259 | args: BaseValue[] 260 | ) => BaseValue 261 | ) { 262 | super(name, params); 263 | } 264 | 265 | print(): string { 266 | return `{[Function builtin]}`; 267 | } 268 | 269 | isBuiltin(): true { 270 | return true; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/binder.ts: -------------------------------------------------------------------------------- 1 | import { ASTNode, Symbol, FunctionStatement, MethodSlot, ObjectsExpression, Declaration, ParameterDeclaration, SourceFile, SyntaxKind, VariableSlot, VariableStatement, HasLocalVariables, SymbolFlag, TypeDefDeclaration, MethodSlotSignatureDeclaration, VariableSlotSignatureDeclaration } from "./types"; 2 | import { assertDef, getDeclarationSymbolFlags, isLocalVariableContainer, setupSymbolDebugInfo } from "./utils"; 3 | import { forEachChild } from "./visitor"; 4 | 5 | export function createBinder(file: SourceFile) { 6 | let container: HasLocalVariables | undefined = undefined; 7 | let parent: Symbol | undefined = undefined; 8 | let uid = 1 9 | 10 | return { 11 | bindFile, 12 | createBuiltinSymbol 13 | } 14 | 15 | function bindFile () { 16 | bind(file) 17 | } 18 | 19 | function currentLocalsTable() { 20 | assertDef(container); 21 | 22 | if (!container.locals) { 23 | container.locals = createSymbolTable(); 24 | } 25 | return container.locals; 26 | } 27 | 28 | function bind (node: ASTNode) { 29 | bindWorker(node); 30 | bindChildren(node); 31 | } 32 | 33 | function bindChildren (node: ASTNode) { 34 | const savedContainer = container; 35 | if (isLocalVariableContainer(node)) { 36 | container = node; 37 | } 38 | 39 | forEachChild(node, bind); 40 | 41 | container = savedContainer; 42 | } 43 | 44 | function bindWorker (node: ASTNode) { 45 | switch (node.kind) { 46 | case SyntaxKind.VariableStatement: 47 | bindVariableStatement(node as VariableStatement); 48 | break 49 | case SyntaxKind.FunctionStatement: 50 | bindFunctionStatement(node as FunctionStatement); 51 | break; 52 | case SyntaxKind.VariableSlot: 53 | bindVariableSlot(node as VariableSlot); 54 | break; 55 | case SyntaxKind.MethodSlot: 56 | bindMethodSlot(node as MethodSlot); 57 | break; 58 | case SyntaxKind.ParameterDeclaration: 59 | bindParameterDeclaration(node as ParameterDeclaration); 60 | break; 61 | case SyntaxKind.ObjectsExpression: 62 | bindObjectsExpression(node as ObjectsExpression); 63 | break; 64 | case SyntaxKind.TypeDefDeclaration: 65 | bindTypeDefDeclaration(node as TypeDefDeclaration); 66 | break; 67 | case SyntaxKind.VariableSlotSignatureDeclaration: 68 | bindVariableSlotSignatureDeclaration(node as VariableSlotSignatureDeclaration); 69 | break; 70 | case SyntaxKind.MethodSlotSignatureDeclaration: 71 | bindMethodSlotSignatureDeclaration(node as MethodSlotSignatureDeclaration); 72 | break; 73 | default: 74 | return bindChildren(node); 75 | } 76 | } 77 | 78 | function bindMethodSlotSignatureDeclaration(node: MethodSlotSignatureDeclaration) { 79 | addMemberToParent(node.name.text, node); 80 | bindChildren(node); 81 | } 82 | 83 | function bindVariableSlotSignatureDeclaration(node: VariableSlotSignatureDeclaration) { 84 | addMemberToParent(node.name.text, node); 85 | bindChildren(node); 86 | } 87 | 88 | function bindTypeDefDeclaration(node: TypeDefDeclaration) { 89 | const symbol = addTypeDeclarationToContainer(node.name.text, node); 90 | const savedParent = parent; 91 | parent = symbol; 92 | bindChildren(node); 93 | parent = savedParent; 94 | } 95 | 96 | function bindVariableStatement (node: VariableStatement) { 97 | addLocalVariableToContainer(node.name.text, node); 98 | bindChildren(node) 99 | } 100 | 101 | function bindFunctionStatement (node: FunctionStatement) { 102 | addLocalVariableToContainer(node.name.text, node); 103 | bindChildren(node) 104 | } 105 | 106 | function bindParameterDeclaration (node: ParameterDeclaration) { 107 | addLocalVariableToContainer(node.name.text, node); 108 | bindChildren(node) 109 | } 110 | 111 | function bindVariableSlot (node: VariableSlot) { 112 | addMemberToParent(node.name.text, node); 113 | bindChildren(node) 114 | } 115 | 116 | function bindMethodSlot (node: MethodSlot) { 117 | addMemberToParent(node.name.text, node); 118 | bindChildren(node) 119 | } 120 | 121 | function bindObjectsExpression (node: ObjectsExpression) { 122 | const flags = getDeclarationSymbolFlags(node); 123 | const symbol = createAnonymousSymbol(flags, node); 124 | const savedParent = parent; 125 | parent = symbol; 126 | 127 | bindChildren(node) 128 | 129 | parent = savedParent; 130 | } 131 | 132 | function addLocalVariableToContainer (name: string, declaration: Declaration) { 133 | const flags = getDeclarationSymbolFlags(declaration); 134 | const symbol = createSymbol(name, flags, declaration); 135 | currentLocalsTable().set(name, symbol); 136 | return symbol; 137 | } 138 | 139 | function addTypeDeclarationToContainer (name: string, declaration: Declaration) { 140 | const flags = getDeclarationSymbolFlags(declaration); 141 | const symbol = createSymbol(name, flags, declaration); 142 | currentLocalsTable().set(name, symbol); 143 | return symbol; 144 | } 145 | 146 | function addMemberToParent (name: string, member: Declaration) { 147 | const flags = getDeclarationSymbolFlags(member); 148 | const symbol = createSymbol(name, flags, member); 149 | 150 | if (parent) { 151 | if (!parent.members) { 152 | parent.members = createSymbolTable(); 153 | } 154 | 155 | parent.members.set(name, symbol); 156 | } 157 | 158 | return symbol; 159 | } 160 | 161 | function createSymbol(name: string, flags: SymbolFlag, declaration: Declaration) { 162 | const symbol: Symbol = { 163 | id: uid++, 164 | name, 165 | flags, 166 | declaration, 167 | parent 168 | }; 169 | declaration.symbol = symbol; 170 | setupSymbolDebugInfo(symbol); 171 | return symbol; 172 | } 173 | 174 | function createAnonymousSymbol(flags: SymbolFlag, declaration: Declaration) { 175 | const symbol: Symbol = { 176 | id: uid++, 177 | name: undefined, 178 | flags, 179 | declaration, 180 | parent 181 | } 182 | declaration.symbol = symbol; 183 | setupSymbolDebugInfo(symbol); 184 | return symbol; 185 | } 186 | 187 | function createBuiltinSymbol(flags: SymbolFlag) { 188 | const symbol: Symbol = { 189 | id: uid++, 190 | name: undefined, 191 | flags: flags | SymbolFlag.Builtin, 192 | declaration: undefined, 193 | parent 194 | } 195 | setupSymbolDebugInfo(symbol); 196 | return symbol; 197 | } 198 | 199 | function createSymbolTable () { 200 | return new Map() 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/visitor.ts: -------------------------------------------------------------------------------- 1 | import { IntegerTypeNode, NullKeywordToken, VariableSlotSignatureDeclaration, SourceFile, ArraysExpression, ExpressionStatement, GetShorthand, ObjectsExpression, ParameterDeclaration, PrintingExpression, VariableAssignmentExpression, ASTNode, BinaryShorthand, FunctionExpression, FunctionStatement, IfExpression, MethodCallExpression, MethodSlot, ParenExpression, SequenceOfStatements, SetShorthand, SlotAssignmentExpression, SlotLookupExpression, SyntaxKind, VariableSlot, VariableStatement, WhileExpression, FunctionCallExpression, TypeDefDeclaration, MethodSlotSignatureDeclaration, ArraysTypeNode } from "./types"; 2 | import { assertKind, isBinaryShorthandTokenSyntaxKind, isDef, isKeywordSyntaxKind } from "./utils"; 3 | 4 | export function forEachChild(node: ASTNode, cb: (node: ASTNode) => T | undefined): T | undefined { 5 | return visitNode(node); 6 | 7 | function visitNode(node: ASTNode | undefined): T | undefined { 8 | if (!isDef(node)) { 9 | return undefined 10 | } 11 | 12 | if (node.kind === SyntaxKind.EndOfFileToken) { 13 | return undefined; 14 | } 15 | 16 | if (node.kind === SyntaxKind.Identifier) { 17 | return undefined; 18 | } 19 | 20 | if (isKeywordSyntaxKind(node.kind) || isBinaryShorthandTokenSyntaxKind(node.kind)) { 21 | return undefined; 22 | } 23 | 24 | switch (node.kind) { 25 | case SyntaxKind.StringLiteralExpression: 26 | case SyntaxKind.IntegerLiteralExpression: 27 | case SyntaxKind.VariableReferenceExpression: 28 | case SyntaxKind.TypeReferenceTypeNode: 29 | case SyntaxKind.NullExpression: 30 | case SyntaxKind.BreakExpression: 31 | case SyntaxKind.ContinueExpression: 32 | case SyntaxKind.ThisExpression: 33 | return undefined; 34 | 35 | case SyntaxKind.SourceFile: 36 | assertKind(node); 37 | return cb(node.body); 38 | case SyntaxKind.PrintingExpression: 39 | assertKind(node); 40 | return visitNodes(node.args) 41 | case SyntaxKind.ArraysExpression: 42 | assertKind(node); 43 | return cb(node.length) || node.defaultValue && cb(node.defaultValue) 44 | case SyntaxKind.ObjectsExpression: 45 | assertKind(node); 46 | return node.extendsClause && cb(node.extendsClause) || visitNodes(node.slots) 47 | case SyntaxKind.FunctionCallExpression: 48 | assertKind(node); 49 | return cb(node.expression) || visitNodes(node.args) 50 | case SyntaxKind.MethodCallExpression: 51 | assertKind(node); 52 | return cb(node.expression) || cb(node.name) || visitNodes(node.args) 53 | case SyntaxKind.SlotLookupExpression: 54 | assertKind(node); 55 | return cb(node.expression) || cb(node.name) 56 | case SyntaxKind.SlotAssignmentExpression: 57 | assertKind(node); 58 | return cb(node.expression) || cb(node.name) || cb(node.value) 59 | case SyntaxKind.FunctionExpression: 60 | assertKind(node); 61 | return cb(node.name) || visitNodes(node.params) || cb(node.body) 62 | case SyntaxKind.VariableAssignmentExpression: 63 | assertKind(node); 64 | return cb(node.expression) || cb(node.value) 65 | case SyntaxKind.IfExpression: 66 | assertKind(node); 67 | return cb(node.condition) || cb(node.thenStatement) || node.elseStatement && cb(node.elseStatement) 68 | case SyntaxKind.WhileExpression: 69 | assertKind(node); 70 | return cb(node.condition) || cb(node.body) 71 | case SyntaxKind.ParenExpression: 72 | assertKind(node); 73 | return cb(node.expression) 74 | case SyntaxKind.VariableSlot: 75 | assertKind(node); 76 | return cb(node.name) || node.type && cb(node.type) || cb(node.initializer) 77 | case SyntaxKind.MethodSlot: 78 | assertKind(node); 79 | return cb(node.name) || node.type && cb(node.type) || visitNodes(node.params) || cb(node.body) 80 | case SyntaxKind.ParameterDeclaration: 81 | assertKind(node); 82 | return cb(node.name) || node.type && cb(node.type) 83 | case SyntaxKind.VariableStatement: 84 | assertKind(node); 85 | return cb(node.name) || node.type && cb(node.type) || cb(node.initializer) 86 | case SyntaxKind.ExpressionStatement: 87 | assertKind(node); 88 | return cb(node.expression) 89 | case SyntaxKind.FunctionStatement: 90 | assertKind(node); 91 | return cb(node.name) || visitNodes(node.params) || node.type && cb(node.type) || cb(node.body) 92 | case SyntaxKind.SequenceOfStatements: 93 | assertKind(node); 94 | return visitNodes(node.statements) 95 | case SyntaxKind.BinaryShorthand: 96 | assertKind(node); 97 | return cb(node.left) || cb(node.operator) || cb(node.right) 98 | case SyntaxKind.GetShorthand: 99 | assertKind(node); 100 | return cb(node.expression) || visitNodes(node.args) 101 | case SyntaxKind.SetShorthand: 102 | assertKind(node); 103 | return cb(node.expression) || visitNodes(node.args) || cb(node.value) 104 | case SyntaxKind.TypeDefDeclaration: 105 | assertKind(node); 106 | return cb(node.name) || visitNodes(node.slots) 107 | case SyntaxKind.MethodSlotSignatureDeclaration: 108 | assertKind(node); 109 | return cb(node.name) || visitNodes(node.params) || node.type && cb(node.type) || cb(node.type) 110 | case SyntaxKind.VariableSlotSignatureDeclaration: 111 | assertKind(node); 112 | return cb(node.name) || cb(node.type) 113 | case SyntaxKind.IntegerTypeNode: 114 | case SyntaxKind.NullTypeNode: 115 | assertKind(node); 116 | return undefined 117 | case SyntaxKind.ArraysTypeNode: 118 | assertKind(node); 119 | return cb(node.type) 120 | default: 121 | throw new Error(`Unknown node kind: ${node.__debugKind}`) 122 | } 123 | } 124 | 125 | function visitNodes(nodes: ReadonlyArray | undefined): T | undefined { 126 | if (!isDef(nodes)) { 127 | return undefined 128 | } 129 | 130 | for (const node of nodes) { 131 | const result = cb(node); 132 | if (isDef(result)) { 133 | return result 134 | } 135 | } 136 | return undefined 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/scanner.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createIdentifier, 3 | createNumberLiteralToken, 4 | createStringLiteralToken, 5 | createToken 6 | } from './factory'; 7 | import { Token, SyntaxKind, TokenSyntaxKind } from './types'; 8 | import { 9 | Chars, 10 | CharsToTokenKind, 11 | createFinishNode, 12 | getIndent, 13 | isAlpha, 14 | isAlphaOrDigitOrLowDashOrDashOrQuestion, 15 | isDigit, 16 | isKeyword, 17 | isWhiteSpaceOrTab, 18 | KeywordsToTokenKind 19 | } from './utils'; 20 | 21 | export function createScanner(text: string) { 22 | let tokenFullStart = 0; 23 | let tokenStart = 0; 24 | let current = 0; 25 | let token: Token | undefined; 26 | let leadingIndent: number = 0; 27 | let shouldUpdateIndent = true; 28 | let hasLineFeed = false; 29 | 30 | const finishNode = createFinishNode(text); 31 | 32 | return { 33 | isEOF, 34 | nextToken, 35 | currentToken, 36 | getTokenStart, 37 | getTokenFullStart, 38 | getCurrentPos, 39 | currentTokenhasLineFeed 40 | }; 41 | 42 | function currentTokenhasLineFeed() { 43 | return hasLineFeed; 44 | } 45 | 46 | function getTokenStart() { 47 | return tokenStart; 48 | } 49 | 50 | function getTokenFullStart() { 51 | return tokenFullStart 52 | } 53 | 54 | function getCurrentPos() { 55 | return current; 56 | } 57 | 58 | function isEOF() { 59 | return token?.kind === SyntaxKind.EndOfFileToken; 60 | } 61 | 62 | function currentToken() { 63 | return token!; 64 | } 65 | 66 | function nextToken() { 67 | scan(); 68 | return currentToken(); 69 | } 70 | 71 | function scan() { 72 | beforeWorker(); 73 | worker(); 74 | afterWorker(); 75 | 76 | function beforeWorker() { 77 | hasLineFeed = false; 78 | tokenFullStart = current; 79 | } 80 | 81 | function afterWorker() { 82 | if (token) { 83 | shouldUpdateIndent = false; 84 | 85 | token.fullPos = tokenFullStart; 86 | token.leadingIndent = leadingIndent; 87 | } 88 | } 89 | 90 | function worker() { 91 | if (current >= text.length) { 92 | token = finishNode( 93 | createToken(SyntaxKind.EndOfFileToken), 94 | tokenFullStart, 95 | current 96 | ); 97 | return; 98 | } 99 | 100 | let ch = text[current]; 101 | tokenStart = current; 102 | 103 | switch (ch) { 104 | case Chars.LineFeed: 105 | current++; 106 | leadingIndent = 0; 107 | shouldUpdateIndent = true; 108 | hasLineFeed = true; 109 | worker(); 110 | break; 111 | 112 | case Chars.Semi: { 113 | let i = 0; 114 | while ( 115 | current + i < text.length && 116 | text[current + i] !== Chars.LineFeed 117 | ) { 118 | i++; 119 | } 120 | current += i; 121 | worker(); 122 | break; 123 | } 124 | 125 | case Chars.Whitespace: 126 | case Chars.Tab: { 127 | let i = 0; 128 | while (isWhiteSpaceOrTab(text[current + i])) { 129 | if (shouldUpdateIndent) { 130 | leadingIndent += getIndent(text[current + i]); 131 | } 132 | i++; 133 | } 134 | current += i; 135 | worker(); 136 | break; 137 | } 138 | 139 | case Chars.Sub: 140 | if (text[current + 1] === Chars.GreaterThan) { 141 | current += 2; 142 | token = finishNode( 143 | createToken(SyntaxKind.SubGreaterThanToken), 144 | tokenStart, 145 | current 146 | ); 147 | break; 148 | } 149 | case Chars.Add: 150 | case Chars.Mul: 151 | case Chars.Div: 152 | case Chars.Mod: 153 | case Chars.OpenParen: 154 | case Chars.CloseParen: 155 | case Chars.OpenBracket: 156 | case Chars.CloseBracket: 157 | case Chars.Dot: 158 | case Chars.Colon: 159 | case Chars.Comma: 160 | current++; 161 | token = finishNode( 162 | createToken(CharsToTokenKind[ch]), 163 | tokenStart, 164 | current 165 | ); 166 | break; 167 | case Chars.LessThan: 168 | if (current + 1 < text.length && text[current + 1] === Chars.Equals) { 169 | current += 2; 170 | token = finishNode( 171 | createToken(SyntaxKind.LessEqualsThanToken), 172 | tokenStart, 173 | current 174 | ); 175 | break; 176 | } 177 | current++; 178 | token = finishNode( 179 | createToken(SyntaxKind.LessThanToken), 180 | tokenStart, 181 | current 182 | ); 183 | break; 184 | case Chars.GreaterThan: 185 | if (current + 1 < text.length && text[current + 1] === Chars.Equals) { 186 | current += 2; 187 | token = finishNode( 188 | createToken(SyntaxKind.GreaterEqualsThanToken), 189 | tokenStart, 190 | current 191 | ); 192 | break; 193 | } 194 | current++; 195 | token = finishNode( 196 | createToken(SyntaxKind.GreaterThanToken), 197 | tokenStart, 198 | current 199 | ); 200 | break; 201 | case Chars.Equals: 202 | if (current + 1 < text.length && text[current + 1] === Chars.Equals) { 203 | current += 2; 204 | token = finishNode( 205 | createToken(SyntaxKind.EqualsEqualsToken), 206 | tokenStart, 207 | current 208 | ); 209 | break; 210 | } 211 | current++; 212 | token = finishNode( 213 | createToken(SyntaxKind.EqualsToken), 214 | tokenStart, 215 | current 216 | ); 217 | break; 218 | case Chars.Quote: { 219 | current++; 220 | const stringContentStart = current; 221 | let i = 0; 222 | while ( 223 | current + i < text.length && 224 | text[current + i] !== Chars.Quote 225 | ) { 226 | if ( 227 | text[current + i] === Chars.BackSlash && 228 | current + i + 1 < text.length && 229 | text[current + i + 1] === Chars.Quote 230 | ) { 231 | i++; 232 | } 233 | i++; 234 | } 235 | const stringContentEnd = current + i; 236 | current = stringContentEnd + 1; 237 | const value = text.substring(stringContentStart, stringContentEnd); 238 | token = finishNode( 239 | createStringLiteralToken(value), 240 | tokenStart, 241 | current 242 | ); 243 | break; 244 | } 245 | 246 | case Chars._0: 247 | case Chars._1: 248 | case Chars._2: 249 | case Chars._3: 250 | case Chars._4: 251 | case Chars._5: 252 | case Chars._6: 253 | case Chars._7: 254 | case Chars._8: 255 | case Chars._9: { 256 | let i = 0; 257 | while (current + i < text.length && isDigit(text[current + i])) { 258 | i++; 259 | } 260 | current += i; 261 | const value = text.substring(tokenStart, current); 262 | token = finishNode( 263 | createNumberLiteralToken(value), 264 | tokenStart, 265 | current 266 | ); 267 | break; 268 | } 269 | 270 | default: 271 | if (isAlpha(ch)) { 272 | let i = 0; 273 | while ( 274 | current + i < text.length && 275 | isAlphaOrDigitOrLowDashOrDashOrQuestion(text[current + i]) 276 | ) { 277 | i++; 278 | } 279 | current += i; 280 | const value = text.substring(tokenStart, current); 281 | if (isKeyword(value)) { 282 | token = finishNode( 283 | createToken(KeywordsToTokenKind[value]), 284 | tokenStart, 285 | current 286 | ); 287 | break; 288 | } 289 | token = finishNode(createIdentifier(value), tokenStart, current); 290 | break; 291 | } 292 | throw new Error('Unknown token: ' + ch); 293 | } 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/factory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ObjectSlotSignature, 3 | TypeDefDeclaration, 4 | TypeNode, 5 | VariableSlotSignatureDeclaration, 6 | Statement, 7 | ObjectsKeywordToken, 8 | ParameterDeclaration, 9 | BreakExpression, 10 | ContinueExpression, 11 | AccessOrAssignmentExpressionOrHigher, 12 | ArraysExpression, 13 | ASTNode, 14 | BinaryShorthand, 15 | BinaryShorthandTokenSyntaxKind, 16 | EndOfFileToken, 17 | Expression, 18 | ExpressionStatement, 19 | FunctionCallExpression, 20 | FunctionStatement, 21 | FunctionExpression, 22 | GetShorthand, 23 | IdentifierToken, 24 | IfExpression, 25 | IntegerLiteralExpression, 26 | IntegerLiteralToken, 27 | MethodCallExpression, 28 | MethodSlot, 29 | NodeArray, 30 | NullExpression, 31 | NullKeywordToken, 32 | ObjectsExpression, 33 | ObjectSlot, 34 | ParenExpression, 35 | PrintingExpression, 36 | SequenceOfStatements, 37 | SetShorthand, 38 | SlotAssignmentExpression, 39 | SlotLookupExpression, 40 | SourceFile, 41 | StringLiteralToken, 42 | SubToken, 43 | SyntaxKind, 44 | ThisExpression, 45 | Token, 46 | TokenSyntaxKind, 47 | VariableAssignmentExpression, 48 | VariableReferenceExpression, 49 | VariableSlot, 50 | VariableStatement, 51 | WhileExpression, 52 | StringLiteralExpression, 53 | MethodSlotSignatureDeclaration, 54 | ArraysTypeNode, 55 | IntegerTypeNode, 56 | TypeReferenceTypeNode, 57 | NullTypeNode 58 | } from './types'; 59 | 60 | export function createNode(kind: T['kind']): T { 61 | const token = { kind } as T; 62 | token.pos = -1; 63 | token.end = -1; 64 | return token; 65 | } 66 | 67 | export function createNodeArray( 68 | nodes: readonly T[] 69 | ): NodeArray { 70 | const arr = nodes as NodeArray; 71 | arr.pos = -1; 72 | arr.end = -1; 73 | return arr; 74 | } 75 | 76 | export function createToken(kind: T): Token { 77 | return createNode(kind); 78 | } 79 | 80 | export function createStringLiteralToken(value: string): StringLiteralToken { 81 | const token = createToken( 82 | SyntaxKind.StringLiteralToken 83 | ) as StringLiteralToken; 84 | token.value = value; 85 | return token; 86 | } 87 | 88 | export function createNumberLiteralToken(value: string): IntegerLiteralToken { 89 | const token = createToken( 90 | SyntaxKind.IntegerLiteralToken 91 | ) as IntegerLiteralToken; 92 | token.value = value; 93 | return token; 94 | } 95 | 96 | export function createIdentifier(text: string): IdentifierToken { 97 | const token = createToken(SyntaxKind.Identifier) as IdentifierToken; 98 | token.text = text; 99 | return token; 100 | } 101 | 102 | export function createSourceFile( 103 | body: SequenceOfStatements, 104 | eof: EndOfFileToken 105 | ): SourceFile { 106 | const node = createNode(SyntaxKind.SourceFile); 107 | node.body = body; 108 | node.eof = eof; 109 | return node; 110 | } 111 | 112 | export function createIntegerLiteralExpression( 113 | value: IntegerLiteralToken, 114 | subToken?: SubToken 115 | ): IntegerLiteralExpression { 116 | const node = createNode( 117 | SyntaxKind.IntegerLiteralExpression 118 | ); 119 | node.value = value; 120 | node.subToken = subToken; 121 | return node; 122 | } 123 | 124 | export function createStringLiteralExpression( 125 | value: StringLiteralToken 126 | ): StringLiteralExpression { 127 | const node = createNode( 128 | SyntaxKind.StringLiteralExpression 129 | ); 130 | node.value = value; 131 | return node; 132 | } 133 | 134 | export function createVariableReferenceExpression( 135 | name: IdentifierToken 136 | ): VariableReferenceExpression { 137 | const node = createNode( 138 | SyntaxKind.VariableReferenceExpression 139 | ); 140 | node.name = name; 141 | return node; 142 | } 143 | 144 | export function createPrintingExpression( 145 | args: NodeArray 146 | ): PrintingExpression { 147 | const node = createNode(SyntaxKind.PrintingExpression); 148 | node.args = args; 149 | return node; 150 | } 151 | 152 | export function createArraysExpression( 153 | length: Expression, 154 | defaultValue?: Expression 155 | ): ArraysExpression { 156 | const node = createNode(SyntaxKind.ArraysExpression); 157 | node.length = length; 158 | node.defaultValue = defaultValue; 159 | return node; 160 | } 161 | 162 | export function createNullExpression(token: NullKeywordToken): NullExpression { 163 | const node = createNode(SyntaxKind.NullExpression); 164 | node.token = token; 165 | return node; 166 | } 167 | 168 | export function createObjectsExpression( 169 | objectToken: ObjectsKeywordToken, 170 | extendsClause: Expression | undefined, 171 | slots: NodeArray 172 | ): ObjectsExpression { 173 | const node = createNode(SyntaxKind.ObjectsExpression); 174 | node.objectToken = objectToken; 175 | node.extendsClause = extendsClause; 176 | node.slots = slots; 177 | return node; 178 | } 179 | 180 | export function createVariableSlot( 181 | name: IdentifierToken, 182 | type: TypeNode | undefined, 183 | initializer: Expression 184 | ): VariableSlot { 185 | const node = createNode(SyntaxKind.VariableSlot); 186 | node.name = name; 187 | node.type = type; 188 | node.initializer = initializer; 189 | return node; 190 | } 191 | 192 | export function createVariableStatement( 193 | name: IdentifierToken, 194 | type: TypeNode | undefined, 195 | initializer: Expression 196 | ): VariableStatement { 197 | const node = createNode(SyntaxKind.VariableStatement); 198 | node.name = name; 199 | node.type = type; 200 | node.initializer = initializer; 201 | return node; 202 | } 203 | 204 | export function createExpressionStatement( 205 | expression: Expression 206 | ): ExpressionStatement { 207 | const node = createNode(SyntaxKind.ExpressionStatement); 208 | node.expression = expression; 209 | return node; 210 | } 211 | 212 | export function createSequenceOfStatements( 213 | statements: NodeArray, 214 | isExpression: T 215 | ): SequenceOfStatements { 216 | const node = createNode>( 217 | SyntaxKind.SequenceOfStatements 218 | ); 219 | node.statements = statements; 220 | node.isExpression = isExpression; 221 | return node; 222 | } 223 | 224 | export function createFunctionStatement( 225 | name: IdentifierToken, 226 | params: NodeArray, 227 | type: TypeNode | undefined, 228 | body: SequenceOfStatements | ExpressionStatement 229 | ): FunctionStatement { 230 | const node = createNode(SyntaxKind.FunctionStatement); 231 | node.name = name; 232 | node.params = params; 233 | node.type = type; 234 | node.body = body; 235 | return node; 236 | } 237 | 238 | export function createBreakExpression(): BreakExpression { 239 | const node = createNode(SyntaxKind.BreakExpression); 240 | return node; 241 | } 242 | 243 | export function createContinueExpression(): ContinueExpression { 244 | const node = createNode(SyntaxKind.ContinueExpression); 245 | return node; 246 | } 247 | 248 | export function createMethodSlot( 249 | name: IdentifierToken, 250 | params: NodeArray, 251 | type: TypeNode | undefined, 252 | body: SequenceOfStatements | ExpressionStatement 253 | ): MethodSlot { 254 | const node = createNode(SyntaxKind.MethodSlot); 255 | node.name = name; 256 | node.params = params; 257 | node.type = type; 258 | node.body = body; 259 | return node; 260 | } 261 | 262 | export function createIfExpression( 263 | condition: Expression, 264 | thenStatement: SequenceOfStatements | ExpressionStatement, 265 | elseStatement?: SequenceOfStatements | ExpressionStatement 266 | ): IfExpression { 267 | const node = createNode(SyntaxKind.IfExpression); 268 | node.condition = condition; 269 | node.thenStatement = thenStatement; 270 | node.elseStatement = elseStatement; 271 | return node; 272 | } 273 | 274 | export function createWhileExpression( 275 | condition: Expression, 276 | body: SequenceOfStatements | ExpressionStatement 277 | ): WhileExpression { 278 | const node = createNode(SyntaxKind.WhileExpression); 279 | node.condition = condition; 280 | node.body = body; 281 | return node; 282 | } 283 | 284 | export function createSlotLookupExpression( 285 | expression: AccessOrAssignmentExpressionOrHigher, 286 | name: IdentifierToken 287 | ): SlotLookupExpression { 288 | const node = createNode( 289 | SyntaxKind.SlotLookupExpression 290 | ); 291 | node.expression = expression; 292 | node.name = name; 293 | return node; 294 | } 295 | 296 | export function createSlotAssignmentExpression( 297 | expression: AccessOrAssignmentExpressionOrHigher, 298 | name: IdentifierToken, 299 | value: Expression 300 | ): SlotAssignmentExpression { 301 | const node = createNode( 302 | SyntaxKind.SlotAssignmentExpression 303 | ); 304 | node.expression = expression; 305 | node.name = name; 306 | node.value = value; 307 | return node; 308 | } 309 | 310 | export function createGetShorthand( 311 | expression: AccessOrAssignmentExpressionOrHigher, 312 | args: NodeArray 313 | ): GetShorthand { 314 | const node = createNode(SyntaxKind.GetShorthand); 315 | node.expression = expression; 316 | node.args = args; 317 | return node; 318 | } 319 | 320 | export function createSetShorthand( 321 | expression: AccessOrAssignmentExpressionOrHigher, 322 | args: NodeArray, 323 | value: Expression 324 | ): SetShorthand { 325 | const node = createNode(SyntaxKind.SetShorthand); 326 | node.expression = expression; 327 | node.args = args; 328 | node.value = value; 329 | return node; 330 | } 331 | 332 | export function createVariableAssignmentExpression( 333 | expression: VariableReferenceExpression, 334 | value: Expression 335 | ): VariableAssignmentExpression { 336 | const node = createNode( 337 | SyntaxKind.VariableAssignmentExpression 338 | ); 339 | node.expression = expression; 340 | node.value = value; 341 | return node; 342 | } 343 | 344 | export function createMethodCallExpression( 345 | expression: AccessOrAssignmentExpressionOrHigher, 346 | name: IdentifierToken, 347 | args: NodeArray 348 | ): MethodCallExpression { 349 | const node = createNode( 350 | SyntaxKind.MethodCallExpression 351 | ); 352 | node.expression = expression; 353 | node.name = name; 354 | node.args = args; 355 | return node; 356 | } 357 | 358 | export function createFunctionCallExpression( 359 | expression: Expression, 360 | args: NodeArray 361 | ): FunctionCallExpression { 362 | const node = createNode( 363 | SyntaxKind.FunctionCallExpression 364 | ); 365 | node.expression = expression; 366 | node.args = args; 367 | return node; 368 | } 369 | 370 | export function createFunctionExpression( 371 | name: IdentifierToken, 372 | params: NodeArray, 373 | type: TypeNode | undefined, 374 | body: SequenceOfStatements | ExpressionStatement 375 | ) { 376 | const node = createNode(SyntaxKind.FunctionExpression); 377 | node.name = name; 378 | node.params = params; 379 | node.type = type; 380 | node.body = body; 381 | return node; 382 | } 383 | 384 | export function createBinaryShorthand( 385 | left: Expression, 386 | operator: Token, 387 | right: Expression 388 | ): BinaryShorthand { 389 | const node = createNode(SyntaxKind.BinaryShorthand); 390 | node.left = left; 391 | node.operator = operator; 392 | node.right = right; 393 | return node; 394 | } 395 | 396 | export function createThisExpression() { 397 | const node = createNode(SyntaxKind.ThisExpression); 398 | return node; 399 | } 400 | 401 | export function createParenExpression(expression: Expression) { 402 | const node = createNode(SyntaxKind.ParenExpression); 403 | node.expression = expression; 404 | return node; 405 | } 406 | 407 | export function createParameterDeclaration(name: IdentifierToken, type: TypeNode | undefined) { 408 | const node = createNode(SyntaxKind.ParameterDeclaration); 409 | node.name = name; 410 | node.type = type; 411 | return node; 412 | } 413 | 414 | export function createTypeDefDeclaration(name: IdentifierToken, slots: NodeArray) { 415 | const node = createNode(SyntaxKind.TypeDefDeclaration); 416 | node.name = name; 417 | node.slots = slots; 418 | return node; 419 | } 420 | 421 | export function createVariableSlotSignautre(name: IdentifierToken, type: TypeNode) { 422 | const node = createNode(SyntaxKind.VariableSlotSignatureDeclaration); 423 | node.name = name; 424 | node.type = type; 425 | return node; 426 | } 427 | 428 | export function createMethodSlotSignature(name: IdentifierToken, params: NodeArray, type: TypeNode) { 429 | const node = createNode(SyntaxKind.MethodSlotSignatureDeclaration); 430 | node.name = name; 431 | node.params = params; 432 | node.type = type; 433 | return node; 434 | } 435 | 436 | export function createIntegerTypeNode() { 437 | const node = createNode(SyntaxKind.IntegerTypeNode); 438 | return node; 439 | } 440 | 441 | export function createNullTypeNode() { 442 | const node = createNode(SyntaxKind.NullTypeNode); 443 | return node; 444 | } 445 | 446 | export function createArraysTypeNode(type: TypeNode) { 447 | const node = createNode(SyntaxKind.ArraysTypeNode); 448 | node.type = type; 449 | return node; 450 | } 451 | 452 | export function createTypeReferenceTypeNode(name: IdentifierToken) { 453 | const node = createNode(SyntaxKind.TypeReferenceTypeNode); 454 | node.name = name; 455 | return node; 456 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { forEachChild } from './visitor'; 2 | import { 3 | KeywordSyntaxKind, 4 | ObjectSlot, 5 | ASTNode, 6 | Symbol, 7 | Token, 8 | SyntaxKind, 9 | NodeArray, 10 | BinaryShorthandTokenSyntaxKind, 11 | BinaryShorthandToken, 12 | Statement, 13 | Expression, 14 | Declaration, 15 | SymbolFlag, 16 | HasLocalVariables, 17 | HasMembers, 18 | TextSpan 19 | } from './types'; 20 | 21 | export function finishNode( 22 | node: T, 23 | pos: number, 24 | end: number, 25 | text: string 26 | ): T { 27 | node.pos = pos; 28 | node.end = end; 29 | 30 | setupDebugInfo(node, text); 31 | return node; 32 | } 33 | 34 | export function createFinishNode(text: string) { 35 | return wrapper; 36 | 37 | function wrapper(node: T, pos: number, end: number): T { 38 | node.pos = pos; 39 | node.end = end; 40 | 41 | setupDebugInfo(node, text); 42 | return node; 43 | } 44 | } 45 | 46 | export function finishNodeArray( 47 | nodes: NodeArray, 48 | pos: number, 49 | end: number 50 | ): NodeArray { 51 | nodes.pos = pos; 52 | nodes.end = end; 53 | return nodes; 54 | } 55 | 56 | export function setupDebugInfo(node: ASTNode, text: string) { 57 | Object.defineProperty(node, '__debugKind', { 58 | get() { 59 | return SyntaxKind[node.kind]; 60 | }, 61 | enumerable: true 62 | }); 63 | 64 | Object.defineProperty(node, '__debugText', { 65 | get() { 66 | return text.substring(node.pos, node.end); 67 | }, 68 | enumerable: false 69 | }); 70 | } 71 | 72 | export function setupSymbolDebugInfo (symbol: Symbol) { 73 | Object.defineProperty(symbol, '_debugFlags', { 74 | enumerable: false, 75 | get () { 76 | return symbolFlagToDisplayText(symbol.flags); 77 | } 78 | }); 79 | } 80 | 81 | export function isDef(v: T): v is NonNullable { 82 | return v !== undefined && v !== null; 83 | } 84 | 85 | export function assertDef( 86 | v: T, 87 | message?: string 88 | ): asserts v is NonNullable { 89 | if (!isDef(v)) { 90 | throw new Error(message ?? 'Must be defined'); 91 | } 92 | } 93 | 94 | export function assert(v: any, message?: string): asserts v { 95 | if (!v) { 96 | throw new Error(message ?? 'Assertion failed'); 97 | } 98 | } 99 | 100 | export function assertKind(node: ASTNode): asserts node is T { 101 | 102 | } 103 | 104 | export function first(v?: readonly T[]): T { 105 | if (!v?.length) { 106 | throw new Error('Index out of range'); 107 | } 108 | return v[0]; 109 | } 110 | 111 | export function last(v?: readonly T[]): T { 112 | if (!v?.length) { 113 | throw new Error('Index out of range'); 114 | } 115 | return v[v.length - 1]; 116 | } 117 | 118 | export function lastOrUndefined(v?: readonly T[]): T | undefined { 119 | if (!v?.length) { 120 | return undefined; 121 | } 122 | return v[v.length - 1]; 123 | } 124 | 125 | export function frontAndTail(v?: readonly T[]): [T[], T] { 126 | if (!v?.length) { 127 | throw new Error('Index out of range'); 128 | } 129 | 130 | const front = v.slice(0, v.length - 1); 131 | const tail = v[v.length - 1]; 132 | return [front, tail]; 133 | } 134 | 135 | 136 | export function findAncestor(node: ASTNode, pred: (v: ASTNode) => v is T): T | undefined 137 | export function findAncestor(node: ASTNode, pred: (v: ASTNode) => boolean): ASTNode | undefined 138 | export function findAncestor(node: ASTNode, pred: (v: ASTNode) => boolean): ASTNode | undefined { 139 | let parent: ASTNode | undefined = node 140 | while (parent) { 141 | if (pred(parent)) { 142 | return parent; 143 | } 144 | parent = parent.parent; 145 | } 146 | } 147 | 148 | export function textSpanIncludesPosition (textSpan: TextSpan, pos: number) { 149 | return textSpan.pos <= pos && pos < textSpan.end 150 | } 151 | 152 | export function findCurrentToken(root: ASTNode, pos: number) { 153 | let parent: ASTNode | undefined; 154 | visitor(root) 155 | return parent; 156 | 157 | function visitor (node: ASTNode) { 158 | if (textSpanIncludesPosition(node, pos)) { 159 | parent = node 160 | forEachChild(parent, visitor) 161 | } 162 | } 163 | } 164 | 165 | export enum Chars { 166 | Add = '+', 167 | Sub = '-', 168 | Mul = '*', 169 | Div = '/', 170 | Mod = '%', 171 | LessThan = '<', 172 | GreaterThan = '>', 173 | Equals = '=', 174 | OpenParen = '(', 175 | CloseParen = ')', 176 | OpenBracket = '[', 177 | CloseBracket = ']', 178 | Dot = '.', 179 | Colon = ':', 180 | Comma = ',', 181 | Quote = '"', 182 | BackSlash = '\\', 183 | Semi = ';', 184 | LowDash = '_', 185 | Question = '?', 186 | 187 | _0 = '0', 188 | _1 = '1', 189 | _2 = '2', 190 | _3 = '3', 191 | _4 = '4', 192 | _5 = '5', 193 | _6 = '6', 194 | _7 = '7', 195 | _8 = '8', 196 | _9 = '9', 197 | 198 | Whitespace = ' ', 199 | Tab = '\t', 200 | LineFeed = '\n' 201 | } 202 | 203 | export enum Keywords { 204 | Null = 'null', 205 | Arrays = 'array', 206 | Objects = 'object', 207 | Var = 'var', 208 | This = 'this', 209 | If = 'if', 210 | Else = 'else', 211 | While = 'while', 212 | Method = 'method', 213 | Defn = 'defn', 214 | Printf = 'printf', 215 | Continue = 'continue', 216 | Break = 'break', 217 | Integer = 'integer', 218 | TypeDef = 'typedef', 219 | } 220 | 221 | export function isKeyword(value: string): value is Keywords { 222 | switch (value) { 223 | case Keywords.Null: 224 | case Keywords.Arrays: 225 | case Keywords.Objects: 226 | case Keywords.Var: 227 | case Keywords.This: 228 | case Keywords.If: 229 | case Keywords.Else: 230 | case Keywords.While: 231 | case Keywords.Method: 232 | case Keywords.Defn: 233 | case Keywords.Printf: 234 | case Keywords.Break: 235 | case Keywords.Continue: 236 | case Keywords.Integer: 237 | case Keywords.TypeDef: 238 | return true; 239 | default: 240 | return false; 241 | } 242 | } 243 | 244 | export function isKeywordSyntaxKind( 245 | kind: SyntaxKind 246 | ): kind is KeywordSyntaxKind { 247 | switch (kind) { 248 | case SyntaxKind.PrintfKeyword: 249 | case SyntaxKind.ArraysKeyword: 250 | case SyntaxKind.NullKeyword: 251 | case SyntaxKind.ObjectsKeyword: 252 | case SyntaxKind.VarKeyword: 253 | case SyntaxKind.ThisKeyword: 254 | case SyntaxKind.IfKeyword: 255 | case SyntaxKind.ElseKeyword: 256 | case SyntaxKind.WhileKeyword: 257 | case SyntaxKind.MethodKeyword: 258 | case SyntaxKind.DefnKeyword: 259 | case SyntaxKind.ContinueKeyword: 260 | case SyntaxKind.BreakKeyword: 261 | case SyntaxKind.IntegerKeyword: 262 | case SyntaxKind.TypeDefKeyword: 263 | return true; 264 | default: 265 | return false; 266 | } 267 | } 268 | 269 | export function isBinaryShorthandTokenSyntaxKind( 270 | kind: SyntaxKind 271 | ): kind is BinaryShorthandTokenSyntaxKind { 272 | switch (kind) { 273 | case SyntaxKind.AddToken: 274 | case SyntaxKind.SubToken: 275 | case SyntaxKind.MulToken: 276 | case SyntaxKind.DivToken: 277 | case SyntaxKind.ModToken: 278 | case SyntaxKind.LessThanToken: 279 | case SyntaxKind.GreaterThanToken: 280 | case SyntaxKind.LessEqualsThanToken: 281 | case SyntaxKind.GreaterEqualsThanToken: 282 | case SyntaxKind.EqualsEqualsToken: 283 | return true; 284 | default: 285 | return false; 286 | } 287 | } 288 | 289 | export enum BinaryShorthandPriority { 290 | Lowest = 0, 291 | Comparison = 4, 292 | Mod = 5, 293 | AddOrSub = 6, 294 | MulOrDiv = 7 295 | } 296 | 297 | export function getBinaryShorthandPriority( 298 | kind: BinaryShorthandTokenSyntaxKind 299 | ) { 300 | switch (kind) { 301 | case SyntaxKind.LessThanToken: 302 | case SyntaxKind.LessEqualsThanToken: 303 | case SyntaxKind.GreaterThanToken: 304 | case SyntaxKind.GreaterEqualsThanToken: 305 | case SyntaxKind.EqualsEqualsToken: 306 | return BinaryShorthandPriority.Comparison; 307 | case SyntaxKind.ModToken: 308 | return BinaryShorthandPriority.Mod; 309 | case SyntaxKind.AddToken: 310 | case SyntaxKind.SubToken: 311 | return BinaryShorthandPriority.AddOrSub; 312 | case SyntaxKind.MulToken: 313 | case SyntaxKind.DivToken: 314 | return BinaryShorthandPriority.MulOrDiv; 315 | default: 316 | throw new Error('Invalid kind: ' + kind); 317 | } 318 | } 319 | 320 | export function isBinaryShorthandToken( 321 | token: Token 322 | ): token is BinaryShorthandToken { 323 | return isBinaryShorthandTokenSyntaxKind(token.kind); 324 | } 325 | 326 | export const CharsToTokenKind = { 327 | [Chars.Add]: SyntaxKind.AddToken, 328 | [Chars.Sub]: SyntaxKind.SubToken, 329 | [Chars.Mul]: SyntaxKind.MulToken, 330 | [Chars.Div]: SyntaxKind.DivToken, 331 | [Chars.Mod]: SyntaxKind.ModToken, 332 | [Chars.LessThan]: SyntaxKind.LessThanToken, 333 | [Chars.GreaterThan]: SyntaxKind.GreaterThanToken, 334 | [Chars.Equals]: SyntaxKind.EqualsToken, 335 | [Chars.OpenParen]: SyntaxKind.OpenParenToken, 336 | [Chars.CloseParen]: SyntaxKind.CloseParenToken, 337 | [Chars.OpenBracket]: SyntaxKind.OpenBracketToken, 338 | [Chars.CloseBracket]: SyntaxKind.CloseBracketToken, 339 | [Chars.Dot]: SyntaxKind.DotToken, 340 | [Chars.Colon]: SyntaxKind.ColonToken, 341 | [Chars.Comma]: SyntaxKind.CommaToken, 342 | } as const; 343 | 344 | export const KeywordsToTokenKind = { 345 | [Keywords.Null]: SyntaxKind.NullKeyword, 346 | [Keywords.Arrays]: SyntaxKind.ArraysKeyword, 347 | [Keywords.Objects]: SyntaxKind.ObjectsKeyword, 348 | [Keywords.Var]: SyntaxKind.VarKeyword, 349 | [Keywords.This]: SyntaxKind.ThisKeyword, 350 | [Keywords.If]: SyntaxKind.IfKeyword, 351 | [Keywords.Else]: SyntaxKind.ElseKeyword, 352 | [Keywords.While]: SyntaxKind.WhileKeyword, 353 | [Keywords.Method]: SyntaxKind.MethodKeyword, 354 | [Keywords.Defn]: SyntaxKind.DefnKeyword, 355 | [Keywords.Printf]: SyntaxKind.PrintfKeyword, 356 | [Keywords.Continue]: SyntaxKind.ContinueKeyword, 357 | [Keywords.Break]: SyntaxKind.BreakKeyword, 358 | [Keywords.Integer]: SyntaxKind.IntegerKeyword, 359 | [Keywords.TypeDef]: SyntaxKind.TypeDefKeyword, 360 | } as const; 361 | 362 | export const TokenKindsToKeyword = { 363 | [SyntaxKind.NullKeyword]: Keywords.Null, 364 | [SyntaxKind.ArraysKeyword]: Keywords.Arrays, 365 | [SyntaxKind.ObjectsKeyword]: Keywords.Objects, 366 | [SyntaxKind.VarKeyword]: Keywords.Var, 367 | [SyntaxKind.ThisKeyword]: Keywords.This, 368 | [SyntaxKind.IfKeyword]: Keywords.If, 369 | [SyntaxKind.ElseKeyword]: Keywords.Else, 370 | [SyntaxKind.WhileKeyword]: Keywords.While, 371 | [SyntaxKind.MethodKeyword]: Keywords.Method, 372 | [SyntaxKind.DefnKeyword]: Keywords.Defn, 373 | [SyntaxKind.PrintfKeyword]: Keywords.Printf, 374 | [SyntaxKind.ContinueKeyword]: Keywords.Continue, 375 | [SyntaxKind.BreakKeyword]: Keywords.Break, 376 | [SyntaxKind.IntegerKeyword]: Keywords.Integer, 377 | [SyntaxKind.TypeDefKeyword]: Keywords.TypeDef, 378 | } as const; 379 | 380 | export function isDigit(char: string): boolean { 381 | switch (char) { 382 | case Chars._0: 383 | case Chars._1: 384 | case Chars._2: 385 | case Chars._3: 386 | case Chars._4: 387 | case Chars._5: 388 | case Chars._6: 389 | case Chars._7: 390 | case Chars._8: 391 | case Chars._9: 392 | return true; 393 | default: 394 | return false; 395 | } 396 | } 397 | 398 | export function isAlpha(char: string): boolean { 399 | const charCode = char.charCodeAt(0); 400 | return ( 401 | (charCode >= 'a'.charCodeAt(0) && charCode <= 'z'.charCodeAt(0)) || 402 | (charCode >= 'A'.charCodeAt(0) && charCode <= 'Z'.charCodeAt(0)) 403 | ); 404 | } 405 | 406 | export function isWhiteSpaceOrTab(char: string): boolean { 407 | return char === Chars.Whitespace || char === Chars.Tab; 408 | } 409 | 410 | export function isAlphaOrDigitOrLowDashOrDashOrQuestion(char: string): boolean { 411 | return ( 412 | isAlpha(char) || 413 | isDigit(char) || 414 | char === Chars.LowDash || 415 | char === Chars.Sub || 416 | char === Chars.Question 417 | ); 418 | } 419 | 420 | export function getIndent(ch: string) { 421 | switch (ch) { 422 | case Chars.Whitespace: 423 | return 1; 424 | case Chars.Tab: 425 | return 4; 426 | default: 427 | return 0; 428 | } 429 | } 430 | 431 | export function isStatement(node: ASTNode): node is Statement { 432 | switch (node.kind) { 433 | case SyntaxKind.SequenceOfStatements: 434 | case SyntaxKind.VariableStatement: 435 | case SyntaxKind.ExpressionStatement: 436 | case SyntaxKind.FunctionStatement: 437 | return true; 438 | default: 439 | return false; 440 | } 441 | } 442 | 443 | export function isObjectSlot(node: ASTNode): node is ObjectSlot { 444 | switch (node.kind) { 445 | case SyntaxKind.MethodSlot: 446 | case SyntaxKind.VariableSlot: 447 | return true; 448 | default: 449 | return false; 450 | } 451 | } 452 | 453 | export function isExpression(node: ASTNode): node is Expression { 454 | switch (node.kind) { 455 | case SyntaxKind.IntegerLiteralExpression: 456 | case SyntaxKind.VariableReferenceExpression: 457 | case SyntaxKind.PrintingExpression: 458 | case SyntaxKind.ArraysExpression: 459 | case SyntaxKind.NullExpression: 460 | case SyntaxKind.ObjectsExpression: 461 | case SyntaxKind.MethodCallExpression: 462 | case SyntaxKind.SlotLookupExpression: 463 | case SyntaxKind.SlotAssignmentExpression: 464 | case SyntaxKind.FunctionCallExpression: 465 | case SyntaxKind.VariableAssignmentExpression: 466 | case SyntaxKind.IfExpression: 467 | case SyntaxKind.WhileExpression: 468 | case SyntaxKind.ThisExpression: 469 | case SyntaxKind.ParenExpression: 470 | case SyntaxKind.GetShorthand: 471 | case SyntaxKind.SetShorthand: 472 | case SyntaxKind.BreakExpression: 473 | case SyntaxKind.ContinueExpression: 474 | return true; 475 | default: 476 | return false; 477 | } 478 | } 479 | 480 | export function isDeclaration(node: ASTNode): node is Declaration { 481 | switch (node.kind) { 482 | case SyntaxKind.VariableStatement: 483 | case SyntaxKind.FunctionStatement: 484 | case SyntaxKind.VariableSlot: 485 | case SyntaxKind.MethodSlot: 486 | case SyntaxKind.ParameterDeclaration: 487 | case SyntaxKind.ObjectsExpression: 488 | case SyntaxKind.MethodSlotSignatureDeclaration: 489 | case SyntaxKind.VariableSlotSignatureDeclaration: 490 | case SyntaxKind.TypeDefDeclaration: 491 | return true; 492 | default: 493 | return false; 494 | } 495 | } 496 | 497 | 498 | export function symbolFlagToDisplayText (flags: SymbolFlag) { 499 | const text = []; 500 | if (flags & SymbolFlag.Variable) { 501 | flags &= ~SymbolFlag.Variable; 502 | text.push(SymbolFlag[SymbolFlag.Variable]); 503 | } 504 | if (flags & SymbolFlag.Parameter) { 505 | flags &= ~SymbolFlag.Parameter; 506 | text.push(SymbolFlag[SymbolFlag.Parameter]); 507 | } 508 | if (flags & SymbolFlag.Function) { 509 | flags &= ~SymbolFlag.Function; 510 | text.push(SymbolFlag[SymbolFlag.Function]); 511 | } 512 | if (flags & SymbolFlag.VariableSlot) { 513 | flags &= ~SymbolFlag.VariableSlot; 514 | text.push(SymbolFlag[SymbolFlag.VariableSlot]); 515 | } 516 | if (flags & SymbolFlag.MethodSlot) { 517 | flags &= ~SymbolFlag.MethodSlot; 518 | text.push(SymbolFlag[SymbolFlag.MethodSlot]); 519 | } 520 | if (flags & SymbolFlag.AnomymousObject) { 521 | flags &= ~SymbolFlag.AnomymousObject; 522 | text.push(SymbolFlag[SymbolFlag.AnomymousObject]); 523 | } 524 | if (flags & SymbolFlag.TypeDef) { 525 | flags &= ~SymbolFlag.TypeDef; 526 | text.push(SymbolFlag[SymbolFlag.TypeDef]); 527 | } 528 | 529 | assert(flags === SymbolFlag.None, `Unknown symbol flag: ${flags}`); 530 | return text.join(' | '); 531 | } 532 | 533 | export function getDeclarationSymbolFlags (node: ASTNode): SymbolFlag { 534 | switch (node.kind) { 535 | case SyntaxKind.VariableSlot: 536 | return SymbolFlag.VariableSlot; 537 | case SyntaxKind.VariableStatement: 538 | case SyntaxKind.VariableSlotSignatureDeclaration: 539 | return SymbolFlag.Variable; 540 | case SyntaxKind.ParameterDeclaration: 541 | return SymbolFlag.Parameter; 542 | case SyntaxKind.FunctionStatement: 543 | return SymbolFlag.Function; 544 | case SyntaxKind.MethodSlot: 545 | case SyntaxKind.MethodSlotSignatureDeclaration: 546 | return SymbolFlag.MethodSlot; 547 | case SyntaxKind.ObjectsExpression: 548 | return SymbolFlag.AnomymousObject; 549 | case SyntaxKind.TypeDefDeclaration: 550 | return SymbolFlag.TypeDef; 551 | default: 552 | return SymbolFlag.None; 553 | } 554 | } 555 | 556 | export function isLocalVariableContainer (node: ASTNode): node is HasLocalVariables { 557 | switch (node.kind) { 558 | case SyntaxKind.SequenceOfStatements: 559 | case SyntaxKind.SourceFile: 560 | case SyntaxKind.MethodSlot: 561 | case SyntaxKind.FunctionStatement: 562 | case SyntaxKind.FunctionExpression: 563 | return true; 564 | default: 565 | return false; 566 | } 567 | } 568 | 569 | export function isMemberContainer (node: ASTNode): node is HasMembers { 570 | switch (node.kind) { 571 | case SyntaxKind.ObjectsExpression: 572 | case SyntaxKind.TypeDefDeclaration: 573 | return true 574 | default: 575 | return false; 576 | } 577 | } 578 | 579 | export function shorthandTokenToOperator(kind: BinaryShorthandTokenSyntaxKind) { 580 | switch (kind) { 581 | case SyntaxKind.AddToken: 582 | return 'add'; 583 | case SyntaxKind.SubToken: 584 | return 'sub'; 585 | case SyntaxKind.MulToken: 586 | return 'mul'; 587 | case SyntaxKind.DivToken: 588 | return 'div'; 589 | case SyntaxKind.ModToken: 590 | return 'mod'; 591 | case SyntaxKind.LessThanToken: 592 | return 'lt'; 593 | case SyntaxKind.GreaterThanToken: 594 | return 'gt'; 595 | case SyntaxKind.LessEqualsThanToken: 596 | return 'le'; 597 | case SyntaxKind.GreaterEqualsThanToken: 598 | return 'ge'; 599 | case SyntaxKind.EqualsEqualsToken: 600 | return 'eq'; 601 | default: 602 | throw new Error('Invalid operator: ' + SyntaxKind[kind]); 603 | } 604 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export enum SyntaxKind { 2 | Unknown, 3 | 4 | // Token 5 | CommaToken, 6 | OpenParenToken, 7 | CloseParenToken, 8 | ColonToken, 9 | DotToken, 10 | EqualsToken, 11 | OpenBracketToken, 12 | CloseBracketToken, 13 | SubGreaterThanToken, 14 | 15 | AddToken, 16 | SubToken, 17 | MulToken, 18 | DivToken, 19 | ModToken, 20 | LessThanToken, 21 | GreaterThanToken, 22 | LessEqualsThanToken, 23 | GreaterEqualsThanToken, 24 | EqualsEqualsToken, 25 | 26 | IntegerLiteralToken, 27 | StringLiteralToken, 28 | Identifier, 29 | 30 | // Syntax 31 | PrintfKeyword, 32 | ArraysKeyword, 33 | NullKeyword, 34 | ObjectsKeyword, 35 | VarKeyword, 36 | ThisKeyword, 37 | IfKeyword, 38 | ElseKeyword, 39 | WhileKeyword, 40 | MethodKeyword, 41 | DefnKeyword, 42 | BreakKeyword, 43 | ContinueKeyword, 44 | IntegerKeyword, 45 | TypeDefKeyword, 46 | 47 | TypeReferenceTypeNode, 48 | NullTypeNode, 49 | IntegerTypeNode, 50 | ArraysTypeNode, 51 | 52 | EndOfFileToken, 53 | 54 | SourceFile, 55 | 56 | // Expression 57 | IntegerLiteralExpression, 58 | StringLiteralExpression, 59 | VariableReferenceExpression, 60 | PrintingExpression, 61 | ArraysExpression, 62 | NullExpression, 63 | ObjectsExpression, 64 | MethodCallExpression, 65 | SlotLookupExpression, 66 | SlotAssignmentExpression, 67 | FunctionCallExpression, 68 | VariableAssignmentExpression, 69 | IfExpression, 70 | WhileExpression, 71 | ThisExpression, 72 | ParenExpression, 73 | FunctionExpression, 74 | BreakExpression, 75 | ContinueExpression, 76 | 77 | // Object slot 78 | VariableSlot, 79 | MethodSlot, 80 | VariableSlotSignatureDeclaration, 81 | MethodSlotSignatureDeclaration, 82 | 83 | // Statements 84 | VariableStatement, 85 | SequenceOfStatements, 86 | ExpressionStatement, 87 | FunctionStatement, 88 | 89 | // Declaration 90 | ParameterDeclaration, 91 | 92 | // Type Declaration 93 | TypeDefDeclaration, 94 | 95 | // Shorthand 96 | BinaryShorthand, 97 | GetShorthand, 98 | SetShorthand 99 | } 100 | 101 | export interface TextSpan { 102 | pos: number; 103 | end: number; 104 | } 105 | 106 | export interface Diangostic { 107 | message: string 108 | span: TextSpan 109 | } 110 | 111 | export interface ASTNode extends TextSpan { 112 | kind: SyntaxKind; 113 | parent?: ASTNode; 114 | 115 | fullPos: number; 116 | 117 | __debugKind?: string; 118 | __debugText?: string; 119 | 120 | leadingIndent: number; 121 | 122 | locals?: SymbolTable; 123 | } 124 | 125 | export interface NodeArray 126 | extends ReadonlyArray, 127 | TextSpan { 128 | _nodeArrayBrand: never; 129 | } 130 | 131 | export interface SourceFile extends ASTNode { 132 | _sourceFileBrand: never; 133 | kind: SyntaxKind.SourceFile; 134 | body: SequenceOfStatements; 135 | eof: EndOfFileToken; 136 | } 137 | 138 | export interface Token extends ASTNode { 139 | _tokenBrand: never; 140 | kind: K; 141 | } 142 | 143 | export interface IdentifierToken extends Token { 144 | text: string; 145 | keyword?: KeywordTokens; 146 | } 147 | 148 | export interface StringLiteralToken 149 | extends Token { 150 | value: string; 151 | } 152 | 153 | export interface IntegerLiteralToken 154 | extends Token { 155 | value: string; 156 | } 157 | 158 | export type EndOfFileToken = Token; 159 | export type NullKeywordToken = Token; 160 | export type ArraysKeywordToken = Token; 161 | export type ObjectsKeywordToken = Token; 162 | export type VarKeywordToken = Token; 163 | export type ThisKeywordToken = Token; 164 | export type IfKeywordToken = Token; 165 | export type ElseKeywordToken = Token; 166 | export type WhileKeywordToken = Token; 167 | export type MethodKeywordToken = Token; 168 | export type DefnKeywordToken = Token; 169 | export type PrintfKeywordToken = Token; 170 | export type ContinueKeywordToken = Token; 171 | export type BreakKeywordToken = Token; 172 | export type IntegerKeywordToken = Token; 173 | export type TypeDefKeywordToken = Token; 174 | 175 | export type OpenParenToken = Token; 176 | export type CloseParenToken = Token; 177 | export type CommaToken = Token; 178 | export type ColonToken = Token; 179 | export type DotToken = Token; 180 | export type EqualsToken = Token; 181 | export type OpenBracketToken = Token; 182 | export type CloseBracketToken = Token; 183 | export type AddToken = Token; 184 | export type SubToken = Token; 185 | export type MulToken = Token; 186 | export type DivToken = Token; 187 | export type ModToken = Token; 188 | export type LessThanToken = Token; 189 | export type GreaterThanToken = Token; 190 | export type LessEqualsThanToken = Token; 191 | export type GreaterEqualsThanToken = Token; 192 | export type EqualsEqualsToken = Token; 193 | export type SubGreaterThanToken = Token; 194 | 195 | export type AllKeywords = 196 | | NullKeywordToken 197 | | ArraysKeywordToken 198 | | ObjectsKeywordToken 199 | | VarKeywordToken 200 | | ThisKeywordToken 201 | | IfKeywordToken 202 | | ElseKeywordToken 203 | | WhileKeywordToken 204 | | MethodKeywordToken 205 | | DefnKeywordToken 206 | | PrintfKeywordToken 207 | | ContinueKeywordToken 208 | | BreakKeywordToken 209 | | IntegerKeywordToken 210 | | TypeDefKeywordToken 211 | 212 | export type AllTokens = 213 | | AllKeywords 214 | | EndOfFileToken 215 | | OpenParenToken 216 | | CloseParenToken 217 | | CommaToken 218 | | ColonToken 219 | | DotToken 220 | | EqualsToken 221 | | OpenBracketToken 222 | | CloseBracketToken 223 | | AddToken 224 | | SubToken 225 | | MulToken 226 | | DivToken 227 | | ModToken 228 | | LessThanToken 229 | | GreaterThanToken 230 | | LessEqualsThanToken 231 | | GreaterEqualsThanToken 232 | | EqualsEqualsToken 233 | | IdentifierToken 234 | | StringLiteralToken 235 | | IntegerLiteralToken 236 | | SubGreaterThanToken; 237 | 238 | export type TokenSyntaxKind = AllTokens['kind']; 239 | 240 | export type KeywordTokens = 241 | | PrintfKeywordToken 242 | | ArraysKeywordToken 243 | | NullKeywordToken 244 | | ObjectsKeywordToken 245 | | VarKeywordToken 246 | | ThisKeywordToken 247 | | IfKeywordToken 248 | | ElseKeywordToken 249 | | WhileKeywordToken 250 | | MethodKeywordToken 251 | | ContinueKeywordToken 252 | | BreakKeywordToken 253 | | DefnKeywordToken; 254 | 255 | export type KeywordSyntaxKind = KeywordTokens['kind']; 256 | 257 | export interface Expression extends ASTNode { 258 | _expressionBrand: never; 259 | } 260 | 261 | export interface IntegerLiteralExpression extends Expression { 262 | kind: SyntaxKind.IntegerLiteralExpression; 263 | value: IntegerLiteralToken; 264 | subToken?: SubToken; 265 | } 266 | 267 | export interface StringLiteralExpression extends Expression { 268 | kind: SyntaxKind.StringLiteralExpression; 269 | value: StringLiteralToken; 270 | } 271 | 272 | export interface VariableReferenceExpression extends Expression { 273 | kind: SyntaxKind.VariableReferenceExpression; 274 | name: IdentifierToken; 275 | } 276 | 277 | export interface PrintingExpression extends Expression { 278 | kind: SyntaxKind.PrintingExpression; 279 | args: NodeArray; 280 | } 281 | 282 | export interface ArraysExpression extends Expression { 283 | kind: SyntaxKind.ArraysExpression; 284 | length: Expression; 285 | defaultValue?: Expression; 286 | } 287 | 288 | export interface NullExpression extends Expression { 289 | kind: SyntaxKind.NullExpression; 290 | token: NullKeywordToken; 291 | } 292 | 293 | export interface ObjectsExpression extends Expression, Declaration { 294 | kind: SyntaxKind.ObjectsExpression; 295 | objectToken: ObjectsKeywordToken; 296 | extendsClause?: Expression; 297 | slots: NodeArray; 298 | } 299 | 300 | export interface MethodCallExpression extends Expression { 301 | kind: SyntaxKind.MethodCallExpression; 302 | expression: AccessOrAssignmentExpressionOrHigher; 303 | name: IdentifierToken; 304 | args: NodeArray; 305 | } 306 | 307 | export interface SlotLookupExpression extends Expression { 308 | kind: SyntaxKind.SlotLookupExpression; 309 | expression: AccessOrAssignmentExpressionOrHigher; 310 | name: IdentifierToken; 311 | } 312 | 313 | export interface SlotAssignmentExpression extends Expression { 314 | kind: SyntaxKind.SlotAssignmentExpression; 315 | expression: AccessOrAssignmentExpressionOrHigher; 316 | name: IdentifierToken; 317 | value: Expression; 318 | } 319 | 320 | export interface FunctionCallExpression extends Expression { 321 | kind: SyntaxKind.FunctionCallExpression; 322 | expression: Expression; 323 | args: NodeArray; 324 | } 325 | 326 | export interface VariableAssignmentExpression extends Expression { 327 | kind: SyntaxKind.VariableAssignmentExpression; 328 | expression: VariableReferenceExpression; 329 | value: Expression; 330 | } 331 | 332 | export interface IfExpression extends Expression { 333 | kind: SyntaxKind.IfExpression; 334 | condition: Expression; 335 | thenStatement: SequenceOfStatements | ExpressionStatement; 336 | elseStatement?: SequenceOfStatements | ExpressionStatement; 337 | } 338 | 339 | export interface WhileExpression extends Expression { 340 | kind: SyntaxKind.WhileExpression; 341 | condition: Expression; 342 | body: SequenceOfStatements | ExpressionStatement; 343 | } 344 | 345 | export interface ContinueExpression extends Expression { 346 | kind: SyntaxKind.ContinueExpression; 347 | } 348 | 349 | export interface BreakExpression extends Expression { 350 | kind: SyntaxKind.BreakExpression; 351 | } 352 | 353 | export interface ThisExpression extends Expression { 354 | kind: SyntaxKind.ThisExpression; 355 | } 356 | 357 | export interface ParenExpression extends Expression { 358 | kind: SyntaxKind.ParenExpression; 359 | expression: Expression; 360 | } 361 | 362 | export interface BinaryShorthand extends Expression { 363 | kind: SyntaxKind.BinaryShorthand; 364 | operator: Token; 365 | left: Expression; 366 | right: Expression; 367 | } 368 | 369 | export interface GetShorthand extends Expression { 370 | kind: SyntaxKind.GetShorthand; 371 | expression: AccessOrAssignmentExpressionOrHigher; 372 | args: NodeArray; 373 | } 374 | 375 | export interface SetShorthand extends Expression { 376 | kind: SyntaxKind.SetShorthand; 377 | expression: AccessOrAssignmentExpressionOrHigher; 378 | args: NodeArray; 379 | value: Expression; 380 | } 381 | 382 | export interface FunctionExpression extends Expression, FunctionBase { 383 | kind: SyntaxKind.FunctionExpression; 384 | name: IdentifierToken; 385 | } 386 | 387 | export interface Declaration extends ASTNode { 388 | _declarationBrand: never 389 | 390 | symbol?: Symbol; 391 | } 392 | 393 | export interface NamedDeclaration extends Declaration { 394 | name: IdentifierToken; 395 | } 396 | 397 | export interface ParameterDeclaration extends NamedDeclaration { 398 | kind: SyntaxKind.ParameterDeclaration; 399 | type?: TypeNode 400 | } 401 | 402 | export interface ParamsAndReturnType { 403 | params: NodeArray; 404 | type?: TypeNode; 405 | } 406 | 407 | export interface FunctionBase extends ParamsAndReturnType { 408 | body: SequenceOfStatements | ExpressionStatement; 409 | } 410 | 411 | export interface ObjectSlot extends NamedDeclaration { 412 | _objectSlotBrand: never; 413 | } 414 | 415 | export interface VariableSlot extends ObjectSlot { 416 | kind: SyntaxKind.VariableSlot; 417 | type?: TypeNode; 418 | initializer: Expression; 419 | } 420 | 421 | export interface MethodSlot extends ObjectSlot, FunctionBase { 422 | kind: SyntaxKind.MethodSlot; 423 | } 424 | export interface Statement extends ASTNode { 425 | _statementBrand: never; 426 | } 427 | 428 | export interface VariableStatement extends NamedDeclaration, Statement { 429 | kind: SyntaxKind.VariableStatement; 430 | type?: TypeNode; 431 | initializer: Expression; 432 | } 433 | 434 | export interface SequenceOfStatements extends Statement { 435 | kind: SyntaxKind.SequenceOfStatements; 436 | statements: NodeArray; 437 | isExpression: IsExpr; 438 | } 439 | 440 | export interface ExpressionStatement extends Statement { 441 | kind: SyntaxKind.ExpressionStatement; 442 | expression: Expression; 443 | } 444 | 445 | export interface FunctionStatement extends Statement, NamedDeclaration, FunctionBase { 446 | kind: SyntaxKind.FunctionStatement; 447 | } 448 | 449 | export interface TypeNode extends ASTNode { 450 | _typeNodeBrand: never; 451 | } 452 | 453 | export interface TypeReferenceTypeNode extends TypeNode { 454 | kind: SyntaxKind.TypeReferenceTypeNode; 455 | name: IdentifierToken; 456 | } 457 | 458 | export interface ArraysTypeNode extends TypeNode { 459 | kind: SyntaxKind.ArraysTypeNode; 460 | type: TypeNode; 461 | } 462 | 463 | export interface NullTypeNode extends TypeNode { 464 | kind: SyntaxKind.NullTypeNode; 465 | } 466 | 467 | export interface IntegerTypeNode extends TypeNode { 468 | kind: SyntaxKind.IntegerTypeNode; 469 | } 470 | 471 | export interface TypeDefDeclaration extends NamedDeclaration, Statement, TypeNode { 472 | kind: SyntaxKind.TypeDefDeclaration 473 | name: IdentifierToken 474 | slots: NodeArray 475 | } 476 | 477 | export interface ObjectSlotSignature extends NamedDeclaration, TypeNode { 478 | 479 | } 480 | 481 | export interface VariableSlotSignatureDeclaration extends ObjectSlotSignature { 482 | kind: SyntaxKind.VariableSlotSignatureDeclaration 483 | type: TypeNode 484 | } 485 | 486 | export interface MethodSlotSignatureDeclaration extends ObjectSlotSignature, ParamsAndReturnType { 487 | kind: SyntaxKind.MethodSlotSignatureDeclaration 488 | params: NodeArray 489 | type: TypeNode 490 | } 491 | 492 | export enum SymbolFlag { 493 | None = 0, 494 | Variable = 1 << 0, 495 | Parameter = 1 << 1, 496 | Function = 1 << 2, 497 | VariableSlot = 1 << 3, 498 | MethodSlot = 1 << 4, 499 | AnomymousObject = 1 << 5, 500 | TypeDef = 1 << 6, 501 | 502 | Builtin = 1 << 28, 503 | VariableLike = Variable | Parameter | VariableSlot, 504 | FunctionLike = Function | MethodSlot, 505 | Value = Variable | Parameter | Function | VariableSlot | MethodSlot | AnomymousObject, 506 | } 507 | 508 | 509 | export enum TypeKind { 510 | Unknown, 511 | Never, 512 | Null, 513 | Integer, 514 | Boolean, 515 | String, 516 | Object, 517 | Function, 518 | Union 519 | } 520 | 521 | export interface Type { 522 | id: number 523 | kind: TypeKind 524 | 525 | symbol?: Symbol 526 | 527 | _debugKind?: string 528 | } 529 | 530 | export interface Symbol { 531 | id: number; 532 | members?: SymbolTable; 533 | name?: string; 534 | flags: SymbolFlag; 535 | declaration?: Declaration 536 | parent?: Symbol; 537 | _debugFlags?: string 538 | type?: Type 539 | } 540 | 541 | export interface SymbolTable extends Map { 542 | 543 | } 544 | 545 | export type HasLocalVariables = 546 | | SequenceOfStatements 547 | | SourceFile 548 | | MethodSlot 549 | | FunctionStatement 550 | | FunctionExpression 551 | 552 | export type HasMembers = 553 | | ObjectsExpression 554 | | TypeDefDeclaration 555 | 556 | export type PrimaryExpression = 557 | | IntegerLiteralExpression 558 | | StringLiteralExpression 559 | | VariableReferenceExpression 560 | | VariableAssignmentExpression 561 | | PrintingExpression 562 | | ArraysExpression 563 | | FunctionExpression 564 | | NullExpression 565 | | ObjectsExpression 566 | | IfExpression 567 | | WhileExpression 568 | | ThisExpression 569 | | BreakExpression 570 | | ContinueExpression 571 | | ParenExpression; 572 | 573 | export type AccessOrAssignmentExpressionOrHigher = 574 | | PrimaryExpression 575 | | SlotAssignmentExpression 576 | | MethodCallExpression 577 | | SlotLookupExpression 578 | | SetShorthand 579 | | GetShorthand; 580 | 581 | export type AllStatement = 582 | | VariableStatement 583 | | SequenceOfStatements 584 | | ExpressionStatement 585 | | FunctionStatement; 586 | 587 | export type AllDeclaration = 588 | | VariableStatement 589 | | FunctionStatement 590 | | VariableSlot 591 | | MethodSlot 592 | | ParameterDeclaration 593 | | ObjectsExpression 594 | | TypeDefDeclaration 595 | | VariableSlotSignatureDeclaration 596 | | MethodSlotSignatureDeclaration 597 | 598 | export type BinaryShorthandTokenSyntaxKind = 599 | | SyntaxKind.AddToken 600 | | SyntaxKind.SubToken 601 | | SyntaxKind.MulToken 602 | | SyntaxKind.DivToken 603 | | SyntaxKind.ModToken 604 | | SyntaxKind.LessThanToken 605 | | SyntaxKind.GreaterThanToken 606 | | SyntaxKind.LessEqualsThanToken 607 | | SyntaxKind.GreaterEqualsThanToken 608 | | SyntaxKind.EqualsEqualsToken; 609 | 610 | export type BinaryShorthandToken = Token; 611 | 612 | 613 | export interface UnknownType extends Type { 614 | kind: TypeKind.Unknown 615 | } 616 | 617 | export interface NeverType extends Type { 618 | kind: TypeKind.Never 619 | } 620 | 621 | export interface NullType extends Type { 622 | kind: TypeKind.Null 623 | } 624 | 625 | export interface IntegerType extends Type { 626 | kind: TypeKind.Integer 627 | } 628 | 629 | export interface BooleanType extends Type { 630 | kind: TypeKind.Boolean 631 | } 632 | 633 | export interface StringType extends Type { 634 | kind: TypeKind.String 635 | } 636 | 637 | export interface ObjectType extends Type { 638 | kind: TypeKind.Object 639 | properties: Map 640 | } 641 | 642 | export interface FunctionType extends Type { 643 | kind: TypeKind.Function 644 | thisType?: Type 645 | paramTypes: Type[] 646 | returnType: Type 647 | } 648 | 649 | export interface UnionType extends Type { 650 | kind: TypeKind.Union 651 | types: Type[] 652 | } 653 | -------------------------------------------------------------------------------- /src/interpreter/interpreter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BreakExpression, 3 | ContinueExpression, 4 | ArraysExpression, 5 | Expression, 6 | FunctionStatement, 7 | FunctionExpression, 8 | IfExpression, 9 | ExpressionStatement, 10 | MethodSlot, 11 | PrintingExpression, 12 | SyntaxKind, 13 | ThisExpression, 14 | VariableSlot, 15 | BinaryShorthand, 16 | FunctionCallExpression, 17 | GetShorthand, 18 | VariableStatement, 19 | MethodCallExpression, 20 | NullExpression, 21 | ObjectsExpression, 22 | ObjectSlot, 23 | ParenExpression, 24 | SetShorthand, 25 | SlotAssignmentExpression, 26 | SlotLookupExpression, 27 | VariableAssignmentExpression, 28 | VariableReferenceExpression, 29 | WhileExpression, 30 | IntegerLiteralExpression, 31 | SequenceOfStatements, 32 | SourceFile, 33 | Statement, 34 | StringLiteralExpression 35 | } from '../types'; 36 | import { 37 | assertDef, 38 | frontAndTail, 39 | last, 40 | shorthandTokenToOperator 41 | } from '../utils'; 42 | import { setupBuiltin } from './builtins'; 43 | import { Environment, CallFrame, Context } from './types'; 44 | import { assertArgumentsLength } from './utils'; 45 | import { 46 | BaseValue, 47 | NullValue, 48 | RuntimeFunction, 49 | ObjectValue, 50 | BooleanValue, 51 | ArrayValue, 52 | IntegerValue, 53 | StringValue 54 | } from './values'; 55 | 56 | setupBuiltin(); 57 | 58 | export function createInterpreter(file: SourceFile, context: Context) { 59 | const globalEnv = new Environment(); 60 | const globalCallframe: CallFrame = { 61 | thisValue: undefined, 62 | name: '', 63 | envStack: [globalEnv], 64 | parent: undefined, 65 | insideLoop: false 66 | }; 67 | const callFrames = [globalCallframe]; 68 | 69 | const exceptionObjects = { 70 | Break: {}, 71 | Continue: {} 72 | } as const; 73 | 74 | return { 75 | evaluate 76 | }; 77 | 78 | function currentEnv() { 79 | return last(currentCallFrame().envStack); 80 | } 81 | 82 | function currentCallFrame() { 83 | return last(callFrames); 84 | } 85 | 86 | function pushCallFrame(callFrame: CallFrame) { 87 | callFrames.push(callFrame); 88 | } 89 | 90 | function popCallFrame(targetCallFrame: CallFrame | undefined) { 91 | const result = callFrames.pop(); 92 | if (!callFrames.length) { 93 | throw new Error('Callframe not balanced'); 94 | } 95 | if (targetCallFrame && result !== targetCallFrame) { 96 | throw new Error('Invalid callframe'); 97 | } 98 | return result; 99 | } 100 | 101 | function pushEnv(env: Environment) { 102 | currentCallFrame().envStack.push(env); 103 | } 104 | 105 | function popEnv(targetEnv: Environment | undefined) { 106 | const envStack = currentCallFrame().envStack; 107 | if (!envStack.length) { 108 | throw new Error('EnvStack not balanced'); 109 | } 110 | 111 | const result = envStack.pop(); 112 | if (targetEnv && result !== targetEnv) { 113 | throw new Error('Invalid environment'); 114 | } 115 | 116 | return result; 117 | } 118 | 119 | function runInEnv(cb: () => R) { 120 | const parent = currentEnv(); 121 | const env = new Environment(parent); 122 | pushEnv(env); 123 | 124 | let result: R; 125 | try { 126 | result = cb(); 127 | } finally { 128 | popEnv(env); 129 | } 130 | 131 | return result; 132 | } 133 | 134 | function runInLoop(cb: () => R) { 135 | const frame = currentCallFrame(); 136 | const savedInsideLoop = frame.insideLoop; 137 | frame.insideLoop = true; 138 | 139 | let result: R; 140 | try { 141 | result = cb(); 142 | } finally { 143 | frame.insideLoop = savedInsideLoop; 144 | } 145 | 146 | return result; 147 | } 148 | 149 | function runInFuncEnv( 150 | name: string, 151 | thisValue: BaseValue | undefined, 152 | params: string[], 153 | args: BaseValue[], 154 | parentEnv: Environment | undefined, 155 | cb: () => R 156 | ) { 157 | const env = new Environment(parentEnv); 158 | 159 | for (let i = 0; i < params.length; i++) { 160 | const param = params[i]; 161 | const arg = args[i]; 162 | env.addBinding(param, arg); 163 | } 164 | 165 | const callFrame: CallFrame = { 166 | thisValue, 167 | envStack: [], 168 | name, 169 | parent: currentCallFrame(), 170 | insideLoop: false 171 | }; 172 | 173 | pushCallFrame(callFrame); 174 | pushEnv(env); 175 | 176 | let result: R; 177 | try { 178 | result = cb(); 179 | } finally { 180 | popEnv(env); 181 | popCallFrame(callFrame); 182 | } 183 | 184 | return result; 185 | } 186 | 187 | function callFunction( 188 | thisValue: BaseValue | undefined, 189 | callable: BaseValue, 190 | args: BaseValue[] 191 | ) { 192 | if (!callable.isFunction()) { 193 | throw new TypeError('Value is not callable: ' + callable.type); 194 | } 195 | 196 | assertArgumentsLength(callable.params.length, args.length); 197 | 198 | if (callable.isBuiltin()) { 199 | return runInFuncEnv( 200 | callable.name, 201 | thisValue, 202 | callable.params, 203 | args, 204 | undefined, 205 | () => { 206 | return callable.fn(thisValue, args); 207 | } 208 | ); 209 | } 210 | 211 | if (!callable.isRuntime()) { 212 | throw new Error('Unknown callable kind: ' + callable.type); 213 | } 214 | 215 | return runInFuncEnv( 216 | callable.name, 217 | thisValue, 218 | callable.params, 219 | args, 220 | callable.closureEnv, 221 | () => { 222 | return callable.body(); 223 | } 224 | ); 225 | } 226 | 227 | function evaluate() { 228 | evaluateSourceFile(file); 229 | } 230 | 231 | function evaluateSourceFile(sourceFile: SourceFile) { 232 | evaluateSequenceOfStatements(sourceFile.body); 233 | } 234 | 235 | function evaluateSequenceOfStatements(seqs: SequenceOfStatements) { 236 | if (!seqs.isExpression) { 237 | seqs.statements.forEach(evaluateStatement); 238 | return NullValue.Instance; 239 | } 240 | 241 | const [front, tail] = frontAndTail(seqs.statements); 242 | 243 | front.forEach(evaluateStatement); 244 | 245 | if (tail.kind !== SyntaxKind.ExpressionStatement) { 246 | throw new Error('Invalid statement: ' + tail.__debugKind); 247 | } 248 | return evaluateExpressionStatement(tail as ExpressionStatement); 249 | } 250 | 251 | function evaluateStatement(stmt: Statement) { 252 | switch (stmt.kind) { 253 | case SyntaxKind.SequenceOfStatements: 254 | return evaluateSequenceOfStatements(stmt as SequenceOfStatements); 255 | case SyntaxKind.ExpressionStatement: 256 | return evaluateExpressionStatement(stmt as ExpressionStatement); 257 | case SyntaxKind.VariableStatement: 258 | return evaluateVariableStatement(stmt as VariableStatement); 259 | case SyntaxKind.FunctionStatement: 260 | return evaluateFunctionStatement(stmt as FunctionStatement); 261 | case SyntaxKind.TypeDefDeclaration: 262 | return undefined; 263 | default: 264 | throw new Error('Invalid statement: ' + stmt.__debugKind); 265 | } 266 | } 267 | 268 | function evaluateBreakOrContinueExpression( 269 | stmt: BreakExpression | ContinueExpression 270 | ): never { 271 | const callFrame = currentCallFrame(); 272 | if (!callFrame.insideLoop) { 273 | throw new Error('Break or continue cannot used outside loop'); 274 | } 275 | 276 | if (stmt.kind === SyntaxKind.BreakExpression) { 277 | throw exceptionObjects.Break; 278 | } else { 279 | throw exceptionObjects.Continue; 280 | } 281 | } 282 | 283 | function evaluateExpression(expr: Expression): BaseValue { 284 | switch (expr.kind) { 285 | case SyntaxKind.IntegerLiteralExpression: 286 | return evaluateIntegerLiteralExpression( 287 | expr as IntegerLiteralExpression 288 | ); 289 | case SyntaxKind.StringLiteralExpression: 290 | return evaluateStringLiteralExpression(expr as StringLiteralExpression); 291 | case SyntaxKind.PrintingExpression: 292 | return evaluatePrintingExpression(expr as PrintingExpression); 293 | case SyntaxKind.NullExpression: 294 | return evaluateNullExpression(expr as NullExpression); 295 | case SyntaxKind.ArraysExpression: 296 | return evaluateArraysExpression(expr as ArraysExpression); 297 | case SyntaxKind.IfExpression: 298 | return evaluateIfExpression(expr as IfExpression); 299 | case SyntaxKind.ParenExpression: 300 | return evaluateParenExpression(expr as ParenExpression); 301 | case SyntaxKind.WhileExpression: 302 | return evaluateWhileExpression(expr as WhileExpression); 303 | case SyntaxKind.VariableReferenceExpression: 304 | return evaluateVariableReferenceExpression( 305 | expr as VariableReferenceExpression 306 | ); 307 | case SyntaxKind.VariableAssignmentExpression: 308 | return evaluateVariableAssignmentExpression( 309 | expr as VariableAssignmentExpression 310 | ); 311 | case SyntaxKind.ThisExpression: 312 | return evaluateThisExpression(expr as ThisExpression); 313 | case SyntaxKind.SlotLookupExpression: 314 | return evaluateSlotLookupExpression(expr as SlotLookupExpression); 315 | case SyntaxKind.SlotAssignmentExpression: 316 | return evaluatSlotAssignmentExpression( 317 | expr as SlotAssignmentExpression 318 | ); 319 | case SyntaxKind.ObjectsExpression: 320 | return evaluateObjectsExpression(expr as ObjectsExpression); 321 | case SyntaxKind.FunctionCallExpression: 322 | return evaluateFunctionCallExpression(expr as FunctionCallExpression); 323 | case SyntaxKind.BinaryShorthand: 324 | return evaluateBinaryShorthand(expr as BinaryShorthand); 325 | case SyntaxKind.MethodCallExpression: 326 | return evaluateMethodCallExpression(expr as MethodCallExpression); 327 | case SyntaxKind.GetShorthand: 328 | return evaluateGetShorthand(expr as GetShorthand); 329 | case SyntaxKind.SetShorthand: 330 | return evaluateSetShorthand(expr as SetShorthand); 331 | case SyntaxKind.FunctionExpression: 332 | return evaluateFunctionExpression(expr as FunctionExpression); 333 | case SyntaxKind.BreakExpression: 334 | case SyntaxKind.ContinueExpression: 335 | return evaluateBreakOrContinueExpression( 336 | expr as BreakExpression | ContinueExpression 337 | ); 338 | default: 339 | throw new Error('Invalid expression: ' + expr.__debugKind); 340 | } 341 | } 342 | 343 | function evaluateFunctionExpression(expr: FunctionExpression) { 344 | const env = currentEnv(); 345 | const params = expr.params.map(x => x.name.text); 346 | const func = new RuntimeFunction( 347 | expr.name.text, 348 | params, 349 | () => evaluateExpressionStatementOrSequenceOfStatements(expr.body), 350 | env 351 | ); 352 | return func; 353 | } 354 | 355 | function evaluateSetShorthand(expr: SetShorthand) { 356 | const left = evaluateExpression(expr.expression); 357 | if (!left.isEnvValue()) { 358 | throw new Error('Left operand must be an environment value'); 359 | } 360 | 361 | const setFunc = left.env.getBinding('set'); 362 | assertDef(setFunc, 'Cannot find definition for shorthand: set'); 363 | 364 | const args = expr.args.map(evaluateExpression); 365 | const value = evaluateExpression(expr.value); 366 | 367 | return callFunction(left, setFunc, args.concat(value)); 368 | } 369 | 370 | function evaluateGetShorthand(expr: GetShorthand) { 371 | const left = evaluateExpression(expr.expression); 372 | if (!left.isEnvValue()) { 373 | throw new Error('Left operand must be an environment value'); 374 | } 375 | 376 | const getFunc = left.env.getBinding('get'); 377 | assertDef(getFunc, 'Cannot find definition for shorthand: get'); 378 | 379 | const args = expr.args.map(evaluateExpression); 380 | return callFunction(left, getFunc, args); 381 | } 382 | 383 | function evaluateThisExpression(expr: ThisExpression) { 384 | const callFrame = currentCallFrame(); 385 | return callFrame.thisValue ?? NullValue.Instance; 386 | } 387 | 388 | function evaluateBinaryShorthand(expr: BinaryShorthand) { 389 | const left = evaluateExpression(expr.left); 390 | if (!left.isEnvValue()) { 391 | throw new TypeError('Left operand must be an environment value'); 392 | } 393 | 394 | const operator = shorthandTokenToOperator(expr.operator.kind); 395 | const callable = left.env.getBinding(operator); 396 | assertDef(callable, 'Cannot find definition for shorthand: ' + operator); 397 | 398 | const right = evaluateExpression(expr.right); 399 | return callFunction(left, callable, [right]); 400 | } 401 | 402 | function evaluateFunctionCallExpression(expr: FunctionCallExpression) { 403 | const value = evaluateExpression(expr.expression); 404 | const args = expr.args.map(evaluateExpression); 405 | return callFunction(undefined, value, args); 406 | } 407 | 408 | function evaluateMethodCallExpression(expr: MethodCallExpression) { 409 | const left = evaluateExpression(expr.expression); 410 | if (!left.isEnvValue()) { 411 | throw new TypeError('Left operand must be an environment value'); 412 | } 413 | const callable = left.env.getBinding(expr.name.text); 414 | assertDef(callable, 'Cannot find definition for method: ' + expr.name.text); 415 | 416 | const args = expr.args.map(evaluateExpression); 417 | return callFunction(left, callable, args); 418 | } 419 | 420 | function evaluateObjectsExpression(expr: ObjectsExpression) { 421 | const inheritValue = expr.extendsClause 422 | ? evaluateExpression(expr.extendsClause) 423 | : undefined; 424 | const value = new ObjectValue(inheritValue); 425 | expr.slots.forEach(slot => evaluateObjectSlot(value, slot)); 426 | return value; 427 | } 428 | 429 | function evaluateObjectSlot(obj: ObjectValue, slot: ObjectSlot) { 430 | if (slot.kind === SyntaxKind.VariableSlot) { 431 | const variableSlot = slot as VariableSlot; 432 | const initializer = evaluateExpression(variableSlot.initializer); 433 | obj.env.addBinding(variableSlot.name.text, initializer); 434 | } else if (slot.kind === SyntaxKind.MethodSlot) { 435 | const env = currentEnv(); 436 | const methodSlot = slot as MethodSlot; 437 | const params = methodSlot.params.map(x => x.name.text); 438 | const callable = new RuntimeFunction( 439 | methodSlot.name.text, 440 | params, 441 | () => 442 | evaluateExpressionStatementOrSequenceOfStatements(methodSlot.body), 443 | env 444 | ); 445 | obj.env.addBinding(methodSlot.name.text, callable); 446 | } else { 447 | throw new Error('Invalid slot kind: ' + slot.__debugKind); 448 | } 449 | } 450 | 451 | function evaluateVariableAssignmentExpression( 452 | expr: VariableAssignmentExpression 453 | ) { 454 | const env = currentEnv(); 455 | const value = evaluateExpression(expr.value); 456 | 457 | env.setBinding(expr.expression.name.text, value); 458 | return value; 459 | } 460 | 461 | function evaluatSlotAssignmentExpression(expr: SlotAssignmentExpression) { 462 | const left = evaluateExpression(expr.expression); 463 | if (!left.isEnvValue()) { 464 | throw new Error('Invalid left value type: ' + left.type); 465 | } 466 | 467 | const right = evaluateExpression(expr.value); 468 | left.env.setBinding(expr.name.text, right); 469 | return right; 470 | } 471 | 472 | function evaluateSlotLookupExpression(expr: SlotLookupExpression) { 473 | const left = evaluateExpression(expr.expression); 474 | if (!left.isEnvValue()) { 475 | throw new Error('Invalid left value type: ' + left.type); 476 | } 477 | 478 | const result = left.env.getBinding(expr.name.text); 479 | assertDef(result, 'Cannot find slot: ' + expr.name.text); 480 | return result; 481 | } 482 | 483 | function evaluateVariableReferenceExpression( 484 | expr: VariableReferenceExpression 485 | ) { 486 | const env = currentEnv(); 487 | const value = env.getBinding(expr.name.text); 488 | 489 | assertDef(value, 'Cannot find reference: ' + expr.name.text); 490 | return value; 491 | } 492 | 493 | function toBooleanValue(value: BaseValue) { 494 | if (value.isNull()) { 495 | return new BooleanValue(false); 496 | } 497 | if (value.isBoolean()) { 498 | return value; 499 | } 500 | if (value.isInteger()) { 501 | return value.value === 0 ? BooleanValue.False : BooleanValue.True; 502 | } 503 | if (value.isString()) { 504 | return value.value ? BooleanValue.True : BooleanValue.False; 505 | } 506 | throw new TypeError('Invalid cast: ' + value.type); 507 | } 508 | 509 | function evaluateWhileExpression(expr: WhileExpression) { 510 | let i = 0; 511 | while (toBooleanValue(evaluateExpression(expr.condition)).value) { 512 | if (i++ > 10000) { 513 | throw new Error('Infinite loop'); 514 | } 515 | 516 | try { 517 | runInEnv(() => { 518 | return runInLoop(() => { 519 | return evaluateExpressionStatementOrSequenceOfStatements(expr.body); 520 | }); 521 | }); 522 | } catch (e: unknown) { 523 | if (e === exceptionObjects.Break) { 524 | break; 525 | } 526 | if (e === exceptionObjects.Continue) { 527 | continue; 528 | } 529 | 530 | throw e; 531 | } 532 | } 533 | 534 | return NullValue.Instance; 535 | } 536 | 537 | function evaluateParenExpression(expr: ParenExpression) { 538 | return evaluateExpression(expr.expression); 539 | } 540 | 541 | function evaluateExpressionStatementOrSequenceOfStatements( 542 | stmt: ExpressionStatement | SequenceOfStatements 543 | ): BaseValue { 544 | if (stmt.kind === SyntaxKind.SequenceOfStatements) { 545 | return evaluateSequenceOfStatements(stmt); 546 | } else { 547 | return evaluateExpressionStatement(stmt); 548 | } 549 | } 550 | 551 | function evaluateIfExpression(expr: IfExpression) { 552 | const condition = evaluateExpression(expr.condition); 553 | const thenStatement = expr.thenStatement; 554 | const elseStatement = expr.elseStatement; 555 | 556 | if (toBooleanValue(condition).value) { 557 | return runInEnv(() => { 558 | return evaluateExpressionStatementOrSequenceOfStatements(thenStatement); 559 | }); 560 | } else if (elseStatement) { 561 | return runInEnv(() => { 562 | return evaluateExpressionStatementOrSequenceOfStatements(elseStatement); 563 | }); 564 | } else { 565 | return NullValue.Instance; 566 | } 567 | } 568 | 569 | function evaluateArraysExpression(expr: ArraysExpression) { 570 | const length = evaluateExpression(expr.length); 571 | const defaultValue = expr.defaultValue 572 | ? evaluateExpression(expr.defaultValue) 573 | : undefined; 574 | if (!length.isInteger()) { 575 | throw new TypeError('Invalid length arguments, expected integer'); 576 | } 577 | 578 | return new ArrayValue(length.value, defaultValue); 579 | } 580 | 581 | function evaluateNullExpression(expr: NullExpression) { 582 | return NullValue.Instance; 583 | } 584 | 585 | function evaluatePrintingExpression(expr: PrintingExpression): NullValue { 586 | expr.args 587 | .map(evaluateExpression) 588 | .map(x => x.print()) 589 | .forEach(text => { 590 | context.stdout(text); 591 | }); 592 | return NullValue.Instance; 593 | } 594 | 595 | function evaluateIntegerLiteralExpression( 596 | expr: IntegerLiteralExpression 597 | ): IntegerValue { 598 | const isNegative = !!expr.subToken; 599 | const value = Number(expr.value.value); 600 | return new IntegerValue(value * (isNegative ? -1 : 1)); 601 | } 602 | 603 | function evaluateStringLiteralExpression( 604 | expr: StringLiteralExpression 605 | ): StringValue { 606 | return new StringValue(expr.value.value); 607 | } 608 | 609 | function evaluateVariableStatement(stmt: VariableStatement) { 610 | const env = currentEnv(); 611 | const value = evaluateExpression(stmt.initializer); 612 | env.addBinding(stmt.name.text, value); 613 | } 614 | 615 | function evaluateFunctionStatement(stmt: FunctionStatement) { 616 | const env = currentEnv(); 617 | const params = stmt.params.map(x => x.name.text); 618 | const func = new RuntimeFunction( 619 | stmt.name.text, 620 | params, 621 | () => evaluateExpressionStatementOrSequenceOfStatements(stmt.body), 622 | env 623 | ); 624 | env.addBinding(stmt.name.text, func); 625 | } 626 | 627 | function evaluateExpressionStatement(stmt: ExpressionStatement) { 628 | return evaluateExpression(stmt.expression); 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /tests/__snapshots__/binder.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Binder should-work-with-arrays - locals 1`] = `Array []`; 4 | 5 | exports[`Binder should-work-with-arrays - members 1`] = `Array []`; 6 | 7 | exports[`Binder should-work-with-binary-shorthand - locals 1`] = ` 8 | Array [ 9 | Object { 10 | "kind": "SequenceOfStatements", 11 | "pos": 0, 12 | "symbols": Array [ 13 | Object { 14 | "declarationPos": 0, 15 | "flags": "Variable", 16 | "id": 7, 17 | "name": "x", 18 | }, 19 | Object { 20 | "declarationPos": 10, 21 | "flags": "Variable", 22 | "id": 8, 23 | "name": "y", 24 | }, 25 | ], 26 | }, 27 | ] 28 | `; 29 | 30 | exports[`Binder should-work-with-binary-shorthand - members 1`] = `Array []`; 31 | 32 | exports[`Binder should-work-with-break-and-continue - locals 1`] = ` 33 | Array [ 34 | Object { 35 | "kind": "SequenceOfStatements", 36 | "pos": 0, 37 | "symbols": Array [ 38 | Object { 39 | "declarationPos": 0, 40 | "flags": "Variable", 41 | "id": 4, 42 | "name": "x", 43 | }, 44 | ], 45 | }, 46 | ] 47 | `; 48 | 49 | exports[`Binder should-work-with-break-and-continue - members 1`] = `Array []`; 50 | 51 | exports[`Binder should-work-with-function - locals 1`] = ` 52 | Array [ 53 | Object { 54 | "kind": "SequenceOfStatements", 55 | "pos": 0, 56 | "symbols": Array [ 57 | Object { 58 | "declarationPos": 0, 59 | "flags": "Function", 60 | "id": 22, 61 | "name": "f", 62 | }, 63 | ], 64 | }, 65 | Object { 66 | "kind": "FunctionStatement", 67 | "pos": 0, 68 | "symbols": Array [ 69 | Object { 70 | "declarationPos": 7, 71 | "flags": "Parameter", 72 | "id": 26, 73 | "name": "x", 74 | }, 75 | Object { 76 | "declarationPos": 10, 77 | "flags": "Parameter", 78 | "id": 27, 79 | "name": "y", 80 | }, 81 | Object { 82 | "declarationPos": 13, 83 | "flags": "Parameter", 84 | "id": 28, 85 | "name": "z", 86 | }, 87 | ], 88 | }, 89 | ] 90 | `; 91 | 92 | exports[`Binder should-work-with-function - members 1`] = `Array []`; 93 | 94 | exports[`Binder should-work-with-function-call - locals 1`] = ` 95 | Array [ 96 | Object { 97 | "kind": "SequenceOfStatements", 98 | "pos": 0, 99 | "symbols": Array [ 100 | Object { 101 | "declarationPos": 0, 102 | "flags": "Function", 103 | "id": 10, 104 | "name": "f", 105 | }, 106 | ], 107 | }, 108 | Object { 109 | "kind": "FunctionStatement", 110 | "pos": 0, 111 | "symbols": Array [ 112 | Object { 113 | "declarationPos": 7, 114 | "flags": "Parameter", 115 | "id": 12, 116 | "name": "v", 117 | }, 118 | ], 119 | }, 120 | ] 121 | `; 122 | 123 | exports[`Binder should-work-with-function-call - members 1`] = `Array []`; 124 | 125 | exports[`Binder should-work-with-function-expression - locals 1`] = ` 126 | Array [ 127 | Object { 128 | "kind": "SequenceOfStatements", 129 | "pos": 0, 130 | "symbols": Array [ 131 | Object { 132 | "declarationPos": 0, 133 | "flags": "Variable", 134 | "id": 16, 135 | "name": "fn", 136 | }, 137 | ], 138 | }, 139 | Object { 140 | "kind": "FunctionExpression", 141 | "pos": 9, 142 | "symbols": Array [ 143 | Object { 144 | "declarationPos": 18, 145 | "flags": "Parameter", 146 | "id": 20, 147 | "name": "x", 148 | }, 149 | ], 150 | }, 151 | ] 152 | `; 153 | 154 | exports[`Binder should-work-with-function-expression - members 1`] = `Array []`; 155 | 156 | exports[`Binder should-work-with-get-shorthand - locals 1`] = ` 157 | Array [ 158 | Object { 159 | "kind": "SequenceOfStatements", 160 | "pos": 0, 161 | "symbols": Array [ 162 | Object { 163 | "declarationPos": 0, 164 | "flags": "Variable", 165 | "id": 4, 166 | "name": "a", 167 | }, 168 | ], 169 | }, 170 | ] 171 | `; 172 | 173 | exports[`Binder should-work-with-get-shorthand - members 1`] = `Array []`; 174 | 175 | exports[`Binder should-work-with-global-variable - locals 1`] = ` 176 | Array [ 177 | Object { 178 | "kind": "SequenceOfStatements", 179 | "pos": 0, 180 | "symbols": Array [ 181 | Object { 182 | "declarationPos": 0, 183 | "flags": "Variable", 184 | "id": 4, 185 | "name": "x", 186 | }, 187 | ], 188 | }, 189 | ] 190 | `; 191 | 192 | exports[`Binder should-work-with-global-variable - members 1`] = `Array []`; 193 | 194 | exports[`Binder should-work-with-if-expression - locals 1`] = ` 195 | Array [ 196 | Object { 197 | "kind": "SequenceOfStatements", 198 | "pos": 0, 199 | "symbols": Array [ 200 | Object { 201 | "declarationPos": 0, 202 | "flags": "Variable", 203 | "id": 4, 204 | "name": "x", 205 | }, 206 | ], 207 | }, 208 | ] 209 | `; 210 | 211 | exports[`Binder should-work-with-if-expression - members 1`] = `Array []`; 212 | 213 | exports[`Binder should-work-with-if-expression-with-if-only - locals 1`] = ` 214 | Array [ 215 | Object { 216 | "kind": "SequenceOfStatements", 217 | "pos": 0, 218 | "symbols": Array [ 219 | Object { 220 | "declarationPos": 0, 221 | "flags": "Variable", 222 | "id": 4, 223 | "name": "x", 224 | }, 225 | ], 226 | }, 227 | ] 228 | `; 229 | 230 | exports[`Binder should-work-with-if-expression-with-if-only - members 1`] = `Array []`; 231 | 232 | exports[`Binder should-work-with-integer - locals 1`] = `Array []`; 233 | 234 | exports[`Binder should-work-with-integer - members 1`] = `Array []`; 235 | 236 | exports[`Binder should-work-with-local-variable - locals 1`] = ` 237 | Array [ 238 | Object { 239 | "kind": "SequenceOfStatements", 240 | "pos": 0, 241 | "symbols": Array [ 242 | Object { 243 | "declarationPos": 0, 244 | "flags": "Function", 245 | "id": 16, 246 | "name": "foo", 247 | }, 248 | ], 249 | }, 250 | Object { 251 | "kind": "SequenceOfStatements", 252 | "pos": 16, 253 | "symbols": Array [ 254 | Object { 255 | "declarationPos": 16, 256 | "flags": "Variable", 257 | "id": 20, 258 | "name": "x", 259 | }, 260 | ], 261 | }, 262 | ] 263 | `; 264 | 265 | exports[`Binder should-work-with-local-variable - members 1`] = `Array []`; 266 | 267 | exports[`Binder should-work-with-method-call - locals 1`] = ` 268 | Array [ 269 | Object { 270 | "kind": "SequenceOfStatements", 271 | "pos": 0, 272 | "symbols": Array [ 273 | Object { 274 | "declarationPos": 0, 275 | "flags": "Variable", 276 | "id": 46, 277 | "name": "o", 278 | }, 279 | ], 280 | }, 281 | Object { 282 | "kind": "MethodSlot", 283 | "pos": 20, 284 | "symbols": Array [ 285 | Object { 286 | "declarationPos": 29, 287 | "flags": "Parameter", 288 | "id": 60, 289 | "name": "x", 290 | }, 291 | ], 292 | }, 293 | ] 294 | `; 295 | 296 | exports[`Binder should-work-with-method-call - members 1`] = ` 297 | Array [ 298 | Object { 299 | "kind": "ObjectsExpression", 300 | "name": undefined, 301 | "pos": 8, 302 | "symbols": Array [ 303 | Object { 304 | "declarationPos": 20, 305 | "flags": "MethodSlot", 306 | "id": 55, 307 | "name": "f", 308 | }, 309 | ], 310 | }, 311 | ] 312 | `; 313 | 314 | exports[`Binder should-work-with-nested-function - locals 1`] = ` 315 | Array [ 316 | Object { 317 | "kind": "SequenceOfStatements", 318 | "pos": 0, 319 | "symbols": Array [ 320 | Object { 321 | "declarationPos": 0, 322 | "flags": "Variable", 323 | "id": 61, 324 | "name": "m", 325 | }, 326 | Object { 327 | "declarationPos": 10, 328 | "flags": "Function", 329 | "id": 62, 330 | "name": "foo", 331 | }, 332 | ], 333 | }, 334 | Object { 335 | "kind": "FunctionStatement", 336 | "pos": 10, 337 | "symbols": Array [ 338 | Object { 339 | "declarationPos": 19, 340 | "flags": "Parameter", 341 | "id": 72, 342 | "name": "x", 343 | }, 344 | ], 345 | }, 346 | Object { 347 | "kind": "SequenceOfStatements", 348 | "pos": 27, 349 | "symbols": Array [ 350 | Object { 351 | "declarationPos": 27, 352 | "flags": "Variable", 353 | "id": 77, 354 | "name": "y", 355 | }, 356 | Object { 357 | "declarationPos": 41, 358 | "flags": "Function", 359 | "id": 78, 360 | "name": "bar", 361 | }, 362 | ], 363 | }, 364 | Object { 365 | "kind": "FunctionStatement", 366 | "pos": 41, 367 | "symbols": Array [ 368 | Object { 369 | "declarationPos": 50, 370 | "flags": "Parameter", 371 | "id": 80, 372 | "name": "z", 373 | }, 374 | ], 375 | }, 376 | ] 377 | `; 378 | 379 | exports[`Binder should-work-with-nested-function - members 1`] = `Array []`; 380 | 381 | exports[`Binder should-work-with-null - locals 1`] = `Array []`; 382 | 383 | exports[`Binder should-work-with-null - members 1`] = `Array []`; 384 | 385 | exports[`Binder should-work-with-object-without-extends - locals 1`] = `Array []`; 386 | 387 | exports[`Binder should-work-with-object-without-extends - members 1`] = ` 388 | Array [ 389 | Object { 390 | "kind": "ObjectsExpression", 391 | "name": undefined, 392 | "pos": 0, 393 | "symbols": Array [ 394 | Object { 395 | "declarationPos": 12, 396 | "flags": "VariableSlot", 397 | "id": 37, 398 | "name": "x", 399 | }, 400 | Object { 401 | "declarationPos": 27, 402 | "flags": "MethodSlot", 403 | "id": 38, 404 | "name": "f", 405 | }, 406 | ], 407 | }, 408 | ] 409 | `; 410 | 411 | exports[`Binder should-work-with-objects - locals 1`] = ` 412 | Array [ 413 | Object { 414 | "kind": "SequenceOfStatements", 415 | "pos": 0, 416 | "symbols": Array [ 417 | Object { 418 | "declarationPos": 0, 419 | "flags": "Variable", 420 | "id": 52, 421 | "name": "p", 422 | }, 423 | ], 424 | }, 425 | ] 426 | `; 427 | 428 | exports[`Binder should-work-with-objects - members 1`] = ` 429 | Array [ 430 | Object { 431 | "kind": "ObjectsExpression", 432 | "name": undefined, 433 | "pos": 8, 434 | "symbols": Array [ 435 | Object { 436 | "declarationPos": 20, 437 | "flags": "VariableSlot", 438 | "id": 57, 439 | "name": "y", 440 | }, 441 | ], 442 | }, 443 | Object { 444 | "kind": "ObjectsExpression", 445 | "name": undefined, 446 | "pos": 32, 447 | "symbols": Array [ 448 | Object { 449 | "declarationPos": 47, 450 | "flags": "VariableSlot", 451 | "id": 65, 452 | "name": "x", 453 | }, 454 | Object { 455 | "declarationPos": 62, 456 | "flags": "MethodSlot", 457 | "id": 66, 458 | "name": "f", 459 | }, 460 | ], 461 | }, 462 | ] 463 | `; 464 | 465 | exports[`Binder should-work-with-objects-and-slots - locals 1`] = ` 466 | Array [ 467 | Object { 468 | "kind": "SequenceOfStatements", 469 | "pos": 0, 470 | "symbols": Array [ 471 | Object { 472 | "declarationPos": 0, 473 | "flags": "Variable", 474 | "id": 124, 475 | "name": "p", 476 | }, 477 | ], 478 | }, 479 | Object { 480 | "kind": "MethodSlot", 481 | "pos": 62, 482 | "symbols": Array [ 483 | Object { 484 | "declarationPos": 71, 485 | "flags": "Parameter", 486 | "id": 162, 487 | "name": "x", 488 | }, 489 | Object { 490 | "declarationPos": 74, 491 | "flags": "Parameter", 492 | "id": 163, 493 | "name": "y", 494 | }, 495 | Object { 496 | "declarationPos": 77, 497 | "flags": "Parameter", 498 | "id": 164, 499 | "name": "z", 500 | }, 501 | ], 502 | }, 503 | ] 504 | `; 505 | 506 | exports[`Binder should-work-with-objects-and-slots - members 1`] = ` 507 | Array [ 508 | Object { 509 | "kind": "ObjectsExpression", 510 | "name": undefined, 511 | "pos": 8, 512 | "symbols": Array [ 513 | Object { 514 | "declarationPos": 20, 515 | "flags": "VariableSlot", 516 | "id": 129, 517 | "name": "y", 518 | }, 519 | ], 520 | }, 521 | Object { 522 | "kind": "ObjectsExpression", 523 | "name": undefined, 524 | "pos": 32, 525 | "symbols": Array [ 526 | Object { 527 | "declarationPos": 47, 528 | "flags": "VariableSlot", 529 | "id": 149, 530 | "name": "x", 531 | }, 532 | Object { 533 | "declarationPos": 62, 534 | "flags": "MethodSlot", 535 | "id": 150, 536 | "name": "f", 537 | }, 538 | ], 539 | }, 540 | ] 541 | `; 542 | 543 | exports[`Binder should-work-with-printf - locals 1`] = `Array []`; 544 | 545 | exports[`Binder should-work-with-printf - members 1`] = `Array []`; 546 | 547 | exports[`Binder should-work-with-set-shorthand - locals 1`] = ` 548 | Array [ 549 | Object { 550 | "kind": "SequenceOfStatements", 551 | "pos": 0, 552 | "symbols": Array [ 553 | Object { 554 | "declarationPos": 0, 555 | "flags": "Variable", 556 | "id": 4, 557 | "name": "a", 558 | }, 559 | ], 560 | }, 561 | ] 562 | `; 563 | 564 | exports[`Binder should-work-with-set-shorthand - members 1`] = `Array []`; 565 | 566 | exports[`Binder should-work-with-slot-assignment - locals 1`] = ` 567 | Array [ 568 | Object { 569 | "kind": "SequenceOfStatements", 570 | "pos": 0, 571 | "symbols": Array [ 572 | Object { 573 | "declarationPos": 0, 574 | "flags": "Variable", 575 | "id": 403, 576 | "name": "o", 577 | }, 578 | Object { 579 | "declarationPos": 35, 580 | "flags": "Variable", 581 | "id": 410, 582 | "name": "a", 583 | }, 584 | ], 585 | }, 586 | ] 587 | `; 588 | 589 | exports[`Binder should-work-with-slot-assignment - members 1`] = ` 590 | Array [ 591 | Object { 592 | "kind": "ObjectsExpression", 593 | "name": undefined, 594 | "pos": 8, 595 | "symbols": Array [ 596 | Object { 597 | "declarationPos": 20, 598 | "flags": "VariableSlot", 599 | "id": 408, 600 | "name": "field", 601 | }, 602 | ], 603 | }, 604 | Object { 605 | "kind": "ObjectsExpression", 606 | "name": undefined, 607 | "pos": 43, 608 | "symbols": Array [ 609 | Object { 610 | "declarationPos": 55, 611 | "flags": "VariableSlot", 612 | "id": 475, 613 | "name": "b", 614 | }, 615 | Object { 616 | "declarationPos": 79, 617 | "flags": "VariableSlot", 618 | "id": 499, 619 | "name": "c", 620 | }, 621 | Object { 622 | "declarationPos": 107, 623 | "flags": "VariableSlot", 624 | "id": 505, 625 | "name": "d", 626 | }, 627 | ], 628 | }, 629 | Object { 630 | "kind": "ObjectsExpression", 631 | "name": undefined, 632 | "pos": 63, 633 | "symbols": Array [ 634 | Object { 635 | "declarationPos": 79, 636 | "flags": "VariableSlot", 637 | "id": 523, 638 | "name": "c", 639 | }, 640 | Object { 641 | "declarationPos": 107, 642 | "flags": "VariableSlot", 643 | "id": 529, 644 | "name": "d", 645 | }, 646 | ], 647 | }, 648 | Object { 649 | "kind": "ObjectsExpression", 650 | "name": undefined, 651 | "pos": 87, 652 | "symbols": Array [ 653 | Object { 654 | "declarationPos": 107, 655 | "flags": "VariableSlot", 656 | "id": 535, 657 | "name": "d", 658 | }, 659 | ], 660 | }, 661 | ] 662 | `; 663 | 664 | exports[`Binder should-work-with-slot-lookup - locals 1`] = ` 665 | Array [ 666 | Object { 667 | "kind": "SequenceOfStatements", 668 | "pos": 0, 669 | "symbols": Array [ 670 | Object { 671 | "declarationPos": 0, 672 | "flags": "Variable", 673 | "id": 403, 674 | "name": "o", 675 | }, 676 | Object { 677 | "declarationPos": 35, 678 | "flags": "Variable", 679 | "id": 410, 680 | "name": "a", 681 | }, 682 | ], 683 | }, 684 | ] 685 | `; 686 | 687 | exports[`Binder should-work-with-slot-lookup - members 1`] = ` 688 | Array [ 689 | Object { 690 | "kind": "ObjectsExpression", 691 | "name": undefined, 692 | "pos": 8, 693 | "symbols": Array [ 694 | Object { 695 | "declarationPos": 20, 696 | "flags": "VariableSlot", 697 | "id": 408, 698 | "name": "field", 699 | }, 700 | ], 701 | }, 702 | Object { 703 | "kind": "ObjectsExpression", 704 | "name": undefined, 705 | "pos": 43, 706 | "symbols": Array [ 707 | Object { 708 | "declarationPos": 55, 709 | "flags": "VariableSlot", 710 | "id": 475, 711 | "name": "b", 712 | }, 713 | Object { 714 | "declarationPos": 79, 715 | "flags": "VariableSlot", 716 | "id": 499, 717 | "name": "c", 718 | }, 719 | Object { 720 | "declarationPos": 107, 721 | "flags": "VariableSlot", 722 | "id": 505, 723 | "name": "d", 724 | }, 725 | ], 726 | }, 727 | Object { 728 | "kind": "ObjectsExpression", 729 | "name": undefined, 730 | "pos": 63, 731 | "symbols": Array [ 732 | Object { 733 | "declarationPos": 79, 734 | "flags": "VariableSlot", 735 | "id": 523, 736 | "name": "c", 737 | }, 738 | Object { 739 | "declarationPos": 107, 740 | "flags": "VariableSlot", 741 | "id": 529, 742 | "name": "d", 743 | }, 744 | ], 745 | }, 746 | Object { 747 | "kind": "ObjectsExpression", 748 | "name": undefined, 749 | "pos": 87, 750 | "symbols": Array [ 751 | Object { 752 | "declarationPos": 107, 753 | "flags": "VariableSlot", 754 | "id": 535, 755 | "name": "d", 756 | }, 757 | ], 758 | }, 759 | ] 760 | `; 761 | 762 | exports[`Binder should-work-with-type-arrays - locals 1`] = ` 763 | Array [ 764 | Object { 765 | "kind": "SequenceOfStatements", 766 | "pos": 0, 767 | "symbols": Array [ 768 | Object { 769 | "declarationPos": 0, 770 | "flags": "Variable", 771 | "id": 4, 772 | "name": "a", 773 | }, 774 | ], 775 | }, 776 | ] 777 | `; 778 | 779 | exports[`Binder should-work-with-type-arrays - members 1`] = `Array []`; 780 | 781 | exports[`Binder should-work-with-type-function-call - locals 1`] = ` 782 | Array [ 783 | Object { 784 | "kind": "SequenceOfStatements", 785 | "pos": 0, 786 | "symbols": Array [ 787 | Object { 788 | "declarationPos": 0, 789 | "flags": "Function", 790 | "id": 19, 791 | "name": "add", 792 | }, 793 | Object { 794 | "declarationPos": 57, 795 | "flags": "Variable", 796 | "id": 24, 797 | "name": "a", 798 | }, 799 | ], 800 | }, 801 | Object { 802 | "kind": "FunctionStatement", 803 | "pos": 0, 804 | "symbols": Array [ 805 | Object { 806 | "declarationPos": 9, 807 | "flags": "Parameter", 808 | "id": 22, 809 | "name": "x", 810 | }, 811 | Object { 812 | "declarationPos": 21, 813 | "flags": "Parameter", 814 | "id": 23, 815 | "name": "y", 816 | }, 817 | ], 818 | }, 819 | ] 820 | `; 821 | 822 | exports[`Binder should-work-with-type-function-call - members 1`] = `Array []`; 823 | 824 | exports[`Binder should-work-with-type-function-expression - locals 1`] = ` 825 | Array [ 826 | Object { 827 | "kind": "SequenceOfStatements", 828 | "pos": 0, 829 | "symbols": Array [ 830 | Object { 831 | "declarationPos": 0, 832 | "flags": "Variable", 833 | "id": 4, 834 | "name": "a", 835 | }, 836 | ], 837 | }, 838 | ] 839 | `; 840 | 841 | exports[`Binder should-work-with-type-function-expression - members 1`] = `Array []`; 842 | 843 | exports[`Binder should-work-with-type-function-parameter - locals 1`] = ` 844 | Array [ 845 | Object { 846 | "kind": "SequenceOfStatements", 847 | "pos": 0, 848 | "symbols": Array [ 849 | Object { 850 | "declarationPos": 0, 851 | "flags": "Function", 852 | "id": 10, 853 | "name": "foo", 854 | }, 855 | ], 856 | }, 857 | Object { 858 | "kind": "FunctionStatement", 859 | "pos": 0, 860 | "symbols": Array [ 861 | Object { 862 | "declarationPos": 9, 863 | "flags": "Parameter", 864 | "id": 12, 865 | "name": "a", 866 | }, 867 | ], 868 | }, 869 | ] 870 | `; 871 | 872 | exports[`Binder should-work-with-type-function-parameter - members 1`] = `Array []`; 873 | 874 | exports[`Binder should-work-with-type-function-statement - locals 1`] = ` 875 | Array [ 876 | Object { 877 | "kind": "SequenceOfStatements", 878 | "pos": 0, 879 | "symbols": Array [ 880 | Object { 881 | "declarationPos": 0, 882 | "flags": "Function", 883 | "id": 4, 884 | "name": "foo", 885 | }, 886 | ], 887 | }, 888 | ] 889 | `; 890 | 891 | exports[`Binder should-work-with-type-function-statement - members 1`] = `Array []`; 892 | 893 | exports[`Binder should-work-with-type-integer - locals 1`] = ` 894 | Array [ 895 | Object { 896 | "kind": "SequenceOfStatements", 897 | "pos": 0, 898 | "symbols": Array [ 899 | Object { 900 | "declarationPos": 0, 901 | "flags": "Variable", 902 | "id": 4, 903 | "name": "a", 904 | }, 905 | ], 906 | }, 907 | ] 908 | `; 909 | 910 | exports[`Binder should-work-with-type-integer - members 1`] = `Array []`; 911 | 912 | exports[`Binder should-work-with-type-method-signature - locals 1`] = ` 913 | Array [ 914 | Object { 915 | "kind": "SequenceOfStatements", 916 | "pos": 0, 917 | "symbols": Array [ 918 | Object { 919 | "declarationPos": 0, 920 | "flags": "TypeDef", 921 | "id": 16, 922 | "name": "foo", 923 | }, 924 | ], 925 | }, 926 | ] 927 | `; 928 | 929 | exports[`Binder should-work-with-type-method-signature - members 1`] = ` 930 | Array [ 931 | Object { 932 | "kind": "TypeDefDeclaration", 933 | "name": "foo", 934 | "pos": 0, 935 | "symbols": Array [ 936 | Object { 937 | "declarationPos": 17, 938 | "flags": "Variable", 939 | "id": 17, 940 | "name": "a", 941 | }, 942 | Object { 943 | "declarationPos": 36, 944 | "flags": "MethodSlot", 945 | "id": 18, 946 | "name": "f", 947 | }, 948 | ], 949 | }, 950 | ] 951 | `; 952 | 953 | exports[`Binder should-work-with-type-method-slot - locals 1`] = ` 954 | Array [ 955 | Object { 956 | "kind": "SequenceOfStatements", 957 | "pos": 0, 958 | "symbols": Array [ 959 | Object { 960 | "declarationPos": 0, 961 | "flags": "Variable", 962 | "id": 58, 963 | "name": "a", 964 | }, 965 | ], 966 | }, 967 | Object { 968 | "kind": "MethodSlot", 969 | "pos": 45, 970 | "symbols": Array [ 971 | Object { 972 | "declarationPos": 56, 973 | "flags": "Parameter", 974 | "id": 76, 975 | "name": "x", 976 | }, 977 | ], 978 | }, 979 | ] 980 | `; 981 | 982 | exports[`Binder should-work-with-type-method-slot - members 1`] = ` 983 | Array [ 984 | Object { 985 | "kind": "ObjectsExpression", 986 | "name": undefined, 987 | "pos": 8, 988 | "symbols": Array [ 989 | Object { 990 | "declarationPos": 20, 991 | "flags": "VariableSlot", 992 | "id": 69, 993 | "name": "foo", 994 | }, 995 | Object { 996 | "declarationPos": 45, 997 | "flags": "MethodSlot", 998 | "id": 70, 999 | "name": "bar", 1000 | }, 1001 | ], 1002 | }, 1003 | ] 1004 | `; 1005 | 1006 | exports[`Binder should-work-with-type-null - locals 1`] = ` 1007 | Array [ 1008 | Object { 1009 | "kind": "SequenceOfStatements", 1010 | "pos": 0, 1011 | "symbols": Array [ 1012 | Object { 1013 | "declarationPos": 0, 1014 | "flags": "Variable", 1015 | "id": 4, 1016 | "name": "a", 1017 | }, 1018 | ], 1019 | }, 1020 | ] 1021 | `; 1022 | 1023 | exports[`Binder should-work-with-type-null - members 1`] = `Array []`; 1024 | 1025 | exports[`Binder should-work-with-type-typedef - locals 1`] = ` 1026 | Array [ 1027 | Object { 1028 | "kind": "SequenceOfStatements", 1029 | "pos": 0, 1030 | "symbols": Array [ 1031 | Object { 1032 | "declarationPos": 0, 1033 | "flags": "TypeDef", 1034 | "id": 10, 1035 | "name": "foo", 1036 | }, 1037 | ], 1038 | }, 1039 | ] 1040 | `; 1041 | 1042 | exports[`Binder should-work-with-type-typedef - members 1`] = ` 1043 | Array [ 1044 | Object { 1045 | "kind": "TypeDefDeclaration", 1046 | "name": "foo", 1047 | "pos": 0, 1048 | "symbols": Array [ 1049 | Object { 1050 | "declarationPos": 17, 1051 | "flags": "Variable", 1052 | "id": 11, 1053 | "name": "a", 1054 | }, 1055 | ], 1056 | }, 1057 | ] 1058 | `; 1059 | 1060 | exports[`Binder should-work-with-type-variable-reference - locals 1`] = ` 1061 | Array [ 1062 | Object { 1063 | "kind": "SequenceOfStatements", 1064 | "pos": 0, 1065 | "symbols": Array [ 1066 | Object { 1067 | "declarationPos": 0, 1068 | "flags": "TypeDef", 1069 | "id": 31, 1070 | "name": "foo", 1071 | }, 1072 | Object { 1073 | "declarationPos": 33, 1074 | "flags": "Variable", 1075 | "id": 34, 1076 | "name": "a", 1077 | }, 1078 | ], 1079 | }, 1080 | ] 1081 | `; 1082 | 1083 | exports[`Binder should-work-with-type-variable-reference - members 1`] = ` 1084 | Array [ 1085 | Object { 1086 | "kind": "TypeDefDeclaration", 1087 | "name": "foo", 1088 | "pos": 0, 1089 | "symbols": Array [ 1090 | Object { 1091 | "declarationPos": 17, 1092 | "flags": "Variable", 1093 | "id": 32, 1094 | "name": "a", 1095 | }, 1096 | ], 1097 | }, 1098 | Object { 1099 | "kind": "ObjectsExpression", 1100 | "name": undefined, 1101 | "pos": 46, 1102 | "symbols": Array [ 1103 | Object { 1104 | "declarationPos": 58, 1105 | "flags": "VariableSlot", 1106 | "id": 39, 1107 | "name": "a", 1108 | }, 1109 | ], 1110 | }, 1111 | ] 1112 | `; 1113 | 1114 | exports[`Binder should-work-with-type-variable-slot - locals 1`] = ` 1115 | Array [ 1116 | Object { 1117 | "kind": "SequenceOfStatements", 1118 | "pos": 0, 1119 | "symbols": Array [ 1120 | Object { 1121 | "declarationPos": 0, 1122 | "flags": "Variable", 1123 | "id": 22, 1124 | "name": "a", 1125 | }, 1126 | ], 1127 | }, 1128 | ] 1129 | `; 1130 | 1131 | exports[`Binder should-work-with-type-variable-slot - members 1`] = ` 1132 | Array [ 1133 | Object { 1134 | "kind": "ObjectsExpression", 1135 | "name": undefined, 1136 | "pos": 8, 1137 | "symbols": Array [ 1138 | Object { 1139 | "declarationPos": 20, 1140 | "flags": "VariableSlot", 1141 | "id": 27, 1142 | "name": "foo", 1143 | }, 1144 | ], 1145 | }, 1146 | ] 1147 | `; 1148 | 1149 | exports[`Binder should-work-with-variable-assignment - locals 1`] = ` 1150 | Array [ 1151 | Object { 1152 | "kind": "SequenceOfStatements", 1153 | "pos": 0, 1154 | "symbols": Array [ 1155 | Object { 1156 | "declarationPos": 0, 1157 | "flags": "Variable", 1158 | "id": 4, 1159 | "name": "x", 1160 | }, 1161 | ], 1162 | }, 1163 | ] 1164 | `; 1165 | 1166 | exports[`Binder should-work-with-variable-assignment - members 1`] = `Array []`; 1167 | 1168 | exports[`Binder should-work-with-variable-reference - locals 1`] = ` 1169 | Array [ 1170 | Object { 1171 | "kind": "SequenceOfStatements", 1172 | "pos": 0, 1173 | "symbols": Array [ 1174 | Object { 1175 | "declarationPos": 0, 1176 | "flags": "Variable", 1177 | "id": 4, 1178 | "name": "x", 1179 | }, 1180 | ], 1181 | }, 1182 | ] 1183 | `; 1184 | 1185 | exports[`Binder should-work-with-variable-reference - members 1`] = `Array []`; 1186 | 1187 | exports[`Binder should-work-with-while-expression - locals 1`] = ` 1188 | Array [ 1189 | Object { 1190 | "kind": "SequenceOfStatements", 1191 | "pos": 0, 1192 | "symbols": Array [ 1193 | Object { 1194 | "declarationPos": 0, 1195 | "flags": "Variable", 1196 | "id": 7, 1197 | "name": "x", 1198 | }, 1199 | Object { 1200 | "declarationPos": 10, 1201 | "flags": "Variable", 1202 | "id": 8, 1203 | "name": "y", 1204 | }, 1205 | ], 1206 | }, 1207 | ] 1208 | `; 1209 | 1210 | exports[`Binder should-work-with-while-expression - members 1`] = `Array []`; 1211 | --------------------------------------------------------------------------------