Compare commits

...

42 Commits
0.3.1 ... 0.3.2

Author SHA1 Message Date
Jeremy Ashkenas
e2d75e6771 CoffeeScript 0.3.2, just in time for the Github feature 2010-02-08 10:58:49 -05:00
Jeremy Ashkenas
1aa966bba6 merging jeff olson's work 2010-02-07 15:45:05 -05:00
Jeremy Ashkenas
a347183f3d waypoint -- parser.coffee can parse basic functions 2010-02-07 15:37:05 -05:00
Jeremy Ashkenas
56499984ca waypoint -- it's beginning to parser 2010-02-07 15:15:36 -05:00
Jeremy Ashkenas
7ec0a8d653 merging node into master -- you can now pass the --narwhal flag to use narwhal instead. All tests are executing successfully against both Node.js and Narwhal/Rhino backends 2010-02-07 12:52:07 -05:00
Jeremy Ashkenas
293c2ffb5b spacing 2010-02-07 11:59:19 -05:00
Jeremy Ashkenas
5ec096e40d merging all narwhal integration into a single file, so we can merge the node branch without breaking narwhal compatibility 2010-02-07 11:54:01 -05:00
Jeremy Ashkenas
47bc1d5fda added a blocks example 2010-02-07 11:33:29 -05:00
Jeremy Ashkenas
6a59c5c9a9 merging master 2010-02-06 10:15:03 -05:00
Jeremy Ashkenas
decaea0f5f adding 'by' to array comprehensions 2010-02-06 10:12:57 -05:00
Jeremy Ashkenas
0a1873dc42 adding assign and return like they should have been 2010-02-05 22:39:39 -05:00
Jeremy Ashkenas
96eb7e2339 merging master 2010-02-05 22:02:11 -05:00
Jeremy Ashkenas
b795ae7fe1 removing arguments as a keyword -- we can detect its use at code-generation time. 2010-02-05 22:01:11 -05:00
Jeffery Olson
74b9545dc8 work on nodes.coffee and adding scope.coffee 2010-02-04 10:36:33 -08:00
Jeremy Ashkenas
0d56b89d12 Merge branch 'master' into node 2010-02-03 18:16:43 -05:00
Jeremy Ashkenas
dc7d0f1568 fixing assigning to @properties within an expression 2010-02-03 18:16:31 -05:00
Jeremy Ashkenas
9b7cfe87b5 remove parens 2010-02-02 20:44:25 -05:00
Jeremy Ashkenas
1587901367 remove parens 2010-02-02 20:44:10 -05:00
Jeremy Ashkenas
df670d47d2 merging in master 2010-02-02 20:38:44 -05:00
Jeremy Ashkenas
cb7a1033fa adding @property for this.property 2010-02-02 20:36:46 -05:00
Jeremy Ashkenas
df588bc9e8 it's puts in node, not print 2010-02-02 10:43:23 -05:00
Jeremy Ashkenas
9648ae2de1 merged in master 2010-02-02 10:41:19 -05:00
Jeremy Ashkenas
c5c841f2fc fixing bug with mixed dot and soak accessors 2010-02-02 10:39:44 -05:00
Jeffery Olson
c8ac7f0533 starting port of nodes.rb to coffee-script.. Node only, so far 2010-02-01 20:57:03 -08:00
Jeremy Ashkenas
681d4f44f4 simplifying order of operations a bit 2010-02-01 18:31:23 -05:00
Jeremy Ashkenas
db00cd6ed4 adding precedence to the Jison parser 2010-01-31 12:55:00 -05:00
Jeremy Ashkenas
3a748755df removing the peg grammar 2010-01-31 01:25:07 -05:00
Jeremy Ashkenas
a0572f161d the parser seems too big to compile 2010-01-30 23:17:36 -05:00
Jeremy Ashkenas
bad50c9aee the rewriter is done 2010-01-30 18:29:53 -05:00
Jeremy Ashkenas
c6457e010d getting there with the rewriter 2010-01-30 17:47:19 -05:00
Jeremy Ashkenas
557cdbba71 rewriter is halfway done, and working 2010-01-30 17:24:48 -05:00
Jeremy Ashkenas
84feab3492 first little piece of the rewriter 2010-01-30 17:02:05 -05:00
Jeremy Ashkenas
e755188878 mixing in sys again, for the tests 2010-01-30 16:14:13 -05:00
Jeremy Ashkenas
babeebcc1a more progress with the lexer, perhaps it's done 2010-01-30 15:56:40 -05:00
Jeremy Ashkenas
f19360c6b9 waypoint on lexing... parses basic strings, no indentation yet 2010-01-30 14:00:23 -05:00
Jeremy Ashkenas
eff2f4b520 a little further on with the lexer 2010-01-30 00:37:38 -05:00
Jeremy Ashkenas
854c796fd6 first little bit of the lexer 2010-01-30 00:08:15 -05:00
Jeremy Ashkenas
b0ecb39e9f made the path handling a little more robust 2010-01-29 23:41:18 -05:00
Jeremy Ashkenas
f5a37035cf node conversion finished, narwhal removed. 2010-01-29 23:30:54 -05:00
Jeremy Ashkenas
e08e99a403 implementing the Node REPL. 2010-01-29 22:53:44 -05:00
Jeremy Ashkenas
ba2d9df25f first draft of node.js REPL 2010-01-29 22:51:51 -05:00
Jeremy Ashkenas
83285fe170 updating resources section with rack-coffee 2010-01-27 16:38:49 -05:00
75 changed files with 4932 additions and 1527 deletions

View File

@@ -17,10 +17,14 @@ namespace :build do
sh "racc #{args[:racc_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y"
end
desc "Compile the Narwhal interface for --interactive and --run"
desc "Compile the Narwhal interface"
task :narwhal do
sh "bin/coffee lib/coffee_script/narwhal/*.coffee -o lib/coffee_script/narwhal/lib/coffee-script"
sh "mv lib/coffee_script/narwhal/lib/coffee-script/coffee-script.js lib/coffee_script/narwhal/lib/coffee-script.js"
sh "bin/coffee src/narwhal/*.coffee -o lib/coffee_script/narwhal"
end
desc "Continually compile the CoffeeScript/Node.js components with --watch"
task :node do
sh "bin/coffee -w src/*.coffee -o lib/coffee_script/"
end
desc "Compile and install the Ultraviolet syntax highlighter"

View File

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

View File

@@ -5,3 +5,5 @@ volume: 10 if band isnt spinal_tap
let_the_wild_rumpus_begin() unless answer is no
if car.speed < speed_limit then accelerate()
print "My name is " + @name

View File

@@ -1,6 +1,6 @@
Account: (customer, cart) ->
this.customer: customer
this.cart: cart
@customer: customer
@cart: cart
$('.shopping_cart').bind 'click', (event) =>
this.customer.purchase this.cart
@customer.purchase @cart

View File

@@ -1,14 +1,14 @@
Animal: ->
Animal::move: (meters) ->
alert this.name + " moved " + meters + "m."
alert @name + " moved " + meters + "m."
Snake: (name) -> this.name: name
Snake: (name) -> @name: name
Snake extends Animal
Snake::move: ->
alert "Slithering..."
super 5
Horse: (name) -> this.name: name
Horse: (name) -> @name: name
Horse extends Animal
Horse::move: ->
alert "Galloping..."

View File

@@ -51,7 +51,7 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.3.1</a>
<a href="http://gemcutter.org/gems/coffee-script">0.3.2</a>
</p>
<h2>Table of Contents</h2>
@@ -118,7 +118,7 @@ gem install coffee-script</pre>
Installing the gem provides the <tt>coffee</tt> command, which can
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
well as debug them. In conjunction with
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee</tt>
<a href="http://nodejs.org/">Node.js</a> (or <a href="http://narwhaljs.org/">Narwhal</a>), the <tt>coffee</tt>
command also provides direct evaluation and 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
@@ -130,14 +130,16 @@ gem install coffee-script</pre>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session.
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
Requires <a href="http://nodejs.org/">Node.js</a>,
or <a href="http://narwhaljs.org/">Narwhal</a>, with <tt>--narwhal</tt>.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute scripts without saving the intermediate
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
JavaScript. Requires <a href="http://nodejs.org/">Node.js</a>,
or <a href="http://narwhaljs.org/">Narwhal</a>, with <tt>--narwhal</tt>.
</td>
</tr>
<tr>
@@ -194,7 +196,7 @@ gem install coffee-script</pre>
<td><code>-n, --no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper.
(Used for CoffeeScript as a Narwhal module.)
(Used for CoffeeScript as a Node.js module.)
</td>
</tr>
<tr>
@@ -367,6 +369,9 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<p>
For single-line statements, <tt>unless</tt> can be used as the inverse of <tt>if</tt>.
</p>
<p>
As a shortcut for <tt>this.property</tt>, you can use <tt>@property</tt>.
</p>
<%= code_for('aliases') %>
<p id="splats">
@@ -624,21 +629,22 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<ul>
<li>
<a href="http://github.com/jashkenas/coffee-script/">Source Code</a><br />
After checking out the source, make sure to run <tt>rake build:parser</tt>
to generate an up-to-date version of the Racc parser.
Use <tt>bin/coffee</tt> to test your changes,
<tt>rake test</tt> to run the test suite,
<tt>rake build:parser</tt> to regenerate the Racc parser if you're
working on the grammar,
and <tt>rake gem:install</tt> to
create and install a custom version of the gem.
</li>
<li>
<a href="http://github.com/jashkenas/coffee-script/issues">Bugs, Feature Requests, and General Discussion</a>
<a href="http://github.com/jashkenas/coffee-script/issues">CoffeeScript Issues</a><br />
Bugs reports, feature requests, and general discussion all belong here.
</li>
<li>
<a href="http://github.com/creationix/coffeepot">CoffeePot</a><br />
An implementation of CoffeeScript, written in CoffeeScript, by
<a href="http://github.com/creationix">Tim Caswell</a>. Compiles just
a limited subset at this point.
<a href="http://github.com/mattly/rack-coffee">rack-coffee</a><br />
Rack middleware for serving CoffeeScripts as JavaScript directly to
the browser, without having to compile them first. From
<a href="http://github.com/mattly">Matt Lyon</a>.
</li>
<li>
<a href="http://github.com/jnicklas/bistro_car">BistroCar</a><br />
@@ -653,9 +659,22 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
</li>
<li>
<a href="http://github.com/creationix/coffeepot">CoffeePot</a><br />
An implementation of CoffeeScript, written in CoffeeScript, by
<a href="http://github.com/creationix">Tim Caswell</a>. Compiles just
a limited subset at this point.
</li>
</ul>
<h2 id="change_log">Change Log</h2>
<p>
<b class="header" style="margin-top: 20px;">0.3.2</b>
<tt>@property</tt> is now a shorthand for <tt>this.property</tt>.<br />
Switched the default JavaScript engine from Narwhal to Node.js. Pass
the <tt>--narwhal</tt> flag if you'd like to continue using it.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.3.0</b>

View File

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

View File

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

View File

@@ -11,6 +11,6 @@ index: (list, target) ->
if val < target then low: mid + 1 else high: mid
return -1
print(2 is index([10, 20, 30, 40, 50], 30))
print(4 is index([-97, 35, 67, 88, 1200], 1200))
print(0 is index([0, 45, 70], 0))
puts 2 is index([10, 20, 30, 40, 50], 30)
puts 4 is index([-97, 35, 67, 88, 1200], 1200)
puts 0 is index([0, 45, 70], 0)

View File

@@ -8,6 +8,6 @@ runtime: (N) ->
t: n - 1 + sum / n
t
print(runtime(3) is 2.6666666666666665)
print(runtime(5) is 7.4)
print(runtime(8) is 16.92142857142857)
puts runtime(3) is 2.6666666666666665
puts runtime(5) is 7.4
puts runtime(8) is 16.92142857142857

View File

@@ -26,9 +26,9 @@ match_star: (c, regexp, text) ->
return false unless text and (text[0] is c or c is '.')
text: text.slice(1)
print(match("ex", "some text"))
print(match("s..t", "spit"))
print(match("^..t", "buttercup"))
print(match("i..$", "cherries"))
print(match("o*m", "vrooooommm!"))
print(match("^hel*o$", "hellllllo"))
puts match("ex", "some text")
puts match("s..t", "spit")
puts match("^..t", "buttercup")
puts match("i..$", "cherries")
puts match("o*m", "vrooooommm!")
puts match("^hel*o$", "hellllllo")

57
examples/blocks.coffee Normal file
View File

@@ -0,0 +1,57 @@
# After wycats' http://yehudakatz.com/2010/02/07/the-building-blocks-of-ruby/
# Sinatra.
get '/hello', ->
'Hello World'
# Append.
append: (location, data) ->
path: new Pathname location
throw "Location does not exist" unless path.exists()
File.open path, 'a', (file) ->
file.puts YAML.dump data
data
# Rubinius' File.open implementation.
File.open: (path, mode, block) ->
io: new File path, mode
return io unless block
try
block io
finally
try
io.close() unless io.closed()
catch error
# nothing, just swallow them.
# Write.
write: (location, data) ->
path = new Pathname location
raise "Location does not exist" unless path.exists()
File.open path, 'w', (file) ->
return false if Digest.MD5.hexdigest(file.read()) is data.hash()
file.puts YAML.dump data
true
# Rails' respond_to.
index: ->
people: Person.find 'all'
respond_to (format) ->
format.html()
format.xml -> render { xml: people.xml() }
# Synchronization.
synchronize: (block) ->
lock()
try block() finally unlock()

View File

@@ -19,7 +19,7 @@ binary_search: (items, value) ->
# Test the function.
print(2 is binary_search([10, 20, 30, 40, 50], 30))
print(4 is binary_search([-97, 35, 67, 88, 1200], 1200))
print(0 is binary_search([0, 45, 70], 0))
print(-1 is binary_search([0, 45, 70], 10))
puts(2 is binary_search([10, 20, 30, 40, 50], 30))
puts(4 is binary_search([-97, 35, 67, 88, 1200], 1200))
puts(0 is binary_search([0, 45, 70], 0))
puts(-1 is binary_search([0, 45, 70], 10))

View File

@@ -7,5 +7,5 @@ bubble_sort: (list) ->
# Test the function.
print(bubble_sort([3, 2, 1]).join(' ') is '1 2 3')
print(bubble_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
puts(bubble_sort([3, 2, 1]).join(' ') is '1 2 3')
puts(bubble_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')

View File

@@ -91,16 +91,16 @@ LinkedList::toString: -> this.toArray().toString()
list: new LinkedList()
list.add("Hi")
print(list.size() is 1)
print(list.item(0) is "Hi")
print(list.item(1) is null)
puts(list.size() is 1)
puts(list.item(0) is "Hi")
puts(list.item(1) is null)
list: new LinkedList()
list.add("zero").add("one").add("two")
print(list.size() is 3)
print(list.item(2) is "two")
print(list.remove(1) is "one")
print(list.item(0) is "zero")
print(list.item(1) is "two")
print(list.size() is 2)
print(list.item(-10) is null)
puts(list.size() is 3)
puts(list.item(2) is "two")
puts(list.remove(1) is "one")
puts(list.item(0) is "zero")
puts(list.item(1) is "two")
puts(list.size() is 2)
puts(list.item(-10) is null)

View File

@@ -31,6 +31,6 @@ is_valid_identifier: (identifier) ->
# Tests.
print(is_valid_identifier("49927398716") is true)
print(is_valid_identifier("4408041234567893") is true)
print(is_valid_identifier("4408041234567890") is false)
puts(is_valid_identifier("49927398716") is true)
puts(is_valid_identifier("4408041234567893") is true)
puts(is_valid_identifier("4408041234567890") is false)

View File

@@ -15,5 +15,5 @@ merge_sort: (list) ->
# Test the function.
print(merge_sort([3, 2, 1]).join(' ') is '1 2 3')
print(merge_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
puts(merge_sort([3, 2, 1]).join(' ') is '1 2 3')
puts(merge_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')

View File

@@ -9,7 +9,7 @@ selection_sort: (list) ->
min: i
# Check the rest of the array to see if anything is smaller.
(min: j if list[j] < list[min]) for j in [i+1...len]
(min: j if list[j] < list[min]) for j in [(i+1)...len]
# Swap if a smaller item has been found.
[list[i], list[min]]: [list[min], list[i]] if i isnt min
@@ -19,5 +19,5 @@ selection_sort: (list) ->
# Test the function.
print(selection_sort([3, 2, 1]).join(' ') is '1 2 3')
print(selection_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
puts(selection_sort([3, 2, 1]).join(' ') is '1 2 3')
puts(selection_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')

View File

@@ -72,6 +72,12 @@
<key>name</key>
<string>constant.numeric.coffee</string>
</dict>
<dict>
<key>match</key>
<string>(@)[a-zA-Z_$]\w*</string>
<key>name</key>
<string>variable.other.readwrite.instance.coffee</string>
</dict>
<dict>
<key>name</key>
<string>string.quoted.heredoc.coffee</string>

View File

@@ -37,7 +37,7 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.3.1</a>
<a href="http://gemcutter.org/gems/coffee-script">0.3.2</a>
</p>
<h2>Table of Contents</h2>
@@ -215,7 +215,7 @@ gem install coffee-script</pre>
Installing the gem provides the <tt>coffee</tt> command, which can
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
well as debug them. In conjunction with
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee</tt>
<a href="http://nodejs.org/">Node.js</a> (or <a href="http://narwhaljs.org/">Narwhal</a>), the <tt>coffee</tt>
command also provides direct evaluation and 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
@@ -227,14 +227,16 @@ gem install coffee-script</pre>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session.
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
Requires <a href="http://nodejs.org/">Node.js</a>,
or <a href="http://narwhaljs.org/">Narwhal</a>, with <tt>--narwhal</tt>.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute scripts without saving the intermediate
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
JavaScript. Requires <a href="http://nodejs.org/">Node.js</a>,
or <a href="http://narwhaljs.org/">Narwhal</a>, with <tt>--narwhal</tt>.
</td>
</tr>
<tr>
@@ -291,7 +293,7 @@ gem install coffee-script</pre>
<td><code>-n, --no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper.
(Used for CoffeeScript as a Narwhal module.)
(Used for CoffeeScript as a Node.js module.)
</td>
</tr>
<tr>
@@ -557,6 +559,9 @@ expensive <span class="Keyword">=</span> expensive <span class="Keyword">||</spa
<p>
For single-line statements, <tt>unless</tt> can be used as the inverse of <tt>if</tt>.
</p>
<p>
As a shortcut for <tt>this.property</tt>, you can use <tt>@property</tt>.
</p>
<div class='code'><pre class="idle">launch() <span class="Keyword">if</span> ignition <span class="Keyword">is</span> <span class="BuiltInConstant">on</span>
<span class="FunctionName">volume</span><span class="Keyword">:</span> <span class="Number">10</span> <span class="Keyword">if</span> band <span class="Keyword">isnt</span> spinal_tap
@@ -564,6 +569,8 @@ expensive <span class="Keyword">=</span> expensive <span class="Keyword">||</spa
let_the_wild_rumpus_begin() <span class="Keyword">unless</span> answer <span class="Keyword">is</span> <span class="BuiltInConstant">no</span>
<span class="Keyword">if</span> car.speed <span class="Keyword">&lt;</span> speed_limit <span class="Keyword">then</span> accelerate()
print <span class="String"><span class="String">&quot;</span>My name is <span class="String">&quot;</span></span> <span class="Keyword">+</span> <span class="Variable">@name</span>
</pre><pre class="idle"><span class="Storage">var</span> volume;
<span class="Keyword">if</span> (ignition <span class="Keyword">===</span> <span class="BuiltInConstant">true</span>) {
launch();
@@ -575,6 +582,7 @@ let_the_wild_rumpus_begin() <span class="Keyword">unless</span> answer <span cla
let_the_wild_rumpus_begin();
}
car.speed <span class="Keyword">&lt;</span> speed_limit ? accelerate() : <span class="BuiltInConstant">null</span>;
<span class="LibraryFunction">print</span>(<span class="String"><span class="String">&quot;</span>My name is <span class="String">&quot;</span></span> <span class="Keyword">+</span> <span class="Variable">this</span>.<span class="LibraryConstant">name</span>);
</pre><br class='clear' /></div>
<p id="splats">
@@ -1050,12 +1058,12 @@ speed <span class="Keyword">?</span><span class="Keyword">=</span> <span class="
</pre><pre class="idle"><span class="Storage">var</span> solipsism, speed;
<span class="Keyword">if</span> ((<span class="Keyword">typeof</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> <span class="Keyword">!</span>(<span class="Keyword">typeof</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>)) {
<span class="Keyword">if</span> ((<span class="Keyword">typeof</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> mind <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> (<span class="Keyword">typeof</span> <span class="Keyword">!</span>world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> <span class="Keyword">!</span>world <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>)) {
solipsism <span class="Keyword">=</span> <span class="BuiltInConstant">true</span>;
}
speed <span class="Keyword">=</span> (<span class="Keyword">typeof</span> speed <span class="Keyword">!</span><span class="Keyword">==</span> <span class="String"><span class="String">&quot;</span>undefined<span class="String">&quot;</span></span> <span class="Keyword">&amp;</span><span class="Keyword">&amp;</span> speed <span class="Keyword">!</span><span class="Keyword">==</span> <span class="BuiltInConstant">null</span>) ? speed : <span class="Number">140</span>;
</pre><button onclick='javascript: var solipsism, speed;
if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) {
if ((typeof mind !== "undefined" && mind !== null) && (typeof !world !== "undefined" && !world !== null)) {
solipsism = true;
}
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
@@ -1101,15 +1109,15 @@ speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
</p>
<div class='code'><pre class="idle"><span class="FunctionName">Animal</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="FunctionName">Animal::move</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">meters</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
alert <span class="Variable">this</span>.name <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>
alert <span class="Variable">@name</span> <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>
<span class="FunctionName">Snake</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="FunctionName">this.name</span><span class="Keyword">:</span> name
<span class="FunctionName">Snake</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Variable">@name</span><span class="Keyword">:</span> name
Snake <span class="Variable">extends</span> Animal
<span class="FunctionName">Snake::move</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
alert <span class="String"><span class="String">&quot;</span>Slithering...<span class="String">&quot;</span></span>
<span class="Variable">super</span> <span class="Number">5</span>
<span class="FunctionName">Horse</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="FunctionName">this.name</span><span class="Keyword">:</span> name
<span class="FunctionName">Horse</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Variable">@name</span><span class="Keyword">:</span> name
Horse <span class="Variable">extends</span> Animal
<span class="FunctionName">Horse::move</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
alert <span class="String"><span class="String">&quot;</span>Galloping...<span class="String">&quot;</span></span>
@@ -1314,11 +1322,11 @@ city = __c[1];
properties of the <tt>this</tt> where they're defined.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">Account</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">customer, cart</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="FunctionName">this.customer</span><span class="Keyword">:</span> customer
<span class="FunctionName">this.cart</span><span class="Keyword">:</span> cart
<span class="Variable">@customer</span><span class="Keyword">:</span> customer
<span class="Variable">@cart</span><span class="Keyword">:</span> cart
$(<span class="String"><span class="String">'</span>.shopping_cart<span class="String">'</span></span>).bind <span class="String"><span class="String">'</span>click<span class="String">'</span></span>, <span class="FunctionArgument">(</span><span class="FunctionArgument">event</span><span class="FunctionArgument">)</span> <span class="Storage">=&gt;</span>
<span class="Variable">this</span>.customer.purchase <span class="Variable">this</span>.cart
<span class="Variable">@customer</span>.purchase <span class="Variable">@cart</span>
</pre><pre class="idle"><span class="Storage">var</span> Account;
Account <span class="Keyword">=</span> <span class="Storage">function</span> <span class="FunctionName">Account</span>(<span class="FunctionArgument">customer, cart</span>) {
<span class="Storage">var</span> __a;
@@ -1489,21 +1497,22 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
<ul>
<li>
<a href="http://github.com/jashkenas/coffee-script/">Source Code</a><br />
After checking out the source, make sure to run <tt>rake build:parser</tt>
to generate an up-to-date version of the Racc parser.
Use <tt>bin/coffee</tt> to test your changes,
<tt>rake test</tt> to run the test suite,
<tt>rake build:parser</tt> to regenerate the Racc parser if you're
working on the grammar,
and <tt>rake gem:install</tt> to
create and install a custom version of the gem.
</li>
<li>
<a href="http://github.com/jashkenas/coffee-script/issues">Bugs, Feature Requests, and General Discussion</a>
<a href="http://github.com/jashkenas/coffee-script/issues">CoffeeScript Issues</a><br />
Bugs reports, feature requests, and general discussion all belong here.
</li>
<li>
<a href="http://github.com/creationix/coffeepot">CoffeePot</a><br />
An implementation of CoffeeScript, written in CoffeeScript, by
<a href="http://github.com/creationix">Tim Caswell</a>. Compiles just
a limited subset at this point.
<a href="http://github.com/mattly/rack-coffee">rack-coffee</a><br />
Rack middleware for serving CoffeeScripts as JavaScript directly to
the browser, without having to compile them first. From
<a href="http://github.com/mattly">Matt Lyon</a>.
</li>
<li>
<a href="http://github.com/jnicklas/bistro_car">BistroCar</a><br />
@@ -1518,9 +1527,22 @@ html <span class="Keyword">=</span> <span class="String"><span class="String">&q
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
</li>
<li>
<a href="http://github.com/creationix/coffeepot">CoffeePot</a><br />
An implementation of CoffeeScript, written in CoffeeScript, by
<a href="http://github.com/creationix">Tim Caswell</a>. Compiles just
a limited subset at this point.
</li>
</ul>
<h2 id="change_log">Change Log</h2>
<p>
<b class="header" style="margin-top: 20px;">0.3.2</b>
<tt>@property</tt> is now a shorthand for <tt>this.property</tt>.<br />
Switched the default JavaScript engine from Narwhal to Node.js. Pass
the <tt>--narwhal</tt> flag if you'd like to continue using it.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.3.0</b>

View File

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

View File

@@ -0,0 +1,50 @@
(function(){
var compiler, path;
// Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
path = require('path');
// The path to the CoffeeScript executable.
compiler = path.normalize(path.dirname(__filename) + '/../../bin/coffee');
// Compile a string over stdin, with global variables, for the REPL.
exports.compile = function compile(code, callback) {
var coffee, js;
js = '';
coffee = process.createChildProcess(compiler, ['--eval', '--no-wrap', '--globals']);
coffee.addListener('output', function(results) {
if ((typeof results !== "undefined" && results !== null)) {
return js += results;
}
});
coffee.addListener('exit', function() {
return callback(js);
});
coffee.write(code);
return coffee.close();
};
// Compile a list of CoffeeScript files on disk.
exports.compile_files = function compile_files(paths, callback) {
var coffee, exit_ran, js;
js = '';
coffee = process.createChildProcess(compiler, ['--print'].concat(paths));
coffee.addListener('output', function(results) {
if ((typeof results !== "undefined" && results !== null)) {
return js += results;
}
});
// NB: we have to add a mutex to make sure it doesn't get called twice.
exit_ran = false;
coffee.addListener('exit', function() {
if (exit_ran) {
return null;
}
exit_ran = true;
return callback(js);
});
return coffee.addListener('error', function(message) {
if (!(message)) {
return null;
}
puts(message);
throw new Error("CoffeeScript compile error");
});
};
})();

View File

@@ -28,8 +28,11 @@ Usage:
# Path to the root of the CoffeeScript install.
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
# Command to execute in Narwhal
LAUNCHER = "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
# Commands to execute CoffeeScripts.
RUNNERS = {
:node => "node #{ROOT}/lib/coffee_script/runner.js",
:narwhal => "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
}
# Run the CommandLine off the contents of ARGV.
def initialize
@@ -114,20 +117,20 @@ Usage:
puts js
end
# Use Narwhal to run an interactive CoffeeScript session.
# Use Node.js or Narwhal to run an interactive CoffeeScript session.
def launch_repl
exec "#{LAUNCHER}"
exec "#{RUNNERS[@options[:runner]]}"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed to use the interactive REPL."
puts "Error: #{@options[:runner]} must be installed to use the interactive REPL."
exit(1)
end
# Use Narwhal to compile and execute CoffeeScripts.
# Use Node.js or Narwhal to compile and execute CoffeeScripts.
def run_scripts
sources = @sources.join(' ')
exec "#{LAUNCHER} #{sources}"
exec "#{RUNNERS[@options[:runner]]} #{sources}"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
puts "Error: #{@options[:runner]} must be installed in order to execute scripts."
exit(1)
end
@@ -166,12 +169,12 @@ Usage:
# Use OptionParser for all the options.
def parse_options
@options = {}
@options = {:runner => :node}
@option_parser = OptionParser.new do |opts|
opts.on('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i|
opts.on('-i', '--interactive', 'run an interactive CoffeeScript REPL') do |i|
@options[:interactive] = true
end
opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r|
opts.on('-r', '--run', 'compile and run a CoffeeScript') do |r|
@options[:run] = true
end
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
@@ -202,6 +205,9 @@ Usage:
opts.on('-g', '--globals', 'attach all top-level variable as globals') do |n|
@options[:globals] = true
end
opts.on_tail('--narwhal', 'use Narwhal instead of Node.js') do |n|
@options[:runner] = :narwhal
end
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
install_bundle
exit

View File

@@ -13,7 +13,7 @@ token FOR IN OF BY WHEN WHILE
token SWITCH LEADING_WHEN
token DELETE INSTANCEOF TYPEOF
token SUPER EXTENDS
token ARGUMENTS
token ASSIGN RETURN
token NEWLINE
token COMMENT
token JS
@@ -21,24 +21,20 @@ token INDENT OUTDENT
# Declare order of operations.
prechigh
left '?'
nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
left '*' '/' '%'
left '*' '/' '%' '?' '.'
left '+' '-'
left '<<' '>>' '>>>'
left '&' '|' '^'
left '<<' '>>' '>>>' '&' '|' '^'
left '<=' '<' '>' '>='
right '==' '!=' IS ISNT
left '&&' '||' AND OR
right '-=' '+=' '/=' '*=' '%='
right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
right DELETE INSTANCEOF TYPEOF
left '.'
right INDENT
left OUTDENT
right WHEN LEADING_WHEN IN OF BY
right THROW FOR NEW SUPER
left EXTENDS
left '||=' '&&=' '?='
right ASSIGN RETURN
right '->' '=>' UNLESS IF ELSE WHILE
preclow
@@ -102,7 +98,6 @@ rule
| REGEX { result = LiteralNode.new(val[0]) }
| BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) }
| ARGUMENTS { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(Value.new(true)) }
| FALSE { result = LiteralNode.new(Value.new(false)) }
| YES { result = LiteralNode.new(Value.new(true)) }
@@ -238,6 +233,7 @@ rule
| Object { result = ValueNode.new(val[0]) }
| Parenthetical { result = ValueNode.new(val[0]) }
| Range { result = ValueNode.new(val[0]) }
| This { result = ValueNode.new(val[0]) }
| Value Accessor { result = val[0] << val[1] }
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
;
@@ -300,6 +296,12 @@ rule
SUPER CALL_START ArgList CALL_END { result = CallNode.new(Value.new('super'), val[2]) }
;
# This references, either naked or to a property.
This:
'@' { result = ThisNode.new }
| '@' IDENTIFIER { result = ThisNode.new(val[1]) }
;
# The range literal.
Range:
"[" Expression

363
lib/coffee_script/lexer.js Normal file
View File

@@ -0,0 +1,363 @@
(function(){
var ASSIGNMENT, CALLABLE, CODE, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex;
Rewriter = require('./rewriter').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 ============================================================
// The list of keywords passed verbatim to the parser.
KEYWORDS = ["if", "else", "then", "unless", "true", "false", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "new", "return", "arguments", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "of", "by", "where", "while", "delete", "instanceof", "typeof", "switch", "when", "super", "extends"];
// 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]*?)([^\\]|\\\\)')/;
HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/;
JS = /^(``|`([\s\S]*?)([^\\]|\\\\)`)/;
OPERATOR = /^([+\*&|\/\-%=<>:!?]+)/;
WHITESPACE = /^([ \t]+)/;
COMMENT = /^(((\n?[ \t]*)?#.*$)+)/;
CODE = /^((-|=)>)/;
REGEX = /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/;
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
LAST_DENTS = /\n([ \t]*)/g;
LAST_DENT = /\n([ \t]*)/;
ASSIGNMENT = /^(:|=)$/;
// Token cleaning regexes.
JS_CLEANER = /(^`|`$)/g;
MULTILINER = /\n/g;
STRING_NEWLINES = /\n[ \t]*/g;
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/mg;
NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/;
HEREDOC_INDENT = /^[ \t]+/g;
// 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.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING'];
// 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]
this.spaced = null;
// The last token that has a space following it.
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;
}
// Keywords are special identifiers tagged with their own name,
// 'if' will result in an ['IF', "if"] token.
tag = KEYWORDS.indexOf(id) >= 0 ? id.toUpperCase() : 'IDENTIFIER';
if (tag === 'WHEN' && (this.tag() === 'OUTDENT' || this.tag() === 'INDENT')) {
tag = 'LEADING_WHEN';
}
if (tag === 'IDENTIFIER' && this.value() === '::') {
this.tag(-1, 'PROTOTYPE_ACCESS');
}
if (tag === 'IDENTIFIER' && this.value() === '.' && !(this.value(-2) === '.')) {
if (this.tag(-2) === '?') {
this.tag(-1, 'SOAK_ACCESS');
this.tokens.splice(-2, 1);
} else {
this.tag(-1, 'PROPERTY_ACCESS');
}
}
this.token(tag, id);
this.i += id.length;
return true;
};
// Matches numbers, including decimals, hex, and exponential notation.
lex.prototype.number_token = function number_token() {
var number;
if (!((number = this.match(NUMBER, 1)))) {
return false;
}
this.token('NUMBER', number);
this.i += number.length;
return true;
};
// Matches strings, including multi-line strings.
lex.prototype.string_token = function string_token() {
var escaped, string;
if (!((string = this.match(STRING, 1)))) {
return false;
}
escaped = string.replace(STRING_NEWLINES, " \\\n");
this.token('STRING', escaped);
this.line += this.count(string, "\n");
this.i += string.length;
return true;
};
// Matches heredocs, adjusting indentation to the correct level.
lex.prototype.heredoc_token = function heredoc_token() {
var doc, indent, match;
if (!((match = this.chunk.match(HEREDOC)))) {
return false;
}
doc = match[2] || match[4];
indent = doc.match(HEREDOC_INDENT).sort()[0];
doc = doc.replace(new RegExp("^" + indent, 'g'), '').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, 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];
no_newlines = next_character === '.' || (this.value().match(NO_NEWLINE) && this.tokens[this.tokens.length - 2][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;
}
this.token('TERMINATOR', "\n");
return true;
};
// Matches and consumes non-meaningful whitespace.
lex.prototype.whitespace_token = function whitespace_token() {
var space;
if (!((space = this.match(WHITESPACE, 1)))) {
return false;
}
this.spaced = this.value();
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.value() === "\n")) {
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.match(ASSIGNMENT) ? 'ASSIGN' : value;
if (value === ';') {
tag = 'TERMINATOR';
}
if (this.value() !== this.spaced && CALLABLE.indexOf(this.tag()) >= 0) {
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.tokens.push([tag, Value.new(value, @line)])
};
// Look at a tag in the current token stream.
lex.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
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.tokens[this.tokens.length - (index || 1)]))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
return (tok[1] = val);
}
return tok[1];
};
// Count the occurences of a character in a string.
lex.prototype.count = function count(string, letter) {
var num, pos;
num = 0;
pos = string.indexOf(letter);
while (pos !== -1) {
count += 1;
pos = string.indexOf(letter, pos + 1);
}
return count;
};
// 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 i, tok;
if (this.tag() !== ')') {
return null;
}
i = 0;
while (true) {
i += 1;
tok = this.tokens[this.tokens.length - i];
if (!tok) {
return null;
}
if (tok[0] === 'IDENTIFIER') {
tok[0] = 'PARAM';
} else if (tok[0] === ')') {
tok[0] = 'PARAM_END';
} else if (tok[0] === '(') {
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);
};
})();

View File

@@ -15,8 +15,7 @@ module CoffeeScript
"for", "in", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends",
"arguments"]
"super", "extends"]
# Token matching regexes.
IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/
@@ -229,8 +228,7 @@ module CoffeeScript
# Helpers ==========================================================
# Add a token to the results, taking note of the line number, and
# immediately-preceding comment.
# Add a token to the results, taking note of the line number.
def token(tag, value)
@tokens << [tag, Value.new(value, @line)]
end

View File

@@ -1,14 +1,12 @@
(function(){
var File, OS, Readline, checkForErrors, coffeePath;
// This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee
// Executes the `coffee` Ruby program to convert from CoffeeScript
// to Javascript. Eventually this will hopefully happen entirely within JS.
var File, OS, Readline, checkForErrors, coffeePath, factories, loader, puts;
// The Narwhal-compatibility wrapper for CoffeeScript.
// Require external dependencies.
OS = require('os');
File = require('file');
Readline = require('readline');
// The path to the CoffeeScript Compiler.
coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee');
coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().join('bin', 'coffee');
// Our general-purpose error handler.
checkForErrors = function checkForErrors(coffeeProcess) {
if (coffeeProcess.wait() === 0) {
@@ -17,6 +15,8 @@
system.stderr.print(coffeeProcess.stderr.read());
throw new Error("CoffeeScript compile error");
};
// Alias print to "puts", for Node.js compatibility:
puts = print;
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
// command.
exports.run = function run(args) {
@@ -77,4 +77,20 @@
return eval("(" + factoryText + ")");
}
};
// The Narwhal loader for '.coffee' files.
factories = {
};
loader = {
};
// Reload the coffee-script environment from source.
loader.reload = function reload(topId, path) {
return factories[topId] = function() {
return exports.makeNarwhalFactory(path);
};
};
// Ensure that the coffee-script environment is loaded.
loader.load = function load(topId, path) {
return factories[topId] = factories[topId] || this.reload(topId, path);
};
require.loader.loaders.unshift([".coffee", loader]);
})();

View File

@@ -1,21 +0,0 @@
(function(){
var coffeescript, factories, loader;
// This (javascript) file is generated from lib/coffee_script/narwhal/loader.coffee
coffeescript = null;
factories = {
};
loader = {
// Reload the coffee-script environment from source.
reload: function reload(topId, path) {
coffeescript = coffeescript || require('coffee-script');
return factories[topId] = function() {
return coffeescript.makeNarwhalFactory(path);
};
},
// Ensure that the coffee-script environment is loaded.
load: function load(topId, path) {
return factories[topId] = factories[topId] || this.reload(topId, path);
}
};
require.loader.loaders.unshift([".coffee", loader]);
})();

View File

@@ -1,19 +0,0 @@
# This (javascript) file is generated from lib/coffee_script/narwhal/loader.coffee
coffeescript: null
factories: {}
loader: {
# Reload the coffee-script environment from source.
reload: (topId, path) ->
coffeescript ||= require('coffee-script')
factories[topId]: -> coffeescript.makeNarwhalFactory(path)
# Ensure that the coffee-script environment is loaded.
load: (topId, path) ->
factories[topId] ||= this.reload(topId, path)
}
require.loader.loaders.unshift([".coffee", loader])

443
lib/coffee_script/nodes.js Normal file
View File

@@ -0,0 +1,443 @@
(function(){
var compact, dup, flatten;
var __hasProp = Object.prototype.hasOwnProperty;
// 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"
// method, which wraps "compile_node" in some extra smarts, to know when the
// 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.
exports.Node = function Node() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.values = arguments;
__a = this.name = this.constructor.name;
return Node === this.constructor ? this : __a;
};
exports.Expressions = function Expressions() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return Expressions === this.constructor ? this : __a;
};
exports.LiteralNode = function LiteralNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return LiteralNode === this.constructor ? this : __a;
};
exports.ReturnNode = function ReturnNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ReturnNode === this.constructor ? this : __a;
};
exports.CommentNode = function CommentNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return CommentNode === this.constructor ? this : __a;
};
exports.CallNode = function CallNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return CallNode === this.constructor ? this : __a;
};
exports.ExtendsNode = function ExtendsNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ExtendsNode === this.constructor ? this : __a;
};
exports.ValueNode = function ValueNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ValueNode === this.constructor ? this : __a;
};
exports.AccessorNode = function AccessorNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return AccessorNode === this.constructor ? this : __a;
};
exports.IndexNode = function IndexNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return IndexNode === this.constructor ? this : __a;
};
exports.RangeNode = function RangeNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return RangeNode === this.constructor ? this : __a;
};
exports.SliceNode = function SliceNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return SliceNode === this.constructor ? this : __a;
};
exports.AssignNode = function AssignNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return AssignNode === this.constructor ? this : __a;
};
exports.OpNode = function OpNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return OpNode === this.constructor ? this : __a;
};
exports.CodeNode = function CodeNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return CodeNode === this.constructor ? this : __a;
};
exports.SplatNode = function SplatNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return SplatNode === this.constructor ? this : __a;
};
exports.ObjectNode = function ObjectNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ObjectNode === this.constructor ? this : __a;
};
exports.ArrayNode = function ArrayNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ArrayNode === this.constructor ? this : __a;
};
exports.PushNode = function PushNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return PushNode === this.constructor ? this : __a;
};
exports.ClosureNode = function ClosureNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ClosureNode === this.constructor ? this : __a;
};
exports.WhileNode = function WhileNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return WhileNode === this.constructor ? this : __a;
};
exports.ForNode = function ForNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ForNode === this.constructor ? this : __a;
};
exports.TryNode = function TryNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return TryNode === this.constructor ? this : __a;
};
exports.ThrowNode = function ThrowNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ThrowNode === this.constructor ? this : __a;
};
exports.ExistenceNode = function ExistenceNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ExistenceNode === this.constructor ? this : __a;
};
exports.ParentheticalNode = function ParentheticalNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return ParentheticalNode === this.constructor ? this : __a;
};
exports.IfNode = function IfNode() {
var __a;
var arguments = Array.prototype.slice.call(arguments, 0);
this.name = this.constructor.name;
__a = this.values = arguments;
return IfNode === this.constructor ? this : __a;
};
exports.Expressions.wrap = function wrap(values) {
return this.values = values;
};
// Some helper functions
// TODO -- shallow (1 deep) flatten..
// need recursive version..
flatten = function flatten(aggList, newList) {
var __a, __b, item;
__a = newList;
for (__b = 0; __b < __a.length; __b++) {
item = __a[__b];
aggList.push(item);
}
return aggList;
};
compact = function compact(input) {
var __a, __b, __c, compected, item;
compected = [];
__a = []; __b = input;
for (__c = 0; __c < __b.length; __c++) {
item = __b[__c];
__a.push((typeof item !== "undefined" && item !== null) ? compacted.push(item) : null);
}
return __a;
};
dup = function dup(input) {
var __a, __b, __c, key, output, val;
output = null;
if (input instanceof Array) {
output = [];
__a = input;
for (__b = 0; __b < __a.length; __b++) {
val = __a[__b];
output.push(val);
}
} else {
output = {
};
__c = input;
for (key in __c) {
val = __c[key];
if (__hasProp.call(__c, key)) {
output.key = val;
}
}
output;
}
return output;
};
exports.Node.prototype.TAB = ' ';
// Tag this node as a statement, meaning that it can't be used directly as
// the result of an expression.
exports.Node.prototype.mark_as_statement = function mark_as_statement() {
return this.is_statement = function is_statement() {
return true;
};
};
// Tag this node as a statement that cannot be transformed into an expression.
// (break, continue, etc.) It doesn't make sense to try to transform it.
exports.Node.prototype.mark_as_statement_only = function mark_as_statement_only() {
this.mark_as_statement();
return this.is_statement_only = function is_statement_only() {
return true;
};
};
// This node needs to know if it's being compiled as a top-level statement,
// in order to compile without special expression conversion.
exports.Node.prototype.mark_as_top_sensitive = function mark_as_top_sensitive() {
return this.is_top_sensitive = function is_top_sensitive() {
return true;
};
};
// Provide a quick implementation of a children method.
exports.Node.prototype.children = function children(attributes) {
var __a, __b, agg, compacted, item;
// TODO -- are these optimal impls of flatten and compact
// .. do better ones exist in a stdlib?
agg = [];
__a = attributes;
for (__b = 0; __b < __a.length; __b++) {
item = __a[__b];
agg = flatten(agg, item);
}
compacted = compact(agg);
return this.children = function children() {
return compacted;
};
};
exports.Node.prototype.write = function write(code) {
// hm..
// TODO -- should print to STDOUT in "VERBOSE" how to
// go about this.. ? jsonify 'this'?
// use node's puts ??
return code;
};
// 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.
exports.Node.prototype.compile = function compile(o) {
var closure, opts, top;
// TODO -- need JS dup/clone
opts = (typeof !o !== "undefined" && !o !== null) ? {
} : o;
this.options = opts;
this.indent = opts.indent;
top = this.options.top;
!this.is_top_sentitive() ? (this.options.top = undefined) : null;
closure = this.is_statement() && !this.is_statement_only() && !top && typeof (this) === "CommentNode";
closure = closure && !this.do_i_contain(function(n) {
return n.is_statement_only();
});
return closure ? this.compile_closure(this.options) : compile_node(this.options);
};
// Statements converted into expressions share scope with their parent
// closure, to preserve JavaScript-style lexical scope.
exports.Node.prototype.compile_closure = function compile_closure(o) {
var opts;
opts = (typeof !o !== "undefined" && !o !== null) ? {
} : o;
this.indent = opts.indent;
opts.shared_scope = o.scope;
return exports.ClosureNode.wrap(this).compile(opts);
};
// Quick short method for the current indentation level, plus tabbing in.
exports.Node.prototype.idt = function idt(tLvl) {
var __a, __b, __c, __d, tabAmt, tabs, x;
tabs = (typeof tLvl !== "undefined" && tLvl !== null) ? tLvl : 0;
tabAmt = '';
__c = 0; __d = tabs;
for (__b=0, x=__c; (__c <= __d ? x < __d : x > __d); (__c <= __d ? x += 1 : x -= 1), __b++) {
tabAmt = tabAmt + this.TAB;
}
return this.indent + tabAmt;
};
//Does this node, or any of it's children, contain a node of a certain kind?
exports.Node.prototype.do_i_contain = function do_i_contain(block) {
var __a, __b, node;
__a = this.children;
for (__b = 0; __b < __a.length; __b++) {
node = __a[__b];
if (block(node)) {
return true;
}
if (node instanceof exports.Node && node.do_i_contain(block)) {
return true;
}
}
return false;
};
// Default implementations of the common node methods.
exports.Node.prototype.unwrap = function unwrap() {
return this;
};
exports.Node.prototype.children = [];
exports.Node.prototype.is_a_statement = function is_a_statement() {
return false;
};
exports.Node.prototype.is_a_statement_only = function is_a_statement_only() {
return false;
};
exports.Node.prototype.is_top_sensitive = function is_top_sensitive() {
return false;
};
// A collection of nodes, each one representing an expression.
// exports.Expressions: (nodes) ->
// this.mark_as_statement()
// this.expressions: []
// this.children([this.expressions])
// for n in nodes
// this.expressions: flatten this.expressions, n
// exports.Expressions extends exports.Node
exports.Expressions.prototype.TRAILING_WHITESPACE = /\s+$/;
// Wrap up a node as an Expressions, unless it already is.
exports.Expressions.prototype.wrap = function wrap(nodes) {
if (nodes.length === 1 && nodes[0] instanceof exports.Expressions) {
return nodes[0];
}
return new Expressions(nodes);
};
// Tack an expression on to the end of this expression list.
exports.Expressions.prototype.push = function push(node) {
this.expressions.push(node);
return this;
};
// Tack an expression on to the beginning of this expression list.
exports.Expressions.prototype.unshift = function unshift(node) {
this.expressions.unshift(node);
return this;
};
// If this Expressions consists of a single node, pull it back out.
exports.Expressions.prototype.unwrap = function unwrap() {
return this.expressions.length === 1 ? this.expressions[0] : this;
};
// Is this an empty block of code?
exports.Expressions.prototype.is_empty = function is_empty() {
return this.expressions.length === 0;
};
// Is the node last in this block of expressions.
exports.Expressions.prototype.is_last = function is_last(node) {
var arr_length;
arr_length = this.expressions.length;
this.last_index = this.last_index || this.expressions[arr_length - 1] instanceof exports.CommentNode ? -2 : -1;
return node === this.expressions[arr_length - this.last_index];
};
exports.Expressions.prototype.compile = function compile(o) {
var opts;
opts = (typeof o !== "undefined" && o !== null) ? o : {
};
return opts.scope ? exports.Expressions.__superClass__.compile.call(this, dup(opts)) : this.compile_root(o);
};
// Compile each expression in the Expressions body.
exports.Expressions.prototype.compile_node = function compile_node(options) {
var __a, __b, __c, __d, __e, code, compiled, e, line, opts;
opts = (typeof options !== "undefined" && options !== null) ? options : {
};
compiled = [];
__a = this.expressions;
for (__b = 0; __b < __a.length; __b++) {
e = __a[__b];
compiled.push(this.compile_expression(e, dup(options)));
}
code = '';
__c = []; __d = compiled;
for (__e = 0; __e < __d.length; __e++) {
line = __d[__e];
__c.push((code = code + line + '\n'));
}
return __c;
};
// If this is the top-level Expressions, wrap everything in a safety closure.
exports.Expressions.prototype.compile_root = function compile_root(o) {
var code, indent, opts;
opts = (typeof o !== "undefined" && o !== null) ? o : {
};
indent = opts.no_wrap ? '' : this.TAB;
this.indent = indent;
opts.indent = indent;
opts.scope = new Scope(null, this, null);
code = opts.globals ? compile_node(opts) : compile_with_declarations(opts);
code.replace(this.TRAILING_WHITESPACE, '');
return this.write(opts.no_wrap ? code : "(function(){\n" + code + "\n})();");
};
})();

View File

@@ -153,7 +153,7 @@ module CoffeeScript
# at the top.
def compile_with_declarations(o={})
code = compile_node(o)
args = self.contains? {|n| n.is_a?(LiteralNode) && n.arguments? }
args = self.contains? {|n| n.is_a?(ValueNode) && n.arguments? }
argv = args && o[:scope].check('arguments') ? '' : 'var '
code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
@@ -203,10 +203,6 @@ module CoffeeScript
end
alias_method :statement_only?, :statement?
def arguments?
@value.to_s == 'arguments'
end
def compile_node(o)
indent = statement? ? idt : ''
ending = statement? ? ';' : ''
@@ -346,7 +342,7 @@ module CoffeeScript
end
def properties?
return !@properties.empty?
return !@properties.empty? || @base.is_a?(ThisNode)
end
def array?
@@ -361,6 +357,10 @@ module CoffeeScript
properties? && @properties.last.is_a?(SliceNode)
end
def arguments?
@base.to_s == 'arguments'
end
def unwrap
@properties.empty? ? @base : self
end
@@ -386,7 +386,9 @@ module CoffeeScript
parts[-1] << "#{SOAK}#{baseline += prop.compile(o)}"
end
else
parts << prop.compile(o)
part = prop.compile(o)
baseline += part
parts << part
end
end
@last = parts.last
@@ -427,6 +429,18 @@ module CoffeeScript
end
end
# A this-reference, using '@'.
class ThisNode < Node
def initialize(property=nil)
@property = property
end
def compile_node(o)
prop = @property ? ".#{@property}" : ''
write("this#{prop}")
end
end
# A range literal. Ranges can be used to extract portions (slices) of arrays,
# or to specify a range for array comprehensions.
class RangeNode < Node
@@ -834,8 +848,9 @@ module CoffeeScript
for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
else
index_var = nil
source_part = "#{svar} = #{source.compile(o)};\n#{idt}"
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{ivar}++"
source_part = "#{svar} = #{@source.compile(o)};\n#{idt}"
step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
# body.unshift(AssignNode.new(@name, ValueNode.new(svar, [IndexNode.new(ivar)]))) if @name
end

477
lib/coffee_script/parser.js Normal file
View File

@@ -0,0 +1,477 @@
(function(){
var Parser, __a, __b, __c, __d, __e, __f, bnf, grammar, name, non_terminal, o, operators, option, parser, part, tokens, unwrap;
var __hasProp = Object.prototype.hasOwnProperty;
Parser = require('jison').Parser;
process.mixin(require('./nodes'));
// DSL ===================================================================
// Detect functions: [
unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/;
// Quickie DSL for Jison access.
o = function o(pattern_string, func) {
var match;
if (func) {
func = (match = (func + "").match(unwrap)) ? match[1] : '(' + func + '())';
return [pattern_string, '$$ = ' + func + ';'];
} else {
return [pattern_string, '$$ = $1;'];
}
};
// Precedence ===========================================================
operators = [["left", '?'], ["right", 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'], ["right", 'THROW', 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["left", '||=', '&&=', '?='], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
// Grammar ==============================================================
grammar = {
// All parsing will end in this rule, being the trunk of the AST.
Root: [o("", function() {
return new Expressions();
}), o("TERMINATOR", function() {
return new Expressions();
}), o("Expressions"), o("Block TERMINATOR")
],
// Any list of expressions or method body, seperated by line breaks or semis.
Expressions: [o("Expression", function() {
return Expressions.wrap([$1]);
}), o("Expressions TERMINATOR Expression", function() {
return $1.push($3);
}), o("Expressions TERMINATOR")
],
// 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")],
// 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() {
return $2;
}), o("INDENT OUTDENT", function() {
return new Expressions();
})
],
// All hard-coded values. These can be printed straight to JavaScript.
Literal: [o("NUMBER", function() {
return new LiteralNode(yytext);
}), o("STRING", function() {
return new LiteralNode(yytext);
}), o("JS", function() {
return new LiteralNode(yytext);
}), o("REGEX", function() {
return new LiteralNode(yytext);
}), o("BREAK", function() {
return new LiteralNode(yytext);
}), o("CONTINUE", function() {
return new LiteralNode(yytext);
}), o("ARGUMENTS", function() {
return new LiteralNode(yytext);
}), o("TRUE", function() {
return new LiteralNode(true);
}), o("FALSE", function() {
return new LiteralNode(false);
}), o("YES", function() {
return new LiteralNode(true);
}), o("NO", function() {
return new LiteralNode(false);
}), o("ON", function() {
return new LiteralNode(true);
}), o("OFF", function() {
return new LiteralNode(false);
})
],
// Assignment to a variable (or index).
Assign: [o("Value ASSIGN Expression", function() {
return new AssignNode($1, $3);
})
],
// Assignment within an object literal (can be quoted).
AssignObj: [o("IDENTIFIER ASSIGN Expression", function() {
return new AssignNode(new ValueNode(yytext), $3, 'object');
}), o("STRING ASSIGN Expression", function() {
return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object');
}), o("Comment")
],
// A return statement.
Return: [o("RETURN Expression", function() {
return new ReturnNode($2);
}), o("RETURN", function() {
return new ReturnNode(new ValueNode(new LiteralNode('null')));
})
],
// A comment.
Comment: [o("COMMENT", function() {
return new CommentNode(yytext);
})
],
//
// # Arithmetic and logical operators
// # For Ruby's Operator precedence, see: [
// # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
// Operation: [
// o "! Expression", -> new OpNode($1, $2)
// o "!! Expression", -> new OpNode($1, $2)
// o "- Expression", -> new OpNode($1, $2)
// o "+ Expression", -> new OpNode($1, $2)
// o "NOT Expression", -> new OpNode($1, $2)
// o "~ Expression", -> new OpNode($1, $2)
// o "-- Expression", -> new OpNode($1, $2)
// o "++ Expression", -> new OpNode($1, $2)
// o "DELETE Expression", -> new OpNode($1, $2)
// o "TYPEOF Expression", -> new OpNode($1, $2)
// o "Expression --", -> new OpNode($2, $1, null, true)
// o "Expression ++", -> new OpNode($2, $1, null, true)
//
// o "Expression * Expression", -> new OpNode($2, $1, $3)
// o "Expression / Expression", -> new OpNode($2, $1, $3)
// o "Expression % Expression", -> new OpNode($2, $1, $3)
//
// o "Expression + Expression", -> new OpNode($2, $1, $3)
// o "Expression - Expression", -> new OpNode($2, $1, $3)
//
// o "Expression << Expression", -> new OpNode($2, $1, $3)
// o "Expression >> Expression", -> new OpNode($2, $1, $3)
// o "Expression >>> Expression", -> new OpNode($2, $1, $3)
//
// o "Expression & Expression", -> new OpNode($2, $1, $3)
// o "Expression | Expression", -> new OpNode($2, $1, $3)
// o "Expression ^ Expression", -> new OpNode($2, $1, $3)
//
// o "Expression <= Expression", -> new OpNode($2, $1, $3)
// o "Expression < Expression", -> new OpNode($2, $1, $3)
// o "Expression > Expression", -> new OpNode($2, $1, $3)
// o "Expression >= Expression", -> new OpNode($2, $1, $3)
//
// o "Expression == Expression", -> new OpNode($2, $1, $3)
// o "Expression != Expression", -> new OpNode($2, $1, $3)
// o "Expression IS Expression", -> new OpNode($2, $1, $3)
// o "Expression ISNT Expression", -> new OpNode($2, $1, $3)
//
// o "Expression && Expression", -> new OpNode($2, $1, $3)
// o "Expression || Expression", -> new OpNode($2, $1, $3)
// o "Expression AND Expression", -> new OpNode($2, $1, $3)
// o "Expression OR Expression", -> new OpNode($2, $1, $3)
// o "Expression ? Expression", -> new OpNode($2, $1, $3)
//
// o "Expression -= Expression", -> new OpNode($2, $1, $3)
// o "Expression += Expression", -> new OpNode($2, $1, $3)
// o "Expression /= Expression", -> new OpNode($2, $1, $3)
// o "Expression *= Expression", -> new OpNode($2, $1, $3)
// o "Expression %= Expression", -> new OpNode($2, $1, $3)
// o "Expression ||= Expression", -> new OpNode($2, $1, $3)
// o "Expression &&= Expression", -> new OpNode($2, $1, $3)
// o "Expression ?= Expression", -> new OpNode($2, $1, $3)
//
// o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3)
// o "Expression IN Expression", -> new OpNode($2, $1, $3)
// ]
// The existence operator.
Existence: [o("Expression ?", function() {
return new ExistenceNode($1);
})
],
// Function definition.
Code: [o("PARAM_START ParamList PARAM_END FuncGlyph Block", function() {
return new CodeNode($2, $5, $4);
}), o("FuncGlyph Block", function() {
return new CodeNode([], $2, $1);
})
],
// The symbols to signify functions, and bound functions.
FuncGlyph: [o("->", function() {
return 'func';
}), o("=>", function() {
return 'boundfunc';
})
],
// The parameters to a function definition.
ParamList: [o("Param", function() {
return [$1];
}), o("ParamList , Param", function() {
return $1.push($3);
})
],
// A Parameter (or ParamSplat) in a function definition.
Param: [o("PARAM", function() {
return yytext;
}), o("PARAM . . .", function() {
return new SplatNode(yytext);
})
],
// A regular splat.
Splat: [o("Expression . . .", function() {
return new SplatNode($1);
})
],
// Expressions that can be treated as values.
Value: [o("IDENTIFIER", function() {
return new ValueNode(yytext);
}), o("Literal", function() {
return new ValueNode($1);
}), o("Array", function() {
return new ValueNode($1);
}), o("Object", function() {
return new ValueNode($1);
}), o("Parenthetical", function() {
return new ValueNode($1);
}), o("Range", function() {
return new ValueNode($1);
}),
// o "Value Accessor", -> $1.push($2)
o("Invocation Accessor", function() {
return new ValueNode($1, [$2]);
})
]
// # Accessing into an object or array, through dot or index notation.
// Accessor: [
// o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode($2)
// o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode($2, 'prototype')
// o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode($2, 'soak')
// o "Index"
// o "Slice", -> new SliceNode($1)
// ]
//
// # Indexing into an object or array.
// Index: [
// o "INDEX_START Expression INDEX_END", -> new IndexNode($2)
// ]
//
// # An object literal.
// Object: [
// o "{ AssignList }", -> new ObjectNode($2)
// ]
//
// # Assignment within an object literal (comma or newline separated).
// AssignList: [
// o "", -> []
// o "AssignObj", -> [$1]
// o "AssignList , AssignObj", -> $1.push $3
// o "AssignList TERMINATOR AssignObj", -> $1.push $3
// o "AssignList , TERMINATOR AssignObj", -> $1.push $4
// o "INDENT AssignList OUTDENT", -> $2
// ]
//
// # All flavors of function call (instantiation, super, and regular).
// Call: [
// o "Invocation", -> $1
// o "NEW Invocation", -> $2.new_instance()
// o "Super", -> $1
// ]
//
// # Extending an object's prototype.
// Extends: [
// o "Value EXTENDS Value", -> new ExtendsNode($1, $3)
// ]
//
// # A generic function invocation.
// Invocation: [
// o "Value Arguments", -> new CallNode($1, $2)
// o "Invocation Arguments", -> new CallNode($1, $2)
// ]
//
// # The list of arguments to a function invocation.
// Arguments: [
// o "CALL_START ArgList CALL_END", -> $2
// ]
//
// # Calling super.
// Super: [
// o "SUPER CALL_START ArgList CALL_END", -> new CallNode('super', $3)
// ]
//
// # The range literal.
// Range: [
// o "[ Expression . . Expression ]", -> new RangeNode($2, $5)
// o "[ Expression . . . Expression ]", -> new RangeNode($2, $6, true)
// ]
//
// # The slice literal.
// Slice: [
// o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode($2, $5)
// o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode($2, $6, true)
// ]
//
// # The array literal.
// Array: [
// o "[ ArgList ]", -> new ArrayNode($2)
// ]
//
// # A list of arguments to a method call, or as the contents of an array.
// ArgList: [
// o "", -> []
// o "Expression", -> val
// o "INDENT Expression", -> [$2]
// o "ArgList , Expression", -> $1.push $3
// o "ArgList TERMINATOR Expression", -> $1.push $3
// o "ArgList , TERMINATOR Expression", -> $1.push $4
// o "ArgList , INDENT Expression", -> $1.push $4
// o "ArgList OUTDENT", -> $1
// ]
//
// # Just simple, comma-separated, required arguments (no fancy syntax).
// SimpleArgs: [
// o "Expression", -> $1
// o "SimpleArgs , Expression", ->
// ([$1].push($3)).reduce (a, b) -> a.concat(b)
// ]
//
// # Try/catch/finally exception handling blocks.
// Try: [
// o "TRY Block Catch", -> new TryNode($2, $3[0], $3[1])
// o "TRY Block FINALLY Block", -> new TryNode($2, nil, nil, $4)
// o "TRY Block Catch FINALLY Block", -> new TryNode($2, $3[0], $3[1], $5)
// ]
//
// # A catch clause.
// Catch: [
// o "CATCH IDENTIFIER Block", -> [$2, $3]
// ]
//
// # Throw an exception.
// Throw: [
// o "THROW Expression", -> new ThrowNode($2)
// ]
//
// # Parenthetical expressions.
// Parenthetical: [
// o "( Expression )", -> new ParentheticalNode($2)
// ]
//
// # The while loop. (there is no do..while).
// While: [
// o "WHILE Expression Block", -> new WhileNode($2, $3)
// o "WHILE Expression", -> new WhileNode($2, nil)
// o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap($1))
// ]
//
// # Array comprehensions, including guard and current index.
// # Looks a little confusing, check nodes.rb for the arguments to ForNode.
// For: [
// o "Expression FOR ForVariables ForSource", -> new ForNode($1, $4, $3[0], $3[1])
// o "FOR ForVariables ForSource Block", -> new ForNode($4, $3, $2[0], $2[1])
// ]
//
// # An array comprehension has variables for the current element and index.
// ForVariables: [
// o "IDENTIFIER", -> [$1]
// o "IDENTIFIER , IDENTIFIER", -> [$1, $3]
// ]
//
// # The source of the array comprehension can optionally be filtered.
// 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
// ]
//
// # 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)
// ]
//
// # The inner list of whens.
// Whens: [
// o "When", -> $1
// o "Whens When", -> $1.push $2
// ]
//
// # An individual when.
// When: [
// o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, nil, {statement: true})
// o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, nil, {statement: true})
// o "Comment TERMINATOR When", -> $3.add_comment($1)
// ]
//
// # The most basic form of "if".
// IfBlock: [
// o "IF Expression Block", -> new IfNode($2, $3)
// ]
//
// # An elsif portion of an if-else block.
// ElsIf: [
// o "ELSE IfBlock", -> $2.force_statement()
// ]
//
// # Multiple elsifs can be chained together.
// ElsIfs: [
// o "ElsIf", -> $1
// o "ElsIfs ElsIf", -> $1.add_else($2)
// ]
//
// # Terminating else bodies are strictly optional.
// ElseBody: [
// o "", -> null
// o "ELSE Block", -> $2
// ]
//
// # All the alternatives for ending an if-else block.
// IfEnd: [
// o "ElseBody", -> $1
// o "ElsIfs ElseBody", -> $1.add_else($2)
// ]
//
// # The full complement of if blocks, including postfix one-liner ifs and unlesses.
// If: [
// o "IfBlock IfEnd", -> $1.add_else($2)
// o "Expression IF Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true})
// o "Expression UNLESS Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true, invert: true})
// ]
};
// Helpers ==============================================================
// Make the Jison parser.
bnf = {
};
tokens = [];
__a = grammar;
for (name in __a) {
non_terminal = __a[name];
if (__hasProp.call(__a, name)) {
bnf[name] = (function() {
__b = []; __c = non_terminal;
for (__d = 0; __d < __c.length; __d++) {
option = __c[__d];
__b.push((function() {
__e = option[0].split(" ");
for (__f = 0; __f < __e.length; __f++) {
part = __e[__f];
!grammar[part] ? tokens.push(part) : null;
}
name === "Root" ? (option[1] = "return " + option[1]) : null;
return option;
}).call(this));
}
return __b;
}).call(this);
}
}
tokens = tokens.join(" ");
parser = new Parser({
tokens: tokens,
bnf: bnf,
operators: operators,
startSymbol: 'Root'
}, {
debug: false
});
// Thin wrapper around the real lexer
parser.lexer = {
lex: function lex() {
var token;
token = this.tokens[this.pos] || [""];
this.pos += 1;
this.yylineno = token[2];
this.yytext = token[1];
return token[0];
},
setInput: function setInput(tokens) {
this.tokens = tokens;
return this.pos = 0;
},
upcomingInput: function upcomingInput() {
return "";
},
showPosition: function showPosition() {
return this.pos;
}
};
exports.Parser = function Parser() { };
exports.Parser.prototype.parse = function parse(tokens) {
return parser.parse(tokens);
};
})();

File diff suppressed because it is too large Load Diff

33
lib/coffee_script/repl.js Normal file
View File

@@ -0,0 +1,33 @@
(function(){
var coffee, prompt, quit, readline, run;
// A CoffeeScript port/version of the Node.js REPL.
// Required modules.
coffee = require('./coffee-script');
process.mixin(require('sys'));
// Shortcut variables.
prompt = 'coffee> ';
quit = function quit() {
return process.stdio.close();
};
// The main REPL function. Called everytime a line of code is entered.
readline = function readline(code) {
return coffee.compile(code, run);
};
// Attempt to evaluate the command. If there's an exception, print it.
run = function run(js) {
var val;
try {
val = eval(js);
if (val !== undefined) {
p(val);
}
} catch (err) {
puts(err.stack || err.toString());
}
return print(prompt);
};
// Start up the REPL.
process.stdio.open();
process.stdio.addListener('data', readline);
print(prompt);
})();

View File

@@ -0,0 +1,377 @@
(function(){
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, __a, __b, __c, __d, __e, __f, __g, __h, pair, re;
var __hasProp = Object.prototype.hasOwnProperty;
// 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']];
// Tokens that signal the start of a balanced pair.
EXPRESSION_START = (function() {
__a = []; __b = BALANCED_PAIRS;
for (__c = 0; __c < __b.length; __c++) {
pair = __b[__c];
__a.push(pair[0]);
}
return __a;
}).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]);
}
return __d;
}).call(this);
// Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL);
// Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END'];
IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'OUTDENT'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'ARGUMENTS', '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];
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_parentheses();
this.add_implicit_indentation();
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;
}
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[0] === 'TERMINATOR' && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens[i - 1] = after;
return 1;
} else if (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) {
if (token[0] === 'CALL_START') {
parens.push(0);
} else if (token[0] === 'INDEX_START') {
brackets.push(0);
} else if (token[0] === '(') {
parens[parens.length - 1] += 1;
} else if (token[0] === '[') {
brackets[brackets.length - 1] += 1;
} else if (token[0] === ')') {
if (parens[parens.length - 1] === 0) {
parens.pop();
token[0] = 'CALL_END';
} else {
parens[parens.length - 1] -= 1;
}
} else if (token[0] === ']') {
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, tmp;
if (token[0] === 'INDENT') {
stack.push(0);
}
if (token[0] === 'OUTDENT') {
last = stack.pop();
stack[stack.length - 1] += last;
}
if (stack[stack.length - 1] > 0 && (IMPLICIT_END.indexOf(token[0]) >= 0 || (typeof !post !== "undefined" && !post !== null))) {
idx = token[0] === 'OUTDENT' ? i + 1 : i;
__k = 0; __l = stack[stack.length - 1];
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', ')']);
}
size = stack[stack.length - 1] + 1;
stack[stack.length - 1] = 0;
return size;
}
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(token[0]) >= 0)) {
return 1;
}
this.tokens.splice(i, 0, ['CALL_START', '(']);
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]);
idx = i + 1;
parens = 0;
while (true) {
idx += 1;
tok = this.tokens[idx];
if ((!tok || SINGLE_CLOSERS.indexOf(tok[0]) >= 0 || (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]);
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) {
value = __j[key];
if (__hasProp.call(__j, key)) {
if (value > 0) {
__i.push(key);
}
}
}
return __i;
}).call(this);
if (unclosed.length) {
throw "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) {
val = __i[key];
if (__hasProp.call(__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;
} 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));
};
})();

View File

@@ -22,9 +22,9 @@ module CoffeeScript
IMPLICIT_FUNC = [:IDENTIFIER, :SUPER, ')', :CALL_END, ']', :INDEX_END]
IMPLICIT_END = [:IF, :UNLESS, :FOR, :WHILE, "\n", :OUTDENT]
IMPLICIT_CALL = [:IDENTIFIER, :NUMBER, :STRING, :JS, :REGEX, :NEW, :PARAM_START,
:TRY, :DELETE, :TYPEOF, :SWITCH, :ARGUMENTS,
:TRY, :DELETE, :TYPEOF, :SWITCH,
:TRUE, :FALSE, :YES, :NO, :ON, :OFF, '!', '!!', :NOT,
'->', '=>', '[', '(', '{']
'@', '->', '=>', '[', '(', '{']
# The inverse mappings of token pairs we're trying to fix up.
INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair|
@@ -151,6 +151,30 @@ module CoffeeScript
end
end
# 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.
def add_implicit_parentheses
stack = [0]
scan_tokens do |prev, token, post, i|
stack.push(0) if token[0] == :INDENT
if token[0] == :OUTDENT
last = stack.pop
stack[-1] += last
end
if stack.last > 0 && (IMPLICIT_END.include?(token[0]) || post.nil?)
idx = token[0] == :OUTDENT ? i + 1 : i
stack.last.times { @tokens.insert(idx, [:CALL_END, Value.new(')', token[1].line)]) }
size, stack[-1] = stack[-1] + 1, 0
next size
end
next 1 unless IMPLICIT_FUNC.include?(prev[0]) && IMPLICIT_CALL.include?(token[0])
@tokens.insert(i, [:CALL_START, Value.new('(', token[1].line)])
stack[-1] += 1
next 2
end
end
# 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.
@@ -183,30 +207,6 @@ module CoffeeScript
end
end
# 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.
def add_implicit_parentheses
stack = [0]
scan_tokens do |prev, token, post, i|
stack.push(0) if token[0] == :INDENT
if token[0] == :OUTDENT
last = stack.pop
stack[-1] += last
end
if stack.last > 0 && (IMPLICIT_END.include?(token[0]) || post.nil?)
idx = token[0] == :OUTDENT ? i + 1 : i
stack.last.times { @tokens.insert(idx, [:CALL_END, Value.new(')', token[1].line)]) }
size, stack[-1] = stack[-1] + 1, 0
next size
end
next 1 unless IMPLICIT_FUNC.include?(prev[0]) && IMPLICIT_CALL.include?(token[0])
@tokens.insert(i, [:CALL_START, Value.new('(', token[1].line)])
stack[-1] += 1
next 2
end
end
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
def ensure_balance(*pairs)

View File

@@ -0,0 +1,11 @@
(function(){
var coffee, paths;
// Quickie script to compile and run all the files given as arguments.
process.mixin(require('sys'));
coffee = require('./coffee-script');
paths = process.ARGV;
paths = paths.slice(2, paths.length);
paths.length ? coffee.compile_files(paths, function(js) {
return eval(js);
}) : require('./repl');
})();

View File

@@ -0,0 +1,73 @@
(function(){
var dup;
var __hasProp = Object.prototype.hasOwnProperty;
dup = function dup(input) {
var __a, __b, __c, key, output, val;
output = null;
if (input instanceof Array) {
output = [];
__a = input;
for (__b = 0; __b < __a.length; __b++) {
val = __a[__b];
output.push(val);
}
} else {
output = {
};
__c = input;
for (key in __c) {
val = __c[key];
if (__hasProp.call(__c, key)) {
output.key = val;
}
}
output;
}
return output;
};
// 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.
exports.Scope = function Scope(parent, expressions, func) {
var __a;
// 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.
this.parent = parent;
this.expressions = expressions;
this.function = func;
this.variables = {
};
__a = this.temp_variable = this.parent ? dup(this.parent.temp_variable) : '__a';
return Scope === this.constructor ? this : __a;
};
// Look up a variable in lexical scope, or declare it if not found.
exports.Scope.prototype.find = function find(name, rem) {
var found, remote;
remote = (typeof rem !== "undefined" && rem !== null) ? rem : false;
found = this.check(name);
if (found || remote) {
return found;
}
this.variables[name] = 'var';
return found;
};
// Define a local variable as originating from a parameter in current scope
// -- no var required.
exports.Scope.prototype.parameter = function parameter(name) {
return this.variables[name] = 'param';
};
// Just check to see if a variable has already been declared.
exports.Scope.prototype.check = function check(name) {
if ((typeof this.variables[name] !== "undefined" && this.variables[name] !== null)) {
return true;
}
// TODO: what does that ruby !! mean..? need to follow up
// .. this next line is prolly wrong ..
return !!(this.parent && this.parent.check(name));
};
// You can reset a found variable on the immediate scope.
exports.Scope.prototype.reset = function reset(name) {
return this.variables[name] = undefined;
};
})();

View File

@@ -1,9 +1,8 @@
{
"name": "coffee-script",
"lib": "lib/coffee_script/narwhal/lib",
"preload": ["coffee-script/loader"],
"lib": "lib/coffee_script/narwhal",
"description": "Unfancy JavaScript",
"keywords": ["javascript", "language"],
"author": "Jeremy Ashkenas",
"version": "0.3.1"
"version": "0.3.2"
}

45
src/coffee-script.coffee Normal file
View File

@@ -0,0 +1,45 @@
# Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
path: require('path')
# The path to the CoffeeScript executable.
compiler: path.normalize(path.dirname(__filename) + '/../../bin/coffee')
# Compile a string over stdin, with global variables, for the REPL.
exports.compile: (code, callback) ->
js: ''
coffee: process.createChildProcess compiler, ['--eval', '--no-wrap', '--globals']
coffee.addListener 'output', (results) ->
js += results if results?
coffee.addListener 'exit', ->
callback(js)
coffee.write(code)
coffee.close()
# Compile a list of CoffeeScript files on disk.
exports.compile_files: (paths, callback) ->
js: ''
coffee: process.createChildProcess compiler, ['--print'].concat(paths)
coffee.addListener 'output', (results) ->
js += results if results?
# NB: we have to add a mutex to make sure it doesn't get called twice.
exit_ran: false
coffee.addListener 'exit', ->
return if exit_ran
exit_ran: true
callback(js)
coffee.addListener 'error', (message) ->
return unless message
puts message
throw new Error "CoffeeScript compile error"

280
src/lexer.coffee Normal file
View File

@@ -0,0 +1,280 @@
Rewriter: require('./rewriter').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 ============================================================
# The list of keywords passed verbatim to the parser.
KEYWORDS: [
"if", "else", "then", "unless",
"true", "false", "yes", "no", "on", "off",
"and", "or", "is", "isnt", "not",
"new", "return", "arguments",
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends"
]
# 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]*?)([^\\]|\\\\)')/
HEREDOC : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/
JS : /^(``|`([\s\S]*?)([^\\]|\\\\)`)/
OPERATOR : /^([+\*&|\/\-%=<>:!?]+)/
WHITESPACE : /^([ \t]+)/
COMMENT : /^(((\n?[ \t]*)?#.*$)+)/
CODE : /^((-|=)>)/
REGEX : /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
LAST_DENTS : /\n([ \t]*)/g
LAST_DENT : /\n([ \t]*)/
ASSIGNMENT : /^(:|=)$/
# Token cleaning regexes.
JS_CLEANER : /(^`|`$)/g
MULTILINER : /\n/g
STRING_NEWLINES : /\n[ \t]*/g
COMMENT_CLEANER : /(^[ \t]*#|\n[ \t]*$)/mg
NO_NEWLINE : /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
HEREDOC_INDENT : /^[ \t]+/g
# 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.
CALLABLE: ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING']
# Scan by attempting to match tokens one character at a time. Slow and steady.
lex::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]
this.spaced : null # The last token that has a space following it.
while this.i < this.code.length
this.chunk: this.code.slice(this.i)
this.extract_next_token()
this.close_indentation()
(new Rewriter()).rewrite this.tokens
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed.
lex::extract_next_token: ->
return if this.identifier_token()
return if this.number_token()
return if this.heredoc_token()
return if this.string_token()
return if this.js_token()
return if this.regex_token()
return if this.indent_token()
return if this.comment_token()
return if this.whitespace_token()
return this.literal_token()
# Tokenizers ==========================================================
# Matches identifying literals: variables, keywords, method names, etc.
lex::identifier_token: ->
return false unless id: this.match IDENTIFIER, 1
# Keywords are special identifiers tagged with their own name,
# 'if' will result in an ['IF', "if"] token.
tag: if KEYWORDS.indexOf(id) >= 0 then id.toUpperCase() else 'IDENTIFIER'
tag: 'LEADING_WHEN' if tag is 'WHEN' and (this.tag() is 'OUTDENT' or this.tag() is 'INDENT')
this.tag(-1, 'PROTOTYPE_ACCESS') if tag is 'IDENTIFIER' and this.value() is '::'
if tag is 'IDENTIFIER' and this.value() is '.' and !(this.value(-2) is '.')
if this.tag(-2) is '?'
this.tag(-1, 'SOAK_ACCESS')
this.tokens.splice(-2, 1)
else
this.tag(-1, 'PROPERTY_ACCESS')
this.token(tag, id)
this.i += id.length
true
# Matches numbers, including decimals, hex, and exponential notation.
lex::number_token: ->
return false unless number: this.match NUMBER, 1
this.token 'NUMBER', number
this.i += number.length
true
# Matches strings, including multi-line strings.
lex::string_token: ->
return false unless string: this.match STRING, 1
escaped: string.replace STRING_NEWLINES, " \\\n"
this.token 'STRING', escaped
this.line += this.count string, "\n"
this.i += string.length
true
# Matches heredocs, adjusting indentation to the correct level.
lex::heredoc_token: ->
return false unless match = this.chunk.match(HEREDOC)
doc: match[2] or match[4]
indent: doc.match(HEREDOC_INDENT).sort()[0]
doc: doc.replace(new RegExp("^" + indent, 'g'), '')
.replace(MULTILINER, "\\n")
.replace('"', '\\"')
this.token 'STRING', '"' + doc + '"'
this.line += this.count match[1], "\n"
this.i += match[1].length
true
# Matches interpolated JavaScript.
lex::js_token: ->
return false unless script: this.match JS, 1
this.token 'JS', script.replace(JS_CLEANER, '')
this.i += script.length
true
# Matches regular expression literals.
lex::regex_token: ->
return false unless regex: this.match REGEX, 1
return false if NOT_REGEX.indexOf(this.tag()) >= 0
this.token 'REGEX', regex
this.i += regex.length
true
# Matches and conumes comments.
lex::comment_token: ->
return false unless comment: this.match COMMENT, 1
this.line += comment.match(MULTILINER).length
this.token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
this.token 'TERMINATOR', "\n"
this.i += comment.length
true
# Record tokens for indentation differing from the previous line.
lex::indent_token: ->
return false unless indent: this.match MULTI_DENT, 1
this.line += indent.match(MULTILINER).length
this.i += indent.length
next_character: this.chunk.match(MULTI_DENT)[4]
no_newlines: next_character is '.' or (this.value().match(NO_NEWLINE) and this.tokens[this.tokens.length - 2][0] isnt '.' and not this.value().match(CODE))
return this.suppress_newlines(indent) if no_newlines
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
return this.newline_token(indent) if size is this.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
true
# 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 this.indents.length
last_indent: this.indents.pop()
this.token 'OUTDENT', last_indent
move_out -= last_indent
this.token 'TERMINATOR', "\n"
true
# Matches and consumes non-meaningful whitespace.
lex::whitespace_token: ->
return false unless space: this.match WHITESPACE, 1
this.spaced: this.value()
this.i += space.length
true
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
lex::newline_token: (newlines) ->
this.token 'TERMINATOR', "\n" unless this.value() is "\n"
true
# Tokens to explicitly escape newlines are removed once their job is done.
lex::suppress_newlines: (newlines) ->
this.tokens.pop() if this.value() is "\\"
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::literal_token: ->
match: this.chunk.match(OPERATOR)
value: match and match[1]
this.tag_parameters() if value and value.match(CODE)
value ||= this.chunk.substr(0, 1)
tag: if value.match(ASSIGNMENT) then 'ASSIGN' else value
tag: 'TERMINATOR' if value == ';'
if this.value() isnt this.spaced and CALLABLE.indexOf(this.tag()) >= 0
tag: 'CALL_START' if value is '('
tag: 'INDEX_START' if value is '['
this.token tag, value
this.i += value.length
true
# Helpers =============================================================
# Add a token to the results, taking note of the line number.
lex::token: (tag, value) ->
this.tokens.push([tag, value])
# this.tokens.push([tag, Value.new(value, @line)])
# Look at a tag in the current token stream.
lex::tag: (index, tag) ->
return unless tok: this.tokens[this.tokens.length - (index || 1)]
return tok[0]: tag if tag?
tok[0]
# Look at a value in the current token stream.
lex::value: (index, val) ->
return unless tok: this.tokens[this.tokens.length - (index || 1)]
return tok[1]: val if val?
tok[1]
# Count the occurences of a character in a string.
lex::count: (string, letter) ->
num: 0
pos: string.indexOf(letter)
while pos isnt -1
count += 1
pos: string.indexOf(letter, pos + 1)
count
# Attempt to match a string against the current chunk, returning the indexed
# match.
lex::match: (regex, index) ->
return false unless m: this.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 this.tag() isnt ')'
i: 0
while true
i += 1
tok: this.tokens[this.tokens.length - 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: ->
this.outdent_token(this.indent)

View File

@@ -1,62 +1,77 @@
# This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee
# Executes the `coffee` Ruby program to convert from CoffeeScript
# to Javascript. Eventually this will hopefully happen entirely within JS.
# The Narwhal-compatibility wrapper for CoffeeScript.
# Require external dependencies.
OS: require('os')
File: require('file')
Readline: require('readline')
OS: require 'os'
File: require 'file'
Readline: require 'readline'
# The path to the CoffeeScript Compiler.
coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee')
coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().join('bin', 'coffee')
# Our general-purpose error handler.
checkForErrors: (coffeeProcess) ->
return true if coffeeProcess.wait() is 0
system.stderr.print(coffeeProcess.stderr.read())
throw new Error("CoffeeScript compile error")
system.stderr.print coffeeProcess.stderr.read()
throw new Error "CoffeeScript compile error"
# Alias print to "puts", for Node.js compatibility:
puts: print
# Run a simple REPL, round-tripping to the CoffeeScript compiler for every
# command.
exports.run: (args) ->
if args.length
for path, i in args
exports.evalCS(File.read(path))
exports.evalCS File.read path
delete args[i]
return true
while true
try
system.stdout.write('coffee> ').flush()
result: exports.evalCS(Readline.readline(), ['--globals'])
print(result) if result isnt undefined
result: exports.evalCS Readline.readline(), ['--globals']
print result if result isnt undefined
catch e
print(e)
print e
# Compile a given CoffeeScript file into JavaScript.
exports.compileFile: (path) ->
coffee: OS.popen([coffeePath, "--print", "--no-wrap", path])
checkForErrors(coffee)
coffee: OS.popen [coffeePath, "--print", "--no-wrap", path]
checkForErrors coffee
coffee.stdout.read()
# Compile a string of CoffeeScript into JavaScript.
exports.compile: (source, flags) ->
coffee: OS.popen([coffeePath, "--eval", "--no-wrap"].concat(flags or []))
coffee: OS.popen [coffeePath, "--eval", "--no-wrap"].concat flags or []
coffee.stdin.write(source).flush().close()
checkForErrors(coffee)
checkForErrors coffee
coffee.stdout.read()
# Evaluating a string of CoffeeScript first compiles it externally.
exports.evalCS: (source, flags) ->
eval(exports.compile(source, flags))
eval exports.compile source, flags
# Make a factory for the CoffeeScript environment.
exports.makeNarwhalFactory: (path) ->
code: exports.compileFile(path)
code: exports.compileFile path
factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}"
if system.engine is "rhino"
Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null)
else
# eval requires parentheses, but parentheses break compileFunction.
eval("(" + factoryText + ")")
eval "(" + factoryText + ")"
# The Narwhal loader for '.coffee' files.
factories: {}
loader: {}
# Reload the coffee-script environment from source.
loader.reload: (topId, path) ->
factories[topId]: ->
exports.makeNarwhalFactory path
# Ensure that the coffee-script environment is loaded.
loader.load: (topId, path) ->
factories[topId] ||= this.reload topId, path
require.loader.loaders.unshift [".coffee", loader]

214
src/nodes.coffee Normal file
View File

@@ -0,0 +1,214 @@
# 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"
# method, which wraps "compile_node" in some extra smarts, to know when the
# 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.
exports.Node : -> @values: arguments; @name: this.constructor.name
exports.Expressions : -> @name: this.constructor.name; @values: arguments
exports.LiteralNode : -> @name: this.constructor.name; @values: arguments
exports.ReturnNode : -> @name: this.constructor.name; @values: arguments
exports.CommentNode : -> @name: this.constructor.name; @values: arguments
exports.CallNode : -> @name: this.constructor.name; @values: arguments
exports.ExtendsNode : -> @name: this.constructor.name; @values: arguments
exports.ValueNode : -> @name: this.constructor.name; @values: arguments
exports.AccessorNode : -> @name: this.constructor.name; @values: arguments
exports.IndexNode : -> @name: this.constructor.name; @values: arguments
exports.RangeNode : -> @name: this.constructor.name; @values: arguments
exports.SliceNode : -> @name: this.constructor.name; @values: arguments
exports.AssignNode : -> @name: this.constructor.name; @values: arguments
exports.OpNode : -> @name: this.constructor.name; @values: arguments
exports.CodeNode : -> @name: this.constructor.name; @values: arguments
exports.SplatNode : -> @name: this.constructor.name; @values: arguments
exports.ObjectNode : -> @name: this.constructor.name; @values: arguments
exports.ArrayNode : -> @name: this.constructor.name; @values: arguments
exports.PushNode : -> @name: this.constructor.name; @values: arguments
exports.ClosureNode : -> @name: this.constructor.name; @values: arguments
exports.WhileNode : -> @name: this.constructor.name; @values: arguments
exports.ForNode : -> @name: this.constructor.name; @values: arguments
exports.TryNode : -> @name: this.constructor.name; @values: arguments
exports.ThrowNode : -> @name: this.constructor.name; @values: arguments
exports.ExistenceNode : -> @name: this.constructor.name; @values: arguments
exports.ParentheticalNode : -> @name: this.constructor.name; @values: arguments
exports.IfNode : -> @name: this.constructor.name; @values: arguments
exports.Expressions.wrap : (values) -> @values: values
# Some helper functions
# TODO -- shallow (1 deep) flatten..
# need recursive version..
flatten: (aggList, newList) ->
for item in newList
aggList.push(item)
aggList
compact: (input) ->
compected: []
for item in input
if item?
compacted.push(item)
dup: (input) ->
output: null
if input instanceof Array
output: []
for val in input
output.push(val)
else
output: {}
for key, val of input
output.key: val
output
output
exports.Node::TAB: ' '
# Tag this node as a statement, meaning that it can't be used directly as
# the result of an expression.
exports.Node::mark_as_statement: ->
this.is_statement: -> true
# Tag this node as a statement that cannot be transformed into an expression.
# (break, continue, etc.) It doesn't make sense to try to transform it.
exports.Node::mark_as_statement_only: ->
this.mark_as_statement()
this.is_statement_only: -> true
# This node needs to know if it's being compiled as a top-level statement,
# in order to compile without special expression conversion.
exports.Node::mark_as_top_sensitive: ->
this.is_top_sensitive: -> true
# Provide a quick implementation of a children method.
exports.Node::children: (attributes) ->
# TODO -- are these optimal impls of flatten and compact
# .. do better ones exist in a stdlib?
agg: []
for item in attributes
agg: flatten agg, item
compacted: compact agg
this.children: ->
compacted
exports.Node::write: (code) ->
# hm..
# TODO -- should print to STDOUT in "VERBOSE" how to
# go about this.. ? jsonify 'this'?
# use node's puts ??
code
# 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.
exports.Node::compile: (o) ->
# TODO -- need JS dup/clone
opts: if not o? then {} else o
this.options: opts
this.indent: opts.indent
top: this.options.top
if not this.is_top_sentitive()
this.options.top: undefined
closure: this.is_statement() and not this.is_statement_only() and not top and typeof(this) == "CommentNode"
closure &&= not this.do_i_contain (n) -> n.is_statement_only()
if closure then this.compile_closure(this.options) else compile_node(this.options)
# Statements converted into expressions share scope with their parent
# closure, to preserve JavaScript-style lexical scope.
exports.Node::compile_closure: (o) ->
opts: if not o? then {} else o
this.indent: opts.indent
opts.shared_scope: o.scope
exports.ClosureNode.wrap(this).compile(opts)
# Quick short method for the current indentation level, plus tabbing in.
exports.Node::idt: (tLvl) ->
tabs: if tLvl? then tLvl else 0
tabAmt: ''
for x in [0...tabs]
tabAmt: tabAmt + this.TAB
this.indent + tabAmt
#Does this node, or any of it's children, contain a node of a certain kind?
exports.Node::do_i_contain: (block) ->
for node in this.children
return true if block(node)
return true if node instanceof exports.Node and node.do_i_contain(block)
false
# Default implementations of the common node methods.
exports.Node::unwrap: -> this
exports.Node::children: []
exports.Node::is_a_statement: -> false
exports.Node::is_a_statement_only: -> false
exports.Node::is_top_sensitive: -> false
# A collection of nodes, each one representing an expression.
# exports.Expressions: (nodes) ->
# this.mark_as_statement()
# this.expressions: []
# this.children([this.expressions])
# for n in nodes
# this.expressions: flatten this.expressions, n
# exports.Expressions extends exports.Node
exports.Expressions::TRAILING_WHITESPACE: /\s+$/
# Wrap up a node as an Expressions, unless it already is.
exports.Expressions::wrap: (nodes) ->
return nodes[0] if nodes.length == 1 and nodes[0] instanceof exports.Expressions
new Expressions(nodes)
# Tack an expression on to the end of this expression list.
exports.Expressions::push: (node) ->
this.expressions.push(node)
this
# Tack an expression on to the beginning of this expression list.
exports.Expressions::unshift: (node) ->
this.expressions.unshift(node)
this
# If this Expressions consists of a single node, pull it back out.
exports.Expressions::unwrap: ->
if this.expressions.length == 1 then this.expressions[0] else this
# Is this an empty block of code?
exports.Expressions::is_empty: ->
this.expressions.length == 0
# Is the node last in this block of expressions.
exports.Expressions::is_last: (node) ->
arr_length: this.expressions.length
this.last_index ||= if this.expressions[arr_length - 1] instanceof exports.CommentNode then -2 else -1
node == this.expressions[arr_length - this.last_index]
exports.Expressions::compile: (o) ->
opts: if o? then o else {}
if opts.scope then super(dup(opts)) else this.compile_root(o)
# Compile each expression in the Expressions body.
exports.Expressions::compile_node: (options) ->
opts: if options? then options else {}
compiled: []
for e in this.expressions
compiled.push(this.compile_expression(e, dup(options)))
code: ''
for line in compiled
code: code + line + '\n'
# If this is the top-level Expressions, wrap everything in a safety closure.
exports.Expressions::compile_root: (o) ->
opts: if o? then o else {}
indent: if opts.no_wrap then '' else this.TAB
this.indent: indent
opts.indent: indent
opts.scope: new Scope(null, this, null)
code: if opts.globals then compile_node(opts) else compile_with_declarations(opts)
code.replace(this.TRAILING_WHITESPACE, '')
this.write(if opts.no_wrap then code else "(function(){\n"+code+"\n})();")

468
src/parser.coffee Normal file
View File

@@ -0,0 +1,468 @@
Parser: require('jison').Parser
process.mixin require './nodes'
# DSL ===================================================================
# Detect functions: [
unwrap: /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
# Quickie DSL for Jison access.
o: (pattern_string, func) ->
if func
func: if match: (func + "").match(unwrap) then match[1] else '(' + func + '())'
[pattern_string, '$$ = ' + func + ';']
else
[pattern_string, '$$ = $1;']
# Precedence ===========================================================
operators: [
["left", '?']
["right", 'NOT', '!', '!!', '~', '++', '--']
["left", '*', '/', '%']
["left", '+', '-']
["left", '<<', '>>', '>>>']
["left", '&', '|', '^']
["left", '<=', '<', '>', '>=']
["right", '==', '!=', 'IS', 'ISNT']
["left", '&&', '||', 'AND', 'OR']
["right", '-=', '+=', '/=', '*=', '%=']
["right", 'DELETE', 'INSTANCEOF', 'TYPEOF']
["left", '.']
["right", 'INDENT']
["left", 'OUTDENT']
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY']
["right", 'THROW', 'FOR', 'NEW', 'SUPER']
["left", 'EXTENDS']
["left", '||=', '&&=', '?=']
["right", 'ASSIGN', 'RETURN']
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']
]
# Grammar ==============================================================
grammar: {
# All parsing will end in this rule, being the trunk of the AST.
Root: [
o "", -> new Expressions()
o "TERMINATOR", -> new Expressions()
o "Expressions"
o "Block TERMINATOR"
]
# Any list of expressions or method body, seperated by line breaks or semis.
Expressions: [
o "Expression", -> Expressions.wrap([$1])
o "Expressions TERMINATOR Expression", -> $1.push($3)
o "Expressions TERMINATOR"
]
# 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"
]
# 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", -> $2
o "INDENT OUTDENT", -> new Expressions()
]
# All hard-coded values. These can be printed straight to JavaScript.
Literal: [
o "NUMBER", -> new LiteralNode(yytext)
o "STRING", -> new LiteralNode(yytext)
o "JS", -> new LiteralNode(yytext)
o "REGEX", -> new LiteralNode(yytext)
o "BREAK", -> new LiteralNode(yytext)
o "CONTINUE", -> new LiteralNode(yytext)
o "ARGUMENTS", -> new LiteralNode(yytext)
o "TRUE", -> new LiteralNode(true)
o "FALSE", -> new LiteralNode(false)
o "YES", -> new LiteralNode(true)
o "NO", -> new LiteralNode(false)
o "ON", -> new LiteralNode(true)
o "OFF", -> new LiteralNode(false)
]
# Assignment to a variable (or index).
Assign: [
o "Value ASSIGN Expression", -> new AssignNode($1, $3)
]
# Assignment within an object literal (can be quoted).
AssignObj: [
o "IDENTIFIER ASSIGN Expression", -> new AssignNode(new ValueNode(yytext), $3, 'object')
o "STRING ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object')
o "Comment"
]
# A return statement.
Return: [
o "RETURN Expression", -> new ReturnNode($2)
o "RETURN", -> new ReturnNode(new ValueNode(new LiteralNode('null')))
]
# A comment.
Comment: [
o "COMMENT", -> new CommentNode(yytext)
]
#
# # Arithmetic and logical operators
# # For Ruby's Operator precedence, see: [
# # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
# Operation: [
# o "! Expression", -> new OpNode($1, $2)
# o "!! Expression", -> new OpNode($1, $2)
# o "- Expression", -> new OpNode($1, $2)
# o "+ Expression", -> new OpNode($1, $2)
# o "NOT Expression", -> new OpNode($1, $2)
# o "~ Expression", -> new OpNode($1, $2)
# o "-- Expression", -> new OpNode($1, $2)
# o "++ Expression", -> new OpNode($1, $2)
# o "DELETE Expression", -> new OpNode($1, $2)
# o "TYPEOF Expression", -> new OpNode($1, $2)
# o "Expression --", -> new OpNode($2, $1, null, true)
# o "Expression ++", -> new OpNode($2, $1, null, true)
#
# o "Expression * Expression", -> new OpNode($2, $1, $3)
# o "Expression / Expression", -> new OpNode($2, $1, $3)
# o "Expression % Expression", -> new OpNode($2, $1, $3)
#
# o "Expression + Expression", -> new OpNode($2, $1, $3)
# o "Expression - Expression", -> new OpNode($2, $1, $3)
#
# o "Expression << Expression", -> new OpNode($2, $1, $3)
# o "Expression >> Expression", -> new OpNode($2, $1, $3)
# o "Expression >>> Expression", -> new OpNode($2, $1, $3)
#
# o "Expression & Expression", -> new OpNode($2, $1, $3)
# o "Expression | Expression", -> new OpNode($2, $1, $3)
# o "Expression ^ Expression", -> new OpNode($2, $1, $3)
#
# o "Expression <= Expression", -> new OpNode($2, $1, $3)
# o "Expression < Expression", -> new OpNode($2, $1, $3)
# o "Expression > Expression", -> new OpNode($2, $1, $3)
# o "Expression >= Expression", -> new OpNode($2, $1, $3)
#
# o "Expression == Expression", -> new OpNode($2, $1, $3)
# o "Expression != Expression", -> new OpNode($2, $1, $3)
# o "Expression IS Expression", -> new OpNode($2, $1, $3)
# o "Expression ISNT Expression", -> new OpNode($2, $1, $3)
#
# o "Expression && Expression", -> new OpNode($2, $1, $3)
# o "Expression || Expression", -> new OpNode($2, $1, $3)
# o "Expression AND Expression", -> new OpNode($2, $1, $3)
# o "Expression OR Expression", -> new OpNode($2, $1, $3)
# o "Expression ? Expression", -> new OpNode($2, $1, $3)
#
# o "Expression -= Expression", -> new OpNode($2, $1, $3)
# o "Expression += Expression", -> new OpNode($2, $1, $3)
# o "Expression /= Expression", -> new OpNode($2, $1, $3)
# o "Expression *= Expression", -> new OpNode($2, $1, $3)
# o "Expression %= Expression", -> new OpNode($2, $1, $3)
# o "Expression ||= Expression", -> new OpNode($2, $1, $3)
# o "Expression &&= Expression", -> new OpNode($2, $1, $3)
# o "Expression ?= Expression", -> new OpNode($2, $1, $3)
#
# o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3)
# o "Expression IN Expression", -> new OpNode($2, $1, $3)
# ]
# The existence operator.
Existence: [
o "Expression ?", -> new ExistenceNode($1)
]
# Function definition.
Code: [
o "PARAM_START ParamList PARAM_END FuncGlyph Block", -> new CodeNode($2, $5, $4)
o "FuncGlyph Block", -> new CodeNode([], $2, $1)
]
# The symbols to signify functions, and bound functions.
FuncGlyph: [
o "->", -> 'func'
o "=>", -> 'boundfunc'
]
# The parameters to a function definition.
ParamList: [
o "Param", -> [$1]
o "ParamList , Param", -> $1.push($3)
]
# A Parameter (or ParamSplat) in a function definition.
Param: [
o "PARAM", -> yytext
o "PARAM . . .", -> new SplatNode(yytext)
]
# A regular splat.
Splat: [
o "Expression . . .", -> new SplatNode($1)
]
# Expressions that can be treated as values.
Value: [
o "IDENTIFIER", -> new ValueNode(yytext)
o "Literal", -> new ValueNode($1)
o "Array", -> new ValueNode($1)
o "Object", -> new ValueNode($1)
o "Parenthetical", -> new ValueNode($1)
o "Range", -> new ValueNode($1)
# o "Value Accessor", -> $1.push($2)
o "Invocation Accessor", -> new ValueNode($1, [$2])
]
# # Accessing into an object or array, through dot or index notation.
# Accessor: [
# o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode($2)
# o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode($2, 'prototype')
# o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode($2, 'soak')
# o "Index"
# o "Slice", -> new SliceNode($1)
# ]
#
# # Indexing into an object or array.
# Index: [
# o "INDEX_START Expression INDEX_END", -> new IndexNode($2)
# ]
#
# # An object literal.
# Object: [
# o "{ AssignList }", -> new ObjectNode($2)
# ]
#
# # Assignment within an object literal (comma or newline separated).
# AssignList: [
# o "", -> []
# o "AssignObj", -> [$1]
# o "AssignList , AssignObj", -> $1.push $3
# o "AssignList TERMINATOR AssignObj", -> $1.push $3
# o "AssignList , TERMINATOR AssignObj", -> $1.push $4
# o "INDENT AssignList OUTDENT", -> $2
# ]
#
# # All flavors of function call (instantiation, super, and regular).
# Call: [
# o "Invocation", -> $1
# o "NEW Invocation", -> $2.new_instance()
# o "Super", -> $1
# ]
#
# # Extending an object's prototype.
# Extends: [
# o "Value EXTENDS Value", -> new ExtendsNode($1, $3)
# ]
#
# # A generic function invocation.
# Invocation: [
# o "Value Arguments", -> new CallNode($1, $2)
# o "Invocation Arguments", -> new CallNode($1, $2)
# ]
#
# # The list of arguments to a function invocation.
# Arguments: [
# o "CALL_START ArgList CALL_END", -> $2
# ]
#
# # Calling super.
# Super: [
# o "SUPER CALL_START ArgList CALL_END", -> new CallNode('super', $3)
# ]
#
# # The range literal.
# Range: [
# o "[ Expression . . Expression ]", -> new RangeNode($2, $5)
# o "[ Expression . . . Expression ]", -> new RangeNode($2, $6, true)
# ]
#
# # The slice literal.
# Slice: [
# o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode($2, $5)
# o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode($2, $6, true)
# ]
#
# # The array literal.
# Array: [
# o "[ ArgList ]", -> new ArrayNode($2)
# ]
#
# # A list of arguments to a method call, or as the contents of an array.
# ArgList: [
# o "", -> []
# o "Expression", -> val
# o "INDENT Expression", -> [$2]
# o "ArgList , Expression", -> $1.push $3
# o "ArgList TERMINATOR Expression", -> $1.push $3
# o "ArgList , TERMINATOR Expression", -> $1.push $4
# o "ArgList , INDENT Expression", -> $1.push $4
# o "ArgList OUTDENT", -> $1
# ]
#
# # Just simple, comma-separated, required arguments (no fancy syntax).
# SimpleArgs: [
# o "Expression", -> $1
# o "SimpleArgs , Expression", ->
# ([$1].push($3)).reduce (a, b) -> a.concat(b)
# ]
#
# # Try/catch/finally exception handling blocks.
# Try: [
# o "TRY Block Catch", -> new TryNode($2, $3[0], $3[1])
# o "TRY Block FINALLY Block", -> new TryNode($2, nil, nil, $4)
# o "TRY Block Catch FINALLY Block", -> new TryNode($2, $3[0], $3[1], $5)
# ]
#
# # A catch clause.
# Catch: [
# o "CATCH IDENTIFIER Block", -> [$2, $3]
# ]
#
# # Throw an exception.
# Throw: [
# o "THROW Expression", -> new ThrowNode($2)
# ]
#
# # Parenthetical expressions.
# Parenthetical: [
# o "( Expression )", -> new ParentheticalNode($2)
# ]
#
# # The while loop. (there is no do..while).
# While: [
# o "WHILE Expression Block", -> new WhileNode($2, $3)
# o "WHILE Expression", -> new WhileNode($2, nil)
# o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap($1))
# ]
#
# # Array comprehensions, including guard and current index.
# # Looks a little confusing, check nodes.rb for the arguments to ForNode.
# For: [
# o "Expression FOR ForVariables ForSource", -> new ForNode($1, $4, $3[0], $3[1])
# o "FOR ForVariables ForSource Block", -> new ForNode($4, $3, $2[0], $2[1])
# ]
#
# # An array comprehension has variables for the current element and index.
# ForVariables: [
# o "IDENTIFIER", -> [$1]
# o "IDENTIFIER , IDENTIFIER", -> [$1, $3]
# ]
#
# # The source of the array comprehension can optionally be filtered.
# 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
# ]
#
# # 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)
# ]
#
# # The inner list of whens.
# Whens: [
# o "When", -> $1
# o "Whens When", -> $1.push $2
# ]
#
# # An individual when.
# When: [
# o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, nil, {statement: true})
# o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, nil, {statement: true})
# o "Comment TERMINATOR When", -> $3.add_comment($1)
# ]
#
# # The most basic form of "if".
# IfBlock: [
# o "IF Expression Block", -> new IfNode($2, $3)
# ]
#
# # An elsif portion of an if-else block.
# ElsIf: [
# o "ELSE IfBlock", -> $2.force_statement()
# ]
#
# # Multiple elsifs can be chained together.
# ElsIfs: [
# o "ElsIf", -> $1
# o "ElsIfs ElsIf", -> $1.add_else($2)
# ]
#
# # Terminating else bodies are strictly optional.
# ElseBody: [
# o "", -> null
# o "ELSE Block", -> $2
# ]
#
# # All the alternatives for ending an if-else block.
# IfEnd: [
# o "ElseBody", -> $1
# o "ElsIfs ElseBody", -> $1.add_else($2)
# ]
#
# # The full complement of if blocks, including postfix one-liner ifs and unlesses.
# If: [
# o "IfBlock IfEnd", -> $1.add_else($2)
# o "Expression IF Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true})
# o "Expression UNLESS Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true, invert: true})
# ]
}
# Helpers ==============================================================
# Make the Jison parser.
bnf: {}
tokens: []
for name, non_terminal of grammar
bnf[name]: for option in non_terminal
for part in option[0].split(" ")
if !grammar[part]
tokens.push(part)
if name == "Root"
option[1] = "return " + option[1]
option
tokens: tokens.join(" ")
parser: new Parser({tokens: tokens, bnf: bnf, operators: operators, startSymbol: 'Root'}, {debug: false})
# Thin wrapper around the real lexer
parser.lexer: {
lex: ->
token: this.tokens[this.pos] or [""]
this.pos += 1
this.yylineno: token[2]
this.yytext: token[1]
token[0]
setInput: (tokens) ->
this.tokens = tokens
this.pos = 0
upcomingInput: -> ""
showPosition: -> this.pos
}
exports.Parser: ->
exports.Parser::parse: (tokens) -> parser.parse(tokens)

26
src/repl.coffee Normal file
View File

@@ -0,0 +1,26 @@
# A CoffeeScript port/version of the Node.js REPL.
# Required modules.
coffee: require './coffee-script'
process.mixin require 'sys'
# Shortcut variables.
prompt: 'coffee> '
quit: -> process.stdio.close()
# The main REPL function. Called everytime a line of code is entered.
readline: (code) -> coffee.compile code, run
# Attempt to evaluate the command. If there's an exception, print it.
run: (js) ->
try
val: eval(js)
p val if val isnt undefined
catch err
puts err.stack or err.toString()
print prompt
# Start up the REPL.
process.stdio.open()
process.stdio.addListener 'data', readline
print prompt

244
src/rewriter.coffee Normal file
View File

@@ -0,0 +1,244 @@
# 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']]
# Tokens that signal the start of a balanced pair.
EXPRESSION_START: pair[0] for pair in BALANCED_PAIRS
# Tokens that signal the end of a balanced pair.
EXPRESSION_TAIL: pair[1] for pair in BALANCED_PAIRS
# Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE: ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL)
# Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC: ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END']
IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'OUTDENT']
IMPLICIT_CALL: ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START',
'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'ARGUMENTS',
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT',
'->', '=>', '[', '(', '{']
# The inverse mappings of token pairs we're trying to fix up.
INVERSES: {}
for pair in BALANCED_PAIRS
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::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_parentheses()
this.add_implicit_indentation()
this.ensure_balance(BALANCED_PAIRS)
this.rewrite_closing_parens()
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::scan_tokens: (block) ->
i: 0
while true
break unless this.tokens[i]
move: block(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i)
i += move
true
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on their own line.
re::adjust_comments: ->
this.scan_tokens (prev, token, post, i) =>
return 1 unless token[0] is 'COMMENT'
before: this.tokens[i - 2]
after: this.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]
this.tokens.splice(i + 2, 1)
this.tokens.splice(i - 2, 1)
return 0
else if prev[0] is 'TERMINATOR' and after[0] is 'INDENT'
this.tokens.splice(i + 2, 1)
this.tokens[i - 1]: after
return 1
else if prev[0] isnt 'TERMINATOR' and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
this.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: ->
this.tokens.shift() if this.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: ->
this.scan_tokens (prev, token, post, i) =>
return 1 unless post and EXPRESSION_CLOSE.indexOf(post[0]) >= 0 and token[0] is 'TERMINATOR'
this.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: ->
this.scan_tokens (prev, token, post, i) =>
this.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]
this.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]
this.scan_tokens (prev, token, post, i) =>
stack.push(0) if token[0] is 'INDENT'
if token[0] is 'OUTDENT'
last: stack.pop()
stack[stack.length - 1] += last
if stack[stack.length - 1] > 0 and (IMPLICIT_END.indexOf(token[0]) >= 0 or !post?)
idx: if token[0] is 'OUTDENT' then i + 1 else i
for tmp in [0...stack[stack.length - 1]]
this.tokens.splice(idx, 0, ['CALL_END', ')'])
size: stack[stack.length - 1] + 1
stack[stack.length - 1]: 0
return size
return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(token[0]) >= 0
this.tokens.splice(i, 0, ['CALL_START', '('])
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: ->
this.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]
this.tokens.splice(i + 1, 0, ['INDENT', 2])
idx: i + 1
parens: 0
while true
idx += 1
tok: this.tokens[idx]
if (not tok or SINGLE_CLOSERS.indexOf(tok[0]) >= 0 or
(tok[0] is ')' && parens is 0)) and
not (starter is 'ELSE' and tok[0] is 'ELSE')
insertion: if this.tokens[idx - 1][0] is "," then idx - 1 else idx
this.tokens.splice(insertion, 0, ['OUTDENT', 2])
break
parens += 1 if tok[0] is '('
parens -= 1 if tok[0] is ')'
return 1 unless token[0] is 'THEN'
this.tokens.splice(i, 1)
return 0
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
re::ensure_balance: (pairs) ->
levels: {}
this.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 "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
this.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
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 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]
this.tokens.splice(i, 0, [INVERSES[mtag], val])
return 1
else
return 1

12
src/runner.coffee Normal file
View File

@@ -0,0 +1,12 @@
# Quickie script to compile and run all the files given as arguments.
process.mixin require 'sys'
coffee: require './coffee-script'
paths: process.ARGV
paths: paths[2...paths.length]
if paths.length
coffee.compile_files paths, (js) -> eval(js)
else
require './repl'

49
src/scope.coffee Normal file
View File

@@ -0,0 +1,49 @@
dup: (input) ->
output: null
if input instanceof Array
output: []
for val in input
output.push(val)
else
output: {}
for key, val of input
output.key: val
output
output
# 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.
exports.Scope: (parent, expressions, func) ->
# 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.
this.parent: parent
this.expressions: expressions
this.function: func
this.variables: {}
this.temp_variable: if this.parent then dup(this.parent.temp_variable) else '__a'
# Look up a variable in lexical scope, or declare it if not found.
exports.Scope::find: (name, rem) ->
remote: if rem? then rem else false
found: this.check(name)
return found if found || remote
this.variables[name]: 'var'
found
# Define a local variable as originating from a parameter in current scope
# -- no var required.
exports.Scope::parameter: (name) ->
this.variables[name]: 'param'
# Just check to see if a variable has already been declared.
exports.Scope::check: (name) ->
return true if this.variables[name]?
# TODO: what does that ruby !! mean..? need to follow up
# .. this next line is prolly wrong ..
not not (this.parent and this.parent.check(name))
# You can reset a found variable on the immediate scope.
exports.Scope::reset: (name) ->
this.variables[name]: undefined

View File

@@ -4,12 +4,12 @@ area: (x, y, x1, y1) ->
x: y: 10
x1: y1: 20
print area(x, y, x1, y1) is 100
puts area(x, y, x1, y1) is 100
print(area(x, y,
puts(area(x, y,
x1, y1) is 100)
print(area(
puts(area(
x
y
x1
@@ -19,7 +19,7 @@ print(area(
# Arguments are turned into arrays.
curried: ->
print area.apply(this, arguments.concat(20, 20)) is 100
puts area.apply(this, arguments.concat(20, 20)) is 100
curried 10, 10
@@ -29,4 +29,9 @@ func: ->
arguments: 25
arguments
print func(100) is 25
puts func(100) is 25
# Arguments can be accessed as a property.
this.arguments: 10
puts @arguments is 10

View File

@@ -1,15 +1,15 @@
nums: n * n for n in [1, 2, 3] when n % 2 isnt 0
results: n * 2 for n in nums
print results.join(',') is '2,18'
puts results.join(',') is '2,18'
obj: {one: 1, two: 2, three: 3}
names: prop + '!' for prop of obj
odds: prop + '!' for prop, value of obj when value % 2 isnt 0
print names.join(' ') is "one! two! three!"
print odds.join(' ') is "one! three!"
puts names.join(' ') is "one! two! three!"
puts odds.join(' ') is "one! three!"
evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
@@ -17,12 +17,12 @@ evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
num -= 2
num * -1
print evens.join(', ') is '4, 6, 8'
puts evens.join(', ') is '4, 6, 8'
# Make sure that the "in" operator still works.
print 2 in evens
puts 2 in evens
# When functions are being defined within the body of a comprehension, make
@@ -37,6 +37,12 @@ for method in methods
obj[name]: ->
"I'm " + name
print obj.one() is "I'm one"
print obj.two() is "I'm two"
print obj.three() is "I'm three"
puts obj.one() is "I'm one"
puts obj.two() is "I'm two"
puts obj.three() is "I'm three"
# Steps should work for array comprehensions.
array: [0..10]
puts num % 2 is 0 for num in array by 2

View File

@@ -7,7 +7,7 @@ catch error
result2: try nonexistent * missing catch error then true
print result is true and result2 is true
puts result is true and result2 is true
# Assign to conditional.
@@ -16,8 +16,8 @@ get_x: -> 10
if x: get_x() then 100
print x is 10
puts x is 10
x: if get_x() then 100
print x is 100
puts x is 100

View File

@@ -1,4 +1,4 @@
results: [1, 2, 3].map (x) ->
x * x
print results.join(' ') is '1 4 9'
puts results.join(' ') is '1 4 9'

View File

@@ -13,18 +13,18 @@ SecondChild::func: (string) ->
super('two/') + string
ThirdChild: ->
this.array: [1, 2, 3]
@array: [1, 2, 3]
ThirdChild extends SecondChild
ThirdChild::func: (string) ->
super('three/') + string
result: (new ThirdChild()).func 'four'
print result is 'zero/one/two/three/four'
puts result is 'zero/one/two/three/four'
TopClass: (arg) ->
this.prop: 'top-' + arg
@prop: 'top-' + arg
SuperClass: (arg) ->
super 'super-' + arg
@@ -35,4 +35,4 @@ SubClass: ->
SuperClass extends TopClass
SubClass extends SuperClass
print((new SubClass()).prop is 'top-super-sub')
puts((new SubClass()).prop is 'top-super-sub')

View File

@@ -3,7 +3,7 @@ identity_wrap: (x) ->
result: identity_wrap(identity_wrap(true))()()
print result
puts result
str: 'god'
@@ -14,7 +14,7 @@ result: str.
reverse().
reverse()
print result.join('') is 'dog'
puts result.join('') is 'dog'
result: str
.split('')
@@ -22,4 +22,4 @@ result: str
.reverse()
.reverse()
print result.join('') is 'dog'
puts result.join('') is 'dog'

View File

@@ -3,26 +3,26 @@ b: -2
[a, b]: [b, a]
print a is -2
print b is -1
puts a is -2
puts b is -1
arr: [1, 2, 3]
[a, b, c]: arr
print a is 1
print b is 2
print c is 3
puts a is 1
puts b is 2
puts c is 3
obj: {x: 10, y: 20, z: 30}
{x: a, y: b, z: c}: obj
print a is 10
print b is 20
print c is 30
puts a is 10
puts b is 20
puts c is 30
person: {
@@ -42,8 +42,8 @@ person: {
{name: a, family: {brother: {addresses: [one, {city: b}]}}}: person
print a is "Bob"
print b is "Moquasset NY, 10021"
puts a is "Bob"
puts b is "Moquasset NY, 10021"
test: {
@@ -59,4 +59,4 @@ test: {
{person: {address: [ignore, addr...]}}: test
print addr.join(', ') is "Street 101, Apt 101, City 101"
puts addr.join(', ') is "Street 101, Apt 101, City 101"

View File

@@ -26,4 +26,4 @@ func: ->
c.single: c.list[1..1][0]
print func() is '-'
puts func() is '-'

View File

@@ -1,8 +1,8 @@
print(if my_special_variable? then false else true)
puts(if my_special_variable? then false else true)
my_special_variable: false
print(if my_special_variable? then true else false)
puts(if my_special_variable? then true else false)
# Existential assignment.
@@ -12,7 +12,7 @@ a: null
a ?= 10
b ?= 10
print a is 10 and b is 10
puts a is 10 and b is 10
# The existential operator.
@@ -20,7 +20,7 @@ print a is 10 and b is 10
z: null
x: z ? "EX"
print z is null and x is "EX"
puts z is null and x is "EX"
# Only evaluate once.
@@ -30,7 +30,7 @@ get_next_node: ->
throw "up" if counter
counter++
print(if get_next_node()? then true else false)
puts(if get_next_node()? then true else false)
# Existence chains, soaking up undefined properties:
@@ -39,17 +39,19 @@ obj: {
prop: "hello"
}
print obj?.prop is "hello"
puts obj?.prop is "hello"
print obj?.prop?.non?.existent?.property is undefined
puts obj.prop?.length is 5
puts obj?.prop?.non?.existent?.property is undefined
# Soaks and caches method calls as well.
arr: ["--", "----"]
print arr.pop()?.length is 4
print arr.pop()?.length is 2
print arr.pop()?.length is undefined
print arr[0]?.length is undefined
print arr.pop()?.length?.non?.existent()?.property is undefined
puts arr.pop()?.length is 4
puts arr.pop()?.length is 2
puts arr.pop()?.length is undefined
puts arr[0]?.length is undefined
puts arr.pop()?.length?.non?.existent()?.property is undefined

View File

@@ -9,7 +9,7 @@ findit: (items) ->
for item in items
return item if item is "bacon"
print findit(items) is "bacon"
puts findit(items) is "bacon"
# When when a closure wrapper is generated for expression conversion, make sure
@@ -26,5 +26,5 @@ obj: {
this.num
}
print obj.num is obj.func()
print obj.num is obj.result
puts obj.num is obj.func()
puts obj.num is obj.result

View File

@@ -7,10 +7,10 @@ result: if a
if d
true
print result
puts result
first: if false then false else second: if false then false else true
print first
print second
puts first
puts second

View File

@@ -2,11 +2,11 @@ x: 1
y: {}
y.x: -> 3
print x is 1
print typeof(y.x) is 'function'
print y.x instanceof Function
print y.x() is 3
print y.x.name is 'x'
puts x is 1
puts typeof(y.x) is 'function'
puts y.x instanceof Function
puts y.x() is 3
puts y.x.name is 'x'
# The empty function should not cause a syntax error.
@@ -17,10 +17,10 @@ obj: {
name: "Fred"
bound: ->
(=> print(this.name is "Fred"))()
(=> puts(this.name is "Fred"))()
unbound: ->
(-> print(!this.name?))()
(-> puts(!this.name?))()
}
obj.unbound()
@@ -44,18 +44,18 @@ Math: {
FastAdd: memoize (a, b) -> a + b
}
print Math.Add(5, 5) is 10
print Math.AnonymousAdd(10, 10) is 20
print Math.FastAdd(20, 20) is 40
puts Math.Add(5, 5) is 10
puts Math.AnonymousAdd(10, 10) is 20
puts Math.FastAdd(20, 20) is 40
# Parens are optional on simple function calls.
print 100 > 1 if 1 > 0
print true unless false
print true for i in [1..3]
puts 100 > 1 if 1 > 0
puts true unless false
puts true for i in [1..3]
print_func: (f) -> print(f())
print_func -> true
puts_func: (f) -> puts(f())
puts_func -> true
# Optional parens can be used in a nested fashion.
call: (func) -> func()
@@ -64,7 +64,7 @@ result: call ->
inner: call ->
Math.Add(5, 5)
print result is 10
puts result is 10
# And even with strange things like this:
@@ -72,8 +72,8 @@ print result is 10
funcs: [(x) -> x, (x) -> x * x]
result: funcs[1] 5
print result is 25
puts result is 25
result: ("hello".slice) 3
print result is 'lo'
puts result is 'lo'

View File

@@ -18,4 +18,4 @@ switch 'string'
code()
# comment
print func()
puts func()

View File

@@ -3,7 +3,7 @@ a: """
on two lines
"""
print a is "basic heredoc\non two lines"
puts a is "basic heredoc\non two lines"
a: '''
@@ -12,12 +12,12 @@ a: '''
c
'''
print a is "a\n \"b\nc"
puts a is "a\n \"b\nc"
a: '''one-liner'''
print a is 'one-liner'
puts a is 'one-liner'
a: """
@@ -25,7 +25,7 @@ a: """
here
"""
print a is "out\nhere"
puts a is "out\nhere"
a: '''
@@ -34,7 +34,7 @@ a: '''
c
'''
print a is " a\n b\nc"
puts a is " a\n b\nc"
a: '''
a
@@ -43,4 +43,4 @@ a
b c
'''
print a is "a\n\n\nb c"
puts a is "a\n\n\nb c"

View File

@@ -1,10 +1,10 @@
num: 1 + 2 + (a: 3)
print num is 6
puts num is 6
result: if true
false
other: "result"
print result is "result" and other is "result"
puts result is "result" and other is "result"

View File

@@ -1,37 +1,48 @@
a: [(x) -> x, (x) -> x * x]
print a.length is 2
puts a.length is 2
regex: /match/i
words: "I think there is a match in here."
print !!words.match(regex)
puts !!words.match(regex)
neg: (3 -4)
print neg is -1
puts neg is -1
func: ->
return if true
print func() is null
puts func() is null
str: "\\"
reg: /\\/
print reg(str) and str is '\\'
puts reg(str) and str is '\\'
i: 10
while i -= 1
print i is 0
puts i is 0
money$: 'dollars'
print money$ is 'dollars'
puts money$ is 'dollars'
bob: {
name: 'Bob'
greet: (salutation) ->
salutation + " " + @name
hello: ->
@greet "Hello"
}
puts bob.hello() is "Hello Bob"

View File

@@ -6,6 +6,6 @@ multi_liner:
single_liner:
[x, y] for y in [3..5] for x in [3..5]
print multi_liner.length is single_liner.length
print 5 is multi_liner[2][2][1]
print 5 is single_liner[2][2][1]
puts multi_liner.length is single_liner.length
puts 5 is multi_liner[2][2][1]
puts 5 is single_liner[2][2][1]

View File

@@ -3,4 +3,4 @@ six:
2 +
3
print six is 6
puts six is 6

View File

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

View File

@@ -5,16 +5,16 @@ negs: negs[0..2]
result: nums.concat(negs).join(', ')
print result is '3, 6, 9, -20, -19, -18'
puts result is '3, 6, 9, -20, -19, -18'
# Ensure that ranges are safe. This used to infinite loop:
j = 5
result: for j in [j..(j+3)]
j
print result.join(' ') is '5 6 7 8'
puts result.join(' ') is '5 6 7 8'
# With range comprehensions, you can loop in steps.
results: x for x in [0..25] by 5
print results.join(' ') is '0 5 10 15 20 25'
puts results.join(' ') is '0 5 10 15 20 25'

View File

@@ -5,7 +5,12 @@ b: array[2...4]
result: a.concat(b).join(' ')
print result is "7 8 9 2 3"
puts result is "7 8 9 2 3"
countdown: [10..1].join(' ')
print countdown is "10 9 8 7 6 5 4 3 2 1"
puts countdown is "10 9 8 7 6 5 4 3 2 1"
array: [(1+5)..1+9]
puts array.join(' ') is "6 7 8 9 10"

View File

@@ -3,7 +3,7 @@ func: (first, second, rest...) ->
result: func 1, 2, 3, 4, 5
print result is "3 4 5"
puts result is "3 4 5"
gold: silver: bronze: the_field: null
@@ -29,7 +29,7 @@ contenders: [
medalists "Mighty Mouse", contenders...
print gold is "Mighty Mouse"
print silver is "Michael Phelps"
print bronze is "Liu Xiang"
print the_field.length is 8
puts gold is "Mighty Mouse"
puts silver is "Michael Phelps"
puts bronze is "Liu Xiang"
puts the_field.length is 8

View File

@@ -2,4 +2,4 @@ array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array[5..10]: [0, 0, 0]
print array.join(' ') is '0 1 2 3 4 0 0 0'
puts array.join(' ') is '0 1 2 3 4 0 0 0'

View File

@@ -14,7 +14,7 @@ result: switch num
when 11 then false
else false
print result
puts result
func: (num) ->
switch num
@@ -24,7 +24,7 @@ func: (num) ->
false
else false
print func(2)
print func(6)
print !func(3)
print !func(8)
puts func(2)
puts func(6)
puts !func(3)
puts !func(8)

View File

@@ -1,17 +1,17 @@
i: 100
while i -= 1
print i is 0
puts i is 0
i: 5
list: while i -= 1
i * 2
print list.join(' ') is "8 6 4 2"
puts list.join(' ') is "8 6 4 2"
i: 5
list: (i * 3 while i -= 1)
print list.join(' ') is "12 9 6 3"
puts list.join(' ') is "12 9 6 3"

View File

@@ -3,14 +3,14 @@
result: while sunny?
go_outside()
print(3 + try
puts(3 + try
nonexistent.no_way
catch error
print(error)
puts(error)
3
)
func: (x) ->
return throw x
print(x * x for x in [1..100])
puts(x * x for x in [1..100])

View File

@@ -19,6 +19,13 @@ class ExecutionTest < Test::Unit::TestCase
end
end
# Test all of the code examples under Narwhal as well.
def test_execution_with_narwhal
(`bin/coffee -r --narwhal #{SOURCES.join(' ')}`).split("\n").each do |line|
assert line == "true"
end
end
def test_lintless_tests
no_warnings `bin/coffee -l test/fixtures/*/*.coffee`
end