Compare commits

..

42 Commits
0.2.4 ... 0.2.6

Author SHA1 Message Date
Jeremy Ashkenas
63c9b5c2f0 CoffeeScript 0.2.6 is on the books 2010-01-17 18:12:59 -05:00
Jeremy Ashkenas
80fbe02fda ignoring the top-down parser that doesn't work 2010-01-17 17:41:38 -05:00
Jeremy Ashkenas
e514a39dd2 added binary search example -- chapter 6 of beautiful code 2010-01-17 16:18:24 -05:00
Jeremy Ashkenas
4a32c58221 added bentley's chapter from beautiful code to the examples/tests -- quicksort runtime analysis 2010-01-17 15:58:44 -05:00
Jeremy Ashkenas
4609ad78c2 added the first chapter of beautiful code as a coffeescript example 2010-01-17 15:36:46 -05:00
Jeremy Ashkenas
2d90a751f7 edits for clarity 2010-01-17 14:55:06 -05:00
Jeremy Ashkenas
8647b54a61 rename compile_double_reference to compile_reference 2010-01-17 14:26:00 -05:00
Jeremy Ashkenas
8e1f3c0eca generating multiple calls to the same function should use compile_double_reference to ensure a single evaluation of the call itself. 2010-01-17 14:23:41 -05:00
Jeremy Ashkenas
c4d0903e6a fixing assignment-in-condition 2010-01-17 10:40:59 -05:00
Jeremy Ashkenas
e72ef1a61a reverting change 2010-01-17 10:28:04 -05:00
Jeremy Ashkenas
d7d9cb8d28 only let returns stop an expression from being closure-ified -- breaks and continues may be valid 2010-01-17 10:21:24 -05:00
Jeremy Ashkenas
f6c8e81ea6 the existential operator can now be used infix as well 2010-01-16 23:03:54 -05:00
Jeremy Ashkenas
52539ae7d2 abbreviating the existential operator 2010-01-16 22:26:34 -05:00
Jeremy Ashkenas
95b362499f added the conditional existence operator 2010-01-16 22:17:55 -05:00
Jeremy Ashkenas
0bc4da2b51 ensure that functions are only called once, when chaining comparators 2010-01-16 22:04:08 -05:00
Jeremy Ashkenas
9679fc0b52 removing redundant unary check 2010-01-16 16:49:03 -05:00
Jeremy Ashkenas
9cb0564972 added Python's chainable comparisons, like: 10 > 5 > 1 2010-01-16 16:37:49 -05:00
Jeremy Ashkenas
c6c0c7d059 simplification of function and prototype naming -- last_assign, immediate_assign, and proto_assign are gone, in favor of 'name' and 'proto' properties on CodeNodes 2010-01-16 15:44:07 -05:00
Jeremy Ashkenas
62e946b8ce purely empty functions at the top level should be wrapped in parens, so as not to cause a JS syntax error 2010-01-16 15:02:04 -05:00
Jeremy Ashkenas
6c782b7723 fixes for syntax highlighting assignments and regexes 2010-01-16 14:28:42 -05:00
Jeremy Ashkenas
9eff443032 arguments no longer is just a find-and-replace -- it'll fix the arguments variable at the top of scope if you use it in a function body 2010-01-16 12:52:26 -05:00
Jeremy Ashkenas
8957feedb4 expression closure wrappers are now safer -- they won't be generated if there's a statement_only inside 2010-01-16 12:10:31 -05:00
Jeremy Ashkenas
1cd7fa8ebe added children macro to Node, using it so that all nodes now have a 'children' method -- used for safe references to 'this' within closure wrappers 2010-01-16 11:24:10 -05:00
Jeremy Ashkenas
701cdb4c13 never try to push a statement_only 2010-01-15 19:47:16 -05:00
Jeremy Ashkenas
8dc5da9cc9 adding coffee-haml-filter to the resources section 2010-01-14 14:44:03 -05:00
Jeremy Ashkenas
001cc29deb slightly shorter generated code for ==> 2010-01-14 08:55:09 -05:00
Jeremy Ashkenas
e77e520607 CoffeeScript 0.2.5 is on the books 2010-01-13 23:24:45 -05:00
Jeremy Ashkenas
ed8a54995d with splats allowed in destructuring assignment 2010-01-13 22:25:58 -05:00
Jeremy Ashkenas
2d206e7b60 pulling out pushes into a pushnode 2010-01-13 21:33:46 -05:00
Jeremy Ashkenas
bb9fdd3015 while loops can now be used as expressions -- they return an array containing the computed result of each iteration. 2010-01-13 21:27:22 -05:00
Jeremy Ashkenas
1e7d638435 adding bound functions, with test 2010-01-13 20:59:57 -05:00
Jeremy Ashkenas
0ceca0778c adding when clauses with multiple values 2010-01-13 19:56:35 -05:00
Jeremy Ashkenas
abd9ab5c71 unified ParamSplatNode and ArgSplatNode into SplatNode 2010-01-12 23:49:47 -05:00
Jeremy Ashkenas
ea349a1a59 more safety type-checks in nodes.rb 2010-01-12 23:26:35 -05:00
Jeremy Ashkenas
f0d5db7e66 fixing heredocs to use the left-most indent as the indentation guide -- not just the first line of the heredoc 2010-01-12 23:06:12 -05:00
Jeremy Ashkenas
914ba1c244 removing commented-out bit 2010-01-12 18:01:12 -05:00
Jeremy Ashkenas
844ea33274 mistaken commit 2010-01-12 17:45:06 -05:00
Jeremy Ashkenas
87e04e9952 nicer syntax error messages for newlines and indentation 2010-01-12 17:44:37 -05:00
Jeremy Ashkenas
197914bcf7 nicer syntax error messages for newlines and indentation 2010-01-12 17:44:03 -05:00
Jeremy Ashkenas
8dfbd1a2a8 using Object.prototype.hasOwnProperty.call instead of obj.hasOwnProperty, with an alias, for Rhino and java objects 2010-01-12 17:35:37 -05:00
Jeremy Ashkenas
c19647ad33 adding and fixing test for empty strings 2010-01-12 08:52:44 -05:00
Jeremy Ashkenas
27f7ef09af allow leading newlines in coffee scripts 2010-01-12 08:49:39 -05:00
56 changed files with 1115 additions and 403 deletions

1
.gitignore vendored
View File

@@ -3,4 +3,5 @@ test.coffee
parser.output parser.output
lib/coffee_script/parser.rb lib/coffee_script/parser.rb
test/fixtures/underscore test/fixtures/underscore
examples/beautiful_code/parse.coffee
*.gem *.gem

View File

@@ -1,7 +1,7 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = 'coffee-script' s.name = 'coffee-script'
s.version = '0.2.4' # Keep version in sync with coffee-script.rb s.version = '0.2.6' # Keep version in sync with coffee-script.rb
s.date = '2010-1-12' s.date = '2010-1-17'
s.homepage = "http://jashkenas.github.com/coffee-script/" s.homepage = "http://jashkenas.github.com/coffee-script/"
s.summary = "The CoffeeScript Compiler" s.summary = "The CoffeeScript Compiler"

View File

@@ -0,0 +1,5 @@
cholesterol: 127
healthy: 200 > cholesterol > 60

View File

@@ -1 +1,8 @@
solipsism: true if mind? and not world? solipsism: true if mind? and not world?
speed ?= 140

View File

@@ -1,2 +1,2 @@
square: x => x * x square: x => x * x
cube: x => square(x) * x cube: x => square(x) * x

View File

@@ -0,0 +1,6 @@
Account: customer, cart =>
this.customer: customer
this.cart: cart
$('.shopping_cart').bind('click') event ==>
this.customer.purchase(this.cart)

View File

@@ -1,9 +1,10 @@
switch day switch day
when "Tuesday" then eat_breakfast() when "Mon" then go_to_work()
when "Wednesday" then go_to_the_park() when "Tue" then go_to_the_park()
when "Saturday" when "Thu" then go_ice_fishing()
when "Fri", "Sat"
if day is bingo_day if day is bingo_day
go_to_bingo() go_to_bingo()
go_dancing() go_dancing()
when "Sunday" then go_to_church() when "Sun" then go_to_church()
else go_to_work() else go_to_work()

View File

@@ -1,5 +1,8 @@
while demand > supply if this.studying_economics
sell() while supply > demand then buy()
restock() while supply < demand then sell()
while supply > demand then buy() num: 6
lyrics: while num -= 1
num + " little monkeys, jumping on the bed.
One fell out and bumped his head."

View File

@@ -51,7 +51,7 @@
<p> <p>
<b>Latest Version:</b> <b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.2.4</a> <a href="http://gemcutter.org/gems/coffee-script">0.2.6</a>
</p> </p>
<h2>Table of Contents</h2> <h2>Table of Contents</h2>
@@ -65,7 +65,7 @@
<a href="#objects_and_arrays">Objects and Arrays</a><br /> <a href="#objects_and_arrays">Objects and Arrays</a><br />
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br /> <a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br />
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br /> <a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br />
<a href="#existence">The Existence Operator</a><br /> <a href="#existence">The Existential Operator</a><br />
<a href="#aliases">Aliases</a><br /> <a href="#aliases">Aliases</a><br />
<a href="#splats">Splats...</a><br /> <a href="#splats">Splats...</a><br />
<a href="#arguments">Arguments are Arrays</a><br /> <a href="#arguments">Arguments are Arrays</a><br />
@@ -76,9 +76,11 @@
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br /> <a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br />
<a href="#blocks">Blocks</a><br /> <a href="#blocks">Blocks</a><br />
<a href="#pattern_matching">Pattern Matching</a><br /> <a href="#pattern_matching">Pattern Matching</a><br />
<a href="#long_arrow">Function Binding</a><br />
<a href="#embedded">Embedded JavaScript</a><br /> <a href="#embedded">Embedded JavaScript</a><br />
<a href="#switch">Switch/When/Else</a><br /> <a href="#switch">Switch/When/Else</a><br />
<a href="#try">Try/Catch/Finally</a><br /> <a href="#try">Try/Catch/Finally</a><br />
<a href="#comparisons">Chained Comparisons</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br /> <a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br /> <a href="#resources">Resources</a><br />
<a href="#contributing">Contributing</a><br /> <a href="#contributing">Contributing</a><br />
@@ -257,7 +259,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b class="header">Functions and Invocation</b> <b class="header">Functions and Invocation</b>
Functions are defined by a list of parameters, an arrow, and the Functions are defined by a list of parameters, an arrow, and the
function body. The empty function looks like this: <tt>=></tt>. All function body. The empty function looks like this: <tt>=></tt>. All
functions in CoffeeScript are named, for the benefit of debug messages. functions in CoffeeScript are named by default, for the benefit of debug messages.
If you'd like to create an anonymous function, just wrap it in parentheses.
</p> </p>
<%= code_for('functions', 'cube(5)') %> <%= code_for('functions', 'cube(5)') %>
@@ -328,14 +331,18 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p> </p>
<p id="existence"> <p id="existence">
<b class="header">The Existence Operator</b> <b class="header">The Existential Operator</b>
It's a little difficult to check for the existence of a variable in It's a little difficult to check for the existence of a variable in
JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero, JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero,
the empty string, and false. The existence operator <tt>?</tt> returns true unless the empty string, and false. The existential operator <tt>?</tt> returns true unless
a variable is <b>null</b> or <b>undefined</b>, which makes it analogous a variable is <b>null</b> or <b>undefined</b>, which makes it analogous
to Ruby's <tt>nil?</tt> to Ruby's <tt>nil?</tt>
</p> </p>
<%= code_for('existence') %> <p>
It can also be used for safer conditional assignment than <tt>||=</tt>
provides, for cases where you may be handling numbers or strings.
</p>
<%= code_for('existence', 'speed') %>
<p id="aliases"> <p id="aliases">
<b class="header">Aliases</b> <b class="header">Aliases</b>
@@ -387,9 +394,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<p id="while"> <p id="while">
<b class="header">While Loops</b> <b class="header">While Loops</b>
The only low-level loop that CoffeeScript provides is the while loop. The only low-level loop that CoffeeScript provides is the <b>while</b> loop. The
main difference from JavaScript is that the <b>while</b> loop can be used
as an expression, returning an array containing the result of each iteration
through the loop.
</p> </p>
<%= code_for('while') %> <%= code_for('while', 'lyrics.join("\n")') %>
<p> <p>
Other JavaScript loops, such as <b>for</b> loops and <b>do-while</b> loops Other JavaScript loops, such as <b>for</b> loops and <b>do-while</b> loops
can be mimicked by variations on <b>while</b>, but the hope is that you can be mimicked by variations on <b>while</b>, but the hope is that you
@@ -469,6 +479,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
into a function call: into a function call:
</p> </p>
<%= code_for('expressions_try', true) %> <%= code_for('expressions_try', true) %>
<p>
There are a handful of statements in JavaScript that can't be meaningfully
converted into expressions: <tt>break</tt>, <tt>continue</tt>,
and <tt>return</tt>. If you make use of them within a block of code,
CoffeeScript won't try to perform the conversion.
</p>
<p id="inheritance"> <p id="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b> <b class="header">Inheritance, and Calling Super from a Subclass</b>
@@ -528,6 +544,17 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p> </p>
<%= code_for('object_extraction', 'poet + " — " + street') %> <%= code_for('object_extraction', 'poet + " — " + street') %>
<p id="long_arrow">
<b class="header">Function binding</b>
The long arrow <tt>==></tt> can be used to both define a function, and to bind
it to the current value of <tt>this</tt>, right on the spot. This is helpful
when using callback-based libraries like Prototype or jQuery, for creating
iterator functions to pass to <tt>each</tt>, or event-handler functions
to use with <tt>bind</tt>. Functions created with the long arrow are able to access
properties of the <tt>this</tt> where they're defined.
</p>
<%= code_for('long_arrow') %>
<p id="embedded"> <p id="embedded">
<b class="header">Embedded JavaScript</b> <b class="header">Embedded JavaScript</b>
Hopefully, you'll never need to use it, but if you ever need to intersperse Hopefully, you'll never need to use it, but if you ever need to intersperse
@@ -546,6 +573,11 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
in a returnable, assignable expression. The format is: <tt>switch</tt> condition, in a returnable, assignable expression. The format is: <tt>switch</tt> condition,
<tt>when</tt> clauses, <tt>else</tt> the default case. <tt>when</tt> clauses, <tt>else</tt> the default case.
</p> </p>
<p>
As in Ruby, <b>switch</b> statements in CoffeeScript can take multiple
values for each <b>when</b> clause. If any of the values match, the clause
runs.
</p>
<%= code_for('switch') %> <%= code_for('switch') %>
<p id="try"> <p id="try">
@@ -555,6 +587,15 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p> </p>
<%= code_for('try') %> <%= code_for('try') %>
<p id="comparisons">
<b class="header">Chained Comparisons</b>
CoffeeScript borrows
<a href="http://docs.python.org/reference/expressions.html#notin">chained comparisons</a>
from Python &mdash; making it easy to test if a value falls within a
certain range.
</p>
<%= code_for('comparisons', 'healthy') %>
<p id="strings"> <p id="strings">
<b class="header">Multiline Strings and Heredocs</b> <b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript. Multiline strings are allowed in CoffeeScript.
@@ -590,6 +631,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
that includes CoffeeScript helpers, that includes CoffeeScript helpers,
bundling and minification. bundling and minification.
</li> </li>
<li>
<a href="http://github.com/inem/coffee-haml-filter">coffee-haml-filter</a><br />
A custom <a href="http://haml-lang.com/">HAML</a> filter, by
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
</li>
</ul> </ul>
<h2 id="contributing">Contributing</h2> <h2 id="contributing">Contributing</h2>
@@ -624,7 +671,24 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</ul> </ul>
<h2 id="change_log">Change Log</h2> <h2 id="change_log">Change Log</h2>
<p>
<b class="header" style="margin-top: 20px;">0.2.6</b>
Added Python-style chained comparisons, the conditional existence
operator <tt>?=</tt>, and some examples from <i>Beautiful Code</i>.
Bugfixes relating to statement-to-expression conversion, arguments-to-array
conversion, and the TextMate syntax highlighter.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.2.5</b>
The conditions in switch statements can now take multiple values at once &mdash;
If any of them are true, the case will run. Added the long arrow <tt>==></tt>,
which defines and immediately binds a function to <tt>this</tt>. While loops can
now be used as expressions, in the same way that comprehensions can. Splats
can be used within pattern matches to soak up the rest of an array.
</p>
<p> <p>
<b class="header" style="margin-top: 20px;">0.2.4</b> <b class="header" style="margin-top: 20px;">0.2.4</b>
Added ECMAScript Harmony style destructuring assignment, for dealing with Added ECMAScript Harmony style destructuring assignment, for dealing with
@@ -668,7 +732,7 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b class="header" style="margin-top: 20px;">0.2.0</b> <b class="header" style="margin-top: 20px;">0.2.0</b>
Major release. Significant whitespace. Better statement-to-expression Major release. Significant whitespace. Better statement-to-expression
conversion. Splats. Splice literals. Object comprehensions. Blocks. conversion. Splats. Splice literals. Object comprehensions. Blocks.
The existence operator. Many thanks to all the folks who posted issues, The existential operator. Many thanks to all the folks who posted issues,
with special thanks to with special thanks to
<a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace <a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace
and expression help. and expression help.

View File

@@ -1,7 +1,8 @@
(function(){ (function(){
var backwards; var backwards;
backwards = function backwards() { backwards = function backwards() {
return alert(Array.prototype.slice.call(arguments, 0).reverse()); var arguments = Array.prototype.slice.call(arguments, 0);
return alert(arguments.reverse());
}; };
backwards("stairway", "to", "heaven"); backwards("stairway", "to", "heaven");
})(); })();

View File

@@ -3,7 +3,7 @@
// Eat lunch. // Eat lunch.
lunch = (function() { lunch = (function() {
__a = []; __b = ['toast', 'cheese', 'wine']; __a = []; __b = ['toast', 'cheese', 'wine'];
for (__c=0; __c<__b.length; __c++) { for (__c = 0; __c < __b.length; __c++) {
food = __b[__c]; food = __b[__c];
__a.push(eat(food)); __a.push(eat(food));
} }
@@ -11,10 +11,10 @@
})(); })();
// Naive collision detection. // Naive collision detection.
__d = asteroids; __d = asteroids;
for (__e=0; __e<__d.length; __e++) { for (__e = 0; __e < __d.length; __e++) {
roid = __d[__e]; roid = __d[__e];
__f = asteroids; __f = asteroids;
for (__g=0; __g<__f.length; __g++) { for (__g = 0; __g < __f.length; __g++) {
roid2 = __f[__g]; roid2 = __f[__g];
if (roid !== roid2) { if (roid !== roid2) {
if (roid.overlaps(roid2)) { if (roid.overlaps(roid2)) {

View File

@@ -0,0 +1,5 @@
(function(){
var cholesterol, healthy;
cholesterol = 127;
healthy = (200 > cholesterol) && (cholesterol > 60);
})();

View File

@@ -1,5 +1,5 @@
(function(){ (function(){
var date, mood; var date, expensive, mood;
if (singing) { if (singing) {
mood = greatly_improved; mood = greatly_improved;
} }

View File

@@ -1,6 +1,7 @@
(function(){ (function(){
var solipsism; var solipsism, speed;
if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) { if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) {
solipsism = true; solipsism = true;
} }
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
})(); })();

View File

@@ -1,4 +1,4 @@
(function(){ (function(){
var one, six, three, two; var one, six, three, two;
six = (one = 1) + (two = 2) + (three = 3); six = ((one = 1)) + ((two = 2)) + ((three = 3));
})(); })();

View File

@@ -1,10 +1,11 @@
(function(){ (function(){
var __a, __b, globals, name; var __a, __b, globals, name;
var __hasProp = Object.prototype.hasOwnProperty;
// The first ten global properties. // The first ten global properties.
globals = ((function() { globals = ((function() {
__a = []; __b = window; __a = []; __b = window;
for (name in __b) { for (name in __b) {
if (__b.hasOwnProperty(name)) { if (__hasProp.call(__b, name)) {
__a.push(name); __a.push(name);
} }
} }

View File

@@ -0,0 +1,17 @@
(function(){
var Account;
Account = function Account(customer, cart) {
var __a;
this.customer = customer;
this.cart = cart;
__a = $('.shopping_cart').bind('click', (function(__this) {
var __func = function(event) {
return this.customer.purchase(this.cart);
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
return Account === this.constructor ? this : __a;
};
})();

View File

@@ -1,5 +1,6 @@
(function(){ (function(){
var __a, __b, age, ages, child, years_old; var __a, __b, age, ages, child, years_old;
var __hasProp = Object.prototype.hasOwnProperty;
years_old = { years_old = {
max: 10, max: 10,
ida: 9, ida: 9,
@@ -9,7 +10,7 @@
__a = []; __b = years_old; __a = []; __b = years_old;
for (child in __b) { for (child in __b) {
age = __b[child]; age = __b[child];
if (__b.hasOwnProperty(child)) { if (__hasProp.call(__b, child)) {
__a.push(child + " is " + age); __a.push(child + " is " + age);
} }
} }

View File

@@ -34,7 +34,7 @@
// Array comprehensions: // Array comprehensions:
cubed_list = (function() { cubed_list = (function() {
__a = []; __b = list; __a = []; __b = list;
for (__c=0; __c<__b.length; __c++) { for (__c = 0; __c < __b.length; __c++) {
num = __b[__c]; num = __b[__c];
__a.push(math.cube(num)); __a.push(math.cube(num));
} }

View File

@@ -1,6 +1,6 @@
(function(){ (function(){
var contenders, gold, medalists, silver, the_field; var contenders, gold, medalists, silver, the_field;
gold = silver = the_field = "unknown"; gold = (silver = (the_field = "unknown"));
medalists = function medalists(first, second) { medalists = function medalists(first, second) {
var rest; var rest;
rest = Array.prototype.slice.call(arguments, 2); rest = Array.prototype.slice.call(arguments, 2);

View File

@@ -1,7 +1,6 @@
(function(){ (function(){
var Animal, Horse, Snake, __a, __b, sam, tom; var Animal, Horse, Snake, __a, __b, sam, tom;
Animal = function Animal() { Animal = function Animal() { };
};
Animal.prototype.move = function move(meters) { Animal.prototype.move = function move(meters) {
return alert(this.name + " moved " + meters + "m."); return alert(this.name + " moved " + meters + "m.");
}; };

View File

@@ -1,14 +1,16 @@
(function(){ (function(){
if (day === "Tuesday") { if (day === "Mon") {
eat_breakfast(); go_to_work();
} else if (day === "Wednesday") { } else if (day === "Tue") {
go_to_the_park(); go_to_the_park();
} else if (day === "Saturday") { } else if (day === "Thu") {
go_ice_fishing();
} else if (day === "Fri" || day === "Sat") {
if (day === bingo_day) { if (day === bingo_day) {
go_to_bingo(); go_to_bingo();
go_dancing(); go_dancing();
} }
} else if (day === "Sunday") { } else if (day === "Sun") {
go_to_church(); go_to_church();
} else { } else {
go_to_work(); go_to_work();

View File

@@ -1,9 +1,20 @@
(function(){ (function(){
while (demand > supply) { var __a, lyrics, num;
sell(); if (this.studying_economics) {
restock(); while (supply > demand) {
} buy();
while (supply > demand) { }
buy(); while (supply < demand) {
sell();
}
} }
num = 6;
lyrics = (function() {
__a = [];
while (num -= 1) {
__a.push(num + " little monkeys, jumping on the bed. \
One fell out and bumped his head.");
}
return __a;
})();
})(); })();

View File

@@ -14,63 +14,39 @@
var arr = []; var arr = [];
while (num--) arr.push(num); while (num--) arr.push(num);
JSLitmus.test('current comprehensions', function() { var f1 = function f1() {
__a = arr; return arr;
__c = []; };
for (__b in __a) {
if (__a.hasOwnProperty(__b)) { JSLitmus.test('regular function', function() {
num = __a[__b]; f1();
__d = num;
__c.push(num);
}
}
}); });
JSLitmus.test('raw for loop (best we can do)', function() { var __this = this;
__a = arr;
__c = new Array(__a.length); var f2 = function f2() {
for (__b=0; __b < __a.length; __b++) { return (function() {
__c[__b] = __a[__b]; return arr;
} }).apply(__this, arguments);
};
JSLitmus.test('bound function', function() {
f2();
}); });
JSLitmus.test('current without hasOwnProperty check', function() { var f3 = (function() {
__a = arr; __b = function() {
__c = []; return arr;
for (__b in __a) {
num = __a[__b];
__d = num;
__c.push(num);
}
});
JSLitmus.test('raw for..in loop', function() {
__a = arr;
__c = new Array(__a.length);
for (__b in __a) {
__c[__b] = __a[__b];
}
});
JSLitmus.test('weepy\'s comprehensions', function() {
__c = []; __a = arr;
__d = function(num, __b) {
__c.push(num);
}; };
if (__a instanceof Array) { return (function f2() {
for (__b=0; __b<__a.length; __b++) __d(__a[__b], __b); return __b.apply(__this, arguments);
} else { });
for (__b in __a) { if (__a.hasOwnProperty(__b)) __d(__a[__b], __b); } })();
}
JSLitmus.test('prebound function', function() {
f3();
}); });
JSLitmus.test('CoffeeScript 0.2.2 comprehensions', function() {
__c = []; __a = arr;
for (__b=0; __b<__a.length; __b++) {
num = __a[__b];
__c.push(num);
}
});
</script> </script>
</body> </body>

View File

@@ -0,0 +1,16 @@
# Beautiful Code, Chapter 6.
# The implementation of binary search that is tested.
# Return the index of an element in a sorted list. (or -1, if not present)
index: list, target =>
[low, high]: [0, list.length]
while low < high
mid: (low + high) >> 1
val: list[mid]
return mid if val is target
if val < target then low: mid + 1 else high: mid
return -1
print(2 is index([10, 20, 30, 40, 50], 30))
print(4 is index([-97, 35, 67, 88, 1200], 1200))
print(0 is index([0, 45, 70], 0))

View File

@@ -0,0 +1,13 @@
# Beautiful Code, Chapter 3.
# Produces the expected runtime of Quicksort, for every integer from 1 to N.
runtime: N =>
[sum, t]: [0, 0]
for n in [1..N]
sum += 2 * t
t: n - 1 + sum / n
t
print(runtime(3) is 2.6666666666666665)
print(runtime(5) is 7.4)
print(runtime(8) is 16.92142857142857)

View File

@@ -0,0 +1,34 @@
# Beautiful Code, Chapter 1.
# Implements a regular expression matcher that supports character matches,
# '.', '^', '$', and '*'.
# Search for the regexp anywhere in the text.
match: regexp, text =>
return match_here(regexp.slice(1), text) if regexp[0] is '^'
while text
return true if match_here(regexp, text)
text: text.slice(1)
false
# Search for the regexp at the beginning of the text.
match_here: regexp, text =>
[cur, next]: [regexp[0], regexp[1]]
if regexp.length is 0 then return true
if next is '*' then return match_star(cur, regexp.slice(2), text)
if cur is '$' and not next then return text.length is 0
if text and (cur is '.' or cur is text[0]) then return match_here(regexp.slice(1), text.slice(1))
false
# Search for a kleene star match at the beginning of the text.
match_star: c, regexp, text =>
while true
return true if match_here(regexp, text)
return false unless text and (text[0] is c or c is '.')
text: text.slice(1)
print(match("ex", "some text"))
print(match("s..t", "spit"))
print(match("^..t", "buttercup"))
print(match("i..$", "cherries"))
print(match("o*m", "vrooooommm!"))
print(match("^hel*o$", "hellllllo"))

View File

@@ -15,8 +15,8 @@ dc.model.Document: dc.Model.extend({
# document by binding to Metadata, instead of on-the-fly. # document by binding to Metadata, instead of on-the-fly.
metadata: => metadata: =>
docId: this.id docId: this.id
_.select(Metadata.models(), (meta => _.select(Metadata.models(), (meta =>
_.any(meta.get('instances'), instance => _.any(meta.get('instances'), instance =>
instance.document_id is docId))) instance.document_id is docId)))
bookmark: pageNumber => bookmark: pageNumber =>
@@ -60,7 +60,7 @@ dc.model.DocumentSet: dc.model.RESTfulSet.extend({
# change their selected state. # change their selected state.
_onModelEvent: e, model => _onModelEvent: e, model =>
this.base(e, model) this.base(e, model)
fire: e == dc.Model.CHANGED and model.hasChanged('selected') fire: e is dc.Model.CHANGED and model.hasChanged('selected')
if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this)) if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this))
}) })

View File

@@ -37,7 +37,7 @@
<p> <p>
<b>Latest Version:</b> <b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.2.4</a> <a href="http://gemcutter.org/gems/coffee-script">0.2.6</a>
</p> </p>
<h2>Table of Contents</h2> <h2>Table of Contents</h2>
@@ -51,7 +51,7 @@
<a href="#objects_and_arrays">Objects and Arrays</a><br /> <a href="#objects_and_arrays">Objects and Arrays</a><br />
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br /> <a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br />
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br /> <a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br />
<a href="#existence">The Existence Operator</a><br /> <a href="#existence">The Existential Operator</a><br />
<a href="#aliases">Aliases</a><br /> <a href="#aliases">Aliases</a><br />
<a href="#splats">Splats...</a><br /> <a href="#splats">Splats...</a><br />
<a href="#arguments">Arguments are Arrays</a><br /> <a href="#arguments">Arguments are Arrays</a><br />
@@ -62,9 +62,11 @@
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br /> <a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br />
<a href="#blocks">Blocks</a><br /> <a href="#blocks">Blocks</a><br />
<a href="#pattern_matching">Pattern Matching</a><br /> <a href="#pattern_matching">Pattern Matching</a><br />
<a href="#long_arrow">Function Binding</a><br />
<a href="#embedded">Embedded JavaScript</a><br /> <a href="#embedded">Embedded JavaScript</a><br />
<a href="#switch">Switch/When/Else</a><br /> <a href="#switch">Switch/When/Else</a><br />
<a href="#try">Try/Catch/Finally</a><br /> <a href="#try">Try/Catch/Finally</a><br />
<a href="#comparisons">Chained Comparisons</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br /> <a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br /> <a href="#resources">Resources</a><br />
<a href="#contributing">Contributing</a><br /> <a href="#contributing">Contributing</a><br />
@@ -139,7 +141,7 @@ race <span class="Keyword">=</span> <span class="Storage">function</span> <span
<span class="Comment"><span class="Comment">//</span> Array comprehensions:</span> <span class="Comment"><span class="Comment">//</span> Array comprehensions:</span>
cubed_list <span class="Keyword">=</span> (<span class="Storage">function</span>() { cubed_list <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> list; __a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> list;
<span class="Keyword">for</span> (__c<span class="Keyword">=</span><span class="Number">0</span>; __c<span class="Keyword">&lt;</span>__b.<span class="LibraryConstant">length</span>; __c<span class="Keyword">++</span>) { <span class="Keyword">for</span> (__c <span class="Keyword">=</span> <span class="Number">0</span>; __c <span class="Keyword">&lt;</span> __b.<span class="LibraryConstant">length</span>; __c<span class="Keyword">++</span>) {
num <span class="Keyword">=</span> __b[__c]; num <span class="Keyword">=</span> __b[__c];
__a.<span class="LibraryFunction">push</span>(math.cube(num)); __a.<span class="LibraryFunction">push</span>(math.cube(num));
} }
@@ -180,7 +182,7 @@ if ((typeof elvis !== "undefined" && elvis !== null)) {
// Array comprehensions: // Array comprehensions:
cubed_list = (function() { cubed_list = (function() {
__a = []; __b = list; __a = []; __b = list;
for (__c=0; __c<__b.length; __c++) { for (__c = 0; __c < __b.length; __c++) {
num = __b[__c]; num = __b[__c];
__a.push(math.cube(num)); __a.push(math.cube(num));
} }
@@ -354,7 +356,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b class="header">Functions and Invocation</b> <b class="header">Functions and Invocation</b>
Functions are defined by a list of parameters, an arrow, and the Functions are defined by a list of parameters, an arrow, and the
function body. The empty function looks like this: <tt>=></tt>. All function body. The empty function looks like this: <tt>=></tt>. All
functions in CoffeeScript are named, for the benefit of debug messages. functions in CoffeeScript are named by default, for the benefit of debug messages.
If you'd like to create an anonymous function, just wrap it in parentheses.
</p> </p>
<div class='code'><pre class="idle"><span class="FunctionName">square</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</span> x <span class="Keyword">*</span> x <div class='code'><pre class="idle"><span class="FunctionName">square</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</span> x <span class="Keyword">*</span> x
<span class="FunctionName">cube</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</span> square(x) <span class="Keyword">*</span> x <span class="FunctionName">cube</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</span> square(x) <span class="Keyword">*</span> x
@@ -499,7 +502,7 @@ new_num = change_numbers();
<span class="FunctionName">date</span><span class="Keyword">:</span> <span class="Keyword">if</span> friday <span class="Keyword">then</span> sue <span class="Keyword">else</span> jill <span class="FunctionName">date</span><span class="Keyword">:</span> <span class="Keyword">if</span> friday <span class="Keyword">then</span> sue <span class="Keyword">else</span> jill
expensive <span class="Keyword">||</span><span class="Keyword">=</span> do_the_math() expensive <span class="Keyword">||</span><span class="Keyword">=</span> do_the_math()
</pre><pre class="idle"><span class="Storage">var</span> date, mood; </pre><pre class="idle"><span class="Storage">var</span> date, expensive, mood;
<span class="Keyword">if</span> (singing) { <span class="Keyword">if</span> (singing) {
mood <span class="Keyword">=</span> greatly_improved; mood <span class="Keyword">=</span> greatly_improved;
} }
@@ -518,19 +521,36 @@ expensive <span class="Keyword">=</span> expensive <span class="Keyword">||</spa
</p> </p>
<p id="existence"> <p id="existence">
<b class="header">The Existence Operator</b> <b class="header">The Existential Operator</b>
It's a little difficult to check for the existence of a variable in It's a little difficult to check for the existence of a variable in
JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero, JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero,
the empty string, and false. The existence operator <tt>?</tt> returns true unless the empty string, and false. The existential operator <tt>?</tt> returns true unless
a variable is <b>null</b> or <b>undefined</b>, which makes it analogous a variable is <b>null</b> or <b>undefined</b>, which makes it analogous
to Ruby's <tt>nil?</tt> to Ruby's <tt>nil?</tt>
</p> </p>
<p>
It can also be used for safer conditional assignment than <tt>||=</tt>
provides, for cases where you may be handling numbers or strings.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">solipsism</span><span class="Keyword">:</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> mind<span class="Keyword">?</span> <span class="Keyword">and</span> <span class="Keyword">not</span> world<span class="Keyword">?</span> <div class='code'><pre class="idle"><span class="FunctionName">solipsism</span><span class="Keyword">:</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> mind<span class="Keyword">?</span> <span class="Keyword">and</span> <span class="Keyword">not</span> world<span class="Keyword">?</span>
</pre><pre class="idle"><span class="Storage">var</span> solipsism;
speed <span class="Keyword">?</span><span class="Keyword">=</span> <span class="Number">140</span>
</pre><pre class="idle"><span class="Storage">var</span> solipsism, speed;
<span class="Keyword">if</span> ((<span class="Keyword">typeof</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> <span class="Keyword">!</span>(<span class="Keyword">typeof</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>)) { <span class="Keyword">if</span> ((<span class="Keyword">typeof</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> <span class="Keyword">!</span>(<span class="Keyword">typeof</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>)) {
solipsism <span class="Keyword">=</span> <span class="BuiltInConstant">true</span>; solipsism <span class="Keyword">=</span> <span class="BuiltInConstant">true</span>;
} }
</pre><br class='clear' /></div> speed <span class="Keyword">=</span> (<span class="Keyword">typeof</span> speed <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> speed <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) ? speed : <span class="Number">140</span>;
</pre><button onclick='javascript: var solipsism, speed;
if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) {
solipsism = true;
}
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
;alert(speed);'>run: speed</button><br class='clear' /></div>
<p id="aliases"> <p id="aliases">
<b class="header">Aliases</b> <b class="header">Aliases</b>
@@ -613,7 +633,7 @@ alert(<span class="String"><span class="String">&quot;</span>Gold: <span class="
alert(<span class="String"><span class="String">&quot;</span>Silver: <span class="String">&quot;</span></span> <span class="Keyword">+</span> silver) alert(<span class="String"><span class="String">&quot;</span>Silver: <span class="String">&quot;</span></span> <span class="Keyword">+</span> silver)
alert(<span class="String"><span class="String">&quot;</span>The Field: <span class="String">&quot;</span></span> <span class="Keyword">+</span> the_field) alert(<span class="String"><span class="String">&quot;</span>The Field: <span class="String">&quot;</span></span> <span class="Keyword">+</span> the_field)
</pre><pre class="idle"><span class="Storage">var</span> contenders, gold, medalists, silver, the_field; </pre><pre class="idle"><span class="Storage">var</span> contenders, gold, medalists, silver, the_field;
gold <span class="Keyword">=</span> silver <span class="Keyword">=</span> the_field <span class="Keyword">=</span> <span class="String"><span class="String">&quot;</span>unknown<span class="String">&quot;</span></span>; gold <span class="Keyword">=</span> (silver <span class="Keyword">=</span> (the_field <span class="Keyword">=</span> <span class="String"><span class="String">&quot;</span>unknown<span class="String">&quot;</span></span>));
medalists <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">medalists</span>(<span class="FunctionArgument">first, second</span>) { medalists <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">medalists</span>(<span class="FunctionArgument">first, second</span>) {
<span class="Storage">var</span> rest; <span class="Storage">var</span> rest;
rest <span class="Keyword">=</span> <span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">2</span>); rest <span class="Keyword">=</span> <span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">2</span>);
@@ -627,7 +647,7 @@ medalists.<span class="LibraryFunction">apply</span>(<span class="Variable">this
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>Silver: <span class="String">&quot;</span></span> <span class="Keyword">+</span> silver); <span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>Silver: <span class="String">&quot;</span></span> <span class="Keyword">+</span> silver);
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>The Field: <span class="String">&quot;</span></span> <span class="Keyword">+</span> the_field); <span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>The Field: <span class="String">&quot;</span></span> <span class="Keyword">+</span> the_field);
</pre><button onclick='javascript: var contenders, gold, medalists, silver, the_field; </pre><button onclick='javascript: var contenders, gold, medalists, silver, the_field;
gold = silver = the_field = "unknown"; gold = (silver = (the_field = "unknown"));
medalists = function medalists(first, second) { medalists = function medalists(first, second) {
var rest; var rest;
rest = Array.prototype.slice.call(arguments, 2); rest = Array.prototype.slice.call(arguments, 2);
@@ -655,33 +675,70 @@ alert("The Field: " + the_field);
backwards(<span class="String"><span class="String">&quot;</span>stairway<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>to<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>heaven<span class="String">&quot;</span></span>) backwards(<span class="String"><span class="String">&quot;</span>stairway<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>to<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>heaven<span class="String">&quot;</span></span>)
</pre><pre class="idle"><span class="Storage">var</span> backwards; </pre><pre class="idle"><span class="Storage">var</span> backwards;
backwards <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">backwards</span>() { backwards <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">backwards</span>() {
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">0</span>).<span class="LibraryFunction">reverse</span>()); <span class="Storage">var</span> arguments <span class="Keyword">=</span> <span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">0</span>);
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(arguments.<span class="LibraryFunction">reverse</span>());
}; };
backwards(<span class="String"><span class="String">&quot;</span>stairway<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>to<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>heaven<span class="String">&quot;</span></span>); backwards(<span class="String"><span class="String">&quot;</span>stairway<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>to<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>heaven<span class="String">&quot;</span></span>);
</pre><button onclick='javascript: var backwards; </pre><button onclick='javascript: var backwards;
backwards = function backwards() { backwards = function backwards() {
return alert(Array.prototype.slice.call(arguments, 0).reverse()); var arguments = Array.prototype.slice.call(arguments, 0);
return alert(arguments.reverse());
}; };
backwards("stairway", "to", "heaven"); backwards("stairway", "to", "heaven");
;'>run</button><br class='clear' /></div> ;'>run</button><br class='clear' /></div>
<p id="while"> <p id="while">
<b class="header">While Loops</b> <b class="header">While Loops</b>
The only low-level loop that CoffeeScript provides is the while loop. The only low-level loop that CoffeeScript provides is the <b>while</b> loop. The
main difference from JavaScript is that the <b>while</b> loop can be used
as an expression, returning an array containing the result of each iteration
through the loop.
</p> </p>
<div class='code'><pre class="idle"><span class="Keyword">while</span> demand <span class="Keyword">&gt;</span> supply <div class='code'><pre class="idle"><span class="Keyword">if</span> <span class="Variable">this</span>.studying_economics
sell() <span class="Keyword">while</span> supply <span class="Keyword">&gt;</span> demand <span class="Keyword">then</span> buy()
restock() <span class="Keyword">while</span> supply <span class="Keyword">&lt;</span> demand <span class="Keyword">then</span> sell()
<span class="Keyword">while</span> supply <span class="Keyword">&gt;</span> demand <span class="Keyword">then</span> buy() <span class="FunctionName">num</span><span class="Keyword">:</span> <span class="Number">6</span>
</pre><pre class="idle"><span class="Keyword">while</span> (demand <span class="Keyword">&gt;</span> supply) { <span class="FunctionName">lyrics</span><span class="Keyword">:</span> <span class="Keyword">while</span> num <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">1</span>
sell(); num <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> little monkeys, jumping on the bed.</span>
restock(); <span class="String"> One fell out and bumped his head.<span class="String">&quot;</span></span>
</pre><pre class="idle"><span class="Storage">var</span> __a, lyrics, num;
<span class="Keyword">if</span> (<span class="Variable">this</span>.studying_economics) {
<span class="Keyword">while</span> (supply <span class="Keyword">&gt;</span> demand) {
buy();
}
<span class="Keyword">while</span> (supply <span class="Keyword">&lt;</span> demand) {
sell();
}
} }
<span class="Keyword">while</span> (supply <span class="Keyword">&gt;</span> demand) { num <span class="Keyword">=</span> <span class="Number">6</span>;
buy(); lyrics <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__a <span class="Keyword">=</span> [];
<span class="Keyword">while</span> (num <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">1</span>) {
__a.<span class="LibraryFunction">push</span>(num <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> little monkeys, jumping on the bed. \</span>
<span class="String">One fell out and bumped his head.<span class="String">&quot;</span></span>);
}
<span class="Keyword">return</span> __a;
})();
</pre><button onclick='javascript: var __a, lyrics, num;
if (this.studying_economics) {
while (supply > demand) {
buy();
}
while (supply < demand) {
sell();
}
} }
</pre><br class='clear' /></div> num = 6;
lyrics = (function() {
__a = [];
while (num -= 1) {
__a.push(num + " little monkeys, jumping on the bed. \
One fell out and bumped his head.");
}
return __a;
})();
;alert(lyrics.join("\n"));'>run: lyrics.join("\n")</button><br class='clear' /></div>
<p> <p>
Other JavaScript loops, such as <b>for</b> loops and <b>do-while</b> loops Other JavaScript loops, such as <b>for</b> loops and <b>do-while</b> loops
can be mimicked by variations on <b>while</b>, but the hope is that you can be mimicked by variations on <b>while</b>, but the hope is that you
@@ -709,7 +766,7 @@ backwards("stairway", "to", "heaven");
<span class="Comment"><span class="Comment">//</span> Eat lunch.</span> <span class="Comment"><span class="Comment">//</span> Eat lunch.</span>
lunch <span class="Keyword">=</span> (<span class="Storage">function</span>() { lunch <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>]; __a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>];
<span class="Keyword">for</span> (__c<span class="Keyword">=</span><span class="Number">0</span>; __c<span class="Keyword">&lt;</span>__b.<span class="LibraryConstant">length</span>; __c<span class="Keyword">++</span>) { <span class="Keyword">for</span> (__c <span class="Keyword">=</span> <span class="Number">0</span>; __c <span class="Keyword">&lt;</span> __b.<span class="LibraryConstant">length</span>; __c<span class="Keyword">++</span>) {
food <span class="Keyword">=</span> __b[__c]; food <span class="Keyword">=</span> __b[__c];
__a.<span class="LibraryFunction">push</span>(eat(food)); __a.<span class="LibraryFunction">push</span>(eat(food));
} }
@@ -717,10 +774,10 @@ lunch <span class="Keyword">=</span> (<span class="Storage">function</span>() {
})(); })();
<span class="Comment"><span class="Comment">//</span> Naive collision detection.</span> <span class="Comment"><span class="Comment">//</span> Naive collision detection.</span>
__d <span class="Keyword">=</span> asteroids; __d <span class="Keyword">=</span> asteroids;
<span class="Keyword">for</span> (__e<span class="Keyword">=</span><span class="Number">0</span>; __e<span class="Keyword">&lt;</span>__d.<span class="LibraryConstant">length</span>; __e<span class="Keyword">++</span>) { <span class="Keyword">for</span> (__e <span class="Keyword">=</span> <span class="Number">0</span>; __e <span class="Keyword">&lt;</span> __d.<span class="LibraryConstant">length</span>; __e<span class="Keyword">++</span>) {
roid <span class="Keyword">=</span> __d[__e]; roid <span class="Keyword">=</span> __d[__e];
__f <span class="Keyword">=</span> asteroids; __f <span class="Keyword">=</span> asteroids;
<span class="Keyword">for</span> (__g<span class="Keyword">=</span><span class="Number">0</span>; __g<span class="Keyword">&lt;</span>__f.<span class="LibraryConstant">length</span>; __g<span class="Keyword">++</span>) { <span class="Keyword">for</span> (__g <span class="Keyword">=</span> <span class="Number">0</span>; __g <span class="Keyword">&lt;</span> __f.<span class="LibraryConstant">length</span>; __g<span class="Keyword">++</span>) {
roid2 <span class="Keyword">=</span> __f[__g]; roid2 <span class="Keyword">=</span> __f[__g];
<span class="Keyword">if</span> (roid <span class="Keyword">!</span><span class="Keyword">==</span> roid2) { <span class="Keyword">if</span> (roid <span class="Keyword">!</span><span class="Keyword">==</span> roid2) {
<span class="Keyword">if</span> (roid.overlaps(roid2)) { <span class="Keyword">if</span> (roid.overlaps(roid2)) {
@@ -791,6 +848,7 @@ egg_delivery = function egg_delivery() {
<span class="FunctionName">ages</span><span class="Keyword">:</span> <span class="Keyword">for</span> child, age <span class="Keyword">of</span> years_old <span class="FunctionName">ages</span><span class="Keyword">:</span> <span class="Keyword">for</span> child, age <span class="Keyword">of</span> years_old
child <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> is <span class="String">&quot;</span></span> <span class="Keyword">+</span> age child <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> is <span class="String">&quot;</span></span> <span class="Keyword">+</span> age
</pre><pre class="idle"><span class="Storage">var</span> __a, __b, age, ages, child, years_old; </pre><pre class="idle"><span class="Storage">var</span> __a, __b, age, ages, child, years_old;
<span class="Storage">var</span> __hasProp <span class="Keyword">=</span> <span class="LibraryClassType">Object</span>.<span class="LibraryConstant">prototype</span>.hasOwnProperty;
years_old <span class="Keyword">=</span> { years_old <span class="Keyword">=</span> {
max: <span class="Number">10</span>, max: <span class="Number">10</span>,
ida: <span class="Number">9</span>, ida: <span class="Number">9</span>,
@@ -800,13 +858,14 @@ ages <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> years_old; __a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> years_old;
<span class="Keyword">for</span> (child <span class="Keyword">in</span> __b) { <span class="Keyword">for</span> (child <span class="Keyword">in</span> __b) {
age <span class="Keyword">=</span> __b[child]; age <span class="Keyword">=</span> __b[child];
<span class="Keyword">if</span> (__b.hasOwnProperty(child)) { <span class="Keyword">if</span> (__hasProp.<span class="LibraryFunction">call</span>(__b, child)) {
__a.<span class="LibraryFunction">push</span>(child <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> is <span class="String">&quot;</span></span> <span class="Keyword">+</span> age); __a.<span class="LibraryFunction">push</span>(child <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> is <span class="String">&quot;</span></span> <span class="Keyword">+</span> age);
} }
} }
<span class="Keyword">return</span> __a; <span class="Keyword">return</span> __a;
})(); })();
</pre><button onclick='javascript: var __a, __b, age, ages, child, years_old; </pre><button onclick='javascript: var __a, __b, age, ages, child, years_old;
var __hasProp = Object.prototype.hasOwnProperty;
years_old = { years_old = {
max: 10, max: 10,
ida: 9, ida: 9,
@@ -816,7 +875,7 @@ ages = (function() {
__a = []; __b = years_old; __a = []; __b = years_old;
for (child in __b) { for (child in __b) {
age = __b[child]; age = __b[child];
if (__b.hasOwnProperty(child)) { if (__hasProp.call(__b, child)) {
__a.push(child + " is " + age); __a.push(child + " is " + age);
} }
} }
@@ -917,9 +976,9 @@ eldest = 24 > 21 ? "Liz" : "Ike";
</p> </p>
<div class='code'><pre class="idle"><span class="FunctionName">six</span><span class="Keyword">:</span> (<span class="FunctionName">one</span><span class="Keyword">:</span> <span class="Number">1</span>) <span class="Keyword">+</span> (<span class="FunctionName">two</span><span class="Keyword">:</span> <span class="Number">2</span>) <span class="Keyword">+</span> (<span class="FunctionName">three</span><span class="Keyword">:</span> <span class="Number">3</span>) <div class='code'><pre class="idle"><span class="FunctionName">six</span><span class="Keyword">:</span> (<span class="FunctionName">one</span><span class="Keyword">:</span> <span class="Number">1</span>) <span class="Keyword">+</span> (<span class="FunctionName">two</span><span class="Keyword">:</span> <span class="Number">2</span>) <span class="Keyword">+</span> (<span class="FunctionName">three</span><span class="Keyword">:</span> <span class="Number">3</span>)
</pre><pre class="idle"><span class="Storage">var</span> one, six, three, two; </pre><pre class="idle"><span class="Storage">var</span> one, six, three, two;
six <span class="Keyword">=</span> (one <span class="Keyword">=</span> <span class="Number">1</span>) <span class="Keyword">+</span> (two <span class="Keyword">=</span> <span class="Number">2</span>) <span class="Keyword">+</span> (three <span class="Keyword">=</span> <span class="Number">3</span>); six <span class="Keyword">=</span> ((one <span class="Keyword">=</span> <span class="Number">1</span>)) <span class="Keyword">+</span> ((two <span class="Keyword">=</span> <span class="Number">2</span>)) <span class="Keyword">+</span> ((three <span class="Keyword">=</span> <span class="Number">3</span>));
</pre><button onclick='javascript: var one, six, three, two; </pre><button onclick='javascript: var one, six, three, two;
six = (one = 1) + (two = 2) + (three = 3); six = ((one = 1)) + ((two = 2)) + ((three = 3));
;alert(six);'>run: six</button><br class='clear' /></div> ;alert(six);'>run: six</button><br class='clear' /></div>
<p> <p>
Things that would otherwise be statements in JavaScript, when used Things that would otherwise be statements in JavaScript, when used
@@ -931,22 +990,24 @@ six = (one = 1) + (two = 2) + (three = 3);
<span class="FunctionName">globals</span><span class="Keyword">:</span> (name <span class="Keyword">for</span> name <span class="Keyword">of</span> window)[<span class="Number">0</span>...<span class="Number">10</span>] <span class="FunctionName">globals</span><span class="Keyword">:</span> (name <span class="Keyword">for</span> name <span class="Keyword">of</span> window)[<span class="Number">0</span>...<span class="Number">10</span>]
</pre><pre class="idle"><span class="Storage">var</span> __a, __b, globals, name; </pre><pre class="idle"><span class="Storage">var</span> __a, __b, globals, name;
<span class="Storage">var</span> __hasProp <span class="Keyword">=</span> <span class="LibraryClassType">Object</span>.<span class="LibraryConstant">prototype</span>.hasOwnProperty;
<span class="Comment"><span class="Comment">//</span> The first ten global properties.</span> <span class="Comment"><span class="Comment">//</span> The first ten global properties.</span>
globals <span class="Keyword">=</span> ((<span class="Storage">function</span>() { globals <span class="Keyword">=</span> ((<span class="Storage">function</span>() {
__a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> <span class="LibraryClassType">window</span>; __a <span class="Keyword">=</span> []; __b <span class="Keyword">=</span> <span class="LibraryClassType">window</span>;
<span class="Keyword">for</span> (name <span class="Keyword">in</span> __b) { <span class="Keyword">for</span> (name <span class="Keyword">in</span> __b) {
<span class="Keyword">if</span> (__b.hasOwnProperty(name)) { <span class="Keyword">if</span> (__hasProp.<span class="LibraryFunction">call</span>(__b, name)) {
__a.<span class="LibraryFunction">push</span>(name); __a.<span class="LibraryFunction">push</span>(name);
} }
} }
<span class="Keyword">return</span> __a; <span class="Keyword">return</span> __a;
})()).<span class="LibraryFunction">slice</span>(<span class="Number">0</span>, <span class="Number">10</span>); })()).<span class="LibraryFunction">slice</span>(<span class="Number">0</span>, <span class="Number">10</span>);
</pre><button onclick='javascript: var __a, __b, globals, name; </pre><button onclick='javascript: var __a, __b, globals, name;
var __hasProp = Object.prototype.hasOwnProperty;
// The first ten global properties. // The first ten global properties.
globals = ((function() { globals = ((function() {
__a = []; __b = window; __a = []; __b = window;
for (name in __b) { for (name in __b) {
if (__b.hasOwnProperty(name)) { if (__hasProp.call(__b, name)) {
__a.push(name); __a.push(name);
} }
} }
@@ -978,6 +1039,12 @@ globals = ((function() {
} }
})()); })());
;'>run</button><br class='clear' /></div> ;'>run</button><br class='clear' /></div>
<p>
There are a handful of statements in JavaScript that can't be meaningfully
converted into expressions: <tt>break</tt>, <tt>continue</tt>,
and <tt>return</tt>. If you make use of them within a block of code,
CoffeeScript won't try to perform the conversion.
</p>
<p id="inheritance"> <p id="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b> <b class="header">Inheritance, and Calling Super from a Subclass</b>
@@ -1025,8 +1092,7 @@ tom.move()
</pre><pre class="idle"><span class="Storage">var</span> Animal, Horse, Snake, __a, __b, sam, tom; </pre><pre class="idle"><span class="Storage">var</span> Animal, Horse, Snake, __a, __b, sam, tom;
Animal <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Animal</span>() { Animal <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Animal</span>() { };
};
<span class="LibraryClassType">Animal</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span> <span class="FunctionName">move</span>(<span class="FunctionArgument">meters</span>) { <span class="LibraryClassType">Animal</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span> <span class="FunctionName">move</span>(<span class="FunctionArgument">meters</span>) {
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>); <span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>);
}; };
@@ -1063,8 +1129,7 @@ tom <span class="Keyword">=</span> <span class="Keyword">new</span> <span class=
sam.move(); sam.move();
tom.move(); tom.move();
</pre><button onclick='javascript: var Animal, Horse, Snake, __a, __b, sam, tom; </pre><button onclick='javascript: var Animal, Horse, Snake, __a, __b, sam, tom;
Animal = function Animal() { Animal = function Animal() { };
};
Animal.prototype.move = function move(meters) { Animal.prototype.move = function move(meters) {
return alert(this.name + " moved " + meters + "m."); return alert(this.name + " moved " + meters + "m.");
}; };
@@ -1230,6 +1295,38 @@ street = __c[0];
city = __c[1]; city = __c[1];
;alert(poet + " — " + street);'>run: poet + " — " + street</button><br class='clear' /></div> ;alert(poet + " — " + street);'>run: poet + " — " + street</button><br class='clear' /></div>
<p id="long_arrow">
<b class="header">Function binding</b>
The long arrow <tt>==></tt> can be used to both define a function, and to bind
it to the current value of <tt>this</tt>, right on the spot. This is helpful
when using callback-based libraries like Prototype or jQuery, for creating
iterator functions to pass to <tt>each</tt>, or event-handler functions
to use with <tt>bind</tt>. Functions created with the long arrow are able to access
properties of the <tt>this</tt> where they're defined.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">Account</span><span class="Keyword">:</span> <span class="FunctionArgument">customer, cart</span> <span class="Storage">=&gt;</span>
<span class="FunctionName">this.customer</span><span class="Keyword">:</span> customer
<span class="FunctionName">this.cart</span><span class="Keyword">:</span> cart
$(<span class="String"><span class="String">'</span>.shopping_cart<span class="String">'</span></span>).bind(<span class="String"><span class="String">'</span>click<span class="String">'</span></span>) <span class="FunctionName">event</span> <span class="Keyword">=</span><span class="Storage">=&gt;</span>
<span class="Variable">this</span>.customer.purchase(<span class="Variable">this</span>.cart)
</pre><pre class="idle"><span class="Storage">var</span> Account;
Account <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Account</span>(<span class="FunctionArgument">customer, cart</span>) {
<span class="Storage">var</span> __a;
<span class="Variable">this</span>.customer <span class="Keyword">=</span> customer;
<span class="Variable">this</span>.cart <span class="Keyword">=</span> cart;
__a <span class="Keyword">=</span> <span class="Keyword">$</span>(<span class="String"><span class="String">'</span>.shopping_cart<span class="String">'</span></span>).bind(<span class="String"><span class="String">'</span>click<span class="String">'</span></span>, (<span class="Storage">function</span>(__this) {
<span class="Storage">var</span> <span class="FunctionName">__func</span> = <span class="Storage">function</span>(<span class="FunctionArgument">event</span>) {
<span class="Keyword">return</span> <span class="Variable">this</span>.customer.purchase(<span class="Variable">this</span>.cart);
};
<span class="Keyword">return</span> (<span class="Storage">function</span>() {
<span class="Keyword">return</span> __func.<span class="LibraryFunction">apply</span>(__this, arguments);
});
})(<span class="Variable">this</span>));
<span class="Keyword">return</span> Account <span class="Keyword">===</span> <span class="Variable">this</span>.<span class="LibraryConstant">constructor</span> ? <span class="Variable">this</span> : __a;
};
</pre><br class='clear' /></div>
<p id="embedded"> <p id="embedded">
<b class="header">Embedded JavaScript</b> <b class="header">Embedded JavaScript</b>
Hopefully, you'll never need to use it, but if you ever need to intersperse Hopefully, you'll never need to use it, but if you ever need to intersperse
@@ -1261,25 +1358,33 @@ return [document.title, "Hello JavaScript"].join(": ");
in a returnable, assignable expression. The format is: <tt>switch</tt> condition, in a returnable, assignable expression. The format is: <tt>switch</tt> condition,
<tt>when</tt> clauses, <tt>else</tt> the default case. <tt>when</tt> clauses, <tt>else</tt> the default case.
</p> </p>
<p>
As in Ruby, <b>switch</b> statements in CoffeeScript can take multiple
values for each <b>when</b> clause. If any of the values match, the clause
runs.
</p>
<div class='code'><pre class="idle"><span class="Keyword">switch</span> day <div class='code'><pre class="idle"><span class="Keyword">switch</span> day
<span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Tuesday<span class="String">&quot;</span></span> <span class="Keyword">then</span> eat_breakfast() <span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Mon<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_to_work()
<span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Wednesday<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_to_the_park() <span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Tue<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_to_the_park()
<span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Saturday<span class="String">&quot;</span></span> <span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Thu<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_ice_fishing()
<span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Fri<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>Sat<span class="String">&quot;</span></span>
<span class="Keyword">if</span> day <span class="Keyword">is</span> bingo_day <span class="Keyword">if</span> day <span class="Keyword">is</span> bingo_day
go_to_bingo() go_to_bingo()
go_dancing() go_dancing()
<span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Sunday<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_to_church() <span class="Keyword">when</span> <span class="String"><span class="String">&quot;</span>Sun<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_to_church()
<span class="Keyword">else</span> go_to_work() <span class="Keyword">else</span> go_to_work()
</pre><pre class="idle"><span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Tuesday<span class="String">&quot;</span></span>) { </pre><pre class="idle"><span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Mon<span class="String">&quot;</span></span>) {
eat_breakfast(); go_to_work();
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Wednesday<span class="String">&quot;</span></span>) { } <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Tue<span class="String">&quot;</span></span>) {
go_to_the_park(); go_to_the_park();
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Saturday<span class="String">&quot;</span></span>) { } <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Thu<span class="String">&quot;</span></span>) {
go_ice_fishing();
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Fri<span class="String">&quot;</span></span> <span class="Keyword">||</span> day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Sat<span class="String">&quot;</span></span>) {
<span class="Keyword">if</span> (day <span class="Keyword">===</span> bingo_day) { <span class="Keyword">if</span> (day <span class="Keyword">===</span> bingo_day) {
go_to_bingo(); go_to_bingo();
go_dancing(); go_dancing();
} }
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Sunday<span class="String">&quot;</span></span>) { } <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">&quot;</span>Sun<span class="String">&quot;</span></span>) {
go_to_church(); go_to_church();
} <span class="Keyword">else</span> { } <span class="Keyword">else</span> {
go_to_work(); go_to_work();
@@ -1308,6 +1413,26 @@ return [document.title, "Hello JavaScript"].join(": ");
} }
</pre><br class='clear' /></div> </pre><br class='clear' /></div>
<p id="comparisons">
<b class="header">Chained Comparisons</b>
CoffeeScript borrows
<a href="http://docs.python.org/reference/expressions.html#notin">chained comparisons</a>
from Python &mdash; making it easy to test if a value falls within a
certain range.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">cholesterol</span><span class="Keyword">:</span> <span class="Number">127</span>
<span class="FunctionName">healthy</span><span class="Keyword">:</span> <span class="Number">200</span> <span class="Keyword">&gt;</span> cholesterol <span class="Keyword">&gt;</span> <span class="Number">60</span>
</pre><pre class="idle"><span class="Storage">var</span> cholesterol, healthy;
cholesterol <span class="Keyword">=</span> <span class="Number">127</span>;
healthy <span class="Keyword">=</span> (<span class="Number">200</span> <span class="Keyword">&gt;</span> cholesterol) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> (cholesterol <span class="Keyword">&gt;</span> <span class="Number">60</span>);
</pre><button onclick='javascript: var cholesterol, healthy;
cholesterol = 127;
healthy = (200 > cholesterol) && (cholesterol > 60);
;alert(healthy);'>run: healthy</button><br class='clear' /></div>
<p id="strings"> <p id="strings">
<b class="header">Multiline Strings and Heredocs</b> <b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript. Multiline strings are allowed in CoffeeScript.
@@ -1372,6 +1497,12 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
that includes CoffeeScript helpers, that includes CoffeeScript helpers,
bundling and minification. bundling and minification.
</li> </li>
<li>
<a href="http://github.com/inem/coffee-haml-filter">coffee-haml-filter</a><br />
A custom <a href="http://haml-lang.com/">HAML</a> filter, by
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
</li>
</ul> </ul>
<h2 id="contributing">Contributing</h2> <h2 id="contributing">Contributing</h2>
@@ -1406,7 +1537,24 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
</ul> </ul>
<h2 id="change_log">Change Log</h2> <h2 id="change_log">Change Log</h2>
<p>
<b class="header" style="margin-top: 20px;">0.2.6</b>
Added Python-style chained comparisons, the conditional existence
operator <tt>?=</tt>, and some examples from <i>Beautiful Code</i>.
Bugfixes relating to statement-to-expression conversion, arguments-to-array
conversion, and the TextMate syntax highlighter.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.2.5</b>
The conditions in switch statements can now take multiple values at once &mdash;
If any of them are true, the case will run. Added the long arrow <tt>==></tt>,
which defines and immediately binds a function to <tt>this</tt>. While loops can
now be used as expressions, in the same way that comprehensions can. Splats
can be used within pattern matches to soak up the rest of an array.
</p>
<p> <p>
<b class="header" style="margin-top: 20px;">0.2.4</b> <b class="header" style="margin-top: 20px;">0.2.4</b>
Added ECMAScript Harmony style destructuring assignment, for dealing with Added ECMAScript Harmony style destructuring assignment, for dealing with
@@ -1450,7 +1598,7 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
<b class="header" style="margin-top: 20px;">0.2.0</b> <b class="header" style="margin-top: 20px;">0.2.0</b>
Major release. Significant whitespace. Better statement-to-expression Major release. Significant whitespace. Better statement-to-expression
conversion. Splats. Splice literals. Object comprehensions. Blocks. conversion. Splats. Splice literals. Object comprehensions. Blocks.
The existence operator. Many thanks to all the folks who posted issues, The existential operator. Many thanks to all the folks who posted issues,
with special thanks to with special thanks to
<a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace <a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace
and expression help. and expression help.

View File

@@ -10,7 +10,7 @@ require "coffee_script/parse_error"
# Namespace for all CoffeeScript internal classes. # Namespace for all CoffeeScript internal classes.
module CoffeeScript module CoffeeScript
VERSION = '0.2.4' # Keep in sync with the gemspec. VERSION = '0.2.6' # Keep in sync with the gemspec.
# Compile a script (String or IO) to JavaScript. # Compile a script (String or IO) to JavaScript.
def self.compile(script, options={}) def self.compile(script, options={})

View File

@@ -43,7 +43,7 @@
<key>comment</key> <key>comment</key>
<string>match stuff like: funcName: =&gt; … </string> <string>match stuff like: funcName: =&gt; … </string>
<key>match</key> <key>match</key>
<string>([a-zA-Z0-9_?.$:*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=&gt;)</string> <string>([a-zA-Z0-9_?.$:*]*?)\s*(=\b|:\b)\s*([\w,\s]*?)\s*(=+&gt;)</string>
<key>name</key> <key>name</key>
<string>meta.function.coffee</string> <string>meta.function.coffee</string>
</dict> </dict>
@@ -64,7 +64,7 @@
<key>comment</key> <key>comment</key>
<string>match stuff like: a =&gt; … </string> <string>match stuff like: a =&gt; … </string>
<key>match</key> <key>match</key>
<string>([a-zA-Z0-9_?., $:*]*)\s*(=&gt;)</string> <string>([a-zA-Z0-9_?., $*]*)\s*(=+&gt;)</string>
<key>name</key> <key>name</key>
<string>meta.inline.function.coffee</string> <string>meta.inline.function.coffee</string>
</dict> </dict>
@@ -230,6 +230,39 @@
<key>name</key> <key>name</key>
<string>comment.line.coffee</string> <string>comment.line.coffee</string>
</dict> </dict>
<dict>
<key>begin</key>
<string>(?&lt;=[=(:]|^|return)\s*(/)(?![/*+{}?])</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>end</key>
<string>(/)[igm]*</string>
<key>endCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
<key>name</key>
<string>string.regexp.coffee</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\.</string>
<key>name</key>
<string>constant.character.escape.coffee</string>
</dict>
</array>
</dict>
<dict> <dict>
<key>match</key> <key>match</key>
<string>\b(break|by|catch|continue|else|finally|for|in|of|if|return|switch|then|throw|try|unless|when|while)\b</string> <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 @@
</dict> </dict>
<dict> <dict>
<key>match</key> <key>match</key>
<string>\b([a-zA-Z$_](\w|\$|:|\.)*)(\:)\s</string> <string>\b([a-zA-Z$_](\w|\$|:|\.)*\s*(?=\:))</string>
<key>name</key> <key>name</key>
<string>variable.assignment.coffee</string> <string>variable.assignment.coffee</string>
<key>captures</key> <key>captures</key>
@@ -248,11 +281,6 @@
<key>name</key> <key>name</key>
<string>entity.name.function.coffee</string> <string>entity.name.function.coffee</string>
</dict> </dict>
<key>3</key>
<dict>
<key>name</key>
<string>keyword.operator.coffee</string>
</dict>
</dict> </dict>
</dict> </dict>
<dict> <dict>
@@ -297,39 +325,6 @@
<key>name</key> <key>name</key>
<string>constant.language.coffee</string> <string>constant.language.coffee</string>
</dict> </dict>
<dict>
<key>begin</key>
<string>(?&lt;=[=(:]|^|return)\s*(/)(?![/*+{}?])</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>end</key>
<string>(/)[igm]*</string>
<key>endCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
<key>name</key>
<string>string.regexp.coffee</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\.</string>
<key>name</key>
<string>constant.character.escape.coffee</string>
</dict>
</array>
</dict>
<dict> <dict>
<key>match</key> <key>match</key>
<string>\;</string> <string>\;</string>

View File

@@ -16,6 +16,7 @@ token ARGUMENTS
token NEWLINE token NEWLINE
token COMMENT token COMMENT
token JS token JS
token THIS
token INDENT OUTDENT token INDENT OUTDENT
# Declare order of operations. # Declare order of operations.
@@ -37,9 +38,9 @@ prechigh
right WHEN LEADING_WHEN IN OF BY right WHEN LEADING_WHEN IN OF BY
right THROW FOR NEW SUPER right THROW FOR NEW SUPER
left EXTENDS left EXTENDS
left ASSIGN '||=' '&&=' left ASSIGN '||=' '&&=' '?='
right RETURN right RETURN
right '=>' UNLESS IF ELSE WHILE right '=>' '==>' UNLESS IF ELSE WHILE
preclow preclow
rule rule
@@ -102,12 +103,12 @@ rule
| BREAK { result = LiteralNode.new(val[0]) } | BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) } | CONTINUE { result = LiteralNode.new(val[0]) }
| ARGUMENTS { result = LiteralNode.new(val[0]) } | ARGUMENTS { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(true) } | TRUE { result = LiteralNode.new(Value.new(true)) }
| FALSE { result = LiteralNode.new(false) } | FALSE { result = LiteralNode.new(Value.new(false)) }
| YES { result = LiteralNode.new(true) } | YES { result = LiteralNode.new(Value.new(true)) }
| NO { result = LiteralNode.new(false) } | NO { result = LiteralNode.new(Value.new(false)) }
| ON { result = LiteralNode.new(true) } | ON { result = LiteralNode.new(Value.new(true)) }
| OFF { result = LiteralNode.new(false) } | OFF { result = LiteralNode.new(Value.new(false)) }
; ;
# Assignment to a variable (or index). # Assignment to a variable (or index).
@@ -178,6 +179,7 @@ rule
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
@@ -186,6 +188,7 @@ rule
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
@@ -198,8 +201,14 @@ rule
# Function definition. # Function definition.
Code: Code:
ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) } ParamList FuncGlyph Block { result = CodeNode.new(val[0], val[2], val[1]) }
| "=>" Block { result = CodeNode.new([], val[1]) } | FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
;
# The symbols to signify functions, and bound functions.
FuncGlyph:
'=>' { result = :func }
| '==>' { result = :boundfunc }
; ;
# The parameters to a function definition. # The parameters to a function definition.
@@ -211,12 +220,12 @@ rule
# A Parameter (or ParamSplat) in a function definition. # A Parameter (or ParamSplat) in a function definition.
Param: Param:
PARAM PARAM
| PARAM "." "." "." { result = ParamSplatNode.new(val[0]) } | PARAM "." "." "." { result = SplatNode.new(val[0]) }
; ;
# A regular splat. # A regular splat.
Splat: Splat:
Expression "." "." "." { result = ArgSplatNode.new(val[0])} Expression "." "." "." { result = SplatNode.new(val[0]) }
; ;
# Expressions that can be treated as values. # Expressions that can be treated as values.
@@ -229,6 +238,7 @@ rule
| Range { result = ValueNode.new(val[0]) } | Range { result = ValueNode.new(val[0]) }
| Value Accessor { result = val[0] << val[1] } | Value Accessor { result = val[0] << val[1] }
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) } | Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
| THIS { result = ValueNode.new(ThisNode.new) }
; ;
# Accessing into an object or array, through dot or index notation. # Accessing into an object or array, through dot or index notation.
@@ -276,7 +286,6 @@ rule
Invocation: Invocation:
Value Arguments { result = CallNode.new(val[0], val[1]) } Value Arguments { result = CallNode.new(val[0], val[1]) }
| Invocation Arguments { result = CallNode.new(val[0], val[1]) } | Invocation Arguments { result = CallNode.new(val[0], val[1]) }
# | Invocation Code { result = val[0] << val[1] }
; ;
# The list of arguments to a function invocation. # The list of arguments to a function invocation.
@@ -287,7 +296,7 @@ rule
# Calling super. # Calling super.
Super: Super:
SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) } SUPER "(" ArgList ")" { result = CallNode.new(Value.new('super'), val[2]) }
; ;
# The range literal. # The range literal.
@@ -315,6 +324,12 @@ rule
| ArgList OUTDENT { result = val[0] } | ArgList OUTDENT { result = val[0] }
; ;
# Just simple, comma-separated, required arguments (no fancy syntax).
SimpleArgs:
Expression { result = val[0] }
| SimpleArgs "," Expression { result = ([val[0]] << val[2]).flatten }
;
# Try/catch/finally exception handling blocks. # Try/catch/finally exception handling blocks.
Try: Try:
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
@@ -342,6 +357,7 @@ rule
While: While:
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) } WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
| WHILE Expression { result = WhileNode.new(val[1], nil) } | WHILE Expression { result = WhileNode.new(val[1], nil) }
| Expression WHILE Expression { result = WhileNode.new(val[2], Expressions.wrap(val[0])) }
; ;
# Array comprehensions, including guard and current index. # Array comprehensions, including guard and current index.
@@ -384,8 +400,8 @@ rule
# An individual when. # An individual when.
When: When:
LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| LEADING_WHEN Expression Block | LEADING_WHEN SimpleArgs Block
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| Comment Terminator When { result = val[2].add_comment(val[0]) } | Comment Terminator When { result = val[2].add_comment(val[0]) }
; ;

View File

@@ -13,31 +13,34 @@ module CoffeeScript
"try", "catch", "finally", "throw", "try", "catch", "finally", "throw",
"break", "continue", "break", "continue",
"for", "in", "of", "by", "where", "while", "for", "in", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when", "switch", "when",
"super", "extends", "super", "extends",
"arguments", "arguments",
"delete", "instanceof", "typeof"] "this"]
# Token matching regexes. # Token matching regexes.
IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/ IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/
NUMBER = /\A(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i NUMBER = /\A(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m
HEREDOC = /\A("{6}|'{6}|"{3}\n?(\s*)(.*?)\n?(\s*)"{3}|'{3}\n?(\s*)(.*?)\n?(\s*)'{3})/m HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?(\s*)"{3}|'{3}\n?(.*?)\n?(\s*)'{3})/m
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ OPERATOR = /\A([+\*&|\/\-%=<>:!?]+)/
WHITESPACE = /\A([ \t]+)/ WHITESPACE = /\A([ \t]+)/
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/ COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
CODE = /\A(=>)/ CODE = /\A(=?=>)/
REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/ REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/ MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/
LAST_DENT = /\n([ \t]*)/ LAST_DENT = /\n([ \t]*)/
ASSIGNMENT = /\A(:|=)\Z/ ASSIGNMENT = /\A(:|=)\Z/
# Token cleaning regexes. # Token cleaning regexes.
JS_CLEANER = /(\A`|`\Z)/ JS_CLEANER = /(\A`|`\Z)/
MULTILINER = /\n/ MULTILINER = /\n/
STRING_NEWLINES = /\n\s*/
COMMENT_CLEANER = /(^\s*#|\n\s*$)/ COMMENT_CLEANER = /(^\s*#|\n\s*$)/
NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/ NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
HEREDOC_INDENT = /^\s+/
# Tokens which a regular expression will never immediately follow, but which # Tokens which a regular expression will never immediately follow, but which
# a division operator might. # a division operator might.
@@ -50,12 +53,12 @@ module CoffeeScript
# Scan by attempting to match tokens one character at a time. Slow and steady. # Scan by attempting to match tokens one character at a time. Slow and steady.
def tokenize(code) def tokenize(code)
@code = code.chomp # Cleanup code by remove extra line breaks @code = code.chomp # Cleanup code by remove extra line breaks
@i = 0 # Current character position we're parsing @i = 0 # Current character position we're parsing
@line = 1 # The current line. @line = 1 # The current line.
@indent = 0 # The current indent level. @indent = 0 # The current indent level.
@indents = [] # The stack of all indent levels we are currently within. @indents = [] # The stack of all indent levels we are currently within.
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while @i < @code.length while @i < @code.length
@chunk = @code[@i..-1] @chunk = @code[@i..-1]
extract_next_token extract_next_token
@@ -105,7 +108,7 @@ module CoffeeScript
# Matches strings, including multi-line strings. # Matches strings, including multi-line strings.
def string_token def string_token
return false unless string = @chunk[STRING, 1] return false unless string = @chunk[STRING, 1]
escaped = string.gsub(MULTILINER, " \\\n") escaped = string.gsub(STRING_NEWLINES, " \\\n")
token(:STRING, escaped) token(:STRING, escaped)
@line += string.count("\n") @line += string.count("\n")
@i += string.length @i += string.length
@@ -114,9 +117,10 @@ module CoffeeScript
# Matches heredocs, adjusting indentation to the correct level. # Matches heredocs, adjusting indentation to the correct level.
def heredoc_token def heredoc_token
return false unless match = @chunk.match(HEREDOC) return false unless match = @chunk.match(HEREDOC)
indent = match[2] || match[5] doc = match[2] || match[4]
doc = match[3] || match[6] indent = doc.scan(HEREDOC_INDENT).min
doc.gsub!(/\n#{indent}/, "\\n") doc.gsub!(/^#{indent}/, "")
doc.gsub!("\n", "\\n")
doc.gsub!('"', '\\"') doc.gsub!('"', '\\"')
token(:STRING, "\"#{doc}\"") token(:STRING, "\"#{doc}\"")
@line += match[1].count("\n") @line += match[1].count("\n")
@@ -153,7 +157,7 @@ module CoffeeScript
@line += indent.scan(MULTILINER).size @line += indent.scan(MULTILINER).size
@i += indent.size @i += indent.size
next_character = @chunk[MULTI_DENT, 4] next_character = @chunk[MULTI_DENT, 4]
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && last_value != "=>") no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && !last_value.match(CODE))
return suppress_newlines(indent) if no_newlines return suppress_newlines(indent) if no_newlines
size = indent.scan(LAST_DENT).last.last.length size = indent.scan(LAST_DENT).last.last.length
return newline_token(indent) if size == @indent return newline_token(indent) if size == @indent

View File

@@ -20,28 +20,31 @@
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every // Run a simple REPL, round-tripping to the CoffeeScript compiler for every
// command. // command.
exports.run = function run(args) { exports.run = function run(args) {
var __a, i, path, result; var __a, __b, i, path, result;
if (args.length) { if (args.length) {
__a = args; __a = args;
for (i=0; i<__a.length; i++) { for (i = 0; i < __a.length; i++) {
path = __a[i]; path = __a[i];
exports.evalCS(File.read(path)); exports.evalCS(File.read(path));
delete args[i]; delete args[i];
} }
return true; return true;
} }
__b = [];
while (true) { while (true) {
try { __b.push((function() {
system.stdout.write('coffee> ').flush(); try {
result = exports.evalCS(Readline.readline(), ['--globals']); system.stdout.write('coffee> ').flush();
if (result !== undefined) { result = exports.evalCS(Readline.readline(), ['--globals']);
print(result); if (result !== undefined) {
return print(result);
}
} catch (e) {
return print(e);
} }
} catch (e) { })());
print(e);
}
} }
return null; return __b;
}; };
// Compile a given CoffeeScript file into JavaScript. // Compile a given CoffeeScript file into JavaScript.
exports.compileFile = function compileFile(path) { exports.compileFile = function compileFile(path) {

View File

@@ -24,6 +24,19 @@ module CoffeeScript
class_eval "def statement_only?; true; end" class_eval "def statement_only?; true; end"
end end
# This node needs to know if it's being compiled as a top-level statement,
# in order to compile without special expression conversion.
def self.top_sensitive
class_eval "def top_sensitive?; true; end"
end
# Provide a quick implementation of a children method.
def self.children(*attributes)
attr_reader(*attributes)
attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
class_eval "def children; [#{attrs}].flatten.compact; end"
end
def write(code) def write(code)
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE'] puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
code code
@@ -36,15 +49,19 @@ module CoffeeScript
def compile(o={}) def compile(o={})
@options = o.dup @options = o.dup
@indent = o[:indent] @indent = o[:indent]
top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top) top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
closure = statement? && !statement_only? && !top && !@options[:return] closure = statement? && !statement_only? && !top && !@options[:return] && !self.is_a?(CommentNode)
closure ? compile_closure(@options) : compile_node(@options) closure &&= !contains? {|n| n.statement_only? }
closure ? compile_closure(@options) : compile_node(@options)
end end
def compile_closure(o={}) def compile_closure(o={})
indent = o[:indent] indent = o[:indent]
@indent = (o[:indent] = idt(1)) @indent = (o[:indent] = idt(1))
"(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()" pass_this = !o[:closure] && contains? {|node| node.is_a?(ThisNode) }
param = pass_this ? '__this' : ''
body = compile_node(o.merge(:return => true, :closure => true))
"(function(#{param}) {\n#{body}\n#{indent}})(#{pass_this ? 'this' : ''})"
end end
# Quick short method for the current indentation level, plus tabbing in. # Quick short method for the current indentation level, plus tabbing in.
@@ -52,19 +69,30 @@ module CoffeeScript
@indent + (TAB * tabs) @indent + (TAB * tabs)
end end
# Does this node, or any of it's children, contain a node of a certain kind?
def contains?(&block)
children.each do |node|
return true if yield(node)
return true if node.is_a?(Node) && node.contains?(&block)
end
false
end
# Default implementations of the common node methods. # Default implementations of the common node methods.
def unwrap; self; end def unwrap; self; end
def children; []; end
def statement?; false; end def statement?; false; end
def statement_only?; false; end def statement_only?; false; end
def top_sensitive?; false; end
end end
# A collection of nodes, each one representing an expression. # A collection of nodes, each one representing an expression.
class Expressions < Node class Expressions < Node
statement statement
attr_reader :expressions children :expressions
attr_accessor :function
TRAILING_WHITESPACE = /\s+$/ TRAILING_WHITESPACE = /\s+$/
UPPERCASE = /[A-Z]/
# Wrap up a node as an Expressions, unless it already is. # Wrap up a node as an Expressions, unless it already is.
def self.wrap(*nodes) def self.wrap(*nodes)
@@ -93,18 +121,17 @@ module CoffeeScript
@expressions.length == 1 ? @expressions.first : self @expressions.length == 1 ? @expressions.first : self
end end
# Is this an empty block of code?
def empty?
@expressions.empty?
end
# Is the node last in this block of expressions. # Is the node last in this block of expressions.
def last?(node) def last?(node)
@last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1 @last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
node == @expressions[@last_index] node == @expressions[@last_index]
end end
# Determine if this is the expressions body within a constructor function.
# Constructors are capitalized by CoffeeScript convention.
def constructor?(o)
o[:top] && o[:last_assign] && o[:last_assign][0..0][UPPERCASE]
end
def compile(o={}) def compile(o={})
o[:scope] ? super(o) : compile_root(o) o[:scope] ? super(o) : compile_root(o)
end end
@@ -118,7 +145,7 @@ module CoffeeScript
def compile_root(o={}) def compile_root(o={})
indent = o[:no_wrap] ? '' : TAB indent = o[:no_wrap] ? '' : TAB
@indent = indent @indent = indent
o.merge!(:indent => indent, :scope => Scope.new(nil, self)) o.merge!(:indent => indent, :scope => Scope.new(nil, self, nil))
code = o[:globals] ? compile_node(o) : compile_with_declarations(o) code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
code.gsub!(TRAILING_WHITESPACE, '') code.gsub!(TRAILING_WHITESPACE, '')
write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();") write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();")
@@ -128,8 +155,12 @@ module CoffeeScript
# at the top. # at the top.
def compile_with_declarations(o={}) def compile_with_declarations(o={})
code = compile_node(o) code = compile_node(o)
return code unless o[:scope].declarations?(self) args = self.contains? {|n| n.is_a?(LiteralNode) && n.arguments? }
write("#{idt}var #{o[:scope].declared_variables.join(', ')};\n#{code}") argv = args && o[:scope].check('arguments') ? '' : 'var '
code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
write(code)
end end
# Compiles a single expression within the expression list. # Compiles a single expression within the expression list.
@@ -143,10 +174,10 @@ module CoffeeScript
# If it's a statement, the node knows how to return itself. # If it's a statement, the node knows how to return itself.
return node.compile(o.merge(:return => true)) if node.statement? return node.compile(o.merge(:return => true)) if node.statement?
# If it's not part of a constructor, we can just return the value of the expression. # If it's not part of a constructor, we can just return the value of the expression.
return "#{idt}return #{node.compile(o)};" unless constructor?(o) return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor?
# It's the last line of a constructor, add a safety check. # It's the last line of a constructor, add a safety check.
temp = o[:scope].free_variable temp = o[:scope].free_variable
"#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:last_assign]} === this.constructor ? this : #{temp};" "#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};"
end end
end end
@@ -154,17 +185,12 @@ module CoffeeScript
# Literals are static values that have a Ruby representation, eg.: a string, a number, # Literals are static values that have a Ruby representation, eg.: a string, a number,
# true, false, nil, etc. # true, false, nil, etc.
class LiteralNode < Node class LiteralNode < Node
children :value
# Values of a literal node that much be treated as a statement -- no # Values of a literal node that much be treated as a statement -- no
# sense returning or assigning them. # sense returning or assigning them.
STATEMENTS = ['break', 'continue'] STATEMENTS = ['break', 'continue']
# If we get handed a literal reference to an arguments object, convert
# it to an array.
ARG_ARRAY = 'Array.prototype.slice.call(arguments, 0)'
attr_reader :value
# Wrap up a compiler-generated string as a LiteralNode. # Wrap up a compiler-generated string as a LiteralNode.
def self.wrap(string) def self.wrap(string)
self.new(Value.new(string)) self.new(Value.new(string))
@@ -179,19 +205,21 @@ module CoffeeScript
end end
alias_method :statement_only?, :statement? alias_method :statement_only?, :statement?
def arguments?
@value.to_s == 'arguments'
end
def compile_node(o) def compile_node(o)
@value = ARG_ARRAY if @value.to_s.to_sym == :arguments
indent = statement? ? idt : '' indent = statement? ? idt : ''
ending = statement? ? ';' : '' ending = statement? ? ';' : ''
write "#{indent}#{@value}#{ending}" "#{indent}#{@value}#{ending}"
end end
end end
# Return an expression, or wrap it in a closure and return it. # Return an expression, or wrap it in a closure and return it.
class ReturnNode < Node class ReturnNode < Node
statement_only statement_only
children :expression
attr_reader :expression
def initialize(expression) def initialize(expression)
@expression = expression @expression = expression
@@ -207,7 +235,7 @@ module CoffeeScript
# Pass through CoffeeScript comments into JavaScript comments at the # Pass through CoffeeScript comments into JavaScript comments at the
# same position. # same position.
class CommentNode < Node class CommentNode < Node
statement_only statement
def initialize(lines) def initialize(lines)
@lines = lines.value @lines = lines.value
@@ -223,47 +251,38 @@ module CoffeeScript
# Node for a function invocation. Takes care of converting super() calls into # Node for a function invocation. Takes care of converting super() calls into
# calls against the prototype's function of the same name. # calls against the prototype's function of the same name.
class CallNode < Node class CallNode < Node
attr_reader :variable, :arguments children :variable, :arguments
def initialize(variable, arguments=[]) def initialize(variable, arguments=[])
@variable, @arguments = variable, arguments @variable, @arguments = variable, arguments
@prefix = ''
end end
def new_instance def new_instance
@new = true @prefix = "new "
self self
end end
def super?
@variable == :super
end
def prefix
@new ? "new " : ''
end
def splat?
@arguments.any? {|a| a.is_a?(ArgSplatNode) }
end
def <<(argument) def <<(argument)
@arguments << argument @arguments << argument
self
end end
# Compile a vanilla function call. # Compile a vanilla function call.
def compile_node(o) def compile_node(o)
return write(compile_splat(o)) if splat? return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
args = @arguments.map{|a| a.compile(o) }.join(', ') args = @arguments.map{|a| a.compile(o) }.join(', ')
return write(compile_super(args, o)) if super? return write(compile_super(args, o)) if @variable == 'super'
write("#{prefix}#{@variable.compile(o)}(#{args})") write("#{@prefix}#{@variable.compile(o)}(#{args})")
end end
# Compile a call against the superclass's implementation of the current function. # Compile a call against the superclass's implementation of the current function.
def compile_super(args, o) def compile_super(args, o)
methname = o[:last_assign] methname = o[:scope].function.name
arg_part = args.empty? ? '' : ", #{args}" arg_part = args.empty? ? '' : ", #{args}"
meth = o[:proto_assign] ? "#{o[:proto_assign]}.__superClass__.#{methname}" : meth = o[:scope].function.proto ?
"#{methname}.__superClass__.constructor" "#{o[:scope].function.proto}.__superClass__.#{methname}" :
"#{methname}.__superClass__.constructor"
"#{meth}.call(this#{arg_part})" "#{meth}.call(this#{arg_part})"
end end
@@ -273,18 +292,26 @@ module CoffeeScript
obj = @variable.source || 'this' obj = @variable.source || 'this'
args = @arguments.map do |arg| args = @arguments.map do |arg|
code = arg.compile(o) code = arg.compile(o)
code = arg.is_a?(ArgSplatNode) ? code : "[#{code}]" code = arg.is_a?(SplatNode) ? code : "[#{code}]"
arg.equal?(@arguments.first) ? code : ".concat(#{code})" arg.equal?(@arguments.first) ? code : ".concat(#{code})"
end end
"#{prefix}#{meth}.apply(#{obj}, #{args.join('')})" "#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
end
# If the code generation wished to use the result of a function call
# in multiple places, ensure that the function is only ever called once.
def compile_reference(o)
reference = o[:scope].free_variable
call = ParentheticalNode.new(AssignNode.new(reference, self))
return call, reference
end end
end end
# Node to extend an object's prototype with an ancestor object. # Node to extend an object's prototype with an ancestor object.
# After goog.inherits from the Closure Library. # After goog.inherits from the Closure Library.
class ExtendsNode < Node class ExtendsNode < Node
children :sub_object, :super_object
statement statement
attr_reader :sub_object, :super_object
def initialize(sub_object, super_object) def initialize(sub_object, super_object)
@sub_object, @super_object = sub_object, super_object @sub_object, @super_object = sub_object, super_object
@@ -305,7 +332,8 @@ module CoffeeScript
# A value, indexed or dotted into, or vanilla. # A value, indexed or dotted into, or vanilla.
class ValueNode < Node class ValueNode < Node
attr_reader :base, :properties, :last, :source children :base, :properties
attr_reader :last, :source
def initialize(base, properties=[]) def initialize(base, properties=[])
@base, @properties = base, properties @base, @properties = base, properties
@@ -332,6 +360,10 @@ module CoffeeScript
properties? && @properties.last.is_a?(SliceNode) properties? && @properties.last.is_a?(SliceNode)
end end
def unwrap
@properties.empty? ? @base : self
end
# Values are statements if their base is a statement. # Values are statements if their base is a statement.
def statement? def statement?
@base.is_a?(Node) && @base.statement? && !properties? @base.is_a?(Node) && @base.statement? && !properties?
@@ -350,7 +382,7 @@ module CoffeeScript
# A dotted accessor into a part of a value, or the :: shorthand for # A dotted accessor into a part of a value, or the :: shorthand for
# an accessor into the object's prototype. # an accessor into the object's prototype.
class AccessorNode < Node class AccessorNode < Node
attr_reader :name children :name
def initialize(name, prototype=false) def initialize(name, prototype=false)
@name, @prototype = name, prototype @name, @prototype = name, prototype
@@ -364,7 +396,7 @@ module CoffeeScript
# An indexed accessor into a part of an array or object. # An indexed accessor into a part of an array or object.
class IndexNode < Node class IndexNode < Node
attr_reader :index children :index
def initialize(index) def initialize(index)
@index = index @index = index
@@ -375,10 +407,20 @@ module CoffeeScript
end end
end end
# A node to represent a reference to "this". Needs to be transformed into a
# reference to the correct value of "this", when used within a closure wrapper.
class ThisNode < Node
def compile_node(o)
write(o[:closure] ? "__this" : "this")
end
end
# A range literal. Ranges can be used to extract portions (slices) of arrays, # A range literal. Ranges can be used to extract portions (slices) of arrays,
# or to specify a range for array comprehensions. # or to specify a range for array comprehensions.
class RangeNode < Node class RangeNode < Node
attr_reader :from, :to children :from, :to
def initialize(from, to, exclusive=false) def initialize(from, to, exclusive=false)
@from, @to, @exclusive = from, to, exclusive @from, @to, @exclusive = from, to, exclusive
@@ -421,7 +463,7 @@ module CoffeeScript
# specifies the index of the end of the slice (just like the first parameter) # specifies the index of the end of the slice (just like the first parameter)
# is the index of the beginning. # is the index of the beginning.
class SliceNode < Node class SliceNode < Node
attr_reader :range children :range
def initialize(range) def initialize(range)
@range = range @range = range
@@ -437,33 +479,43 @@ module CoffeeScript
# Setting the value of a local variable, or the value of an object property. # Setting the value of a local variable, or the value of an object property.
class AssignNode < Node class AssignNode < Node
top_sensitive
children :variable, :value
PROTO_ASSIGN = /\A(\S+)\.prototype/ PROTO_ASSIGN = /\A(\S+)\.prototype/
LEADING_DOT = /\A\.(prototype\.)?/ LEADING_DOT = /\A\.(prototype\.)?/
attr_reader :variable, :value, :context
def initialize(variable, value, context=nil) def initialize(variable, value, context=nil)
@variable, @value, @context = variable, value, context @variable, @value, @context = variable, value, context
end end
def compile_node(o) def compile_node(o)
return compile_pattern_match(o) if @variable.array? || @variable.object? top = o.delete(:top)
return compile_splice(o) if @variable.splice? return compile_pattern_match(o) if statement?
stmt = o.delete(:as_statement) return compile_splice(o) if value? && @variable.splice?
name = @variable.compile(o) stmt = o.delete(:as_statement)
last = @variable.last.to_s.sub(LEADING_DOT, '') name = @variable.compile(o)
proto = name[PROTO_ASSIGN, 1] last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
o = o.merge(:last_assign => last, :proto_assign => proto) proto = name[PROTO_ASSIGN, 1]
o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER) if @value.is_a?(CodeNode)
@value.name = last if last.match(Lexer::IDENTIFIER)
@value.proto = proto if proto
end
return write("#{name}: #{@value.compile(o)}") if @context == :object return write("#{name}: #{@value.compile(o)}") if @context == :object
o[:scope].find(name) unless @variable.properties? o[:scope].find(name) unless value? && @variable.properties?
val = "#{name} = #{@value.compile(o)}" val = "#{name} = #{@value.compile(o)}"
return write("#{idt}#{val};") if stmt return write("#{idt}#{val};") if stmt
write(o[:return] ? "#{idt}return (#{val})" : val) val = "(#{val})" if !top || o[:return]
val = "#{idt}return #{val}" if o[:return]
write(val)
end
def value?
@variable.is_a?(ValueNode)
end end
def statement? def statement?
@variable.array? || @variable.object? value? && (@variable.array? || @variable.object?)
end end
# Implementation of recursive pattern matching, when assigning array or # Implementation of recursive pattern matching, when assigning array or
@@ -476,9 +528,12 @@ module CoffeeScript
@variable.base.objects.each_with_index do |obj, i| @variable.base.objects.each_with_index do |obj, i|
obj, i = obj.value, obj.variable.base if @variable.object? obj, i = obj.value, obj.variable.base if @variable.object?
access_class = @variable.array? ? IndexNode : AccessorNode access_class = @variable.array? ? IndexNode : AccessorNode
assigns << AssignNode.new( if obj.is_a?(SplatNode)
obj, ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))]) val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
).compile(o) else
val = ValueNode.new(val_var, [access_class.new(Value.new(i.to_s))])
end
assigns << AssignNode.new(obj, val).compile(o)
end end
write(assigns.join("\n")) write(assigns.join("\n"))
end end
@@ -496,6 +551,10 @@ module CoffeeScript
# Simple Arithmetic and logical operations. Performs some conversion from # Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents. # CoffeeScript operations into their JavaScript equivalents.
class OpNode < Node class OpNode < Node
children :first, :second
attr_reader :operator
attr_accessor :second
CONVERSIONS = { CONVERSIONS = {
:== => "===", :== => "===",
:'!=' => "!==", :'!=' => "!==",
@@ -505,11 +564,10 @@ module CoffeeScript
:isnt => "!==", :isnt => "!==",
:not => '!' :not => '!'
} }
CONDITIONALS = [:'||=', :'&&='] CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
ASSIGNMENT = [:'||=', :'&&=', :'?=']
PREFIX_OPERATORS = [:typeof, :delete] PREFIX_OPERATORS = [:typeof, :delete]
attr_reader :operator, :first, :second
def initialize(operator, first, second=nil, flip=false) def initialize(operator, first, second=nil, flip=false)
@first, @second, @flip = first, second, flip @first, @second, @flip = first, second, flip
@operator = CONVERSIONS[operator.to_sym] || operator @operator = CONVERSIONS[operator.to_sym] || operator
@@ -519,18 +577,39 @@ module CoffeeScript
@second.nil? @second.nil?
end end
def chainable?
CHAINABLE.include?(operator.to_sym)
end
def compile_node(o) def compile_node(o)
return write(compile_conditional(o)) if CONDITIONALS.include?(@operator.to_sym) return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
return write(compile_unary(o)) if unary? return write(compile_unary(o)) if unary?
return write(compile_existence(o)) if @operator == '?'
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}") write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
end end
def compile_conditional(o) # Mimic Python's chained comparisons. See:
# http://docs.python.org/reference/expressions.html#notin
def compile_chain(o)
shared = @first.unwrap.second
@first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
"(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
end
def compile_assignment(o)
first, second = @first.compile(o), @second.compile(o) first, second = @first.compile(o), @second.compile(o)
o[:scope].find(first) if @first.unwrap.is_a?(Value)
sym = @operator[0..1] sym = @operator[0..1]
return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
"#{first} = #{first} #{sym} #{second}" "#{first} = #{first} #{sym} #{second}"
end end
def compile_existence(o)
first, second = @first.compile(o), @second.compile(o)
"#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
end
def compile_unary(o) def compile_unary(o)
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : '' space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
parts = [@operator.to_s, space, @first.compile(o)] parts = [@operator.to_s, space, @first.compile(o)]
@@ -540,66 +619,79 @@ module CoffeeScript
end end
# A function definition. The only node that creates a new Scope. # A function definition. The only node that creates a new Scope.
# A CodeNode does not have any children -- they're within the new scope.
class CodeNode < Node class CodeNode < Node
attr_reader :params, :body top_sensitive
attr_reader :params, :body, :bound
attr_accessor :name, :proto
def initialize(params, body) # Constructor functions start with an uppercase letter, by convention.
UPPERCASE = /[A-Z]/
def initialize(params, body, tag=nil)
@params = params @params = params
@body = body @body = body
@bound = tag == :boundfunc
end
def constructor?
@name && @name[0..0][UPPERCASE]
end end
def compile_node(o) def compile_node(o)
shared_scope = o.delete(:shared_scope) shared_scope = o.delete(:shared_scope)
o[:scope] = shared_scope || Scope.new(o[:scope], @body) top = o.delete(:top)
o[:scope] = shared_scope || Scope.new(o[:scope], @body, self)
o[:return] = true o[:return] = true
o[:top] = true o[:top] = true
o[:indent] = idt(1) o[:indent] = idt(@bound ? 2 : 1)
o.delete(:no_wrap) o.delete(:no_wrap)
o.delete(:globals) o.delete(:globals)
name = o.delete(:immediate_assign) o.delete(:closure)
if @params.last.is_a?(ParamSplatNode) if @params.last.is_a?(SplatNode)
splat = @params.pop splat = @params.pop
splat.index = @params.length splat.index = @params.length
@body.unshift(splat) @body.unshift(splat)
end end
@params.each {|id| o[:scope].parameter(id.to_s) } @params.each {|id| o[:scope].parameter(id.to_s) }
code = @body.compile_with_declarations(o) code = @body.empty? ? "" : "\n#{@body.compile_with_declarations(o)}\n"
name_part = name ? " #{name}" : '' name_part = @name ? " #{@name}" : ''
write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{idt}}") func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt(@bound ? 1 : 0)}}"
func = "(#{func})" if top && !@bound
return write(func) unless @bound
inner = "(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
write("(function(__this) {\n#{idt(1)}var __func = #{func};\n#{idt(1)}return #{inner}\n#{idt}})(this)")
end end
end end
# A parameter splat in a function definition. # A splat, either as a parameter to a function, an argument to a call,
class ParamSplatNode < Node # or in a destructuring assignment.
class SplatNode < Node
children :name
attr_accessor :index attr_accessor :index
attr_reader :name
def initialize(name) def initialize(name)
@name = name @name = name
end end
def compile_node(o={}) def compile_node(o={})
write(@index ? compile_param(o) : @name.compile(o))
end
def compile_param(o)
o[:scope].find(@name) o[:scope].find(@name)
write("#{@name} = Array.prototype.slice.call(arguments, #{@index})") "#{@name} = Array.prototype.slice.call(arguments, #{@index})"
end
end
class ArgSplatNode < Node
attr_reader :value
def initialize(value)
@value = value
end end
def compile_node(o={}) def compile_value(o, name, index)
write(@value.compile(o)) "Array.prototype.slice.call(#{name}, #{index})"
end end
end end
# An object literal. # An object literal.
class ObjectNode < Node class ObjectNode < Node
attr_reader :properties children :properties
alias_method :objects, :properties alias_method :objects, :properties
def initialize(properties = []) def initialize(properties = [])
@@ -626,7 +718,7 @@ module CoffeeScript
# An array literal. # An array literal.
class ArrayNode < Node class ArrayNode < Node
attr_reader :objects children :objects
def initialize(objects=[]) def initialize(objects=[])
@objects = objects @objects = objects
@@ -644,25 +736,45 @@ module CoffeeScript
end end
end end
# A faux-node that is never created by the grammar, but is used during
# code generation to generate a quick "array.push(value)" tree of nodes.
class PushNode
def self.wrap(array, expressions)
expr = expressions.unwrap
return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
Expressions.wrap(CallNode.new(
ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
[expr]
))
end
end
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From # A while loop, the only sort of low-level loop exposed by CoffeeScript. From
# it, all other loops can be manufactured. # it, all other loops can be manufactured.
class WhileNode < Node class WhileNode < Node
top_sensitive
children :condition, :body
statement statement
attr_reader :condition, :body
def initialize(condition, body) def initialize(condition, body)
@condition, @body = condition, body @condition, @body = condition, body
end end
def compile_node(o) def compile_node(o)
returns = o.delete(:return) returns = o.delete(:return)
top = o.delete(:top) && !returns
o[:indent] = idt(1) o[:indent] = idt(1)
o[:top] = true o[:top] = true
cond = @condition.compile(o) cond = @condition.compile(o)
post = returns ? "\n#{idt}return null;" : '' set = ''
return write("#{idt}while (#{cond}) null;#{post}") if @body.nil? if !top
write("#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}") rvar = o[:scope].free_variable
set = "#{idt}#{rvar} = [];\n"
@body = PushNode.wrap(rvar, @body)
end
post = returns ? "\n#{idt}return #{rvar};" : ''
return write("#{set}#{idt}while (#{cond}) null;#{post}") if @body.nil?
write("#{set}#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
end end
end end
@@ -671,10 +783,11 @@ module CoffeeScript
# of the comprehenion. Unlike Python array comprehensions, it's able to pass # of the comprehenion. Unlike Python array comprehensions, it's able to pass
# the current index of the loop as a second parameter. # the current index of the loop as a second parameter.
class ForNode < Node class ForNode < Node
top_sensitive
children :body, :source, :filter
attr_reader :name, :index, :step
statement statement
attr_reader :body, :source, :name, :index, :filter, :step
def initialize(body, source, name, index=nil) def initialize(body, source, name, index=nil)
@body, @name, @index = body, name, index @body, @name, @index = body, name, index
@source = source[:source] @source = source[:source]
@@ -703,7 +816,7 @@ module CoffeeScript
else else
index_var = nil index_var = nil
source_part = "#{svar} = #{source.compile(o)};\n#{idt}" source_part = "#{svar} = #{source.compile(o)};\n#{idt}"
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar}=0; #{ivar}<#{svar}.length; #{ivar}++" for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{ivar}++"
var_part = @name ? "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" : '' var_part = @name ? "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" : ''
end end
body = @body body = @body
@@ -712,9 +825,7 @@ module CoffeeScript
if top_level if top_level
body = Expressions.wrap(body) body = Expressions.wrap(body)
else else
body = Expressions.wrap(CallNode.new( body = PushNode.wrap(rvar, body)
ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [body.unwrap]
))
end end
if o[:return] if o[:return]
return_result = "return #{return_result}" if o[:return] return_result = "return #{return_result}" if o[:return]
@@ -724,11 +835,13 @@ module CoffeeScript
body = Expressions.wrap(IfNode.new(@filter, body)) body = Expressions.wrap(IfNode.new(@filter, body))
end end
if @object if @object
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
body = Expressions.wrap(IfNode.new( body = Expressions.wrap(IfNode.new(
CallNode.new(ValueNode.new(LiteralNode.wrap(svar), [AccessorNode.new(Value.new('hasOwnProperty'))]), [LiteralNode.wrap(ivar)]), CallNode.new(
Expressions.wrap(body), ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
nil, [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
{:statement => true} ),
Expressions.wrap(body), nil, {:statement => true}
)) ))
end end
@@ -741,10 +854,10 @@ module CoffeeScript
# A try/catch/finally block. # A try/catch/finally block.
class TryNode < Node class TryNode < Node
children :try, :recovery, :finally
attr_reader :error
statement statement
attr_reader :try, :error, :recovery, :finally
def initialize(try, error, recovery, finally=nil) def initialize(try, error, recovery, finally=nil)
@try, @error, @recovery, @finally = try, error, recovery, finally @try, @error, @recovery, @finally = try, error, recovery, finally
end end
@@ -761,10 +874,9 @@ module CoffeeScript
# Throw an exception. # Throw an exception.
class ThrowNode < Node class ThrowNode < Node
children :expression
statement_only statement_only
attr_reader :expression
def initialize(expression) def initialize(expression)
@expression = expression @expression = expression
end end
@@ -776,15 +888,20 @@ module CoffeeScript
# Check an expression for existence (meaning not null or undefined). # Check an expression for existence (meaning not null or undefined).
class ExistenceNode < Node class ExistenceNode < Node
attr_reader :expression children :expression
def self.compile_test(o, variable)
first, second = variable, variable
first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
"(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
end
def initialize(expression) def initialize(expression)
@expression = expression @expression = expression
end end
def compile_node(o) def compile_node(o)
val = @expression.compile(o) write(ExistenceNode.compile_test(o, @expression))
write("(typeof #{val} !== \"undefined\" && #{val} !== null)")
end end
end end
@@ -792,7 +909,7 @@ module CoffeeScript
# You can't wrap parentheses around bits that get compiled into JS statements, # You can't wrap parentheses around bits that get compiled into JS statements,
# unfortunately. # unfortunately.
class ParentheticalNode < Node class ParentheticalNode < Node
attr_reader :expressions children :expressions
def initialize(expressions, line=nil) def initialize(expressions, line=nil)
@expressions = expressions.unwrap @expressions = expressions.unwrap
@@ -811,13 +928,14 @@ module CoffeeScript
# Single-expression IfNodes are compiled into ternary operators if possible, # Single-expression IfNodes are compiled into ternary operators if possible,
# because ternaries are first-class returnable assignable expressions. # because ternaries are first-class returnable assignable expressions.
class IfNode < Node class IfNode < Node
attr_reader :condition, :body, :else_body children :condition, :body, :else_body
def initialize(condition, body, else_body=nil, tags={}) def initialize(condition, body, else_body=nil, tags={})
@condition = condition @condition = condition
@body = body && body.unwrap @body = body && body.unwrap
@else_body = else_body && else_body.unwrap @else_body = else_body && else_body.unwrap
@tags = tags @tags = tags
@multiple = true if @condition.is_a?(Array)
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert] @condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
end end
@@ -839,7 +957,8 @@ module CoffeeScript
# Rewrite a chain of IfNodes with their switch condition for equality. # Rewrite a chain of IfNodes with their switch condition for equality.
def rewrite_condition(expression) def rewrite_condition(expression)
@condition = OpNode.new("is", expression, @condition) @condition = @multiple ? @condition.map {|c| OpNode.new("is", expression, c) } :
OpNode.new("is", expression, @condition)
@else_body.rewrite_condition(expression) if chain? @else_body.rewrite_condition(expression) if chain?
self self
end end
@@ -861,6 +980,10 @@ module CoffeeScript
@is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?)) @is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
end end
def compile_condition(o)
[@condition].flatten.map {|c| c.compile(o) }.join(' || ')
end
def compile_node(o) def compile_node(o)
write(statement? ? compile_statement(o) : compile_ternary(o)) write(statement? ? compile_statement(o) : compile_ternary(o))
end end
@@ -876,7 +999,8 @@ module CoffeeScript
if_dent = child ? '' : idt if_dent = child ? '' : idt
com_dent = child ? idt : '' com_dent = child ? idt : ''
prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : '' prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
if_part = "#{prefix}#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}" body = Expressions.wrap(@body).compile(o)
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
return if_part unless @else_body return if_part unless @else_body
else_part = chain? ? else_part = chain? ?
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" : " else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :

View File

@@ -5,6 +5,12 @@ module CoffeeScript
# line-number aware. # line-number aware.
class ParseError < Racc::ParseError class ParseError < Racc::ParseError
TOKEN_MAP = {
'INDENT' => 'indent',
'OUTDENT' => 'outdent',
"\n" => 'newline'
}
def initialize(token_id, value, stack) def initialize(token_id, value, stack)
@token_id, @value, @stack = token_id, value, stack @token_id, @value, @stack = token_id, value, stack
end end
@@ -13,7 +19,7 @@ module CoffeeScript
line = @value.respond_to?(:line) ? @value.line : "END" line = @value.respond_to?(:line) ? @value.line : "END"
line_part = "line #{line}:" line_part = "line #{line}:"
id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.to_s.downcase}" : "" id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.to_s.downcase}" : ""
val_part = ['INDENT', 'OUTDENT'].include?(@token_id) ? '' : " for '#{@value.to_s}'" val_part = " for #{TOKEN_MAP[@value.to_s] || "'#{@value}'"}"
"#{line_part} syntax error#{val_part}#{id_part}" "#{line_part} syntax error#{val_part}#{id_part}"
end end
alias_method :inspect, :message alias_method :inspect, :message

View File

@@ -26,7 +26,7 @@ module CoffeeScript
# Single-line flavors of block expressions that have unclosed endings. # Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation. # The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] SINGLE_LINERS = [:ELSE, "=>", "==>", :TRY, :FINALLY, :THEN]
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN] SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN]
# Rewrite the token stream in multiple passes, one logical filter at # Rewrite the token stream in multiple passes, one logical filter at
@@ -35,6 +35,7 @@ module CoffeeScript
def rewrite(tokens) def rewrite(tokens)
@tokens = tokens @tokens = tokens
adjust_comments adjust_comments
remove_leading_newlines
remove_mid_expression_newlines remove_mid_expression_newlines
move_commas_outside_outdents move_commas_outside_outdents
add_implicit_indentation add_implicit_indentation
@@ -82,6 +83,12 @@ module CoffeeScript
end end
end end
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
def remove_leading_newlines
@tokens.shift if @tokens[0][0] == "\n"
end
# Some blocks occur in the middle of expressions -- when we're expecting # Some blocks occur in the middle of expressions -- when we're expecting
# this, remove their trailing newlines. # this, remove their trailing newlines.
def remove_mid_expression_newlines def remove_mid_expression_newlines

View File

@@ -5,12 +5,13 @@ module CoffeeScript
# whether a variable has been seen before or if it needs to be declared. # whether a variable has been seen before or if it needs to be declared.
class Scope class Scope
attr_reader :parent, :expressions, :variables, :temp_variable attr_reader :parent, :expressions, :function, :variables, :temp_variable
# Initialize a scope with its parent, for lookups up the chain, # Initialize a scope with its parent, for lookups up the chain,
# as well as the Expressions body where it should declare its variables. # as well as the Expressions body where it should declare its variables,
def initialize(parent, expressions) # and the function that it wraps.
@parent, @expressions = parent, expressions def initialize(parent, expressions, function)
@parent, @expressions, @function = parent, expressions, function
@variables = {} @variables = {}
@temp_variable = @parent ? @parent.temp_variable.dup : '__a' @temp_variable = @parent ? @parent.temp_variable.dup : '__a'
end end
@@ -44,18 +45,43 @@ module CoffeeScript
def free_variable def free_variable
@temp_variable.succ! while check(@temp_variable) @temp_variable.succ! while check(@temp_variable)
@variables[@temp_variable.to_sym] = :var @variables[@temp_variable.to_sym] = :var
@temp_variable.dup Value.new(@temp_variable.dup)
end
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
def assign(name, value, top=false)
return @parent.assign(name, value, top) if top && @parent
@variables[name.to_sym] = Value.new(value)
end end
def declarations?(body) def declarations?(body)
!declared_variables.empty? && body == @expressions !declared_variables.empty? && body == @expressions
end end
def assignments?(body)
!assigned_variables.empty? && body == @expressions
end
# Return the list of variables first declared in current scope. # Return the list of variables first declared in current scope.
def declared_variables def declared_variables
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort @variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
end end
# Return the list of variables that are supposed to be assigned at the top
# of scope.
def assigned_variables
@variables.select {|k, v| v.is_a?(Value) }.sort_by {|pair| pair[0].to_s }
end
def compiled_declarations
declared_variables.join(', ')
end
def compiled_assignments
assigned_variables.map {|name, val| "#{name} = #{val}"}.join(', ')
end
def inspect def inspect
"<Scope:#{__id__} #{@variables.inspect}>" "<Scope:#{__id__} #{@variables.inspect}>"
end end

View File

@@ -2,6 +2,8 @@ module CoffeeScript
# Instead of producing raw Ruby objects, the Lexer produces values of this # Instead of producing raw Ruby objects, the Lexer produces values of this
# class, wrapping native objects tagged with line number information. # class, wrapping native objects tagged with line number information.
# Values masquerade as both strings and nodes -- being used both as nodes in
# the AST, and as literally-interpolated values in the generated code.
class Value class Value
attr_reader :value, :line attr_reader :value, :line
@@ -41,6 +43,22 @@ module CoffeeScript
def hash def hash
@value.hash @value.hash
end end
def match(regex)
@value.match(regex)
end
def children
[]
end
def statement_only?
false
end
def contains?
false
end
end end
end end

View File

@@ -5,5 +5,5 @@
"description": "Unfancy JavaScript", "description": "Unfancy JavaScript",
"keywords": ["javascript", "language"], "keywords": ["javascript", "language"],
"author": "Jeremy Ashkenas", "author": "Jeremy Ashkenas",
"version": "0.2.4" "version": "0.2.6"
} }

View File

@@ -22,3 +22,11 @@ curried: =>
print(area.apply(this, arguments.concat(20, 20)) is 100) print(area.apply(this, arguments.concat(20, 20)) is 100)
curried(10, 10) curried(10, 10)
# Arguments is not a special keyword -- it can be assigned to:
func: =>
arguments: 25
arguments
print(func(100) is 25)

View File

@@ -1,8 +0,0 @@
result: try
nonexistent * missing
catch error
true
result2: try nonexistent * missing catch error then true
print(result is true and result2 is true)

View File

@@ -0,0 +1,23 @@
# Assign to try/catch.
result: try
nonexistent * missing
catch error
true
result2: try nonexistent * missing catch error then true
print(result is true and result2 is true)
# Assign to conditional.
get_x: => 10
if x: get_x() then 100
print(x is 10)
x: if get_x() then 100
print(x is 100)

View File

@@ -43,4 +43,20 @@ person: {
{name: a, family: {brother: {addresses: [one, {city: b}]}}}: person {name: a, family: {brother: {addresses: [one, {city: b}]}}}: person
print(a is "Bob") print(a is "Bob")
print(b is "Moquasset NY, 10021") print(b is "Moquasset NY, 10021")
test: {
person: {
address: [
"------"
"Street 101"
"Apt 101"
"City 101"
]
}
}
{person: {address: [ignore, addr...]}}: test
print(addr.join(', ') is "Street 101, Apt 101, City 101")

View File

@@ -26,4 +26,4 @@ func: =>
c.single: c.list[1..1][0] c.single: c.list[1..1][0]
print(func() == '-') print(func() is '-')

View File

@@ -2,4 +2,32 @@ print(if my_special_variable? then false else true)
my_special_variable: false my_special_variable: false
print(if my_special_variable? then true else false) print(if my_special_variable? then true else false)
# Existential assignment.
a: 5
a: null
a ?= 10
b ?= 10
print(a is 10 and b is 10)
# The existential operator.
z: null
x: z ? "EX"
print(z is null and x is "EX")
# Only evaluate once.
counter: 0
get_next_node: =>
throw "up" if counter
counter++
print(if get_next_node()? then true else false)

View File

@@ -0,0 +1,30 @@
# Ensure that we don't wrap Nodes that are "statement_only" in a closure.
items: [1, 2, 3, "bacon", 4, 5]
for item in items
break if item is "bacon"
findit: items =>
for item in items
return item if item is "bacon"
print(findit(items) is "bacon")
# When when a closure wrapper is generated for expression conversion, make sure
# that references to "this" within the wrapper are safely converted as well.
obj: {
num: 5
func: =>
this.result: if false
10
else
"a"
"b"
this.num
}
print(obj.num is obj.func())
print(obj.num is obj.result)

View File

@@ -0,0 +1,48 @@
x: 1
y: {}
y.x: => 3
print(x is 1)
print(typeof(y.x) is 'function')
print(y.x() is 3)
print(y.x.name is 'x')
# The empty function should not cause a syntax error.
=>
obj: {
name: "Fred"
bound: =>
(==> print(this.name is "Fred"))()
unbound: =>
(=> print(!this.name?))()
}
obj.unbound()
obj.bound()
# The named function should be cleared out before a call occurs:
# Python decorator style wrapper that memoizes any function
memoize: fn =>
cache: {}
self: this
args... =>
key: args.toString()
return cache[key] if cache[key]
cache[key] = fn.apply(self, args)
Math: {
Add: a, b => a + b
AnonymousAdd: (a, b => a + b)
FastAdd: memoize() a, b => a + b
}
print(Math.Add(5, 5) is 10)
print(Math.AnonymousAdd(10, 10) is 20)
print(Math.FastAdd(20, 20) is 40)

View File

@@ -25,4 +25,13 @@ a: """
here here
""" """
print(a is "out\nhere") print(a is "out\nhere")
a: '''
a
b
c
'''
print(a is " a\n b\nc")

View File

@@ -1,8 +0,0 @@
x: 1
y: {}
y.x: => 3
print(x is 1)
print(typeof(y.x) is 'function')
print(y.x() is 3)
print(y.x.name is 'x')

View File

@@ -0,0 +1,18 @@
# CoffeeScript's operations should be chainable, like Python's.
print(500 > 50 > 5 > -5)
print(true is not false is true is not false)
print(10 < 20 > 10)
print(50 > 10 > 5 is parseInt('5', 10))
# Make sure that each argument is only evaluated once, even if used
# more than once.
i: 0
func: => i++
print(1 > func() < 1)

View File

@@ -1,3 +1,5 @@
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array[5..10]: [0, 0, 0] array[5..10]: [0, 0, 0]

View File

@@ -15,3 +15,17 @@ result: switch num
else false else false
print(result) print(result)
func: num =>
switch num
when 2, 4, 6
true
when 1, 3, 5
false
else false
print(func(2))
print(func(6))
print(!func(3))
print(!func(8))

View File

@@ -0,0 +1,17 @@
i: 100
while i -= 1
print(i is 0)
i: 5
list: while i -= 1
i * 2
print(list.join(' ') is "8 6 4 2")
i: 5
list: (i * 3 while i -= 1)
print(list.join(' ') is "12 9 6 3")

View File

@@ -4,12 +4,16 @@ class ExecutionTest < Test::Unit::TestCase
NO_WARNINGS = "0 error(s), 0 warning(s)" NO_WARNINGS = "0 error(s), 0 warning(s)"
SOURCES = [
'test/fixtures/execution/*.coffee',
'examples/beautiful_code/*.coffee'
]
# This is by far the most important test. It evaluates all of the # This is by far the most important test. It evaluates all of the
# CoffeeScript in test/fixtures/execution, ensuring that all our # CoffeeScript in test/fixtures/execution, as well as examples/beautiful_code,
# syntax actually works. # ensuring that all our syntax actually works.
def test_execution_of_coffeescript def test_execution_of_coffeescript
sources = ['test/fixtures/execution/*.coffee'].join(' ') (`bin/coffee -r #{SOURCES.join(' ')}`).split("\n").each do |line|
(`bin/coffee -r #{sources}`).split("\n").each do |line|
assert line == "true" assert line == "true"
end end
end end

View File

@@ -7,14 +7,14 @@ class LexerTest < Test::Unit::TestCase
end end
def test_lexing_an_empty_string def test_lexing_an_empty_string
assert @lex.tokenize("") == [["\n", "\n"]] assert @lex.tokenize("") == []
end end
def test_lexing_basic_assignment def test_lexing_basic_assignment
code = "a: 'one'\nb: [1, 2]" code = "a: 'one'\nb: [1, 2]"
assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"],
[:STRING, "'one'"], ["\n", "\n"], [:IDENTIFIER, "b"], [:ASSIGN, ":"], [:STRING, "'one'"], ["\n", "\n"], [:IDENTIFIER, "b"], [:ASSIGN, ":"],
["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"], ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"],
["\n", "\n"]] ["\n", "\n"]]
end end
@@ -27,7 +27,7 @@ class LexerTest < Test::Unit::TestCase
def test_lexing_function_definition def test_lexing_function_definition
code = "x, y => x * y" code = "x, y => x * y"
assert @lex.tokenize(code) == [[:PARAM, "x"], [",", ","], [:PARAM, "y"], assert @lex.tokenize(code) == [[:PARAM, "x"], [",", ","], [:PARAM, "y"],
["=>", "=>"], [:INDENT, 2], [:IDENTIFIER, "x"], ["*", "*"], ["=>", "=>"], [:INDENT, 2], [:IDENTIFIER, "x"], ["*", "*"],
[:IDENTIFIER, "y"], [:OUTDENT, 2], ["\n", "\n"]] [:IDENTIFIER, "y"], [:OUTDENT, 2], ["\n", "\n"]]
end end