coffee-script 0.2.4 → 0.2.5
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/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))}" :
|