mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-01-13 08:47:55 -05:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
parser.output
|
||||
parser.output
|
||||
*.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-script /path/to/script.cs
|
||||
|
||||
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
|
||||
50
Rakefile
Normal file
50
Rakefile
Normal file
@@ -0,0 +1,50 @@
|
||||
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
|
||||
|
||||
desc "Recompile the Racc parser (pass -v and -g for verbose debugging)"
|
||||
task :build, :extra_args do |t, args|
|
||||
sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y"
|
||||
end
|
||||
|
||||
desc "Build the documentation page"
|
||||
task :doc do
|
||||
source = 'documentation/index.html.erb'
|
||||
child = fork { exec "bin/coffee-script documentation/cs/*.cs -o documentation/js -w" }
|
||||
at_exit { Process.kill("INT", child) }
|
||||
Signal.trap("INT") { exit }
|
||||
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-script
Executable file
5
bin/coffee-script
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "#{File.dirname(__FILE__)}/../lib/coffee_script/command_line.rb"
|
||||
|
||||
CoffeeScript::CommandLine.new
|
||||
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.1.1' # Keep version in sync with coffee-script.rb
|
||||
s.date = '2009-12-24'
|
||||
|
||||
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-script']
|
||||
|
||||
s.files = Dir['bin/*', 'examples/*', 'lib/**/*', 'coffee-script.gemspec', 'LICENSE', 'README']
|
||||
end
|
||||
7
documentation/cs/aliases.cs
Normal file
7
documentation/cs/aliases.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
launch() if ignition is on
|
||||
|
||||
volume: 10 if band aint spinal_tap
|
||||
|
||||
let_the_wild_rumpus_begin() unless answer is no
|
||||
|
||||
if car.speed < speed_limit then accelerate().
|
||||
5
documentation/cs/array_comprehensions.cs
Normal file
5
documentation/cs/array_comprehensions.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
# Eat lunch.
|
||||
lunch: food.eat() for food in ['toast', 'cheese', 'wine'].
|
||||
|
||||
# Zebra-stripe a table.
|
||||
highlight(row) for row, i in table if i % 2 is 0.
|
||||
2
documentation/cs/assignment.cs
Normal file
2
documentation/cs/assignment.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
greeting: "Hello CoffeeScript"
|
||||
difficulty: 0.5
|
||||
9
documentation/cs/conditionals.cs
Normal file
9
documentation/cs/conditionals.cs
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()
|
||||
4
documentation/cs/embedded.cs
Normal file
4
documentation/cs/embedded.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
hi: `function() {
|
||||
return [document.title, "Hello JavaScript"].join(": ");
|
||||
}`
|
||||
|
||||
9
documentation/cs/expressions.cs
Normal file
9
documentation/cs/expressions.cs
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".
|
||||
2
documentation/cs/functions.cs
Normal file
2
documentation/cs/functions.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
square: x => x * x.
|
||||
cube: x => square(x) * x.
|
||||
3
documentation/cs/intro.cs
Normal file
3
documentation/cs/intro.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
# CoffeeScript on the left, JS on the right.
|
||||
|
||||
square: x => x * x.
|
||||
6
documentation/cs/objects_and_arrays.cs
Normal file
6
documentation/cs/objects_and_arrays.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
song: ["do", "re", "mi", "fa", "so"]
|
||||
ages: {
|
||||
max: 10
|
||||
ida: 9
|
||||
tim: 11
|
||||
}
|
||||
22
documentation/cs/overview.cs
Normal file
22
documentation/cs/overview.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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).
|
||||
}
|
||||
|
||||
# Array comprehensions:
|
||||
cubed_list: math.cube(num) for num in list.
|
||||
11
documentation/cs/punctuation.cs
Normal file
11
documentation/cs/punctuation.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
# Comments start with hash marks.
|
||||
|
||||
# Periods mark the end of a block.
|
||||
left_hand: if raining then umbrella else parasol.
|
||||
|
||||
# To signal the beginning of the next expression,
|
||||
# use "then", or a newline.
|
||||
left_hand: if raining
|
||||
umbrella
|
||||
else
|
||||
parasol.
|
||||
5
documentation/cs/scope.cs
Normal file
5
documentation/cs/scope.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
num: 1
|
||||
change_numbers: =>
|
||||
num: 2
|
||||
new_num: 3.
|
||||
new_num: change_numbers()
|
||||
2
documentation/cs/slices.cs
Normal file
2
documentation/cs/slices.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
three_to_six: nums[3, 6]
|
||||
7
documentation/cs/strings.cs
Normal file
7
documentation/cs/strings.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
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/cs/super.cs
Normal file
25
documentation/cs/super.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
Animal: => .
|
||||
Animal.prototype.move: meters =>
|
||||
alert(this.name + " moved " + meters + "m.").
|
||||
|
||||
Snake: name => this.name: name.
|
||||
Snake.prototype: new Animal()
|
||||
Snake.prototype.move: =>
|
||||
alert("Slithering...")
|
||||
super(5).
|
||||
|
||||
Horse: name => this.name: name.
|
||||
Horse.prototype: new 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/cs/switch.cs
Normal file
9
documentation/cs/switch.cs
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/cs/try.cs
Normal file
7
documentation/cs/try.cs
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/cs/while.cs
Normal file
5
documentation/cs/while.cs
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;
|
||||
}
|
||||
85
documentation/css/docs.css
Normal file
85
documentation/css/docs.css
Normal file
@@ -0,0 +1,85 @@
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
425
documentation/index.html.erb
Normal file
425
documentation/index.html.erb
Normal file
@@ -0,0 +1,425 @@
|
||||
<%
|
||||
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/cs/#{file}.cs")
|
||||
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>
|
||||
|
||||
<!-- <%# code_for('intro') %>-->
|
||||
|
||||
<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>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<p>
|
||||
<a href="#overview">Mini Overview</a><br />
|
||||
<a href="#installation">Installation and Usage</a><br />
|
||||
<a href="#punctuation">Punctuation Primer</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="#expressions">Everything is an Expression</a><br />
|
||||
<a href="#aliases">Aliases</a><br />
|
||||
<a href="#while">While Loops</a><br />
|
||||
<a href="#array_comprehensions">Array Comprehensions</a><br />
|
||||
<a href="#slice">Array Slice Literals</a><br />
|
||||
<a href="#super">Calling Super from a Subclass</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="#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') %>
|
||||
|
||||
<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-script</tt> command, which can
|
||||
be used to compile CoffeeScript <tt>.cs</tt> files into JavaScript, as
|
||||
well as debug them. By default, <tt>coffee-script</tt> writes out the
|
||||
JavaScript as <tt>.js</tt> files in the same directory, but output
|
||||
can be customized with the following options:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="25%"><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-script -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>--install-bundle</code></td>
|
||||
<td>
|
||||
Install the TextMate bundle for CoffeeScript syntax highlighting.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<b>Examples:</b>
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
coffee-script path/to/script.cs
|
||||
coffee-script --watch --lint experimental.cs
|
||||
coffee-script --print app/scripts/*.cs > 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="punctuation">
|
||||
<b class="header">Punctuation Primer</b>
|
||||
You don't need to use semicolons <tt>;</tt> to terminate expressions, ending
|
||||
the line will do just as well. All other whitespace is
|
||||
not significant. Instead of using curly braces <tt>{ }</tt>
|
||||
to delimit a block of code, use a period <tt>.</tt> to mark the end of a
|
||||
block, for
|
||||
<a href="#functions">functions</a>,
|
||||
<a href="#conditionals">if-statements</a>,
|
||||
<a href="#switch">switch</a>, and <a href="#try">try/catch</a>.
|
||||
</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>
|
||||
</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 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.
|
||||
</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 variables are declared with <tt>var</tt> the first time
|
||||
they appear. The second reference of <b>num</b>, within the function,
|
||||
is not redeclared because <b>num</b> is still in scope. As opposed
|
||||
to the second occurrence of <b>new_num</b>, in the last line.
|
||||
</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.
|
||||
</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 parenthesis and
|
||||
curly brackets. As with functions and other block expressions, conditionals
|
||||
are closed with periods. No period is necessary when using the single-line
|
||||
postfix form, with the <tt>if</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 available: <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="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>
|
||||
The same mechanism is used to push down assignment through <b>switch</b>
|
||||
statements, and <b>if-elses</b> (although the ternary operator is preferred).
|
||||
</p>
|
||||
|
||||
<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>aint</tt> into <tt>!==</tt>.
|
||||
</p>
|
||||
<p>
|
||||
You can use <tt>not</tt> as an alias for <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="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="array_comprehensions">
|
||||
<b class="header">Array Comprehensions</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 id="slice">
|
||||
<b class="header">Array Slice Literals</b>
|
||||
CoffeeScript includes syntax for extracting slices of arrays.
|
||||
The first argument is the index of the first element in the slice, and
|
||||
the second is the index of the last one.
|
||||
</p>
|
||||
<%= code_for('slices', 'three_to_six') %>
|
||||
|
||||
<p id="super">
|
||||
<b class="header">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 one small exception:
|
||||
it's very awkward to call <b>super</b>, the prototype object's
|
||||
implementation of the current function. CoffeeScript converts
|
||||
<tt>super()</tt> calls into calls against the immediate ancestor's
|
||||
method of the same name.
|
||||
</p>
|
||||
<%= code_for('super', true) %>
|
||||
|
||||
<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="try">
|
||||
<b class="header">Multiline Strings</b>
|
||||
Multiline strings are allowed in CoffeeScript.
|
||||
</p>
|
||||
<%= code_for('strings', 'moby_dick') %>
|
||||
|
||||
<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 JavaScript version of the compiler, perhaps using Alessandro Warth's
|
||||
<a href="http://tinlizzie.org/ometa/">OMeta</a>.
|
||||
</li>
|
||||
<li>
|
||||
Ideas for alternate syntax to end blocks of expressions — the periods
|
||||
can look a little weird with deeply nested structure.
|
||||
</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: 25px;">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(){
|
||||
if (ignition === true) {
|
||||
launch();
|
||||
}
|
||||
var volume;
|
||||
if (band !== spinal_tap) {
|
||||
volume = 10;
|
||||
}
|
||||
if (!(answer === false)) {
|
||||
let_the_wild_rumpus_begin();
|
||||
}
|
||||
car.speed < speed_limit ? accelerate() : null;
|
||||
})();
|
||||
19
documentation/js/array_comprehensions.js
Normal file
19
documentation/js/array_comprehensions.js
Normal file
@@ -0,0 +1,19 @@
|
||||
(function(){
|
||||
|
||||
// Eat lunch.
|
||||
var lunch;
|
||||
var a = ['toast', 'cheese', 'wine'];
|
||||
var d = [];
|
||||
for (var b=0, c=a.length; b<c; b++) {
|
||||
var food = a[b];
|
||||
d[b] = food.eat();
|
||||
}
|
||||
lunch = d;
|
||||
// Zebra-stripe a table.
|
||||
var e = table;
|
||||
for (var f=0, g=e.length; f<g; f++) {
|
||||
var row = e[f];
|
||||
var i = f;
|
||||
i % 2 === 0 ? highlight(row) : null;
|
||||
}
|
||||
})();
|
||||
4
documentation/js/assignment.js
Normal file
4
documentation/js/assignment.js
Normal file
@@ -0,0 +1,4 @@
|
||||
(function(){
|
||||
var greeting = "Hello CoffeeScript";
|
||||
var difficulty = 0.5;
|
||||
})();
|
||||
12
documentation/js/conditionals.js
Normal file
12
documentation/js/conditionals.js
Normal file
@@ -0,0 +1,12 @@
|
||||
(function(){
|
||||
var mood;
|
||||
if (singing) {
|
||||
mood = greatly_improved;
|
||||
}
|
||||
if (happy && knows_it) {
|
||||
claps_hands();
|
||||
cha_cha_cha();
|
||||
}
|
||||
var date = friday ? sue : jill;
|
||||
expensive = expensive || do_the_math();
|
||||
})();
|
||||
5
documentation/js/embedded.js
Normal file
5
documentation/js/embedded.js
Normal file
@@ -0,0 +1,5 @@
|
||||
(function(){
|
||||
var hi = function() {
|
||||
return [document.title, "Hello JavaScript"].join(": ");
|
||||
};
|
||||
})();
|
||||
12
documentation/js/expressions.js
Normal file
12
documentation/js/expressions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
(function(){
|
||||
var grade = function(student) {
|
||||
if (student.excellent_work) {
|
||||
return "A+";
|
||||
} else if (student.okay_stuff) {
|
||||
return student.tried_hard ? "B" : "B-";
|
||||
} else {
|
||||
return "C";
|
||||
}
|
||||
};
|
||||
var eldest = 24 > 21 ? "Liz" : "Ike";
|
||||
})();
|
||||
8
documentation/js/functions.js
Normal file
8
documentation/js/functions.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function(){
|
||||
var square = function(x) {
|
||||
return x * x;
|
||||
};
|
||||
var cube = function(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;
|
||||
};
|
||||
})();
|
||||
8
documentation/js/objects_and_arrays.js
Normal file
8
documentation/js/objects_and_arrays.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function(){
|
||||
var song = ["do", "re", "mi", "fa", "so"];
|
||||
var ages = {
|
||||
max: 10,
|
||||
ida: 9,
|
||||
tim: 11
|
||||
};
|
||||
})();
|
||||
33
documentation/js/overview.js
Normal file
33
documentation/js/overview.js
Normal file
@@ -0,0 +1,33 @@
|
||||
(function(){
|
||||
|
||||
// Assignment:
|
||||
var number = 42;
|
||||
var opposite_day = true;
|
||||
// Conditions:
|
||||
if (opposite_day) {
|
||||
number = -42;
|
||||
}
|
||||
// Functions:
|
||||
var square = function(x) {
|
||||
return x * x;
|
||||
};
|
||||
// Arrays:
|
||||
var list = [1, 2, 3, 4, 5];
|
||||
// Objects:
|
||||
var math = {
|
||||
root: Math.sqrt,
|
||||
square: square,
|
||||
cube: function(x) {
|
||||
return x * square(x);
|
||||
}
|
||||
};
|
||||
// Array comprehensions:
|
||||
var cubed_list;
|
||||
var a = list;
|
||||
var d = [];
|
||||
for (var b=0, c=a.length; b<c; b++) {
|
||||
var num = a[b];
|
||||
d[b] = math.cube(num);
|
||||
}
|
||||
cubed_list = d;
|
||||
})();
|
||||
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/scope.js
Normal file
9
documentation/js/scope.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
var num = 1;
|
||||
var change_numbers = function() {
|
||||
num = 2;
|
||||
var new_num = 3;
|
||||
return new_num;
|
||||
};
|
||||
var new_num = change_numbers();
|
||||
})();
|
||||
4
documentation/js/slices.js
Normal file
4
documentation/js/slices.js
Normal file
@@ -0,0 +1,4 @@
|
||||
(function(){
|
||||
var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
var three_to_six = nums.slice(3, 6 + 1);
|
||||
})();
|
||||
8
documentation/js/strings.js
Normal file
8
documentation/js/strings.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function(){
|
||||
var 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...";
|
||||
})();
|
||||
29
documentation/js/super.js
Normal file
29
documentation/js/super.js
Normal file
@@ -0,0 +1,29 @@
|
||||
(function(){
|
||||
var Animal = function() {
|
||||
};
|
||||
Animal.prototype.move = function(meters) {
|
||||
return alert(this.name + " moved " + meters + "m.");
|
||||
};
|
||||
var Snake = function(name) {
|
||||
this.name = name;
|
||||
return this.name;
|
||||
};
|
||||
Snake.prototype = new Animal();
|
||||
Snake.prototype.move = function() {
|
||||
alert("Slithering...");
|
||||
return this.constructor.prototype.move.call(this, 5);
|
||||
};
|
||||
var Horse = function(name) {
|
||||
this.name = name;
|
||||
return this.name;
|
||||
};
|
||||
Horse.prototype = new Animal();
|
||||
Horse.prototype.move = function() {
|
||||
alert("Galloping...");
|
||||
return this.constructor.prototype.move.call(this, 45);
|
||||
};
|
||||
var sam = new Snake("Sammy the Python");
|
||||
var 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();
|
||||
}
|
||||
})();
|
||||
@@ -1,5 +1,3 @@
|
||||
# TODO: Add range indexing: array[5..7] => array.slice(5, 7)
|
||||
|
||||
# Functions:
|
||||
square: x => x * x.
|
||||
|
||||
@@ -18,17 +16,14 @@ run_loop: =>
|
||||
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:
|
||||
@@ -38,10 +33,17 @@ 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().
|
||||
|
||||
@@ -59,12 +61,12 @@ race: =>
|
||||
if tired then return sleep().
|
||||
race().
|
||||
|
||||
# Conditional operators:
|
||||
good ||= evil
|
||||
wine &&= cheese
|
||||
# Conditional assignment:
|
||||
good ||: evil
|
||||
wine &&: cheese
|
||||
|
||||
# Nested property access and calls.
|
||||
((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position
|
||||
((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position['top'].offset('x')
|
||||
|
||||
a: b: c: 5
|
||||
|
||||
@@ -85,13 +87,17 @@ finally
|
||||
|
||||
try all_hell_breaks_loose() catch error print(error) finally clean_up().
|
||||
|
||||
# While loops.
|
||||
# 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
|
||||
|
||||
@@ -107,20 +113,61 @@ 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().
|
||||
# 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]
|
||||
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.prototype: Animal
|
||||
Snake.prototype.move: =>
|
||||
alert('Slithering...')
|
||||
super(5).
|
||||
|
||||
Horse: name => this.name: name.
|
||||
Horse.prototype: 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
|
||||
@@ -102,4 +102,52 @@ Creature : {
|
||||
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.cs
Normal file
20
examples/syntax_errors.cs
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]
|
||||
# }
|
||||
597
examples/underscore.cs
Normal file
597
examples/underscore.cs
Normal file
@@ -0,0 +1,597 @@
|
||||
# Underscore.js
|
||||
# (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.
|
||||
|
||||
# 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.1'
|
||||
|
||||
# ------------------------ 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
|
||||
return iterator.call(context, item, i, obj) for item, i in obj. if _.isArray(obj) or _.isArguments(obj)
|
||||
iterator.call(context, obj[key], key, obj) for key in _.keys(obj).
|
||||
catch e
|
||||
throw e if e aint 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: []
|
||||
mapper: value, index, list => results.push(iterator.call(context, value, index, list)).
|
||||
_.each(obj, mapper)
|
||||
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))
|
||||
reducer: value, index, list => memo: iterator.call(context, memo, value, index, list).
|
||||
_.each(obj, reducer)
|
||||
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))
|
||||
reversed: _.clone(_.toArray(obj)).reverse()
|
||||
reverser: value, index => memo: iterator.call(context, memo, value, index, obj).
|
||||
_.each(reversed, reverser)
|
||||
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 =>
|
||||
iterator.call(context, value, index, list) and results.push(value).))
|
||||
results.
|
||||
|
||||
#
|
||||
# # Return all the elements for which a truth test fails.
|
||||
# _.reject = function(obj, iterator, context) {
|
||||
# var results = [];
|
||||
# _.each(obj, function(value, index, list) {
|
||||
# !iterator.call(context, value, index, list) && results.push(value);
|
||||
# });
|
||||
# return results;
|
||||
# };
|
||||
#
|
||||
# # Determine whether all of the elements match a truth test. Delegate to
|
||||
# # JavaScript 1.6's every(), if it is present.
|
||||
# _.all = function(obj, iterator, context) {
|
||||
# iterator = iterator || _.identity;
|
||||
# if (obj && _.isFunction(obj.every)) return obj.every(iterator, context);
|
||||
# var result = true;
|
||||
# _.each(obj, function(value, index, list) {
|
||||
# if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop();
|
||||
# });
|
||||
# return result;
|
||||
# };
|
||||
#
|
||||
# # Determine if at least one element in the object matches a truth test. Use
|
||||
# # JavaScript 1.6's some(), if it exists.
|
||||
# _.any = function(obj, iterator, context) {
|
||||
# iterator = iterator || _.identity;
|
||||
# if (obj && _.isFunction(obj.some)) return obj.some(iterator, context);
|
||||
# var result = false;
|
||||
# _.each(obj, function(value, index, list) {
|
||||
# if (result = iterator.call(context, value, index, list)) _.breakLoop();
|
||||
# });
|
||||
# return result;
|
||||
# };
|
||||
#
|
||||
# # Determine if a given value is included in the array or object,
|
||||
# # based on '==='.
|
||||
# _.include = function(obj, target) {
|
||||
# if (_.isArray(obj)) return _.indexOf(obj, target) != -1;
|
||||
# var found = false;
|
||||
# _.each(obj, function(value) {
|
||||
# if (found = value === target) _.breakLoop();
|
||||
# });
|
||||
# return found;
|
||||
# };
|
||||
#
|
||||
# # Invoke a method with arguments on every item in a collection.
|
||||
# _.invoke = function(obj, method) {
|
||||
# var args = _.rest(arguments, 2);
|
||||
# return _.map(obj, function(value) {
|
||||
# return (method ? value[method] : value).apply(value, args);
|
||||
# });
|
||||
# };
|
||||
#
|
||||
# # Convenience version of a common use case of map: fetching a property.
|
||||
# _.pluck = function(obj, key) {
|
||||
# return _.map(obj, function(value){ return value[key]; });
|
||||
# };
|
||||
#
|
||||
# # Return the maximum item or (item-based computation).
|
||||
# _.max = function(obj, iterator, context) {
|
||||
# if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
|
||||
# var result = {computed : -Infinity};
|
||||
# _.each(obj, function(value, index, list) {
|
||||
# var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
# computed >= result.computed && (result = {value : value, computed : computed});
|
||||
# });
|
||||
# return result.value;
|
||||
# };
|
||||
#
|
||||
# # Return the minimum element (or element-based computation).
|
||||
# _.min = function(obj, iterator, context) {
|
||||
# if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
|
||||
# var result = {computed : Infinity};
|
||||
# _.each(obj, function(value, index, list) {
|
||||
# var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
# computed < result.computed && (result = {value : value, computed : computed});
|
||||
# });
|
||||
# return result.value;
|
||||
# };
|
||||
#
|
||||
# # Sort the object's values by a criteria produced by an iterator.
|
||||
# _.sortBy = function(obj, iterator, context) {
|
||||
# return _.pluck(_.map(obj, function(value, index, list) {
|
||||
# return {
|
||||
# value : value,
|
||||
# criteria : iterator.call(context, value, index, list)
|
||||
# };
|
||||
# }).sort(function(left, right) {
|
||||
# var a = left.criteria, b = right.criteria;
|
||||
# return a < b ? -1 : a > b ? 1 : 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 = function(array, obj, iterator) {
|
||||
# iterator = iterator || _.identity;
|
||||
# var low = 0, high = array.length;
|
||||
# while (low < high) {
|
||||
# var mid = (low + high) >> 1;
|
||||
# iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
|
||||
# }
|
||||
# return low;
|
||||
# };
|
||||
#
|
||||
# # Convert anything iterable into a real, live array.
|
||||
# _.toArray = function(iterable) {
|
||||
# if (!iterable) return [];
|
||||
# if (iterable.toArray) return iterable.toArray();
|
||||
# if (_.isArray(iterable)) return iterable;
|
||||
# if (_.isArguments(iterable)) return slice.call(iterable);
|
||||
# return _.map(iterable, function(val){ return val; });
|
||||
# };
|
||||
#
|
||||
# # Return the number of elements in an object.
|
||||
# _.size = function(obj) {
|
||||
# return _.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 = function(array, n, guard) {
|
||||
# return n && !guard ? slice.call(array, 0, n) : 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 = function(array, index, guard) {
|
||||
# return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
|
||||
# };
|
||||
#
|
||||
# # Get the last element of an array.
|
||||
# _.last = function(array) {
|
||||
# return array[array.length - 1];
|
||||
# };
|
||||
#
|
||||
# # Trim out all falsy values from an array.
|
||||
# _.compact = function(array) {
|
||||
# return _.select(array, function(value){ return !!value; });
|
||||
# };
|
||||
#
|
||||
# # Return a completely flattened version of an array.
|
||||
# _.flatten = function(array) {
|
||||
# return _.reduce(array, [], function(memo, value) {
|
||||
# if (_.isArray(value)) return memo.concat(_.flatten(value));
|
||||
# memo.push(value);
|
||||
# return memo;
|
||||
# });
|
||||
# };
|
||||
#
|
||||
# # Return a version of the array that does not contain the specified value(s).
|
||||
# _.without = function(array) {
|
||||
# var values = _.rest(arguments);
|
||||
# return _.select(array, function(value){ return !_.include(values, value); });
|
||||
# };
|
||||
#
|
||||
# # 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 = function(array, isSorted) {
|
||||
# return _.reduce(array, [], function(memo, el, i) {
|
||||
# if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
|
||||
# return memo;
|
||||
# });
|
||||
# };
|
||||
#
|
||||
# # Produce an array that contains every item shared between all the
|
||||
# # passed-in arrays.
|
||||
# _.intersect = function(array) {
|
||||
# var rest = _.rest(arguments);
|
||||
# return _.select(_.uniq(array), function(item) {
|
||||
# return _.all(rest, function(other) {
|
||||
# return _.indexOf(other, item) >= 0;
|
||||
# });
|
||||
# });
|
||||
# };
|
||||
#
|
||||
# # Zip together multiple lists into a single array -- elements that share
|
||||
# # an index go together.
|
||||
# _.zip = function() {
|
||||
# var args = _.toArray(arguments);
|
||||
# var length = _.max(_.pluck(args, 'length'));
|
||||
# var results = new Array(length);
|
||||
# for (var i=0; i<length; i++) results[i] = _.pluck(args, String(i));
|
||||
# return 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 = function(array, item) {
|
||||
# if (array.indexOf) return array.indexOf(item);
|
||||
# for (var i=0, l=array.length; i<l; i++) if (array[i] === item) return i;
|
||||
# return -1;
|
||||
# };
|
||||
#
|
||||
# # Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
|
||||
# # if possible.
|
||||
# _.lastIndexOf = function(array, item) {
|
||||
# if (array.lastIndexOf) return array.lastIndexOf(item);
|
||||
# var i = array.length;
|
||||
# while (i--) if (array[i] === item) return i;
|
||||
# return -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 = function(start, stop, step) {
|
||||
# var a = _.toArray(arguments);
|
||||
# var solo = a.length <= 1;
|
||||
# var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1;
|
||||
# var len = Math.ceil((stop - start) / step);
|
||||
# if (len <= 0) return [];
|
||||
# var range = new Array(len);
|
||||
# for (var i = start, idx = 0; true; i += step) {
|
||||
# if ((step > 0 ? i - stop : stop - i) >= 0) return range;
|
||||
# range[idx++] = i;
|
||||
# }
|
||||
# };
|
||||
#
|
||||
# /* ----------------------- Function Functions: -----------------------------*/
|
||||
#
|
||||
# # Create a function bound to a given object (assigning 'this', and arguments,
|
||||
# # optionally). Binding with arguments is also known as 'curry'.
|
||||
# _.bind = function(func, obj) {
|
||||
# var args = _.rest(arguments, 2);
|
||||
# return function() {
|
||||
# return func.apply(obj || 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 = function(obj) {
|
||||
# var funcs = _.rest(arguments);
|
||||
# if (funcs.length == 0) funcs = _.functions(obj);
|
||||
# _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
|
||||
# return obj;
|
||||
# };
|
||||
#
|
||||
# # Delays a function for the given number of milliseconds, and then calls
|
||||
# # it with the arguments supplied.
|
||||
# _.delay = function(func, wait) {
|
||||
# var args = _.rest(arguments, 2);
|
||||
# return setTimeout(function(){ return func.apply(func, args); }, wait);
|
||||
# };
|
||||
#
|
||||
# # Defers a function, scheduling it to run after the current call stack has
|
||||
# # cleared.
|
||||
# _.defer = function(func) {
|
||||
# return _.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 = function(func, wrapper) {
|
||||
# return function() {
|
||||
# var args = [func].concat(_.toArray(arguments));
|
||||
# return wrapper.apply(wrapper, args);
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# # Returns a function that is the composition of a list of functions, each
|
||||
# # consuming the return value of the function that follows.
|
||||
# _.compose = function() {
|
||||
# var funcs = _.toArray(arguments);
|
||||
# return function() {
|
||||
# var args = _.toArray(arguments);
|
||||
# for (var i=funcs.length-1; i >= 0; i--) {
|
||||
# args = [funcs[i].apply(this, args)];
|
||||
# }
|
||||
# return args[0];
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# /* ------------------------- Object Functions: ---------------------------- */
|
||||
#
|
||||
# # Retrieve the names of an object's properties.
|
||||
# _.keys = function(obj) {
|
||||
# if(_.isArray(obj)) return _.range(0, obj.length);
|
||||
# var keys = [];
|
||||
# for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
|
||||
# return keys;
|
||||
# };
|
||||
#
|
||||
# # Retrieve the values of an object's properties.
|
||||
# _.values = function(obj) {
|
||||
# return _.map(obj, _.identity);
|
||||
# };
|
||||
#
|
||||
# # Return a sorted list of the function names available in Underscore.
|
||||
# _.functions = function(obj) {
|
||||
# return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
|
||||
# };
|
||||
#
|
||||
# # Extend a given object with all of the properties in a source object.
|
||||
# _.extend = function(destination, source) {
|
||||
# for (var property in source) destination[property] = source[property];
|
||||
# return destination;
|
||||
# };
|
||||
#
|
||||
# # Create a (shallow-cloned) duplicate of an object.
|
||||
# _.clone = function(obj) {
|
||||
# if (_.isArray(obj)) return obj.slice(0);
|
||||
# return _.extend({}, obj);
|
||||
# };
|
||||
#
|
||||
# # Perform a deep comparison to check if two objects are equal.
|
||||
# _.isEqual = function(a, b) {
|
||||
# # Check object identity.
|
||||
# if (a === b) return true;
|
||||
# # Different types?
|
||||
# var atype = typeof(a), btype = typeof(b);
|
||||
# if (atype != btype) return false;
|
||||
# # Basic equality test (watch out for coercions).
|
||||
# if (a == b) return true;
|
||||
# # One is falsy and the other truthy.
|
||||
# if ((!a && b) || (a && !b)) return false;
|
||||
# # One of them implements an isEqual()?
|
||||
# if (a.isEqual) return a.isEqual(b);
|
||||
# # Check dates' integer values.
|
||||
# if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
|
||||
# # Both are NaN?
|
||||
# if (_.isNaN(a) && _.isNaN(b)) return true;
|
||||
# # Compare regular expressions.
|
||||
# if (_.isRegExp(a) && _.isRegExp(b))
|
||||
# return a.source === b.source &&
|
||||
# a.global === b.global &&
|
||||
# a.ignoreCase === b.ignoreCase &&
|
||||
# a.multiline === b.multiline;
|
||||
# # If a is not an object by this point, we can't handle it.
|
||||
# if (atype !== 'object') return false;
|
||||
# # Check for different array lengths before comparing contents.
|
||||
# if (a.length && (a.length !== b.length)) return false;
|
||||
# # Nothing else worked, deep compare the contents.
|
||||
# var aKeys = _.keys(a), bKeys = _.keys(b);
|
||||
# # Different object sizes?
|
||||
# if (aKeys.length != bKeys.length) return false;
|
||||
# # 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 = function(obj) {
|
||||
# return _.keys(obj).length == 0;
|
||||
# };
|
||||
#
|
||||
# # Is a given value a DOM element?
|
||||
# _.isElement = function(obj) {
|
||||
# return !!(obj && obj.nodeType == 1);
|
||||
# };
|
||||
#
|
||||
# # Is a given variable an arguments object?
|
||||
# _.isArguments = function(obj) {
|
||||
# return obj && _.isNumber(obj.length) && !_.isArray(obj) && !propertyIsEnumerable.call(obj, 'length');
|
||||
# };
|
||||
#
|
||||
# # 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 = function(obj) {
|
||||
# return _.isNumber(obj) && isNaN(obj);
|
||||
# };
|
||||
#
|
||||
# # Is a given value equal to null?
|
||||
# _.isNull = function(obj) {
|
||||
# return obj === null;
|
||||
# };
|
||||
#
|
||||
# # Is a given variable undefined?
|
||||
# _.isUndefined = function(obj) {
|
||||
# return typeof obj == 'undefined';
|
||||
# };
|
||||
#
|
||||
# # 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 = function(obj, interceptor) {
|
||||
# interceptor(obj);
|
||||
# return obj;
|
||||
# }
|
||||
#
|
||||
# # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString
|
||||
# # functions based on their toString identifiers.
|
||||
# var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String'];
|
||||
# for (var i=0, l=types.length; i<l; i++) {
|
||||
# (function() {
|
||||
# var identifier = '[object ' + types[i] + ']';
|
||||
# _['is' + types[i]] = function(obj) { return toString.call(obj) == identifier; };
|
||||
# })();
|
||||
# }
|
||||
#
|
||||
# /* -------------------------- Utility Functions: -------------------------- */
|
||||
#
|
||||
# # Run Underscore.js in noConflict mode, returning the '_' variable to its
|
||||
# # previous owner. Returns a reference to the Underscore object.
|
||||
# _.noConflict = function() {
|
||||
# root._ = previousUnderscore;
|
||||
# return this;
|
||||
# };
|
||||
#
|
||||
# # Keep the identity function around for default iterators.
|
||||
# _.identity = function(value) {
|
||||
# return value;
|
||||
# };
|
||||
#
|
||||
# # Break out of the middle of an iteration.
|
||||
# _.breakLoop = function() {
|
||||
# throw breaker;
|
||||
# };
|
||||
#
|
||||
# # Generate a unique integer id (unique within the entire client session).
|
||||
# # Useful for temporary DOM ids.
|
||||
# var idCounter = 0;
|
||||
# _.uniqueId = function(prefix) {
|
||||
# var id = idCounter++;
|
||||
# return prefix ? prefix + id : id;
|
||||
# };
|
||||
#
|
||||
# # JavaScript templating a-la ERB, pilfered from John Resig's
|
||||
# # "Secrets of the JavaScript Ninja", page 83.
|
||||
# _.template = function(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('');");
|
||||
# return data ? fn(data) : 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.
|
||||
# var result = function(obj, chain) {
|
||||
# return chain ? _(obj).chain() : obj;
|
||||
# };
|
||||
#
|
||||
# # Add all of the Underscore functions to the wrapper object.
|
||||
# _.each(_.functions(_), function(name) {
|
||||
# var method = _[name];
|
||||
# wrapper.prototype[name] = function() {
|
||||
# unshift.call(arguments, this._wrapped);
|
||||
# return result(method.apply(_, arguments), this._chain);
|
||||
# };
|
||||
# });
|
||||
#
|
||||
# # Add all mutator Array functions to the wrapper.
|
||||
# _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
||||
# var method = Array.prototype[name];
|
||||
# wrapper.prototype[name] = function() {
|
||||
# method.apply(this._wrapped, arguments);
|
||||
# return result(this._wrapped, this._chain);
|
||||
# };
|
||||
# });
|
||||
#
|
||||
# # Add all accessor Array functions to the wrapper.
|
||||
# _.each(['concat', 'join', 'slice'], function(name) {
|
||||
# var method = Array.prototype[name];
|
||||
# wrapper.prototype[name] = function() {
|
||||
# return result(method.apply(this._wrapped, arguments), this._chain);
|
||||
# };
|
||||
# });
|
||||
#
|
||||
# # Start chaining a wrapped Underscore object.
|
||||
# wrapper.prototype.chain = function() {
|
||||
# this._chain = true;
|
||||
# return this;
|
||||
# };
|
||||
#
|
||||
# # Extracts the result from a wrapped and chained object.
|
||||
# wrapper.prototype.value = function() {
|
||||
# return this._wrapped;
|
||||
# };
|
||||
#
|
||||
# ()
|
||||
804
index.html
Normal file
804
index.html
Normal file
@@ -0,0 +1,804 @@
|
||||
|
||||
|
||||
|
||||
<!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>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<p>
|
||||
<a href="#overview">Mini Overview</a><br />
|
||||
<a href="#installation">Installation and Usage</a><br />
|
||||
<a href="#punctuation">Punctuation Primer</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="#expressions">Everything is an Expression</a><br />
|
||||
<a href="#aliases">Aliases</a><br />
|
||||
<a href="#while">While Loops</a><br />
|
||||
<a href="#array_comprehensions">Array Comprehensions</a><br />
|
||||
<a href="#slice">Array Slice Literals</a><br />
|
||||
<a href="#super">Calling Super from a Subclass</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="#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>
|
||||
|
||||
<div class='code'><pre class="idle"><span class="Comment"><span class="Comment">#</span> Assignment:</span>
|
||||
number<span class="Keyword">:</span> <span class="Number">42</span>
|
||||
opposite_day<span class="Keyword">:</span> <span class="BuiltInConstant">true</span>
|
||||
|
||||
<span class="Comment"><span class="Comment">#</span> Conditions:</span>
|
||||
number<span class="Keyword">:</span> <span class="Keyword">-</span><span class="Number">42</span> <span class="Keyword">if</span> opposite_day
|
||||
|
||||
<span class="Comment"><span class="Comment">#</span> Functions:</span>
|
||||
<span class="FunctionName">square</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=></span> x <span class="Keyword">*</span> x.
|
||||
|
||||
<span class="Comment"><span class="Comment">#</span> Arrays:</span>
|
||||
list<span class="Keyword">:</span> [<span class="Number">1</span>, <span class="Number">2</span>, <span class="Number">3</span>, <span class="Number">4</span>, <span class="Number">5</span>]
|
||||
|
||||
<span class="Comment"><span class="Comment">#</span> Objects:</span>
|
||||
math<span class="Keyword">:</span> {
|
||||
root<span class="Keyword">:</span> Math.sqrt
|
||||
square<span class="Keyword">:</span> square
|
||||
<span class="FunctionName">cube</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=></span> x <span class="Keyword">*</span> square(x).
|
||||
}
|
||||
|
||||
<span class="Comment"><span class="Comment">#</span> Array comprehensions:</span>
|
||||
cubed_list<span class="Keyword">:</span> math.cube(num) <span class="Keyword">for</span> num <span class="Keyword">in</span> list.
|
||||
</pre><pre class="idle">
|
||||
<span class="Comment"><span class="Comment">//</span> Assignment:</span>
|
||||
<span class="Storage">var</span> number <span class="Keyword">=</span> <span class="Number">42</span>;
|
||||
<span class="Storage">var</span> opposite_day <span class="Keyword">=</span> <span class="BuiltInConstant">true</span>;
|
||||
<span class="Comment"><span class="Comment">//</span> Conditions:</span>
|
||||
<span class="Keyword">if</span> (opposite_day) {
|
||||
number <span class="Keyword">=</span> <span class="Keyword">-</span><span class="Number">42</span>;
|
||||
}
|
||||
<span class="Comment"><span class="Comment">//</span> Functions:</span>
|
||||
<span class="Storage">var</span> <span class="FunctionName">square</span> = <span class="Storage">function</span>(<span class="FunctionArgument">x</span>) {
|
||||
<span class="Keyword">return</span> x <span class="Keyword">*</span> x;
|
||||
};
|
||||
<span class="Comment"><span class="Comment">//</span> Arrays:</span>
|
||||
<span class="Storage">var</span> list <span class="Keyword">=</span> [<span class="Number">1</span>, <span class="Number">2</span>, <span class="Number">3</span>, <span class="Number">4</span>, <span class="Number">5</span>];
|
||||
<span class="Comment"><span class="Comment">//</span> Objects:</span>
|
||||
<span class="Storage">var</span> math <span class="Keyword">=</span> {
|
||||
root: <span class="LibraryClassType">Math</span>.sqrt,
|
||||
square: square,
|
||||
<span class="FunctionName">cube</span>: <span class="Storage">function</span>(<span class="FunctionArgument">x</span>) {
|
||||
<span class="Keyword">return</span> x <span class="Keyword">*</span> square(x);
|
||||
}
|
||||
};
|
||||
<span class="Comment"><span class="Comment">//</span> Array comprehensions:</span>
|
||||
<span class="Storage">var</span> cubed_list;
|
||||
<span class="Storage">var</span> a <span class="Keyword">=</span> list;
|
||||
<span class="Storage">var</span> d <span class="Keyword">=</span> [];
|
||||
<span class="Keyword">for</span> (<span class="Storage">var</span> b<span class="Keyword">=</span><span class="Number">0</span>, c<span class="Keyword">=</span>a.<span class="LibraryConstant">length</span>; b<span class="Keyword"><</span>c; b<span class="Keyword">++</span>) {
|
||||
<span class="Storage">var</span> num <span class="Keyword">=</span> a[b];
|
||||
d[b] <span class="Keyword">=</span> math.cube(num);
|
||||
}
|
||||
cubed_list <span class="Keyword">=</span> d;
|
||||
</pre><button onclick='javascript:
|
||||
// Assignment:
|
||||
var number = 42;
|
||||
var opposite_day = true;
|
||||
// Conditions:
|
||||
if (opposite_day) {
|
||||
number = -42;
|
||||
}
|
||||
// Functions:
|
||||
var square = function(x) {
|
||||
return x * x;
|
||||
};
|
||||
// Arrays:
|
||||
var list = [1, 2, 3, 4, 5];
|
||||
// Objects:
|
||||
var math = {
|
||||
root: Math.sqrt,
|
||||
square: square,
|
||||
cube: function(x) {
|
||||
return x * square(x);
|
||||
}
|
||||
};
|
||||
// Array comprehensions:
|
||||
var cubed_list;
|
||||
var a = list;
|
||||
var d = [];
|
||||
for (var b=0, c=a.length; b<c; b++) {
|
||||
var num = a[b];
|
||||
d[b] = math.cube(num);
|
||||
}
|
||||
cubed_list = d;
|
||||
;alert(cubed_list);'>run: cubed_list</button><br class='clear' /></div>
|
||||
|
||||
<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-script</tt> command, which can
|
||||
be used to compile CoffeeScript <tt>.cs</tt> files into JavaScript, as
|
||||
well as debug them. By default, <tt>coffee-script</tt> writes out the
|
||||
JavaScript as <tt>.js</tt> files in the same directory, but output
|
||||
can be customized with the following options:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="25%"><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-script -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>--install-bundle</code></td>
|
||||
<td>
|
||||
Install the TextMate bundle for CoffeeScript syntax highlighting.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<b>Examples:</b>
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
coffee-script path/to/script.cs
|
||||
coffee-script --watch --lint experimental.cs
|
||||
coffee-script --print app/scripts/*.cs > 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="punctuation">
|
||||
<b class="header">Punctuation Primer</b>
|
||||
You don't need to use semicolons <tt>;</tt> to terminate expressions, ending
|
||||
the line will do just as well. All other whitespace is
|
||||
not significant. Instead of using curly braces <tt>{ }</tt>
|
||||
to delimit a block of code, use a period <tt>.</tt> to mark the end of a
|
||||
block, for
|
||||
<a href="#functions">functions</a>,
|
||||
<a href="#conditionals">if-statements</a>,
|
||||
<a href="#switch">switch</a>, and <a href="#try">try/catch</a>.
|
||||
</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>
|
||||
</p>
|
||||
<div class='code'><pre class="idle"><span class="FunctionName">square</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=></span> x <span class="Keyword">*</span> x.
|
||||
<span class="FunctionName">cube</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=></span> square(x) <span class="Keyword">*</span> x.
|
||||
</pre><pre class="idle"><span class="Storage">var</span> <span class="FunctionName">square</span> = <span class="Storage">function</span>(<span class="FunctionArgument">x</span>) {
|
||||
<span class="Keyword">return</span> x <span class="Keyword">*</span> x;
|
||||
};
|
||||
<span class="Storage">var</span> <span class="FunctionName">cube</span> = <span class="Storage">function</span>(<span class="FunctionArgument">x</span>) {
|
||||
<span class="Keyword">return</span> square(x) <span class="Keyword">*</span> x;
|
||||
};
|
||||
</pre><button onclick='javascript: var square = function(x) {
|
||||
return x * x;
|
||||
};
|
||||
var cube = function(x) {
|
||||
return square(x) * x;
|
||||
};
|
||||
;alert(cube(5));'>run: cube(5)</button><br class='clear' /></div>
|
||||
|
||||
<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>
|
||||
<div class='code'><pre class="idle">greeting<span class="Keyword">:</span> <span class="String"><span class="String">"</span>Hello CoffeeScript<span class="String">"</span></span>
|
||||
difficulty<span class="Keyword">:</span> <span class="Number">0.5</span>
|
||||
</pre><pre class="idle"><span class="Storage">var</span> greeting <span class="Keyword">=</span> <span class="String"><span class="String">"</span>Hello CoffeeScript<span class="String">"</span></span>;
|
||||
<span class="Storage">var</span> difficulty <span class="Keyword">=</span> <span class="Number">0.5</span>;
|
||||
</pre><button onclick='javascript: var greeting = "Hello CoffeeScript";
|
||||
var difficulty = 0.5;
|
||||
;alert(greeting);'>run: greeting</button><br class='clear' /></div>
|
||||
|
||||
<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.
|
||||
</p>
|
||||
<div class='code'><pre class="idle">song<span class="Keyword">:</span> [<span class="String"><span class="String">"</span>do<span class="String">"</span></span>, <span class="String"><span class="String">"</span>re<span class="String">"</span></span>, <span class="String"><span class="String">"</span>mi<span class="String">"</span></span>, <span class="String"><span class="String">"</span>fa<span class="String">"</span></span>, <span class="String"><span class="String">"</span>so<span class="String">"</span></span>]
|
||||
ages<span class="Keyword">:</span> {
|
||||
max<span class="Keyword">:</span> <span class="Number">10</span>
|
||||
ida<span class="Keyword">:</span> <span class="Number">9</span>
|
||||
tim<span class="Keyword">:</span> <span class="Number">11</span>
|
||||
}
|
||||
</pre><pre class="idle"><span class="Storage">var</span> song <span class="Keyword">=</span> [<span class="String"><span class="String">"</span>do<span class="String">"</span></span>, <span class="String"><span class="String">"</span>re<span class="String">"</span></span>, <span class="String"><span class="String">"</span>mi<span class="String">"</span></span>, <span class="String"><span class="String">"</span>fa<span class="String">"</span></span>, <span class="String"><span class="String">"</span>so<span class="String">"</span></span>];
|
||||
<span class="Storage">var</span> ages <span class="Keyword">=</span> {
|
||||
max: <span class="Number">10</span>,
|
||||
ida: <span class="Number">9</span>,
|
||||
tim: <span class="Number">11</span>
|
||||
};
|
||||
</pre><button onclick='javascript: var song = ["do", "re", "mi", "fa", "so"];
|
||||
var ages = {
|
||||
max: 10,
|
||||
ida: 9,
|
||||
tim: 11
|
||||
};
|
||||
;alert(song.join(","));'>run: song.join(",")</button><br class='clear' /></div>
|
||||
|
||||
<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>
|
||||
<div class='code'><pre class="idle">num<span class="Keyword">:</span> <span class="Number">1</span>
|
||||
<span class="FunctionName">change_numbers</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
num<span class="Keyword">:</span> <span class="Number">2</span>
|
||||
new_num<span class="Keyword">:</span> <span class="Number">3</span>.
|
||||
new_num<span class="Keyword">:</span> change_numbers()
|
||||
</pre><pre class="idle"><span class="Storage">var</span> num <span class="Keyword">=</span> <span class="Number">1</span>;
|
||||
<span class="Storage">var</span> <span class="FunctionName">change_numbers</span> = <span class="Storage">function</span>() {
|
||||
num <span class="Keyword">=</span> <span class="Number">2</span>;
|
||||
<span class="Storage">var</span> new_num <span class="Keyword">=</span> <span class="Number">3</span>;
|
||||
<span class="Keyword">return</span> new_num;
|
||||
};
|
||||
<span class="Storage">var</span> new_num <span class="Keyword">=</span> change_numbers();
|
||||
</pre><button onclick='javascript: var num = 1;
|
||||
var change_numbers = function() {
|
||||
num = 2;
|
||||
var new_num = 3;
|
||||
return new_num;
|
||||
};
|
||||
var new_num = change_numbers();
|
||||
;alert(new_num);'>run: new_num</button><br class='clear' /></div>
|
||||
<p>
|
||||
Notice how the variables are declared with <tt>var</tt> the first time
|
||||
they appear. The second reference of <b>num</b>, within the function,
|
||||
is not redeclared because <b>num</b> is still in scope. As opposed
|
||||
to the second occurrence of <b>new_num</b>, in the last line.
|
||||
</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.
|
||||
</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 parenthesis and
|
||||
curly brackets. As with functions and other block expressions, conditionals
|
||||
are closed with periods. No period is necessary when using the single-line
|
||||
postfix form, with the <tt>if</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>
|
||||
<div class='code'><pre class="idle">mood<span class="Keyword">:</span> greatly_improved <span class="Keyword">if</span> singing
|
||||
|
||||
<span class="Keyword">if</span> happy <span class="Keyword">and</span> knows_it
|
||||
claps_hands()
|
||||
cha_cha_cha().
|
||||
|
||||
date<span class="Keyword">:</span> <span class="Keyword">if</span> friday <span class="Keyword">then</span> sue <span class="Keyword">else</span> jill.
|
||||
|
||||
expensive <span class="Keyword">||</span><span class="Keyword">:</span> do_the_math()
|
||||
</pre><pre class="idle"><span class="Storage">var</span> mood;
|
||||
<span class="Keyword">if</span> (singing) {
|
||||
mood <span class="Keyword">=</span> greatly_improved;
|
||||
}
|
||||
<span class="Keyword">if</span> (happy <span class="Keyword">&</span><span class="Keyword">&</span> knows_it) {
|
||||
claps_hands();
|
||||
cha_cha_cha();
|
||||
}
|
||||
<span class="Storage">var</span> date <span class="Keyword">=</span> friday ? sue : jill;
|
||||
expensive <span class="Keyword">=</span> expensive <span class="Keyword">||</span> do_the_math();
|
||||
</pre><br class='clear' /></div>
|
||||
<p>
|
||||
The conditional assignment operators are available: <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="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>
|
||||
<div class='code'><pre class="idle"><span class="FunctionName">grade</span><span class="Keyword">:</span> <span class="FunctionArgument">student</span> <span class="Storage">=></span>
|
||||
<span class="Keyword">if</span> student.excellent_work
|
||||
<span class="String"><span class="String">"</span>A+<span class="String">"</span></span>
|
||||
<span class="Keyword">else</span> <span class="Keyword">if</span> student.okay_stuff
|
||||
<span class="Keyword">if</span> student.tried_hard <span class="Keyword">then</span> <span class="String"><span class="String">"</span>B<span class="String">"</span></span> <span class="Keyword">else</span> <span class="String"><span class="String">"</span>B-<span class="String">"</span></span>.
|
||||
<span class="Keyword">else</span>
|
||||
<span class="String"><span class="String">"</span>C<span class="String">"</span></span>..
|
||||
|
||||
eldest<span class="Keyword">:</span> <span class="Keyword">if</span> <span class="Number">24</span> <span class="Keyword">></span> <span class="Number">21</span> <span class="Keyword">then</span> <span class="String"><span class="String">"</span>Liz<span class="String">"</span></span> <span class="Keyword">else</span> <span class="String"><span class="String">"</span>Ike<span class="String">"</span></span>.
|
||||
</pre><pre class="idle"><span class="Storage">var</span> <span class="FunctionName">grade</span> = <span class="Storage">function</span>(<span class="FunctionArgument">student</span>) {
|
||||
<span class="Keyword">if</span> (student.excellent_work) {
|
||||
<span class="Keyword">return</span> <span class="String"><span class="String">"</span>A+<span class="String">"</span></span>;
|
||||
} <span class="Keyword">else</span> <span class="Keyword">if</span> (student.okay_stuff) {
|
||||
<span class="Keyword">return</span> student.tried_hard ? <span class="String"><span class="String">"</span>B<span class="String">"</span></span> : <span class="String"><span class="String">"</span>B-<span class="String">"</span></span>;
|
||||
} <span class="Keyword">else</span> {
|
||||
<span class="Keyword">return</span> <span class="String"><span class="String">"</span>C<span class="String">"</span></span>;
|
||||
}
|
||||
};
|
||||
<span class="Storage">var</span> eldest <span class="Keyword">=</span> <span class="Number">24</span> <span class="Keyword">></span> <span class="Number">21</span> ? <span class="String"><span class="String">"</span>Liz<span class="String">"</span></span> : <span class="String"><span class="String">"</span>Ike<span class="String">"</span></span>;
|
||||
</pre><button onclick='javascript: var grade = function(student) {
|
||||
if (student.excellent_work) {
|
||||
return "A+";
|
||||
} else if (student.okay_stuff) {
|
||||
return student.tried_hard ? "B" : "B-";
|
||||
} else {
|
||||
return "C";
|
||||
}
|
||||
};
|
||||
var eldest = 24 > 21 ? "Liz" : "Ike";
|
||||
;alert(eldest);'>run: eldest</button><br class='clear' /></div>
|
||||
<p>
|
||||
The same mechanism is used to push down assignment through <b>switch</b>
|
||||
statements, and <b>if-elses</b> (although the ternary operator is preferred).
|
||||
</p>
|
||||
|
||||
<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>aint</tt> into <tt>!==</tt>.
|
||||
</p>
|
||||
<p>
|
||||
You can use <tt>not</tt> as an alias for <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>
|
||||
<div class='code'><pre class="idle">launch() <span class="Keyword">if</span> ignition <span class="Keyword">is</span> <span class="BuiltInConstant">on</span>
|
||||
|
||||
volume<span class="Keyword">:</span> <span class="Number">10</span> <span class="Keyword">if</span> band <span class="Keyword">aint</span> spinal_tap
|
||||
|
||||
let_the_wild_rumpus_begin() <span class="Keyword">unless</span> answer <span class="Keyword">is</span> <span class="BuiltInConstant">no</span>
|
||||
|
||||
<span class="Keyword">if</span> car.speed <span class="Keyword"><</span> speed_limit <span class="Keyword">then</span> accelerate().
|
||||
</pre><pre class="idle"><span class="Keyword">if</span> (ignition <span class="Keyword">===</span> <span class="BuiltInConstant">true</span>) {
|
||||
launch();
|
||||
}
|
||||
<span class="Storage">var</span> volume;
|
||||
<span class="Keyword">if</span> (band <span class="Keyword">!</span><span class="Keyword">==</span> spinal_tap) {
|
||||
volume <span class="Keyword">=</span> <span class="Number">10</span>;
|
||||
}
|
||||
<span class="Keyword">if</span> (<span class="Keyword">!</span>(answer <span class="Keyword">===</span> <span class="BuiltInConstant">false</span>)) {
|
||||
let_the_wild_rumpus_begin();
|
||||
}
|
||||
car.speed <span class="Keyword"><</span> speed_limit ? accelerate() : <span class="BuiltInConstant">null</span>;
|
||||
</pre><br class='clear' /></div>
|
||||
|
||||
<p id="while">
|
||||
<b class="header">While Loops</b>
|
||||
The only low-level loop that CoffeeScript provides is the while loop.
|
||||
</p>
|
||||
<div class='code'><pre class="idle"><span class="Keyword">while</span> demand <span class="Keyword">></span> supply
|
||||
sell()
|
||||
restock().
|
||||
|
||||
<span class="Keyword">while</span> supply <span class="Keyword">></span> demand <span class="Keyword">then</span> buy().
|
||||
</pre><pre class="idle"><span class="Keyword">while</span> (demand <span class="Keyword">></span> supply) {
|
||||
sell();
|
||||
restock();
|
||||
}
|
||||
<span class="Keyword">while</span> (supply <span class="Keyword">></span> demand) {
|
||||
buy();
|
||||
}
|
||||
</pre><br class='clear' /></div>
|
||||
<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="array_comprehensions">
|
||||
<b class="header">Array Comprehensions</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>
|
||||
<div class='code'><pre class="idle"><span class="Comment"><span class="Comment">#</span> Eat lunch.</span>
|
||||
lunch<span class="Keyword">:</span> food.eat() <span class="Keyword">for</span> food <span class="Keyword">in</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>].
|
||||
|
||||
<span class="Comment"><span class="Comment">#</span> Zebra-stripe a table.</span>
|
||||
highlight(row) <span class="Keyword">for</span> row, i <span class="Keyword">in</span> table <span class="Keyword">if</span> i <span class="Keyword">%</span> <span class="Number">2</span> <span class="Keyword">is</span> <span class="Number">0</span>.
|
||||
</pre><pre class="idle">
|
||||
<span class="Comment"><span class="Comment">//</span> Eat lunch.</span>
|
||||
<span class="Storage">var</span> lunch;
|
||||
<span class="Storage">var</span> a <span class="Keyword">=</span> [<span class="String"><span class="String">'</span>toast<span class="String">'</span></span>, <span class="String"><span class="String">'</span>cheese<span class="String">'</span></span>, <span class="String"><span class="String">'</span>wine<span class="String">'</span></span>];
|
||||
<span class="Storage">var</span> d <span class="Keyword">=</span> [];
|
||||
<span class="Keyword">for</span> (<span class="Storage">var</span> b<span class="Keyword">=</span><span class="Number">0</span>, c<span class="Keyword">=</span>a.<span class="LibraryConstant">length</span>; b<span class="Keyword"><</span>c; b<span class="Keyword">++</span>) {
|
||||
<span class="Storage">var</span> food <span class="Keyword">=</span> a[b];
|
||||
d[b] <span class="Keyword">=</span> food.eat();
|
||||
}
|
||||
lunch <span class="Keyword">=</span> d;
|
||||
<span class="Comment"><span class="Comment">//</span> Zebra-stripe a table.</span>
|
||||
<span class="Storage">var</span> e <span class="Keyword">=</span> table;
|
||||
<span class="Keyword">for</span> (<span class="Storage">var</span> f<span class="Keyword">=</span><span class="Number">0</span>, g<span class="Keyword">=</span>e.<span class="LibraryConstant">length</span>; f<span class="Keyword"><</span>g; f<span class="Keyword">++</span>) {
|
||||
<span class="Storage">var</span> row <span class="Keyword">=</span> e[f];
|
||||
<span class="Storage">var</span> i <span class="Keyword">=</span> f;
|
||||
i <span class="Keyword">%</span> <span class="Number">2</span> <span class="Keyword">===</span> <span class="Number">0</span> ? highlight(row) : <span class="BuiltInConstant">null</span>;
|
||||
}
|
||||
</pre><br class='clear' /></div>
|
||||
|
||||
<p id="slice">
|
||||
<b class="header">Array Slice Literals</b>
|
||||
CoffeeScript includes syntax for extracting slices of arrays.
|
||||
The first argument is the index of the first element in the slice, and
|
||||
the second is the index of the last one.
|
||||
</p>
|
||||
<div class='code'><pre class="idle">nums<span class="Keyword">:</span> [<span class="Number">0</span>, <span class="Number">1</span>, <span class="Number">2</span>, <span class="Number">3</span>, <span class="Number">4</span>, <span class="Number">5</span>, <span class="Number">6</span>, <span class="Number">7</span>, <span class="Number">8</span>, <span class="Number">9</span>]
|
||||
three_to_six<span class="Keyword">:</span> nums[<span class="Number">3</span>, <span class="Number">6</span>]
|
||||
</pre><pre class="idle"><span class="Storage">var</span> nums <span class="Keyword">=</span> [<span class="Number">0</span>, <span class="Number">1</span>, <span class="Number">2</span>, <span class="Number">3</span>, <span class="Number">4</span>, <span class="Number">5</span>, <span class="Number">6</span>, <span class="Number">7</span>, <span class="Number">8</span>, <span class="Number">9</span>];
|
||||
<span class="Storage">var</span> three_to_six <span class="Keyword">=</span> nums.<span class="LibraryFunction">slice</span>(<span class="Number">3</span>, <span class="Number">6</span> <span class="Keyword">+</span> <span class="Number">1</span>);
|
||||
</pre><button onclick='javascript: var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
var three_to_six = nums.slice(3, 6 + 1);
|
||||
;alert(three_to_six);'>run: three_to_six</button><br class='clear' /></div>
|
||||
|
||||
<p id="super">
|
||||
<b class="header">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 one small exception:
|
||||
it's very awkward to call <b>super</b>, the prototype object's
|
||||
implementation of the current function. CoffeeScript converts
|
||||
<tt>super()</tt> calls into calls against the immediate ancestor's
|
||||
method of the same name.
|
||||
</p>
|
||||
<div class='code'><pre class="idle"><span class="FunctionName">Animal</span><span class="Keyword">:</span> <span class="Storage">=></span> .
|
||||
<span class="FunctionName">Animal.prototype.move</span><span class="Keyword">:</span> <span class="FunctionArgument">meters</span> <span class="Storage">=></span>
|
||||
alert(<span class="Variable">this</span>.name <span class="Keyword">+</span> <span class="String"><span class="String">"</span> moved <span class="String">"</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">"</span>m.<span class="String">"</span></span>).
|
||||
|
||||
<span class="FunctionName">Snake</span><span class="Keyword">:</span> <span class="FunctionArgument">name</span> <span class="Storage">=></span> <span class="Variable">this</span>.name<span class="Keyword">:</span> name.
|
||||
Snake.prototype<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Animal</span>()
|
||||
<span class="FunctionName">Snake.prototype.move</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
alert(<span class="String"><span class="String">"</span>Slithering...<span class="String">"</span></span>)
|
||||
<span class="Variable">super</span>(<span class="Number">5</span>).
|
||||
|
||||
<span class="FunctionName">Horse</span><span class="Keyword">:</span> <span class="FunctionArgument">name</span> <span class="Storage">=></span> <span class="Variable">this</span>.name<span class="Keyword">:</span> name.
|
||||
Horse.prototype<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Animal</span>()
|
||||
<span class="FunctionName">Horse.prototype.move</span><span class="Keyword">:</span> <span class="Storage">=></span>
|
||||
alert(<span class="String"><span class="String">"</span>Galloping...<span class="String">"</span></span>)
|
||||
<span class="Variable">super</span>(<span class="Number">45</span>).
|
||||
|
||||
sam<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Snake</span>(<span class="String"><span class="String">"</span>Sammy the Python<span class="String">"</span></span>)
|
||||
tom<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Horse</span>(<span class="String"><span class="String">"</span>Tommy the Palomino<span class="String">"</span></span>)
|
||||
|
||||
sam.move()
|
||||
tom.move()
|
||||
|
||||
|
||||
|
||||
|
||||
</pre><pre class="idle"><span class="Storage">var</span> <span class="FunctionName">Animal</span> = <span class="Storage">function</span>() {
|
||||
};
|
||||
<span class="LibraryClassType">Animal</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span>(<span class="FunctionArgument">meters</span>) {
|
||||
<span class="Keyword">return</span> <span class="LibraryFunction">alert</span>(<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">+</span> <span class="String"><span class="String">"</span> moved <span class="String">"</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">"</span>m.<span class="String">"</span></span>);
|
||||
};
|
||||
<span class="Storage">var</span> <span class="FunctionName">Snake</span> = <span class="Storage">function</span>(<span class="FunctionArgument">name</span>) {
|
||||
<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">=</span> name;
|
||||
<span class="Keyword">return</span> <span class="Variable">this</span>.<span class="LibraryConstant">name</span>;
|
||||
};
|
||||
<span class="LibraryClassType">Snake</span>.<span class="LibraryConstant">prototype</span> = <span class="Keyword">new</span> <span class="TypeName">Animal</span>();
|
||||
<span class="LibraryClassType">Snake</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span>() {
|
||||
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">"</span>Slithering...<span class="String">"</span></span>);
|
||||
<span class="Keyword">return</span> <span class="Variable">this</span>.<span class="LibraryConstant">constructor</span>.<span class="LibraryConstant">prototype</span>.move.<span class="LibraryFunction">call</span>(<span class="Variable">this</span>, <span class="Number">5</span>);
|
||||
};
|
||||
<span class="Storage">var</span> <span class="FunctionName">Horse</span> = <span class="Storage">function</span>(<span class="FunctionArgument">name</span>) {
|
||||
<span class="Variable">this</span>.<span class="LibraryConstant">name</span> <span class="Keyword">=</span> name;
|
||||
<span class="Keyword">return</span> <span class="Variable">this</span>.<span class="LibraryConstant">name</span>;
|
||||
};
|
||||
<span class="LibraryClassType">Horse</span>.<span class="LibraryConstant">prototype</span> = <span class="Keyword">new</span> <span class="TypeName">Animal</span>();
|
||||
<span class="LibraryClassType">Horse</span>.<span class="LibraryConstant">prototype</span>.<span class="FunctionName">move</span> = <span class="Storage">function</span>() {
|
||||
<span class="LibraryFunction">alert</span>(<span class="String"><span class="String">"</span>Galloping...<span class="String">"</span></span>);
|
||||
<span class="Keyword">return</span> <span class="Variable">this</span>.<span class="LibraryConstant">constructor</span>.<span class="LibraryConstant">prototype</span>.move.<span class="LibraryFunction">call</span>(<span class="Variable">this</span>, <span class="Number">45</span>);
|
||||
};
|
||||
<span class="Storage">var</span> sam <span class="Keyword">=</span> <span class="Keyword">new</span> <span class="TypeName">Snake</span>(<span class="String"><span class="String">"</span>Sammy the Python<span class="String">"</span></span>);
|
||||
<span class="Storage">var</span> tom <span class="Keyword">=</span> <span class="Keyword">new</span> <span class="TypeName">Horse</span>(<span class="String"><span class="String">"</span>Tommy the Palomino<span class="String">"</span></span>);
|
||||
sam.move();
|
||||
tom.move();
|
||||
</pre><button onclick='javascript: var Animal = function() {
|
||||
};
|
||||
Animal.prototype.move = function(meters) {
|
||||
return alert(this.name + " moved " + meters + "m.");
|
||||
};
|
||||
var Snake = function(name) {
|
||||
this.name = name;
|
||||
return this.name;
|
||||
};
|
||||
Snake.prototype = new Animal();
|
||||
Snake.prototype.move = function() {
|
||||
alert("Slithering...");
|
||||
return this.constructor.prototype.move.call(this, 5);
|
||||
};
|
||||
var Horse = function(name) {
|
||||
this.name = name;
|
||||
return this.name;
|
||||
};
|
||||
Horse.prototype = new Animal();
|
||||
Horse.prototype.move = function() {
|
||||
alert("Galloping...");
|
||||
return this.constructor.prototype.move.call(this, 45);
|
||||
};
|
||||
var sam = new Snake("Sammy the Python");
|
||||
var tom = new Horse("Tommy the Palomino");
|
||||
sam.move();
|
||||
tom.move();
|
||||
;'>run</button><br class='clear' /></div>
|
||||
|
||||
<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>
|
||||
<div class='code'><pre class="idle">hi<span class="Keyword">:</span> <span class="String"><span class="String">`</span>function() {</span>
|
||||
<span class="String"> return [document.title, "Hello JavaScript"].join(": ");</span>
|
||||
<span class="String">}<span class="String">`</span></span>
|
||||
|
||||
</pre><pre class="idle"><span class="Storage">var</span> <span class="FunctionName">hi</span> = <span class="Storage">function</span>() {
|
||||
<span class="Keyword">return</span> [<span class="LibraryClassType">document</span>.<span class="LibraryConstant">title</span>, <span class="String"><span class="String">"</span>Hello JavaScript<span class="String">"</span></span>].<span class="LibraryFunction">join</span>(<span class="String"><span class="String">"</span>: <span class="String">"</span></span>);
|
||||
};
|
||||
</pre><button onclick='javascript: var hi = function() {
|
||||
return [document.title, "Hello JavaScript"].join(": ");
|
||||
};
|
||||
;alert(hi());'>run: hi()</button><br class='clear' /></div>
|
||||
|
||||
<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>
|
||||
<div class='code'><pre class="idle"><span class="Keyword">switch</span> day
|
||||
when <span class="String"><span class="String">"</span>Tuesday<span class="String">"</span></span> <span class="Keyword">then</span> eat_breakfast()
|
||||
when <span class="String"><span class="String">"</span>Wednesday<span class="String">"</span></span> <span class="Keyword">then</span> go_to_the_park()
|
||||
when <span class="String"><span class="String">"</span>Saturday<span class="String">"</span></span>
|
||||
<span class="Keyword">if</span> day <span class="Keyword">is</span> bingo_day
|
||||
go_to_bingo()
|
||||
go_dancing().
|
||||
when <span class="String"><span class="String">"</span>Sunday<span class="String">"</span></span> <span class="Keyword">then</span> go_to_church()
|
||||
<span class="Keyword">else</span> go_to_work().
|
||||
</pre><pre class="idle"><span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">"</span>Tuesday<span class="String">"</span></span>) {
|
||||
eat_breakfast();
|
||||
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">"</span>Wednesday<span class="String">"</span></span>) {
|
||||
go_to_the_park();
|
||||
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">"</span>Saturday<span class="String">"</span></span>) {
|
||||
<span class="Keyword">if</span> (day <span class="Keyword">===</span> bingo_day) {
|
||||
go_to_bingo();
|
||||
go_dancing();
|
||||
}
|
||||
} <span class="Keyword">else</span> <span class="Keyword">if</span> (day <span class="Keyword">===</span> <span class="String"><span class="String">"</span>Sunday<span class="String">"</span></span>) {
|
||||
go_to_church();
|
||||
} <span class="Keyword">else</span> {
|
||||
go_to_work();
|
||||
}
|
||||
</pre><br class='clear' /></div>
|
||||
|
||||
<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>
|
||||
<div class='code'><pre class="idle"><span class="Keyword">try</span>
|
||||
all_hell_breaks_loose()
|
||||
cats_and_dogs_living_together()
|
||||
<span class="Keyword">catch</span> error
|
||||
print(error)
|
||||
<span class="Keyword">finally</span>
|
||||
clean_up().
|
||||
</pre><pre class="idle"><span class="Keyword">try</span> {
|
||||
all_hell_breaks_loose();
|
||||
cats_and_dogs_living_together();
|
||||
} <span class="Keyword">catch</span> (error) {
|
||||
<span class="LibraryFunction">print</span>(error);
|
||||
} <span class="Keyword">finally</span> {
|
||||
clean_up();
|
||||
}
|
||||
</pre><br class='clear' /></div>
|
||||
|
||||
<p id="try">
|
||||
<b class="header">Multiline Strings</b>
|
||||
Multiline strings are allowed in CoffeeScript.
|
||||
</p>
|
||||
<div class='code'><pre class="idle">moby_dick<span class="Keyword">:</span> <span class="String"><span class="String">"</span>Call me Ishmael. Some years ago --</span>
|
||||
<span class="String">never mind how long precisely -- having little</span>
|
||||
<span class="String">or no money in my purse, and nothing particular</span>
|
||||
<span class="String">to interest me on shore, I thought I would sail</span>
|
||||
<span class="String">about a little and see the watery part of the</span>
|
||||
<span class="String">world...<span class="String">"</span></span>
|
||||
|
||||
</pre><pre class="idle"><span class="Storage">var</span> moby_dick <span class="Keyword">=</span> <span class="String"><span class="String">"</span>Call me Ishmael. Some years ago -- \</span>
|
||||
<span class="String">never mind how long precisely -- having little \</span>
|
||||
<span class="String">or no money in my purse, and nothing particular \</span>
|
||||
<span class="String">to interest me on shore, I thought I would sail \</span>
|
||||
<span class="String">about a little and see the watery part of the \</span>
|
||||
<span class="String">world...<span class="String">"</span></span>;
|
||||
</pre><button onclick='javascript: var 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...";
|
||||
;alert(moby_dick);'>run: moby_dick</button><br class='clear' /></div>
|
||||
|
||||
<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 JavaScript version of the compiler, perhaps using Alessandro Warth's
|
||||
<a href="http://tinlizzie.org/ometa/">OMeta</a>.
|
||||
</li>
|
||||
<li>
|
||||
Ideas for alternate syntax to end blocks of expressions — the periods
|
||||
can look a little weird with deeply nested structure.
|
||||
</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: 25px;">0.1.0</b>
|
||||
Initial CoffeeScript release.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
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'))
|
||||
20
lib/coffee-script.rb
Normal file
20
lib/coffee-script.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
$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/parse_error"
|
||||
|
||||
# Namespace for all CoffeeScript internal classes.
|
||||
module CoffeeScript
|
||||
|
||||
VERSION = '0.1.1' # Keep in sync with the gemspec.
|
||||
|
||||
# Compile a script (String or IO) to JavaScript.
|
||||
def self.compile(script)
|
||||
script = script.read if script.respond_to?(:read)
|
||||
Parser.new.parse(script).compile
|
||||
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.cs</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,329 @@
|
||||
<?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>cs</string>
|
||||
<string>coffeescript</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.cs</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.cs</string>
|
||||
</dict>
|
||||
<key>3</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>variable.parameter.function.cs</string>
|
||||
</dict>
|
||||
<key>4</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>storage.type.function.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>comment</key>
|
||||
<string>match stuff like: funcName: => … </string>
|
||||
<key>match</key>
|
||||
<string>([a-zA-Z_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)</string>
|
||||
<key>name</key>
|
||||
<string>meta.function.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>variable.parameter.function.cs</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>storage.type.function.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>comment</key>
|
||||
<string>match stuff like: a => … </string>
|
||||
<key>match</key>
|
||||
<string>([a-zA-Z_?., $]*)\s*(=>)</string>
|
||||
<key>name</key>
|
||||
<string>meta.inline.function.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.new.cs</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>entity.name.type.instance.cs</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.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>'</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>'</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.single.cs</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.cs</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.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>"</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.double.cs</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.cs</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.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>`</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.script.cs</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.cs</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.comment.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>match</key>
|
||||
<string>(#).*$\n?</string>
|
||||
<key>name</key>
|
||||
<string>comment.line.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b</string>
|
||||
<key>name</key>
|
||||
<string>keyword.control.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(true|on|yes)\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.boolean.true.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(false|off|no)\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.boolean.false.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\bnull\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.null.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(super|this)\b</string>
|
||||
<key>name</key>
|
||||
<string>variable.language.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(debugger)\b</string>
|
||||
<key>name</key>
|
||||
<string>keyword.other.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>!|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|aint|not)\b</string>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(Infinity|NaN|undefined)\b</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.cs</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.cs</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.cs</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.regexp.cs</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.cs</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\;</string>
|
||||
<key>name</key>
|
||||
<string>punctuation.terminator.statement.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>,[ |\t]*</string>
|
||||
<key>name</key>
|
||||
<string>meta.delimiter.object.comma.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\.</string>
|
||||
<key>name</key>
|
||||
<string>meta.delimiter.method.period.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\{|\}</string>
|
||||
<key>name</key>
|
||||
<string>meta.brace.curly.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\(|\)</string>
|
||||
<key>name</key>
|
||||
<string>meta.brace.round.cs</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\[|\]</string>
|
||||
<key>name</key>
|
||||
<string>meta.brace.square.cs</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>scopeName</key>
|
||||
<string>source.cs</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>
|
||||
183
lib/coffee_script/command_line.rb
Normal file
183
lib/coffee_script/command_line.rb
Normal file
@@ -0,0 +1,183 @@
|
||||
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-script`
|
||||
# utility.
|
||||
class CommandLine
|
||||
|
||||
BANNER = <<-EOS
|
||||
coffee-script compiles CoffeeScript source files into JavaScript.
|
||||
|
||||
Usage:
|
||||
coffee-script path/to/script.cs
|
||||
EOS
|
||||
|
||||
# Seconds to pause between checks for changed source files.
|
||||
WATCH_INTERVAL = 0.5
|
||||
|
||||
# Run the CommandLine off the contents of ARGV.
|
||||
def initialize
|
||||
@mtimes = {}
|
||||
parse_options
|
||||
return eval_scriptlet if @options[:eval]
|
||||
check_sources
|
||||
@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
|
||||
|
||||
# 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='')
|
||||
begin
|
||||
CoffeeScript.compile(script)
|
||||
rescue CoffeeScript::ParseError => e
|
||||
STDERR.puts e.message(source)
|
||||
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('-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', 'eval a little scriptlet directly from the cli') 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_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
|
||||
install_bundle
|
||||
exit
|
||||
end
|
||||
opts.on_tail('--version', 'display coffee-script version') do
|
||||
puts "coffee-script 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
|
||||
@@ -3,61 +3,77 @@ class Parser
|
||||
# Declare tokens produced by the lexer
|
||||
token IF ELSE THEN UNLESS
|
||||
token NUMBER STRING REGEX
|
||||
token TRUE FALSE NULL
|
||||
token TRUE FALSE YES NO ON OFF
|
||||
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 SWITCH WHEN
|
||||
token DELETE INSTANCEOF TYPEOF
|
||||
token SUPER
|
||||
token NEWLINE
|
||||
token COMMENT
|
||||
token JS
|
||||
|
||||
# Declare order of operations.
|
||||
prechigh
|
||||
nonassoc UMINUS NOT '!'
|
||||
nonassoc UMINUS NOT '!' '!!' '~' '++' '--'
|
||||
left '*' '/' '%'
|
||||
left '+' '-'
|
||||
left '<<' '>>' '>>>'
|
||||
left '&' '|' '^'
|
||||
left '<=' '<' '>' '>='
|
||||
right '==' '!=' IS AINT
|
||||
left '&&' '||' AND OR
|
||||
left ':'
|
||||
right '-=' '+=' '/=' '*=' '||=' '&&='
|
||||
nonassoc IF
|
||||
left UNLESS
|
||||
right RETURN THROW FOR WHILE
|
||||
nonassoc "."
|
||||
right '-=' '+=' '/=' '*='
|
||||
right DELETE INSTANCEOF TYPEOF
|
||||
left "."
|
||||
right THROW FOR IN WHILE NEW
|
||||
left UNLESS IF ELSE
|
||||
left ":" '||:' '&&:'
|
||||
right RETURN
|
||||
preclow
|
||||
|
||||
# We expect 2 shift/reduce errors for optional syntax.
|
||||
# We expect 4 shift/reduce errors for optional syntax.
|
||||
# There used to be 252 -- greatly improved.
|
||||
expect 2
|
||||
expect 4
|
||||
|
||||
rule
|
||||
|
||||
# All parsing will end in this rule, being the trunk of the AST.
|
||||
Root:
|
||||
/* nothing */ { result = Nodes.new([]) }
|
||||
| Terminator { result = Nodes.new([]) }
|
||||
/* nothing */ { result = Expressions.new([]) }
|
||||
| Terminator { result = Expressions.new([]) }
|
||||
| Expressions { result = val[0] }
|
||||
;
|
||||
|
||||
# Any list of expressions or method body, seperated by line breaks or semis.
|
||||
Expressions:
|
||||
Expression { result = Nodes.new(val) }
|
||||
Expression { result = Expressions.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
|
||||
# All types of expressions in our language.
|
||||
Expression:
|
||||
PureExpression
|
||||
| Statement
|
||||
;
|
||||
|
||||
# The parts that are natural JavaScript expressions.
|
||||
PureExpression:
|
||||
Literal
|
||||
| Value
|
||||
| Call
|
||||
| Assign
|
||||
| Code
|
||||
| Operation
|
||||
;
|
||||
|
||||
# We have to take extra care to convert these statements into expressions.
|
||||
Statement:
|
||||
Assign
|
||||
| If
|
||||
| Try
|
||||
| Throw
|
||||
@@ -65,34 +81,38 @@ rule
|
||||
| While
|
||||
| For
|
||||
| Switch
|
||||
| Comment
|
||||
;
|
||||
|
||||
# All tokens that can terminate an expression
|
||||
# All tokens that can terminate an expression.
|
||||
Terminator:
|
||||
"\n"
|
||||
| ";"
|
||||
;
|
||||
|
||||
# All tokens that can serve to begin the second block
|
||||
# All tokens that can serve to begin the second block of a multi-part expression.
|
||||
Then:
|
||||
THEN
|
||||
| Terminator
|
||||
;
|
||||
|
||||
# All hard-coded values
|
||||
# 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]) }
|
||||
| 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) }
|
||||
;
|
||||
|
||||
# Assign to a variable
|
||||
# Assignment to a variable.
|
||||
Assign:
|
||||
Value ":" Expression { result = AssignNode.new(val[0], val[2]) }
|
||||
;
|
||||
@@ -100,21 +120,32 @@ rule
|
||||
# Assignment within an object literal.
|
||||
AssignObj:
|
||||
IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
|
||||
| Comment { result = val[0] }
|
||||
;
|
||||
|
||||
# A Return statement.
|
||||
# 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]) }
|
||||
| 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]) }
|
||||
@@ -123,6 +154,14 @@ rule
|
||||
| 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]) }
|
||||
@@ -142,49 +181,65 @@ rule
|
||||
| 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]) }
|
||||
|
||||
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
|
||||
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
;
|
||||
|
||||
|
||||
# Method definition
|
||||
# Function definition.
|
||||
Code:
|
||||
ParamList "=>" Expressions "." { result = CodeNode.new(val[0], val[2]) }
|
||||
| "=>" Expressions "." { result = CodeNode.new([], val[1]) }
|
||||
ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) }
|
||||
| "=>" CodeBody "." { result = CodeNode.new([], val[1]) }
|
||||
;
|
||||
|
||||
# The body of a function.
|
||||
CodeBody:
|
||||
/* nothing */ { result = Expressions.new([]) }
|
||||
| Expressions { result = val[0] }
|
||||
;
|
||||
|
||||
# The parameters to a function definition.
|
||||
ParamList:
|
||||
PARAM { result = val }
|
||||
| ParamList "," PARAM { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
# Expressions that can be treated as values.
|
||||
Value:
|
||||
IDENTIFIER { result = ValueNode.new(val) }
|
||||
| Array { result = ValueNode.new(val) }
|
||||
| Object { result = ValueNode.new(val) }
|
||||
| Parenthetical { result = ValueNode.new(val) }
|
||||
IDENTIFIER { 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] }
|
||||
| Slice { result = val[0] }
|
||||
;
|
||||
|
||||
# Indexing into an object or array.
|
||||
Index:
|
||||
"[" Expression "]" { result = IndexNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# Array slice literal.
|
||||
Slice:
|
||||
"[" Expression "," Expression "]" { result = SliceNode.new(val[1], val[3]) }
|
||||
;
|
||||
|
||||
# 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 }
|
||||
@@ -192,22 +247,29 @@ rule
|
||||
| AssignList Terminator AssignObj { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
# A method call.
|
||||
# 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] }
|
||||
;
|
||||
|
||||
# A generic function invocation.
|
||||
Invocation:
|
||||
Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
|
||||
;
|
||||
|
||||
# An Array.
|
||||
# Calling super.
|
||||
Super:
|
||||
SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) }
|
||||
;
|
||||
|
||||
# The array literal.
|
||||
Array:
|
||||
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# A list of arguments to a method call.
|
||||
# A list of arguments to a method call, or as the contents of an array.
|
||||
ArgList:
|
||||
/* nothing */ { result = [] }
|
||||
| Expression { result = val }
|
||||
@@ -215,84 +277,129 @@ rule
|
||||
| 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/catch/finally exception handling blocks.
|
||||
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]) }
|
||||
TRY Expressions Catch "." { result = TryNode.new(val[1], val[2][0], val[2][1]) }
|
||||
| TRY Expressions Catch
|
||||
FINALLY Expressions "." { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
|
||||
;
|
||||
|
||||
# A catch clause.
|
||||
Catch:
|
||||
/* nothing */ { result = [nil, nil] }
|
||||
| CATCH IDENTIFIER Expressions { result = [val[1], val[2]] }
|
||||
;
|
||||
|
||||
# Throw an exception.
|
||||
Throw:
|
||||
THROW Expression { result = ThrowNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# Parenthetical expressions.
|
||||
Parenthetical:
|
||||
"(" Expressions ")" { result = ParentheticalNode.new(val[1]) }
|
||||
;
|
||||
|
||||
# The while loop. (there is no do..while).
|
||||
While:
|
||||
WHILE Expression Then
|
||||
Expressions "." { result = WhileNode.new(val[1], val[3]) }
|
||||
;
|
||||
|
||||
# Array comprehensions, including guard and current index.
|
||||
For:
|
||||
Expression FOR IDENTIFIER
|
||||
IN Expression "." { result = ForNode.new(val[0], val[4], val[2]) }
|
||||
IN PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) }
|
||||
| Expression FOR
|
||||
IDENTIFIER "," IDENTIFIER
|
||||
IN Expression "." { result = ForNode.new(val[0], val[6], val[2], val[4]) }
|
||||
IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) }
|
||||
| Expression FOR IDENTIFIER
|
||||
IN Expression
|
||||
IF Expression "." { result = ForNode.new(IfNode.new(val[6], Nodes.new([val[0]])), val[4], val[2]) }
|
||||
IN PureExpression
|
||||
IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) }
|
||||
| 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]) }
|
||||
IN PureExpression
|
||||
IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) }
|
||||
;
|
||||
|
||||
# Switch/When blocks.
|
||||
Switch:
|
||||
SWITCH Expression Then
|
||||
Cases "." { result = val[3].rewrite_condition(val[1]) }
|
||||
Whens "." { result = val[3].rewrite_condition(val[1]) }
|
||||
| SWITCH Expression Then
|
||||
Cases DEFAULT Expressions "." { result = val[3].rewrite_condition(val[1]).add_default(val[5]) }
|
||||
Whens ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
|
||||
;
|
||||
|
||||
Cases:
|
||||
Case { result = val[0] }
|
||||
| Cases Case { result = val[0] << val[1] }
|
||||
# The inner list of whens.
|
||||
Whens:
|
||||
When { result = val[0] }
|
||||
| Whens When { result = val[0] << val[1] }
|
||||
;
|
||||
|
||||
Case:
|
||||
CASE Expression Then Expressions { result = IfNode.new(val[1], val[3]) }
|
||||
# An individual when.
|
||||
When:
|
||||
WHEN Expression Then Expressions { result = IfNode.new(val[1], val[3]) }
|
||||
;
|
||||
|
||||
# All of the following nutso if-else destructuring is to make the
|
||||
# grammar expand unambiguously.
|
||||
|
||||
# An elsif portion of an if-else block.
|
||||
ElsIf:
|
||||
ELSE IF Expression
|
||||
Then Expressions { result = IfNode.new(val[2], val[4]) }
|
||||
;
|
||||
|
||||
# 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
|
||||
"." { result = nil }
|
||||
| ELSE Expressions "." { 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:
|
||||
IF Expression
|
||||
Then Expressions IfEnd { result = IfNode.new(val[1], val[3], val[4]) }
|
||||
| Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) }
|
||||
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) }
|
||||
;
|
||||
|
||||
end
|
||||
|
||||
---- header
|
||||
require "lexer"
|
||||
require "nodes"
|
||||
module CoffeeScript
|
||||
|
||||
---- inner
|
||||
def parse(code, show_tokens=false)
|
||||
# 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)
|
||||
puts @tokens.inspect if show_tokens
|
||||
do_parse
|
||||
end
|
||||
|
||||
# Retrieve the next token from the list.
|
||||
def next_token
|
||||
@tokens.shift
|
||||
end
|
||||
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
|
||||
187
lib/coffee_script/lexer.rb
Normal file
187
lib/coffee_script/lexer.rb
Normal file
@@ -0,0 +1,187 @@
|
||||
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", "aint", "not",
|
||||
"new", "return",
|
||||
"try", "catch", "finally", "throw",
|
||||
"break", "continue",
|
||||
"for", "in", "while",
|
||||
"switch", "when",
|
||||
"super",
|
||||
"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\r]+)/
|
||||
NEWLINE = /\A(\n+)/
|
||||
COMMENT = /\A((#[^\n]*\s*)+)/m
|
||||
CODE = /\A(=>)/
|
||||
REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/
|
||||
|
||||
# Token cleaning regexes.
|
||||
JS_CLEANER = /(\A`|`\Z)/
|
||||
MULTILINER = /\n/
|
||||
COMMENT_CLEANER = /(^\s*#|\n\s*$)/
|
||||
|
||||
# Tokens that always constitute the start of an expression.
|
||||
EXP_START = ['{', '(', '[']
|
||||
|
||||
# Tokens that always constitute the end of an expression.
|
||||
EXP_END = ['}', ')', ']']
|
||||
|
||||
# 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.
|
||||
@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
|
||||
|
||||
# At every position, run this list of match attempts, 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 comment_token
|
||||
return if whitespace_token
|
||||
return literal_token
|
||||
end
|
||||
|
||||
# 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
|
||||
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.'
|
||||
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]
|
||||
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
|
||||
|
||||
# Matches and consumes non-meaningful 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 together.
|
||||
def literal_token
|
||||
value = @chunk[NEWLINE, 1]
|
||||
if value
|
||||
@line += value.length
|
||||
token("\n", "\n") unless last_value == "\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)
|
||||
token(value, value)
|
||||
@i += value.length
|
||||
end
|
||||
|
||||
# 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.
|
||||
def last_value
|
||||
@tokens.last && @tokens.last[1]
|
||||
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.
|
||||
def tag_parameters
|
||||
index = 0
|
||||
loop do
|
||||
tok = @tokens[index -= 1]
|
||||
return if !tok
|
||||
next if tok[0] == ','
|
||||
return if tok[0] != :IDENTIFIER
|
||||
tok[0] = :PARAM
|
||||
end
|
||||
end
|
||||
|
||||
# Consume and ignore newlines immediately after this point.
|
||||
def skip_following_newlines
|
||||
newlines = @code[(@i+1)..-1][NEWLINE, 1]
|
||||
if newlines
|
||||
@line += newlines.length
|
||||
@i += newlines.length
|
||||
end
|
||||
end
|
||||
|
||||
# Discard newlines immediately before this point.
|
||||
def remove_leading_newlines
|
||||
@tokens.pop if last_value == "\n"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
681
lib/coffee_script/nodes.rb
Normal file
681
lib/coffee_script/nodes.rb
Normal file
@@ -0,0 +1,681 @@
|
||||
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 having a custom return, meaning that instead of returning
|
||||
# it from the outside, you ask it to return itself, and it obliges.
|
||||
def self.custom_return
|
||||
class_eval "def custom_return?; true; end"
|
||||
end
|
||||
|
||||
# Tag this node as having a custom assignment, meaning that instead of
|
||||
# assigning it to a variable name from the outside, you pass it the variable
|
||||
# name and let it take care of it.
|
||||
def self.custom_assign
|
||||
class_eval "def custom_assign?; true; end"
|
||||
end
|
||||
|
||||
def write(code)
|
||||
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
|
||||
code
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
@options = o.dup
|
||||
end
|
||||
|
||||
# Default implementations of the common node methods.
|
||||
def unwrap; self; end
|
||||
def line_ending; ';'; end
|
||||
def statement?; false; end
|
||||
def custom_return?; false; end
|
||||
def custom_assign?; 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(node)
|
||||
node.is_a?(Expressions) ? node : Expressions.new([node])
|
||||
end
|
||||
|
||||
def initialize(nodes)
|
||||
@expressions = nodes
|
||||
end
|
||||
|
||||
# Tack an expression onto the end of this node.
|
||||
def <<(node)
|
||||
@expressions << 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
|
||||
|
||||
# If this is the top-level Expressions, wrap everything in a safety closure.
|
||||
def root_compile
|
||||
code = compile(:indent => TAB, :scope => Scope.new)
|
||||
code.gsub!(STRIP_TRAILING_WHITESPACE, '')
|
||||
"(function(){\n#{code}\n})();"
|
||||
end
|
||||
|
||||
# The extra fancy is to handle pushing down returns and assignments
|
||||
# recursively to the final lines of inner statements.
|
||||
def compile(options={})
|
||||
return root_compile unless options[:scope]
|
||||
code = @expressions.map { |node|
|
||||
o = super(options)
|
||||
if last?(node) && (o[:return] || o[:assign])
|
||||
if o[:return]
|
||||
if node.statement? || node.custom_return?
|
||||
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
|
||||
else
|
||||
"#{o[:indent]}return #{node.compile(o)}#{node.line_ending}"
|
||||
end
|
||||
elsif o[:assign]
|
||||
if node.statement? || node.custom_assign?
|
||||
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
|
||||
else
|
||||
"#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};"
|
||||
end
|
||||
end
|
||||
else
|
||||
o.delete(:return) and o.delete(:assign)
|
||||
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
|
||||
end
|
||||
}.join("\n")
|
||||
write(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
|
||||
|
||||
def line_ending
|
||||
@value.to_s[-1..-1] == ';' ? '' : ';'
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
write(@value.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
# Try to return your expression, or tell it to return itself.
|
||||
class ReturnNode < Node
|
||||
statement
|
||||
custom_return
|
||||
|
||||
attr_reader :expression
|
||||
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def line_ending
|
||||
@expression.custom_return? ? '' : ';'
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return?
|
||||
compiled = @expression.compile(o)
|
||||
write(@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}")
|
||||
end
|
||||
end
|
||||
|
||||
# Pass through CoffeeScript comments into JavaScript comments at the
|
||||
# same position.
|
||||
class CommentNode < Node
|
||||
statement
|
||||
|
||||
def initialize(lines)
|
||||
@lines = lines.value
|
||||
end
|
||||
|
||||
def line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def compile(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
|
||||
LEADING_DOT = /\A\./
|
||||
|
||||
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 compile(o={})
|
||||
o = super(o)
|
||||
args = @arguments.map{|a| a.compile(o.merge(:no_paren => true)) }.join(', ')
|
||||
return write(compile_super(args, o)) if super?
|
||||
prefix = @new ? "new " : ''
|
||||
write("#{prefix}#{@variable.compile(o)}(#{args})")
|
||||
end
|
||||
|
||||
def compile_super(args, o)
|
||||
methname = o[:last_assign].sub(LEADING_DOT, '')
|
||||
"this.constructor.prototype.#{methname}.call(this, #{args})"
|
||||
end
|
||||
end
|
||||
|
||||
# A value, indexed or dotted into, or vanilla.
|
||||
class ValueNode < Node
|
||||
attr_reader :literal, :properties, :last
|
||||
|
||||
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 custom_assign?
|
||||
@literal.is_a?(Node) && @literal.custom_assign? && !properties?
|
||||
end
|
||||
|
||||
def custom_return?
|
||||
@literal.is_a?(Node) && @literal.custom_return? && !properties?
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
parts = [@literal, @properties].flatten.map do |val|
|
||||
val.respond_to?(:compile) ? val.compile(o) : val.to_s
|
||||
end
|
||||
@last = parts.last
|
||||
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(o={})
|
||||
o = super(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(o={})
|
||||
o = super(o)
|
||||
write("[#{@index.compile(o)}]")
|
||||
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 :from, :to
|
||||
|
||||
def initialize(from, to)
|
||||
@from, @to = from, to
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
write(".slice(#{@from.compile(o)}, #{@to.compile(o)} + 1)")
|
||||
end
|
||||
end
|
||||
|
||||
# Setting the value of a local variable, or the value of an object property.
|
||||
class AssignNode < Node
|
||||
LEADING_VAR = /\Avar\s+/
|
||||
|
||||
statement
|
||||
custom_return
|
||||
|
||||
attr_reader :variable, :value, :context
|
||||
|
||||
def initialize(variable, value, context=nil)
|
||||
@variable, @value, @context = variable, value, context
|
||||
end
|
||||
|
||||
def line_ending
|
||||
@value.custom_assign? ? '' : ';'
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable
|
||||
last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s
|
||||
o = o.merge(:assign => name, :last_assign => last)
|
||||
postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : ''
|
||||
return write("#{@variable}: #{@value.compile(o)}") if @context == :object
|
||||
return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign?
|
||||
defined = o[:scope].find(name)
|
||||
def_part = defined || @variable.properties? ? "" : "var #{name};\n#{o[:indent]}"
|
||||
return write(def_part + @value.compile(o)) if @value.custom_assign?
|
||||
def_part = defined ? name : "var #{name}"
|
||||
val_part = @value.compile(o).sub(LEADING_VAR, '')
|
||||
write("#{def_part} = #{val_part}#{postfix}")
|
||||
end
|
||||
end
|
||||
|
||||
# Simple Arithmetic and logical operations. Performs some conversion from
|
||||
# CoffeeScript operations into their JavaScript equivalents.
|
||||
class OpNode < Node
|
||||
CONVERSIONS = {
|
||||
"==" => "===",
|
||||
"!=" => "!==",
|
||||
'and' => '&&',
|
||||
'or' => '||',
|
||||
'is' => '===',
|
||||
"aint" => "!==",
|
||||
'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] || operator
|
||||
end
|
||||
|
||||
def unary?
|
||||
@second.nil?
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
return write(compile_conditional(o)) if CONDITIONALS.include?(@operator)
|
||||
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_s) ? ' ' : ''
|
||||
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(o={})
|
||||
o = super(o)
|
||||
o[:scope] = Scope.new(o[:scope])
|
||||
o[:return] = true
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
o.delete(:assign)
|
||||
@params.each {|id| o[:scope].find(id.to_s) }
|
||||
code = @body.compile(o)
|
||||
write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
|
||||
end
|
||||
end
|
||||
|
||||
# An object literal.
|
||||
class ObjectNode < Node
|
||||
attr_reader :properties
|
||||
|
||||
def initialize(properties = [])
|
||||
@properties = properties
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
props = @properties.map { |prop|
|
||||
joiner = prop == @properties.last ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n"
|
||||
o[:indent] + prop.compile(o) + joiner
|
||||
}.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(o={})
|
||||
o = super(o)
|
||||
objects = @objects.map { |obj|
|
||||
joiner = obj.is_a?(CommentNode) ? "\n#{o[:indent] + TAB}" : obj == @objects.last ? '' : ', '
|
||||
obj.compile(o.merge(:indent => o[:indent] + TAB)) + joiner
|
||||
}.join('')
|
||||
ending = objects.include?("\n") ? "\n#{o[: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 line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
indent = o[:indent] + TAB
|
||||
cond = @condition.compile(o.merge(:no_paren => true))
|
||||
write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}")
|
||||
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
|
||||
custom_return
|
||||
custom_assign
|
||||
|
||||
attr_reader :body, :source, :name, :filter, :index
|
||||
|
||||
def initialize(body, source, name, filter, index=nil)
|
||||
@body, @source, @name, @filter, @index = body, source, name, filter, index
|
||||
end
|
||||
|
||||
def line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
scope = o[:scope]
|
||||
name_found = scope.find(@name)
|
||||
index_found = @index && scope.find(@index)
|
||||
svar = scope.free_variable
|
||||
ivar = scope.free_variable
|
||||
lvar = scope.free_variable
|
||||
name_part = name_found ? @name : "var #{@name}"
|
||||
index_name = @index ? (index_found ? @index : "var #{@index}") : nil
|
||||
source_part = "var #{svar} = #{@source.compile(o)};"
|
||||
for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
|
||||
var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n"
|
||||
index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : ''
|
||||
|
||||
set_result = ''
|
||||
save_result = ''
|
||||
return_result = ''
|
||||
body = @body
|
||||
suffix = ';'
|
||||
if o[:return] || o[:assign]
|
||||
rvar = scope.free_variable
|
||||
set_result = "var #{rvar} = [];\n#{o[:indent]}"
|
||||
save_result += "#{rvar}[#{ivar}] = "
|
||||
return_result = rvar
|
||||
return_result = "#{o[:assign]} = #{return_result};" if o[:assign]
|
||||
return_result = "return #{return_result};" if o[:return]
|
||||
return_result = "\n#{o[:indent]}#{return_result}"
|
||||
if @filter
|
||||
body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body])
|
||||
body = IfNode.new(@filter, body, nil, :statement => true)
|
||||
save_result = ''
|
||||
suffix = ''
|
||||
end
|
||||
elsif @filter
|
||||
body = IfNode.new(@filter, @body)
|
||||
end
|
||||
|
||||
indent = o[:indent] + TAB
|
||||
body = body.compile(o.merge(:indent => indent))
|
||||
write("#{source_part}\n#{o[:indent]}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}")
|
||||
end
|
||||
end
|
||||
|
||||
# A try/catch/finally block.
|
||||
class TryNode < Node
|
||||
statement
|
||||
custom_return
|
||||
custom_assign
|
||||
|
||||
attr_reader :try, :error, :recovery, :finally
|
||||
|
||||
def initialize(try, error, recovery, finally=nil)
|
||||
@try, @error, @recovery, @finally = try, error, recovery, finally
|
||||
end
|
||||
|
||||
def line_ending
|
||||
''
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
indent = o[:indent]
|
||||
o[:indent] += TAB
|
||||
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(:assign => nil, :return => nil))}\n#{indent}}"
|
||||
write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}")
|
||||
end
|
||||
end
|
||||
|
||||
# Throw an exception.
|
||||
class ThrowNode < Node
|
||||
statement
|
||||
|
||||
attr_reader :expression
|
||||
|
||||
def initialize(expression)
|
||||
@expression = expression
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
write("throw #{@expression.compile(o)}")
|
||||
end
|
||||
end
|
||||
|
||||
# An extra set of parenthesis, supplied by the script source.
|
||||
class ParentheticalNode < Node
|
||||
attr_reader :expressions
|
||||
|
||||
def initialize(expressions)
|
||||
@expressions = expressions.unwrap
|
||||
end
|
||||
|
||||
def statement?
|
||||
@expressions.statement?
|
||||
end
|
||||
|
||||
def custom_assign?
|
||||
@expressions.custom_assign?
|
||||
end
|
||||
|
||||
def custom_return?
|
||||
@expressions.custom_return?
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(o)
|
||||
compiled = @expressions.compile(o)
|
||||
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
|
||||
write(o[:no_paren] || statement? ? compiled : "(#{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
|
||||
|
||||
# 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(expressions)
|
||||
chain? ? @else_body.add_else(expressions) : @else_body = expressions
|
||||
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 custom_return?
|
||||
statement?
|
||||
end
|
||||
|
||||
def custom_assign?
|
||||
statement?
|
||||
end
|
||||
|
||||
def line_ending
|
||||
statement? ? '' : ';'
|
||||
end
|
||||
|
||||
def compile(o={})
|
||||
o = super(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]
|
||||
o[:indent] += TAB
|
||||
if_part = "if (#{@condition.compile(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))}" :
|
||||
" 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
|
||||
22
lib/coffee_script/parse_error.rb
Normal file
22
lib/coffee_script/parse_error.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
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(source_file=nil)
|
||||
line = @value.respond_to?(:line) ? @value.line : "END"
|
||||
line_part = source_file ? "#{source_file}:#{line}:" : "line #{line}:"
|
||||
id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.downcase}" : ""
|
||||
"#{line_part} syntax error for '#{@value.to_s}'#{id_part}"
|
||||
end
|
||||
alias_method :inspect, :message
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
2016
lib/coffee_script/parser.rb
Normal file
2016
lib/coffee_script/parser.rb
Normal file
File diff suppressed because it is too large
Load Diff
45
lib/coffee_script/scope.rb
Normal file
45
lib/coffee_script/scope.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
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, :temp_variable
|
||||
|
||||
# Initialize a scope with its parent, for lookups up the chain.
|
||||
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.to_sym] = true
|
||||
found
|
||||
end
|
||||
|
||||
# Just check to see if a variable has already been declared.
|
||||
def check(name, remote=false)
|
||||
return true if @variables[name.to_sym]
|
||||
@parent && @parent.find(name, true)
|
||||
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] = true
|
||||
@temp_variable.dup
|
||||
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
|
||||
@@ -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
|
||||
14
test/fixtures/each.cs
vendored
Normal file
14
test/fixtures/each.cs
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# The cornerstone, an each implementation.
|
||||
# Handles objects implementing forEach, arrays, and raw objects.
|
||||
_.each: obj, iterator, context =>
|
||||
index: 0
|
||||
try
|
||||
if obj.forEach
|
||||
obj.forEach(iterator, context)
|
||||
else if _.isArray(obj) or _.isArguments(obj)
|
||||
iterator.call(context, item, i, obj) for item, i in obj.
|
||||
else
|
||||
iterator.call(context, obj[key], key, obj) for key in _.keys(obj)..
|
||||
catch e
|
||||
throw e if e aint breaker.
|
||||
obj.
|
||||
31
test/fixtures/each.js
vendored
Normal file
31
test/fixtures/each.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
(function(){
|
||||
|
||||
// The cornerstone, an each implementation.
|
||||
// Handles objects implementing forEach, arrays, and raw objects.
|
||||
_.each = function(obj, iterator, context) {
|
||||
var index = 0;
|
||||
try {
|
||||
if (obj.forEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (_.isArray(obj) || _.isArguments(obj)) {
|
||||
var a = obj;
|
||||
for (var b=0, c=a.length; b<c; b++) {
|
||||
var item = a[b];
|
||||
var i = b;
|
||||
iterator.call(context, item, i, obj);
|
||||
}
|
||||
} else {
|
||||
var d = _.keys(obj);
|
||||
for (var e=0, f=d.length; e<f; e++) {
|
||||
var key = d[e];
|
||||
iterator.call(context, obj[key], key, obj);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e !== breaker) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
})();
|
||||
1
test/fixtures/each.tokens
vendored
Normal file
1
test/fixtures/each.tokens
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]]
|
||||
4
test/fixtures/execution/array_comprehension.cs
vendored
Normal file
4
test/fixtures/execution/array_comprehension.cs
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
nums: n * n for n in [1, 2, 3] if n % 2 aint 0.
|
||||
result: n * 2 for n in nums.
|
||||
|
||||
print(result.join(',') is '2,18')
|
||||
21
test/fixtures/execution/array_comprehension.js
vendored
Normal file
21
test/fixtures/execution/array_comprehension.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
(function(){
|
||||
var nums;
|
||||
var a = [1, 2, 3];
|
||||
var d = [];
|
||||
for (var b=0, c=a.length; b<c; b++) {
|
||||
var n = a[b];
|
||||
if (n % 2 !== 0) {
|
||||
nums = d.push(n * n);
|
||||
}
|
||||
}
|
||||
nums = d;
|
||||
var result;
|
||||
var e = nums;
|
||||
var h = [];
|
||||
for (var f=0, g=e.length; f<g; f++) {
|
||||
n = e[f];
|
||||
h[f] = n * 2;
|
||||
}
|
||||
result = h;
|
||||
print(result.join(',') === '2,18');
|
||||
})();
|
||||
6
test/fixtures/execution/assign_to_try_catch.cs
vendored
Normal file
6
test/fixtures/execution/assign_to_try_catch.cs
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
result: try
|
||||
nonexistent * missing
|
||||
catch error
|
||||
true.
|
||||
|
||||
print(result)
|
||||
9
test/fixtures/execution/assign_to_try_catch.js
vendored
Normal file
9
test/fixtures/execution/assign_to_try_catch.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
(function(){
|
||||
var result;
|
||||
try {
|
||||
result = nonexistent * missing;
|
||||
} catch (error) {
|
||||
result = true;
|
||||
}
|
||||
print(result);
|
||||
})();
|
||||
11
test/fixtures/execution/fancy_if_statement.cs
vendored
Normal file
11
test/fixtures/execution/fancy_if_statement.cs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
a: b: d: true
|
||||
c: false
|
||||
|
||||
result: if a
|
||||
if b
|
||||
if c then false
|
||||
else
|
||||
if d
|
||||
true....
|
||||
|
||||
print(result)
|
||||
6
test/fixtures/execution/fancy_if_statement.js
vendored
Normal file
6
test/fixtures/execution/fancy_if_statement.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
(function(){
|
||||
var a = b = d = true;
|
||||
var c = false;
|
||||
var result = a ? b ? c ? false : d ? true : null : null : null;
|
||||
print(result);
|
||||
})();
|
||||
10
test/fixtures/execution/keyword_operators.cs
vendored
Normal file
10
test/fixtures/execution/keyword_operators.cs
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
a: 5
|
||||
atype: typeof a
|
||||
|
||||
b: "hello"
|
||||
btype: typeof b
|
||||
|
||||
Klass: => .
|
||||
k: new Klass()
|
||||
|
||||
print(atype is 'number' and btype is 'string' and k instanceof Klass)
|
||||
10
test/fixtures/execution/keyword_operators.js
vendored
Normal file
10
test/fixtures/execution/keyword_operators.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
(function(){
|
||||
var a = 5;
|
||||
var atype = typeof a;
|
||||
var b = "hello";
|
||||
var btype = typeof b;
|
||||
var Klass = function() {
|
||||
};
|
||||
var k = new Klass();
|
||||
print(atype === 'number' && btype === 'string' && k instanceof Klass);
|
||||
})();
|
||||
23
test/fixtures/execution/test_everything.cs
vendored
Normal file
23
test/fixtures/execution/test_everything.cs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
func: =>
|
||||
a: 3
|
||||
b: []
|
||||
while a >= 0
|
||||
b.push('o')
|
||||
a--.
|
||||
|
||||
c: {
|
||||
text: b
|
||||
}
|
||||
|
||||
c: 'error' unless 42 > 41
|
||||
|
||||
c.text: if false
|
||||
'error'
|
||||
else
|
||||
c.text + '---'.
|
||||
|
||||
c.list: let for let in c.text.split('') if let is '-'.
|
||||
|
||||
c.single: c.list[1, 1][0].
|
||||
|
||||
print(func() == '-')
|
||||
29
test/fixtures/execution/test_everything.js
vendored
Normal file
29
test/fixtures/execution/test_everything.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
(function(){
|
||||
var func = function() {
|
||||
var a = 3;
|
||||
var b = [];
|
||||
while (a >= 0) {
|
||||
b.push('o');
|
||||
a--;
|
||||
}
|
||||
var c = {
|
||||
text: b
|
||||
};
|
||||
if (!(42 > 41)) {
|
||||
c = 'error';
|
||||
}
|
||||
c.text = false ? 'error' : c.text + '---';
|
||||
var d = c.text.split('');
|
||||
var g = [];
|
||||
for (var e=0, f=d.length; e<f; e++) {
|
||||
var let = d[e];
|
||||
if (let === '-') {
|
||||
c.list = g.push(let);
|
||||
}
|
||||
}
|
||||
c.list = g;
|
||||
c.single = c.list.slice(1, 1 + 1)[0];
|
||||
return c.single;
|
||||
};
|
||||
print(func() === '-');
|
||||
})();
|
||||
11
test/fixtures/execution/test_switch.cs
vendored
Normal file
11
test/fixtures/execution/test_switch.cs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
num: 10
|
||||
|
||||
result: switch num
|
||||
when 5 then false
|
||||
when 'a'
|
||||
false
|
||||
when 10 then true
|
||||
when 11 then false
|
||||
else false.
|
||||
|
||||
print(result)
|
||||
16
test/fixtures/execution/test_switch.js
vendored
Normal file
16
test/fixtures/execution/test_switch.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
(function(){
|
||||
var num = 10;
|
||||
var result;
|
||||
if (num === 5) {
|
||||
result = false;
|
||||
} else if (num === 'a') {
|
||||
result = false;
|
||||
} else if (num === 10) {
|
||||
result = true;
|
||||
} else if (num === 11) {
|
||||
result = false;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
print(result);
|
||||
})();
|
||||
15
test/fixtures/inner_comments.cs
vendored
Normal file
15
test/fixtures/inner_comments.cs
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
object: {
|
||||
a: 1
|
||||
# Comments between the elements.
|
||||
b: 2
|
||||
# Like this.
|
||||
c: 3
|
||||
}
|
||||
|
||||
array: [
|
||||
1
|
||||
# Comments between the elements.
|
||||
2
|
||||
# Like this.
|
||||
3
|
||||
]
|
||||
15
test/fixtures/inner_comments.js
vendored
Normal file
15
test/fixtures/inner_comments.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
(function(){
|
||||
var object = {
|
||||
a: 1,
|
||||
// Comments between the elements.
|
||||
b: 2,
|
||||
// Like this.
|
||||
c: 3
|
||||
};
|
||||
var array = [1,
|
||||
// Comments between the elements.
|
||||
2,
|
||||
// Like this.
|
||||
3
|
||||
];
|
||||
})();
|
||||
5
test/test_helper.rb
Normal file
5
test/test_helper.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
require 'lib/coffee-script'
|
||||
|
||||
class Test::Unit::TestCase
|
||||
include CoffeeScript
|
||||
end
|
||||
30
test/unit/test_execution.rb
Normal file
30
test/unit/test_execution.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ExecutionTest < Test::Unit::TestCase
|
||||
|
||||
NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/
|
||||
|
||||
def test_execution_of_coffeescript
|
||||
`bin/coffee-script test/fixtures/execution/*.cs`
|
||||
sources = Dir['test/fixtures/execution/*.js'].map {|f| File.expand_path(f) }
|
||||
starting_place = File.expand_path(Dir.pwd)
|
||||
Dir.chdir('/Users/jashkenas/Desktop/Beauty/Code/v8')
|
||||
sources.each do |source|
|
||||
# puts `./shell #{source}`
|
||||
assert `./shell #{source}`.chomp.to_sym == :true
|
||||
end
|
||||
ensure
|
||||
Dir.chdir(starting_place)
|
||||
end
|
||||
|
||||
def test_lintless_coffeescript
|
||||
lint_results = `bin/coffee-script -l test/fixtures/execution/*.cs`
|
||||
assert lint_results.match(NO_WARNINGS)
|
||||
end
|
||||
|
||||
def test_lintless_examples
|
||||
lint_results = `bin/coffee-script -l examples/*.cs`
|
||||
assert lint_results.match(NO_WARNINGS)
|
||||
end
|
||||
|
||||
end
|
||||
50
test/unit/test_lexer.rb
Normal file
50
test/unit/test_lexer.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
require 'test_helper'
|
||||
|
||||
class LexerTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@lex = Lexer.new
|
||||
end
|
||||
|
||||
def test_lexing_an_empty_string
|
||||
assert @lex.tokenize("") == []
|
||||
end
|
||||
|
||||
def test_lexing_basic_assignment
|
||||
code = "a: 'one'; b: [1, 2]"
|
||||
assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"],
|
||||
[:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [":", ":"],
|
||||
["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"]]
|
||||
end
|
||||
|
||||
def test_lexing_object_literal
|
||||
code = "{one : 1}"
|
||||
assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [":", ":"],
|
||||
[:NUMBER, "1"], ["}", "}"]]
|
||||
end
|
||||
|
||||
def test_lexing_function_definition
|
||||
code = "x, y => x * y."
|
||||
assert @lex.tokenize(code) == [[:PARAM, "x"], [",", ","], [:PARAM, "y"],
|
||||
["=>", "=>"], [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "y"], [".", "."]]
|
||||
end
|
||||
|
||||
def test_lexing_if_statement
|
||||
code = "clap_your_hands() if happy"
|
||||
assert @lex.tokenize(code) == [[:IDENTIFIER, "clap_your_hands"], ["(", "("],
|
||||
[")", ")"], [:IF, "if"], [:IDENTIFIER, "happy"]]
|
||||
end
|
||||
|
||||
def test_lexing_comment
|
||||
code = "a: 1\n # comment\n # on two lines\nb: 2"
|
||||
assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], [:NUMBER, "1"],
|
||||
["\n", "\n"], [:COMMENT, [" comment", " on two lines"]], ["\n", "\n"],
|
||||
[:IDENTIFIER, "b"], [":", ":"], [:NUMBER, "2"]]
|
||||
end
|
||||
|
||||
def test_lexing
|
||||
tokens = @lex.tokenize(File.read('test/fixtures/each.cs'))
|
||||
assert tokens.inspect == File.read('test/fixtures/each.tokens')
|
||||
end
|
||||
|
||||
end
|
||||
75
test/unit/test_parser.rb
Normal file
75
test/unit/test_parser.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ParserTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@par = Parser.new
|
||||
end
|
||||
|
||||
def test_parsing_an_empty_string
|
||||
nodes = @par.parse("")
|
||||
assert nodes.is_a? Expressions
|
||||
assert nodes.expressions.empty?
|
||||
end
|
||||
|
||||
def test_parsing_a_basic_assignment
|
||||
nodes = @par.parse("a: 'one'").expressions
|
||||
assert nodes.length == 1
|
||||
assign = nodes.first
|
||||
assert assign.is_a? AssignNode
|
||||
assert assign.variable.literal == 'a'
|
||||
end
|
||||
|
||||
def test_parsing_an_object_literal
|
||||
nodes = @par.parse("{one : 1 \n two : 2}").expressions
|
||||
obj = nodes.first.literal
|
||||
assert obj.is_a? ObjectNode
|
||||
assert obj.properties.first.variable == "one"
|
||||
assert obj.properties.last.variable == "two"
|
||||
end
|
||||
|
||||
def test_parsing_an_function_definition
|
||||
code = @par.parse("x, y => x * y.").expressions.first
|
||||
assert code.params == ['x', 'y']
|
||||
body = code.body.expressions.first
|
||||
assert body.is_a? OpNode
|
||||
assert body.operator == '*'
|
||||
end
|
||||
|
||||
def test_parsing_if_statement
|
||||
the_if = @par.parse("clap_your_hands() if happy").expressions.first
|
||||
assert the_if.is_a? IfNode
|
||||
assert the_if.condition.literal == 'happy'
|
||||
assert the_if.body.is_a? CallNode
|
||||
assert the_if.body.variable.literal == 'clap_your_hands'
|
||||
end
|
||||
|
||||
def test_parsing_array_comprehension
|
||||
nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] if i % 2 is 0.").expressions
|
||||
assert nodes.first.is_a? ForNode
|
||||
assert nodes.first.body.literal == 'i'
|
||||
assert nodes.first.filter.operator == '==='
|
||||
assert nodes.first.source.literal.objects.last.value == "5"
|
||||
end
|
||||
|
||||
def test_parsing_comment
|
||||
nodes = @par.parse("a: 1\n # comment\nb: 2").expressions
|
||||
assert nodes[1].is_a? CommentNode
|
||||
end
|
||||
|
||||
def test_parsing_inner_comments
|
||||
nodes = @par.parse(File.read('test/fixtures/inner_comments.cs'))
|
||||
assert nodes.compile == File.read('test/fixtures/inner_comments.js')
|
||||
end
|
||||
|
||||
def test_parsing
|
||||
nodes = @par.parse(File.read('test/fixtures/each.cs'))
|
||||
assign = nodes.expressions[1]
|
||||
assert assign.is_a? AssignNode
|
||||
assert assign.variable.literal == '_'
|
||||
assert assign.value.is_a? CodeNode
|
||||
assert assign.value.params == ['obj', 'iterator', 'context']
|
||||
assert nodes.compile == File.read('test/fixtures/each.js')
|
||||
end
|
||||
|
||||
end
|
||||
607
underscore.cs
607
underscore.cs
@@ -1,607 +0,0 @@
|
||||
# Underscore.js
|
||||
# (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.
|
||||
|
||||
# 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.
|
||||
exports._: _ if typeof exports aint 'undefined'
|
||||
|
||||
# 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.1'
|
||||
|
||||
# ------------------------ Collection Functions: ---------------------------
|
||||
|
||||
# The cornerstone, an each implementation.
|
||||
# Handles objects implementing forEach, arrays, and raw objects.
|
||||
_.each: obj, iterator, context =>
|
||||
index: 0
|
||||
try
|
||||
if obj.forEach then return obj.forEach(iterator, context).
|
||||
if _.isArray(obj) or _.isArguments(obj) then return iterator.call(context, item, i, obj) for item, i in obj..
|
||||
iterator.call(context, obj[key], key, obj) for key in _.keys(obj).
|
||||
catch e
|
||||
throw e if e aint 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 = function(obj, memo, iterator, context) {
|
||||
if (obj && _.isFunction(obj.reduceRight)) return obj.reduceRight(_.bind(iterator, context), memo);
|
||||
var reversed = _.clone(_.toArray(obj)).reverse();
|
||||
_.each(reversed, function(value, index) {
|
||||
memo = iterator.call(context, memo, value, index, obj);
|
||||
});
|
||||
return memo;
|
||||
};
|
||||
|
||||
# Return the first value which passes a truth test.
|
||||
_.detect = function(obj, iterator, context) {
|
||||
var result;
|
||||
_.each(obj, function(value, index, list) {
|
||||
if (iterator.call(context, value, index, list)) {
|
||||
result = value;
|
||||
_.breakLoop();
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
# Return all the elements that pass a truth test. Use JavaScript 1.6's
|
||||
# filter(), if it exists.
|
||||
_.select = function(obj, iterator, context) {
|
||||
if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context);
|
||||
var results = [];
|
||||
_.each(obj, function(value, index, list) {
|
||||
iterator.call(context, value, index, list) && results.push(value);
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
# Return all the elements for which a truth test fails.
|
||||
_.reject = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
_.each(obj, function(value, index, list) {
|
||||
!iterator.call(context, value, index, list) && results.push(value);
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
# Determine whether all of the elements match a truth test. Delegate to
|
||||
# JavaScript 1.6's every(), if it is present.
|
||||
_.all = function(obj, iterator, context) {
|
||||
iterator = iterator || _.identity;
|
||||
if (obj && _.isFunction(obj.every)) return obj.every(iterator, context);
|
||||
var result = true;
|
||||
_.each(obj, function(value, index, list) {
|
||||
if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop();
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
# Determine if at least one element in the object matches a truth test. Use
|
||||
# JavaScript 1.6's some(), if it exists.
|
||||
_.any = function(obj, iterator, context) {
|
||||
iterator = iterator || _.identity;
|
||||
if (obj && _.isFunction(obj.some)) return obj.some(iterator, context);
|
||||
var result = false;
|
||||
_.each(obj, function(value, index, list) {
|
||||
if (result = iterator.call(context, value, index, list)) _.breakLoop();
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
# Determine if a given value is included in the array or object,
|
||||
# based on '==='.
|
||||
_.include = function(obj, target) {
|
||||
if (_.isArray(obj)) return _.indexOf(obj, target) != -1;
|
||||
var found = false;
|
||||
_.each(obj, function(value) {
|
||||
if (found = value === target) _.breakLoop();
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
# Invoke a method with arguments on every item in a collection.
|
||||
_.invoke = function(obj, method) {
|
||||
var args = _.rest(arguments, 2);
|
||||
return _.map(obj, function(value) {
|
||||
return (method ? value[method] : value).apply(value, args);
|
||||
});
|
||||
};
|
||||
|
||||
# Convenience version of a common use case of map: fetching a property.
|
||||
_.pluck = function(obj, key) {
|
||||
return _.map(obj, function(value){ return value[key]; });
|
||||
};
|
||||
|
||||
# Return the maximum item or (item-based computation).
|
||||
_.max = function(obj, iterator, context) {
|
||||
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
|
||||
var result = {computed : -Infinity};
|
||||
_.each(obj, function(value, index, list) {
|
||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
computed >= result.computed && (result = {value : value, computed : computed});
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
# Return the minimum element (or element-based computation).
|
||||
_.min = function(obj, iterator, context) {
|
||||
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
|
||||
var result = {computed : Infinity};
|
||||
_.each(obj, function(value, index, list) {
|
||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
computed < result.computed && (result = {value : value, computed : computed});
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
# Sort the object's values by a criteria produced by an iterator.
|
||||
_.sortBy = function(obj, iterator, context) {
|
||||
return _.pluck(_.map(obj, function(value, index, list) {
|
||||
return {
|
||||
value : value,
|
||||
criteria : iterator.call(context, value, index, list)
|
||||
};
|
||||
}).sort(function(left, right) {
|
||||
var a = left.criteria, b = right.criteria;
|
||||
return a < b ? -1 : a > b ? 1 : 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 = function(array, obj, iterator) {
|
||||
iterator = iterator || _.identity;
|
||||
var low = 0, high = array.length;
|
||||
while (low < high) {
|
||||
var mid = (low + high) >> 1;
|
||||
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
|
||||
}
|
||||
return low;
|
||||
};
|
||||
|
||||
# Convert anything iterable into a real, live array.
|
||||
_.toArray = function(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
if (_.isArray(iterable)) return iterable;
|
||||
if (_.isArguments(iterable)) return slice.call(iterable);
|
||||
return _.map(iterable, function(val){ return val; });
|
||||
};
|
||||
|
||||
# Return the number of elements in an object.
|
||||
_.size = function(obj) {
|
||||
return _.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 = function(array, n, guard) {
|
||||
return n && !guard ? slice.call(array, 0, n) : 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 = function(array, index, guard) {
|
||||
return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
|
||||
};
|
||||
|
||||
# Get the last element of an array.
|
||||
_.last = function(array) {
|
||||
return array[array.length - 1];
|
||||
};
|
||||
|
||||
# Trim out all falsy values from an array.
|
||||
_.compact = function(array) {
|
||||
return _.select(array, function(value){ return !!value; });
|
||||
};
|
||||
|
||||
# Return a completely flattened version of an array.
|
||||
_.flatten = function(array) {
|
||||
return _.reduce(array, [], function(memo, value) {
|
||||
if (_.isArray(value)) return memo.concat(_.flatten(value));
|
||||
memo.push(value);
|
||||
return memo;
|
||||
});
|
||||
};
|
||||
|
||||
# Return a version of the array that does not contain the specified value(s).
|
||||
_.without = function(array) {
|
||||
var values = _.rest(arguments);
|
||||
return _.select(array, function(value){ return !_.include(values, value); });
|
||||
};
|
||||
|
||||
# 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 = function(array, isSorted) {
|
||||
return _.reduce(array, [], function(memo, el, i) {
|
||||
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
|
||||
return memo;
|
||||
});
|
||||
};
|
||||
|
||||
# Produce an array that contains every item shared between all the
|
||||
# passed-in arrays.
|
||||
_.intersect = function(array) {
|
||||
var rest = _.rest(arguments);
|
||||
return _.select(_.uniq(array), function(item) {
|
||||
return _.all(rest, function(other) {
|
||||
return _.indexOf(other, item) >= 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
# Zip together multiple lists into a single array -- elements that share
|
||||
# an index go together.
|
||||
_.zip = function() {
|
||||
var args = _.toArray(arguments);
|
||||
var length = _.max(_.pluck(args, 'length'));
|
||||
var results = new Array(length);
|
||||
for (var i=0; i<length; i++) results[i] = _.pluck(args, String(i));
|
||||
return 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 = function(array, item) {
|
||||
if (array.indexOf) return array.indexOf(item);
|
||||
for (var i=0, l=array.length; i<l; i++) if (array[i] === item) return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
# Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
|
||||
# if possible.
|
||||
_.lastIndexOf = function(array, item) {
|
||||
if (array.lastIndexOf) return array.lastIndexOf(item);
|
||||
var i = array.length;
|
||||
while (i--) if (array[i] === item) return i;
|
||||
return -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 = function(start, stop, step) {
|
||||
var a = _.toArray(arguments);
|
||||
var solo = a.length <= 1;
|
||||
var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1;
|
||||
var len = Math.ceil((stop - start) / step);
|
||||
if (len <= 0) return [];
|
||||
var range = new Array(len);
|
||||
for (var i = start, idx = 0; true; i += step) {
|
||||
if ((step > 0 ? i - stop : stop - i) >= 0) return range;
|
||||
range[idx++] = i;
|
||||
}
|
||||
};
|
||||
|
||||
/* ----------------------- Function Functions: -----------------------------*/
|
||||
|
||||
# Create a function bound to a given object (assigning 'this', and arguments,
|
||||
# optionally). Binding with arguments is also known as 'curry'.
|
||||
_.bind = function(func, obj) {
|
||||
var args = _.rest(arguments, 2);
|
||||
return function() {
|
||||
return func.apply(obj || 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 = function(obj) {
|
||||
var funcs = _.rest(arguments);
|
||||
if (funcs.length == 0) funcs = _.functions(obj);
|
||||
_.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
|
||||
return obj;
|
||||
};
|
||||
|
||||
# Delays a function for the given number of milliseconds, and then calls
|
||||
# it with the arguments supplied.
|
||||
_.delay = function(func, wait) {
|
||||
var args = _.rest(arguments, 2);
|
||||
return setTimeout(function(){ return func.apply(func, args); }, wait);
|
||||
};
|
||||
|
||||
# Defers a function, scheduling it to run after the current call stack has
|
||||
# cleared.
|
||||
_.defer = function(func) {
|
||||
return _.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 = function(func, wrapper) {
|
||||
return function() {
|
||||
var args = [func].concat(_.toArray(arguments));
|
||||
return wrapper.apply(wrapper, args);
|
||||
};
|
||||
};
|
||||
|
||||
# Returns a function that is the composition of a list of functions, each
|
||||
# consuming the return value of the function that follows.
|
||||
_.compose = function() {
|
||||
var funcs = _.toArray(arguments);
|
||||
return function() {
|
||||
var args = _.toArray(arguments);
|
||||
for (var i=funcs.length-1; i >= 0; i--) {
|
||||
args = [funcs[i].apply(this, args)];
|
||||
}
|
||||
return args[0];
|
||||
};
|
||||
};
|
||||
|
||||
/* ------------------------- Object Functions: ---------------------------- */
|
||||
|
||||
# Retrieve the names of an object's properties.
|
||||
_.keys = function(obj) {
|
||||
if(_.isArray(obj)) return _.range(0, obj.length);
|
||||
var keys = [];
|
||||
for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
|
||||
return keys;
|
||||
};
|
||||
|
||||
# Retrieve the values of an object's properties.
|
||||
_.values = function(obj) {
|
||||
return _.map(obj, _.identity);
|
||||
};
|
||||
|
||||
# Return a sorted list of the function names available in Underscore.
|
||||
_.functions = function(obj) {
|
||||
return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
|
||||
};
|
||||
|
||||
# Extend a given object with all of the properties in a source object.
|
||||
_.extend = function(destination, source) {
|
||||
for (var property in source) destination[property] = source[property];
|
||||
return destination;
|
||||
};
|
||||
|
||||
# Create a (shallow-cloned) duplicate of an object.
|
||||
_.clone = function(obj) {
|
||||
if (_.isArray(obj)) return obj.slice(0);
|
||||
return _.extend({}, obj);
|
||||
};
|
||||
|
||||
# Perform a deep comparison to check if two objects are equal.
|
||||
_.isEqual = function(a, b) {
|
||||
# Check object identity.
|
||||
if (a === b) return true;
|
||||
# Different types?
|
||||
var atype = typeof(a), btype = typeof(b);
|
||||
if (atype != btype) return false;
|
||||
# Basic equality test (watch out for coercions).
|
||||
if (a == b) return true;
|
||||
# One is falsy and the other truthy.
|
||||
if ((!a && b) || (a && !b)) return false;
|
||||
# One of them implements an isEqual()?
|
||||
if (a.isEqual) return a.isEqual(b);
|
||||
# Check dates' integer values.
|
||||
if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
|
||||
# Both are NaN?
|
||||
if (_.isNaN(a) && _.isNaN(b)) return true;
|
||||
# Compare regular expressions.
|
||||
if (_.isRegExp(a) && _.isRegExp(b))
|
||||
return a.source === b.source &&
|
||||
a.global === b.global &&
|
||||
a.ignoreCase === b.ignoreCase &&
|
||||
a.multiline === b.multiline;
|
||||
# If a is not an object by this point, we can't handle it.
|
||||
if (atype !== 'object') return false;
|
||||
# Check for different array lengths before comparing contents.
|
||||
if (a.length && (a.length !== b.length)) return false;
|
||||
# Nothing else worked, deep compare the contents.
|
||||
var aKeys = _.keys(a), bKeys = _.keys(b);
|
||||
# Different object sizes?
|
||||
if (aKeys.length != bKeys.length) return false;
|
||||
# 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 = function(obj) {
|
||||
return _.keys(obj).length == 0;
|
||||
};
|
||||
|
||||
# Is a given value a DOM element?
|
||||
_.isElement = function(obj) {
|
||||
return !!(obj && obj.nodeType == 1);
|
||||
};
|
||||
|
||||
# Is a given variable an arguments object?
|
||||
_.isArguments = function(obj) {
|
||||
return obj && _.isNumber(obj.length) && !_.isArray(obj) && !propertyIsEnumerable.call(obj, 'length');
|
||||
};
|
||||
|
||||
# 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 = function(obj) {
|
||||
return _.isNumber(obj) && isNaN(obj);
|
||||
};
|
||||
|
||||
# Is a given value equal to null?
|
||||
_.isNull = function(obj) {
|
||||
return obj === null;
|
||||
};
|
||||
|
||||
# Is a given variable undefined?
|
||||
_.isUndefined = function(obj) {
|
||||
return typeof obj == 'undefined';
|
||||
};
|
||||
|
||||
# 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 = function(obj, interceptor) {
|
||||
interceptor(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
# Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString
|
||||
# functions based on their toString identifiers.
|
||||
var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String'];
|
||||
for (var i=0, l=types.length; i<l; i++) {
|
||||
(function() {
|
||||
var identifier = '[object ' + types[i] + ']';
|
||||
_['is' + types[i]] = function(obj) { return toString.call(obj) == identifier; };
|
||||
})();
|
||||
}
|
||||
|
||||
/* -------------------------- Utility Functions: -------------------------- */
|
||||
|
||||
# Run Underscore.js in noConflict mode, returning the '_' variable to its
|
||||
# previous owner. Returns a reference to the Underscore object.
|
||||
_.noConflict = function() {
|
||||
root._ = previousUnderscore;
|
||||
return this;
|
||||
};
|
||||
|
||||
# Keep the identity function around for default iterators.
|
||||
_.identity = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
# Break out of the middle of an iteration.
|
||||
_.breakLoop = function() {
|
||||
throw breaker;
|
||||
};
|
||||
|
||||
# Generate a unique integer id (unique within the entire client session).
|
||||
# Useful for temporary DOM ids.
|
||||
var idCounter = 0;
|
||||
_.uniqueId = function(prefix) {
|
||||
var id = idCounter++;
|
||||
return prefix ? prefix + id : id;
|
||||
};
|
||||
|
||||
# JavaScript templating a-la ERB, pilfered from John Resig's
|
||||
# "Secrets of the JavaScript Ninja", page 83.
|
||||
_.template = function(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('');");
|
||||
return data ? fn(data) : 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.
|
||||
var result = function(obj, chain) {
|
||||
return chain ? _(obj).chain() : obj;
|
||||
};
|
||||
|
||||
# Add all of the Underscore functions to the wrapper object.
|
||||
_.each(_.functions(_), function(name) {
|
||||
var method = _[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
unshift.call(arguments, this._wrapped);
|
||||
return result(method.apply(_, arguments), this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
# Add all mutator Array functions to the wrapper.
|
||||
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
||||
var method = Array.prototype[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
method.apply(this._wrapped, arguments);
|
||||
return result(this._wrapped, this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
# Add all accessor Array functions to the wrapper.
|
||||
_.each(['concat', 'join', 'slice'], function(name) {
|
||||
var method = Array.prototype[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
return result(method.apply(this._wrapped, arguments), this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
# Start chaining a wrapped Underscore object.
|
||||
wrapper.prototype.chain = function() {
|
||||
this._chain = true;
|
||||
return this;
|
||||
};
|
||||
|
||||
# Extracts the result from a wrapped and chained object.
|
||||
wrapper.prototype.value = function() {
|
||||
return this._wrapped;
|
||||
};
|
||||
|
||||
()
|
||||
Reference in New Issue
Block a user