ast-types
ast-types copied to clipboard
alternate builder / copier
An idea I've been playing with:
I have got a situation where I am going to take my custom AST, perform some optimizing transforms, and then use that AST to build nodes that actually perform the work. In my case, I intend to target two different platforms (primitive Strings
and Node.js Streams
), and the built nodes will be so drastically different, I intend to just create separate instances for each. Also there will be some optimizing transforms that I will only want to perform for one platform or the other (so I need a way to create an identical deep copy after I perform the common transforms).
My idea is to provide a copy
function that takes an alternate builders
implementation to create the copy. If an alternate builders
implementation is not provided, it uses the default one (types.builders
), essentially creating a deep copy of the AST. Providing an alternate builders
implementation requires you provide all the methods in types.builders
(with identical signatures), but you are free to return whatever you want from each method (whatever you return will be used as an argument to the buildFn of the parent element).
Was my explanation clear? Is this worth including in ast-types
, or is it to niche?
This relies on the buildFn metadata exposed by #121.
var types = require('ast-types/lib/types');
var shared = require('ast-types/lib/shared');
var isPrimitive = shared.isPrimitive;
var b = types.builders;
function copy(type, builder) {
builder = builder || b;
if (isPrimitive.check(type)) {
return type;
}
if (type instanceof Array) {
return type.map(function(child) {
return copy(child, builder);
})
}
if ('string' !== typeof type.type) {
throw new Error('not an AST node: ' + JSON.stringify(type));
}
var builderName = types.getBuilderName(type.type);
var buildFn = b[builderName];
if (!buildFn) {
throw new Error(type.type + ' does not have a registered builder');
}
var buildFn2 = builder[builderName];
if ('function' !== typeof buildFn2) {
throw new Error('builder does not have function: ' + builderName);
}
var args = [];
for (var i = 0; i < buildFn.paramCount; i++) {
var p = copy(type[buildFn[i].name], builder);
if (typeof p !== 'undefined') {
args[i] = p;
}
}
return buildFn2.apply(builder, args);
}
P.S. As this is a completely custom AST I'm working with, I anxiously await the resolution of #57.
Here is my copyTree function in typescript. paths are relative to ast-nodes/script and it relies on immutable-js. It takes a different approach, I like the simplicity of yours. They are actually different, I realize - copyTree generates a JSON serializable structure, and one that can be passed into the builder's 'from' function after deserialization.
import {visit,getFieldNames, getFieldValue, namedTypes} from '../main';
import { NodePath } from '../lib/node-path';
import { Map, List, Set} from 'immutable';
export type ValueKind = any|any[];
export type CopyTreeResult = Map<string, ValueKind>;
export function copyTree(node: namedTypes.Node,
): CopyTreeResult {
let cache: List<Map<string, any>> = List<Map<string, any>>();
let depth = 0;
visit(node, {
visitNode(path: NodePath<namedTypes.Node>): any {
depth++;
this.traverse(path);
depth--;
if(cache.get(depth + 1) === undefined ) {
cache = cache.set(depth + 1, Map<string, any>());
}
if(cache.get(depth) === undefined ) {
cache = cache.set(depth, Map<string, any>());
}
const anode = path.node;
//@ts-ignore
if(!namedTypes[anode.type].check(anode)) {
throw new Error('invalid node');
}
let nary: Set<string> = Set<string>(getFieldNames(anode));
nary = nary.subtract(cache.get(depth + 1)!.keySeq());
const result: Map<string, any> = Map<string, any>(
nary.map(fName => [fName, getFieldValue(anode, fName)]))
.merge(cache.get(depth + 1)!);
cache = cache.delete(depth + 1);
if(typeof path.name === 'string') {
cache = cache.set(depth,
cache.get(depth)!.set(path.name, result));
} else {
let ary = cache.get(depth)!.get(path.parentPath.name)
|| List<Map<string, any>>();
ary = ary.set(path.name, result);
cache = cache.set(depth,
cache.get(depth)!.set(path.parentPath.name, ary));
}
},
});
return cache.get(0)!.get('root');
}
`