The Magic of IE, Chapter 385: Conditional Compilation

Recently Rob Hogan reported in Sencha forum, that the uncompressed source code of Ext JS fails to load in IE. For some strange reason IE chokes at the following code:

someObj = {
    //@private
    someProp: "foo"
}

Giving an error message Expected ':'. And the strangest thing is, when you add just one space to comment:

someObj = {
    // @private
    someProp: "foo"
}

IE will parse it just fine.

Doesn't it just make you cheer to the wonderful and magical IE?

Thankfully Rob was kind enough to track down the issue to another library he was using. That happened to use another amazing feature of IE: Conditional Compilation (CC). Most people know about IE conditional comments for HTML, this is pretty much the same, but for JavaScript.

The greatness is turned off by default. You need to enable it with:

//@cc_on

After this there's no turning back - all the comments following this declaration will be subject to CC.

The thing supports simple if statements and some variables:

/*@cc_on
  @set @version = @_jscript_version
  @if (@_win32)
    document.write("You are running 32 bit IE " + @version);
  @elif (@win_16)
    document.write("You are running 16 bit IE " + @version);
  @else @*/
    document.write("You are running another browser or an old IE.");
/*@end @*/

That's pretty much all the syntax there is. But this doesn't quite explain why //@private should cause any trouble as there is no @private statement.

Still, there is a bit more syntax:

var isMSIE = /*@cc_on!@*/false;

That's some nifty JavaScript from Dean Edwards which evaluates to true if browser is IE. When CC is already turned on, it can be abbreviated to:

var isMSIE = /*@!@*/false;

Nice. So now I have a theory of what //@private does: it outputs the identifier private into the document. Let's test that:

//@ alert
("Hello world");

I run the program and get a nice "Hello world" popup. Great! Now let's try without the space:

//@alert
("Hello world");

I run it and... oh... IE says: Function expected.

Oh man! IE just had us again. What kind of amazing logic is he following?

After some more reading about conditional compilation I come to the conclusion that @private is a variable. But those CC variables can have only two types of values: Boolean or Number.

So let's see what kind of value //@private yields:

var foo = //@private
alert(foo);

And it outputs... NaN.

Isn't it just neat how all undefined variables are initialized to NaN. Now it all makes perfect sense. This code:

someObj = {
    //@private
    someProp: "foo"
}

would be seen by IE as:

someObj = {
    NaN someProp: "foo"
}

And NaN happens to be a valid key in object literal, because the key can be either identifier, String or Number... and NaN (Not a Number) obviously is a Number. Well... obviously if you have been programming in JavaScript for way too long.

Thank you all for reading. This was chapter 385 from my book "The Magic of IE" which I will never write.

Kirjutatud 2. novembril 2011. Kommentaarid (4)

Meta Regular Expression

As part of JSDuck I needed to write a lexer for JavaScript source code. This consisted mostly of writing regular expressions to recognize various JavaScript tokens: identifiers, numbers, strings, ... and regular expressions. That's of course a meta regular expression - a regular expression to match a regular expression. It turned out to be trickier than I thought.

First off the / character can be the beginning of comment, regex or a division operator. I've already covered this part in an earlier blog post, so I'm going to continue from there, assuming we already know that / is a beginning of regex. So what would a meta-regex look like? Let's take the most naive approach possible:

%r{/.*?/[gim]*}

I'm using special regex delimiters of Ruby %r{ ... } to avoid escaping the / characters, keeping the regex more readable. Thankfully to our lexer the regular expression in JavaScript can only be delimited by /.

This meta-regex probably matches about 80% of regexes in the wild. It even supports optional modifiers. But it fails as soon as the regex contains an escaped / like this one:

/foo\/bar/

Fortunately this is a pretty standard problem. We would stumble upon a similar one when writing a regex to match a string where the delimiters can also be escaped. So here's a solution:

%r{/([^/\\]|\\.)*/[gim]*}

The text between regex delimiters can consist of:

This is quite neat and compact, and one would think that it covers all possible JavaScript regular expressions. And indeed, it's going to probably cover about 99.9% of all regular expressions in the wild. But I wouldn't be talking about this if I hadn't stumbled upon a valid regex that it didn't cover:

/[/]/

That's right, inside a character set [...] the only meta-characters that need to be escaped are ] and \, meaning that the regex delimiter can also be there without needing to be escaped.

Now that's going to get a bit ugly, as we need to use different matching rules between character set delimiters. Here's a regex to match only the character set part of a regex:

%r{\[([^\]\\]|\\.)*\]}

This should look familiar, as we repeat the pattern we already used above. The text between charset delimiters can consist of:

It might hurt your eyes, but it's quite simple really.

Now all that's left to do, is merge this into the whole meta-regex:

META_REGEX = %r{
  /               (?# beginning    )
  (
    [^/\[\\]      (?# any character except \ / [    )
    |
    \\.           (?# an escaping \ followed by any character    )
    |
    \[ ([^\]\\]|\\.)* \]    (?# [...] containing any characters including /    )
                            (?# except \ ] which have to be escaped    )
  )*
  /[gim]*         (?# ending + modifiers    )
}x

Here I'm using the x modifier to ignore the whitespace and I'm also documenting the whole thing with comments.

I'm hoping this now finally covers 100% of JavaScript regular expressions. But this meta regular expression is complex, things might go wrong when they aren't simple...

Kirjutatud 19. oktoobril 2011. Kommentaarid (3)

Mixed feelings about JavaScript

Let me say it out clear: there is no Mixed type in JavaScript.

Some time ago our code contained a lot of comments that documented function parameters as Mixed. For example:

/**
 * Adds item to container.
 * @param {Mixed} item Any type of item you wish to add.
 */
function add(item) {

Then somebody came and told me: there is no Mixed type in JavaScript. Oh yeah, I realized, a variable can't be of type X when there is no such type. How silly of me.

But how to document such an anything-goes parameter then? Luckily enough, everything in JavaScript is an object, all inheriting from Object. So if I just specify the type as Object, it will essentially mean that the value can be any JavaScript object.

And so I did. Replacing all the occurrences of Mixed with Object. Well... not all. Often the variable wasn't of anything-goes type, rather an alteration between a select few types. Like it could have been either Number or String, but the author was too lazy, so he just wrote Mixed.

Oh what a happy man I was when I went through and corrected all these bogus type definitions. And I made JSDuck throw a warning when it sees Mixed or any other unknown type. All was well...

...and then it came haunting me.

You know, everything really inherits from Object in JavaScript:

> Object.prototype.foo = 123
123
> "hello".foo
123
> (5).foo
123

But everything doesn't quite act like an object:

> x = "hello"
"hello"
> x.foo = 123
123
> x.foo
undefined
> x instanceof Object
false
> x instanceof String
false

Therefore there is quite a bit difference between a function that accepts an Object:

/**
 * Copies properties of second object into first one.
 * @param {Object} obj1
 * @param {Object} obj2
 */
function merge(obj1, obj2) {
    for (var i in obj2) {
        obj1[i] = obj2[2];
    }
}

and a function that accepts any value:

/**
 * Wraps item inside array if it's not an array already.
 * @param {Object} obj
 * @return {Array}
 */
function toArray(obj) {
    return obj instanceof Array ? obj : [obj];
}

But you can't tell the difference if you look at the documented parameter types. That's not good. The role of the documentation should be to tell you, which is which.

Let me repeat it again: there is no Mixed type in JavaScript. But this doesn't mean the Mixed type can't have it's place in the documentation.

One should be careful though. Use of Mixed should be the last resort, reserved for cases where you really accept any value whatsoever.

With that being said, I'm reintroducing the support of Mixed type into JSDuck.

Kirjutatud 30. septembril 2011. Kommentaarid (3)

Trinoloogialeht

Eesti Trinoloogide Maja. Eesti trinoloogiahuviliste avalik kogunemiskoht. info@triin.net

Peamenüü

Viimased artiklid

RSS, RSS kommentaarid, XHTML, CSS, AA