HomeBlogJavaScript... wat?!

JavaScript... wat?!

Following this hilarious talk, let me add my preferred JavaScript WAT #1.  Can you spot the error in the following code:

[ The semantics of the code aren't really important here, I'm just pasting it all to make it match real-world difficulty.  If I pasted only the line with the error + a few context lines, you'd spot it immediately. ]

[
    //// local vars namespace
    ["LVAR", "i j", {
        run: function(m) {
            //max_stat("lvar_frame", this.i);
            //max_stat("lvar_index", this.j);
            m.push(frame(m.env, this.i)[this.j]);
        }
    }],
    ["LSET", "i j", {
        run: function(m) {
            frame(m.env, this.i)[this.j] = m.top();
        }
    }],
    //// global/dynamic vars namespace
    ["GVAR", "name", {
        run: function(m) {
            m.push(m.gvar(this.name));
        }
    }],
    ["GSET", "name", {
        run: function(m) {
            m.gset(this.name, m.top());
        }
    }],
    ["BIND", "name i", {
        run: function(m) {
            m.bind(this.name, this.i);
        }
    }],
    //// global functions namespace
    ["FGVAR", "name", {
        run: function(m) {
            var f = this.name.func();
            if (!f) console.error("Undefined function", this.name);
            m.push(f);
        }
    }],
    ["FGSET", "name", {
        run: function(m) {
            this.name.setv("function", m.top());
        }
    }],
    ////
    ["POP", 0, {
        run: function(m) {
            m.pop();
        }
    }],
    ["CONST", "val", {
        run: function(m) {
            m.push(this.val);
        }
    }],
    ["JUMP", "addr", {
        run: function(m) {
            m.pc = this.addr;
        }
    }],
    ["TJUMP", "addr", {
        run: function(m) {
            if (m.pop() !== null) m.pc = this.addr;
        }
    }],
    ["FJUMP", "addr", {
        run: function(m) {
            if (m.pop() === null) m.pc = this.addr;
        }
    }],
    ["BLOCK", 0, {
        run: function(m) {
            // this is moderately tricky: we can't do
            //   m.env = new LispCons([ new LispBlockRet(m) ], m.env);
            // I'll let you figure out why.
            var frame = [];
            m.env = new LispCons(frame, m.env);
            frame[0] = new LispBlockRet(m);
        }
    }],
    ["LJUMP", "addr", {
        run: function(m) {
            m.pop().run(m, this.addr);
        }
    }],
    ["LRET", "addr", {
        run: function(m) {
            var bret = m.pop(), val = m.pop();
            bret.run(m, this.addr);
            m.push(val);
        }
    }]
    ["NOT", 0, {
        run: function(m) {
            m.push(m.pop() === null ? true : null);
        }
    }],
    ["SETCC", 0, {
        run: function(m) {
            m.uncont(m.top());
        }
    }],
    ["SAVE", "addr", {
        run: function(m) {
            m.push(m.mkret(this.addr));
        }
    }],
    ["RET", 0, {
        run: function(m) {
            var noval = m.f.noval;
            var val = m.pop();
            m.unret(m.pop());
            if (!noval) m.push(val);
        }
    }],
    ["CALL", "count", {
        run: function(m){
            var closure = m.pop();
            if (m.trace) m.trace.push([ closure, m.stack.slice(-this.count) ]);
            m.n_args = this.count;
            m.code = closure.code;
            m.env = closure.env;
            m.pc = 0;
            m.f = closure;
        }
    }],
    ["LET", "count", {
        run: function(m){
            var count = this.count;
            var frame = new Array(count);
            while (--count >= 0) frame[count] = m.pop();
            m.env = new LispCons(frame, m.env);
        }
    }],
    ["ARGS", "count", {
        run: function(m){
            var count = this.count;
            if (count != m.n_args) {
                console.error(m.f);
                throw new Error("Wrong number of arguments - expecting " + count + ", got " + m.n_args);
            }
            var frame = new Array(count);
            while (--count >= 0) frame[count] = m.pop();
            m.env = new LispCons(frame, m.env);
        }
    }],
    ["ARG_", "count", {
        run: function(m) {
            var count = this.count;
            var passed = m.n_args;
            if (passed < count) throw new Error("Insufficient number of arguments");
            var p = null;
            while (passed-- > count) p = new LispCons(m.pop(), p);
            var frame = new Array(count + 1);
            frame[count] = p;
            while (--count >= 0) frame[count] = m.pop();
            m.env = new LispCons(frame, m.env);
        }
    }],
    ["FRAME", 0, {
        run: function(m) {
            m.env = new LispCons([], m.env);
        }
    }],
    ["VAR", 0, {
        run: function(m) {
            m.env.car.push(m.pop());
        }
    }],
    ["VARS", "count", {
        run: function(m) {
            var count = this.count, a = m.env.car, n = a.length;
            while (--count >= 0) a[n + count] = m.pop();
        }
    }],
    ["UNFR", "lex spec", {
        run: function(m) {
            if (this.lex) m.env = rewind(m.env, this.lex);
            if (this.spec) m.denv = rewind(m.denv, this.spec);
        }
    }],
    ["FN", "code name", {
        run: function(m) {
            m.push(new LispClosure(this.code, this.name, m.env));
        },
        _disp: function() {
            return "FN(" + D.serialize(this.code) + (this.name ? "," + LispMachine.serialize_const(this.name) : "") + ")";
        }
    }],
    ["PRIM", "name nargs", {
        run: function(m) {
            var n = this.nargs;
            if (n == -1) n = m.n_args;
            var ret = this.name.primitive()(m, n);
            if (ret !== false) m.push(ret);
        }
    }],
    ["NIL", 0, { run: function(m) { m.push(null) } }],
    ["T", 0, { run: function(m) { m.push(true) } }],
    ["CONS", 0, {
        run: function(m) {
            var b = m.pop(), a = m.pop();
            m.push(new LispCons(a, b));
        }
    }],
    ["LIST", "count", {
        run: function(m) {
            var p = null, n = this.count;
            if (n == -1) n = m.n_args;
            while (n-- > 0) p = new LispCons(m.pop(), p);
            m.push(p);
        }
    }],
    ["LIST_", "count", {
        run: function(m) {
            var p = m.pop(), n = this.count;
            if (n == -1) n = m.n_args;
            while (--n > 0) p = new LispCons(m.pop(), p);
            m.push(p);
        }
    }]

].map(function(_){ defop(_[0], _[1], _[2]) });

I'm a touch typist, I get 80 wpm with close to 100% accuracy, I'm extremely careful about how I write—even though English is not my native language—well generally I'd say I pay a lot of attention to this stuff.  But still, shit happens!  The code above is valid, syntactically, although the error is blatant and I'd prefer if the JavaScript compiler would yell at me at parse time about it.  If you don't notice the problem immediately, it can take hours to debug (the error messages you get usually have nothing to do with that missing comma; if you're unlucky it can take days or weeks for the problem to manifest...  WAT.).

Even js2-mode doesn't notice it, because by JavaScript specification, that syntax is okay.

WHAT were they thinking?

Luckily I'm a grown up now and I triple-fucking-check every comma in an array of arrays.

Comments

  • By: Cristian TincuFeb 16 (00:52) 2012RE: JavaScript... wat?! §

    In JavaScript, the “[]” sequence has two uses:
    — Array literal;
    — Property accessor.

    Let’s combine them:
    [1, 2, 3, 4, 5][3, 4]

    [1, 2, 3, 4, 5] is an Array literal, which is also an Object. [3, 4] is a property accessor for this Object. (3, 4) evaluates to 4, because the comma is also an operator (but this is a different story). Therefore, the whole expression evaluates as 5.

    Tricky.

  • By: thornFeb 16 (11:39) 2012RE: JavaScript... wat?! §

    JSHint immediately finds it.

  • By: Cristian TincuFeb 16 (16:02) 2012RE[2]: JavaScript... wat?! §

    The question here—and I suppose *that* was what you were pondering about—is why is this permitted:
    [1, 2, 3, 4, 5][{}]

    • By: mishooFeb 16 (16:20) 2012RE[3]: JavaScript... wat?! §

      Exactly.  I know why it happens, of course — the object is stringified as "[object Object]" and then the result is undefined because my array doesn't have such property.

      How could they possibly think there might be a legitimate use of this crap?

      Type inference isn't rocket science these days, the compiler could be a bit less dumb.  I wouldn't mind a runtime error either, but returning `undefined` is absolutely useless.

Page info
Created:
2012/02/15 22:26
Modified:
2012/02/15 22:49
Author:
Mihai Bazon
Comments:
4
See also