SnowC - Challenges - Some Final Pointers

:date: 2026-07-30 16:33

There is actually a point to this series on challenges I faced when creating a decent implementation of SnowC. I think the SnowC idea is very sensible and it is worth pursuing but while I'm proud of what I've accomplished, I'm not claiming to be our species' best candidate to implement the idea. And one day in the future  —  but not today!  —  maybe our robot friends can even improve on what I've done. So these posts serve as a guide for what someone will need to think about if they want to create a SnowC conversion system of their own. This post will round out some of the other random challenges I ran into. Some were expected, some not so much.

Literals

Let's start with the first thing you need to start with. It may come as a slight surprise that the first thing to deal with is character and string literals. I take a special interest in specifying text in programming languages so it came as no surprise to me to discover that strings must be isolated and dealt with first. It's pretty easy to see why if we imagine a string talking about the syntax of specifying strings.

char *help = "Use \" to specify strings in C: \"a string\"";

Obviously that's annoying but really SnowC doesn't care what your string says, it just needs your strings to not confuse things. That could happen in an example like this.

char *a = "char /*{ick}*/ *b = \"b\"; int c = 0; "; int c = 0;

You can use the c2snowc map diagnostic option (-m) to show how the characters of this line get classified, or mapped into general categories.

char *a = "char /*{ick}*/ *b = \"b\"; int c = 0; "; int c = 0;
CCCC_CC_C_$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$;_CCC_C_C_C;+

With the characters of the string properly identified it's much easier to see that there are only two proper semicolons here to worry about. The same idea applies to this line involving character literals.

char c = ';' == '\'' + ';'; c++;
CCCC_C_C_'''_CC_''''_C_''';_CCC;+

It may seem odd, but since strings can say any crazy thing, they must be neutralized before anything else is touched. And although the fail cases are subtle, character literals should be immediately neutralized next.

Comments

The next thing to isolate and neutralize must be modern double slash comments. This was originally a C++ thing and looks like this.

char *comments = "can get messy"; // So true!

This (everything after //) is the primary comment style that takes precedence over a secondary comment style. Old school K&R comments (/* which look like this */) are very clever. Too clever. Probably the biggest source of real problems is C's classic comment style. It's bad enough looking for the last meaningful character of a line knowing that any random junk can follow in the form of a comment. For example, the last meaningful character here is the first semicolon.

x /= 3; //= 3; ///= 3; /* This is totally valid C code because FML. */

But the problems get infinitely worse when you consider that classic C commenting can be mischievously injected anywhere. Looking for a thing that needs to follow a thing? Well, you better count on it following a near random collection of obfuscating garbage in the form of a comment.

So identify all the characters that are double slash comments and then identify all the other comment characters. Then don't forget about them, because they could possibly be causing mischief damn near anywhere!

Just remember that you can comment out preprocessor syntax, so deal with comments first. Which brings us to preprocessor problems.

Preprocessor

Comments are hard because they can lurk anywhere muddling important semantic details. But preprocessor directives have a different problem. They are often used to swap in chunks of real C code at the last minute. I actually did not do a super deep dive into insane preprocessor abuse. I am pretty tame with my usage personally and, well, it was a lower priority for me. Sorry.

Basically if the first significant character of a line is a # I'm calling it a preprocessor directive and it gets ignored much like a comment. If there is some kind of #if, #ifdef, #elif, #else or similar construction with some ugly confusing code hiding inside, well, you'll probably need to take special care.

One conceptual problem is that the SnowC to C conversion injects the missing C syntax (braces, semicolons) back into the code. If a preprocessor range isn't anticipating this, there can be problems. Here's a simplified example equivalent to something I found in the Linux kernel starting with the original C.

void fn() {
  for (q1) {
    if (q2) {
      a();
      #if (FANCY_FEATURE == ENABLED)
      fancy_feature();
      #endif
    }
  }
}

The SnowC is fine.

void fn()
  for (q1)
    if (q2)
      a()
      #if (FANCY_FEATURE == ENABLED)
      fancy_feature()
      #endif

But look what happens when it goes back to C.

void fn() {
  for (q1) {
    if (q2) {
      a();
      #if (FANCY_FEATURE == ENABLED)
      fancy_feature();
    }
  }
}
      #endif

Leaving out all the closing braces when the fancy feature is not enabled will be a serious problem. Comments also can be moved to undesired locations in this way but that's less problematic.

I could have all closing brace generation wait until after any preprocessor lines (and comments) but this is sometimes not the right choice either. I suspect it is the more common choice and I may switch the default to it. I'm starting to think that the correct solution is for SnowC to insist you place your preprocessor statement's level intentions the same way as everything else  —  with indent level. Consider it on the to do list.

Labels

C has a quirky syntax for "labels" which are targets for the goto statement. Using goto in the first place is not really encouraged so labels aren't even all that common. But they're common enough that they need to be dealt with.

In C, a label is generally a word followed by a colon. Like this.

found:

Easy, right? Well, not so fast! Have a look at some of my test code where I try to anticipate label problems.

label1:
    label_with_leading_spcs:
label_with_trailing_spcs:
label_with_intercolon_spcs   :
/*precomment*/label_with_precomment:
label_with_post_comment:// Post comment.
label_with_post_comment_trailings:/* Post comment w trailing. */
label_with_intercomment/* Inter comment.*/:
_yes_a_label:
_yes_2_label:
3_not_a_label:  // Can't start with a number.
not%a_label:
not^a_label:
default_plus_is_label:
default_ : // Is a label.
default:   // Technically not a label despite appearances.

C has a way of making you think it's simple and obvious. And then you dig into it and find out, yikes, a lot can be taken for granted by the programmer.

So why exactly do we need to know what text is a label? Because SnowC needs to mostly ignore them sometimes. For example when they are between a singleton control word and the resolving statement.

Unfortunately C has another use for colons. A couple in fact. Look at this syntax  —  which I checked does actually compile.

 int not_a_label = 0;            | int not_a_label = 0
 x2 = x?                         | x2 = x?\
 not_a_label:                    | not_a_label:
 99;                             | 99

Fortunately, despite failing to realize that not_a_label: is not a label, the SnowC conversion just happens to work because of, well, luck. I am walking away from this one.

Nugatory

If you don't know what the word "nugatory" means, I suggest you look it up (click here!) and start using it. It's a great word!

You can probably figure it out from my test file called nugatory.c which gave me a surprising amount of grief. Here it is with the generated SnowC on the right.

 //TEST: Blank statements and blocks.           | //TEST: Blank statements and blocks.
 if (q0) {} // Empty block.                     | if (q0) {}\ // Empty block.
 if (q1) ; // Blank statement.                  | if (q1)  // Blank statement.
 else if (q2) ;                                 | else if (q2)
 else if (q3) {                                 | else if (q3) {\
 }else if (q4) {      // Some spaces.           | }else if (q4) {\      // Some spaces.
 } else if (q5)  // Ick.                        | }else if (q5)\  // Ick.
 { }                                            | {}\
 else if (q6) {                                 | else if (q6) {\
     /* Pass. */                                | /* Pass. */
 } else {// Uncomment for diagnostic.           | }else {\// Uncomment for diagnostic.
     //printf("Error:\n"); /* ERROR */          | //printf("Error:\n"); /* ERROR */
 }                                              | }\
 x++;                                           | x++
                                                |

Dang that's gnarly! But if you follow the simple SnowC rules to convert that back to C, it will produce semantically equivalent C code.

Note the final line is blank. I also have several tests for blank lines at the beginning and end and other inconvenient places. It's little things like that which can be a real pain in the ass.

I'm sure there are other challenges I could highlight but that concludes my tour of the main problems that one must deal with when trying to pull the redundancy out of C code. It may not be easy, but for practical use, I have demonstrated that it is possible.