coffee-script 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/coffee-script.gemspec +2 -2
- data/examples/documents.coffee +3 -3
- data/lib/coffee-script.rb +1 -1
- data/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +2 -2
- data/lib/coffee_script/grammar.y +20 -8
- data/lib/coffee_script/lexer.rb +19 -16
- data/lib/coffee_script/nodes.rb +94 -42
- data/lib/coffee_script/parse_error.rb +7 -1
- data/lib/coffee_script/parser.rb +1115 -1045
- data/lib/coffee_script/rewriter.rb +8 -1
- data/lib/coffee_script/scope.rb +25 -0
- data/lib/coffee_script/value.rb +4 -0
- data/package.json +1 -1
- metadata +2 -2
data/coffee-script.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'coffee-script'
|
3
|
-
s.version = '0.2.
|
4
|
-
s.date = '2010-1-
|
3
|
+
s.version = '0.2.5' # Keep version in sync with coffee-script.rb
|
4
|
+
s.date = '2010-1-13'
|
5
5
|
|
6
6
|
s.homepage = "http://jashkenas.github.com/coffee-script/"
|
7
7
|
s.summary = "The CoffeeScript Compiler"
|
data/examples/documents.coffee
CHANGED
@@ -15,8 +15,8 @@ dc.model.Document: dc.Model.extend({
|
|
15
15
|
# document by binding to Metadata, instead of on-the-fly.
|
16
16
|
metadata: =>
|
17
17
|
docId: this.id
|
18
|
-
_.select(Metadata.models(), (meta =>
|
19
|
-
_.any(meta.get('instances'), instance =>
|
18
|
+
_.select(Metadata.models(), (meta =>
|
19
|
+
_.any(meta.get('instances'), instance =>
|
20
20
|
instance.document_id is docId)))
|
21
21
|
|
22
22
|
bookmark: pageNumber =>
|
@@ -60,7 +60,7 @@ dc.model.DocumentSet: dc.model.RESTfulSet.extend({
|
|
60
60
|
# change their selected state.
|
61
61
|
_onModelEvent: e, model =>
|
62
62
|
this.base(e, model)
|
63
|
-
fire: e
|
63
|
+
fire: e is dc.Model.CHANGED and model.hasChanged('selected')
|
64
64
|
if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this))
|
65
65
|
|
66
66
|
})
|
data/lib/coffee-script.rb
CHANGED
@@ -10,7 +10,7 @@ require "coffee_script/parse_error"
|
|
10
10
|
# Namespace for all CoffeeScript internal classes.
|
11
11
|
module CoffeeScript
|
12
12
|
|
13
|
-
VERSION = '0.2.
|
13
|
+
VERSION = '0.2.5' # Keep in sync with the gemspec.
|
14
14
|
|
15
15
|
# Compile a script (String or IO) to JavaScript.
|
16
16
|
def self.compile(script, options={})
|
@@ -43,7 +43,7 @@
|
|
43
43
|
<key>comment</key>
|
44
44
|
<string>match stuff like: funcName: => … </string>
|
45
45
|
<key>match</key>
|
46
|
-
<string>([a-zA-Z0-9_?.$:*]
|
46
|
+
<string>([a-zA-Z0-9_?.$:*]*?)\s*(=\b|:\b)\s*([\w,\s]*?)\s*(=+>)</string>
|
47
47
|
<key>name</key>
|
48
48
|
<string>meta.function.coffee</string>
|
49
49
|
</dict>
|
@@ -64,7 +64,7 @@
|
|
64
64
|
<key>comment</key>
|
65
65
|
<string>match stuff like: a => … </string>
|
66
66
|
<key>match</key>
|
67
|
-
<string>([a-zA-Z0-9_?.,
|
67
|
+
<string>([a-zA-Z0-9_?., $*]*)\s*(=+>)</string>
|
68
68
|
<key>name</key>
|
69
69
|
<string>meta.inline.function.coffee</string>
|
70
70
|
</dict>
|
data/lib/coffee_script/grammar.y
CHANGED
@@ -39,7 +39,7 @@ prechigh
|
|
39
39
|
left EXTENDS
|
40
40
|
left ASSIGN '||=' '&&='
|
41
41
|
right RETURN
|
42
|
-
right '=>' UNLESS IF ELSE WHILE
|
42
|
+
right '=>' '==>' UNLESS IF ELSE WHILE
|
43
43
|
preclow
|
44
44
|
|
45
45
|
rule
|
@@ -198,8 +198,14 @@ rule
|
|
198
198
|
|
199
199
|
# Function definition.
|
200
200
|
Code:
|
201
|
-
ParamList
|
202
|
-
|
|
201
|
+
ParamList FuncGlyph Block { result = CodeNode.new(val[0], val[2], val[1]) }
|
202
|
+
| FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
|
203
|
+
;
|
204
|
+
|
205
|
+
# The symbols to signify functions, and bound functions.
|
206
|
+
FuncGlyph:
|
207
|
+
'=>' { result = :func }
|
208
|
+
| '==>' { result = :boundfunc }
|
203
209
|
;
|
204
210
|
|
205
211
|
# The parameters to a function definition.
|
@@ -211,12 +217,12 @@ rule
|
|
211
217
|
# A Parameter (or ParamSplat) in a function definition.
|
212
218
|
Param:
|
213
219
|
PARAM
|
214
|
-
| PARAM "." "." "." { result =
|
220
|
+
| PARAM "." "." "." { result = SplatNode.new(val[0]) }
|
215
221
|
;
|
216
222
|
|
217
223
|
# A regular splat.
|
218
224
|
Splat:
|
219
|
-
Expression "." "." "." { result =
|
225
|
+
Expression "." "." "." { result = SplatNode.new(val[0]) }
|
220
226
|
;
|
221
227
|
|
222
228
|
# Expressions that can be treated as values.
|
@@ -276,7 +282,6 @@ rule
|
|
276
282
|
Invocation:
|
277
283
|
Value Arguments { result = CallNode.new(val[0], val[1]) }
|
278
284
|
| Invocation Arguments { result = CallNode.new(val[0], val[1]) }
|
279
|
-
# | Invocation Code { result = val[0] << val[1] }
|
280
285
|
;
|
281
286
|
|
282
287
|
# The list of arguments to a function invocation.
|
@@ -315,6 +320,12 @@ rule
|
|
315
320
|
| ArgList OUTDENT { result = val[0] }
|
316
321
|
;
|
317
322
|
|
323
|
+
# Just simple, comma-separated, required arguments (no fancy syntax).
|
324
|
+
SimpleArgs:
|
325
|
+
Expression { result = val[0] }
|
326
|
+
| SimpleArgs "," Expression { result = ([val[0]] << val[2]).flatten }
|
327
|
+
;
|
328
|
+
|
318
329
|
# Try/catch/finally exception handling blocks.
|
319
330
|
Try:
|
320
331
|
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
|
@@ -342,6 +353,7 @@ rule
|
|
342
353
|
While:
|
343
354
|
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
|
344
355
|
| WHILE Expression { result = WhileNode.new(val[1], nil) }
|
356
|
+
| Expression WHILE Expression { result = WhileNode.new(val[2], Expressions.wrap(val[0])) }
|
345
357
|
;
|
346
358
|
|
347
359
|
# Array comprehensions, including guard and current index.
|
@@ -384,8 +396,8 @@ rule
|
|
384
396
|
|
385
397
|
# An individual when.
|
386
398
|
When:
|
387
|
-
LEADING_WHEN
|
388
|
-
| LEADING_WHEN
|
399
|
+
LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
400
|
+
| LEADING_WHEN SimpleArgs Block
|
389
401
|
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
390
402
|
| Comment Terminator When { result = val[2].add_comment(val[0]) }
|
391
403
|
;
|
data/lib/coffee_script/lexer.rb
CHANGED
@@ -22,22 +22,24 @@ module CoffeeScript
|
|
22
22
|
IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/
|
23
23
|
NUMBER = /\A(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
|
24
24
|
STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m
|
25
|
-
HEREDOC = /\A("{6}|'{6}|"{3}\n?(
|
25
|
+
HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?(\s*)"{3}|'{3}\n?(.*?)\n?(\s*)'{3})/m
|
26
26
|
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
|
27
27
|
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
|
28
28
|
WHITESPACE = /\A([ \t]+)/
|
29
29
|
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
|
30
|
-
CODE = /\A(
|
30
|
+
CODE = /\A(=?=>)/
|
31
31
|
REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
|
32
32
|
MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/
|
33
33
|
LAST_DENT = /\n([ \t]*)/
|
34
34
|
ASSIGNMENT = /\A(:|=)\Z/
|
35
35
|
|
36
36
|
# Token cleaning regexes.
|
37
|
-
JS_CLEANER
|
38
|
-
MULTILINER
|
37
|
+
JS_CLEANER = /(\A`|`\Z)/
|
38
|
+
MULTILINER = /\n/
|
39
|
+
STRING_NEWLINES = /\n\s*/
|
39
40
|
COMMENT_CLEANER = /(^\s*#|\n\s*$)/
|
40
|
-
NO_NEWLINE
|
41
|
+
NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
|
42
|
+
HEREDOC_INDENT = /^\s+/
|
41
43
|
|
42
44
|
# Tokens which a regular expression will never immediately follow, but which
|
43
45
|
# a division operator might.
|
@@ -50,12 +52,12 @@ module CoffeeScript
|
|
50
52
|
|
51
53
|
# Scan by attempting to match tokens one character at a time. Slow and steady.
|
52
54
|
def tokenize(code)
|
53
|
-
@code
|
54
|
-
@i
|
55
|
-
@line
|
56
|
-
@indent
|
57
|
-
@indents = []
|
58
|
-
@tokens
|
55
|
+
@code = code.chomp # Cleanup code by remove extra line breaks
|
56
|
+
@i = 0 # Current character position we're parsing
|
57
|
+
@line = 1 # The current line.
|
58
|
+
@indent = 0 # The current indent level.
|
59
|
+
@indents = [] # The stack of all indent levels we are currently within.
|
60
|
+
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
|
59
61
|
while @i < @code.length
|
60
62
|
@chunk = @code[@i..-1]
|
61
63
|
extract_next_token
|
@@ -105,7 +107,7 @@ module CoffeeScript
|
|
105
107
|
# Matches strings, including multi-line strings.
|
106
108
|
def string_token
|
107
109
|
return false unless string = @chunk[STRING, 1]
|
108
|
-
escaped = string.gsub(
|
110
|
+
escaped = string.gsub(STRING_NEWLINES, " \\\n")
|
109
111
|
token(:STRING, escaped)
|
110
112
|
@line += string.count("\n")
|
111
113
|
@i += string.length
|
@@ -114,9 +116,10 @@ module CoffeeScript
|
|
114
116
|
# Matches heredocs, adjusting indentation to the correct level.
|
115
117
|
def heredoc_token
|
116
118
|
return false unless match = @chunk.match(HEREDOC)
|
117
|
-
|
118
|
-
|
119
|
-
doc.gsub!(
|
119
|
+
doc = match[2] || match[4]
|
120
|
+
indent = doc.scan(HEREDOC_INDENT).min
|
121
|
+
doc.gsub!(/^#{indent}/, "")
|
122
|
+
doc.gsub!("\n", "\\n")
|
120
123
|
doc.gsub!('"', '\\"')
|
121
124
|
token(:STRING, "\"#{doc}\"")
|
122
125
|
@line += match[1].count("\n")
|
@@ -153,7 +156,7 @@ module CoffeeScript
|
|
153
156
|
@line += indent.scan(MULTILINER).size
|
154
157
|
@i += indent.size
|
155
158
|
next_character = @chunk[MULTI_DENT, 4]
|
156
|
-
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && last_value
|
159
|
+
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && !last_value.match(CODE))
|
157
160
|
return suppress_newlines(indent) if no_newlines
|
158
161
|
size = indent.scan(LAST_DENT).last.last.length
|
159
162
|
return newline_token(indent) if size == @indent
|
data/lib/coffee_script/nodes.rb
CHANGED
@@ -36,9 +36,9 @@ module CoffeeScript
|
|
36
36
|
def compile(o={})
|
37
37
|
@options = o.dup
|
38
38
|
@indent = o[:indent]
|
39
|
-
top
|
40
|
-
closure
|
41
|
-
closure
|
39
|
+
top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
|
40
|
+
closure = statement? && !statement_only? && !top && !@options[:return]
|
41
|
+
closure ? compile_closure(@options) : compile_node(@options)
|
42
42
|
end
|
43
43
|
|
44
44
|
def compile_closure(o={})
|
@@ -56,6 +56,7 @@ module CoffeeScript
|
|
56
56
|
def unwrap; self; end
|
57
57
|
def statement?; false; end
|
58
58
|
def statement_only?; false; end
|
59
|
+
def top_sensitive?; false; end
|
59
60
|
end
|
60
61
|
|
61
62
|
# A collection of nodes, each one representing an expression.
|
@@ -128,8 +129,9 @@ module CoffeeScript
|
|
128
129
|
# at the top.
|
129
130
|
def compile_with_declarations(o={})
|
130
131
|
code = compile_node(o)
|
131
|
-
|
132
|
-
|
132
|
+
code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
|
133
|
+
code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
|
134
|
+
write(code)
|
133
135
|
end
|
134
136
|
|
135
137
|
# Compiles a single expression within the expression list.
|
@@ -243,7 +245,7 @@ module CoffeeScript
|
|
243
245
|
end
|
244
246
|
|
245
247
|
def splat?
|
246
|
-
@arguments.any? {|a| a.is_a?(
|
248
|
+
@arguments.any? {|a| a.is_a?(SplatNode) }
|
247
249
|
end
|
248
250
|
|
249
251
|
def <<(argument)
|
@@ -273,7 +275,7 @@ module CoffeeScript
|
|
273
275
|
obj = @variable.source || 'this'
|
274
276
|
args = @arguments.map do |arg|
|
275
277
|
code = arg.compile(o)
|
276
|
-
code = arg.is_a?(
|
278
|
+
code = arg.is_a?(SplatNode) ? code : "[#{code}]"
|
277
279
|
arg.equal?(@arguments.first) ? code : ".concat(#{code})"
|
278
280
|
end
|
279
281
|
"#{prefix}#{meth}.apply(#{obj}, #{args.join('')})"
|
@@ -447,23 +449,27 @@ module CoffeeScript
|
|
447
449
|
end
|
448
450
|
|
449
451
|
def compile_node(o)
|
450
|
-
return compile_pattern_match(o) if
|
451
|
-
return compile_splice(o) if @variable.splice?
|
452
|
+
return compile_pattern_match(o) if statement?
|
453
|
+
return compile_splice(o) if value? && @variable.splice?
|
452
454
|
stmt = o.delete(:as_statement)
|
453
455
|
name = @variable.compile(o)
|
454
|
-
last = @variable.last.to_s.sub(LEADING_DOT, '')
|
456
|
+
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
|
455
457
|
proto = name[PROTO_ASSIGN, 1]
|
456
458
|
o = o.merge(:last_assign => last, :proto_assign => proto)
|
457
459
|
o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER)
|
458
460
|
return write("#{name}: #{@value.compile(o)}") if @context == :object
|
459
|
-
o[:scope].find(name) unless @variable.properties?
|
461
|
+
o[:scope].find(name) unless value? && @variable.properties?
|
460
462
|
val = "#{name} = #{@value.compile(o)}"
|
461
463
|
return write("#{idt}#{val};") if stmt
|
462
464
|
write(o[:return] ? "#{idt}return (#{val})" : val)
|
463
465
|
end
|
464
466
|
|
467
|
+
def value?
|
468
|
+
@variable.is_a?(ValueNode)
|
469
|
+
end
|
470
|
+
|
465
471
|
def statement?
|
466
|
-
@variable.array? || @variable.object?
|
472
|
+
value? && (@variable.array? || @variable.object?)
|
467
473
|
end
|
468
474
|
|
469
475
|
# Implementation of recursive pattern matching, when assigning array or
|
@@ -476,9 +482,12 @@ module CoffeeScript
|
|
476
482
|
@variable.base.objects.each_with_index do |obj, i|
|
477
483
|
obj, i = obj.value, obj.variable.base if @variable.object?
|
478
484
|
access_class = @variable.array? ? IndexNode : AccessorNode
|
479
|
-
|
480
|
-
|
481
|
-
|
485
|
+
if obj.is_a?(SplatNode)
|
486
|
+
val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
|
487
|
+
else
|
488
|
+
val = ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))])
|
489
|
+
end
|
490
|
+
assigns << AssignNode.new(obj, val).compile(o)
|
482
491
|
end
|
483
492
|
write(assigns.join("\n"))
|
484
493
|
end
|
@@ -541,14 +550,23 @@ module CoffeeScript
|
|
541
550
|
|
542
551
|
# A function definition. The only node that creates a new Scope.
|
543
552
|
class CodeNode < Node
|
544
|
-
attr_reader :params, :body
|
553
|
+
attr_reader :params, :body, :bound
|
545
554
|
|
546
|
-
def initialize(params, body)
|
555
|
+
def initialize(params, body, tag=nil)
|
547
556
|
@params = params
|
548
|
-
@body
|
557
|
+
@body = body
|
558
|
+
@bound = tag == :boundfunc
|
559
|
+
end
|
560
|
+
|
561
|
+
def statement?
|
562
|
+
@bound
|
549
563
|
end
|
550
564
|
|
551
565
|
def compile_node(o)
|
566
|
+
if @bound
|
567
|
+
o[:scope].assign("__this", "this")
|
568
|
+
fvar = o[:scope].free_variable
|
569
|
+
end
|
552
570
|
shared_scope = o.delete(:shared_scope)
|
553
571
|
o[:scope] = shared_scope || Scope.new(o[:scope], @body)
|
554
572
|
o[:return] = true
|
@@ -557,20 +575,23 @@ module CoffeeScript
|
|
557
575
|
o.delete(:no_wrap)
|
558
576
|
o.delete(:globals)
|
559
577
|
name = o.delete(:immediate_assign)
|
560
|
-
if @params.last.is_a?(
|
578
|
+
if @params.last.is_a?(SplatNode)
|
561
579
|
splat = @params.pop
|
562
580
|
splat.index = @params.length
|
563
581
|
@body.unshift(splat)
|
564
582
|
end
|
565
583
|
@params.each {|id| o[:scope].parameter(id.to_s) }
|
566
|
-
code = @body.compile_with_declarations(o)
|
584
|
+
code = "\n#{@body.compile_with_declarations(o)}\n"
|
567
585
|
name_part = name ? " #{name}" : ''
|
568
|
-
|
586
|
+
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt}}"
|
587
|
+
return write(func) unless @bound
|
588
|
+
write("#{idt}#{fvar} = #{func};\n#{idt}#{o[:return] ? 'return ' : ''}(function#{name_part}() {\n#{idt(1)}return #{fvar}.apply(__this, arguments);\n#{idt}});")
|
569
589
|
end
|
570
590
|
end
|
571
591
|
|
572
|
-
# A
|
573
|
-
|
592
|
+
# A splat, either as a parameter to a function, an argument to a call,
|
593
|
+
# or in a destructuring assignment.
|
594
|
+
class SplatNode < Node
|
574
595
|
attr_accessor :index
|
575
596
|
attr_reader :name
|
576
597
|
|
@@ -579,20 +600,20 @@ module CoffeeScript
|
|
579
600
|
end
|
580
601
|
|
581
602
|
def compile_node(o={})
|
582
|
-
o
|
583
|
-
write("#{@name} = Array.prototype.slice.call(arguments, #{@index})")
|
603
|
+
write(@index ? compile_param(o) : compile_arg(o))
|
584
604
|
end
|
585
|
-
end
|
586
605
|
|
587
|
-
|
588
|
-
|
606
|
+
def compile_param(o)
|
607
|
+
o[:scope].find(@name)
|
608
|
+
"#{@name} = Array.prototype.slice.call(arguments, #{@index})"
|
609
|
+
end
|
589
610
|
|
590
|
-
def
|
591
|
-
@
|
611
|
+
def compile_arg(o)
|
612
|
+
@name.compile(o)
|
592
613
|
end
|
593
614
|
|
594
|
-
def
|
595
|
-
|
615
|
+
def compile_value(o, name, index)
|
616
|
+
"Array.prototype.slice.call(#{name}, #{index})"
|
596
617
|
end
|
597
618
|
|
598
619
|
end
|
@@ -644,6 +665,17 @@ module CoffeeScript
|
|
644
665
|
end
|
645
666
|
end
|
646
667
|
|
668
|
+
# A faux-node that is never created by the grammar, but is used during
|
669
|
+
# code generation to generate a quick "array.push(value)" tree of nodes.
|
670
|
+
class PushNode
|
671
|
+
def self.wrap(array, expressions)
|
672
|
+
Expressions.wrap(CallNode.new(
|
673
|
+
ValueNode.new(LiteralNode.new(array), [AccessorNode.new('push')]),
|
674
|
+
[expressions.unwrap]
|
675
|
+
))
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
647
679
|
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
648
680
|
# it, all other loops can be manufactured.
|
649
681
|
class WhileNode < Node
|
@@ -655,14 +687,25 @@ module CoffeeScript
|
|
655
687
|
@condition, @body = condition, body
|
656
688
|
end
|
657
689
|
|
690
|
+
def top_sensitive?
|
691
|
+
true
|
692
|
+
end
|
693
|
+
|
658
694
|
def compile_node(o)
|
659
695
|
returns = o.delete(:return)
|
696
|
+
top = o.delete(:top) && !returns
|
660
697
|
o[:indent] = idt(1)
|
661
698
|
o[:top] = true
|
662
699
|
cond = @condition.compile(o)
|
663
|
-
|
664
|
-
|
665
|
-
|
700
|
+
set = ''
|
701
|
+
if !top
|
702
|
+
rvar = o[:scope].free_variable
|
703
|
+
set = "#{idt}#{rvar} = [];\n"
|
704
|
+
@body = PushNode.wrap(rvar, @body)
|
705
|
+
end
|
706
|
+
post = returns ? "\n#{idt}return #{rvar};" : ''
|
707
|
+
return write("#{set}#{idt}while (#{cond}) null;#{post}") if @body.nil?
|
708
|
+
write("#{set}#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
|
666
709
|
end
|
667
710
|
end
|
668
711
|
|
@@ -684,6 +727,10 @@ module CoffeeScript
|
|
684
727
|
@name, @index = @index, @name if @object
|
685
728
|
end
|
686
729
|
|
730
|
+
def top_sensitive?
|
731
|
+
true
|
732
|
+
end
|
733
|
+
|
687
734
|
def compile_node(o)
|
688
735
|
top_level = o.delete(:top) && !o[:return]
|
689
736
|
range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
|
@@ -703,7 +750,7 @@ module CoffeeScript
|
|
703
750
|
else
|
704
751
|
index_var = nil
|
705
752
|
source_part = "#{svar} = #{source.compile(o)};\n#{idt}"
|
706
|
-
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar}=0; #{ivar}
|
753
|
+
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{ivar}++"
|
707
754
|
var_part = @name ? "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" : ''
|
708
755
|
end
|
709
756
|
body = @body
|
@@ -712,9 +759,7 @@ module CoffeeScript
|
|
712
759
|
if top_level
|
713
760
|
body = Expressions.wrap(body)
|
714
761
|
else
|
715
|
-
body =
|
716
|
-
ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [body.unwrap]
|
717
|
-
))
|
762
|
+
body = PushNode.wrap(rvar, body)
|
718
763
|
end
|
719
764
|
if o[:return]
|
720
765
|
return_result = "return #{return_result}" if o[:return]
|
@@ -724,8 +769,9 @@ module CoffeeScript
|
|
724
769
|
body = Expressions.wrap(IfNode.new(@filter, body))
|
725
770
|
end
|
726
771
|
if @object
|
772
|
+
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
|
727
773
|
body = Expressions.wrap(IfNode.new(
|
728
|
-
CallNode.new(ValueNode.new(LiteralNode.wrap(
|
774
|
+
CallNode.new(ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]), [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]),
|
729
775
|
Expressions.wrap(body),
|
730
776
|
nil,
|
731
777
|
{:statement => true}
|
@@ -818,6 +864,7 @@ module CoffeeScript
|
|
818
864
|
@body = body && body.unwrap
|
819
865
|
@else_body = else_body && else_body.unwrap
|
820
866
|
@tags = tags
|
867
|
+
@multiple = true if @condition.is_a?(Array)
|
821
868
|
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
|
822
869
|
end
|
823
870
|
|
@@ -839,7 +886,8 @@ module CoffeeScript
|
|
839
886
|
|
840
887
|
# Rewrite a chain of IfNodes with their switch condition for equality.
|
841
888
|
def rewrite_condition(expression)
|
842
|
-
@condition = OpNode.new("is", expression,
|
889
|
+
@condition = @multiple ? @condition.map {|c| OpNode.new("is", expression, c) } :
|
890
|
+
OpNode.new("is", expression, @condition)
|
843
891
|
@else_body.rewrite_condition(expression) if chain?
|
844
892
|
self
|
845
893
|
end
|
@@ -861,6 +909,10 @@ module CoffeeScript
|
|
861
909
|
@is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
|
862
910
|
end
|
863
911
|
|
912
|
+
def compile_condition(o)
|
913
|
+
[@condition].flatten.map {|c| c.compile(o) }.join(' || ')
|
914
|
+
end
|
915
|
+
|
864
916
|
def compile_node(o)
|
865
917
|
write(statement? ? compile_statement(o) : compile_ternary(o))
|
866
918
|
end
|
@@ -876,7 +928,7 @@ module CoffeeScript
|
|
876
928
|
if_dent = child ? '' : idt
|
877
929
|
com_dent = child ? idt : ''
|
878
930
|
prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
|
879
|
-
if_part = "#{prefix}#{if_dent}if (#{
|
931
|
+
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}"
|
880
932
|
return if_part unless @else_body
|
881
933
|
else_part = chain? ?
|
882
934
|
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :
|