I'd like to point you to the 2008 paper by Felix Klock , or perhaps the more easily digestible presentation , where you will find (slide 39-47) the description of the inner workings of procedural macros like define-c-info, where it's explicitly stated that an intermediate C program is generated, compiled & executed to get the desired result(s).
This is not groundbreaking per se, but it is certainly a smart compile-time tactic of interfacing with a C ABI.
I was specifically referring to the usage of intermediate files...I think I assumed too much, since obviously you must have a very different view of what consists an intermediate file than I do. Would you care to elaborate?
I don't want to get too bogged down here, but sure:
When you use autoconf, you typically run configure, then you're left with a config.h, which then parameterizes later builds.
On the other hand, when you evaluate the the definition of dirent->name, no intermediate file is left behind.
Of course in both cases you make temporary C files, but they are ephemeral. The difference is that in the first case you are generating files for inclusion in a later phase, and in the second you are effectively extending your scheme compiler with a c compiler.
The surprising about this code from a Scheme programmer's POV is that usually macros are about rewriting Scheme source using Scheme. In this case the macro generates C source, forks to compile and run it, and munges the result into the resulting text.
But sure, I can see that from a certain point of view, autoconf and scheme macros can do similar things :)
If I understood correctly, here the Lisp code generates the C code which generates the small chunks of Lisp code which are then compiled. But I don't understand what you mean under "intermediate expanded file." In autoconf the config.h is the final resulting chunk that is used in the final compilation (somewhat in parallel to Lisp chunks generated by the C)." What's your take?
You know, the whole dynamic FFI world is very much seatbelts-off, which is initially quite disconcerting. Usually I rely on my distro to ensure compatibility, but once you start saying "this syscall gives me a pointer to memory which should be interpreted as two ints, one char, and a float, packed conventionally" you begin to realize what exactly are the interfaces between various bits on your system. For better and for worse of course.
What Guile has now is the "list-all-members-in-the-struct" approach that Klock discourages. It's the difference between API and ABI compatibility. I'd like to figure out how to do the former.