etnbrd/flx-compiler

View on GitHub
prototypes/blender.old/index.js

Summary

Maintainability
F
5 days
Test Coverage
var recast = require("recast");
var b = require("recast").types.builders;
var fs = require("fs");
var util = require("util");

var dep = require("./lib/dependency");
var depB = require("./lib/dependency").builders;
var depHash = require("./lib/dependency").hash;
var res = require("./lib/resolution");
var extract = require("./lib/extraction");
var red = require("./lib/reduction");

var walk = require("./lib/traverse");
var bld = require("./lib/builders");
var graphviz = require("./lib/graphviz");
var utils = require("./lib/utils");
var tools = require("./lib/tools");
var path = require("./lib/path");

var build = require("./lib/build");

var file = process.argv[2];
if (!file) {
  console.log("Please specify a filename as an argument");
  return -1;
}

var code = "" + fs.readFileSync(file);
var ast = recast.parse(code);
var flx = {};


// TODO crap, I thought find would return an array of ALL matching values, it doesn't.

if (!Array.prototype.find) {
  Object.defineProperty(Array.prototype, 'find', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(predicate) {
      if (this == null) {
        throw new TypeError('Array.prototype.find called on null or undefined');
      }
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }
      var list = Object(this);
      var length = list.length >>> 0;
      var thisArg = arguments[1];
      var value;

      for (var i = 0; i < length; i++) {
        if (i in list) {
          value = list[i];
          if (predicate.call(thisArg, value, i, list)) {
            return value;
          }
        }
      }
      return undefined;
    }
  });
}


// TODO 
/*
Il faut utiliser un graphe de dépendance pour récupérer les identifieur et descendre toute la chaine express.
require('express') -> app -> app.get -> callback -> req, res

Le res.send correspond à la fin de la chaine fluxionnel.
Le req, correspond au début de la chaine fluxionnel

En fonction du comportement de la variable globale, on découpera la chaine fluxionnel en plusieurs fluxions.
 .                   .
/!\ VARIABLE GLOBAL /!\

*/




var depTrees = dep.walk(ast);
// depTrees.inspect = graphviz.inspect; // Console.log
depTrees.toString = graphviz.depTree; // writeFile
utils.writeFile('depTrees', depTrees);
// console.log(depTrees);

var resTree = res.resolve(depTrees);
resTree.toString = graphviz.resTree;
// console.log(resTree);
utils.writeFile('resTree', resTree);

var appGetNode = resTree.nodes.find(function(node) {
  if (node.name === "app.get" && node.kind === "CallExpression")
    return node;
  return false;
})

// Find app.get route
var _dep = resTree.deps.find(function(dep) {
    return dep.to === appGetNode.id && dep.type === "Argument" && dep.index === 0;
})

var routePath = resTree.nodes.find(function(node) {
  return _dep.id === node.id
}).name;

var appGetChain = extract.down(resTree, appGetNode.id);
appGetChain.toString = graphviz.resTree;
// console.log(extracted);
utils.writeFile('appGetChain', appGetChain);

var resSendNode = resTree.nodes.find(function(node) {
  if (node.name === "res.send" && node.kind === "CallExpression")
    return node;
  return false;
})

var resSendArgDep = resTree.deps.find(function(dep) {
  if (dep.to === resSendNode.id && dep.type === "Argument")
    return dep;
  return false;
})

var resSendArgNode = resTree.nodes.find(function(node) {
  if (node.id === resSendArgDep.id)
    return node;
  return false;
})

var resSendChain = extract.up(resTree, resSendArgNode.id);
resSendChain.toString = graphviz.resTree;
// console.log(extracted);
utils.writeFile('resSendChain', resSendChain);

// TODO find all variables, among the extracted resSendChain

var varDec = resSendChain.nodes.filter(function(node) {
    return node.kind === "VariableDeclarator";
  }).reduce(function(node, _node, index, array) {
    // console.log(node, _node);
    node[_node.name] = node[_node.name] || [];
    node[_node.name][_node.version || 0] = _node;
    return node;
  }, {});

// TODO this is bullshit, we don't need to find paths, we just need the unresolved graph to detect out of scope variables.

function isGlobal(tree, node, appGetNode) { // TODO this is bad design, why can't I detect from which appGet the node is from ?
  // Among the variables used in resSendChain, find the global ones
  return !path.find(tree, node.id, appGetNode.id)
    .every(function(_path) {
      return _path.some(function(dep) {
        if (dep.type === "Before")
          return false;
      })
    })
}

for (var _var in varDec) {  
  if (isGlobal(resTree, varDec[_var][0], appGetNode)) {
    varDec[_var].isGlobal = true;
  }
}

// console.log(varDec);

var argChain = extract.upOnly(resTree, resSendArgNode.id);
argChain.toString = graphviz.resTree;
// console.log(extracted);
utils.writeFile('argChain', argChain);


var flxScp;

// extract the global variables chains
for (var __var in varDec) { var _var = varDec[__var];
  if (_var.isGlobal) {    
    // var globals = argChain.nodes.find(function(node) {
    //   return node.id === varDec
    // })

    var varChain = extract.upOnly(argChain, _var[0].id);

    // break the VariableDeclarator dependency
    argChain.deps = argChain.deps.filter(function(dep) {
      if (dep.to === _var[0].id && dep.type === "VariableDeclarator") {
        // clean the graph from free nodes
        // TODO there might be more than one node to clean
        argChain.nodes = argChain.nodes.filter(function(node) {
          return !(node.id === dep.id);
        })

        return false;
      }
      return true;
    })



    var bottomNode = varChain.nodes.find(function(node) {
      return varChain.deps.every(function(dep) {
        return dep.id !== node.id;
      })
    })

    // this is the code for the global stuff
    // TODO transform this into an object
    var _flxScp = _next(resTree, varChain, bottomNode);
    // console.log(" --- GLOBAL ---");
    // TODO generalize that

    flxScp = b.property("init", _flxScp.id, _flxScp.init);

    // console.log(recast.print(flxScp).code);
    // console.log(" --------------");

    function appendThisMemberExpression(node) {
      var meHash = depHash(node.ref);
      var thisHash = depHash(node.ref);

      return {
        id: meHash,
        nodes: [
          depB.node(undefined, "this." + node.name, meHash, "MemberExpression"),
          depB.node(undefined, "this", thisHash, "ThisExpression")
        ],
        deps: [
          depB.dep(thisHash, meHash, "object"),
          depB.dep(node.id, meHash, "property"),
        ]
      };
    }

    // console.log(argChain)

    // Transform every reference to that variable by the me(this, var)
    argChain.nodes.forEach(function(node) {
      if (node.name === _var[0].name) {

        var appendNode = appendThisMemberExpression(node);

        // If more than one occurence found, shit ...
        if (1 < argChain.deps.find(function(dep) {
            return dep.id === node.id || dep.to === node.id;
          }).length)
          throw "ERROR : too much variable found, expected only one";

        // Replace each dependency pointing to that occurence (should be only one)
        argChain.deps.forEach(function(dep) {
          if (node.id === dep.id) {
            dep.id = appendNode.id;
          }
          if (node.id === dep.to) {
            dep.to = appendNode.id;
          }
        })

        appendNode.deps.forEach(function(dep) {
          argChain.deps.push(dep);
        });

        appendNode.nodes.forEach(function(node) {
          argChain.nodes.push(node);
        });

        // TODO WTF ?!?
        // appendNode.deps.forEach(argChain.deps.push);
        // appendNode.nodes.forEach(argChain.nodes.push);
      }
    })

    utils.writeFile('argChain2', argChain);

  }
}

// Get the top node
// var topNode = argChain.nodes.find(function(node) {
//   return argChain.deps.every(function(dep) {
//     return dep.to !== node.id;
//   })
// })

// Get the bottom node
var bottomNode = argChain.nodes.find(function(node) {
  return argChain.deps.every(function(dep) {
    return dep.id !== node.id;
  })
})

// From the bottom node, we build the code up.
function _next(tree, chain, node) {

  // TODO What about building this fucking _next function so I don't have to pass these tree and chain as arguments every fucking time !

  // console.log(">> ", node, node.kind);

  this.VariableDeclarator = function(tree, chain, node) {

    var upwards = chain.deps.find(function(dep) {
      return dep.to === node.id && dep.type === "Assignment";
    })

    var varDecl = chain.deps.find(function(dep) {
      return dep.to === node.id && dep.type === "VariableDeclarator";
    })

    // TODO we might not find Assignment dependency, but there is still other dependency, like VariableDeclarator
    // but if variable is global, need to put it in a different chain of compilation.
    // We could return {global: [], local: []} and a peeler function to plug everything correctly by merging stuffs.

    if (upwards) {
      if (upwards.length > 1) {
        throw "ERROR : multiple assignement for " + node;
      }

      var up = chain.nodes.find(function(node) {
        return node.id === upwards.id;
      })

      if (up.length > 1) {
        throw "ERROR : multiple node, expected only one " + node;
      }

      var id = this.Identifier(tree, chain, node); // TODO bad design
      var ass = _next(tree, chain, up);

      return b.variableDeclarator(id, ass);
    } else if (varDecl) {

      var up = chain.nodes.find(function(node) {
        return node.id === varDecl.id;
      })

      if (up.length > 1) {
        throw "ERROR : multiple node, expected only one " + node;
      }

      var id = this.Identifier(tree, chain, node); // TODO bad design
      var ass = _next(tree, chain, up);

      return b.variableDeclarator(id, ass);
    } else {
      return this.Identifier(tree, chain, node);
    }
  }

  this.BinaryExpression = function(tree, chain, node) {

    var ops = ['left', 'right'].map(function(op) {
      // transform the op name in their respective dependencies
      return chain.deps.find(function(dep) {
        return dep.to === node.id && dep.side === op;
      })
    }).map(function(op) {
      // transform the dependencies in their respective up nodes
      return chain.nodes.find(function(node) {
        return node.id === op.id
      })
    }).map(function(op) {
      // build the nodes
      return _next(tree, chain, op);
    })

    ops.unshift(node.operator);

    return b.binaryExpression.apply(this, ops);

    // return b.binaryExpression(node.operator, ops[0], ops[1]);
  }

  this.Identifier = function(tree, chain, node) {
    return b.identifier(node.name);
  }

  this.Literal = function(tree, chain, node) {
    var lit = b.literal(node.name);
    return lit;
  }

  this.MemberExpression = function(tree, chain, node) {
    var ops = ['object', 'property'].map(function(op) {
      // transform the op name in their respective dependencies
      return chain.deps.find(function(dep) {
        return dep.to === node.id && dep.type === op;
      })
    }).map(function(op) {
      // transform the dependencies in their respective up nodes
      return chain.nodes.find(function(node) {
        return node.id === op.id
      })
    }).map(function(op) {
      // build the nodes
      return _next(tree, chain, op);
    })

    ops.push(false) // isComputed ?

    // TODO bad design, this shouldn't be here, should be gathered for every nodes.
    // Detect every upwards dependencies, then process them with the types constructor.
    var upwards = chain.deps.find(function(dep) {
      return dep.to === node.id && dep.type === "Assignment";
    })

    if (upwards) { // There is an assignement
      var _node = chain.nodes.find(function(node) {
        return node.id === upwards.id;
      })

      return b.assignmentExpression(upwards.operator, b.memberExpression.apply(this, ops), _next(tree, chain, _node) )
    } else {
      return b.memberExpression.apply(this, ops);
    }

  }

  this.ThisExpression = function(tree, chain, node) {
    return b.thisExpression();
  }

  return this[node.kind](tree, chain, node);
}


// TODO here we have only one expression, but we should expect several

flxBody = _next(resTree, argChain, bottomNode);

flx = b.expressionStatement(
    b.callExpression(
      b.memberExpression(b.identifier("flx"), b.identifier("register"), false),
      [
        b.literal(routePath),
        bld.flxSimple(flxBody),
        b.objectExpression([flxScp])
      ]
    )
  )

prg = bld.program([
    bld.requires("../../"),
    flx,
    bld.route(routePath, routePath, routePath)
  ])

// console.log(recast.print(prg).code);

utils.writeFile(file.split('.')[0] + "-flx", recast.print(prg).code, 'js');


// var _tree = tools.convert(extracted);

// function extractOutput(_tree) {
//   var _nodes = [];
//   for (var _node in _tree.nodes) { var node = _tree.nodes[_node];
//     if (node.name === "res.send" && node.kind === "CallExpression") {
//       _nodes.push(node);
//     }
//   }
//   if (_nodes.length !== 1) {
//     throw "ERROR : none, or multiple res.send nodes, expected one";
//   } else {
//     return _nodes[0];
//   }
// }
// // Extract the res.send CallExpression node
// var resSend = extractOutput(_tree);



/*

walk(ast, function(n) {


  console.log(n.callee);

  if (!n.callee || !n.type || !n.callee.object || !n.callee.property) {
    return
  }

  if (n.callee.object.name === "app"
  &&  n.callee.property.name === "get"
  &&  n.type === "CallExpression") {
    var route = "";

    n.arguments.forEach(function(arg, index) {
      if (arg.type === "Literal") {
        route = arg;
      }

      if (arg.type === "FunctionExpression") {

        var name = route || arg.id.name || "flx" + flx.length();
        while (flx[name]) {
          name += "2";
        }
        flx[name] = arg;
        n.arguments[index] = b.placeholder(name, name);
      }
    })
  }
})


console.log(" SOURCE \n");
console.log(code);
console.log("\n -------------- ");

console.log(" TARGET \n");

var _flx = [];
var _route = [];


for (var _fl in flx) { var fl = flx[_fl];

  var body = fl.body.body;
  var output = "fluxion " + _fl + " >> " + "output\n" +
  body.reduce(function(output, line) {
    return output + "  " + recast.print(line).code.replace(/\n/g, '\n  ') + "\n";
  }, "")

  _flx.push(output);
  _route.push("web.route " + _fl + ", " + _fl );
}

console.log(_flx.join("\n"));
console.log(_route.join("\n"));

console.log(recast.print(ast).code);

*/