Type-Safe Printf for C
github.com
github.com
[1]> (format t "~05,'0d" 5)
00005
NIL
[2]> (format t "~05,'0d" "abc")
00abc
NIL
Here ~d (decimal) doesn't care that it didn't get an integer. 'Type-safe' in this context does not mean that you get more compile errors, but that the format specifier does not need to specify the argument type, but just defines the print format. In fact, format strings with this library will have less compile-time checking (namely none) than with modern compilers for standard printf. This approach is still safer.
EDIT The answer is at the bottom apparently. Maybe put that up higher?What maybe 'should' happen in C2x is: 'int' is defined as i16, 'long' as i32, 'long long' as i64 etc and then see which programs break. Because it's perfectly OK to have 16-bit 'ints' on a 64-bit arch. (size_t is what you use to deal with architecture-specific chunks). And then remove all this 'int' etc crap from C. (Obv, some 'compat switch' would need to exist, but you get the idea.)
But there must be no conceptual 'leaking' (behaviors that I experience using e.g. uint_fast8_t that are in any way different from behaviors I experience when using u8 ).
I don't especially care how the HW works; because my 'virtual machine' is C. (yes, yes, I know Reality intrudes, and sometimes you need to get closer to the machine. But, isn't this because of 'leaking' between 'virtual machine' and 'actual machine' that I mention above?)
This stackoverflow answer gives an example: https://stackoverflow.com/questions/7597025/difference-betwe...
I copied some examples of how to implement variadic macros, and expanded on that for C basic types. It mostly worked, you'll always have difficulty for corner cases like separating pointers and arrays, but it worked well for the basic C types.
I gave up for a few reasons:
- I wanted a form to register new types, so it could work for user-defined types;
- the C pre-processor knows nothing about lists that can be expanded multiple times;
- variadic C macros are ugly hacks.
Maybe one day I'll get back to it and publish it.The interesting part is that _Generic combined with macros allows some very interesting tools for implementing primitive forms of polymorphism. Actually, if the C pre-processor supported lists, it would be possible to implement RTTI in C.
> - the C pre-processor knows nothing about lists that can be expanded multiple times;
I'm actually working on both features as Clang extensions.
#repeat, a preprocessor directive to loop, can be combined with _Pragma(push_macro/pop_macro) to create lists by redefining a macro.
and currently #increment, though I think I want to expand on this so that other macros can be redefined more easily to create lists via push/pop macro.
The reason push_macro/pop_macro pragmas can't work, is the macro has to be undefined and redefined, and the value then pushed onto a stack in the compiler.
and you can't redefine a macro in the body of another macro directly.
so I've been thinking about maybe a _Pragma(redefine_macro(MacroToRedefine, NewValueForRedefinedMacro))
but I don't want it to be limited to the _Pragma area of the compiler, I want it to be eventually standardized.
I've been talking to a friend at WG14 who suggested making it a "Preprocessor Expression, like `__has_c_attribute` and `defined()`
So that's the area I've been working on recently for the Increment/Redefine PE lately.
If you get anything close to that working, that would be a godsend. It is the last remaining piece of the puzzle for me to implement complete RTTI in C. It would certainly help to minimize glib boiler plate code too.
I'd really like it to be part of c2x, but I think it is too late now. If it is implemented by either GCC or Clang, the remaining other would certainly it too since it is too useful. So getting it to work in any of these would be good enough for me.
How can I track/follow your progress?
And if there was also __EVAL__ to force the macro preprocessor into another evaluation level, you could write recursive macros quite easily, e.g., to wrap every argument into a function call:
#define EACH(f,x,...) f(x) __VA_OPT__(, __EVAL__(EACH(f, __VA_ARGS__)))
This would make the macro magic for this library trivial: you could process lists recursively.Edit: added missing paren
Yes, I had the same urge. You can easily fall into the trap of too many features on the list. I settled on keeping user types out: you can always write a stringify() and pass that to the printf. Not the same, I know. But a more finite project.
> - the C pre-processor knows nothing about lists that can be expanded multiple times;
Yeah, that's a hack. Look at the 'VA_EXP()' macros in include/va_print/base.h. Ugly. Incomprehensible.
> - variadic C macros are ugly hacks.
Absolutely. But I think there is no other way in C.
> Actually, if the C pre-processor supported lists, it would be possible to implement RTTI in C.
I couldn't resist to put in '%t' which prints the C type of the argument...
How can I have strict type checking, but a user defined format string?
If you use this in a big code base, there will still be the old printf all over the place.
Now you have to think: is this custom logging function here based on the safe printf from github, or is it vsprintf under the hood?
So thank you, this is fixed.
Unless you need maximum performance (SIMD, GPUs, etc.) you should use a developer-efficient, productive language.
Well-written Go executes almost as fast as C, and you will be more productive as a programmer.
This is on top of Go being an unstable, immature language.
Repo for compiler itself: https://github.com/nikki93/gx (no README.md etc. yet, will be getting to that when I next have a chance (it's a side project)). It just takes around 1500 lines of Go thanks to the parser and typechecker in the standard library.
Go's perf was definitely non-trivially bad for me on WebAssembly.
Did you try implementing pooling (e.g. sync.Pool) for game objects/entities/components/etc? How did that go perf-wise?
Ultimately in the game scenario the GC is actually just ... not helpful. Game logic code already explicitly handles lifetimes to some degree (eg. when this entity collides with that one, destroy it, etc.) -- emergently deciding when to free things based on references is usually not what you want. You do want it for resource management (like a texture cache), but it actually makes sense to kind of roll that on your own and adapt it to the game. So having a GC and then fighting it just sounds like an ill-fitted solution.
In a well designed game engine, you will only need to implement it a handful of times (if that) to cover 99% of the hot code. Certainly not something to need to do for each class of game object.
1. Go is a garbage-collected language, C is not.
2. Go is a single-company-managed language, while C is managed by an international standards committee within ISO. You might not care about this difference, but its quite significant w.r.t. how future language developments happen.
3. C types are intentional, Go types are extentional ("structural typing").
These fundamental differences are not cases of one language being superior, or further advanced, than the other - they're about going in different directions.
What I mean is that if you like C99, you will probably like Go. Go can be understood as a modernization of C that doesn't abandon C's simplicity but adds a few important facilities that C lacks.
Go obviously derives from C. It's a very C-like language. It makes sense to view Go as an enhanced C that makes slightly different trade-offs and that is applicable to a slightly different set of purposes.