Scandalous Weird Old Things About The C Preprocessor (2015)
blog.robertelder.org
blog.robertelder.org
However, see [0] for a way to parse C with preprocessor #ifdefs (but after expanding #includes and #defines).
[0] "SuperC : Parsing All of C by Taming the Preprocessor" http://cs.nyu.edu/rgrimm/papers/pldi12.pdf
include-directive:
beginning-of-file # INCLUDE ...
newline-from-previous-line # INCLUDE ...
Since 'beginning-of-file' isn't really something you can reduce to a token I justified that it would be context sensitive.Of course you can, generate a token immediately upon opening a file. Nothing special about b-o-f or b-o-l. Similar to EOF.
http://trevorjim.com/how-to-prove-that-a-programming-languag...
My initial thoughts are that the property of being context-free only refers to a parser, not to the lexer. A lexer can be line-oriented, as mentioned.
The blog post mentions the lexer issue. The definition of a context-free language is fuzzy because you could have an infinitely powerful lexer, but he gives a precise definition.
The lexical syntax is usually a "regular language" or quite close to one, this includes C. All regular languages are also context-free, the set of regular languages is a strict subset of the context-free languages. Regular languages can be parsed using regular expressions.
And again, there's nothing wrong with being line-oriented. That's not "more powerful" in any sense of the term. Recognizing newlines, SOF, EOF, etc. are really simple things (in the language hierarchy sense) that you can do in regular expressions without using any extensions that make them non-regular (like backreferences), therefore they can be converted to a context-free grammar.
Commonly, a programming language will have regular expressions for its lexer and a context-free grammar for its parser. Since the lexer is technically also context-free, it's possible to combine the lexer and parser into a single stage, but this is a bad idea for various software engineering reasons.
Some older languages have context-free parsers, like C, whose grammar is not context-free (that's not the same as "context-sensitive" which is a particular technical term). One major reason C's grammar is not context-free because it has to distinguish between typedefs and other identifiers.
Of course, a language as a whole is almost never context-free, but we usually use "context-free" to mean that the parser and lexer are context-free.
Also Haskell: http://trevorjim.com/haskell-is-not-context-free/
I'm pretty sure in JavaScript that the /s+/ regex syntax can't be recognized with a regular language. Perl is of course insane -- it's not even statically parseable [1]. Shell isn't context-free.
The theme of this set of posts is that most languages used in practice aren't expressible with CFGs, and I agree. I didn't prove it but he shows how you would prove it.
[1] http://www.oilshell.org/blog/2016/10/20.html (see the end of this post for links about C++, Perl, Make)
IMHO This is not a mistake. This is a subtle indication about how much you should be doing with the C preprocessor.
People who try to do crazy clever tricks with the preprocessor are usually just creating a huge problem for themselves down the road, especially when porting over to a different OS or compiler.
If you're considering doing complex metaprogramming with the preprocessor you need to step back and reconsider your approach and ask yourself who is going to maintain it once you've moved on.
The C preprocessor has just enough power for most of what it exists for (smoothing over build-time oddities, code which should/must be manually inlined with a macro to perform as desired).
Yes, you can abuse it and produce Bournegol, but there are many decent reasons to use most features of the C preprocessor, and the way it is defined is largely ergonomic, aside from the whitespace gotchas around function macros.
Robert is fully entitled to lament the process of implementing the preprocessor. Far be it from me to disagree. However, none of these examples seem weird if you just follow the intended implementation pattern: execute each step separately (at least as a model of behaviour) and you will have the desired result.
Even the "let me destroy your world with this example" example is straightforward as long as you follow the process.
-2: Digraphs and trigraphs are expanded
-1: Line continuations are collapsed
0: Comments are removed or ignored
1. A macro is defined, `foo` is `<stdio.h>`
2. The first non-whitespace character of a line is #
3. The first non-whitespace character, and the contiguous non-whitespace characters form "include", followed by a macro expansion token, `foo` which we expand to `<stdio.h>`
4. Splice in the included file, and continue preprocessing
5. We're done, really, it was quite straightforward.
I think Robert's pain and confusion rests solely on his desire to define C as a context free grammar, when C parsers are really character-oriented state machines.
Variadic overloading of function macros is technique from http://stackoverflow.com/questions/11761703/overloading-macr... combined with _Generic which overloads by type. It allows things like print("abc",1.2,3) to transform into printf("%s","abc"),printf("%f",1.2),printf("%d",3) without any overhead in the generated code.
see https://www.reddit.com/r/frozenvoid/wiki/voidh Edit: and the tuple members are just symbolic tokens, they can contain anything from code to nested tuple trees. (code can be encapsulated in https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html)
It became an abomination consisting out of many C-preprocessor hacks and impossible to debug. I never put it online (and I never will).
In the end if you want to use C at a more abstract level you may as well use Nim (not C, but it compiles to C89), C++ (of course) or write your own code generator (using libclang with annotations for ex.).
I actually used the fact that:
#define return(r) ...
only expands for: return (something);
and not for: return something;
in a (non-serious non-production) program I wrote. I felt slightly dirty, but it made sense in the context -- tracking stack frames in order to do precise garbage collection of C code.The C preprocessor can be remarkably useful for metaprogramming.