coffee-script 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|