Compare commits

...

27 Commits
0.2.3 ... 0.2.5

Author SHA1 Message Date
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
Jeremy Ashkenas
9a61bbf005 CoffeeScript 0.2.4, with pattern matching and heredocs 2010-01-12 00:09:23 -05:00
Jeremy Ashkenas
c8d505e85d heredoc docs 2010-01-12 00:00:38 -05:00
Jeremy Ashkenas
477c510345 adding heredocs, with tests 2010-01-11 23:53:50 -05:00
Jeremy Ashkenas
c3029faca7 documentation for pattern matching 2010-01-11 22:55:01 -05:00
Jeremy Ashkenas
186797a745 got compile_pattern_match about as small as its going to get 2010-01-11 22:16:23 -05:00
Jeremy Ashkenas
d54fa2f2a1 a whole chunk of compile_pattern_match was redundant, axed it -- along with the array/vs/object split 2010-01-11 22:12:18 -05:00
Jeremy Ashkenas
5e1e949bf6 a passing test for destructuring assignment (it needs a better name) 2010-01-11 22:04:25 -05:00
Jeremy Ashkenas
6c980d8adc first draft of destructuring assignment -- working out the wrinkles -- not sure if we want to do the full spec 2010-01-11 21:44:47 -05:00
Jeremy Ashkenas
2f63439bff swapping around the order of variable declaration in array comprehensions, so that it comes out in the proper order: __a, __b, __c 2010-01-11 09:16:08 -05:00
Jeremy Ashkenas
c7cb308b6d adding note about parens-around-ambiguous-function-defs to the docs 2010-01-11 09:09:06 -05:00
Jeremy Ashkenas
9cc7d6af27 little lexer tweak 2010-01-11 08:46:50 -05:00
44 changed files with 998 additions and 286 deletions

View File

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

View File

@@ -0,0 +1,5 @@
html: '''
<strong>
cup of coffeescript
</strong>
'''

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

@@ -0,0 +1,5 @@
weather_report: location =>
# Make an Ajax request to fetch the weather...
[location, 72, "Mostly Sunny"]
[city, temp, forecast]: weather_report("Berkeley, CA")

View File

@@ -0,0 +1,13 @@
futurists: {
sculptor: "Umberto Boccioni"
painter: "Vladimir Burliuk"
poet: {
name: "F.T. Marinetti"
address: [
"Via Roma 42R"
"Bellagio, Italy 22021"
]
}
}
{poet: {name: poet, address: [street, city]}}: futurists

View File

@@ -0,0 +1,4 @@
bait: 1000
and_switch: 0
[bait, and_switch]: [and_switch, bait]

View File

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

View File

@@ -1,5 +1,8 @@
while demand > supply
sell()
restock()
if this.studying_economics
while supply > demand then buy()
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>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.2.3</a>
<a href="http://gemcutter.org/gems/coffee-script">0.2.5</a>
</p>
<h2>Table of Contents</h2>
@@ -75,10 +75,12 @@
<a href="#expressions">Everything is an Expression</a><br />
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br />
<a href="#blocks">Blocks</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="#switch">Switch/When/Else</a><br />
<a href="#try">Try/Catch/Finally</a><br />
<a href="#strings">Multiline Strings</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br />
<a href="#contributing">Contributing</a><br />
<a href="#change_log">Change Log</a><br />
@@ -95,7 +97,7 @@
<a href="documentation/underscore.html">Underscore.coffee</a>, a port
of the <a href="http://documentcloud.github.com/underscore/">Underscore.js</a>
library of helper functions. Underscore.coffee can pass the entire Underscore.js
test suite. The CoffeeScript version is faster than the original for a number
test suite. The CoffeeScript version is faster than the original for a number
of methods (in general, due to the speed of CoffeeScript's array comprehensions), and
after being minified and gzipped, is only 241 bytes larger than the original
JavaScript version.
@@ -386,9 +388,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<p id="while">
<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>
<%= code_for('while') %>
<%= code_for('while', 'lyrics.join("\n")') %>
<p>
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
@@ -415,7 +420,7 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<%= code_for('range_comprehensions', 'countdown') %>
<p>
Comprehensions can also be used to iterate over the keys and values in
an object. Use <tt>of</tt> to signal comprehension over the properties of
an object. Use <tt>of</tt> to signal comprehension over the properties of
an object instead of the values in an array.
</p>
<%= code_for('object_comprehensions', 'ages.join(", ")') %>
@@ -481,12 +486,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
be completely usable if it weren't for a couple of small exceptions:
it's awkward to call <b>super</b> (the prototype object's
implementation of the current function), and it's awkward to correctly
set the prototype chain.
set the prototype chain.
</p>
<p>
CoffeeScript provides <tt>extends</tt>
to help with prototype setup, <tt>::</tt> for quick access to an
object's prototype, and converts <tt>super()</tt> into a call against
object's prototype, and converts <tt>super()</tt> into a call against
the immediate ancestor's method of the same name.
</p>
<%= code_for('super', true) %>
@@ -499,11 +504,50 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
so you don't have to close the parentheses on the other side.
</p>
<%= code_for('blocks') %>
<p>
If you prefer not to use blocks, you'll need to add a pair of parentheses
to help distinguish the arguments from the definition of the function:
<tt>_.map(array, (num => num * 2))</tt>
</p>
<p id="pattern_matching">
<b class="header">Pattern Matching (Destructuring Assignment)</b>
To make extracting values from complex arrays and objects more convenient,
CoffeeScript implements ECMAScript Harmony's proposed
<a href="http://wiki.ecmascript.org/doku.php?id=harmony:destructuring">destructuring assignment</a>
syntax. When you assign an array or object literal to a value, CoffeeScript
breaks up and matches both sides against each other, assigning the values
on the right to the variables on the left. In the simplest case, it can be
used for parallel assignment:
</p>
<%= code_for('parallel_assignment', 'bait') %>
<p>
But it's also helpful for dealing with functions that return multiple
values.
</p>
<%= code_for('multiple_return_values', 'forecast') %>
<p>
Pattern matching can be used with any depth of array and object nesting,
to help pull out deeply nested properties.
</p>
<%= 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">
<b class="header">Embedded JavaScript</b>
If you ever need to interpolate literal JavaScript snippets, you can
use backticks to pass JavaScript straight through.
Hopefully, you'll never need to use it, but if you ever need to intersperse
snippets of JavaScript within your CoffeeScript, you can
use backticks to pass it straight through.
</p>
<%= code_for('embedded', 'hi()') %>
@@ -517,6 +561,11 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
in a returnable, assignable expression. The format is: <tt>switch</tt> condition,
<tt>when</tt> clauses, <tt>else</tt> the default case.
</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') %>
<p id="try">
@@ -527,10 +576,17 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<%= code_for('try') %>
<p id="strings">
<b class="header">Multiline Strings</b>
<b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript.
</p>
<%= code_for('strings', 'moby_dick') %>
<p>
Heredocs can be used to hold formatted or indentation-sensitive text
(or, if you just don't feel like escaping quotes and apostrophes). The
indentation level that begins the heredoc is maintained throughout, so
you can keep it all aligned with the body of your code.
</p>
<%= code_for('heredocs') %>
<h2 id="resources">Resources</h2>
@@ -539,7 +595,7 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<a href="http://github.com/jashkenas/coffee-script/">Source Code</a><br />
After checking out the source, make sure to run <tt>rake build:parser</tt>
to generate an up-to-date version of the Racc parser.
Use <tt>bin/coffee</tt> to test your changes,
Use <tt>bin/coffee</tt> to test your changes,
<tt>rake test</tt> to run the test suite,
and <tt>rake gem:install</tt> to
create and install a custom version of the gem.
@@ -588,11 +644,27 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</ul>
<h2 id="change_log">Change Log</h2>
<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>
<b class="header" style="margin-top: 20px;">0.2.4</b>
Added ECMAScript Harmony style destructuring assignment, for dealing with
extracting values from nested arrays and objects. Added indentation-sensitive
heredocs for nicely formatted strings or chunks of code.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.2.3</b>
Axed the unsatisfactory <tt>ino</tt> keyword, replacing it with <tt>of</tt> for
object comprehensions. They now look like: <tt>for key, value of object</tt>.
object comprehensions. They now look like: <tt>for prop, value of object</tt>.
</p>
<p>

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
(function(){
var html;
html = "<strong>\n cup of coffeescript\n</strong>";
})();

View File

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

View File

@@ -0,0 +1,11 @@
(function(){
var __a, city, forecast, temp, weather_report;
weather_report = function weather_report(location) {
// Make an Ajax request to fetch the weather...
return [location, 72, "Mostly Sunny"];
};
__a = weather_report("Berkeley, CA");
city = __a[0];
temp = __a[1];
forecast = __a[2];
})();

View File

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

View File

@@ -0,0 +1,17 @@
(function(){
var __a, __b, __c, city, futurists, poet, street;
futurists = {
sculptor: "Umberto Boccioni",
painter: "Vladimir Burliuk",
poet: {
name: "F.T. Marinetti",
address: ["Via Roma 42R", "Bellagio, Italy 22021"]
}
};
__a = futurists;
__b = __a.poet;
poet = __b.name;
__c = __b.address;
street = __c[0];
city = __c[1];
})();

View File

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

View File

@@ -0,0 +1,8 @@
(function(){
var __a, and_switch, bait;
bait = 1000;
and_switch = 0;
__a = [and_switch, bait];
bait = __a[0];
and_switch = __a[1];
})();

View File

@@ -1,21 +1,21 @@
(function(){
var __a, __b, __c, __d, __e, countdown, egg_delivery, num;
countdown = (function() {
__b = []; __d = 10; __e = 1;
__a = []; __d = 10; __e = 1;
for (__c=0, num=__d; (__d <= __e ? num <= __e : num >= __e); (__d <= __e ? num += 1 : num -= 1), __c++) {
__b.push(num);
__a.push(num);
}
return __b;
return __a;
})();
egg_delivery = function egg_delivery() {
var __f, __g, __h, __i, __j, dozen_eggs, i;
__g = []; __i = 0; __j = eggs.length;
__f = []; __i = 0; __j = eggs.length;
for (__h=0, i=__i; (__i <= __j ? i < __j : i > __j); (__i <= __j ? i += 12 : i -= 12), __h++) {
__g.push((function() {
__f.push((function() {
dozen_eggs = eggs.slice(i, i + 12);
return deliver(new egg_carton(dozen));
})());
}
return __g;
return __f;
};
})();

View File

@@ -1,14 +1,16 @@
(function(){
if (day === "Tuesday") {
eat_breakfast();
} else if (day === "Wednesday") {
if (day === "Mon") {
go_to_work();
} else if (day === "Tue") {
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) {
go_to_bingo();
go_dancing();
}
} else if (day === "Sunday") {
} else if (day === "Sun") {
go_to_church();
} else {
go_to_work();

View File

@@ -1,9 +1,20 @@
(function(){
while (demand > supply) {
sell();
restock();
}
while (supply > demand) {
buy();
var __a, lyrics, num;
if (this.studying_economics) {
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 = [];
while (num--) arr.push(num);
JSLitmus.test('current comprehensions', function() {
__a = arr;
__c = [];
for (__b in __a) {
if (__a.hasOwnProperty(__b)) {
num = __a[__b];
__d = num;
__c.push(num);
}
}
var f1 = function f1() {
return arr;
};
JSLitmus.test('regular function', function() {
f1();
});
JSLitmus.test('raw for loop (best we can do)', function() {
__a = arr;
__c = new Array(__a.length);
for (__b=0; __b < __a.length; __b++) {
__c[__b] = __a[__b];
}
var __this = this;
var f2 = function f2() {
return (function() {
return arr;
}).apply(__this, arguments);
};
JSLitmus.test('bound function', function() {
f2();
});
JSLitmus.test('current without hasOwnProperty check', function() {
__a = arr;
__c = [];
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);
var f3 = (function() {
__b = function() {
return arr;
};
if (__a instanceof Array) {
for (__b=0; __b<__a.length; __b++) __d(__a[__b], __b);
} else {
for (__b in __a) { if (__a.hasOwnProperty(__b)) __d(__a[__b], __b); }
}
return (function f2() {
return __b.apply(__this, arguments);
});
})();
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>
</body>

View File

@@ -15,8 +15,8 @@ dc.model.Document: dc.Model.extend({
# document by binding to Metadata, instead of on-the-fly.
metadata: =>
docId: this.id
_.select(Metadata.models(), (meta =>
_.any(meta.get('instances'), instance =>
_.select(Metadata.models(), (meta =>
_.any(meta.get('instances'), instance =>
instance.document_id is docId)))
bookmark: pageNumber =>
@@ -60,7 +60,7 @@ dc.model.DocumentSet: dc.model.RESTfulSet.extend({
# change their selected state.
_onModelEvent: 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))
})

View File

@@ -37,7 +37,7 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.2.3</a>
<a href="http://gemcutter.org/gems/coffee-script">0.2.5</a>
</p>
<h2>Table of Contents</h2>
@@ -61,10 +61,12 @@
<a href="#expressions">Everything is an Expression</a><br />
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br />
<a href="#blocks">Blocks</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="#switch">Switch/When/Else</a><br />
<a href="#try">Try/Catch/Finally</a><br />
<a href="#strings">Multiline Strings</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br />
<a href="#contributing">Contributing</a><br />
<a href="#change_log">Change Log</a><br />
@@ -137,12 +139,12 @@ race <span class="Keyword">=</span> <span class="Storage">function</span> <span
}
<span class="Comment"><span class="Comment">//</span> Array comprehensions:</span>
cubed_list <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__c <span class="Keyword">=</span> []; __a <span class="Keyword">=</span> list;
<span class="Keyword">for</span> (__b<span class="Keyword">=</span><span class="Number">0</span>; __b<span class="Keyword">&lt;</span>__a.<span class="LibraryConstant">length</span>; __b<span class="Keyword">++</span>) {
num <span class="Keyword">=</span> __a[__b];
__c.<span class="LibraryFunction">push</span>(math.cube(num));
__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>) {
num <span class="Keyword">=</span> __b[__c];
__a.<span class="LibraryFunction">push</span>(math.cube(num));
}
<span class="Keyword">return</span> __c;
<span class="Keyword">return</span> __a;
})();
</pre><button onclick='javascript: var __a, __b, __c, cubed_list, list, math, num, number, opposite_day, race, square;
// Assignment:
@@ -178,12 +180,12 @@ if ((typeof elvis !== "undefined" && elvis !== null)) {
}
// Array comprehensions:
cubed_list = (function() {
__c = []; __a = list;
for (__b=0; __b<__a.length; __b++) {
num = __a[__b];
__c.push(math.cube(num));
__a = []; __b = list;
for (__c = 0; __c < __b.length; __c++) {
num = __b[__c];
__a.push(math.cube(num));
}
return __c;
return __a;
})();
;alert(cubed_list);'>run: cubed_list</button><br class='clear' /></div>
@@ -192,7 +194,7 @@ cubed_list = (function() {
<a href="documentation/underscore.html">Underscore.coffee</a>, a port
of the <a href="http://documentcloud.github.com/underscore/">Underscore.js</a>
library of helper functions. Underscore.coffee can pass the entire Underscore.js
test suite. The CoffeeScript version is faster than the original for a number
test suite. The CoffeeScript version is faster than the original for a number
of methods (in general, due to the speed of CoffeeScript's array comprehensions), and
after being minified and gzipped, is only 241 bytes larger than the original
JavaScript version.
@@ -666,21 +668,56 @@ backwards("stairway", "to", "heaven");
<p id="while">
<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>
<div class='code'><pre class="idle"><span class="Keyword">while</span> demand <span class="Keyword">&gt;</span> supply
sell()
restock()
<div class='code'><pre class="idle"><span class="Keyword">if</span> <span class="Variable">this</span>.studying_economics
<span class="Keyword">while</span> supply <span class="Keyword">&gt;</span> demand <span class="Keyword">then</span> buy()
<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()
</pre><pre class="idle"><span class="Keyword">while</span> (demand <span class="Keyword">&gt;</span> supply) {
sell();
restock();
<span class="FunctionName">num</span><span class="Keyword">:</span> <span class="Number">6</span>
<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>
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>
</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) {
buy();
num <span class="Keyword">=</span> <span class="Number">6</span>;
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>
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
@@ -707,19 +744,19 @@ backwards("stairway", "to", "heaven");
</pre><pre class="idle"><span class="Storage">var</span> __a, __b, __c, __d, __e, __f, __g, food, lunch, roid, roid2;
<span class="Comment"><span class="Comment">//</span> Eat lunch.</span>
lunch <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__c <span class="Keyword">=</span> []; __a <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> (__b<span class="Keyword">=</span><span class="Number">0</span>; __b<span class="Keyword">&lt;</span>__a.<span class="LibraryConstant">length</span>; __b<span class="Keyword">++</span>) {
food <span class="Keyword">=</span> __a[__b];
__c.<span class="LibraryFunction">push</span>(eat(food));
__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>) {
food <span class="Keyword">=</span> __b[__c];
__a.<span class="LibraryFunction">push</span>(eat(food));
}
<span class="Keyword">return</span> __c;
<span class="Keyword">return</span> __a;
})();
<span class="Comment"><span class="Comment">//</span> Naive collision detection.</span>
__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];
__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];
<span class="Keyword">if</span> (roid <span class="Keyword">!</span><span class="Keyword">==</span> roid2) {
<span class="Keyword">if</span> (roid.overlaps(roid2)) {
@@ -743,46 +780,46 @@ __d <span class="Keyword">=</span> asteroids;
deliver(<span class="Keyword">new</span> <span class="TypeName">egg_carton</span>(dozen))
</pre><pre class="idle"><span class="Storage">var</span> __a, __b, __c, __d, __e, countdown, egg_delivery, num;
countdown <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__b <span class="Keyword">=</span> []; __d <span class="Keyword">=</span> <span class="Number">10</span>; __e <span class="Keyword">=</span> <span class="Number">1</span>;
__a <span class="Keyword">=</span> []; __d <span class="Keyword">=</span> <span class="Number">10</span>; __e <span class="Keyword">=</span> <span class="Number">1</span>;
<span class="Keyword">for</span> (__c<span class="Keyword">=</span><span class="Number">0</span>, num<span class="Keyword">=</span>__d; (__d <span class="Keyword">&lt;=</span> __e ? num <span class="Keyword">&lt;=</span> __e : num <span class="Keyword">&gt;=</span> __e); (__d <span class="Keyword">&lt;=</span> __e ? num <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">1</span> : num <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">1</span>), __c<span class="Keyword">++</span>) {
__b.<span class="LibraryFunction">push</span>(num);
__a.<span class="LibraryFunction">push</span>(num);
}
<span class="Keyword">return</span> __b;
<span class="Keyword">return</span> __a;
})();
egg_delivery <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">egg_delivery</span>() {
<span class="Storage">var</span> __f, __g, __h, __i, __j, dozen_eggs, i;
__g <span class="Keyword">=</span> []; __i <span class="Keyword">=</span> <span class="Number">0</span>; __j <span class="Keyword">=</span> eggs.<span class="LibraryConstant">length</span>;
__f <span class="Keyword">=</span> []; __i <span class="Keyword">=</span> <span class="Number">0</span>; __j <span class="Keyword">=</span> eggs.<span class="LibraryConstant">length</span>;
<span class="Keyword">for</span> (__h<span class="Keyword">=</span><span class="Number">0</span>, i<span class="Keyword">=</span>__i; (__i <span class="Keyword">&lt;=</span> __j ? i <span class="Keyword">&lt;</span> __j : i <span class="Keyword">&gt;</span> __j); (__i <span class="Keyword">&lt;=</span> __j ? i <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">12</span> : i <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">12</span>), __h<span class="Keyword">++</span>) {
__g.<span class="LibraryFunction">push</span>((<span class="Storage">function</span>() {
__f.<span class="LibraryFunction">push</span>((<span class="Storage">function</span>() {
dozen_eggs <span class="Keyword">=</span> eggs.<span class="LibraryFunction">slice</span>(i, i <span class="Keyword">+</span> <span class="Number">12</span>);
<span class="Keyword">return</span> deliver(<span class="Keyword">new</span> <span class="TypeName">egg_carton</span>(dozen));
})());
}
<span class="Keyword">return</span> __g;
<span class="Keyword">return</span> __f;
};
</pre><button onclick='javascript: var __a, __b, __c, __d, __e, countdown, egg_delivery, num;
countdown = (function() {
__b = []; __d = 10; __e = 1;
__a = []; __d = 10; __e = 1;
for (__c=0, num=__d; (__d <= __e ? num <= __e : num >= __e); (__d <= __e ? num += 1 : num -= 1), __c++) {
__b.push(num);
__a.push(num);
}
return __b;
return __a;
})();
egg_delivery = function egg_delivery() {
var __f, __g, __h, __i, __j, dozen_eggs, i;
__g = []; __i = 0; __j = eggs.length;
__f = []; __i = 0; __j = eggs.length;
for (__h=0, i=__i; (__i <= __j ? i < __j : i > __j); (__i <= __j ? i += 12 : i -= 12), __h++) {
__g.push((function() {
__f.push((function() {
dozen_eggs = eggs.slice(i, i + 12);
return deliver(new egg_carton(dozen));
})());
}
return __g;
return __f;
};
;alert(countdown);'>run: countdown</button><br class='clear' /></div>
<p>
Comprehensions can also be used to iterate over the keys and values in
an object. Use <tt>of</tt> to signal comprehension over the properties of
an object. Use <tt>of</tt> to signal comprehension over the properties of
an object instead of the values in an array.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">years_old</span><span class="Keyword">:</span> {<span class="FunctionName">max</span><span class="Keyword">:</span> <span class="Number">10</span>, <span class="FunctionName">ida</span><span class="Keyword">:</span> <span class="Number">9</span>, <span class="FunctionName">tim</span><span class="Keyword">:</span> <span class="Number">11</span>}
@@ -790,36 +827,38 @@ 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
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;
<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> {
max: <span class="Number">10</span>,
ida: <span class="Number">9</span>,
tim: <span class="Number">11</span>
};
ages <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__b <span class="Keyword">=</span> []; __a <span class="Keyword">=</span> years_old;
<span class="Keyword">for</span> (child <span class="Keyword">in</span> __a) {
age <span class="Keyword">=</span> __a[child];
<span class="Keyword">if</span> (__a.hasOwnProperty(child)) {
__b.<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="Keyword">=</span> []; __b <span class="Keyword">=</span> years_old;
<span class="Keyword">for</span> (child <span class="Keyword">in</span> __b) {
age <span class="Keyword">=</span> __b[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);
}
}
<span class="Keyword">return</span> __b;
<span class="Keyword">return</span> __a;
})();
</pre><button onclick='javascript: var __a, __b, age, ages, child, years_old;
var __hasProp = Object.prototype.hasOwnProperty;
years_old = {
max: 10,
ida: 9,
tim: 11
};
ages = (function() {
__b = []; __a = years_old;
for (child in __a) {
age = __a[child];
if (__a.hasOwnProperty(child)) {
__b.push(child + " is " + age);
__a = []; __b = years_old;
for (child in __b) {
age = __b[child];
if (__hasProp.call(__b, child)) {
__a.push(child + " is " + age);
}
}
return __b;
return __a;
})();
;alert(ages.join(", "));'>run: ages.join(", ")</button><br class='clear' /></div>
@@ -930,26 +969,28 @@ 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>]
</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>
globals <span class="Keyword">=</span> ((<span class="Storage">function</span>() {
__b <span class="Keyword">=</span> []; __a <span class="Keyword">=</span> <span class="LibraryClassType">window</span>;
<span class="Keyword">for</span> (name <span class="Keyword">in</span> __a) {
<span class="Keyword">if</span> (__a.hasOwnProperty(name)) {
__b.<span class="LibraryFunction">push</span>(name);
__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">if</span> (__hasProp.<span class="LibraryFunction">call</span>(__b, name)) {
__a.<span class="LibraryFunction">push</span>(name);
}
}
<span class="Keyword">return</span> __b;
<span class="Keyword">return</span> __a;
})()).<span class="LibraryFunction">slice</span>(<span class="Number">0</span>, <span class="Number">10</span>);
</pre><button onclick='javascript: var __a, __b, globals, name;
var __hasProp = Object.prototype.hasOwnProperty;
// The first ten global properties.
globals = ((function() {
__b = []; __a = window;
for (name in __a) {
if (__a.hasOwnProperty(name)) {
__b.push(name);
__a = []; __b = window;
for (name in __b) {
if (__hasProp.call(__b, name)) {
__a.push(name);
}
}
return __b;
return __a;
})()).slice(0, 10);
;alert(globals);'>run: globals</button><br class='clear' /></div>
<p>
@@ -990,12 +1031,12 @@ globals = ((function() {
be completely usable if it weren't for a couple of small exceptions:
it's awkward to call <b>super</b> (the prototype object's
implementation of the current function), and it's awkward to correctly
set the prototype chain.
set the prototype chain.
</p>
<p>
CoffeeScript provides <tt>extends</tt>
to help with prototype setup, <tt>::</tt> for quick access to an
object's prototype, and converts <tt>super()</tt> into a call against
object's prototype, and converts <tt>super()</tt> into a call against
the immediate ancestor's method of the same name.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">Animal</span><span class="Keyword">:</span> <span class="Storage">=&gt;</span>
@@ -1118,12 +1159,157 @@ tom.move();
<span class="Keyword">return</span> row.highlight();
});
});
</pre><br class='clear' /></div>
<p>
If you prefer not to use blocks, you'll need to add a pair of parentheses
to help distinguish the arguments from the definition of the function:
<tt>_.map(array, (num => num * 2))</tt>
</p>
<p id="pattern_matching">
<b class="header">Pattern Matching (Destructuring Assignment)</b>
To make extracting values from complex arrays and objects more convenient,
CoffeeScript implements ECMAScript Harmony's proposed
<a href="http://wiki.ecmascript.org/doku.php?id=harmony:destructuring">destructuring assignment</a>
syntax. When you assign an array or object literal to a value, CoffeeScript
breaks up and matches both sides against each other, assigning the values
on the right to the variables on the left. In the simplest case, it can be
used for parallel assignment:
</p>
<div class='code'><pre class="idle"><span class="FunctionName">bait</span><span class="Keyword">:</span> <span class="Number">1000</span>
<span class="FunctionName">and_switch</span><span class="Keyword">:</span> <span class="Number">0</span>
[bait, and_switch]<span class="Keyword">:</span> [and_switch, bait]
</pre><pre class="idle"><span class="Storage">var</span> __a, and_switch, bait;
bait <span class="Keyword">=</span> <span class="Number">1000</span>;
and_switch <span class="Keyword">=</span> <span class="Number">0</span>;
__a <span class="Keyword">=</span> [and_switch, bait];
bait <span class="Keyword">=</span> __a[<span class="Number">0</span>];
and_switch <span class="Keyword">=</span> __a[<span class="Number">1</span>];
</pre><button onclick='javascript: var __a, and_switch, bait;
bait = 1000;
and_switch = 0;
__a = [and_switch, bait];
bait = __a[0];
and_switch = __a[1];
;alert(bait);'>run: bait</button><br class='clear' /></div>
<p>
But it's also helpful for dealing with functions that return multiple
values.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">weather_report</span><span class="Keyword">:</span> <span class="FunctionArgument">location</span> <span class="Storage">=&gt;</span>
<span class="Comment"><span class="Comment">#</span> Make an Ajax request to fetch the weather...</span>
[location, <span class="Number">72</span>, <span class="String"><span class="String">&quot;</span>Mostly Sunny<span class="String">&quot;</span></span>]
[city, temp, forecast]<span class="Keyword">:</span> weather_report(<span class="String"><span class="String">&quot;</span>Berkeley, CA<span class="String">&quot;</span></span>)
</pre><pre class="idle"><span class="Storage">var</span> __a, city, forecast, temp, weather_report;
weather_report <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">weather_report</span>(<span class="FunctionArgument">location</span>) {
<span class="Comment"><span class="Comment">//</span> Make an Ajax request to fetch the weather...</span>
<span class="Keyword">return</span> [location, <span class="Number">72</span>, <span class="String"><span class="String">&quot;</span>Mostly Sunny<span class="String">&quot;</span></span>];
};
__a <span class="Keyword">=</span> weather_report(<span class="String"><span class="String">&quot;</span>Berkeley, CA<span class="String">&quot;</span></span>);
city <span class="Keyword">=</span> __a[<span class="Number">0</span>];
temp <span class="Keyword">=</span> __a[<span class="Number">1</span>];
forecast <span class="Keyword">=</span> __a[<span class="Number">2</span>];
</pre><button onclick='javascript: var __a, city, forecast, temp, weather_report;
weather_report = function weather_report(location) {
// Make an Ajax request to fetch the weather...
return [location, 72, "Mostly Sunny"];
};
__a = weather_report("Berkeley, CA");
city = __a[0];
temp = __a[1];
forecast = __a[2];
;alert(forecast);'>run: forecast</button><br class='clear' /></div>
<p>
Pattern matching can be used with any depth of array and object nesting,
to help pull out deeply nested properties.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">futurists</span><span class="Keyword">:</span> {
<span class="FunctionName">sculptor</span><span class="Keyword">:</span> <span class="String"><span class="String">&quot;</span>Umberto Boccioni<span class="String">&quot;</span></span>
<span class="FunctionName">painter</span><span class="Keyword">:</span> <span class="String"><span class="String">&quot;</span>Vladimir Burliuk<span class="String">&quot;</span></span>
<span class="FunctionName">poet</span><span class="Keyword">:</span> {
<span class="FunctionName">name</span><span class="Keyword">:</span> <span class="String"><span class="String">&quot;</span>F.T. Marinetti<span class="String">&quot;</span></span>
<span class="FunctionName">address</span><span class="Keyword">:</span> [
<span class="String"><span class="String">&quot;</span>Via Roma 42R<span class="String">&quot;</span></span>
<span class="String"><span class="String">&quot;</span>Bellagio, Italy 22021<span class="String">&quot;</span></span>
]
}
}
{<span class="FunctionName">poet</span><span class="Keyword">:</span> {<span class="FunctionName">name</span><span class="Keyword">:</span> poet, <span class="FunctionName">address</span><span class="Keyword">:</span> [street, city]}}<span class="Keyword">:</span> futurists
</pre><pre class="idle"><span class="Storage">var</span> __a, __b, __c, city, futurists, poet, street;
futurists <span class="Keyword">=</span> {
sculptor: <span class="String"><span class="String">&quot;</span>Umberto Boccioni<span class="String">&quot;</span></span>,
painter: <span class="String"><span class="String">&quot;</span>Vladimir Burliuk<span class="String">&quot;</span></span>,
poet: {
name: <span class="String"><span class="String">&quot;</span>F.T. Marinetti<span class="String">&quot;</span></span>,
address: [<span class="String"><span class="String">&quot;</span>Via Roma 42R<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>Bellagio, Italy 22021<span class="String">&quot;</span></span>]
}
};
__a <span class="Keyword">=</span> futurists;
__b <span class="Keyword">=</span> __a.poet;
poet <span class="Keyword">=</span> __b.<span class="LibraryConstant">name</span>;
__c <span class="Keyword">=</span> __b.address;
street <span class="Keyword">=</span> __c[<span class="Number">0</span>];
city <span class="Keyword">=</span> __c[<span class="Number">1</span>];
</pre><button onclick='javascript: var __a, __b, __c, city, futurists, poet, street;
futurists = {
sculptor: "Umberto Boccioni",
painter: "Vladimir Burliuk",
poet: {
name: "F.T. Marinetti",
address: ["Via Roma 42R", "Bellagio, Italy 22021"]
}
};
__a = futurists;
__b = __a.poet;
poet = __b.name;
__c = __b.address;
street = __c[0];
city = __c[1];
;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, __b;
<span class="Storage">var</span> __this <span class="Keyword">=</span> <span class="Variable">this</span>;
<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>() {
<span class="FunctionName">__b</span> = <span class="Storage">function</span>(<span class="FunctionArgument">event</span>) {
<span class="Storage">var</span> __c;
__c <span class="Keyword">=</span> <span class="Variable">this</span>.customer.purchase(<span class="Variable">this</span>.cart);
<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> : __c;
};
<span class="Keyword">return</span> (<span class="Storage">function</span>() {
<span class="Keyword">return</span> __b.<span class="LibraryFunction">apply</span>(__this, arguments);
});
})());
<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">
<b class="header">Embedded JavaScript</b>
If you ever need to interpolate literal JavaScript snippets, you can
use backticks to pass JavaScript straight through.
Hopefully, you'll never need to use it, but if you ever need to intersperse
snippets of JavaScript within your CoffeeScript, you can
use backticks to pass it straight through.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">hi</span><span class="Keyword">:</span> <span class="String"><span class="String">`</span>function() {</span>
<span class="String"> return [document.title, &quot;Hello JavaScript&quot;].join(&quot;: &quot;);</span>
@@ -1150,25 +1336,33 @@ return [document.title, "Hello JavaScript"].join(": ");
in a returnable, assignable expression. The format is: <tt>switch</tt> condition,
<tt>when</tt> clauses, <tt>else</tt> the default case.
</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
<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>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>Saturday<span class="String">&quot;</span></span>
<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>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>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
go_to_bingo()
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()
</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>) {
eat_breakfast();
} <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>) {
</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>) {
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>Tue<span class="String">&quot;</span></span>) {
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) {
go_to_bingo();
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();
} <span class="Keyword">else</span> {
go_to_work();
@@ -1198,7 +1392,7 @@ return [document.title, "Hello JavaScript"].join(": ");
</pre><br class='clear' /></div>
<p id="strings">
<b class="header">Multiline Strings</b>
<b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">moby_dick</span><span class="Keyword">:</span> <span class="String"><span class="String">&quot;</span>Call me Ishmael. Some years ago --</span>
@@ -1224,6 +1418,20 @@ to interest me on shore, I thought I would sail \
about a little and see the watery part of the \
world...";
;alert(moby_dick);'>run: moby_dick</button><br class='clear' /></div>
<p>
Heredocs can be used to hold formatted or indentation-sensitive text
(or, if you just don't feel like escaping quotes and apostrophes). The
indentation level that begins the heredoc is maintained throughout, so
you can keep it all aligned with the body of your code.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">html</span><span class="Keyword">:</span> <span class="String"><span class="String">'</span><span class="String">'</span></span><span class="String"><span class="String">'</span></span>
<span class="String"> &lt;strong&gt;</span>
<span class="String"> cup of coffeescript</span>
<span class="String"> &lt;/strong&gt;</span>
<span class="String"> <span class="String">'</span></span><span class="String"><span class="String">'</span><span class="String">'</span></span>
</pre><pre class="idle"><span class="Storage">var</span> html;
html <span class="Keyword">=</span> <span class="String"><span class="String">&quot;</span>&lt;strong&gt;<span class="UserDefinedConstant">\n</span> cup of coffeescript<span class="UserDefinedConstant">\n</span>&lt;/strong&gt;<span class="String">&quot;</span></span>;
</pre><br class='clear' /></div>
<h2 id="resources">Resources</h2>
@@ -1232,7 +1440,7 @@ world...";
<a href="http://github.com/jashkenas/coffee-script/">Source Code</a><br />
After checking out the source, make sure to run <tt>rake build:parser</tt>
to generate an up-to-date version of the Racc parser.
Use <tt>bin/coffee</tt> to test your changes,
Use <tt>bin/coffee</tt> to test your changes,
<tt>rake test</tt> to run the test suite,
and <tt>rake gem:install</tt> to
create and install a custom version of the gem.
@@ -1281,11 +1489,27 @@ world...";
</ul>
<h2 id="change_log">Change Log</h2>
<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>
<b class="header" style="margin-top: 20px;">0.2.4</b>
Added ECMAScript Harmony style destructuring assignment, for dealing with
extracting values from nested arrays and objects. Added indentation-sensitive
heredocs for nicely formatted strings or chunks of code.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.2.3</b>
Axed the unsatisfactory <tt>ino</tt> keyword, replacing it with <tt>of</tt> for
object comprehensions. They now look like: <tt>for key, value of object</tt>.
object comprehensions. They now look like: <tt>for prop, value of object</tt>.
</p>
<p>

View File

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

View File

@@ -43,7 +43,7 @@
<key>comment</key>
<string>match stuff like: funcName: =&gt; … </string>
<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>
<string>meta.function.coffee</string>
</dict>
@@ -64,7 +64,7 @@
<key>comment</key>
<string>match stuff like: a =&gt; … </string>
<key>match</key>
<string>([a-zA-Z0-9_?., $:*]*)\s*(=&gt;)</string>
<string>([a-zA-Z0-9_?., $*]*)\s*(=+&gt;)</string>
<key>name</key>
<string>meta.inline.function.coffee</string>
</dict>
@@ -93,6 +93,30 @@
<key>name</key>
<string>constant.numeric.coffee</string>
</dict>
<dict>
<key>name</key>
<string>string.quoted.heredoc.coffee</string>
<key>begin</key>
<string>("""|''')</string>
<key>end</key>
<string>("""|''')</string>
<key>beginCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>endCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
</dict>
<dict>
<key>begin</key>
<string>'</string>

View File

@@ -39,7 +39,7 @@ prechigh
left EXTENDS
left ASSIGN '||=' '&&='
right RETURN
right '=>' UNLESS IF ELSE WHILE
right '=>' '==>' UNLESS IF ELSE WHILE
preclow
rule
@@ -198,8 +198,14 @@ rule
# Function definition.
Code:
ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) }
| "=>" Block { result = CodeNode.new([], val[1]) }
ParamList FuncGlyph Block { result = CodeNode.new(val[0], val[2], 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.
@@ -211,12 +217,12 @@ rule
# A Parameter (or ParamSplat) in a function definition.
Param:
PARAM
| PARAM "." "." "." { result = ParamSplatNode.new(val[0]) }
| PARAM "." "." "." { result = SplatNode.new(val[0]) }
;
# A regular splat.
Splat:
Expression "." "." "." { result = ArgSplatNode.new(val[0])}
Expression "." "." "." { result = SplatNode.new(val[0]) }
;
# Expressions that can be treated as values.
@@ -276,7 +282,6 @@ rule
Invocation:
Value 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.
@@ -315,6 +320,12 @@ rule
| 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:
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
@@ -342,6 +353,7 @@ rule
While:
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
| 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.
@@ -384,8 +396,8 @@ rule
# An individual when.
When:
LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| LEADING_WHEN Expression Block
LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| LEADING_WHEN SimpleArgs Block
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| Comment Terminator When { result = val[2].add_comment(val[0]) }
;

View File

@@ -22,21 +22,24 @@ module CoffeeScript
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
STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m
HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?(\s*)"{3}|'{3}\n?(.*?)\n?(\s*)'{3})/m
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
WHITESPACE = /\A([ \t]+)/
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
CODE = /\A(=>)/
CODE = /\A(=?=>)/
REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/
LAST_DENT = /\n([ \t]*)/
ASSIGNMENT = /\A(:|=)\Z/
# Token cleaning regexes.
JS_CLEANER = /(\A`|`\Z)/
MULTILINER = /\n/
JS_CLEANER = /(\A`|`\Z)/
MULTILINER = /\n/
STRING_NEWLINES = /\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
# a division operator might.
@@ -49,12 +52,12 @@ module CoffeeScript
# Scan by attempting to match tokens one character at a time. Slow and steady.
def tokenize(code)
@code = code.chomp # Cleanup code by remove extra line breaks
@i = 0 # Current character position we're parsing
@line = 1 # The current line.
@indent = 0 # The current indent level.
@indents = [] # The stack of all indent levels we are currently within.
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
@code = code.chomp # Cleanup code by remove extra line breaks
@i = 0 # Current character position we're parsing
@line = 1 # The current line.
@indent = 0 # The current indent level.
@indents = [] # The stack of all indent levels we are currently within.
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while @i < @code.length
@chunk = @code[@i..-1]
extract_next_token
@@ -69,6 +72,7 @@ module CoffeeScript
def extract_next_token
return if identifier_token
return if number_token
return if heredoc_token
return if string_token
return if js_token
return if regex_token
@@ -87,7 +91,7 @@ module CoffeeScript
# 'if' will result in an [:IF, "if"] token.
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.')
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2] && @tokens[-2][1] == '.')
@tokens[-1][0] = :PROTOTYPE_ACCESS if tag == :IDENTIFIER && last_value == '::'
token(tag, identifier)
@i += identifier.length
@@ -103,14 +107,25 @@ module CoffeeScript
# Matches strings, including multi-line strings.
def string_token
return false unless string = @chunk[STRING, 1]
escaped = string.gsub(MULTILINER) do |match|
@line += 1
" \\\n"
end
escaped = string.gsub(STRING_NEWLINES, " \\\n")
token(:STRING, escaped)
@line += string.count("\n")
@i += string.length
end
# Matches heredocs, adjusting indentation to the correct level.
def heredoc_token
return false unless match = @chunk.match(HEREDOC)
doc = match[2] || match[4]
indent = doc.scan(HEREDOC_INDENT).min
doc.gsub!(/^#{indent}/, "")
doc.gsub!("\n", "\\n")
doc.gsub!('"', '\\"')
token(:STRING, "\"#{doc}\"")
@line += match[1].count("\n")
@i += match[1].length
end
# Matches interpolated JavaScript.
def js_token
return false unless script = @chunk[JS, 1]
@@ -141,7 +156,7 @@ module CoffeeScript
@line += indent.scan(MULTILINER).size
@i += indent.size
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
size = indent.scan(LAST_DENT).last.last.length
return newline_token(indent) if size == @indent

View File

@@ -36,9 +36,9 @@ module CoffeeScript
def compile(o={})
@options = o.dup
@indent = o[:indent]
top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top)
closure = statement? && !statement_only? && !top && !@options[:return]
closure ? compile_closure(@options) : compile_node(@options)
top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
closure = statement? && !statement_only? && !top && !@options[:return]
closure ? compile_closure(@options) : compile_node(@options)
end
def compile_closure(o={})
@@ -56,6 +56,7 @@ module CoffeeScript
def unwrap; self; end
def statement?; false; end
def statement_only?; false; end
def top_sensitive?; false; end
end
# A collection of nodes, each one representing an expression.
@@ -128,8 +129,9 @@ module CoffeeScript
# at the top.
def compile_with_declarations(o={})
code = compile_node(o)
return code unless o[:scope].declarations?(self)
write("#{idt}var #{o[:scope].declared_variables.join(', ')};\n#{code}")
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
# Compiles a single expression within the expression list.
@@ -243,7 +245,7 @@ module CoffeeScript
end
def splat?
@arguments.any? {|a| a.is_a?(ArgSplatNode) }
@arguments.any? {|a| a.is_a?(SplatNode) }
end
def <<(argument)
@@ -273,7 +275,7 @@ module CoffeeScript
obj = @variable.source || 'this'
args = @arguments.map do |arg|
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})"
end
"#{prefix}#{meth}.apply(#{obj}, #{args.join('')})"
@@ -320,6 +322,18 @@ module CoffeeScript
return !@properties.empty?
end
def array?
@base.is_a?(ArrayNode) && !properties?
end
def object?
@base.is_a?(ObjectNode) && !properties?
end
def splice?
properties? && @properties.last.is_a?(SliceNode)
end
# Values are statements if their base is a statement.
def statement?
@base.is_a?(Node) && @base.statement? && !properties?
@@ -435,18 +449,49 @@ module CoffeeScript
end
def compile_node(o)
return compile_splice(o) if @variable.properties.last.is_a?(SliceNode)
return compile_pattern_match(o) if statement?
return compile_splice(o) if value? && @variable.splice?
stmt = o.delete(:as_statement)
name = @variable.compile(o)
last = @variable.last.to_s.sub(LEADING_DOT, '')
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
proto = name[PROTO_ASSIGN, 1]
o = o.merge(:last_assign => last, :proto_assign => proto)
o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER)
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)}"
return write("#{idt}#{val};") if stmt
write(o[:return] ? "#{idt}return (#{val})" : val)
end
def value?
@variable.is_a?(ValueNode)
end
def statement?
value? && (@variable.array? || @variable.object?)
end
# Implementation of recursive pattern matching, when assigning array or
# object literals to a value. Peeks at their properties to assign inner names.
# See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
def compile_pattern_match(o)
val_var = o[:scope].free_variable
assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"]
o.merge!(:top => true, :as_statement => true)
@variable.base.objects.each_with_index do |obj, i|
obj, i = obj.value, obj.variable.base if @variable.object?
access_class = @variable.array? ? IndexNode : AccessorNode
if obj.is_a?(SplatNode)
val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
else
val = ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))])
end
assigns << AssignNode.new(obj, val).compile(o)
end
write(assigns.join("\n"))
end
def compile_splice(o)
var = @variable.compile(o.merge(:only_first => true))
range = @variable.properties.last.range
@@ -505,14 +550,23 @@ module CoffeeScript
# A function definition. The only node that creates a new Scope.
class CodeNode < Node
attr_reader :params, :body
attr_reader :params, :body, :bound
def initialize(params, body)
def initialize(params, body, tag=nil)
@params = params
@body = body
@body = body
@bound = tag == :boundfunc
end
def statement?
@bound
end
def compile_node(o)
if @bound
o[:scope].assign("__this", "this")
fvar = o[:scope].free_variable
end
shared_scope = o.delete(:shared_scope)
o[:scope] = shared_scope || Scope.new(o[:scope], @body)
o[:return] = true
@@ -521,20 +575,23 @@ module CoffeeScript
o.delete(:no_wrap)
o.delete(:globals)
name = o.delete(:immediate_assign)
if @params.last.is_a?(ParamSplatNode)
if @params.last.is_a?(SplatNode)
splat = @params.pop
splat.index = @params.length
@body.unshift(splat)
end
@params.each {|id| o[:scope].parameter(id.to_s) }
code = @body.compile_with_declarations(o)
code = "\n#{@body.compile_with_declarations(o)}\n"
name_part = name ? " #{name}" : ''
write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{idt}}")
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt}}"
return write(func) unless @bound
write("#{idt}#{fvar} = #{func};\n#{idt}#{o[:return] ? 'return ' : ''}(function#{name_part}() {\n#{idt(1)}return #{fvar}.apply(__this, arguments);\n#{idt}});")
end
end
# A parameter splat in a function definition.
class ParamSplatNode < Node
# A splat, either as a parameter to a function, an argument to a call,
# or in a destructuring assignment.
class SplatNode < Node
attr_accessor :index
attr_reader :name
@@ -543,20 +600,20 @@ module CoffeeScript
end
def compile_node(o={})
write(@index ? compile_param(o) : compile_arg(o))
end
def compile_param(o)
o[:scope].find(@name)
write("#{@name} = Array.prototype.slice.call(arguments, #{@index})")
end
end
class ArgSplatNode < Node
attr_reader :value
def initialize(value)
@value = value
"#{@name} = Array.prototype.slice.call(arguments, #{@index})"
end
def compile_node(o={})
write(@value.compile(o))
def compile_arg(o)
@name.compile(o)
end
def compile_value(o, name, index)
"Array.prototype.slice.call(#{name}, #{index})"
end
end
@@ -564,6 +621,7 @@ module CoffeeScript
# An object literal.
class ObjectNode < Node
attr_reader :properties
alias_method :objects, :properties
def initialize(properties = [])
@properties = properties
@@ -607,6 +665,17 @@ module CoffeeScript
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)
Expressions.wrap(CallNode.new(
ValueNode.new(LiteralNode.new(array), [AccessorNode.new('push')]),
[expressions.unwrap]
))
end
end
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
# it, all other loops can be manufactured.
class WhileNode < Node
@@ -618,14 +687,25 @@ module CoffeeScript
@condition, @body = condition, body
end
def top_sensitive?
true
end
def compile_node(o)
returns = o.delete(:return)
top = o.delete(:top) && !returns
o[:indent] = idt(1)
o[:top] = true
cond = @condition.compile(o)
post = returns ? "\n#{idt}return null;" : ''
return write("#{idt}while (#{cond}) null;#{post}") if @body.nil?
write("#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
set = ''
if !top
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
@@ -647,6 +727,10 @@ module CoffeeScript
@name, @index = @index, @name if @object
end
def top_sensitive?
true
end
def compile_node(o)
top_level = o.delete(:top) && !o[:return]
range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
@@ -655,9 +739,9 @@ module CoffeeScript
name_found = @name && scope.find(@name)
index_found = @index && scope.find(@index)
body_dent = idt(1)
rvar = scope.free_variable unless top_level
svar = scope.free_variable
ivar = range ? name : @index ? @index : scope.free_variable
rvar = scope.free_variable unless top_level
if range
index_var = scope.free_variable
source_part = source.compile_variables(o)
@@ -666,7 +750,7 @@ module CoffeeScript
else
index_var = nil
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" : ''
end
body = @body
@@ -675,9 +759,7 @@ module CoffeeScript
if top_level
body = Expressions.wrap(body)
else
body = Expressions.wrap(CallNode.new(
ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [body.unwrap]
))
body = PushNode.wrap(rvar, body)
end
if o[:return]
return_result = "return #{return_result}" if o[:return]
@@ -687,8 +769,9 @@ module CoffeeScript
body = Expressions.wrap(IfNode.new(@filter, body))
end
if @object
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
body = Expressions.wrap(IfNode.new(
CallNode.new(ValueNode.new(LiteralNode.wrap(svar), [AccessorNode.new(Value.new('hasOwnProperty'))]), [LiteralNode.wrap(ivar)]),
CallNode.new(ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]), [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]),
Expressions.wrap(body),
nil,
{:statement => true}
@@ -781,6 +864,7 @@ module CoffeeScript
@body = body && body.unwrap
@else_body = else_body && else_body.unwrap
@tags = tags
@multiple = true if @condition.is_a?(Array)
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
end
@@ -802,7 +886,8 @@ module CoffeeScript
# Rewrite a chain of IfNodes with their switch condition for equality.
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?
self
end
@@ -824,6 +909,10 @@ module CoffeeScript
@is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
end
def compile_condition(o)
[@condition].flatten.map {|c| c.compile(o) }.join(' || ')
end
def compile_node(o)
write(statement? ? compile_statement(o) : compile_ternary(o))
end
@@ -839,7 +928,7 @@ module CoffeeScript
if_dent = child ? '' : idt
com_dent = child ? idt : ''
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}}"
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}"
return if_part unless @else_body
else_part = chain? ?
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :

View File

@@ -5,6 +5,12 @@ module CoffeeScript
# line-number aware.
class ParseError < Racc::ParseError
TOKEN_MAP = {
'INDENT' => 'indent',
'OUTDENT' => 'outdent',
"\n" => 'newline'
}
def initialize(token_id, value, stack)
@token_id, @value, @stack = token_id, value, stack
end
@@ -13,7 +19,7 @@ module CoffeeScript
line = @value.respond_to?(:line) ? @value.line : "END"
line_part = "line #{line}:"
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}"
end
alias_method :inspect, :message

View File

@@ -26,7 +26,7 @@ module CoffeeScript
# Single-line flavors of block expressions that have unclosed endings.
# 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]
# Rewrite the token stream in multiple passes, one logical filter at
@@ -35,6 +35,7 @@ module CoffeeScript
def rewrite(tokens)
@tokens = tokens
adjust_comments
remove_leading_newlines
remove_mid_expression_newlines
move_commas_outside_outdents
add_implicit_indentation
@@ -82,6 +83,12 @@ module CoffeeScript
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
# this, remove their trailing newlines.
def remove_mid_expression_newlines

View File

@@ -47,15 +47,40 @@ module CoffeeScript
@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
def declarations?(body)
!declared_variables.empty? && body == @expressions
end
def assignments?(body)
!assigned_variables.empty? && body == @expressions
end
# Return the list of variables first declared in current scope.
def declared_variables
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
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
"<Scope:#{__id__} #{@variables.inspect}>"
end

View File

@@ -41,6 +41,10 @@ module CoffeeScript
def hash
@value.hash
end
def match(regex)
@value.match(regex)
end
end
end

View File

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

View File

@@ -5,8 +5,8 @@ print(results.join(',') is '2,18')
obj: {one: 1, two: 2, three: 3}
names: key + '!' for key of obj
odds: key + '!' for key, value of obj when value % 2 isnt 0
names: prop + '!' for prop of obj
odds: prop + '!' for prop, value of obj when value % 2 isnt 0
print(names.join(' ') is "one! two! three!")
print(odds.join(' ') is "one! three!")

View File

@@ -0,0 +1,62 @@
a: -1
b: -2
[a, b]: [b, a]
print(a is -2)
print(b is -1)
arr: [1, 2, 3]
[a, b, c]: arr
print(a is 1)
print(b is 2)
print(c is 3)
obj: {x: 10, y: 20, z: 30}
{x: a, y: b, z: c}: obj
print(a is 10)
print(b is 20)
print(c is 30)
person: {
name: "Bob"
family: {
brother: {
addresses: [
"first"
{
street: "101 Deercreek Ln."
city: "Moquasset NY, 10021"
}
]
}
}
}
{name: a, family: {brother: {addresses: [one, {city: b}]}}}: person
print(a is "Bob")
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]
print(func() == '-')
print(func() is '-')

View File

@@ -0,0 +1,22 @@
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')
obj: {
name: "Fred"
bound: =>
(==> print(this.name is "Fred"))()
unbound: =>
(=> print(!this.name?))()
}
obj.unbound()
obj.bound()

View File

@@ -0,0 +1,37 @@
a: """
basic heredoc
on two lines
"""
print(a is "basic heredoc\non two lines")
a: '''
a
"b
c
'''
print(a is "a\n \"b\nc")
a: '''one-liner'''
print(a is 'one-liner')
a: """
out
here
"""
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

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

View File

@@ -15,3 +15,17 @@ result: switch num
else false
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

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