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.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'coffee-script'
3
- s.version = '0.2.4' # Keep version in sync with coffee-script.rb
4
- s.date = '2010-1-12'
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"
@@ -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 == dc.Model.CHANGED and model.hasChanged('selected')
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.4' # Keep in sync with the gemspec.
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: =&gt; … </string>
45
45
  <key>match</key>
46
- <string>([a-zA-Z0-9_?.$:*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=&gt;)</string>
46
+ <string>([a-zA-Z0-9_?.$:*]*?)\s*(=\b|:\b)\s*([\w,\s]*?)\s*(=+&gt;)</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 =&gt; … </string>
66
66
  <key>match</key>
67
- <string>([a-zA-Z0-9_?., $:*]*)\s*(=&gt;)</string>
67
+ <string>([a-zA-Z0-9_?., $*]*)\s*(=+&gt;)</string>
68
68
  <key>name</key>
69
69
  <string>meta.inline.function.coffee</string>
70
70
  </dict>
@@ -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 "=>" Block { result = CodeNode.new(val[0], val[2]) }
202
- | "=>" Block { result = CodeNode.new([], val[1]) }
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 = ParamSplatNode.new(val[0]) }
220
+ | PARAM "." "." "." { result = SplatNode.new(val[0]) }
215
221
  ;
216
222
 
217
223
  # A regular splat.
218
224
  Splat:
219
- Expression "." "." "." { result = ArgSplatNode.new(val[0])}
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 Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
388
- | LEADING_WHEN Expression Block
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
  ;
@@ -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?(\s*)(.*?)\n?(\s*)"{3}|'{3}\n?(\s*)(.*?)\n?(\s*)'{3})/m
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 = /(\A`|`\Z)/
38
- MULTILINER = /\n/
37
+ JS_CLEANER = /(\A`|`\Z)/
38
+ MULTILINER = /\n/
39
+ STRING_NEWLINES = /\n\s*/
39
40
  COMMENT_CLEANER = /(^\s*#|\n\s*$)/
40
- NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
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 = code.chomp # Cleanup code by remove extra line breaks
54
- @i = 0 # Current character position we're parsing
55
- @line = 1 # The current line.
56
- @indent = 0 # The current indent level.
57
- @indents = [] # The stack of all indent levels we are currently within.
58
- @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
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(MULTILINER, " \\\n")
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
- indent = match[2] || match[5]
118
- doc = match[3] || match[6]
119
- doc.gsub!(/\n#{indent}/, "\\n")
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
@@ -36,9 +36,9 @@ module CoffeeScript
36
36
  def compile(o={})
37
37
  @options = o.dup
38
38
  @indent = o[:indent]
39
- top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top)
40
- closure = statement? && !statement_only? && !top && !@options[:return]
41
- closure ? compile_closure(@options) : compile_node(@options)
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
- return code unless o[:scope].declarations?(self)
132
- write("#{idt}var #{o[:scope].declared_variables.join(', ')};\n#{code}")
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?(ArgSplatNode) }
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?(ArgSplatNode) ? code : "[#{code}]"
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 @variable.array? || @variable.object?
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
- assigns << AssignNode.new(
480
- obj, ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))])
481
- ).compile(o)
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 = 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?(ParamSplatNode)
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
- write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{idt}}")
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 parameter splat in a function definition.
573
- class ParamSplatNode < Node
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[:scope].find(@name)
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
- class ArgSplatNode < Node
588
- attr_reader :value
606
+ def compile_param(o)
607
+ o[:scope].find(@name)
608
+ "#{@name} = Array.prototype.slice.call(arguments, #{@index})"
609
+ end
589
610
 
590
- def initialize(value)
591
- @value = value
611
+ def compile_arg(o)
612
+ @name.compile(o)
592
613
  end
593
614
 
594
- def compile_node(o={})
595
- write(@value.compile(o))
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
- post = returns ? "\n#{idt}return null;" : ''
664
- return write("#{idt}while (#{cond}) null;#{post}") if @body.nil?
665
- write("#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
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}<#{svar}.length; #{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 = Expressions.wrap(CallNode.new(
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(svar), [AccessorNode.new(Value.new('hasOwnProperty'))]), [LiteralNode.wrap(ivar)]),
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, @condition)
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 (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}"
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))}" :