parsejs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ require "parsejs/visitor"
2
+
3
+ module ParseJS
4
+ module AST
5
+ module Scope
6
+ attr_accessor :scope_variables, :parent_variables, :variable_access
7
+ attr_accessor :parent_scope, :child_scopes
8
+
9
+ def variable_in_scope?(name)
10
+ return true if variable?(name)
11
+ parent_scope && parent_scope.variable_in_scope?(name)
12
+ end
13
+
14
+ def variable?(name)
15
+ scope_variable?(name) || parent_variable?(name) || variable_access?(name)
16
+ end
17
+
18
+ def scope_variable?(name)
19
+ scope_variables && scope_variables.include?(name)
20
+ end
21
+
22
+ def parent_variable?(name)
23
+ parent_variables && parent_variables.include?(name)
24
+ end
25
+
26
+ def variable_access?(name)
27
+ variable_access && variable_access.include?(name)
28
+ end
29
+
30
+ # determine whether there is a reference for a particular variable
31
+ # to a parent scope.
32
+ def parent_variable_access?(name)
33
+ # if this scope has a "var x = 1" type of declaration for this name,
34
+ # it is not referencing a parent scope.
35
+ return false if scope_variable?(name)
36
+
37
+ # otherwise, if there is a variable access for this name, it's
38
+ # referencing a parent scope.
39
+ variable_access?(name)
40
+ end
41
+
42
+ # determine whether a variable can be added without causing damage
43
+ # to child scopes.
44
+ def available_variable?(name)
45
+ # if the current scope is already using the variable, it's
46
+ # unavailable.
47
+ return false if variable?(name)
48
+
49
+ # if any of the child scopes reference the variable as a
50
+ # parent variable, it's not available.
51
+ !any_child_references_parent_variable?(name)
52
+ end
53
+
54
+ def any_child_references_parent_variable?(name)
55
+ unless child_scopes.nil?
56
+ # this should really check if all descendent scopes see a
57
+ # scope variable before they see a parent refernce
58
+
59
+ return false if child_scopes.all? { |s| s.scope_variable?(name) }
60
+
61
+ child_scopes.any? do |s|
62
+ s.parent_variable_access?(name) ||
63
+ s.parent_variable?(name) ||
64
+ s.any_child_references_parent_variable?(name)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class ProcessVariables < Visitor
71
+ include ParseJS::Visitor::ScopeManager
72
+
73
+ def self.process(ast)
74
+ new.visit(ast)
75
+ end
76
+
77
+ def push_scope_variable(name)
78
+ vars = current_scope.scope_variables ||= Set.new
79
+ vars << name
80
+ end
81
+
82
+ def push_parent_variable(name)
83
+ vars = current_scope.parent_variables ||= Set.new
84
+ vars << name
85
+ end
86
+
87
+ def push_variable_access(name)
88
+ vars = current_scope.variable_access ||= Set.new
89
+ vars << name
90
+ end
91
+
92
+ def possible_variable_access(var)
93
+ push_variable_access var.val if var && var.type?("Identifier")
94
+ end
95
+
96
+ def possible_variable_access_array(arr)
97
+ arr.each { |arg| possible_variable_access arg }
98
+ end
99
+
100
+ def visit_VariableDeclarator(decl)
101
+ push_scope_variable decl.id.val
102
+ possible_variable_access decl.init
103
+
104
+ super
105
+ end
106
+
107
+ def visit_MemberExpression(expr)
108
+ possible_variable_access expr.object
109
+
110
+ super
111
+ end
112
+
113
+ def visit_CallExpression(expr)
114
+ possible_variable_access expr.callee
115
+ possible_variable_access_array expr.args
116
+
117
+ super
118
+ end
119
+
120
+ def visit_NewExpression(expr)
121
+ possible_variable_access expr.callee
122
+ possible_variable_access_array expr.args if expr.args
123
+
124
+ super
125
+ end
126
+
127
+ def visit_ArrayExpression(expr)
128
+ possible_variable_access_array expr.elements
129
+
130
+ super
131
+ end
132
+
133
+ def visit_AssignmentExpression(expr)
134
+ if expr.left.type?("Identifier")
135
+ push_parent_variable expr.left.val
136
+ end
137
+
138
+ possible_variable_access expr.right
139
+
140
+ super
141
+ end
142
+
143
+ def visit_Property(prop)
144
+ possible_variable_access prop.value
145
+
146
+ super
147
+ end
148
+
149
+ def visit_SequenceExpression(expr)
150
+ possible_variable_access_array expr.expressions
151
+
152
+ super
153
+ end
154
+
155
+ def visit_UpdateExpression(expr)
156
+ possible_variable_access expr.argument
157
+
158
+ super
159
+ end
160
+
161
+ def visit_UnaryExpression(expr)
162
+ possible_variable_access expr.argument
163
+
164
+ super
165
+ end
166
+
167
+ def visit_BinaryExpression(expr)
168
+ possible_variable_access expr.left
169
+ possible_variable_access expr.right
170
+
171
+ super
172
+ end
173
+
174
+ def visit_ConditionalExpression(expr)
175
+ possible_variable_access expr.alternate
176
+ possible_variable_access expr.consequent
177
+ possible_variable_access expr.test
178
+
179
+ super
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,450 @@
1
+ require "parsejs/visitor"
2
+ require "json"
3
+
4
+ module ParseJS
5
+ require "pp"
6
+
7
+ class Stringifier < Visitor
8
+ def self.to_string(ast)
9
+ stringifier = new(ast)
10
+ yield stringifier if block_given?
11
+ stringifier.to_string
12
+ end
13
+
14
+ attr_accessor :include_comments
15
+
16
+ def initialize(ast)
17
+ @ast = ast
18
+ @indent = 0
19
+ @include_comments = false
20
+ end
21
+
22
+ def accept(node)
23
+ return "" if node.nil?
24
+
25
+ nl = @newline
26
+
27
+ out = if node.cuddly?
28
+ " " << super
29
+ elsif @newline
30
+ @newline = false
31
+ current_indent << super
32
+ else
33
+ super
34
+ end
35
+
36
+ out << newline if node.needs_newline? && needs_newline?(out)
37
+
38
+ out
39
+ end
40
+
41
+ def indent
42
+ @indent += 1
43
+ end
44
+
45
+ def outdent
46
+ @indent -= 1
47
+ end
48
+
49
+ def current_indent
50
+ " " * @indent
51
+ end
52
+
53
+ def newline
54
+ @newline = true
55
+ "\n"
56
+ end
57
+
58
+ def cuddle(node, out, more=false)
59
+ if node.cuddly?
60
+ node.cuddle! if more
61
+ else
62
+ indent
63
+ out << newline
64
+ end
65
+
66
+ out << accept(node)
67
+
68
+ if more && node.cuddly?
69
+ out << " "
70
+ @newline = false
71
+ end
72
+
73
+ unless node.cuddly?
74
+ outdent
75
+ out << current_indent if more
76
+ end
77
+ end
78
+
79
+ def without_newline
80
+ old, @skip_newline = @skip_newline, true
81
+ ret = yield
82
+ @skip_newline = old
83
+ ret
84
+ end
85
+
86
+ def needs_newline?(out)
87
+ out !~ /\n$/ && !@skip_newline
88
+ end
89
+
90
+ def strip_newline(str)
91
+ @newline = false
92
+ str.sub(/\n$/, '')
93
+ end
94
+
95
+ def to_string
96
+ accept @ast
97
+ end
98
+
99
+ def params(node)
100
+ map(node).join(", ")
101
+ end
102
+
103
+ def each(node)
104
+ node.each { |element| accept(element) }
105
+ end
106
+
107
+ def visit_Program(program)
108
+ map(program.elements).join("")
109
+ end
110
+
111
+ def visit_ExpressionStatement(statement)
112
+ accept(statement.expression) + ";"
113
+ end
114
+
115
+ def visit_SequenceExpression(expression)
116
+ out = ""
117
+ out << "(" if expression.parens
118
+ exprs = params(expression.expressions)
119
+ exprs = strip_newline(exprs) if expression.parens
120
+ out << exprs
121
+ out << ")" if expression.parens
122
+ out
123
+ end
124
+
125
+ def visit_Literal(literal)
126
+ case val = literal.val
127
+ when nil
128
+ "null"
129
+ when ParseJS::AST::Node
130
+ accept val
131
+ else
132
+ val.inspect
133
+ end
134
+ end
135
+
136
+ def visit_String(string)
137
+ string.quote + super + string.quote
138
+ end
139
+
140
+ def visit_RegExp(regex)
141
+ "/#{super.join("/")}"
142
+ end
143
+
144
+ def visit_DebuggerStatement(expr)
145
+ "#{super};"
146
+ end
147
+
148
+ def visit_UnaryExpression(unary)
149
+ op, argument = super
150
+ space = op =~ /\w/ ? sp : ""
151
+ "#{op}#{space}#{argument}"
152
+ end
153
+
154
+ def visit_AssignmentExpression(expr)
155
+ left, op, right = super
156
+ "#{left} #{op} #{right}"
157
+ end
158
+
159
+ def visit_CallExpression(expr)
160
+ out = strip_newline(accept(expr.callee))
161
+ args = params(expr.args)
162
+ args = strip_newline(args)
163
+ out << "(" + args + ")"
164
+ end
165
+
166
+ def visit_ArrayExpression(expr)
167
+
168
+ "[" + begin
169
+
170
+ last, out = expr.elements.size - 1, ""
171
+ expr.elements.each_with_index do |element, i|
172
+ if element.nil?
173
+ out << ","
174
+ out << " " unless i == last
175
+ else
176
+ out << accept(element)
177
+ out << ", " unless i == last
178
+ end
179
+ end
180
+ out
181
+
182
+ end + "]"
183
+ end
184
+
185
+ def visit_ObjectExpression(expr)
186
+ if expr.properties.length > 2
187
+ out = "{" << newline
188
+ indent
189
+
190
+ last = expr.properties.size - 1
191
+ expr.properties.each_with_index do |prop, i|
192
+ out << strip_newline(accept(prop))
193
+ out << "," unless last == i
194
+ out << newline
195
+ end
196
+
197
+ outdent
198
+ out << current_indent << "}"
199
+ else
200
+ "{#{params(expr.properties)}}"
201
+ end
202
+ end
203
+
204
+ def visit_Property(property)
205
+ comments, key, value = super
206
+
207
+ "#{comments.join}#{key}: #{value}"
208
+ end
209
+
210
+ def visit_CommentedStatement(statement)
211
+ comments, statement = super
212
+ "#{comments.join}#{statement}"
213
+ end
214
+
215
+ def visit_MemberExpression(expr)
216
+ left = strip_newline(accept(expr.object))
217
+ right = accept(expr.property)
218
+
219
+ if expr.computed
220
+ "#{left}[#{right}]"
221
+ else
222
+ "#{left}.#{right}"
223
+ end
224
+ end
225
+
226
+ def visit_NewExpression(expr)
227
+ callee, args = super
228
+
229
+ left = "new #{callee}"
230
+ arg_string = "(#{args.join(", ")})" if args
231
+ return "#{left}#{arg_string}"
232
+ end
233
+
234
+ def visit_BinaryExpression(expr)
235
+ left = strip_newline(accept(expr.left))
236
+ right = accept(expr.right)
237
+
238
+ "#{left} #{expr.op} #{right}"
239
+ end
240
+
241
+ def visit_BlockStatement(statement)
242
+ out = "{" << newline
243
+ indent
244
+ out << super.join
245
+ outdent
246
+ out << current_indent << "}"
247
+ @newline = false unless statement.cuddly
248
+ out
249
+ end
250
+
251
+ def visit_IfStatement(statement)
252
+ consequent = statement.consequent
253
+ alternate = statement.alternate
254
+
255
+ out = "if (" + accept(statement.test) + ")"
256
+
257
+ cuddle(consequent, out, alternate)
258
+
259
+ if alternate
260
+ out << "else"
261
+ cuddle(alternate, out, false)
262
+ end
263
+
264
+ out
265
+ end
266
+
267
+ def visit_WhileStatement(statement)
268
+ test = statement.test
269
+ body = statement.body
270
+
271
+ out = "while (" + accept(test) + ")"
272
+ cuddle(body, out, false)
273
+ out
274
+ end
275
+
276
+ def visit_DoWhileStatement(statement)
277
+ out = "do"
278
+ cuddle(statement.body, out, true)
279
+ out << "while (" + accept(statement.test) + ");"
280
+ end
281
+
282
+ def visit_ForStatement(statement)
283
+ init = statement.init
284
+ test = statement.test
285
+ update = statement.update
286
+ body = statement.body
287
+ out = ""
288
+
289
+ without_newline do
290
+ out << "for (" + accept(init) + ";"
291
+ test = accept(test)
292
+ out << " #{test}" unless test.empty?
293
+ out << ";"
294
+ update = accept(update)
295
+ out << " #{update}" unless update.empty?
296
+ out << ")"
297
+ end
298
+
299
+ cuddle(body, out, false)
300
+ out
301
+ end
302
+
303
+ def visit_VariableDeclaration(decl)
304
+ kind, declarations, semicolon = super
305
+ "#{kind} #{declarations.join(", ")}#{";" if semicolon}"
306
+ end
307
+
308
+ def visit_VariableDeclarator(decl)
309
+ id, init = super
310
+
311
+ out = id
312
+ out << (init ? " = #{init}" : "")
313
+ end
314
+
315
+ def visit_UpdateExpression(expr)
316
+ op, prefix, argument = super
317
+
318
+ op += " " if op =~ /\w/
319
+
320
+ if prefix
321
+ "#{op}#{argument}"
322
+ else
323
+ "#{argument}#{op}"
324
+ end
325
+ end
326
+
327
+ def visit_ForInStatement(statement)
328
+ left = statement.left
329
+ right = statement.right
330
+ body = statement.body
331
+
332
+ out = ""
333
+
334
+ without_newline do
335
+ out << "for (" + accept(left) + " in " + accept(right) + ")"
336
+ end
337
+
338
+ cuddle(body, out, false)
339
+ out
340
+ end
341
+
342
+ def visit_SwitchStatement(statement)
343
+ out = ""
344
+
345
+ without_newline do
346
+ out << "switch (" + accept(statement.discriminant) + ") {" << newline
347
+ indent
348
+ end
349
+
350
+ out << map(statement.cases).join
351
+ outdent
352
+ out << current_indent << "}" << newline
353
+ end
354
+
355
+ def visit_SwitchCase(switch)
356
+ if switch.test
357
+ out = "case #{accept(switch.test)}:" << newline
358
+ else
359
+ out = "default:" << newline
360
+ end
361
+
362
+ indent
363
+ out << map(switch.consequent).join
364
+ outdent
365
+ out
366
+ end
367
+
368
+ def visit_ThrowStatement(statement)
369
+ "throw #{super};"
370
+ end
371
+
372
+ def visit_TryStatement(statement)
373
+ handler = statement.handler
374
+ finalizer = statement.finalizer
375
+
376
+ out = "try"
377
+
378
+ cuddle(statement.block, out, handler || finalizer)
379
+
380
+ if handler
381
+ out << "catch (" + accept(handler.param) + ")"
382
+ cuddle(handler.body, out, finalizer)
383
+ end
384
+
385
+ if finalizer
386
+ out << "finally"
387
+ cuddle(finalizer, out, false)
388
+ end
389
+
390
+ out
391
+ end
392
+
393
+ def visit_FunctionDeclaration(decl)
394
+ id = decl.id
395
+ parameters = decl.params.list
396
+ body = decl.body
397
+
398
+ out = "function " + accept(id) + "("
399
+ out << params(parameters)
400
+ out << ") {" << newline
401
+
402
+ indent
403
+ out << map(decl.body).join
404
+ outdent
405
+ out << current_indent << "}"
406
+ end
407
+
408
+ def labeled(name, label)
409
+ out = name
410
+ out << " #{label}" if label
411
+ out << ";"
412
+ end
413
+
414
+ def visit_ReturnStatement(statement)
415
+ labeled("return", super)
416
+ end
417
+
418
+ def visit_BreakStatement(statement)
419
+ labeled("break", super)
420
+ end
421
+
422
+ def visit_ContinueStatement(statement)
423
+ labeled("continue", super)
424
+ end
425
+
426
+ def visit_ConditionalExpression(expr)
427
+ out = strip_newline(accept(expr.test))
428
+ out << " ? " << strip_newline(accept(expr.consequent))
429
+ out << " : " << accept(expr.alternate)
430
+ end
431
+
432
+ def visit_Comment(comment)
433
+ return "" unless include_comments
434
+ if comment.type == 'singleline'
435
+ "//" + comment.body + newline
436
+ else
437
+ body = comment.body.split("\n")
438
+ first = body.shift
439
+ out = "/*" + first + newline + body.map { |s| "#{current_indent}#{s}" }.join(newline) + "*/"
440
+ out << "\n" if comment.newline
441
+ out
442
+ end
443
+ end
444
+
445
+ private
446
+ def sp
447
+ " "
448
+ end
449
+ end
450
+ end