| LEFT | RIGHT | 
|    1 /** |  | 
|    2  * A brief description of some nodes. |  | 
|    3  * |  | 
|    4  * Scope block information: |  | 
|    5  *  variables (Variable[]): a list of variables declared in the block |  | 
|    6  *  functions (Function[]): a list of functions declared in the block |  | 
|    7  *  constants (Variable[]): a list of constants declared in the block |  | 
|    8  *  classes (Class[]): a list of classes declared in the block |  | 
|    9  *  objects (Class[]): a list of objects declared in the block |  | 
|   10  *  code (Statement[]): a list of statements in the block |  | 
|   11  */ |  | 
|   12  |  | 
|   13 /** |  | 
|   14  * Takes the node rooted at the AST and decomposes it into readable sections. |  | 
|   15  */ |  | 
|   16 function clean_ast(ast) { |  | 
|   17   assert(ast.type == TOK_LC); |  | 
|   18   let info = { |  | 
|   19     variables: [], |  | 
|   20     constants: [], |  | 
|   21     objects: [], |  | 
|   22     classes: [], |  | 
|   23     functions: [], |  | 
|   24     code: [] |  | 
|   25   }; |  | 
|   26  |  | 
|   27   for (let statement of ast.kids) { |  | 
|   28     if (statement.op == JSOP_DEFVAR) { |  | 
|   29       let ret = make_variables(statement); |  | 
|   30       info.variables = info.variables.concat(ret.vars); |  | 
|   31       info.objects = info.objects.concat(ret.objs); |  | 
|   32     } else if (statement.op == JSOP_DEFCONST) { |  | 
|   33       let ret = make_variables(statement); |  | 
|   34       info.constants = info.constants.concat(ret.vars); |  | 
|   35       info.objects = info.objects.concat(ret.objs); |  | 
|   36     } else if (statement.type == TOK_FUNCTION) { |  | 
|   37       info.functions.push(make_function(statement)); |  | 
|   38     } else if (prototype_assign(statement)) { |  | 
|   39       let obj = make_class(statement); |  | 
|   40       merge_class(info, obj); |  | 
|   41     } else { |  | 
|   42       info.code.push(statement); |  | 
|   43     } |  | 
|   44   } |  | 
|   45   return info; |  | 
|   46 } |  | 
|   47  |  | 
|   48 /** |  | 
|   49  * Visits the AST using the given function as a parameter. |  | 
|   50  * The parameter will take in a single argument, the AST node. |  | 
|   51  */ |  | 
|   52 function visit(root_ast, func, to_expand) { |  | 
|   53   function v_r(ast, func) { |  | 
|   54     if (ast == null) |  | 
|   55       return; |  | 
|   56     if (func(ast)) return; |  | 
|   57     for (let child of ast.kids) |  | 
|   58       v_r(child, func); |  | 
|   59   } |  | 
|   60  |  | 
|   61   function sanitize(ast) { |  | 
|   62     if (ast == null) |  | 
|   63       return null; |  | 
|   64     if (ast.op == JSOP_NAME && ast.atom in to_expand) { |  | 
|   65       ast = sanitize(to_expand[ast.atom]); |  | 
|   66       ast.expanded = true; |  | 
|   67     } |  | 
|   68     let sanitized_ast = { kids: [] }; |  | 
|   69     for (let key of ast) { |  | 
|   70       if (key == 'kids') { |  | 
|   71         for (let kid of ast.kids) { |  | 
|   72           sanitized_ast.kids.push(sanitize(kid)); |  | 
|   73         } |  | 
|   74       } else { |  | 
|   75         sanitized_ast[key] = ast[key]; |  | 
|   76       } |  | 
|   77     } |  | 
|   78     return sanitized_ast; |  | 
|   79   } |  | 
|   80  |  | 
|   81   if (to_expand) |  | 
|   82     v_r(sanitize(root_ast), func); |  | 
|   83   else |  | 
|   84     v_r(root_ast, func); |  | 
|   85 } |  | 
|   86  |  | 
|   87 function prototype_assign(statement) { |  | 
|   88   if (statement.type != TOK_SEMI || !statement.kids[0]) |  | 
|   89     return false; |  | 
|   90   statement = statement.kids[0]; |  | 
|   91   if (statement.type != TOK_ASSIGN || !statement.kids[0]) |  | 
|   92     return false; |  | 
|   93  |  | 
|   94   statement = statement.kids[0]; |  | 
|   95   // Statement is either prototype or a property of prototype |  | 
|   96   if (statement.op != JSOP_SETPROP) |  | 
|   97     return false; |  | 
|   98   if (statement.atom == "prototype") |  | 
|   99     return true; |  | 
|  100   if (statement.kids[0] && statement.kids[0] == "prototype") |  | 
|  101     return true; |  | 
|  102  |  | 
|  103   // Or not! |  | 
|  104   return false; |  | 
|  105 } |  | 
|  106  |  | 
|  107 function make_class(class_root) { |  | 
|  108   let clazz = {}; |  | 
|  109  |  | 
|  110   class_root = class_root.kids[0]; |  | 
|  111   let lhs = class_root.kids[0], rhs = class_root.kids[1]; |  | 
|  112   if (lhs.atom == "prototype") { |  | 
|  113     clazz.init = rhs; |  | 
|  114     clazz = make_object(clazz); |  | 
|  115   } else { |  | 
|  116     clazz.functions = {}; |  | 
|  117     clazz.functions[lhs.atom] = make_function(rhs); |  | 
|  118     lhs = lhs.kids[0]; |  | 
|  119   } |  | 
|  120   clazz.name = lhs.kids[0].atom; |  | 
|  121   return clazz; |  | 
|  122 } |  | 
|  123  |  | 
|  124 function merge_class(info_list, obj) { |  | 
|  125   let name = obj.name; |  | 
|  126   for (let i = 0; i < info_list.functions.length; i++) { |  | 
|  127     if (info_list.functions[i].name == name) { |  | 
|  128       obj.constructor = info_list.functions[i]; |  | 
|  129       // remove the constructor from the list of global functions |  | 
|  130       info_list.functions.splice(i, 1); |  | 
|  131       break; |  | 
|  132     } |  | 
|  133   } |  | 
|  134   if (obj.constructor) |  | 
|  135     obj.loc = obj.constructor.loc; |  | 
|  136   let oldObj = null; |  | 
|  137   for (let i = 0; i < info_list.classes.length; i++) { |  | 
|  138     if (info_list.classes[i].name == name) { |  | 
|  139       oldObj = info_list.classes[i]; |  | 
|  140       break; |  | 
|  141     } |  | 
|  142   } |  | 
|  143   if (oldObj) { |  | 
|  144     for (let prop in obj) { |  | 
|  145       if (!(prop in oldObj)) { |  | 
|  146         oldObj[prop] = obj[prop]; |  | 
|  147       } else if (typeof obj[prop] == "object") { |  | 
|  148         for (let item in obj[prop]) |  | 
|  149           oldObj[prop][item] = obj[prop][item]; |  | 
|  150       } |  | 
|  151     } |  | 
|  152   } else { |  | 
|  153     info_list.classes = info_list.classes.concat(obj); |  | 
|  154   } |  | 
|  155 } |  | 
|  156 function make_variables(var_root) { |  | 
|  157   assert(var_root.op == JSOP_DEFVAR || var_root.op == JSOP_DEFCONST); |  | 
|  158   let variables = []; |  | 
|  159   let objects = []; |  | 
|  160   for (let name of var_root.kids) { |  | 
|  161     let v = { name: name.atom }; |  | 
|  162     v.init = (name.kids.length > 0 ? name.kids[0] : null); |  | 
|  163     v.loc = get_location(var_root); |  | 
|  164     if (v.init && v.init.op == JSOP_NEWINIT && v.init.kids[0] && |  | 
|  165         v.init.kids[0].type == TOK_COLON) |  | 
|  166       objects.push(make_object(v)); |  | 
|  167     else |  | 
|  168       variables.push(v); |  | 
|  169   } |  | 
|  170   return { vars: variables, objs: objects }; |  | 
|  171 } |  | 
|  172  |  | 
|  173 function make_object(stub) { |  | 
|  174   stub.variables = {}; |  | 
|  175   stub.functions = {}; |  | 
|  176   stub.getters = {}; |  | 
|  177   stub.setters = {}; |  | 
|  178   let ast = stub.init; |  | 
|  179   let proto = stub.init.op == JSOP_GETPROP && stub.init.atom == 'prototype'; |  | 
|  180   delete stub['init']; |  | 
|  181   if (proto) { |  | 
|  182     stub.inherits = [ast.kids[0].atom]; |  | 
|  183     return stub; |  | 
|  184   } |  | 
|  185   for (let init of ast.kids) { |  | 
|  186     if (init.type != TOK_COLON) { |  | 
|  187       dump_ast(init); |  | 
|  188     } |  | 
|  189     assert(init.type == TOK_COLON); |  | 
|  190     if (init.kids[0].type == TOK_NAME) { |  | 
|  191       let name = init.kids[0].atom; |  | 
|  192       let value = init.kids[1]; |  | 
|  193       if (init.op == JSOP_GETTER) |  | 
|  194         stub.getters[name] = make_function(value); |  | 
|  195       else if (init.op == JSOP_SETTER) |  | 
|  196         stub.setters[name] = make_function(value); |  | 
|  197       else if (value.type == TOK_FUNCTION) |  | 
|  198         stub.functions[name] = make_function(value); |  | 
|  199       else if (name == '__proto__') { |  | 
|  200         let supername; |  | 
|  201         if (value.op == JSOP_NEW) |  | 
|  202           supername = value.kids[0].atom; |  | 
|  203         else if (value.op == JSOP_GETPROP && value.atom == 'prototype') |  | 
|  204           supername = value.kids[0].atom; |  | 
|  205         else |  | 
|  206           assert(false); |  | 
|  207         if ('inherits' in stub) |  | 
|  208           stub.inherits.push(supoername); |  | 
|  209         else |  | 
|  210           stub.inherits = [supername]; |  | 
|  211       } |  | 
|  212       else |  | 
|  213         stub.variables[name] = { loc: get_location(value), init: value }; |  | 
|  214     } else { |  | 
|  215       dump_ast(init); |  | 
|  216     } |  | 
|  217   } |  | 
|  218   return stub; |  | 
|  219 } |  | 
|  220  |  | 
|  221 function make_function(func_root) { |  | 
|  222   assert(func_root.type == TOK_FUNCTION); |  | 
|  223   let stmts = func_root.kids[0]; |  | 
|  224   if (stmts.type == TOK_UPVARS) |  | 
|  225     stmts = stmts.kids[0]; |  | 
|  226   if (stmts.type == TOK_ARGSBODY) |  | 
|  227     stmts = stmts.kids[stmts.kids.length - 1]; |  | 
|  228   if (stmts.type == TOK_RETURN) { |  | 
|  229     // This is a lambda function. For simplicity's sake, I'm going to turn this |  | 
|  230     // into a regular function by wrapping with TOK_LC (consumers of functions |  | 
|  231     // will find this easier to use) |  | 
|  232     let newtop = { fakeNode: true, line: stmts.line, col: stmts.col, |  | 
|  233       type: TOK_LC, op: JSOP_NOP, kids: [stmts]}; |  | 
|  234     stmts = newtop; |  | 
|  235   } |  | 
|  236   assert(stmts.type == TOK_LC); |  | 
|  237   return { name: func_root.name, body: stmts, loc: get_location(func_root)}; |  | 
|  238 } |  | 
|  239  |  | 
|  240 function assert(cmd) { |  | 
|  241   if (!cmd) { |  | 
|  242     throw new Error("Assertion failed"); |  | 
|  243   } |  | 
|  244 } |  | 
|  245  |  | 
|  246 function get_location(ast_node) { |  | 
|  247   return { line: ast_node.line, column: ast_node.column }; |  | 
|  248 } |  | 
| LEFT | RIGHT |