| 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 |