style-script 1.0.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,1079 @@
1
+ module StyleScript
2
+
3
+ # The abstract base class for all StyleScript nodes.
4
+ # All nodes are implement a "compile_node" method, which performs the
5
+ # code generation for that node. To compile a node, call the "compile"
6
+ # method, which wraps "compile_node" in some extra smarts, to know when the
7
+ # generated code should be wrapped up in a closure. An options hash is passed
8
+ # and cloned throughout, containing messages from higher in the AST,
9
+ # information about the current scope, and indentation level.
10
+ class Node
11
+ # Tabs are two spaces for pretty-printing.
12
+ TAB = ' '
13
+
14
+ # Tag this node as a statement, meaning that it can't be used directly as
15
+ # the result of an expression.
16
+ def self.statement
17
+ class_eval "def statement?; true; end"
18
+ end
19
+
20
+ # Tag this node as a statement that cannot be transformed into an expression.
21
+ # (break, continue, etc.) It doesn't make sense to try to transform it.
22
+ def self.statement_only
23
+ statement
24
+ class_eval "def statement_only?; true; end"
25
+ end
26
+
27
+ # This node needs to know if it's being compiled as a top-level statement,
28
+ # in order to compile without special expression conversion.
29
+ def self.top_sensitive
30
+ class_eval "def top_sensitive?; true; end"
31
+ end
32
+
33
+ # Provide a quick implementation of a children method.
34
+ def self.children(*attributes)
35
+ attr_reader(*attributes)
36
+ attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
37
+ class_eval "def children; [#{attrs}].flatten.compact; end"
38
+ end
39
+
40
+ def write(code)
41
+ puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
42
+ code
43
+ end
44
+
45
+ # This is extremely important -- we convert JS statements into expressions
46
+ # by wrapping them in a closure, only if it's possible, and we're not at
47
+ # the top level of a block (which would be unnecessary), and we haven't
48
+ # already been asked to return the result.
49
+ def compile(o={})
50
+ @options = o.dup
51
+ @indent = o[:indent]
52
+ top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
53
+ closure = statement? && !statement_only? && !top && !@options[:return] && !self.is_a?(CommentNode)
54
+ closure &&= !contains? {|n| n.statement_only? }
55
+ closure ? compile_closure(@options) : compile_node(@options)
56
+ end
57
+
58
+ # Statements converted into expressions share scope with their parent
59
+ # closure, to preserve JavaScript-style lexical scope.
60
+ def compile_closure(o={})
61
+ @indent = o[:indent]
62
+ ClosureNode.wrap(self).compile(o.merge(:shared_scope => o[:scope]))
63
+ end
64
+
65
+ # Quick short method for the current indentation level, plus tabbing in.
66
+ def idt(tabs=0)
67
+ @indent + (TAB * tabs)
68
+ end
69
+
70
+ # Does this node, or any of its children, contain a node of a certain kind?
71
+ def contains?(&block)
72
+ children.each do |node|
73
+ return true if yield(node)
74
+ return true if node.is_a?(Node) && node.contains?(&block)
75
+ end
76
+ false
77
+ end
78
+
79
+ # Default implementations of the common node methods.
80
+ def unwrap; self; end
81
+ def children; []; end
82
+ def statement?; false; end
83
+ def statement_only?; false; end
84
+ def top_sensitive?; false; end
85
+ end
86
+
87
+ # A collection of nodes, each one representing an expression.
88
+ class Expressions < Node
89
+ statement
90
+ children :expressions
91
+ attr_accessor :function
92
+
93
+ TRAILING_WHITESPACE = /\s+$/
94
+
95
+ # Wrap up a node as an Expressions, unless it already is.
96
+ def self.wrap(*nodes)
97
+ return nodes[0] if nodes.length == 1 && nodes[0].is_a?(Expressions)
98
+ Expressions.new(*nodes)
99
+ end
100
+
101
+ def initialize(*nodes)
102
+ @expressions = nodes.flatten
103
+ end
104
+
105
+ # Tack an expression on to the end of this expression list.
106
+ def <<(node)
107
+ @expressions << node
108
+ self
109
+ end
110
+
111
+ # Tack an expression on to the beginning of this expression list.
112
+ def unshift(node)
113
+ @expressions.unshift(node)
114
+ self
115
+ end
116
+
117
+ # If this Expressions consists of a single node, pull it back out.
118
+ def unwrap
119
+ @expressions.length == 1 ? @expressions.first : self
120
+ end
121
+
122
+ # Is this an empty block of code?
123
+ def empty?
124
+ @expressions.empty?
125
+ end
126
+
127
+ # Is the node last in this block of expressions?
128
+ def last?(node)
129
+ @last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
130
+ node == @expressions[@last_index]
131
+ end
132
+
133
+ def compile(o={})
134
+ o[:scope] ? super(o) : compile_root(o)
135
+ end
136
+
137
+ # Compile each expression in the Expressions body.
138
+ def compile_node(o={})
139
+ write(@expressions.map {|n| compile_expression(n, o.dup) }.join("\n"))
140
+ end
141
+
142
+ # If this is the top-level Expressions, wrap everything in a safety closure.
143
+ def compile_root(o={})
144
+ indent = o[:no_wrap] ? '' : TAB
145
+ @indent = indent
146
+ o.merge!(:indent => indent, :scope => Scope.new(nil, self, nil))
147
+ code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
148
+ code.gsub!(TRAILING_WHITESPACE, '')
149
+ write(o[:no_wrap] ? code : "// Generated by StyleScript v1.0.0\n(function(){\n#{code}\n})();")
150
+ end
151
+
152
+ # Compile the expressions body, with declarations of all inner variables
153
+ # pushed up to the top.
154
+ def compile_with_declarations(o={})
155
+ code = compile_node(o)
156
+ args = self.contains? {|n| n.is_a?(ValueNode) && n.arguments? }
157
+ argv = args && o[:scope].check('arguments') ? '' : 'var '
158
+ code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
159
+ code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
160
+ code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
161
+ write(code)
162
+ end
163
+
164
+ # Compiles a single expression within the expressions body.
165
+ def compile_expression(node, o)
166
+ @indent = o[:indent]
167
+ stmt = node.statement?
168
+ # We need to return the result if this is the last node in the expressions body.
169
+ returns = o.delete(:return) && last?(node) && !node.statement_only?
170
+ # Return the regular compile of the node, unless we need to return the result.
171
+ return "#{stmt ? '' : idt}#{node.compile(o.merge(:top => true))}#{stmt ? '' : ';'}" unless returns
172
+ # If it's a statement, the node knows how to return itself.
173
+ return node.compile(o.merge(:return => true)) if node.statement?
174
+ # If it's not part of a constructor, we can just return the value of the expression.
175
+ return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor?
176
+ # It's the last line of a constructor, add a safety check.
177
+ temp = o[:scope].free_variable
178
+ "#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};"
179
+ end
180
+
181
+ end
182
+
183
+ # Literals are static values that can be passed through directly into
184
+ # JavaScript without translation, eg.: strings, numbers, true, false, null...
185
+ class LiteralNode < Node
186
+ children :value
187
+
188
+ # Values of a literal node that much be treated as a statement -- no
189
+ # sense returning or assigning them.
190
+ STATEMENTS = ['break', 'continue']
191
+
192
+ # Wrap up a compiler-generated string as a LiteralNode.
193
+ def self.wrap(string)
194
+ self.new(Value.new(string))
195
+ end
196
+
197
+ def initialize(value)
198
+ @value = value
199
+ end
200
+
201
+ def statement?
202
+ STATEMENTS.include?(@value.to_s)
203
+ end
204
+ alias_method :statement_only?, :statement?
205
+
206
+ def compile_node(o)
207
+ indent = statement? ? idt : ''
208
+ ending = statement? ? ';' : ''
209
+ "#{indent}#{@value}#{ending}"
210
+ end
211
+ end
212
+
213
+ # Return an expression, or wrap it in a closure and return it.
214
+ class ReturnNode < Node
215
+ statement_only
216
+ children :expression
217
+
218
+ def initialize(expression)
219
+ @expression = expression
220
+ end
221
+
222
+ def compile_node(o)
223
+ return write(@expression.compile(o.merge(:return => true))) if @expression.statement?
224
+ compiled = @expression.compile(o)
225
+ write("#{idt}return #{compiled};")
226
+ end
227
+ end
228
+
229
+ # A value, indexed or dotted into, or vanilla.
230
+ class ValueNode < Node
231
+ children :base, :properties
232
+ attr_reader :last, :source
233
+
234
+ # Soak up undefined properties and call attempts.
235
+ SOAK = " == undefined ? undefined : "
236
+
237
+ def initialize(base, properties=[])
238
+ @base, @properties = base, [properties].flatten
239
+ end
240
+
241
+ def <<(other)
242
+ @properties << other
243
+ self
244
+ end
245
+
246
+ def properties?
247
+ return !@properties.empty? || @base.is_a?(ThisNode)
248
+ end
249
+
250
+ def array?
251
+ @base.is_a?(ArrayNode) && !properties?
252
+ end
253
+
254
+ def object?
255
+ @base.is_a?(ObjectNode) && !properties?
256
+ end
257
+
258
+ def splice?
259
+ properties? && @properties.last.is_a?(SliceNode)
260
+ end
261
+
262
+ def arguments?
263
+ @base.to_s == 'arguments'
264
+ end
265
+
266
+ def unwrap
267
+ @properties.empty? ? @base : self
268
+ end
269
+
270
+ # Values are statements if their base is a statement.
271
+ def statement?
272
+ @base.is_a?(Node) && @base.statement? && !properties?
273
+ end
274
+
275
+ def compile_node(o)
276
+ soaked = false
277
+ only = o.delete(:only_first)
278
+ props = only ? @properties[0...-1] : @properties
279
+ baseline = @base.compile(o)
280
+ parts = [baseline.dup]
281
+ props.each do |prop|
282
+ if prop.is_a?(AccessorNode) && prop.soak
283
+ soaked = true
284
+ if @base.is_a?(CallNode) && prop == props.first
285
+ temp = o[:scope].free_variable
286
+ parts[-1] = "(#{temp} = #{baseline})#{SOAK}#{baseline = temp.to_s + prop.compile(o)}"
287
+ else
288
+ parts[-1] << "#{SOAK}#{baseline += prop.compile(o)}"
289
+ end
290
+ else
291
+ part = prop.compile(o)
292
+ baseline += part
293
+ parts << part
294
+ end
295
+ end
296
+ @last = parts.last
297
+ @source = parts.length > 1 ? parts[0...-1].join('') : nil
298
+ code = parts.join('').gsub(')())', '()))')
299
+ write(soaked ? "(#{code})" : code)
300
+ end
301
+ end
302
+
303
+ # Pass through StyleScript comments into JavaScript comments at the
304
+ # same position.
305
+ class CommentNode < Node
306
+ statement
307
+
308
+ def initialize(lines)
309
+ @lines = lines.value
310
+ end
311
+
312
+ def compile_node(o={})
313
+ delimiter = "\n#{idt}//"
314
+ write("#{delimiter}#{@lines.join(delimiter)}")
315
+ end
316
+
317
+ end
318
+
319
+ # Node for a function invocation. Takes care of converting super() calls into
320
+ # calls against the prototype's function of the same name.
321
+ class CallNode < Node
322
+ children :variable, :arguments
323
+
324
+ def initialize(variable, arguments=[])
325
+ @variable, @arguments = variable, arguments
326
+ @prefix = ''
327
+ end
328
+
329
+ def new_instance
330
+ @prefix = "new "
331
+ self
332
+ end
333
+
334
+ def <<(argument)
335
+ @arguments << argument
336
+ self
337
+ end
338
+
339
+ # Compile a vanilla function call.
340
+ def compile_node(o)
341
+ return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
342
+ args = @arguments.map{|a| a.compile(o) }.join(', ')
343
+ return write(compile_super(args, o)) if @variable == 'super'
344
+ write("#{@prefix}#{@variable.compile(o)}(#{args})")
345
+ end
346
+
347
+ # Compile a call against the superclass's implementation of the current function.
348
+ def compile_super(args, o)
349
+ methname = o[:scope].function.name
350
+ arg_part = args.empty? ? '' : ", #{args}"
351
+ meth = o[:scope].function.proto ?
352
+ "#{o[:scope].function.proto}.__superClass__.#{methname}" :
353
+ "#{methname}.__superClass__.constructor"
354
+ "#{meth}.call(this#{arg_part})"
355
+ end
356
+
357
+ # Compile a function call being passed variable arguments.
358
+ def compile_splat(o)
359
+ meth = @variable.compile(o)
360
+ obj = @variable.source || 'this'
361
+ args = @arguments.map do |arg|
362
+ code = arg.compile(o)
363
+ code = arg.is_a?(SplatNode) ? code : "[#{code}]"
364
+ arg.equal?(@arguments.first) ? code : ".concat(#{code})"
365
+ end
366
+ "#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
367
+ end
368
+
369
+ # If the code generation wished to use the result of a function call
370
+ # in multiple places, ensure that the function is only ever called once.
371
+ def compile_reference(o)
372
+ reference = o[:scope].free_variable
373
+ call = ParentheticalNode.new(AssignNode.new(reference, self))
374
+ return call, reference
375
+ end
376
+ end
377
+
378
+ # Node to extend an object's prototype with an ancestor object.
379
+ # After goog.inherits from the Closure Library.
380
+ class ExtendsNode < Node
381
+ children :sub_object, :super_object
382
+ statement
383
+
384
+ def initialize(sub_object, super_object)
385
+ @sub_object, @super_object = sub_object, super_object
386
+ end
387
+
388
+ # Hooking one constructor into another's prototype chain.
389
+ def compile_node(o={})
390
+ constructor = o[:scope].free_variable
391
+ sub, sup = @sub_object.compile(o), @super_object.compile(o)
392
+ "#{idt}#{constructor} = function(){};\n#{idt}" +
393
+ "#{constructor}.prototype = #{sup}.prototype;\n#{idt}" +
394
+ "#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" +
395
+ "#{sub}.prototype = new #{constructor}();\n#{idt}" +
396
+ "#{sub}.prototype.constructor = #{sub};"
397
+ end
398
+
399
+ end
400
+
401
+ # A dotted accessor into a part of a value, or the :: shorthand for
402
+ # an accessor into the object's prototype.
403
+ class AccessorNode < Node
404
+ children :name
405
+ attr_reader :soak
406
+
407
+ def initialize(name, tag=nil)
408
+ @name = name
409
+ @prototype = tag == :prototype
410
+ @soak = tag == :soak
411
+ end
412
+
413
+ def compile_node(o)
414
+ proto = @prototype ? "prototype." : ''
415
+ write(".#{proto}#{@name}")
416
+ end
417
+ end
418
+
419
+ # An indexed accessor into a part of an array or object.
420
+ class IndexNode < Node
421
+ children :index
422
+
423
+ def initialize(index)
424
+ @index = index
425
+ end
426
+
427
+ def compile_node(o)
428
+ write("[#{@index.compile(o)}]")
429
+ end
430
+ end
431
+
432
+ # A this-reference, using '@'.
433
+ class ThisNode < Node
434
+ def initialize(property=nil)
435
+ @property = property
436
+ end
437
+
438
+ def compile_node(o)
439
+ prop = @property ? ".#{@property}" : ''
440
+ write("this#{prop}")
441
+ end
442
+ end
443
+
444
+ # A range literal. Ranges can be used to extract portions (slices) of arrays,
445
+ # or to specify a range for array comprehensions.
446
+ class RangeNode < Node
447
+ children :from, :to
448
+
449
+ def initialize(from, to, exclusive=false)
450
+ @from, @to, @exclusive = from, to, exclusive
451
+ end
452
+
453
+ def exclusive?
454
+ @exclusive
455
+ end
456
+
457
+ def compile_variables(o)
458
+ @indent = o[:indent]
459
+ @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable
460
+ from_val, to_val = @from.compile(o), @to.compile(o)
461
+ write("#{@from_var} = #{from_val}; #{@to_var} = #{to_val};\n#{idt}")
462
+ end
463
+
464
+ def compile_node(o)
465
+ return compile_array(o) unless o[:index]
466
+ idx, step = o.delete(:index), o.delete(:step)
467
+ vars = "#{idx}=#{@from_var}"
468
+ step = step ? step.compile(o) : '1'
469
+ equals = @exclusive ? '' : '='
470
+ compare = "(#{@from_var} <= #{@to_var} ? #{idx} <#{equals} #{@to_var} : #{idx} >#{equals} #{@to_var})"
471
+ incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})"
472
+ write("#{vars}; #{compare}; #{incr}")
473
+ end
474
+
475
+ # Expand the range into the equivalent array, if it's not being used as
476
+ # part of a comprehension, slice, or splice.
477
+ # TODO: This generates pretty ugly code ... shrink it.
478
+ def compile_array(o)
479
+ body = Expressions.wrap(LiteralNode.wrap('i'))
480
+ arr = Expressions.wrap(ForNode.new(body, {:source => ValueNode.new(self)}, Value.new('i')))
481
+ ParentheticalNode.new(CallNode.new(CodeNode.new([], arr))).compile(o)
482
+ end
483
+
484
+ end
485
+
486
+ # An array slice literal. Unlike JavaScript's Array#slice, the second parameter
487
+ # specifies the index of the end of the slice (just like the first parameter)
488
+ # is the index of the beginning.
489
+ class SliceNode < Node
490
+ children :range
491
+
492
+ def initialize(range)
493
+ @range = range
494
+ end
495
+
496
+ def compile_node(o)
497
+ from = @range.from.compile(o)
498
+ to = @range.to.compile(o)
499
+ plus_part = @range.exclusive? ? '' : ' + 1'
500
+ write(".slice(#{from}, #{to}#{plus_part})")
501
+ end
502
+ end
503
+
504
+ # Setting the value of a local variable, or the value of an object property.
505
+ class AssignNode < Node
506
+ top_sensitive
507
+ children :variable, :value
508
+
509
+ PROTO_ASSIGN = /\A(\S+)\.prototype/
510
+ LEADING_DOT = /\A\.(prototype\.)?/
511
+
512
+ def initialize(variable, value, context=nil)
513
+ @variable, @value, @context = variable, value, context
514
+ end
515
+
516
+ def compile_node(o)
517
+ top = o.delete(:top)
518
+ return compile_pattern_match(o) if statement?
519
+ return compile_splice(o) if value? && @variable.splice?
520
+ stmt = o.delete(:as_statement)
521
+ name = @variable.compile(o)
522
+ last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
523
+ proto = name[PROTO_ASSIGN, 1]
524
+ if @value.is_a?(CodeNode)
525
+ @value.name = last if last.match(Lexer::IDENTIFIER)
526
+ @value.proto = proto if proto
527
+ end
528
+ return write("#{name}: #{@value.compile(o)}") if @context == :object
529
+ o[:scope].find(name) unless value? && @variable.properties?
530
+ val = "#{name} = #{@value.compile(o)}"
531
+ return write("#{idt}#{val};") if stmt
532
+ val = "(#{val})" if !top || o[:return]
533
+ val = "#{idt}return #{val}" if o[:return]
534
+ write(val)
535
+ end
536
+
537
+ def value?
538
+ @variable.is_a?(ValueNode)
539
+ end
540
+
541
+ def statement?
542
+ value? && (@variable.array? || @variable.object?)
543
+ end
544
+
545
+ # Implementation of recursive pattern matching, when assigning array or
546
+ # object literals to a value. Peeks at their properties to assign inner names.
547
+ # See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
548
+ def compile_pattern_match(o)
549
+ val_var = o[:scope].free_variable
550
+ assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"]
551
+ o.merge!(:top => true, :as_statement => true)
552
+ @variable.base.objects.each_with_index do |obj, i|
553
+ obj, i = obj.value, obj.variable.base if @variable.object?
554
+ access_class = @variable.array? ? IndexNode : AccessorNode
555
+ if obj.is_a?(SplatNode)
556
+ val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
557
+ else
558
+ val = ValueNode.new(val_var, [access_class.new(Value.new(i.to_s))])
559
+ end
560
+ assigns << AssignNode.new(obj, val).compile(o)
561
+ end
562
+ write(assigns.join("\n"))
563
+ end
564
+
565
+ def compile_splice(o)
566
+ var = @variable.compile(o.merge(:only_first => true))
567
+ range = @variable.properties.last.range
568
+ plus = range.exclusive? ? '' : ' + 1'
569
+ from = range.from.compile(o)
570
+ to = "#{range.to.compile(o)} - #{from}#{plus}"
571
+ write("#{var}.splice.apply(#{var}, [#{from}, #{to}].concat(#{@value.compile(o)}))")
572
+ end
573
+ end
574
+
575
+ # Simple Arithmetic and logical operations. Performs some conversion from
576
+ # StyleScript operations into their JavaScript equivalents.
577
+ class OpNode < Node
578
+ children :first, :second
579
+ attr_reader :operator
580
+ attr_accessor :second
581
+
582
+ CONVERSIONS = {
583
+ :== => "===",
584
+ :'!=' => "!==",
585
+ :and => '&&',
586
+ :or => '||',
587
+ :is => '===',
588
+ :isnt => "!==",
589
+ :not => '!'
590
+ }
591
+ CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
592
+ ASSIGNMENT = [:'||=', :'&&=', :'?=']
593
+ PREFIX_OPERATORS = [:typeof, :delete]
594
+
595
+ def initialize(operator, first, second=nil, flip=false)
596
+ @first, @second, @flip = first, second, flip
597
+ @operator = CONVERSIONS[operator.to_sym] || operator
598
+ end
599
+
600
+ def unary?
601
+ @second.nil?
602
+ end
603
+
604
+ def chainable?
605
+ CHAINABLE.include?(operator.to_sym)
606
+ end
607
+
608
+ def compile_node(o)
609
+ return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
610
+ return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
611
+ return write(compile_unary(o)) if unary?
612
+ return write(compile_existence(o)) if @operator == '?'
613
+ write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
614
+ end
615
+
616
+ # Mimic Python's chained comparisons. See:
617
+ # http://docs.python.org/reference/expressions.html#notin
618
+ def compile_chain(o)
619
+ shared = @first.unwrap.second
620
+ @first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
621
+ "(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
622
+ end
623
+
624
+ def compile_assignment(o)
625
+ first, second = @first.compile(o), @second.compile(o)
626
+ o[:scope].find(first) if @first.unwrap.is_a?(Value)
627
+ sym = @operator[0..1]
628
+ return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
629
+ "#{first} = #{first} #{sym} #{second}"
630
+ end
631
+
632
+ def compile_existence(o)
633
+ first, second = @first.compile(o), @second.compile(o)
634
+ "#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
635
+ end
636
+
637
+ def compile_unary(o)
638
+ space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
639
+ parts = [@operator.to_s, space, @first.compile(o)]
640
+ parts.reverse! if @flip
641
+ parts.join('')
642
+ end
643
+ end
644
+
645
+ # A function definition. The only node that creates a new Scope.
646
+ # A CodeNode does not have any children -- they're within the new scope.
647
+ class CodeNode < Node
648
+ top_sensitive
649
+ attr_reader :params, :body, :bound
650
+ attr_accessor :name, :proto
651
+
652
+ # Constructor functions start with an uppercase letter, by convention.
653
+ UPPERCASE = /[A-Z]/
654
+
655
+ def initialize(params, body, tag=nil)
656
+ @params = params
657
+ @body = body
658
+ @bound = tag == :boundfunc
659
+ end
660
+
661
+ def constructor?
662
+ @name && @name[0..0][UPPERCASE]
663
+ end
664
+
665
+ def compile_node(o)
666
+ shared_scope = o.delete(:shared_scope)
667
+ top = o.delete(:top)
668
+ o[:scope] = shared_scope || Scope.new(o[:scope], @body, self)
669
+ o[:return] = true
670
+ o[:top] = true
671
+ o[:indent] = idt(@bound ? 2 : 1)
672
+ o.delete(:no_wrap)
673
+ o.delete(:globals)
674
+ if @params.last.is_a?(SplatNode)
675
+ splat = @params.pop
676
+ splat.index = @params.length
677
+ @body.unshift(splat)
678
+ end
679
+ @params.each {|id| o[:scope].parameter(id.to_s) }
680
+ code = @body.empty? ? "" : "\n#{@body.compile_with_declarations(o)}\n"
681
+ name_part = @name ? " #{@name}" : ''
682
+ func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt(@bound ? 1 : 0)}}"
683
+ func = "(#{func})" if top && !@bound
684
+ return write(func) unless @bound
685
+ inner = "// Generated by StyleScript v1.0.0\n(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
686
+ write("// Generated by StyleScript v1.0.0\n(function(__this) {\n#{idt(1)}var __func = #{func};\n#{idt(1)}return #{inner}\n#{idt}})(this)")
687
+ end
688
+ end
689
+
690
+ # A splat, either as a parameter to a function, an argument to a call,
691
+ # or in a destructuring assignment.
692
+ class SplatNode < Node
693
+ children :name
694
+ attr_accessor :index
695
+
696
+ def initialize(name)
697
+ @name = name
698
+ end
699
+
700
+ def compile_node(o={})
701
+ write(@index ? compile_param(o) : @name.compile(o))
702
+ end
703
+
704
+ def compile_param(o)
705
+ o[:scope].find(@name)
706
+ "#{@name} = Array.prototype.slice.call(arguments, #{@index})"
707
+ end
708
+
709
+ def compile_value(o, name, index)
710
+ "Array.prototype.slice.call(#{name}, #{index})"
711
+ end
712
+
713
+ end
714
+
715
+ # An object literal.
716
+ class ObjectNode < Node
717
+ children :properties
718
+ alias_method :objects, :properties
719
+
720
+ def initialize(properties = [])
721
+ @properties = properties
722
+ end
723
+
724
+ # All the mucking about with commas is to make sure that CommentNodes and
725
+ # AssignNodes get interleaved correctly, with no trailing commas or
726
+ # commas affixed to comments. TODO: Extract this and add it to ArrayNode.
727
+ def compile_node(o)
728
+ o[:indent] = idt(1)
729
+ joins = Hash.new("\n")
730
+ non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
731
+ non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
732
+ props = @properties.map { |prop|
733
+ join = joins[prop]
734
+ join = '' if prop == @properties.last
735
+ indent = prop.is_a?(CommentNode) ? '' : idt(1)
736
+ "#{indent}#{prop.compile(o)}#{join}"
737
+ }.join('')
738
+ write("{\n#{props}\n#{idt}}")
739
+ end
740
+ end
741
+
742
+ # An array literal.
743
+ class ArrayNode < Node
744
+ children :objects
745
+
746
+ def initialize(objects=[])
747
+ @objects = objects
748
+ end
749
+
750
+ def compile_node(o)
751
+ o[:indent] = idt(1)
752
+ objects = @objects.map { |obj|
753
+ code = obj.compile(o)
754
+ obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
755
+ obj == @objects.last ? code : "#{code}, "
756
+ }.join('')
757
+ ending = objects.include?("\n") ? "\n#{idt}]" : ']'
758
+ write("[#{objects}#{ending}")
759
+ end
760
+ end
761
+
762
+ # A faux-node that is never created by the grammar, but is used during
763
+ # code generation to generate a quick "array.push(value)" tree of nodes.
764
+ class PushNode
765
+ def self.wrap(array, expressions)
766
+ expr = expressions.unwrap
767
+ return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
768
+ Expressions.wrap(CallNode.new(
769
+ ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
770
+ [expr]
771
+ ))
772
+ end
773
+ end
774
+
775
+ # A faux-node used to wrap an expressions body in a closure.
776
+ class ClosureNode
777
+ def self.wrap(expressions, statement=false)
778
+ func = ParentheticalNode.new(CodeNode.new([], Expressions.wrap(expressions)))
779
+ call = CallNode.new(ValueNode.new(func, AccessorNode.new(Value.new('call'))), [Value.new('this')])
780
+ statement ? Expressions.wrap(call) : call
781
+ end
782
+ end
783
+
784
+ # A while loop, the only sort of low-level loop exposed by StyleScript. From
785
+ # it, all other loops can be manufactured.
786
+ class WhileNode < Node
787
+ top_sensitive
788
+ children :condition, :body
789
+ statement
790
+
791
+ def initialize(condition, body)
792
+ @condition, @body = condition, body
793
+ end
794
+
795
+ def compile_node(o)
796
+ returns = o.delete(:return)
797
+ top = o.delete(:top) && !returns
798
+ o[:indent] = idt(1)
799
+ o[:top] = true
800
+ cond = @condition.compile(o)
801
+ set = ''
802
+ if !top
803
+ rvar = o[:scope].free_variable
804
+ set = "#{idt}#{rvar} = [];\n"
805
+ @body = PushNode.wrap(rvar, @body)
806
+ end
807
+ post = returns ? "\n#{idt}return #{rvar};" : ''
808
+ return write("#{set}#{idt}while (#{cond}) null;#{post}") if @body.nil?
809
+ write("#{set}#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
810
+ end
811
+ end
812
+
813
+ # A until loop, the only sort of low-level loop exposed by StyleScript. From
814
+ # it, all other loops can be manufactured.
815
+ class UntilNode < Node
816
+ top_sensitive
817
+ children :condition, :body
818
+ statement
819
+
820
+ def initialize(condition, body)
821
+ @condition, @body = condition, body
822
+ end
823
+
824
+ def compile_node(o)
825
+ returns = o.delete(:return)
826
+ top = o.delete(:top) && !returns
827
+ o[:indent] = idt(1)
828
+ o[:top] = true
829
+ cond = @condition.compile(o)
830
+ set = ''
831
+ if !top
832
+ rvar = o[:scope].free_variable
833
+ set = "#{idt}#{rvar} = [];\n"
834
+ @body = PushNode.wrap(rvar, @body)
835
+ end
836
+ post = returns ? "\n#{idt}return #{rvar};" : ''
837
+ return write("#{set}#{idt}while (!(#{cond})) null;#{post}") if @body.nil?
838
+ write("#{set}#{idt}while (!(#{cond})) {\n#{@body.compile(o)}\n#{idt}}#{post}")
839
+ end
840
+ end
841
+
842
+ # The replacement for the for loop is an array comprehension (that compiles)
843
+ # into a for loop. Also acts as an expression, able to return the result
844
+ # of the comprehenion. Unlike Python array comprehensions, it's able to pass
845
+ # the current index of the loop as a second parameter.
846
+ class ForNode < Node
847
+ top_sensitive
848
+ children :body, :source, :filter
849
+ attr_reader :name, :index, :step
850
+ statement
851
+
852
+ def initialize(body, source, name, index=nil)
853
+ @body, @name, @index = body, name, index
854
+ @source = source[:source]
855
+ @filter = source[:filter]
856
+ @step = source[:step]
857
+ @object = !!source[:object]
858
+ @name, @index = @index, @name if @object
859
+ end
860
+
861
+ def compile_node(o)
862
+ top_level = o.delete(:top) && !o[:return]
863
+ range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
864
+ source = range ? @source.base : @source
865
+ scope = o[:scope]
866
+ name_found = @name && scope.find(@name)
867
+ index_found = @index && scope.find(@index)
868
+ body_dent = idt(1)
869
+ rvar = scope.free_variable unless top_level
870
+ svar = scope.free_variable
871
+ ivar = range ? name : @index ? @index : scope.free_variable
872
+ var_part = ''
873
+ body = Expressions.wrap(@body)
874
+ if range
875
+ index_var = scope.free_variable
876
+ source_part = source.compile_variables(o)
877
+ for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
878
+ else
879
+ index_var = nil
880
+ source_part = "#{svar} = #{@source.compile(o)};\n#{idt}"
881
+ step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
882
+ for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
883
+ var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
884
+ # body.unshift(AssignNode.new(@name, ValueNode.new(svar, [IndexNode.new(ivar)]))) if @name
885
+ end
886
+ set_result = rvar ? "#{idt}#{rvar} = []; " : idt
887
+ return_result = rvar || ''
888
+ body = ClosureNode.wrap(body, true) if top_level && contains? {|n| n.is_a? CodeNode }
889
+ body = PushNode.wrap(rvar, body) unless top_level
890
+ if o[:return]
891
+ return_result = "return #{return_result}" if o[:return]
892
+ o.delete(:return)
893
+ body = IfNode.new(@filter, body, nil, :statement => true) if @filter
894
+ elsif @filter
895
+ body = Expressions.wrap(IfNode.new(@filter, body))
896
+ end
897
+ if @object
898
+ o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
899
+ body = Expressions.wrap(IfNode.new(
900
+ CallNode.new(
901
+ ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
902
+ [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
903
+ ),
904
+ Expressions.wrap(body), nil, {:statement => true}
905
+ ))
906
+ end
907
+
908
+ return_result = "\n#{idt}#{return_result};" unless top_level
909
+ body = body.compile(o.merge(:indent => body_dent, :top => true))
910
+ vars = range ? @name : "#{@name}, #{ivar}"
911
+ return write(set_result + source_part + "for (#{for_part}) {\n#{var_part}#{body}\n#{idt}}\n#{idt}#{return_result}")
912
+ end
913
+ end
914
+
915
+ # A try/catch/finally block.
916
+ class TryNode < Node
917
+ children :try, :recovery, :finally
918
+ attr_reader :error
919
+ statement
920
+
921
+ def initialize(try, error, recovery, finally=nil)
922
+ @try, @error, @recovery, @finally = try, error, recovery, finally
923
+ end
924
+
925
+ def compile_node(o)
926
+ o[:indent] = idt(1)
927
+ o[:top] = true
928
+ error_part = @error ? " (#{@error}) " : ' '
929
+ catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}"
930
+ finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}"
931
+ write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}")
932
+ end
933
+ end
934
+
935
+ # Throw an exception.
936
+ class ThrowNode < Node
937
+ children :expression
938
+ statement_only
939
+
940
+ def initialize(expression)
941
+ @expression = expression
942
+ end
943
+
944
+ def compile_node(o)
945
+ write("#{idt}throw #{@expression.compile(o)};")
946
+ end
947
+ end
948
+
949
+ # Check an expression for existence (meaning not null or undefined).
950
+ class ExistenceNode < Node
951
+ children :expression
952
+
953
+ def self.compile_test(o, variable)
954
+ first, second = variable, variable
955
+ first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
956
+ "(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
957
+ end
958
+
959
+ def initialize(expression)
960
+ @expression = expression
961
+ end
962
+
963
+ def compile_node(o)
964
+ write(ExistenceNode.compile_test(o, @expression))
965
+ end
966
+ end
967
+
968
+ # An extra set of parentheses, supplied by the script source.
969
+ # You can't wrap parentheses around bits that get compiled into JS statements,
970
+ # unfortunately.
971
+ class ParentheticalNode < Node
972
+ children :expressions
973
+
974
+ def initialize(expressions, line=nil)
975
+ @expressions = expressions.unwrap
976
+ @line = line
977
+ end
978
+
979
+ def compile_node(o)
980
+ compiled = @expressions.compile(o)
981
+ compiled = compiled[0...-1] if compiled[-1..-1] == ';'
982
+ write("(#{compiled})")
983
+ end
984
+ end
985
+
986
+ # If/else statements. Switch/whens get compiled into these. Acts as an
987
+ # expression by pushing down requested returns to the expression bodies.
988
+ # Single-expression IfNodes are compiled into ternary operators if possible,
989
+ # because ternaries are first-class returnable assignable expressions.
990
+ class IfNode < Node
991
+ children :condition, :body, :else_body
992
+
993
+ def initialize(condition, body, else_body=nil, tags={})
994
+ @condition = condition
995
+ @body = body && body.unwrap
996
+ @else_body = else_body && else_body.unwrap
997
+ @tags = tags
998
+ @multiple = true if @condition.is_a?(Array)
999
+ @condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
1000
+ end
1001
+
1002
+ def <<(else_body)
1003
+ eb = else_body.unwrap
1004
+ @else_body ? @else_body << eb : @else_body = eb
1005
+ self
1006
+ end
1007
+
1008
+ def add_comment(comment)
1009
+ @comment = comment
1010
+ self
1011
+ end
1012
+
1013
+ def force_statement
1014
+ @tags[:statement] = true
1015
+ self
1016
+ end
1017
+
1018
+ # Rewrite a chain of IfNodes with their switch condition for equality.
1019
+ def rewrite_condition(expression)
1020
+ @condition = @multiple ? @condition.map {|c| OpNode.new("is", expression, c) } :
1021
+ OpNode.new("is", expression, @condition)
1022
+ @else_body.rewrite_condition(expression) if chain?
1023
+ self
1024
+ end
1025
+
1026
+ # Rewrite a chain of IfNodes to add a default case as the final else.
1027
+ def add_else(exprs)
1028
+ chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap)
1029
+ self
1030
+ end
1031
+
1032
+ # If the else_body is an IfNode itself, then we've got an if-else chain.
1033
+ def chain?
1034
+ @chain ||= @else_body && @else_body.is_a?(IfNode)
1035
+ end
1036
+
1037
+ # The IfNode only compiles into a statement if either of the bodies needs
1038
+ # to be a statement.
1039
+ def statement?
1040
+ @is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
1041
+ end
1042
+
1043
+ def compile_condition(o)
1044
+ [@condition].flatten.map {|c| c.compile(o) }.join(' || ')
1045
+ end
1046
+
1047
+ def compile_node(o)
1048
+ write(statement? ? compile_statement(o) : compile_ternary(o))
1049
+ end
1050
+
1051
+ # Compile the IfNode as a regular if-else statement. Flattened chains
1052
+ # force sub-else bodies into statement form.
1053
+ def compile_statement(o)
1054
+ child = o.delete(:chain_child)
1055
+ cond_o = o.dup
1056
+ cond_o.delete(:return)
1057
+ o[:indent] = idt(1)
1058
+ o[:top] = true
1059
+ if_dent = child ? '' : idt
1060
+ com_dent = child ? idt : ''
1061
+ prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
1062
+ body = Expressions.wrap(@body).compile(o)
1063
+ if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
1064
+ return if_part unless @else_body
1065
+ else_part = chain? ?
1066
+ " else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :
1067
+ " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{idt}}"
1068
+ if_part + else_part
1069
+ end
1070
+
1071
+ # Compile the IfNode into a ternary operator.
1072
+ def compile_ternary(o)
1073
+ if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}"
1074
+ else_part = @else_body ? "#{@else_body.compile(o)}" : 'null'
1075
+ "#{if_part} : #{else_part}"
1076
+ end
1077
+ end
1078
+
1079
+ end