nscript 0.1.0

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,932 @@
1
+ module NScript
2
+
3
+ class Node
4
+ TAB = ' '
5
+
6
+ def self.statement
7
+ class_eval "def statement?; true; end"
8
+ end
9
+
10
+ def self.statement_only
11
+ statement
12
+ class_eval "def statement_only?; true; end"
13
+ end
14
+
15
+ def self.top_sensitive
16
+ class_eval "def top_sensitive?; true; end"
17
+ end
18
+
19
+ def self.children(*attributes)
20
+ attr_reader(*attributes)
21
+ attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
22
+ class_eval "def children; [#{attrs}].flatten.compact; end"
23
+ end
24
+
25
+ def write(code)
26
+ puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
27
+ code
28
+ end
29
+
30
+ def compile(o={})
31
+ @options = o.dup
32
+ @indent = o[:indent]
33
+ top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
34
+ closure = statement? && !statement_only? && !top && !@options[:return] && !self.is_a?(CommentNode)
35
+ closure &&= !contains? {|n| n.statement_only? }
36
+ closure ? compile_closure(@options) : compile_node(@options)
37
+ end
38
+
39
+ def compile_closure(o={})
40
+ @indent = o[:indent]
41
+ ClosureNode.wrap(self).compile(o.merge(:shared_scope => o[:scope]))
42
+ end
43
+
44
+ def idt(tabs=0)
45
+ @indent + (TAB * tabs)
46
+ end
47
+
48
+ def contains?(&block)
49
+ children.each do |node|
50
+ return true if yield(node)
51
+ return true if node.is_a?(Node) && node.contains?(&block)
52
+ end
53
+ false
54
+ end
55
+
56
+ def unwrap; self; end
57
+ def children; []; end
58
+ def statement?; false; end
59
+ def statement_only?; false; end
60
+ def top_sensitive?; false; end
61
+ end
62
+
63
+ class Expressions < Node
64
+ statement
65
+ children :expressions
66
+ attr_accessor :function
67
+
68
+ TRAILING_WHITESPACE = /\s+$/
69
+
70
+ def self.wrap(*nodes)
71
+ return nodes[0] if nodes.length == 1 && nodes[0].is_a?(Expressions)
72
+ Expressions.new(*nodes)
73
+ end
74
+
75
+ def initialize(*nodes)
76
+ @expressions = nodes.flatten
77
+ end
78
+
79
+ def <<(node)
80
+ @expressions << node
81
+ self
82
+ end
83
+
84
+ def unshift(node)
85
+ @expressions.unshift(node)
86
+ self
87
+ end
88
+
89
+ def unwrap
90
+ @expressions.length == 1 ? @expressions.first : self
91
+ end
92
+
93
+ def empty?
94
+ @expressions.empty?
95
+ end
96
+
97
+ def last?(node)
98
+ @last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
99
+ node == @expressions[@last_index]
100
+ end
101
+
102
+ def compile(o={})
103
+ o[:scope] ? super(o) : compile_root(o)
104
+ end
105
+
106
+ def compile_node(options={})
107
+ write(@expressions.map {|n| compile_expression(n, options.dup) }.join("\n"))
108
+ end
109
+
110
+ def compile_root(o={})
111
+ indent = o[:no_wrap] ? '' : TAB
112
+ @indent = indent
113
+ o.merge!(:indent => indent, :scope => Scope.new(nil, self, nil))
114
+ code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
115
+ code.gsub!(TRAILING_WHITESPACE, '')
116
+ write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();")
117
+ end
118
+
119
+ def compile_with_declarations(o={})
120
+ code = compile_node(o)
121
+ args = self.contains? {|n| n.is_a?(ValueNode) && n.arguments? }
122
+ argv = args && o[:scope].check('arguments') ? '' : 'var '
123
+ code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
124
+ code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
125
+ code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
126
+ write(code)
127
+ end
128
+
129
+ def compile_expression(node, o)
130
+ @indent = o[:indent]
131
+ stmt = node.statement?
132
+ # We need to return the result if this is the last node in the expressions body.
133
+ returns = o.delete(:return) && last?(node) && !node.statement_only?
134
+ # Return the regular compile of the node, unless we need to return the result.
135
+ return "#{stmt ? '' : idt}#{node.compile(o.merge(:top => true))}#{stmt ? '' : ';'}" unless returns
136
+ # If it's a statement, the node knows how to return itself.
137
+ return node.compile(o.merge(:return => true)) if node.statement?
138
+ # If it's not part of a constructor, we can just return the value of the expression.
139
+ return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor?
140
+ # It's the last line of a constructor, add a safety check.
141
+ temp = o[:scope].free_variable
142
+ "#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};"
143
+ end
144
+ end
145
+
146
+ class LiteralNode < Node
147
+ children :value
148
+
149
+ STATEMENTS = ['break', 'continue']
150
+
151
+ def self.wrap(string)
152
+ self.new(Value.new(string))
153
+ end
154
+
155
+ def initialize(value)
156
+ @value = value
157
+ end
158
+
159
+ def statement?
160
+ STATEMENTS.include?(@value.to_s)
161
+ end
162
+ alias_method :statement_only?, :statement?
163
+
164
+ def compile_node(o)
165
+ indent = statement? ? idt : ''
166
+ ending = statement? ? ';' : ''
167
+ "#{indent}#{@value}#{ending}"
168
+ end
169
+ end
170
+
171
+ class ReturnNode < Node
172
+ statement_only
173
+ children :expression
174
+
175
+ def initialize(expression)
176
+ @expression = expression
177
+ end
178
+
179
+ def compile_node(o)
180
+ return write(@expression.compile(o.merge(:return => true))) if @expression.statement?
181
+ compiled = @expression.compile(o)
182
+ write(@expression.statement? ? "#{compiled}\n#{idt}return null;" : "#{idt}return #{compiled};")
183
+ end
184
+ end
185
+
186
+ class CommentNode < Node
187
+ statement
188
+
189
+ def initialize(lines)
190
+ @lines = lines.value
191
+ end
192
+
193
+ def compile_node(o={})
194
+ delimiter = "\n#{idt}//"
195
+ write("#{delimiter}#{@lines.join(delimiter)}")
196
+ end
197
+
198
+ end
199
+
200
+ class CallNode < Node
201
+ children :variable, :arguments
202
+
203
+ def initialize(variable, arguments=[])
204
+ @variable, @arguments = variable, arguments
205
+ @prefix = ''
206
+ end
207
+
208
+ def new_instance
209
+ @prefix = "new "
210
+ self
211
+ end
212
+
213
+ def <<(argument)
214
+ @arguments << argument
215
+ self
216
+ end
217
+
218
+ def compile_node(o)
219
+ return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
220
+ args = @arguments.map{|a| a.compile(o) }.join(', ')
221
+ return write(compile_super(args, o)) if @variable == 'super'
222
+ write("#{@prefix}#{@variable.compile(o)}(#{args})")
223
+ end
224
+
225
+ def compile_super(args, o)
226
+ methname = o[:scope].function.name
227
+ arg_part = args.empty? ? '' : ", #{args}"
228
+ meth = o[:scope].function.proto ?
229
+ "#{o[:scope].function.proto}.__superClass__.#{methname}" :
230
+ "#{methname}.__superClass__.constructor"
231
+ "#{meth}.call(this#{arg_part})"
232
+ end
233
+
234
+ def compile_splat(o)
235
+ meth = @variable.compile(o)
236
+ obj = @variable.source || 'this'
237
+ args = @arguments.map do |arg|
238
+ code = arg.compile(o)
239
+ code = arg.is_a?(SplatNode) ? code : "[#{code}]"
240
+ arg.equal?(@arguments.first) ? code : ".concat(#{code})"
241
+ end
242
+ "#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
243
+ end
244
+
245
+ def compile_reference(o)
246
+ reference = o[:scope].free_variable
247
+ call = ParentheticalNode.new(AssignNode.new(reference, self))
248
+ return call, reference
249
+ end
250
+ end
251
+
252
+ class ExtendsNode < Node
253
+ children :sub_object, :super_object
254
+ statement
255
+
256
+ def initialize(sub_object, super_object)
257
+ @sub_object, @super_object = sub_object, super_object
258
+ end
259
+
260
+ def compile_node(o={})
261
+ constructor = o[:scope].free_variable
262
+ sub, sup = @sub_object.compile(o), @super_object.compile(o)
263
+ "#{idt}#{constructor} = function(){};\n#{idt}" +
264
+ "#{constructor}.prototype = #{sup}.prototype;\n#{idt}" +
265
+ "#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" +
266
+ "#{sub}.prototype = new #{constructor}();\n#{idt}" +
267
+ "#{sub}.prototype.constructor = #{sub};"
268
+ end
269
+ end
270
+
271
+ class ValueNode < Node
272
+ children :base, :properties
273
+ attr_reader :last, :source
274
+
275
+ SOAK = " == undefined ? undefined : "
276
+
277
+ def initialize(base, properties=[])
278
+ @base, @properties = base, [properties].flatten
279
+ end
280
+
281
+ def <<(other)
282
+ @properties << other
283
+ self
284
+ end
285
+
286
+ def properties?
287
+ return !@properties.empty? || @base.is_a?(ThisNode)
288
+ end
289
+
290
+ def array?
291
+ @base.is_a?(ArrayNode) && !properties?
292
+ end
293
+
294
+ def object?
295
+ @base.is_a?(ObjectNode) && !properties?
296
+ end
297
+
298
+ def splice?
299
+ properties? && @properties.last.is_a?(SliceNode)
300
+ end
301
+
302
+ def arguments?
303
+ @base.to_s == 'arguments'
304
+ end
305
+
306
+ def unwrap
307
+ @properties.empty? ? @base : self
308
+ end
309
+
310
+ def statement?
311
+ @base.is_a?(Node) && @base.statement? && !properties?
312
+ end
313
+
314
+ def compile_node(o)
315
+ soaked = false
316
+ only = o.delete(:only_first)
317
+ props = only ? @properties[0...-1] : @properties
318
+ baseline = @base.compile(o)
319
+ parts = [baseline.dup]
320
+ props.each do |prop|
321
+ if prop.is_a?(AccessorNode) && prop.soak
322
+ soaked = true
323
+ if @base.is_a?(CallNode) && prop == props.first
324
+ temp = o[:scope].free_variable
325
+ parts[-1] = "(#{temp} = #{baseline})#{SOAK}#{baseline = temp.to_s + prop.compile(o)}"
326
+ else
327
+ parts[-1] << "#{SOAK}#{baseline += prop.compile(o)}"
328
+ end
329
+ else
330
+ part = prop.compile(o)
331
+ baseline += part
332
+ parts << part
333
+ end
334
+ end
335
+ @last = parts.last
336
+ @source = parts.length > 1 ? parts[0...-1].join('') : nil
337
+ code = parts.join('').gsub(')())', '()))')
338
+ write(soaked ? "(#{code})" : code)
339
+ end
340
+ end
341
+
342
+ class AccessorNode < Node
343
+ children :name
344
+ attr_reader :soak
345
+
346
+ def initialize(name, tag=nil)
347
+ @name = name
348
+ @prototype = tag == :prototype
349
+ @soak = tag == :soak
350
+ end
351
+
352
+ def compile_node(o)
353
+ proto = @prototype ? "prototype." : ''
354
+ write(".#{proto}#{@name}")
355
+ end
356
+ end
357
+
358
+ class IndexNode < Node
359
+ children :index
360
+
361
+ def initialize(index)
362
+ @index = index
363
+ end
364
+
365
+ def compile_node(o)
366
+ write("[#{@index.compile(o)}]")
367
+ end
368
+ end
369
+
370
+ class ThisNode < Node
371
+ def initialize(property=nil)
372
+ @property = property
373
+ end
374
+
375
+ def compile_node(o)
376
+ prop = @property ? ".#{@property}" : ''
377
+ write("this#{prop}")
378
+ end
379
+ end
380
+
381
+ class RangeNode < Node
382
+ children :from, :to
383
+
384
+ def initialize(from, to, exclusive=false)
385
+ @from, @to, @exclusive = from, to, exclusive
386
+ end
387
+
388
+ def exclusive?
389
+ @exclusive
390
+ end
391
+
392
+ def compile_variables(o)
393
+ @indent = o[:indent]
394
+ @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable
395
+ from_val, to_val = @from.compile(o), @to.compile(o)
396
+ write("#{@from_var} = #{from_val}; #{@to_var} = #{to_val};\n#{idt}")
397
+ end
398
+
399
+ def compile_node(o)
400
+ return compile_array(o) unless o[:index]
401
+ idx, step = o.delete(:index), o.delete(:step)
402
+ vars = "#{idx}=#{@from_var}"
403
+ step = step ? step.compile(o) : '1'
404
+ equals = @exclusive ? '' : '='
405
+ compare = "(#{@from_var} <= #{@to_var} ? #{idx} <#{equals} #{@to_var} : #{idx} >#{equals} #{@to_var})"
406
+ incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})"
407
+ write("#{vars}; #{compare}; #{incr}")
408
+ end
409
+
410
+ def compile_array(o)
411
+ body = Expressions.wrap(LiteralNode.wrap('i'))
412
+ arr = Expressions.wrap(ForNode.new(body, {:source => ValueNode.new(self)}, Value.new('i')))
413
+ ParentheticalNode.new(CallNode.new(CodeNode.new([], arr))).compile(o)
414
+ end
415
+ end
416
+
417
+ class SliceNode < Node
418
+ children :range
419
+
420
+ def initialize(range)
421
+ @range = range
422
+ end
423
+
424
+ def compile_node(o)
425
+ from = @range.from.compile(o)
426
+ to = @range.to.compile(o)
427
+ plus_part = @range.exclusive? ? '' : ' + 1'
428
+ write(".slice(#{from}, #{to}#{plus_part})")
429
+ end
430
+ end
431
+
432
+ class AssignNode < Node
433
+ top_sensitive
434
+ children :variable, :value
435
+
436
+ PROTO_ASSIGN = /\A(\S+)\.prototype/
437
+ LEADING_DOT = /\A\.(prototype\.)?/
438
+
439
+ def initialize(variable, value, context=nil)
440
+ @variable, @value, @context = variable, value, context
441
+ end
442
+
443
+ def compile_node(o)
444
+ top = o.delete(:top)
445
+ return compile_pattern_match(o) if statement?
446
+ return compile_splice(o) if value? && @variable.splice?
447
+ stmt = o.delete(:as_statement)
448
+ name = @variable.compile(o)
449
+ last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
450
+ proto = name[PROTO_ASSIGN, 1]
451
+ if @value.is_a?(CodeNode)
452
+ @value.name = last if last.match(Lexer::IDENTIFIER)
453
+ @value.proto = proto if proto
454
+ end
455
+ return write("#{name}: #{@value.compile(o)}") if @context == :object
456
+ o[:scope].find(name) unless value? && @variable.properties?
457
+ val = "#{name} = #{@value.compile(o)}"
458
+ return write("#{idt}#{val};") if stmt
459
+ val = "(#{val})" if !top || o[:return]
460
+ val = "#{idt}return #{val}" if o[:return]
461
+ write(val)
462
+ end
463
+
464
+ def value?
465
+ @variable.is_a?(ValueNode)
466
+ end
467
+
468
+ def statement?
469
+ value? && (@variable.array? || @variable.object?)
470
+ end
471
+
472
+ def compile_pattern_match(o)
473
+ val_var = o[:scope].free_variable
474
+ assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"]
475
+ o.merge!(:top => true, :as_statement => true)
476
+ @variable.base.objects.each_with_index do |obj, i|
477
+ obj, i = obj.value, obj.variable.base if @variable.object?
478
+ access_class = @variable.array? ? IndexNode : AccessorNode
479
+ if obj.is_a?(SplatNode)
480
+ val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
481
+ else
482
+ val = ValueNode.new(val_var, [access_class.new(Value.new(i.to_s))])
483
+ end
484
+ assigns << AssignNode.new(obj, val).compile(o)
485
+ end
486
+ write(assigns.join("\n"))
487
+ end
488
+
489
+ def compile_splice(o)
490
+ var = @variable.compile(o.merge(:only_first => true))
491
+ range = @variable.properties.last.range
492
+ plus = range.exclusive? ? '' : ' + 1'
493
+ from = range.from.compile(o)
494
+ to = "#{range.to.compile(o)} - #{from}#{plus}"
495
+ write("#{var}.splice.apply(#{var}, [#{from}, #{to}].concat(#{@value.compile(o)}))")
496
+ end
497
+ end
498
+
499
+ class OpNode < Node
500
+ children :first, :second
501
+ attr_reader :operator
502
+ attr_accessor :second
503
+
504
+ CONVERSIONS = {
505
+ :== => "===",
506
+ :'!=' => "!==",
507
+ :and => '&&',
508
+ :or => '||',
509
+ :is => '===',
510
+ :isnt => "!==",
511
+ :not => '!'
512
+ }
513
+ CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
514
+ ASSIGNMENT = [:'||=', :'&&=', :'?=']
515
+ PREFIX_OPERATORS = [:typeof, :delete]
516
+
517
+ def initialize(operator, first, second=nil, flip=false)
518
+ @first, @second, @flip = first, second, flip
519
+ @operator = CONVERSIONS[operator.to_sym] || operator
520
+ end
521
+
522
+ def unary?
523
+ @second.nil?
524
+ end
525
+
526
+ def chainable?
527
+ CHAINABLE.include?(operator.to_sym)
528
+ end
529
+
530
+ def compile_node(o)
531
+ return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
532
+ return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
533
+ return write(compile_unary(o)) if unary?
534
+ return write(compile_existence(o)) if @operator == '?'
535
+ write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
536
+ end
537
+
538
+ def compile_chain(o)
539
+ shared = @first.unwrap.second
540
+ @first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
541
+ "(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
542
+ end
543
+
544
+ def compile_assignment(o)
545
+ first, second = @first.compile(o), @second.compile(o)
546
+ o[:scope].find(first) if @first.unwrap.is_a?(Value)
547
+ sym = @operator[0..1]
548
+ return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
549
+ "#{first} = #{first} #{sym} #{second}"
550
+ end
551
+
552
+ def compile_existence(o)
553
+ first, second = @first.compile(o), @second.compile(o)
554
+ "#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
555
+ end
556
+
557
+ def compile_unary(o)
558
+ space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
559
+ parts = [@operator.to_s, space, @first.compile(o)]
560
+ parts.reverse! if @flip
561
+ parts.join('')
562
+ end
563
+ end
564
+
565
+ class CodeNode < Node
566
+ top_sensitive
567
+ attr_reader :params, :body, :bound
568
+ attr_accessor :name, :proto
569
+
570
+ UPPERCASE = /[A-Z]/
571
+
572
+ def initialize(params, body, tag=nil)
573
+ @params = params
574
+ @body = body
575
+ @bound = tag == :boundfunc
576
+ end
577
+
578
+ def constructor?
579
+ @name && @name[0..0][UPPERCASE]
580
+ end
581
+
582
+ def compile_node(o)
583
+ shared_scope = o.delete(:shared_scope)
584
+ top = o.delete(:top)
585
+ o[:scope] = shared_scope || Scope.new(o[:scope], @body, self)
586
+ o[:return] = true
587
+ o[:top] = true
588
+ o[:indent] = idt(@bound ? 2 : 1)
589
+ o.delete(:no_wrap)
590
+ o.delete(:globals)
591
+ if @params.last.is_a?(SplatNode)
592
+ splat = @params.pop
593
+ splat.index = @params.length
594
+ @body.unshift(splat)
595
+ end
596
+ @params.each {|id| o[:scope].parameter(id.to_s) }
597
+ code = @body.empty? ? "" : "\n#{@body.compile_with_declarations(o)}\n"
598
+ name_part = @name ? " #{@name}" : ''
599
+ func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt(@bound ? 1 : 0)}}"
600
+ func = "(#{func})" if top && !@bound
601
+ return write(func) unless @bound
602
+ inner = "(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
603
+ write("(function(__this) {\n#{idt(1)}var __func = #{func};\n#{idt(1)}return #{inner}\n#{idt}})(this)")
604
+ end
605
+ end
606
+
607
+ class SplatNode < Node
608
+ children :name
609
+ attr_accessor :index
610
+
611
+ def initialize(name)
612
+ @name = name
613
+ end
614
+
615
+ def compile_node(o={})
616
+ write(@index ? compile_param(o) : @name.compile(o))
617
+ end
618
+
619
+ def compile_param(o)
620
+ o[:scope].find(@name)
621
+ "#{@name} = Array.prototype.slice.call(arguments, #{@index})"
622
+ end
623
+
624
+ def compile_value(o, name, index)
625
+ "Array.prototype.slice.call(#{name}, #{index})"
626
+ end
627
+ end
628
+
629
+ class ObjectNode < Node
630
+ children :properties
631
+ alias_method :objects, :properties
632
+
633
+ def initialize(properties = [])
634
+ @properties = properties
635
+ end
636
+
637
+ def compile_node(o)
638
+ o[:indent] = idt(1)
639
+ joins = Hash.new("\n")
640
+ non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
641
+ non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
642
+ props = @properties.map { |prop|
643
+ join = joins[prop]
644
+ join = '' if prop == @properties.last
645
+ indent = prop.is_a?(CommentNode) ? '' : idt(1)
646
+ "#{indent}#{prop.compile(o)}#{join}"
647
+ }.join('')
648
+ write("{\n#{props}\n#{idt}}")
649
+ end
650
+ end
651
+
652
+ class ArrayNode < Node
653
+ children :objects
654
+
655
+ def initialize(objects=[])
656
+ @objects = objects
657
+ end
658
+
659
+ def compile_node(o)
660
+ o[:indent] = idt(1)
661
+ objects = @objects.map { |obj|
662
+ code = obj.compile(o)
663
+ obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
664
+ obj == @objects.last ? code : "#{code}, "
665
+ }.join('')
666
+ ending = objects.include?("\n") ? "\n#{idt}]" : ']'
667
+ write("[#{objects}#{ending}")
668
+ end
669
+ end
670
+
671
+ class PushNode
672
+ def self.wrap(array, expressions)
673
+ expr = expressions.unwrap
674
+ return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
675
+ Expressions.wrap(CallNode.new(
676
+ ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
677
+ [expr]
678
+ ))
679
+ end
680
+ end
681
+
682
+ class ClosureNode
683
+ def self.wrap(expressions, statement=false)
684
+ func = ParentheticalNode.new(CodeNode.new([], Expressions.wrap(expressions)))
685
+ call = CallNode.new(ValueNode.new(func, AccessorNode.new(Value.new('call'))), [Value.new('this')])
686
+ statement ? Expressions.wrap(call) : call
687
+ end
688
+ end
689
+
690
+ class WhileNode < Node
691
+ top_sensitive
692
+ children :condition, :body
693
+ statement
694
+
695
+ def initialize(condition, body)
696
+ @condition, @body = condition, body
697
+ end
698
+
699
+ def compile_node(o)
700
+ returns = o.delete(:return)
701
+ top = o.delete(:top) && !returns
702
+ o[:indent] = idt(1)
703
+ o[:top] = true
704
+ cond = @condition.compile(o)
705
+ set = ''
706
+ if !top
707
+ rvar = o[:scope].free_variable
708
+ set = "#{idt}#{rvar} = [];\n"
709
+ @body = PushNode.wrap(rvar, @body)
710
+ end
711
+ post = returns ? "\n#{idt}return #{rvar};" : ''
712
+ return write("#{set}#{idt}while (#{cond}) null;#{post}") if @body.nil?
713
+ write("#{set}#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
714
+ end
715
+ end
716
+
717
+ class ForNode < Node
718
+ top_sensitive
719
+ children :body, :source, :filter
720
+ attr_reader :name, :index, :step
721
+ statement
722
+
723
+ def initialize(body, source, name, index=nil)
724
+ @body, @name, @index = body, name, index
725
+ @source = source[:source]
726
+ @filter = source[:filter]
727
+ @step = source[:step]
728
+ @object = !!source[:object]
729
+ @name, @index = @index, @name if @object
730
+ end
731
+
732
+ def compile_node(o)
733
+ top_level = o.delete(:top) && !o[:return]
734
+ range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
735
+ source = range ? @source.base : @source
736
+ scope = o[:scope]
737
+ name_found = @name && scope.find(@name)
738
+ index_found = @index && scope.find(@index)
739
+ body_dent = idt(1)
740
+ rvar = scope.free_variable unless top_level
741
+ svar = scope.free_variable
742
+ ivar = range ? name : @index ? @index : scope.free_variable
743
+ var_part = ''
744
+ body = Expressions.wrap(@body)
745
+ if range
746
+ index_var = scope.free_variable
747
+ source_part = source.compile_variables(o)
748
+ for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
749
+ else
750
+ index_var = nil
751
+ source_part = "#{svar} = #{@source.compile(o)};\n#{idt}"
752
+ step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
753
+ for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
754
+ var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
755
+ # body.unshift(AssignNode.new(@name, ValueNode.new(svar, [IndexNode.new(ivar)]))) if @name
756
+ end
757
+ set_result = rvar ? "#{idt}#{rvar} = []; " : idt
758
+ return_result = rvar || ''
759
+ body = ClosureNode.wrap(body, true) if top_level && contains? {|n| n.is_a? CodeNode }
760
+ body = PushNode.wrap(rvar, body) unless top_level
761
+ if o[:return]
762
+ return_result = "return #{return_result}" if o[:return]
763
+ o.delete(:return)
764
+ body = IfNode.new(@filter, body, nil, :statement => true) if @filter
765
+ elsif @filter
766
+ body = Expressions.wrap(IfNode.new(@filter, body))
767
+ end
768
+ if @object
769
+ o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
770
+ body = Expressions.wrap(IfNode.new(
771
+ CallNode.new(
772
+ ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
773
+ [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
774
+ ),
775
+ Expressions.wrap(body), nil, {:statement => true}
776
+ ))
777
+ end
778
+
779
+ return_result = "\n#{idt}#{return_result};" unless top_level
780
+ body = body.compile(o.merge(:indent => body_dent, :top => true))
781
+ vars = range ? @name : "#{@name}, #{ivar}"
782
+ return write(set_result + source_part + "for (#{for_part}) {\n#{var_part}#{body}\n#{idt}}\n#{idt}#{return_result}")
783
+ end
784
+ end
785
+
786
+ class TryNode < Node
787
+ children :try, :recovery, :finally
788
+ attr_reader :error
789
+ statement
790
+
791
+ def initialize(try, error, recovery, finally=nil)
792
+ @try, @error, @recovery, @finally = try, error, recovery, finally
793
+ end
794
+
795
+ def compile_node(o)
796
+ o[:indent] = idt(1)
797
+ o[:top] = true
798
+ error_part = @error ? " (#{@error}) " : ' '
799
+ catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}"
800
+ finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}"
801
+ write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}")
802
+ end
803
+ end
804
+
805
+ class ThrowNode < Node
806
+ children :expression
807
+ statement_only
808
+
809
+ def initialize(expression)
810
+ @expression = expression
811
+ end
812
+
813
+ def compile_node(o)
814
+ write("#{idt}throw #{@expression.compile(o)};")
815
+ end
816
+ end
817
+
818
+ class ExistenceNode < Node
819
+ children :expression
820
+
821
+ def self.compile_test(o, variable)
822
+ first, second = variable, variable
823
+ first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
824
+ "(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
825
+ end
826
+
827
+ def initialize(expression)
828
+ @expression = expression
829
+ end
830
+
831
+ def compile_node(o)
832
+ write(ExistenceNode.compile_test(o, @expression))
833
+ end
834
+ end
835
+
836
+ class ParentheticalNode < Node
837
+ children :expressions
838
+
839
+ def initialize(expressions, line=nil)
840
+ @expressions = expressions.unwrap
841
+ @line = line
842
+ end
843
+
844
+ def compile_node(o)
845
+ compiled = @expressions.compile(o)
846
+ compiled = compiled[0...-1] if compiled[-1..-1] == ';'
847
+ write("(#{compiled})")
848
+ end
849
+ end
850
+
851
+ class IfNode < Node
852
+ children :condition, :body, :else_body
853
+
854
+ def initialize(condition, body, else_body=nil, tags={})
855
+ @condition = condition
856
+ @body = body && body.unwrap
857
+ @else_body = else_body && else_body.unwrap
858
+ @tags = tags
859
+ @multiple = true if @condition.is_a?(Array)
860
+ @condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
861
+ end
862
+
863
+ def <<(else_body)
864
+ eb = else_body.unwrap
865
+ @else_body ? @else_body << eb : @else_body = eb
866
+ self
867
+ end
868
+
869
+ def add_comment(comment)
870
+ @comment = comment
871
+ self
872
+ end
873
+
874
+ def force_statement
875
+ @tags[:statement] = true
876
+ self
877
+ end
878
+
879
+ def rewrite_condition(expression)
880
+ @condition = @multiple ? @condition.map {|c| OpNode.new("is", expression, c) } :
881
+ OpNode.new("is", expression, @condition)
882
+ @else_body.rewrite_condition(expression) if chain?
883
+ self
884
+ end
885
+
886
+ def add_else(exprs)
887
+ chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap)
888
+ self
889
+ end
890
+
891
+ def chain?
892
+ @chain ||= @else_body && @else_body.is_a?(IfNode)
893
+ end
894
+
895
+ def statement?
896
+ @is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
897
+ end
898
+
899
+ def compile_condition(o)
900
+ [@condition].flatten.map {|c| c.compile(o) }.join(' || ')
901
+ end
902
+
903
+ def compile_node(o)
904
+ write(statement? ? compile_statement(o) : compile_ternary(o))
905
+ end
906
+
907
+ def compile_statement(o)
908
+ child = o.delete(:chain_child)
909
+ cond_o = o.dup
910
+ cond_o.delete(:return)
911
+ o[:indent] = idt(1)
912
+ o[:top] = true
913
+ if_dent = child ? '' : idt
914
+ com_dent = child ? idt : ''
915
+ prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
916
+ body = Expressions.wrap(@body).compile(o)
917
+ if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
918
+ return if_part unless @else_body
919
+ else_part = chain? ?
920
+ " else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :
921
+ " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{idt}}"
922
+ if_part + else_part
923
+ end
924
+
925
+ def compile_ternary(o)
926
+ if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}"
927
+ else_part = @else_body ? "#{@else_body.compile(o)}" : 'null'
928
+ "#{if_part} : #{else_part}"
929
+ end
930
+ end
931
+
932
+ end # end - NScript