├── .gitignore ├── .npmignore ├── README.md ├── build.js ├── lib ├── thinc.c └── thinc.js ├── out ├── benchmark.html ├── benchmark.thin ├── common.js ├── compiled.c ├── compiled.h ├── compiled.js ├── compiled.wasm └── index.html ├── package.json └── src ├── bytearray.thin ├── c.thin ├── checker.thin ├── compiler.thin ├── imports.thin ├── js.thin ├── lexer.thin ├── library.thin ├── log.thin ├── main.thin ├── node.thin ├── parser.thin ├── preprocessor.thin ├── scope.thin ├── shaking.thin ├── stringbuilder.thin ├── symbol.thin ├── type.thin └── wasm.thin /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.obj 3 | /out/thinc 4 | /out/thinc.exe 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /build.js 2 | /lib/thinc.c 3 | /out/benchmark.html 4 | /out/benchmark.thin 5 | /out/common.js 6 | /out/compiled.c 7 | /out/compiled.wasm 8 | /out/index.html 9 | /out/thinc 10 | /src 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThinScript 2 | 3 | ThinScript is an experimental programming language that compiles to JavaScript, WebAssembly, and C. 4 | It's meant to be a thin layer on top of WebAssembly that makes it easier to work with: no dependencies and fast compile times. 5 | The syntax is inspired by TypeScript and the compiler is open source and bootstrapped (it can compile itself). 6 | 7 | This is still an experiment and isn't intended for real use yet. 8 | The biggest issue is that the generated code currently doesn't delete anything (garbage collection is planned but not yet implemented). 9 | Also the WebAssembly specification is still being developed and the current binary format will stop working when WebAssembly is officially released. 10 | 11 | ## Demo 12 | 13 | An interactive compiler demo is available online at http://evanw.github.io/thinscript/. 14 | Here's some example code to demonstrate the language (documentation will be written at some point): 15 | 16 | ```TypeScript 17 | declare function print(text: string): void; 18 | 19 | class Link { 20 | value: int; 21 | next: Link; 22 | } 23 | 24 | class List { 25 | first: Link; 26 | last: Link; 27 | 28 | append(value: int): void { 29 | var link = new Link(); 30 | link.value = value; 31 | 32 | // Append the new link to the end of the chain 33 | if (this.first == null) this.first = link; 34 | else this.last.next = link; 35 | this.last = link; 36 | } 37 | } 38 | 39 | extern function main(): int { 40 | var list = new List(); 41 | list.append(1); 42 | list.append(2); 43 | list.append(3); 44 | 45 | var total = 0; 46 | var link = list.first; 47 | while (link != null) { 48 | total = total + link.value; 49 | link = link.next; 50 | } 51 | 52 | #if JS 53 | print("Hello from JavaScript"); 54 | #elif WASM 55 | print("Hello from WebAssembly"); 56 | #elif C 57 | print("Hello from C"); 58 | #else 59 | print("Unknown target"); 60 | #endif 61 | 62 | return total; 63 | } 64 | ``` 65 | 66 | # Building 67 | 68 | Run `node build.js` to build the compiler using itself. 69 | This generates updated versions of `out/compiled.js`, `out/compiled.wasm`, and `out/compiled.c`. 70 | If you have a C compiler installed, this also builds a native version of the compiler by compiling `out/compiled.c` and `lib/thinc.c` together into the `out/thinc` binary. 71 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var child_process = require('child_process'); 2 | var fs = require('fs'); 3 | 4 | eval(fs.readFileSync('./out/common.js', 'utf8')); 5 | 6 | // Always build all targets to catch errors in other targets 7 | function compile(compiler, sources) { 8 | var compiled = compileJavaScript(compiler); 9 | 10 | var compiledC = compiled(sources, 'C', 'compiled'); 11 | if (compiledC.stdout) process.stdout.write(compiledC.stdout); 12 | if (!compiledC.success) process.exit(1); 13 | 14 | var compiledJS = compiled(sources, 'JavaScript', 'compiled'); 15 | if (compiledJS.stdout) process.stdout.write(compiledJS.stdout); 16 | if (!compiledJS.success) process.exit(1); 17 | 18 | var compiledWASM = compiled(sources, 'WebAssembly', 'compiled'); 19 | if (compiledWASM.stdout) process.stdout.write(compiledWASM.stdout); 20 | if (!compiledWASM.success) process.exit(1); 21 | 22 | return { 23 | c: compiledC.output, 24 | h: compiledC.secondaryOutput, 25 | js: compiledJS.output, 26 | wasm: compiledWASM.output, 27 | }; 28 | } 29 | 30 | function compileNativeUnix() { 31 | try { 32 | var command = [ 33 | 'cc', 34 | __dirname + '/lib/thinc.c', 35 | __dirname + '/out/compiled.c', 36 | '-o', __dirname + '/out/thinc', 37 | '-Wall', 38 | '-Wextra', 39 | '-Wno-unused-parameter', 40 | '-Wno-unused-function', 41 | '-std=c99', 42 | '-O3', 43 | ]; 44 | console.log(command.join(' ')); 45 | var child = child_process.spawn(command.shift(), command, {stdio: 'inherit'}); 46 | } catch (e) { 47 | console.log('failed to build the native compiler'); 48 | } 49 | } 50 | 51 | function compileNativeWindows() { 52 | // Find all installed Visual Studio versions 53 | var versions = []; 54 | Object.keys(process.env).forEach(function(key) { 55 | var match = /^VS(\d+)COMNTOOLS$/.exec(key); 56 | if (match) { 57 | versions.push(match[1] | 0); 58 | } 59 | }); 60 | 61 | // Try the compilers in descending order 62 | versions.sort(function(a, b) { 63 | return b - a; 64 | }); 65 | next(); 66 | 67 | function next() { 68 | if (!versions.length) { 69 | console.log('failed to build the native compiler'); 70 | return; 71 | } 72 | 73 | var version = versions.shift(); 74 | var folder = process.env['VS' + version + 'COMNTOOLS']; 75 | var child = child_process.spawn('cmd.exe', [], {cwd: __dirname, stdio: ['pipe', process.stdout, process.stderr]}); 76 | child.stdin.write('"' + folder + '/../../VC/bin/vcvars32.bat"\n'); 77 | child.stdin.write('cl.exe /O2 lib/thinc.c out/compiled.c /Fe"out/thinc.exe"\n'); 78 | child.stdin.end(); 79 | child.on('close', function(code) { 80 | if (code !== 0 || !fs.existsSync(__dirname + '/out/thinc.exe')) { 81 | next(); 82 | } 83 | }); 84 | } 85 | } 86 | 87 | var sourceDir = __dirname + '/src'; 88 | var sources = []; 89 | 90 | fs.readdirSync(sourceDir).forEach(function(entry) { 91 | if (/\.thin$/.test(entry)) { 92 | sources.push({ 93 | name: entry, 94 | contents: fs.readFileSync(sourceDir + '/' + entry, 'utf8').replace(/\r\n/g, '\n'), 95 | }); 96 | } 97 | }); 98 | 99 | var compiled = fs.readFileSync(__dirname + '/out/compiled.js', 'utf8'); 100 | 101 | console.log('compiling...'); 102 | var compiled = compile(compiled, sources); 103 | 104 | console.log('compiling again...'); 105 | var compiled = compile(compiled.js, sources); 106 | 107 | console.log('compiling again...'); 108 | var compiled = compile(compiled.js, sources); 109 | 110 | fs.writeFileSync(__dirname + '/out/compiled.c', compiled.c); 111 | fs.writeFileSync(__dirname + '/out/compiled.h', compiled.h); 112 | console.log('wrote to "out/compiled.c"'); 113 | console.log('wrote to "out/compiled.h"'); 114 | 115 | fs.writeFileSync(__dirname + '/out/compiled.js', compiled.js); 116 | console.log('wrote to "out/compiled.js"'); 117 | 118 | fs.writeFileSync(__dirname + '/out/compiled.wasm', Buffer(compiled.wasm)); 119 | console.log('wrote to "out/compiled.wasm"'); 120 | 121 | console.log('building the native compiler...'); 122 | if (process.platform === 'win32') compileNativeWindows(); 123 | else compileNativeUnix(); 124 | -------------------------------------------------------------------------------- /lib/thinc.c: -------------------------------------------------------------------------------- 1 | #include "../out/compiled.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | 10 | static HANDLE handle = INVALID_HANDLE_VALUE; 11 | static CONSOLE_SCREEN_BUFFER_INFO info; 12 | #else 13 | #include 14 | #endif 15 | 16 | void Profiler_begin() { 17 | } 18 | 19 | void Profiler_end(const uint16_t *text) { 20 | } 21 | 22 | void assert(uint8_t truth) { 23 | if (!truth) { 24 | puts("Assertion failed"); 25 | abort(); 26 | } 27 | } 28 | 29 | void Terminal_setColor(int32_t color) { 30 | #ifdef _WIN32 31 | SetConsoleTextAttribute(handle, 32 | color == Color_BOLD ? info.wAttributes | FOREGROUND_INTENSITY : 33 | color == Color_RED ? FOREGROUND_RED | FOREGROUND_INTENSITY : 34 | color == Color_GREEN ? FOREGROUND_GREEN | FOREGROUND_INTENSITY : 35 | color == Color_MAGENTA ? FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY : 36 | info.wAttributes); 37 | #else 38 | if (isatty(STDOUT_FILENO)) { 39 | printf("\x1B[0;%dm", 40 | color == Color_BOLD ? 1 : 41 | color == Color_RED ? 91 : 42 | color == Color_GREEN ? 92 : 43 | color == Color_MAGENTA ? 95 : 44 | 0); 45 | } 46 | #endif 47 | } 48 | 49 | void Terminal_write(const uint16_t *text) { 50 | #ifdef _WIN32 51 | WriteConsoleW(handle, text + 2, *(uint32_t *)text, NULL, NULL); 52 | #else 53 | printf("%s", (const char *)utf16_to_cstring(text)); 54 | #endif 55 | } 56 | 57 | const uint16_t *IO_readTextFile(const uint16_t *path) { 58 | FILE *f = fopen((const char *)utf16_to_cstring(path), "r"); 59 | if (f == NULL) return NULL; 60 | fseek(f, 0, SEEK_END); 61 | long lengthIncludingCarriageReturns = ftell(f); 62 | fseek(f, 0, SEEK_SET); 63 | char *text = malloc(lengthIncludingCarriageReturns + 1); 64 | 65 | // Ignore the return value of fread() and check ftell() instead because 66 | // on Windows, ftell() treats newlines as "\r\n" while fread() treats them 67 | // as "\n". This means the counts aren't comparable and will cause errors. 68 | size_t lengthExcludingCarriageReturns = fread(text, sizeof(char), lengthIncludingCarriageReturns, f); 69 | 70 | if (ftell(f) < lengthIncludingCarriageReturns) { 71 | fclose(f); 72 | return NULL; 73 | } 74 | 75 | // Make sure to end the text at the end of the translated character stream 76 | text[lengthExcludingCarriageReturns] = '\0'; 77 | 78 | fclose(f); 79 | return cstring_to_utf16((uint8_t *)text); 80 | } 81 | 82 | uint8_t IO_writeTextFile(const uint16_t *path, const uint16_t *contents) { 83 | FILE *f = fopen((const char *)utf16_to_cstring(path), "w"); 84 | if (f == NULL) return 0; 85 | const char *text = (const char *)utf16_to_cstring(contents); 86 | size_t length = strlen(text); 87 | if (fwrite(text, sizeof(char), length, f) < length) { 88 | fclose(f); 89 | return 0; 90 | } 91 | fclose(f); 92 | return 1; 93 | } 94 | 95 | uint8_t IO_writeBinaryFile(const uint16_t *path, struct ByteArray *contents) { 96 | FILE *f = fopen((const char *)utf16_to_cstring(path), "wb"); 97 | if (f == NULL) return 0; 98 | size_t length = ByteArray_length(contents); 99 | if (fwrite(ByteArray_bytes(contents), sizeof(uint8_t), length, f) < length) { 100 | fclose(f); 101 | return 0; 102 | } 103 | fclose(f); 104 | return 1; 105 | } 106 | 107 | int main(int argc, char *argv[]) { 108 | #ifdef _WIN32 109 | handle = GetStdHandle(STD_OUTPUT_HANDLE); 110 | GetConsoleScreenBufferInfo(handle, &info); 111 | #endif 112 | 113 | setlocale(LC_ALL, "en_US.UTF-8"); 114 | 115 | for (int i = 1; i < argc; i++) { 116 | main_addArgument(cstring_to_utf16((uint8_t *)argv[i])); 117 | } 118 | 119 | return main_entry(); 120 | } 121 | -------------------------------------------------------------------------------- /lib/thinc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var compiled = null; 5 | 6 | function Terminal_setColor(color) { 7 | if (process.stdout.isTTY) { 8 | var code = 9 | color === compiled.Color.BOLD ? 1 : 10 | color === compiled.Color.RED ? 91 : 11 | color === compiled.Color.GREEN ? 92 : 12 | color === compiled.Color.MAGENTA ? 95 : 13 | 0; 14 | process.stdout.write('\x1B[0;' + code + 'm'); 15 | } 16 | } 17 | 18 | function Terminal_write(text) { 19 | process.stdout.write(text); 20 | } 21 | 22 | function IO_readTextFile(path) { 23 | try { 24 | return fs.readFileSync(path, 'utf8').replace(/\r\n/g, '\n'); 25 | } catch (e) { 26 | return null; 27 | } 28 | } 29 | 30 | function IO_writeTextFile(path, contents) { 31 | try { 32 | fs.writeFileSync(path, contents); 33 | return true; 34 | } catch (e) { 35 | return false; 36 | } 37 | } 38 | 39 | function IO_writeBinaryFile(path, contents) { 40 | try { 41 | fs.writeFileSync(path, Buffer(contents._data.subarray(0, contents._length))); 42 | return true; 43 | } catch (e) { 44 | return false; 45 | } 46 | } 47 | 48 | function main() { 49 | global.assert = require('assert'); 50 | global.Terminal_setColor = Terminal_setColor; 51 | global.Terminal_write = Terminal_write; 52 | global.IO_readTextFile = IO_readTextFile; 53 | global.IO_writeTextFile = IO_writeTextFile; 54 | global.IO_writeBinaryFile = IO_writeBinaryFile; 55 | global.Profiler_begin = function() {}; 56 | global.Profiler_end = function() {}; 57 | global.StringBuilder_append = function(a, b) { return a + b; }; 58 | global.StringBuilder_appendChar = function(a, b) { return a + String.fromCharCode(b); }; 59 | global.Uint8Array_new = function(x) { return new Uint8Array(x); }; 60 | compiled = require(__dirname + '/../out/compiled'); 61 | 62 | for (var i = 2; i < process.argv.length; i++) { 63 | compiled.main_addArgument(process.argv[i]); 64 | } 65 | 66 | process.exit(compiled.main_entry()); 67 | } 68 | 69 | main(); 70 | -------------------------------------------------------------------------------- /out/benchmark.html: -------------------------------------------------------------------------------- 1 | 2 | ThinScript Compiler Demo 3 | 18 | 19 |

ThinScript Compiler Benchmark

20 |

21 | This tests the relative speed of the two compiler backends. 22 |

23 | 24 |

25 | Your browser doesn't support WebAssembly. 26 |

27 | 28 |

JS

29 | 30 | 31 |

WASM

32 | 33 | 34 | 35 | 184 | -------------------------------------------------------------------------------- /out/common.js: -------------------------------------------------------------------------------- 1 | function loadStdlibForWebAssembly() { 2 | var stdlib = { 3 | exports: null, 4 | bytes: null, 5 | chars: null, 6 | ints: null, 7 | stdout: '', 8 | fs: {}, 9 | 10 | reset: function() { 11 | stdlib.stdout = ''; 12 | stdlib.fs = {}; 13 | }, 14 | 15 | createLengthPrefixedString: function(text) { 16 | var chars = stdlib.chars, length = text.length, result = stdlib.exports.main_newString(length), ptr = result + 4 >> 1; 17 | for (var i = 0; i < length; i++) chars[ptr + i] = text.charCodeAt(i); 18 | return result; 19 | }, 20 | 21 | extractLengthPrefixedString: function(index) { 22 | var chars = stdlib.chars, text = '', length = stdlib.ints[index >> 2], i = index + 4 >> 1; 23 | while (length-- > 0) text += String.fromCharCode(chars[i++]); 24 | return text; 25 | }, 26 | 27 | assert: function(truth) { 28 | if (!truth) { 29 | throw new Error('Assertion failed'); 30 | } 31 | }, 32 | 33 | Terminal_setColor: function(color) { 34 | }, 35 | 36 | Terminal_write: function(text) { 37 | stdlib.stdout += stdlib.extractLengthPrefixedString(text); 38 | }, 39 | 40 | IO_readTextFile: function(path) { 41 | var contents = stdlib.fs[stdlib.extractLengthPrefixedString(path)]; 42 | return typeof contents === 'string' ? stdlib.createLengthPrefixedString(contents) : null; 43 | }, 44 | 45 | IO_writeTextFile: function(path, contents) { 46 | stdlib.fs[stdlib.extractLengthPrefixedString(path)] = stdlib.extractLengthPrefixedString(contents); 47 | return true; 48 | }, 49 | 50 | IO_writeBinaryFile: function(path, contents) { 51 | var data = stdlib.ints[contents >> 2]; 52 | var length = stdlib.ints[contents + 4 >> 2]; 53 | stdlib.fs[stdlib.extractLengthPrefixedString(path)] = stdlib.bytes.subarray(data, data + length); 54 | return true; 55 | }, 56 | 57 | Profiler_begin: function() { 58 | time = now(); 59 | }, 60 | 61 | Profiler_end: function(text) { 62 | console.log(stdlib.extractLengthPrefixedString(text) + ': ' + Math.round(now() - time) + 'ms'); 63 | }, 64 | }; 65 | 66 | return stdlib; 67 | } 68 | 69 | function loadStdlibForJavaScript() { 70 | var time = 0; 71 | 72 | return { 73 | stdout: '', 74 | fs: {}, 75 | 76 | reset: function() { 77 | this.stdout = ''; 78 | this.fs = {}; 79 | }, 80 | 81 | assert: function(truth) { 82 | if (!truth) { 83 | throw new Error('Assertion failed'); 84 | } 85 | }, 86 | 87 | Terminal_setColor: function(color) { 88 | }, 89 | 90 | Terminal_write: function(text) { 91 | this.stdout += text; 92 | }, 93 | 94 | IO_readTextFile: function(path) { 95 | var contents = this.fs[path]; 96 | return typeof contents === 'string' ? contents : null; 97 | }, 98 | 99 | IO_writeTextFile: function(path, contents) { 100 | this.fs[path] = contents; 101 | return true; 102 | }, 103 | 104 | IO_writeBinaryFile: function(path, contents) { 105 | this.fs[path] = contents._data.subarray(0, contents._length); 106 | return true; 107 | }, 108 | 109 | Profiler_begin: function() { 110 | time = now(); 111 | }, 112 | 113 | Profiler_end: function(text) { 114 | console.log(text + ': ' + Math.round(now() - time) + 'ms'); 115 | }, 116 | 117 | StringBuilder_append: function(a, b) { 118 | return a + b; 119 | }, 120 | 121 | StringBuilder_appendChar: function(a, b) { 122 | return a + String.fromCharCode(b); 123 | }, 124 | 125 | Uint8Array_new: function(length) { 126 | return new Uint8Array(length); 127 | }, 128 | }; 129 | } 130 | 131 | function fetch(url, responseType, callback) { 132 | var xhr = new XMLHttpRequest; 133 | xhr.open('GET', url); 134 | xhr.onload = function() { 135 | callback(xhr.response); 136 | }; 137 | xhr.responseType = responseType; 138 | xhr.send(null); 139 | } 140 | 141 | function loadJavaScript(callback) { 142 | fetch('compiled.js', 'text', callback); 143 | } 144 | 145 | function loadWebAssembly(callback) { 146 | fetch('compiled.wasm', 'arraybuffer', function(buffer) { 147 | callback(new Uint8Array(buffer)); 148 | }); 149 | } 150 | 151 | function compileWebAssembly(code) { 152 | var stdlib = loadStdlibForWebAssembly(); 153 | var module = Wasm.instantiateModule(code, {global: stdlib}); 154 | var exports = module.exports; 155 | var memory = exports.memory; 156 | stdlib.exports = exports; 157 | stdlib.bytes = new Uint8Array(memory); 158 | stdlib.chars = new Uint16Array(memory); 159 | stdlib.ints = new Int32Array(memory); 160 | 161 | return function(sources, target, name) { 162 | var output = name; 163 | switch (target) { 164 | case 'C': output += '.c'; break; 165 | case 'JavaScript': output += '.js'; break; 166 | case 'WebAssembly': output += '.wasm'; break; 167 | default: throw new Error('Invalid target: ' + target); 168 | } 169 | 170 | console.log('compiling to ' + target + ' using WebAssembly'); 171 | var before = now(); 172 | 173 | stdlib.reset(); 174 | exports.main_reset(); 175 | 176 | sources.forEach(function(source) { 177 | stdlib.fs[source.name] = source.contents; 178 | exports.main_addArgument(stdlib.createLengthPrefixedString(source.name)); 179 | }); 180 | 181 | exports.main_addArgument(stdlib.createLengthPrefixedString('--out')); 182 | exports.main_addArgument(stdlib.createLengthPrefixedString(output)); 183 | 184 | var success = exports.main_entry() === 0; 185 | var after = now(); 186 | var totalTime = Math.round(after - before); 187 | console.log('total time: ' + totalTime + 'ms'); 188 | 189 | return { 190 | secondaryOutput: success && name + '.h' in stdlib.fs ? stdlib.fs[name + '.h'] : null, 191 | output: success ? stdlib.fs[output] : null, 192 | totalTime: totalTime, 193 | stdout: stdlib.stdout, 194 | success: success, 195 | }; 196 | }; 197 | } 198 | 199 | function compileJavaScript(code) { 200 | var stdlib = loadStdlibForJavaScript(); 201 | var exports = {}; 202 | new Function('global', 'exports', code)(stdlib, exports); 203 | 204 | return function(sources, target, name) { 205 | var output = name; 206 | switch (target) { 207 | case 'C': output += '.c'; break; 208 | case 'JavaScript': output += '.js'; break; 209 | case 'WebAssembly': output += '.wasm'; break; 210 | default: throw new Error('Invalid target: ' + target); 211 | } 212 | 213 | console.log('compiling to ' + target + ' using JavaScript'); 214 | var before = now(); 215 | 216 | stdlib.reset(); 217 | exports.main_reset(); 218 | 219 | sources.forEach(function(source) { 220 | stdlib.fs[source.name] = source.contents; 221 | exports.main_addArgument(source.name); 222 | }); 223 | 224 | exports.main_addArgument('--out'); 225 | exports.main_addArgument(output); 226 | 227 | var success = exports.main_entry() === 0; 228 | var after = now(); 229 | var totalTime = Math.round(after - before); 230 | console.log('total time: ' + totalTime + 'ms'); 231 | 232 | return { 233 | secondaryOutput: success && name + '.h' in stdlib.fs ? stdlib.fs[name + '.h'] : null, 234 | output: success ? stdlib.fs[output] : null, 235 | totalTime: totalTime, 236 | stdout: stdlib.stdout, 237 | success: success, 238 | }; 239 | }; 240 | } 241 | 242 | function now() { 243 | return typeof performance !== 'undefined' && performance.now ? performance.now() : +new Date; 244 | } 245 | -------------------------------------------------------------------------------- /out/compiled.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct ByteArray; 4 | 5 | enum { 6 | Color_DEFAULT = 0, 7 | Color_BOLD = 1, 8 | Color_RED = 2, 9 | Color_GREEN = 3, 10 | Color_MAGENTA = 4 11 | }; 12 | 13 | const uint16_t *cstring_to_utf16(uint8_t *utf8); 14 | uint8_t *utf16_to_cstring(const uint16_t *input); 15 | int32_t ByteArray_length(struct ByteArray *__this); 16 | uint8_t *ByteArray_bytes(struct ByteArray *__this); 17 | void ByteArray_clear(struct ByteArray *__this); 18 | uint8_t ByteArray_get(struct ByteArray *__this, int32_t index); 19 | void ByteArray_set(struct ByteArray *__this, int32_t index, uint8_t value); 20 | void ByteArray_append(struct ByteArray *__this, uint8_t value); 21 | void ByteArray_resize(struct ByteArray *__this, int32_t length); 22 | void assert(uint8_t truth); 23 | void Profiler_begin(); 24 | void Profiler_end(const uint16_t *text); 25 | void Terminal_setColor(int32_t color); 26 | void Terminal_write(const uint16_t *text); 27 | const uint16_t *IO_readTextFile(const uint16_t *path); 28 | uint8_t IO_writeTextFile(const uint16_t *path, const uint16_t *contents); 29 | uint8_t IO_writeBinaryFile(const uint16_t *path, struct ByteArray *contents); 30 | void main_addArgument(const uint16_t *text); 31 | void main_reset(); 32 | int32_t main_entry(); 33 | -------------------------------------------------------------------------------- /out/compiled.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanw/thinscript/d76de3df16bd6be49a2b0ec209e7be80b78dc012/out/compiled.wasm -------------------------------------------------------------------------------- /out/index.html: -------------------------------------------------------------------------------- 1 | 2 | ThinScript Compiler Demo 3 | 141 | 142 |

ThinScript Compiler Demo

143 | 144 |

145 | ThinScript is an experimental programming language that compiles to both WebAssembly and JavaScript. 146 | It's meant to be a thin layer on top of WebAssembly that makes it easier to work with: no dependencies and fast compile times. 147 | The syntax is inspired by TypeScript and the compiler is open source and bootstrapped (it can compile itself). 148 |

149 | 150 |

151 | This is still an experiment and isn't intended for real use yet. 152 | The biggest issue is that the generated code currently doesn't delete anything (garbage collection is planned but not yet implemented). 153 | Also the WebAssembly specification is still being developed and the current binary format will stop working when WebAssembly is officially released. 154 |

155 | 156 |
157 | 158 |
159 |

Input

160 | 161 |
162 | 163 |
164 |

Output

165 | 166 | 167 |
168 | 169 | 170 |
171 |
172 | 173 |

174 | Compile using: 175 | 176 | 177 |
178 | Compile to: 179 | 180 | 181 | 182 |
183 | Compile time: 184 | 185 |

186 | 187 |
188 | 189 |
190 |

Usage

191 | 192 | 193 |
194 | 195 |
196 |

Log

197 | 198 | 199 |
200 | 201 | 202 | 535 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thinscript", 3 | "version": "0.0.2", 4 | "description": "A low-level programming language inspired by TypeScript", 5 | "bin": { 6 | "thinc": "lib/thinc.js" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/bytearray.thin: -------------------------------------------------------------------------------- 1 | function ByteArray_set16(array: ByteArray, index: int, value: int): void { 2 | array.set(index, value as byte); 3 | array.set(index + 1, (value >> 8) as byte); 4 | } 5 | 6 | function ByteArray_set32(array: ByteArray, index: int, value: int): void { 7 | array.set(index, value as byte); 8 | array.set(index + 1, (value >> 8) as byte); 9 | array.set(index + 2, (value >> 16) as byte); 10 | array.set(index + 3, (value >> 24) as byte); 11 | } 12 | 13 | function ByteArray_append32(array: ByteArray, value: int): void { 14 | array.append(value as byte); 15 | array.append((value >> 8) as byte); 16 | array.append((value >> 16) as byte); 17 | array.append((value >> 24) as byte); 18 | } 19 | 20 | #if JS //////////////////////////////////////////////////////////////////////////////// 21 | 22 | declare function Uint8Array_new(length: int): Uint8Array; 23 | 24 | declare class Uint8Array { 25 | length: int; 26 | set(source: Uint8Array): void; 27 | subarray(start: int, end: int): Uint8Array; 28 | operator [] (index: int): byte; 29 | operator []= (index: int, value: byte): void; 30 | } 31 | 32 | function ByteArray_setString(array: ByteArray, index: int, text: string): void { 33 | var length = text.length; 34 | assert(index >= 0 && index + length * 2 <= array.length()); 35 | var data = array._data; 36 | var i = 0; 37 | while (i < length) { 38 | var c = text[i]; 39 | data[index] = c as byte; 40 | data[index + 1] = (c >> 8) as byte; 41 | index = index + 2; 42 | i = i + 1; 43 | } 44 | } 45 | 46 | extern class ByteArray { 47 | _data: Uint8Array; 48 | _length: int; 49 | 50 | length(): int { 51 | return this._length; 52 | } 53 | 54 | clear(): void { 55 | this._length = 0; 56 | } 57 | 58 | get(index: int): byte { 59 | assert((index as uint) < (this._length as uint)); 60 | return this._data[index]; 61 | } 62 | 63 | set(index: int, value: byte): void { 64 | assert((index as uint) < (this._length as uint)); 65 | this._data[index] = value; 66 | } 67 | 68 | append(value: byte): void { 69 | var index = this._length; 70 | this.resize(index + 1); 71 | this._data[index] = value; 72 | } 73 | 74 | resize(length: int): void { 75 | if (length > (this._data != null ? this._data.length : 0)) { 76 | var capacity = length * 2; 77 | var data = Uint8Array_new(capacity); 78 | if (this._data != null) data.set(this._data); 79 | this._data = data; 80 | } 81 | 82 | this._length = length; 83 | } 84 | } 85 | 86 | 87 | #elif WASM || C //////////////////////////////////////////////////////////////////////////////// 88 | 89 | function ByteArray_setString(array: ByteArray, index: int, text: string): void { 90 | var length = text.length; 91 | unsafe { 92 | assert(index >= 0 && index + length * 2 <= array.length()); 93 | memcpy(array._data + index, text as *byte + 4, length as uint * 2); 94 | } 95 | } 96 | 97 | extern class ByteArray { 98 | _data: *byte; 99 | _length: uint; 100 | _capacity: uint; 101 | 102 | length(): int { 103 | return this._length as int; 104 | } 105 | 106 | unsafe bytes(): *byte { 107 | return this._data; 108 | } 109 | 110 | clear(): void { 111 | this._length = 0; 112 | } 113 | 114 | get(index: int): byte { 115 | assert((index as uint) < this._length); 116 | unsafe { 117 | return *(this._data + index); 118 | } 119 | } 120 | 121 | set(index: int, value: byte): void { 122 | assert((index as uint) < this._length); 123 | unsafe { 124 | *(this._data + index) = value; 125 | } 126 | } 127 | 128 | append(value: byte): void { 129 | var offset = this._length; 130 | unsafe { 131 | this.resize(offset as int + 1); 132 | *(this._data + offset) = value; 133 | } 134 | } 135 | 136 | resize(length: int): void { 137 | if (length as uint > this._capacity) { 138 | unsafe { 139 | var capacity = length as uint * 2; 140 | var data = malloc(capacity); 141 | memcpy(data, this._data, this._length); 142 | this._capacity = capacity; 143 | this._data = data; 144 | } 145 | } 146 | 147 | this._length = length as uint; 148 | } 149 | } 150 | 151 | #else //////////////////////////////////////////////////////////////////////////////// 152 | 153 | declare function ByteArray_setString(array: ByteArray, index: int, text: string): void; 154 | 155 | declare class ByteArray { 156 | length(): int; 157 | clear(): void; 158 | get(index: int): byte; 159 | set(index: int, value: byte): void; 160 | append(value: byte): void; 161 | resize(length: int): void; 162 | } 163 | 164 | #endif 165 | -------------------------------------------------------------------------------- /src/c.thin: -------------------------------------------------------------------------------- 1 | enum TypeMode { 2 | NORMAL, 3 | DECLARATION, 4 | BARE, 5 | } 6 | 7 | enum SourceMode { 8 | HEADER, 9 | IMPLEMENTATION, 10 | } 11 | 12 | class CResult { 13 | context: CheckContext; 14 | code: StringBuilder; 15 | codePrefix: StringBuilder; 16 | headerName: string; 17 | private indent: int; 18 | private hasStrings: bool; 19 | private previousNode: Node; 20 | private nextStringLiteral: int; 21 | 22 | emitIndent(): void { 23 | var i = this.indent; 24 | while (i > 0) { 25 | this.code.append(" "); 26 | i = i - 1; 27 | } 28 | } 29 | 30 | emitNewlineBefore(node: Node): void { 31 | if (this.previousNode != null && (!isCompactNodeKind(this.previousNode.kind) || !isCompactNodeKind(node.kind))) { 32 | this.code.appendChar('\n'); 33 | } 34 | this.previousNode = null; 35 | } 36 | 37 | emitNewlineAfter(node: Node): void { 38 | this.previousNode = node; 39 | } 40 | 41 | emitStatements(node: Node): void { 42 | while (node != null) { 43 | this.emitStatement(node); 44 | node = node.nextSibling; 45 | } 46 | } 47 | 48 | emitBlock(node: Node): void { 49 | this.previousNode = null; 50 | this.code.append("{\n"); 51 | this.indent = this.indent + 1; 52 | this.emitStatements(node.firstChild); 53 | this.indent = this.indent - 1; 54 | this.emitIndent(); 55 | this.code.appendChar('}'); 56 | this.previousNode = null; 57 | } 58 | 59 | emitUnary(node: Node, parentPrecedence: Precedence, operator: string): void { 60 | var isPostfix = isUnaryPostfix(node.kind); 61 | var operatorPrecedence = isPostfix ? Precedence.UNARY_POSTFIX : Precedence.UNARY_PREFIX; 62 | var code = this.code; 63 | 64 | if (parentPrecedence > operatorPrecedence) { 65 | code.appendChar('('); 66 | } 67 | 68 | if (!isPostfix) { 69 | code.append(operator); 70 | } 71 | 72 | this.emitExpression(node.unaryValue(), operatorPrecedence); 73 | 74 | if (isPostfix) { 75 | code.append(operator); 76 | } 77 | 78 | if (parentPrecedence > operatorPrecedence) { 79 | code.appendChar(')'); 80 | } 81 | } 82 | 83 | emitBinary(node: Node, parentPrecedence: Precedence, operator: string, operatorPrecedence: Precedence): void { 84 | var kind = node.kind; 85 | var isRightAssociative = kind == NodeKind.ASSIGN; 86 | var needsParentheses = parentPrecedence > operatorPrecedence; 87 | var parentKind = node.parent.kind; 88 | var code = this.code; 89 | 90 | // Try to avoid warnings from Clang and GCC 91 | if (parentKind == NodeKind.LOGICAL_OR && kind == NodeKind.LOGICAL_AND || 92 | parentKind == NodeKind.BITWISE_OR && kind == NodeKind.BITWISE_AND || 93 | (parentKind == NodeKind.EQUAL || parentKind == NodeKind.NOT_EQUAL) && (kind == NodeKind.EQUAL || kind == NodeKind.NOT_EQUAL) || 94 | (kind == NodeKind.ADD || kind == NodeKind.SUBTRACT) && ( 95 | parentKind == NodeKind.BITWISE_AND || parentKind == NodeKind.BITWISE_OR || parentKind == NodeKind.BITWISE_XOR || 96 | parentKind == NodeKind.SHIFT_LEFT || parentKind == NodeKind.SHIFT_RIGHT)) { 97 | needsParentheses = true; 98 | } 99 | 100 | if (needsParentheses) { 101 | code.appendChar('('); 102 | } 103 | 104 | this.emitExpression(node.binaryLeft(), isRightAssociative ? (operatorPrecedence as int + 1) as Precedence : operatorPrecedence); 105 | code.append(operator); 106 | this.emitExpression(node.binaryRight(), isRightAssociative ? operatorPrecedence : (operatorPrecedence as int + 1) as Precedence); 107 | 108 | if (needsParentheses) { 109 | code.appendChar(')'); 110 | } 111 | } 112 | 113 | emitCommaSeparatedExpressions(start: Node, stop: Node): void { 114 | while (start != stop) { 115 | this.emitExpression(start, Precedence.LOWEST); 116 | start = start.nextSibling; 117 | 118 | if (start != stop) { 119 | this.code.append(", "); 120 | } 121 | } 122 | } 123 | 124 | emitSymbolName(symbol: Symbol): void { 125 | if (symbol.kind == SymbolKind.FUNCTION_INSTANCE) { 126 | this.code.append(symbol.parent().name).appendChar('_'); 127 | } 128 | 129 | this.code.append(symbol.rename != null ? symbol.rename : symbol.name); 130 | } 131 | 132 | emitExpression(node: Node, parentPrecedence: Precedence): void { 133 | var code = this.code; 134 | 135 | assert(node.resolvedType != null); 136 | 137 | if (node.kind == NodeKind.NAME) { 138 | this.emitSymbolName(node.symbol); 139 | } 140 | 141 | else if (node.kind == NodeKind.NULL) { 142 | code.append("NULL"); 143 | } 144 | 145 | else if (node.kind == NodeKind.BOOL) { 146 | code.appendChar(node.intValue != 0 ? '1' as byte : '0' as byte); 147 | } 148 | 149 | else if (node.kind == NodeKind.INT) { 150 | code.append(node.resolvedType.isUnsigned() 151 | ? (node.intValue as uint).toString() 152 | : node.intValue.toString()); 153 | } 154 | 155 | else if (node.kind == NodeKind.STRING) { 156 | var id = this.nextStringLiteral; 157 | var builder = StringBuilder_new(); 158 | builder.append("__string_").append(id.toString()); 159 | var value = node.stringValue; 160 | var codePrefix = this.codePrefix; 161 | var length = value.length; 162 | var i = 0; 163 | if (!this.hasStrings) { 164 | codePrefix.append(` 165 | #ifdef THINSCRIPT_BIG_ENDIAN 166 | #define S(a, b) (((a) << 16) | (b)) 167 | #else 168 | #define S(a, b) ((a) | ((b) << 16)) 169 | #endif 170 | 171 | `); 172 | this.hasStrings = true; 173 | } 174 | var underscore = true; 175 | i = 0; 176 | while (i < length && i < 32) { 177 | var c = value[i]; 178 | if (isAlpha(c) || isNumber(c)) { 179 | if (underscore) { 180 | builder.appendChar('_'); 181 | underscore = false; 182 | } 183 | builder.appendChar(c); 184 | } else { 185 | underscore = true; 186 | } 187 | i = i + 1; 188 | } 189 | var name = builder.finish(); 190 | codePrefix.append("static const uint32_t ").append(name).append("[] = {").append(length.toString()); 191 | i = 0; 192 | while (i < length) { 193 | codePrefix.append(", S("); 194 | cEmitCharacter(codePrefix, value[i]); 195 | if (i + 1 < length) { 196 | codePrefix.append(i % 32 == 20 ? ",\n " : ", "); 197 | cEmitCharacter(codePrefix, value[i + 1]); 198 | codePrefix.appendChar(')'); 199 | } else { 200 | codePrefix.append(", 0)"); 201 | } 202 | i = i + 2; 203 | } 204 | codePrefix.append("};\n"); 205 | this.nextStringLiteral = this.nextStringLiteral + 1; 206 | code.append("(const uint16_t *)").append(name); 207 | } 208 | 209 | else if (node.kind == NodeKind.CAST) { 210 | if (parentPrecedence > Precedence.UNARY_PREFIX) { 211 | code.appendChar('('); 212 | } 213 | 214 | code.appendChar('('); 215 | this.emitType(node.resolvedType, TypeMode.NORMAL); 216 | code.appendChar(')'); 217 | this.emitExpression(node.castValue(), Precedence.UNARY_PREFIX); 218 | 219 | if (parentPrecedence > Precedence.UNARY_PREFIX) { 220 | code.appendChar(')'); 221 | } 222 | } 223 | 224 | else if (node.kind == NodeKind.DOT) { 225 | var target = node.dotTarget(); 226 | this.emitExpression(target, Precedence.MEMBER); 227 | code.append(target.resolvedType.isReference() ? "->" : "."); 228 | this.emitSymbolName(node.symbol); 229 | } 230 | 231 | else if (node.kind == NodeKind.HOOK) { 232 | if (parentPrecedence > Precedence.ASSIGN) { 233 | code.appendChar('('); 234 | } 235 | 236 | this.emitExpression(node.hookValue(), Precedence.LOGICAL_OR); 237 | code.append(" ? "); 238 | this.emitExpression(node.hookTrue(), Precedence.ASSIGN); 239 | code.append(" : "); 240 | this.emitExpression(node.hookFalse(), Precedence.ASSIGN); 241 | 242 | if (parentPrecedence > Precedence.ASSIGN) { 243 | code.appendChar(')'); 244 | } 245 | } 246 | 247 | else if (node.kind == NodeKind.CALL) { 248 | var value = node.callValue(); 249 | this.emitSymbolName(value.symbol); 250 | code.appendChar('('); 251 | 252 | // Make sure to emit "this" 253 | if (value.kind == NodeKind.DOT) { 254 | this.emitExpression(value.dotTarget(), Precedence.LOWEST); 255 | if (value.nextSibling != null) { 256 | code.append(", "); 257 | } 258 | } 259 | 260 | this.emitCommaSeparatedExpressions(value.nextSibling, null); 261 | code.appendChar(')'); 262 | } 263 | 264 | // This uses "calloc" instead of "malloc" because it needs to be zero-initialized 265 | else if (node.kind == NodeKind.NEW) { 266 | code.append("calloc(1, sizeof("); 267 | this.emitType(node.resolvedType, TypeMode.BARE); 268 | code.append("))"); 269 | } 270 | 271 | else if (node.kind == NodeKind.COMPLEMENT) this.emitUnary(node, parentPrecedence, "~"); 272 | else if (node.kind == NodeKind.DEREFERENCE) this.emitUnary(node, parentPrecedence, "*"); 273 | else if (node.kind == NodeKind.NEGATIVE) this.emitUnary(node, parentPrecedence, "-"); 274 | else if (node.kind == NodeKind.NOT) this.emitUnary(node, parentPrecedence, "!"); 275 | else if (node.kind == NodeKind.POSITIVE) this.emitUnary(node, parentPrecedence, "+"); 276 | else if (node.kind == NodeKind.POSTFIX_DECREMENT) this.emitUnary(node, parentPrecedence, "--"); 277 | else if (node.kind == NodeKind.POSTFIX_INCREMENT) this.emitUnary(node, parentPrecedence, "++"); 278 | else if (node.kind == NodeKind.PREFIX_DECREMENT) this.emitUnary(node, parentPrecedence, "--"); 279 | else if (node.kind == NodeKind.PREFIX_INCREMENT) this.emitUnary(node, parentPrecedence, "++"); 280 | 281 | else if (node.kind == NodeKind.ADD) this.emitBinary(node, parentPrecedence, " + ", Precedence.ADD); 282 | else if (node.kind == NodeKind.ASSIGN) this.emitBinary(node, parentPrecedence, " = ", Precedence.ASSIGN); 283 | else if (node.kind == NodeKind.BITWISE_AND) this.emitBinary(node, parentPrecedence, " & ", Precedence.BITWISE_AND); 284 | else if (node.kind == NodeKind.BITWISE_OR) this.emitBinary(node, parentPrecedence, " | ", Precedence.BITWISE_OR); 285 | else if (node.kind == NodeKind.BITWISE_XOR) this.emitBinary(node, parentPrecedence, " ^ ", Precedence.BITWISE_XOR); 286 | else if (node.kind == NodeKind.DIVIDE) this.emitBinary(node, parentPrecedence, " / ", Precedence.MULTIPLY); 287 | else if (node.kind == NodeKind.EQUAL) this.emitBinary(node, parentPrecedence, " == ", Precedence.EQUAL); 288 | else if (node.kind == NodeKind.GREATER_THAN) this.emitBinary(node, parentPrecedence, " > ", Precedence.COMPARE); 289 | else if (node.kind == NodeKind.GREATER_THAN_EQUAL) this.emitBinary(node, parentPrecedence, " >= ", Precedence.COMPARE); 290 | else if (node.kind == NodeKind.LESS_THAN) this.emitBinary(node, parentPrecedence, " < ", Precedence.COMPARE); 291 | else if (node.kind == NodeKind.LESS_THAN_EQUAL) this.emitBinary(node, parentPrecedence, " <= ", Precedence.COMPARE); 292 | else if (node.kind == NodeKind.LOGICAL_AND) this.emitBinary(node, parentPrecedence, " && ", Precedence.LOGICAL_AND); 293 | else if (node.kind == NodeKind.LOGICAL_OR) this.emitBinary(node, parentPrecedence, " || ", Precedence.LOGICAL_OR); 294 | else if (node.kind == NodeKind.MULTIPLY) this.emitBinary(node, parentPrecedence, " * ", Precedence.MULTIPLY); 295 | else if (node.kind == NodeKind.NOT_EQUAL) this.emitBinary(node, parentPrecedence, " != ", Precedence.EQUAL); 296 | else if (node.kind == NodeKind.REMAINDER) this.emitBinary(node, parentPrecedence, " % ", Precedence.MULTIPLY); 297 | else if (node.kind == NodeKind.SHIFT_LEFT) this.emitBinary(node, parentPrecedence, " << ", Precedence.SHIFT); 298 | else if (node.kind == NodeKind.SHIFT_RIGHT) this.emitBinary(node, parentPrecedence, " >> ", Precedence.SHIFT); 299 | else if (node.kind == NodeKind.SUBTRACT) this.emitBinary(node, parentPrecedence, " - ", Precedence.ADD); 300 | 301 | else { 302 | assert(false); 303 | } 304 | } 305 | 306 | shouldEmitClass(node: Node): bool { 307 | assert(node.kind == NodeKind.CLASS); 308 | return node.symbol.kind == SymbolKind.TYPE_CLASS && node.symbol != this.context.stringType.symbol; 309 | } 310 | 311 | emitType(originalType: Type, mode: TypeMode): void { 312 | var context = this.context; 313 | var code = this.code; 314 | var type = originalType; 315 | 316 | if (type.isEnum()) { 317 | type = type.underlyingType(this.context); 318 | } 319 | 320 | else { 321 | while (type.pointerTo != null) { 322 | type = type.pointerTo; 323 | } 324 | } 325 | 326 | if (type.isClass()) { 327 | code.append("struct "); 328 | } 329 | 330 | if (type == context.boolType || type == context.byteType) code.append("uint8_t"); 331 | else if (type == context.sbyteType) code.append("int8_t"); 332 | else if (type == context.intType) code.append("int32_t"); 333 | else if (type == context.shortType) code.append("int16_t"); 334 | else if (type == context.stringType) code.append("const uint16_t"); 335 | else if (type == context.uintType) code.append("uint32_t"); 336 | else if (type == context.ushortType) code.append("uint16_t"); 337 | else this.emitSymbolName(type.symbol); 338 | 339 | if (originalType.pointerTo != null) { 340 | code.appendChar(' '); 341 | while (originalType.pointerTo != null) { 342 | code.appendChar('*'); 343 | originalType = originalType.pointerTo; 344 | } 345 | } 346 | 347 | else if (mode != TypeMode.BARE) { 348 | if (type.isReference()) code.append(" *"); 349 | else if (mode == TypeMode.DECLARATION) code.appendChar(' '); 350 | } 351 | } 352 | 353 | emitStatement(node: Node): void { 354 | var code = this.code; 355 | 356 | if (node.kind == NodeKind.IF) { 357 | this.emitNewlineBefore(node); 358 | this.emitIndent(); 359 | while (true) { 360 | code.append("if ("); 361 | this.emitExpression(node.ifValue(), Precedence.LOWEST); 362 | code.append(") "); 363 | this.emitBlock(node.ifTrue()); 364 | var no = node.ifFalse(); 365 | if (no == null) { 366 | code.appendChar('\n'); 367 | break; 368 | } 369 | code.append("\n\n"); 370 | this.emitIndent(); 371 | code.append("else "); 372 | if (no.firstChild == null || no.firstChild != no.lastChild || no.firstChild.kind != NodeKind.IF) { 373 | this.emitBlock(no); 374 | code.appendChar('\n'); 375 | break; 376 | } 377 | node = no.firstChild; 378 | } 379 | this.emitNewlineAfter(node); 380 | } 381 | 382 | else if (node.kind == NodeKind.WHILE) { 383 | this.emitNewlineBefore(node); 384 | this.emitIndent(); 385 | code.append("while ("); 386 | this.emitExpression(node.whileValue(), Precedence.LOWEST); 387 | code.append(") "); 388 | this.emitBlock(node.whileBody()); 389 | code.appendChar('\n'); 390 | this.emitNewlineAfter(node); 391 | } 392 | 393 | else if (node.kind == NodeKind.BREAK) { 394 | this.emitNewlineBefore(node); 395 | this.emitIndent(); 396 | code.append("break;\n"); 397 | this.emitNewlineAfter(node); 398 | } 399 | 400 | else if (node.kind == NodeKind.CONTINUE) { 401 | this.emitNewlineBefore(node); 402 | this.emitIndent(); 403 | code.append("continue;\n"); 404 | this.emitNewlineAfter(node); 405 | } 406 | 407 | else if (node.kind == NodeKind.EXPRESSION) { 408 | this.emitNewlineBefore(node); 409 | this.emitIndent(); 410 | this.emitExpression(node.expressionValue(), Precedence.LOWEST); 411 | code.append(";\n"); 412 | this.emitNewlineAfter(node); 413 | } 414 | 415 | else if (node.kind == NodeKind.EMPTY) { 416 | } 417 | 418 | else if (node.kind == NodeKind.RETURN) { 419 | var value = node.returnValue(); 420 | this.emitNewlineBefore(node); 421 | this.emitIndent(); 422 | if (value != null) { 423 | code.append("return "); 424 | this.emitExpression(value, Precedence.LOWEST); 425 | code.append(";\n"); 426 | } else { 427 | code.append("return;\n"); 428 | } 429 | this.emitNewlineAfter(node); 430 | } 431 | 432 | else if (node.kind == NodeKind.BLOCK) { 433 | if (node.parent.kind == NodeKind.BLOCK) { 434 | this.emitStatements(node.firstChild); 435 | } else { 436 | this.emitNewlineBefore(node); 437 | this.emitIndent(); 438 | this.emitBlock(node); 439 | code.appendChar('\n'); 440 | this.emitNewlineAfter(node); 441 | } 442 | } 443 | 444 | else if (node.kind == NodeKind.VARIABLES) { 445 | this.emitNewlineBefore(node); 446 | var child = node.firstChild; 447 | while (child != null) { 448 | var value = child.variableValue(); 449 | this.emitIndent(); 450 | this.emitType(child.symbol.resolvedType, TypeMode.DECLARATION); 451 | this.emitSymbolName(child.symbol); 452 | assert(value != null); 453 | code.append(" = "); 454 | this.emitExpression(value, Precedence.LOWEST); 455 | code.append(";\n"); 456 | child = child.nextSibling; 457 | } 458 | this.emitNewlineAfter(node); 459 | } 460 | 461 | else if (node.kind == NodeKind.CONSTANTS || node.kind == NodeKind.ENUM) { 462 | } 463 | 464 | else { 465 | assert(false); 466 | } 467 | } 468 | 469 | emitIncludes(code: StringBuilder, mode: SourceMode): void { 470 | if (mode == SourceMode.HEADER) { 471 | code.append("#include \n"); // Need "int32_t" and friends 472 | } 473 | 474 | else { 475 | code.append("#include \"").append(this.headerName).append("\"\n"); 476 | code.append("#include \n"); // Need "NULL" and "calloc" 477 | code.append("#include \n"); // Need "memcpy" and "memcmp" 478 | } 479 | } 480 | 481 | emitTypeDeclarations(node: Node, mode: SourceMode): void { 482 | var code = this.code; 483 | 484 | while (node != null) { 485 | if (node.kind == NodeKind.CLASS) { 486 | if (this.shouldEmitClass(node) && (node.isDeclareOrExtern() ? mode == SourceMode.HEADER : mode == SourceMode.IMPLEMENTATION)) { 487 | this.emitNewlineBefore(node); 488 | code.append("struct ").append(node.symbol.name).append(";\n"); 489 | } 490 | } 491 | 492 | node = node.nextSibling; 493 | } 494 | } 495 | 496 | emitTypeDefinitions(node: Node, mode: SourceMode): void { 497 | var code = this.code; 498 | 499 | while (node != null) { 500 | if (node.kind == NodeKind.CLASS) { 501 | if (this.shouldEmitClass(node) && mode != SourceMode.HEADER) { 502 | this.emitNewlineBefore(node); 503 | code.append("struct "); 504 | this.emitSymbolName(node.symbol); 505 | code.append(" {\n"); 506 | this.indent = this.indent + 1; 507 | 508 | // Emit member variables 509 | var child = node.firstChild; 510 | while (child != null) { 511 | if (child.kind == NodeKind.VARIABLE) { 512 | this.emitIndent(); 513 | this.emitType(child.symbol.resolvedType, TypeMode.DECLARATION); 514 | this.emitSymbolName(child.symbol); 515 | code.append(";\n"); 516 | } 517 | child = child.nextSibling; 518 | } 519 | 520 | this.indent = this.indent - 1; 521 | code.append("};\n"); 522 | this.emitNewlineAfter(node); 523 | } 524 | } 525 | 526 | else if (node.kind == NodeKind.ENUM) { 527 | if (mode == SourceMode.HEADER && node.isExtern()) { 528 | this.emitNewlineBefore(node); 529 | code.append("enum {\n"); 530 | this.indent = this.indent + 1; 531 | 532 | // Emit enum values 533 | var child = node.firstChild; 534 | while (child != null) { 535 | assert(child.kind == NodeKind.VARIABLE); 536 | this.emitIndent(); 537 | this.emitSymbolName(node.symbol); 538 | code.append("_"); 539 | this.emitSymbolName(child.symbol); 540 | code.append(" = "); 541 | code.append(child.symbol.offset.toString()); 542 | child = child.nextSibling; 543 | code.append(child != null ? ",\n" : "\n"); 544 | } 545 | 546 | this.indent = this.indent - 1; 547 | this.emitIndent(); 548 | code.append("};\n"); 549 | this.emitNewlineAfter(node); 550 | } 551 | } 552 | 553 | node = node.nextSibling; 554 | } 555 | } 556 | 557 | shouldEmitFunction(symbol: Symbol): bool { 558 | return symbol.kind != SymbolKind.FUNCTION_GLOBAL || symbol.name != "malloc" && symbol.name != "memcpy" && symbol.name != "memcmp"; 559 | } 560 | 561 | emitFunctionDeclarations(node: Node, mode: SourceMode): void { 562 | var code = this.code; 563 | 564 | while (node != null) { 565 | if (node.kind == NodeKind.FUNCTION && (mode != SourceMode.HEADER || node.isDeclareOrExtern())) { 566 | var symbol = node.symbol; 567 | 568 | if (this.shouldEmitFunction(symbol)) { 569 | var returnType = node.functionReturnType(); 570 | var child = node.functionFirstArgument(); 571 | 572 | this.emitNewlineBefore(node); 573 | if (!node.isDeclareOrExtern()) { 574 | code.append("static "); 575 | } 576 | this.emitType(returnType.resolvedType, TypeMode.DECLARATION); 577 | this.emitSymbolName(symbol); 578 | code.appendChar('('); 579 | 580 | if (symbol.kind == SymbolKind.FUNCTION_INSTANCE) { 581 | child.symbol.rename = "__this"; 582 | } 583 | 584 | while (child != returnType) { 585 | assert(child.kind == NodeKind.VARIABLE); 586 | this.emitType(child.symbol.resolvedType, TypeMode.DECLARATION); 587 | this.emitSymbolName(child.symbol); 588 | child = child.nextSibling; 589 | if (child != returnType) { 590 | code.append(", "); 591 | } 592 | } 593 | 594 | code.append(");\n"); 595 | } 596 | } 597 | 598 | else if (node.kind == NodeKind.CLASS) { 599 | this.emitFunctionDeclarations(node.firstChild, mode); 600 | } 601 | 602 | node = node.nextSibling; 603 | } 604 | } 605 | 606 | emitGlobalVariables(node: Node, mode: SourceMode): void { 607 | var code = this.code; 608 | 609 | while (node != null) { 610 | if (node.kind == NodeKind.VARIABLE && (mode != SourceMode.HEADER || node.isExtern())) { 611 | var value = node.variableValue(); 612 | this.emitNewlineBefore(node); 613 | if (!node.isDeclareOrExtern()) { 614 | code.append("static "); 615 | } 616 | this.emitType(node.symbol.resolvedType, TypeMode.DECLARATION); 617 | this.emitSymbolName(node.symbol); 618 | code.append(" = "); 619 | this.emitExpression(value, Precedence.LOWEST); 620 | code.append(";\n"); 621 | } 622 | 623 | else if (node.kind == NodeKind.VARIABLES) { 624 | this.emitGlobalVariables(node.firstChild, mode); 625 | } 626 | 627 | node = node.nextSibling; 628 | } 629 | } 630 | 631 | emitFunctionDefinitions(node: Node): void { 632 | var code = this.code; 633 | 634 | while (node != null) { 635 | if (node.kind == NodeKind.FUNCTION) { 636 | var body = node.functionBody(); 637 | var symbol = node.symbol; 638 | 639 | if (body != null && this.shouldEmitFunction(symbol)) { 640 | var returnType = node.functionReturnType(); 641 | var child = node.firstChild; 642 | 643 | this.emitNewlineBefore(node); 644 | if (!node.isDeclareOrExtern()) { 645 | code.append("static "); 646 | } 647 | this.emitType(returnType.resolvedType, TypeMode.DECLARATION); 648 | this.emitSymbolName(symbol); 649 | code.appendChar('('); 650 | 651 | while (child != returnType) { 652 | assert(child.kind == NodeKind.VARIABLE); 653 | this.emitType(child.symbol.resolvedType, TypeMode.DECLARATION); 654 | this.emitSymbolName(child.symbol); 655 | child = child.nextSibling; 656 | if (child != returnType) { 657 | code.append(", "); 658 | } 659 | } 660 | 661 | code.append(") "); 662 | this.emitBlock(node.functionBody()); 663 | code.appendChar('\n'); 664 | this.emitNewlineAfter(node); 665 | } 666 | } 667 | 668 | else if (node.kind == NodeKind.CLASS) { 669 | this.emitFunctionDefinitions(node.firstChild); 670 | } 671 | 672 | node = node.nextSibling; 673 | } 674 | } 675 | 676 | finishImplementation(): void { 677 | if (this.hasStrings) { 678 | this.codePrefix.append("\n#undef S\n"); 679 | } 680 | } 681 | } 682 | 683 | function cEmitCharacter(builder: StringBuilder, c: ushort): void { 684 | if (isASCII(c)) { 685 | builder.appendChar('\''); 686 | if (c == '\\' || c == '\'') { 687 | builder.appendChar('\\'); 688 | } 689 | builder.appendChar(c); 690 | builder.appendChar('\''); 691 | } 692 | else if (c == '\0') builder.append("\'\\0\'"); 693 | else if (c == '\r') builder.append("\'\\r\'"); 694 | else if (c == '\n') builder.append("\'\\n\'"); 695 | else if (c == '\t') builder.append("\'\\t\'"); 696 | else builder.append(c.toString()); 697 | } 698 | 699 | function cEmit(compiler: Compiler): void { 700 | var child = compiler.global.firstChild; 701 | var temporaryCode = StringBuilder_new(); 702 | var headerCode = StringBuilder_new(); 703 | var implementationCode = StringBuilder_new(); 704 | var result = new CResult(); 705 | result.context = compiler.context; 706 | result.code = temporaryCode; 707 | result.codePrefix = implementationCode; 708 | result.headerName = replaceFileExtension(compiler.outputName, ".h"); 709 | 710 | if (child != null) { 711 | // Emit implementation 712 | result.emitIncludes(implementationCode, SourceMode.IMPLEMENTATION); 713 | result.emitNewlineAfter(child); 714 | 715 | result.emitTypeDeclarations(child, SourceMode.IMPLEMENTATION); 716 | result.emitNewlineAfter(child); 717 | 718 | result.emitTypeDefinitions(child, SourceMode.IMPLEMENTATION); 719 | result.emitNewlineAfter(child); 720 | 721 | result.emitFunctionDeclarations(child, SourceMode.IMPLEMENTATION); 722 | result.emitNewlineAfter(child); 723 | 724 | result.emitGlobalVariables(child, SourceMode.IMPLEMENTATION); 725 | result.emitNewlineAfter(child); 726 | 727 | result.emitFunctionDefinitions(child); 728 | result.finishImplementation(); 729 | implementationCode.append(temporaryCode.finish()); 730 | 731 | // Emit header 732 | result.code = headerCode; 733 | result.emitIncludes(headerCode, SourceMode.HEADER); 734 | result.emitNewlineAfter(child); 735 | 736 | result.emitTypeDeclarations(child, SourceMode.HEADER); 737 | result.emitNewlineAfter(child); 738 | 739 | result.emitTypeDefinitions(child, SourceMode.HEADER); 740 | result.emitNewlineAfter(child); 741 | 742 | result.emitFunctionDeclarations(child, SourceMode.HEADER); 743 | result.emitNewlineAfter(child); 744 | 745 | result.emitGlobalVariables(child, SourceMode.HEADER); 746 | result.emitNewlineAfter(child); 747 | } 748 | 749 | compiler.outputC = implementationCode.finish(); 750 | compiler.outputH = headerCode.finish(); 751 | } 752 | -------------------------------------------------------------------------------- /src/compiler.thin: -------------------------------------------------------------------------------- 1 | enum CompileTarget { 2 | NONE, 3 | C, 4 | JAVASCRIPT, 5 | WEBASSEMBLY, 6 | } 7 | 8 | class Compiler { 9 | log: Log; 10 | global: Node; 11 | firstSource: Source; 12 | lastSource: Source; 13 | preprocessor: Preprocessor; 14 | target: CompileTarget; 15 | context: CheckContext; 16 | librarySource: Source; 17 | outputName: string; 18 | outputWASM: ByteArray; 19 | outputJS: string; 20 | outputC: string; 21 | outputH: string; 22 | 23 | initialize(target: CompileTarget, outputName: string): void { 24 | assert(this.log == null); 25 | this.log = new Log(); 26 | this.preprocessor = new Preprocessor(); 27 | this.target = target; 28 | this.outputName = outputName; 29 | this.librarySource = this.addInput("", library()); 30 | this.librarySource.isLibrary = true; 31 | this.createGlobals(); 32 | 33 | if (target == CompileTarget.C) { 34 | this.preprocessor.define("C", true); 35 | } 36 | 37 | else if (target == CompileTarget.JAVASCRIPT) { 38 | this.preprocessor.define("JS", true); 39 | } 40 | 41 | else if (target == CompileTarget.WEBASSEMBLY) { 42 | this.preprocessor.define("WASM", true); 43 | } 44 | } 45 | 46 | createGlobals(): void { 47 | var context = new CheckContext(); 48 | context.log = this.log; 49 | context.target = this.target; 50 | context.pointerByteSize = 4; // Assume 32-bit code generation for now 51 | 52 | var global = new Node(); 53 | global.kind = NodeKind.GLOBAL; 54 | 55 | var scope = new Scope(); 56 | global.scope = scope; 57 | 58 | // Hard-coded types 59 | context.errorType = scope.defineNativeType(context.log, ""); 60 | context.nullType = scope.defineNativeType(context.log, "null"); 61 | context.voidType = scope.defineNativeType(context.log, "void"); 62 | 63 | this.context = context; 64 | this.global = global; 65 | } 66 | 67 | addInput(name: string, contents: string): Source { 68 | var source = new Source(); 69 | source.name = name; 70 | source.contents = contents; 71 | 72 | if (this.firstSource == null) this.firstSource = source; 73 | else this.lastSource.next = source; 74 | this.lastSource = source; 75 | 76 | return source; 77 | } 78 | 79 | finish(): bool { 80 | Profiler_begin(); 81 | 82 | var source = this.firstSource; 83 | while (source != null) { 84 | source.firstToken = tokenize(source, this.log); 85 | source = source.next; 86 | } 87 | 88 | Profiler_end("lexing"); 89 | Profiler_begin(); 90 | 91 | source = this.firstSource; 92 | while (source != null) { 93 | this.preprocessor.run(source, this.log); 94 | source = source.next; 95 | } 96 | 97 | Profiler_end("preprocessing"); 98 | Profiler_begin(); 99 | 100 | source = this.firstSource; 101 | while (source != null) { 102 | if (source.firstToken != null) { 103 | source.file = parse(source.firstToken, this.log); 104 | } 105 | source = source.next; 106 | } 107 | 108 | Profiler_end("parsing"); 109 | Profiler_begin(); 110 | 111 | var global = this.global; 112 | var context = this.context; 113 | var fullResolve = true; 114 | 115 | source = this.firstSource; 116 | while (source != null) { 117 | var file = source.file; 118 | 119 | if (file != null) { 120 | if (source == this.librarySource) { 121 | initialize(context, file, global.scope, CheckMode.INITIALIZE); 122 | resolve(context, file, global.scope); 123 | } else { 124 | initialize(context, file, global.scope, CheckMode.NORMAL); 125 | } 126 | 127 | while (file.firstChild != null) { 128 | var child = file.firstChild; 129 | child.remove(); 130 | global.appendChild(child); 131 | } 132 | } 133 | 134 | // Stop if the library code has errors because it's highly likely that everything is broken 135 | if (source == this.librarySource && this.log.hasErrors()) { 136 | fullResolve = false; 137 | break; 138 | } 139 | 140 | source = source.next; 141 | } 142 | 143 | if (fullResolve) { 144 | resolve(context, global, global.scope); 145 | } 146 | 147 | Profiler_end("checking"); 148 | 149 | if (this.log.hasErrors()) { 150 | return false; 151 | } 152 | 153 | Profiler_begin(); 154 | 155 | treeShaking(global); 156 | 157 | Profiler_end("shaking"); 158 | Profiler_begin(); 159 | 160 | if (this.target == CompileTarget.C) { 161 | cEmit(this); 162 | } 163 | 164 | else if (this.target == CompileTarget.JAVASCRIPT) { 165 | jsEmit(this); 166 | } 167 | 168 | else if (this.target == CompileTarget.WEBASSEMBLY) { 169 | wasmEmit(this); 170 | } 171 | 172 | Profiler_end("emitting"); 173 | 174 | return true; 175 | } 176 | } 177 | 178 | function replaceFileExtension(path: string, extension: string): string { 179 | var builder = StringBuilder_new(); 180 | var dot = path.lastIndexOf("."); 181 | var forward = path.lastIndexOf("/"); 182 | var backward = path.lastIndexOf("\\"); 183 | 184 | // Make sure that there's a non-empty file name that the dot is a part of 185 | if (dot > 0 && dot > forward && dot > backward) { 186 | path = path.slice(0, dot); 187 | } 188 | 189 | return builder.append(path).append(extension).finish(); 190 | } 191 | -------------------------------------------------------------------------------- /src/imports.thin: -------------------------------------------------------------------------------- 1 | declare function assert(truth: bool): void; 2 | declare function Profiler_begin(): void; 3 | declare function Profiler_end(text: string): void; 4 | 5 | function isPositivePowerOf2(value: int): bool { 6 | return value > 0 && (value & (value - 1)) == 0; 7 | } 8 | 9 | function alignToNextMultipleOf(offset: int, alignment: int): int { 10 | assert(isPositivePowerOf2(alignment)); 11 | return (offset + alignment - 1) & -alignment; 12 | } 13 | -------------------------------------------------------------------------------- /src/js.thin: -------------------------------------------------------------------------------- 1 | enum EmitBinary { 2 | NORMAL, 3 | CAST_TO_INT, 4 | } 5 | 6 | class JsResult { 7 | context: CheckContext; 8 | code: StringBuilder; 9 | indent: int; 10 | foundMultiply: bool; 11 | previousNode: Node; 12 | 13 | emitIndent(): void { 14 | var i = this.indent; 15 | while (i > 0) { 16 | this.code.append(" "); 17 | i = i - 1; 18 | } 19 | } 20 | 21 | emitNewlineBefore(node: Node): void { 22 | if (this.previousNode != null && (!isCompactNodeKind(this.previousNode.kind) || !isCompactNodeKind(node.kind))) { 23 | this.code.appendChar('\n'); 24 | } 25 | this.previousNode = null; 26 | } 27 | 28 | emitNewlineAfter(node: Node): void { 29 | this.previousNode = node; 30 | } 31 | 32 | emitStatements(node: Node): void { 33 | while (node != null) { 34 | this.emitStatement(node); 35 | node = node.nextSibling; 36 | } 37 | } 38 | 39 | emitBlock(node: Node): void { 40 | this.previousNode = null; 41 | this.code.append("{\n"); 42 | this.indent = this.indent + 1; 43 | this.emitStatements(node.firstChild); 44 | this.indent = this.indent - 1; 45 | this.emitIndent(); 46 | this.code.appendChar('}'); 47 | this.previousNode = null; 48 | } 49 | 50 | emitUnary(node: Node, parentPrecedence: Precedence, operator: string): void { 51 | var isPostfix = isUnaryPostfix(node.kind); 52 | var shouldCastToInt = node.kind == NodeKind.NEGATIVE && !jsKindCastsOperandsToInt(node.parent.kind); 53 | var isUnsigned = node.isUnsignedOperator(); 54 | var operatorPrecedence = shouldCastToInt ? isUnsigned ? Precedence.SHIFT : Precedence.BITWISE_OR : isPostfix ? Precedence.UNARY_POSTFIX : Precedence.UNARY_PREFIX; 55 | var code = this.code; 56 | 57 | if (parentPrecedence > operatorPrecedence) { 58 | code.appendChar('('); 59 | } 60 | 61 | if (!isPostfix) { 62 | code.append(operator); 63 | } 64 | 65 | this.emitExpression(node.unaryValue(), operatorPrecedence); 66 | 67 | if (isPostfix) { 68 | code.append(operator); 69 | } 70 | 71 | if (shouldCastToInt) { 72 | code.append(isUnsigned ? " >>> 0" : " | 0"); 73 | } 74 | 75 | if (parentPrecedence > operatorPrecedence) { 76 | code.appendChar(')'); 77 | } 78 | } 79 | 80 | emitBinary(node: Node, parentPrecedence: Precedence, operator: string, operatorPrecedence: Precedence, mode: EmitBinary): void { 81 | var isRightAssociative = node.kind == NodeKind.ASSIGN; 82 | var isUnsigned = node.isUnsignedOperator(); 83 | var code = this.code; 84 | 85 | // Avoid casting when the parent operator already does a cast 86 | var shouldCastToInt = mode == EmitBinary.CAST_TO_INT && (isUnsigned || !jsKindCastsOperandsToInt(node.parent.kind)); 87 | var selfPrecedence = shouldCastToInt ? isUnsigned ? Precedence.SHIFT : Precedence.BITWISE_OR : parentPrecedence; 88 | 89 | if (parentPrecedence > selfPrecedence) { 90 | code.appendChar('('); 91 | } 92 | 93 | if (selfPrecedence > operatorPrecedence) { 94 | code.appendChar('('); 95 | } 96 | 97 | this.emitExpression(node.binaryLeft(), isRightAssociative ? (operatorPrecedence as int + 1) as Precedence : operatorPrecedence); 98 | code.append(operator); 99 | this.emitExpression(node.binaryRight(), isRightAssociative ? operatorPrecedence : (operatorPrecedence as int + 1) as Precedence); 100 | 101 | if (selfPrecedence > operatorPrecedence) { 102 | code.appendChar(')'); 103 | } 104 | 105 | if (shouldCastToInt) { 106 | code.append(isUnsigned ? " >>> 0" : " | 0"); 107 | } 108 | 109 | if (parentPrecedence > selfPrecedence) { 110 | code.appendChar(')'); 111 | } 112 | } 113 | 114 | emitCommaSeparatedExpressions(start: Node, stop: Node): void { 115 | while (start != stop) { 116 | this.emitExpression(start, Precedence.LOWEST); 117 | start = start.nextSibling; 118 | 119 | if (start != stop) { 120 | this.code.append(", "); 121 | } 122 | } 123 | } 124 | 125 | emitExpression(node: Node, parentPrecedence: Precedence): void { 126 | var code = this.code; 127 | 128 | if (node.kind == NodeKind.NAME) { 129 | var symbol = node.symbol; 130 | if (symbol.kind == SymbolKind.FUNCTION_GLOBAL && symbol.node.isDeclare()) { 131 | code.append("__declare."); 132 | } 133 | this.emitSymbolName(symbol); 134 | } 135 | 136 | else if (node.kind == NodeKind.NULL) { 137 | code.append("null"); 138 | } 139 | 140 | else if (node.kind == NodeKind.BOOL) { 141 | code.append(node.intValue != 0 ? "true" : "false"); 142 | } 143 | 144 | else if (node.kind == NodeKind.INT) { 145 | if (parentPrecedence == Precedence.MEMBER) { 146 | code.appendChar('('); 147 | } 148 | 149 | code.append(node.resolvedType.isUnsigned() 150 | ? (node.intValue as uint).toString() 151 | : node.intValue.toString()); 152 | 153 | if (parentPrecedence == Precedence.MEMBER) { 154 | code.appendChar(')'); 155 | } 156 | } 157 | 158 | else if (node.kind == NodeKind.STRING) { 159 | StringBuilder_appendQuoted(code, node.stringValue); 160 | } 161 | 162 | else if (node.kind == NodeKind.CAST) { 163 | var context = this.context; 164 | var value = node.castValue(); 165 | var from = value.resolvedType.underlyingType(context); 166 | var type = node.resolvedType.underlyingType(context); 167 | var fromSize = from.variableSizeOf(context); 168 | var typeSize = type.variableSizeOf(context); 169 | 170 | // The cast isn't needed if it's to a wider integer type 171 | if (from == type || fromSize < typeSize) { 172 | this.emitExpression(value, parentPrecedence); 173 | } 174 | 175 | else { 176 | // Sign-extend 177 | if (type == context.sbyteType || type == context.shortType) { 178 | if (parentPrecedence > Precedence.SHIFT) { 179 | code.appendChar('('); 180 | } 181 | 182 | var shift = (32 - typeSize * 8).toString(); 183 | this.emitExpression(value, Precedence.SHIFT); 184 | code.append(" << "); 185 | code.append(shift); 186 | code.append(" >> "); 187 | code.append(shift); 188 | 189 | if (parentPrecedence > Precedence.SHIFT) { 190 | code.appendChar(')'); 191 | } 192 | } 193 | 194 | // Mask 195 | else if (type == context.byteType || type == context.ushortType) { 196 | if (parentPrecedence > Precedence.BITWISE_AND) { 197 | code.appendChar('('); 198 | } 199 | 200 | this.emitExpression(value, Precedence.BITWISE_AND); 201 | code.append(" & "); 202 | code.append(type.integerBitMask(context).toString()); 203 | 204 | if (parentPrecedence > Precedence.BITWISE_AND) { 205 | code.appendChar(')'); 206 | } 207 | } 208 | 209 | // Truncate signed 210 | else if (type == context.intType) { 211 | if (parentPrecedence > Precedence.BITWISE_OR) { 212 | code.appendChar('('); 213 | } 214 | 215 | this.emitExpression(value, Precedence.BITWISE_OR); 216 | code.append(" | 0"); 217 | 218 | if (parentPrecedence > Precedence.BITWISE_OR) { 219 | code.appendChar(')'); 220 | } 221 | } 222 | 223 | // Truncate unsigned 224 | else if (type == context.uintType) { 225 | if (parentPrecedence > Precedence.SHIFT) { 226 | code.appendChar('('); 227 | } 228 | 229 | this.emitExpression(value, Precedence.SHIFT); 230 | code.append(" >>> 0"); 231 | 232 | if (parentPrecedence > Precedence.SHIFT) { 233 | code.appendChar(')'); 234 | } 235 | } 236 | 237 | // No cast needed 238 | else { 239 | this.emitExpression(value, parentPrecedence); 240 | } 241 | } 242 | } 243 | 244 | else if (node.kind == NodeKind.DOT) { 245 | this.emitExpression(node.dotTarget(), Precedence.MEMBER); 246 | code.appendChar('.'); 247 | this.emitSymbolName(node.symbol); 248 | } 249 | 250 | else if (node.kind == NodeKind.HOOK) { 251 | if (parentPrecedence > Precedence.ASSIGN) { 252 | code.appendChar('('); 253 | } 254 | 255 | this.emitExpression(node.hookValue(), Precedence.LOGICAL_OR); 256 | code.append(" ? "); 257 | this.emitExpression(node.hookTrue(), Precedence.ASSIGN); 258 | code.append(" : "); 259 | this.emitExpression(node.hookFalse(), Precedence.ASSIGN); 260 | 261 | if (parentPrecedence > Precedence.ASSIGN) { 262 | code.appendChar(')'); 263 | } 264 | } 265 | 266 | else if (node.kind == NodeKind.INDEX) { 267 | var value = node.indexTarget(); 268 | this.emitExpression(value, Precedence.UNARY_POSTFIX); 269 | code.appendChar('['); 270 | this.emitCommaSeparatedExpressions(value.nextSibling, null); 271 | code.appendChar(']'); 272 | } 273 | 274 | else if (node.kind == NodeKind.CALL) { 275 | if (node.expandCallIntoOperatorTree()) { 276 | this.emitExpression(node, parentPrecedence); 277 | } 278 | 279 | else { 280 | var value = node.callValue(); 281 | this.emitExpression(value, Precedence.UNARY_POSTFIX); 282 | 283 | if (value.symbol == null || !value.symbol.isGetter()) { 284 | code.appendChar('('); 285 | this.emitCommaSeparatedExpressions(value.nextSibling, null); 286 | code.appendChar(')'); 287 | } 288 | } 289 | } 290 | 291 | else if (node.kind == NodeKind.NEW) { 292 | code.append("new "); 293 | this.emitExpression(node.newType(), Precedence.UNARY_POSTFIX); 294 | code.append("()"); 295 | } 296 | 297 | else if (node.kind == NodeKind.NOT) { 298 | var value = node.unaryValue(); 299 | 300 | // Automatically invert operators for readability 301 | value.expandCallIntoOperatorTree(); 302 | var invertedKind = invertedBinaryKind(value.kind); 303 | 304 | if (invertedKind != value.kind) { 305 | value.kind = invertedKind; 306 | this.emitExpression(value, parentPrecedence); 307 | } 308 | 309 | else { 310 | this.emitUnary(node, parentPrecedence, "!"); 311 | } 312 | } 313 | 314 | else if (node.kind == NodeKind.COMPLEMENT) this.emitUnary(node, parentPrecedence, "~"); 315 | else if (node.kind == NodeKind.NEGATIVE) this.emitUnary(node, parentPrecedence, "-"); 316 | else if (node.kind == NodeKind.POSITIVE) this.emitUnary(node, parentPrecedence, "+"); 317 | else if (node.kind == NodeKind.PREFIX_INCREMENT) this.emitUnary(node, parentPrecedence, "++"); 318 | else if (node.kind == NodeKind.PREFIX_DECREMENT) this.emitUnary(node, parentPrecedence, "--"); 319 | else if (node.kind == NodeKind.POSTFIX_INCREMENT) this.emitUnary(node, parentPrecedence, "++"); 320 | else if (node.kind == NodeKind.POSTFIX_DECREMENT) this.emitUnary(node, parentPrecedence, "--"); 321 | 322 | else if (node.kind == NodeKind.ADD) this.emitBinary(node, parentPrecedence, " + ", Precedence.ADD, EmitBinary.CAST_TO_INT); 323 | else if (node.kind == NodeKind.ASSIGN) this.emitBinary(node, parentPrecedence, " = ", Precedence.ASSIGN, EmitBinary.NORMAL); 324 | else if (node.kind == NodeKind.BITWISE_AND) this.emitBinary(node, parentPrecedence, " & ", Precedence.BITWISE_AND, EmitBinary.NORMAL); 325 | else if (node.kind == NodeKind.BITWISE_OR) this.emitBinary(node, parentPrecedence, " | ", Precedence.BITWISE_OR, EmitBinary.NORMAL); 326 | else if (node.kind == NodeKind.BITWISE_XOR) this.emitBinary(node, parentPrecedence, " ^ ", Precedence.BITWISE_XOR, EmitBinary.NORMAL); 327 | else if (node.kind == NodeKind.DIVIDE) this.emitBinary(node, parentPrecedence, " / ", Precedence.MULTIPLY, EmitBinary.CAST_TO_INT); 328 | else if (node.kind == NodeKind.EQUAL) this.emitBinary(node, parentPrecedence, " === ", Precedence.EQUAL, EmitBinary.NORMAL); 329 | else if (node.kind == NodeKind.GREATER_THAN) this.emitBinary(node, parentPrecedence, " > ", Precedence.COMPARE, EmitBinary.NORMAL); 330 | else if (node.kind == NodeKind.GREATER_THAN_EQUAL) this.emitBinary(node, parentPrecedence, " >= ", Precedence.COMPARE, EmitBinary.NORMAL); 331 | else if (node.kind == NodeKind.LESS_THAN) this.emitBinary(node, parentPrecedence, " < ", Precedence.COMPARE, EmitBinary.NORMAL); 332 | else if (node.kind == NodeKind.LESS_THAN_EQUAL) this.emitBinary(node, parentPrecedence, " <= ", Precedence.COMPARE, EmitBinary.NORMAL); 333 | else if (node.kind == NodeKind.LOGICAL_AND) this.emitBinary(node, parentPrecedence, " && ", Precedence.LOGICAL_AND, EmitBinary.NORMAL); 334 | else if (node.kind == NodeKind.LOGICAL_OR) this.emitBinary(node, parentPrecedence, " || ", Precedence.LOGICAL_OR, EmitBinary.NORMAL); 335 | else if (node.kind == NodeKind.NOT_EQUAL) this.emitBinary(node, parentPrecedence, " !== ", Precedence.EQUAL, EmitBinary.NORMAL); 336 | else if (node.kind == NodeKind.REMAINDER) this.emitBinary(node, parentPrecedence, " % ", Precedence.MULTIPLY, EmitBinary.CAST_TO_INT); 337 | else if (node.kind == NodeKind.SHIFT_LEFT) this.emitBinary(node, parentPrecedence, " << ", Precedence.SHIFT, EmitBinary.NORMAL); 338 | else if (node.kind == NodeKind.SHIFT_RIGHT) this.emitBinary(node, parentPrecedence, node.isUnsignedOperator() ? " >>> " : " >> ", Precedence.SHIFT, EmitBinary.NORMAL); 339 | else if (node.kind == NodeKind.SUBTRACT) this.emitBinary(node, parentPrecedence, " - ", Precedence.ADD, EmitBinary.CAST_TO_INT); 340 | 341 | else if (node.kind == NodeKind.MULTIPLY) { 342 | var left = node.binaryLeft(); 343 | var right = node.binaryRight(); 344 | var isUnsigned = node.isUnsignedOperator(); 345 | 346 | if (isUnsigned && parentPrecedence > Precedence.SHIFT) { 347 | code.appendChar('('); 348 | } 349 | 350 | code.append("__imul("); 351 | this.emitExpression(left, Precedence.LOWEST); 352 | code.append(", "); 353 | this.emitExpression(right, Precedence.LOWEST); 354 | code.appendChar(')'); 355 | this.foundMultiply = true; 356 | 357 | if (isUnsigned) { 358 | code.append(" >>> 0"); 359 | 360 | if (parentPrecedence > Precedence.SHIFT) { 361 | code.appendChar(')'); 362 | } 363 | } 364 | } 365 | 366 | else { 367 | assert(false); 368 | } 369 | } 370 | 371 | emitSymbolName(symbol: Symbol): void { 372 | this.code.append(symbol.rename != null ? symbol.rename : symbol.name); 373 | } 374 | 375 | emitStatement(node: Node): void { 376 | var code = this.code; 377 | 378 | if (node.kind == NodeKind.FUNCTION) { 379 | var body = node.functionBody(); 380 | if (body == null) { 381 | return; 382 | } 383 | 384 | var symbol = node.symbol; 385 | var needsSemicolon = false; 386 | this.emitNewlineBefore(node); 387 | this.emitIndent(); 388 | 389 | if (symbol.kind == SymbolKind.FUNCTION_INSTANCE) { 390 | this.emitSymbolName(symbol.parent()); 391 | code.append(".prototype."); 392 | this.emitSymbolName(symbol); 393 | code.append(" = function"); 394 | needsSemicolon = true; 395 | } 396 | 397 | else if (node.isExtern()) { 398 | code.append("var "); 399 | this.emitSymbolName(symbol); 400 | code.append(" = __extern."); 401 | this.emitSymbolName(symbol); 402 | code.append(" = function"); 403 | needsSemicolon = true; 404 | } 405 | 406 | else { 407 | code.append("function "); 408 | this.emitSymbolName(symbol); 409 | } 410 | 411 | code.appendChar('('); 412 | 413 | var returnType = node.functionReturnType(); 414 | var child = node.functionFirstArgumentIgnoringThis(); 415 | 416 | while (child != returnType) { 417 | assert(child.kind == NodeKind.VARIABLE); 418 | this.emitSymbolName(child.symbol); 419 | child = child.nextSibling; 420 | if (child != returnType) { 421 | code.append(", "); 422 | } 423 | } 424 | 425 | code.append(") "); 426 | this.emitBlock(node.functionBody()); 427 | code.append(needsSemicolon ? ";\n" : "\n"); 428 | this.emitNewlineAfter(node); 429 | } 430 | 431 | else if (node.kind == NodeKind.IF) { 432 | this.emitNewlineBefore(node); 433 | this.emitIndent(); 434 | while (true) { 435 | code.append("if ("); 436 | this.emitExpression(node.ifValue(), Precedence.LOWEST); 437 | code.append(") "); 438 | this.emitBlock(node.ifTrue()); 439 | var no = node.ifFalse(); 440 | if (no == null) { 441 | code.appendChar('\n'); 442 | break; 443 | } 444 | code.append("\n\n"); 445 | this.emitIndent(); 446 | code.append("else "); 447 | if (no.firstChild == null || no.firstChild != no.lastChild || no.firstChild.kind != NodeKind.IF) { 448 | this.emitBlock(no); 449 | code.appendChar('\n'); 450 | break; 451 | } 452 | node = no.firstChild; 453 | } 454 | this.emitNewlineAfter(node); 455 | } 456 | 457 | else if (node.kind == NodeKind.WHILE) { 458 | this.emitNewlineBefore(node); 459 | this.emitIndent(); 460 | code.append("while ("); 461 | this.emitExpression(node.whileValue(), Precedence.LOWEST); 462 | code.append(") "); 463 | this.emitBlock(node.whileBody()); 464 | code.appendChar('\n'); 465 | this.emitNewlineAfter(node); 466 | } 467 | 468 | else if (node.kind == NodeKind.BREAK) { 469 | this.emitNewlineBefore(node); 470 | this.emitIndent(); 471 | code.append("break;\n"); 472 | this.emitNewlineAfter(node); 473 | } 474 | 475 | else if (node.kind == NodeKind.CONTINUE) { 476 | this.emitNewlineBefore(node); 477 | this.emitIndent(); 478 | code.append("continue;\n"); 479 | this.emitNewlineAfter(node); 480 | } 481 | 482 | else if (node.kind == NodeKind.EXPRESSION) { 483 | this.emitNewlineBefore(node); 484 | this.emitIndent(); 485 | this.emitExpression(node.expressionValue(), Precedence.LOWEST); 486 | code.append(";\n"); 487 | this.emitNewlineAfter(node); 488 | } 489 | 490 | else if (node.kind == NodeKind.EMPTY) { 491 | } 492 | 493 | else if (node.kind == NodeKind.RETURN) { 494 | var value = node.returnValue(); 495 | this.emitNewlineBefore(node); 496 | this.emitIndent(); 497 | if (value != null) { 498 | code.append("return "); 499 | this.emitExpression(value, Precedence.LOWEST); 500 | code.append(";\n"); 501 | } else { 502 | code.append("return;\n"); 503 | } 504 | this.emitNewlineAfter(node); 505 | } 506 | 507 | else if (node.kind == NodeKind.BLOCK) { 508 | if (node.parent.kind == NodeKind.BLOCK) { 509 | this.emitStatements(node.firstChild); 510 | } else { 511 | this.emitNewlineBefore(node); 512 | this.emitIndent(); 513 | this.emitBlock(node); 514 | code.appendChar('\n'); 515 | this.emitNewlineAfter(node); 516 | } 517 | } 518 | 519 | else if (node.kind == NodeKind.VARIABLES) { 520 | this.emitNewlineBefore(node); 521 | this.emitIndent(); 522 | code.append("var "); 523 | var child = node.firstChild; 524 | 525 | while (child != null) { 526 | var value = child.variableValue(); 527 | this.emitSymbolName(child.symbol); 528 | child = child.nextSibling; 529 | if (child != null) { 530 | code.append(", "); 531 | } 532 | assert(value != null); 533 | code.append(" = "); 534 | this.emitExpression(value, Precedence.LOWEST); 535 | } 536 | 537 | code.append(";\n"); 538 | this.emitNewlineAfter(node); 539 | } 540 | 541 | else if (node.kind == NodeKind.CLASS) { 542 | // Emit constructor 543 | if (!node.isDeclare()) { 544 | this.emitNewlineBefore(node); 545 | this.emitIndent(); 546 | code.append("function "); 547 | this.emitSymbolName(node.symbol); 548 | code.append("() {\n"); 549 | this.indent = this.indent + 1; 550 | 551 | var argument = node.firstChild; 552 | while (argument != null) { 553 | if (argument.kind == NodeKind.VARIABLE) { 554 | this.emitIndent(); 555 | code.append("this."); 556 | this.emitSymbolName(argument.symbol); 557 | code.append(" = "); 558 | this.emitExpression(argument.variableValue(), Precedence.LOWEST); 559 | code.append(";\n"); 560 | } 561 | argument = argument.nextSibling; 562 | } 563 | 564 | this.indent = this.indent - 1; 565 | this.emitIndent(); 566 | code.append("}\n"); 567 | this.emitNewlineAfter(node); 568 | } 569 | 570 | // Emit instance functions 571 | var child = node.firstChild; 572 | while (child != null) { 573 | if (child.kind == NodeKind.FUNCTION) { 574 | this.emitStatement(child); 575 | } 576 | child = child.nextSibling; 577 | } 578 | } 579 | 580 | else if (node.kind == NodeKind.ENUM) { 581 | if (node.isExtern()) { 582 | this.emitNewlineBefore(node); 583 | this.emitIndent(); 584 | code.append("__extern."); 585 | this.emitSymbolName(node.symbol); 586 | code.append(" = {\n"); 587 | this.indent = this.indent + 1; 588 | 589 | // Emit enum values 590 | var child = node.firstChild; 591 | while (child != null) { 592 | assert(child.kind == NodeKind.VARIABLE); 593 | this.emitIndent(); 594 | this.emitSymbolName(child.symbol); 595 | code.append(": "); 596 | code.append(child.symbol.offset.toString()); 597 | child = child.nextSibling; 598 | code.append(child != null ? ",\n" : "\n"); 599 | } 600 | 601 | this.indent = this.indent - 1; 602 | this.emitIndent(); 603 | code.append("};\n"); 604 | this.emitNewlineAfter(node); 605 | } 606 | } 607 | 608 | else if (node.kind == NodeKind.CONSTANTS) { 609 | } 610 | 611 | else { 612 | assert(false); 613 | } 614 | } 615 | } 616 | 617 | function jsKindCastsOperandsToInt(kind: NodeKind): bool { 618 | return 619 | kind == NodeKind.SHIFT_LEFT || kind == NodeKind.SHIFT_RIGHT || 620 | kind == NodeKind.BITWISE_OR || kind == NodeKind.BITWISE_AND || kind == NodeKind.BITWISE_XOR; 621 | } 622 | 623 | function jsEmit(compiler: Compiler): void { 624 | var code = StringBuilder_new(); 625 | var result = new JsResult(); 626 | result.context = compiler.context; 627 | result.code = code; 628 | 629 | code.append("(function(__declare, __extern) {\n"); 630 | result.indent = 1; 631 | result.emitStatements(compiler.global.firstChild); 632 | 633 | if (result.foundMultiply) { 634 | code.appendChar('\n'); 635 | result.emitIndent(); 636 | code.append("var __imul = Math.imul || function(a, b) {\n"); 637 | result.indent = 2; 638 | result.emitIndent(); 639 | code.append("return (a * (b >>> 16) << 16) + a * (b & 65535) | 0;\n"); 640 | result.indent = 1; 641 | result.emitIndent(); 642 | code.append("};\n"); 643 | } 644 | 645 | code.append("}(\n"); 646 | result.emitIndent(); 647 | code.append("typeof global !== 'undefined' ? global : this,\n"); 648 | result.emitIndent(); 649 | code.append("typeof exports !== 'undefined' ? exports : this\n"); 650 | code.append("));\n"); 651 | 652 | compiler.outputJS = code.finish(); 653 | } 654 | -------------------------------------------------------------------------------- /src/lexer.thin: -------------------------------------------------------------------------------- 1 | enum TokenKind { 2 | END_OF_FILE, 3 | 4 | // Literals 5 | CHARACTER, 6 | IDENTIFIER, 7 | INT, 8 | STRING, 9 | 10 | // Punctuation 11 | ASSIGN, 12 | BITWISE_AND, 13 | BITWISE_OR, 14 | BITWISE_XOR, 15 | COLON, 16 | COMMA, 17 | COMPLEMENT, 18 | DIVIDE, 19 | DOT, 20 | EQUAL, 21 | EXPONENT, 22 | GREATER_THAN, 23 | GREATER_THAN_EQUAL, 24 | LEFT_BRACE, 25 | LEFT_BRACKET, 26 | LEFT_PARENTHESIS, 27 | LESS_THAN, 28 | LESS_THAN_EQUAL, 29 | LOGICAL_AND, 30 | LOGICAL_OR, 31 | MINUS, 32 | MINUS_MINUS, 33 | MULTIPLY, 34 | NOT, 35 | NOT_EQUAL, 36 | PLUS, 37 | PLUS_PLUS, 38 | QUESTION_MARK, 39 | REMAINDER, 40 | RIGHT_BRACE, 41 | RIGHT_BRACKET, 42 | RIGHT_PARENTHESIS, 43 | SEMICOLON, 44 | SHIFT_LEFT, 45 | SHIFT_RIGHT, 46 | 47 | // Keywords 48 | ALIGNOF, 49 | AS, 50 | BREAK, 51 | CLASS, 52 | CONST, 53 | CONTINUE, 54 | DECLARE, 55 | ELSE, 56 | ENUM, 57 | EXPORT, 58 | EXTENDS, 59 | EXTERN, 60 | FALSE, 61 | FUNCTION, 62 | IF, 63 | IMPLEMENTS, 64 | IMPORT, 65 | INTERFACE, 66 | LET, 67 | NEW, 68 | NULL, 69 | OPERATOR, 70 | PRIVATE, 71 | PROTECTED, 72 | PUBLIC, 73 | RETURN, 74 | SIZEOF, 75 | STATIC, 76 | THIS, 77 | TRUE, 78 | UNSAFE, 79 | VAR, 80 | WHILE, 81 | 82 | // Preprocessor 83 | PREPROCESSOR_DEFINE, 84 | PREPROCESSOR_ELIF, 85 | PREPROCESSOR_ELSE, 86 | PREPROCESSOR_ENDIF, 87 | PREPROCESSOR_ERROR, 88 | PREPROCESSOR_IF, 89 | PREPROCESSOR_NEEDED, 90 | PREPROCESSOR_NEWLINE, 91 | PREPROCESSOR_UNDEF, 92 | PREPROCESSOR_WARNING, 93 | } 94 | 95 | function isKeyword(kind: TokenKind): bool { 96 | return kind >= TokenKind.ALIGNOF && kind <= TokenKind.WHILE; 97 | } 98 | 99 | class Token { 100 | kind: TokenKind; 101 | range: Range; 102 | next: Token; 103 | } 104 | 105 | function splitToken(first: Token, firstKind: TokenKind, secondKind: TokenKind): void { 106 | var range = first.range; 107 | assert(range.end - range.start >= 2); 108 | 109 | var second = new Token(); 110 | second.kind = secondKind; 111 | second.range = createRange(range.source, range.start + 1, range.end); 112 | second.next = first.next; 113 | 114 | first.kind = firstKind; 115 | first.next = second; 116 | range.end = range.start + 1; 117 | } 118 | 119 | function tokenToString(token: TokenKind): string { 120 | if (token == TokenKind.END_OF_FILE) return "end of file"; 121 | 122 | // Literals 123 | if (token == TokenKind.CHARACTER) return "character literal"; 124 | if (token == TokenKind.IDENTIFIER) return "identifier"; 125 | if (token == TokenKind.INT) return "integer literal"; 126 | if (token == TokenKind.STRING) return "string literal"; 127 | 128 | // Punctuation 129 | if (token == TokenKind.ASSIGN) return "'='"; 130 | if (token == TokenKind.BITWISE_AND) return "'&'"; 131 | if (token == TokenKind.BITWISE_OR) return "'|'"; 132 | if (token == TokenKind.BITWISE_XOR) return "'^'"; 133 | if (token == TokenKind.COLON) return "':'"; 134 | if (token == TokenKind.COMMA) return "','"; 135 | if (token == TokenKind.COMPLEMENT) return "'~'"; 136 | if (token == TokenKind.DIVIDE) return "'/'"; 137 | if (token == TokenKind.DOT) return "'.'"; 138 | if (token == TokenKind.EQUAL) return "'=='"; 139 | if (token == TokenKind.EXPONENT) return "'**'"; 140 | if (token == TokenKind.GREATER_THAN) return "'>'"; 141 | if (token == TokenKind.GREATER_THAN_EQUAL) return "'>='"; 142 | if (token == TokenKind.LEFT_BRACE) return "'{'"; 143 | if (token == TokenKind.LEFT_BRACKET) return "'['"; 144 | if (token == TokenKind.LEFT_PARENTHESIS) return "'('"; 145 | if (token == TokenKind.LESS_THAN) return "'<'"; 146 | if (token == TokenKind.LESS_THAN_EQUAL) return "'<='"; 147 | if (token == TokenKind.LOGICAL_AND) return "'&&'"; 148 | if (token == TokenKind.LOGICAL_OR) return "'||'"; 149 | if (token == TokenKind.MINUS) return "'-'"; 150 | if (token == TokenKind.MINUS_MINUS) return "'--'"; 151 | if (token == TokenKind.MULTIPLY) return "'*'"; 152 | if (token == TokenKind.NOT) return "'!'"; 153 | if (token == TokenKind.NOT_EQUAL) return "'!='"; 154 | if (token == TokenKind.PLUS) return "'+'"; 155 | if (token == TokenKind.PLUS_PLUS) return "'++'"; 156 | if (token == TokenKind.QUESTION_MARK) return "'?'"; 157 | if (token == TokenKind.REMAINDER) return "'%'"; 158 | if (token == TokenKind.RIGHT_BRACE) return "'}'"; 159 | if (token == TokenKind.RIGHT_BRACKET) return "']'"; 160 | if (token == TokenKind.RIGHT_PARENTHESIS) return "')'"; 161 | if (token == TokenKind.SEMICOLON) return "';'"; 162 | if (token == TokenKind.SHIFT_LEFT) return "'<<'"; 163 | if (token == TokenKind.SHIFT_RIGHT) return "'>>'"; 164 | 165 | // Keywords 166 | if (token == TokenKind.ALIGNOF) return "'alignof'"; 167 | if (token == TokenKind.AS) return "'as'"; 168 | if (token == TokenKind.BREAK) return "'break'"; 169 | if (token == TokenKind.CLASS) return "'class'"; 170 | if (token == TokenKind.CONST) return "'const'"; 171 | if (token == TokenKind.CONTINUE) return "'continue'"; 172 | if (token == TokenKind.DECLARE) return "'declare'"; 173 | if (token == TokenKind.ELSE) return "'else'"; 174 | if (token == TokenKind.ENUM) return "'enum'"; 175 | if (token == TokenKind.EXPORT) return "'export'"; 176 | if (token == TokenKind.EXTENDS) return "'extends'"; 177 | if (token == TokenKind.EXTERN) return "'extern'"; 178 | if (token == TokenKind.FALSE) return "'false'"; 179 | if (token == TokenKind.FUNCTION) return "'function'"; 180 | if (token == TokenKind.IF) return "'if'"; 181 | if (token == TokenKind.IMPLEMENTS) return "'implements'"; 182 | if (token == TokenKind.IMPORT) return "'import'"; 183 | if (token == TokenKind.INTERFACE) return "'interface'"; 184 | if (token == TokenKind.LET) return "'let'"; 185 | if (token == TokenKind.NEW) return "'new'"; 186 | if (token == TokenKind.NULL) return "'null'"; 187 | if (token == TokenKind.OPERATOR) return "'operator'"; 188 | if (token == TokenKind.PRIVATE) return "'private'"; 189 | if (token == TokenKind.PROTECTED) return "'protected'"; 190 | if (token == TokenKind.PUBLIC) return "'public'"; 191 | if (token == TokenKind.RETURN) return "'return'"; 192 | if (token == TokenKind.SIZEOF) return "'sizeof'"; 193 | if (token == TokenKind.STATIC) return "'static'"; 194 | if (token == TokenKind.THIS) return "'this'"; 195 | if (token == TokenKind.TRUE) return "'true'"; 196 | if (token == TokenKind.UNSAFE) return "'unsafe'"; 197 | if (token == TokenKind.VAR) return "'var'"; 198 | if (token == TokenKind.WHILE) return "'while'"; 199 | 200 | // Preprocessor 201 | if (token == TokenKind.PREPROCESSOR_DEFINE) return "'#define'"; 202 | if (token == TokenKind.PREPROCESSOR_ELIF) return "'#elif'"; 203 | if (token == TokenKind.PREPROCESSOR_ELSE) return "'#else'"; 204 | if (token == TokenKind.PREPROCESSOR_ENDIF) return "'#endif'"; 205 | if (token == TokenKind.PREPROCESSOR_ERROR) return "'#error'"; 206 | if (token == TokenKind.PREPROCESSOR_IF) return "'#if'"; 207 | if (token == TokenKind.PREPROCESSOR_NEWLINE) return "newline"; 208 | if (token == TokenKind.PREPROCESSOR_UNDEF) return "'#undef'"; 209 | if (token == TokenKind.PREPROCESSOR_WARNING) return "'#warning'"; 210 | 211 | assert(false); 212 | return null; 213 | } 214 | 215 | function isAlpha(c: ushort): bool { 216 | return 217 | c >= 'a' && c <= 'z' || 218 | c >= 'A' && c <= 'Z' || 219 | c == '_'; 220 | } 221 | 222 | function isASCII(c: ushort): bool { 223 | return c >= 0x20 && c <= 0x7E; 224 | } 225 | 226 | function isNumber(c: ushort): bool { 227 | return c >= '0' && c <= '9'; 228 | } 229 | 230 | function isDigit(c: ushort, base: byte): bool { 231 | if (base == 16) { 232 | return isNumber(c) || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f'; 233 | } 234 | return c >= '0' && c < '0' + base; 235 | } 236 | 237 | function tokenize(source: Source, log: Log): Token { 238 | var first: Token = null; 239 | var last: Token = null; 240 | var contents = source.contents; 241 | var limit = contents.length; 242 | var needsPreprocessor = false; 243 | var wantNewline = false; 244 | var i = 0; 245 | 246 | while (i < limit) { 247 | var start = i; 248 | var c = contents[i]; 249 | i = i + 1; 250 | 251 | if (c == ' ' || c == '\t' || c == '\r') { 252 | continue; 253 | } 254 | 255 | var kind = TokenKind.END_OF_FILE; 256 | 257 | // Newline 258 | if (c == '\n') { 259 | if (!wantNewline) { 260 | continue; 261 | } 262 | 263 | // Preprocessor commands all end in a newline 264 | kind = TokenKind.PREPROCESSOR_NEWLINE; 265 | wantNewline = false; 266 | } 267 | 268 | // Identifier 269 | else if (isAlpha(c)) { 270 | kind = TokenKind.IDENTIFIER; 271 | 272 | while (i < limit && (isAlpha(contents[i]) || isNumber(contents[i]))) { 273 | i = i + 1; 274 | } 275 | 276 | // Keywords 277 | var length = i - start; 278 | if (length >= 2 && length <= 10) { 279 | var text = contents.slice(start, i); 280 | 281 | if (length == 2) { 282 | if (text == "as") kind = TokenKind.AS; else 283 | if (text == "if") kind = TokenKind.IF; 284 | } 285 | 286 | else if (length == 3) { 287 | if (text == "let") kind = TokenKind.LET; else 288 | if (text == "new") kind = TokenKind.NEW; else 289 | if (text == "var") kind = TokenKind.VAR; 290 | } 291 | 292 | else if (length == 4) { 293 | if (text == "else") kind = TokenKind.ELSE; else 294 | if (text == "enum") kind = TokenKind.ENUM; else 295 | if (text == "null") kind = TokenKind.NULL; else 296 | if (text == "this") kind = TokenKind.THIS; else 297 | if (text == "true") kind = TokenKind.TRUE; 298 | } 299 | 300 | else if (length == 5) { 301 | if (text == "break") kind = TokenKind.BREAK; else 302 | if (text == "class") kind = TokenKind.CLASS; else 303 | if (text == "const") kind = TokenKind.CONST; else 304 | if (text == "false") kind = TokenKind.FALSE; else 305 | if (text == "while") kind = TokenKind.WHILE; 306 | } 307 | 308 | else if (length == 6) { 309 | if (text == "export") kind = TokenKind.EXPORT; else 310 | if (text == "extern") kind = TokenKind.EXTERN; else 311 | if (text == "import") kind = TokenKind.IMPORT; else 312 | if (text == "public") kind = TokenKind.PUBLIC; else 313 | if (text == "return") kind = TokenKind.RETURN; else 314 | if (text == "sizeof") kind = TokenKind.SIZEOF; else 315 | if (text == "static") kind = TokenKind.STATIC; else 316 | if (text == "unsafe") kind = TokenKind.UNSAFE; 317 | } 318 | 319 | else if (length == 7) { 320 | if (text == "alignof") kind = TokenKind.ALIGNOF; else 321 | if (text == "declare") kind = TokenKind.DECLARE; else 322 | if (text == "extends") kind = TokenKind.EXTENDS; else 323 | if (text == "private") kind = TokenKind.PRIVATE; 324 | } 325 | 326 | else { 327 | if (text == "continue") kind = TokenKind.CONTINUE; else 328 | if (text == "function") kind = TokenKind.FUNCTION; else 329 | if (text == "implements") kind = TokenKind.IMPLEMENTS; else 330 | if (text == "interface") kind = TokenKind.INTERFACE; else 331 | if (text == "protected") kind = TokenKind.PROTECTED; 332 | } 333 | } 334 | } 335 | 336 | // Integer 337 | else if (isNumber(c)) { 338 | kind = TokenKind.INT; 339 | 340 | if (i < limit) { 341 | var next = contents[i]; 342 | var base: byte = 10; 343 | 344 | // Handle binary, octal, and hexadecimal prefixes 345 | if (c == '0' && i + 1 < limit) { 346 | if (next == 'b' || next == 'B') base = 2; 347 | else if (next == 'o' || next == 'O') base = 8; 348 | else if (next == 'x' || next == 'X') base = 16; 349 | if (base != 10) { 350 | if (isDigit(contents[i + 1], base)) i = i + 2; 351 | else base = 10; 352 | } 353 | } 354 | 355 | // Scan the payload 356 | while (i < limit && isDigit(contents[i], base)) { 357 | i = i + 1; 358 | } 359 | 360 | // Extra letters after the end is an error 361 | if (i < limit && (isAlpha(contents[i]) || isNumber(contents[i]))) { 362 | i = i + 1; 363 | 364 | while (i < limit && (isAlpha(contents[i]) || isNumber(contents[i]))) { 365 | i = i + 1; 366 | } 367 | 368 | log.error(createRange(source, start, i), StringBuilder_new() 369 | .append("Invalid integer literal: '") 370 | .appendSlice(contents, start, i) 371 | .appendChar('\'') 372 | .finish()); 373 | return null; 374 | } 375 | } 376 | } 377 | 378 | // Character or string 379 | else if (c == '"' || c == '\'' || c == '`') { 380 | while (i < limit) { 381 | var next = contents[i]; 382 | 383 | // Escape any character including newlines 384 | if (i + 1 < limit && next == '\\') { 385 | i = i + 2; 386 | } 387 | 388 | // Only allow newlines in template literals 389 | else if (next == '\n' && c != '`') { 390 | break; 391 | } 392 | 393 | // Handle a normal character 394 | else { 395 | i = i + 1; 396 | 397 | // End the string with a matching quote character 398 | if (next == c) { 399 | kind = c == '\'' ? TokenKind.CHARACTER : TokenKind.STRING; 400 | break; 401 | } 402 | } 403 | } 404 | 405 | // It's an error if we didn't find a matching quote character 406 | if (kind == TokenKind.END_OF_FILE) { 407 | log.error(createRange(source, start, i), 408 | c == '\'' ? "Unterminated character literal" : 409 | c == '`' ? "Unterminated template literal" : 410 | "Unterminated string literal"); 411 | return null; 412 | } 413 | } 414 | 415 | // Operators 416 | else if (c == '%') kind = TokenKind.REMAINDER; 417 | else if (c == '(') kind = TokenKind.LEFT_PARENTHESIS; 418 | else if (c == ')') kind = TokenKind.RIGHT_PARENTHESIS; 419 | else if (c == ',') kind = TokenKind.COMMA; 420 | else if (c == '.') kind = TokenKind.DOT; 421 | else if (c == ':') kind = TokenKind.COLON; 422 | else if (c == ';') kind = TokenKind.SEMICOLON; 423 | else if (c == '?') kind = TokenKind.QUESTION_MARK; 424 | else if (c == '[') kind = TokenKind.LEFT_BRACKET; 425 | else if (c == ']') kind = TokenKind.RIGHT_BRACKET; 426 | else if (c == '^') kind = TokenKind.BITWISE_XOR; 427 | else if (c == '{') kind = TokenKind.LEFT_BRACE; 428 | else if (c == '}') kind = TokenKind.RIGHT_BRACE; 429 | else if (c == '~') kind = TokenKind.COMPLEMENT; 430 | 431 | // * or ** 432 | else if (c == '*') { 433 | kind = TokenKind.MULTIPLY; 434 | 435 | if (i < limit && contents[i] == '*') { 436 | kind = TokenKind.EXPONENT; 437 | i = i + 1; 438 | } 439 | } 440 | 441 | // / or // or /* 442 | else if (c == '/') { 443 | kind = TokenKind.DIVIDE; 444 | 445 | // Single-line comments 446 | if (i < limit && contents[i] == '/') { 447 | i = i + 1; 448 | 449 | while (i < limit && contents[i] != '\n') { 450 | i = i + 1; 451 | } 452 | 453 | continue; 454 | } 455 | 456 | // Multi-line comments 457 | if (i < limit && contents[i] == '*') { 458 | i = i + 1; 459 | var foundEnd = false; 460 | 461 | while (i < limit) { 462 | var next = contents[i]; 463 | 464 | if (next == '*' && i + 1 < limit && contents[i + 1] == '/') { 465 | foundEnd = true; 466 | i = i + 2; 467 | break; 468 | } 469 | 470 | i = i + 1; 471 | } 472 | 473 | if (!foundEnd) { 474 | log.error(createRange(source, start, start + 2), "Unterminated multi-line comment"); 475 | return null; 476 | } 477 | 478 | continue; 479 | } 480 | } 481 | 482 | // ! or != 483 | else if (c == '!') { 484 | kind = TokenKind.NOT; 485 | 486 | if (i < limit && contents[i] == '=') { 487 | kind = TokenKind.NOT_EQUAL; 488 | i = i + 1; 489 | 490 | // Recover from !== 491 | if (i < limit && contents[i] == '=') { 492 | i = i + 1; 493 | log.error(createRange(source, start, i), "Use '!=' instead of '!=='"); 494 | } 495 | } 496 | } 497 | 498 | // = or == 499 | else if (c == '=') { 500 | kind = TokenKind.ASSIGN; 501 | 502 | if (i < limit && contents[i] == '=') { 503 | kind = TokenKind.EQUAL; 504 | i = i + 1; 505 | 506 | // Recover from === 507 | if (i < limit && contents[i] == '=') { 508 | i = i + 1; 509 | log.error(createRange(source, start, i), "Use '==' instead of '==='"); 510 | } 511 | } 512 | } 513 | 514 | // + or ++ 515 | else if (c == '+') { 516 | kind = TokenKind.PLUS; 517 | 518 | if (i < limit && contents[i] == '+') { 519 | kind = TokenKind.PLUS_PLUS; 520 | i = i + 1; 521 | } 522 | } 523 | 524 | // - or -- 525 | else if (c == '-') { 526 | kind = TokenKind.MINUS; 527 | 528 | if (i < limit && contents[i] == '-') { 529 | kind = TokenKind.MINUS_MINUS; 530 | i = i + 1; 531 | } 532 | } 533 | 534 | // & or && 535 | else if (c == '&') { 536 | kind = TokenKind.BITWISE_AND; 537 | 538 | if (i < limit && contents[i] == '&') { 539 | kind = TokenKind.LOGICAL_AND; 540 | i = i + 1; 541 | } 542 | } 543 | 544 | // | or || 545 | else if (c == '|') { 546 | kind = TokenKind.BITWISE_OR; 547 | 548 | if (i < limit && contents[i] == '|') { 549 | kind = TokenKind.LOGICAL_OR; 550 | i = i + 1; 551 | } 552 | } 553 | 554 | // < or << or <= 555 | else if (c == '<') { 556 | kind = TokenKind.LESS_THAN; 557 | 558 | if (i < limit) { 559 | c = contents[i]; 560 | 561 | if (c == '<') { 562 | kind = TokenKind.SHIFT_LEFT; 563 | i = i + 1; 564 | } 565 | 566 | else if (c == '=') { 567 | kind = TokenKind.LESS_THAN_EQUAL; 568 | i = i + 1; 569 | } 570 | } 571 | } 572 | 573 | // > or >> or >= 574 | else if (c == '>') { 575 | kind = TokenKind.GREATER_THAN; 576 | 577 | if (i < limit) { 578 | c = contents[i]; 579 | 580 | if (c == '>') { 581 | kind = TokenKind.SHIFT_RIGHT; 582 | i = i + 1; 583 | } 584 | 585 | else if (c == '=') { 586 | kind = TokenKind.GREATER_THAN_EQUAL; 587 | i = i + 1; 588 | } 589 | } 590 | } 591 | 592 | else if (c == '#') { 593 | while (i < limit && (isAlpha(contents[i]) || isNumber(contents[i]))) { 594 | i = i + 1; 595 | } 596 | 597 | var text = contents.slice(start, i); 598 | 599 | if (text == "#define") kind = TokenKind.PREPROCESSOR_DEFINE; 600 | else if (text == "#elif") kind = TokenKind.PREPROCESSOR_ELIF; 601 | else if (text == "#else") kind = TokenKind.PREPROCESSOR_ELSE; 602 | else if (text == "#endif") kind = TokenKind.PREPROCESSOR_ENDIF; 603 | else if (text == "#error") kind = TokenKind.PREPROCESSOR_ERROR; 604 | else if (text == "#if") kind = TokenKind.PREPROCESSOR_IF; 605 | else if (text == "#undef") kind = TokenKind.PREPROCESSOR_UNDEF; 606 | else if (text == "#warning") kind = TokenKind.PREPROCESSOR_WARNING; 607 | 608 | // Allow a shebang at the start of the file 609 | else if (start == 0 && text == "#" && i < limit && contents[i] == '!') { 610 | while (i < limit && contents[i] != '\n') { 611 | i = i + 1; 612 | } 613 | continue; 614 | } 615 | 616 | else { 617 | var builder = StringBuilder_new().append("Invalid preprocessor token '").append(text).appendChar('\''); 618 | 619 | // Check for #if typos 620 | if (text == "#ifdef") { 621 | builder.append(", did you mean '#if'?"); 622 | kind = TokenKind.PREPROCESSOR_IF; 623 | } 624 | 625 | // Check for #elif typos 626 | else if (text == "#elsif" || text == "#elseif") { 627 | builder.append(", did you mean '#elif'?"); 628 | kind = TokenKind.PREPROCESSOR_ELIF; 629 | } 630 | 631 | // Check for #endif typos 632 | else if (text == "#end") { 633 | builder.append(", did you mean '#endif'?"); 634 | kind = TokenKind.PREPROCESSOR_ENDIF; 635 | } 636 | 637 | log.error(createRange(source, start, i), builder.finish()); 638 | } 639 | 640 | // All preprocessor directives must be on a line by themselves 641 | if (last != null && last.kind != TokenKind.PREPROCESSOR_NEWLINE) { 642 | var end = last.range.end; 643 | var j = i - 1; 644 | while (j >= end) { 645 | if (contents[j] == '\n') { 646 | break; 647 | } 648 | j = j - 1; 649 | } 650 | if (j < end) { 651 | log.error(createRange(source, start, i), StringBuilder_new() 652 | .append("Expected newline before ") 653 | .append(tokenToString(kind)) 654 | .finish()); 655 | } 656 | } 657 | 658 | needsPreprocessor = true; 659 | wantNewline = true; 660 | } 661 | 662 | var range = createRange(source, start, i); 663 | 664 | if (kind == TokenKind.END_OF_FILE) { 665 | log.error(range, StringBuilder_new() 666 | .append("Syntax error: '") 667 | .appendSlice(contents, start, start + 1) 668 | .appendChar('\'') 669 | .finish()); 670 | return null; 671 | } 672 | 673 | var token = new Token(); 674 | token.kind = kind; 675 | token.range = range; 676 | 677 | if (first == null) first = token; 678 | else last.next = token; 679 | last = token; 680 | } 681 | 682 | var eof = new Token(); 683 | eof.kind = TokenKind.END_OF_FILE; 684 | eof.range = createRange(source, limit, limit); 685 | 686 | if (first == null) first = eof; 687 | else last.next = eof; 688 | last = eof; 689 | 690 | // Pass a "flag" for whether the preprocessor is needed back to the caller 691 | if (needsPreprocessor) { 692 | var token = new Token(); 693 | token.kind = TokenKind.PREPROCESSOR_NEEDED; 694 | token.next = first; 695 | return token; 696 | } 697 | 698 | return first; 699 | } 700 | -------------------------------------------------------------------------------- /src/library.thin: -------------------------------------------------------------------------------- 1 | function library(): string { 2 | return ` 3 | #if WASM 4 | 5 | // These will be filled in by the WebAssembly code generator 6 | unsafe var currentHeapPointer: *byte = null; 7 | unsafe var originalHeapPointer: *byte = null; 8 | 9 | extern unsafe function malloc(sizeOf: uint): *byte { 10 | // Align all allocations to 8 bytes 11 | var offset = ((currentHeapPointer as uint + 7) & ~7 as uint) as *byte; 12 | sizeOf = (sizeOf + 7) & ~7 as uint; 13 | 14 | // Use a simple bump allocator for now 15 | var limit = offset + sizeOf; 16 | currentHeapPointer = limit; 17 | 18 | // Make sure the memory starts off at zero 19 | var ptr = offset; 20 | while (ptr < limit) { 21 | *(ptr as *int) = 0; 22 | ptr = ptr + 4; 23 | } 24 | 25 | return offset; 26 | } 27 | 28 | unsafe function memcpy(target: *byte, source: *byte, length: uint): void { 29 | // No-op if either of the inputs are null 30 | if (source == null || target == null) { 31 | return; 32 | } 33 | 34 | // Optimized aligned copy 35 | if (length >= 16 && (source as uint) % 4 == (target as uint) % 4) { 36 | // Pick off the beginning 37 | while ((target as uint) % 4 != 0) { 38 | *target = *source; 39 | target = target + 1; 40 | source = source + 1; 41 | length = length - 1; 42 | } 43 | 44 | // Pick off the end 45 | while (length % 4 != 0) { 46 | length = length - 1; 47 | *(target + length) = *(source + length); 48 | } 49 | 50 | // Zip over the middle 51 | var end = target + length; 52 | while (target < end) { 53 | *(target as *int) = *(source as *int); 54 | target = target + 4; 55 | source = source + 4; 56 | } 57 | } 58 | 59 | // Slow unaligned copy 60 | else { 61 | var end = target + length; 62 | while (target < end) { 63 | *target = *source; 64 | target = target + 1; 65 | source = source + 1; 66 | } 67 | } 68 | } 69 | 70 | unsafe function memcmp(a: *byte, b: *byte, length: uint): int { 71 | // No-op if either of the inputs are null 72 | if (a == null || b == null) { 73 | return 0; 74 | } 75 | 76 | // Return the first non-zero difference 77 | while (length > 0) { 78 | var delta = *a as int - *b as int; 79 | if (delta != 0) { 80 | return delta; 81 | } 82 | a = a + 1; 83 | b = b + 1; 84 | length = length - 1; 85 | } 86 | 87 | // Both inputs are identical 88 | return 0; 89 | } 90 | 91 | #elif C 92 | 93 | declare unsafe function malloc(sizeOf: uint): *byte; 94 | declare unsafe function memcpy(target: *byte, source: *byte, length: uint): void; 95 | declare unsafe function memcmp(a: *byte, b: *byte, length: uint): int; 96 | 97 | #endif 98 | 99 | #if WASM || C 100 | 101 | declare class bool { 102 | toString(): string { 103 | return this ? "true" : "false"; 104 | } 105 | } 106 | 107 | declare class sbyte { 108 | toString(): string { 109 | return (this as int).toString(); 110 | } 111 | } 112 | 113 | declare class byte { 114 | toString(): string { 115 | return (this as uint).toString(); 116 | } 117 | } 118 | 119 | declare class short { 120 | toString(): string { 121 | return (this as int).toString(); 122 | } 123 | } 124 | 125 | declare class ushort { 126 | toString(): string { 127 | return (this as uint).toString(); 128 | } 129 | } 130 | 131 | declare class int { 132 | toString(): string { 133 | // Special-case this to keep the rest of the code simple 134 | if (this == -2147483648) { 135 | return "-2147483648"; 136 | } 137 | 138 | // Treat this like an unsigned integer prefixed by '-' if it's negative 139 | return internalIntToString((this < 0 ? -this : this) as uint, this < 0); 140 | } 141 | } 142 | 143 | declare class uint { 144 | toString(): string { 145 | return internalIntToString(this, false); 146 | } 147 | } 148 | 149 | function internalIntToString(value: uint, sign: bool): string { 150 | // Avoid allocation for common cases 151 | if (value == 0) return "0"; 152 | if (value == 1) return sign ? "-1" : "1"; 153 | 154 | unsafe { 155 | // Determine how many digits we need 156 | var length = ((sign ? 1 : 0) + ( 157 | value >= 100000000 ? 158 | value >= 1000000000 ? 10 : 9 : 159 | value >= 10000 ? 160 | value >= 1000000 ? 161 | value >= 10000000 ? 8 : 7 : 162 | value >= 100000 ? 6 : 5 : 163 | value >= 100 ? 164 | value >= 1000 ? 4 : 3 : 165 | value >= 10 ? 2 : 1)) as uint; 166 | 167 | var ptr = string_new(length) as *byte; 168 | var end = ptr + 4 + length * 2; 169 | 170 | if (sign) { 171 | *((ptr + 4) as *ushort) = '-'; 172 | } 173 | 174 | while (value != 0) { 175 | end = end + -2; 176 | *(end as *ushort) = (value % 10 + '0') as ushort; 177 | value = value / 10; 178 | } 179 | 180 | return ptr as string; 181 | } 182 | } 183 | 184 | function string_new(length: uint): string { 185 | unsafe { 186 | var ptr = malloc(4 + length * 2); 187 | *(ptr as *uint) = length; 188 | return ptr as string; 189 | } 190 | } 191 | 192 | declare class string { 193 | charAt(index: int): string { 194 | return this.slice(index, index + 1); 195 | } 196 | 197 | charCodeAt(index: int): ushort { 198 | return this[index]; 199 | } 200 | 201 | get length(): int { 202 | unsafe { 203 | return *(this as *int); 204 | } 205 | } 206 | 207 | operator [] (index: int): ushort { 208 | if (index as uint < this.length as uint) { 209 | unsafe { 210 | return *((this as *byte + 4 + index * 2) as *ushort); 211 | } 212 | } 213 | return 0; 214 | } 215 | 216 | operator == (other: string): bool { 217 | unsafe { 218 | if (this as *byte == other as *byte) return true; 219 | if (this as *byte == null || other as *byte == null) return false; 220 | var length = this.length; 221 | if (length != other.length) return false; 222 | return memcmp(this as *byte + 4, other as *byte + 4, length as uint * 2) == 0; 223 | } 224 | } 225 | 226 | slice(start: int, end: int): string { 227 | var length = this.length; 228 | 229 | if (start < 0) start = start + length; 230 | if (end < 0) end = end + length; 231 | 232 | if (start < 0) start = 0; 233 | else if (start > length) start = length; 234 | 235 | if (end < start) end = start; 236 | else if (end > length) end = length; 237 | 238 | unsafe { 239 | var range = (end - start) as uint; 240 | var ptr = string_new(range); 241 | memcpy(ptr as *byte + 4, this as *byte + 4 + start * 2, range * 2); 242 | return ptr; 243 | } 244 | } 245 | 246 | startsWith(text: string): bool { 247 | var textLength = text.length; 248 | if (this.length < textLength) return false; 249 | unsafe { 250 | return memcmp(this as *byte + 4, text as *byte + 4, textLength as uint * 2) == 0; 251 | } 252 | } 253 | 254 | endsWith(text: string): bool { 255 | var thisLength = this.length; 256 | var textLength = text.length; 257 | if (thisLength < textLength) return false; 258 | unsafe { 259 | return memcmp(this as *byte + 4 + (thisLength - textLength) * 2, text as *byte + 4, textLength as uint * 2) == 0; 260 | } 261 | } 262 | 263 | indexOf(text: string): int { 264 | var thisLength = this.length; 265 | var textLength = text.length; 266 | if (thisLength >= textLength) { 267 | var i = 0; 268 | while (i < thisLength - textLength) { 269 | unsafe { 270 | if (memcmp(this as *byte + 4 + i * 2, text as *byte + 4, textLength as uint * 2) == 0) { 271 | return i; 272 | } 273 | } 274 | i = i + 1; 275 | } 276 | } 277 | return -1; 278 | } 279 | 280 | lastIndexOf(text: string): int { 281 | var thisLength = this.length; 282 | var textLength = text.length; 283 | if (thisLength >= textLength) { 284 | var i = thisLength - textLength; 285 | while (i >= 0) { 286 | unsafe { 287 | if (memcmp(this as *byte + 4 + i * 2, text as *byte + 4, textLength as uint * 2) == 0) { 288 | return i; 289 | } 290 | } 291 | i = i - 1; 292 | } 293 | } 294 | return -1; 295 | } 296 | } 297 | 298 | #else 299 | 300 | declare class bool { 301 | toString(): string; 302 | } 303 | 304 | declare class sbyte { 305 | toString(): string; 306 | } 307 | 308 | declare class byte { 309 | toString(): string; 310 | } 311 | 312 | declare class short { 313 | toString(): string; 314 | } 315 | 316 | declare class ushort { 317 | toString(): string; 318 | } 319 | 320 | declare class int { 321 | toString(): string; 322 | } 323 | 324 | declare class uint { 325 | toString(): string; 326 | } 327 | 328 | declare class string { 329 | charAt(index: int): string; 330 | charCodeAt(index: int): ushort; 331 | get length(): int; 332 | indexOf(text: string): int; 333 | lastIndexOf(text: string): int; 334 | operator == (other: string): bool; 335 | operator [] (index: int): ushort { return this.charCodeAt(index); } 336 | slice(start: int, end: int): string; 337 | 338 | #if JS 339 | startsWith(text: string): bool { return this.slice(0, text.length) == text; } 340 | endsWith(text: string): bool { return this.slice(-text.length, this.length) == text; } 341 | #else 342 | startsWith(text: string): bool; 343 | endsWith(text: string): bool; 344 | #endif 345 | } 346 | 347 | #endif 348 | 349 | #if C 350 | 351 | extern unsafe function cstring_to_utf16(utf8: *byte): string { 352 | if (utf8 == null) { 353 | return null; 354 | } 355 | 356 | var utf16_length: uint = 0; 357 | var a: byte, b: byte, c: byte, d: byte; 358 | 359 | // Measure text 360 | var i: uint = 0; 361 | while ((a = *(utf8 + i)) != '\\0') { 362 | i = i + 1; 363 | var codePoint: uint; 364 | 365 | // Decode UTF-8 366 | if ((b = *(utf8 + i)) != '\\0' && a >= 0xC0) { 367 | i = i + 1; 368 | if ((c = *(utf8 + i)) != '\\0' && a >= 0xE0) { 369 | i = i + 1; 370 | if ((d = *(utf8 + i)) != '\\0' && a >= 0xF0) { 371 | i = i + 1; 372 | codePoint = ((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F); 373 | } else { 374 | codePoint = ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F); 375 | } 376 | } else { 377 | codePoint = ((a & 0x1F) << 6) | (b & 0x3F); 378 | } 379 | } else { 380 | codePoint = a; 381 | } 382 | 383 | // Encode UTF-16 384 | utf16_length = utf16_length + (codePoint < 0x10000 ? 1 : 2) as uint; 385 | } 386 | 387 | var output = string_new(utf16_length); 388 | var utf16 = output as *ushort + 2; 389 | 390 | // Convert text 391 | i = 0; 392 | while ((a = *(utf8 + i)) != '\\0') { 393 | i = i + 1; 394 | var codePoint: uint; 395 | 396 | // Decode UTF-8 397 | if ((b = *(utf8 + i)) != '\\0' && a >= 0xC0) { 398 | i = i + 1; 399 | if ((c = *(utf8 + i)) != '\\0' && a >= 0xE0) { 400 | i = i + 1; 401 | if ((d = *(utf8 + i)) != '\\0' && a >= 0xF0) { 402 | i = i + 1; 403 | codePoint = ((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F); 404 | } else { 405 | codePoint = ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F); 406 | } 407 | } else { 408 | codePoint = ((a & 0x1F) << 6) | (b & 0x3F); 409 | } 410 | } else { 411 | codePoint = a; 412 | } 413 | 414 | // Encode UTF-16 415 | if (codePoint < 0x10000) { 416 | *utf16 = codePoint as ushort; 417 | } else { 418 | *utf16 = ((codePoint >> 10) + (0xD800 - (0x10000 >> 10))) as ushort; 419 | utf16 = utf16 + 1; 420 | *utf16 = ((codePoint & 0x3FF) + 0xDC00) as ushort; 421 | } 422 | utf16 = utf16 + 1; 423 | } 424 | 425 | return output; 426 | } 427 | 428 | extern unsafe function utf16_to_cstring(input: string): *byte { 429 | if (input as *uint == null) { 430 | return null; 431 | } 432 | 433 | var utf16_length = *(input as *uint); 434 | var utf8_length: uint = 0; 435 | var utf16 = input as *ushort + 2; 436 | 437 | // Measure text 438 | var i: uint = 0; 439 | while (i < utf16_length) { 440 | var codePoint: uint; 441 | 442 | // Decode UTF-16 443 | var a = *(utf16 + i); 444 | i = i + 1; 445 | if (i < utf16_length && a >= 0xD800 && a <= 0xDBFF) { 446 | var b = *(utf16 + i); 447 | i = i + 1; 448 | codePoint = (a << 10) + b + (0x10000 - (0xD800 << 10) - 0xDC00) as uint; 449 | } else { 450 | codePoint = a; 451 | } 452 | 453 | // Encode UTF-8 454 | utf8_length = utf8_length + ( 455 | codePoint < 0x80 ? 1 : 456 | codePoint < 0x800 ? 2 : 457 | codePoint < 0x10000 ? 3 : 458 | 4) as uint; 459 | } 460 | 461 | var utf8 = malloc(utf8_length + 1); 462 | var next = utf8; 463 | 464 | // Convert text 465 | i = 0; 466 | while (i < utf16_length) { 467 | var codePoint: uint; 468 | 469 | // Decode UTF-16 470 | var a = *(utf16 + i); 471 | i = i + 1; 472 | if (i < utf16_length && a >= 0xD800 && a <= 0xDBFF) { 473 | var b = *(utf16 + i); 474 | i = i + 1; 475 | codePoint = (a << 10) + b + (0x10000 - (0xD800 << 10) - 0xDC00) as uint; 476 | } else { 477 | codePoint = a; 478 | } 479 | 480 | // Encode UTF-8 481 | if (codePoint < 0x80) { 482 | *next = codePoint as byte; 483 | } else { 484 | if (codePoint < 0x800) { 485 | *next = (((codePoint >> 6) & 0x1F) | 0xC0) as byte; 486 | } else { 487 | if (codePoint < 0x10000) { 488 | *next = (((codePoint >> 12) & 0x0F) | 0xE0) as byte; 489 | } else { 490 | *next = (((codePoint >> 18) & 0x07) | 0xF0) as byte; 491 | next = next + 1; 492 | *next = (((codePoint >> 12) & 0x3F) | 0x80) as byte; 493 | } 494 | next = next + 1; 495 | *next = (((codePoint >> 6) & 0x3F) | 0x80) as byte; 496 | } 497 | next = next + 1; 498 | *next = ((codePoint & 0x3F) | 0x80) as byte; 499 | } 500 | next = next + 1; 501 | } 502 | 503 | // C strings are null-terminated 504 | *next = '\\0'; 505 | 506 | return utf8; 507 | } 508 | 509 | #endif 510 | `; 511 | } 512 | -------------------------------------------------------------------------------- /src/log.thin: -------------------------------------------------------------------------------- 1 | class LineColumn { 2 | line: int; // 0-based 3 | column: int; // 0-based 4 | } 5 | 6 | class Source { 7 | name: string; 8 | contents: string; 9 | 10 | // These are for internal use by the compiler 11 | next: Source; 12 | isLibrary: bool; 13 | firstToken: Token; 14 | file: Node; 15 | 16 | indexToLineColumn(index: int): LineColumn { 17 | var contents = this.contents; 18 | var column = 0; 19 | var line = 0; 20 | var i = 0; 21 | 22 | // Just count the number of lines from the beginning of the file for now 23 | while (i < index) { 24 | var c = contents[i]; 25 | 26 | if (c == '\n') { 27 | line = line + 1; 28 | column = 0; 29 | } 30 | 31 | // Ignore low surrogates 32 | else if (c < 0xDC00 || c > 0xDFFF) { 33 | column = column + 1; 34 | } 35 | 36 | i = i + 1; 37 | } 38 | 39 | var location = new LineColumn(); 40 | location.line = line; 41 | location.column = column; 42 | return location; 43 | } 44 | } 45 | 46 | class Range { 47 | source: Source; 48 | start: int; 49 | end: int; 50 | 51 | toString(): string { 52 | return this.source.contents.slice(this.start, this.end); 53 | } 54 | 55 | equals(other: Range): bool { 56 | return 57 | this.source == other.source && 58 | this.start == other.start && 59 | this.end == other.end; 60 | } 61 | 62 | enclosingLine(): Range { 63 | var contents = this.source.contents; 64 | var start = this.start; 65 | var end = this.start; 66 | 67 | while (start > 0 && contents[start - 1] != '\n') { 68 | start = start - 1; 69 | } 70 | 71 | var length = contents.length; 72 | while (end < length && contents[end] != '\n') { 73 | end = end + 1; 74 | } 75 | 76 | return createRange(this.source, start, end); 77 | } 78 | 79 | rangeAtEnd(): Range { 80 | return createRange(this.source, this.end, this.end); 81 | } 82 | } 83 | 84 | function createRange(source: Source, start: int, end: int): Range { 85 | assert(start <= end); 86 | var range = new Range(); 87 | range.source = source; 88 | range.start = start; 89 | range.end = end; 90 | return range; 91 | } 92 | 93 | function spanRanges(left: Range, right: Range): Range { 94 | assert(left.source == right.source); 95 | assert(left.start <= right.start); 96 | assert(left.end <= right.end); 97 | return createRange(left.source, left.start, right.end); 98 | } 99 | 100 | enum DiagnosticKind { 101 | ERROR, 102 | WARNING, 103 | } 104 | 105 | class Diagnostic { 106 | range: Range; 107 | message: string; 108 | kind: DiagnosticKind; 109 | next: Diagnostic; 110 | 111 | appendSourceName(builder: StringBuilder, location: LineColumn): void { 112 | builder 113 | .append(this.range.source.name) 114 | .appendChar(':') 115 | .append((location.line + 1).toString()) 116 | .appendChar(':') 117 | .append((location.column + 1).toString()) 118 | .append(": "); 119 | } 120 | 121 | appendKind(builder: StringBuilder): void { 122 | builder.append(this.kind == DiagnosticKind.ERROR ? "error: " : "warning: "); 123 | } 124 | 125 | appendMessage(builder: StringBuilder): void { 126 | builder.append(this.message).appendChar('\n'); 127 | } 128 | 129 | appendLineContents(builder: StringBuilder, location: LineColumn): void { 130 | var range = this.range.enclosingLine(); 131 | builder.appendSlice(range.source.contents, range.start, range.end).appendChar('\n'); 132 | } 133 | 134 | appendRange(builder: StringBuilder, location: LineColumn): void { 135 | var range = this.range; 136 | var column = location.column; 137 | var contents = range.source.contents; 138 | 139 | // Whitespace 140 | while (column > 0) { 141 | builder.appendChar(' '); 142 | column = column - 1; 143 | } 144 | 145 | // Single character 146 | if (range.end - range.start <= 1) { 147 | builder.appendChar('^'); 148 | } 149 | 150 | // Multiple characters 151 | else { 152 | var i = range.start; 153 | while (i < range.end && contents[i] != '\n') { 154 | builder.appendChar('~'); 155 | i = i + 1; 156 | } 157 | } 158 | 159 | builder.appendChar('\n'); 160 | } 161 | } 162 | 163 | class Log { 164 | first: Diagnostic; 165 | last: Diagnostic; 166 | 167 | error(range: Range, message: string): void { 168 | this.append(range, message, DiagnosticKind.ERROR); 169 | } 170 | 171 | warning(range: Range, message: string): void { 172 | this.append(range, message, DiagnosticKind.WARNING); 173 | } 174 | 175 | append(range: Range, message: string, kind: DiagnosticKind): void { 176 | var diagnostic = new Diagnostic(); 177 | diagnostic.range = range; 178 | diagnostic.message = message; 179 | diagnostic.kind = kind; 180 | 181 | if (this.first == null) this.first = diagnostic; 182 | else this.last.next = diagnostic; 183 | this.last = diagnostic; 184 | } 185 | 186 | toString(): string { 187 | var builder = StringBuilder_new(); 188 | var diagnostic = this.first; 189 | 190 | while (diagnostic != null) { 191 | var location = diagnostic.range.source.indexToLineColumn(diagnostic.range.start); 192 | diagnostic.appendSourceName(builder, location); 193 | diagnostic.appendKind(builder); 194 | diagnostic.appendMessage(builder); 195 | diagnostic.appendLineContents(builder, location); 196 | diagnostic.appendRange(builder, location); 197 | diagnostic = diagnostic.next; 198 | } 199 | 200 | return builder.finish(); 201 | } 202 | 203 | hasErrors(): bool { 204 | var diagnostic = this.first; 205 | 206 | while (diagnostic != null) { 207 | if (diagnostic.kind == DiagnosticKind.ERROR) { 208 | return true; 209 | } 210 | diagnostic = diagnostic.next; 211 | } 212 | 213 | return false; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main.thin: -------------------------------------------------------------------------------- 1 | extern enum Color { 2 | DEFAULT, 3 | BOLD, 4 | RED, 5 | GREEN, 6 | MAGENTA, 7 | } 8 | 9 | declare function Terminal_setColor(color: Color): void; 10 | declare function Terminal_write(text: string): void; 11 | 12 | declare function IO_readTextFile(path: string): string; 13 | declare function IO_writeTextFile(path: string, contents: string): bool; 14 | declare function IO_writeBinaryFile(path: string, contents: ByteArray): bool; 15 | 16 | function writeLogToTerminal(log: Log): void { 17 | var diagnostic = log.first; 18 | 19 | while (diagnostic != null) { 20 | var location = diagnostic.range.source.indexToLineColumn(diagnostic.range.start); 21 | 22 | // Source 23 | var builder = StringBuilder_new(); 24 | diagnostic.appendSourceName(builder, location); 25 | Terminal_setColor(Color.BOLD); 26 | Terminal_write(builder.finish()); 27 | 28 | // Kind 29 | builder = StringBuilder_new(); 30 | diagnostic.appendKind(builder); 31 | Terminal_setColor(diagnostic.kind == DiagnosticKind.ERROR ? Color.RED : Color.MAGENTA); 32 | Terminal_write(builder.finish()); 33 | 34 | // Message 35 | builder = StringBuilder_new(); 36 | diagnostic.appendMessage(builder); 37 | Terminal_setColor(Color.BOLD); 38 | Terminal_write(builder.finish()); 39 | 40 | // Line contents 41 | builder = StringBuilder_new(); 42 | diagnostic.appendLineContents(builder, location); 43 | Terminal_setColor(Color.DEFAULT); 44 | Terminal_write(builder.finish()); 45 | 46 | // Range 47 | builder = StringBuilder_new(); 48 | diagnostic.appendRange(builder, location); 49 | Terminal_setColor(Color.GREEN); 50 | Terminal_write(builder.finish()); 51 | 52 | diagnostic = diagnostic.next; 53 | } 54 | 55 | Terminal_setColor(Color.DEFAULT); 56 | } 57 | 58 | function printError(text: string): void { 59 | Terminal_setColor(Color.RED); 60 | Terminal_write("error: "); 61 | Terminal_setColor(Color.BOLD); 62 | Terminal_write(text); 63 | Terminal_write("\n"); 64 | Terminal_setColor(Color.DEFAULT); 65 | } 66 | 67 | class CommandLineArgument { 68 | text: string; 69 | next: CommandLineArgument; 70 | } 71 | 72 | var firstArgument: CommandLineArgument; 73 | var lastArgument: CommandLineArgument; 74 | 75 | extern function main_addArgument(text: string): void { 76 | var argument = new CommandLineArgument(); 77 | argument.text = text; 78 | 79 | if (firstArgument == null) firstArgument = argument; 80 | else lastArgument.next = argument; 81 | lastArgument = argument; 82 | } 83 | 84 | #if WASM 85 | extern unsafe function main_newString(length: uint): string { 86 | return string_new(length); 87 | } 88 | #endif 89 | 90 | extern unsafe function main_reset(): void { 91 | firstArgument = null; 92 | lastArgument = null; 93 | 94 | #if WASM 95 | currentHeapPointer = originalHeapPointer; // Reset the heap 96 | stringBuilderPool = null; 97 | #endif 98 | } 99 | 100 | function printUsage(): void { 101 | Terminal_write(` 102 | Usage: thinc [FLAGS] [INPUTS] 103 | 104 | --help Print this message. 105 | --out [PATH] Emit code to PATH (the target format is the file extension). 106 | --define [NAME] Define the flag NAME in all input files. 107 | 108 | Examples: 109 | 110 | thinc main.thin --out main.js 111 | thinc src/*.thin --out main.wasm 112 | thinc native.thin --out main.c --define ENABLE_TESTS 113 | 114 | `); 115 | } 116 | 117 | extern function main_entry(): int { 118 | var target = CompileTarget.NONE; 119 | var argument = firstArgument; 120 | var inputCount = 0; 121 | var output: string; 122 | 123 | // Print usage by default 124 | if (firstArgument == null) { 125 | printUsage(); 126 | return 1; 127 | } 128 | 129 | // Initial pass over the argument list 130 | while (argument != null) { 131 | var text = argument.text; 132 | if (text.startsWith("-")) { 133 | if (text == "-h" || text == "-help" || text == "--help" || text == "/?") { 134 | printUsage(); 135 | return 0; 136 | } else if (text == "--c") { 137 | target = CompileTarget.C; 138 | } else if (text == "--js") { 139 | target = CompileTarget.JAVASCRIPT; 140 | } else if (text == "--wasm") { 141 | target = CompileTarget.WEBASSEMBLY; 142 | } else if (text == "--define" && argument.next != null) { 143 | argument = argument.next; 144 | } else if (text == "--out" && argument.next != null) { 145 | argument = argument.next; 146 | output = argument.text; 147 | } else { 148 | printError(StringBuilder_new().append("Invalid flag: ").append(text).finish()); 149 | return 1; 150 | } 151 | } else { 152 | inputCount = inputCount + 1; 153 | } 154 | argument = argument.next; 155 | } 156 | 157 | // Must have inputs 158 | if (inputCount == 0) { 159 | printError("No input files"); 160 | return 1; 161 | } 162 | 163 | // Must have an output 164 | if (output == null) { 165 | printError("Missing an output file (use the --out flag)"); 166 | return 1; 167 | } 168 | 169 | // Automatically set the target based on the file extension 170 | if (target == CompileTarget.NONE) { 171 | if (output.endsWith(".c")) target = CompileTarget.C; 172 | else if (output.endsWith(".js")) target = CompileTarget.JAVASCRIPT; 173 | else if (output.endsWith(".wasm")) target = CompileTarget.WEBASSEMBLY; 174 | else { 175 | printError("Missing a target (use either --c, --js, or --wasm)"); 176 | return 1; 177 | } 178 | } 179 | 180 | // Start the compilation 181 | var compiler = new Compiler(); 182 | compiler.initialize(target, output); 183 | 184 | // Second pass over the argument list 185 | argument = firstArgument; 186 | while (argument != null) { 187 | var text = argument.text; 188 | if (text == "--define") { 189 | argument = argument.next; 190 | compiler.preprocessor.define(argument.text, true); 191 | } else if (text == "--out") { 192 | argument = argument.next; 193 | } else if (!text.startsWith("-")) { 194 | var contents = IO_readTextFile(text); 195 | if (contents == null) { 196 | printError(StringBuilder_new().append("Cannot read from ").append(text).finish()); 197 | return 1; 198 | } 199 | compiler.addInput(text, contents); 200 | } 201 | argument = argument.next; 202 | } 203 | 204 | // Finish the compilation 205 | compiler.finish(); 206 | writeLogToTerminal(compiler.log); 207 | 208 | // Only emit the output if the compilation succeeded 209 | if (!compiler.log.hasErrors()) { 210 | if (target == CompileTarget.C && IO_writeTextFile(output, compiler.outputC) && IO_writeTextFile(replaceFileExtension(output, ".h"), compiler.outputH) || 211 | target == CompileTarget.JAVASCRIPT && IO_writeTextFile(output, compiler.outputJS) || 212 | target == CompileTarget.WEBASSEMBLY && IO_writeBinaryFile(output, compiler.outputWASM)) { 213 | return 0; 214 | } 215 | 216 | printError(StringBuilder_new().append("Cannot write to ").append(output).finish()); 217 | } 218 | 219 | return 1; 220 | } 221 | -------------------------------------------------------------------------------- /src/node.thin: -------------------------------------------------------------------------------- 1 | enum NodeKind { 2 | // Other 3 | EXTENDS, 4 | FILE, 5 | GLOBAL, 6 | IMPLEMENTS, 7 | PARAMETER, 8 | PARAMETERS, 9 | VARIABLE, 10 | 11 | // Statements 12 | BLOCK, 13 | BREAK, 14 | CLASS, 15 | CONSTANTS, 16 | CONTINUE, 17 | EMPTY, 18 | ENUM, 19 | EXPRESSION, 20 | FUNCTION, 21 | IF, 22 | RETURN, 23 | UNSAFE, 24 | VARIABLES, 25 | WHILE, 26 | 27 | // Expressions 28 | ALIGN_OF, 29 | BOOL, 30 | CALL, 31 | CAST, 32 | DOT, 33 | HOOK, 34 | INDEX, 35 | INT, 36 | NAME, 37 | NEW, 38 | NULL, 39 | PARSE_ERROR, 40 | SIZE_OF, 41 | STRING, 42 | THIS, 43 | TYPE, 44 | 45 | // Unary expressions 46 | ADDRESS_OF, 47 | COMPLEMENT, 48 | DEREFERENCE, 49 | NEGATIVE, 50 | NOT, 51 | POINTER_TYPE, 52 | POSITIVE, 53 | POSTFIX_DECREMENT, 54 | POSTFIX_INCREMENT, 55 | PREFIX_DECREMENT, 56 | PREFIX_INCREMENT, 57 | 58 | // Binary expressions 59 | ADD, 60 | ASSIGN, 61 | BITWISE_AND, 62 | BITWISE_OR, 63 | BITWISE_XOR, 64 | DIVIDE, 65 | EQUAL, 66 | EXPONENT, 67 | GREATER_THAN, 68 | GREATER_THAN_EQUAL, 69 | LESS_THAN, 70 | LESS_THAN_EQUAL, 71 | LOGICAL_AND, 72 | LOGICAL_OR, 73 | MULTIPLY, 74 | NOT_EQUAL, 75 | REMAINDER, 76 | SHIFT_LEFT, 77 | SHIFT_RIGHT, 78 | SUBTRACT, 79 | } 80 | 81 | function isUnary(kind: NodeKind): bool { 82 | return kind >= NodeKind.ADDRESS_OF && kind <= NodeKind.PREFIX_INCREMENT; 83 | } 84 | 85 | function isUnaryPostfix(kind: NodeKind): bool { 86 | return kind >= NodeKind.POSTFIX_DECREMENT && kind <= NodeKind.POSTFIX_INCREMENT; 87 | } 88 | 89 | function isBinary(kind: NodeKind): bool { 90 | return kind >= NodeKind.ADD && kind <= NodeKind.SUBTRACT; 91 | } 92 | 93 | function invertedBinaryKind(kind: NodeKind): NodeKind { 94 | if (kind == NodeKind.EQUAL) return NodeKind.NOT_EQUAL; 95 | if (kind == NodeKind.NOT_EQUAL) return NodeKind.EQUAL; 96 | if (kind == NodeKind.GREATER_THAN) return NodeKind.LESS_THAN_EQUAL; 97 | if (kind == NodeKind.GREATER_THAN_EQUAL) return NodeKind.LESS_THAN; 98 | if (kind == NodeKind.LESS_THAN) return NodeKind.GREATER_THAN_EQUAL; 99 | if (kind == NodeKind.LESS_THAN_EQUAL) return NodeKind.GREATER_THAN; 100 | return kind; 101 | } 102 | 103 | function isExpression(node: Node): bool { 104 | return node.kind >= NodeKind.ALIGN_OF && node.kind <= NodeKind.SUBTRACT; 105 | } 106 | 107 | function isCompactNodeKind(kind: NodeKind): bool { 108 | return 109 | kind == NodeKind.CONSTANTS || 110 | kind == NodeKind.EXPRESSION || 111 | kind == NodeKind.VARIABLES; 112 | } 113 | 114 | const NODE_FLAG_DECLARE = 1 << 0; 115 | const NODE_FLAG_EXPORT = 1 << 1; 116 | const NODE_FLAG_EXTERN = 1 << 2; 117 | const NODE_FLAG_GET = 1 << 3; 118 | const NODE_FLAG_OPERATOR = 1 << 4; 119 | const NODE_FLAG_POSITIVE = 1 << 5; 120 | const NODE_FLAG_PRIVATE = 1 << 6; 121 | const NODE_FLAG_PROTECTED = 1 << 7; 122 | const NODE_FLAG_PUBLIC = 1 << 8; 123 | const NODE_FLAG_SET = 1 << 9; 124 | const NODE_FLAG_STATIC = 1 << 10; 125 | const NODE_FLAG_UNSAFE = 1 << 11; 126 | const NODE_FLAG_UNSIGNED_OPERATOR = 1 << 12; 127 | 128 | class NodeFlag { 129 | flag: int; 130 | range: Range; 131 | next: NodeFlag; 132 | } 133 | 134 | function appendFlag(first: NodeFlag, flag: int, range: Range): NodeFlag { 135 | var link = new NodeFlag(); 136 | link.flag = flag; 137 | link.range = range; 138 | 139 | // Is the list empty? 140 | if (first == null) { 141 | return link; 142 | } 143 | 144 | // Append the flag to the end of the list 145 | var secondToLast = first; 146 | while (secondToLast.next != null) { 147 | secondToLast = secondToLast.next; 148 | } 149 | secondToLast.next = link; 150 | return first; 151 | } 152 | 153 | function allFlags(link: NodeFlag): int { 154 | var all = 0; 155 | while (link != null) { 156 | all = all | link.flag; 157 | link = link.next; 158 | } 159 | return all; 160 | } 161 | 162 | function rangeForFlag(link: NodeFlag, flag: int): Range { 163 | while (link != null) { 164 | if (link.flag == flag) { 165 | return link.range; 166 | } 167 | link = link.next; 168 | } 169 | return null; 170 | } 171 | 172 | class Node { 173 | kind: NodeKind; 174 | flags: int; 175 | firstFlag: NodeFlag; 176 | range: Range; 177 | internalRange: Range; 178 | parent: Node; 179 | firstChild: Node; 180 | lastChild: Node; 181 | previousSibling: Node; 182 | nextSibling: Node; 183 | intValue: int; 184 | stringValue: string; 185 | resolvedType: Type; 186 | symbol: Symbol; 187 | scope: Scope; 188 | 189 | become(node: Node): void { 190 | assert(node != this); 191 | assert(node.parent == null); 192 | 193 | this.kind = node.kind; 194 | this.flags = node.flags; 195 | this.firstFlag = node.firstFlag; 196 | this.range = node.range; 197 | this.internalRange = node.internalRange; 198 | this.intValue = node.intValue; 199 | this.stringValue = node.stringValue; 200 | this.resolvedType = node.resolvedType; 201 | this.symbol = node.symbol; 202 | this.scope = node.scope; 203 | } 204 | 205 | becomeSymbolReference(symbol: Symbol): void { 206 | this.kind = NodeKind.NAME; 207 | this.symbol = symbol; 208 | this.stringValue = symbol.name; 209 | this.resolvedType = symbol.resolvedType; 210 | this.removeChildren(); 211 | } 212 | 213 | becomeIntegerConstant(value: int): void { 214 | this.kind = NodeKind.INT; 215 | this.symbol = null; 216 | this.intValue = value; 217 | this.removeChildren(); 218 | } 219 | 220 | becomeBooleanConstant(value: bool): void { 221 | this.kind = NodeKind.BOOL; 222 | this.symbol = null; 223 | this.intValue = value ? 1 : 0; 224 | this.removeChildren(); 225 | } 226 | 227 | isNegativeInteger(): bool { 228 | return this.kind == NodeKind.INT && this.intValue < 0; 229 | } 230 | 231 | isNonNegativeInteger(): bool { 232 | return this.kind == NodeKind.INT && this.intValue >= 0; 233 | } 234 | 235 | isDeclare(): bool { 236 | return (this.flags & NODE_FLAG_DECLARE) != 0; 237 | } 238 | 239 | isExtern(): bool { 240 | return (this.flags & NODE_FLAG_EXTERN) != 0; 241 | } 242 | 243 | isDeclareOrExtern(): bool { 244 | return (this.flags & (NODE_FLAG_DECLARE | NODE_FLAG_EXTERN)) != 0; 245 | } 246 | 247 | isGet(): bool { 248 | return (this.flags & NODE_FLAG_GET) != 0; 249 | } 250 | 251 | isSet(): bool { 252 | return (this.flags & NODE_FLAG_SET) != 0; 253 | } 254 | 255 | isOperator(): bool { 256 | return (this.flags & NODE_FLAG_OPERATOR) != 0; 257 | } 258 | 259 | isPositive(): bool { 260 | return (this.flags & NODE_FLAG_POSITIVE) != 0; 261 | } 262 | 263 | isPrivate(): bool { 264 | return (this.flags & NODE_FLAG_PRIVATE) != 0; 265 | } 266 | 267 | isUnsafe(): bool { 268 | return (this.flags & NODE_FLAG_UNSAFE) != 0; 269 | } 270 | 271 | isUnsignedOperator(): bool { 272 | return (this.flags & NODE_FLAG_UNSIGNED_OPERATOR) != 0; 273 | } 274 | 275 | childCount(): int { 276 | var count = 0; 277 | var child = this.firstChild; 278 | while (child != null) { 279 | count = count + 1; 280 | child = child.nextSibling; 281 | } 282 | return count; 283 | } 284 | 285 | appendChild(child: Node): void { 286 | child.parent = this; 287 | 288 | if (this.firstChild == null) { 289 | this.firstChild = child; 290 | } 291 | 292 | else { 293 | child.previousSibling = this.lastChild; 294 | this.lastChild.nextSibling = child; 295 | } 296 | 297 | this.lastChild = child; 298 | } 299 | 300 | insertChildBefore(after: Node, before: Node): void { 301 | if (before == null) { 302 | return; 303 | } 304 | 305 | assert(before != after); 306 | assert(before.parent == null); 307 | assert(before.previousSibling == null); 308 | assert(before.nextSibling == null); 309 | assert(after == null || after.parent == this); 310 | 311 | if (after == null) { 312 | this.appendChild(before); 313 | return; 314 | } 315 | 316 | before.parent = this; 317 | before.previousSibling = after.previousSibling; 318 | before.nextSibling = after; 319 | 320 | if (after.previousSibling != null) { 321 | assert(after == after.previousSibling.nextSibling); 322 | after.previousSibling.nextSibling = before; 323 | } else { 324 | assert(after == this.firstChild); 325 | this.firstChild = before; 326 | } 327 | 328 | after.previousSibling = before; 329 | } 330 | 331 | remove(): Node { 332 | assert(this.parent != null); 333 | 334 | if (this.previousSibling != null) { 335 | assert(this.previousSibling.nextSibling == this); 336 | this.previousSibling.nextSibling = this.nextSibling; 337 | } else { 338 | assert(this.parent.firstChild == this); 339 | this.parent.firstChild = this.nextSibling; 340 | } 341 | 342 | if (this.nextSibling != null) { 343 | assert(this.nextSibling.previousSibling == this); 344 | this.nextSibling.previousSibling = this.previousSibling; 345 | } else { 346 | assert(this.parent.lastChild == this); 347 | this.parent.lastChild = this.previousSibling; 348 | } 349 | 350 | this.parent = null; 351 | this.previousSibling = null; 352 | this.nextSibling = null; 353 | return this; 354 | } 355 | 356 | removeChildren(): void { 357 | while (this.lastChild != null) { 358 | this.lastChild.remove(); 359 | } 360 | } 361 | 362 | replaceWith(node: Node): void { 363 | assert(node != this); 364 | assert(this.parent != null); 365 | assert(node.parent == null); 366 | assert(node.previousSibling == null); 367 | assert(node.nextSibling == null); 368 | 369 | node.parent = this.parent; 370 | node.previousSibling = this.previousSibling; 371 | node.nextSibling = this.nextSibling; 372 | 373 | if (this.previousSibling != null) { 374 | assert(this.previousSibling.nextSibling == this); 375 | this.previousSibling.nextSibling = node; 376 | } else { 377 | assert(this.parent.firstChild == this); 378 | this.parent.firstChild = node; 379 | } 380 | 381 | if (this.nextSibling != null) { 382 | assert(this.nextSibling.previousSibling == this); 383 | this.nextSibling.previousSibling = node; 384 | } else { 385 | assert(this.parent.lastChild == this); 386 | this.parent.lastChild = node; 387 | } 388 | 389 | this.parent = null; 390 | this.previousSibling = null; 391 | this.nextSibling = null; 392 | } 393 | 394 | isType(): bool { 395 | return this.kind == NodeKind.TYPE || this.kind == NodeKind.POINTER_TYPE || this.symbol != null && isType(this.symbol.kind); 396 | } 397 | 398 | isCallValue(): bool { 399 | return this.parent.kind == NodeKind.CALL && this == this.parent.callValue(); 400 | } 401 | 402 | isAssignTarget(): bool { 403 | return this.parent.kind == NodeKind.ASSIGN && this == this.parent.binaryLeft(); 404 | } 405 | 406 | withRange(range: Range): Node { 407 | this.range = range; 408 | return this; 409 | } 410 | 411 | withInternalRange(range: Range): Node { 412 | this.internalRange = range; 413 | return this; 414 | } 415 | 416 | functionFirstArgument(): Node { 417 | assert(this.kind == NodeKind.FUNCTION); 418 | assert(this.childCount() >= 2); 419 | var child = this.firstChild; 420 | if (child.kind == NodeKind.PARAMETERS) { 421 | child = child.nextSibling; 422 | } 423 | return child; 424 | } 425 | 426 | functionFirstArgumentIgnoringThis(): Node { 427 | assert(this.kind == NodeKind.FUNCTION); 428 | assert(this.childCount() >= 2); 429 | assert(this.symbol != null); 430 | var child = this.functionFirstArgument(); 431 | if (this.symbol.kind == SymbolKind.FUNCTION_INSTANCE) { 432 | child = child.nextSibling; 433 | } 434 | return child; 435 | } 436 | 437 | functionReturnType(): Node { 438 | assert(this.kind == NodeKind.FUNCTION); 439 | assert(this.childCount() >= 2); 440 | assert(isExpression(this.lastChild.previousSibling)); 441 | return this.lastChild.previousSibling; 442 | } 443 | 444 | functionBody(): Node { 445 | assert(this.kind == NodeKind.FUNCTION); 446 | assert(this.childCount() >= 2); 447 | assert(this.lastChild.kind == NodeKind.BLOCK || this.lastChild.kind == NodeKind.EMPTY); 448 | var body = this.lastChild; 449 | return body.kind == NodeKind.BLOCK ? body : null; 450 | } 451 | 452 | newType(): Node { 453 | assert(this.kind == NodeKind.NEW); 454 | assert(this.childCount() >= 1); 455 | assert(isExpression(this.firstChild)); 456 | return this.firstChild; 457 | } 458 | 459 | callValue(): Node { 460 | assert(this.kind == NodeKind.CALL); 461 | assert(this.childCount() >= 1); 462 | assert(isExpression(this.firstChild)); 463 | return this.firstChild; 464 | } 465 | 466 | castValue(): Node { 467 | assert(this.kind == NodeKind.CAST); 468 | assert(this.childCount() == 2); 469 | assert(isExpression(this.firstChild)); 470 | return this.firstChild; 471 | } 472 | 473 | castType(): Node { 474 | assert(this.kind == NodeKind.CAST); 475 | assert(this.childCount() == 2); 476 | assert(isExpression(this.lastChild)); 477 | return this.lastChild; 478 | } 479 | 480 | alignOfType(): Node { 481 | assert(this.kind == NodeKind.ALIGN_OF); 482 | assert(this.childCount() == 1); 483 | assert(isExpression(this.firstChild)); 484 | return this.firstChild; 485 | } 486 | 487 | sizeOfType(): Node { 488 | assert(this.kind == NodeKind.SIZE_OF); 489 | assert(this.childCount() == 1); 490 | assert(isExpression(this.firstChild)); 491 | return this.firstChild; 492 | } 493 | 494 | dotTarget(): Node { 495 | assert(this.kind == NodeKind.DOT); 496 | assert(this.childCount() == 1); 497 | assert(isExpression(this.firstChild)); 498 | return this.firstChild; 499 | } 500 | 501 | returnValue(): Node { 502 | assert(this.kind == NodeKind.RETURN); 503 | assert(this.childCount() <= 1); 504 | assert(this.firstChild == null || isExpression(this.firstChild)); 505 | return this.firstChild; 506 | } 507 | 508 | extendsType(): Node { 509 | assert(this.kind == NodeKind.EXTENDS); 510 | assert(this.childCount() == 1); 511 | assert(isExpression(this.firstChild)); 512 | return this.firstChild; 513 | } 514 | 515 | variableType(): Node { 516 | assert(this.kind == NodeKind.VARIABLE); 517 | assert(this.childCount() <= 2); 518 | assert(isExpression(this.firstChild) || this.firstChild.kind == NodeKind.EMPTY); 519 | var type = this.firstChild; 520 | return type.kind != NodeKind.EMPTY ? type : null; 521 | } 522 | 523 | variableValue(): Node { 524 | assert(this.kind == NodeKind.VARIABLE); 525 | assert(this.childCount() <= 2); 526 | assert(this.firstChild.nextSibling == null || isExpression(this.firstChild.nextSibling)); 527 | return this.firstChild.nextSibling; 528 | } 529 | 530 | expressionValue(): Node { 531 | assert(this.kind == NodeKind.EXPRESSION); 532 | assert(this.childCount() == 1); 533 | assert(isExpression(this.firstChild)); 534 | return this.firstChild; 535 | } 536 | 537 | binaryLeft(): Node { 538 | assert(isBinary(this.kind)); 539 | assert(this.childCount() == 2); 540 | assert(isExpression(this.firstChild)); 541 | return this.firstChild; 542 | } 543 | 544 | binaryRight(): Node { 545 | assert(isBinary(this.kind)); 546 | assert(this.childCount() == 2); 547 | assert(isExpression(this.lastChild)); 548 | return this.lastChild; 549 | } 550 | 551 | unaryValue(): Node { 552 | assert(isUnary(this.kind)); 553 | assert(this.childCount() == 1); 554 | assert(isExpression(this.firstChild)); 555 | return this.firstChild; 556 | } 557 | 558 | whileValue(): Node { 559 | assert(this.kind == NodeKind.WHILE); 560 | assert(this.childCount() == 2); 561 | assert(isExpression(this.firstChild)); 562 | return this.firstChild; 563 | } 564 | 565 | whileBody(): Node { 566 | assert(this.kind == NodeKind.WHILE); 567 | assert(this.childCount() == 2); 568 | assert(this.lastChild.kind == NodeKind.BLOCK); 569 | return this.lastChild; 570 | } 571 | 572 | hookValue(): Node { 573 | assert(this.kind == NodeKind.HOOK); 574 | assert(this.childCount() == 3); 575 | assert(isExpression(this.firstChild)); 576 | return this.firstChild; 577 | } 578 | 579 | hookTrue(): Node { 580 | assert(this.kind == NodeKind.HOOK); 581 | assert(this.childCount() == 3); 582 | assert(isExpression(this.firstChild.nextSibling)); 583 | return this.firstChild.nextSibling; 584 | } 585 | 586 | hookFalse(): Node { 587 | assert(this.kind == NodeKind.HOOK); 588 | assert(this.childCount() == 3); 589 | assert(isExpression(this.lastChild)); 590 | return this.lastChild; 591 | } 592 | 593 | indexTarget(): Node { 594 | assert(this.kind == NodeKind.INDEX); 595 | assert(this.childCount() >= 1); 596 | assert(isExpression(this.firstChild)); 597 | return this.firstChild; 598 | } 599 | 600 | ifValue(): Node { 601 | assert(this.kind == NodeKind.IF); 602 | assert(this.childCount() == 2 || this.childCount() == 3); 603 | assert(isExpression(this.firstChild)); 604 | return this.firstChild; 605 | } 606 | 607 | ifTrue(): Node { 608 | assert(this.kind == NodeKind.IF); 609 | assert(this.childCount() == 2 || this.childCount() == 3); 610 | assert(this.firstChild.nextSibling.kind == NodeKind.BLOCK); 611 | return this.firstChild.nextSibling; 612 | } 613 | 614 | ifFalse(): Node { 615 | assert(this.kind == NodeKind.IF); 616 | assert(this.childCount() == 2 || this.childCount() == 3); 617 | assert(this.firstChild.nextSibling.nextSibling == null || this.firstChild.nextSibling.nextSibling.kind == NodeKind.BLOCK); 618 | return this.firstChild.nextSibling.nextSibling; 619 | } 620 | 621 | expandCallIntoOperatorTree(): bool { 622 | if (this.kind != NodeKind.CALL) { 623 | return false; 624 | } 625 | 626 | var value = this.callValue(); 627 | var symbol = value.symbol; 628 | 629 | if (value.kind == NodeKind.DOT && symbol.node.isOperator() && symbol.node.isDeclare()) { 630 | var binaryKind = NodeKind.NULL; 631 | 632 | if (symbol.name == "%") binaryKind = NodeKind.REMAINDER; 633 | else if (symbol.name == "&") binaryKind = NodeKind.BITWISE_AND; 634 | else if (symbol.name == "*") binaryKind = NodeKind.MULTIPLY; 635 | else if (symbol.name == "**") binaryKind = NodeKind.EXPONENT; 636 | else if (symbol.name == "/") binaryKind = NodeKind.DIVIDE; 637 | else if (symbol.name == "<") binaryKind = NodeKind.LESS_THAN; 638 | else if (symbol.name == "<<") binaryKind = NodeKind.SHIFT_LEFT; 639 | else if (symbol.name == "==") binaryKind = NodeKind.EQUAL; 640 | else if (symbol.name == ">") binaryKind = NodeKind.GREATER_THAN; 641 | else if (symbol.name == ">>") binaryKind = NodeKind.SHIFT_RIGHT; 642 | else if (symbol.name == "[]") binaryKind = NodeKind.INDEX; 643 | else if (symbol.name == "^") binaryKind = NodeKind.BITWISE_XOR; 644 | else if (symbol.name == "|") binaryKind = NodeKind.BITWISE_OR; 645 | 646 | if (binaryKind != NodeKind.NULL) { 647 | this.kind = binaryKind; 648 | value.remove(); 649 | this.insertChildBefore(this.firstChild, value.dotTarget().remove()); 650 | return true; 651 | } 652 | 653 | else if (symbol.name == "[]=") { 654 | this.kind = NodeKind.ASSIGN; 655 | var target = createIndex(value.remove().dotTarget().remove()); 656 | target.appendChild(this.firstChild.remove()); 657 | this.insertChildBefore(this.firstChild, target); 658 | return true; 659 | } 660 | } 661 | 662 | return false; 663 | } 664 | } 665 | 666 | function createNew(type: Node): Node { 667 | assert(isExpression(type)); 668 | var node = new Node(); 669 | node.kind = NodeKind.NEW; 670 | node.appendChild(type); 671 | return node; 672 | } 673 | 674 | function createHook(test: Node, primary: Node, secondary: Node): Node { 675 | assert(isExpression(test)); 676 | assert(isExpression(primary)); 677 | assert(isExpression(secondary)); 678 | var node = new Node(); 679 | node.kind = NodeKind.HOOK; 680 | node.appendChild(test); 681 | node.appendChild(primary); 682 | node.appendChild(secondary); 683 | return node; 684 | } 685 | 686 | function createIndex(target: Node): Node { 687 | assert(isExpression(target)); 688 | var node = new Node(); 689 | node.kind = NodeKind.INDEX; 690 | node.appendChild(target); 691 | return node; 692 | } 693 | 694 | function createNull(): Node { 695 | var node = new Node(); 696 | node.kind = NodeKind.NULL; 697 | return node; 698 | } 699 | 700 | function createThis(): Node { 701 | var node = new Node(); 702 | node.kind = NodeKind.THIS; 703 | return node; 704 | } 705 | 706 | function createAddressOf(value: Node): Node { 707 | assert(isExpression(value)); 708 | var node = new Node(); 709 | node.kind = NodeKind.ADDRESS_OF; 710 | node.appendChild(value); 711 | return node; 712 | } 713 | 714 | function createDereference(value: Node): Node { 715 | assert(isExpression(value)); 716 | var node = new Node(); 717 | node.kind = NodeKind.DEREFERENCE; 718 | node.appendChild(value); 719 | return node; 720 | } 721 | 722 | function createAlignOf(type: Node): Node { 723 | assert(isExpression(type)); 724 | var node = new Node(); 725 | node.kind = NodeKind.ALIGN_OF; 726 | node.appendChild(type); 727 | return node; 728 | } 729 | 730 | function createSizeOf(type: Node): Node { 731 | assert(isExpression(type)); 732 | var node = new Node(); 733 | node.kind = NodeKind.SIZE_OF; 734 | node.appendChild(type); 735 | return node; 736 | } 737 | 738 | function createBool(value: bool): Node { 739 | var node = new Node(); 740 | node.kind = NodeKind.BOOL; 741 | node.intValue = value ? 1 : 0; 742 | return node; 743 | } 744 | 745 | function createInt(value: int): Node { 746 | var node = new Node(); 747 | node.kind = NodeKind.INT; 748 | node.intValue = value; 749 | return node; 750 | } 751 | 752 | function createString(value: string): Node { 753 | var node = new Node(); 754 | node.kind = NodeKind.STRING; 755 | node.stringValue = value; 756 | return node; 757 | } 758 | 759 | function createName(value: string): Node { 760 | var node = new Node(); 761 | node.kind = NodeKind.NAME; 762 | node.stringValue = value; 763 | return node; 764 | } 765 | 766 | function createType(type: Type): Node { 767 | assert(type != null); 768 | var node = new Node(); 769 | node.kind = NodeKind.TYPE; 770 | node.resolvedType = type; 771 | return node; 772 | } 773 | 774 | function createEmpty(): Node { 775 | var node = new Node(); 776 | node.kind = NodeKind.EMPTY; 777 | return node; 778 | } 779 | 780 | function createExpression(value: Node): Node { 781 | assert(isExpression(value)); 782 | var node = new Node(); 783 | node.kind = NodeKind.EXPRESSION; 784 | node.appendChild(value); 785 | return node; 786 | } 787 | 788 | function createBlock(): Node { 789 | var node = new Node(); 790 | node.kind = NodeKind.BLOCK; 791 | return node; 792 | } 793 | 794 | function createClass(name: string): Node { 795 | var node = new Node(); 796 | node.kind = NodeKind.CLASS; 797 | node.stringValue = name; 798 | return node; 799 | } 800 | 801 | function createEnum(name: string): Node { 802 | var node = new Node(); 803 | node.kind = NodeKind.ENUM; 804 | node.stringValue = name; 805 | return node; 806 | } 807 | 808 | function createIf(value: Node, trueBranch: Node, falseBranch: Node): Node { 809 | assert(isExpression(value)); 810 | assert(trueBranch.kind == NodeKind.BLOCK); 811 | assert(falseBranch == null || falseBranch.kind == NodeKind.BLOCK); 812 | var node = new Node(); 813 | node.kind = NodeKind.IF; 814 | node.appendChild(value); 815 | node.appendChild(trueBranch); 816 | if (falseBranch != null) { 817 | node.appendChild(falseBranch); 818 | } 819 | return node; 820 | } 821 | 822 | function createWhile(value: Node, body: Node): Node { 823 | assert(isExpression(value)); 824 | assert(body.kind == NodeKind.BLOCK); 825 | var node = new Node(); 826 | node.kind = NodeKind.WHILE; 827 | node.appendChild(value); 828 | node.appendChild(body); 829 | return node; 830 | } 831 | 832 | function createReturn(value: Node): Node { 833 | assert(value == null || isExpression(value)); 834 | var node = new Node(); 835 | node.kind = NodeKind.RETURN; 836 | if (value != null) { 837 | node.appendChild(value); 838 | } 839 | return node; 840 | } 841 | 842 | function createVariables(): Node { 843 | var node = new Node(); 844 | node.kind = NodeKind.VARIABLES; 845 | return node; 846 | } 847 | 848 | function createConstants(): Node { 849 | var node = new Node(); 850 | node.kind = NodeKind.CONSTANTS; 851 | return node; 852 | } 853 | 854 | function createParameters(): Node { 855 | var node = new Node(); 856 | node.kind = NodeKind.PARAMETERS; 857 | return node; 858 | } 859 | 860 | function createExtends(type: Node): Node { 861 | assert(isExpression(type)); 862 | var node = new Node(); 863 | node.kind = NodeKind.EXTENDS; 864 | node.appendChild(type); 865 | return node; 866 | } 867 | 868 | function createImplements(): Node { 869 | var node = new Node(); 870 | node.kind = NodeKind.IMPLEMENTS; 871 | return node; 872 | } 873 | 874 | function createParameter(name: string): Node { 875 | var node = new Node(); 876 | node.kind = NodeKind.PARAMETER; 877 | node.stringValue = name; 878 | return node; 879 | } 880 | 881 | function createVariable(name: string, type: Node, value: Node): Node { 882 | assert(type == null || isExpression(type)); 883 | assert(value == null || isExpression(value)); 884 | 885 | var node = new Node(); 886 | node.kind = NodeKind.VARIABLE; 887 | node.stringValue = name; 888 | 889 | node.appendChild(type != null ? type : createEmpty()); 890 | if (value != null) { 891 | node.appendChild(value); 892 | } 893 | 894 | return node; 895 | } 896 | 897 | function createFunction(name: string): Node { 898 | var node = new Node(); 899 | node.kind = NodeKind.FUNCTION; 900 | node.stringValue = name; 901 | return node; 902 | } 903 | 904 | function createUnary(kind: NodeKind, value: Node): Node { 905 | assert(isUnary(kind)); 906 | assert(isExpression(value)); 907 | var node = new Node(); 908 | node.kind = kind; 909 | node.appendChild(value); 910 | return node; 911 | } 912 | 913 | function createBinary(kind: NodeKind, left: Node, right: Node): Node { 914 | assert(isBinary(kind)); 915 | assert(isExpression(left)); 916 | assert(isExpression(right)); 917 | var node = new Node(); 918 | node.kind = kind; 919 | node.appendChild(left); 920 | node.appendChild(right); 921 | return node; 922 | } 923 | 924 | function createCall(value: Node): Node { 925 | assert(isExpression(value)); 926 | var node = new Node(); 927 | node.kind = NodeKind.CALL; 928 | node.appendChild(value); 929 | return node; 930 | } 931 | 932 | function createCast(value: Node, type: Node): Node { 933 | assert(isExpression(value)); 934 | assert(isExpression(type)); 935 | var node = new Node(); 936 | node.kind = NodeKind.CAST; 937 | node.appendChild(value); 938 | node.appendChild(type); 939 | return node; 940 | } 941 | 942 | function createDot(value: Node, name: string): Node { 943 | assert(isExpression(value)); 944 | var node = new Node(); 945 | node.kind = NodeKind.DOT; 946 | node.stringValue = name; 947 | node.appendChild(value); 948 | return node; 949 | } 950 | 951 | function createSymbolReference(symbol: Symbol): Node { 952 | var node = createName(symbol.name); 953 | node.symbol = symbol; 954 | node.resolvedType = symbol.resolvedType; 955 | return node; 956 | } 957 | 958 | function createMemberReference(value: Node, symbol: Symbol): Node { 959 | var node = createDot(value, symbol.name); 960 | node.symbol = symbol; 961 | node.resolvedType = symbol.resolvedType; 962 | return node; 963 | } 964 | 965 | function createParseError(): Node { 966 | var node = new Node(); 967 | node.kind = NodeKind.PARSE_ERROR; 968 | return node; 969 | } 970 | -------------------------------------------------------------------------------- /src/preprocessor.thin: -------------------------------------------------------------------------------- 1 | enum PreprocessorValue { 2 | FALSE, 3 | TRUE, 4 | ERROR, 5 | } 6 | 7 | class PreprocessorFlag { 8 | isDefined: bool; 9 | name: string; 10 | next: PreprocessorFlag; 11 | } 12 | 13 | // This preprocessor implements the flag-only conditional behavior from C#. 14 | // There are two scopes for flags: global-level and file-level. This is stored 15 | // using an ever-growing linked list of PreprocessorFlag objects that turn a 16 | // flag either on or off. That way file-level state can just reference the 17 | // memory of the global-level state and the global-level state can easily be 18 | // restored after parsing a file just by restoring the pointer. 19 | class Preprocessor { 20 | firstFlag: PreprocessorFlag; 21 | isDefineAndUndefAllowed: bool; 22 | previous: Token; 23 | current: Token; 24 | log: Log; 25 | 26 | peek(kind: TokenKind): bool { 27 | return this.current.kind == kind; 28 | } 29 | 30 | eat(kind: TokenKind): bool { 31 | if (this.peek(kind)) { 32 | this.advance(); 33 | return true; 34 | } 35 | 36 | return false; 37 | } 38 | 39 | advance(): void { 40 | if (!this.peek(TokenKind.END_OF_FILE)) { 41 | this.previous = this.current; 42 | this.current = this.current.next; 43 | } 44 | } 45 | 46 | unexpectedToken(): void { 47 | this.log.error(this.current.range, StringBuilder_new() 48 | .append("Unexpected ") 49 | .append(tokenToString(this.current.kind)) 50 | .finish()); 51 | } 52 | 53 | expect(kind: TokenKind): bool { 54 | if (!this.peek(kind)) { 55 | this.log.error(this.current.range, StringBuilder_new() 56 | .append("Expected ") 57 | .append(tokenToString(kind)) 58 | .append(" but found ") 59 | .append(tokenToString(this.current.kind)) 60 | .finish()); 61 | return false; 62 | } 63 | 64 | this.advance(); 65 | return true; 66 | } 67 | 68 | removeTokensFrom(before: Token): void { 69 | before.next = this.current; 70 | this.previous = before; 71 | } 72 | 73 | isDefined(name: string): bool { 74 | var flag = this.firstFlag; 75 | while (flag != null) { 76 | if (flag.name == name) { 77 | return flag.isDefined; 78 | } 79 | flag = flag.next; 80 | } 81 | return false; 82 | } 83 | 84 | define(name: string, isDefined: bool): void { 85 | var flag = new PreprocessorFlag(); 86 | flag.isDefined = isDefined; 87 | flag.name = name; 88 | flag.next = this.firstFlag; 89 | this.firstFlag = flag; 90 | } 91 | 92 | run(source: Source, log: Log): void { 93 | var firstToken = source.firstToken; 94 | 95 | if (firstToken != null && firstToken.kind == TokenKind.PREPROCESSOR_NEEDED) { 96 | var firstFlag = this.firstFlag; 97 | 98 | // Initialize 99 | this.isDefineAndUndefAllowed = true; 100 | this.previous = firstToken; 101 | this.current = firstToken.next; 102 | this.log = log; 103 | 104 | // Don't parse this file if preprocessing failed 105 | if (!this.scan(true)) { 106 | source.firstToken = null; 107 | return; 108 | } 109 | 110 | // Make sure blocks are balanced 111 | if (!this.peek(TokenKind.END_OF_FILE)) { 112 | this.unexpectedToken(); 113 | } 114 | 115 | // Restore the global-level state instead of letting the file-level state 116 | // leak over into the next file that the preprocessor is run on 117 | this.firstFlag = firstFlag; 118 | 119 | // Skip over the PREPROCESSOR_NEEDED token so the parser doesn't see it 120 | source.firstToken = source.firstToken.next; 121 | } 122 | } 123 | 124 | // Scan over the next reachable tokens, evaluate #define/#undef directives, 125 | // and fold #if/#else chains. Stop on #elif/#else/#endif. Return false on 126 | // failure. Takes a boolean flag for whether or not control flow is live in 127 | // this block. 128 | scan(isParentLive: bool): bool { 129 | while (!this.peek(TokenKind.END_OF_FILE) && 130 | !this.peek(TokenKind.PREPROCESSOR_ELIF) && 131 | !this.peek(TokenKind.PREPROCESSOR_ELSE) && 132 | !this.peek(TokenKind.PREPROCESSOR_ENDIF)) { 133 | var previous = this.previous; 134 | var current = this.current; 135 | 136 | // #define or #undef 137 | if (this.eat(TokenKind.PREPROCESSOR_DEFINE) || this.eat(TokenKind.PREPROCESSOR_UNDEF)) { 138 | // Only process the directive if control flow is live at this point 139 | if (this.expect(TokenKind.IDENTIFIER) && isParentLive) { 140 | this.define(this.previous.range.toString(), current.kind == TokenKind.PREPROCESSOR_DEFINE); 141 | } 142 | 143 | // Help out people trying to use this like C 144 | if (this.eat(TokenKind.FALSE) || this.eat(TokenKind.INT) && this.previous.range.toString() == "0") { 145 | this.log.error(this.previous.range, "Use '#undef' to turn a preprocessor flag off"); 146 | } 147 | 148 | // Scan up to the next newline 149 | if (!this.peek(TokenKind.END_OF_FILE) && !this.expect(TokenKind.PREPROCESSOR_NEWLINE)) { 150 | while (!this.eat(TokenKind.PREPROCESSOR_NEWLINE) && !this.eat(TokenKind.END_OF_FILE)) { 151 | this.advance(); 152 | } 153 | } 154 | 155 | // These statements are only valid at the top of the file 156 | if (!this.isDefineAndUndefAllowed) { 157 | this.log.error(spanRanges(current.range, this.previous.range), 158 | "All '#define' and '#undef' directives must be at the top of the file"); 159 | } 160 | 161 | // Remove all of these tokens 162 | this.removeTokensFrom(previous); 163 | } 164 | 165 | // #warning or #error 166 | else if (this.eat(TokenKind.PREPROCESSOR_WARNING) || this.eat(TokenKind.PREPROCESSOR_ERROR)) { 167 | var next = this.current; 168 | 169 | // Scan up to the next newline 170 | while (!this.peek(TokenKind.PREPROCESSOR_NEWLINE) && !this.peek(TokenKind.END_OF_FILE)) { 171 | this.advance(); 172 | } 173 | 174 | // Only process the directive if control flow is live at this point 175 | if (isParentLive) { 176 | var range = this.current == next ? current.range : spanRanges(next.range, this.previous.range); 177 | this.log.append(range, range.toString(), current.kind == TokenKind.PREPROCESSOR_WARNING ? DiagnosticKind.WARNING : DiagnosticKind.ERROR); 178 | } 179 | 180 | // Remove all of these tokens 181 | this.eat(TokenKind.PREPROCESSOR_NEWLINE); 182 | this.removeTokensFrom(previous); 183 | } 184 | 185 | // #if 186 | else if (this.eat(TokenKind.PREPROCESSOR_IF)) { 187 | var isLive = isParentLive; 188 | 189 | // Scan over the entire if-else chain 190 | while (true) { 191 | var condition = this.parseExpression(Precedence.LOWEST); 192 | 193 | // Reject if the condition is missing 194 | if (condition == PreprocessorValue.ERROR || !this.expect(TokenKind.PREPROCESSOR_NEWLINE)) { 195 | return false; 196 | } 197 | 198 | // Remove the #if/#elif header 199 | this.removeTokensFrom(previous); 200 | 201 | // Scan to the next #elif, #else, or #endif 202 | if (!this.scan(isLive && condition == PreprocessorValue.TRUE)) { 203 | return false; 204 | } 205 | 206 | // Remove these tokens? 207 | if (!isLive || condition == PreprocessorValue.FALSE) { 208 | this.removeTokensFrom(previous); 209 | } 210 | 211 | // Keep these tokens but remove all subsequent branches 212 | else { 213 | isLive = false; 214 | } 215 | 216 | // Update the previous pointer so we remove from here next 217 | previous = this.previous; 218 | 219 | // #elif 220 | if (this.eat(TokenKind.PREPROCESSOR_ELIF)) { 221 | continue; 222 | } 223 | 224 | // #else 225 | if (this.eat(TokenKind.PREPROCESSOR_ELSE)) { 226 | if (!this.expect(TokenKind.PREPROCESSOR_NEWLINE)) { 227 | return false; 228 | } 229 | 230 | // Remove the #else 231 | this.removeTokensFrom(previous); 232 | 233 | // Scan to the #endif 234 | if (!this.scan(isLive)) { 235 | return false; 236 | } 237 | 238 | // Remove these tokens? 239 | if (!isLive) { 240 | this.removeTokensFrom(previous); 241 | } 242 | } 243 | 244 | // #endif 245 | break; 246 | } 247 | 248 | // All if-else chains end with an #endif 249 | previous = this.previous; 250 | if (!this.expect(TokenKind.PREPROCESSOR_ENDIF) || !this.peek(TokenKind.END_OF_FILE) && !this.expect(TokenKind.PREPROCESSOR_NEWLINE)) { 251 | return false; 252 | } 253 | this.removeTokensFrom(previous); 254 | } 255 | 256 | // Skip normal tokens 257 | else { 258 | this.isDefineAndUndefAllowed = false; 259 | this.advance(); 260 | } 261 | } 262 | 263 | return true; 264 | } 265 | 266 | parsePrefix(): PreprocessorValue { 267 | var isDefinedOperator = false; 268 | var start = this.current; 269 | 270 | // true or false 271 | if (this.eat(TokenKind.TRUE)) return PreprocessorValue.TRUE; 272 | if (this.eat(TokenKind.FALSE)) return PreprocessorValue.FALSE; 273 | 274 | // Identifier 275 | if (this.eat(TokenKind.IDENTIFIER)) { 276 | var name = this.previous.range.toString(); 277 | 278 | // Recover from a C-style define operator 279 | if (this.peek(TokenKind.LEFT_PARENTHESIS) && name == "defined") { 280 | isDefinedOperator = true; 281 | } 282 | 283 | else { 284 | var isTrue = this.isDefined(name); 285 | return isTrue ? PreprocessorValue.TRUE : PreprocessorValue.FALSE; 286 | } 287 | } 288 | 289 | // ! 290 | if (this.eat(TokenKind.NOT)) { 291 | var value = this.parseExpression(Precedence.UNARY_PREFIX); 292 | if (value == PreprocessorValue.ERROR) return PreprocessorValue.ERROR; 293 | return value == PreprocessorValue.TRUE ? PreprocessorValue.FALSE : PreprocessorValue.TRUE; 294 | } 295 | 296 | // Group 297 | if (this.eat(TokenKind.LEFT_PARENTHESIS)) { 298 | var first = this.current; 299 | var value = this.parseExpression(Precedence.LOWEST); 300 | if (value == PreprocessorValue.ERROR || !this.expect(TokenKind.RIGHT_PARENTHESIS)) { 301 | return PreprocessorValue.ERROR; 302 | } 303 | 304 | // Recover from a C-style define operator 305 | if (isDefinedOperator) { 306 | var builder = StringBuilder_new().append("There is no 'defined' operator"); 307 | if (first.kind == TokenKind.IDENTIFIER && this.previous == first.next) { 308 | builder.append(" (just use '").append(first.range.toString()).append("' instead)"); 309 | } 310 | this.log.error(spanRanges(start.range, this.previous.range), builder.finish()); 311 | } 312 | 313 | return value; 314 | } 315 | 316 | // Recover from a C-style boolean 317 | if (this.eat(TokenKind.INT)) { 318 | var isTrue = this.previous.range.toString() != "0"; 319 | this.log.error(this.previous.range, StringBuilder_new() 320 | .append("Unexpected integer (did you mean '") 321 | .append(isTrue ? "true" : "false") 322 | .append("')?") 323 | .finish()); 324 | return isTrue ? PreprocessorValue.TRUE : PreprocessorValue.FALSE; 325 | } 326 | 327 | this.unexpectedToken(); 328 | return PreprocessorValue.ERROR; 329 | } 330 | 331 | parseInfix(precedence: Precedence, left: PreprocessorValue): PreprocessorValue { 332 | var operator = this.current.kind; 333 | 334 | // == or != 335 | if (precedence < Precedence.EQUAL && (this.eat(TokenKind.EQUAL) || this.eat(TokenKind.NOT_EQUAL))) { 336 | var right = this.parseExpression(Precedence.EQUAL); 337 | if (right == PreprocessorValue.ERROR) return PreprocessorValue.ERROR; 338 | return (operator == TokenKind.EQUAL) == (left == right) ? PreprocessorValue.TRUE : PreprocessorValue.FALSE; 339 | } 340 | 341 | // && 342 | if (precedence < Precedence.LOGICAL_AND && this.eat(TokenKind.LOGICAL_AND)) { 343 | var right = this.parseExpression(Precedence.LOGICAL_AND); 344 | if (right == PreprocessorValue.ERROR) return PreprocessorValue.ERROR; 345 | return (left == PreprocessorValue.TRUE && right == PreprocessorValue.TRUE) ? PreprocessorValue.TRUE : PreprocessorValue.FALSE; 346 | } 347 | 348 | // || 349 | if (precedence < Precedence.LOGICAL_OR && this.eat(TokenKind.LOGICAL_OR)) { 350 | var right = this.parseExpression(Precedence.LOGICAL_OR); 351 | if (right == PreprocessorValue.ERROR) return PreprocessorValue.ERROR; 352 | return (left == PreprocessorValue.TRUE || right == PreprocessorValue.TRUE) ? PreprocessorValue.TRUE : PreprocessorValue.FALSE; 353 | } 354 | 355 | // Hook 356 | if (precedence == Precedence.LOWEST && this.eat(TokenKind.QUESTION_MARK)) { 357 | var middle = this.parseExpression(Precedence.LOWEST); 358 | if (middle == PreprocessorValue.ERROR || !this.expect(TokenKind.COLON)) { 359 | return PreprocessorValue.ERROR; 360 | } 361 | 362 | var right = this.parseExpression(Precedence.LOWEST); 363 | if (right == PreprocessorValue.ERROR) { 364 | return PreprocessorValue.ERROR; 365 | } 366 | 367 | return left == PreprocessorValue.TRUE ? middle : right; 368 | } 369 | 370 | return left; 371 | } 372 | 373 | parseExpression(precedence: Precedence): PreprocessorValue { 374 | // Prefix 375 | var value = this.parsePrefix(); 376 | if (value == PreprocessorValue.ERROR) { 377 | return PreprocessorValue.ERROR; 378 | } 379 | 380 | // Infix 381 | while (true) { 382 | var current = this.current; 383 | value = this.parseInfix(precedence, value); 384 | if (value == PreprocessorValue.ERROR) return PreprocessorValue.ERROR; 385 | if (this.current == current) break; 386 | } 387 | 388 | return value; 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /src/scope.thin: -------------------------------------------------------------------------------- 1 | enum FindNested { 2 | NORMAL, 3 | ALLOW_INSTANCE_ERRORS, 4 | } 5 | 6 | enum ScopeHint { 7 | NORMAL, 8 | NOT_BINARY, 9 | NOT_GETTER, 10 | NOT_SETTER, 11 | NOT_UNARY, 12 | PREFER_GETTER, 13 | PREFER_SETTER, 14 | } 15 | 16 | class Scope { 17 | parent: Scope; 18 | symbol: Symbol; 19 | firstSymbol: Symbol; 20 | lastSymbol: Symbol; 21 | 22 | findLocal(name: string, hint: ScopeHint): Symbol { 23 | var symbol = this.firstSymbol; 24 | var fallback: Symbol = null; 25 | while (symbol != null) { 26 | if (symbol.name == name) { 27 | if (hint == ScopeHint.PREFER_GETTER && symbol.isSetter() || 28 | hint == ScopeHint.PREFER_SETTER && symbol.isGetter()) { 29 | fallback = symbol; 30 | } 31 | 32 | else if ( 33 | (hint != ScopeHint.NOT_GETTER || !symbol.isGetter()) && 34 | (hint != ScopeHint.NOT_SETTER || !symbol.isSetter()) && 35 | (hint != ScopeHint.NOT_BINARY || !symbol.isBinaryOperator()) && 36 | (hint != ScopeHint.NOT_UNARY || !symbol.isUnaryOperator())) { 37 | return symbol; 38 | } 39 | } 40 | symbol = symbol.next; 41 | } 42 | return fallback; 43 | } 44 | 45 | findNested(name: string, hint: ScopeHint, mode: FindNested): Symbol { 46 | var scope = this; 47 | while (scope != null) { 48 | if (scope.symbol == null || scope.symbol.kind != SymbolKind.TYPE_CLASS || mode == FindNested.ALLOW_INSTANCE_ERRORS) { 49 | var local = scope.findLocal(name, hint); 50 | if (local != null) { 51 | return local; 52 | } 53 | } 54 | scope = scope.parent; 55 | } 56 | return null; 57 | } 58 | 59 | define(log: Log, symbol: Symbol, hint: ScopeHint): bool { 60 | var existing = this.findLocal(symbol.name, hint); 61 | if (existing != null) { 62 | log.error(symbol.range, StringBuilder_new() 63 | .append("Duplicate symbol '") 64 | .append(symbol.name) 65 | .append("'") 66 | .finish()); 67 | return false; 68 | } 69 | 70 | if (this.firstSymbol == null) this.firstSymbol = symbol; 71 | else this.lastSymbol.next = symbol; 72 | this.lastSymbol = symbol; 73 | 74 | return true; 75 | } 76 | 77 | defineNativeType(log: Log, name: string): Type { 78 | var symbol = new Symbol(); 79 | symbol.kind = SymbolKind.TYPE_NATIVE; 80 | symbol.name = name; 81 | symbol.resolvedType = new Type(); 82 | symbol.resolvedType.symbol = symbol; 83 | symbol.state = SymbolState.INITIALIZED; 84 | this.define(log, symbol, ScopeHint.NORMAL); 85 | return symbol.resolvedType; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/shaking.thin: -------------------------------------------------------------------------------- 1 | function treeShakingMarkAllUsed(node: Node): void { 2 | var symbol = node.symbol; 3 | if (symbol != null && !symbol.isUsed() && isFunction(symbol.kind) && symbol.node != null) { 4 | symbol.flags = symbol.flags | SYMBOL_FLAG_USED; 5 | treeShakingMarkAllUsed(symbol.node); 6 | if (node == symbol.node) return; 7 | } 8 | 9 | if (node.kind == NodeKind.NEW) { 10 | var type = node.newType().resolvedType; 11 | if (type.symbol != null) { 12 | type.symbol.flags = type.symbol.flags | SYMBOL_FLAG_USED; 13 | } 14 | } 15 | 16 | var child = node.firstChild; 17 | while (child != null) { 18 | treeShakingMarkAllUsed(child); 19 | child = child.nextSibling; 20 | } 21 | } 22 | 23 | function treeShakingSearchForUsed(node: Node): void { 24 | if (node.kind == NodeKind.FUNCTION && node.isExtern()) { 25 | treeShakingMarkAllUsed(node); 26 | } 27 | 28 | else if (node.kind == NodeKind.GLOBAL || node.kind == NodeKind.CLASS) { 29 | var child = node.firstChild; 30 | while (child != null) { 31 | treeShakingSearchForUsed(child); 32 | child = child.nextSibling; 33 | } 34 | 35 | if (node.kind == NodeKind.CLASS && node.isExtern()) { 36 | node.symbol.flags = node.symbol.flags | SYMBOL_FLAG_USED; 37 | } 38 | } 39 | } 40 | 41 | function treeShakingRemoveUnused(node: Node): void { 42 | if (node.kind == NodeKind.FUNCTION && !node.symbol.isUsed() && node.range.source.isLibrary) { 43 | node.remove(); 44 | } 45 | 46 | else if (node.kind == NodeKind.GLOBAL || node.kind == NodeKind.CLASS) { 47 | var child = node.firstChild; 48 | while (child != null) { 49 | var next = child.nextSibling; 50 | treeShakingRemoveUnused(child); 51 | child = next; 52 | } 53 | 54 | if (node.kind == NodeKind.CLASS && !node.symbol.isUsed() && !node.isDeclare() && node.range.source.isLibrary) { 55 | node.remove(); 56 | } 57 | } 58 | } 59 | 60 | function treeShaking(node: Node): void { 61 | treeShakingSearchForUsed(node); 62 | treeShakingRemoveUnused(node); 63 | } 64 | -------------------------------------------------------------------------------- /src/stringbuilder.thin: -------------------------------------------------------------------------------- 1 | var stringBuilderPool: StringBuilder = null; 2 | 3 | // Remove an object from the pool or allocate a new object if the pool is empty 4 | function StringBuilder_new(): StringBuilder { 5 | var sb = stringBuilderPool; 6 | if (sb != null) stringBuilderPool = sb.next; 7 | else sb = new StringBuilder(); 8 | sb.clear(); 9 | return sb; 10 | } 11 | 12 | function StringBuilder_appendQuoted(sb: StringBuilder, text: string): void { 13 | var end = 0; 14 | var limit = text.length; 15 | var start = end; 16 | 17 | sb.appendChar('"'); 18 | 19 | while (end < limit) { 20 | var c = text[end]; 21 | 22 | if (c == '"') sb.appendSlice(text, start, end).append("\\\""); 23 | else if (c == '\0') sb.appendSlice(text, start, end).append("\\0"); 24 | else if (c == '\t') sb.appendSlice(text, start, end).append("\\t"); 25 | else if (c == '\r') sb.appendSlice(text, start, end).append("\\r"); 26 | else if (c == '\n') sb.appendSlice(text, start, end).append("\\n"); 27 | else if (c == '\\') sb.appendSlice(text, start, end).append("\\\\"); 28 | else { 29 | end = end + 1; 30 | continue; 31 | } 32 | 33 | end = end + 1; 34 | start = end; 35 | } 36 | 37 | sb.appendSlice(text, start, end).appendChar('"'); 38 | } 39 | 40 | #if JS //////////////////////////////////////////////////////////////////////////////// 41 | 42 | declare function StringBuilder_appendChar(a: string, b: ushort): string; 43 | declare function StringBuilder_append(a: string, b: string): string; 44 | 45 | class StringBuilder { 46 | next: StringBuilder; 47 | _text: string; 48 | 49 | clear(): void { 50 | this._text = ""; 51 | } 52 | 53 | appendChar(c: ushort): StringBuilder { 54 | this._text = StringBuilder_appendChar(this._text, c); 55 | return this; 56 | } 57 | 58 | appendSlice(text: string, start: int, end: int): StringBuilder { 59 | this._text = StringBuilder_append(this._text, text.slice(start, end)); 60 | return this; 61 | } 62 | 63 | append(text: string): StringBuilder { 64 | this._text = StringBuilder_append(this._text, text); 65 | return this; 66 | } 67 | 68 | // This also "frees" this object (puts it back in the pool) 69 | finish(): string { 70 | this.next = stringBuilderPool; 71 | stringBuilderPool = this; 72 | return this._text; 73 | } 74 | } 75 | 76 | #elif WASM || C //////////////////////////////////////////////////////////////////////////////// 77 | 78 | class StringBuilder { 79 | next: StringBuilder; 80 | _bytes: ByteArray; 81 | 82 | clear(): void { 83 | var bytes = this._bytes; 84 | if (bytes == null) { 85 | bytes = new ByteArray(); 86 | this._bytes = bytes; 87 | } else { 88 | bytes.clear(); 89 | } 90 | } 91 | 92 | appendChar(c: ushort): StringBuilder { 93 | this._bytes.append(c as byte); 94 | this._bytes.append((c >> 8) as byte); 95 | return this; 96 | } 97 | 98 | // TODO: Make this more efficient 99 | appendSlice(text: string, start: int, end: int): StringBuilder { 100 | return this.append(text.slice(start, end)); 101 | } 102 | 103 | append(text: string): StringBuilder { 104 | var bytes = this._bytes; 105 | var index = bytes._length; 106 | var length = text.length; 107 | unsafe { 108 | bytes.resize(index as int + length * 2); 109 | memcpy(bytes._data + index, text as *byte + 4, length as uint * 2); 110 | } 111 | return this; 112 | } 113 | 114 | // This also "frees" this object (puts it back in the pool) 115 | finish(): string { 116 | this.next = stringBuilderPool; 117 | stringBuilderPool = this; 118 | unsafe { 119 | var bytes = this._bytes; 120 | var length = bytes._length as uint / 2; 121 | var ptr = string_new(length); 122 | memcpy(ptr as *byte + 4, bytes._data, length * 2); 123 | return ptr; 124 | } 125 | } 126 | } 127 | 128 | #else //////////////////////////////////////////////////////////////////////////////// 129 | 130 | declare class StringBuilder { 131 | next: StringBuilder; 132 | clear(): void; 133 | appendChar(c: ushort): StringBuilder; 134 | appendSlice(text: string, start: int, end: int): StringBuilder; 135 | append(text: string): StringBuilder; 136 | finish(): string; 137 | } 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /src/symbol.thin: -------------------------------------------------------------------------------- 1 | enum SymbolKind { 2 | TYPE_CLASS, 3 | TYPE_ENUM, 4 | TYPE_GLOBAL, 5 | TYPE_NATIVE, 6 | 7 | FUNCTION_INSTANCE, 8 | FUNCTION_GLOBAL, 9 | 10 | VARIABLE_ARGUMENT, 11 | VARIABLE_CONSTANT, 12 | VARIABLE_GLOBAL, 13 | VARIABLE_INSTANCE, 14 | VARIABLE_LOCAL, 15 | } 16 | 17 | function isType(kind: SymbolKind): bool { 18 | return kind >= SymbolKind.TYPE_CLASS && kind <= SymbolKind.TYPE_NATIVE; 19 | } 20 | 21 | function isFunction(kind: SymbolKind): bool { 22 | return kind >= SymbolKind.FUNCTION_INSTANCE && kind <= SymbolKind.FUNCTION_GLOBAL; 23 | } 24 | 25 | function isVariable(kind: SymbolKind): bool { 26 | return kind >= SymbolKind.VARIABLE_ARGUMENT && kind <= SymbolKind.VARIABLE_LOCAL; 27 | } 28 | 29 | enum SymbolState { 30 | UNINITIALIZED, 31 | INITIALIZING, 32 | INITIALIZED, 33 | } 34 | 35 | const SYMBOL_FLAG_CONVERT_INSTANCE_TO_GLOBAL = 1 << 0; 36 | const SYMBOL_FLAG_IS_BINARY_OPERATOR = 1 << 1; 37 | const SYMBOL_FLAG_IS_REFERENCE = 1 << 2; 38 | const SYMBOL_FLAG_IS_UNARY_OPERATOR = 1 << 3; 39 | const SYMBOL_FLAG_IS_UNSIGNED = 1 << 4; 40 | const SYMBOL_FLAG_NATIVE_INTEGER = 1 << 5; 41 | const SYMBOL_FLAG_USED = 1 << 6; 42 | 43 | class Symbol { 44 | kind: SymbolKind; 45 | name: string; 46 | node: Node; 47 | range: Range; 48 | scope: Scope; 49 | resolvedType: Type; 50 | next: Symbol; 51 | state: SymbolState; 52 | flags: int; 53 | byteSize: int; 54 | maxAlignment: int; 55 | rename: string; 56 | 57 | // The "offset" variable is used to store kind-specific information 58 | // 59 | // TYPE_CLASS: N/A 60 | // TYPE_ENUM: N/A 61 | // TYPE_GLOBAL: N/A 62 | // TYPE_NATIVE: N/A 63 | // 64 | // FUNCTION_INSTANCE: N/A 65 | // FUNCTION_GLOBAL: N/A 66 | // 67 | // VARIABLE_ARGUMENT: Argument index 68 | // VARIABLE_CONSTANT: Integer constant value 69 | // VARIABLE_GLOBAL: Memory address relative to start address 70 | // VARIABLE_INSTANCE: Instance offset 71 | // VARIABLE_LOCAL: N/A 72 | // 73 | offset: int; 74 | 75 | isEnumValue(): bool { 76 | return this.node.parent.kind == NodeKind.ENUM; 77 | } 78 | 79 | isUnsafe(): bool { 80 | return this.node != null && this.node.isUnsafe(); 81 | } 82 | 83 | isGetter(): bool { 84 | return this.node.isGet(); 85 | } 86 | 87 | isSetter(): bool { 88 | return this.node.isSet(); 89 | } 90 | 91 | isBinaryOperator(): bool { 92 | return (this.flags & SYMBOL_FLAG_IS_BINARY_OPERATOR) != 0; 93 | } 94 | 95 | isUnaryOperator(): bool { 96 | return (this.flags & SYMBOL_FLAG_IS_UNARY_OPERATOR) != 0; 97 | } 98 | 99 | shouldConvertInstanceToGlobal(): bool { 100 | return (this.flags & SYMBOL_FLAG_CONVERT_INSTANCE_TO_GLOBAL) != 0; 101 | } 102 | 103 | isUsed(): bool { 104 | return (this.flags & SYMBOL_FLAG_USED) != 0; 105 | } 106 | 107 | parent(): Symbol { 108 | var parent = this.node.parent; 109 | return parent.kind == NodeKind.CLASS ? parent.symbol : null; 110 | } 111 | 112 | resolvedTypeUnderlyingIfEnumValue(context: CheckContext): Type { 113 | return this.isEnumValue() ? this.resolvedType.underlyingType(context) : this.resolvedType; 114 | } 115 | 116 | determineClassLayout(context: CheckContext): void { 117 | assert(this.kind == SymbolKind.TYPE_CLASS); 118 | 119 | // Only determine class layout once 120 | if (this.byteSize != 0) { 121 | return; 122 | } 123 | 124 | var offset = 0; 125 | var child = this.node.firstChild; 126 | var maxAlignment = 1; 127 | 128 | while (child != null) { 129 | if (child.kind == NodeKind.VARIABLE) { 130 | var type = child.symbol.resolvedType; 131 | 132 | // Ignore invalid members 133 | if (type != context.errorType) { 134 | var alignmentOf = type.variableAlignmentOf(context); 135 | 136 | // Align the member to the next available slot 137 | offset = alignToNextMultipleOf(offset, alignmentOf); 138 | if (alignmentOf > maxAlignment) maxAlignment = alignmentOf; 139 | 140 | // Allocate the member by extending the object 141 | child.symbol.offset = offset; 142 | offset = offset + type.variableSizeOf(context); 143 | } 144 | } 145 | 146 | child = child.nextSibling; 147 | } 148 | 149 | // All objects must have a non-zero size 150 | if (offset == 0) { 151 | offset = 1; 152 | } 153 | 154 | // The object size must be a multiple of the maximum alignment for arrays to work correctly 155 | offset = alignToNextMultipleOf(offset, maxAlignment); 156 | 157 | this.byteSize = offset; 158 | this.maxAlignment = maxAlignment; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/type.thin: -------------------------------------------------------------------------------- 1 | enum ConversionKind { 2 | IMPLICIT, 3 | EXPLICIT, 4 | } 5 | 6 | class Type { 7 | symbol: Symbol; 8 | pointerTo: Type; 9 | private cachedToString: string; 10 | private cachedPointerType: Type; 11 | 12 | isClass(): bool { 13 | return this.symbol != null && this.symbol.kind == SymbolKind.TYPE_CLASS; 14 | } 15 | 16 | isEnum(): bool { 17 | return this.symbol != null && this.symbol.kind == SymbolKind.TYPE_ENUM; 18 | } 19 | 20 | isInteger(): bool { 21 | return this.symbol != null && (this.symbol.flags & SYMBOL_FLAG_NATIVE_INTEGER) != 0 || this.isEnum(); 22 | } 23 | 24 | isUnsigned(): bool { 25 | return this.symbol != null && (this.symbol.flags & SYMBOL_FLAG_IS_UNSIGNED) != 0; 26 | } 27 | 28 | isReference(): bool { 29 | return this.pointerTo != null || this.symbol != null && (this.symbol.flags & SYMBOL_FLAG_IS_REFERENCE) != 0; 30 | } 31 | 32 | underlyingType(context: CheckContext): Type { 33 | return this.isEnum() ? context.intType : this.pointerTo != null ? context.uintType : this; 34 | } 35 | 36 | integerBitCount(context: CheckContext): int { 37 | return this.symbol != null ? this.symbol.byteSize * 8 : 0; 38 | } 39 | 40 | integerBitMask(context: CheckContext): uint { 41 | return ~0 as uint >> (32 - this.integerBitCount(context)) as uint; 42 | } 43 | 44 | allocationSizeOf(context: CheckContext): int { 45 | return this.symbol == null ? context.pointerByteSize : this.symbol.byteSize; 46 | } 47 | 48 | allocationAlignmentOf(context: CheckContext): int { 49 | return this.allocationSizeOf(context); // This is true right now 50 | } 51 | 52 | variableSizeOf(context: CheckContext): int { 53 | return this.isReference() ? context.pointerByteSize : this.symbol.byteSize; 54 | } 55 | 56 | variableAlignmentOf(context: CheckContext): int { 57 | return this.variableSizeOf(context); // This is true right now 58 | } 59 | 60 | pointerType(): Type { 61 | var type = this.cachedPointerType; 62 | if (type == null) { 63 | type = new Type(); 64 | type.pointerTo = this; 65 | this.cachedPointerType = type; 66 | } 67 | return type; 68 | } 69 | 70 | toString(): string { 71 | if (this.cachedToString == null) { 72 | this.cachedToString = 73 | this.pointerTo != null ? StringBuilder_new().appendChar('*').append(this.pointerTo.toString()).finish() : 74 | this.symbol.name; 75 | } 76 | return this.cachedToString; 77 | } 78 | 79 | findMember(name: string, hint: ScopeHint): Symbol { 80 | var symbol = this.symbol; 81 | return symbol != null && symbol.scope != null ? symbol.scope.findLocal(name, hint) : null; 82 | } 83 | 84 | hasInstanceMembers(): bool { 85 | var symbol = this.symbol; 86 | return symbol != null && (symbol.kind == SymbolKind.TYPE_CLASS || symbol.kind == SymbolKind.TYPE_NATIVE); 87 | } 88 | } 89 | --------------------------------------------------------------------------------