coffee-script 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.
- data/LICENSE +22 -0
- data/README +38 -0
- data/bin/coffee-script +5 -0
- data/coffee-script.gemspec +21 -0
- data/examples/code.cs +173 -0
- data/examples/documents.cs +72 -0
- data/examples/poignant.cs +153 -0
- data/examples/syntax_errors.cs +20 -0
- data/examples/underscore.cs +597 -0
- data/lib/coffee-script.rb +20 -0
- data/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences +24 -0
- data/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +329 -0
- data/lib/coffee_script/CoffeeScript.tmbundle/info.plist +10 -0
- data/lib/coffee_script/command_line.rb +183 -0
- data/lib/coffee_script/grammar.y +403 -0
- data/lib/coffee_script/lexer.rb +187 -0
- data/lib/coffee_script/nodes.rb +680 -0
- data/lib/coffee_script/parse_error.rb +22 -0
- data/lib/coffee_script/parser.rb +1987 -0
- data/lib/coffee_script/scope.rb +45 -0
- data/lib/coffee_script/value.rb +42 -0
- metadata +75 -0
@@ -0,0 +1,680 @@
|
|
1
|
+
module CoffeeScript
|
2
|
+
|
3
|
+
# The abstract base class for all CoffeeScript nodes.
|
4
|
+
class Node
|
5
|
+
# Tabs are two spaces for pretty-printing.
|
6
|
+
TAB = ' '
|
7
|
+
|
8
|
+
# Tag this node as a statement, meaning that it can't be used directly as
|
9
|
+
# the result of an expression.
|
10
|
+
def self.statement
|
11
|
+
class_eval "def statement?; true; end"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Tag this node as having a custom return, meaning that instead of returning
|
15
|
+
# it from the outside, you ask it to return itself, and it obliges.
|
16
|
+
def self.custom_return
|
17
|
+
class_eval "def custom_return?; true; end"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Tag this node as having a custom assignment, meaning that instead of
|
21
|
+
# assigning it to a variable name from the outside, you pass it the variable
|
22
|
+
# name and let it take care of it.
|
23
|
+
def self.custom_assign
|
24
|
+
class_eval "def custom_assign?; true; end"
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(code)
|
28
|
+
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
|
29
|
+
code
|
30
|
+
end
|
31
|
+
|
32
|
+
def compile(o={})
|
33
|
+
@options = o.dup
|
34
|
+
end
|
35
|
+
|
36
|
+
# Default implementations of the common node methods.
|
37
|
+
def unwrap; self; end
|
38
|
+
def line_ending; ';'; end
|
39
|
+
def statement?; false; end
|
40
|
+
def custom_return?; false; end
|
41
|
+
def custom_assign?; false; end
|
42
|
+
end
|
43
|
+
|
44
|
+
# A collection of nodes, each one representing an expression.
|
45
|
+
class Expressions < Node
|
46
|
+
statement
|
47
|
+
attr_reader :expressions
|
48
|
+
|
49
|
+
STRIP_TRAILING_WHITESPACE = /\s+$/
|
50
|
+
|
51
|
+
# Wrap up a node as an Expressions, unless it already is.
|
52
|
+
def self.wrap(node)
|
53
|
+
node.is_a?(Expressions) ? node : Expressions.new([node])
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(nodes)
|
57
|
+
@expressions = nodes
|
58
|
+
end
|
59
|
+
|
60
|
+
# Tack an expression onto the end of this node.
|
61
|
+
def <<(node)
|
62
|
+
@expressions << node
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# If this Expressions consists of a single node, pull it back out.
|
67
|
+
def unwrap
|
68
|
+
@expressions.length == 1 ? @expressions.first : self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Is the node last in this block of expressions.
|
72
|
+
def last?(node)
|
73
|
+
@last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
|
74
|
+
node == @expressions[@last_index]
|
75
|
+
end
|
76
|
+
|
77
|
+
# If this is the top-level Expressions, wrap everything in a safety closure.
|
78
|
+
def root_compile
|
79
|
+
code = compile(:indent => TAB, :scope => Scope.new)
|
80
|
+
code.gsub!(STRIP_TRAILING_WHITESPACE, '')
|
81
|
+
"(function(){\n#{code}\n})();"
|
82
|
+
end
|
83
|
+
|
84
|
+
# The extra fancy is to handle pushing down returns and assignments
|
85
|
+
# recursively to the final lines of inner statements.
|
86
|
+
def compile(options={})
|
87
|
+
return root_compile unless options[:scope]
|
88
|
+
code = @expressions.map { |node|
|
89
|
+
o = super(options)
|
90
|
+
if last?(node) && (o[:return] || o[:assign])
|
91
|
+
if o[:return]
|
92
|
+
if node.statement? || node.custom_return?
|
93
|
+
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
|
94
|
+
else
|
95
|
+
"#{o[:indent]}return #{node.compile(o)}#{node.line_ending}"
|
96
|
+
end
|
97
|
+
elsif o[:assign]
|
98
|
+
if node.statement? || node.custom_assign?
|
99
|
+
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
|
100
|
+
else
|
101
|
+
"#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
else
|
105
|
+
o.delete(:return) and o.delete(:assign)
|
106
|
+
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
|
107
|
+
end
|
108
|
+
}.join("\n")
|
109
|
+
write(code)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Literals are static values that have a Ruby representation, eg.: a string, a number,
|
114
|
+
# true, false, nil, etc.
|
115
|
+
class LiteralNode < Node
|
116
|
+
STATEMENTS = ['break', 'continue']
|
117
|
+
|
118
|
+
attr_reader :value
|
119
|
+
|
120
|
+
def initialize(value)
|
121
|
+
@value = value
|
122
|
+
end
|
123
|
+
|
124
|
+
def statement?
|
125
|
+
STATEMENTS.include?(@value.to_s)
|
126
|
+
end
|
127
|
+
|
128
|
+
def line_ending
|
129
|
+
@value.to_s[-1..-1] == ';' ? '' : ';'
|
130
|
+
end
|
131
|
+
|
132
|
+
def compile(o={})
|
133
|
+
o = super(o)
|
134
|
+
write(@value.to_s)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Try to return your expression, or tell it to return itself.
|
139
|
+
class ReturnNode < Node
|
140
|
+
statement
|
141
|
+
custom_return
|
142
|
+
|
143
|
+
attr_reader :expression
|
144
|
+
|
145
|
+
def initialize(expression)
|
146
|
+
@expression = expression
|
147
|
+
end
|
148
|
+
|
149
|
+
def line_ending
|
150
|
+
@expression.custom_return? ? '' : ';'
|
151
|
+
end
|
152
|
+
|
153
|
+
def compile(o={})
|
154
|
+
o = super(o)
|
155
|
+
return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return?
|
156
|
+
compiled = @expression.compile(o)
|
157
|
+
write(@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Pass through CoffeeScript comments into JavaScript comments at the
|
162
|
+
# same position.
|
163
|
+
class CommentNode < Node
|
164
|
+
statement
|
165
|
+
|
166
|
+
def initialize(lines)
|
167
|
+
@lines = lines.value
|
168
|
+
end
|
169
|
+
|
170
|
+
def line_ending
|
171
|
+
''
|
172
|
+
end
|
173
|
+
|
174
|
+
def compile(o={})
|
175
|
+
delimiter = "\n#{o[:indent]}//"
|
176
|
+
comment = "#{delimiter}#{@lines.join(delimiter)}"
|
177
|
+
write(comment)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
# Node for a function invocation. Takes care of converting super() calls into
|
183
|
+
# calls against the prototype's function of the same name.
|
184
|
+
class CallNode < Node
|
185
|
+
LEADING_DOT = /\A\./
|
186
|
+
|
187
|
+
attr_reader :variable, :arguments
|
188
|
+
|
189
|
+
def initialize(variable, arguments=[])
|
190
|
+
@variable, @arguments = variable, arguments
|
191
|
+
end
|
192
|
+
|
193
|
+
def new_instance
|
194
|
+
@new = true
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
def super?
|
199
|
+
@variable == :super
|
200
|
+
end
|
201
|
+
|
202
|
+
def compile(o={})
|
203
|
+
o = super(o)
|
204
|
+
args = @arguments.map{|a| a.compile(o.merge(:no_paren => true)) }.join(', ')
|
205
|
+
return write(compile_super(args, o)) if super?
|
206
|
+
prefix = @new ? "new " : ''
|
207
|
+
write("#{prefix}#{@variable.compile(o)}(#{args})")
|
208
|
+
end
|
209
|
+
|
210
|
+
def compile_super(args, o)
|
211
|
+
methname = o[:last_assign].sub(LEADING_DOT, '')
|
212
|
+
"this.constructor.prototype.#{methname}.call(this, #{args})"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# A value, indexed or dotted into, or vanilla.
|
217
|
+
class ValueNode < Node
|
218
|
+
attr_reader :literal, :properties, :last
|
219
|
+
|
220
|
+
def initialize(literal, properties=[])
|
221
|
+
@literal, @properties = literal, properties
|
222
|
+
end
|
223
|
+
|
224
|
+
def <<(other)
|
225
|
+
@properties << other
|
226
|
+
self
|
227
|
+
end
|
228
|
+
|
229
|
+
def properties?
|
230
|
+
return !@properties.empty?
|
231
|
+
end
|
232
|
+
|
233
|
+
def statement?
|
234
|
+
@literal.is_a?(Node) && @literal.statement? && !properties?
|
235
|
+
end
|
236
|
+
|
237
|
+
def custom_assign?
|
238
|
+
@literal.is_a?(Node) && @literal.custom_assign? && !properties?
|
239
|
+
end
|
240
|
+
|
241
|
+
def custom_return?
|
242
|
+
@literal.is_a?(Node) && @literal.custom_return? && !properties?
|
243
|
+
end
|
244
|
+
|
245
|
+
def compile(o={})
|
246
|
+
o = super(o)
|
247
|
+
parts = [@literal, @properties].flatten.map do |val|
|
248
|
+
val.respond_to?(:compile) ? val.compile(o) : val.to_s
|
249
|
+
end
|
250
|
+
@last = parts.last
|
251
|
+
write(parts.join(''))
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# A dotted accessor into a part of a value.
|
256
|
+
class AccessorNode < Node
|
257
|
+
attr_reader :name
|
258
|
+
|
259
|
+
def initialize(name)
|
260
|
+
@name = name
|
261
|
+
end
|
262
|
+
|
263
|
+
def compile(o={})
|
264
|
+
o = super(o)
|
265
|
+
write(".#{@name}")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# An indexed accessor into a part of an array or object.
|
270
|
+
class IndexNode < Node
|
271
|
+
attr_reader :index
|
272
|
+
|
273
|
+
def initialize(index)
|
274
|
+
@index = index
|
275
|
+
end
|
276
|
+
|
277
|
+
def compile(o={})
|
278
|
+
o = super(o)
|
279
|
+
write("[#{@index.compile(o)}]")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# An array slice literal. Unlike JavaScript's Array#slice, the second parameter
|
284
|
+
# specifies the index of the end of the slice (just like the first parameter)
|
285
|
+
# is the index of the beginning.
|
286
|
+
class SliceNode < Node
|
287
|
+
attr_reader :from, :to
|
288
|
+
|
289
|
+
def initialize(from, to)
|
290
|
+
@from, @to = from, to
|
291
|
+
end
|
292
|
+
|
293
|
+
def compile(o={})
|
294
|
+
o = super(o)
|
295
|
+
write(".slice(#{@from.compile(o)}, #{@to.compile(o)} + 1)")
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Setting the value of a local variable, or the value of an object property.
|
300
|
+
class AssignNode < Node
|
301
|
+
LEADING_VAR = /\Avar\s+/
|
302
|
+
|
303
|
+
statement
|
304
|
+
custom_return
|
305
|
+
|
306
|
+
attr_reader :variable, :value, :context
|
307
|
+
|
308
|
+
def initialize(variable, value, context=nil)
|
309
|
+
@variable, @value, @context = variable, value, context
|
310
|
+
end
|
311
|
+
|
312
|
+
def line_ending
|
313
|
+
@value.custom_assign? ? '' : ';'
|
314
|
+
end
|
315
|
+
|
316
|
+
def compile(o={})
|
317
|
+
o = super(o)
|
318
|
+
name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable
|
319
|
+
last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s
|
320
|
+
o = o.merge(:assign => name, :last_assign => last)
|
321
|
+
postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : ''
|
322
|
+
return write("#{@variable}: #{@value.compile(o)}") if @context == :object
|
323
|
+
return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign?
|
324
|
+
defined = o[:scope].find(name)
|
325
|
+
def_part = defined || @variable.properties? ? "" : "var #{name};\n#{o[:indent]}"
|
326
|
+
return write(def_part + @value.compile(o)) if @value.custom_assign?
|
327
|
+
def_part = defined ? name : "var #{name}"
|
328
|
+
val_part = @value.compile(o).sub(LEADING_VAR, '')
|
329
|
+
write("#{def_part} = #{val_part}#{postfix}")
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Simple Arithmetic and logical operations. Performs some conversion from
|
334
|
+
# CoffeeScript operations into their JavaScript equivalents.
|
335
|
+
class OpNode < Node
|
336
|
+
CONVERSIONS = {
|
337
|
+
"==" => "===",
|
338
|
+
"!=" => "!==",
|
339
|
+
'and' => '&&',
|
340
|
+
'or' => '||',
|
341
|
+
'is' => '===',
|
342
|
+
"aint" => "!==",
|
343
|
+
'not' => '!',
|
344
|
+
}
|
345
|
+
CONDITIONALS = ['||:', '&&:']
|
346
|
+
|
347
|
+
attr_reader :operator, :first, :second
|
348
|
+
|
349
|
+
def initialize(operator, first, second=nil, flip=false)
|
350
|
+
@first, @second, @flip = first, second, flip
|
351
|
+
@operator = CONVERSIONS[operator] || operator
|
352
|
+
end
|
353
|
+
|
354
|
+
def unary?
|
355
|
+
@second.nil?
|
356
|
+
end
|
357
|
+
|
358
|
+
def compile(o={})
|
359
|
+
o = super(o)
|
360
|
+
return write(compile_conditional(o)) if CONDITIONALS.include?(@operator)
|
361
|
+
return write(compile_unary(o)) if unary?
|
362
|
+
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
|
363
|
+
end
|
364
|
+
|
365
|
+
def compile_conditional(o)
|
366
|
+
first, second = @first.compile(o), @second.compile(o)
|
367
|
+
sym = @operator[0..1]
|
368
|
+
"#{first} = #{first} #{sym} #{second}"
|
369
|
+
end
|
370
|
+
|
371
|
+
def compile_unary(o)
|
372
|
+
space = @operator.to_s == 'delete' ? ' ' : ''
|
373
|
+
parts = [@operator.to_s, space, @first.compile(o)]
|
374
|
+
parts.reverse! if @flip
|
375
|
+
parts.join('')
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# A function definition. The only node that creates a new Scope.
|
380
|
+
class CodeNode < Node
|
381
|
+
attr_reader :params, :body
|
382
|
+
|
383
|
+
def initialize(params, body)
|
384
|
+
@params = params
|
385
|
+
@body = body
|
386
|
+
end
|
387
|
+
|
388
|
+
def compile(o={})
|
389
|
+
o = super(o)
|
390
|
+
o[:scope] = Scope.new(o[:scope])
|
391
|
+
o[:return] = true
|
392
|
+
indent = o[:indent]
|
393
|
+
o[:indent] += TAB
|
394
|
+
o.delete(:assign)
|
395
|
+
@params.each {|id| o[:scope].find(id.to_s) }
|
396
|
+
code = @body.compile(o)
|
397
|
+
write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# An object literal.
|
402
|
+
class ObjectNode < Node
|
403
|
+
attr_reader :properties
|
404
|
+
|
405
|
+
def initialize(properties = [])
|
406
|
+
@properties = properties
|
407
|
+
end
|
408
|
+
|
409
|
+
def compile(o={})
|
410
|
+
o = super(o)
|
411
|
+
indent = o[:indent]
|
412
|
+
o[:indent] += TAB
|
413
|
+
props = @properties.map { |prop|
|
414
|
+
joiner = prop == @properties.last ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n"
|
415
|
+
o[:indent] + prop.compile(o) + joiner
|
416
|
+
}.join('')
|
417
|
+
write("{\n#{props}\n#{indent}}")
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
# An array literal.
|
422
|
+
class ArrayNode < Node
|
423
|
+
attr_reader :objects
|
424
|
+
|
425
|
+
def initialize(objects=[])
|
426
|
+
@objects = objects
|
427
|
+
end
|
428
|
+
|
429
|
+
def compile(o={})
|
430
|
+
o = super(o)
|
431
|
+
objects = @objects.map { |obj|
|
432
|
+
joiner = obj.is_a?(CommentNode) ? "\n#{o[:indent] + TAB}" : obj == @objects.last ? '' : ', '
|
433
|
+
obj.compile(o.merge(:indent => o[:indent] + TAB)) + joiner
|
434
|
+
}.join('')
|
435
|
+
ending = objects.include?("\n") ? "\n#{o[:indent]}]" : ']'
|
436
|
+
write("[#{objects}#{ending}")
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
441
|
+
# it, all other loops can be manufactured.
|
442
|
+
class WhileNode < Node
|
443
|
+
statement
|
444
|
+
|
445
|
+
attr_reader :condition, :body
|
446
|
+
|
447
|
+
def initialize(condition, body)
|
448
|
+
@condition, @body = condition, body
|
449
|
+
end
|
450
|
+
|
451
|
+
def line_ending
|
452
|
+
''
|
453
|
+
end
|
454
|
+
|
455
|
+
def compile(o={})
|
456
|
+
o = super(o)
|
457
|
+
indent = o[:indent] + TAB
|
458
|
+
cond = @condition.compile(o.merge(:no_paren => true))
|
459
|
+
write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}")
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
# The replacement for the for loop is an array comprehension (that compiles)
|
464
|
+
# into a for loop. Also acts as an expression, able to return the result
|
465
|
+
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
466
|
+
# the current index of the loop as a second parameter.
|
467
|
+
class ForNode < Node
|
468
|
+
statement
|
469
|
+
custom_return
|
470
|
+
custom_assign
|
471
|
+
|
472
|
+
attr_reader :body, :source, :name, :filter, :index
|
473
|
+
|
474
|
+
def initialize(body, source, name, filter, index=nil)
|
475
|
+
@body, @source, @name, @filter, @index = body, source, name, filter, index
|
476
|
+
end
|
477
|
+
|
478
|
+
def line_ending
|
479
|
+
''
|
480
|
+
end
|
481
|
+
|
482
|
+
def compile(o={})
|
483
|
+
o = super(o)
|
484
|
+
scope = o[:scope]
|
485
|
+
name_found = scope.find(@name)
|
486
|
+
index_found = @index && scope.find(@index)
|
487
|
+
svar = scope.free_variable
|
488
|
+
ivar = scope.free_variable
|
489
|
+
lvar = scope.free_variable
|
490
|
+
name_part = name_found ? @name : "var #{@name}"
|
491
|
+
index_name = @index ? (index_found ? @index : "var #{@index}") : nil
|
492
|
+
source_part = "var #{svar} = #{@source.compile(o)};"
|
493
|
+
for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
|
494
|
+
var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n"
|
495
|
+
index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : ''
|
496
|
+
|
497
|
+
set_result = ''
|
498
|
+
save_result = ''
|
499
|
+
return_result = ''
|
500
|
+
body = @body
|
501
|
+
suffix = ';'
|
502
|
+
if o[:return] || o[:assign]
|
503
|
+
rvar = scope.free_variable
|
504
|
+
set_result = "var #{rvar} = [];\n#{o[:indent]}"
|
505
|
+
save_result += "#{rvar}[#{ivar}] = "
|
506
|
+
return_result = rvar
|
507
|
+
return_result = "#{o[:assign]} = #{return_result};" if o[:assign]
|
508
|
+
return_result = "return #{return_result};" if o[:return]
|
509
|
+
return_result = "\n#{o[:indent]}#{return_result}"
|
510
|
+
if @filter
|
511
|
+
body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body])
|
512
|
+
body = IfNode.new(@filter, body, nil, :statement => true)
|
513
|
+
save_result = ''
|
514
|
+
suffix = ''
|
515
|
+
end
|
516
|
+
elsif @filter
|
517
|
+
body = IfNode.new(@filter, @body)
|
518
|
+
end
|
519
|
+
|
520
|
+
indent = o[:indent] + TAB
|
521
|
+
body = body.compile(o.merge(:indent => indent))
|
522
|
+
write("#{source_part}\n#{o[:indent]}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}")
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
# A try/catch/finally block.
|
527
|
+
class TryNode < Node
|
528
|
+
statement
|
529
|
+
custom_return
|
530
|
+
custom_assign
|
531
|
+
|
532
|
+
attr_reader :try, :error, :recovery, :finally
|
533
|
+
|
534
|
+
def initialize(try, error, recovery, finally=nil)
|
535
|
+
@try, @error, @recovery, @finally = try, error, recovery, finally
|
536
|
+
end
|
537
|
+
|
538
|
+
def line_ending
|
539
|
+
''
|
540
|
+
end
|
541
|
+
|
542
|
+
def compile(o={})
|
543
|
+
o = super(o)
|
544
|
+
indent = o[:indent]
|
545
|
+
o[:indent] += TAB
|
546
|
+
error_part = @error ? " (#{@error}) " : ' '
|
547
|
+
catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}"
|
548
|
+
finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:assign => nil, :return => nil))}\n#{indent}}"
|
549
|
+
write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}")
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
# Throw an exception.
|
554
|
+
class ThrowNode < Node
|
555
|
+
statement
|
556
|
+
|
557
|
+
attr_reader :expression
|
558
|
+
|
559
|
+
def initialize(expression)
|
560
|
+
@expression = expression
|
561
|
+
end
|
562
|
+
|
563
|
+
def compile(o={})
|
564
|
+
o = super(o)
|
565
|
+
write("throw #{@expression.compile(o)}")
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
# An extra set of parenthesis, supplied by the script source.
|
570
|
+
class ParentheticalNode < Node
|
571
|
+
attr_reader :expressions
|
572
|
+
|
573
|
+
def initialize(expressions)
|
574
|
+
@expressions = expressions.unwrap
|
575
|
+
end
|
576
|
+
|
577
|
+
def statement?
|
578
|
+
@expressions.statement?
|
579
|
+
end
|
580
|
+
|
581
|
+
def custom_assign?
|
582
|
+
@expressions.custom_assign?
|
583
|
+
end
|
584
|
+
|
585
|
+
def custom_return?
|
586
|
+
@expressions.custom_return?
|
587
|
+
end
|
588
|
+
|
589
|
+
def compile(o={})
|
590
|
+
o = super(o)
|
591
|
+
compiled = @expressions.compile(o)
|
592
|
+
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
|
593
|
+
write(o[:no_paren] || statement? ? compiled : "(#{compiled})")
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
# If/else statements. Switch/whens get compiled into these. Acts as an
|
598
|
+
# expression by pushing down requested returns to the expression bodies.
|
599
|
+
# Single-expression IfNodes are compiled into ternary operators if possible,
|
600
|
+
# because ternaries are first-class returnable assignable expressions.
|
601
|
+
class IfNode < Node
|
602
|
+
attr_reader :condition, :body, :else_body
|
603
|
+
|
604
|
+
def initialize(condition, body, else_body=nil, tags={})
|
605
|
+
@condition = condition
|
606
|
+
@body = body && body.unwrap
|
607
|
+
@else_body = else_body && else_body.unwrap
|
608
|
+
@tags = tags
|
609
|
+
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
|
610
|
+
end
|
611
|
+
|
612
|
+
def <<(else_body)
|
613
|
+
eb = else_body.unwrap
|
614
|
+
@else_body ? @else_body << eb : @else_body = eb
|
615
|
+
self
|
616
|
+
end
|
617
|
+
|
618
|
+
# Rewrite a chain of IfNodes with their switch condition for equality.
|
619
|
+
def rewrite_condition(expression)
|
620
|
+
@condition = OpNode.new("is", expression, @condition)
|
621
|
+
@else_body.rewrite_condition(expression) if chain?
|
622
|
+
self
|
623
|
+
end
|
624
|
+
|
625
|
+
# Rewrite a chain of IfNodes to add a default case as the final else.
|
626
|
+
def add_else(expressions)
|
627
|
+
chain? ? @else_body.add_else(expressions) : @else_body = expressions
|
628
|
+
self
|
629
|
+
end
|
630
|
+
|
631
|
+
# If the else_body is an IfNode itself, then we've got an if-else chain.
|
632
|
+
def chain?
|
633
|
+
@chain ||= @else_body && @else_body.is_a?(IfNode)
|
634
|
+
end
|
635
|
+
|
636
|
+
# The IfNode only compiles into a statement if either of the bodies needs
|
637
|
+
# to be a statement.
|
638
|
+
def statement?
|
639
|
+
@is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
|
640
|
+
end
|
641
|
+
|
642
|
+
def custom_return?
|
643
|
+
statement?
|
644
|
+
end
|
645
|
+
|
646
|
+
def custom_assign?
|
647
|
+
statement?
|
648
|
+
end
|
649
|
+
|
650
|
+
def line_ending
|
651
|
+
statement? ? '' : ';'
|
652
|
+
end
|
653
|
+
|
654
|
+
def compile(o={})
|
655
|
+
o = super(o)
|
656
|
+
write(statement? ? compile_statement(o) : compile_ternary(o))
|
657
|
+
end
|
658
|
+
|
659
|
+
# Compile the IfNode as a regular if-else statement. Flattened chains
|
660
|
+
# force sub-else bodies into statement form.
|
661
|
+
def compile_statement(o)
|
662
|
+
indent = o[:indent]
|
663
|
+
o[:indent] += TAB
|
664
|
+
if_part = "if (#{@condition.compile(o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}"
|
665
|
+
return if_part unless @else_body
|
666
|
+
else_part = chain? ?
|
667
|
+
" else #{@else_body.compile(o.merge(:indent => indent))}" :
|
668
|
+
" else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{indent}}"
|
669
|
+
if_part + else_part
|
670
|
+
end
|
671
|
+
|
672
|
+
# Compile the IfNode into a ternary operator.
|
673
|
+
def compile_ternary(o)
|
674
|
+
if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}"
|
675
|
+
else_part = @else_body ? "#{@else_body.compile(o)}" : 'null'
|
676
|
+
"#{if_part} : #{else_part}"
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
end
|