Creating New Nodes

Sometimes you may want to use AjaxMin.dll to parse JavaScript source into an Abstract Syntax Tree (AST), manipulate that tree by inserting new nodes of your own design, and then output the results. The problem is that AjaxMin uses a Visitor Pattern to traverse the tree, particularly when outputting the results. The issue there is that the visitors need to know about all the possible nodes, and the nodes need to know about the IVisitor interface. But if you want to add your own node when referencing the DLL, you can’t modify the IVisitor interface. So what do you do?

The AST nodes supplied by the AjaxMin.dll include a CustomNode class. This class should be the base class for any and all custom nodes you wish to insert into your AST. All you need to do is derive from CustomNode and override the virtual string ToCode() method. Your custom class’s implementation can be as simple or complex as you wish. For instance, here’s a super-simple implementation of a custom InjectedCode class that simply outputs whatever was set on the node’s Code property.

AjaxMin Version 5.x

In Version 5.x, AST nodes no longer require a reference to a JSParser object in their constructors, and the pattern for utilizing a JSParser object has changed.

    public class InjectedCode : CustomNode
    {
        public string Code { get; set; }

        public InjectedCode(Context context)
            : base(context)
        {
        }

        public override string ToCode()
        {
            return this.Code;
        }
    }

 

As an example of how this class might be used, let’s create a custom visitor class that operates on Block nodes by inserting an instance of our custom node at the top of every block:

    public class MyVisitor : TreeVisitor
    {
        public override void Visit(Block node)
        {
            base.Visit(node);
            if (node.Parent != null)
            {
                node.Insert(0, new InjectedCode(node.Context.FlattenToStart()) { Code = "FOO()" });
            }
        }
    }

 

An application could use this visitor by applying it to a parsed syntax tree returned from JSParser.Parse (assuming the variable “source” is of type String and contains the source code to parse):

    var parser = new JSParser();
    parser.CompilerError += (sender, ea) =>
        {
            Console.Error.WriteLine(ea.Error.ToString());
        };
    var settings = new CodeSettings
        {
            OutputMode = OutputMode.MultipleLines,
            KillSwitch = 8192
        };

    var block = parser.Parse(source, settings);
    var myVisitor = new MyVisitor();
    myVisitor.Visit(block);
    OutputVisitor.Apply(Console.Out, block, settings);

 

So given the following input source code:

    +function (obj)
    {
        for (var p in obj)
        {
            if (obj[p])
            {
                alert(obj[p]);
            }
        }
    }(window);

 

The following output would be sent to the standard out stream:

    +function(n)
    {
        FOO();
        for(var t in n)
        {
            FOO();
            if(n[t])
            {
                FOO();
                alert(n[t])
            }
        }
    }(window)
 

AjaxMin Version 4.x

In version 4.x of AjaxMin, nodes must take a JSParser parameter in their constructor.

public class InjectedCode : CustomNode
{
    public string Code { get; set; }

    public InjectedCode(JSParser parser)
        : base(null, parser)
    {
    }

    public override string ToCode()
    {
        return this.Code;
    }
}

As an example of how this might be used, here’s a custom visitor that when run on an AST, walks the tree, inserting one of these new nodes at the top of every Block node. Note the passing of the JSParser object to the constructor (not required for version 5.x):

class MyVisitor : TreeVisitor
{
    public MyVisitor() { }

    public override void Visit(Block node)
    {
        base.Visit(node);
        if (node.Parent != null)
        {
            node.Insert(0, new InjectedCode(node.Parser) { Code = "FOO()" });
        }
    }
}

This implementation basically inserts a statement “FOO()” at the top of every block (below the top-level block). So if my application did this:

var parser = new JSParser(source);
parser.CompilerError += (sender, ea) =>
{
    Console.Error.WriteLine(ea.Error.ToString());
};
var settings = new CodeSettings() 
{ 
    OutputMode = OutputMode.MultipleLines, 
    KillSwitch = 8192 
};
var block = parser.Parse(settings);
var myVisitor = new MyVisitor();
myVisitor.Visit(block);
Console.WriteLine(block.ToCode());

then this source code:

+function (obj)
{
    for (var p in obj)
    {
        if (obj[p])
        {
            alert(obj[p]);
        }
    }
}(window);

would get transformed into this:

+function(n)
{
    FOO();
    for(var t in n)
    {
        FOO();
        if(n[t])
        {
            FOO();
            alert(n[t])
        }
    }
}(window)

Summary

So remember: when designing new node classes to be added to the AST by your code, derive your new nodes from CustomNode and override the ToCode method. Everything should work fine from there.

Last edited Sep 16, 2013 at 6:42 PM by ronlo, version 18

Comments

No comments yet.