Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alternate builder / copier #122

Open
jamestalmage opened this issue Aug 11, 2015 · 1 comment
Open

alternate builder / copier #122

jamestalmage opened this issue Aug 11, 2015 · 1 comment

Comments

@jamestalmage
Copy link
Contributor

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.

@kaymccormick
Copy link

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');
}

`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants