coffee-script 0.1.6 → 0.2.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.
@@ -12,69 +12,80 @@ module CoffeeScript
12
12
  "new", "return",
13
13
  "try", "catch", "finally", "throw",
14
14
  "break", "continue",
15
- "for", "in", "while",
15
+ "for", "in", "by", "where", "while",
16
16
  "switch", "when",
17
17
  "super", "extends",
18
18
  "delete", "instanceof", "typeof"]
19
19
 
20
20
  # Token matching regexes.
21
21
  IDENTIFIER = /\A([a-zA-Z$_]\w*)/
22
- NUMBER = /\A((\b|-)((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
22
+ NUMBER = /\A(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
23
23
  STRING = /\A(""|''|"(.*?)[^\\]"|'(.*?)[^\\]')/m
24
24
  JS = /\A(``|`(.*?)[^\\]`)/m
25
25
  OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
26
- WHITESPACE = /\A([ \t\r]+)/
27
- NEWLINE = /\A(\n+)/
28
- COMMENT = /\A((#[^\n]*\s*)+)/m
26
+ WHITESPACE = /\A([ \t]+)/
27
+ COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
29
28
  CODE = /\A(=>)/
30
29
  REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/
30
+ MULTI_DENT = /\A((\n([ \t]*)?)+)/
31
+ LAST_DENT = /\n([ \t]*)/
32
+ ASSIGNMENT = /\A(:|=)\Z/
31
33
 
32
34
  # Token cleaning regexes.
33
35
  JS_CLEANER = /(\A`|`\Z)/
34
36
  MULTILINER = /\n/
35
37
  COMMENT_CLEANER = /(^\s*#|\n\s*$)/
38
+ NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
36
39
 
37
- # Tokens that always constitute the start of an expression.
38
- EXP_START = ['{', '(', '[']
39
-
40
- # Tokens that always constitute the end of an expression.
41
- EXP_END = ['}', ')', ']']
42
-
43
- # Assignment tokens.
44
- ASSIGN = [':', '=']
40
+ # Tokens which a regular expression will never immediately follow, but which
41
+ # a division operator might.
42
+ # See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
43
+ NOT_REGEX = [
44
+ :IDENTIFIER, :NUMBER, :REGEX, :STRING,
45
+ ')', '++', '--', ']', '}',
46
+ :FALSE, :NULL, :THIS, :TRUE
47
+ ]
45
48
 
46
49
  # Scan by attempting to match tokens one character at a time. Slow and steady.
47
50
  def tokenize(code)
48
51
  @code = code.chomp # Cleanup code by remove extra line breaks
49
52
  @i = 0 # Current character position we're parsing
50
53
  @line = 1 # The current line.
54
+ @indent = 0 # The current indent level.
55
+ @indents = [] # The stack of all indent levels we are currently within.
51
56
  @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
52
57
  while @i < @code.length
53
58
  @chunk = @code[@i..-1]
54
59
  extract_next_token
55
60
  end
56
- @tokens
61
+ puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE']
62
+ close_indentation
63
+ Rewriter.new.rewrite(@tokens)
57
64
  end
58
65
 
59
- # At every position, run this list of match attempts, short-circuiting if
60
- # any of them succeed.
66
+ # At every position, run through this list of attempted matches,
67
+ # short-circuiting if any of them succeed.
61
68
  def extract_next_token
62
69
  return if identifier_token
63
70
  return if number_token
64
71
  return if string_token
65
72
  return if js_token
66
73
  return if regex_token
74
+ return if indent_token
67
75
  return if comment_token
68
76
  return if whitespace_token
69
77
  return literal_token
70
78
  end
71
79
 
80
+ # Tokenizers ==========================================================
81
+
72
82
  # Matches identifying literals: variables, keywords, method names, etc.
73
83
  def identifier_token
74
84
  return false unless identifier = @chunk[IDENTIFIER, 1]
75
- # Keywords are special identifiers tagged with their own name, 'if' will result
76
- # in an [:IF, "if"] token
85
+ # Keywords are special identifiers tagged with their own name,
86
+ # 'if' will result in an [:IF, "if"] token.
77
87
  tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
88
+ tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
78
89
  @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.')
79
90
  token(tag, identifier)
80
91
  @i += identifier.length
@@ -108,6 +119,7 @@ module CoffeeScript
108
119
  # Matches regular expression literals.
109
120
  def regex_token
110
121
  return false unless regex = @chunk[REGEX, 1]
122
+ return false if NOT_REGEX.include?(last_tag)
111
123
  token(:REGEX, regex)
112
124
  @i += regex.length
113
125
  end
@@ -121,73 +133,105 @@ module CoffeeScript
121
133
  @i += comment.length
122
134
  end
123
135
 
136
+ # Record tokens for indentation differing from the previous line.
137
+ def indent_token
138
+ return false unless indent = @chunk[MULTI_DENT, 1]
139
+ @line += indent.scan(MULTILINER).size
140
+ @i += indent.size
141
+ return suppress_newlines(indent) if last_value.to_s.match(NO_NEWLINE) && last_value != "=>"
142
+ size = indent.scan(LAST_DENT).last.last.length
143
+ return newline_token(indent) if size == @indent
144
+ if size > @indent
145
+ token(:INDENT, size - @indent)
146
+ @indents << (size - @indent)
147
+ else
148
+ outdent_token(@indent - size)
149
+ end
150
+ @indent = size
151
+ end
152
+
153
+ # Record an oudent token or tokens, if we're moving back inwards past
154
+ # multiple recorded indents.
155
+ def outdent_token(move_out)
156
+ while move_out > 0 && !@indents.empty?
157
+ last_indent = @indents.pop
158
+ token(:OUTDENT, last_indent)
159
+ move_out -= last_indent
160
+ end
161
+ token("\n", "\n")
162
+ end
163
+
124
164
  # Matches and consumes non-meaningful whitespace.
125
165
  def whitespace_token
126
166
  return false unless whitespace = @chunk[WHITESPACE, 1]
127
167
  @i += whitespace.length
128
168
  end
129
169
 
170
+ # Multiple newlines get merged together.
171
+ # Use a trailing \ to escape newlines.
172
+ def newline_token(newlines)
173
+ token("\n", "\n") unless last_value == "\n"
174
+ true
175
+ end
176
+
177
+ # Tokens to explicitly escape newlines are removed once their job is done.
178
+ def suppress_newlines(newlines)
179
+ @tokens.pop if last_value == "\\"
180
+ true
181
+ end
182
+
130
183
  # We treat all other single characters as a token. Eg.: ( ) , . !
131
184
  # Multi-character operators are also literal tokens, so that Racc can assign
132
- # the proper order of operations. Multiple newlines get merged together.
133
- # Use a trailing \ to escape newlines.
185
+ # the proper order of operations.
134
186
  def literal_token
135
- value = @chunk[NEWLINE, 1]
136
- if value
137
- @line += value.length
138
- token("\n", "\n") unless ["\n", "\\"].include?(last_value)
139
- @tokens.pop if last_value == "\\"
140
- return @i += value.length
141
- end
142
187
  value = @chunk[OPERATOR, 1]
143
188
  tag_parameters if value && value.match(CODE)
144
189
  value ||= @chunk[0,1]
145
- skip_following_newlines if EXP_START.include?(value)
146
- remove_leading_newlines if EXP_END.include?(value)
147
- tag = ASSIGN.include?(value) ? :ASSIGN : value
190
+ tag = value.match(ASSIGNMENT) ? :ASSIGN : value
148
191
  token(tag, value)
149
192
  @i += value.length
150
193
  end
151
194
 
195
+ # Helpers ==========================================================
196
+
152
197
  # Add a token to the results, taking note of the line number, and
153
198
  # immediately-preceding comment.
154
199
  def token(tag, value)
155
200
  @tokens << [tag, Value.new(value, @line)]
156
201
  end
157
202
 
158
- # Peek at the previous token.
203
+ # Peek at the previous token's value.
159
204
  def last_value
160
205
  @tokens.last && @tokens.last[1]
161
206
  end
162
207
 
208
+ # Peek at the previous token's tag.
209
+ def last_tag
210
+ @tokens.last && @tokens.last[0]
211
+ end
212
+
163
213
  # A source of ambiguity in our grammar was parameter lists in function
164
214
  # definitions (as opposed to argument lists in function calls). Tag
165
- # parameter identifiers in order to avoid this.
215
+ # parameter identifiers in order to avoid this. Also, parameter lists can
216
+ # make use of splats.
166
217
  def tag_parameters
167
- index = 0
218
+ i = 0
168
219
  loop do
169
- tok = @tokens[index -= 1]
220
+ i -= 1
221
+ tok = @tokens[i]
170
222
  return if !tok
171
223
  next if tok[0] == ','
224
+ next tok[0] = :PARAM_SPLAT if tok[0] == '*'
172
225
  return if tok[0] != :IDENTIFIER
173
226
  tok[0] = :PARAM
174
227
  end
175
228
  end
176
229
 
177
- # Consume and ignore newlines immediately after this point.
178
- def skip_following_newlines
179
- newlines = @code[(@i+1)..-1][NEWLINE, 1]
180
- if newlines
181
- @line += newlines.length
182
- @i += newlines.length
183
- end
184
- end
185
-
186
- # Discard newlines immediately before this point.
187
- def remove_leading_newlines
188
- @tokens.pop if last_value == "\n"
230
+ # Close up all remaining open blocks. IF the first token is an indent,
231
+ # axe it.
232
+ def close_indentation
233
+ outdent_token(@indent)
189
234
  end
190
235
 
191
236
  end
192
-
193
237
  end
@@ -15,15 +15,16 @@ coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirna
15
15
  checkForErrors: coffeeProcess =>
16
16
  return true if coffeeProcess.wait() is 0
17
17
  system.stderr.print(coffeeProcess.stderr.read())
18
- throw new Error("CoffeeScript compile error").
18
+ throw new Error("CoffeeScript compile error")
19
19
 
20
20
  # Run a simple REPL, round-tripping to the CoffeeScript compiler for every
21
21
  # command.
22
22
  exports.run: args =>
23
- args.shift()
24
23
  if args.length
25
- exports.evalCS(File.read(path)) for path in args.
26
- return true.
24
+ for path, i in args
25
+ exports.evalCS(File.read(path))
26
+ delete args[i]
27
+ return true
27
28
 
28
29
  while true
29
30
  try
@@ -31,31 +32,31 @@ exports.run: args =>
31
32
  result: exports.evalCS(Readline.readline())
32
33
  print(result) if result isnt undefined
33
34
  catch e
34
- print(e)...
35
+ print(e)
35
36
 
36
37
  # Compile a given CoffeeScript file into JavaScript.
37
38
  exports.compileFile: path =>
38
39
  coffee: OS.popen([coffeePath, "--print", "--no-wrap", path])
39
40
  checkForErrors(coffee)
40
- coffee.stdout.read().
41
+ coffee.stdout.read()
41
42
 
42
43
  # Compile a string of CoffeeScript into JavaScript.
43
44
  exports.compile: source =>
44
45
  coffee: OS.popen([coffeePath, "--eval", "--no-wrap"])
45
46
  coffee.stdin.write(source).flush().close()
46
47
  checkForErrors(coffee)
47
- coffee.stdout.read().
48
+ coffee.stdout.read()
48
49
 
49
50
  # Evaluating a string of CoffeeScript first compiles it externally.
50
51
  exports.evalCS: source =>
51
- eval(exports.compile(source)).
52
+ eval(exports.compile(source))
52
53
 
53
54
  # Make a factory for the CoffeeScript environment.
54
55
  exports.makeNarwhalFactory: path =>
55
- code: exports.compileFile(path)
56
- factoryText: "function(require,exports,module,system,print){ 1 + 1 /**/\n}"
57
- if system.engine is "rhino"
58
- Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null)
59
- else
60
- # eval requires parenthesis, but parenthesis break compileFunction.
61
- eval("(" + factoryText + ")")..
56
+ code: exports.compileFile(path)
57
+ factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}"
58
+ if system.engine is "rhino"
59
+ Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null)
60
+ else
61
+ # eval requires parentheses, but parentheses break compileFunction.
62
+ eval("(" + factoryText + ")")
@@ -1,14 +1,16 @@
1
1
  (function(){
2
2
  var File, OS, Readline, checkForErrors, coffeePath;
3
- // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee Executes the `coffee` Ruby program to convert from CoffeeScript
4
- // to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies.
3
+ // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee
4
+ // Executes the `coffee` Ruby program to convert from CoffeeScript
5
+ // to Javascript. Eventually this will hopefully happen entirely within JS.
6
+ // Require external dependencies.
5
7
  OS = require('os');
6
8
  File = require('file');
7
9
  Readline = require('readline');
8
10
  // The path to the CoffeeScript Compiler.
9
11
  coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee');
10
12
  // Our general-purpose error handler.
11
- checkForErrors = function(coffeeProcess) {
13
+ checkForErrors = function checkForErrors(coffeeProcess) {
12
14
  if (coffeeProcess.wait() === 0) {
13
15
  return true;
14
16
  }
@@ -17,17 +19,20 @@
17
19
  };
18
20
  // Run a simple REPL, round-tripping to the CoffeeScript compiler for every
19
21
  // command.
20
- exports.run = function(args) {
21
- var __a, __b, __c, __d, path, result;
22
- args.shift();
22
+ exports.run = function run(args) {
23
+ var __a, __b, __c, i, path, result;
23
24
  if (args.length) {
24
25
  __a = args;
25
- __d = [];
26
- for (__b=0, __c=__a.length; __b<__c; __b++) {
27
- path = __a[__b];
28
- __d[__b] = exports.evalCS(File.read(path));
26
+ __b = [];
27
+ for (i in __a) {
28
+ if (__a.hasOwnProperty(i)) {
29
+ path = __a[i];
30
+ exports.evalCS(File.read(path));
31
+ __c = delete args[i];
32
+ __b.push(__c);
33
+ }
29
34
  }
30
- __d;
35
+ __b;
31
36
  return true;
32
37
  }
33
38
  while (true) {
@@ -43,14 +48,14 @@
43
48
  }
44
49
  };
45
50
  // Compile a given CoffeeScript file into JavaScript.
46
- exports.compileFile = function(path) {
51
+ exports.compileFile = function compileFile(path) {
47
52
  var coffee;
48
53
  coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]);
49
54
  checkForErrors(coffee);
50
55
  return coffee.stdout.read();
51
56
  };
52
57
  // Compile a string of CoffeeScript into JavaScript.
53
- exports.compile = function(source) {
58
+ exports.compile = function compile(source) {
54
59
  var coffee;
55
60
  coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]);
56
61
  coffee.stdin.write(source).flush().close();
@@ -58,18 +63,18 @@
58
63
  return coffee.stdout.read();
59
64
  };
60
65
  // Evaluating a string of CoffeeScript first compiles it externally.
61
- exports.evalCS = function(source) {
66
+ exports.evalCS = function evalCS(source) {
62
67
  return eval(exports.compile(source));
63
68
  };
64
69
  // Make a factory for the CoffeeScript environment.
65
- exports.makeNarwhalFactory = function(path) {
70
+ exports.makeNarwhalFactory = function makeNarwhalFactory(path) {
66
71
  var code, factoryText;
67
72
  code = exports.compileFile(path);
68
- factoryText = "function(require,exports,module,system,print){ 1 + 1 /**/\n}";
73
+ factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}";
69
74
  if (system.engine === "rhino") {
70
75
  return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null);
71
76
  } else {
72
- // eval requires parenthesis, but parenthesis break compileFunction.
77
+ // eval requires parentheses, but parentheses break compileFunction.
73
78
  return eval("(" + factoryText + ")");
74
79
  }
75
80
  };
@@ -6,12 +6,14 @@
6
6
  };
7
7
  loader = {
8
8
  // Reload the coffee-script environment from source.
9
- reload: function(topId, path) {
10
- coffeescript = coffeescript || require('./coffee-script');
11
- return (factories[topId] = coffeescript.makeNarwhalFactory(path));
9
+ reload: function reload(topId, path) {
10
+ coffeescript = coffeescript || require('coffee-script');
11
+ return (factories[topId] = function() {
12
+ return coffeescript.makeNarwhalFactory(path);
13
+ });
12
14
  },
13
15
  // Ensure that the coffee-script environment is loaded.
14
- load: function(topId, path) {
16
+ load: function load(topId, path) {
15
17
  return factories[topId] = factories[topId] || this.reload(topId, path);
16
18
  }
17
19
  };
@@ -7,12 +7,12 @@ loader: {
7
7
 
8
8
  # Reload the coffee-script environment from source.
9
9
  reload: topId, path =>
10
- coffeescript ||= require('./coffee-script')
11
- factories[topId]: coffeescript.makeNarwhalFactory(path).
10
+ coffeescript ||= require('coffee-script')
11
+ factories[topId]: => coffeescript.makeNarwhalFactory(path)
12
12
 
13
13
  # Ensure that the coffee-script environment is loaded.
14
14
  load: topId, path =>
15
- factories[topId] ||= this.reload(topId, path).
15
+ factories[topId] ||= this.reload(topId, path)
16
16
 
17
17
  }
18
18
 
@@ -11,17 +11,11 @@ module CoffeeScript
11
11
  class_eval "def statement?; true; end"
12
12
  end
13
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"
14
+ # Tag this node as a statement that cannot be transformed into an expression.
15
+ # (break, continue, etc.) It doesn't make sense to try to transform it.
16
+ def self.statement_only
17
+ statement
18
+ class_eval "def statement_only?; true; end"
25
19
  end
26
20
 
27
21
  def write(code)
@@ -29,16 +23,27 @@ module CoffeeScript
29
23
  code
30
24
  end
31
25
 
26
+ # This is extremely important -- we convert JS statements into expressions
27
+ # by wrapping them in a closure, only if it's possible, and we're not at
28
+ # the top level of a block (which would be unnecessary), and we haven't
29
+ # already been asked to return the result.
32
30
  def compile(o={})
33
31
  @options = o.dup
32
+ top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top)
33
+ closure = statement? && !statement_only? && !top && !@options[:return]
34
+ closure ? compile_closure(@options) : compile_node(@options)
35
+ end
36
+
37
+ def compile_closure(o={})
38
+ indent = o[:indent]
39
+ o[:indent] += TAB
40
+ "(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()"
34
41
  end
35
42
 
36
43
  # 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
44
+ def unwrap; self; end
45
+ def statement?; false; end
46
+ def statement_only?; false; end
42
47
  end
43
48
 
44
49
  # A collection of nodes, each one representing an expression.
@@ -49,12 +54,13 @@ module CoffeeScript
49
54
  STRIP_TRAILING_WHITESPACE = /\s+$/
50
55
 
51
56
  # 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])
57
+ def self.wrap(*nodes)
58
+ return nodes[0] if nodes.length == 1 && nodes[0].is_a?(Expressions)
59
+ Expressions.new(*nodes)
54
60
  end
55
61
 
56
- def initialize(nodes)
57
- @expressions = nodes
62
+ def initialize(*nodes)
63
+ @expressions = nodes.flatten
58
64
  end
59
65
 
60
66
  # Tack an expression onto the end of this node.
@@ -63,6 +69,11 @@ module CoffeeScript
63
69
  self
64
70
  end
65
71
 
72
+ def unshift(node)
73
+ @expressions.unshift(node)
74
+ self
75
+ end
76
+
66
77
  # If this Expressions consists of a single node, pull it back out.
67
78
  def unwrap
68
79
  @expressions.length == 1 ? @expressions.first : self
@@ -74,47 +85,48 @@ module CoffeeScript
74
85
  node == @expressions[@last_index]
75
86
  end
76
87
 
77
- # If this is the top-level Expressions, wrap everything in a safety closure.
78
- def root_compile(o={})
79
- indent = o[:no_wrap] ? '' : TAB
80
- code = compile(o.merge(:indent => indent, :scope => Scope.new), o[:no_wrap] ? nil : :code)
81
- code.gsub!(STRIP_TRAILING_WHITESPACE, '')
82
- o[:no_wrap] ? code : "(function(){\n#{code}\n})();"
88
+ def compile(o={})
89
+ o[:scope] ? super(o) : compile_root(o)
83
90
  end
84
91
 
85
- # The extra fancy is to handle pushing down returns and assignments
86
- # recursively to the final lines of inner statements.
87
- # Variables first defined within the Expressions body have their
88
- # declarations pushed up to the top scope.
89
- def compile(options={}, parent=nil)
90
- return root_compile(options) unless options[:scope]
92
+ # The extra fancy is to handle pushing down returns to the final lines of
93
+ # inner statements. Variables first defined within the Expressions body
94
+ # have their declarations pushed up top of the closest scope.
95
+ def compile_node(options={})
91
96
  compiled = @expressions.map do |node|
92
- o = super(options)
93
- if last?(node) && (o[:return] || o[:assign])
94
- if o[:return]
95
- if node.statement? || node.custom_return?
96
- "#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
97
- else
98
- o.delete(:return)
99
- "#{o[:indent]}return #{node.compile(o)}#{node.line_ending}"
100
- end
101
- elsif o[:assign]
102
- if node.statement? || node.custom_assign?
103
- "#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
104
- else
105
- "#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};"
106
- end
97
+ o = options.dup
98
+ returns = o.delete(:return)
99
+ if last?(node) && returns && !node.statement_only?
100
+ if node.statement?
101
+ node.compile(o.merge(:return => true))
102
+ else
103
+ "#{o[:indent]}return #{node.compile(o)};"
107
104
  end
108
105
  else
109
- o.delete(:return) and o.delete(:assign)
110
- "#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
106
+ ending = node.statement? ? '' : ';'
107
+ indent = node.statement? ? '' : o[:indent]
108
+ "#{indent}#{node.compile(o.merge(:top => true))}#{ending}"
111
109
  end
112
110
  end
113
- scope = options[:scope]
114
- declarations = scope.any_declared? && parent == :code ? "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" : ''
115
- code = declarations + compiled.join("\n")
116
- write(code)
111
+ write(compiled.join("\n"))
112
+ end
113
+
114
+ # If this is the top-level Expressions, wrap everything in a safety closure.
115
+ def compile_root(o={})
116
+ indent = o[:no_wrap] ? '' : TAB
117
+ o.merge!(:indent => indent, :scope => Scope.new(nil, self))
118
+ code = o[:no_wrap] ? compile_node(o) : compile_with_declarations(o)
119
+ code.gsub!(STRIP_TRAILING_WHITESPACE, '')
120
+ o[:no_wrap] ? code : "(function(){\n#{code}\n})();"
121
+ end
122
+
123
+ def compile_with_declarations(o={})
124
+ code = compile_node(o)
125
+ decls = ''
126
+ decls = "#{o[:indent]}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self)
127
+ decls + code
117
128
  end
129
+
118
130
  end
119
131
 
120
132
  # Literals are static values that have a Ruby representation, eg.: a string, a number,
@@ -131,21 +143,18 @@ module CoffeeScript
131
143
  def statement?
132
144
  STATEMENTS.include?(@value.to_s)
133
145
  end
146
+ alias_method :statement_only?, :statement?
134
147
 
135
- def line_ending
136
- @value.to_s[-1..-1] == ';' ? '' : ';'
137
- end
138
-
139
- def compile(o={})
140
- o = super(o)
141
- write(@value.to_s)
148
+ def compile_node(o)
149
+ indent = statement? ? o[:indent] : ''
150
+ ending = statement? ? ';' : ''
151
+ write(indent + @value.to_s + ending)
142
152
  end
143
153
  end
144
154
 
145
155
  # Try to return your expression, or tell it to return itself.
146
156
  class ReturnNode < Node
147
- statement
148
- custom_return
157
+ statement_only
149
158
 
150
159
  attr_reader :expression
151
160
 
@@ -153,32 +162,23 @@ module CoffeeScript
153
162
  @expression = expression
154
163
  end
155
164
 
156
- def line_ending
157
- @expression.custom_return? ? '' : ';'
158
- end
159
-
160
- def compile(o={})
161
- o = super(o)
162
- return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return?
165
+ def compile_node(o)
166
+ return write(@expression.compile(o.merge(:return => true))) if @expression.statement?
163
167
  compiled = @expression.compile(o)
164
- write(@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}")
168
+ write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null;" : "#{o[:indent]}return #{compiled};")
165
169
  end
166
170
  end
167
171
 
168
172
  # Pass through CoffeeScript comments into JavaScript comments at the
169
173
  # same position.
170
174
  class CommentNode < Node
171
- statement
175
+ statement_only
172
176
 
173
177
  def initialize(lines)
174
178
  @lines = lines.value
175
179
  end
176
180
 
177
- def line_ending
178
- ''
179
- end
180
-
181
- def compile(o={})
181
+ def compile_node(o={})
182
182
  delimiter = "\n#{o[:indent]}//"
183
183
  comment = "#{delimiter}#{@lines.join(delimiter)}"
184
184
  write(comment)
@@ -189,8 +189,6 @@ module CoffeeScript
189
189
  # Node for a function invocation. Takes care of converting super() calls into
190
190
  # calls against the prototype's function of the same name.
191
191
  class CallNode < Node
192
- LEADING_DOT = /\A\./
193
-
194
192
  attr_reader :variable, :arguments
195
193
 
196
194
  def initialize(variable, arguments=[])
@@ -206,42 +204,65 @@ module CoffeeScript
206
204
  @variable == :super
207
205
  end
208
206
 
209
- def compile(o={})
210
- o = super(o)
207
+ def prefix
208
+ @new ? "new " : ''
209
+ end
210
+
211
+ def splat?
212
+ @arguments.any? {|a| a.is_a?(ArgSplatNode) }
213
+ end
214
+
215
+ def <<(argument)
216
+ @arguments << argument
217
+ end
218
+
219
+ def compile_node(o)
220
+ return write(compile_splat(o)) if splat?
211
221
  args = @arguments.map{|a| a.compile(o) }.join(', ')
212
222
  return write(compile_super(args, o)) if super?
213
- prefix = @new ? "new " : ''
214
223
  write("#{prefix}#{@variable.compile(o)}(#{args})")
215
224
  end
216
225
 
217
226
  def compile_super(args, o)
218
- methname = o[:last_assign].sub(LEADING_DOT, '')
227
+ methname = o[:last_assign]
219
228
  arg_part = args.empty? ? '' : ", #{args}"
220
229
  "#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})"
221
230
  end
231
+
232
+ def compile_splat(o)
233
+ meth = @variable.compile(o)
234
+ obj = @variable.source || 'this'
235
+ args = @arguments.map do |arg|
236
+ code = arg.compile(o)
237
+ code = arg.is_a?(ArgSplatNode) ? code : "[#{code}]"
238
+ arg.equal?(@arguments.first) ? code : ".concat(#{code})"
239
+ end
240
+ "#{prefix}#{meth}.apply(#{obj}, #{args.join('')})"
241
+ end
222
242
  end
223
243
 
224
244
  # Node to extend an object's prototype with an ancestor object.
225
245
  # After goog.inherits from the Closure Library.
226
246
  class ExtendsNode < Node
247
+ statement
227
248
  attr_reader :sub_object, :super_object
228
249
 
229
250
  def initialize(sub_object, super_object)
230
251
  @sub_object, @super_object = sub_object, super_object
231
252
  end
232
253
 
233
- def compile(o={})
254
+ def compile_node(o={})
234
255
  sub, sup = @sub_object.compile(o), @super_object.compile(o)
235
- "#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" +
256
+ "#{o[:indent]}#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" +
236
257
  "#{sub}.prototype = new #{sup}();\n#{o[:indent]}" +
237
- "#{sub}.prototype.constructor = #{sub}"
258
+ "#{sub}.prototype.constructor = #{sub};"
238
259
  end
239
260
 
240
261
  end
241
262
 
242
263
  # A value, indexed or dotted into, or vanilla.
243
264
  class ValueNode < Node
244
- attr_reader :literal, :properties, :last
265
+ attr_reader :literal, :properties, :last, :source
245
266
 
246
267
  def initialize(literal, properties=[])
247
268
  @literal, @properties = literal, properties
@@ -260,20 +281,14 @@ module CoffeeScript
260
281
  @literal.is_a?(Node) && @literal.statement? && !properties?
261
282
  end
262
283
 
263
- def custom_assign?
264
- @literal.is_a?(Node) && @literal.custom_assign? && !properties?
265
- end
266
-
267
- def custom_return?
268
- @literal.is_a?(Node) && @literal.custom_return? && !properties?
269
- end
270
-
271
- def compile(o={})
272
- o = super(o)
273
- parts = [@literal, @properties].flatten.map do |val|
284
+ def compile_node(o)
285
+ only = o.delete(:only_first)
286
+ props = only ? @properties[0...-1] : @properties
287
+ parts = [@literal, props].flatten.map do |val|
274
288
  val.respond_to?(:compile) ? val.compile(o) : val.to_s
275
289
  end
276
290
  @last = parts.last
291
+ @source = parts.length > 1 ? parts[0...-1].join('') : nil
277
292
  write(parts.join(''))
278
293
  end
279
294
  end
@@ -286,8 +301,7 @@ module CoffeeScript
286
301
  @name = name
287
302
  end
288
303
 
289
- def compile(o={})
290
- o = super(o)
304
+ def compile_node(o)
291
305
  write(".#{@name}")
292
306
  end
293
307
  end
@@ -300,15 +314,14 @@ module CoffeeScript
300
314
  @index = index
301
315
  end
302
316
 
303
- def compile(o={})
304
- o = super(o)
317
+ def compile_node(o)
305
318
  write("[#{@index.compile(o)}]")
306
319
  end
307
320
  end
308
321
 
309
322
  # A range literal. Ranges can be used to extract portions (slices) of arrays,
310
323
  # or to specify a range for array comprehensions.
311
- class RangeNode
324
+ class RangeNode < Node
312
325
  attr_reader :from, :to
313
326
 
314
327
  def initialize(from, to, exclusive=false)
@@ -327,12 +340,21 @@ module CoffeeScript
327
340
  @exclusive ? '>' : '>='
328
341
  end
329
342
 
330
- def compile(o, fv, tv)
331
- fvv, tvv = @from.compile(o), @to.compile(o)
332
- vars = "#{fv}=#{fvv}, #{tv}=#{tvv}"
333
- compare = "(#{fvv} <= #{tvv} ? #{fv} #{less_operator} #{tv} : #{fv} #{greater_operator} #{tv})"
334
- incr = "(#{fvv} <= #{tvv} ? #{fv} += 1 : #{fv} -= 1)"
335
- "#{vars}; #{compare}; #{incr}"
343
+ def compile_variables(o)
344
+ idt = o[:indent]
345
+ @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable
346
+ from_val, to_val = @from.compile(o), @to.compile(o)
347
+ write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}")
348
+ end
349
+
350
+ def compile_node(o)
351
+ idx, step = o.delete(:index), o.delete(:step)
352
+ raise SyntaxError, "unexpected range literal" unless idx
353
+ vars = "#{idx}=#{@from_var}"
354
+ step = step ? step.compile(o) : '1'
355
+ compare = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})"
356
+ incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})"
357
+ write("#{vars}; #{compare}; #{incr}")
336
358
  end
337
359
 
338
360
  end
@@ -347,8 +369,7 @@ module CoffeeScript
347
369
  @range = range
348
370
  end
349
371
 
350
- def compile(o={})
351
- o = super(o)
372
+ def compile_node(o)
352
373
  from = @range.from.compile(o)
353
374
  to = @range.to.compile(o)
354
375
  plus_part = @range.exclusive? ? '' : ' + 1'
@@ -359,8 +380,7 @@ module CoffeeScript
359
380
  # Setting the value of a local variable, or the value of an object property.
360
381
  class AssignNode < Node
361
382
  PROTO_ASSIGN = /\A(\S+)\.prototype/
362
-
363
- custom_return
383
+ LEADING_DOT = /\A\./
364
384
 
365
385
  attr_reader :variable, :value, :context
366
386
 
@@ -368,21 +388,26 @@ module CoffeeScript
368
388
  @variable, @value, @context = variable, value, context
369
389
  end
370
390
 
371
- def line_ending
372
- @value.custom_assign? ? '' : ';'
373
- end
374
-
375
- def compile(o={})
376
- o = super(o)
391
+ def compile_node(o)
392
+ return compile_splice(o) if @variable.properties.last.is_a?(SliceNode)
377
393
  name = @variable.compile(o)
378
- last = @variable.last.to_s
394
+ last = @variable.last.to_s.sub(LEADING_DOT, '')
379
395
  proto = name[PROTO_ASSIGN, 1]
380
- o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto)
396
+ o = o.merge(:last_assign => last, :proto_assign => proto)
397
+ o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER)
381
398
  return write("#{name}: #{@value.compile(o)}") if @context == :object
382
399
  o[:scope].find(name) unless @variable.properties?
383
- return write(@value.compile(o)) if @value.custom_assign?
384
400
  val = "#{name} = #{@value.compile(o)}"
385
- write(o[:return] && !@value.custom_return? ? "return (#{val})" : val)
401
+ write(o[:return] ? "#{o[:indent]}return (#{val})" : val)
402
+ end
403
+
404
+ def compile_splice(o)
405
+ var = @variable.compile(o.merge(:only_first => true))
406
+ range = @variable.properties.last.range
407
+ plus = range.exclusive? ? '' : ' + 1'
408
+ from = range.from.compile(o)
409
+ to = "#{range.to.compile(o)} - #{from}#{plus}"
410
+ write("#{var}.splice.apply(#{var}, [#{from}, #{to}].concat(#{@value.compile(o)}))")
386
411
  end
387
412
  end
388
413
 
@@ -390,31 +415,30 @@ module CoffeeScript
390
415
  # CoffeeScript operations into their JavaScript equivalents.
391
416
  class OpNode < Node
392
417
  CONVERSIONS = {
393
- "==" => "===",
394
- "!=" => "!==",
395
- 'and' => '&&',
396
- 'or' => '||',
397
- 'is' => '===',
398
- "isnt" => "!==",
399
- 'not' => '!'
418
+ :== => "===",
419
+ :'!=' => "!==",
420
+ :and => '&&',
421
+ :or => '||',
422
+ :is => '===',
423
+ :isnt => "!==",
424
+ :not => '!'
400
425
  }
401
- CONDITIONALS = ['||=', '&&=']
402
- PREFIX_OPERATORS = ['typeof', 'delete']
426
+ CONDITIONALS = [:'||=', :'&&=']
427
+ PREFIX_OPERATORS = [:typeof, :delete]
403
428
 
404
429
  attr_reader :operator, :first, :second
405
430
 
406
431
  def initialize(operator, first, second=nil, flip=false)
407
432
  @first, @second, @flip = first, second, flip
408
- @operator = CONVERSIONS[operator] || operator
433
+ @operator = CONVERSIONS[operator.to_sym] || operator
409
434
  end
410
435
 
411
436
  def unary?
412
437
  @second.nil?
413
438
  end
414
439
 
415
- def compile(o={})
416
- o = super(o)
417
- return write(compile_conditional(o)) if CONDITIONALS.include?(@operator)
440
+ def compile_node(o)
441
+ return write(compile_conditional(o)) if CONDITIONALS.include?(@operator.to_sym)
418
442
  return write(compile_unary(o)) if unary?
419
443
  write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
420
444
  end
@@ -426,7 +450,7 @@ module CoffeeScript
426
450
  end
427
451
 
428
452
  def compile_unary(o)
429
- space = PREFIX_OPERATORS.include?(@operator.to_s) ? ' ' : ''
453
+ space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
430
454
  parts = [@operator.to_s, space, @first.compile(o)]
431
455
  parts.reverse! if @flip
432
456
  parts.join('')
@@ -442,18 +466,53 @@ module CoffeeScript
442
466
  @body = body
443
467
  end
444
468
 
445
- def compile(o={})
446
- o = super(o)
447
- o[:scope] = Scope.new(o[:scope])
448
- o[:return] = true
449
- indent = o[:indent]
450
- o[:indent] += TAB
451
- o.delete(:assign)
469
+ def compile_node(o)
470
+ shared_scope = o.delete(:shared_scope)
471
+ indent = o[:indent]
472
+ o[:scope] = shared_scope || Scope.new(o[:scope], @body)
473
+ o[:return] = true
474
+ o[:top] = true
475
+ o[:indent] += TAB
452
476
  o.delete(:no_wrap)
477
+ name = o.delete(:immediate_assign)
478
+ if @params.last.is_a?(ParamSplatNode)
479
+ splat = @params.pop
480
+ splat.index = @params.length
481
+ @body.unshift(splat)
482
+ end
453
483
  @params.each {|id| o[:scope].parameter(id.to_s) }
454
- code = @body.compile(o, :code)
455
- write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
484
+ code = @body.compile_with_declarations(o)
485
+ name_part = name ? " #{name}" : ''
486
+ write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
487
+ end
488
+ end
489
+
490
+ # A parameter splat in a function definition.
491
+ class ParamSplatNode < Node
492
+ attr_accessor :index
493
+ attr_reader :name
494
+
495
+ def initialize(name)
496
+ @name = name
497
+ end
498
+
499
+ def compile_node(o={})
500
+ o[:scope].find(@name)
501
+ write("#{@name} = Array.prototype.slice.call(arguments, #{@index})")
502
+ end
503
+ end
504
+
505
+ class ArgSplatNode < Node
506
+ attr_reader :value
507
+
508
+ def initialize(value)
509
+ @value = value
510
+ end
511
+
512
+ def compile_node(o={})
513
+ write(@value.compile(o))
456
514
  end
515
+
457
516
  end
458
517
 
459
518
  # An object literal.
@@ -464,13 +523,20 @@ module CoffeeScript
464
523
  @properties = properties
465
524
  end
466
525
 
467
- def compile(o={})
468
- o = super(o)
526
+ # All the mucking about with commas is to make sure that CommentNodes and
527
+ # AssignNodes get interleaved correctly, with no trailing commas or
528
+ # commas affixed to comments. TODO: Extract this and add it to ArrayNode.
529
+ def compile_node(o)
469
530
  indent = o[:indent]
470
531
  o[:indent] += TAB
471
- props = @properties.map { |prop|
472
- joiner = prop == @properties.last ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n"
473
- o[:indent] + prop.compile(o) + joiner
532
+ joins = Hash.new("\n")
533
+ non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
534
+ non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
535
+ props = @properties.map { |prop|
536
+ join = joins[prop]
537
+ join = '' if prop == @properties.last
538
+ idt = prop.is_a?(CommentNode) ? '' : o[:indent]
539
+ "#{idt}#{prop.compile(o)}#{join}"
474
540
  }.join('')
475
541
  write("{\n#{props}\n#{indent}}")
476
542
  end
@@ -484,13 +550,15 @@ module CoffeeScript
484
550
  @objects = objects
485
551
  end
486
552
 
487
- def compile(o={})
488
- o = super(o)
553
+ def compile_node(o)
554
+ indent = o[:indent]
555
+ o[:indent] += TAB
489
556
  objects = @objects.map { |obj|
490
- joiner = obj.is_a?(CommentNode) ? "\n#{o[:indent] + TAB}" : obj == @objects.last ? '' : ', '
491
- obj.compile(o.merge(:indent => o[:indent] + TAB)) + joiner
557
+ code = obj.compile(o)
558
+ obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
559
+ obj == @objects.last ? code : "#{code}, "
492
560
  }.join('')
493
- ending = objects.include?("\n") ? "\n#{o[:indent]}]" : ']'
561
+ ending = objects.include?("\n") ? "\n#{indent}]" : ']'
494
562
  write("[#{objects}#{ending}")
495
563
  end
496
564
  end
@@ -506,16 +574,14 @@ module CoffeeScript
506
574
  @condition, @body = condition, body
507
575
  end
508
576
 
509
- def line_ending
510
- ''
511
- end
512
-
513
- def compile(o={})
514
- o = super(o)
515
- o.delete(:return)
516
- indent = o[:indent] + TAB
517
- cond = @condition.compile(o)
518
- write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}")
577
+ def compile_node(o)
578
+ returns = o.delete(:return)
579
+ indent = o[:indent]
580
+ o[:indent] += TAB
581
+ o[:top] = true
582
+ cond = @condition.compile(o)
583
+ post = returns ? "\n#{indent}return null;" : ''
584
+ write("#{indent}while (#{cond}) {\n#{@body.compile(o)}\n#{indent}}#{post}")
519
585
  end
520
586
  end
521
587
 
@@ -525,74 +591,70 @@ module CoffeeScript
525
591
  # the current index of the loop as a second parameter.
526
592
  class ForNode < Node
527
593
  statement
528
- custom_return
529
- custom_assign
530
-
531
- attr_reader :body, :source, :name, :filter, :index
532
594
 
533
- def initialize(body, source, name, filter, index=nil)
534
- @body, @source, @name, @filter, @index = body, source, name, filter, index
535
- end
595
+ attr_reader :body, :source, :name, :index, :filter, :step
536
596
 
537
- def line_ending
538
- ''
597
+ def initialize(body, source, name, index=nil)
598
+ @body, @name, @index = body, name, index
599
+ @source = source[:source]
600
+ @filter = source[:filter]
601
+ @step = source[:step]
539
602
  end
540
603
 
541
- def compile(o={})
542
- o = super(o)
604
+ def compile_node(o)
605
+ top_level = o.delete(:top) && !o[:return]
543
606
  range = @source.is_a?(RangeNode)
544
607
  scope = o[:scope]
545
608
  name_found = scope.find(@name)
546
609
  index_found = @index && scope.find(@index)
547
610
  svar = scope.free_variable
548
- ivar = range ? name : scope.free_variable
549
- lvar = scope.free_variable
550
- rvar = scope.free_variable
551
- index_name = @index ? @index : nil
611
+ ivar = range ? name : @index ? @index : scope.free_variable
612
+ rvar = scope.free_variable unless top_level
613
+ tvar = scope.free_variable
552
614
  if range
553
- source_part = ''
554
- var_part = ''
555
- index_part = ''
615
+ body_dent = o[:indent] + TAB
616
+ var_part, pre_cond, post_cond = '', '', ''
556
617
  index_var = scope.free_variable
557
- for_part = "#{index_var}=0, #{@source.compile(o, ivar, lvar)}, #{index_var}++"
618
+ source_part = @source.compile_variables(o)
619
+ for_part = "#{index_var}=0, #{@source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
558
620
  else
559
621
  index_var = nil
560
- source_part = "#{svar} = #{@source.compile(o)};\n#{o[:indent]}"
561
- for_part = "#{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
562
- var_part = "\n#{o[:indent] + TAB}#{@name} = #{svar}[#{ivar}];"
563
- index_part = @index ? "\n#{o[:indent] + TAB}#{index_name} = #{ivar};" : ''
622
+ body_dent = o[:indent] + TAB + TAB
623
+ source_part = "#{o[:indent]}#{svar} = #{@source.compile(o)};\n#{o[:indent]}"
624
+ for_part = "#{ivar} in #{svar}"
625
+ pre_cond = "\n#{o[:indent] + TAB}if (#{svar}.hasOwnProperty(#{ivar})) {"
626
+ var_part = "\n#{body_dent}#{@name} = #{svar}[#{ivar}];"
627
+ post_cond = "\n#{o[:indent] + TAB}}"
564
628
  end
565
629
  body = @body
566
- suffix = ';'
567
- set_result = "#{rvar} = [];\n#{o[:indent]}"
568
- save_result = "#{rvar}[#{index_var || ivar}] = "
569
- return_result = rvar
570
-
571
- if o[:return] || o[:assign]
572
- return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign]
630
+ set_result = rvar ? "#{rvar} = [];\n#{o[:indent]}" : ''
631
+ return_result = rvar || ''
632
+ temp_var = ValueNode.new(LiteralNode.new(tvar))
633
+ if top_level
634
+ body = Expressions.wrap(body)
635
+ else
636
+ body = Expressions.wrap(
637
+ AssignNode.new(temp_var, @body.unwrap),
638
+ CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var])
639
+ )
640
+ end
641
+ if o[:return]
573
642
  return_result = "return #{return_result}" if o[:return]
574
- if @filter
575
- body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body])
576
- body = IfNode.new(@filter, body, nil, :statement => true)
577
- save_result = ''
578
- suffix = ''
579
- end
643
+ o.delete(:return)
644
+ body = IfNode.new(@filter, body, nil, :statement => true) if @filter
580
645
  elsif @filter
581
- body = IfNode.new(@filter, @body)
646
+ body = Expressions.wrap(IfNode.new(@filter, @body))
582
647
  end
583
648
 
584
- return_result = "\n#{o[:indent]}#{return_result};"
585
- indent = o[:indent] + TAB
586
- body = body.compile(o.merge(:indent => indent))
587
- write("#{source_part}#{set_result}for (#{for_part}) {#{var_part}#{index_part}\n#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}")
649
+ return_result = "\n#{o[:indent]}#{return_result};" unless top_level
650
+ body = body.compile(o.merge(:indent => body_dent, :top => true))
651
+ write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body}#{post_cond}\n#{o[:indent]}}#{return_result}")
588
652
  end
589
653
  end
590
654
 
591
655
  # A try/catch/finally block.
592
656
  class TryNode < Node
593
657
  statement
594
- custom_return
595
- custom_assign
596
658
 
597
659
  attr_reader :try, :error, :recovery, :finally
598
660
 
@@ -600,24 +662,20 @@ module CoffeeScript
600
662
  @try, @error, @recovery, @finally = try, error, recovery, finally
601
663
  end
602
664
 
603
- def line_ending
604
- ''
605
- end
606
-
607
- def compile(o={})
608
- o = super(o)
665
+ def compile_node(o)
609
666
  indent = o[:indent]
610
667
  o[:indent] += TAB
668
+ o[:top] = true
611
669
  error_part = @error ? " (#{@error}) " : ' '
612
670
  catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}"
613
- finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:assign => nil, :return => nil))}\n#{indent}}"
614
- write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}")
671
+ finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{indent}}"
672
+ write("#{indent}try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}")
615
673
  end
616
674
  end
617
675
 
618
676
  # Throw an exception.
619
677
  class ThrowNode < Node
620
- statement
678
+ statement_only
621
679
 
622
680
  attr_reader :expression
623
681
 
@@ -625,9 +683,22 @@ module CoffeeScript
625
683
  @expression = expression
626
684
  end
627
685
 
628
- def compile(o={})
629
- o = super(o)
630
- write("throw #{@expression.compile(o)}")
686
+ def compile_node(o)
687
+ write("#{o[:indent]}throw #{@expression.compile(o)};")
688
+ end
689
+ end
690
+
691
+ # Check an expression for existence (meaning not null or undefined).
692
+ class ExistenceNode < Node
693
+ attr_reader :expression
694
+
695
+ def initialize(expression)
696
+ @expression = expression
697
+ end
698
+
699
+ def compile_node(o)
700
+ val = @expression.compile(o)
701
+ write("(typeof #{val} !== \"undefined\" && #{val} !== null)")
631
702
  end
632
703
  end
633
704
 
@@ -637,25 +708,12 @@ module CoffeeScript
637
708
  class ParentheticalNode < Node
638
709
  attr_reader :expressions
639
710
 
640
- def initialize(expressions)
711
+ def initialize(expressions, line=nil)
641
712
  @expressions = expressions.unwrap
713
+ @line = line
642
714
  end
643
715
 
644
- def statement?
645
- @expressions.unwrap.statement?
646
- end
647
-
648
- def custom_assign?
649
- @expressions.custom_assign?
650
- end
651
-
652
- def custom_return?
653
- @expressions.custom_return?
654
- end
655
-
656
- def compile(o={})
657
- raise SyntaxError, "parentheses can't be wrapped around a statement" if statement?
658
- o = super(o)
716
+ def compile_node(o)
659
717
  compiled = @expressions.compile(o)
660
718
  compiled = compiled[0...-1] if compiled[-1..-1] == ';'
661
719
  write("(#{compiled})")
@@ -683,6 +741,11 @@ module CoffeeScript
683
741
  self
684
742
  end
685
743
 
744
+ def force_statement
745
+ @tags[:statement] = true
746
+ self
747
+ end
748
+
686
749
  # Rewrite a chain of IfNodes with their switch condition for equality.
687
750
  def rewrite_condition(expression)
688
751
  @condition = OpNode.new("is", expression, @condition)
@@ -691,8 +754,8 @@ module CoffeeScript
691
754
  end
692
755
 
693
756
  # Rewrite a chain of IfNodes to add a default case as the final else.
694
- def add_else(expressions)
695
- chain? ? @else_body.add_else(expressions) : @else_body = expressions
757
+ def add_else(exprs)
758
+ chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap)
696
759
  self
697
760
  end
698
761
 
@@ -707,20 +770,7 @@ module CoffeeScript
707
770
  @is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
708
771
  end
709
772
 
710
- def custom_return?
711
- statement?
712
- end
713
-
714
- def custom_assign?
715
- statement?
716
- end
717
-
718
- def line_ending
719
- statement? ? '' : ';'
720
- end
721
-
722
- def compile(o={})
723
- o = super(o)
773
+ def compile_node(o)
724
774
  write(statement? ? compile_statement(o) : compile_ternary(o))
725
775
  end
726
776
 
@@ -728,14 +778,16 @@ module CoffeeScript
728
778
  # force sub-else bodies into statement form.
729
779
  def compile_statement(o)
730
780
  indent = o[:indent]
781
+ child = o.delete(:chain_child)
731
782
  cond_o = o.dup
732
- cond_o.delete(:assign)
733
783
  cond_o.delete(:return)
734
784
  o[:indent] += TAB
735
- if_part = "if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}"
785
+ o[:top] = true
786
+ if_dent = child ? '' : indent
787
+ if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}"
736
788
  return if_part unless @else_body
737
789
  else_part = chain? ?
738
- " else #{@else_body.compile(o.merge(:indent => indent))}" :
790
+ " else #{@else_body.compile(o.merge(:indent => indent, :chain_child => true))}" :
739
791
  " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{indent}}"
740
792
  if_part + else_part
741
793
  end