Compare commits

...

30 Commits
0.5.0 ... 0.5.2

Author SHA1 Message Date
Jeremy Ashkenas
05d95acfc3 docs for CoffeeScript 0.5.2, which is now out. 2010-02-25 00:26:59 -05:00
Jeremy Ashkenas
22674bc536 removing CoffeeScript.activate() simply including the tag will do for text/coffeescript 2010-02-24 23:57:39 -05:00
Jeremy Ashkenas
23c5ebb00f call it 'CoffeeScript' in the command_line, so that --run scripts may access it as such 2010-02-24 22:30:22 -05:00
Jeremy Ashkenas
c14869f008 implementing the inline javascript in the documentation page in text/coffeescript, switching from the closure compiler to the yui compressor for building the browser version -- the closure compiler had a bug for our input -- fixable by hand but not worth the tiny savings 2010-02-24 20:41:56 -05:00
Jeremy Ashkenas
c1427d6558 adding a minified combined coffee-script.js. Include it on the page, after any text/coffeescript tags, and call CoffeeScript.activate(); to run it 2010-02-24 19:57:29 -05:00
Jeremy Ashkenas
2a46e13d33 moving print_tokens (the pretty printer) from coffee_script to command_line 2010-02-24 18:56:32 -05:00
Jeremy Ashkenas
b26e577244 adding documentation for --stdio 2010-02-24 18:27:10 -05:00
Jeremy Ashkenas
9f8710b631 adding compilation over stdin/stdout. Use --stdio or -s, and pipe away. 2010-02-24 18:18:29 -05:00
Jeremy Ashkenas
aba8cb1b08 upgrading the optparse library to avoid having to register callbacks for each argument. It just returns a simple options hash. 2010-02-24 17:57:58 -05:00
Jeremy Ashkenas
92cd80226c bumping to 0.5.1 2010-02-24 00:54:07 -05:00
Jeremy Ashkenas
10d335ccb1 adding existence soaks for indexed-lookup property accesses: obj?['property'] 2010-02-24 00:06:01 -05:00
Jeremy Ashkenas
4eeb8c4bd2 adding conditional while loops with while ... when 2010-02-23 22:53:43 -05:00
Jeremy Ashkenas
4d146bacb1 fixing throwing an error string in the Rewriter, when it should have been an Error object 2010-02-23 21:59:29 -05:00
Jeremy Ashkenas
7de4caffca removing a shift-reduce error from the grammar that worked its way in 2010-02-23 21:22:28 -05:00
Jeremy Ashkenas
8db0cb9fa5 rebuilding with new for spacing 2010-02-23 21:12:44 -05:00
Jeremy Ashkenas
c30b3d3c48 updating to the latest Jison 2010-02-23 21:03:05 -05:00
Jeremy Ashkenas
52db4fbf8c merging Tim-Smart's branch 2010-02-23 20:52:03 -05:00
Jeremy Ashkenas
5cd8f2c52c Merge branch 'master' of git://github.com/Tim-Smart/coffee-script 2010-02-23 20:51:29 -05:00
Jeremy Ashkenas
5a1aa44393 going back to == undefined instead of == null to appease the angry JSLint. 2010-02-22 22:19:17 -05:00
Tim
432696d6eb nodes.coffee: ForNode: Small whitespace change in for_part 2010-02-22 20:19:00 -07:00
Jeremy Ashkenas
3df7bd98f4 fixing issue 196, better handling of soak/existence chains 2010-02-22 22:11:47 -05:00
Jeremy Ashkenas
1f870911c9 Merge branch 'master' of git://github.com/cehoffman/coffee-script 2010-02-22 20:28:41 -05:00
Jeremy Ashkenas
4bb9392753 adding warning about Node.js/Windows to the docs. 2010-02-22 20:25:15 -05:00
Chris Hoffman
fdffacfb40 Make trailing else on switch fix pass on to multiple when switches 2010-02-22 19:17:54 -06:00
Jeremy Ashkenas
a64afe6162 fixing the trailing-else-in-switch-getting-sucked-in-bug, Issue 195. 2010-02-22 19:22:09 -05:00
Jeremy Ashkenas
15b86a5f7a doc updates 2010-02-22 17:14:51 -05:00
Jeremy Ashkenas
9b78fb67cf allowing checked out versions of the source to use bin/cake and bin/coffee from any directory. 2010-02-22 09:12:52 -05:00
Jeremy Ashkenas
4817b96bac fixing the build:ultraviolet task in the Cakefile 2010-02-22 09:09:35 -05:00
Jeremy Ashkenas
6985802eb3 making the docs a little less jumpy with more stable menus 2010-02-21 23:29:31 -05:00
Jeremy Ashkenas
aad0ce162d ensuring that we don't write --eval scriptlets out to disk -- they should print, if nothing else 2010-02-21 22:41:19 -05:00
73 changed files with 821 additions and 576 deletions

View File

@@ -30,7 +30,7 @@ task 'build:parser', 'rebuild the Jison parser (run build first)', ->
task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter', ->
exec('plist2syntax extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage').addCallback ->
exec 'plist2syntax extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage', (err) ->
exec 'sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax'
@@ -38,6 +38,14 @@ task 'build:underscore', 'rebuild the Underscore.coffee documentation page', ->
exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html'
task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
exec 'rake browser'
task 'doc', 'watch and continually rebuild the documentation', ->
exec 'rake doc'
task 'test', 'run the CoffeeScript language test suite', ->
process.mixin require 'assert'
test_count: 0

View File

@@ -1,6 +1,8 @@
require 'erb'
require 'fileutils'
require 'rake/testtask'
require 'rubygems'
require 'yui/compressor'
desc "Build the documentation page"
task :doc do
@@ -18,3 +20,12 @@ task :doc do
sleep 1
end
end
desc "Build the single concatenated and minified script for the browser"
task :browser do
sources = %w(rewriter.js lexer.js parser.js scope.js nodes.js coffee-script.js)
code = sources.map {|s| File.read('lib/' + s) }.join('')
code = YUI::JavaScriptCompressor.new.compress(code)
File.open('extras/coffee-script.js', 'w+') {|f| f.write(code) }
end

View File

@@ -2,6 +2,6 @@
process.mixin(require('sys'));
require.paths.unshift('./lib');
require.paths.unshift(__dirname + '/../lib');
require('cake').run();

View File

@@ -2,6 +2,6 @@
process.mixin(require('sys'));
require.paths.unshift('./lib');
require.paths.unshift(__dirname + '/../lib');
require('command_line').run();

View File

@@ -63,6 +63,11 @@ code, pre, tt, textarea {
padding: 3px 0 3px 12px;
font-size: 12px;
}
pre.no_bar {
border-left: 0;
margin-left: 0;
padding-left: 0;
}
div.code {
position: relative;
border: 1px solid #cacaca;
@@ -130,7 +135,8 @@ div.code {
.navigation.try {
border-left: 0;
}
.navigation:hover {
.navigation:hover,
.navigation.active {
background: #d0d0d0;
background: -webkit-gradient(linear, left top, left bottom, from(#f0f0f0), to(#c0c0c0));
background: -moz-linear-gradient(top, #f0f0f0, #c0c0c0);
@@ -147,7 +153,7 @@ div.code {
-webkit-border-top-right-radius: 0; -moz-border-radius-topright: 0;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
}
.navigation:hover .contents {
.navigation.active .contents {
display: block;
}
.navigation .contents.repl_wrapper {

View File

@@ -1,7 +1,7 @@
<%
require 'uv'
def code_for(file, executable=false)
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\Z|^ )/
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\n*\Z|^ )/
return '' unless File.exists?("documentation/js/#{file}.js")
cs = File.read("documentation/coffee/#{file}.coffee")
js = File.read("documentation/js/#{file}.js").gsub(@stripper, '')
@@ -60,6 +60,7 @@
<a href="#comparisons">Chained Comparisons</a>
<a href="#strings">Multiline Strings and Heredocs</a>
<a href="#cake">Cake, and Cakefiles</a>
<a href="#scripts">"text/coffeescript" Script Tags</a>
<a href="#resources">Resources</a>
<a href="#change_log">Change Log</a>
</div>
@@ -107,7 +108,7 @@ alert reverse '!tpircseeffoC'</textarea>
<p>
<b>Latest Version:</b>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</a>
</p>
<h2>
@@ -138,18 +139,21 @@ alert reverse '!tpircseeffoC'</textarea>
</h2>
<p>
The CoffeeScript compiler is written in pure CoffeeScript, and is available
The CoffeeScript compiler is written in pure CoffeeScript, using a
<a href="http://github.com/jashkenas/coffee-script/blob/master/src/grammar.coffee">small DSL</a>
on top of the <a href="http://github.com/zaach/jison">Jison parser generator</a>, and is available
as a <a href="http://nodejs.org/">Node.js</a> utility. The core compiler however,
does not depend on Node, and can be run in other server-side-JavaScript environments,
or in the browser (see "Try CoffeeScript", above).
or in the browser (see "Try CoffeeScript", above). This may be helpful,
as Node only run on flavors of nix, and not Windows, for the time being.
</p>
<p>
To install, first make sure you have a working version of
To install, first make sure you have a working version of
<a href="http://nodejs.org/">Node.js</a>, 0.1.30 or higher. Then clone the CoffeeScript
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
from GitHub, or download the latest
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>.
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
@@ -211,6 +215,14 @@ sudo bin/cake install</pre>
conjunction with <tt>--watch</tt>)
</td>
</tr>
<tr>
<td><code>-s, --stdio</code></td>
<td>
Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
Good for use with processes written in other languages. An example:<br />
<tt>cat src/cake.coffee | coffee -s</tt>
</td>
</tr>
<tr>
<td><code>-e, --eval</code></td>
<td>
@@ -237,14 +249,14 @@ sudo bin/cake install</pre>
<td>
Instead of compiling the CoffeeScript, just lex and parse it, and print
out the parse tree:
<pre>
Expressions
Assign
Value "square"
Code "x"
Op *
Value "x"
Value "x"</pre>
<pre class="no_bar">
Expressions
Assign
Value "square"
Code "x"
Op *
Value "x"
Value "x"</pre>
</td>
</tr>
</table>
@@ -714,6 +726,34 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('cake_tasks') %>
<h2>
<span id="scripts" class="bookmark"></span>
"text/coffeescript" Script Tags
</h2>
<p>
While it's not recommended for serious use, CoffeeScripts may be included
directly within the browser using <tt>&lt;script type="text/coffeescript"&gt;</tt>
tags. The codebase includes a compressed and minified version of the compiler
(<a href="extras/coffee-script.js">Download current version here, 43k when gzipped</a>).
Include <tt>coffee-script.js</tt> on the page <b>after</b> any <tt>text/coffeescript</tt> tags
with inline CoffeeScript, and it will compile and evaluate them in order.
</p>
<p>
In fact, the little bit of glue script that runs "Try CoffeeScript" above,
as well as jQuery for the menu, is implemented in just this way.
View source and look at the bottom of the page to see the example.
Including the script also gives you access to <tt>CoffeeScript.compile()</tt>
so you can pop open Firebug and try compiling some strings.
</p>
<p>
The usual caveats about CoffeeScript apply &mdash; your inline scripts will
run within a closure wrapper, so if you want to expose global variables or
functions, attach them to the <tt>window</tt> object.
</p>
<h2>
<span id="resources" class="bookmark"></span>
Resources
@@ -742,6 +782,23 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
Change Log
</h2>
<p>
<b class="header" style="margin-top: 20px;">0.5.2</b>
Added a compressed version of the compiler for inclusion in web pages as
<br /><tt>extras/coffee-script.js</tt>. It'll automatically run any script tags
with type <tt>text/coffeescript</tt> for you. Added a <tt>--stdio</tt> option
to the <tt>coffee</tt> command, for piped-in compiles.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.1</b>
Improvements to null soaking with the existential operator, including
soaks on indexed properties. Added conditions to <tt>while</tt> loops,
so you can use them as filters with <tt>when</tt>, in the same manner as
comprehensions.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.0</b>
CoffeeScript 0.5.0 is a major release, While there are no language changes,
@@ -904,32 +961,42 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</div>
<script type="text/javascript" src="lib/rewriter.js"></script>
<script type="text/javascript" src="lib/lexer.js"></script>
<script type="text/javascript" src="lib/parser.js"></script>
<script type="text/javascript" src="lib/scope.js"></script>
<script type="text/javascript" src="lib/nodes.js"></script>
<script type="text/javascript" src="lib/coffee-script.js"></script>
<script type="text/coffeescript">
window.repl_compile: ->
source: $('#repl_source').val()
window.compiled_js: ''
try
window.compiled_js: CoffeeScript.compile source, {no_wrap: true}
catch error then alert error
$('#repl_results').html window.compiled_js
window.repl_run: ->
try
eval window.compiled_js
catch error then alert error
nav: $('.navigation')
current_nav: null
close_menus: ->
current_nav.removeClass 'active' if current_nav
current_nav: null
nav.click (e) ->
return if e.target.tagName.toLowerCase() is 'a'
if this isnt (current_nav and current_nav[0])
close_menus();
current_nav: $(this)
current_nav.addClass 'active'
false
$(document.body).click -> close_menus()
<script type="text/javascript">
window.repl_compile = function() {
var source = document.getElementById('repl_source').value;
window.compiled_js = '';
try {
window.compiled_js = CoffeeScript.compile(source, {no_wrap: true});
} catch(error) {
alert(error);
}
document.getElementById('repl_results').innerHTML = window.compiled_js;
}
window.repl_run = function() {
try {
eval(window.compiled_js);
} catch(error) {
alert(error);
}
}
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script src="extras/coffee-script.js"></script>
</body>
</html>

View File

@@ -11,4 +11,4 @@
}
car.speed < speed_limit ? accelerate() : null;
print("My name is " + this.name);
})();
})();

View File

@@ -5,4 +5,4 @@
return alert(arguments.reverse());
};
backwards("stairway", "to", "heaven");
})();
})();

View File

@@ -23,4 +23,4 @@
}
}
}
})();
})();

View File

@@ -2,4 +2,4 @@
var difficulty, greeting;
greeting = "Hello CoffeeScript";
difficulty = 0.5;
})();
})();

View File

@@ -11,4 +11,4 @@
}
return _a;
});
})();
})();

View File

@@ -2,4 +2,4 @@
var cholesterol, healthy;
cholesterol = 127;
healthy = (200 > cholesterol) && (cholesterol > 60);
})();
})();

View File

@@ -9,4 +9,4 @@
}
date = friday ? sue : jill;
expensive = expensive || do_the_math();
})();
})();

View File

@@ -3,4 +3,4 @@
hi = function() {
return [document.title, "Hello JavaScript"].join(": ");
};
})();
})();

View File

@@ -4,4 +4,4 @@
solipsism = true;
}
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
})();
})();

View File

@@ -10,4 +10,4 @@
}
};
eldest = 24 > 21 ? "Liz" : "Ike";
})();
})();

View File

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

View File

@@ -9,4 +9,4 @@
}}
return _a;
}).call(this).slice(0, 10);
})();
})();

View File

@@ -6,4 +6,4 @@
return "And the error is ... " + error;
}
}).call(this));
})();
})();

View File

@@ -12,4 +12,4 @@
});
})(this));
};
})();
})();

View File

@@ -6,4 +6,4 @@
cube = function cube(x) {
return square(x) * x;
};
})();
})();

View File

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

View File

@@ -8,4 +8,4 @@
city = _a[0];
temp = _a[1];
forecast = _a[2];
})();
})();

View File

@@ -14,4 +14,4 @@
}}
return _a;
}).call(this);
})();
})();

View File

@@ -14,4 +14,4 @@
_c = _b.address;
street = _c[0];
city = _c[1];
})();
})();

View File

@@ -7,4 +7,4 @@
tim: 11
};
matrix = [1, 0, 1, 0, 0, 1, 1, 1, 0];
})();
})();

View File

@@ -40,4 +40,4 @@
}
return _a;
}).call(this);
})();
})();

View File

@@ -5,4 +5,4 @@
_a = [and_switch, bait];
bait = _a[0];
and_switch = _a[1];
})();
})();

View File

@@ -2,7 +2,7 @@
var _a, _b, _c, _d, _e, countdown, egg_delivery, num;
countdown = (function() {
_a = []; _d = 10; _e = 1;
for (_c=0, num=_d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
for (_c = 0, num=_d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
_a.push(num);
}
return _a;
@@ -10,7 +10,7 @@
egg_delivery = function egg_delivery() {
var _f, _g, _h, _i, _j, dozen_eggs, i;
_f = []; _i = 0; _j = eggs.length;
for (_h=0, i=_i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
for (_h = 0, i=_i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
_f.push((function() {
dozen_eggs = eggs.slice(i, i + 12);
return deliver(new egg_carton(dozen));
@@ -18,4 +18,4 @@
}
return _f;
};
})();
})();

View File

@@ -7,4 +7,4 @@
return num = 10;
};
new_num = change_numbers();
})();
})();

View File

@@ -3,4 +3,4 @@
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
three_to_six = numbers.slice(3, 6 + 1);
numbers_copy = numbers.slice(0, numbers.length);
})();
})();

View File

@@ -1,4 +1,4 @@
(function(){
var _a;
(_a = lottery.draw_winner()) == null ? undefined : _a.address == null ? undefined : _a.address.zipcode;
})();
(_a = lottery.draw_winner()) == undefined ? undefined : _a.address == undefined ? undefined : _a.address.zipcode;
})();

View File

@@ -13,4 +13,4 @@
alert("Gold: " + gold);
alert("Silver: " + silver);
alert("The Field: " + the_field);
})();
})();

View File

@@ -2,4 +2,4 @@
var numbers;
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6]));
})();
})();

View File

@@ -6,4 +6,4 @@ or no money in my purse, and nothing particular \
to interest me on shore, I thought I would sail \
about a little and see the watery part of the \
world...";
})();
})();

View File

@@ -34,4 +34,4 @@
tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
})();
})();

View File

@@ -15,4 +15,4 @@
} else {
go_to_work();
}
})();
})();

View File

@@ -7,4 +7,4 @@
} finally {
clean_up();
}
})();
})();

View File

@@ -19,4 +19,4 @@ One fell out and bumped his head.");
}
return _a;
}).call(this);
})();
})();

View File

@@ -8,7 +8,7 @@ get '/hello', ->
# Append.
append: (location, data) ->
path: new Pathname location
throw "Location does not exist" unless path.exists()
throw new Error("Location does not exist") unless path.exists()
File.open path, 'a', (file) ->
file.puts YAML.dump data

View File

@@ -1,4 +1,12 @@
This folder includes rough cuts of CoffeeScript syntax highlighters for
EXTRAS:
"extras/coffee-script.js" is a concatenated and compressed version of the
CoffeeScript compiler. To use it in the browser, include the script after any
inline script tags of type "text/coffeescript" on the page. It will compile
and evaluate all of the scripts in order.
This folder also includes rough cuts of CoffeeScript syntax highlighters for
TextMate and Vim. Improvements to their lexing ability are always welcome.
To install the TextMate bundle, drop it into:

1
extras/coffee-script.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -46,6 +46,7 @@
<a href="#comparisons">Chained Comparisons</a>
<a href="#strings">Multiline Strings and Heredocs</a>
<a href="#cake">Cake, and Cakefiles</a>
<a href="#scripts">"text/coffeescript" Script Tags</a>
<a href="#resources">Resources</a>
<a href="#change_log">Change Log</a>
</div>
@@ -93,7 +94,7 @@ alert reverse '!tpircseeffoC'</textarea>
<p>
<b>Latest Version:</b>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</a>
</p>
<h2>
@@ -235,18 +236,21 @@ cubed_list = (function() {
</h2>
<p>
The CoffeeScript compiler is written in pure CoffeeScript, and is available
The CoffeeScript compiler is written in pure CoffeeScript, using a
<a href="http://github.com/jashkenas/coffee-script/blob/master/src/grammar.coffee">small DSL</a>
on top of the <a href="http://github.com/zaach/jison">Jison parser generator</a>, and is available
as a <a href="http://nodejs.org/">Node.js</a> utility. The core compiler however,
does not depend on Node, and can be run in other server-side-JavaScript environments,
or in the browser (see "Try CoffeeScript", above).
or in the browser (see "Try CoffeeScript", above). This may be helpful,
as Node only run on flavors of nix, and not Windows, for the time being.
</p>
<p>
To install, first make sure you have a working version of
To install, first make sure you have a working version of
<a href="http://nodejs.org/">Node.js</a>, 0.1.30 or higher. Then clone the CoffeeScript
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
from GitHub, or download the latest
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>.
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
@@ -308,6 +312,14 @@ sudo bin/cake install</pre>
conjunction with <tt>--watch</tt>)
</td>
</tr>
<tr>
<td><code>-s, --stdio</code></td>
<td>
Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
Good for use with processes written in other languages. An example:<br />
<tt>cat src/cake.coffee | coffee -s</tt>
</td>
</tr>
<tr>
<td><code>-e, --eval</code></td>
<td>
@@ -334,14 +346,14 @@ sudo bin/cake install</pre>
<td>
Instead of compiling the CoffeeScript, just lex and parse it, and print
out the parse tree:
<pre>
Expressions
Assign
Value "square"
Code "x"
Op *
Value "x"
Value "x"</pre>
<pre class="no_bar">
Expressions
Assign
Value "square"
Code "x"
Op *
Value "x"
Value "x"</pre>
</td>
</tr>
</table>
@@ -851,7 +863,7 @@ _d <span class="Keyword">=</span> asteroids;
</pre><pre class="idle"><span class="Storage">var</span> _a, _b, _c, _d, _e, countdown, egg_delivery, num;
countdown <span class="Keyword">=</span> (<span class="Storage">function</span>() {
_a <span class="Keyword">=</span> []; _d <span class="Keyword">=</span> <span class="Number">10</span>; _e <span class="Keyword">=</span> <span class="Number">1</span>;
<span class="Keyword">for</span> (_c<span class="Keyword">=</span><span class="Number">0</span>, num<span class="Keyword">=</span>_d; (_d <span class="Keyword">&lt;=</span> _e ? num <span class="Keyword">&lt;=</span> _e : num <span class="Keyword">&gt;=</span> _e); (_d <span class="Keyword">&lt;=</span> _e ? num <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">1</span> : num <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">1</span>), _c<span class="Keyword">++</span>) {
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>, num<span class="Keyword">=</span>_d; (_d <span class="Keyword">&lt;=</span> _e ? num <span class="Keyword">&lt;=</span> _e : num <span class="Keyword">&gt;=</span> _e); (_d <span class="Keyword">&lt;=</span> _e ? num <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">1</span> : num <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">1</span>), _c<span class="Keyword">++</span>) {
_a.<span class="LibraryFunction">push</span>(num);
}
<span class="Keyword">return</span> _a;
@@ -859,7 +871,7 @@ countdown <span class="Keyword">=</span> (<span class="Storage">function</span>(
egg_delivery <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">egg_delivery</span>() {
<span class="Storage">var</span> _f, _g, _h, _i, _j, dozen_eggs, i;
_f <span class="Keyword">=</span> []; _i <span class="Keyword">=</span> <span class="Number">0</span>; _j <span class="Keyword">=</span> eggs.<span class="LibraryConstant">length</span>;
<span class="Keyword">for</span> (_h<span class="Keyword">=</span><span class="Number">0</span>, i<span class="Keyword">=</span>_i; (_i <span class="Keyword">&lt;=</span> _j ? i <span class="Keyword">&lt;</span> _j : i <span class="Keyword">&gt;</span> _j); (_i <span class="Keyword">&lt;=</span> _j ? i <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">12</span> : i <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">12</span>), _h<span class="Keyword">++</span>) {
<span class="Keyword">for</span> (_h <span class="Keyword">=</span> <span class="Number">0</span>, i<span class="Keyword">=</span>_i; (_i <span class="Keyword">&lt;=</span> _j ? i <span class="Keyword">&lt;</span> _j : i <span class="Keyword">&gt;</span> _j); (_i <span class="Keyword">&lt;=</span> _j ? i <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">12</span> : i <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">12</span>), _h<span class="Keyword">++</span>) {
_f.<span class="LibraryFunction">push</span>((<span class="Storage">function</span>() {
dozen_eggs <span class="Keyword">=</span> eggs.<span class="LibraryFunction">slice</span>(i, i <span class="Keyword">+</span> <span class="Number">12</span>);
<span class="Keyword">return</span> deliver(<span class="Keyword">new</span> <span class="TypeName">egg_carton</span>(dozen));
@@ -870,7 +882,7 @@ egg_delivery <span class="Keyword">=</span> <span class="Storage">function</span
</pre><button onclick='javascript: var _a, _b, _c, _d, _e, countdown, egg_delivery, num;
countdown = (function() {
_a = []; _d = 10; _e = 1;
for (_c=0, num=_d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
for (_c = 0, num=_d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
_a.push(num);
}
return _a;
@@ -878,7 +890,7 @@ countdown = (function() {
egg_delivery = function egg_delivery() {
var _f, _g, _h, _i, _j, dozen_eggs, i;
_f = []; _i = 0; _j = eggs.length;
for (_h=0, i=_i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
for (_h = 0, i=_i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
_f.push((function() {
dozen_eggs = eggs.slice(i, i + 12);
return deliver(new egg_carton(dozen));
@@ -1131,7 +1143,7 @@ speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
</p>
<div class='code'><pre class="idle">lottery.draw_winner()<span class="Keyword">?</span>.address<span class="Keyword">?</span>.zipcode
</pre><pre class="idle"><span class="Storage">var</span> _a;
(_a <span class="Keyword">=</span> lottery.draw_winner()) <span class="Keyword">==</span> <span class="BuiltInConstant">null</span> ? undefined : _a.address <span class="Keyword">==</span> <span class="BuiltInConstant">null</span> ? undefined : _a.address.zipcode;
(_a <span class="Keyword">=</span> lottery.draw_winner()) <span class="Keyword">==</span> undefined ? undefined : _a.address <span class="Keyword">==</span> undefined ? undefined : _a.address.zipcode;
</pre><br class='clear' /></div>
<p>
Soaking up nulls is similar to Ruby's
@@ -1594,6 +1606,34 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
});
</pre><br class='clear' /></div>
<h2>
<span id="scripts" class="bookmark"></span>
"text/coffeescript" Script Tags
</h2>
<p>
While it's not recommended for serious use, CoffeeScripts may be included
directly within the browser using <tt>&lt;script type="text/coffeescript"&gt;</tt>
tags. The codebase includes a compressed and minified version of the compiler
(<a href="extras/coffee-script.js">Download current version here, 43k when gzipped</a>).
Include <tt>coffee-script.js</tt> on the page <b>after</b> any <tt>text/coffeescript</tt> tags
with inline CoffeeScript, and it will compile and evaluate them in order.
</p>
<p>
In fact, the little bit of glue script that runs "Try CoffeeScript" above,
as well as jQuery for the menu, is implemented in just this way.
View source and look at the bottom of the page to see the example.
Including the script also gives you access to <tt>CoffeeScript.compile()</tt>
so you can pop open Firebug and try compiling some strings.
</p>
<p>
The usual caveats about CoffeeScript apply &mdash; your inline scripts will
run within a closure wrapper, so if you want to expose global variables or
functions, attach them to the <tt>window</tt> object.
</p>
<h2>
<span id="resources" class="bookmark"></span>
Resources
@@ -1622,6 +1662,23 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
Change Log
</h2>
<p>
<b class="header" style="margin-top: 20px;">0.5.2</b>
Added a compressed version of the compiler for inclusion in web pages as
<br /><tt>extras/coffee-script.js</tt>. It'll automatically run any script tags
with type <tt>text/coffeescript</tt> for you. Added a <tt>--stdio</tt> option
to the <tt>coffee</tt> command, for piped-in compiles.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.1</b>
Improvements to null soaking with the existential operator, including
soaks on indexed properties. Added conditions to <tt>while</tt> loops,
so you can use them as filters with <tt>when</tt>, in the same manner as
comprehensions.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.0</b>
CoffeeScript 0.5.0 is a major release, While there are no language changes,
@@ -1784,32 +1841,42 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
</div>
<script type="text/javascript" src="lib/rewriter.js"></script>
<script type="text/javascript" src="lib/lexer.js"></script>
<script type="text/javascript" src="lib/parser.js"></script>
<script type="text/javascript" src="lib/scope.js"></script>
<script type="text/javascript" src="lib/nodes.js"></script>
<script type="text/javascript" src="lib/coffee-script.js"></script>
<script type="text/coffeescript">
window.repl_compile: ->
source: $('#repl_source').val()
window.compiled_js: ''
try
window.compiled_js: CoffeeScript.compile source, {no_wrap: true}
catch error then alert error
$('#repl_results').html window.compiled_js
window.repl_run: ->
try
eval window.compiled_js
catch error then alert error
nav: $('.navigation')
current_nav: null
close_menus: ->
current_nav.removeClass 'active' if current_nav
current_nav: null
nav.click (e) ->
return if e.target.tagName.toLowerCase() is 'a'
if this isnt (current_nav and current_nav[0])
close_menus();
current_nav: $(this)
current_nav.addClass 'active'
false
$(document.body).click -> close_menus()
<script type="text/javascript">
window.repl_compile = function() {
var source = document.getElementById('repl_source').value;
window.compiled_js = '';
try {
window.compiled_js = CoffeeScript.compile(source, {no_wrap: true});
} catch(error) {
alert(error);
}
document.getElementById('repl_results').innerHTML = window.compiled_js;
}
window.repl_run = function() {
try {
eval(window.compiled_js);
} catch(error) {
alert(error);
}
}
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script src="extras/coffee-script.js"></script>
</body>
</html>

View File

@@ -38,7 +38,7 @@
spaces = 20 - name.length;
spaces = spaces > 0 ? (function() {
_c = []; _f = 0; _g = spaces;
for (_e=0, i=_f; (_f <= _g ? i <= _g : i >= _g); (_f <= _g ? i += 1 : i -= 1), _e++) {
for (_e = 0, i=_f; (_f <= _g ? i <= _g : i >= _g); (_f <= _g ? i += 1 : i -= 1), _e++) {
_c.push(' ');
}
return _c;
@@ -77,4 +77,4 @@
});
});
};
})();
})();

View File

@@ -1,5 +1,5 @@
(function(){
var lexer, parser, path;
var _a, _b, lexer, parser, path, tag;
// Set up for both the browser and the server.
if ((typeof process !== "undefined" && process !== null)) {
process.mixin(require('nodes'));
@@ -32,7 +32,7 @@
return this.pos;
}
};
exports.VERSION = '0.5.0';
exports.VERSION = '0.5.2';
// Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile = function compile(code, options) {
return (parser.parse(lexer.tokenize(code))).compile(options);
@@ -45,17 +45,13 @@
exports.tree = function tree(code) {
return parser.parse(lexer.tokenize(code));
};
// Pretty-print a token stream.
exports.print_tokens = function print_tokens(tokens) {
var _a, _b, _c, strings, token;
strings = (function() {
_a = []; _b = tokens;
for (_c = 0; _c < _b.length; _c++) {
token = _b[_c];
_a.push('[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']');
}
return _a;
}).call(this);
return puts(strings.join(' '));
};
})();
// Activate CoffeeScript in the browser by having it compile and eval
// all script tags with a content-type of text/coffeescript.
if ((typeof document !== "undefined" && document !== null) && document.getElementsByTagName) {
_a = document.getElementsByTagName('script');
for (_b = 0; _b < _a.length; _b++) {
tag = _a[_b];
tag.type === 'text/coffeescript' ? eval(exports.compile(tag.innerHTML)) : null;
}
}
})();

View File

@@ -1,11 +1,11 @@
(function(){
var BANNER, SWITCHES, coffee, compile_script, compile_scripts, fs, lint, option_parser, options, optparse, parse_options, path, sources, usage, version, watch_scripts, write_js;
var BANNER, CoffeeScript, SWITCHES, compile_options, compile_script, compile_scripts, compile_stdio, fs, lint, option_parser, options, optparse, parse_options, path, print_tokens, sources, usage, version, watch_scripts, write_js;
fs = require('fs');
path = require('path');
coffee = require('coffee-script');
optparse = require('optparse');
CoffeeScript = require('coffee-script');
BANNER = "coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee";
SWITCHES = [['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-r', '--run', 'compile and run a CoffeeScript'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-e', '--eval', 'compile a string from the command line'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-tr', '--tree', 'print the parse tree that Jison produces'], ['-n', '--no-wrap', 'compile without the top-level function wrapper'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
SWITCHES = [['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-r', '--run', 'compile and run a CoffeeScript'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-e', '--eval', 'compile a string from the command line'], ['-n', '--no-wrap', 'compile without the top-level function wrapper'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-tr', '--tree', 'print the parse tree that Jison produces'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
options = {};
sources = [];
option_parser = null;
@@ -13,14 +13,23 @@
exports.run = function run() {
var flags, separator;
parse_options();
if (options.help) {
return usage();
}
if (options.version) {
return version();
}
if (options.interactive) {
return require('repl');
}
if (options.stdio) {
return compile_stdio();
}
if (options.eval) {
return compile_script('terminal', sources[0]);
return compile_script('unknown', sources[0]);
}
if (!(sources.length)) {
usage();
return usage();
}
separator = sources.indexOf('--');
flags = [];
@@ -42,7 +51,7 @@
};
// The "--version" message.
version = function version() {
puts("CoffeeScript version " + coffee.VERSION);
puts("CoffeeScript version " + CoffeeScript.VERSION);
return process.exit(0);
};
// Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
@@ -64,36 +73,47 @@
// Compile a single source script, containing the given code, according to the
// requested options. Both compile_scripts and watch_scripts share this method.
compile_script = function compile_script(source, code) {
var js, o, opts;
opts = options;
o = opts.no_wrap ? {
no_wrap: true
} : {};
var js, o;
o = options;
try {
if (opts.tokens) {
return coffee.print_tokens(coffee.tokenize(code));
} else if (opts.tree) {
return puts(coffee.tree(code).toString());
if (o.tokens) {
return print_tokens(CoffeeScript.tokenize(code));
} else if (o.tree) {
return puts(CoffeeScript.tree(code).toString());
} else {
js = coffee.compile(code, o);
if (opts.run) {
js = CoffeeScript.compile(code, compile_options());
if (o.run) {
return eval(js);
} else if (opts.print) {
return puts(js);
} else if (opts.lint) {
} else if (o.lint) {
return lint(js);
} else if (o.print || o.eval) {
return print(js);
} else {
return write_js(source, js);
}
}
} catch (err) {
if (opts.watch) {
if (o.watch) {
return puts(err.message);
} else {
throw err;
}
}
};
// Listen for and compile scripts over stdio.
compile_stdio = function compile_stdio() {
var code;
code = '';
process.stdio.open();
process.stdio.addListener('data', function(string) {
if (string) {
return code += string;
}
});
return process.stdio.addListener('close', function() {
return process.stdio.write(CoffeeScript.compile(code, compile_options()));
});
};
// Watch a list of source CoffeeScript files, recompiling them every time the
// files are updated.
watch_scripts = function watch_scripts() {
@@ -143,59 +163,29 @@
jsl.write(js);
return jsl.close();
};
// Pretty-print a token stream.
print_tokens = function print_tokens(tokens) {
var _a, _b, _c, strings, token;
strings = (function() {
_a = []; _b = tokens;
for (_c = 0; _c < _b.length; _c++) {
token = _b[_c];
_a.push('[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']');
}
return _a;
}).call(this);
return puts(strings.join(' '));
};
// Use OptionParser for all the options.
parse_options = function parse_options() {
var oparser, opts, paths;
opts = (options = {});
oparser = (option_parser = new optparse.OptionParser(SWITCHES));
oparser.banner = BANNER;
oparser.add('interactive', function() {
return opts.interactive = true;
});
oparser.add('run', function() {
return opts.run = true;
});
oparser.add('output', function(dir) {
return opts.output = dir;
});
oparser.add('watch', function() {
return opts.watch = true;
});
oparser.add('print', function() {
return opts.print = true;
});
oparser.add('lint', function() {
return opts.lint = true;
});
oparser.add('eval', function() {
return opts.eval = true;
});
oparser.add('tokens', function() {
return opts.tokens = true;
});
oparser.add('tree', function() {
return opts.tree = true;
});
oparser.add('no-wrap', function() {
return opts.no_wrap = true;
});
oparser.add('help', (function(__this) {
var __func = function() {
return usage();
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
oparser.add('version', (function(__this) {
var __func = function() {
return version();
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
paths = oparser.parse(process.ARGV);
return sources = paths.slice(2, paths.length);
option_parser = new optparse.OptionParser(SWITCHES, BANNER);
options = option_parser.parse(process.ARGV);
return sources = options.arguments.slice(2, options.arguments.length);
};
})();
// The options to pass to the CoffeeScript compiler.
compile_options = function compile_options() {
return options['no-wrap'] ? {
no_wrap: true
} : {};
};
})();

View File

@@ -285,6 +285,8 @@
// Indexing into an object or array.
Index: [o("INDEX_START Expression INDEX_END", function() {
return new IndexNode($2);
}), o("SOAKED_INDEX_START Expression SOAKED_INDEX_END", function() {
return new IndexNode($2, 'soak');
})
],
// An object literal.
@@ -343,8 +345,6 @@
return new ValueNode(new LiteralNode('this'));
}), o("@ Identifier", function() {
return new ValueNode(new LiteralNode('this'), [new AccessorNode($2)]);
}), o("@ Index", function() {
return new ValueNode(new LiteralNode('this'), [$2]);
})
],
// The range literal.
@@ -416,13 +416,20 @@
return new ParentheticalNode($2);
})
],
// The condition for a while loop.
WhileSource: [o("WHILE Expression", function() {
return new WhileNode($2);
}), o("WHILE Expression WHEN Expression", function() {
return new WhileNode($2, {
filter: $4
});
})
],
// The while loop. (there is no do..while).
While: [o("WHILE Expression Block", function() {
return new WhileNode($2, $3);
}), o("WHILE Expression", function() {
return new WhileNode($2, null);
}), o("Expression WHILE Expression", function() {
return new WhileNode($3, Expressions.wrap([$1]));
While: [o("WhileSource Block", function() {
return $1.add_body($2);
}), o("Expression WhileSource", function() {
return $2.add_body($1);
})
],
// Array comprehensions, including guard and current index.
@@ -462,7 +469,7 @@
Switch: [o("SWITCH Expression INDENT Whens OUTDENT", function() {
return $4.rewrite_condition($2);
}), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() {
return $4.rewrite_condition($2).add_else($6);
return $4.rewrite_condition($2).add_else($6, true);
})
],
// The inner list of whens.
@@ -554,4 +561,4 @@
}, {
debug: false
});
})();
})();

View File

@@ -285,24 +285,30 @@
// Multi-character operators are also literal tokens, so that Racc can assign
// the proper order of operations.
lex.prototype.literal_token = function literal_token() {
var match, tag, value;
var match, not_spaced, tag, value;
match = this.chunk.match(OPERATOR);
value = match && match[1];
if (value && value.match(CODE)) {
this.tag_parameters();
}
value = value || this.chunk.substr(0, 1);
not_spaced = !this.prev() || !this.prev().spaced;
tag = value;
if (value.match(ASSIGNMENT)) {
tag = 'ASSIGN';
if (JS_FORBIDDEN.indexOf(this.value()) >= 0) {
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
}
}
if (value === ';') {
} else if (value === ';') {
tag = 'TERMINATOR';
}
if (CALLABLE.indexOf(this.tag()) >= 0 && (!this.prev() || !this.prev().spaced)) {
} else if (value === '[' && this.tag() === '?' && not_spaced) {
tag = 'SOAKED_INDEX_START';
this.soaked_index = true;
this.tokens.pop();
} else if (value === ']' && this.soaked_index) {
tag = 'SOAKED_INDEX_END';
this.soaked_index = false;
} else if (CALLABLE.indexOf(this.tag()) >= 0 && not_spaced) {
if (value === '(') {
tag = 'CALL_START';
}
@@ -396,4 +402,4 @@
lex.prototype.close_indentation = function close_indentation() {
return this.outdent_token(this.indent);
};
})();
})();

View File

@@ -41,4 +41,4 @@
return factories[topId] = factories[topId] || this.reload(topId, path);
};
require.loader.loaders.unshift([".coffee", loader]);
})();
})();

View File

@@ -1,5 +1,5 @@
(function(){
var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, Node, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, inherit, merge, statement;
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, inherit, merge, statement;
var __hasProp = Object.prototype.hasOwnProperty;
(typeof process !== "undefined" && process !== null) ? process.mixin(require('scope')) : (this.exports = this);
// Some helper functions
@@ -90,12 +90,12 @@
// generated code should be wrapped up in a closure. An options hash is passed
// and cloned throughout, containing messages from higher in the AST,
// information about the current scope, and indentation level.
Node = (exports.Node = function Node() { });
BaseNode = (exports.BaseNode = function BaseNode() { });
// This is extremely important -- we convert JS statements into expressions
// by wrapping them in a closure, only if it's possible, and we're not at
// the top level of a block (which would be unnecessary), and we haven't
// already been asked to return the result.
Node.prototype.compile = function compile(o) {
BaseNode.prototype.compile = function compile(o) {
var closure, top;
this.options = merge(o || {});
this.indent = o.indent;
@@ -110,23 +110,31 @@
};
// Statements converted into expressions share scope with their parent
// closure, to preserve JavaScript-style lexical scope.
Node.prototype.compile_closure = function compile_closure(o) {
BaseNode.prototype.compile_closure = function compile_closure(o) {
this.indent = o.indent;
o.shared_scope = o.scope;
return ClosureNode.wrap(this).compile(o);
};
// If the code generation wishes to use the result of a complex expression
// in multiple places, ensure that the expression is only ever evaluated once.
BaseNode.prototype.compile_reference = function compile_reference(o) {
var compiled, reference;
reference = new LiteralNode(o.scope.free_variable());
compiled = new AssignNode(reference, this);
return [compiled, reference];
};
// Quick short method for the current indentation level, plus tabbing in.
Node.prototype.idt = function idt(tabs) {
BaseNode.prototype.idt = function idt(tabs) {
var _a, _b, _c, _d, i, idt;
idt = (this.indent || '');
_c = 0; _d = (tabs || 0);
for (_b=0, i=_c; (_c <= _d ? i < _d : i > _d); (_c <= _d ? i += 1 : i -= 1), _b++) {
for (_b = 0, i=_c; (_c <= _d ? i < _d : i > _d); (_c <= _d ? i += 1 : i -= 1), _b++) {
idt += TAB;
}
return idt;
};
// Does this node, or any of its children, contain a node of a certain kind?
Node.prototype.contains = function contains(block) {
BaseNode.prototype.contains = function contains(block) {
var _a, _b, node;
_a = this.children;
for (_b = 0; _b < _a.length; _b++) {
@@ -134,14 +142,14 @@
if (block(node)) {
return true;
}
if (node instanceof Node && node.contains(block)) {
if (node instanceof BaseNode && node.contains(block)) {
return true;
}
}
return false;
};
// toString representation of the node, for inspecting the parse tree.
Node.prototype.toString = function toString(idt) {
BaseNode.prototype.toString = function toString(idt) {
var _a, _b, _c, child;
idt = idt || '';
return '\n' + idt + this.type + (function() {
@@ -154,24 +162,24 @@
}).call(this).join('');
};
// Default implementations of the common node methods.
Node.prototype.unwrap = function unwrap() {
BaseNode.prototype.unwrap = function unwrap() {
return this;
};
Node.prototype.children = [];
Node.prototype.is_statement = function is_statement() {
BaseNode.prototype.children = [];
BaseNode.prototype.is_statement = function is_statement() {
return false;
};
Node.prototype.is_statement_only = function is_statement_only() {
BaseNode.prototype.is_statement_only = function is_statement_only() {
return false;
};
Node.prototype.top_sensitive = function top_sensitive() {
BaseNode.prototype.top_sensitive = function top_sensitive() {
return false;
};
Node.prototype.operation_sensitive = function operation_sensitive() {
BaseNode.prototype.operation_sensitive = function operation_sensitive() {
return false;
};
// A collection of nodes, each one representing an expression.
Expressions = (exports.Expressions = inherit(Node, {
Expressions = (exports.Expressions = inherit(BaseNode, {
type: 'Expressions',
constructor: function constructor(nodes) {
this.children = (this.expressions = compact(flatten(nodes || [])));
@@ -204,7 +212,7 @@
},
compile: function compile(o) {
o = o || {};
return o.scope ? Node.prototype.compile.call(this, o) : this.compile_root(o);
return o.scope ? BaseNode.prototype.compile.call(this, o) : this.compile_root(o);
},
// Compile each expression in the Expressions body.
compile_node: function compile_node(o) {
@@ -225,7 +233,7 @@
o.scope = new Scope(null, this, null);
code = o.globals ? this.compile_node(o) : this.compile_with_declarations(o);
code = code.replace(TRAILING_WHITESPACE, '');
return o.no_wrap ? code : "(function(){\n" + code + "\n})();";
return o.no_wrap ? code : "(function(){\n" + code + "\n})();\n";
},
// Compile the expressions body, with declarations of all inner variables
// pushed up to the top.
@@ -279,7 +287,7 @@
statement(Expressions);
// Literals are static values that can be passed through directly into
// JavaScript without translation, eg.: strings, numbers, true, false, null...
LiteralNode = (exports.LiteralNode = inherit(Node, {
LiteralNode = (exports.LiteralNode = inherit(BaseNode, {
type: 'Literal',
constructor: function constructor(value) {
this.value = value;
@@ -302,7 +310,7 @@
}));
LiteralNode.prototype.is_statement_only = LiteralNode.prototype.is_statement;
// Return an expression, or wrap it in a closure and return it.
ReturnNode = (exports.ReturnNode = inherit(Node, {
ReturnNode = (exports.ReturnNode = inherit(BaseNode, {
type: 'Return',
constructor: function constructor(expression) {
this.children = [(this.expression = expression)];
@@ -319,9 +327,9 @@
}));
statement(ReturnNode, true);
// A value, indexed or dotted into, or vanilla.
ValueNode = (exports.ValueNode = inherit(Node, {
ValueNode = (exports.ValueNode = inherit(BaseNode, {
type: 'Value',
SOAK: " == null ? undefined : ",
SOAK: " == undefined ? undefined : ",
constructor: function constructor(base, properties) {
this.children = flatten([(this.base = base), (this.properties = (properties || []))]);
return this;
@@ -357,7 +365,7 @@
return this.base.is_statement && this.base.is_statement() && !this.has_properties();
},
compile_node: function compile_node(o) {
var _a, _b, baseline, code, only, op, part, parts, prop, props, soaked, temp;
var _a, _b, baseline, complete, only, op, part, prop, props, soaked, temp;
soaked = false;
only = del(o, 'only_first');
op = del(o, 'operation');
@@ -366,33 +374,32 @@
if (this.base instanceof ObjectNode && this.has_properties()) {
baseline = '(' + baseline + ')';
}
parts = [baseline];
complete = (this.last = baseline);
_a = props;
for (_b = 0; _b < _a.length; _b++) {
prop = _a[_b];
if (prop instanceof AccessorNode && prop.soak) {
this.source = baseline;
if (prop.soak_node) {
soaked = true;
if (this.base instanceof CallNode && prop === props[0]) {
temp = o.scope.free_variable();
parts[parts.length - 1] = '(' + temp + ' = ' + baseline + ')' + this.SOAK + ((baseline = temp + prop.compile(o)));
complete = '(' + temp + ' = ' + complete + ')' + this.SOAK + ((baseline = temp + prop.compile(o)));
} else {
parts[parts.length - 1] += (this.SOAK + (baseline += prop.compile(o)));
complete = complete + this.SOAK + (baseline += prop.compile(o));
}
} else {
part = prop.compile(o);
baseline += part;
parts.push(part);
complete += part;
this.last = part;
}
}
this.last = parts[parts.length - 1];
this.source = parts.length > 1 ? parts.slice(0, (parts.length - 1)).join('') : null;
code = parts.join('').replace(/\)\(\)\)/, '()))');
return op && soaked ? '(' + code + ')' : code;
return op && soaked ? '(' + complete + ')' : complete;
}
}));
// Pass through CoffeeScript comments into JavaScript comments at the
// same position.
CommentNode = (exports.CommentNode = inherit(Node, {
CommentNode = (exports.CommentNode = inherit(BaseNode, {
type: 'Comment',
constructor: function constructor(lines) {
this.lines = lines;
@@ -405,7 +412,7 @@
statement(CommentNode);
// Node for a function invocation. Takes care of converting super() calls into
// calls against the prototype's function of the same name.
CallNode = (exports.CallNode = inherit(Node, {
CallNode = (exports.CallNode = inherit(BaseNode, {
type: 'Call',
constructor: function constructor(variable, args) {
this.children = flatten([(this.variable = variable), (this.args = (args || []))]);
@@ -466,19 +473,11 @@
return _a;
}).call(this);
return this.prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')';
},
// 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.
compile_reference: function compile_reference(o) {
var call, reference;
reference = new LiteralNode(o.scope.free_variable());
call = new ParentheticalNode(new AssignNode(reference, this));
return [call, reference];
}
}));
// Node to extend an object's prototype with an ancestor object.
// After goog.inherits from the Closure Library.
ExtendsNode = (exports.ExtendsNode = inherit(Node, {
ExtendsNode = (exports.ExtendsNode = inherit(BaseNode, {
type: 'Extends',
constructor: function constructor(child, parent) {
this.children = [(this.child = child), (this.parent = parent)];
@@ -507,12 +506,12 @@
statement(ExtendsNode);
// A dotted accessor into a part of a value, or the :: shorthand for
// an accessor into the object's prototype.
AccessorNode = (exports.AccessorNode = inherit(Node, {
AccessorNode = (exports.AccessorNode = inherit(BaseNode, {
type: 'Accessor',
constructor: function constructor(name, tag) {
this.children = [(this.name = name)];
this.prototype = tag === 'prototype';
this.soak = tag === 'soak';
this.soak_node = tag === 'soak';
return this;
},
compile_node: function compile_node(o) {
@@ -520,10 +519,11 @@
}
}));
// An indexed accessor into a part of an array or object.
IndexNode = (exports.IndexNode = inherit(Node, {
IndexNode = (exports.IndexNode = inherit(BaseNode, {
type: 'Index',
constructor: function constructor(index) {
constructor: function constructor(index, tag) {
this.children = [(this.index = index)];
this.soak_node = tag === 'soak';
return this;
},
compile_node: function compile_node(o) {
@@ -532,7 +532,7 @@
}));
// A range literal. Ranges can be used to extract portions (slices) of arrays,
// or to specify a range for list comprehensions.
RangeNode = (exports.RangeNode = inherit(Node, {
RangeNode = (exports.RangeNode = inherit(BaseNode, {
type: 'Range',
constructor: function constructor(from, to, exclusive) {
this.children = [(this.from = from), (this.to = to)];
@@ -577,7 +577,7 @@
// An array slice literal. Unlike JavaScript's Array#slice, the second parameter
// specifies the index of the end of the slice (just like the first parameter)
// is the index of the beginning.
SliceNode = (exports.SliceNode = inherit(Node, {
SliceNode = (exports.SliceNode = inherit(BaseNode, {
type: 'Slice',
constructor: function constructor(range) {
this.children = [(this.range = range)];
@@ -592,7 +592,7 @@
}
}));
// An object literal.
ObjectNode = (exports.ObjectNode = inherit(Node, {
ObjectNode = (exports.ObjectNode = inherit(BaseNode, {
type: 'Object',
constructor: function constructor(props) {
this.children = (this.objects = (this.properties = props || []));
@@ -639,7 +639,7 @@
}
}));
// An array literal.
ArrayNode = (exports.ArrayNode = inherit(Node, {
ArrayNode = (exports.ArrayNode = inherit(BaseNode, {
type: 'Array',
constructor: function constructor(objects) {
this.children = (this.objects = objects || []);
@@ -694,7 +694,7 @@
}
});
// Setting the value of a local variable, or the value of an object property.
AssignNode = (exports.AssignNode = inherit(Node, {
AssignNode = (exports.AssignNode = inherit(BaseNode, {
type: 'Assign',
PROTO_ASSIGN: /^(\S+)\.prototype/,
LEADING_DOT: /^\.(prototype\.)?/,
@@ -798,7 +798,7 @@
}));
// A function definition. The only node that creates a new Scope.
// A CodeNode does not have any children -- they're within the new scope.
CodeNode = (exports.CodeNode = inherit(Node, {
CodeNode = (exports.CodeNode = inherit(BaseNode, {
type: 'Code',
constructor: function constructor(params, body, tag) {
this.params = params;
@@ -865,7 +865,7 @@
}));
// A splat, either as a parameter to a function, an argument to a call,
// or in a destructuring assignment.
SplatNode = (exports.SplatNode = inherit(Node, {
SplatNode = (exports.SplatNode = inherit(BaseNode, {
type: 'Splat',
constructor: function constructor(name) {
if (!(name.compile)) {
@@ -875,7 +875,8 @@
return this;
},
compile_node: function compile_node(o) {
return (typeof this.index !== "undefined" && this.index !== null) ? this.compile_param(o) : this.name.compile(o);
var _a;
return (typeof (_a = this.index) !== "undefined" && _a !== null) ? this.compile_param(o) : this.name.compile(o);
},
compile_param: function compile_param(o) {
var name;
@@ -889,10 +890,15 @@
}));
// A while loop, the only sort of low-level loop exposed by CoffeeScript. From
// it, all other loops can be manufactured.
WhileNode = (exports.WhileNode = inherit(Node, {
WhileNode = (exports.WhileNode = inherit(BaseNode, {
type: 'While',
constructor: function constructor(condition, body) {
this.children = [(this.condition = condition), (this.body = body)];
constructor: function constructor(condition, opts) {
this.children = [(this.condition = condition)];
this.filter = opts && opts.filter;
return this;
},
add_body: function add_body(body) {
this.children.push((this.body = body));
return this;
},
top_sensitive: function top_sensitive() {
@@ -909,20 +915,25 @@
if (!top) {
rvar = o.scope.free_variable();
set = this.idt() + rvar + ' = [];\n';
this.body = PushNode.wrap(rvar, this.body);
if (this.body) {
this.body = PushNode.wrap(rvar, this.body);
}
}
post = returns ? '\n' + this.idt() + 'return ' + rvar + ';' : '';
pre = set + this.idt() + 'while (' + cond + ')';
if (!this.body) {
return pre + ' null;' + post;
}
if (this.filter) {
this.body = Expressions.wrap([new IfNode(this.filter, this.body)]);
}
return pre + ' {\n' + this.body.compile(o) + '\n' + this.idt() + '}' + post;
}
}));
statement(WhileNode);
// Simple Arithmetic and logical operations. Performs some conversion from
// CoffeeScript operations into their JavaScript equivalents.
OpNode = (exports.OpNode = inherit(Node, {
OpNode = (exports.OpNode = inherit(BaseNode, {
type: 'Op',
CONVERSIONS: {
'==': '===',
@@ -1008,7 +1019,7 @@
}
}));
// A try/catch/finally block.
TryNode = (exports.TryNode = inherit(Node, {
TryNode = (exports.TryNode = inherit(BaseNode, {
type: 'Try',
constructor: function constructor(attempt, error, recovery, ensure) {
this.children = compact([(this.attempt = attempt), (this.recovery = recovery), (this.ensure = ensure)]);
@@ -1029,7 +1040,7 @@
}));
statement(TryNode);
// Throw an exception.
ThrowNode = (exports.ThrowNode = inherit(Node, {
ThrowNode = (exports.ThrowNode = inherit(BaseNode, {
type: 'Throw',
constructor: function constructor(expression) {
this.children = [(this.expression = expression)];
@@ -1041,7 +1052,7 @@
}));
statement(ThrowNode, true);
// Check an expression for existence (meaning not null or undefined).
ExistenceNode = (exports.ExistenceNode = inherit(Node, {
ExistenceNode = (exports.ExistenceNode = inherit(BaseNode, {
type: 'Existence',
constructor: function constructor(expression) {
this.children = [(this.expression = expression)];
@@ -1056,7 +1067,7 @@
_a = [variable, variable];
first = _a[0];
second = _a[1];
if (variable instanceof CallNode) {
if (variable instanceof CallNode || (variable instanceof ValueNode && variable.has_properties())) {
_b = variable.compile_reference(o);
first = _b[0];
second = _b[1];
@@ -1064,7 +1075,7 @@
return '(typeof ' + first.compile(o) + ' !== "undefined" && ' + second.compile(o) + ' !== null)';
};
// An extra set of parentheses, specified explicitly in the source.
ParentheticalNode = (exports.ParentheticalNode = inherit(Node, {
ParentheticalNode = (exports.ParentheticalNode = inherit(BaseNode, {
type: 'Paren',
constructor: function constructor(expression) {
this.children = [(this.expression = expression)];
@@ -1090,7 +1101,7 @@
// into a for loop. Also acts as an expression, able to return the result
// of the comprehenion. Unlike Python array comprehensions, it's able to pass
// the current index of the loop as a second parameter.
ForNode = (exports.ForNode = inherit(Node, {
ForNode = (exports.ForNode = inherit(BaseNode, {
type: 'For',
constructor: function constructor(body, source, name, index) {
var _a;
@@ -1133,7 +1144,7 @@
if (range) {
index_var = scope.free_variable();
source_part = source.compile_variables(o);
for_part = index_var + '=0, ' + source.compile(merge(o, {
for_part = index_var + ' = 0, ' + source.compile(merge(o, {
index: ivar,
step: this.step
})) + ', ' + index_var + '++';
@@ -1188,7 +1199,7 @@
// expression by pushing down requested returns to the expression bodies.
// Single-expression IfNodes are compiled into ternary operators if possible,
// because ternaries are first-class returnable assignable expressions.
IfNode = (exports.IfNode = inherit(Node, {
IfNode = (exports.IfNode = inherit(BaseNode, {
type: 'If',
constructor: function constructor(condition, body, else_body, tags) {
this.condition = condition;
@@ -1246,9 +1257,15 @@
return this;
},
// Rewrite a chain of IfNodes to add a default case as the final else.
add_else: function add_else(exprs) {
this.is_chain() ? this.else_body.add_else(exprs) : (this.else_body = exprs && exprs.unwrap());
this.children.push(exprs);
add_else: function add_else(exprs, statement) {
if (this.is_chain()) {
this.else_body.add_else(exprs, statement);
} else {
if (!(statement)) {
exprs = exprs.unwrap();
}
this.children.push((this.else_body = exprs));
}
return this;
},
// If the else_body is an IfNode itself, then we've got an if-else chain.
@@ -1308,4 +1325,4 @@
return if_part + ' : ' + else_part;
}
}));
})();
})();

View File

@@ -1,21 +1,19 @@
(function(){
var LONG_FLAG, OPTIONAL, SHORT_FLAG, build_rule, build_rules, op, spaces;
// Create an OptionParser with a list of valid options.
op = (exports.OptionParser = function OptionParser(rules) {
this.banner = 'Usage: [Options]';
op = (exports.OptionParser = function OptionParser(rules, banner) {
this.banner = banner || 'Usage: [Options]';
this.options_title = 'Available options:';
this.rules = build_rules(rules);
this.actions = {};
return this;
});
// Add a callback to fire when a particular option is encountered.
op.prototype.add = function add(value, callback) {
return this.actions[value] = callback;
};
// Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
op.prototype.parse = function parse(args) {
var _a, _b, arg, callback, is_option, results, rule, value;
results = [];
var _a, _b, arg, is_option, options, rule;
arguments = Array.prototype.slice.call(arguments, 0);
options = {
arguments: []
};
args = args.concat([]);
while (((arg = args.shift()))) {
is_option = false;
@@ -23,20 +21,16 @@
for (_b = 0; _b < _a.length; _b++) {
rule = _a[_b];
if (rule.letter === arg || rule.flag === arg) {
callback = this.actions[rule.name];
value = rule.argument && args.shift();
if (callback) {
callback(value);
}
options[rule.name] = rule.argument ? args.shift() : true;
is_option = true;
break;
}
}
if (!(is_option)) {
results.push(arg);
options.arguments.push(arg);
}
}
return results;
return options;
};
// Return the help text for this OptionParser, for --help and such.
op.prototype.help = function help() {
@@ -114,4 +108,4 @@
}
return builder.join('');
};
})();
})();

File diff suppressed because one or more lines are too long

View File

@@ -29,4 +29,4 @@
process.stdio.addListener('data', readline);
process.stdio.open();
print(prompt);
})();
})();

View File

@@ -9,7 +9,7 @@
// indentation, and single-line flavors of expressions.
exports.Rewriter = (re = function re() { });
// Tokens that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']];
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']];
// Tokens that signal the start of a balanced pair.
EXPRESSION_START = (function() {
_a = []; _b = BALANCED_PAIRS;
@@ -212,7 +212,7 @@
idx = tag === 'OUTDENT' ? i + 1 : i;
stack_pointer = tag === 'INDENT' ? 2 : 1;
_k = 0; _l = stack[stack.length - stack_pointer];
for (_j=0, tmp=_k; (_k <= _l ? tmp < _l : tmp > _l); (_k <= _l ? tmp += 1 : tmp -= 1), _j++) {
for (_j = 0, tmp=_k; (_k <= _l ? tmp < _l : tmp > _l); (_k <= _l ? tmp += 1 : tmp -= 1), _j++) {
this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
}
size = stack[stack.length - stack_pointer] + 1;
@@ -295,7 +295,7 @@
levels[open] -= 1;
}
if (levels[open] < 0) {
throw "too many " + token[1];
throw new Error("too many " + token[1]);
}
}
return 1;
@@ -380,4 +380,4 @@
});
})(this));
};
})();
})();

View File

@@ -111,4 +111,4 @@
Scope.prototype.compiled_assignments = function compiled_assignments() {
return this.assigned_variables().join(', ');
};
})();
})();

View File

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

View File

@@ -24,7 +24,7 @@ parser.lexer: {
showPosition: -> @pos
}
exports.VERSION: '0.5.0'
exports.VERSION: '0.5.2'
# Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile: (code, options) ->
@@ -38,8 +38,8 @@ exports.tokenize: (code) ->
exports.tree: (code) ->
parser.parse lexer.tokenize code
# Pretty-print a token stream.
exports.print_tokens: (tokens) ->
strings: for token in tokens
'[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']'
puts strings.join(' ')
# Activate CoffeeScript in the browser by having it compile and eval
# all script tags with a content-type of text/coffeescript.
if document? and document.getElementsByTagName
for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript'
eval exports.compile tag.innerHTML

View File

@@ -1,7 +1,7 @@
fs: require 'fs'
path: require 'path'
coffee: require 'coffee-script'
optparse: require('optparse')
fs: require 'fs'
path: require 'path'
optparse: require 'optparse'
CoffeeScript: require 'coffee-script'
BANNER: '''
coffee compiles CoffeeScript source files into JavaScript.
@@ -17,10 +17,11 @@ SWITCHES: [
['-w', '--watch', 'watch scripts for changes, and recompile']
['-p', '--print', 'print the compiled JavaScript to stdout']
['-l', '--lint', 'pipe the compiled JavaScript through JSLint']
['-s', '--stdio', 'listen for and compile scripts over stdio']
['-e', '--eval', 'compile a string from the command line']
['-n', '--no-wrap', 'compile without the top-level function wrapper']
['-t', '--tokens', 'print the tokens that the lexer produces']
['-tr','--tree', 'print the parse tree that Jison produces']
['-n', '--no-wrap', 'compile without the top-level function wrapper']
['-v', '--version', 'display CoffeeScript version']
['-h', '--help', 'display this help message']
]
@@ -32,9 +33,12 @@ option_parser: null
# The CommandLine handles all of the functionality of the `coffee` utility.
exports.run: ->
parse_options()
return require 'repl' if options.interactive
return compile_script 'terminal', sources[0] if options.eval
usage() unless sources.length
return usage() if options.help
return version() if options.version
return require 'repl' if options.interactive
return compile_stdio() if options.stdio
return compile_script 'unknown', sources[0] if options.eval
return usage() unless sources.length
separator: sources.indexOf '--'
flags: []
if separator >= 0
@@ -52,7 +56,7 @@ usage: ->
# The "--version" message.
version: ->
puts "CoffeeScript version " + coffee.VERSION
puts "CoffeeScript version " + CoffeeScript.VERSION
process.exit 0
# Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
@@ -62,23 +66,30 @@ compile_scripts: ->
fs.readFile source, (err, code) -> compile_script(source, code)
compile(source) for source in sources
# Compile a single source script, containing the given code, according to the
# requested options. Both compile_scripts and watch_scripts share this method.
compile_script: (source, code) ->
opts: options
o: if opts.no_wrap then {no_wrap: true} else {}
o: options
try
if opts.tokens then coffee.print_tokens coffee.tokenize code
else if opts.tree then puts coffee.tree(code).toString()
if o.tokens then print_tokens CoffeeScript.tokenize code
else if o.tree then puts CoffeeScript.tree(code).toString()
else
js: coffee.compile code, o
if opts.run then eval js
else if opts.print then puts js
else if opts.lint then lint js
else write_js source, js
js: CoffeeScript.compile code, compile_options()
if o.run then eval js
else if o.lint then lint js
else if o.print or o.eval then print js
else write_js source, js
catch err
if opts.watch then puts err.message else throw err
if o.watch then puts err.message else throw err
# Listen for and compile scripts over stdio.
compile_stdio: ->
code: ''
process.stdio.open()
process.stdio.addListener 'data', (string) ->
code += string if string
process.stdio.addListener 'close', ->
process.stdio.write CoffeeScript.compile code, compile_options()
# Watch a list of source CoffeeScript files, recompiling them every time the
# files are updated.
@@ -106,25 +117,18 @@ lint: (js) ->
jsl.write js
jsl.close()
# Pretty-print a token stream.
print_tokens: (tokens) ->
strings: for token in tokens
'[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']'
puts strings.join(' ')
# Use OptionParser for all the options.
parse_options: ->
opts: options: {}
oparser: option_parser: new optparse.OptionParser SWITCHES
oparser.banner: BANNER
oparser.add 'interactive', -> opts.interactive: true
oparser.add 'run', -> opts.run: true
oparser.add 'output', (dir) -> opts.output: dir
oparser.add 'watch', -> opts.watch: true
oparser.add 'print', -> opts.print: true
oparser.add 'lint', -> opts.lint: true
oparser.add 'eval', -> opts.eval: true
oparser.add 'tokens', -> opts.tokens: true
oparser.add 'tree', -> opts.tree: true
oparser.add 'no-wrap', -> opts.no_wrap: true
oparser.add 'help', => usage()
oparser.add 'version', => version()
paths: oparser.parse(process.ARGV)
sources: paths[2...paths.length]
option_parser: new optparse.OptionParser SWITCHES, BANNER
options: option_parser.parse(process.ARGV)
sources: options.arguments[2...options.arguments.length]
# The options to pass to the CoffeeScript compiler.
compile_options: ->
if options['no-wrap'] then {no_wrap: true} else {}

View File

@@ -251,6 +251,7 @@ grammar: {
# Indexing into an object or array.
Index: [
o "INDEX_START Expression INDEX_END", -> new IndexNode($2)
o "SOAKED_INDEX_START Expression SOAKED_INDEX_END", -> new IndexNode($2, 'soak')
]
# An object literal.
@@ -300,7 +301,6 @@ grammar: {
This: [
o "@", -> new ValueNode(new LiteralNode('this'))
o "@ Identifier", -> new ValueNode(new LiteralNode('this'), [new AccessorNode($2)])
o "@ Index", -> new ValueNode(new LiteralNode('this'), [$2])
]
# The range literal.
@@ -361,11 +361,16 @@ grammar: {
o "( Expression )", -> new ParentheticalNode($2)
]
# The condition for a while loop.
WhileSource: [
o "WHILE Expression", -> new WhileNode($2)
o "WHILE Expression WHEN Expression", -> new WhileNode($2, {filter : $4})
]
# The while loop. (there is no do..while).
While: [
o "WHILE Expression Block", -> new WhileNode($2, $3)
o "WHILE Expression", -> new WhileNode($2, null)
o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap([$1]))
o "WhileSource Block", -> $1.add_body $2
o "Expression WhileSource", -> $2.add_body $1
]
# Array comprehensions, including guard and current index.
@@ -385,16 +390,14 @@ grammar: {
ForSource: [
o "IN Expression", -> {source: $2}
o "OF Expression", -> {source: $2, object: true}
o "ForSource WHEN Expression", ->
$1.filter: $3; $1
o "ForSource BY Expression", ->
$1.step: $3; $1
o "ForSource WHEN Expression", -> $1.filter: $3; $1
o "ForSource BY Expression", -> $1.step: $3; $1
]
# Switch/When blocks.
Switch: [
o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition($2)
o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6)
o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6, true)
]
# The inner list of whens.
@@ -407,8 +410,7 @@ grammar: {
When: [
o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, null, {statement: true})
o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, null, {statement: true})
o "Comment TERMINATOR When", ->
$3.comment: $1; $3
o "Comment TERMINATOR When", -> $3.comment: $1; $3
]
# The most basic form of "if".

View File

@@ -246,13 +246,21 @@ lex::literal_token: ->
value: match and match[1]
@tag_parameters() if value and value.match(CODE)
value ||= @chunk.substr(0, 1)
not_spaced: not @prev() or not @prev().spaced
tag: value
if value.match(ASSIGNMENT)
tag: 'ASSIGN'
throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
tag: 'TERMINATOR' if value == ';'
if CALLABLE.indexOf(@tag()) >= 0 and (not @prev() or not @prev().spaced)
else if value is ';'
tag: 'TERMINATOR'
else if value is '[' and @tag() is '?' and not_spaced
tag: 'SOAKED_INDEX_START'
@soaked_index: true
@tokens.pop()
else if value is ']' and @soaked_index
tag: 'SOAKED_INDEX_END'
@soaked_index: false
else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
tag: 'CALL_START' if value is '('
tag: 'INDEX_START' if value is '['
@token tag, value

View File

@@ -54,13 +54,13 @@ statement: (klass, only) ->
# generated code should be wrapped up in a closure. An options hash is passed
# and cloned throughout, containing messages from higher in the AST,
# information about the current scope, and indentation level.
Node: exports.Node: ->
BaseNode: exports.BaseNode: ->
# This is extremely important -- we convert JS statements into expressions
# by wrapping them in a closure, only if it's possible, and we're not at
# the top level of a block (which would be unnecessary), and we haven't
# already been asked to return the result.
Node::compile: (o) ->
BaseNode::compile: (o) ->
@options: merge o or {}
@indent: o.indent
del @options, 'operation' unless @operation_sensitive()
@@ -72,39 +72,46 @@ Node::compile: (o) ->
# Statements converted into expressions share scope with their parent
# closure, to preserve JavaScript-style lexical scope.
Node::compile_closure: (o) ->
BaseNode::compile_closure: (o) ->
@indent: o.indent
o.shared_scope: o.scope
ClosureNode.wrap(this).compile(o)
# If the code generation wishes to use the result of a complex expression
# in multiple places, ensure that the expression is only ever evaluated once.
BaseNode::compile_reference: (o) ->
reference: new LiteralNode(o.scope.free_variable())
compiled: new AssignNode(reference, this)
[compiled, reference]
# Quick short method for the current indentation level, plus tabbing in.
Node::idt: (tabs) ->
BaseNode::idt: (tabs) ->
idt: (@indent || '')
idt += TAB for i in [0...(tabs or 0)]
idt
# Does this node, or any of its children, contain a node of a certain kind?
Node::contains: (block) ->
BaseNode::contains: (block) ->
for node in @children
return true if block(node)
return true if node instanceof Node and node.contains block
return true if node instanceof BaseNode and node.contains block
false
# toString representation of the node, for inspecting the parse tree.
Node::toString: (idt) ->
BaseNode::toString: (idt) ->
idt ||= ''
'\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
# Default implementations of the common node methods.
Node::unwrap: -> this
Node::children: []
Node::is_statement: -> false
Node::is_statement_only: -> false
Node::top_sensitive: -> false
Node::operation_sensitive: -> false
BaseNode::unwrap: -> this
BaseNode::children: []
BaseNode::is_statement: -> false
BaseNode::is_statement_only: -> false
BaseNode::top_sensitive: -> false
BaseNode::operation_sensitive: -> false
# A collection of nodes, each one representing an expression.
Expressions: exports.Expressions: inherit Node, {
Expressions: exports.Expressions: inherit BaseNode, {
type: 'Expressions'
constructor: (nodes) ->
@@ -137,7 +144,7 @@ Expressions: exports.Expressions: inherit Node, {
compile: (o) ->
o ||= {}
if o.scope then Node::compile.call(this, o) else @compile_root(o)
if o.scope then BaseNode::compile.call(this, o) else @compile_root(o)
# Compile each expression in the Expressions body.
compile_node: (o) ->
@@ -149,7 +156,7 @@ Expressions: exports.Expressions: inherit Node, {
o.scope: new Scope(null, this, null)
code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
code: code.replace(TRAILING_WHITESPACE, '')
if o.no_wrap then code else "(function(){\n"+code+"\n})();"
if o.no_wrap then code else "(function(){\n"+code+"\n})();\n"
# Compile the expressions body, with declarations of all inner variables
# pushed up to the top.
@@ -185,7 +192,7 @@ statement Expressions
# Literals are static values that can be passed through directly into
# JavaScript without translation, eg.: strings, numbers, true, false, null...
LiteralNode: exports.LiteralNode: inherit Node, {
LiteralNode: exports.LiteralNode: inherit BaseNode, {
type: 'Literal'
constructor: (value) ->
@@ -210,7 +217,7 @@ LiteralNode: exports.LiteralNode: inherit Node, {
LiteralNode::is_statement_only: LiteralNode::is_statement
# Return an expression, or wrap it in a closure and return it.
ReturnNode: exports.ReturnNode: inherit Node, {
ReturnNode: exports.ReturnNode: inherit BaseNode, {
type: 'Return'
constructor: (expression) ->
@@ -226,10 +233,10 @@ ReturnNode: exports.ReturnNode: inherit Node, {
statement ReturnNode, true
# A value, indexed or dotted into, or vanilla.
ValueNode: exports.ValueNode: inherit Node, {
ValueNode: exports.ValueNode: inherit BaseNode, {
type: 'Value'
SOAK: " == null ? undefined : "
SOAK: " == undefined ? undefined : "
constructor: (base, properties) ->
@children: flatten [@base: base, @properties: (properties or [])]
@@ -272,31 +279,30 @@ ValueNode: exports.ValueNode: inherit Node, {
props: if only then @properties[0...@properties.length - 1] else @properties
baseline: @base.compile o
baseline: '(' + baseline + ')' if @base instanceof ObjectNode and @has_properties()
parts: [baseline]
complete: @last: baseline
for prop in props
if prop instanceof AccessorNode and prop.soak
@source: baseline
if prop.soak_node
soaked: true
if @base instanceof CallNode and prop is props[0]
temp: o.scope.free_variable()
parts[parts.length - 1]: '(' + temp + ' = ' + baseline + ')' + @SOAK + (baseline: temp + prop.compile(o))
complete: '(' + temp + ' = ' + complete + ')' + @SOAK + (baseline: temp + prop.compile(o))
else
parts[parts.length - 1] += (@SOAK + (baseline += prop.compile(o)))
complete: complete + @SOAK + (baseline += prop.compile(o))
else
part: prop.compile(o)
baseline += part
parts.push(part)
complete += part
@last: part
@last: parts[parts.length - 1]
@source: if parts.length > 1 then parts[0...(parts.length - 1)].join('') else null
code: parts.join('').replace(/\)\(\)\)/, '()))')
if op and soaked then '(' + code + ')' else code
if op and soaked then '(' + complete + ')' else complete
}
# Pass through CoffeeScript comments into JavaScript comments at the
# same position.
CommentNode: exports.CommentNode: inherit Node, {
CommentNode: exports.CommentNode: inherit BaseNode, {
type: 'Comment'
constructor: (lines) ->
@@ -312,7 +318,7 @@ statement CommentNode
# Node for a function invocation. Takes care of converting super() calls into
# calls against the prototype's function of the same name.
CallNode: exports.CallNode: inherit Node, {
CallNode: exports.CallNode: inherit BaseNode, {
type: 'Call'
constructor: (variable, args) ->
@@ -356,18 +362,11 @@ CallNode: exports.CallNode: inherit Node, {
if i is 0 then code else '.concat(' + code + ')'
@prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')'
# 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.
compile_reference: (o) ->
reference: new LiteralNode(o.scope.free_variable())
call: new ParentheticalNode(new AssignNode(reference, this))
[call, reference]
}
# Node to extend an object's prototype with an ancestor object.
# After goog.inherits from the Closure Library.
ExtendsNode: exports.ExtendsNode: inherit Node, {
ExtendsNode: exports.ExtendsNode: inherit BaseNode, {
type: 'Extends'
constructor: (child, parent) ->
@@ -400,13 +399,13 @@ statement ExtendsNode
# A dotted accessor into a part of a value, or the :: shorthand for
# an accessor into the object's prototype.
AccessorNode: exports.AccessorNode: inherit Node, {
AccessorNode: exports.AccessorNode: inherit BaseNode, {
type: 'Accessor'
constructor: (name, tag) ->
@children: [@name: name]
@prototype: tag is 'prototype'
@soak: tag is 'soak'
@soak_node: tag is 'soak'
this
compile_node: (o) ->
@@ -415,11 +414,12 @@ AccessorNode: exports.AccessorNode: inherit Node, {
}
# An indexed accessor into a part of an array or object.
IndexNode: exports.IndexNode: inherit Node, {
IndexNode: exports.IndexNode: inherit BaseNode, {
type: 'Index'
constructor: (index) ->
@children: [@index: index]
constructor: (index, tag) ->
@children: [@index: index]
@soak_node: tag is 'soak'
this
compile_node: (o) ->
@@ -429,7 +429,7 @@ IndexNode: exports.IndexNode: inherit Node, {
# A range literal. Ranges can be used to extract portions (slices) of arrays,
# or to specify a range for list comprehensions.
RangeNode: exports.RangeNode: inherit Node, {
RangeNode: exports.RangeNode: inherit BaseNode, {
type: 'Range'
constructor: (from, to, exclusive) ->
@@ -469,7 +469,7 @@ RangeNode: exports.RangeNode: inherit Node, {
# An array slice literal. Unlike JavaScript's Array#slice, the second parameter
# specifies the index of the end of the slice (just like the first parameter)
# is the index of the beginning.
SliceNode: exports.SliceNode: inherit Node, {
SliceNode: exports.SliceNode: inherit BaseNode, {
type: 'Slice'
constructor: (range) ->
@@ -485,7 +485,7 @@ SliceNode: exports.SliceNode: inherit Node, {
}
# An object literal.
ObjectNode: exports.ObjectNode: inherit Node, {
ObjectNode: exports.ObjectNode: inherit BaseNode, {
type: 'Object'
constructor: (props) ->
@@ -512,7 +512,7 @@ ObjectNode: exports.ObjectNode: inherit Node, {
}
# An array literal.
ArrayNode: exports.ArrayNode: inherit Node, {
ArrayNode: exports.ArrayNode: inherit BaseNode, {
type: 'Array'
constructor: (objects) ->
@@ -559,7 +559,7 @@ ClosureNode: exports.ClosureNode: {
}
# Setting the value of a local variable, or the value of an object property.
AssignNode: exports.AssignNode: inherit Node, {
AssignNode: exports.AssignNode: inherit BaseNode, {
type: 'Assign'
PROTO_ASSIGN: /^(\S+)\.prototype/
@@ -632,7 +632,7 @@ AssignNode: exports.AssignNode: inherit Node, {
# A function definition. The only node that creates a new Scope.
# A CodeNode does not have any children -- they're within the new scope.
CodeNode: exports.CodeNode: inherit Node, {
CodeNode: exports.CodeNode: inherit BaseNode, {
type: 'Code'
constructor: (params, body, tag) ->
@@ -676,7 +676,7 @@ CodeNode: exports.CodeNode: inherit Node, {
# A splat, either as a parameter to a function, an argument to a call,
# or in a destructuring assignment.
SplatNode: exports.SplatNode: inherit Node, {
SplatNode: exports.SplatNode: inherit BaseNode, {
type: 'Splat'
constructor: (name) ->
@@ -699,11 +699,16 @@ SplatNode: exports.SplatNode: inherit Node, {
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
# it, all other loops can be manufactured.
WhileNode: exports.WhileNode: inherit Node, {
WhileNode: exports.WhileNode: inherit BaseNode, {
type: 'While'
constructor: (condition, body) ->
@children:[@condition: condition, @body: body]
constructor: (condition, opts) ->
@children:[@condition: condition]
@filter: opts and opts.filter
this
add_body: (body) ->
@children.push @body: body
this
top_sensitive: ->
@@ -719,10 +724,11 @@ WhileNode: exports.WhileNode: inherit Node, {
if not top
rvar: o.scope.free_variable()
set: @idt() + rvar + ' = [];\n'
@body: PushNode.wrap(rvar, @body)
@body: PushNode.wrap(rvar, @body) if @body
post: if returns then '\n' + @idt() + 'return ' + rvar + ';' else ''
pre: set + @idt() + 'while (' + cond + ')'
return pre + ' null;' + post if not @body
@body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter
pre + ' {\n' + @body.compile(o) + '\n' + @idt() + '}' + post
}
@@ -731,7 +737,7 @@ statement WhileNode
# Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents.
OpNode: exports.OpNode: inherit Node, {
OpNode: exports.OpNode: inherit BaseNode, {
type: 'Op'
CONVERSIONS: {
@@ -795,7 +801,7 @@ OpNode: exports.OpNode: inherit Node, {
}
# A try/catch/finally block.
TryNode: exports.TryNode: inherit Node, {
TryNode: exports.TryNode: inherit BaseNode, {
type: 'Try'
constructor: (attempt, error, recovery, ensure) ->
@@ -816,7 +822,7 @@ TryNode: exports.TryNode: inherit Node, {
statement TryNode
# Throw an exception.
ThrowNode: exports.ThrowNode: inherit Node, {
ThrowNode: exports.ThrowNode: inherit BaseNode, {
type: 'Throw'
constructor: (expression) ->
@@ -831,7 +837,7 @@ ThrowNode: exports.ThrowNode: inherit Node, {
statement ThrowNode, true
# Check an expression for existence (meaning not null or undefined).
ExistenceNode: exports.ExistenceNode: inherit Node, {
ExistenceNode: exports.ExistenceNode: inherit BaseNode, {
type: 'Existence'
constructor: (expression) ->
@@ -845,11 +851,12 @@ ExistenceNode: exports.ExistenceNode: inherit Node, {
ExistenceNode.compile_test: (o, variable) ->
[first, second]: [variable, variable]
[first, second]: variable.compile_reference(o) if variable instanceof CallNode
if variable instanceof CallNode or (variable instanceof ValueNode and variable.has_properties())
[first, second]: variable.compile_reference(o)
'(typeof ' + first.compile(o) + ' !== "undefined" && ' + second.compile(o) + ' !== null)'
# An extra set of parentheses, specified explicitly in the source.
ParentheticalNode: exports.ParentheticalNode: inherit Node, {
ParentheticalNode: exports.ParentheticalNode: inherit BaseNode, {
type: 'Paren'
constructor: (expression) ->
@@ -872,7 +879,7 @@ ParentheticalNode: exports.ParentheticalNode: inherit Node, {
# into a for loop. Also acts as an expression, able to return the result
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
# the current index of the loop as a second parameter.
ForNode: exports.ForNode: inherit Node, {
ForNode: exports.ForNode: inherit BaseNode, {
type: 'For'
constructor: (body, source, name, index) ->
@@ -908,7 +915,7 @@ ForNode: exports.ForNode: inherit Node, {
if range
index_var: scope.free_variable()
source_part: source.compile_variables(o)
for_part: index_var + '=0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
for_part: index_var + ' = 0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
else
index_var: null
source_part: svar + ' = ' + @source.compile(o) + ';\n' + @idt()
@@ -942,7 +949,7 @@ statement ForNode
# expression by pushing down requested returns to the expression bodies.
# Single-expression IfNodes are compiled into ternary operators if possible,
# because ternaries are first-class returnable assignable expressions.
IfNode: exports.IfNode: inherit Node, {
IfNode: exports.IfNode: inherit BaseNode, {
type: 'If'
constructor: (condition, body, else_body, tags) ->
@@ -985,9 +992,12 @@ IfNode: exports.IfNode: inherit Node, {
this
# Rewrite a chain of IfNodes to add a default case as the final else.
add_else: (exprs) ->
if @is_chain() then @else_body.add_else(exprs) else @else_body: exprs and exprs.unwrap()
@children.push(exprs)
add_else: (exprs, statement) ->
if @is_chain()
@else_body.add_else exprs, statement
else
exprs: exprs.unwrap() unless statement
@children.push @else_body: exprs
this
# If the else_body is an IfNode itself, then we've got an if-else chain.

View File

@@ -1,30 +1,23 @@
# Create an OptionParser with a list of valid options.
op: exports.OptionParser: (rules) ->
@banner: 'Usage: [Options]'
op: exports.OptionParser: (rules, banner) ->
@banner: banner or 'Usage: [Options]'
@options_title: 'Available options:'
@rules: build_rules(rules)
@actions: {}
this
# Add a callback to fire when a particular option is encountered.
op::add: (value, callback) ->
@actions[value]: callback
# Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
op::parse: (args) ->
results: []
options: {arguments: []}
args: args.concat []
while (arg: args.shift())
is_option: false
for rule in @rules
if rule.letter is arg or rule.flag is arg
callback: @actions[rule.name]
value: rule.argument and args.shift()
callback(value) if callback
options[rule.name]: if rule.argument then args.shift() else true
is_option: true
break
results.push arg unless is_option
results
options.arguments.push arg unless is_option
options
# Return the help text for this OptionParser, for --help and such.
op::help: ->

View File

@@ -7,7 +7,8 @@ exports.Rewriter: re: ->
# Tokens that must be balanced.
BALANCED_PAIRS: [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'],
['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']]
['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'],
['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']]
# Tokens that signal the start of a balanced pair.
EXPRESSION_START: pair[0] for pair in BALANCED_PAIRS
@@ -199,7 +200,7 @@ re::ensure_balance: (pairs) ->
levels[open] ||= 0
levels[open] += 1 if token[0] is open
levels[open] -= 1 if token[0] is close
throw "too many " + token[1] if levels[open] < 0
throw new Error("too many " + token[1]) if levels[open] < 0
return 1
unclosed: key for key, value of levels when value > 0
throw new Error("unclosed " + unclosed[0]) if unclosed.length

View File

@@ -41,10 +41,16 @@ obj: {
ok obj?.prop is "hello"
ok obj?['prop'] is "hello"
ok obj.prop?.length is 5
ok obj?['prop']?['length'] is 5
ok obj?.prop?.non?.existent?.property is undefined
ok obj?['non']?['existent'].property is undefined
# Soaks and caches method calls as well.
@@ -68,3 +74,8 @@ result: value?.toString().toLowerCase()
ok result is '10'
# Safely existence test on soaks.
result: not value?.property?
ok result

View File

@@ -26,12 +26,6 @@ reg: /\\/
ok reg(str) and str is '\\'
i: 10
while i -= 1
ok i is 0
money$: 'dollars'
ok money$ is 'dollars'

View File

@@ -41,3 +41,24 @@ result: switch num += 5
ok result
# Ensure that trailing switch elses don't get rewritten.
result: false
switch "word"
when "one thing"
do_something()
else
result: true unless false
ok result
result: false
switch "word"
when "one thing"
do_something()
when "other thing"
do_something()
else
result: true unless false
ok result

View File

@@ -1,9 +1,3 @@
i: 100
while i -= 1
ok i is 0
i: 5
list: while i -= 1
i * 2
@@ -25,4 +19,12 @@ results: while func 1
assert()
i
ok results.join(' ') is '4 3 2 1'
ok results.join(' ') is '4 3 2 1'
i: 10
results: while i -= 1 when i % 2 is 0
i * 2
ok results.join(' ') is '16 12 8 4'

View File

@@ -369,8 +369,8 @@ lookaheadMixin.followSets = function followSets () {
var part = production.handle.slice(i+1);
set = self.first(part);
if (set.length === 0 && bool) { // set was nullable
set = nonterminals[production.symbol].follows;
if (self.nullable(part) && bool) {
set.push.apply(set, nonterminals[production.symbol].follows);
}
}
oldcount = nonterminals[t].follows.length;
@@ -838,7 +838,7 @@ lrGeneratorMixin.generateModule = function generateModule (opt) {
var out = "/* Jison generated parser */\n";
out += (moduleName.match(/\./) ? moduleName : "var "+moduleName)+" = (function(){";
out += "\nvar parser = "+this.generateModule_();
if (this.lexer) {
if (this.lexer && this.lexer.generateModule) {
out += this.lexer.generateModule();
out += "\nparser.lexer = lexer;";
}

View File

@@ -57,6 +57,9 @@ function buildActions (dict, tokens) {
}
function RegExpLexer (dict, input, tokens) {
if (typeof dict === 'string') {
dict = require("./jisonlex").parse(dict);
}
dict = dict || {};
this.performAction = buildActions.call(this, dict, tokens);

View File

@@ -138,8 +138,6 @@ parse: function parse(input) {
var symbol, state, action, a, r, yyval = {}, p, len, ip = 0, newState, expected;
symbol = lex();
while (true) {
this.trace("stack:", JSON.stringify(stack), "\n\t\t\tinput:", this.lexer._input);
this.trace("vstack:", JSON.stringify(vstack));
state = stack[stack.length - 1];
action = table[state] && table[state][symbol];
if (typeof action == "undefined" || !action.length || !action[0]) {
@@ -149,11 +147,11 @@ parse: function parse(input) {
expected.push("'" + this.terminals_[p] + "'");
}
}
self.trace("stack:", JSON.stringify(stack), "symbol:", symbol, "input", this.lexer.upcomingInput());
if (this.lexer.upcomingInput) {
self.trace("input", this.lexer.upcomingInput());
if (this.lexer.showPosition) {
parseError("Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", "), {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
} else {
parseError("Parse error on line " + (yylineno + 1) + ": Unexpected '" + this.terminals_[symbol] + "'", {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
}
parseError("Parse error on line " + (yylineno + 1) + ". Expecting: " + expected.join(", ") + "\n" + (this.lexer.showPosition && this.lexer.showPosition()), {text: this.lexer.match, token: symbol, line: this.lexer.yylineno});
}
this.trace("action:", action);
if (action.length > 1) {
@@ -175,15 +173,12 @@ parse: function parse(input) {
case 2:
reductions++;
len = this.productions_[a[1]][1];
this.trace("reduce by: ", this.productions ? this.productions[a[1]] : a[1]);
yyval.$ = vstack[vstack.length - len];
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, a[1], vstack);
if (typeof r !== "undefined") {
return r;
}
this.trace("yyval=", JSON.stringify(yyval.$));
if (len) {
this.trace("production length:", len);
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
}
@@ -193,10 +188,8 @@ parse: function parse(input) {
stack.push(newState);
break;
case 3:
this.trace("stack:", stack, "\n\tinput:", this.lexer._input);
this.trace("vstack:", JSON.stringify(vstack));
this.trace("Total reductions:", reductions);
this.trace("Total shifts:", shifts);
this.reductionCount = reductions;
this.shiftCount = shifts;
return true;
default:;
}

View File

@@ -134,8 +134,6 @@ parse: function parse(input) {
var symbol, state, action, a, r, yyval = {}, p, len, ip = 0, newState, expected;
symbol = lex();
while (true) {
this.trace("stack:", JSON.stringify(stack), "\n\t\t\tinput:", this.lexer._input);
this.trace("vstack:", JSON.stringify(vstack));
state = stack[stack.length - 1];
action = table[state] && table[state][symbol];
if (typeof action == "undefined" || !action.length || !action[0]) {
@@ -145,11 +143,11 @@ parse: function parse(input) {
expected.push("'" + this.terminals_[p] + "'");
}
}
self.trace("stack:", JSON.stringify(stack), "symbol:", symbol, "input", this.lexer.upcomingInput());
if (this.lexer.upcomingInput) {
self.trace("input", this.lexer.upcomingInput());
if (this.lexer.showPosition) {
parseError("Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", "), {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
} else {
parseError("Parse error on line " + (yylineno + 1) + ": Unexpected '" + this.terminals_[symbol] + "'", {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
}
parseError("Parse error on line " + (yylineno + 1) + ". Expecting: " + expected.join(", ") + "\n" + (this.lexer.showPosition && this.lexer.showPosition()), {text: this.lexer.match, token: symbol, line: this.lexer.yylineno});
}
this.trace("action:", action);
if (action.length > 1) {
@@ -171,15 +169,12 @@ parse: function parse(input) {
case 2:
reductions++;
len = this.productions_[a[1]][1];
this.trace("reduce by: ", this.productions ? this.productions[a[1]] : a[1]);
yyval.$ = vstack[vstack.length - len];
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, a[1], vstack);
if (typeof r !== "undefined") {
return r;
}
this.trace("yyval=", JSON.stringify(yyval.$));
if (len) {
this.trace("production length:", len);
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
}
@@ -189,10 +184,8 @@ parse: function parse(input) {
stack.push(newState);
break;
case 3:
this.trace("stack:", stack, "\n\tinput:", this.lexer._input);
this.trace("vstack:", JSON.stringify(vstack));
this.trace("Total reductions:", reductions);
this.trace("Total shifts:", shifts);
this.reductionCount = reductions;
this.shiftCount = shifts;
return true;
default:;
}

View File

@@ -403,3 +403,15 @@ exports["test DJ lexer"] = function() {
assert.equal(typeof tok, "string");
}
};
exports["test instantiation from string"] = function() {
var dict = "%%\n'x' {return 'X';}\n'y' {return 'Y';}\n<<EOF>> {return 'EOF';}";
var input = "x";
var lexer = new RegExpLexer(dict);
lexer.setInput(input);
assert.equal(lexer.lex(), "X");
assert.equal(lexer.lex(), "EOF");
};

View File

@@ -167,3 +167,17 @@ exports["test LR(1) grammar"] = function () {
var gen = new Jison.Generator(grammar, {type: "lalr"});
assert.equal(gen.conflicts, 2);
};
exports["test BNF grammar bootstrap"] = function () {
var grammar = "%%\n\nspec\n : declaration_list '%%' grammar EOF\n {$$ = $1; $$.bnf = $3; return $$;}\n | declaration_list '%%' grammar '%%' EOF\n {$$ = $1; $$.bnf = $3; return $$;}\n ;\n\ndeclaration_list\n : declaration_list declaration\n {$$ = $1; yy.addDeclaration($$, $2);}\n | \n <$$ = {};>\n ;\n\ndeclaration\n : START id\n <$$ = {start: $2};>\n | operator\n <$$ = {operator: $1};>\n ;\n\noperator\n : associativity token_list\n {$$ = [$1]; $$.push.apply($$, $2);}\n ;\n\nassociativity\n : LEFT\n {$$ = 'left';}\n | RIGHT\n {$$ = 'right';}\n | NONASSOC\n {$$ = 'nonassoc';}\n ;\n\ntoken_list\n : token_list symbol\n {$$ = $1; $$.push($2);}\n | symbol\n {$$ = [$1];}\n ;\n\ngrammar\n : production_list\n {$$ = $1;}\n ;\n\nproduction_list\n : production_list production\n {$$ = $1; $$[$2[0]] = $2[1];}\n | production\n <$$ = {}; $$[$1[0]] = $1[1];>\n ;\n\nproduction\n : id ':' handle_list ';'\n {$$ = [$1, $3];}\n ;\n\nhandle_list\n : handle_list '|' handle_action\n {$$ = $1; $$.push($3);}\n | handle_action\n {$$ = [$1];}\n ;\n\nhandle_action\n : handle action prec\n {$$ = [($1.length ? $1.join(' ') : '')];\n if($2) $$.push($2);\n if($3) $$.push($3);\n if ($$.length === 1) $$ = $$[0];\n }\n ;\n\nhandle\n : handle symbol\n {$$ = $1; $$.push($2)}\n | \n {$$ = [];}\n ;\n\nprec\n : PREC symbol\n <$$ = {prec: $2};>\n | \n {$$ = null;}\n ;\n\nsymbol\n : id\n {$$ = $1;}\n | STRING\n {$$ = yytext;}\n ;\n\nid\n : ID\n {$$ = yytext;}\n ;\n\naction\n : ACTION\n {$$ = yytext;}\n | \n {$$ = '';}\n ;\n\n";
var lex = "\n%%\n\\s+ \t{/* skip whitespace */}\n\"/*\"[^*]*\"*\" \t{return yy.lexComment(this);}\n[a-zA-Z][a-zA-Z0-9_-]* \t{return 'ID';}\n'\"'[^\"]+'\"' \t{yytext = yytext.substr(1, yyleng-2); return 'STRING';}\n\"'\"[^']+\"'\" \t{yytext = yytext.substr(1, yyleng-2); return 'STRING';}\n\":\" \t{return ':';}\n\";\" \t{return ';';}\n\"|\" \t{return '|';}\n\"%%\" \t{return '%%';}\n\"%prec\" \t{return 'PREC';}\n\"%start\" \t{return 'START';}\n\"%left\" \t{return 'LEFT';}\n\"%right\" \t{return 'RIGHT';}\n\"%nonassoc\" \t{return 'NONASSOC';}\n\"%\"[a-zA-Z]+[^\\n]* \t{/* ignore unrecognized decl */}\n\"{{\"[^}]*\"}\" \t{return yy.lexAction(this);}\n\"{\"[^}]*\"}\" \t{yytext = yytext.substr(1, yyleng-2); return 'ACTION';}\n\"<\"[^>]*\">\" \t{yytext = yytext.substr(1, yyleng-2); return 'ACTION';}\n. \t{/* ignore bad characters */}\n<<EOF>> \t{return 'EOF';}\n\n%%\n\n";
var gen = new Jison.Generator(grammar, {type: "lalr"});
gen.lexer = new Lexer(lex);
var parser = gen.createParser();
assert.ok(parser.parse(grammar), "bootstrapped bnf parser should parse correctly.");
};