├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── README.md ├── examples ├── brainfuck.ts ├── fibonacci.ts ├── llvm │ └── printf.ts ├── multi │ ├── lib.ts │ └── main.ts ├── riscv.ts └── storage.ts ├── package-lock.json ├── package.json ├── res ├── brainfuck.png └── flow.png ├── src ├── codegen │ ├── array-literal-expression.ts │ ├── binary-expression.ts │ ├── boolean-literal-expression.ts │ ├── call-expression.ts │ ├── class-declaration.ts │ ├── condition-expression.ts │ ├── do-statement.ts │ ├── element-access-expression.ts │ ├── enum-declaration.ts │ ├── export-declaration.ts │ ├── for-of-statement.ts │ ├── for-statement.ts │ ├── function-declaration.ts │ ├── global-object-int8array.ts │ ├── if-statement.ts │ ├── index.ts │ ├── module-declaration.ts │ ├── new-expression.ts │ ├── numeric-expression.ts │ ├── object-literal-expression.ts │ ├── prefix-unary-expression.ts │ ├── property-access-expression.ts │ ├── return-statement.ts │ ├── string-literal-expression.ts │ ├── variable-declaration.ts │ └── while-statement.ts ├── index.ts ├── prelude.ts ├── stdlib │ ├── index.ts │ └── syscall.ll ├── symtab.ts └── tests │ ├── application.spec.ts │ ├── array-literal-expression.spec.ts │ ├── binary-expression.spec.ts │ ├── binary.spec.ts │ ├── class-declaration.spec.ts │ ├── condition-expression.spec.ts │ ├── element-access-expression.spec.ts │ ├── enum-declaration.spec.ts │ ├── for-of-statement.spec.ts │ ├── function-declaration.spec.ts │ ├── if-statement.spec.ts │ ├── int8array.spec.ts │ ├── object-literal-expression.spec.ts.backup │ ├── statement.sepc.ts │ ├── stdlib.spec.ts │ ├── string-literal.spec.ts │ ├── switch-statement.spec.ts │ ├── util.ts │ └── variable-declaration.spec.ts ├── tsconfig.json ├── tsconfig.module.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | /examples/foo.ts 64 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: node_js 5 | 6 | node_js: 7 | - '10' 8 | 9 | cache: 10 | directories: 11 | - /node_modules 12 | 13 | after_script: 14 | - npm test 15 | - npm run cov:send 16 | - npm run cov:check 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of minits authors for copyright purposes. 2 | 3 | # Names should be added to this file as 4 | # Name or Organization 5 | # The email address is not required for organizations. 6 | 7 | # Please ensure that the list is sorted by lexicographic 8 | # order by name. 9 | 10 | mohanson 11 | yejiayu 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Cryptape 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minits 2 | 3 | [![Build Status](https://travis-ci.org/cryptape/minits.svg?branch=master)](https://travis-ci.org/cryptape/minits) 4 | 5 | Typescript with LLVM backend. 6 | 7 | ![img](/res/flow.png) 8 | 9 | # Installing minits on Linux or macOS 10 | 11 | First of all, you need install LLVM, See [https://llvm.org/docs/GettingStarted.html](https://llvm.org/docs/GettingStarted.html). But if you are using Ubuntu or other distributions, using the built-in package management tool is a more convenient option like: 12 | 13 | ``` 14 | $ apt install -y llvm 15 | ``` 16 | 17 | Then install minits: 18 | 19 | ``` 20 | $ git clone https://github.com/cryptape/minits 21 | $ npm install 22 | $ npm run build 23 | ``` 24 | 25 | # Writing and Compiling a TypeScript Program 26 | 27 | Filename: main.ts 28 | 29 | ```ts 30 | function main(): number { 31 | console.log("Hello World!"); 32 | return 0; 33 | } 34 | ``` 35 | 36 | Save the file and open your terminal: 37 | 38 | ``` 39 | $ node build/main/index.js build main.ts -o main.ll 40 | $ clang main.ll -o main 41 | $ ./main 42 | ``` 43 | 44 | # Analysis for a minits Program: Brainfuck 45 | 46 | minits is a completely static language, similar to clang or rust, `function main()` is the entry point to every executable minits program. It receives(optional) 2 parameters `argc: number` and `argv: string[]`, and returns the exit code. So you can also write it as `function main(argc: number, argv: string[]): number`. 47 | 48 | We suggest you read the source code under `./examples`. Let's hava a look at `./examples/brainfuck.ts`. Brainfuck is an esoteric programming language created in 1993 by Urban Müller, and is notable for its extreme minimalism. We wrote a brainfuck interpreter by minits. Compile this interpreter by 49 | 50 | ``` 51 | $ node build/main/index.js build examples/brainfuck.ts -o brainfuck.ll 52 | $ clang brainfuck.ll -o brainfuck 53 | ``` 54 | 55 | And then execute a piece of code that generates Sierpinski Triangle: 56 | 57 | ``` 58 | $ ./brainfuck ">++++[<++++++++>-]>++++++++[>++++<-]>>++>>>+>>>+<<<<<<<<<<[-[->+<]>[-<+>>>.<<]>>>[[->++++++++[>++++<-]>.<<[->+<]+>[->++++++++++<<+>]>.[-]>]]+<<<[-[->+<]+>[-<+>>>-[->+<]++>[-<->]<<<]<<<<]++++++++++.+++.[-]<]+++++" 59 | ``` 60 | 61 | ![img](/res/brainfuck.png) 62 | 63 | Most ts syntax is available at now, but there is still a lot of work to be done. 64 | 65 | # Project status 66 | 67 | We plan to implement the following syntax: 68 | 69 | ## Types 70 | 71 | - [ ] Primitive Types 72 | - [x] number(support signed 64): `0x10`, `12` 73 | - [x] boolean: `true`, `false` 74 | - [x] string: `"Hello"` 75 | - [x] void 76 | - [ ] null 77 | - [ ] * undefined 78 | - [x] enum: `enum { a = 1, b, c }` 79 | - [ ] Object 80 | - [x] Array 81 | - [ ] Tuple 82 | 83 | ## Expression 84 | 85 | - [x] Assignment: `let a: number = 1;`, `let a: number[] = [1, 2, 3]` 86 | - [x] Parentheses 87 | - [x] Function Expressions 88 | - [ ] Arrow Functions 89 | - [ ] * Class Expressions 90 | - [x] Function Calls 91 | - [x] `++` / `--` 92 | - [x] `+` / `-` / `~` 93 | - [x] `!` 94 | - [ ] * `typeof` 95 | - [x] +: number + number, string + string, eg. 96 | - [x] `*`, `/`, `%,` `–`, `<<`, `>>`, `>>>`, `&`, `^`, and `|` operators 97 | - [x] `<`, `>`, `<=`, `>=`, `==`, `!=`, `===`, and `!==` operators 98 | - [ ] * `in` 99 | - [x] `&&` and `||` 100 | - [x] The Conditional Operator: `test ? expr1 : expr2` 101 | - [x] `*=`, `/=`, `%=`, `+=`, `-=`, `<<=`, `>>=`, `>>>=`, `&=`, `^=`, `|=` 102 | - [ ] Destructuring Assignment: `[x, y] = [y, x];` 103 | 104 | ## Statements 105 | 106 | - [x] Block 107 | - [x] Variable Statements 108 | - [x] Let and Const Declarations 109 | - [x] If, Do, and While Statements 110 | - [x] For Statements 111 | - [ ] For-In Statements 112 | - [x] For-Of Statements 113 | - [x] Continue Statements 114 | - [x] Break Statements 115 | - [x] Return Statements 116 | - [ ] With Statements 117 | - [x] Switch Statements 118 | - [ ] Throw Statements 119 | - [ ] * Try Statements 120 | 121 | ## Function 122 | 123 | - [x] Function Declarations 124 | - [x] Function Implementations 125 | 126 | 127 | ## Interfaces 128 | 129 | TODO 130 | 131 | ## Class 132 | 133 | - [ ] Class Declarations 134 | - [ ] New class 135 | 136 | ## Build in functions 137 | 138 | - [ ] See: https://www.tutorialspoint.com/javascript/javascript_builtin_functions.htm 139 | 140 | # Licences 141 | 142 | [MIT](/LICENSE) 143 | -------------------------------------------------------------------------------- /examples/brainfuck.ts: -------------------------------------------------------------------------------- 1 | // Inspired by this project: https://github.com/mohanson/brainfuck 2 | // 3 | // $ node build/main/index.js build examples/brainfuck.ts -o brainfuck.ll 4 | // $ clang brainfuck.ll -o brainfuck 5 | // 6 | // $ ./brainfuck "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>." 7 | // $ ./brainfuck ">++++[<++++++++>-]>++++++++[>++++<-]>>++>>>+>>>+<<<<<<<<<<[-[->+<]>[-<+>>>.<<]>>>[[->++++++++[>++++<-]>.<<[->+<]+>[->++++++++++<<+>]>.[-]>]]+<<<[-[->+<]+>[-<+>>>-[->+<]++>[-<->]<<<]<<<<]++++++++++.+++.[-]<]+++++" 8 | 9 | const enum Opcode { 10 | SHR = '>', 11 | SHL = '<', 12 | ADD = '+', 13 | SUB = '-', 14 | PUTCHAR = '.', 15 | GETCHAR = ',', 16 | LB = '[', 17 | RB = ']', 18 | } 19 | 20 | function uint8(n: number): number { 21 | if (n > 0xff) { 22 | return uint8(n - 256); 23 | } 24 | if (n < 0x00) { 25 | return uint8(n + 256); 26 | } 27 | return n 28 | } 29 | 30 | function main(argc: number, argv: string[]): number { 31 | if (argc !== 2) { 32 | return 1; 33 | } 34 | let pc = 0; 35 | let ps = 0; 36 | 37 | // pre generated space: stack and src. 38 | let stack = [ 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55 | ]; 56 | let src = [ 57 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 58 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 59 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 60 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 61 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 62 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 63 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 64 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 65 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 66 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 67 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 68 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 69 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 70 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 71 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 72 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 73 | ]; 74 | let arg1 = argv[1]; 75 | for (let i = 0; i < arg1.length; i++) { 76 | src[i] = arg1[i]; 77 | } 78 | 79 | for (; pc < arg1.length;) { 80 | let op = src[pc]; 81 | if (op === Opcode.SHR) { 82 | ps += 1; 83 | pc += 1; 84 | continue 85 | } 86 | if (op === Opcode.SHL) { 87 | ps -= 1; 88 | pc += 1; 89 | continue 90 | } 91 | if (op === Opcode.ADD) { 92 | stack[ps] = uint8(stack[ps] + 1); 93 | pc += 1; 94 | continue 95 | } 96 | if (op === Opcode.SUB) { 97 | stack[ps] = uint8(stack[ps] - 1); 98 | pc += 1; 99 | continue 100 | } 101 | if (op === Opcode.PUTCHAR) { 102 | console.log('%c', stack[ps]); 103 | pc += 1; 104 | continue 105 | } 106 | if (op === Opcode.GETCHAR) { 107 | console.log('GETCHAR is disabled'); 108 | return 1; 109 | } 110 | 111 | 112 | if (op === Opcode.LB) { 113 | if (stack[ps] != 0x00) { 114 | pc += 1; 115 | continue 116 | } 117 | let n = 1; 118 | for (; n !== 0;) { 119 | pc += 1; 120 | if (src[pc] === Opcode.LB) { 121 | n += 1; 122 | continue 123 | } 124 | if (src[pc] === Opcode.RB) { 125 | n -= 1; 126 | continue 127 | } 128 | } 129 | pc += 1; 130 | continue 131 | } 132 | if (op === Opcode.RB) { 133 | if (stack[ps] === 0x00) { 134 | pc += 1; 135 | continue 136 | } 137 | let n = 1; 138 | for (; n !== 0;) { 139 | pc -= 1; 140 | if (src[pc] === Opcode.RB) { 141 | n += 1; 142 | continue 143 | } 144 | if (src[pc] === Opcode.LB) { 145 | n -= 1; 146 | continue 147 | } 148 | } 149 | pc += 1; 150 | continue 151 | } 152 | } 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /examples/fibonacci.ts: -------------------------------------------------------------------------------- 1 | // In mathematics, the Fibonacci numbers, 2 | // commonly denoted Fn form a sequence, called the Fibonacci sequence, such that 3 | // each number is the sum of the two preceding ones, starting from 0 and 1. That is 4 | // 5 | // F(0) = 0 6 | // F(1) = 1 7 | // F(n) = F(n − 1) + F(n − 2) 8 | function fibo(n: number): number { 9 | if (n < 2) { 10 | return n; 11 | } 12 | return fibo(n - 1) + fibo(n - 2); 13 | } 14 | 15 | function main(): number { 16 | return fibo(10); 17 | } 18 | -------------------------------------------------------------------------------- /examples/llvm/printf.ts: -------------------------------------------------------------------------------- 1 | // Print "Hello World!" by LLVM 2 | // 3 | // Usage: 4 | // $ ts-node examples/llvm/printf.ts 5 | // $ lli /tmp/minits.ll 6 | import * as llvm from "llvm-node"; 7 | import fs from "fs"; 8 | 9 | function main() { 10 | const context = new llvm.LLVMContext() 11 | const module = new llvm.Module("main", context) 12 | const builder = new llvm.IRBuilder(context) 13 | 14 | const funcPrintfTy = llvm.FunctionType.get( 15 | llvm.Type.getInt64Ty(context), 16 | [llvm.Type.getInt8Ty(context).getPointerTo()], 17 | true 18 | ) 19 | const funcPrintf = llvm.Function.create(funcPrintfTy, llvm.LinkageTypes.ExternalLinkage, "printf", module) 20 | 21 | const funcMainTy = llvm.FunctionType.get( 22 | llvm.Type.getVoidTy(context), 23 | false, 24 | ) 25 | const funcMain = llvm.Function.create(funcMainTy, llvm.LinkageTypes.ExternalLinkage, "main", module) 26 | const body = llvm.BasicBlock.create(context, 'body', funcMain) 27 | builder.setInsertionPoint(body) 28 | 29 | const strType = llvm.ArrayType.get(llvm.Type.getInt8Ty(context), 14) 30 | const str = llvm.ConstantArray.get(strType, [ 31 | llvm.ConstantInt.get(context, 0x48, 8), // H 32 | llvm.ConstantInt.get(context, 0x65, 8), // E 33 | llvm.ConstantInt.get(context, 0x6c, 8), // L 34 | llvm.ConstantInt.get(context, 0x6c, 8), // L 35 | llvm.ConstantInt.get(context, 0x6f, 8), // O 36 | llvm.ConstantInt.get(context, 0x20, 8), // 37 | llvm.ConstantInt.get(context, 0x57, 8), // W 38 | llvm.ConstantInt.get(context, 0x6f, 8), // O 39 | llvm.ConstantInt.get(context, 0x72, 8), // R 40 | llvm.ConstantInt.get(context, 0x6c, 8), // L 41 | llvm.ConstantInt.get(context, 0x64, 8), // D 42 | llvm.ConstantInt.get(context, 0x21, 8), // ! 43 | llvm.ConstantInt.get(context, 0x0a, 8), // \n 44 | llvm.ConstantInt.get(context, 0x00, 8), // \0 45 | ]); 46 | const ptr = builder.createAlloca(strType); 47 | builder.createStore(str, ptr); 48 | 49 | const fmtArg = builder.createBitCast(ptr, llvm.Type.getInt8Ty(context).getPointerTo()) 50 | 51 | builder.createCall(funcPrintf, [fmtArg]); 52 | builder.createRetVoid(); 53 | 54 | llvm.initializeAllTargetInfos(); 55 | llvm.initializeAllTargets(); 56 | llvm.initializeAllTargetMCs(); 57 | llvm.initializeAllAsmParsers(); 58 | llvm.initializeAllAsmPrinters(); 59 | const target = llvm.TargetRegistry.lookupTarget(llvm.config.LLVM_DEFAULT_TARGET_TRIPLE); 60 | const m = target.createTargetMachine(llvm.config.LLVM_DEFAULT_TARGET_TRIPLE, 'generic'); 61 | module.dataLayout = m.createDataLayout(); 62 | module.targetTriple = llvm.config.LLVM_DEFAULT_TARGET_TRIPLE; 63 | 64 | fs.writeFileSync("/tmp/minits.ll", module.print()); 65 | } 66 | 67 | main() 68 | -------------------------------------------------------------------------------- /examples/multi/lib.ts: -------------------------------------------------------------------------------- 1 | export const everything = 42; 2 | -------------------------------------------------------------------------------- /examples/multi/main.ts: -------------------------------------------------------------------------------- 1 | import * as lib from './lib' 2 | 3 | function main(): number { 4 | return lib.everything; 5 | } 6 | -------------------------------------------------------------------------------- /examples/riscv.ts: -------------------------------------------------------------------------------- 1 | // A simple example for riscv machine 2 | // 3 | // $ ts-node src/index.ts build examples/riscv.ts -o /tmp/riscv.ll --triple riscv64-unknown-unknown-elf 4 | // $ llvm-as src/stdlib/syscall.ll -o /tmp/syscall.bc 5 | // $ llvm-as /tmp/riscv.ll -o /tmp/riscv.bc 6 | // $ llvm-link /tmp/syscall.bc /tmp/riscv.bc -o /tmp/main.bc 7 | // $ llc -filetype=obj /tmp/main.bc -o /tmp/main.obj 8 | // $ riscv64-unknown-elf-gcc /tmp/main.obj -o /tmp/main.out 9 | 10 | declare function syscall(n: number, a: any, b: any, c: any, d: any, e: any, f: any): number; 11 | 12 | function main(): number { 13 | let a = "Hello World!"; 14 | return syscall(2177, a, 0, 0, 0, 0, 0); 15 | } 16 | -------------------------------------------------------------------------------- /examples/storage.ts: -------------------------------------------------------------------------------- 1 | // A simplestorage contract for blockchain. 2 | 3 | const STORAGE_SET = 2180; 4 | const STORAGE_GET = 2181; 5 | const RET = 2182; 6 | 7 | function syscall(n: number, a: any, b: any, c: any, d: any, e: any, f: any): number { 8 | return 0; 9 | } 10 | 11 | function set_storage(k: string, v: string): number { 12 | return syscall(STORAGE_SET, k, v, 0, 0, 0, 0); 13 | } 14 | 15 | function get_storage(k: string): string { 16 | let v = ""; 17 | syscall(STORAGE_GET, k, v, 0, 0, 0, 0); 18 | return v 19 | } 20 | 21 | function ret(d: string): number { 22 | return syscall(RET, d, 0, 0, 0, 0, 0); 23 | } 24 | 25 | function main(argc: number, argv: string[]): number { 26 | if (argc == 1) { 27 | return 1; 28 | } 29 | switch (argv[1]) { 30 | case "get": 31 | const v = get_storage(argv[2]); 32 | ret(v); 33 | return 0; 34 | case "set": 35 | set_storage(argv[2], argv[3]); 36 | return 0; 37 | default: 38 | return 1; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minits", 3 | "version": "1.0.0", 4 | "description": "TypeScript to LLVM compiler", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/module/index.js", 8 | "repository": "git@github.com:cryptape/minits.git", 9 | "license": "MIT", 10 | "keywords": [], 11 | "scripts": { 12 | "start": "npm run build && DEBUG=minits:* node ./build/main/index.js", 13 | "describe": "npm-scripts-info", 14 | "build": "run-s clean && run-p build:*", 15 | "build:main": "tsc -p tsconfig.json", 16 | "build:module": "tsc -p tsconfig.module.json", 17 | "fix": "run-s fix:*", 18 | "fix:prettier": "prettier \"src/**/*.ts\" --write", 19 | "fix:tslint": "tslint --fix --project .", 20 | "test": "DEBUG=minits:tests:* run-s build test:*", 21 | "test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different", 22 | "test:unit": "nyc --silent ava | tee", 23 | "watch": "run-s clean build:main && run-p \"build:main -- -w\" \"test:unit -- --watch\"", 24 | "cov": "run-s build test:unit cov:html && open-cli coverage/index.html", 25 | "cov:html": "nyc report --reporter=html", 26 | "cov:send": "nyc report --reporter=lcov && codecov", 27 | "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100", 28 | "doc": "run-s doc:html && open-cli build/docs/index.html", 29 | "doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs", 30 | "doc:json": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --json build/docs/typedoc.json", 31 | "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs", 32 | "version": "standard-version", 33 | "reset": "git clean -dfx && git reset --hard && npm i", 34 | "clean": "trash build test", 35 | "prepare-release": "run-s reset test cov:check doc:html version doc:publish", 36 | "preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('minits must be installed with Yarn: https://yarnpkg.com/')\"" 37 | }, 38 | "scripts-info": { 39 | "info": "Display information about the package scripts", 40 | "build": "Clean and rebuild the project", 41 | "fix": "Try to automatically fix any linting problems", 42 | "test": "Lint and unit test the project", 43 | "watch": "Watch and rebuild the project on save, then rerun relevant tests", 44 | "cov": "Rebuild, run tests, then create and open the coverage report", 45 | "doc": "Generate HTML API documentation and open it in a browser", 46 | "doc:json": "Generate API documentation in typedoc JSON format", 47 | "version": "Bump package.json version, update CHANGELOG.md, tag release", 48 | "reset": "Delete all untracked files and reset the repo to the last commit", 49 | "prepare-release": "One-step: clean, build, test, publish docs, and prep a release" 50 | }, 51 | "engines": { 52 | "node": ">=8.9" 53 | }, 54 | "dependencies": { 55 | "@types/debug": "^4.1.5", 56 | "@types/node": "^12.7.1", 57 | "@types/shelljs": "^0.8.5", 58 | "commander": "^3.0.0", 59 | "debug": "^4.1.1", 60 | "llvm-node": "^2.1.0", 61 | "shelljs": "^0.8.3", 62 | "typescript": "^3.5.3" 63 | }, 64 | "devDependencies": { 65 | "@bitjson/npm-scripts-info": "^1.0.0", 66 | "ava": "2.2.0", 67 | "codecov": "^3.5.0", 68 | "cz-conventional-changelog": "^2.1.0", 69 | "gh-pages": "^2.0.1", 70 | "npm-run-all": "^4.1.5", 71 | "nyc": "^14.1.1", 72 | "prettier": "^1.18.2", 73 | "standard-version": "^6.0.1", 74 | "trash-cli": "^3.0.0", 75 | "tslint": "^5.18.0", 76 | "tslint-config-prettier": "^1.18.0", 77 | "tslint-immutable": "^6.0.1", 78 | "typescript": "^3.5.3" 79 | }, 80 | "ava": { 81 | "failFast": true, 82 | "files": [ 83 | "build/main/**/*.spec.js" 84 | ], 85 | "sources": [ 86 | "build/main/**/*.js" 87 | ] 88 | }, 89 | "config": { 90 | "commitizen": { 91 | "path": "cz-conventional-changelog" 92 | } 93 | }, 94 | "prettier": { 95 | "singleQuote": true, 96 | "printWidth": 120 97 | }, 98 | "nyc": { 99 | "extends": "@istanbuljs/nyc-config-typescript", 100 | "exclude": [ 101 | "**/*.spec.js" 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /res/brainfuck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nervosnetwork/muta-minits/37aae6b99be88c4e71ba7a27a38940f70b7f19bb/res/brainfuck.png -------------------------------------------------------------------------------- /res/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nervosnetwork/muta-minits/37aae6b99be88c4e71ba7a27a38940f70b7f19bb/res/flow.png -------------------------------------------------------------------------------- /src/codegen/array-literal-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenArray { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | // Function genArrayType returns the real array type by it's initialization. 14 | // 15 | // Examples: 16 | // const a = [1, 2, 3, 4] => ArrayType(elemType=i64, size=4) 17 | // const b = [a, b, 3, 4] => ArrayType(elemType=a.type, size=4) 18 | // const c: number[] = [] => ArrayType(elemType=i64, size=0) 19 | public genArrayType(node: ts.ArrayLiteralExpression): llvm.ArrayType { 20 | const size = node.elements.length; 21 | const elemType = (() => { 22 | if (this.cgen.currentType) { 23 | return this.cgen.genType((this.cgen.currentType! as ts.ArrayTypeNode).elementType); 24 | } else { 25 | return this.cgen.genExpression(node.elements[0]).type; 26 | } 27 | })(); 28 | return llvm.ArrayType.get(elemType, size); 29 | } 30 | 31 | // [0] https://stackoverflow.com/questions/38548680/confused-about-llvm-arrays 32 | // [1] https://stackoverflow.com/questions/33003928/allow-llvm-generate-code-to-access-a-global-array 33 | public genArrayLiteralLocale(node: ts.ArrayLiteralExpression): llvm.Value { 34 | const arrayType = this.genArrayType(node); 35 | const arrayPtr = this.cgen.builder.createAlloca(arrayType); 36 | node.elements 37 | .map(item => { 38 | return this.cgen.genExpression(item); 39 | }) 40 | .forEach((item, i) => { 41 | const ptr = this.cgen.builder.createInBoundsGEP(arrayPtr, [ 42 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 43 | llvm.ConstantInt.get(this.cgen.context, i, 64) 44 | ]); 45 | this.cgen.builder.createStore(item, ptr); 46 | }); 47 | return this.cgen.builder.createInBoundsGEP(arrayPtr, [ 48 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 49 | llvm.ConstantInt.get(this.cgen.context, 0, 64) 50 | ]); 51 | } 52 | 53 | public genArrayLiteralGlobal(node: ts.ArrayLiteralExpression): llvm.Value { 54 | const arrayType = this.genArrayType(node); 55 | const arrayData = llvm.ConstantArray.get( 56 | arrayType, 57 | node.elements.map(item => { 58 | return this.cgen.genExpression(item) as llvm.Constant; 59 | }) 60 | ); 61 | const r = new llvm.GlobalVariable(this.cgen.module, arrayType, false, llvm.LinkageTypes.ExternalLinkage, arrayData); 62 | return this.cgen.builder.createBitCast(r, arrayType.elementType.getPointerTo()); 63 | } 64 | 65 | public genArrayLiteral(node: ts.ArrayLiteralExpression): llvm.Value { 66 | if (this.cgen.currentFunction) { 67 | return this.genArrayLiteralLocale(node); 68 | } else { 69 | return this.genArrayLiteralGlobal(node); 70 | } 71 | } 72 | 73 | public getElementAccessPtr(identifier: llvm.Value, i: llvm.Value): llvm.Value { 74 | return this.cgen.builder.createInBoundsGEP(identifier, [i]); 75 | } 76 | 77 | public getElementAccess(identifier: llvm.Value, i: llvm.Value): llvm.Value { 78 | const ptr = this.getElementAccessPtr(identifier, i); 79 | return this.cgen.builder.createLoad(ptr); 80 | } 81 | 82 | public genElementAccessExpressionPtr(node: ts.ElementAccessExpression): llvm.Value { 83 | const identifier = this.cgen.genExpression(node.expression); 84 | const argumentExpression = this.cgen.genExpression(node.argumentExpression); 85 | return this.getElementAccessPtr(identifier, argumentExpression); 86 | } 87 | 88 | public genElementAccessExpression(node: ts.ElementAccessExpression): llvm.Value { 89 | const identifier = this.cgen.genExpression(node.expression); 90 | const argumentExpression = this.cgen.genExpression(node.argumentExpression); 91 | return this.getElementAccess(identifier, argumentExpression); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/codegen/binary-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import * as symtab from '../symtab'; 5 | import LLVMCodeGen from './'; 6 | 7 | export default class CodeGenBinary { 8 | private cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public genBinaryExpression(expr: ts.BinaryExpression): llvm.Value { 15 | const lhs = (() => { 16 | if (expr.operatorToken.kind === ts.SyntaxKind.EqualsToken) { 17 | return this.genSymbolPtr(expr.left); 18 | } 19 | return this.cgen.genExpression(expr.left); 20 | })(); 21 | const rhs = this.cgen.genExpression(expr.right); 22 | 23 | switch (expr.operatorToken.kind) { 24 | // < 25 | case ts.SyntaxKind.LessThanToken: 26 | return this.cgen.builder.createICmpSLT(lhs, rhs); 27 | // > 28 | case ts.SyntaxKind.GreaterThanToken: 29 | return this.cgen.builder.createICmpSGT(lhs, rhs); 30 | // <= 31 | case ts.SyntaxKind.LessThanEqualsToken: 32 | return this.cgen.builder.createICmpSLE(lhs, rhs); 33 | // >= 34 | case ts.SyntaxKind.GreaterThanEqualsToken: 35 | return this.cgen.builder.createICmpSGE(lhs, rhs); 36 | // != 37 | case ts.SyntaxKind.ExclamationEqualsToken: 38 | return this.cgen.builder.createICmpNE(lhs, rhs); 39 | // === 40 | case ts.SyntaxKind.EqualsEqualsEqualsToken: 41 | return this.cgen.builder.createICmpEQ(lhs, rhs); 42 | // !== 43 | case ts.SyntaxKind.ExclamationEqualsEqualsToken: 44 | return this.cgen.builder.createICmpNE(lhs, rhs); 45 | // + 46 | case ts.SyntaxKind.PlusToken: 47 | return this.cgen.builder.createAdd(lhs, rhs); 48 | // - 49 | case ts.SyntaxKind.MinusToken: 50 | return this.cgen.builder.createSub(lhs, rhs); 51 | // * 52 | case ts.SyntaxKind.AsteriskToken: 53 | return this.cgen.builder.createMul(lhs, rhs); 54 | // / 55 | case ts.SyntaxKind.SlashToken: 56 | return this.cgen.builder.createSDiv(lhs, rhs); 57 | // % 58 | case ts.SyntaxKind.PercentToken: 59 | return this.cgen.builder.createSRem(lhs, rhs); 60 | // << 61 | case ts.SyntaxKind.LessThanLessThanToken: 62 | return this.cgen.builder.createShl(lhs, rhs); 63 | // >> 64 | case ts.SyntaxKind.GreaterThanGreaterThanToken: 65 | return this.cgen.builder.createAShr(lhs, rhs); 66 | // >>> 67 | case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: 68 | return this.cgen.builder.createLShr(lhs, rhs); 69 | // & 70 | case ts.SyntaxKind.AmpersandToken: 71 | return this.cgen.builder.createAnd(lhs, rhs); 72 | // | 73 | case ts.SyntaxKind.BarToken: 74 | return this.cgen.builder.createOr(lhs, rhs); 75 | // ^ 76 | case ts.SyntaxKind.CaretToken: 77 | return this.cgen.builder.createXor(lhs, rhs); 78 | // && 79 | case ts.SyntaxKind.AmpersandAmpersandToken: 80 | const aaInitBlock = this.cgen.builder.getInsertBlock()!; 81 | const aaNextBlock = llvm.BasicBlock.create(this.cgen.context, 'next', this.cgen.currentFunction); 82 | const aaQuitBlock = llvm.BasicBlock.create(this.cgen.context, 'quit', this.cgen.currentFunction); 83 | this.cgen.builder.createCondBr(lhs, aaNextBlock, aaQuitBlock); 84 | this.cgen.builder.setInsertionPoint(aaNextBlock); 85 | this.cgen.builder.createBr(aaQuitBlock); 86 | this.cgen.builder.setInsertionPoint(aaQuitBlock); 87 | const aaPhi = this.cgen.builder.createPhi(llvm.Type.getInt1Ty(this.cgen.context), 2); 88 | aaPhi.addIncoming(llvm.ConstantInt.get(this.cgen.context, 0, 1), aaInitBlock); 89 | aaPhi.addIncoming(rhs, aaNextBlock); 90 | return aaPhi; 91 | // || 92 | case ts.SyntaxKind.BarBarToken: 93 | const bbInitBlock = this.cgen.builder.getInsertBlock()!; 94 | const bbNextBlock = llvm.BasicBlock.create(this.cgen.context, 'next', this.cgen.currentFunction); 95 | const bbQuitBlock = llvm.BasicBlock.create(this.cgen.context, 'quit', this.cgen.currentFunction); 96 | this.cgen.builder.createCondBr(lhs, bbQuitBlock, bbNextBlock); 97 | this.cgen.builder.setInsertionPoint(bbNextBlock); 98 | this.cgen.builder.createBr(bbQuitBlock); 99 | this.cgen.builder.setInsertionPoint(bbQuitBlock); 100 | const bbPhi = this.cgen.builder.createPhi(llvm.Type.getInt1Ty(this.cgen.context), 2); 101 | bbPhi.addIncoming(llvm.ConstantInt.get(this.cgen.context, 1, 1), bbInitBlock); 102 | bbPhi.addIncoming(rhs, bbNextBlock); 103 | return bbPhi; 104 | // = 105 | case ts.SyntaxKind.EqualsToken: 106 | if (expr.left.kind === ts.SyntaxKind.ElementAccessExpression) { 107 | const e = (expr.left as ts.ElementAccessExpression).expression; 108 | const type = this.cgen.checker.getTypeAtLocation(e); 109 | if (type.symbol.escapedName === 'Int8Array') { 110 | const v = this.cgen.builder.createIntCast(rhs, llvm.Type.getInt8Ty(this.cgen.context), true); 111 | return this.cgen.builder.createStore(v, lhs); 112 | } 113 | } 114 | return this.cgen.builder.createStore(rhs, lhs); 115 | default: 116 | throw new Error('Error that should never happen'); 117 | } 118 | } 119 | 120 | public genSymbolPtr(node: ts.Expression): llvm.Value { 121 | switch (node.kind) { 122 | case ts.SyntaxKind.Identifier: 123 | return (this.cgen.symtab.get((node as ts.Identifier).getText()) as symtab.Leaf).data; 124 | case ts.SyntaxKind.ElementAccessExpression: 125 | return this.cgen.cgArray.genElementAccessExpressionPtr(node as ts.ElementAccessExpression); 126 | case ts.SyntaxKind.PropertyAccessExpression: 127 | return this.cgen.genPropertyAccessExpressionPtr(node as ts.PropertyAccessExpression); 128 | default: 129 | throw new Error(`Unsupported grammar ${node.kind}`); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/codegen/boolean-literal-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenBoolean { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genBoolean(node: ts.BooleanLiteral): llvm.ConstantInt { 14 | switch (node.kind) { 15 | case ts.SyntaxKind.FalseKeyword: 16 | return llvm.ConstantInt.get(this.cgen.context, 0, 1); 17 | case ts.SyntaxKind.TrueKeyword: 18 | return llvm.ConstantInt.get(this.cgen.context, 1, 1); 19 | default: 20 | throw new Error('Error that should never happen'); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/codegen/call-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenFuncDecl { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genCallExpression(node: ts.CallExpression): llvm.Value { 14 | // const funcDecl = this.cgen.checker.getResolvedSignature(node)!.getDeclaration() as ts.FunctionDeclaration; 15 | const funcName = node.expression.getText(); 16 | const callArgs = node.arguments.map(item => this.cgen.genExpression(item)); 17 | 18 | switch (funcName) { 19 | case 'atoi': 20 | return this.cgen.stdlib.atoi(callArgs); 21 | case 'console.log': 22 | return this.cgen.stdlib.printf(callArgs); 23 | case 'itoa': 24 | return this.cgen.stdlib.itoa(callArgs); 25 | case 'malloc': 26 | return this.cgen.stdlib.malloc(callArgs); 27 | case 'parseInt': 28 | return this.cgen.stdlib.atoi(callArgs); 29 | case 'printf': 30 | return this.cgen.stdlib.printf(callArgs); 31 | case `sprintf`: 32 | return this.cgen.stdlib.sprintf(callArgs); 33 | case 'strcat': 34 | return this.cgen.stdlib.strcat(callArgs); 35 | case 'strcmp': 36 | return this.cgen.stdlib.strcmp(callArgs); 37 | case 'strcpy': 38 | return this.cgen.stdlib.strcpy(callArgs); 39 | case 'strlen': 40 | return this.cgen.stdlib.strlen(callArgs); 41 | case 'syscall': 42 | return this.cgen.stdlib.syscall(callArgs); 43 | default: 44 | const func = this.cgen.genExpression(node.expression) as llvm.Function; 45 | return this.cgen.builder.createCall(func, callArgs); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/codegen/class-declaration.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenClassDeclaration { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genClassDeclaration(node: ts.ClassDeclaration): llvm.StructType { 14 | const name = node.name!.getText(); 15 | const show = this.cgen.symtab.name() + name; 16 | const properties: ts.PropertyDeclaration[] = []; 17 | for (const e of node.members) { 18 | if (ts.isPropertyDeclaration(e)) { 19 | properties.push(e); 20 | } 21 | } 22 | const memberTypeList = properties.map(e => this.cgen.genType(e.type!)); 23 | const structType = llvm.StructType.create(this.cgen.context, show); 24 | structType.setBody(memberTypeList, false); 25 | return structType; 26 | } 27 | 28 | public genPropertyAccessExpressionPtr(node: ts.PropertyAccessExpression): llvm.Value { 29 | const type = this.cgen.checker.getTypeAtLocation(node.expression); 30 | const properties = this.cgen.checker.getPropertiesOfType(type); 31 | const index = properties.findIndex(property => property.name === node.name.getText()); 32 | const value = this.cgen.genExpression(node.expression); 33 | 34 | return this.cgen.builder.createInBoundsGEP(value, [ 35 | llvm.ConstantInt.get(this.cgen.context, 0, 32, true), 36 | llvm.ConstantInt.get(this.cgen.context, index, 32, true) 37 | ]); 38 | } 39 | 40 | public genPropertyAccessExpression(node: ts.PropertyAccessExpression): llvm.Value { 41 | const ptr = this.genPropertyAccessExpressionPtr(node); 42 | return this.cgen.builder.createLoad(ptr); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/codegen/condition-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenCondition { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genConditionalExpression(node: ts.ConditionalExpression): llvm.Value { 14 | const condition = this.cgen.genExpression(node.condition); 15 | const thenBlock = llvm.BasicBlock.create(this.cgen.context, 'if.then', this.cgen.currentFunction); 16 | const elseBlock = llvm.BasicBlock.create(this.cgen.context, 'if.else', this.cgen.currentFunction); 17 | const quitBlock = llvm.BasicBlock.create(this.cgen.context, 'if.quit', this.cgen.currentFunction); 18 | this.cgen.builder.createCondBr(condition, thenBlock, elseBlock); 19 | 20 | this.cgen.builder.setInsertionPoint(thenBlock); 21 | const vThen = this.cgen.genExpression(node.whenTrue); 22 | this.cgen.builder.createBr(quitBlock); 23 | 24 | this.cgen.builder.setInsertionPoint(elseBlock); 25 | const vElse = this.cgen.genExpression(node.whenFalse); 26 | this.cgen.builder.createBr(quitBlock); 27 | 28 | if (vThen.type.typeID !== vElse.type.typeID) { 29 | throw new Error('Type mismatch'); 30 | } 31 | 32 | this.cgen.builder.setInsertionPoint(quitBlock); 33 | const phi = this.cgen.builder.createPhi(vThen.type, 2); 34 | phi.addIncoming(vThen, thenBlock); 35 | phi.addIncoming(vElse, elseBlock); 36 | return phi; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/codegen/do-statement.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenDo { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genDoStatement(node: ts.DoStatement): void { 14 | const loopBody = llvm.BasicBlock.create(this.cgen.context, 'loop.body', this.cgen.currentFunction); 15 | const loopCond = llvm.BasicBlock.create(this.cgen.context, 'loop.cond', this.cgen.currentFunction); 16 | const loopQuit = llvm.BasicBlock.create(this.cgen.context, 'loop.quit', this.cgen.currentFunction); 17 | 18 | this.cgen.builder.createBr(loopBody); 19 | this.cgen.builder.setInsertionPoint(loopBody); 20 | this.cgen.withContinueBreakBlock(loopCond, loopQuit, () => { 21 | this.cgen.genStatement(node.statement); 22 | }); 23 | this.cgen.builder.createBr(loopCond); 24 | this.cgen.builder.setInsertionPoint(loopCond); 25 | const cond = this.cgen.genExpression(node.expression); 26 | this.cgen.builder.createCondBr(cond, loopBody, loopQuit); 27 | this.cgen.builder.setInsertionPoint(loopQuit); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/codegen/element-access-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenElemAccess { 7 | private readonly cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genElementAccessExpression(node: ts.ElementAccessExpression): llvm.Value { 14 | const identifer = this.cgen.genExpression(node.expression); 15 | const argumentExpression = this.cgen.genExpression(node.argumentExpression); 16 | const type = this.cgen.checker.getTypeAtLocation(node.expression); 17 | 18 | if (this.cgen.cgString.isStringLiteral(node.expression)) { 19 | return this.cgen.cgString.getElementAccess(identifer, argumentExpression); 20 | } 21 | if (type.symbol.escapedName === 'Int8Array') { 22 | return this.cgen.cgInt8Array.getElementAccess(identifer, argumentExpression); 23 | } 24 | return this.cgen.cgArray.getElementAccess(identifer, argumentExpression); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/codegen/enum-declaration.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import * as symtab from '../symtab'; 5 | import LLVMCodeGen from './'; 6 | 7 | export default class CodeGenStruct { 8 | private readonly cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public genEnumDeclaration(node: ts.EnumDeclaration): void { 15 | this.cgen.symtab.with(node.name.text, () => { 16 | node.members.forEach(item => { 17 | const name = item.name.getText(); 18 | const v = this.cgen.checker.getConstantValue(item); 19 | switch (typeof v) { 20 | case 'string': 21 | const a = new symtab.Leaf(this.cgen.genStringLiteral(item.initializer! as ts.StringLiteral), 0); 22 | this.cgen.symtab.set(name, a); 23 | break; 24 | case 'number': 25 | const b = new symtab.Leaf(llvm.ConstantInt.get(this.cgen.context, v, 64), 0); 26 | this.cgen.symtab.set(name, b); 27 | break; 28 | } 29 | }); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/codegen/export-declaration.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenExport { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | // Internal/external variables have not been implemented yet. 14 | public genExportDeclaration(expr: ts.ExportDeclaration): void { 15 | assert(this.cgen); 16 | assert(expr); 17 | return; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/codegen/for-of-statement.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import * as symtab from '../symtab'; 5 | import LLVMCodeGen from './'; 6 | 7 | export default class CodeGenForOf { 8 | private cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public genForOfStatementString(node: ts.ForOfStatement): void { 15 | const pi = (() => { 16 | const name = 'loop.i'; 17 | const initializer = llvm.ConstantInt.get(this.cgen.context, 0, 64); 18 | const type = initializer.type; 19 | const alloca = this.cgen.builder.createAlloca(type, undefined, name); 20 | this.cgen.builder.createStore(initializer, alloca); 21 | this.cgen.symtab.set(name, new symtab.Leaf(alloca, 1)); 22 | return alloca; 23 | })(); 24 | const identifier = this.cgen.genExpression(node.expression) as llvm.AllocaInst; 25 | const pv = (() => { 26 | const name = (node.initializer! as ts.VariableDeclarationList).declarations!.map(item => item.getText())[0]; 27 | const alloca = this.cgen.builder.createAlloca(llvm.Type.getInt8PtrTy(this.cgen.context), undefined, name); 28 | this.cgen.symtab.set(name, new symtab.Leaf(alloca, 1)); 29 | return alloca; 30 | })(); 31 | 32 | const loopCond = llvm.BasicBlock.create(this.cgen.context, 'loop.cond', this.cgen.currentFunction); 33 | const loopBody = llvm.BasicBlock.create(this.cgen.context, 'loop.body', this.cgen.currentFunction); 34 | const loopIncr = llvm.BasicBlock.create(this.cgen.context, 'loop.incr', this.cgen.currentFunction); 35 | const loopQuit = llvm.BasicBlock.create(this.cgen.context, 'loop.quit', this.cgen.currentFunction); 36 | 37 | const l = this.cgen.stdlib.strlen([identifier]); 38 | this.cgen.builder.createBr(loopCond); 39 | this.cgen.builder.setInsertionPoint(loopCond); 40 | const cond = this.cgen.builder.createICmpSLT(this.cgen.builder.createLoad(pi), l); 41 | this.cgen.builder.createCondBr(cond, loopBody, loopQuit); 42 | 43 | this.cgen.builder.setInsertionPoint(loopBody); 44 | const v = this.cgen.cgString.getElementAccess(identifier, this.cgen.builder.createLoad(pi)); 45 | this.cgen.builder.createStore(v, pv); 46 | 47 | this.cgen.withContinueBreakBlock(loopIncr, loopQuit, () => { 48 | this.cgen.genStatement(node.statement); 49 | }); 50 | this.cgen.builder.createBr(loopIncr); 51 | 52 | this.cgen.builder.setInsertionPoint(loopIncr); 53 | const n = this.cgen.builder.createAdd( 54 | this.cgen.builder.createLoad(pi), 55 | llvm.ConstantInt.get(this.cgen.context, 1, 64) 56 | ); 57 | this.cgen.builder.createStore(n, pi); 58 | this.cgen.builder.createBr(loopCond); 59 | this.cgen.builder.setInsertionPoint(loopQuit); 60 | } 61 | 62 | public genForOfStatementArray(node: ts.ForOfStatement): void { 63 | const pi = (() => { 64 | const name = 'loop.i'; 65 | const initializer = llvm.ConstantInt.get(this.cgen.context, 0, 64); 66 | const type = initializer.type; 67 | const alloca = this.cgen.builder.createAlloca(type, undefined, name); 68 | this.cgen.builder.createStore(initializer, alloca); 69 | this.cgen.symtab.set(name, new symtab.Leaf(alloca, 1)); 70 | return alloca; 71 | })(); 72 | const identifier = this.cgen.genExpression(node.expression); 73 | const pv = (() => { 74 | const type = (identifier.type as llvm.PointerType).elementType; 75 | const name = (node.initializer! as ts.VariableDeclarationList).declarations!.map(item => item.getText())[0]; 76 | const alloca = this.cgen.builder.createAlloca(type, undefined, name); 77 | this.cgen.symtab.set(name, new symtab.Leaf(alloca, 1)); 78 | return alloca; 79 | })(); 80 | const loopCond = llvm.BasicBlock.create(this.cgen.context, 'loop.cond', this.cgen.currentFunction); 81 | const loopBody = llvm.BasicBlock.create(this.cgen.context, 'loop.body', this.cgen.currentFunction); 82 | const loopIncr = llvm.BasicBlock.create(this.cgen.context, 'loop.incr', this.cgen.currentFunction); 83 | const loopQuit = llvm.BasicBlock.create(this.cgen.context, 'loop.quit', this.cgen.currentFunction); 84 | 85 | const numElements = (() => { 86 | if (ts.isArrayLiteralExpression(node.expression)) { 87 | return (node.expression as ts.ArrayLiteralExpression).elements.length; 88 | } 89 | const symbol = this.cgen.checker.getSymbolAtLocation(node.expression)!; 90 | return (symbol.valueDeclaration as any).initializer.elements.length; 91 | })(); 92 | const l = llvm.ConstantInt.get(this.cgen.context, numElements, 64); 93 | this.cgen.builder.createBr(loopCond); 94 | this.cgen.builder.setInsertionPoint(loopCond); 95 | const cond = this.cgen.builder.createICmpSLT(this.cgen.builder.createLoad(pi), l); 96 | this.cgen.builder.createCondBr(cond, loopBody, loopQuit); 97 | 98 | this.cgen.builder.setInsertionPoint(loopBody); 99 | const v = this.cgen.cgArray.getElementAccess(identifier, this.cgen.builder.createLoad(pi)); 100 | this.cgen.builder.createStore(v, pv); 101 | 102 | this.cgen.withContinueBreakBlock(loopIncr, loopQuit, () => { 103 | this.cgen.genStatement(node.statement); 104 | }); 105 | this.cgen.builder.createBr(loopIncr); 106 | 107 | this.cgen.builder.setInsertionPoint(loopIncr); 108 | const n = this.cgen.builder.createAdd( 109 | this.cgen.builder.createLoad(pi), 110 | llvm.ConstantInt.get(this.cgen.context, 1, 64) 111 | ); 112 | this.cgen.builder.createStore(n, pi); 113 | this.cgen.builder.createBr(loopCond); 114 | this.cgen.builder.setInsertionPoint(loopQuit); 115 | } 116 | 117 | public genForOfStatement(node: ts.ForOfStatement): void { 118 | if (this.cgen.cgString.isStringLiteral(node.expression)) { 119 | return this.genForOfStatementString(node); 120 | } else { 121 | return this.genForOfStatementArray(node); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/codegen/for-statement.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenFor { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genForStatement(node: ts.ForStatement): void { 14 | if (node.initializer) { 15 | if (ts.isVariableDeclarationList(node.initializer)) { 16 | node.initializer.declarations.forEach(item => { 17 | this.cgen.genVariableDeclaration(item); 18 | }); 19 | } else { 20 | throw new Error('Unsupported for statement'); 21 | } 22 | } 23 | const loopCond = llvm.BasicBlock.create(this.cgen.context, 'loop.cond', this.cgen.currentFunction); 24 | const loopBody = llvm.BasicBlock.create(this.cgen.context, 'loop.body', this.cgen.currentFunction); 25 | const loopIncr = llvm.BasicBlock.create(this.cgen.context, 'loop.incr', this.cgen.currentFunction); 26 | const loopQuit = llvm.BasicBlock.create(this.cgen.context, 'loop.quit', this.cgen.currentFunction); 27 | 28 | this.cgen.builder.createBr(loopCond); 29 | this.cgen.builder.setInsertionPoint(loopCond); 30 | const cond = (() => { 31 | if (node.condition) { 32 | return this.cgen.genExpression(node.condition!); 33 | } else { 34 | return llvm.ConstantInt.get(this.cgen.context, 1, 1); 35 | } 36 | })(); 37 | this.cgen.builder.createCondBr(cond, loopBody, loopQuit); 38 | this.cgen.builder.setInsertionPoint(loopBody); 39 | this.cgen.withContinueBreakBlock(loopIncr, loopQuit, () => { 40 | this.cgen.genStatement(node.statement); 41 | }); 42 | this.cgen.builder.createBr(loopIncr); 43 | this.cgen.builder.setInsertionPoint(loopIncr); 44 | if (node.incrementor) { 45 | this.cgen.genExpression(node.incrementor); 46 | } 47 | this.cgen.builder.createBr(loopCond); 48 | this.cgen.builder.setInsertionPoint(loopQuit); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/codegen/function-declaration.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | import llvm from 'llvm-node'; 3 | import ts from 'typescript'; 4 | 5 | import * as symtab from '../symtab'; 6 | import LLVMCodeGen from './'; 7 | 8 | const debug = Debug('minits:codegen'); 9 | 10 | interface LLVMFunctionWithTsDeclaration { 11 | func: llvm.Function; 12 | parameters: ts.NodeArray; 13 | body: ts.Block; 14 | } 15 | 16 | export default class CodeGenFuncDecl { 17 | private cgen: LLVMCodeGen; 18 | private list: LLVMFunctionWithTsDeclaration[]; 19 | 20 | constructor(cgen: LLVMCodeGen) { 21 | this.cgen = cgen; 22 | this.list = []; 23 | } 24 | 25 | public genFunctionDeclaration(node: ts.FunctionDeclaration): void { 26 | if (node.modifiers && node.modifiers[0].kind === ts.SyntaxKind.DeclareKeyword) { 27 | return; 28 | } 29 | const funcReturnType = this.cgen.genType(node.type!); 30 | const funcArgsType = node.parameters.map(item => { 31 | return this.cgen.genType(item.type!); 32 | }); 33 | const fnty = llvm.FunctionType.get(funcReturnType, funcArgsType, false); 34 | const name = (node.name as ts.Identifier).text; 35 | const linkage = llvm.LinkageTypes.ExternalLinkage; 36 | const show = this.cgen.symtab.name() + name; 37 | debug(`Declare function ${show}`); 38 | const func = llvm.Function.create(fnty, linkage, show, this.cgen.module); 39 | if (name === 'main') { 40 | func.addFnAttr(llvm.Attribute.AttrKind.NoInline); 41 | func.addFnAttr(llvm.Attribute.AttrKind.OptimizeNone); 42 | } 43 | this.cgen.symtab.set(name, new symtab.Leaf(func, 0)); 44 | this.list.push({ func, parameters: node.parameters, body: node.body! }); 45 | return; 46 | } 47 | 48 | public genImplemention(): void { 49 | for (const { func, parameters, body } of this.list) { 50 | this.cgen.symtab.with('', () => { 51 | func.getArguments().forEach(item => { 52 | item.name = parameters[item.argumentNumber].name.getText(); 53 | this.cgen.symtab.set(item.name, new symtab.Leaf(item, 0)); 54 | }); 55 | const bd = llvm.BasicBlock.create(this.cgen.context, 'body', func); 56 | this.cgen.builder.setInsertionPoint(bd); 57 | this.cgen.withFunction(func, () => { 58 | this.cgen.genBlock(body); 59 | if (!this.cgen.builder.getInsertBlock()!.getTerminator()) { 60 | this.cgen.builder.createRetVoid(); 61 | } 62 | }); 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/codegen/global-object-int8array.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenGlobalObjectInt8Array { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genNewExpression(node: ts.NewExpression): llvm.Value { 14 | switch (node.arguments![0].kind) { 15 | case ts.SyntaxKind.NumericLiteral: 16 | return (() => { 17 | const n = parseInt((node.arguments![0] as ts.NumericLiteral).getText(), undefined); 18 | const arrayType = llvm.ArrayType.get(llvm.Type.getInt8Ty(this.cgen.context), n); 19 | const arrayPtr = this.cgen.builder.createAlloca(arrayType); 20 | return this.cgen.builder.createInBoundsGEP(arrayPtr, [ 21 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 22 | llvm.ConstantInt.get(this.cgen.context, 0, 64) 23 | ]); 24 | })(); 25 | case ts.SyntaxKind.ArrayLiteralExpression: 26 | return (() => { 27 | const expr = node.arguments![0] as ts.ArrayLiteralExpression; 28 | const l = expr.elements.length; 29 | const arrayType = llvm.ArrayType.get(llvm.Type.getInt8Ty(this.cgen.context), l); 30 | const arrayPtr = this.cgen.builder.createAlloca(arrayType); 31 | for (let i = 0; i < l; i++) { 32 | const item = this.cgen.builder.createIntCast( 33 | this.cgen.genExpression(expr.elements[i]), 34 | llvm.Type.getInt8Ty(this.cgen.context), 35 | true 36 | ); 37 | 38 | const ptr = this.cgen.builder.createInBoundsGEP(arrayPtr, [ 39 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 40 | llvm.ConstantInt.get(this.cgen.context, i, 64) 41 | ]); 42 | this.cgen.builder.createStore(item, ptr); 43 | } 44 | return this.cgen.builder.createInBoundsGEP(arrayPtr, [ 45 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 46 | llvm.ConstantInt.get(this.cgen.context, 0, 64) 47 | ]); 48 | })(); 49 | } 50 | throw new Error(`Failed to initialize Int8Array: ${node.getText()}`); 51 | } 52 | 53 | public getElementAccess(identifier: llvm.Value, i: llvm.Value): llvm.Value { 54 | const ptr = this.cgen.cgArray.getElementAccessPtr(identifier, i); 55 | const v = this.cgen.builder.createLoad(ptr); 56 | return this.cgen.builder.createIntCast(v, llvm.Type.getInt64Ty(this.cgen.context), true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/codegen/if-statement.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenIf { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genIfStatement(node: ts.IfStatement): void { 14 | const condition = this.cgen.genExpression(node.expression); 15 | const thenBlock = llvm.BasicBlock.create(this.cgen.context, 'if.then', this.cgen.currentFunction); 16 | const elseBlock = llvm.BasicBlock.create(this.cgen.context, 'if.else', this.cgen.currentFunction); 17 | const quitBlock = llvm.BasicBlock.create(this.cgen.context, 'if.quit', this.cgen.currentFunction); 18 | this.cgen.builder.createCondBr(condition, thenBlock, elseBlock); 19 | 20 | this.cgen.symtab.with('', () => { 21 | this.cgen.builder.setInsertionPoint(thenBlock); 22 | this.cgen.genStatement(node.thenStatement); 23 | if (!this.cgen.builder.getInsertBlock()!.getTerminator()) { 24 | this.cgen.builder.createBr(quitBlock); 25 | } 26 | }); 27 | 28 | this.cgen.symtab.with('', () => { 29 | this.cgen.builder.setInsertionPoint(elseBlock); 30 | if (node.elseStatement) { 31 | this.cgen.genStatement(node.elseStatement); 32 | } 33 | if (!this.cgen.builder.getInsertBlock()!.getTerminator()) { 34 | this.cgen.builder.createBr(quitBlock); 35 | } 36 | }); 37 | 38 | this.cgen.builder.setInsertionPoint(quitBlock); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/codegen/index.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import { Stdlib } from '../stdlib'; 5 | import * as symtab from '../symtab'; 6 | import CodeGenArray from './array-literal-expression'; 7 | import CodeGenBinary from './binary-expression'; 8 | import CodeGenBoolean from './boolean-literal-expression'; 9 | import CodeGenCall from './call-expression'; 10 | import CodeGenClassDeclaration from './class-declaration'; 11 | import CodeGenCondition from './condition-expression'; 12 | import CodeGenDo from './do-statement'; 13 | import CodeGenElemAccess from './element-access-expression'; 14 | import CodeGenEnum from './enum-declaration'; 15 | import CodeGenExport from './export-declaration'; 16 | import CodeGenForOf from './for-of-statement'; 17 | import CodeGenFor from './for-statement'; 18 | import CodeGenFuncDecl from './function-declaration'; 19 | import CodeGenGlobalObjectInt8Array from './global-object-int8array'; 20 | import CodeGenIf from './if-statement'; 21 | import CodeGenModule from './module-declaration'; 22 | import CodeGenNew from './new-expression'; 23 | import CodeGenNumeric from './numeric-expression'; 24 | import CodeGenObject from './object-literal-expression'; 25 | import CodeGenPrefixUnary from './prefix-unary-expression'; 26 | import CodeGenPropertyAccessExpression from './property-access-expression'; 27 | import CodeGenReturn from './return-statement'; 28 | import CodeGenString from './string-literal-expression'; 29 | import CodeGenVarDecl from './variable-declaration'; 30 | import CodeGenWhile from './while-statement'; 31 | 32 | export default class LLVMCodeGen { 33 | public readonly program: ts.Program; 34 | public readonly checker: ts.TypeChecker; 35 | 36 | public readonly builder: llvm.IRBuilder; 37 | public readonly context: llvm.LLVMContext; 38 | public readonly module: llvm.Module; 39 | public readonly symtab: symtab.Symtab; 40 | public readonly stdlib: Stdlib; 41 | 42 | public readonly cgArray: CodeGenArray; 43 | public readonly cgBinary: CodeGenBinary; 44 | public readonly cgBoolean: CodeGenBoolean; 45 | public readonly cgCall: CodeGenCall; 46 | public readonly cgClassDeclaration: CodeGenClassDeclaration; 47 | public readonly cgCondition: CodeGenCondition; 48 | public readonly cgDo: CodeGenDo; 49 | public readonly cgElemAccess: CodeGenElemAccess; 50 | public readonly cgEnum: CodeGenEnum; 51 | public readonly cgExport: CodeGenExport; 52 | public readonly cgForOf: CodeGenForOf; 53 | public readonly cgFor: CodeGenFor; 54 | public readonly cgFuncDecl: CodeGenFuncDecl; 55 | public readonly cgInt8Array: CodeGenGlobalObjectInt8Array; 56 | public readonly cgIf: CodeGenIf; 57 | public readonly cgModule: CodeGenModule; 58 | public readonly cgNumeric: CodeGenNumeric; 59 | public readonly cgObject: CodeGenObject; 60 | public readonly cgNew: CodeGenNew; 61 | public readonly cgPrefixUnary: CodeGenPrefixUnary; 62 | public readonly cgPropertyAccessExpression: CodeGenPropertyAccessExpression; 63 | public readonly cgReturn: CodeGenReturn; 64 | public readonly cgString: CodeGenString; 65 | public readonly cgVarDecl: CodeGenVarDecl; 66 | public readonly cgWhile: CodeGenWhile; 67 | 68 | public currentBreakBlock: llvm.BasicBlock | undefined; 69 | public currentContinueBlock: llvm.BasicBlock | undefined; 70 | public currentFunction: llvm.Function | undefined; 71 | public currentType: ts.TypeNode | undefined; 72 | 73 | constructor(main: string) { 74 | this.program = ts.createProgram([main], {}); 75 | this.checker = this.program.getTypeChecker(); 76 | 77 | this.context = new llvm.LLVMContext(); 78 | this.module = new llvm.Module('main', this.context); 79 | this.builder = new llvm.IRBuilder(this.context); 80 | this.symtab = new symtab.Symtab(); 81 | this.stdlib = new Stdlib(this); 82 | 83 | this.cgArray = new CodeGenArray(this); 84 | this.cgBinary = new CodeGenBinary(this); 85 | this.cgBoolean = new CodeGenBoolean(this); 86 | this.cgCall = new CodeGenCall(this); 87 | this.cgClassDeclaration = new CodeGenClassDeclaration(this); 88 | this.cgCondition = new CodeGenCondition(this); 89 | this.cgDo = new CodeGenDo(this); 90 | this.cgElemAccess = new CodeGenElemAccess(this); 91 | this.cgEnum = new CodeGenEnum(this); 92 | this.cgExport = new CodeGenExport(this); 93 | this.cgForOf = new CodeGenForOf(this); 94 | this.cgFor = new CodeGenFor(this); 95 | this.cgFuncDecl = new CodeGenFuncDecl(this); 96 | this.cgInt8Array = new CodeGenGlobalObjectInt8Array(this); 97 | this.cgIf = new CodeGenIf(this); 98 | this.cgModule = new CodeGenModule(this); 99 | this.cgNumeric = new CodeGenNumeric(this); 100 | this.cgObject = new CodeGenObject(this); 101 | this.cgNew = new CodeGenNew(this); 102 | this.cgPrefixUnary = new CodeGenPrefixUnary(this); 103 | this.cgPropertyAccessExpression = new CodeGenPropertyAccessExpression(this); 104 | this.cgReturn = new CodeGenReturn(this); 105 | this.cgString = new CodeGenString(this); 106 | this.cgVarDecl = new CodeGenVarDecl(this); 107 | this.cgWhile = new CodeGenWhile(this); 108 | 109 | this.currentBreakBlock = undefined; 110 | this.currentContinueBlock = undefined; 111 | this.currentFunction = undefined; 112 | this.currentType = undefined; 113 | } 114 | 115 | public withFunction(func: llvm.Function, body: () => any): any { 116 | const a = this.currentFunction; 117 | this.currentFunction = func; 118 | const r = body(); 119 | this.currentFunction = a; 120 | return r; 121 | } 122 | 123 | public withType(type: ts.TypeNode | undefined, body: () => any): any { 124 | const a = this.currentType; 125 | this.currentType = type; 126 | const r = body(); 127 | this.currentType = a; 128 | return r; 129 | } 130 | 131 | public withContinueBreakBlock(c: llvm.BasicBlock, b: llvm.BasicBlock, body: () => any): any { 132 | const rc = this.currentContinueBlock; 133 | this.currentContinueBlock = c; 134 | const rb = this.currentBreakBlock; 135 | this.currentBreakBlock = b; 136 | const r = body(); 137 | this.currentContinueBlock = rc; 138 | this.currentBreakBlock = rb; 139 | return r; 140 | } 141 | 142 | public genText(): string { 143 | return this.module.print(); 144 | } 145 | 146 | public genGlobalVariable(initializer: llvm.Constant): llvm.Value { 147 | return new llvm.GlobalVariable( 148 | this.module, 149 | initializer.type, 150 | false, 151 | llvm.LinkageTypes.ExternalLinkage, 152 | initializer 153 | ); 154 | } 155 | 156 | public genSourceFile(file: string): void { 157 | this.program.getSourceFile(file)!.forEachChild(node => { 158 | switch (node.kind) { 159 | case ts.SyntaxKind.EndOfFileToken: 160 | return; 161 | case ts.SyntaxKind.VariableStatement: 162 | this.genVariableStatement(node as ts.VariableStatement); 163 | break; 164 | case ts.SyntaxKind.FunctionDeclaration: 165 | this.genFunctionDeclaration(node as ts.FunctionDeclaration); 166 | break; 167 | case ts.SyntaxKind.ClassDeclaration: 168 | this.genClassDeclaration(node as ts.ClassDeclaration); 169 | break; 170 | case ts.SyntaxKind.EnumDeclaration: 171 | this.genEnumDeclaration(node as ts.EnumDeclaration); 172 | break; 173 | case ts.SyntaxKind.ExpressionStatement: 174 | this.genExpressionStatement(node as ts.ExpressionStatement); 175 | break; 176 | case ts.SyntaxKind.ModuleDeclaration: 177 | this.genModuleDeclaration(node as ts.ModuleDeclaration); 178 | break; 179 | case ts.SyntaxKind.ExportDeclaration: 180 | this.genExportDeclaration(node as ts.ExportDeclaration); 181 | break; 182 | default: 183 | throw new Error('Unsupported grammar'); 184 | } 185 | }); 186 | 187 | this.cgFuncDecl.genImplemention(); 188 | } 189 | 190 | public genNumeric(node: ts.NumericLiteral): llvm.ConstantInt { 191 | return this.cgNumeric.genNumeric(node); 192 | } 193 | 194 | public genStringLiteral(node: ts.StringLiteral): llvm.Value { 195 | return this.cgString.genStringLiteral(node); 196 | } 197 | 198 | public genBoolean(node: ts.BooleanLiteral): llvm.ConstantInt { 199 | return this.cgBoolean.genBoolean(node); 200 | } 201 | 202 | public genIdentifier(node: ts.Identifier): llvm.Value { 203 | const symbol = this.symtab.get(node.getText())! as symtab.Leaf; 204 | let r = symbol.data; 205 | for (let i = 0; i < symbol.ptrs; i++) { 206 | r = this.builder.createLoad(r); 207 | } 208 | return r; 209 | } 210 | 211 | public genType(type: ts.TypeNode): llvm.Type { 212 | switch (type.kind) { 213 | case ts.SyntaxKind.VoidKeyword: 214 | return llvm.Type.getVoidTy(this.context); 215 | case ts.SyntaxKind.AnyKeyword: 216 | return llvm.Type.getInt64Ty(this.context); 217 | case ts.SyntaxKind.BooleanKeyword: 218 | return llvm.Type.getInt1Ty(this.context); 219 | case ts.SyntaxKind.NumberKeyword: 220 | return llvm.Type.getInt64Ty(this.context); 221 | case ts.SyntaxKind.StringKeyword: 222 | return llvm.Type.getInt8PtrTy(this.context); 223 | case ts.SyntaxKind.TypeReference: 224 | const real = type as ts.TypeReferenceNode; 225 | if (real.typeName.kind === ts.SyntaxKind.Identifier) { 226 | const typeName = (real.typeName as ts.Identifier).getText(); 227 | if (typeName === 'Int8Array') { 228 | return llvm.Type.getInt8PtrTy(this.context); 229 | } 230 | const structType = this.module.getTypeByName(typeName); 231 | if (structType) { 232 | return structType.getPointerTo(); 233 | } 234 | const dest = this.symtab.get(typeName); 235 | if (symtab.isMeso(dest)) { 236 | for (const v of dest.data.values()) { 237 | return (v as symtab.Leaf).data.type; 238 | } 239 | } 240 | throw new Error('Unsupported type'); // TODO: impl struct 241 | } 242 | throw new Error(`Unsupported type ${type.getText()}`); 243 | case ts.SyntaxKind.TypeLiteral: 244 | return this.cgObject.genObjectLiteralType(type as ts.TypeLiteralNode); 245 | case ts.SyntaxKind.ArrayType: 246 | const elementType = this.genType((type as ts.ArrayTypeNode).elementType); 247 | return elementType.getPointerTo(); 248 | default: 249 | throw new Error(`Unsupported type ${type.kind}`); 250 | } 251 | } 252 | 253 | public genBlock(node: ts.Block): void { 254 | node.statements.forEach(b => { 255 | this.genStatement(b); 256 | }); 257 | } 258 | 259 | public genExpression(expr: ts.Expression): llvm.Value { 260 | switch (expr.kind) { 261 | case ts.SyntaxKind.NumericLiteral: 262 | return this.genNumeric(expr as ts.NumericLiteral); 263 | case ts.SyntaxKind.StringLiteral: 264 | return this.genStringLiteral(expr as ts.StringLiteral); 265 | case ts.SyntaxKind.Identifier: 266 | return this.genIdentifier(expr as ts.Identifier); 267 | case ts.SyntaxKind.FalseKeyword: 268 | return this.genBoolean(expr as ts.BooleanLiteral); 269 | case ts.SyntaxKind.TrueKeyword: 270 | return this.genBoolean(expr as ts.BooleanLiteral); 271 | case ts.SyntaxKind.ArrayLiteralExpression: 272 | return this.genArrayLiteral(expr as ts.ArrayLiteralExpression); 273 | case ts.SyntaxKind.ElementAccessExpression: 274 | return this.genElementAccess(expr as ts.ElementAccessExpression); 275 | case ts.SyntaxKind.CallExpression: 276 | return this.genCallExpression(expr as ts.CallExpression); 277 | case ts.SyntaxKind.ParenthesizedExpression: 278 | return this.genParenthesizedExpression(expr as ts.ParenthesizedExpression); 279 | case ts.SyntaxKind.PrefixUnaryExpression: 280 | return this.genPrefixUnaryExpression(expr as ts.PrefixUnaryExpression); 281 | case ts.SyntaxKind.BinaryExpression: 282 | return this.genBinaryExpression(expr as ts.BinaryExpression); 283 | case ts.SyntaxKind.PropertyAccessExpression: 284 | return this.genPropertyAccessExpression(expr as ts.PropertyAccessExpression); 285 | case ts.SyntaxKind.ObjectLiteralExpression: 286 | return this.genObjectLiteralExpression(expr as ts.ObjectLiteralExpression); 287 | case ts.SyntaxKind.NewExpression: 288 | return this.genNewExpression(expr as ts.NewExpression); 289 | case ts.SyntaxKind.ConditionalExpression: 290 | return this.genConditionalExpression(expr as ts.ConditionalExpression); 291 | default: 292 | throw new Error('Unsupported expression'); 293 | } 294 | } 295 | 296 | public genArrayLiteral(node: ts.ArrayLiteralExpression): llvm.Value { 297 | return this.cgArray.genArrayLiteral(node); 298 | } 299 | 300 | public genElementAccess(node: ts.ElementAccessExpression): llvm.Value { 301 | return this.cgElemAccess.genElementAccessExpression(node); 302 | } 303 | 304 | public genCallExpression(node: ts.CallExpression): llvm.Value { 305 | return this.cgCall.genCallExpression(node); 306 | } 307 | 308 | public genParenthesizedExpression(node: ts.ParenthesizedExpression): llvm.Value { 309 | return this.genExpression(node.expression); 310 | } 311 | 312 | public genPrefixUnaryExpression(node: ts.PrefixUnaryExpression): llvm.Value { 313 | return this.cgPrefixUnary.genPrefixUnaryExpression(node); 314 | } 315 | 316 | public genBinaryExpression(node: ts.BinaryExpression): llvm.Value { 317 | return this.cgBinary.genBinaryExpression(node); 318 | } 319 | 320 | public genStatement(node: ts.Statement): llvm.Value | void { 321 | switch (node.kind) { 322 | case ts.SyntaxKind.Block: // 219 323 | return this.genBlock(node as ts.Block); 324 | case ts.SyntaxKind.VariableStatement: // 220 325 | return this.genVariableStatement(node as ts.VariableStatement); 326 | case ts.SyntaxKind.EmptyStatement: // 221 327 | return; 328 | case ts.SyntaxKind.ExpressionStatement: // 222 329 | return this.genExpressionStatement(node as ts.ExpressionStatement); 330 | case ts.SyntaxKind.IfStatement: // 223 331 | return this.genIfStatement(node as ts.IfStatement); 332 | case ts.SyntaxKind.DoStatement: // 224 333 | return this.genDoStatement(node as ts.DoStatement); 334 | case ts.SyntaxKind.WhileStatement: // 225 335 | return this.genWhileStatement(node as ts.WhileStatement); 336 | case ts.SyntaxKind.ForStatement: // 226 337 | return this.genForStatement(node as ts.ForStatement); 338 | case ts.SyntaxKind.ForOfStatement: // 228 339 | return this.genForOfStatement(node as ts.ForOfStatement); 340 | case ts.SyntaxKind.ContinueStatement: // 229 341 | return this.genContinueStatement(); 342 | case ts.SyntaxKind.BreakStatement: // 230 343 | return this.genBreakStatement(); 344 | case ts.SyntaxKind.ReturnStatement: // 231 345 | return this.genReturnStatement(node as ts.ReturnStatement); 346 | case ts.SyntaxKind.FunctionDeclaration: // 240 347 | return this.genFunctionDeclaration(node as ts.FunctionDeclaration); 348 | case ts.SyntaxKind.ClassDeclaration: // 241 349 | this.genClassDeclaration(node as ts.ClassDeclaration); 350 | return; 351 | case ts.SyntaxKind.EnumDeclaration: // 244 352 | return this.genEnumDeclaration(node as ts.EnumDeclaration); 353 | default: 354 | throw new Error('Unsupported statement'); 355 | } 356 | } 357 | 358 | public genVariableDeclaration(node: ts.VariableDeclaration): void { 359 | this.cgVarDecl.genVariableDeclaration(node); 360 | } 361 | 362 | public genVariableStatement(node: ts.VariableStatement): void { 363 | node.declarationList.declarations.forEach(item => { 364 | this.genVariableDeclaration(item); 365 | }); 366 | } 367 | 368 | public genExpressionStatement(node: ts.ExpressionStatement): llvm.Value { 369 | return this.genExpression(node.expression); 370 | } 371 | 372 | public genContinueStatement(): void { 373 | this.builder.createBr(this.currentContinueBlock!); 374 | } 375 | 376 | public genBreakStatement(): void { 377 | this.builder.createBr(this.currentBreakBlock!); 378 | } 379 | 380 | public genReturnStatement(node: ts.ReturnStatement): llvm.Value { 381 | return this.cgReturn.genReturnStatement(node); 382 | } 383 | 384 | public genIfStatement(node: ts.IfStatement): void { 385 | return this.cgIf.genIfStatement(node); 386 | } 387 | 388 | public genDoStatement(node: ts.DoStatement): void { 389 | return this.cgDo.genDoStatement(node); 390 | } 391 | 392 | public genWhileStatement(node: ts.WhileStatement): void { 393 | return this.cgWhile.genWhileStatement(node); 394 | } 395 | 396 | public genForStatement(node: ts.ForStatement): void { 397 | return this.cgFor.genForStatement(node); 398 | } 399 | 400 | public genForOfStatement(node: ts.ForOfStatement): void { 401 | return this.cgForOf.genForOfStatement(node); 402 | } 403 | 404 | // 240 ts.SyntaxKind.FunctionDeclaration 405 | public genFunctionDeclaration(node: ts.FunctionDeclaration): void { 406 | return this.cgFuncDecl.genFunctionDeclaration(node); 407 | } 408 | 409 | public genClassDeclaration(node: ts.ClassDeclaration): llvm.StructType { 410 | return this.cgClassDeclaration.genClassDeclaration(node); 411 | } 412 | 413 | public genEnumDeclaration(node: ts.EnumDeclaration): void { 414 | return this.cgEnum.genEnumDeclaration(node); 415 | } 416 | 417 | // 245 SyntakKind.ModuleDeclaration 418 | public genModuleDeclaration(node: ts.ModuleDeclaration): void { 419 | return this.cgModule.genModuleDeclaration(node); 420 | } 421 | 422 | public genExportDeclaration(node: ts.ExportDeclaration): void { 423 | return this.cgExport.genExportDeclaration(node); 424 | } 425 | 426 | public genPropertyAccessExpression(node: ts.PropertyAccessExpression): llvm.Value { 427 | return this.cgPropertyAccessExpression.genPropertyAccessExpression(node); 428 | } 429 | 430 | public genPropertyAccessExpressionPtr(node: ts.PropertyAccessExpression): llvm.Value { 431 | return this.cgClassDeclaration.genPropertyAccessExpressionPtr(node); 432 | } 433 | 434 | public genObjectLiteralExpression(node: ts.ObjectLiteralExpression): llvm.Value { 435 | return this.cgObject.genObjectLiteralExpression(node); 436 | } 437 | 438 | public genNewExpression(node: ts.NewExpression): llvm.Value { 439 | return this.cgNew.genNewExpression(node); 440 | } 441 | 442 | public genConditionalExpression(node: ts.ConditionalExpression): llvm.Value { 443 | return this.cgCondition.genConditionalExpression(node); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/codegen/module-declaration.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript'; 2 | 3 | import LLVMCodeGen from './'; 4 | 5 | export default class CodeGenModule { 6 | private cgen: LLVMCodeGen; 7 | 8 | constructor(cgen: LLVMCodeGen) { 9 | this.cgen = cgen; 10 | } 11 | 12 | public genModuleDeclaration(node: ts.ModuleDeclaration): void { 13 | this.cgen.symtab.with(node.name.text, () => { 14 | (node.body! as ts.ModuleBlock).statements.forEach(e => { 15 | this.cgen.genStatement(e); 16 | }); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/codegen/new-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenNew { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genNewExpression(node: ts.NewExpression): llvm.Value { 14 | if (node.expression.kind === ts.SyntaxKind.Identifier) { 15 | const name = (node.expression as ts.Identifier).getText(); 16 | switch (name) { 17 | case 'Int8Array': 18 | return this.cgen.cgInt8Array.genNewExpression(node); 19 | default: 20 | const type = this.cgen.module.getTypeByName(name)!; 21 | const alloc = this.cgen.builder.createAlloca(type); 22 | 23 | const func = this.cgen.module.getFunction(name + '_constructor')!; 24 | let args = node.arguments!.map(e => this.cgen.genExpression(e)); 25 | args = [alloc, ...args]; 26 | this.cgen.builder.createCall(func, args); 27 | return alloc; 28 | } 29 | } 30 | throw new Error(`Failed to initialize struct: ${node.getText()}`); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/codegen/numeric-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenNumeric { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genNumeric(node: ts.NumericLiteral): llvm.ConstantInt { 14 | const text = node.getText(); 15 | const bits = (() => { 16 | if (text.startsWith('0x')) { 17 | return 16; 18 | } else if (text.startsWith('0b')) { 19 | return 2; 20 | } else { 21 | return 10; 22 | } 23 | })(); 24 | return llvm.ConstantInt.get(this.cgen.context, parseInt(text, bits), 64); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/codegen/object-literal-expression.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import llvm from 'llvm-node'; 3 | import ts from 'typescript'; 4 | 5 | import LLVMCodeGen from '.'; 6 | 7 | export default class GenObject { 8 | private cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public getObjectLiteralTypeName(memberTypes: llvm.Type[]): string { 15 | const data = memberTypes.map(t => t.typeID).join('-'); 16 | const prefix = 'TypeLiteral-'; 17 | const suffix = crypto 18 | .createHash('md5') 19 | .update(data) 20 | .digest() 21 | .toString('hex'); 22 | return prefix + suffix; 23 | } 24 | 25 | public genObjectLiteralType(type: ts.TypeLiteralNode): llvm.Type { 26 | const memberTypes: llvm.Type[] = []; 27 | type.members.forEach(m => memberTypes.push(this.cgen.genType((m as ts.PropertySignature).type!))); 28 | const name = this.getObjectLiteralTypeName(memberTypes); 29 | if (!this.cgen.module.getTypeByName(name)) { 30 | const structType = llvm.StructType.create(this.cgen.context, name); 31 | structType.setBody(memberTypes, false); 32 | } 33 | return this.cgen.module.getTypeByName(name)!.getPointerTo(); 34 | } 35 | 36 | public genObjectLiteralExpression(node: ts.ObjectLiteralExpression): llvm.Value { 37 | const members: llvm.Value[] = []; 38 | for (const p of node.properties) { 39 | const e = this.cgen.genExpression((p as ts.PropertyAssignment).initializer); 40 | members.push(e); 41 | } 42 | const memberTypes = members.map(e => e.type); 43 | const name = (() => { 44 | if (this.cgen.currentType) { 45 | return this.cgen.currentType!.getText(); 46 | } else { 47 | return this.getObjectLiteralTypeName(memberTypes); 48 | } 49 | })(); 50 | if (!this.cgen.module.getTypeByName(name)) { 51 | const structType = llvm.StructType.create(this.cgen.context, name); 52 | structType.setBody(memberTypes, false); 53 | } 54 | if (this.cgen.currentFunction) { 55 | return this.genObjectLiteralExpressionLocale(this.cgen.module.getTypeByName(name)!, members); 56 | } else { 57 | return this.genObjectLiteralExpressionGlobal(this.cgen.module.getTypeByName(name)!, members); 58 | } 59 | } 60 | 61 | public genObjectLiteralExpressionLocale(structType: llvm.StructType, values: llvm.Value[]): llvm.Value { 62 | const alloc = this.cgen.builder.createAlloca(structType); 63 | for (let i = 0; i < values.length; i++) { 64 | const ptr = this.cgen.builder.createInBoundsGEP(alloc, [ 65 | llvm.ConstantInt.get(this.cgen.context, 0, 32, true), 66 | llvm.ConstantInt.get(this.cgen.context, i, 32, true) 67 | ]); 68 | this.cgen.builder.createStore(values[i], ptr); 69 | } 70 | return alloc; 71 | } 72 | 73 | public genObjectLiteralExpressionGlobal(structType: llvm.StructType, values: llvm.Value[]): llvm.Value { 74 | const argInitializer = llvm.ConstantStruct.get(structType, values as llvm.Constant[]); 75 | const argLinkage = llvm.LinkageTypes.ExternalLinkage; 76 | return new llvm.GlobalVariable(this.cgen.module, structType, false, argLinkage, argInitializer); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/codegen/prefix-unary-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenPrefixUnary { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genPrefixUnaryExpression(expr: ts.PrefixUnaryExpression): llvm.Value { 14 | switch (expr.operator) { 15 | // ~ 16 | case ts.SyntaxKind.TildeToken: 17 | return this.cgen.builder.createXor( 18 | this.cgen.genExpression(expr.operand), 19 | llvm.ConstantInt.get(this.cgen.context, -1, 64) 20 | ); 21 | // ! 22 | case ts.SyntaxKind.ExclamationToken: 23 | return this.cgen.builder.createNot(this.cgen.genExpression(expr.operand)); 24 | } 25 | throw new Error('Error that should never happen'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/codegen/property-access-expression.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import * as symtab from '../symtab'; 5 | import LLVMCodeGen from './'; 6 | 7 | export default class CodeGenPropertyAccessExpression { 8 | private cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public getPropertyAccessExpression(node: ts.PropertyAccessExpression): symtab.Node { 15 | let parent: symtab.Node; 16 | if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) { 17 | parent = this.getPropertyAccessExpression(node.expression as ts.PropertyAccessExpression); 18 | } else if (node.expression.kind === ts.SyntaxKind.Identifier) { 19 | parent = this.cgen.symtab.get((node.expression as ts.Identifier).getText()); 20 | } else { 21 | parent = new symtab.Leaf(this.cgen.genExpression(node.expression), 0); 22 | } 23 | 24 | if (symtab.isMeso(parent)) { 25 | return parent.data.get(node.name.getText())!; 26 | } else { 27 | const e = this.cgen.cgClassDeclaration.genPropertyAccessExpression(node); 28 | return new symtab.Leaf(e, 0); 29 | } 30 | } 31 | 32 | public genPropertyAccessExpression(node: ts.PropertyAccessExpression): llvm.Value { 33 | const real = this.getPropertyAccessExpression(node) as symtab.Leaf; 34 | let r = real.data; 35 | for (let i = 0; i < real.ptrs; i++) { 36 | r = this.cgen.builder.createLoad(r); 37 | } 38 | return r; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/codegen/return-statement.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenReturn { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genReturnStatement(node: ts.ReturnStatement): llvm.Value { 14 | if (node.expression) { 15 | return this.cgen.builder.createRet(this.cgen.genExpression(node.expression)); 16 | } else { 17 | return this.cgen.builder.createRetVoid(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/codegen/string-literal-expression.ts: -------------------------------------------------------------------------------- 1 | // [0] https://stackoverflow.com/questions/1061753/how-can-i-implement-a-string-data-type-in-llvm 2 | import llvm from 'llvm-node'; 3 | import ts from 'typescript'; 4 | 5 | import LLVMCodeGen from './'; 6 | 7 | export default class CodeGenString { 8 | private cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public isStringLiteral(expr: ts.Expression): boolean { 15 | if (ts.isStringLiteral(expr)) { 16 | return true; 17 | } 18 | const type = this.cgen.checker.getTypeAtLocation(expr); 19 | if (type.isStringLiteral()) { 20 | return true; 21 | } 22 | if (type.flags === ts.TypeFlags.String) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | 28 | public genStringLiteral(node: ts.StringLiteral): llvm.Value { 29 | if (this.cgen.currentFunction) { 30 | return this.genStringLiteralLocale(node); 31 | } else { 32 | return this.genStringLiteralGlobal(node); 33 | } 34 | } 35 | 36 | public genStringLiteralLocale(node: ts.StringLiteral): llvm.Value { 37 | return this.cgen.builder.createGlobalStringPtr(node.text); 38 | } 39 | 40 | // [0] How to create global string array? http://lists.llvm.org/pipermail/llvm-dev/2010-June/032072.html 41 | public genStringLiteralGlobal(node: ts.StringLiteral): llvm.Value { 42 | const v = llvm.ConstantDataArray.getString(this.cgen.context, node.text); 43 | const r = new llvm.GlobalVariable(this.cgen.module, v.type, false, llvm.LinkageTypes.ExternalLinkage, v); 44 | return this.cgen.builder.createBitCast(r, llvm.Type.getInt8PtrTy(this.cgen.context)); 45 | } 46 | 47 | public getElementAccess(identifier: llvm.Value, i: llvm.Value): llvm.Value { 48 | const ptr = this.cgen.builder.createInBoundsGEP(identifier, [i]); 49 | const val = this.cgen.builder.createLoad(ptr); 50 | 51 | const arrayType = llvm.ArrayType.get(llvm.Type.getInt8Ty(this.cgen.context), 2); 52 | const arraySize = llvm.ConstantInt.get(this.cgen.context, 2, 64); 53 | const arrayPtr = this.cgen.builder.createAlloca(arrayType, arraySize); 54 | 55 | const ptr0 = this.cgen.builder.createInBoundsGEP(arrayPtr, [ 56 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 57 | llvm.ConstantInt.get(this.cgen.context, 0, 64) 58 | ]); 59 | const ptr1 = this.cgen.builder.createInBoundsGEP(arrayPtr, [ 60 | llvm.ConstantInt.get(this.cgen.context, 0, 64), 61 | llvm.ConstantInt.get(this.cgen.context, 1, 64) 62 | ]); 63 | this.cgen.builder.createStore(val, ptr0); 64 | this.cgen.builder.createStore(llvm.ConstantInt.get(this.cgen.context, 0, 8), ptr1); 65 | return this.cgen.builder.createBitCast(arrayPtr, llvm.Type.getInt8PtrTy(this.cgen.context)); 66 | } 67 | 68 | public genElementAccessExpression(node: ts.ElementAccessExpression): llvm.Value { 69 | const identifier = this.cgen.genExpression(node.expression); 70 | const argumentExpression = this.cgen.genExpression(node.argumentExpression); 71 | return this.getElementAccess(identifier, argumentExpression); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/codegen/variable-declaration.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import * as symtab from '../symtab'; 5 | import LLVMCodeGen from './'; 6 | 7 | export default class CodeGenArray { 8 | private cgen: LLVMCodeGen; 9 | 10 | constructor(cgen: LLVMCodeGen) { 11 | this.cgen = cgen; 12 | } 13 | 14 | public genVariableDeclaration(node: ts.VariableDeclaration): void { 15 | this.cgen.withType(node.type, () => { 16 | const name = node.name.getText(); 17 | const initializer = this.cgen.genExpression(node.initializer!); 18 | const type = initializer.type; 19 | 20 | if (this.cgen.currentFunction) { 21 | const alloca = this.cgen.builder.createAlloca(type, undefined, name); 22 | this.cgen.builder.createStore(initializer, alloca); 23 | this.cgen.symtab.set(name, new symtab.Leaf(alloca, 1)); 24 | return; 25 | } 26 | 27 | switch (node.initializer!.kind) { 28 | case ts.SyntaxKind.NumericLiteral: 29 | this.cgen.symtab.set(name, new symtab.Leaf(this.cgen.genGlobalVariable(initializer as llvm.Constant), 1)); 30 | break; 31 | case ts.SyntaxKind.StringLiteral: 32 | this.cgen.symtab.set(name, new symtab.Leaf(this.cgen.genGlobalVariable(initializer as llvm.Constant), 1)); 33 | break; 34 | case ts.SyntaxKind.ArrayLiteralExpression: 35 | this.cgen.symtab.set(name, new symtab.Leaf(initializer, 0)); 36 | break; 37 | case ts.SyntaxKind.ObjectLiteralExpression: 38 | this.cgen.symtab.set(name, new symtab.Leaf(initializer, 0)); 39 | break; 40 | } 41 | return; 42 | }); 43 | return; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/codegen/while-statement.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | import ts from 'typescript'; 3 | 4 | import LLVMCodeGen from './'; 5 | 6 | export default class CodeGenWhile { 7 | private cgen: LLVMCodeGen; 8 | 9 | constructor(cgen: LLVMCodeGen) { 10 | this.cgen = cgen; 11 | } 12 | 13 | public genWhileStatement(node: ts.WhileStatement): void { 14 | const loopCond = llvm.BasicBlock.create(this.cgen.context, 'loop.cond', this.cgen.currentFunction); 15 | const loopBody = llvm.BasicBlock.create(this.cgen.context, 'loop.body', this.cgen.currentFunction); 16 | const loopQuit = llvm.BasicBlock.create(this.cgen.context, 'loop.quit', this.cgen.currentFunction); 17 | 18 | this.cgen.builder.createBr(loopCond); 19 | this.cgen.builder.setInsertionPoint(loopCond); 20 | const cond = this.cgen.genExpression(node.expression); 21 | this.cgen.builder.createCondBr(cond, loopBody, loopQuit); 22 | 23 | this.cgen.builder.setInsertionPoint(loopBody); 24 | this.cgen.withContinueBreakBlock(loopCond, loopQuit, () => { 25 | this.cgen.genStatement(node.statement); 26 | }); 27 | this.cgen.builder.createBr(loopCond); 28 | this.cgen.builder.setInsertionPoint(loopQuit); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // References: 2 | // [0] https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API 3 | // [1] https://github.com/microsoft/TypeScript/blob/master/doc/spec.md 4 | 5 | process.env.DEBUG_COLORS = '0'; 6 | process.env.DEBUG_HIDE_DATE = '1'; 7 | process.env.DEBUG = 'minits*'; 8 | 9 | import commander from 'commander'; 10 | import fs from 'fs'; 11 | import llvm from 'llvm-node'; 12 | import path from 'path'; 13 | import shell from 'shelljs'; 14 | 15 | import LLVMCodeGen from './codegen'; 16 | import Prelude from './prelude'; 17 | 18 | const program = new commander.Command(); 19 | 20 | program.version('v0.0.1'); 21 | 22 | program 23 | .command('build ') 24 | .description('compile packages and dependencies') 25 | .option('-o, --output ', 'place the output into ') 26 | .option('-s, --show', 'show IR code to stdout') 27 | .option('-t, --triple ', 'LLVM triple') 28 | .action((args, opts) => build(args, opts)); 29 | 30 | program 31 | .command('run ') 32 | .description('compile and run ts program') 33 | .option('-t, --triple ', 'LLVM triple') 34 | .action((args, opts) => run(args, opts)); 35 | 36 | program.parse(process.argv); 37 | 38 | interface BuildInfo { 39 | tempdir: string; 40 | } 41 | 42 | function build(args: any, opts: any): BuildInfo { 43 | llvm.initializeAllTargetInfos(); 44 | llvm.initializeAllTargets(); 45 | llvm.initializeAllTargetMCs(); 46 | llvm.initializeAllAsmParsers(); 47 | llvm.initializeAllAsmPrinters(); 48 | 49 | const prelude = new Prelude(args); 50 | const outputs = prelude.process(); 51 | 52 | const codegen = new LLVMCodeGen(outputs); 53 | const triple: string = opts.triple ? opts.triple : llvm.config.LLVM_DEFAULT_TARGET_TRIPLE; 54 | const target = llvm.TargetRegistry.lookupTarget(triple); 55 | const m = target.createTargetMachine(triple, 'generic'); 56 | codegen.module.dataLayout = m.createDataLayout(); 57 | codegen.module.targetTriple = triple; 58 | codegen.module.sourceFileName = outputs; 59 | codegen.genSourceFile(outputs); 60 | 61 | const codeText = codegen.genText(); 62 | const output = path.join(path.dirname(outputs), 'output.ll'); 63 | fs.writeFileSync(output, codeText); 64 | if (opts.show) { 65 | process.stdout.write(codeText); 66 | } 67 | if (opts.output) { 68 | fs.copyFileSync(output, opts.output); 69 | } 70 | llvm.verifyModule(codegen.module); 71 | 72 | return { 73 | tempdir: path.dirname(outputs) 74 | }; 75 | } 76 | 77 | function run(args: any, opts: any): void { 78 | const info = build(args, opts); 79 | const output = path.join(info.tempdir, 'output.ll'); 80 | const execResp = shell.exec(`lli ${output}`, { 81 | async: false 82 | }); 83 | process.exit(execResp.code); 84 | } 85 | -------------------------------------------------------------------------------- /src/prelude.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import Debug from 'debug'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import shell from 'shelljs'; 6 | import ts from 'typescript'; 7 | 8 | const debug = Debug('minits:prelude'); 9 | 10 | interface Option { 11 | entryfn: string; 12 | tempdir: string; 13 | } 14 | 15 | class Base { 16 | public option: Option; 17 | public program: ts.Program; 18 | public checker: ts.TypeChecker; 19 | 20 | constructor(option: Option) { 21 | this.option = option; 22 | this.program = ts.createProgram([option.entryfn], {}); 23 | this.checker = this.program.getTypeChecker(); 24 | } 25 | 26 | public process(): string { 27 | return ''; 28 | } 29 | 30 | // 008 SyntaxKind.NumericLiteral 31 | public genNumericLiteral(node: ts.NumericLiteral): ts.NumericLiteral { 32 | return ts.createNumericLiteral(node.text); 33 | } 34 | 35 | // 010 SyntaxKind.StringLiteral 36 | public genStringLiteral(node: ts.StringLiteral): ts.StringLiteral { 37 | return ts.createStringLiteral(node.text); 38 | } 39 | 40 | // 073 SyntaxKind.Identifier 41 | public genIdentifier(node: ts.Identifier): ts.Identifier { 42 | return ts.createIdentifier(node.text); 43 | } 44 | 45 | // 101 SyntaxKind.ThisKeyword 46 | public genThisKeyword(): ts.Expression { 47 | return ts.createThis(); 48 | } 49 | 50 | // 188 SyntaxKind.ArrayLiteralExpression 51 | public genArrayLiteralExpression(node: ts.ArrayLiteralExpression): ts.ArrayLiteralExpression { 52 | return ts.createArrayLiteral(node.elements.map(e => this.genExpression(e)), false); 53 | } 54 | 55 | // 189 SyntaxKind.ObjectLiteralExpression 56 | public genObjectLiteralExpression(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { 57 | const properties = node.properties.map(e => { 58 | const a = e as ts.PropertyAssignment; 59 | return ts.createPropertyAssignment(a.name, this.genExpression(a.initializer)); 60 | }); 61 | return ts.createObjectLiteral(properties, false); 62 | } 63 | 64 | // 190 SyntaxKind.PropertyAccessExpression 65 | public genPropertyAccessExpression(node: ts.PropertyAccessExpression): ts.Expression { 66 | return ts.createPropertyAccess(this.genExpression(node.expression), this.genIdentifier(node.name)); 67 | } 68 | 69 | // 191 SyntaxKind.ElementAccessExpression 70 | public genElementAccessExpression(node: ts.ElementAccessExpression): ts.ElementAccessExpression { 71 | return ts.createElementAccess(this.genExpression(node.expression), this.genExpression(node.argumentExpression)); 72 | } 73 | 74 | // 192 SyntaxKind.CallExpression 75 | public genCallExpression(node: ts.CallExpression): ts.CallExpression { 76 | return ts.createCall( 77 | this.genExpression(node.expression), 78 | node.typeArguments, 79 | ts.createNodeArray(node.arguments.map(e => this.genExpression(e))) 80 | ); 81 | } 82 | 83 | // 193 SyntaxKind.NewExpression 84 | public genNewExpression(node: ts.NewExpression): ts.NewExpression { 85 | return ts.createNew( 86 | this.genExpression(node.expression), 87 | node.typeArguments, 88 | node.arguments ? node.arguments!.map(e => this.genExpression(e)) : undefined 89 | ); 90 | } 91 | 92 | // 203 SyntaxKind.PrefixUnaryExpression 93 | public genPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ts.Expression { 94 | return ts.createPrefix(node.operator, this.genExpression(node.operand)); 95 | } 96 | 97 | // 204 SyntaxKind.PostfixUnaryExpression 98 | public genPostfixUnaryExpression(node: ts.PostfixUnaryExpression): ts.Expression { 99 | return ts.createPostfix(this.genExpression(node.operand), node.operator); 100 | } 101 | 102 | // 205 SyntaxKind.BinaryExpression 103 | public genBinaryExpression(node: ts.BinaryExpression): ts.Expression { 104 | return ts.createBinary(this.genExpression(node.left), node.operatorToken, this.genExpression(node.right)); 105 | } 106 | 107 | // 206 SyntaxKind.ConditionalExpression 108 | public genConditionalExpression(node: ts.ConditionalExpression): ts.ConditionalExpression { 109 | return ts.createConditional( 110 | this.genExpression(node.condition), 111 | this.genExpression(node.whenTrue), 112 | this.genExpression(node.whenFalse) 113 | ); 114 | } 115 | 116 | // 219 SyntaxKind.Block 117 | public genBlock(node: ts.Block): ts.Block { 118 | return ts.createBlock(this.genStatementList(node.statements), true); 119 | } 120 | 121 | // 220 SyntaxKind.VariableStatement 122 | public genVariableStatement(node: ts.VariableStatement): ts.VariableStatement { 123 | return ts.createVariableStatement(node.modifiers, this.genVariableDeclarationList(node.declarationList)); 124 | } 125 | 126 | // 222 SyntaxKind.ExpressionStatement 127 | public genExpressionStatement(node: ts.ExpressionStatement): ts.ExpressionStatement { 128 | return ts.createExpressionStatement(this.genExpression(node.expression)); 129 | } 130 | 131 | // 223 SyntaxKind.IfStatement 132 | public genIfStatement(node: ts.IfStatement): ts.IfStatement { 133 | return ts.createIf( 134 | this.genExpression(node.expression), 135 | this.genStatement(node.thenStatement), 136 | node.elseStatement ? this.genStatement(node.elseStatement) : undefined 137 | ); 138 | } 139 | 140 | // 224 SyntaxKind.DoStatement 141 | public genDoStatement(node: ts.DoStatement): ts.DoStatement { 142 | return ts.createDo(this.genStatement(node), this.genExpression(node.expression)); 143 | } 144 | 145 | // 225 SyntaxKind.WhileStatement 146 | public genWhileStatement(node: ts.WhileStatement): ts.WhileStatement { 147 | return ts.createWhile(this.genExpression(node.expression), this.genStatement(node.statement)); 148 | } 149 | 150 | // 226 SyntaxKind.ForStatement 151 | public genForStatement(node: ts.ForStatement): ts.ForStatement { 152 | const initializer = (() => { 153 | if (!node.initializer) { 154 | return undefined; 155 | } 156 | if (ts.isVariableDeclarationList(node.initializer)) { 157 | return this.genVariableDeclarationList(node.initializer); 158 | } 159 | return this.genExpression(node.initializer); 160 | })(); 161 | return ts.createFor( 162 | initializer, 163 | node.condition ? this.genExpression(node.condition) : undefined, 164 | node.incrementor ? this.genExpression(node.incrementor) : undefined, 165 | this.genStatement(node.statement) 166 | ); 167 | } 168 | 169 | // 228 SyntaxKind.ForOfStatement 170 | public genForOfStatement(node: ts.ForOfStatement): ts.ForOfStatement { 171 | return ts.createForOf( 172 | node.awaitModifier, 173 | this.genExpression(node.initializer as ts.Expression), 174 | this.genExpression(node.expression), 175 | this.genStatement(node.statement) 176 | ); 177 | } 178 | 179 | // 231 SyntaxKind.ReturnStatement 180 | public genReturnStatement(node: ts.ReturnStatement): ts.ReturnStatement { 181 | return ts.createReturn(node.expression ? this.genExpression(node.expression) : undefined); 182 | } 183 | 184 | // 233 SyntaxKind.SwitchStatement 185 | public genSwitchStatement(node: ts.SwitchStatement): ts.Statement { 186 | return ts.createSwitch( 187 | this.genExpression(node.expression), 188 | ts.createCaseBlock( 189 | node.caseBlock.clauses.map(node => { 190 | if (node.kind === ts.SyntaxKind.CaseClause) { 191 | return ts.createCaseClause(this.genExpression(node.expression), this.genStatementList(node.statements)); 192 | } else { 193 | return ts.createDefaultClause(this.genStatementList(node.statements)); 194 | } 195 | }) 196 | ) 197 | ); 198 | } 199 | 200 | // 238 SyntaxKind.VariableDeclaration 201 | public genVariableDeclaration(node: ts.VariableDeclaration): ts.VariableDeclaration { 202 | return ts.createVariableDeclaration( 203 | node.name, 204 | node.type, 205 | node.initializer ? this.genExpression(node.initializer) : undefined 206 | ); 207 | } 208 | 209 | // 239 SyntaxKind.VariableDeclarationList 210 | public genVariableDeclarationList(node: ts.VariableDeclarationList): ts.VariableDeclarationList { 211 | return ts.createVariableDeclarationList(node.declarations.map(e => this.genVariableDeclaration(e)), node.flags); 212 | } 213 | 214 | // 240 SyntaxKind.FunctionDeclaration 215 | public genFunctionDeclaration(node: ts.FunctionDeclaration): ts.FunctionDeclaration { 216 | return ts.createFunctionDeclaration( 217 | node.decorators, 218 | node.modifiers, 219 | node.asteriskToken, 220 | node.name, 221 | node.typeParameters, 222 | node.parameters, 223 | node.type ? node.type : ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword), 224 | node.body ? this.genBlock(node.body) : undefined 225 | ); 226 | } 227 | 228 | // 241 SyntaxKind.ClassDeclaration 229 | public genClassDeclaration(node: ts.ClassDeclaration): ts.Block { 230 | return ts.createBlock([ 231 | ts.createClassDeclaration( 232 | node.decorators, 233 | node.modifiers, 234 | node.name, 235 | node.typeParameters, 236 | node.heritageClauses, 237 | ts.createNodeArray( 238 | node.members.map(node => { 239 | if (ts.isPropertyDeclaration(node)) { 240 | return ts.createProperty( 241 | node.decorators, 242 | node.modifiers, 243 | node.name, 244 | node.questionToken, 245 | node.type, 246 | node.initializer 247 | ); 248 | } 249 | if (ts.isConstructorDeclaration(node)) { 250 | return ts.createConstructor(node.decorators, node.modifiers, node.parameters, this.genBlock(node.body!)); 251 | } 252 | if (ts.isMethodDeclaration(node)) { 253 | return ts.createMethod( 254 | node.decorators, 255 | node.modifiers, 256 | node.asteriskToken, 257 | node.name, 258 | node.questionToken, 259 | node.typeParameters, 260 | node.parameters, 261 | node.type, 262 | this.genBlock(node.body!) 263 | ); 264 | } 265 | return node; 266 | }) 267 | ) 268 | ) 269 | ]); 270 | } 271 | 272 | // 244 SyntaxKind.EnumDeclaration 273 | public genEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration { 274 | return ts.createEnumDeclaration( 275 | node.decorators, 276 | node.modifiers, 277 | node.name, 278 | node.members.map(e => { 279 | return ts.createEnumMember(e.name, e.initializer ? this.genExpression(e.initializer) : undefined); 280 | }) 281 | ); 282 | } 283 | 284 | // 245 SyntaxKind.ModuleDeclaration 285 | public genModuleDeclaration(node: ts.ModuleDeclaration): ts.ModuleDeclaration { 286 | return ts.createModuleDeclaration( 287 | node.decorators, 288 | node.modifiers, 289 | ts.createIdentifier(node.name.text), 290 | ts.createModuleBlock((node.body! as ts.ModuleBlock).statements.map(e => this.genStatement(e))), 291 | node.flags 292 | ); 293 | } 294 | 295 | // 285 SyntaxKind.SourceFile 296 | public genSourceFile(node: ts.SourceFile): ts.Statement[] { 297 | const r: ts.Statement[] = []; 298 | node.forEachChild(node => { 299 | switch (node.kind) { 300 | case ts.SyntaxKind.VariableStatement: 301 | r.push(this.genStatement(node as ts.VariableStatement)); 302 | break; 303 | case ts.SyntaxKind.FunctionDeclaration: 304 | r.push(this.genStatement(node as ts.FunctionDeclaration)); 305 | break; 306 | case ts.SyntaxKind.ClassDeclaration: 307 | this.genClassDeclaration(node as ts.ClassDeclaration).statements.forEach(node => { 308 | r.push(node); 309 | }); 310 | break; 311 | case ts.SyntaxKind.EnumDeclaration: 312 | r.push(this.genStatement(node as ts.EnumDeclaration)); 313 | break; 314 | case ts.SyntaxKind.ModuleDeclaration: 315 | r.push(this.genStatement(node as ts.ModuleDeclaration)); 316 | break; 317 | } 318 | }); 319 | return r; 320 | } 321 | 322 | public genExpression(node: ts.Expression): ts.Expression { 323 | switch (node.kind) { 324 | case ts.SyntaxKind.NumericLiteral: 325 | return this.genNumericLiteral(node as ts.NumericLiteral); 326 | case ts.SyntaxKind.StringLiteral: 327 | return this.genStringLiteral(node as ts.StringLiteral); 328 | case ts.SyntaxKind.Identifier: 329 | return this.genIdentifier(node as ts.Identifier); 330 | case ts.SyntaxKind.ThisKeyword: 331 | return this.genThisKeyword(); 332 | case ts.SyntaxKind.ArrayLiteralExpression: 333 | return this.genArrayLiteralExpression(node as ts.ArrayLiteralExpression); 334 | case ts.SyntaxKind.ObjectLiteralExpression: 335 | return this.genObjectLiteralExpression(node as ts.ObjectLiteralExpression); 336 | case ts.SyntaxKind.PropertyAccessExpression: 337 | return this.genPropertyAccessExpression(node as ts.PropertyAccessExpression); 338 | case ts.SyntaxKind.ElementAccessExpression: 339 | return this.genElementAccessExpression(node as ts.ElementAccessExpression); 340 | case ts.SyntaxKind.CallExpression: 341 | return this.genCallExpression(node as ts.CallExpression); 342 | case ts.SyntaxKind.NewExpression: 343 | return this.genNewExpression(node as ts.NewExpression); 344 | case ts.SyntaxKind.PrefixUnaryExpression: 345 | return this.genPrefixUnaryExpression(node as ts.PrefixUnaryExpression); 346 | case ts.SyntaxKind.PostfixUnaryExpression: 347 | return this.genPostfixUnaryExpression(node as ts.PostfixUnaryExpression); 348 | case ts.SyntaxKind.BinaryExpression: 349 | return this.genBinaryExpression(node as ts.BinaryExpression); 350 | case ts.SyntaxKind.ConditionalExpression: 351 | return this.genConditionalExpression(node as ts.ConditionalExpression); 352 | } 353 | return node; 354 | } 355 | 356 | public genStatement(node: ts.Statement): ts.Statement { 357 | switch (node.kind) { 358 | case ts.SyntaxKind.Block: 359 | return this.genBlock(node as ts.Block); 360 | case ts.SyntaxKind.VariableStatement: 361 | return this.genVariableStatement(node as ts.VariableStatement); 362 | case ts.SyntaxKind.ExpressionStatement: 363 | return this.genExpressionStatement(node as ts.ExpressionStatement); 364 | case ts.SyntaxKind.IfStatement: 365 | return this.genIfStatement(node as ts.IfStatement); 366 | case ts.SyntaxKind.DoStatement: 367 | return this.genDoStatement(node as ts.DoStatement); 368 | case ts.SyntaxKind.WhileStatement: 369 | return this.genWhileStatement(node as ts.WhileStatement); 370 | case ts.SyntaxKind.ForStatement: 371 | return this.genForStatement(node as ts.ForStatement); 372 | case ts.SyntaxKind.ForOfStatement: 373 | return this.genForOfStatement(node as ts.ForOfStatement); 374 | case ts.SyntaxKind.ReturnStatement: 375 | return this.genReturnStatement(node as ts.ReturnStatement); 376 | case ts.SyntaxKind.SwitchStatement: 377 | return this.genSwitchStatement(node as ts.SwitchStatement); 378 | case ts.SyntaxKind.FunctionDeclaration: 379 | return this.genFunctionDeclaration(node as ts.FunctionDeclaration); 380 | case ts.SyntaxKind.ClassDeclaration: 381 | return this.genClassDeclaration(node as ts.ClassDeclaration); 382 | case ts.SyntaxKind.EnumDeclaration: 383 | return this.genEnumDeclaration(node as ts.EnumDeclaration); 384 | case ts.SyntaxKind.ModuleDeclaration: 385 | return this.genModuleDeclaration(node as ts.ModuleDeclaration); 386 | default: 387 | return node; 388 | } 389 | } 390 | 391 | public genStatementList(node: ts.NodeArray): ts.NodeArray { 392 | const statements: ts.Statement[] = []; 393 | node.forEach(node => { 394 | if (ts.isBlock(node)) { 395 | statements.push(...this.genStatementList(node.statements)); 396 | } else { 397 | statements.push(this.genStatement(node)); 398 | } 399 | }); 400 | return ts.createNodeArray(statements); 401 | } 402 | } 403 | 404 | // Layer0 is a module-level transformation. It process lib importation as an internal module. 405 | // 406 | // Before 407 | // 408 | // lib.ts 409 | // export const name = 'lib'; 410 | // main.ts 411 | // import * as lib from './lib'; 412 | // 413 | // Bypass 414 | // 415 | // main.ts 416 | // module lib { 417 | // export const name = 'lib'; 418 | // } 419 | class PreludeLayer0 extends Base { 420 | public process(): string { 421 | const depends = this.getDepends(this.option.entryfn).slice(1); 422 | for (const e of depends) { 423 | debug(`Find depend ${e}`); 424 | } 425 | const allfile = [this.option.entryfn, ...depends]; 426 | const program = ts.createProgram(allfile, {}); 427 | 428 | const tmpPath = path.join(this.option.tempdir, 'output.layer0.ts'); 429 | const tmpFile = ts.createSourceFile( 430 | tmpPath, 431 | fs.readFileSync(this.option.entryfn).toString(), 432 | ts.ScriptTarget.Latest, 433 | false, 434 | ts.ScriptKind.TS 435 | ); 436 | const printer = ts.createPrinter({ 437 | newLine: ts.NewLineKind.LineFeed 438 | }); 439 | const r: string[] = []; 440 | depends.reverse().forEach(file => { 441 | const a = ts.createModuleDeclaration( 442 | undefined, 443 | undefined, 444 | ts.createIdentifier(path.basename(file).slice(0, -3)), 445 | ts.createModuleBlock(this.genSourceFile(program.getSourceFile(file)!)), 446 | undefined 447 | ); 448 | r.push(printer.printNode(ts.EmitHint.Unspecified, a, tmpFile)); 449 | r.push('\n'); 450 | }); 451 | this.genSourceFile(program.getSourceFile(this.option.entryfn)!).forEach(e => { 452 | r.push(printer.printNode(ts.EmitHint.Unspecified, e, tmpFile)); 453 | r.push('\n'); 454 | }); 455 | fs.writeFileSync(tmpPath, r.join('')); 456 | debug(`Expand ${this.option.entryfn} => ${tmpPath}`); 457 | return tmpPath; 458 | } 459 | 460 | // Dependency tree takes in a starting file, extracts its declared dependencies via precinct, resolves each of those 461 | // dependencies to a file on the filesystem via filing-cabinet, then recursively performs those steps until there are 462 | // no more dependencies to process. 463 | // 464 | // GA: DFS. 465 | private getDepends(main: string): string[] { 466 | const open: string[] = [main]; 467 | let closed: string[] = []; 468 | 469 | while (open.length !== 0) { 470 | const name = open.pop()!; 471 | const sourceFile = ts.createSourceFile(name, fs.readFileSync(name).toString(), ts.ScriptTarget.ES2020, true); 472 | sourceFile.forEachChild(node => { 473 | if (!ts.isImportDeclaration(node)) { 474 | return; 475 | } 476 | const importNode = node as ts.ImportDeclaration; 477 | const relPath = (importNode.moduleSpecifier as ts.StringLiteral).text + '.ts'; 478 | const absPath = path.join(path.dirname(name), relPath); 479 | if (closed.includes(absPath)) { 480 | closed = closed.filter(e => e !== absPath); 481 | closed.push(absPath); 482 | return; 483 | } 484 | open.push(absPath); 485 | }); 486 | closed.push(name); 487 | } 488 | return closed; 489 | } 490 | } 491 | 492 | // Layer1 is a class-level transformation. It represents method as a normal function. 493 | // 494 | // Before 495 | // class Point { 496 | // echo() {} 497 | // } 498 | // 499 | // Bypass 500 | // class Point {} 501 | // function Point_echo(_this: Point) {} 502 | // 503 | class PreludeLayer1 extends Base { 504 | public process(): string { 505 | const tmpPath = path.join(this.option.tempdir, 'output.layer1.ts'); 506 | const tmpFile = ts.createSourceFile( 507 | tmpPath, 508 | fs.readFileSync(this.option.entryfn).toString(), 509 | ts.ScriptTarget.Latest, 510 | false, 511 | ts.ScriptKind.TS 512 | ); 513 | const printer = ts.createPrinter({ 514 | newLine: ts.NewLineKind.LineFeed 515 | }); 516 | const r: string[] = []; 517 | this.genSourceFile(this.program.getSourceFile(this.option.entryfn)!).forEach(e => { 518 | r.push(printer.printNode(ts.EmitHint.Unspecified, e, tmpFile)); 519 | r.push('\n'); 520 | }); 521 | fs.writeFileSync(tmpPath, r.join('')); 522 | debug(`Expand ${this.option.entryfn} => ${tmpPath}`); 523 | return tmpPath; 524 | } 525 | 526 | // 101 SyntaxKind.ThisKeyword 527 | public genThisKeyword(): ts.Identifier { 528 | return ts.createIdentifier('_this'); 529 | } 530 | 531 | // 241 SyntaxKind.ClassDeclaration 532 | public genClassDeclaration(node: ts.ClassDeclaration): ts.Block { 533 | const properties: ts.PropertyDeclaration[] = []; 534 | const constructor: ts.ConstructorDeclaration[] = []; 535 | const methods: ts.MethodDeclaration[] = []; 536 | 537 | for (const e of node.members) { 538 | if (ts.isPropertyDeclaration(e)) { 539 | properties.push(e); 540 | continue; 541 | } 542 | if (ts.isConstructorDeclaration(e)) { 543 | constructor.push(e); 544 | continue; 545 | } 546 | if (ts.isMethodDeclaration(e)) { 547 | methods.push(e); 548 | continue; 549 | } 550 | } 551 | 552 | const statements: ts.Statement[] = []; 553 | statements.push(node); 554 | 555 | if (constructor.length !== 0) { 556 | const c = this.genConstructorDeclaration(constructor[0], node.name!.text); 557 | statements.push(c); 558 | } 559 | for (const e of methods) { 560 | const c = this.genMethodDeclaration(e, node.name!.text); 561 | statements.push(c); 562 | } 563 | 564 | return ts.createBlock(statements); 565 | } 566 | 567 | private genConstructorDeclaration(node: ts.ConstructorDeclaration, name: string): ts.FunctionDeclaration { 568 | const body: ts.Statement[] = []; 569 | for (const e of node.body!.statements) { 570 | const f = this.genStatement(e); 571 | body.push(f); 572 | } 573 | 574 | const arg0 = ts.createParameter( 575 | undefined, 576 | undefined, 577 | undefined, 578 | ts.createIdentifier('_this'), 579 | undefined, 580 | ts.createTypeReferenceNode(ts.createIdentifier(name), undefined), 581 | undefined 582 | ); 583 | const args = [arg0, ...node.parameters]; 584 | 585 | const full = ts.createFunctionDeclaration( 586 | undefined, 587 | undefined, 588 | undefined, 589 | ts.createIdentifier(name + '_constructor'), 590 | undefined, 591 | args, 592 | ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword), 593 | ts.createBlock(body, true) 594 | ); 595 | 596 | return full; 597 | } 598 | 599 | private genMethodDeclaration(node: ts.MethodDeclaration, name: string): ts.FunctionDeclaration { 600 | const body: ts.Statement[] = []; 601 | for (const e of node.body!.statements) { 602 | const f = this.genStatement(e); 603 | body.push(f); 604 | } 605 | 606 | const arg0 = ts.createParameter( 607 | undefined, 608 | undefined, 609 | undefined, 610 | ts.createIdentifier('_this'), 611 | undefined, 612 | ts.createTypeReferenceNode(ts.createIdentifier(name), undefined), 613 | undefined 614 | ); 615 | const args = [arg0, ...node.parameters]; 616 | 617 | const full = ts.createFunctionDeclaration( 618 | undefined, 619 | undefined, 620 | undefined, 621 | ts.createIdentifier(name + '_' + (node.name as ts.Identifier).text), 622 | undefined, 623 | args, 624 | node.type, 625 | ts.createBlock(body, true) 626 | ); 627 | 628 | return full; 629 | } 630 | } 631 | 632 | // Layer2 is a function-level transformation. 633 | class PreludeLayer2 extends Base { 634 | public process(): string { 635 | const tmpPath = path.join(this.option.tempdir, 'output.layer2.ts'); 636 | const tmpFile = ts.createSourceFile( 637 | tmpPath, 638 | fs.readFileSync(this.option.entryfn).toString(), 639 | ts.ScriptTarget.Latest, 640 | false, 641 | ts.ScriptKind.TS 642 | ); 643 | const printer = ts.createPrinter({ 644 | newLine: ts.NewLineKind.LineFeed 645 | }); 646 | const r: string[] = []; 647 | this.genSourceFile(this.program.getSourceFile(this.option.entryfn)!).forEach(e => { 648 | r.push(printer.printNode(ts.EmitHint.Unspecified, e, tmpFile)); 649 | r.push('\n'); 650 | }); 651 | fs.writeFileSync(tmpPath, r.join('')); 652 | debug(`Expand ${this.option.entryfn} => ${tmpPath}`); 653 | return tmpPath; 654 | } 655 | 656 | // 240 SyntaxKind.FunctionDeclaration 657 | // 658 | // The function must explicitly specify its return type. The void return type could be ignored, but it is now forced 659 | // to join. 660 | // 661 | // Example: 662 | // function echo() { return; } => function echo(): void { return; } 663 | public genFunctionDeclaration(node: ts.FunctionDeclaration): ts.FunctionDeclaration { 664 | return ts.createFunctionDeclaration( 665 | node.decorators, 666 | node.modifiers, 667 | node.asteriskToken, 668 | node.name, 669 | node.typeParameters, 670 | node.parameters, 671 | node.type ? node.type : ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword), 672 | node.body ? this.genBlock(node.body) : undefined 673 | ); 674 | } 675 | } 676 | 677 | // Layer3 is a statement-level transformation. 678 | class PreludeLayer3 extends Base { 679 | public process(): string { 680 | const tmpPath = path.join(this.option.tempdir, 'output.layer3.ts'); 681 | const tmpFile = ts.createSourceFile( 682 | tmpPath, 683 | fs.readFileSync(this.option.entryfn).toString(), 684 | ts.ScriptTarget.Latest, 685 | false, 686 | ts.ScriptKind.TS 687 | ); 688 | const printer = ts.createPrinter({ 689 | newLine: ts.NewLineKind.LineFeed 690 | }); 691 | const r: string[] = []; 692 | this.genSourceFile(this.program.getSourceFile(this.option.entryfn)!).forEach(e => { 693 | r.push(printer.printNode(ts.EmitHint.Unspecified, e, tmpFile)); 694 | r.push('\n'); 695 | }); 696 | fs.writeFileSync(tmpPath, r.join('')); 697 | debug(`Expand ${this.option.entryfn} => ${tmpPath}`); 698 | return tmpPath; 699 | } 700 | 701 | // 233 SyntaxKind.SwitchStatement 702 | // Convert a switch statement to an if statement. 703 | public genSwitchStatement(node: ts.SwitchStatement): ts.Statement { 704 | const data: ts.Statement[] = []; 705 | 706 | const quit = this.createQuitDeclare(); 707 | data.push(quit); 708 | 709 | for (const e of node.caseBlock.clauses) { 710 | if (e.kind === ts.SyntaxKind.CaseClause) { 711 | const b = this.createCase(e, this.genExpression(node.expression)); 712 | data.push(b); 713 | } else { 714 | const b = this.createDefault(e); 715 | data.push(b); 716 | } 717 | } 718 | 719 | return ts.createBlock(data); 720 | } 721 | 722 | private createQuitDeclare(): ts.Statement { 723 | return ts.createVariableStatement( 724 | undefined, 725 | ts.createVariableDeclarationList( 726 | [ 727 | ts.createVariableDeclaration( 728 | ts.createIdentifier('_quit'), 729 | ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), 730 | ts.createFalse() 731 | ) 732 | ], 733 | ts.NodeFlags.Let 734 | ) 735 | ); 736 | } 737 | 738 | private createCase(node: ts.CaseClause, cond: ts.Expression): ts.Statement { 739 | const data: ts.Statement[] = []; 740 | for (const e of node.statements) { 741 | if (e.kind === ts.SyntaxKind.BreakStatement) { 742 | const quit = ts.createExpressionStatement( 743 | ts.createBinary(ts.createIdentifier('_quit'), ts.createToken(ts.SyntaxKind.EqualsToken), ts.createTrue()) 744 | ); 745 | data.push(quit); 746 | } else { 747 | data.push(this.genStatement(e)); 748 | } 749 | } 750 | 751 | return ts.createIf( 752 | ts.createBinary( 753 | ts.createPrefix(ts.SyntaxKind.ExclamationToken, ts.createIdentifier('_quit')), 754 | ts.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 755 | this.genExpression( 756 | ts.createBinary(node.expression, ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), cond) 757 | ) 758 | ), 759 | ts.createBlock(data), 760 | undefined 761 | ); 762 | } 763 | 764 | private createDefault(node: ts.DefaultClause): ts.Statement { 765 | const data: ts.Statement[] = []; 766 | for (const e of node.statements) { 767 | if (e.kind === ts.SyntaxKind.BreakStatement) { 768 | continue; 769 | } else { 770 | data.push(this.genStatement(e)); 771 | } 772 | } 773 | 774 | return ts.createIf( 775 | ts.createPrefix(ts.SyntaxKind.ExclamationToken, ts.createIdentifier('_quit')), 776 | ts.createBlock(data), 777 | undefined 778 | ); 779 | } 780 | } 781 | 782 | // Layer4 is a expression-level transformation. 783 | class PreludeLayer4 extends Base { 784 | public process(): string { 785 | const tmpPath = path.join(this.option.tempdir, 'output.layer4.ts'); 786 | const tmpFile = ts.createSourceFile( 787 | tmpPath, 788 | fs.readFileSync(this.option.entryfn).toString(), 789 | ts.ScriptTarget.Latest, 790 | false, 791 | ts.ScriptKind.TS 792 | ); 793 | const printer = ts.createPrinter({ 794 | newLine: ts.NewLineKind.LineFeed 795 | }); 796 | const r: string[] = []; 797 | this.genSourceFile(this.program.getSourceFile(this.option.entryfn)!).forEach(e => { 798 | r.push(printer.printNode(ts.EmitHint.Unspecified, e, tmpFile)); 799 | r.push('\n'); 800 | }); 801 | fs.writeFileSync(tmpPath, r.join('')); 802 | debug(`Expand ${this.option.entryfn} => ${tmpPath}`); 803 | return tmpPath; 804 | } 805 | 806 | // 192 SyntaxKind.CallExpression 807 | public genCallExpression(node: ts.CallExpression): ts.CallExpression { 808 | if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) { 809 | const real = node.expression as ts.PropertyAccessExpression; 810 | const type = this.checker.getTypeAtLocation(real.expression); 811 | 812 | if (type.symbol.valueDeclaration.kind === ts.SyntaxKind.ClassDeclaration) { 813 | return ts.createCall( 814 | ts.createIdentifier(type.symbol.name + '_' + real.name.text), 815 | node.typeArguments, 816 | [real.expression, ...node.arguments].map(e => this.genExpression(e)) 817 | ); 818 | } 819 | } 820 | 821 | return ts.createCall( 822 | this.genExpression(node.expression), 823 | node.typeArguments, 824 | ts.createNodeArray(node.arguments.map(e => this.genExpression(e))) 825 | ); 826 | } 827 | 828 | // 203 SyntaxKind.PrefixUnaryExpression 829 | public genPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ts.Expression { 830 | switch (node.operator) { 831 | // - 832 | // Treat -n as 0 - n. 833 | case ts.SyntaxKind.MinusToken: 834 | return ts.createBinary( 835 | ts.createNumericLiteral('0'), 836 | ts.createToken(ts.SyntaxKind.MinusToken), 837 | this.genExpression(node.operand) 838 | ); 839 | } 840 | return ts.createPrefix(node.operator, this.genExpression(node.operand)); 841 | } 842 | 843 | // 204 SyntaxKind.PostfixUnaryExpression 844 | public genPostfixUnaryExpression(node: ts.PostfixUnaryExpression): ts.Expression { 845 | const operand = this.genExpression(node.operand); 846 | switch (node.operator) { 847 | // ++ 848 | // Treat i++ as i = i + 1 849 | case ts.SyntaxKind.PlusPlusToken: 850 | return ts.createBinary( 851 | operand, 852 | ts.createToken(ts.SyntaxKind.EqualsToken), 853 | ts.createBinary(operand, ts.createToken(ts.SyntaxKind.PlusToken), ts.createNumericLiteral('1')) 854 | ); 855 | // -- 856 | // Treat i-- as i = i - 1 857 | case ts.SyntaxKind.MinusMinusToken: 858 | return ts.createBinary( 859 | operand, 860 | ts.createToken(ts.SyntaxKind.EqualsToken), 861 | ts.createBinary(operand, ts.createToken(ts.SyntaxKind.MinusToken), ts.createNumericLiteral('1')) 862 | ); 863 | } 864 | throw new Error('Error that should never happen'); 865 | } 866 | 867 | // 205 SyntaxKind.BinaryExpression 868 | public genBinaryExpression(node: ts.BinaryExpression): ts.Expression { 869 | const lhs = this.genExpression(node.left); 870 | const rhs = this.genExpression(node.right); 871 | 872 | switch (node.operatorToken.kind) { 873 | // == 874 | // == is equivalent to ===. In a strict type context, type compare is irrelevant. 875 | case ts.SyntaxKind.EqualsEqualsToken: 876 | return this.genBinaryExpression( 877 | ts.createBinary(lhs, ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), rhs) 878 | ); 879 | // != 880 | // != is equivalent to !==. In a strict type context, type compare is irrelevant. 881 | case ts.SyntaxKind.ExclamationEqualsToken: 882 | return this.genBinaryExpression( 883 | ts.createBinary(lhs, ts.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), rhs) 884 | ); 885 | // += 886 | case ts.SyntaxKind.PlusEqualsToken: 887 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.PlusToken); 888 | // -= 889 | case ts.SyntaxKind.MinusEqualsToken: 890 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.MinusToken); 891 | // *= 892 | case ts.SyntaxKind.AsteriskEqualsToken: 893 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.AsteriskToken); 894 | // /= 895 | case ts.SyntaxKind.SlashEqualsToken: 896 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.SlashToken); 897 | // %= 898 | case ts.SyntaxKind.PercentEqualsToken: 899 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.PercentToken); 900 | // <<= 901 | case ts.SyntaxKind.LessThanLessThanEqualsToken: 902 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.LessThanLessThanToken); 903 | // &= 904 | case ts.SyntaxKind.AmpersandEqualsToken: 905 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.AmpersandToken); 906 | // |= 907 | case ts.SyntaxKind.BarEqualsToken: 908 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.BarToken); 909 | // ^= 910 | case ts.SyntaxKind.CaretEqualsToken: 911 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.CaretToken); 912 | // >>= 913 | case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: 914 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.GreaterThanGreaterThanToken); 915 | // >>>= 916 | case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: 917 | return this.genCompoundAssignment(lhs, rhs, ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken); 918 | } 919 | return ts.createBinary(lhs, node.operatorToken, rhs); 920 | } 921 | 922 | // 220 SyntaxKind.VariableStatement 923 | public genVariableDeclaration(node: ts.VariableDeclaration): ts.VariableDeclaration { 924 | // Make up for the missing type tags. 925 | // 926 | // Example: 927 | // let a = 10; => let a: number = 10; 928 | // let a = "Hello"; => let a: string = "Hello"; 929 | // let a = false; => let a: boolean = false; 930 | // let a = [1, 2, 3]; => let a: number[] = [1, 2, 3]; 931 | // let a = new Foo(); => let a: Foo = new Foo(); 932 | const type = (() => { 933 | if (node.type) { 934 | return node.type; 935 | } 936 | const a = this.checker.getTypeAtLocation(node.name); 937 | if (a.flags & ts.TypeFlags.Number) { 938 | return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); 939 | } 940 | if (a.flags & ts.TypeFlags.String) { 941 | return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); 942 | } 943 | if (a.flags & ts.TypeFlags.Boolean) { 944 | return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); 945 | } 946 | if (a.flags & ts.TypeFlags.Object && a.symbol.escapedName.toString() === 'Array') { 947 | if ((a as any).typeArguments[0].flags & ts.TypeFlags.Number) { 948 | return ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)); 949 | } 950 | if ((a as any).typeArguments[0].flags & ts.TypeFlags.String) { 951 | return ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)); 952 | } 953 | if ((a as any).typeArguments[0].flags & ts.TypeFlags.Boolean) { 954 | return ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)); 955 | } 956 | throw new Error('Array type must be specified explicitly'); 957 | } 958 | if (a.flags & ts.TypeFlags.Object && a.symbol.escapedName.toString() !== '__object') { 959 | return ts.createTypeReferenceNode(a.symbol.escapedName.toString(), undefined); 960 | } 961 | return undefined; 962 | })(); 963 | return ts.createVariableDeclaration( 964 | node.name, 965 | type, 966 | node.initializer ? this.genExpression(node.initializer) : undefined 967 | ); 968 | } 969 | 970 | private genCompoundAssignment(lhs: ts.Expression, rhs: ts.Expression, op: ts.BinaryOperator): ts.Expression { 971 | const r = ts.createBinary(lhs, ts.createToken(op), rhs); 972 | return ts.createBinary(lhs, ts.createToken(ts.SyntaxKind.EqualsToken), r); 973 | } 974 | } 975 | 976 | // Layer5 is a c-stdlib-based-level transformation. 977 | class PreludeLayer5 extends Base { 978 | public process(): string { 979 | const tmpPath = path.join(this.option.tempdir, 'output.layer5.ts'); 980 | const tmpFile = ts.createSourceFile( 981 | tmpPath, 982 | fs.readFileSync(this.option.entryfn).toString(), 983 | ts.ScriptTarget.Latest, 984 | false, 985 | ts.ScriptKind.TS 986 | ); 987 | const printer = ts.createPrinter({ 988 | newLine: ts.NewLineKind.LineFeed 989 | }); 990 | const r: string[] = []; 991 | this.genSourceFile(this.program.getSourceFile(this.option.entryfn)!).forEach(e => { 992 | r.push(printer.printNode(ts.EmitHint.Unspecified, e, tmpFile)); 993 | r.push('\n'); 994 | }); 995 | fs.writeFileSync(tmpPath, r.join('')); 996 | debug(`Expand ${this.option.entryfn} => ${tmpPath}`); 997 | return tmpPath; 998 | } 999 | 1000 | // 190 SyntaxKind.PropertyAccessExpression 1001 | public genPropertyAccessExpression(node: ts.PropertyAccessExpression): ts.Expression { 1002 | const objectType = this.checker.getTypeAtLocation(node.expression); 1003 | if (objectType.flags & ts.TypeFlags.String || objectType.flags & ts.TypeFlags.StringLiteral) { 1004 | if (node.name.text === 'length') { 1005 | return ts.createCall( 1006 | ts.createIdentifier('strlen'), 1007 | undefined, 1008 | ts.createNodeArray([this.genExpression(node.expression)]) 1009 | ); 1010 | } 1011 | } 1012 | return ts.createPropertyAccess(this.genExpression(node.expression), this.genIdentifier(node.name)); 1013 | } 1014 | 1015 | // 205 SyntaxKind.BinaryExpression 1016 | public genBinaryExpression(node: ts.BinaryExpression): ts.Expression { 1017 | const lhs = this.genExpression(node.left); 1018 | const lhsType = this.checker.getTypeAtLocation(node.left); 1019 | const rhs = this.genExpression(node.right); 1020 | 1021 | switch (node.operatorToken.kind) { 1022 | // === 1023 | case ts.SyntaxKind.EqualsEqualsEqualsToken: 1024 | // "Hello" === "Hello" => strcmp("Hello", "Hello") === 0 1025 | if (lhsType.flags & ts.TypeFlags.String || lhsType.flags & ts.TypeFlags.StringLiteral) { 1026 | return ts.createBinary( 1027 | ts.createCall(ts.createIdentifier('strcmp'), undefined, ts.createNodeArray([lhs, rhs])), 1028 | ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 1029 | ts.createNumericLiteral('0') 1030 | ); 1031 | } 1032 | return ts.createBinary(lhs, node.operatorToken, rhs); 1033 | // !== 1034 | case ts.SyntaxKind.ExclamationEqualsEqualsToken: 1035 | // "Hello" !== "Hello" => strcmp("Hello", "Hello") !== 0 1036 | if (lhsType.flags & ts.TypeFlags.String || lhsType.flags & ts.TypeFlags.StringLiteral) { 1037 | return ts.createBinary( 1038 | ts.createCall(ts.createIdentifier('strcmp'), undefined, ts.createNodeArray([lhs, rhs])), 1039 | ts.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), 1040 | ts.createNumericLiteral('0') 1041 | ); 1042 | } 1043 | return ts.createBinary(lhs, node.operatorToken, rhs); 1044 | // + 1045 | case ts.SyntaxKind.PlusToken: 1046 | // "Hello" + "Hello" => strcat(strcpy(malloc(a.lenght + b.length + 1), a), b) 1047 | if (lhsType.flags & ts.TypeFlags.String || lhsType.flags & ts.TypeFlags.StringLiteral) { 1048 | return ts.createCall(ts.createIdentifier('strcat'), undefined, [ 1049 | ts.createCall(ts.createIdentifier('strcpy'), undefined, [ 1050 | ts.createCall(ts.createIdentifier('malloc'), undefined, [ 1051 | ts.createBinary( 1052 | ts.createBinary( 1053 | ts.createCall(ts.createIdentifier('strlen'), undefined, ts.createNodeArray([lhs])), 1054 | ts.createToken(ts.SyntaxKind.PlusToken), 1055 | ts.createCall(ts.createIdentifier('strlen'), undefined, ts.createNodeArray([rhs])) 1056 | ), 1057 | ts.createToken(ts.SyntaxKind.PlusToken), 1058 | ts.createNumericLiteral('1') 1059 | ) 1060 | ]), 1061 | lhs 1062 | ]), 1063 | rhs 1064 | ]); 1065 | } 1066 | return ts.createBinary(lhs, node.operatorToken, rhs); 1067 | } 1068 | return ts.createBinary(lhs, node.operatorToken, rhs); 1069 | } 1070 | } 1071 | 1072 | export default class Prelude { 1073 | public readonly main: string; 1074 | 1075 | constructor(main: string) { 1076 | this.main = main; 1077 | } 1078 | 1079 | public process(): string { 1080 | debug(`Starts ${this.main}`); 1081 | const hash = crypto 1082 | .createHash('md5') 1083 | .update(fs.readFileSync(this.main)) 1084 | .digest() 1085 | .toString('hex'); 1086 | const tempdir = path.join(shell.tempdir(), 'minits', hash); 1087 | debug(`Create TmpDir ${tempdir}`); 1088 | fs.mkdirSync(tempdir, { recursive: true }); 1089 | 1090 | const option: Option = { entryfn: this.main, tempdir }; 1091 | option.entryfn = new PreludeLayer0(option).process(); 1092 | option.entryfn = new PreludeLayer1(option).process(); 1093 | option.entryfn = new PreludeLayer2(option).process(); 1094 | option.entryfn = new PreludeLayer3(option).process(); 1095 | option.entryfn = new PreludeLayer4(option).process(); 1096 | option.entryfn = new PreludeLayer5(option).process(); 1097 | const out = path.join(tempdir, 'output.ts'); 1098 | debug(`Rename ${option.entryfn} => ${out}`); 1099 | fs.copyFileSync(option.entryfn, out); 1100 | return out; 1101 | } 1102 | } 1103 | -------------------------------------------------------------------------------- /src/stdlib/index.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | 3 | import LLVMCodeGen from '../codegen'; 4 | 5 | export class Stdlib { 6 | private cgen: LLVMCodeGen; 7 | 8 | constructor(cgen: LLVMCodeGen) { 9 | this.cgen = cgen; 10 | } 11 | 12 | public atoi(args: llvm.Value[]): llvm.Value { 13 | const name = 'atoi'; 14 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context)]; 15 | const varg = false; 16 | const o = llvm.Type.getInt64Ty(this.cgen.context); 17 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 18 | return this.cgen.builder.createCall(func, args); 19 | } 20 | 21 | // Warning: implicit declaration of function 'itoa' is invalid in C99 22 | public itoa(args: llvm.Value[]): llvm.Value { 23 | const name = 'itoa'; 24 | const i: llvm.Type[] = []; 25 | const varg = true; 26 | const o = llvm.Type.getInt64Ty(this.cgen.context); 27 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 28 | const dest = llvm.FunctionType.get( 29 | llvm.Type.getInt64Ty(this.cgen.context), 30 | [ 31 | llvm.Type.getInt64Ty(this.cgen.context), 32 | llvm.Type.getInt8PtrTy(this.cgen.context), 33 | llvm.Type.getInt64Ty(this.cgen.context) 34 | ], 35 | true 36 | ); 37 | return this.cgen.builder.createCall(this.cgen.builder.createBitCast(func, dest.getPointerTo()), args); 38 | } 39 | 40 | public malloc(args: llvm.Value[]): llvm.Value { 41 | const name = 'malloc'; 42 | const i = [llvm.Type.getInt64Ty(this.cgen.context)]; 43 | const varg = false; 44 | const o = llvm.Type.getInt8PtrTy(this.cgen.context); 45 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 46 | return this.cgen.builder.createCall(func, args); 47 | } 48 | 49 | public printf(args: llvm.Value[]): llvm.Value { 50 | const name = 'printf'; 51 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context)]; 52 | const varg = true; 53 | const o = llvm.Type.getInt64Ty(this.cgen.context); 54 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 55 | return this.cgen.builder.createCall(func, args); 56 | } 57 | 58 | public sprintf(args: llvm.Value[]): llvm.Value { 59 | const name = 'sprintf'; 60 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context), llvm.Type.getInt8PtrTy(this.cgen.context)]; 61 | const varg = true; 62 | const o = llvm.Type.getInt64Ty(this.cgen.context); 63 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 64 | return this.cgen.builder.createCall(func, args); 65 | } 66 | 67 | public strcat(args: llvm.Value[]): llvm.Value { 68 | const name = 'strcat'; 69 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context), llvm.Type.getInt8PtrTy(this.cgen.context)]; 70 | const varg = false; 71 | const o = llvm.Type.getInt8PtrTy(this.cgen.context); 72 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 73 | return this.cgen.builder.createCall(func, args); 74 | } 75 | 76 | public strcmp(args: llvm.Value[]): llvm.Value { 77 | const name = 'strcmp'; 78 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context), llvm.Type.getInt8PtrTy(this.cgen.context)]; 79 | const varg = false; 80 | const o = llvm.Type.getInt64Ty(this.cgen.context); 81 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 82 | return this.cgen.builder.createCall(func, args); 83 | } 84 | 85 | public strcpy(args: llvm.Value[]): llvm.Value { 86 | const name = 'strcpy'; 87 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context), llvm.Type.getInt8PtrTy(this.cgen.context)]; 88 | const varg = false; 89 | const o = llvm.Type.getInt8PtrTy(this.cgen.context); 90 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 91 | return this.cgen.builder.createCall(func, args); 92 | } 93 | 94 | public strlen(args: llvm.Value[]): llvm.Value { 95 | const name = 'strlen'; 96 | const i = [llvm.Type.getInt8PtrTy(this.cgen.context)]; 97 | const varg = false; 98 | const o = llvm.Type.getInt64Ty(this.cgen.context); 99 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 100 | return this.cgen.builder.createCall(func, args); 101 | } 102 | 103 | public syscall(args: llvm.Value[]): llvm.Value { 104 | const name = 'syscall'; 105 | const i = [ 106 | llvm.Type.getInt64Ty(this.cgen.context), 107 | llvm.Type.getInt64Ty(this.cgen.context), 108 | llvm.Type.getInt64Ty(this.cgen.context), 109 | llvm.Type.getInt64Ty(this.cgen.context), 110 | llvm.Type.getInt64Ty(this.cgen.context), 111 | llvm.Type.getInt64Ty(this.cgen.context), 112 | llvm.Type.getInt64Ty(this.cgen.context) 113 | ]; 114 | const varg = false; 115 | const o = llvm.Type.getInt64Ty(this.cgen.context); 116 | const func = this.cgen.module.getOrInsertFunction(name, llvm.FunctionType.get(o, i, varg)); 117 | const argl = args.map(item => { 118 | if (item.type.isPointerTy()) { 119 | return this.cgen.builder.createPtrToInt(item, llvm.Type.getInt64Ty(this.cgen.context)); 120 | } 121 | return item; 122 | }); 123 | return this.cgen.builder.createCall(func, argl); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/stdlib/syscall.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'syscall.c' 2 | source_filename = "syscall.c" 3 | target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n64-S128" 4 | target triple = "riscv64-unknown-unknown-elf" 5 | 6 | ; Function Attrs: noinline nounwind optnone 7 | define dso_local i64 @syscall(i64 %n, i64 %_a0, i64 %_a1, i64 %_a2, i64 %_a3, i64 %_a4, i64 %_a5) #0 { 8 | entry: 9 | %n.addr = alloca i64, align 8 10 | %_a0.addr = alloca i64, align 8 11 | %_a1.addr = alloca i64, align 8 12 | %_a2.addr = alloca i64, align 8 13 | %_a3.addr = alloca i64, align 8 14 | %_a4.addr = alloca i64, align 8 15 | %_a5.addr = alloca i64, align 8 16 | %a0 = alloca i64, align 8 17 | %a1 = alloca i64, align 8 18 | %a2 = alloca i64, align 8 19 | %a3 = alloca i64, align 8 20 | %a4 = alloca i64, align 8 21 | %a5 = alloca i64, align 8 22 | %syscall_id = alloca i64, align 8 23 | store i64 %n, i64* %n.addr, align 8 24 | store i64 %_a0, i64* %_a0.addr, align 8 25 | store i64 %_a1, i64* %_a1.addr, align 8 26 | store i64 %_a2, i64* %_a2.addr, align 8 27 | store i64 %_a3, i64* %_a3.addr, align 8 28 | store i64 %_a4, i64* %_a4.addr, align 8 29 | store i64 %_a5, i64* %_a5.addr, align 8 30 | %0 = load i64, i64* %_a0.addr, align 8 31 | store i64 %0, i64* %a0, align 8 32 | %1 = load i64, i64* %_a1.addr, align 8 33 | store i64 %1, i64* %a1, align 8 34 | %2 = load i64, i64* %_a2.addr, align 8 35 | store i64 %2, i64* %a2, align 8 36 | %3 = load i64, i64* %_a3.addr, align 8 37 | store i64 %3, i64* %a3, align 8 38 | %4 = load i64, i64* %_a4.addr, align 8 39 | store i64 %4, i64* %a4, align 8 40 | %5 = load i64, i64* %_a5.addr, align 8 41 | store i64 %5, i64* %a5, align 8 42 | %6 = load i64, i64* %n.addr, align 8 43 | store i64 %6, i64* %syscall_id, align 8 44 | %7 = load i64, i64* %a0, align 8 45 | %8 = load i64, i64* %a1, align 8 46 | %9 = load i64, i64* %a2, align 8 47 | %10 = load i64, i64* %a3, align 8 48 | %11 = load i64, i64* %a4, align 8 49 | %12 = load i64, i64* %a5, align 8 50 | %13 = load i64, i64* %syscall_id, align 8 51 | %14 = call i64 asm sideeffect "scall", "={x10},{x11},{x12},{x13},{x14},{x15},{x17},0"(i64 %8, i64 %9, i64 %10, i64 %11, i64 %12, i64 %13, i64 %7) #1, !srcloc !2 52 | store i64 %14, i64* %a0, align 8 53 | %15 = load i64, i64* %a0, align 8 54 | ret i64 %15 55 | } 56 | 57 | attributes #0 = { noinline nounwind optnone "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+relax" "unsafe-fp-math"="false" "use-soft-float"="false" } 58 | attributes #1 = { nounwind } 59 | 60 | !llvm.module.flags = !{!0} 61 | !llvm.ident = !{!1} 62 | 63 | !0 = !{i32 1, !"wchar_size", i32 4} 64 | !1 = !{!"clang version 10.0.0 (https://github.com/llvm/llvm-project.git 3d68adebc579720a3914d50e77a413773be34f16)"} 65 | !2 = !{i32 375} 66 | -------------------------------------------------------------------------------- /src/symtab.ts: -------------------------------------------------------------------------------- 1 | import llvm from 'llvm-node'; 2 | 3 | interface Node { 4 | data: llvm.Value | Map; 5 | } 6 | 7 | class Leaf implements Node { 8 | public readonly data: llvm.Value; 9 | public readonly ptrs: number; 10 | 11 | constructor(data: llvm.Value, ptrs: number) { 12 | this.data = data; 13 | this.ptrs = ptrs; 14 | } 15 | } 16 | 17 | class Meso implements Node { 18 | public parent: Meso | undefined; 19 | public name: string; 20 | public deep: number; 21 | public data: Map; 22 | 23 | constructor(parent: Meso | undefined, name: string) { 24 | this.parent = parent; 25 | this.name = name; 26 | this.deep = this.parent ? this.parent.deep + 1 : 0; 27 | this.data = new Map(); 28 | } 29 | } 30 | 31 | function isLeaf(node: Node): node is Leaf { 32 | return node instanceof Leaf; 33 | } 34 | 35 | function isMeso(node: Node): node is Meso { 36 | return node instanceof Meso; 37 | } 38 | 39 | class Symtab { 40 | public data: Meso; 41 | 42 | constructor() { 43 | this.data = new Meso(undefined, ''); 44 | } 45 | 46 | public deep(): number { 47 | return this.data.deep; 48 | } 49 | 50 | public into(name: string): Meso { 51 | const n = new Meso(this.data, name); 52 | if (name) { 53 | this.data.data.set(name, n); 54 | } 55 | this.data = n; 56 | return n; 57 | } 58 | 59 | public exit(): void { 60 | this.data = this.data.parent!; 61 | } 62 | 63 | public name(): string { 64 | if (!this.data.parent) { 65 | return ''; 66 | } 67 | 68 | const list = []; 69 | let n: Meso | undefined = this.data; 70 | for (;;) { 71 | if (!n) { 72 | break; 73 | } 74 | if (n.name) { 75 | list.push(n.name); 76 | } 77 | n = n.parent; 78 | } 79 | return list.reverse().join('.') + '.'; 80 | } 81 | 82 | public with(name: string, body: () => void): void { 83 | this.into(name); 84 | body(); 85 | this.exit(); 86 | } 87 | 88 | public set(key: string, node: Node): void { 89 | this.data.data.set(key, node); 90 | } 91 | 92 | public get(key: string): Node { 93 | const node = this.tryGet(key); 94 | if (node) { 95 | return node; 96 | } 97 | throw new Error(`Symbol ${key} not found`); 98 | } 99 | 100 | public tryGet(key: string): Node | null { 101 | let n: Meso = this.data; 102 | 103 | while (true) { 104 | const v = n.data.get(key); 105 | if (v) { 106 | return v; 107 | } 108 | 109 | if (n.parent) { 110 | n = n.parent; 111 | } else { 112 | return null; 113 | } 114 | } 115 | } 116 | } 117 | 118 | export { Node, Leaf, Meso, isLeaf, isMeso, Symtab }; 119 | -------------------------------------------------------------------------------- /src/tests/application.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcFactorial = ` 4 | function factorial(n: number): number { 5 | let s: number = 1; 6 | for (let i: number = 1; i <= n; i++) { 7 | s = s * i; 8 | } 9 | return s; 10 | } 11 | 12 | function main(): number { 13 | return factorial(5); // 120 14 | } 15 | `; 16 | 17 | const srcFibonacci = ` 18 | function fibo(n: number): number { 19 | if (n < 2) { 20 | return n; 21 | } 22 | return fibo(n - 1) + fibo(n - 2); 23 | } 24 | 25 | function main(): number { 26 | return fibo(10); 27 | } 28 | `; 29 | 30 | runTest('test application: factorial', srcFactorial); 31 | runTest('test application: fibonacci', srcFibonacci); 32 | -------------------------------------------------------------------------------- /src/tests/array-literal-expression.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcSizedArray = ` 4 | function echo(a: number): number { 5 | let array = [a, 0, 0, 1]; 6 | return array[0]; 7 | } 8 | 9 | function main(): number { 10 | let a: number[] = [1, 2, 3]; 11 | // Get 12 | if (a[2] !== 3) { 13 | return 1; 14 | } 15 | let i = 2; 16 | if (a[i] !== 3) { 17 | return 1; 18 | } 19 | let s = 0; 20 | for (let j = 0; j < 3; j++) { 21 | s += a[j]; 22 | } 23 | if (s !== 6) { 24 | return 1; 25 | } 26 | let b = [1, 2, echo(3)]; 27 | if (b[2] != 3) { 28 | return 1; 29 | } 30 | // Set 31 | s = 0; 32 | a[0] = 4; 33 | a[1] = 5; 34 | a[2] = 6; 35 | for (let j = 0; j < 3; j++) { 36 | s += a[j]; 37 | } 38 | if (s !== 15) { 39 | return 1; 40 | } 41 | return 0; 42 | } 43 | `; 44 | 45 | const srcString = ` 46 | let globalarr: string[] = ["10", "20"]; 47 | 48 | function main(): number { 49 | let localarr: string[] = ["10", "20"]; 50 | if (globalarr[0] !== localarr[0]) { 51 | return 1; 52 | } 53 | if (globalarr[1] !== localarr[1]) { 54 | return 1; 55 | } 56 | return 0; 57 | } 58 | `; 59 | 60 | const srcAsArgument = ` 61 | function echo(n: number[]): number { 62 | return n[0] 63 | } 64 | 65 | function main(): number { 66 | let l = [0x10, 0x11, 0x12]; 67 | return echo(l); 68 | } 69 | `; 70 | 71 | runTest('test array: sized', srcSizedArray); 72 | runTest('test array: string', srcString); 73 | runTest('test array: as argument', srcAsArgument); 74 | -------------------------------------------------------------------------------- /src/tests/binary-expression.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcOperatorArithmeticOperations = ` 4 | function main(): number { 5 | let n: number = 0; 6 | n = n + 10; // 10 7 | n = n - 2; // 8 8 | n = n * 2; // 16 9 | n = n / 4; // 4 10 | n = n * 10; // 40 11 | n = n % 17; // 6 12 | return n; 13 | } 14 | `; 15 | 16 | const srcOperatorBits = ` 17 | const Test = { 18 | a: 1, 19 | d: 12 20 | }; 21 | 22 | function main(): number { 23 | const Test = { 24 | a: 1, 25 | c: 10 26 | }; 27 | 28 | return Test.c; 29 | } 30 | `; 31 | 32 | const srcOperatorCompare = ` 33 | const Test = { 34 | a: 1 35 | }; 36 | 37 | function main(): number { 38 | const Test = { 39 | a: 1, 40 | c: 'str' 41 | }; 42 | 43 | if (Test.c === 'str') { 44 | return 0; 45 | } 46 | 47 | return 1; 48 | } 49 | `; 50 | 51 | const srcOperatorCompoundAssignmentConst = ` 52 | const Test = { 53 | a: 1 54 | }; 55 | 56 | function main(): number { 57 | const Test = { 58 | a: 1, 59 | c: 'str' 60 | }; 61 | 62 | if (Test.c === 'str') { 63 | return 0; 64 | } 65 | 66 | return 1; 67 | } 68 | `; 69 | 70 | const srcoOperatorCompoundAssignmentVar = ` 71 | const Test = { 72 | a: 1 73 | }; 74 | 75 | function main(): number { 76 | const Test = { 77 | a: 1, 78 | c: 'str' 79 | }; 80 | 81 | if (Test.c === 'str') { 82 | return 0; 83 | } 84 | 85 | return 1; 86 | } 87 | `; 88 | 89 | const srcOperatorNumber = ` 90 | const Test = { 91 | a: 1 92 | }; 93 | 94 | function main(): number { 95 | const Test = { 96 | a: 1, 97 | c: 'str' 98 | }; 99 | 100 | if (Test.c === 'str') { 101 | return 0; 102 | } 103 | 104 | return 1; 105 | } 106 | `; 107 | 108 | const srcOperatorParen = ` 109 | const Test = { 110 | a: 1 111 | }; 112 | 113 | function main(): number { 114 | const Test = { 115 | a: 1, 116 | c: 'str' 117 | }; 118 | 119 | if (Test.c === 'str') { 120 | return 0; 121 | } 122 | 123 | return 1; 124 | } 125 | `; 126 | 127 | const srcOperatorPostfixUnary = ` 128 | const Test = { 129 | a: 1 130 | }; 131 | 132 | function main(): number { 133 | const Test = { 134 | a: 1, 135 | c: 'str' 136 | }; 137 | 138 | if (Test.c === 'str') { 139 | return 0; 140 | } 141 | 142 | return 1; 143 | } 144 | `; 145 | 146 | runTest('test binary: arithmetic operations', srcOperatorArithmeticOperations); 147 | runTest('test binary: bits', srcOperatorBits); 148 | runTest('test binary: compare', srcOperatorCompare); 149 | runTest('test binary: compound assignment const', srcOperatorCompoundAssignmentConst); 150 | runTest('test binary: compound assignment var', srcoOperatorCompoundAssignmentVar); 151 | runTest('test binary: number', srcOperatorNumber); 152 | runTest('test binary: paren', srcOperatorParen); 153 | runTest('test binary: postfix unary', srcOperatorPostfixUnary); 154 | -------------------------------------------------------------------------------- /src/tests/binary.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import crypto from 'crypto'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import shell from 'shelljs'; 6 | 7 | import LLVMCodeGen from '../codegen'; 8 | import Prelude from '../prelude'; 9 | 10 | const srcArgsFromShell = ` 11 | function main(argc: number, argv: string[]): number { 12 | console.log('%s\\n', argv[1]); 13 | console.log('%s\\n', argv[2]); 14 | console.log('%d\\n', argc); 15 | return 0; 16 | } 17 | `; 18 | 19 | test('test args', async t => { 20 | const hash = crypto 21 | .createHash('md5') 22 | .update(srcArgsFromShell) 23 | .digest() 24 | .toString('hex'); 25 | 26 | fs.mkdirSync(path.join(shell.tempdir(), 'minits'), { recursive: true }); 27 | const name = path.join(shell.tempdir(), 'minits', hash + '.ts'); 28 | fs.writeFileSync(name, srcArgsFromShell); 29 | 30 | const prelude = new Prelude(name); 31 | const outputs = prelude.process(); 32 | const codegen = new LLVMCodeGen(outputs); 33 | codegen.genSourceFile(outputs); 34 | 35 | const full = path.join(path.dirname(outputs), 'output'); 36 | fs.writeFileSync(`${full}.ll`, codegen.genText()); 37 | shell.exec(`llvm-as ${full}.ll -o ${full}.bc`, { async: false }); 38 | shell.exec(`llc -filetype=obj ${full}.bc -o ${full}.o`, { async: false }); 39 | shell.exec(`gcc ${full}.o -o ${full}`, { async: false }); 40 | const ret = shell.exec(`${full} foo bar`, { async: false }); 41 | t.log(ret.stdout); 42 | if (ret.code === 0) { 43 | t.pass(); 44 | } else { 45 | t.fail(); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /src/tests/class-declaration.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcClassInit = ` 4 | class Employee { 5 | empCode: number; 6 | empName: string; 7 | } 8 | 9 | function main(): number { 10 | const a: Employee = { 11 | empCode: 42, 12 | empName: "dddd", 13 | }; 14 | return a.empCode; 15 | } 16 | `; 17 | 18 | const srcClassAsFunctionArgument = ` 19 | class Employee { 20 | empCode: number; 21 | empName: string; 22 | } 23 | 24 | function getEmpCode(emp: Employee): number { 25 | return emp.empCode 26 | } 27 | 28 | function main(): number { 29 | const a: Employee = { 30 | empCode: 42, 31 | empName: "dddd", 32 | }; 33 | return getEmpCode(a); 34 | } 35 | `; 36 | 37 | const srcClassInClass = ` 38 | class Employee { 39 | empCode: number; 40 | empName: string; 41 | } 42 | 43 | class Man { 44 | employee: Employee; 45 | name: string; 46 | } 47 | 48 | function main(): number { 49 | const e: Employee = { 50 | empCode: 42, 51 | empName: "dddd", 52 | }; 53 | const m: Man = { 54 | employee: e, 55 | name: "jack", 56 | } 57 | return m.employee.empCode; 58 | } 59 | `; 60 | 61 | const srcClassConstructor = ` 62 | class Employee { 63 | empCode: number; 64 | empName: string; 65 | 66 | constructor(empCode: number, empName: string) { 67 | this.empCode = empCode; 68 | this.empName = empName; 69 | } 70 | } 71 | 72 | function main(): number { 73 | const a = new Employee(42, "Mohanson"); 74 | return a.empCode; 75 | } 76 | `; 77 | 78 | const srcClassMethod = ` 79 | class Son { 80 | age: number 81 | 82 | constructor(age: number) { 83 | this.age = age; 84 | } 85 | 86 | get(): number { 87 | return this.age 88 | } 89 | } 90 | 91 | class Father { 92 | age: number; 93 | son: Son; 94 | 95 | constructor(age: number, son: Son) { 96 | this.age = age; 97 | this.son = son; 98 | } 99 | 100 | get(): number { 101 | return this.age 102 | } 103 | 104 | all(): number { 105 | return this.get() + this.son.get() 106 | } 107 | } 108 | 109 | function main(): number { 110 | const son = new Son(18); 111 | const father = new Father(38, son); 112 | return father.all(); 113 | } 114 | `; 115 | 116 | runTest('test class: init', srcClassInit); 117 | runTest('test class: as function argument', srcClassAsFunctionArgument); 118 | runTest('test class: class in class', srcClassInClass); 119 | runTest('test class: constructor', srcClassConstructor); 120 | runTest('test class: method', srcClassMethod); 121 | -------------------------------------------------------------------------------- /src/tests/condition-expression.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcNumber = ` 4 | function main(): number { 5 | return 2 > 1? 20 : 10; 6 | } 7 | `; 8 | 9 | const srcString = ` 10 | function main(): number { 11 | let a = 2 > 1? "Hello": "World"; 12 | if (a !== "Hello") { 13 | return 1; 14 | } 15 | return 0; 16 | } 17 | `; 18 | 19 | runTest('test condition: number', srcNumber); 20 | runTest('test condition: string', srcString); 21 | -------------------------------------------------------------------------------- /src/tests/element-access-expression.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcNumber = ` 4 | let globalarr = [1, 2, 3]; 5 | 6 | function main(): number { 7 | let localearr = [4, 5, 6]; 8 | let s: number = 0; 9 | s += globalarr[0]; 10 | s += localearr[0]; 11 | s += [1, 2, 3][0]; 12 | return s; // 6 13 | } 14 | `; 15 | 16 | const srcString = ` 17 | let globalstr = "Hello"; 18 | 19 | function main(): number { 20 | let localestr = "Hello"; 21 | if (globalstr[0] !== "H") { 22 | return 1; 23 | } 24 | if (localestr[0] !== "H") { 25 | return 1; 26 | } 27 | if ("Hello"[0] !== "H") { 28 | return 1; 29 | } 30 | return 0; 31 | } 32 | `; 33 | 34 | runTest('test element: access number', srcNumber); 35 | runTest('test element: access string', srcString); 36 | -------------------------------------------------------------------------------- /src/tests/enum-declaration.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcEnumInitializer = ` 4 | enum Test { 5 | a = 100, 6 | b = 101, 7 | c = 102 8 | } 9 | 10 | function main(): number { 11 | return Test.a; 12 | } 13 | `; 14 | 15 | const srcEnumIota = ` 16 | enum Test { 17 | a = 100, 18 | b, 19 | c = 103 20 | } 21 | 22 | function main(): number { 23 | return Test.b; 24 | } 25 | `; 26 | 27 | const srcEnumReturn = ` 28 | enum Test { 29 | a, 30 | b 31 | } 32 | 33 | function main(): Test { 34 | return Test.a; 35 | } 36 | `; 37 | 38 | const srcEnumVar = ` 39 | enum Test { 40 | a, 41 | b 42 | } 43 | 44 | function runTest(t: Test): number { 45 | return t; 46 | } 47 | 48 | function main(): number { 49 | return runTest(Test.b); 50 | } 51 | `; 52 | 53 | const srcEnumPlus = ` 54 | enum Test { 55 | a = 100, 56 | b = 101 57 | } 58 | 59 | function runTest(n: number): number { 60 | return n; 61 | } 62 | 63 | function main(): number { 64 | runTest(1 + Test.a); 65 | return runTest(Test.b + 1); 66 | } 67 | `; 68 | 69 | runTest('test enum: initializer', srcEnumInitializer); 70 | runTest('test enum: iota', srcEnumIota); 71 | runTest('test enum: as return value', srcEnumReturn); 72 | runTest('test enum: var', srcEnumVar); 73 | runTest('test enum: plus', srcEnumPlus); 74 | -------------------------------------------------------------------------------- /src/tests/for-of-statement.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcForOfArray = ` 4 | let globalarr = [1, 2, 3]; 5 | 6 | function main(): number { 7 | let localearr = [4, 5, 6]; 8 | let emptyarr: number[] = []; 9 | let s: number = 0; 10 | for (let v of globalarr) { 11 | s += v; 12 | } 13 | for (let v of localearr) { 14 | s += v; 15 | } 16 | for (let v of [1, 2, 3]) { 17 | s += v; 18 | } 19 | for (let v of emptyarr) { 20 | s += v; 21 | } 22 | return s; // 27 23 | } 24 | `; 25 | 26 | const srcForOfString = ` 27 | function main(): number { 28 | let a = "Hello"; 29 | let s = ""; 30 | for (let i of a) { 31 | s = s + i; 32 | } 33 | for (let i of "World") { 34 | s = s + i; 35 | } 36 | if (s !== "HelloWorld") { 37 | return 1; 38 | } 39 | return 0; 40 | } 41 | `; 42 | 43 | runTest('test for of: array', srcForOfArray); 44 | runTest('test for of: string', srcForOfString); 45 | -------------------------------------------------------------------------------- /src/tests/function-declaration.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcFunctionCall = ` 4 | function echo(n: number): number { 5 | return n; 6 | } 7 | 8 | function main(): number { 9 | let a = echo(42); 10 | let n = 42; 11 | let b = echo(n); 12 | return a + b; // 84 13 | } 14 | `; 15 | 16 | const srcFunctionReturnZero = ` 17 | function main(): number { 18 | return 0; 19 | } 20 | `; 21 | 22 | const srcFunctionReturnOne = ` 23 | function main(): number { 24 | return 1; 25 | } 26 | `; 27 | 28 | const srcFunctionIgnoreReturn = ` 29 | function echo() { 30 | console.log("Hello World!"); 31 | } 32 | 33 | function main(): number { 34 | echo(); 35 | return 0; 36 | } 37 | `; 38 | 39 | const srcFunctionOrder = ` 40 | function main(): number { 41 | funcA(); 42 | return 0; 43 | } 44 | 45 | function funcD() { 46 | funcC(); 47 | } 48 | 49 | function funcC() {} 50 | 51 | function funcB() { 52 | funcD(); 53 | } 54 | 55 | function funcA() { 56 | funcD(); 57 | } 58 | `; 59 | 60 | runTest('test function: call', srcFunctionCall); 61 | runTest('test function: return 0', srcFunctionReturnZero); 62 | runTest('test function: return 1', srcFunctionReturnOne); 63 | runTest('test function: ignore return', srcFunctionIgnoreReturn); 64 | runTest('test function: order', srcFunctionOrder); 65 | -------------------------------------------------------------------------------- /src/tests/if-statement.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcIf = ` 4 | function main(): number { 5 | let a = 2; 6 | let r = 0; 7 | if (a === 0) { 8 | r = 0; 9 | } else if (a === 1) { 10 | r = 1; 11 | } else { 12 | r = 2; 13 | } 14 | return r; 15 | } 16 | `; 17 | 18 | const srcIfContainsFor = ` 19 | function main(): number { 20 | let s = 0; 21 | if (true) { 22 | for (let j = 0; j < 10; j++) { 23 | s += 1; 24 | } 25 | } 26 | return s; 27 | } 28 | `; 29 | 30 | runTest('test if: smoke', srcIf); 31 | runTest('test if: contains for', srcIfContainsFor); 32 | -------------------------------------------------------------------------------- /src/tests/int8array.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcGetAndSet = ` 4 | function main(): number { 5 | let a = new Int8Array(10); 6 | a[0] = 278; 7 | return a[0]; 8 | } 9 | `; 10 | 11 | const srcNewWithNumberArray = ` 12 | function main(): number { 13 | let a = new Int8Array([1, 2000, 3]); 14 | return a[1]; 15 | } 16 | `; 17 | 18 | const srcReturnByFunction = ` 19 | function func(): Int8Array { 20 | return new Int8Array([0, 1, 2, 3]); 21 | } 22 | 23 | function main(): number { 24 | const r = func(); 25 | return r[3]; 26 | } 27 | `; 28 | 29 | runTest('test buffer: get and set', srcGetAndSet); 30 | runTest('test buffer: new with number array', srcNewWithNumberArray); 31 | runTest('test buffer: return by func', srcReturnByFunction); 32 | -------------------------------------------------------------------------------- /src/tests/object-literal-expression.spec.ts.backup: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcObjectCallMut = ` 4 | function mut(obj: { num: number; str: string }): void { 5 | obj.num = 100; 6 | obj.str = 'hello world'; 7 | return; 8 | } 9 | 10 | function main(): number { 11 | let testObj = { 12 | num: 10, 13 | str: '123' 14 | }; 15 | 16 | mut(testObj); 17 | if (testObj.str !== 'hello world') { 18 | return 1; 19 | } 20 | return testObj.num; 21 | } 22 | `; 23 | 24 | const srcObjectReturnNumber = ` 25 | const Test = { 26 | a: 1, 27 | d: 12 28 | }; 29 | 30 | function main(): number { 31 | const Test = { 32 | a: 1, 33 | c: 10 34 | }; 35 | 36 | return Test.c; 37 | } 38 | `; 39 | 40 | const srcObjectReturnString = ` 41 | const Test = { 42 | a: 1 43 | }; 44 | 45 | function main(): number { 46 | const Test = { 47 | a: 1, 48 | c: 'str' 49 | }; 50 | 51 | if (Test.c === 'str') { 52 | return 0; 53 | } 54 | 55 | return 1; 56 | } 57 | `; 58 | 59 | const srcInitWithVariable = ` 60 | function main(): number { 61 | let n = 10; 62 | let a = {a: n}; 63 | return a.a 64 | } 65 | `; 66 | 67 | const srcObjectDuckType = ` 68 | function echo(num: { num: number }): number { 69 | return num.num; 70 | } 71 | 72 | function main(): number { 73 | const val0 = echo({ num: 10 ); 74 | if (val0 !== 10) { 75 | return 1; 76 | } 77 | 78 | const obj1 = { num: 11, str: '12' }; 79 | const val1 = echo(obj1); 80 | if (val1 !== 11) { 81 | return 1; 82 | } 83 | 84 | const obj2 = { num: 12, str: '12', b: true }; 85 | const val2 = echo(obj2); 86 | if (val2 !== 12) { 87 | return 1; 88 | } 89 | 90 | return 0; 91 | } 92 | `; 93 | 94 | const srcObjectDuckTypeMut = ` 95 | function echo(num: { num: number }): number { 96 | num.num = 20; 97 | return num.num; 98 | } 99 | 100 | function main(): number { 101 | const obj1 = { num: 11, str: '12' }; 102 | echo(obj1); 103 | return obj1.num; 104 | } 105 | `; 106 | 107 | const srcObjectGlobal = ` 108 | let a = { name: 10 }; 109 | 110 | function main(): number { 111 | return a.name; 112 | } 113 | `; 114 | 115 | const srcNest = ` 116 | function echo(o: { a: { b: number } }): number { 117 | return o.a.b 118 | } 119 | 120 | let d = { b: 20 }; 121 | let c = { a: d }; 122 | 123 | function main(): number { 124 | let b = { b: 10 }; 125 | let a = { a: b }; 126 | return echo(a) + echo(c); 127 | } 128 | `; 129 | 130 | runTest('test object: call mut', srcObjectCallMut); 131 | runTest('test object: return number', srcObjectReturnNumber); 132 | runTest('test object: return string', srcObjectReturnString); 133 | runTest('test object: init with variable', srcInitWithVariable); 134 | runTest('test object: duck type', srcObjectDuckType); 135 | runTest('test object: duck type mut', srcObjectDuckTypeMut); 136 | runTest('test object: global', srcObjectGlobal); 137 | runTest('test object: nest', srcNest); 138 | -------------------------------------------------------------------------------- /src/tests/statement.sepc.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcStatementBreak = ` 4 | function main(): number { 5 | let s = 0; 6 | 7 | for (let i = 0; i < 10; i++) { 8 | s += i; 9 | if (i == 5) { 10 | break; 11 | } 12 | } // s == 15 13 | 14 | while (true) { 15 | s++; 16 | if (s == 20) { 17 | break; 18 | } 19 | } // s == 20 20 | 21 | 22 | return s; 23 | } 24 | `; 25 | 26 | const srcStatementContinue = ` 27 | function main(): number { 28 | let s = 0; 29 | 30 | for (let i = 0; i < 10; i++) { 31 | if (i === 5) { 32 | continue 33 | } 34 | s++; 35 | } // s === 9 36 | 37 | let i = 0; 38 | while (i < 10) { 39 | i++; 40 | if (i === 5) { 41 | continue 42 | } 43 | s++; 44 | } // s === 18 45 | 46 | let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] 47 | for (let v of a) { 48 | if (v === 5) { 49 | continue 50 | } 51 | s++; 52 | } // s === 27 53 | 54 | return s; 55 | } 56 | `; 57 | 58 | const srcStatementDo = ` 59 | function main(): number { 60 | let s = 0; 61 | let i = 0; 62 | do { 63 | s += i; 64 | i += 1; 65 | } while (i <= 10) 66 | return s; // 55 67 | } 68 | `; 69 | 70 | const srcStatementForLoop = ` 71 | function main(): number { 72 | let s: number = 0; 73 | 74 | for (let i: number = 0; i < 5; i++) { 75 | s = s + i; 76 | } // s === 10 77 | 78 | let j: number = 0; 79 | for (; j < 5; j++) { 80 | s = s + j; 81 | } // s === 20 82 | 83 | let k: number = 0; 84 | for (; k < 5;) { 85 | s = s + k; 86 | k++; 87 | } // s === 30 88 | 89 | return s; 90 | } 91 | `; 92 | 93 | const srcStatementLogicAndOr = ` 94 | function main(): number { 95 | let a: boolean = false; 96 | 97 | a = false && false; 98 | if (a !== false) { 99 | return 1; 100 | } 101 | a = false && true; 102 | if (a !== false) { 103 | return 2; 104 | } 105 | a = true && false; 106 | if (a !== false) { 107 | return 3; 108 | } 109 | a = true && true; 110 | if (a !== true) { 111 | return 4; 112 | } 113 | 114 | a = false || false; 115 | if (a !== false) { 116 | return 5; 117 | } 118 | a = false || true; 119 | if (a !== true) { 120 | return 6; 121 | } 122 | a = true || false; 123 | if (a !== true) { 124 | return 7; 125 | } 126 | a = true || true; 127 | if (a !== true) { 128 | return 8; 129 | } 130 | 131 | return 0; 132 | } 133 | `; 134 | 135 | const srcStatementWhile = ` 136 | function main(): number { 137 | let s = 0; 138 | while (s != 10) { 139 | s++; 140 | } 141 | return s; // 20 142 | } 143 | `; 144 | 145 | runTest('test statement break', srcStatementBreak); 146 | runTest('test statement continue', srcStatementContinue); 147 | runTest('test statement do', srcStatementDo); 148 | runTest('test statement for loop', srcStatementForLoop); 149 | runTest('test statement logic and or', srcStatementLogicAndOr); 150 | runTest('test statement while', srcStatementWhile); 151 | -------------------------------------------------------------------------------- /src/tests/stdlib.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcConsoleLog = ` 4 | let globalstr = "globalstr"; 5 | 6 | function main(): number { 7 | let localstr = "localstr"; 8 | console.log(globalstr); 9 | console.log(localstr); 10 | console.log("%s", globalstr); 11 | console.log("%s", localstr); 12 | console.log("%d", 42); 13 | return 0; 14 | } 15 | `; 16 | 17 | const srcAtoI = ` 18 | function main(): number { 19 | const s = "100"; 20 | let d: number = parseInt(s); 21 | d += 5; // 105 22 | return d; 23 | } 24 | `; 25 | 26 | runTest('test stdlib: console.log', srcConsoleLog); 27 | runTest('test stdlib: atoi', srcAtoI); 28 | -------------------------------------------------------------------------------- /src/tests/string-literal.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcStringElem = ` 4 | function main(): number { 5 | let s = "Hello"; 6 | if (s[0] !== "H") { 7 | return 1; 8 | } 9 | if (s[1] !== "e") { 10 | return 1; 11 | } 12 | if (s[4] !== "o") { 13 | return 1; 14 | } 15 | return 0; 16 | } 17 | `; 18 | 19 | const srcStringLength = ` 20 | function main(): number { 21 | let s = "Hello"; 22 | return s.length 23 | } 24 | `; 25 | 26 | const srcStringConcat = ` 27 | function main(): number { 28 | let a = "Hello"; 29 | let b = "World"; 30 | let c = a + " " + b; 31 | c += "!"; 32 | return c.length 33 | } 34 | `; 35 | 36 | runTest('test string: elem', srcStringElem); 37 | runTest('test string: length', srcStringLength); 38 | runTest('test string: concat', srcStringConcat); 39 | -------------------------------------------------------------------------------- /src/tests/switch-statement.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcSwitchCaseDefault = ` 4 | function main(): number { 5 | let a = 2; 6 | let r = 0; 7 | 8 | switch (a) { 9 | case 0: 10 | r = 0; 11 | break; 12 | case 1: 13 | r = 1; 14 | break; 15 | case 2: 16 | r = 2; 17 | break; 18 | default: 19 | r = 3; 20 | break; 21 | } 22 | 23 | return r; 24 | } 25 | `; 26 | 27 | const srcSwitchString = ` 28 | function main(): number { 29 | let a = "B"; 30 | let r = 0; 31 | 32 | switch (a) { 33 | case "A": 34 | r = 0; 35 | break; 36 | case "B": 37 | r = 1; 38 | break; 39 | case "C": 40 | r = 2; 41 | break; 42 | default: 43 | r = 3; 44 | break; 45 | } 46 | 47 | return r; 48 | } 49 | `; 50 | 51 | const srcNoDefault = ` 52 | function main(): number { 53 | let a = 2; 54 | let r = 0; 55 | 56 | switch (a) { 57 | case 0: 58 | r = 0; 59 | break; 60 | case 1: 61 | r = 1; 62 | break; 63 | case 2: 64 | r = 2; 65 | break; 66 | } 67 | 68 | return r; 69 | } 70 | `; 71 | 72 | const srcNoCase = ` 73 | function main(): number { 74 | let a = 2; 75 | switch (a) { 76 | } 77 | return a; 78 | } 79 | `; 80 | 81 | const srcSwitchInSwitch = ` 82 | function main(): number { 83 | let a = 1; 84 | let b = 2; 85 | let r = 0; 86 | 87 | switch (a) { 88 | case 0: 89 | r = 0; 90 | break 91 | case 1: 92 | switch (b) { 93 | case a: 94 | r = 10; 95 | case 2: 96 | r = 20; 97 | } 98 | break; 99 | } 100 | return r; 101 | } 102 | `; 103 | 104 | runTest('test switch: switch case default', srcSwitchCaseDefault); 105 | runTest('test switch: string', srcSwitchString); 106 | runTest('test switch: no default', srcNoDefault); 107 | runTest('test switch: no case', srcNoCase); 108 | runTest('test switch: switch in switch', srcSwitchInSwitch); 109 | -------------------------------------------------------------------------------- /src/tests/util.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import crypto from 'crypto'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import shell from 'shelljs'; 6 | import ts from 'typescript'; 7 | 8 | import LLVMCodeGen from '../codegen'; 9 | import Prelude from '../prelude'; 10 | 11 | async function runCode(source: string): Promise { 12 | const jsRet = await runWithNodeJS(source); 13 | const irRet = await runWithLLVM(source); 14 | if (jsRet !== irRet.code && 256 + jsRet !== irRet.code) { 15 | return false; 16 | } 17 | if (irRet.stderr) { 18 | return false; 19 | } 20 | return true; 21 | } 22 | 23 | async function runWithNodeJS(source: string): Promise { 24 | const result = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ES2015 } }); 25 | return eval(`${result.outputText}; main();`); 26 | } 27 | 28 | async function runWithLLVM(source: string): Promise { 29 | const hash = crypto 30 | .createHash('md5') 31 | .update(source) 32 | .digest() 33 | .toString('hex'); 34 | 35 | fs.mkdirSync(path.join(shell.tempdir(), 'minits'), { recursive: true }); 36 | const name = path.join(shell.tempdir(), 'minits', hash + '.ts'); 37 | fs.writeFileSync(name, source); 38 | 39 | const prelude = new Prelude(name); 40 | const outputs = prelude.process(); 41 | const codegen = new LLVMCodeGen(outputs); 42 | codegen.genSourceFile(outputs); 43 | 44 | const tmppath = path.join(path.dirname(outputs), 'output.ll'); 45 | fs.writeFileSync(tmppath, codegen.genText()); 46 | return shell.exec(`lli ${tmppath}`, { async: false }); 47 | } 48 | 49 | export function runTest(name: string, code: string): void { 50 | test(name, async t => { 51 | if (await runCode(code)) { 52 | t.pass(); 53 | } else { 54 | t.fail(); 55 | } 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/tests/variable-declaration.spec.ts: -------------------------------------------------------------------------------- 1 | import { runTest } from './util'; 2 | 3 | const srcGlobalVar = ` 4 | let a: number = 1; 5 | let b: number[] = [2, 3]; 6 | 7 | function main(): number { 8 | let c = 4; 9 | return a + b[0] + c; 10 | } 11 | `; 12 | 13 | const srcTypeInference = ` 14 | function main(): number { 15 | let a = 10; // Skip the annotation type 16 | let b: number = 20; 17 | return a + b; 18 | } 19 | `; 20 | 21 | runTest('test variable declaration: global var', srcGlobalVar); 22 | runTest('test variable declaration: type inference', srcTypeInference); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "outDir": "build/main", 5 | "rootDir": "src", 6 | "moduleResolution": "node", 7 | "module": "commonjs", 8 | "declaration": true, 9 | "inlineSourceMap": true, 10 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 11 | 12 | "strict": true /* Enable all strict type-checking options. */, 13 | 14 | /* Strict Type-Checking Options */ 15 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 16 | // "strictNullChecks": true /* Enable strict null checks. */, 17 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 18 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 19 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 20 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 21 | 22 | /* Additional Checks */ 23 | "noUnusedLocals": true /* Report errors on unused locals. */, 24 | "noUnusedParameters": true /* Report errors on unused parameters. */, 25 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 26 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 27 | 28 | /* Debugging Options */ 29 | "traceResolution": false /* Report module resolution log messages. */, 30 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 31 | "listFiles": false /* Print names of files part of the compilation. */, 32 | "pretty": true /* Stylize errors and messages using color and context. */, 33 | 34 | /* Experimental Options */ 35 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 36 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 37 | 38 | "lib": ["es2017"], 39 | "types": ["node"], 40 | "typeRoots": ["node_modules/@types", "src/types"] 41 | }, 42 | "include": ["src/**/*.ts"], 43 | "exclude": ["node_modules/**"], 44 | "compileOnSave": false 45 | } 46 | -------------------------------------------------------------------------------- /tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "outDir": "build/module", 6 | "module": "esnext" 7 | }, 8 | "exclude": [ 9 | "node_modules/**" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-config-prettier", "tslint-immutable"], 3 | "rules": { 4 | "max-line-length": [true, 120], 5 | "max-classes-per-file": false, 6 | "interface-name": [true, "never-prefix"], 7 | "no-bitwise": false, 8 | "no-implicit-dependencies": [true, "dev"], 9 | "no-var-keyword": true, 10 | "no-parameter-reassignment": true, 11 | "typedef": [true, "call-signature"], 12 | "readonly-keyword": false, 13 | "readonly-array": false, 14 | "no-delete": true, 15 | "no-method-signature": true, 16 | "no-mixed-interface": true, 17 | "no-eval": false, 18 | "no-shadowed-variable": false 19 | } 20 | } 21 | --------------------------------------------------------------------------------