:date: 2026-07-28 19:53
Back in 2015 when I conceived of the idea for SnowC, I first thought it would be so easy to convert from SnowC to C that I could do it by storing the state of the indentation level in the call stack using recursion. That turned out to be too ambitious but it was close.
However what was really delusional and naive was thinking that converting from C might be possible using recursion too. While I got over that quickly, even starting on it fresh this year, I thought, surely this conversion can not be that difficult. Wow, was I profoundly wrong! Over two months later I now know an unhealthy amount about pathological C code and I have been humbled. When it comes to the entire possibility space of C source code, nothing is simple.
Which brings us to indent levels. Easy, right? Well let's just say that it's easy when it's easy. Like this example (C left, SnwoC right).
// Level 0 | // Level 0
int level1(int x) { // Level 0 -> 1 | int level1(int x) // Level 0 -> 1
if (x) { // Level 1 -> 2 | if (x) // Level 1 -> 2
if (x % 2) { // Level 2 -> 3 | if (x % 2) // Level 2 -> 3
x++; | x++
for (int y=x; y<99; y++) { // Level 3 -> 4 | for (int y=x; y<99; y++) // Level 3 -> 4
if (x + y == 99) { // Level 4 -> 5 | if (x + y == 99) // Level 4 -> 5
return y; | return y
} // Level 5 -> 4 | // Level 5 -> 4
} // Level 4 -> 3 | // Level 4 -> 3
} // Level 3 -> 2 | // Level 3 -> 2
} //Level 2 -> 1 | //Level 2 -> 1
} //Level 1 -> 0 | //Level 1 -> 0
And without the comments, the SnowC is pretty legible.
int level1(int x)
if (x)
if (x % 2)
x++
for (int y=x; y<99; y++)
if (x + y == 99)
return y
Those comments are there to show how the core concept works. You
basically need a simple integer variable that stores the indent level.
When you find an opening brace ({), increment the indent level, and
when you find a closing brace (}), decrement the indent level. Easy,
right? Right? Ohhhhh no.
It gets pretty bad. Let's start with relatively simple singleton hassles. In a recent post we just covered how the C creators loved to do level nesting with no braces at all. I call those cases singletons. Ok, fine, so that is a thing. Sure, whatever. Surely there's a solution, I thought.
Usually SnowC can happily not really care one bit what your C code is actually doing. But because of this issue, I realized there needed to be a complex parsing effort so all the singleton (braceless) control syntax could correctly contribute to the indent levels being adjusted properly. Not easy but doable. So I dug in and got to it.
Single line singletons are easily handled by just ignoring them. They are resolved before they can even make an impact on the code's indentation structure.
Multi-line singletons are a different story, so let's start with them.
An interesting property of SnowC is that a round trip will completely remove multi-line singletons! For example, consider this authentic K&R example (from page 92).
for (i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i];
In SnowC it becomes.
for (i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i]
Note how close this actually is to what K&R imagined! But there is no way to hint that you do not want the more ordinary and predictable C you will get on the return conversion. Here is what converting back to C produces.
for (i = 1; yearday > daytab[leap][i]; i++) {
yearday -= daytab[leap][i];
}
Note that this C produces the exact same SnowC shown above.
What if the entire singleton resolves on one line? And importantly
what of subsequent additional elements like b() in this line of C
code?
if (q) a(); b();
In theory this could be done by leaving it mostly as is with the non-trailing semicolons intact.
if (q) a(); b()
That is actually valid SnowC and will convert to the previous C one liner. However, when that C one liner converts to SnowC, it will get broken up.
if (q) a()
b()
The reason for this choice can be seen when there are line breaks which can cause trouble. Consider this SnowC.
if (q)
a(); b()
If you are a real C programmer, hopefully that did not cause some life threatening allergic reaction. Which of these two possibilities is correct when it converts to C?
if (q) {
a();
}
b();
Or...
if (q) {
a(); b();
}
Both are reasonable interpretations.
While the first looks good at a glance, the second follows the simple SnowC rules. And for a few good reasons. What we can be sure about is that the following C code won't ever be possible to generate by converting from SnowC.
if (q)
a(); b();
Currently converting from C to SnowC, the rule is that singletons get split up if they resolve on the same line as another statement that would not share its level.
To see why this must be consider the following round trip sequences of conversions. Following the conversions vertically, on the left is C -> SnowC -> C and on the right is SnowC -> C -> SnowC.
C: SnowC:
if (q) if (q)
a; b; a; b
| |
V V
SnowC: C:
if (q) if (q){
a a; b;
b }
| |
V V
C: SnowC:
if(q) { if (q)
a; a; b
}
b;
Note how the SnowC can mostly preserve its form into C and back. But the C structured like this is confusing to begin with, and must get sorted out in SnowC for the braces to actually work when converted back. Because the C style in the upper left is confusing and discouraged (heck, K&R don't even do it!) it is better to just cut up singletons packed on a line which will prepare them for how SnowC needs them to be. If you want SnowC as found on the right, use braces in the C like a normal person i.e. like the C code in the middle on the right.
If you want to see some serious SnowC puzzles check out my test file
called spaceless.c and its corresponding SnowC.
$ echo "=== $F";pr -w$(tput cols) -o1 -s'| ' -tm $D/$F <(./c2snowc -i4 $D/$F)
=== spaceless.c
void f(int q,int a){ | void f(int q,int a)
if(q)a++; | if(q)a++
if(q)a++;b++; | if(q)a++
if(q)a++;else--a; | b++
if(q)if(q)a++;else--a; | if(q)a++
if(q)a++;else/**/if(q)a++;else--a; | else--a
} | if(q)if(q)a++
| else--a
if(q) | if(q)a++
a();b(); | else/**/if(q)a++
| else--a
if(q)a+ |
a;b(); | if(q)
| a()
if(q){ | b()
a();b();} |
| if(q)a+\
if(q){ | a
a();b(); | b()
c();} |
| if(q)
| a();b()
|
| if(q)
| a();b()
| c()
Like many of my test files, it is a reminder that polite indenting is a choice in C; in SnowC it is a promise. It's also an unusual example of a conversion to SnowC that results in more lines.
But those single singletons are relatively easy. Let's take a look at more serious level hassles.
Check out this normal example from K&R p56, where the braces of the
do control keyword control the indent. Also note that the indent is
properly controlled for the two if statements too even though there
are no braces. (C left, SnowC right)
/* itoa: convert n to characters in s */ | /* itoa: convert n to characters in s */
void itoa(int n, char s[]) | void itoa(int n, char s[])
{ | int i, sign
int i, sign; | if ((sign = n) < 0) /* record sign */
if ((sign = n) < 0) /* record sign */ | n = -n /* make n positive */
n = -n; /* make n positive */ | i = 0
i = 0; | do /* generate digits in reverse order */
do { /* generate digits in reverse order */ | s[i++] = n % 10 + '0' /* get next digit */
s[i++] = n % 10 + '0'; /* get next digit */ | while ((n /= 10) > 0) /* delete it */
} while ((n /= 10) > 0); /* delete it */ | if (sign < 0)
if (sign < 0) | s[i++] = '-'
s[i++] = '-'; | s[i] = '\0'
s[i] = '\0'; | reverse(s)
reverse(s); |
} |
To pull this off requires parsing the code deeply enough to recognize
all 6 of the possible control words that can trigger a singleton
(if, while, for, switch, do, else) and recognize when they
are not going to be using braces. Just identifying those is a big job.
When I finally got that working I was pretty relieved. Mostly that it
was even possible.
But it gets worse. The C language allows an unhealthy mix of brace levels and singleton levels. While experimenting with demented code that nobody would ever use (I hope!) I discovered something very unnerving. I slowly started to realize that simply tracking the brace depth and the singleton level depth was not going to do the job. (Theoretically; in practice it mostly would.)
Check out this sample program where I've labeled the conditions to
reflect the brace depth (e.g. B1) and also the singleton depth (e.g.
S1). (mixed_lvls.c, C left, SnowC right)
void main (int argc, char **argv) { | void main (int argc, char **argv)
if (B1) { | if (B1)
while (B1S1) | while (B1S1)
for (B1S2) | for (B1S2)
if (B2) { | if (B2)
if (B2S1) | if (B2S1)
if (B2S2) | if (B2S2)
B2S2Resolve(); | B2S2Resolve()
if (B2S1) | if (B2S1)
if (B2S2) | if (B2S2)
B2S2Resolve(); | B2S2Resolve()
else //B2S2 | else //B2S2
B2S2ElseResolve(); | B2S2ElseResolve()
} | else if (B1)
} else if (B1) { | while (B1S1)
while (B1S1) | for (B1S2)
for (B1S2) | if (B1S3)
if (B1S3) | B1S3Resolve()
B1S3Resolve(); | else // B1
} else // B1 | while (B1S1)
while (B1S1) | for (B1S2)
for (B1S2) | if (B1S3)
if (B1S3) | B1S3Resolve()
B1S3Resolve(); | // End main()
} // End main() |
What's important to note here is that although chains of
braceless singletons like if (a) if (b) if (c) do_this(); do
increase indent depth, when the do_this() finally runs you can't
just set the singleton depth to zero and call it a day. That whole
thing may be nested in one or more sets of braces that each have their
own chain of singletons to unwind!
When I realized this, I had to scrap a lot of work and rethink the whole deal. And for what? A crazy obscure problem that in practice would hardly ever come up. But damn it, I was committed to doing it as well as I could.
The solution I came up with is that I create a heap array where I can store the singleton depth for each brace level. This is the only way an accurate unraveling of the most complex structures can be done. The initial allocation allows for ten brace levels deep. Is that the nesting depth limit then? No. If you go beyond ten, it will allocate twenty; if you fill that up, forty more, etc. until you run out of RAM.
This is my test of the dynamic array provision storing a long chain of singletons in a clause 11 brace levels deep.
int main(int c, char **v) { | int main(int c, char **v)
if (q) { | if (q)
if (q) { | if (q)
if (q) { | if (q)
if (q) { | if (q)
if (q) { | if (q)
if (q1) { | if (q1)
if (q2){ | if (q2)
if (q3) { | if (q3)
if (q) { | if (q)
if (q) { | if (q)
for (qS) a(); | for (qS) a()
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
if (qS) | if (qS)
a++; | a++
} } } } } } } } } } | // Final line comment immediately after action.
} |
// Final line comment immediately after action. |
If you understand how ugly that is, buckle up, it gets quite a bit worse! I'll save that for the next post.