General
- Built-in build system. Use a more restrictive version of C as a "scripting language" (whatever is required to allow it to compile instantaneously), it compiles into a temporary program that runs inside the compiler, essentially it's only purpose is to set up the compiler settings and some global variables, maybe copy some files around. I'm fine with using a separate build script for personal use, but as soon as I have to interact with someone else's code and build processes, I just want to throw all of computers into the trash. The compiler should only have 1 or 2 options: selecting the build file, and maybe an output path. Build files should be able to inherit other build files (so you can keep most of the settings same, and only change a couple things for different build types).
- Structs are automatically typedef'd. Maybe you can optionally pre-declare it to solve visibility issues (2 structs that contain pointers to each other).
- Types for enums, and type checking (can't set a value from enum 1 into a variable whose type is enum 2, without casting).
- Namespaced enums and automatic member picking. For the enums and function "enum FOO {X, Y, Z}; enum BAR {X, Y, Z}; void fizzle (FOO);", you can call the function with "fizzle(.X)" which has the same meaning as "fizzle(FOO.X)". "X" by itself would not refer to anything unless one of the enums was anonymous.
- Bitfield type. Basically just an enum, except it gives each member a different 1 bit (1, 2, 4, 8, 16...).
- {} automatically casts to the target type where ever possible, no need to cast it manually. Example: for function "void foo (Vec2)", you can just call it with "foo({.x=1})" instead of "foo((Vec2){.x=1})"
- Variables do not implicitly cast if there may be loss of information. For example u8 to u32 works, but u32 to u8 requires manual casting. Same with floats to ints, signed ints to unsigned ints.
- Variables initialize to zero by default unless you say otherwise. For example "Vec2 pos;" is the same as "Vec2 pos = {0};", meanwhile "Vec2 pos = #noinit;" is undefined.
- works on all types, including structs. "if (foo bar)" just works, always, as long as the types are same.
- Anonymous structs should just work, and be compatible with each other. For example if you just want to group 2 values, you shouldn't need to declare a type name for it when you send it around.
- Ability to get the member of a struct directly from a return value. For example for the function "Vec2 foo ()", you can do "float x = foo().x". This, when combined with anonymous structs as mentioned above, would allow you to do multiple return values by yourself without explicit language support for it.
- Compile-time functions (constexpr). Basically a it is guaranteed to run at compile-time, so it just leaves behind it's return value and doesn't even exist at runtime.
- Make it easier to get rid of libc or whatever it is that bloats the hell out of your program. Your hello world program should be about 1kb, and if you don't import any libraries then the only thing that should make it bigger is the code that you type in.
- 16-bit floats.
- Switch cases break by default. "fallthrough" keyword added for falling through. "break" does not interact with switch, so you can break from an outer loop with switch.
- More run-time memory safety options, better error reporting, and callstack tracing (especially upon a crash), toggleable with build settings.
- Safe version of alloca, or just make alloca be safe (returns NULL if it fails, instead of silently exploding)
- function_id, basically func except it returns a unique integer ID for the function instead of a string for the name, starts from 1 so 0 can be treated as an error. Also function_count or max_function_id so you can create an array that contains information for every function.
- Inline functions actually inline properly and work properly, there's no reason that a function should ever fail or refuse to inline, unless you explicitly allow "optimizations" to ignore it when you told the compiler to inline the fucking function.
- Better function and variable visibility logic: "local" = local to the current file only (this is the default), "public" = visible to other files through #import, "export" = available outside of the program as dynamic library functions
- _Generic lets you select arbitrary code snippers, not just words, for example "_Generic(x, int:{ foo(x, 0.5) }, default:{ foo(100, x) })"
- Forbid setting variables inside "if" parentheses, it makes code less readable, but more importantly is error-prone. "if (x = foo())" should not be valid, and looks too similar to "if (x == foo())".
- #defines that are local to a function (2 functions with the same #define inside of them won't conflict).
- Multiline macros. For example:
c
#startmacro(x)
foo(x);
x = 14;
#endmacro
- Custom string delimiters with options. Especially options to manipulate whitespaces so you can still tab and space the text block in your source code properly without affecting the string content. For example:
c
char* foo = #string(ignore_tabs, ignore_leading_whitespace, ignore_trailing_whitespace) #LOL_STRING#
this is a "story"
all about how my #1 life
got turned upside down
#LOL_STRING#;
- #onbreak, similar to a macro, except it's automatically placed directly before all the following scope breaks (at the end of loops and if blocks, before return statements, etc) from the scope where it was defined in.
c
void foo () {
for (...) {
if (e) return;
char* foo = malloc(123);
#onbreak free(foo);
if (x) {
continue;
}
do_thing();
if (z) {
return;
}
do_party();
}
}
- A better way to do variable arguments. There's many ways you could handle it but anything is probably better than the half assed crap that C currently does. I would be inclined to use some kind of type IDs because they have the least structural implications, all the language really needs to do is create a list of IDs for all the argument types:
c
void foo (... arg) {
void* p = va_datapointer(arg);
for (int i=0; i<va_count(arg); i++) {
switch (va_typeid(arg, i)) {
case typeid(int): printf("Argument %i is an int: %i\n", i, *p); p += sizeof(int); break;
case typeid(float): printf("Argument %i is a float: %f\n", i, *p); p += sizeof(float); break;
case typeid(Vec2): printf("Argument %i is a Vec2: %i %i\n", i, ((Foo*)p)->x ((Foo*)p)->y); p += sizeof(Vec2); break;
default: printf("Argument %i is invalid!\n", i); return;
}
}
}
Vec2 pos = {};
foo(123, 0.5, pos);
#define va_count(arg) *(int*)arg
#define va_datapointer(arg) arg + sizeof(int) + (*(int*)arg * sizeof(u16))
#define va_typeid(arg, i) *(u16)(arg + sizeof(int) + (i * sizeof(u16)))
int argcount = 3;
u16 typeid = typeid(int);
u16 typeid = typeid(float);
u16 typeid = typeid(Vec2);
int arg1 = 123;
float arg2 = 0.5;
Vec2 arg2 = {};
foo(&argcount);
- Some extremely simple templates. I shouldn't have to manually declare 800 different vectors and a new array type every time I want to put a new variable into an array.