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 |