Nested Preprocessor Directives?

May 15, 2012 at 4:34 AM

It appears the Minifier does not support nested preprocessor directives. I had code that looked like this:

	///#IFDEF ASPNETROOT
	///#ELSE
	if (!$.support.cors) {
		var script = document.createElement('script');
		///#IFDEF MIN
			///#IFDEF CFDNS
			script.src = '//a.' + domain + '/ui/flxhr/flxhr.js';
			///#ELSE
			script.src = '//' + domain + '/ui/flxhr/flxhr.js';
			///#ENDIF
		///#ELSE
			script.src = '/ui/flxhr/flxhr.js';
		///#ENDIF
		document.body.insertBefore(script, document.body.getElementsByTagName('script')[0]);
	}
	///#ENDIF

With ASPNETROOT defined. What I got out eliminated the if statement, but the closing curly brace of that if statement was kept. As a result the function holding all of this code was closed prematurely and the code that followed broke.

Is this a known bug? Should I expect this to work? Could this be fixed so it does work?

May 15, 2012 at 7:30 AM
Edited May 15, 2012 at 7:42 AM

I've verified that the preprocessor directives are extremely limited; they support only the very narrow scenario discussed in the blog post documentation about them:

///#IFDEF A
stuff for A
///#ELSE
stuff for not A
///#ENDIF

There's no support for not, and, or, or nested ifs. So if for example you wanted to say #IFDEF !A, you actually have to do this:

///#IFDEF A
///#ELSE
stuff for !A
///#ENDIF

If you have 2 preprocessor variables, things get much worse. If you wanted to say #IFDEF A && B, you can't do this:

///#IFDEF A
///#IFDEF B
stuff for a and b - does not work
///#ENDIF
///#ENDIF

The minifier does not support nested ifs. That will check for A, but it won't check for B - the nested condition performs as though it's not there. You can fake things in code that calls the minifier to for example provide A_B to mean A && B. Unfortunately there also is no support for NOT, so you must also create additional entries, like A_B, $A_B, A_$B, $A_$B. If you have more variables this quickly explodes (to the square of the number of variables). Nonetheless this lets you fake support for AND and NOT, like:

///#IFDEF A
Stuff for A
    ///#ENDIF
    ///#IFDEF A_B
    Like saying IF B inside of IF A
    ///#ENDIF
    ///#IFDEF A_$B
    Like saying ELSE for B condition inside A
    Can't just use ELSE here because it would mean not A not B
    ///#ENDIF
    ///#IFDEF A
Have to resume the if from above to simulate still being inside IF A
///#ENDIF

It would help a lot if the minifier supported either constants and the ability to remove dead code based on them, or preprocessor directives like the ones we're used to in C# - supporting if, elif, not, and, and or.

Coordinator
May 15, 2012 at 4:09 PM

Yes, the functionality is very rudimentary at this time; there is no AND, or OR, and nesting is not supported. I could've sworn I had NOT-functionality through an ///#IFNDEF statement, but I just checked the code and it's not there. Nor is there an ///#ELIFDEF or //#ELIFNDEF branch.

I'll see what I can do to add a little more functionality to those structures.  

Coordinator
May 15, 2012 at 5:25 PM

Okay, you should give release 4.52 a try. I made it so the preprocessor statements are nestable, so your original code should now work as you expect. And I added ///#IFNDEF, so you don't have to do that awkward ///#IFDEF ASPNETROOT\n///#ELSE ...\n///#ENDIF structure. I didn't add the else-if style statements, though; I figure with the nesting you should be able to create the desired behavior. If you (or anyone else) really wants the else-if statements, let me know and I'll add them.

I also didn't do the expressions. It would complicate things far more than simply adding the nesting, and I think the desired behavior can be replicated with the existing structure, although not as efficiently. If you feel expressions are really worth having, let me know and I can see what I can do there as well.

May 16, 2012 at 7:29 AM

Thank you! I appreciate it. I'll give it a go tomorrow.

The most efficient route for more complex conditions might be to just implement constants and dead code removal in response to those constants. Then you can leverage existing expression logic. The extra work seems to be around getting constants to be handled the way literals are already to simplify expressions.

Jun 23, 2012 at 3:17 PM

Clearly it took some time to come back to my preprocessor defines and build a test harness for them. I set a script file aside with the preprocessor lines rewritten to leverage nesting and have verified this as fixed - thanks for adding this.