Generating C code that people want to use
jonathan.protzenko.fr
jonathan.protzenko.fr
Yes, yes, yes! Just because one is automatically generating/translating code, that doesn't mean it can't be pretty! When automatically translating code, the matching engine needs to be done with the full syntactic expressiveness of the source language, and what is matched and translated need to be idioms in each language! (As opposed to fine-grained syntactic elements. When the translation is done below the level of idioms, what results is non-idiomatic. It sounds pretty obvious when put like that.)
No one is going to demand that GHC generate readable assembly. Why should they demand that GHC generate readable C if it were generating C instead of assembly?
Also, the code generator should include tracing and debugging support. That seems much more important to me than generating idiomatic and readable C.
Is there anything like this for C?
Part of the reason C still exists is enormous amounts of work went into the tooling.
There is also the other potential issue that the final code is doing something strange because of some #define magic, which is also very hard to trace without being able to walk through code.
According to the oldest docs I could quickly find (https://gcc.gnu.org/onlinedocs/gcc-4.0.4/cpp/Line-Control.ht...) it goes back to at least gcc 2.95 in 1999, but I wouldn't be surprised if it predated javascript and css themselves.
I've been fixing up some ~15 year old code to compilable state and the flex/bison bugs were showing up with the flex/bison line numbers where the errors originated -- which somewhat helps but it turned out none of the errors were actually from flex/bison but because of how their API changed over the years.
I'd get some WTF compilers errors and track them down to running the output of flex through sed in a perl script to do something that it now does out of the box and didn't like being messed with, fun times...well, it actually is fun times since I'm just doing this on my own so I can play with the software.
The code they generate is supposed to live on as source code, along with other code in the project. We should expect people to read and try to reason about code that exists in source control.
For compilers, however, I do not. For example, one codebase I help maintain is Heimdal, and it has its own ASN.1 compiler that generates C (and also something of an interpreted bytecode, as an option), and it's output is not even properly indented -- nobody minds because it works, and on the rare occasion I have to inspect that code, I use tools like VIM or clang to format it.
Debugging in programs that are mixed C and other code. If you're in the middle of debugging Firefox (or any composed system) and start stepping through unstructured gobbledegook, you'll end up cursing the people who did this to you.
This becomes less of an issue if/when the non-C language becomes so ubiquitous that there is proper debugging support for it (viz Python or C++ support in gdb), but as long as a bug may be triggered, propagated or otherwise be interacted from the non-C side, you want that code easily readable.
Comparison debugging mixed C source and assembly in a debugger is trivial.
TypeScript is an interesting example, the generated JS is pretty close to the original TS, largely just with types removed, so for someone with lots of JS experience it is easy to get confidence in the compiler as the output is pretty close to what it would have been in the first place.
This happened to me at work too. A large majority of the codebase is manually written C, but some of that is too tedious/error-prone to be written so they are generated. We invested a great deal of effort to generate extremely readable code, even with comments and all. The reason is that other people need to read this C code—both interface and implementation—as they work.
Speaking of GHC, the Haskell ecosystem actually has great tools to generate readable code. The various Wadler/Leijen libraries like ansi-wl-pprint make it a breeze to generate readable code. Indeed not many other languages have so many good, if a bit idiosyncratic, libraries to choose from just for pretty printing!
Also, crash reports produce stack traces. I’m positive that Microsoft and Mozilla are both heavy users of automated crash reports.
Abstractions are leaky. There are many tools for debugging assembly as well.
Identifiers like:
TM_iLzrQjTMtHjOSlNDU8lfsw_2
TM_iLzrQjTMtHjOSlNDU8lfsw_3
Suppose you're selling a code generator. The customer using that code generator is going to ask "what happens if you disappear? How do we maintain our code?" This drives them to demand that the code you generate being able to be continue to be maintained on its own, even if the code generator rots and dies. And that means it must be readable.
I've seen a case where a company has been stuck with generated code, and not only did they not have the code generator, they didn't even have the documentation for the code generator (nor anyone who ever used it). The company that sold the code generator had died many years earlier.
This same consideration doesn't apply to compilers, because you can buy compilers from many vendors, as well as get well-supported free ones.
This consideration absolutely applies to compilers for various applications (embedded, exotic platforms etc.) and also if you are relying on implementation-defined behaviour and extensions.
When I was a young(er) and naive(r) C programmer, I had printed out this precedence table for occasional reference while coding. I was rather proud that I knew of such subtle corner cases!
I've since learned and don't need to use that table anymore... because I just use parentheses to avoid ambiguity. Don't rely on subtle behavior in your code, folks. As the Python Mantra says: explicit is better than implicit.
From a readability standpoint, the C output of Gambit and Chicken is a hot mess in comparison.
If you want something like Icon or a Lisp or Scheme to generate readable C, you're going to have to use Simon Tatham's C co-routine scheme [0] (used by PuTTY) so that you can keep code looking sequential. You'd have to generalize the co-routine thing so it's not so much about co-routines but about closures (the two concepts are remarkably related). If you allocate call frames on the stack, then you get depth-first search and backtracking support, and closures of dynamic extent. If you allocate call frames on the heap then you get breadth-first search and backtracking, closures of indefinite extent, and co-routines.
(PuTTY has all of the SSHv1 and SSHv2 protocol up to the end of authentication coded as one enormous function each, and this reads surprisingly well in spite of being such enormous functions. That works for PuTTY because those protocols are extremely sequential up to the end of authentication. Think of Simon Tatham's co-routines a an await for C.)
[0] https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
Pre-Scheme is intended for a different use case -- when you want the level of fine-grained control that C gives you but don't want to leave Lisp behind. Its semantics are accordingly quite different from Scheme's and it doesn't support the full set of Scheme control structures, nor implicit garbage collection.
See: https://en.wikipedia.org/wiki/CHICKEN_(Scheme_implementation...
Gambit and Chicken are fine programs -- close to best of breed in the Scheme->C transpiler space. Pre-Scheme was intended to map straightforwardly onto C semantics so that applications like the one for which it was designed -- implementing the Scheme48 VM -- could be coded and tested in a Lisp environment before being transpiled to understandable C code. They sacrificed full Scheme semantics for this.
At this stage is there any software engineer who considers a language with lambdas strange or this is a joke from the author?
It's been a while but I used to use Simulink to generate C code and they had the same naming problems. It was hideous. They also solved the C99 types issue independently and generated their own target dependent header files that defined INT16_T in a target dependent way. I asked a couple guys from that company to please just implement a C99 target which is more portable - best if it actually produced code using the C99 types instead of renaming them.
So many issues with auto-generated code, but sometimes it's really useful.
Ok, so all the talk about generating idiomatic C went for nothing in the end?
Putting the important content on top: TODO.