IronJS Blog

All things IronJS and F#

JavaScript Quotations

with one comment

Lately I’ve been working on the ECMA3 conformance in IronJS, but last night I did a small side-tour into something completely different: JavaScript Quotations. The ideal is similar to the one found in F# code quotations or Lisp macros, not as evolved as any of them though – but still pretty nice. I wanna say right now that this is my own extension to the JavaScript language and it’s not something you can do in any browser or other implementation (that I know of).

What it does is it introduces a new symbol, @ – stolen from F#, which gives you access to the syntax tree of a function during runtime and allows you to modify it as you see fit and then compile it to a regular JavaScript function. While it could be abused to no end it allows for some pretty interesting possibilities. The example I’m going to show creates a function which reads a property out of an object, and optionally compiles a console.log statement into the function body.

function makeLoggedPropertyReader(includeLog, propertyName) {
    // Note the @ symbol infront of the function keyword
    var quoted = @function (x) {
        if(x) {
            console.log(x);
        }

        return x._;
    };

    // This is how the quoted structure looks like,
    // it's basically a syntax tree that you can
    // traverse, modify as you see fit and then compile

    /*
    quoted = {
        type: 19, // function
        body: [
            {
                type: 18, // if statement
                test: {
                    type: 5, // identifier
                    value: "x"
                },
                trueBranch: [
                    {
                        type: 9, // method call
                        target: {
                            type: 5, // identifier
                            value: "console"
                        },
                        member: {
                            type: 5, // identifier
                            value: "log"
                        },
                        arguments: [
                            {
                                type: 5, // identifier
                                value: "x"
                            }
                        ]
                    }
                ],
                elseBranch: {
                    type: 0 // void node
                }
            },
            {
                type: 25, // return
                value: {
                    type: 43, // property accessor
                    object: {
                        type: 5, // identifier
                        value: "x"
                    },
                    name: {
                        type: 5, // identifier
                        value: "_" // the value we're going to replace
                    }
                }
            }
        ]
    }
    */

    // The first statement in the qouted body
    // is the ifStatement, which we will conditionally
    // remove depending on the boolean value of includeLog
    if(!includeLog) {
        quoted.body[0] = Quotations.voidStatement();
    }

    // Pull the second statement out of the function body
    var returnStmt = quoted.body[1];

    // Pull the value node out of the return statement
    var propertyAccessor = returnStmt.value;

    // Set the value of the "name" node of the property accessor
    // to the string value of the propertyName that is passed in
    propertyAccessor.name.value = propertyName.toString();

    // We've modified our qouted expression
    // and we can now compile it so it
    // becomes a
    return quoted.compile();
}

// And here we'll use it:

var logged = makeLoggedPropertyReader(true, "myProp");
var notLogged = makeLoggedPropertyReader(false, "myProp");

var myObj = {myProp: "hello world"}

var xValue = logged(myObj); // will return and print the value of myProp to console.log
var xValue = notLogged(myObj); // will only return the value of myProp
About these ads

Written by Fredrik Holmström

March 26, 2011 at 10:24 am

Posted in IronJS

One Response

Subscribe to comments with RSS.

  1. I’ve been working on a little language recently, and I’ve found that a combination of ast quotation and substitution works wonders. (Disclaimer: these ideas were more or less stolen from LISP/Scheme, although I don’t know either language well enough to state whether they’re a good facsimile or not. :P)

    Imagine for a moment that you added @$ which caused the expression immediately following to be substituted into the quoted ast; your above example could be rewritten into something like:

    http://pastebin.com/qgYs4awx (I don’t know how this blog handles code in comments, so I won’t risk trying it.)

    For my uses, this sort of direct substitution tends to cover most cases. For more complex cases, I’ve made a simple ast api available that lets you pull apart and construct nodes.

    Of course, there’s the issue of how to work out where in the grammar to start for a given quotation. That’s partly why I went with a language that, apart from very few top-level statements, is all expressions. You could probably come up with something, however. Maybe force @ to be followed by either ‘function’, ‘statement’ or ‘expression’ to clarify what you’re quoting.

    Daniel

    March 27, 2011 at 4:23 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: