Kartwheel: Dynamically typed C?
Hello, welcome to my first blog post. Grab a drink and stay a while.
The other night I had a flash of neurodivergent inspiration. The kind I ended up sleeping too late for. I was thinking about what I’d like to add to C in some crazy idealistic dream world, and decided I needed to yap about it to my Discord friends. After discussing it with them—who (understandably) hate C—the idea of the object of this post began to form in my mind. It was like that rock candy science experiment for kids, where you leave a skewer overnight in sugar water, and it crystallizes on the skewer.
That idea is what I’m calling Kartwheel. Why the word-initial K? It looks nice, makes it sound silly, and reminds me of KDE. Kartwheel adds types as values to C, as well as mechanisms to compare them at runtime. (It also adds some other things for funsies, like decorators and parameterized types.) As of right now, I have only a basic design. Here’s how it works.
_Type
Every building needs a foundation, ideally a strong one. For us, it’s a new primitive, called _Type. It represents any type as a bitstring in memory.
_Type is either two or four bytes wide. Primitives (like int and double) are two, while compound types (structs, unions, and typedefs) are four. The structure looks like the text-based diagram below. Each character is one byte, here’s what they represent:
F= flags (bitflags; represents primitive/compound for now, but could be used to represent other things)A= argument count (used for parameterized types; those will be explained more later)#= hash (different for primitives and compounds; helps to avoid collisions)
primitives = F#
compound types = FA##
When a compound type is declared, the compiler adds a new entry to a table. This contains the type name, its bitstring, and a pointer to its argument list (more on this later). This is then put in memory before main. I call this the “type declaration table.”
Since _Types are bitstrings, it makes it easy to store and compare them. I could probably make them a regular old C union. As for how they get compared, there’s another table containing mangled local and global variable names, and their type bitstrings. I call this one Steve the “type reference table.” When you need to compare, you do a lookup. But how do you represent this in code in a way that’s useful to the programmer?
type and gettype
It turns out that it’s pretty straightforward. Enter type, which is a literal that allows the programmer to use a type as a value, like type(int). This uses the type declaration table to resolve the type name to a bitstring. It’s slightly clunky, but hopefully easier to parse.
gettype is an operator in the way sizeof is. [Edit to clarify: gettype works at runtime, unlike sizeof.] It returns the type of its only argument as a _Type, so int x; gettype(x) returns type(int). (typeof was taken already, and I don’t want to break existing C code.) Naturally, gettype uses the type reference table to do this.
As for comparisons, I initially had a separate operator for it, but I hate writing more parser code than I need to, so I decided to leave that out. It would have been the same as gettype(x) == type(y) anyways.
Fun things
These are miscellaneous features added to Kartwheel mostly for the hell of it. Some of them have to do with types, others don’t. Some of them are useful, others aren’t. I just like them in other languages and wanted to include them here.
Parameterized types
These are essentially the same as generics in other languages, but only on structs. I’m not adding it to functions to make it easier to parse and to preserve the purity of the dynamic typing aspects. You can switch on a _Type for that.
The syntax is simple, using a custom C23 attribute:
[[generic(T)]]
struct Stack
{
T items[128];
int top;
};
At compile time, variants are created for whatever type combinations this struct gets used with.
typeargs can be used to get the type of the argument passed to an instance of the Stack struct:
struct Stack(int) x = { 0 };
typeargs(x, 0); // int
Remember that pointer from the type declaration table? This is where that comes in. typeargs uses it to get the argument at the specified index. It will cause a segfault if it ends up dereferencing a nullptr.
Python-style decorators
I loved these in Python when I used to use it. I find Nextcord and Flask in particular to make great use of them. I’m not quite sure how to add these yet, but the premise is the same. Here’s an example with the syntax I have in mind:
void logger(int (*some_func)())
{
printf("%d\n", some_func());
}
[[decorate(logger)]]
int add2()
{
return 3 + 9;
}
Conclusion
Thanks for reading my autistic infodump on something I’ve been designing. Expect future instalments in this series.
I plan to implement these concepts by forking the cake compiler, as it’s apparently small and understandable. At the end of the day, this is mostly a learning experience for me. I don’t think Kartwheel will be that useful for people, or even for me. Also, storing type information like this would present significant memory usage issues, which pose a problem in some environments.
[Edit: Vote for which features I should add next lol]
If I sent this to you, please give feedback! I’m willing to edit this post for clarity and to answer questions.