mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-01-13 08:47:55 -05:00
Compare commits
268 Commits
2.0.0-beta
...
0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e645cce41 | ||
|
|
4f564dec60 | ||
|
|
20144abe25 | ||
|
|
a8631dcbaf | ||
|
|
f5cd6578b8 | ||
|
|
f1af50ee33 | ||
|
|
ad5e69d303 | ||
|
|
7d800b5e5b | ||
|
|
f150011d9d | ||
|
|
a09f807ce0 | ||
|
|
6082b9dc36 | ||
|
|
326904826d | ||
|
|
3f30712ca1 | ||
|
|
e9b72ee955 | ||
|
|
7be3b8edac | ||
|
|
3daac200e5 | ||
|
|
f77877d7eb | ||
|
|
536bdd2107 | ||
|
|
0093455d29 | ||
|
|
67d34ec40b | ||
|
|
d8603dbff2 | ||
|
|
6b6cb3ab12 | ||
|
|
10f53bac15 | ||
|
|
15f12423c3 | ||
|
|
885dbaf7e4 | ||
|
|
0d0df09c6d | ||
|
|
7df7ff26c8 | ||
|
|
841258b360 | ||
|
|
f715393b06 | ||
|
|
abfe3c35f8 | ||
|
|
8517455746 | ||
|
|
0e86fa534e | ||
|
|
1dbf257df7 | ||
|
|
5cbd94bf14 | ||
|
|
fa3f3e41d4 | ||
|
|
94bab256b4 | ||
|
|
dfa2f50076 | ||
|
|
e2de88544d | ||
|
|
010a2dde11 | ||
|
|
2fb8f48e75 | ||
|
|
271ea24b20 | ||
|
|
792fd359bd | ||
|
|
acf4a5ee47 | ||
|
|
d9afe2cf44 | ||
|
|
ee304485b8 | ||
|
|
5957ba624a | ||
|
|
2b40607b57 | ||
|
|
8da4185bbf | ||
|
|
e8df8abf7a | ||
|
|
b7ef9510c9 | ||
|
|
fba131c5a4 | ||
|
|
4985a400e6 | ||
|
|
2ed041b5e1 | ||
|
|
5bcb2b2629 | ||
|
|
adca8183de | ||
|
|
c187f2160f | ||
|
|
959c9a31cb | ||
|
|
9f6233473e | ||
|
|
32b0f9fa4f | ||
|
|
f7e49eaae4 | ||
|
|
3042a50f87 | ||
|
|
c8e820e851 | ||
|
|
66f92e770d | ||
|
|
f3472b7437 | ||
|
|
fdf2a76c53 | ||
|
|
d11569b434 | ||
|
|
86be82f3ae | ||
|
|
bcc5aa2bc3 | ||
|
|
5658b2b41f | ||
|
|
34bf4ce325 | ||
|
|
4251aa30c6 | ||
|
|
3ad9316fd0 | ||
|
|
e4ae324e8f | ||
|
|
370d05148d | ||
|
|
3f27b0ff72 | ||
|
|
71cf6ab031 | ||
|
|
f3e9a18c3c | ||
|
|
d2176acf25 | ||
|
|
62725c5a52 | ||
|
|
cc66b31e69 | ||
|
|
199d314903 | ||
|
|
f2a805db24 | ||
|
|
b7e29aac4d | ||
|
|
2ee04b8421 | ||
|
|
65ce74fbcb | ||
|
|
4b520d04e3 | ||
|
|
4eb10f9d43 | ||
|
|
7e58d1d914 | ||
|
|
4097a81456 | ||
|
|
c4413b933b | ||
|
|
e3da53e3df | ||
|
|
675a5f5d7c | ||
|
|
02c19b3170 | ||
|
|
96859e749b | ||
|
|
622ddea343 | ||
|
|
8e8efe4288 | ||
|
|
df5c5d9fe2 | ||
|
|
cf7ce8a1af | ||
|
|
b3cd5721cf | ||
|
|
893908b8fe | ||
|
|
6821660905 | ||
|
|
bc0214730a | ||
|
|
57d0f25054 | ||
|
|
f456d1b78e | ||
|
|
332f499c31 | ||
|
|
92cdeb093e | ||
|
|
f2bdd555fa | ||
|
|
5c7dee556a | ||
|
|
8c6e5d0b37 | ||
|
|
1128beb49b | ||
|
|
0963eea60e | ||
|
|
c1cdedd260 | ||
|
|
3b0b93ec06 | ||
|
|
9c2f66ff13 | ||
|
|
1b688d7077 | ||
|
|
df1f9c27eb | ||
|
|
e227a3bc69 | ||
|
|
e4c6119550 | ||
|
|
6c9e8f28b6 | ||
|
|
cc7e685428 | ||
|
|
4abd88f2a9 | ||
|
|
a44fe402a1 | ||
|
|
350cb623ae | ||
|
|
42c9c53a4c | ||
|
|
5a49c22121 | ||
|
|
2bc4cbbdcc | ||
|
|
8fe6fa1cd7 | ||
|
|
835db4b279 | ||
|
|
f89c864911 | ||
|
|
542726911a | ||
|
|
575dc7d12e | ||
|
|
ff0062b088 | ||
|
|
78589f5db1 | ||
|
|
903331f3ff | ||
|
|
6aa247f73d | ||
|
|
da71735066 | ||
|
|
47b45c4494 | ||
|
|
d6ac6a3535 | ||
|
|
c322d77b86 | ||
|
|
7aa69579ff | ||
|
|
5f3e2b7fc7 | ||
|
|
c4844abb28 | ||
|
|
96ae6d80f3 | ||
|
|
60342e8cd9 | ||
|
|
f9c3d3fc14 | ||
|
|
b1e25eea88 | ||
|
|
1486bbab9f | ||
|
|
59d912cc26 | ||
|
|
9adf2e2d30 | ||
|
|
cf46fa8c2c | ||
|
|
16ca3d1608 | ||
|
|
476a251c80 | ||
|
|
274152aff7 | ||
|
|
00659e5f76 | ||
|
|
88fe4f6fd1 | ||
|
|
03a90928e1 | ||
|
|
67865d3341 | ||
|
|
0337513172 | ||
|
|
1ee2c53391 | ||
|
|
4b7c965101 | ||
|
|
11c394fb7e | ||
|
|
54a7c405e7 | ||
|
|
781f3b5fa4 | ||
|
|
2393472924 | ||
|
|
859ab7583f | ||
|
|
3eedd5bb50 | ||
|
|
5b7e695f6c | ||
|
|
1e3182727b | ||
|
|
e595dbfcee | ||
|
|
12b830893d | ||
|
|
cca80342aa | ||
|
|
4412f590cf | ||
|
|
0cd2d78027 | ||
|
|
be672ebfc1 | ||
|
|
9047c87567 | ||
|
|
31d630bb91 | ||
|
|
7a2f5a333f | ||
|
|
66303636dc | ||
|
|
9dc932e380 | ||
|
|
a71de4b5b6 | ||
|
|
ada8dfc6d4 | ||
|
|
4112595368 | ||
|
|
c281db7730 | ||
|
|
5e6b49ad1e | ||
|
|
ec1a527575 | ||
|
|
7f066aa168 | ||
|
|
166ea578f9 | ||
|
|
dc0ab1afca | ||
|
|
ae72fbfd0d | ||
|
|
ad0735f765 | ||
|
|
d49c178f1d | ||
|
|
cb3b47690a | ||
|
|
cf6060bdb3 | ||
|
|
8f8ba255b3 | ||
|
|
a4bc24817d | ||
|
|
9eeac9b272 | ||
|
|
f859eb6cec | ||
|
|
b29afc2c09 | ||
|
|
95b79973f9 | ||
|
|
1f79733a33 | ||
|
|
cfc29f7830 | ||
|
|
34486039e1 | ||
|
|
53e8ea7d9e | ||
|
|
9841b78ed8 | ||
|
|
065cfddd0d | ||
|
|
6882a3d36c | ||
|
|
85c595e84c | ||
|
|
b8f563d49e | ||
|
|
3b3d57e84a | ||
|
|
a1ee622aa6 | ||
|
|
64733981fd | ||
|
|
7833b11724 | ||
|
|
a7032d0964 | ||
|
|
f2c872230e | ||
|
|
8d26488748 | ||
|
|
d92ed46503 | ||
|
|
777eac045a | ||
|
|
37e2f3b944 | ||
|
|
3902a8b268 | ||
|
|
63a910d7ce | ||
|
|
3cee51cc37 | ||
|
|
45b559a721 | ||
|
|
7c59eb2c36 | ||
|
|
40f633b8d0 | ||
|
|
4e3d7cb974 | ||
|
|
d14b127b60 | ||
|
|
0d566ed1ec | ||
|
|
d86f92c6f2 | ||
|
|
efc5150144 | ||
|
|
7474ed1a5e | ||
|
|
2f4433af71 | ||
|
|
91303efd2c | ||
|
|
d73ff9a79f | ||
|
|
7ec6300a48 | ||
|
|
42c84fc54b | ||
|
|
f0a790d624 | ||
|
|
fdcff7aaf0 | ||
|
|
ab2362e372 | ||
|
|
98cf9f5af2 | ||
|
|
e74af51a7d | ||
|
|
35b5d8c630 | ||
|
|
8575d91c66 | ||
|
|
8338f124be | ||
|
|
be19f7ad4f | ||
|
|
5c737d29ab | ||
|
|
92c59ea4a5 | ||
|
|
dd28074436 | ||
|
|
f8ab30fa42 | ||
|
|
581ad8ba1e | ||
|
|
5703c1ed6d | ||
|
|
a71a3cdf3f | ||
|
|
f5d31b78e6 | ||
|
|
e1e6bb72c6 | ||
|
|
d89ca33cdb | ||
|
|
58ecfeb815 | ||
|
|
955d01a302 | ||
|
|
f8a5f7595d | ||
|
|
9f33cf19ad | ||
|
|
6deb85e083 | ||
|
|
9ad108281e | ||
|
|
cd0091c045 | ||
|
|
01ecae2c55 | ||
|
|
cef4bcd756 | ||
|
|
ad50bd7154 | ||
|
|
b97f9cf5ec | ||
|
|
2d65d3d73b | ||
|
|
733a76fdba | ||
|
|
fd63698005 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
parser.output
|
||||
test.coffee
|
||||
parser.output
|
||||
test/fixtures/underscore
|
||||
*.gem
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2009 Jeremy Ashkenas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
38
README
Normal file
38
README
Normal file
@@ -0,0 +1,38 @@
|
||||
=
|
||||
{
|
||||
} } {
|
||||
{ { } }
|
||||
} }{ {
|
||||
{ }{ } } _____ __ __
|
||||
( }{ }{ { ) / ____| / _|/ _|
|
||||
.- { { } { }} -. | | ___ | |_| |_ ___ ___
|
||||
( ( } { } { } } ) | | / _ \| _| _/ _ \/ _ \
|
||||
|`-..________ ..-'| | |___| (_) | | | || __/ __/
|
||||
| | \_____\___/|_| |_| \___|\___|
|
||||
| ;--.
|
||||
| (__ \ _____ _ _
|
||||
| | ) ) / ____| (_) | |
|
||||
| |/ / | (___ ___ _ __ _ _ __ | |_
|
||||
| ( / \___ \ / __| '__| | '_ \| __|
|
||||
| |/ ____) | (__| | | | |_) | |_
|
||||
| | |_____/ \___|_| |_| .__/ \__|
|
||||
`-.._________..-' | |
|
||||
|_|
|
||||
|
||||
|
||||
CoffeeScript is a little language that compiles into JavaScript.
|
||||
|
||||
Install the compiler:
|
||||
gem install coffee-script
|
||||
|
||||
Compile a script:
|
||||
coffee /path/to/script.coffee
|
||||
|
||||
For documentation, usage, and examples, see:
|
||||
http://jashkenas.github.com/coffee-script/
|
||||
|
||||
To suggest a feature or report a bug:
|
||||
http://github.com/jashkenas/coffee-script/issues/
|
||||
|
||||
The source repository:
|
||||
git://github.com/jashkenas/coffee-script.git
|
||||
67
Rakefile
Normal file
67
Rakefile
Normal file
@@ -0,0 +1,67 @@
|
||||
require 'erb'
|
||||
require 'fileutils'
|
||||
require 'rake/testtask'
|
||||
|
||||
desc "Run all tests"
|
||||
task :test do
|
||||
$LOAD_PATH.unshift(File.expand_path('test'))
|
||||
require 'redgreen' if Gem.available?('redgreen')
|
||||
require 'test/unit'
|
||||
Dir['test/*/**/test_*.rb'].each {|test| require test }
|
||||
end
|
||||
|
||||
namespace :build do
|
||||
|
||||
desc "Recompile the Racc parser (pass -v and -g for verbose debugging)"
|
||||
task :parser, :racc_args do |t, args|
|
||||
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"
|
||||
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"
|
||||
end
|
||||
|
||||
desc "Compile and install the Ultraviolet syntax highlighter"
|
||||
task :ultraviolet do
|
||||
sh "plist2syntax lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage"
|
||||
sh "sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
desc "Build the documentation page"
|
||||
task :doc do
|
||||
source = 'documentation/index.html.erb'
|
||||
child = fork { exec "bin/coffee documentation/coffee/*.coffee -o documentation/js -w" }
|
||||
at_exit { Process.kill("INT", child) }
|
||||
Signal.trap("INT") { exit }
|
||||
# `uv -t idle -s coffeescript -h examples/underscore.coffee > documentation/underscore.html`
|
||||
loop do
|
||||
mtime = File.stat(source).mtime
|
||||
if !@mtime || mtime > @mtime
|
||||
rendered = ERB.new(File.read(source)).result(binding)
|
||||
File.open('index.html', 'w+') {|f| f.write(rendered) }
|
||||
end
|
||||
@mtime = mtime
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
|
||||
namespace :gem do
|
||||
|
||||
desc 'Build and install the coffee-script gem'
|
||||
task :install do
|
||||
sh "gem build coffee-script.gemspec"
|
||||
sh "sudo gem install #{Dir['*.gem'].join(' ')} --local --no-ri --no-rdoc"
|
||||
end
|
||||
|
||||
desc 'Uninstall the coffee-script gem'
|
||||
task :uninstall do
|
||||
sh "sudo gem uninstall -x coffee-script"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
task :default => :test
|
||||
30
SYNTAX
30
SYNTAX
@@ -1,30 +0,0 @@
|
||||
Every line is an expression. Multiple expressions on a single line can be
|
||||
separated by a ";" character.
|
||||
|
||||
NUM: 1
|
||||
1.0
|
||||
|
||||
STRING: "hello"
|
||||
'hello'
|
||||
|
||||
OBJECT: {one : 1, two : 2}
|
||||
|
||||
ARRAY: [1, 2, 3]
|
||||
|
||||
CODE: a, b => a * b.
|
||||
|
||||
IF: return x if x > 1
|
||||
|
||||
if (x > 1) return x
|
||||
|
||||
ASSIGN: a : b
|
||||
|
||||
LOGICAL: x && y
|
||||
x and y
|
||||
x || y
|
||||
x or y
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
6
WISHLIST
6
WISHLIST
@@ -1,6 +0,0 @@
|
||||
* Is it possible to close blocks (functions, ifs, trys) without an explicit
|
||||
block delimiter or significant whitespace?
|
||||
|
||||
* Is it possible to pass comments through cleanly and have them show up on
|
||||
the other end? This includes comments in the middle of array and object
|
||||
literals, and argument lists.
|
||||
5
bin/coffee
Executable file
5
bin/coffee
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "#{File.dirname(__FILE__)}/../lib/coffee_script/command_line.rb"
|
||||
|
||||
CoffeeScript::CommandLine.new
|
||||
126
code.cs
126
code.cs
@@ -1,126 +0,0 @@
|
||||
# TODO: Add range indexing: array[5..7] => array.slice(5, 7)
|
||||
|
||||
# Functions:
|
||||
square: x => x * x.
|
||||
|
||||
sum: x, y => x + y.
|
||||
|
||||
odd: x => x % 2 is 0.
|
||||
|
||||
even: x => x % 2 aint 0.
|
||||
|
||||
run_loop: =>
|
||||
fire_events( e => e.stopPropagation(). )
|
||||
listen()
|
||||
wait().
|
||||
|
||||
# Objects:
|
||||
dense_object_literal: {one: 1, two: 2, three: 3}
|
||||
|
||||
spaced_out_multiline_object: {
|
||||
|
||||
pi: 3.14159
|
||||
|
||||
list: [1, 2, 3, 4]
|
||||
|
||||
three: new Idea()
|
||||
|
||||
inner_obj: {
|
||||
freedom: => _.freedom().
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Arrays:
|
||||
stooges : [{moe: 45}, {curly: 43}, {larry: 46}]
|
||||
|
||||
exponents : [x => x., x => x * x., x => x * x * x.]
|
||||
|
||||
empty: []
|
||||
|
||||
# Conditionals and ternaries.
|
||||
if submarine.shields_up
|
||||
full_speed_ahead()
|
||||
fire_torpedos()
|
||||
else
|
||||
run_away().
|
||||
|
||||
eldest: if 25 > 21 then liz else marge.
|
||||
|
||||
decoration: medal_of_honor if war_hero
|
||||
|
||||
go_to_sleep() unless coffee
|
||||
|
||||
# Returning early:
|
||||
race: =>
|
||||
run()
|
||||
walk()
|
||||
crawl()
|
||||
if tired then return sleep().
|
||||
race().
|
||||
|
||||
# Conditional operators:
|
||||
good ||= evil
|
||||
wine &&= cheese
|
||||
|
||||
# Nested property access and calls.
|
||||
((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position
|
||||
|
||||
a: b: c: 5
|
||||
|
||||
# Embedded JavaScript.
|
||||
callback(
|
||||
`function(e) { e.stop(); }`
|
||||
)
|
||||
|
||||
# Try/Catch/Finally/Throw.
|
||||
try
|
||||
all_hell_breaks_loose()
|
||||
dogs_and_cats_living_together()
|
||||
throw "up"
|
||||
catch error
|
||||
print( error )
|
||||
finally
|
||||
clean_up().
|
||||
|
||||
try all_hell_breaks_loose() catch error print(error) finally clean_up().
|
||||
|
||||
# While loops.
|
||||
while demand > supply
|
||||
sell()
|
||||
restock().
|
||||
|
||||
while supply > demand then buy().
|
||||
|
||||
# Unary operators.
|
||||
!!true
|
||||
|
||||
# Lexical scoping.
|
||||
a: 5
|
||||
change_a_and_set_b: =>
|
||||
a: 10
|
||||
b: 15.
|
||||
b: 20
|
||||
|
||||
# Array comprehensions.
|
||||
supper: food.capitalize() for food in ['toast', 'cheese', 'wine'].
|
||||
|
||||
drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i).
|
||||
|
||||
# Switch statements.
|
||||
switch day
|
||||
case "Tuesday" then eat_breakfast()
|
||||
case "Sunday" then go_to_church()
|
||||
case "Saturday" then go_to_the_park()
|
||||
case "Wednesday"
|
||||
eat_breakfast()
|
||||
go_to_work()
|
||||
eat_dinner()
|
||||
default go_to_work().
|
||||
|
||||
# Semicolons can optionally be used instead of newlines.
|
||||
wednesday: => eat_breakfast(); go_to_work(); eat_dinner(); .
|
||||
|
||||
# Array slice literals.
|
||||
zero_to_nine: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
three_to_six: zero_to_nine[3, 6]
|
||||
26
coffee-script.gemspec
Normal file
26
coffee-script.gemspec
Normal file
@@ -0,0 +1,26 @@
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'coffee-script'
|
||||
s.version = '0.2.0' # Keep version in sync with coffee-script.rb
|
||||
s.date = '2010-1-5'
|
||||
|
||||
s.homepage = "http://jashkenas.github.com/coffee-script/"
|
||||
s.summary = "The CoffeeScript Compiler"
|
||||
s.description = <<-EOS
|
||||
CoffeeScript is a little language that compiles into JavaScript. Think
|
||||
of it as JavaScript's less ostentatious kid brother -- the same genes,
|
||||
roughly the same height, but a different sense of style. Apart from a
|
||||
handful of bonus goodies, statements in CoffeeScript correspond
|
||||
one-to-one with their equivalent in JavaScript, it's just another
|
||||
way of saying it.
|
||||
EOS
|
||||
|
||||
s.authors = ['Jeremy Ashkenas']
|
||||
s.email = 'jashkenas@gmail.com'
|
||||
s.rubyforge_project = 'coffee-script'
|
||||
s.has_rdoc = false
|
||||
|
||||
s.require_paths = ['lib']
|
||||
s.executables = ['coffee']
|
||||
|
||||
s.files = Dir['bin/*', 'examples/*', 'lib/**/*', 'coffee-script.gemspec', 'LICENSE', 'README', 'package.json']
|
||||
end
|
||||
7
documentation/coffee/aliases.coffee
Normal file
7
documentation/coffee/aliases.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
launch() if ignition is on
|
||||
|
||||
volume: 10 if band isnt spinal_tap
|
||||
|
||||
let_the_wild_rumpus_begin() unless answer is no
|
||||
|
||||
if car.speed < speed_limit then accelerate()
|
||||
7
documentation/coffee/array_comprehensions.coffee
Normal file
7
documentation/coffee/array_comprehensions.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
# Eat lunch.
|
||||
lunch: eat(food) for food in ['toast', 'cheese', 'wine']
|
||||
|
||||
# Naive collision detection.
|
||||
for roid in asteroids
|
||||
for roid2 in asteroids when roid isnt roid2
|
||||
roid.explode() if roid.overlaps(roid2)
|
||||
2
documentation/coffee/assignment.coffee
Normal file
2
documentation/coffee/assignment.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
greeting: "Hello CoffeeScript"
|
||||
difficulty: 0.5
|
||||
4
documentation/coffee/blocks.coffee
Normal file
4
documentation/coffee/blocks.coffee
Normal file
@@ -0,0 +1,4 @@
|
||||
$('table.list').each() table =>
|
||||
$('tr.account', table).each() row =>
|
||||
row.show()
|
||||
row.highlight()
|
||||
9
documentation/coffee/conditionals.coffee
Normal file
9
documentation/coffee/conditionals.coffee
Normal file
@@ -0,0 +1,9 @@
|
||||
mood: greatly_improved if singing
|
||||
|
||||
if happy and knows_it
|
||||
claps_hands()
|
||||
cha_cha_cha()
|
||||
|
||||
date: if friday then sue else jill
|
||||
|
||||
expensive ||= do_the_math()
|
||||
5
documentation/coffee/embedded.coffee
Normal file
5
documentation/coffee/embedded.coffee
Normal file
@@ -0,0 +1,5 @@
|
||||
hi: `function() {
|
||||
return [document.title, "Hello JavaScript"].join(": ");
|
||||
}`
|
||||
|
||||
|
||||
1
documentation/coffee/existence.coffee
Normal file
1
documentation/coffee/existence.coffee
Normal file
@@ -0,0 +1 @@
|
||||
solipsism: true if mind? and not world?
|
||||
9
documentation/coffee/expressions.coffee
Normal file
9
documentation/coffee/expressions.coffee
Normal file
@@ -0,0 +1,9 @@
|
||||
grade: student =>
|
||||
if student.excellent_work
|
||||
"A+"
|
||||
else if student.okay_stuff
|
||||
if student.tried_hard then "B" else "B-"
|
||||
else
|
||||
"C"
|
||||
|
||||
eldest: if 24 > 21 then "Liz" else "Ike"
|
||||
1
documentation/coffee/expressions_assignment.coffee
Normal file
1
documentation/coffee/expressions_assignment.coffee
Normal file
@@ -0,0 +1 @@
|
||||
six: (one: 1) + (two: 2) + (three: 3)
|
||||
3
documentation/coffee/expressions_comprehension.coffee
Normal file
3
documentation/coffee/expressions_comprehension.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# The first ten global properties.
|
||||
|
||||
globals: (name for property, name in window)[0...10]
|
||||
6
documentation/coffee/expressions_try.coffee
Normal file
6
documentation/coffee/expressions_try.coffee
Normal file
@@ -0,0 +1,6 @@
|
||||
alert(
|
||||
try
|
||||
nonexistent / undefined
|
||||
catch error
|
||||
"The error is: " + error
|
||||
)
|
||||
2
documentation/coffee/functions.coffee
Normal file
2
documentation/coffee/functions.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
square: x => x * x
|
||||
cube: x => square(x) * x
|
||||
3
documentation/coffee/object_comprehensions.coffee
Normal file
3
documentation/coffee/object_comprehensions.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
years_old: {max: 10, ida: 9, tim: 11}
|
||||
|
||||
ages: child + " is " + age for age, child in years_old
|
||||
13
documentation/coffee/objects_and_arrays.coffee
Normal file
13
documentation/coffee/objects_and_arrays.coffee
Normal file
@@ -0,0 +1,13 @@
|
||||
song: ["do", "re", "mi", "fa", "so"]
|
||||
|
||||
ages: {
|
||||
max: 10
|
||||
ida: 9
|
||||
tim: 11
|
||||
}
|
||||
|
||||
matrix: [
|
||||
1, 0, 1
|
||||
0, 0, 1
|
||||
1, 1, 0
|
||||
]
|
||||
29
documentation/coffee/overview.coffee
Normal file
29
documentation/coffee/overview.coffee
Normal file
@@ -0,0 +1,29 @@
|
||||
# Assignment:
|
||||
number: 42
|
||||
opposite_day: true
|
||||
|
||||
# Conditions:
|
||||
number: -42 if opposite_day
|
||||
|
||||
# Functions:
|
||||
square: x => x * x
|
||||
|
||||
# Arrays:
|
||||
list: [1, 2, 3, 4, 5]
|
||||
|
||||
# Objects:
|
||||
math: {
|
||||
root: Math.sqrt
|
||||
square: square
|
||||
cube: x => x * square(x)
|
||||
}
|
||||
|
||||
# Splats:
|
||||
race: winner, *runners =>
|
||||
print(winner, runners)
|
||||
|
||||
# Existence:
|
||||
alert("I knew it!") if elvis?
|
||||
|
||||
# Array comprehensions:
|
||||
cubed_list: math.cube(num) for num in list
|
||||
3
documentation/coffee/range_comprehensions.coffee
Normal file
3
documentation/coffee/range_comprehensions.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
for i in [0...eggs.length] by 12
|
||||
dozen_eggs: eggs[i...i+12]
|
||||
deliver(new egg_carton(dozen))
|
||||
5
documentation/coffee/scope.coffee
Normal file
5
documentation/coffee/scope.coffee
Normal file
@@ -0,0 +1,5 @@
|
||||
num: 1
|
||||
change_numbers: =>
|
||||
new_num: -1
|
||||
num: 10
|
||||
new_num: change_numbers()
|
||||
6
documentation/coffee/slices.coffee
Normal file
6
documentation/coffee/slices.coffee
Normal file
@@ -0,0 +1,6 @@
|
||||
numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
three_to_six: numbers[3..6]
|
||||
|
||||
numbers_copy: numbers[0...numbers.length]
|
||||
|
||||
25
documentation/coffee/splats.coffee
Normal file
25
documentation/coffee/splats.coffee
Normal file
@@ -0,0 +1,25 @@
|
||||
gold: silver: the_field: "unknown"
|
||||
|
||||
medalists: first, second, *rest =>
|
||||
gold: first
|
||||
silver: second
|
||||
the_field: rest
|
||||
|
||||
contenders: [
|
||||
"Michael Phelps"
|
||||
"Liu Xiang"
|
||||
"Yao Ming"
|
||||
"Allyson Felix"
|
||||
"Shawn Johnson"
|
||||
"Roman Sebrle"
|
||||
"Guo Jingjing"
|
||||
"Tyson Gay"
|
||||
"Asafa Powell"
|
||||
"Usain Bolt"
|
||||
]
|
||||
|
||||
medalists(*contenders)
|
||||
|
||||
alert("Gold: " + gold)
|
||||
alert("Silver: " + silver)
|
||||
alert("The Field: " + the_field)
|
||||
5
documentation/coffee/splices.coffee
Normal file
5
documentation/coffee/splices.coffee
Normal file
@@ -0,0 +1,5 @@
|
||||
numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
numbers[3..6]: [-3, -4, -5, -6]
|
||||
|
||||
|
||||
8
documentation/coffee/strings.coffee
Normal file
8
documentation/coffee/strings.coffee
Normal file
@@ -0,0 +1,8 @@
|
||||
moby_dick: "Call me Ishmael. Some years ago --
|
||||
never mind how long precisely -- having little
|
||||
or no money in my purse, and nothing particular
|
||||
to interest me on shore, I thought I would sail
|
||||
about a little and see the watery part of the
|
||||
world..."
|
||||
|
||||
|
||||
25
documentation/coffee/super.coffee
Normal file
25
documentation/coffee/super.coffee
Normal file
@@ -0,0 +1,25 @@
|
||||
Animal: =>
|
||||
Animal.prototype.move: meters =>
|
||||
alert(this.name + " moved " + meters + "m.")
|
||||
|
||||
Snake: name => this.name: name
|
||||
Snake extends Animal
|
||||
Snake.prototype.move: =>
|
||||
alert("Slithering...")
|
||||
super(5)
|
||||
|
||||
Horse: name => this.name: name
|
||||
Horse extends Animal
|
||||
Horse.prototype.move: =>
|
||||
alert("Galloping...")
|
||||
super(45)
|
||||
|
||||
sam: new Snake("Sammy the Python")
|
||||
tom: new Horse("Tommy the Palomino")
|
||||
|
||||
sam.move()
|
||||
tom.move()
|
||||
|
||||
|
||||
|
||||
|
||||
9
documentation/coffee/switch.coffee
Normal file
9
documentation/coffee/switch.coffee
Normal file
@@ -0,0 +1,9 @@
|
||||
switch day
|
||||
when "Tuesday" then eat_breakfast()
|
||||
when "Wednesday" then go_to_the_park()
|
||||
when "Saturday"
|
||||
if day is bingo_day
|
||||
go_to_bingo()
|
||||
go_dancing()
|
||||
when "Sunday" then go_to_church()
|
||||
else go_to_work()
|
||||
7
documentation/coffee/try.coffee
Normal file
7
documentation/coffee/try.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
try
|
||||
all_hell_breaks_loose()
|
||||
cats_and_dogs_living_together()
|
||||
catch error
|
||||
print(error)
|
||||
finally
|
||||
clean_up()
|
||||
5
documentation/coffee/while.coffee
Normal file
5
documentation/coffee/while.coffee
Normal file
@@ -0,0 +1,5 @@
|
||||
while demand > supply
|
||||
sell()
|
||||
restock()
|
||||
|
||||
while supply > demand then buy()
|
||||
147
documentation/css/amy.css
Normal file
147
documentation/css/amy.css
Normal file
@@ -0,0 +1,147 @@
|
||||
pre.amy .PolymorphicVariants {
|
||||
color: #60B0FF;
|
||||
font-style: italic;
|
||||
}
|
||||
pre.amy .KeywordDecorator {
|
||||
color: #D0D0FF;
|
||||
}
|
||||
pre.amy .Punctuation {
|
||||
color: #805080;
|
||||
}
|
||||
pre.amy .InheritedClass {
|
||||
}
|
||||
pre.amy .InvalidDepricated {
|
||||
background-color: #CC66FF;
|
||||
color: #200020;
|
||||
}
|
||||
pre.amy .LibraryVariable {
|
||||
}
|
||||
pre.amy .TokenReferenceOcamlyacc {
|
||||
color: #3CB0D0;
|
||||
}
|
||||
pre.amy .Storage {
|
||||
color: #B0FFF0;
|
||||
}
|
||||
pre.amy .KeywordOperator {
|
||||
color: #A0A0FF;
|
||||
}
|
||||
pre.amy .CharacterConstant {
|
||||
color: #666666;
|
||||
}
|
||||
pre.amy .line-numbers {
|
||||
background-color: #800000;
|
||||
color: #000000;
|
||||
}
|
||||
pre.amy .ClassName {
|
||||
color: #70E080;
|
||||
}
|
||||
pre.amy .Int64Constant {
|
||||
font-style: italic;
|
||||
}
|
||||
pre.amy .NonTerminalReferenceOcamlyacc {
|
||||
color: #C0F0F0;
|
||||
}
|
||||
pre.amy .TokenDefinitionOcamlyacc {
|
||||
color: #3080A0;
|
||||
}
|
||||
pre.amy .ClassType {
|
||||
color: #70E0A0;
|
||||
}
|
||||
pre.amy .ControlKeyword {
|
||||
color: #80A0FF;
|
||||
}
|
||||
pre.amy .LineNumberDirectives {
|
||||
text-decoration: underline;
|
||||
color: #C080C0;
|
||||
}
|
||||
pre.amy .FloatingPointConstant {
|
||||
text-decoration: underline;
|
||||
}
|
||||
pre.amy .Int32Constant {
|
||||
font-weight: bold;
|
||||
}
|
||||
pre.amy .TagName {
|
||||
color: #009090;
|
||||
}
|
||||
pre.amy .ModuleTypeDefinitions {
|
||||
text-decoration: underline;
|
||||
color: #B000B0;
|
||||
}
|
||||
pre.amy .Integer {
|
||||
color: #7090B0;
|
||||
}
|
||||
pre.amy .Camlp4TempParser {
|
||||
}
|
||||
pre.amy .InvalidIllegal {
|
||||
font-weight: bold;
|
||||
background-color: #FFFF00;
|
||||
color: #400080;
|
||||
}
|
||||
pre.amy .LibraryConstant {
|
||||
background-color: #200020;
|
||||
}
|
||||
pre.amy .ModuleDefinitions {
|
||||
color: #B000B0;
|
||||
}
|
||||
pre.amy .Variants {
|
||||
color: #60B0FF;
|
||||
}
|
||||
pre.amy .CompilerDirectives {
|
||||
color: #C080C0;
|
||||
}
|
||||
pre.amy .FloatingPointInfixOperator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
pre.amy .BuiltInConstant1 {
|
||||
}
|
||||
pre.amy {
|
||||
background-color: #200020;
|
||||
color: #D0D0FF;
|
||||
}
|
||||
pre.amy .FunctionArgument {
|
||||
color: #80B0B0;
|
||||
}
|
||||
pre.amy .FloatingPointPrefixOperator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
pre.amy .NativeintConstant {
|
||||
font-weight: bold;
|
||||
}
|
||||
pre.amy .BuiltInConstant {
|
||||
color: #707090;
|
||||
}
|
||||
pre.amy .BooleanConstant {
|
||||
color: #8080A0;
|
||||
}
|
||||
pre.amy .LibraryClassType {
|
||||
}
|
||||
pre.amy .TagAttribute {
|
||||
}
|
||||
pre.amy .Keyword {
|
||||
color: #A080FF;
|
||||
}
|
||||
pre.amy .UserDefinedConstant {
|
||||
}
|
||||
pre.amy .String {
|
||||
color: #999999;
|
||||
}
|
||||
pre.amy .Camlp4Code {
|
||||
background-color: #350060;
|
||||
}
|
||||
pre.amy .NonTerminalDefinitionOcamlyacc {
|
||||
color: #90E0E0;
|
||||
}
|
||||
pre.amy .FunctionName {
|
||||
color: #50A0A0;
|
||||
}
|
||||
pre.amy .SupportModules {
|
||||
color: #A00050;
|
||||
}
|
||||
pre.amy .Variable {
|
||||
color: #008080;
|
||||
}
|
||||
pre.amy .Comment {
|
||||
background-color: #200020;
|
||||
color: #404080;
|
||||
font-style: italic;
|
||||
}
|
||||
87
documentation/css/docs.css
Normal file
87
documentation/css/docs.css
Normal file
@@ -0,0 +1,87 @@
|
||||
body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
background: #f3f3f9;
|
||||
color: #191933;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
div.container {
|
||||
width: 850px;
|
||||
margin: 50px 0 50px 50px;
|
||||
}
|
||||
p {
|
||||
padding-left: 13px;
|
||||
width: 625px;
|
||||
}
|
||||
a {
|
||||
color: #000055;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
padding-left: 13px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
br.clear {
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
b.header {
|
||||
color: #000055;
|
||||
display: block;
|
||||
margin: 40px 0 5px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
table {
|
||||
margin: 16px 0 0 13px; padding: 0;
|
||||
width: 625px;
|
||||
}
|
||||
tr, td {
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
td {
|
||||
padding: 9px 15px 9px 0;
|
||||
}
|
||||
code, pre, tt {
|
||||
font-family: Monaco, Consolas, "Lucida Console", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #191955;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
tt {
|
||||
background: #f8f8ff;
|
||||
border: 1px solid #dedede;
|
||||
font-size: 85%;
|
||||
padding: 0px 0.2em;
|
||||
}
|
||||
pre {
|
||||
border-left: 6px solid #222255;
|
||||
margin-left: 13px;
|
||||
padding: 3px 0 3px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
div.code {
|
||||
position: relative;
|
||||
border: 1px solid #cacaca;
|
||||
background: #fff;
|
||||
padding: 7px 0 10px 0;
|
||||
-moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;
|
||||
-webkit-box-shadow: 0px 0px 7px #cacaca;
|
||||
}
|
||||
div.code button {
|
||||
position: absolute;
|
||||
right: 8px; bottom: 8px;
|
||||
}
|
||||
div.code pre {
|
||||
float: left;
|
||||
width: 410px;
|
||||
border-left: 1px dotted #559;
|
||||
padding: 0 0 0 12px;
|
||||
margin: 0;
|
||||
}
|
||||
div.code pre:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
62
documentation/css/idle.css
Normal file
62
documentation/css/idle.css
Normal file
@@ -0,0 +1,62 @@
|
||||
pre.idle .InheritedClass {
|
||||
}
|
||||
pre.idle .TypeName {
|
||||
color: #21439C;
|
||||
}
|
||||
pre.idle .Number {
|
||||
}
|
||||
pre.idle .LibraryVariable {
|
||||
color: #A535AE;
|
||||
}
|
||||
pre.idle .Storage {
|
||||
color: #FF5600;
|
||||
}
|
||||
pre.idle .line-numbers {
|
||||
background-color: #BAD6FD;
|
||||
color: #000000;
|
||||
}
|
||||
pre.idle {
|
||||
background-color: #FFFFFF;
|
||||
color: #000000;
|
||||
}
|
||||
pre.idle .StringInterpolation {
|
||||
color: #990000;
|
||||
}
|
||||
pre.idle .TagName {
|
||||
}
|
||||
pre.idle .LibraryConstant {
|
||||
color: #A535AE;
|
||||
}
|
||||
pre.idle .FunctionArgument {
|
||||
}
|
||||
pre.idle .BuiltInConstant {
|
||||
color: #A535AE;
|
||||
}
|
||||
pre.idle .Invalid {
|
||||
background-color: #990000;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
pre.idle .LibraryClassType {
|
||||
color: #A535AE;
|
||||
}
|
||||
pre.idle .LibraryFunction {
|
||||
color: #A535AE;
|
||||
}
|
||||
pre.idle .TagAttribute {
|
||||
}
|
||||
pre.idle .Keyword {
|
||||
color: #FF5600;
|
||||
}
|
||||
pre.idle .UserDefinedConstant {
|
||||
}
|
||||
pre.idle .String {
|
||||
color: #00A33F;
|
||||
}
|
||||
pre.idle .FunctionName {
|
||||
color: #21439C;
|
||||
}
|
||||
pre.idle .Variable {
|
||||
}
|
||||
pre.idle .Comment {
|
||||
color: #919191;
|
||||
}
|
||||
649
documentation/index.html.erb
Normal file
649
documentation/index.html.erb
Normal file
@@ -0,0 +1,649 @@
|
||||
<%#
|
||||
TODO:
|
||||
Multiline and nested array comprehensions (and filters with 'when').
|
||||
Range comprehension examples (expression endpoints), with steps.
|
||||
Object comprehension examples.
|
||||
Significant Whitespace Rules.
|
||||
Newline-delimited Matrix.
|
||||
Automatic newline escaping.
|
||||
All functions are named functions.
|
||||
Splats in function definitions.
|
||||
(Multiple) splats as arguments to a function call.
|
||||
Exists?
|
||||
Array assignment splice literals.
|
||||
%>
|
||||
|
||||
<%
|
||||
require 'uv'
|
||||
def code_for(file, executable=false)
|
||||
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\Z|^ )/
|
||||
return '' unless File.exists?("documentation/js/#{file}.js")
|
||||
cs = File.read("documentation/coffee/#{file}.coffee")
|
||||
js = File.read("documentation/js/#{file}.js").gsub(@stripper, '')
|
||||
cshtml = Uv.parse(cs, 'xhtml', 'coffeescript', false, 'idle', false)
|
||||
jshtml = Uv.parse(js, 'xhtml', 'javascript', false, 'idle', false)
|
||||
append = executable == true ? '' : "alert(#{executable});"
|
||||
run = executable == true ? 'run' : "run: #{executable}"
|
||||
button = executable ? "<button onclick='javascript: #{js};#{append}'>#{run}</button>" : ''
|
||||
"<div class='code'>#{cshtml}#{jshtml}#{button}<br class='clear' /></div>"
|
||||
end
|
||||
%>
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
||||
<title>CoffeeScript</title>
|
||||
<link rel="stylesheet" type="text/css" href="documentation/css/docs.css" />
|
||||
<link rel="stylesheet" type="text/css" href="documentation/css/idle.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1><sub style="font-size: 100px;">☕</sub> CoffeeScript</h1>
|
||||
|
||||
<p>
|
||||
CoffeeScript is a little language that compiles into JavaScript. Think
|
||||
of it as JavaScript's less ostentatious kid brother — the same genes,
|
||||
roughly the same height, but a different sense of style. Apart from a handful of
|
||||
bonus goodies, statements in CoffeeScript correspond one-to-one with their
|
||||
equivalent in JavaScript, it's just another way of saying it.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Disclaimer:</b>
|
||||
CoffeeScript is just for fun and seriously alpha. I'm sure that there are still
|
||||
plenty of holes in the lexer and leaks in the syntax. <i>There is no guarantee,
|
||||
explicit or implied, of its suitability for any purpose.</i> That said,
|
||||
it compiles into clean JavaScript (the good parts) that can use existing
|
||||
JavaScript libraries seamlessly, and passes through
|
||||
<a href="http://www.jslint.com/">JSLint</a> without warnings. The compiled
|
||||
output is quite readable — pretty-printed, with comments
|
||||
preserved intact.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Latest Version:</b>
|
||||
<a href="http://gemcutter.org/gems/coffee-script">0.2.0</a>
|
||||
</p>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<p>
|
||||
<a href="#overview">Mini Overview</a><br />
|
||||
<a href="#installation">Installation and Usage</a><br />
|
||||
<a href="#whitespace">Significant Whitespace</a><br />
|
||||
<a href="#functions">Functions and Invocation</a><br />
|
||||
<a href="#assignment">Assignment</a><br />
|
||||
<a href="#objects_and_arrays">Objects and Arrays</a><br />
|
||||
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br />
|
||||
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br />
|
||||
<a href="#existence">The Existence Operator</a><br />
|
||||
<a href="#aliases">Aliases</a><br />
|
||||
<a href="#splats">Splats</a><br />
|
||||
<a href="#while">While Loops</a><br />
|
||||
<a href="#comprehensions">Comprehensions (Arrays, Objects, and Ranges)</a><br />
|
||||
<a href="#slice_splice">Array Slicing and Splicing with Ranges</a><br />
|
||||
<a href="#expressions">Everything is an Expression</a><br />
|
||||
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br />
|
||||
<a href="#blocks">Blocks</a><br />
|
||||
<a href="#embedded">Embedded JavaScript</a><br />
|
||||
<a href="#switch">Switch/When/Else</a><br />
|
||||
<a href="#try">Try/Catch/Finally</a><br />
|
||||
<a href="#strings">Multiline Strings</a><br />
|
||||
<a href="#resources">Resources</a><br />
|
||||
<a href="#contributing">Contributing</a><br />
|
||||
<a href="#change_log">Change Log</a><br />
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Mini Overview</h2>
|
||||
|
||||
<p><i>CoffeeScript on the left, compiled JavaScript output on the right.</i></p>
|
||||
|
||||
<%= code_for('overview', 'cubed_list') %>
|
||||
|
||||
<p>
|
||||
For a longer CoffeeScript example, check out
|
||||
<a href="documentation/underscore.html">Underscore.coffee</a>, a port
|
||||
of <a href="http://documentcloud.github.com/underscore/">Underscore.js</a>
|
||||
to CoffeeScript, which, when compiled, can pass the complete Underscore test suite.
|
||||
Or, clone the source and take a look in the
|
||||
<a href="http://github.com/jashkenas/coffee-script/tree/master/examples/">examples</a> folder.
|
||||
</p>
|
||||
|
||||
<h2 id="installation">Installation and Usage</h2>
|
||||
|
||||
<p>
|
||||
The CoffeeScript compiler is written in pure Ruby, and is available
|
||||
as a Ruby Gem.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
gem install coffee-script</pre>
|
||||
|
||||
<p>
|
||||
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>
|
||||
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
|
||||
can be customized with the following options:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="25%"><code>-i, --interactive</code></td>
|
||||
<td>
|
||||
Launch an interactive CoffeeScript session.
|
||||
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
|
||||
</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>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-o, --output [DIR]</code></td>
|
||||
<td>
|
||||
Write out all compiled JavaScript files into the specified directory.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-w, --watch</code></td>
|
||||
<td>
|
||||
Watch the modification times of the coffee-scripts, recompiling as
|
||||
soon as a change occurs.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-p, --print</code></td>
|
||||
<td>
|
||||
Instead of writing out the JavaScript as a file, print it
|
||||
directly to <b>stdout</b>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-l, --lint</code></td>
|
||||
<td>
|
||||
If the <tt>jsl</tt> (JavaScript Lint) command is installed, use it
|
||||
to check the compilation of a CoffeeScript file. (Handy in
|
||||
conjunction with <tt>--watch</tt>)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-e, --eval</code></td>
|
||||
<td>
|
||||
Compile and print a little snippet of CoffeeScript directly from the
|
||||
command line (or from <b>stdin</b>). For example:<br /><tt>coffee -e "square: x => x * x"</tt>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-t, --tokens</code></td>
|
||||
<td>
|
||||
Instead of parsing the CoffeeScript, just lex it, and print out the
|
||||
token stream: <tt>[:IDENTIFIER, "square"], [":", ":"], [:PARAM, "x"]</tt> ...
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-v, --verbose</code></td>
|
||||
<td>
|
||||
As the JavaScript is being generated, print out every step of code
|
||||
generation, including lexical scope and the node in the
|
||||
AST.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-n, --no-wrap</code></td>
|
||||
<td>
|
||||
Compile the JavaScript without the top-level function safety wrapper
|
||||
or var declarations, for situations where you want to add every
|
||||
variable to global scope.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--install-bundle</code></td>
|
||||
<td>
|
||||
Install the TextMate bundle for CoffeeScript syntax highlighting.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<b>Examples:</b>
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
coffee path/to/script.coffee
|
||||
coffee --interactive
|
||||
coffee --watch --lint experimental.coffee
|
||||
coffee --print app/scripts/*.coffee > concatenation.js</pre>
|
||||
|
||||
<h2>Language Reference</h2>
|
||||
|
||||
<p>
|
||||
<i>
|
||||
This reference is structured so that it can be read from top to bottom,
|
||||
if you like. Later sections use ideas and syntax previously introduced.
|
||||
Familiarity with JavaScript is assumed.
|
||||
In all of the following examples, the source CoffeeScript is provided on
|
||||
the left, and the direct compilation into JavaScript is on the right.
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<p id="whitespace">
|
||||
<b class="header">Significant Whitespace</b>
|
||||
CoffeeScript uses Python-style significant whitespace: You don't need to
|
||||
use semicolons <tt>;</tt> to terminate expressions, ending
|
||||
the line will do just as well. Semicolons can still be used to fit
|
||||
multiple expressions onto a single line. Instead of using curly braces
|
||||
<tt>{ }</tt> to delimit blocks of code (like <a href="#functions">functions</a>,
|
||||
<a href="#conditionals">if-statements</a>,
|
||||
<a href="#switch">switch</a>, and <a href="#try">try/catch</a>),
|
||||
use indentation.
|
||||
</p>
|
||||
|
||||
<p id="functions">
|
||||
<b class="header">Functions and Invocation</b>
|
||||
Functions are defined by a list of parameters, an arrow, and the
|
||||
function body. The empty function looks like this: <tt>=></tt>. All
|
||||
functions in CoffeeScript are named, for the benefit of debug messages.
|
||||
</p>
|
||||
<%= code_for('functions', 'cube(5)') %>
|
||||
|
||||
<p id="assignment">
|
||||
<b class="header">Assignment</b>
|
||||
Use a colon <tt>:</tt> to assign, as in
|
||||
<a href="http://json.org">JSON</a>. Equal signs are only needed for
|
||||
mathy things.
|
||||
</p>
|
||||
<%= code_for('assignment', 'greeting') %>
|
||||
<p>
|
||||
Declarations of new variables are pushed up to the top of the nearest
|
||||
lexical scope, so that assignment may always be performed within expressions.
|
||||
</p>
|
||||
|
||||
<p id="objects_and_arrays">
|
||||
<b class="header">Objects and Arrays</b>
|
||||
Object and Array literals look very similar to their JavaScript cousins.
|
||||
When you spread out each assignment on a separate line, the commas are
|
||||
optional. In this way, assigning object properties looks the same as
|
||||
assigning local variables, and can be moved around freely. You can mix
|
||||
and match the two styles.
|
||||
</p>
|
||||
<%= code_for('objects_and_arrays', 'song.join(",")') %>
|
||||
|
||||
<p id="lexical_scope">
|
||||
<b class="header">Lexical Scoping and Variable Safety</b>
|
||||
The CoffeeScript compiler takes care to make sure that all of your variables
|
||||
are properly declared within lexical scope — you never need to write
|
||||
<tt>var</tt> yourself.
|
||||
</p>
|
||||
<%= code_for('scope', 'new_num') %>
|
||||
<p>
|
||||
Notice how the all of the variable declarations have been pushed up to
|
||||
the top of the closest scope, the first time they appear.
|
||||
<b>num</b> is not redeclared within the inner function, because it's
|
||||
already in scope; the <b>new_num</b> within the function, on the other hand,
|
||||
should not be able to change the value of the external variable of the same name, and
|
||||
therefore has a declaration of its own.
|
||||
</p>
|
||||
<p>
|
||||
Although suppressed within this documentation for clarity, all
|
||||
CoffeeScript output is wrapped in an anonymous function:
|
||||
<tt>(function(){ ... })();</tt> This safety wrapper, combined with the
|
||||
automatic generation of the <tt>var</tt> keyword, make it exceedingly difficult
|
||||
to pollute the global namespace by accident. If you'd like to create
|
||||
global variables, attach them as properties on <b>window</b>,
|
||||
or on the <b>exports</b> object in CommonJS.
|
||||
</p>
|
||||
|
||||
<p id="conditionals">
|
||||
<b class="header">Conditionals, Ternaries, and Conditional Assignment</b>
|
||||
<b>If/else</b> statements can be written without the use of parentheses and
|
||||
curly brackets. As with functions and other block expressions,
|
||||
multi-line conditionals are delimited by indentation. There's also a handy
|
||||
postfix form, with the <tt>if</tt> or <tt>unless</tt> at the end.
|
||||
</p>
|
||||
<p>
|
||||
CoffeeScript will compile <b>if</b> statements using the ternary operator
|
||||
when possible, to make it easier to use the result as an expression.
|
||||
</p>
|
||||
<%= code_for('conditionals') %>
|
||||
<p>
|
||||
The conditional assignment operators are included: <tt>||=</tt>,
|
||||
which only assigns a value to a variable if the variable's current value
|
||||
is falsy, and <tt>&&=</tt>, which only replaces the value of
|
||||
truthy variables.
|
||||
</p>
|
||||
|
||||
<p id="existence">
|
||||
<b class="header">The Existence Operator</b>
|
||||
It's a little difficult to check for the existence of a variable in
|
||||
JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero,
|
||||
the empty string, and false. The existence operator <tt>?</tt> returns true unless
|
||||
a variable is <b>null</b> or <b>undefined</b>, which makes it analogous
|
||||
to Ruby's <tt>nil?</tt>
|
||||
</p>
|
||||
<%= code_for('existence') %>
|
||||
|
||||
<p id="aliases">
|
||||
<b class="header">Aliases</b>
|
||||
Because the <tt>==</tt> operator frequently causes undesirable coercion,
|
||||
is intransitive, and has a different meaning than in other languages,
|
||||
CoffeeScript compiles <tt>==</tt> into <tt>===</tt>, and <tt>!=</tt> into
|
||||
<tt>!==</tt>.
|
||||
In addition, <tt>is</tt> compiles into <tt>===</tt>,
|
||||
and <tt>isnt</tt> into <tt>!==</tt>.
|
||||
</p>
|
||||
<p>
|
||||
You can use <tt>not</tt> as an alias for <tt>!</tt>.
|
||||
</p>
|
||||
<p>
|
||||
For logic, <tt>and</tt> compiles to <tt>&&</tt>, and <tt>or</tt>
|
||||
into <tt>||</tt>.
|
||||
</p>
|
||||
<p>
|
||||
Instead of a newline or semicolon, <tt>then</tt> can be used to separate
|
||||
conditions from expressions, in <b>while</b>,
|
||||
<b>if</b>/<b>else</b>, and <b>switch</b>/<b>when</b> statements.
|
||||
</p>
|
||||
<p>
|
||||
As in <a href="http://yaml.org/">YAML</a>, <tt>on</tt> and <tt>yes</tt>
|
||||
are the same as boolean <tt>true</tt>, while <tt>off</tt> and <tt>no</tt> are boolean <tt>false</tt>.
|
||||
</p>
|
||||
<p>
|
||||
For single-line statements, <tt>unless</tt> can be used as the inverse of <tt>if</tt>.
|
||||
</p>
|
||||
<%= code_for('aliases') %>
|
||||
|
||||
<p id="splats">
|
||||
<b class="header">Splats</b>
|
||||
The JavaScript <b>arguments object</b> is a useful way to work with
|
||||
functions that accept variable numbers of arguments. CoffeeScript provides
|
||||
splats <tt>*</tt>, both for function definition as well as invocation,
|
||||
making variable arguments a little bit more palatable.
|
||||
</p>
|
||||
<%= code_for('splats', true) %>
|
||||
|
||||
<p id="while">
|
||||
<b class="header">While Loops</b>
|
||||
The only low-level loop that CoffeeScript provides is the while loop.
|
||||
</p>
|
||||
<%= code_for('while') %>
|
||||
<p>
|
||||
Other JavaScript loops, such as <b>for</b> loops and <b>do-while</b> loops
|
||||
can be mimicked by variations on <b>while</b>, but the hope is that you
|
||||
won't need to do that with CoffeeScript, either because you're using
|
||||
<b>each</b> (<b>forEach</b>) style iterators, or...
|
||||
</p>
|
||||
|
||||
<p id="comprehensions">
|
||||
<b class="header">Comprehensions (Arrays, Objects, and Ranges)</b>
|
||||
For your looping needs, CoffeeScript provides array comprehensions
|
||||
similar to Python's. They replace (and compile into) <b>for</b> loops, with
|
||||
optional guard clauses and the value of the current array index.
|
||||
Unlike for loops, array comprehensions are expressions, and can be returned
|
||||
and assigned. They should be able to handle most places where you otherwise
|
||||
would use a loop, <b>each</b>/<b>forEach</b>, <b>map</b>, or <b>select</b>/<b>filter</b>.
|
||||
</p>
|
||||
<%= code_for('array_comprehensions') %>
|
||||
<p>
|
||||
If you know the start and end of your loop, or would like to step through
|
||||
in fixed-size increments, you can use a range to specify the start and
|
||||
end of your comprehension:
|
||||
</p>
|
||||
<%= code_for('range_comprehensions') %>
|
||||
<p>
|
||||
Comprehensions can also be used to iterate over the values and keys in
|
||||
an object:
|
||||
</p>
|
||||
<%= code_for('object_comprehensions', 'ages.join(", ")') %>
|
||||
|
||||
<p id="slice_splice">
|
||||
<b class="header">Array Slicing and Splicing with Ranges</b>
|
||||
CoffeeScript borrows Ruby's
|
||||
<a href="http://ruby-doc.org/core/classes/Range.html">range syntax</a>
|
||||
for extracting slices of arrays. With two dots (<tt>3..5</tt>), the range
|
||||
is inclusive: the first argument is the index of the first element in
|
||||
the slice, and the second is the index of the last one. Three dots signify
|
||||
a range that excludes the end.
|
||||
</p>
|
||||
<%= code_for('slices', 'numbers_copy') %>
|
||||
<p>
|
||||
The same syntax can be used with assignment to replace a segment of an
|
||||
array with new values (to splice it).
|
||||
</p>
|
||||
<%= code_for('splices', 'numbers') %>
|
||||
|
||||
<p id="expressions">
|
||||
<b class="header">Everything is an Expression (at least, as much as possible)</b>
|
||||
You might have noticed how even though we don't add return statements
|
||||
to CoffeeScript functions, they nonetheless return their final value.
|
||||
The CoffeeScript compiler tries to make sure that all statements in the
|
||||
language can be used as expressions. Watch how the <tt>return</tt> gets
|
||||
pushed down into each possible branch of execution, in the function
|
||||
below.
|
||||
</p>
|
||||
<%= code_for('expressions', 'eldest') %>
|
||||
<p>
|
||||
Because variable declarations occur at the top of scope, assignment can
|
||||
be used within expressions, even for variables that haven't been seen before:
|
||||
</p>
|
||||
<%= code_for('expressions_assignment', 'six') %>
|
||||
<p>
|
||||
Things that would otherwise be statements in JavaScript, when used
|
||||
as part of an expression in CoffeeScript, are converted into expressions
|
||||
by wrapping them in a closure. This lets you do useful things, like assign
|
||||
the result of a comprehension to a variable:
|
||||
</p>
|
||||
<%= code_for('expressions_comprehension', 'globals') %>
|
||||
<p>
|
||||
As well as silly things, like passing a <b>try/catch</b> statement directly
|
||||
into a function call:
|
||||
</p>
|
||||
<%= code_for('expressions_try', true) %>
|
||||
|
||||
<p id="inheritance">
|
||||
<b class="header">Inheritance, and Calling Super from a Subclass</b>
|
||||
JavaScript's prototypal inheritance has always been a bit of a
|
||||
brain-bender, with a whole family tree of libraries that provide a cleaner
|
||||
syntax for classical inheritance on top of JavaScript's prototypes:
|
||||
<a href="http://code.google.com/p/base2/">Base2</a>,
|
||||
<a href="http://prototypejs.org/">Prototype.js</a>,
|
||||
<a href="http://jsclass.jcoglan.com/">JS.Class</a>, etc.
|
||||
The libraries provide syntactic sugar, but the built-in inheritance would
|
||||
be completely usable if it weren't for a couple of small exceptions:
|
||||
it's awkward to call <b>super</b> (the prototype object's
|
||||
implementation of the current function), and it's awkward to correctly
|
||||
set the prototype chain. CoffeeScript provides <tt>extends</tt>
|
||||
to help with prototype setup, and converts
|
||||
<tt>super()</tt> calls into calls against the immediate ancestor's
|
||||
method of the same name.
|
||||
</p>
|
||||
<%= code_for('super', true) %>
|
||||
|
||||
<p id="blocks">
|
||||
<b class="header">Blocks</b>
|
||||
Many common looping functions (in Prototype, jQuery, and Underscore,
|
||||
for example) take a single function as their final argument. To make
|
||||
final functions easier to pass, CoffeeScript includes block syntax,
|
||||
so you don't have to close the parentheses on the other side.
|
||||
</p>
|
||||
<%= code_for('blocks') %>
|
||||
|
||||
<p id="embedded">
|
||||
<b class="header">Embedded JavaScript</b>
|
||||
If you ever need to interpolate literal JavaScript snippets, you can
|
||||
use backticks to pass JavaScript straight through.
|
||||
</p>
|
||||
<%= code_for('embedded', 'hi()') %>
|
||||
|
||||
<p id="switch">
|
||||
<b class="header">Switch/When/Else</b>
|
||||
<b>Switch</b> statements in JavaScript are rather broken. You can only
|
||||
do comparisons based on string equality, and need to remember to <b>break</b> at the end of
|
||||
every <b>case</b> statement to avoid accidentally falling through to
|
||||
the default case. CoffeeScript compiles <b>switch</b> statements into JavaScript if-else chains, allowing you to
|
||||
compare any object (via <b>===</b>), preventing fall-through, and resulting
|
||||
in a returnable, assignable expression. The format is: <tt>switch</tt> condition,
|
||||
<tt>when</tt> clauses, <tt>else</tt> the default case.
|
||||
</p>
|
||||
<%= code_for('switch') %>
|
||||
|
||||
<p id="try">
|
||||
<b class="header">Try/Catch/Finally</b>
|
||||
Try/catch statements are just about the same as JavaScript (although
|
||||
they work as expressions).
|
||||
</p>
|
||||
<%= code_for('try') %>
|
||||
|
||||
<p id="strings">
|
||||
<b class="header">Multiline Strings</b>
|
||||
Multiline strings are allowed in CoffeeScript.
|
||||
</p>
|
||||
<%= code_for('strings', 'moby_dick') %>
|
||||
|
||||
<h2 id="resources">Resources</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="http://github.com/jashkenas/coffee-script/">Source Code</a><br />
|
||||
Use <tt>bin/coffee</tt> to test your changes, or <tt>rake gem:install</tt> to
|
||||
create and install a custom version of the gem. If you're hacking on the
|
||||
parser, use <tt>rake build:parser</tt> to rebuild it.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://github.com/jashkenas/coffee-script/issues">Bugs and Feature Requests</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://github.com/jnicklas/bistro_car">BistroCar</a><br />
|
||||
A Rails plugin by
|
||||
<a href="http://github.com/jnicklas">Jonas Nicklas</a>
|
||||
that includes CoffeeScript helpers,
|
||||
bundling and minification.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="contributing">Contributing</h2>
|
||||
|
||||
<p>
|
||||
Here's a wish list of things that would be wonderful to have in
|
||||
CoffeeScript:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
A clean, safe syntax for manipulating the prototype chain, and performing
|
||||
inheritance. <a href="#inheritance"><b>extends</b> and <b>super</b></a> are the start of this, but
|
||||
aren't a complete answer.
|
||||
</li>
|
||||
<li>
|
||||
A CoffeeScript version of the compiler, perhaps using Alessandro Warth's
|
||||
<a href="http://tinlizzie.org/ometa/">OMeta</a>.
|
||||
</li>
|
||||
<li>
|
||||
Test cases for any syntax errors you encounter that you think CoffeeScript
|
||||
should be able to compile properly.
|
||||
</li>
|
||||
<li>
|
||||
A tutorial that introduces CoffeeScript from the ground up for folks
|
||||
without knowledge of JavaScript.
|
||||
</li>
|
||||
<li>
|
||||
Integration with Processing.js's JavaScript API (this would depend on
|
||||
having a JavaScript version of the compiler).
|
||||
</li>
|
||||
<li>
|
||||
A lot of the code generation in <tt>nodes.rb</tt> gets into messy
|
||||
string manipulation. Techniques for cleaning this up across the board
|
||||
would be appreciated.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="change_log">Change Log</h2>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.2.0</b>
|
||||
Major release. Significant whitespace. Better statement-to-expression
|
||||
conversion. Splats. Splice literals. Object comprehensions. Blocks.
|
||||
The existence operator. Many thanks to all the folks who posted issues,
|
||||
with special thanks to
|
||||
<a href="http://github.com/kamatsu">Liam O'Connor-Davis</a> for whitespace
|
||||
and expression help.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.6</b>
|
||||
Bugfix for running <tt>coffee --interactive</tt> and <tt>--run</tt>
|
||||
from outside of the CoffeeScript directory. Bugfix for nested
|
||||
function/if-statements.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.5</b>
|
||||
Array slice literals and array comprehensions can now both take Ruby-style
|
||||
ranges to specify the start and end. JavaScript variable declaration is
|
||||
now pushed up to the top of the scope, making all assignment statements into
|
||||
expressions. You can use <tt>\</tt> to escape newlines.
|
||||
The <tt>coffee-script</tt> command is now called <tt>coffee</tt>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.4</b>
|
||||
The official CoffeeScript extension is now <tt>.coffee</tt> instead of
|
||||
<tt>.cs</tt>, which properly belongs to
|
||||
<a href="http://en.wikipedia.org/wiki/C_Sharp_(programming_language)">C#</a>.
|
||||
Due to popular demand, you can now also use <tt>=</tt> to assign. Unlike
|
||||
JavaScript, <tt>=</tt> can also be used within object literals, interchangeably
|
||||
with <tt>:</tt>. Made a grammatical fix for chained function calls
|
||||
like <tt>func(1)(2)(3)(4)</tt>. Inheritance and super no longer use
|
||||
<tt>__proto__</tt>, so they should be IE-compatible now.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.3</b>
|
||||
The <tt>coffee</tt> command now includes <tt>--interactive</tt>,
|
||||
which launches an interactive CoffeeScript session, and <tt>--run</tt>,
|
||||
which directly compiles and executes a script. Both options depend on a
|
||||
working installation of Narwhal.
|
||||
The <tt>aint</tt> keyword has been replaced by <tt>isnt</tt>, which goes
|
||||
together a little smoother with <tt>is</tt>.
|
||||
Quoted strings are now allowed as identifiers within object literals: eg.
|
||||
<tt>{"5+5": 10}</tt>.
|
||||
All assignment operators now use a colon: <tt>+:</tt>, <tt>-:</tt>,
|
||||
<tt>*:</tt>, etc.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.2</b>
|
||||
Fixed a bug with calling <tt>super()</tt> through more than one level of
|
||||
inheritance, with the re-addition of the <tt>extends</tt> keyword.
|
||||
Added experimental <a href="http://narwhaljs.org/">Narwhal</a>
|
||||
support (as a Tusk package), contributed by
|
||||
<a href="http://tlrobinson.net/">Tom Robinson</a>, including
|
||||
<b>bin/cs</b> as a CoffeeScript REPL and interpreter.
|
||||
New <tt>--no-wrap</tt> option to suppress the safety function
|
||||
wrapper.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.1</b>
|
||||
Added <tt>instanceof</tt> and <tt>typeof</tt> as operators.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b class="header" style="margin-top: 20px;">0.1.0</b>
|
||||
Initial CoffeeScript release.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
13
documentation/js/aliases.js
Normal file
13
documentation/js/aliases.js
Normal file
@@ -0,0 +1,13 @@
|
||||
(function(){
|
||||
var volume;
|
||||
if (ignition === true) {
|
||||
launch();
|
||||
}
|
||||
if (band !== spinal_tap) {
|
||||
volume = 10;
|
||||
}
|
||||
if (!(answer === false)) {
|
||||
let_the_wild_rumpus_begin();
|
||||
}
|
||||
car.speed < speed_limit ? accelerate() : null;
|
||||
})();
|
||||
34
documentation/js/array_comprehensions.js
Normal file
34
documentation/js/array_comprehensions.js
Normal file
@@ -0,0 +1,34 @@
|
||||
(function(){
|
||||
var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, food, lunch, roid, roid2;
|
||||
// Eat lunch.
|
||||
lunch = (function() {
|
||||
__a = ['toast', 'cheese', 'wine'];
|
||||
__c = [];
|
||||
for (__b in __a) {
|
||||
if (__a.hasOwnProperty(__b)) {
|
||||
food = __a[__b];
|
||||
__d = eat(food);
|
||||
__c.push(__d);
|
||||
}
|
||||
}
|
||||
return __c;
|
||||
})();
|
||||
// Naive collision detection.
|
||||
__e = asteroids;
|
||||
for (__f in __e) {
|
||||
if (__e.hasOwnProperty(__f)) {
|
||||
roid = __e[__f];
|
||||
__h = asteroids;
|
||||
for (__i in __h) {
|
||||
if (__h.hasOwnProperty(__i)) {
|
||||
roid2 = __h[__i];
|
||||
if (roid !== roid2) {
|
||||
if (roid.overlaps(roid2)) {
|
||||
roid.explode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
5
documentation/js/assignment.js
Normal file
5
documentation/js/assignment.js
Normal file
@@ -0,0 +1,5 @@
|
||||
(function(){
|
||||
var difficulty, greeting;
|
||||
greeting = "Hello CoffeeScript";
|
||||
difficulty = 0.5;
|
||||
})();
|
||||
8
documentation/js/blocks.js
Normal file
8
documentation/js/blocks.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function(){
|
||||
$('table.list').each(function(table) {
|
||||
return $('tr.account', table).each(function(row) {
|
||||
row.show();
|
||||
return row.highlight();
|
||||
});
|
||||
});
|
||||
})();
|
||||
12
documentation/js/conditionals.js
Normal file
12
documentation/js/conditionals.js
Normal file
@@ -0,0 +1,12 @@
|
||||
(function(){
|
||||
var date, mood;
|
||||
if (singing) {
|
||||
mood = greatly_improved;
|
||||
}
|
||||
if (happy && knows_it) {
|
||||
claps_hands();
|
||||
cha_cha_cha();
|
||||
}
|
||||
date = friday ? sue : jill;
|
||||
expensive = expensive || do_the_math();
|
||||
})();
|
||||
6
documentation/js/embedded.js
Normal file
6
documentation/js/embedded.js
Normal file
@@ -0,0 +1,6 @@
|
||||
(function(){
|
||||
var hi;
|
||||
hi = function() {
|
||||
return [document.title, "Hello JavaScript"].join(": ");
|
||||
};
|
||||
})();
|
||||
6
documentation/js/existence.js
Normal file
6
documentation/js/existence.js
Normal file
@@ -0,0 +1,6 @@
|
||||
(function(){
|
||||
var solipsism;
|
||||
if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) {
|
||||
solipsism = true;
|
||||
}
|
||||
})();
|
||||
13
documentation/js/expressions.js
Normal file
13
documentation/js/expressions.js
Normal file
@@ -0,0 +1,13 @@
|
||||
(function(){
|
||||
var eldest, grade;
|
||||
grade = function grade(student) {
|
||||
if (student.excellent_work) {
|
||||
return "A+";
|
||||
} else if (student.okay_stuff) {
|
||||
return student.tried_hard ? "B" : "B-";
|
||||
} else {
|
||||
return "C";
|
||||
}
|
||||
};
|
||||
eldest = 24 > 21 ? "Liz" : "Ike";
|
||||
})();
|
||||
4
documentation/js/expressions_assignment.js
Normal file
4
documentation/js/expressions_assignment.js
Normal file
@@ -0,0 +1,4 @@
|
||||
(function(){
|
||||
var one, six, three, two;
|
||||
six = (one = 1) + (two = 2) + (three = 3);
|
||||
})();
|
||||
16
documentation/js/expressions_comprehension.js
Normal file
16
documentation/js/expressions_comprehension.js
Normal file
@@ -0,0 +1,16 @@
|
||||
(function(){
|
||||
var __a, __b, __c, globals, name, property;
|
||||
// The first ten global properties.
|
||||
globals = ((function() {
|
||||
__a = window;
|
||||
__b = [];
|
||||
for (name in __a) {
|
||||
if (__a.hasOwnProperty(name)) {
|
||||
property = __a[name];
|
||||
__c = name;
|
||||
__b.push(__c);
|
||||
}
|
||||
}
|
||||
return __b;
|
||||
})()).slice(0, 10);
|
||||
})();
|
||||
9
documentation/js/expressions_try.js
Normal file
9
documentation/js/expressions_try.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
alert((function() {
|
||||
try {
|
||||
return nonexistent / undefined;
|
||||
} catch (error) {
|
||||
return "The error is: " + error;
|
||||
}
|
||||
})());
|
||||
})();
|
||||
9
documentation/js/functions.js
Normal file
9
documentation/js/functions.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
var cube, square;
|
||||
square = function square(x) {
|
||||
return x * x;
|
||||
};
|
||||
cube = function cube(x) {
|
||||
return square(x) * x;
|
||||
};
|
||||
})();
|
||||
7
documentation/js/intro.js
Normal file
7
documentation/js/intro.js
Normal file
@@ -0,0 +1,7 @@
|
||||
(function(){
|
||||
|
||||
// CoffeeScript on the left, JS on the right.
|
||||
var square = function(x) {
|
||||
return x * x;
|
||||
};
|
||||
})();
|
||||
20
documentation/js/object_comprehensions.js
Normal file
20
documentation/js/object_comprehensions.js
Normal file
@@ -0,0 +1,20 @@
|
||||
(function(){
|
||||
var __a, __b, __c, age, ages, child, years_old;
|
||||
years_old = {
|
||||
max: 10,
|
||||
ida: 9,
|
||||
tim: 11
|
||||
};
|
||||
ages = (function() {
|
||||
__a = years_old;
|
||||
__b = [];
|
||||
for (child in __a) {
|
||||
if (__a.hasOwnProperty(child)) {
|
||||
age = __a[child];
|
||||
__c = child + " is " + age;
|
||||
__b.push(__c);
|
||||
}
|
||||
}
|
||||
return __b;
|
||||
})();
|
||||
})();
|
||||
10
documentation/js/objects_and_arrays.js
Normal file
10
documentation/js/objects_and_arrays.js
Normal file
@@ -0,0 +1,10 @@
|
||||
(function(){
|
||||
var ages, matrix, song;
|
||||
song = ["do", "re", "mi", "fa", "so"];
|
||||
ages = {
|
||||
max: 10,
|
||||
ida: 9,
|
||||
tim: 11
|
||||
};
|
||||
matrix = [1, 0, 1, 0, 0, 1, 1, 1, 0];
|
||||
})();
|
||||
47
documentation/js/overview.js
Normal file
47
documentation/js/overview.js
Normal file
@@ -0,0 +1,47 @@
|
||||
(function(){
|
||||
var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, race, square;
|
||||
// Assignment:
|
||||
number = 42;
|
||||
opposite_day = true;
|
||||
// Conditions:
|
||||
if (opposite_day) {
|
||||
number = -42;
|
||||
}
|
||||
// Functions:
|
||||
square = function square(x) {
|
||||
return x * x;
|
||||
};
|
||||
// Arrays:
|
||||
list = [1, 2, 3, 4, 5];
|
||||
// Objects:
|
||||
math = {
|
||||
root: Math.sqrt,
|
||||
square: square,
|
||||
cube: function cube(x) {
|
||||
return x * square(x);
|
||||
}
|
||||
};
|
||||
// Splats:
|
||||
race = function race(winner) {
|
||||
var runners;
|
||||
runners = Array.prototype.slice.call(arguments, 1);
|
||||
return print(winner, runners);
|
||||
};
|
||||
// Existence:
|
||||
if ((typeof elvis !== "undefined" && elvis !== null)) {
|
||||
alert("I knew it!");
|
||||
}
|
||||
// Array comprehensions:
|
||||
cubed_list = (function() {
|
||||
__a = list;
|
||||
__c = [];
|
||||
for (__b in __a) {
|
||||
if (__a.hasOwnProperty(__b)) {
|
||||
num = __a[__b];
|
||||
__d = math.cube(num);
|
||||
__c.push(__d);
|
||||
}
|
||||
}
|
||||
return __c;
|
||||
})();
|
||||
})();
|
||||
8
documentation/js/punctuation.js
Normal file
8
documentation/js/punctuation.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function(){
|
||||
|
||||
// Comments start with hash marks. Periods mark the end of a block.
|
||||
var left_hand = raining ? umbrella : parasol;
|
||||
// To signal the beginning of the next expression,
|
||||
// use "then", or a newline.
|
||||
left_hand = raining ? umbrella : parasol;
|
||||
})();
|
||||
9
documentation/js/range_comprehensions.js
Normal file
9
documentation/js/range_comprehensions.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
var __a, __b, __c, __d, __e, dozen_eggs, i;
|
||||
__d = 0;
|
||||
__e = eggs.length;
|
||||
for (__c=0, i=__d; (__d <= __e ? i < __e : i > __e); (__d <= __e ? i += 12 : i -= 12), __c++) {
|
||||
dozen_eggs = eggs.slice(i, i + 12);
|
||||
deliver(new egg_carton(dozen));
|
||||
}
|
||||
})();
|
||||
10
documentation/js/scope.js
Normal file
10
documentation/js/scope.js
Normal file
@@ -0,0 +1,10 @@
|
||||
(function(){
|
||||
var change_numbers, new_num, num;
|
||||
num = 1;
|
||||
change_numbers = function change_numbers() {
|
||||
var new_num;
|
||||
new_num = -1;
|
||||
return num = 10;
|
||||
};
|
||||
new_num = change_numbers();
|
||||
})();
|
||||
6
documentation/js/slices.js
Normal file
6
documentation/js/slices.js
Normal file
@@ -0,0 +1,6 @@
|
||||
(function(){
|
||||
var numbers, numbers_copy, three_to_six;
|
||||
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
three_to_six = numbers.slice(3, 6 + 1);
|
||||
numbers_copy = numbers.slice(0, numbers.length);
|
||||
})();
|
||||
16
documentation/js/splats.js
Normal file
16
documentation/js/splats.js
Normal file
@@ -0,0 +1,16 @@
|
||||
(function(){
|
||||
var contenders, gold, medalists, silver, the_field;
|
||||
gold = silver = the_field = "unknown";
|
||||
medalists = function medalists(first, second) {
|
||||
var rest;
|
||||
rest = Array.prototype.slice.call(arguments, 2);
|
||||
gold = first;
|
||||
silver = second;
|
||||
return the_field = rest;
|
||||
};
|
||||
contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"];
|
||||
medalists.apply(this, contenders);
|
||||
alert("Gold: " + gold);
|
||||
alert("Silver: " + silver);
|
||||
alert("The Field: " + the_field);
|
||||
})();
|
||||
5
documentation/js/splices.js
Normal file
5
documentation/js/splices.js
Normal file
@@ -0,0 +1,5 @@
|
||||
(function(){
|
||||
var numbers;
|
||||
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6]));
|
||||
})();
|
||||
9
documentation/js/strings.js
Normal file
9
documentation/js/strings.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
var moby_dick;
|
||||
moby_dick = "Call me Ishmael. Some years ago -- \
|
||||
never mind how long precisely -- having little \
|
||||
or no money in my purse, and nothing particular \
|
||||
to interest me on shore, I thought I would sail \
|
||||
about a little and see the watery part of the \
|
||||
world...";
|
||||
})();
|
||||
32
documentation/js/super.js
Normal file
32
documentation/js/super.js
Normal file
@@ -0,0 +1,32 @@
|
||||
(function(){
|
||||
var Animal, Horse, Snake, sam, tom;
|
||||
Animal = function Animal() {
|
||||
};
|
||||
Animal.prototype.move = function move(meters) {
|
||||
return alert(this.name + " moved " + meters + "m.");
|
||||
};
|
||||
Snake = function Snake(name) {
|
||||
return this.name = name;
|
||||
};
|
||||
Snake.__superClass__ = Animal.prototype;
|
||||
Snake.prototype = new Animal();
|
||||
Snake.prototype.constructor = Snake;
|
||||
Snake.prototype.move = function move() {
|
||||
alert("Slithering...");
|
||||
return Snake.__superClass__.move.call(this, 5);
|
||||
};
|
||||
Horse = function Horse(name) {
|
||||
return this.name = name;
|
||||
};
|
||||
Horse.__superClass__ = Animal.prototype;
|
||||
Horse.prototype = new Animal();
|
||||
Horse.prototype.constructor = Horse;
|
||||
Horse.prototype.move = function move() {
|
||||
alert("Galloping...");
|
||||
return Horse.__superClass__.move.call(this, 45);
|
||||
};
|
||||
sam = new Snake("Sammy the Python");
|
||||
tom = new Horse("Tommy the Palomino");
|
||||
sam.move();
|
||||
tom.move();
|
||||
})();
|
||||
16
documentation/js/switch.js
Normal file
16
documentation/js/switch.js
Normal file
@@ -0,0 +1,16 @@
|
||||
(function(){
|
||||
if (day === "Tuesday") {
|
||||
eat_breakfast();
|
||||
} else if (day === "Wednesday") {
|
||||
go_to_the_park();
|
||||
} else if (day === "Saturday") {
|
||||
if (day === bingo_day) {
|
||||
go_to_bingo();
|
||||
go_dancing();
|
||||
}
|
||||
} else if (day === "Sunday") {
|
||||
go_to_church();
|
||||
} else {
|
||||
go_to_work();
|
||||
}
|
||||
})();
|
||||
10
documentation/js/try.js
Normal file
10
documentation/js/try.js
Normal file
@@ -0,0 +1,10 @@
|
||||
(function(){
|
||||
try {
|
||||
all_hell_breaks_loose();
|
||||
cats_and_dogs_living_together();
|
||||
} catch (error) {
|
||||
print(error);
|
||||
} finally {
|
||||
clean_up();
|
||||
}
|
||||
})();
|
||||
9
documentation/js/while.js
Normal file
9
documentation/js/while.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
while (demand > supply) {
|
||||
sell();
|
||||
restock();
|
||||
}
|
||||
while (supply > demand) {
|
||||
buy();
|
||||
}
|
||||
})();
|
||||
616
documentation/underscore.html
Normal file
616
documentation/underscore.html
Normal file
@@ -0,0 +1,616 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
|
||||
<link rel="stylesheet" type="text/css" media="screen,projection,print" href="css/idle.css" />
|
||||
<title>Underscore.coffee</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
pre.idle {
|
||||
font-family: "Monaco", "Consolas", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre class="idle"><span class="line-numbers"> 1 </span>
|
||||
<span class="line-numbers"> 2 </span> <span class="Comment"><span class="Comment">#</span> Underscore.coffee</span>
|
||||
<span class="line-numbers"> 3 </span> <span class="Comment"><span class="Comment">#</span> (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.</span>
|
||||
<span class="line-numbers"> 4 </span> <span class="Comment"><span class="Comment">#</span> Underscore is freely distributable under the terms of the MIT license.</span>
|
||||
<span class="line-numbers"> 5 </span> <span class="Comment"><span class="Comment">#</span> Portions of Underscore are inspired by or borrowed from Prototype.js,</span>
|
||||
<span class="line-numbers"> 6 </span> <span class="Comment"><span class="Comment">#</span> Oliver Steele's Functional, and John Resig's Micro-Templating.</span>
|
||||
<span class="line-numbers"> 7 </span> <span class="Comment"><span class="Comment">#</span> For all details and documentation:</span>
|
||||
<span class="line-numbers"> 8 </span> <span class="Comment"><span class="Comment">#</span> http://documentcloud.github.com/underscore/</span>
|
||||
<span class="line-numbers"> 9 </span>
|
||||
<span class="line-numbers"> 10 </span>
|
||||
<span class="line-numbers"> 11 </span> <span class="Comment"><span class="Comment">#</span> ------------------------- Baseline setup ---------------------------------</span>
|
||||
<span class="line-numbers"> 12 </span>
|
||||
<span class="line-numbers"> 13 </span> <span class="Comment"><span class="Comment">#</span> Establish the root object, "window" in the browser, or "global" on the server.</span>
|
||||
<span class="line-numbers"> 14 </span> root<span class="Keyword">:</span> <span class="Variable">this</span>
|
||||
<span class="line-numbers"> 15 </span>
|
||||
<span class="line-numbers"> 16 </span>
|
||||
<span class="line-numbers"> 17 </span> <span class="Comment"><span class="Comment">#</span> Save the previous value of the "_" variable.</span>
|
||||
<span class="line-numbers"> 18 </span> previousUnderscore<span class="Keyword">:</span> root._
|
||||
<span class="line-numbers"> 19 </span>
|
||||
<span class="line-numbers"> 20 </span>
|
||||
<span class="line-numbers"> 21 </span> <span class="Comment"><span class="Comment">#</span> If Underscore is called as a function, it returns a wrapped object that</span>
|
||||
<span class="line-numbers"> 22 </span> <span class="Comment"><span class="Comment">#</span> can be used OO-style. This wrapper holds altered versions of all the</span>
|
||||
<span class="line-numbers"> 23 </span> <span class="Comment"><span class="Comment">#</span> underscore functions. Wrapped objects may be chained.</span>
|
||||
<span class="line-numbers"> 24 </span> <span class="FunctionName">wrapper</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 25 </span> <span class="Variable">this</span>._wrapped<span class="Keyword">:</span> obj
|
||||
<span class="line-numbers"> 26 </span> <span class="Variable">this</span>
|
||||
<span class="line-numbers"> 27 </span>
|
||||
<span class="line-numbers"> 28 </span>
|
||||
<span class="line-numbers"> 29 </span> <span class="Comment"><span class="Comment">#</span> Establish the object that gets thrown to break out of a loop iteration.</span>
|
||||
<span class="line-numbers"> 30 </span> breaker<span class="Keyword">:</span> <span class="Keyword">if</span> <span class="Keyword">typeof</span>(StopIteration) <span class="Keyword">is</span> <span class="String"><span class="String">'</span>undefined<span class="String">'</span></span> <span class="Keyword">then</span> <span class="String"><span class="String">'</span>__break__<span class="String">'</span></span> <span class="Keyword">else</span> StopIteration
|
||||
<span class="line-numbers"> 31 </span>
|
||||
<span class="line-numbers"> 32 </span>
|
||||
<span class="line-numbers"> 33 </span> <span class="Comment"><span class="Comment">#</span> Create a safe reference to the Underscore object for reference below.</span>
|
||||
<span class="line-numbers"> 34 </span> _<span class="Keyword">:</span> <span class="FunctionName">root._</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">new</span> <span class="TypeName">wrapper</span>(obj)
|
||||
<span class="line-numbers"> 35 </span>
|
||||
<span class="line-numbers"> 36 </span>
|
||||
<span class="line-numbers"> 37 </span> <span class="Comment"><span class="Comment">#</span> Export the Underscore object for CommonJS.</span>
|
||||
<span class="line-numbers"> 38 </span> <span class="Keyword">if</span> <span class="Keyword">typeof</span>(exports) <span class="Keyword">!</span><span class="Keyword">=</span> <span class="String"><span class="String">'</span>undefined<span class="String">'</span></span> <span class="Keyword">then</span> exports._<span class="Keyword">:</span> _
|
||||
<span class="line-numbers"> 39 </span>
|
||||
<span class="line-numbers"> 40 </span>
|
||||
<span class="line-numbers"> 41 </span> <span class="Comment"><span class="Comment">#</span> Create quick reference variables for speed access to core prototypes.</span>
|
||||
<span class="line-numbers"> 42 </span> slice<span class="Keyword">:</span> Array.prototype.slice
|
||||
<span class="line-numbers"> 43 </span> unshift<span class="Keyword">:</span> Array.prototype.unshift
|
||||
<span class="line-numbers"> 44 </span> toString<span class="Keyword">:</span> Object.prototype.toString
|
||||
<span class="line-numbers"> 45 </span> hasOwnProperty<span class="Keyword">:</span> Object.prototype.hasOwnProperty
|
||||
<span class="line-numbers"> 46 </span> propertyIsEnumerable<span class="Keyword">:</span> Object.prototype.propertyIsEnumerable
|
||||
<span class="line-numbers"> 47 </span>
|
||||
<span class="line-numbers"> 48 </span>
|
||||
<span class="line-numbers"> 49 </span> <span class="Comment"><span class="Comment">#</span> Current version.</span>
|
||||
<span class="line-numbers"> 50 </span> _.VERSION<span class="Keyword">:</span> <span class="String"><span class="String">'</span>0.5.3<span class="String">'</span></span>
|
||||
<span class="line-numbers"> 51 </span>
|
||||
<span class="line-numbers"> 52 </span>
|
||||
<span class="line-numbers"> 53 </span> <span class="Comment"><span class="Comment">#</span> ------------------------ Collection Functions: ---------------------------</span>
|
||||
<span class="line-numbers"> 54 </span>
|
||||
<span class="line-numbers"> 55 </span> <span class="Comment"><span class="Comment">#</span> The cornerstone, an each implementation.</span>
|
||||
<span class="line-numbers"> 56 </span> <span class="Comment"><span class="Comment">#</span> Handles objects implementing forEach, arrays, and raw objects.</span>
|
||||
<span class="line-numbers"> 57 </span> <span class="FunctionName">_.each</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 58 </span> index<span class="Keyword">:</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 59 </span> <span class="Keyword">try</span>
|
||||
<span class="line-numbers"> 60 </span> <span class="Keyword">return</span> obj.forEach(iterator, context) <span class="Keyword">if</span> obj.forEach
|
||||
<span class="line-numbers"> 61 </span> <span class="Keyword">if</span> _.isArray(obj) <span class="Keyword">or</span> _.isArguments(obj)
|
||||
<span class="line-numbers"> 62 </span> <span class="Keyword">return</span> iterator.call(context, obj[i], i, obj) <span class="Keyword">for</span> i <span class="Keyword">in</span> [<span class="Number">0</span>...obj.length]
|
||||
<span class="line-numbers"> 63 </span> iterator.call(context, val, key, obj) <span class="Keyword">for</span> val, key <span class="Keyword">in</span> obj
|
||||
<span class="line-numbers"> 64 </span> <span class="Keyword">catch</span> e
|
||||
<span class="line-numbers"> 65 </span> <span class="Keyword">throw</span> e <span class="Keyword">if</span> e <span class="Keyword">isnt</span> breaker
|
||||
<span class="line-numbers"> 66 </span> obj
|
||||
<span class="line-numbers"> 67 </span>
|
||||
<span class="line-numbers"> 68 </span>
|
||||
<span class="line-numbers"> 69 </span> <span class="Comment"><span class="Comment">#</span> Return the results of applying the iterator to each element. Use JavaScript</span>
|
||||
<span class="line-numbers"> 70 </span> <span class="Comment"><span class="Comment">#</span> 1.6's version of map, if possible.</span>
|
||||
<span class="line-numbers"> 71 </span> <span class="FunctionName">_.map</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 72 </span> <span class="Keyword">return</span> obj.map(iterator, context) <span class="Keyword">if</span> (obj <span class="Keyword">and</span> _.isFunction(obj.map))
|
||||
<span class="line-numbers"> 73 </span> results<span class="Keyword">:</span> []
|
||||
<span class="line-numbers"> 74 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 75 </span> results.push(iterator.call(context, value, index, list))
|
||||
<span class="line-numbers"> 76 </span> results
|
||||
<span class="line-numbers"> 77 </span>
|
||||
<span class="line-numbers"> 78 </span>
|
||||
<span class="line-numbers"> 79 </span> <span class="Comment"><span class="Comment">#</span> Reduce builds up a single result from a list of values. Also known as</span>
|
||||
<span class="line-numbers"> 80 </span> <span class="Comment"><span class="Comment">#</span> inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.</span>
|
||||
<span class="line-numbers"> 81 </span> <span class="FunctionName">_.reduce</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, memo, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 82 </span> <span class="Keyword">return</span> obj.reduce(_.bind(iterator, context), memo) <span class="Keyword">if</span> (obj <span class="Keyword">and</span> _.isFunction(obj.reduce))
|
||||
<span class="line-numbers"> 83 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 84 </span> memo<span class="Keyword">:</span> iterator.call(context, memo, value, index, list)
|
||||
<span class="line-numbers"> 85 </span> memo
|
||||
<span class="line-numbers"> 86 </span>
|
||||
<span class="line-numbers"> 87 </span>
|
||||
<span class="line-numbers"> 88 </span> <span class="Comment"><span class="Comment">#</span> The right-associative version of reduce, also known as foldr. Uses</span>
|
||||
<span class="line-numbers"> 89 </span> <span class="Comment"><span class="Comment">#</span> JavaScript 1.8's version of reduceRight, if available.</span>
|
||||
<span class="line-numbers"> 90 </span> <span class="FunctionName">_.reduceRight</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, memo, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 91 </span> <span class="Keyword">return</span> obj.reduceRight(_.bind(iterator, context), memo) <span class="Keyword">if</span> (obj <span class="Keyword">and</span> _.isFunction(obj.reduceRight))
|
||||
<span class="line-numbers"> 92 </span> _.each(_.clone(_.toArray(obj)).reverse())<span class="FunctionArgument"> value, index </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 93 </span> memo<span class="Keyword">:</span> iterator.call(context, memo, value, index, obj)
|
||||
<span class="line-numbers"> 94 </span> memo
|
||||
<span class="line-numbers"> 95 </span>
|
||||
<span class="line-numbers"> 96 </span>
|
||||
<span class="line-numbers"> 97 </span> <span class="Comment"><span class="Comment">#</span> Return the first value which passes a truth test.</span>
|
||||
<span class="line-numbers"> 98 </span> <span class="FunctionName">_.detect</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 99 </span> result<span class="Keyword">:</span> <span class="BuiltInConstant">null</span>
|
||||
<span class="line-numbers"> 100 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 101 </span> <span class="Keyword">if</span> iterator.call(context, value, index, list)
|
||||
<span class="line-numbers"> 102 </span> result<span class="Keyword">:</span> value
|
||||
<span class="line-numbers"> 103 </span> _.breakLoop()
|
||||
<span class="line-numbers"> 104 </span> result
|
||||
<span class="line-numbers"> 105 </span>
|
||||
<span class="line-numbers"> 106 </span>
|
||||
<span class="line-numbers"> 107 </span> <span class="Comment"><span class="Comment">#</span> Return all the elements that pass a truth test. Use JavaScript 1.6's</span>
|
||||
<span class="line-numbers"> 108 </span> <span class="Comment"><span class="Comment">#</span> filter(), if it exists.</span>
|
||||
<span class="line-numbers"> 109 </span> <span class="FunctionName">_.select</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 110 </span> <span class="Keyword">if</span> obj <span class="Keyword">and</span> _.isFunction(obj.filter) <span class="Keyword">then</span> <span class="Keyword">return</span> obj.filter(iterator, context)
|
||||
<span class="line-numbers"> 111 </span> results<span class="Keyword">:</span> []
|
||||
<span class="line-numbers"> 112 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 113 </span> results.push(value) <span class="Keyword">if</span> iterator.call(context, value, index, list)
|
||||
<span class="line-numbers"> 114 </span> results
|
||||
<span class="line-numbers"> 115 </span>
|
||||
<span class="line-numbers"> 116 </span>
|
||||
<span class="line-numbers"> 117 </span> <span class="Comment"><span class="Comment">#</span> Return all the elements for which a truth test fails.</span>
|
||||
<span class="line-numbers"> 118 </span> <span class="FunctionName">_.reject</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 119 </span> results<span class="Keyword">:</span> []
|
||||
<span class="line-numbers"> 120 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 121 </span> results.push(value) <span class="Keyword">if</span> <span class="Keyword">not</span> iterator.call(context, value, index, list)
|
||||
<span class="line-numbers"> 122 </span> results
|
||||
<span class="line-numbers"> 123 </span>
|
||||
<span class="line-numbers"> 124 </span>
|
||||
<span class="line-numbers"> 125 </span> <span class="Comment"><span class="Comment">#</span> Determine whether all of the elements match a truth test. Delegate to</span>
|
||||
<span class="line-numbers"> 126 </span> <span class="Comment"><span class="Comment">#</span> JavaScript 1.6's every(), if it is present.</span>
|
||||
<span class="line-numbers"> 127 </span> <span class="FunctionName">_.all</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 128 </span> iterator <span class="Keyword">||</span><span class="Keyword">=</span> _.identity
|
||||
<span class="line-numbers"> 129 </span> <span class="Keyword">return</span> obj.every(iterator, context) <span class="Keyword">if</span> obj <span class="Keyword">and</span> _.isFunction(obj.every)
|
||||
<span class="line-numbers"> 130 </span> result<span class="Keyword">:</span> <span class="BuiltInConstant">true</span>
|
||||
<span class="line-numbers"> 131 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 132 </span> _.breakLoop() <span class="Keyword">unless</span> (result<span class="Keyword">:</span> result <span class="Keyword">and</span> iterator.call(context, value, index, list))
|
||||
<span class="line-numbers"> 133 </span> result
|
||||
<span class="line-numbers"> 134 </span>
|
||||
<span class="line-numbers"> 135 </span>
|
||||
<span class="line-numbers"> 136 </span> <span class="Comment"><span class="Comment">#</span> Determine if at least one element in the object matches a truth test. Use</span>
|
||||
<span class="line-numbers"> 137 </span> <span class="Comment"><span class="Comment">#</span> JavaScript 1.6's some(), if it exists.</span>
|
||||
<span class="line-numbers"> 138 </span> <span class="FunctionName">_.any</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 139 </span> iterator <span class="Keyword">||</span><span class="Keyword">=</span> _.identity
|
||||
<span class="line-numbers"> 140 </span> <span class="Keyword">return</span> obj.some(iterator, context) <span class="Keyword">if</span> obj <span class="Keyword">and</span> _.isFunction(obj.some)
|
||||
<span class="line-numbers"> 141 </span> result<span class="Keyword">:</span> <span class="BuiltInConstant">false</span>
|
||||
<span class="line-numbers"> 142 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 143 </span> _.breakLoop() <span class="Keyword">if</span> (result<span class="Keyword">:</span> iterator.call(context, value, index, list))
|
||||
<span class="line-numbers"> 144 </span> result
|
||||
<span class="line-numbers"> 145 </span>
|
||||
<span class="line-numbers"> 146 </span>
|
||||
<span class="line-numbers"> 147 </span> <span class="Comment"><span class="Comment">#</span> Determine if a given value is included in the array or object,</span>
|
||||
<span class="line-numbers"> 148 </span> <span class="Comment"><span class="Comment">#</span> based on '==='.</span>
|
||||
<span class="line-numbers"> 149 </span> <span class="FunctionName">_.include</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, target</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 150 </span> <span class="Keyword">return</span> _.indexOf(obj, target) <span class="Keyword">isnt</span> <span class="Keyword">-</span><span class="Number">1</span> <span class="Keyword">if</span> _.isArray(obj)
|
||||
<span class="line-numbers"> 151 </span> <span class="Keyword">for</span> val <span class="Keyword">in</span> obj
|
||||
<span class="line-numbers"> 152 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> val <span class="Keyword">is</span> target
|
||||
<span class="line-numbers"> 153 </span> <span class="BuiltInConstant">false</span>
|
||||
<span class="line-numbers"> 154 </span>
|
||||
<span class="line-numbers"> 155 </span>
|
||||
<span class="line-numbers"> 156 </span> <span class="Comment"><span class="Comment">#</span> Invoke a method with arguments on every item in a collection.</span>
|
||||
<span class="line-numbers"> 157 </span> <span class="FunctionName">_.invoke</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, method</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 158 </span> args<span class="Keyword">:</span> _.rest(arguments, <span class="Number">2</span>)
|
||||
<span class="line-numbers"> 159 </span> (<span class="Keyword">if</span> method <span class="Keyword">then</span> val[method] <span class="Keyword">else</span> val).apply(val, args) <span class="Keyword">for</span> val <span class="Keyword">in</span> obj
|
||||
<span class="line-numbers"> 160 </span>
|
||||
<span class="line-numbers"> 161 </span>
|
||||
<span class="line-numbers"> 162 </span> <span class="Comment"><span class="Comment">#</span> Convenience version of a common use case of map: fetching a property.</span>
|
||||
<span class="line-numbers"> 163 </span> <span class="FunctionName">_.pluck</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, key</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 164 </span> _.map(obj, (<span class="FunctionArgument">val </span><span class="Storage">=></span> val[key]))
|
||||
<span class="line-numbers"> 165 </span>
|
||||
<span class="line-numbers"> 166 </span>
|
||||
<span class="line-numbers"> 167 </span> <span class="Comment"><span class="Comment">#</span> Return the maximum item or (item-based computation).</span>
|
||||
<span class="line-numbers"> 168 </span> <span class="FunctionName">_.max</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 169 </span> <span class="Keyword">return</span> Math.max.apply(Math, obj) <span class="Keyword">if</span> <span class="Keyword">not</span> iterator <span class="Keyword">and</span> _.isArray(obj)
|
||||
<span class="line-numbers"> 170 </span> result<span class="Keyword">:</span> {computed<span class="Keyword">:</span> <span class="Keyword">-</span><span class="BuiltInConstant">Infinity</span>}
|
||||
<span class="line-numbers"> 171 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 172 </span> computed<span class="Keyword">:</span> <span class="Keyword">if</span> iterator <span class="Keyword">then</span> iterator.call(context, value, index, list) <span class="Keyword">else</span> value
|
||||
<span class="line-numbers"> 173 </span> computed <span class="Keyword">>=</span> result.computed <span class="Keyword">and</span> (result<span class="Keyword">:</span> {value<span class="Keyword">:</span> value, computed<span class="Keyword">:</span> computed})
|
||||
<span class="line-numbers"> 174 </span> result.value
|
||||
<span class="line-numbers"> 175 </span>
|
||||
<span class="line-numbers"> 176 </span>
|
||||
<span class="line-numbers"> 177 </span> <span class="Comment"><span class="Comment">#</span> Return the minimum element (or element-based computation).</span>
|
||||
<span class="line-numbers"> 178 </span> <span class="FunctionName">_.min</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 179 </span> <span class="Keyword">return</span> Math.min.apply(Math, obj) <span class="Keyword">if</span> <span class="Keyword">not</span> iterator <span class="Keyword">and</span> _.isArray(obj)
|
||||
<span class="line-numbers"> 180 </span> result<span class="Keyword">:</span> {computed<span class="Keyword">:</span> <span class="BuiltInConstant">Infinity</span>}
|
||||
<span class="line-numbers"> 181 </span> _.each(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 182 </span> computed<span class="Keyword">:</span> <span class="Keyword">if</span> iterator <span class="Keyword">then</span> iterator.call(context, value, index, list) <span class="Keyword">else</span> value
|
||||
<span class="line-numbers"> 183 </span> computed <span class="Keyword"><</span> result.computed <span class="Keyword">and</span> (result<span class="Keyword">:</span> {value<span class="Keyword">:</span> value, computed<span class="Keyword">:</span> computed})
|
||||
<span class="line-numbers"> 184 </span> result.value
|
||||
<span class="line-numbers"> 185 </span>
|
||||
<span class="line-numbers"> 186 </span>
|
||||
<span class="line-numbers"> 187 </span> <span class="Comment"><span class="Comment">#</span> Sort the object's values by a criteria produced by an iterator.</span>
|
||||
<span class="line-numbers"> 188 </span> <span class="FunctionName">_.sortBy</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, iterator, context</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 189 </span> _.pluck(((_.map(obj)<span class="FunctionArgument"> value, index, list </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 190 </span> {value<span class="Keyword">:</span> value, criteria<span class="Keyword">:</span> iterator.call(context, value, index, list)}
|
||||
<span class="line-numbers"> 191 </span> ).sort()<span class="FunctionArgument"> left, right </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 192 </span> a<span class="Keyword">:</span> left.criteria; b<span class="Keyword">:</span> right.criteria
|
||||
<span class="line-numbers"> 193 </span> <span class="Keyword">if</span> a <span class="Keyword"><</span> b <span class="Keyword">then</span> <span class="Keyword">-</span><span class="Number">1</span> <span class="Keyword">else</span> <span class="Keyword">if</span> a <span class="Keyword">></span> b <span class="Keyword">then</span> <span class="Number">1</span> <span class="Keyword">else</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 194 </span> ), <span class="String"><span class="String">'</span>value<span class="String">'</span></span>)
|
||||
<span class="line-numbers"> 195 </span>
|
||||
<span class="line-numbers"> 196 </span>
|
||||
<span class="line-numbers"> 197 </span> <span class="Comment"><span class="Comment">#</span> Use a comparator function to figure out at what index an object should</span>
|
||||
<span class="line-numbers"> 198 </span> <span class="Comment"><span class="Comment">#</span> be inserted so as to maintain order. Uses binary search.</span>
|
||||
<span class="line-numbers"> 199 </span> <span class="FunctionName">_.sortedIndex</span><span class="Keyword">:</span> <span class="FunctionArgument">array, obj, iterator</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 200 </span> iterator <span class="Keyword">||</span><span class="Keyword">=</span> _.identity
|
||||
<span class="line-numbers"> 201 </span> low<span class="Keyword">:</span> <span class="Number">0</span>; high<span class="Keyword">:</span> array.length
|
||||
<span class="line-numbers"> 202 </span> <span class="Keyword">while</span> low <span class="Keyword"><</span> high
|
||||
<span class="line-numbers"> 203 </span> mid<span class="Keyword">:</span> (low <span class="Keyword">+</span> high) <span class="Keyword">></span><span class="Keyword">></span> <span class="Number">1</span>
|
||||
<span class="line-numbers"> 204 </span> <span class="Keyword">if</span> iterator(array[mid]) <span class="Keyword"><</span> iterator(obj) <span class="Keyword">then</span> low<span class="Keyword">:</span> mid <span class="Keyword">+</span> <span class="Number">1</span> <span class="Keyword">else</span> high<span class="Keyword">:</span> mid
|
||||
<span class="line-numbers"> 205 </span> low
|
||||
<span class="line-numbers"> 206 </span>
|
||||
<span class="line-numbers"> 207 </span>
|
||||
<span class="line-numbers"> 208 </span> <span class="Comment"><span class="Comment">#</span> Convert anything iterable into a real, live array.</span>
|
||||
<span class="line-numbers"> 209 </span> <span class="FunctionName">_.toArray</span><span class="Keyword">:</span> <span class="FunctionArgument">iterable</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 210 </span> <span class="Keyword">return</span> [] <span class="Keyword">if</span> (<span class="Keyword">!</span>iterable)
|
||||
<span class="line-numbers"> 211 </span> <span class="Keyword">return</span> iterable.toArray() <span class="Keyword">if</span> (iterable.toArray)
|
||||
<span class="line-numbers"> 212 </span> <span class="Keyword">return</span> iterable <span class="Keyword">if</span> (_.isArray(iterable))
|
||||
<span class="line-numbers"> 213 </span> <span class="Keyword">return</span> slice.call(iterable) <span class="Keyword">if</span> (_.isArguments(iterable))
|
||||
<span class="line-numbers"> 214 </span> _.values(iterable)
|
||||
<span class="line-numbers"> 215 </span>
|
||||
<span class="line-numbers"> 216 </span>
|
||||
<span class="line-numbers"> 217 </span> <span class="Comment"><span class="Comment">#</span> Return the number of elements in an object.</span>
|
||||
<span class="line-numbers"> 218 </span> <span class="FunctionName">_.size</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> _.toArray(obj).length
|
||||
<span class="line-numbers"> 219 </span>
|
||||
<span class="line-numbers"> 220 </span>
|
||||
<span class="line-numbers"> 221 </span> <span class="Comment"><span class="Comment">#</span> -------------------------- Array Functions: ------------------------------</span>
|
||||
<span class="line-numbers"> 222 </span>
|
||||
<span class="line-numbers"> 223 </span> <span class="Comment"><span class="Comment">#</span> Get the first element of an array. Passing "n" will return the first N</span>
|
||||
<span class="line-numbers"> 224 </span> <span class="Comment"><span class="Comment">#</span> values in the array. Aliased as "head". The "guard" check allows it to work</span>
|
||||
<span class="line-numbers"> 225 </span> <span class="Comment"><span class="Comment">#</span> with _.map.</span>
|
||||
<span class="line-numbers"> 226 </span> <span class="FunctionName">_.first</span><span class="Keyword">:</span> <span class="FunctionArgument">array, n, guard</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 227 </span> <span class="Keyword">if</span> n <span class="Keyword">and</span> <span class="Keyword">not</span> guard <span class="Keyword">then</span> slice.call(array, <span class="Number">0</span>, n) <span class="Keyword">else</span> array[<span class="Number">0</span>]
|
||||
<span class="line-numbers"> 228 </span>
|
||||
<span class="line-numbers"> 229 </span>
|
||||
<span class="line-numbers"> 230 </span> <span class="Comment"><span class="Comment">#</span> Returns everything but the first entry of the array. Aliased as "tail".</span>
|
||||
<span class="line-numbers"> 231 </span> <span class="Comment"><span class="Comment">#</span> Especially useful on the arguments object. Passing an "index" will return</span>
|
||||
<span class="line-numbers"> 232 </span> <span class="Comment"><span class="Comment">#</span> the rest of the values in the array from that index onward. The "guard"</span>
|
||||
<span class="line-numbers"> 233 </span> <span class="Comment"><span class="Comment">#</span> check allows it to work with _.map.</span>
|
||||
<span class="line-numbers"> 234 </span> <span class="FunctionName">_.rest</span><span class="Keyword">:</span> <span class="FunctionArgument">array, index, guard</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 235 </span> slice.call(array, <span class="Keyword">if</span> _.isUndefined(index) <span class="Keyword">or</span> guard <span class="Keyword">then</span> <span class="Number">1</span> <span class="Keyword">else</span> index)
|
||||
<span class="line-numbers"> 236 </span>
|
||||
<span class="line-numbers"> 237 </span>
|
||||
<span class="line-numbers"> 238 </span> <span class="Comment"><span class="Comment">#</span> Get the last element of an array.</span>
|
||||
<span class="line-numbers"> 239 </span> <span class="FunctionName">_.last</span><span class="Keyword">:</span> <span class="FunctionArgument">array</span> <span class="Storage">=></span> array[array.length <span class="Keyword">-</span> <span class="Number">1</span>]
|
||||
<span class="line-numbers"> 240 </span>
|
||||
<span class="line-numbers"> 241 </span>
|
||||
<span class="line-numbers"> 242 </span> <span class="Comment"><span class="Comment">#</span> Trim out all falsy values from an array.</span>
|
||||
<span class="line-numbers"> 243 </span> <span class="FunctionName">_.compact</span><span class="Keyword">:</span> <span class="FunctionArgument">array</span> <span class="Storage">=></span> array[i] <span class="Keyword">for</span> i <span class="Keyword">in</span> [<span class="Number">0</span>...array.length] <span class="Keyword">when</span> array[i]
|
||||
<span class="line-numbers"> 244 </span>
|
||||
<span class="line-numbers"> 245 </span>
|
||||
<span class="line-numbers"> 246 </span> <span class="Comment"><span class="Comment">#</span> Return a completely flattened version of an array.</span>
|
||||
<span class="line-numbers"> 247 </span> <span class="FunctionName">_.flatten</span><span class="Keyword">:</span> <span class="FunctionArgument">array</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 248 </span> _.reduce(array, [])<span class="FunctionArgument"> memo, value </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 249 </span> <span class="Keyword">return</span> memo.concat(_.flatten(value)) <span class="Keyword">if</span> _.isArray(value)
|
||||
<span class="line-numbers"> 250 </span> memo.push(value)
|
||||
<span class="line-numbers"> 251 </span> memo
|
||||
<span class="line-numbers"> 252 </span>
|
||||
<span class="line-numbers"> 253 </span>
|
||||
<span class="line-numbers"> 254 </span> <span class="Comment"><span class="Comment">#</span> Return a version of the array that does not contain the specified value(s).</span>
|
||||
<span class="line-numbers"> 255 </span> <span class="FunctionName">_.without</span><span class="Keyword">:</span> <span class="FunctionArgument">array</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 256 </span> values<span class="Keyword">:</span> _.rest(arguments)
|
||||
<span class="line-numbers"> 257 </span> val <span class="Keyword">for</span> val <span class="Keyword">in</span> _.toArray(array) <span class="Keyword">when</span> <span class="Keyword">not</span> _.include(values, val)
|
||||
<span class="line-numbers"> 258 </span>
|
||||
<span class="line-numbers"> 259 </span>
|
||||
<span class="line-numbers"> 260 </span> <span class="Comment"><span class="Comment">#</span> Produce a duplicate-free version of the array. If the array has already</span>
|
||||
<span class="line-numbers"> 261 </span> <span class="Comment"><span class="Comment">#</span> been sorted, you have the option of using a faster algorithm.</span>
|
||||
<span class="line-numbers"> 262 </span> <span class="FunctionName">_.uniq</span><span class="Keyword">:</span> <span class="FunctionArgument">array, isSorted</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 263 </span> memo<span class="Keyword">:</span> []
|
||||
<span class="line-numbers"> 264 </span> <span class="Keyword">for</span> el, i <span class="Keyword">in</span> _.toArray(array)
|
||||
<span class="line-numbers"> 265 </span> memo.push(el) <span class="Keyword">if</span> i <span class="Keyword">is</span> <span class="Number">0</span> <span class="Keyword">||</span> (<span class="Keyword">if</span> isSorted <span class="Keyword">is</span> <span class="BuiltInConstant">true</span> <span class="Keyword">then</span> _.last(memo) <span class="Keyword">isnt</span> el <span class="Keyword">else</span> <span class="Keyword">not</span> _.include(memo, el))
|
||||
<span class="line-numbers"> 266 </span> memo
|
||||
<span class="line-numbers"> 267 </span>
|
||||
<span class="line-numbers"> 268 </span>
|
||||
<span class="line-numbers"> 269 </span> <span class="Comment"><span class="Comment">#</span> Produce an array that contains every item shared between all the</span>
|
||||
<span class="line-numbers"> 270 </span> <span class="Comment"><span class="Comment">#</span> passed-in arrays.</span>
|
||||
<span class="line-numbers"> 271 </span> <span class="FunctionName">_.intersect</span><span class="Keyword">:</span> <span class="FunctionArgument">array</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 272 </span> rest<span class="Keyword">:</span> _.rest(arguments)
|
||||
<span class="line-numbers"> 273 </span> _.select(_.uniq(array))<span class="FunctionArgument"> item </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 274 </span> _.all(rest)<span class="FunctionArgument"> other </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 275 </span> _.indexOf(other, item) <span class="Keyword">>=</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 276 </span>
|
||||
<span class="line-numbers"> 277 </span>
|
||||
<span class="line-numbers"> 278 </span> <span class="Comment"><span class="Comment">#</span> Zip together multiple lists into a single array -- elements that share</span>
|
||||
<span class="line-numbers"> 279 </span> <span class="Comment"><span class="Comment">#</span> an index go together.</span>
|
||||
<span class="line-numbers"> 280 </span> <span class="FunctionName">_.zip</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 281 </span> args<span class="Keyword">:</span> _.toArray(arguments)
|
||||
<span class="line-numbers"> 282 </span> length<span class="Keyword">:</span> _.max(_.pluck(args, <span class="String"><span class="String">'</span>length<span class="String">'</span></span>))
|
||||
<span class="line-numbers"> 283 </span> results<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Array</span>(length)
|
||||
<span class="line-numbers"> 284 </span> <span class="Keyword">for</span> i <span class="Keyword">in</span> [<span class="Number">0</span>...length]
|
||||
<span class="line-numbers"> 285 </span> results[i]<span class="Keyword">:</span> _.pluck(args, String(i))
|
||||
<span class="line-numbers"> 286 </span> results
|
||||
<span class="line-numbers"> 287 </span>
|
||||
<span class="line-numbers"> 288 </span>
|
||||
<span class="line-numbers"> 289 </span> <span class="Comment"><span class="Comment">#</span> If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),</span>
|
||||
<span class="line-numbers"> 290 </span> <span class="Comment"><span class="Comment">#</span> we need this function. Return the position of the first occurence of an</span>
|
||||
<span class="line-numbers"> 291 </span> <span class="Comment"><span class="Comment">#</span> item in an array, or -1 if the item is not included in the array.</span>
|
||||
<span class="line-numbers"> 292 </span> <span class="FunctionName">_.indexOf</span><span class="Keyword">:</span> <span class="FunctionArgument">array, item</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 293 </span> <span class="Keyword">return</span> array.indexOf(item) <span class="Keyword">if</span> array.indexOf
|
||||
<span class="line-numbers"> 294 </span> i<span class="Keyword">:</span> <span class="Number">0</span>; l<span class="Keyword">:</span> array.length
|
||||
<span class="line-numbers"> 295 </span> <span class="Keyword">while</span> l <span class="Keyword">-</span> i
|
||||
<span class="line-numbers"> 296 </span> <span class="Keyword">if</span> array[i] <span class="Keyword">is</span> item <span class="Keyword">then</span> <span class="Keyword">return</span> i <span class="Keyword">else</span> i<span class="Keyword">++</span>
|
||||
<span class="line-numbers"> 297 </span> <span class="Keyword">-</span><span class="Number">1</span>
|
||||
<span class="line-numbers"> 298 </span>
|
||||
<span class="line-numbers"> 299 </span>
|
||||
<span class="line-numbers"> 300 </span> <span class="Comment"><span class="Comment">#</span> Provide JavaScript 1.6's lastIndexOf, delegating to the native function,</span>
|
||||
<span class="line-numbers"> 301 </span> <span class="Comment"><span class="Comment">#</span> if possible.</span>
|
||||
<span class="line-numbers"> 302 </span> <span class="FunctionName">_.lastIndexOf</span><span class="Keyword">:</span> <span class="FunctionArgument">array, item</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 303 </span> <span class="Keyword">return</span> array.lastIndexOf(item) <span class="Keyword">if</span> array.lastIndexOf
|
||||
<span class="line-numbers"> 304 </span> i<span class="Keyword">:</span> array.length
|
||||
<span class="line-numbers"> 305 </span> <span class="Keyword">while</span> i
|
||||
<span class="line-numbers"> 306 </span> <span class="Keyword">if</span> array[i] <span class="Keyword">is</span> item <span class="Keyword">then</span> <span class="Keyword">return</span> i <span class="Keyword">else</span> i<span class="Keyword">--</span>
|
||||
<span class="line-numbers"> 307 </span> <span class="Keyword">-</span><span class="Number">1</span>
|
||||
<span class="line-numbers"> 308 </span>
|
||||
<span class="line-numbers"> 309 </span>
|
||||
<span class="line-numbers"> 310 </span> <span class="Comment"><span class="Comment">#</span> Generate an integer Array containing an arithmetic progression. A port of</span>
|
||||
<span class="line-numbers"> 311 </span> <span class="Comment"><span class="Comment">#</span> the native Python range() function. See:</span>
|
||||
<span class="line-numbers"> 312 </span> <span class="Comment"><span class="Comment">#</span> http://docs.python.org/library/functions.html#range</span>
|
||||
<span class="line-numbers"> 313 </span> <span class="FunctionName">_.range</span><span class="Keyword">:</span> <span class="FunctionArgument">start, stop, step</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 314 </span> a<span class="Keyword">:</span> _.toArray(arguments)
|
||||
<span class="line-numbers"> 315 </span> solo<span class="Keyword">:</span> a.length <span class="Keyword"><=</span> <span class="Number">1</span>
|
||||
<span class="line-numbers"> 316 </span> i<span class="Keyword">:</span> start<span class="Keyword">:</span> <span class="Keyword">if</span> solo <span class="Keyword">then</span> <span class="Number">0</span> <span class="Keyword">else</span> a[<span class="Number">0</span>];
|
||||
<span class="line-numbers"> 317 </span> stop<span class="Keyword">:</span> <span class="Keyword">if</span> solo <span class="Keyword">then</span> a[<span class="Number">0</span>] <span class="Keyword">else</span> a[<span class="Number">1</span>];
|
||||
<span class="line-numbers"> 318 </span> step<span class="Keyword">:</span> a[<span class="Number">2</span>] <span class="Keyword">or</span> <span class="Number">1</span>
|
||||
<span class="line-numbers"> 319 </span> len<span class="Keyword">:</span> Math.ceil((stop <span class="Keyword">-</span> start) <span class="Keyword">/</span> step)
|
||||
<span class="line-numbers"> 320 </span> <span class="Keyword">return</span> [] <span class="Keyword">if</span> len <span class="Keyword"><=</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 321 </span> range<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Array</span>(len)
|
||||
<span class="line-numbers"> 322 </span> idx<span class="Keyword">:</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 323 </span> <span class="Keyword">while</span> <span class="BuiltInConstant">true</span>
|
||||
<span class="line-numbers"> 324 </span> <span class="Keyword">return</span> range <span class="Keyword">if</span> (<span class="Keyword">if</span> step <span class="Keyword">></span> <span class="Number">0</span> <span class="Keyword">then</span> i <span class="Keyword">-</span> stop <span class="Keyword">else</span> stop <span class="Keyword">-</span> i) <span class="Keyword">>=</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 325 </span> range[idx]<span class="Keyword">:</span> i
|
||||
<span class="line-numbers"> 326 </span> idx<span class="Keyword">++</span>
|
||||
<span class="line-numbers"> 327 </span> i<span class="Keyword">+</span><span class="Keyword">=</span> step
|
||||
<span class="line-numbers"> 328 </span>
|
||||
<span class="line-numbers"> 329 </span>
|
||||
<span class="line-numbers"> 330 </span> <span class="Comment"><span class="Comment">#</span> ----------------------- Function Functions: -----------------------------</span>
|
||||
<span class="line-numbers"> 331 </span>
|
||||
<span class="line-numbers"> 332 </span> <span class="Comment"><span class="Comment">#</span> Create a function bound to a given object (assigning 'this', and arguments,</span>
|
||||
<span class="line-numbers"> 333 </span> <span class="Comment"><span class="Comment">#</span> optionally). Binding with arguments is also known as 'curry'.</span>
|
||||
<span class="line-numbers"> 334 </span> <span class="FunctionName">_.bind</span><span class="Keyword">:</span> <span class="FunctionArgument">func, obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 335 </span> args<span class="Keyword">:</span> _.rest(arguments, <span class="Number">2</span>)
|
||||
<span class="line-numbers"> 336 </span> <span class="FunctionArgument"> </span><span class="Storage">=></span> func.apply(obj <span class="Keyword">or</span> root, args.concat(_.toArray(arguments)))
|
||||
<span class="line-numbers"> 337 </span>
|
||||
<span class="line-numbers"> 338 </span>
|
||||
<span class="line-numbers"> 339 </span> <span class="Comment"><span class="Comment">#</span> Bind all of an object's methods to that object. Useful for ensuring that</span>
|
||||
<span class="line-numbers"> 340 </span> <span class="Comment"><span class="Comment">#</span> all callbacks defined on an object belong to it.</span>
|
||||
<span class="line-numbers"> 341 </span> <span class="FunctionName">_.bindAll</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 342 </span> funcs<span class="Keyword">:</span> <span class="Keyword">if</span> arguments.length <span class="Keyword">></span> <span class="Number">1</span> <span class="Keyword">then</span> _.rest(arguments) <span class="Keyword">else</span> _.functions(obj)
|
||||
<span class="line-numbers"> 343 </span> _.each(funcs, (<span class="FunctionArgument">f </span><span class="Storage">=></span> obj[f]<span class="Keyword">:</span> _.bind(obj[f], obj)))
|
||||
<span class="line-numbers"> 344 </span> obj
|
||||
<span class="line-numbers"> 345 </span>
|
||||
<span class="line-numbers"> 346 </span>
|
||||
<span class="line-numbers"> 347 </span> <span class="Comment"><span class="Comment">#</span> Delays a function for the given number of milliseconds, and then calls</span>
|
||||
<span class="line-numbers"> 348 </span> <span class="Comment"><span class="Comment">#</span> it with the arguments supplied.</span>
|
||||
<span class="line-numbers"> 349 </span> <span class="FunctionName">_.delay</span><span class="Keyword">:</span> <span class="FunctionArgument">func, wait</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 350 </span> args<span class="Keyword">:</span> _.rest(arguments, <span class="Number">2</span>)
|
||||
<span class="line-numbers"> 351 </span> setTimeout((<span class="Storage">=></span> func.apply(func, args)), wait)
|
||||
<span class="line-numbers"> 352 </span>
|
||||
<span class="line-numbers"> 353 </span>
|
||||
<span class="line-numbers"> 354 </span> <span class="Comment"><span class="Comment">#</span> Defers a function, scheduling it to run after the current call stack has</span>
|
||||
<span class="line-numbers"> 355 </span> <span class="Comment"><span class="Comment">#</span> cleared.</span>
|
||||
<span class="line-numbers"> 356 </span> <span class="FunctionName">_.defer</span><span class="Keyword">:</span> <span class="FunctionArgument">func</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 357 </span> _.delay.apply(_, [func, <span class="Number">1</span>].concat(_.rest(arguments)))
|
||||
<span class="line-numbers"> 358 </span>
|
||||
<span class="line-numbers"> 359 </span>
|
||||
<span class="line-numbers"> 360 </span> <span class="Comment"><span class="Comment">#</span> Returns the first function passed as an argument to the second,</span>
|
||||
<span class="line-numbers"> 361 </span> <span class="Comment"><span class="Comment">#</span> allowing you to adjust arguments, run code before and after, and</span>
|
||||
<span class="line-numbers"> 362 </span> <span class="Comment"><span class="Comment">#</span> conditionally execute the original function.</span>
|
||||
<span class="line-numbers"> 363 </span> <span class="FunctionName">_.wrap</span><span class="Keyword">:</span> <span class="FunctionArgument">func, wrapper</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 364 </span> <span class="FunctionArgument"> </span><span class="Storage">=></span> wrapper.apply(wrapper, [func].concat(_.toArray(arguments)))
|
||||
<span class="line-numbers"> 365 </span>
|
||||
<span class="line-numbers"> 366 </span>
|
||||
<span class="line-numbers"> 367 </span> <span class="Comment"><span class="Comment">#</span> Returns a function that is the composition of a list of functions, each</span>
|
||||
<span class="line-numbers"> 368 </span> <span class="Comment"><span class="Comment">#</span> consuming the return value of the function that follows.</span>
|
||||
<span class="line-numbers"> 369 </span> <span class="FunctionName">_.compose</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 370 </span> funcs<span class="Keyword">:</span> _.toArray(arguments)
|
||||
<span class="line-numbers"> 371 </span> <span class="FunctionArgument"> </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 372 </span> args<span class="Keyword">:</span> _.toArray(arguments)
|
||||
<span class="line-numbers"> 373 </span> <span class="Keyword">for</span> i <span class="Keyword">in</span> [(funcs.length <span class="Keyword">-</span> <span class="Number">1</span>)..<span class="Number">0</span>]
|
||||
<span class="line-numbers"> 374 </span> args<span class="Keyword">:</span> [funcs[i].apply(<span class="Variable">this</span>, args)]
|
||||
<span class="line-numbers"> 375 </span> args[<span class="Number">0</span>]
|
||||
<span class="line-numbers"> 376 </span>
|
||||
<span class="line-numbers"> 377 </span>
|
||||
<span class="line-numbers"> 378 </span> <span class="Comment"><span class="Comment">#</span> ------------------------- Object Functions: ----------------------------</span>
|
||||
<span class="line-numbers"> 379 </span>
|
||||
<span class="line-numbers"> 380 </span> <span class="Comment"><span class="Comment">#</span> Retrieve the names of an object's properties.</span>
|
||||
<span class="line-numbers"> 381 </span> <span class="FunctionName">_.keys</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 382 </span> <span class="Keyword">return</span> _.range(<span class="Number">0</span>, obj.length) <span class="Keyword">if</span> _.isArray(obj)
|
||||
<span class="line-numbers"> 383 </span> key <span class="Keyword">for</span> val, key <span class="Keyword">in</span> obj
|
||||
<span class="line-numbers"> 384 </span>
|
||||
<span class="line-numbers"> 385 </span>
|
||||
<span class="line-numbers"> 386 </span> <span class="Comment"><span class="Comment">#</span> Retrieve the values of an object's properties.</span>
|
||||
<span class="line-numbers"> 387 </span> <span class="FunctionName">_.values</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 388 </span> _.map(obj, _.identity)
|
||||
<span class="line-numbers"> 389 </span>
|
||||
<span class="line-numbers"> 390 </span>
|
||||
<span class="line-numbers"> 391 </span> <span class="Comment"><span class="Comment">#</span> Return a sorted list of the function names available in Underscore.</span>
|
||||
<span class="line-numbers"> 392 </span> <span class="FunctionName">_.functions</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 393 </span> _.select(_.keys(obj)<span class="FunctionArgument">, key </span><span class="Storage">=></span> _.isFunction(obj[key])).sort()
|
||||
<span class="line-numbers"> 394 </span>
|
||||
<span class="line-numbers"> 395 </span>
|
||||
<span class="line-numbers"> 396 </span> <span class="Comment"><span class="Comment">#</span> Extend a given object with all of the properties in a source object.</span>
|
||||
<span class="line-numbers"> 397 </span> <span class="FunctionName">_.extend</span><span class="Keyword">:</span> <span class="FunctionArgument">destination, source</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 398 </span> <span class="Keyword">for</span> val, key <span class="Keyword">in</span> source
|
||||
<span class="line-numbers"> 399 </span> destination[key]<span class="Keyword">:</span> val
|
||||
<span class="line-numbers"> 400 </span> destination
|
||||
<span class="line-numbers"> 401 </span>
|
||||
<span class="line-numbers"> 402 </span>
|
||||
<span class="line-numbers"> 403 </span> <span class="Comment"><span class="Comment">#</span> Create a (shallow-cloned) duplicate of an object.</span>
|
||||
<span class="line-numbers"> 404 </span> <span class="FunctionName">_.clone</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 405 </span> <span class="Keyword">return</span> obj.slice(<span class="Number">0</span>) <span class="Keyword">if</span> _.isArray(obj)
|
||||
<span class="line-numbers"> 406 </span> _.extend({}, obj)
|
||||
<span class="line-numbers"> 407 </span>
|
||||
<span class="line-numbers"> 408 </span>
|
||||
<span class="line-numbers"> 409 </span> <span class="Comment"><span class="Comment">#</span> Invokes interceptor with the obj, and then returns obj.</span>
|
||||
<span class="line-numbers"> 410 </span> <span class="Comment"><span class="Comment">#</span> The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.</span>
|
||||
<span class="line-numbers"> 411 </span> <span class="FunctionName">_.tap</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, interceptor</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 412 </span> interceptor(obj)
|
||||
<span class="line-numbers"> 413 </span> obj
|
||||
<span class="line-numbers"> 414 </span>
|
||||
<span class="line-numbers"> 415 </span>
|
||||
<span class="line-numbers"> 416 </span> <span class="Comment"><span class="Comment">#</span> Perform a deep comparison to check if two objects are equal.</span>
|
||||
<span class="line-numbers"> 417 </span> <span class="FunctionName">_.isEqual</span><span class="Keyword">:</span> <span class="FunctionArgument">a, b</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 418 </span> <span class="Comment"><span class="Comment">#</span> Check object identity.</span>
|
||||
<span class="line-numbers"> 419 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> a <span class="Keyword">is</span> b
|
||||
<span class="line-numbers"> 420 </span> <span class="Comment"><span class="Comment">#</span> Different types?</span>
|
||||
<span class="line-numbers"> 421 </span> atype<span class="Keyword">:</span> <span class="Keyword">typeof</span>(a); btype<span class="Keyword">:</span> <span class="Keyword">typeof</span>(b)
|
||||
<span class="line-numbers"> 422 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">false</span> <span class="Keyword">if</span> atype <span class="Keyword">isnt</span> btype
|
||||
<span class="line-numbers"> 423 </span> <span class="Comment"><span class="Comment">#</span> Basic equality test (watch out for coercions).</span>
|
||||
<span class="line-numbers"> 424 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> <span class="String"><span class="String">`</span>a == b<span class="String">`</span></span>
|
||||
<span class="line-numbers"> 425 </span> <span class="Comment"><span class="Comment">#</span> One is falsy and the other truthy.</span>
|
||||
<span class="line-numbers"> 426 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">false</span> <span class="Keyword">if</span> (<span class="Keyword">!</span>a <span class="Keyword">and</span> b) <span class="Keyword">or</span> (a <span class="Keyword">and</span> <span class="Keyword">!</span>b)
|
||||
<span class="line-numbers"> 427 </span> <span class="Comment"><span class="Comment">#</span> One of them implements an isEqual()?</span>
|
||||
<span class="line-numbers"> 428 </span> <span class="Keyword">return</span> a.isEqual(b) <span class="Keyword">if</span> a.isEqual
|
||||
<span class="line-numbers"> 429 </span> <span class="Comment"><span class="Comment">#</span> Check dates' integer values.</span>
|
||||
<span class="line-numbers"> 430 </span> <span class="Keyword">return</span> a.getTime() <span class="Keyword">is</span> b.getTime() <span class="Keyword">if</span> _.isDate(a) <span class="Keyword">and</span> _.isDate(b)
|
||||
<span class="line-numbers"> 431 </span> <span class="Comment"><span class="Comment">#</span> Both are NaN?</span>
|
||||
<span class="line-numbers"> 432 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> _.isNaN(a) <span class="Keyword">and</span> _.isNaN(b)
|
||||
<span class="line-numbers"> 433 </span> <span class="Comment"><span class="Comment">#</span> Compare regular expressions.</span>
|
||||
<span class="line-numbers"> 434 </span> <span class="Keyword">if</span> _.isRegExp(a) <span class="Keyword">and</span> _.isRegExp(b)
|
||||
<span class="line-numbers"> 435 </span> <span class="Keyword">return</span> a.source <span class="Keyword">is</span> b.source <span class="Keyword">and</span>
|
||||
<span class="line-numbers"> 436 </span> a.global <span class="Keyword">is</span> b.global <span class="Keyword">and</span>
|
||||
<span class="line-numbers"> 437 </span> a.ignoreCase <span class="Keyword">is</span> b.ignoreCase <span class="Keyword">and</span>
|
||||
<span class="line-numbers"> 438 </span> a.multiline <span class="Keyword">is</span> b.multiline
|
||||
<span class="line-numbers"> 439 </span> <span class="Comment"><span class="Comment">#</span> If a is not an object by this point, we can't handle it.</span>
|
||||
<span class="line-numbers"> 440 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">false</span> <span class="Keyword">if</span> atype <span class="Keyword">isnt</span> <span class="String"><span class="String">'</span>object<span class="String">'</span></span>
|
||||
<span class="line-numbers"> 441 </span> <span class="Comment"><span class="Comment">#</span> Check for different array lengths before comparing contents.</span>
|
||||
<span class="line-numbers"> 442 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">false</span> <span class="Keyword">if</span> a.length <span class="Keyword">and</span> (a.length <span class="Keyword">isnt</span> b.length)
|
||||
<span class="line-numbers"> 443 </span> <span class="Comment"><span class="Comment">#</span> Nothing else worked, deep compare the contents.</span>
|
||||
<span class="line-numbers"> 444 </span> aKeys<span class="Keyword">:</span> _.keys(a); bKeys<span class="Keyword">:</span> _.keys(b)
|
||||
<span class="line-numbers"> 445 </span> <span class="Comment"><span class="Comment">#</span> Different object sizes?</span>
|
||||
<span class="line-numbers"> 446 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">false</span> <span class="Keyword">if</span> aKeys.length <span class="Keyword">isnt</span> bKeys.length
|
||||
<span class="line-numbers"> 447 </span> <span class="Comment"><span class="Comment">#</span> Recursive comparison of contents.</span>
|
||||
<span class="line-numbers"> 448 </span> <span class="Comment"><span class="Comment">#</span> for (var key in a) if (!_.isEqual(a[key], b[key])) return false;</span>
|
||||
<span class="line-numbers"> 449 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span>
|
||||
<span class="line-numbers"> 450 </span>
|
||||
<span class="line-numbers"> 451 </span>
|
||||
<span class="line-numbers"> 452 </span> <span class="Comment"><span class="Comment">#</span> Is a given array or object empty?</span>
|
||||
<span class="line-numbers"> 453 </span> <span class="FunctionName">_.isEmpty</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> _.keys(obj).length <span class="Keyword">is</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 454 </span>
|
||||
<span class="line-numbers"> 455 </span>
|
||||
<span class="line-numbers"> 456 </span> <span class="Comment"><span class="Comment">#</span> Is a given value a DOM element?</span>
|
||||
<span class="line-numbers"> 457 </span> <span class="FunctionName">_.isElement</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> obj <span class="Keyword">and</span> obj.nodeType <span class="Keyword">is</span> <span class="Number">1</span>
|
||||
<span class="line-numbers"> 458 </span>
|
||||
<span class="line-numbers"> 459 </span>
|
||||
<span class="line-numbers"> 460 </span> <span class="Comment"><span class="Comment">#</span> Is a given value an array?</span>
|
||||
<span class="line-numbers"> 461 </span> <span class="FunctionName">_.isArray</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.concat <span class="Keyword">and</span> obj.unshift)
|
||||
<span class="line-numbers"> 462 </span>
|
||||
<span class="line-numbers"> 463 </span>
|
||||
<span class="line-numbers"> 464 </span> <span class="Comment"><span class="Comment">#</span> Is a given variable an arguments object?</span>
|
||||
<span class="line-numbers"> 465 </span> <span class="FunctionName">_.isArguments</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> obj <span class="Keyword">and</span> _.isNumber(obj.length) <span class="Keyword">and</span> <span class="Keyword">!</span>_.isArray(obj) <span class="Keyword">and</span> <span class="Keyword">!</span>propertyIsEnumerable.call(obj, <span class="String"><span class="String">'</span>length<span class="String">'</span></span>)
|
||||
<span class="line-numbers"> 466 </span>
|
||||
<span class="line-numbers"> 467 </span>
|
||||
<span class="line-numbers"> 468 </span> <span class="Comment"><span class="Comment">#</span> Is the given value a function?</span>
|
||||
<span class="line-numbers"> 469 </span> <span class="FunctionName">_.isFunction</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.constructor <span class="Keyword">and</span> obj.call <span class="Keyword">and</span> obj.apply)
|
||||
<span class="line-numbers"> 470 </span>
|
||||
<span class="line-numbers"> 471 </span>
|
||||
<span class="line-numbers"> 472 </span> <span class="Comment"><span class="Comment">#</span> Is the given value a string?</span>
|
||||
<span class="line-numbers"> 473 </span> <span class="FunctionName">_.isString</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">is</span> <span class="String"><span class="String">'</span><span class="String">'</span></span> <span class="Keyword">or</span> (obj <span class="Keyword">and</span> obj.charCodeAt <span class="Keyword">and</span> obj.substr))
|
||||
<span class="line-numbers"> 474 </span>
|
||||
<span class="line-numbers"> 475 </span>
|
||||
<span class="line-numbers"> 476 </span> <span class="Comment"><span class="Comment">#</span> Is a given value a number?</span>
|
||||
<span class="line-numbers"> 477 </span> <span class="FunctionName">_.isNumber</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> toString.call(obj) <span class="Keyword">is</span> <span class="String"><span class="String">'</span>[object Number]<span class="String">'</span></span>
|
||||
<span class="line-numbers"> 478 </span>
|
||||
<span class="line-numbers"> 479 </span>
|
||||
<span class="line-numbers"> 480 </span> <span class="Comment"><span class="Comment">#</span> Is a given value a Date?</span>
|
||||
<span class="line-numbers"> 481 </span> <span class="FunctionName">_.isDate</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.getTimezoneOffset <span class="Keyword">and</span> obj.setUTCFullYear)
|
||||
<span class="line-numbers"> 482 </span>
|
||||
<span class="line-numbers"> 483 </span>
|
||||
<span class="line-numbers"> 484 </span> <span class="Comment"><span class="Comment">#</span> Is the given value a regular expression?</span>
|
||||
<span class="line-numbers"> 485 </span> <span class="FunctionName">_.isRegExp</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.exec <span class="Keyword">and</span> (obj.ignoreCase <span class="Keyword">or</span> obj.ignoreCase <span class="Keyword">is</span> <span class="BuiltInConstant">false</span>))
|
||||
<span class="line-numbers"> 486 </span>
|
||||
<span class="line-numbers"> 487 </span>
|
||||
<span class="line-numbers"> 488 </span> <span class="Comment"><span class="Comment">#</span> Is the given value NaN -- this one is interesting. NaN != NaN, and</span>
|
||||
<span class="line-numbers"> 489 </span> <span class="Comment"><span class="Comment">#</span> isNaN(undefined) == true, so we make sure it's a number first.</span>
|
||||
<span class="line-numbers"> 490 </span> <span class="FunctionName">_.isNaN</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> _.isNumber(obj) <span class="Keyword">and</span> window.isNaN(obj)
|
||||
<span class="line-numbers"> 491 </span>
|
||||
<span class="line-numbers"> 492 </span>
|
||||
<span class="line-numbers"> 493 </span> <span class="Comment"><span class="Comment">#</span> Is a given value equal to null?</span>
|
||||
<span class="line-numbers"> 494 </span> <span class="FunctionName">_.isNull</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> obj <span class="Keyword">is</span> <span class="BuiltInConstant">null</span>
|
||||
<span class="line-numbers"> 495 </span>
|
||||
<span class="line-numbers"> 496 </span>
|
||||
<span class="line-numbers"> 497 </span> <span class="Comment"><span class="Comment">#</span> Is a given variable undefined?</span>
|
||||
<span class="line-numbers"> 498 </span> <span class="FunctionName">_.isUndefined</span><span class="Keyword">:</span> <span class="FunctionArgument">obj</span> <span class="Storage">=></span> <span class="Keyword">typeof</span> obj <span class="Keyword">is</span> <span class="String"><span class="String">'</span>undefined<span class="String">'</span></span>
|
||||
<span class="line-numbers"> 499 </span>
|
||||
<span class="line-numbers"> 500 </span>
|
||||
<span class="line-numbers"> 501 </span> <span class="Comment"><span class="Comment">#</span> -------------------------- Utility Functions: --------------------------</span>
|
||||
<span class="line-numbers"> 502 </span>
|
||||
<span class="line-numbers"> 503 </span> <span class="Comment"><span class="Comment">#</span> Run Underscore.js in noConflict mode, returning the '_' variable to its</span>
|
||||
<span class="line-numbers"> 504 </span> <span class="Comment"><span class="Comment">#</span> previous owner. Returns a reference to the Underscore object.</span>
|
||||
<span class="line-numbers"> 505 </span> <span class="FunctionName">_.noConflict</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 506 </span> root._<span class="Keyword">:</span> previousUnderscore
|
||||
<span class="line-numbers"> 507 </span> <span class="Variable">this</span>
|
||||
<span class="line-numbers"> 508 </span>
|
||||
<span class="line-numbers"> 509 </span>
|
||||
<span class="line-numbers"> 510 </span> <span class="Comment"><span class="Comment">#</span> Keep the identity function around for default iterators.</span>
|
||||
<span class="line-numbers"> 511 </span> <span class="FunctionName">_.identity</span><span class="Keyword">:</span> <span class="FunctionArgument">value</span> <span class="Storage">=></span> value
|
||||
<span class="line-numbers"> 512 </span>
|
||||
<span class="line-numbers"> 513 </span>
|
||||
<span class="line-numbers"> 514 </span> <span class="Comment"><span class="Comment">#</span> Break out of the middle of an iteration.</span>
|
||||
<span class="line-numbers"> 515 </span> <span class="FunctionName">_.breakLoop</span><span class="Keyword">:</span> <span class="Storage">=></span> <span class="Keyword">throw</span> breaker
|
||||
<span class="line-numbers"> 516 </span>
|
||||
<span class="line-numbers"> 517 </span>
|
||||
<span class="line-numbers"> 518 </span> <span class="Comment"><span class="Comment">#</span> Generate a unique integer id (unique within the entire client session).</span>
|
||||
<span class="line-numbers"> 519 </span> <span class="Comment"><span class="Comment">#</span> Useful for temporary DOM ids.</span>
|
||||
<span class="line-numbers"> 520 </span> idCounter<span class="Keyword">:</span> <span class="Number">0</span>
|
||||
<span class="line-numbers"> 521 </span> <span class="FunctionName">_.uniqueId</span><span class="Keyword">:</span> <span class="FunctionArgument">prefix</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 522 </span> (prefix <span class="Keyword">or</span> <span class="String"><span class="String">'</span><span class="String">'</span></span>) <span class="Keyword">+</span> idCounter<span class="Keyword">++</span>
|
||||
<span class="line-numbers"> 523 </span>
|
||||
<span class="line-numbers"> 524 </span>
|
||||
<span class="line-numbers"> 525 </span> <span class="Comment"><span class="Comment">#</span> JavaScript templating a-la ERB, pilfered from John Resig's</span>
|
||||
<span class="line-numbers"> 526 </span> <span class="Comment"><span class="Comment">#</span> "Secrets of the JavaScript Ninja", page 83.</span>
|
||||
<span class="line-numbers"> 527 </span> <span class="FunctionName">_.template</span><span class="Keyword">:</span> <span class="FunctionArgument">str, data</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 528 </span> <span class="String"><span class="String">`</span>var fn = new Function('obj',</span>
|
||||
<span class="line-numbers"> 529 </span> <span class="String"> 'var p=[],print=function(){p.push.apply(p,arguments);};' +</span>
|
||||
<span class="line-numbers"> 530 </span> <span class="String"> 'with(obj){p.push(<span class="UserDefinedConstant">\'</span>' +</span>
|
||||
<span class="line-numbers"> 531 </span> <span class="String"> str.</span>
|
||||
<span class="line-numbers"> 532 </span> <span class="String"> replace(/[<span class="UserDefinedConstant">\r</span><span class="UserDefinedConstant">\t</span><span class="UserDefinedConstant">\n</span>]/g, " ").</span>
|
||||
<span class="line-numbers"> 533 </span> <span class="String"> split("<%").join("<span class="UserDefinedConstant">\t</span>").</span>
|
||||
<span class="line-numbers"> 534 </span> <span class="String"> replace(/((^|%>)[^<span class="UserDefinedConstant">\t</span>]*)'/g, "$1<span class="UserDefinedConstant">\r</span>").</span>
|
||||
<span class="line-numbers"> 535 </span> <span class="String"> replace(/<span class="UserDefinedConstant">\t</span>=(.*?)%>/g, "',$1,'").</span>
|
||||
<span class="line-numbers"> 536 </span> <span class="String"> split("<span class="UserDefinedConstant">\t</span>").join("');").</span>
|
||||
<span class="line-numbers"> 537 </span> <span class="String"> split("%>").join("p.push('").</span>
|
||||
<span class="line-numbers"> 538 </span> <span class="String"> split("<span class="UserDefinedConstant">\r</span>").join("<span class="UserDefinedConstant">\\</span>'") +</span>
|
||||
<span class="line-numbers"> 539 </span> <span class="String"> "');}return p.join('');")<span class="String">`</span></span>
|
||||
<span class="line-numbers"> 540 </span> <span class="Keyword">if</span> data <span class="Keyword">then</span> fn(data) <span class="Keyword">else</span> fn
|
||||
<span class="line-numbers"> 541 </span>
|
||||
<span class="line-numbers"> 542 </span>
|
||||
<span class="line-numbers"> 543 </span> <span class="Comment"><span class="Comment">#</span> ------------------------------- Aliases ----------------------------------</span>
|
||||
<span class="line-numbers"> 544 </span>
|
||||
<span class="line-numbers"> 545 </span> _.forEach<span class="Keyword">:</span> _.each
|
||||
<span class="line-numbers"> 546 </span> _.foldl<span class="Keyword">:</span> _.inject<span class="Keyword">:</span> _.reduce
|
||||
<span class="line-numbers"> 547 </span> _.foldr<span class="Keyword">:</span> _.reduceRight
|
||||
<span class="line-numbers"> 548 </span> _.filter<span class="Keyword">:</span> _.select
|
||||
<span class="line-numbers"> 549 </span> _.every<span class="Keyword">:</span> _.all
|
||||
<span class="line-numbers"> 550 </span> _.some<span class="Keyword">:</span> _.any
|
||||
<span class="line-numbers"> 551 </span> _.head<span class="Keyword">:</span> _.first
|
||||
<span class="line-numbers"> 552 </span> _.tail<span class="Keyword">:</span> _.rest
|
||||
<span class="line-numbers"> 553 </span> _.methods<span class="Keyword">:</span> _.functions
|
||||
<span class="line-numbers"> 554 </span>
|
||||
<span class="line-numbers"> 555 </span>
|
||||
<span class="line-numbers"> 556 </span> <span class="Comment"><span class="Comment">#</span> /*------------------------ Setup the OOP Wrapper: --------------------------*/</span>
|
||||
<span class="line-numbers"> 557 </span>
|
||||
<span class="line-numbers"> 558 </span> <span class="Comment"><span class="Comment">#</span> Helper function to continue chaining intermediate results.</span>
|
||||
<span class="line-numbers"> 559 </span> <span class="FunctionName">result</span><span class="Keyword">:</span> <span class="FunctionArgument">obj, chain</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 560 </span> <span class="Keyword">if</span> chain <span class="Keyword">then</span> _(obj).chain() <span class="Keyword">else</span> obj
|
||||
<span class="line-numbers"> 561 </span>
|
||||
<span class="line-numbers"> 562 </span>
|
||||
<span class="line-numbers"> 563 </span> <span class="Comment"><span class="Comment">#</span> Add all of the Underscore functions to the wrapper object.</span>
|
||||
<span class="line-numbers"> 564 </span> _.each(_.functions(_))<span class="FunctionArgument"> name </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 565 </span> method<span class="Keyword">:</span> _[name]
|
||||
<span class="line-numbers"> 566 </span> wrapper.prototype[name]<span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 567 </span> unshift.call(arguments, <span class="Variable">this</span>._wrapped)
|
||||
<span class="line-numbers"> 568 </span> result(method.apply(_, arguments), <span class="Variable">this</span>._chain)
|
||||
<span class="line-numbers"> 569 </span>
|
||||
<span class="line-numbers"> 570 </span>
|
||||
<span class="line-numbers"> 571 </span> <span class="Comment"><span class="Comment">#</span> Add all mutator Array functions to the wrapper.</span>
|
||||
<span class="line-numbers"> 572 </span> _.each([<span class="String"><span class="String">'</span>pop<span class="String">'</span></span>, <span class="String"><span class="String">'</span>push<span class="String">'</span></span>, <span class="String"><span class="String">'</span>reverse<span class="String">'</span></span>, <span class="String"><span class="String">'</span>shift<span class="String">'</span></span>, <span class="String"><span class="String">'</span>sort<span class="String">'</span></span>, <span class="String"><span class="String">'</span>splice<span class="String">'</span></span>, <span class="String"><span class="String">'</span>unshift<span class="String">'</span></span>])<span class="FunctionArgument"> name </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 573 </span> method<span class="Keyword">:</span> Array.prototype[name]
|
||||
<span class="line-numbers"> 574 </span> wrapper.prototype[name]<span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 575 </span> method.apply(<span class="Variable">this</span>._wrapped, arguments)
|
||||
<span class="line-numbers"> 576 </span> result(<span class="Variable">this</span>._wrapped, <span class="Variable">this</span>._chain)
|
||||
<span class="line-numbers"> 577 </span>
|
||||
<span class="line-numbers"> 578 </span>
|
||||
<span class="line-numbers"> 579 </span> <span class="Comment"><span class="Comment">#</span> Add all accessor Array functions to the wrapper.</span>
|
||||
<span class="line-numbers"> 580 </span> _.each([<span class="String"><span class="String">'</span>concat<span class="String">'</span></span>, <span class="String"><span class="String">'</span>join<span class="String">'</span></span>, <span class="String"><span class="String">'</span>slice<span class="String">'</span></span>])<span class="FunctionArgument"> name </span><span class="Storage">=></span>
|
||||
<span class="line-numbers"> 581 </span> method<span class="Keyword">:</span> Array.prototype[name]
|
||||
<span class="line-numbers"> 582 </span> wrapper.prototype[name]<span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 583 </span> result(method.apply(<span class="Variable">this</span>._wrapped, arguments), <span class="Variable">this</span>._chain)
|
||||
<span class="line-numbers"> 584 </span>
|
||||
<span class="line-numbers"> 585 </span>
|
||||
<span class="line-numbers"> 586 </span> <span class="Comment"><span class="Comment">#</span> Start chaining a wrapped Underscore object.</span>
|
||||
<span class="line-numbers"> 587 </span> <span class="FunctionName">wrapper.prototype.chain</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
<span class="line-numbers"> 588 </span> <span class="Variable">this</span>._chain<span class="Keyword">:</span> <span class="BuiltInConstant">true</span>
|
||||
<span class="line-numbers"> 589 </span> <span class="Variable">this</span>
|
||||
<span class="line-numbers"> 590 </span>
|
||||
<span class="line-numbers"> 591 </span>
|
||||
<span class="line-numbers"> 592 </span> <span class="Comment"><span class="Comment">#</span> Extracts the result from a wrapped and chained object.</span>
|
||||
<span class="line-numbers"> 593 </span> <span class="FunctionName">wrapper.prototype.value</span><span class="Keyword">:</span> <span class="Storage">=></span> <span class="Variable">this</span>._wrapped
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
173
examples/code.coffee
Normal file
173
examples/code.coffee
Normal file
@@ -0,0 +1,173 @@
|
||||
# Functions:
|
||||
square: x => x * x
|
||||
|
||||
sum: x, y => x + y
|
||||
|
||||
odd: x => x % 2 is 0
|
||||
|
||||
even: x => x % 2 isnt 0
|
||||
|
||||
run_loop: =>
|
||||
fire_events(e => e.stopPropagation())
|
||||
listen()
|
||||
wait()
|
||||
|
||||
# Objects:
|
||||
dense_object_literal: {one: 1, two: 2, three: 3}
|
||||
|
||||
spaced_out_multiline_object: {
|
||||
pi: 3.14159
|
||||
list: [1, 2, 3, 4]
|
||||
regex: /match[ing](every|thing|\/)/gi
|
||||
three: new Idea()
|
||||
|
||||
inner_obj: {
|
||||
freedom: => _.freedom()
|
||||
}
|
||||
}
|
||||
|
||||
# Arrays:
|
||||
stooges: [{moe: 45}, {curly: 43}, {larry: 46}]
|
||||
|
||||
exponents: [(x => x), (x => x * x), (x => x * x * x)]
|
||||
|
||||
empty: []
|
||||
|
||||
multiline: [
|
||||
'line one'
|
||||
'line two'
|
||||
]
|
||||
|
||||
# Conditionals and ternaries.
|
||||
if submarine.shields_up
|
||||
full_speed_ahead()
|
||||
fire_torpedos()
|
||||
else if submarine.sinking
|
||||
abandon_ship()
|
||||
else
|
||||
run_away()
|
||||
|
||||
eldest: if 25 > 21 then liz else marge
|
||||
|
||||
decoration: medal_of_honor if war_hero
|
||||
|
||||
go_to_sleep() unless coffee
|
||||
|
||||
# Returning early:
|
||||
race: =>
|
||||
run()
|
||||
walk()
|
||||
crawl()
|
||||
if tired then return sleep()
|
||||
race()
|
||||
|
||||
# Conditional assignment:
|
||||
good ||= evil
|
||||
wine &&= cheese
|
||||
|
||||
# Nested property access and calls.
|
||||
((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position['top'].offset('x')
|
||||
|
||||
a: b: c: 5
|
||||
|
||||
# Embedded JavaScript.
|
||||
callback(
|
||||
`function(e) { e.stop(); }`
|
||||
)
|
||||
|
||||
# Try/Catch/Finally/Throw.
|
||||
try
|
||||
all_hell_breaks_loose()
|
||||
dogs_and_cats_living_together()
|
||||
throw "up"
|
||||
catch error
|
||||
print(error)
|
||||
finally
|
||||
clean_up()
|
||||
|
||||
try all_hell_breaks_loose() catch error then print(error) finally clean_up()
|
||||
|
||||
# While loops, break and continue.
|
||||
while demand > supply
|
||||
sell()
|
||||
restock()
|
||||
|
||||
while supply > demand then buy()
|
||||
|
||||
while true
|
||||
break if broken
|
||||
continue if continuing
|
||||
|
||||
# Unary operators.
|
||||
!!true
|
||||
|
||||
# Lexical scoping.
|
||||
v_1: 5
|
||||
change_a_and_set_b: =>
|
||||
v_1: 10
|
||||
v_2: 15
|
||||
v_2: 20
|
||||
|
||||
# Array comprehensions.
|
||||
supper: food.capitalize() for food in ['toast', 'cheese', 'wine']
|
||||
|
||||
drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] when even(i)
|
||||
|
||||
# Switch statements ("else" serves as a default).
|
||||
activity: switch day
|
||||
when "Tuesday" then eat_breakfast()
|
||||
when "Sunday" then go_to_church()
|
||||
when "Saturday" then go_to_the_park()
|
||||
when "Wednesday"
|
||||
if day is bingo_day
|
||||
go_to_bingo()
|
||||
else
|
||||
eat_breakfast()
|
||||
go_to_work()
|
||||
eat_dinner()
|
||||
else go_to_work()
|
||||
|
||||
# Semicolons can optionally be used instead of newlines.
|
||||
wednesday: => eat_breakfast(); go_to_work(); eat_dinner()
|
||||
|
||||
# Array slice literals.
|
||||
zero_to_nine: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
three_to_six: zero_to_nine[3..6]
|
||||
|
||||
# Multiline strings with inner quotes.
|
||||
story: "Lorem ipsum dolor \"sit\" amet, consectetuer adipiscing elit,
|
||||
sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna
|
||||
aliquam erat volutpat. Ut wisi enim ad."
|
||||
|
||||
# Inheritance and calling super.
|
||||
Animal: =>
|
||||
Animal.prototype.move: meters =>
|
||||
alert(this.name + " moved " + meters + "m.")
|
||||
|
||||
Snake: name => this.name: name
|
||||
Snake extends Animal
|
||||
Snake.prototype.move: =>
|
||||
alert('Slithering...')
|
||||
super(5)
|
||||
|
||||
Horse: name => this.name: name
|
||||
Horse extends Animal
|
||||
Horse.prototype.move: =>
|
||||
alert('Galloping...')
|
||||
super(45)
|
||||
|
||||
sam: new Snake("Sammy the Snake")
|
||||
tom: new Horse("Tommy the Horse")
|
||||
|
||||
sam.move()
|
||||
tom.move()
|
||||
|
||||
# Numbers.
|
||||
a_googol: 1e100
|
||||
hex: 0xff0000
|
||||
negative: -1.0
|
||||
infinity: Infinity
|
||||
nan: NaN
|
||||
|
||||
# Deleting.
|
||||
delete secret.identity
|
||||
@@ -1,7 +1,7 @@
|
||||
# Document Model
|
||||
dc.model.Document: dc.Model.extend({
|
||||
|
||||
constructor: attributes => this.base(attributes).
|
||||
constructor: attributes => this.base(attributes)
|
||||
|
||||
# For display, show either the highlighted search results, or the summary,
|
||||
# if no highlights are available.
|
||||
@@ -9,22 +9,22 @@ dc.model.Document: dc.Model.extend({
|
||||
# version of the summary has all runs of whitespace squeezed out.
|
||||
displaySummary: =>
|
||||
text: this.get('highlight') or this.get('summary') or ''
|
||||
text and text.replace(/\s+/g, ' ').
|
||||
text and text.replace(/\s+/g, ' ')
|
||||
|
||||
# Return a list of the document's metadata. Think about caching this on the
|
||||
# document by binding to Metadata, instead of on-the-fly.
|
||||
metadata: =>
|
||||
docId: this.id
|
||||
_.select(Metadata.models()
|
||||
meta => _.any(meta.get('instances')
|
||||
instance => instance.document_id is docId.).).
|
||||
_.select(Metadata.models(), (meta =>
|
||||
_.any(meta.get('instances'), instance =>
|
||||
instance.document_id is docId)))
|
||||
|
||||
bookmark: pageNumber =>
|
||||
bookmark: new dc.model.Bookmark({title: this.get('title'), page_number: pageNumber, document_id: this.id})
|
||||
Bookmarks.create(bookmark).
|
||||
Bookmarks.create(bookmark)
|
||||
|
||||
# Inspect.
|
||||
toString: => 'Document ' + this.id + ' "' + this.get('title') + '"'.
|
||||
toString: => 'Document ' + this.id + ' "' + this.get('title') + '"'
|
||||
|
||||
})
|
||||
|
||||
@@ -37,31 +37,31 @@ dc.model.DocumentSet: dc.model.RESTfulSet.extend({
|
||||
|
||||
constructor: options =>
|
||||
this.base(options)
|
||||
_.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText').
|
||||
_.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText')
|
||||
|
||||
selected: => _.select(this.models(), m => m.get('selected').).
|
||||
selected: => _.select(this.models(), m => m.get('selected'))
|
||||
|
||||
selectedIds: => _.pluck(this.selected(), 'id').
|
||||
selectedIds: => _.pluck(this.selected(), 'id')
|
||||
|
||||
countSelected: => this.selected().length.
|
||||
countSelected: => this.selected().length
|
||||
|
||||
downloadSelectedViewers: =>
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip').
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip')
|
||||
|
||||
downloadSelectedPDF: =>
|
||||
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('pdf_url')).
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip').
|
||||
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('pdf_url'))
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip')
|
||||
|
||||
downloadSelectedFullText: =>
|
||||
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('full_text_url')).
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip').
|
||||
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('full_text_url'))
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip')
|
||||
|
||||
# We override "_onModelEvent" to fire selection changed events when documents
|
||||
# change their selected state.
|
||||
_onModelEvent: e, model =>
|
||||
this.base(e, model)
|
||||
fire: e == dc.Model.CHANGED and model.hasChanged('selected')
|
||||
if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this))..
|
||||
if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this))
|
||||
|
||||
})
|
||||
|
||||
153
examples/poignant.coffee
Normal file
153
examples/poignant.coffee
Normal file
@@ -0,0 +1,153 @@
|
||||
# Examples from the Poignant Guide.
|
||||
|
||||
# ['toast', 'cheese', 'wine'].each { |food| print food.capitalize }
|
||||
|
||||
['toast', 'wine', 'cheese'].each(food => print(food.capitalize()))
|
||||
|
||||
|
||||
|
||||
# class LotteryTicket
|
||||
# def picks; @picks; end
|
||||
# def picks=(var); @picks = var; end
|
||||
# def purchased; @purchased; end
|
||||
# def purchased=(var); @purchased = var; end
|
||||
# end
|
||||
|
||||
LotteryTicket: {
|
||||
get_picks: => this.picks
|
||||
set_picks: nums => this.picks: nums
|
||||
get_purchase: => this.purchase
|
||||
set_purchase: amount => this.purchase: amount
|
||||
}
|
||||
|
||||
|
||||
|
||||
# module WishScanner
|
||||
# def scan_for_a_wish
|
||||
# wish = self.read.detect do |thought|
|
||||
# thought.index( 'wish: ' ) == 0
|
||||
# end
|
||||
# wish.gsub( 'wish: ', '' )
|
||||
# end
|
||||
# end
|
||||
|
||||
WishScanner: {
|
||||
scan_for_a_wish: =>
|
||||
wish: this.read().detect(thought => thought.index('wish: ') is 0)
|
||||
wish.replace('wish: ', '')
|
||||
}
|
||||
|
||||
|
||||
|
||||
# class Creature
|
||||
#
|
||||
# # This method applies a hit taken during a fight.
|
||||
# def hit( damage )
|
||||
# p_up = rand( charisma )
|
||||
# if p_up % 9 == 7
|
||||
# @life += p_up / 4
|
||||
# puts "[#{ self.class } magick powers up #{ p_up }!]"
|
||||
# end
|
||||
# @life -= damage
|
||||
# puts "[#{ self.class } has died.]" if @life <= 0
|
||||
# end
|
||||
#
|
||||
# # This method takes one turn in a fight.
|
||||
# def fight( enemy, weapon )
|
||||
# if life <= 0
|
||||
# puts "[#{ self.class } is too dead to fight!]"
|
||||
# return
|
||||
# end
|
||||
#
|
||||
# # Attack the opponent
|
||||
# your_hit = rand( strength + weapon )
|
||||
# puts "[You hit with #{ your_hit } points of damage!]"
|
||||
# enemy.hit( your_hit )
|
||||
#
|
||||
# # Retaliation
|
||||
# p enemy
|
||||
# if enemy.life > 0
|
||||
# enemy_hit = rand( enemy.strength + enemy.weapon )
|
||||
# puts "[Your enemy hit with #{ enemy_hit } points of damage!]"
|
||||
# self.hit( enemy_hit )
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# end
|
||||
|
||||
Creature : {
|
||||
|
||||
# This method applies a hit taken during a fight.
|
||||
hit: damage =>
|
||||
p_up: Math.rand(this.charisma)
|
||||
if p_up % 9 is 7
|
||||
this.life += p_up / 4
|
||||
puts("[" + this.name + " magick powers up " + p_up + "!]")
|
||||
this.life -= damage
|
||||
if this.life <= 0 then puts("[" + this.name + " has died.]")
|
||||
|
||||
# This method takes one turn in a fight.
|
||||
fight: enemy, weapon =>
|
||||
if this.life <= 0 then return puts("[" + this.name + "is too dead to fight!]")
|
||||
|
||||
# Attack the opponent.
|
||||
your_hit: Math.rand(this.strength + weapon)
|
||||
puts("[You hit with " + your_hit + "points of damage!]")
|
||||
enemy.hit(your_hit)
|
||||
|
||||
# Retaliation.
|
||||
puts(enemy)
|
||||
if enemy.life > 0
|
||||
enemy_hit: Math.rand(enemy.strength + enemy.weapon)
|
||||
puts("[Your enemy hit with " + enemy_hit + "points of damage!]")
|
||||
this.hit(enemy_hit)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
# # Get evil idea and swap in code words
|
||||
# print "Enter your new idea: "
|
||||
# idea = gets
|
||||
# code_words.each do |real, code|
|
||||
# idea.gsub!( real, code )
|
||||
# end
|
||||
#
|
||||
# # Save the jibberish to a new file
|
||||
# print "File encoded. Please enter a name for this idea: "
|
||||
# idea_name = gets.strip
|
||||
# File::open( "idea-" + idea_name + ".txt", "w" ) do |f|
|
||||
# f << idea
|
||||
# end
|
||||
|
||||
# Get evil idea and swap in code words
|
||||
print("Enter your new idea: ")
|
||||
idea: gets()
|
||||
code_words.each(real, code => idea.replace(real, code))
|
||||
|
||||
# Save the jibberish to a new file
|
||||
print("File encoded. Please enter a name for this idea: ")
|
||||
idea_name: gets().strip()
|
||||
File.open("idea-" + idea_name + '.txt', 'w', file => file.write(idea))
|
||||
|
||||
|
||||
|
||||
# def wipe_mutterings_from( sentence )
|
||||
# unless sentence.respond_to? :include?
|
||||
# raise ArgumentError,
|
||||
# "cannot wipe mutterings from a #{ sentence.class }"
|
||||
# end
|
||||
# while sentence.include? '('
|
||||
# open = sentence.index( '(' )
|
||||
# close = sentence.index( ')', open )
|
||||
# sentence[open..close] = '' if close
|
||||
# end
|
||||
# end
|
||||
|
||||
wipe_mutterings_from: sentence =>
|
||||
throw new Error("cannot wipe mutterings") unless sentence.indexOf
|
||||
while sentence.indexOf('(') >= 0
|
||||
open: sentence.indexOf('(') - 1
|
||||
close: sentence.indexOf(')') + 1
|
||||
sentence: sentence[0..open] + sentence[close..sentence.length]
|
||||
sentence
|
||||
20
examples/syntax_errors.coffee
Normal file
20
examples/syntax_errors.coffee
Normal file
@@ -0,0 +1,20 @@
|
||||
# Identifiers run together:
|
||||
# a b c
|
||||
|
||||
# Trailing comma in array:
|
||||
# array: [1, 2, 3, 4, 5,]
|
||||
|
||||
# Unterminated object literal:
|
||||
# obj: { one: 1, two: 2
|
||||
|
||||
# Numbers run together:
|
||||
# 101 202
|
||||
|
||||
# Strings run together:
|
||||
# str: "broken" "words"
|
||||
|
||||
# Forgot to terminate a function:
|
||||
# obj: {
|
||||
# first: a => a[0].
|
||||
# last: a => a[a.length-1]
|
||||
# }
|
||||
593
examples/underscore.coffee
Normal file
593
examples/underscore.coffee
Normal file
@@ -0,0 +1,593 @@
|
||||
|
||||
# Underscore.coffee
|
||||
# (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
# Underscore is freely distributable under the terms of the MIT license.
|
||||
# Portions of Underscore are inspired by or borrowed from Prototype.js,
|
||||
# Oliver Steele's Functional, and John Resig's Micro-Templating.
|
||||
# For all details and documentation:
|
||||
# http://documentcloud.github.com/underscore/
|
||||
|
||||
|
||||
# ------------------------- Baseline setup ---------------------------------
|
||||
|
||||
# Establish the root object, "window" in the browser, or "global" on the server.
|
||||
root: this
|
||||
|
||||
|
||||
# Save the previous value of the "_" variable.
|
||||
previousUnderscore: root._
|
||||
|
||||
|
||||
# If Underscore is called as a function, it returns a wrapped object that
|
||||
# can be used OO-style. This wrapper holds altered versions of all the
|
||||
# underscore functions. Wrapped objects may be chained.
|
||||
wrapper: obj =>
|
||||
this._wrapped: obj
|
||||
this
|
||||
|
||||
|
||||
# Establish the object that gets thrown to break out of a loop iteration.
|
||||
breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
|
||||
|
||||
|
||||
# Create a safe reference to the Underscore object for reference below.
|
||||
_: root._: obj => new wrapper(obj)
|
||||
|
||||
|
||||
# Export the Underscore object for CommonJS.
|
||||
if typeof(exports) != 'undefined' then exports._: _
|
||||
|
||||
|
||||
# Create quick reference variables for speed access to core prototypes.
|
||||
slice: Array.prototype.slice
|
||||
unshift: Array.prototype.unshift
|
||||
toString: Object.prototype.toString
|
||||
hasOwnProperty: Object.prototype.hasOwnProperty
|
||||
propertyIsEnumerable: Object.prototype.propertyIsEnumerable
|
||||
|
||||
|
||||
# Current version.
|
||||
_.VERSION: '0.5.3'
|
||||
|
||||
|
||||
# ------------------------ Collection Functions: ---------------------------
|
||||
|
||||
# The cornerstone, an each implementation.
|
||||
# Handles objects implementing forEach, arrays, and raw objects.
|
||||
_.each: obj, iterator, context =>
|
||||
index: 0
|
||||
try
|
||||
return obj.forEach(iterator, context) if obj.forEach
|
||||
if _.isArray(obj) or _.isArguments(obj)
|
||||
return iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
|
||||
iterator.call(context, val, key, obj) for val, key in obj
|
||||
catch e
|
||||
throw e if e isnt breaker
|
||||
obj
|
||||
|
||||
|
||||
# Return the results of applying the iterator to each element. Use JavaScript
|
||||
# 1.6's version of map, if possible.
|
||||
_.map: obj, iterator, context =>
|
||||
return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
|
||||
results: []
|
||||
_.each(obj) value, index, list =>
|
||||
results.push(iterator.call(context, value, index, list))
|
||||
results
|
||||
|
||||
|
||||
# Reduce builds up a single result from a list of values. Also known as
|
||||
# inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
|
||||
_.reduce: obj, memo, iterator, context =>
|
||||
return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce))
|
||||
_.each(obj) value, index, list =>
|
||||
memo: iterator.call(context, memo, value, index, list)
|
||||
memo
|
||||
|
||||
|
||||
# The right-associative version of reduce, also known as foldr. Uses
|
||||
# JavaScript 1.8's version of reduceRight, if available.
|
||||
_.reduceRight: obj, memo, iterator, context =>
|
||||
return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight))
|
||||
_.each(_.clone(_.toArray(obj)).reverse()) value, index =>
|
||||
memo: iterator.call(context, memo, value, index, obj)
|
||||
memo
|
||||
|
||||
|
||||
# Return the first value which passes a truth test.
|
||||
_.detect: obj, iterator, context =>
|
||||
result: null
|
||||
_.each(obj) value, index, list =>
|
||||
if iterator.call(context, value, index, list)
|
||||
result: value
|
||||
_.breakLoop()
|
||||
result
|
||||
|
||||
|
||||
# Return all the elements that pass a truth test. Use JavaScript 1.6's
|
||||
# filter(), if it exists.
|
||||
_.select: obj, iterator, context =>
|
||||
if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context)
|
||||
results: []
|
||||
_.each(obj) value, index, list =>
|
||||
results.push(value) if iterator.call(context, value, index, list)
|
||||
results
|
||||
|
||||
|
||||
# Return all the elements for which a truth test fails.
|
||||
_.reject: obj, iterator, context =>
|
||||
results: []
|
||||
_.each(obj) value, index, list =>
|
||||
results.push(value) if not iterator.call(context, value, index, list)
|
||||
results
|
||||
|
||||
|
||||
# Determine whether all of the elements match a truth test. Delegate to
|
||||
# JavaScript 1.6's every(), if it is present.
|
||||
_.all: obj, iterator, context =>
|
||||
iterator ||= _.identity
|
||||
return obj.every(iterator, context) if obj and _.isFunction(obj.every)
|
||||
result: true
|
||||
_.each(obj) value, index, list =>
|
||||
_.breakLoop() unless (result: result and iterator.call(context, value, index, list))
|
||||
result
|
||||
|
||||
|
||||
# Determine if at least one element in the object matches a truth test. Use
|
||||
# JavaScript 1.6's some(), if it exists.
|
||||
_.any: obj, iterator, context =>
|
||||
iterator ||= _.identity
|
||||
return obj.some(iterator, context) if obj and _.isFunction(obj.some)
|
||||
result: false
|
||||
_.each(obj) value, index, list =>
|
||||
_.breakLoop() if (result: iterator.call(context, value, index, list))
|
||||
result
|
||||
|
||||
|
||||
# Determine if a given value is included in the array or object,
|
||||
# based on '==='.
|
||||
_.include: obj, target =>
|
||||
return _.indexOf(obj, target) isnt -1 if _.isArray(obj)
|
||||
for val in obj
|
||||
return true if val is target
|
||||
false
|
||||
|
||||
|
||||
# Invoke a method with arguments on every item in a collection.
|
||||
_.invoke: obj, method =>
|
||||
args: _.rest(arguments, 2)
|
||||
(if method then val[method] else val).apply(val, args) for val in obj
|
||||
|
||||
|
||||
# Convenience version of a common use case of map: fetching a property.
|
||||
_.pluck: obj, key =>
|
||||
_.map(obj, (val => val[key]))
|
||||
|
||||
|
||||
# Return the maximum item or (item-based computation).
|
||||
_.max: obj, iterator, context =>
|
||||
return Math.max.apply(Math, obj) if not iterator and _.isArray(obj)
|
||||
result: {computed: -Infinity}
|
||||
_.each(obj) value, index, list =>
|
||||
computed: if iterator then iterator.call(context, value, index, list) else value
|
||||
computed >= result.computed and (result: {value: value, computed: computed})
|
||||
result.value
|
||||
|
||||
|
||||
# Return the minimum element (or element-based computation).
|
||||
_.min: obj, iterator, context =>
|
||||
return Math.min.apply(Math, obj) if not iterator and _.isArray(obj)
|
||||
result: {computed: Infinity}
|
||||
_.each(obj) value, index, list =>
|
||||
computed: if iterator then iterator.call(context, value, index, list) else value
|
||||
computed < result.computed and (result: {value: value, computed: computed})
|
||||
result.value
|
||||
|
||||
|
||||
# Sort the object's values by a criteria produced by an iterator.
|
||||
_.sortBy: obj, iterator, context =>
|
||||
_.pluck(((_.map(obj) value, index, list =>
|
||||
{value: value, criteria: iterator.call(context, value, index, list)}
|
||||
).sort() left, right =>
|
||||
a: left.criteria; b: right.criteria
|
||||
if a < b then -1 else if a > b then 1 else 0
|
||||
), 'value')
|
||||
|
||||
|
||||
# Use a comparator function to figure out at what index an object should
|
||||
# be inserted so as to maintain order. Uses binary search.
|
||||
_.sortedIndex: array, obj, iterator =>
|
||||
iterator ||= _.identity
|
||||
low: 0; high: array.length
|
||||
while low < high
|
||||
mid: (low + high) >> 1
|
||||
if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid
|
||||
low
|
||||
|
||||
|
||||
# Convert anything iterable into a real, live array.
|
||||
_.toArray: iterable =>
|
||||
return [] if (!iterable)
|
||||
return iterable.toArray() if (iterable.toArray)
|
||||
return iterable if (_.isArray(iterable))
|
||||
return slice.call(iterable) if (_.isArguments(iterable))
|
||||
_.values(iterable)
|
||||
|
||||
|
||||
# Return the number of elements in an object.
|
||||
_.size: obj => _.toArray(obj).length
|
||||
|
||||
|
||||
# -------------------------- Array Functions: ------------------------------
|
||||
|
||||
# Get the first element of an array. Passing "n" will return the first N
|
||||
# values in the array. Aliased as "head". The "guard" check allows it to work
|
||||
# with _.map.
|
||||
_.first: array, n, guard =>
|
||||
if n and not guard then slice.call(array, 0, n) else array[0]
|
||||
|
||||
|
||||
# Returns everything but the first entry of the array. Aliased as "tail".
|
||||
# Especially useful on the arguments object. Passing an "index" will return
|
||||
# the rest of the values in the array from that index onward. The "guard"
|
||||
# check allows it to work with _.map.
|
||||
_.rest: array, index, guard =>
|
||||
slice.call(array, if _.isUndefined(index) or guard then 1 else index)
|
||||
|
||||
|
||||
# Get the last element of an array.
|
||||
_.last: array => array[array.length - 1]
|
||||
|
||||
|
||||
# Trim out all falsy values from an array.
|
||||
_.compact: array => array[i] for i in [0...array.length] when array[i]
|
||||
|
||||
|
||||
# Return a completely flattened version of an array.
|
||||
_.flatten: array =>
|
||||
_.reduce(array, []) memo, value =>
|
||||
return memo.concat(_.flatten(value)) if _.isArray(value)
|
||||
memo.push(value)
|
||||
memo
|
||||
|
||||
|
||||
# Return a version of the array that does not contain the specified value(s).
|
||||
_.without: array =>
|
||||
values: _.rest(arguments)
|
||||
val for val in _.toArray(array) when not _.include(values, val)
|
||||
|
||||
|
||||
# Produce a duplicate-free version of the array. If the array has already
|
||||
# been sorted, you have the option of using a faster algorithm.
|
||||
_.uniq: array, isSorted =>
|
||||
memo: []
|
||||
for el, i in _.toArray(array)
|
||||
memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
|
||||
memo
|
||||
|
||||
|
||||
# Produce an array that contains every item shared between all the
|
||||
# passed-in arrays.
|
||||
_.intersect: array =>
|
||||
rest: _.rest(arguments)
|
||||
_.select(_.uniq(array)) item =>
|
||||
_.all(rest) other =>
|
||||
_.indexOf(other, item) >= 0
|
||||
|
||||
|
||||
# Zip together multiple lists into a single array -- elements that share
|
||||
# an index go together.
|
||||
_.zip: =>
|
||||
args: _.toArray(arguments)
|
||||
length: _.max(_.pluck(args, 'length'))
|
||||
results: new Array(length)
|
||||
for i in [0...length]
|
||||
results[i]: _.pluck(args, String(i))
|
||||
results
|
||||
|
||||
|
||||
# If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),
|
||||
# we need this function. Return the position of the first occurence of an
|
||||
# item in an array, or -1 if the item is not included in the array.
|
||||
_.indexOf: array, item =>
|
||||
return array.indexOf(item) if array.indexOf
|
||||
i: 0; l: array.length
|
||||
while l - i
|
||||
if array[i] is item then return i else i++
|
||||
-1
|
||||
|
||||
|
||||
# Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
|
||||
# if possible.
|
||||
_.lastIndexOf: array, item =>
|
||||
return array.lastIndexOf(item) if array.lastIndexOf
|
||||
i: array.length
|
||||
while i
|
||||
if array[i] is item then return i else i--
|
||||
-1
|
||||
|
||||
|
||||
# Generate an integer Array containing an arithmetic progression. A port of
|
||||
# the native Python range() function. See:
|
||||
# http://docs.python.org/library/functions.html#range
|
||||
_.range: start, stop, step =>
|
||||
a: _.toArray(arguments)
|
||||
solo: a.length <= 1
|
||||
i: start: if solo then 0 else a[0];
|
||||
stop: if solo then a[0] else a[1];
|
||||
step: a[2] or 1
|
||||
len: Math.ceil((stop - start) / step)
|
||||
return [] if len <= 0
|
||||
range: new Array(len)
|
||||
idx: 0
|
||||
while true
|
||||
return range if (if step > 0 then i - stop else stop - i) >= 0
|
||||
range[idx]: i
|
||||
idx++
|
||||
i+= step
|
||||
|
||||
|
||||
# ----------------------- Function Functions: -----------------------------
|
||||
|
||||
# Create a function bound to a given object (assigning 'this', and arguments,
|
||||
# optionally). Binding with arguments is also known as 'curry'.
|
||||
_.bind: func, obj =>
|
||||
args: _.rest(arguments, 2)
|
||||
=> func.apply(obj or root, args.concat(_.toArray(arguments)))
|
||||
|
||||
|
||||
# Bind all of an object's methods to that object. Useful for ensuring that
|
||||
# all callbacks defined on an object belong to it.
|
||||
_.bindAll: obj =>
|
||||
funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
|
||||
_.each(funcs, (f => obj[f]: _.bind(obj[f], obj)))
|
||||
obj
|
||||
|
||||
|
||||
# Delays a function for the given number of milliseconds, and then calls
|
||||
# it with the arguments supplied.
|
||||
_.delay: func, wait =>
|
||||
args: _.rest(arguments, 2)
|
||||
setTimeout((=> func.apply(func, args)), wait)
|
||||
|
||||
|
||||
# Defers a function, scheduling it to run after the current call stack has
|
||||
# cleared.
|
||||
_.defer: func =>
|
||||
_.delay.apply(_, [func, 1].concat(_.rest(arguments)))
|
||||
|
||||
|
||||
# Returns the first function passed as an argument to the second,
|
||||
# allowing you to adjust arguments, run code before and after, and
|
||||
# conditionally execute the original function.
|
||||
_.wrap: func, wrapper =>
|
||||
=> wrapper.apply(wrapper, [func].concat(_.toArray(arguments)))
|
||||
|
||||
|
||||
# Returns a function that is the composition of a list of functions, each
|
||||
# consuming the return value of the function that follows.
|
||||
_.compose: =>
|
||||
funcs: _.toArray(arguments)
|
||||
=>
|
||||
args: _.toArray(arguments)
|
||||
for i in [(funcs.length - 1)..0]
|
||||
args: [funcs[i].apply(this, args)]
|
||||
args[0]
|
||||
|
||||
|
||||
# ------------------------- Object Functions: ----------------------------
|
||||
|
||||
# Retrieve the names of an object's properties.
|
||||
_.keys: obj =>
|
||||
return _.range(0, obj.length) if _.isArray(obj)
|
||||
key for val, key in obj
|
||||
|
||||
|
||||
# Retrieve the values of an object's properties.
|
||||
_.values: obj =>
|
||||
_.map(obj, _.identity)
|
||||
|
||||
|
||||
# Return a sorted list of the function names available in Underscore.
|
||||
_.functions: obj =>
|
||||
_.select(_.keys(obj), key => _.isFunction(obj[key])).sort()
|
||||
|
||||
|
||||
# Extend a given object with all of the properties in a source object.
|
||||
_.extend: destination, source =>
|
||||
for val, key in source
|
||||
destination[key]: val
|
||||
destination
|
||||
|
||||
|
||||
# Create a (shallow-cloned) duplicate of an object.
|
||||
_.clone: obj =>
|
||||
return obj.slice(0) if _.isArray(obj)
|
||||
_.extend({}, obj)
|
||||
|
||||
|
||||
# Invokes interceptor with the obj, and then returns obj.
|
||||
# The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
|
||||
_.tap: obj, interceptor =>
|
||||
interceptor(obj)
|
||||
obj
|
||||
|
||||
|
||||
# Perform a deep comparison to check if two objects are equal.
|
||||
_.isEqual: a, b =>
|
||||
# Check object identity.
|
||||
return true if a is b
|
||||
# Different types?
|
||||
atype: typeof(a); btype: typeof(b)
|
||||
return false if atype isnt btype
|
||||
# Basic equality test (watch out for coercions).
|
||||
return true if `a == b`
|
||||
# One is falsy and the other truthy.
|
||||
return false if (!a and b) or (a and !b)
|
||||
# One of them implements an isEqual()?
|
||||
return a.isEqual(b) if a.isEqual
|
||||
# Check dates' integer values.
|
||||
return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b)
|
||||
# Both are NaN?
|
||||
return true if _.isNaN(a) and _.isNaN(b)
|
||||
# Compare regular expressions.
|
||||
if _.isRegExp(a) and _.isRegExp(b)
|
||||
return a.source is b.source and
|
||||
a.global is b.global and
|
||||
a.ignoreCase is b.ignoreCase and
|
||||
a.multiline is b.multiline
|
||||
# If a is not an object by this point, we can't handle it.
|
||||
return false if atype isnt 'object'
|
||||
# Check for different array lengths before comparing contents.
|
||||
return false if a.length and (a.length isnt b.length)
|
||||
# Nothing else worked, deep compare the contents.
|
||||
aKeys: _.keys(a); bKeys: _.keys(b)
|
||||
# Different object sizes?
|
||||
return false if aKeys.length isnt bKeys.length
|
||||
# Recursive comparison of contents.
|
||||
# for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
|
||||
return true
|
||||
|
||||
|
||||
# Is a given array or object empty?
|
||||
_.isEmpty: obj => _.keys(obj).length is 0
|
||||
|
||||
|
||||
# Is a given value a DOM element?
|
||||
_.isElement: obj => obj and obj.nodeType is 1
|
||||
|
||||
|
||||
# Is a given value an array?
|
||||
_.isArray: obj => !!(obj and obj.concat and obj.unshift)
|
||||
|
||||
|
||||
# Is a given variable an arguments object?
|
||||
_.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length')
|
||||
|
||||
|
||||
# Is the given value a function?
|
||||
_.isFunction: obj => !!(obj and obj.constructor and obj.call and obj.apply)
|
||||
|
||||
|
||||
# Is the given value a string?
|
||||
_.isString: obj => !!(obj is '' or (obj and obj.charCodeAt and obj.substr))
|
||||
|
||||
|
||||
# Is a given value a number?
|
||||
_.isNumber: obj => toString.call(obj) is '[object Number]'
|
||||
|
||||
|
||||
# Is a given value a Date?
|
||||
_.isDate: obj => !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
|
||||
|
||||
|
||||
# Is the given value a regular expression?
|
||||
_.isRegExp: obj => !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false))
|
||||
|
||||
|
||||
# Is the given value NaN -- this one is interesting. NaN != NaN, and
|
||||
# isNaN(undefined) == true, so we make sure it's a number first.
|
||||
_.isNaN: obj => _.isNumber(obj) and window.isNaN(obj)
|
||||
|
||||
|
||||
# Is a given value equal to null?
|
||||
_.isNull: obj => obj is null
|
||||
|
||||
|
||||
# Is a given variable undefined?
|
||||
_.isUndefined: obj => typeof obj is 'undefined'
|
||||
|
||||
|
||||
# -------------------------- Utility Functions: --------------------------
|
||||
|
||||
# Run Underscore.js in noConflict mode, returning the '_' variable to its
|
||||
# previous owner. Returns a reference to the Underscore object.
|
||||
_.noConflict: =>
|
||||
root._: previousUnderscore
|
||||
this
|
||||
|
||||
|
||||
# Keep the identity function around for default iterators.
|
||||
_.identity: value => value
|
||||
|
||||
|
||||
# Break out of the middle of an iteration.
|
||||
_.breakLoop: => throw breaker
|
||||
|
||||
|
||||
# Generate a unique integer id (unique within the entire client session).
|
||||
# Useful for temporary DOM ids.
|
||||
idCounter: 0
|
||||
_.uniqueId: prefix =>
|
||||
(prefix or '') + idCounter++
|
||||
|
||||
|
||||
# JavaScript templating a-la ERB, pilfered from John Resig's
|
||||
# "Secrets of the JavaScript Ninja", page 83.
|
||||
_.template: str, data =>
|
||||
`var fn = new Function('obj',
|
||||
'var p=[],print=function(){p.push.apply(p,arguments);};' +
|
||||
'with(obj){p.push(\'' +
|
||||
str.
|
||||
replace(/[\r\t\n]/g, " ").
|
||||
split("<%").join("\t").
|
||||
replace(/((^|%>)[^\t]*)'/g, "$1\r").
|
||||
replace(/\t=(.*?)%>/g, "',$1,'").
|
||||
split("\t").join("');").
|
||||
split("%>").join("p.push('").
|
||||
split("\r").join("\\'") +
|
||||
"');}return p.join('');")`
|
||||
if data then fn(data) else fn
|
||||
|
||||
|
||||
# ------------------------------- Aliases ----------------------------------
|
||||
|
||||
_.forEach: _.each
|
||||
_.foldl: _.inject: _.reduce
|
||||
_.foldr: _.reduceRight
|
||||
_.filter: _.select
|
||||
_.every: _.all
|
||||
_.some: _.any
|
||||
_.head: _.first
|
||||
_.tail: _.rest
|
||||
_.methods: _.functions
|
||||
|
||||
|
||||
# /*------------------------ Setup the OOP Wrapper: --------------------------*/
|
||||
|
||||
# Helper function to continue chaining intermediate results.
|
||||
result: obj, chain =>
|
||||
if chain then _(obj).chain() else obj
|
||||
|
||||
|
||||
# Add all of the Underscore functions to the wrapper object.
|
||||
_.each(_.functions(_)) name =>
|
||||
method: _[name]
|
||||
wrapper.prototype[name]: =>
|
||||
unshift.call(arguments, this._wrapped)
|
||||
result(method.apply(_, arguments), this._chain)
|
||||
|
||||
|
||||
# Add all mutator Array functions to the wrapper.
|
||||
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']) name =>
|
||||
method: Array.prototype[name]
|
||||
wrapper.prototype[name]: =>
|
||||
method.apply(this._wrapped, arguments)
|
||||
result(this._wrapped, this._chain)
|
||||
|
||||
|
||||
# Add all accessor Array functions to the wrapper.
|
||||
_.each(['concat', 'join', 'slice']) name =>
|
||||
method: Array.prototype[name]
|
||||
wrapper.prototype[name]: =>
|
||||
result(method.apply(this._wrapped, arguments), this._chain)
|
||||
|
||||
|
||||
# Start chaining a wrapped Underscore object.
|
||||
wrapper.prototype.chain: =>
|
||||
this._chain: true
|
||||
this
|
||||
|
||||
|
||||
# Extracts the result from a wrapped and chained object.
|
||||
wrapper.prototype.value: => this._wrapped
|
||||
298
grammar.y
298
grammar.y
@@ -1,298 +0,0 @@
|
||||
class Parser
|
||||
|
||||
# Declare tokens produced by the lexer
|
||||
token IF ELSE THEN UNLESS
|
||||
token NUMBER STRING REGEX
|
||||
token TRUE FALSE NULL
|
||||
token IDENTIFIER PROPERTY_ACCESS
|
||||
token CODE PARAM NEW RETURN
|
||||
token TRY CATCH FINALLY THROW
|
||||
token BREAK CONTINUE
|
||||
token FOR IN WHILE
|
||||
token SWITCH CASE DEFAULT
|
||||
token NEWLINE
|
||||
token JS
|
||||
|
||||
# Declare order of operations.
|
||||
prechigh
|
||||
nonassoc UMINUS NOT '!'
|
||||
left '*' '/' '%'
|
||||
left '+' '-'
|
||||
left '<=' '<' '>' '>='
|
||||
right '==' '!=' IS AINT
|
||||
left '&&' '||' AND OR
|
||||
left ':'
|
||||
right '-=' '+=' '/=' '*=' '||=' '&&='
|
||||
nonassoc IF
|
||||
left UNLESS
|
||||
right RETURN THROW FOR WHILE
|
||||
nonassoc "."
|
||||
preclow
|
||||
|
||||
# We expect 2 shift/reduce errors for optional syntax.
|
||||
# There used to be 252 -- greatly improved.
|
||||
expect 2
|
||||
|
||||
rule
|
||||
|
||||
# All parsing will end in this rule, being the trunk of the AST.
|
||||
Root:
|
||||
/* nothing */ { result = Nodes.new([]) }
|
||||
| Terminator { result = Nodes.new([]) }
|
||||
| Expressions { result = val[0] }
|
||||
;
|
||||
|
||||
# Any list of expressions or method body, seperated by line breaks or semis.
|
||||
Expressions:
|
||||
Expression { result = Nodes.new(val) }
|
||||
| Expressions Terminator Expression { result = val[0] << val[2] }
|
||||
| Expressions Terminator { result = val[0] }
|
||||
| Terminator Expressions { result = val[1] }
|
||||
;
|
||||
|
||||
# All types of expressions in our language
|
||||
Expression:
|
||||
Literal
|
||||
| Value
|
||||
| Call
|
||||
| Assign
|
||||
| Code
|
||||
| Operation
|
||||
| If
|
||||
| Try
|
||||
| Throw
|
||||
| Return
|
||||
| While
|
||||
| For
|
||||
| Switch
|
||||
;
|
||||
|
||||
# All tokens that can terminate an expression
|
||||
Terminator:
|
||||
"\n"
|
||||
| ";"
|
||||
;
|
||||
|
||||
# All tokens that can serve to begin the second block
|
||||
Then:
|
||||
THEN
|
||||
| Terminator
|
||||
;
|
||||
|
||||
# All hard-coded values
|
||||
Literal:
|
||||
NUMBER { result = LiteralNode.new(val[0]) }
|
||||
| STRING { result = LiteralNode.new(val[0]) }
|
||||
| JS { result = LiteralNode.new(val[0]) }
|
||||
| REGEX { result = LiteralNode.new(val[0]) }
|
||||
| TRUE { result = LiteralNode.new(true) }
|
||||
| FALSE { result = LiteralNode.new(false) }
|
||||
| NULL { result = LiteralNode.new(nil) }
|
||||
| BREAK { result = LiteralNode.new(val[0]) }
|
||||
| CONTINUE { result = LiteralNode.new(val[0]) }
|
||||
;
|
||||
|
||||
# Assign to a variable
|
||||
Assign:
|
||||
Value ":" Expression { result = AssignNode.new(val[0], val[2]) }
|
||||
;
|
||||
|
||||
# Assignment within an object literal.
|
||||
AssignObj:
|
||||
IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
|
||||
;
|
||||
|
||||
# A Return statement.
|
||||
Return:
|
||||
RETURN Expression { result = ReturnNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# Arithmetic and logical operators
|
||||
# For Ruby's Operator precedence, see:
|
||||
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
|
||||
Operation:
|
||||
'!' Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
|
||||
| NOT Expression { result = OpNode.new(val[0], val[1]) }
|
||||
|
||||
|
||||
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression AINT Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
;
|
||||
|
||||
|
||||
# Method definition
|
||||
Code:
|
||||
ParamList "=>" Expressions "." { result = CodeNode.new(val[0], val[2]) }
|
||||
| "=>" Expressions "." { result = CodeNode.new([], val[1]) }
|
||||
;
|
||||
|
||||
ParamList:
|
||||
PARAM { result = val }
|
||||
| ParamList "," PARAM { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
Value:
|
||||
IDENTIFIER { result = ValueNode.new(val) }
|
||||
| Array { result = ValueNode.new(val) }
|
||||
| Object { result = ValueNode.new(val) }
|
||||
| Parenthetical { result = ValueNode.new(val) }
|
||||
| Value Accessor { result = val[0] << val[1] }
|
||||
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
|
||||
;
|
||||
|
||||
Accessor:
|
||||
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
|
||||
| Index { result = val[0] }
|
||||
| Slice { result = val[0] }
|
||||
;
|
||||
|
||||
Index:
|
||||
"[" Expression "]" { result = IndexNode.new(val[1]) }
|
||||
;
|
||||
|
||||
Slice:
|
||||
"[" Expression "," Expression "]" { result = SliceNode.new(val[1], val[3]) }
|
||||
;
|
||||
|
||||
Object:
|
||||
"{" AssignList "}" { result = ObjectNode.new(val[1]) }
|
||||
;
|
||||
|
||||
AssignList:
|
||||
/* nothing */ { result = []}
|
||||
| AssignObj { result = val }
|
||||
| AssignList "," AssignObj { result = val[0] << val[2] }
|
||||
| AssignList Terminator AssignObj { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
# A method call.
|
||||
Call:
|
||||
Invocation { result = val[0] }
|
||||
| NEW Invocation { result = val[1].new_instance }
|
||||
;
|
||||
|
||||
Invocation:
|
||||
Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
|
||||
;
|
||||
|
||||
# An Array.
|
||||
Array:
|
||||
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# A list of arguments to a method call.
|
||||
ArgList:
|
||||
/* nothing */ { result = [] }
|
||||
| Expression { result = val }
|
||||
| ArgList "," Expression { result = val[0] << val[2] }
|
||||
| ArgList Terminator Expression { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
If:
|
||||
IF Expression
|
||||
Then Expressions "." { result = IfNode.new(val[1], val[3]) }
|
||||
| IF Expression
|
||||
Then Expressions
|
||||
ELSE Expressions "." { result = IfNode.new(val[1], val[3], val[5]) }
|
||||
| Expression IF Expression { result = IfNode.new(val[2], Nodes.new([val[0]])) }
|
||||
| Expression UNLESS Expression { result = IfNode.new(val[2], Nodes.new([val[0]]), nil, :invert) }
|
||||
;
|
||||
|
||||
Try:
|
||||
TRY Expressions CATCH IDENTIFIER
|
||||
Expressions "." { result = TryNode.new(val[1], val[3], val[4]) }
|
||||
| TRY Expressions FINALLY
|
||||
Expressions "." { result = TryNode.new(val[1], nil, nil, val[3]) }
|
||||
| TRY Expressions CATCH IDENTIFIER
|
||||
Expressions
|
||||
FINALLY Expressions "." { result = TryNode.new(val[1], val[3], val[4], val[6]) }
|
||||
;
|
||||
|
||||
Throw:
|
||||
THROW Expression { result = ThrowNode.new(val[1]) }
|
||||
;
|
||||
|
||||
Parenthetical:
|
||||
"(" Expressions ")" { result = ParentheticalNode.new(val[1]) }
|
||||
;
|
||||
|
||||
While:
|
||||
WHILE Expression Then
|
||||
Expressions "." { result = WhileNode.new(val[1], val[3]) }
|
||||
;
|
||||
|
||||
For:
|
||||
Expression FOR IDENTIFIER
|
||||
IN Expression "." { result = ForNode.new(val[0], val[4], val[2]) }
|
||||
| Expression FOR
|
||||
IDENTIFIER "," IDENTIFIER
|
||||
IN Expression "." { result = ForNode.new(val[0], val[6], val[2], val[4]) }
|
||||
| Expression FOR IDENTIFIER
|
||||
IN Expression
|
||||
IF Expression "." { result = ForNode.new(IfNode.new(val[6], Nodes.new([val[0]])), val[4], val[2]) }
|
||||
| Expression FOR
|
||||
IDENTIFIER "," IDENTIFIER
|
||||
IN Expression
|
||||
IF Expression "." { result = ForNode.new(IfNode.new(val[8], Nodes.new([val[0]])), val[6], val[2], val[4]) }
|
||||
;
|
||||
|
||||
Switch:
|
||||
SWITCH Expression Then
|
||||
Cases "." { result = val[3].rewrite_condition(val[1]) }
|
||||
| SWITCH Expression Then
|
||||
Cases DEFAULT Expressions "." { result = val[3].rewrite_condition(val[1]).add_default(val[5]) }
|
||||
;
|
||||
|
||||
Cases:
|
||||
Case { result = val[0] }
|
||||
| Cases Case { result = val[0] << val[1] }
|
||||
;
|
||||
|
||||
Case:
|
||||
CASE Expression Then Expressions { result = IfNode.new(val[1], val[3]) }
|
||||
;
|
||||
|
||||
end
|
||||
|
||||
---- header
|
||||
require "lexer"
|
||||
require "nodes"
|
||||
|
||||
---- inner
|
||||
def parse(code, show_tokens=false)
|
||||
# @yydebug = true
|
||||
@tokens = Lexer.new.tokenize(code)
|
||||
puts @tokens.inspect if show_tokens
|
||||
do_parse
|
||||
end
|
||||
|
||||
def next_token
|
||||
@tokens.shift
|
||||
end
|
||||
1290
index.html
Normal file
1290
index.html
Normal file
File diff suppressed because it is too large
Load Diff
140
lexer.rb
140
lexer.rb
@@ -1,140 +0,0 @@
|
||||
class Lexer
|
||||
|
||||
KEYWORDS = ["if", "else", "then", "unless",
|
||||
"true", "false", "null",
|
||||
"and", "or", "is", "aint", "not",
|
||||
"new", "return",
|
||||
"try", "catch", "finally", "throw",
|
||||
"break", "continue",
|
||||
"for", "in", "while",
|
||||
"switch", "case", "default"]
|
||||
|
||||
IDENTIFIER = /\A([a-zA-Z$_]\w*)/
|
||||
NUMBER = /\A([0-9]+(\.[0-9]+)?)/
|
||||
STRING = /\A("(.*?)"|'(.*?)')/
|
||||
JS = /\A(`(.*?)`)/
|
||||
OPERATOR = /\A([+\*&|\/\-%=<>]+)/
|
||||
WHITESPACE = /\A([ \t\r]+)/
|
||||
NEWLINE = /\A([\r\n]+)/
|
||||
COMMENT = /\A(#[^\r\n]*)/
|
||||
CODE = /\A(=>)/
|
||||
REGEX = /\A(\/(.*?)\/[imgy]{0,4})/
|
||||
|
||||
JS_CLEANER = /(\A`|`\Z)/
|
||||
|
||||
EXP_START = ['{', '(', '[']
|
||||
EXP_END = ['}', ')', ']']
|
||||
|
||||
# This is how to implement a very simple scanner.
|
||||
# Scan one caracter at the time until you find something to parse.
|
||||
def tokenize(code)
|
||||
@code = code.chomp # Cleanup code by remove extra line breaks
|
||||
@i = 0 # Current character position we're parsing
|
||||
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
|
||||
while @i < @code.length
|
||||
@chunk = @code[@i..-1]
|
||||
extract_next_token
|
||||
end
|
||||
@tokens
|
||||
end
|
||||
|
||||
def extract_next_token
|
||||
return if identifier_token
|
||||
return if number_token
|
||||
return if string_token
|
||||
return if js_token
|
||||
return if regex_token
|
||||
return if remove_comment
|
||||
return if whitespace_token
|
||||
return literal_token
|
||||
end
|
||||
|
||||
# Matching if, print, method names, etc.
|
||||
def identifier_token
|
||||
return false unless identifier = @chunk[IDENTIFIER, 1]
|
||||
# Keywords are special identifiers tagged with their own name, 'if' will result
|
||||
# in an [:IF, "if"] token
|
||||
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
|
||||
if tag == :IDENTIFIER && @tokens[-1] && @tokens[-1][1] == '.'
|
||||
@tokens[-1] = [:PROPERTY_ACCESS, '.']
|
||||
end
|
||||
@tokens << [tag, identifier]
|
||||
@i += identifier.length
|
||||
end
|
||||
|
||||
def number_token
|
||||
return false unless number = @chunk[NUMBER, 1]
|
||||
float = number.include?('.')
|
||||
@tokens << [:NUMBER, float ? number.to_f : number.to_i]
|
||||
@i += number.length
|
||||
end
|
||||
|
||||
def string_token
|
||||
return false unless string = @chunk[STRING, 1]
|
||||
@tokens << [:STRING, string]
|
||||
@i += string.length
|
||||
end
|
||||
|
||||
def js_token
|
||||
return false unless script = @chunk[JS, 1]
|
||||
@tokens << [:JS, script.gsub(JS_CLEANER, '')]
|
||||
@i += script.length
|
||||
end
|
||||
|
||||
def regex_token
|
||||
return false unless regex = @chunk[REGEX, 1]
|
||||
@tokens << [:REGEX, regex]
|
||||
@i += regex.length
|
||||
end
|
||||
|
||||
def remove_comment
|
||||
return false unless comment = @chunk[COMMENT, 1]
|
||||
@i += comment.length
|
||||
end
|
||||
|
||||
# Ignore whitespace
|
||||
def whitespace_token
|
||||
return false unless whitespace = @chunk[WHITESPACE, 1]
|
||||
@i += whitespace.length
|
||||
end
|
||||
|
||||
# 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. Multiple newlines get merged.
|
||||
def literal_token
|
||||
value = @chunk[NEWLINE, 1]
|
||||
if value
|
||||
@tokens << ["\n", "\n"] unless @tokens.last && @tokens.last[0] == "\n"
|
||||
return @i += value.length
|
||||
end
|
||||
value = @chunk[OPERATOR, 1]
|
||||
tag_parameters if value && value.match(CODE)
|
||||
value ||= @chunk[0,1]
|
||||
skip_following_newlines if EXP_START.include?(value)
|
||||
remove_leading_newlines if EXP_END.include?(value)
|
||||
@tokens << [value, value]
|
||||
@i += value.length
|
||||
end
|
||||
|
||||
# The main source of ambiguity in our grammar was Parameter lists (as opposed
|
||||
# to argument lists in method calls). Tag parameter identifiers to avoid this.
|
||||
def tag_parameters
|
||||
index = 0
|
||||
loop do
|
||||
tok = @tokens[index -= 1]
|
||||
next if tok[0] == ','
|
||||
return if tok[0] != :IDENTIFIER
|
||||
tok[0] = :PARAM
|
||||
end
|
||||
end
|
||||
|
||||
def skip_following_newlines
|
||||
newlines = @code[(@i+1)..-1][NEWLINE, 1]
|
||||
@i += newlines.length if newlines
|
||||
end
|
||||
|
||||
def remove_leading_newlines
|
||||
@tokens.pop if @tokens.last[1] == "\n"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
require "lexer"
|
||||
p Lexer.new.tokenize(File.read('code.cs'))
|
||||
21
lib/coffee-script.rb
Normal file
21
lib/coffee-script.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||
require "coffee_script/lexer"
|
||||
require "coffee_script/parser"
|
||||
require "coffee_script/nodes"
|
||||
require "coffee_script/value"
|
||||
require "coffee_script/scope"
|
||||
require "coffee_script/rewriter"
|
||||
require "coffee_script/parse_error"
|
||||
|
||||
# Namespace for all CoffeeScript internal classes.
|
||||
module CoffeeScript
|
||||
|
||||
VERSION = '0.2.0' # Keep in sync with the gemspec.
|
||||
|
||||
# Compile a script (String or IO) to JavaScript.
|
||||
def self.compile(script, options={})
|
||||
script = script.read if script.respond_to?(:read)
|
||||
Parser.new.parse(script).compile(options)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>comments</string>
|
||||
<key>scope</key>
|
||||
<string>source.coffee</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>shellVariables</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>TM_COMMENT_START</string>
|
||||
<key>value</key>
|
||||
<string># </string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>uuid</key>
|
||||
<string>0A92C6F6-4D73-4859-B38C-4CC19CBC191F</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,347 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>comment</key>
|
||||
<string>CoffeeScript Syntax: version 1</string>
|
||||
<key>fileTypes</key>
|
||||
<array>
|
||||
<string>coffee</string>
|
||||
</array>
|
||||
<key>name</key>
|
||||
<string>CoffeeScript</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>entity.name.function.coffee</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.coffee</string>
|
||||
</dict>
|
||||
<key>3</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>variable.parameter.function.coffee</string>
|
||||
</dict>
|
||||
<key>4</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>storage.type.function.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>comment</key>
|
||||
<string>match stuff like: funcName: => … </string>
|
||||
<key>match</key>
|
||||
<string>([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)</string>
|
||||
<key>name</key>
|
||||
<string>meta.function.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>variable.parameter.function.coffee</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>storage.type.function.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>comment</key>
|
||||
<string>match stuff like: a => … </string>
|
||||
<key>match</key>
|
||||
<string>([a-zA-Z0-9_?., $*]*)\s*(=>)</string>
|
||||
<key>name</key>
|
||||
<string>meta.inline.function.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.new.coffee</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>entity.name.type.instance.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>match</key>
|
||||
<string>(new)\s+(\w+(?:\.\w*)?)</string>
|
||||
<key>name</key>
|
||||
<string>meta.class.instance.constructor</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.numeric.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>'</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>'</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.single.coffee</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.coffee</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>"</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>"</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.double.coffee</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.coffee</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>`</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>`</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.script.coffee</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.coffee</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.comment.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>match</key>
|
||||
<string>(#).*$\n?</string>
|
||||
<key>name</key>
|
||||
<string>comment.line.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(break|by|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b</string>
|
||||
<key>name</key>
|
||||
<string>keyword.control.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b([a-zA-Z$_]\w*)(\:)\s</string>
|
||||
<key>name</key>
|
||||
<string>variable.assignment.coffee</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>entity.name.function.coffee</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(true|on|yes)\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.boolean.true.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(false|off|no)\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.boolean.false.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\bnull\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.null.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(super|this|extends)\b</string>
|
||||
<key>name</key>
|
||||
<string>variable.language.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(debugger|\\)\b</string>
|
||||
<key>name</key>
|
||||
<string>keyword.other.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>!|\$|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b</string>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(Infinity|NaN|undefined)\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(?<=[=(:]|^|return)\s*(/)(?![/*+{}?])</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(/)[igm]*</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.coffee</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.regexp.coffee</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.coffee</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\;</string>
|
||||
<key>name</key>
|
||||
<string>punctuation.terminator.statement.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>,[ |\t]*</string>
|
||||
<key>name</key>
|
||||
<string>meta.delimiter.object.comma.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\.</string>
|
||||
<key>name</key>
|
||||
<string>meta.delimiter.method.period.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\{|\}</string>
|
||||
<key>name</key>
|
||||
<string>meta.brace.curly.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\(|\)</string>
|
||||
<key>name</key>
|
||||
<string>meta.brace.round.coffee</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\[|\]</string>
|
||||
<key>name</key>
|
||||
<string>meta.brace.square.coffee</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>scopeName</key>
|
||||
<string>source.coffee</string>
|
||||
<key>uuid</key>
|
||||
<string>5B520980-A7D5-4E10-8582-1A4C889A8DE5</string>
|
||||
</dict>
|
||||
</plist>
|
||||
10
lib/coffee_script/CoffeeScript.tmbundle/info.plist
Normal file
10
lib/coffee_script/CoffeeScript.tmbundle/info.plist
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>CoffeeScript</string>
|
||||
<key>uuid</key>
|
||||
<string>A46E4382-F1AC-405B-8F22-65FF470F34D7</string>
|
||||
</dict>
|
||||
</plist>
|
||||
217
lib/coffee_script/command_line.rb
Normal file
217
lib/coffee_script/command_line.rb
Normal file
@@ -0,0 +1,217 @@
|
||||
require 'optparse'
|
||||
require 'fileutils'
|
||||
require 'open3'
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../coffee-script')
|
||||
|
||||
module CoffeeScript
|
||||
|
||||
# The CommandLine handles all of the functionality of the `coffee`
|
||||
# utility.
|
||||
class CommandLine
|
||||
|
||||
BANNER = <<-EOS
|
||||
coffee compiles CoffeeScript source files into JavaScript.
|
||||
|
||||
Usage:
|
||||
coffee path/to/script.coffee
|
||||
EOS
|
||||
|
||||
# Seconds to pause between checks for changed source files.
|
||||
WATCH_INTERVAL = 0.5
|
||||
|
||||
# Command to execute in Narwhal
|
||||
PACKAGE = File.expand_path(File.dirname(__FILE__) + '/../..')
|
||||
LAUNCHER = "narwhal -p #{PACKAGE} -e 'require(\"coffee-script\").run(system.args);'"
|
||||
|
||||
# Run the CommandLine off the contents of ARGV.
|
||||
def initialize
|
||||
@mtimes = {}
|
||||
parse_options
|
||||
return launch_repl if @options[:interactive]
|
||||
return eval_scriptlet if @options[:eval]
|
||||
check_sources
|
||||
return run_scripts if @options[:run]
|
||||
@sources.each {|source| compile_javascript(source) }
|
||||
watch_coffee_scripts if @options[:watch]
|
||||
end
|
||||
|
||||
# The "--help" usage message.
|
||||
def usage
|
||||
puts "\n#{@option_parser}\n"
|
||||
exit
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Compiles (or partially compiles) the source CoffeeScript file, returning
|
||||
# the desired JS, tokens, or lint results.
|
||||
def compile_javascript(source)
|
||||
script = File.read(source)
|
||||
return tokens(script) if @options[:tokens]
|
||||
js = compile(script, source)
|
||||
return unless js
|
||||
return puts(js) if @options[:print]
|
||||
return lint(js) if @options[:lint]
|
||||
File.open(path_for(source), 'w+') {|f| f.write(js) }
|
||||
end
|
||||
|
||||
# Spins up a watcher thread to keep track of the modification times of the
|
||||
# source files, recompiling them whenever they're saved.
|
||||
def watch_coffee_scripts
|
||||
watch_thread = Thread.start do
|
||||
loop do
|
||||
@sources.each do |source|
|
||||
mtime = File.stat(source).mtime
|
||||
@mtimes[source] ||= mtime
|
||||
if mtime > @mtimes[source]
|
||||
@mtimes[source] = mtime
|
||||
compile_javascript(source)
|
||||
end
|
||||
end
|
||||
sleep WATCH_INTERVAL
|
||||
end
|
||||
end
|
||||
Signal.trap("INT") { watch_thread.kill }
|
||||
watch_thread.join
|
||||
end
|
||||
|
||||
# Ensure that all of the source files exist.
|
||||
def check_sources
|
||||
usage if @sources.empty?
|
||||
missing = @sources.detect {|s| !File.exists?(s) }
|
||||
if missing
|
||||
STDERR.puts("File not found: '#{missing}'")
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
# Pipe compiled JS through JSLint (requires a working 'jsl' command).
|
||||
def lint(js)
|
||||
stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin')
|
||||
stdin.write(js)
|
||||
stdin.close
|
||||
puts stdout.read.tr("\n", '')
|
||||
errs = stderr.read.chomp
|
||||
puts errs unless errs.empty?
|
||||
stdout.close and stderr.close
|
||||
end
|
||||
|
||||
# Eval a little piece of CoffeeScript directly from the command line.
|
||||
def eval_scriptlet
|
||||
script = STDIN.tty? ? @sources.join(' ') : STDIN.read
|
||||
return tokens(script) if @options[:tokens]
|
||||
js = compile(script)
|
||||
return lint(js) if @options[:lint]
|
||||
puts js
|
||||
end
|
||||
|
||||
# Use Narwhal to run an interactive CoffeeScript session.
|
||||
def launch_repl
|
||||
exec "#{LAUNCHER}"
|
||||
rescue Errno::ENOENT
|
||||
puts "Error: Narwhal must be installed to use the interactive REPL."
|
||||
exit(1)
|
||||
end
|
||||
|
||||
# Use Narwhal to compile and execute CoffeeScripts.
|
||||
def run_scripts
|
||||
sources = @sources.join(' ')
|
||||
exec "#{LAUNCHER} #{sources}"
|
||||
rescue Errno::ENOENT
|
||||
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
|
||||
exit(1)
|
||||
end
|
||||
|
||||
# Print the tokens that the lexer generates from a source script.
|
||||
def tokens(script)
|
||||
puts Lexer.new.tokenize(script).inspect
|
||||
end
|
||||
|
||||
# Compile a single source file to JavaScript.
|
||||
def compile(script, source='error')
|
||||
begin
|
||||
options = {}
|
||||
options[:no_wrap] = true if @options[:no_wrap]
|
||||
CoffeeScript.compile(script, options)
|
||||
rescue CoffeeScript::ParseError, SyntaxError => e
|
||||
STDERR.puts "#{source}: #{e.message}"
|
||||
exit(1) unless @options[:watch]
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Write out JavaScript alongside CoffeeScript unless an output directory
|
||||
# is specified.
|
||||
def path_for(source)
|
||||
filename = File.basename(source, File.extname(source)) + '.js'
|
||||
dir = @options[:output] || File.dirname(source)
|
||||
File.join(dir, filename)
|
||||
end
|
||||
|
||||
# Install the CoffeeScript TextMate bundle to ~/Library.
|
||||
def install_bundle
|
||||
bundle_dir = File.expand_path('~/Library/Application Support/TextMate/Bundles/')
|
||||
FileUtils.cp_r(File.dirname(__FILE__) + '/CoffeeScript.tmbundle', bundle_dir)
|
||||
end
|
||||
|
||||
# Use OptionParser for all the options.
|
||||
def parse_options
|
||||
@options = {}
|
||||
@option_parser = OptionParser.new do |opts|
|
||||
opts.on('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i|
|
||||
@options[:interactive] = true
|
||||
end
|
||||
opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r|
|
||||
@options[:run] = true
|
||||
end
|
||||
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
|
||||
@options[:output] = d
|
||||
FileUtils.mkdir_p(d) unless File.exists?(d)
|
||||
end
|
||||
opts.on('-w', '--watch', 'watch scripts for changes, and recompile') do |w|
|
||||
@options[:watch] = true
|
||||
end
|
||||
opts.on('-p', '--print', 'print the compiled JavaScript to stdout') do |d|
|
||||
@options[:print] = true
|
||||
end
|
||||
opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l|
|
||||
@options[:lint] = true
|
||||
end
|
||||
opts.on('-e', '--eval', 'compile a cli scriptlet or read from stdin') do |e|
|
||||
@options[:eval] = true
|
||||
end
|
||||
opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t|
|
||||
@options[:tokens] = true
|
||||
end
|
||||
opts.on('-v', '--verbose', 'print at every step of code generation') do |v|
|
||||
ENV['VERBOSE'] = 'true'
|
||||
end
|
||||
opts.on('-n', '--no-wrap', 'raw output, no safety wrapper or vars') do |n|
|
||||
@options[:no_wrap] = true
|
||||
end
|
||||
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
|
||||
install_bundle
|
||||
exit
|
||||
end
|
||||
opts.on_tail('--version', 'display CoffeeScript version') do
|
||||
puts "CoffeeScript version #{CoffeeScript::VERSION}"
|
||||
exit
|
||||
end
|
||||
opts.on_tail('-h', '--help', 'display this help message') do
|
||||
usage
|
||||
end
|
||||
end
|
||||
@option_parser.banner = BANNER
|
||||
begin
|
||||
@option_parser.parse!(ARGV)
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts e.message
|
||||
exit(1)
|
||||
end
|
||||
@sources = ARGV
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
441
lib/coffee_script/grammar.y
Normal file
441
lib/coffee_script/grammar.y
Normal file
@@ -0,0 +1,441 @@
|
||||
class Parser
|
||||
|
||||
# Declare tokens produced by the lexer
|
||||
token IF ELSE UNLESS
|
||||
token NUMBER STRING REGEX
|
||||
token TRUE FALSE YES NO ON OFF
|
||||
token IDENTIFIER PROPERTY_ACCESS
|
||||
token CODE PARAM PARAM_SPLAT NEW RETURN
|
||||
token TRY CATCH FINALLY THROW
|
||||
token BREAK CONTINUE
|
||||
token FOR IN BY WHEN WHILE
|
||||
token SWITCH LEADING_WHEN
|
||||
token DELETE INSTANCEOF TYPEOF
|
||||
token SUPER EXTENDS
|
||||
token NEWLINE
|
||||
token COMMENT
|
||||
token JS
|
||||
token INDENT OUTDENT
|
||||
|
||||
# Declare order of operations.
|
||||
prechigh
|
||||
left '?'
|
||||
nonassoc UMINUS PARAM_SPLAT SPLAT 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 BY
|
||||
right THROW FOR NEW SUPER
|
||||
left EXTENDS
|
||||
left ASSIGN '||=' '&&='
|
||||
right RETURN '=>' UNLESS IF ELSE WHILE
|
||||
preclow
|
||||
|
||||
rule
|
||||
|
||||
# All parsing will end in this rule, being the trunk of the AST.
|
||||
Root:
|
||||
/* nothing */ { result = Expressions.new }
|
||||
| Terminator { result = Expressions.new }
|
||||
| Expressions { result = val[0] }
|
||||
| Block Terminator { result = val[0] }
|
||||
;
|
||||
|
||||
# Any list of expressions or method body, seperated by line breaks or semis.
|
||||
Expressions:
|
||||
Expression { result = Expressions.wrap(val) }
|
||||
| Expressions Terminator Expression { result = val[0] << val[2] }
|
||||
| Expressions Terminator { result = val[0] }
|
||||
;
|
||||
|
||||
# All types of expressions in our language.
|
||||
Expression:
|
||||
Value
|
||||
| Call
|
||||
| Code
|
||||
| Operation
|
||||
| Range
|
||||
| Assign
|
||||
| If
|
||||
| Try
|
||||
| Throw
|
||||
| Return
|
||||
| While
|
||||
| For
|
||||
| Switch
|
||||
| Extends
|
||||
| Splat
|
||||
| Existence
|
||||
| Comment
|
||||
;
|
||||
|
||||
Block:
|
||||
INDENT Expressions OUTDENT { result = val[1] }
|
||||
| INDENT OUTDENT { result = Expressions.new }
|
||||
;
|
||||
|
||||
# All tokens that can terminate an expression.
|
||||
Terminator:
|
||||
"\n"
|
||||
| ";"
|
||||
;
|
||||
|
||||
# All hard-coded values.
|
||||
Literal:
|
||||
NUMBER { result = LiteralNode.new(val[0]) }
|
||||
| STRING { result = LiteralNode.new(val[0]) }
|
||||
| JS { result = LiteralNode.new(val[0]) }
|
||||
| REGEX { result = LiteralNode.new(val[0]) }
|
||||
| BREAK { result = LiteralNode.new(val[0]) }
|
||||
| CONTINUE { result = LiteralNode.new(val[0]) }
|
||||
| TRUE { result = LiteralNode.new(true) }
|
||||
| FALSE { result = LiteralNode.new(false) }
|
||||
| YES { result = LiteralNode.new(true) }
|
||||
| NO { result = LiteralNode.new(false) }
|
||||
| ON { result = LiteralNode.new(true) }
|
||||
| OFF { result = LiteralNode.new(false) }
|
||||
;
|
||||
|
||||
# Assignment to a variable.
|
||||
Assign:
|
||||
Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
|
||||
;
|
||||
|
||||
# Assignment within an object literal.
|
||||
AssignObj:
|
||||
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
|
||||
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
|
||||
| Comment { result = val[0] }
|
||||
;
|
||||
|
||||
# A return statement.
|
||||
Return:
|
||||
RETURN Expression { result = ReturnNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# A comment.
|
||||
Comment:
|
||||
COMMENT { result = CommentNode.new(val[0]) }
|
||||
;
|
||||
|
||||
# Arithmetic and logical operators
|
||||
# For Ruby's Operator precedence, see:
|
||||
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
|
||||
Operation:
|
||||
'!' Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| '!!' Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
|
||||
| NOT Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| '~' Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| '--' Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| '++' Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
|
||||
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
|
||||
|
||||
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
|
||||
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
;
|
||||
|
||||
Existence:
|
||||
Expression '?' { result = ExistenceNode.new(val[0]) }
|
||||
;
|
||||
|
||||
# Function definition.
|
||||
Code:
|
||||
ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) }
|
||||
| "=>" Block { result = CodeNode.new([], val[1]) }
|
||||
;
|
||||
|
||||
# The parameters to a function definition.
|
||||
ParamList:
|
||||
Param { result = val }
|
||||
| ParamList "," Param { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
Param:
|
||||
PARAM
|
||||
| PARAM_SPLAT PARAM { result = ParamSplatNode.new(val[1]) }
|
||||
;
|
||||
|
||||
Splat:
|
||||
'*' Value = SPLAT { result = ArgSplatNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# Expressions that can be treated as values.
|
||||
Value:
|
||||
IDENTIFIER { result = ValueNode.new(val[0]) }
|
||||
| Literal { result = ValueNode.new(val[0]) }
|
||||
| Array { result = ValueNode.new(val[0]) }
|
||||
| Object { result = ValueNode.new(val[0]) }
|
||||
| Parenthetical { result = ValueNode.new(val[0]) }
|
||||
| Value Accessor { result = val[0] << val[1] }
|
||||
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
|
||||
;
|
||||
|
||||
# Accessing into an object or array, through dot or index notation.
|
||||
Accessor:
|
||||
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
|
||||
| Index { result = val[0] }
|
||||
| Range { result = SliceNode.new(val[0]) }
|
||||
;
|
||||
|
||||
# Indexing into an object or array.
|
||||
Index:
|
||||
"[" Expression "]" { result = IndexNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# An object literal.
|
||||
Object:
|
||||
"{" AssignList "}" { result = ObjectNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# Assignment within an object literal (comma or newline separated).
|
||||
AssignList:
|
||||
/* nothing */ { result = [] }
|
||||
| AssignObj { result = val }
|
||||
| AssignList "," AssignObj { result = val[0] << val[2] }
|
||||
| AssignList Terminator AssignObj { result = val[0] << val[2] }
|
||||
| AssignList ","
|
||||
Terminator AssignObj { result = val[0] << val[3] }
|
||||
| INDENT AssignList OUTDENT { result = val[1] }
|
||||
;
|
||||
|
||||
# All flavors of function call (instantiation, super, and regular).
|
||||
Call:
|
||||
Invocation { result = val[0] }
|
||||
| NEW Invocation { result = val[1].new_instance }
|
||||
| Super { result = val[0] }
|
||||
;
|
||||
|
||||
# Extending an object's prototype.
|
||||
Extends:
|
||||
Value EXTENDS Value { result = ExtendsNode.new(val[0], val[2]) }
|
||||
;
|
||||
|
||||
# A generic function invocation.
|
||||
Invocation:
|
||||
Value Arguments { result = CallNode.new(val[0], val[1]) }
|
||||
| Invocation Arguments { result = CallNode.new(val[0], val[1]) }
|
||||
# | Invocation Code { result = val[0] << val[1] }
|
||||
;
|
||||
|
||||
Arguments:
|
||||
"(" ArgList ")" { result = val[1] }
|
||||
| "(" ArgList ")" Code { result = val[1] << val[3] }
|
||||
;
|
||||
|
||||
# Calling super.
|
||||
Super:
|
||||
SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) }
|
||||
;
|
||||
|
||||
# The range literal.
|
||||
Range:
|
||||
"[" Expression
|
||||
"." "." Expression "]" { result = RangeNode.new(val[1], val[4]) }
|
||||
| "[" Expression
|
||||
"." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) }
|
||||
;
|
||||
|
||||
# The array literal.
|
||||
Array:
|
||||
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# A list of arguments to a method call, or as the contents of an array.
|
||||
ArgList:
|
||||
/* nothing */ { result = [] }
|
||||
| Expression { result = val }
|
||||
| INDENT Expression { result = [val[1]] }
|
||||
| ArgList "," Expression { result = val[0] << val[2] }
|
||||
| ArgList Terminator Expression { result = val[0] << val[2] }
|
||||
| ArgList "," Terminator Expression { result = val[0] << val[3] }
|
||||
| ArgList "," INDENT Expression { result = val[0] << val[3] }
|
||||
| ArgList OUTDENT { result = val[0] }
|
||||
;
|
||||
|
||||
# Try/catch/finally exception handling blocks.
|
||||
Try:
|
||||
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
|
||||
| TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) }
|
||||
| TRY Block Catch
|
||||
FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
|
||||
;
|
||||
|
||||
# A catch clause.
|
||||
Catch:
|
||||
CATCH IDENTIFIER Block { result = [val[1], val[2]] }
|
||||
;
|
||||
|
||||
# Throw an exception.
|
||||
Throw:
|
||||
THROW Expression { result = ThrowNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# Parenthetical expressions.
|
||||
Parenthetical:
|
||||
"(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) }
|
||||
;
|
||||
|
||||
# The while loop. (there is no do..while).
|
||||
While:
|
||||
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
|
||||
;
|
||||
|
||||
# Array comprehensions, including guard and current index.
|
||||
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
|
||||
For:
|
||||
Expression FOR
|
||||
ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) }
|
||||
| FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) }
|
||||
;
|
||||
|
||||
# An array comprehension has variables for the current element and index.
|
||||
ForVariables:
|
||||
IDENTIFIER { result = val }
|
||||
| IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] }
|
||||
;
|
||||
|
||||
# The source of the array comprehension can optionally be filtered.
|
||||
ForSource:
|
||||
IN Expression { result = {:source => val[1]} }
|
||||
| ForSource
|
||||
WHEN Expression { result = val[0].merge(:filter => val[2]) }
|
||||
| ForSource
|
||||
BY Expression { result = val[0].merge(:step => val[2]) }
|
||||
;
|
||||
|
||||
# Switch/When blocks.
|
||||
Switch:
|
||||
SWITCH Expression INDENT
|
||||
Whens OUTDENT { result = val[3].rewrite_condition(val[1]) }
|
||||
| SWITCH Expression INDENT
|
||||
Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
|
||||
;
|
||||
|
||||
# The inner list of whens.
|
||||
Whens:
|
||||
When { result = val[0] }
|
||||
| Whens When { result = val[0] << val[1] }
|
||||
;
|
||||
|
||||
# An individual when.
|
||||
When:
|
||||
LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
||||
| LEADING_WHEN Expression Block
|
||||
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
||||
| Comment
|
||||
;
|
||||
|
||||
# All of the following nutso if-else destructuring is to make the
|
||||
# grammar expand unambiguously.
|
||||
|
||||
IfBlock:
|
||||
IF Expression Block { result = IfNode.new(val[1], val[2]) }
|
||||
;
|
||||
|
||||
# An elsif portion of an if-else block.
|
||||
ElsIf:
|
||||
ELSE IfBlock { result = val[1].force_statement }
|
||||
;
|
||||
|
||||
# Multiple elsifs can be chained together.
|
||||
ElsIfs:
|
||||
ElsIf { result = val[0] }
|
||||
| ElsIfs ElsIf { result = val[0].add_else(val[1]) }
|
||||
;
|
||||
|
||||
# Terminating else bodies are strictly optional.
|
||||
ElseBody
|
||||
/* nothing */ { result = nil }
|
||||
| ELSE Block { result = val[1] }
|
||||
;
|
||||
|
||||
# All the alternatives for ending an if-else block.
|
||||
IfEnd:
|
||||
ElseBody { result = val[0] }
|
||||
| ElsIfs ElseBody { result = val[0].add_else(val[1]) }
|
||||
;
|
||||
|
||||
# The full complement of if blocks, including postfix one-liner ifs and unlesses.
|
||||
If:
|
||||
IfBlock IfEnd { result = val[0].add_else(val[1]) }
|
||||
| Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) }
|
||||
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) }
|
||||
;
|
||||
|
||||
end
|
||||
|
||||
---- header
|
||||
module CoffeeScript
|
||||
|
||||
---- inner
|
||||
# Lex and parse a CoffeeScript.
|
||||
def parse(code)
|
||||
# Uncomment the following line to enable grammar debugging, in combination
|
||||
# with the -g flag in the Rake build task.
|
||||
# @yydebug = true
|
||||
@tokens = Lexer.new.tokenize(code)
|
||||
do_parse
|
||||
end
|
||||
|
||||
# Retrieve the next token from the list.
|
||||
def next_token
|
||||
@tokens.shift
|
||||
end
|
||||
|
||||
# Raise a custom error class that knows about line numbers.
|
||||
def on_error(error_token_id, error_value, value_stack)
|
||||
raise ParseError.new(token_to_str(error_token_id), error_value, value_stack)
|
||||
end
|
||||
|
||||
---- footer
|
||||
end
|
||||
237
lib/coffee_script/lexer.rb
Normal file
237
lib/coffee_script/lexer.rb
Normal file
@@ -0,0 +1,237 @@
|
||||
module CoffeeScript
|
||||
|
||||
# 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.
|
||||
class Lexer
|
||||
|
||||
# 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",
|
||||
"try", "catch", "finally", "throw",
|
||||
"break", "continue",
|
||||
"for", "in", "by", "where", "while",
|
||||
"switch", "when",
|
||||
"super", "extends",
|
||||
"delete", "instanceof", "typeof"]
|
||||
|
||||
# Token matching regexes.
|
||||
IDENTIFIER = /\A([a-zA-Z$_]\w*)/
|
||||
NUMBER = /\A(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
|
||||
STRING = /\A(""|''|"(.*?)[^\\]"|'(.*?)[^\\]')/m
|
||||
JS = /\A(``|`(.*?)[^\\]`)/m
|
||||
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
|
||||
WHITESPACE = /\A([ \t]+)/
|
||||
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
|
||||
CODE = /\A(=>)/
|
||||
REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/
|
||||
MULTI_DENT = /\A((\n([ \t]*)?)+)/
|
||||
LAST_DENT = /\n([ \t]*)/
|
||||
ASSIGNMENT = /\A(:|=)\Z/
|
||||
|
||||
# Token cleaning regexes.
|
||||
JS_CLEANER = /(\A`|`\Z)/
|
||||
MULTILINER = /\n/
|
||||
COMMENT_CLEANER = /(^\s*#|\n\s*$)/
|
||||
NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
|
||||
|
||||
# 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, :THIS, :TRUE
|
||||
]
|
||||
|
||||
# Scan by attempting to match tokens one character at a time. Slow and steady.
|
||||
def tokenize(code)
|
||||
@code = code.chomp # Cleanup code by remove extra line breaks
|
||||
@i = 0 # Current character position we're parsing
|
||||
@line = 1 # The current line.
|
||||
@indent = 0 # The current indent level.
|
||||
@indents = [] # The stack of all indent levels we are currently within.
|
||||
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
|
||||
while @i < @code.length
|
||||
@chunk = @code[@i..-1]
|
||||
extract_next_token
|
||||
end
|
||||
puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE']
|
||||
close_indentation
|
||||
Rewriter.new.rewrite(@tokens)
|
||||
end
|
||||
|
||||
# At every position, run through this list of attempted matches,
|
||||
# short-circuiting if any of them succeed.
|
||||
def extract_next_token
|
||||
return if identifier_token
|
||||
return if number_token
|
||||
return if string_token
|
||||
return if js_token
|
||||
return if regex_token
|
||||
return if indent_token
|
||||
return if comment_token
|
||||
return if whitespace_token
|
||||
return literal_token
|
||||
end
|
||||
|
||||
# Tokenizers ==========================================================
|
||||
|
||||
# Matches identifying literals: variables, keywords, method names, etc.
|
||||
def identifier_token
|
||||
return false unless identifier = @chunk[IDENTIFIER, 1]
|
||||
# Keywords are special identifiers tagged with their own name,
|
||||
# 'if' will result in an [:IF, "if"] token.
|
||||
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
|
||||
tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
|
||||
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.')
|
||||
token(tag, identifier)
|
||||
@i += identifier.length
|
||||
end
|
||||
|
||||
# Matches numbers, including decimals, hex, and exponential notation.
|
||||
def number_token
|
||||
return false unless number = @chunk[NUMBER, 1]
|
||||
token(:NUMBER, number)
|
||||
@i += number.length
|
||||
end
|
||||
|
||||
# Matches strings, including multi-line strings.
|
||||
def string_token
|
||||
return false unless string = @chunk[STRING, 1]
|
||||
escaped = string.gsub(MULTILINER) do |match|
|
||||
@line += 1
|
||||
" \\\n"
|
||||
end
|
||||
token(:STRING, escaped)
|
||||
@i += string.length
|
||||
end
|
||||
|
||||
# Matches interpolated JavaScript.
|
||||
def js_token
|
||||
return false unless script = @chunk[JS, 1]
|
||||
token(:JS, script.gsub(JS_CLEANER, ''))
|
||||
@i += script.length
|
||||
end
|
||||
|
||||
# Matches regular expression literals.
|
||||
def regex_token
|
||||
return false unless regex = @chunk[REGEX, 1]
|
||||
return false if NOT_REGEX.include?(last_tag)
|
||||
token(:REGEX, regex)
|
||||
@i += regex.length
|
||||
end
|
||||
|
||||
# Matches and consumes comments.
|
||||
def comment_token
|
||||
return false unless comment = @chunk[COMMENT, 1]
|
||||
@line += comment.scan(MULTILINER).length
|
||||
token(:COMMENT, comment.gsub(COMMENT_CLEANER, '').split(MULTILINER))
|
||||
token("\n", "\n")
|
||||
@i += comment.length
|
||||
end
|
||||
|
||||
# Record tokens for indentation differing from the previous line.
|
||||
def indent_token
|
||||
return false unless indent = @chunk[MULTI_DENT, 1]
|
||||
@line += indent.scan(MULTILINER).size
|
||||
@i += indent.size
|
||||
return suppress_newlines(indent) if last_value.to_s.match(NO_NEWLINE) && last_value != "=>"
|
||||
size = indent.scan(LAST_DENT).last.last.length
|
||||
return newline_token(indent) if size == @indent
|
||||
if size > @indent
|
||||
token(:INDENT, size - @indent)
|
||||
@indents << (size - @indent)
|
||||
else
|
||||
outdent_token(@indent - size)
|
||||
end
|
||||
@indent = size
|
||||
end
|
||||
|
||||
# Record an oudent token or tokens, if we're moving back inwards past
|
||||
# multiple recorded indents.
|
||||
def outdent_token(move_out)
|
||||
while move_out > 0 && !@indents.empty?
|
||||
last_indent = @indents.pop
|
||||
token(:OUTDENT, last_indent)
|
||||
move_out -= last_indent
|
||||
end
|
||||
token("\n", "\n")
|
||||
end
|
||||
|
||||
# Matches and consumes non-meaningful whitespace.
|
||||
def whitespace_token
|
||||
return false unless whitespace = @chunk[WHITESPACE, 1]
|
||||
@i += whitespace.length
|
||||
end
|
||||
|
||||
# Multiple newlines get merged together.
|
||||
# Use a trailing \ to escape newlines.
|
||||
def newline_token(newlines)
|
||||
token("\n", "\n") unless last_value == "\n"
|
||||
true
|
||||
end
|
||||
|
||||
# Tokens to explicitly escape newlines are removed once their job is done.
|
||||
def suppress_newlines(newlines)
|
||||
@tokens.pop if last_value == "\\"
|
||||
true
|
||||
end
|
||||
|
||||
# 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.
|
||||
def literal_token
|
||||
value = @chunk[OPERATOR, 1]
|
||||
tag_parameters if value && value.match(CODE)
|
||||
value ||= @chunk[0,1]
|
||||
tag = value.match(ASSIGNMENT) ? :ASSIGN : value
|
||||
token(tag, value)
|
||||
@i += value.length
|
||||
end
|
||||
|
||||
# Helpers ==========================================================
|
||||
|
||||
# Add a token to the results, taking note of the line number, and
|
||||
# immediately-preceding comment.
|
||||
def token(tag, value)
|
||||
@tokens << [tag, Value.new(value, @line)]
|
||||
end
|
||||
|
||||
# Peek at the previous token's value.
|
||||
def last_value
|
||||
@tokens.last && @tokens.last[1]
|
||||
end
|
||||
|
||||
# Peek at the previous token's tag.
|
||||
def last_tag
|
||||
@tokens.last && @tokens.last[0]
|
||||
end
|
||||
|
||||
# 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.
|
||||
def tag_parameters
|
||||
i = 0
|
||||
loop do
|
||||
i -= 1
|
||||
tok = @tokens[i]
|
||||
return if !tok
|
||||
next if tok[0] == ','
|
||||
next tok[0] = :PARAM_SPLAT if tok[0] == '*'
|
||||
return if tok[0] != :IDENTIFIER
|
||||
tok[0] = :PARAM
|
||||
end
|
||||
end
|
||||
|
||||
# Close up all remaining open blocks. IF the first token is an indent,
|
||||
# axe it.
|
||||
def close_indentation
|
||||
outdent_token(@indent)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
62
lib/coffee_script/narwhal/coffee-script.coffee
Normal file
62
lib/coffee_script/narwhal/coffee-script.coffee
Normal file
@@ -0,0 +1,62 @@
|
||||
# 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.
|
||||
|
||||
# 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')
|
||||
|
||||
# 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")
|
||||
|
||||
# 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))
|
||||
delete args[i]
|
||||
return true
|
||||
|
||||
while true
|
||||
try
|
||||
system.stdout.write('coffee> ').flush()
|
||||
result: exports.evalCS(Readline.readline())
|
||||
print(result) if result isnt undefined
|
||||
catch e
|
||||
print(e)
|
||||
|
||||
# Compile a given CoffeeScript file into JavaScript.
|
||||
exports.compileFile: path =>
|
||||
coffee: OS.popen([coffeePath, "--print", "--no-wrap", path])
|
||||
checkForErrors(coffee)
|
||||
coffee.stdout.read()
|
||||
|
||||
# Compile a string of CoffeeScript into JavaScript.
|
||||
exports.compile: source =>
|
||||
coffee: OS.popen([coffeePath, "--eval", "--no-wrap"])
|
||||
coffee.stdin.write(source).flush().close()
|
||||
checkForErrors(coffee)
|
||||
coffee.stdout.read()
|
||||
|
||||
# Evaluating a string of CoffeeScript first compiles it externally.
|
||||
exports.evalCS: source =>
|
||||
eval(exports.compile(source))
|
||||
|
||||
# Make a factory for the CoffeeScript environment.
|
||||
exports.makeNarwhalFactory: 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 + ")")
|
||||
81
lib/coffee_script/narwhal/lib/coffee-script.js
Normal file
81
lib/coffee_script/narwhal/lib/coffee-script.js
Normal file
@@ -0,0 +1,81 @@
|
||||
(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.
|
||||
// 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');
|
||||
// Our general-purpose error handler.
|
||||
checkForErrors = function checkForErrors(coffeeProcess) {
|
||||
if (coffeeProcess.wait() === 0) {
|
||||
return true;
|
||||
}
|
||||
system.stderr.print(coffeeProcess.stderr.read());
|
||||
throw new Error("CoffeeScript compile error");
|
||||
};
|
||||
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
|
||||
// command.
|
||||
exports.run = function run(args) {
|
||||
var __a, __b, __c, i, path, result;
|
||||
if (args.length) {
|
||||
__a = args;
|
||||
__b = [];
|
||||
for (i in __a) {
|
||||
if (__a.hasOwnProperty(i)) {
|
||||
path = __a[i];
|
||||
exports.evalCS(File.read(path));
|
||||
__c = delete args[i];
|
||||
__b.push(__c);
|
||||
}
|
||||
}
|
||||
__b;
|
||||
return true;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
system.stdout.write('coffee> ').flush();
|
||||
result = exports.evalCS(Readline.readline());
|
||||
if (result !== undefined) {
|
||||
print(result);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Compile a given CoffeeScript file into JavaScript.
|
||||
exports.compileFile = function compileFile(path) {
|
||||
var coffee;
|
||||
coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]);
|
||||
checkForErrors(coffee);
|
||||
return coffee.stdout.read();
|
||||
};
|
||||
// Compile a string of CoffeeScript into JavaScript.
|
||||
exports.compile = function compile(source) {
|
||||
var coffee;
|
||||
coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]);
|
||||
coffee.stdin.write(source).flush().close();
|
||||
checkForErrors(coffee);
|
||||
return coffee.stdout.read();
|
||||
};
|
||||
// Evaluating a string of CoffeeScript first compiles it externally.
|
||||
exports.evalCS = function evalCS(source) {
|
||||
return eval(exports.compile(source));
|
||||
};
|
||||
// Make a factory for the CoffeeScript environment.
|
||||
exports.makeNarwhalFactory = function makeNarwhalFactory(path) {
|
||||
var code, factoryText;
|
||||
code = exports.compileFile(path);
|
||||
factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}";
|
||||
if (system.engine === "rhino") {
|
||||
return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null);
|
||||
} else {
|
||||
// eval requires parentheses, but parentheses break compileFunction.
|
||||
return eval("(" + factoryText + ")");
|
||||
}
|
||||
};
|
||||
})();
|
||||
21
lib/coffee_script/narwhal/lib/coffee-script/loader.js
Normal file
21
lib/coffee_script/narwhal/lib/coffee-script/loader.js
Normal file
@@ -0,0 +1,21 @@
|
||||
(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]);
|
||||
})();
|
||||
19
lib/coffee_script/narwhal/loader.coffee
Normal file
19
lib/coffee_script/narwhal/loader.coffee
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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])
|
||||
803
lib/coffee_script/nodes.rb
Normal file
803
lib/coffee_script/nodes.rb
Normal file
@@ -0,0 +1,803 @@
|
||||
module CoffeeScript
|
||||
|
||||
# The abstract base class for all CoffeeScript nodes.
|
||||
class Node
|
||||
# Tabs are two spaces for pretty-printing.
|
||||
TAB = ' '
|
||||
|
||||
# Tag this node as a statement, meaning that it can't be used directly as
|
||||
# the result of an expression.
|
||||
def self.statement
|
||||
class_eval "def statement?; true; end"
|
||||
end
|
||||
|
||||
# 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.
|
||||
def self.statement_only
|
||||
statement
|
||||
class_eval "def statement_only?; true; end"
|
||||
end
|
||||
|
||||
def write(code)
|
||||
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
|
||||
code
|
||||
end
|
||||
|
||||
# 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.
|
||||
def compile(o={})
|
||||
@options = o.dup
|
||||
top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top)
|
||||
closure = statement? && !statement_only? && !top && !@options[:return]
|
||||
closure ? compile_closure(@options) : compile_node(@options)
|
||||
end
|
||||
|
||||
def compile_closure(o={})
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
"(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()"
|
||||
end
|
||||
|
||||
# Default implementations of the common node methods.
|
||||
def unwrap; self; end
|
||||
def statement?; false; end
|
||||
def statement_only?; false; end
|
||||
end
|
||||
|
||||
# A collection of nodes, each one representing an expression.
|
||||
class Expressions < Node
|
||||
statement
|
||||
attr_reader :expressions
|
||||
|
||||
STRIP_TRAILING_WHITESPACE = /\s+$/
|
||||
|
||||
# Wrap up a node as an Expressions, unless it already is.
|
||||
def self.wrap(*nodes)
|
||||
return nodes[0] if nodes.length == 1 && nodes[0].is_a?(Expressions)
|
||||
Expressions.new(*nodes)
|
||||
end
|
||||
|
||||
def initialize(*nodes)
|
||||
@expressions = nodes.flatten
|
||||
end
|
||||
|
||||
# Tack an expression onto the end of this node.
|
||||
def <<(node)
|
||||
@expressions << node
|
||||
self
|
||||
end
|
||||
|
||||
def unshift(node)
|
||||
@expressions.unshift(node)
|
||||
self
|
||||
end
|
||||
|
||||
# If this Expressions consists of a single node, pull it back out.
|
||||
def unwrap
|
||||
@expressions.length == 1 ? @expressions.first : self
|
||||
end
|
||||
|
||||
# Is the node last in this block of expressions.
|
||||
def last?(node)
|
||||
@last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
|
||||
node == @expressions[@last_index]
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o[:scope] ? super(o) : compile_root(o)
|
||||
end
|
||||
|
||||
# The extra fancy is to handle pushing down returns to the final lines of
|
||||
# inner statements. Variables first defined within the Expressions body
|
||||
# have their declarations pushed up top of the closest scope.
|
||||
def compile_node(options={})
|
||||
compiled = @expressions.map do |node|
|
||||
o = options.dup
|
||||
returns = o.delete(:return)
|
||||
if last?(node) && returns && !node.statement_only?
|
||||
if node.statement?
|
||||
node.compile(o.merge(:return => true))
|
||||
else
|
||||
"#{o[:indent]}return #{node.compile(o)};"
|
||||
end
|
||||
else
|
||||
ending = node.statement? ? '' : ';'
|
||||
indent = node.statement? ? '' : o[:indent]
|
||||
"#{indent}#{node.compile(o.merge(:top => true))}#{ending}"
|
||||
end
|
||||
end
|
||||
write(compiled.join("\n"))
|
||||
end
|
||||
|
||||
# If this is the top-level Expressions, wrap everything in a safety closure.
|
||||
def compile_root(o={})
|
||||
indent = o[:no_wrap] ? '' : TAB
|
||||
o.merge!(:indent => indent, :scope => Scope.new(nil, self))
|
||||
code = o[:no_wrap] ? compile_node(o) : compile_with_declarations(o)
|
||||
code.gsub!(STRIP_TRAILING_WHITESPACE, '')
|
||||
o[:no_wrap] ? code : "(function(){\n#{code}\n})();"
|
||||
end
|
||||
|
||||
def compile_with_declarations(o={})
|
||||
code = compile_node(o)
|
||||
decls = ''
|
||||
decls = "#{o[:indent]}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self)
|
||||
decls + code
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Literals are static values that have a Ruby representation, eg.: a string, a number,
|
||||
# true, false, nil, etc.
|
||||
class LiteralNode < Node
|
||||
STATEMENTS = ['break', 'continue']
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def statement?
|
||||
STATEMENTS.include?(@value.to_s)
|
||||
end
|
||||
alias_method :statement_only?, :statement?
|
||||
|
||||
def compile_node(o)
|
||||
indent = statement? ? o[:indent] : ''
|
||||
ending = statement? ? ';' : ''
|
||||
write(indent + @value.to_s + ending)
|
||||
end
|
||||
end
|
||||
|
||||
# Try to return your expression, or tell it to return itself.
|
||||
class ReturnNode < Node
|
||||
statement_only
|
||||
|
||||
attr_reader :expression
|
||||
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
return write(@expression.compile(o.merge(:return => true))) if @expression.statement?
|
||||
compiled = @expression.compile(o)
|
||||
write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null;" : "#{o[:indent]}return #{compiled};")
|
||||
end
|
||||
end
|
||||
|
||||
# Pass through CoffeeScript comments into JavaScript comments at the
|
||||
# same position.
|
||||
class CommentNode < Node
|
||||
statement_only
|
||||
|
||||
def initialize(lines)
|
||||
@lines = lines.value
|
||||
end
|
||||
|
||||
def compile_node(o={})
|
||||
delimiter = "\n#{o[:indent]}//"
|
||||
comment = "#{delimiter}#{@lines.join(delimiter)}"
|
||||
write(comment)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Node for a function invocation. Takes care of converting super() calls into
|
||||
# calls against the prototype's function of the same name.
|
||||
class CallNode < Node
|
||||
attr_reader :variable, :arguments
|
||||
|
||||
def initialize(variable, arguments=[])
|
||||
@variable, @arguments = variable, arguments
|
||||
end
|
||||
|
||||
def new_instance
|
||||
@new = true
|
||||
self
|
||||
end
|
||||
|
||||
def super?
|
||||
@variable == :super
|
||||
end
|
||||
|
||||
def prefix
|
||||
@new ? "new " : ''
|
||||
end
|
||||
|
||||
def splat?
|
||||
@arguments.any? {|a| a.is_a?(ArgSplatNode) }
|
||||
end
|
||||
|
||||
def <<(argument)
|
||||
@arguments << argument
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
return write(compile_splat(o)) if splat?
|
||||
args = @arguments.map{|a| a.compile(o) }.join(', ')
|
||||
return write(compile_super(args, o)) if super?
|
||||
write("#{prefix}#{@variable.compile(o)}(#{args})")
|
||||
end
|
||||
|
||||
def compile_super(args, o)
|
||||
methname = o[:last_assign]
|
||||
arg_part = args.empty? ? '' : ", #{args}"
|
||||
"#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})"
|
||||
end
|
||||
|
||||
def compile_splat(o)
|
||||
meth = @variable.compile(o)
|
||||
obj = @variable.source || 'this'
|
||||
args = @arguments.map do |arg|
|
||||
code = arg.compile(o)
|
||||
code = arg.is_a?(ArgSplatNode) ? code : "[#{code}]"
|
||||
arg.equal?(@arguments.first) ? code : ".concat(#{code})"
|
||||
end
|
||||
"#{prefix}#{meth}.apply(#{obj}, #{args.join('')})"
|
||||
end
|
||||
end
|
||||
|
||||
# Node to extend an object's prototype with an ancestor object.
|
||||
# After goog.inherits from the Closure Library.
|
||||
class ExtendsNode < Node
|
||||
statement
|
||||
attr_reader :sub_object, :super_object
|
||||
|
||||
def initialize(sub_object, super_object)
|
||||
@sub_object, @super_object = sub_object, super_object
|
||||
end
|
||||
|
||||
def compile_node(o={})
|
||||
sub, sup = @sub_object.compile(o), @super_object.compile(o)
|
||||
"#{o[:indent]}#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" +
|
||||
"#{sub}.prototype = new #{sup}();\n#{o[:indent]}" +
|
||||
"#{sub}.prototype.constructor = #{sub};"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# A value, indexed or dotted into, or vanilla.
|
||||
class ValueNode < Node
|
||||
attr_reader :literal, :properties, :last, :source
|
||||
|
||||
def initialize(literal, properties=[])
|
||||
@literal, @properties = literal, properties
|
||||
end
|
||||
|
||||
def <<(other)
|
||||
@properties << other
|
||||
self
|
||||
end
|
||||
|
||||
def properties?
|
||||
return !@properties.empty?
|
||||
end
|
||||
|
||||
def statement?
|
||||
@literal.is_a?(Node) && @literal.statement? && !properties?
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
only = o.delete(:only_first)
|
||||
props = only ? @properties[0...-1] : @properties
|
||||
parts = [@literal, props].flatten.map do |val|
|
||||
val.respond_to?(:compile) ? val.compile(o) : val.to_s
|
||||
end
|
||||
@last = parts.last
|
||||
@source = parts.length > 1 ? parts[0...-1].join('') : nil
|
||||
write(parts.join(''))
|
||||
end
|
||||
end
|
||||
|
||||
# A dotted accessor into a part of a value.
|
||||
class AccessorNode < Node
|
||||
attr_reader :name
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
write(".#{@name}")
|
||||
end
|
||||
end
|
||||
|
||||
# An indexed accessor into a part of an array or object.
|
||||
class IndexNode < Node
|
||||
attr_reader :index
|
||||
|
||||
def initialize(index)
|
||||
@index = index
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
write("[#{@index.compile(o)}]")
|
||||
end
|
||||
end
|
||||
|
||||
# A range literal. Ranges can be used to extract portions (slices) of arrays,
|
||||
# or to specify a range for array comprehensions.
|
||||
class RangeNode < Node
|
||||
attr_reader :from, :to
|
||||
|
||||
def initialize(from, to, exclusive=false)
|
||||
@from, @to, @exclusive = from, to, exclusive
|
||||
end
|
||||
|
||||
def exclusive?
|
||||
@exclusive
|
||||
end
|
||||
|
||||
def less_operator
|
||||
@exclusive ? '<' : '<='
|
||||
end
|
||||
|
||||
def greater_operator
|
||||
@exclusive ? '>' : '>='
|
||||
end
|
||||
|
||||
def compile_variables(o)
|
||||
idt = o[:indent]
|
||||
@from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable
|
||||
from_val, to_val = @from.compile(o), @to.compile(o)
|
||||
write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}")
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
idx, step = o.delete(:index), o.delete(:step)
|
||||
raise SyntaxError, "unexpected range literal" unless idx
|
||||
vars = "#{idx}=#{@from_var}"
|
||||
step = step ? step.compile(o) : '1'
|
||||
compare = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})"
|
||||
incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})"
|
||||
write("#{vars}; #{compare}; #{incr}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# An array slice literal. Unlike JavaScript's Array#slice, the second parameter
|
||||
# specifies the index of the end of the slice (just like the first parameter)
|
||||
# is the index of the beginning.
|
||||
class SliceNode < Node
|
||||
attr_reader :range
|
||||
|
||||
def initialize(range)
|
||||
@range = range
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
from = @range.from.compile(o)
|
||||
to = @range.to.compile(o)
|
||||
plus_part = @range.exclusive? ? '' : ' + 1'
|
||||
write(".slice(#{from}, #{to}#{plus_part})")
|
||||
end
|
||||
end
|
||||
|
||||
# Setting the value of a local variable, or the value of an object property.
|
||||
class AssignNode < Node
|
||||
PROTO_ASSIGN = /\A(\S+)\.prototype/
|
||||
LEADING_DOT = /\A\./
|
||||
|
||||
attr_reader :variable, :value, :context
|
||||
|
||||
def initialize(variable, value, context=nil)
|
||||
@variable, @value, @context = variable, value, context
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
return compile_splice(o) if @variable.properties.last.is_a?(SliceNode)
|
||||
name = @variable.compile(o)
|
||||
last = @variable.last.to_s.sub(LEADING_DOT, '')
|
||||
proto = name[PROTO_ASSIGN, 1]
|
||||
o = o.merge(:last_assign => last, :proto_assign => proto)
|
||||
o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER)
|
||||
return write("#{name}: #{@value.compile(o)}") if @context == :object
|
||||
o[:scope].find(name) unless @variable.properties?
|
||||
val = "#{name} = #{@value.compile(o)}"
|
||||
write(o[:return] ? "#{o[:indent]}return (#{val})" : val)
|
||||
end
|
||||
|
||||
def compile_splice(o)
|
||||
var = @variable.compile(o.merge(:only_first => true))
|
||||
range = @variable.properties.last.range
|
||||
plus = range.exclusive? ? '' : ' + 1'
|
||||
from = range.from.compile(o)
|
||||
to = "#{range.to.compile(o)} - #{from}#{plus}"
|
||||
write("#{var}.splice.apply(#{var}, [#{from}, #{to}].concat(#{@value.compile(o)}))")
|
||||
end
|
||||
end
|
||||
|
||||
# Simple Arithmetic and logical operations. Performs some conversion from
|
||||
# CoffeeScript operations into their JavaScript equivalents.
|
||||
class OpNode < Node
|
||||
CONVERSIONS = {
|
||||
:== => "===",
|
||||
:'!=' => "!==",
|
||||
:and => '&&',
|
||||
:or => '||',
|
||||
:is => '===',
|
||||
:isnt => "!==",
|
||||
:not => '!'
|
||||
}
|
||||
CONDITIONALS = [:'||=', :'&&=']
|
||||
PREFIX_OPERATORS = [:typeof, :delete]
|
||||
|
||||
attr_reader :operator, :first, :second
|
||||
|
||||
def initialize(operator, first, second=nil, flip=false)
|
||||
@first, @second, @flip = first, second, flip
|
||||
@operator = CONVERSIONS[operator.to_sym] || operator
|
||||
end
|
||||
|
||||
def unary?
|
||||
@second.nil?
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
return write(compile_conditional(o)) if CONDITIONALS.include?(@operator.to_sym)
|
||||
return write(compile_unary(o)) if unary?
|
||||
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
|
||||
end
|
||||
|
||||
def compile_conditional(o)
|
||||
first, second = @first.compile(o), @second.compile(o)
|
||||
sym = @operator[0..1]
|
||||
"#{first} = #{first} #{sym} #{second}"
|
||||
end
|
||||
|
||||
def compile_unary(o)
|
||||
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
|
||||
parts = [@operator.to_s, space, @first.compile(o)]
|
||||
parts.reverse! if @flip
|
||||
parts.join('')
|
||||
end
|
||||
end
|
||||
|
||||
# A function definition. The only node that creates a new Scope.
|
||||
class CodeNode < Node
|
||||
attr_reader :params, :body
|
||||
|
||||
def initialize(params, body)
|
||||
@params = params
|
||||
@body = body
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
shared_scope = o.delete(:shared_scope)
|
||||
indent = o[:indent]
|
||||
o[:scope] = shared_scope || Scope.new(o[:scope], @body)
|
||||
o[:return] = true
|
||||
o[:top] = true
|
||||
o[:indent] += TAB
|
||||
o.delete(:no_wrap)
|
||||
name = o.delete(:immediate_assign)
|
||||
if @params.last.is_a?(ParamSplatNode)
|
||||
splat = @params.pop
|
||||
splat.index = @params.length
|
||||
@body.unshift(splat)
|
||||
end
|
||||
@params.each {|id| o[:scope].parameter(id.to_s) }
|
||||
code = @body.compile_with_declarations(o)
|
||||
name_part = name ? " #{name}" : ''
|
||||
write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
|
||||
end
|
||||
end
|
||||
|
||||
# A parameter splat in a function definition.
|
||||
class ParamSplatNode < Node
|
||||
attr_accessor :index
|
||||
attr_reader :name
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def compile_node(o={})
|
||||
o[:scope].find(@name)
|
||||
write("#{@name} = Array.prototype.slice.call(arguments, #{@index})")
|
||||
end
|
||||
end
|
||||
|
||||
class ArgSplatNode < Node
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def compile_node(o={})
|
||||
write(@value.compile(o))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# An object literal.
|
||||
class ObjectNode < Node
|
||||
attr_reader :properties
|
||||
|
||||
def initialize(properties = [])
|
||||
@properties = properties
|
||||
end
|
||||
|
||||
# All the mucking about with commas is to make sure that CommentNodes and
|
||||
# AssignNodes get interleaved correctly, with no trailing commas or
|
||||
# commas affixed to comments. TODO: Extract this and add it to ArrayNode.
|
||||
def compile_node(o)
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
joins = Hash.new("\n")
|
||||
non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
|
||||
non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
|
||||
props = @properties.map { |prop|
|
||||
join = joins[prop]
|
||||
join = '' if prop == @properties.last
|
||||
idt = prop.is_a?(CommentNode) ? '' : o[:indent]
|
||||
"#{idt}#{prop.compile(o)}#{join}"
|
||||
}.join('')
|
||||
write("{\n#{props}\n#{indent}}")
|
||||
end
|
||||
end
|
||||
|
||||
# An array literal.
|
||||
class ArrayNode < Node
|
||||
attr_reader :objects
|
||||
|
||||
def initialize(objects=[])
|
||||
@objects = objects
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
objects = @objects.map { |obj|
|
||||
code = obj.compile(o)
|
||||
obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
|
||||
obj == @objects.last ? code : "#{code}, "
|
||||
}.join('')
|
||||
ending = objects.include?("\n") ? "\n#{indent}]" : ']'
|
||||
write("[#{objects}#{ending}")
|
||||
end
|
||||
end
|
||||
|
||||
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
||||
# it, all other loops can be manufactured.
|
||||
class WhileNode < Node
|
||||
statement
|
||||
|
||||
attr_reader :condition, :body
|
||||
|
||||
def initialize(condition, body)
|
||||
@condition, @body = condition, body
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
returns = o.delete(:return)
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
o[:top] = true
|
||||
cond = @condition.compile(o)
|
||||
post = returns ? "\n#{indent}return null;" : ''
|
||||
write("#{indent}while (#{cond}) {\n#{@body.compile(o)}\n#{indent}}#{post}")
|
||||
end
|
||||
end
|
||||
|
||||
# The replacement for the for loop is an array comprehension (that compiles)
|
||||
# into a for loop. Also acts as an expression, able to return the result
|
||||
# of the comprehenion. Unlike Python array comprehensions, it's able to pass
|
||||
# the current index of the loop as a second parameter.
|
||||
class ForNode < Node
|
||||
statement
|
||||
|
||||
attr_reader :body, :source, :name, :index, :filter, :step
|
||||
|
||||
def initialize(body, source, name, index=nil)
|
||||
@body, @name, @index = body, name, index
|
||||
@source = source[:source]
|
||||
@filter = source[:filter]
|
||||
@step = source[:step]
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
top_level = o.delete(:top) && !o[:return]
|
||||
range = @source.is_a?(RangeNode)
|
||||
scope = o[:scope]
|
||||
name_found = scope.find(@name)
|
||||
index_found = @index && scope.find(@index)
|
||||
svar = scope.free_variable
|
||||
ivar = range ? name : @index ? @index : scope.free_variable
|
||||
rvar = scope.free_variable unless top_level
|
||||
tvar = scope.free_variable
|
||||
if range
|
||||
body_dent = o[:indent] + TAB
|
||||
var_part, pre_cond, post_cond = '', '', ''
|
||||
index_var = scope.free_variable
|
||||
source_part = @source.compile_variables(o)
|
||||
for_part = "#{index_var}=0, #{@source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
|
||||
else
|
||||
index_var = nil
|
||||
body_dent = o[:indent] + TAB + TAB
|
||||
source_part = "#{o[:indent]}#{svar} = #{@source.compile(o)};\n#{o[:indent]}"
|
||||
for_part = "#{ivar} in #{svar}"
|
||||
pre_cond = "\n#{o[:indent] + TAB}if (#{svar}.hasOwnProperty(#{ivar})) {"
|
||||
var_part = "\n#{body_dent}#{@name} = #{svar}[#{ivar}];"
|
||||
post_cond = "\n#{o[:indent] + TAB}}"
|
||||
end
|
||||
body = @body
|
||||
set_result = rvar ? "#{rvar} = [];\n#{o[:indent]}" : ''
|
||||
return_result = rvar || ''
|
||||
temp_var = ValueNode.new(LiteralNode.new(tvar))
|
||||
if top_level
|
||||
body = Expressions.wrap(body)
|
||||
else
|
||||
body = Expressions.wrap(
|
||||
AssignNode.new(temp_var, @body.unwrap),
|
||||
CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var])
|
||||
)
|
||||
end
|
||||
if o[:return]
|
||||
return_result = "return #{return_result}" if o[:return]
|
||||
o.delete(:return)
|
||||
body = IfNode.new(@filter, body, nil, :statement => true) if @filter
|
||||
elsif @filter
|
||||
body = Expressions.wrap(IfNode.new(@filter, @body))
|
||||
end
|
||||
|
||||
return_result = "\n#{o[:indent]}#{return_result};" unless top_level
|
||||
body = body.compile(o.merge(:indent => body_dent, :top => true))
|
||||
write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body}#{post_cond}\n#{o[:indent]}}#{return_result}")
|
||||
end
|
||||
end
|
||||
|
||||
# A try/catch/finally block.
|
||||
class TryNode < Node
|
||||
statement
|
||||
|
||||
attr_reader :try, :error, :recovery, :finally
|
||||
|
||||
def initialize(try, error, recovery, finally=nil)
|
||||
@try, @error, @recovery, @finally = try, error, recovery, finally
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
o[:top] = true
|
||||
error_part = @error ? " (#{@error}) " : ' '
|
||||
catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}"
|
||||
finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{indent}}"
|
||||
write("#{indent}try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}")
|
||||
end
|
||||
end
|
||||
|
||||
# Throw an exception.
|
||||
class ThrowNode < Node
|
||||
statement_only
|
||||
|
||||
attr_reader :expression
|
||||
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
write("#{o[:indent]}throw #{@expression.compile(o)};")
|
||||
end
|
||||
end
|
||||
|
||||
# Check an expression for existence (meaning not null or undefined).
|
||||
class ExistenceNode < Node
|
||||
attr_reader :expression
|
||||
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
val = @expression.compile(o)
|
||||
write("(typeof #{val} !== \"undefined\" && #{val} !== null)")
|
||||
end
|
||||
end
|
||||
|
||||
# An extra set of parentheses, supplied by the script source.
|
||||
# You can't wrap parentheses around bits that get compiled into JS statements,
|
||||
# unfortunately.
|
||||
class ParentheticalNode < Node
|
||||
attr_reader :expressions
|
||||
|
||||
def initialize(expressions, line=nil)
|
||||
@expressions = expressions.unwrap
|
||||
@line = line
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
compiled = @expressions.compile(o)
|
||||
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
|
||||
write("(#{compiled})")
|
||||
end
|
||||
end
|
||||
|
||||
# If/else statements. Switch/whens get compiled into these. Acts as an
|
||||
# expression by pushing down requested returns to the expression bodies.
|
||||
# Single-expression IfNodes are compiled into ternary operators if possible,
|
||||
# because ternaries are first-class returnable assignable expressions.
|
||||
class IfNode < Node
|
||||
attr_reader :condition, :body, :else_body
|
||||
|
||||
def initialize(condition, body, else_body=nil, tags={})
|
||||
@condition = condition
|
||||
@body = body && body.unwrap
|
||||
@else_body = else_body && else_body.unwrap
|
||||
@tags = tags
|
||||
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
|
||||
end
|
||||
|
||||
def <<(else_body)
|
||||
eb = else_body.unwrap
|
||||
@else_body ? @else_body << eb : @else_body = eb
|
||||
self
|
||||
end
|
||||
|
||||
def force_statement
|
||||
@tags[:statement] = true
|
||||
self
|
||||
end
|
||||
|
||||
# Rewrite a chain of IfNodes with their switch condition for equality.
|
||||
def rewrite_condition(expression)
|
||||
@condition = OpNode.new("is", expression, @condition)
|
||||
@else_body.rewrite_condition(expression) if chain?
|
||||
self
|
||||
end
|
||||
|
||||
# Rewrite a chain of IfNodes to add a default case as the final else.
|
||||
def add_else(exprs)
|
||||
chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap)
|
||||
self
|
||||
end
|
||||
|
||||
# If the else_body is an IfNode itself, then we've got an if-else chain.
|
||||
def chain?
|
||||
@chain ||= @else_body && @else_body.is_a?(IfNode)
|
||||
end
|
||||
|
||||
# The IfNode only compiles into a statement if either of the bodies needs
|
||||
# to be a statement.
|
||||
def statement?
|
||||
@is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
|
||||
end
|
||||
|
||||
def compile_node(o)
|
||||
write(statement? ? compile_statement(o) : compile_ternary(o))
|
||||
end
|
||||
|
||||
# Compile the IfNode as a regular if-else statement. Flattened chains
|
||||
# force sub-else bodies into statement form.
|
||||
def compile_statement(o)
|
||||
indent = o[:indent]
|
||||
child = o.delete(:chain_child)
|
||||
cond_o = o.dup
|
||||
cond_o.delete(:return)
|
||||
o[:indent] += TAB
|
||||
o[:top] = true
|
||||
if_dent = child ? '' : indent
|
||||
if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}"
|
||||
return if_part unless @else_body
|
||||
else_part = chain? ?
|
||||
" else #{@else_body.compile(o.merge(:indent => indent, :chain_child => true))}" :
|
||||
" else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{indent}}"
|
||||
if_part + else_part
|
||||
end
|
||||
|
||||
# Compile the IfNode into a ternary operator.
|
||||
def compile_ternary(o)
|
||||
if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}"
|
||||
else_part = @else_body ? "#{@else_body.compile(o)}" : 'null'
|
||||
"#{if_part} : #{else_part}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
23
lib/coffee_script/parse_error.rb
Normal file
23
lib/coffee_script/parse_error.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
module CoffeeScript
|
||||
|
||||
# Racc will raise this Exception whenever a syntax error occurs. The main
|
||||
# benefit over the Racc::ParseError is that the CoffeeScript::ParseError is
|
||||
# line-number aware.
|
||||
class ParseError < Racc::ParseError
|
||||
|
||||
def initialize(token_id, value, stack)
|
||||
@token_id, @value, @stack = token_id, value, stack
|
||||
end
|
||||
|
||||
def message
|
||||
line = @value.respond_to?(:line) ? @value.line : "END"
|
||||
line_part = "line #{line}:"
|
||||
id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.to_s.downcase}" : ""
|
||||
val_part = ['INDENT', 'OUTDENT'].include?(@token_id) ? '' : " for '#{@value.to_s}'"
|
||||
"#{line_part} syntax error#{val_part}#{id_part}"
|
||||
end
|
||||
alias_method :inspect, :message
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
2283
lib/coffee_script/parser.rb
Normal file
2283
lib/coffee_script/parser.rb
Normal file
File diff suppressed because it is too large
Load Diff
208
lib/coffee_script/rewriter.rb
Normal file
208
lib/coffee_script/rewriter.rb
Normal file
@@ -0,0 +1,208 @@
|
||||
module CoffeeScript
|
||||
|
||||
# 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.
|
||||
class Rewriter
|
||||
|
||||
# Tokens that must be balanced.
|
||||
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]]
|
||||
|
||||
# Tokens that signal the start of a balanced pair.
|
||||
EXPRESSION_START = BALANCED_PAIRS.map {|pair| pair.first }
|
||||
|
||||
# Tokens that signal the end of a balanced pair.
|
||||
EXPRESSION_TAIL = BALANCED_PAIRS.map {|pair| pair.last }
|
||||
|
||||
# Tokens that indicate the close of a clause of an expression.
|
||||
EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL
|
||||
|
||||
# The inverse mappings of token pairs we're trying to fix up.
|
||||
INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair|
|
||||
memo[pair.first] = pair.last
|
||||
memo[pair.last] = pair.first
|
||||
memo
|
||||
end
|
||||
|
||||
# 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 = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN]
|
||||
|
||||
# Rewrite the token stream in multiple passes, one logical filter at
|
||||
# a time. This could certainly be changed into a single pass through the
|
||||
# stream, with a big ol' efficient switch, but it's much nicer like this.
|
||||
def rewrite(tokens)
|
||||
@tokens = tokens
|
||||
adjust_comments
|
||||
remove_mid_expression_newlines
|
||||
move_commas_outside_outdents
|
||||
add_implicit_indentation
|
||||
ensure_balance(*BALANCED_PAIRS)
|
||||
rewrite_closing_parens
|
||||
@tokens
|
||||
end
|
||||
|
||||
# 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.
|
||||
def scan_tokens
|
||||
i = 0
|
||||
loop do
|
||||
break unless @tokens[i]
|
||||
move = yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
|
||||
i += move
|
||||
end
|
||||
end
|
||||
|
||||
# Massage newlines and indentations so that comments don't have to be
|
||||
# correctly indented, or appear on their own line.
|
||||
def adjust_comments
|
||||
scan_tokens do |prev, token, post, i|
|
||||
next 1 unless token[0] == :COMMENT
|
||||
before, after = @tokens[i - 2], @tokens[i + 2]
|
||||
if before && after &&
|
||||
((before[0] == :INDENT && after[0] == :OUTDENT) ||
|
||||
(before[0] == :OUTDENT && after[0] == :INDENT)) &&
|
||||
before[1] == after[1]
|
||||
@tokens.delete_at(i + 2)
|
||||
@tokens.delete_at(i - 2)
|
||||
next 0
|
||||
elsif !["\n", :INDENT, :OUTDENT].include?(prev[0])
|
||||
@tokens.insert(i, ["\n", Value.new("\n", token[1].line)])
|
||||
next 2
|
||||
else
|
||||
next 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Some blocks occur in the middle of expressions -- when we're expecting
|
||||
# this, remove their trailing newlines.
|
||||
def remove_mid_expression_newlines
|
||||
scan_tokens do |prev, token, post, i|
|
||||
next 1 unless post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n"
|
||||
@tokens.delete_at(i)
|
||||
next 0
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure that we don't accidentally break trailing commas, which need
|
||||
# to go on the outside of expression closers.
|
||||
def move_commas_outside_outdents
|
||||
scan_tokens do |prev, token, post, i|
|
||||
if token[0] == :OUTDENT && prev[0] == ','
|
||||
@tokens.delete_at(i)
|
||||
@tokens.insert(i - 1, token)
|
||||
end
|
||||
next 1
|
||||
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.
|
||||
# ')' can close a single-line block, but we need to make sure it's balanced.
|
||||
def add_implicit_indentation
|
||||
scan_tokens do |prev, token, post, i|
|
||||
next 1 unless SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT &&
|
||||
!(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks.
|
||||
line = token[1].line
|
||||
@tokens.insert(i + 1, [:INDENT, Value.new(2, line)])
|
||||
idx = i + 1
|
||||
parens = 0
|
||||
loop do
|
||||
idx += 1
|
||||
tok = @tokens[idx]
|
||||
if !tok || SINGLE_CLOSERS.include?(tok[0]) ||
|
||||
(tok[0] == ')' && parens == 0)
|
||||
@tokens.insert(idx, [:OUTDENT, Value.new(2, line)])
|
||||
break
|
||||
end
|
||||
parens += 1 if tok[0] == '('
|
||||
parens -= 1 if tok[0] == ')'
|
||||
end
|
||||
next 1 unless token[0] == :THEN
|
||||
@tokens.delete_at(i)
|
||||
next 0
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||
# the course of the token stream.
|
||||
def ensure_balance(*pairs)
|
||||
levels = Hash.new(0)
|
||||
scan_tokens do |prev, token, post, i|
|
||||
pairs.each do |pair|
|
||||
open, close = *pair
|
||||
levels[open] += 1 if token[0] == open
|
||||
levels[open] -= 1 if token[0] == close
|
||||
raise ParseError.new(token[0], token[1], nil) if levels[open] < 0
|
||||
end
|
||||
next 1
|
||||
end
|
||||
unclosed = levels.detect {|k, v| v > 0 }
|
||||
raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed
|
||||
end
|
||||
|
||||
# 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.
|
||||
#
|
||||
def rewrite_closing_parens
|
||||
verbose = ENV['VERBOSE']
|
||||
stack, debt = [], Hash.new(0)
|
||||
stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" }
|
||||
puts "rewrite_closing_original: #{@tokens.inspect}" if verbose
|
||||
scan_tokens do |prev, token, post, i|
|
||||
tag, inv = token[0], INVERSES[token[0]]
|
||||
# Push openers onto the stack.
|
||||
if EXPRESSION_START.include?(tag)
|
||||
stack.push(token)
|
||||
puts "pushing #{tag} #{stack_stats[]}" if verbose
|
||||
next 1
|
||||
# The end of an expression, check stack and debt for a pair.
|
||||
elsif EXPRESSION_TAIL.include?(tag)
|
||||
puts @tokens[i..-1].inspect if verbose
|
||||
# If the tag is already in our debt, swallow it.
|
||||
if debt[inv] > 0
|
||||
debt[inv] -= 1
|
||||
@tokens.delete_at(i)
|
||||
puts "tag in debt #{tag} #{stack_stats[]}" if verbose
|
||||
next 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]
|
||||
puts "expected tag #{tag} #{stack_stats[]}" if verbose
|
||||
next 1
|
||||
else
|
||||
# Unexpected close, insert correct close, adding to the debt.
|
||||
debt[mtag] += 1
|
||||
puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose
|
||||
val = mtag == :INDENT ? match[1] : INVERSES[mtag]
|
||||
@tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)])
|
||||
next 1
|
||||
end
|
||||
end
|
||||
else
|
||||
# Uninteresting token:
|
||||
next 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
65
lib/coffee_script/scope.rb
Normal file
65
lib/coffee_script/scope.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
module CoffeeScript
|
||||
|
||||
# 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.
|
||||
class Scope
|
||||
|
||||
attr_reader :parent, :expressions, :variables, :temp_variable
|
||||
|
||||
# Initialize a scope with its parent, for lookups up the chain,
|
||||
# as well as the Expressions body where it should declare its variables.
|
||||
def initialize(parent, expressions)
|
||||
@parent, @expressions = parent, expressions
|
||||
@variables = {}
|
||||
@temp_variable = @parent ? @parent.temp_variable.dup : '__a'
|
||||
end
|
||||
|
||||
# Look up a variable in lexical scope, or declare it if not found.
|
||||
def find(name, remote=false)
|
||||
found = check(name)
|
||||
return found if found || remote
|
||||
@variables[name.to_sym] = :var
|
||||
found
|
||||
end
|
||||
|
||||
# Define a local variable as originating from a parameter in current scope
|
||||
# -- no var required.
|
||||
def parameter(name)
|
||||
@variables[name.to_sym] = :param
|
||||
end
|
||||
|
||||
# Just check to see if a variable has already been declared.
|
||||
def check(name)
|
||||
return true if @variables[name.to_sym]
|
||||
!!(@parent && @parent.check(name))
|
||||
end
|
||||
|
||||
# You can reset a found variable on the immediate scope.
|
||||
def reset(name)
|
||||
@variables[name.to_sym] = false
|
||||
end
|
||||
|
||||
# Find an available, short, name for a compiler-generated variable.
|
||||
def free_variable
|
||||
@temp_variable.succ! while check(@temp_variable)
|
||||
@variables[@temp_variable.to_sym] = :var
|
||||
@temp_variable.dup
|
||||
end
|
||||
|
||||
def declarations?(body)
|
||||
!declared_variables.empty? && body == @expressions
|
||||
end
|
||||
|
||||
# Return the list of variables first declared in current scope.
|
||||
def declared_variables
|
||||
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<Scope:#{__id__} #{@variables.inspect}>"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
42
lib/coffee_script/value.rb
Normal file
42
lib/coffee_script/value.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
module CoffeeScript
|
||||
|
||||
# Instead of producing raw Ruby objects, the Lexer produces values of this
|
||||
# class, wrapping native objects tagged with line number information.
|
||||
class Value
|
||||
attr_reader :value, :line
|
||||
|
||||
def initialize(value, line)
|
||||
@value, @line = value, line
|
||||
end
|
||||
|
||||
def to_str
|
||||
@value.to_s
|
||||
end
|
||||
alias_method :to_s, :to_str
|
||||
|
||||
def to_sym
|
||||
to_str.to_sym
|
||||
end
|
||||
|
||||
def inspect
|
||||
@value.inspect
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@value == other
|
||||
end
|
||||
|
||||
def [](index)
|
||||
@value[index]
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
@value.eql?(other)
|
||||
end
|
||||
|
||||
def hash
|
||||
@value.hash
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
455
nodes.rb
455
nodes.rb
@@ -1,455 +0,0 @@
|
||||
class Scope
|
||||
|
||||
attr_reader :parent, :temp_variable
|
||||
|
||||
def initialize(parent=nil)
|
||||
@parent = parent
|
||||
@variables = {}
|
||||
@temp_variable = @parent ? @parent.temp_variable : 'a'
|
||||
end
|
||||
|
||||
# Look up a variable in lexical scope, or declare it if not found.
|
||||
def find(name, remote=false)
|
||||
found = check(name, remote)
|
||||
return found if found || remote
|
||||
@variables[name] = true
|
||||
found
|
||||
end
|
||||
|
||||
# Just check for the pre-definition of a variable.
|
||||
def check(name, remote=false)
|
||||
return true if @variables[name]
|
||||
@parent && @parent.find(name, true)
|
||||
end
|
||||
|
||||
# Find an available, short variable name.
|
||||
def free_variable
|
||||
@temp_variable.succ! while check(@temp_variable)
|
||||
@variables[@temp_variable] = true
|
||||
@temp_variable.dup
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Node
|
||||
# Tabs are two spaces for pretty-printing.
|
||||
TAB = ' '
|
||||
|
||||
def flatten; self; end
|
||||
def line_ending; ';'; end
|
||||
def statement?; false; end
|
||||
def custom_return?; false; end
|
||||
def custom_assign?; false; end
|
||||
|
||||
def compile(indent='', scope=nil, opts={}); end
|
||||
end
|
||||
|
||||
# Collection of nodes each one representing an expression.
|
||||
class Nodes < Node
|
||||
attr_reader :nodes
|
||||
|
||||
def self.wrap(node)
|
||||
node.is_a?(Nodes) ? node : Nodes.new([node])
|
||||
end
|
||||
|
||||
def initialize(nodes)
|
||||
@nodes = nodes
|
||||
end
|
||||
|
||||
def <<(node)
|
||||
@nodes << node
|
||||
self
|
||||
end
|
||||
|
||||
def flatten
|
||||
@nodes.length == 1 ? @nodes.first : self
|
||||
end
|
||||
|
||||
def begin_compile
|
||||
"(function(){\n#{compile(TAB, Scope.new)}\n})();"
|
||||
end
|
||||
|
||||
# Fancy to handle pushing down returns recursively to the final lines of
|
||||
# inner statements (to make expressions out of them).
|
||||
def compile(indent='', scope=nil, opts={})
|
||||
return begin_compile unless scope
|
||||
@nodes.map { |n|
|
||||
if opts[:return] && n == @nodes.last
|
||||
if n.statement? || n.custom_return?
|
||||
"#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}"
|
||||
else
|
||||
"#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}"
|
||||
end
|
||||
else
|
||||
"#{indent}#{n.compile(indent, scope)}#{n.line_ending}"
|
||||
end
|
||||
}.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# Literals are static values that have a Ruby representation, eg.: a string, a number,
|
||||
# true, false, nil, etc.
|
||||
class LiteralNode < Node
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
@value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class ReturnNode < Node
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def custom_return?
|
||||
true
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
compiled = @expression.compile(indent, scope)
|
||||
@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}"
|
||||
end
|
||||
end
|
||||
|
||||
# Node of a method call or local variable access, can take any of these forms:
|
||||
#
|
||||
# method # this form can also be a local variable
|
||||
# method(argument1, argument2)
|
||||
# receiver.method
|
||||
# receiver.method(argument1, argument2)
|
||||
#
|
||||
class CallNode < Node
|
||||
def initialize(variable, arguments=[])
|
||||
@variable, @arguments = variable, arguments
|
||||
end
|
||||
|
||||
def new_instance
|
||||
@new = true
|
||||
self
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ')
|
||||
prefix = @new ? "new " : ''
|
||||
"#{prefix}#{@variable.compile(indent, scope)}(#{args})"
|
||||
end
|
||||
end
|
||||
|
||||
class ValueNode < Node
|
||||
def initialize(name, properties=[])
|
||||
@name, @properties = name, properties
|
||||
end
|
||||
|
||||
def <<(other)
|
||||
@properties << other
|
||||
self
|
||||
end
|
||||
|
||||
def properties?
|
||||
return !@properties.empty?
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
[@name, @properties].flatten.map { |v|
|
||||
v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s
|
||||
}.join('')
|
||||
end
|
||||
end
|
||||
|
||||
class AccessorNode
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
".#{@name}"
|
||||
end
|
||||
end
|
||||
|
||||
class IndexNode
|
||||
def initialize(index)
|
||||
@index = index
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
"[#{@index.compile(indent, scope)}]"
|
||||
end
|
||||
end
|
||||
|
||||
class SliceNode
|
||||
def initialize(from, to)
|
||||
@from, @to = from, to
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)"
|
||||
end
|
||||
end
|
||||
|
||||
# Setting the value of a local variable.
|
||||
class AssignNode < Node
|
||||
def initialize(variable, value, context=nil)
|
||||
@variable, @value, @context = variable, value, context
|
||||
end
|
||||
|
||||
def custom_return?
|
||||
true
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
value = @value.compile(indent + TAB, scope)
|
||||
return "#{@variable}: #{value}" if @context == :object
|
||||
name = @variable.compile(indent, scope)
|
||||
return "#{name} = #{value}" if @variable.properties?
|
||||
defined = scope.find(name)
|
||||
postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : ''
|
||||
def_part = defined ? "" : "var #{name};\n#{indent}"
|
||||
return def_part + @value.compile(indent, scope, opts.merge(:assign => name)) if @value.custom_assign?
|
||||
def_part = defined ? name : "var #{name}"
|
||||
"#{def_part} = #{@value.compile(indent, scope)}#{postfix}"
|
||||
end
|
||||
end
|
||||
|
||||
# Simple Arithmetic and logical operations
|
||||
class OpNode < Node
|
||||
CONVERSIONS = {
|
||||
"==" => "===",
|
||||
"!=" => "!==",
|
||||
'and' => '&&',
|
||||
'or' => '||',
|
||||
'is' => '===',
|
||||
"aint" => "!==",
|
||||
'not' => '!',
|
||||
}
|
||||
CONDITIONALS = ['||=', '&&=']
|
||||
|
||||
def initialize(operator, first, second=nil)
|
||||
@first, @second = first, second
|
||||
@operator = CONVERSIONS[operator] || operator
|
||||
end
|
||||
|
||||
def unary?
|
||||
@second.nil?
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
return compile_conditional(indent, scope) if CONDITIONALS.include?(@operator)
|
||||
return compile_unary(indent, scope) if unary?
|
||||
"#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}"
|
||||
end
|
||||
|
||||
def compile_conditional(indent, scope)
|
||||
first, second = @first.compile(indent, scope), @second.compile(indent, scope)
|
||||
sym = @operator[0..1]
|
||||
"#{first} = #{first} #{sym} #{second}"
|
||||
end
|
||||
|
||||
def compile_unary(indent, scope)
|
||||
"#{@operator}#{@first.compile(indent, scope)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Method definition.
|
||||
class CodeNode < Node
|
||||
def initialize(params, body)
|
||||
@params = params
|
||||
@body = body
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
code = @body.compile(indent + TAB, Scope.new(scope), {:return => true})
|
||||
"function(#{@params.join(', ')}) {\n#{code}\n#{indent}}"
|
||||
end
|
||||
end
|
||||
|
||||
class ObjectNode < Node
|
||||
def initialize(properties = [])
|
||||
@properties = properties
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
props = @properties.map {|p| indent + TAB + p.compile(indent, scope) }.join(",\n")
|
||||
"{\n#{props}\n#{indent}}"
|
||||
end
|
||||
end
|
||||
|
||||
class ArrayNode < Node
|
||||
def initialize(objects=[])
|
||||
@objects = objects
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
objects = @objects.map {|o| o.compile(indent, scope) }.join(', ')
|
||||
"[#{objects}]"
|
||||
end
|
||||
end
|
||||
|
||||
# "if-else" control structure. Look at this node if you want to implement other control
|
||||
# structures like while, for, loop, etc.
|
||||
class IfNode < Node
|
||||
FORCE_STATEMENT = [Nodes, ReturnNode, AssignNode, IfNode]
|
||||
|
||||
def initialize(condition, body, else_body=nil, tag=nil)
|
||||
@condition = condition
|
||||
@body = body && body.flatten
|
||||
@else_body = else_body && else_body.flatten
|
||||
@condition = OpNode.new("!", @condition) if tag == :invert
|
||||
end
|
||||
|
||||
def <<(else_body)
|
||||
eb = else_body.flatten
|
||||
@else_body ? @else_body << eb : @else_body = eb
|
||||
self
|
||||
end
|
||||
|
||||
# Rewrite a chain of IfNodes with their switch condition for equality.
|
||||
def rewrite_condition(expression)
|
||||
@condition = OpNode.new("is", expression, @condition)
|
||||
@else_body.rewrite_condition(expression) if chain?
|
||||
self
|
||||
end
|
||||
|
||||
# Rewrite a chain of IfNodes to add a default case as the final else.
|
||||
def add_default(expressions)
|
||||
chain? ? @else_body.add_default(expressions) : @else_body = expressions
|
||||
self
|
||||
end
|
||||
|
||||
def chain?
|
||||
@chain ||= @else_body && @else_body.is_a?(IfNode)
|
||||
end
|
||||
|
||||
def statement?
|
||||
@is_statement ||= (FORCE_STATEMENT.include?(@body.class) || FORCE_STATEMENT.include?(@else_body.class))
|
||||
end
|
||||
|
||||
def line_ending
|
||||
statement? ? '' : ';'
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope)
|
||||
end
|
||||
|
||||
def compile_statement(indent, scope, opts)
|
||||
if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}"
|
||||
else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : ''
|
||||
if_part + else_part
|
||||
end
|
||||
|
||||
def compile_ternary(indent, scope)
|
||||
if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}"
|
||||
else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null'
|
||||
"#{if_part} : #{else_part}"
|
||||
end
|
||||
end
|
||||
|
||||
class WhileNode < Node
|
||||
def initialize(condition, body)
|
||||
@condition, @body = condition, body
|
||||
end
|
||||
|
||||
def line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def statement?
|
||||
true
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
"while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}"
|
||||
end
|
||||
end
|
||||
|
||||
class ForNode < Node
|
||||
|
||||
def initialize(body, source, name, index=nil)
|
||||
@body, @source, @name, @index = body, source, name, index
|
||||
end
|
||||
|
||||
def line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def custom_return?
|
||||
true
|
||||
end
|
||||
|
||||
def custom_assign?
|
||||
true
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
svar = scope.free_variable
|
||||
ivar = scope.free_variable
|
||||
lvar = scope.free_variable
|
||||
name_part = scope.find(@name) ? @name : "var #{@name}"
|
||||
index_name = @index ? (scope.find(@index) ? @index : "var #{@index}") : nil
|
||||
source_part = "var #{svar} = #{@source.compile(indent, scope)};"
|
||||
for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
|
||||
var_part = "\n#{indent + TAB}#{name_part} = #{svar}[#{ivar}];\n"
|
||||
index_part = @index ? "#{indent + TAB}#{index_name} = #{ivar};\n" : ''
|
||||
|
||||
set_result = ''
|
||||
save_result = ''
|
||||
return_result = ''
|
||||
if opts[:return] || opts[:assign]
|
||||
rvar = scope.free_variable
|
||||
set_result = "var #{rvar} = [];\n#{indent}"
|
||||
save_result = "#{rvar}[#{ivar}] = "
|
||||
return_result = rvar
|
||||
return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign]
|
||||
return_result = "return #{return_result}" if opts[:return]
|
||||
return_result = "\n#{indent}#{return_result}"
|
||||
end
|
||||
|
||||
body = @body.compile(indent + TAB, scope)
|
||||
"#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body};\n#{indent}}#{return_result}"
|
||||
end
|
||||
end
|
||||
|
||||
class TryNode < Node
|
||||
def initialize(try, error, recovery, finally=nil)
|
||||
@try, @error, @recovery, @finally = try, error, recovery, finally
|
||||
end
|
||||
|
||||
def line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def statement?
|
||||
true
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}"
|
||||
finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}"
|
||||
"try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}"
|
||||
end
|
||||
end
|
||||
|
||||
class ThrowNode < Node
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
"throw #{@expression.compile(indent, scope)}"
|
||||
end
|
||||
end
|
||||
|
||||
class ParentheticalNode < Node
|
||||
def initialize(expressions)
|
||||
@expressions = expressions
|
||||
end
|
||||
|
||||
def compile(indent, scope, opts={})
|
||||
compiled = @expressions.flatten.compile(indent, scope)
|
||||
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
|
||||
opts[:no_paren] ? compiled : "(#{compiled})"
|
||||
end
|
||||
end
|
||||
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "coffee-script",
|
||||
"lib": "lib/coffee_script/narwhal/lib",
|
||||
"preload": ["coffee-script/loader"],
|
||||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
"version": "0.2.0"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# Recompile the Parser.
|
||||
# With debugging and verbose: -v -g
|
||||
`racc -v -o parser.rb grammar.y`
|
||||
|
||||
# Parse and print the compiled CoffeeScript source.
|
||||
require "parser.rb"
|
||||
js = Parser.new.parse(File.read('code.cs')).compile
|
||||
puts "\n\n"
|
||||
puts js
|
||||
|
||||
# Pipe compiled JS through JSLint.
|
||||
puts "\n\n"
|
||||
require 'open3'
|
||||
stdin, stdout, stderr = Open3.popen3('/Users/jashkenas/Library/Application\ Support/TextMate/Bundles/JavaScript\ Tools.tmbundle/Support/bin/jsl -nologo -stdin')
|
||||
stdin.write(js)
|
||||
stdin.close
|
||||
puts stdout.read
|
||||
stdout.close
|
||||
stderr.close
|
||||
105
poignant.cs
105
poignant.cs
@@ -1,105 +0,0 @@
|
||||
# Examples from the Poignant Guide.
|
||||
|
||||
# ['toast', 'cheese', 'wine'].each { |food| print food.capitalize }
|
||||
|
||||
['toast', 'wine', 'cheese'].each( food => print(food.capitalize()). )
|
||||
|
||||
|
||||
|
||||
# class LotteryTicket
|
||||
# def picks; @picks; end
|
||||
# def picks=(var); @picks = var; end
|
||||
# def purchased; @purchased; end
|
||||
# def purchased=(var); @purchased = var; end
|
||||
# end
|
||||
|
||||
LotteryTicket: {
|
||||
get_picks: => this.picks.
|
||||
set_picks: nums => this.picks: nums.
|
||||
get_purchase: => this.purchase.
|
||||
set_purchase: amount => this.purchase: amount.
|
||||
}
|
||||
|
||||
|
||||
|
||||
# module WishScanner
|
||||
# def scan_for_a_wish
|
||||
# wish = self.read.detect do |thought|
|
||||
# thought.index( 'wish: ' ) == 0
|
||||
# end
|
||||
# wish.gsub( 'wish: ', '' )
|
||||
# end
|
||||
# end
|
||||
|
||||
WishScanner: {
|
||||
scan_for_a_wish: =>
|
||||
wish: this.read().detect( thought => thought.index('wish: ') is 0. )
|
||||
wish.replace('wish: ', '').
|
||||
}
|
||||
|
||||
|
||||
|
||||
# class Creature
|
||||
#
|
||||
# # This method applies a hit taken during a fight.
|
||||
# def hit( damage )
|
||||
# p_up = rand( charisma )
|
||||
# if p_up % 9 == 7
|
||||
# @life += p_up / 4
|
||||
# puts "[#{ self.class } magick powers up #{ p_up }!]"
|
||||
# end
|
||||
# @life -= damage
|
||||
# puts "[#{ self.class } has died.]" if @life <= 0
|
||||
# end
|
||||
#
|
||||
# # This method takes one turn in a fight.
|
||||
# def fight( enemy, weapon )
|
||||
# if life <= 0
|
||||
# puts "[#{ self.class } is too dead to fight!]"
|
||||
# return
|
||||
# end
|
||||
#
|
||||
# # Attack the opponent
|
||||
# your_hit = rand( strength + weapon )
|
||||
# puts "[You hit with #{ your_hit } points of damage!]"
|
||||
# enemy.hit( your_hit )
|
||||
#
|
||||
# # Retaliation
|
||||
# p enemy
|
||||
# if enemy.life > 0
|
||||
# enemy_hit = rand( enemy.strength + enemy.weapon )
|
||||
# puts "[Your enemy hit with #{ enemy_hit } points of damage!]"
|
||||
# self.hit( enemy_hit )
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# end
|
||||
|
||||
Creature : {
|
||||
|
||||
# This method applies a hit taken during a fight.
|
||||
hit: damage =>
|
||||
p_up: Math.rand( this.charisma )
|
||||
if p_up % 9 is 7
|
||||
this.life += p_up / 4
|
||||
puts( "[" + this.name + " magick powers up " + p_up + "!]" ).
|
||||
this.life -= damage
|
||||
if this.life <= 0 then puts( "[" + this.name + " has died.]" )..
|
||||
|
||||
# This method takes one turn in a fight.
|
||||
fight: enemy, weapon =>
|
||||
if this.life <= 0 then return puts( "[" + this.name + "is too dead to fight!]" ).
|
||||
|
||||
# Attack the opponent.
|
||||
your_hit: Math.rand( this.strength + weapon )
|
||||
puts( "[You hit with " + your_hit + "points of damage!]" )
|
||||
enemy.hit( your_hit )
|
||||
|
||||
# Retaliation.
|
||||
puts( enemy )
|
||||
if enemy.life > 0
|
||||
enemy_hit: Math.rand( enemy.strength + enemy.weapon )
|
||||
puts( "[Your enemy hit with " + enemy_hit + "points of damage!]" )
|
||||
this.hit( enemy_hit )..
|
||||
|
||||
}
|
||||
17
test/fixtures/execution/test_arguments.coffee
vendored
Normal file
17
test/fixtures/execution/test_arguments.coffee
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
area: x, y, x1, y1 =>
|
||||
(x - x1) * (x - y1)
|
||||
|
||||
x: y: 10
|
||||
x1: y1: 20
|
||||
|
||||
print(area(x, y, x1, y1) is 100)
|
||||
|
||||
print(area(x, y,
|
||||
x1, y1) is 100)
|
||||
|
||||
print(area(
|
||||
x
|
||||
y
|
||||
x1
|
||||
y1
|
||||
) is 100)
|
||||
21
test/fixtures/execution/test_array_comprehension.coffee
vendored
Normal file
21
test/fixtures/execution/test_array_comprehension.coffee
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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')
|
||||
|
||||
|
||||
obj: {one: 1, two: 2, three: 3}
|
||||
names: key + '!' for value, key in obj
|
||||
odds: key + '!' for value, key in obj when value % 2 isnt 0
|
||||
|
||||
print(names.join(' ') is "one! two! three!")
|
||||
print(odds.join(' ') is "one! three!")
|
||||
|
||||
|
||||
evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
|
||||
num *= -1
|
||||
num -= 2
|
||||
num * -1
|
||||
|
||||
print(evens.join(', ') is '4, 6, 8')
|
||||
|
||||
8
test/fixtures/execution/test_assign_to_try_catch.coffee
vendored
Normal file
8
test/fixtures/execution/test_assign_to_try_catch.coffee
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
result: try
|
||||
nonexistent * missing
|
||||
catch error
|
||||
true
|
||||
|
||||
result2: try nonexistent * missing catch error then true
|
||||
|
||||
print(result is true and result2 is true)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user