etnbrd/flx-compiler

View on GitHub
prototypes/recast/main.js

Summary

Maintainability
D
2 days
Test Coverage
var recast = require("recast");
var util = require("util");
var fs = require("fs");

// Let's turn this function declaration into a variable declaration.
var code = [
  "function add(a, b) {",
  "  return a + b;",
  "}",
  "var c = add(4, 5);"
].join("\n");

var id = {};
var fn = {};
var types = {};
var graphParts = [];

function toGraphviz() {
  var _output = [
    'digraph G {\n',
    '  graph [ fontname="HelveticaNeue-Thin", fontcolor=black, fontsize=20 ];',
    '  node [ fontname="HelveticaNeue-Thin", fontcolor=black, fontsize=20, penwidth=0.5 shape=circle, fixedsize=true, width=1.2, height=1.2 ];',
    '  edge [ fontname="HelveticaNeue-Thin", fontcolor=black, fontsize=20, penwidth=0.5 splines=line, arrowsize=0.7 ];\n\n'
  ].join("\n");


  _output += '  plus [label="+"]\n';

  var _nodes = "";

  for (var i = 0; i < this.length; i++) {
    var _name = (this[i].name === "+") ? 'plus' : this[i].name;
    var _to = (this[i].to === "+") ? 'plus' : this[i].to;

    if (_name[1] == ',') {
      _output += '  ' + _name.replace(',', '') + ' [label="' + _name + '"] ';
      _name = _name.replace(',', '');
    }
    if (_to[1] == ',') {
      _output += '  ' + _to.replace(',', '') + ' [label="' + _to + '"] ';
      _to = _to.replace(',', '');
    }

    _nodes += "  " + _name + " -> " + _to + ';\n';
  };

  return _output + "\n\n" + _nodes + "}\n";
}

// graphParts.inspect = toGraphviz; // override to get same output on file and console
graphParts.toString = toGraphviz; // toString used by writeFile, inspect used by console

function oups() {
  return console.error("Unknown graph topology");
}

function writeFile(name, data) {
  var path = name + ".dot"

  process.stdout.write('\x1B[1m\x1B[36m>\x1B[35m>\x1B[39m\x1B[22m ' + path);
  fs.writeFileSync(path, data);
  console.log('  \x1B[1m\x1B[32m✓\x1B[39m\x1B[22m');
}

function Node(name) {

  function inspect() {
    return this.name + " ⇢  " + this.to;
  }

  return {
    name: name,
    toString: inspect,
    inspect: inspect
  }
}

function walk(o, cb) {

  function _walk_o (o) {
    // console.log(">> walking object");
    for (var i in o) {
      _walk(o[i]);
    }
  }

  function _walk_a (a) {
    // console.log(">> walking array");
    for (var i = 0; i < a.length; i++) {
      _walk(a[i]);
    };
  }
  
  function _walk(o) {
    if (!o)
      return;

    cb(o);

    // Array
    if (o.length && typeof o !== "string") {
      _walk_a(o);
    }
    // Object
    else if (o.toString() === "[object Object]") {
      _walk_o(o);
    } else {
      // console.log("---");
    }
  }

  return _walk(o);
}

// Parse the code using an interface similar to require("esprima").parse.
var ast = recast.parse(code);

// Remove code location to lighten ast
walk(ast, function(o) {
  o.loc = undefined;
})

// console.log(JSON.stringify(ast, undefined, 2));


var output = recast.print(ast).code;

console.log(output);
console.log(" ------------------- ");


function walkGraph(ast) {
  walk(ast, function(o) {

    if(o.type) {
      types[o.type] = {};

      if (o.type === "Identifier")
        id[o.name] = {};

      if (o.type === "FunctionExpression" || o.type === "FunctionDeclaration") {
        fn[o.id.name] = {
          params: {}
        }; // TODO composition pattern
        for (var i = 0; i < o.params.length; i++) {
          fn[o.id.name].params[o.params[i].name] = {};
        };
      }
    }

    // GRAPH PARTS

    if (o.type === "VariableDeclaration") {
      for (var i = 0; i < o.declarations.length; i++) { var decl = o.declarations[i];
        var node = new Node(decl.init.callee.name) // TODO if (CallExpression), return getId(callee) // With getId a generic visitor

        if (decl.type === "VariableDeclarator") {
          node.to = decl.id.name; // TODO getId(decl)
        } else {
          oups();
        }

        graphParts.push(node);
      };
    }


    if (o.type === "CallExpression") {
      for (var i = 0; i < o.arguments.length; i++) {
        var node = new Node(o.arguments[i].value)
        node.to = o.callee.name
        graphParts.push(node);
      };
    }

    // TODO we need a way to know the path in the AST

    if (o.type === "ReturnStatement") {
      if (o.argument.type === "BinaryExpression" ) {
        for (var i in {'left':'', 'right':''}) {
          var node = new Node(o.argument[i].name);
          node.to = o.argument.operator;
          node.context = "add"; // TODO this is pure bullshit, needs the path
          graphParts.push(node);
        }

        var node = new Node(o.argument.operator);
        node.to = "return";
        node.context = "add"; // TODO this is pure bullshit, needs the path
        graphParts.push(node);
      }
    }
  });
}

function resolution(graphParts) {

  var _contexts = {};

  for (var i = 0; i < graphParts.length; i++) {
    if (graphParts[i].context) {
      _contexts[graphParts[i].context] = _contexts[graphParts[i].context] || [];
      _contexts[graphParts[i].context].push(graphParts[i]);
    }
  };

  for (var _name in _contexts) {
    // Looking for patterns like : 4 ⇢ add, (add) a ⇢ +
    // Something like 4 ⇢ add would be replaced by : 4 ⇢ a
    var _args = [];
    var _params = [];
    var _c = _contexts[_name];

    console.log(">> context", _c);

    for (var arg in fn[_name].params) {
      _args.push(arg);
    };

    // Here : 4 ⇢ add, 5 ⇢ add
    for (var index = 0; index < graphParts.length; index++) { var _part = graphParts[index] ;
      if (_part.to === _name) {
        _params.push(_part);
      }
    }

    if (_args.length === _params.length) for (var i = 0; i < _args.length; i++) {
      _params[i].to = _args[i];
    } else {
      console.error("Error : in context " + _name + " not matching argument and params size")
    }

    // Here + ⇢ return, add ⇢ c
    for (var index = 0; index < graphParts.length; index++) { var _part = graphParts[index] ;
      if (_part.name === _name) {
        _part.name = 'return';
      }
    }

  };
}

function reduction(graphParts) {

  // Detect if a node has multiple dependencies
  var _branches = [];
  var _dirty = false;
  do {
    _dirty = false;
    for (var i = 0; i < graphParts.length; i++) {
      for (var j = 0; j < graphParts.length; j++) {
        if (i !== j && graphParts[i].to === graphParts[j].to) { // TODO what if there is more than two upcoming dependencies ?
          _dirty = true;
          graphParts[i].names = [graphParts[i].name, graphParts[j].name];
          graphParts[i].name = graphParts[i].name + ',' + graphParts[j].name;
          graphParts.splice(j, 1);

          for (var k = 0; k < graphParts.length; k++) {
            for (var m = 0; m < graphParts[i].names.length; m++) { var name = graphParts[i].names[m];
              if (name === graphParts[k].to) {
                console.log(name + " === " + graphParts[k].to)
                graphParts[k].index = m;
                graphParts[k].to = graphParts[i].name;
              }
            };
          };
        }
      };
    };
  } while(!_dirty)
}

function build(graphParts) {

  var _code = "";

  // console.log(graphParts);

  for (var i = 0; i < graphParts.length; i++) {
    graphParts[i]
    var _output = [
      "flx.register('" + graphParts[i].name + "', function(msg) {",
      "// here, code generation",
      "return m('" + graphParts[i].to + "', msg);",
      "})"
    ].join("\n");

    console.log(_output + "\n\n");
  };
}

// console.log("Identifiers : ", id);
// console.log("Functions : ", fn);
// console.log("Types : ", types);

walkGraph(ast);
writeFile('graphWal', graphParts);
console.log(graphParts);

resolution(graphParts);
writeFile('graphRes', graphParts);
console.log(graphParts);

reduction(graphParts);
writeFile('graphRed', graphParts);
console.log(graphParts);

build(graphParts);

// writeFile("graph", graphParts);