coffee-script 0.3.2 → 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.
- data/LICENSE +2 -2
- data/README.md +15 -0
- data/lib/coffee-script.rb +1 -21
- data/lib/coffee_script.rb +31 -0
- metadata +30 -46
- data/README +0 -41
- data/bin/coffee +0 -5
- data/coffee-script.gemspec +0 -27
- data/examples/blocks.coffee +0 -57
- data/examples/code.coffee +0 -173
- data/examples/poignant.coffee +0 -186
- data/examples/potion.coffee +0 -205
- data/examples/underscore.coffee +0 -603
- data/extras/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences +0 -24
- data/extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +0 -361
- data/extras/CoffeeScript.tmbundle/info.plist +0 -10
- data/extras/EXTRAS +0 -20
- data/extras/coffee.vim +0 -111
- data/lib/coffee_script/coffee-script.js +0 -50
- data/lib/coffee_script/command_line.rb +0 -235
- data/lib/coffee_script/grammar.y +0 -481
- data/lib/coffee_script/lexer.js +0 -363
- data/lib/coffee_script/lexer.rb +0 -272
- data/lib/coffee_script/narwhal/coffee-script.js +0 -96
- data/lib/coffee_script/nodes.js +0 -443
- data/lib/coffee_script/nodes.rb +0 -1050
- data/lib/coffee_script/parse_error.rb +0 -29
- data/lib/coffee_script/parser.js +0 -477
- data/lib/coffee_script/parser.rb +0 -2611
- data/lib/coffee_script/repl.js +0 -33
- data/lib/coffee_script/rewriter.js +0 -377
- data/lib/coffee_script/rewriter.rb +0 -289
- data/lib/coffee_script/runner.js +0 -11
- data/lib/coffee_script/scope.js +0 -73
- data/lib/coffee_script/scope.rb +0 -91
- data/lib/coffee_script/value.rb +0 -64
- data/package.json +0 -8
data/lib/coffee_script/nodes.rb
DELETED
@@ -1,1050 +0,0 @@
|
|
1
|
-
module CoffeeScript
|
2
|
-
|
3
|
-
# The abstract base class for all CoffeeScript 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 it's 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(options={})
|
139
|
-
write(@expressions.map {|n| compile_expression(n, options.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 : "(function(){\n#{code}\n})();")
|
150
|
-
end
|
151
|
-
|
152
|
-
# Compile the expressions body, with declarations of all inner variables
|
153
|
-
# at 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 expression list.
|
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 have a Ruby representation, eg.: a string, a number,
|
184
|
-
# true, false, nil, etc.
|
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(@expression.statement? ? "#{compiled}\n#{idt}return null;" : "#{idt}return #{compiled};")
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
# Pass through CoffeeScript comments into JavaScript comments at the
|
230
|
-
# same position.
|
231
|
-
class CommentNode < Node
|
232
|
-
statement
|
233
|
-
|
234
|
-
def initialize(lines)
|
235
|
-
@lines = lines.value
|
236
|
-
end
|
237
|
-
|
238
|
-
def compile_node(o={})
|
239
|
-
delimiter = "\n#{idt}//"
|
240
|
-
write("#{delimiter}#{@lines.join(delimiter)}")
|
241
|
-
end
|
242
|
-
|
243
|
-
end
|
244
|
-
|
245
|
-
# Node for a function invocation. Takes care of converting super() calls into
|
246
|
-
# calls against the prototype's function of the same name.
|
247
|
-
class CallNode < Node
|
248
|
-
children :variable, :arguments
|
249
|
-
|
250
|
-
def initialize(variable, arguments=[])
|
251
|
-
@variable, @arguments = variable, arguments
|
252
|
-
@prefix = ''
|
253
|
-
end
|
254
|
-
|
255
|
-
def new_instance
|
256
|
-
@prefix = "new "
|
257
|
-
self
|
258
|
-
end
|
259
|
-
|
260
|
-
def <<(argument)
|
261
|
-
@arguments << argument
|
262
|
-
self
|
263
|
-
end
|
264
|
-
|
265
|
-
# Compile a vanilla function call.
|
266
|
-
def compile_node(o)
|
267
|
-
return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
|
268
|
-
args = @arguments.map{|a| a.compile(o) }.join(', ')
|
269
|
-
return write(compile_super(args, o)) if @variable == 'super'
|
270
|
-
write("#{@prefix}#{@variable.compile(o)}(#{args})")
|
271
|
-
end
|
272
|
-
|
273
|
-
# Compile a call against the superclass's implementation of the current function.
|
274
|
-
def compile_super(args, o)
|
275
|
-
methname = o[:scope].function.name
|
276
|
-
arg_part = args.empty? ? '' : ", #{args}"
|
277
|
-
meth = o[:scope].function.proto ?
|
278
|
-
"#{o[:scope].function.proto}.__superClass__.#{methname}" :
|
279
|
-
"#{methname}.__superClass__.constructor"
|
280
|
-
"#{meth}.call(this#{arg_part})"
|
281
|
-
end
|
282
|
-
|
283
|
-
# Compile a function call being passed variable arguments.
|
284
|
-
def compile_splat(o)
|
285
|
-
meth = @variable.compile(o)
|
286
|
-
obj = @variable.source || 'this'
|
287
|
-
args = @arguments.map do |arg|
|
288
|
-
code = arg.compile(o)
|
289
|
-
code = arg.is_a?(SplatNode) ? code : "[#{code}]"
|
290
|
-
arg.equal?(@arguments.first) ? code : ".concat(#{code})"
|
291
|
-
end
|
292
|
-
"#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
|
293
|
-
end
|
294
|
-
|
295
|
-
# If the code generation wished to use the result of a function call
|
296
|
-
# in multiple places, ensure that the function is only ever called once.
|
297
|
-
def compile_reference(o)
|
298
|
-
reference = o[:scope].free_variable
|
299
|
-
call = ParentheticalNode.new(AssignNode.new(reference, self))
|
300
|
-
return call, reference
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
# Node to extend an object's prototype with an ancestor object.
|
305
|
-
# After goog.inherits from the Closure Library.
|
306
|
-
class ExtendsNode < Node
|
307
|
-
children :sub_object, :super_object
|
308
|
-
statement
|
309
|
-
|
310
|
-
def initialize(sub_object, super_object)
|
311
|
-
@sub_object, @super_object = sub_object, super_object
|
312
|
-
end
|
313
|
-
|
314
|
-
# Hooking one constructor into another's prototype chain.
|
315
|
-
def compile_node(o={})
|
316
|
-
constructor = o[:scope].free_variable
|
317
|
-
sub, sup = @sub_object.compile(o), @super_object.compile(o)
|
318
|
-
"#{idt}#{constructor} = function(){};\n#{idt}" +
|
319
|
-
"#{constructor}.prototype = #{sup}.prototype;\n#{idt}" +
|
320
|
-
"#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" +
|
321
|
-
"#{sub}.prototype = new #{constructor}();\n#{idt}" +
|
322
|
-
"#{sub}.prototype.constructor = #{sub};"
|
323
|
-
end
|
324
|
-
|
325
|
-
end
|
326
|
-
|
327
|
-
# A value, indexed or dotted into, or vanilla.
|
328
|
-
class ValueNode < Node
|
329
|
-
children :base, :properties
|
330
|
-
attr_reader :last, :source
|
331
|
-
|
332
|
-
# Soak up undefined properties and call attempts.
|
333
|
-
SOAK = " == undefined ? undefined : "
|
334
|
-
|
335
|
-
def initialize(base, properties=[])
|
336
|
-
@base, @properties = base, [properties].flatten
|
337
|
-
end
|
338
|
-
|
339
|
-
def <<(other)
|
340
|
-
@properties << other
|
341
|
-
self
|
342
|
-
end
|
343
|
-
|
344
|
-
def properties?
|
345
|
-
return !@properties.empty? || @base.is_a?(ThisNode)
|
346
|
-
end
|
347
|
-
|
348
|
-
def array?
|
349
|
-
@base.is_a?(ArrayNode) && !properties?
|
350
|
-
end
|
351
|
-
|
352
|
-
def object?
|
353
|
-
@base.is_a?(ObjectNode) && !properties?
|
354
|
-
end
|
355
|
-
|
356
|
-
def splice?
|
357
|
-
properties? && @properties.last.is_a?(SliceNode)
|
358
|
-
end
|
359
|
-
|
360
|
-
def arguments?
|
361
|
-
@base.to_s == 'arguments'
|
362
|
-
end
|
363
|
-
|
364
|
-
def unwrap
|
365
|
-
@properties.empty? ? @base : self
|
366
|
-
end
|
367
|
-
|
368
|
-
# Values are statements if their base is a statement.
|
369
|
-
def statement?
|
370
|
-
@base.is_a?(Node) && @base.statement? && !properties?
|
371
|
-
end
|
372
|
-
|
373
|
-
def compile_node(o)
|
374
|
-
soaked = false
|
375
|
-
only = o.delete(:only_first)
|
376
|
-
props = only ? @properties[0...-1] : @properties
|
377
|
-
baseline = @base.compile(o)
|
378
|
-
parts = [baseline.dup]
|
379
|
-
props.each do |prop|
|
380
|
-
if prop.is_a?(AccessorNode) && prop.soak
|
381
|
-
soaked = true
|
382
|
-
if @base.is_a?(CallNode) && prop == props.first
|
383
|
-
temp = o[:scope].free_variable
|
384
|
-
parts[-1] = "(#{temp} = #{baseline})#{SOAK}#{baseline = temp.to_s + prop.compile(o)}"
|
385
|
-
else
|
386
|
-
parts[-1] << "#{SOAK}#{baseline += prop.compile(o)}"
|
387
|
-
end
|
388
|
-
else
|
389
|
-
part = prop.compile(o)
|
390
|
-
baseline += part
|
391
|
-
parts << part
|
392
|
-
end
|
393
|
-
end
|
394
|
-
@last = parts.last
|
395
|
-
@source = parts.length > 1 ? parts[0...-1].join('') : nil
|
396
|
-
code = parts.join('').gsub(')())', '()))')
|
397
|
-
write(soaked ? "(#{code})" : code)
|
398
|
-
end
|
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
|
-
# CoffeeScript 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 = "(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
|
686
|
-
write("(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 CoffeeScript. 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
|
-
# The replacement for the for loop is an array comprehension (that compiles)
|
814
|
-
# into a for loop. Also acts as an expression, able to return the result
|
815
|
-
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
816
|
-
# the current index of the loop as a second parameter.
|
817
|
-
class ForNode < Node
|
818
|
-
top_sensitive
|
819
|
-
children :body, :source, :filter
|
820
|
-
attr_reader :name, :index, :step
|
821
|
-
statement
|
822
|
-
|
823
|
-
def initialize(body, source, name, index=nil)
|
824
|
-
@body, @name, @index = body, name, index
|
825
|
-
@source = source[:source]
|
826
|
-
@filter = source[:filter]
|
827
|
-
@step = source[:step]
|
828
|
-
@object = !!source[:object]
|
829
|
-
@name, @index = @index, @name if @object
|
830
|
-
end
|
831
|
-
|
832
|
-
def compile_node(o)
|
833
|
-
top_level = o.delete(:top) && !o[:return]
|
834
|
-
range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
|
835
|
-
source = range ? @source.base : @source
|
836
|
-
scope = o[:scope]
|
837
|
-
name_found = @name && scope.find(@name)
|
838
|
-
index_found = @index && scope.find(@index)
|
839
|
-
body_dent = idt(1)
|
840
|
-
rvar = scope.free_variable unless top_level
|
841
|
-
svar = scope.free_variable
|
842
|
-
ivar = range ? name : @index ? @index : scope.free_variable
|
843
|
-
var_part = ''
|
844
|
-
body = Expressions.wrap(@body)
|
845
|
-
if range
|
846
|
-
index_var = scope.free_variable
|
847
|
-
source_part = source.compile_variables(o)
|
848
|
-
for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
|
849
|
-
else
|
850
|
-
index_var = nil
|
851
|
-
source_part = "#{svar} = #{@source.compile(o)};\n#{idt}"
|
852
|
-
step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
|
853
|
-
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
|
854
|
-
var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
|
855
|
-
# body.unshift(AssignNode.new(@name, ValueNode.new(svar, [IndexNode.new(ivar)]))) if @name
|
856
|
-
end
|
857
|
-
set_result = rvar ? "#{idt}#{rvar} = []; " : idt
|
858
|
-
return_result = rvar || ''
|
859
|
-
body = ClosureNode.wrap(body, true) if top_level && contains? {|n| n.is_a? CodeNode }
|
860
|
-
body = PushNode.wrap(rvar, body) unless top_level
|
861
|
-
if o[:return]
|
862
|
-
return_result = "return #{return_result}" if o[:return]
|
863
|
-
o.delete(:return)
|
864
|
-
body = IfNode.new(@filter, body, nil, :statement => true) if @filter
|
865
|
-
elsif @filter
|
866
|
-
body = Expressions.wrap(IfNode.new(@filter, body))
|
867
|
-
end
|
868
|
-
if @object
|
869
|
-
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
|
870
|
-
body = Expressions.wrap(IfNode.new(
|
871
|
-
CallNode.new(
|
872
|
-
ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
|
873
|
-
[LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
|
874
|
-
),
|
875
|
-
Expressions.wrap(body), nil, {:statement => true}
|
876
|
-
))
|
877
|
-
end
|
878
|
-
|
879
|
-
return_result = "\n#{idt}#{return_result};" unless top_level
|
880
|
-
body = body.compile(o.merge(:indent => body_dent, :top => true))
|
881
|
-
vars = range ? @name : "#{@name}, #{ivar}"
|
882
|
-
return write(set_result + source_part + "for (#{for_part}) {\n#{var_part}#{body}\n#{idt}}\n#{idt}#{return_result}")
|
883
|
-
end
|
884
|
-
end
|
885
|
-
|
886
|
-
# A try/catch/finally block.
|
887
|
-
class TryNode < Node
|
888
|
-
children :try, :recovery, :finally
|
889
|
-
attr_reader :error
|
890
|
-
statement
|
891
|
-
|
892
|
-
def initialize(try, error, recovery, finally=nil)
|
893
|
-
@try, @error, @recovery, @finally = try, error, recovery, finally
|
894
|
-
end
|
895
|
-
|
896
|
-
def compile_node(o)
|
897
|
-
o[:indent] = idt(1)
|
898
|
-
o[:top] = true
|
899
|
-
error_part = @error ? " (#{@error}) " : ' '
|
900
|
-
catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}"
|
901
|
-
finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}"
|
902
|
-
write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}")
|
903
|
-
end
|
904
|
-
end
|
905
|
-
|
906
|
-
# Throw an exception.
|
907
|
-
class ThrowNode < Node
|
908
|
-
children :expression
|
909
|
-
statement_only
|
910
|
-
|
911
|
-
def initialize(expression)
|
912
|
-
@expression = expression
|
913
|
-
end
|
914
|
-
|
915
|
-
def compile_node(o)
|
916
|
-
write("#{idt}throw #{@expression.compile(o)};")
|
917
|
-
end
|
918
|
-
end
|
919
|
-
|
920
|
-
# Check an expression for existence (meaning not null or undefined).
|
921
|
-
class ExistenceNode < Node
|
922
|
-
children :expression
|
923
|
-
|
924
|
-
def self.compile_test(o, variable)
|
925
|
-
first, second = variable, variable
|
926
|
-
first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
|
927
|
-
"(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
|
928
|
-
end
|
929
|
-
|
930
|
-
def initialize(expression)
|
931
|
-
@expression = expression
|
932
|
-
end
|
933
|
-
|
934
|
-
def compile_node(o)
|
935
|
-
write(ExistenceNode.compile_test(o, @expression))
|
936
|
-
end
|
937
|
-
end
|
938
|
-
|
939
|
-
# An extra set of parentheses, supplied by the script source.
|
940
|
-
# You can't wrap parentheses around bits that get compiled into JS statements,
|
941
|
-
# unfortunately.
|
942
|
-
class ParentheticalNode < Node
|
943
|
-
children :expressions
|
944
|
-
|
945
|
-
def initialize(expressions, line=nil)
|
946
|
-
@expressions = expressions.unwrap
|
947
|
-
@line = line
|
948
|
-
end
|
949
|
-
|
950
|
-
def compile_node(o)
|
951
|
-
compiled = @expressions.compile(o)
|
952
|
-
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
|
953
|
-
write("(#{compiled})")
|
954
|
-
end
|
955
|
-
end
|
956
|
-
|
957
|
-
# If/else statements. Switch/whens get compiled into these. Acts as an
|
958
|
-
# expression by pushing down requested returns to the expression bodies.
|
959
|
-
# Single-expression IfNodes are compiled into ternary operators if possible,
|
960
|
-
# because ternaries are first-class returnable assignable expressions.
|
961
|
-
class IfNode < Node
|
962
|
-
children :condition, :body, :else_body
|
963
|
-
|
964
|
-
def initialize(condition, body, else_body=nil, tags={})
|
965
|
-
@condition = condition
|
966
|
-
@body = body && body.unwrap
|
967
|
-
@else_body = else_body && else_body.unwrap
|
968
|
-
@tags = tags
|
969
|
-
@multiple = true if @condition.is_a?(Array)
|
970
|
-
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
|
971
|
-
end
|
972
|
-
|
973
|
-
def <<(else_body)
|
974
|
-
eb = else_body.unwrap
|
975
|
-
@else_body ? @else_body << eb : @else_body = eb
|
976
|
-
self
|
977
|
-
end
|
978
|
-
|
979
|
-
def add_comment(comment)
|
980
|
-
@comment = comment
|
981
|
-
self
|
982
|
-
end
|
983
|
-
|
984
|
-
def force_statement
|
985
|
-
@tags[:statement] = true
|
986
|
-
self
|
987
|
-
end
|
988
|
-
|
989
|
-
# Rewrite a chain of IfNodes with their switch condition for equality.
|
990
|
-
def rewrite_condition(expression)
|
991
|
-
@condition = @multiple ? @condition.map {|c| OpNode.new("is", expression, c) } :
|
992
|
-
OpNode.new("is", expression, @condition)
|
993
|
-
@else_body.rewrite_condition(expression) if chain?
|
994
|
-
self
|
995
|
-
end
|
996
|
-
|
997
|
-
# Rewrite a chain of IfNodes to add a default case as the final else.
|
998
|
-
def add_else(exprs)
|
999
|
-
chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap)
|
1000
|
-
self
|
1001
|
-
end
|
1002
|
-
|
1003
|
-
# If the else_body is an IfNode itself, then we've got an if-else chain.
|
1004
|
-
def chain?
|
1005
|
-
@chain ||= @else_body && @else_body.is_a?(IfNode)
|
1006
|
-
end
|
1007
|
-
|
1008
|
-
# The IfNode only compiles into a statement if either of the bodies needs
|
1009
|
-
# to be a statement.
|
1010
|
-
def statement?
|
1011
|
-
@is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
|
1012
|
-
end
|
1013
|
-
|
1014
|
-
def compile_condition(o)
|
1015
|
-
[@condition].flatten.map {|c| c.compile(o) }.join(' || ')
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
def compile_node(o)
|
1019
|
-
write(statement? ? compile_statement(o) : compile_ternary(o))
|
1020
|
-
end
|
1021
|
-
|
1022
|
-
# Compile the IfNode as a regular if-else statement. Flattened chains
|
1023
|
-
# force sub-else bodies into statement form.
|
1024
|
-
def compile_statement(o)
|
1025
|
-
child = o.delete(:chain_child)
|
1026
|
-
cond_o = o.dup
|
1027
|
-
cond_o.delete(:return)
|
1028
|
-
o[:indent] = idt(1)
|
1029
|
-
o[:top] = true
|
1030
|
-
if_dent = child ? '' : idt
|
1031
|
-
com_dent = child ? idt : ''
|
1032
|
-
prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
|
1033
|
-
body = Expressions.wrap(@body).compile(o)
|
1034
|
-
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
|
1035
|
-
return if_part unless @else_body
|
1036
|
-
else_part = chain? ?
|
1037
|
-
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :
|
1038
|
-
" else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{idt}}"
|
1039
|
-
if_part + else_part
|
1040
|
-
end
|
1041
|
-
|
1042
|
-
# Compile the IfNode into a ternary operator.
|
1043
|
-
def compile_ternary(o)
|
1044
|
-
if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}"
|
1045
|
-
else_part = @else_body ? "#{@else_body.compile(o)}" : 'null'
|
1046
|
-
"#{if_part} : #{else_part}"
|
1047
|
-
end
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
end
|