Compare commits

...

26 Commits
0.2.5 ... 0.2.6

Author SHA1 Message Date
Jeremy Ashkenas
63c9b5c2f0 CoffeeScript 0.2.6 is on the books 2010-01-17 18:12:59 -05:00
Jeremy Ashkenas
80fbe02fda ignoring the top-down parser that doesn't work 2010-01-17 17:41:38 -05:00
Jeremy Ashkenas
e514a39dd2 added binary search example -- chapter 6 of beautiful code 2010-01-17 16:18:24 -05:00
Jeremy Ashkenas
4a32c58221 added bentley's chapter from beautiful code to the examples/tests -- quicksort runtime analysis 2010-01-17 15:58:44 -05:00
Jeremy Ashkenas
4609ad78c2 added the first chapter of beautiful code as a coffeescript example 2010-01-17 15:36:46 -05:00
Jeremy Ashkenas
2d90a751f7 edits for clarity 2010-01-17 14:55:06 -05:00
Jeremy Ashkenas
8647b54a61 rename compile_double_reference to compile_reference 2010-01-17 14:26:00 -05:00
Jeremy Ashkenas
8e1f3c0eca generating multiple calls to the same function should use compile_double_reference to ensure a single evaluation of the call itself. 2010-01-17 14:23:41 -05:00
Jeremy Ashkenas
c4d0903e6a fixing assignment-in-condition 2010-01-17 10:40:59 -05:00
Jeremy Ashkenas
e72ef1a61a reverting change 2010-01-17 10:28:04 -05:00
Jeremy Ashkenas
d7d9cb8d28 only let returns stop an expression from being closure-ified -- breaks and continues may be valid 2010-01-17 10:21:24 -05:00
Jeremy Ashkenas
f6c8e81ea6 the existential operator can now be used infix as well 2010-01-16 23:03:54 -05:00
Jeremy Ashkenas
52539ae7d2 abbreviating the existential operator 2010-01-16 22:26:34 -05:00
Jeremy Ashkenas
95b362499f added the conditional existence operator 2010-01-16 22:17:55 -05:00
Jeremy Ashkenas
0bc4da2b51 ensure that functions are only called once, when chaining comparators 2010-01-16 22:04:08 -05:00
Jeremy Ashkenas
9679fc0b52 removing redundant unary check 2010-01-16 16:49:03 -05:00
Jeremy Ashkenas
9cb0564972 added Python's chainable comparisons, like: 10 > 5 > 1 2010-01-16 16:37:49 -05:00
Jeremy Ashkenas
c6c0c7d059 simplification of function and prototype naming -- last_assign, immediate_assign, and proto_assign are gone, in favor of 'name' and 'proto' properties on CodeNodes 2010-01-16 15:44:07 -05:00
Jeremy Ashkenas
62e946b8ce purely empty functions at the top level should be wrapped in parens, so as not to cause a JS syntax error 2010-01-16 15:02:04 -05:00
Jeremy Ashkenas
6c782b7723 fixes for syntax highlighting assignments and regexes 2010-01-16 14:28:42 -05:00
Jeremy Ashkenas
9eff443032 arguments no longer is just a find-and-replace -- it'll fix the arguments variable at the top of scope if you use it in a function body 2010-01-16 12:52:26 -05:00
Jeremy Ashkenas
8957feedb4 expression closure wrappers are now safer -- they won't be generated if there's a statement_only inside 2010-01-16 12:10:31 -05:00
Jeremy Ashkenas
1cd7fa8ebe added children macro to Node, using it so that all nodes now have a 'children' method -- used for safe references to 'this' within closure wrappers 2010-01-16 11:24:10 -05:00
Jeremy Ashkenas
701cdb4c13 never try to push a statement_only 2010-01-15 19:47:16 -05:00
Jeremy Ashkenas
8dc5da9cc9 adding coffee-haml-filter to the resources section 2010-01-14 14:44:03 -05:00
Jeremy Ashkenas
001cc29deb slightly shorter generated code for ==> 2010-01-14 08:55:09 -05:00
35 changed files with 644 additions and 255 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,7 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.2.5</a>
<a href="http://gemcutter.org/gems/coffee-script">0.2.6</a>
</p>
<h2>Table of Contents</h2>
@@ -65,7 +65,7 @@
<a href="#objects_and_arrays">Objects and Arrays</a><br />
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br />
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br />
<a href="#existence">The Existence Operator</a><br />
<a href="#existence">The Existential Operator</a><br />
<a href="#aliases">Aliases</a><br />
<a href="#splats">Splats...</a><br />
<a href="#arguments">Arguments are Arrays</a><br />
@@ -80,6 +80,7 @@
<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="#comparisons">Chained Comparisons</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br />
<a href="#contributing">Contributing</a><br />
@@ -258,7 +259,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b class="header">Functions and Invocation</b>
Functions are defined by a list of parameters, an arrow, and the
function body. The empty function looks like this: <tt>=></tt>. All
functions in CoffeeScript are named, for the benefit of debug messages.
functions in CoffeeScript are named by default, for the benefit of debug messages.
If you'd like to create an anonymous function, just wrap it in parentheses.
</p>
<%= code_for('functions', 'cube(5)') %>
@@ -329,14 +331,18 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<p id="existence">
<b class="header">The Existence Operator</b>
<b class="header">The Existential Operator</b>
It's a little difficult to check for the existence of a variable in
JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero,
the empty string, and false. The existence operator <tt>?</tt> returns true unless
the empty string, and false. The existential operator <tt>?</tt> returns true unless
a variable is <b>null</b> or <b>undefined</b>, which makes it analogous
to Ruby's <tt>nil?</tt>
</p>
<%= code_for('existence') %>
<p>
It can also be used for safer conditional assignment than <tt>||=</tt>
provides, for cases where you may be handling numbers or strings.
</p>
<%= code_for('existence', 'speed') %>
<p id="aliases">
<b class="header">Aliases</b>
@@ -473,6 +479,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
into a function call:
</p>
<%= code_for('expressions_try', true) %>
<p>
There are a handful of statements in JavaScript that can't be meaningfully
converted into expressions: <tt>break</tt>, <tt>continue</tt>,
and <tt>return</tt>. If you make use of them within a block of code,
CoffeeScript won't try to perform the conversion.
</p>
<p id="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b>
@@ -575,6 +587,15 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('try') %>
<p id="comparisons">
<b class="header">Chained Comparisons</b>
CoffeeScript borrows
<a href="http://docs.python.org/reference/expressions.html#notin">chained comparisons</a>
from Python &mdash; making it easy to test if a value falls within a
certain range.
</p>
<%= code_for('comparisons', 'healthy') %>
<p id="strings">
<b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript.
@@ -610,6 +631,12 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
that includes CoffeeScript helpers,
bundling and minification.
</li>
<li>
<a href="http://github.com/inem/coffee-haml-filter">coffee-haml-filter</a><br />
A custom <a href="http://haml-lang.com/">HAML</a> filter, by
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
</li>
</ul>
<h2 id="contributing">Contributing</h2>
@@ -645,6 +672,14 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<h2 id="change_log">Change Log</h2>
<p>
<b class="header" style="margin-top: 20px;">0.2.6</b>
Added Python-style chained comparisons, the conditional existence
operator <tt>?=</tt>, and some examples from <i>Beautiful Code</i>.
Bugfixes relating to statement-to-expression conversion, arguments-to-array
conversion, and the TextMate syntax highlighter.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.2.5</b>
The conditions in switch statements can now take multiple values at once &mdash;
@@ -697,7 +732,7 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b class="header" style="margin-top: 20px;">0.2.0</b>
Major release. Significant whitespace. Better statement-to-expression
conversion. Splats. Splice literals. Object comprehensions. Blocks.
The existence operator. Many thanks to all the folks who posted issues,
The existential operator. Many thanks to all the folks who posted issues,
with special thanks to
<a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace
and expression help.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,17 @@
(function(){
var Account;
Account = function Account(customer, cart) {
var __a, __b;
var __this = this;
var __a;
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;
__a = $('.shopping_cart').bind('click', (function(__this) {
var __func = function(event) {
return this.customer.purchase(this.cart);
};
return (function() {
return __b.apply(__this, arguments);
return __func.apply(__this, arguments);
});
})());
})(this));
return Account === this.constructor ? this : __a;
};
})();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,7 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.2.5</a>
<a href="http://gemcutter.org/gems/coffee-script">0.2.6</a>
</p>
<h2>Table of Contents</h2>
@@ -51,7 +51,7 @@
<a href="#objects_and_arrays">Objects and Arrays</a><br />
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br />
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br />
<a href="#existence">The Existence Operator</a><br />
<a href="#existence">The Existential Operator</a><br />
<a href="#aliases">Aliases</a><br />
<a href="#splats">Splats...</a><br />
<a href="#arguments">Arguments are Arrays</a><br />
@@ -66,6 +66,7 @@
<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="#comparisons">Chained Comparisons</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br />
<a href="#contributing">Contributing</a><br />
@@ -355,7 +356,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b class="header">Functions and Invocation</b>
Functions are defined by a list of parameters, an arrow, and the
function body. The empty function looks like this: <tt>=></tt>. All
functions in CoffeeScript are named, for the benefit of debug messages.
functions in CoffeeScript are named by default, for the benefit of debug messages.
If you'd like to create an anonymous function, just wrap it in parentheses.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">square</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</span> x <span class="Keyword">*</span> x
<span class="FunctionName">cube</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</span> square(x) <span class="Keyword">*</span> x
@@ -500,7 +502,7 @@ new_num = change_numbers();
<span class="FunctionName">date</span><span class="Keyword">:</span> <span class="Keyword">if</span> friday <span class="Keyword">then</span> sue <span class="Keyword">else</span> jill
expensive <span class="Keyword">||</span><span class="Keyword">=</span> do_the_math()
</pre><pre class="idle"><span class="Storage">var</span> date, mood;
</pre><pre class="idle"><span class="Storage">var</span> date, expensive, mood;
<span class="Keyword">if</span> (singing) {
mood <span class="Keyword">=</span> greatly_improved;
}
@@ -519,19 +521,36 @@ expensive <span class="Keyword">=</span> expensive <span class="Keyword">||</spa
</p>
<p id="existence">
<b class="header">The Existence Operator</b>
<b class="header">The Existential Operator</b>
It's a little difficult to check for the existence of a variable in
JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero,
the empty string, and false. The existence operator <tt>?</tt> returns true unless
the empty string, and false. The existential operator <tt>?</tt> returns true unless
a variable is <b>null</b> or <b>undefined</b>, which makes it analogous
to Ruby's <tt>nil?</tt>
</p>
<p>
It can also be used for safer conditional assignment than <tt>||=</tt>
provides, for cases where you may be handling numbers or strings.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">solipsism</span><span class="Keyword">:</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> mind<span class="Keyword">?</span> <span class="Keyword">and</span> <span class="Keyword">not</span> world<span class="Keyword">?</span>
</pre><pre class="idle"><span class="Storage">var</span> solipsism;
speed <span class="Keyword">?</span><span class="Keyword">=</span> <span class="Number">140</span>
</pre><pre class="idle"><span class="Storage">var</span> solipsism, speed;
<span class="Keyword">if</span> ((<span class="Keyword">typeof</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> <span class="Keyword">!</span>(<span class="Keyword">typeof</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>)) {
solipsism <span class="Keyword">=</span> <span class="BuiltInConstant">true</span>;
}
</pre><br class='clear' /></div>
speed <span class="Keyword">=</span> (<span class="Keyword">typeof</span> speed <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> speed <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) ? speed : <span class="Number">140</span>;
</pre><button onclick='javascript: var solipsism, speed;
if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) {
solipsism = true;
}
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
;alert(speed);'>run: speed</button><br class='clear' /></div>
<p id="aliases">
<b class="header">Aliases</b>
@@ -614,7 +633,7 @@ alert(<span class="String"><span class="String">&quot;</span>Gold: <span class="
alert(<span class="String"><span class="String">&quot;</span>Silver: <span class="String">&quot;</span></span> <span class="Keyword">+</span> silver)
alert(<span class="String"><span class="String">&quot;</span>The Field: <span class="String">&quot;</span></span> <span class="Keyword">+</span> the_field)
</pre><pre class="idle"><span class="Storage">var</span> contenders, gold, medalists, silver, the_field;
gold <span class="Keyword">=</span> silver <span class="Keyword">=</span> the_field <span class="Keyword">=</span> <span class="String"><span class="String">&quot;</span>unknown<span class="String">&quot;</span></span>;
gold <span class="Keyword">=</span> (silver <span class="Keyword">=</span> (the_field <span class="Keyword">=</span> <span class="String"><span class="String">&quot;</span>unknown<span class="String">&quot;</span></span>));
medalists <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">medalists</span>(<span class="FunctionArgument">first, second</span>) {
<span class="Storage">var</span> rest;
rest <span class="Keyword">=</span> <span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">2</span>);
@@ -628,7 +647,7 @@ medalists.<span class="LibraryFunction">apply</span>(<span class="Variable">this
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>Silver: <span class="String">&quot;</span></span> <span class="Keyword">+</span> silver);
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>The Field: <span class="String">&quot;</span></span> <span class="Keyword">+</span> the_field);
</pre><button onclick='javascript: var contenders, gold, medalists, silver, the_field;
gold = silver = the_field = "unknown";
gold = (silver = (the_field = "unknown"));
medalists = function medalists(first, second) {
var rest;
rest = Array.prototype.slice.call(arguments, 2);
@@ -656,12 +675,14 @@ alert("The Field: " + the_field);
backwards(<span class="String"><span class="String">&quot;</span>stairway<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>to<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>heaven<span class="String">&quot;</span></span>)
</pre><pre class="idle"><span class="Storage">var</span> backwards;
backwards <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">backwards</span>() {
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">0</span>).<span class="LibraryFunction">reverse</span>());
<span class="Storage">var</span> arguments <span class="Keyword">=</span> <span class="LibraryClassType">Array</span>.<span class="LibraryConstant">prototype</span>.slice.<span class="LibraryFunction">call</span>(arguments, <span class="Number">0</span>);
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(arguments.<span class="LibraryFunction">reverse</span>());
};
backwards(<span class="String"><span class="String">&quot;</span>stairway<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>to<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>heaven<span class="String">&quot;</span></span>);
</pre><button onclick='javascript: var backwards;
backwards = function backwards() {
return alert(Array.prototype.slice.call(arguments, 0).reverse());
var arguments = Array.prototype.slice.call(arguments, 0);
return alert(arguments.reverse());
};
backwards("stairway", "to", "heaven");
;'>run</button><br class='clear' /></div>
@@ -955,9 +976,9 @@ eldest = 24 > 21 ? "Liz" : "Ike";
</p>
<div class='code'><pre class="idle"><span class="FunctionName">six</span><span class="Keyword">:</span> (<span class="FunctionName">one</span><span class="Keyword">:</span> <span class="Number">1</span>) <span class="Keyword">+</span> (<span class="FunctionName">two</span><span class="Keyword">:</span> <span class="Number">2</span>) <span class="Keyword">+</span> (<span class="FunctionName">three</span><span class="Keyword">:</span> <span class="Number">3</span>)
</pre><pre class="idle"><span class="Storage">var</span> one, six, three, two;
six <span class="Keyword">=</span> (one <span class="Keyword">=</span> <span class="Number">1</span>) <span class="Keyword">+</span> (two <span class="Keyword">=</span> <span class="Number">2</span>) <span class="Keyword">+</span> (three <span class="Keyword">=</span> <span class="Number">3</span>);
six <span class="Keyword">=</span> ((one <span class="Keyword">=</span> <span class="Number">1</span>)) <span class="Keyword">+</span> ((two <span class="Keyword">=</span> <span class="Number">2</span>)) <span class="Keyword">+</span> ((three <span class="Keyword">=</span> <span class="Number">3</span>));
</pre><button onclick='javascript: var one, six, three, two;
six = (one = 1) + (two = 2) + (three = 3);
six = ((one = 1)) + ((two = 2)) + ((three = 3));
;alert(six);'>run: six</button><br class='clear' /></div>
<p>
Things that would otherwise be statements in JavaScript, when used
@@ -1018,6 +1039,12 @@ globals = ((function() {
}
})());
;'>run</button><br class='clear' /></div>
<p>
There are a handful of statements in JavaScript that can't be meaningfully
converted into expressions: <tt>break</tt>, <tt>continue</tt>,
and <tt>return</tt>. If you make use of them within a block of code,
CoffeeScript won't try to perform the conversion.
</p>
<p id="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b>
@@ -1065,8 +1092,7 @@ tom.move()
</pre><pre class="idle"><span class="Storage">var</span> Animal, Horse, Snake, __a, __b, sam, tom;
Animal <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Animal</span>() {
};
Animal <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Animal</span>() { };
<span class="LibraryClassType">Animal</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span> <span class="FunctionName">move</span>(<span class="FunctionArgument">meters</span>) {
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>);
};
@@ -1103,8 +1129,7 @@ tom <span class="Keyword">=</span> <span class="Keyword">new</span> <span class=
sam.move();
tom.move();
</pre><button onclick='javascript: var Animal, Horse, Snake, __a, __b, sam, tom;
Animal = function Animal() {
};
Animal = function Animal() { };
Animal.prototype.move = function move(meters) {
return alert(this.name + " moved " + meters + "m.");
};
@@ -1287,20 +1312,17 @@ city = __c[1];
<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="Storage">var</span> __a;
<span class="Variable">this</span>.customer <span class="Keyword">=</span> customer;
<span class="Variable">this</span>.cart <span class="Keyword">=</span> cart;
__a <span class="Keyword">=</span> <span class="Keyword">$</span>(<span class="String"><span class="String">'</span>.shopping_cart<span class="String">'</span></span>).bind(<span class="String"><span class="String">'</span>click<span class="String">'</span></span>, (<span class="Storage">function</span>() {
<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;
__a <span class="Keyword">=</span> <span class="Keyword">$</span>(<span class="String"><span class="String">'</span>.shopping_cart<span class="String">'</span></span>).bind(<span class="String"><span class="String">'</span>click<span class="String">'</span></span>, (<span class="Storage">function</span>(__this) {
<span class="Storage">var</span> <span class="FunctionName">__func</span> = <span class="Storage">function</span>(<span class="FunctionArgument">event</span>) {
<span class="Keyword">return</span> <span class="Variable">this</span>.customer.purchase(<span class="Variable">this</span>.cart);
};
<span class="Keyword">return</span> (<span class="Storage">function</span>() {
<span class="Keyword">return</span> __b.<span class="LibraryFunction">apply</span>(__this, arguments);
<span class="Keyword">return</span> __func.<span class="LibraryFunction">apply</span>(__this, arguments);
});
})());
})(<span class="Variable">this</span>));
<span class="Keyword">return</span> Account <span class="Keyword">===</span> <span class="Variable">this</span>.<span class="LibraryConstant">constructor</span> ? <span class="Variable">this</span> : __a;
};
</pre><br class='clear' /></div>
@@ -1391,6 +1413,26 @@ return [document.title, "Hello JavaScript"].join(": ");
}
</pre><br class='clear' /></div>
<p id="comparisons">
<b class="header">Chained Comparisons</b>
CoffeeScript borrows
<a href="http://docs.python.org/reference/expressions.html#notin">chained comparisons</a>
from Python &mdash; making it easy to test if a value falls within a
certain range.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">cholesterol</span><span class="Keyword">:</span> <span class="Number">127</span>
<span class="FunctionName">healthy</span><span class="Keyword">:</span> <span class="Number">200</span> <span class="Keyword">&gt;</span> cholesterol <span class="Keyword">&gt;</span> <span class="Number">60</span>
</pre><pre class="idle"><span class="Storage">var</span> cholesterol, healthy;
cholesterol <span class="Keyword">=</span> <span class="Number">127</span>;
healthy <span class="Keyword">=</span> (<span class="Number">200</span> <span class="Keyword">&gt;</span> cholesterol) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> (cholesterol <span class="Keyword">&gt;</span> <span class="Number">60</span>);
</pre><button onclick='javascript: var cholesterol, healthy;
cholesterol = 127;
healthy = (200 > cholesterol) && (cholesterol > 60);
;alert(healthy);'>run: healthy</button><br class='clear' /></div>
<p id="strings">
<b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript.
@@ -1455,6 +1497,12 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
that includes CoffeeScript helpers,
bundling and minification.
</li>
<li>
<a href="http://github.com/inem/coffee-haml-filter">coffee-haml-filter</a><br />
A custom <a href="http://haml-lang.com/">HAML</a> filter, by
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
</li>
</ul>
<h2 id="contributing">Contributing</h2>
@@ -1490,6 +1538,14 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
<h2 id="change_log">Change Log</h2>
<p>
<b class="header" style="margin-top: 20px;">0.2.6</b>
Added Python-style chained comparisons, the conditional existence
operator <tt>?=</tt>, and some examples from <i>Beautiful Code</i>.
Bugfixes relating to statement-to-expression conversion, arguments-to-array
conversion, and the TextMate syntax highlighter.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.2.5</b>
The conditions in switch statements can now take multiple values at once &mdash;
@@ -1542,7 +1598,7 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
<b class="header" style="margin-top: 20px;">0.2.0</b>
Major release. Significant whitespace. Better statement-to-expression
conversion. Splats. Splice literals. Object comprehensions. Blocks.
The existence operator. Many thanks to all the folks who posted issues,
The existential operator. Many thanks to all the folks who posted issues,
with special thanks to
<a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace
and expression help.

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ token ARGUMENTS
token NEWLINE
token COMMENT
token JS
token THIS
token INDENT OUTDENT
# Declare order of operations.
@@ -37,7 +38,7 @@ prechigh
right WHEN LEADING_WHEN IN OF BY
right THROW FOR NEW SUPER
left EXTENDS
left ASSIGN '||=' '&&='
left ASSIGN '||=' '&&=' '?='
right RETURN
right '=>' '==>' UNLESS IF ELSE WHILE
preclow
@@ -102,12 +103,12 @@ rule
| BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) }
| ARGUMENTS { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(true) }
| FALSE { result = LiteralNode.new(false) }
| YES { result = LiteralNode.new(true) }
| NO { result = LiteralNode.new(false) }
| ON { result = LiteralNode.new(true) }
| OFF { result = LiteralNode.new(false) }
| TRUE { result = LiteralNode.new(Value.new(true)) }
| FALSE { result = LiteralNode.new(Value.new(false)) }
| YES { result = LiteralNode.new(Value.new(true)) }
| NO { result = LiteralNode.new(Value.new(false)) }
| ON { result = LiteralNode.new(Value.new(true)) }
| OFF { result = LiteralNode.new(Value.new(false)) }
;
# Assignment to a variable (or index).
@@ -178,6 +179,7 @@ rule
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
@@ -186,6 +188,7 @@ rule
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
@@ -235,6 +238,7 @@ rule
| Range { result = ValueNode.new(val[0]) }
| Value Accessor { result = val[0] << val[1] }
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
| THIS { result = ValueNode.new(ThisNode.new) }
;
# Accessing into an object or array, through dot or index notation.
@@ -292,7 +296,7 @@ rule
# Calling super.
Super:
SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) }
SUPER "(" ArgList ")" { result = CallNode.new(Value.new('super'), val[2]) }
;
# The range literal.

View File

@@ -13,10 +13,11 @@ module CoffeeScript
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends",
"arguments",
"delete", "instanceof", "typeof"]
"this"]
# Token matching regexes.
IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/
@@ -24,7 +25,7 @@ module CoffeeScript
STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m
HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?(\s*)"{3}|'{3}\n?(.*?)\n?(\s*)'{3})/m
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
OPERATOR = /\A([+\*&|\/\-%=<>:!?]+)/
WHITESPACE = /\A([ \t]+)/
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
CODE = /\A(=?=>)/

View File

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

View File

@@ -24,6 +24,19 @@ module CoffeeScript
class_eval "def statement_only?; true; end"
end
# This node needs to know if it's being compiled as a top-level statement,
# in order to compile without special expression conversion.
def self.top_sensitive
class_eval "def top_sensitive?; true; end"
end
# Provide a quick implementation of a children method.
def self.children(*attributes)
attr_reader(*attributes)
attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
class_eval "def children; [#{attrs}].flatten.compact; end"
end
def write(code)
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
code
@@ -37,14 +50,18 @@ module CoffeeScript
@options = o.dup
@indent = o[:indent]
top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
closure = statement? && !statement_only? && !top && !@options[:return]
closure = statement? && !statement_only? && !top && !@options[:return] && !self.is_a?(CommentNode)
closure &&= !contains? {|n| n.statement_only? }
closure ? compile_closure(@options) : compile_node(@options)
end
def compile_closure(o={})
indent = o[:indent]
@indent = (o[:indent] = idt(1))
"(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()"
indent = o[:indent]
@indent = (o[:indent] = idt(1))
pass_this = !o[:closure] && contains? {|node| node.is_a?(ThisNode) }
param = pass_this ? '__this' : ''
body = compile_node(o.merge(:return => true, :closure => true))
"(function(#{param}) {\n#{body}\n#{indent}})(#{pass_this ? 'this' : ''})"
end
# Quick short method for the current indentation level, plus tabbing in.
@@ -52,8 +69,18 @@ module CoffeeScript
@indent + (TAB * tabs)
end
# Does this node, or any of it's children, contain a node of a certain kind?
def contains?(&block)
children.each do |node|
return true if yield(node)
return true if node.is_a?(Node) && node.contains?(&block)
end
false
end
# Default implementations of the common node methods.
def unwrap; self; end
def children; []; end
def statement?; false; end
def statement_only?; false; end
def top_sensitive?; false; end
@@ -62,10 +89,10 @@ module CoffeeScript
# A collection of nodes, each one representing an expression.
class Expressions < Node
statement
attr_reader :expressions
children :expressions
attr_accessor :function
TRAILING_WHITESPACE = /\s+$/
UPPERCASE = /[A-Z]/
# Wrap up a node as an Expressions, unless it already is.
def self.wrap(*nodes)
@@ -94,18 +121,17 @@ module CoffeeScript
@expressions.length == 1 ? @expressions.first : self
end
# Is this an empty block of code?
def empty?
@expressions.empty?
end
# Is the node last in this block of expressions.
def last?(node)
@last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
node == @expressions[@last_index]
end
# Determine if this is the expressions body within a constructor function.
# Constructors are capitalized by CoffeeScript convention.
def constructor?(o)
o[:top] && o[:last_assign] && o[:last_assign][0..0][UPPERCASE]
end
def compile(o={})
o[:scope] ? super(o) : compile_root(o)
end
@@ -119,7 +145,7 @@ module CoffeeScript
def compile_root(o={})
indent = o[:no_wrap] ? '' : TAB
@indent = indent
o.merge!(:indent => indent, :scope => Scope.new(nil, self))
o.merge!(:indent => indent, :scope => Scope.new(nil, self, nil))
code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
code.gsub!(TRAILING_WHITESPACE, '')
write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();")
@@ -129,8 +155,11 @@ module CoffeeScript
# at the top.
def compile_with_declarations(o={})
code = compile_node(o)
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)
args = self.contains? {|n| n.is_a?(LiteralNode) && n.arguments? }
argv = args && o[:scope].check('arguments') ? '' : 'var '
code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
write(code)
end
@@ -145,10 +174,10 @@ module CoffeeScript
# If it's a statement, the node knows how to return itself.
return node.compile(o.merge(:return => true)) if node.statement?
# If it's not part of a constructor, we can just return the value of the expression.
return "#{idt}return #{node.compile(o)};" unless constructor?(o)
return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor?
# It's the last line of a constructor, add a safety check.
temp = o[:scope].free_variable
"#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:last_assign]} === this.constructor ? this : #{temp};"
"#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};"
end
end
@@ -156,17 +185,12 @@ module CoffeeScript
# Literals are static values that have a Ruby representation, eg.: a string, a number,
# true, false, nil, etc.
class LiteralNode < Node
children :value
# Values of a literal node that much be treated as a statement -- no
# sense returning or assigning them.
STATEMENTS = ['break', 'continue']
# If we get handed a literal reference to an arguments object, convert
# it to an array.
ARG_ARRAY = 'Array.prototype.slice.call(arguments, 0)'
attr_reader :value
# Wrap up a compiler-generated string as a LiteralNode.
def self.wrap(string)
self.new(Value.new(string))
@@ -181,19 +205,21 @@ module CoffeeScript
end
alias_method :statement_only?, :statement?
def arguments?
@value.to_s == 'arguments'
end
def compile_node(o)
@value = ARG_ARRAY if @value.to_s.to_sym == :arguments
indent = statement? ? idt : ''
ending = statement? ? ';' : ''
write "#{indent}#{@value}#{ending}"
"#{indent}#{@value}#{ending}"
end
end
# Return an expression, or wrap it in a closure and return it.
class ReturnNode < Node
statement_only
attr_reader :expression
children :expression
def initialize(expression)
@expression = expression
@@ -209,7 +235,7 @@ module CoffeeScript
# Pass through CoffeeScript comments into JavaScript comments at the
# same position.
class CommentNode < Node
statement_only
statement
def initialize(lines)
@lines = lines.value
@@ -225,47 +251,38 @@ module CoffeeScript
# Node for a function invocation. Takes care of converting super() calls into
# calls against the prototype's function of the same name.
class CallNode < Node
attr_reader :variable, :arguments
children :variable, :arguments
def initialize(variable, arguments=[])
@variable, @arguments = variable, arguments
@prefix = ''
end
def new_instance
@new = true
@prefix = "new "
self
end
def super?
@variable == :super
end
def prefix
@new ? "new " : ''
end
def splat?
@arguments.any? {|a| a.is_a?(SplatNode) }
end
def <<(argument)
@arguments << argument
self
end
# Compile a vanilla function call.
def compile_node(o)
return write(compile_splat(o)) if splat?
return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
args = @arguments.map{|a| a.compile(o) }.join(', ')
return write(compile_super(args, o)) if super?
write("#{prefix}#{@variable.compile(o)}(#{args})")
return write(compile_super(args, o)) if @variable == 'super'
write("#{@prefix}#{@variable.compile(o)}(#{args})")
end
# Compile a call against the superclass's implementation of the current function.
def compile_super(args, o)
methname = o[:last_assign]
methname = o[:scope].function.name
arg_part = args.empty? ? '' : ", #{args}"
meth = o[:proto_assign] ? "#{o[:proto_assign]}.__superClass__.#{methname}" :
"#{methname}.__superClass__.constructor"
meth = o[:scope].function.proto ?
"#{o[:scope].function.proto}.__superClass__.#{methname}" :
"#{methname}.__superClass__.constructor"
"#{meth}.call(this#{arg_part})"
end
@@ -278,15 +295,23 @@ module CoffeeScript
code = arg.is_a?(SplatNode) ? code : "[#{code}]"
arg.equal?(@arguments.first) ? code : ".concat(#{code})"
end
"#{prefix}#{meth}.apply(#{obj}, #{args.join('')})"
"#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
end
# If the code generation wished to use the result of a function call
# in multiple places, ensure that the function is only ever called once.
def compile_reference(o)
reference = o[:scope].free_variable
call = ParentheticalNode.new(AssignNode.new(reference, self))
return call, reference
end
end
# Node to extend an object's prototype with an ancestor object.
# After goog.inherits from the Closure Library.
class ExtendsNode < Node
children :sub_object, :super_object
statement
attr_reader :sub_object, :super_object
def initialize(sub_object, super_object)
@sub_object, @super_object = sub_object, super_object
@@ -307,7 +332,8 @@ module CoffeeScript
# A value, indexed or dotted into, or vanilla.
class ValueNode < Node
attr_reader :base, :properties, :last, :source
children :base, :properties
attr_reader :last, :source
def initialize(base, properties=[])
@base, @properties = base, properties
@@ -334,6 +360,10 @@ module CoffeeScript
properties? && @properties.last.is_a?(SliceNode)
end
def unwrap
@properties.empty? ? @base : self
end
# Values are statements if their base is a statement.
def statement?
@base.is_a?(Node) && @base.statement? && !properties?
@@ -352,7 +382,7 @@ module CoffeeScript
# A dotted accessor into a part of a value, or the :: shorthand for
# an accessor into the object's prototype.
class AccessorNode < Node
attr_reader :name
children :name
def initialize(name, prototype=false)
@name, @prototype = name, prototype
@@ -366,7 +396,7 @@ module CoffeeScript
# An indexed accessor into a part of an array or object.
class IndexNode < Node
attr_reader :index
children :index
def initialize(index)
@index = index
@@ -377,10 +407,20 @@ module CoffeeScript
end
end
# A node to represent a reference to "this". Needs to be transformed into a
# reference to the correct value of "this", when used within a closure wrapper.
class ThisNode < Node
def compile_node(o)
write(o[:closure] ? "__this" : "this")
end
end
# A range literal. Ranges can be used to extract portions (slices) of arrays,
# or to specify a range for array comprehensions.
class RangeNode < Node
attr_reader :from, :to
children :from, :to
def initialize(from, to, exclusive=false)
@from, @to, @exclusive = from, to, exclusive
@@ -423,7 +463,7 @@ module CoffeeScript
# specifies the index of the end of the slice (just like the first parameter)
# is the index of the beginning.
class SliceNode < Node
attr_reader :range
children :range
def initialize(range)
@range = range
@@ -439,29 +479,35 @@ module CoffeeScript
# Setting the value of a local variable, or the value of an object property.
class AssignNode < Node
top_sensitive
children :variable, :value
PROTO_ASSIGN = /\A(\S+)\.prototype/
LEADING_DOT = /\A\.(prototype\.)?/
attr_reader :variable, :value, :context
def initialize(variable, value, context=nil)
@variable, @value, @context = variable, value, context
end
def compile_node(o)
top = o.delete(:top)
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 = 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)
stmt = o.delete(:as_statement)
name = @variable.compile(o)
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
proto = name[PROTO_ASSIGN, 1]
if @value.is_a?(CodeNode)
@value.name = last if last.match(Lexer::IDENTIFIER)
@value.proto = proto if proto
end
return write("#{name}: #{@value.compile(o)}") if @context == :object
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)
val = "(#{val})" if !top || o[:return]
val = "#{idt}return #{val}" if o[:return]
write(val)
end
def value?
@@ -485,7 +531,7 @@ module CoffeeScript
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))])
val = ValueNode.new(val_var, [access_class.new(Value.new(i.to_s))])
end
assigns << AssignNode.new(obj, val).compile(o)
end
@@ -505,6 +551,10 @@ module CoffeeScript
# Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents.
class OpNode < Node
children :first, :second
attr_reader :operator
attr_accessor :second
CONVERSIONS = {
:== => "===",
:'!=' => "!==",
@@ -514,11 +564,10 @@ module CoffeeScript
:isnt => "!==",
:not => '!'
}
CONDITIONALS = [:'||=', :'&&=']
CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
ASSIGNMENT = [:'||=', :'&&=', :'?=']
PREFIX_OPERATORS = [:typeof, :delete]
attr_reader :operator, :first, :second
def initialize(operator, first, second=nil, flip=false)
@first, @second, @flip = first, second, flip
@operator = CONVERSIONS[operator.to_sym] || operator
@@ -528,18 +577,39 @@ module CoffeeScript
@second.nil?
end
def chainable?
CHAINABLE.include?(operator.to_sym)
end
def compile_node(o)
return write(compile_conditional(o)) if CONDITIONALS.include?(@operator.to_sym)
return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
return write(compile_unary(o)) if unary?
return write(compile_existence(o)) if @operator == '?'
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
end
def compile_conditional(o)
# Mimic Python's chained comparisons. See:
# http://docs.python.org/reference/expressions.html#notin
def compile_chain(o)
shared = @first.unwrap.second
@first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
"(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
end
def compile_assignment(o)
first, second = @first.compile(o), @second.compile(o)
o[:scope].find(first) if @first.unwrap.is_a?(Value)
sym = @operator[0..1]
return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
"#{first} = #{first} #{sym} #{second}"
end
def compile_existence(o)
first, second = @first.compile(o), @second.compile(o)
"#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
end
def compile_unary(o)
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
parts = [@operator.to_s, space, @first.compile(o)]
@@ -549,8 +619,14 @@ module CoffeeScript
end
# A function definition. The only node that creates a new Scope.
# A CodeNode does not have any children -- they're within the new scope.
class CodeNode < Node
top_sensitive
attr_reader :params, :body, :bound
attr_accessor :name, :proto
# Constructor functions start with an uppercase letter, by convention.
UPPERCASE = /[A-Z]/
def initialize(params, body, tag=nil)
@params = params
@@ -558,49 +634,48 @@ module CoffeeScript
@bound = tag == :boundfunc
end
def statement?
@bound
def constructor?
@name && @name[0..0][UPPERCASE]
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)
top = o.delete(:top)
o[:scope] = shared_scope || Scope.new(o[:scope], @body, self)
o[:return] = true
o[:top] = true
o[:indent] = idt(1)
o[:indent] = idt(@bound ? 2 : 1)
o.delete(:no_wrap)
o.delete(:globals)
name = o.delete(:immediate_assign)
o.delete(:closure)
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 = "\n#{@body.compile_with_declarations(o)}\n"
name_part = name ? " #{name}" : ''
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt}}"
code = @body.empty? ? "" : "\n#{@body.compile_with_declarations(o)}\n"
name_part = @name ? " #{@name}" : ''
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt(@bound ? 1 : 0)}}"
func = "(#{func})" if top && !@bound
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}});")
inner = "(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
write("(function(__this) {\n#{idt(1)}var __func = #{func};\n#{idt(1)}return #{inner}\n#{idt}})(this)")
end
end
# A splat, either as a parameter to a function, an argument to a call,
# or in a destructuring assignment.
class SplatNode < Node
children :name
attr_accessor :index
attr_reader :name
def initialize(name)
@name = name
end
def compile_node(o={})
write(@index ? compile_param(o) : compile_arg(o))
write(@index ? compile_param(o) : @name.compile(o))
end
def compile_param(o)
@@ -608,10 +683,6 @@ module CoffeeScript
"#{@name} = Array.prototype.slice.call(arguments, #{@index})"
end
def compile_arg(o)
@name.compile(o)
end
def compile_value(o, name, index)
"Array.prototype.slice.call(#{name}, #{index})"
end
@@ -620,7 +691,7 @@ module CoffeeScript
# An object literal.
class ObjectNode < Node
attr_reader :properties
children :properties
alias_method :objects, :properties
def initialize(properties = [])
@@ -647,7 +718,7 @@ module CoffeeScript
# An array literal.
class ArrayNode < Node
attr_reader :objects
children :objects
def initialize(objects=[])
@objects = objects
@@ -669,9 +740,11 @@ module CoffeeScript
# code generation to generate a quick "array.push(value)" tree of nodes.
class PushNode
def self.wrap(array, expressions)
expr = expressions.unwrap
return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
Expressions.wrap(CallNode.new(
ValueNode.new(LiteralNode.new(array), [AccessorNode.new('push')]),
[expressions.unwrap]
ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
[expr]
))
end
end
@@ -679,18 +752,14 @@ module CoffeeScript
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
# it, all other loops can be manufactured.
class WhileNode < Node
top_sensitive
children :condition, :body
statement
attr_reader :condition, :body
def initialize(condition, body)
@condition, @body = condition, body
end
def top_sensitive?
true
end
def compile_node(o)
returns = o.delete(:return)
top = o.delete(:top) && !returns
@@ -714,10 +783,11 @@ module CoffeeScript
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
# the current index of the loop as a second parameter.
class ForNode < Node
top_sensitive
children :body, :source, :filter
attr_reader :name, :index, :step
statement
attr_reader :body, :source, :name, :index, :filter, :step
def initialize(body, source, name, index=nil)
@body, @name, @index = body, name, index
@source = source[:source]
@@ -727,10 +797,6 @@ 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?
@@ -771,10 +837,11 @@ module CoffeeScript
if @object
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
body = Expressions.wrap(IfNode.new(
CallNode.new(ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]), [LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]),
Expressions.wrap(body),
nil,
{:statement => true}
CallNode.new(
ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
[LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
),
Expressions.wrap(body), nil, {:statement => true}
))
end
@@ -787,10 +854,10 @@ module CoffeeScript
# A try/catch/finally block.
class TryNode < Node
children :try, :recovery, :finally
attr_reader :error
statement
attr_reader :try, :error, :recovery, :finally
def initialize(try, error, recovery, finally=nil)
@try, @error, @recovery, @finally = try, error, recovery, finally
end
@@ -807,10 +874,9 @@ module CoffeeScript
# Throw an exception.
class ThrowNode < Node
children :expression
statement_only
attr_reader :expression
def initialize(expression)
@expression = expression
end
@@ -822,15 +888,20 @@ module CoffeeScript
# Check an expression for existence (meaning not null or undefined).
class ExistenceNode < Node
attr_reader :expression
children :expression
def self.compile_test(o, variable)
first, second = variable, variable
first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
"(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
end
def initialize(expression)
@expression = expression
end
def compile_node(o)
val = @expression.compile(o)
write("(typeof #{val} !== \"undefined\" && #{val} !== null)")
write(ExistenceNode.compile_test(o, @expression))
end
end
@@ -838,7 +909,7 @@ module CoffeeScript
# You can't wrap parentheses around bits that get compiled into JS statements,
# unfortunately.
class ParentheticalNode < Node
attr_reader :expressions
children :expressions
def initialize(expressions, line=nil)
@expressions = expressions.unwrap
@@ -857,7 +928,7 @@ module CoffeeScript
# Single-expression IfNodes are compiled into ternary operators if possible,
# because ternaries are first-class returnable assignable expressions.
class IfNode < Node
attr_reader :condition, :body, :else_body
children :condition, :body, :else_body
def initialize(condition, body, else_body=nil, tags={})
@condition = condition
@@ -928,7 +999,8 @@ 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 (#{compile_condition(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}"
body = Expressions.wrap(@body).compile(o)
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
return if_part unless @else_body
else_part = chain? ?
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :

View File

@@ -5,12 +5,13 @@ module CoffeeScript
# whether a variable has been seen before or if it needs to be declared.
class Scope
attr_reader :parent, :expressions, :variables, :temp_variable
attr_reader :parent, :expressions, :function, :variables, :temp_variable
# Initialize a scope with its parent, for lookups up the chain,
# as well as the Expressions body where it should declare its variables.
def initialize(parent, expressions)
@parent, @expressions = parent, expressions
# as well as the Expressions body where it should declare its variables,
# and the function that it wraps.
def initialize(parent, expressions, function)
@parent, @expressions, @function = parent, expressions, function
@variables = {}
@temp_variable = @parent ? @parent.temp_variable.dup : '__a'
end
@@ -44,7 +45,7 @@ module CoffeeScript
def free_variable
@temp_variable.succ! while check(@temp_variable)
@variables[@temp_variable.to_sym] = :var
@temp_variable.dup
Value.new(@temp_variable.dup)
end
# Ensure that an assignment is made at the top of scope (or top-level

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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