Compare commits

..

16 Commits
0.2.4 ... 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
34 changed files with 528 additions and 205 deletions

View File

@@ -1,7 +1,7 @@
Gem::Specification.new do |s|
s.name = 'coffee-script'
s.version = '0.2.4' # Keep version in sync with coffee-script.rb
s.date = '2010-1-12'
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,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
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.4</a>
<a href="http://gemcutter.org/gems/coffee-script">0.2.5</a>
</p>
<h2>Table of Contents</h2>
@@ -76,6 +76,7 @@
<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 />
@@ -387,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
@@ -528,6 +532,17 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</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>
Hopefully, you'll never need to use it, but if you ever need to intersperse
@@ -546,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">
@@ -624,7 +644,16 @@ 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

View File

@@ -3,7 +3,7 @@
// Eat lunch.
lunch = (function() {
__a = []; __b = ['toast', 'cheese', 'wine'];
for (__c=0; __c<__b.length; __c++) {
for (__c = 0; __c < __b.length; __c++) {
food = __b[__c];
__a.push(eat(food));
}
@@ -11,10 +11,10 @@
})();
// 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,10 +1,11 @@
(function(){
var __a, __b, globals, name;
var __hasProp = Object.prototype.hasOwnProperty;
// The first ten global properties.
globals = ((function() {
__a = []; __b = window;
for (name in __b) {
if (__b.hasOwnProperty(name)) {
if (__hasProp.call(__b, name)) {
__a.push(name);
}
}

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

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

View File

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

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.4</a>
<a href="http://gemcutter.org/gems/coffee-script">0.2.5</a>
</p>
<h2>Table of Contents</h2>
@@ -62,6 +62,7 @@
<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 />
@@ -139,7 +140,7 @@ 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>() {
__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];
__a.<span class="LibraryFunction">push</span>(math.cube(num));
}
@@ -180,7 +181,7 @@ if ((typeof elvis !== "undefined" && elvis !== null)) {
// Array comprehensions:
cubed_list = (function() {
__a = []; __b = list;
for (__c=0; __c<__b.length; __c++) {
for (__c = 0; __c < __b.length; __c++) {
num = __b[__c];
__a.push(math.cube(num));
}
@@ -667,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
@@ -709,7 +745,7 @@ backwards("stairway", "to", "heaven");
<span class="Comment"><span class="Comment">//</span> Eat lunch.</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>];
<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];
__a.<span class="LibraryFunction">push</span>(eat(food));
}
@@ -717,10 +753,10 @@ lunch <span class="Keyword">=</span> (<span class="Storage">function</span>() {
})();
<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)) {
@@ -791,6 +827,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
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>,
@@ -800,13 +837,14 @@ ages <span class="Keyword">=</span> (<span class="Storage">function</span>() {
__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> (__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);
}
}
<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,
@@ -816,7 +854,7 @@ ages = (function() {
__a = []; __b = years_old;
for (child in __b) {
age = __b[child];
if (__b.hasOwnProperty(child)) {
if (__hasProp.call(__b, child)) {
__a.push(child + " is " + age);
}
}
@@ -931,22 +969,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>]
</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>() {
__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> (__b.hasOwnProperty(name)) {
<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> __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() {
__a = []; __b = window;
for (name in __b) {
if (__b.hasOwnProperty(name)) {
if (__hasProp.call(__b, name)) {
__a.push(name);
}
}
@@ -1230,6 +1270,41 @@ 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>
Hopefully, you'll never need to use it, but if you ever need to intersperse
@@ -1261,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();
@@ -1406,7 +1489,16 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
</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

View File

@@ -10,7 +10,7 @@ require "coffee_script/parse_error"
# Namespace for all CoffeeScript internal classes.
module CoffeeScript
VERSION = '0.2.4' # 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>

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,22 +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?(\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
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.
@@ -50,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
@@ -105,7 +107,7 @@ module CoffeeScript
# Matches strings, including multi-line strings.
def string_token
return false unless string = @chunk[STRING, 1]
escaped = string.gsub(MULTILINER, " \\\n")
escaped = string.gsub(STRING_NEWLINES, " \\\n")
token(:STRING, escaped)
@line += string.count("\n")
@i += string.length
@@ -114,9 +116,10 @@ module CoffeeScript
# Matches heredocs, adjusting indentation to the correct level.
def heredoc_token
return false unless match = @chunk.match(HEREDOC)
indent = match[2] || match[5]
doc = match[3] || match[6]
doc.gsub!(/\n#{indent}/, "\\n")
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")
@@ -153,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('')})"
@@ -447,23 +449,27 @@ module CoffeeScript
end
def compile_node(o)
return compile_pattern_match(o) if @variable.array? || @variable.object?
return compile_splice(o) if @variable.splice?
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?
@variable.array? || @variable.object?
value? && (@variable.array? || @variable.object?)
end
# Implementation of recursive pattern matching, when assigning array or
@@ -476,9 +482,12 @@ module CoffeeScript
@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
assigns << AssignNode.new(
obj, ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))])
).compile(o)
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
@@ -541,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
@@ -557,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
@@ -579,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
@@ -644,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
@@ -655,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
@@ -684,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?
@@ -703,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
@@ -712,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]
@@ -724,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}
@@ -818,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
@@ -839,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
@@ -861,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
@@ -876,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.4"
"version": "0.2.5"
}

View File

@@ -43,4 +43,20 @@ person: {
{name: a, family: {brother: {addresses: [one, {city: b}]}}}: person
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]
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

@@ -25,4 +25,13 @@ a: """
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

@@ -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