Compare commits

...

41 Commits
0.5.2 ... 0.5.3

Author SHA1 Message Date
Jeremy Ashkenas
62b2ab29cd CoffeeScript 0.5.3, with classes 2010-02-27 20:21:46 -05:00
Jeremy Ashkenas
a35693554c updating documentation for classes 2010-02-27 20:12:47 -05:00
Jeremy Ashkenas
f7427259ee reserving __extends and __hasProp 2010-02-27 20:03:57 -05:00
Jeremy Ashkenas
e02ab76edf converting the remainder of the CoffeeScript compiler (Rewriter, Scope, Optparse) to use classes 2010-02-27 19:46:45 -05:00
Jeremy Ashkenas
9f46c306e5 super is now possible in nodes/Expressions, where it wasn't possible before. 2010-02-27 19:42:10 -05:00
Jeremy Ashkenas
b5c9d779bd updating the Lexer to use classes and some of the older documentation 2010-02-27 19:40:53 -05:00
Jeremy Ashkenas
d2cb1f321e making sure that the body of extends only gets defined once per file. 2010-02-27 19:29:34 -05:00
Jeremy Ashkenas
8f871a8218 converting the nodes.coffee AST to use the new class system 2010-02-27 19:19:53 -05:00
Jeremy Ashkenas
4ec7514d10 making inner comments work within class definitions 2010-02-27 19:03:23 -05:00
Jeremy Ashkenas
1c7e4c4203 first draft of adding classes to CoffeeScript 2010-02-27 18:57:45 -05:00
Jeremy Ashkenas
7d39fe1c56 fixing multiple evaluation of splat sources, when it's an invoked function 2010-02-27 15:15:26 -05:00
Jeremy Ashkenas
afa26c37f1 fixing regexp literals versus division, with tests 2010-02-27 14:30:14 -05:00
Jeremy Ashkenas
2f658ba925 fixing multiple single-line function forms on the same line 2010-02-27 11:03:43 -05:00
Jeremy Ashkenas
0ab810e4cb fancy new fullscreen version of 'Try CoffeeScript' 2010-02-27 02:21:26 -05:00
Jeremy Ashkenas
37d086b670 rebuilding the browser code in anticipation of a page push 2010-02-27 01:38:50 -05:00
Jeremy Ashkenas
5e7f5f390a adding a traverse method to the AST, so we can do fancy processing from external scripts. 2010-02-27 01:22:21 -05:00
Jeremy Ashkenas
f4cd0bdf29 making --run the default option for consistency. If you want to save the file, use -c or --compile. 2010-02-27 00:38:04 -05:00
Jeremy Ashkenas
723ea53585 adding the long-ago-needed documentation for constructors with 'return this' 2010-02-26 20:09:49 -05:00
Jeremy Ashkenas
93f644fae2 finishing the second half of prefix installs. Using readLink to refer to the CoffeeScript installation reliably. 2010-02-26 19:49:12 -05:00
Jeremy Ashkenas
82951a469b adding favicon to docs 2010-02-26 19:10:56 -05:00
Jeremy Ashkenas
d2d5f649d3 caching the length property lookup for vanilla array comprehensions and rebuilding docs 2010-02-25 23:39:14 -05:00
Jeremy Ashkenas
5c7526a741 moving some of the fs methods over to sync methods, where it's alright and where it makes things clearer 2010-02-25 21:53:42 -05:00
Jeremy Ashkenas
17ea48c543 first draft of options for Cakefiles, using optparse.coffee, as well as a cake install task that takes --prefix. Still need to fix the lib/bin scripts 2010-02-25 21:43:42 -05:00
Jeremy Ashkenas
55ed202957 no outline on the Try CoffeeScript input for safari 2010-02-25 21:10:32 -05:00
Jeremy Ashkenas
d5df5505f9 hide the error div on initial page load 2010-02-25 20:48:54 -05:00
Jeremy Ashkenas
dc7f4b4be0 new live 'Try CoffeeScript' 2010-02-25 20:39:34 -05:00
Jeremy Ashkenas
406a18067d allowing merged short flags in optparse.coffee, via normalize_arguments 2010-02-25 19:06:08 -05:00
Jeremy Ashkenas
3ae2ebe5ea Merge branch 'fix_example_webserver' of git://github.com/hugs/coffee-script 2010-02-25 18:56:11 -05:00
Jeremy Ashkenas
a23dc6b753 raising an error on unrecognized options 2010-02-25 18:54:08 -05:00
Jeremy Ashkenas
5f1d3fd775 updating docs with the new flags 2010-02-25 18:43:33 -05:00
Jeremy Ashkenas
9d4e06e8a8 moving -tr --tree to -n --nodes, and --no-wrap gives up its -n short flag. 2010-02-25 18:42:35 -05:00
Jason Huggins
6bc61ec1a1 Fixed web_server example to be compatible with Node v0.1.30 2010-02-25 17:42:05 -06:00
Jeremy Ashkenas
c62f93f930 improving errors for undefined options, and error messages for compile attempts on nonexistent files 2010-02-25 18:36:43 -05:00
Jeremy Ashkenas
213ae1430e using text instead of html to escape entities in the rendered JS for Try CoffeeScript 2010-02-25 18:20:15 -05:00
Jeremy Ashkenas
ee5d738827 doc update 2010-02-25 17:18:28 -05:00
Jeremy Ashkenas
eab9bbf04f adding a test for nested pattern matching 2010-02-25 07:31:33 -05:00
Jeremy Ashkenas
4ed51536bb fixing patternmatched assigns within assigns within calls 2010-02-25 07:28:48 -05:00
Jeremy Ashkenas
b32a60585b improving CoffeeScript in browser script activation, and updating docs 2010-02-25 06:26:27 -05:00
Jeremy Ashkenas
66a6568fe7 cleaning and shrinking the option parser 2010-02-25 06:15:58 -05:00
Jeremy Ashkenas
fe32146adc cleaning and commenting cake.coffee 2010-02-25 01:17:43 -05:00
Jeremy Ashkenas
69feac3a01 adding return values for destructuring assignment. 2010-02-25 00:43:02 -05:00
54 changed files with 3086 additions and 2614 deletions

View File

@@ -7,19 +7,25 @@ run: (args) ->
proc.addListener 'error', (err) -> if err then puts err
task 'install', 'install CoffeeScript into /usr/local', ->
option '-p', '--prefix [DIR]', 'set the installation prefix for `cake install`'
task 'install', 'install CoffeeScript into /usr/local (or --prefix)', (options) ->
base: options.prefix or '/usr/local'
lib: base + '/lib/coffee-script'
exec([
'mkdir -p /usr/local/lib/coffee-script'
'cp -rf bin lib LICENSE README package.json src vendor /usr/local/lib/coffee-script'
'ln -sf /usr/local/lib/coffee-script/lib/bin/coffee /usr/local/bin/coffee'
'ln -sf /usr/local/lib/coffee-script/lib/bin/cake /usr/local/bin/cake'
].join(' && '))
'mkdir -p ' + lib
'cp -rf bin lib LICENSE README package.json src vendor ' + lib
'ln -sf ' + lib + '/bin/coffee ' + base + '/bin/coffee'
'ln -sf ' + lib + '/bin/cake ' + base + '/bin/cake'
].join(' && '), (err, stdout, stderr) ->
if err then print stderr
)
task 'build', 'build the CoffeeScript language from source', ->
fs.readdir 'src', (err, files) ->
files: 'src/' + file for file in files when file.match(/\.coffee$/)
run ['-o', 'lib'].concat(files)
files: fs.readdirSync 'src'
files: 'src/' + file for file in files when file.match(/\.coffee$/)
run ['-c', '-o', 'lib'].concat(files)
task 'build:parser', 'rebuild the Jison parser (run build first)', ->

3
README
View File

@@ -34,7 +34,8 @@
To suggest a feature, report a bug, or general discussion:
http://github.com/jashkenas/coffee-script/issues/
If you'd like to chat, drop by #coffeescript on Freenode.
If you'd like to chat, drop by #coffeescript on Freenode IRC,
or on webchat.freenode.net.
The source repository:
git://github.com/jashkenas/coffee-script.git

View File

@@ -7,7 +7,7 @@ require 'yui/compressor'
desc "Build the documentation page"
task :doc do
source = 'documentation/index.html.erb'
child = fork { exec "bin/coffee documentation/coffee/*.coffee -o documentation/js -w" }
child = fork { exec "bin/coffee -c documentation/coffee/*.coffee -o documentation/js -w" }
at_exit { Process.kill("INT", child) }
Signal.trap("INT") { exit }
loop do

View File

@@ -1,7 +1,15 @@
#!/usr/bin/env node
process.mixin(require('sys'));
var path = require('path');
var fs = require('fs');
var lib = null;
require.paths.unshift(__dirname + '/../lib');
if (fs.lstatSync(__filename).isSymbolicLink()) {
lib = path.join(path.dirname(fs.readlinkSync(__filename)), '../lib');
} else {
lib = path.join(__dirname, '../lib');
}
require.paths.unshift(lib);
require('cake').run();

View File

@@ -1,7 +1,15 @@
#!/usr/bin/env node
process.mixin(require('sys'));
var path = require('path');
var fs = require('fs');
var lib = null;
require.paths.unshift(__dirname + '/../lib');
if (fs.lstatSync(__filename).isSymbolicLink()) {
lib = path.join(path.dirname(fs.readlinkSync(__filename)), '../lib');
} else {
lib = path.join(__dirname, '../lib');
}
require.paths.unshift(lib);
require('command_line').run();

View File

@@ -1,5 +1,5 @@
# Eat lunch.
lunch: eat(food) for food in ['toast', 'cheese', 'wine']
lunch: eat food for food in ['toast', 'cheese', 'wine']
# Naive collision detection.
for roid in asteroids

View File

@@ -0,0 +1,29 @@
class Animal
move: (meters) ->
alert @name + " moved " + meters + "m."
class Snake extends Animal
constructor: (name) ->
@name: name
move: ->
alert "Slithering..."
super 5
class Horse extends Animal
constructor: (name) ->
@name: name
move: ->
alert "Galloping..."
super 45
sam: new Snake "Sammy the Python"
tom: new Horse "Tommy the Palomino"
sam.move()
tom.move()

View File

@@ -0,0 +1,2 @@
String::dasherize: ->
this.replace(/_/g, "-")

View File

@@ -1,34 +0,0 @@
Animal: ->
Animal::move: (meters) ->
alert @name + " moved " + meters + "m."
Snake: (name) ->
@name: name
this
Snake extends Animal
Snake::move: ->
alert "Slithering..."
super 5
Horse: (name) ->
@name: name
this
Horse extends Animal
Horse::move: ->
alert "Galloping..."
super 45
sam: new Snake "Sammy the Python"
tom: new Horse "Tommy the Palomino"
sam.move()
tom.move()

View File

@@ -117,10 +117,23 @@ div.code {
#logo {
display: block;
width: 215px; height: 50px;
background: url('logo.png');
background: url('../images/logo.png');
position: absolute;
top: 0px; left: 10px;
}
#error {
position: absolute;
-webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px;
right: 15px; top: 15px; left: 565px;
height: 15px;
padding: 2px 5px;
background: #fdcdcc;
color: #864544;
border: 1px solid #864544;
font: 10px/15px Arial;
overflow: hidden;
text-transform: uppercase;
}
.navigation {
height: 50px;
font: bold 11px/50px Arial;
@@ -132,6 +145,9 @@ div.code {
border-top: 0; border-bottom: 0;
cursor: pointer;
}
body.full_screen .navigation {
position: static;
}
.navigation.try {
border-left: 0;
}
@@ -161,17 +177,33 @@ div.code {
width: 700px;
padding: 0;
}
body.full_screen .navigation .contents.repl_wrapper {
position: fixed;
width: auto; height: auto;
left: 60px; top: 75px; right: 60px; bottom: 30px;
}
.navigation .contents.repl_wrapper .code {
-webkit-box-shadow: none; -moz-box-shadow: none;
background: transparent;
border: 0;
position: static;
}
body.full_screen .navigation .contents.repl_wrapper .code {
height: 100%;
padding: 0; margin: 0;
}
.navigation .code button {
bottom: 10px;
text-transform: none;
}
.navigation .compile {
left: 240px; right: auto;
.navigation .full_screen, .navigation .minimize {
right: auto;
left: 10px;
display: none;
}
body.minimized .full_screen, body.full_screen .minimize {
display: inline;
}
.navigation .contents a {
display: block;
width: 300px;
@@ -195,19 +227,40 @@ div.code {
#repl_source, #repl_results {
background: transparent;
outline: none;
margin: 5px 0 20px;
}
#repl_source {
border: 0;
padding: 5px 7px;
#repl_source_wrap {
margin-left: 5px;
min-height: 250px;
resize: none;
width: 295px;
height: 250px;
width: 307px;
position: relative;
float: left;
}
#repl_source {
width: 96%;
height: 100%;
border: 0;
resize: none;
}
#repl_results {
font-family: Monaco, Consolas, "Lucida Console", monospace;
text-transform: none;
font-weight: normal;
min-height: 260px;
width: 355px;
}
height: 260px;
margin-bottom: 25px;
overflow-y: auto;
width: 370px;
}
body.full_screen #repl_results, body.full_screen #repl_source_wrap {
width: auto; height: auto;
position: absolute;
margin-bottom: 0;
top: 10px; left: 10px; right: 10px; bottom: 40px;
}
body.full_screen #repl_source_wrap {
right: 50%;
}
body.full_screen #repl_results {
left: 50%;
}

View File

@@ -57,6 +57,7 @@ pre.idle .FunctionName {
color: #21439C;
}
pre.idle .Variable {
color: #A535AE;
}
pre.idle .Comment {
color: #919191;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -22,8 +22,9 @@
<title>CoffeeScript</title>
<link rel="stylesheet" type="text/css" href="documentation/css/docs.css" />
<link rel="stylesheet" type="text/css" href="documentation/css/idle.css" />
<link rel="shortcut icon" href="documentation/images/favicon.ico" />
</head>
<body>
<body class="minimized">
<div id="fadeout"></div>
@@ -51,7 +52,7 @@
<a href="#slice_splice">Array Slicing and Splicing with Ranges</a>
<a href="#expressions">Everything is an Expression</a>
<a href="#existence">The Existential Operator</a>
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a>
<a href="#classes">Classes, Inheritance, and Super</a>
<a href="#pattern_matching">Pattern Matching</a>
<a href="#fat_arrow">Function Binding</a>
<a href="#embedded">Embedded JavaScript</a>
@@ -71,17 +72,19 @@
</div>
<div class="contents repl_wrapper">
<div class="code">
<textarea id="repl_source">reverse: (string) ->
<div id="repl_source_wrap"><textarea id="repl_source">reverse: (string) ->
string.split('').reverse().join ''
alert reverse '!tpircseeffoC'</textarea>
alert reverse '!tpircseeffoC'</textarea></div>
<pre id="repl_results"></pre>
<button class="compile" onclick="javascript: repl_compile();">compile</button>
<button class="run" onclick="javascript: repl_run();">run</button>
<button class="full_screen">go full screen</button>
<button class="minimize">minimize</button>
<button class="run">run</button>
<br class="clear" />
</div>
</div>
</div>
<div id="error" style="display:none;"></div>
</div>
<div class="container">
@@ -108,7 +111,7 @@ alert reverse '!tpircseeffoC'</textarea>
<p>
<b>Latest Version:</b>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>
</p>
<h2>
@@ -153,7 +156,7 @@ alert reverse '!tpircseeffoC'</textarea>
<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.2">0.5.2</a>.
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
@@ -162,33 +165,33 @@ alert reverse '!tpircseeffoC'</textarea>
sudo bin/cake install</pre>
<p>
This provides the <tt>coffee</tt> command, which can
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
well as debug them. The <tt>coffee</tt>
command also provides direct evaluation and an interactive REPL.
This provides the <tt>coffee</tt> command, which will execute CoffeeScripts
under Node.js by default, but is also used to compile CoffeeScript
<tt>.coffee</tt> files into JavaScript, or to run an an interactive REPL.
When compiling to JavaScript, <tt>coffee</tt> writes the output
as <tt>.js</tt> files in the same directory by default, but output
can be customized with the following options:
</p>
<table>
<tr>
<td><code>-c, --compile</code></td>
<td>
Compile a <tt>.coffee</tt> script into a <tt>.js</tt> JavaScript file
of the same name.
</td>
</tr>
<tr>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session to try short snippets.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute a given CoffeeScript without saving the intermediate
JavaScript.
</td>
</tr>
<tr>
<td><code>-o, --output [DIR]</code></td>
<td>
Write out all compiled JavaScript files into the specified directory.
Use in conjunction with <tt>--compile</tt> or <tt>--watch</tt>.
</td>
</tr>
<tr>
@@ -231,7 +234,7 @@ sudo bin/cake install</pre>
</td>
</tr>
<tr>
<td><code>-n, --no-wrap</code></td>
<td><code>--no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper.
(Used for CoffeeScript as a Node.js module.)
@@ -245,7 +248,7 @@ sudo bin/cake install</pre>
</td>
</tr>
<tr>
<td><code>-tr, --tree</code></td>
<td><code>-n, --nodes</code></td>
<td>
Instead of compiling the CoffeeScript, just lex and parse it, and print
out the parse tree:
@@ -266,7 +269,7 @@ Expressions
</p>
<pre>
coffee path/to/script.coffee
coffee -c path/to/script.coffee
coffee --interactive
coffee --watch --lint experimental.coffee
coffee --print app/scripts/*.coffee > concatenation.js</pre>
@@ -589,8 +592,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<p>
<span id="inheritance" class="bookmark"></span>
<b class="header">Inheritance, and Calling Super from a Subclass</b>
<span id="classes" class="bookmark"></span>
<b class="header">Classes, Inheritance, and Super</b>
JavaScript's prototypal inheritance has always been a bit of a
brain-bender, with a whole family tree of libraries that provide a cleaner
syntax for classical inheritance on top of JavaScript's prototypes:
@@ -604,12 +607,20 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
set the prototype chain.
</p>
<p>
CoffeeScript provides <tt>extends</tt>
to help with prototype setup, <tt>::</tt> for quick access to an
object's prototype, and converts <tt>super()</tt> into a call against
the immediate ancestor's method of the same name.
Instead of repetitively attaching functions to a prototype, CoffeeScript
provides a basic <tt>class</tt> structure that allows you to name your class,
set the superclass, assign prototypal properties, and define the constructor,
in a single assignable expression.
</p>
<%= code_for('super', true) %>
<%= code_for('classes', true) %>
<p>
If structuring your prototypes classically isn't your cup of tea, CoffeeScript
provides a couple of lower-level conveniences. The <tt>extends</tt> operator
helps with proper prototype setup, as seen above, <tt>::</tt> gives you
quick access to an object's prototype, and <tt>super()</tt>
is converted into a call against the immediate ancestor's method of the same name.
</p>
<%= code_for('prototypes', '"one_two".dasherize()') %>
<p>
<span id="pattern_matching" class="bookmark"></span>
@@ -734,20 +745,20 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<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.
tags. The source includes a compressed and minified version of the compiler
(<a href="extras/coffee-script.js">Download current version here, 43k when gzipped</a>)
as <tt>extras/coffee-script.js</tt>. Include this file on a page with
inline CoffeeScript tags, 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.
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
@@ -773,7 +784,9 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
Bugs reports, feature requests, and general discussion all belong here.
</li>
<li>
If you'd like to chat, stop by <tt>#coffeescript</tt> on Freenode.
If you'd like to chat, stop by <tt>#coffeescript</tt> on Freenode in the
IRC client of your choice, or on
<a href="http://webchat.freenode.net/">webchat.freenode.net</a>.
</li>
</ul>
@@ -782,6 +795,16 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
Change Log
</h2>
<p>
<b class="header" style="margin-top: 20px;">0.5.3</b>
CoffeeScript now has a syntax for defining classes. Many of the core
components (Nodes, Lexer, Rewriter, Scope, Optparse) are using them.
Cakefiles can use <tt>optparse.coffee</tt> to define options for tasks.
<tt>--run</tt> is now the default flag for the <tt>coffee</tt> command,
use <tt>--compile</tt> to save JavaScripts. Bugfix for an ambiguity between
RegExp literals and chained divisions.
</p>
<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
@@ -789,7 +812,7 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
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>
@@ -963,27 +986,37 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<script type="text/coffeescript">
window.repl_compile: ->
# Set up the compilation function, to run when you stop typing.
compile_source: ->
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
$('#repl_results').text window.compiled_js
$('#error').hide();
catch error
$('#error').text(error.message).show();
window.repl_run: ->
# Listen for keypresses and recompile.
$('#repl_source').keyup -> compile_source()
# Eval the compiled js.
$('button.run').click ->
try
eval window.compiled_js
catch error then alert error
nav: $('.navigation')
current_nav: null
# Helper to hide the menus.
close_menus: ->
current_nav.removeClass 'active' if current_nav
if current_nav
current_nav.removeClass 'active'
document.body.className: 'minimized'
current_nav: null
nav.click (e) ->
# Bind navigation buttons to open the menus.
$('.navigation').click (e) ->
return if e.target.tagName.toLowerCase() is 'a'
if this isnt (current_nav and current_nav[0])
close_menus();
@@ -993,6 +1026,14 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
$(document.body).click -> close_menus()
$('.navigation .full_screen').click ->
document.body.className: 'full_screen'
$('.navigation .minimize').click ->
document.body.className: 'minimized'
compile_source()
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>

View File

@@ -1,21 +1,21 @@
(function(){
var _a, _b, _c, _d, _e, _f, _g, food, lunch, roid, roid2;
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, food, lunch, roid, roid2;
// Eat lunch.
lunch = (function() {
_a = []; _b = ['toast', 'cheese', 'wine'];
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
food = _b[_c];
_a.push(eat(food));
}
return _a;
}).call(this);
// Naive collision detection.
_d = asteroids;
for (_e = 0; _e < _d.length; _e++) {
roid = _d[_e];
_f = asteroids;
for (_g = 0; _g < _f.length; _g++) {
roid2 = _f[_g];
_e = asteroids;
for (_f = 0, _g = _e.length; _f < _g; _f++) {
roid = _e[_f];
_h = asteroids;
for (_i = 0, _j = _h.length; _i < _j; _i++) {
roid2 = _h[_i];
if (roid !== roid2) {
if (roid.overlaps(roid2)) {
roid.explode();

View File

@@ -1,9 +1,9 @@
(function(){
process.mixin(require('assert'));
task('test', 'run each of the unit tests', function() {
var _a, _b, _c, test;
var _a, _b, _c, _d, test;
_a = []; _b = test_files;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
test = _b[_c];
_a.push(fs.readFile(test, function(err, code) {
return eval(coffee.compile(code));

View File

@@ -1,5 +1,12 @@
(function(){
var Animal, Horse, Snake, _a, _b, sam, tom;
var Animal, Horse, Snake, sam, tom;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
};
Animal = function Animal() { };
Animal.prototype.move = function move(meters) {
return alert(this.name + " moved " + meters + "m.");
@@ -8,11 +15,7 @@
this.name = name;
return this;
};
_a = function(){};
_a.prototype = Animal.prototype;
Snake.__superClass__ = Animal.prototype;
Snake.prototype = new _a();
Snake.prototype.constructor = Snake;
__extends(Snake, Animal);
Snake.prototype.move = function move() {
alert("Slithering...");
return Snake.__superClass__.move.call(this, 5);
@@ -21,11 +24,7 @@
this.name = name;
return this;
};
_b = function(){};
_b.prototype = Animal.prototype;
Horse.__superClass__ = Animal.prototype;
Horse.prototype = new _b();
Horse.prototype.constructor = Horse;
__extends(Horse, Animal);
Horse.prototype.move = function move() {
alert("Galloping...");
return Horse.__superClass__.move.call(this, 45);

View File

@@ -1,5 +1,5 @@
(function(){
var _a, _b, _c, cubed_list, list, math, num, number, opposite_day, race, square;
var _a, _b, _c, _d, cubed_list, list, math, num, number, opposite_day, race, square;
// Assignment:
number = 42;
opposite_day = true;
@@ -34,7 +34,7 @@
// Array comprehensions:
cubed_list = (function() {
_a = []; _b = list;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
num = _b[_c];
_a.push(math.cube(num));
}

5
documentation/js/prototypes.js vendored Normal file
View File

@@ -0,0 +1,5 @@
(function(){
String.prototype.dasherize = function dasherize() {
return this.replace(/_/g, "-");
};
})();

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));

View File

@@ -140,24 +140,28 @@ sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna
aliquam erat volutpat. Ut wisi enim ad."
# Inheritance and calling super.
Animal: ->
Animal::move: (meters) ->
alert(this.name + " moved " + meters + "m.")
class Animal
move: (meters) ->
alert this.name + " moved " + meters + "m."
Snake: (name) -> this.name: name
Snake extends Animal
Snake::move: ->
alert('Slithering...')
super(5)
class Snake extends Animal
constructor: (name) ->
@name: name
Horse: (name) -> this.name: name
Horse extends Animal
Horse::move: ->
alert('Galloping...')
super(45)
move: ->
alert 'Slithering...'
super 5
sam: new Snake("Sammy the Snake")
tom: new Horse("Tommy the Horse")
class Horse extends Animal
constructor: (name) ->
@name: name
move: ->
alert 'Galloping...'
super 45
sam: new Snake "Sammy the Snake"
tom: new Horse "Tommy the Horse"
sam.move()
tom.move()

View File

@@ -1,90 +1,92 @@
# "Classic" linked list implementation that doesn't keep track of its size.
LinkedList: ->
this._head: null # Pointer to the first item in the list.
class LinkedList
constructor: ->
this._head: null # Pointer to the first item in the list.
# Appends some data to the end of the list. This method traverses the existing
# list and places the value at the end in a new node.
LinkedList::add: (data) ->
# Appends some data to the end of the list. This method traverses the existing
# list and places the value at the end in a new node.
add: (data) ->
# Create a new node object to wrap the data.
node: {data: data, next: null}
# Create a new node object to wrap the data.
node: {data: data, next: null}
current: this._head ||= node
current: this._head ||= node
if this._head isnt node
current: current.next while current.next
current.next: node
if this._head isnt node
current: current.next while current.next
current.next: node
this
this
# Retrieves the data at the given position in the list.
LinkedList::item: (index) ->
# Retrieves the data at the given position in the list.
item: (index) ->
# Check for out-of-bounds values.
return null if index < 0
# Check for out-of-bounds values.
return null if index < 0
current: this._head or null
i: -1
current: this._head or null
i: -1
# Advance through the list.
current: current.next while current and index > (i += 1)
# Advance through the list.
current: current.next while current and index > (i += 1)
# Return null if we've reached the end.
current and current.data
# Return null if we've reached the end.
current and current.data
# Remove the item from the given location in the list.
LinkedList::remove: (index) ->
# Remove the item from the given location in the list.
remove: (index) ->
# Check for out-of-bounds values.
return null if index < 0
# Check for out-of-bounds values.
return null if index < 0
current: this._head or null
i: -1
current: this._head or null
i: -1
# Special case: removing the first item.
if index is 0
this._head: current.next
else
# Special case: removing the first item.
if index is 0
this._head: current.next
else
# Find the right location.
[previous, current]: [current, current.next] while index > (i += 1)
# Find the right location.
[previous, current]: [current, current.next] while index > (i += 1)
# Skip over the item to remove.
previous.next: current.next
# Skip over the item to remove.
previous.next: current.next
# Return the value.
current and current.data
# Return the value.
current and current.data
# Calculate the number of items in the list.
LinkedList::size: ->
current: this._head
count: 0
# Calculate the number of items in the list.
size: ->
current: this._head
count: 0
while current
count += 1
current: current.next
while current
count += 1
current: current.next
count
count
# Convert the list into an array.
LinkedList::toArray: ->
result: []
current: this._head
# Convert the list into an array.
toArray: ->
result: []
current: this._head
while current
result.push(current.data)
current: current.next
while current
result.push(current.data)
current: current.next
result
result
# The string representation of the linked list.
LinkedList::toString: -> this.toArray().toString()
# The string representation of the linked list.
toString: -> this.toArray().toString()
# Tests.

View File

@@ -53,16 +53,16 @@ for key, val of {dog: 'canine', cat: 'feline', fox: 'vulpine'}
# Person print = ():
# ('My name is ', /name, '.') join print.
Person: ->
Person::print: ->
print('My name is ' + this.name + '.')
class Person
print: ->
print 'My name is ' + this.name + '.'
# p = Person ()
# p /name string print
p: new Person()
print(p.name)
print p.name
# Policeman = Person class (rank): /rank = rank.
@@ -71,12 +71,13 @@ print(p.name)
#
# Policeman ('Constable') print
Policeman: (rank) -> this.rank: rank
Policeman extends Person
Policeman::print: ->
print('My name is ' + this.name + " and I'm a " + this.rank + '.')
class Policeman extends Person
constructor: (rank) ->
@rank: rank
print: ->
print 'My name is ' + this.name + " and I'm a " + this.rank + '.'
print(new Policeman('Constable'))
print new Policeman 'Constable'
# app = [window (width=200, height=400)

View File

@@ -4,9 +4,9 @@ http: require 'http'
server: http.createServer (req, res) ->
res.sendHeader 200, {'Content-Type': 'text/plain'}
res.sendBody 'Hello, World!'
res.finish()
res.write 'Hello, World!'
res.close()
server.listen 3000
puts "Server running at http://localhost:3000/"
puts "Server running at http://localhost:3000/"

View File

@@ -288,7 +288,7 @@
</dict>
<dict>
<key>match</key>
<string>\b(super|this|extends)\b</string>
<string>\b(super|this|extends|class)\b</string>
<key>name</key>
<string>variable.language.coffee</string>
</dict>

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,9 @@
<title>CoffeeScript</title>
<link rel="stylesheet" type="text/css" href="documentation/css/docs.css" />
<link rel="stylesheet" type="text/css" href="documentation/css/idle.css" />
<link rel="shortcut icon" href="documentation/images/favicon.ico" />
</head>
<body>
<body class="minimized">
<div id="fadeout"></div>
@@ -37,7 +38,7 @@
<a href="#slice_splice">Array Slicing and Splicing with Ranges</a>
<a href="#expressions">Everything is an Expression</a>
<a href="#existence">The Existential Operator</a>
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a>
<a href="#classes">Classes, Inheritance, and Super</a>
<a href="#pattern_matching">Pattern Matching</a>
<a href="#fat_arrow">Function Binding</a>
<a href="#embedded">Embedded JavaScript</a>
@@ -57,17 +58,19 @@
</div>
<div class="contents repl_wrapper">
<div class="code">
<textarea id="repl_source">reverse: (string) ->
<div id="repl_source_wrap"><textarea id="repl_source">reverse: (string) ->
string.split('').reverse().join ''
alert reverse '!tpircseeffoC'</textarea>
alert reverse '!tpircseeffoC'</textarea></div>
<pre id="repl_results"></pre>
<button class="compile" onclick="javascript: repl_compile();">compile</button>
<button class="run" onclick="javascript: repl_run();">run</button>
<button class="full_screen">go full screen</button>
<button class="minimize">minimize</button>
<button class="run">run</button>
<br class="clear" />
</div>
</div>
</div>
<div id="error" style="display:none;"></div>
</div>
<div class="container">
@@ -94,7 +97,7 @@ alert reverse '!tpircseeffoC'</textarea>
<p>
<b>Latest Version:</b>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>
</p>
<h2>
@@ -133,7 +136,7 @@ alert <span class="String"><span class="String">&quot;</span>I knew it!<span cla
<span class="Comment"><span class="Comment">#</span> Array comprehensions:</span>
<span class="FunctionName">cubed_list</span><span class="Keyword">:</span> math.cube num <span class="Keyword">for</span> num <span class="Keyword">in</span> list
</pre><pre class="idle"><span class="Storage">var</span> _a, _b, _c, cubed_list, list, math, num, number, opposite_day, race, square;
</pre><pre class="idle"><span class="Storage">var</span> _a, _b, _c, _d, cubed_list, list, math, num, number, opposite_day, race, square;
<span class="Comment"><span class="Comment">//</span> Assignment:</span>
number <span class="Keyword">=</span> <span class="Number">42</span>;
opposite_day <span class="Keyword">=</span> <span class="BuiltInConstant">true</span>;
@@ -168,13 +171,13 @@ race <span class="Keyword">=</span> <span class="Storage">function</span> <span
<span class="Comment"><span class="Comment">//</span> Array comprehensions:</span>
cubed_list <span class="Keyword">=</span> (<span class="Storage">function</span>() {
_a <span class="Keyword">=</span> []; _b <span class="Keyword">=</span> list;
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>; _c <span class="Keyword">&lt;</span> _b.<span class="LibraryConstant">length</span>; _c<span class="Keyword">++</span>) {
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>, _d <span class="Keyword">=</span> _b.<span class="LibraryConstant">length</span>; _c <span class="Keyword">&lt;</span> _d; _c<span class="Keyword">++</span>) {
num <span class="Keyword">=</span> _b[_c];
_a.<span class="LibraryFunction">push</span>(math.cube(num));
}
<span class="Keyword">return</span> _a;
}).<span class="LibraryFunction">call</span>(<span class="Variable">this</span>);
</pre><button onclick='javascript: var _a, _b, _c, cubed_list, list, math, num, number, opposite_day, race, square;
</pre><button onclick='javascript: var _a, _b, _c, _d, cubed_list, list, math, num, number, opposite_day, race, square;
// Assignment:
number = 42;
opposite_day = true;
@@ -209,7 +212,7 @@ if ((typeof elvis !== "undefined" && elvis !== null)) {
// Array comprehensions:
cubed_list = (function() {
_a = []; _b = list;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
num = _b[_c];
_a.push(math.cube(num));
}
@@ -250,7 +253,7 @@ cubed_list = (function() {
<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.2">0.5.2</a>.
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
@@ -259,33 +262,33 @@ cubed_list = (function() {
sudo bin/cake install</pre>
<p>
This provides the <tt>coffee</tt> command, which can
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
well as debug them. The <tt>coffee</tt>
command also provides direct evaluation and an interactive REPL.
This provides the <tt>coffee</tt> command, which will execute CoffeeScripts
under Node.js by default, but is also used to compile CoffeeScript
<tt>.coffee</tt> files into JavaScript, or to run an an interactive REPL.
When compiling to JavaScript, <tt>coffee</tt> writes the output
as <tt>.js</tt> files in the same directory by default, but output
can be customized with the following options:
</p>
<table>
<tr>
<td><code>-c, --compile</code></td>
<td>
Compile a <tt>.coffee</tt> script into a <tt>.js</tt> JavaScript file
of the same name.
</td>
</tr>
<tr>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session to try short snippets.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute a given CoffeeScript without saving the intermediate
JavaScript.
</td>
</tr>
<tr>
<td><code>-o, --output [DIR]</code></td>
<td>
Write out all compiled JavaScript files into the specified directory.
Use in conjunction with <tt>--compile</tt> or <tt>--watch</tt>.
</td>
</tr>
<tr>
@@ -328,7 +331,7 @@ sudo bin/cake install</pre>
</td>
</tr>
<tr>
<td><code>-n, --no-wrap</code></td>
<td><code>--no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper.
(Used for CoffeeScript as a Node.js module.)
@@ -342,7 +345,7 @@ sudo bin/cake install</pre>
</td>
</tr>
<tr>
<td><code>-tr, --tree</code></td>
<td><code>-n, --nodes</code></td>
<td>
Instead of compiling the CoffeeScript, just lex and parse it, and print
out the parse tree:
@@ -363,7 +366,7 @@ Expressions
</p>
<pre>
coffee path/to/script.coffee
coffee -c path/to/script.coffee
coffee --interactive
coffee --watch --lint experimental.coffee
coffee --print app/scripts/*.coffee > concatenation.js</pre>
@@ -817,29 +820,29 @@ One fell out and bumped his head.");
would use a loop, <b>each</b>/<b>forEach</b>, <b>map</b>, or <b>select</b>/<b>filter</b>.
</p>
<div class='code'><pre class="idle"><span class="Comment"><span class="Comment">#</span> Eat lunch.</span>
<span class="FunctionName">lunch</span><span class="Keyword">:</span> eat(food) <span class="Keyword">for</span> food <span class="Keyword">in</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>]
<span class="FunctionName">lunch</span><span class="Keyword">:</span> eat food <span class="Keyword">for</span> food <span class="Keyword">in</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>]
<span class="Comment"><span class="Comment">#</span> Naive collision detection.</span>
<span class="Keyword">for</span> roid <span class="Keyword">in</span> asteroids
<span class="Keyword">for</span> roid2 <span class="Keyword">in</span> asteroids <span class="Keyword">when</span> roid <span class="Keyword">isnt</span> roid2
roid.explode() <span class="Keyword">if</span> roid.overlaps roid2
</pre><pre class="idle"><span class="Storage">var</span> _a, _b, _c, _d, _e, _f, _g, food, lunch, roid, roid2;
</pre><pre class="idle"><span class="Storage">var</span> _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, food, lunch, roid, roid2;
<span class="Comment"><span class="Comment">//</span> Eat lunch.</span>
lunch <span class="Keyword">=</span> (<span class="Storage">function</span>() {
_a <span class="Keyword">=</span> []; _b <span class="Keyword">=</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>];
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>; _c <span class="Keyword">&lt;</span> _b.<span class="LibraryConstant">length</span>; _c<span class="Keyword">++</span>) {
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>, _d <span class="Keyword">=</span> _b.<span class="LibraryConstant">length</span>; _c <span class="Keyword">&lt;</span> _d; _c<span class="Keyword">++</span>) {
food <span class="Keyword">=</span> _b[_c];
_a.<span class="LibraryFunction">push</span>(eat(food));
}
<span class="Keyword">return</span> _a;
}).<span class="LibraryFunction">call</span>(<span class="Variable">this</span>);
<span class="Comment"><span class="Comment">//</span> Naive collision detection.</span>
_d <span class="Keyword">=</span> asteroids;
<span class="Keyword">for</span> (_e <span class="Keyword">=</span> <span class="Number">0</span>; _e <span class="Keyword">&lt;</span> _d.<span class="LibraryConstant">length</span>; _e<span class="Keyword">++</span>) {
roid <span class="Keyword">=</span> _d[_e];
_f <span class="Keyword">=</span> asteroids;
<span class="Keyword">for</span> (_g <span class="Keyword">=</span> <span class="Number">0</span>; _g <span class="Keyword">&lt;</span> _f.<span class="LibraryConstant">length</span>; _g<span class="Keyword">++</span>) {
roid2 <span class="Keyword">=</span> _f[_g];
_e <span class="Keyword">=</span> asteroids;
<span class="Keyword">for</span> (_f <span class="Keyword">=</span> <span class="Number">0</span>, _g <span class="Keyword">=</span> _e.<span class="LibraryConstant">length</span>; _f <span class="Keyword">&lt;</span> _g; _f<span class="Keyword">++</span>) {
roid <span class="Keyword">=</span> _e[_f];
_h <span class="Keyword">=</span> asteroids;
<span class="Keyword">for</span> (_i <span class="Keyword">=</span> <span class="Number">0</span>, _j <span class="Keyword">=</span> _h.<span class="LibraryConstant">length</span>; _i <span class="Keyword">&lt;</span> _j; _i<span class="Keyword">++</span>) {
roid2 <span class="Keyword">=</span> _h[_i];
<span class="Keyword">if</span> (roid <span class="Keyword">!</span><span class="Keyword">==</span> roid2) {
<span class="Keyword">if</span> (roid.overlaps(roid2)) {
roid.explode();
@@ -863,7 +866,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;
@@ -871,7 +874,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));
@@ -882,7 +885,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;
@@ -890,7 +893,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));
@@ -1153,8 +1156,8 @@ speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
</p>
<p>
<span id="inheritance" class="bookmark"></span>
<b class="header">Inheritance, and Calling Super from a Subclass</b>
<span id="classes" class="bookmark"></span>
<b class="header">Classes, Inheritance, and Super</b>
JavaScript's prototypal inheritance has always been a bit of a
brain-bender, with a whole family tree of libraries that provide a cleaner
syntax for classical inheritance on top of JavaScript's prototypes:
@@ -1168,35 +1171,30 @@ speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
set the prototype chain.
</p>
<p>
CoffeeScript provides <tt>extends</tt>
to help with prototype setup, <tt>::</tt> for quick access to an
object's prototype, and converts <tt>super()</tt> into a call against
the immediate ancestor's method of the same name.
Instead of repetitively attaching functions to a prototype, CoffeeScript
provides a basic <tt>class</tt> structure that allows you to name your class,
set the superclass, assign prototypal properties, and define the constructor,
in a single assignable expression.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">Animal</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<div class='code'><pre class="idle"><span class="Variable">class</span> Animal
<span class="FunctionName">move</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">meters</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
alert <span class="Variable">@name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>
<span class="FunctionName">Animal::move</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">meters</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
alert <span class="Variable">@name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>
<span class="Variable">class</span> Snake <span class="Variable">extends</span> Animal
<span class="FunctionName">constructor</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="Variable">@name</span><span class="Keyword">:</span> name
<span class="FunctionName">Snake</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="Variable">@name</span><span class="Keyword">:</span> name
<span class="Variable">this</span>
<span class="FunctionName">move</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
alert <span class="String"><span class="String">&quot;</span>Slithering...<span class="String">&quot;</span></span>
<span class="Variable">super</span> <span class="Number">5</span>
Snake <span class="Variable">extends</span> Animal
<span class="Variable">class</span> Horse <span class="Variable">extends</span> Animal
<span class="FunctionName">constructor</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="Variable">@name</span><span class="Keyword">:</span> name
<span class="FunctionName">Snake::move</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
alert <span class="String"><span class="String">&quot;</span>Slithering...<span class="String">&quot;</span></span>
<span class="Variable">super</span> <span class="Number">5</span>
<span class="FunctionName">Horse</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="Variable">@name</span><span class="Keyword">:</span> name
<span class="Variable">this</span>
Horse <span class="Variable">extends</span> Animal
<span class="FunctionName">Horse::move</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
alert <span class="String"><span class="String">&quot;</span>Galloping...<span class="String">&quot;</span></span>
<span class="Variable">super</span> <span class="Number">45</span>
<span class="FunctionName">move</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
alert <span class="String"><span class="String">&quot;</span>Galloping...<span class="String">&quot;</span></span>
<span class="Variable">super</span> <span class="Number">45</span>
<span class="FunctionName">sam</span><span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Snake</span> <span class="String"><span class="String">&quot;</span>Sammy the Python<span class="String">&quot;</span></span>
<span class="FunctionName">tom</span><span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Horse</span> <span class="String"><span class="String">&quot;</span>Tommy the Palomino<span class="String">&quot;</span></span>
@@ -1207,7 +1205,14 @@ tom.move()
</pre><pre class="idle"><span class="Storage">var</span> Animal, Horse, Snake, _a, _b, sam, tom;
</pre><pre class="idle"><span class="Storage">var</span> Animal, Horse, Snake, sam, tom;
<span class="Storage">var</span> <span class="FunctionName">__extends</span> = <span class="Storage">function</span>(<span class="FunctionArgument">child, parent</span>) {
<span class="Storage">var</span> <span class="FunctionName">ctor</span> = <span class="Storage">function</span>(){ };
<span class="LibraryClassType">ctor</span>.<span class="LibraryConstant">prototype</span> = parent.<span class="LibraryConstant">prototype</span>;
child.__superClass__ <span class="Keyword">=</span> parent.<span class="LibraryConstant">prototype</span>;
<span class="LibraryClassType">child</span>.<span class="LibraryConstant">prototype</span> = <span class="Keyword">new</span> <span class="TypeName">ctor</span>();
<span class="LibraryClassType">child</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">constructor</span> = child;
};
Animal <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Animal</span>() { };
<span class="LibraryClassType">Animal</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span> <span class="FunctionName">move</span>(<span class="FunctionArgument">meters</span>) {
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>);
@@ -1216,11 +1221,7 @@ Snake <span class="Keyword">=</span> <span class="Storage">function</span> <span
<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">=</span> name;
<span class="Keyword">return</span> <span class="Variable">this</span>;
};
<span class="FunctionName">_a</span> = <span class="Storage">function</span>(){};
<span class="LibraryClassType">_a</span>.<span class="LibraryConstant">prototype</span> = Animal.<span class="LibraryConstant">prototype</span>;
Snake.__superClass__ <span class="Keyword">=</span> Animal.<span class="LibraryConstant">prototype</span>;
<span class="LibraryClassType">Snake</span>.<span class="LibraryConstant">prototype</span> = <span class="Keyword">new</span> <span class="TypeName">_a</span>();
<span class="LibraryClassType">Snake</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">constructor</span> = Snake;
__extends(Snake, Animal);
<span class="LibraryClassType">Snake</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span> <span class="FunctionName">move</span>() {
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>Slithering...<span class="String">&quot;</span></span>);
<span class="Keyword">return</span> Snake.__superClass__.move.<span class="LibraryFunction">call</span>(<span class="Variable">this</span>, <span class="Number">5</span>);
@@ -1229,11 +1230,7 @@ Horse <span class="Keyword">=</span> <span class="Storage">function</span> <span
<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">=</span> name;
<span class="Keyword">return</span> <span class="Variable">this</span>;
};
<span class="FunctionName">_b</span> = <span class="Storage">function</span>(){};
<span class="LibraryClassType">_b</span>.<span class="LibraryConstant">prototype</span> = Animal.<span class="LibraryConstant">prototype</span>;
Horse.__superClass__ <span class="Keyword">=</span> Animal.<span class="LibraryConstant">prototype</span>;
<span class="LibraryClassType">Horse</span>.<span class="LibraryConstant">prototype</span> = <span class="Keyword">new</span> <span class="TypeName">_b</span>();
<span class="LibraryClassType">Horse</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">constructor</span> = Horse;
__extends(Horse, Animal);
<span class="LibraryClassType">Horse</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span> <span class="FunctionName">move</span>() {
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">&quot;</span>Galloping...<span class="String">&quot;</span></span>);
<span class="Keyword">return</span> Horse.__superClass__.move.<span class="LibraryFunction">call</span>(<span class="Variable">this</span>, <span class="Number">45</span>);
@@ -1242,7 +1239,14 @@ sam <span class="Keyword">=</span> <span class="Keyword">new</span> <span class=
tom <span class="Keyword">=</span> <span class="Keyword">new</span> <span class="TypeName">Horse</span>(<span class="String"><span class="String">&quot;</span>Tommy the Palomino<span class="String">&quot;</span></span>);
sam.move();
tom.move();
</pre><button onclick='javascript: var Animal, Horse, Snake, _a, _b, sam, tom;
</pre><button onclick='javascript: var Animal, Horse, Snake, sam, tom;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
};
Animal = function Animal() { };
Animal.prototype.move = function move(meters) {
return alert(this.name + " moved " + meters + "m.");
@@ -1251,11 +1255,7 @@ Snake = function Snake(name) {
this.name = name;
return this;
};
_a = function(){};
_a.prototype = Animal.prototype;
Snake.__superClass__ = Animal.prototype;
Snake.prototype = new _a();
Snake.prototype.constructor = Snake;
__extends(Snake, Animal);
Snake.prototype.move = function move() {
alert("Slithering...");
return Snake.__superClass__.move.call(this, 5);
@@ -1264,11 +1264,7 @@ Horse = function Horse(name) {
this.name = name;
return this;
};
_b = function(){};
_b.prototype = Animal.prototype;
Horse.__superClass__ = Animal.prototype;
Horse.prototype = new _b();
Horse.prototype.constructor = Horse;
__extends(Horse, Animal);
Horse.prototype.move = function move() {
alert("Galloping...");
return Horse.__superClass__.move.call(this, 45);
@@ -1278,6 +1274,22 @@ tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
;'>run</button><br class='clear' /></div>
<p>
If structuring your prototypes classically isn't your cup of tea, CoffeeScript
provides a couple of lower-level conveniences. The <tt>extends</tt> operator
helps with proper prototype setup, as seen above, <tt>::</tt> gives you
quick access to an object's prototype, and <tt>super()</tt>
is converted into a call against the immediate ancestor's method of the same name.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">String::dasherize</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="Variable">this</span>.replace(<span class="String"><span class="String">/</span>_<span class="String">/</span>g</span>, <span class="String"><span class="String">&quot;</span>-<span class="String">&quot;</span></span>)
</pre><pre class="idle"><span class="LibraryClassType">String</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">dasherize</span> = <span class="Storage">function</span> <span class="FunctionName">dasherize</span>() {
<span class="Keyword">return</span> <span class="Variable">this</span>.<span class="LibraryFunction">replace</span>(<span class="String"><span class="String">/</span>_<span class="String">/</span>g</span>, <span class="String"><span class="String">&quot;</span>-<span class="String">&quot;</span></span>);
};
</pre><button onclick='javascript: String.prototype.dasherize = function dasherize() {
return this.replace(/_/g, "-");
};
;alert("one_two".dasherize());'>run: "one_two".dasherize()</button><br class='clear' /></div>
<p>
<span id="pattern_matching" class="bookmark"></span>
@@ -1594,9 +1606,9 @@ task <span class="String"><span class="String">'</span>test<span class="String">
fs.readFile test, <span class="FunctionArgument">(</span><span class="FunctionArgument">err, code</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> eval coffee.compile code
</pre><pre class="idle">process.mixin(require(<span class="String"><span class="String">'</span>assert<span class="String">'</span></span>));
task(<span class="String"><span class="String">'</span>test<span class="String">'</span></span>, <span class="String"><span class="String">'</span>run each of the unit tests<span class="String">'</span></span>, <span class="Storage">function</span>() {
<span class="Storage">var</span> _a, _b, _c, test;
<span class="Storage">var</span> _a, _b, _c, _d, test;
_a <span class="Keyword">=</span> []; _b <span class="Keyword">=</span> test_files;
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>; _c <span class="Keyword">&lt;</span> _b.<span class="LibraryConstant">length</span>; _c<span class="Keyword">++</span>) {
<span class="Keyword">for</span> (_c <span class="Keyword">=</span> <span class="Number">0</span>, _d <span class="Keyword">=</span> _b.<span class="LibraryConstant">length</span>; _c <span class="Keyword">&lt;</span> _d; _c<span class="Keyword">++</span>) {
test <span class="Keyword">=</span> _b[_c];
_a.<span class="LibraryFunction">push</span>(fs.readFile(test, <span class="Storage">function</span>(err, code) {
<span class="Keyword">return</span> <span class="LibraryFunction">eval</span>(coffee.<span class="LibraryFunction">compile</span>(code));
@@ -1614,20 +1626,20 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
<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.
tags. The source includes a compressed and minified version of the compiler
(<a href="extras/coffee-script.js">Download current version here, 43k when gzipped</a>)
as <tt>extras/coffee-script.js</tt>. Include this file on a page with
inline CoffeeScript tags, 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.
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
@@ -1653,7 +1665,9 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
Bugs reports, feature requests, and general discussion all belong here.
</li>
<li>
If you'd like to chat, stop by <tt>#coffeescript</tt> on Freenode.
If you'd like to chat, stop by <tt>#coffeescript</tt> on Freenode in the
IRC client of your choice, or on
<a href="http://webchat.freenode.net/">webchat.freenode.net</a>.
</li>
</ul>
@@ -1662,6 +1676,16 @@ 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.3</b>
CoffeeScript now has a syntax for defining classes. Many of the core
components (Nodes, Lexer, Rewriter, Scope, Optparse) are using them.
Cakefiles can use <tt>optparse.coffee</tt> to define options for tasks.
<tt>--run</tt> is now the default flag for the <tt>coffee</tt> command,
use <tt>--compile</tt> to save JavaScripts. Bugfix for an ambiguity between
RegExp literals and chained divisions.
</p>
<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
@@ -1669,7 +1693,7 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
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>
@@ -1843,27 +1867,37 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
<script type="text/coffeescript">
window.repl_compile: ->
# Set up the compilation function, to run when you stop typing.
compile_source: ->
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
$('#repl_results').text window.compiled_js
$('#error').hide();
catch error
$('#error').text(error.message).show();
window.repl_run: ->
# Listen for keypresses and recompile.
$('#repl_source').keyup -> compile_source()
# Eval the compiled js.
$('button.run').click ->
try
eval window.compiled_js
catch error then alert error
nav: $('.navigation')
current_nav: null
# Helper to hide the menus.
close_menus: ->
current_nav.removeClass 'active' if current_nav
if current_nav
current_nav.removeClass 'active'
document.body.className: 'minimized'
current_nav: null
nav.click (e) ->
# Bind navigation buttons to open the menus.
$('.navigation').click (e) ->
return if e.target.tagName.toLowerCase() is 'a'
if this isnt (current_nav and current_nav[0])
close_menus();
@@ -1873,6 +1907,14 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
$(document.body).click -> close_menus()
$('.navigation .full_screen').click ->
document.body.className: 'full_screen'
$('.navigation .minimize').click ->
document.body.className: 'minimized'
compile_source()
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env node
process.mixin(require('sys'));
require.paths.unshift('/usr/local/lib/coffee-script/lib');
require('cake').run();

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env node
process.mixin(require('sys'));
require.paths.unshift('/usr/local/lib/coffee-script/lib');
require('command_line').run();

View File

@@ -1,16 +1,18 @@
(function(){
var coffee, fs, no_such_task, path, print_tasks, tasks;
var coffee, fs, no_such_task, oparse, options, optparse, path, print_tasks, switches, tasks;
var __hasProp = Object.prototype.hasOwnProperty;
// `cake` is a simplified version of Make (Rake, Jake) for CoffeeScript.
// You define tasks with names and descriptions in a Cakefile, and can call them
// from the command line, or invoke them from other tasks.
fs = require('fs');
path = require('path');
coffee = require('coffee-script');
optparse = require('optparse');
tasks = {};
no_such_task = function no_such_task(task) {
process.stdio.writeError('No such task: "' + task + '"\n');
return process.exit(1);
};
// Mixin the Cake functionality.
options = {};
switches = [];
oparse = null;
// Mixin the top-level Cake functions for Cakefiles to use.
process.mixin({
// Define a task with a name, a description, and the action itself.
task: function task(name, description, action) {
@@ -20,61 +22,65 @@
action: action
};
},
// Define an option that the Cakefile accepts.
option: function option(letter, flag, description) {
return switches.push([letter, flag, description]);
},
// Invoke another task in the Cakefile.
invoke: function invoke(name) {
if (!(tasks[name])) {
no_such_task(name);
}
return tasks[name].action();
return tasks[name].action(options);
}
});
// Display the list of Cake tasks.
print_tasks = function print_tasks() {
var _a, _b, _c, _d, _e, _f, _g, i, name, spaces, task;
_a = []; _b = tasks;
for (name in _b) { if (__hasProp.call(_b, name)) {
task = _b[name];
_a.push((function() {
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++) {
_c.push(' ');
}
return _c;
}).call(this).join('') : '';
return puts("cake " + name + spaces + ' # ' + task.description);
}).call(this));
}}
return _a;
};
// Running `cake` runs the tasks you pass asynchronously (node-style), or
// prints them out, with no arguments.
exports.run = function run() {
return path.exists('Cakefile', function(exists) {
var args;
var _a, _b, _c, _d, arg, args;
if (!(exists)) {
throw new Error('Cakefile not found in ' + process.cwd());
}
args = process.ARGV.slice(2, process.ARGV.length);
return fs.readFile('Cakefile', function(err, source) {
var _a, _b, _c, arg;
eval(coffee.compile(source));
if (!(args.length)) {
return print_tasks();
}
_a = []; _b = args;
for (_c = 0; _c < _b.length; _c++) {
arg = _b[_c];
_a.push((function() {
if (!(tasks[arg])) {
no_such_task(arg);
}
return tasks[arg].action();
}).call(this));
}
return _a;
});
eval(coffee.compile(fs.readFileSync('Cakefile')));
oparse = new optparse.OptionParser(switches);
if (!(args.length)) {
return print_tasks();
}
options = oparse.parse(args);
_a = []; _b = options.arguments;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
arg = _b[_c];
_a.push(invoke(arg));
}
return _a;
});
};
// Display the list of Cake tasks.
print_tasks = function print_tasks() {
var _a, _b, _c, _d, _e, _f, i, name, spaces, task;
puts('');
_a = tasks;
for (name in _a) { if (__hasProp.call(_a, name)) {
task = _a[name];
spaces = 20 - name.length;
spaces = spaces > 0 ? (function() {
_b = []; _e = 0; _f = spaces;
for (_d = 0, i = _e; (_e <= _f ? i <= _f : i >= _f); (_e <= _f ? i += 1 : i -= 1), _d++) {
_b.push(' ');
}
return _b;
}).call(this).join('') : '';
puts("cake " + name + spaces + ' # ' + task.description);
}}
if (switches.length) {
return puts('\n' + oparse.help() + '\n');
}
};
// Print an error and exit when attempting to all an undefined task.
no_such_task = function no_such_task(task) {
process.stdio.writeError('No such task: "' + task + '"\n');
return process.exit(1);
};
})();

View File

@@ -1,5 +1,5 @@
(function(){
var _a, _b, lexer, parser, path, tag;
var lexer, parser, path, process_scripts;
// Set up for both the browser and the server.
if ((typeof process !== "undefined" && process !== null)) {
process.mixin(require('nodes'));
@@ -32,26 +32,37 @@
return this.pos;
}
};
exports.VERSION = '0.5.2';
exports.VERSION = '0.5.3';
// Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile = function compile(code, options) {
return (parser.parse(lexer.tokenize(code))).compile(options);
};
// Just the tokens.
exports.tokenize = function tokenize(code) {
exports.tokens = function tokens(code) {
return lexer.tokenize(code);
};
// Just the nodes.
exports.tree = function tree(code) {
exports.nodes = function nodes(code) {
return parser.parse(lexer.tokenize(code));
};
// 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;
process_scripts = function process_scripts() {
var _a, _b, _c, _d, tag;
_a = []; _b = document.getElementsByTagName('script');
for (_c = 0, _d = _b.length; _c < _d; _c++) {
tag = _b[_c];
if (tag.type === 'text/coffeescript') {
_a.push(eval(exports.compile(tag.innerHTML)));
}
}
return _a;
};
if (window.addEventListener) {
window.addEventListener('load', process_scripts, false);
} else if (window.attachEvent) {
window.attachEvent('onload', process_scripts);
}
}
})();

View File

@@ -5,7 +5,7 @@
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'], ['-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']];
SWITCHES = [['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-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'], ['--no-wrap', 'compile without the top-level function wrapper'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-n', '--nodes', 'print the parse tree that Jison produces'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
options = {};
sources = [];
option_parser = null;
@@ -57,14 +57,19 @@
// Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
// or JSLint results.
compile_scripts = function compile_scripts() {
var _a, _b, _c, compile, source;
var _a, _b, _c, _d, compile, source;
compile = function compile(source) {
return fs.readFile(source, function(err, code) {
return compile_script(source, code);
return path.exists(source, function(exists) {
if (!(exists)) {
throw new Error('File not found: ' + source);
}
return fs.readFile(source, function(err, code) {
return compile_script(source, code);
});
});
};
_a = []; _b = sources;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
source = _b[_c];
_a.push(compile(source));
}
@@ -77,19 +82,19 @@
o = options;
try {
if (o.tokens) {
return print_tokens(CoffeeScript.tokenize(code));
} else if (o.tree) {
return puts(CoffeeScript.tree(code).toString());
return print_tokens(CoffeeScript.tokens(code));
} else if (o.nodes) {
return puts(CoffeeScript.nodes(code).toString());
} else {
js = CoffeeScript.compile(code, compile_options());
if (o.run) {
return eval(js);
if (o.compile) {
return write_js(source, js);
} else if (o.lint) {
return lint(js);
} else if (o.print || o.eval) {
return print(js);
} else {
return write_js(source, js);
return eval(js);
}
}
} catch (err) {
@@ -117,7 +122,7 @@
// Watch a list of source CoffeeScript files, recompiling them every time the
// files are updated.
watch_scripts = function watch_scripts() {
var _a, _b, _c, source, watch;
var _a, _b, _c, _d, source, watch;
watch = function watch(source) {
return process.watchFile(source, {
persistent: true,
@@ -132,7 +137,7 @@
});
};
_a = []; _b = sources;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
source = _b[_c];
_a.push(watch(source));
}
@@ -165,10 +170,10 @@
};
// Pretty-print a token stream.
print_tokens = function print_tokens(tokens) {
var _a, _b, _c, strings, token;
var _a, _b, _c, _d, strings, token;
strings = (function() {
_a = []; _b = tokens;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
token = _b[_c];
_a.push('[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']');
}

View File

@@ -1,5 +1,5 @@
(function(){
var Parser, _a, _b, _c, _d, _e, _f, bnf, grammar, name, non_terminal, o, operators, option, part, tokens, unwrap;
var Parser, _a, _b, _c, _d, _e, _f, _g, _h, bnf, grammar, name, non_terminal, o, operators, option, part, tokens, unwrap;
var __hasProp = Object.prototype.hasOwnProperty;
Parser = require('jison').Parser;
// DSL ===================================================================
@@ -16,7 +16,7 @@
}
};
// Precedence ===========================================================
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
// Grammar ==============================================================
grammar = {
// All parsing will end in this rule, being the trunk of the AST.
@@ -41,7 +41,7 @@
],
// All types of expressions in our language. The basic unit of CoffeeScript
// is the expression.
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("Throw"), o("Return"), o("While"), o("For"), o("Switch"), o("Extends"), o("Splat"), o("Existence"), o("Comment")],
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("Throw"), o("Return"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence"), o("Comment")],
// A block of expressions. Note that the Rewriter will convert some postfix
// forms into blocks for us, by altering the token stream.
Block: [o("INDENT Expressions OUTDENT", function() {
@@ -292,6 +292,19 @@
// An object literal.
Object: [o("{ AssignList }", function() {
return new ObjectNode($2);
}), o("{ IndentedAssignList }", function() {
return new ObjectNode($2);
})
],
// A class literal.
Class: [o("CLASS Value", function() {
return new ClassNode($2);
}), o("CLASS Value EXTENDS Value", function() {
return new ClassNode($2, $4);
}), o("CLASS Value IndentedAssignList", function() {
return new ClassNode($2, null, $3);
}), o("CLASS Value EXTENDS Value IndentedAssignList", function() {
return new ClassNode($2, $4, $5);
})
],
// Assignment within an object literal (comma or newline separated).
@@ -305,7 +318,10 @@
return $1.concat([$3]);
}), o("AssignList , TERMINATOR AssignObj", function() {
return $1.concat([$4]);
}), o("INDENT AssignList OUTDENT", function() {
})
],
// A list of assignments in a block indentation.
IndentedAssignList: [o("INDENT AssignList OUTDENT", function() {
return $2;
})
],
@@ -537,12 +553,12 @@
non_terminal = _a[name];
bnf[name] = (function() {
_b = []; _c = non_terminal;
for (_d = 0; _d < _c.length; _d++) {
for (_d = 0, _e = _c.length; _d < _e; _d++) {
option = _c[_d];
_b.push((function() {
_e = option[0].split(" ");
for (_f = 0; _f < _e.length; _f++) {
part = _e[_f];
_f = option[0].split(" ");
for (_g = 0, _h = _f.length; _g < _h; _g++) {
part = _f[_g];
!grammar[part] ? tokens.push(part) : null;
}
name === "Root" ? (option[1] = "return " + option[1]) : null;

View File

@@ -1,25 +1,21 @@
(function(){
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex;
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE;
if ((typeof process !== "undefined" && process !== null)) {
Rewriter = require('./rewriter').Rewriter;
} else {
this.exports = this;
Rewriter = this.Rewriter;
}
// The lexer reads a stream of CoffeeScript and divvys it up into tagged
// tokens. A minor bit of the ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (lex = function lex() { });
// Constants ============================================================
// Keywords that CoffeScript shares in common with JS.
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends"];
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends", "class"];
// CoffeeScript-only keywords -- which we're more relaxed about allowing.
COFFEE_KEYWORDS = ["then", "unless", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "of", "by", "where", "when"];
// The list of keywords passed verbatim to the parser.
KEYWORDS = JS_KEYWORDS.concat(COFFEE_KEYWORDS);
// The list of keywords that are reserved by JavaScript, but not used, and aren't
// used by CoffeeScript. Using these will throw an error at compile time.
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "class", "const", "let", "debugger", "enum", "export", "import", "native"];
// The list of keywords that are reserved by JavaScript, but not used, or are
// used by CoffeeScript internally. Using these will throw an error.
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "const", "let", "debugger", "enum", "export", "import", "native", "__extends", "__hasProp"];
// JavaScript keywords and reserved words together, excluding CoffeeScript ones.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
// Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
@@ -32,7 +28,7 @@
WHITESPACE = /^([ \t]+)/;
COMMENT = /^(((\n?[ \t]*)?#[^\n]*)+)/;
CODE = /^((-|=)>)/;
REGEX = /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/;
REGEX = /^(\/(\S.*?)?([^\\]|\\\\)\/[imgy]{0,4})/;
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
LAST_DENTS = /\n([ \t]*)/g;
LAST_DENT = /\n([ \t]*)/;
@@ -47,7 +43,8 @@
// Tokens which a regular expression will never immediately follow, but which
// a division operator might.
// See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
NOT_REGEX = ['IDENTIFIER', 'NUMBER', 'REGEX', 'STRING', ')', '++', '--', ']', '}', 'FALSE', 'NULL', 'TRUE'];
// Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE'];
// Tokens which could legitimately be invoked or indexed.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@'];
// Tokens that indicate an access -- keywords immediately following will be
@@ -55,351 +52,358 @@
ACCESSORS = ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@'];
// Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
BEFORE_WHEN = ['INDENT', 'OUTDENT', 'TERMINATOR'];
// Scan by attempting to match tokens one character at a time. Slow and steady.
lex.prototype.tokenize = function tokenize(code) {
this.code = code;
// Cleanup code by remove extra line breaks, TODO: chomp
this.i = 0;
// Current character position we're parsing
this.line = 1;
// The current line.
this.indent = 0;
// The current indent level.
this.indents = [];
// The stack of all indent levels we are currently within.
this.tokens = [];
// Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while (this.i < this.code.length) {
this.chunk = this.code.slice(this.i);
this.extract_next_token();
}
this.close_indentation();
return (new Rewriter()).rewrite(this.tokens);
};
// At every position, run through this list of attempted matches,
// short-circuiting if any of them succeed.
lex.prototype.extract_next_token = function extract_next_token() {
if (this.identifier_token()) {
return null;
}
if (this.number_token()) {
return null;
}
if (this.heredoc_token()) {
return null;
}
if (this.string_token()) {
return null;
}
if (this.js_token()) {
return null;
}
if (this.regex_token()) {
return null;
}
if (this.indent_token()) {
return null;
}
if (this.comment_token()) {
return null;
}
if (this.whitespace_token()) {
return null;
}
return this.literal_token();
};
// Tokenizers ==========================================================
// Matches identifying literals: variables, keywords, method names, etc.
lex.prototype.identifier_token = function identifier_token() {
var id, tag;
if (!((id = this.match(IDENTIFIER, 1)))) {
return false;
}
if (this.value() === '::') {
this.tag(1, 'PROTOTYPE_ACCESS');
}
if (this.value() === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
this.tokens.splice(-2, 1);
} else {
this.tag(1, 'PROPERTY_ACCESS');
// The lexer reads a stream of CoffeeScript and divvys it up into tagged
// tokens. A minor bit of the ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (function() {
Lexer = function Lexer() { };
// Scan by attempting to match tokens one character at a time. Slow and steady.
Lexer.prototype.tokenize = function tokenize(code) {
this.code = code;
// Cleanup code by remove extra line breaks, TODO: chomp
this.i = 0;
// Current character position we're parsing
this.line = 1;
// The current line.
this.indent = 0;
// The current indent level.
this.indents = [];
// The stack of all indent levels we are currently within.
this.tokens = [];
// Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while (this.i < this.code.length) {
this.chunk = this.code.slice(this.i);
this.extract_next_token();
}
}
tag = 'IDENTIFIER';
if (KEYWORDS.indexOf(id) >= 0 && !((ACCESSORS.indexOf(this.tag()) >= 0) && !this.prev().spaced)) {
tag = id.toUpperCase();
}
if (RESERVED.indexOf(id) >= 0) {
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + this.line);
}
if (tag === 'WHEN' && BEFORE_WHEN.indexOf(this.tag()) >= 0) {
tag = 'LEADING_WHEN';
}
this.token(tag, id);
this.i += id.length;
return true;
};
// Matches numbers, including decimals, hex, and exponential notation.
lex.prototype.number_token = function number_token() {
var number;
if (!((number = this.match(NUMBER, 1)))) {
return false;
}
this.token('NUMBER', number);
this.i += number.length;
return true;
};
// Matches strings, including multi-line strings.
lex.prototype.string_token = function string_token() {
var escaped, string;
if (!((string = this.match(STRING, 1)))) {
return false;
}
escaped = string.replace(STRING_NEWLINES, " \\\n");
this.token('STRING', escaped);
this.line += this.count(string, "\n");
this.i += string.length;
return true;
};
// Matches heredocs, adjusting indentation to the correct level.
lex.prototype.heredoc_token = function heredoc_token() {
var doc, indent, match;
if (!((match = this.chunk.match(HEREDOC)))) {
return false;
}
doc = match[2] || match[4];
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
doc = doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
this.token('STRING', '"' + doc + '"');
this.line += this.count(match[1], "\n");
this.i += match[1].length;
return true;
};
// Matches interpolated JavaScript.
lex.prototype.js_token = function js_token() {
var script;
if (!((script = this.match(JS, 1)))) {
return false;
}
this.token('JS', script.replace(JS_CLEANER, ''));
this.i += script.length;
return true;
};
// Matches regular expression literals.
lex.prototype.regex_token = function regex_token() {
var regex;
if (!((regex = this.match(REGEX, 1)))) {
return false;
}
if (NOT_REGEX.indexOf(this.tag()) >= 0) {
return false;
}
this.token('REGEX', regex);
this.i += regex.length;
return true;
};
// Matches and conumes comments.
lex.prototype.comment_token = function comment_token() {
var comment;
if (!((comment = this.match(COMMENT, 1)))) {
return false;
}
this.line += (comment.match(MULTILINER) || []).length;
this.token('COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
this.token('TERMINATOR', "\n");
this.i += comment.length;
return true;
};
// Record tokens for indentation differing from the previous line.
lex.prototype.indent_token = function indent_token() {
var diff, indent, next_character, no_newlines, prev, size;
if (!((indent = this.match(MULTI_DENT, 1)))) {
return false;
}
this.line += indent.match(MULTILINER).length;
this.i += indent.length;
next_character = this.chunk.match(MULTI_DENT)[4];
prev = this.prev(2);
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
if (no_newlines) {
return this.suppress_newlines(indent);
}
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
if (size === this.indent) {
return this.newline_token(indent);
}
if (size > this.indent) {
diff = size - this.indent;
this.token('INDENT', diff);
this.indents.push(diff);
} else {
this.outdent_token(this.indent - size);
}
this.indent = size;
return true;
};
// Record an oudent token or tokens, if we're moving back inwards past
// multiple recorded indents.
lex.prototype.outdent_token = function outdent_token(move_out) {
var last_indent;
while (move_out > 0 && this.indents.length) {
last_indent = this.indents.pop();
this.token('OUTDENT', last_indent);
move_out -= last_indent;
}
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Matches and consumes non-meaningful whitespace.
lex.prototype.whitespace_token = function whitespace_token() {
var prev, space;
if (!((space = this.match(WHITESPACE, 1)))) {
return false;
}
prev = this.prev();
if (prev) {
prev.spaced = true;
}
this.i += space.length;
return true;
};
// Multiple newlines get merged together.
// Use a trailing \ to escape newlines.
lex.prototype.newline_token = function newline_token(newlines) {
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Tokens to explicitly escape newlines are removed once their job is done.
lex.prototype.suppress_newlines = function suppress_newlines(newlines) {
if (this.value() === "\\") {
this.tokens.pop();
}
return true;
};
// We treat all other single characters as a token. Eg.: ( ) , . !
// 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, 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');
}
} else if (value === ';') {
tag = 'TERMINATOR';
} 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';
}
if (value === '[') {
tag = 'INDEX_START';
}
}
this.token(tag, value);
this.i += value.length;
return true;
};
// Helpers =============================================================
// Add a token to the results, taking note of the line number.
lex.prototype.token = function token(tag, value) {
return this.tokens.push([tag, value, this.line]);
};
// Look at a tag in the current token stream.
lex.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof tag !== "undefined" && tag !== null)) {
return (tok[0] = tag);
}
return tok[0];
};
// Look at a value in the current token stream.
lex.prototype.value = function value(index, val) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
return (tok[1] = val);
}
return tok[1];
};
// Look at a previous token.
lex.prototype.prev = function prev(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Count the occurences of a character in a string.
lex.prototype.count = function count(string, letter) {
var num, pos;
num = 0;
pos = string.indexOf(letter);
while (pos !== -1) {
num += 1;
pos = string.indexOf(letter, pos + 1);
}
return num;
};
// Attempt to match a string against the current chunk, returning the indexed
// match.
lex.prototype.match = function match(regex, index) {
var m;
if (!((m = this.chunk.match(regex)))) {
return false;
}
return m ? m[index] : false;
};
// A source of ambiguity in our grammar was parameter lists in function
// definitions (as opposed to argument lists in function calls). Tag
// parameter identifiers in order to avoid this. Also, parameter lists can
// make use of splats.
lex.prototype.tag_parameters = function tag_parameters() {
var _a, i, tok;
if (this.tag() !== ')') {
return null;
}
i = 0;
while (true) {
i += 1;
tok = this.prev(i);
if (!tok) {
this.close_indentation();
return (new Rewriter()).rewrite(this.tokens);
};
// At every position, run through this list of attempted matches,
// short-circuiting if any of them succeed.
Lexer.prototype.extract_next_token = function extract_next_token() {
if (this.identifier_token()) {
return null;
}
if ((_a = tok[0]) === 'IDENTIFIER') {
tok[0] = 'PARAM';
} else if (_a === ')') {
tok[0] = 'PARAM_END';
} else if (_a === '(') {
return (tok[0] = 'PARAM_START');
if (this.number_token()) {
return null;
}
}
return true;
};
// Close up all remaining open blocks. IF the first token is an indent,
// axe it.
lex.prototype.close_indentation = function close_indentation() {
return this.outdent_token(this.indent);
};
if (this.heredoc_token()) {
return null;
}
if (this.string_token()) {
return null;
}
if (this.js_token()) {
return null;
}
if (this.regex_token()) {
return null;
}
if (this.indent_token()) {
return null;
}
if (this.comment_token()) {
return null;
}
if (this.whitespace_token()) {
return null;
}
return this.literal_token();
};
// Tokenizers ==========================================================
// Matches identifying literals: variables, keywords, method names, etc.
Lexer.prototype.identifier_token = function identifier_token() {
var id, tag;
if (!((id = this.match(IDENTIFIER, 1)))) {
return false;
}
if (this.value() === '::') {
this.tag(1, 'PROTOTYPE_ACCESS');
}
if (this.value() === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
this.tokens.splice(-2, 1);
} else {
this.tag(1, 'PROPERTY_ACCESS');
}
}
tag = 'IDENTIFIER';
if (KEYWORDS.indexOf(id) >= 0 && !((ACCESSORS.indexOf(this.tag()) >= 0) && !this.prev().spaced)) {
tag = id.toUpperCase();
}
if (RESERVED.indexOf(id) >= 0) {
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + this.line);
}
if (tag === 'WHEN' && BEFORE_WHEN.indexOf(this.tag()) >= 0) {
tag = 'LEADING_WHEN';
}
this.token(tag, id);
this.i += id.length;
return true;
};
// Matches numbers, including decimals, hex, and exponential notation.
Lexer.prototype.number_token = function number_token() {
var number;
if (!((number = this.match(NUMBER, 1)))) {
return false;
}
this.token('NUMBER', number);
this.i += number.length;
return true;
};
// Matches strings, including multi-line strings.
Lexer.prototype.string_token = function string_token() {
var escaped, string;
if (!((string = this.match(STRING, 1)))) {
return false;
}
escaped = string.replace(STRING_NEWLINES, " \\\n");
this.token('STRING', escaped);
this.line += this.count(string, "\n");
this.i += string.length;
return true;
};
// Matches heredocs, adjusting indentation to the correct level.
Lexer.prototype.heredoc_token = function heredoc_token() {
var doc, indent, match;
if (!((match = this.chunk.match(HEREDOC)))) {
return false;
}
doc = match[2] || match[4];
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
doc = doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
this.token('STRING', '"' + doc + '"');
this.line += this.count(match[1], "\n");
this.i += match[1].length;
return true;
};
// Matches interpolated JavaScript.
Lexer.prototype.js_token = function js_token() {
var script;
if (!((script = this.match(JS, 1)))) {
return false;
}
this.token('JS', script.replace(JS_CLEANER, ''));
this.i += script.length;
return true;
};
// Matches regular expression literals.
Lexer.prototype.regex_token = function regex_token() {
var regex;
if (!((regex = this.match(REGEX, 1)))) {
return false;
}
if (NOT_REGEX.indexOf(this.tag()) >= 0) {
return false;
}
this.token('REGEX', regex);
this.i += regex.length;
return true;
};
// Matches and conumes comments.
Lexer.prototype.comment_token = function comment_token() {
var comment;
if (!((comment = this.match(COMMENT, 1)))) {
return false;
}
this.line += (comment.match(MULTILINER) || []).length;
this.token('COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
this.token('TERMINATOR', "\n");
this.i += comment.length;
return true;
};
// Record tokens for indentation differing from the previous line.
Lexer.prototype.indent_token = function indent_token() {
var diff, indent, next_character, no_newlines, prev, size;
if (!((indent = this.match(MULTI_DENT, 1)))) {
return false;
}
this.line += indent.match(MULTILINER).length;
this.i += indent.length;
next_character = this.chunk.match(MULTI_DENT)[4];
prev = this.prev(2);
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
if (no_newlines) {
return this.suppress_newlines(indent);
}
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
if (size === this.indent) {
return this.newline_token(indent);
}
if (size > this.indent) {
diff = size - this.indent;
this.token('INDENT', diff);
this.indents.push(diff);
} else {
this.outdent_token(this.indent - size);
}
this.indent = size;
return true;
};
// Record an oudent token or tokens, if we're moving back inwards past
// multiple recorded indents.
Lexer.prototype.outdent_token = function outdent_token(move_out) {
var last_indent;
while (move_out > 0 && this.indents.length) {
last_indent = this.indents.pop();
this.token('OUTDENT', last_indent);
move_out -= last_indent;
}
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Matches and consumes non-meaningful whitespace.
Lexer.prototype.whitespace_token = function whitespace_token() {
var prev, space;
if (!((space = this.match(WHITESPACE, 1)))) {
return false;
}
prev = this.prev();
if (prev) {
prev.spaced = true;
}
this.i += space.length;
return true;
};
// Multiple newlines get merged together.
// Use a trailing \ to escape newlines.
Lexer.prototype.newline_token = function newline_token(newlines) {
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Tokens to explicitly escape newlines are removed once their job is done.
Lexer.prototype.suppress_newlines = function suppress_newlines(newlines) {
if (this.value() === "\\") {
this.tokens.pop();
}
return true;
};
// We treat all other single characters as a token. Eg.: ( ) , . !
// Multi-character operators are also literal tokens, so that Racc can assign
// the proper order of operations.
Lexer.prototype.literal_token = function literal_token() {
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');
}
} else if (value === ';') {
tag = 'TERMINATOR';
} 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';
}
if (value === '[') {
tag = 'INDEX_START';
}
}
this.token(tag, value);
this.i += value.length;
return true;
};
// Helpers =============================================================
// Add a token to the results, taking note of the line number.
Lexer.prototype.token = function token(tag, value) {
return this.tokens.push([tag, value, this.line]);
};
// Look at a tag in the current token stream.
Lexer.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof tag !== "undefined" && tag !== null)) {
return (tok[0] = tag);
}
return tok[0];
};
// Look at a value in the current token stream.
Lexer.prototype.value = function value(index, val) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
return (tok[1] = val);
}
return tok[1];
};
// Look at a previous token.
Lexer.prototype.prev = function prev(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Count the occurences of a character in a string.
Lexer.prototype.count = function count(string, letter) {
var num, pos;
num = 0;
pos = string.indexOf(letter);
while (pos !== -1) {
num += 1;
pos = string.indexOf(letter, pos + 1);
}
return num;
};
// Attempt to match a string against the current chunk, returning the indexed
// match.
Lexer.prototype.match = function match(regex, index) {
var m;
if (!((m = this.chunk.match(regex)))) {
return false;
}
return m ? m[index] : false;
};
// A source of ambiguity in our grammar was parameter lists in function
// definitions (as opposed to argument lists in function calls). Tag
// parameter identifiers in order to avoid this. Also, parameter lists can
// make use of splats.
Lexer.prototype.tag_parameters = function tag_parameters() {
var _a, i, tok;
if (this.tag() !== ')') {
return null;
}
i = 0;
while (true) {
i += 1;
tok = this.prev(i);
if (!tok) {
return null;
}
if ((_a = tok[0]) === 'IDENTIFIER') {
tok[0] = 'PARAM';
} else if (_a === ')') {
tok[0] = 'PARAM_END';
} else if (_a === '(') {
return (tok[0] = 'PARAM_START');
}
}
return true;
};
// Close up all remaining open blocks. IF the first token is an indent,
// axe it.
Lexer.prototype.close_indentation = function close_indentation() {
return this.outdent_token(this.indent);
};
return Lexer;
}).call(this);
})();

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +1,81 @@
(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, banner) {
this.banner = banner || 'Usage: [Options]';
this.options_title = 'Available options:';
this.rules = build_rules(rules);
return this;
});
// Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
op.prototype.parse = function parse(args) {
var _a, _b, arg, is_option, options, rule;
arguments = Array.prototype.slice.call(arguments, 0);
options = {
arguments: []
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, build_rule, build_rules, normalize_arguments;
// Create an OptionParser with a list of valid options, in the form:
// [short-flag (optional), long-flag, description]
// And an optional banner for the usage help.
exports.OptionParser = (function() {
OptionParser = function OptionParser(rules, banner) {
this.banner = banner;
this.rules = build_rules(rules);
return this;
};
args = args.concat([]);
while (((arg = args.shift()))) {
is_option = false;
_a = this.rules;
for (_b = 0; _b < _a.length; _b++) {
rule = _a[_b];
if (rule.letter === arg || rule.flag === arg) {
options[rule.name] = rule.argument ? args.shift() : true;
is_option = true;
break;
// Parse the argument array, populating an options object with all of the
// specified options, and returning it. options.arguments will be an array
// containing the remaning non-option arguments.
OptionParser.prototype.parse = function parse(args) {
var _a, _b, _c, arg, is_option, matched_rule, options, rule;
arguments = Array.prototype.slice.call(arguments, 0);
options = {
arguments: []
};
args = normalize_arguments(args);
while (arg = args.shift()) {
is_option = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG));
matched_rule = false;
_a = this.rules;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
rule = _a[_b];
if (rule.letter === arg || rule.flag === arg) {
options[rule.name] = rule.has_argument ? args.shift() : true;
matched_rule = true;
break;
}
}
if (is_option && !matched_rule) {
throw new Error("unrecognized option: " + arg);
}
if (!(is_option)) {
options.arguments.push(arg);
}
}
if (!(is_option)) {
options.arguments.push(arg);
return options;
};
// Return the help text for this OptionParser, for --help and such.
OptionParser.prototype.help = function help() {
var _a, _b, _c, _d, _e, _f, _g, _h, i, let_part, lines, rule, spaces;
lines = ['Available options:'];
if (this.banner) {
lines.unshift(this.banner + '\n');
}
}
return options;
};
// Return the help text for this OptionParser, for --help and such.
op.prototype.help = function help() {
var _a, _b, _c, _d, has_shorts, lines, longest, rule, text;
longest = 0;
has_shorts = false;
lines = [this.banner, '', this.options_title];
_a = this.rules;
for (_b = 0; _b < _a.length; _b++) {
rule = _a[_b];
if (rule.letter) {
has_shorts = true;
_a = this.rules;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
rule = _a[_b];
spaces = 15 - rule.flag.length;
spaces = spaces > 0 ? (function() {
_d = []; _g = 0; _h = spaces;
for (_f = 0, i = _g; (_g <= _h ? i <= _h : i >= _h); (_g <= _h ? i += 1 : i -= 1), _f++) {
_d.push(' ');
}
return _d;
}).call(this).join('') : '';
let_part = rule.letter ? rule.letter + ', ' : ' ';
lines.push(' ' + let_part + rule.flag + spaces + rule.description);
}
if (rule.flag.length > longest) {
longest = rule.flag.length;
}
}
_c = this.rules;
for (_d = 0; _d < _c.length; _d++) {
rule = _c[_d];
has_shorts ? (text = rule.letter ? spaces(2) + rule.letter + ', ' : spaces(6)) : null;
text += spaces(longest, rule.flag) + spaces(3);
text += rule.description;
lines.push(text);
}
return lines.join('\n');
};
// Private:
return lines.join('\n');
};
return OptionParser;
}).call(this);
// Regex matchers for option flags.
LONG_FLAG = /^(--[\w\-]+)/;
SHORT_FLAG = /^(-\w+)/;
LONG_FLAG = /^(--\w[\w\-]+)/;
SHORT_FLAG = /^(-\w)/;
MULTI_FLAG = /^-(\w{2,})/;
OPTIONAL = /\[(.+)\]/;
// Build rules from a list of valid switch tuples in the form:
// [letter-flag, long-flag, help], or [long-flag, help].
build_rules = function build_rules(rules) {
var _a, _b, _c, tuple;
var _a, _b, _c, _d, tuple;
_a = []; _b = rules;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
tuple = _b[_c];
_a.push((function() {
if (tuple.length < 3) {
@@ -89,23 +96,27 @@
letter: letter,
flag: flag,
description: description,
argument: !!(match && match[1])
has_argument: !!(match && match[1])
};
};
// Space-pad a string with the specified number of characters.
spaces = function spaces(num, text) {
var builder;
builder = [];
if (text) {
if (text.length >= num) {
return text;
// Normalize arguments by expanding merged flags into multiple flags.
normalize_arguments = function normalize_arguments(args) {
var _a, _b, _c, _d, _e, _f, arg, l, match, result;
args = args.slice(0);
result = [];
_a = args;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
arg = _a[_b];
if ((match = arg.match(MULTI_FLAG))) {
_d = match[1].split('');
for (_e = 0, _f = _d.length; _e < _f; _e++) {
l = _d[_e];
result.push('-' + l);
}
} else {
result.push(arg);
}
num -= text.length;
builder.push(text);
}
while (num -= 1) {
builder.push(' ');
}
return builder.join('');
return result;
};
})();

File diff suppressed because one or more lines are too long

View File

@@ -1,19 +1,15 @@
(function(){
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, pair, re;
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, pair;
var __hasProp = Object.prototype.hasOwnProperty;
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
// In order to keep the grammar simple, the stream of tokens that the Lexer
// emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
// 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'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']];
// Tokens that signal the start of a balanced pair.
EXPRESSION_START = (function() {
_a = []; _b = BALANCED_PAIRS;
for (_c = 0; _c < _b.length; _c++) {
for (_c = 0, _d = _b.length; _c < _d; _c++) {
pair = _b[_c];
_a.push(pair[0]);
}
@@ -21,12 +17,12 @@
}).call(this);
// Tokens that signal the end of a balanced pair.
EXPRESSION_TAIL = (function() {
_d = []; _e = BALANCED_PAIRS;
for (_f = 0; _f < _e.length; _f++) {
pair = _e[_f];
_d.push(pair[1]);
_e = []; _f = BALANCED_PAIRS;
for (_g = 0, _h = _f.length; _g < _h; _g++) {
pair = _f[_g];
_e.push(pair[1]);
}
return _d;
return _e;
}).call(this);
// Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL);
@@ -37,347 +33,355 @@
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '@', '->', '=>', '[', '(', '{'];
// The inverse mappings of token pairs we're trying to fix up.
INVERSES = {};
_g = BALANCED_PAIRS;
for (_h = 0; _h < _g.length; _h++) {
pair = _g[_h];
_i = BALANCED_PAIRS;
for (_j = 0, _k = _i.length; _j < _k; _j++) {
pair = _i[_j];
INVERSES[pair[0]] = pair[1];
INVERSES[pair[1]] = pair[0];
}
// Single-line flavors of block expressions that have unclosed endings.
// The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN', 'PARAM_START'];
// Rewrite the token stream in multiple passes, one logical filter at
// a time. This could certainly be changed into a single pass through the
// stream, with a big ol' efficient switch, but it's much nicer like this.
re.prototype.rewrite = function rewrite(tokens) {
this.tokens = tokens;
this.adjust_comments();
this.remove_leading_newlines();
this.remove_mid_expression_newlines();
this.move_commas_outside_outdents();
this.close_open_calls_and_indexes();
this.add_implicit_indentation();
this.add_implicit_parentheses();
this.ensure_balance(BALANCED_PAIRS);
this.rewrite_closing_parens();
return this.tokens;
};
// Rewrite the token stream, looking one token ahead and behind.
// Allow the return value of the block to tell us how many tokens to move
// forwards (or backwards) in the stream, to make sure we don't miss anything
// as the stream changes length under our feet.
re.prototype.scan_tokens = function scan_tokens(block) {
var i, move;
i = 0;
while (true) {
if (!(this.tokens[i])) {
break;
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
// In order to keep the grammar simple, the stream of tokens that the Lexer
// emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
// indentation, and single-line flavors of expressions.
exports.Rewriter = (function() {
Rewriter = function Rewriter() { };
// Rewrite the token stream in multiple passes, one logical filter at
// a time. This could certainly be changed into a single pass through the
// stream, with a big ol' efficient switch, but it's much nicer like this.
Rewriter.prototype.rewrite = function rewrite(tokens) {
this.tokens = tokens;
this.adjust_comments();
this.remove_leading_newlines();
this.remove_mid_expression_newlines();
this.move_commas_outside_outdents();
this.close_open_calls_and_indexes();
this.add_implicit_indentation();
this.add_implicit_parentheses();
this.ensure_balance(BALANCED_PAIRS);
this.rewrite_closing_parens();
return this.tokens;
};
// Rewrite the token stream, looking one token ahead and behind.
// Allow the return value of the block to tell us how many tokens to move
// forwards (or backwards) in the stream, to make sure we don't miss anything
// as the stream changes length under our feet.
Rewriter.prototype.scan_tokens = function scan_tokens(block) {
var i, move;
i = 0;
while (true) {
if (!(this.tokens[i])) {
break;
}
move = block(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i);
i += move;
}
move = block(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i);
i += move;
}
return true;
};
// Massage newlines and indentations so that comments don't have to be
// correctly indented, or appear on their own line.
re.prototype.adjust_comments = function adjust_comments() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var after, before;
if (!(token[0] === 'COMMENT')) {
return 1;
}
before = this.tokens[i - 2];
after = this.tokens[i + 2];
if (before && after && ((before[0] === 'INDENT' && after[0] === 'OUTDENT') || (before[0] === 'OUTDENT' && after[0] === 'INDENT')) && before[1] === after[1]) {
this.tokens.splice(i + 2, 1);
this.tokens.splice(i - 2, 1);
return 0;
} else if (prev && prev[0] === 'TERMINATOR' && after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens[i - 1] = after;
return 1;
} else if (prev && prev[0] !== 'TERMINATOR' && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
return 2;
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Leading newlines would introduce an ambiguity in the grammar, so we
// dispatch them here.
re.prototype.remove_leading_newlines = function remove_leading_newlines() {
if (this.tokens[0][0] === 'TERMINATOR') {
return this.tokens.shift();
}
};
// Some blocks occur in the middle of expressions -- when we're expecting
// this, remove their trailing newlines.
re.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (!(post && EXPRESSION_CLOSE.indexOf(post[0]) >= 0 && token[0] === 'TERMINATOR')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Make sure that we don't accidentally break trailing commas, which need
// to go on the outside of expression closers.
re.prototype.move_commas_outside_outdents = function move_commas_outside_outdents() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (token[0] === 'OUTDENT' && prev[0] === ',') {
this.tokens.splice(i, 1, token);
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// We've tagged the opening parenthesis of a method call, and the opening
// bracket of an indexing operation. Match them with their close.
re.prototype.close_open_calls_and_indexes = function close_open_calls_and_indexes() {
var brackets, parens;
parens = [0];
brackets = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _i;
if ((_i = token[0]) === 'CALL_START') {
parens.push(0);
} else if (_i === 'INDEX_START') {
brackets.push(0);
} else if (_i === '(') {
parens[parens.length - 1] += 1;
} else if (_i === '[') {
brackets[brackets.length - 1] += 1;
} else if (_i === ')') {
if (parens[parens.length - 1] === 0) {
parens.pop();
token[0] = 'CALL_END';
} else {
parens[parens.length - 1] -= 1;
}
} else if (_i === ']') {
if (brackets[brackets.length - 1] === 0) {
brackets.pop();
token[0] = 'INDEX_END';
} else {
brackets[brackets.length - 1] -= 1;
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Methods may be optionally called without parentheses, for simple cases.
// Insert the implicit parentheses here, so that the parser doesn't have to
// deal with them.
re.prototype.add_implicit_parentheses = function add_implicit_parentheses() {
var stack;
stack = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _i, _j, _k, _l, idx, last, size, stack_pointer, tag, tmp;
tag = token[0];
if (tag === 'INDENT') {
stack.push(0);
}
if (tag === 'OUTDENT') {
last = stack.pop();
stack[stack.length - 1] += last;
}
if (IMPLICIT_END.indexOf(tag) >= 0 || !(typeof post !== "undefined" && post !== null)) {
if (tag === 'INDENT' && prev && IMPLICIT_BLOCK.indexOf(prev[0]) >= 0) {
return true;
};
// Massage newlines and indentations so that comments don't have to be
// correctly indented, or appear on their own line.
Rewriter.prototype.adjust_comments = function adjust_comments() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var after, before;
if (!(token[0] === 'COMMENT')) {
return 1;
}
if (stack[stack.length - 1] > 0 || tag === 'INDENT') {
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++) {
this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
}
size = stack[stack.length - stack_pointer] + 1;
stack[stack.length - stack_pointer] = 0;
return size;
}
}
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(tag) >= 0)) {
return 1;
}
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
stack[stack.length - 1] += 1;
return 2;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Because our grammar is LALR(1), it can't handle some single-line
// expressions that lack ending delimiters. Use the lexer to add the implicit
// blocks, so it doesn't need to.
// ')' can close a single-line block, but we need to make sure it's balanced.
re.prototype.add_implicit_indentation = function add_implicit_indentation() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var idx, insertion, parens, starter, tok;
if (!(SINGLE_LINERS.indexOf(token[0]) >= 0 && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) {
return 1;
}
starter = token[0];
this.tokens.splice(i + 1, 0, ['INDENT', 2, token[2]]);
idx = i + 1;
parens = 0;
while (true) {
idx += 1;
tok = this.tokens[idx];
if ((!tok || (SINGLE_CLOSERS.indexOf(tok[0]) >= 0 && tok[1] !== ';') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
insertion = this.tokens[idx - 1][0] === "," ? idx - 1 : idx;
this.tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]]);
break;
}
if (tok[0] === '(') {
parens += 1;
}
if (tok[0] === ')') {
parens -= 1;
}
}
if (!(token[0] === 'THEN')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Ensure that all listed pairs of tokens are correctly balanced throughout
// the course of the token stream.
re.prototype.ensure_balance = function ensure_balance(pairs) {
var _i, _j, key, levels, unclosed, value;
levels = {};
this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _i, _j, _k, close, open;
_i = pairs;
for (_j = 0; _j < _i.length; _j++) {
pair = _i[_j];
_k = pair;
open = _k[0];
close = _k[1];
levels[open] = levels[open] || 0;
if (token[0] === open) {
levels[open] += 1;
}
if (token[0] === close) {
levels[open] -= 1;
}
if (levels[open] < 0) {
throw new Error("too many " + token[1]);
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
unclosed = (function() {
_i = []; _j = levels;
for (key in _j) { if (__hasProp.call(_j, key)) {
value = _j[key];
if (value > 0) {
_i.push(key);
}
}}
return _i;
}).call(this);
if (unclosed.length) {
throw new Error("unclosed " + unclosed[0]);
}
};
// We'd like to support syntax like this:
// el.click((event) ->
// el.hide())
// In order to accomplish this, move outdents that follow closing parens
// inwards, safely. The steps to accomplish this are:
//
// 1. Check that all paired tokens are balanced and in order.
// 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
// to the stack. If you see an ')' or OUTDENT, pop the stack and replace
// it with the inverse of what we've just popped.
// 3. Keep track of "debt" for tokens that we fake, to make sure we end
// up balanced in the end.
//
re.prototype.rewrite_closing_parens = function rewrite_closing_parens() {
var _i, debt, key, stack, val;
stack = [];
debt = {};
_i = INVERSES;
for (key in _i) { if (__hasProp.call(_i, key)) {
val = _i[key];
((debt[key] = 0));
}}
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var inv, match, mtag, tag;
tag = token[0];
inv = INVERSES[token[0]];
// Push openers onto the stack.
if (EXPRESSION_START.indexOf(tag) >= 0) {
stack.push(token);
return 1;
// The end of an expression, check stack and debt for a pair.
} else if (EXPRESSION_TAIL.indexOf(tag) >= 0) {
// If the tag is already in our debt, swallow it.
if (debt[inv] > 0) {
debt[inv] -= 1;
this.tokens.splice(i, 1);
before = this.tokens[i - 2];
after = this.tokens[i + 2];
if (before && after && ((before[0] === 'INDENT' && after[0] === 'OUTDENT') || (before[0] === 'OUTDENT' && after[0] === 'INDENT')) && before[1] === after[1]) {
this.tokens.splice(i + 2, 1);
this.tokens.splice(i - 2, 1);
return 0;
} else if (prev && prev[0] === 'TERMINATOR' && after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens[i - 1] = after;
return 1;
} else if (prev && prev[0] !== 'TERMINATOR' && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
return 2;
} else {
// Pop the stack of open delimiters.
match = stack.pop();
mtag = match[0];
// Continue onwards if it's the expected tag.
if (tag === INVERSES[mtag]) {
return 1;
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Leading newlines would introduce an ambiguity in the grammar, so we
// dispatch them here.
Rewriter.prototype.remove_leading_newlines = function remove_leading_newlines() {
if (this.tokens[0][0] === 'TERMINATOR') {
return this.tokens.shift();
}
};
// Some blocks occur in the middle of expressions -- when we're expecting
// this, remove their trailing newlines.
Rewriter.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (!(post && EXPRESSION_CLOSE.indexOf(post[0]) >= 0 && token[0] === 'TERMINATOR')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Make sure that we don't accidentally break trailing commas, which need
// to go on the outside of expression closers.
Rewriter.prototype.move_commas_outside_outdents = function move_commas_outside_outdents() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (token[0] === 'OUTDENT' && prev[0] === ',') {
this.tokens.splice(i, 1, token);
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// We've tagged the opening parenthesis of a method call, and the opening
// bracket of an indexing operation. Match them with their close.
Rewriter.prototype.close_open_calls_and_indexes = function close_open_calls_and_indexes() {
var brackets, parens;
parens = [0];
brackets = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _l;
if ((_l = token[0]) === 'CALL_START') {
parens.push(0);
} else if (_l === 'INDEX_START') {
brackets.push(0);
} else if (_l === '(') {
parens[parens.length - 1] += 1;
} else if (_l === '[') {
brackets[brackets.length - 1] += 1;
} else if (_l === ')') {
if (parens[parens.length - 1] === 0) {
parens.pop();
token[0] = 'CALL_END';
} else {
// Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1;
val = mtag === 'INDENT' ? match[1] : INVERSES[mtag];
this.tokens.splice(i, 0, [INVERSES[mtag], val]);
return 1;
parens[parens.length - 1] -= 1;
}
} else if (_l === ']') {
if (brackets[brackets.length - 1] === 0) {
brackets.pop();
token[0] = 'INDEX_END';
} else {
brackets[brackets.length - 1] -= 1;
}
}
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Methods may be optionally called without parentheses, for simple cases.
// Insert the implicit parentheses here, so that the parser doesn't have to
// deal with them.
Rewriter.prototype.add_implicit_parentheses = function add_implicit_parentheses() {
var stack;
stack = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _l, _m, _n, _o, idx, last, size, stack_pointer, tag, tmp;
tag = token[0];
if (tag === 'INDENT') {
stack.push(0);
}
if (tag === 'OUTDENT') {
last = stack.pop();
stack[stack.length - 1] += last;
}
if (IMPLICIT_END.indexOf(tag) >= 0 || !(typeof post !== "undefined" && post !== null)) {
if (tag === 'INDENT' && prev && IMPLICIT_BLOCK.indexOf(prev[0]) >= 0) {
return 1;
}
if (stack[stack.length - 1] > 0 || tag === 'INDENT') {
idx = tag === 'OUTDENT' ? i + 1 : i;
stack_pointer = tag === 'INDENT' ? 2 : 1;
_n = 0; _o = stack[stack.length - stack_pointer];
for (_m = 0, tmp = _n; (_n <= _o ? tmp < _o : tmp > _o); (_n <= _o ? tmp += 1 : tmp -= 1), _m++) {
this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
}
size = stack[stack.length - stack_pointer] + 1;
stack[stack.length - stack_pointer] = 0;
return size;
}
}
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(tag) >= 0)) {
return 1;
}
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
stack[stack.length - 1] += 1;
return 2;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Because our grammar is LALR(1), it can't handle some single-line
// expressions that lack ending delimiters. Use the lexer to add the implicit
// blocks, so it doesn't need to.
// ')' can close a single-line block, but we need to make sure it's balanced.
Rewriter.prototype.add_implicit_indentation = function add_implicit_indentation() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var idx, insertion, parens, pre, starter, tok;
if (!(SINGLE_LINERS.indexOf(token[0]) >= 0 && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) {
return 1;
}
starter = token[0];
this.tokens.splice(i + 1, 0, ['INDENT', 2, token[2]]);
idx = i + 1;
parens = 0;
while (true) {
idx += 1;
tok = this.tokens[idx];
pre = this.tokens[idx - 1];
if ((!tok || (SINGLE_CLOSERS.indexOf(tok[0]) >= 0 && tok[1] !== ';') || (pre[0] === ',' && tok[0] === 'PARAM_START') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
insertion = pre[0] === "," ? idx - 1 : idx;
this.tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]]);
break;
}
if (tok[0] === '(') {
parens += 1;
}
if (tok[0] === ')') {
parens -= 1;
}
}
if (!(token[0] === 'THEN')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Ensure that all listed pairs of tokens are correctly balanced throughout
// the course of the token stream.
Rewriter.prototype.ensure_balance = function ensure_balance(pairs) {
var _l, _m, key, levels, unclosed, value;
levels = {};
this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _l, _m, _n, _o, close, open;
_l = pairs;
for (_m = 0, _n = _l.length; _m < _n; _m++) {
pair = _l[_m];
_o = pair;
open = _o[0];
close = _o[1];
levels[open] = levels[open] || 0;
if (token[0] === open) {
levels[open] += 1;
}
if (token[0] === close) {
levels[open] -= 1;
}
if (levels[open] < 0) {
throw new Error("too many " + token[1]);
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
unclosed = (function() {
_l = []; _m = levels;
for (key in _m) { if (__hasProp.call(_m, key)) {
value = _m[key];
if (value > 0) {
_l.push(key);
}
}}
return _l;
}).call(this);
if (unclosed.length) {
throw new Error("unclosed " + unclosed[0]);
}
};
// We'd like to support syntax like this:
// el.click((event) ->
// el.hide())
// In order to accomplish this, move outdents that follow closing parens
// inwards, safely. The steps to accomplish this are:
//
// 1. Check that all paired tokens are balanced and in order.
// 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
// to the stack. If you see an ')' or OUTDENT, pop the stack and replace
// it with the inverse of what we've just popped.
// 3. Keep track of "debt" for tokens that we fake, to make sure we end
// up balanced in the end.
//
Rewriter.prototype.rewrite_closing_parens = function rewrite_closing_parens() {
var _l, debt, key, stack, val;
stack = [];
debt = {};
_l = INVERSES;
for (key in _l) { if (__hasProp.call(_l, key)) {
val = _l[key];
((debt[key] = 0));
}}
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var inv, match, mtag, tag;
tag = token[0];
inv = INVERSES[token[0]];
// Push openers onto the stack.
if (EXPRESSION_START.indexOf(tag) >= 0) {
stack.push(token);
return 1;
// The end of an expression, check stack and debt for a pair.
} else if (EXPRESSION_TAIL.indexOf(tag) >= 0) {
// If the tag is already in our debt, swallow it.
if (debt[inv] > 0) {
debt[inv] -= 1;
this.tokens.splice(i, 1);
return 0;
} else {
// Pop the stack of open delimiters.
match = stack.pop();
mtag = match[0];
// Continue onwards if it's the expected tag.
if (tag === INVERSES[mtag]) {
return 1;
} else {
// Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1;
val = mtag === 'INDENT' ? match[1] : INVERSES[mtag];
this.tokens.splice(i, 0, [INVERSES[mtag], val]);
return 1;
}
}
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
return Rewriter;
}).call(this);
})();

View File

@@ -11,104 +11,107 @@
// Initialize a scope with its parent, for lookups up the chain,
// as well as the Expressions body where it should declare its variables,
// and the function that it wraps.
Scope = (exports.Scope = function Scope(parent, expressions, method) {
var _a;
_a = [parent, expressions, method];
this.parent = _a[0];
this.expressions = _a[1];
this.method = _a[2];
this.variables = {};
this.temp_var = this.parent ? this.parent.temp_var : '_a';
return this;
});
// Look up a variable in lexical scope, or declare it if not found.
Scope.prototype.find = function find(name) {
if (this.check(name)) {
return true;
}
this.variables[name] = 'var';
return false;
};
// Define a local variable as originating from a parameter in current scope
// -- no var required.
Scope.prototype.parameter = function parameter(name) {
return this.variables[name] = 'param';
};
// Just check to see if a variable has already been declared.
Scope.prototype.check = function check(name) {
if (this.variables[name]) {
return true;
}
return !!(this.parent && this.parent.check(name));
};
// You can reset a found variable on the immediate scope.
Scope.prototype.reset = function reset(name) {
return delete this.variables[name];
};
// Find an available, short, name for a compiler-generated variable.
Scope.prototype.free_variable = function free_variable() {
var ordinal;
while (this.check(this.temp_var)) {
ordinal = 1 + parseInt(this.temp_var.substr(1), 36);
this.temp_var = '_' + ordinal.toString(36).replace(/\d/g, 'a');
}
this.variables[this.temp_var] = 'var';
return this.temp_var;
};
// Ensure that an assignment is made at the top of scope (or top-level
// scope, if requested).
Scope.prototype.assign = function assign(name, value, top_level) {
if (top_level && this.parent) {
return this.parent.assign(name, value, top_level);
}
return this.variables[name] = {
value: value,
assigned: true
exports.Scope = (function() {
Scope = function Scope(parent, expressions, method) {
var _a;
_a = [parent, expressions, method];
this.parent = _a[0];
this.expressions = _a[1];
this.method = _a[2];
this.variables = {};
this.temp_var = this.parent ? this.parent.temp_var : '_a';
return this;
};
};
// Does this scope reference any variables that need to be declared in the
// given function body?
Scope.prototype.has_declarations = function has_declarations(body) {
return body === this.expressions && this.declared_variables().length;
};
// Does this scope reference any assignments that need to be declared at the
// top of the given function body?
Scope.prototype.has_assignments = function has_assignments(body) {
return body === this.expressions && this.assigned_variables().length;
};
// Return the list of variables first declared in current scope.
Scope.prototype.declared_variables = function declared_variables() {
var _a, _b, key, val;
return (function() {
// Look up a variable in lexical scope, or declare it if not found.
Scope.prototype.find = function find(name) {
if (this.check(name)) {
return true;
}
this.variables[name] = 'var';
return false;
};
// Define a local variable as originating from a parameter in current scope
// -- no var required.
Scope.prototype.parameter = function parameter(name) {
return this.variables[name] = 'param';
};
// Just check to see if a variable has already been declared.
Scope.prototype.check = function check(name) {
if (this.variables[name]) {
return true;
}
return !!(this.parent && this.parent.check(name));
};
// You can reset a found variable on the immediate scope.
Scope.prototype.reset = function reset(name) {
return delete this.variables[name];
};
// Find an available, short, name for a compiler-generated variable.
Scope.prototype.free_variable = function free_variable() {
var ordinal;
while (this.check(this.temp_var)) {
ordinal = 1 + parseInt(this.temp_var.substr(1), 36);
this.temp_var = '_' + ordinal.toString(36).replace(/\d/g, 'a');
}
this.variables[this.temp_var] = 'var';
return this.temp_var;
};
// Ensure that an assignment is made at the top of scope (or top-level
// scope, if requested).
Scope.prototype.assign = function assign(name, value, top_level) {
if (top_level && this.parent) {
return this.parent.assign(name, value, top_level);
}
return this.variables[name] = {
value: value,
assigned: true
};
};
// Does this scope reference any variables that need to be declared in the
// given function body?
Scope.prototype.has_declarations = function has_declarations(body) {
return body === this.expressions && this.declared_variables().length;
};
// Does this scope reference any assignments that need to be declared at the
// top of the given function body?
Scope.prototype.has_assignments = function has_assignments(body) {
return body === this.expressions && this.assigned_variables().length;
};
// Return the list of variables first declared in current scope.
Scope.prototype.declared_variables = function declared_variables() {
var _a, _b, key, val;
return (function() {
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val === 'var') {
_a.push(key);
}
}}
return _a;
}).call(this).sort();
};
// Return the list of variables that are supposed to be assigned at the top
// of scope.
Scope.prototype.assigned_variables = function assigned_variables() {
var _a, _b, key, val;
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val === 'var') {
_a.push(key);
if (val.assigned) {
_a.push(key + ' = ' + val.value);
}
}}
return _a;
}).call(this).sort();
};
// Return the list of variables that are supposed to be assigned at the top
// of scope.
Scope.prototype.assigned_variables = function assigned_variables() {
var _a, _b, key, val;
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val.assigned) {
_a.push(key + ' = ' + val.value);
}
}}
return _a;
};
// Compile the string representing all of the declared variables for this scope.
Scope.prototype.compiled_declarations = function compiled_declarations() {
return this.declared_variables().join(', ');
};
// Compile the string performing all of the variable assignments for this scope.
Scope.prototype.compiled_assignments = function compiled_assignments() {
return this.assigned_variables().join(', ');
};
};
// Compile the string representing all of the declared variables for this scope.
Scope.prototype.compiled_declarations = function compiled_declarations() {
return this.declared_variables().join(', ');
};
// Compile the string performing all of the variable assignments for this scope.
Scope.prototype.compiled_assignments = function compiled_assignments() {
return this.assigned_variables().join(', ');
};
return Scope;
}).call(this);
})();

View File

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

View File

@@ -1,34 +1,34 @@
# `cake` is a simplified version of Make (Rake, Jake) for CoffeeScript.
# You define tasks with names and descriptions in a Cakefile, and can call them
# from the command line, or invoke them from other tasks.
fs: require 'fs'
path: require 'path'
coffee: require 'coffee-script'
optparse: require 'optparse'
tasks: {}
options: {}
switches: []
oparse: null
no_such_task: (task) ->
process.stdio.writeError('No such task: "' + task + '"\n')
process.exit(1)
# Mixin the Cake functionality.
# Mixin the top-level Cake functions for Cakefiles to use.
process.mixin {
# Define a task with a name, a description, and the action itself.
task: (name, description, action) ->
tasks[name]: {name: name, description: description, action: action}
# Define an option that the Cakefile accepts.
option: (letter, flag, description) ->
switches.push [letter, flag, description]
# Invoke another task in the Cakefile.
invoke: (name) ->
no_such_task name unless tasks[name]
tasks[name].action()
}
tasks[name].action(options)
# Display the list of Cake tasks.
print_tasks: ->
for name, task of tasks
spaces: 20 - name.length
spaces: if spaces > 0 then (' ' for i in [0..spaces]).join('') else ''
puts "cake " + name + spaces + ' # ' + task.description
}
# Running `cake` runs the tasks you pass asynchronously (node-style), or
# prints them out, with no arguments.
@@ -36,10 +36,22 @@ exports.run: ->
path.exists 'Cakefile', (exists) ->
throw new Error('Cakefile not found in ' + process.cwd()) unless exists
args: process.ARGV[2...process.ARGV.length]
fs.readFile 'Cakefile', (err, source) ->
eval coffee.compile source
return print_tasks() unless args.length
for arg in args
no_such_task arg unless tasks[arg]
tasks[arg].action()
eval coffee.compile fs.readFileSync 'Cakefile'
oparse: new optparse.OptionParser switches
return print_tasks() unless args.length
options: oparse.parse(args)
invoke arg for arg in options.arguments
# Display the list of Cake tasks.
print_tasks: ->
puts ''
for name, task of tasks
spaces: 20 - name.length
spaces: if spaces > 0 then (' ' for i in [0..spaces]).join('') else ''
puts "cake " + name + spaces + ' # ' + task.description
puts '\n' + oparse.help() + '\n' if switches.length
# Print an error and exit when attempting to all an undefined task.
no_such_task: (task) ->
process.stdio.writeError('No such task: "' + task + '"\n')
process.exit(1)

View File

@@ -24,22 +24,27 @@ parser.lexer: {
showPosition: -> @pos
}
exports.VERSION: '0.5.2'
exports.VERSION: '0.5.3'
# Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile: (code, options) ->
(parser.parse lexer.tokenize code).compile(options)
# Just the tokens.
exports.tokenize: (code) ->
exports.tokens: (code) ->
lexer.tokenize code
# Just the nodes.
exports.tree: (code) ->
exports.nodes: (code) ->
parser.parse lexer.tokenize code
# 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
process_scripts: ->
for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript'
eval exports.compile tag.innerHTML
if window.addEventListener
window.addEventListener 'load', process_scripts, false
else if window.attachEvent
window.attachEvent 'onload', process_scripts

View File

@@ -11,17 +11,17 @@ BANNER: '''
'''
SWITCHES: [
['-c', '--compile', 'compile to JavaScript and save as .js files']
['-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']
[ '--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', '--nodes', 'print the parse tree that Jison produces']
['-v', '--version', 'display CoffeeScript version']
['-h', '--help', 'display this help message']
]
@@ -63,7 +63,9 @@ version: ->
# or JSLint results.
compile_scripts: ->
compile: (source) ->
fs.readFile source, (err, code) -> compile_script(source, code)
path.exists source, (exists) ->
throw new Error 'File not found: ' + source unless exists
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
@@ -71,14 +73,14 @@ compile_scripts: ->
compile_script: (source, code) ->
o: options
try
if o.tokens then print_tokens CoffeeScript.tokenize code
else if o.tree then puts CoffeeScript.tree(code).toString()
if o.tokens then print_tokens CoffeeScript.tokens code
else if o.nodes then puts CoffeeScript.nodes(code).toString()
else
js: CoffeeScript.compile code, compile_options()
if o.run then eval js
if o.compile then write_js source, js
else if o.lint then lint js
else if o.print or o.eval then print js
else write_js source, js
else eval js
catch err
if o.watch then puts err.message else throw err

View File

@@ -31,7 +31,7 @@ operators: [
["right", 'INDENT']
["left", 'OUTDENT']
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
["right", 'FOR', 'NEW', 'SUPER']
["right", 'FOR', 'NEW', 'SUPER', 'CLASS']
["left", 'EXTENDS']
["right", 'ASSIGN', 'RETURN']
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']
@@ -72,6 +72,7 @@ grammar: {
o "For"
o "Switch"
o "Extends"
o "Class"
o "Splat"
o "Existence"
o "Comment"
@@ -257,6 +258,15 @@ grammar: {
# An object literal.
Object: [
o "{ AssignList }", -> new ObjectNode($2)
o "{ IndentedAssignList }", -> new ObjectNode($2)
]
# A class literal.
Class: [
o "CLASS Value", -> new ClassNode($2)
o "CLASS Value EXTENDS Value", -> new ClassNode($2, $4)
o "CLASS Value IndentedAssignList", -> new ClassNode($2, null, $3)
o "CLASS Value EXTENDS Value IndentedAssignList", -> new ClassNode($2, $4, $5)
]
# Assignment within an object literal (comma or newline separated).
@@ -266,6 +276,10 @@ grammar: {
o "AssignList , AssignObj", -> $1.concat [$3]
o "AssignList TERMINATOR AssignObj", -> $1.concat [$3]
o "AssignList , TERMINATOR AssignObj", -> $1.concat [$4]
]
# A list of assignments in a block indentation.
IndentedAssignList: [
o "INDENT AssignList OUTDENT", -> $2
]

View File

@@ -4,11 +4,6 @@ else
this.exports: this
Rewriter: this.Rewriter
# The lexer reads a stream of CoffeeScript and divvys it up into tagged
# tokens. A minor bit of the ambiguity in the grammar has been avoided by
# pushing some extra smarts into the Lexer.
exports.Lexer: lex: ->
# Constants ============================================================
# Keywords that CoffeScript shares in common with JS.
@@ -20,7 +15,7 @@ JS_KEYWORDS: [
"break", "continue",
"for", "in", "while",
"delete", "instanceof", "typeof",
"switch", "super", "extends"
"switch", "super", "extends", "class"
]
# CoffeeScript-only keywords -- which we're more relaxed about allowing.
@@ -34,11 +29,12 @@ COFFEE_KEYWORDS: [
# The list of keywords passed verbatim to the parser.
KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS
# The list of keywords that are reserved by JavaScript, but not used, and aren't
# used by CoffeeScript. Using these will throw an error at compile time.
# The list of keywords that are reserved by JavaScript, but not used, or are
# used by CoffeeScript internally. Using these will throw an error.
RESERVED: [
"case", "default", "do", "function", "var", "void", "with", "class"
"const", "let", "debugger", "enum", "export", "import", "native"
"case", "default", "do", "function", "var", "void", "with"
"const", "let", "debugger", "enum", "export", "import", "native",
"__extends", "__hasProp"
]
# JavaScript keywords and reserved words together, excluding CoffeeScript ones.
@@ -54,7 +50,7 @@ OPERATOR : /^([+\*&|\/\-%=<>:!?]+)/
WHITESPACE : /^([ \t]+)/
COMMENT : /^(((\n?[ \t]*)?#[^\n]*)+)/
CODE : /^((-|=)>)/
REGEX : /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
REGEX : /^(\/(\S.*?)?([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
LAST_DENTS : /\n([ \t]*)/g
LAST_DENT : /\n([ \t]*)/
@@ -71,10 +67,9 @@ HEREDOC_INDENT : /^[ \t]+/mg
# Tokens which a regular expression will never immediately follow, but which
# a division operator might.
# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
# Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX: [
'IDENTIFIER', 'NUMBER', 'REGEX', 'STRING',
')', '++', '--', ']', '}',
'FALSE', 'NULL', 'TRUE'
'NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE'
]
# Tokens which could legitimately be invoked or indexed.
@@ -87,241 +82,246 @@ ACCESSORS: ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@']
# Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']
# Scan by attempting to match tokens one character at a time. Slow and steady.
lex::tokenize: (code) ->
@code : code # Cleanup code by remove extra line breaks, TODO: chomp
@i : 0 # Current character position we're parsing
@line : 1 # The current line.
@indent : 0 # The current indent level.
@indents : [] # The stack of all indent levels we are currently within.
@tokens : [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while @i < @code.length
@chunk: @code.slice(@i)
@extract_next_token()
@close_indentation()
(new Rewriter()).rewrite @tokens
# The lexer reads a stream of CoffeeScript and divvys it up into tagged
# tokens. A minor bit of the ambiguity in the grammar has been avoided by
# pushing some extra smarts into the Lexer.
exports.Lexer: class Lexer
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed.
lex::extract_next_token: ->
return if @identifier_token()
return if @number_token()
return if @heredoc_token()
return if @string_token()
return if @js_token()
return if @regex_token()
return if @indent_token()
return if @comment_token()
return if @whitespace_token()
return @literal_token()
# Scan by attempting to match tokens one character at a time. Slow and steady.
tokenize: (code) ->
@code : code # Cleanup code by remove extra line breaks, TODO: chomp
@i : 0 # Current character position we're parsing
@line : 1 # The current line.
@indent : 0 # The current indent level.
@indents : [] # The stack of all indent levels we are currently within.
@tokens : [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while @i < @code.length
@chunk: @code.slice(@i)
@extract_next_token()
@close_indentation()
(new Rewriter()).rewrite @tokens
# Tokenizers ==========================================================
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed.
extract_next_token: ->
return if @identifier_token()
return if @number_token()
return if @heredoc_token()
return if @string_token()
return if @js_token()
return if @regex_token()
return if @indent_token()
return if @comment_token()
return if @whitespace_token()
return @literal_token()
# Matches identifying literals: variables, keywords, method names, etc.
lex::identifier_token: ->
return false unless id: @match IDENTIFIER, 1
@tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
if @value() is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@tag(1, 'SOAK_ACCESS')
@tokens.splice(-2, 1)
# Tokenizers ==========================================================
# Matches identifying literals: variables, keywords, method names, etc.
identifier_token: ->
return false unless id: @match IDENTIFIER, 1
@tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
if @value() is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@tag(1, 'SOAK_ACCESS')
@tokens.splice(-2, 1)
else
@tag(1, 'PROPERTY_ACCESS')
tag: 'IDENTIFIER'
tag: id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
@token(tag, id)
@i += id.length
true
# Matches numbers, including decimals, hex, and exponential notation.
number_token: ->
return false unless number: @match NUMBER, 1
@token 'NUMBER', number
@i += number.length
true
# Matches strings, including multi-line strings.
string_token: ->
return false unless string: @match STRING, 1
escaped: string.replace STRING_NEWLINES, " \\\n"
@token 'STRING', escaped
@line += @count string, "\n"
@i += string.length
true
# Matches heredocs, adjusting indentation to the correct level.
heredoc_token: ->
return false unless match = @chunk.match(HEREDOC)
doc: match[2] or match[4]
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace('"', '\\"')
@token 'STRING', '"' + doc + '"'
@line += @count match[1], "\n"
@i += match[1].length
true
# Matches interpolated JavaScript.
js_token: ->
return false unless script: @match JS, 1
@token 'JS', script.replace(JS_CLEANER, '')
@i += script.length
true
# Matches regular expression literals.
regex_token: ->
return false unless regex: @match REGEX, 1
return false if NOT_REGEX.indexOf(@tag()) >= 0
@token 'REGEX', regex
@i += regex.length
true
# Matches and conumes comments.
comment_token: ->
return false unless comment: @match COMMENT, 1
@line += (comment.match(MULTILINER) or []).length
@token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
@token 'TERMINATOR', "\n"
@i += comment.length
true
# Record tokens for indentation differing from the previous line.
indent_token: ->
return false unless indent: @match MULTI_DENT, 1
@line += indent.match(MULTILINER).length
@i += indent.length
next_character: @chunk.match(MULTI_DENT)[4]
prev: @prev(2)
no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
return @suppress_newlines(indent) if no_newlines
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
return @newline_token(indent) if size is @indent
if size > @indent
diff: size - @indent
@token 'INDENT', diff
@indents.push diff
else
@tag(1, 'PROPERTY_ACCESS')
tag: 'IDENTIFIER'
tag: id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
@token(tag, id)
@i += id.length
true
@outdent_token @indent - size
@indent: size
true
# Matches numbers, including decimals, hex, and exponential notation.
lex::number_token: ->
return false unless number: @match NUMBER, 1
@token 'NUMBER', number
@i += number.length
true
# Record an oudent token or tokens, if we're moving back inwards past
# multiple recorded indents.
outdent_token: (move_out) ->
while move_out > 0 and @indents.length
last_indent: @indents.pop()
@token 'OUTDENT', last_indent
move_out -= last_indent
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Matches strings, including multi-line strings.
lex::string_token: ->
return false unless string: @match STRING, 1
escaped: string.replace STRING_NEWLINES, " \\\n"
@token 'STRING', escaped
@line += @count string, "\n"
@i += string.length
true
# Matches and consumes non-meaningful whitespace.
whitespace_token: ->
return false unless space: @match WHITESPACE, 1
prev: @prev()
prev.spaced: true if prev
@i += space.length
true
# Matches heredocs, adjusting indentation to the correct level.
lex::heredoc_token: ->
return false unless match = @chunk.match(HEREDOC)
doc: match[2] or match[4]
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace('"', '\\"')
@token 'STRING', '"' + doc + '"'
@line += @count match[1], "\n"
@i += match[1].length
true
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
newline_token: (newlines) ->
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Matches interpolated JavaScript.
lex::js_token: ->
return false unless script: @match JS, 1
@token 'JS', script.replace(JS_CLEANER, '')
@i += script.length
true
# Tokens to explicitly escape newlines are removed once their job is done.
suppress_newlines: (newlines) ->
@tokens.pop() if @value() is "\\"
true
# Matches regular expression literals.
lex::regex_token: ->
return false unless regex: @match REGEX, 1
return false if NOT_REGEX.indexOf(@tag()) >= 0
@token 'REGEX', regex
@i += regex.length
true
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# the proper order of operations.
literal_token: ->
match: @chunk.match(OPERATOR)
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
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
@i += value.length
true
# Matches and conumes comments.
lex::comment_token: ->
return false unless comment: @match COMMENT, 1
@line += (comment.match(MULTILINER) or []).length
@token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
@token 'TERMINATOR', "\n"
@i += comment.length
true
# Helpers =============================================================
# Record tokens for indentation differing from the previous line.
lex::indent_token: ->
return false unless indent: @match MULTI_DENT, 1
@line += indent.match(MULTILINER).length
@i += indent.length
next_character: @chunk.match(MULTI_DENT)[4]
prev: @prev(2)
no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
return @suppress_newlines(indent) if no_newlines
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
return @newline_token(indent) if size is @indent
if size > @indent
diff: size - @indent
@token 'INDENT', diff
@indents.push diff
else
@outdent_token @indent - size
@indent: size
true
# Add a token to the results, taking note of the line number.
token: (tag, value) ->
@tokens.push([tag, value, @line])
# Record an oudent token or tokens, if we're moving back inwards past
# multiple recorded indents.
lex::outdent_token: (move_out) ->
while move_out > 0 and @indents.length
last_indent: @indents.pop()
@token 'OUTDENT', last_indent
move_out -= last_indent
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Look at a tag in the current token stream.
tag: (index, tag) ->
return unless tok: @prev(index)
return tok[0]: tag if tag?
tok[0]
# Matches and consumes non-meaningful whitespace.
lex::whitespace_token: ->
return false unless space: @match WHITESPACE, 1
prev: @prev()
prev.spaced: true if prev
@i += space.length
true
# Look at a value in the current token stream.
value: (index, val) ->
return unless tok: @prev(index)
return tok[1]: val if val?
tok[1]
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
lex::newline_token: (newlines) ->
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Look at a previous token.
prev: (index) ->
@tokens[@tokens.length - (index or 1)]
# Tokens to explicitly escape newlines are removed once their job is done.
lex::suppress_newlines: (newlines) ->
@tokens.pop() if @value() is "\\"
true
# Count the occurences of a character in a string.
count: (string, letter) ->
num: 0
pos: string.indexOf(letter)
while pos isnt -1
num += 1
pos: string.indexOf(letter, pos + 1)
num
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# the proper order of operations.
lex::literal_token: ->
match: @chunk.match(OPERATOR)
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
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
@i += value.length
true
# Attempt to match a string against the current chunk, returning the indexed
# match.
match: (regex, index) ->
return false unless m: @chunk.match(regex)
if m then m[index] else false
# Helpers =============================================================
# A source of ambiguity in our grammar was parameter lists in function
# definitions (as opposed to argument lists in function calls). Tag
# parameter identifiers in order to avoid this. Also, parameter lists can
# make use of splats.
tag_parameters: ->
return if @tag() isnt ')'
i: 0
while true
i += 1
tok: @prev(i)
return if not tok
switch tok[0]
when 'IDENTIFIER' then tok[0]: 'PARAM'
when ')' then tok[0]: 'PARAM_END'
when '(' then return tok[0]: 'PARAM_START'
true
# Add a token to the results, taking note of the line number.
lex::token: (tag, value) ->
@tokens.push([tag, value, @line])
# Look at a tag in the current token stream.
lex::tag: (index, tag) ->
return unless tok: @prev(index)
return tok[0]: tag if tag?
tok[0]
# Look at a value in the current token stream.
lex::value: (index, val) ->
return unless tok: @prev(index)
return tok[1]: val if val?
tok[1]
# Look at a previous token.
lex::prev: (index) ->
@tokens[@tokens.length - (index or 1)]
# Count the occurences of a character in a string.
lex::count: (string, letter) ->
num: 0
pos: string.indexOf(letter)
while pos isnt -1
num += 1
pos: string.indexOf(letter, pos + 1)
num
# Attempt to match a string against the current chunk, returning the indexed
# match.
lex::match: (regex, index) ->
return false unless m: @chunk.match(regex)
if m then m[index] else false
# A source of ambiguity in our grammar was parameter lists in function
# definitions (as opposed to argument lists in function calls). Tag
# parameter identifiers in order to avoid this. Also, parameter lists can
# make use of splats.
lex::tag_parameters: ->
return if @tag() isnt ')'
i: 0
while true
i += 1
tok: @prev(i)
return if not tok
switch tok[0]
when 'IDENTIFIER' then tok[0]: 'PARAM'
when ')' then tok[0]: 'PARAM_END'
when '(' then return tok[0]: 'PARAM_START'
true
# Close up all remaining open blocks. IF the first token is an indent,
# axe it.
lex::close_indentation: ->
@outdent_token(@indent)
# Close up all remaining open blocks. IF the first token is an indent,
# axe it.
close_indentation: ->
@outdent_token(@indent)

View File

@@ -12,6 +12,7 @@ TRAILING_WHITESPACE: /\s+$/gm
# Keep the identifier regex in sync with the Lexer.
IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/
# Merge objects.
merge: (options, overrides) ->
fresh: {}
@@ -47,6 +48,7 @@ statement: (klass, only) ->
klass::is_statement: -> true
(klass::is_statement_only: -> true) if only
# The abstract base class for all CoffeeScript nodes.
# All nodes are implement a "compile_node" method, which performs the
# code generation for that node. To compile a node, call the "compile"
@@ -54,69 +56,75 @@ 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.
BaseNode: exports.BaseNode: ->
exports.BaseNode: class 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.
BaseNode::compile: (o) ->
@options: merge o or {}
@indent: o.indent
del @options, 'operation' unless @operation_sensitive()
top: if @top_sensitive() then @options.top else del @options, 'top'
closure: @is_statement() and not @is_statement_only() and not top and
not @options.returns and not (this instanceof CommentNode) and
not @contains (node) -> node.is_statement_only()
if closure then @compile_closure(@options) else @compile_node(@options)
# 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.
compile: (o) ->
@options: merge o or {}
@indent: o.indent
del @options, 'operation' unless @operation_sensitive()
top: if @top_sensitive() then @options.top else del @options, 'top'
closure: @is_statement() and not @is_statement_only() and not top and
not @options.returns and not (this instanceof CommentNode) and
not @contains (node) -> node.is_statement_only()
if closure then @compile_closure(@options) else @compile_node(@options)
# Statements converted into expressions share scope with their parent
# closure, to preserve JavaScript-style lexical scope.
BaseNode::compile_closure: (o) ->
@indent: o.indent
o.shared_scope: o.scope
ClosureNode.wrap(this).compile(o)
# Statements converted into expressions share scope with their parent
# closure, to preserve JavaScript-style lexical scope.
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]
# 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.
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.
BaseNode::idt: (tabs) ->
idt: (@indent || '')
idt += TAB for i in [0...(tabs or 0)]
idt
# Quick short method for the current indentation level, plus tabbing in.
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?
BaseNode::contains: (block) ->
for node in @children
return true if block(node)
return true if node instanceof BaseNode and node.contains block
false
# Does this node, or any of its children, contain a node of a certain kind?
contains: (block) ->
for node in @children
return true if block(node)
return true if node.contains and node.contains block
false
# toString representation of the node, for inspecting the parse tree.
BaseNode::toString: (idt) ->
idt ||= ''
'\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
# Perform an in-order traversal of the AST.
traverse: (block) ->
for node in @children
block node
node.traverse block if node.traverse
# toString representation of the node, for inspecting the parse tree.
toString: (idt) ->
idt ||= ''
'\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
# Default implementations of the common node methods.
unwrap: -> this
children: []
is_statement: -> false
is_statement_only: -> false
top_sensitive: -> false
operation_sensitive: -> false
# Default implementations of the common node methods.
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 BaseNode, {
exports.Expressions: class Expressions extends BaseNode
type: 'Expressions'
constructor: (nodes) ->
@children: @expressions: compact flatten nodes or []
this
# Tack an expression on to the end of this expression list.
push: (node) ->
@@ -144,7 +152,7 @@ Expressions: exports.Expressions: inherit BaseNode, {
compile: (o) ->
o ||= {}
if o.scope then BaseNode::compile.call(this, o) else @compile_root(o)
if o.scope then super(o) else @compile_root(o)
# Compile each expression in the Expressions body.
compile_node: (o) ->
@@ -181,8 +189,6 @@ Expressions: exports.Expressions: inherit BaseNode, {
# Otherwise, we can just return the value of the expression.
return @idt() + 'return ' + node.compile(o) + ';'
}
# Wrap up a node as an Expressions, unless it already is one.
Expressions.wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
@@ -190,20 +196,22 @@ Expressions.wrap: (nodes) ->
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 BaseNode, {
exports.LiteralNode: class LiteralNode extends BaseNode
type: 'Literal'
constructor: (value) ->
@value: value
this
# Break and continue must be treated as statements -- they lose their meaning
# when wrapped in a closure.
is_statement: ->
@value is 'break' or @value is 'continue'
is_statement_only: LiteralNode::is_statement
compile_node: (o) ->
idt: if @is_statement() then @idt() else ''
end: if @is_statement() then ';' else ''
@@ -212,35 +220,29 @@ LiteralNode: exports.LiteralNode: inherit BaseNode, {
toString: (idt) ->
' "' + @value + '"'
}
LiteralNode::is_statement_only: LiteralNode::is_statement
# Return an expression, or wrap it in a closure and return it.
ReturnNode: exports.ReturnNode: inherit BaseNode, {
exports.ReturnNode: class ReturnNode extends BaseNode
type: 'Return'
constructor: (expression) ->
@children: [@expression: expression]
this
compile_node: (o) ->
return @expression.compile(merge(o, {returns: true})) if @expression.is_statement()
@idt() + 'return ' + @expression.compile(o) + ';'
}
statement ReturnNode, true
# A value, indexed or dotted into, or vanilla.
ValueNode: exports.ValueNode: inherit BaseNode, {
exports.ValueNode: class ValueNode extends BaseNode
type: 'Value'
SOAK: " == undefined ? undefined : "
constructor: (base, properties) ->
@children: flatten [@base: base, @properties: (properties or [])]
this
push: (prop) ->
@properties.push(prop)
@@ -298,11 +300,10 @@ ValueNode: exports.ValueNode: inherit BaseNode, {
if op and soaked then '(' + complete + ')' else complete
}
# Pass through CoffeeScript comments into JavaScript comments at the
# same position.
CommentNode: exports.CommentNode: inherit BaseNode, {
exports.CommentNode: class CommentNode extends BaseNode
type: 'Comment'
constructor: (lines) ->
@@ -312,19 +313,17 @@ CommentNode: exports.CommentNode: inherit BaseNode, {
compile_node: (o) ->
@idt() + '//' + @lines.join('\n' + @idt() + '//')
}
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 BaseNode, {
exports.CallNode: class CallNode extends BaseNode
type: 'Call'
constructor: (variable, args) ->
@children: flatten [@variable: variable, @args: (args or [])]
@prefix: ''
this
new_instance: ->
@prefix: 'new '
@@ -356,50 +355,46 @@ CallNode: exports.CallNode: inherit BaseNode, {
compile_splat: (o) ->
meth: @variable.compile o
obj: @variable.source or 'this'
if obj.match(/\(/)
temp: o.scope.free_variable()
obj: temp
meth: '(' + temp + ' = ' + @variable.source + ')' + @variable.last
args: for arg, i in @args
code: arg.compile o
code: if arg instanceof SplatNode then code else '[' + code + ']'
if i is 0 then code else '.concat(' + code + ')'
@prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')'
}
# Node to extend an object's prototype with an ancestor object.
# After goog.inherits from the Closure Library.
ExtendsNode: exports.ExtendsNode: inherit BaseNode, {
exports.ExtendsNode: class ExtendsNode extends BaseNode
type: 'Extends'
code: '''
function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
}
'''
constructor: (child, parent) ->
@children: [@child: child, @parent: parent]
this
# Hooking one constructor into another's prototype chain.
compile_node: (o) ->
construct: o.scope.free_variable()
child: @child.compile(o)
parent: @parent.compile(o)
prefix: ''
if not (@child instanceof ValueNode) or @child.has_properties() or not (@child.unwrap() instanceof LiteralNode)
child_var: o.scope.free_variable()
prefix += @idt() + child_var + ' = ' + child + ';\n'
child: child_var
if not (@parent instanceof ValueNode) or @parent.has_properties() or not (@parent.unwrap() instanceof LiteralNode)
parent_var: o.scope.free_variable()
prefix += @idt() + parent_var + ' = ' + parent + ';\n'
parent: parent_var
prefix + @idt() + construct + ' = function(){};\n' + @idt() +
construct + '.prototype = ' + parent + ".prototype;\n" + @idt() +
child + '.__superClass__ = ' + parent + ".prototype;\n" + @idt() +
child + '.prototype = new ' + construct + "();\n" + @idt() +
child + '.prototype.constructor = ' + child + ';'
o.scope.assign('__extends', @code, true)
ref: new ValueNode new LiteralNode '__extends'
call: new CallNode ref, [@child, @parent]
call.compile(o)
}
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 BaseNode, {
exports.AccessorNode: class AccessorNode extends BaseNode
type: 'Accessor'
constructor: (name, tag) ->
@@ -411,31 +406,27 @@ AccessorNode: exports.AccessorNode: inherit BaseNode, {
compile_node: (o) ->
'.' + (if @prototype then 'prototype.' else '') + @name.compile(o)
}
# An indexed accessor into a part of an array or object.
IndexNode: exports.IndexNode: inherit BaseNode, {
exports.IndexNode: class IndexNode extends BaseNode
type: 'Index'
constructor: (index, tag) ->
@children: [@index: index]
@soak_node: tag is 'soak'
this
compile_node: (o) ->
'[' + @index.compile(o) + ']'
}
# 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 BaseNode, {
exports.RangeNode: class RangeNode extends BaseNode
type: 'Range'
constructor: (from, to, exclusive) ->
@children: [@from: from, @to: to]
@exclusive: !!exclusive
this
compile_variables: (o) ->
@indent: o.indent
@@ -447,7 +438,7 @@ RangeNode: exports.RangeNode: inherit BaseNode, {
return @compile_array(o) unless o.index
idx: del o, 'index'
step: del o, 'step'
vars: idx + '=' + @from_var
vars: idx + ' = ' + @from_var
step: if step then step.compile(o) else '1'
equals: if @exclusive then '' else '='
intro: '(' + @from_var + ' <= ' + @to_var + ' ? ' + idx
@@ -464,12 +455,11 @@ RangeNode: exports.RangeNode: inherit BaseNode, {
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, new LiteralNode(name))])
(new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
}
# 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 BaseNode, {
exports.SliceNode: class SliceNode extends BaseNode
type: 'Slice'
constructor: (range) ->
@@ -482,15 +472,13 @@ SliceNode: exports.SliceNode: inherit BaseNode, {
plus_part: if @range.exclusive then '' else ' + 1'
".slice(" + from + ', ' + to + plus_part + ')'
}
# An object literal.
ObjectNode: exports.ObjectNode: inherit BaseNode, {
exports.ObjectNode: class ObjectNode extends BaseNode
type: 'Object'
constructor: (props) ->
@children: @objects: @properties: props or []
this
# All the mucking about with commas is to make sure that CommentNodes and
# AssignNodes get interleaved correctly, with no trailing commas or
@@ -509,15 +497,49 @@ ObjectNode: exports.ObjectNode: inherit BaseNode, {
inner: if props then '\n' + props + '\n' + @idt() else ''
'{' + inner + '}'
}
# A class literal, including optional superclass and constructor.
exports.ClassNode: class ClassNode extends BaseNode
type: 'Class'
constructor: (variable, parent, props) ->
@children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
compile_node: (o) ->
extension: @parent and new ExtendsNode(@variable, @parent)
constructor: null
props: new Expressions()
o.top: true
ret: del o, 'returns'
for prop in @properties
if prop.variable and prop.variable.base.value is 'constructor'
func: prop.value
func.body.push(new ReturnNode(new LiteralNode('this')))
constructor: new AssignNode(@variable, func)
else
if prop.variable
val: new ValueNode(@variable, [new AccessorNode(prop.variable, 'prototype')])
prop: new AssignNode(val, prop.value)
props.push prop
constructor: new AssignNode(@variable, new CodeNode()) unless constructor
construct: @idt() + constructor.compile(o) + ';\n'
props: if props.empty() then '' else props.compile(o) + '\n'
extension: if extension then @idt() + extension.compile(o) + ';\n' else ''
returns: if ret then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else ''
construct + extension + props + returns
statement ClassNode
# An array literal.
ArrayNode: exports.ArrayNode: inherit BaseNode, {
exports.ArrayNode: class ArrayNode extends BaseNode
type: 'Array'
constructor: (objects) ->
@children: @objects: objects or []
this
compile_node: (o) ->
o.indent: @idt(1)
@@ -533,7 +555,6 @@ ArrayNode: exports.ArrayNode: inherit BaseNode, {
ending: if objects.indexOf('\n') >= 0 then "\n" + @idt() + ']' else ']'
'[' + objects + ending
}
# A faux-node that is never created by the grammar, but is used during
# code generation to generate a quick "array.push(value)" tree of nodes.
@@ -548,6 +569,7 @@ PushNode: exports.PushNode: {
}
# A faux-node used to wrap an expressions body in a closure.
ClosureNode: exports.ClosureNode: {
@@ -558,8 +580,9 @@ ClosureNode: exports.ClosureNode: {
}
# Setting the value of a local variable, or the value of an object property.
AssignNode: exports.AssignNode: inherit BaseNode, {
exports.AssignNode: class AssignNode extends BaseNode
type: 'Assign'
PROTO_ASSIGN: /^(\S+)\.prototype/
@@ -568,7 +591,6 @@ AssignNode: exports.AssignNode: inherit BaseNode, {
constructor: (variable, value, context) ->
@children: [@variable: variable, @value: value]
@context: context
this
top_sensitive: ->
true
@@ -604,7 +626,8 @@ AssignNode: exports.AssignNode: inherit BaseNode, {
# See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
compile_pattern_match: (o) ->
val_var: o.scope.free_variable()
assigns: [@idt() + val_var + ' = ' + @value.compile(o) + ';']
value: if @value.is_statement() then ClosureNode.wrap(@value) else @value
assigns: [@idt() + val_var + ' = ' + value.compile(o) + ';']
o.top: true
o.as_statement: true
for obj, i in @variable.base.objects
@@ -617,7 +640,9 @@ AssignNode: exports.AssignNode: inherit BaseNode, {
idx: new LiteralNode(idx) unless typeof idx is 'object'
val: new ValueNode(new LiteralNode(val_var), [new access_class(idx)])
assigns.push(new AssignNode(obj, val).compile(o))
assigns.join("\n")
code: assigns.join("\n")
code += '\n' + @idt() + 'return ' + @variable.compile(o) + ';' if o.returns
code
compile_splice: (o) ->
name: @variable.compile(merge(o, {only_first: true}))
@@ -628,18 +653,16 @@ AssignNode: exports.AssignNode: inherit BaseNode, {
to: range.to.compile(o) + ' - ' + from + plus
name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + @value.compile(o) + '))'
}
# 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 BaseNode, {
exports.CodeNode: class CodeNode extends BaseNode
type: 'Code'
constructor: (params, body, tag) ->
@params: params
@body: body
@params: params or []
@body: body or new Expressions()
@bound: tag is 'boundfunc'
this
compile_node: (o) ->
shared_scope: del o, 'shared_scope'
@@ -667,22 +690,26 @@ CodeNode: exports.CodeNode: inherit BaseNode, {
top_sensitive: ->
true
real_children: ->
flatten [@params, @body.expressions]
traverse: (block) ->
block this
block(child) for child in @real_children()
toString: (idt) ->
idt ||= ''
children: flatten [@params, @body.expressions]
'\n' + idt + @type + (child.toString(idt + TAB) for child in children).join('')
'\n' + idt + @type + (child.toString(idt + TAB) for child in @real_children()).join('')
}
# A splat, either as a parameter to a function, an argument to a call,
# or in a destructuring assignment.
SplatNode: exports.SplatNode: inherit BaseNode, {
exports.SplatNode: class SplatNode extends BaseNode
type: 'Splat'
constructor: (name) ->
name: new LiteralNode(name) unless name.compile
@children: [@name: name]
this
compile_node: (o) ->
if @index? then @compile_param(o) else @name.compile(o)
@@ -695,17 +722,15 @@ SplatNode: exports.SplatNode: inherit BaseNode, {
compile_value: (o, name, index) ->
"Array.prototype.slice.call(" + name + ', ' + index + ')'
}
# 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 BaseNode, {
exports.WhileNode: class WhileNode extends BaseNode
type: 'While'
constructor: (condition, opts) ->
@children:[@condition: condition]
@filter: opts and opts.filter
this
add_body: (body) ->
@children.push @body: body
@@ -731,13 +756,12 @@ WhileNode: exports.WhileNode: inherit BaseNode, {
@body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter
pre + ' {\n' + @body.compile(o) + '\n' + @idt() + '}' + post
}
statement WhileNode
# Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents.
OpNode: exports.OpNode: inherit BaseNode, {
exports.OpNode: class OpNode extends BaseNode
type: 'Op'
CONVERSIONS: {
@@ -759,7 +783,6 @@ OpNode: exports.OpNode: inherit BaseNode, {
@children: compact [@first: first, @second: second]
@operator: @CONVERSIONS[operator] or operator
@flip: !!flip
this
is_unary: ->
not @second
@@ -798,10 +821,9 @@ OpNode: exports.OpNode: inherit BaseNode, {
parts: parts.reverse() if @flip
parts.join('')
}
# A try/catch/finally block.
TryNode: exports.TryNode: inherit BaseNode, {
exports.TryNode: class TryNode extends BaseNode
type: 'Try'
constructor: (attempt, error, recovery, ensure) ->
@@ -817,51 +839,45 @@ TryNode: exports.TryNode: inherit BaseNode, {
finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + '\n' + @idt() + '}'
@idt() + 'try {\n' + @attempt.compile(o) + '\n' + @idt() + '}' + catch_part + finally_part
}
statement TryNode
# Throw an exception.
ThrowNode: exports.ThrowNode: inherit BaseNode, {
exports.ThrowNode: class ThrowNode extends BaseNode
type: 'Throw'
constructor: (expression) ->
@children: [@expression: expression]
this
compile_node: (o) ->
@idt() + 'throw ' + @expression.compile(o) + ';'
}
statement ThrowNode, true
# Check an expression for existence (meaning not null or undefined).
ExistenceNode: exports.ExistenceNode: inherit BaseNode, {
exports.ExistenceNode: class ExistenceNode extends BaseNode
type: 'Existence'
constructor: (expression) ->
@children: [@expression: expression]
this
compile_node: (o) ->
ExistenceNode.compile_test(o, @expression)
}
ExistenceNode.compile_test: (o, variable) ->
[first, second]: [variable, variable]
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 BaseNode, {
exports.ParentheticalNode: class ParentheticalNode extends BaseNode
type: 'Paren'
constructor: (expression) ->
@children: [@expression: expression]
this
is_statement: ->
@expression.is_statement()
@@ -873,13 +889,12 @@ ParentheticalNode: exports.ParentheticalNode: inherit BaseNode, {
code: code.substr(o, l-1) if code.substr(l-1, 1) is ';'
'(' + code + ')'
}
# The replacement for the for loop is an array comprehension (that compiles)
# 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 BaseNode, {
exports.ForNode: class ForNode extends BaseNode
type: 'For'
constructor: (body, source, name, index) ->
@@ -892,7 +907,6 @@ ForNode: exports.ForNode: inherit BaseNode, {
@object: !!source.object
[@name, @index]: [@index, @name] if @object
@children: compact [@body, @source, @filter]
this
top_sensitive: ->
true
@@ -919,9 +933,11 @@ ForNode: exports.ForNode: inherit BaseNode, {
else
index_var: null
source_part: svar + ' = ' + @source.compile(o) + ';\n' + @idt()
step_part: if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
for_part: ivar + ' = 0; ' + ivar + ' < ' + svar + '.length; ' + step_part
var_part: body_dent + name + ' = ' + svar + '[' + ivar + '];\n' if name
if not @object
lvar: scope.free_variable()
step_part: if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
for_part: ivar + ' = 0, ' + lvar + ' = ' + svar + '.length; ' + ivar + ' < ' + lvar + '; ' + step_part
set_result: if rvar then @idt() + rvar + ' = []; ' else @idt()
return_result: rvar or ''
body: ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode
@@ -941,15 +957,14 @@ ForNode: exports.ForNode: inherit BaseNode, {
close: if @object then '}}\n' else '}\n'
set_result + source_part + 'for (' + for_part + ') {\n' + var_part + body + '\n' + @idt() + close + @idt() + return_result
}
statement ForNode
# If/else statements. Switch/whens get compiled into these. Acts as an
# 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 BaseNode, {
exports.IfNode: class IfNode extends BaseNode
type: 'If'
constructor: (condition, body, else_body, tags) ->
@@ -960,7 +975,6 @@ IfNode: exports.IfNode: inherit BaseNode, {
@tags: tags or {}
@multiple: true if @condition instanceof Array
@condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert
this
push: (else_body) ->
eb: else_body.unwrap()
@@ -1041,5 +1055,3 @@ IfNode: exports.IfNode: inherit BaseNode, {
if_part: @condition.compile(o) + ' ? ' + @body.compile(o)
else_part: if @else_body then @else_body.compile(o) else 'null'
if_part + ' : ' + else_part
}

View File

@@ -1,72 +1,73 @@
# Create an OptionParser with a list of valid options.
op: exports.OptionParser: (rules, banner) ->
@banner: banner or 'Usage: [Options]'
@options_title: 'Available options:'
@rules: build_rules(rules)
this
# Create an OptionParser with a list of valid options, in the form:
# [short-flag (optional), long-flag, description]
# And an optional banner for the usage help.
exports.OptionParser: class OptionParser
# Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
op::parse: (args) ->
options: {arguments: []}
args: args.concat []
while (arg: args.shift())
is_option: false
constructor: (rules, banner) ->
@banner: banner
@rules: build_rules(rules)
# Parse the argument array, populating an options object with all of the
# specified options, and returning it. options.arguments will be an array
# containing the remaning non-option arguments.
parse: (args) ->
options: {arguments: []}
args: normalize_arguments args
while arg: args.shift()
is_option: !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
matched_rule: no
for rule in @rules
if rule.letter is arg or rule.flag is arg
options[rule.name]: if rule.has_argument then args.shift() else true
matched_rule: yes
break
throw new Error "unrecognized option: " + arg if is_option and not matched_rule
options.arguments.push arg unless is_option
options
# Return the help text for this OptionParser, for --help and such.
help: ->
lines: ['Available options:']
lines.unshift @banner + '\n' if @banner
for rule in @rules
if rule.letter is arg or rule.flag is arg
options[rule.name]: if rule.argument then args.shift() else true
is_option: true
break
options.arguments.push arg unless is_option
options
# Return the help text for this OptionParser, for --help and such.
op::help: ->
longest: 0
has_shorts: false
lines: [@banner, '', @options_title]
for rule in @rules
has_shorts: true if rule.letter
longest: rule.flag.length if rule.flag.length > longest
for rule in @rules
if has_shorts
text: if rule.letter then spaces(2) + rule.letter + ', ' else spaces(6)
text += spaces(longest, rule.flag) + spaces(3)
text += rule.description
lines.push text
lines.join('\n')
# Private:
spaces: 15 - rule.flag.length
spaces: if spaces > 0 then (' ' for i in [0..spaces]).join('') else ''
let_part: if rule.letter then rule.letter + ', ' else ' '
lines.push ' ' + let_part + rule.flag + spaces + rule.description
lines.join('\n')
# Regex matchers for option flags.
LONG_FLAG: /^(--[\w\-]+)/
SHORT_FLAG: /^(-\w+)/
LONG_FLAG: /^(--\w[\w\-]+)/
SHORT_FLAG: /^(-\w)/
MULTI_FLAG: /^-(\w{2,})/
OPTIONAL: /\[(.+)\]/
# Build rules from a list of valid switch tuples in the form:
# [letter-flag, long-flag, help], or [long-flag, help].
build_rules: (rules) ->
for tuple in rules
tuple.unshift(null) if tuple.length < 3
build_rule(tuple...)
tuple.unshift null if tuple.length < 3
build_rule tuple...
# Build a rule from a short-letter-flag, long-form-flag, and help text.
build_rule: (letter, flag, description) ->
match: flag.match(OPTIONAL)
flag: flag.match(LONG_FLAG)[1]
{
name: flag.substr(2)
name: flag.substr 2
letter: letter
flag: flag
description: description
argument: !!(match and match[1])
has_argument: !!(match and match[1])
}
# Space-pad a string with the specified number of characters.
spaces: (num, text) ->
builder: []
if text
return text if text.length >= num
num -= text.length
builder.push text
while num -= 1 then builder.push ' '
builder.join ''
# Normalize arguments by expanding merged flags into multiple flags.
normalize_arguments: (args) ->
args: args.slice 0
result: []
for arg in args
if match: arg.match MULTI_FLAG
result.push '-' + l for l in match[1].split ''
else
result.push arg
result

View File

@@ -1,10 +1,5 @@
this.exports: this unless process?
# In order to keep the grammar simple, the stream of tokens that the Lexer
# emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
# indentation, and single-line flavors of expressions.
exports.Rewriter: re: ->
# Tokens that must be balanced.
BALANCED_PAIRS: [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'],
['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'],
@@ -37,217 +32,224 @@ for pair in BALANCED_PAIRS
# Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN', 'PARAM_START']
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
# Rewrite the token stream in multiple passes, one logical filter at
# a time. This could certainly be changed into a single pass through the
# stream, with a big ol' efficient switch, but it's much nicer like this.
re::rewrite: (tokens) ->
@tokens: tokens
@adjust_comments()
@remove_leading_newlines()
@remove_mid_expression_newlines()
@move_commas_outside_outdents()
@close_open_calls_and_indexes()
@add_implicit_indentation()
@add_implicit_parentheses()
@ensure_balance(BALANCED_PAIRS)
@rewrite_closing_parens()
@tokens
# In order to keep the grammar simple, the stream of tokens that the Lexer
# emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
# indentation, and single-line flavors of expressions.
exports.Rewriter: class Rewriter
# Rewrite the token stream, looking one token ahead and behind.
# Allow the return value of the block to tell us how many tokens to move
# forwards (or backwards) in the stream, to make sure we don't miss anything
# as the stream changes length under our feet.
re::scan_tokens: (block) ->
i: 0
while true
break unless @tokens[i]
move: block(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
i += move
true
# Rewrite the token stream in multiple passes, one logical filter at
# a time. This could certainly be changed into a single pass through the
# stream, with a big ol' efficient switch, but it's much nicer like this.
rewrite: (tokens) ->
@tokens: tokens
@adjust_comments()
@remove_leading_newlines()
@remove_mid_expression_newlines()
@move_commas_outside_outdents()
@close_open_calls_and_indexes()
@add_implicit_indentation()
@add_implicit_parentheses()
@ensure_balance(BALANCED_PAIRS)
@rewrite_closing_parens()
@tokens
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on their own line.
re::adjust_comments: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless token[0] is 'COMMENT'
before: @tokens[i - 2]
after: @tokens[i + 2]
if before and after and
((before[0] is 'INDENT' and after[0] is 'OUTDENT') or
(before[0] is 'OUTDENT' and after[0] is 'INDENT')) and
before[1] is after[1]
@tokens.splice(i + 2, 1)
@tokens.splice(i - 2, 1)
return 0
else if prev and prev[0] is 'TERMINATOR' and after and after[0] is 'INDENT'
@tokens.splice(i + 2, 1)
@tokens[i - 1]: after
return 1
else if prev and prev[0] isnt 'TERMINATOR' and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
@tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]])
return 2
else
return 1
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
re::remove_leading_newlines: ->
@tokens.shift() if @tokens[0][0] is 'TERMINATOR'
# Some blocks occur in the middle of expressions -- when we're expecting
# this, remove their trailing newlines.
re::remove_mid_expression_newlines: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless post and EXPRESSION_CLOSE.indexOf(post[0]) >= 0 and token[0] is 'TERMINATOR'
@tokens.splice(i, 1)
return 0
# Make sure that we don't accidentally break trailing commas, which need
# to go on the outside of expression closers.
re::move_commas_outside_outdents: ->
@scan_tokens (prev, token, post, i) =>
@tokens.splice(i, 1, token) if token[0] is 'OUTDENT' and prev[0] is ','
return 1
# We've tagged the opening parenthesis of a method call, and the opening
# bracket of an indexing operation. Match them with their close.
re::close_open_calls_and_indexes: ->
parens: [0]
brackets: [0]
@scan_tokens (prev, token, post, i) =>
switch token[0]
when 'CALL_START' then parens.push(0)
when 'INDEX_START' then brackets.push(0)
when '(' then parens[parens.length - 1] += 1
when '[' then brackets[brackets.length - 1] += 1
when ')'
if parens[parens.length - 1] is 0
parens.pop()
token[0]: 'CALL_END'
else
parens[parens.length - 1] -= 1
when ']'
if brackets[brackets.length - 1] == 0
brackets.pop()
token[0]: 'INDEX_END'
else
brackets[brackets.length - 1] -= 1
return 1
# Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them.
re::add_implicit_parentheses: ->
stack: [0]
@scan_tokens (prev, token, post, i) =>
tag: token[0]
stack.push(0) if tag is 'INDENT'
if tag is 'OUTDENT'
last: stack.pop()
stack[stack.length - 1] += last
if IMPLICIT_END.indexOf(tag) >= 0 or !post?
return 1 if tag is 'INDENT' and prev and IMPLICIT_BLOCK.indexOf(prev[0]) >= 0
if stack[stack.length - 1] > 0 or tag is 'INDENT'
idx: if tag is 'OUTDENT' then i + 1 else i
stack_pointer: if tag is 'INDENT' then 2 else 1
for tmp in [0...stack[stack.length - stack_pointer]]
@tokens.splice(idx, 0, ['CALL_END', ')', token[2]])
size: stack[stack.length - stack_pointer] + 1
stack[stack.length - stack_pointer]: 0
return size
return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(tag) >= 0
@tokens.splice(i, 0, ['CALL_START', '(', token[2]])
stack[stack.length - 1] += 1
return 2
# Because our grammar is LALR(1), it can't handle some single-line
# expressions that lack ending delimiters. Use the lexer to add the implicit
# blocks, so it doesn't need to.
# ')' can close a single-line block, but we need to make sure it's balanced.
re::add_implicit_indentation: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless SINGLE_LINERS.indexOf(token[0]) >= 0 and post[0] isnt 'INDENT' and
not (token[0] is 'ELSE' and post[0] is 'IF')
starter: token[0]
@tokens.splice(i + 1, 0, ['INDENT', 2, token[2]])
idx: i + 1
parens: 0
# Rewrite the token stream, looking one token ahead and behind.
# Allow the return value of the block to tell us how many tokens to move
# forwards (or backwards) in the stream, to make sure we don't miss anything
# as the stream changes length under our feet.
scan_tokens: (block) ->
i: 0
while true
idx += 1
tok: @tokens[idx]
if (not tok or
(SINGLE_CLOSERS.indexOf(tok[0]) >= 0 and tok[1] isnt ';') or
(tok[0] is ')' && parens is 0)) and
not (starter is 'ELSE' and tok[0] is 'ELSE')
insertion: if @tokens[idx - 1][0] is "," then idx - 1 else idx
@tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]])
break
parens += 1 if tok[0] is '('
parens -= 1 if tok[0] is ')'
return 1 unless token[0] is 'THEN'
@tokens.splice(i, 1)
return 0
break unless @tokens[i]
move: block(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
i += move
true
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
re::ensure_balance: (pairs) ->
levels: {}
@scan_tokens (prev, token, post, i) =>
for pair in pairs
[open, close]: pair
levels[open] ||= 0
levels[open] += 1 if token[0] is open
levels[open] -= 1 if token[0] is close
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
# We'd like to support syntax like this:
# el.click((event) ->
# el.hide())
# In order to accomplish this, move outdents that follow closing parens
# inwards, safely. The steps to accomplish this are:
#
# 1. Check that all paired tokens are balanced and in order.
# 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
# to the stack. If you see an ')' or OUTDENT, pop the stack and replace
# it with the inverse of what we've just popped.
# 3. Keep track of "debt" for tokens that we fake, to make sure we end
# up balanced in the end.
#
re::rewrite_closing_parens: ->
stack: []
debt: {}
(debt[key]: 0) for key, val of INVERSES
@scan_tokens (prev, token, post, i) =>
tag: token[0]
inv: INVERSES[token[0]]
# Push openers onto the stack.
if EXPRESSION_START.indexOf(tag) >= 0
stack.push(token)
return 1
# The end of an expression, check stack and debt for a pair.
else if EXPRESSION_TAIL.indexOf(tag) >= 0
# If the tag is already in our debt, swallow it.
if debt[inv] > 0
debt[inv] -= 1
@tokens.splice(i, 1)
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on their own line.
adjust_comments: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless token[0] is 'COMMENT'
before: @tokens[i - 2]
after: @tokens[i + 2]
if before and after and
((before[0] is 'INDENT' and after[0] is 'OUTDENT') or
(before[0] is 'OUTDENT' and after[0] is 'INDENT')) and
before[1] is after[1]
@tokens.splice(i + 2, 1)
@tokens.splice(i - 2, 1)
return 0
else if prev and prev[0] is 'TERMINATOR' and after and after[0] is 'INDENT'
@tokens.splice(i + 2, 1)
@tokens[i - 1]: after
return 1
else if prev and prev[0] isnt 'TERMINATOR' and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
@tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]])
return 2
else
# Pop the stack of open delimiters.
match: stack.pop()
mtag: match[0]
# Continue onwards if it's the expected tag.
if tag is INVERSES[mtag]
return 1
else
# Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1
val: if mtag is 'INDENT' then match[1] else INVERSES[mtag]
@tokens.splice(i, 0, [INVERSES[mtag], val])
return 1
else
return 1
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
remove_leading_newlines: ->
@tokens.shift() if @tokens[0][0] is 'TERMINATOR'
# Some blocks occur in the middle of expressions -- when we're expecting
# this, remove their trailing newlines.
remove_mid_expression_newlines: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless post and EXPRESSION_CLOSE.indexOf(post[0]) >= 0 and token[0] is 'TERMINATOR'
@tokens.splice(i, 1)
return 0
# Make sure that we don't accidentally break trailing commas, which need
# to go on the outside of expression closers.
move_commas_outside_outdents: ->
@scan_tokens (prev, token, post, i) =>
@tokens.splice(i, 1, token) if token[0] is 'OUTDENT' and prev[0] is ','
return 1
# We've tagged the opening parenthesis of a method call, and the opening
# bracket of an indexing operation. Match them with their close.
close_open_calls_and_indexes: ->
parens: [0]
brackets: [0]
@scan_tokens (prev, token, post, i) =>
switch token[0]
when 'CALL_START' then parens.push(0)
when 'INDEX_START' then brackets.push(0)
when '(' then parens[parens.length - 1] += 1
when '[' then brackets[brackets.length - 1] += 1
when ')'
if parens[parens.length - 1] is 0
parens.pop()
token[0]: 'CALL_END'
else
parens[parens.length - 1] -= 1
when ']'
if brackets[brackets.length - 1] == 0
brackets.pop()
token[0]: 'INDEX_END'
else
brackets[brackets.length - 1] -= 1
return 1
# Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them.
add_implicit_parentheses: ->
stack: [0]
@scan_tokens (prev, token, post, i) =>
tag: token[0]
stack.push(0) if tag is 'INDENT'
if tag is 'OUTDENT'
last: stack.pop()
stack[stack.length - 1] += last
if IMPLICIT_END.indexOf(tag) >= 0 or !post?
return 1 if tag is 'INDENT' and prev and IMPLICIT_BLOCK.indexOf(prev[0]) >= 0
if stack[stack.length - 1] > 0 or tag is 'INDENT'
idx: if tag is 'OUTDENT' then i + 1 else i
stack_pointer: if tag is 'INDENT' then 2 else 1
for tmp in [0...stack[stack.length - stack_pointer]]
@tokens.splice(idx, 0, ['CALL_END', ')', token[2]])
size: stack[stack.length - stack_pointer] + 1
stack[stack.length - stack_pointer]: 0
return size
return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(tag) >= 0
@tokens.splice(i, 0, ['CALL_START', '(', token[2]])
stack[stack.length - 1] += 1
return 2
# Because our grammar is LALR(1), it can't handle some single-line
# expressions that lack ending delimiters. Use the lexer to add the implicit
# blocks, so it doesn't need to.
# ')' can close a single-line block, but we need to make sure it's balanced.
add_implicit_indentation: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless SINGLE_LINERS.indexOf(token[0]) >= 0 and post[0] isnt 'INDENT' and
not (token[0] is 'ELSE' and post[0] is 'IF')
starter: token[0]
@tokens.splice(i + 1, 0, ['INDENT', 2, token[2]])
idx: i + 1
parens: 0
while true
idx += 1
tok: @tokens[idx]
pre: @tokens[idx - 1]
if (not tok or
(SINGLE_CLOSERS.indexOf(tok[0]) >= 0 and tok[1] isnt ';') or
(pre[0] is ',' and tok[0] is 'PARAM_START') or
(tok[0] is ')' && parens is 0)) and
not (starter is 'ELSE' and tok[0] is 'ELSE')
insertion: if pre[0] is "," then idx - 1 else idx
@tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]])
break
parens += 1 if tok[0] is '('
parens -= 1 if tok[0] is ')'
return 1 unless token[0] is 'THEN'
@tokens.splice(i, 1)
return 0
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
ensure_balance: (pairs) ->
levels: {}
@scan_tokens (prev, token, post, i) =>
for pair in pairs
[open, close]: pair
levels[open] ||= 0
levels[open] += 1 if token[0] is open
levels[open] -= 1 if token[0] is close
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
# We'd like to support syntax like this:
# el.click((event) ->
# el.hide())
# In order to accomplish this, move outdents that follow closing parens
# inwards, safely. The steps to accomplish this are:
#
# 1. Check that all paired tokens are balanced and in order.
# 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
# to the stack. If you see an ')' or OUTDENT, pop the stack and replace
# it with the inverse of what we've just popped.
# 3. Keep track of "debt" for tokens that we fake, to make sure we end
# up balanced in the end.
#
rewrite_closing_parens: ->
stack: []
debt: {}
(debt[key]: 0) for key, val of INVERSES
@scan_tokens (prev, token, post, i) =>
tag: token[0]
inv: INVERSES[token[0]]
# Push openers onto the stack.
if EXPRESSION_START.indexOf(tag) >= 0
stack.push(token)
return 1
# The end of an expression, check stack and debt for a pair.
else if EXPRESSION_TAIL.indexOf(tag) >= 0
# If the tag is already in our debt, swallow it.
if debt[inv] > 0
debt[inv] -= 1
@tokens.splice(i, 1)
return 0
else
# Pop the stack of open delimiters.
match: stack.pop()
mtag: match[0]
# Continue onwards if it's the expected tag.
if tag is INVERSES[mtag]
return 1
else
# Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1
val: if mtag is 'INDENT' then match[1] else INVERSES[mtag]
@tokens.splice(i, 0, [INVERSES[mtag], val])
return 1
else
return 1

View File

@@ -7,69 +7,70 @@ this.exports: this unless process?
# Initialize a scope with its parent, for lookups up the chain,
# as well as the Expressions body where it should declare its variables,
# and the function that it wraps.
Scope: exports.Scope: (parent, expressions, method) ->
[@parent, @expressions, @method]: [parent, expressions, method]
@variables: {}
@temp_var: if @parent then @parent.temp_var else '_a'
this
exports.Scope: class Scope
# Look up a variable in lexical scope, or declare it if not found.
Scope::find: (name) ->
return true if @check name
@variables[name]: 'var'
false
constructor: (parent, expressions, method) ->
[@parent, @expressions, @method]: [parent, expressions, method]
@variables: {}
@temp_var: if @parent then @parent.temp_var else '_a'
# Define a local variable as originating from a parameter in current scope
# -- no var required.
Scope::parameter: (name) ->
@variables[name]: 'param'
# Look up a variable in lexical scope, or declare it if not found.
find: (name) ->
return true if @check name
@variables[name]: 'var'
false
# Just check to see if a variable has already been declared.
Scope::check: (name) ->
return true if @variables[name]
!!(@parent and @parent.check(name))
# Define a local variable as originating from a parameter in current scope
# -- no var required.
parameter: (name) ->
@variables[name]: 'param'
# You can reset a found variable on the immediate scope.
Scope::reset: (name) ->
delete @variables[name]
# Just check to see if a variable has already been declared.
check: (name) ->
return true if @variables[name]
!!(@parent and @parent.check(name))
# Find an available, short, name for a compiler-generated variable.
Scope::free_variable: ->
while @check @temp_var
ordinal: 1 + parseInt @temp_var.substr(1), 36
@temp_var: '_' + ordinal.toString(36).replace(/\d/g, 'a')
@variables[@temp_var]: 'var'
@temp_var
# You can reset a found variable on the immediate scope.
reset: (name) ->
delete @variables[name]
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
Scope::assign: (name, value, top_level) ->
return @parent.assign(name, value, top_level) if top_level and @parent
@variables[name]: {value: value, assigned: true}
# Find an available, short, name for a compiler-generated variable.
free_variable: ->
while @check @temp_var
ordinal: 1 + parseInt @temp_var.substr(1), 36
@temp_var: '_' + ordinal.toString(36).replace(/\d/g, 'a')
@variables[@temp_var]: 'var'
@temp_var
# Does this scope reference any variables that need to be declared in the
# given function body?
Scope::has_declarations: (body) ->
body is @expressions and @declared_variables().length
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
assign: (name, value, top_level) ->
return @parent.assign(name, value, top_level) if top_level and @parent
@variables[name]: {value: value, assigned: true}
# Does this scope reference any assignments that need to be declared at the
# top of the given function body?
Scope::has_assignments: (body) ->
body is @expressions and @assigned_variables().length
# Does this scope reference any variables that need to be declared in the
# given function body?
has_declarations: (body) ->
body is @expressions and @declared_variables().length
# Return the list of variables first declared in current scope.
Scope::declared_variables: ->
(key for key, val of @variables when val is 'var').sort()
# Does this scope reference any assignments that need to be declared at the
# top of the given function body?
has_assignments: (body) ->
body is @expressions and @assigned_variables().length
# Return the list of variables that are supposed to be assigned at the top
# of scope.
Scope::assigned_variables: ->
key + ' = ' + val.value for key, val of @variables when val.assigned
# Return the list of variables first declared in current scope.
declared_variables: ->
(key for key, val of @variables when val is 'var').sort()
# Compile the string representing all of the declared variables for this scope.
Scope::compiled_declarations: ->
@declared_variables().join ', '
# Return the list of variables that are supposed to be assigned at the top
# of scope.
assigned_variables: ->
key + ' = ' + val.value for key, val of @variables when val.assigned
# Compile the string performing all of the variable assignments for this scope.
Scope::compiled_assignments: ->
@assigned_variables().join ', '
# Compile the string representing all of the declared variables for this scope.
compiled_declarations: ->
@declared_variables().join ', '
# Compile the string performing all of the variable assignments for this scope.
compiled_assignments: ->
@assigned_variables().join ', '

38
test/test_classes.coffee Normal file
View File

@@ -0,0 +1,38 @@
class Base
func: (string) ->
'zero/' + string
class FirstChild extends Base
func: (string) ->
super('one/') + string
class SecondChild extends FirstChild
func: (string) ->
super('two/') + string
class ThirdChild extends SecondChild
constructor: ->
@array: [1, 2, 3]
# Gratuitous comment for testing.
func: (string) ->
super('three/') + string
result: (new ThirdChild()).func 'four'
ok result is 'zero/one/two/three/four'
class TopClass
constructor: (arg) ->
@prop: 'top-' + arg
class SuperClass extends TopClass
constructor: (arg) ->
super 'super-' + arg
class SubClass extends SuperClass
constructor: ->
super 'sub'
ok (new SubClass()).prop is 'top-super-sub'

View File

@@ -77,4 +77,10 @@ ok result is 25
result: ("hello".slice) 3
ok result is 'lo'
ok result is 'lo'
# And with multiple single-line functions on the same line.
func: (x) -> (x) -> (x) -> x
ok func(1)(2)(3) is 3

View File

@@ -7,6 +7,19 @@ ok a is -2
ok b is -1
func: ->
[a, b]: [b, a]
ok func().join(' ') is '-1 -2'
noop: ->
noop [a,b]: [c,d]: [1,2]
ok a is 1 and b is 2
arr: [1, 2, 3]
[a, b, c]: arr

12
test/test_regexps.coffee Normal file
View File

@@ -0,0 +1,12 @@
ok 'x'.match(/x/g)
ok 'x'.match /x/g
ok 'x'.match(/x/)
ok 'x'.match /x/
ok 4 / 2 / 1 is 2
y: 4
x: 2
g: 1
ok y / x/g is 2