SnowC - Challenges - Else

:date: 2026-07-29 05:59

In the last post we looked at how challenging it was to just track level depth. To understand the problem better let's review how singletons can cause mischief. The classic singleton is something like this conceptual code.

SINGLETON_KEYWORD (optional_conditions) resolving_statement;

The SINGLETON_KEYWORD can be if, for, while, switch, do, or else. When the resolving statement completes, the singleton is complete. But what if the resolving_statement is also another SINGLETON_KEYWORD?

    SINGLETON_KEYWORD
        SINGLETON_KEYWORD
            SINGLETON_KEYWORD
                SINGLETON_KEYWORD
                    resolving_statement;

The resolving statement closes out the nearest singleton which then closes out the next and the whole chain unwinds. Now that I've explained it to you, you're ready to believe that's how it truly works, right? If you don't know anything about C you are certainly ready to believe this. Even if you are a professional C programmer, you might still be fooled because no sane person ever writes code like this!

The resolving statement unraveling all the nested singletons is a nice neat concept, but unfortunately, it is not C's nice neat concept. Let's see how C really behaves. Check out this singleton puzzle paying attention to the indent levels.

 if (L1)
     for (L2;;)
         if (L3)
             resolve(3,2,1);
 if (L1)  // Previous resolve fully resets level.
     resolve(1);

That does look like the neat algorithm I described. The level of the first if has been fully reset because the entire chain of singletons has been resolved. Seems reasonable, right? C then puts you in the uncomfortable position of thinking the following code is also reasonable even though there is a kind of inconsistency to it.

 if (L1)
     for (L2;0;)
         if (L3)
             resolve(only3);
         else // Previous resolves only one level!
             resolve(3,2,1); // Finally resolves.

The else resolves with a single statement clearly making it a singleton keyword. But there is more to it. It turns out that else is a special singleton keyword that has its own ideas about indent level.

This inconsistent feature of C's control keywords introduces the need to track if the last singleton keyword was specifically an if and then on closing out that if, the levels get adjusted differently if a look ahead can find an else nearby. Good times.

If you think all that sounds challenging, I've got some bad news for you because it turns out to be a lot worse than that. I realized that any else must align with any inner if regardless of either executing a braced clause or not. This actually requires a FILO stack to keep track of any pending if that might possibly go with an else at every level of brace depth. And these must be politely dropped once there is no possibility for an else at that brace level. Don't worry if that takes you several days to get your head around; been there.

At this point I was seriously starting to question my judgment at taking on this project. But I pressed on and figured out a way. The need for this quirky syntax tracking should almost never arise in any sane modern production code. For that reason each tracked level on the stack gets a leisurely heap allocation and I'm not even going to apologize.

When I finally implemented that, my test setup caught another error in the K&R sample from page 132! There already was a missing semicolon in that code but also I realized line 20 is incorrectly indented! Fortunately SnowC caught it nicely. And of course it must be said that in C, this is not technically an error  —  it does compile fine. But it sure is a faux pas!

(KR132-cat.c)

 #include <stdio.h>                                              | #include <stdio.h>
 /* cat: concatenate files, version 1 */                         | /* cat: concatenate files, version 1 */
 main(int argc, char *argv[])                                    | main(int argc, char *argv[])
 {                                                               |     FILE *fp
     FILE *fp;                                                   |     void filecopy(FILE *, FILE *)\ //[Original missing ;]
     void filecopy(FILE *, FILE *) //[Original missing ;]        |     if (argc == 1) /* no args; copy standard input */
     if (argc == 1) /* no args; copy standard input */           |         filecopy(stdin, stdout)
         filecopy(stdin, stdout);                                |     else
     else                                                        |         while(--argc > 0)
         while(--argc > 0)                                       |             if ((fp = fopen(*++argv, "r")) == NULL)
             if ((fp = fopen(*++argv, "r")) == NULL) {           |                 printf("cat: can't open %s\n", *argv)
                 printf("cat: can't open %s\n", *argv);          |                 return 1
                 return 1;                                       |             else
             } else {                                            |                 filecopy(fp, stdout)
                 filecopy(fp, stdout);                           |                 fclose(fp)
                 fclose(fp);                                     |     return 0 //[Original indent wrong!]
             }                                                   | /* filecopy: copy file ifp to file ofp */
         return 0; //[Original indent wrong!]                    | void filecopy(FILE *ifp, FILE *ofp)
 }                                                               |     int c
 /* filecopy: copy file ifp to file ofp */                       |     while ((c = getc(ifp)) != EOF)
 void filecopy(FILE *ifp, FILE *ofp)                             |         putc(c, ofp)
 {                                                               |
     int c;                                                      |
     while ((c = getc(ifp)) != EOF)                              |
         putc(c, ofp);                                           |
 }                                                               |

I already noted their page 51 example which highlights this exact problem deliberately. The SnowC on the right is automatically corrected.

 if (n > 0)                                       | if (n > 0)
     for (i = 0; i < n; i++)                      |     for (i = 0; i < n; i++)
         if (s[i] > 0) {                          |         if (s[i] > 0)
             printf("...");                       |             printf("...")
             return i;                            |             return i
         }                                        |         else /* WRONG */
 else /* WRONG */                                 |             printf("error -- n is negative\n")
     printf("error -- n is negative\n");          |

Hopefully you now have a better understanding of just how wrong I was when I originally thought of the SnowC concept and I thought it surely couldn't be all that hard. It was a hell of a challenge but I'm glad I stuck with it and I am very happy with the results.

I'll leave you with a couple of extra awful conversion tests that I used during development, starting with an example of singletons, braces, and a mix. Remember, SnowC is ignoring all the (possibly spurious) indentation of the original and recreating it with structural rigor.

(nested_if_else.c)

 int main(int argc, char** argv) {        | int main(int argc, char** argv)
   // All singletons.                     |   // All singletons.
   if (q1)                                |   if (q1)
     if (q2)                              |     if (q2)
       if (q3)                            |       if (q3)
         if (q4)                          |         if (q4)
           a4();                          |           a4()
         else                             |         else
           e4();                          |           e4()
       else                               |       else
         e3();                            |         e3()
     else                                 |     else
       e2();                              |       e2()
   else                                   |   else
     e1();                                |     e1()
   // All brace blocks.                   |   // All brace blocks.
   if (q1) {                              |   if (q1)
     if (q2) {                            |     if (q2)
       if (q3) {                          |       if (q3)
         if (q4) {                        |         if (q4)
           a4();                          |           a4()
         }                                |         else
         else {                           |           e4()
           e4();                          |       else
         }                                |         e3()
       }                                  |     else
       else {                             |       e2()
         e3();                            |   else
       }                                  |     e1()
     }                                    |   // Mixed.
     else {                               |   if (q1)
       e2();                              |     if (q2)
     }                                    |       if (q3)
   }                                      |         if (q4)
   else {                                 |           a4()
     e1();                                |         else
   }                                      |           e4()
   // Mixed.                              |       else
   if (q1) {                              |         e3()
     if (q2)                              |     else e2()
       if (q3) {                          |   else
         if (q4) {                        |     e1()
           a4();                          |
         }                                |
         else                             |
           e4();                          |
       }                                  |
       else {                             |
         e3();                            |
       }                                  |
     else e2();                           |
   }                                      |
   else {                                 |
     e1();                                |
   }                                      |
 }                                        |

Here is another challenging test for the if/else matching. The B is brace level and the S is singleton level.

(badelse.c)

 void main() {                                                          | void main()
     // All singletons.                                                 |     // All singletons.
     for (q1)                                                           |     for (q1)
         for (q2)                                                       |         for (q2)
             if (q3)                                                    |             if (q3)
                 while (q4)                                             |                 while (q4)
                     for (q5)                                           |                     for (q5)
                         a();                                           |                         a()
             else                                                       |             else
                 if                                                     |                 if
                     while                                              |                     while
                         for                                            |                         for
                             b();                                       |                             b()
                 else                                                   |                 else
                     c();                                               |                     c()
     ok1();                                                             |     ok1()
     // With some braces.                                               |     // With some braces.
     for (q1)                                                           |     for (q1)
         for (q2)                                                       |         for (q2)
             if (q3) {                                                  |             if (q3)
                 while (q4)                                             |                 while (q4)
                     for (q5)                                           |                     for (q5)
                         a();                                           |                         a()
             }                                                          |             else
             else {                                                     |                 if
                 if                                                     |                     while
                     while                                              |                         for
                         for                                            |                             b()
                             b();                                       |                 else
                 else                                                   |                     c()
                     c();                                               |     ok2()
             }                                                          |     // With challenging else requirements.
     ok2();                                                             |     if (d)                        // B1 S0
     // With challenging else requirements.                             |         while (w)                 // B1 S1
     if (d)                        // B1 S0                             |             if (c)               // B1 S2 --------+
         while (w)                 // B1 S1                             |                 if (b)            // B2 S0 -----+  |
             if (c) {              // B1 S2 --------+                   |                     for (f)       // B2 S1      |  |
                 if (b)            // B2 S0 -----+  |                   |                         if (a)    // B2 S2 ---+ |  |
                     for (f)       // B2 S1      |  |                   |                             a++  // B2 S3    | |  |
                         if (a)    // B2 S2 ---+ |  |                   |                         else      // B2 S2 ---+ |  |
                             a++;  // B2 S3    | |  |                   |                             a()  // B2 S3      |  |
                         else      // B2 S2 ---+ |  |                   |                 else              // B2 S0 -----+  |
                             a();  // B2 S3      |  |                   |                     b()          // B2 S1         |
                 else              // B2 S0 -----+  |                   |     // B2 S1         |
                     b();          // B2 S1         |                   |             else                  // B1 S2 --------+
             }                     // B2 S1         |                   |                 c()              // B1 S3
             else                  // B1 S2 --------+                   |     else                          // B1 S0
                 c();              // B1 S3                             |         d()                      // B1 s1
     else                          // B1 S0                             |     return                       // B1 S0
         d();                      // B1 s1                             |
     return;                       // B1 S0                             |
 }                                                                      |