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.
- data/coffee-script.gemspec +3 -3
- data/examples/code.coffee +43 -43
- data/examples/documents.coffee +17 -17
- data/examples/poignant.coffee +22 -22
- data/examples/underscore.coffee +457 -416
- data/lib/coffee-script.rb +2 -1
- data/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +23 -4
- data/lib/coffee_script/command_line.rb +5 -4
- data/lib/coffee_script/grammar.y +99 -78
- data/lib/coffee_script/lexer.rb +91 -47
- data/lib/coffee_script/narwhal/coffee-script.coffee +16 -15
- data/lib/coffee_script/narwhal/{js → lib}/coffee-script.js +22 -17
- data/lib/coffee_script/narwhal/{js → lib/coffee-script}/loader.js +6 -4
- data/lib/coffee_script/narwhal/loader.coffee +3 -3
- data/lib/coffee_script/nodes.rb +307 -255
- data/lib/coffee_script/parse_error.rb +3 -2
- data/lib/coffee_script/parser.output +10284 -9773
- data/lib/coffee_script/parser.rb +1286 -1141
- data/lib/coffee_script/rewriter.rb +208 -0
- data/lib/coffee_script/scope.rb +15 -10
- data/package.json +9 -0
- metadata +6 -6
- data/lib/coffee_script/narwhal/js/launcher.js +0 -3
- data/lib/coffee_script/narwhal/launcher.coffee +0 -1
data/lib/coffee_script/lexer.rb
CHANGED
@@ -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(
|
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
|
27
|
-
|
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
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
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,
|
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.
|
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
|
-
|
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
|
-
|
218
|
+
i = 0
|
168
219
|
loop do
|
169
|
-
|
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
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
4
|
-
//
|
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,
|
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
|
-
|
26
|
-
for (
|
27
|
-
|
28
|
-
|
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
|
-
|
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){
|
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
|
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('
|
11
|
-
return (factories[topId] =
|
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('
|
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
|
|
data/lib/coffee_script/nodes.rb
CHANGED
@@ -11,17 +11,11 @@ module CoffeeScript
|
|
11
11
|
class_eval "def statement?; true; end"
|
12
12
|
end
|
13
13
|
|
14
|
-
# Tag this node as
|
15
|
-
#
|
16
|
-
def self.
|
17
|
-
|
18
|
-
|
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;
|
38
|
-
def
|
39
|
-
def
|
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(
|
53
|
-
|
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
|
-
|
78
|
-
|
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
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
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 =
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
110
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|
157
|
-
@expression.
|
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
|
-
|
175
|
+
statement_only
|
172
176
|
|
173
177
|
def initialize(lines)
|
174
178
|
@lines = lines.value
|
175
179
|
end
|
176
180
|
|
177
|
-
def
|
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
|
210
|
-
|
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]
|
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
|
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
|
264
|
-
|
265
|
-
|
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
|
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
|
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
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
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
|
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
|
372
|
-
@
|
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(:
|
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]
|
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
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
418
|
+
:== => "===",
|
419
|
+
:'!=' => "!==",
|
420
|
+
:and => '&&',
|
421
|
+
:or => '||',
|
422
|
+
:is => '===',
|
423
|
+
:isnt => "!==",
|
424
|
+
:not => '!'
|
400
425
|
}
|
401
|
-
CONDITIONALS = ['||=', '&&=']
|
402
|
-
PREFIX_OPERATORS = [
|
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
|
416
|
-
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.
|
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
|
446
|
-
|
447
|
-
|
448
|
-
o[:
|
449
|
-
|
450
|
-
o[:
|
451
|
-
o
|
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.
|
455
|
-
|
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
|
-
|
468
|
-
|
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
|
-
|
472
|
-
|
473
|
-
|
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
|
488
|
-
|
553
|
+
def compile_node(o)
|
554
|
+
indent = o[:indent]
|
555
|
+
o[:indent] += TAB
|
489
556
|
objects = @objects.map { |obj|
|
490
|
-
|
491
|
-
obj.
|
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#{
|
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
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
indent
|
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
|
-
|
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
|
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
|
542
|
-
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
|
-
|
550
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
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
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
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
|
-
|
575
|
-
|
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
|
-
|
586
|
-
|
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
|
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(:
|
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
|
-
|
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
|
629
|
-
o
|
630
|
-
|
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
|
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(
|
695
|
-
chain? ? @else_body.add_else(
|
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
|
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
|
-
|
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
|