├── .editorconfig ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── compiler.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── factorial.tek ├── fib.tek ├── fizzbuzz.tek ├── snake.tek └── staircase.tek ├── index.html ├── package.json ├── src ├── dev.tek ├── diagnostic │ ├── Diagnostic.ts │ ├── Level.ts │ ├── ReportFunction.ts │ ├── SubDiagnostic.ts │ └── index.ts ├── grammar │ ├── LexicalToken.ts │ ├── Node.ts │ ├── SyntacticToken.ts │ ├── Token.ts │ ├── grammar-utils.ts │ ├── index.ts │ └── nodes │ │ ├── Declarations.ts │ │ ├── Expressions.ts │ │ ├── Other.ts │ │ └── Statements.ts ├── index.ts ├── linter │ ├── Linter.ts │ ├── LinterRule.ts │ ├── index.ts │ └── rules │ │ ├── declarations │ │ ├── illegal-redeclaration.ts │ │ ├── index.ts │ │ ├── no-undefined.ts │ │ └── use-before-define.ts │ │ └── parser │ │ ├── class-at-top-level.ts │ │ ├── declarations-in-class.ts │ │ ├── illegal-abstract.ts │ │ ├── illegal-assignment-expr.ts │ │ ├── illegal-control-statement.ts │ │ ├── illegal-if.ts │ │ ├── illegal-super-this.ts │ │ ├── import-at-top-level.ts │ │ └── index.ts ├── parser │ ├── Parser.ts │ ├── Precedence.ts │ ├── Tokenizer.ts │ ├── index.ts │ ├── internal │ │ ├── declarations │ │ │ ├── classDecl.ts │ │ │ ├── functionDecl.ts │ │ │ ├── importDecl.ts │ │ │ └── variableDecl.ts │ │ ├── expressions │ │ │ ├── arrayExpr.ts │ │ │ ├── assignmentExpr.ts │ │ │ ├── asyncExpr.ts │ │ │ ├── binaryExpr.ts │ │ │ ├── callExpr.ts │ │ │ ├── ifExpr.ts │ │ │ ├── indexExpr.ts │ │ │ ├── instanceofExpr.ts │ │ │ ├── memberExpr.ts │ │ │ ├── newExpr.ts │ │ │ ├── unaryExpr.ts │ │ │ └── wrappedExpr.ts │ │ ├── literals │ │ │ ├── identifier.ts │ │ │ ├── literals.ts │ │ │ ├── superLiteral.ts │ │ │ └── thisLiteral.ts │ │ └── statements │ │ │ ├── breakStmt.ts │ │ │ ├── continueStmt.ts │ │ │ ├── expressionStmt.ts │ │ │ ├── forStmt.ts │ │ │ ├── returnStmt.ts │ │ │ ├── whileStmt.ts │ │ │ └── yieldStmt.ts │ ├── parse-rules.ts │ └── parse-utils.ts ├── position │ ├── Span.ts │ ├── index.ts │ └── utils.ts ├── symbols │ ├── index.ts │ ├── mangle.ts │ ├── scopes │ │ ├── BlockScope.ts │ │ ├── ClassScope.ts │ │ ├── FunctionScope.ts │ │ ├── GlobalScope.ts │ │ ├── ProgramScope.ts │ │ ├── Scope.ts │ │ └── StaticScope.ts │ └── symbols │ │ ├── SymbolEntry.ts │ │ └── SymbolTable.ts ├── types │ ├── TypeChecker.ts │ ├── index.ts │ ├── internal │ │ ├── declarations │ │ │ ├── classDecl.ts │ │ │ ├── functionDecl.ts │ │ │ ├── importDecl.ts │ │ │ └── variableDecl.ts │ │ ├── expressions │ │ │ ├── unaryExpr.ts │ │ │ └── wrappedExpr.ts │ │ └── literals │ │ │ ├── booleanLiteral.ts │ │ │ ├── numberLiteral.ts │ │ │ └── stringLiteral.ts │ ├── type-check-rules.ts │ └── types │ │ ├── ArrayType.ts │ │ ├── BooleanType.ts │ │ ├── ClassType.ts │ │ ├── FunctionType.ts │ │ ├── ImportType.ts │ │ ├── NumberType.ts │ │ ├── ObjectType.ts │ │ ├── StringType.ts │ │ ├── Type.ts │ │ └── UnknownType.ts ├── typings.d.ts └── walker │ ├── ASTWalker.ts │ ├── WalkerContext.ts │ └── index.ts ├── tests ├── index.test.ts ├── linter │ ├── declarations │ │ ├── illegal-redeclaration.test.ts │ │ ├── no-undefined.test.ts │ │ └── use-before-define.test.ts │ ├── index.test.ts │ ├── parser │ │ ├── class-at-top-level.test.ts │ │ ├── declarations-in-class.test.ts │ │ ├── illegal-abstract.test.ts │ │ ├── illegal-assignment-expr.test.ts │ │ ├── illegal-control-statement.test.ts │ │ ├── illegal-if.test.ts │ │ ├── illegal-super-this.test.ts │ │ └── import-at-top-level.test.ts │ └── rule-tester.ts ├── parser │ ├── declarations │ │ ├── class │ │ │ ├── abstract │ │ │ │ ├── empty.tek │ │ │ │ ├── func.tek │ │ │ │ ├── static.tek │ │ │ │ └── var.tek │ │ │ ├── class.test.ts │ │ │ ├── constructor │ │ │ ├── empty.tek │ │ │ ├── extend │ │ │ │ ├── multi-extend.tek │ │ │ │ └── single-extend.tek │ │ │ ├── func │ │ │ │ ├── func.tek │ │ │ │ └── static-func.tek │ │ │ ├── generic │ │ │ │ ├── generic-class.tek │ │ │ │ └── generic-extends.tek │ │ │ └── var │ │ │ │ ├── static-var.tek │ │ │ │ └── var.tek │ │ ├── function │ │ │ ├── empty.tek │ │ │ ├── function.test.ts │ │ │ ├── generic-param.tek │ │ │ ├── multi-param.tek │ │ │ ├── return.tek │ │ │ ├── single-param.tek │ │ │ └── without-params.tek │ │ ├── import │ │ │ ├── full │ │ │ │ ├── full.tek │ │ │ │ └── rename.tek │ │ │ ├── import.test.ts │ │ │ └── partial │ │ │ │ ├── partial.tek │ │ │ │ └── rename.tek │ │ └── variable │ │ │ ├── empty.tek │ │ │ ├── generic.tek │ │ │ ├── variable.test.ts │ │ │ ├── with-type.tek │ │ │ └── without-type.tek │ ├── expressions │ │ ├── array │ │ │ ├── array.test.ts │ │ │ ├── multi-param.tek │ │ │ ├── no-params.tek │ │ │ └── single-param.tek │ │ ├── assignment │ │ │ ├── assignment.test.ts │ │ │ ├── index.tek │ │ │ └── variable.tek │ │ ├── async │ │ │ ├── async.tek │ │ │ └── async.test.ts │ │ ├── binary │ │ │ ├── arithmetic │ │ │ │ ├── addition.tek │ │ │ │ ├── division.tek │ │ │ │ ├── exponentation.tek │ │ │ │ ├── multiplication.tek │ │ │ │ ├── remainder.tek │ │ │ │ ├── subtraction.tek │ │ │ │ └── wrapped-subtraction.tek │ │ │ ├── binary.test.ts │ │ │ ├── comparison │ │ │ │ ├── equal.tek │ │ │ │ ├── greater-equal.tek │ │ │ │ ├── greater.tek │ │ │ │ ├── less-equal.tek │ │ │ │ ├── less.tek │ │ │ │ └── not-equal.tek │ │ │ └── logical │ │ │ │ ├── and.tek │ │ │ │ └── or.tek │ │ ├── call │ │ │ ├── call.test.ts │ │ │ ├── generic.tek │ │ │ ├── multi-param.tek │ │ │ ├── no-params.tek │ │ │ └── single-param.tek │ │ ├── if │ │ │ ├── empty.tek │ │ │ ├── if-with-else-if.tek │ │ │ ├── if-with-else.tek │ │ │ ├── if.test.ts │ │ │ ├── single-if.tek │ │ │ └── wrapped.tek │ │ ├── index │ │ │ ├── index.tek │ │ │ └── index.test.ts │ │ ├── instanceof │ │ │ ├── generic.tek │ │ │ ├── instanceof.tek │ │ │ └── instanceof.test.ts │ │ ├── member │ │ │ ├── identifier.tek │ │ │ ├── member.test.ts │ │ │ └── super.tek │ │ ├── new │ │ │ ├── generic.tek │ │ │ ├── member-class.tek │ │ │ ├── multi-param.tek │ │ │ ├── new.test.ts │ │ │ ├── no-params.tek │ │ │ └── single-param.tek │ │ ├── super │ │ │ └── super.test.ts │ │ ├── this │ │ │ └── this.test.ts │ │ ├── unary │ │ │ ├── negation.tek │ │ │ ├── not.tek │ │ │ └── unary.test.ts │ │ └── wrapped │ │ │ ├── wrapped.tek │ │ │ └── wrapped.test.ts │ ├── index.test.ts │ ├── literals │ │ ├── boolean │ │ │ └── boolean.test.ts │ │ ├── identifier │ │ │ ├── identifier.tek │ │ │ └── identifier.test.ts │ │ ├── number │ │ │ ├── number.tek │ │ │ └── number.test.ts │ │ ├── string │ │ │ ├── double-quote.tek │ │ │ ├── single-quote.tek │ │ │ └── string.test.ts │ │ ├── super │ │ │ └── super.test.ts │ │ └── this │ │ │ └── this.test.ts │ ├── other │ │ ├── parse-utils.test.ts │ │ └── program.test.ts │ ├── statements │ │ ├── break │ │ │ └── break.test.ts │ │ ├── continue │ │ │ └── continue.test.ts │ │ ├── expression │ │ │ └── expression.test.ts │ │ ├── for │ │ │ ├── for.test.ts │ │ │ ├── with-type.tek │ │ │ └── without-type.tek │ │ ├── return │ │ │ └── return.test.ts │ │ ├── while │ │ │ ├── while.tek │ │ │ └── while.test.ts │ │ └── yield │ │ │ ├── yield.tek │ │ │ └── yield.test.ts │ └── tokenizer │ │ ├── boolean.test.ts │ │ ├── comments.test.ts │ │ ├── identifier.test.ts │ │ ├── keywords.test.ts │ │ ├── number.test.ts │ │ ├── operators-punctuation.test.ts │ │ └── string.test.ts ├── symbols │ ├── index.test.ts │ └── mangle.test.ts ├── test-utils.ts └── walker │ ├── ASTWalker.test.ts │ └── index.test.ts ├── tsconfig.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | indent_style = tab 14 | 15 | [*.tek] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '@syntek/syntek/typescript', 3 | rules: { 4 | 'import/prefer-default-export': 'off', 5 | }, 6 | overrides: [ 7 | { 8 | files: ['*.test.ts'], 9 | rules: { 10 | 'no-unused-expressions': 'off', 11 | '@typescript-eslint/no-non-null-assertion': 'off', 12 | }, 13 | }, 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | 10 | github: SebastiaanYN 11 | patreon: synteklang 12 | -------------------------------------------------------------------------------- /.github/workflows/compiler.yml: -------------------------------------------------------------------------------- 1 | name: Compiler 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [10.x, 12.x] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@master 22 | 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@master 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | 28 | - name: npm install 29 | run: npm install 30 | 31 | - name: Build 32 | run: npm run build 33 | 34 | - name: Unit 35 | run: npm run unit 36 | 37 | - name: Lint 38 | run: npm run lint 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | build 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | install: npm i 10 | cache: npm 11 | 12 | jobs: 13 | include: 14 | - stage: Test 15 | script: npm run build 16 | name: "Build" 17 | - script: npm run unit 18 | name: "Unit" 19 | - script: npm run lint 20 | name: "Lint" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 SebastiaanYN 2 | Permission to use, copy, modify, and/or distribute this software for any purpose 3 | with or without fee is hereby granted, provided that the above copyright notice 4 | and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 8 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 10 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 11 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 12 | THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Discord 3 | Patreon 4 | Build 5 | Version 6 |
7 | 8 | # Syntek 9 | 10 | Syntek is an easy to use educational programming language. It is designed to provide a good ground for beginners to start their programming journey through interactive tasks and assignments. Syntek is highly configurable to make it the right fit for all assignments. Try out the demo at [syntek.dev](https://syntek.dev/)! 11 | 12 | ## Resources 13 | 14 | - [Site](https://syntek.dev/) 15 | - [Guide](https://docs.syntek.dev/guide/) 16 | - [Specification](https://docs.syntek.dev/spec/) 17 | 18 | --- 19 | 20 | ## Sponsor 21 | 22 |
23 | JDL Enterprises 24 |
25 | JDL Enterprises jdl-enterprises.co.uk 26 |
27 | -------------------------------------------------------------------------------- /examples/factorial.tek: -------------------------------------------------------------------------------- 1 | # Get the factorial of n 2 | function factorial(n) 3 | var x = 1 4 | 5 | for i in range(1, n + 1) 6 | x = x * i 7 | 8 | return x 9 | 10 | factorial(10) # 3628800 11 | -------------------------------------------------------------------------------- /examples/fib.tek: -------------------------------------------------------------------------------- 1 | # Fibonacci Sequence 2 | 3 | # The first x Fibonacci numbers 4 | function fib(x: Number): Number[] 5 | var numbers: Number[] = [0, 1] 6 | 7 | for i in range(x) 8 | numbers.add(numbers[x] + numbers[x + 1]) 9 | 10 | return numbers 11 | 12 | fib(21) 13 | -------------------------------------------------------------------------------- /examples/fizzbuzz.tek: -------------------------------------------------------------------------------- 1 | # Write a short program that prints each number from 1 to 100 on a new line. 2 | # For each multiple of 3, print "Fizz" instead of the number. 3 | # For each multiple of 5, print "Buzz" instead of the number. 4 | # For numbers which are multiples of both 3 and 5, print "FizzBuzz" instead of the number. 5 | 6 | # Loop from 1 to 100 7 | for i in range(1, 101) 8 | var output: String = '' 9 | 10 | if i % 3 is 0 11 | output.append('Fizz') 12 | 13 | if i % 5 is 0 14 | output.append('Buzz') 15 | 16 | if output.length() is greater than 0 17 | print(output) 18 | else 19 | print(i) 20 | -------------------------------------------------------------------------------- /examples/snake.tek: -------------------------------------------------------------------------------- 1 | # Snake 2 | 3 | var direction = 1 4 | 5 | function arrowUp() 6 | direction = 0 7 | 8 | function arrowRight() 9 | direction = 1 10 | 11 | function arrowDown() 12 | direction = 2 13 | 14 | function arrowLeft() 15 | direction = 3 16 | 17 | function loop() 18 | if direction is 0 19 | moveUp() 20 | else if direction is 1 21 | moveRight() 22 | else if direction is 2 23 | moveDown() 24 | else if direction is 3 25 | moveLeft() 26 | -------------------------------------------------------------------------------- /examples/staircase.tek: -------------------------------------------------------------------------------- 1 | # Print a staircase of x stairs 2 | 3 | # Sample input: 4 4 | # Output: 5 | # # 6 | # ## 7 | # ### 8 | # #### 9 | 10 | function printStairs(x) 11 | for i in range(1, x + 1) 12 | print(' '.repeat(x - i).append('#'.repeat(i))) 13 | 14 | printStairs(4) 15 | printStairs(6) 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | 
17 |   
84 | 
85 | 
86 | 
87 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "syntek",
 3 |   "version": "0.0.1",
 4 |   "description": "Syntek is an easy to use educational programming language",
 5 |   "main": "build/bundle.js",
 6 |   "module": "src/index.ts",
 7 |   "unpkg": "build/bundle.js",
 8 |   "scripts": {
 9 |     "test": "npm run build && npm run lint && npm run unit",
10 |     "dev": "webpack --watch",
11 |     "build": "webpack -p --devtool false",
12 |     "prepublishOnly": "npm run build",
13 |     "lint": "eslint --ext .js,.ts --ignore-path .gitignore .",
14 |     "unit": "mocha -r ts-node/register tests/index.test.ts"
15 |   },
16 |   "repository": {
17 |     "type": "git",
18 |     "url": "git+https://github.com/SebastiaanYN/Syntek.git"
19 |   },
20 |   "author": "SebastiaanYN",
21 |   "license": "ISC",
22 |   "bugs": {
23 |     "url": "https://github.com/SebastiaanYN/Syntek/issues"
24 |   },
25 |   "homepage": "https://syntek.dev",
26 |   "dependencies": {},
27 |   "devDependencies": {
28 |     "@syntek/eslint-config-syntek": "^2.0.2",
29 |     "@types/chai": "^4.2.0",
30 |     "@types/mocha": "^5.2.6",
31 |     "@types/node": "^12.7.1",
32 |     "@typescript-eslint/eslint-plugin": "^1.13.0",
33 |     "@typescript-eslint/parser": "^1.13.0",
34 |     "chai": "^4.2.0",
35 |     "eslint": "^6.1.0",
36 |     "eslint-config-airbnb-base": "^13.2.0",
37 |     "eslint-plugin-eslint-comments": "^3.1.2",
38 |     "eslint-plugin-import": "^2.18.2",
39 |     "eslint-plugin-jsdoc": "^15.8.0",
40 |     "mocha": "^6.2.0",
41 |     "raw-loader": "^3.1.0",
42 |     "ts-loader": "^6.0.4",
43 |     "ts-node": "^8.1.0",
44 |     "typescript": "^3.5.3",
45 |     "webpack": "^4.39.1",
46 |     "webpack-cli": "^3.3.6"
47 |   }
48 | }
49 | 


--------------------------------------------------------------------------------
/src/dev.tek:
--------------------------------------------------------------------------------
 1 | new Class()
 2 | 
 3 | new Class(
 4 | )
 5 | 
 6 | new
 7 | Class()
 8 | 
 9 | new
10 | Class(
11 | )
12 | 
13 | new Class
14 | ()
15 | 
16 | new Class
17 | (
18 | )
19 | 
20 | new
21 | Class
22 | ()
23 | 
24 | new
25 | Class
26 | (
27 | )
28 | 


--------------------------------------------------------------------------------
/src/diagnostic/Diagnostic.ts:
--------------------------------------------------------------------------------
 1 | import { Level } from '.';
 2 | import { Span } from '../position';
 3 | import { SubDiagnostic } from './SubDiagnostic';
 4 | 
 5 | export class Diagnostic {
 6 |   readonly level: Level;
 7 | 
 8 |   readonly name: string;
 9 | 
10 |   readonly msg: string;
11 | 
12 |   readonly span: Span;
13 | 
14 |   readonly children: SubDiagnostic[] = [];
15 | 
16 |   constructor(level: Level, name: string, msg: string, span: Span) {
17 |     this.level = level;
18 |     this.name = name;
19 |     this.msg = msg;
20 |     this.span = span;
21 |   }
22 | 
23 |   info(msg: string, span: Span): Diagnostic {
24 |     return this.sub(Level.INFO, msg, span);
25 |   }
26 | 
27 |   warn(msg: string, span: Span): Diagnostic {
28 |     return this.sub(Level.WARN, msg, span);
29 |   }
30 | 
31 |   error(msg: string, span: Span): Diagnostic {
32 |     return this.sub(Level.ERROR, msg, span);
33 |   }
34 | 
35 |   private sub(level: Level, msg: string, span: Span): Diagnostic {
36 |     this.children.push(new SubDiagnostic(level, msg, span));
37 |     return this;
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/src/diagnostic/Level.ts:
--------------------------------------------------------------------------------
1 | export enum Level {
2 |   INFO,
3 |   WARN,
4 |   ERROR,
5 | }
6 | 


--------------------------------------------------------------------------------
/src/diagnostic/ReportFunction.ts:
--------------------------------------------------------------------------------
 1 | import { Diagnostic } from '.';
 2 | import { Span } from '../position';
 3 | 
 4 | export type ErrorHandler = (error: Diagnostic) => void;
 5 | 
 6 | export type ReportFunction = (
 7 |   msg: string,
 8 |   span: Span,
 9 |   errorHandler?: ErrorHandler,
10 | ) => void;
11 | 


--------------------------------------------------------------------------------
/src/diagnostic/SubDiagnostic.ts:
--------------------------------------------------------------------------------
 1 | import { Level } from '.';
 2 | import { Span } from '../position';
 3 | 
 4 | export class SubDiagnostic {
 5 |   readonly level: Level;
 6 | 
 7 |   readonly msg: string;
 8 | 
 9 |   readonly span: Span;
10 | 
11 |   constructor(level: Level, msg: string, span: Span) {
12 |     this.level = level;
13 |     this.msg = msg;
14 |     this.span = span;
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/diagnostic/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Level';
2 | export * from './Diagnostic';
3 | export * from './SubDiagnostic';
4 | export * from './ReportFunction';
5 | 


--------------------------------------------------------------------------------
/src/grammar/LexicalToken.ts:
--------------------------------------------------------------------------------
 1 | export enum LexicalToken {
 2 |   // Whitespace
 3 |   NEWLINE,
 4 |   COMMENT,
 5 | 
 6 |   // Identifier
 7 |   IDENTIFIER,
 8 | 
 9 |   // Operators
10 |   PLUS,
11 |   MINUS,
12 |   STAR,
13 |   SLASH,
14 |   PERCENT,
15 |   CARET,
16 | 
17 |   EQUAL,
18 |   EQUAL_EQUAL,
19 | 
20 |   BANG,
21 |   BANG_EQUAL,
22 | 
23 |   LT,
24 |   LT_EQUAL,
25 | 
26 |   GT,
27 |   GT_EQUAL,
28 | 
29 |   // Punctuation
30 |   DOT,
31 |   COMMA,
32 |   L_SQB,
33 |   R_SQB,
34 |   L_PAR,
35 |   R_PAR,
36 |   L_BRACE,
37 |   R_BRACE,
38 |   COLON,
39 | 
40 |   // Literals
41 |   NUMBER,
42 |   STRING,
43 |   BOOLEAN,
44 | 
45 |   // Keywords
46 |   CLASS,
47 |   EXTENDS,
48 |   NEW,
49 | 
50 |   ABSTRACT,
51 |   STATIC,
52 | 
53 |   THIS,
54 |   SUPER,
55 |   INSTANCEOF,
56 | 
57 |   IF,
58 |   ELSE,
59 | 
60 |   FUNCTION,
61 |   RETURN,
62 |   VOID,
63 | 
64 |   ASYNC,
65 | 
66 |   IMPORT,
67 |   AS,
68 | 
69 |   FOR,
70 |   IN,
71 |   WHILE,
72 | 
73 |   CONTINUE,
74 |   BREAK,
75 |   YIELD,
76 | 
77 |   AND,
78 |   OR,
79 | 
80 |   VAR,
81 | 
82 |   // Other
83 |   EOF,
84 |   NATIVE_TOKEN,
85 | }
86 | 


--------------------------------------------------------------------------------
/src/grammar/Node.ts:
--------------------------------------------------------------------------------
 1 | import { Token, SyntacticToken } from '.';
 2 | import { Span } from '../position';
 3 | 
 4 | export abstract class Node {
 5 |   readonly type: SyntacticToken;
 6 | 
 7 |   readonly span: Span;
 8 | 
 9 |   constructor(type: SyntacticToken, span: Span) {
10 |     this.type = type;
11 |     this.span = span;
12 |   }
13 | }
14 | 
15 | export abstract class DeclarationNode extends Node {
16 |   readonly identifier: Token;
17 | 
18 |   constructor(identifier: Token, type: SyntacticToken, span: Span) {
19 |     super(type, span);
20 | 
21 |     this.identifier = identifier;
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/src/grammar/SyntacticToken.ts:
--------------------------------------------------------------------------------
 1 | export enum SyntacticToken {
 2 |   // Declarations
 3 |   EMPTY_VARIABLE_DECL,
 4 |   VARIABLE_DECL,
 5 |   EMPTY_FUNCTION_DECL,
 6 |   FUNCTION_DECL,
 7 |   CLASS_DECL,
 8 |   FULL_IMPORT_DECL,
 9 |   PARTIAL_IMPORT_DECL,
10 | 
11 |   // Expressions
12 |   ASSIGNMENT_EXPR,
13 |   WRAPPED_EXPR,
14 |   UNARY_EXPR,
15 |   BINARY_EXPR,
16 |   CALL_EXPR,
17 |   INDEX_EXPR,
18 |   MEMBER_EXPR,
19 |   NEW_EXPR,
20 |   INSTANCEOF_EXPR,
21 |   ASYNC_EXPR,
22 |   ARRAY_EXPR,
23 |   IF_EXPR,
24 |   ELSE_EXPR,
25 | 
26 |   IDENTIFIER,
27 |   SUPER,
28 |   THIS,
29 | 
30 |   NUMBER_LITERAL,
31 |   STRING_LITERAL,
32 |   BOOLEAN_LITERAL,
33 | 
34 |   // Statements
35 |   FOR_STMT,
36 |   WHILE_STMT,
37 |   RETURN_STMT,
38 |   YIELD_STMT,
39 |   EXPRESSION_STMT,
40 | 
41 |   BREAK_STMT,
42 |   CONTINUE_STMT,
43 | 
44 |   // Other
45 |   NATIVE_NODE,
46 |   PROGRAM,
47 |   GENERIC_PARAM,
48 |   VARIABLE_TYPE,
49 |   PARAMETER,
50 |   CONSTRUCTOR,
51 |   CLASS_PROP,
52 |   IMPORT_EXPOSE,
53 | }
54 | 


--------------------------------------------------------------------------------
/src/grammar/Token.ts:
--------------------------------------------------------------------------------
 1 | import { LexicalToken } from '.';
 2 | import { Span } from '../position';
 3 | 
 4 | export class Token {
 5 |   readonly type: LexicalToken;
 6 | 
 7 |   readonly lexeme: string;
 8 | 
 9 |   readonly span: Span;
10 | 
11 |   constructor(type: LexicalToken, lexeme: string, span: Span) {
12 |     this.type = type;
13 |     this.lexeme = lexeme;
14 |     this.span = span;
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/grammar/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './Token';
 2 | export * from './Node';
 3 | 
 4 | export * from './SyntacticToken';
 5 | export * from './LexicalToken';
 6 | 
 7 | export * from './nodes/Declarations';
 8 | export * from './nodes/Expressions';
 9 | export * from './nodes/Statements';
10 | export * from './nodes/Other';
11 | 
12 | export * from './grammar-utils';
13 | 


--------------------------------------------------------------------------------
/src/grammar/nodes/Other.ts:
--------------------------------------------------------------------------------
  1 | import {
  2 |   Node, DeclarationNode, Token, SyntacticToken, Identifier, MemberExpression,
  3 | } from '..';
  4 | import { Span } from '../../position';
  5 | 
  6 | export class NativeNode extends DeclarationNode {
  7 |   constructor(identifier: Token) {
  8 |     super(identifier, SyntacticToken.NATIVE_NODE, new Span([0, 0], [0, 0]));
  9 |   }
 10 | }
 11 | 
 12 | export class Program extends Node {
 13 |   readonly body: Node[];
 14 | 
 15 |   constructor(body: Node[], span: Span) {
 16 |     super(SyntacticToken.PROGRAM, span);
 17 | 
 18 |     this.body = body;
 19 |   }
 20 | }
 21 | 
 22 | export class GenericParam extends DeclarationNode {
 23 |   readonly extend?: VariableType;
 24 | 
 25 |   constructor(identifier: Token, extend: VariableType | undefined, span: Span) {
 26 |     super(identifier, SyntacticToken.GENERIC_PARAM, span);
 27 | 
 28 |     this.extend = extend;
 29 |   }
 30 | }
 31 | 
 32 | export class VariableType extends Node {
 33 |   readonly object: Identifier | MemberExpression;
 34 | 
 35 |   readonly generics: VariableType[];
 36 | 
 37 |   readonly arrayDepth: number;
 38 | 
 39 |   constructor(
 40 |     object: Identifier | MemberExpression,
 41 |     generics: VariableType[],
 42 |     arrayDepth: number,
 43 |     span: Span,
 44 |   ) {
 45 |     super(SyntacticToken.VARIABLE_TYPE, span);
 46 | 
 47 |     this.object = object;
 48 |     this.generics = generics;
 49 |     this.arrayDepth = arrayDepth;
 50 |   }
 51 | }
 52 | 
 53 | export class Parameter extends DeclarationNode {
 54 |   readonly variableType: VariableType;
 55 | 
 56 |   constructor(identifier: Token, variableType: VariableType, span: Span) {
 57 |     super(identifier, SyntacticToken.PARAMETER, span);
 58 | 
 59 |     this.variableType = variableType;
 60 |   }
 61 | }
 62 | 
 63 | export class Constructor extends Node {
 64 |   readonly params: Parameter[];
 65 | 
 66 |   readonly body: Node[];
 67 | 
 68 |   constructor(params: Parameter[], body: Node[], span: Span) {
 69 |     super(SyntacticToken.CONSTRUCTOR, span);
 70 | 
 71 |     this.params = params;
 72 |     this.body = body;
 73 |   }
 74 | }
 75 | 
 76 | export class ClassProp extends Node {
 77 |   readonly value: Node;
 78 | 
 79 |   readonly abstract: boolean;
 80 | 
 81 |   readonly static: boolean;
 82 | 
 83 |   constructor(value: Node, abstract: boolean, isStatic: boolean, span: Span) {
 84 |     super(SyntacticToken.CLASS_PROP, span);
 85 | 
 86 |     this.value = value;
 87 |     this.abstract = abstract;
 88 |     this.static = isStatic;
 89 |   }
 90 | }
 91 | 
 92 | export class ImportExpose extends DeclarationNode {
 93 |   readonly value: Token;
 94 | 
 95 |   constructor(value: Token, rename: Token | undefined, span: Span) {
 96 |     const identifier = rename || value;
 97 |     super(identifier, SyntacticToken.IMPORT_EXPOSE, span);
 98 | 
 99 |     this.value = value;
100 |   }
101 | }
102 | 


--------------------------------------------------------------------------------
/src/grammar/nodes/Statements.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, DeclarationNode, Token, SyntacticToken, VariableType,
 3 | } from '..';
 4 | 
 5 | import { Span } from '../../position';
 6 | 
 7 | export class ForStatement extends DeclarationNode {
 8 |   readonly variableType: VariableType | null;
 9 | 
10 |   readonly object: Node;
11 | 
12 |   readonly body: Node[];
13 | 
14 |   constructor(
15 |     identifier: Token,
16 |     variableType: VariableType | null,
17 |     object: Node,
18 |     body: Node[],
19 |     span: Span,
20 |   ) {
21 |     super(identifier, SyntacticToken.FOR_STMT, span);
22 | 
23 |     this.variableType = variableType;
24 |     this.object = object;
25 |     this.body = body;
26 |   }
27 | }
28 | 
29 | export class WhileStatement extends Node {
30 |   readonly condition: Node;
31 | 
32 |   readonly body: Node[];
33 | 
34 |   constructor(condition: Node, body: Node[], span: Span) {
35 |     super(SyntacticToken.WHILE_STMT, span);
36 | 
37 |     this.condition = condition;
38 |     this.body = body;
39 |   }
40 | }
41 | 
42 | export class ReturnStatement extends Node {
43 |   readonly expression: Node | null;
44 | 
45 |   constructor(expression: Node | null, span: Span) {
46 |     super(SyntacticToken.RETURN_STMT, span);
47 | 
48 |     this.expression = expression;
49 |   }
50 | }
51 | 
52 | export class YieldStatement extends Node {
53 |   readonly expression: Node;
54 | 
55 |   constructor(expression: Node, span: Span) {
56 |     super(SyntacticToken.YIELD_STMT, span);
57 | 
58 |     this.expression = expression;
59 |   }
60 | }
61 | 
62 | export class ExpressionStatement extends Node {
63 |   readonly expression: Node;
64 | 
65 |   constructor(expression: Node, span: Span) {
66 |     super(SyntacticToken.EXPRESSION_STMT, span);
67 | 
68 |     this.expression = expression;
69 |   }
70 | }
71 | 
72 | export class BreakStatement extends Node {
73 |   constructor(span: Span) {
74 |     super(SyntacticToken.BREAK_STMT, span);
75 |   }
76 | }
77 | 
78 | export class ContinueStatement extends Node {
79 |   constructor(span: Span) {
80 |     super(SyntacticToken.CONTINUE_STMT, span);
81 |   }
82 | }
83 | 


--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
 1 | /* eslint-disable import/first, import/newline-after-import */
 2 | 
 3 | // import program from '../tests/syntek/programs/fizzbuzz.tek';
 4 | import program from './dev.tek';
 5 | console.log(program);
 6 | 
 7 | // Parsing
 8 | import { Tokenizer, Parser } from './parser';
 9 | export const tokenResult = new Tokenizer(program).tokenize();
10 | console.log(tokenResult);
11 | 
12 | export const parseResult = new Parser(tokenResult.tokens).parse();
13 | console.log(parseResult);
14 | 
15 | // Lint parser
16 | import { Linter } from './linter';
17 | import * as lintParser from './linter/rules/parser';
18 | 
19 | console.log(new Linter(
20 |   parseResult.ast,
21 |   lintParser,
22 | ).lint());
23 | 
24 | // Scopes
25 | import { ProgramScope } from './symbols';
26 | 
27 | export const scope = new ProgramScope(parseResult.ast);
28 | scope.build();
29 | console.log(scope);
30 | 
31 | // Lint declarations
32 | import * as lintDeclarations from './linter/rules/declarations';
33 | 
34 | console.log(new Linter(
35 |   scope,
36 |   lintDeclarations,
37 | ).lint());
38 | 
39 | // Collect types
40 | import { TypeChecker } from './types';
41 | 
42 | export const typedScope = new ProgramScope(parseResult.ast);
43 | typedScope.build();
44 | console.log(new TypeChecker(typedScope).check());
45 | console.log(typedScope);
46 | 
47 | // Export everything
48 | export const code = program;
49 | export * from './diagnostic';
50 | export * from './grammar';
51 | export * from './linter';
52 | export * from './parser';
53 | export * from './position';
54 | export * from './symbols';
55 | export * from './types';
56 | export * from './walker';
57 | 


--------------------------------------------------------------------------------
/src/linter/Linter.ts:
--------------------------------------------------------------------------------
 1 | import { LinterRule } from '.';
 2 | import { Scope } from '../symbols';
 3 | import { Node } from '../grammar';
 4 | import { ASTWalker } from '../walker';
 5 | import { Diagnostic } from '../diagnostic';
 6 | 
 7 | interface LinterRules {
 8 |   [name: string]: LinterRule;
 9 | }
10 | 
11 | export class Linter {
12 |   private readonly ast: Node;
13 | 
14 |   private readonly rules: LinterRules;
15 | 
16 |   private readonly scope?: Scope;
17 | 
18 |   private readonly diagnostics: Diagnostic[] = [];
19 | 
20 |   constructor(astOrScope: Node | Scope, rules: LinterRules) {
21 |     if (astOrScope instanceof Node) {
22 |       this.ast = astOrScope;
23 |     } else {
24 |       this.ast = astOrScope.node;
25 |       this.scope = astOrScope;
26 |     }
27 | 
28 |     this.rules = rules;
29 |   }
30 | 
31 |   lint(): Diagnostic[] {
32 |     const walker = new ASTWalker(this.ast, this.scope);
33 | 
34 |     Object.entries(this.rules).forEach(([name, rule]) => {
35 |       rule.create(walker, (msg, span, errorHandler) => {
36 |         const error = new Diagnostic(rule.level, name, msg, span);
37 | 
38 |         if (errorHandler) {
39 |           errorHandler(error);
40 |         }
41 | 
42 |         this.diagnostics.push(error);
43 |       });
44 |     });
45 | 
46 |     walker.walk();
47 |     return this.diagnostics;
48 |   }
49 | }
50 | 


--------------------------------------------------------------------------------
/src/linter/LinterRule.ts:
--------------------------------------------------------------------------------
1 | import { ASTWalker } from '../walker';
2 | import { Level, ReportFunction } from '../diagnostic';
3 | 
4 | export interface LinterRule {
5 |   description: string;
6 |   level: Level;
7 |   create(walker: ASTWalker, report: ReportFunction): void;
8 | }
9 | 


--------------------------------------------------------------------------------
/src/linter/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Linter';
2 | export * from './LinterRule';
3 | 


--------------------------------------------------------------------------------
/src/linter/rules/declarations/index.ts:
--------------------------------------------------------------------------------
1 | export * from './illegal-redeclaration';
2 | export * from './no-undefined';
3 | export * from './use-before-define';
4 | 


--------------------------------------------------------------------------------
/src/linter/rules/declarations/no-undefined.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | import { WalkerContext } from '../../../walker';
 7 | 
 8 | export const noUndefined: LinterRule = {
 9 |   description: 'Report references to undefined variables',
10 |   level: Level.ERROR,
11 |   create(walker, report) {
12 |     function check(node: grammar.Node, ctx: WalkerContext): void {
13 |       if (node.type === grammar.SyntacticToken.IDENTIFIER) {
14 |         const identifier = node as grammar.Identifier;
15 |         const scope = ctx.getScope();
16 | 
17 |         // If there is no symbol or function with the given name, report it
18 |         if (!scope.hasSymbol(identifier.lexeme) && !scope.hasFunction(identifier.lexeme)) {
19 |           report(`No symbol with the name '${identifier.lexeme}'`, identifier.span);
20 |         }
21 |       }
22 |     }
23 | 
24 |     walker
25 |       // Declarations
26 |       .onEnter(grammar.VariableDeclaration, (node, ctx) => check(node.value, ctx))
27 | 
28 |       // Expressions
29 |       .onEnter(grammar.AssignmentExpression, (node, ctx) => {
30 |         check(node.left, ctx);
31 |         check(node.value, ctx);
32 |       })
33 |       .onEnter(grammar.WrappedExpression, (node, ctx) => check(node.expression, ctx))
34 |       .onEnter(grammar.UnaryExpression, (node, ctx) => check(node.right, ctx))
35 |       .onEnter(grammar.BinaryExpression, (node, ctx) => {
36 |         check(node.left, ctx);
37 |         check(node.right, ctx);
38 |       })
39 |       .onEnter(grammar.CallExpression, (node, ctx) => {
40 |         check(node.object, ctx);
41 |         node.params.forEach(param => check(param, ctx));
42 |       })
43 |       .onEnter(grammar.IndexExpression, (node, ctx) => {
44 |         check(node.object, ctx);
45 |         check(node.index, ctx);
46 |       })
47 |       .onEnter(grammar.MemberExpression, (node, ctx) => check(node.object, ctx))
48 |       .onEnter(grammar.NewExpression, (node, ctx) => {
49 |         check(node.object, ctx);
50 |         node.params.forEach(param => check(param, ctx));
51 |       })
52 |       .onEnter(grammar.InstanceofExpression, (node, ctx) => {
53 |         check(node.left, ctx);
54 |         check(node.right, ctx);
55 |       })
56 |       .onEnter(grammar.AsyncExpression, (node, ctx) => check(node.expression, ctx))
57 |       .onEnter(grammar.ArrayExpression, (node, ctx) => {
58 |         node.content.forEach(value => check(value, ctx));
59 |       })
60 |       .onEnter(grammar.IfExpression, (node, ctx) => check(node.condition, ctx))
61 | 
62 |       // Statements
63 |       .onEnter(grammar.ForStatement, (node, ctx) => check(node.object, ctx))
64 |       .onEnter(grammar.WhileStatement, (node, ctx) => check(node.condition, ctx))
65 |       .onEnter(grammar.ReturnStatement, (node, ctx) => {
66 |         if (node.expression) {
67 |           check(node.expression, ctx);
68 |         }
69 |       })
70 |       .onEnter(grammar.YieldStatement, (node, ctx) => check(node.expression, ctx))
71 |       .onEnter(grammar.ExpressionStatement, (node, ctx) => check(node.expression, ctx))
72 | 
73 |       // Other
74 |       .onEnter(grammar.VariableType, (node, ctx) => check(node.object, ctx));
75 |   },
76 | };
77 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/class-at-top-level.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | export const classAtTopLevel: LinterRule = {
 7 |   description: 'Report when a class declaration is not at the top level',
 8 |   level: Level.ERROR,
 9 |   create(walker, report) {
10 |     walker.onEnter(grammar.ClassDeclaration, (node, ctx) => {
11 |       if (ctx.parents[ctx.parents.length - 1].type !== grammar.SyntacticToken.PROGRAM) {
12 |         report('A class can only be declared at the top level', node.span);
13 |       }
14 |     });
15 |   },
16 | };
17 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/declarations-in-class.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | export const declarationsInClass: LinterRule = {
 7 |   description: 'Report when an expression or statement is in a class body',
 8 |   level: Level.ERROR,
 9 |   create(walker, report) {
10 |     function checkNode(node: grammar.Node): void {
11 |       if (!grammar.isDeclaration(node)) {
12 |         report('You can only put declarations in a class body', node.span);
13 |       }
14 |     }
15 | 
16 |     walker.onEnter(grammar.ClassDeclaration, (node) => {
17 |       node.staticBody.forEach(prop => checkNode(prop.value));
18 |       node.instanceBody.forEach(prop => checkNode(prop.value));
19 |     });
20 |   },
21 | };
22 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/illegal-abstract.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | function isEmptyValue(node: grammar.Node): boolean {
 7 |   return node.type === grammar.SyntacticToken.EMPTY_FUNCTION_DECL
 8 |     || node.type === grammar.SyntacticToken.EMPTY_VARIABLE_DECL;
 9 | }
10 | 
11 | export const illegalAbstract: LinterRule = {
12 |   description: 'Report illegal abstract properties',
13 |   level: Level.ERROR,
14 |   create(walker, report) {
15 |     function checkAbstractClass(prop: grammar.ClassProp): void {
16 |       if (prop.abstract) {
17 |         report('Only abstract classes can contain abstract properties', prop.span);
18 |       }
19 |     }
20 | 
21 |     function checkAbstractProperty(prop: grammar.ClassProp): void {
22 |       if (prop.abstract && !isEmptyValue(prop.value)) {
23 |         report('An abstract property can not contain a value', prop.span);
24 |       }
25 |     }
26 | 
27 |     walker.onEnter(grammar.ClassDeclaration, (node) => {
28 |       if (!node.abstract) {
29 |         node.staticBody.forEach(checkAbstractClass);
30 |         node.instanceBody.forEach(checkAbstractClass);
31 |       }
32 | 
33 |       node.staticBody.forEach(checkAbstractProperty);
34 |       node.instanceBody.forEach(checkAbstractProperty);
35 |     });
36 |   },
37 | };
38 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/illegal-assignment-expr.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | export const illegalAssignmentExpr: LinterRule = {
 7 |   description: 'Report illegal assignments',
 8 |   level: Level.ERROR,
 9 |   create(walker, report) {
10 |     walker.onEnter(grammar.AssignmentExpression, (node, ctx) => {
11 |       if (ctx.parents[ctx.parents.length - 1].type !== grammar.SyntacticToken.EXPRESSION_STMT) {
12 |         report('Assignments can not be inside another expression', node.span);
13 |       }
14 | 
15 |       switch (node.left.type) {
16 |         case grammar.SyntacticToken.IDENTIFIER:
17 |         case grammar.SyntacticToken.MEMBER_EXPR:
18 |         case grammar.SyntacticToken.INDEX_EXPR:
19 |           break;
20 |         default:
21 |           report('You can only assign to an identifier, member expression, and index expression', node.left.span);
22 |           break;
23 |       }
24 |     });
25 |   },
26 | };
27 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/illegal-control-statement.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | function inIfExpr(parents: grammar.Node[]): boolean {
 7 |   for (let i = parents.length - 1; i > 0; i -= 1) {
 8 |     if (parents[i].type === grammar.SyntacticToken.IF_EXPR) {
 9 |       // Return true if the parent is not an expression stmt, making it an expr
10 |       return parents[i - 1].type !== grammar.SyntacticToken.EXPRESSION_STMT;
11 |     }
12 |   }
13 | 
14 |   return false;
15 | }
16 | 
17 | function inLoop(parents: grammar.Node[]): boolean {
18 |   return parents.some(parent => parent.type === grammar.SyntacticToken.FOR_STMT
19 |     || parent.type === grammar.SyntacticToken.WHILE_STMT);
20 | }
21 | 
22 | export const illegalControlStatement: LinterRule = {
23 |   description: 'Report illegal control statements',
24 |   level: Level.ERROR,
25 |   create(walker, report) {
26 |     walker.onEnter(grammar.ReturnStatement, (node, ctx) => {
27 |       // If the return is inside an if expression, report it
28 |       if (inIfExpr(ctx.parents)) {
29 |         report('You can not return from an if expression', node.span);
30 |         return;
31 |       }
32 | 
33 |       // Return must be inside a function
34 |       const isValid = ctx.parents
35 |         .some(parent => parent.type === grammar.SyntacticToken.FUNCTION_DECL);
36 | 
37 |       if (!isValid) {
38 |         report('You can only place return inside a function', node.span);
39 |       }
40 |     });
41 | 
42 |     walker.onEnter(grammar.YieldStatement, (node, ctx) => {
43 |       if (!inIfExpr(ctx.parents)) {
44 |         report('You can only place yield inside an if expression', node.span);
45 |       }
46 |     });
47 | 
48 |     walker.onEnter(grammar.BreakStatement, (node, ctx) => {
49 |       if (!inLoop(ctx.parents)) {
50 |         report('You can only place break inside a loop', node.span);
51 |       }
52 |     });
53 | 
54 |     walker.onEnter(grammar.ContinueStatement, (node, ctx) => {
55 |       if (!inLoop(ctx.parents)) {
56 |         report('You can only place continue inside a loop', node.span);
57 |       }
58 |     });
59 |   },
60 | };
61 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/illegal-if.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | function isExpr(node: grammar.IfExpression, parents: grammar.Node[]): boolean {
 7 |   for (let i = parents.length - 1; i >= 0; i -= 1) {
 8 |     const parent = parents[i];
 9 | 
10 |     if (parent.type === grammar.SyntacticToken.IF_EXPR) {
11 |       if ((parent as grammar.IfExpression).elseClause !== node) {
12 |         // If the parent if statement is not part of this statement it's an expression
13 |         return true;
14 |       }
15 |     } else {
16 |       return parent.type !== grammar.SyntacticToken.EXPRESSION_STMT;
17 |     }
18 |   }
19 | 
20 |   return false;
21 | }
22 | 
23 | export const illegalIf: LinterRule = {
24 |   description: 'Report illegal if expressions',
25 |   level: Level.ERROR,
26 |   create(walker, report) {
27 |     walker.onEnter(grammar.IfExpression, (node, ctx) => {
28 |       if (isExpr(node, ctx.parents)) {
29 |         if (!node.elseClause) {
30 |           report('An if expression must have an else body', node.span);
31 |         }
32 |       }
33 |     });
34 |   },
35 | };
36 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/illegal-super-this.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | export const illegalSuperThis: LinterRule = {
 7 |   description: 'Report this and super usage outside of a class',
 8 |   level: Level.ERROR,
 9 |   create(walker, report) {
10 |     function getClassProp(parents: grammar.Node[]): grammar.ClassProp | undefined {
11 |       for (let i = parents.length - 1; i >= 0; i -= 1) {
12 |         if (parents[i].type === grammar.SyntacticToken.CLASS_PROP) {
13 |           return parents[i] as grammar.ClassProp;
14 |         }
15 |       }
16 | 
17 |       return undefined;
18 |     }
19 | 
20 |     walker.onEnter(grammar.Super, (node, ctx) => {
21 |       const classProp = getClassProp(ctx.parents);
22 | 
23 |       if (!classProp) {
24 |         report("You can only use 'super' inside a class", node.span);
25 |         return;
26 |       }
27 | 
28 |       if (classProp.static) {
29 |         report("You can not use 'super' in a static variable or function", node.span);
30 |       }
31 |     });
32 | 
33 |     walker.onEnter(grammar.MemberExpression, (node, ctx) => {
34 |       if (node.property.type !== grammar.LexicalToken.SUPER) {
35 |         return;
36 |       }
37 | 
38 |       const classProp = getClassProp(ctx.parents);
39 | 
40 |       if (!classProp) {
41 |         report("You can only use 'super' inside a class", node.property.span);
42 |         return;
43 |       }
44 | 
45 |       if (classProp.static) {
46 |         report("You can not use 'super' in a static variable or function", node.property.span);
47 |       }
48 |     });
49 | 
50 |     walker.onEnter(grammar.This, (node, ctx) => {
51 |       const classProp = getClassProp(ctx.parents);
52 | 
53 |       if (!classProp) {
54 |         report("You can only use 'this' inside a class", node.span);
55 |         return;
56 |       }
57 | 
58 |       if (classProp.static) {
59 |         report("You can not use 'this' in a static variable or function", node.span);
60 |       }
61 |     });
62 |   },
63 | };
64 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/import-at-top-level.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { LinterRule } from '../..';
 4 | import { Level } from '../../../diagnostic';
 5 | 
 6 | export const importAtTopLevel: LinterRule = {
 7 |   description: 'Report when an import declaration is not at the top level',
 8 |   level: Level.ERROR,
 9 |   create(walker, report) {
10 |     function check(node: grammar.Node, parents: grammar.Node[]): void {
11 |       if (parents[parents.length - 1].type !== grammar.SyntacticToken.PROGRAM) {
12 |         report('An import can only be at the top level', node.span);
13 |       }
14 |     }
15 | 
16 |     walker
17 |       .onEnter(grammar.FullImportDeclaration, (node, ctx) => check(node, ctx.parents))
18 |       .onEnter(grammar.PartialImportDeclaration, (node, ctx) => check(node, ctx.parents));
19 |   },
20 | };
21 | 


--------------------------------------------------------------------------------
/src/linter/rules/parser/index.ts:
--------------------------------------------------------------------------------
1 | export * from './class-at-top-level';
2 | export * from './declarations-in-class';
3 | export * from './illegal-abstract';
4 | export * from './illegal-assignment-expr';
5 | export * from './illegal-control-statement';
6 | export * from './illegal-if';
7 | export * from './illegal-super-this';
8 | export * from './import-at-top-level';
9 | 


--------------------------------------------------------------------------------
/src/parser/Precedence.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * @see https://docs.syntek.dev/spec/operator-precedence.html
 3 |  */
 4 | export enum Precedence {
 5 |   NONE,
 6 |   OP1,
 7 |   OP2,
 8 |   OP3,
 9 |   OP4,
10 |   OP5,
11 |   OP6,
12 |   OP7,
13 |   OP8,
14 |   OP9,
15 |   OP10,
16 |   OP11,
17 | }
18 | 


--------------------------------------------------------------------------------
/src/parser/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tokenizer';
2 | export * from './Parser';
3 | 


--------------------------------------------------------------------------------
/src/parser/internal/declarations/classDecl.ts:
--------------------------------------------------------------------------------
  1 | import {
  2 |   Node, Token, LexicalToken,
  3 |   ClassDeclaration,
  4 |   Constructor, ClassProp, GenericParam, VariableType,
  5 | } from '../../../grammar';
  6 | 
  7 | import { Parser } from '../..';
  8 | import { Span } from '../../../position';
  9 | import {
 10 |   matchGenericParams, matchTypeDecl, matchParamList, matchBlock,
 11 | } from '../../parse-utils';
 12 | 
 13 | export function classDecl(parser: Parser): Node {
 14 |   const isAbstract = parser.previous().type === LexicalToken.ABSTRACT;
 15 | 
 16 |   let classToken: Token;
 17 |   if (isAbstract) {
 18 |     parser.ignoreNewline();
 19 | 
 20 |     classToken = parser.consume(LexicalToken.CLASS, "Expected 'class' after 'abstract'");
 21 |   } else {
 22 |     classToken = parser.previous();
 23 |   }
 24 | 
 25 |   parser.ignoreNewline();
 26 | 
 27 |   const identifier = parser.consume(LexicalToken.IDENTIFIER, "Expected an identifier after 'class'", (error) => {
 28 |     error.info('Add an identifier after this class', classToken.span);
 29 |   });
 30 | 
 31 |   let genericParams: GenericParam[] = [];
 32 |   if (parser.matchIgnoreNewline(LexicalToken.LT)) {
 33 |     parser.ignoreNewline();
 34 | 
 35 |     genericParams = matchGenericParams(parser);
 36 |   }
 37 | 
 38 |   const extend: VariableType[] = [];
 39 |   if (parser.matchIgnoreNewline(LexicalToken.EXTENDS)) {
 40 |     do {
 41 |       parser.ignoreNewline();
 42 | 
 43 |       extend.push(matchTypeDecl(parser));
 44 |     } while (parser.matchIgnoreNewline(LexicalToken.COMMA));
 45 |   }
 46 | 
 47 |   parser.ignoreNewline();
 48 |   parser.consume(LexicalToken.L_BRACE, "Expected '{'");
 49 | 
 50 |   const constructors: Constructor[] = [];
 51 |   const staticBody: ClassProp[] = [];
 52 |   const instanceBody: ClassProp[] = [];
 53 |   while (!parser.matchIgnoreNewline(LexicalToken.R_BRACE)) {
 54 |     let abstract = false;
 55 |     let isStatic = false;
 56 | 
 57 |     while (parser.matchIgnoreNewline(LexicalToken.ABSTRACT, LexicalToken.STATIC)) {
 58 |       if (parser.previous().type === LexicalToken.ABSTRACT) {
 59 |         abstract = true;
 60 |       } else {
 61 |         isStatic = true;
 62 |       }
 63 |     }
 64 | 
 65 |     // Match constructor
 66 |     if (parser.matchIgnoreNewline(LexicalToken.NEW)) {
 67 |       const newSpan = parser.previous().span;
 68 |       parser.ignoreNewline();
 69 | 
 70 |       parser.consume(LexicalToken.L_PAR, "Expected '(' after 'new'", (error) => {
 71 |         error.info("Add '(' after this identifier", identifier.span);
 72 |       });
 73 | 
 74 |       const params = matchParamList(parser);
 75 |       const body = matchBlock(parser);
 76 | 
 77 |       constructors.push(new Constructor(
 78 |         params,
 79 |         body,
 80 |         new Span(newSpan.start, parser.previous().span.end),
 81 |       ));
 82 |     } else {
 83 |       parser.ignoreNewline();
 84 |       const decl = parser.declaration();
 85 |       const prop = new ClassProp(decl, abstract, isStatic, decl.span);
 86 | 
 87 |       if (isStatic) {
 88 |         staticBody.push(prop);
 89 |       } else {
 90 |         instanceBody.push(prop);
 91 |       }
 92 |     }
 93 |   }
 94 | 
 95 |   return new ClassDeclaration(
 96 |     isAbstract,
 97 |     identifier,
 98 |     genericParams,
 99 |     extend,
100 |     constructors,
101 |     staticBody,
102 |     instanceBody,
103 |     new Span(classToken.span.start, parser.previous().span.end),
104 |   );
105 | }
106 | 


--------------------------------------------------------------------------------
/src/parser/internal/declarations/functionDecl.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, LexicalToken,
 3 |   FunctionDeclaration, EmptyFunctionDeclaration,
 4 |   GenericParam, VariableType,
 5 | } from '../../../grammar';
 6 | 
 7 | import { Parser } from '../..';
 8 | import { Span } from '../../../position';
 9 | import {
10 |   matchGenericParams, matchTypeDecl, matchParamList, matchBlock,
11 | } from '../../parse-utils';
12 | 
13 | export function functionDecl(parser: Parser): Node {
14 |   const functionSpan = parser.previous().span;
15 | 
16 |   let genericParams: GenericParam[] = [];
17 |   if (parser.matchIgnoreNewline(LexicalToken.LT)) {
18 |     parser.ignoreNewline();
19 | 
20 |     genericParams = matchGenericParams(parser);
21 |   }
22 | 
23 |   parser.ignoreNewline();
24 | 
25 |   const identifier = parser.consume(LexicalToken.IDENTIFIER, "Expected an identifier after 'function'", (error) => {
26 |     error.info('Add an identifier after this function', functionSpan);
27 |   });
28 | 
29 |   parser.ignoreNewline();
30 | 
31 |   parser.consume(LexicalToken.L_PAR, "Expected '(' after the function name", (error) => {
32 |     error.info("Add '(' after this identifier", identifier.span);
33 |   });
34 |   const params = matchParamList(parser);
35 | 
36 |   let returnType: VariableType | null = null;
37 |   if (parser.matchIgnoreNewline(LexicalToken.COLON)) {
38 |     parser.ignoreNewline();
39 | 
40 |     returnType = matchTypeDecl(parser);
41 |   }
42 | 
43 |   if (parser.checkIgnoreNewline(LexicalToken.L_BRACE)) {
44 |     const body = matchBlock(parser);
45 | 
46 |     return new FunctionDeclaration(
47 |       identifier,
48 |       genericParams,
49 |       params,
50 |       returnType,
51 |       body,
52 |       new Span(functionSpan.start, parser.previous().span.end),
53 |     );
54 |   }
55 | 
56 |   return new EmptyFunctionDeclaration(
57 |     identifier,
58 |     genericParams,
59 |     params,
60 |     returnType,
61 |     new Span(functionSpan.start, parser.previous().span.end),
62 |   );
63 | }
64 | 


--------------------------------------------------------------------------------
/src/parser/internal/declarations/importDecl.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token, LexicalToken,
 3 |   FullImportDeclaration, PartialImportDeclaration, ImportExpose,
 4 | } from '../../../grammar';
 5 | 
 6 | import { Parser } from '../..';
 7 | import { Span } from '../../../position';
 8 | 
 9 | export function importDecl(parser: Parser): Node {
10 |   const importSpan = parser.previous().span;
11 |   const path: Token[] = [];
12 | 
13 |   do {
14 |     parser.ignoreNewline();
15 | 
16 |     path.push(parser.consume(LexicalToken.IDENTIFIER, "Expected an identifier after 'import'"));
17 |   } while (
18 |     // import std.math.{
19 |     //                ^^
20 |     parser.matchIgnoreNewline(LexicalToken.DOT) && !parser.checkIgnoreNewline(LexicalToken.L_BRACE)
21 |   );
22 | 
23 |   // Expose
24 |   if (parser.matchIgnoreNewline(LexicalToken.L_BRACE)) {
25 |     const expose: ImportExpose[] = [];
26 | 
27 |     do {
28 |       parser.ignoreNewline();
29 | 
30 |       const value = parser.consume(LexicalToken.IDENTIFIER, 'Expected an identifier');
31 |       let rename: Token | undefined;
32 | 
33 |       if (parser.matchIgnoreNewline(LexicalToken.AS)) {
34 |         parser.ignoreNewline();
35 | 
36 |         rename = parser.consume(LexicalToken.IDENTIFIER, "Expected identifier after 'as'");
37 |       }
38 | 
39 |       expose.push(new ImportExpose(
40 |         value,
41 |         rename,
42 |         new Span(
43 |           value.span.start,
44 |           rename ? rename.span.end : value.span.end,
45 |         ),
46 |       ));
47 |     } while (parser.matchIgnoreNewline(LexicalToken.COMMA));
48 | 
49 |     parser.ignoreNewline();
50 |     parser.consume(LexicalToken.R_BRACE, "Expected '}'");
51 | 
52 |     return new PartialImportDeclaration(
53 |       path,
54 |       expose,
55 |       new Span(importSpan.start, parser.previous().span.end),
56 |     );
57 |   }
58 | 
59 |   let rename: Token | undefined;
60 |   if (parser.matchIgnoreNewline(LexicalToken.AS)) {
61 |     parser.ignoreNewline();
62 | 
63 |     rename = parser.consume(LexicalToken.IDENTIFIER, "Expected identifier after 'as'");
64 |   }
65 | 
66 |   return new FullImportDeclaration(
67 |     path,
68 |     rename,
69 |     new Span(importSpan.start, parser.previous().span.end),
70 |   );
71 | }
72 | 


--------------------------------------------------------------------------------
/src/parser/internal/declarations/variableDecl.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, LexicalToken, VariableType,
 3 |   EmptyVariableDeclaration, VariableDeclaration,
 4 | } from '../../../grammar';
 5 | 
 6 | import { Parser } from '../..';
 7 | import { Span } from '../../../position';
 8 | import { matchTypeDecl } from '../../parse-utils';
 9 | 
10 | export function variableDecl(parser: Parser): Node {
11 |   const start = parser.peek().span.start;
12 | 
13 |   parser.ignoreNewline();
14 | 
15 |   const identifier = parser.consume(LexicalToken.IDENTIFIER, 'Expected an identifier after "var"');
16 | 
17 |   let variableType: VariableType | null = null;
18 |   if (parser.matchIgnoreNewline(LexicalToken.COLON)) {
19 |     parser.ignoreNewline();
20 | 
21 |     variableType = matchTypeDecl(parser);
22 |   }
23 | 
24 |   // If it's followed with an assignment return a variable declaration
25 |   if (parser.matchIgnoreNewline(LexicalToken.EQUAL)) {
26 |     parser.ignoreNewline();
27 | 
28 |     const expr = parser.expression("Expected an expression after '='", (error) => {
29 |       error.info('Add an expression after this =', parser.previous().span);
30 |     });
31 | 
32 |     return new VariableDeclaration(
33 |       identifier,
34 |       variableType,
35 |       expr,
36 |       new Span(start, parser.previous().span.end),
37 |     );
38 |   }
39 | 
40 |   if (!variableType) {
41 |     throw parser.error('Empty variable declaration must have a type', new Span(start, parser.previous().span.end));
42 |   }
43 | 
44 |   return new EmptyVariableDeclaration(
45 |     identifier,
46 |     variableType,
47 |     new Span(start, parser.previous().span.end),
48 |   );
49 | }
50 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/arrayExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token, LexicalToken, ArrayExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | import { matchExpressionList } from '../../parse-utils';
 8 | 
 9 | export function arrayExpr(parser: Parser, prefix: Token): Node {
10 |   const start = prefix.span.start;
11 | 
12 |   const content = matchExpressionList(parser, LexicalToken.R_SQB);
13 |   return new ArrayExpression(content, new Span(start, parser.previous().span.end));
14 | }
15 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/assignmentExpr.ts:
--------------------------------------------------------------------------------
 1 | import { Node, Token, AssignmentExpression } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function assignmentExpr(parser: Parser, left: Node, operator: Token): Node {
 7 |   parser.ignoreNewline();
 8 | 
 9 |   const value = parser.expression("Expected an expression after '='", (error) => {
10 |     error.info('Add an expression after this =', operator.span);
11 |   });
12 | 
13 |   return new AssignmentExpression(left, value, new Span(left.span.start, value.span.end));
14 | }
15 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/asyncExpr.ts:
--------------------------------------------------------------------------------
 1 | import { Node, Token, AsyncExpression } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function asyncExpr(parser: Parser, operator: Token): Node {
 7 |   parser.ignoreNewline();
 8 | 
 9 |   const right = parser.parsePrecedence(parser.getRule(operator.type).precedence, "Expected an expression after 'async'", (error) => {
10 |     error.info('Add an expression after this async', operator.span);
11 |   });
12 | 
13 |   return new AsyncExpression(right, new Span(operator.span.start, right.span.end));
14 | }
15 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/binaryExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token, LexicalToken, BinaryExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | 
 8 | export function binaryExpr(parser: Parser, left: Node, operator: Token): Node {
 9 |   parser.ignoreNewline();
10 | 
11 |   const rule = parser.getRule(operator.type);
12 |   const exponentation = operator.type === LexicalToken.CARET;
13 | 
14 |   // Exponentation is right-associative
15 |   const right = parser.parsePrecedence(
16 |     rule.precedence + (exponentation ? 0 : 1),
17 |     `Expected an expression after '${operator.lexeme}'`,
18 |     (error) => {
19 |       error.info('Add an expression after this operator', operator.span);
20 |     },
21 |   );
22 | 
23 |   return new BinaryExpression(left, operator, right, new Span(left.span.start, right.span.end));
24 | }
25 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/callExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, LexicalToken, VariableType, CallExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | import { matchExpressionList } from '../../parse-utils';
 8 | 
 9 | export function callExpr(parser: Parser, left: Node): Node {
10 |   const genericArgs: VariableType[] = [];
11 |   // if (infix.type === LexicalToken.LT) {
12 |   //   genericArgs = matchGenericArgs(parser);
13 |   //
14 |   //   parser.consume(LexicalToken.L_PAR, "Expected '(' after '>'");
15 |   // }
16 | 
17 |   const params = matchExpressionList(parser, LexicalToken.R_PAR);
18 | 
19 |   return new CallExpression(
20 |     left,
21 |     genericArgs,
22 |     params,
23 |     new Span(left.span.start, parser.previous().span.end),
24 |   );
25 | }
26 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/ifExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, LexicalToken, IfExpression, ElseExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | import { matchBlock } from '../../parse-utils';
 8 | 
 9 | export function ifExpr(parser: Parser): Node {
10 |   const ifSpan = parser.previous().span;
11 | 
12 |   parser.ignoreNewline();
13 | 
14 |   const condition = parser.expression("Expected a condition after 'if'", (error) => {
15 |     error.info('Add an expression after this if', ifSpan);
16 |   });
17 | 
18 |   const body = matchBlock(parser);
19 | 
20 |   let elseClause: Node | null = null;
21 |   if (parser.matchIgnoreNewline(LexicalToken.ELSE)) {
22 |     if (parser.matchIgnoreNewline(LexicalToken.IF)) {
23 |       elseClause = ifExpr(parser);
24 |     } else {
25 |       // eslint-disable-next-line @typescript-eslint/no-use-before-define
26 |       elseClause = elseExpr(parser);
27 |     }
28 |   }
29 | 
30 |   return new IfExpression(
31 |     condition,
32 |     body,
33 |     elseClause,
34 |     new Span(ifSpan.start, parser.previous().span.end),
35 |   );
36 | }
37 | 
38 | function elseExpr(parser: Parser): Node {
39 |   const elseSpan = parser.previous().span;
40 | 
41 |   const body = matchBlock(parser);
42 | 
43 |   return new ElseExpression(body, new Span(elseSpan.start, parser.previous().span.end));
44 | }
45 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/indexExpr.ts:
--------------------------------------------------------------------------------
 1 | import { Node, LexicalToken, IndexExpression } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function indexExpr(parser: Parser, left: Node): Node {
 7 |   parser.ignoreNewline();
 8 | 
 9 |   const expr = parser.expression("Expected an expression after '['", (error) => {
10 |     error.info('Add an expression after this [', left.span);
11 |   });
12 | 
13 |   parser.ignoreNewline();
14 | 
15 |   parser.consume(LexicalToken.R_SQB, "Expected ']' after the expression", (error) => {
16 |     error.info("Add a ']' after this expression", expr.span);
17 |   });
18 | 
19 |   return new IndexExpression(left, expr, new Span(left.span.start, parser.previous().span.end));
20 | }
21 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/instanceofExpr.ts:
--------------------------------------------------------------------------------
 1 | import { Node, InstanceofExpression } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | import { matchTypeDecl } from '../../parse-utils';
 7 | 
 8 | export function instanceofExpr(parser: Parser, left: Node): Node {
 9 |   parser.ignoreNewline();
10 | 
11 |   const right = matchTypeDecl(parser);
12 | 
13 |   return new InstanceofExpression(left, right, new Span(left.span.start, right.span.end));
14 | }
15 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/memberExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token, LexicalToken, MemberExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | 
 8 | export function memberExpr(parser: Parser, left: Node, operator: Token): Node {
 9 |   parser.ignoreNewline();
10 | 
11 |   const property = parser.match(LexicalToken.IDENTIFIER, LexicalToken.SUPER);
12 | 
13 |   if (!property) {
14 |     throw parser.error("Expected an identifier or 'super' after '.'", parser.peek().span, (error) => {
15 |       error.info("Add an identifier or 'super' after this '.'", operator.span);
16 |     });
17 |   }
18 | 
19 |   return new MemberExpression(
20 |     left,
21 |     parser.previous(),
22 |     new Span(left.span.start, parser.previous().span.end),
23 |   );
24 | }
25 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/newExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token, LexicalToken, VariableType, NewExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | import { matchGenericArgs, matchExpressionList, matchVarLoc } from '../../parse-utils';
 8 | 
 9 | export function newExpr(parser: Parser, prefix: Token): Node {
10 |   const start = prefix.span.start;
11 | 
12 |   parser.ignoreNewline();
13 |   const object = matchVarLoc(parser);
14 |   parser.ignoreNewline();
15 | 
16 |   let genericArgs: VariableType[] = [];
17 |   if (parser.match(LexicalToken.LT)) {
18 |     genericArgs = matchGenericArgs(parser);
19 |     parser.ignoreNewline();
20 |   }
21 | 
22 |   parser.consume(LexicalToken.L_PAR, "Expected '(' after the class", (error) => {
23 |     error.info("Add a '(' after this class", object.span);
24 |   });
25 |   const params = matchExpressionList(parser, LexicalToken.R_PAR);
26 | 
27 |   return new NewExpression(
28 |     object,
29 |     genericArgs,
30 |     params,
31 |     new Span(start, parser.previous().span.end),
32 |   );
33 | }
34 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/unaryExpr.ts:
--------------------------------------------------------------------------------
 1 | import { Node, Token, UnaryExpression } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function unaryExpr(parser: Parser, operator: Token): Node {
 7 |   parser.ignoreNewline();
 8 | 
 9 |   const right = parser.parsePrecedence(
10 |     parser.getRule(operator.type).precedence + 1,
11 |     `Expected an expression after '${operator.lexeme}'`,
12 |     (error) => {
13 |       error.info('Add an expression after this operator', operator.span);
14 |     },
15 |   );
16 | 
17 |   return new UnaryExpression(operator, right, new Span(operator.span.start, right.span.end));
18 | }
19 | 


--------------------------------------------------------------------------------
/src/parser/internal/expressions/wrappedExpr.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token, LexicalToken, WrappedExpression,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | 
 8 | export function wrappedExpr(parser: Parser, prefix: Token): Node {
 9 |   const start = prefix.span.start;
10 | 
11 |   const ignoreAllNewlines = parser.ignoreAllNewlines;
12 |   parser.ignoreAllNewlines = true; // eslint-disable-line no-param-reassign
13 | 
14 |   parser.ignoreNewline();
15 | 
16 |   const expr = parser.expression("Expected an expression after '('", (error) => {
17 |     error.info('Add an expression after this (', prefix.span);
18 |   });
19 | 
20 |   parser.ignoreNewline();
21 | 
22 |   parser.consume(LexicalToken.R_PAR, "Expected ')' after expression", (error) => {
23 |     error.info("Add a ')' after the expression", expr.span);
24 |   });
25 | 
26 |   parser.ignoreAllNewlines = ignoreAllNewlines; // eslint-disable-line no-param-reassign
27 | 
28 |   return new WrappedExpression(expr, new Span(start, parser.previous().span.end));
29 | }
30 | 


--------------------------------------------------------------------------------
/src/parser/internal/literals/identifier.ts:
--------------------------------------------------------------------------------
1 | import { Node, Token, Identifier } from '../../../grammar';
2 | 
3 | import { Parser } from '../..';
4 | 
5 | export function identifier(_parser: Parser, literal: Token): Node {
6 |   return new Identifier(literal, literal.span);
7 | }
8 | 


--------------------------------------------------------------------------------
/src/parser/internal/literals/literals.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, Token,
 3 |   NumberLiteral, StringLiteral, BooleanLiteral,
 4 | } from '../../../grammar';
 5 | 
 6 | import { Parser } from '../..';
 7 | 
 8 | export function numberLiteral(_parser: Parser, literal: Token): Node {
 9 |   return new NumberLiteral(literal);
10 | }
11 | 
12 | export function stringLiteral(_parser: Parser, literal: Token): Node {
13 |   return new StringLiteral(literal);
14 | }
15 | 
16 | export function booleanLiteral(_parser: Parser, literal: Token): Node {
17 |   return new BooleanLiteral(literal);
18 | }
19 | 


--------------------------------------------------------------------------------
/src/parser/internal/literals/superLiteral.ts:
--------------------------------------------------------------------------------
1 | import { Node, Token, Super } from '../../../grammar';
2 | 
3 | import { Parser } from '../..';
4 | 
5 | export function superLiteral(_parser: Parser, literal: Token): Node {
6 |   return new Super(literal.span);
7 | }
8 | 


--------------------------------------------------------------------------------
/src/parser/internal/literals/thisLiteral.ts:
--------------------------------------------------------------------------------
1 | import { Node, Token, This } from '../../../grammar';
2 | 
3 | import { Parser } from '../..';
4 | 
5 | export function thisLiteral(_parser: Parser, literal: Token): Node {
6 |   return new This(literal.span);
7 | }
8 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/breakStmt.ts:
--------------------------------------------------------------------------------
1 | import { Node, BreakStatement } from '../../../grammar';
2 | 
3 | import { Parser } from '../..';
4 | import { Span } from '../../../position';
5 | 
6 | export function breakStmt(parser: Parser): Node {
7 |   return new BreakStatement(new Span(parser.previous().span.start, parser.previous().span.end));
8 | }
9 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/continueStmt.ts:
--------------------------------------------------------------------------------
1 | import { Node, ContinueStatement } from '../../../grammar';
2 | 
3 | import { Parser } from '../..';
4 | import { Span } from '../../../position';
5 | 
6 | export function continueStmt(parser: Parser): Node {
7 |   return new ContinueStatement(new Span(parser.previous().span.start, parser.previous().span.end));
8 | }
9 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/expressionStmt.ts:
--------------------------------------------------------------------------------
 1 | import { Node, ExpressionStatement } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function expressionStmt(parser: Parser): Node {
 7 |   const expr = parser.expression();
 8 | 
 9 |   return new ExpressionStatement(expr, new Span(expr.span.start, parser.previous().span.end));
10 | }
11 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/forStmt.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Node, LexicalToken, ForStatement, VariableType,
 3 | } from '../../../grammar';
 4 | 
 5 | import { Parser } from '../..';
 6 | import { Span } from '../../../position';
 7 | import { matchTypeDecl, matchBlock } from '../../parse-utils';
 8 | 
 9 | export function forStmt(parser: Parser): Node {
10 |   const forSpan = parser.previous().span;
11 | 
12 |   parser.ignoreNewline();
13 | 
14 |   const identifier = parser.consume(LexicalToken.IDENTIFIER, 'Expected identifier after "for"');
15 | 
16 |   let variableType: VariableType | null = null;
17 |   if (parser.matchIgnoreNewline(LexicalToken.COLON)) {
18 |     parser.ignoreNewline();
19 | 
20 |     variableType = matchTypeDecl(parser);
21 |   }
22 | 
23 |   parser.ignoreNewline();
24 | 
25 |   const inSpan = parser.consume(LexicalToken.IN, "Expected 'in' after the variable", (/* error */) => {
26 |     // TODO: Add this back
27 |     // error.info("Add 'in' after this variable", varDecl.span);
28 |   }).span;
29 | 
30 |   parser.ignoreNewline();
31 | 
32 |   const object = parser.expression("Expected an expression after 'in'", (error) => {
33 |     error.info("Add an expression after this 'in'", inSpan);
34 |   });
35 | 
36 |   const body = matchBlock(parser);
37 | 
38 |   return new ForStatement(
39 |     identifier,
40 |     variableType,
41 |     object,
42 |     body,
43 |     new Span(forSpan.start, parser.previous().span.end),
44 |   );
45 | }
46 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/returnStmt.ts:
--------------------------------------------------------------------------------
 1 | import { Node, ReturnStatement } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function returnStmt(parser: Parser): Node {
 7 |   const start = parser.previous().span.start;
 8 | 
 9 |   let expression: Node | null = null;
10 |   if (!parser.isEOL()) {
11 |     expression = parser.expression("Expected a newline or expression after 'return'", (error) => {
12 |       error.info('Add a newline or expression after this return', parser.previous().span);
13 |     });
14 |   }
15 | 
16 |   return new ReturnStatement(expression, new Span(start, parser.previous().span.end));
17 | }
18 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/whileStmt.ts:
--------------------------------------------------------------------------------
 1 | import { Node, WhileStatement } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | import { matchBlock } from '../../parse-utils';
 6 | 
 7 | export function whileStmt(parser: Parser): Node {
 8 |   const whileSpan = parser.previous().span;
 9 | 
10 |   parser.ignoreNewline();
11 | 
12 |   const condition = parser.expression("Expected a condition after 'while'", (error) => {
13 |     error.info('Add an expression after this while', whileSpan);
14 |   });
15 | 
16 |   const body = matchBlock(parser);
17 | 
18 |   return new WhileStatement(condition, body, new Span(whileSpan.start, parser.previous().span.end));
19 | }
20 | 


--------------------------------------------------------------------------------
/src/parser/internal/statements/yieldStmt.ts:
--------------------------------------------------------------------------------
 1 | import { Node, YieldStatement } from '../../../grammar';
 2 | 
 3 | import { Parser } from '../..';
 4 | import { Span } from '../../../position';
 5 | 
 6 | export function yieldStmt(parser: Parser): Node {
 7 |   const start = parser.previous().span.start;
 8 | 
 9 |   parser.ignoreNewline();
10 | 
11 |   const expression = parser.expression("Expected an expression after 'yield'", (error) => {
12 |     error.info('Add an expression after this yield', parser.previous().span);
13 |   });
14 | 
15 |   return new YieldStatement(expression, new Span(start, parser.previous().span.end));
16 | }
17 | 


--------------------------------------------------------------------------------
/src/position/Span.ts:
--------------------------------------------------------------------------------
 1 | type LineColumn = [number, number];
 2 | 
 3 | export class Span {
 4 |   readonly start: LineColumn;
 5 | 
 6 |   readonly end: LineColumn;
 7 | 
 8 |   constructor(start: LineColumn, end: LineColumn) {
 9 |     this.start = start;
10 |     this.end = end;
11 |   }
12 | 
13 |   before(span: Span): boolean {
14 |     // Line above
15 |     if (this.end[0] < span.start[0]) {
16 |       return true;
17 |     }
18 | 
19 |     // Same line
20 |     if (this.end[0] === span.start[0]) {
21 |       // Ends before the span starts
22 |       return this.end[1] < span.start[1];
23 |     }
24 | 
25 |     // Line below
26 |     return false;
27 |   }
28 | 
29 |   after(span: Span): boolean {
30 |     // Line below
31 |     if (this.start[0] > span.end[0]) {
32 |       return true;
33 |     }
34 | 
35 |     // Same line
36 |     if (this.start[0] === span.end[0]) {
37 |       // Starts after the span ends
38 |       return this.start[1] > span.end[1];
39 |     }
40 | 
41 |     // Line above
42 |     return false;
43 |   }
44 | 
45 |   contains(span: Span): boolean {
46 |     return !this.before(span) && !this.after(span);
47 |   }
48 | }
49 | 


--------------------------------------------------------------------------------
/src/position/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Span';
2 | export * from './utils';
3 | 


--------------------------------------------------------------------------------
/src/position/utils.ts:
--------------------------------------------------------------------------------
 1 | import { Span } from '.';
 2 | 
 3 | export function getSpanFromString(string: string, span: Span): string {
 4 |   const lines = string.split('\n');
 5 |   const remaining = lines
 6 |     .slice(span.start[0], span.end[0] + 1)
 7 |     .join('\n');
 8 | 
 9 |   return remaining.slice(
10 |     span.start[1],
11 |     remaining.length - lines[span.end[0]].length + span.end[1],
12 |   );
13 | }
14 | 


--------------------------------------------------------------------------------
/src/symbols/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './scopes/BlockScope';
 2 | export * from './scopes/ClassScope';
 3 | export * from './scopes/FunctionScope';
 4 | export * from './scopes/GlobalScope';
 5 | export * from './scopes/ProgramScope';
 6 | export * from './scopes/Scope';
 7 | export * from './scopes/StaticScope';
 8 | 
 9 | export * from './symbols/SymbolEntry';
10 | export * from './symbols/SymbolTable';
11 | 
12 | export * from './mangle';
13 | 


--------------------------------------------------------------------------------
/src/symbols/mangle.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   FunctionDeclaration, EmptyFunctionDeclaration,
 3 |   Identifier, MemberExpression,
 4 |   Constructor, GenericParam,
 5 |   VariableType, SyntacticToken,
 6 | } from '../grammar';
 7 | 
 8 | type Func = FunctionDeclaration | EmptyFunctionDeclaration;
 9 | 
10 | function variableTypeObjectToString(node: Identifier | MemberExpression): string {
11 |   if (node.type === SyntacticToken.IDENTIFIER) {
12 |     return (node as Identifier).lexeme;
13 |   }
14 | 
15 |   const expr = node as MemberExpression;
16 |   return `${variableTypeObjectToString(expr.object as any)}.${expr.property.lexeme}`;
17 | }
18 | 
19 | function mangleVariableType(type: VariableType, generics: GenericParam[]): string {
20 |   let mangle = variableTypeObjectToString(type.object);
21 | 
22 |   // Find a generic that equals the mangle
23 |   const genericType = generics.find(generic => generic.identifier.lexeme === mangle);
24 | 
25 |   // If there is such a generic, change the mangle
26 |   if (genericType) {
27 |     // If the generic extends a type, use that
28 |     // Otherwise default to Object
29 |     //
30 |     // function  foo(t: T) -> function foo(t: Object)
31 |     // function  foo(t: T) -> function foo(t: E)
32 |     if (genericType.extend) {
33 |       mangle = mangleVariableType(genericType.extend, generics);
34 |     } else {
35 |       mangle = 'Object';
36 |     }
37 |   }
38 | 
39 |   if (type.generics.length) {
40 |     mangle += '<';
41 | 
42 |     type.generics.forEach((generic) => {
43 |       mangle += mangleVariableType(generic, generics);
44 |     });
45 | 
46 |     mangle += '>';
47 |   }
48 | 
49 |   if (type.arrayDepth) {
50 |     mangle += '['.repeat(type.arrayDepth);
51 |   }
52 | 
53 |   return mangle;
54 | }
55 | 
56 | export function mangleFunctionName(node: Func): string {
57 |   let mangle = `${node.identifier.lexeme}-`;
58 | 
59 |   mangle += node.params
60 |     .map(param => mangleVariableType(param.variableType, node.genericParams))
61 |     .join('-');
62 | 
63 |   return mangle;
64 | }
65 | 
66 | export function mangleConstructor(node: Constructor, generics: GenericParam[]): string {
67 |   return node.params
68 |     .map(param => mangleVariableType(param.variableType, generics))
69 |     .join('-');
70 | }
71 | 


--------------------------------------------------------------------------------
/src/symbols/scopes/BlockScope.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../grammar';
 2 | 
 3 | import { Scope } from './Scope';
 4 | import { SymbolEntry } from '../symbols/SymbolEntry';
 5 | 
 6 | export class BlockScope extends Scope {
 7 |   build(): void {
 8 |     switch (this.node.type) {
 9 |       // Expressions
10 |       case grammar.SyntacticToken.IF_EXPR: {
11 |         const expr = this.node as grammar.IfExpression;
12 |         expr.body.forEach(node => this.add(node));
13 |         break;
14 |       }
15 | 
16 |       case grammar.SyntacticToken.ELSE_EXPR: {
17 |         const expr = this.node as grammar.ElseExpression;
18 |         expr.body.forEach(node => this.add(node));
19 |         break;
20 |       }
21 | 
22 |       // Statements
23 |       case grammar.SyntacticToken.FOR_STMT: {
24 |         const stmt = this.node as grammar.ForStatement;
25 | 
26 |         this.table.add(stmt.identifier.lexeme, new SymbolEntry(stmt, this));
27 |         stmt.body.forEach(node => this.add(node));
28 | 
29 |         break;
30 |       }
31 | 
32 |       case grammar.SyntacticToken.WHILE_STMT: {
33 |         const stmt = this.node as grammar.WhileStatement;
34 |         stmt.body.forEach(node => this.add(node));
35 |         break;
36 |       }
37 | 
38 |       default:
39 |         throw new Error(`Block scope can't contain node of type ${grammar.SyntacticToken[this.node.type]}`);
40 |     }
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/src/symbols/scopes/ClassScope.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../grammar';
 2 | 
 3 | import { Scope } from './Scope';
 4 | import { StaticScope } from './StaticScope';
 5 | import { SymbolTable } from '../symbols/SymbolTable';
 6 | import { SymbolEntry } from '../symbols/SymbolEntry';
 7 | 
 8 | export class ClassScope extends Scope {
 9 |   readonly staticScope: StaticScope;
10 | 
11 |   readonly generics = new SymbolTable();
12 | 
13 |   constructor(node: grammar.ClassDeclaration, parent?: Scope) {
14 |     super(node, parent);
15 | 
16 |     this.staticScope = new StaticScope(node, parent);
17 |     this.staticScope.build();
18 |   }
19 | 
20 |   getSymbol(name: string): SymbolEntry {
21 |     const generic = this.generics.get(name);
22 |     if (generic) {
23 |       return generic;
24 |     }
25 | 
26 |     if (this.parent) {
27 |       return this.parent.getSymbol(name);
28 |     }
29 | 
30 |     throw new Error(`No symbol with the name ${name}`);
31 |   }
32 | 
33 |   hasSymbol(name: string): boolean {
34 |     if (this.generics.has(name)) {
35 |       return true;
36 |     }
37 | 
38 |     if (this.parent) {
39 |       return this.parent.hasSymbol(name);
40 |     }
41 | 
42 |     return false;
43 |   }
44 | 
45 |   hasFunction(name: string): boolean {
46 |     if (this.parent) {
47 |       return this.parent.hasFunction(name);
48 |     }
49 | 
50 |     return false;
51 |   }
52 | 
53 |   build(): void {
54 |     this.node.genericParams.forEach((generic) => {
55 |       const entry = new SymbolEntry(generic, this);
56 | 
57 |       // Generics are stored in the general table, and a map for generics, because
58 |       // a generic can be acquired directly
59 |       this.table.add(generic.identifier.lexeme, entry);
60 |       this.generics.add(generic.identifier.lexeme, entry);
61 |     });
62 | 
63 |     this.node.instanceBody.forEach(prop => this.add(prop.value));
64 |   }
65 | }
66 | 


--------------------------------------------------------------------------------
/src/symbols/scopes/FunctionScope.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../grammar';
 2 | 
 3 | import { Scope } from './Scope';
 4 | import { SymbolEntry } from '../symbols/SymbolEntry';
 5 | 
 6 | type Func = grammar.FunctionDeclaration | grammar.EmptyFunctionDeclaration;
 7 | 
 8 | export class FunctionScope extends Scope {
 9 |   build(): void {
10 |     this.node.genericParams.forEach((generic) => {
11 |       this.table.add(generic.identifier.lexeme, new SymbolEntry(generic, this));
12 |     });
13 | 
14 |     this.node.params.forEach(param => this.add(param));
15 | 
16 |     if (this.node.type === grammar.SyntacticToken.FUNCTION_DECL) {
17 |       (this.node as grammar.FunctionDeclaration).body.forEach(node => this.add(node));
18 |     }
19 |   }
20 | }
21 | 


--------------------------------------------------------------------------------
/src/symbols/scopes/GlobalScope.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../grammar';
 2 | 
 3 | import { Scope } from './Scope';
 4 | import { Span } from '../../position';
 5 | import { SymbolEntry } from '../symbols/SymbolEntry';
 6 | 
 7 | export class GlobalScope extends Scope {
 8 |   private static readonly span = new Span([0, 0], [0, 0])
 9 | 
10 |   constructor() {
11 |     super(new grammar.NativeNode(new grammar.Token(grammar.LexicalToken.NATIVE_TOKEN, 'GlobalScope', GlobalScope.span)));
12 | 
13 |     this.build();
14 |   }
15 | 
16 |   build(): void {
17 |     [
18 |       // Functions
19 |       'print',
20 |       'range',
21 | 
22 |       // Classes
23 |       'Object',
24 |       'Number',
25 |       'String',
26 |       'Boolean',
27 |       'Optional',
28 |       'Array',
29 |       'Function',
30 |       'VoidFunction',
31 |       'Error',
32 |       'Promise',
33 |     ].forEach((builtin) => {
34 |       const identifier = new grammar.Token(
35 |         grammar.LexicalToken.NATIVE_TOKEN,
36 |         builtin,
37 |         GlobalScope.span,
38 |       );
39 | 
40 |       this.table.add(builtin, new SymbolEntry(new grammar.NativeNode(identifier), this));
41 |     });
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/src/symbols/scopes/ProgramScope.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../grammar';
 2 | 
 3 | import { Scope } from './Scope';
 4 | import { GlobalScope } from './GlobalScope';
 5 | 
 6 | export class ProgramScope extends Scope {
 7 |   private static readonly globalScope = new GlobalScope();
 8 | 
 9 |   constructor(node: grammar.Program) {
10 |     super(node, ProgramScope.globalScope);
11 |   }
12 | 
13 |   build(): void {
14 |     this.node.body.forEach(node => this.add(node));
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/symbols/scopes/StaticScope.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../grammar';
 2 | 
 3 | import { Scope } from './Scope';
 4 | import { SymbolEntry } from '../symbols/SymbolEntry';
 5 | 
 6 | export class StaticScope extends Scope {
 7 |   getSymbol(name: string): SymbolEntry {
 8 |     if (this.parent) {
 9 |       return this.parent.getSymbol(name);
10 |     }
11 | 
12 |     throw new Error(`No symbol with the name ${name}`);
13 |   }
14 | 
15 |   hasSymbol(name: string): boolean {
16 |     if (this.parent) {
17 |       return this.parent.hasSymbol(name);
18 |     }
19 | 
20 |     return false;
21 |   }
22 | 
23 |   hasFunction(name: string): boolean {
24 |     if (this.parent) {
25 |       return this.parent.hasFunction(name);
26 |     }
27 | 
28 |     return false;
29 |   }
30 | 
31 |   build(): void {
32 |     this.node.staticBody.forEach(prop => this.add(prop.value));
33 |   }
34 | }
35 | 


--------------------------------------------------------------------------------
/src/symbols/symbols/SymbolEntry.ts:
--------------------------------------------------------------------------------
 1 | import { Type } from '../../types';
 2 | import { Scope } from '../scopes/Scope';
 3 | import { Node, DeclarationNode } from '../../grammar';
 4 | 
 5 | export class SymbolEntry {
 6 |   readonly node: DeclarationNode;
 7 | 
 8 |   readonly scope: Scope;
 9 | 
10 |   readonly refs: Node[] = [];
11 | 
12 |   private type?: Type;
13 | 
14 |   constructor(node: DeclarationNode, scope: Scope) {
15 |     this.node = node;
16 |     this.scope = scope;
17 |   }
18 | 
19 |   getType(): Type {
20 |     if (this.type) {
21 |       return this.type;
22 |     }
23 | 
24 |     throw new Error('Type is not set');
25 |   }
26 | 
27 |   setType(type: Type): void {
28 |     if (this.type) {
29 |       throw new Error('Can not change type');
30 |     }
31 | 
32 |     this.type = type;
33 |   }
34 | 
35 |   hasType(): boolean {
36 |     return !!this.type;
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/src/symbols/symbols/SymbolTable.ts:
--------------------------------------------------------------------------------
 1 | import { SymbolEntry } from './SymbolEntry';
 2 | import { FunctionDeclaration, EmptyFunctionDeclaration } from '../../grammar';
 3 | 
 4 | type Func = FunctionDeclaration | EmptyFunctionDeclaration;
 5 | 
 6 | export class SymbolTable {
 7 |   readonly symbols = new Map();
 8 | 
 9 |   readonly functions = new Map();
10 | 
11 |   get(name: string): SymbolEntry | undefined {
12 |     return this.symbols.get(name);
13 |   }
14 | 
15 |   add(name: string, entry: SymbolEntry): void {
16 |     if (!this.symbols.has(name)) {
17 |       this.symbols.set(name, entry);
18 |     }
19 |   }
20 | 
21 |   has(name: string): boolean {
22 |     return this.symbols.has(name);
23 |   }
24 | 
25 |   addFunction(node: Func): void {
26 |     const functions = this.functions.get(node.identifier.lexeme);
27 | 
28 |     if (functions) {
29 |       functions.push(node);
30 |     } else {
31 |       this.functions.set(node.identifier.lexeme, [node]);
32 |     }
33 |   }
34 | 
35 |   hasFunction(name: string): boolean {
36 |     return this.functions.has(name);
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/src/types/TypeChecker.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../grammar';
 2 | 
 3 | import { Span } from '../position';
 4 | import { Type } from './types/Type';
 5 | import { Scope, SymbolEntry } from '../symbols';
 6 | import { Diagnostic, Level, ErrorHandler } from '../diagnostic';
 7 | 
 8 | import { typeCheckRules } from './type-check-rules';
 9 | 
10 | export class TypeChecker {
11 |   readonly scope: Scope;
12 | 
13 |   private readonly diagnostics: Diagnostic[] = [];
14 | 
15 |   constructor(scope: Scope) {
16 |     this.scope = scope;
17 |   }
18 | 
19 |   check(): Diagnostic[] {
20 |     try {
21 |       this.handleScope(this.scope);
22 |     } catch (err) {
23 |       console.error(err);
24 |     }
25 | 
26 |     return this.diagnostics;
27 |   }
28 | 
29 |   handleScope(scope: Scope): void {
30 |     scope.table.symbols.forEach(symbol => this.handleSymbol(symbol));
31 |     scope.scopes.forEach(nested => this.handleScope(nested));
32 |   }
33 | 
34 |   handleSymbol(symbol: SymbolEntry): void {
35 |     symbol.setType(this.getType(symbol.node));
36 |   }
37 | 
38 |   getType(node: grammar.Node): Type {
39 |     return typeCheckRules[node.type](node, this);
40 |   }
41 | 
42 |   error(msg: string, span: Span, errorHandler?: ErrorHandler): void {
43 |     const diagnostic = new Diagnostic(Level.ERROR, 'TypeChecker', msg, span);
44 |     this.diagnostics.push(diagnostic);
45 | 
46 |     if (errorHandler) {
47 |       errorHandler(diagnostic);
48 |     }
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './types/ArrayType';
 2 | export * from './types/BooleanType';
 3 | export * from './types/ClassType';
 4 | export * from './types/FunctionType';
 5 | export * from './types/ImportType';
 6 | export * from './types/NumberType';
 7 | export * from './types/ObjectType';
 8 | export * from './types/StringType';
 9 | export * from './types/Type';
10 | export * from './types/UnknownType';
11 | 
12 | export * from './TypeChecker';
13 | 


--------------------------------------------------------------------------------
/src/types/internal/declarations/classDecl.ts:
--------------------------------------------------------------------------------
1 | import * as grammar from '../../../grammar';
2 | 
3 | import { Type } from '../../types/Type';
4 | import { ClassType } from '../../types/ClassType';
5 | 
6 | export function classDecl(decl: grammar.ClassDeclaration): Type {
7 |   return new ClassType(decl);
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/internal/declarations/functionDecl.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { Type } from '../../types/Type';
 4 | import { FunctionType } from '../../types/FunctionType';
 5 | 
 6 | type Func = grammar.FunctionDeclaration | grammar.EmptyFunctionDeclaration;
 7 | 
 8 | export function functionDecl(decl: Func): Type {
 9 |   return new FunctionType(decl);
10 | }
11 | 


--------------------------------------------------------------------------------
/src/types/internal/declarations/importDecl.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { Type } from '../../types/Type';
 4 | import { ImportType } from '../../types/ImportType';
 5 | 
 6 | type Import = grammar.FullImportDeclaration | grammar.PartialImportDeclaration;
 7 | 
 8 | export function importDecl(decl: Import): Type {
 9 |   return new ImportType(decl);
10 | }
11 | 


--------------------------------------------------------------------------------
/src/types/internal/declarations/variableDecl.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { Type } from '../../types/Type';
 4 | import { TypeChecker } from '../../TypeChecker';
 5 | 
 6 | type Variable = grammar.VariableDeclaration | grammar.EmptyVariableDeclaration;
 7 | 
 8 | export function variableDecl(decl: Variable, checker: TypeChecker): Type {
 9 |   if (decl instanceof grammar.VariableDeclaration) {
10 |     if (decl.variableType) {
11 |       const expected = checker.getType(decl.variableType);
12 |       const value = checker.getType(decl.value);
13 | 
14 |       if (!value.matches(expected)) {
15 |         checker.error(`Expected ${value} to be ${expected}`, decl.span);
16 |       }
17 | 
18 |       return expected;
19 |     }
20 | 
21 |     return checker.getType(decl.value);
22 |   }
23 | 
24 |   return checker.getType(decl.variableType);
25 | }
26 | 


--------------------------------------------------------------------------------
/src/types/internal/expressions/unaryExpr.ts:
--------------------------------------------------------------------------------
 1 | import * as grammar from '../../../grammar';
 2 | 
 3 | import { Type } from '../../types/Type';
 4 | import { TypeChecker } from '../../TypeChecker';
 5 | 
 6 | import { NumberType } from '../../types/NumberType';
 7 | import { BooleanType } from '../../types/BooleanType';
 8 | 
 9 | export function unaryExpr(expr: grammar.UnaryExpression, checker: TypeChecker): Type {
10 |   const right = checker.getType(expr.right);
11 | 
12 |   // -
13 |   if (expr.operator.type === grammar.LexicalToken.MINUS) {
14 |     if (!(right instanceof NumberType)) {
15 |       checker.error(`Expected right side of '${expr.operator.lexeme}' to be a number`, expr.right.span);
16 |     }
17 | 
18 |     return new NumberType(expr);
19 |   }
20 | 
21 |   // !
22 |   if (!(right instanceof BooleanType)) {
23 |     checker.error(`Expected right side of '${expr.operator.lexeme}' to be a boolean`, expr.right.span);
24 |   }
25 | 
26 |   return new BooleanType(expr);
27 | }
28 | 


--------------------------------------------------------------------------------
/src/types/internal/expressions/wrappedExpr.ts:
--------------------------------------------------------------------------------
1 | import * as grammar from '../../../grammar';
2 | 
3 | import { Type } from '../../types/Type';
4 | import { TypeChecker } from '../../TypeChecker';
5 | 
6 | export function wrappedExpr(expr: grammar.WrappedExpression, checker: TypeChecker): Type {
7 |   return checker.getType(expr.expression);
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/internal/literals/booleanLiteral.ts:
--------------------------------------------------------------------------------
1 | import * as grammar from '../../../grammar';
2 | 
3 | import { Type } from '../../types/Type';
4 | import { BooleanType } from '../../types/BooleanType';
5 | 
6 | export function booleanLiteral(literal: grammar.BooleanLiteral): Type {
7 |   return new BooleanType(literal);
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/internal/literals/numberLiteral.ts:
--------------------------------------------------------------------------------
1 | import * as grammar from '../../../grammar';
2 | 
3 | import { Type } from '../../types/Type';
4 | import { NumberType } from '../../types/NumberType';
5 | 
6 | export function numberLiteral(literal: grammar.NumberLiteral): Type {
7 |   return new NumberType(literal);
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/internal/literals/stringLiteral.ts:
--------------------------------------------------------------------------------
1 | import * as grammar from '../../../grammar';
2 | 
3 | import { Type } from '../../types/Type';
4 | import { StringType } from '../../types/StringType';
5 | 
6 | export function stringLiteral(literal: grammar.StringLiteral): Type {
7 |   return new StringType(literal);
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/type-check-rules.ts:
--------------------------------------------------------------------------------
  1 | import * as grammar from '../grammar';
  2 | 
  3 | import { Type } from './types/Type';
  4 | import { TypeChecker } from './TypeChecker';
  5 | 
  6 | // Declarations
  7 | import { classDecl } from './internal/declarations/classDecl';
  8 | import { functionDecl } from './internal/declarations/functionDecl';
  9 | import { importDecl } from './internal/declarations/importDecl';
 10 | import { variableDecl } from './internal/declarations/variableDecl';
 11 | 
 12 | // Expressions
 13 | // import { arrayExpr } from './internal/expressions/arrayExpr';
 14 | // import { assignmentExpr } from './internal/expressions/assignmentExpr';
 15 | // import { asyncExpr } from './internal/expressions/asyncExpr';
 16 | // import { binaryExpr } from './internal/expressions/binaryExpr';
 17 | // import { callExpr } from './internal/expressions/callExpr';
 18 | // import { ifExpr } from './internal/expressions/ifExpr';
 19 | // import { indexExpr } from './internal/expressions/indexExpr';
 20 | // import { instanceofExpr } from './internal/expressions/instanceofExpr';
 21 | // import { memberExpr } from './internal/expressions/memberExpr';
 22 | // import { newExpr } from './internal/expressions/newExpr';
 23 | import { unaryExpr } from './internal/expressions/unaryExpr';
 24 | import { wrappedExpr } from './internal/expressions/wrappedExpr';
 25 | 
 26 | // Literals
 27 | import { booleanLiteral } from './internal/literals/booleanLiteral';
 28 | import { numberLiteral } from './internal/literals/numberLiteral';
 29 | import { stringLiteral } from './internal/literals/stringLiteral';
 30 | 
 31 | // Statements
 32 | // import { breakStmt } from './internal/statements/breakStmt';
 33 | // import { continueStmt } from './internal/statements/continueStmt';
 34 | // import { forStmt } from './internal/statements/forStmt';
 35 | // import { returnStmt } from './internal/statements/returnStmt';
 36 | // import { whileStmt } from './internal/statements/whileStmt';
 37 | // import { yieldStmt } from './internal/statements/yieldStmt';
 38 | 
 39 | type TypeCheckFunction = (node: any, checker: TypeChecker) => Type;
 40 | 
 41 | function unimplemented(node: grammar.Node): Type {
 42 |   throw new Error(`TypeCheckFunction for node of type ${grammar.SyntacticToken[node.type]} has not been implemented`);
 43 | }
 44 | 
 45 | /**
 46 |  * An array containing type checkers for each node. The array is ordered the same as SyntacticToken
 47 |  */
 48 | export const typeCheckRules: TypeCheckFunction[] = [
 49 |   // Declarations
 50 |   variableDecl, // EMPTY_VARIABLE_DECL
 51 |   variableDecl, // VARIABLE_DECL
 52 |   functionDecl, // EMPTY_FUNCTION_DECL
 53 |   functionDecl, // FUNCTION_DECL
 54 |   classDecl, // CLASS_DECL
 55 |   importDecl, // FULL_IMPORT_DECL
 56 |   importDecl, // PARTIAL_IMPORT_DECL
 57 | 
 58 |   // Expressions
 59 |   unimplemented, // ASSIGNMENT_EXPR
 60 |   wrappedExpr, // WRAPPED_EXPR
 61 |   unaryExpr, // UNARY_EXPR
 62 |   unimplemented, // BINARY_EXPR
 63 |   unimplemented, // CALL_EXPR
 64 |   unimplemented, // INDEX_EXPR
 65 |   unimplemented, // MEMBER_EXPR
 66 |   unimplemented, // NEW_EXPR
 67 |   unimplemented, // INSTANCEOF_EXPR
 68 |   unimplemented, // ASYNC_EXPR
 69 |   unimplemented, // ARRAY_EXPR
 70 |   unimplemented, // IF_EXPR
 71 |   unimplemented, // ELSE_EXPR
 72 | 
 73 |   unimplemented, // IDENTIFIER
 74 |   unimplemented, // SUPER
 75 |   unimplemented, // THIS
 76 | 
 77 |   numberLiteral, // NUMBER_LITERAL
 78 |   stringLiteral, // STRING_LITERAL
 79 |   booleanLiteral, // BOOLEAN_LITERAL
 80 | 
 81 |   // Statements
 82 |   unimplemented, // FOR_STMT
 83 |   unimplemented, // WHILE_STMT
 84 |   unimplemented, // RETURN_STMT
 85 |   unimplemented, // YIELD_STMT
 86 |   unimplemented, // EXPRESSION_STMT
 87 | 
 88 |   unimplemented, // BREAK_STMT
 89 |   unimplemented, // CONTINUE_STMT
 90 | 
 91 |   // Other
 92 |   unimplemented, // NATIVE_NODE
 93 |   unimplemented, // PROGRAM
 94 |   unimplemented, // GENERIC_PARAM
 95 |   unimplemented, // VARIABLE_TYPE
 96 |   unimplemented, // PARAMETER
 97 |   unimplemented, // CONSTRUCTOR
 98 |   unimplemented, // CLASS_PROP
 99 |   unimplemented, // IMPORT_EXPOSE
100 | ];
101 | 


--------------------------------------------------------------------------------
/src/types/types/ArrayType.ts:
--------------------------------------------------------------------------------
 1 | import { Type } from './Type';
 2 | import { Node } from '../../grammar';
 3 | 
 4 | export class ArrayType extends Type {
 5 |   readonly content: Type;
 6 | 
 7 |   constructor(content: Type, node: Node) {
 8 |     super(node);
 9 | 
10 |     this.content = content;
11 |   }
12 | 
13 |   matches(type: Type): boolean {
14 |     return type instanceof ArrayType && type.content.matches(this.content);
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/src/types/types/BooleanType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class BooleanType extends Type {
4 |   matches(type: Type): boolean {
5 |     return type instanceof BooleanType;
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/src/types/types/ClassType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class ClassType extends Type {
4 |   matches(): boolean {
5 |     // A class never matches another type
6 |     return false;
7 |   }
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/types/FunctionType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class FunctionType extends Type {
4 |   matches(): boolean {
5 |     // TODO: check if the type matches
6 |     return false;
7 |   }
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/types/ImportType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class ImportType extends Type {
4 |   matches(): boolean {
5 |     // An import never matches another type
6 |     return false;
7 |   }
8 | }
9 | 


--------------------------------------------------------------------------------
/src/types/types/NumberType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class NumberType extends Type {
4 |   matches(type: Type): boolean {
5 |     return type instanceof NumberType;
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/src/types/types/ObjectType.ts:
--------------------------------------------------------------------------------
 1 | import { Type } from './Type';
 2 | import { Node } from '../../grammar';
 3 | 
 4 | export class ObjectType extends Type {
 5 |   readonly generics: Type[];
 6 | 
 7 |   constructor(generics: Type[], node: Node) {
 8 |     super(node);
 9 | 
10 |     this.generics = generics;
11 |   }
12 | 
13 |   matches(): boolean {
14 |     // TODO: check type and generics
15 |     return false;
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/src/types/types/StringType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class StringType extends Type {
4 |   matches(type: Type): boolean {
5 |     return type instanceof StringType;
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/src/types/types/Type.ts:
--------------------------------------------------------------------------------
 1 | import { Node } from '../../grammar';
 2 | 
 3 | export abstract class Type {
 4 |   readonly node: Node;
 5 | 
 6 |   constructor(node: Node) {
 7 |     this.node = node;
 8 |   }
 9 | 
10 |   abstract matches(type: Type): boolean;
11 | }
12 | 


--------------------------------------------------------------------------------
/src/types/types/UnknownType.ts:
--------------------------------------------------------------------------------
1 | import { Type } from './Type';
2 | 
3 | export class UnknownType extends Type {
4 |   matches(): boolean {
5 |     // Unknown type matches all other types to prevent incorrect errors
6 |     return true;
7 |   }
8 | }
9 | 


--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.tek' {
2 |   const value: string;
3 |   export default value;
4 | }
5 | 


--------------------------------------------------------------------------------
/src/walker/WalkerContext.ts:
--------------------------------------------------------------------------------
 1 | import { Node } from '../grammar';
 2 | import { Scope } from '../symbols';
 3 | 
 4 | export class WalkerContext {
 5 |   readonly parents: Node[];
 6 | 
 7 |   private readonly scope?: Scope;
 8 | 
 9 |   constructor(parents: Node[], scope?: Scope) {
10 |     this.parents = parents;
11 |     this.scope = scope;
12 |   }
13 | 
14 |   getScope(): Scope {
15 |     if (this.scope) {
16 |       return this.scope;
17 |     }
18 | 
19 |     throw new Error('Scope is not available at this stage');
20 |   }
21 | 
22 |   hasScope(): boolean {
23 |     return !!this.scope;
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/src/walker/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ASTWalker';
2 | export * from './WalkerContext';
3 | 


--------------------------------------------------------------------------------
/tests/index.test.ts:
--------------------------------------------------------------------------------
1 | import 'mocha';
2 | 
3 | import { loadIndexInDir } from './test-utils';
4 | 
5 | loadIndexInDir(__dirname, './linter');
6 | loadIndexInDir(__dirname, './parser');
7 | loadIndexInDir(__dirname, './symbols');
8 | loadIndexInDir(__dirname, './walker');
9 | 


--------------------------------------------------------------------------------
/tests/linter/declarations/use-before-define.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as declarations from '../../../src/linter/rules/declarations';
 4 | 
 5 | const ERROR = (name: string): string => `'${name}' is used before its declaration`;
 6 | 
 7 | testRule('useBeforeDefine', {
 8 |   rules: declarations,
 9 |   scope: true,
10 | 
11 |   valid: [
12 |     'var x = 5 \n x',
13 |     'var x = 5 \n x = 10',
14 |     'var x = 5 \n if true { x }',
15 |     'if true { var x = 5 \n x }',
16 | 
17 |     'function x(y: Number) { y }',
18 |     'var x = 5 \n function y() { x }',
19 |     'function x() { y } \n var y = 5',
20 |     'function x() { if true { y } } \n var y = 5',
21 |     'function x() {} \n x()',
22 |     'x() \n function x() {}',
23 |     'function x() { x() }',
24 | 
25 |     'class A {} \n new A()',
26 |     'var x = 5 \n class A { var x = x }',
27 |     'var x = 5 \n class A { static var x = x }',
28 |     'class A { var x = x } \n var x = 5',
29 | 
30 |     'class A { var x = 5 \n var y = this.x }',
31 |     'class A { static var x = 5 \n static var y = A.x }',
32 |   ],
33 |   invalid: [
34 |     { code: 'var x = x', errors: [ERROR('x')] },
35 |     { code: 'var x = x + 5', errors: [ERROR('x')] },
36 |     { code: 'var x = 5 + x', errors: [ERROR('x')] },
37 | 
38 |     { code: 'x \n var x = 5', errors: [ERROR('x')] },
39 |     { code: 'x = 10 \n var x = 5', errors: [ERROR('x')] },
40 |     { code: 'var x = y \n var y = x', errors: [ERROR('y')] },
41 |     { code: 'if true { x } \n var x = 5', errors: [ERROR('x')] },
42 |     { code: 'if true { x \n var x = 5 }', errors: [ERROR('x')] },
43 |     { code: 'for x in y {} \n var y = 5', errors: [ERROR('y')] },
44 | 
45 |     { code: 'new A() \n class A {}', errors: [ERROR('A')] },
46 |     { code: 'class A { static var x = x } \n var x = A.x', errors: [ERROR('x')] },
47 |     { code: 'var x = A.x \n class A { static var x = x }', errors: [ERROR('A')] },
48 | 
49 |     { code: 'class A { var x = this.x }', errors: [ERROR('x')] },
50 |     { code: 'class A { var x = 5 + this.x }', errors: [ERROR('x')] },
51 |     { code: 'class A { var x = this.y \n var y = 5 }', errors: [ERROR('y')] },
52 | 
53 |     { code: 'class A { static var x = A.x }', errors: [ERROR('x')] },
54 |     { code: 'class A { static var x = 5 + A.x }', errors: [ERROR('x')] },
55 |     { code: 'class A { static var x = A.y \n static var y = 5 }', errors: [ERROR('y')] },
56 |   ],
57 | });
58 | 


--------------------------------------------------------------------------------
/tests/linter/index.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | 
 3 | import { loadTestsInDir } from '../test-utils';
 4 | 
 5 | describe('linter', () => {
 6 |   describe('parser', () => {
 7 |     loadTestsInDir(__dirname, './parser');
 8 |   });
 9 | 
10 |   describe('declarations', () => {
11 |     loadTestsInDir(__dirname, './declarations');
12 |   });
13 | });
14 | 


--------------------------------------------------------------------------------
/tests/linter/parser/class-at-top-level.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const CLASS = 'class MyClass { var x = 5 }';
 6 | const ERROR = 'A class can only be declared at the top level';
 7 | 
 8 | testRule('classAtTopLevel', {
 9 |   rules: parser,
10 |   scope: false,
11 | 
12 |   valid: [CLASS],
13 |   invalid: [
14 |     { code: `if x { ${CLASS} }`, errors: [ERROR] },
15 |     { code: `function x() { ${CLASS} }`, errors: [ERROR] },
16 |     { code: `class X { ${CLASS} }`, errors: [ERROR] },
17 |     { code: `function x() { class Y { ${CLASS} } }`, errors: [ERROR, ERROR] },
18 |   ],
19 | });
20 | 


--------------------------------------------------------------------------------
/tests/linter/parser/declarations-in-class.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const ERROR = 'You can only put declarations in a class body';
 6 | 
 7 | testRule('declarationsInClass', {
 8 |   rules: parser,
 9 |   scope: false,
10 | 
11 |   valid: [
12 |     'class X { var x: A }',
13 |     'class X { var x = 5 }',
14 |     'class X { function x() { return } }',
15 |     'class X { import x }',
16 |     'class X { class Y { var x: A } }',
17 |   ],
18 |   invalid: [
19 |     { code: 'class X { 5 }', errors: [ERROR] },
20 |     { code: 'class X { x = 5 }', errors: [ERROR] },
21 |     { code: 'class X { 5 + 10 }', errors: [ERROR] },
22 |     { code: 'class X { if x { y } }', errors: [ERROR] },
23 |     { code: 'class X { for x in y { z } }', errors: [ERROR] },
24 |     { code: 'class X { x \n y }', errors: [ERROR, ERROR] },
25 |   ],
26 | });
27 | 


--------------------------------------------------------------------------------
/tests/linter/parser/illegal-abstract.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const CLASS_ERROR = 'Only abstract classes can contain abstract properties';
 6 | const PROP_ERROR = 'An abstract property can not contain a value';
 7 | 
 8 | testRule('illegalAbstract', {
 9 |   rules: parser,
10 |   scope: false,
11 | 
12 |   valid: [
13 |     'abstract class A { abstract var x: A \n abstract function y() }',
14 |     'abstract class A { abstract static var x: A \n abstract static function y() }',
15 |     'abstract class A { var x = 5 \n abstract var y: A }',
16 |     'abstract class A { function x() {} \n abstract function y() }',
17 |   ],
18 |   invalid: [
19 |     // Class
20 |     { code: 'class A { abstract var x: A }', errors: [CLASS_ERROR] },
21 |     { code: 'class A { abstract function x() }', errors: [CLASS_ERROR] },
22 | 
23 |     // Prop
24 |     { code: 'abstract class A { abstract var x = 5 }', errors: [PROP_ERROR] },
25 |     { code: 'abstract class A { abstract function x() {} }', errors: [PROP_ERROR] },
26 | 
27 |     // Combined
28 |     { code: 'class A { abstract var x = 5 }', errors: [CLASS_ERROR, PROP_ERROR] },
29 |     { code: 'class A { abstract function x() {} }', errors: [CLASS_ERROR, PROP_ERROR] },
30 |   ],
31 | });
32 | 


--------------------------------------------------------------------------------
/tests/linter/parser/illegal-assignment-expr.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const EXPR_ERROR = 'Assignments can not be inside another expression';
 6 | const LEFT_ERROR = 'You can only assign to an identifier, member expression, and index expression';
 7 | 
 8 | testRule('illegalAssignmentExpr', {
 9 |   rules: parser,
10 |   scope: false,
11 | 
12 |   valid: [
13 |     'x = 5',
14 |     'x = 10 - 5',
15 |     'if x { x = 5 }',
16 |     'function x() { x = 5 }',
17 |     'class C { function x() { x = 5 } }',
18 | 
19 |     'obj.x = 5',
20 |     'array[0] = 5',
21 |     'if x { obj.x = 5 }',
22 |     'if x { array[0] = 5 }',
23 |   ],
24 |   invalid: [
25 |     { code: '(x = 5)', errors: [EXPR_ERROR] },
26 |     { code: 'if x = 5 { y }', errors: [EXPR_ERROR] },
27 |     { code: 'for x in y = 5 { z }', errors: [EXPR_ERROR] },
28 | 
29 |     { code: 'x + y = 5', errors: [LEFT_ERROR] },
30 |     { code: 'fn() = 5', errors: [LEFT_ERROR] },
31 |     { code: '(x) = 5', errors: [LEFT_ERROR] },
32 |     { code: 'async x = 5', errors: [LEFT_ERROR] },
33 | 
34 |     { code: '((x) = 5)', errors: [EXPR_ERROR, LEFT_ERROR] },
35 |   ],
36 | });
37 | 


--------------------------------------------------------------------------------
/tests/linter/parser/illegal-control-statement.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const RETURN_ERROR = 'You can only place return inside a function';
 6 | const IF_RETURN_ERROR = 'You can not return from an if expression';
 7 | const YIELD_ERROR = 'You can only place yield inside an if expression';
 8 | const BREAK_ERROR = 'You can only place break inside a loop';
 9 | const CONTINUE_ERROR = 'You can only place continue inside a loop';
10 | 
11 | testRule('illegalControlStatement', {
12 |   rules: parser,
13 |   scope: false,
14 | 
15 |   valid: [
16 |     // Return
17 |     'function x() { return }',
18 |     'function x() { if y { return } }',
19 |     'if x { function x() { return } }',
20 |     'class C { function x() { return } }',
21 | 
22 |     // Yield
23 |     'var a = if b { yield c } else { yield d }',
24 |     'for a in if b { yield c } else { yield d } {}',
25 |     '(if a { yield if b { yield c } else { yield d } } else { yield e })',
26 | 
27 |     // Break
28 |     'for x in y { break }',
29 |     'for x in y { if z { break } }',
30 |     'if x { for y in z { break } }',
31 | 
32 |     // Continue
33 |     'for x in y { continue }',
34 |     'for x in y { if z { continue } }',
35 |     'if x { for y in z { continue } }',
36 |   ],
37 |   invalid: [
38 |     // Return
39 |     { code: 'return', errors: [RETURN_ERROR] },
40 |     { code: 'if x { return }', errors: [RETURN_ERROR] },
41 |     { code: 'for x in y { return }', errors: [RETURN_ERROR] },
42 | 
43 |     { code: '(if x { return y } else { yield z })', errors: [IF_RETURN_ERROR] },
44 |     { code: 'function a() { return if b { return c } else { yield d } }', errors: [IF_RETURN_ERROR] },
45 | 
46 |     // Yield
47 |     { code: 'yield x', errors: [YIELD_ERROR] },
48 |     { code: 'for x in y { yield z }', errors: [YIELD_ERROR] },
49 |     { code: 'if x { yield y }', errors: [YIELD_ERROR] },
50 |     { code: 'if x { yield y } else { yield z }', errors: [YIELD_ERROR, YIELD_ERROR] },
51 |     { code: 'if a { yield if b { yield c } else { yield d } } else { yield e }', errors: [YIELD_ERROR, YIELD_ERROR] },
52 | 
53 |     // Break
54 |     { code: 'break', errors: [BREAK_ERROR] },
55 |     { code: 'if x { break }', errors: [BREAK_ERROR] },
56 |     { code: 'function x() { break }', errors: [BREAK_ERROR] },
57 | 
58 |     // Continue
59 |     { code: 'continue', errors: [CONTINUE_ERROR] },
60 |     { code: 'if x { continue }', errors: [CONTINUE_ERROR] },
61 |     { code: 'function x() { continue }', errors: [CONTINUE_ERROR] },
62 |   ],
63 | });
64 | 


--------------------------------------------------------------------------------
/tests/linter/parser/illegal-if.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const ERROR = 'An if expression must have an else body';
 6 | 
 7 | testRule('illegalIf', {
 8 |   rules: parser,
 9 |   scope: false,
10 | 
11 |   valid: [
12 |     'if x {}',
13 |     'if x {} else {}',
14 |     'if x { if y {} }',
15 |     'if x { if y {} } else { if z {} }',
16 |     'if x {} else if y {} else {}',
17 |     '(if x {} else {})',
18 |     '(if x {} else if y {} else {})',
19 |     'if if x {} else {} {}',
20 |     'for x in if y {} else {} {}',
21 |   ],
22 |   invalid: [
23 |     { code: '(if x {})', errors: [ERROR] },
24 |     { code: '(if x {} else if y {})', errors: [ERROR] },
25 |     { code: 'if if x {} {}', errors: [ERROR] },
26 |     { code: 'if if x {} else if y {} {}', errors: [ERROR] },
27 |     { code: 'for x in if y {} {}', errors: [ERROR] },
28 |     { code: 'for x in if y {} else if z {} {}', errors: [ERROR] },
29 |   ],
30 | });
31 | 


--------------------------------------------------------------------------------
/tests/linter/parser/illegal-super-this.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const SUPER_ERROR = "You can only use 'super' inside a class";
 6 | const STATIC_SUPER_ERROR = "You can not use 'super' in a static variable or function";
 7 | 
 8 | const THIS_ERROR = "You can only use 'this' inside a class";
 9 | const STATIC_THIS_ERROR = "You can not use 'this' in a static variable or function";
10 | 
11 | testRule('illegalSuperThis', {
12 |   rules: parser,
13 |   scope: false,
14 | 
15 |   valid: [
16 |     // Super
17 |     'class C { var x = super.x }',
18 |     'class C { function x() { super } }',
19 | 
20 |     'class C { var x = C.super }',
21 |     'class C { function x() { C.super } }',
22 | 
23 |     // This
24 |     'class C { var x = this.x }',
25 |     'class C { function x() { this } }',
26 |   ],
27 |   invalid: [
28 |     // Super
29 |     { code: 'super', errors: [SUPER_ERROR] },
30 |     { code: 'if x { super }', errors: [SUPER_ERROR] },
31 |     { code: 'function x() { super }', errors: [SUPER_ERROR] },
32 | 
33 |     { code: 'class C { static var x = super }', errors: [STATIC_SUPER_ERROR] },
34 |     { code: 'class C { static function x() { super } }', errors: [STATIC_SUPER_ERROR] },
35 | 
36 |     { code: 'C.super', errors: [SUPER_ERROR] },
37 |     { code: 'if x { C.super }', errors: [SUPER_ERROR] },
38 |     { code: 'function x() { C.super }', errors: [SUPER_ERROR] },
39 | 
40 |     { code: 'class C { static var x = C.super }', errors: [STATIC_SUPER_ERROR] },
41 |     { code: 'class C { static function x() { C.super } }', errors: [STATIC_SUPER_ERROR] },
42 | 
43 |     // This
44 |     { code: 'this', errors: [THIS_ERROR] },
45 |     { code: 'if x { this }', errors: [THIS_ERROR] },
46 |     { code: 'function x() { this }', errors: [THIS_ERROR] },
47 | 
48 |     { code: 'class C { static var x = this }', errors: [STATIC_THIS_ERROR] },
49 |     { code: 'class C { static function x() { this } }', errors: [STATIC_THIS_ERROR] },
50 |   ],
51 | });
52 | 


--------------------------------------------------------------------------------
/tests/linter/parser/import-at-top-level.test.ts:
--------------------------------------------------------------------------------
 1 | import { testRule } from '../rule-tester';
 2 | 
 3 | import * as parser from '../../../src/linter/rules/parser';
 4 | 
 5 | const IMPORT = 'import myImport';
 6 | const ERROR = 'An import can only be at the top level';
 7 | 
 8 | testRule('importAtTopLevel', {
 9 |   rules: parser,
10 |   scope: false,
11 | 
12 |   valid: [IMPORT],
13 |   invalid: [
14 |     { code: `if x { ${IMPORT} }`, errors: [ERROR] },
15 |     { code: `function x() { ${IMPORT} }`, errors: [ERROR] },
16 |     { code: `class X { ${IMPORT} }`, errors: [ERROR] },
17 |   ],
18 | });
19 | 


--------------------------------------------------------------------------------
/tests/linter/rule-tester.ts:
--------------------------------------------------------------------------------
  1 | import 'mocha';
  2 | import { expect } from 'chai';
  3 | 
  4 | import { Linter } from '../../src/linter/Linter';
  5 | import { LinterRule } from '../../src/linter/LinterRule';
  6 | 
  7 | import { parse, sanitize } from '../test-utils';
  8 | import { ProgramScope } from '../../src/symbols';
  9 | 
 10 | type ValidTest =
 11 |   string
 12 |   | {
 13 |     code: string;
 14 |     skip: true;
 15 |   }
 16 |   | {
 17 |     group: string;
 18 |     tests: ValidTest[];
 19 |   };
 20 | 
 21 | type InvalidTest =
 22 |   {
 23 |     code: string;
 24 |     errors: string[];
 25 |     skip?: true;
 26 |   }
 27 |   | {
 28 |     group: string;
 29 |     tests: InvalidTest[];
 30 |   };
 31 | 
 32 | interface TestSettings {
 33 |   rules: { [name: string]: LinterRule };
 34 |   scope: boolean;
 35 | 
 36 |   valid: ValidTest[];
 37 |   invalid: InvalidTest[];
 38 | }
 39 | 
 40 | export function testRule(name: string, settings: TestSettings): void {
 41 |   function lintCode(code: string): string[] {
 42 |     const program = parse(code);
 43 | 
 44 |     let scope: ProgramScope | undefined;
 45 |     if (settings.scope) {
 46 |       scope = new ProgramScope(program);
 47 |       scope.build();
 48 |     }
 49 | 
 50 |     const rule = settings.rules[name];
 51 | 
 52 |     return new Linter(scope || program, { [name]: rule })
 53 |       .lint()
 54 |       .map(error => error.msg);
 55 |   }
 56 | 
 57 |   describe(name, () => {
 58 |     describe('valid', () => {
 59 |       function runValidTest(valid: ValidTest): void {
 60 |         let code: string;
 61 |         let skip = false;
 62 | 
 63 |         if (typeof valid === 'string') {
 64 |           code = valid;
 65 |         } else if ('code' in valid) {
 66 |           code = valid.code;
 67 |           skip = valid.skip;
 68 |         } else {
 69 |           describe(valid.group, () => {
 70 |             valid.tests.forEach(runValidTest);
 71 |           });
 72 | 
 73 |           return;
 74 |         }
 75 | 
 76 |         const test = skip ? it.skip : it;
 77 | 
 78 |         test(sanitize(code), () => {
 79 |           lintCode(code)
 80 |             .forEach((error) => {
 81 |               // Any error throws as the code should be valid
 82 |               throw new Error(error);
 83 |             });
 84 |         });
 85 |       }
 86 | 
 87 |       settings.valid.forEach(runValidTest);
 88 |     });
 89 | 
 90 |     describe('invalid', () => {
 91 |       function runInvalidTest(invalid: InvalidTest): void {
 92 |         if ('code' in invalid) {
 93 |           const test = invalid.skip ? it.skip : it;
 94 | 
 95 |           test(sanitize(invalid.code), () => {
 96 |             expect(invalid.errors.length).to.be.greaterThan(0, 'Invalid code must expect 1 or more errors');
 97 | 
 98 |             const errors = lintCode(invalid.code);
 99 | 
100 |             expect(errors.length).to.equal(invalid.errors.length, `Expected ${invalid.errors.length} error${invalid.errors.length === 1 ? '' : 's'}, but got ${errors.length}`);
101 | 
102 |             errors.forEach((error) => {
103 |               // The error should be in the list of expected errors
104 |               expect(invalid.errors).to.include(error);
105 |             });
106 |           });
107 |         } else {
108 |           describe(invalid.group, () => {
109 |             invalid.tests.forEach(runInvalidTest);
110 |           });
111 |         }
112 |       }
113 | 
114 |       settings.invalid.forEach(runInvalidTest);
115 |     });
116 |   });
117 | }
118 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/abstract/empty.tek:
--------------------------------------------------------------------------------
 1 | abstract class A {}
 2 | 
 3 | abstract class A {
 4 | }
 5 | 
 6 | abstract class A
 7 | {}
 8 | 
 9 | abstract class
10 | A {}
11 | 
12 | abstract
13 | class A {}
14 | 
15 | abstract
16 | class
17 | A
18 | {
19 | }
20 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/abstract/func.tek:
--------------------------------------------------------------------------------
 1 | abstract class A {
 2 | 	abstract function x()
 3 | }
 4 | 
 5 | abstract class A {
 6 | 	abstract function x(
 7 | 		)
 8 | }
 9 | 
10 | abstract class A {
11 | 	abstract function x
12 | 		()
13 | }
14 | 
15 | abstract class A {
16 | 	abstract function
17 | 		x()
18 | }
19 | 
20 | abstract class A {
21 | 	abstract
22 | 		function x()
23 | }
24 | 
25 | abstract class A {
26 | 	abstract
27 | 		function
28 | 		x
29 | 		(
30 | 		)
31 | }
32 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/abstract/static.tek:
--------------------------------------------------------------------------------
 1 | # abstract static
 2 | abstract class A {
 3 | 	abstract static var x: A
 4 | }
 5 | 
 6 | abstract class A {
 7 | 	abstract static var x:
 8 | 		A
 9 | }
10 | 
11 | abstract class A {
12 | 	abstract static var x
13 | 		: A
14 | }
15 | 
16 | abstract class A {
17 | 	abstract static var
18 | 		x: A
19 | }
20 | 
21 | abstract class A {
22 | 	abstract static
23 | 		var x: A
24 | }
25 | 
26 | abstract class A {
27 | 	abstract
28 | 		static var x: A
29 | }
30 | 
31 | abstract class A {
32 | 	abstract
33 | 		static
34 | 		var
35 | 		x
36 | 		:
37 | 		A
38 | }
39 | 
40 | # static abstract
41 | abstract class A {
42 | 	static abstract var x: A
43 | }
44 | 
45 | abstract class A {
46 | 	static abstract var x:
47 | 		A
48 | }
49 | 
50 | abstract class A {
51 | 	static abstract var x
52 | 		: A
53 | }
54 | 
55 | abstract class A {
56 | 	static abstract var
57 | 		x: A
58 | }
59 | 
60 | abstract class A {
61 | 	static abstract
62 | 		var x: A
63 | }
64 | 
65 | abstract class A {
66 | 	static
67 | 		abstract var x: A
68 | }
69 | 
70 | abstract class A {
71 | 	static
72 | 		abstract
73 | 		var
74 | 		x
75 | 		:
76 | 		A
77 | }
78 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/abstract/var.tek:
--------------------------------------------------------------------------------
 1 | abstract class A {
 2 | 	abstract var x: A
 3 | }
 4 | 
 5 | abstract class A {
 6 | 	abstract var x:
 7 | 		A
 8 | }
 9 | 
10 | abstract class A {
11 | 	abstract var x
12 | 		: A
13 | }
14 | 
15 | abstract class A {
16 | 	abstract var
17 | 		x: A
18 | }
19 | 
20 | abstract class A {
21 | 	abstract
22 | 		var x: A
23 | }
24 | 
25 | abstract class A {
26 | 	abstract
27 | 		var
28 | 		x
29 | 		:
30 | 		A
31 | }
32 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/constructor/no-param.tek:
--------------------------------------------------------------------------------
 1 | class A { new() {} }
 2 | 
 3 | class A { new() {}
 4 | }
 5 | 
 6 | class A { new() {
 7 | } }
 8 | 
 9 | class A { new()
10 | {} }
11 | 
12 | class A { new(
13 | ) {} }
14 | 
15 | class A { new
16 | () {} }
17 | 
18 | class A {
19 | new() {} }
20 | 
21 | class A
22 | { new() {} }
23 | 
24 | class
25 | A { new() {} }
26 | 
27 | class
28 | A
29 | {
30 | new
31 | (
32 | )
33 | {
34 | }
35 | }
36 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/constructor/param.tek:
--------------------------------------------------------------------------------
 1 | class A { new(x: A) {} }
 2 | 
 3 | class A { new(x: A) {}
 4 | }
 5 | 
 6 | class A { new(x: A) {
 7 | } }
 8 | 
 9 | class A { new(x: A)
10 | {} }
11 | 
12 | class A { new(x: A
13 | ) {} }
14 | 
15 | class A { new(x:
16 | A) {} }
17 | 
18 | class A { new(x
19 | : A) {} }
20 | 
21 | class A { new(
22 | x: A) {} }
23 | 
24 | class A { new
25 | (x: A) {} }
26 | 
27 | class A {
28 | new(x: A) {} }
29 | 
30 | class A
31 | { new(x: A) {} }
32 | 
33 | class
34 | A { new(x: A) {} }
35 | 
36 | class
37 | A
38 | {
39 | new
40 | (
41 | x
42 | :
43 | A
44 | )
45 | {
46 | }
47 | }
48 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/empty.tek:
--------------------------------------------------------------------------------
 1 | class A {}
 2 | 
 3 | class A {
 4 | }
 5 | 
 6 | class A
 7 | {}
 8 | 
 9 | class
10 | A {}
11 | 
12 | class
13 | A
14 | {
15 | }
16 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/extend/multi-extend.tek:
--------------------------------------------------------------------------------
 1 | class A extends B, C {}
 2 | 
 3 | class A extends B, C {
 4 | }
 5 | 
 6 | class A extends B, C
 7 | {}
 8 | 
 9 | class A extends B,
10 | C {}
11 | 
12 | class A extends B
13 | , C {}
14 | 
15 | class A extends
16 | B, C {}
17 | 
18 | class A
19 | extends B, C {}
20 | 
21 | class
22 | A extends B, C {}
23 | 
24 | class
25 | A
26 | extends
27 | B
28 | ,
29 | C
30 | {
31 | }
32 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/extend/single-extend.tek:
--------------------------------------------------------------------------------
 1 | class A extends B {}
 2 | 
 3 | class A extends B {
 4 | }
 5 | 
 6 | class A extends B
 7 | {}
 8 | 
 9 | class A extends
10 | B {}
11 | 
12 | class A
13 | extends B {}
14 | 
15 | class
16 | A extends B {}
17 | 
18 | class
19 | A
20 | extends
21 | B
22 | {
23 | }
24 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/func/func.tek:
--------------------------------------------------------------------------------
 1 | class A { function x() {} }
 2 | 
 3 | class A { function x() {}
 4 | }
 5 | 
 6 | class A { function x() {
 7 | } }
 8 | 
 9 | class A { function x()
10 | {} }
11 | 
12 | class A { function x(
13 | ) {} }
14 | 
15 | class A { function x
16 | () {} }
17 | 
18 | class A { function
19 | x() {} }
20 | 
21 | class A {
22 | function x() {} }
23 | 
24 | class A
25 | { function x() {} }
26 | 
27 | class
28 | A { function x() {} }
29 | 
30 | class
31 | A
32 | {
33 | function
34 | x
35 | (
36 | )
37 | {
38 | }
39 | }
40 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/func/static-func.tek:
--------------------------------------------------------------------------------
 1 | class A { static function x() {} }
 2 | 
 3 | class A { static function x() {}
 4 | }
 5 | 
 6 | class A { static function x() {
 7 | } }
 8 | 
 9 | class A { static function x()
10 | {} }
11 | 
12 | class A { static function x(
13 | ) {} }
14 | 
15 | class A { static function x
16 | () {} }
17 | 
18 | class A { static function
19 | x() {} }
20 | 
21 | class A { static
22 | function x() {} }
23 | 
24 | class A {
25 | static function x() {} }
26 | 
27 | class A
28 | { static function x() {} }
29 | 
30 | class
31 | A { static function x() {} }
32 | 
33 | class
34 | A
35 | {
36 | static
37 | function
38 | x
39 | (
40 | )
41 | {
42 | }
43 | }
44 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/generic/generic-class.tek:
--------------------------------------------------------------------------------
 1 | class A {}
 2 | 
 3 | class A {
 4 | }
 5 | 
 6 | class A
 7 | {}
 8 | 
 9 | class A {}
11 | 
12 | class A<
13 | B> {}
14 | 
15 | class A
16 |  {}
17 | 
18 | class
19 | A {}
20 | 
21 | class
22 | A
23 | <
24 | B
25 | >
26 | {
27 | }
28 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/generic/generic-extends.tek:
--------------------------------------------------------------------------------
 1 | class A extends B {}
 2 | 
 3 | class A extends B {
 4 | }
 5 | 
 6 | class A extends B
 7 | {}
 8 | 
 9 | class A extends B {}
11 | 
12 | class A extends B<
13 | C> {}
14 | 
15 | class A extends B
16 |  {}
17 | 
18 | class A extends
19 | B {}
20 | 
21 | class A
22 | extends B {}
23 | 
24 | class
25 | A extends B {}
26 | 
27 | class
28 | A
29 | extends
30 | B
31 | <
32 | C
33 | >
34 | {
35 | }
36 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/var/static-var.tek:
--------------------------------------------------------------------------------
 1 | class A { static var x = y }
 2 | 
 3 | class A { static var x = y
 4 | }
 5 | 
 6 | class A { static var x =
 7 | y }
 8 | 
 9 | class A { static var x
10 | = y }
11 | 
12 | class A { static var
13 | x = y }
14 | 
15 | class A { static
16 | var x = y }
17 | 
18 | class A {
19 | static var x = y }
20 | 
21 | class A
22 | { static var x = y }
23 | 
24 | class
25 | A { static var x = y }
26 | 
27 | class
28 | A
29 | {
30 | static
31 | var
32 | x
33 | =
34 | y
35 | }
36 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/class/var/var.tek:
--------------------------------------------------------------------------------
 1 | class A { var x = y }
 2 | 
 3 | class A { var x = y
 4 | }
 5 | 
 6 | class A { var x =
 7 | y }
 8 | 
 9 | class A { var x
10 | = y }
11 | 
12 | class A { var
13 | x = y }
14 | 
15 | class A {
16 | var x = y }
17 | 
18 | class A
19 | { var x = y }
20 | 
21 | class
22 | A { var x = y }
23 | 
24 | class
25 | A
26 | {
27 | var
28 | x
29 | =
30 | y
31 | }
32 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/function/empty.tek:
--------------------------------------------------------------------------------
 1 | function x(): A
 2 | 
 3 | function x():
 4 | A
 5 | 
 6 | function x()
 7 | : A
 8 | 
 9 | function x(
10 | ): A
11 | 
12 | function x
13 | (): A
14 | 
15 | function
16 | x(): A
17 | 
18 | function
19 | x
20 | (
21 | )
22 | :
23 | A
24 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/function/generic-param.tek:
--------------------------------------------------------------------------------
 1 | function  x() {}
 2 | 
 3 | function  x() {
 4 | }
 5 | 
 6 | function  x()
 7 | {}
 8 | 
 9 | function  x(
10 | ) {}
11 | 
12 | function  x
13 | () {}
14 | 
15 | function 
16 | x() {}
17 | 
18 | function  x() {}
20 | 
21 | function <
22 | A> x() {}
23 | 
24 | function
25 |  x() {}
26 | 
27 | function
28 | <
29 | A
30 | >
31 | x
32 | (
33 | )
34 | {
35 | }
36 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/function/multi-param.tek:
--------------------------------------------------------------------------------
 1 | function x(y: A, z: B) {}
 2 | 
 3 | function x(y: A, z: B) {
 4 | }
 5 | 
 6 | function x(y: A, z: B)
 7 | {}
 8 | 
 9 | function x(y: A, z: B
10 | ) {}
11 | 
12 | function x(y: A, z:
13 | B) {}
14 | 
15 | function x(y: A, z
16 | : B) {}
17 | 
18 | function x(y: A,
19 | z: B) {}
20 | 
21 | function x(y: A
22 | , z: B) {}
23 | 
24 | function x(y:
25 | A, z: B) {}
26 | 
27 | function x(y
28 | : A, z: B) {}
29 | 
30 | function x(
31 | y: A, z: B) {}
32 | 
33 | function x
34 | (y: A, z: B) {}
35 | 
36 | function
37 | x(y: A, z: B) {}
38 | 
39 | function
40 | x
41 | (
42 | y
43 | :
44 | A
45 | ,
46 | z
47 | :
48 | B
49 | )
50 | {
51 | }
52 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/function/return.tek:
--------------------------------------------------------------------------------
 1 | function x(): A {}
 2 | 
 3 | function x(): A {
 4 | }
 5 | 
 6 | function x(): A
 7 | {}
 8 | 
 9 | function x():
10 | A {}
11 | 
12 | function x()
13 | : A {}
14 | 
15 | function x(
16 | ): A {}
17 | 
18 | function x
19 | (): A {}
20 | 
21 | function
22 | x(): A {}
23 | 
24 | function
25 | x
26 | (
27 | )
28 | :
29 | A
30 | {
31 | }
32 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/function/single-param.tek:
--------------------------------------------------------------------------------
 1 | function x(y: A) {}
 2 | 
 3 | function x(y: A) {
 4 | }
 5 | 
 6 | function x(y: A)
 7 | {}
 8 | 
 9 | function x(y: A
10 | ) {}
11 | 
12 | function x(y:
13 | A) {}
14 | 
15 | function x(y
16 | : A) {}
17 | 
18 | function x(
19 | y: A) {}
20 | 
21 | function x
22 | (y: A) {}
23 | 
24 | function
25 | x(y: A) {}
26 | 
27 | function
28 | x
29 | (
30 | y
31 | :
32 | A
33 | )
34 | {
35 | }
36 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/function/without-params.tek:
--------------------------------------------------------------------------------
 1 | function x() {}
 2 | 
 3 | function x() {
 4 | }
 5 | 
 6 | function x()
 7 | {}
 8 | 
 9 | function x(
10 | ) {}
11 | 
12 | function x
13 | () {}
14 | 
15 | function
16 | x() {}
17 | 
18 | function
19 | x
20 | (
21 | )
22 | {
23 | }
24 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/import/full/full.tek:
--------------------------------------------------------------------------------
 1 | import a.b
 2 | 
 3 | import a.
 4 | b
 5 | 
 6 | import a
 7 | .b
 8 | 
 9 | import
10 | a.b
11 | 
12 | import
13 | a
14 | .
15 | b
16 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/import/full/rename.tek:
--------------------------------------------------------------------------------
 1 | import a.b as c
 2 | 
 3 | import a.b as
 4 | c
 5 | 
 6 | import a.b
 7 | as c
 8 | 
 9 | import a.
10 | b as c
11 | 
12 | import a
13 | .b as c
14 | 
15 | import
16 | a.b as c
17 | 
18 | import
19 | a
20 | .
21 | b
22 | as
23 | c
24 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/import/import.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { FullImportDeclaration, PartialImportDeclaration } from '../../../../src/grammar/nodes/Declarations';
 9 | 
10 | describe('import', () => {
11 |   describe('full', () => {
12 |     it('parses full correctly', () => {
13 |       const program = parse(loadRaw(__dirname, './full/full.tek'));
14 | 
15 |       function check(node: Node): void {
16 |         const decl = node as FullImportDeclaration;
17 |         expect(decl.type).to.equal(SyntacticToken.FULL_IMPORT_DECL);
18 |         expect(decl).to.be.an.instanceof(FullImportDeclaration);
19 | 
20 |         expect(decl.path.length).to.equal(2);
21 |         expect(decl.path[0].lexeme).to.equal('a');
22 |         expect(decl.path[1].lexeme).to.equal('b');
23 | 
24 |         expect(decl.identifier.lexeme).to.equal('b');
25 |       }
26 | 
27 |       program.body.forEach(check);
28 |     });
29 | 
30 |     it('parses rename correctly', () => {
31 |       const program = parse(loadRaw(__dirname, './full/rename.tek'));
32 | 
33 |       function check(node: Node): void {
34 |         const decl = node as FullImportDeclaration;
35 |         expect(decl.type).to.equal(SyntacticToken.FULL_IMPORT_DECL);
36 |         expect(decl).to.be.an.instanceof(FullImportDeclaration);
37 | 
38 |         expect(decl.path.length).to.equal(2);
39 |         expect(decl.path[0].lexeme).to.equal('a');
40 |         expect(decl.path[1].lexeme).to.equal('b');
41 | 
42 |         expect(decl.identifier.lexeme).to.equal('c');
43 |       }
44 | 
45 |       program.body.forEach(check);
46 |     });
47 |   });
48 | 
49 |   describe('partial', () => {
50 |     it('parses partial correctly', () => {
51 |       const program = parse(loadRaw(__dirname, './partial/partial.tek'));
52 | 
53 |       function check(node: Node): void {
54 |         const decl = node as PartialImportDeclaration;
55 |         expect(decl.type).to.equal(SyntacticToken.PARTIAL_IMPORT_DECL);
56 |         expect(decl).to.be.an.instanceof(PartialImportDeclaration);
57 | 
58 |         expect(decl.path.length).to.equal(2);
59 |         expect(decl.path[0].lexeme).to.equal('a');
60 |         expect(decl.path[1].lexeme).to.equal('b');
61 | 
62 |         expect(decl.expose.length).to.equal(2);
63 | 
64 |         expect(decl.expose[0].value.lexeme).to.equal('c');
65 |         expect(decl.expose[0].identifier.lexeme).to.equal('c');
66 | 
67 |         expect(decl.expose[1].value.lexeme).to.equal('d');
68 |         expect(decl.expose[1].identifier.lexeme).to.equal('d');
69 |       }
70 | 
71 |       program.body.forEach(check);
72 |     });
73 | 
74 |     it('parses rename correctly', () => {
75 |       const program = parse(loadRaw(__dirname, './partial/rename.tek'));
76 | 
77 |       function check(node: Node): void {
78 |         const decl = node as PartialImportDeclaration;
79 |         expect(decl.type).to.equal(SyntacticToken.PARTIAL_IMPORT_DECL);
80 |         expect(decl).to.be.an.instanceof(PartialImportDeclaration);
81 | 
82 |         expect(decl.path.length).to.equal(2);
83 |         expect(decl.path[0].lexeme).to.equal('a');
84 |         expect(decl.path[1].lexeme).to.equal('b');
85 | 
86 |         expect(decl.expose.length).to.equal(2);
87 | 
88 |         expect(decl.expose[0].value.lexeme).to.equal('c');
89 |         expect(decl.expose[0].identifier.lexeme).to.equal('d');
90 | 
91 |         expect(decl.expose[1].value.lexeme).to.equal('e');
92 |         expect(decl.expose[1].identifier.lexeme).to.equal('f');
93 |       }
94 | 
95 |       program.body.forEach(check);
96 |     });
97 |   });
98 | });
99 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/import/partial/partial.tek:
--------------------------------------------------------------------------------
 1 | import a.b.{c, d}
 2 | 
 3 | import a.b.{c, d
 4 | }
 5 | 
 6 | import a.b.{c,
 7 | d}
 8 | 
 9 | import a.b.{c
10 | , d}
11 | 
12 | import a.b.{
13 | c, d}
14 | 
15 | import a.b.
16 | {c, d}
17 | 
18 | import a.b
19 | .{c, d}
20 | 
21 | import a.
22 | b.{c, d}
23 | 
24 | import a
25 | .b.{c, d}
26 | 
27 | import
28 | a.b.{c, d}
29 | 
30 | import
31 | a
32 | .
33 | b
34 | .
35 | {
36 | c
37 | ,
38 | d
39 | }
40 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/import/partial/rename.tek:
--------------------------------------------------------------------------------
 1 | import a.b.{c as d, e as f}
 2 | 
 3 | import a.b.{c as d, e as f
 4 | }
 5 | 
 6 | import a.b.{c as d, e as
 7 | f}
 8 | 
 9 | import a.b.{c as d, e
10 | as f}
11 | 
12 | import a.b.{c as d,
13 | e as f}
14 | 
15 | import a.b.{c as d
16 | , e as f}
17 | 
18 | import a.b.{c as
19 | d, e as f}
20 | 
21 | import a.b.{c
22 | as d, e as f}
23 | 
24 | import a.b.{
25 | c as d, e as f}
26 | 
27 | import a.b.
28 | {c as d, e as f}
29 | 
30 | import a.b
31 | .{c as d, e as f}
32 | 
33 | import a.
34 | b.{c as d, e as f}
35 | 
36 | import a
37 | .b.{c as d, e as f}
38 | 
39 | import
40 | a.b.{c as d, e as f}
41 | 
42 | import
43 | a
44 | .
45 | b
46 | .
47 | {
48 | c
49 | as
50 | d
51 | ,
52 | e
53 | as
54 | f
55 | }
56 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/variable/empty.tek:
--------------------------------------------------------------------------------
 1 | var x: A
 2 | 
 3 | var x:
 4 | A
 5 | 
 6 | var x
 7 | : A
 8 | 
 9 | var
10 | x: A
11 | 
12 | var
13 | x
14 | :
15 | A
16 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/variable/generic.tek:
--------------------------------------------------------------------------------
 1 | var x: A = y
 2 | 
 3 | var x: A =
 4 | y
 5 | 
 6 | var x: A
 7 | = y
 8 | 
 9 | var x: A = y
11 | 
12 | var x: A<
13 | B> = y
14 | 
15 | var x: A
16 |  = y
17 | 
18 | var x:
19 | A = y
20 | 
21 | var x
22 | : A = y
23 | 
24 | var
25 | x: A = y
26 | 
27 | var
28 | x: A
29 | = y
30 | 
31 | var
32 | x
33 | :
34 | A
35 | <
36 | B
37 | >
38 | =
39 | y
40 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/variable/with-type.tek:
--------------------------------------------------------------------------------
 1 | var x: A = y
 2 | 
 3 | var x: A =
 4 | y
 5 | 
 6 | var x: A
 7 | = y
 8 | 
 9 | var x:
10 | A = y
11 | 
12 | var x
13 | : A = y
14 | 
15 | var
16 | x: A = y
17 | 
18 | var
19 | x: A
20 | = y
21 | 
22 | var
23 | x
24 | :
25 | A
26 | =
27 | y
28 | 


--------------------------------------------------------------------------------
/tests/parser/declarations/variable/without-type.tek:
--------------------------------------------------------------------------------
 1 | var x = y
 2 | 
 3 | var x =
 4 | y
 5 | 
 6 | var x
 7 | = y
 8 | 
 9 | var
10 | x = y
11 | 
12 | var
13 | x =
14 | y
15 | 
16 | var
17 | x
18 | = y
19 | 
20 | var
21 | x
22 | =
23 | y
24 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/array/array.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | import { Identifier, ArrayExpression } from '../../../../src/grammar/nodes/Expressions';
10 | 
11 | describe('array', () => {
12 |   it('parses no params correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './no-params.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as ArrayExpression;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.ARRAY_EXPR);
22 |       expect(expr).to.be.an.instanceof(ArrayExpression);
23 | 
24 |       expect(expr.content.length).to.equal(0);
25 |     }
26 | 
27 |     program.body.forEach(check);
28 |   });
29 | 
30 |   it('parses single param correctly', () => {
31 |     const program = parse(loadRaw(__dirname, './single-param.tek'));
32 | 
33 |     function check(node: Node): void {
34 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
35 |       expect(node).to.be.an.instanceof(ExpressionStatement);
36 | 
37 |       const expr = (node as ExpressionStatement).expression as ArrayExpression;
38 | 
39 |       expect(expr.type).to.equal(SyntacticToken.ARRAY_EXPR);
40 |       expect(expr).to.be.an.instanceof(ArrayExpression);
41 | 
42 |       expect(expr.content.length).to.equal(1);
43 | 
44 |       const param = expr.content[0] as Identifier;
45 |       expect(param.type).to.equal(SyntacticToken.IDENTIFIER);
46 |       expect(param).to.be.an.instanceof(Identifier);
47 |       expect(param.lexeme).to.equal('x');
48 |     }
49 | 
50 |     program.body.forEach(check);
51 |   });
52 | 
53 |   it('parses multi param correctly', () => {
54 |     const program = parse(loadRaw(__dirname, './multi-param.tek'));
55 | 
56 |     function check(node: Node): void {
57 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
58 |       expect(node).to.be.an.instanceof(ExpressionStatement);
59 | 
60 |       const expr = (node as ExpressionStatement).expression as ArrayExpression;
61 | 
62 |       expect(expr.type).to.equal(SyntacticToken.ARRAY_EXPR);
63 |       expect(expr).to.be.an.instanceof(ArrayExpression);
64 | 
65 |       expect(expr.content.length).to.equal(2);
66 | 
67 |       const firstParam = expr.content[0] as Identifier;
68 |       expect(firstParam.type).to.equal(SyntacticToken.IDENTIFIER);
69 |       expect(firstParam).to.be.an.instanceof(Identifier);
70 |       expect(firstParam.lexeme).to.equal('x');
71 | 
72 |       const secondParam = expr.content[1] as Identifier;
73 |       expect(secondParam.type).to.equal(SyntacticToken.IDENTIFIER);
74 |       expect(secondParam).to.be.an.instanceof(Identifier);
75 |       expect(secondParam.lexeme).to.equal('y');
76 |     }
77 | 
78 |     program.body.forEach(check);
79 |   });
80 | });
81 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/array/multi-param.tek:
--------------------------------------------------------------------------------
 1 | [x, y]
 2 | 
 3 | [x, y
 4 | ]
 5 | 
 6 | [x,
 7 | y]
 8 | 
 9 | [x
10 | , y]
11 | 
12 | [
13 | x, y]
14 | 
15 | [
16 | x,
17 | y
18 | ]
19 | 
20 | [
21 | x
22 | ,
23 | y
24 | ]
25 | 
26 | [x, y,]
27 | 
28 | [x, y,
29 | ]
30 | 
31 | [x, y
32 | ,]
33 | 
34 | [x,
35 | y,]
36 | 
37 | [x
38 | , y,]
39 | 
40 | [
41 | x, y,]
42 | 
43 | [
44 | x,
45 | y,
46 | ]
47 | 
48 | [
49 | x
50 | ,
51 | y
52 | ,
53 | ]
54 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/array/no-params.tek:
--------------------------------------------------------------------------------
1 | []
2 | 
3 | [
4 | ]
5 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/array/single-param.tek:
--------------------------------------------------------------------------------
 1 | [x]
 2 | 
 3 | [x
 4 | ]
 5 | 
 6 | [
 7 | x]
 8 | 
 9 | [
10 | x
11 | ]
12 | 
13 | [x,]
14 | 
15 | [x,
16 | ]
17 | 
18 | [x
19 | ,]
20 | 
21 | [
22 | x,]
23 | 
24 | [
25 | x
26 | ,
27 | ]
28 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/assignment/assignment.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | import { AssignmentExpression, IndexExpression, Identifier } from '../../../../src/grammar/nodes/Expressions';
10 | 
11 | describe('assignment', () => {
12 |   it('parses variable correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './variable.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as AssignmentExpression;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.ASSIGNMENT_EXPR);
22 |       expect(expr).to.be.an.instanceof(AssignmentExpression);
23 | 
24 |       const left = expr.left as Identifier;
25 |       expect(left.type).to.equal(SyntacticToken.IDENTIFIER);
26 |       expect(left).to.be.an.instanceOf(Identifier);
27 |       expect(left.lexeme).to.equal('x');
28 | 
29 |       const value = expr.value as Identifier;
30 |       expect(value.type).to.equal(SyntacticToken.IDENTIFIER);
31 |       expect(value).to.be.an.instanceof(Identifier);
32 |       expect(value.lexeme).to.equal('y');
33 |     }
34 | 
35 |     program.body.forEach(check);
36 |   });
37 | 
38 |   it('parses index expression correctly', () => {
39 |     const program = parse(loadRaw(__dirname, './index.tek'));
40 | 
41 |     function check(node: Node): void {
42 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
43 |       expect(node).to.be.an.instanceof(ExpressionStatement);
44 | 
45 |       const expr = (node as ExpressionStatement).expression as AssignmentExpression;
46 | 
47 |       expect(expr.type).to.equal(SyntacticToken.ASSIGNMENT_EXPR);
48 |       expect(expr).to.be.an.instanceof(AssignmentExpression);
49 | 
50 |       const left = expr.left as IndexExpression;
51 |       expect(left.type).to.equal(SyntacticToken.INDEX_EXPR);
52 |       expect(left).to.be.an.instanceof(IndexExpression);
53 | 
54 |       const object = left.object as Identifier;
55 |       expect(object.type).to.equal(SyntacticToken.IDENTIFIER);
56 |       expect(object).to.be.an.instanceof(Identifier);
57 |       expect(object.lexeme).to.equal('x');
58 | 
59 |       const index = left.index as Identifier;
60 |       expect(index.type).to.equal(SyntacticToken.IDENTIFIER);
61 |       expect(index).to.be.an.instanceof(Identifier);
62 |       expect(index.lexeme).to.equal('y');
63 | 
64 |       const value = expr.value as Identifier;
65 |       expect(value.type).to.equal(SyntacticToken.IDENTIFIER);
66 |       expect(value).to.be.an.instanceof(Identifier);
67 |       expect(value.lexeme).to.equal('z');
68 |     }
69 | 
70 |     program.body.forEach(check);
71 |   });
72 | });
73 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/assignment/index.tek:
--------------------------------------------------------------------------------
 1 | x[y] = z
 2 | 
 3 | x[y] =
 4 | z
 5 | 
 6 | x[y]
 7 | = z
 8 | 
 9 | x[y]
10 | =
11 | z
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/assignment/variable.tek:
--------------------------------------------------------------------------------
 1 | x = y
 2 | 
 3 | x =
 4 | y
 5 | 
 6 | x
 7 | = y
 8 | 
 9 | x
10 | =
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/async/async.tek:
--------------------------------------------------------------------------------
1 | async x
2 | 
3 | async
4 | x
5 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/async/async.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | import { Identifier, AsyncExpression } from '../../../../src/grammar/nodes/Expressions';
10 | 
11 | describe('async', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './async.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as AsyncExpression;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.ASYNC_EXPR);
22 |       expect(expr).to.be.an.instanceof(AsyncExpression);
23 | 
24 |       const literal = expr.expression as Identifier;
25 |       expect(literal.type).to.equal(SyntacticToken.IDENTIFIER);
26 |       expect(literal).to.be.an.instanceof(Identifier);
27 |       expect(literal.lexeme).to.equal('x');
28 |     }
29 | 
30 |     program.body.forEach(check);
31 |   });
32 | });
33 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/addition.tek:
--------------------------------------------------------------------------------
 1 | x + y
 2 | 
 3 | x +
 4 | y
 5 | 
 6 | x
 7 | + y
 8 | 
 9 | x
10 | +
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/division.tek:
--------------------------------------------------------------------------------
 1 | x / y
 2 | 
 3 | x /
 4 | y
 5 | 
 6 | x
 7 | / y
 8 | 
 9 | x
10 | /
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/exponentation.tek:
--------------------------------------------------------------------------------
 1 | x ^ y
 2 | 
 3 | x ^
 4 | y
 5 | 
 6 | x
 7 | ^ y
 8 | 
 9 | x
10 | ^
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/multiplication.tek:
--------------------------------------------------------------------------------
 1 | x * y
 2 | 
 3 | x *
 4 | y
 5 | 
 6 | x
 7 | * y
 8 | 
 9 | x
10 | *
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/remainder.tek:
--------------------------------------------------------------------------------
 1 | x % y
 2 | 
 3 | x %
 4 | y
 5 | 
 6 | x
 7 | % y
 8 | 
 9 | x
10 | %
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/subtraction.tek:
--------------------------------------------------------------------------------
1 | x - y
2 | 
3 | x -
4 | y
5 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/arithmetic/wrapped-subtraction.tek:
--------------------------------------------------------------------------------
 1 | ((x - y) - z)
 2 | 
 3 | ((x - y) - z
 4 | )
 5 | 
 6 | ((x - y) -
 7 | z)
 8 | 
 9 | ((x - y)
10 | - z)
11 | 
12 | ((x - y
13 | ) - z)
14 | 
15 | ((x -
16 | y) - z)
17 | 
18 | ((x
19 | - y) - z)
20 | 
21 | ((
22 | x - y) - z)
23 | 
24 | (
25 | (x - y) - z)
26 | 
27 | (
28 | (
29 | x
30 | -
31 | y
32 | )
33 | -
34 | z
35 | )
36 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/comparison/equal.tek:
--------------------------------------------------------------------------------
 1 | x == y
 2 | 
 3 | x ==
 4 | y
 5 | 
 6 | x
 7 | == y
 8 | 
 9 | x
10 | ==
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/comparison/greater-equal.tek:
--------------------------------------------------------------------------------
 1 | x >= y
 2 | 
 3 | x >=
 4 | y
 5 | 
 6 | x
 7 | >= y
 8 | 
 9 | x
10 | >=
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/comparison/greater.tek:
--------------------------------------------------------------------------------
 1 | x > y
 2 | 
 3 | x >
 4 | y
 5 | 
 6 | x
 7 | > y
 8 | 
 9 | x
10 | >
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/comparison/less-equal.tek:
--------------------------------------------------------------------------------
 1 | x <= y
 2 | 
 3 | x <=
 4 | y
 5 | 
 6 | x
 7 | <= y
 8 | 
 9 | x
10 | <=
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/comparison/less.tek:
--------------------------------------------------------------------------------
 1 | x < y
 2 | 
 3 | x <
 4 | y
 5 | 
 6 | x
 7 | < y
 8 | 
 9 | x
10 | <
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/comparison/not-equal.tek:
--------------------------------------------------------------------------------
 1 | x != y
 2 | 
 3 | x !=
 4 | y
 5 | 
 6 | x
 7 | != y
 8 | 
 9 | x
10 | !=
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/logical/and.tek:
--------------------------------------------------------------------------------
 1 | x and y
 2 | 
 3 | x and
 4 | y
 5 | 
 6 | x
 7 | and y
 8 | 
 9 | x
10 | and
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/binary/logical/or.tek:
--------------------------------------------------------------------------------
 1 | x or y
 2 | 
 3 | x or
 4 | y
 5 | 
 6 | x
 7 | or y
 8 | 
 9 | x
10 | or
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/call/generic.tek:
--------------------------------------------------------------------------------
 1 | fn()
 2 | 
 3 | fn<
 4 | T>()
 5 | 
 6 | fn()
 8 | 
 9 | fn
10 | ()
11 | 
12 | fn
13 | (
14 | )
15 | 
16 | fn
17 | ()
18 | 
19 | fn
20 | <
21 | 	T
22 | >()
23 | 
24 | fn
25 | 	<
26 | 	T
27 | 	>
28 | 	(
29 | 	)
30 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/call/multi-param.tek:
--------------------------------------------------------------------------------
 1 | x(y, z)
 2 | 
 3 | x(y, z
 4 | )
 5 | 
 6 | x(y,
 7 | z)
 8 | 
 9 | x(y
10 | , z)
11 | 
12 | x(
13 | y, z)
14 | 
15 | x(
16 | y,
17 | z
18 | )
19 | 
20 | x(
21 | y
22 | ,
23 | z
24 | )
25 | 
26 | x(y, z,)
27 | 
28 | x(y, z,
29 | )
30 | 
31 | x(y, z
32 | ,)
33 | 
34 | x(y,
35 | z,)
36 | 
37 | x(y
38 | , z,)
39 | 
40 | x(
41 | y, z,)
42 | 
43 | x(
44 | y, z,
45 | )
46 | 
47 | x(
48 | y,
49 | z,
50 | )
51 | 
52 | x(
53 | y
54 | ,
55 | z
56 | ,
57 | )
58 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/call/no-params.tek:
--------------------------------------------------------------------------------
1 | x()
2 | 
3 | x(
4 | )
5 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/call/single-param.tek:
--------------------------------------------------------------------------------
 1 | x(y)
 2 | 
 3 | x(y
 4 | )
 5 | 
 6 | x(
 7 | y)
 8 | 
 9 | x(
10 | y
11 | )
12 | 
13 | x(y,)
14 | 
15 | x(y,
16 | )
17 | 
18 | x(
19 | y,)
20 | 
21 | x(
22 | y,
23 | )
24 | 
25 | x(
26 | y
27 | ,
28 | )
29 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/if/empty.tek:
--------------------------------------------------------------------------------
 1 | if x {}
 2 | 
 3 | if x {
 4 | }
 5 | 
 6 | if x
 7 | {}
 8 | 
 9 | if
10 | x {}
11 | 
12 | if
13 | x
14 | {
15 | }
16 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/if/if-with-else-if.tek:
--------------------------------------------------------------------------------
 1 | if x { a } else if y { b } else { c }
 2 | 
 3 | if x { a } else if y { b } else { c
 4 | }
 5 | 
 6 | if x { a } else if y { b } else {
 7 | c }
 8 | 
 9 | if x { a } else if y { b } else
10 | { c }
11 | 
12 | if x { a } else if y { b }
13 | else { c }
14 | 
15 | if x { a } else if y { b
16 | } else { c }
17 | 
18 | if x { a } else if y {
19 | b } else { c }
20 | 
21 | if x { a } else if y
22 | { b } else { c }
23 | 
24 | if x { a } else if
25 | y { b } else { c }
26 | 
27 | if x { a } else
28 | if y { b } else { c }
29 | 
30 | if x { a }
31 | else if y { b } else { c }
32 | 
33 | if x { a
34 | } else if y { b } else { c }
35 | 
36 | if x {
37 | a } else if y { b } else { c }
38 | 
39 | if x
40 | { a } else if y { b } else { c }
41 | 
42 | if
43 | x { a } else if y { b } else { c }
44 | 
45 | if
46 | x { a }
47 | else if y { b }
48 | else { c }
49 | 
50 | if
51 | x
52 | {
53 | a
54 | }
55 | else
56 | if
57 | y
58 | {
59 | b
60 | }
61 | else
62 | {
63 | c
64 | }
65 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/if/if-with-else.tek:
--------------------------------------------------------------------------------
 1 | if x { y } else { z }
 2 | 
 3 | if x { y } else { z
 4 | }
 5 | 
 6 | if x { y } else {
 7 | z }
 8 | 
 9 | if x { y } else
10 | { z }
11 | 
12 | if x { y }
13 | else { z }
14 | 
15 | if x { y
16 | } else { z }
17 | 
18 | if x {
19 | y } else { z }
20 | 
21 | if x
22 | { y } else { z }
23 | 
24 | if
25 | x { y } else { z }
26 | 
27 | if
28 | x
29 | { y }
30 | else
31 | { z }
32 | 
33 | if
34 | x
35 | {
36 | y
37 | }
38 | else
39 | {
40 | z
41 | }
42 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/if/single-if.tek:
--------------------------------------------------------------------------------
 1 | if x { y }
 2 | 
 3 | if x { y
 4 | }
 5 | 
 6 | if x {
 7 | y }
 8 | 
 9 | if x
10 | { y }
11 | 
12 | if
13 | x { y }
14 | 
15 | if
16 | x
17 | { y }
18 | 
19 | if
20 | x
21 | {
22 | y
23 | }
24 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/if/wrapped.tek:
--------------------------------------------------------------------------------
 1 | (if x { y
 2 | - z } else {})
 3 | 
 4 | (if x { y
 5 | - z } else {}
 6 | )
 7 | 
 8 | (if x { y
 9 | - z } else {
10 | })
11 | 
12 | (if x { y
13 | - z } else
14 | {})
15 | 
16 | (if x { y
17 | - z }
18 | else {})
19 | 
20 | (if x { y
21 | - z
22 | } else {})
23 | 
24 | (if x { y
25 | -
26 | z } else {})
27 | 
28 | (if x {
29 | y
30 | - z } else {})
31 | 
32 | (if x
33 | { y
34 | - z } else {})
35 | 
36 | (if
37 | x { y
38 | - z } else {})
39 | 
40 | (
41 | if x { y
42 | - z } else {})
43 | 
44 | (
45 | if
46 | x
47 | {
48 | y
49 | -
50 | z
51 | }
52 | else
53 | {
54 | }
55 | )
56 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/index/index.tek:
--------------------------------------------------------------------------------
 1 | x[y]
 2 | 
 3 | x[y
 4 | ]
 5 | 
 6 | x[
 7 | y]
 8 | 
 9 | x[
10 | y
11 | ]
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/index/index.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | import { Identifier, IndexExpression } from '../../../../src/grammar/nodes/Expressions';
10 | 
11 | describe('index', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './index.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as IndexExpression;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.INDEX_EXPR);
22 |       expect(expr).to.be.an.instanceof(IndexExpression);
23 | 
24 |       const object = expr.object as Identifier;
25 |       expect(object.type).to.equal(SyntacticToken.IDENTIFIER);
26 |       expect(object).to.be.an.instanceof(Identifier);
27 |       expect(object.lexeme).to.equal('x');
28 | 
29 |       const index = expr.index as Identifier;
30 |       expect(index.type).to.equal(SyntacticToken.IDENTIFIER);
31 |       expect(index).to.be.an.instanceof(Identifier);
32 |       expect(index.lexeme).to.equal('y');
33 |     }
34 | 
35 |     program.body.forEach(check);
36 |   });
37 | });
38 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/instanceof/generic.tek:
--------------------------------------------------------------------------------
 1 | x instanceof A
 2 | 
 3 | x instanceof A
 5 | 
 6 | x instanceof A<
 7 | B>
 8 | 
 9 | x instanceof A
10 | 
11 | 
12 | x instanceof
13 | A
14 | 
15 | x
16 | instanceof A
17 | 
18 | x
19 | instanceof
20 | A
21 | <
22 | B
23 | >
24 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/instanceof/instanceof.tek:
--------------------------------------------------------------------------------
 1 | x instanceof y
 2 | 
 3 | x instanceof
 4 | y
 5 | 
 6 | x
 7 | instanceof y
 8 | 
 9 | x
10 | instanceof
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/instanceof/instanceof.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | import { Identifier, InstanceofExpression } from '../../../../src/grammar/nodes/Expressions';
10 | 
11 | describe('instanceof', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './instanceof.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as InstanceofExpression;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.INSTANCEOF_EXPR);
22 |       expect(expr).to.be.an.instanceof(InstanceofExpression);
23 | 
24 |       const left = expr.left as Identifier;
25 |       expect(left.type).to.equal(SyntacticToken.IDENTIFIER);
26 |       expect(left).to.be.an.instanceof(Identifier);
27 |       expect(left.lexeme).to.equal('x');
28 | 
29 |       expect((expr.right.object as Identifier).lexeme).to.equal('y');
30 |       expect(expr.right.generics.length).to.equal(0);
31 |       expect(expr.right.arrayDepth).to.equal(0);
32 |     }
33 | 
34 |     program.body.forEach(check);
35 |   });
36 | 
37 |   it('parses generic correctly', () => {
38 |     const program = parse(loadRaw(__dirname, './generic.tek'));
39 | 
40 |     function check(node: Node): void {
41 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
42 |       expect(node).to.be.an.instanceof(ExpressionStatement);
43 | 
44 |       const expr = (node as ExpressionStatement).expression as InstanceofExpression;
45 | 
46 |       expect(expr.type).to.equal(SyntacticToken.INSTANCEOF_EXPR);
47 |       expect(expr).to.be.an.instanceof(InstanceofExpression);
48 | 
49 |       const left = expr.left as Identifier;
50 |       expect(left.type).to.equal(SyntacticToken.IDENTIFIER);
51 |       expect(left).to.be.an.instanceof(Identifier);
52 |       expect(left.lexeme).to.equal('x');
53 | 
54 |       expect((expr.right.object as Identifier).lexeme).to.equal('A');
55 |       expect(expr.right.generics.length).to.equal(1);
56 | 
57 |       const generic = expr.right.generics[0];
58 |       expect((generic.object as Identifier).lexeme).to.equal('B');
59 |       expect(generic.generics.length).to.equal(0);
60 |       expect(generic.arrayDepth).to.equal(0);
61 | 
62 |       expect(expr.right.arrayDepth).to.equal(0);
63 |     }
64 | 
65 |     program.body.forEach(check);
66 |   });
67 | });
68 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/member/identifier.tek:
--------------------------------------------------------------------------------
 1 | x.y
 2 | 
 3 | x.
 4 | y
 5 | 
 6 | x
 7 | .y
 8 | 
 9 | x
10 | .
11 | y
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/member/member.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { LexicalToken } from '../../../../src/grammar/LexicalToken';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
10 | import { Identifier, MemberExpression } from '../../../../src/grammar/nodes/Expressions';
11 | 
12 | describe('member', () => {
13 |   it('parses identifier correctly', () => {
14 |     const program = parse(loadRaw(__dirname, './identifier.tek'));
15 | 
16 |     function check(node: Node): void {
17 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
18 |       expect(node).to.be.an.instanceof(ExpressionStatement);
19 | 
20 |       const expr = (node as ExpressionStatement).expression as MemberExpression;
21 | 
22 |       expect(expr.type).to.equal(SyntacticToken.MEMBER_EXPR);
23 |       expect(expr).to.be.an.instanceof(MemberExpression);
24 | 
25 |       const object = expr.object as Identifier;
26 |       expect(object.type).to.equal(SyntacticToken.IDENTIFIER);
27 |       expect(object).to.be.an.instanceof(Identifier);
28 |       expect(object.lexeme).to.equal('x');
29 | 
30 |       const property = expr.property;
31 |       expect(property.type).to.equal(LexicalToken.IDENTIFIER);
32 |       expect(property.lexeme).to.equal('y');
33 |     }
34 | 
35 |     program.body.forEach(check);
36 |   });
37 | 
38 |   it('parses super correctly', () => {
39 |     const program = parse(loadRaw(__dirname, './super.tek'));
40 | 
41 |     function check(node: Node): void {
42 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
43 |       expect(node).to.be.an.instanceof(ExpressionStatement);
44 | 
45 |       const expr = (node as ExpressionStatement).expression as MemberExpression;
46 | 
47 |       expect(expr.type).to.equal(SyntacticToken.MEMBER_EXPR);
48 |       expect(expr).to.be.an.instanceof(MemberExpression);
49 | 
50 |       const object = expr.object as Identifier;
51 |       expect(object.type).to.equal(SyntacticToken.IDENTIFIER);
52 |       expect(object).to.be.an.instanceof(Identifier);
53 |       expect(object.lexeme).to.equal('x');
54 | 
55 |       const property = expr.property;
56 |       expect(property.type).to.equal(LexicalToken.SUPER);
57 |       expect(property.lexeme).to.equal('super');
58 |     }
59 | 
60 |     program.body.forEach(check);
61 |   });
62 | });
63 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/member/super.tek:
--------------------------------------------------------------------------------
 1 | x.super
 2 | 
 3 | x.
 4 | super
 5 | 
 6 | x
 7 | .super
 8 | 
 9 | x
10 | .
11 | super
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/new/generic.tek:
--------------------------------------------------------------------------------
 1 | new x()
 2 | 
 3 | new x(
 4 | )
 5 | 
 6 | new x
 7 | ()
 8 | 
 9 | new x()
11 | 
12 | new x<
13 | y>()
14 | 
15 | new x
16 | ()
17 | 
18 | new
19 | x()
20 | 
21 | new
22 | x
23 | <
24 | y
25 | >
26 | (
27 | )
28 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/new/member-class.tek:
--------------------------------------------------------------------------------
 1 | new x.y()
 2 | 
 3 | new x.y(
 4 | )
 5 | 
 6 | new x.
 7 | y()
 8 | 
 9 | new x.
10 | y(
11 | )
12 | 
13 | new x
14 | .y()
15 | 
16 | new x
17 | .y(
18 | )
19 | 
20 | new x
21 | .
22 | y()
23 | 
24 | new x
25 | .
26 | y(
27 | )
28 | 
29 | new
30 | x
31 | .
32 | y
33 | (
34 | )
35 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/new/multi-param.tek:
--------------------------------------------------------------------------------
  1 | new x(y, z)
  2 | 
  3 | new x(y, z
  4 | )
  5 | 
  6 | new x(y,
  7 | z)
  8 | 
  9 | new x(y
 10 | , z)
 11 | 
 12 | new x(
 13 | y, z)
 14 | 
 15 | new x(
 16 | y,
 17 | z
 18 | )
 19 | 
 20 | new x(
 21 | y
 22 | ,
 23 | z
 24 | )
 25 | 
 26 | new x
 27 | (y, z)
 28 | 
 29 | new x
 30 | (y, z
 31 | )
 32 | 
 33 | new x
 34 | (y,
 35 | z)
 36 | 
 37 | new x
 38 | (y
 39 | , z)
 40 | 
 41 | new x
 42 | (
 43 | y, z)
 44 | 
 45 | new x
 46 | (
 47 | y,
 48 | z
 49 | )
 50 | 
 51 | new x
 52 | (
 53 | y
 54 | ,
 55 | z
 56 | )
 57 | 
 58 | new
 59 | x(y, z)
 60 | 
 61 | new
 62 | x(y, z
 63 | )
 64 | 
 65 | new
 66 | x(y,
 67 | z)
 68 | 
 69 | new
 70 | x(y
 71 | , z)
 72 | 
 73 | new
 74 | x(
 75 | y, z)
 76 | 
 77 | new
 78 | x(
 79 | y,
 80 | z
 81 | )
 82 | 
 83 | new
 84 | x(
 85 | y
 86 | ,
 87 | z
 88 | )
 89 | 
 90 | new
 91 | x
 92 | (
 93 | y
 94 | ,
 95 | z
 96 | )
 97 | 
 98 | # Trailing comma
 99 | new
100 | x
101 | (
102 | y
103 | ,
104 | z
105 | ,
106 | )
107 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/new/no-params.tek:
--------------------------------------------------------------------------------
 1 | new x()
 2 | 
 3 | new x(
 4 | )
 5 | 
 6 | new x
 7 | ()
 8 | 
 9 | new x
10 | (
11 | )
12 | 
13 | new
14 | x()
15 | 
16 | new
17 | x(
18 | )
19 | 
20 | new
21 | x
22 | ()
23 | 
24 | new
25 | x
26 | (
27 | )
28 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/new/single-param.tek:
--------------------------------------------------------------------------------
  1 | # No trailing comma
  2 | new x(y)
  3 | 
  4 | new x(y
  5 | )
  6 | 
  7 | new x(
  8 | y)
  9 | 
 10 | new x(
 11 | y
 12 | )
 13 | 
 14 | new x
 15 | (y)
 16 | 
 17 | new x
 18 | (y
 19 | )
 20 | 
 21 | new x
 22 | (
 23 | y)
 24 | 
 25 | new x
 26 | (
 27 | y
 28 | )
 29 | 
 30 | new
 31 | x(y)
 32 | 
 33 | new
 34 | x(y
 35 | )
 36 | 
 37 | new
 38 | x(
 39 | y)
 40 | 
 41 | new
 42 | x(
 43 | y
 44 | )
 45 | 
 46 | new
 47 | x
 48 | (y)
 49 | 
 50 | new
 51 | x
 52 | (y
 53 | )
 54 | 
 55 | new
 56 | x
 57 | (
 58 | y)
 59 | 
 60 | new
 61 | x
 62 | (
 63 | y
 64 | )
 65 | 
 66 | # Trailing comma
 67 | new x(y,)
 68 | 
 69 | new x(y,
 70 | )
 71 | 
 72 | new x(y
 73 | ,)
 74 | 
 75 | new x(
 76 | y,)
 77 | 
 78 | new x(
 79 | y,
 80 | )
 81 | 
 82 | new x(
 83 | y
 84 | ,
 85 | )
 86 | 
 87 | new x
 88 | (y,)
 89 | 
 90 | new x
 91 | (y,
 92 | )
 93 | 
 94 | new x
 95 | (y
 96 | ,)
 97 | 
 98 | new x
 99 | (
100 | y,)
101 | 
102 | new x
103 | (
104 | y,
105 | )
106 | 
107 | new x
108 | (
109 | y
110 | ,
111 | )
112 | 
113 | new
114 | x(y,)
115 | 
116 | new
117 | x(y,
118 | )
119 | 
120 | new
121 | x(y
122 | ,)
123 | 
124 | new
125 | x(
126 | y,)
127 | 
128 | new
129 | x(
130 | y,
131 | )
132 | 
133 | new
134 | x(
135 | y
136 | ,
137 | )
138 | 
139 | new
140 | x
141 | (y,)
142 | 
143 | new
144 | x
145 | (y,
146 | )
147 | 
148 | new
149 | x
150 | (y
151 | ,)
152 | 
153 | new
154 | x
155 | (
156 | y,)
157 | 
158 | new
159 | x
160 | (
161 | y,
162 | )
163 | 
164 | new
165 | x
166 | (
167 | y
168 | ,
169 | )
170 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/super/super.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { Super } from '../../../../src/grammar/nodes/Expressions';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
10 | 
11 | describe('super', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse('super');
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as Super;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.SUPER);
22 |       expect(expr).to.be.an.instanceof(Super);
23 |     }
24 | 
25 |     program.body.forEach(check);
26 |   });
27 | });
28 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/this/this.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { This } from '../../../../src/grammar/nodes/Expressions';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
10 | 
11 | describe('this', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse('this');
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as This;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.THIS);
22 |       expect(expr).to.be.an.instanceof(This);
23 |     }
24 | 
25 |     program.body.forEach(check);
26 |   });
27 | });
28 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/unary/negation.tek:
--------------------------------------------------------------------------------
1 | -x
2 | 
3 | -
4 | x
5 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/unary/not.tek:
--------------------------------------------------------------------------------
1 | !x
2 | 
3 | !
4 | x
5 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/unary/unary.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { LexicalToken } from '../../../../src/grammar/LexicalToken';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
10 | import { Identifier, UnaryExpression } from '../../../../src/grammar/nodes/Expressions';
11 | 
12 | describe('unary', () => {
13 |   it('parses "negation" correctly', () => {
14 |     const program = parse(loadRaw(__dirname, './negation.tek'));
15 | 
16 |     function check(node: Node): void {
17 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
18 |       expect(node).to.be.an.instanceof(ExpressionStatement);
19 | 
20 |       const expr = (node as ExpressionStatement).expression as UnaryExpression;
21 | 
22 |       expect(expr.type).to.equal(SyntacticToken.UNARY_EXPR);
23 |       expect(expr).to.be.an.instanceof(UnaryExpression);
24 | 
25 |       const right = expr.right as Identifier;
26 |       expect(right.type).to.equal(SyntacticToken.IDENTIFIER);
27 |       expect(right).to.be.an.instanceof(Identifier);
28 | 
29 |       expect(expr.operator.type).to.equal(LexicalToken.MINUS);
30 |       expect(right.lexeme).to.equal('x');
31 |     }
32 | 
33 |     program.body.forEach(check);
34 |   });
35 | 
36 |   it('parses "not" correctly', () => {
37 |     const program = parse(loadRaw(__dirname, './not.tek'));
38 | 
39 |     function check(node: Node): void {
40 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
41 |       expect(node).to.be.an.instanceof(ExpressionStatement);
42 | 
43 |       const expr = (node as ExpressionStatement).expression as UnaryExpression;
44 | 
45 |       expect(expr.type).to.equal(SyntacticToken.UNARY_EXPR);
46 |       expect(expr).to.be.an.instanceof(UnaryExpression);
47 | 
48 |       const right = expr.right as Identifier;
49 |       expect(right.type).to.equal(SyntacticToken.IDENTIFIER);
50 |       expect(right).to.be.an.instanceof(Identifier);
51 | 
52 |       expect(expr.operator.type).to.equal(LexicalToken.BANG);
53 |       expect(right.lexeme).to.equal('x');
54 |     }
55 | 
56 |     program.body.forEach(check);
57 |   });
58 | });
59 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/wrapped/wrapped.tek:
--------------------------------------------------------------------------------
 1 | (x)
 2 | 
 3 | (x
 4 | )
 5 | 
 6 | (
 7 | x)
 8 | 
 9 | (
10 | x
11 | )
12 | 


--------------------------------------------------------------------------------
/tests/parser/expressions/wrapped/wrapped.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | import { Identifier, WrappedExpression } from '../../../../src/grammar/nodes/Expressions';
10 | 
11 | describe('wrapped', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './wrapped.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as WrappedExpression;
20 | 
21 |       expect(expr.type).to.equal(SyntacticToken.WRAPPED_EXPR);
22 |       expect(expr).to.be.an.instanceof(WrappedExpression);
23 | 
24 |       const content = expr.expression as Identifier;
25 |       expect(content.type).to.equal(SyntacticToken.IDENTIFIER);
26 |       expect(content).to.be.an.instanceof(Identifier);
27 |       expect(content.lexeme).to.equal('x');
28 |     }
29 | 
30 |     program.body.forEach(check);
31 |   });
32 | });
33 | 


--------------------------------------------------------------------------------
/tests/parser/index.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | 
 3 | import { loadTestsInDir, getDirsFrom } from '../test-utils';
 4 | 
 5 | describe('parser', () => {
 6 |   describe('tokenizer', () => {
 7 |     loadTestsInDir(__dirname, './tokenizer');
 8 |   });
 9 | 
10 |   describe('declarations', () => {
11 |     getDirsFrom(__dirname, './declarations')
12 |       .forEach(dir => loadTestsInDir(__dirname, dir));
13 |   });
14 | 
15 |   describe('expressions', () => {
16 |     getDirsFrom(__dirname, './expressions')
17 |       .forEach(dir => loadTestsInDir(__dirname, dir));
18 |   });
19 | 
20 |   describe('statements', () => {
21 |     getDirsFrom(__dirname, './statements')
22 |       .forEach(dir => loadTestsInDir(__dirname, dir));
23 |   });
24 | 
25 |   describe('literals', () => {
26 |     getDirsFrom(__dirname, './literals')
27 |       .forEach(dir => loadTestsInDir(__dirname, dir));
28 |   });
29 | 
30 |   // Remaining tests
31 |   loadTestsInDir(__dirname, './other');
32 | });
33 | 


--------------------------------------------------------------------------------
/tests/parser/literals/boolean/boolean.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { BooleanLiteral } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('boolean', () => {
11 |   it('parses "true" correctly', () => {
12 |     const program = parse('true');
13 | 
14 |     const stmt = program.body[0] as ExpressionStatement;
15 |     expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
16 |     expect(stmt).to.be.an.instanceof(ExpressionStatement);
17 | 
18 |     const expr = stmt.expression as BooleanLiteral;
19 |     expect(expr.type).to.equal(SyntacticToken.BOOLEAN_LITERAL);
20 |     expect(expr).to.be.an.instanceof(BooleanLiteral);
21 |     expect(expr.value.lexeme).to.equal('true');
22 |   });
23 | 
24 |   it('parses "false" correctly', () => {
25 |     const program = parse('false');
26 | 
27 |     const stmt = program.body[0] as ExpressionStatement;
28 |     expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
29 |     expect(stmt).to.be.an.instanceof(ExpressionStatement);
30 | 
31 |     const expr = stmt.expression as BooleanLiteral;
32 |     expect(expr.type).to.equal(SyntacticToken.BOOLEAN_LITERAL);
33 |     expect(expr).to.be.an.instanceof(BooleanLiteral);
34 |     expect(expr.value.lexeme).to.equal('false');
35 |   });
36 | });
37 | 


--------------------------------------------------------------------------------
/tests/parser/literals/identifier/identifier.tek:
--------------------------------------------------------------------------------
 1 | a
 2 | b
 3 | _
 4 | aZ_
 5 | a0
 6 | a00011
 7 | _0000_
 8 | _____
 9 | _a1
10 | _5
11 | t5
12 | P5t_5
13 | P1_g
14 | 


--------------------------------------------------------------------------------
/tests/parser/literals/identifier/identifier.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Identifier } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('identifier', () => {
11 |   it('parses correctly', () => {
12 |     const valid = loadRaw(__dirname, './identifier.tek');
13 |     const identifiers = valid.split('\n');
14 | 
15 |     parse(valid).body.forEach((node, i) => {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as Identifier;
20 |       expect(expr.type).to.equal(SyntacticToken.IDENTIFIER);
21 |       expect(expr).to.be.an.instanceof(Identifier);
22 |       expect(expr.lexeme).to.equal(identifiers[i]);
23 |     });
24 |   });
25 | });
26 | 


--------------------------------------------------------------------------------
/tests/parser/literals/number/number.tek:
--------------------------------------------------------------------------------
 1 | 0
 2 | 000
 3 | 
 4 | 0.0
 5 | 000.000
 6 | 
 7 | 1.2
 8 | 15.25
 9 | 
10 | 1_000.2_001
11 | 1___1.2___0
12 | 
13 | 1___
14 | 1___.2____
15 | 
16 | 0_________0
17 | 0_1_2_3_4_5
18 | 
19 | 0.1_2_3_4_5
20 | 
21 | 1_851_619_781_613
22 | 


--------------------------------------------------------------------------------
/tests/parser/literals/number/number.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { NumberLiteral } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('number', () => {
11 |   it('parses numbers correctly', () => {
12 |     const valid = loadRaw(__dirname, './number.tek');
13 |     const numbers = valid.split('\n').filter(x => x);
14 | 
15 |     parse(valid).body.forEach((node, i) => {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as NumberLiteral;
20 |       expect(expr.type).to.equal(SyntacticToken.NUMBER_LITERAL);
21 |       expect(expr).to.be.an.instanceof(NumberLiteral);
22 |       expect(expr.value.lexeme).to.equal(numbers[i]);
23 |     });
24 |   });
25 | });
26 | 


--------------------------------------------------------------------------------
/tests/parser/literals/string/double-quote.tek:
--------------------------------------------------------------------------------
 1 | ""
 2 | "a"
 3 | "hello"
 4 | "\t"
 5 | "\""
 6 | "\\"
 7 | "'"
 8 | "'hello'"
 9 | "don't do \"it\""
10 | 


--------------------------------------------------------------------------------
/tests/parser/literals/string/single-quote.tek:
--------------------------------------------------------------------------------
 1 | ''
 2 | 'a'
 3 | 'hello'
 4 | '\t'
 5 | '\''
 6 | '\\'
 7 | '"'
 8 | '"hello"'
 9 | 'don\'t do "it"'
10 | 


--------------------------------------------------------------------------------
/tests/parser/literals/string/string.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { StringLiteral } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('string', () => {
11 |   it('parses single quote correctly', () => {
12 |     const valid = loadRaw(__dirname, './single-quote.tek');
13 |     const strings = valid.split('\n');
14 | 
15 |     parse(valid).body.forEach((node, i) => {
16 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
17 |       expect(node).to.be.an.instanceof(ExpressionStatement);
18 | 
19 |       const expr = (node as ExpressionStatement).expression as StringLiteral;
20 |       expect(expr.type).to.equal(SyntacticToken.STRING_LITERAL);
21 |       expect(expr).to.be.an.instanceof(StringLiteral);
22 |       expect(expr.value.lexeme).to.equal(strings[i]);
23 |     });
24 |   });
25 | 
26 |   it('parses double quote correctly', () => {
27 |     const valid = loadRaw(__dirname, './double-quote.tek');
28 |     const strings = valid.split('\n');
29 | 
30 |     parse(valid).body.forEach((node, i) => {
31 |       expect(node.type).to.equal(SyntacticToken.EXPRESSION_STMT);
32 |       expect(node).to.be.an.instanceof(ExpressionStatement);
33 | 
34 |       const expr = (node as ExpressionStatement).expression as StringLiteral;
35 |       expect(expr.type).to.equal(SyntacticToken.STRING_LITERAL);
36 |       expect(expr).to.be.an.instanceof(StringLiteral);
37 |       expect(expr.value.lexeme).to.equal(strings[i]);
38 |     });
39 |   });
40 | });
41 | 


--------------------------------------------------------------------------------
/tests/parser/literals/super/super.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { Super } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('super', () => {
11 |   it('parses correctly', () => {
12 |     const program = parse('super');
13 | 
14 |     const stmt = program.body[0] as ExpressionStatement;
15 |     expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
16 |     expect(stmt).to.be.an.instanceof(ExpressionStatement);
17 | 
18 |     const expr = stmt.expression as Super;
19 |     expect(expr.type).to.equal(SyntacticToken.SUPER);
20 |     expect(expr).to.be.an.instanceof(Super);
21 |   });
22 | });
23 | 


--------------------------------------------------------------------------------
/tests/parser/literals/this/this.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { This } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('this', () => {
11 |   it('parses correctly', () => {
12 |     const program = parse('this');
13 | 
14 |     const stmt = program.body[0] as ExpressionStatement;
15 |     expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
16 |     expect(stmt).to.be.an.instanceof(ExpressionStatement);
17 | 
18 |     const expr = stmt.expression as This;
19 |     expect(expr.type).to.equal(SyntacticToken.THIS);
20 |     expect(expr).to.be.an.instanceof(This);
21 |   });
22 | });
23 | 


--------------------------------------------------------------------------------
/tests/parser/other/program.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../test-utils';
 5 | 
 6 | import { Program } from '../../../src/grammar/nodes/Other';
 7 | import { SyntacticToken } from '../../../src/grammar/SyntacticToken';
 8 | 
 9 | describe('program', () => {
10 |   it('works when empty', () => {
11 |     const program = parse('');
12 |     expect(program.type).to.equal(SyntacticToken.PROGRAM);
13 |     expect(program).to.be.an.instanceof(Program);
14 |     expect(program.body.length).to.equal(0);
15 | 
16 |     expect(program.span).to.deep.equal({
17 |       start: [0, 0],
18 |       end: [0, 0],
19 |     });
20 |   });
21 | 
22 |   it('works with code', () => {
23 |     const program = parse('5');
24 |     expect(program.type).to.equal(SyntacticToken.PROGRAM);
25 |     expect(program).to.be.an.instanceof(Program);
26 |     expect(program.body.length).to.equal(1);
27 | 
28 |     expect(program.span).to.deep.equal({
29 |       start: [0, 0],
30 |       end: [0, 1],
31 |     });
32 |   });
33 | });
34 | 


--------------------------------------------------------------------------------
/tests/parser/statements/break/break.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 7 | import { BreakStatement } from '../../../../src/grammar/nodes/Statements';
 8 | 
 9 | describe('break', () => {
10 |   it('parses correctly', () => {
11 |     const program = parse('break');
12 | 
13 |     const stmt = program.body[0] as BreakStatement;
14 |     expect(stmt.type).to.equal(SyntacticToken.BREAK_STMT);
15 |     expect(stmt).to.be.an.instanceof(BreakStatement);
16 |   });
17 | });
18 | 


--------------------------------------------------------------------------------
/tests/parser/statements/continue/continue.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 7 | import { ContinueStatement } from '../../../../src/grammar/nodes/Statements';
 8 | 
 9 | describe('continue', () => {
10 |   it('parses correctly', () => {
11 |     const program = parse('continue');
12 | 
13 |     const stmt = program.body[0] as ContinueStatement;
14 |     expect(stmt.type).to.equal(SyntacticToken.CONTINUE_STMT);
15 |     expect(stmt).to.be.an.instanceof(ContinueStatement);
16 |   });
17 | });
18 | 


--------------------------------------------------------------------------------
/tests/parser/statements/expression/expression.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { NumberLiteral, Identifier } from '../../../../src/grammar/nodes/Expressions';
 9 | import { ExpressionStatement } from '../../../../src/grammar/nodes/Statements';
10 | 
11 | describe('expression', () => {
12 |   it('parses identifier correctly', () => {
13 |     const program = parse('x');
14 | 
15 |     function check(node: Node): void {
16 |       const stmt = node as ExpressionStatement;
17 |       expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
18 |       expect(stmt).to.be.an.instanceof(ExpressionStatement);
19 | 
20 |       const expr = stmt.expression as Identifier;
21 |       expect(expr.type).to.equal(SyntacticToken.IDENTIFIER);
22 |       expect(expr).to.be.an.instanceof(Identifier);
23 |       expect(expr.lexeme).to.equal('x');
24 |     }
25 | 
26 |     program.body.forEach(check);
27 |   });
28 | 
29 |   it('parses literal correctly', () => {
30 |     const program = parse('5');
31 | 
32 |     function check(node: Node): void {
33 |       const stmt = node as ExpressionStatement;
34 |       expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
35 |       expect(stmt).to.be.an.instanceof(ExpressionStatement);
36 | 
37 |       const expr = stmt.expression as NumberLiteral;
38 |       expect(expr.type).to.equal(SyntacticToken.NUMBER_LITERAL);
39 |       expect(expr).to.be.an.instanceof(NumberLiteral);
40 |       expect(expr.value.lexeme).to.equal('5');
41 |     }
42 | 
43 |     program.body.forEach(check);
44 |   });
45 | });
46 | 


--------------------------------------------------------------------------------
/tests/parser/statements/for/for.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { Identifier } from '../../../../src/grammar/nodes/Expressions';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { ExpressionStatement, ForStatement } from '../../../../src/grammar/nodes/Statements';
10 | 
11 | function checkBody(nodes: Node[], name: string): void {
12 |   expect(nodes.length).to.equal(1);
13 | 
14 |   const stmt = nodes[0] as ExpressionStatement;
15 |   expect(stmt.type).to.equal(SyntacticToken.EXPRESSION_STMT);
16 |   expect(stmt).to.be.an.instanceof(ExpressionStatement);
17 | 
18 |   const expr = stmt.expression as Identifier;
19 |   expect(expr.type).to.equal(SyntacticToken.IDENTIFIER);
20 |   expect(expr).to.be.an.instanceof(Identifier);
21 |   expect(expr.lexeme).to.equal(name);
22 | }
23 | 
24 | describe('for', () => {
25 |   it('parses correctly without type', () => {
26 |     const program = parse(loadRaw(__dirname, './without-type.tek'));
27 | 
28 |     function check(node: Node): void {
29 |       const stmt = node as ForStatement;
30 |       expect(stmt.type).to.equal(SyntacticToken.FOR_STMT);
31 |       expect(stmt).to.be.an.instanceof(ForStatement);
32 | 
33 |       expect(stmt.identifier.lexeme).to.equal('x');
34 |       expect(stmt.variableType).to.be.null;
35 | 
36 |       const object = stmt.object as Identifier;
37 |       expect(object.type).to.equal(SyntacticToken.IDENTIFIER);
38 |       expect(object).to.be.an.instanceof(Identifier);
39 |       expect(object.lexeme).to.equal('y');
40 | 
41 |       checkBody(stmt.body, 'z');
42 |     }
43 | 
44 |     program.body.forEach(check);
45 |   });
46 | 
47 |   it('parses correctly with type', () => {
48 |     const program = parse(loadRaw(__dirname, './with-type.tek'));
49 | 
50 |     function check(node: Node): void {
51 |       const stmt = node as ForStatement;
52 |       expect(stmt.type).to.equal(SyntacticToken.FOR_STMT);
53 |       expect(stmt).to.be.an.instanceof(ForStatement);
54 | 
55 |       expect(stmt.identifier.lexeme).to.equal('x');
56 | 
57 |       expect(stmt.variableType).to.not.be.null;
58 |       expect((stmt.variableType!.object as Identifier).lexeme).to.equal('A');
59 |       expect(stmt.variableType!.generics.length).to.equal(0);
60 |       expect(stmt.variableType!.arrayDepth).to.equal(0);
61 | 
62 |       const object = stmt.object as Identifier;
63 |       expect(object.type).to.equal(SyntacticToken.IDENTIFIER);
64 |       expect(object).to.be.an.instanceof(Identifier);
65 |       expect(object.lexeme).to.equal('y');
66 | 
67 |       checkBody(stmt.body, 'z');
68 |     }
69 | 
70 |     program.body.forEach(check);
71 |   });
72 | });
73 | 


--------------------------------------------------------------------------------
/tests/parser/statements/for/with-type.tek:
--------------------------------------------------------------------------------
 1 | for x: A in y { z }
 2 | 
 3 | for x: A in y { z
 4 | }
 5 | 
 6 | for x: A in y {
 7 | z }
 8 | 
 9 | for x: A in y
10 | { z }
11 | 
12 | for x: A in
13 | y { z }
14 | 
15 | for x: A
16 | in y { z }
17 | 
18 | for x:
19 | A in y { z }
20 | 
21 | for x
22 | : A in y { z }
23 | 
24 | for
25 | x: A in y { z }
26 | 
27 | for
28 | x: A
29 | in y
30 | { z }
31 | 
32 | for
33 | x
34 | :
35 | A
36 | in
37 | y
38 | {
39 | z
40 | }
41 | 


--------------------------------------------------------------------------------
/tests/parser/statements/for/without-type.tek:
--------------------------------------------------------------------------------
 1 | for x in y { z }
 2 | 
 3 | for x in y { z
 4 | }
 5 | 
 6 | for x in y {
 7 | z }
 8 | 
 9 | for x in y
10 | { z }
11 | 
12 | for x in
13 | y { z }
14 | 
15 | for x
16 | in y { z }
17 | 
18 | for
19 | x in y { z }
20 | 
21 | for
22 | x
23 | in
24 | y
25 | { z }
26 | 
27 | 
28 | for
29 | x
30 | in
31 | y
32 | {
33 | z
34 | }
35 | 


--------------------------------------------------------------------------------
/tests/parser/statements/return/return.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../../../test-utils';
 5 | 
 6 | import { Identifier } from '../../../../src/grammar/nodes/Expressions';
 7 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 8 | import { ReturnStatement } from '../../../../src/grammar/nodes/Statements';
 9 | 
10 | describe('return', () => {
11 |   it('parses correctly without value', () => {
12 |     const program = parse('return');
13 | 
14 |     const stmt = program.body[0] as ReturnStatement;
15 |     expect(stmt.type).to.equal(SyntacticToken.RETURN_STMT);
16 |     expect(stmt).to.be.an.instanceof(ReturnStatement);
17 |   });
18 | 
19 |   it('parses correctly with value', () => {
20 |     const program = parse('return x');
21 | 
22 |     const stmt = program.body[0] as ReturnStatement;
23 |     expect(stmt.type).to.equal(SyntacticToken.RETURN_STMT);
24 |     expect(stmt).to.be.an.instanceof(ReturnStatement);
25 | 
26 |     const expr = stmt.expression as Identifier;
27 |     expect(expr.type).to.equal(SyntacticToken.IDENTIFIER);
28 |     expect(expr).to.be.an.instanceof(Identifier);
29 |     expect(expr.lexeme).to.equal('x');
30 |   });
31 | });
32 | 


--------------------------------------------------------------------------------
/tests/parser/statements/while/while.tek:
--------------------------------------------------------------------------------
 1 | while x { y }
 2 | 
 3 | while x { y
 4 | }
 5 | 
 6 | while x {
 7 | y }
 8 | 
 9 | while x
10 | { y }
11 | 
12 | while
13 | x { y }
14 | 
15 | 
16 | while
17 | x
18 | { y }
19 | 
20 | 
21 | while
22 | x
23 | {
24 | y
25 | }
26 | 


--------------------------------------------------------------------------------
/tests/parser/statements/while/while.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { Identifier } from '../../../../src/grammar/nodes/Expressions';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { ExpressionStatement, WhileStatement } from '../../../../src/grammar/nodes/Statements';
10 | 
11 | describe('while', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './while.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       const stmt = node as WhileStatement;
17 |       expect(stmt.type).to.equal(SyntacticToken.WHILE_STMT);
18 |       expect(stmt).to.be.an.instanceof(WhileStatement);
19 | 
20 |       const condition = stmt.condition as Identifier;
21 |       expect(condition.type).to.equal(SyntacticToken.IDENTIFIER);
22 |       expect(condition).to.be.an.instanceof(Identifier);
23 |       expect(condition.lexeme).to.equal('x');
24 | 
25 |       expect(stmt.body.length).to.equal(1);
26 |       expect(((stmt.body[0] as ExpressionStatement).expression as Identifier).lexeme).to.equal('y');
27 |     }
28 | 
29 |     program.body.forEach(check);
30 |   });
31 | });
32 | 


--------------------------------------------------------------------------------
/tests/parser/statements/yield/yield.tek:
--------------------------------------------------------------------------------
1 | yield x
2 | 
3 | yield
4 | x
5 | 


--------------------------------------------------------------------------------
/tests/parser/statements/yield/yield.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse, loadRaw } from '../../../test-utils';
 5 | 
 6 | import { Node } from '../../../../src/grammar/Node';
 7 | import { Identifier } from '../../../../src/grammar/nodes/Expressions';
 8 | import { SyntacticToken } from '../../../../src/grammar/SyntacticToken';
 9 | import { YieldStatement } from '../../../../src/grammar/nodes/Statements';
10 | 
11 | describe('yield', () => {
12 |   it('parses correctly', () => {
13 |     const program = parse(loadRaw(__dirname, './yield.tek'));
14 | 
15 |     function check(node: Node): void {
16 |       const stmt = node as YieldStatement;
17 |       expect(stmt.type).to.equal(SyntacticToken.YIELD_STMT);
18 |       expect(stmt).to.be.an.instanceof(YieldStatement);
19 | 
20 |       const expr = stmt.expression as Identifier;
21 |       expect(expr.type).to.equal(SyntacticToken.IDENTIFIER);
22 |       expect(expr).to.be.an.instanceof(Identifier);
23 |       expect(expr.lexeme).to.equal('x');
24 |     }
25 | 
26 |     program.body.forEach(check);
27 |   });
28 | });
29 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/boolean.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | describe('boolean', () => {
 9 |   it('parses "true" correctly', () => {
10 |     const { tokens } = new Tokenizer('true').tokenize();
11 | 
12 |     expect(tokens[0].type).to.equal(LexicalToken.BOOLEAN);
13 |     expect(tokens[0].lexeme).to.equal('true');
14 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 4] });
15 |   });
16 | 
17 |   it('parses "false" correctly', () => {
18 |     const { tokens } = new Tokenizer('false').tokenize();
19 | 
20 |     expect(tokens[0].type).to.equal(LexicalToken.BOOLEAN);
21 |     expect(tokens[0].lexeme).to.equal('false');
22 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 5] });
23 |   });
24 | });
25 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/comments.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | describe('comments', () => {
 9 |   it('returns 0 comments when program is empty', () => {
10 |     const { comments } = new Tokenizer('').tokenize();
11 |     expect(comments.length).to.equal(0);
12 |   });
13 | 
14 |   it('parses single comment correctly', () => {
15 |     const { comments } = new Tokenizer('# this is a comment').tokenize();
16 |     expect(comments.length).to.equal(1);
17 | 
18 |     expect(comments[0].lexeme).to.equal('# this is a comment');
19 |     expect(comments[0].type).to.equal(LexicalToken.COMMENT);
20 |     expect(comments[0].span).to.deep.equal({
21 |       start: [0, 0],
22 |       end: [0, 19],
23 |     });
24 |   });
25 | 
26 |   it('parses multiple comments correctly', () => {
27 |     const { comments } = new Tokenizer('# this is a comment\n\n# this is another #comment').tokenize();
28 |     expect(comments.length).to.equal(2);
29 | 
30 |     expect(comments[0].lexeme).to.equal('# this is a comment');
31 |     expect(comments[0].type).to.equal(LexicalToken.COMMENT);
32 |     expect(comments[0].span).to.deep.equal({
33 |       start: [0, 0],
34 |       end: [0, 19],
35 |     });
36 | 
37 |     expect(comments[1].lexeme).to.equal('# this is another #comment');
38 |     expect(comments[1].type).to.equal(LexicalToken.COMMENT);
39 |     expect(comments[1].span).to.deep.equal({
40 |       start: [3, 0],
41 |       end: [3, 26],
42 |     });
43 |   });
44 | 
45 |   it('parses comment on filled line correctly', () => {
46 |     const { comments } = new Tokenizer('while true # loop forever').tokenize();
47 |     expect(comments.length).to.equal(1);
48 | 
49 |     expect(comments[0].lexeme).to.equal('# loop forever');
50 |     expect(comments[0].type).to.equal(LexicalToken.COMMENT);
51 |     expect(comments[0].span).to.deep.equal({
52 |       start: [0, 11],
53 |       end: [0, 25],
54 |     });
55 |   });
56 | });
57 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/identifier.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | const valid = [
 9 |   'a',
10 |   '_',
11 |   '_0',
12 |   '_a',
13 |   'abc',
14 |   'ABC',
15 |   '___',
16 |   'any',
17 |   '_05ab',
18 |   'test',
19 |   'HelloWorld',
20 | ];
21 | 
22 | const invalid = [
23 |   '0_',
24 |   'function',
25 |   'var',
26 |   'for',
27 |   'class',
28 |   'static',
29 |   'true',
30 |   '0',
31 | ];
32 | 
33 | describe('identifier', () => {
34 |   it('parses valid identifiers correctly', () => {
35 |     valid.forEach((identifier) => {
36 |       const { tokens } = new Tokenizer(identifier).tokenize();
37 | 
38 |       expect(tokens[0].type).to.equal(LexicalToken.IDENTIFIER);
39 |       expect(tokens[0].lexeme).to.equal(identifier);
40 |       expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, identifier.length] });
41 |     });
42 |   });
43 | 
44 |   it('does not see invalid identifiers as identifiers', () => {
45 |     invalid.forEach((string) => {
46 |       const { tokens } = new Tokenizer(string).tokenize();
47 |       expect(tokens[0].type).to.not.equal(LexicalToken.IDENTIFIER);
48 |     });
49 |   });
50 | });
51 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/keywords.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | const map = {
 9 |   // Boolean
10 |   true: LexicalToken.BOOLEAN,
11 |   false: LexicalToken.BOOLEAN,
12 | 
13 |   // Keywords
14 |   class: LexicalToken.CLASS,
15 |   new: LexicalToken.NEW,
16 |   static: LexicalToken.STATIC,
17 |   extends: LexicalToken.EXTENDS,
18 | 
19 |   this: LexicalToken.THIS,
20 |   super: LexicalToken.SUPER,
21 |   instanceof: LexicalToken.INSTANCEOF,
22 | 
23 |   if: LexicalToken.IF,
24 |   else: LexicalToken.ELSE,
25 | 
26 |   function: LexicalToken.FUNCTION,
27 |   return: LexicalToken.RETURN,
28 |   void: LexicalToken.VOID,
29 | 
30 |   async: LexicalToken.ASYNC,
31 | 
32 |   import: LexicalToken.IMPORT,
33 |   as: LexicalToken.AS,
34 | 
35 |   for: LexicalToken.FOR,
36 |   in: LexicalToken.IN,
37 |   while: LexicalToken.WHILE,
38 | 
39 |   continue: LexicalToken.CONTINUE,
40 |   break: LexicalToken.BREAK,
41 |   yield: LexicalToken.YIELD,
42 | 
43 |   and: LexicalToken.AND,
44 |   or: LexicalToken.OR,
45 | 
46 |   var: LexicalToken.VAR,
47 | };
48 | 
49 | describe('keywords', () => {
50 |   it('parses correctly', () => {
51 |     Object.entries(map).forEach(([keyword, type]) => {
52 |       const { tokens } = new Tokenizer(keyword).tokenize();
53 | 
54 |       expect(tokens[0].type).to.equal(type);
55 |       expect(tokens[0].lexeme).to.equal(keyword);
56 |       expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, keyword.length] });
57 |     });
58 |   });
59 | });
60 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/number.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | describe('number', () => {
 9 |   it('parses zero correctly', () => {
10 |     const { tokens } = new Tokenizer('0').tokenize();
11 | 
12 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
13 |     expect(tokens[0].lexeme).to.equal('0');
14 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 1] });
15 |   });
16 | 
17 |   it('parses multiple zeros correctly', () => {
18 |     const { tokens } = new Tokenizer('0000').tokenize();
19 | 
20 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
21 |     expect(tokens[0].lexeme).to.equal('0000');
22 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 4] });
23 |   });
24 | 
25 |   it('parses number correctly', () => {
26 |     const { tokens } = new Tokenizer('5').tokenize();
27 | 
28 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
29 |     expect(tokens[0].lexeme).to.equal('5');
30 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 1] });
31 |   });
32 | 
33 |   it('parses number prefixed with zeros correctly', () => {
34 |     const { tokens } = new Tokenizer('007').tokenize();
35 | 
36 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
37 |     expect(tokens[0].lexeme).to.equal('007');
38 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 3] });
39 |   });
40 | 
41 |   it('parses decimal correctly correctly', () => {
42 |     const { tokens } = new Tokenizer('0.9').tokenize();
43 | 
44 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
45 |     expect(tokens[0].lexeme).to.equal('0.9');
46 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 3] });
47 |   });
48 | 
49 |   it('parses number with underscores correctly', () => {
50 |     const { tokens } = new Tokenizer('0__0').tokenize();
51 | 
52 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
53 |     expect(tokens[0].lexeme).to.equal('0__0');
54 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 4] });
55 |   });
56 | 
57 |   it('parses decimal with underscores correctly', () => {
58 |     const { tokens } = new Tokenizer('0__0.5__5').tokenize();
59 | 
60 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
61 |     expect(tokens[0].lexeme).to.equal('0__0.5__5');
62 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 9] });
63 |   });
64 | 
65 |   it('sees a number with trailing underscores as a number', () => {
66 |     const { tokens } = new Tokenizer('6_').tokenize();
67 | 
68 |     expect(tokens[0].type).to.equal(LexicalToken.NUMBER);
69 |     expect(tokens[0].lexeme).to.equal('6_');
70 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 2] });
71 |   });
72 | 
73 |   it('does not see number prefixed with underscores as a number', () => {
74 |     const { tokens } = new Tokenizer('_6').tokenize();
75 |     expect(tokens[0].type).to.not.equal(LexicalToken.NUMBER);
76 |   });
77 | });
78 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/operators-punctuation.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | const map = {
 9 |   // Operators
10 |   '+': LexicalToken.PLUS,
11 |   '-': LexicalToken.MINUS,
12 |   '*': LexicalToken.STAR,
13 |   '/': LexicalToken.SLASH,
14 |   '%': LexicalToken.PERCENT,
15 |   '^': LexicalToken.CARET,
16 | 
17 |   '=': LexicalToken.EQUAL,
18 |   '==': LexicalToken.EQUAL_EQUAL,
19 | 
20 |   '!': LexicalToken.BANG,
21 |   '!=': LexicalToken.BANG_EQUAL,
22 | 
23 |   '<': LexicalToken.LT,
24 |   '<=': LexicalToken.LT_EQUAL,
25 | 
26 |   '>': LexicalToken.GT,
27 |   '>=': LexicalToken.GT_EQUAL,
28 | 
29 |   // Punctuation
30 |   '.': LexicalToken.DOT,
31 |   ',': LexicalToken.COMMA,
32 |   '[': LexicalToken.L_SQB,
33 |   ']': LexicalToken.R_SQB,
34 |   '(': LexicalToken.L_PAR,
35 |   ')': LexicalToken.R_PAR,
36 |   '{': LexicalToken.L_BRACE,
37 |   '}': LexicalToken.R_BRACE,
38 |   ':': LexicalToken.COLON,
39 | };
40 | 
41 | describe('operators and punctuation', () => {
42 |   it('parses correctly', () => {
43 |     Object.entries(map).forEach(([char, type]) => {
44 |       const { tokens } = new Tokenizer(char).tokenize();
45 | 
46 |       expect(tokens[0].type).to.equal(type);
47 |       expect(tokens[0].lexeme).to.equal(char);
48 |       expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, char.length] });
49 |     });
50 |   });
51 | });
52 | 


--------------------------------------------------------------------------------
/tests/parser/tokenizer/string.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { Tokenizer } from '../../../src/parser/Tokenizer';
 5 | 
 6 | import { LexicalToken } from '../../../src/grammar/LexicalToken';
 7 | 
 8 | describe('string', () => {
 9 |   it('parses an empty string correctly', () => {
10 |     const { tokens } = new Tokenizer("''").tokenize();
11 | 
12 |     expect(tokens[0].type).to.equal(LexicalToken.STRING);
13 |     expect(tokens[0].lexeme).to.equal("''");
14 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 2] });
15 |   });
16 | 
17 |   it('parses a string with chars correctly', () => {
18 |     const { tokens } = new Tokenizer("'abc'").tokenize();
19 | 
20 |     expect(tokens[0].type).to.equal(LexicalToken.STRING);
21 |     expect(tokens[0].lexeme).to.equal("'abc'");
22 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 5] });
23 |   });
24 | 
25 |   it('parses a string with newline correctly', () => {
26 |     const { tokens } = new Tokenizer("'\\n'").tokenize();
27 | 
28 |     expect(tokens[0].type).to.equal(LexicalToken.STRING);
29 |     expect(tokens[0].lexeme).to.equal("'\\n'");
30 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 4] });
31 |   });
32 | 
33 |   it('parses a string with escaped quote correctly', () => {
34 |     const { tokens } = new Tokenizer("'\\''").tokenize();
35 | 
36 |     expect(tokens[0].type).to.equal(LexicalToken.STRING);
37 |     expect(tokens[0].lexeme).to.equal("'\\''");
38 |     expect(tokens[0].span).to.deep.equal({ start: [0, 0], end: [0, 4] });
39 |   });
40 | 
41 |   it('does not see an escaped quote as the end of a string', () => {
42 |     const { diagnostics, tokens } = new Tokenizer("'\\'").tokenize();
43 | 
44 |     expect(diagnostics.length).to.be.greaterThan(0);
45 |     expect(tokens[0].type).to.not.equal(LexicalToken.STRING);
46 |   });
47 | });
48 | 


--------------------------------------------------------------------------------
/tests/symbols/index.test.ts:
--------------------------------------------------------------------------------
1 | import 'mocha';
2 | 
3 | import { loadTestsInDir } from '../test-utils';
4 | 
5 | loadTestsInDir(__dirname, '.');
6 | 


--------------------------------------------------------------------------------
/tests/symbols/mangle.test.ts:
--------------------------------------------------------------------------------
 1 | import 'mocha';
 2 | import { expect } from 'chai';
 3 | 
 4 | import { parse } from '../test-utils';
 5 | 
 6 | import * as grammar from '../../src/grammar';
 7 | import { mangleFunctionName, mangleConstructor } from '../../src/symbols/mangle';
 8 | 
 9 | type Func = grammar.FunctionDeclaration | grammar.EmptyFunctionDeclaration;
10 | 
11 | describe('mangle', () => {
12 |   describe('correctly mangles function', () => {
13 |     const tests: [string, string][] = [
14 |       ['function foo()', 'foo-'],
15 |       ['function foo(): Object', 'foo-'],
16 |       ['function foo(): Array', 'foo-'],
17 | 
18 |       ['function foo(x: Object)', 'foo-Object'],
19 |       ['function foo(x: Object, y: Number)', 'foo-Object-Number'],
20 | 
21 |       ['function foo(x: Object[])', 'foo-Object['],
22 |       ['function foo(x: Object[][])', 'foo-Object[['],
23 | 
24 |       ['function foo(x: Array)', 'foo-Array'],
25 |       ['function foo(x: Array)', 'foo-Array'],
26 | 
27 |       ['function  foo()', 'foo-'],
28 |       ['function  foo(x: T)', 'foo-Object'],
29 | 
30 |       ['function  foo()', 'foo-'],
31 |       ['function  foo(x: T)', 'foo-E'],
32 | 
33 |       ['function  foo(x: T)', 'foo-E['],
34 |       ['function  foo(x: T)', 'foo-E[['],
35 | 
36 |       ['function > foo(t: T, e: E)', 'foo-Object-Array'],
37 |       ['function > foo(a: A, b: B, c: C)', 'foo-B-B-Array'],
38 |     ];
39 | 
40 |     tests.forEach(([input, output]) => {
41 |       it(input, () => {
42 |         const program = parse(input);
43 |         const mangled = mangleFunctionName(program.body[0] as Func);
44 | 
45 |         expect(mangled).to.equal(output);
46 |       });
47 |     });
48 |   });
49 | 
50 |   describe('correctly mangles constructor', () => {
51 |     const tests: [string, string][] = [
52 |       ['class A { new(t: T) {} }', 'Object'],
53 |       ['class A { new(t: T) {} }', 'E'],
54 |       ['class A { new(t: T, e: E) {} }', 'Object-Object'],
55 | 
56 |       ['class A { new(d: D) {} }', 'C'],
57 |       ['class A { new(b: B) {} }', 'C[['],
58 | 
59 |       ['class A> { new(b: B, c: C, d: D) {} }', 'C-C-Array'],
60 |     ];
61 | 
62 |     tests.forEach(([input, output]) => {
63 |       it(input, () => {
64 |         const program = parse(input);
65 |         const decl = program.body[0] as grammar.ClassDeclaration;
66 | 
67 |         const mangled = mangleConstructor(decl.constructors[0], decl.genericParams);
68 | 
69 |         expect(mangled).to.equal(output);
70 |       });
71 |     });
72 |   });
73 | });
74 | 


--------------------------------------------------------------------------------
/tests/test-utils.ts:
--------------------------------------------------------------------------------
 1 | /* eslint-disable eslint-comments/no-unlimited-disable */
 2 | 
 3 | import { join } from 'path';
 4 | import { readFileSync, readdirSync, lstatSync } from 'fs';
 5 | 
 6 | import { Program } from '../src/grammar/nodes/Other';
 7 | 
 8 | import { Tokenizer } from '../src/parser/Tokenizer';
 9 | import { Parser } from '../src/parser/Parser';
10 | import { Token } from '../src/grammar/Token';
11 | 
12 | export function loadRaw(base: string, path: string): string {
13 |   const fullPath = join(base, path);
14 |   const content = readFileSync(fullPath);
15 |   return content.toString();
16 | }
17 | 
18 | export function tokenize(code: string): Token[] {
19 |   const tokenizer = new Tokenizer(code);
20 |   const result = tokenizer.tokenize();
21 | 
22 |   if (result.diagnostics.length) {
23 |     let msg = `Error tokenizing:\n${code}\nInfo:`;
24 | 
25 |     result.diagnostics.forEach((diagnostic) => {
26 |       msg += `\n- ${diagnostic.msg}`;
27 |     });
28 | 
29 |     throw new Error(msg);
30 |   }
31 | 
32 |   return result.tokens;
33 | }
34 | 
35 | export function parse(code: string): Program {
36 |   const tokens = tokenize(code);
37 | 
38 |   const parser = new Parser(tokens);
39 |   const result = parser.parse();
40 | 
41 |   if (result.diagnostics.length) {
42 |     let msg = `Error parsing:\n${code}\nInfo:`;
43 | 
44 |     result.diagnostics.forEach((diagnostic) => {
45 |       msg += `\n- ${diagnostic.msg}`;
46 |     });
47 | 
48 |     throw new Error(msg);
49 |   }
50 | 
51 |   return result.ast;
52 | }
53 | 
54 | export function loadIndexInDir(base: string, dir: string): void {
55 |   const path = join(base, dir);
56 | 
57 |   const index = readdirSync(path).find(file => /index(?:\.test)?\.(?:ts|js)/.test(file));
58 |   if (index) {
59 |     require(join(path, index)); // eslint-disable-line
60 |   } else {
61 |     throw new Error('No index file found');
62 |   }
63 | }
64 | 
65 | export function loadTestsInDir(base: string, dir: string): void {
66 |   const path = join(base, dir);
67 | 
68 |   // eslint-disable-next-line @typescript-eslint/no-require-imports
69 |   readdirSync(path)
70 |     .filter(file => file.endsWith('.ts') || file.endsWith('.js'))
71 |     .map(file => join(path, file))
72 |     .filter(file => lstatSync(file).isFile())
73 |     .forEach(require);
74 | }
75 | 
76 | export function getDirsFrom(base: string, dir: string): string[] {
77 |   const path = join(base, dir);
78 | 
79 |   return readdirSync(path)
80 |     .filter(file => lstatSync(join(path, file)).isDirectory())
81 |     .map(file => join(dir, file));
82 | }
83 | 
84 | export function sanitize(text: string): string {
85 |   return text
86 |     .replace(/\\/g, '\\\\')
87 |     .replace(/\n/g, '\\n')
88 |     .replace(/\t/g, '\\t');
89 | }
90 | 


--------------------------------------------------------------------------------
/tests/walker/ASTWalker.test.ts:
--------------------------------------------------------------------------------
  1 | import 'mocha';
  2 | import { expect } from 'chai';
  3 | 
  4 | import { parse } from '../test-utils';
  5 | 
  6 | import { ASTWalker } from '../../src/walker/ASTWalker';
  7 | import { WalkerContext } from '../../src/walker/WalkerContext';
  8 | 
  9 | import * as grammar from '../../src/grammar';
 10 | import { Node } from '../../src/grammar/Node';
 11 | 
 12 | describe('ASTWalker', () => {
 13 |   const program = parse('10 + 2 * 5');
 14 | 
 15 |   it('calls the callbacks on enter', () => {
 16 |     const array: Node[] = [];
 17 |     let parentCount = 0;
 18 | 
 19 |     function walkerCB(node: Node, context: WalkerContext): void {
 20 |       array.push(node);
 21 | 
 22 |       expect(context.parents.length).to.equal(parentCount);
 23 |       parentCount += 1;
 24 |     }
 25 | 
 26 |     new ASTWalker(program)
 27 |       .onEnter(grammar.Program, walkerCB)
 28 |       .onEnter(grammar.ExpressionStatement, walkerCB)
 29 |       .onEnter(grammar.BinaryExpression, walkerCB)
 30 |       .walk();
 31 | 
 32 |     expect(parentCount).to.equal(4);
 33 |     expect(array.length).to.equal(4);
 34 |     expect(array).to.deep.equal([
 35 |       program,
 36 |       program.body[0],
 37 |       (program.body[0] as any).expression,
 38 |       (program.body[0] as any).expression.right,
 39 |     ]);
 40 |   });
 41 | 
 42 |   it('calls the callbacks on leave', () => {
 43 |     const array: Node[] = [];
 44 |     let parentCount = 3;
 45 | 
 46 |     function walkerCB(node: Node, context: WalkerContext): void {
 47 |       array.push(node);
 48 | 
 49 |       expect(context.parents.length).to.equal(parentCount);
 50 |       parentCount -= 1;
 51 |     }
 52 | 
 53 |     new ASTWalker(program)
 54 |       .onLeave(grammar.Program, walkerCB)
 55 |       .onLeave(grammar.ExpressionStatement, walkerCB)
 56 |       .onLeave(grammar.BinaryExpression, walkerCB)
 57 |       .walk();
 58 | 
 59 |     expect(parentCount).to.equal(-1);
 60 |     expect(array.length).to.equal(4);
 61 |     expect(array).to.deep.equal([
 62 |       (program.body[0] as any).expression.right,
 63 |       (program.body[0] as any).expression,
 64 |       program.body[0],
 65 |       program,
 66 |     ]);
 67 |   });
 68 | 
 69 |   it('calls the callbacks when both enter and leave are provided', () => {
 70 |     const array: Node[] = [];
 71 |     let parentCount = 0;
 72 | 
 73 |     function walkerCB(node: Node, context: WalkerContext, enter: boolean): void {
 74 |       array.push(node);
 75 | 
 76 |       if (!enter) {
 77 |         parentCount -= 1;
 78 |       }
 79 | 
 80 |       expect(context.parents.length).to.equal(parentCount);
 81 | 
 82 |       if (enter) {
 83 |         parentCount += 1;
 84 |       }
 85 |     }
 86 | 
 87 |     const onEnter = (node: Node, context: WalkerContext): void => walkerCB(node, context, true);
 88 |     const onLeave = (node: Node, context: WalkerContext): void => walkerCB(node, context, false);
 89 | 
 90 |     new ASTWalker(program)
 91 |       .onEnter(grammar.Program, onEnter)
 92 |       .onLeave(grammar.Program, onLeave)
 93 |       .onEnter(grammar.ExpressionStatement, onEnter)
 94 |       .onLeave(grammar.ExpressionStatement, onLeave)
 95 |       .onEnter(grammar.BinaryExpression, onEnter)
 96 |       .onLeave(grammar.BinaryExpression, onLeave)
 97 |       .walk();
 98 | 
 99 |     expect(parentCount).to.equal(0);
100 |     expect(array.length).to.equal(8);
101 |     expect(array).to.deep.equal([
102 |       program,
103 |       program.body[0],
104 |       (program.body[0] as any).expression,
105 |       (program.body[0] as any).expression.right,
106 |       (program.body[0] as any).expression.right,
107 |       (program.body[0] as any).expression,
108 |       program.body[0],
109 |       program,
110 |     ]);
111 |   });
112 | 
113 |   it('provides the correct scope');
114 | });
115 | 


--------------------------------------------------------------------------------
/tests/walker/index.test.ts:
--------------------------------------------------------------------------------
1 | import 'mocha';
2 | 
3 | import { loadTestsInDir } from '../test-utils';
4 | 
5 | loadTestsInDir(__dirname, '.');
6 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "esnext",
 4 |     "module": "commonjs",
 5 | 
 6 |     "strict": true,
 7 | 
 8 |     "noUnusedLocals": true,
 9 |     "noUnusedParameters": true,
10 | 
11 |     "sourceMap": true
12 |   }
13 | }
14 | 


--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
 1 | /* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */
 2 | const path = require('path');
 3 | 
 4 | module.exports = {
 5 |   entry: './src/index',
 6 |   mode: 'development',
 7 |   devtool: 'eval',
 8 |   module: {
 9 |     rules: [
10 |       {
11 |         test: /\.ts$/,
12 |         loader: 'ts-loader',
13 |       },
14 |       {
15 |         test: /\.tek$/,
16 |         loader: 'raw-loader',
17 |       },
18 |     ],
19 |   },
20 |   resolve: {
21 |     extensions: [
22 |       '.ts',
23 |       '.js',
24 |     ],
25 |   },
26 |   output: {
27 |     library: 'syntek',
28 |     libraryTarget: 'umd',
29 |     globalObject: "(typeof self !== 'undefined' ? self : this)", // https://github.com/webpack/webpack/issues/6522
30 |     filename: 'bundle.js',
31 |     path: path.resolve(__dirname, './build'),
32 |   },
33 | };
34 | 


--------------------------------------------------------------------------------