A parseExpression example

47 views
Skip to first unread message

Michael

unread,
May 23, 2011, 4:09:23 PM5/23/11
to JSShaper
Hi Olov et al,


I'm struggling with what must be a trivial thing to do. I'm hoping you
can give a quick example of how to accomplish this, I assume by using
parseExpression to match a pattern like below.

I want to replace this:

var a,
b = anything,
c = {},
d;

with this:

var a;
var b = anything;
var c = {};
var d;

Obviously there could by 1 or n-many members of the original var list.
Any hints?

Thanks,
Michael

Olov Lassus

unread,
May 23, 2011, 6:58:24 PM5/23/11
to jssh...@googlegroups.com
23 maj 2011 kl. 22.09 skrev Michael:

> I want to replace this:
>
> var a,
> b = anything,
> c = {},
> d;
>
> with this:
>
> var a;
> var b = anything;
> var c = {};
> var d;
>
> Obviously there could by 1 or n-many members of the original var list.
> Any hints?


Sure. Here are some hints:

An interactive shell is your friend to learn the structure of a node type (VAR in this case). I use d8:
~/projects/js/jsshaper/src % d8 --shell shaper.js
V8 version 3.3.1 [console: readline]

d8> var vars = Shaper.parseExpression("var a, b = anything, c = {}, d")
d8> vars.printTree()
VAR: var @, @, @, @ < root
IDENTIFIER: a < VAR.children[0]
ASSIGN: @ = @ < VAR.children[1]
IDENTIFIER: b < ASSIGN.children[0]
IDENTIFIER: anything < ASSIGN.children[1]
ASSIGN: @ = @ < VAR.children[2]
IDENTIFIER: c < ASSIGN.children[0]
OBJECT_INIT: {} < ASSIGN.children[1]
IDENTIFIER: d < VAR.children[3]

The template to match for is easy: "var $, $$". We can verify this:
d8> Shaper.match("var $, $$", vars)
1

For each of the remaining vars (VAR.children[1..3]) you want to create a new VAR node and then hook in the var initializer. I show you how to do this for one of them:
d8> var v = Shaper.parseExpression("var $")
d8> Shaper.replace(v, vars.children[1])

Let's see how that worked:
d8> v.printTree()
VAR: var @ < root
ASSIGN: @ = @ < VAR.children[0]
IDENTIFIER: b < ASSIGN.children[0]
IDENTIFIER: anything < ASSIGN.children[1]
d8> v.getSrc()
var b = anything.

Finally, you must hook the new VAR nodes into the tree, replacing the old VAR node. Assuming that the original VAR's parent-node is a SCRIPT, you'll just splice them into SCRIPT.children. Splice SCRIPT.srcs as well. See my last mail to Juho for more info on that. You'll find the reference to the parent in ref.base. ref.prop[] contains "children" and the position, again assuming a parent SCRIPT. If this is tricky for you just let me know and I'll code up a Shaper helper similar to how Shaper.insertArgument is used to splice nodes into a funcall argument-list. Actually Shaper.insertArgument should work fine for you as-is if you hack it to extend the Assert and splice a "\n" instead of ", " in srcs.

By the way was scoper of any help to you?

/Olov

Olov Lassus

unread,
May 25, 2011, 6:40:54 PM5/25/11
to jssh...@googlegroups.com
23 maj 2011 kl. 22.09 skrev Michael:

> I'm struggling with what must be a trivial thing to do. I'm hoping you
> can give a quick example of how to accomplish this, I assume by using
> parseExpression to match a pattern like below.
>
> I want to replace this:
>
> var a,
> b = anything,
> c = {},
> d;
>
> with this:
>
> var a;
> var b = anything;
> var c = {};
> var d;

I coded it up in the var-splitter plugin (pushed to github). I refactored the insertArgument helper into something more generic (insertBefore/insertAfter) and teamed it up with remove.

The plugin matches the var pattern against a template with Shaper.match, creates new VAR statements with Shaper.replace, hooks in nodes under those with Shaper.insertAfter and finally removes the same nodes one by one from the original VAR statement with Shaper.remove.

Shaper("var-splitter", function(root) {
var templ = Shaper.parse("var $, $$");
return Shaper.traverse(root, {pre: function(node, ref) {
if (Shaper.match(templ, node)) {
// VAR node has at least two children
// keep the first child in the existing VAR node,
// create new VAR nodes for the rest
var insertIndex = Number(ref.properties[1]); // ref.properties: ["children", index]
var rest = node.children.slice(1);

for (var i = 0; i < rest.length; i++) {
var varNode = Shaper.replace("$;", Shaper.replace("var $", rest[i]));
Shaper.insertAfter(new Ref(ref.base, "children", insertIndex++), varNode);
Shaper.remove(new Ref(node, "children", 1));
}
}
}});
});

/Olov

Michael

unread,
May 28, 2011, 5:59:47 AM5/28/11
to JSShaper
Hi Olov,

The var splitter plugin is a huge help to me. I use it to simplify the
source code before doing some analysis on it. So thanks very much. One
problem I found was that it throws an "Assertion failed" error on the
following input:

for ( var i = 0, length = foo.length; i < length; i++ ) {
}

Seems, in the above case

(new Ref(ref.base, "children", insertIndex++)).properties.length

Results in 1, when Shaper.insertAfter expects it to be 2.

I'm not sure how to fix that myself, but I do just guard against it by
not calling insertAfter in that case. I'm sure you can think of a
better solution!

Regards,
Michael

Olov Lassus

unread,
May 28, 2011, 3:20:54 PM5/28/11
to jssh...@googlegroups.com
28 maj 2011 kl. 11.59 skrev Michael:

> One
> problem I found was that it throws an "Assertion failed" error on the
> following input:
>
> for ( var i = 0, length = foo.length; i < length; i++ ) {
> }

That won't work in var-splitter's current shape - thanks for bringing it up. It currently assumes the VAR-node's direct parent to be a SCRIPT or BLOCK. In the for-example above, FOR is parent to the VAR so there's another level in between. You should be able to see the same issue in <if (1) var x, y>, this time because there's no BLOCK, just a statement.

What result would you like? This?
var i = 0;
var length = foo.length;
for (; i < length; i++) {
}

I'm redoing how SEMICOLON works (by diverging from Narcissus) and that will make a similar problem turn up in var-splitter and other plugins, so it needs solving anyways. More about that that later.

Here are the different alternatives for fixing var-splitter:
1. Insert all VAR nodes at the beginning of the function instead of directly prior to the existing VAR. It's easy to create a function stack to facilitate this.
2. Search for two templates instead of one: "var $, $$" and "for (var $, $$; $; $) $". You'll then catch the for-var when you have all the context you need.
3. A bigger change to Shaper that allows the traverse callback to get not only the Ref of the current node, but also Refs to all ancestors. There's two ways of doing that. Either by providing an array of Refs to the callback, or by adding a parent (ref) property to all nodes. I've resisted the latter since I started working on Shaper because I'm no fan on the redundancy it introduces, but I may need to give in on that.

Alternative 2 should be easy to implement with a few extra lines in the plugin. Unfortunately it may break again when the SEMICOLON changes land. Let me think about that some more.

/Olov

Olov Lassus

unread,
Jun 20, 2011, 4:47:24 PM6/20/11
to JSShaper
28 maj 2011 kl. 21.20 skrev Olov Lassus:

> I'm redoing how SEMICOLON works (by diverging from Narcissus) and that will make a similar problem turn up in var-splitter and other plugins, so it needs solving anyways. More about that that later.
>
> Here are the different alternatives for fixing var-splitter:
> 1. Insert all VAR nodes at the beginning of the function instead of directly prior to the existing VAR. It's easy to create a function stack to facilitate this.
> 2. Search for two templates instead of one: "var $, $$" and "for (var $, $$; $; $) $". You'll then catch the for-var when you have all the context you need.
> 3. A bigger change to Shaper that allows the traverse callback to get not only the Ref of the current node, but also Refs to all ancestors. There's two ways of doing that. Either by providing an array of Refs to the callback, or by adding a parent (ref) property to all nodes. I've resisted the latter since I started working on Shaper because I'm no fan on the redundancy it introduces, but I may need to give in on that.
>
> Alternative 2 should be easy to implement with a few extra lines in the plugin. Unfortunately it may break again when the SEMICOLON changes land. Let me think about that some more.


I've implemented the new SEMICOLON behavior now. It's a 1-1 map between SEMICOLON nodes and tokens. Examples:
1+2 (no SEMICOLON node unlike before [used to auto-insert invisible])
1+2; (has SEMICOLON node like before)
var x (no SEMICOLON node like before)
var x; (has SEMICOLON node unlike before)

It makes some things easier and some harder but is more consistent and helpful when juggling around nodes, tree shaping style.

There's also a new version of the var-splitter plugin, supporting for-declarations. It matches using these templates: "var $, $$", "var $, $$;" and "for (var $, $$; $; $) $".

/Olov

Reply all
Reply to author
Forward
0 new messages