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.
@@ -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))}" :