coffee-script 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/coffee-script.gemspec +2 -2
- data/lib/coffee-script.rb +1 -1
- data/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +34 -39
- data/lib/coffee_script/grammar.y +12 -8
- data/lib/coffee_script/lexer.rb +3 -2
- data/lib/coffee_script/narwhal/lib/coffee-script.js +14 -11
- data/lib/coffee_script/nodes.rb +191 -119
- data/lib/coffee_script/parser.rb +1289 -1222
- data/lib/coffee_script/scope.rb +6 -5
- data/lib/coffee_script/value.rb +14 -0
- data/package.json +1 -1
- metadata +2 -2
data/coffee-script.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'coffee-script'
|
3
|
-
s.version = '0.2.
|
4
|
-
s.date = '2010-1-
|
3
|
+
s.version = '0.2.6' # Keep version in sync with coffee-script.rb
|
4
|
+
s.date = '2010-1-17'
|
5
5
|
|
6
6
|
s.homepage = "http://jashkenas.github.com/coffee-script/"
|
7
7
|
s.summary = "The CoffeeScript Compiler"
|
data/lib/coffee-script.rb
CHANGED
@@ -10,7 +10,7 @@ require "coffee_script/parse_error"
|
|
10
10
|
# Namespace for all CoffeeScript internal classes.
|
11
11
|
module CoffeeScript
|
12
12
|
|
13
|
-
VERSION = '0.2.
|
13
|
+
VERSION = '0.2.6' # Keep in sync with the gemspec.
|
14
14
|
|
15
15
|
# Compile a script (String or IO) to JavaScript.
|
16
16
|
def self.compile(script, options={})
|
@@ -230,6 +230,39 @@
|
|
230
230
|
<key>name</key>
|
231
231
|
<string>comment.line.coffee</string>
|
232
232
|
</dict>
|
233
|
+
<dict>
|
234
|
+
<key>begin</key>
|
235
|
+
<string>(?<=[=(:]|^|return)\s*(/)(?![/*+{}?])</string>
|
236
|
+
<key>beginCaptures</key>
|
237
|
+
<dict>
|
238
|
+
<key>1</key>
|
239
|
+
<dict>
|
240
|
+
<key>name</key>
|
241
|
+
<string>punctuation.definition.string.begin.coffee</string>
|
242
|
+
</dict>
|
243
|
+
</dict>
|
244
|
+
<key>end</key>
|
245
|
+
<string>(/)[igm]*</string>
|
246
|
+
<key>endCaptures</key>
|
247
|
+
<dict>
|
248
|
+
<key>1</key>
|
249
|
+
<dict>
|
250
|
+
<key>name</key>
|
251
|
+
<string>punctuation.definition.string.end.coffee</string>
|
252
|
+
</dict>
|
253
|
+
</dict>
|
254
|
+
<key>name</key>
|
255
|
+
<string>string.regexp.coffee</string>
|
256
|
+
<key>patterns</key>
|
257
|
+
<array>
|
258
|
+
<dict>
|
259
|
+
<key>match</key>
|
260
|
+
<string>\\.</string>
|
261
|
+
<key>name</key>
|
262
|
+
<string>constant.character.escape.coffee</string>
|
263
|
+
</dict>
|
264
|
+
</array>
|
265
|
+
</dict>
|
233
266
|
<dict>
|
234
267
|
<key>match</key>
|
235
268
|
<string>\b(break|by|catch|continue|else|finally|for|in|of|if|return|switch|then|throw|try|unless|when|while)\b</string>
|
@@ -238,7 +271,7 @@
|
|
238
271
|
</dict>
|
239
272
|
<dict>
|
240
273
|
<key>match</key>
|
241
|
-
<string>\b([a-zA-Z$_](\w|\$|:|\.)*
|
274
|
+
<string>\b([a-zA-Z$_](\w|\$|:|\.)*\s*(?=\:))</string>
|
242
275
|
<key>name</key>
|
243
276
|
<string>variable.assignment.coffee</string>
|
244
277
|
<key>captures</key>
|
@@ -248,11 +281,6 @@
|
|
248
281
|
<key>name</key>
|
249
282
|
<string>entity.name.function.coffee</string>
|
250
283
|
</dict>
|
251
|
-
<key>3</key>
|
252
|
-
<dict>
|
253
|
-
<key>name</key>
|
254
|
-
<string>keyword.operator.coffee</string>
|
255
|
-
</dict>
|
256
284
|
</dict>
|
257
285
|
</dict>
|
258
286
|
<dict>
|
@@ -297,39 +325,6 @@
|
|
297
325
|
<key>name</key>
|
298
326
|
<string>constant.language.coffee</string>
|
299
327
|
</dict>
|
300
|
-
<dict>
|
301
|
-
<key>begin</key>
|
302
|
-
<string>(?<=[=(:]|^|return)\s*(/)(?![/*+{}?])</string>
|
303
|
-
<key>beginCaptures</key>
|
304
|
-
<dict>
|
305
|
-
<key>1</key>
|
306
|
-
<dict>
|
307
|
-
<key>name</key>
|
308
|
-
<string>punctuation.definition.string.begin.coffee</string>
|
309
|
-
</dict>
|
310
|
-
</dict>
|
311
|
-
<key>end</key>
|
312
|
-
<string>(/)[igm]*</string>
|
313
|
-
<key>endCaptures</key>
|
314
|
-
<dict>
|
315
|
-
<key>1</key>
|
316
|
-
<dict>
|
317
|
-
<key>name</key>
|
318
|
-
<string>punctuation.definition.string.end.coffee</string>
|
319
|
-
</dict>
|
320
|
-
</dict>
|
321
|
-
<key>name</key>
|
322
|
-
<string>string.regexp.coffee</string>
|
323
|
-
<key>patterns</key>
|
324
|
-
<array>
|
325
|
-
<dict>
|
326
|
-
<key>match</key>
|
327
|
-
<string>\\.</string>
|
328
|
-
<key>name</key>
|
329
|
-
<string>constant.character.escape.coffee</string>
|
330
|
-
</dict>
|
331
|
-
</array>
|
332
|
-
</dict>
|
333
328
|
<dict>
|
334
329
|
<key>match</key>
|
335
330
|
<string>\;</string>
|
data/lib/coffee_script/grammar.y
CHANGED
@@ -16,6 +16,7 @@ token ARGUMENTS
|
|
16
16
|
token NEWLINE
|
17
17
|
token COMMENT
|
18
18
|
token JS
|
19
|
+
token THIS
|
19
20
|
token INDENT OUTDENT
|
20
21
|
|
21
22
|
# Declare order of operations.
|
@@ -37,7 +38,7 @@ prechigh
|
|
37
38
|
right WHEN LEADING_WHEN IN OF BY
|
38
39
|
right THROW FOR NEW SUPER
|
39
40
|
left EXTENDS
|
40
|
-
left ASSIGN '||=' '&&='
|
41
|
+
left ASSIGN '||=' '&&=' '?='
|
41
42
|
right RETURN
|
42
43
|
right '=>' '==>' UNLESS IF ELSE WHILE
|
43
44
|
preclow
|
@@ -102,12 +103,12 @@ rule
|
|
102
103
|
| BREAK { result = LiteralNode.new(val[0]) }
|
103
104
|
| CONTINUE { result = LiteralNode.new(val[0]) }
|
104
105
|
| ARGUMENTS { result = LiteralNode.new(val[0]) }
|
105
|
-
| TRUE { result = LiteralNode.new(true) }
|
106
|
-
| FALSE { result = LiteralNode.new(false) }
|
107
|
-
| YES { result = LiteralNode.new(true) }
|
108
|
-
| NO { result = LiteralNode.new(false) }
|
109
|
-
| ON { result = LiteralNode.new(true) }
|
110
|
-
| OFF { result = LiteralNode.new(false) }
|
106
|
+
| TRUE { result = LiteralNode.new(Value.new(true)) }
|
107
|
+
| FALSE { result = LiteralNode.new(Value.new(false)) }
|
108
|
+
| YES { result = LiteralNode.new(Value.new(true)) }
|
109
|
+
| NO { result = LiteralNode.new(Value.new(false)) }
|
110
|
+
| ON { result = LiteralNode.new(Value.new(true)) }
|
111
|
+
| OFF { result = LiteralNode.new(Value.new(false)) }
|
111
112
|
;
|
112
113
|
|
113
114
|
# Assignment to a variable (or index).
|
@@ -178,6 +179,7 @@ rule
|
|
178
179
|
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
179
180
|
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
180
181
|
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
182
|
+
| Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
181
183
|
|
182
184
|
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
183
185
|
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
@@ -186,6 +188,7 @@ rule
|
|
186
188
|
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
187
189
|
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
188
190
|
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
191
|
+
| Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
189
192
|
|
190
193
|
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
191
194
|
| Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
@@ -235,6 +238,7 @@ rule
|
|
235
238
|
| Range { result = ValueNode.new(val[0]) }
|
236
239
|
| Value Accessor { result = val[0] << val[1] }
|
237
240
|
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
|
241
|
+
| THIS { result = ValueNode.new(ThisNode.new) }
|
238
242
|
;
|
239
243
|
|
240
244
|
# Accessing into an object or array, through dot or index notation.
|
@@ -292,7 +296,7 @@ rule
|
|
292
296
|
|
293
297
|
# Calling super.
|
294
298
|
Super:
|
295
|
-
SUPER "(" ArgList ")" { result = CallNode.new(
|
299
|
+
SUPER "(" ArgList ")" { result = CallNode.new(Value.new('super'), val[2]) }
|
296
300
|
;
|
297
301
|
|
298
302
|
# The range literal.
|
data/lib/coffee_script/lexer.rb
CHANGED
@@ -13,10 +13,11 @@ module CoffeeScript
|
|
13
13
|
"try", "catch", "finally", "throw",
|
14
14
|
"break", "continue",
|
15
15
|
"for", "in", "of", "by", "where", "while",
|
16
|
+
"delete", "instanceof", "typeof",
|
16
17
|
"switch", "when",
|
17
18
|
"super", "extends",
|
18
19
|
"arguments",
|
19
|
-
"
|
20
|
+
"this"]
|
20
21
|
|
21
22
|
# Token matching regexes.
|
22
23
|
IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/
|
@@ -24,7 +25,7 @@ module CoffeeScript
|
|
24
25
|
STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m
|
25
26
|
HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?(\s*)"{3}|'{3}\n?(.*?)\n?(\s*)'{3})/m
|
26
27
|
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
|
27
|
-
OPERATOR = /\A([
|
28
|
+
OPERATOR = /\A([+\*&|\/\-%=<>:!?]+)/
|
28
29
|
WHITESPACE = /\A([ \t]+)/
|
29
30
|
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
|
30
31
|
CODE = /\A(=?=>)/
|
@@ -20,28 +20,31 @@
|
|
20
20
|
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
|
21
21
|
// command.
|
22
22
|
exports.run = function run(args) {
|
23
|
-
var __a, i, path, result;
|
23
|
+
var __a, __b, i, path, result;
|
24
24
|
if (args.length) {
|
25
25
|
__a = args;
|
26
|
-
for (i=0; i<__a.length; i++) {
|
26
|
+
for (i = 0; i < __a.length; i++) {
|
27
27
|
path = __a[i];
|
28
28
|
exports.evalCS(File.read(path));
|
29
29
|
delete args[i];
|
30
30
|
}
|
31
31
|
return true;
|
32
32
|
}
|
33
|
+
__b = [];
|
33
34
|
while (true) {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
__b.push((function() {
|
36
|
+
try {
|
37
|
+
system.stdout.write('coffee> ').flush();
|
38
|
+
result = exports.evalCS(Readline.readline(), ['--globals']);
|
39
|
+
if (result !== undefined) {
|
40
|
+
return print(result);
|
41
|
+
}
|
42
|
+
} catch (e) {
|
43
|
+
return print(e);
|
39
44
|
}
|
40
|
-
}
|
41
|
-
print(e);
|
42
|
-
}
|
45
|
+
})());
|
43
46
|
}
|
44
|
-
return
|
47
|
+
return __b;
|
45
48
|
};
|
46
49
|
// Compile a given CoffeeScript file into JavaScript.
|
47
50
|
exports.compileFile = function compileFile(path) {
|
data/lib/coffee_script/nodes.rb
CHANGED
@@ -24,6 +24,19 @@ module CoffeeScript
|
|
24
24
|
class_eval "def statement_only?; true; end"
|
25
25
|
end
|
26
26
|
|
27
|
+
# This node needs to know if it's being compiled as a top-level statement,
|
28
|
+
# in order to compile without special expression conversion.
|
29
|
+
def self.top_sensitive
|
30
|
+
class_eval "def top_sensitive?; true; end"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Provide a quick implementation of a children method.
|
34
|
+
def self.children(*attributes)
|
35
|
+
attr_reader(*attributes)
|
36
|
+
attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
|
37
|
+
class_eval "def children; [#{attrs}].flatten.compact; end"
|
38
|
+
end
|
39
|
+
|
27
40
|
def write(code)
|
28
41
|
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
|
29
42
|
code
|
@@ -37,14 +50,18 @@ module CoffeeScript
|
|
37
50
|
@options = o.dup
|
38
51
|
@indent = o[:indent]
|
39
52
|
top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
|
40
|
-
closure = statement? && !statement_only? && !top && !@options[:return]
|
53
|
+
closure = statement? && !statement_only? && !top && !@options[:return] && !self.is_a?(CommentNode)
|
54
|
+
closure &&= !contains? {|n| n.statement_only? }
|
41
55
|
closure ? compile_closure(@options) : compile_node(@options)
|
42
56
|
end
|
43
57
|
|
44
58
|
def compile_closure(o={})
|
45
|
-
indent
|
46
|
-
@indent
|
47
|
-
|
59
|
+
indent = o[:indent]
|
60
|
+
@indent = (o[:indent] = idt(1))
|
61
|
+
pass_this = !o[:closure] && contains? {|node| node.is_a?(ThisNode) }
|
62
|
+
param = pass_this ? '__this' : ''
|
63
|
+
body = compile_node(o.merge(:return => true, :closure => true))
|
64
|
+
"(function(#{param}) {\n#{body}\n#{indent}})(#{pass_this ? 'this' : ''})"
|
48
65
|
end
|
49
66
|
|
50
67
|
# Quick short method for the current indentation level, plus tabbing in.
|
@@ -52,8 +69,18 @@ module CoffeeScript
|
|
52
69
|
@indent + (TAB * tabs)
|
53
70
|
end
|
54
71
|
|
72
|
+
# Does this node, or any of it's children, contain a node of a certain kind?
|
73
|
+
def contains?(&block)
|
74
|
+
children.each do |node|
|
75
|
+
return true if yield(node)
|
76
|
+
return true if node.is_a?(Node) && node.contains?(&block)
|
77
|
+
end
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
55
81
|
# Default implementations of the common node methods.
|
56
82
|
def unwrap; self; end
|
83
|
+
def children; []; end
|
57
84
|
def statement?; false; end
|
58
85
|
def statement_only?; false; end
|
59
86
|
def top_sensitive?; false; end
|
@@ -62,10 +89,10 @@ module CoffeeScript
|
|
62
89
|
# A collection of nodes, each one representing an expression.
|
63
90
|
class Expressions < Node
|
64
91
|
statement
|
65
|
-
|
92
|
+
children :expressions
|
93
|
+
attr_accessor :function
|
66
94
|
|
67
95
|
TRAILING_WHITESPACE = /\s+$/
|
68
|
-
UPPERCASE = /[A-Z]/
|
69
96
|
|
70
97
|
# Wrap up a node as an Expressions, unless it already is.
|
71
98
|
def self.wrap(*nodes)
|
@@ -94,18 +121,17 @@ module CoffeeScript
|
|
94
121
|
@expressions.length == 1 ? @expressions.first : self
|
95
122
|
end
|
96
123
|
|
124
|
+
# Is this an empty block of code?
|
125
|
+
def empty?
|
126
|
+
@expressions.empty?
|
127
|
+
end
|
128
|
+
|
97
129
|
# Is the node last in this block of expressions.
|
98
130
|
def last?(node)
|
99
131
|
@last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
|
100
132
|
node == @expressions[@last_index]
|
101
133
|
end
|
102
134
|
|
103
|
-
# Determine if this is the expressions body within a constructor function.
|
104
|
-
# Constructors are capitalized by CoffeeScript convention.
|
105
|
-
def constructor?(o)
|
106
|
-
o[:top] && o[:last_assign] && o[:last_assign][0..0][UPPERCASE]
|
107
|
-
end
|
108
|
-
|
109
135
|
def compile(o={})
|
110
136
|
o[:scope] ? super(o) : compile_root(o)
|
111
137
|
end
|
@@ -119,7 +145,7 @@ module CoffeeScript
|
|
119
145
|
def compile_root(o={})
|
120
146
|
indent = o[:no_wrap] ? '' : TAB
|
121
147
|
@indent = indent
|
122
|
-
o.merge!(:indent => indent, :scope => Scope.new(nil, self))
|
148
|
+
o.merge!(:indent => indent, :scope => Scope.new(nil, self, nil))
|
123
149
|
code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
|
124
150
|
code.gsub!(TRAILING_WHITESPACE, '')
|
125
151
|
write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();")
|
@@ -129,8 +155,11 @@ module CoffeeScript
|
|
129
155
|
# at the top.
|
130
156
|
def compile_with_declarations(o={})
|
131
157
|
code = compile_node(o)
|
132
|
-
|
133
|
-
|
158
|
+
args = self.contains? {|n| n.is_a?(LiteralNode) && n.arguments? }
|
159
|
+
argv = args && o[:scope].check('arguments') ? '' : 'var '
|
160
|
+
code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
|
161
|
+
code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
|
162
|
+
code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
|
134
163
|
write(code)
|
135
164
|
end
|
136
165
|
|
@@ -145,10 +174,10 @@ module CoffeeScript
|
|
145
174
|
# If it's a statement, the node knows how to return itself.
|
146
175
|
return node.compile(o.merge(:return => true)) if node.statement?
|
147
176
|
# If it's not part of a constructor, we can just return the value of the expression.
|
148
|
-
return "#{idt}return #{node.compile(o)};" unless constructor?
|
177
|
+
return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor?
|
149
178
|
# It's the last line of a constructor, add a safety check.
|
150
179
|
temp = o[:scope].free_variable
|
151
|
-
"#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:
|
180
|
+
"#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};"
|
152
181
|
end
|
153
182
|
|
154
183
|
end
|
@@ -156,17 +185,12 @@ module CoffeeScript
|
|
156
185
|
# Literals are static values that have a Ruby representation, eg.: a string, a number,
|
157
186
|
# true, false, nil, etc.
|
158
187
|
class LiteralNode < Node
|
188
|
+
children :value
|
159
189
|
|
160
190
|
# Values of a literal node that much be treated as a statement -- no
|
161
191
|
# sense returning or assigning them.
|
162
192
|
STATEMENTS = ['break', 'continue']
|
163
193
|
|
164
|
-
# If we get handed a literal reference to an arguments object, convert
|
165
|
-
# it to an array.
|
166
|
-
ARG_ARRAY = 'Array.prototype.slice.call(arguments, 0)'
|
167
|
-
|
168
|
-
attr_reader :value
|
169
|
-
|
170
194
|
# Wrap up a compiler-generated string as a LiteralNode.
|
171
195
|
def self.wrap(string)
|
172
196
|
self.new(Value.new(string))
|
@@ -181,19 +205,21 @@ module CoffeeScript
|
|
181
205
|
end
|
182
206
|
alias_method :statement_only?, :statement?
|
183
207
|
|
208
|
+
def arguments?
|
209
|
+
@value.to_s == 'arguments'
|
210
|
+
end
|
211
|
+
|
184
212
|
def compile_node(o)
|
185
|
-
@value = ARG_ARRAY if @value.to_s.to_sym == :arguments
|
186
213
|
indent = statement? ? idt : ''
|
187
214
|
ending = statement? ? ';' : ''
|
188
|
-
|
215
|
+
"#{indent}#{@value}#{ending}"
|
189
216
|
end
|
190
217
|
end
|
191
218
|
|
192
219
|
# Return an expression, or wrap it in a closure and return it.
|
193
220
|
class ReturnNode < Node
|
194
221
|
statement_only
|
195
|
-
|
196
|
-
attr_reader :expression
|
222
|
+
children :expression
|
197
223
|
|
198
224
|
def initialize(expression)
|
199
225
|
@expression = expression
|
@@ -209,7 +235,7 @@ module CoffeeScript
|
|
209
235
|
# Pass through CoffeeScript comments into JavaScript comments at the
|
210
236
|
# same position.
|
211
237
|
class CommentNode < Node
|
212
|
-
|
238
|
+
statement
|
213
239
|
|
214
240
|
def initialize(lines)
|
215
241
|
@lines = lines.value
|
@@ -225,47 +251,38 @@ module CoffeeScript
|
|
225
251
|
# Node for a function invocation. Takes care of converting super() calls into
|
226
252
|
# calls against the prototype's function of the same name.
|
227
253
|
class CallNode < Node
|
228
|
-
|
254
|
+
children :variable, :arguments
|
229
255
|
|
230
256
|
def initialize(variable, arguments=[])
|
231
257
|
@variable, @arguments = variable, arguments
|
258
|
+
@prefix = ''
|
232
259
|
end
|
233
260
|
|
234
261
|
def new_instance
|
235
|
-
@
|
262
|
+
@prefix = "new "
|
236
263
|
self
|
237
264
|
end
|
238
265
|
|
239
|
-
def super?
|
240
|
-
@variable == :super
|
241
|
-
end
|
242
|
-
|
243
|
-
def prefix
|
244
|
-
@new ? "new " : ''
|
245
|
-
end
|
246
|
-
|
247
|
-
def splat?
|
248
|
-
@arguments.any? {|a| a.is_a?(SplatNode) }
|
249
|
-
end
|
250
|
-
|
251
266
|
def <<(argument)
|
252
267
|
@arguments << argument
|
268
|
+
self
|
253
269
|
end
|
254
270
|
|
255
271
|
# Compile a vanilla function call.
|
256
272
|
def compile_node(o)
|
257
|
-
return write(compile_splat(o)) if
|
273
|
+
return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
|
258
274
|
args = @arguments.map{|a| a.compile(o) }.join(', ')
|
259
|
-
return write(compile_super(args, o)) if super
|
260
|
-
write("#{prefix}#{@variable.compile(o)}(#{args})")
|
275
|
+
return write(compile_super(args, o)) if @variable == 'super'
|
276
|
+
write("#{@prefix}#{@variable.compile(o)}(#{args})")
|
261
277
|
end
|
262
278
|
|
263
279
|
# Compile a call against the superclass's implementation of the current function.
|
264
280
|
def compile_super(args, o)
|
265
|
-
methname = o[:
|
281
|
+
methname = o[:scope].function.name
|
266
282
|
arg_part = args.empty? ? '' : ", #{args}"
|
267
|
-
meth = o[:
|
268
|
-
|
283
|
+
meth = o[:scope].function.proto ?
|
284
|
+
"#{o[:scope].function.proto}.__superClass__.#{methname}" :
|
285
|
+
"#{methname}.__superClass__.constructor"
|
269
286
|
"#{meth}.call(this#{arg_part})"
|
270
287
|
end
|
271
288
|
|
@@ -278,15 +295,23 @@ module CoffeeScript
|
|
278
295
|
code = arg.is_a?(SplatNode) ? code : "[#{code}]"
|
279
296
|
arg.equal?(@arguments.first) ? code : ".concat(#{code})"
|
280
297
|
end
|
281
|
-
"#{prefix}#{meth}.apply(#{obj}, #{args.join('')})"
|
298
|
+
"#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
|
299
|
+
end
|
300
|
+
|
301
|
+
# If the code generation wished to use the result of a function call
|
302
|
+
# in multiple places, ensure that the function is only ever called once.
|
303
|
+
def compile_reference(o)
|
304
|
+
reference = o[:scope].free_variable
|
305
|
+
call = ParentheticalNode.new(AssignNode.new(reference, self))
|
306
|
+
return call, reference
|
282
307
|
end
|
283
308
|
end
|
284
309
|
|
285
310
|
# Node to extend an object's prototype with an ancestor object.
|
286
311
|
# After goog.inherits from the Closure Library.
|
287
312
|
class ExtendsNode < Node
|
313
|
+
children :sub_object, :super_object
|
288
314
|
statement
|
289
|
-
attr_reader :sub_object, :super_object
|
290
315
|
|
291
316
|
def initialize(sub_object, super_object)
|
292
317
|
@sub_object, @super_object = sub_object, super_object
|
@@ -307,7 +332,8 @@ module CoffeeScript
|
|
307
332
|
|
308
333
|
# A value, indexed or dotted into, or vanilla.
|
309
334
|
class ValueNode < Node
|
310
|
-
|
335
|
+
children :base, :properties
|
336
|
+
attr_reader :last, :source
|
311
337
|
|
312
338
|
def initialize(base, properties=[])
|
313
339
|
@base, @properties = base, properties
|
@@ -334,6 +360,10 @@ module CoffeeScript
|
|
334
360
|
properties? && @properties.last.is_a?(SliceNode)
|
335
361
|
end
|
336
362
|
|
363
|
+
def unwrap
|
364
|
+
@properties.empty? ? @base : self
|
365
|
+
end
|
366
|
+
|
337
367
|
# Values are statements if their base is a statement.
|
338
368
|
def statement?
|
339
369
|
@base.is_a?(Node) && @base.statement? && !properties?
|
@@ -352,7 +382,7 @@ module CoffeeScript
|
|
352
382
|
# A dotted accessor into a part of a value, or the :: shorthand for
|
353
383
|
# an accessor into the object's prototype.
|
354
384
|
class AccessorNode < Node
|
355
|
-
|
385
|
+
children :name
|
356
386
|
|
357
387
|
def initialize(name, prototype=false)
|
358
388
|
@name, @prototype = name, prototype
|
@@ -366,7 +396,7 @@ module CoffeeScript
|
|
366
396
|
|
367
397
|
# An indexed accessor into a part of an array or object.
|
368
398
|
class IndexNode < Node
|
369
|
-
|
399
|
+
children :index
|
370
400
|
|
371
401
|
def initialize(index)
|
372
402
|
@index = index
|
@@ -377,10 +407,20 @@ module CoffeeScript
|
|
377
407
|
end
|
378
408
|
end
|
379
409
|
|
410
|
+
# A node to represent a reference to "this". Needs to be transformed into a
|
411
|
+
# reference to the correct value of "this", when used within a closure wrapper.
|
412
|
+
class ThisNode < Node
|
413
|
+
|
414
|
+
def compile_node(o)
|
415
|
+
write(o[:closure] ? "__this" : "this")
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
419
|
+
|
380
420
|
# A range literal. Ranges can be used to extract portions (slices) of arrays,
|
381
421
|
# or to specify a range for array comprehensions.
|
382
422
|
class RangeNode < Node
|
383
|
-
|
423
|
+
children :from, :to
|
384
424
|
|
385
425
|
def initialize(from, to, exclusive=false)
|
386
426
|
@from, @to, @exclusive = from, to, exclusive
|
@@ -423,7 +463,7 @@ module CoffeeScript
|
|
423
463
|
# specifies the index of the end of the slice (just like the first parameter)
|
424
464
|
# is the index of the beginning.
|
425
465
|
class SliceNode < Node
|
426
|
-
|
466
|
+
children :range
|
427
467
|
|
428
468
|
def initialize(range)
|
429
469
|
@range = range
|
@@ -439,29 +479,35 @@ module CoffeeScript
|
|
439
479
|
|
440
480
|
# Setting the value of a local variable, or the value of an object property.
|
441
481
|
class AssignNode < Node
|
482
|
+
top_sensitive
|
483
|
+
children :variable, :value
|
484
|
+
|
442
485
|
PROTO_ASSIGN = /\A(\S+)\.prototype/
|
443
486
|
LEADING_DOT = /\A\.(prototype\.)?/
|
444
487
|
|
445
|
-
attr_reader :variable, :value, :context
|
446
|
-
|
447
488
|
def initialize(variable, value, context=nil)
|
448
489
|
@variable, @value, @context = variable, value, context
|
449
490
|
end
|
450
491
|
|
451
492
|
def compile_node(o)
|
493
|
+
top = o.delete(:top)
|
452
494
|
return compile_pattern_match(o) if statement?
|
453
495
|
return compile_splice(o) if value? && @variable.splice?
|
454
|
-
stmt
|
455
|
-
name
|
456
|
-
last
|
457
|
-
proto
|
458
|
-
|
459
|
-
|
496
|
+
stmt = o.delete(:as_statement)
|
497
|
+
name = @variable.compile(o)
|
498
|
+
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
|
499
|
+
proto = name[PROTO_ASSIGN, 1]
|
500
|
+
if @value.is_a?(CodeNode)
|
501
|
+
@value.name = last if last.match(Lexer::IDENTIFIER)
|
502
|
+
@value.proto = proto if proto
|
503
|
+
end
|
460
504
|
return write("#{name}: #{@value.compile(o)}") if @context == :object
|
461
505
|
o[:scope].find(name) unless value? && @variable.properties?
|
462
506
|
val = "#{name} = #{@value.compile(o)}"
|
463
507
|
return write("#{idt}#{val};") if stmt
|
464
|
-
|
508
|
+
val = "(#{val})" if !top || o[:return]
|
509
|
+
val = "#{idt}return #{val}" if o[:return]
|
510
|
+
write(val)
|
465
511
|
end
|
466
512
|
|
467
513
|
def value?
|
@@ -485,7 +531,7 @@ module CoffeeScript
|
|
485
531
|
if obj.is_a?(SplatNode)
|
486
532
|
val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
|
487
533
|
else
|
488
|
-
val = ValueNode.new(
|
534
|
+
val = ValueNode.new(val_var, [access_class.new(Value.new(i.to_s))])
|
489
535
|
end
|
490
536
|
assigns << AssignNode.new(obj, val).compile(o)
|
491
537
|
end
|
@@ -505,6 +551,10 @@ module CoffeeScript
|
|
505
551
|
# Simple Arithmetic and logical operations. Performs some conversion from
|
506
552
|
# CoffeeScript operations into their JavaScript equivalents.
|
507
553
|
class OpNode < Node
|
554
|
+
children :first, :second
|
555
|
+
attr_reader :operator
|
556
|
+
attr_accessor :second
|
557
|
+
|
508
558
|
CONVERSIONS = {
|
509
559
|
:== => "===",
|
510
560
|
:'!=' => "!==",
|
@@ -514,11 +564,10 @@ module CoffeeScript
|
|
514
564
|
:isnt => "!==",
|
515
565
|
:not => '!'
|
516
566
|
}
|
517
|
-
|
567
|
+
CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
|
568
|
+
ASSIGNMENT = [:'||=', :'&&=', :'?=']
|
518
569
|
PREFIX_OPERATORS = [:typeof, :delete]
|
519
570
|
|
520
|
-
attr_reader :operator, :first, :second
|
521
|
-
|
522
571
|
def initialize(operator, first, second=nil, flip=false)
|
523
572
|
@first, @second, @flip = first, second, flip
|
524
573
|
@operator = CONVERSIONS[operator.to_sym] || operator
|
@@ -528,18 +577,39 @@ module CoffeeScript
|
|
528
577
|
@second.nil?
|
529
578
|
end
|
530
579
|
|
580
|
+
def chainable?
|
581
|
+
CHAINABLE.include?(operator.to_sym)
|
582
|
+
end
|
583
|
+
|
531
584
|
def compile_node(o)
|
532
|
-
return write(
|
585
|
+
return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
|
586
|
+
return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
|
533
587
|
return write(compile_unary(o)) if unary?
|
588
|
+
return write(compile_existence(o)) if @operator == '?'
|
534
589
|
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
|
535
590
|
end
|
536
591
|
|
537
|
-
|
592
|
+
# Mimic Python's chained comparisons. See:
|
593
|
+
# http://docs.python.org/reference/expressions.html#notin
|
594
|
+
def compile_chain(o)
|
595
|
+
shared = @first.unwrap.second
|
596
|
+
@first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
|
597
|
+
"(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
|
598
|
+
end
|
599
|
+
|
600
|
+
def compile_assignment(o)
|
538
601
|
first, second = @first.compile(o), @second.compile(o)
|
602
|
+
o[:scope].find(first) if @first.unwrap.is_a?(Value)
|
539
603
|
sym = @operator[0..1]
|
604
|
+
return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
|
540
605
|
"#{first} = #{first} #{sym} #{second}"
|
541
606
|
end
|
542
607
|
|
608
|
+
def compile_existence(o)
|
609
|
+
first, second = @first.compile(o), @second.compile(o)
|
610
|
+
"#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
|
611
|
+
end
|
612
|
+
|
543
613
|
def compile_unary(o)
|
544
614
|
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
|
545
615
|
parts = [@operator.to_s, space, @first.compile(o)]
|
@@ -549,8 +619,14 @@ module CoffeeScript
|
|
549
619
|
end
|
550
620
|
|
551
621
|
# A function definition. The only node that creates a new Scope.
|
622
|
+
# A CodeNode does not have any children -- they're within the new scope.
|
552
623
|
class CodeNode < Node
|
624
|
+
top_sensitive
|
553
625
|
attr_reader :params, :body, :bound
|
626
|
+
attr_accessor :name, :proto
|
627
|
+
|
628
|
+
# Constructor functions start with an uppercase letter, by convention.
|
629
|
+
UPPERCASE = /[A-Z]/
|
554
630
|
|
555
631
|
def initialize(params, body, tag=nil)
|
556
632
|
@params = params
|
@@ -558,49 +634,48 @@ module CoffeeScript
|
|
558
634
|
@bound = tag == :boundfunc
|
559
635
|
end
|
560
636
|
|
561
|
-
def
|
562
|
-
@
|
637
|
+
def constructor?
|
638
|
+
@name && @name[0..0][UPPERCASE]
|
563
639
|
end
|
564
640
|
|
565
641
|
def compile_node(o)
|
566
|
-
if @bound
|
567
|
-
o[:scope].assign("__this", "this")
|
568
|
-
fvar = o[:scope].free_variable
|
569
|
-
end
|
570
642
|
shared_scope = o.delete(:shared_scope)
|
571
|
-
|
643
|
+
top = o.delete(:top)
|
644
|
+
o[:scope] = shared_scope || Scope.new(o[:scope], @body, self)
|
572
645
|
o[:return] = true
|
573
646
|
o[:top] = true
|
574
|
-
o[:indent] = idt(1)
|
647
|
+
o[:indent] = idt(@bound ? 2 : 1)
|
575
648
|
o.delete(:no_wrap)
|
576
649
|
o.delete(:globals)
|
577
|
-
|
650
|
+
o.delete(:closure)
|
578
651
|
if @params.last.is_a?(SplatNode)
|
579
652
|
splat = @params.pop
|
580
653
|
splat.index = @params.length
|
581
654
|
@body.unshift(splat)
|
582
655
|
end
|
583
656
|
@params.each {|id| o[:scope].parameter(id.to_s) }
|
584
|
-
code
|
585
|
-
name_part = name ? " #{name}" : ''
|
586
|
-
func
|
657
|
+
code = @body.empty? ? "" : "\n#{@body.compile_with_declarations(o)}\n"
|
658
|
+
name_part = @name ? " #{@name}" : ''
|
659
|
+
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt(@bound ? 1 : 0)}}"
|
660
|
+
func = "(#{func})" if top && !@bound
|
587
661
|
return write(func) unless @bound
|
588
|
-
|
662
|
+
inner = "(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
|
663
|
+
write("(function(__this) {\n#{idt(1)}var __func = #{func};\n#{idt(1)}return #{inner}\n#{idt}})(this)")
|
589
664
|
end
|
590
665
|
end
|
591
666
|
|
592
667
|
# A splat, either as a parameter to a function, an argument to a call,
|
593
668
|
# or in a destructuring assignment.
|
594
669
|
class SplatNode < Node
|
670
|
+
children :name
|
595
671
|
attr_accessor :index
|
596
|
-
attr_reader :name
|
597
672
|
|
598
673
|
def initialize(name)
|
599
674
|
@name = name
|
600
675
|
end
|
601
676
|
|
602
677
|
def compile_node(o={})
|
603
|
-
write(@index ? compile_param(o) :
|
678
|
+
write(@index ? compile_param(o) : @name.compile(o))
|
604
679
|
end
|
605
680
|
|
606
681
|
def compile_param(o)
|
@@ -608,10 +683,6 @@ module CoffeeScript
|
|
608
683
|
"#{@name} = Array.prototype.slice.call(arguments, #{@index})"
|
609
684
|
end
|
610
685
|
|
611
|
-
def compile_arg(o)
|
612
|
-
@name.compile(o)
|
613
|
-
end
|
614
|
-
|
615
686
|
def compile_value(o, name, index)
|
616
687
|
"Array.prototype.slice.call(#{name}, #{index})"
|
617
688
|
end
|
@@ -620,7 +691,7 @@ module CoffeeScript
|
|
620
691
|
|
621
692
|
# An object literal.
|
622
693
|
class ObjectNode < Node
|
623
|
-
|
694
|
+
children :properties
|
624
695
|
alias_method :objects, :properties
|
625
696
|
|
626
697
|
def initialize(properties = [])
|
@@ -647,7 +718,7 @@ module CoffeeScript
|
|
647
718
|
|
648
719
|
# An array literal.
|
649
720
|
class ArrayNode < Node
|
650
|
-
|
721
|
+
children :objects
|
651
722
|
|
652
723
|
def initialize(objects=[])
|
653
724
|
@objects = objects
|
@@ -669,9 +740,11 @@ module CoffeeScript
|
|
669
740
|
# code generation to generate a quick "array.push(value)" tree of nodes.
|
670
741
|
class PushNode
|
671
742
|
def self.wrap(array, expressions)
|
743
|
+
expr = expressions.unwrap
|
744
|
+
return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
|
672
745
|
Expressions.wrap(CallNode.new(
|
673
|
-
ValueNode.new(LiteralNode.new(array), [AccessorNode.new('push')]),
|
674
|
-
[
|
746
|
+
ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
|
747
|
+
[expr]
|
675
748
|
))
|
676
749
|
end
|
677
750
|
end
|
@@ -679,18 +752,14 @@ module CoffeeScript
|
|
679
752
|
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
680
753
|
# it, all other loops can be manufactured.
|
681
754
|
class WhileNode < Node
|
755
|
+
top_sensitive
|
756
|
+
children :condition, :body
|
682
757
|
statement
|
683
758
|
|
684
|
-
attr_reader :condition, :body
|
685
|
-
|
686
759
|
def initialize(condition, body)
|
687
760
|
@condition, @body = condition, body
|
688
761
|
end
|
689
762
|
|
690
|
-
def top_sensitive?
|
691
|
-
true
|
692
|
-
end
|
693
|
-
|
694
763
|
def compile_node(o)
|
695
764
|
returns = o.delete(:return)
|
696
765
|
top = o.delete(:top) && !returns
|
@@ -714,10 +783,11 @@ module CoffeeScript
|
|
714
783
|
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
715
784
|
# the current index of the loop as a second parameter.
|
716
785
|
class ForNode < Node
|
786
|
+
top_sensitive
|
787
|
+
children :body, :source, :filter
|
788
|
+
attr_reader :name, :index, :step
|
717
789
|
statement
|
718
790
|
|
719
|
-
attr_reader :body, :source, :name, :index, :filter, :step
|
720
|
-
|
721
791
|
def initialize(body, source, name, index=nil)
|
722
792
|
@body, @name, @index = body, name, index
|
723
793
|
@source = source[:source]
|
@@ -727,10 +797,6 @@ module CoffeeScript
|
|
727
797
|
@name, @index = @index, @name if @object
|
728
798
|
end
|
729
799
|
|
730
|
-
def top_sensitive?
|
731
|
-
true
|
732
|
-
end
|
733
|
-
|
734
800
|
def compile_node(o)
|
735
801
|
top_level = o.delete(:top) && !o[:return]
|
736
802
|
range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
|
@@ -771,10 +837,11 @@ module CoffeeScript
|
|
771
837
|
if @object
|
772
838
|
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
|
773
839
|
body = Expressions.wrap(IfNode.new(
|
774
|
-
CallNode.new(
|
775
|
-
|
776
|
-
|
777
|
-
|
840
|
+
CallNode.new(
|
841
|
+
ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
|
842
|
+
[LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
|
843
|
+
),
|
844
|
+
Expressions.wrap(body), nil, {:statement => true}
|
778
845
|
))
|
779
846
|
end
|
780
847
|
|
@@ -787,10 +854,10 @@ module CoffeeScript
|
|
787
854
|
|
788
855
|
# A try/catch/finally block.
|
789
856
|
class TryNode < Node
|
857
|
+
children :try, :recovery, :finally
|
858
|
+
attr_reader :error
|
790
859
|
statement
|
791
860
|
|
792
|
-
attr_reader :try, :error, :recovery, :finally
|
793
|
-
|
794
861
|
def initialize(try, error, recovery, finally=nil)
|
795
862
|
@try, @error, @recovery, @finally = try, error, recovery, finally
|
796
863
|
end
|
@@ -807,10 +874,9 @@ module CoffeeScript
|
|
807
874
|
|
808
875
|
# Throw an exception.
|
809
876
|
class ThrowNode < Node
|
877
|
+
children :expression
|
810
878
|
statement_only
|
811
879
|
|
812
|
-
attr_reader :expression
|
813
|
-
|
814
880
|
def initialize(expression)
|
815
881
|
@expression = expression
|
816
882
|
end
|
@@ -822,15 +888,20 @@ module CoffeeScript
|
|
822
888
|
|
823
889
|
# Check an expression for existence (meaning not null or undefined).
|
824
890
|
class ExistenceNode < Node
|
825
|
-
|
891
|
+
children :expression
|
892
|
+
|
893
|
+
def self.compile_test(o, variable)
|
894
|
+
first, second = variable, variable
|
895
|
+
first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
|
896
|
+
"(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
|
897
|
+
end
|
826
898
|
|
827
899
|
def initialize(expression)
|
828
900
|
@expression = expression
|
829
901
|
end
|
830
902
|
|
831
903
|
def compile_node(o)
|
832
|
-
|
833
|
-
write("(typeof #{val} !== \"undefined\" && #{val} !== null)")
|
904
|
+
write(ExistenceNode.compile_test(o, @expression))
|
834
905
|
end
|
835
906
|
end
|
836
907
|
|
@@ -838,7 +909,7 @@ module CoffeeScript
|
|
838
909
|
# You can't wrap parentheses around bits that get compiled into JS statements,
|
839
910
|
# unfortunately.
|
840
911
|
class ParentheticalNode < Node
|
841
|
-
|
912
|
+
children :expressions
|
842
913
|
|
843
914
|
def initialize(expressions, line=nil)
|
844
915
|
@expressions = expressions.unwrap
|
@@ -857,7 +928,7 @@ module CoffeeScript
|
|
857
928
|
# Single-expression IfNodes are compiled into ternary operators if possible,
|
858
929
|
# because ternaries are first-class returnable assignable expressions.
|
859
930
|
class IfNode < Node
|
860
|
-
|
931
|
+
children :condition, :body, :else_body
|
861
932
|
|
862
933
|
def initialize(condition, body, else_body=nil, tags={})
|
863
934
|
@condition = condition
|
@@ -928,7 +999,8 @@ module CoffeeScript
|
|
928
999
|
if_dent = child ? '' : idt
|
929
1000
|
com_dent = child ? idt : ''
|
930
1001
|
prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
|
931
|
-
|
1002
|
+
body = Expressions.wrap(@body).compile(o)
|
1003
|
+
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
|
932
1004
|
return if_part unless @else_body
|
933
1005
|
else_part = chain? ?
|
934
1006
|
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :
|