n2liquid's sandbox

Archive for the ‘Shameless Soliloquy’ Category

So, I’ve been working on a pretty small project lately called js-emitter (GitHub). I’m still just trying out some pieces of code, so I haven’t really given the repo’s structure much thought (I’m just editing a single test.js file).

The idea behind js-emitter is to emit (human-readable) JavaScript source-code based on a syntax tree I’m defining for it. Overall it’s pretty simple to implement, except that for human-readable source-code I wouldn’t want it riddled with unnecessary parentheses.

Take the following syntax tree as an example:

// Syntax tree for: (my_callback = fn)()
{
    type: 'call-expression',

    target:
    {
        type: 'assignment-expression',

        assignee:
        {
            type: 'identifier',
            name: 'my_callback'
        },

        assigned:
        {
            type: 'identifier',
            name: 'fn'
        }
    },

    args: []
}

In this case, the parentheses wrapping the call target expression are necessary, otherwise the expression would mean something entirely different: my_callback = fn().

In some other cases, however, they are not necessary. Take this example:

// Syntax tree for: callback_array[2]()
{
    type: 'call-expression',

    target:
    {
        type: 'subscript-expression',

        target:
        {
            type: 'identifier',
            name: 'callback_array'
        },

        subscript:
        {
            type: 'number',
            value: 2
        }
    },

    args: []
}

If parentheses were emitted for the target expression here, it would result in cluttered code: (callback_array[2])().

So I need a way to determine when they are needed as the code is emitted.

Basically parentheses are only needed when operator expressions of lower precedence are being emitted as operands for an operator expression of higher precedence. In order to evaluate this scenario, it will be necessary to add metadata to the generated expressions. Specifically, generated expressions will need a “precedence” property that will be used during composite expression emission to determine whether to add parentheses or not.

Let’s analyze how this solution deals with a complex composite expression:

// Syntax tree for: (a + b) * (c = d)()
{
    type: 'multiplication-expression',

    left:
    {
        type: 'addition',

        left:
        {
            type: 'identifier',
            name: 'a'
        },

        right:
        {
            type: 'identifier',
            name: 'b'
        }
    },

    right:
    {
        type: 'call-expression',

        target:
        {
            type: 'assignment-expression',

            assignee:
            {
                type: 'identifier',
                name: 'c'
            },

            assigned:
            {
                type: 'identifier',
                name: 'd'
            }
        },

        args: []
    }
}

The emitter starts at the multiplication expression. It knows the precedence of a multiplication in JavaScript is 5. It then proceeds to emit both left and right expressions. For the left expression, it knows it has to wrap it in parentheses since the precedence of addition is 6, which is lower than multiplication (ps.: precedence is numbered in reverse order; the higher the number, the lower the precedence). For the right side expression, it knows there’s no need for parentheses since the precedence of the function call operator is 2, which is higher than multiplication.

Inside the call expression (precedence 2), the target expression is an assignment (precedence 17). For that reason, since the assignment expression has lower precedence, it warrants parentheses.

In the end, we did get back to (a + b) * (c = d)(), so it looks like that logic will do.

Fun! I had to write this post in order to get to that conclusion. I was having a block when I started 🙂

I hope I’m not missing anything important here, I’m not sure if some combination of expressions can’t render this logic insufficient…

Time will tell.

Touhou's Sakuya


un.ma.i!

Twitter (technical)

Error: Please make sure the Twitter account is public.

Twitter (personal)

Error: Twitter did not respond. Please wait a few minutes and refresh this page.

Get messaged when I post something new!
Just enter your e-mail and hit Follow:

Join 171 other followers

%d bloggers like this: