coffee-script 0.2.5 → 0.2.6
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 +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))}" :
|