Looks like a decent start, but not enough there to really judge it yet. Best of luck, I'll keep an eye on it! If you'd like feedback (ignore this post if not):
Caseless switch is a great idea, especially no-fallthrough by default. Consider extending it with substituion, eg "case > 7:", "case >= 13 && < 16:", or "switch longvariablename as N { case N > 13 && N < 16:". Return types after function parameters are nice, consider saying "function foo(params) returns type" instead.
Tuples always feel important but in practice I tend to not like them. Variable definitions are a bit clunky, and inconsistent with struct variable definitions. Consider ctors/dtors and/or inline member initialization. I would recommend not to compress keywords: func -> function, const -> constant, etc. "type name struct" would feel better and more consistent as "class name". The C. prefix feels like it'd be great if you only use a very small number of C APIs, and very annoying if you used a lot of them.
The boldest and coolest part is building this directly on LLVM. My thought was always to transform the IL into C (trying my best to preserve line numbers and variable names), and then let GCC or Clang build that.
My ideas were unified function call syntax (never any functions inside classes), subclasses (declare a class inside another class, but the child class has direct access to the parent class' scope), namespace-aware multi-line macros (trailing \ is grotesque), and if implementing a backend instead of a translator, zero undefined behaviors in the language (do what people expect on eg signed integer overflow instead of GCC doing whatever is fastest yet obviously wrong. Aim for hardware that is actually used today instead of worrying about hypothetical PDP-11 programs, so no 9-bit chars.)
I've put some thought into macros into my various cross-assemblers. One thing that's important is to have different types of macros, and different types of argument values (some that are evaluated for you, some that are intentionally not evaluated.)
macro function square(integer value) {
return value * value;
}
integer x = square(foo() + bar()); //foo() and bar() each only called once
//square(integer) will exist as an exported function symbol
macro inline twice(statement operation) {
operation;
operation;
}
twice(foo()); //invokes foo() two times
//twice() will not generate an actual function
macro class vector(type T) {
T* pool;
integer capacity;
integer size;
function append(type U) { ... }
...
}
vector(integer) powersOfTwo{2, 4, 8, 16, 32, 64};
//any vector(type) will generate all the class functions for it
> I think if you emit C code, it is much harder to get good debug information, which is important.
I think you can annotate the generated code with certain preprocessor instructions to tell the compiler about the real source lines in order to get proper debug information. I think lex/yacc or flex/bison use that. Don't know if it's a gcc extension.