├── .gitmodules ├── cx.ini ├── .gitattributes ├── .gitignore ├── cx.kdev4 ├── README.md ├── test ├── fail_compilation │ ├── afl_1.cx │ ├── afl_4.cx │ ├── afl_3.cx │ ├── afl_2.cx │ ├── named_arg.cx │ ├── named_arg_struct2.cx │ ├── named_arg_struct.cx │ ├── either.cx │ ├── named_arg_class.cx │ ├── abstract.cx │ ├── contravariance.cx │ └── covariance.cx └── runnable │ ├── eqtest.cx │ ├── typeof.cx │ ├── floattest.cx │ ├── malloctest.cx │ ├── withtest.cx │ ├── module_level.cx │ ├── ptrtest.cx │ ├── stringtest.cx │ ├── funcptrtest.cx │ ├── order.cx │ ├── nested.cx │ ├── here.cx │ ├── abstract.cx │ ├── hex.cx │ ├── named_arg.cx │ ├── nested_import.cx │ ├── tuples.cx │ ├── nestedfntest.cx │ ├── obj_is.cx │ ├── enumtest.cx │ ├── symbol_identifier.cx │ ├── classfntest.cx │ ├── mathtest.cx │ ├── vectortest.cx │ ├── covariance_contravariance.cx │ ├── ufcs.cx │ ├── ack.cx │ ├── sha256.cx │ ├── ptrarraytest.cx │ ├── iftest.cx │ ├── whiletest.cx │ ├── casts.cx │ ├── vartest.cx │ ├── templates.cx │ ├── structtest.cx │ ├── shortcircuittest.cx │ ├── forlooptest.cx │ ├── eithertest.cx │ ├── classtest.cx │ ├── arraytest.cx │ └── macrotest.cx ├── src ├── c │ ├── sys │ │ ├── stat.cx │ │ └── time.cx │ ├── stdlib.cx │ ├── libgen.cx │ └── pthread.cx ├── std │ ├── time.cx │ ├── http.cx │ ├── math │ │ ├── vector.cx │ │ └── matrix.cx │ ├── math.cx │ ├── json │ │ └── macro.cx │ ├── file.cx │ ├── thread.cx │ ├── string.cx │ ├── json.cx │ └── sha256.cx ├── cx │ ├── hash.cx │ ├── unittest_.cx │ ├── macros │ │ ├── the.cx │ │ ├── hash.cx │ │ ├── quasiquoting.cx │ │ ├── once.cx │ │ ├── assert.cx │ │ └── listcomprehension.cx │ ├── with_.cx │ ├── enums.cx │ ├── parser.cx │ ├── vectors.cx │ ├── parser_base.cx │ ├── types.cx │ └── tuples.cx ├── helpers.cx ├── runtime.c └── backend │ └── base.cx ├── demos ├── some_grass_or_we.png ├── sparkline │ ├── package.json │ └── src │ │ ├── sparkline_cli.cx │ │ └── sparkline.cx ├── sha256sum.cx └── glfw.cx ├── Makefile ├── unittest.sh ├── ack.c ├── TODO.md ├── doc ├── index.md ├── generations.md └── compilerbase.md ├── release.sh ├── hellogl.cx ├── rebuild.sh ├── ack.cx ├── runtests.sh └── sdltest.cx /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cx.ini: -------------------------------------------------------------------------------- 1 | -syspackage compiler:src 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cx linguist-language=D 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | build 3 | *.bc 4 | *.ll 5 | *.png 6 | *.o 7 | -------------------------------------------------------------------------------- /cx.kdev4: -------------------------------------------------------------------------------- 1 | [Project] 2 | Name=cx 3 | Manager=KDevCustomMakeManager 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cx is now called [Neat](https://github.com/neat-lang/neat). 2 | -------------------------------------------------------------------------------- /test/fail_compilation/afl_1.cx: -------------------------------------------------------------------------------- 1 | module afl_1; 2 | 3 | int ack(int m,) {} 4 | -------------------------------------------------------------------------------- /test/fail_compilation/afl_4.cx: -------------------------------------------------------------------------------- 1 | module afl_4; 2 | 3 | void main() { string s = " 4 | -------------------------------------------------------------------------------- /test/fail_compilation/afl_3.cx: -------------------------------------------------------------------------------- 1 | module afl_3; 2 | 3 | void main() { 4 | &x; 5 | } 6 | -------------------------------------------------------------------------------- /src/c/sys/stat.cx: -------------------------------------------------------------------------------- 1 | module c.sys.stat; 2 | 3 | extern(C) int mkdir(char* pathname, int mode); 4 | -------------------------------------------------------------------------------- /demos/some_grass_or_we.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FeepingCreature/Cx/HEAD/demos/some_grass_or_we.png -------------------------------------------------------------------------------- /src/c/stdlib.cx: -------------------------------------------------------------------------------- 1 | module c.stdlib; 2 | 3 | extern(C) char* realpath(char* path, char* resolved_path); 4 | -------------------------------------------------------------------------------- /test/fail_compilation/afl_2.cx: -------------------------------------------------------------------------------- 1 | module afl_2; 2 | 3 | void main() { 4 | for (bla < 10; i = 1) { } 5 | } 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Helper makefile so that kdevelop has something to run. 2 | .PHONY: build/cx 3 | build/cx: 4 | ./bootstrap.sh 5 | -------------------------------------------------------------------------------- /src/c/libgen.cx: -------------------------------------------------------------------------------- 1 | module c.libgen; 2 | 3 | extern(C) char* dirname(char* path); 4 | extern(C) char* basename(char* path); 5 | -------------------------------------------------------------------------------- /test/fail_compilation/named_arg.cx: -------------------------------------------------------------------------------- 1 | module named_arg; 2 | 3 | void foo(int i) { } 4 | 5 | void main() { 6 | foo(j=1); 7 | } 8 | -------------------------------------------------------------------------------- /demos/sparkline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "binary": "sparkline", 3 | "source": "src", 4 | "main": "src/sparkline_cli.cx" 5 | } 6 | -------------------------------------------------------------------------------- /test/runnable/eqtest.cx: -------------------------------------------------------------------------------- 1 | module eqtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | char ch = "A"[0]; 7 | assert(ch == "A"[0]); 8 | } 9 | -------------------------------------------------------------------------------- /test/fail_compilation/named_arg_struct2.cx: -------------------------------------------------------------------------------- 1 | module named_arg_struct2; 2 | 3 | struct S { 4 | int i; 5 | } 6 | 7 | void main() { 8 | auto s = S(j=1); 9 | } 10 | -------------------------------------------------------------------------------- /test/fail_compilation/named_arg_struct.cx: -------------------------------------------------------------------------------- 1 | module named_arg_struct; 2 | 3 | struct S { 4 | void foo(int i) { } 5 | } 6 | 7 | void main() { 8 | S().foo(j=1); 9 | } 10 | -------------------------------------------------------------------------------- /src/c/sys/time.cx: -------------------------------------------------------------------------------- 1 | module c.sys.time; 2 | 3 | struct timeval 4 | { 5 | long time_t; 6 | long suseconds_t; 7 | } 8 | 9 | extern(C) int gettimeofday(timeval* tv, void* tz); 10 | -------------------------------------------------------------------------------- /test/runnable/typeof.cx: -------------------------------------------------------------------------------- 1 | module typeof; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | int foo() { assert(false); } 7 | 8 | typeof(foo()) bla = 5; 9 | } 10 | -------------------------------------------------------------------------------- /test/fail_compilation/either.cx: -------------------------------------------------------------------------------- 1 | module either; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | (string | int) e = "foo"; 7 | assert(e.case(string s: 1) == 1); 8 | } 9 | -------------------------------------------------------------------------------- /test/fail_compilation/named_arg_class.cx: -------------------------------------------------------------------------------- 1 | module named_arg_class; 2 | 3 | class C { 4 | this() { } 5 | void foo(int i) { } 6 | } 7 | 8 | void main() { 9 | (new C).foo(j=1); 10 | } 11 | -------------------------------------------------------------------------------- /test/runnable/floattest.cx: -------------------------------------------------------------------------------- 1 | module floattest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | float f = 2; 7 | assert(f + 2 == 4); 8 | assert(5 / f == 2.5); 9 | } 10 | -------------------------------------------------------------------------------- /test/runnable/malloctest.cx: -------------------------------------------------------------------------------- 1 | module malloctest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | extern(C) void* malloc(size_t); 6 | 7 | void main() { 8 | int* ip = malloc(4); 9 | *ip = 3; 10 | assert(*ip == 3); 11 | } 12 | -------------------------------------------------------------------------------- /test/runnable/withtest.cx: -------------------------------------------------------------------------------- 1 | module withtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | struct S 6 | { 7 | int i; 8 | } 9 | 10 | void main() { 11 | with (S(5)) 12 | { 13 | assert(i == 5); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/runnable/module_level.cx: -------------------------------------------------------------------------------- 1 | module module_level; 2 | 3 | macro import cx.macros.assert; 4 | 5 | int foo() { return 1; } 6 | 7 | void main() { 8 | int foo() { return 2; } 9 | assert(foo() == 2); 10 | assert(.foo() == 1); 11 | } 12 | -------------------------------------------------------------------------------- /test/runnable/ptrtest.cx: -------------------------------------------------------------------------------- 1 | module ptrtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | int i = 0; 7 | setPtr(&i, 5); 8 | assert(i == 5); 9 | } 10 | 11 | void setPtr(int* ip, int value) { 12 | *ip = value; 13 | } 14 | -------------------------------------------------------------------------------- /test/runnable/stringtest.cx: -------------------------------------------------------------------------------- 1 | module stringtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | extern(C) void print(char[]); 6 | 7 | void main() { 8 | string str = "Hello World"; 9 | print(str); 10 | assert("\n" == " 11 | "); 12 | } 13 | -------------------------------------------------------------------------------- /test/runnable/funcptrtest.cx: -------------------------------------------------------------------------------- 1 | module funcptrtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | assert(callFp(&returnFive) == 5); 7 | } 8 | 9 | int callFp(int function() fn) { return fn(); } 10 | 11 | int returnFive() { return 5; } 12 | -------------------------------------------------------------------------------- /test/runnable/order.cx: -------------------------------------------------------------------------------- 1 | module order; 2 | 3 | void main() { foo(); auto a = new A; S s; } 4 | 5 | void foo() { bar(); } 6 | 7 | void bar() { } 8 | 9 | class A : B { this() { } } 10 | 11 | class B { } 12 | 13 | struct S { T t; } 14 | 15 | struct T { } 16 | -------------------------------------------------------------------------------- /test/runnable/nested.cx: -------------------------------------------------------------------------------- 1 | module nested; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | { 7 | int i; 8 | } 9 | mut int i; 10 | void foo() { i = 2; } 11 | void bar() { foo(); } 12 | bar(); 13 | assert(i == 2); 14 | } 15 | -------------------------------------------------------------------------------- /test/fail_compilation/abstract.cx: -------------------------------------------------------------------------------- 1 | module abstract; 2 | 3 | extern(C) void assert(int); 4 | 5 | abstract class A 6 | { 7 | abstract void foo() { assert(false); } 8 | } 9 | 10 | class B : A 11 | { 12 | this() { } 13 | } 14 | 15 | void main() { A a = new B; a.foo(); } 16 | -------------------------------------------------------------------------------- /test/runnable/here.cx: -------------------------------------------------------------------------------- 1 | module here; 2 | 3 | macro import cx.macros.assert; 4 | 5 | import package(compiler).cx.parser_base; 6 | 7 | void main() { 8 | auto loc = __HERE__; 9 | 10 | assert(loc.filename == "test/runnable/here.cx" && loc.row == 7 && loc.column == 15); 11 | } 12 | -------------------------------------------------------------------------------- /test/fail_compilation/contravariance.cx: -------------------------------------------------------------------------------- 1 | module contravariance; 2 | 3 | macro import cx.macros.assert; 4 | 5 | class A { 6 | this() { } 7 | void set(A a) { } 8 | } 9 | 10 | class B : A { 11 | this() { } 12 | override void set(B b) { } 13 | } 14 | 15 | void main() { } 16 | -------------------------------------------------------------------------------- /test/runnable/abstract.cx: -------------------------------------------------------------------------------- 1 | module abstract; 2 | 3 | extern(C) void assert(int); 4 | 5 | abstract class A 6 | { 7 | abstract void foo() { assert(false); } 8 | } 9 | 10 | class B : A 11 | { 12 | this() { } 13 | 14 | override void foo() { } 15 | } 16 | 17 | void main() { A a = new B; a.foo(); } 18 | -------------------------------------------------------------------------------- /test/runnable/hex.cx: -------------------------------------------------------------------------------- 1 | module hex; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | assert(0x1234 == 4660); 7 | assert(0x10000000 == 268435456); 8 | assert(-1 == 0xffffFFFF); 9 | assert(0xabcdef == 0xABCDEF); 10 | assert(0xabcdef == 11259375); 11 | assert(-0xf == -15); 12 | } 13 | -------------------------------------------------------------------------------- /test/runnable/named_arg.cx: -------------------------------------------------------------------------------- 1 | module named_arg; 2 | 3 | void foo(int i) { } 4 | 5 | class C { 6 | this() { } 7 | void foo(int i) { } 8 | } 9 | 10 | struct S { 11 | int i; 12 | void foo(int i) { } 13 | } 14 | 15 | void main() { 16 | foo(i=1); 17 | (new C).foo(i=1); 18 | S(i=1).foo(i=1); 19 | } 20 | -------------------------------------------------------------------------------- /test/runnable/nested_import.cx: -------------------------------------------------------------------------------- 1 | module nested_import; 2 | 3 | extern(C) void assert(bool); 4 | 5 | int print(string s) { assert(s == ""); return 5; } 6 | 7 | void foo() { 8 | import helpers : print; 9 | print("Hello World"); 10 | } 11 | 12 | void main() { 13 | foo; 14 | assert(print("") == 5); 15 | } 16 | -------------------------------------------------------------------------------- /test/runnable/tuples.cx: -------------------------------------------------------------------------------- 1 | module tuples; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | (int, float) tuple = (2, 3.0); 7 | assert(tuple[0] == 2 && tuple[1] == 3.0); 8 | (int i, float) fun() { return (2, 3.0); } 9 | assert(fun.i == 2); 10 | // TODO 11 | // assert(tuple == fun); 12 | } 13 | -------------------------------------------------------------------------------- /test/runnable/nestedfntest.cx: -------------------------------------------------------------------------------- 1 | module nestedfntest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void callme(void delegate() dg) { 6 | dg(); 7 | } 8 | 9 | void main() { 10 | mut int i = 1; 11 | void incr() { i += 1; } 12 | incr(); 13 | assert(i == 2); 14 | callme(&incr); 15 | assert(i == 3); 16 | } 17 | -------------------------------------------------------------------------------- /test/runnable/obj_is.cx: -------------------------------------------------------------------------------- 1 | module obj_is; 2 | 3 | macro import cx.macros.assert; 4 | 5 | class Object 6 | { 7 | this() { } 8 | } 9 | 10 | void main() { 11 | auto a = new Object; 12 | auto b = new Object; 13 | assert(a is a); 14 | assert(!(a is b)); 15 | assert(a !is b); 16 | assert(!(a !is a)); 17 | } 18 | -------------------------------------------------------------------------------- /test/fail_compilation/covariance.cx: -------------------------------------------------------------------------------- 1 | module covariance; 2 | 3 | macro import cx.macros.assert; 4 | 5 | class A { 6 | this() { } 7 | A get() { return new A; } 8 | } 9 | 10 | class C { 11 | this() { } 12 | } 13 | 14 | class B : A { 15 | this() { } 16 | override C get() { return new C; } 17 | } 18 | 19 | void main() { } 20 | -------------------------------------------------------------------------------- /demos/sha256sum.cx: -------------------------------------------------------------------------------- 1 | module sha256sum; 2 | 3 | import std.file; 4 | import std.sha256; 5 | import std.string; 6 | 7 | void main(string[] args) { 8 | for (int i <- 1 .. args.length) { 9 | auto sha256 = new Sha256; 10 | sha256.update(args[i].readFile); 11 | print(sha256.finalize.toHexString ~ " " ~ args[i]); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/runnable/enumtest.cx: -------------------------------------------------------------------------------- 1 | module enumtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | enum Enum 6 | { 7 | first, 8 | second, 9 | third, 10 | } 11 | 12 | void main() { 13 | Enum a = Enum.first; 14 | assert(a == 0); 15 | assert(a == Enum.first); 16 | assert(a != Enum.second); 17 | assert(Enum.third == 2); 18 | } 19 | -------------------------------------------------------------------------------- /demos/sparkline/src/sparkline_cli.cx: -------------------------------------------------------------------------------- 1 | module sparkline_cli; 2 | 3 | macro import cx.macros.listcomprehension; 4 | 5 | import sparkline; 6 | import std.string; 7 | 8 | extern(C) void print(string); 9 | 10 | void main(mut string[] args) { 11 | string[] ticks = default_ticks(); 12 | int[] data = [atoi(x) for x in args[1 .. $]]; 13 | 14 | print(sparkline(ticks, data)); 15 | } 16 | -------------------------------------------------------------------------------- /test/runnable/symbol_identifier.cx: -------------------------------------------------------------------------------- 1 | module symbol_identifier; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | (:integer, int | :floating, float) i = (:integer, 2); 7 | bool isInteger = i.case((:integer, int i): true, (:floating, float f): false); 8 | int value = i.case((:integer, int i): i, (:floating, float f): cast(int) f); 9 | assert(value == 2); 10 | } 11 | -------------------------------------------------------------------------------- /test/runnable/classfntest.cx: -------------------------------------------------------------------------------- 1 | module classfntest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void callme(void delegate() dg) { 6 | dg(); 7 | } 8 | 9 | class A { 10 | int i; 11 | this() { } 12 | void incr() { i += 1; } 13 | } 14 | 15 | void main() { 16 | auto a = new A; 17 | a.incr; 18 | assert(a.i == 1); 19 | callme(&a.incr); 20 | assert(a.i == 2); 21 | } 22 | -------------------------------------------------------------------------------- /unittest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euxo pipefail 3 | PACKAGES="-Pbuild:build:src -Psrc:src:compiler" 4 | FLAGS="-lm -lpthread" 5 | mkdir -p build 6 | ( 7 | echo "module unittest;" 8 | find src/std/ -name \*.cx |sed -e 's,/,.,g' -e 's/^src.\(.*\).cx$/import \1;/' 9 | ) > build/unittest.cx 10 | cx -unittest -no-main $PACKAGES $FLAGS build/unittest.cx -o build/unittest 11 | build/unittest 12 | -------------------------------------------------------------------------------- /test/runnable/mathtest.cx: -------------------------------------------------------------------------------- 1 | module mathtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | int i = 1; 7 | assert(i++ == 1); 8 | assert(++i == 3); 9 | assert(i-- == 3); 10 | assert(--i == 1); 11 | assert(-i == -1); 12 | assert(2 + 3 * 4 == 14); 13 | assert(15 % 4 == 3); 14 | // assert(2 << 2 == 8); 15 | assert(5 & 3 == 1); 16 | assert(5 | 3 == 7); 17 | } 18 | -------------------------------------------------------------------------------- /test/runnable/vectortest.cx: -------------------------------------------------------------------------------- 1 | module vectortest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | alias vec3f = Vector(float, 3); 6 | 7 | void main() { 8 | auto v1 = vec3f(1, 2, 4); 9 | assert(v1.x == 1 && v1.y == 2 && v1.z == 4); 10 | assert(v1.zyx == vec3f(4, 2, 1)); 11 | assert(v1 * 2 == vec3f(2, 4, 8)); 12 | assert(v1 / 2 == vec3f(0.5, 1, 2)); 13 | assert(v1 + v1 == vec3f(2, 4, 8)); 14 | } 15 | -------------------------------------------------------------------------------- /src/std/time.cx: -------------------------------------------------------------------------------- 1 | module std.time; 2 | 3 | import c.sys.time; 4 | 5 | struct Time 6 | { 7 | timeval val; 8 | float delta(Time other) { 9 | return cast(int) (val.time_t - other.val.time_t) 10 | + (cast(int) val.suseconds_t - cast(int) other.val.suseconds_t) / 1000000.0; 11 | } 12 | } 13 | 14 | Time time() { 15 | Time res; 16 | gettimeofday(&res.val, null); 17 | return res; 18 | } 19 | -------------------------------------------------------------------------------- /test/runnable/covariance_contravariance.cx: -------------------------------------------------------------------------------- 1 | module covariance_contravariance; 2 | 3 | macro import cx.macros.assert; 4 | 5 | class A { 6 | this() { } 7 | A get() { return new A; } 8 | void set(B b) { } 9 | } 10 | 11 | class B : A { 12 | this() { } 13 | override B get() { return new B; } 14 | override void set(A a) { } 15 | } 16 | 17 | void main() { 18 | assert((new B()).get().instanceOf(A)); 19 | } 20 | -------------------------------------------------------------------------------- /ack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int ack(int m, int n) { 5 | if (m == 0) return n + 1; 6 | if (n == 0) return ack(m - 1, 1); 7 | return ack(m - 1, ack(m, n - 1)); 8 | } 9 | 10 | int main(int argc, char **argv) { 11 | int m = atoi(argv[1]), n = atoi(argv[2]); 12 | for (int i = 0; i < 10; i++) { 13 | printf("ack(%i, %i) = %i\n", m, n, ack(m, n)); 14 | } 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - fold ClassMethodPtr into LateSymbol 2 | - endLifetime should not take or need to take a Reference! 3 | - copyInto should not exist; instead there should be a copy() op that can then be chained into Assignment. 4 | - Sure? Needs more thinky. Maybe just `beginLifetime`? 5 | - `Loc`/`ReLoc`/`FileRegistry` should not ought to exist. It doesn't save anything. 6 | - Loc should just have the file/line/column info. The parser can track it easy. 7 | -------------------------------------------------------------------------------- /test/runnable/ufcs.cx: -------------------------------------------------------------------------------- 1 | module ufcs; 2 | 3 | macro import cx.macros.assert; 4 | 5 | import helpers; 6 | 7 | struct S 8 | { 9 | int i; 10 | int bar() { return i; } 11 | } 12 | 13 | int getI(S s) { return s.i; } 14 | int bar(S s) { return 6; } 15 | 16 | void main() { 17 | S s = S(5); 18 | assert(s.getI == 5); 19 | assert(s.getI() == 5); 20 | assert(s.bar() == 5); 21 | assert(s.bar == 5); 22 | 23 | "Success!".print; 24 | } 25 | -------------------------------------------------------------------------------- /test/runnable/ack.cx: -------------------------------------------------------------------------------- 1 | module ack; 2 | 3 | macro import cx.macros.assert; 4 | import helpers; 5 | 6 | void main() { int result = ack(3, 8); print("ack(3, 8) = " ~ itoa(result)); assert(result == 2045); } 7 | 8 | int ack(int m, int n) { 9 | // TODO , mut int n) 10 | mut int n = n; 11 | if (m == 0) { n = n + 1; return n; } 12 | if (n == 0) { int m1 = m - 1; return ack(m1, 1); } 13 | int m1 = m - 1; int n1 = n - 1; 14 | return ack(m1, ack(m, n1)); 15 | } 16 | -------------------------------------------------------------------------------- /test/runnable/sha256.cx: -------------------------------------------------------------------------------- 1 | module sha256; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.listcomprehension; 5 | 6 | import std.sha256; 7 | import std.string; 8 | 9 | void main() { 10 | auto digest = new Sha256; 11 | // TODO cast(ubyte[]) 12 | digest.update([cast(ubyte) a for a in "Hello World"]); 13 | auto result = digest.finalize.toHexString; 14 | assert(result == "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"); 15 | } 16 | -------------------------------------------------------------------------------- /test/runnable/ptrarraytest.cx: -------------------------------------------------------------------------------- 1 | module ptrarraytest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | extern(C) void* malloc(size_t); 6 | extern(C) void free(void*); 7 | 8 | void main() { 9 | int* ip = malloc(40); 10 | mut int i = 0; 11 | while (i < 10) { 12 | ip[i] = i; 13 | i += 1; 14 | } 15 | i = 0; 16 | mut int sum = 0; 17 | while (i < 10) { 18 | sum = sum + ip[i]; 19 | i += 1; 20 | } 21 | assert(sum == 45); 22 | free(ip); 23 | } 24 | -------------------------------------------------------------------------------- /src/std/http.cx: -------------------------------------------------------------------------------- 1 | module std.http; 2 | 3 | import std.file; 4 | import std.string; 5 | 6 | extern(C) char* tmpnam(char*); 7 | extern(C) void cxruntime_system(string command); 8 | 9 | // Downloads a file. 10 | // The way this is done right now is really hacky. But meh. 11 | ubyte[] download(string url) { 12 | // yes this is shit but it's just a placeholder. 13 | auto tmp = tmpnam(null).cToString; 14 | 15 | cxruntime_system("curl -s " ~ url ~ " -o " ~ tmp); 16 | return readFile(tmp); 17 | } 18 | -------------------------------------------------------------------------------- /test/runnable/iftest.cx: -------------------------------------------------------------------------------- 1 | module iftest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | mut int i = 0; 7 | if (1 == 1) i = 1; 8 | assert(i == 1); 9 | if (1 == 1) i = 1; else i = 2; 10 | assert(i == 1); 11 | if (1 == 2) i = 1; else i = 2; 12 | assert(i == 2); 13 | if (!(1 == 1)) assert(false); 14 | if (1 != 1) assert(false); 15 | int k = 3; 16 | if (int k = 5) assert(k == 5); 17 | assert(k == 3); 18 | if (int zero = 0) assert(false); 19 | } 20 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Notes on Cx Compiler Development 2 | 3 | Cx tries to do the simple, straightforward thing in every case. 4 | Nonetheless, during development, several lessons were learnt, 5 | leading to late changes in functionality. 6 | 7 | The goal of these documents is to document those cases 8 | and give insight into their motivations. 9 | 10 | ## Index 11 | 12 | - [The CompilerBase Class](./compilerbase.md) 13 | - [\_\_GENERATION\_\_ and bootstrapping](./generations.md) 14 | - TODO Multi-Stage Feature Transitions 15 | -------------------------------------------------------------------------------- /test/runnable/whiletest.cx: -------------------------------------------------------------------------------- 1 | module whiletest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | assert(whiletest(5) == 32); 7 | 8 | mut int i; 9 | mut int k; 10 | while (k < 10) { 11 | k += 1; 12 | if (k < 3) continue; 13 | i += 1; 14 | if (k > 5) break; 15 | } 16 | assert(i == 4); 17 | } 18 | 19 | int whiletest(int k) { 20 | mut int k = k; // TODO (mut int k) 21 | mut int i = 1; 22 | while (k > 0) { 23 | i = i * 2; 24 | k = k - 1; 25 | } 26 | return i; 27 | } 28 | -------------------------------------------------------------------------------- /test/runnable/casts.cx: -------------------------------------------------------------------------------- 1 | module casts; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | { 7 | long l = 1; 8 | int i = cast(int) l; 9 | assert(i == 1); 10 | } 11 | { 12 | float f = 2.5; 13 | int i = cast(int) f; 14 | assert(i == 2); 15 | } 16 | { 17 | int i = 5; 18 | int *ip = &i; 19 | void *vp = cast(void*) ip; 20 | int* ip2 = cast(int*) vp; 21 | assert(*ip2 == 5); 22 | *cast(int*) vp += 1; 23 | assert(*ip2 == 6); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/runnable/vartest.cx: -------------------------------------------------------------------------------- 1 | module vartest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | int a; 7 | assert(a == 0); 8 | int b = 3; 9 | assert(b == 3); 10 | int c, d; 11 | assert(c == 0 && d == 0); 12 | int e = 4, f; 13 | assert(e == 4 && f == 0); 14 | int g, h = 5; 15 | assert(g == 0 && h == 5); 16 | int i = 6, j = 7; 17 | assert(i == 6 && j == 7); 18 | int k = 5, l = k; 19 | assert(k == 5 && l == 5); 20 | auto m = 0.5, n = 7; 21 | assert(m / 2 == 0.25 && n / 2 == 3); 22 | // is this a good idea??? 23 | auto = 2, = 3; 24 | } 25 | -------------------------------------------------------------------------------- /src/std/math/vector.cx: -------------------------------------------------------------------------------- 1 | module std.math.vector; 2 | 3 | macro import cx.macros.assert; 4 | 5 | import std.math; 6 | 7 | alias vec2f = Vector(float, 2); 8 | alias vec3f = Vector(float, 3); 9 | alias vec4f = Vector(float, 4); 10 | 11 | vec3f cross(vec3f a, vec3f b) { 12 | return a.yzx * b.zxy - a.zxy * b.yzx; 13 | } 14 | 15 | vec3f normal(vec3f v) { 16 | return v / v.length; 17 | } 18 | 19 | unittest { 20 | assert(vec3f(2, 0, 0).normal == vec3f(1, 0, 0)); 21 | } 22 | 23 | float length(vec3f v) { 24 | vec3f v2 = v * v; 25 | return sqrt(v2.x + v2.y + v2.z); 26 | } 27 | 28 | float angle(vec3f a, vec3f b) { 29 | auto prod = a * b; 30 | return acos(prod.x + prod.y + prod.z); 31 | } 32 | -------------------------------------------------------------------------------- /test/runnable/templates.cx: -------------------------------------------------------------------------------- 1 | module templates; 2 | 3 | macro import cx.macros.assert; 4 | 5 | import helpers; 6 | 7 | template LinkedList(T) { 8 | class LinkedList { 9 | this() { } 10 | LinkedList next; 11 | T value; 12 | } 13 | } 14 | 15 | void linkedList() { 16 | auto ll = new LinkedList!int(); 17 | ll.next = new LinkedList!int(); 18 | ll.value = 5; 19 | ll.next.value = 6; 20 | } 21 | 22 | template max(T) { 23 | T max(T a, T b) { 24 | if (a > b) return a; 25 | return b; 26 | } 27 | } 28 | 29 | void maxTypes() { 30 | assert(max!int(2, 3) == 3); 31 | assert(max!float(3.5, 2) == 3.5); 32 | } 33 | 34 | void main() { 35 | linkedList; 36 | } 37 | -------------------------------------------------------------------------------- /demos/sparkline/src/sparkline.cx: -------------------------------------------------------------------------------- 1 | module sparkline; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.listcomprehension; 5 | 6 | string[] default_ticks() { 7 | return ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]; 8 | } 9 | 10 | string sparkline(string[] ticks, int[] data) { 11 | assert(ticks.length > 0); 12 | assert(data.length > 0); 13 | 14 | int n = (cast(int) ticks.length) - 1; 15 | int min = [min point for point in data]; 16 | int max = [max point for point in data]; 17 | 18 | if (min == max) { 19 | // All points are the same. 20 | return [join "" ticks[n / 2] for _ in data]; 21 | } else { 22 | string spark(int point) { 23 | return ticks[n * (point - min) / (max - min)]; 24 | } 25 | return [join "" spark(n) for n in data]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euxo pipefail 3 | if [ -e release ] 4 | then 5 | echo "Release folder already exists!" 1>&2 6 | exit 1 7 | fi 8 | TARGET=release/cx 9 | mkdir -p $TARGET 10 | rm -rf .obj 11 | ./bootstrap.sh 12 | build/cx -backend=c -Pcompiler:src -dump-intermediates build/intermediates.txt src/main.cx -c 13 | mkdir $TARGET/intermediate 14 | cp -R src/ $TARGET 15 | cp $(cat build/intermediates.txt) $TARGET/intermediate/ 16 | cat > $TARGET/build.sh < $TARGET/cx.ini < 0; i -= 1) { 14 | k -= 1; 15 | } 16 | assert(k == 0); 17 | } 18 | // test: loop variable reinitialization 19 | for (mut int i = 0; i < 10; i += 1) { 20 | mut int l; 21 | assert(l == 0); 22 | l = 1; 23 | } 24 | { 25 | mut int i; 26 | for (mut int k = 0; k < 10; k += 1) { 27 | if (k < 3) continue; 28 | i += 1; 29 | if (k > 5) break; 30 | } 31 | assert(i == 4); 32 | } 33 | { 34 | mut int i; 35 | for (int j <- 0..10) { 36 | i += 1; 37 | } 38 | assert(i == 10); 39 | } 40 | { 41 | mut int sum; 42 | for (int j <- [2, 3, 4]) { 43 | sum += j; 44 | } 45 | assert(sum == 9); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/runnable/eithertest.cx: -------------------------------------------------------------------------------- 1 | module eithertest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | extern(C) void print(string); 6 | extern(C) string cxruntime_itoa(int); 7 | 8 | string itoa(int i) { return cxruntime_itoa(i); } 9 | 10 | void main() { 11 | (int | float) either1 = 5; 12 | string s = either1.case(int i: "int " ~ itoa(i), float: "float"); 13 | // should be one ref 14 | // TODO 15 | // assert(*cast(size_t*) s.base == 1); 16 | assert(s == "int 5"); 17 | 18 | float f = 2; 19 | (int | float) either2 = f; 20 | assert(either2.case(int i: "int", float: "float") == "float"); 21 | 22 | mut (int, int | int) either3 = (2, 2); 23 | either3 = 5; 24 | 25 | int test() { 26 | (int, int) remainder = either3.case((int a, int b): (a, b), int i: return i); 27 | assert(false); 28 | } 29 | assert(test == 5); 30 | 31 | mut int count; 32 | (:a | :b) countCalls() { count += 1; return :a; } 33 | countCalls.case { (:a): {} (:b): {} } 34 | assert(count == 1); 35 | 36 | ((:a | :b), (:c | :d)) test = (:a, :c); 37 | 38 | print("Success."); 39 | } 40 | -------------------------------------------------------------------------------- /src/cx/hash.cx: -------------------------------------------------------------------------------- 1 | module cx.hash; 2 | 3 | struct PolyHashState { 4 | long add; 5 | long mult; 6 | } 7 | 8 | // see runtime.c 9 | extern(C) void print(string); 10 | extern(C) PolyHashState* poly_init(); 11 | extern(C) void poly_add_string(PolyHashState*, string); 12 | extern(C) void poly_add_long(PolyHashState*, long); 13 | extern(C) void poly_apply_hash(PolyHashState*, PolyHashState*); 14 | extern(C) string poly_hex_value(PolyHashState*); 15 | extern(C) PolyHashState poly_hash_string(string); 16 | 17 | // Polynomial hash for composability 18 | final class Hash 19 | { 20 | PolyHashState* state; 21 | this() 22 | { 23 | state = poly_init; 24 | } 25 | void adds(string s) 26 | { 27 | poly_add_string(state, s); 28 | } 29 | void addl(long l) 30 | { 31 | poly_add_long(state, l); 32 | } 33 | void apply(long add, long mult) 34 | { 35 | auto hash = PolyHashState(add, mult); 36 | poly_apply_hash(state, &hash); 37 | } 38 | void applyHash(Hash other) 39 | { 40 | poly_apply_hash(state, other.state); 41 | } 42 | string text() 43 | { 44 | return poly_hex_value(state); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/runnable/classtest.cx: -------------------------------------------------------------------------------- 1 | module classtest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | extern(C) void print(string); 6 | 7 | void main() 8 | { 9 | Class1 class1 = new Class1; 10 | assert(class1.getValue() == 5); 11 | Class2 class2 = new Class2(6); 12 | assert(class2.getValue() == 6); 13 | Class1 class2_as_1 = class2; // TODO new Class2(6) 14 | assert(class2_as_1.getValue() == 6); 15 | assert(!!class2_as_1.instanceOf(Class1)); 16 | assert(!!class2_as_1.instanceOf(Class2)); 17 | if (class2_as_1.instanceOf(Class3)) print("something is very wrong."); 18 | assert(!class2_as_1.instanceOf(Class3)); 19 | } 20 | 21 | class Class1 22 | { 23 | this() { } 24 | int value() { return 5; } 25 | int getValue() { return this.value(); } 26 | } 27 | 28 | class Class2 : Class1 29 | { 30 | int a; 31 | this(this.a) { } 32 | override int value() { return this.a; } 33 | } 34 | 35 | class Class3 : Class2 36 | { 37 | Class3 parent; 38 | this(this.a) { } 39 | override int value() { return super.value + 4; } 40 | } 41 | 42 | class Class4a { Class4b partner; } 43 | class Class4b { Class4a partner; } 44 | 45 | unittest 46 | { 47 | assert((new Class3(5)).value == 9); 48 | } 49 | -------------------------------------------------------------------------------- /src/c/pthread.cx: -------------------------------------------------------------------------------- 1 | module c.pthread; 2 | 3 | struct pthread_mutex_t 4 | { 5 | // __SIZEOF_PTHREAD_MUTEX_T is like 40 max 6 | long a; long b; long c; long d; long e; 7 | } 8 | 9 | extern(C) int pthread_mutex_init(pthread_mutex_t* mutex, void* attr); 10 | extern(C) int pthread_mutex_destroy(pthread_mutex_t* mutex); 11 | extern(C) int pthread_mutex_lock(pthread_mutex_t* mutex); 12 | extern(C) int pthread_mutex_unlock(pthread_mutex_t* mutex); 13 | 14 | struct pthread_cond_t 15 | { 16 | // __SIZEOF_PTHREAD_MUTEX_T is like 48? 17 | long a; long b; long c; long d; long e; long f; 18 | } 19 | 20 | extern(C) int pthread_cond_init(pthread_cond_t*, void* attr); 21 | extern(C) int pthread_cond_destroy(pthread_cond_t*); 22 | extern(C) int pthread_cond_wait(pthread_cond_t*, pthread_mutex_t*); 23 | extern(C) int pthread_cond_broadcast(pthread_cond_t*); 24 | extern(C) int pthread_cond_signal(pthread_cond_t*); 25 | 26 | struct pthread_t 27 | { 28 | // __SIZEOF_PTHREAD_T is 8, I think? 29 | long a; 30 | } 31 | 32 | // TODO extern(C) with definition 33 | void call_thread_dg(void* arg) { 34 | auto dg = *(cast(void delegate()*) arg); 35 | 36 | dg(); 37 | } 38 | 39 | extern(C) int pthread_create(pthread_t* thread, void* attr, void function(void*) start_routine, void* arg); 40 | -------------------------------------------------------------------------------- /src/std/math.cx: -------------------------------------------------------------------------------- 1 | module std.math; 2 | 3 | macro import cx.macros.assert; 4 | 5 | alias pi = 3.1415926538; 6 | alias PI = pi; 7 | alias π = pi; 8 | 9 | extern(C) float logf(float); 10 | extern(C) float fabsf(float); 11 | extern(C) float expf(float); 12 | extern(C) float sinf(float); 13 | extern(C) float cosf(float); 14 | extern(C) float acosf(float); 15 | extern(C) float tanf(float); 16 | // extern(C) float atan2f(float y, float x); 17 | extern(C) float sqrtf(float); 18 | extern(C) float powf(float, float); 19 | 20 | float sin(float f) { return sinf(f); } 21 | float cos(float f) { return cosf(f); } 22 | float acos(float f) { return acosf(f); } 23 | float tan(float f) { return tanf(f); } 24 | // float atan2(float y, float x) { return atan2f(y, x); } 25 | // see http://dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization/ thanks SO 26 | float atan2(float y, float x) { 27 | float coeff_1 = 3.1415926537/4; 28 | float coeff_2 = 3*coeff_1; 29 | float abs_y = fabsf(y)+0.000001; 30 | mut float angle; 31 | if (x >= 0) { 32 | float r = (x - abs_y) / (x + abs_y); 33 | angle = coeff_1 - coeff_1 * r; 34 | } else { 35 | float r = (x + abs_y) / (abs_y - x); 36 | angle = coeff_2 - coeff_1 * r; 37 | } 38 | if (y < 0) return -angle; 39 | else return angle; 40 | } 41 | float sqrt(float f) { return sqrtf(f); } 42 | float max(float a, float b) { if (a > b) return a; return b; } 43 | 44 | unittest { 45 | assert(max(2, 3) == 3); 46 | assert(max(-4, -5) == -4); 47 | } 48 | 49 | float min(float a, float b) { if (a < b) return a; return b; } 50 | 51 | unittest { 52 | assert(min(2, 3) == 2); 53 | assert(min(-4, -5) == -5); 54 | } 55 | -------------------------------------------------------------------------------- /test/runnable/arraytest.cx: -------------------------------------------------------------------------------- 1 | module arraytest; 2 | 3 | macro import cx.macros.assert; 4 | 5 | void main() { 6 | mut int[] arr; 7 | assert(arr.length == 0); 8 | // arr = [2, 3]; 9 | arr = new int[](0) ~ 2 ~ 3; 10 | assert(arr.length == 2); 11 | assert(arr[1] == 3); 12 | arr = arr ~ 4; // [2, 3, 4] 13 | assert(arr.length == 3); 14 | assert(arr[2] == 4); 15 | arr = arr ~ (new int[](0) ~ 5); // [2, 3, 4, 5] 16 | assert(arr.length == 4); 17 | assert(arr[3] == 5); 18 | assert(arr == [2, 3, 4, 5]); 19 | assert(arr[$ - 1] == 5); 20 | assert(arr[0 .. $ - 1] == [2, 3, 4]); 21 | assert(arr[1 .. 3] == [3, 4]); 22 | assert(arr.ptr[1 .. 2] == arr[1 .. 2]); 23 | assert("Hello World"["Hello ".length .. "Hello World".length] == "World"); 24 | int[] arr = []; 25 | { 26 | mut int count; 27 | int[] test() { count += 1; return [1, 2]; } 28 | assert(test()[$ - 1] == 2); 29 | assert(count == 1); 30 | } 31 | doublingTestArrElem(); 32 | doublingTestArrArr(); 33 | appendLoopTest(); 34 | } 35 | 36 | void doublingTestArrElem() { 37 | mut int[] a = [1, 2, 3, 4]; 38 | a ~= 5; 39 | int[] a1 = a ~ 6; 40 | int[] a2 = a ~ 7; 41 | assert(a1 == [1, 2, 3, 4, 5, 6]); 42 | assert(a2 == [1, 2, 3, 4, 5, 7]); 43 | assert(a1.ptr is a.ptr); 44 | assert(a2.ptr !is a.ptr); 45 | } 46 | 47 | void doublingTestArrArr() { 48 | mut int[] a = [1, 2, 3, 4]; 49 | a ~= [5]; 50 | int[] a1 = a ~ [6]; 51 | int[] a2 = a ~ [7]; 52 | assert(a1 == [1, 2, 3, 4, 5, 6]); 53 | assert(a2 == [1, 2, 3, 4, 5, 7]); 54 | assert(a1.ptr is a.ptr); 55 | assert(a2.ptr !is a.ptr); 56 | } 57 | 58 | void appendLoopTest() { 59 | mut int[] ia; 60 | for (int i <- 0 .. 100) ia ~= i; 61 | for (int i <- 0 .. 100) assert(ia[i] == i); 62 | } 63 | -------------------------------------------------------------------------------- /doc/generations.md: -------------------------------------------------------------------------------- 1 | # \_\_GENERATION\_\_ and bootstrapping 2 | 3 | Any self-hosting static-typed compiled language with macros in the source tree faces a problem. 4 | 5 | Macros need access to the compiler source code in order to be API compatible 6 | with the compiler they're loaded into. But the compiler source changes 7 | in the course of development. So we're looking at at least two build steps: 8 | 9 | - old compiler builds stage1 compiler with new source but old API 10 | - stage1 compiler builds stage2 compiler with new source and new API. 11 | 12 | Cx has several parts that make this process more manageable. 13 | 14 | ## Packages 15 | 16 | First, every module exists inside a package. So cx.base can exist both in 17 | package "compiler" and package "stage1", allowing macros to reference the 18 | specific version for the API of the compiler currently running. 19 | 20 | The package of the running compiler is called "compiler". 21 | 22 | However, that alone would not be enough. Because the mangling of symbols 23 | involves packages, and the mangling of symbols must stay stable for the 24 | same source file during a build (because otherwise dynamic casts will break), 25 | we **cannot** for instance build the new compiler as package "stage1" and then 26 | rename it to "compiler" to build stage2. 27 | 28 | ## \_\_GENERATION\_\_ 29 | 30 | There is a global variable, `__GENERATION__`, that counts up by one every time 31 | the compiler builds itself (with `rebuild.sh`). When we use the package 32 | "compiler", the actual mangling is "compiler\_\_GENERATION\_\_". As such, the rebuild 33 | script can use a commandline flag (`-print-generation`) to discover the current 34 | "compiler" mangling, then just set the new source code to be built in package 35 | "compiler$(\_\_GENERATION\_\_ + 1)". Which will be the correct mangling when 36 | that source code gets defined as package "compiler" to build stage2. 37 | -------------------------------------------------------------------------------- /src/cx/unittest_.cx: -------------------------------------------------------------------------------- 1 | module cx.unittest_; 2 | 3 | import cx.base; 4 | import cx.function_; 5 | import cx.parser_base; 6 | import cx.types; 7 | import helpers; 8 | 9 | ASTUnitTest parseUnitTest(Parser parser, LexicalContext lexicalContext) 10 | { 11 | if (!parser.accept("unittest")) 12 | return null; 13 | auto loc = parser.loc; 14 | ASTStatement body_ = lexicalContext.compiler.parseStatement(parser, lexicalContext); 15 | return new ASTUnitTest(loc, body_, lexicalContext.macroState); 16 | } 17 | 18 | class ASTUnitTest 19 | { 20 | Loc loc; 21 | 22 | ASTStatement body_; 23 | 24 | MacroState macroState; 25 | 26 | this(this.loc, this.body_, this.macroState) { } 27 | 28 | FunctionDeclaration compile(Context context) 29 | { 30 | auto unittestFun = new UnittestFunction(this.loc, this.body_, this.macroState); 31 | 32 | unittestFun.parent = context.namespace; 33 | return unittestFun; 34 | } 35 | } 36 | 37 | class UnittestFunction : Function 38 | { 39 | this(this.loc, this.statement, this.macroState) 40 | { 41 | this.name = ""; 42 | this.ret = new Void; 43 | this.params = []; 44 | this.hasThisArg = false; 45 | } 46 | 47 | override string mangle() 48 | { 49 | return "unittest_" ~ loc.filename.cleanup ~ "_" ~ ltoa(loc.row); 50 | } 51 | 52 | // FIXME isn't this kinda sus? 53 | override CompiledFunction mkCompiledFunction( 54 | Function fun, Statement compiledStatement, FunctionScope stackframe, Statement[] argAssignments) 55 | { 56 | return new CompiledUnittestFunction(fun, compiledStatement, stackframe, argAssignments); 57 | } 58 | } 59 | 60 | string cleanup(string filename) { 61 | import std.string : replace; 62 | 63 | return filename.replace("/", "_").replace(".", "_"); 64 | } 65 | 66 | class CompiledUnittestFunction : CompiledFunction 67 | { 68 | } 69 | -------------------------------------------------------------------------------- /rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # kdevelop build mode 3 | if [ ! -z "${BUILD+x}" ] 4 | then 5 | FAST=1 6 | # no log spam 7 | set -euo pipefail 8 | else 9 | set -euxo pipefail 10 | fi 11 | FLAGS="-O" 12 | #FLAGS="" 13 | 14 | if [ \! -z ${FAST+x} ] 15 | then 16 | FLAGS="" 17 | fi 18 | 19 | function checksum { 20 | # approximate hash: outright remove all 16-byte constants 21 | # I couldn't find another way to handle compiler_hash_{add,mult} 22 | # remove the detritus at the start of the assembly line 23 | REMOVE_BYTES='s/^ *[0-9a-f]*:\t\([0-9a-f]\{2\} \)* *\t\?//' 24 | objdump -S $1 2>/dev/null |grep -v file\ format |\ 25 | sed -e "$REMOVE_BYTES" |\ 26 | sed -e 's/[0-9a-f]\{16\}//' |\ 27 | md5sum 28 | } 29 | 30 | if [ ! -d build/src ] 31 | then 32 | cp -R src build/src 33 | fi 34 | 35 | # compiler now always looks inside the build dir 36 | cp src/runtime.c build/src/ 37 | 38 | rm build/cx.ini || true 39 | 40 | I=1 41 | NEXT=compiler$(($(build/cx -print-generation) + 1)) 42 | build/cx $FLAGS -next-generation -P$NEXT:src -Pcompiler:build/src src/main.cx -o build/cx_test$I 43 | 44 | if [ \! -z ${FAST+x} ] 45 | then 46 | mv build/cx_test$I build/cx 47 | # store compiler source next to compiler 48 | rm -rf build/src 49 | cp -R src build/ 50 | exit 51 | fi 52 | 53 | cp cx.ini build/ 54 | 55 | SUM=$(checksum build/cx_test$I) 56 | SUMNEXT="" 57 | while true 58 | do 59 | K=$((I+1)) 60 | build/cx_test$I $FLAGS -Pcompiler:src src/main.cx -o build/cx_test$K 61 | SUMNEXT=$(checksum build/cx_test$K) 62 | if [ "$SUM" == "$SUMNEXT" ]; then break; fi 63 | SUM="$SUMNEXT" 64 | if [ "${K+x}" == "${STAGE+x}" ] 65 | then 66 | echo "Stage $STAGE reached, aborting" 67 | exit 1 68 | fi 69 | I=$K 70 | done 71 | mv build/cx_test$I build/cx 72 | rm build/cx_test* 73 | # store compiler source next to compiler 74 | rm -rf build/src 75 | cp -R src build/ 76 | -------------------------------------------------------------------------------- /test/runnable/macrotest.cx: -------------------------------------------------------------------------------- 1 | module macrotest; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.the; 5 | macro import cx.macros.listcomprehension; 6 | macro import cx.macros.once; 7 | 8 | extern(C) void print(char[]); 9 | 10 | int square(int) { return (the int) * the int; } 11 | 12 | class A { 13 | this() { } 14 | } 15 | 16 | void listcotest() { 17 | int[] arr = [2, 3, 4]; 18 | assert([a * 2 for a in arr] == [4, 6, 8]); 19 | mut int i; 20 | void foo() { i += 1; } 21 | [foo() for x in arr]; 22 | assert(i == 3); 23 | 24 | assert([any a > 2 for a in arr]); 25 | assert(![any a > 4 for a in arr]); 26 | 27 | // equivalent 28 | assert([first true for a in arr where a > 2 else false]); 29 | assert(![first true for a in arr where a > 4 else false]); 30 | assert([first i for i, a in arr where a > 3 else -1] == 2); 31 | 32 | auto list = [[1, 2], [3, 4], [5, 6]]; 33 | assert([first a[1] for a in list where a[0] > 1 else -1] == 4); 34 | 35 | assert([all a > 1 for a in arr]); 36 | assert(![all a > 2 for a in arr]); 37 | 38 | assert([count a in arr where a > 2] == 2); 39 | assert([sum a for a in arr where a > 2] == 7); 40 | 41 | assert([min x for x in [2, 3, 4, 5] where x > 3] == 4); 42 | assert([min x for x in [2, 3] where x > 3 base 0] == 0); 43 | 44 | assert([max x for x in [2, 3, 4, 5] where x < 4] == 3); 45 | assert([max x for x in [4, 5] where x < 4 base 0] == 0); 46 | } 47 | 48 | class Object { 49 | int i; 50 | 51 | this(this.i) { } 52 | } 53 | 54 | void oncetest() { 55 | Object make(int i) { return once new Object(i); } 56 | Object o1 = make(1), o2 = make(2); 57 | assert(o1.i == 1 && o1 is o2); 58 | } 59 | 60 | void main(string[] args) { 61 | print("macrotest"); 62 | A a = new A; 63 | assert(a); // this would not have worked before, because objects are not bools 64 | listcotest(); 65 | oncetest(); 66 | print("- success"); 67 | assert(square(2) == 4); 68 | } 69 | -------------------------------------------------------------------------------- /doc/compilerbase.md: -------------------------------------------------------------------------------- 1 | # The CompilerBase Class 2 | 3 | ## Introduction 4 | 5 | In order to avoid circular compiler dependencies, `cx.base` contains a 6 | `CompilerBase` class that is implemented in `main` and passed through 7 | to all compiler calls. 8 | 9 | ## Rationale 10 | 11 | Normally, the compiler classes would be divided into cleanly separated modules. 12 | However, compilers are annoyingly interconnected. When parsing, most more 13 | involved parsed constructs will require looping back into statement/expression 14 | parsing. Similarly, AST and runtime classes are usually expressed in terms of 15 | other classes, which again introduces cycles. For example, the array feature requires 16 | generating an array-append function that loops over source elements, which pulls 17 | in functions, scopes, loops, etc. 18 | 19 | To resolve this, the compiler defines `CompilerBase` as a generic interface to the 20 | rest of the compiler. This class allows parsing source, creating AST trees, or 21 | creating IR trees. This works because usually when we're creating an IR or AST 22 | object, we don't particularly care about it for its members or methods, but just use 23 | it to represent a certain behavioral meaning in the program tree. So we don't care 24 | that an astIndexAccess call creates an ASTIndexAccess class, so much as an ASTSymbol 25 | class that happens to represent an index access. 26 | 27 | ## Self-Hosting 28 | 29 | In the context of macros called inside the compiler, the existence of `CompilerBase` 30 | leads to a snag. 31 | 32 | Since the macro will be pulling in the original compiler's version of `CompilerBase`, 33 | we cannot use new `Compiler` features immediately. Instead, we need to create a 34 | commit with the extended `CompilerBase`, then add that commit to the bootstrap script 35 | and use it in a following commit. 36 | 37 | In order to change behavior, a more complicated dance is required. Each line is one commit: 38 | 39 | - Introduce a new function `2` with the new behavior 40 | - Use the new function in all code, switch the old function to the new semantics 41 | - Use the old function again. 42 | -------------------------------------------------------------------------------- /ack.cx: -------------------------------------------------------------------------------- 1 | module ack; 2 | 3 | import backend.proxy; 4 | 5 | extern(C) void print(char[]); 6 | extern(C) void assert(int); 7 | 8 | /*int ack(int m, int n) { 9 | if (m == 0) return n + 1; 10 | if (n == 0) return ack(m - 1, 1); 11 | return ack(m - 1, ack(m, n - 1)); 12 | }*/ 13 | 14 | void main(string[] args) { 15 | print("-----"); 16 | Backend backend = new Backend(); 17 | BackendModule mod = backend.createModule(); 18 | Platform platform = new Platform; 19 | void* intType = platform.intType(); 20 | 21 | void*[] ackArgs = new void*[](2); 22 | ackArgs[0] = intType; ackArgs[1] = intType; 23 | 24 | BackendFunction ack = mod.define("ack", intType, ackArgs); 25 | int m = ack.arg(0); 26 | int n = ack.arg(1); 27 | int zero = ack.intLiteral(0); 28 | int one = ack.intLiteral(1); 29 | 30 | int if1_test_reg = ack.binop("==", m, zero); 31 | TestBranchRecord if1_test_jumprecord = ack.testBranch(if1_test_reg); 32 | 33 | if1_test_jumprecord.resolveThen(ack.blockIndex()); 34 | int add = ack.binop("+", n, one); 35 | ack.ret(add); 36 | 37 | if1_test_jumprecord.resolveElse(ack.blockIndex()); 38 | int if2_test_reg = ack.binop("==", n, zero); 39 | TestBranchRecord if2_test_jumprecord = ack.testBranch(if2_test_reg); 40 | 41 | if2_test_jumprecord.resolveThen(ack.blockIndex()); 42 | int sub = ack.binop("-", m, one); 43 | int[] recArgs = new int[](2); 44 | recArgs[0] = sub; recArgs[1] = one; 45 | int ackrec = ack.call(intType, "ack", recArgs); 46 | ack.ret(ackrec); 47 | 48 | if2_test_jumprecord.resolveElse(ack.blockIndex()); 49 | int n1 = ack.binop("-", n, one); 50 | int[] rec1Args = new int[](2); 51 | rec1Args[0] = m; rec1Args[1] = n1; 52 | int ackrec1 = ack.call(intType, "ack", rec1Args); 53 | int m1 = ack.binop("-", m, one); 54 | int[] rec2Args = new int[](2); 55 | rec2Args[0] = m1; rec2Args[1] = ackrec1; 56 | int ackrec2 = ack.call(intType, "ack", rec2Args); 57 | ack.ret(ackrec2); 58 | 59 | mod.dump(); 60 | 61 | int marg = 3; 62 | int narg = 8; 63 | void*[] args = new void*[](2); 64 | args[0] = &marg; 65 | args[1] = &narg; 66 | int ret; 67 | mod.call(&ret, "ack", args); 68 | assert(ret == 2045); 69 | } 70 | -------------------------------------------------------------------------------- /src/cx/macros/the.cx: -------------------------------------------------------------------------------- 1 | module cx.macros.the; 2 | 3 | import package(compiler).cx.base; 4 | import package(compiler).cx.expr; 5 | import package(compiler).cx.function_; 6 | import package(compiler).cx.parser; 7 | import package(compiler).cx.parser_base; 8 | import package(compiler).cx.statements; 9 | import package(compiler).cx.struct_; 10 | import package(compiler).cx.stuff; 11 | import package(compiler).cx.types; 12 | import package(compiler).helpers; 13 | 14 | class ASTTheValue : ASTSymbol 15 | { 16 | ASTSymbol type; 17 | 18 | Loc loc; 19 | 20 | this(ASTSymbol type, Loc loc) { this.type = type; this.loc = loc; } 21 | 22 | override Expression compile(Context context) 23 | { 24 | auto type = beType(this.loc, this.type.compile(context)); 25 | mut auto namespace = context.namespace; 26 | while (namespace) { 27 | auto varDecl = namespace.instanceOf(VariableDeclaration); 28 | if (varDecl) { 29 | if (varDecl.variable.name == "") { 30 | // FIXME rename to accessDecl2 31 | auto member = varDecl.accessDecl2(context.compiler); 32 | 33 | if (member.type.same(type)) 34 | return member; 35 | } 36 | namespace = namespace.parent; 37 | } 38 | // skip marker 39 | else namespace = null; 40 | } 41 | this.loc.assert2s2(false, "Type not found: ", type.repr()); 42 | } 43 | 44 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTTheValue'!"); assert(false); } 45 | } 46 | 47 | class TheValue : Macro 48 | { 49 | this() { } 50 | override void apply(MacroArgs args) { 51 | auto args = args.instanceOf(ParseExpressionBaseArgs); 52 | if (args) { 53 | args.symbol = this.parse(args.parser, args.lexicalContext); 54 | } 55 | } 56 | 57 | ASTSymbol parse(Parser parser, LexicalContext context) 58 | { 59 | if (!acceptIdentifier(parser, "the")) 60 | { 61 | return null; 62 | } 63 | ASTSymbol type = parseType(parser, context); 64 | return new ASTTheValue(type, parser.loc()); 65 | } 66 | } 67 | 68 | void addTheValueMacro(MacroState macroState) 69 | { 70 | macroState.addMacro(new TheValue); 71 | } 72 | 73 | macro(addTheValueMacro); 74 | -------------------------------------------------------------------------------- /src/std/json/macro.cx: -------------------------------------------------------------------------------- 1 | module std.json.macro; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.listcomprehension; 5 | 6 | import package(compiler).cx.base; 7 | import package(compiler).cx.parser_base; 8 | import package(compiler).cx.parser; 9 | 10 | import std.json; 11 | 12 | class JsonSyntax : Macro 13 | { 14 | this() { } 15 | override void apply(MacroArgs args) { 16 | auto args = args.instanceOf(ParseExpressionBaseArgs); 17 | if (args) { 18 | args.symbol = this.parse(args.parser, args.lexicalContext); 19 | } 20 | } 21 | 22 | ASTSymbol parse(Parser parser, LexicalContext lexicalContext) 23 | { 24 | auto loc = parser.loc(); 25 | parser.begin; 26 | if (!acceptIdentifier(parser, "JSONValue") || !parser.accept("(")) 27 | { 28 | parser.revert; 29 | return null; 30 | } 31 | auto json = jsonParseImpl(parser); 32 | parser.expect(")"); 33 | parser.commit; 34 | return quoteJson(lexicalContext.compiler, json); 35 | } 36 | } 37 | 38 | ASTSymbol quoteJson(CompilerBase compiler, JSONValue v) { 39 | ASTSymbol jv(ASTSymbol sym) { 40 | return compiler.astCall( 41 | compiler.astIdentifier("JSONValue", __HERE__), 42 | [sym], 43 | __HERE__); 44 | } 45 | v.value.case { 46 | (:false): return compiler.astIdentifier("false", __HERE__).jv; 47 | (:true): return compiler.astIdentifier("true", __HERE__).jv; 48 | int i: return compiler.astIntLiteral(i, __HERE__).jv; 49 | string s: return compiler.astStringLiteral(s, __HERE__).jv; 50 | JSONValue[] arr: return compiler.astArrayLiteral( 51 | [quoteJson(compiler, a) for a in arr], 52 | __HERE__).jv; 53 | (string key, JSONValue value)[] obj: 54 | return compiler.astArrayLiteral( 55 | [compiler.astTupleLiteral([ 56 | compiler.astStringLiteral(a.key, __HERE__), 57 | quoteJson(compiler, a.value)], __HERE__) 58 | for a in obj], __HERE__).jv; 59 | } 60 | } 61 | 62 | void jsonMacro(MacroState macroState) 63 | { 64 | macroState.addMacro(new JsonSyntax); 65 | } 66 | 67 | macro(jsonMacro); 68 | 69 | unittest 70 | { 71 | auto value = JSONValue({"Hello": "World", "five": 5, "array": [6]}); 72 | assert(value.str == "{\"Hello\": \"World\", \"five\": 5, \"array\": [6]}"); 73 | } 74 | -------------------------------------------------------------------------------- /src/cx/with_.cx: -------------------------------------------------------------------------------- 1 | module cx.with_; 2 | 3 | import cx.base; 4 | import cx.parser; 5 | import cx.parser_base; 6 | import helpers; 7 | 8 | class WithNamespace : DeclarationHolder 9 | { 10 | // This can't work! 11 | // When you do with() { void nested() { accessWithMember; } }, then the 12 | // lookup will try to access a nonlocal register. 13 | // We need to localize the reference in the stackframe, 14 | // by declaring it as an anonymous variable. 15 | Reference temporary; 16 | 17 | this(this.parent, this.temporary) { this.isContextScope = parent.isContextScope; } 18 | 19 | override Reference accessDecl(CompilerBase compiler) 20 | { 21 | return temporary; 22 | } 23 | 24 | override Symbol lookup(string name, Context context, Loc loc, Expression frame) 25 | { 26 | auto member = context.compiler.accessMemberWithLifetime(context, temporary, name, loc, true); 27 | if (member) return member; 28 | if (this.parent) return this.parent.lookup(name, context, loc, frame); 29 | return null; 30 | } 31 | } 32 | 33 | class ASTWithStatement : ASTStatement 34 | { 35 | ASTSymbol expr; 36 | 37 | ASTStatement stmt; 38 | 39 | Loc loc; 40 | 41 | this(this.expr, this.stmt, this.loc) { } 42 | 43 | override StatementCompileResult compile(Context context) 44 | { 45 | Expression expr = beExpression3(context, this.expr.compile(context), this.loc); 46 | 47 | Statement do_(Reference temp) 48 | { 49 | auto subContext = context.withNamespace(new WithNamespace(context.namespace, temp)); 50 | auto subStmtPair = this.stmt.compile(subContext); 51 | return subStmtPair.statement; 52 | } 53 | auto stmt = context.compiler.consumeTemporaryStmt(context, expr, &do_); 54 | 55 | return StatementCompileResult(stmt, context); 56 | } 57 | 58 | override ASTSymbol quote(Quoter quoter) { 59 | print("TODO: quote(ASTWithStatement)"); 60 | assert(false); 61 | } 62 | } 63 | 64 | ASTStatement parseWithStatement(Parser parser, LexicalContext lexicalContext) 65 | { 66 | auto loc = parser.loc; 67 | if (!acceptIdentifier(parser, "with")) 68 | { 69 | return null; 70 | } 71 | parser.expect("("); 72 | auto subExpr = lexicalContext.compiler.parseExpression(parser, lexicalContext); 73 | parser.assert_(!!subExpr, "with expression expected"); 74 | parser.expect(")"); 75 | auto body_ = lexicalContext.compiler.parseStatement(parser, lexicalContext); 76 | 77 | return new ASTWithStatement(subExpr, body_, loc); 78 | } 79 | -------------------------------------------------------------------------------- /src/cx/macros/hash.cx: -------------------------------------------------------------------------------- 1 | module cx.macros.hash; 2 | 3 | macro import cx.macros.quasiquoting; 4 | 5 | import package(compiler).cx.base; 6 | import package(compiler).cx.parser_base; 7 | import package(compiler).helpers; 8 | import cx.hash; 9 | 10 | /** 11 | * This is almost certainly too clever by half. 12 | */ 13 | class StringHashMacro : Macro 14 | { 15 | Type hashType; 16 | 17 | this() { } 18 | 19 | override void apply(MacroArgs args) { 20 | auto callMacroArgs = args.instanceOf(CallMacroArgs); 21 | if (!callMacroArgs) return; 22 | auto context = callMacroArgs.context; 23 | // auto isHashMethod = callMacroArgs.target.compile(context).same( 24 | // (compiler.$expr (new Hash).adds).compile(context)); 25 | auto classMethodPtr = callMacroArgs.target.compile(context).instanceOf(ClassMethodPtr); 26 | if (!classMethodPtr) return; 27 | auto type = classMethodPtr.thisPtr.type; 28 | // TODO look up Hash with fqn path 29 | if (!this.hashType) { 30 | auto hashType = findParent!ModuleBase(context.namespace).lookup("Hash", context, __HERE__, null); 31 | if (!hashType || !hashType.instanceOf(Type)) return; 32 | this.hashType = hashType.instanceOf(Type); 33 | } 34 | auto isHash = type.same(hashType); 35 | if (!isHash) return; 36 | auto method = classMethodPtr.funcPtr.instanceOf(FunctionReferenceBase); 37 | if (!method) return; 38 | auto fun = method.getFunction(); 39 | if (fun.name != "adds") return; 40 | // it's a Hash.adds() call. 41 | assert(callMacroArgs.args.length == 1); 42 | auto str = callMacroArgs.args[0].sym.compile(context).instanceOf(StringLiteralBase); 43 | if (!str) return; // variable call 44 | auto astMember = callMacroArgs.target.instanceOf(ASTMemberBase); 45 | if (!astMember) return; 46 | callMacroArgs.transformed 47 | = optimizeStringCall(astMember.base, str.text, context, callMacroArgs.loc); 48 | } 49 | 50 | Expression optimizeStringCall(ASTSymbol base, string str, Context context, Loc loc) { 51 | auto state = poly_hash_string(str); 52 | auto add = context.compiler.astLongLiteral(state.add, loc); 53 | auto mult = context.compiler.astLongLiteral(state.mult, loc); 54 | 55 | return (context.compiler.$expr $base.apply($add, $mult)).compile(context).instanceOf(Expression); 56 | } 57 | } 58 | 59 | void addStringHashMacro(MacroState macroState) 60 | { 61 | macroState.addMacro(new StringHashMacro); 62 | } 63 | 64 | macro(addStringHashMacro); 65 | 66 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | CX=${CX:-build/cx} 4 | CXFLAGS="-Pcompiler:build/src" 5 | CXFLAGS="${CXFLAGS} -Prunnable:test/runnable:compiler" 6 | CXFLAGS="${CXFLAGS} -Pfail_compilation:test/fail_compilation:compiler" 7 | 8 | num_total=$(ls -q test/runnable/*.cx test/fail_compilation/*.cx |wc -l) 9 | build_failed=0 10 | run_failed=0 11 | build_crashed=0 12 | falsely_succeeded=0 13 | test_skipped=0 14 | 15 | function test_enabled { 16 | test="$1" 17 | shift 18 | if [ $# -eq 0 ]; then return 0; fi 19 | while [ $# -gt 0 ] 20 | do 21 | if [[ "$test" == *"$1"* ]]; then return 0; fi 22 | shift 23 | done 24 | return 1 25 | } 26 | 27 | # runnable 28 | mkdir -p build/test/runnable 29 | while read file 30 | do 31 | if ! test_enabled "$file" "$@" 32 | then 33 | test_skipped=$((test_skipped+1)) 34 | continue 35 | fi 36 | echo "$file"... 37 | executable=build/"$file" 38 | CMD="$CX $CXFLAGS \"$file\" -o \"$executable\"" 39 | if ! eval $CMD 2>&1 |cat>build/out.txt 40 | then 41 | build_failed=$((build_failed+1)) 42 | echo $CMD 43 | cat build/out.txt 44 | elif ! "$executable" 45 | then 46 | run_failed=$((run_failed+1)) 47 | fi 48 | done < <(ls -q test/runnable/*.cx) 49 | 50 | # fail_compilation 51 | # tests should fail with an exit code, not a segfault 52 | mkdir -p build/test/fail_compilation 53 | while read file 54 | do 55 | if ! test_enabled "$file" "$@" 56 | then 57 | test_skipped=$((test_skipped+1)) 58 | continue 59 | fi 60 | echo "$file"... 61 | executable=build/"$file" 62 | CMD="$CX $CXFLAGS \"$file\" -o \"$executable\"" 63 | set +e 64 | eval $CMD 2>&1 |cat>build/out.txt 65 | EXIT=$? 66 | set -e 67 | if [ $EXIT -eq 0 ]; then 68 | falsely_succeeded=$((falsely_succeeded+1)) 69 | echo $CMD 70 | cat build/out.txt 71 | echo "Error expected but not found!" 72 | elif [ $EXIT -gt 128 ]; then 73 | # signal 74 | build_crashed=$((build_crashed+1)) 75 | echo $CMD 76 | cat build/out.txt 77 | fi 78 | done < <(ls -q test/fail_compilation/*.cx) 79 | 80 | num_total=$((num_total - test_skipped)) 81 | num_success=$((num_total - build_failed - run_failed - falsely_succeeded - build_crashed)) 82 | 83 | echo "Result:" 84 | echo " ${num_success}/${num_total} successful" 85 | echo " ${build_failed} failed to build" 86 | echo " ${run_failed} failed to run" 87 | echo " ${falsely_succeeded} built when they should have failed" 88 | echo " ${build_crashed} crashed the compiler instead of erroring" 89 | echo " ${test_skipped} tests were skipped" 90 | [ $num_success -eq $num_total ] 91 | -------------------------------------------------------------------------------- /src/std/file.cx: -------------------------------------------------------------------------------- 1 | module std.file; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.listcomprehension; 5 | 6 | import std.string : cToString, cToStringFree, toStringz; 7 | 8 | extern(C) void free(void*); 9 | 10 | string dirname(string path) { 11 | import c.libgen : dirname; 12 | 13 | return path.toStringz.dirname.cToString; 14 | } 15 | 16 | unittest { 17 | assert("foo/bar.txt".dirname == "foo"); 18 | } 19 | 20 | string basename(string path) { 21 | import c.libgen : basename; 22 | 23 | return path.toStringz.basename.cToString; 24 | } 25 | 26 | unittest { 27 | assert("foo/bar.txt".basename == "bar.txt"); 28 | } 29 | 30 | string realpath(string path) { 31 | import c.stdlib : realpath; 32 | 33 | auto ptr = path.toStringz; 34 | auto ret = ptr.realpath(null).cToStringFree; 35 | 36 | free(ptr); 37 | return ret; 38 | } 39 | 40 | extern(C) void* fopen(char* pathname, char* mode); 41 | extern(C) int fseek(void* stream, long offset, int whence); 42 | extern(C) long ftell(void* stream); 43 | extern(C) size_t fread(void* ptr, size_t size, size_t nmemb, void* stream); 44 | extern(C) size_t fwrite(void* ptr, size_t size, size_t nmemb, void* stream); 45 | extern(C) int fclose(void* stream); 46 | 47 | alias SEEK_SET = 0; 48 | alias SEEK_END = 2; 49 | 50 | ubyte[] readFile(string file) { 51 | char* fn = toStringz(file); 52 | char* rb = toStringz("rb"); 53 | void* f = fopen(fn, rb); 54 | free(rb); 55 | fseek(f, 0, SEEK_END); 56 | long fsize = ftell(f); 57 | fseek(f, 0, SEEK_SET); /* same as rewind(f); */ 58 | 59 | ubyte[] buffer = new ubyte[](fsize); 60 | fread(buffer.ptr, 1, fsize, f); 61 | fclose(f); 62 | free(fn); 63 | 64 | return buffer; 65 | } 66 | 67 | void writeFile(string file, ubyte[] data) { 68 | char* fn = toStringz(file); 69 | char* wb = toStringz("wb"); 70 | void* f = fopen(fn, wb); 71 | free(wb); 72 | fwrite(data.ptr, 1, data.length, f); 73 | fclose(f); 74 | free(fn); 75 | } 76 | 77 | string readText(string file) { 78 | // TODO validate utf-8? 79 | // TODO cast(string)!! 80 | return [cast(char) a for a in file.readFile]; 81 | } 82 | 83 | void writeText(string path, string data) { 84 | // TODO validate utf-8? 85 | // TODO cast(ubyte[])!! 86 | path.writeFile([cast(ubyte) a for a in data]); 87 | } 88 | 89 | extern(C) int access(char* pathname, int mode); 90 | 91 | alias F_OK = 0; 92 | 93 | bool exists(string file) { 94 | auto fn = toStringz(file); 95 | bool ret = access(fn, F_OK) != -1; 96 | free(fn); 97 | return ret; 98 | } 99 | 100 | void mkdir(string path) { 101 | import c.sys.stat : mkdir; 102 | auto ptr = path.toStringz; 103 | mkdir(ptr, 511); // 0777 104 | free(ptr); 105 | } 106 | -------------------------------------------------------------------------------- /src/cx/macros/quasiquoting.cx: -------------------------------------------------------------------------------- 1 | // TODO: $var escaping 2 | module cx.macros.quasiquoting; 3 | 4 | import package(compiler).cx.base; 5 | import package(compiler).cx.parser_base; 6 | import package(compiler).helpers; 7 | 8 | class QuasiQuoterImpl : Quoter 9 | { 10 | ASTSymbol compilerExpr; 11 | 12 | this(this.compiler, this.compilerExpr) { } 13 | 14 | override ASTSymbol compilerCall(string name, ASTSymbol[] parameters, Loc loc) { 15 | return compiler.astCall(compiler.astMember(compilerExpr, name, loc), parameters ~ quoteLoc(loc), loc); 16 | } 17 | 18 | override ASTSymbol quoteLoc(Loc loc) { 19 | return compiler.astCall( 20 | compiler.astIdentifier("Loc", __HERE__), [ 21 | compiler.astStringLiteral(loc.filename, __HERE__), 22 | compiler.astIntLiteral(loc.row, __HERE__), 23 | compiler.astIntLiteral(loc.column, __HERE__), 24 | ], __HERE__); 25 | } 26 | } 27 | 28 | class QuasiQuoting : Macro 29 | { 30 | this() { } 31 | override void apply(MacroArgs args) { 32 | auto args = args.instanceOf(ParsePropertyArgs); 33 | if (args) { 34 | args.result = this.parse(args.parser, args.lexicalContext, args.left); 35 | } 36 | } 37 | 38 | ASTSymbol parse(Parser parser, LexicalContext lexicalContext, ASTSymbol compilerExpr) 39 | { 40 | auto compiler = lexicalContext.compiler; 41 | 42 | auto quoter = new QuasiQuoterImpl(compiler, compilerExpr); 43 | { 44 | parser.begin(); 45 | if (parser.accept(".") && parser.accept("$stmt")) { 46 | parser.commit(); 47 | auto stmt = compiler.parseStatement(parser, lexicalContext); 48 | parser.assert_(!!stmt, "statement expected"); 49 | return stmt.quote(quoter); 50 | } 51 | parser.revert(); 52 | } 53 | { 54 | parser.begin(); 55 | if (parser.accept(".") && parser.accept("$expr")) { 56 | parser.commit(); 57 | auto expr = compiler.parseExpression(parser, lexicalContext); 58 | parser.assert_(!!expr, "expression expected"); 59 | return expr.quote(quoter); 60 | } 61 | parser.revert(); 62 | } 63 | { 64 | parser.begin(); 65 | if (parser.accept(".") && parser.accept("$type")) { 66 | parser.commit(); 67 | auto type = compiler.parseType(parser, lexicalContext); 68 | parser.assert_(!!type, "type expected"); 69 | return type.quote(quoter); 70 | } 71 | parser.revert(); 72 | } 73 | return null; 74 | } 75 | } 76 | 77 | void addQuasiQuotingMacro(MacroState macroState) 78 | { 79 | macroState.addMacro(new QuasiQuoting); 80 | } 81 | 82 | macro(addQuasiQuotingMacro); 83 | -------------------------------------------------------------------------------- /src/cx/enums.cx: -------------------------------------------------------------------------------- 1 | module cx.enums; 2 | 3 | macro import cx.macros.listcomprehension; 4 | 5 | import backend.base; 6 | import cx.base; 7 | import cx.hash; 8 | import cx.parser_base; 9 | import cx.parser; 10 | import cx.types; 11 | import helpers; 12 | 13 | struct EnumEntry 14 | { 15 | string name; 16 | int value; 17 | } 18 | 19 | class Enum : Type 20 | { 21 | string name; 22 | 23 | EnumEntry[] entries; 24 | 25 | // TODO like so, in Class{} 26 | Hash precomputedHash; 27 | 28 | this(this.name, this.entries) { 29 | precomputedHash = new Hash(); 30 | precomputedHash.adds("hash"); 31 | precomputedHash.adds(name); 32 | precomputedHash.addl(entries.length); 33 | [precomputedHash.adds(e.name) for e in entries]; 34 | [precomputedHash.addl(e.value) for e in entries]; 35 | } 36 | 37 | override BackendType emit(Platform platform) { return new BackendIntType; } 38 | override bool same(Type other) { 39 | auto otherEnum = other.instanceOf(Enum); 40 | 41 | // TODO fqn 42 | return otherEnum && otherEnum.name == name; 43 | } 44 | override string repr() { return name; } 45 | override string mangle() { return "enum_" ~ name; } 46 | override void hash(Hash hash) { hash.applyHash(precomputedHash); } 47 | override Symbol accessMember(Loc loc, Context context, Expression base, string field) 48 | { 49 | if (base) return null; 50 | Symbol asEnum(int value) { 51 | return context.compiler.castTo(this, 52 | context.compiler.intLiteral(value)); 53 | } 54 | return [first asEnum(e.value) for e in entries where e.name == field else null]; 55 | } 56 | override Expression binaryOp(Context context, string op, Expression lhs, Expression rhs, Loc loc) 57 | { 58 | if (op == "==") 59 | { 60 | // TODO check type of rhs 61 | auto left = context.compiler.castTo(new Integer, lhs); 62 | auto right = context.compiler.castTo(new Integer, rhs); 63 | 64 | return context.compiler.binaryOp("==", context, left, right, loc); 65 | } 66 | return null; 67 | } 68 | } 69 | 70 | class ASTEnumDecl : ASTSymbol 71 | { 72 | string name; 73 | 74 | EnumEntry[] entries; 75 | 76 | this(this.name, this.entries) { } 77 | 78 | override Type compile(Context context) { 79 | return new Enum(name, entries); 80 | } 81 | 82 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTEnumDecl'!"); assert(false); } 83 | } 84 | 85 | ASTEnumDecl parseEnumDecl(Parser parser, LexicalContext lexicalContext) 86 | { 87 | auto loc = parser.loc; 88 | if (!acceptIdentifier(parser, "enum")) 89 | { 90 | return null; 91 | } 92 | string name = parseIdentifier(parser); 93 | mut EnumEntry[] entries; 94 | parser.expect("{"); 95 | while (!parser.accept("}")) 96 | { 97 | if (entries.length) 98 | parser.expect(","); 99 | // is there a better way to write 'there may be a trailing comma'? 100 | if (parser.accept("}")) 101 | break; 102 | string entryName = parseIdentifier(parser); 103 | entries ~= EnumEntry(entryName, cast(int) entries.length); 104 | } 105 | return new ASTEnumDecl(name, entries); 106 | } 107 | -------------------------------------------------------------------------------- /src/helpers.cx: -------------------------------------------------------------------------------- 1 | module helpers; 2 | 3 | extern(C) void print(char[]); 4 | extern(C) void assert(int); 5 | extern(C) int cxruntime_atoi(string); 6 | extern(C) float cxruntime_atof(string); 7 | extern(C) string cxruntime_itoa(int); 8 | extern(C) string cxruntime_ltoa(long); 9 | extern(C) string cxruntime_ftoa(float); 10 | extern(C) string cxruntime_ftoa_hex(float); 11 | extern(C) string cxruntime_ptr_id(void*); 12 | extern(C) void* memcpy(void* target, void* source, size_t length); 13 | extern(C) void free(void*); 14 | extern(C) int access(char* pathname, int mode); 15 | extern(C) void* fopen(char* pathname, char* mode); 16 | extern(C) int fseek(void* stream, long offset, int whence); 17 | extern(C) long ftell(void* stream); 18 | extern(C) size_t fread(void* ptr, size_t size, size_t nmemb, void* stream); 19 | extern(C) size_t fwrite(void* ptr, size_t size, size_t nmemb, void* stream); 20 | extern(C) int fclose(void* stream); 21 | extern(C) void* malloc(size_t size); 22 | 23 | char* toStringz(string s) { 24 | char* ret = malloc(s.length + 1); 25 | memcpy(ret, s.ptr, s.length); 26 | ret[s.length] = "\0"[0]; 27 | return ret; 28 | } 29 | 30 | int atoi(string s) { return cxruntime_atoi(s); } 31 | float atof(string s) { return cxruntime_atof(s); } 32 | string itoa(int i) { return cxruntime_itoa(i); } 33 | string ltoa(long l) { return cxruntime_ltoa(l); } 34 | string ftoa(float f) { return cxruntime_ftoa(f); } 35 | string ftoa_hex(float f) { return cxruntime_ftoa_hex(f); } 36 | string ptrId(void* ptr) { return cxruntime_ptr_id(ptr); } 37 | 38 | int find(string text, string match) { 39 | for (int i <- 0 .. text.length - match.length + 1) 40 | if (text[i .. i + match.length] == match) return i; 41 | return -1; 42 | } 43 | 44 | string[] split(mut string text, string sep) { 45 | mut string[] result; 46 | while (true) { 47 | int pos = find(text, sep); 48 | if (pos == -1) { 49 | result ~= text; 50 | return result; 51 | } 52 | result ~= text[0 .. pos]; 53 | text = text[pos + sep.length .. $]; 54 | } 55 | } 56 | 57 | string join(string[] array, string sep) { 58 | mut string result; 59 | for (int i <- 0 .. array.length) { 60 | if (i > 0) result ~= sep; 61 | result ~= array[i]; 62 | } 63 | return result; 64 | } 65 | 66 | bool exists(string file) { 67 | auto fn = toStringz(file); 68 | int F_OK = 0; 69 | bool ret = access(fn, F_OK) != -1; 70 | free(fn); 71 | return ret; 72 | } 73 | 74 | alias SEEK_SET = 0; 75 | alias SEEK_END = 2; 76 | 77 | string read(string file) { 78 | // thanks, 79 | // https://stackoverflow.com/questions/14002954/c-programming-how-to-read-the-whole-file-contents-into-a-buffer 80 | char* fn = toStringz(file); 81 | char* rb = toStringz("rb"); 82 | void* f = fopen(fn, rb); 83 | free(rb); 84 | fseek(f, 0, SEEK_END); 85 | long fsize = ftell(f); 86 | fseek(f, 0, SEEK_SET); /* same as rewind(f); */ 87 | 88 | char[] buffer = new char[](fsize); 89 | fread(buffer.ptr, 1, fsize, f); 90 | fclose(f); 91 | free(fn); 92 | 93 | return buffer; 94 | } 95 | 96 | void write(string file, string content) { 97 | char* fn = toStringz(file); 98 | char* wb = toStringz("wb"); 99 | void* f = fopen(fn, wb); 100 | free(wb); 101 | fwrite(content.ptr, 1, content.length, f); 102 | fclose(f); 103 | free(fn); 104 | } 105 | -------------------------------------------------------------------------------- /src/std/math/matrix.cx: -------------------------------------------------------------------------------- 1 | module std.math.matrix; 2 | 3 | import std.math; 4 | import std.math.vector; 5 | 6 | extern(C) void assert(bool); 7 | 8 | struct mat4x4 { 9 | vec4f row1, row2, row3, row4; 10 | mat4x4 rotateX(float by) { return mul(mat4x4.rotationX(by)); } 11 | mat4x4 rotateY(float by) { return mul(mat4x4.rotationY(by)); } 12 | mat4x4 rotateZ(float by) { return mul(mat4x4.rotationZ(by)); } 13 | mat4x4 scale(float x, float y, float z) { return mul(mat4x4.scaling(x, y, z)); } 14 | mat4x4 translate(float x, float y, float z) { return mul(mat4x4.translation(x, y, z)); } 15 | mat4x4 mul(mat4x4 other) { 16 | // this is all super unoptimized 17 | float rowmul(int row, int col) { 18 | auto mul = this.row(row) * other.row(col); 19 | return mul.x + mul.y + mul.z + mul.w; 20 | } 21 | return mat4x4( 22 | vec4f(rowmul(0, 0), rowmul(0, 1), rowmul(0, 2), rowmul(0, 3)), 23 | vec4f(rowmul(1, 0), rowmul(1, 1), rowmul(1, 2), rowmul(1, 3)), 24 | vec4f(rowmul(2, 0), rowmul(2, 1), rowmul(2, 2), rowmul(2, 3)), 25 | vec4f(rowmul(3, 0), rowmul(3, 1), rowmul(3, 2), rowmul(3, 3))); 26 | 27 | } 28 | vec4f row(int r) { 29 | if (r == 0) return row1; 30 | if (r == 1) return row2; 31 | if (r == 2) return row3; 32 | if (r == 3) return row4; 33 | assert(false); 34 | } 35 | mat4x4 transpose() { 36 | return mat4x4( 37 | vec4f(row1.x, row2.x, row3.x, row4.x), 38 | vec4f(row1.y, row2.y, row3.y, row4.y), 39 | vec4f(row1.z, row2.z, row3.z, row4.z), 40 | vec4f(row1.w, row2.w, row3.w, row4.w)); 41 | } 42 | 43 | static mat4x4 identity() { 44 | return mat4x4( 45 | vec4f(1, 0, 0, 0), 46 | vec4f(0, 1, 0, 0), 47 | vec4f(0, 0, 1, 0), 48 | vec4f(0, 0, 0, 1)); 49 | } 50 | static mat4x4 rotationX(float angle) { 51 | float cos = cos(angle), sin = sin(angle); 52 | return mat4x4( 53 | vec4f(1, 0, 0, 0), 54 | vec4f(0, cos, sin, 0), 55 | vec4f(0, -sin, cos, 0), 56 | vec4f(0, 0, 0, 1)); 57 | } 58 | static mat4x4 rotationY(float angle) { 59 | float cos = cos(angle), sin = sin(angle); 60 | return mat4x4( 61 | vec4f(cos, 0, -sin, 0), 62 | vec4f( 0, 1, 0, 0), 63 | vec4f(sin, 0, cos, 0), 64 | vec4f( 0, 0, 0, 1)); 65 | } 66 | 67 | static mat4x4 rotationZ(float angle) { 68 | float cos = cos(angle), sin = sin(angle); 69 | return mat4x4( 70 | vec4f(cos, -sin, 0, 0), 71 | vec4f(sin, cos, 0, 0), 72 | vec4f( 0, 0, 1, 0), 73 | vec4f( 0, 0, 0, 1)); 74 | } 75 | 76 | static mat4x4 scaling(float x, float y, float z) { 77 | return mat4x4( 78 | vec4f(x, 0, 0, 0), 79 | vec4f(0, y, 0, 0), 80 | vec4f(0, 0, z, 0), 81 | vec4f(0, 0, 0, 1)); 82 | } 83 | 84 | static mat4x4 translation(float x, float y, float z) { 85 | return mat4x4( 86 | vec4f(1, 0, 0, x), 87 | vec4f(0, 1, 0, y), 88 | vec4f(0, 0, 1, z), 89 | vec4f(0, 0, 0, 1)); 90 | } 91 | 92 | static mat4x4 ortho(float left, float right, float bottom, float top, float near, float far) { 93 | float dx = right - left, dy = top - bottom, dz = far - near; 94 | float tx = -(right + left) / dx; 95 | float ty = -(top + bottom) / dy; 96 | float tz = -(far + near) / dz; 97 | return mat4x4( 98 | vec4f(2/dx, 0, 0, tx), 99 | vec4f( 0, 2/dy, 0, ty), 100 | vec4f( 0, 0, -2/dz, tz), 101 | vec4f( 0, 0, 0, 1)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/cx/macros/once.cx: -------------------------------------------------------------------------------- 1 | module cx.macros.once; 2 | 3 | macro import cx.macros.quasiquoting; 4 | 5 | import package(compiler).cx.base; 6 | import package(compiler).cx.hash; 7 | import package(compiler).cx.parser; 8 | import package(compiler).cx.parser_base; 9 | import package(compiler).cx.types; 10 | import package(compiler).helpers; 11 | 12 | class ASTOnceExpression : ASTSymbol 13 | { 14 | OnceMacro macro_; 15 | 16 | ASTSymbol target; 17 | 18 | Loc loc; 19 | 20 | this(this.macro_, this.target, this.loc) { } 21 | 22 | override Symbol compile(Context context) 23 | { 24 | mut Context context = context; 25 | auto key = context.compiler.astIntLiteral(this.macro_.keyCounter, this.loc); 26 | this.macro_.keyCounter += 1; 27 | 28 | auto cacheIsSet = new FunctionDeclaration( 29 | "cxruntime_cache_isset", new Integer, [ 30 | Parameter(false, "key", false, new Integer)]); 31 | auto cacheSet = new FunctionDeclaration( 32 | "cxruntime_cache_set", new Void, [ 33 | Parameter(false, "key", false, new Integer), 34 | Parameter(false, "ptr", false, new Pointer(new Void)), 35 | Parameter(false, "free", false, new FunctionPointer(new Void, [Parameter.fromType(new Pointer(new Void))]))]); 36 | auto cacheGet = new FunctionDeclaration( 37 | "cxruntime_cache_get", new Pointer(new Void), [ 38 | Parameter(false, "key", false, new Integer)]); 39 | auto rtFree = genRtFree(context, target.compile(context).instanceOf(Expression).type); 40 | 41 | context = context.withNamespace(context.compiler.exprAlias( 42 | context.namespace, "cxruntime_cache_isset", cacheIsSet)); 43 | context = context.withNamespace(context.compiler.exprAlias( 44 | context.namespace, "cxruntime_cache_set", cacheSet)); 45 | context = context.withNamespace(context.compiler.exprAlias( 46 | context.namespace, "cxruntime_cache_get", cacheGet)); 47 | context = context.withNamespace(context.compiler.exprAlias( 48 | context.namespace, "rt_free", rtFree)); 49 | 50 | return (context.compiler.$expr ({ 51 | if (!cxruntime_cache_isset($key)) 52 | { 53 | cxruntime_cache_set($key, cast(void*) $target, &rt_free); 54 | } 55 | cast(typeof($target)) cxruntime_cache_get($key); 56 | })).compile(context); 57 | } 58 | } 59 | 60 | class FixedSymbolAlias : ASTSymbol 61 | { 62 | Symbol symbol; 63 | this(this.symbol) { } 64 | override Symbol compile(Context context) { return this.symbol; } 65 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'FixedSymbolAlias'"); assert(false); } 66 | } 67 | 68 | FunctionDeclaration genRtFree(Context context, Type type) { 69 | ASTStatement body_() { 70 | auto type = new FixedSymbolAlias(type); 71 | return context.compiler.$stmt __destroy(cast($type) ptr);; 72 | } 73 | 74 | Parameter[] params = [Parameter(false, "ptr", false, new Pointer(new Void))]; 75 | return context.compiler.createRuntimeFunction( 76 | context, "cache_free_" ~ type.mangle, new Void, params, &body_); 77 | } 78 | 79 | class OnceMacro : Macro 80 | { 81 | int keyCounter; 82 | 83 | this() { } 84 | 85 | override void apply(MacroArgs args) { 86 | auto args = args.instanceOf(ParseExpressionBaseArgs); 87 | if (args) { 88 | args.symbol = this.parse(args.parser, args.lexicalContext); 89 | } 90 | } 91 | 92 | ASTSymbol parse(Parser parser, LexicalContext lexicalContext) 93 | { 94 | auto loc = parser.loc(); 95 | if (!acceptIdentifier(parser, "once")) 96 | { 97 | return null; 98 | } 99 | auto expression = lexicalContext.compiler.parseExpression(parser, lexicalContext); 100 | loc.assert2s(!!expression, "expression expected"); 101 | return new ASTOnceExpression(this, expression, loc); 102 | } 103 | } 104 | 105 | void addOnceMacro(MacroState macroState) 106 | { 107 | macroState.addMacro(new OnceMacro); 108 | } 109 | 110 | macro(addOnceMacro); 111 | -------------------------------------------------------------------------------- /src/std/thread.cx: -------------------------------------------------------------------------------- 1 | module std.thread; 2 | 3 | import c.pthread; 4 | 5 | extern(C) void assert(bool); 6 | 7 | class Mutex 8 | { 9 | pthread_mutex_t mutex; 10 | 11 | this() { pthread_mutex_init(&mutex, null); } 12 | void lock() { pthread_mutex_lock(&mutex); } 13 | void unlock() { pthread_mutex_unlock(&mutex); } 14 | } 15 | 16 | class CondVar 17 | { 18 | pthread_cond_t cond; 19 | 20 | this() { pthread_cond_init(&cond, null); } 21 | void wait(Mutex mutex) { pthread_cond_wait(&cond, &mutex.mutex); } 22 | void signal() { pthread_cond_signal(&cond); } 23 | void broadcast() { pthread_cond_broadcast(&cond); } 24 | } 25 | 26 | template Waitable(T) 27 | { 28 | class Waitable 29 | { 30 | Mutex mutex; 31 | CondVar signal; 32 | T value; 33 | this(this.value) { 34 | this.mutex = new Mutex; 35 | this.signal = new CondVar; 36 | } 37 | void set(T value) { 38 | mutex.lock; 39 | this.value = value; 40 | signal.broadcast; 41 | mutex.unlock; 42 | } 43 | void update(T delegate(T) action) { 44 | mutex.lock; 45 | this.value = action(this.value); 46 | signal.broadcast; 47 | mutex.unlock; 48 | } 49 | // TODO move into waitFor 50 | T id(T value) { return value; } 51 | void waitFor(bool delegate(T) condition) { 52 | waitReact(condition, &id); 53 | } 54 | void waitReact(bool delegate(T) condition, T delegate(T) react) { 55 | mutex.lock; 56 | while (true) { 57 | if (condition(this.value)) { 58 | this.value = react(this.value); 59 | signal.broadcast; 60 | mutex.unlock; 61 | return; 62 | } 63 | signal.wait(mutex); 64 | } 65 | } 66 | } 67 | } 68 | 69 | class Semaphore 70 | { 71 | Waitable!int waitable; 72 | 73 | this(int i) { this.waitable = new Waitable!int(i); } 74 | 75 | void acquire() { 76 | bool greaterZero(int i) { return i > 0; } 77 | int decrement(int i) { return i - 1; } 78 | waitable.waitReact(&greaterZero, &decrement); 79 | } 80 | void release() { 81 | int increment(int i) { return i + 1; } 82 | waitable.update(&increment); 83 | } 84 | } 85 | 86 | abstract class Task 87 | { 88 | abstract void run() { assert(false); } 89 | } 90 | 91 | class Thread 92 | { 93 | pthread_t thr; 94 | 95 | void delegate() run; 96 | this(this.run) { } 97 | void start() { 98 | pthread_create(&thr, null, &call_thread_dg, &run); 99 | } 100 | } 101 | 102 | class ThreadPool 103 | { 104 | Mutex mutex; 105 | Task[] tasks; 106 | Thread[] threads; 107 | int queuedTasks; 108 | Semaphore waitingTasks; 109 | Semaphore doneTasks; 110 | 111 | this(int i) { 112 | this.mutex = new Mutex; 113 | this.waitingTasks = new Semaphore(0); 114 | this.doneTasks = new Semaphore(0); 115 | for (int j <- 0..i) { 116 | auto thread = new Thread(&run); 117 | thread.start; 118 | threads ~= thread; 119 | } 120 | } 121 | void run() { 122 | while (true) { 123 | // TODO why is this broken, probably double calls getTask 124 | // getTask.run; 125 | auto task = getTask; 126 | task.run; 127 | this.doneTasks.release; 128 | } 129 | } 130 | void waitComplete(void delegate(float) progress) { 131 | this.mutex.lock; 132 | int tasks = this.queuedTasks; 133 | this.queuedTasks = 0; 134 | this.mutex.unlock; 135 | for (int i <- 0..tasks) { 136 | this.doneTasks.acquire; 137 | progress((i + 1) * 1.0 / tasks); 138 | } 139 | } 140 | void addTask(Task task) { 141 | mutex.lock; 142 | tasks ~= task; 143 | this.queuedTasks += 1; 144 | mutex.unlock; 145 | this.waitingTasks.release; 146 | } 147 | Task getTask() { 148 | this.waitingTasks.acquire; 149 | mutex.lock; 150 | auto ret = tasks[$ - 1]; 151 | tasks = tasks[0 .. $ - 1]; 152 | mutex.unlock; 153 | return ret; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/std/string.cx: -------------------------------------------------------------------------------- 1 | module std.string; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.listcomprehension; 5 | 6 | // TODO import or private 7 | extern(C) void* malloc(size_t size); 8 | extern(C) void* memcpy(void* target, void* source, size_t length); 9 | extern(C) size_t strlen(char*); 10 | extern(C) string cxruntime_itoa(int i); 11 | extern(C) string cxruntime_ltoa(long); 12 | extern(C) string cxruntime_ftoa(float f); 13 | extern(C) int cxruntime_atoi(string); 14 | extern(C) float cxruntime_atof(string); 15 | 16 | char* toStringz(string s) { 17 | char* ret = malloc(s.length + 1); 18 | memcpy(ret, s.ptr, s.length); 19 | ret[s.length] = "\0"[0]; 20 | return ret; 21 | } 22 | 23 | string cToString(char* ptr) { 24 | auto len = strlen(ptr); 25 | auto ret = new char[](len); 26 | memcpy(ret.ptr, ptr, len); 27 | return ret; 28 | } 29 | 30 | string cToStringFree(char* ptr) { 31 | string ret = cToString(ptr); 32 | free(ptr); 33 | return ret; 34 | } 35 | 36 | bool startsWith(string haystack, string needle) { 37 | if (haystack.length < needle.length) return false; 38 | return haystack[0 .. needle.length] == needle; 39 | } 40 | 41 | unittest { 42 | assert("Hello World".startsWith("Hello")); 43 | assert(!"Hello World".startsWith("World")); 44 | } 45 | 46 | bool endsWith(string haystack, string needle) { 47 | if (haystack.length < needle.length) return false; 48 | return haystack[$ - needle.length .. $] == needle; 49 | } 50 | 51 | unittest { 52 | assert("Hello World".endsWith("World")); 53 | assert(!"Hello World".endsWith("Hello")); 54 | } 55 | 56 | string itoa(int i) { return cxruntime_itoa(i); } 57 | 58 | unittest { 59 | assert(itoa(5) == "5"); 60 | assert(itoa(-3) == "-3"); 61 | } 62 | 63 | string ltoa(long l) { return cxruntime_ltoa(l); } 64 | 65 | string ftoa(float f) { return cxruntime_ftoa(f); } 66 | 67 | int atoi(string s) { return cxruntime_atoi(s); } 68 | 69 | unittest { 70 | assert(atoi("5") == 5); 71 | assert(atoi("-3") == -3); 72 | } 73 | 74 | float atof(string s) { return cxruntime_atof(s); } 75 | 76 | int find(string text, string match) { 77 | for (int i <- 0 .. text.length - match.length + 1) 78 | if (text[i .. i + match.length] == match) return i; 79 | return -1; 80 | } 81 | 82 | unittest { 83 | assert("Hello World".find("o") == 4); 84 | assert("Hello World".find("p") == -1); 85 | } 86 | 87 | string[] split(mut string text, string sep) { 88 | if (text.length == 0) return []; 89 | mut string[] result; 90 | while (true) { 91 | int pos = find(text, sep); 92 | if (pos == -1) { 93 | result ~= text; 94 | return result; 95 | } 96 | result ~= text[0 .. pos]; 97 | text = text[pos + sep.length .. $]; 98 | } 99 | } 100 | 101 | unittest { 102 | assert("Hello World".split(" ") == ["Hello", "World"]); 103 | assert("Hello".split(" ") == ["Hello"]); 104 | assert("".split(" ").length == 0); 105 | } 106 | 107 | (string fragment, string rest) slice(string text, string sep) { 108 | int pos = find(text, sep); 109 | if (pos == -1) 110 | return (text, ""); 111 | return (text[0 .. pos], text[pos + sep.length .. $]); 112 | } 113 | 114 | string toHexString(ubyte[] data) { 115 | auto hexLetters = "0123456789abcdef"; 116 | string hex(ubyte ub) { 117 | return [hexLetters[cast(int) ub >> 4], hexLetters[cast(int) ub & 0xf]]; 118 | } 119 | return [join "" hex(ub) for ub in data]; 120 | } 121 | 122 | string join(string[] array, string sep) { 123 | mut string result; 124 | for (int i <- 0 .. array.length) { 125 | if (i > 0) result ~= sep; 126 | result ~= array[i]; 127 | } 128 | return result; 129 | } 130 | 131 | string strip(mut string text) { 132 | bool isWhitespace(char ch) { 133 | return ch == " "[0] || ch == "\r"[0] || ch == "\n"[0]; 134 | } 135 | while (text[0].isWhitespace) text = text[1 .. $]; 136 | while (text[$ - 1].isWhitespace) text = text[0 .. $ - 1]; 137 | return text; 138 | } 139 | 140 | string replace(string str, string match, string replace) { 141 | mut string result; 142 | mut size_t i = 0; 143 | while (i <= str.length - match.length) { 144 | if (str[i .. i + match.length] == match) { 145 | result ~= replace; 146 | i += match.length; 147 | } else { 148 | result ~= str[i]; 149 | i += 1; 150 | } 151 | } 152 | result ~= str[i .. $]; 153 | return result; 154 | } 155 | -------------------------------------------------------------------------------- /sdltest.cx: -------------------------------------------------------------------------------- 1 | module sdltest; 2 | 3 | import sdl; 4 | 5 | extern(C) float logf(float); 6 | extern(C) float fabsf(float); 7 | extern(C) float expf(float); 8 | extern(C) void print(string); 9 | extern(C) string cxruntime_itoa(int i); 10 | string itoa(int i) { return cxruntime_itoa(i); } 11 | 12 | struct Complex { 13 | float re; 14 | float im; 15 | 16 | float magn() { 17 | float resq = re * re, imsq = im * im; 18 | return resq + imsq; 19 | } 20 | 21 | Complex sqr() { 22 | return Complex(re * re - im * im, 2 * re * im); 23 | } 24 | 25 | Complex add(Complex other) { 26 | return Complex(re + other.re, im + other.im); 27 | } 28 | 29 | Complex sub(Complex other) { 30 | return Complex(re - other.re, im - other.im); 31 | } 32 | 33 | Complex mul(float f) { 34 | return Complex(re * f, im * f); 35 | } 36 | 37 | bool approxEqual(Complex other) { 38 | return fabsf(re - other.re) < 0.001 && fabsf(im - other.im) < 0.001; 39 | } 40 | } 41 | 42 | int paint(int ix, int iy, float z) { 43 | int r = 0; int g = 0; int b = 0; 44 | int AA = 1; 45 | for (int sy = 0; sy < AA; sy += 1) { 46 | for (int sx = 0; sx < AA; sx += 1) { 47 | float cx = (ix + sx * 1.0 / AA) / 640.0; cx = cx * 4 - 2; 48 | float cy = (iy + sy * 1.0 / AA) / 480.0; cy = cy * 4 - 2; 49 | auto c = Complex(cx, cy); 50 | auto target = Complex(-0.743643887035763, 0.13182590421259918); 51 | auto d = c.sub(target); 52 | d = d.mul(expf(-z)); 53 | c = target.add(d); 54 | // cardioid test, see 55 | // https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set#Advanced_plotting_algorithms 56 | { 57 | auto test = c.sub(Complex(0.25, 0)); 58 | auto q = test.magn(); 59 | if (q*(q+test.re) <= c.im * c.im / 4) continue; 60 | } 61 | { 62 | auto test = c.add(Complex(1, 0)); 63 | if (test.magn() <= 1/16.0) continue; 64 | } 65 | auto z = Complex(0, 0); 66 | auto periodCheck = z; 67 | int period = 0; 68 | for (int i = 0; i < 500; i += 1) { 69 | float magn = z.magn(); 70 | if (magn > 40) { 71 | float log_zn = logf(magn) / 2; 72 | float nu = logf(log_zn / logf(2)) / logf(2); 73 | float fi = i + 1 - nu; 74 | r += cast(int) (fi * 20) & 0xff; 75 | g += cast(int) (fi * 15) & 0xff; 76 | b += cast(int) (fi * 10) & 0xff; 77 | break; 78 | } 79 | z = z.sqr(); 80 | z = z.add(c); 81 | if (z.approxEqual(periodCheck)) break; 82 | period += 1; 83 | if (period > 20) { period = 0; periodCheck = z; } 84 | } 85 | } 86 | } 87 | return (r * (65536 / (AA * AA))) & 0xff0000 + (g * (256 / (AA * AA))) & 0xff00 + (b / (AA * AA)); 88 | } 89 | 90 | alias SDL_QUIT = 256; 91 | 92 | void main() { 93 | void* window; 94 | int width = 640; 95 | int height = 480; 96 | int SDL_INIT_VIDEO = 32; 97 | int SDL_WINDOWPOS_UNDEFINED = 536805376; 98 | int SDL_WINDOW_SHOWN = 4; 99 | 100 | // Initialize SDL systems 101 | if(SDL_Init( SDL_INIT_VIDEO ) < 0) { 102 | print("SDL could not initialize!"); 103 | } 104 | else { 105 | //Create a window 106 | window = SDL_CreateWindow("Hello World".ptr, 107 | SDL_WINDOWPOS_UNDEFINED, 108 | SDL_WINDOWPOS_UNDEFINED, 109 | width, height, 110 | SDL_WINDOW_SHOWN); 111 | if(!window) { 112 | print("Window could not be created!"); 113 | } 114 | } 115 | 116 | // Poll for events and wait till user closes window 117 | bool quit = false; 118 | SDL_Event currentEvent; 119 | float z = 1; 120 | while(!quit) { 121 | while(SDL_PollEvent(¤tEvent) != 0) { 122 | if(currentEvent.type == SDL_QUIT) { 123 | quit = true; 124 | } 125 | } 126 | 127 | auto screenSurface = SDL_GetWindowSurface(window); 128 | for (int y = 0; y < height; y += 1) { 129 | for (int x = 0; x < width; x += 1) { 130 | int rgb = paint(x, y, z); 131 | auto rect = SDL_Rect(x, y, 1, 1); 132 | SDL_FillRect(screenSurface, &rect, rgb); 133 | } 134 | } 135 | SDL_UpdateWindowSurface(window); 136 | 137 | z += 0.01; 138 | } 139 | 140 | SDL_DestroyWindow(window); 141 | SDL_Quit(); 142 | } 143 | -------------------------------------------------------------------------------- /src/std/json.cx: -------------------------------------------------------------------------------- 1 | module std.json; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.listcomprehension; 5 | 6 | import package(compiler).cx.parser; 7 | import package(compiler).cx.parser_base; 8 | 9 | import std.string; 10 | 11 | struct JSONValue 12 | { 13 | (:false | :true | int | string | JSONValue[] | (string key, JSONValue value)[]) value; 14 | 15 | static JSONValue parse(string str) { 16 | auto parser = new Parser("", str); 17 | auto ret = jsonParseImpl(parser); 18 | if (!parser.eof) parser.fail("text after json"); 19 | return ret; 20 | } 21 | 22 | ((string key, JSONValue value)[] | :wrongType) object() { 23 | return value.case( 24 | (:false): :wrongType, (:true): :wrongType, int: :wrongType, string: :wrongType, JSONValue[]: :wrongType, 25 | (string key, JSONValue value)[] obj: obj); 26 | } 27 | 28 | bool isObject() { 29 | // FIXME object.case() once fix built 30 | return object().case( 31 | (:wrongType): false, 32 | (string key, JSONValue value)[]: true); 33 | } 34 | 35 | JSONValue get(string key) { 36 | object.case { 37 | (:wrongType): assert(false); 38 | (string key, JSONValue value)[] obj: { 39 | assert([any a.key == key for a in obj]); 40 | return [first a.value for a in obj where a.key == key else ({ assert(false); JSONValue(:false); })]; 41 | } 42 | } 43 | } 44 | 45 | bool has(string key) { 46 | object.case { 47 | (:wrongType): assert(false); 48 | (string key, JSONValue value)[] obj: 49 | return [any a.key == key for a in obj]; 50 | } 51 | } 52 | } 53 | 54 | unittest 55 | { 56 | auto value = JSONValue([("Hello", JSONValue("World"))]); 57 | assert(value.str == "{\"Hello\": \"World\"}"); 58 | assert(JSONValue.parse("{\"Hello\": \"World\"}").str == "{\"Hello\": \"World\"}"); 59 | // TODO 60 | // auto value = JSONValue({ "Hello": "World" }); 61 | } 62 | 63 | JSONValue jsonParseImpl(Parser parser) { 64 | if (parser.accept("\"")) { 65 | return JSONValue(parseStringLiteral(parser)); 66 | } 67 | parser.parseNumber.case { 68 | (:failure): {} 69 | int value: return JSONValue(value); 70 | } 71 | if (parser.accept("[")) { 72 | mut JSONValue[] entries; 73 | if (!parser.accept("]")) while (true) { 74 | entries ~= jsonParseImpl(parser); 75 | if (parser.accept("]")) break; 76 | parser.expect(","); 77 | } 78 | return JSONValue(entries); 79 | } 80 | if (parser.accept("{")) { 81 | mut (string key, JSONValue val)[] entries; 82 | if (!parser.accept("}")) while (true) { 83 | parser.expect("\""); 84 | auto key = parseStringLiteral(parser); 85 | parser.expect(":"); 86 | auto value = jsonParseImpl(parser); 87 | entries ~= (key, value); 88 | if (parser.accept("}")) break; 89 | parser.expect(","); 90 | } 91 | return JSONValue(entries); 92 | } 93 | parser.fail("unexpected input"); 94 | } 95 | 96 | string str(JSONValue jv) { 97 | jv.value.case { 98 | (:false): return "false"; 99 | (:true): return "true"; 100 | int i: return itoa(i); 101 | string s: return quote(s); 102 | JSONValue[] array: 103 | return "[" ~ [join ", " v.str for v in array] ~ "]"; 104 | (string key, JSONValue value)[] obj: 105 | return "{" ~ [join ", " quote(e.key) ~ ": " ~ e.value.str for e in obj] ~ "}"; 106 | } 107 | } 108 | 109 | string quote(string s) { 110 | mut string quoted; 111 | for (auto ch <- s) { 112 | if (ch == "\""[0]) quoted ~= "\\\""; 113 | else quoted ~= ch; 114 | } 115 | return "\"" ~ quoted ~ "\""; 116 | } 117 | 118 | (:failure | int) parseNumber(Parser parser) 119 | { 120 | parser.begin; 121 | mut bool negative = parser.accept("-"); 122 | if (parser.accept("-")) 123 | negative = true; 124 | parser.strip; 125 | if (parser.eof || !isDigit(parser.text[0])) 126 | { 127 | parser.revert; 128 | return :failure; 129 | } 130 | mut string number; 131 | while (!parser.eof && isDigit(parser.text[0])) 132 | { 133 | number ~= parser.text[0]; 134 | parser.drop(1); 135 | } 136 | parser.commit; 137 | mut int i = atoi(number); 138 | if (negative) i = -i; 139 | return i; 140 | } 141 | 142 | string parseStringLiteral(Parser parser) 143 | { 144 | mut int matchLen; 145 | auto loc = parser.loc; 146 | string start = parser.text; 147 | while (parser.text[0 .. 1] != "\"") { 148 | if (parser.text.length == 0) { 149 | parser.fail("expected end of string, got end of file"); 150 | } 151 | if (parser.text[0 .. 1] == "\\") { 152 | matchLen = matchLen + 1; 153 | parser.drop(1); 154 | } 155 | matchLen = matchLen + 1; 156 | parser.drop(1); 157 | } 158 | string str = start[0 .. matchLen]; 159 | if (!parser.accept("\"")) { 160 | parser.fail("this should never happen"); 161 | } 162 | 163 | return replaceEscapes(str); 164 | } 165 | 166 | string replaceEscapes(string text) { 167 | mut string result; 168 | mut int i; 169 | while (i < text.length) { 170 | string ch = text[i .. i + 1]; 171 | i += 1; 172 | if (ch == "\\") { 173 | string ctl = text[i .. i + 1]; 174 | i += 1; 175 | if (ctl == "\"") { 176 | result ~= "\""; 177 | } else if (ctl == "\\") { 178 | result ~= "\\"; 179 | } else { 180 | print("Unknown control sequence \\" ~ ctl); 181 | assert(false); 182 | } 183 | } else { 184 | result ~= ch; 185 | } 186 | } 187 | return result; 188 | } 189 | -------------------------------------------------------------------------------- /src/cx/parser.cx: -------------------------------------------------------------------------------- 1 | module cx.parser; 2 | 3 | import cx.base; 4 | // TODO public import 5 | import cx.parser_base; 6 | import helpers; 7 | 8 | bool isAlpha(int ch) { 9 | // TODO support full unicode 10 | if ((ch >= cast(int) "a"[0] && ch <= cast(int) "z"[0]) 11 | || (ch >= cast(int) "A"[0] && ch <= cast(int) "Z"[0])) 12 | return true; 13 | if (ch < 0x80) 14 | return false; 15 | // greek letters 16 | if (ch >= 0x0391 && ch <= 0x03c9) 17 | return true; 18 | return false; 19 | } 20 | 21 | bool isDigit(int ch) { 22 | return ch >= cast(int) "0"[0] && ch <= cast(int) "9"[0]; 23 | } 24 | 25 | (:failure | :success, int value) parseNumber(Parser parser) 26 | { 27 | parser.begin; 28 | mut bool negative = parser.accept("-"); 29 | if (parser.accept("-")) 30 | negative = true; 31 | parser.strip; 32 | if (parser.accept("0x")) 33 | { 34 | return parseHexNumber(parser, negative); 35 | } 36 | if (parser.accept("0b")) 37 | { 38 | return parseBinaryNumber(parser, negative); 39 | } 40 | if (parser.eof || !isDigit(parser.text[0])) 41 | { 42 | parser.revert; 43 | return :failure; 44 | } 45 | mut string number; 46 | while (!parser.eof && isDigit(parser.text[0])) 47 | { 48 | number ~= parser.text[0]; 49 | parser.drop(1); 50 | } 51 | parser.commit; 52 | mut int i = atoi(number); 53 | if (negative) i = -i; 54 | return (:success, i); 55 | } 56 | 57 | (:failure | :success, int value) parseHexNumber(Parser parser, bool negative) 58 | { 59 | if (parser.eof || !isHexDigit(parser.text[0 .. 1])) 60 | { 61 | parser.revert; 62 | return :failure; 63 | } 64 | mut int result; 65 | while (!parser.eof && isHexDigit(parser.text[0 .. 1])) 66 | { 67 | auto ch = parser.text[0 .. 1]; 68 | mut int digit; 69 | if (isDigit(ch[0])) digit = atoi(ch); 70 | else if (ch == "a" || ch == "A") digit = 10; 71 | else if (ch == "b" || ch == "B") digit = 11; 72 | else if (ch == "c" || ch == "C") digit = 12; 73 | else if (ch == "d" || ch == "D") digit = 13; 74 | else if (ch == "e" || ch == "E") digit = 14; 75 | else if (ch == "f" || ch == "F") digit = 15; 76 | else assert(false); 77 | result = result * 16 + digit; 78 | parser.drop(1); 79 | } 80 | parser.commit; 81 | if (negative) result = -result; 82 | return (:success, result); 83 | } 84 | 85 | bool isHexDigit(string digit) 86 | { 87 | if (isDigit(digit[0])) return true; 88 | if (digit == "a" || digit == "A") return true; 89 | if (digit == "b" || digit == "B") return true; 90 | if (digit == "c" || digit == "C") return true; 91 | if (digit == "d" || digit == "D") return true; 92 | if (digit == "e" || digit == "E") return true; 93 | if (digit == "f" || digit == "F") return true; 94 | return false; 95 | } 96 | 97 | (:failure | :success, int value) parseBinaryNumber(Parser parser, bool negative) 98 | { 99 | bool isBinaryDigit(string d) { 100 | return d == "0" || d == "1"; 101 | } 102 | if (parser.eof || !parser.text[0 .. 1].isBinaryDigit) 103 | { 104 | parser.revert; 105 | return :failure; 106 | } 107 | mut int result; 108 | while (!parser.eof && parser.text[0 .. 1].isBinaryDigit) 109 | { 110 | auto ch = parser.text[0 .. 1]; 111 | // mut int digit = if (ch == "0") 0; else 1; 112 | mut int digit; 113 | if (ch == "0") digit = 0; 114 | else if (ch == "1") digit = 1; 115 | else assert(false); 116 | result = result * 2 + digit; 117 | parser.drop(1); 118 | } 119 | parser.commit; 120 | if (negative) result = -result; 121 | return (:success, result); 122 | } 123 | 124 | (:failure | :success, float value) parseFloat(Parser parser) 125 | { 126 | parser.begin; 127 | bool negative = parser.accept("-"); 128 | parser.strip; 129 | mut string number; 130 | while (!parser.eof && isDigit(parser.text[0])) 131 | { 132 | number ~= parser.text[0]; 133 | parser.drop(1); 134 | } 135 | if (!parser.accept(".")) 136 | { 137 | parser.revert; 138 | return :failure; 139 | } 140 | number ~= "."; 141 | // 2.foo 142 | if (parser.eof || !isDigit(parser.text[0])) 143 | { 144 | parser.revert; 145 | return :failure; 146 | } 147 | while (!parser.eof && isDigit(parser.text[0])) 148 | { 149 | number ~= parser.text[0]; 150 | parser.drop(1); 151 | } 152 | parser.commit; 153 | mut float f = atof(number); 154 | if (negative) f = -f; 155 | return (:success, f); 156 | } 157 | 158 | bool isAlnum(int ch) 159 | { 160 | return isAlpha(ch) || isDigit(ch); 161 | } 162 | 163 | string parseIdentifier2(Parser parser, string allowedChars) 164 | { 165 | parser.begin; 166 | parser.strip; 167 | // identifiers starting with $ are quasiquoted (see ASTIdentifier). 168 | bool allowed(int ch) { 169 | for (char ch2 <- allowedChars) if (ch == cast(int) ch2) return true; 170 | return false; 171 | } 172 | mut string identifier = ""; 173 | string startText = parser.text; 174 | { 175 | if (parser.eof) { 176 | parser.revert; 177 | return ""; 178 | } 179 | string unichar = parser.peekUniChar; 180 | int codepoint = unichar.utf8Decode; 181 | if (!isAlpha(codepoint) 182 | && codepoint != cast(int) "_"[0] && codepoint != cast(int) "$"[0] 183 | && !allowed(codepoint)) 184 | { 185 | parser.revert; 186 | return ""; 187 | } 188 | identifier = startText[0 .. unichar.length]; 189 | parser.drop(unichar.length); 190 | } 191 | 192 | while (true) { 193 | if (parser.eof) break; 194 | string unichar = parser.peekUniChar; 195 | int codepoint = unichar.utf8Decode; 196 | if (!isAlnum(codepoint) 197 | && codepoint != cast(int) "_"[0] 198 | && !allowed(codepoint)) 199 | break; 200 | parser.drop(unichar.length); 201 | identifier = startText[0 .. identifier.length + unichar.length]; 202 | } 203 | parser.commit; 204 | return identifier; 205 | } 206 | 207 | string parseIdentifier(Parser parser) 208 | { 209 | return parseIdentifier2(parser, ""); 210 | } 211 | 212 | bool acceptIdentifier(Parser parser, string identifier) 213 | { 214 | parser.begin; 215 | 216 | string nextIdent = parseIdentifier(parser); 217 | 218 | if (nextIdent != identifier) 219 | { 220 | parser.revert; 221 | return false; 222 | } 223 | parser.commit; 224 | return true; 225 | } 226 | -------------------------------------------------------------------------------- /src/std/sha256.cx: -------------------------------------------------------------------------------- 1 | /* 2 | * FIPS 180-2 SHA-224/256/384/512 implementation 3 | * Last update: 2021-04-19 4 | * Issue date: 04/30/2005 5 | * 6 | * Copyright (C) 2013, Con Kolivas 7 | * Copyright (C) 2005, 2007 Olivier Gay 8 | * All rights reserved. 9 | * 10 | * Ported to cx by Mathis Beer 2021-04-19. Any errors are certainly mine. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 3. Neither the name of the project nor the names of its contributors 21 | * may be used to endorse or promote products derived from this software 22 | * without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 28 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 | * SUCH DAMAGE. 35 | */ 36 | module std.sha256; 37 | 38 | alias SHA256_BLOCK_SIZE = 512 / 8; 39 | 40 | final class Sha256 41 | { 42 | int[] hash; 43 | ubyte[] block; 44 | int[] k; 45 | int len; 46 | int tot_len; 47 | int[] w; 48 | int[] wv; 49 | this() { 50 | this.block = new ubyte[](2 * SHA256_BLOCK_SIZE); 51 | this.hash = [ 52 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 53 | 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, 54 | ]; 55 | this.k = [ 56 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 57 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 58 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 59 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 60 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 61 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 62 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 63 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 64 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 65 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 66 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 67 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 68 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 69 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 70 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 71 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, 72 | ]; 73 | this.w = new int[](64); 74 | this.wv = new int[](8); 75 | } 76 | 77 | void update(ubyte[] data) { 78 | int min(int a, int b) { 79 | if (a < b) return a; 80 | return b; 81 | } 82 | 83 | int tmp_len = SHA256_BLOCK_SIZE - this.len; 84 | int rem_len = min(cast(int) data.length, tmp_len); 85 | 86 | for (int i <- 0 .. rem_len) 87 | this.block[this.len + i] = data[i]; 88 | 89 | if (this.len + data.length < SHA256_BLOCK_SIZE) { 90 | this.len += cast(int) data.length; 91 | return; 92 | } 93 | 94 | int new_len = cast(int) data.length - rem_len; 95 | int block_nb = new_len / SHA256_BLOCK_SIZE; 96 | 97 | auto shifted = data[rem_len .. $]; 98 | 99 | transf(this.block, 1); 100 | transf(shifted, block_nb); 101 | 102 | int rem_len2 = new_len % SHA256_BLOCK_SIZE; 103 | 104 | for (int i <- 0 .. rem_len2) 105 | this.block[i] = shifted[(block_nb << 6) + i]; 106 | this.len = rem_len2; 107 | this.tot_len += (block_nb + 1) << 6; 108 | } 109 | 110 | void scr(int i) { 111 | w[i] = f4(w[i - 2]) + w[i - 7] + f3(w[i - 15]) + w[i - 16]; 112 | } 113 | 114 | void transf(ubyte[] data, int block_nb) { 115 | for (int i <- 0 .. block_nb) { 116 | int offset = i << 6; 117 | auto sub_block = data[offset .. offset + 64]; 118 | for (int j <- 0 .. 16) { 119 | w[j] = pack32(sub_block[j * 4 .. (j + 1) * 4]); 120 | } 121 | for (int j <- 16 .. 64) { 122 | scr(j); 123 | } 124 | for (int j <- 0 .. 8) { 125 | wv[j] = hash[j]; 126 | } 127 | for (int j <- 0 .. 64) { 128 | auto t1 = wv[7] + f2(wv[4]) + ch(wv[4], wv[5], wv[6]) + k[j] + w[j]; 129 | auto t2 = f1(wv[0]) + maj(wv[0], wv[1], wv[2]); 130 | 131 | wv[7] = wv[6]; 132 | wv[6] = wv[5]; 133 | wv[5] = wv[4]; 134 | wv[4] = wv[3] + t1; 135 | wv[3] = wv[2]; 136 | wv[2] = wv[1]; 137 | wv[1] = wv[0]; 138 | wv[0] = t1 + t2; 139 | } 140 | for (int j <- 0 .. 8) { 141 | hash[j] += wv[j]; 142 | } 143 | } 144 | } 145 | 146 | ubyte[] finalize() { 147 | mut int block_nb = 1; 148 | if ((SHA256_BLOCK_SIZE - 9) < (this.len % SHA256_BLOCK_SIZE)) block_nb += 1; 149 | 150 | int len_b = (this.tot_len + this.len) << 3; 151 | int pm_len = block_nb << 6; 152 | 153 | for (int i <- 0 .. pm_len - this.len) 154 | this.block[this.len + i] = 0; 155 | this.block[this.len] = 0x80; 156 | unpack32(len_b, this.block[pm_len - 4 .. pm_len]); 157 | transf(this.block, block_nb); 158 | 159 | ubyte[] ret = new ubyte[](32); 160 | for (int i <- 0 .. 8) 161 | unpack32(this.hash[i], ret[i * 4 .. i * 4 + 4]); 162 | return ret; 163 | } 164 | } 165 | 166 | int rotr(int x, int n) { 167 | return ((x >>> n) | (x << (32 - n))); 168 | } 169 | 170 | // TODO 171 | int invert(int i) { 172 | return -i - 1; 173 | } 174 | 175 | int xor(int a, int b) { 176 | return (a | b) & (a.invert | b.invert); 177 | } 178 | 179 | int f1(int x) { 180 | return rotr(x, 2).xor(rotr(x, 13)).xor(rotr(x, 22)); 181 | } 182 | 183 | int f2(int x) { 184 | return rotr(x, 6).xor(rotr(x, 11)).xor(rotr(x, 25)); 185 | } 186 | 187 | int f3(int x) { 188 | return rotr(x, 7).xor(rotr(x, 18)).xor(x >>> 3); 189 | } 190 | 191 | int f4(int x) { 192 | return rotr(x, 17).xor(rotr(x, 19)).xor(x >>> 10); 193 | } 194 | 195 | int ch(int x, int y, int z) { 196 | return (x & y).xor(x.invert & z); 197 | } 198 | 199 | int maj(int x, int y, int z) { 200 | return (x & y).xor(x & z).xor(y & z); 201 | } 202 | 203 | void unpack32(int i, ubyte[] target) { 204 | target[3] = cast(ubyte) i; 205 | target[2] = cast(ubyte) (i >>> 8); 206 | target[1] = cast(ubyte) (i >>> 16); 207 | target[0] = cast(ubyte) (i >>> 24); 208 | } 209 | 210 | int pack32(ubyte[] source) { 211 | return cast(int) source[3] | (cast(int) source[2] << 8) 212 | | (cast(int) source[1] << 16) | (cast(int) source[0] << 24); 213 | } 214 | -------------------------------------------------------------------------------- /demos/glfw.cx: -------------------------------------------------------------------------------- 1 | module glfw; 2 | 3 | macro import cx.macros.assert; 4 | macro import cx.macros.cimport; 5 | 6 | import c_header("GL/gl.h"); 7 | import c_header("GL/glext.h", "-include GL/gl.h -DGL_GLEXT_PROTOTYPES"); 8 | import c_header("GLFW/glfw3.h"); 9 | import c_header("SOIL.h"); 10 | import std.math; 11 | import std.math.matrix; 12 | import std.math.vector; 13 | import std.string : toStringz; 14 | 15 | struct Vertex { 16 | vec3f pos; 17 | vec3f color; 18 | vec2f texCoord; 19 | } 20 | 21 | Vertex[] vertices() { 22 | mut Vertex[] ret; 23 | float h(int x, int y) { 24 | if (x >= 4 && x <= 6 && y >= 4 && y <= 6) return 0.3; 25 | return 0; 26 | } 27 | for (mut int y = 0; y < 10; y += 1) { 28 | for (mut int x = 0; x < 10; x += 1) { 29 | auto base = vec3f(x - 5, y - 5, 0); 30 | auto a = base + vec3f(0, 0, h(x+0, y+0)); 31 | auto b = base + vec3f(0, 1, h(x+0, y+1)); 32 | auto c = base + vec3f(1, 1, h(x+1, y+1)); 33 | auto d = base + vec3f(1, 0, h(x+1, y+0)); 34 | auto n = (c - a).cross(d - b); 35 | auto l = vec3f(0.4, 1, 0.2).normal; 36 | auto angle = max(0.6, angle(n, vec3f(0, 0, 0) - l)); 37 | // vec3f col = vec3f(0.6, 0.5, 0.4); 38 | vec3f col = vec3f(1, 1, 1) * angle; 39 | ret ~= Vertex(a, col, vec2f(0, 0)); 40 | ret ~= Vertex(b, col, vec2f(0, 1)); 41 | ret ~= Vertex(c, col, vec2f(1, 1)); 42 | ret ~= Vertex(d, col, vec2f(1, 0)); 43 | } 44 | } 45 | return ret; 46 | } 47 | 48 | string vertexShader() { 49 | return "#version 330 50 | uniform mat4 MVP; 51 | attribute vec3 vPos; 52 | attribute vec3 vColor; 53 | attribute vec2 vTexPos; 54 | out vec2 texCoord; 55 | flat out vec3 color; 56 | void main() 57 | { 58 | gl_Position = MVP * vec4(vPos, 1.0); 59 | color = vColor; 60 | texCoord = vTexPos; 61 | };"; 62 | } 63 | 64 | string fragmentShader() { 65 | return "#version 330 66 | in vec2 texCoord; 67 | flat in vec3 color; 68 | uniform sampler2D tex; 69 | void main() 70 | { 71 | gl_FragColor = texture2D(tex, texCoord) * vec4(color, 1); 72 | }"; 73 | } 74 | 75 | class Shader { 76 | (:vertex | :fragment) kind; 77 | 78 | string source; 79 | 80 | this(this.kind, this.source) { } 81 | 82 | GLuint compile() { 83 | auto shaderId = glCreateShader( 84 | kind.case((:vertex): GL_VERTEX_SHADER, (:fragment): GL_FRAGMENT_SHADER)); 85 | char* shaderPtr = source.toStringz; 86 | glShaderSource(shaderId, 1, &shaderPtr, null); 87 | glCompileShader(shaderId); 88 | free(shaderPtr); 89 | 90 | int isCompiled; 91 | glGetShaderiv(shaderId, GL_COMPILE_STATUS, &isCompiled); 92 | if (!isCompiled) { 93 | int maxLength = 0; 94 | glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &maxLength); 95 | 96 | auto errorLog = new string(maxLength); 97 | glGetShaderInfoLog(shaderId, maxLength, &maxLength, errorLog.ptr); 98 | 99 | print(errorLog); 100 | assert(false); 101 | } 102 | return shaderId; 103 | } 104 | } 105 | 106 | void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) 107 | { 108 | auto window = cast(Window) glfwGetWindowUserPointer(window); 109 | 110 | window.key(key, scancode, action, mods); 111 | } 112 | 113 | class Window 114 | { 115 | GLFWwindow* handle; 116 | bool[] keyPressed_; 117 | 118 | float lastFrameTime; 119 | float delay; 120 | 121 | this() { 122 | if (!glfwInit) return; 123 | 124 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 125 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 126 | 127 | this.handle = glfwCreateWindow(640, 480, "Simple example".toStringz, null, null); 128 | if (!this.handle) { 129 | glfwTerminate; 130 | return; 131 | } 132 | glfwSetWindowUserPointer(this.handle, this); 133 | glfwSetKeyCallback(this.handle, &key_callback); 134 | 135 | glfwMakeContextCurrent(this.handle); 136 | glfwSwapInterval(1); 137 | } 138 | 139 | void key(int key, int scancode, int action, int mods) { 140 | while (key >= keyPressed_.length) keyPressed_ ~= false; 141 | if (action == GLFW_PRESS) keyPressed_[key] = true; 142 | else if (action == GLFW_RELEASE) keyPressed_[key] = false; 143 | } 144 | 145 | bool keyPressed(int key) { 146 | if (key >= keyPressed_.length) return false; 147 | return keyPressed_[key]; 148 | } 149 | 150 | bool shouldClose() { 151 | return glfwWindowShouldClose(this.handle); 152 | } 153 | 154 | (int width, int height) getFramebufferSize() { 155 | int width, height; 156 | glfwGetFramebufferSize(this.handle, &width, &height); 157 | return (width, height); 158 | } 159 | 160 | void swap() { 161 | glfwSwapBuffers(this.handle); 162 | auto time = cast(float) glfwGetTime; 163 | this.delay = time - this.lastFrameTime; 164 | this.lastFrameTime = time; 165 | } 166 | 167 | void poll() { 168 | glfwPollEvents; 169 | } 170 | 171 | void destroy() { 172 | glfwDestroyWindow(this.handle); 173 | glfwTerminate; 174 | } 175 | } 176 | 177 | class Program { 178 | Shader vertexShader, fragmentShader; 179 | 180 | GLuint handle; 181 | 182 | this(this.vertexShader, this.fragmentShader) { 183 | this.handle = glCreateProgram; 184 | handle.glAttachShader(this.vertexShader.compile); 185 | handle.glAttachShader(this.fragmentShader.compile); 186 | handle.glLinkProgram; 187 | } 188 | 189 | GLuint uniformLocation(string name) { 190 | auto ptr = name.toStringz; 191 | auto res = glGetUniformLocation(handle, ptr); 192 | free(ptr); 193 | return res; 194 | } 195 | 196 | GLuint attribLocation(string name) { 197 | auto ptr = name.toStringz; 198 | auto res = glGetAttribLocation(handle, ptr); 199 | free(ptr); 200 | return res; 201 | } 202 | } 203 | 204 | void main() { 205 | auto window = new Window; 206 | 207 | glEnable(GL_DEPTH_TEST); 208 | 209 | GLuint vertex_buffer; 210 | glGenBuffers(1, &vertex_buffer); 211 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 212 | 213 | auto vertex_shader = new Shader(:vertex, vertexShader); 214 | auto fragment_shader = new Shader(:fragment, fragmentShader); 215 | auto program = new Program(vertex_shader, fragment_shader); 216 | 217 | auto mvp_location = program.uniformLocation("MVP"); 218 | auto vpos_location = program.attribLocation("vPos"); 219 | auto vcolor_location = program.attribLocation("vColor"); 220 | auto vtexpos_location = program.attribLocation("vTexPos"); 221 | 222 | auto tex = SOIL_load_OGL_texture("some_grass_or_we.png".toStringz, 223 | SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, 224 | SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT); 225 | assert(tex != 0); 226 | 227 | glEnableVertexAttribArray(vpos_location); 228 | glVertexAttribPointer(vpos_location, 3, GL_FLOAT, false, 229 | sizeof(Vertex), null); 230 | glEnableVertexAttribArray(vcolor_location); 231 | glVertexAttribPointer(vcolor_location, 3, GL_FLOAT, false, 232 | sizeof(Vertex), cast(void*) sizeof(vec3f)); 233 | glEnableVertexAttribArray(vtexpos_location); 234 | glVertexAttribPointer(vtexpos_location, 2, GL_FLOAT, false, 235 | sizeof(Vertex), cast(void*) sizeof((vec3f, vec3f))); 236 | 237 | mut float rotate = 0; 238 | mut vec3f base = vec3f(0, 0, 0); 239 | while (!window.shouldClose) { 240 | (int width, int height) size = window.getFramebufferSize; 241 | auto ratio = size.width * 1.0 / size.height; 242 | 243 | auto vertices = vertices; 244 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.length, cast(void*) vertices.ptr, GL_STATIC_DRAW); 245 | 246 | glViewport(0, 0, size.width, size.height); 247 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 248 | 249 | auto m = mat4x4_identity.rotateX(1).rotateZ(rotate); 250 | auto p = mat4x4_ortho(-ratio * 5, ratio * 5, -5, 5, 1, -1); 251 | auto mvp = p.mul(m); 252 | 253 | glUseProgram(program.handle); 254 | glUniformMatrix4fv(mvp_location, 1, false, cast(GLfloat*) &mvp); 255 | 256 | glBindTexture(GL_TEXTURE_2D, tex); 257 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 258 | glDrawArrays(GL_QUADS, 0, cast(int) vertices.length * 4); 259 | 260 | window.swap; 261 | window.poll; 262 | if (window.keyPressed(GLFW_KEY_Q)) rotate += 1.4 * window.delay; 263 | if (window.keyPressed(GLFW_KEY_E)) rotate -= 1.4 * window.delay; 264 | } 265 | 266 | // TODO destructor 267 | window.destroy; 268 | } 269 | -------------------------------------------------------------------------------- /src/cx/vectors.cx: -------------------------------------------------------------------------------- 1 | module cx.vectors; 2 | 3 | macro import cx.macros.listcomprehension; 4 | 5 | import backend.base; 6 | import cx.base; 7 | import cx.expr; 8 | import cx.hash; 9 | import cx.parser_base; 10 | import helpers; 11 | 12 | class ASTVectorType : ASTSymbol 13 | { 14 | Loc loc; 15 | 16 | ASTSymbol elementType; 17 | 18 | int count; 19 | 20 | this(this.loc, this.elementType, this.count) { } 21 | 22 | override Symbol compile(Context context) 23 | { 24 | return new VectorType(beType(this.loc, this.elementType.compile(context)), count); 25 | } 26 | } 27 | 28 | class VectorMember : Expression 29 | { 30 | Expression base; 31 | 32 | int index; 33 | 34 | VectorType vectorType; 35 | 36 | this(this.base, this.index) 37 | { 38 | this.vectorType = this.base.type.instanceOf(VectorType); 39 | assert(!!vectorType); 40 | this.type = this.vectorType.elementType; 41 | } 42 | 43 | override int emit(Generator output) 44 | { 45 | return output.fun.field(vectorType.emit(output.platform), base.emit(output), index); 46 | } 47 | 48 | override ExprInfo info() { return base.info; } 49 | 50 | override void hash(Hash hash) 51 | { 52 | hash.adds("VectorMember"); 53 | base.hash(hash); 54 | hash.addl(index); 55 | } 56 | } 57 | 58 | class VectorMemberReference : Reference 59 | { 60 | Reference base; 61 | 62 | int index; 63 | 64 | VectorType vectorType; 65 | 66 | this(this.base, this.index) 67 | { 68 | this.vectorType = this.base.type.instanceOf(VectorType); 69 | assert(!!vectorType); 70 | this.type = this.vectorType.elementType; 71 | } 72 | 73 | override int emit(Generator output) 74 | { 75 | return output.fun.load(this.type.emit(output.platform), this.emitLocation(output)); 76 | } 77 | 78 | override int emitLocation(Generator output) 79 | { 80 | int reg = this.base.emitLocation(output); 81 | 82 | return output.fun.fieldOffset(this.base.type.emit(output.platform), reg, this.index); 83 | } 84 | 85 | override ExprInfo info() { return base.info; } 86 | 87 | override void hash(Hash hash) 88 | { 89 | hash.adds("VectorMember"); 90 | base.hash(hash); 91 | hash.addl(index); 92 | } 93 | } 94 | 95 | class VectorType : Type 96 | { 97 | Type elementType; 98 | 99 | int length; 100 | 101 | this(this.elementType, this.length) { } 102 | 103 | override BackendType emit(Platform platform) { 104 | // TODO BackendVectorType 105 | // TODO [element for 0 .. length] 106 | BackendType elementType = this.elementType.emit(platform); 107 | mut BackendType[] members; 108 | for (int i <- 0 .. length) members ~= elementType; 109 | return new BackendStructType(members); 110 | } 111 | 112 | override bool same(Type other) { 113 | // TODO 114 | // return other.instanceOf(VectorType)?.(element.same(this.element) && length == this.length); 115 | if (auto vt = other.instanceOf(VectorType)) 116 | return elementType.same(vt.elementType) && length == vt.length; 117 | return false; 118 | } 119 | 120 | override string repr() { 121 | return "Vector(" ~ elementType.repr ~ ", " ~ itoa(length) ~ ")"; 122 | } 123 | 124 | override void hash(Hash hash) { 125 | hash.adds("Vector"); 126 | elementType.hash(hash); 127 | hash.addl(length); 128 | } 129 | 130 | override Symbol accessMember(Loc loc, Context context, Expression base, string field) 131 | { 132 | // TODO part of type? third parameter? 133 | Expression do_(Expression base) { 134 | string coords1 = "uv", coords2 = "xyzw", coords3 = "rgba"; 135 | mut Expression[] members; 136 | int indexIn(char ch, string str) { 137 | return [first cast(int) i for i, ch2 in str where ch == ch2 else -1]; 138 | } 139 | for (char ch <- field) { 140 | if (ch.indexIn(coords1) != -1) 141 | members ~= new VectorMember(base, ch.indexIn(coords1)); 142 | else if (ch.indexIn(coords2) != -1) 143 | members ~= new VectorMember(base, ch.indexIn(coords2)); 144 | else if (ch.indexIn(coords3) != -1) 145 | members ~= new VectorMember(base, ch.indexIn(coords3)); 146 | else return null; 147 | } 148 | if (members.length == 1) 149 | return members[0]; 150 | return (new VectorType(elementType, cast(int) members.length)).mkVector(context, loc, members); 151 | } 152 | return context.compiler.exprWithTemporaryExpr(base, &do_); 153 | } 154 | 155 | override Expression binaryOp(Context context, string op, Expression lhs, Expression rhs, Loc loc) 156 | { 157 | auto lhsVectorType = lhs.type.instanceOf(VectorType); 158 | auto rhsVectorType = rhs.type.instanceOf(VectorType); 159 | if (lhsVectorType && rhsVectorType) { 160 | loc.assert2s(lhsVectorType.length == rhsVectorType.length, "vectors have different size"); 161 | } 162 | loc.assert2s(!!lhsVectorType, "lhs must be vector (TODO)"); 163 | if (op == "==" || op == ">" || op == "<" || op == ">=" || op == "<=") { 164 | Expression getRight1(int index) { 165 | if (rhsVectorType) return new VectorMember(rhs, index); 166 | return rhs; 167 | } 168 | mut Expression andList; 169 | for (int i <- 0 .. lhsVectorType.length) { 170 | auto leftMember = new VectorMember(lhs, i); 171 | auto rightMember = getRight1(i); 172 | auto test = context.compiler.binaryOp(op, context, leftMember, rightMember, loc); 173 | if (!andList) andList = test; 174 | else andList = new BoolAnd(andList, test); 175 | } 176 | return andList; 177 | } 178 | Expression do1(Expression lhs) { 179 | Expression do2(Expression rhs) { 180 | Expression getRight2(int index) { 181 | if (rhsVectorType) return new VectorMember(rhs, index); 182 | return rhs; 183 | } 184 | mut Expression[] members; 185 | for (int i <- 0 .. lhsVectorType.length) { 186 | auto leftMember = new VectorMember(lhs, i); 187 | auto rightMember = getRight2(i); 188 | members ~= new BinaryOp(op, leftMember, rightMember, loc); 189 | } 190 | return mkVector(context, loc, members); 191 | } 192 | return context.compiler.exprWithTemporaryExpr(rhs, &do2); 193 | } 194 | return context.compiler.exprWithTemporaryExpr(lhs, &do1); 195 | } 196 | 197 | override Expression call(Context context, Loc loc, ASTArgument[] args) 198 | { 199 | loc.assert2s(args.length == this.length || args.length == 1, "invalid number of args for vector constructor"); 200 | 201 | // TODO arg name validation 202 | auto args = [ 203 | beExpression3(context, arg.sym.compile(context), loc) 204 | for arg in args]; 205 | 206 | // TODO a better way to generically construct types backed by BackendStructType 207 | if (args.length == 1) { 208 | auto result = new PairedTemporary(this, Ownership.gifted); 209 | mut Statement initialize = new UninitializeTemporaryStatement(result); 210 | Expression do1(Expression arg) { 211 | Expression do2(Expression arg) { 212 | auto arg = expectImplicitConvertTo(context, arg, elementType, loc); 213 | for (int i <- 0 .. this.length) { 214 | auto field = new VectorMemberReference(result, i); 215 | auto stmt = context.compiler.assignStatement(field, arg); 216 | 217 | initialize = context.compiler.sequence(initialize, stmt); 218 | } 219 | return context.compiler.statementExpression(initialize, result); 220 | } 221 | return context.compiler.consume(context, arg, &do2); 222 | } 223 | return context.compiler.exprWithTemporaryExpr(args[0], &do1); 224 | } else { 225 | return mkVector(context, loc, args); 226 | } 227 | } 228 | 229 | // consumes "args" 230 | Expression mkVector(Context context, Loc loc, Expression[] args) { 231 | auto result = new PairedTemporary(this, Ownership.gifted); 232 | mut Statement initialize = new UninitializeTemporaryStatement(result); 233 | for (int i <- 0 .. this.length) { 234 | auto arg = expectImplicitConvertTo(context, args[i], elementType, loc); 235 | auto field = new VectorMemberReference(result, i); 236 | auto stmt = context.compiler.move(context, field, arg); 237 | 238 | initialize = context.compiler.sequence(initialize, stmt); 239 | } 240 | return context.compiler.statementExpression(initialize, result); 241 | } 242 | } 243 | 244 | -------------------------------------------------------------------------------- /src/cx/parser_base.cx: -------------------------------------------------------------------------------- 1 | module cx.parser_base; 2 | 3 | import backend.base; 4 | import helpers; 5 | 6 | /** 7 | * Advance row/column by the text `text`. 8 | */ 9 | StackEntry advance(mut StackEntry entry, size_t distance) { 10 | mut auto skiptext = entry.text[0 .. distance]; 11 | entry.text = entry.text[distance .. $]; 12 | 13 | mut auto nl = skiptext.find("\n"); 14 | if (nl == -1) { 15 | entry.column += cast(int) distance; 16 | return entry; 17 | } 18 | while (nl != -1) { 19 | skiptext = skiptext[nl + 1 .. $]; 20 | entry.row += 1; 21 | nl = skiptext.find("\n"); 22 | } 23 | // no more newlines found, remainder are columns 24 | entry.column = cast(int) skiptext.length; 25 | return entry; 26 | } 27 | 28 | struct StackEntry 29 | { 30 | string text; 31 | int row, column; 32 | bool stripped; 33 | } 34 | 35 | final class Parser 36 | { 37 | StackEntry[] stack; 38 | 39 | int level; 40 | 41 | string filename; 42 | 43 | string fulltext; 44 | 45 | this(this.filename, this.fulltext) 46 | { 47 | this.stack = new StackEntry[](1); 48 | this.stack[0] = StackEntry(this.fulltext, row=0, column=0, stripped=false); 49 | this.level = 0; 50 | this.verify; 51 | } 52 | 53 | Loc loc() { 54 | return Loc(this.filename, row=this.stack[this.level].row, column=this.stack[this.level].column); 55 | } 56 | 57 | void verify() 58 | { 59 | assert(this.stack.length > 0); 60 | assert(this.level < this.stack.length && this.level >= 0); 61 | if (this.stack.length > 1024) { 62 | fail("parse stack overflow"); 63 | } 64 | } 65 | 66 | string text() 67 | { 68 | if (this.stack.length == 0 || this.level >= this.stack.length) 69 | assert(false); 70 | return this.stack[this.level].text; 71 | } 72 | 73 | void begin() 74 | { 75 | this.verify; 76 | if (this.level == this.stack.length - 1) 77 | { 78 | StackEntry[] newStack = new StackEntry[](this.stack.length * 2 + 1); 79 | for (int i <- 0 .. this.stack.length) newStack[i] = this.stack[i]; 80 | this.stack = newStack; 81 | } 82 | this.stack[this.level + 1] = this.stack[this.level]; 83 | this.level = this.level + 1; 84 | } 85 | 86 | void commit() 87 | { 88 | assert(this.level > 0); 89 | this.verify; 90 | this.stack[this.level - 1] = this.stack[this.level]; 91 | this.level = this.level - 1; 92 | this.verify; 93 | } 94 | 95 | void revert() 96 | { 97 | this.verify; 98 | this.level = this.level - 1; 99 | } 100 | 101 | void drop(size_t length) 102 | { 103 | this.stack[this.level] = this.stack[this.level].advance(length); 104 | this.stack[this.level].stripped = false; 105 | } 106 | 107 | string peekUniChar() 108 | { 109 | auto text = this.text; 110 | auto len = this.text.utf8NextLength; 111 | return this.text[0 .. len]; 112 | } 113 | 114 | bool accept(string match) 115 | { 116 | this.begin; 117 | this.strip; 118 | if (this.text.length < match.length) 119 | { 120 | this.revert; 121 | return false; 122 | } 123 | if (this.text[0 .. match.length] == match) 124 | { 125 | this.drop(match.length); 126 | this.commit; 127 | return true; 128 | } 129 | this.revert; 130 | return false; 131 | } 132 | 133 | void expect(string match) 134 | { 135 | if (!this.accept(match)) 136 | { 137 | this.fail("'" ~ match ~ "' expected"); 138 | } 139 | } 140 | 141 | bool eof() 142 | { 143 | this.begin; 144 | this.strip; 145 | if (this.text.length == 0) 146 | { 147 | this.commit; 148 | return true; 149 | } 150 | this.revert; 151 | return false; 152 | } 153 | 154 | void strip() 155 | { 156 | if (this.stack[this.level].stripped) return; 157 | 158 | mut string text = this.text; 159 | while (true) 160 | { 161 | /* this.text = this.text.strip; */ 162 | while (text.length > 0 && isWhitespace(text[0])) 163 | { 164 | text = text[1 .. $]; 165 | } 166 | if (startsWith(text, "//")) 167 | { 168 | int newline = find(text, "\n"); 169 | assert(newline != -1); 170 | text = text[newline + 1 .. $]; 171 | } 172 | else 173 | { 174 | if (!startsWith(text, "/*")) 175 | { 176 | drop(this.text.length - text.length); 177 | this.stack[this.level].stripped = true; 178 | return; 179 | } 180 | text = text["/*".length .. $]; 181 | mut int commentLevel = 1; 182 | while (commentLevel > 0) 183 | { 184 | int more = find(text, "/*"); 185 | int less = find(text, "*/"); 186 | 187 | if (more == -1 && less == -1) { 188 | drop(this.text.length - text.length); 189 | this.fail("comment spans end of file"); 190 | } 191 | if (less != -1 && (more == -1 || less < more)) 192 | { 193 | text = text[less + "*/".length .. $]; 194 | commentLevel = commentLevel - 1; 195 | } 196 | if (more != -1 && (less == -1 || more < less)) 197 | { 198 | text = text[more + "/*".length .. $]; 199 | commentLevel = commentLevel + 1; 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | void assert_(bool flag, string message) 207 | { 208 | if (!flag) this.fail(message); 209 | } 210 | 211 | void fail(string message) 212 | { 213 | this.strip; 214 | this.loc.fail(message); 215 | } 216 | } 217 | 218 | bool acceptButNot(Parser parser, string match, string nomatch) { 219 | // TODO peek? 220 | parser.begin; 221 | bool fail = parser.accept(nomatch); 222 | parser.revert; 223 | return !fail && parser.accept(match); 224 | } 225 | 226 | bool isWhitespace(char c) 227 | { 228 | return c == " "[0] || c == "\t"[0] || c == "\r"[0] || c == "\n"[0]; 229 | } 230 | 231 | int utf8Decode(string ch) 232 | { 233 | assert(ch.length > 0); 234 | if (ch.length == 1) return ch[0]; 235 | if (ch.length == 2) return cast(int)(ch[0]) & 0x1f << 6 | cast(int)(ch[1]) & 0x3f; 236 | if (ch.length == 3) return cast(int)(ch[0]) & 0x0f << 12 | cast(int)(ch[1]) & 0x3f << 6 | cast(int)(ch[2]) & 0x3f; 237 | if (ch.length == 4) 238 | return cast(int)(ch[0]) & 0x07 << 18 | cast(int)(ch[1]) & 0x3f << 12 239 | | cast(int)(ch[2]) & 0x3f << 6 | cast(int)(ch[3]) & 0x3f; 240 | if (ch.length == 5) 241 | return cast(int)(ch[0]) & 0x03 << 24 | cast(int)(ch[1]) & 0x3f << 18 242 | | cast(int)(ch[2]) & 0x3f << 12 | cast(int)(ch[3]) & 0x3f << 6 | cast(int)(ch[4]) & 0x3f; 243 | return cast(int)(ch[0]) & 0x01 << 30 | cast(int)(ch[1]) & 0x3f << 24 | cast(int)(ch[2]) & 0x3f << 18 244 | | cast(int)(ch[3]) & 0x3f << 12 | cast(int)(ch[4]) & 0x3f << 6 | cast(int)(ch[5]) & 0x3f; 245 | } 246 | 247 | int utf8NextLength(string text) 248 | { 249 | // see https://en.wikipedia.org/wiki/UTF-8#FSS-UTF 250 | if (text.length < 1) return 0; 251 | int ch0 = text[0]; 252 | if (ch0 < 128) return 1; 253 | assert(ch0 >= 192); 254 | assert(text.length >= 2); 255 | if (ch0 < 224) return 2; 256 | assert(text.length >= 3); 257 | if (ch0 < 240) return 3; 258 | assert(text.length >= 4); 259 | if (ch0 < 248) return 4; 260 | assert(text.length >= 5); 261 | if (ch0 < 252) return 5; 262 | assert(text.length >= 6); 263 | if (ch0 < 254) return 6; 264 | assert(false); 265 | } 266 | 267 | bool startsWith(string haystack, string needle) 268 | { 269 | if (needle.length == 1) { 270 | return haystack.length >= 1 && haystack[0] == needle[0]; 271 | } else if (needle.length == 2) { 272 | return haystack.length >= 2 && haystack[0] == needle[0] && haystack[1] == needle[1]; 273 | } else { 274 | return haystack.length >= needle.length && haystack[0 .. needle.length] == needle; 275 | } 276 | } 277 | 278 | int find(string haystack, string needle) 279 | { 280 | if (needle.length == 1) { 281 | for (int i <- 0 .. haystack.length) { 282 | if (haystack[i] == needle[0]) return i; 283 | } 284 | } else if (needle.length == 2) { 285 | for (int i <- 0 .. haystack.length - 1) { 286 | if (haystack[i] == needle[0] && haystack[i + 1] == needle[1]) return i; 287 | } 288 | } else { 289 | for (int i <- 0 .. haystack.length - needle.length) { 290 | if (haystack[i .. i + needle.length] == needle) return i; 291 | } 292 | } 293 | return -1; 294 | } 295 | 296 | extern(C) void exit(int); 297 | 298 | struct Loc { 299 | string filename; 300 | int row, column; 301 | 302 | BackendLocation toBackendLoc() { 303 | mut BackendLocation ret; 304 | ret.file = filename; 305 | ret.line = row; 306 | ret.column = column; 307 | return ret; 308 | } 309 | 310 | string location() { 311 | return filename ~ ":" ~ itoa(row + 1) ~ ":" ~ itoa(column + 1); 312 | } 313 | 314 | void fail(string message) { 315 | .print(location ~ ": error: " ~ message); 316 | exit(1); 317 | } 318 | 319 | void assert2s(int test, string msg) { if (!test) this.fail(msg); } 320 | void assert2s2(int test, string a, string b) { if (!test) this.fail(a ~ b); } 321 | void assert2s3(int test, string a, string b, string c) { if (!test) this.fail(a ~ b ~ c); } 322 | void assert2s4(int test, string a, string b, string c, string d) { 323 | if (!test) this.fail(a ~ b ~ c ~ d); } 324 | void assert2s5(int test, string a, string b, string c, string d, string e) { 325 | if (!test) this.fail(a ~ b ~ c ~ d ~ e); } 326 | } 327 | -------------------------------------------------------------------------------- /src/cx/types.cx: -------------------------------------------------------------------------------- 1 | module cx.types; 2 | 3 | import backend.base; 4 | import cx.base; 5 | import cx.hash; 6 | import cx.parser; 7 | import cx.parser_base; 8 | import helpers; 9 | 10 | class Character : Type 11 | { 12 | BackendType type; 13 | this() { this.type = new BackendCharType; } 14 | 15 | override BackendType emit(Platform platform) { return this.type; } 16 | 17 | override bool same(Type other) 18 | { 19 | return !!other.instanceOf(Character); 20 | } 21 | 22 | override string repr() { return "char"; } 23 | 24 | override string mangle() { return "char"; } 25 | 26 | override void hash(Hash hash) { hash.adds("char"); } 27 | } 28 | 29 | class UByte : Type 30 | { 31 | BackendType type; 32 | 33 | this() { this.type = new BackendCharType; } 34 | 35 | override BackendType emit(Platform platform) { return this.type; } 36 | 37 | override bool same(Type other) 38 | { 39 | return !!other.instanceOf(UByte); 40 | } 41 | 42 | override string repr() { return "ubyte"; } 43 | 44 | override string mangle() { return "ubyte"; } 45 | 46 | override void hash(Hash hash) { hash.adds("ubyte"); } 47 | } 48 | 49 | class Integer : Type 50 | { 51 | BackendType type; 52 | this() { this.type = new BackendIntType; } 53 | 54 | override BackendType emit(Platform platform) { return this.type; } 55 | 56 | override bool same(Type other) { return !!other.instanceOf(Integer); } 57 | 58 | override string repr() { return "int"; } 59 | 60 | override string mangle() { return "int"; } 61 | 62 | override void hash(Hash hash) { hash.adds("int"); } 63 | } 64 | 65 | class Long : Type 66 | { 67 | BackendType type; 68 | this() { this.type = new BackendLongType; } 69 | 70 | override BackendType emit(Platform platform) { return this.type; } 71 | 72 | override bool same(Type other) { return !!other.instanceOf(Long); } 73 | 74 | override string repr() { return "long"; } 75 | 76 | override string mangle() { return "long"; } 77 | 78 | override void hash(Hash hash) { hash.adds("long"); } 79 | } 80 | 81 | class Short : Type 82 | { 83 | BackendType type; 84 | this() { this.type = new BackendShortType; } 85 | 86 | override BackendType emit(Platform platform) { return this.type; } 87 | 88 | override bool same(Type other) { return !!other.instanceOf(Short); } 89 | 90 | override string repr() { return "short"; } 91 | 92 | override string mangle() { return "short"; } 93 | 94 | override void hash(Hash hash) { hash.adds("short"); } 95 | } 96 | 97 | class Float : Type 98 | { 99 | BackendType type; 100 | this() { this.type = new BackendFloatType; } 101 | 102 | override BackendType emit(Platform platform) { return this.type; } 103 | 104 | override bool same(Type other) { return !!other.instanceOf(Float); } 105 | 106 | override string repr() { return "float"; } 107 | 108 | override string mangle() { return "float"; } 109 | 110 | override void hash(Hash hash) { hash.adds("float"); } 111 | } 112 | 113 | class Double : Type 114 | { 115 | BackendType type; 116 | this() { this.type = new BackendDoubleType; } 117 | 118 | override BackendType emit(Platform platform) { return this.type; } 119 | 120 | override bool same(Type other) { return !!other.instanceOf(Double); } 121 | 122 | override string repr() { return "double"; } 123 | 124 | override string mangle() { return "double"; } 125 | 126 | override void hash(Hash hash) { hash.adds("double"); } 127 | } 128 | 129 | class ASTPointer : ASTSymbol 130 | { 131 | ASTSymbol subType; 132 | 133 | Loc loc; 134 | 135 | this(this.subType, this.loc) { } 136 | 137 | override Type compile(Context context) 138 | { 139 | Type subType = beType(this.loc, this.subType.compile(context)); 140 | 141 | return new Pointer(subType); 142 | } 143 | 144 | override ASTSymbol quote(Quoter quoter) { 145 | return quoter.compilerCall("astPointer", [subType.quote(quoter)], loc); 146 | } 147 | 148 | override string repr() { return subType.repr ~ "*"; } 149 | } 150 | 151 | class ASTBasicType : ASTSymbol 152 | { 153 | string name; 154 | 155 | Loc loc; 156 | 157 | this(this.name, this.loc) { 158 | assert(name.length > 0); 159 | } 160 | 161 | override Type compile(Context context) 162 | { 163 | if (name == "int") return new Integer; 164 | if (name == "long") return new Long; 165 | if (name == "short") return new Short; 166 | if (name == "char") return new Character; 167 | if (name == "ubyte") return new UByte; 168 | if (name == "void") return new Void; 169 | if (name == "float") return new Float; 170 | if (name == "double") return new Double; 171 | 172 | this.loc.assert2s(false, this.name ~ " is not a basic type"); 173 | } 174 | 175 | override ASTSymbol quote(Quoter quoter) { 176 | return quoter.compilerCall("astBasicType", [quoter.compiler.astStringLiteral(name, loc)], loc); 177 | } 178 | 179 | override string repr() { return this.name; } 180 | } 181 | 182 | class ASTTypeof : ASTSymbol 183 | { 184 | ASTSymbol value; 185 | 186 | Loc loc; 187 | 188 | this(this.value, this.loc) { } 189 | 190 | override Type compile(Context context) 191 | { 192 | // right now, declarations in typeof() will be tracked in the stackframe and take up space, 193 | // even if they're never initialized. 194 | // TODO prevent this. 195 | auto value = beExpression2(this.value.compile(context), this.loc); 196 | return value.type; 197 | } 198 | 199 | override ASTSymbol quote(Quoter quoter) { 200 | return quoter.compilerCall("astTypeOf", [value.quote(quoter)], loc); 201 | } 202 | } 203 | 204 | // hook type parsing 205 | class TypeMacroArgs : MacroArgs 206 | { 207 | Parser parser; 208 | LexicalContext lexicalContext; 209 | ASTSymbol astType; 210 | this(this.parser, this.lexicalContext) { 211 | this.astType = null; 212 | } 213 | override bool done() { return !!this.astType; } 214 | } 215 | 216 | class ASTFunctionPointer : ASTSymbol 217 | { 218 | ASTSymbol ret; 219 | 220 | ASTSymbol[] params; 221 | 222 | Loc loc; 223 | 224 | this(this.ret, this.params, this.loc) { } 225 | 226 | override Type compile(Context context) 227 | { 228 | Type ret = beType(this.loc, this.ret.compile(context)); 229 | mut Parameter[] params; 230 | for (auto param <- this.params) 231 | { 232 | params ~= Parameter.fromType(beType(this.loc, param.compile(context))); 233 | } 234 | 235 | return new FunctionPointer(ret, params); 236 | } 237 | 238 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTFunctionPointer'!"); assert(false); } 239 | } 240 | 241 | class FunctionPointer : Type 242 | { 243 | Type ret; 244 | 245 | Parameter[] params; 246 | 247 | this(this.ret, this.params) { } 248 | 249 | override BackendType emit(Platform platform) 250 | { 251 | auto params = new BackendType[](this.params.length); 252 | for (int i <- 0 .. this.params.length) params[i] = this.params[i].type.emit(platform); 253 | return new BackendFunctionPointerType(this.ret.emit(platform), params); 254 | } 255 | 256 | override bool same(Type other) 257 | { 258 | FunctionPointer otherPtr = other.instanceOf(FunctionPointer); 259 | if (!otherPtr) return false; 260 | if (!this.ret.same(otherPtr.ret)) return false; 261 | if (this.params.length != otherPtr.params.length) return false; 262 | for (int i <- 0 .. this.params.length) 263 | if (!this.params[i].type.same(otherPtr.params[i].type)) return false; 264 | return true; 265 | } 266 | 267 | override string repr() { 268 | mut string ret = this.ret.repr ~ "("; 269 | for (int i <- 0 .. this.params.length) { 270 | if (i) ret ~= ", "; 271 | ret ~= this.params[i].type.repr; 272 | if (this.params[i].name.length > 0) 273 | ret ~= " " ~ this.params[i].name; 274 | } 275 | return ret ~ ")"; 276 | } 277 | 278 | override string mangle() { 279 | // return "fp_" ~ this.ret.repr ~ "_" ~ this.params.map!repr.join("_"); 280 | mut string ret = "fp_" ~ this.ret.repr; 281 | for (int i <- 0 .. this.params.length) 282 | ret ~= "_" ~ this.params[i].type.mangle; 283 | return ret; 284 | } 285 | 286 | override void hash(Hash hash) { 287 | hash.adds("funcptr"); 288 | ret.hash(hash); 289 | hash.addl(params.length); 290 | for (int i <- 0 .. params.length) 291 | params[i].type.hash(hash); 292 | } 293 | } 294 | 295 | class ASTNestedFunctionPointer : ASTSymbol 296 | { 297 | ASTSymbol ret; 298 | 299 | ASTSymbol[] params; 300 | 301 | Loc loc; 302 | 303 | this(this.ret, this.params, this.loc) { } 304 | 305 | override Type compile(Context context) 306 | { 307 | Type ret = beType(loc, this.ret.compile(context)); 308 | mut Type[] params; 309 | for (auto arg <- this.params) 310 | { 311 | params ~= beType(this.loc, arg.compile(context)); 312 | } 313 | 314 | return new NestedFunctionPointer(ret, params); 315 | } 316 | 317 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTNestedFunctionPointer'!"); assert(false); } 318 | } 319 | 320 | /** 321 | * { void *base; void *data; Ret(Args...) funcptr; } 322 | * base is held in reserve for when we reference count delegates 323 | */ 324 | class NestedFunctionPointer : Type 325 | { 326 | Type ret; 327 | 328 | Type[] params; 329 | 330 | this(this.ret, this.params) { } 331 | 332 | override BackendType emit(Platform platform) 333 | { 334 | auto params = new BackendType[](this.params.length + 1); 335 | params[0] = platform.voidp; 336 | for (int i <- 0 .. this.params.length) params[i + 1] = this.params[i].emit(platform); 337 | auto fp = new BackendFunctionPointerType(this.ret.emit(platform), params); 338 | return new BackendStructType([platform.voidp, platform.voidp, fp]); 339 | } 340 | 341 | override bool same(Type other) 342 | { 343 | NestedFunctionPointer otherPtr = other.instanceOf(NestedFunctionPointer); 344 | if (!otherPtr) return false; 345 | if (!this.ret.same(otherPtr.ret)) return false; 346 | if (this.params.length != otherPtr.params.length) return false; 347 | for (int i <- 0 .. this.params.length) 348 | if (!this.params[i].same(otherPtr.params[i])) return false; 349 | return true; 350 | } 351 | 352 | override string repr() { 353 | mut string ret = this.ret.repr ~ " delegate("; 354 | for (int i <- 0 .. this.params.length) { 355 | if (i) ret ~= ", "; 356 | ret ~= this.params[i].repr; 357 | } 358 | return ret ~ ")"; 359 | } 360 | 361 | override string mangle() { 362 | // return "fp_" ~ this.ret.repr ~ "_" ~ this.params.map!repr.join("_"); 363 | mut string ret = "dg_" ~ this.ret.repr; 364 | for (auto param <- this.params) 365 | ret ~= "_" ~ param.mangle; 366 | return ret; 367 | } 368 | 369 | override void hash(Hash hash) { 370 | hash.adds("dgptr"); 371 | ret.hash(hash); 372 | hash.addl(params.length); 373 | for (auto param <- this.params) 374 | param.hash(hash); 375 | } 376 | } 377 | 378 | Type nativeWordType(Platform platform) 379 | { 380 | BackendType type = platform.nativeWordType; 381 | if (type.instanceOf(BackendIntType)) return new Integer; 382 | if (type.instanceOf(BackendLongType)) return new Long; 383 | assert(false); 384 | } 385 | -------------------------------------------------------------------------------- /src/cx/macros/assert.cx: -------------------------------------------------------------------------------- 1 | module cx.macros.assert; 2 | 3 | macro import cx.macros.quasiquoting; 4 | 5 | import package(compiler).cx.array; 6 | import package(compiler).cx.base; 7 | import package(compiler).cx.expr; 8 | import package(compiler).cx.parser_base; 9 | import package(compiler).cx.statements; 10 | import package(compiler).cx.types; 11 | import package(compiler).helpers; 12 | 13 | class ASTAssertion : ASTStatement 14 | { 15 | ASTSymbol test; 16 | 17 | string exprText; 18 | 19 | Loc loc; 20 | 21 | this(this.test, this.exprText, this.loc) { } 22 | 23 | Expression symbolTest(Context context, ASTSymbol sym) { 24 | auto context = context.withNamespace(context.compiler.exprAlias( 25 | context.namespace, "identName", context.compiler.stringLiteral(repr(sym)))); 26 | 27 | return ( 28 | context.compiler.$expr identName ~ " = " ~ itoa(cast(int) $sym) 29 | ).compile(context).instanceOf(Expression); 30 | } 31 | 32 | Expression binopTest(mut Context context, ASTBinaryOp binop) { 33 | void set(string name, Symbol value) { 34 | context = context.withNamespace( 35 | context.compiler.exprAlias(context.namespace, name, value)); 36 | } 37 | string leftStr = repr(binop.left), rightStr = repr(binop.right); 38 | set("leftStr", context.compiler.stringLiteral(leftStr)); 39 | set("rightStr", context.compiler.stringLiteral(rightStr)); 40 | auto 41 | left = binop.left.compile(context).instanceOf(Expression), 42 | right = binop.right.compile(context).instanceOf(Expression); 43 | if (!left || !right) return null; 44 | set("left", left); 45 | set("right", right); 46 | 47 | if (binop.op == "&&") { 48 | auto leftRecurse = dispatch(context, binop.left); 49 | auto rightRecurse = dispatch(context, binop.right); 50 | if (!leftRecurse || !rightRecurse) return null; 51 | set("leftRecurse", leftRecurse); 52 | set("rightRecurse", rightRecurse); 53 | 54 | return (context.compiler.$expr ({ 55 | mut string ret; 56 | if (!left) ret = leftStr ~ " failed, because " ~ leftRecurse; 57 | else ret = rightStr ~ " failed, because " ~ rightRecurse; 58 | ret; 59 | })).compile(context).instanceOf(Expression); 60 | } 61 | if (binop.op == "||") { 62 | auto leftRecurse = dispatch(context, binop.left); 63 | auto rightRecurse = dispatch(context, binop.right); 64 | if (!leftRecurse || !rightRecurse) return null; 65 | set("leftRecurse", leftRecurse); 66 | set("rightRecurse", rightRecurse); 67 | 68 | return (context.compiler.$expr ({ 69 | mut string ret; 70 | if (left) ret = rightStr ~ " failed, because " ~ rightRecurse; 71 | else if (right) ret = leftStr ~ " failed, because " ~ leftRecurse; 72 | else ret = leftStr ~ " failed (because " ~ leftRecurse ~ ") and " 73 | ~ rightStr ~ " failed (because " ~ rightRecurse ~ ")"; 74 | ret; 75 | })).compile(context).instanceOf(Expression); 76 | } 77 | bool isString(Type type) { 78 | if (auto arr = type.instanceOf(Array)) 79 | return !!arr.elementType.instanceOf(Character); 80 | return false; 81 | } 82 | bool isIntCastable(Type type) { 83 | return type.instanceOf(Integer) || type.instanceOf(Long) 84 | || type.instanceOf(Character) || type.instanceOf(Short); 85 | } 86 | if (left.type.isIntCastable && right.type.isIntCastable) { 87 | if (right.instanceOf(IntLiteral)) { 88 | return ( 89 | context.compiler.$expr leftStr ~ " = " ~ itoa(cast(int) left) 90 | ).compile(context).instanceOf(Expression); 91 | } 92 | return ( 93 | context.compiler.$expr 94 | leftStr ~ " = " ~ itoa(cast(int) left) 95 | ~ " and " ~ rightStr ~ " = " ~ itoa(cast(int) right) 96 | ).compile(context).instanceOf(Expression); 97 | } 98 | if (left.type.isString && right.type.isString) { 99 | if (right.instanceOf(StringLiteral)) { 100 | return ( 101 | context.compiler.$expr leftStr ~ " = \"" ~ left ~ "\"" 102 | ).compile(context).instanceOf(Expression); 103 | } 104 | return ( 105 | context.compiler.$expr 106 | leftStr ~ " = \"" ~ left ~ "\"" 107 | ~ " and " ~ rightStr ~ " = \"" ~ right ~ "\"" 108 | ).compile(context).instanceOf(Expression); 109 | } 110 | return null; 111 | } 112 | 113 | Expression negTest(mut Context context, ASTNegation astNegation) 114 | { 115 | void set(string name, Symbol value) { 116 | context = context.withNamespace( 117 | context.compiler.exprAlias(context.namespace, name, value)); 118 | } 119 | 120 | auto nextRecurse = dispatch(context, astNegation.next); 121 | if (!nextRecurse) return null; 122 | set("nextRecurse", nextRecurse); 123 | string nextStr = repr(astNegation.next); 124 | set("nextStr", context.compiler.stringLiteral(nextStr)); 125 | 126 | return ( 127 | context.compiler.$expr nextRecurse ~ " was true" 128 | ).compile(context).instanceOf(Expression); 129 | } 130 | 131 | Expression dispatch(Context context, ASTSymbol sym) { 132 | if (auto ident = sym.instanceOf(ASTIdentifier)) 133 | { 134 | return symbolTest(context, ident); 135 | } 136 | if (auto member = sym.instanceOf(ASTMemberBase)) 137 | { 138 | return symbolTest(context, member); 139 | } 140 | if (auto binop = sym.instanceOf(ASTBinaryOp)) 141 | { 142 | return binopTest(context, binop); 143 | } 144 | if (auto neg = sym.instanceOf(ASTNegation)) 145 | { 146 | return negTest(context, neg); 147 | } 148 | return null; 149 | } 150 | 151 | override StatementCompileResult compile(Context context) 152 | { 153 | mut Context context = context; 154 | auto printFun = new FunctionDeclaration( 155 | "print", new Void, [Parameter(false, "str", false, new Array(new Character))]); 156 | auto assertFun = new FunctionDeclaration( 157 | "assert", new Void, [Parameter(false, "test", false, new Integer)]); 158 | auto itoaFun = new FunctionDeclaration( 159 | "cxruntime_itoa", new Array(new Character), [Parameter(false, "value", false, new Integer)]); 160 | 161 | context = context.withNamespace(context.compiler.exprAlias( 162 | context.namespace, "print", printFun)); 163 | context = context.withNamespace(context.compiler.exprAlias( 164 | context.namespace, "assert", assertFun)); 165 | context = context.withNamespace(context.compiler.exprAlias( 166 | context.namespace, "itoa", itoaFun)); 167 | string failedMsg = loc.location() ~ ": assertion failed: " ~ exprText ~ ", because "; 168 | auto ifFailed = dispatch(context, test); 169 | if (ifFailed) { 170 | context = context.withNamespace(context.compiler.exprAlias( 171 | context.namespace, "ifFailed", ifFailed)); 172 | context = context.withNamespace(context.compiler.exprAlias( 173 | context.namespace, "failedMsg", context.compiler.stringLiteral(failedMsg))); 174 | auto assertTest = context.compiler.$stmt { 175 | if (!$test) { 176 | print(failedMsg ~ ifFailed); 177 | assert(false); 178 | } 179 | }; 180 | auto assertTestStmt = assertTest.compile(context); 181 | return StatementCompileResult(assertTestStmt.statement, context); 182 | } 183 | 184 | // fallback: old impl 185 | // TODO quasiquote this 186 | auto test = this.test.compile(context); 187 | auto expr = beExpression2(test, this.loc); 188 | auto texpr = context.compiler.truthy(context, expr, this.loc); 189 | auto nexpr = context.compiler.binaryOp( 190 | "==", context, texpr, context.compiler.intLiteral(0), this.loc); 191 | 192 | Expression failStr = context.compiler.binaryOp( 193 | "~", context, 194 | context.compiler.stringLiteral(this.loc.location()), 195 | context.compiler.stringLiteral(": '" ~ this.exprText ~ "' was false"), 196 | this.loc); 197 | Expression false_ = context.compiler.intLiteral(0); 198 | Statement printSt = context.compiler.exprStatement( 199 | context.compiler.plainCall(context, printFun, [failStr], this.loc, false)); 200 | Statement failSt = context.compiler.exprStatement( 201 | context.compiler.plainCall(context, assertFun, [false_], this.loc, false)); 202 | // `if (!$expression) { print($assertLocStr ~ ": '" ~ $exprText ~ "' was false."); assert(false); }` 203 | return StatementCompileResult( 204 | context.compiler.ifStatement( 205 | nexpr, 206 | context.compiler.sequenceStatement([printSt, failSt]), 207 | null), 208 | context); 209 | } 210 | 211 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTAssertion'!"); assert(false); } 212 | } 213 | 214 | string repr(ASTSymbol expr) { 215 | assert(!!expr); 216 | if (auto ident = expr.instanceOf(ASTIdentifier)) { 217 | return ident.name; 218 | } 219 | if (auto neg = expr.instanceOf(ASTNegation)) { 220 | return "!" ~ repr(neg.next); 221 | } 222 | if (auto member = expr.instanceOf(ASTMemberBase)) { 223 | return repr(member.base) ~ "." ~ member.member; 224 | } 225 | if (auto binop = expr.instanceOf(ASTBinaryOp)) { 226 | return "(" ~ repr(binop.left) ~ " " ~ binop.op ~ " " ~ repr(binop.right) ~ ")"; 227 | } 228 | if (auto intl = expr.instanceOf(ASTIntLiteral)) { 229 | return itoa(intl.value); 230 | } 231 | if (auto str = expr.instanceOf(ASTStringLiteral)) { 232 | return "\"" ~ str.text ~ "\""; 233 | } 234 | return "TODO"; 235 | } 236 | 237 | class ParseAssert : Macro 238 | { 239 | this() { } 240 | override void apply(MacroArgs args) { 241 | auto args = args.instanceOf(ParseStatementArgs); 242 | if (args) { 243 | args.statement = this.parse(args.parser, args.lexicalContext); 244 | } 245 | } 246 | 247 | ASTStatement parse(Parser parser, LexicalContext context) 248 | { 249 | parser.begin(); 250 | parser.strip(); 251 | auto assertLoc = parser.loc(); 252 | if (!parser.accept("assert")) 253 | { 254 | parser.revert(); 255 | return null; 256 | } 257 | if (!parser.accept("(")) 258 | { 259 | parser.revert(); 260 | return null; 261 | } 262 | parser.commit(); 263 | auto exprStartLoc = parser.loc; 264 | auto exprStart = parser.text; 265 | auto expression = context.compiler.parseExpression(parser, context); 266 | auto exprEnd = parser.text; 267 | auto exprText = exprStart[0 .. exprStart.length - exprEnd.length]; 268 | 269 | parser.expect(")"); 270 | parser.expect(";"); 271 | 272 | return new ASTAssertion(expression, exprText, exprStartLoc); 273 | } 274 | } 275 | 276 | void assertMacro(MacroState macroState) 277 | { 278 | macroState.addMacro(new ParseAssert); 279 | } 280 | 281 | macro(assertMacro); 282 | -------------------------------------------------------------------------------- /src/cx/tuples.cx: -------------------------------------------------------------------------------- 1 | module cx.tuples; 2 | 3 | macro import cx.macros.listcomprehension; 4 | macro import cx.macros.quasiquoting; 5 | 6 | import backend.base; 7 | import cx.base; 8 | import cx.hash; 9 | import cx.parser_base; 10 | import helpers; 11 | 12 | Expression makeTuple(Context context, Loc loc, TupleType type, Expression[] fields) { 13 | assert(type.members.length == fields.length); 14 | Statement do_(Reference target) { 15 | mut Statement init; 16 | for (int i <- 0 .. fields.length) { 17 | init = context.compiler.sequence(init, 18 | context.compiler.move(context, new TupleMemberRef(loc, target, i), fields[i])); 19 | } 20 | return init; 21 | } 22 | return context.compiler.exprWithScratchspace(type, true, &do_); 23 | } 24 | 25 | class ASTTupleExpr : ASTSymbol 26 | { 27 | Loc loc; 28 | 29 | ASTSymbol[] members; 30 | 31 | this(this.loc, this.members) { } 32 | 33 | override Expression compile(Context context) { 34 | Expression[] fields = [member.compile(context).beExpressionImplCall(context, loc) for member in members]; 35 | auto type = new TupleType([("", member.type) for member in fields]); 36 | 37 | return makeTuple(context, this.loc, type, fields); 38 | } 39 | 40 | override ASTSymbol quote(Quoter quoter) { 41 | print("cannot quote 'ASTTupleExpr'!"); 42 | assert(false); 43 | } 44 | } 45 | 46 | ASTSymbol parseTupleExpression(Parser parser, LexicalContext lexicalContext) 47 | { 48 | parser.begin; 49 | auto loc = parser.loc; 50 | if (!parser.accept("(")) { 51 | parser.revert; 52 | return null; 53 | } 54 | mut ASTSymbol[] members; 55 | while (!parser.accept(")")) 56 | { 57 | if (members.length && !parser.accept(",")) { 58 | // be a bit lenient for (bla bla) 59 | if (members.length == 1) { 60 | parser.revert; 61 | return null; 62 | } 63 | auto loc = parser.loc; 64 | loc.assert2s(false, "tuple: ',' expected"); 65 | } 66 | auto member = lexicalContext.compiler.parseExpression(parser, lexicalContext); 67 | if (!member) { 68 | // (bla bla) still 69 | if (members.length <= 1) { 70 | parser.revert; 71 | return null; 72 | } 73 | // FIXME parser.loc.assert2s 74 | auto loc = parser.loc; 75 | loc.assert2s(false, "tuple: member expected"); 76 | } 77 | members ~= member; 78 | } 79 | // (bla) is not a tuple, it's a paren expression 80 | if (members.length == 1) { 81 | parser.revert; 82 | return null; 83 | } 84 | parser.commit; 85 | return new ASTTupleExpr(loc, members); 86 | } 87 | 88 | class TupleMemberExpr : Expression 89 | { 90 | Loc loc; 91 | 92 | Expression tuple; 93 | 94 | int index; 95 | 96 | this(this.loc, this.tuple, this.index) { 97 | if (auto tuple = this.tuple.type.instanceOf(TupleType)) { 98 | loc.assert2s(this.index >= 0 && this.index < tuple.members.length, "tuple index out of range"); 99 | this.type = tuple.members[index].type; 100 | } else { 101 | // This should never happen! 102 | // TODO index overload on types 103 | loc.assert2s(false, "tuple index on non-tuple type"); 104 | } 105 | } 106 | 107 | override int emit(Generator output) { 108 | // TODO free unused fields 109 | auto tuple = this.tuple.emit(output); 110 | return output.fun.field(this.tuple.type.emit(output.platform), tuple, index); 111 | } 112 | override ExprInfo info() { 113 | return tuple.info; 114 | } 115 | override void hash(Hash hash) { 116 | hash.adds("tuple_member"); 117 | hash.addl(index); 118 | tuple.hash(hash); 119 | } 120 | } 121 | 122 | // TODO merge with TupleMemberExpression somehow... mixin? static if (is(T == Reference))? 123 | class TupleMemberRef : Reference 124 | { 125 | Loc loc; 126 | 127 | Reference tuple; 128 | 129 | int index; 130 | 131 | this(this.loc, this.tuple, this.index) { 132 | // FIXME we should check this earlier 133 | if (auto tuple = this.tuple.type.instanceOf(TupleType)) { 134 | loc.assert2s(this.index >= 0 && this.index < tuple.members.length, "tuple index out of range"); 135 | this.type = tuple.members[index].type; 136 | } else { 137 | // This should never happen! 138 | // TODO index overload on types 139 | loc.assert2s(false, "tuple index on non-tuple type"); 140 | } 141 | } 142 | 143 | override int emit(Generator output) { 144 | // TODO free unused fields 145 | auto tuple = this.tuple.emit(output); 146 | return output.fun.field(this.tuple.type.emit(output.platform), tuple, index); 147 | } 148 | override ExprInfo info() { 149 | return tuple.info; 150 | } 151 | override int emitLocation(Generator output) { 152 | auto tupleLocation = this.tuple.instanceOf(Reference).emitLocation(output); 153 | return output.fun.fieldOffset(this.tuple.type.emit(output.platform), tupleLocation, index); 154 | } 155 | override void hash(Hash hash) { 156 | hash.adds("tuple_member_ref"); 157 | hash.addl(index); 158 | tuple.hash(hash); 159 | } 160 | } 161 | 162 | class TupleType : Type 163 | { 164 | (string name, Type type)[] members; 165 | 166 | this(this.members) { } 167 | 168 | override BackendType emit(Platform platform) { 169 | return new BackendStructType([member.type.emit(platform) for member in members]); 170 | } 171 | 172 | override bool same(Type type) { 173 | if (auto other = type.instanceOf(TupleType)) { 174 | return other.members.length == members.length && 175 | [all member.type.same(other.members[i].type) for i, member in members]; 176 | } 177 | return false; 178 | } 179 | 180 | override string repr() { 181 | return "(" ~ [join ", " member.type.repr for member in members] ~ ")"; 182 | } 183 | 184 | override string mangle() { 185 | return "tuple_" ~ [join "_" member.type.mangle for member in members]; 186 | } 187 | 188 | override Symbol accessMember(Loc loc, Context context, Expression base, string field) 189 | { 190 | if (!base) return null; 191 | int index = [first cast(int) i for i, member in members where member.name == field else -1]; 192 | // TODO return (Symbol | LookupError); 193 | if (index == -1) return null; 194 | // loc.assert2s(index != -1, "no such tuple field: " ~ field); 195 | if (auto ref_ = base.instanceOf(Reference)) { 196 | return new TupleMemberRef(loc, ref_, index); 197 | } 198 | return new TupleMemberExpr(loc, base, index); 199 | } 200 | 201 | // basically just like struct 202 | override Statement copyInto(Context context, Reference target, Expression source) 203 | { 204 | Statement do1(Expression targetptr) { 205 | auto target = context.compiler.dereference(targetptr); 206 | Statement do2(Expression source) { 207 | mut Statement[] assigns; 208 | mut bool anySpecialAssignments; 209 | for (int i <- 0 .. members.length) { 210 | auto targetField = new TupleMemberRef(__HERE__, target, i); 211 | auto sourceField = new TupleMemberExpr(__HERE__, source, i); 212 | mut auto copyInto = members[i].type.copyInto(context, targetField, sourceField); 213 | if (copyInto) anySpecialAssignments = true; 214 | if (!copyInto) copyInto = context.compiler.assignStatement(targetField, sourceField); 215 | assigns ~= copyInto; 216 | } 217 | if (!anySpecialAssignments) return null; 218 | return context.compiler.sequenceStatement(assigns); 219 | } 220 | return context.compiler.stmtWithTemporaryExpr(source, &do2); 221 | } 222 | return context.compiler.stmtWithTemporaryExpr(context.compiler.reference(target), &do1); 223 | } 224 | 225 | // also like struct 226 | override Statement endLifetime(Context context, Reference ref_) 227 | { 228 | auto compiler = context.compiler; 229 | 230 | Statement doRef(Expression refptr) { 231 | auto context = context.withNamespace( 232 | compiler.exprAlias(context.namespace, "ref_", compiler.dereference(refptr))); 233 | 234 | mut ASTStatement[] destructors; 235 | for (int i <- 0 .. members.length) { 236 | auto member = new TupleMemberRef(__HERE__, ref_, i); 237 | auto destroy = members[i].type.endLifetime(context, member); 238 | auto index = compiler.astIntLiteral(i, __HERE__); 239 | if (destroy) { 240 | destructors ~= compiler.$stmt __destroy(ref_[$index]);; 241 | } 242 | } 243 | if (!destructors.length) return null; 244 | ASTStatement body_ = compiler.astSequence(destructors, __HERE__); 245 | 246 | return body_.compile(context).statement; 247 | } 248 | return compiler.stmtWithTemporaryExpr(compiler.reference(ref_), &doRef); 249 | } 250 | 251 | override Expression implicitConvertTo(Context context, Expression source, Type target) 252 | { 253 | auto tupleTargetType = target.instanceOf(TupleType); 254 | if (!tupleTargetType || tupleTargetType.members.length != members.length) 255 | return null; 256 | 257 | Expression do_(Expression source) { 258 | auto sourceMembers = [new TupleMemberExpr(__HERE__, source, cast(int) i) for i, _ in members]; 259 | auto convFields = [ 260 | context.compiler.implicitConvertTo(context, member, tupleTargetType.members[i].type) 261 | for i, member in sourceMembers]; 262 | 263 | if ([any !a for a in convFields]) return null; 264 | 265 | return makeTuple(context, __HERE__, tupleTargetType, convFields); 266 | } 267 | return context.compiler.exprWithTemporaryExpr(source, &do_); 268 | } 269 | 270 | override void hash(Hash hash) { 271 | hash.adds("tuple"); 272 | hash.addl(this.members.length); 273 | [member.type.hash(hash) for member in this.members]; 274 | } 275 | } 276 | 277 | /** 278 | * Tuple with pending member type 279 | */ 280 | class PendingTuple : PendingSymbol 281 | { 282 | Loc loc; 283 | 284 | (string name, (PendingSymbol | Type) sym)[] members; 285 | 286 | this(this.loc, this.members) { } 287 | 288 | override Symbol resolve() { 289 | mut (string name, Type)[] result; 290 | for (int i <- 0 .. members.length) { 291 | string name = members[i].name; 292 | auto sym = members[i].sym; 293 | result ~= (name, sym.case( 294 | Type t: t, 295 | PendingSymbol s: beType(this.loc, s.resolve) 296 | )); 297 | } 298 | return new TupleType(result); 299 | } 300 | } 301 | 302 | class ASTTupleType : ASTSymbol 303 | { 304 | Loc loc; 305 | 306 | (string name, ASTSymbol type)[] members; 307 | 308 | this(this.loc, this.members) { } 309 | 310 | override Symbol compile(Context context) { 311 | Symbol[] syms = [member.type.compile(context) for member in this.members]; 312 | if ([any s.instanceOf(PendingSymbol) for s in syms]) { 313 | mut (string name, (PendingSymbol | Type) sym)[] result; 314 | for (int i <- 0 .. this.members.length) { 315 | if (auto ps = syms[i].instanceOf(PendingSymbol)) 316 | result ~= (members[i].name, ps); 317 | else 318 | result ~= (members[i].name, beType(this.loc, syms[i])); 319 | } 320 | return new PendingTuple(this.loc, result); 321 | } 322 | return new TupleType([ 323 | (member.name, beType(this.loc, syms[i])) 324 | for i, member in this.members]); 325 | } 326 | 327 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTTupleType'!"); assert(false); } 328 | } 329 | -------------------------------------------------------------------------------- /src/runtime.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #ifdef __GLIBC__ 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct String 14 | { 15 | size_t length; 16 | char *ptr; 17 | void *base; 18 | }; 19 | 20 | struct StringArray 21 | { 22 | size_t length; 23 | struct String *ptr; 24 | void *base; 25 | }; 26 | 27 | struct String string_alloc(size_t length) { 28 | void *memory = malloc(sizeof(size_t) * 3 + length); 29 | ((size_t*) memory)[0] = 1; // references 30 | ((size_t*) memory)[1] = length; // capacity 31 | ((size_t*) memory)[2] = length; // used 32 | return (struct String) { length, memory + sizeof(size_t) * 3, memory }; 33 | } 34 | 35 | void print(struct String str) { printf("%.*s\n", (int) str.length, str.ptr); } 36 | void assert(int test) { 37 | if (!test) { 38 | fprintf(stderr, "Assertion failed! Aborting.\n"); 39 | exit(1); 40 | } 41 | } 42 | int cxruntime_ptr_test(void* ptr) { return !!ptr; } 43 | int _arraycmp(void* a, void* b, size_t la, size_t lb, size_t sz) { 44 | if (la != lb) return 0; 45 | return memcmp(a, b, la * sz) == 0; 46 | } 47 | char* toStringz(struct String str) { 48 | char *buffer = malloc(str.length + 1); 49 | strncpy(buffer, str.ptr, str.length); 50 | buffer[str.length] = 0; 51 | return buffer; 52 | } 53 | int cxruntime_atoi(struct String str) { 54 | char *temp = toStringz(str); 55 | int res = atoi(temp); 56 | free(temp); 57 | // printf("atoi(%.*s) = %i\n", str.length, str.ptr, res); 58 | return res; 59 | } 60 | float cxruntime_atof(struct String str) { 61 | char *temp = toStringz(str); 62 | float res = atof(temp); 63 | free(temp); 64 | // printf("atof(%.*s) = %f\n", str.length, str.ptr, res); 65 | return res; 66 | } 67 | struct String cxruntime_itoa(int i) { 68 | int len = snprintf(NULL, 0, "%i", i); 69 | struct String res = string_alloc(len + 1); 70 | res.length = snprintf(res.ptr, res.length, "%i", i); 71 | return res; 72 | } 73 | struct String cxruntime_ltoa(long long l) { 74 | int len = snprintf(NULL, 0, "%lld", l); 75 | struct String res = string_alloc(len + 1); 76 | res.length = snprintf(res.ptr, res.length, "%lld", l); 77 | return res; 78 | } 79 | struct String cxruntime_ftoa(float f) { 80 | int len = snprintf(NULL, 0, "%f", f); 81 | struct String res = string_alloc(len + 1); 82 | res.length = snprintf(res.ptr, res.length, "%f", f); 83 | return res; 84 | } 85 | struct String cxruntime_ftoa_hex(float f) { 86 | double d = f; 87 | int len = snprintf(NULL, 0, "%llx", *(long long int*) &d); 88 | struct String res = string_alloc(len + 1); 89 | res.length = snprintf(res.ptr, res.length, "%llx", *(long long int*) &d); 90 | return res; 91 | } 92 | struct String cxruntime_ptr_id(void* ptr) { 93 | int len = snprintf(NULL, 0, "%p", ptr); 94 | struct String res = string_alloc(len + 1); 95 | res.length = snprintf(res.ptr, res.length, "%p", ptr); 96 | return res; 97 | } 98 | int cxruntime_toInt(float f) { return (int) f; } 99 | 100 | FILE* cxruntime_stdout() { 101 | return stdout; 102 | } 103 | 104 | void cxruntime_system(struct String command) { 105 | char *cmd = toStringz(command); 106 | int ret = system(cmd); 107 | if (ret != 0) fprintf(stderr, "command failed with %i\n", ret); 108 | assert(ret == 0); 109 | free(cmd); 110 | } 111 | 112 | int cxruntime_execbg(struct String command, struct StringArray arguments) { 113 | int ret = fork(); 114 | if (ret != 0) return ret; 115 | char *cmd = toStringz(command); 116 | char **args = malloc(sizeof(char*) * (arguments.length + 2)); 117 | args[0] = cmd; 118 | for (int i = 0; i < arguments.length; i++) { 119 | args[1 + i] = toStringz(arguments.ptr[i]); 120 | } 121 | args[1 + arguments.length] = NULL; 122 | return execvp(cmd, args); 123 | } 124 | 125 | bool cxruntime_waitpid(int pid) { 126 | int wstatus; 127 | int ret = waitpid(pid, &wstatus, 0); 128 | if (ret == -1) fprintf(stderr, "waitpid() failed: %s\n", strerror(errno)); 129 | return WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0; 130 | } 131 | 132 | // No idea why this is necessary. 133 | __attribute__((optnone)) 134 | __attribute__((optimize(0))) 135 | bool cxruntime_symbol_defined_in_main(struct String symbol) { 136 | // even if a DL is loaded with RTLD_GLOBAL, main symbols are special. 137 | // so we want to avoid redefining symbols that are in the main program. 138 | void *main = dlopen(NULL, RTLD_LAZY); 139 | char *symbolPtr = toStringz(symbol); 140 | void *sym = dlsym(main, symbolPtr); 141 | free(symbolPtr); 142 | dlclose(main); 143 | return sym ? true : false; 144 | } 145 | 146 | void cxruntime_dlcall(struct String dlfile, struct String fun, void* arg) { 147 | void *handle = dlopen(toStringz(dlfile), RTLD_LAZY | RTLD_GLOBAL); 148 | if (!handle) fprintf(stderr, "can't open %.*s - %s\n", (int) dlfile.length, dlfile.ptr, dlerror()); 149 | assert(!!handle); 150 | void *sym = dlsym(handle, toStringz(fun)); 151 | if (!sym) fprintf(stderr, "can't load symbol '%.*s'\n", (int) fun.length, fun.ptr); 152 | assert(!!sym); 153 | 154 | ((void(*)(void*)) sym)(arg); 155 | } 156 | 157 | void *cxruntime_alloc(size_t size) { 158 | return calloc(1, size); 159 | } 160 | 161 | extern void _run_unittests(); 162 | 163 | #ifndef CX_NO_MAIN 164 | extern void _main(struct StringArray args); 165 | #endif 166 | 167 | int main(int argc, char **argv) { 168 | struct StringArray args = (struct StringArray) { 169 | argc, 170 | malloc(sizeof(struct String) * argc) 171 | }; 172 | for (int i = 0; i < argc; i++) { 173 | args.ptr[i] = (struct String) { strlen(argv[i]), argv[i] }; 174 | } 175 | _run_unittests(); 176 | #ifndef CX_NO_MAIN 177 | _main(args); 178 | #endif 179 | free(args.ptr); 180 | return 0; 181 | } 182 | 183 | // 184 | // fnv hash 185 | // 186 | 187 | typedef long long int FNVState; 188 | 189 | void *fnv_init() 190 | { 191 | void *ret = malloc(sizeof(FNVState)); 192 | *(long long int*) ret = 14695981039346656037UL; // offset basis 193 | return ret; 194 | } 195 | 196 | void fnv_add_string(void *state, struct String s) 197 | { 198 | #define HASH (*(long long int*) state) 199 | for (int i = 0; i < s.length; i++) { 200 | HASH = HASH ^ s.ptr[i]; 201 | HASH = HASH * 1099511628211; 202 | } 203 | #undef HASH 204 | } 205 | 206 | void fnv_add_long(void *state, long long int value) 207 | { 208 | #define HASH (*(long long int*) state) 209 | for (int i = 0; i < sizeof(long long int); i++) { 210 | HASH = HASH ^ (value & 0xff); 211 | HASH = HASH * 1099511628211; 212 | value >>= 8; 213 | } 214 | #undef HASH 215 | } 216 | 217 | struct String fnv_hex_value(void *state) 218 | { 219 | char *ptr = malloc(sizeof(FNVState) * 2 + 1); 220 | snprintf(ptr, sizeof(FNVState) + 1, "%.*llX", (int) sizeof(FNVState), *(long long int*) state); 221 | return (struct String) { .length = sizeof(FNVState), .ptr = ptr }; 222 | } 223 | 224 | // 225 | // polynomial hash 226 | // 227 | 228 | #define PRIME 1099511628211 229 | 230 | typedef struct { 231 | long long add, mult; 232 | } PolyHashState; 233 | 234 | PolyHashState *poly_init() 235 | { 236 | // hopefully copied from fnv 237 | PolyHashState *result = malloc(sizeof(PolyHashState)); 238 | *result = (PolyHashState) { 239 | .add = 14695981039346656037UL, // offset basis 240 | .mult = 1, 241 | }; 242 | return result; 243 | } 244 | 245 | void poly_apply_hash(PolyHashState *left, PolyHashState *right) 246 | { 247 | left->add = left->add * right->mult + right->add; 248 | left->mult *= right->mult; 249 | } 250 | 251 | PolyHashState poly_hash_string(struct String s) 252 | { 253 | // in a polynomial hash, we apply a string by computing h * p^(s.length) + ((s[0]*p + s[1])*p + s[2])*p... 254 | // iow, h * p^(s.length) + s[0] * p^(s.length - 1) + s[1] * p^(s.length - 2) + ... 255 | // p^(s.length) can be efficiently determined by counting along 256 | PolyHashState result = (PolyHashState) { .add = 0, .mult = 1 }; 257 | // INVERSE index cause we're counting up factors 258 | for (size_t i = 0; i < s.length; i++) { 259 | result.add += s.ptr[s.length - 1 - i] * result.mult; 260 | result.mult *= PRIME; 261 | } 262 | return result; 263 | } 264 | 265 | void poly_add_string(PolyHashState *state, struct String s) 266 | { 267 | PolyHashState right = poly_hash_string(s); 268 | poly_apply_hash(state, &right); 269 | } 270 | 271 | PolyHashState poly_hash_long(long long int value) 272 | { 273 | PolyHashState result = (PolyHashState) { .add = 0, .mult = 1 }; 274 | for (size_t i = 0; i < sizeof(long long int); i++) { 275 | result.add += ((value >> (8 * i)) & 0xff) * result.mult; 276 | result.mult *= PRIME; 277 | } 278 | return result; 279 | } 280 | 281 | void poly_add_long(void *state, long long int value) 282 | { 283 | PolyHashState right = poly_hash_long(value); 284 | poly_apply_hash(state, &right); 285 | } 286 | 287 | #undef PRIME 288 | 289 | struct String poly_hex_value(PolyHashState *state) 290 | { 291 | struct String ret = string_alloc(sizeof(state->add) * 2 + 1); 292 | ret.length = snprintf(ret.ptr, ret.length, "%.*llX", (int) sizeof(state->add), state->add); 293 | return ret; 294 | } 295 | 296 | long long int poly_hash_whole_string(struct String s) 297 | { 298 | PolyHashState state = { 299 | .add = 14695981039346656037UL, // offset basis 300 | .mult = 1, 301 | }; 302 | poly_add_string(&state, s); 303 | return state.add; 304 | } 305 | 306 | // for debug breaks 307 | void debug() { } 308 | 309 | void print_backtrace() 310 | { 311 | #ifdef __GLIBC__ 312 | void *array[20]; 313 | int size = backtrace(array, 20); 314 | char **strings = backtrace_symbols(array, size); 315 | if (strings != NULL) { 316 | printf("Backtrace:\n"); 317 | for (int i = 0; i < size; i++) 318 | printf(" %i: %s\n", i, strings[i]); 319 | } 320 | free(strings); 321 | #endif 322 | } 323 | 324 | void cxruntime_refcount_violation(struct String s, long long int *ptr) 325 | { 326 | printf("<%.*s: refcount logic violated: %lld at %p\n", (int) s.length, s.ptr, *ptr, ptr); 327 | print_backtrace(); 328 | } 329 | 330 | void cxruntime_refcount_inc(struct String s, long long int *ptr) 331 | { 332 | long long int result = __atomic_add_fetch(ptr, 1, __ATOMIC_RELEASE); 333 | if (result <= 1) { 334 | cxruntime_refcount_violation(s, ptr); 335 | } 336 | } 337 | 338 | int cxruntime_refcount_dec(struct String s, long long int *ptr) 339 | { 340 | long long int result = __atomic_sub_fetch(ptr, 1, __ATOMIC_ACQUIRE); 341 | if (result <= -1) 342 | { 343 | cxruntime_refcount_violation(s, ptr); 344 | } 345 | 346 | return result == 0; 347 | } 348 | 349 | struct CacheEntry 350 | { 351 | void* ptr; 352 | void(*free)(void*); 353 | }; 354 | 355 | struct Cache 356 | { 357 | size_t length; 358 | struct CacheEntry *entries; 359 | }; 360 | 361 | __thread struct Cache cxruntime_cache = {0}; 362 | 363 | int cxruntime_cache_isset(int key) 364 | { 365 | if (key >= cxruntime_cache.length) 366 | return false; 367 | return cxruntime_cache.entries[key].ptr != NULL; 368 | } 369 | 370 | void *cxruntime_cache_get(int key) 371 | { 372 | return cxruntime_cache.entries[key].ptr; 373 | } 374 | 375 | void cxruntime_cache_clear() 376 | { 377 | for (int i = 0; i < cxruntime_cache.length; i++) { 378 | struct CacheEntry entry = cxruntime_cache.entries[i]; 379 | if (entry.ptr != NULL) { 380 | entry.free(entry.ptr); 381 | } 382 | } 383 | free(cxruntime_cache.entries); 384 | cxruntime_cache.entries = NULL; 385 | cxruntime_cache.length = 0; 386 | } 387 | 388 | int cxruntime_errno() { return errno; } 389 | 390 | void cxruntime_cache_set(int key, void *ptr, void(*free)(void*)) 391 | { 392 | assert(ptr != NULL); 393 | if (key >= cxruntime_cache.length) { 394 | size_t oldlen = cxruntime_cache.length; 395 | size_t newlen = key + 1; 396 | cxruntime_cache.entries = realloc(cxruntime_cache.entries, sizeof(struct CacheEntry) * newlen); 397 | memset(cxruntime_cache.entries + oldlen, 0, sizeof(struct CacheEntry) * (newlen - oldlen)); 398 | cxruntime_cache.length = newlen; 399 | } 400 | cxruntime_cache.entries[key] = (struct CacheEntry) { .ptr = ptr, .free = free }; 401 | } 402 | -------------------------------------------------------------------------------- /src/backend/base.cx: -------------------------------------------------------------------------------- 1 | module backend.base; 2 | 3 | import helpers; 4 | 5 | abstract class BackendFunction 6 | { 7 | NamedReg[] namedRegs; 8 | 9 | abstract int arg(int index) { assert(false); } 10 | abstract int shortLiteral(int value) { assert(false); } 11 | abstract int byteLiteral(int value) { assert(false); } 12 | abstract int intLiteral(long value) { assert(false); } 13 | abstract int longLiteral(long value) { assert(false); } 14 | abstract int wordLiteral(Platform platform, long value) { assert(false); } 15 | abstract int floatLiteral(float value) { assert(false); } 16 | abstract int stringLiteral(string text) { assert(false); } 17 | abstract int voidLiteral() { assert(false); } 18 | abstract int zeroLiteral(BackendType type) { assert(false); } 19 | abstract int structLiteral(BackendType struct_, int[] regs) { assert(false); } 20 | abstract int symbolList(string name) { assert(false); } 21 | abstract int binop(string op, BackendType type, int left, int right) { assert(false); } 22 | abstract int bitcast(int from, BackendType to) { assert(false); } 23 | abstract int zeroExtend(int value, BackendType to) { assert(false); } 24 | abstract int signExtend(int value, BackendType to) { assert(false); } 25 | abstract int trunc(int value, BackendType to) { assert(false); } 26 | // TODO roll into bitcast 27 | abstract int intToFloat(int reg) { assert(false); } 28 | abstract int floatToInt(int reg) { assert(false); } 29 | abstract int call(BackendType ret, string name, int[] args) { assert(false); } 30 | abstract int getFuncPtr(string name) { assert(false); } 31 | abstract int callFuncPtr(BackendType type, int reg, int[] args) { assert(false); } 32 | abstract int load(BackendType backendType, int reg) { assert(false); } 33 | // replacement for the previously used savestack/alloca/restorestack dance 34 | // return an alloca pointer, but it's the same pointer every time we pass over this instr 35 | abstract int staticAlloca(BackendType backendType) { assert(false); } 36 | abstract int field(BackendType backendType, int reg, int index) { assert(false); } 37 | abstract int fieldOffset(BackendType backendType, int reg, size_t index) { assert(false); } 38 | abstract int ptrOffset(BackendType backendType, int ptrReg, int offsetReg) { assert(false); } 39 | abstract void store(BackendType backendType, int target_reg, int value_reg) { assert(false); } 40 | abstract void ret(int reg) { assert(false); } 41 | abstract void branch(string label) { assert(false); } 42 | abstract void testBranch(int reg, string thenLabel, string elseLabel) { assert(false); } 43 | abstract void setLabel(string label) { assert(false); } 44 | abstract string getLabel() { assert(false); } 45 | // call when the function is finished 46 | void done() { } 47 | 48 | bool hasNamedReg(string name) { 49 | for (mut int i = 0; i < namedRegs.length; i += 1) 50 | if (namedRegs[i].name == name) return true; 51 | return false; 52 | } 53 | 54 | int getNamedReg(string name) { 55 | for (mut int i = 0; i < namedRegs.length; i += 1) 56 | if (namedRegs[i].name == name) return namedRegs[i].value; 57 | assert(false); 58 | } 59 | 60 | void setNamedReg(string name, int value) { 61 | assert(!hasNamedReg(name)); 62 | namedRegs ~= NamedReg(name, value); 63 | } 64 | 65 | bool isBooleanOp(string op) { 66 | return op == "==" || op == "!=" || op == "<" || op == ">" || op == "<=" || op == ">="; 67 | } 68 | } 69 | 70 | struct NamedReg 71 | { 72 | string name; 73 | int value; 74 | } 75 | 76 | // compilation unit 77 | abstract class BackendModule 78 | { 79 | string[] alreadySeen; 80 | 81 | abstract void declare(string name, BackendType ret, BackendType[] params) { assert(false); } 82 | abstract bool declared(string name) { assert(false); } 83 | abstract BackendFunction define( 84 | string name, string decoration, BackendType ret, BackendType[] params, BackendLocation loc) 85 | { 86 | assert(false); 87 | } 88 | abstract void declareSymbolList(string name, size_t length) { assert(false); } 89 | abstract void defineSymbolList(string name, string[] symbols) { assert(false); } 90 | 91 | // allow to only emit a symbol once per module 92 | // TODO is this still necessary with declared()? 93 | bool once(string identifier) 94 | { 95 | // TODO use a hash 96 | for (mut int i = 0; i < this.alreadySeen.length; i += 1) 97 | if (this.alreadySeen[i] == identifier) 98 | return false; 99 | this.alreadySeen ~= identifier; 100 | return true; 101 | } 102 | 103 | // called before emitting, or whatever you do with it 104 | void done() { } 105 | } 106 | 107 | struct BackendLocation 108 | { 109 | string file; 110 | int line; 111 | int column; 112 | } 113 | 114 | abstract class Backend 115 | { 116 | abstract BackendModule createModule(Platform platform, BackendLocation loc, bool forMacro) { assert(false); } 117 | } 118 | 119 | struct PlatformFlags 120 | { 121 | } 122 | 123 | class Platform 124 | { 125 | BackendType nativeWordType; 126 | 127 | PlatformFlags platformFlags; 128 | 129 | this(this.nativeWordType, this.platformFlags) { 130 | this.void_ = new BackendVoidType; 131 | this.voidp = new BackendPointerType(this.void_); 132 | } 133 | // cached type instances 134 | // TODO remove for once 135 | BackendType void_; 136 | BackendType voidp; 137 | } 138 | 139 | abstract class BackendType 140 | { 141 | abstract string repr() { assert(false); } 142 | abstract int size(Platform platform) { assert(false); } 143 | abstract int alignment(Platform platform) { assert(false); } 144 | abstract bool same(BackendType other) { assert(false); } 145 | } 146 | 147 | class BackendLongType : BackendType 148 | { 149 | this() { } 150 | override string repr() { return "long"; } 151 | override int size(Platform platform) { return 8; } 152 | override int alignment(Platform platform) { return 8; } 153 | override bool same(BackendType other) { return !!other.instanceOf(BackendLongType); } 154 | } 155 | 156 | class BackendIntType : BackendType 157 | { 158 | this() { } 159 | override string repr() { return "int"; } 160 | override int size(Platform platform) { return 4; } 161 | override int alignment(Platform platform) { return 4; } 162 | override bool same(BackendType other) { return !!other.instanceOf(BackendIntType); } 163 | } 164 | 165 | class BackendShortType : BackendType 166 | { 167 | this() { } 168 | override string repr() { return "short"; } 169 | override int size(Platform platform) { return 2; } 170 | override int alignment(Platform platform) { return 2; } 171 | override bool same(BackendType other) { return !!other.instanceOf(BackendShortType); } 172 | } 173 | 174 | class BackendCharType : BackendType 175 | { 176 | this() { } 177 | override string repr() { return "char"; } 178 | override int size(Platform platform) { return 1; } 179 | override int alignment(Platform platform) { return 1; } 180 | override bool same(BackendType other) { return !!other.instanceOf(BackendCharType); } 181 | } 182 | 183 | class BackendVoidType : BackendType 184 | { 185 | this() { } 186 | override string repr() { return "void"; } 187 | override int size(Platform platform) { return 0; } 188 | override int alignment(Platform platform) { return 1; } 189 | override bool same(BackendType other) { return !!other.instanceOf(BackendVoidType); } 190 | } 191 | 192 | class BackendFloatType : BackendType 193 | { 194 | this() { } 195 | override string repr() { return "float"; } 196 | override int size(Platform platform) { return 4; } 197 | override int alignment(Platform platform) { return 4; } 198 | override bool same(BackendType other) { return !!other.instanceOf(BackendFloatType); } 199 | } 200 | 201 | class BackendDoubleType : BackendType 202 | { 203 | this() { } 204 | override string repr() { return "double"; } 205 | override int size(Platform platform) { return 8; } 206 | override int alignment(Platform platform) { return 8; } 207 | override bool same(BackendType other) { return !!other.instanceOf(BackendDoubleType); } 208 | } 209 | 210 | class BackendPointerType : BackendType 211 | { 212 | BackendType target; 213 | 214 | this(BackendType target) { this.target = target; } 215 | 216 | override string repr() { return this.target.repr ~ "*"; } 217 | override int size(Platform platform) { return platform.nativeWordType.size(platform); } 218 | override int alignment(Platform platform) { return platform.nativeWordType.size(platform); } 219 | override bool same(BackendType other) { 220 | auto otherPtr = other.instanceOf(BackendPointerType); 221 | return otherPtr && this.target.same(otherPtr.target); 222 | } 223 | } 224 | 225 | class BackendFunctionPointerType : BackendType 226 | { 227 | BackendType ret; 228 | BackendType[] params; 229 | 230 | this(BackendType ret, BackendType[] params) { 231 | this.ret = ret; 232 | this.params = params; 233 | } 234 | 235 | override string repr() { return "TODO fp"; } 236 | override int size(Platform platform) { return platform.nativeWordType.size(platform); } 237 | override int alignment(Platform platform) { return platform.nativeWordType.size(platform); } 238 | override bool same(BackendType other) { 239 | auto otherFp = other.instanceOf(BackendFunctionPointerType); 240 | if (!otherFp || !this.ret.same(otherFp.ret) || this.params.length != otherFp.params.length) 241 | return false; 242 | for (mut int i = 0; i < this.params.length; i += 1) 243 | if (!this.params[i].same(otherFp.params[i])) return false; 244 | return true; 245 | } 246 | } 247 | 248 | class BackendStructType : BackendType 249 | { 250 | BackendType[] members; 251 | 252 | this(BackendType[] members) { this.members = members; } 253 | 254 | override string repr() { return "TODO struct"; } 255 | 256 | override int size(Platform platform) { 257 | // TODO destructuring by unnamed variable 258 | (int size, int alignment) pair = calcPrefix(platform, this.members); 259 | 260 | return roundToNext(pair.size, pair.alignment); 261 | } 262 | 263 | override int alignment(Platform platform) { 264 | (int size, int alignment) pair = calcPrefix(platform, this.members); 265 | 266 | return pair.alignment; 267 | } 268 | 269 | override bool same(BackendType other) { 270 | auto otherStr = other.instanceOf(BackendStructType); 271 | if (!otherStr || this.members.length != otherStr.members.length) 272 | return false; 273 | for (mut int i = 0; i < this.members.length; i += 1) 274 | if (!this.members[i].same(otherStr.members[i])) return false; 275 | return true; 276 | } 277 | } 278 | 279 | class BackendSpacerType : BackendType 280 | { 281 | int size_; 282 | int alignment_; 283 | 284 | this(int size, int alignment) { this.size_ = size; this.alignment_ = alignment; } 285 | 286 | override string repr() { return "TODO spacer"; } 287 | override int size(Platform platform) { return this.size_; } 288 | override int alignment(Platform platform) { return this.alignment_; } 289 | override bool same(BackendType other) { 290 | auto otherSpacer = other.instanceOf(BackendSpacerType); 291 | return otherSpacer && this.size_ == otherSpacer.size_ && this.alignment_ == otherSpacer.alignment_; 292 | } 293 | } 294 | 295 | class BackendStaticArrayType : BackendType 296 | { 297 | BackendType element; 298 | int length; 299 | 300 | this(BackendType element, int length) { this.element = element; this.length = length; } 301 | 302 | override string repr() { return element.repr ~ "[" ~ itoa(length) ~ "]"; } 303 | override int size(Platform platform) { return element.size(platform) * length; } 304 | override int alignment(Platform platform) { return this.element.alignment(platform); } 305 | override bool same(BackendType other) { 306 | auto otherSA = other.instanceOf(BackendStaticArrayType); 307 | return otherSA && this.length == otherSA.length && this.element.same(otherSA.element); 308 | } 309 | } 310 | 311 | (int size, int alignment) calcPrefix(Platform platform, BackendType[] members) 312 | { 313 | mut int structSize = 0; 314 | mut int structAlign = 1; 315 | for (mut int i = 0; i < members.length; i += 1) { 316 | auto member = members[i]; 317 | int alignment = member.alignment(platform); 318 | int size = member.size(platform); 319 | // round to next 320 | structSize = roundToNext(structSize, alignment) + size; 321 | if (alignment > structAlign) structAlign = alignment; 322 | } 323 | return (structSize, structAlign); 324 | } 325 | 326 | int roundToNext(mut int size, int alignment) 327 | { 328 | size = size + alignment - 1; 329 | // alignment is a power of two, so alignment - 1 is a mask 330 | // size -= size % alignment; 331 | size = size - (size & (alignment - 1)); 332 | return size; 333 | } 334 | -------------------------------------------------------------------------------- /src/cx/macros/listcomprehension.cx: -------------------------------------------------------------------------------- 1 | module cx.macros.listcomprehension; 2 | 3 | macro import cx.macros.quasiquoting; 4 | 5 | import package(compiler).cx.array; 6 | import package(compiler).cx.base; 7 | import package(compiler).cx.either; 8 | import package(compiler).cx.parser; 9 | import package(compiler).cx.parser_base; 10 | import package(compiler).cx.statements; 11 | import package(compiler).helpers; 12 | 13 | class ASTListComprehension : ASTSymbol 14 | { 15 | string iterationMode; // "", "any", "all", "first", "join", "count", "sum", "min", "max" 16 | 17 | ASTSymbol expr; 18 | 19 | ASTSymbol default_; 20 | 21 | ASTSymbol joinKey; 22 | 23 | string iname; // iteration variable 24 | 25 | string varname; 26 | 27 | ASTSymbol source; 28 | 29 | ASTSymbol where; 30 | 31 | Loc loc; 32 | 33 | this(this.iterationMode, this.expr, this.default_, this.joinKey, 34 | this.iname, this.varname, this.source, this.where, this.loc) 35 | { 36 | if (!iname.length) iname = "__i"; 37 | } 38 | 39 | override Symbol compile(Context context) { 40 | auto compiler = context.compiler; 41 | 42 | auto sourceType = beExpression3(context, source.compile(context), loc).type; 43 | loc.assert2s(!!sourceType.instanceOf(Array), "expected array for source of list comprehension"); 44 | 45 | auto i = compiler.astIdentifier(iname, loc); 46 | 47 | if (iterationMode == "count") { 48 | loc.assert2s(!expr, "no expr for count"); 49 | mut auto test = compiler.$expr true; 50 | if (where) test = compiler.$expr $where && $test; 51 | return (compiler.$expr ({ 52 | auto __source = $source; 53 | mut int __count = 0; 54 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 55 | auto $varname = __source[$i]; 56 | if ($test) __count += 1; 57 | } 58 | __count; 59 | })).compile(context); 60 | } 61 | 62 | auto astType = compiler.$type typeof(({ auto $varname = $source[0]; size_t $iname = 0; $expr; })); 63 | auto type = astType.compile(context); 64 | 65 | if (type.instanceOf(Void)) { 66 | loc.assert2s(iterationMode == "", "non-void expression expected"); 67 | assert(!default_); 68 | if (where) { 69 | return (compiler.$expr ({ 70 | auto __source = $source; 71 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 72 | auto $varname = __source[$i]; 73 | if ($where) $expr; 74 | } 75 | 0; 76 | })).compile(context); 77 | } 78 | return (compiler.$expr ({ 79 | auto __source = $source; 80 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 81 | auto $varname = __source[$i]; 82 | $expr; 83 | } 84 | 0; 85 | })).compile(context); 86 | } else if (iterationMode == "any") { 87 | assert(!default_); 88 | mut auto test = expr; 89 | if (where) test = compiler.$expr $where && $test; 90 | return (compiler.$expr ({ 91 | auto __source = $source; 92 | mut bool __result = false; 93 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 94 | auto $varname = __source[$i]; 95 | if ($test) { __result = true; break; } 96 | } 97 | __result; 98 | })).compile(context); 99 | } else if (iterationMode == "all") { 100 | assert(!default_); 101 | mut auto test = compiler.$expr !$expr; 102 | if (where) test = compiler.$expr $where && $test; 103 | return (compiler.$expr ({ 104 | auto __source = $source; 105 | mut bool __result = true; 106 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 107 | auto $varname = __source[$i]; 108 | if ($test) { __result = false; break; } 109 | } 110 | __result; 111 | })).compile(context); 112 | } else if (iterationMode == "join") { 113 | assert(!default_); 114 | mut auto test = compiler.$expr true; 115 | if (where) test = compiler.$expr $where && $test; 116 | return (compiler.$expr ({ 117 | auto __source = $source; 118 | mut string __result; 119 | mut bool __first = true; 120 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 121 | auto $varname = __source[$i]; 122 | if ($test) { 123 | if (__first) { 124 | __first = false; 125 | } else { 126 | __result ~= $joinKey; 127 | } 128 | __result ~= $expr; 129 | } 130 | } 131 | __result; 132 | })).compile(context); 133 | } else if (iterationMode == "first") { 134 | assert(!!default_); 135 | mut auto test = compiler.$expr true; 136 | if (where) test = where; 137 | return (compiler.$expr ({ 138 | auto __source = $source; 139 | mut auto __found = false; 140 | mut $astType __result; 141 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 142 | auto $varname = __source[$i]; 143 | __result = $expr; 144 | if ($test) { __found = true; break; } 145 | } 146 | // TODO for (...) { } finally { } 147 | if (!__found) __result = $default_; 148 | __result; 149 | })).compile(context); 150 | } else if (iterationMode == "sum") { 151 | assert(!default_); 152 | mut auto test = compiler.$expr true; 153 | if (where) test = where; 154 | return (compiler.$expr ({ 155 | auto __source = $source; 156 | mut $astType __sum = 0; 157 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 158 | auto $varname = __source[$i]; 159 | if ($test) { __sum += $expr; } 160 | } 161 | __sum; 162 | })).compile(context); 163 | } else if (iterationMode == "min") { 164 | mut auto test = compiler.$expr true; 165 | if (where) test = where; 166 | mut ASTSymbol init; 167 | if (default_) { 168 | init = default_; 169 | } else { 170 | init = compiler.$expr ({ assert(false); $astType __unreachable; __unreachable; }); 171 | } 172 | return (compiler.$expr ({ 173 | auto __source = $source; 174 | mut (:initial | $astType) __min = :initial; 175 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 176 | auto $varname = __source[$i]; 177 | if ($test) { 178 | auto __value = $expr; 179 | __min.case { 180 | (:initial): __min = __value; 181 | $astType current: { 182 | if (__value < current) 183 | __min = __value; 184 | } 185 | } 186 | } 187 | } 188 | __min.case( 189 | (:initial): $init, 190 | $astType v: v); 191 | })).compile(context); 192 | } else if (iterationMode == "max") { 193 | mut auto test = compiler.$expr true; 194 | if (where) test = where; 195 | mut ASTSymbol init; 196 | if (default_) { 197 | init = default_; 198 | } else { 199 | init = compiler.$expr ({ assert(false); $astType __unreachable; __unreachable; }); 200 | } 201 | return (compiler.$expr ({ 202 | auto __source = $source; 203 | mut (:initial | $astType) __max = :initial; 204 | for (mut size_t $iname = 0; $i < __source.length; $i += 1) { 205 | auto $varname = __source[$i]; 206 | if ($test) { 207 | auto __value = $expr; 208 | __max.case { 209 | (:initial): __max = __value; 210 | $astType current: { 211 | if (__value > current) 212 | __max = __value; 213 | } 214 | } 215 | } 216 | } 217 | __max.case( 218 | (:initial): $init, 219 | $astType v: v); 220 | })).compile(context); 221 | } else { 222 | assert(iterationMode == ""); 223 | if (where) { 224 | return (compiler.$expr ({ 225 | auto __source = $source; 226 | mut $astType[] __result; 227 | for (mut auto $iname = 0; $i < __source.length; $i += 1) { 228 | auto $varname = __source[$i]; 229 | if ($where) __result ~= $expr; 230 | } 231 | __result; 232 | })).compile(context); 233 | } 234 | return (compiler.$expr ({ 235 | auto __source = $source; 236 | auto __result = new $astType[](__source.length); 237 | for (mut auto $iname = 0; $i < __source.length; $i += 1) { 238 | auto $varname = __source[$i]; 239 | __result[$i] = $expr; 240 | } 241 | __result; 242 | })).compile(context); 243 | } 244 | } 245 | 246 | override ASTSymbol quote(Quoter quoter) { print("cannot quote 'ASTListComprehension'"); assert(false); } 247 | } 248 | 249 | class ListComprehension : Macro 250 | { 251 | this() { } 252 | override void apply(MacroArgs args) { 253 | auto args = args.instanceOf(ParseExpressionBaseArgs); 254 | if (args) { 255 | args.symbol = this.parse(args.parser, args.lexicalContext); 256 | } 257 | } 258 | 259 | ASTSymbol parse(Parser parser, LexicalContext lexicalContext) 260 | { 261 | auto compiler = lexicalContext.compiler; 262 | auto loc = parser.loc(); 263 | 264 | parser.begin(); 265 | if (!parser.accept("[")) { 266 | parser.revert(); 267 | return null; 268 | } 269 | mut string iterationMode; 270 | mut ASTSymbol default_; 271 | mut ASTSymbol joinKey; 272 | if (acceptIdentifier(parser, "any")) iterationMode = "any"; 273 | else if (acceptIdentifier(parser, "all")) iterationMode = "all"; 274 | else if (acceptIdentifier(parser, "first")) iterationMode = "first"; 275 | else if (acceptIdentifier(parser, "join")) { 276 | iterationMode = "join"; 277 | joinKey = compiler.parseExpression(parser, lexicalContext); 278 | auto loc = parser.loc; 279 | loc.assert2s(!!joinKey, "join key expected"); 280 | } 281 | else if (acceptIdentifier(parser, "count")) { 282 | iterationMode = "count"; 283 | } 284 | else if (acceptIdentifier(parser, "sum")) { 285 | iterationMode = "sum"; 286 | } 287 | else if (acceptIdentifier(parser, "min")) iterationMode = "min"; 288 | else if (acceptIdentifier(parser, "max")) iterationMode = "max"; 289 | mut ASTSymbol expr; 290 | // count is expressionless 291 | if (iterationMode != "count") { 292 | expr = compiler.parseExpression(parser, lexicalContext); 293 | if (!expr) { 294 | parser.revert(); 295 | return null; 296 | } 297 | if (!acceptIdentifier(parser, "for")) { 298 | parser.revert(); 299 | return null; 300 | } 301 | } 302 | mut string iname; 303 | mut string varname = parseIdentifier(parser); 304 | if (!varname.length) { 305 | parser.fail("variable name expected"); 306 | } 307 | if (parser.accept(",")) { 308 | iname = varname; 309 | varname = parseIdentifier(parser); 310 | if (!varname.length) { 311 | parser.fail("variable name expected"); 312 | } 313 | } 314 | parser.expect("in"); 315 | auto source = compiler.parseExpression(parser, lexicalContext); 316 | if (!source) { 317 | parser.fail("source expression expected"); 318 | } 319 | mut ASTSymbol where; 320 | if (acceptIdentifier(parser, "where")) { 321 | where = compiler.parseExpression(parser, lexicalContext); 322 | if (!where) { 323 | parser.fail("where expression expected"); 324 | } 325 | } 326 | if (iterationMode == "first") { 327 | if (!acceptIdentifier(parser, "else")) 328 | parser.fail("'else' expected"); 329 | default_ = compiler.parseExpression(parser, lexicalContext); 330 | } 331 | else if (iterationMode == "min" || iterationMode == "max") { 332 | if (acceptIdentifier(parser, "base")) 333 | default_ = compiler.parseExpression(parser, lexicalContext); 334 | } 335 | parser.expect("]"); 336 | parser.commit(); 337 | return new ASTListComprehension(iterationMode, expr, default_, joinKey, iname, varname, source, where, loc); 338 | } 339 | } 340 | 341 | void addListComprehensionMacro(MacroState macroState) 342 | { 343 | macroState.addMacro(new ListComprehension); 344 | } 345 | 346 | macro(addListComprehensionMacro); 347 | --------------------------------------------------------------------------------