coffee-script 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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