mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-01-14 01:07:55 -05:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44398d044f | ||
|
|
3feb874b1e | ||
|
|
f7183e6918 | ||
|
|
707cd2d734 | ||
|
|
c11c3ed2f2 | ||
|
|
5fd0972b5d | ||
|
|
70cb195e6f | ||
|
|
c219adffd5 | ||
|
|
cd6dd5abfd | ||
|
|
29ece0e6ba | ||
|
|
45bad556ab | ||
|
|
30cf63ec92 | ||
|
|
969c2e528d | ||
|
|
bb2bf7ce57 | ||
|
|
2969e156c7 | ||
|
|
b08995cbcc | ||
|
|
47f71f9193 | ||
|
|
fec2eaef7e | ||
|
|
56eb474bf3 | ||
|
|
62b2ab29cd | ||
|
|
a35693554c | ||
|
|
f7427259ee | ||
|
|
e02ab76edf | ||
|
|
9f46c306e5 | ||
|
|
b5c9d779bd | ||
|
|
d2cb1f321e | ||
|
|
8f871a8218 | ||
|
|
4ec7514d10 | ||
|
|
1c7e4c4203 | ||
|
|
7d39fe1c56 | ||
|
|
afa26c37f1 | ||
|
|
2f658ba925 | ||
|
|
0ab810e4cb | ||
|
|
37d086b670 | ||
|
|
5e7f5f390a | ||
|
|
f4cd0bdf29 | ||
|
|
723ea53585 | ||
|
|
93f644fae2 | ||
|
|
82951a469b | ||
|
|
d2d5f649d3 | ||
|
|
5c7526a741 | ||
|
|
17ea48c543 | ||
|
|
55ed202957 | ||
|
|
d5df5505f9 | ||
|
|
dc7f4b4be0 | ||
|
|
406a18067d | ||
|
|
3ae2ebe5ea | ||
|
|
a23dc6b753 | ||
|
|
5f1d3fd775 | ||
|
|
9d4e06e8a8 | ||
|
|
6bc61ec1a1 | ||
|
|
c62f93f930 | ||
|
|
213ae1430e | ||
|
|
ee5d738827 | ||
|
|
eab9bbf04f | ||
|
|
4ed51536bb | ||
|
|
b32a60585b | ||
|
|
66a6568fe7 | ||
|
|
fe32146adc | ||
|
|
69feac3a01 | ||
|
|
05d95acfc3 | ||
|
|
22674bc536 | ||
|
|
23c5ebb00f | ||
|
|
c14869f008 | ||
|
|
c1427d6558 | ||
|
|
2a46e13d33 | ||
|
|
b26e577244 | ||
|
|
9f8710b631 | ||
|
|
aba8cb1b08 | ||
|
|
92cd80226c | ||
|
|
10d335ccb1 | ||
|
|
4eeb8c4bd2 | ||
|
|
4d146bacb1 | ||
|
|
7de4caffca | ||
|
|
8db0cb9fa5 | ||
|
|
c30b3d3c48 | ||
|
|
52db4fbf8c | ||
|
|
5cd8f2c52c | ||
|
|
5a1aa44393 | ||
|
|
432696d6eb | ||
|
|
3df7bd98f4 | ||
|
|
1f870911c9 | ||
|
|
4bb9392753 | ||
|
|
fdffacfb40 | ||
|
|
a64afe6162 | ||
|
|
15b86a5f7a | ||
|
|
9b78fb67cf | ||
|
|
4817b96bac | ||
|
|
6985802eb3 | ||
|
|
aad0ce162d |
36
Cakefile
36
Cakefile
@@ -7,22 +7,29 @@ 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)', ->
|
||||
require.paths.unshift 'vendor/jison/lib'
|
||||
parser: require('grammar').parser
|
||||
js: parser.generate()
|
||||
parser_path: 'lib/parser.js'
|
||||
@@ -30,7 +37,7 @@ task 'build:parser', 'rebuild the Jison parser (run build first)', ->
|
||||
|
||||
|
||||
task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter', ->
|
||||
exec('plist2syntax extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage').addCallback ->
|
||||
exec 'plist2syntax extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage', (err) ->
|
||||
exec 'sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax'
|
||||
|
||||
|
||||
@@ -38,6 +45,15 @@ task 'build:underscore', 'rebuild the Underscore.coffee documentation page', ->
|
||||
exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html'
|
||||
|
||||
|
||||
task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
|
||||
exec 'rake browser', (err) ->
|
||||
throw err if err
|
||||
|
||||
|
||||
task 'doc', 'watch and continually rebuild the documentation', ->
|
||||
exec 'rake doc'
|
||||
|
||||
|
||||
task 'test', 'run the CoffeeScript language test suite', ->
|
||||
process.mixin require 'assert'
|
||||
test_count: 0
|
||||
|
||||
3
README
3
README
@@ -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
|
||||
|
||||
13
Rakefile
13
Rakefile
@@ -1,11 +1,13 @@
|
||||
require 'erb'
|
||||
require 'fileutils'
|
||||
require 'rake/testtask'
|
||||
require 'rubygems'
|
||||
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
|
||||
@@ -18,3 +20,12 @@ task :doc do
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build the single concatenated and minified script for the browser"
|
||||
task :browser do
|
||||
sources = %w(rewriter.js lexer.js parser.js scope.js nodes.js coffee-script.js)
|
||||
code = sources.map {|s| File.read('lib/' + s) }.join('')
|
||||
code = YUI::JavaScriptCompressor.new.compress(code)
|
||||
File.open('extras/coffee-script.js', 'w+') {|f| f.write(code) }
|
||||
end
|
||||
|
||||
|
||||
6
bin/cake
6
bin/cake
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.mixin(require('sys'));
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
|
||||
|
||||
require.paths.unshift('./lib');
|
||||
|
||||
require.paths.unshift(lib);
|
||||
require('cake').run();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.mixin(require('sys'));
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
|
||||
|
||||
require.paths.unshift('./lib');
|
||||
|
||||
require.paths.unshift(lib);
|
||||
require('command_line').run();
|
||||
|
||||
@@ -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
|
||||
|
||||
29
documentation/coffee/classes.coffee
Normal file
29
documentation/coffee/classes.coffee
Normal 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()
|
||||
|
||||
|
||||
|
||||
|
||||
2
documentation/coffee/prototypes.coffee
Normal file
2
documentation/coffee/prototypes.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
String::dasherize: ->
|
||||
this.replace(/_/g, "-")
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ code, pre, tt, textarea {
|
||||
padding: 3px 0 3px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
pre.no_bar {
|
||||
border-left: 0;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
div.code {
|
||||
position: relative;
|
||||
border: 1px solid #cacaca;
|
||||
@@ -112,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;
|
||||
@@ -127,10 +145,14 @@ div.code {
|
||||
border-top: 0; border-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
body.full_screen .navigation {
|
||||
position: static;
|
||||
}
|
||||
.navigation.try {
|
||||
border-left: 0;
|
||||
}
|
||||
.navigation:hover {
|
||||
.navigation:hover,
|
||||
.navigation.active {
|
||||
background: #d0d0d0;
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#f0f0f0), to(#c0c0c0));
|
||||
background: -moz-linear-gradient(top, #f0f0f0, #c0c0c0);
|
||||
@@ -147,7 +169,7 @@ div.code {
|
||||
-webkit-border-top-right-radius: 0; -moz-border-radius-topright: 0;
|
||||
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
|
||||
}
|
||||
.navigation:hover .contents {
|
||||
.navigation.active .contents {
|
||||
display: block;
|
||||
}
|
||||
.navigation .contents.repl_wrapper {
|
||||
@@ -155,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;
|
||||
@@ -189,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%;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ pre.idle .FunctionName {
|
||||
color: #21439C;
|
||||
}
|
||||
pre.idle .Variable {
|
||||
color: #A535AE;
|
||||
}
|
||||
pre.idle .Comment {
|
||||
color: #919191;
|
||||
|
||||
BIN
documentation/images/favicon.ico
Normal file
BIN
documentation/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -1,7 +1,7 @@
|
||||
<%
|
||||
require 'uv'
|
||||
def code_for(file, executable=false)
|
||||
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\Z|^ )/
|
||||
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\n*\Z|^ )/
|
||||
return '' unless File.exists?("documentation/js/#{file}.js")
|
||||
cs = File.read("documentation/coffee/#{file}.coffee")
|
||||
js = File.read("documentation/js/#{file}.js").gsub(@stripper, '')
|
||||
@@ -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>
|
||||
@@ -60,6 +61,7 @@
|
||||
<a href="#comparisons">Chained Comparisons</a>
|
||||
<a href="#strings">Multiline Strings and Heredocs</a>
|
||||
<a href="#cake">Cake, and Cakefiles</a>
|
||||
<a href="#scripts">"text/coffeescript" Script Tags</a>
|
||||
<a href="#resources">Resources</a>
|
||||
<a href="#change_log">Change Log</a>
|
||||
</div>
|
||||
@@ -70,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">
|
||||
@@ -107,7 +111,7 @@ alert reverse '!tpircseeffoC'</textarea>
|
||||
|
||||
<p>
|
||||
<b>Latest Version:</b>
|
||||
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>
|
||||
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>
|
||||
</p>
|
||||
|
||||
<h2>
|
||||
@@ -138,18 +142,23 @@ alert reverse '!tpircseeffoC'</textarea>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
The CoffeeScript compiler is written in pure CoffeeScript, and is available
|
||||
The CoffeeScript compiler is written in pure CoffeeScript, using a
|
||||
<a href="http://github.com/jashkenas/coffee-script/blob/master/src/grammar.coffee">small DSL</a>
|
||||
on top of the <a href="http://github.com/zaach/jison">Jison parser generator</a>, and is available
|
||||
as a <a href="http://nodejs.org/">Node.js</a> utility. The core compiler however,
|
||||
does not depend on Node, and can be run in other server-side-JavaScript environments,
|
||||
or in the browser (see "Try CoffeeScript", above).
|
||||
or in the browser (see "Try CoffeeScript", above). This may be helpful,
|
||||
as Node only run on flavors of nix, and not Windows, for the time being.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To install, first make sure you have a working version of
|
||||
<a href="http://nodejs.org/">Node.js</a>, 0.1.30 or higher. Then clone the CoffeeScript
|
||||
To install, first make sure you have a working version of
|
||||
<a href="http://nodejs.org/">Node.js</a> greater than version 0.1.30 (Node
|
||||
moves quickly, using the latest master is your best bet).
|
||||
Then clone the CoffeeScript
|
||||
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
|
||||
from GitHub, or download the latest
|
||||
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>.
|
||||
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>.
|
||||
To install the CoffeeScript compiler system-wide
|
||||
under <tt>/usr/local</tt>, open the directory and run:
|
||||
</p>
|
||||
@@ -158,33 +167,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>
|
||||
@@ -211,6 +220,14 @@ sudo bin/cake install</pre>
|
||||
conjunction with <tt>--watch</tt>)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-s, --stdio</code></td>
|
||||
<td>
|
||||
Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
|
||||
Good for use with processes written in other languages. An example:<br />
|
||||
<tt>cat src/cake.coffee | coffee -s</tt>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-e, --eval</code></td>
|
||||
<td>
|
||||
@@ -219,7 +236,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.)
|
||||
@@ -233,18 +250,18 @@ 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:
|
||||
<pre>
|
||||
Expressions
|
||||
Assign
|
||||
Value "square"
|
||||
Code "x"
|
||||
Op *
|
||||
Value "x"
|
||||
Value "x"</pre>
|
||||
<pre class="no_bar">
|
||||
Expressions
|
||||
Assign
|
||||
Value "square"
|
||||
Code "x"
|
||||
Op *
|
||||
Value "x"
|
||||
Value "x"</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -254,7 +271,7 @@ sudo bin/cake install</pre>
|
||||
</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>
|
||||
@@ -577,8 +594,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:
|
||||
@@ -592,12 +609,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>
|
||||
@@ -714,6 +739,34 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
|
||||
</p>
|
||||
<%= code_for('cake_tasks') %>
|
||||
|
||||
<h2>
|
||||
<span id="scripts" class="bookmark"></span>
|
||||
"text/coffeescript" Script Tags
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
While it's not recommended for serious use, CoffeeScripts may be included
|
||||
directly within the browser using <tt><script type="text/coffeescript"></tt>
|
||||
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.
|
||||
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 — your inline scripts will
|
||||
run within a closure wrapper, so if you want to expose global variables or
|
||||
functions, attach them to the <tt>window</tt> object.
|
||||
</p>
|
||||
|
||||
<h2>
|
||||
<span id="resources" class="bookmark"></span>
|
||||
Resources
|
||||
@@ -733,7 +786,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>
|
||||
|
||||
@@ -742,6 +797,40 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
|
||||
Change Log
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.5.4</b>
|
||||
Bugfix that corrects the Node.js global constants <tt>__filename</tt> and
|
||||
<tt>__dirname</tt>. Tweaks for more flexible parsing of nested function
|
||||
literals and improperly-indented comments. Updates for the latest Node.js API.
|
||||
</p>
|
||||
|
||||
<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
|
||||
<br /><tt>extras/coffee-script.js</tt>. It'll automatically run any script tags
|
||||
with type <tt>text/coffeescript</tt> for you. Added a <tt>--stdio</tt> option
|
||||
to the <tt>coffee</tt> command, for piped-in compiles.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.5.1</b>
|
||||
Improvements to null soaking with the existential operator, including
|
||||
soaks on indexed properties. Added conditions to <tt>while</tt> loops,
|
||||
so you can use them as filters with <tt>when</tt>, in the same manner as
|
||||
comprehensions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.5.0</b>
|
||||
CoffeeScript 0.5.0 is a major release, While there are no language changes,
|
||||
@@ -904,32 +993,60 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="lib/rewriter.js"></script>
|
||||
<script type="text/javascript" src="lib/lexer.js"></script>
|
||||
<script type="text/javascript" src="lib/parser.js"></script>
|
||||
<script type="text/javascript" src="lib/scope.js"></script>
|
||||
<script type="text/javascript" src="lib/nodes.js"></script>
|
||||
<script type="text/javascript" src="lib/coffee-script.js"></script>
|
||||
<script type="text/coffeescript">
|
||||
|
||||
# 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}
|
||||
$('#repl_results').text window.compiled_js
|
||||
$('#error').hide();
|
||||
catch error
|
||||
$('#error').text(error.message).show();
|
||||
|
||||
# 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
|
||||
|
||||
current_nav: null
|
||||
|
||||
# Helper to hide the menus.
|
||||
close_menus: ->
|
||||
if current_nav
|
||||
current_nav.removeClass 'active'
|
||||
document.body.className: 'minimized'
|
||||
current_nav: null
|
||||
|
||||
# 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();
|
||||
current_nav: $(this)
|
||||
current_nav.addClass 'active'
|
||||
false
|
||||
|
||||
$(document.body).click -> close_menus()
|
||||
|
||||
$('.navigation .full_screen').click ->
|
||||
document.body.className: 'full_screen'
|
||||
|
||||
$('.navigation .minimize').click ->
|
||||
document.body.className: 'minimized'
|
||||
|
||||
compile_source()
|
||||
|
||||
<script type="text/javascript">
|
||||
window.repl_compile = function() {
|
||||
var source = document.getElementById('repl_source').value;
|
||||
window.compiled_js = '';
|
||||
try {
|
||||
window.compiled_js = CoffeeScript.compile(source, {no_wrap: true});
|
||||
} catch(error) {
|
||||
alert(error);
|
||||
}
|
||||
document.getElementById('repl_results').innerHTML = window.compiled_js;
|
||||
}
|
||||
window.repl_run = function() {
|
||||
try {
|
||||
eval(window.compiled_js);
|
||||
} catch(error) {
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script src="extras/coffee-script.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
}
|
||||
car.speed < speed_limit ? accelerate() : null;
|
||||
print("My name is " + this.name);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
return alert(arguments.reverse());
|
||||
};
|
||||
backwards("stairway", "to", "heaven");
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -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();
|
||||
@@ -23,4 +23,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
var difficulty, greeting;
|
||||
greeting = "Hello CoffeeScript";
|
||||
difficulty = 0.5;
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -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));
|
||||
@@ -11,4 +11,4 @@
|
||||
}
|
||||
return _a;
|
||||
});
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -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);
|
||||
@@ -34,4 +33,4 @@
|
||||
tom = new Horse("Tommy the Palomino");
|
||||
sam.move();
|
||||
tom.move();
|
||||
})();
|
||||
})();
|
||||
@@ -2,4 +2,4 @@
|
||||
var cholesterol, healthy;
|
||||
cholesterol = 127;
|
||||
healthy = (200 > cholesterol) && (cholesterol > 60);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
}
|
||||
date = friday ? sue : jill;
|
||||
expensive = expensive || do_the_math();
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
hi = function() {
|
||||
return [document.title, "Hello JavaScript"].join(": ");
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
solipsism = true;
|
||||
}
|
||||
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
}
|
||||
};
|
||||
eldest = 24 > 21 ? "Liz" : "Ike";
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function(){
|
||||
var one, six, three, two;
|
||||
six = ((one = 1)) + ((two = 2)) + ((three = 3));
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
}}
|
||||
return _a;
|
||||
}).call(this).slice(0, 10);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
return "And the error is ... " + error;
|
||||
}
|
||||
}).call(this));
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
cube = function cube(x) {
|
||||
return square(x) * x;
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function(){
|
||||
var html;
|
||||
html = "<strong>\n cup of coffeescript\n</strong>";
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
city = _a[0];
|
||||
temp = _a[1];
|
||||
forecast = _a[2];
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
}}
|
||||
return _a;
|
||||
}).call(this);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
_c = _b.address;
|
||||
street = _c[0];
|
||||
city = _c[1];
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
tim: 11
|
||||
};
|
||||
matrix = [1, 0, 1, 0, 0, 1, 1, 1, 0];
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -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,10 +34,10 @@
|
||||
// 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));
|
||||
}
|
||||
return _a;
|
||||
}).call(this);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
_a = [and_switch, bait];
|
||||
bait = _a[0];
|
||||
and_switch = _a[1];
|
||||
})();
|
||||
})();
|
||||
|
||||
5
documentation/js/prototypes.js
vendored
Normal file
5
documentation/js/prototypes.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
(function(){
|
||||
String.prototype.dasherize = function dasherize() {
|
||||
return this.replace(/_/g, "-");
|
||||
};
|
||||
})();
|
||||
@@ -2,7 +2,7 @@
|
||||
var _a, _b, _c, _d, _e, countdown, egg_delivery, num;
|
||||
countdown = (function() {
|
||||
_a = []; _d = 10; _e = 1;
|
||||
for (_c=0, num=_d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
|
||||
for (_c = 0, num = _d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
|
||||
_a.push(num);
|
||||
}
|
||||
return _a;
|
||||
@@ -10,7 +10,7 @@
|
||||
egg_delivery = function egg_delivery() {
|
||||
var _f, _g, _h, _i, _j, dozen_eggs, i;
|
||||
_f = []; _i = 0; _j = eggs.length;
|
||||
for (_h=0, i=_i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
|
||||
for (_h = 0, i = _i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
|
||||
_f.push((function() {
|
||||
dozen_eggs = eggs.slice(i, i + 12);
|
||||
return deliver(new egg_carton(dozen));
|
||||
@@ -18,4 +18,4 @@
|
||||
}
|
||||
return _f;
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
return num = 10;
|
||||
};
|
||||
new_num = change_numbers();
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
three_to_six = numbers.slice(3, 6 + 1);
|
||||
numbers_copy = numbers.slice(0, numbers.length);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function(){
|
||||
var _a;
|
||||
(_a = lottery.draw_winner()) == null ? undefined : _a.address == null ? undefined : _a.address.zipcode;
|
||||
})();
|
||||
(_a = lottery.draw_winner()) == undefined ? undefined : _a.address == undefined ? undefined : _a.address.zipcode;
|
||||
})();
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
alert("Gold: " + gold);
|
||||
alert("Silver: " + silver);
|
||||
alert("The Field: " + the_field);
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
var numbers;
|
||||
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6]));
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -6,4 +6,4 @@ or no money in my purse, and nothing particular \
|
||||
to interest me on shore, I thought I would sail \
|
||||
about a little and see the watery part of the \
|
||||
world...";
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
} else {
|
||||
go_to_work();
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
} finally {
|
||||
clean_up();
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -19,4 +19,4 @@ One fell out and bumped his head.");
|
||||
}
|
||||
return _a;
|
||||
}).call(this);
|
||||
})();
|
||||
})();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ get '/hello', ->
|
||||
# Append.
|
||||
append: (location, data) ->
|
||||
path: new Pathname location
|
||||
throw "Location does not exist" unless path.exists()
|
||||
throw new Error("Location does not exist") unless path.exists()
|
||||
|
||||
File.open path, 'a', (file) ->
|
||||
file.puts YAML.dump data
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,36 +18,55 @@
|
||||
previousUnderscore: root._
|
||||
|
||||
|
||||
# If Underscore is called as a function, it returns a wrapped object that
|
||||
# can be used OO-style. This wrapper holds altered versions of all the
|
||||
# underscore functions. Wrapped objects may be chained.
|
||||
wrapper: (obj) ->
|
||||
this._wrapped: obj
|
||||
this
|
||||
|
||||
|
||||
# Establish the object that gets thrown to break out of a loop iteration.
|
||||
breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
|
||||
|
||||
|
||||
# Create a safe reference to the Underscore object forreference below.
|
||||
_: root._: (obj) -> new wrapper(obj)
|
||||
# Quick regexp-escaping function, because JS doesn't have RegExp.escape().
|
||||
escapeRegExp: (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
|
||||
|
||||
|
||||
# Save bytes in the minified (but not gzipped) version:
|
||||
ArrayProto: Array.prototype
|
||||
ObjProto: Object.prototype
|
||||
|
||||
|
||||
#Create quick reference variables for speed access to core prototypes.
|
||||
slice: ArrayProto.slice
|
||||
unshift: ArrayProto.unshift
|
||||
toString: ObjProto.toString
|
||||
hasOwnProperty: ObjProto.hasOwnProperty
|
||||
propertyIsEnumerable: ObjProto.propertyIsEnumerable
|
||||
|
||||
|
||||
# All ECMA5 native implementations we hope to use are declared here.
|
||||
nativeForEach: ArrayProto.forEach
|
||||
nativeMap: ArrayProto.map
|
||||
nativeReduce: ArrayProto.reduce
|
||||
nativeReduceRight: ArrayProto.reduceRight
|
||||
nativeFilter: ArrayProto.filter
|
||||
nativeEvery: ArrayProto.every
|
||||
nativeSome: ArrayProto.some
|
||||
nativeIndexOf: ArrayProto.indexOf
|
||||
nativeLastIndexOf: ArrayProto.lastIndexOf
|
||||
nativeIsArray: Array.isArray
|
||||
nativeKeys: Object.keys
|
||||
|
||||
|
||||
# Create a safe reference to the Underscore object for use below.
|
||||
_: (obj) -> new wrapper(obj)
|
||||
|
||||
|
||||
# Export the Underscore object for CommonJS.
|
||||
if typeof(exports) != 'undefined' then exports._: _
|
||||
|
||||
|
||||
# Create quick reference variables for speed access to core prototypes.
|
||||
slice: Array::slice
|
||||
unshift: Array::unshift
|
||||
toString: Object::toString
|
||||
hasOwnProperty: Object::hasOwnProperty
|
||||
propertyIsEnumerable: Object::propertyIsEnumerable
|
||||
# Export Underscore to global scope.
|
||||
root._: _
|
||||
|
||||
|
||||
# Current version.
|
||||
_.VERSION: '0.5.8'
|
||||
_.VERSION: '0.6.0'
|
||||
|
||||
|
||||
# ------------------------ Collection Functions: ---------------------------
|
||||
@@ -55,12 +74,13 @@
|
||||
# The cornerstone, an each implementation.
|
||||
# Handles objects implementing forEach, arrays, and raw objects.
|
||||
_.each: (obj, iterator, context) ->
|
||||
index: 0
|
||||
try
|
||||
return obj.forEach(iterator, context) if obj.forEach
|
||||
if _.isNumber(obj.length)
|
||||
return iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
|
||||
iterator.call(context, val, key, obj) for key, val of obj
|
||||
if nativeForEach and obj.forEach is nativeForEach
|
||||
obj.forEach iterator, context
|
||||
else if _.isNumber obj.length
|
||||
iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
|
||||
else
|
||||
iterator.call(context, val, key, obj) for key, val of obj
|
||||
catch e
|
||||
throw e if e isnt breaker
|
||||
obj
|
||||
@@ -69,28 +89,28 @@
|
||||
# Return the results of applying the iterator to each element. Use JavaScript
|
||||
# 1.6's version of map, if possible.
|
||||
_.map: (obj, iterator, context) ->
|
||||
return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
|
||||
return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
|
||||
results: []
|
||||
_.each obj, (value, index, list) ->
|
||||
results.push(iterator.call(context, value, index, list))
|
||||
results.push iterator.call context, value, index, list
|
||||
results
|
||||
|
||||
|
||||
# Reduce builds up a single result from a list of values. Also known as
|
||||
# inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
|
||||
_.reduce: (obj, memo, iterator, context) ->
|
||||
return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce))
|
||||
return obj.reduce(_.bind(iterator, context), memo) if nativeReduce and obj.reduce is nativeReduce
|
||||
_.each obj, (value, index, list) ->
|
||||
memo: iterator.call(context, memo, value, index, list)
|
||||
memo: iterator.call context, memo, value, index, list
|
||||
memo
|
||||
|
||||
|
||||
# The right-associative version of reduce, also known as foldr. Uses
|
||||
# JavaScript 1.8's version of reduceRight, if available.
|
||||
_.reduceRight: (obj, memo, iterator, context) ->
|
||||
return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight))
|
||||
return obj.reduceRight(_.bind(iterator, context), memo) if nativeReduceRight and obj.reduceRight is nativeReduceRight
|
||||
_.each _.clone(_.toArray(obj)).reverse(), (value, index) ->
|
||||
memo: iterator.call(context, memo, value, index, obj)
|
||||
memo: iterator.call context, memo, value, index, obj
|
||||
memo
|
||||
|
||||
|
||||
@@ -98,7 +118,7 @@
|
||||
_.detect: (obj, iterator, context) ->
|
||||
result: null
|
||||
_.each obj, (value, index, list) ->
|
||||
if iterator.call(context, value, index, list)
|
||||
if iterator.call context, value, index, list
|
||||
result: value
|
||||
_.breakLoop()
|
||||
result
|
||||
@@ -106,11 +126,11 @@
|
||||
|
||||
# Return all the elements that pass a truth test. Use JavaScript 1.6's
|
||||
# filter(), if it exists.
|
||||
_.select: (obj, iterator, context) ->
|
||||
if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context)
|
||||
_.filter: (obj, iterator, context) ->
|
||||
return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
|
||||
results: []
|
||||
_.each obj, (value, index, list) ->
|
||||
results.push(value) if iterator.call(context, value, index, list)
|
||||
results.push value if iterator.call context, value, index, list
|
||||
results
|
||||
|
||||
|
||||
@@ -118,15 +138,15 @@
|
||||
_.reject: (obj, iterator, context) ->
|
||||
results: []
|
||||
_.each obj, (value, index, list) ->
|
||||
results.push(value) if not iterator.call(context, value, index, list)
|
||||
results.push value if not iterator.call context, value, index, list
|
||||
results
|
||||
|
||||
|
||||
# Determine whether all of the elements match a truth test. Delegate to
|
||||
# JavaScript 1.6's every(), if it is present.
|
||||
_.all: (obj, iterator, context) ->
|
||||
_.every: (obj, iterator, context) ->
|
||||
iterator ||= _.identity
|
||||
return obj.every(iterator, context) if obj and _.isFunction(obj.every)
|
||||
return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
|
||||
result: true
|
||||
_.each obj, (value, index, list) ->
|
||||
_.breakLoop() unless (result: result and iterator.call(context, value, index, list))
|
||||
@@ -135,9 +155,9 @@
|
||||
|
||||
# Determine if at least one element in the object matches a truth test. Use
|
||||
# JavaScript 1.6's some(), if it exists.
|
||||
_.any: (obj, iterator, context) ->
|
||||
_.some: (obj, iterator, context) ->
|
||||
iterator ||= _.identity
|
||||
return obj.some(iterator, context) if obj and _.isFunction(obj.some)
|
||||
return obj.some iterator, context if nativeSome and obj.some is nativeSome
|
||||
result: false
|
||||
_.each obj, (value, index, list) ->
|
||||
_.breakLoop() if (result: iterator.call(context, value, index, list))
|
||||
@@ -147,7 +167,7 @@
|
||||
# Determine if a given value is included in the array or object,
|
||||
# based on '==='.
|
||||
_.include: (obj, target) ->
|
||||
return _.indexOf(obj, target) isnt -1 if obj and _.isFunction(obj.indexOf)
|
||||
return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
|
||||
for key, val of obj
|
||||
return true if val is target
|
||||
false
|
||||
@@ -155,13 +175,13 @@
|
||||
|
||||
# Invoke a method with arguments on every item in a collection.
|
||||
_.invoke: (obj, method) ->
|
||||
args: _.rest(arguments, 2)
|
||||
args: _.rest arguments, 2
|
||||
(if method then val[method] else val).apply(val, args) for val in obj
|
||||
|
||||
|
||||
# Convenience version of a common use case of map: fetching a property.
|
||||
_.pluck: (obj, key) ->
|
||||
_.map(obj, ((val) -> val[key]))
|
||||
_.map(obj, (val) -> val[key])
|
||||
|
||||
|
||||
# Return the maximum item or (item-based computation).
|
||||
@@ -184,7 +204,7 @@
|
||||
result.value
|
||||
|
||||
|
||||
# Sort the object's values by a criteria produced by an iterator.
|
||||
# Sort the object's values by a criterion produced by an iterator.
|
||||
_.sortBy: (obj, iterator, context) ->
|
||||
_.pluck(((_.map obj, (value, index, list) ->
|
||||
{value: value, criteria: iterator.call(context, value, index, list)}
|
||||
@@ -198,7 +218,8 @@
|
||||
# be inserted so as to maintain order. Uses binary search.
|
||||
_.sortedIndex: (array, obj, iterator) ->
|
||||
iterator ||= _.identity
|
||||
low: 0; high: array.length
|
||||
low: 0
|
||||
high: array.length
|
||||
while low < high
|
||||
mid: (low + high) >> 1
|
||||
if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid
|
||||
@@ -246,30 +267,30 @@
|
||||
# Return a completely flattened version of an array.
|
||||
_.flatten: (array) ->
|
||||
_.reduce array, [], (memo, value) ->
|
||||
return memo.concat(_.flatten(value)) if _.isArray(value)
|
||||
memo.push(value)
|
||||
return memo.concat(_.flatten(value)) if _.isArray value
|
||||
memo.push value
|
||||
memo
|
||||
|
||||
|
||||
# Return a version of the array that does not contain the specified value(s).
|
||||
_.without: (array) ->
|
||||
values: _.rest(arguments)
|
||||
val for val in _.toArray(array) when not _.include(values, val)
|
||||
values: _.rest arguments
|
||||
val for val in _.toArray(array) when not _.include values, val
|
||||
|
||||
|
||||
# Produce a duplicate-free version of the array. If the array has already
|
||||
# been sorted, you have the option of using a faster algorithm.
|
||||
_.uniq: (array, isSorted) ->
|
||||
memo: []
|
||||
for el, i in _.toArray(array)
|
||||
memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
|
||||
for el, i in _.toArray array
|
||||
memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
|
||||
memo
|
||||
|
||||
|
||||
# Produce an array that contains every item shared between all the
|
||||
# passed-in arrays.
|
||||
_.intersect: (array) ->
|
||||
rest: _.rest(arguments)
|
||||
rest: _.rest arguments
|
||||
_.select _.uniq(array), (item) ->
|
||||
_.all rest, (other) ->
|
||||
_.indexOf(other, item) >= 0
|
||||
@@ -278,10 +299,10 @@
|
||||
# Zip together multiple lists into a single array -- elements that share
|
||||
# an index go together.
|
||||
_.zip: ->
|
||||
length: _.max(_.pluck(arguments, 'length'))
|
||||
results: new Array(length)
|
||||
length: _.max _.pluck arguments, 'length'
|
||||
results: new Array length
|
||||
for i in [0...length]
|
||||
results[i]: _.pluck(arguments, String(i))
|
||||
results[i]: _.pluck arguments, String i
|
||||
results
|
||||
|
||||
|
||||
@@ -289,7 +310,7 @@
|
||||
# we need this function. Return the position of the first occurence of an
|
||||
# item in an array, or -1 if the item is not included in the array.
|
||||
_.indexOf: (array, item) ->
|
||||
return array.indexOf(item) if array.indexOf
|
||||
return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
|
||||
i: 0; l: array.length
|
||||
while l - i
|
||||
if array[i] is item then return i else i++
|
||||
@@ -299,7 +320,7 @@
|
||||
# Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
|
||||
# if possible.
|
||||
_.lastIndexOf: (array, item) ->
|
||||
return array.lastIndexOf(item) if array.lastIndexOf
|
||||
return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
|
||||
i: array.length
|
||||
while i
|
||||
if array[i] is item then return i else i--
|
||||
@@ -317,7 +338,7 @@
|
||||
step: a[2] or 1
|
||||
len: Math.ceil((stop - start) / step)
|
||||
return [] if len <= 0
|
||||
range: new Array(len)
|
||||
range: new Array len
|
||||
idx: 0
|
||||
while true
|
||||
return range if (if step > 0 then i - stop else stop - i) >= 0
|
||||
@@ -331,36 +352,36 @@
|
||||
# Create a function bound to a given object (assigning 'this', and arguments,
|
||||
# optionally). Binding with arguments is also known as 'curry'.
|
||||
_.bind: (func, obj) ->
|
||||
args: _.rest(arguments, 2)
|
||||
-> func.apply(obj or root, args.concat(arguments))
|
||||
args: _.rest arguments, 2
|
||||
-> func.apply obj or root, args.concat arguments
|
||||
|
||||
|
||||
# Bind all of an object's methods to that object. Useful for ensuring that
|
||||
# all callbacks defined on an object belong to it.
|
||||
_.bindAll: (obj) ->
|
||||
funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
|
||||
_.each(funcs, (f) -> obj[f]: _.bind(obj[f], obj))
|
||||
_.each funcs, (f) -> obj[f]: _.bind obj[f], obj
|
||||
obj
|
||||
|
||||
|
||||
# Delays a function for the given number of milliseconds, and then calls
|
||||
# it with the arguments supplied.
|
||||
_.delay: (func, wait) ->
|
||||
args: _.rest(arguments, 2)
|
||||
args: _.rest arguments, 2
|
||||
setTimeout((-> func.apply(func, args)), wait)
|
||||
|
||||
|
||||
# Defers a function, scheduling it to run after the current call stack has
|
||||
# cleared.
|
||||
_.defer: (func) ->
|
||||
_.delay.apply(_, [func, 1].concat(_.rest(arguments)))
|
||||
_.delay.apply _, [func, 1].concat _.rest arguments
|
||||
|
||||
|
||||
# Returns the first function passed as an argument to the second,
|
||||
# allowing you to adjust arguments, run code before and after, and
|
||||
# conditionally execute the original function.
|
||||
_.wrap: (func, wrapper) ->
|
||||
-> wrapper.apply(wrapper, [func].concat(arguments))
|
||||
-> wrapper.apply wrapper, [func].concat arguments
|
||||
|
||||
|
||||
# Returns a function that is the composition of a list of functions, each
|
||||
@@ -377,38 +398,37 @@
|
||||
# ------------------------- Object Functions: ----------------------------
|
||||
|
||||
# Retrieve the names of an object's properties.
|
||||
_.keys: (obj) ->
|
||||
return _.range(0, obj.length) if _.isArray(obj)
|
||||
_.keys: nativeKeys or (obj) ->
|
||||
return _.range 0, obj.length if _.isArray(obj)
|
||||
key for key, val of obj
|
||||
|
||||
|
||||
# Retrieve the values of an object's properties.
|
||||
_.values: (obj) ->
|
||||
_.map(obj, _.identity)
|
||||
_.map obj, _.identity
|
||||
|
||||
|
||||
# Return a sorted list of the function names available in Underscore.
|
||||
_.functions: (obj) ->
|
||||
_.select(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
|
||||
_.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
|
||||
|
||||
|
||||
# Extend a given object with all of the properties in a source object.
|
||||
_.extend: (destination, source) ->
|
||||
for key, val of source
|
||||
destination[key]: val
|
||||
(destination[key]: val) for key, val of source
|
||||
destination
|
||||
|
||||
|
||||
# Create a (shallow-cloned) duplicate of an object.
|
||||
_.clone: (obj) ->
|
||||
return obj.slice(0) if _.isArray(obj)
|
||||
_.extend({}, obj)
|
||||
return obj.slice 0 if _.isArray obj
|
||||
_.extend {}, obj
|
||||
|
||||
|
||||
# Invokes interceptor with the obj, and then returns obj.
|
||||
# The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
|
||||
_.tap: (obj, interceptor) ->
|
||||
interceptor(obj)
|
||||
interceptor obj
|
||||
obj
|
||||
|
||||
|
||||
@@ -444,12 +464,15 @@
|
||||
# Different object sizes?
|
||||
return false if aKeys.length isnt bKeys.length
|
||||
# Recursive comparison of contents.
|
||||
# for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
|
||||
return true
|
||||
(return false) for key, val of a when !_.isEqual(val, b[key])
|
||||
true
|
||||
|
||||
|
||||
# Is a given array or object empty?
|
||||
_.isEmpty: (obj) -> _.keys(obj).length is 0
|
||||
_.isEmpty: (obj) ->
|
||||
return obj.length is 0 if _.isArray obj
|
||||
(return false) for key of obj when hasOwnProperty.call(obj, key)
|
||||
true
|
||||
|
||||
|
||||
# Is a given value a DOM element?
|
||||
@@ -457,7 +480,7 @@
|
||||
|
||||
|
||||
# Is a given value an array?
|
||||
_.isArray: (obj) -> !!(obj and obj.concat and obj.unshift)
|
||||
_.isArray: nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift)
|
||||
|
||||
|
||||
# Is a given variable an arguments object?
|
||||
@@ -477,6 +500,10 @@
|
||||
_.isNumber: (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
|
||||
|
||||
|
||||
# Is a given value a boolean?
|
||||
_.isBoolean: (obj) -> obj is true or obj is false
|
||||
|
||||
|
||||
# Is a given value a Date?
|
||||
_.isDate: (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
|
||||
|
||||
@@ -511,10 +538,22 @@
|
||||
_.identity: (value) -> value
|
||||
|
||||
|
||||
# Run a function n times.
|
||||
_.times: (n, iterator, context) ->
|
||||
iterator.call(context, i) for i in [0...n]
|
||||
|
||||
|
||||
# Break out of the middle of an iteration.
|
||||
_.breakLoop: -> throw breaker
|
||||
|
||||
|
||||
# Add your own custom functions to the Underscore object, ensuring that
|
||||
# they're correctly added to the OOP wrapper as well.
|
||||
_.mixin: (obj) ->
|
||||
for name in _.functions(obj)
|
||||
addToWrapper name, _[name]: obj[name]
|
||||
|
||||
|
||||
# Generate a unique integer id (unique within the entire client session).
|
||||
# Useful for temporary DOM ids.
|
||||
idCounter: 0
|
||||
@@ -536,11 +575,12 @@
|
||||
# Single-quote fix from Rick Strahl's version.
|
||||
_.template: (str, data) ->
|
||||
c: _.templateSettings
|
||||
endMatch: new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
|
||||
fn: new Function 'obj',
|
||||
'var p=[],print=function(){p.push.apply(p,arguments);};' +
|
||||
'with(obj){p.push(\'' +
|
||||
str.replace(/[\r\t\n]/g, " ")
|
||||
.replace(new RegExp("'(?=[^"+c.end[0]+"]*"+c.end+")","g"),"\t")
|
||||
.replace(endMatch,"\t")
|
||||
.split("'").join("\\'")
|
||||
.split("\t").join("'")
|
||||
.replace(c.interpolate, "',$1,'")
|
||||
@@ -555,9 +595,9 @@
|
||||
_.forEach: _.each
|
||||
_.foldl: _.inject: _.reduce
|
||||
_.foldr: _.reduceRight
|
||||
_.filter: _.select
|
||||
_.every: _.all
|
||||
_.some: _.any
|
||||
_.select: _.filter
|
||||
_.all: _.every
|
||||
_.any: _.some
|
||||
_.head: _.first
|
||||
_.tail: _.rest
|
||||
_.methods: _.functions
|
||||
@@ -565,17 +605,29 @@
|
||||
|
||||
# ------------------------ Setup the OOP Wrapper: --------------------------
|
||||
|
||||
# If Underscore is called as a function, it returns a wrapped object that
|
||||
# can be used OO-style. This wrapper holds altered versions of all the
|
||||
# underscore functions. Wrapped objects may be chained.
|
||||
wrapper: (obj) ->
|
||||
this._wrapped: obj
|
||||
this
|
||||
|
||||
|
||||
# Helper function to continue chaining intermediate results.
|
||||
result: (obj, chain) ->
|
||||
if chain then _(obj).chain() else obj
|
||||
|
||||
|
||||
# Add all of the Underscore functions to the wrapper object.
|
||||
_.each _.functions(_), (name) ->
|
||||
method: _[name]
|
||||
# A method to easily add functions to the OOP wrapper.
|
||||
addToWrapper: (name, func) ->
|
||||
wrapper.prototype[name]: ->
|
||||
unshift.call(arguments, this._wrapped)
|
||||
result(method.apply(_, arguments), this._chain)
|
||||
args: _.toArray arguments
|
||||
unshift.call args, this._wrapped
|
||||
result func.apply(_, args), this._chain
|
||||
|
||||
|
||||
# Add all of the Underscore functions to the wrapper object.
|
||||
_.mixin _
|
||||
|
||||
|
||||
# Add all mutator Array functions to the wrapper.
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
This folder includes rough cuts of CoffeeScript syntax highlighters for
|
||||
EXTRAS:
|
||||
|
||||
"extras/coffee-script.js" is a concatenated and compressed version of the
|
||||
CoffeeScript compiler. To use it in the browser, include the script after any
|
||||
inline script tags of type "text/coffeescript" on the page. It will compile
|
||||
and evaluate all of the scripts in order.
|
||||
|
||||
|
||||
This folder also includes rough cuts of CoffeeScript syntax highlighters for
|
||||
TextMate and Vim. Improvements to their lexing ability are always welcome.
|
||||
|
||||
To install the TextMate bundle, drop it into:
|
||||
|
||||
1
extras/coffee-script.js
Normal file
1
extras/coffee-script.js
Normal file
File diff suppressed because one or more lines are too long
372
index.html
372
index.html
@@ -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>
|
||||
@@ -46,6 +47,7 @@
|
||||
<a href="#comparisons">Chained Comparisons</a>
|
||||
<a href="#strings">Multiline Strings and Heredocs</a>
|
||||
<a href="#cake">Cake, and Cakefiles</a>
|
||||
<a href="#scripts">"text/coffeescript" Script Tags</a>
|
||||
<a href="#resources">Resources</a>
|
||||
<a href="#change_log">Change Log</a>
|
||||
</div>
|
||||
@@ -56,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">
|
||||
@@ -93,7 +97,7 @@ alert reverse '!tpircseeffoC'</textarea>
|
||||
|
||||
<p>
|
||||
<b>Latest Version:</b>
|
||||
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>
|
||||
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>
|
||||
</p>
|
||||
|
||||
<h2>
|
||||
@@ -132,7 +136,7 @@ alert <span class="String"><span class="String">"</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>;
|
||||
@@ -167,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"><</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"><</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;
|
||||
@@ -208,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));
|
||||
}
|
||||
@@ -235,18 +239,23 @@ cubed_list = (function() {
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
The CoffeeScript compiler is written in pure CoffeeScript, and is available
|
||||
The CoffeeScript compiler is written in pure CoffeeScript, using a
|
||||
<a href="http://github.com/jashkenas/coffee-script/blob/master/src/grammar.coffee">small DSL</a>
|
||||
on top of the <a href="http://github.com/zaach/jison">Jison parser generator</a>, and is available
|
||||
as a <a href="http://nodejs.org/">Node.js</a> utility. The core compiler however,
|
||||
does not depend on Node, and can be run in other server-side-JavaScript environments,
|
||||
or in the browser (see "Try CoffeeScript", above).
|
||||
or in the browser (see "Try CoffeeScript", above). This may be helpful,
|
||||
as Node only run on flavors of nix, and not Windows, for the time being.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To install, first make sure you have a working version of
|
||||
<a href="http://nodejs.org/">Node.js</a>, 0.1.30 or higher. Then clone the CoffeeScript
|
||||
To install, first make sure you have a working version of
|
||||
<a href="http://nodejs.org/">Node.js</a> greater than version 0.1.30 (Node
|
||||
moves quickly, using the latest master is your best bet).
|
||||
Then clone the CoffeeScript
|
||||
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
|
||||
from GitHub, or download the latest
|
||||
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.0">0.5.0</a>.
|
||||
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>.
|
||||
To install the CoffeeScript compiler system-wide
|
||||
under <tt>/usr/local</tt>, open the directory and run:
|
||||
</p>
|
||||
@@ -255,33 +264,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>
|
||||
@@ -308,6 +317,14 @@ sudo bin/cake install</pre>
|
||||
conjunction with <tt>--watch</tt>)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-s, --stdio</code></td>
|
||||
<td>
|
||||
Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
|
||||
Good for use with processes written in other languages. An example:<br />
|
||||
<tt>cat src/cake.coffee | coffee -s</tt>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-e, --eval</code></td>
|
||||
<td>
|
||||
@@ -316,7 +333,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.)
|
||||
@@ -330,18 +347,18 @@ 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:
|
||||
<pre>
|
||||
Expressions
|
||||
Assign
|
||||
Value "square"
|
||||
Code "x"
|
||||
Op *
|
||||
Value "x"
|
||||
Value "x"</pre>
|
||||
<pre class="no_bar">
|
||||
Expressions
|
||||
Assign
|
||||
Value "square"
|
||||
Code "x"
|
||||
Op *
|
||||
Value "x"
|
||||
Value "x"</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -351,7 +368,7 @@ sudo bin/cake install</pre>
|
||||
</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>
|
||||
@@ -805,29 +822,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"><</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"><</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"><</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"><</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"><</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"><</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();
|
||||
@@ -851,7 +868,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"><=</span> _e ? num <span class="Keyword"><=</span> _e : num <span class="Keyword">>=</span> _e); (_d <span class="Keyword"><=</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"><=</span> _e ? num <span class="Keyword"><=</span> _e : num <span class="Keyword">>=</span> _e); (_d <span class="Keyword"><=</span> _e ? num <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">1</span> : num <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">1</span>), _c<span class="Keyword">++</span>) {
|
||||
_a.<span class="LibraryFunction">push</span>(num);
|
||||
}
|
||||
<span class="Keyword">return</span> _a;
|
||||
@@ -859,7 +876,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"><=</span> _j ? i <span class="Keyword"><</span> _j : i <span class="Keyword">></span> _j); (_i <span class="Keyword"><=</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"><=</span> _j ? i <span class="Keyword"><</span> _j : i <span class="Keyword">></span> _j); (_i <span class="Keyword"><=</span> _j ? i <span class="Keyword">+</span><span class="Keyword">=</span> <span class="Number">12</span> : i <span class="Keyword">-</span><span class="Keyword">=</span> <span class="Number">12</span>), _h<span class="Keyword">++</span>) {
|
||||
_f.<span class="LibraryFunction">push</span>((<span class="Storage">function</span>() {
|
||||
dozen_eggs <span class="Keyword">=</span> eggs.<span class="LibraryFunction">slice</span>(i, i <span class="Keyword">+</span> <span class="Number">12</span>);
|
||||
<span class="Keyword">return</span> deliver(<span class="Keyword">new</span> <span class="TypeName">egg_carton</span>(dozen));
|
||||
@@ -870,7 +887,7 @@ egg_delivery <span class="Keyword">=</span> <span class="Storage">function</span
|
||||
</pre><button onclick='javascript: var _a, _b, _c, _d, _e, countdown, egg_delivery, num;
|
||||
countdown = (function() {
|
||||
_a = []; _d = 10; _e = 1;
|
||||
for (_c=0, num=_d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
|
||||
for (_c = 0, num = _d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
|
||||
_a.push(num);
|
||||
}
|
||||
return _a;
|
||||
@@ -878,7 +895,7 @@ countdown = (function() {
|
||||
egg_delivery = function egg_delivery() {
|
||||
var _f, _g, _h, _i, _j, dozen_eggs, i;
|
||||
_f = []; _i = 0; _j = eggs.length;
|
||||
for (_h=0, i=_i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
|
||||
for (_h = 0, i = _i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
|
||||
_f.push((function() {
|
||||
dozen_eggs = eggs.slice(i, i + 12);
|
||||
return deliver(new egg_carton(dozen));
|
||||
@@ -1131,7 +1148,7 @@ speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
|
||||
</p>
|
||||
<div class='code'><pre class="idle">lottery.draw_winner()<span class="Keyword">?</span>.address<span class="Keyword">?</span>.zipcode
|
||||
</pre><pre class="idle"><span class="Storage">var</span> _a;
|
||||
(_a <span class="Keyword">=</span> lottery.draw_winner()) <span class="Keyword">==</span> <span class="BuiltInConstant">null</span> ? undefined : _a.address <span class="Keyword">==</span> <span class="BuiltInConstant">null</span> ? undefined : _a.address.zipcode;
|
||||
(_a <span class="Keyword">=</span> lottery.draw_winner()) <span class="Keyword">==</span> undefined ? undefined : _a.address <span class="Keyword">==</span> undefined ? undefined : _a.address.zipcode;
|
||||
</pre><br class='clear' /></div>
|
||||
<p>
|
||||
Soaking up nulls is similar to Ruby's
|
||||
@@ -1141,8 +1158,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:
|
||||
@@ -1156,35 +1173,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">-></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">-></span>
|
||||
alert <span class="Variable">@name</span> <span class="Keyword">+</span> <span class="String"><span class="String">"</span> moved <span class="String">"</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">"</span>m.<span class="String">"</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">-></span>
|
||||
alert <span class="Variable">@name</span> <span class="Keyword">+</span> <span class="String"><span class="String">"</span> moved <span class="String">"</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">"</span>m.<span class="String">"</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">-></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">-></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">-></span>
|
||||
alert <span class="String"><span class="String">"</span>Slithering...<span class="String">"</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">-></span>
|
||||
<span class="Variable">@name</span><span class="Keyword">:</span> name
|
||||
|
||||
<span class="FunctionName">Snake::move</span><span class="Keyword">:</span> <span class="Storage">-></span>
|
||||
alert <span class="String"><span class="String">"</span>Slithering...<span class="String">"</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">-></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">-></span>
|
||||
alert <span class="String"><span class="String">"</span>Galloping...<span class="String">"</span></span>
|
||||
<span class="Variable">super</span> <span class="Number">45</span>
|
||||
<span class="FunctionName">move</span><span class="Keyword">:</span> <span class="Storage">-></span>
|
||||
alert <span class="String"><span class="String">"</span>Galloping...<span class="String">"</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">"</span>Sammy the Python<span class="String">"</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">"</span>Tommy the Palomino<span class="String">"</span></span>
|
||||
@@ -1195,7 +1207,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">"</span> moved <span class="String">"</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">"</span>m.<span class="String">"</span></span>);
|
||||
@@ -1204,11 +1223,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">"</span>Slithering...<span class="String">"</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>);
|
||||
@@ -1217,11 +1232,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">"</span>Galloping...<span class="String">"</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>);
|
||||
@@ -1230,7 +1241,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">"</span>Tommy the Palomino<span class="String">"</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.");
|
||||
@@ -1239,11 +1257,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);
|
||||
@@ -1252,11 +1266,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);
|
||||
@@ -1266,6 +1276,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">-></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">"</span>-<span class="String">"</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">"</span>-<span class="String">"</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>
|
||||
@@ -1582,9 +1608,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">-></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"><</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"><</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));
|
||||
@@ -1594,6 +1620,34 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
|
||||
});
|
||||
</pre><br class='clear' /></div>
|
||||
|
||||
<h2>
|
||||
<span id="scripts" class="bookmark"></span>
|
||||
"text/coffeescript" Script Tags
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
While it's not recommended for serious use, CoffeeScripts may be included
|
||||
directly within the browser using <tt><script type="text/coffeescript"></tt>
|
||||
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.
|
||||
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 — your inline scripts will
|
||||
run within a closure wrapper, so if you want to expose global variables or
|
||||
functions, attach them to the <tt>window</tt> object.
|
||||
</p>
|
||||
|
||||
<h2>
|
||||
<span id="resources" class="bookmark"></span>
|
||||
Resources
|
||||
@@ -1613,7 +1667,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>
|
||||
|
||||
@@ -1622,6 +1678,40 @@ 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.4</b>
|
||||
Bugfix that corrects the Node.js global constants <tt>__filename</tt> and
|
||||
<tt>__dirname</tt>. Tweaks for more flexible parsing of nested function
|
||||
literals and improperly-indented comments. Updates for the latest Node.js API.
|
||||
</p>
|
||||
|
||||
<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
|
||||
<br /><tt>extras/coffee-script.js</tt>. It'll automatically run any script tags
|
||||
with type <tt>text/coffeescript</tt> for you. Added a <tt>--stdio</tt> option
|
||||
to the <tt>coffee</tt> command, for piped-in compiles.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.5.1</b>
|
||||
Improvements to null soaking with the existential operator, including
|
||||
soaks on indexed properties. Added conditions to <tt>while</tt> loops,
|
||||
so you can use them as filters with <tt>when</tt>, in the same manner as
|
||||
comprehensions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.5.0</b>
|
||||
CoffeeScript 0.5.0 is a major release, While there are no language changes,
|
||||
@@ -1784,32 +1874,60 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="lib/rewriter.js"></script>
|
||||
<script type="text/javascript" src="lib/lexer.js"></script>
|
||||
<script type="text/javascript" src="lib/parser.js"></script>
|
||||
<script type="text/javascript" src="lib/scope.js"></script>
|
||||
<script type="text/javascript" src="lib/nodes.js"></script>
|
||||
<script type="text/javascript" src="lib/coffee-script.js"></script>
|
||||
<script type="text/coffeescript">
|
||||
|
||||
# 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}
|
||||
$('#repl_results').text window.compiled_js
|
||||
$('#error').hide();
|
||||
catch error
|
||||
$('#error').text(error.message).show();
|
||||
|
||||
# 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
|
||||
|
||||
current_nav: null
|
||||
|
||||
# Helper to hide the menus.
|
||||
close_menus: ->
|
||||
if current_nav
|
||||
current_nav.removeClass 'active'
|
||||
document.body.className: 'minimized'
|
||||
current_nav: null
|
||||
|
||||
# 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();
|
||||
current_nav: $(this)
|
||||
current_nav.addClass 'active'
|
||||
false
|
||||
|
||||
$(document.body).click -> close_menus()
|
||||
|
||||
$('.navigation .full_screen').click ->
|
||||
document.body.className: 'full_screen'
|
||||
|
||||
$('.navigation .minimize').click ->
|
||||
document.body.className: 'minimized'
|
||||
|
||||
compile_source()
|
||||
|
||||
<script type="text/javascript">
|
||||
window.repl_compile = function() {
|
||||
var source = document.getElementById('repl_source').value;
|
||||
window.compiled_js = '';
|
||||
try {
|
||||
window.compiled_js = CoffeeScript.compile(source, {no_wrap: true});
|
||||
} catch(error) {
|
||||
alert(error);
|
||||
}
|
||||
document.getElementById('repl_results').innerHTML = window.compiled_js;
|
||||
}
|
||||
window.repl_run = function() {
|
||||
try {
|
||||
eval(window.compiled_js);
|
||||
} catch(error) {
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script src="extras/coffee-script.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
process.mixin(require('sys'));
|
||||
|
||||
require.paths.unshift('/usr/local/lib/coffee-script/lib');
|
||||
|
||||
require('cake').run();
|
||||
@@ -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();
|
||||
100
lib/cake.js
100
lib/cake.js
@@ -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);
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function(){
|
||||
var lexer, parser, path;
|
||||
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,30 +32,37 @@
|
||||
return this.pos;
|
||||
}
|
||||
};
|
||||
exports.VERSION = '0.5.0';
|
||||
exports.VERSION = '0.5.4';
|
||||
// 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));
|
||||
};
|
||||
// Pretty-print a token stream.
|
||||
exports.print_tokens = function print_tokens(tokens) {
|
||||
var _a, _b, _c, strings, token;
|
||||
strings = (function() {
|
||||
_a = []; _b = tokens;
|
||||
for (_c = 0; _c < _b.length; _c++) {
|
||||
token = _b[_c];
|
||||
_a.push('[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']');
|
||||
// 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) {
|
||||
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;
|
||||
}).call(this);
|
||||
return puts(strings.join(' '));
|
||||
};
|
||||
})();
|
||||
};
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', process_scripts, false);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', process_scripts);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(function(){
|
||||
var BANNER, SWITCHES, coffee, compile_script, compile_scripts, fs, lint, option_parser, options, optparse, parse_options, path, sources, usage, version, watch_scripts, write_js;
|
||||
var BANNER, CoffeeScript, SWITCHES, compile_options, compile_script, compile_scripts, compile_stdio, fs, lint, option_parser, options, optparse, parse_options, path, print_tokens, sources, usage, version, watch_scripts, write_js;
|
||||
fs = require('fs');
|
||||
path = require('path');
|
||||
coffee = require('coffee-script');
|
||||
optparse = require('optparse');
|
||||
CoffeeScript = require('coffee-script');
|
||||
BANNER = "coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee";
|
||||
SWITCHES = [['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-r', '--run', 'compile and run a CoffeeScript'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-e', '--eval', 'compile a string from the command line'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-tr', '--tree', 'print the parse tree that Jison produces'], ['-n', '--no-wrap', 'compile without the top-level function wrapper'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
|
||||
SWITCHES = [['-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;
|
||||
@@ -13,14 +13,23 @@
|
||||
exports.run = function run() {
|
||||
var flags, separator;
|
||||
parse_options();
|
||||
if (options.help) {
|
||||
return usage();
|
||||
}
|
||||
if (options.version) {
|
||||
return version();
|
||||
}
|
||||
if (options.interactive) {
|
||||
return require('repl');
|
||||
}
|
||||
if (options.stdio) {
|
||||
return compile_stdio();
|
||||
}
|
||||
if (options.eval) {
|
||||
return compile_script('terminal', sources[0]);
|
||||
return compile_script('unknown', sources[0]);
|
||||
}
|
||||
if (!(sources.length)) {
|
||||
usage();
|
||||
return usage();
|
||||
}
|
||||
separator = sources.indexOf('--');
|
||||
flags = [];
|
||||
@@ -42,20 +51,25 @@
|
||||
};
|
||||
// The "--version" message.
|
||||
version = function version() {
|
||||
puts("CoffeeScript version " + coffee.VERSION);
|
||||
puts("CoffeeScript version " + CoffeeScript.VERSION);
|
||||
return process.exit(0);
|
||||
};
|
||||
// Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
|
||||
// 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));
|
||||
}
|
||||
@@ -64,42 +78,55 @@
|
||||
// Compile a single source script, containing the given code, according to the
|
||||
// requested options. Both compile_scripts and watch_scripts share this method.
|
||||
compile_script = function compile_script(source, code) {
|
||||
var js, o, opts;
|
||||
opts = options;
|
||||
o = opts.no_wrap ? {
|
||||
no_wrap: true
|
||||
} : {};
|
||||
var __dirname, __filename, js, o;
|
||||
o = options;
|
||||
try {
|
||||
if (opts.tokens) {
|
||||
return coffee.print_tokens(coffee.tokenize(code));
|
||||
} else if (opts.tree) {
|
||||
return puts(coffee.tree(code).toString());
|
||||
if (o.tokens) {
|
||||
return print_tokens(CoffeeScript.tokens(code));
|
||||
} else if (o.nodes) {
|
||||
return puts(CoffeeScript.nodes(code).toString());
|
||||
} else {
|
||||
js = coffee.compile(code, o);
|
||||
if (opts.run) {
|
||||
return eval(js);
|
||||
} else if (opts.print) {
|
||||
return puts(js);
|
||||
} else if (opts.lint) {
|
||||
return lint(js);
|
||||
} else {
|
||||
js = CoffeeScript.compile(code, compile_options());
|
||||
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 {
|
||||
__filename = source;
|
||||
__dirname = path.dirname(source);
|
||||
return eval(js);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (opts.watch) {
|
||||
if (o.watch) {
|
||||
return puts(err.message);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Listen for and compile scripts over stdio.
|
||||
compile_stdio = function compile_stdio() {
|
||||
var code;
|
||||
code = '';
|
||||
process.stdio.open();
|
||||
process.stdio.addListener('data', function(string) {
|
||||
if (string) {
|
||||
return code += string;
|
||||
}
|
||||
});
|
||||
return process.stdio.addListener('close', function() {
|
||||
return process.stdio.write(CoffeeScript.compile(code, compile_options()));
|
||||
});
|
||||
};
|
||||
// Watch a list of source CoffeeScript files, recompiling them every time the
|
||||
// files are updated.
|
||||
watch_scripts = function watch_scripts() {
|
||||
var _a, _b, _c, source, watch;
|
||||
var _a, _b, _c, _d, source, watch;
|
||||
watch = function watch(source) {
|
||||
return process.watchFile(source, {
|
||||
return fs.watchFile(source, {
|
||||
persistent: true,
|
||||
interval: 500
|
||||
}, function(curr, prev) {
|
||||
@@ -112,7 +139,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));
|
||||
}
|
||||
@@ -143,59 +170,29 @@
|
||||
jsl.write(js);
|
||||
return jsl.close();
|
||||
};
|
||||
// Pretty-print a token stream.
|
||||
print_tokens = function print_tokens(tokens) {
|
||||
var _a, _b, _c, _d, strings, token;
|
||||
strings = (function() {
|
||||
_a = []; _b = tokens;
|
||||
for (_c = 0, _d = _b.length; _c < _d; _c++) {
|
||||
token = _b[_c];
|
||||
_a.push('[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']');
|
||||
}
|
||||
return _a;
|
||||
}).call(this);
|
||||
return puts(strings.join(' '));
|
||||
};
|
||||
// Use OptionParser for all the options.
|
||||
parse_options = function parse_options() {
|
||||
var oparser, opts, paths;
|
||||
opts = (options = {});
|
||||
oparser = (option_parser = new optparse.OptionParser(SWITCHES));
|
||||
oparser.banner = BANNER;
|
||||
oparser.add('interactive', function() {
|
||||
return opts.interactive = true;
|
||||
});
|
||||
oparser.add('run', function() {
|
||||
return opts.run = true;
|
||||
});
|
||||
oparser.add('output', function(dir) {
|
||||
return opts.output = dir;
|
||||
});
|
||||
oparser.add('watch', function() {
|
||||
return opts.watch = true;
|
||||
});
|
||||
oparser.add('print', function() {
|
||||
return opts.print = true;
|
||||
});
|
||||
oparser.add('lint', function() {
|
||||
return opts.lint = true;
|
||||
});
|
||||
oparser.add('eval', function() {
|
||||
return opts.eval = true;
|
||||
});
|
||||
oparser.add('tokens', function() {
|
||||
return opts.tokens = true;
|
||||
});
|
||||
oparser.add('tree', function() {
|
||||
return opts.tree = true;
|
||||
});
|
||||
oparser.add('no-wrap', function() {
|
||||
return opts.no_wrap = true;
|
||||
});
|
||||
oparser.add('help', (function(__this) {
|
||||
var __func = function() {
|
||||
return usage();
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
oparser.add('version', (function(__this) {
|
||||
var __func = function() {
|
||||
return version();
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
paths = oparser.parse(process.ARGV);
|
||||
return sources = paths.slice(2, paths.length);
|
||||
option_parser = new optparse.OptionParser(SWITCHES, BANNER);
|
||||
options = option_parser.parse(process.ARGV);
|
||||
return sources = options.arguments.slice(2, options.arguments.length);
|
||||
};
|
||||
})();
|
||||
// The options to pass to the CoffeeScript compiler.
|
||||
compile_options = function compile_options() {
|
||||
return options['no-wrap'] ? {
|
||||
no_wrap: true
|
||||
} : {};
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -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() {
|
||||
@@ -285,11 +285,26 @@
|
||||
// Indexing into an object or array.
|
||||
Index: [o("INDEX_START Expression INDEX_END", function() {
|
||||
return new IndexNode($2);
|
||||
}), o("SOAKED_INDEX_START Expression SOAKED_INDEX_END", function() {
|
||||
return new IndexNode($2, 'soak');
|
||||
})
|
||||
],
|
||||
// An object literal.
|
||||
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).
|
||||
@@ -303,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;
|
||||
})
|
||||
],
|
||||
@@ -343,8 +361,6 @@
|
||||
return new ValueNode(new LiteralNode('this'));
|
||||
}), o("@ Identifier", function() {
|
||||
return new ValueNode(new LiteralNode('this'), [new AccessorNode($2)]);
|
||||
}), o("@ Index", function() {
|
||||
return new ValueNode(new LiteralNode('this'), [$2]);
|
||||
})
|
||||
],
|
||||
// The range literal.
|
||||
@@ -416,13 +432,20 @@
|
||||
return new ParentheticalNode($2);
|
||||
})
|
||||
],
|
||||
// The condition for a while loop.
|
||||
WhileSource: [o("WHILE Expression", function() {
|
||||
return new WhileNode($2);
|
||||
}), o("WHILE Expression WHEN Expression", function() {
|
||||
return new WhileNode($2, {
|
||||
filter: $4
|
||||
});
|
||||
})
|
||||
],
|
||||
// The while loop. (there is no do..while).
|
||||
While: [o("WHILE Expression Block", function() {
|
||||
return new WhileNode($2, $3);
|
||||
}), o("WHILE Expression", function() {
|
||||
return new WhileNode($2, null);
|
||||
}), o("Expression WHILE Expression", function() {
|
||||
return new WhileNode($3, Expressions.wrap([$1]));
|
||||
While: [o("WhileSource Block", function() {
|
||||
return $1.add_body($2);
|
||||
}), o("Expression WhileSource", function() {
|
||||
return $2.add_body($1);
|
||||
})
|
||||
],
|
||||
// Array comprehensions, including guard and current index.
|
||||
@@ -462,7 +485,7 @@
|
||||
Switch: [o("SWITCH Expression INDENT Whens OUTDENT", function() {
|
||||
return $4.rewrite_condition($2);
|
||||
}), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() {
|
||||
return $4.rewrite_condition($2).add_else($6);
|
||||
return $4.rewrite_condition($2).add_else($6, true);
|
||||
})
|
||||
],
|
||||
// The inner list of whens.
|
||||
@@ -530,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;
|
||||
@@ -554,4 +577,4 @@
|
||||
}, {
|
||||
debug: false
|
||||
});
|
||||
})();
|
||||
})();
|
||||
|
||||
769
lib/lexer.js
769
lib/lexer.js
@@ -1,28 +1,36 @@
|
||||
(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, compact, count, include;
|
||||
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
|
||||
// matches against the beginning of the source code. When a match is found,
|
||||
// a token is produced, we consume the match, and start again. Tokens are in the
|
||||
// form:
|
||||
// [tag, value, line_number]
|
||||
// Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
|
||||
// Set up the Lexer for both Node.js and the browser, depending on where we are.
|
||||
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"];
|
||||
// CoffeeScript-only keywords -- which we're more relaxed about allowing.
|
||||
// Constants
|
||||
// ---------
|
||||
// Keywords that CoffeeScript shares in common with JavaScript.
|
||||
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. They can't
|
||||
// be used standalone, but you can reference them as an attached property.
|
||||
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.
|
||||
// The combined list of keywords is the superset that gets 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"];
|
||||
// JavaScript keywords and reserved words together, excluding CoffeeScript ones.
|
||||
// The list of keywords that are reserved by JavaScript, but not used, or are
|
||||
// used by CoffeeScript internally. We throw an error when these are encountered,
|
||||
// to avoid having a JavaScript error at runtime.
|
||||
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "const", "let", "debugger", "enum", "export", "import", "native", "__extends", "__hasProp"];
|
||||
// The superset of both JavaScript keywords and reserved words, none of which may
|
||||
// be used as identifiers or properties.
|
||||
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
|
||||
// Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
|
||||
// Token matching regexes.
|
||||
IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/;
|
||||
NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i;
|
||||
STRING = /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/;
|
||||
@@ -32,7 +40,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,306 +55,411 @@
|
||||
// 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'];
|
||||
// Tokens which could legitimately be invoked or indexed.
|
||||
// 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. A opening
|
||||
// parentheses or bracket following these tokens will be recorded as the start
|
||||
// of a function invocation or indexing operation.
|
||||
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@'];
|
||||
// Tokens that indicate an access -- keywords immediately following will be
|
||||
// treated as identifiers.
|
||||
ACCESSORS = ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@'];
|
||||
// Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
|
||||
// Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
|
||||
// occurs at the start of a line. We disambiguate these from trailing whens to
|
||||
// avoid an ambiguity in the grammar.
|
||||
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);
|
||||
// The Lexer Class
|
||||
// ---------------
|
||||
// The Lexer class 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 at a time. Slow and steady.
|
||||
Lexer.prototype.tokenize = function tokenize(code) {
|
||||
this.code = code;
|
||||
// The remainder of the source code.
|
||||
this.i = 0;
|
||||
// Current character position we're parsing.
|
||||
this.line = 0;
|
||||
// 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, line]
|
||||
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.
|
||||
Lexer.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.comment_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.line_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;
|
||||
}
|
||||
this.name_access_type();
|
||||
tag = 'IDENTIFIER';
|
||||
if (include(KEYWORDS, id) && !(include(ACCESSORS, this.tag(0)) && !this.prev().spaced)) {
|
||||
tag = id.toUpperCase();
|
||||
}
|
||||
if (include(RESERVED, id)) {
|
||||
this.identifier_error(id);
|
||||
}
|
||||
if (tag === 'WHEN' && include(BEFORE_WHEN, this.tag())) {
|
||||
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 += 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, match;
|
||||
if (!((match = this.chunk.match(HEREDOC)))) {
|
||||
return false;
|
||||
}
|
||||
doc = this.sanitize_heredoc(match[2] || match[4]);
|
||||
this.token('STRING', '"' + doc + '"');
|
||||
this.line += 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 (include(NOT_REGEX, this.tag())) {
|
||||
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, lines;
|
||||
if (!((comment = this.match(COMMENT, 1)))) {
|
||||
return false;
|
||||
}
|
||||
this.line += (comment.match(MULTILINER) || []).length;
|
||||
lines = comment.replace(COMMENT_CLEANER, '').split(MULTILINER);
|
||||
this.token('COMMENT', compact(lines));
|
||||
this.token('TERMINATOR', "\n");
|
||||
this.i += comment.length;
|
||||
return true;
|
||||
};
|
||||
// Matches newlines, indents, and outdents, and determines which is which.
|
||||
Lexer.prototype.line_token = function line_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;
|
||||
prev = this.prev(2);
|
||||
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
|
||||
next_character = this.chunk.match(MULTI_DENT)[4];
|
||||
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
|
||||
if (size === this.indent) {
|
||||
if (no_newlines) {
|
||||
return this.suppress_newlines(indent);
|
||||
}
|
||||
return this.newline_token(indent);
|
||||
} else if (size > this.indent) {
|
||||
if (no_newlines) {
|
||||
return this.suppress_newlines(indent);
|
||||
}
|
||||
diff = size - this.indent;
|
||||
this.token('INDENT', diff);
|
||||
this.indents.push(diff);
|
||||
} else {
|
||||
this.tag(1, 'PROPERTY_ACCESS');
|
||||
this.outdent_token(this.indent - size, no_newlines);
|
||||
}
|
||||
this.indent = size;
|
||||
return true;
|
||||
};
|
||||
// Record an outdent token or tokens, if we happen to be moving back inwards
|
||||
// past multiple recorded indents.
|
||||
Lexer.prototype.outdent_token = function outdent_token(move_out, no_newlines) {
|
||||
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' || no_newlines)) {
|
||||
this.token('TERMINATOR', "\n");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Matches and consumes non-meaningful whitespace. Tag the previous token
|
||||
// as being "spaced", because there are some cases where it makes a difference.
|
||||
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;
|
||||
};
|
||||
// Generate a newline token. Multiple newlines get merged together.
|
||||
Lexer.prototype.newline_token = function newline_token(newlines) {
|
||||
if (!(this.tag() === 'TERMINATOR')) {
|
||||
this.token('TERMINATOR', "\n");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Use a `\` at a line-ending to suppress the newline.
|
||||
// The slash is removed here once its 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 Jison 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 (include(JS_FORBIDDEN, this.value)) {
|
||||
this.assignment_error();
|
||||
}
|
||||
} 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 (include(CALLABLE, this.tag()) && not_spaced) {
|
||||
if (value === '(') {
|
||||
tag = 'CALL_START';
|
||||
}
|
||||
if (value === '[') {
|
||||
tag = 'INDEX_START';
|
||||
}
|
||||
}
|
||||
this.token(tag, value);
|
||||
this.i += value.length;
|
||||
return true;
|
||||
};
|
||||
// Token Manipulators
|
||||
// ------------------
|
||||
// As we consume a new `IDENTIFIER`, look at the previous token to determine
|
||||
// if it's a special kind of accessor.
|
||||
Lexer.prototype.name_access_type = function name_access_type() {
|
||||
if (this.value() === '::') {
|
||||
this.tag(1, 'PROTOTYPE_ACCESS');
|
||||
}
|
||||
if (this.value() === '.' && !(this.value(2) === '.')) {
|
||||
if (this.tag(2) === '?') {
|
||||
this.tag(1, 'SOAK_ACCESS');
|
||||
return this.tokens.splice(-2, 1);
|
||||
} else {
|
||||
return this.tag(1, 'PROPERTY_ACCESS');
|
||||
}
|
||||
}
|
||||
};
|
||||
// Sanitize a heredoc by escaping double quotes and erasing all external
|
||||
// indentation on the left-hand side.
|
||||
Lexer.prototype.sanitize_heredoc = function sanitize_heredoc(doc) {
|
||||
var indent;
|
||||
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
|
||||
return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(/"/g, '\\"');
|
||||
};
|
||||
// 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 at the end of the file.
|
||||
Lexer.prototype.close_indentation = function close_indentation() {
|
||||
return this.outdent_token(this.indent);
|
||||
};
|
||||
// Error for when you try to use a forbidden word in JavaScript as
|
||||
// an identifier.
|
||||
Lexer.prototype.identifier_error = function identifier_error(word) {
|
||||
throw new Error('SyntaxError: Reserved word "' + word + '" on line ' + this.line);
|
||||
};
|
||||
// Error for when you try to assign to a reserved word in JavaScript,
|
||||
// like "function" or "default".
|
||||
Lexer.prototype.assignment_error = function assignment_error() {
|
||||
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
|
||||
};
|
||||
// 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]);
|
||||
};
|
||||
// Peek 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];
|
||||
};
|
||||
// Peek 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];
|
||||
};
|
||||
// Peek at a previous token, entire.
|
||||
Lexer.prototype.prev = function prev(index) {
|
||||
return this.tokens[this.tokens.length - (index || 1)];
|
||||
};
|
||||
// Attempt to match a string against the current chunk, returning the indexed
|
||||
// match if successful, and `false` otherwise.
|
||||
Lexer.prototype.match = function match(regex, index) {
|
||||
var m;
|
||||
if (!((m = this.chunk.match(regex)))) {
|
||||
return false;
|
||||
}
|
||||
return m ? m[index] : false;
|
||||
};
|
||||
return Lexer;
|
||||
}).call(this);
|
||||
// Utility Functions
|
||||
// -----------------
|
||||
// Does a list include a value?
|
||||
include = function include(list, value) {
|
||||
return list.indexOf(value) >= 0;
|
||||
};
|
||||
// Trim out all falsy values from an array.
|
||||
compact = function compact(array) {
|
||||
var _a, _b, _c, _d, item;
|
||||
_a = []; _b = array;
|
||||
for (_c = 0, _d = _b.length; _c < _d; _c++) {
|
||||
item = _b[_c];
|
||||
if (item) {
|
||||
_a.push(item);
|
||||
}
|
||||
}
|
||||
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;
|
||||
return _a;
|
||||
};
|
||||
// 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, 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);
|
||||
tag = value;
|
||||
if (value.match(ASSIGNMENT)) {
|
||||
tag = 'ASSIGN';
|
||||
if (JS_FORBIDDEN.indexOf(this.value()) >= 0) {
|
||||
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
|
||||
}
|
||||
}
|
||||
if (value === ';') {
|
||||
tag = 'TERMINATOR';
|
||||
}
|
||||
if (CALLABLE.indexOf(this.tag()) >= 0 && (!this.prev() || !this.prev().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) {
|
||||
// Count the number of occurences of a character in a string.
|
||||
count = function count(string, letter) {
|
||||
var num, pos;
|
||||
num = 0;
|
||||
pos = string.indexOf(letter);
|
||||
@@ -356,44 +469,4 @@
|
||||
}
|
||||
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) {
|
||||
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.
|
||||
lex.prototype.close_indentation = function close_indentation() {
|
||||
return this.outdent_token(this.indent);
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -41,4 +41,4 @@
|
||||
return factories[topId] = factories[topId] || this.reload(topId, path);
|
||||
};
|
||||
require.loader.loaders.unshift([".coffee", loader]);
|
||||
})();
|
||||
})();
|
||||
|
||||
1108
lib/nodes.js
1108
lib/nodes.js
File diff suppressed because it is too large
Load Diff
165
lib/optparse.js
165
lib/optparse.js
@@ -1,80 +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) {
|
||||
this.banner = 'Usage: [Options]';
|
||||
this.options_title = 'Available options:';
|
||||
this.rules = build_rules(rules);
|
||||
this.actions = {};
|
||||
return this;
|
||||
});
|
||||
// Add a callback to fire when a particular option is encountered.
|
||||
op.prototype.add = function add(value, callback) {
|
||||
return this.actions[value] = callback;
|
||||
};
|
||||
// Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
|
||||
op.prototype.parse = function parse(args) {
|
||||
var _a, _b, arg, callback, is_option, results, rule, value;
|
||||
results = [];
|
||||
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) {
|
||||
callback = this.actions[rule.name];
|
||||
value = rule.argument && args.shift();
|
||||
if (callback) {
|
||||
callback(value);
|
||||
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;
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
is_option = true;
|
||||
break;
|
||||
}
|
||||
if (is_option && !matched_rule) {
|
||||
throw new Error("unrecognized option: " + arg);
|
||||
}
|
||||
if (!(is_option)) {
|
||||
options.arguments.push(arg);
|
||||
}
|
||||
}
|
||||
if (!(is_option)) {
|
||||
results.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 results;
|
||||
};
|
||||
// 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) {
|
||||
@@ -95,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;
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
320
lib/parser.js
320
lib/parser.js
File diff suppressed because one or more lines are too long
@@ -29,4 +29,4 @@
|
||||
process.stdio.addListener('data', readline);
|
||||
process.stdio.open();
|
||||
print(prompt);
|
||||
})();
|
||||
})();
|
||||
|
||||
681
lib/rewriter.js
681
lib/rewriter.js
@@ -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']];
|
||||
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,348 @@
|
||||
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;
|
||||
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 "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);
|
||||
return 0;
|
||||
after = this.tokens[i + 2];
|
||||
if (after && after[0] === 'INDENT') {
|
||||
this.tokens.splice(i + 2, 1);
|
||||
this.tokens.splice(i, 0, 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] !== ';') || (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);
|
||||
})();
|
||||
|
||||
192
lib/scope.js
192
lib/scope.js
@@ -7,108 +7,110 @@
|
||||
// Scope objects form a tree corresponding to the shape of the function
|
||||
// definitions present in the script. They provide lexical scope, to determine
|
||||
// whether a variable has been seen before or if it needs to be declared.
|
||||
//
|
||||
// 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);
|
||||
})();
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
"version": "0.5.0"
|
||||
"version": "0.5.4"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -24,22 +24,27 @@ parser.lexer: {
|
||||
showPosition: -> @pos
|
||||
}
|
||||
|
||||
exports.VERSION: '0.5.0'
|
||||
exports.VERSION: '0.5.4'
|
||||
|
||||
# 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
|
||||
|
||||
# Pretty-print a token stream.
|
||||
exports.print_tokens: (tokens) ->
|
||||
strings: for token in tokens
|
||||
'[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']'
|
||||
puts strings.join(' ')
|
||||
# Activate CoffeeScript in the browser by having it compile and eval
|
||||
# all script tags with a content-type of text/coffeescript.
|
||||
if document? and document.getElementsByTagName
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
fs: require 'fs'
|
||||
path: require 'path'
|
||||
coffee: require 'coffee-script'
|
||||
optparse: require('optparse')
|
||||
fs: require 'fs'
|
||||
path: require 'path'
|
||||
optparse: require 'optparse'
|
||||
CoffeeScript: require 'coffee-script'
|
||||
|
||||
BANNER: '''
|
||||
coffee compiles CoffeeScript source files into JavaScript.
|
||||
@@ -11,16 +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']
|
||||
[ '--no-wrap', 'compile without the top-level function wrapper']
|
||||
['-t', '--tokens', 'print the tokens that the lexer produces']
|
||||
['-tr','--tree', 'print the parse tree that Jison produces']
|
||||
['-n', '--no-wrap', 'compile without the top-level function wrapper']
|
||||
['-n', '--nodes', 'print the parse tree that Jison produces']
|
||||
['-v', '--version', 'display CoffeeScript version']
|
||||
['-h', '--help', 'display this help message']
|
||||
]
|
||||
@@ -32,9 +33,12 @@ option_parser: null
|
||||
# The CommandLine handles all of the functionality of the `coffee` utility.
|
||||
exports.run: ->
|
||||
parse_options()
|
||||
return require 'repl' if options.interactive
|
||||
return compile_script 'terminal', sources[0] if options.eval
|
||||
usage() unless sources.length
|
||||
return usage() if options.help
|
||||
return version() if options.version
|
||||
return require 'repl' if options.interactive
|
||||
return compile_stdio() if options.stdio
|
||||
return compile_script 'unknown', sources[0] if options.eval
|
||||
return usage() unless sources.length
|
||||
separator: sources.indexOf '--'
|
||||
flags: []
|
||||
if separator >= 0
|
||||
@@ -52,39 +56,51 @@ usage: ->
|
||||
|
||||
# The "--version" message.
|
||||
version: ->
|
||||
puts "CoffeeScript version " + coffee.VERSION
|
||||
puts "CoffeeScript version " + CoffeeScript.VERSION
|
||||
process.exit 0
|
||||
|
||||
# Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
|
||||
# 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
|
||||
# requested options. Both compile_scripts and watch_scripts share this method.
|
||||
compile_script: (source, code) ->
|
||||
opts: options
|
||||
o: if opts.no_wrap then {no_wrap: true} else {}
|
||||
o: options
|
||||
try
|
||||
if opts.tokens then coffee.print_tokens coffee.tokenize code
|
||||
else if opts.tree then puts coffee.tree(code).toString()
|
||||
if o.tokens then print_tokens CoffeeScript.tokens code
|
||||
else if o.nodes then puts CoffeeScript.nodes(code).toString()
|
||||
else
|
||||
js: coffee.compile code, o
|
||||
if opts.run then eval js
|
||||
else if opts.print then puts js
|
||||
else if opts.lint then lint js
|
||||
else write_js source, js
|
||||
js: CoffeeScript.compile code, compile_options()
|
||||
if o.compile then write_js source, js
|
||||
else if o.lint then lint js
|
||||
else if o.print or o.eval then print js
|
||||
else
|
||||
__filename: source
|
||||
__dirname: path.dirname source
|
||||
eval js
|
||||
catch err
|
||||
if opts.watch then puts err.message else throw err
|
||||
if o.watch then puts err.message else throw err
|
||||
|
||||
# Listen for and compile scripts over stdio.
|
||||
compile_stdio: ->
|
||||
code: ''
|
||||
process.stdio.open()
|
||||
process.stdio.addListener 'data', (string) ->
|
||||
code += string if string
|
||||
process.stdio.addListener 'close', ->
|
||||
process.stdio.write CoffeeScript.compile code, compile_options()
|
||||
|
||||
# Watch a list of source CoffeeScript files, recompiling them every time the
|
||||
# files are updated.
|
||||
watch_scripts: ->
|
||||
watch: (source) ->
|
||||
process.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
|
||||
fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
|
||||
return if curr.mtime.getTime() is prev.mtime.getTime()
|
||||
fs.readFile source, (err, code) -> compile_script(source, code)
|
||||
watch(source) for source in sources
|
||||
@@ -106,25 +122,18 @@ lint: (js) ->
|
||||
jsl.write js
|
||||
jsl.close()
|
||||
|
||||
# Pretty-print a token stream.
|
||||
print_tokens: (tokens) ->
|
||||
strings: for token in tokens
|
||||
'[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']'
|
||||
puts strings.join(' ')
|
||||
|
||||
# Use OptionParser for all the options.
|
||||
parse_options: ->
|
||||
opts: options: {}
|
||||
oparser: option_parser: new optparse.OptionParser SWITCHES
|
||||
oparser.banner: BANNER
|
||||
|
||||
oparser.add 'interactive', -> opts.interactive: true
|
||||
oparser.add 'run', -> opts.run: true
|
||||
oparser.add 'output', (dir) -> opts.output: dir
|
||||
oparser.add 'watch', -> opts.watch: true
|
||||
oparser.add 'print', -> opts.print: true
|
||||
oparser.add 'lint', -> opts.lint: true
|
||||
oparser.add 'eval', -> opts.eval: true
|
||||
oparser.add 'tokens', -> opts.tokens: true
|
||||
oparser.add 'tree', -> opts.tree: true
|
||||
oparser.add 'no-wrap', -> opts.no_wrap: true
|
||||
oparser.add 'help', => usage()
|
||||
oparser.add 'version', => version()
|
||||
|
||||
paths: oparser.parse(process.ARGV)
|
||||
sources: paths[2...paths.length]
|
||||
option_parser: new optparse.OptionParser SWITCHES, BANNER
|
||||
options: option_parser.parse(process.ARGV)
|
||||
sources: options.arguments[2...options.arguments.length]
|
||||
|
||||
# The options to pass to the CoffeeScript compiler.
|
||||
compile_options: ->
|
||||
if options['no-wrap'] then {no_wrap: true} else {}
|
||||
|
||||
@@ -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"
|
||||
@@ -251,11 +252,21 @@ grammar: {
|
||||
# Indexing into an object or array.
|
||||
Index: [
|
||||
o "INDEX_START Expression INDEX_END", -> new IndexNode($2)
|
||||
o "SOAKED_INDEX_START Expression SOAKED_INDEX_END", -> new IndexNode($2, 'soak')
|
||||
]
|
||||
|
||||
# An object literal.
|
||||
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).
|
||||
@@ -265,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
|
||||
]
|
||||
|
||||
@@ -300,7 +315,6 @@ grammar: {
|
||||
This: [
|
||||
o "@", -> new ValueNode(new LiteralNode('this'))
|
||||
o "@ Identifier", -> new ValueNode(new LiteralNode('this'), [new AccessorNode($2)])
|
||||
o "@ Index", -> new ValueNode(new LiteralNode('this'), [$2])
|
||||
]
|
||||
|
||||
# The range literal.
|
||||
@@ -361,11 +375,16 @@ grammar: {
|
||||
o "( Expression )", -> new ParentheticalNode($2)
|
||||
]
|
||||
|
||||
# The condition for a while loop.
|
||||
WhileSource: [
|
||||
o "WHILE Expression", -> new WhileNode($2)
|
||||
o "WHILE Expression WHEN Expression", -> new WhileNode($2, {filter : $4})
|
||||
]
|
||||
|
||||
# The while loop. (there is no do..while).
|
||||
While: [
|
||||
o "WHILE Expression Block", -> new WhileNode($2, $3)
|
||||
o "WHILE Expression", -> new WhileNode($2, null)
|
||||
o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap([$1]))
|
||||
o "WhileSource Block", -> $1.add_body $2
|
||||
o "Expression WhileSource", -> $2.add_body $1
|
||||
]
|
||||
|
||||
# Array comprehensions, including guard and current index.
|
||||
@@ -385,16 +404,14 @@ grammar: {
|
||||
ForSource: [
|
||||
o "IN Expression", -> {source: $2}
|
||||
o "OF Expression", -> {source: $2, object: true}
|
||||
o "ForSource WHEN Expression", ->
|
||||
$1.filter: $3; $1
|
||||
o "ForSource BY Expression", ->
|
||||
$1.step: $3; $1
|
||||
o "ForSource WHEN Expression", -> $1.filter: $3; $1
|
||||
o "ForSource BY Expression", -> $1.step: $3; $1
|
||||
]
|
||||
|
||||
# Switch/When blocks.
|
||||
Switch: [
|
||||
o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition($2)
|
||||
o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6)
|
||||
o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6, true)
|
||||
]
|
||||
|
||||
# The inner list of whens.
|
||||
@@ -407,8 +424,7 @@ grammar: {
|
||||
When: [
|
||||
o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, null, {statement: true})
|
||||
o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, null, {statement: true})
|
||||
o "Comment TERMINATOR When", ->
|
||||
$3.comment: $1; $3
|
||||
o "Comment TERMINATOR When", -> $3.comment: $1; $3
|
||||
]
|
||||
|
||||
# The most basic form of "if".
|
||||
|
||||
516
src/lexer.coffee
516
src/lexer.coffee
@@ -1,17 +1,23 @@
|
||||
# The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
|
||||
# matches against the beginning of the source code. When a match is found,
|
||||
# a token is produced, we consume the match, and start again. Tokens are in the
|
||||
# form:
|
||||
#
|
||||
# [tag, value, line_number]
|
||||
#
|
||||
# Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
|
||||
|
||||
# Set up the Lexer for both Node.js and the browser, depending on where we are.
|
||||
if process?
|
||||
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: ->
|
||||
# Constants
|
||||
# ---------
|
||||
|
||||
# Constants ============================================================
|
||||
|
||||
# Keywords that CoffeScript shares in common with JS.
|
||||
# Keywords that CoffeeScript shares in common with JavaScript.
|
||||
JS_KEYWORDS: [
|
||||
"if", "else",
|
||||
"true", "false",
|
||||
@@ -20,10 +26,11 @@ 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.
|
||||
# CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
|
||||
# be used standalone, but you can reference them as an attached property.
|
||||
COFFEE_KEYWORDS: [
|
||||
"then", "unless",
|
||||
"yes", "no", "on", "off",
|
||||
@@ -31,20 +38,24 @@ COFFEE_KEYWORDS: [
|
||||
"of", "by", "where", "when"
|
||||
]
|
||||
|
||||
# The list of keywords passed verbatim to the parser.
|
||||
# The combined list of keywords is the superset that gets 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. We throw an error when these are encountered,
|
||||
# to avoid having a JavaScript error at runtime.
|
||||
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.
|
||||
# The superset of both JavaScript keywords and reserved words, none of which may
|
||||
# be used as identifiers or properties.
|
||||
JS_FORBIDDEN: JS_KEYWORDS.concat RESERVED
|
||||
|
||||
# Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
|
||||
# Token matching regexes.
|
||||
IDENTIFIER : /^([a-zA-Z$_](\w|\$)*)/
|
||||
NUMBER : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
|
||||
STRING : /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/
|
||||
@@ -54,7 +65,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]*)/
|
||||
@@ -70,250 +81,309 @@ 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.
|
||||
# Tokens which could legitimately be invoked or indexed. A opening
|
||||
# parentheses or bracket following these tokens will be recorded as the start
|
||||
# of a function invocation or indexing operation.
|
||||
CALLABLE: ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@']
|
||||
|
||||
# Tokens that indicate an access -- keywords immediately following will be
|
||||
# treated as identifiers.
|
||||
ACCESSORS: ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@']
|
||||
|
||||
# Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
|
||||
# Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
|
||||
# occurs at the start of a line. We disambiguate these from trailing whens to
|
||||
# avoid an ambiguity in the grammar.
|
||||
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 Class
|
||||
# ---------------
|
||||
|
||||
# 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()
|
||||
# The Lexer class 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
|
||||
|
||||
# Tokenizers ==========================================================
|
||||
# Scan by attempting to match tokens one at a time. Slow and steady.
|
||||
tokenize: (code) ->
|
||||
@code : code # The remainder of the source code.
|
||||
@i : 0 # Current character position we're parsing.
|
||||
@line : 0 # 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, line]
|
||||
while @i < @code.length
|
||||
@chunk: @code.slice(@i)
|
||||
@extract_next_token()
|
||||
@close_indentation()
|
||||
(new Rewriter()).rewrite @tokens
|
||||
|
||||
# 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)
|
||||
# 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 @comment_token()
|
||||
return if @line_token()
|
||||
return if @whitespace_token()
|
||||
return @literal_token()
|
||||
|
||||
# Tokenizers
|
||||
# ----------
|
||||
|
||||
# Matches identifying literals: variables, keywords, method names, etc.
|
||||
identifier_token: ->
|
||||
return false unless id: @match IDENTIFIER, 1
|
||||
@name_access_type()
|
||||
tag: 'IDENTIFIER'
|
||||
tag: id.toUpperCase() if include(KEYWORDS, id) and
|
||||
not (include(ACCESSORS, @tag(0)) and not @prev().spaced)
|
||||
@identifier_error id if include RESERVED, id
|
||||
tag: 'LEADING_WHEN' if tag is 'WHEN' and include BEFORE_WHEN, @tag()
|
||||
@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: @sanitize_heredoc match[2] or match[4]
|
||||
@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 include NOT_REGEX, @tag()
|
||||
@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
|
||||
lines: comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
|
||||
@token 'COMMENT', compact lines
|
||||
@token 'TERMINATOR', "\n"
|
||||
@i += comment.length
|
||||
true
|
||||
|
||||
# Matches newlines, indents, and outdents, and determines which is which.
|
||||
line_token: ->
|
||||
return false unless indent: @match MULTI_DENT, 1
|
||||
@line += indent.match(MULTILINER).length
|
||||
@i += indent.length
|
||||
prev: @prev(2)
|
||||
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
|
||||
next_character: @chunk.match(MULTI_DENT)[4]
|
||||
no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and
|
||||
prev and (prev[0] isnt '.') and not @value().match(CODE))
|
||||
if size is @indent
|
||||
return @suppress_newlines(indent) if no_newlines
|
||||
return @newline_token(indent)
|
||||
else if size > @indent
|
||||
return @suppress_newlines(indent) if no_newlines
|
||||
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, no_newlines
|
||||
@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 outdent token or tokens, if we happen to be moving back inwards
|
||||
# past multiple recorded indents.
|
||||
outdent_token: (move_out, no_newlines) ->
|
||||
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' or no_newlines
|
||||
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. Tag the previous token
|
||||
# as being "spaced", because there are some cases where it makes a difference.
|
||||
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
|
||||
# Generate a newline token. Multiple newlines get merged together.
|
||||
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
|
||||
# Use a `\` at a line-ending to suppress the newline.
|
||||
# The slash is removed here once its 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 Jison 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'
|
||||
@assignment_error() if include JS_FORBIDDEN, @value
|
||||
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 include(CALLABLE, @tag()) 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
|
||||
# Token Manipulators
|
||||
# ------------------
|
||||
|
||||
# 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
|
||||
# As we consume a new `IDENTIFIER`, look at the previous token to determine
|
||||
# if it's a special kind of accessor.
|
||||
name_access_type: ->
|
||||
@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'
|
||||
|
||||
# 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
|
||||
# Sanitize a heredoc by escaping double quotes and erasing all external
|
||||
# indentation on the left-hand side.
|
||||
sanitize_heredoc: (doc) ->
|
||||
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
|
||||
doc.replace(new RegExp("^" + indent, 'gm'), '')
|
||||
.replace(MULTILINER, "\\n")
|
||||
.replace(/"/g, '\\"')
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# Multiple newlines get merged together.
|
||||
# Use a trailing \ to escape newlines.
|
||||
lex::newline_token: (newlines) ->
|
||||
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
|
||||
true
|
||||
# Close up all remaining open blocks at the end of the file.
|
||||
close_indentation: ->
|
||||
@outdent_token(@indent)
|
||||
|
||||
# Tokens to explicitly escape newlines are removed once their job is done.
|
||||
lex::suppress_newlines: (newlines) ->
|
||||
@tokens.pop() if @value() is "\\"
|
||||
true
|
||||
# Error for when you try to use a forbidden word in JavaScript as
|
||||
# an identifier.
|
||||
identifier_error: (word) ->
|
||||
throw new Error 'SyntaxError: Reserved word "' + word + '" on line ' + @line
|
||||
|
||||
# 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)
|
||||
tag: value
|
||||
if value.match(ASSIGNMENT)
|
||||
tag: 'ASSIGN'
|
||||
throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
|
||||
tag: 'TERMINATOR' if value == ';'
|
||||
# Error for when you try to assign to a reserved word in JavaScript,
|
||||
# like "function" or "default".
|
||||
assignment_error: ->
|
||||
throw new Error 'SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned'
|
||||
|
||||
if CALLABLE.indexOf(@tag()) >= 0 and (not @prev() or not @prev().spaced)
|
||||
tag: 'CALL_START' if value is '('
|
||||
tag: 'INDEX_START' if value is '['
|
||||
@token tag, value
|
||||
@i += value.length
|
||||
true
|
||||
# Helpers
|
||||
# -------
|
||||
|
||||
# Helpers =============================================================
|
||||
# Add a token to the results, taking note of the line number.
|
||||
token: (tag, value) ->
|
||||
@tokens.push([tag, value, @line])
|
||||
|
||||
# Add a token to the results, taking note of the line number.
|
||||
lex::token: (tag, value) ->
|
||||
@tokens.push([tag, value, @line])
|
||||
# Peek at a tag in the current token stream.
|
||||
tag: (index, tag) ->
|
||||
return unless tok: @prev(index)
|
||||
return tok[0]: tag if tag?
|
||||
tok[0]
|
||||
|
||||
# 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]
|
||||
# Peek at a value in the current token stream.
|
||||
value: (index, val) ->
|
||||
return unless tok: @prev(index)
|
||||
return tok[1]: val if val?
|
||||
tok[1]
|
||||
|
||||
# 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]
|
||||
# Peek at a previous token, entire.
|
||||
prev: (index) ->
|
||||
@tokens[@tokens.length - (index or 1)]
|
||||
|
||||
# Look at a previous token.
|
||||
lex::prev: (index) ->
|
||||
@tokens[@tokens.length - (index or 1)]
|
||||
# Attempt to match a string against the current chunk, returning the indexed
|
||||
# match if successful, and `false` otherwise.
|
||||
match: (regex, index) ->
|
||||
return false unless m: @chunk.match(regex)
|
||||
if m then m[index] else false
|
||||
|
||||
# Count the occurences of a character in a string.
|
||||
lex::count: (string, letter) ->
|
||||
# Utility Functions
|
||||
# -----------------
|
||||
|
||||
# Does a list include a value?
|
||||
include: (list, value) ->
|
||||
list.indexOf(value) >= 0
|
||||
|
||||
# Trim out all falsy values from an array.
|
||||
compact: (array) -> item for item in array when item
|
||||
|
||||
# Count the number of 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
|
||||
|
||||
# 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)
|
||||
|
||||
412
src/nodes.coffee
412
src/nodes.coffee
@@ -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: {}
|
||||
@@ -35,18 +36,16 @@ del: (obj, key) ->
|
||||
delete obj[key]
|
||||
val
|
||||
|
||||
# Quickie inheritance convenience wrapper to reduce typing.
|
||||
inherit: (parent, props) ->
|
||||
klass: del(props, 'constructor')
|
||||
klass extends parent
|
||||
(klass.prototype[name]: prop) for name, prop of props
|
||||
klass
|
||||
# Quickie helper for a generated LiteralNode.
|
||||
literal: (name) ->
|
||||
new LiteralNode(name)
|
||||
|
||||
# Mark a node as a statement, or a statement only.
|
||||
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,62 +53,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.
|
||||
Node: exports.Node: ->
|
||||
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.
|
||||
Node::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.
|
||||
Node::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)
|
||||
|
||||
# Quick short method for the current indentation level, plus tabbing in.
|
||||
Node::idt: (tabs) ->
|
||||
idt: (@indent || '')
|
||||
idt += TAB for i in [0...(tabs or 0)]
|
||||
idt
|
||||
# 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: literal(o.scope.free_variable())
|
||||
compiled: new AssignNode(reference, this)
|
||||
[compiled, reference]
|
||||
|
||||
# Does this node, or any of its children, contain a node of a certain kind?
|
||||
Node::contains: (block) ->
|
||||
for node in @children
|
||||
return true if block(node)
|
||||
return true if node instanceof Node and node.contains block
|
||||
false
|
||||
# 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
|
||||
|
||||
# toString representation of the node, for inspecting the parse tree.
|
||||
Node::toString: (idt) ->
|
||||
idt ||= ''
|
||||
'\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
|
||||
# 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
|
||||
|
||||
# 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.
|
||||
Node::unwrap: -> this
|
||||
Node::children: []
|
||||
Node::is_statement: -> false
|
||||
Node::is_statement_only: -> false
|
||||
Node::top_sensitive: -> false
|
||||
Node::operation_sensitive: -> false
|
||||
|
||||
# A collection of nodes, each one representing an expression.
|
||||
Expressions: exports.Expressions: inherit Node, {
|
||||
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) ->
|
||||
@@ -137,7 +149,7 @@ Expressions: exports.Expressions: inherit Node, {
|
||||
|
||||
compile: (o) ->
|
||||
o ||= {}
|
||||
if o.scope then Node::compile.call(this, o) else @compile_root(o)
|
||||
if o.scope then super(o) else @compile_root(o)
|
||||
|
||||
# Compile each expression in the Expressions body.
|
||||
compile_node: (o) ->
|
||||
@@ -149,7 +161,7 @@ Expressions: exports.Expressions: inherit Node, {
|
||||
o.scope: new Scope(null, this, null)
|
||||
code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
|
||||
code: code.replace(TRAILING_WHITESPACE, '')
|
||||
if o.no_wrap then code else "(function(){\n"+code+"\n})();"
|
||||
if o.no_wrap then code else "(function(){\n"+code+"\n})();\n"
|
||||
|
||||
# Compile the expressions body, with declarations of all inner variables
|
||||
# pushed up to the top.
|
||||
@@ -174,8 +186,6 @@ Expressions: exports.Expressions: inherit Node, {
|
||||
# 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
|
||||
@@ -183,20 +193,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 Node, {
|
||||
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 ''
|
||||
@@ -205,35 +217,29 @@ LiteralNode: exports.LiteralNode: inherit Node, {
|
||||
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 Node, {
|
||||
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 Node, {
|
||||
exports.ValueNode: class ValueNode extends BaseNode
|
||||
type: 'Value'
|
||||
|
||||
SOAK: " == null ? undefined : "
|
||||
SOAK: " == undefined ? undefined : "
|
||||
|
||||
constructor: (base, properties) ->
|
||||
@children: flatten [@base: base, @properties: (properties or [])]
|
||||
this
|
||||
|
||||
push: (prop) ->
|
||||
@properties.push(prop)
|
||||
@@ -272,31 +278,29 @@ ValueNode: exports.ValueNode: inherit Node, {
|
||||
props: if only then @properties[0...@properties.length - 1] else @properties
|
||||
baseline: @base.compile o
|
||||
baseline: '(' + baseline + ')' if @base instanceof ObjectNode and @has_properties()
|
||||
parts: [baseline]
|
||||
complete: @last: baseline
|
||||
|
||||
for prop in props
|
||||
if prop instanceof AccessorNode and prop.soak
|
||||
@source: baseline
|
||||
if prop.soak_node
|
||||
soaked: true
|
||||
if @base instanceof CallNode and prop is props[0]
|
||||
temp: o.scope.free_variable()
|
||||
parts[parts.length - 1]: '(' + temp + ' = ' + baseline + ')' + @SOAK + (baseline: temp + prop.compile(o))
|
||||
complete: '(' + temp + ' = ' + complete + ')' + @SOAK + (baseline: temp + prop.compile(o))
|
||||
else
|
||||
parts[parts.length - 1] += (@SOAK + (baseline += prop.compile(o)))
|
||||
complete: complete + @SOAK + (baseline += prop.compile(o))
|
||||
else
|
||||
part: prop.compile(o)
|
||||
baseline += part
|
||||
parts.push(part)
|
||||
complete += part
|
||||
@last: part
|
||||
|
||||
@last: parts[parts.length - 1]
|
||||
@source: if parts.length > 1 then parts[0...(parts.length - 1)].join('') else null
|
||||
code: parts.join('').replace(/\)\(\)\)/, '()))')
|
||||
if op and soaked then '(' + code + ')' else code
|
||||
if op and soaked then '(' + complete + ')' else complete
|
||||
|
||||
}
|
||||
|
||||
# Pass through CoffeeScript comments into JavaScript comments at the
|
||||
# same position.
|
||||
CommentNode: exports.CommentNode: inherit Node, {
|
||||
exports.CommentNode: class CommentNode extends BaseNode
|
||||
type: 'Comment'
|
||||
|
||||
constructor: (lines) ->
|
||||
@@ -306,19 +310,17 @@ CommentNode: exports.CommentNode: inherit Node, {
|
||||
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 Node, {
|
||||
exports.CallNode: class CallNode extends BaseNode
|
||||
type: 'Call'
|
||||
|
||||
constructor: (variable, args) ->
|
||||
@children: flatten [@variable: variable, @args: (args or [])]
|
||||
@prefix: ''
|
||||
this
|
||||
|
||||
new_instance: ->
|
||||
@prefix: 'new '
|
||||
@@ -350,92 +352,78 @@ CallNode: exports.CallNode: inherit Node, {
|
||||
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('') + ')'
|
||||
|
||||
# If the code generation wished to use the result of a function call
|
||||
# in multiple places, ensure that the function is only ever called once.
|
||||
compile_reference: (o) ->
|
||||
reference: new LiteralNode(o.scope.free_variable())
|
||||
call: new ParentheticalNode(new AssignNode(reference, this))
|
||||
[call, reference]
|
||||
|
||||
}
|
||||
|
||||
# Node to extend an object's prototype with an ancestor object.
|
||||
# After goog.inherits from the Closure Library.
|
||||
ExtendsNode: exports.ExtendsNode: inherit Node, {
|
||||
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 literal('__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 Node, {
|
||||
exports.AccessorNode: class AccessorNode extends BaseNode
|
||||
type: 'Accessor'
|
||||
|
||||
constructor: (name, tag) ->
|
||||
@children: [@name: name]
|
||||
@prototype: tag is 'prototype'
|
||||
@soak: tag is 'soak'
|
||||
@soak_node: tag is 'soak'
|
||||
this
|
||||
|
||||
compile_node: (o) ->
|
||||
'.' + (if @prototype then 'prototype.' else '') + @name.compile(o)
|
||||
|
||||
}
|
||||
|
||||
# An indexed accessor into a part of an array or object.
|
||||
IndexNode: exports.IndexNode: inherit Node, {
|
||||
exports.IndexNode: class IndexNode extends BaseNode
|
||||
type: 'Index'
|
||||
|
||||
constructor: (index) ->
|
||||
@children: [@index: index]
|
||||
this
|
||||
constructor: (index, tag) ->
|
||||
@children: [@index: index]
|
||||
@soak_node: tag is 'soak'
|
||||
|
||||
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 Node, {
|
||||
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 +435,7 @@ RangeNode: exports.RangeNode: inherit Node, {
|
||||
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
|
||||
@@ -460,16 +448,15 @@ RangeNode: exports.RangeNode: inherit Node, {
|
||||
# TODO: This generates pretty ugly code ... shrink it.
|
||||
compile_array: (o) ->
|
||||
name: o.scope.free_variable()
|
||||
body: Expressions.wrap([new LiteralNode(name)])
|
||||
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, new LiteralNode(name))])
|
||||
body: Expressions.wrap([literal(name)])
|
||||
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(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 Node, {
|
||||
exports.SliceNode: class SliceNode extends BaseNode
|
||||
type: 'Slice'
|
||||
|
||||
constructor: (range) ->
|
||||
@@ -482,15 +469,13 @@ SliceNode: exports.SliceNode: inherit Node, {
|
||||
plus_part: if @range.exclusive then '' else ' + 1'
|
||||
".slice(" + from + ', ' + to + plus_part + ')'
|
||||
|
||||
}
|
||||
|
||||
# An object literal.
|
||||
ObjectNode: exports.ObjectNode: inherit Node, {
|
||||
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 +494,56 @@ ObjectNode: exports.ObjectNode: inherit Node, {
|
||||
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(literal('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
|
||||
|
||||
if not constructor
|
||||
if @parent
|
||||
applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
|
||||
constructor: new AssignNode(@variable, new CodeNode([], new Expressions([
|
||||
new CallNode(applied, [literal('this'), literal('arguments')])
|
||||
])))
|
||||
else
|
||||
constructor: new AssignNode(@variable, new CodeNode())
|
||||
|
||||
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 Node, {
|
||||
exports.ArrayNode: class ArrayNode extends BaseNode
|
||||
type: 'Array'
|
||||
|
||||
constructor: (objects) ->
|
||||
@children: @objects: objects or []
|
||||
this
|
||||
|
||||
compile_node: (o) ->
|
||||
o.indent: @idt(1)
|
||||
@@ -533,7 +559,6 @@ ArrayNode: exports.ArrayNode: inherit Node, {
|
||||
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.
|
||||
@@ -543,23 +568,25 @@ PushNode: exports.PushNode: {
|
||||
expr: expressions.unwrap()
|
||||
return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
|
||||
Expressions.wrap([new CallNode(
|
||||
new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]
|
||||
new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr]
|
||||
)])
|
||||
|
||||
}
|
||||
|
||||
|
||||
# A faux-node used to wrap an expressions body in a closure.
|
||||
ClosureNode: exports.ClosureNode: {
|
||||
|
||||
wrap: (expressions, statement) ->
|
||||
func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
|
||||
call: new CallNode(new ValueNode(func, [new AccessorNode(new LiteralNode('call'))]), [new LiteralNode('this')])
|
||||
call: new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')])
|
||||
if statement then Expressions.wrap([call]) else call
|
||||
|
||||
}
|
||||
|
||||
|
||||
# Setting the value of a local variable, or the value of an object property.
|
||||
AssignNode: exports.AssignNode: inherit Node, {
|
||||
exports.AssignNode: class AssignNode extends BaseNode
|
||||
type: 'Assign'
|
||||
|
||||
PROTO_ASSIGN: /^(\S+)\.prototype/
|
||||
@@ -568,7 +595,6 @@ AssignNode: exports.AssignNode: inherit Node, {
|
||||
constructor: (variable, value, context) ->
|
||||
@children: [@variable: variable, @value: value]
|
||||
@context: context
|
||||
this
|
||||
|
||||
top_sensitive: ->
|
||||
true
|
||||
@@ -604,7 +630,8 @@ AssignNode: exports.AssignNode: inherit Node, {
|
||||
# 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
|
||||
@@ -612,12 +639,14 @@ AssignNode: exports.AssignNode: inherit Node, {
|
||||
[obj, idx]: [obj.value, obj.variable.base] if @variable.is_object()
|
||||
access_class: if @variable.is_array() then IndexNode else AccessorNode
|
||||
if obj instanceof SplatNode
|
||||
val: new LiteralNode(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
|
||||
val: literal(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
|
||||
else
|
||||
idx: new LiteralNode(idx) unless typeof idx is 'object'
|
||||
val: new ValueNode(new LiteralNode(val_var), [new access_class(idx)])
|
||||
idx: literal(idx) unless typeof idx is 'object'
|
||||
val: new ValueNode(literal(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 +657,16 @@ AssignNode: exports.AssignNode: inherit Node, {
|
||||
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 Node, {
|
||||
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 +694,26 @@ CodeNode: exports.CodeNode: inherit Node, {
|
||||
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 Node, {
|
||||
exports.SplatNode: class SplatNode extends BaseNode
|
||||
type: 'Splat'
|
||||
|
||||
constructor: (name) ->
|
||||
name: new LiteralNode(name) unless name.compile
|
||||
name: literal(name) unless name.compile
|
||||
@children: [@name: name]
|
||||
this
|
||||
|
||||
compile_node: (o) ->
|
||||
if @index? then @compile_param(o) else @name.compile(o)
|
||||
@@ -695,15 +726,18 @@ SplatNode: exports.SplatNode: inherit Node, {
|
||||
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 Node, {
|
||||
exports.WhileNode: class WhileNode extends BaseNode
|
||||
type: 'While'
|
||||
|
||||
constructor: (condition, body) ->
|
||||
@children:[@condition: condition, @body: body]
|
||||
constructor: (condition, opts) ->
|
||||
@children:[@condition: condition]
|
||||
@filter: opts and opts.filter
|
||||
|
||||
add_body: (body) ->
|
||||
@children.push @body: body
|
||||
this
|
||||
|
||||
top_sensitive: ->
|
||||
@@ -719,19 +753,19 @@ WhileNode: exports.WhileNode: inherit Node, {
|
||||
if not top
|
||||
rvar: o.scope.free_variable()
|
||||
set: @idt() + rvar + ' = [];\n'
|
||||
@body: PushNode.wrap(rvar, @body)
|
||||
@body: PushNode.wrap(rvar, @body) if @body
|
||||
post: if returns then '\n' + @idt() + 'return ' + rvar + ';' else ''
|
||||
pre: set + @idt() + 'while (' + cond + ')'
|
||||
return pre + ' null;' + post if not @body
|
||||
@body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter
|
||||
pre + ' {\n' + @body.compile(o) + '\n' + @idt() + '}' + post
|
||||
|
||||
}
|
||||
|
||||
statement WhileNode
|
||||
|
||||
|
||||
# Simple Arithmetic and logical operations. Performs some conversion from
|
||||
# CoffeeScript operations into their JavaScript equivalents.
|
||||
OpNode: exports.OpNode: inherit Node, {
|
||||
exports.OpNode: class OpNode extends BaseNode
|
||||
type: 'Op'
|
||||
|
||||
CONVERSIONS: {
|
||||
@@ -753,7 +787,6 @@ OpNode: exports.OpNode: inherit Node, {
|
||||
@children: compact [@first: first, @second: second]
|
||||
@operator: @CONVERSIONS[operator] or operator
|
||||
@flip: !!flip
|
||||
this
|
||||
|
||||
is_unary: ->
|
||||
not @second
|
||||
@@ -792,10 +825,9 @@ OpNode: exports.OpNode: inherit Node, {
|
||||
parts: parts.reverse() if @flip
|
||||
parts.join('')
|
||||
|
||||
}
|
||||
|
||||
# A try/catch/finally block.
|
||||
TryNode: exports.TryNode: inherit Node, {
|
||||
exports.TryNode: class TryNode extends BaseNode
|
||||
type: 'Try'
|
||||
|
||||
constructor: (attempt, error, recovery, ensure) ->
|
||||
@@ -811,50 +843,45 @@ TryNode: exports.TryNode: inherit Node, {
|
||||
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 Node, {
|
||||
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 Node, {
|
||||
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]
|
||||
[first, second]: variable.compile_reference(o) if variable instanceof CallNode
|
||||
if variable instanceof CallNode or (variable instanceof ValueNode and variable.has_properties())
|
||||
[first, second]: variable.compile_reference(o)
|
||||
'(typeof ' + first.compile(o) + ' !== "undefined" && ' + second.compile(o) + ' !== null)'
|
||||
|
||||
|
||||
# An extra set of parentheses, specified explicitly in the source.
|
||||
ParentheticalNode: exports.ParentheticalNode: inherit Node, {
|
||||
exports.ParentheticalNode: class ParentheticalNode extends BaseNode
|
||||
type: 'Paren'
|
||||
|
||||
constructor: (expression) ->
|
||||
@children: [@expression: expression]
|
||||
this
|
||||
|
||||
is_statement: ->
|
||||
@expression.is_statement()
|
||||
@@ -866,13 +893,12 @@ ParentheticalNode: exports.ParentheticalNode: inherit Node, {
|
||||
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 Node, {
|
||||
exports.ForNode: class ForNode extends BaseNode
|
||||
type: 'For'
|
||||
|
||||
constructor: (body, source, name, index) ->
|
||||
@@ -885,7 +911,6 @@ ForNode: exports.ForNode: inherit Node, {
|
||||
@object: !!source.object
|
||||
[@name, @index]: [@index, @name] if @object
|
||||
@children: compact [@body, @source, @filter]
|
||||
this
|
||||
|
||||
top_sensitive: ->
|
||||
true
|
||||
@@ -908,13 +933,15 @@ ForNode: exports.ForNode: inherit Node, {
|
||||
if range
|
||||
index_var: scope.free_variable()
|
||||
source_part: source.compile_variables(o)
|
||||
for_part: index_var + '=0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
|
||||
for_part: index_var + ' = 0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
|
||||
else
|
||||
index_var: null
|
||||
source_part: svar + ' = ' + @source.compile(o) + ';\n' + @idt()
|
||||
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
|
||||
@@ -934,15 +961,14 @@ ForNode: exports.ForNode: inherit Node, {
|
||||
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 Node, {
|
||||
exports.IfNode: class IfNode extends BaseNode
|
||||
type: 'If'
|
||||
|
||||
constructor: (condition, body, else_body, tags) ->
|
||||
@@ -953,7 +979,6 @@ IfNode: exports.IfNode: inherit Node, {
|
||||
@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()
|
||||
@@ -973,7 +998,7 @@ IfNode: exports.IfNode: inherit Node, {
|
||||
rewrite_switch: (o) ->
|
||||
assigner: @switcher
|
||||
if not (@switcher.unwrap() instanceof LiteralNode)
|
||||
variable: new LiteralNode(o.scope.free_variable())
|
||||
variable: literal(o.scope.free_variable())
|
||||
assigner: new AssignNode(variable, @switcher)
|
||||
@switcher: variable
|
||||
@condition: if @multiple
|
||||
@@ -985,9 +1010,12 @@ IfNode: exports.IfNode: inherit Node, {
|
||||
this
|
||||
|
||||
# Rewrite a chain of IfNodes to add a default case as the final else.
|
||||
add_else: (exprs) ->
|
||||
if @is_chain() then @else_body.add_else(exprs) else @else_body: exprs and exprs.unwrap()
|
||||
@children.push(exprs)
|
||||
add_else: (exprs, statement) ->
|
||||
if @is_chain()
|
||||
@else_body.add_else exprs, statement
|
||||
else
|
||||
exprs: exprs.unwrap() unless statement
|
||||
@children.push @else_body: exprs
|
||||
this
|
||||
|
||||
# If the else_body is an IfNode itself, then we've got an if-else chain.
|
||||
@@ -1031,5 +1059,3 @@ IfNode: exports.IfNode: inherit Node, {
|
||||
if_part: @condition.compile(o) + ' ? ' + @body.compile(o)
|
||||
else_part: if @else_body then @else_body.compile(o) else 'null'
|
||||
if_part + ' : ' + else_part
|
||||
|
||||
}
|
||||
|
||||
@@ -1,79 +1,73 @@
|
||||
# Create an OptionParser with a list of valid options.
|
||||
op: exports.OptionParser: (rules) ->
|
||||
@banner: 'Usage: [Options]'
|
||||
@options_title: 'Available options:'
|
||||
@rules: build_rules(rules)
|
||||
@actions: {}
|
||||
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
|
||||
|
||||
# Add a callback to fire when a particular option is encountered.
|
||||
op::add: (value, callback) ->
|
||||
@actions[value]: callback
|
||||
constructor: (rules, banner) ->
|
||||
@banner: banner
|
||||
@rules: build_rules(rules)
|
||||
|
||||
# Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
|
||||
op::parse: (args) ->
|
||||
results: []
|
||||
args: args.concat []
|
||||
while (arg: args.shift())
|
||||
is_option: false
|
||||
# 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
|
||||
callback: @actions[rule.name]
|
||||
value: rule.argument and args.shift()
|
||||
callback(value) if callback
|
||||
is_option: true
|
||||
break
|
||||
results.push arg unless is_option
|
||||
results
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
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'], ['INDEX_START', 'INDEX_END']]
|
||||
['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'],
|
||||
['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']]
|
||||
|
||||
# Tokens that signal the start of a balanced pair.
|
||||
EXPRESSION_START: pair[0] for pair in BALANCED_PAIRS
|
||||
@@ -36,217 +32,215 @@ 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 "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)
|
||||
return 0
|
||||
# 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'
|
||||
after: @tokens[i + 2]
|
||||
if after and after[0] is 'INDENT'
|
||||
@tokens.splice(i + 2, 1)
|
||||
@tokens.splice(i, 0, 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
|
||||
(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
|
||||
|
||||
109
src/scope.coffee
109
src/scope.coffee
@@ -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 ', '
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
results: [1, 2, 3].map (x) ->
|
||||
x * x
|
||||
|
||||
ok results.join(' ') is '1 4 9', 'basic block syntax'
|
||||
ok results.join(' ') is '1 4 9', 'basic block syntax'
|
||||
|
||||
|
||||
# Chained blocks, with proper indentation levels:
|
||||
results: []
|
||||
|
||||
counter: {
|
||||
tick: (func) ->
|
||||
results.push func()
|
||||
this
|
||||
}
|
||||
|
||||
counter
|
||||
.tick ->
|
||||
3
|
||||
.tick ->
|
||||
2
|
||||
.tick ->
|
||||
1
|
||||
|
||||
ok results.join(' ') is '3 2 1'
|
||||
46
test/test_classes.coffee
Normal file
46
test/test_classes.coffee
Normal file
@@ -0,0 +1,46 @@
|
||||
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'
|
||||
|
||||
|
||||
class OneClass
|
||||
constructor: (name) -> @name: name
|
||||
|
||||
class TwoClass extends OneClass
|
||||
|
||||
ok (new TwoClass('three')).name is 'three'
|
||||
@@ -41,10 +41,16 @@ obj: {
|
||||
|
||||
ok obj?.prop is "hello"
|
||||
|
||||
ok obj?['prop'] is "hello"
|
||||
|
||||
ok obj.prop?.length is 5
|
||||
|
||||
ok obj?['prop']?['length'] is 5
|
||||
|
||||
ok obj?.prop?.non?.existent?.property is undefined
|
||||
|
||||
ok obj?['non']?['existent'].property is undefined
|
||||
|
||||
|
||||
# Soaks and caches method calls as well.
|
||||
|
||||
@@ -68,3 +74,8 @@ result: value?.toString().toLowerCase()
|
||||
|
||||
ok result is '10'
|
||||
|
||||
|
||||
# Safely existence test on soaks.
|
||||
result: not value?.property?
|
||||
ok result
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ ok y.x.name is 'x'
|
||||
() ->
|
||||
|
||||
|
||||
# Multiple nested function declarations mixed with implicit calls should not
|
||||
# cause a syntax error.
|
||||
(one) -> (two) -> three four, (five) -> six seven, eight, (nine) ->
|
||||
|
||||
|
||||
obj: {
|
||||
name: "Fred"
|
||||
|
||||
@@ -70,11 +75,17 @@ ok result is 10
|
||||
|
||||
# And even with strange things like this:
|
||||
|
||||
funcs: [(x) -> x, (x) -> x * x]
|
||||
funcs: [((x) -> x), ((x) -> x * x)]
|
||||
result: funcs[1] 5
|
||||
|
||||
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
|
||||
|
||||
@@ -23,3 +23,13 @@ ok func()
|
||||
func
|
||||
func
|
||||
# Line3
|
||||
|
||||
obj: {
|
||||
# comment
|
||||
# comment
|
||||
# comment
|
||||
one: 1
|
||||
# comment
|
||||
two: 2
|
||||
# comment
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ a: '''
|
||||
|
||||
ok a is " a\n b\nc"
|
||||
|
||||
|
||||
a: '''
|
||||
a
|
||||
|
||||
@@ -44,3 +45,8 @@ b c
|
||||
'''
|
||||
|
||||
ok a is "a\n\n\nb c"
|
||||
|
||||
|
||||
a: '''more"than"one"quote'''
|
||||
|
||||
ok a is 'more"than"one"quote'
|
||||
@@ -1,4 +1,4 @@
|
||||
a: [(x) -> x, (x) -> x * x]
|
||||
a: [((x) -> x), ((x) -> x * x)]
|
||||
|
||||
ok a.length is 2
|
||||
|
||||
@@ -26,12 +26,6 @@ reg: /\\/
|
||||
ok reg(str) and str is '\\'
|
||||
|
||||
|
||||
i: 10
|
||||
while i -= 1
|
||||
|
||||
ok i is 0
|
||||
|
||||
|
||||
money$: 'dollars'
|
||||
|
||||
ok money$ is 'dollars'
|
||||
|
||||
@@ -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
12
test/test_regexps.coffee
Normal 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
|
||||
@@ -41,3 +41,24 @@ result: switch num += 5
|
||||
|
||||
ok result
|
||||
|
||||
|
||||
# Ensure that trailing switch elses don't get rewritten.
|
||||
result: false
|
||||
switch "word"
|
||||
when "one thing"
|
||||
do_something()
|
||||
else
|
||||
result: true unless false
|
||||
|
||||
ok result
|
||||
|
||||
result: false
|
||||
switch "word"
|
||||
when "one thing"
|
||||
do_something()
|
||||
when "other thing"
|
||||
do_something()
|
||||
else
|
||||
result: true unless false
|
||||
|
||||
ok result
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
i: 100
|
||||
while i -= 1
|
||||
|
||||
ok i is 0
|
||||
|
||||
|
||||
i: 5
|
||||
list: while i -= 1
|
||||
i * 2
|
||||
@@ -25,4 +19,12 @@ results: while func 1
|
||||
assert()
|
||||
i
|
||||
|
||||
ok results.join(' ') is '4 3 2 1'
|
||||
ok results.join(' ') is '4 3 2 1'
|
||||
|
||||
|
||||
i: 10
|
||||
results: while i -= 1 when i % 2 is 0
|
||||
i * 2
|
||||
|
||||
ok results.join(' ') is '16 12 8 4'
|
||||
|
||||
|
||||
6
vendor/jison/lib/jison.js
vendored
6
vendor/jison/lib/jison.js
vendored
@@ -369,8 +369,8 @@ lookaheadMixin.followSets = function followSets () {
|
||||
var part = production.handle.slice(i+1);
|
||||
|
||||
set = self.first(part);
|
||||
if (set.length === 0 && bool) { // set was nullable
|
||||
set = nonterminals[production.symbol].follows;
|
||||
if (self.nullable(part) && bool) {
|
||||
set.push.apply(set, nonterminals[production.symbol].follows);
|
||||
}
|
||||
}
|
||||
oldcount = nonterminals[t].follows.length;
|
||||
@@ -838,7 +838,7 @@ lrGeneratorMixin.generateModule = function generateModule (opt) {
|
||||
var out = "/* Jison generated parser */\n";
|
||||
out += (moduleName.match(/\./) ? moduleName : "var "+moduleName)+" = (function(){";
|
||||
out += "\nvar parser = "+this.generateModule_();
|
||||
if (this.lexer) {
|
||||
if (this.lexer && this.lexer.generateModule) {
|
||||
out += this.lexer.generateModule();
|
||||
out += "\nparser.lexer = lexer;";
|
||||
}
|
||||
|
||||
3
vendor/jison/lib/jison/lexer.js
vendored
3
vendor/jison/lib/jison/lexer.js
vendored
@@ -57,6 +57,9 @@ function buildActions (dict, tokens) {
|
||||
}
|
||||
|
||||
function RegExpLexer (dict, input, tokens) {
|
||||
if (typeof dict === 'string') {
|
||||
dict = require("./jisonlex").parse(dict);
|
||||
}
|
||||
dict = dict || {};
|
||||
|
||||
this.performAction = buildActions.call(this, dict, tokens);
|
||||
|
||||
19
vendor/jison/lib/jison/util/bnf-parser.js
vendored
19
vendor/jison/lib/jison/util/bnf-parser.js
vendored
@@ -138,8 +138,6 @@ parse: function parse(input) {
|
||||
var symbol, state, action, a, r, yyval = {}, p, len, ip = 0, newState, expected;
|
||||
symbol = lex();
|
||||
while (true) {
|
||||
this.trace("stack:", JSON.stringify(stack), "\n\t\t\tinput:", this.lexer._input);
|
||||
this.trace("vstack:", JSON.stringify(vstack));
|
||||
state = stack[stack.length - 1];
|
||||
action = table[state] && table[state][symbol];
|
||||
if (typeof action == "undefined" || !action.length || !action[0]) {
|
||||
@@ -149,11 +147,11 @@ parse: function parse(input) {
|
||||
expected.push("'" + this.terminals_[p] + "'");
|
||||
}
|
||||
}
|
||||
self.trace("stack:", JSON.stringify(stack), "symbol:", symbol, "input", this.lexer.upcomingInput());
|
||||
if (this.lexer.upcomingInput) {
|
||||
self.trace("input", this.lexer.upcomingInput());
|
||||
if (this.lexer.showPosition) {
|
||||
parseError("Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", "), {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
|
||||
} else {
|
||||
parseError("Parse error on line " + (yylineno + 1) + ": Unexpected '" + this.terminals_[symbol] + "'", {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
|
||||
}
|
||||
parseError("Parse error on line " + (yylineno + 1) + ". Expecting: " + expected.join(", ") + "\n" + (this.lexer.showPosition && this.lexer.showPosition()), {text: this.lexer.match, token: symbol, line: this.lexer.yylineno});
|
||||
}
|
||||
this.trace("action:", action);
|
||||
if (action.length > 1) {
|
||||
@@ -175,15 +173,12 @@ parse: function parse(input) {
|
||||
case 2:
|
||||
reductions++;
|
||||
len = this.productions_[a[1]][1];
|
||||
this.trace("reduce by: ", this.productions ? this.productions[a[1]] : a[1]);
|
||||
yyval.$ = vstack[vstack.length - len];
|
||||
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, a[1], vstack);
|
||||
if (typeof r !== "undefined") {
|
||||
return r;
|
||||
}
|
||||
this.trace("yyval=", JSON.stringify(yyval.$));
|
||||
if (len) {
|
||||
this.trace("production length:", len);
|
||||
stack = stack.slice(0, -1 * len * 2);
|
||||
vstack = vstack.slice(0, -1 * len);
|
||||
}
|
||||
@@ -193,10 +188,8 @@ parse: function parse(input) {
|
||||
stack.push(newState);
|
||||
break;
|
||||
case 3:
|
||||
this.trace("stack:", stack, "\n\tinput:", this.lexer._input);
|
||||
this.trace("vstack:", JSON.stringify(vstack));
|
||||
this.trace("Total reductions:", reductions);
|
||||
this.trace("Total shifts:", shifts);
|
||||
this.reductionCount = reductions;
|
||||
this.shiftCount = shifts;
|
||||
return true;
|
||||
default:;
|
||||
}
|
||||
|
||||
19
vendor/jison/lib/jison/util/lex-parser.js
vendored
19
vendor/jison/lib/jison/util/lex-parser.js
vendored
@@ -134,8 +134,6 @@ parse: function parse(input) {
|
||||
var symbol, state, action, a, r, yyval = {}, p, len, ip = 0, newState, expected;
|
||||
symbol = lex();
|
||||
while (true) {
|
||||
this.trace("stack:", JSON.stringify(stack), "\n\t\t\tinput:", this.lexer._input);
|
||||
this.trace("vstack:", JSON.stringify(vstack));
|
||||
state = stack[stack.length - 1];
|
||||
action = table[state] && table[state][symbol];
|
||||
if (typeof action == "undefined" || !action.length || !action[0]) {
|
||||
@@ -145,11 +143,11 @@ parse: function parse(input) {
|
||||
expected.push("'" + this.terminals_[p] + "'");
|
||||
}
|
||||
}
|
||||
self.trace("stack:", JSON.stringify(stack), "symbol:", symbol, "input", this.lexer.upcomingInput());
|
||||
if (this.lexer.upcomingInput) {
|
||||
self.trace("input", this.lexer.upcomingInput());
|
||||
if (this.lexer.showPosition) {
|
||||
parseError("Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", "), {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
|
||||
} else {
|
||||
parseError("Parse error on line " + (yylineno + 1) + ": Unexpected '" + this.terminals_[symbol] + "'", {text: this.lexer.match, token: this.terminals_[symbol], line: this.lexer.yylineno, expected: expected});
|
||||
}
|
||||
parseError("Parse error on line " + (yylineno + 1) + ". Expecting: " + expected.join(", ") + "\n" + (this.lexer.showPosition && this.lexer.showPosition()), {text: this.lexer.match, token: symbol, line: this.lexer.yylineno});
|
||||
}
|
||||
this.trace("action:", action);
|
||||
if (action.length > 1) {
|
||||
@@ -171,15 +169,12 @@ parse: function parse(input) {
|
||||
case 2:
|
||||
reductions++;
|
||||
len = this.productions_[a[1]][1];
|
||||
this.trace("reduce by: ", this.productions ? this.productions[a[1]] : a[1]);
|
||||
yyval.$ = vstack[vstack.length - len];
|
||||
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, a[1], vstack);
|
||||
if (typeof r !== "undefined") {
|
||||
return r;
|
||||
}
|
||||
this.trace("yyval=", JSON.stringify(yyval.$));
|
||||
if (len) {
|
||||
this.trace("production length:", len);
|
||||
stack = stack.slice(0, -1 * len * 2);
|
||||
vstack = vstack.slice(0, -1 * len);
|
||||
}
|
||||
@@ -189,10 +184,8 @@ parse: function parse(input) {
|
||||
stack.push(newState);
|
||||
break;
|
||||
case 3:
|
||||
this.trace("stack:", stack, "\n\tinput:", this.lexer._input);
|
||||
this.trace("vstack:", JSON.stringify(vstack));
|
||||
this.trace("Total reductions:", reductions);
|
||||
this.trace("Total shifts:", shifts);
|
||||
this.reductionCount = reductions;
|
||||
this.shiftCount = shifts;
|
||||
return true;
|
||||
default:;
|
||||
}
|
||||
|
||||
12
vendor/jison/tests/lexer/regexplexer.js
vendored
12
vendor/jison/tests/lexer/regexplexer.js
vendored
@@ -403,3 +403,15 @@ exports["test DJ lexer"] = function() {
|
||||
assert.equal(typeof tok, "string");
|
||||
}
|
||||
};
|
||||
|
||||
exports["test instantiation from string"] = function() {
|
||||
var dict = "%%\n'x' {return 'X';}\n'y' {return 'Y';}\n<<EOF>> {return 'EOF';}";
|
||||
|
||||
var input = "x";
|
||||
|
||||
var lexer = new RegExpLexer(dict);
|
||||
lexer.setInput(input);
|
||||
|
||||
assert.equal(lexer.lex(), "X");
|
||||
assert.equal(lexer.lex(), "EOF");
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user