├── .npmignore ├── .gitignore ├── tests ├── asmjs │ ├── Makefile │ ├── linkedlist.bit │ ├── vtables.html │ ├── linkedlist.html │ └── vtables.bit ├── common.ts ├── circular.ts ├── list.bit ├── statements.ts ├── modifiers.ts └── operators.ts ├── README.md ├── package.json ├── src ├── compiler.ts ├── symbols.ts ├── common.ts ├── parser.ts ├── nativetypes.ts ├── binarylayout.ts ├── tokens.ts ├── typelogic.ts ├── types.ts ├── diagnostics.ts ├── cli.ts ├── grammar.ts ├── ast.ts ├── output.js.ts └── output.cpp.ts ├── Makefile └── demo ├── codemirror.css ├── index.html └── source-map.min.js /.npmignore: -------------------------------------------------------------------------------- 1 | /compiled.js 2 | /compiled.js.map 3 | /test.js 4 | /test.js.map 5 | /tests/ 6 | /src/ 7 | /demo/ 8 | /Makefile 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /compiled.js 3 | /compiled.js.map 4 | /test.js 5 | /test.js.map 6 | /bitc 7 | /tests/asmjs/*.js 8 | /tests/asmjs/*.js.map 9 | -------------------------------------------------------------------------------- /tests/asmjs/Makefile: -------------------------------------------------------------------------------- 1 | all: linkedlist 2 | 3 | linkedlist: 4 | node ../../compiled.js linkedlist.bit --js linkedlist.js --asmjs linkedlist.asm.js 5 | node ../../compiled.js vtables.bit --js vtables.js --asmjs vtables.asm.js 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BitScript 2 | 3 | BitScript is an experimental language that compiles to both JavaScript and C++. Memory management is done using annotations for shared and owned pointers. These annotations are thrown away when compiling to JavaScript but are converted to smart pointers when compiling to C++. 4 | 5 | Demo: [http://evanw.github.io/bitscript/demo/](http://evanw.github.io/bitscript/demo/) 6 | -------------------------------------------------------------------------------- /tests/common.ts: -------------------------------------------------------------------------------- 1 | declare var it: any; 2 | declare var require: any; 3 | 4 | require('source-map-support').install(); 5 | 6 | function test(lines: string[], expected: string[]) { 7 | it(lines.join(' ').replace(/\s+/g, ' '), () => { 8 | var compiler = new Compiler(); 9 | compiler.addSource('', lines.join('\n')); 10 | compiler.compile(); 11 | require('assert').strictEqual(compiler.log.diagnostics.join('\n\n').trim(), expected.join('\n')); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /tests/asmjs/linkedlist.bit: -------------------------------------------------------------------------------- 1 | class Link { 2 | int data; 3 | owned Link next; 4 | } 5 | 6 | double main() { 7 | // Make a long linked list 8 | owned Link head; 9 | int i; 10 | for (i = 0; i < 10000; i = i + 1) { 11 | head = new Link(i, move head); 12 | } 13 | 14 | // Do some O(n^2) operation 15 | Link outer; 16 | Link inner; 17 | double total = 0; 18 | for (outer = head; outer != null; outer = outer.next) { 19 | for (inner = outer; inner != null; inner = inner.next) { 20 | total = total + inner.data; 21 | } 22 | } 23 | return total; 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitscript", 3 | "description": "An experimental language that compiles to JavaScript and C++", 4 | "version": "0.1.0", 5 | "bin": { 6 | "bitc": "./bitc" 7 | }, 8 | "main": "./bitc", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/evanw/bitscript.git" 12 | }, 13 | "dependencies": { 14 | "terminal-notifier": "0.1.2", 15 | "cppcodegen": "0.1.0", 16 | "escodegen": "0.0.26", 17 | "esprima": "1.0.3" 18 | }, 19 | "devDependencies": { 20 | "source-map-support": "*", 21 | "typescript": "0.9.1", 22 | "mocha": "1.12.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/asmjs/vtables.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /tests/asmjs/linkedlist.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /tests/asmjs/vtables.bit: -------------------------------------------------------------------------------- 1 | void output(int value); 2 | 3 | class A { 4 | int foo(); 5 | int bar(); 6 | } 7 | 8 | class B : A { 9 | over int foo() { return 1; } 10 | over int bar() { return 2; } 11 | } 12 | 13 | class C : A { 14 | over int foo() { return 3; } 15 | over int bar() { return 4; } 16 | } 17 | 18 | class D : B { 19 | over int foo() { return 5; } 20 | over int bar() { return 6; } 21 | } 22 | 23 | void testA(A a) { 24 | output(a.foo()); 25 | output(a.bar()); 26 | } 27 | 28 | void testB(B b) { 29 | output(b.foo()); 30 | output(b.bar()); 31 | } 32 | 33 | void testC(C c) { 34 | output(c.foo()); 35 | output(c.bar()); 36 | } 37 | 38 | void testD(D d) { 39 | output(d.foo()); 40 | output(d.bar()); 41 | } 42 | 43 | void main() { 44 | testA(new B()); 45 | testA(new C()); 46 | testA(new D()); 47 | 48 | testB(new B()); 49 | testB(new D()); 50 | 51 | testC(new C()); 52 | 53 | testD(new D()); 54 | } 55 | -------------------------------------------------------------------------------- /src/compiler.ts: -------------------------------------------------------------------------------- 1 | class Compiler { 2 | log: Log = new Log(); 3 | sources: Source[] = []; 4 | tokens: Token[] = []; 5 | module: Module = null; 6 | 7 | addSource(fileName: string, input: string) { 8 | this.sources.push(new Source(fileName, input)); 9 | } 10 | 11 | compile() { 12 | // Tokenize and parse each module individually 13 | var modules: Module[] = this.sources.map(source => { 14 | var errorCount: number = this.log.errorCount; 15 | var tokens: Token[] = prepareTokens(tokenize(this.log, source)); 16 | this.tokens = this.tokens.concat(tokens); 17 | return this.log.errorCount === errorCount ? parse(this.log, tokens) : null; 18 | }); 19 | if (this.log.errorCount > 0) return; 20 | 21 | // Create one module and resolve everything together 22 | this.module = new Module(null, new Block(null, flatten(modules.map(n => n.block.statements)))); 23 | Resolver.resolve(this.log, this.module); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | TSC=node_modules/typescript/bin/tsc 4 | MOCHA=node_modules/mocha/bin/mocha 5 | 6 | SOURCES= \ 7 | src/common.ts \ 8 | src/diagnostics.ts \ 9 | src/tokens.ts \ 10 | src/types.ts \ 11 | src/symbols.ts \ 12 | src/nativetypes.ts \ 13 | src/ast.ts \ 14 | src/parser.ts \ 15 | src/grammar.ts \ 16 | src/typelogic.ts \ 17 | src/resolver.ts \ 18 | src/compiler.ts \ 19 | src/binarylayout.ts \ 20 | src/output.js.ts \ 21 | src/output.asmjs.ts \ 22 | src/output.cpp.ts 23 | 24 | TESTS= \ 25 | tests/common.ts \ 26 | tests/statements.ts \ 27 | tests/operators.ts \ 28 | tests/modifiers.ts \ 29 | tests/circular.ts 30 | 31 | build: 32 | $(TSC) $(SOURCES) src/cli.ts --sourcemap --out compiled.js 33 | python -c 'open("bitc", "w").write(open("compiled.js").read().replace("var usr_bin_env_node;", "#!/usr/bin/env node"))' 34 | chmod +x bitc 35 | 36 | watch: 37 | $(TSC) $(SOURCES) src/cli.ts --sourcemap --out compiled.js -w 38 | 39 | test: 40 | $(TSC) $(SOURCES) $(TESTS) --sourcemap --out test.js 41 | $(MOCHA) 42 | -------------------------------------------------------------------------------- /tests/circular.ts: -------------------------------------------------------------------------------- 1 | test([ 2 | 'class A : A {}', 3 | ], [ 4 | 'error on line 1 of : circular type', 5 | '', 6 | 'class A : A {}', 7 | ' ^', 8 | ]); 9 | 10 | test([ 11 | 'class A : B {}', 12 | 'class B : A {}', 13 | ], [ 14 | 'error on line 2 of : circular type', 15 | '', 16 | 'class B : A {}', 17 | ' ^', 18 | ]); 19 | 20 | test([ 21 | 'class A : B {}', 22 | 'class B : C {}', 23 | 'class C : A {}', 24 | ], [ 25 | 'error on line 3 of : circular type', 26 | '', 27 | 'class C : A {}', 28 | ' ^', 29 | ]); 30 | 31 | test([ 32 | 'class A { C c; }', 33 | 'class B : A {}', 34 | 'class C { B b; }', 35 | ], [ 36 | ]); 37 | 38 | test([ 39 | 'class A {', 40 | ' B foo();', 41 | '}', 42 | 'class B : A {', 43 | ' over B foo() { // This line should not cause a circular type error', 44 | ' new A(); // This should be detected as abstract', 45 | ' new B(); // This should not be detected as abstract', 46 | ' return this;', 47 | ' }', 48 | '}', 49 | ], [ 50 | 'error on line 6 of : cannot use new on abstract type A', 51 | '', 52 | ' new A(); // This should be detected as abstract', 53 | ' ^', 54 | ]); 55 | 56 | test([ 57 | 'class A {', 58 | ' void foo() {', 59 | ' new B(0); // This should not crash', 60 | ' }', 61 | '}', 62 | 'class B {', 63 | ' int x;', 64 | '}', 65 | ], [ 66 | ]); 67 | 68 | test([ 69 | 'class Bar : Foo {}', 70 | 'void foo() {', 71 | ' new Bar(); // This should be detected as abstract', 72 | '}', 73 | 'class Foo {', 74 | ' void bar();', 75 | '}', 76 | 'void baz() {', 77 | ' new Bar(); // This should be detected as abstract', 78 | '}', 79 | ], [ 80 | 'error on line 3 of : cannot use new on abstract type Bar', 81 | '', 82 | ' new Bar(); // This should be detected as abstract', 83 | ' ~~~', 84 | '', 85 | 'error on line 9 of : cannot use new on abstract type Bar', 86 | '', 87 | ' new Bar(); // This should be detected as abstract', 88 | ' ~~~', 89 | ]); 90 | -------------------------------------------------------------------------------- /tests/list.bit: -------------------------------------------------------------------------------- 1 | class Foo { 2 | } 3 | 4 | void testOwnedList() { 5 | owned List foo = new List(); 6 | foo.push(new Foo()); 7 | foo.push(new Foo()); 8 | foo.push(new Foo()); 9 | foo.unshift(new Foo()); 10 | foo.unshift(new Foo()); 11 | foo.unshift(new Foo()); 12 | 13 | // Test indexOf 14 | foo.indexOf(foo.get(0)); 15 | foo.indexOf(new Foo()); 16 | 17 | // Test owned 18 | owned Foo f1 = foo.pop(); 19 | owned Foo f2 = foo.shift(); 20 | 21 | // Test shared 22 | shared List bar = foo; 23 | owned Foo f3 = bar.pop(); 24 | owned Foo f4 = bar.shift(); 25 | 26 | // Test raw 27 | List baz = bar; 28 | owned Foo f5 = baz.pop(); 29 | owned Foo f6 = baz.shift(); 30 | } 31 | 32 | void testSharedList() { 33 | owned List foo = new List(); 34 | foo.push(new Foo()); 35 | foo.push(new Foo()); 36 | foo.push(new Foo()); 37 | foo.unshift(new Foo()); 38 | foo.unshift(new Foo()); 39 | foo.unshift(new Foo()); 40 | 41 | // Test indexOf 42 | foo.indexOf(foo.get(0)); 43 | foo.indexOf(new Foo()); 44 | 45 | // Test owned 46 | shared Foo f1 = foo.pop(); 47 | shared Foo f2 = foo.shift(); 48 | 49 | // Test shared 50 | shared List bar = foo; 51 | shared Foo f3 = bar.pop(); 52 | shared Foo f4 = bar.shift(); 53 | 54 | // Test raw 55 | List baz = bar; 56 | shared Foo f5 = baz.pop(); 57 | shared Foo f6 = baz.shift(); 58 | } 59 | 60 | void testRawList( 61 | owned Foo i1, owned Foo i2, owned Foo i3, 62 | owned Foo i4, owned Foo i5, owned Foo i6) { 63 | owned List foo = new List(); 64 | foo.push(i1); 65 | foo.push(i2); 66 | foo.push(i3); 67 | foo.unshift(i4); 68 | foo.unshift(i5); 69 | foo.unshift(i6); 70 | 71 | // Test indexOf 72 | foo.indexOf(foo.get(0)); 73 | foo.indexOf(new Foo()); 74 | 75 | // Test owned 76 | Foo f1 = foo.pop(); 77 | Foo f2 = foo.shift(); 78 | 79 | // Test shared 80 | shared List bar = foo; 81 | Foo f3 = bar.pop(); 82 | Foo f4 = bar.shift(); 83 | 84 | // Test raw 85 | List baz = bar; 86 | Foo f5 = baz.pop(); 87 | Foo f6 = baz.shift(); 88 | } 89 | 90 | int main() { 91 | testOwnedList(); 92 | testSharedList(); 93 | testRawList( 94 | new Foo(), new Foo(), new Foo(), 95 | new Foo(), new Foo(), new Foo()); 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /src/symbols.ts: -------------------------------------------------------------------------------- 1 | // TODO: static members 2 | enum SymbolModifier { 3 | OVER = 1, // Is this symbol hiding another symbol from the base type? 4 | } 5 | 6 | class Symbol { 7 | modifiers: number = 0; 8 | node: Declaration = null; 9 | enclosingObject: ObjectType = null; 10 | overriddenSymbol: Symbol = null; 11 | isOverridden: boolean = false; 12 | isAbstract: boolean = false; 13 | 14 | // This means the byte offset in the object for object fields and the 15 | // byte offset in the object's vtable for virtual member functions. 16 | byteOffset: number = 0; 17 | 18 | constructor( 19 | public name: string, 20 | public type: WrappedType, 21 | public scope: Scope) { 22 | } 23 | 24 | isOver(): boolean { 25 | return (this.modifiers & SymbolModifier.OVER) !== 0; 26 | } 27 | 28 | isVirtual(): boolean { 29 | return this.isAbstract || this.isOverridden || this.overriddenSymbol !== null; 30 | } 31 | 32 | originalOverriddenSymbol(): Symbol { 33 | var symbol: Symbol = this.overriddenSymbol; 34 | while (symbol !== null && symbol.overriddenSymbol !== null) { 35 | symbol = symbol.overriddenSymbol; 36 | } 37 | return symbol; 38 | } 39 | } 40 | 41 | enum ForEachSymbol { 42 | CONTINUE, 43 | BREAK, 44 | } 45 | 46 | class Scope { 47 | // Note: All symbols are prefixed with ' ' to avoid collisions with native properties (i.e. __proto__) 48 | private symbols: { [name: string]: Symbol } = {}; 49 | 50 | constructor( 51 | public lexicalParent: Scope) { 52 | } 53 | 54 | // Return value determines continue vs break 55 | forEachSymbol(callback: (symbol: Symbol) => ForEachSymbol) { 56 | for (var name in this.symbols) { 57 | if (name[0] === ' ' && callback(this.symbols[name]) === ForEachSymbol.BREAK) { 58 | break; 59 | } 60 | } 61 | } 62 | 63 | containsAbstractSymbols(): boolean { 64 | var isAbstract: boolean = false; 65 | this.forEachSymbol(s => { 66 | if (s.isAbstract) isAbstract = true; 67 | return isAbstract ? ForEachSymbol.BREAK : ForEachSymbol.CONTINUE; 68 | }); 69 | return isAbstract; 70 | } 71 | 72 | replace(symbol: Symbol) { 73 | this.symbols[' ' + symbol.name] = symbol; 74 | } 75 | 76 | define(name: string, type: WrappedType): Symbol { 77 | return this.symbols[' ' + name] = new Symbol(name, type, this); 78 | } 79 | 80 | find(name: string): Symbol { 81 | return this.symbols[' ' + name] || null; 82 | } 83 | 84 | lexicalFind(name: string): Symbol { 85 | var symbol: Symbol = this.find(name); 86 | if (symbol === null && this.lexicalParent !== null) { 87 | return this.lexicalParent.lexicalFind(name); 88 | } 89 | return symbol; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/statements.ts: -------------------------------------------------------------------------------- 1 | test([ 2 | '100;', 3 | ], [ 4 | 'error on line 1 of : cannot use expression statement here', 5 | '', 6 | '100;', 7 | '~~~~', 8 | ]); 9 | 10 | test([ 11 | 'if (true) {}', 12 | ], [ 13 | 'error on line 1 of : cannot use if statement here', 14 | '', 15 | 'if (true) {}', 16 | '~~~~~~~~~~~~', 17 | ]); 18 | 19 | test([ 20 | 'while (true) {}', 21 | ], [ 22 | 'error on line 1 of : cannot use while statement here', 23 | '', 24 | 'while (true) {}', 25 | '~~~~~~~~~~~~~~~', 26 | ]); 27 | 28 | test([ 29 | 'return;', 30 | ], [ 31 | 'error on line 1 of : cannot use return statement here', 32 | '', 33 | 'return;', 34 | '~~~~~~~', 35 | ]); 36 | 37 | test([ 38 | 'break;', 39 | ], [ 40 | 'error on line 1 of : cannot use break statement here', 41 | '', 42 | 'break;', 43 | '~~~~~~', 44 | ]); 45 | 46 | test([ 47 | 'continue;', 48 | ], [ 49 | 'error on line 1 of : cannot use continue statement here', 50 | '', 51 | 'continue;', 52 | '~~~~~~~~~', 53 | ]); 54 | 55 | test([ 56 | 'class Foo {', 57 | ' class Bar {}', 58 | '}', 59 | ], [ 60 | 'error on line 2 of : cannot use class declaration here', 61 | '', 62 | ' class Bar {}', 63 | ' ~~~~~~~~~~~~', 64 | ]); 65 | 66 | test([ 67 | 'void foo() {', 68 | ' class Foo {}', 69 | '}', 70 | ], [ 71 | 'error on line 2 of : cannot use class declaration here', 72 | '', 73 | ' class Foo {}', 74 | ' ~~~~~~~~~~~~', 75 | ]); 76 | 77 | test([ 78 | 'class Foo {', 79 | ' void foo() {}', 80 | '}', 81 | ], [ 82 | ]); 83 | 84 | test([ 85 | 'void foo() {', 86 | ' void bar() {}', 87 | '}', 88 | ], [ 89 | 'error on line 2 of : cannot use function declaration here', 90 | '', 91 | ' void bar() {}', 92 | ' ~~~~~~~~~~~~~', 93 | ]); 94 | 95 | test([ 96 | 'void foo() {', 97 | ' 100;', 98 | '}', 99 | ], [ 100 | ]); 101 | 102 | test([ 103 | 'void foo() {', 104 | ' if (true) {}', 105 | '}', 106 | ], [ 107 | ]); 108 | 109 | test([ 110 | 'void foo() {', 111 | ' while (true) {}', 112 | '}', 113 | ], [ 114 | ]); 115 | 116 | test([ 117 | 'void foo() {', 118 | ' return;', 119 | '}', 120 | ], [ 121 | ]); 122 | 123 | test([ 124 | 'void foo() {', 125 | ' break;', 126 | '}', 127 | ], [ 128 | 'error on line 2 of : cannot use break statement here', 129 | '', 130 | ' break;', 131 | ' ~~~~~~', 132 | ]); 133 | 134 | test([ 135 | 'void foo() {', 136 | ' continue;', 137 | '}', 138 | ], [ 139 | 'error on line 2 of : cannot use continue statement here', 140 | '', 141 | ' continue;', 142 | ' ~~~~~~~~~', 143 | ]); 144 | 145 | test([ 146 | 'void foo() {', 147 | ' while (true) break;', 148 | '}', 149 | ], [ 150 | ]); 151 | 152 | test([ 153 | 'void foo() {', 154 | ' while (true) continue;', 155 | '}', 156 | ], [ 157 | ]); 158 | 159 | test([ 160 | 'int foo;', 161 | ], [ 162 | ]); 163 | 164 | test([ 165 | 'int foo = 100;', 166 | ], [ 167 | ]); 168 | 169 | test([ 170 | 'class Foo {', 171 | ' int foo;', 172 | '}', 173 | ], [ 174 | ]); 175 | 176 | test([ 177 | 'class Foo {', 178 | ' int foo = 100;', 179 | '}', 180 | ], [ 181 | ]); 182 | 183 | test([ 184 | 'void foo() {', 185 | ' int bar;', 186 | '}', 187 | ], [ 188 | ]); 189 | 190 | test([ 191 | 'void foo() {', 192 | ' int bar = 100;', 193 | '}', 194 | ], [ 195 | ]); 196 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | declare var usr: any; 2 | declare var module: any; 3 | declare var exports: any; 4 | declare var require: any; 5 | declare var process: any; 6 | 7 | var usr_bin_env_node; // This will turn into '#!/usr/bin/env node' but must be here to reserve the line in the source map 8 | 9 | if (typeof process !== 'undefined') { 10 | require('source-map-support').install(); 11 | } 12 | 13 | if (typeof esprima === 'undefined') { 14 | var esprima = require('esprima'); 15 | } 16 | 17 | if (typeof escodegen === 'undefined') { 18 | var escodegen = require('escodegen'); 19 | } 20 | 21 | if (typeof cppcodegen === 'undefined') { 22 | var cppcodegen = require('cppcodegen'); 23 | } 24 | 25 | function assert(truth: boolean) { 26 | if (!truth) { 27 | throw new Error('assertion failed'); 28 | } 29 | } 30 | 31 | function repeat(text: string, times: number): string { 32 | return new Array(times + 1).join(text); 33 | } 34 | 35 | function flatten(array: any[][]): any[] { 36 | return Array.prototype.concat.apply(Array.prototype, array); 37 | } 38 | 39 | function stableSort(array: T[], compare: (left: T, right: T) => number) { 40 | // Optimized bubble-sort from http://en.wikipedia.org/wiki/Bubble_sort 41 | var current = array.length; 42 | while (current > 0) { 43 | var next = 0; 44 | for (var i = 1; i < current; i++) { 45 | if (compare(array[i - 1], array[i]) > 0) { 46 | var temp = array[i - 1]; 47 | array[i - 1] = array[i]; 48 | array[i] = temp; 49 | next = i; 50 | } 51 | } 52 | current = next; 53 | } 54 | } 55 | 56 | function nextMultipleOf(size: number, align: number): number { 57 | return size + (align - size % align) % align; 58 | } 59 | 60 | class Source { 61 | lines: string[]; 62 | 63 | constructor( 64 | public name: string, 65 | public contents: string) { 66 | this.lines = contents.split('\n'); 67 | } 68 | } 69 | 70 | class Marker { 71 | constructor( 72 | public index: number, 73 | public line: number, 74 | public column: number) { 75 | } 76 | } 77 | 78 | class SourceRange { 79 | constructor( 80 | public source: Source, 81 | public start: Marker, 82 | public end: Marker) { 83 | } 84 | 85 | locationString(): string { 86 | return 'on line ' + this.start.line + ' of ' + this.source.name; 87 | } 88 | 89 | sourceString(): string { 90 | var line: string = this.source.lines[this.start.line - 1]; 91 | var a: number = this.start.column - 1; 92 | var b: number = this.end.line === this.start.line ? this.end.column - 1 : line.length; 93 | return line + '\n' + repeat(' ', a) + (b - a < 2 ? '^' : repeat('~', b - a)); 94 | } 95 | } 96 | 97 | class Diagnostic { 98 | constructor( 99 | public type: string, 100 | public range: SourceRange, 101 | public text: string) { 102 | } 103 | 104 | toString(): string { 105 | return this.type + ' ' + this.range.locationString() + ': ' + this.text + '\n\n' + this.range.sourceString(); 106 | } 107 | } 108 | 109 | class Log { 110 | diagnostics: Diagnostic[] = []; 111 | errorCount: number = 0; 112 | 113 | error(range: SourceRange, text: string) { 114 | this.diagnostics.push(new Diagnostic('error', range, text)); 115 | this.errorCount++; 116 | } 117 | 118 | warning(range: SourceRange, text: string) { 119 | this.diagnostics.push(new Diagnostic('warning', range, text)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | function spanRange(start: SourceRange, end: SourceRange): SourceRange { 2 | assert(start.source === end.source && start.start.index <= end.end.index); 3 | return new SourceRange(start.source, start.start, end.end); 4 | } 5 | 6 | class ParserContext { 7 | index: number = 0; 8 | 9 | constructor( 10 | public log: Log, 11 | public tokens: Token[]) { 12 | } 13 | 14 | current(): Token { 15 | return this.tokens[this.index]; 16 | } 17 | 18 | next(): Token { 19 | var token: Token = this.current(); 20 | if (this.index + 1 < this.tokens.length) { this.index++; } 21 | return token; 22 | } 23 | 24 | spanSince(range: SourceRange): SourceRange { 25 | return spanRange(range, this.tokens[this.index > 0 ? this.index - 1 : 0].range); 26 | } 27 | 28 | peek(kind: string): boolean { 29 | return this.current().kind === kind; 30 | } 31 | 32 | eat(kind: string): boolean { 33 | if (this.peek(kind)) { 34 | this.next(); 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | expect(kind: string): boolean { 41 | if (!this.eat(kind)) { 42 | syntaxErrorExpectedToken(this.log, this.current(), kind); 43 | return false; 44 | } 45 | return true; 46 | } 47 | } 48 | 49 | class Parselet { 50 | prefix: (context: ParserContext) => Expression = null; 51 | infix: (context: ParserContext, left: Expression) => Expression = null; 52 | 53 | constructor( 54 | public power: number) { 55 | } 56 | } 57 | 58 | // A Pratt parser is a parser that associates up to two operations per token, 59 | // each with its own precedence. Pratt parsers excel at parsing expression 60 | // trees with deeply nested precedence levels. For an excellent writeup, see: 61 | // 62 | // http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ 63 | // 64 | class Pratt { 65 | table: { [index: string]: Parselet } = {}; 66 | 67 | parselet(kind: string, power: number): Parselet { 68 | if (kind in this.table) { 69 | var parselet: Parselet = this.table[kind]; 70 | if (power > parselet.power) parselet.power = power; 71 | return parselet; 72 | } 73 | return this.table[kind] = new Parselet(power); 74 | } 75 | 76 | parse(context: ParserContext, power: number): Expression { 77 | var kind: string = context.current().kind; 78 | var parselet: Parselet = this.table[kind] || null; 79 | if (parselet === null || parselet.prefix === null) { 80 | syntaxErrorUnexpectedToken(context.log, context.current()); 81 | return null; 82 | } 83 | return this.resume(context, power, parselet.prefix(context)); 84 | } 85 | 86 | resume(context: ParserContext, power: number, left: Expression): Expression { 87 | while (left !== null) { 88 | var kind: string = context.current().kind; 89 | var parselet: Parselet = this.table[kind] || null; 90 | if (parselet === null || parselet.infix === null || parselet.power <= power) break; 91 | left = parselet.infix(context, left); 92 | } 93 | return left; 94 | } 95 | 96 | literal(kind: string, callback: (context: ParserContext, token: Token) => Expression) { 97 | this.parselet(kind, Power.LOWEST).prefix = context => callback(context, context.next()); 98 | } 99 | 100 | prefix(kind: string, power: number, callback: (context: ParserContext, token: Token, value: Expression) => Expression) { 101 | this.parselet(kind, Power.LOWEST).prefix = context => { 102 | var token: Token = context.next(); 103 | var value: Expression = this.parse(context, power); 104 | return value !== null ? callback(context, token, value) : null; 105 | }; 106 | } 107 | 108 | postfix(kind: string, power: number, callback: (context: ParserContext, value: Expression, token: Token) => Expression) { 109 | this.parselet(kind, power).infix = (context, left) => { 110 | return callback(context, left, context.next()); 111 | }; 112 | } 113 | 114 | infix(kind: string, power: number, callback: (context: ParserContext, left: Expression, token: Token, right: Expression) => Expression) { 115 | this.parselet(kind, power).infix = (context, left) => { 116 | var token: Token = context.next(); 117 | var right: Expression = this.parse(context, power); 118 | return right !== null ? callback(context, left, token, right) : null; 119 | }; 120 | } 121 | 122 | infixRight(kind: string, power: number, callback: (context: ParserContext, left: Expression, token: Token, right: Expression) => Expression) { 123 | this.parselet(kind, power).infix = (context, left) => { 124 | var token: Token = context.next(); 125 | var right: Expression = this.parse(context, power - 1); // Subtract 1 for right-associativity 126 | return right !== null ? callback(context, left, token, right) : null; 127 | }; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/nativetypes.ts: -------------------------------------------------------------------------------- 1 | class NativeTypes { 2 | static MATH: ObjectType = new ObjectType('Math', new Scope(null)); 3 | static LIST: ObjectType = new ObjectType('List', new Scope(null)); 4 | static LIST_T: TypeParameter = new TypeParameter('T'); 5 | static LIST_LENGTH: Symbol; 6 | static LIST_GET: Symbol; 7 | static LIST_SET: Symbol; 8 | static LIST_PUSH: Symbol; 9 | static LIST_POP: Symbol; 10 | static LIST_UNSHIFT: Symbol; 11 | static LIST_SHIFT: Symbol; 12 | static LIST_INDEX_OF: Symbol; 13 | static LIST_INSERT: Symbol; 14 | static LIST_REMOVE: Symbol; 15 | 16 | static createFunction(result: Type, args: Type[]): WrappedType { 17 | return new FunctionType(result.wrap(TypeModifier.INSTANCE), args.map(t => t.wrap(TypeModifier.INSTANCE))).wrap(TypeModifier.INSTANCE); 18 | } 19 | } 20 | 21 | // TODO: Use static functions when those work 22 | // TODO: Need Math.round() 23 | // TODO: Need a way to convert from double to int 24 | NativeTypes.MATH.scope.define('E', SpecialType.DOUBLE.wrap(TypeModifier.INSTANCE)); 25 | NativeTypes.MATH.scope.define('PI', SpecialType.DOUBLE.wrap(TypeModifier.INSTANCE)); 26 | NativeTypes.MATH.scope.define('NAN', SpecialType.DOUBLE.wrap(TypeModifier.INSTANCE)); 27 | NativeTypes.MATH.scope.define('INFINITY', SpecialType.DOUBLE.wrap(TypeModifier.INSTANCE)); 28 | NativeTypes.MATH.scope.define('cos', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 29 | NativeTypes.MATH.scope.define('sin', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 30 | NativeTypes.MATH.scope.define('tan', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 31 | NativeTypes.MATH.scope.define('acos', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 32 | NativeTypes.MATH.scope.define('asin', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 33 | NativeTypes.MATH.scope.define('atan', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 34 | NativeTypes.MATH.scope.define('atan2', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE, SpecialType.DOUBLE])); 35 | NativeTypes.MATH.scope.define('floor', NativeTypes.createFunction(SpecialType.INT, [SpecialType.DOUBLE])); 36 | NativeTypes.MATH.scope.define('ceil', NativeTypes.createFunction(SpecialType.INT, [SpecialType.DOUBLE])); 37 | NativeTypes.MATH.scope.define('abs', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 38 | NativeTypes.MATH.scope.define('log', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 39 | NativeTypes.MATH.scope.define('exp', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 40 | NativeTypes.MATH.scope.define('sqrt', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE])); 41 | NativeTypes.MATH.scope.define('pow', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE, SpecialType.DOUBLE])); 42 | NativeTypes.MATH.scope.define('min', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE, SpecialType.DOUBLE])); 43 | NativeTypes.MATH.scope.define('max', NativeTypes.createFunction(SpecialType.DOUBLE, [SpecialType.DOUBLE, SpecialType.DOUBLE])); 44 | NativeTypes.MATH.scope.define('random', NativeTypes.createFunction(SpecialType.DOUBLE, [])); 45 | 46 | // Lists are special-cased for now 47 | NativeTypes.LIST.isSealed = true; 48 | NativeTypes.LIST.byteAlignment = 4; 49 | NativeTypes.LIST.byteSize = 8; 50 | NativeTypes.LIST._constructorType = new FunctionType(null, []); 51 | NativeTypes.LIST.parameters.push(NativeTypes.LIST_T); 52 | NativeTypes.LIST_LENGTH = NativeTypes.LIST.scope.define('length', SpecialType.INT.wrap(TypeModifier.INSTANCE)); 53 | NativeTypes.LIST_GET = NativeTypes.LIST.scope.define('get', NativeTypes.createFunction(NativeTypes.LIST_T, [SpecialType.INT])); 54 | NativeTypes.LIST_SET = NativeTypes.LIST.scope.define('set', NativeTypes.createFunction(SpecialType.VOID, [SpecialType.INT, NativeTypes.LIST_T])); 55 | NativeTypes.LIST_PUSH = NativeTypes.LIST.scope.define('push', NativeTypes.createFunction(SpecialType.VOID, [NativeTypes.LIST_T])); 56 | NativeTypes.LIST_POP = NativeTypes.LIST.scope.define('pop', NativeTypes.createFunction(NativeTypes.LIST_T, [])); 57 | NativeTypes.LIST_UNSHIFT = NativeTypes.LIST.scope.define('unshift', NativeTypes.createFunction(SpecialType.VOID, [NativeTypes.LIST_T])); 58 | NativeTypes.LIST_SHIFT = NativeTypes.LIST.scope.define('shift', NativeTypes.createFunction(NativeTypes.LIST_T, [])); 59 | NativeTypes.LIST_INDEX_OF = NativeTypes.LIST.scope.define('indexOf', NativeTypes.createFunction(SpecialType.INT, [NativeTypes.LIST_T])); 60 | NativeTypes.LIST_INSERT = NativeTypes.LIST.scope.define('insert', NativeTypes.createFunction(SpecialType.VOID, [SpecialType.INT, NativeTypes.LIST_T])); 61 | NativeTypes.LIST_REMOVE = NativeTypes.LIST.scope.define('remove', NativeTypes.createFunction(SpecialType.VOID, [SpecialType.INT])); 62 | 63 | // Getting an element from a list of owned pointers should not steal ownership 64 | NativeTypes.LIST_GET.type.asFunction().result.modifiers |= TypeModifier.UNOWNED | TypeModifier.UNSHARED; 65 | NativeTypes.LIST_INDEX_OF.type.asFunction().args[0].modifiers |= TypeModifier.UNOWNED | TypeModifier.UNSHARED; 66 | -------------------------------------------------------------------------------- /src/binarylayout.ts: -------------------------------------------------------------------------------- 1 | class BinaryLayout { 2 | static run(node: Module) { 3 | // Find all user-defined object types 4 | var objectTypes: ObjectType[] = node.block.sortedObjectDeclarations().map(n => n.symbol.type.asObject()); 5 | 6 | // All classes with the same original base class should have the same 7 | // alignment because of polymorphism. Figure out alignment in two phases: 8 | // 9 | // 1) Compute the maximum alignment of all symbols 10 | // 2) Propagate that alignment up to the base classes 11 | // 12 | // This needs to be done using one pass up and one pass down the base class 13 | // tree because siblings may have alignments that are different but are both 14 | // larger than their common parent. All three classes need to have the same 15 | // alignment in this case regardless of declaration order. 16 | // 17 | // TODO: Do all three classes need to have the same alignment in this case? 18 | // Making them the same is definitely safe, but is it actually needed? 19 | objectTypes.forEach(BinaryLayout.computeAlignment); 20 | objectTypes.forEach(BinaryLayout.propagateAlignmentToBase); 21 | objectTypes.forEach(BinaryLayout.computeVTable); 22 | objectTypes.forEach(BinaryLayout.computeSize); 23 | } 24 | 25 | // Each object's alignment is the maximum of every field's alignment 26 | static computeAlignment(objectType: ObjectType) { 27 | // This should only be run once per object type 28 | assert(objectType.byteAlignment === 0); 29 | 30 | // Objects must not be empty 31 | objectType.byteAlignment = 1; 32 | 33 | // Objects must be aligned to their base type 34 | if (objectType.baseType !== null) { 35 | objectType.byteAlignment = Math.max(objectType.baseType.byteAlignment, objectType.byteAlignment); 36 | } 37 | 38 | // Objects must be aligned to the maximum alignment of each member 39 | objectType.scope.forEachSymbol(symbol => { 40 | objectType.byteAlignment = Math.max(objectType.byteAlignment, symbol.type.byteAlignment()); 41 | return ForEachSymbol.CONTINUE; 42 | }); 43 | } 44 | 45 | static propagateAlignmentToBase(objectType: ObjectType) { 46 | for (var baseType: ObjectType = objectType.baseType; baseType !== null; baseType = baseType.baseType) { 47 | baseType.byteAlignment = Math.max(baseType.byteAlignment, objectType.byteAlignment); 48 | } 49 | } 50 | 51 | static computeVTable(objectType: ObjectType) { 52 | // This should only be run once per object type 53 | assert(objectType.vtable.length === 0); 54 | 55 | // Start off with the size of the parent's vtable 56 | if (objectType.baseType !== null) { 57 | objectType.vtable = objectType.baseType.vtable.slice(0); 58 | } 59 | 60 | // Give each new symbol a vtable slot and match up each overridden symbol 61 | objectType.scope.forEachSymbol(symbol => { 62 | // Only look at virtual symbols (which are all functions) 63 | if (!symbol.isVirtual()) { 64 | return ForEachSymbol.CONTINUE; 65 | } 66 | 67 | // Overridden symbols reuse the same vtable slot 68 | if (symbol.overriddenSymbol !== null) { 69 | symbol.byteOffset = symbol.overriddenSymbol.byteOffset; 70 | objectType.vtable[symbol.byteOffset >> 2] = symbol; 71 | } 72 | 73 | // Other symbols create a new vtable slot 74 | else { 75 | symbol.byteOffset = objectType.vtable.length << 2; 76 | objectType.vtable.push(symbol); 77 | } 78 | 79 | return ForEachSymbol.CONTINUE; 80 | }); 81 | } 82 | 83 | static computeSize(objectType: ObjectType) { 84 | // This should only be run once per object type 85 | assert(objectType.byteSize === 0); 86 | assert(objectType.vtableByteOffset === 0); 87 | 88 | // The ObjectType array is sorted so the base class should have a size 89 | assert(objectType.baseType === null || objectType.baseType.byteSize !== 0); 90 | 91 | // Collect all symbols 92 | var symbols: Symbol[] = []; 93 | objectType.scope.forEachSymbol(symbol => { 94 | // Only take symbols from this scope, not base class scopes 95 | if (symbol.scope === objectType.scope && !symbol.type.isFunction()) { 96 | symbols.push(symbol); 97 | } 98 | return ForEachSymbol.CONTINUE; 99 | }); 100 | 101 | // Stable sort symbols by decreasing alignment to pack them tightly 102 | // together (we might as well since we have no pointer arithmetic) 103 | stableSort(symbols, (a, b) => b.type.byteSize() - a.type.byteSize()); 104 | 105 | // Start from the size of the base class if there is one 106 | var byteOffset: number = objectType.baseType !== null ? objectType.baseType.byteSize : 0; 107 | 108 | // Add a vtable pointer if the base class doesn't have one 109 | if (objectType.needsVTable() && (objectType.baseType === null || !objectType.baseType.needsVTable())) { 110 | objectType.vtableByteOffset = nextMultipleOf(byteOffset, 4); 111 | byteOffset = objectType.vtableByteOffset + 4; 112 | } 113 | 114 | // Give each symbol an offset with the correct alignment 115 | symbols.forEach(symbol => { 116 | var byteSize: number = symbol.type.byteSize(); 117 | if (byteSize !== 0) { 118 | symbol.byteOffset = nextMultipleOf(byteOffset, symbol.type.byteAlignment()); 119 | byteOffset = symbol.byteOffset + symbol.type.byteSize(); 120 | } 121 | }); 122 | 123 | // Round up the size of the object from the end of the last field 124 | objectType.byteSize = Math.max(1, nextMultipleOf(byteOffset, objectType.byteAlignment)); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/tokens.ts: -------------------------------------------------------------------------------- 1 | class Token { 2 | constructor( 3 | public range: SourceRange, 4 | public kind: string, 5 | public text: string) { 6 | } 7 | } 8 | 9 | function tokenize(log: Log, source: Source): Token[] { 10 | // Lists for tokenizing 11 | var operators: string[] = [ 12 | '\\(', '\\)', '\\{', '\\}', '\\[', '\\]', 13 | '\\.', '~', ',', ';', '\\?', ':', 14 | '\\+\\+', '--', '&&', '\\|\\|', 15 | '\\+=', '-=', '\\*=', '/=', '%=', '&=', '\\|=', '\\^=', '>>>=', '<<=', '>>=', 16 | '\\+', '-', '\\*', '/', '%', '&', '\\|', '\\^', '>>>', '<<', '>>', 17 | '!=', '==', '<=', '>=', '<', '>', '!', '=', 18 | ]; 19 | var keywords: string[] = [ 20 | 'if', 'else', 'while', 'for', 'continue', 'break', 'return', 21 | 'class', 'true', 'false', 'null', 'new', 'this', 'move', 22 | 'owned', 'shared', 'over', 23 | ]; 24 | 25 | // Regular expressions for tokenizing 26 | var splitter: RegExp = new RegExp('(' + [ 27 | '\\n', 28 | '//.*', 29 | '[ \\t]+', 30 | '(?:\\b)[0-9]+(?:\\.[0-9]+)?\\b', 31 | '\\b[A-Za-z_][A-Za-z\\$_0-9]*', 32 | '(?:' + operators.join('|') + ')', 33 | ].join('|') + ')'); 34 | var isSpace: RegExp = new RegExp('^(?:[\\n \\t]|//|$)'); 35 | var isDouble: RegExp = new RegExp('^[0-9]'); 36 | var isIdent: RegExp = new RegExp('^[A-Za-z\\_]'); 37 | var isKeyword: RegExp = new RegExp('^(?:' + keywords.join('|') + ')$'); 38 | 39 | // Do most of the lexing with the runtime's built-in regular expression JIT 40 | var parts: string[] = source.contents.split(splitter); 41 | var tokens: Token[] = []; 42 | var empty: boolean = true; 43 | var i: number = 0; 44 | var line: number = 1; 45 | var index: number = 0; 46 | var columnAdjust: number = 1; 47 | 48 | // Extract tokens from the split results 49 | while (i < parts.length) { 50 | var part: string = parts[i]; 51 | var length: number = part.length; 52 | i++; 53 | 54 | // Every other part should be empty 55 | if (empty) { 56 | empty = false; 57 | if (length > 0) { 58 | var start: Marker = new Marker(index, line, index + columnAdjust); 59 | var end: Marker = new Marker(index + length, line, index + length + columnAdjust); 60 | syntaxErrorExtraData(log, new SourceRange(source, start, end), part); 61 | } 62 | index += length; 63 | continue; 64 | } 65 | empty = true; 66 | 67 | // Decode the matched part (more frequent parts are tested earlier for efficiency) 68 | var kind: string = part; 69 | if (isSpace.test(part)) { 70 | index += length; 71 | if (part === '\n') { 72 | columnAdjust = 1 - index; 73 | line++; 74 | } 75 | continue; 76 | } 77 | else if (isIdent.test(part)) { if (!isKeyword.test(part)) kind = 'IDENTIFIER'; } 78 | else if (isDouble.test(part)) kind = part.indexOf('.') >= 0 ? 'DOUBLE' : 'INT'; 79 | 80 | // Create the new token 81 | var start: Marker = new Marker(index, line, index + columnAdjust); 82 | var end: Marker = new Marker(index + length, line, index + length + columnAdjust); 83 | tokens.push(new Token(new SourceRange(source, start, end), kind, part)); 84 | index += length; 85 | } 86 | 87 | // Every token stream ends in END 88 | var marker: Marker = new Marker(index, line, index + columnAdjust); 89 | tokens.push(new Token(new SourceRange(source, marker, marker), 'END', '')); 90 | return tokens; 91 | } 92 | 93 | function prepareTokens(tokens: Token[]): Token[] { 94 | var tokenStack: Token[] = []; 95 | var indexStack: number[] = []; 96 | 97 | nextToken: 98 | for (var i = 0; i < tokens.length; i++) { 99 | var token: Token = tokens[i]; 100 | 101 | // Remove tokens on the stack if they aren't working out 102 | while (tokenStack.length > 0) { 103 | var top: Token = tokenStack[tokenStack.length - 1]; 104 | 105 | // Stop parsing a type if we find a token that no type expression uses 106 | if (top.kind === '<' && token.kind !== '<' && token.kind[0] !== '>' && token.kind !== 'IDENTIFIER' && 107 | token.kind !== ',' && token.kind !== 'owned' && token.kind !== 'shared') { 108 | tokenStack.pop(); 109 | indexStack.pop(); 110 | } else { 111 | break; 112 | } 113 | } 114 | 115 | // Group open 116 | if (token.kind === '(' || token.kind === '{' || token.kind === '[' || token.kind === '<') { 117 | tokenStack.push(token); 118 | indexStack.push(i); 119 | continue; 120 | } 121 | 122 | // Group close 123 | if (token.kind === ')' || token.kind === '}' || token.kind === ']' || token.kind[0] === '>') { 124 | // Search for a matching opposite token 125 | while (tokenStack.length > 0) { 126 | var top: Token = tokenStack[tokenStack.length - 1]; 127 | 128 | // Don't match closing angle brackets that don't work since they are just operators 129 | if (token.kind[0] === '>' && top.kind !== '<') { 130 | break; 131 | } 132 | 133 | // Remove tentative matches that didn't work out 134 | if (top.kind === '<' && token.kind[0] !== '>') { 135 | tokenStack.pop(); 136 | indexStack.pop(); 137 | continue; 138 | } 139 | 140 | // Break apart operators that start with a closing angle bracket 141 | if (token.kind[0] === '>' && token.kind.length > 1) { 142 | var start: Marker = token.range.start; 143 | var middle: Marker = new Marker(start.index + 1, start.line, start.column + 1); 144 | tokens.splice(i + 1, 0, new Token(new SourceRange(token.range.source, middle, token.range.end), token.kind.slice(1), token.text.slice(1))); 145 | token.range.end = middle; 146 | token.kind = '>'; 147 | token.text = '>'; 148 | } 149 | 150 | // Consume the matching token 151 | var match: Token = tokenStack.pop(); 152 | var index: number = indexStack.pop(); 153 | 154 | // Convert < and > into bounds for type parameter lists 155 | if (match.kind === '<' && token.kind === '>') { 156 | match.kind = 'START_PARAMETER_LIST'; 157 | token.kind = 'END_PARAMETER_LIST'; 158 | } 159 | 160 | // Stop the search since we found a match 161 | continue nextToken; 162 | } 163 | } 164 | } 165 | 166 | return tokens; 167 | } 168 | -------------------------------------------------------------------------------- /src/typelogic.ts: -------------------------------------------------------------------------------- 1 | class TypeLogic { 2 | static equal(a: Type, b: Type): boolean { 3 | if (a === b) return true; 4 | if (a instanceof FunctionType && b instanceof FunctionType) { 5 | var fa: FunctionType = a; 6 | var fb: FunctionType = b; 7 | return TypeLogic.equalWrapped(fa.result, fb.result) && TypeLogic.allEqualWrapped(fa.args, fb.args); 8 | } 9 | return false; 10 | } 11 | 12 | static equalWrapped(a: WrappedType, b: WrappedType): boolean { 13 | return TypeLogic.equal(a.innerType, b.innerType) && a.modifiers === b.modifiers; 14 | } 15 | 16 | static allEqualWrapped(a: WrappedType[], b: WrappedType[]): boolean { 17 | return a.length === b.length && a.every((a, i) => TypeLogic.equalWrapped(a, b[i])); 18 | } 19 | 20 | static isBaseTypeOf(derived: ObjectType, base: ObjectType): boolean { 21 | for (var type: ObjectType = derived; type !== null; type = type.baseType) { 22 | if (type === base) return true; 23 | } 24 | return false; 25 | } 26 | 27 | static commonBaseType(a: ObjectType, b: ObjectType): ObjectType { 28 | for (var c: ObjectType = a; c !== null; c = c.baseType) { 29 | for (var d: ObjectType = b; d !== null; d = d.baseType) { 30 | if (c === d) return c; 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | static isValidOverride(derived: WrappedType, base: WrappedType): boolean { 37 | return derived.isFunction() && base.isFunction() && TypeLogic.equalWrapped(derived, base); 38 | } 39 | 40 | static checkImplicitConversionTypes(from: WrappedType, to: WrappedType): boolean { 41 | if (from.isInt() && to.isDouble()) return true; 42 | if (from.isNull() && to.isPointer()) return true; 43 | if (from.isObject() && to.isObject()) { 44 | return TypeLogic.isBaseTypeOf(from.asObject(), to.asObject()); // Upcasting is implicit 45 | } 46 | return TypeLogic.equal(from.innerType, to.innerType); 47 | } 48 | 49 | static checkImplicitConversionTypeModifiers(from: WrappedType, to: WrappedType): boolean { 50 | if (!from.isNull()) { 51 | if (from.substitutions.length !== to.substitutions.length) return false; 52 | if (from.substitutions.some(f => to.substitutions.every(t => f.parameter !== t.parameter || !TypeLogic.equalWrapped(f.type, t.type)))) return false; 53 | } else if (to.isPointer()) { 54 | return true; 55 | } 56 | if (from.isRawPointer() && to.isRawPointer()) return true; 57 | if (from.isOwned() && to.isPointer()) return true; 58 | if (from.isShared() && to.isPointer() && !to.isOwned()) return true; 59 | if (from.isPrimitive() && to.isPrimitive()) return true; 60 | return false; 61 | } 62 | 63 | static canImplicitlyConvert(from: WrappedType, to: WrappedType): boolean { 64 | return TypeLogic.checkImplicitConversionTypes(from, to) && 65 | TypeLogic.checkImplicitConversionTypeModifiers(from, to); 66 | } 67 | 68 | static commonImplicitType(a: WrappedType, b: WrappedType): WrappedType { 69 | if (TypeLogic.canImplicitlyConvert(a, b)) return b.wrapWithout(TypeModifier.STORAGE); 70 | if (TypeLogic.canImplicitlyConvert(b, a)) return a.wrapWithout(TypeModifier.STORAGE); 71 | if (a.isObject() && b.isObject()) { 72 | var base: ObjectType = TypeLogic.commonBaseType(a.asObject(), b.asObject()); 73 | if (base !== null) { 74 | if (a.isRawPointer() || b.isRawPointer()) { 75 | return base.wrap(TypeModifier.INSTANCE); 76 | } 77 | if (a.isShared() || b.isShared()) { 78 | return base.wrap(TypeModifier.INSTANCE | TypeModifier.SHARED); 79 | } 80 | assert(a.isOwned() && b.isOwned()); 81 | return base.wrap(TypeModifier.INSTANCE | TypeModifier.OWNED); 82 | } 83 | } 84 | return null; 85 | } 86 | 87 | static hasTypeParameters(type: WrappedType): boolean { 88 | return type.innerType.parameters.length > 0; 89 | } 90 | 91 | static isParameterized(type: WrappedType): boolean { 92 | if (TypeLogic.hasTypeParameters(type)) { 93 | // If a type has type parameters, make sure every parameter has a substitution 94 | if (type.innerType.parameters.some(p => !type.substitutions.some(s => s.parameter === p))) { 95 | return false; 96 | } 97 | 98 | // Recursively check the substitutions 99 | return type.substitutions.every(s => !TypeLogic.hasTypeParameters(s.type) || TypeLogic.isParameterized(s.type)); 100 | } 101 | 102 | return false; 103 | } 104 | 105 | static filterSubstitutionsForType(substitutions: Substitution[], type: Type): Substitution[] { 106 | return substitutions.filter(s => type.parameters.indexOf(s.parameter) >= 0); 107 | } 108 | 109 | static substitute(type: WrappedType, substitutions: Substitution[]): WrappedType { 110 | if (substitutions.length === 0) { 111 | return type; 112 | } 113 | assert(type.substitutions.length === 0); 114 | 115 | if (type.innerType instanceof TypeParameter) { 116 | for (var i = 0; i < substitutions.length; i++) { 117 | var sub: Substitution = substitutions[i]; 118 | if (type.innerType === sub.parameter) { 119 | var result: WrappedType = sub.type.wrapWith(TypeModifier.INSTANCE); 120 | 121 | // Possibly strip owned before returning the substitution. We may need 122 | // to strip owned to maintain ownership. For example, lists of owned 123 | // pointers should not relinquish ownership just because they returned 124 | // a value from a getter. 125 | if (type.isUnowned()) { 126 | result.modifiers &= ~TypeModifier.OWNED; 127 | } 128 | 129 | // Stripping shared may also be useful for performance reasons 130 | if (type.isUnshared()) { 131 | result.modifiers &= ~TypeModifier.SHARED; 132 | } 133 | 134 | return result; 135 | } 136 | } 137 | } 138 | 139 | if (type.innerType instanceof FunctionType) { 140 | var f: FunctionType = type.asFunction(); 141 | return new WrappedType(new FunctionType( 142 | TypeLogic.substitute(f.result, substitutions), 143 | f.args.map(t => TypeLogic.substitute(t, substitutions)) 144 | ), type.modifiers, []); 145 | } 146 | 147 | if (type.innerType instanceof ObjectType) { 148 | var o: ObjectType = type.asObject(); 149 | return new WrappedType(o, type.modifiers, TypeLogic.filterSubstitutionsForType(substitutions, o)); 150 | } 151 | 152 | return type; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | enum TypeModifier { 2 | OWNED = 1, // Is a unique pointer 3 | SHARED = 2, // Is a reference-counted pointer 4 | STORAGE = 4, // Can this be stored to (is this an L-value)? 5 | INSTANCE = 8, // Is this an instance of the type instead of the type itself? 6 | UNOWNED = 16, // Should this type parameter be stripped of OWNED here? 7 | UNSHARED = 32, // Should this type parameter be stripped of SHARED here? 8 | } 9 | 10 | class Type { 11 | parameters: TypeParameter[] = []; 12 | 13 | constructor( 14 | public byteAlignment: number, 15 | public byteSize: number) { 16 | } 17 | 18 | wrap(modifiers: number): WrappedType { 19 | return new WrappedType(this, modifiers, []); 20 | } 21 | 22 | asString(): string { 23 | assert(false); 24 | return ''; 25 | } 26 | } 27 | 28 | class SpecialType extends Type { 29 | constructor( 30 | byteSize: number, 31 | public name: string) { 32 | super(byteSize, byteSize); 33 | } 34 | 35 | static INT: SpecialType = new SpecialType(4, 'int'); 36 | static BOOL: SpecialType = new SpecialType(1, 'bool'); 37 | static NULL: SpecialType = new SpecialType(4, 'null'); 38 | static VOID: SpecialType = new SpecialType(0, 'void'); 39 | static ERROR: SpecialType = new SpecialType(0, ''); 40 | static DOUBLE: SpecialType = new SpecialType(8, 'double'); 41 | static CIRCULAR: SpecialType = new SpecialType(0, ''); 42 | 43 | asString(): string { 44 | return this.name; 45 | } 46 | } 47 | 48 | class FunctionType extends Type { 49 | constructor( 50 | public result: WrappedType, 51 | public args: WrappedType[]) { 52 | super(0, 0); 53 | } 54 | 55 | asString(): string { 56 | return this.result.asString() + ' function(' + this.args.map(t => t.asString()).join(', ') + ')'; 57 | } 58 | } 59 | 60 | class ObjectType extends Type { 61 | lazyInitializer: () => void = null; 62 | _constructorType: FunctionType = null; 63 | baseType: ObjectType = null; 64 | vtableByteOffset: number = 0; 65 | vtable: Symbol[] = []; 66 | 67 | // Does some other object type have this as a base? 68 | hasDerivedTypes: boolean = false; 69 | 70 | // Does this object type have a (possibly inherited) function without a body? 71 | _isAbstract: boolean = false; 72 | 73 | // Is this object type allowed to be the base class of another object type? 74 | isSealed: boolean = false; 75 | 76 | constructor( 77 | public name: string, 78 | public scope: Scope) { 79 | super(0, 0); 80 | } 81 | 82 | // Lazily compute the constructor type when it's needed instead of when the 83 | // class is first initialized to get around tricky ordering problems: 84 | // 85 | // class A { C c; } 86 | // class B : A {} 87 | // class C { B b; } 88 | // 89 | ensureIsInitialized() { 90 | if (this.lazyInitializer !== null) { 91 | this.lazyInitializer(); 92 | this.lazyInitializer = null; 93 | } 94 | } 95 | 96 | isAbstract(): boolean { 97 | this.ensureIsInitialized(); 98 | return this._isAbstract; 99 | } 100 | 101 | constructorType(): FunctionType { 102 | this.ensureIsInitialized(); 103 | return this._constructorType; 104 | } 105 | 106 | asString(): string { 107 | return this.name; 108 | } 109 | 110 | needsVTable(): boolean { 111 | return this.vtable.length !== 0; 112 | } 113 | } 114 | 115 | class TypeParameter extends Type { 116 | constructor( 117 | public name: string) { 118 | super(4, 4); // All type parameters are pointers 119 | } 120 | 121 | asString(): string { 122 | return this.name; 123 | } 124 | } 125 | 126 | class Substitution { 127 | constructor( 128 | public parameter: TypeParameter, 129 | public type: WrappedType) { 130 | } 131 | } 132 | 133 | class WrappedType { 134 | constructor( 135 | public innerType: Type, 136 | public modifiers: number, 137 | public substitutions: Substitution[]) { 138 | assert(innerType !== null); 139 | } 140 | 141 | isOwned(): boolean { 142 | return (this.modifiers & TypeModifier.OWNED) !== 0; 143 | } 144 | 145 | isShared(): boolean { 146 | return (this.modifiers & TypeModifier.SHARED) !== 0; 147 | } 148 | 149 | isStorage(): boolean { 150 | return (this.modifiers & TypeModifier.STORAGE) !== 0; 151 | } 152 | 153 | isInstance(): boolean { 154 | return (this.modifiers & TypeModifier.INSTANCE) !== 0; 155 | } 156 | 157 | isUnowned(): boolean { 158 | return (this.modifiers & TypeModifier.UNOWNED) !== 0; 159 | } 160 | 161 | isUnshared(): boolean { 162 | return (this.modifiers & TypeModifier.UNSHARED) !== 0; 163 | } 164 | 165 | isPointer(): boolean { 166 | return this.isObject() || this.isNull(); 167 | } 168 | 169 | isRawPointer(): boolean { 170 | return this.isPointer() && !this.isOwned() && !this.isShared(); 171 | } 172 | 173 | isError(): boolean { 174 | return this.innerType === SpecialType.ERROR; 175 | } 176 | 177 | isCircular(): boolean { 178 | return this.innerType === SpecialType.CIRCULAR; 179 | } 180 | 181 | isNull(): boolean { 182 | return this.innerType === SpecialType.NULL; 183 | } 184 | 185 | isVoid(): boolean { 186 | return this.innerType === SpecialType.VOID; 187 | } 188 | 189 | isInt(): boolean { 190 | return this.innerType === SpecialType.INT; 191 | } 192 | 193 | isDouble(): boolean { 194 | return this.innerType === SpecialType.DOUBLE; 195 | } 196 | 197 | isBool(): boolean { 198 | return this.innerType === SpecialType.BOOL; 199 | } 200 | 201 | isPrimitive(): boolean { 202 | return this.isInt() || this.isDouble() || this.isBool(); 203 | } 204 | 205 | isObject(): boolean { 206 | return this.innerType instanceof ObjectType; 207 | } 208 | 209 | isFunction(): boolean { 210 | return this.innerType instanceof FunctionType; 211 | } 212 | 213 | asObject(): ObjectType { 214 | return this.innerType instanceof ObjectType ? this.innerType : null; 215 | } 216 | 217 | asFunction(): FunctionType { 218 | return this.innerType instanceof FunctionType ? this.innerType : null; 219 | } 220 | 221 | byteAlignment(): number { 222 | return this.isPointer() ? 4 : this.innerType.byteAlignment; 223 | } 224 | 225 | byteSize(): number { 226 | return this.isPointer() ? 4 : this.innerType.byteSize; 227 | } 228 | 229 | asString(): string { 230 | return ( 231 | (this.modifiers & TypeModifier.OWNED ? 'owned ' : '') + 232 | (this.modifiers & TypeModifier.SHARED ? 'shared ' : '') + 233 | this.innerType.asString() + 234 | (this.substitutions.length > 0 ? '<' + TypeLogic.filterSubstitutionsForType( 235 | this.substitutions, this.innerType).map(s => s.type.asString()).join(', ') + '>' : '') 236 | ); 237 | } 238 | 239 | toString(): string { 240 | return (this.modifiers & TypeModifier.INSTANCE ? (this.isPointer() ? 'pointer' : 'value') + ' of type ' : 'type ') + this.asString(); 241 | } 242 | 243 | wrapWith(flag: number): WrappedType { 244 | return new WrappedType(this.innerType, this.modifiers | flag, this.substitutions); 245 | } 246 | 247 | wrapWithout(flag: number): WrappedType { 248 | return new WrappedType(this.innerType, this.modifiers & ~flag, this.substitutions); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /demo/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | white-space: nowrap; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | } 40 | 41 | /* CURSOR */ 42 | 43 | .CodeMirror div.CodeMirror-cursor { 44 | border-left: 1px solid black; 45 | z-index: 3; 46 | } 47 | /* Shown when moving in bi-directional text */ 48 | .CodeMirror div.CodeMirror-secondarycursor { 49 | border-left: 1px solid silver; 50 | } 51 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 52 | width: auto; 53 | border: 0; 54 | background: #7e7; 55 | z-index: 1; 56 | } 57 | /* Can style cursor different in overwrite (non-insert) mode */ 58 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 59 | 60 | .cm-tab { display: inline-block; } 61 | 62 | /* DEFAULT THEME */ 63 | 64 | .cm-s-default .cm-keyword {color: #708;} 65 | .cm-s-default .cm-atom {color: #219;} 66 | .cm-s-default .cm-number {color: #164;} 67 | .cm-s-default .cm-def {color: #00f;} 68 | .cm-s-default .cm-variable {color: black;} 69 | .cm-s-default .cm-variable-2 {color: #05a;} 70 | .cm-s-default .cm-variable-3 {color: #085;} 71 | .cm-s-default .cm-property {color: black;} 72 | .cm-s-default .cm-operator {color: black;} 73 | .cm-s-default .cm-comment {color: #a50;} 74 | .cm-s-default .cm-string {color: #a11;} 75 | .cm-s-default .cm-string-2 {color: #f50;} 76 | .cm-s-default .cm-meta {color: #555;} 77 | .cm-s-default .cm-error {color: #f00;} 78 | .cm-s-default .cm-qualifier {color: #555;} 79 | .cm-s-default .cm-builtin {color: #30a;} 80 | .cm-s-default .cm-bracket {color: #997;} 81 | .cm-s-default .cm-tag {color: #170;} 82 | .cm-s-default .cm-attribute {color: #00c;} 83 | .cm-s-default .cm-header {color: blue;} 84 | .cm-s-default .cm-quote {color: #090;} 85 | .cm-s-default .cm-hr {color: #999;} 86 | .cm-s-default .cm-link {color: #00c;} 87 | 88 | .cm-negative {color: #d44;} 89 | .cm-positive {color: #292;} 90 | .cm-header, .cm-strong {font-weight: bold;} 91 | .cm-em {font-style: italic;} 92 | .cm-link {text-decoration: underline;} 93 | 94 | .cm-invalidchar {color: #f00;} 95 | 96 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 97 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 98 | 99 | /* STOP */ 100 | 101 | /* The rest of this file contains styles related to the mechanics of 102 | the editor. You probably shouldn't touch them. */ 103 | 104 | .CodeMirror { 105 | line-height: 1; 106 | position: relative; 107 | overflow: hidden; 108 | background: white; 109 | color: black; 110 | } 111 | 112 | .CodeMirror-scroll { 113 | /* 30px is the magic margin used to hide the element's real scrollbars */ 114 | /* See overflow: hidden in .CodeMirror */ 115 | margin-bottom: -30px; margin-right: -30px; 116 | padding-bottom: 30px; padding-right: 30px; 117 | height: 100%; 118 | outline: none; /* Prevent dragging from highlighting the element */ 119 | position: relative; 120 | } 121 | .CodeMirror-sizer { 122 | position: relative; 123 | } 124 | 125 | /* The fake, visible scrollbars. Used to force redraw during scrolling 126 | before actuall scrolling happens, thus preventing shaking and 127 | flickering artifacts. */ 128 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 129 | position: absolute; 130 | z-index: 6; 131 | display: none; 132 | } 133 | .CodeMirror-vscrollbar { 134 | right: 0; top: 0; 135 | overflow-x: hidden; 136 | overflow-y: scroll; 137 | } 138 | .CodeMirror-hscrollbar { 139 | bottom: 0; left: 0; 140 | overflow-y: hidden; 141 | overflow-x: scroll; 142 | } 143 | .CodeMirror-scrollbar-filler { 144 | right: 0; bottom: 0; 145 | } 146 | .CodeMirror-gutter-filler { 147 | left: 0; bottom: 0; 148 | } 149 | 150 | .CodeMirror-gutters { 151 | position: absolute; left: 0; top: 0; 152 | padding-bottom: 30px; 153 | z-index: 3; 154 | } 155 | .CodeMirror-gutter { 156 | white-space: normal; 157 | height: 100%; 158 | padding-bottom: 30px; 159 | margin-bottom: -32px; 160 | display: inline-block; 161 | /* Hack to make IE7 behave */ 162 | *zoom:1; 163 | *display:inline; 164 | } 165 | .CodeMirror-gutter-elt { 166 | position: absolute; 167 | cursor: default; 168 | z-index: 4; 169 | } 170 | 171 | .CodeMirror-lines { 172 | cursor: text; 173 | } 174 | .CodeMirror pre { 175 | /* Reset some styles that the rest of the page might have set */ 176 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 177 | border-width: 0; 178 | background: transparent; 179 | font-family: inherit; 180 | font-size: inherit; 181 | margin: 0; 182 | white-space: pre; 183 | word-wrap: normal; 184 | line-height: inherit; 185 | color: inherit; 186 | z-index: 2; 187 | position: relative; 188 | overflow: visible; 189 | } 190 | .CodeMirror-wrap pre { 191 | word-wrap: break-word; 192 | white-space: pre-wrap; 193 | word-break: normal; 194 | } 195 | .CodeMirror-code pre { 196 | border-right: 30px solid transparent; 197 | width: -webkit-fit-content; 198 | width: -moz-fit-content; 199 | width: fit-content; 200 | } 201 | .CodeMirror-wrap .CodeMirror-code pre { 202 | border-right: none; 203 | width: auto; 204 | } 205 | .CodeMirror-linebackground { 206 | position: absolute; 207 | left: 0; right: 0; top: 0; bottom: 0; 208 | z-index: 0; 209 | } 210 | 211 | .CodeMirror-linewidget { 212 | position: relative; 213 | z-index: 2; 214 | overflow: auto; 215 | } 216 | 217 | .CodeMirror-widget { 218 | } 219 | 220 | .CodeMirror-wrap .CodeMirror-scroll { 221 | overflow-x: hidden; 222 | } 223 | 224 | .CodeMirror-measure { 225 | position: absolute; 226 | width: 100%; height: 0px; 227 | overflow: hidden; 228 | visibility: hidden; 229 | } 230 | .CodeMirror-measure pre { position: static; } 231 | 232 | .CodeMirror div.CodeMirror-cursor { 233 | position: absolute; 234 | visibility: hidden; 235 | border-right: none; 236 | width: 0; 237 | } 238 | .CodeMirror-focused div.CodeMirror-cursor { 239 | visibility: visible; 240 | } 241 | 242 | .CodeMirror-selected { background: #d9d9d9; } 243 | .CodeMirror-focused .CodeMirror-selected { background: #b0d3ff; } 244 | 245 | .cm-searching { 246 | background: #ffa; 247 | background: rgba(255, 255, 0, .4); 248 | } 249 | 250 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 251 | .CodeMirror span { *vertical-align: text-bottom; } 252 | 253 | @media print { 254 | /* Hide the cursor when printing */ 255 | .CodeMirror div.CodeMirror-cursor { 256 | visibility: hidden; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/diagnostics.ts: -------------------------------------------------------------------------------- 1 | function tokenKindToText(kind: string): string { 2 | return new RegExp('\\w').test(kind) ? kind : '"' + kind + '"'; 3 | } 4 | 5 | //////////////////////////////////////////////////////////////////////////////// 6 | // Syntax diagnostics 7 | //////////////////////////////////////////////////////////////////////////////// 8 | 9 | function syntaxErrorUnexpectedToken(log: Log, token: Token) { 10 | log.error(token.range, 'unexpected ' + tokenKindToText(token.kind)); 11 | } 12 | 13 | function syntaxErrorExpectedToken(log: Log, found: Token, expected: string) { 14 | log.error(found.range, 'expected ' + tokenKindToText(expected) + ' but found ' + tokenKindToText(found.kind)); 15 | } 16 | 17 | function syntaxErrorBadEscapeSequence(log: Log, range: SourceRange, text: string) { 18 | log.error(range, 'bad escape sequence "' + text + '"'); 19 | } 20 | 21 | function syntaxErrorExtraData(log: Log, range: SourceRange, text: string) { 22 | log.error(range, 'syntax error "' + text + '"'); 23 | } 24 | 25 | function syntaxErrorDuplicateModifier(log: Log, token: Token) { 26 | log.error(token.range, 'duplicate ' + token.text + ' modifier'); 27 | } 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Semantic diagnostics 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | function semanticErrorDuplicateSymbol(log: Log, range: SourceRange, symbol: Symbol) { 34 | log.error(range, symbol.name + ' is already defined ' + 35 | (symbol.node !== null ? 'on line ' + symbol.node.range.start.line + 36 | (range.source.name !== symbol.node.range.source.name ? ' of ' + 37 | symbol.node.range.source.name : '') : 'internally')); 38 | } 39 | 40 | function semanticErrorIncompatibleTypes(log: Log, range: SourceRange, from: WrappedType, to: WrappedType) { 41 | log.error(range, 'cannot convert from ' + from + ' to ' + to); 42 | } 43 | 44 | function semanticErrorCircularType(log: Log, range: SourceRange) { 45 | log.error(range, 'circular type'); 46 | } 47 | 48 | function semanticErrorUnknownSymbol(log: Log, range: SourceRange, name: string) { 49 | log.error(range, name + ' is not defined'); 50 | } 51 | 52 | function semanticErrorUnexpectedExpression(log: Log, range: SourceRange, type: WrappedType) { 53 | log.error(range, 'unexpected ' + type); 54 | } 55 | 56 | function semanticErrorPointerModifierConflict(log: Log, range: SourceRange) { 57 | log.error(range, 'cannot use both owned and shared'); 58 | } 59 | 60 | function semanticErrorInvalidPointerModifier(log: Log, range: SourceRange, type: WrappedType) { 61 | log.error(range, 'cannot make a pointer to ' + type); 62 | } 63 | 64 | function semanticErrorUnexpectedStatement(log: Log, range: SourceRange, text: string) { 65 | log.error(range, 'cannot use ' + text + ' here'); 66 | } 67 | 68 | function semanticErrorInvalidNew(log: Log, range: SourceRange, type: WrappedType) { 69 | log.error(range, 'cannot use new on ' + type); 70 | } 71 | 72 | function semanticErrorInvalidCall(log: Log, range: SourceRange, type: WrappedType) { 73 | log.error(range, 'cannot call ' + type); 74 | } 75 | 76 | function semanticErrorArgumentCount(log: Log, range: SourceRange, expected: number, found: number) { 77 | log.error(range, 78 | 'expected ' + expected + ' argument' + (expected === 1 ? '' : 's') + 79 | ' but found ' + found + ' argument' + (found === 1 ? '' : 's')); 80 | } 81 | 82 | function semanticErrorRValueToRawPointer(log: Log, range: SourceRange) { 83 | log.error(range, 'new object will be deleted immediately (store it somewhere with an owned or shared type instead)'); 84 | } 85 | 86 | function semanticErrorNoMembers(log: Log, range: SourceRange, type: WrappedType) { 87 | log.error(range, 'no members on ' + type); 88 | } 89 | 90 | function semanticErrorUnknownMemberSymbol(log: Log, range: SourceRange, name: string, type: WrappedType) { 91 | log.error(range, name + ' is not defined on ' + type); 92 | } 93 | 94 | function semanticErrorNoUnaryOperator(log: Log, range: SourceRange, op: string, type: WrappedType) { 95 | log.error(range, 'no unary operator ' + op + ' for ' + type); 96 | } 97 | 98 | function semanticErrorNoBinaryOperator(log: Log, range: SourceRange, op: string, left: WrappedType, right: WrappedType) { 99 | log.error(range, 'no binary operator ' + op + ' for ' + left + ' and ' + right); 100 | } 101 | 102 | function semanticErrorExpectedReturnValue(log: Log, range: SourceRange, type: WrappedType) { 103 | log.error(range, 'return statement must return ' + type); 104 | } 105 | 106 | function semanticErrorBadStorage(log: Log, range: SourceRange) { 107 | log.error(range, 'cannot store to this location'); 108 | } 109 | 110 | function semanticErrorBadBaseType(log: Log, range: SourceRange, type: WrappedType) { 111 | log.error(range, 'cannot inherit from ' + type); 112 | } 113 | 114 | function semanticErrorNoCommonType(log: Log, range: SourceRange, a: WrappedType, b: WrappedType) { 115 | log.error(range, 'no common type for ' + a + ' and ' + b); 116 | } 117 | 118 | function semanticErrorUnexpectedModifier(log: Log, range: SourceRange, modifier: string, why: string) { 119 | log.error(range, 'cannot use the ' + modifier + ' modifier ' + why); 120 | } 121 | 122 | function semanticErrorModifierOverMissingBase(log: Log, range: SourceRange, name: string) { 123 | log.error(range, name + ' has the "over" modifier but does not override anything'); 124 | } 125 | 126 | function semanticErrorModifierMissingOver(log: Log, range: SourceRange, name: string) { 127 | log.error(range, name + ' overrides another symbol with the same name but is missing the "over" modifier'); 128 | } 129 | 130 | function semanticErrorOverrideNotFunctions(log: Log, range: SourceRange, name: string, base: ObjectType) { 131 | log.error(range, name + ' overrides symbol with the same name in base class ' + base.asString()); 132 | } 133 | 134 | function semanticErrorOverrideDifferentTypes(log: Log, range: SourceRange, name: string, base: WrappedType, derived: WrappedType) { 135 | log.error(range, name + ' must have the same signature as the function it overrides (' + derived.asString() + ' overrides ' + base.asString() + ')'); 136 | } 137 | 138 | function semanticErrorAbstractNew(log: Log, node: Expression) { 139 | log.error(node.range, 'cannot use new on abstract ' + node.computedType); 140 | } 141 | 142 | function semanticErrorCannotParameterize(log: Log, range: SourceRange, type: WrappedType) { 143 | log.error(range, 'cannot parameterize ' + type); 144 | } 145 | 146 | function semanticErrorParameterCount(log: Log, range: SourceRange, expected: number, found: number) { 147 | log.error(range, 148 | 'expected ' + expected + ' type parameter' + (expected === 1 ? '' : 's') + 149 | ' but found ' + found + ' type parameter' + (found === 1 ? '' : 's')); 150 | } 151 | 152 | function semanticErrorUnparameterizedExpression(log: Log, range: SourceRange, type: WrappedType) { 153 | log.error(range, 'cannot use unparameterized ' + type); 154 | } 155 | 156 | function semanticErrorParameterizedExpression(log: Log, range: SourceRange, type: WrappedType) { 157 | log.error(range, 'cannot use parameterized ' + type); 158 | } 159 | 160 | function semanticErrorBadParameter(log: Log, range: SourceRange, type: WrappedType) { 161 | log.error(range, 'cannot use ' + type + ' as a type parameter'); 162 | } 163 | 164 | function semanticErrorMoveAndUse(log: Log, range: SourceRange, symbol: Symbol) { 165 | log.error(range, symbol.name + ' is both moved and used in the same expression'); 166 | } 167 | 168 | function semanticErrorBadMove(log: Log, range: SourceRange, type: WrappedType) { 169 | log.error(range, 'cannot move ' + type); 170 | } 171 | 172 | function semanticErrorExpectedMove(log: Log, range: SourceRange, type: WrappedType) { 173 | log.error(range, 'cannot move ' + type + ' without a move expression'); 174 | } 175 | 176 | function semanticErrorBadVariableType(log: Log, range: SourceRange, type: WrappedType) { 177 | log.error(range, 'cannot create variable of ' + type); 178 | } 179 | -------------------------------------------------------------------------------- /tests/modifiers.ts: -------------------------------------------------------------------------------- 1 | test([ 2 | 'class Foo {}', 3 | 'Foo foo = new Foo();', 4 | ], [ 5 | 'error on line 2 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 6 | '', 7 | 'Foo foo = new Foo();', 8 | ' ~~~~~~~~~', 9 | ]); 10 | 11 | test([ 12 | 'class Foo {}', 13 | 'owned Foo foo = new Foo();', 14 | ], [ 15 | ]); 16 | 17 | test([ 18 | 'class Foo {}', 19 | 'shared Foo foo = new Foo();', 20 | ], [ 21 | ]); 22 | 23 | test([ 24 | 'class Foo {}', 25 | 'Foo foo;', 26 | 'owned Foo bar = foo;', 27 | ], [ 28 | 'error on line 3 of : cannot convert from pointer of type Foo to pointer of type owned Foo', 29 | '', 30 | 'owned Foo bar = foo;', 31 | ' ~~~', 32 | ]); 33 | 34 | test([ 35 | 'class Foo {}', 36 | 'Foo foo;', 37 | 'shared Foo bar = foo;', 38 | ], [ 39 | 'error on line 3 of : cannot convert from pointer of type Foo to pointer of type shared Foo', 40 | '', 41 | 'shared Foo bar = foo;', 42 | ' ~~~', 43 | ]); 44 | 45 | test([ 46 | 'class Foo {}', 47 | 'shared Foo foo;', 48 | 'owned Foo bar = foo;', 49 | ], [ 50 | 'error on line 3 of : cannot convert from pointer of type shared Foo to pointer of type owned Foo', 51 | '', 52 | 'owned Foo bar = foo;', 53 | ' ~~~', 54 | ]); 55 | 56 | test([ 57 | 'class Foo {}', 58 | 'owned shared Foo foo = new Foo();', 59 | ], [ 60 | 'error on line 2 of : cannot use both owned and shared', 61 | '', 62 | 'owned shared Foo foo = new Foo();', 63 | '~~~~~~~~~~~~~~~~', 64 | ]); 65 | 66 | test([ 67 | 'class Link {', 68 | ' Link next; // Test circular types', 69 | '}', 70 | ], [ 71 | ]); 72 | 73 | test([ 74 | 'class Foo {}', 75 | 'Foo foo() {', 76 | ' return new Foo();', 77 | '}', 78 | ], [ 79 | 'error on line 3 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 80 | '', 81 | ' return new Foo();', 82 | ' ~~~~~~~~~', 83 | ]); 84 | 85 | test([ 86 | 'class Foo {}', 87 | 'shared Foo foo() {', 88 | ' return new Foo();', 89 | '}', 90 | ], [ 91 | ]); 92 | 93 | test([ 94 | 'class Foo {}', 95 | 'owned Foo foo() {', 96 | ' return new Foo();', 97 | '}', 98 | ], [ 99 | ]); 100 | 101 | test([ 102 | 'class Foo {}', 103 | 'owned Foo foo() {}', 104 | 'Foo bar() {', 105 | ' return foo();', 106 | '}', 107 | ], [ 108 | 'error on line 4 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 109 | '', 110 | ' return foo();', 111 | ' ~~~~~', 112 | ]); 113 | 114 | test([ 115 | 'class Foo {}', 116 | 'void bar(owned Foo foo) {', 117 | ' Foo bar = foo;', 118 | '}', 119 | ], [ 120 | ]); 121 | 122 | test([ 123 | 'class Foo {}', 124 | 'void foo() {', 125 | ' owned Foo foo = null;', 126 | '}', 127 | ], [ 128 | ]); 129 | 130 | test([ 131 | 'class Foo {}', 132 | 'void foo() {', 133 | ' shared Foo foo = null;', 134 | '}', 135 | ], [ 136 | ]); 137 | 138 | test([ 139 | 'class Foo {}', 140 | 'void foo() {', 141 | ' Foo foo = null;', 142 | '}', 143 | ], [ 144 | ]); 145 | 146 | // TODO: Warn about each one individually 147 | test([ 148 | 'class Foo {}', 149 | 'void main() {', 150 | ' Foo bar = Math.random() < 0.5 ? new Foo() : new Foo();', 151 | '}', 152 | ], [ 153 | 'error on line 3 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 154 | '', 155 | ' Foo bar = Math.random() < 0.5 ? new Foo() : new Foo();', 156 | ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 157 | ]); 158 | 159 | // TODO: Warn only about 'new Foo()', and also must not delete foo until the end of the scope 160 | test([ 161 | 'class Foo {}', 162 | 'void main() {', 163 | ' owned Foo foo = new Foo();', 164 | ' Foo bar = Math.random() < 0.5 ? new Foo() : foo;', 165 | '}', 166 | ], [ 167 | 'error on line 4 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 168 | '', 169 | ' Foo bar = Math.random() < 0.5 ? new Foo() : foo;', 170 | ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 171 | ]); 172 | 173 | // TODO: Warn only about 'new Foo()', and also must not delete foo until the end of the scope 174 | test([ 175 | 'class Foo {}', 176 | 'void main() {', 177 | ' owned Foo foo = new Foo();', 178 | ' Foo bar = Math.random() < 0.5 ? foo : new Foo();', 179 | '}', 180 | ], [ 181 | 'error on line 4 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 182 | '', 183 | ' Foo bar = Math.random() < 0.5 ? foo : new Foo();', 184 | ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 185 | ]); 186 | 187 | // TODO: This should work, and also must not delete foo until the end of the scope 188 | test([ 189 | 'class Foo {}', 190 | 'void main() {', 191 | ' owned Foo foo = new Foo();', 192 | ' Foo bar = Math.random() < 0.5 ? foo : foo;', 193 | '}', 194 | ], [ 195 | 'error on line 4 of : new object will be deleted immediately (store it somewhere with an owned or shared type instead)', 196 | '', 197 | ' Foo bar = Math.random() < 0.5 ? foo : foo;', 198 | ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 199 | ]); 200 | 201 | // This should work, and should transfer foo to bar 202 | test([ 203 | 'class Foo {}', 204 | 'void main() {', 205 | ' owned Foo foo = new Foo();', 206 | ' owned Foo bar = Math.random() < 0.5 ? foo : foo;', 207 | '}', 208 | ], [ 209 | ]); 210 | 211 | test([ 212 | 'class Foo {', 213 | ' int x = 0;', 214 | '}', 215 | 'void bar(Foo a, int b) {}', 216 | 'void baz(int a, Foo b) {}', 217 | 'int main() {', 218 | ' owned Foo foo = new Foo();', 219 | ' bar(foo, foo.x);', 220 | ' baz(foo.x, foo);', 221 | ' return 0;', 222 | '}', 223 | ], [ 224 | ]); 225 | 226 | test([ 227 | 'class Foo {', 228 | ' int x = 0;', 229 | '}', 230 | 'void bar1(owned Foo a, int b) {}', 231 | 'void bar2(int a, owned Foo b) {}', 232 | 'void baz1(owned Foo a, int b, int c) {}', 233 | 'void baz2(int a, owned Foo b, int c) {}', 234 | 'void baz3(int a, int b, owned Foo c) {}', 235 | 'int main() {', 236 | ' owned Foo foo = new Foo();', 237 | ' bar1(move foo, foo.x);', 238 | ' bar2(foo.x, move foo);', 239 | ' baz1(move foo, foo.x, foo.x);', 240 | ' baz2(foo.x, move foo, foo.x);', 241 | ' baz3(foo.x, foo.x, move foo);', 242 | ' return 0;', 243 | '}', 244 | ], [ 245 | 'error on line 11 of : foo is both moved and used in the same expression', 246 | '', 247 | ' bar1(move foo, foo.x);', 248 | ' ~~~', 249 | '', 250 | 'error on line 12 of : foo is both moved and used in the same expression', 251 | '', 252 | ' bar2(foo.x, move foo);', 253 | ' ~~~', 254 | '', 255 | 'error on line 13 of : foo is both moved and used in the same expression', 256 | '', 257 | ' baz1(move foo, foo.x, foo.x);', 258 | ' ~~~', 259 | '', 260 | 'error on line 14 of : foo is both moved and used in the same expression', 261 | '', 262 | ' baz2(foo.x, move foo, foo.x);', 263 | ' ~~~', 264 | '', 265 | 'error on line 15 of : foo is both moved and used in the same expression', 266 | '', 267 | ' baz3(foo.x, foo.x, move foo);', 268 | ' ~~~', 269 | ]); 270 | 271 | test([ 272 | 'class Foo {', 273 | '}', 274 | 'bool foo(owned Foo foo, shared Foo bar, Foo baz) {', 275 | ' // This should compile in C++ by implicitly converting to raw pointers before each comparison', 276 | ' return foo == foo || bar == bar || baz == baz || foo == bar || foo == baz || bar == baz;', 277 | '}', 278 | ], [ 279 | ]); 280 | 281 | test([ 282 | 'class Foo {}', 283 | 'int main() {', 284 | ' owned Foo foo;', 285 | ' Foo bar;', 286 | ' bar = foo = new Foo(); // This should compile correctly in C++', 287 | ' return 0;', 288 | '}', 289 | ], [ 290 | ]); 291 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | function cli() { 2 | var inputs: string[] = []; 3 | var outputJS: any = null; 4 | var outputCPP: any = null; 5 | var outputAsmJS: any = null; 6 | var moduleName: any = null; 7 | var helpFlag: boolean = false; 8 | var watchFlag: boolean = false; 9 | 10 | var fs: any = require('fs'); 11 | var tty: any = require('tty'); 12 | var path: any = require('path'); 13 | var notifier: any = require('terminal-notifier'); 14 | var useColors: boolean = tty.isatty(1) && tty.isatty(2); 15 | 16 | function time(): string { 17 | var now: Date = new Date(); 18 | if (!watchFlag) return ''; 19 | return ((now.getHours() % 12 + 11) % 12 + 1) + ':' + 20 | (100 + now.getMinutes()).toString().slice(1) + 21 | ['am', 'pm'][now.getHours() / 12 | 0] + 22 | ' - '; 23 | } 24 | 25 | function indent(text: string): string { 26 | return ' ' + text.replace(/\n/g, '\n '); 27 | } 28 | 29 | function wrapColor(color: number): (text: string) => string { 30 | if (!useColors) return text => { return text; }; 31 | return text => { return '\u001b[' + color + 'm' + text + '\u001b[0m'; }; 32 | } 33 | 34 | var gray: (text: string) => string = wrapColor(90); 35 | var red: (text: string) => string = wrapColor(91); 36 | var green: (text: string) => string = wrapColor(92); 37 | 38 | function showNotification(diagnostic: Diagnostic) { 39 | if (!watchFlag) return; 40 | var options: any = { 41 | title: diagnostic.range !== null ? diagnostic.range.source.name + ' on line ' + diagnostic.range.start.line : 'Build error', 42 | group: 'bitscript' 43 | }; 44 | if (diagnostic.range !== null && process.env.EDITOR) { 45 | options.execute = process.env.EDITOR + ' "' + path.resolve(diagnostic.range.source.name) + ':' + diagnostic.range.start.line + '"'; 46 | } 47 | notifier(diagnostic.text, options); 48 | } 49 | 50 | function compile() { 51 | var compiler = new Compiler(); 52 | inputs.forEach(input => compiler.addSource(input, fs.readFileSync(input, 'utf8'))); 53 | compiler.compile(); 54 | 55 | // Output code on success 56 | if (compiler.log.errorCount === 0) { 57 | if (outputJS !== null) { 58 | var root: string = path.relative(path.dirname(outputJS), '.'); 59 | var codeAndMap: { code: string; map: string } = OutputJS.generateWithSourceMap(compiler.module, root); 60 | fs.writeFileSync(outputJS, codeAndMap.code + '\n/' + /* Break this up to make Chrome ignore it */ '/# sourceMappingURL=' + path.basename(outputJS) + '.map\n'); 61 | fs.writeFileSync(outputJS + '.map', codeAndMap.map + '\n'); 62 | } 63 | if (outputCPP !== null) fs.writeFileSync(outputCPP, OutputCPP.generate(compiler.module) + '\n'); 64 | if (outputAsmJS !== null) fs.writeFileSync(outputAsmJS, OutputAsmJS.generate(compiler.module, moduleName) + '\n'); 65 | console.log(gray(time() + 'build successful')); 66 | return true; 67 | } 68 | 69 | // Remove files on failure 70 | if (outputJS !== null && fs.existsSync(outputJS)) { 71 | fs.unlinkSync(outputJS); 72 | fs.unlinkSync(outputJS + '.map'); 73 | } 74 | if (outputCPP !== null && fs.existsSync(outputCPP)) fs.unlinkSync(outputCPP); 75 | if (outputAsmJS !== null && fs.existsSync(outputAsmJS)) fs.unlinkSync(outputAsmJS); 76 | if (watchFlag) showNotification(compiler.log.diagnostics[0]); 77 | 78 | // Use fancy colored output for TTYs 79 | console.log(gray(time() + 'build failed\n\n') + indent(compiler.log.diagnostics.map(d => { 80 | var parts = d.range.sourceString().split('\n'); 81 | return gray(d.type + ' on line ' + d.range.start.line + ' of ' + d.range.source.name + ': ') + red(d.text) + '\n\n' + parts[0] + '\n' + green(parts[1]) + '\n'; 82 | }).join('\n'))); 83 | return false; 84 | } 85 | 86 | // Return a unique string that will change when one of the files changes 87 | function stat(): string { 88 | return inputs.map(input => input + fs.statSync(input).mtime).join('\n'); 89 | } 90 | 91 | function usage() { 92 | console.log([ 93 | '', 94 | 'usage: bitc in1.bit in2.bit ... [--js out.js] [--cpp out.cpp] [--asmjs out.js] [--name moduleName] [--watch]', 95 | '', 96 | ].join('\n')); 97 | } 98 | 99 | // Parse command-line flags 100 | var args = process.argv.slice(2); 101 | while (args.length > 0) { 102 | var arg = args.shift(); 103 | switch (arg) { 104 | case '-h': case '--help': helpFlag = true; break; 105 | case '--js': outputJS = args.shift(); break; 106 | case '--cpp': outputCPP = args.shift(); break; 107 | case '--asmjs': outputAsmJS = args.shift(); break; 108 | case '--name': moduleName = args.shift(); break; 109 | case '--watch': watchFlag = true; break; 110 | default: inputs.push(arg); break; 111 | } 112 | } 113 | 114 | // Validate command-line flags 115 | if (helpFlag || outputJS === void 0 || outputCPP === void 0 || outputAsmJS === void 0 || moduleName === void 0 || inputs.length === 0) { 116 | usage(); 117 | process.exit(1); 118 | } 119 | 120 | // Automatically generate a module name 121 | if (outputAsmJS !== null && moduleName === null) { 122 | moduleName = path.basename(outputAsmJS).replace(/[^\w]/g, '_'); 123 | } 124 | 125 | // Main compilation logic 126 | if (!watchFlag) process.exit(compile() ? 0 : 1); 127 | var oldStat: string = stat(); 128 | compile(); 129 | setInterval(() => { 130 | var newStat = stat(); 131 | if (oldStat !== newStat) { 132 | oldStat = newStat; 133 | compile(); 134 | } 135 | }, 100); 136 | } 137 | 138 | // Export symbols if we are being used as a node library 139 | if (typeof exports !== 'undefined') { 140 | // Log 141 | exports.Source = Source; 142 | exports.Marker = Marker; 143 | exports.SourceRange = SourceRange; 144 | exports.Diagnostic = Diagnostic; 145 | exports.Log = Log; 146 | 147 | // Other 148 | exports.Symbol = Symbol; 149 | exports.Scope = Scope; 150 | exports.Token = Token; 151 | 152 | // Types 153 | exports.TypeLogic = TypeLogic; 154 | exports.TypeModifier = TypeModifier; 155 | exports.Type = Type; 156 | exports.SpecialType = SpecialType; 157 | exports.FunctionType = FunctionType; 158 | exports.ObjectType = ObjectType; 159 | exports.TypeParameter = TypeParameter; 160 | exports.Substitution = Substitution; 161 | exports.WrappedType = WrappedType; 162 | exports.NativeTypes = NativeTypes; 163 | 164 | // AST 165 | exports.AST = AST; 166 | exports.Module = Module; 167 | exports.Identifier = Identifier; 168 | exports.Block = Block; 169 | exports.Statement = Statement; 170 | exports.ExpressionStatement = ExpressionStatement; 171 | exports.IfStatement = IfStatement; 172 | exports.WhileStatement = WhileStatement; 173 | exports.ForStatement = ForStatement; 174 | exports.ReturnStatement = ReturnStatement; 175 | exports.BreakStatement = BreakStatement; 176 | exports.ContinueStatement = ContinueStatement; 177 | exports.Declaration = Declaration; 178 | exports.ObjectDeclaration = ObjectDeclaration; 179 | exports.FunctionDeclaration = FunctionDeclaration; 180 | exports.VariableDeclaration = VariableDeclaration; 181 | exports.Expression = Expression; 182 | exports.SymbolExpression = SymbolExpression; 183 | exports.MoveExpression = MoveExpression; 184 | exports.UnaryExpression = UnaryExpression; 185 | exports.BinaryExpression = BinaryExpression; 186 | exports.TernaryExpression = TernaryExpression; 187 | exports.MemberExpression = MemberExpression; 188 | exports.IntExpression = IntExpression; 189 | exports.BoolExpression = BoolExpression; 190 | exports.DoubleExpression = DoubleExpression; 191 | exports.NullExpression = NullExpression; 192 | exports.ThisExpression = ThisExpression; 193 | exports.CallExpression = CallExpression; 194 | exports.NewExpression = NewExpression; 195 | exports.TypeModifierExpression = TypeModifierExpression; 196 | exports.TypeParameterExpression = TypeParameterExpression; 197 | 198 | // API 199 | exports.Compiler = Compiler; 200 | exports.OutputJS = OutputJS; 201 | exports.OutputCPP = OutputCPP; 202 | exports.OutputAsmJS = OutputAsmJS; 203 | } 204 | 205 | // Launch the command-line interface if we are run from the terminal 206 | if (typeof require !== 'undefined' && typeof module !== 'undefined' && require.main === module) { 207 | cli(); 208 | } 209 | -------------------------------------------------------------------------------- /tests/operators.ts: -------------------------------------------------------------------------------- 1 | ['+', '-'].map(op => { 2 | test([ 3 | 'bool foo = ' + op + 'false;', 4 | ], [ 5 | 'error on line 1 of : no unary operator ' + op + ' for value of type bool', 6 | '', 7 | 'bool foo = ' + op + 'false;', 8 | ' ~~~~~~', 9 | ]); 10 | 11 | test([ 12 | 'void foo() {}', 13 | 'bool bar = ' + op + 'foo();', 14 | ], [ 15 | 'error on line 2 of : no unary operator ' + op + ' for value of type void', 16 | '', 17 | 'bool bar = ' + op + 'foo();', 18 | ' ~~~~~~', 19 | ]); 20 | 21 | test([ 22 | 'bool foo = ' + op + '1;', 23 | ], [ 24 | 'error on line 1 of : cannot convert from value of type int to value of type bool', 25 | '', 26 | 'bool foo = ' + op + '1;', 27 | ' ~~', 28 | ]); 29 | 30 | test([ 31 | 'bool foo = ' + op + '1.5;', 32 | ], [ 33 | 'error on line 1 of : cannot convert from value of type double to value of type bool', 34 | '', 35 | 'bool foo = ' + op + '1.5;', 36 | ' ~~~~', 37 | ]); 38 | }); 39 | 40 | test([ 41 | 'int foo = !false;', 42 | ], [ 43 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 44 | '', 45 | 'int foo = !false;', 46 | ' ~~~~~~', 47 | ]); 48 | 49 | test([ 50 | 'void foo() {}', 51 | 'int bar = !foo();', 52 | ], [ 53 | 'error on line 2 of : no unary operator ! for value of type void', 54 | '', 55 | 'int bar = !foo();', 56 | ' ~~~~~~', 57 | ]); 58 | 59 | test([ 60 | 'int foo = !1;', 61 | ], [ 62 | 'error on line 1 of : no unary operator ! for value of type int', 63 | '', 64 | 'int foo = !1;', 65 | ' ~~', 66 | ]); 67 | 68 | test([ 69 | 'int foo = !1.5;', 70 | ], [ 71 | 'error on line 1 of : no unary operator ! for value of type double', 72 | '', 73 | 'int foo = !1.5;', 74 | ' ~~~~', 75 | ]); 76 | 77 | test([ 78 | 'bool foo = ~false;', 79 | ], [ 80 | 'error on line 1 of : no unary operator ~ for value of type bool', 81 | '', 82 | 'bool foo = ~false;', 83 | ' ~~~~~~', 84 | ]); 85 | 86 | test([ 87 | 'void foo() {}', 88 | 'bool bar = ~foo();', 89 | ], [ 90 | 'error on line 2 of : no unary operator ~ for value of type void', 91 | '', 92 | 'bool bar = ~foo();', 93 | ' ~~~~~~', 94 | ]); 95 | 96 | test([ 97 | 'bool foo = ~1;', 98 | ], [ 99 | 'error on line 1 of : cannot convert from value of type int to value of type bool', 100 | '', 101 | 'bool foo = ~1;', 102 | ' ~~', 103 | ]); 104 | 105 | test([ 106 | 'bool foo = ~1.5;', 107 | ], [ 108 | 'error on line 1 of : no unary operator ~ for value of type double', 109 | '', 110 | 'bool foo = ~1.5;', 111 | ' ~~~~', 112 | ]); 113 | 114 | ['+', '-', '*', '/'].map(op => { 115 | test([ 116 | 'bool foo = 1 ' + op + ' false;', 117 | ], [ 118 | 'error on line 1 of : no binary operator ' + op + ' for value of type int and value of type bool', 119 | '', 120 | 'bool foo = 1 ' + op + ' false;', 121 | ' ~~~~~~~~~', 122 | ]); 123 | 124 | test([ 125 | 'bool foo = false ' + op + ' 1;', 126 | ], [ 127 | 'error on line 1 of : no binary operator ' + op + ' for value of type bool and value of type int', 128 | '', 129 | 'bool foo = false ' + op + ' 1;', 130 | ' ~~~~~~~~~', 131 | ]); 132 | 133 | test([ 134 | 'bool foo = 1 ' + op + ' 1;', 135 | ], [ 136 | 'error on line 1 of : cannot convert from value of type int to value of type bool', 137 | '', 138 | 'bool foo = 1 ' + op + ' 1;', 139 | ' ~~~~~', 140 | ]); 141 | 142 | test([ 143 | 'bool foo = 1 ' + op + ' 1.5;', 144 | ], [ 145 | 'error on line 1 of : cannot convert from value of type double to value of type bool', 146 | '', 147 | 'bool foo = 1 ' + op + ' 1.5;', 148 | ' ~~~~~~~', 149 | ]); 150 | 151 | test([ 152 | 'bool foo = 1.5 ' + op + ' 1;', 153 | ], [ 154 | 'error on line 1 of : cannot convert from value of type double to value of type bool', 155 | '', 156 | 'bool foo = 1.5 ' + op + ' 1;', 157 | ' ~~~~~~~', 158 | ]); 159 | 160 | test([ 161 | 'bool foo = 1.5 ' + op + ' 1.5;', 162 | ], [ 163 | 'error on line 1 of : cannot convert from value of type double to value of type bool', 164 | '', 165 | 'bool foo = 1.5 ' + op + ' 1.5;', 166 | ' ~~~~~~~~~', 167 | ]); 168 | }); 169 | 170 | ['%', '<<', '>>', '|', '&', '^'].map(op => { 171 | var tildes = op.replace(/./g, '~'); 172 | 173 | test([ 174 | 'bool foo = 1 ' + op + ' 1;', 175 | ], [ 176 | 'error on line 1 of : cannot convert from value of type int to value of type bool', 177 | '', 178 | 'bool foo = 1 ' + op + ' 1;', 179 | ' ~~~~' + tildes, 180 | ]); 181 | 182 | test([ 183 | 'bool foo = 1 ' + op + ' 1.5;', 184 | ], [ 185 | 'error on line 1 of : no binary operator ' + op + ' for value of type int and value of type double', 186 | '', 187 | 'bool foo = 1 ' + op + ' 1.5;', 188 | ' ~~~~~~' + tildes, 189 | ]); 190 | 191 | test([ 192 | 'bool foo = 1.5 ' + op + ' 1;', 193 | ], [ 194 | 'error on line 1 of : no binary operator ' + op + ' for value of type double and value of type int', 195 | '', 196 | 'bool foo = 1.5 ' + op + ' 1;', 197 | ' ~~~~~~' + tildes, 198 | ]); 199 | }); 200 | 201 | ['||', '&&'].map(op => { 202 | test([ 203 | 'int foo = false ' + op + ' true;', 204 | ], [ 205 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 206 | '', 207 | 'int foo = false ' + op + ' true;', 208 | ' ~~~~~~~~~~~~~', 209 | ]); 210 | 211 | test([ 212 | 'int foo = false ' + op + ' 1;', 213 | ], [ 214 | 'error on line 1 of : no binary operator ' + op + ' for value of type bool and value of type int', 215 | '', 216 | 'int foo = false ' + op + ' 1;', 217 | ' ~~~~~~~~~~', 218 | ]); 219 | 220 | test([ 221 | 'int foo = 1 ' + op + ' false;', 222 | ], [ 223 | 'error on line 1 of : no binary operator ' + op + ' for value of type int and value of type bool', 224 | '', 225 | 'int foo = 1 ' + op + ' false;', 226 | ' ~~~~~~~~~~', 227 | ]); 228 | }); 229 | 230 | ['<', '>', '<=', '>='].map(op => { 231 | var tildes = op.replace(/./g, '~'); 232 | 233 | test([ 234 | 'int foo = false ' + op + ' true;', 235 | ], [ 236 | 'error on line 1 of : no binary operator ' + op + ' for value of type bool and value of type bool', 237 | '', 238 | 'int foo = false ' + op + ' true;', 239 | ' ~~~~~~~~~~~' + tildes, 240 | ]); 241 | 242 | test([ 243 | 'int foo = 1 ' + op + ' true;', 244 | ], [ 245 | 'error on line 1 of : no binary operator ' + op + ' for value of type int and value of type bool', 246 | '', 247 | 'int foo = 1 ' + op + ' true;', 248 | ' ~~~~~~~' + tildes, 249 | ]); 250 | 251 | test([ 252 | 'int foo = false ' + op + ' 1;', 253 | ], [ 254 | 'error on line 1 of : no binary operator ' + op + ' for value of type bool and value of type int', 255 | '', 256 | 'int foo = false ' + op + ' 1;', 257 | ' ~~~~~~~~' + tildes, 258 | ]); 259 | 260 | test([ 261 | 'int foo = 1 ' + op + ' 1;', 262 | ], [ 263 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 264 | '', 265 | 'int foo = 1 ' + op + ' 1;', 266 | ' ~~~~' + tildes, 267 | ]); 268 | 269 | test([ 270 | 'int foo = 1 ' + op + ' 1.5;', 271 | ], [ 272 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 273 | '', 274 | 'int foo = 1 ' + op + ' 1.5;', 275 | ' ~~~~~~' + tildes, 276 | ]); 277 | 278 | test([ 279 | 'int foo = 1.5 ' + op + ' 1;', 280 | ], [ 281 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 282 | '', 283 | 'int foo = 1.5 ' + op + ' 1;', 284 | ' ~~~~~~' + tildes, 285 | ]); 286 | 287 | test([ 288 | 'int foo = 1.5 ' + op + ' 1.5;', 289 | ], [ 290 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 291 | '', 292 | 'int foo = 1.5 ' + op + ' 1.5;', 293 | ' ~~~~~~~~' + tildes, 294 | ]); 295 | }); 296 | 297 | ['==', '!='].map(op => { 298 | test([ 299 | 'int foo = false ' + op + ' true;', 300 | ], [ 301 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 302 | '', 303 | 'int foo = false ' + op + ' true;', 304 | ' ~~~~~~~~~~~~~', 305 | ]); 306 | 307 | test([ 308 | 'int foo = 1 ' + op + ' true;', 309 | ], [ 310 | 'error on line 1 of : no binary operator ' + op + ' for value of type int and value of type bool', 311 | '', 312 | 'int foo = 1 ' + op + ' true;', 313 | ' ~~~~~~~~~', 314 | ]); 315 | 316 | test([ 317 | 'int foo = false ' + op + ' 1;', 318 | ], [ 319 | 'error on line 1 of : no binary operator ' + op + ' for value of type bool and value of type int', 320 | '', 321 | 'int foo = false ' + op + ' 1;', 322 | ' ~~~~~~~~~~', 323 | ]); 324 | 325 | test([ 326 | 'int foo = 1 ' + op + ' 1;', 327 | ], [ 328 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 329 | '', 330 | 'int foo = 1 ' + op + ' 1;', 331 | ' ~~~~~~', 332 | ]); 333 | 334 | test([ 335 | 'int foo = 1 ' + op + ' 1.5;', 336 | ], [ 337 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 338 | '', 339 | 'int foo = 1 ' + op + ' 1.5;', 340 | ' ~~~~~~~~', 341 | ]); 342 | 343 | test([ 344 | 'int foo = 1.5 ' + op + ' 1;', 345 | ], [ 346 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 347 | '', 348 | 'int foo = 1.5 ' + op + ' 1;', 349 | ' ~~~~~~~~', 350 | ]); 351 | 352 | test([ 353 | 'int foo = 1.5 ' + op + ' 1.5;', 354 | ], [ 355 | 'error on line 1 of : cannot convert from value of type bool to value of type int', 356 | '', 357 | 'int foo = 1.5 ' + op + ' 1.5;', 358 | ' ~~~~~~~~~~', 359 | ]); 360 | }); 361 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BitScript Demo 6 | 81 | 82 | 83 | 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /src/grammar.ts: -------------------------------------------------------------------------------- 1 | // The same operator precedence as C 2 | enum Power { 3 | LOWEST, 4 | COMMA, 5 | ASSIGN, 6 | TERNARY, 7 | OR, 8 | AND, 9 | BITOR, 10 | BITXOR, 11 | BITAND, 12 | EQ_NEQ, 13 | COMPARE, 14 | SHIFT, 15 | ADD_SUB, 16 | MUL_DIV, 17 | UNARY, 18 | CALL, 19 | MEMBER, 20 | } 21 | 22 | function parseGroup(context: ParserContext): Expression { 23 | if (!context.expect('(')) return null; 24 | var value: Expression = pratt.parse(context, Power.LOWEST); if (value === null) return null; 25 | if (!context.expect(')')) return null; 26 | return value; 27 | } 28 | 29 | function parseBlock(context: ParserContext): Block { 30 | var token: Token = context.current(); 31 | if (!context.expect('{')) return null; 32 | var statements: Statement[] = parseStatements(context); if (statements === null) return null; 33 | if (!context.expect('}')) return null; 34 | return new Block(context.spanSince(token.range), statements); 35 | } 36 | 37 | function parseBlockOrStatement(context: ParserContext): Block { 38 | if (context.peek('{')) return parseBlock(context); 39 | var statement: Statement = parseStatement(context); 40 | if (statement === null) return null; 41 | return new Block(statement.range, [statement]); 42 | } 43 | 44 | function parseIdentifier(context: ParserContext): Identifier { 45 | var token: Token = context.current(); if (!context.expect('IDENTIFIER')) return null; 46 | return new Identifier(token.range, token.text); 47 | } 48 | 49 | function parseType(context: ParserContext): Expression { 50 | var range: SourceRange = context.current().range; 51 | 52 | // Parse type modifiers 53 | var modifiers: number = 0; 54 | for (;;) { 55 | var token: Token = context.current(); 56 | var modifier: number = 0; 57 | if (context.eat('owned')) modifier = TypeModifier.OWNED; 58 | else if (context.eat('shared')) modifier = TypeModifier.SHARED; 59 | else break; 60 | if (modifiers & modifier) syntaxErrorDuplicateModifier(context.log, token); 61 | modifiers |= modifier; 62 | } 63 | 64 | var value: Expression = pratt.parse(context, Power.MEMBER - 1); if (value === null) return null; 65 | return modifiers !== 0 ? new TypeModifierExpression(context.spanSince(range), value, modifiers) : value; 66 | } 67 | 68 | function parseArguments(context: ParserContext): VariableDeclaration[] { 69 | var args: VariableDeclaration[] = []; 70 | while (!context.peek(')')) { 71 | if (args.length > 0 && !context.expect(',')) return null; 72 | var type: Expression = parseType(context); if (type === null) return null; 73 | var id: Identifier = parseIdentifier(context); if (id === null) return null; 74 | args.push(new VariableDeclaration(spanRange(type.range, id.range), id, 0, type, null)); 75 | } 76 | return args; 77 | } 78 | 79 | function parseStatements(context: ParserContext): Statement[] { 80 | var statements: Statement[] = []; 81 | while (!context.peek('}') && !context.peek('END')) { 82 | var statement: Statement = parseStatement(context); if (statement === null) return null; 83 | statements.push(statement); 84 | } 85 | return statements; 86 | } 87 | 88 | function parseStatement(context: ParserContext): Statement { 89 | var range: SourceRange = context.current().range; 90 | 91 | // Parse symbol modifiers 92 | var modifiers: number = 0; 93 | for (;;) { 94 | var token: Token = context.current(); 95 | var modifier: number = 0; 96 | if (context.eat('over')) modifier = SymbolModifier.OVER; 97 | else break; 98 | if (modifiers & modifier) syntaxErrorDuplicateModifier(context.log, token); 99 | modifiers |= modifier; 100 | } 101 | 102 | // Object declaration 103 | if (context.eat('class')) { 104 | var id: Identifier = parseIdentifier(context); if (id === null) return null; 105 | var base: Expression = null; 106 | if (context.eat(':')) { 107 | base = pratt.parse(context, Power.CALL); if (base === null) return null; 108 | } 109 | var block: Block = parseBlock(context); if (block === null) return null; 110 | return new ObjectDeclaration(context.spanSince(range), id, modifiers, base, block); 111 | } 112 | 113 | // Disambiguate identifiers used in expressions from identifiers used 114 | // as types in symbol declarations by starting to parse a type and 115 | // switching over to parsing an expression if it doesn't work out 116 | if (modifiers !== 0 || 117 | context.peek('IDENTIFIER') || 118 | context.peek('owned') || 119 | context.peek('shared')) { 120 | var type: Expression = parseType(context); if (type === null) return null; 121 | if (modifiers === 0 && !context.peek('IDENTIFIER')) { 122 | var value: Expression = pratt.resume(context, Power.LOWEST, type); if (value === null) return null; 123 | if (!context.expect(';')) return null; 124 | return new ExpressionStatement(context.spanSince(range), value); 125 | } 126 | var id: Identifier = parseIdentifier(context); if (id === null) return null; 127 | 128 | // Function declaration 129 | var group: Token = context.current(); 130 | if (context.eat('(')) { 131 | var args: VariableDeclaration[] = parseArguments(context); if (args === null) return null; 132 | if (!context.expect(')')) return null; 133 | var block: Block = null; 134 | if (!context.eat(';')) { 135 | block = parseBlock(context); if (block === null) return null; 136 | } 137 | return new FunctionDeclaration(context.spanSince(range), id, modifiers, type, args, block); 138 | } 139 | 140 | // Variable declaration 141 | var value: Expression = null; 142 | if (context.eat('=')) { 143 | value = pratt.parse(context, Power.LOWEST); if (value === null) return null; 144 | } 145 | if (!context.expect(';')) return null; 146 | return new VariableDeclaration(context.spanSince(range), id, modifiers, type, value); 147 | } 148 | 149 | // If statement 150 | if (context.eat('if')) { 151 | var value: Expression = parseGroup(context); if (value === null) return null; 152 | var thenBlock: Block = parseBlockOrStatement(context); if (thenBlock === null) return null; 153 | var elseBlock: Block = null; 154 | if (context.eat('else')) { 155 | elseBlock = parseBlockOrStatement(context); if (elseBlock === null) return null; 156 | } 157 | return new IfStatement(context.spanSince(range), value, thenBlock, elseBlock); 158 | } 159 | 160 | // While statement 161 | if (context.eat('while')) { 162 | var value: Expression = parseGroup(context); if (value === null) return null; 163 | var block: Block = parseBlockOrStatement(context); if (block === null) return null; 164 | return new WhileStatement(context.spanSince(range), value, block); 165 | } 166 | 167 | // For statement 168 | if (context.eat('for')) { 169 | if (!context.expect('(')) return null; 170 | var setup: Expression = null; 171 | var test: Expression = null; 172 | var update: Expression = null; 173 | if (!context.peek(';')) { 174 | setup = pratt.parse(context, Power.LOWEST); if (setup === null) return null; 175 | } 176 | if (!context.expect(';')) return null; 177 | if (!context.peek(';')) { 178 | test = pratt.parse(context, Power.LOWEST); if (test === null) return null; 179 | } 180 | if (!context.expect(';')) return null; 181 | if (!context.peek(')')) { 182 | update = pratt.parse(context, Power.LOWEST); if (update === null) return null; 183 | } 184 | if (!context.expect(')')) return null; 185 | var block: Block = parseBlockOrStatement(context); if (block === null) return null; 186 | return new ForStatement(context.spanSince(range), setup, test, update, block); 187 | } 188 | 189 | // Return statement 190 | if (context.eat('return')) { 191 | var value: Expression = null; 192 | if (!context.eat(';')) { 193 | value = pratt.parse(context, Power.LOWEST); if (value === null) return null; 194 | if (!context.expect(';')) return null; 195 | } 196 | return new ReturnStatement(context.spanSince(range), value); 197 | } 198 | 199 | // Break statement 200 | if (context.eat('break')) { 201 | if (!context.expect(';')) return null; 202 | return new BreakStatement(context.spanSince(range)); 203 | } 204 | 205 | // Continue statement 206 | if (context.eat('continue')) { 207 | if (!context.expect(';')) return null; 208 | return new ContinueStatement(context.spanSince(range)); 209 | } 210 | 211 | // Expression statement 212 | var value: Expression = pratt.parse(context, Power.LOWEST); if (value === null) return null; 213 | if (!context.expect(';')) return null; 214 | return new ExpressionStatement(context.spanSince(range), value); 215 | } 216 | 217 | function parseExpressions(context: ParserContext): Expression[] { 218 | var values: Expression[] = []; 219 | while (!context.peek(')')) { 220 | if (values.length > 0 && !context.expect(',')) return null; 221 | var value: Expression = pratt.parse(context, Power.COMMA); if (value === null) return null; 222 | values.push(value); 223 | } 224 | return values; 225 | } 226 | 227 | function parseTypes(context: ParserContext): Expression[] { 228 | var types: Expression[] = []; 229 | while (!context.peek('END_PARAMETER_LIST')) { 230 | if (types.length > 0 && !context.expect(',')) return null; 231 | var type: Expression = parseType(context); if (type === null) return null; 232 | types.push(type); 233 | } 234 | return types; 235 | } 236 | 237 | function buildUnaryPrefix(context: ParserContext, token: Token, node: Expression): Expression { 238 | return new UnaryExpression(spanRange(token.range, node.range), token.text, node); 239 | } 240 | 241 | function buildBinary(context: ParserContext, left: Expression, token: Token, right: Expression): Expression { 242 | return new BinaryExpression(spanRange(left.range, right.range), token.text, left, right); 243 | } 244 | 245 | // Cached parser 246 | var pratt: Pratt = new Pratt(); 247 | 248 | // Literals 249 | pratt.literal('null', (context, token) => new NullExpression(token.range)); 250 | pratt.literal('this', (context, token) => new ThisExpression(token.range)); 251 | pratt.literal('INT', (context, token) => new IntExpression(token.range, 0 | token.text)); 252 | pratt.literal('true', (context, token) => new BoolExpression(token.range, true)); 253 | pratt.literal('false', (context, token) => new BoolExpression(token.range, false)); 254 | pratt.literal('DOUBLE', (context, token) => new DoubleExpression(token.range, +token.text)); 255 | pratt.literal('IDENTIFIER', (context, token) => new SymbolExpression(token.range, token.text)); 256 | 257 | // Unary expressions 258 | pratt.prefix('+', Power.UNARY, buildUnaryPrefix); 259 | pratt.prefix('-', Power.UNARY, buildUnaryPrefix); 260 | pratt.prefix('!', Power.UNARY, buildUnaryPrefix); 261 | pratt.prefix('~', Power.UNARY, buildUnaryPrefix); 262 | pratt.prefix('move', Power.UNARY, (context, token, node) => new MoveExpression(spanRange(token.range, node.range), node)); 263 | 264 | // Binary expressions 265 | pratt.infix(',', Power.COMMA, buildBinary); 266 | pratt.infixRight('=', Power.ASSIGN, buildBinary); 267 | pratt.infix('||', Power.OR, buildBinary); 268 | pratt.infix('&&', Power.AND, buildBinary); 269 | pratt.infix('|', Power.BITOR, buildBinary); 270 | pratt.infix('^', Power.BITXOR, buildBinary); 271 | pratt.infix('&', Power.BITAND, buildBinary); 272 | pratt.infix('==', Power.EQ_NEQ, buildBinary); 273 | pratt.infix('!=', Power.EQ_NEQ, buildBinary); 274 | pratt.infix('<', Power.COMPARE, buildBinary); 275 | pratt.infix('>', Power.COMPARE, buildBinary); 276 | pratt.infix('<=', Power.COMPARE, buildBinary); 277 | pratt.infix('>=', Power.COMPARE, buildBinary); 278 | pratt.infix('<<', Power.SHIFT, buildBinary); 279 | pratt.infix('>>', Power.SHIFT, buildBinary); 280 | pratt.infix('>>>', Power.SHIFT, buildBinary); 281 | pratt.infix('+', Power.ADD_SUB, buildBinary); 282 | pratt.infix('-', Power.ADD_SUB, buildBinary); 283 | pratt.infix('*', Power.MUL_DIV, buildBinary); 284 | pratt.infix('/', Power.MUL_DIV, buildBinary); 285 | pratt.infix('%', Power.MUL_DIV, buildBinary); 286 | 287 | // Parenthetic group 288 | pratt.parselet('(', Power.LOWEST).prefix = context => { 289 | return parseGroup(context); 290 | }; 291 | 292 | // Ternary expression 293 | pratt.parselet('?', Power.TERNARY).infix = (context, left) => { 294 | context.next(); 295 | var middle: Expression = pratt.parse(context, Power.TERNARY); if (middle === null) return null; 296 | if (!context.expect(':')) return null; 297 | var right: Expression = pratt.parse(context, Power.TERNARY - 1); if (right === null) return null; 298 | return new TernaryExpression(context.spanSince(left.range), left, middle, right); 299 | }; 300 | 301 | // Member expression 302 | pratt.parselet('.', Power.MEMBER).infix = (context, left) => { 303 | var token: Token = context.next(); 304 | var id: Identifier = parseIdentifier(context); if (id === null) return null; 305 | return new MemberExpression(context.spanSince(left.range), left, id); 306 | }; 307 | 308 | // Call expression 309 | pratt.parselet('(', Power.CALL).infix = (context, left) => { 310 | var token: Token = context.next(); 311 | var args: Expression[] = parseExpressions(context); if (args === null) return null; 312 | if (!context.expect(')')) return null; 313 | return new CallExpression(context.spanSince(left.range), left, args); 314 | }; 315 | 316 | // Constructor expression 317 | pratt.parselet('new', Power.LOWEST).prefix = context => { 318 | var token: Token = context.next(); 319 | var type: Expression = parseType(context); if (type === null) return null; 320 | if (!context.expect('(')) return null; 321 | var args: Expression[] = parseExpressions(context); if (args === null) return null; 322 | if (!context.expect(')')) return null; 323 | return new NewExpression(context.spanSince(token.range), type, args); 324 | }; 325 | 326 | // Type parameter expression 327 | pratt.parselet('START_PARAMETER_LIST', Power.MEMBER).infix = (context, left) => { 328 | var token: Token = context.next(); 329 | var parameters: Expression[] = parseTypes(context); if (parameters === null) return null; 330 | if (!context.expect('END_PARAMETER_LIST')) return null; 331 | return new TypeParameterExpression(context.spanSince(left.range), left, parameters); 332 | }; 333 | 334 | function parse(log: Log, tokens: Token[]): Module { 335 | var context: ParserContext = new ParserContext(log, tokens); 336 | var range: SourceRange = context.current().range; 337 | var statements: Statement[] = parseStatements(context); if (statements === null) return null; 338 | if (!context.expect('END')) return null; 339 | range = context.spanSince(range); 340 | return new Module(range, new Block(range, statements)); 341 | } 342 | -------------------------------------------------------------------------------- /src/ast.ts: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Nodes 3 | //////////////////////////////////////////////////////////////////////////////// 4 | 5 | class AST { 6 | uniqueID: number; 7 | static nextUniqueID: number = 0; 8 | 9 | constructor( 10 | public range: SourceRange) { 11 | this.uniqueID = AST.nextUniqueID++; 12 | } 13 | } 14 | 15 | class Module extends AST { 16 | constructor( 17 | range: SourceRange, 18 | public block: Block) { 19 | super(range); 20 | } 21 | } 22 | 23 | class Identifier extends AST { 24 | constructor( 25 | range: SourceRange, 26 | public name: string) { 27 | super(range); 28 | } 29 | } 30 | 31 | class Block extends AST { 32 | scope: Scope = null; 33 | 34 | constructor( 35 | range: SourceRange, 36 | public statements: Statement[]) { 37 | super(range); 38 | } 39 | 40 | objectDeclarations(): ObjectDeclaration[] { 41 | return this.statements.filter(n => n instanceof ObjectDeclaration); 42 | } 43 | 44 | // Sort objects so base objects come before derived objects 45 | sortedObjectDeclarations(): ObjectDeclaration[] { 46 | var list = this.objectDeclarations(); 47 | for (var i = 0; i < list.length; i++) { 48 | var I = list[i].symbol.type.asObject(); 49 | for (var j = 0; j < i; j++) { 50 | var J = list[j].symbol.type.asObject(); 51 | if (TypeLogic.isBaseTypeOf(J, I)) { 52 | list.splice(j, 0, list.splice(i, 1)[0]); 53 | i = j - 1; 54 | } 55 | } 56 | } 57 | return list; 58 | } 59 | 60 | variableDeclarations(): VariableDeclaration[] { 61 | return this.statements.filter(n => n instanceof VariableDeclaration); 62 | } 63 | 64 | variableDeclarationsWithValues(): VariableDeclaration[] { 65 | return this.variableDeclarations().filter(n => n.value !== null); 66 | } 67 | 68 | variableDeclarationsWithoutValues(): VariableDeclaration[] { 69 | return this.variableDeclarations().filter(n => n.value !== null); 70 | } 71 | 72 | functionDeclarations(): FunctionDeclaration[] { 73 | return this.statements.filter(n => n instanceof FunctionDeclaration); 74 | } 75 | 76 | functionDeclarationsWithBlocks(): FunctionDeclaration[] { 77 | return this.functionDeclarations().filter(n => n.block !== null); 78 | } 79 | 80 | functionDeclarationsWithoutBlocks(): FunctionDeclaration[] { 81 | return this.functionDeclarations().filter(n => n.block !== null); 82 | } 83 | } 84 | 85 | //////////////////////////////////////////////////////////////////////////////// 86 | // Statements 87 | //////////////////////////////////////////////////////////////////////////////// 88 | 89 | interface StatementVisitor { 90 | visitExpressionStatement(node: ExpressionStatement): T; 91 | visitIfStatement(node: IfStatement): T; 92 | visitWhileStatement(node: WhileStatement): T; 93 | visitForStatement(node: ForStatement): T; 94 | visitReturnStatement(node: ReturnStatement): T; 95 | visitBreakStatement(node: BreakStatement): T; 96 | visitContinueStatement(node: ContinueStatement): T; 97 | visitDeclaration(node: Declaration): T; 98 | } 99 | 100 | class Statement extends AST { 101 | acceptStatementVisitor(visitor: StatementVisitor): T { 102 | assert(false); 103 | return null; 104 | } 105 | } 106 | 107 | class ExpressionStatement extends Statement { 108 | constructor( 109 | range: SourceRange, 110 | public value: Expression) { 111 | super(range); 112 | } 113 | 114 | acceptStatementVisitor(visitor: StatementVisitor): T { 115 | return visitor.visitExpressionStatement(this); 116 | } 117 | } 118 | 119 | class IfStatement extends Statement { 120 | constructor( 121 | range: SourceRange, 122 | public test: Expression, 123 | public thenBlock: Block, 124 | public elseBlock: Block) { 125 | super(range); 126 | } 127 | 128 | acceptStatementVisitor(visitor: StatementVisitor): T { 129 | return visitor.visitIfStatement(this); 130 | } 131 | } 132 | 133 | class WhileStatement extends Statement { 134 | constructor( 135 | range: SourceRange, 136 | public test: Expression, 137 | public block: Block) { 138 | super(range); 139 | } 140 | 141 | acceptStatementVisitor(visitor: StatementVisitor): T { 142 | return visitor.visitWhileStatement(this); 143 | } 144 | } 145 | 146 | class ForStatement extends Statement { 147 | constructor( 148 | range: SourceRange, 149 | public setup: Expression, 150 | public test: Expression, 151 | public update: Expression, 152 | public block: Block) { 153 | super(range); 154 | } 155 | 156 | acceptStatementVisitor(visitor: StatementVisitor): T { 157 | return visitor.visitForStatement(this); 158 | } 159 | } 160 | 161 | class ReturnStatement extends Statement { 162 | constructor( 163 | range: SourceRange, 164 | public value: Expression) { 165 | super(range); 166 | } 167 | 168 | acceptStatementVisitor(visitor: StatementVisitor): T { 169 | return visitor.visitReturnStatement(this); 170 | } 171 | } 172 | 173 | class BreakStatement extends Statement { 174 | constructor( 175 | range: SourceRange) { 176 | super(range); 177 | } 178 | 179 | acceptStatementVisitor(visitor: StatementVisitor): T { 180 | return visitor.visitBreakStatement(this); 181 | } 182 | } 183 | 184 | class ContinueStatement extends Statement { 185 | constructor( 186 | range: SourceRange) { 187 | super(range); 188 | } 189 | 190 | acceptStatementVisitor(visitor: StatementVisitor): T { 191 | return visitor.visitContinueStatement(this); 192 | } 193 | } 194 | 195 | //////////////////////////////////////////////////////////////////////////////// 196 | // Declarations 197 | //////////////////////////////////////////////////////////////////////////////// 198 | 199 | interface DeclarationVisitor { 200 | visitObjectDeclaration(node: ObjectDeclaration): T; 201 | visitFunctionDeclaration(node: FunctionDeclaration): T; 202 | visitVariableDeclaration(node: VariableDeclaration): T; 203 | } 204 | 205 | class Declaration extends Statement { 206 | symbol: Symbol = null; 207 | 208 | constructor( 209 | range: SourceRange, 210 | public id: Identifier, 211 | public modifiers: number) { 212 | super(range); 213 | } 214 | 215 | acceptStatementVisitor(visitor: StatementVisitor): T { 216 | return visitor.visitDeclaration(this); 217 | } 218 | 219 | acceptDeclarationVisitor(visitor: DeclarationVisitor): T { 220 | assert(false); 221 | return null; 222 | } 223 | } 224 | 225 | class ObjectDeclaration extends Declaration { 226 | constructor( 227 | range: SourceRange, 228 | id: Identifier, 229 | modifiers: number, 230 | public base: Expression, 231 | public block: Block) { 232 | super(range, id, modifiers); 233 | } 234 | 235 | acceptDeclarationVisitor(visitor: DeclarationVisitor): T { 236 | return visitor.visitObjectDeclaration(this); 237 | } 238 | } 239 | 240 | class FunctionDeclaration extends Declaration { 241 | // Store a separate scope for the function arguments because the function 242 | // may be abstract, in which case we can't use the scope of the body block 243 | scope: Scope = null; 244 | 245 | constructor( 246 | range: SourceRange, 247 | id: Identifier, 248 | modifiers: number, 249 | public result: Expression, 250 | public args: VariableDeclaration[], 251 | public block: Block) { 252 | super(range, id, modifiers); 253 | } 254 | 255 | acceptDeclarationVisitor(visitor: DeclarationVisitor): T { 256 | return visitor.visitFunctionDeclaration(this); 257 | } 258 | } 259 | 260 | class VariableDeclaration extends Declaration { 261 | constructor( 262 | range: SourceRange, 263 | id: Identifier, 264 | modifiers: number, 265 | public type: Expression, 266 | public value: Expression) { 267 | super(range, id, modifiers); 268 | } 269 | 270 | acceptDeclarationVisitor(visitor: DeclarationVisitor): T { 271 | return visitor.visitVariableDeclaration(this); 272 | } 273 | } 274 | 275 | //////////////////////////////////////////////////////////////////////////////// 276 | // Expressions 277 | //////////////////////////////////////////////////////////////////////////////// 278 | 279 | interface ExpressionVisitor { 280 | visitSymbolExpression(node: SymbolExpression): T; 281 | visitMoveExpression(node: MoveExpression): T; 282 | visitUnaryExpression(node: UnaryExpression): T; 283 | visitBinaryExpression(node: BinaryExpression): T; 284 | visitTernaryExpression(node: TernaryExpression): T; 285 | visitMemberExpression(node: MemberExpression): T; 286 | visitIntExpression(node: IntExpression): T; 287 | visitBoolExpression(node: BoolExpression): T; 288 | visitDoubleExpression(node: DoubleExpression): T; 289 | visitNullExpression(node: NullExpression): T; 290 | visitThisExpression(node: ThisExpression): T; 291 | visitCallExpression(node: CallExpression): T; 292 | visitNewExpression(node: NewExpression): T; 293 | visitTypeModifierExpression(node: TypeModifierExpression): T; 294 | visitTypeParameterExpression(node: TypeParameterExpression): T; 295 | } 296 | 297 | class Expression extends AST { 298 | computedType: WrappedType = null; 299 | 300 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 301 | assert(false); 302 | return null; 303 | } 304 | } 305 | 306 | class SymbolExpression extends Expression { 307 | symbol: Symbol = null; 308 | 309 | constructor( 310 | range: SourceRange, 311 | public name: string) { 312 | super(range); 313 | } 314 | 315 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 316 | return visitor.visitSymbolExpression(this); 317 | } 318 | } 319 | 320 | // A move expression is the only way to convert from an owned L-value. 321 | // Originally you could transfer ownership with a simple assignment, but 322 | // that led to too many dangling pointer mistakes. This way, ownership 323 | // transfers are explicit and easy to see when reading your code. 324 | class MoveExpression extends Expression { 325 | constructor( 326 | range: SourceRange, 327 | public value: Expression) { 328 | super(range); 329 | } 330 | 331 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 332 | return visitor.visitMoveExpression(this); 333 | } 334 | } 335 | 336 | class UnaryExpression extends Expression { 337 | constructor( 338 | range: SourceRange, 339 | public op: string, 340 | public value: Expression) { 341 | super(range); 342 | } 343 | 344 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 345 | return visitor.visitUnaryExpression(this); 346 | } 347 | } 348 | 349 | class BinaryExpression extends Expression { 350 | constructor( 351 | range: SourceRange, 352 | public op: string, 353 | public left: Expression, 354 | public right: Expression) { 355 | super(range); 356 | } 357 | 358 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 359 | return visitor.visitBinaryExpression(this); 360 | } 361 | 362 | isAssignment(): boolean { 363 | return this.op === '='; 364 | } 365 | } 366 | 367 | class TernaryExpression extends Expression { 368 | constructor( 369 | range: SourceRange, 370 | public value: Expression, 371 | public trueValue: Expression, 372 | public falseValue: Expression) { 373 | super(range); 374 | } 375 | 376 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 377 | return visitor.visitTernaryExpression(this); 378 | } 379 | } 380 | 381 | class MemberExpression extends Expression { 382 | symbol: Symbol = null; 383 | 384 | constructor( 385 | range: SourceRange, 386 | public value: Expression, 387 | public id: Identifier) { 388 | super(range); 389 | } 390 | 391 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 392 | return visitor.visitMemberExpression(this); 393 | } 394 | } 395 | 396 | class IntExpression extends Expression { 397 | constructor( 398 | range: SourceRange, 399 | public value: number) { 400 | super(range); 401 | assert(value === (0 | value)); 402 | } 403 | 404 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 405 | return visitor.visitIntExpression(this); 406 | } 407 | } 408 | 409 | class BoolExpression extends Expression { 410 | constructor( 411 | range: SourceRange, 412 | public value: boolean) { 413 | super(range); 414 | } 415 | 416 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 417 | return visitor.visitBoolExpression(this); 418 | } 419 | } 420 | 421 | class DoubleExpression extends Expression { 422 | constructor( 423 | range: SourceRange, 424 | public value: number) { 425 | super(range); 426 | } 427 | 428 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 429 | return visitor.visitDoubleExpression(this); 430 | } 431 | } 432 | 433 | class NullExpression extends Expression { 434 | constructor( 435 | range: SourceRange) { 436 | super(range); 437 | } 438 | 439 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 440 | return visitor.visitNullExpression(this); 441 | } 442 | } 443 | 444 | class ThisExpression extends Expression { 445 | constructor( 446 | range: SourceRange) { 447 | super(range); 448 | } 449 | 450 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 451 | return visitor.visitThisExpression(this); 452 | } 453 | } 454 | 455 | class CallExpression extends Expression { 456 | constructor( 457 | range: SourceRange, 458 | public value: Expression, 459 | public args: Expression[]) { 460 | super(range); 461 | } 462 | 463 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 464 | return visitor.visitCallExpression(this); 465 | } 466 | } 467 | 468 | class NewExpression extends Expression { 469 | constructor( 470 | range: SourceRange, 471 | public type: Expression, 472 | public args: Expression[]) { 473 | super(range); 474 | } 475 | 476 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 477 | return visitor.visitNewExpression(this); 478 | } 479 | } 480 | 481 | class TypeModifierExpression extends Expression { 482 | constructor( 483 | range: SourceRange, 484 | public type: Expression, 485 | public modifiers: number) { 486 | super(range); 487 | } 488 | 489 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 490 | return visitor.visitTypeModifierExpression(this); 491 | } 492 | } 493 | 494 | class TypeParameterExpression extends Expression { 495 | constructor( 496 | range: SourceRange, 497 | public type: Expression, 498 | public parameters: Expression[]) { 499 | super(range); 500 | } 501 | 502 | acceptExpressionVisitor(visitor: ExpressionVisitor): T { 503 | return visitor.visitTypeParameterExpression(this); 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /demo/source-map.min.js: -------------------------------------------------------------------------------- 1 | function define(e,t,n){if(typeof e!="string")throw new TypeError("Expected string, got: "+e);arguments.length==2&&(n=t);if(e in define.modules)throw new Error("Module already defined: "+e);define.modules[e]=n}function Domain(){this.modules={},this._currentModule=null}define.modules={},function(){function e(e){var t=e.split("/"),n=1;while(nt)-(e0&&t.column>=0&&!n&&!r&&!i)return;if(t&&"line"in t&&"column"in t&&n&&"line"in n&&"column"in n&&t.line>0&&t.column>=0&&n.line>0&&n.column>=0&&r)return;throw new Error("Invalid mapping.")},o.prototype._serializeMappings=function(){var t=0,n=1,i=0,s=0,o=0,u=0,a="",l;this._mappings.sort(f);for(var c=0,h=this._mappings.length;c0){if(!f(l,this._mappings[c-1]))continue;a+=","}a+=r.encode(l.generated.column-t),t=l.generated.column,l.source&&l.original&&(a+=r.encode(this._sources.indexOf(l.source)-u),u=this._sources.indexOf(l.source),a+=r.encode(l.original.line-1-s),s=l.original.line-1,a+=r.encode(l.original.column-i),i=l.original.column,l.name&&(a+=r.encode(this._names.indexOf(l.name)-o),o=this._names.indexOf(l.name)))}return a},o.prototype.toJSON=function(){var t={version:this._version,file:this._file,sources:this._sources.toArray(),names:this._names.toArray(),mappings:this._serializeMappings()};return this._sourceRoot&&(t.sourceRoot=this._sourceRoot),this._sourcesContents&&(t.sourcesContent=t.sources.map(function(e){return t.sourceRoot&&(e=i.relative(t.sourceRoot,e)),Object.prototype.hasOwnProperty.call(this._sourcesContents,i.toSetString(e))?this._sourcesContents[i.toSetString(e)]:null},this)),t},o.prototype.toString=function(){return JSON.stringify(this)},t.SourceMapGenerator=o}),define("source-map/base64-vlq",["require","exports","module","source-map/base64"],function(e,t,n){function a(e){return e<0?(-e<<1)+1:(e<<1)+0}function f(e){var t=(e&1)===1,n=e>>1;return t?-n:n}var r=e("./base64"),i=5,s=1<>>=i,f>0&&(s|=u),n+=r.encode(s);while(f>0);return n},t.decode=function(t){var n=0,s=t.length,a=0,l=0,c,h;do{if(n>=s)throw new Error("Expected more digits in base 64 VLQ value.");h=r.decode(t.charAt(n++)),c=!!(h&u),h&=o,a+=h<=0&&t0)if(c.charAt(0)===";")r++,c=c.slice(1),i=0;else if(c.charAt(0)===",")c=c.slice(1);else{h={},h.generatedLine=r,p=o.decode(c),h.generatedColumn=i+p.value,i=h.generatedColumn,c=p.rest;if(c.length>0&&!l.test(c.charAt(0))){p=o.decode(c),h.source=this._sources.at(a+p.value),a+=p.value,c=p.rest;if(c.length===0||l.test(c.charAt(0)))throw new Error("Found a source, but no line and column");p=o.decode(c),h.originalLine=s+p.value,s=h.originalLine,h.originalLine+=1,c=p.rest;if(c.length===0||l.test(c.charAt(0)))throw new Error("Found a source and line, but no column");p=o.decode(c),h.originalColumn=u+p.value,u=h.originalColumn,c=p.rest,c.length>0&&!l.test(c.charAt(0))&&(p=o.decode(c),h.name=this._names.at(f+p.value),f+=p.value,c=p.rest)}this._generatedMappings.push(h),typeof h.originalLine=="number"&&this._originalMappings.push(h)}this._originalMappings.sort(this._compareOriginalPositions)},u.prototype._compareOriginalPositions=function(t,n){if(t.source>n.source)return 1;if(t.source0?t-o>1?r(o,t,n,i,s):i[o]:o-e>1?r(e,o,n,i,s):e<0?null:i[e]}t.search=function(t,n,i){return n.length>0?r(-1,n.length,t,n,i):null}}),define("source-map/source-node",["require","exports","module","source-map/source-map-generator","source-map/util"],function(e,t,n){function s(e,t,n,r,i){this.children=[],this.sourceContents={},this.line=e===undefined?null:e,this.column=t===undefined?null:t,this.source=n===undefined?null:n,this.name=i===undefined?null:i,r!=null&&this.add(r)}var r=e("./source-map-generator").SourceMapGenerator,i=e("./util");s.fromStringWithSourceMap=function(t,n){function f(e,t){e===null||e.source===undefined?r.add(t):r.add(new s(e.originalLine,e.originalColumn,e.source,t,e.name))}var r=new s,i=t.split("\n"),o=1,u=0,a=null;return n.eachMapping(function(e){if(a===null){while(o=0;n--)this.prepend(t[n]);else{if(!(t instanceof s||typeof t=="string"))throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+t);this.children.unshift(t)}return this},s.prototype.walk=function(t){this.children.forEach(function(e){e instanceof s?e.walk(t):e!==""&&t(e,{source:this.source,line:this.line,column:this.column,name:this.name})},this)},s.prototype.join=function(t){var n,r,i=this.children.length;if(i>0){n=[];for(r=0;r, DeclarationVisitor, ExpressionVisitor { 2 | needExtendsPolyfill: boolean = false; 3 | needMultiplicationPolyfill: boolean = false; 4 | 5 | constructor( 6 | public wrap: (node: AST, result: any) => any) { 7 | } 8 | 9 | static generate(node: Module): string { 10 | return escodegen.generate(new OutputJS((node, result) => result).visitModule(node), { 11 | format: { indent: { style: ' ' } } 12 | }); 13 | } 14 | 15 | static generateWithSourceMap(node: Module, root: string): { code: string; map: string } { 16 | return escodegen.generate(new OutputJS((node, result) => { 17 | // Source map support in escodegen is pretty bad. Every single object 18 | // that escodegen touches must have a valid location or it puts NaNs in 19 | // the map. This unfortunately means that the source map is way too 20 | // fine-grained and doesn't accurately represent the source code in 21 | // many places. Oh well, it's quick and dirty and better than nothing. 22 | // It still seems to generate a few NaNs in between functions :( 23 | if (node.range !== null) { 24 | var start = node.range.start; 25 | var end = node.range.end; 26 | result.loc = { 27 | source: node.range.source.name, 28 | start: { 29 | line: start.line, 30 | column: start.column - 1 31 | }, 32 | end: { 33 | line: end.line, 34 | column: end.column - 1 35 | } 36 | }; 37 | } 38 | 39 | return result; 40 | }).visitModule(node), { 41 | sourceMap: true, 42 | sourceMapRoot: root, 43 | sourceMapWithCode: true, 44 | format: { indent: { style: ' ' } } 45 | }); 46 | } 47 | 48 | defaultForType(type: WrappedType): Object { 49 | var t: Type = type.innerType; 50 | return { 51 | type: 'Literal', 52 | value: 53 | t === SpecialType.INT || t === SpecialType.DOUBLE ? 0 : 54 | t === SpecialType.BOOL ? false : 55 | null 56 | }; 57 | } 58 | 59 | visitModule(node: Module): Object { 60 | var result: any = { 61 | type: 'Program', 62 | body: flatten([ 63 | flatten(node.block.sortedObjectDeclarations().map(n => this.generateObjectDeclaration(n))), 64 | node.block.variableDeclarations().map(n => n.acceptStatementVisitor(this)), 65 | node.block.functionDeclarationsWithBlocks().map(n => n.acceptStatementVisitor(this)), 66 | ]) 67 | }; 68 | 69 | if (this.needMultiplicationPolyfill) { 70 | result.body = esprima.parse([ 71 | 'if (!Math.imul) {', 72 | ' Math.imul = function(a, b) {', 73 | ' var al = a & 0xFFFF, bl = b & 0xFFFF;', 74 | ' return al * bl + ((a >>> 16) * bl + al * (b >>> 16) << 16) | 0;', 75 | ' };', 76 | '}', 77 | ].join('\n')).body.concat(result.body); 78 | } 79 | 80 | if (this.needExtendsPolyfill) { 81 | result.body = esprima.parse([ 82 | 'function __extends(d, b) {', 83 | ' function c() {}', 84 | ' c.prototype = b.prototype;', 85 | ' d.prototype = new c();', 86 | ' d.prototype.constructor = d;', 87 | '}', 88 | ].join('\n')).body.concat(result.body); 89 | } 90 | 91 | return this.wrap(node, result); 92 | } 93 | 94 | visitBlock(node: Block): Object { 95 | return this.wrap(node, { 96 | type: 'BlockStatement', 97 | body: node.statements.map(n => n.acceptStatementVisitor(this)) 98 | }); 99 | } 100 | 101 | visitIdentifier(node: Identifier): Object { 102 | return this.wrap(node, { 103 | type: 'Identifier', 104 | name: node.name 105 | }); 106 | } 107 | 108 | visitExpressionStatement(node: ExpressionStatement): Object { 109 | return this.wrap(node, { 110 | type: 'ExpressionStatement', 111 | expression: node.value.acceptExpressionVisitor(this) 112 | }); 113 | } 114 | 115 | visitIfStatement(node: IfStatement): Object { 116 | var elseBlock: any = node.elseBlock !== null ? this.visitBlock(node.elseBlock) : null; 117 | return this.wrap(node, { 118 | type: 'IfStatement', 119 | test: node.test.acceptExpressionVisitor(this), 120 | consequent: this.visitBlock(node.thenBlock), 121 | alternate: elseBlock !== null && elseBlock.body.length === 1 && elseBlock.body[0].type === 'IfStatement' ? elseBlock.body[0] : elseBlock 122 | }); 123 | } 124 | 125 | visitWhileStatement(node: WhileStatement): Object { 126 | return this.wrap(node, { 127 | type: 'WhileStatement', 128 | test: node.test.acceptExpressionVisitor(this), 129 | body: this.visitBlock(node.block) 130 | }); 131 | } 132 | 133 | visitForStatement(node: ForStatement): Object { 134 | return this.wrap(node, { 135 | type: 'ForStatement', 136 | init: node.setup !== null ? node.setup.acceptExpressionVisitor(this) : null, 137 | test: node.test !== null ? node.test.acceptExpressionVisitor(this) : null, 138 | update: node.update !== null ? node.update.acceptExpressionVisitor(this) : null, 139 | body: this.visitBlock(node.block) 140 | }); 141 | } 142 | 143 | visitReturnStatement(node: ReturnStatement): Object { 144 | return this.wrap(node, { 145 | type: 'ReturnStatement', 146 | argument: node.value !== null ? node.value.acceptExpressionVisitor(this) : null 147 | }); 148 | } 149 | 150 | visitBreakStatement(node: BreakStatement): Object { 151 | return this.wrap(node, { 152 | type: 'BreakStatement', 153 | label: null 154 | }); 155 | } 156 | 157 | visitContinueStatement(node: ContinueStatement): Object { 158 | return this.wrap(node, { 159 | type: 'ContinueStatement', 160 | label: null 161 | }); 162 | } 163 | 164 | visitDeclaration(node: Declaration): Object { 165 | return node.acceptDeclarationVisitor(this); 166 | } 167 | 168 | getBaseVariables(node: Expression): VariableDeclaration[] { 169 | if (node instanceof SymbolExpression) { 170 | var base: ObjectDeclaration = (node).symbol.node; 171 | return this.getBaseVariables(base.base).concat(base.block.statements.filter(n => n instanceof VariableDeclaration)); 172 | } 173 | return []; 174 | } 175 | 176 | generateConstructor(node: ObjectDeclaration): Object[] { 177 | var variables: VariableDeclaration[] = node.block.statements.filter(n => n instanceof VariableDeclaration); 178 | var baseVariables: VariableDeclaration[] = this.getBaseVariables(node.base).filter(n => n.value === null); 179 | 180 | // Create the constructor function 181 | var result: any[] = [this.wrap(node, { 182 | type: 'FunctionDeclaration', 183 | params: baseVariables.concat(variables.filter(n => n.value === null)).map(n => this.visitIdentifier(n.id)), 184 | id: this.visitIdentifier(node.id), 185 | body: this.wrap(node, { 186 | type: 'BlockStatement', 187 | body: variables.map(n => this.wrap(node, { 188 | type: 'ExpressionStatement', 189 | expression: this.wrap(node, { 190 | type: 'AssignmentExpression', 191 | operator: '=', 192 | left: this.wrap(node, { 193 | type: 'MemberExpression', 194 | object: this.wrap(node, { 195 | type: 'ThisExpression' 196 | }), 197 | property: this.visitIdentifier(n.id), 198 | computed: false 199 | }), 200 | right: n.value !== null ? n.value.acceptExpressionVisitor(this) : this.visitIdentifier(n.id) 201 | }) 202 | })) 203 | }) 204 | })]; 205 | 206 | // Inherit from the base class 207 | if (node.base !== null) { 208 | // Add a call to the constructor for the base class 209 | result[0].body.body.unshift(this.wrap(node, { 210 | type: 'ExpressionStatement', 211 | expression: this.wrap(node, { 212 | type: 'CallExpression', 213 | callee: this.wrap(node, { 214 | type: 'MemberExpression', 215 | object: node.base.acceptExpressionVisitor(this), 216 | property: this.wrap(node, { type: 'Identifier', name: 'call' }) 217 | }), 218 | arguments: [this.wrap(node, { type: 'ThisExpression' })].concat(baseVariables.map(n => this.visitIdentifier(n.id))) 219 | }) 220 | })); 221 | 222 | // Add a call to __extends() 223 | this.needExtendsPolyfill = true; 224 | result.push(this.wrap(node, { 225 | type: 'ExpressionStatement', 226 | expression: this.wrap(node, { 227 | type: 'CallExpression', 228 | callee: this.wrap(node, { type: 'Identifier', name: '__extends' }), 229 | arguments: [ 230 | this.visitIdentifier(node.id), 231 | node.base.acceptExpressionVisitor(this) 232 | ] 233 | }) 234 | })); 235 | } 236 | 237 | return this.wrap(node, result); 238 | } 239 | 240 | generateMemberFunctions(node: ObjectDeclaration): Object[] { 241 | return node.block.statements.filter(n => n instanceof FunctionDeclaration && n.block !== null).map(n => { 242 | var result: any = this.visitFunctionDeclaration(n); 243 | result.type = 'FunctionExpression'; 244 | result.id = null; 245 | return this.wrap(n, { 246 | type: 'ExpressionStatement', 247 | expression: this.wrap(n, { 248 | type: 'AssignmentExpression', 249 | operator: '=', 250 | left: this.wrap(n, { 251 | type: 'MemberExpression', 252 | object: this.wrap(n, { 253 | type: 'MemberExpression', 254 | object: this.visitIdentifier(node.id), 255 | property: this.wrap(n, { type: 'Identifier', name: 'prototype' }) 256 | }), 257 | property: this.visitIdentifier(n.id) 258 | }), 259 | right: result 260 | }) 261 | }); 262 | }); 263 | } 264 | 265 | generateObjectDeclaration(node: ObjectDeclaration): Object[] { 266 | return this.generateConstructor(node).concat(this.generateMemberFunctions(node)); 267 | } 268 | 269 | visitObjectDeclaration(node: ObjectDeclaration): Object { 270 | assert(false); 271 | return null; 272 | } 273 | 274 | visitFunctionDeclaration(node: FunctionDeclaration): Object { 275 | assert(node.block !== null); 276 | return this.wrap(node, { 277 | type: 'FunctionDeclaration', 278 | params: node.args.map(n => this.visitIdentifier(n.id)), 279 | id: this.visitIdentifier(node.id), 280 | body: this.visitBlock(node.block) 281 | }); 282 | } 283 | 284 | visitVariableDeclaration(node: VariableDeclaration): Object { 285 | return this.wrap(node, { 286 | type: 'VariableDeclaration', 287 | kind: 'var', 288 | declarations: [this.wrap(node, { 289 | type: 'VariableDeclarator', 290 | id: this.visitIdentifier(node.id), 291 | init: node.value !== null ? node.value.acceptExpressionVisitor(this) : this.defaultForType(node.symbol.type) 292 | })] 293 | }); 294 | } 295 | 296 | visitSymbolExpression(node: SymbolExpression): Object { 297 | var result: Object = this.wrap(node, { 298 | type: 'Identifier', 299 | name: node.name 300 | }); 301 | 302 | // Insert "this." before object fields 303 | if (node.symbol.enclosingObject !== null) { 304 | return this.wrap(node, { 305 | type: 'MemberExpression', 306 | object: this.wrap(node, { 307 | type: 'ThisExpression' 308 | }), 309 | property: result 310 | }); 311 | } 312 | 313 | return result; 314 | } 315 | 316 | static INTEGER_OPS : { [op: string]: boolean } = { 317 | '~': true, 318 | '|': true, 319 | '&': true, 320 | '^': true, 321 | '<<': true, 322 | '>>': true, 323 | 324 | // This is an integer operator because we force every value to be an integer 325 | // before we assign it to the symbol, so assignment expressions will always 326 | // result in an integer 327 | '=': true, 328 | }; 329 | 330 | wrapIntegerOperator(node: AST, result: any): any { 331 | // Don't need to emit anything for unary operators on literals 332 | // (otherwise all negative values will have "| 0" next to them) 333 | if (result.type === 'UnaryExpression' && result.argument.type === 'Literal') { 334 | return result; 335 | } 336 | 337 | return this.wrap(node, { 338 | type: 'BinaryExpression', 339 | operator: '|', 340 | left: result, 341 | right: this.wrap(node, { 342 | type: 'Literal', 343 | value: 0 344 | }) 345 | }); 346 | } 347 | 348 | visitMoveExpression(node: MoveExpression): Object { 349 | return node.value.acceptExpressionVisitor(this); 350 | } 351 | 352 | visitUnaryExpression(node: UnaryExpression): Object { 353 | var result: Object = this.wrap(node, { 354 | type: 'UnaryExpression', 355 | operator: node.op, 356 | argument: node.value.acceptExpressionVisitor(this), 357 | prefix: true 358 | }); 359 | 360 | // Cast the result to an integer if needed (- -2147483648 is still -2147483648) 361 | if (!OutputJS.INTEGER_OPS[node.op] && node.computedType.innerType === SpecialType.INT) { 362 | result = this.wrapIntegerOperator(node, result); 363 | } 364 | 365 | return result; 366 | } 367 | 368 | visitBinaryExpression(node: BinaryExpression): Object { 369 | // Special-case integer multiplication 370 | if (node.op === '*' && node.computedType.innerType === SpecialType.INT) { 371 | this.needMultiplicationPolyfill = true; 372 | return this.wrap(node, { 373 | type: 'CallExpression', 374 | callee: this.wrap(node, { 375 | type: 'MemberExpression', 376 | object: this.wrap(node, { type: 'Identifier', name: 'Math' }), 377 | property: this.wrap(node, { type: 'Identifier', name: 'imul' }) 378 | }), 379 | arguments: [ 380 | node.left.acceptExpressionVisitor(this), 381 | node.right.acceptExpressionVisitor(this) 382 | ] 383 | }); 384 | } 385 | 386 | var result: Object = this.wrap(node, { 387 | type: 388 | node.op === '=' ? 'AssignmentExpression' : 389 | node.op === '&&' || node.op === '||' ? 'LogicalExpression' : 390 | 'BinaryExpression', 391 | operator: node.op, 392 | left: node.left.acceptExpressionVisitor(this), 393 | right: node.right.acceptExpressionVisitor(this) 394 | }); 395 | 396 | // Cast the result to an integer if needed (1073741824 + 1073741824 is -2147483648) 397 | if (!OutputJS.INTEGER_OPS[node.op] && node.computedType.innerType === SpecialType.INT) { 398 | result = this.wrapIntegerOperator(node, result); 399 | } 400 | 401 | return result; 402 | } 403 | 404 | visitTernaryExpression(node: TernaryExpression): Object { 405 | return this.wrap(node, { 406 | type: 'ConditionalExpression', 407 | test: node.value.acceptExpressionVisitor(this), 408 | consequent: node.trueValue.acceptExpressionVisitor(this), 409 | alternate: node.falseValue.acceptExpressionVisitor(this) 410 | }); 411 | } 412 | 413 | visitMemberExpression(node: MemberExpression): Object { 414 | if (node.value.computedType.innerType === NativeTypes.MATH) { 415 | switch (node.id.name) { 416 | case 'NAN': 417 | return this.wrap(node, { 418 | type: 'Identifier', 419 | name: 'NaN' 420 | }); 421 | 422 | case 'INFINITY': 423 | return this.wrap(node, { 424 | type: 'Identifier', 425 | name: 'Infinity' 426 | }); 427 | } 428 | } 429 | 430 | return this.wrap(node, { 431 | type: 'MemberExpression', 432 | object: node.value.acceptExpressionVisitor(this), 433 | property: this.visitIdentifier(node.id) 434 | }); 435 | } 436 | 437 | visitIntExpression(node: IntExpression): Object { 438 | return this.wrap(node, { 439 | type: 'Literal', 440 | value: node.value 441 | }); 442 | } 443 | 444 | visitBoolExpression(node: BoolExpression): Object { 445 | return this.wrap(node, { 446 | type: 'Literal', 447 | value: node.value 448 | }); 449 | } 450 | 451 | visitDoubleExpression(node: DoubleExpression): Object { 452 | return this.wrap(node, { 453 | type: 'Literal', 454 | value: node.value 455 | }); 456 | } 457 | 458 | visitNullExpression(node: NullExpression): Object { 459 | return this.wrap(node, { 460 | type: 'Literal', 461 | value: null 462 | }); 463 | } 464 | 465 | visitThisExpression(node: ThisExpression): Object { 466 | return this.wrap(node, { 467 | type: 'ThisExpression' 468 | }); 469 | } 470 | 471 | visitCallExpression(node: CallExpression): Object { 472 | if (node.value instanceof MemberExpression) { 473 | var member: MemberExpression = node.value; 474 | if (member.value.computedType.innerType === NativeTypes.LIST) { 475 | switch (member.symbol) { 476 | 477 | case NativeTypes.LIST_GET: 478 | assert(node.args.length === 1); 479 | return this.wrap(node, { 480 | type: 'MemberExpression', 481 | object: member.value.acceptExpressionVisitor(this), 482 | property: node.args[0].acceptExpressionVisitor(this), 483 | computed: true 484 | }); 485 | 486 | case NativeTypes.LIST_SET: 487 | assert(node.args.length === 2); 488 | return this.wrap(node, { 489 | type: 'AssignmentExpression', 490 | operator: '=', 491 | left: this.wrap(node, { 492 | type: 'MemberExpression', 493 | object: member.value.acceptExpressionVisitor(this), 494 | property: node.args[0].acceptExpressionVisitor(this), 495 | computed: true 496 | }), 497 | right: node.args[1].acceptExpressionVisitor(this) 498 | }); 499 | 500 | case NativeTypes.LIST_PUSH: 501 | assert(node.args.length === 1); 502 | return this.wrap(node, { 503 | type: 'CallExpression', 504 | callee: this.wrap(node, { 505 | type: 'MemberExpression', 506 | object: member.value.acceptExpressionVisitor(this), 507 | property: this.wrap(node, { type: 'Identifier', name: 'push' }) 508 | }), 509 | arguments: [node.args[0].acceptExpressionVisitor(this)] 510 | }); 511 | 512 | case NativeTypes.LIST_POP: 513 | assert(node.args.length === 0); 514 | return this.wrap(node, { 515 | type: 'CallExpression', 516 | callee: this.wrap(node, { 517 | type: 'MemberExpression', 518 | object: member.value.acceptExpressionVisitor(this), 519 | property: this.wrap(node, { type: 'Identifier', name: 'pop' }) 520 | }), 521 | arguments: [] 522 | }); 523 | 524 | case NativeTypes.LIST_UNSHIFT: 525 | assert(node.args.length === 1); 526 | return this.wrap(node, { 527 | type: 'CallExpression', 528 | callee: this.wrap(node, { 529 | type: 'MemberExpression', 530 | object: member.value.acceptExpressionVisitor(this), 531 | property: this.wrap(node, { type: 'Identifier', name: 'unshift' }) 532 | }), 533 | arguments: [node.args[0].acceptExpressionVisitor(this)] 534 | }); 535 | 536 | case NativeTypes.LIST_SHIFT: 537 | assert(node.args.length === 0); 538 | return this.wrap(node, { 539 | type: 'CallExpression', 540 | callee: this.wrap(node, { 541 | type: 'MemberExpression', 542 | object: member.value.acceptExpressionVisitor(this), 543 | property: this.wrap(node, { type: 'Identifier', name: 'shift' }) 544 | }), 545 | arguments: [] 546 | }); 547 | 548 | case NativeTypes.LIST_INDEX_OF: 549 | assert(node.args.length === 1); 550 | return this.wrap(node, { 551 | type: 'CallExpression', 552 | callee: this.wrap(node, { 553 | type: 'MemberExpression', 554 | object: member.value.acceptExpressionVisitor(this), 555 | property: this.wrap(node, { type: 'Identifier', name: 'indexOf' }) 556 | }), 557 | arguments: [node.args[0].acceptExpressionVisitor(this)] 558 | }); 559 | 560 | case NativeTypes.LIST_INSERT: 561 | assert(node.args.length === 2); 562 | return this.wrap(node, { 563 | type: 'CallExpression', 564 | callee: this.wrap(node, { 565 | type: 'MemberExpression', 566 | object: member.value.acceptExpressionVisitor(this), 567 | property: this.wrap(node, { type: 'Identifier', name: 'splice' }) 568 | }), 569 | arguments: [ 570 | node.args[0].acceptExpressionVisitor(this), 571 | this.wrap(node, { type: 'Literal', value: 0 }), 572 | node.args[1].acceptExpressionVisitor(this) 573 | ] 574 | }); 575 | 576 | case NativeTypes.LIST_REMOVE: 577 | assert(node.args.length === 1); 578 | return this.wrap(node, { 579 | type: 'CallExpression', 580 | callee: this.wrap(node, { 581 | type: 'MemberExpression', 582 | object: member.value.acceptExpressionVisitor(this), 583 | property: this.wrap(node, { type: 'Identifier', name: 'splice' }) 584 | }), 585 | arguments: [ 586 | node.args[0].acceptExpressionVisitor(this), 587 | this.wrap(node, { type: 'Literal', value: 1 }) 588 | ] 589 | }); 590 | 591 | default: 592 | assert(false); 593 | } 594 | } 595 | } 596 | 597 | return this.wrap(node, { 598 | type: 'CallExpression', 599 | callee: node.value.acceptExpressionVisitor(this), 600 | arguments: node.args.map(n => n.acceptExpressionVisitor(this)) 601 | }); 602 | } 603 | 604 | visitNewExpression(node: NewExpression): Object { 605 | if (node.type.computedType.innerType === NativeTypes.LIST) { 606 | assert(node.args.length === 0); 607 | return this.wrap(node, { 608 | type: 'ArrayExpression', 609 | elements: [] 610 | }); 611 | } 612 | 613 | return this.wrap(node, { 614 | type: 'NewExpression', 615 | callee: node.type.acceptExpressionVisitor(this), 616 | arguments: node.args.map(n => n.acceptExpressionVisitor(this)) 617 | }); 618 | } 619 | 620 | visitTypeModifierExpression(node: TypeModifierExpression): Object { 621 | assert(false); 622 | return null; 623 | } 624 | 625 | visitTypeParameterExpression(node: TypeParameterExpression): Object { 626 | return node.type.acceptExpressionVisitor(this); 627 | } 628 | } 629 | -------------------------------------------------------------------------------- /src/output.cpp.ts: -------------------------------------------------------------------------------- 1 | class OutputCPP implements StatementVisitor, DeclarationVisitor, ExpressionVisitor { 2 | needMemoryHeader: boolean = false; 3 | needVectorHeader: boolean = false; 4 | needMathHeader: boolean = false; 5 | needStdlibHeader: boolean = false; 6 | needMathRandom: boolean = false; 7 | needAlgorithmHeader: boolean = false; 8 | needListPop: boolean = false; 9 | needListUnshift: boolean = false; 10 | needListShift: boolean = false; 11 | needListIndexOf: boolean = false; 12 | needListInsert: boolean = false; 13 | needListRemove: boolean = false; 14 | returnType: WrappedType = null; 15 | 16 | static generate(node: Module): string { 17 | var output: OutputCPP = new OutputCPP(); 18 | var result: string = cppcodegen.generate(output.visitModule(node), { 19 | indent: ' ', 20 | cpp11: true, 21 | parenthesizeAndInsideOr: true 22 | }).trim(); 23 | 24 | // Cheat for now since I don't feel like writing tons of JSON 25 | var listStuff: string = ''; 26 | if (output.needListPop) { 27 | listStuff += [ 28 | 'template ', 29 | 'T List_pop(std::vector *list) {', 30 | ' T t = std::move(*(list->end() - 1));', 31 | ' list->pop_back();', 32 | ' return std::move(t);', 33 | '}', 34 | ].join('\n') + '\n'; 35 | } 36 | if (output.needListUnshift) { 37 | listStuff += [ 38 | 'template ', 39 | 'void List_unshift(std::vector *list, T t) {', 40 | ' list->insert(list->begin(), std::move(t));', 41 | '}', 42 | ].join('\n') + '\n'; 43 | } 44 | if (output.needListShift) { 45 | listStuff += [ 46 | 'template ', 47 | 'T List_shift(std::vector *list) {', 48 | ' T t = std::move(*list->begin());', 49 | ' list->erase(list->begin());', 50 | ' return std::move(t);', 51 | '}', 52 | ].join('\n') + '\n'; 53 | } 54 | if (output.needListIndexOf) { 55 | listStuff += [ 56 | 'template ', 57 | 'int List_indexOf(std::vector> *list, U *u) {', 58 | ' for (typename std::vector>::iterator i = list->begin(); i != list->end(); i++) {', 59 | ' if (i->get() == u) {', 60 | ' return i - list->begin();', 61 | ' }', 62 | ' }', 63 | ' return -1;', 64 | '}', 65 | 'template ', 66 | 'int List_indexOf(std::vector> *list, U *u) {', 67 | ' for (typename std::vector>::iterator i = list->begin(); i != list->end(); i++) {', 68 | ' if (i->get() == u) {', 69 | ' return i - list->begin();', 70 | ' }', 71 | ' }', 72 | ' return -1;', 73 | '}', 74 | 'template ', 75 | 'int List_indexOf(std::vector *list, U *u) {', 76 | ' for (typename std::vector::iterator i = list->begin(); i != list->end(); i++) {', 77 | ' if (*i == u) {', 78 | ' return i - list->begin();', 79 | ' }', 80 | ' }', 81 | ' return -1;', 82 | '}', 83 | ].join('\n') + '\n'; 84 | } 85 | if (output.needListInsert) { 86 | listStuff += [ 87 | 'template ', 88 | 'void List_insert(std::vector *list, int offset, T t) {', 89 | ' list->insert(list->begin() + offset, std::move(t));', 90 | '}', 91 | ].join('\n') + '\n'; 92 | } 93 | if (output.needListRemove) { 94 | listStuff += [ 95 | 'template ', 96 | 'void List_remove(std::vector *list, int offset) {', 97 | ' list->erase(list->begin() + offset);', 98 | '}', 99 | ].join('\n') + '\n'; 100 | } 101 | return result.replace(/\n(?!#)/, '\n' + listStuff); 102 | } 103 | 104 | defaultForType(type: WrappedType): Object { 105 | switch (type.innerType) { 106 | case SpecialType.INT: 107 | return { 108 | kind: 'IntegerLiteral', 109 | value: 0 110 | }; 111 | 112 | case SpecialType.DOUBLE: 113 | return { 114 | kind: 'DoubleLiteral', 115 | value: 0 116 | }; 117 | 118 | case SpecialType.BOOL: 119 | return { 120 | kind: 'BooleanLiteral', 121 | value: false 122 | }; 123 | } 124 | 125 | return { 126 | kind: 'NullLiteral' 127 | }; 128 | } 129 | 130 | visitType(type: WrappedType): any { 131 | switch (type.innerType) { 132 | case SpecialType.INT: return { kind: 'Identifier', name: 'int' }; 133 | case SpecialType.VOID: return { kind: 'Identifier', name: 'void' }; 134 | case SpecialType.BOOL: return { kind: 'Identifier', name: 'bool' }; 135 | case SpecialType.DOUBLE: return { kind: 'Identifier', name: 'double' }; 136 | } 137 | 138 | assert(type.isObject()); 139 | var objectType: ObjectType = type.asObject(); 140 | var result: Object = { 141 | kind: 'Identifier', 142 | name: objectType.name 143 | }; 144 | 145 | if (objectType === NativeTypes.LIST) { 146 | this.needVectorHeader = true; 147 | assert(type.substitutions.length === 1); 148 | assert(type.substitutions[0].parameter === NativeTypes.LIST_T); 149 | result = { 150 | kind: 'SpecializeTemplate', 151 | template: { 152 | kind: 'MemberType', 153 | inner: { kind: 'Identifier', name: 'std' }, 154 | member: { kind: 'Identifier', name: 'vector' } 155 | }, 156 | parameters: [this.visitType(type.substitutions[0].type)] 157 | }; 158 | } 159 | 160 | if (type.isRawPointer()) { 161 | return { 162 | kind: 'PointerType', 163 | inner: result 164 | }; 165 | } 166 | 167 | if (type.isOwned()) { 168 | this.needMemoryHeader = true; 169 | return { 170 | kind: 'SpecializeTemplate', 171 | template: { 172 | kind: 'MemberType', 173 | inner: { kind: 'Identifier', name: 'std' }, 174 | member: { kind: 'Identifier', name: 'unique_ptr' } 175 | }, 176 | parameters: [result] 177 | }; 178 | } 179 | 180 | if (type.isShared()) { 181 | this.needMemoryHeader = true; 182 | return { 183 | kind: 'SpecializeTemplate', 184 | template: { 185 | kind: 'MemberType', 186 | inner: { kind: 'Identifier', name: 'std' }, 187 | member: { kind: 'Identifier', name: 'shared_ptr' } 188 | }, 189 | parameters: [result] 190 | }; 191 | } 192 | 193 | return result; 194 | } 195 | 196 | forwardDeclareObjectType(node: ObjectDeclaration): Object { 197 | return { 198 | kind: 'ObjectDeclaration', 199 | type: { 200 | kind: 'ObjectType', 201 | keyword: 'struct', 202 | id: this.visitIdentifier(node.id), 203 | bases: [] 204 | } 205 | }; 206 | } 207 | 208 | createVariables(variables: VariableDeclaration[]): Object[] { 209 | return variables.map(n => { 210 | kind: 'Variable', 211 | type: this.visitType(n.type.computedType), 212 | id: this.visitIdentifier(n.id) 213 | }); 214 | } 215 | 216 | needsVirtualDestructor(node: ObjectDeclaration): boolean { 217 | var type: ObjectType = node.symbol.type.asObject(); 218 | return type.baseType === null && type.hasDerivedTypes; 219 | } 220 | 221 | getBaseVariables(node: Expression): VariableDeclaration[] { 222 | if (node instanceof SymbolExpression) { 223 | var base: ObjectDeclaration = (node).symbol.node; 224 | return this.getBaseVariables(base.base).concat(base.block.statements.filter(n => n instanceof VariableDeclaration)); 225 | } 226 | return []; 227 | } 228 | 229 | createFunctionsForObjectType(node: ObjectDeclaration, 230 | ctor: (result: any) => void, 231 | dtor: (result: any) => void, 232 | memberFunction: (node: FunctionDeclaration, result: any) => void) { 233 | var variables: VariableDeclaration[] = node.block.statements.filter(n => n instanceof VariableDeclaration); 234 | var functions: FunctionDeclaration[] = node.block.statements.filter(n => n instanceof FunctionDeclaration); 235 | var baseVariables: VariableDeclaration[] = this.getBaseVariables(node.base).filter(n => n.value === null); 236 | 237 | // Initialize member variables using an initialization list 238 | var initializations: Object[] = variables.map(n => ({ 239 | kind: 'CallExpression', 240 | callee: this.visitIdentifier(n.id), 241 | arguments: [ 242 | n.value !== null ? this.insertImplicitConversion(n.value, n.symbol.type) : 243 | n.symbol.type.isOwned() ? { 244 | kind: 'CallExpression', 245 | callee: { 246 | kind: 'MemberType', 247 | inner: { kind: 'Identifier', name: 'std' }, 248 | member: { kind: 'Identifier', name: 'move' } 249 | }, 250 | arguments: [this.visitIdentifier(n.id)] 251 | } : 252 | this.visitIdentifier(n.id)] 253 | })); 254 | 255 | // Call the inherited constructor 256 | if (node.base !== null) { 257 | initializations.unshift({ 258 | kind: 'CallExpression', 259 | callee: node.base.acceptExpressionVisitor(this), 260 | arguments: baseVariables.map(n => this.visitIdentifier(n.id)) 261 | }); 262 | } 263 | 264 | // Create the constructor 265 | ctor({ 266 | kind: 'FunctionDeclaration', 267 | type: { 268 | kind: 'FunctionType', 269 | arguments: this.createVariables(baseVariables.concat(variables.filter(n => n.value === null))) 270 | }, 271 | id: { 272 | kind: 'MemberType', 273 | inner: this.visitIdentifier(node.id), 274 | member: this.visitIdentifier(node.id) 275 | }, 276 | initializations: initializations, 277 | body: { kind: 'BlockStatement', body: [] } 278 | }); 279 | 280 | // Create the destructor 281 | dtor({ 282 | kind: 'FunctionDeclaration', 283 | type: { kind: 'FunctionType', arguments: [] }, 284 | id: { 285 | kind: 'MemberType', 286 | inner: this.visitIdentifier(node.id), 287 | member: { kind: 'Identifier', name: '~' + node.id.name } 288 | }, 289 | body: { kind: 'BlockStatement', body: [] } 290 | }); 291 | 292 | // Create the member functions 293 | functions.forEach(n => { 294 | var result: any = this.visitFunctionDeclaration(n); 295 | result.id = { 296 | kind: 'MemberType', 297 | inner: this.visitIdentifier(node.id), 298 | member: result.id 299 | }; 300 | memberFunction(n, result); 301 | }); 302 | } 303 | 304 | declareObjectType(node: ObjectDeclaration): Object { 305 | var variables: VariableDeclaration[] = node.block.statements.filter(n => n instanceof VariableDeclaration); 306 | 307 | // Create member variables 308 | var statements: any[] = this.createVariables(variables).map(n => { 309 | kind: 'VariableDeclaration', 310 | qualifiers: [], 311 | variables: [n] 312 | }); 313 | 314 | // Forward-declare the constructor, the destructor, and any member functions 315 | this.createFunctionsForObjectType(node, 316 | ctor => { 317 | ctor.id = ctor.id.member; 318 | ctor.body = ctor.initializations = null; 319 | statements.push(ctor); 320 | }, 321 | dtor => { 322 | if (this.needsVirtualDestructor(node)) { 323 | dtor.id = dtor.id.member; 324 | dtor.qualifiers = [{ kind: 'Identifier', name: 'virtual' }]; 325 | statements.push(dtor); 326 | } 327 | }, 328 | (n, memberFunction) => { 329 | memberFunction.id = memberFunction.id.member; 330 | memberFunction.body = null; 331 | if (n.symbol.isOverridden || n.symbol.isOver()) { 332 | memberFunction.qualifiers = [{ kind: 'Identifier', name: 'virtual' }]; 333 | if (n.block === null) { 334 | memberFunction.body = { kind: 'IntegerLiteral', value: 0 }; 335 | } 336 | } 337 | statements.push(memberFunction); 338 | } 339 | ); 340 | 341 | // Bundle everything in a struct declaration 342 | return { 343 | kind: 'ObjectDeclaration', 344 | type: { 345 | kind: 'ObjectType', 346 | keyword: 'struct', 347 | id: this.visitIdentifier(node.id), 348 | bases: node.base === null ? [] : [node.base.acceptExpressionVisitor(this)], 349 | body: { 350 | kind: 'BlockStatement', 351 | body: statements 352 | } 353 | } 354 | }; 355 | } 356 | 357 | generateFunctionsForObjectType(node: ObjectDeclaration, callback: (n: FunctionDeclaration, o: any) => Object): any[] { 358 | var statements: any[] = []; 359 | 360 | // Implement the constructor, and any member functions 361 | this.createFunctionsForObjectType(node, 362 | ctor => { 363 | statements.push(ctor); 364 | }, 365 | dtor => { 366 | // The destructor is inline (and so is already implemented) 367 | }, 368 | (n, memberFunction) => { 369 | if (n.block !== null) { 370 | statements.push(memberFunction); 371 | } 372 | } 373 | ); 374 | 375 | return statements; 376 | } 377 | 378 | insertImplicitConversion(from: Expression, to: WrappedType): Object { 379 | if (from.computedType.isOwned() && to.isShared()) { 380 | if (from instanceof NewExpression) { 381 | var node: NewExpression = from; 382 | var functionType: FunctionType = node.type.computedType.asObject().constructorType(); 383 | this.needMemoryHeader = true; 384 | return { 385 | kind: 'CallExpression', 386 | callee: { 387 | kind: 'SpecializeTemplate', 388 | template: { 389 | kind: 'MemberType', 390 | inner: { kind: 'Identifier', name: 'std' }, 391 | member: { kind: 'Identifier', name: 'make_shared' } 392 | }, 393 | parameters: [{ kind: 'Identifier', name: to.asObject().name }] 394 | }, 395 | arguments: node.args.map((n, i) => this.insertImplicitConversion(n, functionType.args[i])) 396 | }; 397 | } 398 | return { 399 | kind: 'CallExpression', 400 | callee: this.visitType(to), 401 | arguments: [{ 402 | kind: 'CallExpression', 403 | callee: { 404 | kind: 'MemberExpression', 405 | operator: '.', 406 | object: from.acceptExpressionVisitor(this), 407 | member: { kind: 'Identifier', name: 'release' } 408 | }, 409 | arguments: [] 410 | }] 411 | }; 412 | } 413 | 414 | if ((from.computedType.isOwned() || from.computedType.isShared()) && to.isRawPointer()) { 415 | return { 416 | kind: 'CallExpression', 417 | callee: { 418 | kind: 'MemberExpression', 419 | operator: '.', 420 | object: from.acceptExpressionVisitor(this), 421 | member: { kind: 'Identifier', name: 'get' } 422 | }, 423 | arguments: [] 424 | }; 425 | } 426 | 427 | return from.acceptExpressionVisitor(this); 428 | } 429 | 430 | declareFunction(node: FunctionDeclaration): Object { 431 | return { 432 | kind: 'FunctionDeclaration', 433 | qualifiers: [], 434 | type: { 435 | kind: 'FunctionType', 436 | 'return': this.visitType(node.result.computedType), 437 | arguments: node.args.map(n => ({ 438 | kind: 'Variable', 439 | type: this.visitType(n.type.computedType), 440 | id: this.visitIdentifier(n.id) 441 | })) 442 | }, 443 | id: this.visitIdentifier(node.id), 444 | body: null 445 | }; 446 | } 447 | 448 | visitModule(node: Module): Object { 449 | var objects: ObjectDeclaration[] = node.block.sortedObjectDeclarations(); 450 | var result: any = { 451 | kind: 'Program', 452 | body: flatten([ 453 | objects.map(n => this.forwardDeclareObjectType(n)), 454 | objects.map(n => this.declareObjectType(n)), 455 | node.block.variableDeclarations().map(n => n.acceptStatementVisitor(this)), 456 | node.block.functionDeclarations().map(n => this.declareFunction(n)), 457 | flatten(objects.map(n => this.generateFunctionsForObjectType(n, (n, o) => n.block !== null ? o : null))), 458 | node.block.functionDeclarationsWithBlocks().map(n => n.acceptStatementVisitor(this)), 459 | ]) 460 | }; 461 | 462 | if (this.needMathRandom) { 463 | result.body.unshift({ 464 | kind: 'FunctionDeclaration', 465 | qualifiers: [], 466 | type: { 467 | kind: 'FunctionType', 468 | 'return': { kind: 'Identifier', name: 'double' }, 469 | arguments: [] 470 | }, 471 | id: { kind: 'Identifier', name: 'Math_random' }, 472 | body: { 473 | kind: 'BlockStatement', 474 | body: [{ 475 | kind: 'ReturnStatement', 476 | argument: { 477 | kind: 'BinaryExpression', 478 | operator: '/', 479 | left: { 480 | kind: 'CallExpression', 481 | callee: { kind: 'Identifier', name: 'rand' }, 482 | arguments: [] 483 | }, 484 | right: { 485 | kind: 'CallExpression', 486 | callee: { 487 | kind: 'SpecializeTemplate', 488 | template: { kind: 'Identifier', name: 'static_cast' }, 489 | parameters: [{ kind: 'Identifier', name: 'double' }] 490 | }, 491 | arguments: [{ kind: 'Identifier', name: 'RAND_MAX' }] 492 | } 493 | } 494 | }] 495 | } 496 | }); 497 | } 498 | 499 | // Include headers as needed 500 | if (this.needMemoryHeader) { 501 | result.body.unshift({ 502 | kind: 'IncludeStatement', 503 | text: '' 504 | }); 505 | } 506 | if (this.needMathHeader) { 507 | result.body.unshift({ 508 | kind: 'IncludeStatement', 509 | text: '' 510 | }); 511 | } 512 | if (this.needStdlibHeader) { 513 | result.body.unshift({ 514 | kind: 'IncludeStatement', 515 | text: '' 516 | }); 517 | } 518 | if (this.needVectorHeader) { 519 | result.body.unshift({ 520 | kind: 'IncludeStatement', 521 | text: '' 522 | }); 523 | } 524 | if (this.needAlgorithmHeader) { 525 | result.body.unshift({ 526 | kind: 'IncludeStatement', 527 | text: '' 528 | }); 529 | } 530 | 531 | return result; 532 | } 533 | 534 | visitBlock(node: Block): Object { 535 | return { 536 | kind: 'BlockStatement', 537 | body: node.statements.map(n => n.acceptStatementVisitor(this)) 538 | }; 539 | } 540 | 541 | visitIdentifier(node: Identifier): Object { 542 | return { 543 | kind: 'Identifier', 544 | name: node.name 545 | }; 546 | } 547 | 548 | visitExpressionStatement(node: ExpressionStatement): Object { 549 | return { 550 | kind: 'ExpressionStatement', 551 | expression: node.value.acceptExpressionVisitor(this) 552 | }; 553 | } 554 | 555 | visitIfStatement(node: IfStatement): Object { 556 | var elseBlock: any = node.elseBlock !== null ? this.visitBlock(node.elseBlock) : null; 557 | return { 558 | kind: 'IfStatement', 559 | test: node.test.acceptExpressionVisitor(this), 560 | consequent: this.visitBlock(node.thenBlock), 561 | alternate: elseBlock !== null && elseBlock.body.length === 1 && elseBlock.body[0].kind === 'IfStatement' ? elseBlock.body[0] : elseBlock 562 | }; 563 | } 564 | 565 | visitWhileStatement(node: WhileStatement): Object { 566 | return { 567 | kind: 'WhileStatement', 568 | test: node.test.acceptExpressionVisitor(this), 569 | body: this.visitBlock(node.block) 570 | }; 571 | } 572 | 573 | visitForStatement(node: ForStatement): Object { 574 | return { 575 | kind: 'ForStatement', 576 | init: node.setup !== null ? node.setup.acceptExpressionVisitor(this) : null, 577 | test: node.test !== null ? node.test.acceptExpressionVisitor(this) : null, 578 | update: node.update !== null ? node.update.acceptExpressionVisitor(this) : null, 579 | body: this.visitBlock(node.block) 580 | }; 581 | } 582 | 583 | visitReturnStatement(node: ReturnStatement): Object { 584 | return { 585 | kind: 'ReturnStatement', 586 | argument: node.value !== null ? this.insertImplicitConversion(node.value, this.returnType) : null 587 | }; 588 | } 589 | 590 | visitBreakStatement(node: BreakStatement): Object { 591 | return { 592 | kind: 'BreakStatement' 593 | }; 594 | } 595 | 596 | visitContinueStatement(node: ContinueStatement): Object { 597 | return { 598 | kind: 'ContinueStatement' 599 | }; 600 | } 601 | 602 | visitDeclaration(node: Declaration): Object { 603 | return node.acceptDeclarationVisitor(this); 604 | } 605 | 606 | visitObjectDeclaration(node: ObjectDeclaration): Object { 607 | assert(false); 608 | return null; 609 | } 610 | 611 | visitFunctionDeclaration(node: FunctionDeclaration): Object { 612 | this.returnType = node.symbol.type.asFunction().result; 613 | return { 614 | kind: 'FunctionDeclaration', 615 | qualifiers: [], 616 | type: { 617 | kind: 'FunctionType', 618 | 'return': this.visitType(node.result.computedType), 619 | arguments: node.args.map(n => ({ 620 | kind: 'Variable', 621 | type: this.visitType(n.type.computedType), 622 | id: this.visitIdentifier(n.id) 623 | })) 624 | }, 625 | id: this.visitIdentifier(node.id), 626 | body: node.block !== null ? this.visitBlock(node.block) : null 627 | }; 628 | } 629 | 630 | visitVariableDeclaration(node: VariableDeclaration): Object { 631 | return { 632 | kind: 'VariableDeclaration', 633 | qualifiers: [], 634 | variables: [{ 635 | kind: 'Variable', 636 | type: this.visitType(node.type.computedType), 637 | id: this.visitIdentifier(node.id), 638 | init: node.value !== null ? this.insertImplicitConversion(node.value, node.symbol.type) : this.defaultForType(node.symbol.type) 639 | }] 640 | }; 641 | } 642 | 643 | visitSymbolExpression(node: SymbolExpression): Object { 644 | return { 645 | kind: 'Identifier', 646 | name: node.name 647 | }; 648 | } 649 | 650 | visitMoveExpression(node: MoveExpression): Object { 651 | return { 652 | kind: 'CallExpression', 653 | callee: { 654 | kind: 'MemberType', 655 | inner: { kind: 'Identifier', name: 'std' }, 656 | member: { kind: 'Identifier', name: 'move' } 657 | }, 658 | arguments: [node.value.acceptExpressionVisitor(this)] 659 | }; 660 | } 661 | 662 | visitUnaryExpression(node: UnaryExpression): Object { 663 | return { 664 | kind: 'UnaryExpression', 665 | operator: node.op, 666 | argument: node.value.acceptExpressionVisitor(this) 667 | }; 668 | } 669 | 670 | visitBinaryExpression(node: BinaryExpression): Object { 671 | // Always do pointer comparisons with raw pointers 672 | if (node.op === '==' || node.op === '!=') { 673 | return { 674 | kind: 'BinaryExpression', 675 | operator: node.op, 676 | left: this.insertImplicitConversion(node.left, node.left.computedType.wrapWithout(TypeModifier.OWNED | TypeModifier.SHARED)), 677 | right: this.insertImplicitConversion(node.right, node.right.computedType.wrapWithout(TypeModifier.OWNED | TypeModifier.SHARED)) 678 | }; 679 | } 680 | 681 | return { 682 | kind: node.op === '=' ? 'AssignmentExpression' : 'BinaryExpression', 683 | operator: node.op, 684 | left: node.left.acceptExpressionVisitor(this), 685 | right: node.op === '=' ? this.insertImplicitConversion(node.right, node.left.computedType) : node.right.acceptExpressionVisitor(this) 686 | }; 687 | } 688 | 689 | visitTernaryExpression(node: TernaryExpression): Object { 690 | return { 691 | kind: 'ConditionalExpression', 692 | test: node.value.acceptExpressionVisitor(this), 693 | consequent: this.insertImplicitConversion(node.trueValue, node.computedType), 694 | alternate: this.insertImplicitConversion(node.falseValue, node.computedType) 695 | }; 696 | } 697 | 698 | visitMemberExpression(node: MemberExpression): Object { 699 | if (node.value.computedType.innerType === NativeTypes.MATH) { 700 | switch (node.id.name) { 701 | case 'E': 702 | return { 703 | kind: 'DoubleLiteral', 704 | value: Math.E 705 | }; 706 | 707 | case 'PI': 708 | return { 709 | kind: 'DoubleLiteral', 710 | value: Math.PI 711 | }; 712 | 713 | case 'NAN': 714 | case 'INFINITY': 715 | case 'cos': 716 | case 'sin': 717 | case 'tan': 718 | case 'acos': 719 | case 'asin': 720 | case 'atan': 721 | case 'atan2': 722 | case 'floor': 723 | case 'ceil': 724 | case 'exp': 725 | case 'log': 726 | case 'sqrt': 727 | case 'pow': 728 | this.needMathHeader = true; 729 | return this.visitIdentifier(node.id); 730 | 731 | case 'min': 732 | case 'max': 733 | case 'abs': 734 | this.needMathHeader = true; 735 | return { 736 | kind: 'Identifier', 737 | name: 'f' + node.id.name 738 | }; 739 | 740 | case 'random': 741 | this.needStdlibHeader = true; 742 | this.needMathRandom = true; 743 | return { 744 | kind: 'Identifier', 745 | name: 'Math_random' 746 | }; 747 | 748 | default: 749 | assert(false); 750 | } 751 | } 752 | 753 | else if (node.value.computedType.innerType === NativeTypes.LIST) { 754 | switch (node.symbol) { 755 | 756 | case NativeTypes.LIST_LENGTH: 757 | return { 758 | kind: 'CallExpression', 759 | callee: { 760 | kind: 'SpecializeTemplate', 761 | template: { kind: 'Identifier', name: 'static_cast' }, 762 | parameters: [{ kind: 'Identifier', name: 'int' }] 763 | }, 764 | arguments: [{ 765 | kind: 'CallExpression', 766 | callee: { 767 | kind: 'MemberExpression', 768 | operator: '->', 769 | object: node.value.acceptExpressionVisitor(this), 770 | member: { kind: 'Identifier', name: 'size' } 771 | }, 772 | arguments: [] 773 | }] 774 | }; 775 | } 776 | } 777 | 778 | return { 779 | kind: 'MemberExpression', 780 | operator: node.value.computedType.isPointer() ? '->' : '.', 781 | object: node.value.acceptExpressionVisitor(this), 782 | member: this.visitIdentifier(node.id) 783 | }; 784 | } 785 | 786 | visitIntExpression(node: IntExpression): Object { 787 | return { 788 | kind: 'IntegerLiteral', 789 | value: node.value 790 | }; 791 | } 792 | 793 | visitBoolExpression(node: BoolExpression): Object { 794 | return { 795 | kind: 'BooleanLiteral', 796 | value: node.value 797 | }; 798 | } 799 | 800 | visitDoubleExpression(node: DoubleExpression): Object { 801 | return { 802 | kind: 'DoubleLiteral', 803 | value: node.value 804 | }; 805 | } 806 | 807 | visitNullExpression(node: NullExpression): Object { 808 | return { 809 | kind: 'NullLiteral' 810 | }; 811 | } 812 | 813 | visitThisExpression(node: ThisExpression): Object { 814 | return { 815 | kind: 'ThisExpression' 816 | }; 817 | } 818 | 819 | visitCallExpression(node: CallExpression): Object { 820 | var functionType: FunctionType = node.value.computedType.asFunction(); 821 | var args: Object[] = node.args.map((n, i) => this.insertImplicitConversion(n, functionType.args[i])); 822 | 823 | if (node.value instanceof MemberExpression) { 824 | var member: MemberExpression = node.value; 825 | if (member.value.computedType.innerType === NativeTypes.LIST) { 826 | switch (member.symbol) { 827 | 828 | case NativeTypes.LIST_GET: 829 | assert(args.length === 1); 830 | var result: Object = { 831 | kind: 'BinaryExpression', 832 | operator: '[]', 833 | left: { 834 | kind: 'UnaryExpression', 835 | operator: '*', 836 | argument: member.value.acceptExpressionVisitor(this) 837 | }, 838 | right: args[0] 839 | }; 840 | assert(member.value.computedType.substitutions.length === 1); 841 | if (!member.value.computedType.substitutions[0].type.isRawPointer()) { 842 | return { 843 | kind: 'CallExpression', 844 | callee: { 845 | kind: 'MemberExpression', 846 | operator: '.', 847 | object: result, 848 | member: { kind: 'Identifier', name: 'get' } 849 | }, 850 | arguments: [] 851 | }; 852 | } 853 | return result; 854 | 855 | case NativeTypes.LIST_SET: 856 | assert(args.length === 2); 857 | return { 858 | kind: 'AssignmentExpression', 859 | operator: '=', 860 | left: { 861 | kind: 'BinaryExpression', 862 | operator: '[]', 863 | left: { 864 | kind: 'UnaryExpression', 865 | operator: '*', 866 | argument: member.value.acceptExpressionVisitor(this) 867 | }, 868 | right: args[0] 869 | }, 870 | right: args[1] 871 | }; 872 | 873 | case NativeTypes.LIST_PUSH: 874 | assert(args.length === 1); 875 | return { 876 | kind: 'CallExpression', 877 | callee: { 878 | kind: 'MemberExpression', 879 | operator: '->', 880 | object: member.value.acceptExpressionVisitor(this), 881 | member: { kind: 'Identifier', name: 'push_back' } 882 | }, 883 | arguments: args 884 | }; 885 | 886 | case NativeTypes.LIST_POP: 887 | case NativeTypes.LIST_UNSHIFT: 888 | case NativeTypes.LIST_SHIFT: 889 | case NativeTypes.LIST_INDEX_OF: 890 | case NativeTypes.LIST_INSERT: 891 | case NativeTypes.LIST_REMOVE: 892 | switch (member.symbol) { 893 | case NativeTypes.LIST_POP: this.needListPop = true; break; 894 | case NativeTypes.LIST_UNSHIFT: this.needListUnshift = true; break; 895 | case NativeTypes.LIST_SHIFT: this.needListShift = true; break; 896 | case NativeTypes.LIST_INDEX_OF: this.needListIndexOf = this.needAlgorithmHeader = true; break; 897 | case NativeTypes.LIST_INSERT: this.needListInsert = true; break; 898 | case NativeTypes.LIST_REMOVE: this.needListRemove = true; break; 899 | default: assert(false); 900 | } 901 | return { 902 | kind: 'CallExpression', 903 | callee: { kind: 'Identifier', name: 'List_' + member.symbol.name }, 904 | arguments: [this.insertImplicitConversion(member.value, NativeTypes.LIST.wrap(0))].concat(args) 905 | }; 906 | 907 | default: 908 | assert(false); 909 | } 910 | } 911 | } 912 | 913 | return { 914 | kind: 'CallExpression', 915 | callee: node.value.acceptExpressionVisitor(this), 916 | arguments: args 917 | }; 918 | } 919 | 920 | visitNewExpression(node: NewExpression): Object { 921 | var functionType: FunctionType = node.type.computedType.asObject().constructorType(); 922 | this.needMemoryHeader = true; 923 | return { 924 | kind: 'CallExpression', 925 | callee: { 926 | kind: 'SpecializeTemplate', 927 | template: { 928 | kind: 'MemberType', 929 | inner: { kind: 'Identifier', name: 'std' }, 930 | member: { kind: 'Identifier', name: 'unique_ptr' } 931 | }, 932 | parameters: [this.visitType(node.type.computedType).inner] 933 | }, 934 | arguments: [{ 935 | kind: 'NewExpression', 936 | callee: this.visitType(node.type.computedType).inner, 937 | arguments: node.args.map((n, i) => this.insertImplicitConversion(n, functionType.args[i])) 938 | }] 939 | }; 940 | } 941 | 942 | visitTypeModifierExpression(node: TypeModifierExpression): Object { 943 | assert(false); 944 | return null; 945 | } 946 | 947 | visitTypeParameterExpression(node: TypeParameterExpression): Object { 948 | return node.type.acceptExpressionVisitor(this); 949 | } 950 | } 951 | --------------------------------------------------------------------------------