Compare commits

...

100 Commits
0.5.3 ... 0.1.3

Author SHA1 Message Date
Jeremy Ashkenas
859ab7583f bumping to 0.1.3 ... here we go 2009-12-25 00:16:56 -08:00
Jeremy Ashkenas
3eedd5bb50 better error warnings on the command line 2009-12-25 00:02:27 -08:00
Jeremy Ashkenas
5b7e695f6c removed bin/cs in favor of a more comprehensive coffee-script command ... now with --interactive and --run 2009-12-24 23:57:27 -08:00
Jeremy Ashkenas
1e3182727b majorly cleaned up the CoffeeScript that defines the Narwhal integration 2009-12-24 23:28:01 -08:00
Jeremy Ashkenas
e595dbfcee the narwhal integration written in JavaScript has been replaced with CoffeeScript, and compiler-generated variable names now start with '__' 2009-12-24 23:09:24 -08:00
Jeremy Ashkenas
12b830893d sped up the execution test a good deal by running it all in one pass 2009-12-24 22:29:30 -08:00
Jeremy Ashkenas
cca80342aa making all assignment-y operators use a colon -- now it's +: -: *: /:, and friends 2009-12-24 22:25:29 -08:00
Jeremy Ashkenas
4412f590cf removed dependency on v8 in favor of bin/cs 2009-12-24 22:08:32 -08:00
tlrobinson
0cd2d78027 Print compiler errors to stderr 2009-12-24 19:34:17 -08:00
Jeremy Ashkenas
be672ebfc1 fixed the bin/cs repl to save assignment between commands by using the new --no-wrap 2009-12-24 17:45:23 -08:00
Jeremy Ashkenas
9047c87567 the --no-wrap option now disables top-level var declarations 2009-12-24 17:37:24 -08:00
Jeremy Ashkenas
31d630bb91 updating docs for isnt 2009-12-24 17:22:46 -08:00
Jeremy Ashkenas
7a2f5a333f trading aint for isnt -- let's be serious 2009-12-24 17:21:20 -08:00
Jeremy Ashkenas
66303636dc allowing quoted strings within object assignment, a in JS and JSON 2009-12-24 17:14:53 -08:00
Jeremy Ashkenas
9dc932e380 bumping to 0.1.2 to get the super()/extends fix out there 2009-12-24 17:05:55 -08:00
Jeremy Ashkenas
a71de4b5b6 got extends back in the language -- use it together with super 2009-12-24 16:49:23 -08:00
Jeremy Ashkenas
ada8dfc6d4 fixing super() calls, thanks to tolmasky 2009-12-24 16:23:23 -08:00
Jeremy Ashkenas
4112595368 removing the special-case std-reading in favor of '--eval' 2009-12-24 15:49:42 -08:00
Jeremy Ashkenas
c281db7730 document that -e can read from stdin 2009-12-24 15:35:58 -08:00
Jeremy Ashkenas
5e6b49ad1e with a working -n --no-wrap option to disable the top-level function safety wrapper 2009-12-24 15:31:00 -08:00
Jeremy Ashkenas
ec1a527575 Merge branch 'master' of git://github.com/tlrobinson/coffee-script 2009-12-24 15:05:56 -08:00
tlrobinson
dc0ab1afca Command line CoffeeScript 2009-12-24 14:42:57 -08:00
tlrobinson
ae72fbfd0d Narwhal support for CoffeeScript 2009-12-24 14:41:35 -08:00
tlrobinson
ad0735f765 Read from stdin if source is "-" 2009-12-24 14:40:39 -08:00
Jeremy Ashkenas
d49c178f1d added and -> &&, or -> || to the docs (they were missing) 2009-12-24 14:37:30 -08:00
Jeremy Ashkenas
8f8ba255b3 docs for 0.1.1 2009-12-24 12:02:28 -08:00
Jeremy Ashkenas
a4bc24817d bumping to 0.1.1 2009-12-24 11:59:19 -08:00
Jeremy Ashkenas
9eeac9b272 added the typeof operater as an OpNode 2009-12-24 11:50:44 -08:00
Jeremy Ashkenas
f859eb6cec added the instanceof operator to the grammar as an operation node 2009-12-24 11:46:51 -08:00
Jeremy Ashkenas
b29afc2c09 another wish 2009-12-24 10:31:44 -08:00
Jeremy Ashkenas
95b79973f9 docs 2009-12-24 09:56:44 -08:00
Jeremy Ashkenas
1f79733a33 added a wish list to the docs 2009-12-24 09:54:12 -08:00
Jeremy Ashkenas
cfc29f7830 doc tweaks 2009-12-24 01:38:32 -08:00
Jeremy Ashkenas
34486039e1 changing switch/case to switch/when -- it's a better word 2009-12-24 01:33:59 -08:00
Jeremy Ashkenas
53e8ea7d9e added comprehensive linting to the test suit 2009-12-24 00:49:11 -08:00
Jeremy Ashkenas
9841b78ed8 fixed the broken try/catch grammar 2009-12-24 00:45:16 -08:00
Jeremy Ashkenas
065cfddd0d with a more comprehensive execution test that uncovered some missing spots 2009-12-24 00:41:12 -08:00
Jeremy Ashkenas
6882a3d36c added some execution test 2009-12-24 00:12:07 -08:00
Jeremy Ashkenas
85c595e84c added readme 2009-12-23 23:12:29 -08:00
Jeremy Ashkenas
b8f563d49e first draft of docs are done 2009-12-23 23:01:39 -08:00
Jeremy Ashkenas
3b3d57e84a waypoint 2009-12-24 01:22:41 -05:00
Jeremy Ashkenas
a1ee622aa6 added git st with the new operator regex 2009-12-24 00:37:33 -05:00
Jeremy Ashkenas
64733981fd ported over a little more underscore 2009-12-23 21:09:32 -05:00
Jeremy Ashkenas
7833b11724 added the ! sign as an allowed operator 2009-12-23 21:00:04 -05:00
Jeremy Ashkenas
a7032d0964 ... 2009-12-23 20:57:35 -05:00
Jeremy Ashkenas
f2c872230e more better super docs, better switch docs 2009-12-23 20:48:55 -05:00
Jeremy Ashkenas
8d26488748 added yes, no, on and off as boolean aliases and a nice aliases section to the docs 2009-12-23 20:24:55 -05:00
Jeremy Ashkenas
d92ed46503 broken waypoint, but fixed line numbers with the new JS comments 2009-12-23 19:42:44 -05:00
Jeremy Ashkenas
777eac045a broken waypoint, but fixed line numbers with the new JS comments 2009-12-23 19:42:18 -05:00
Jeremy Ashkenas
37e2f3b944 for whatever reason, don't need to force else-bodies to compile as statements anymore ... let them do what they want 2009-12-22 12:18:27 -05:00
Jeremy Ashkenas
3902a8b268 removed all traces of 'extends' -- it's not any shorter or more convenient than just setting the prototype 2009-12-22 12:08:29 -05:00
Jeremy Ashkenas
63a910d7ce got comments within object and array literals working out 2009-12-22 11:50:43 -05:00
Jeremy Ashkenas
3cee51cc37 first draft of parsing and printing along comments -- unfortunately, not yet working within objects and arrays 2009-12-22 11:27:19 -05:00
Jeremy Ashkenas
45b559a721 passing through comments as tags on Values, but not printing them out quite yet... 2009-12-22 10:48:58 -05:00
Jeremy Ashkenas
7c59eb2c36 nice -- it's pushing down assignments properly (recursively) now 2009-12-22 10:16:53 -05:00
Jeremy Ashkenas
40f633b8d0 moderate refactor of nodes.rb -- tests pass and examples compile without warnings 2009-12-22 10:11:41 -05:00
Jeremy Ashkenas
4e3d7cb974 clean up children at exit -- had about twenty processes all watching and recompiling the docs 2009-12-21 12:15:13 -05:00
Jeremy Ashkenas
d14b127b60 documentation waypoint 2009-12-21 11:41:45 -05:00
Jeremy Ashkenas
0d566ed1ec added full complement of bitwise operators 2009-12-19 22:56:27 -05:00
Jeremy Ashkenas
d86f92c6f2 added full complement of bitwise operators 2009-12-19 22:55:58 -05:00
Jeremy Ashkenas
efc5150144 making the each fixture a little more like underscore, and avoiding passing assignment into functions from the outside 2009-12-19 00:45:36 -05:00
Jeremy Ashkenas
7474ed1a5e added the verbose option to the CLI 2009-12-19 00:37:54 -05:00
Jeremy Ashkenas
2f4433af71 more little fixes, lots of subtle things, added a verbose logging mode 2009-12-19 00:33:34 -05:00
Jeremy Ashkenas
91303efd2c lots of tweaks make the tests pass again 2009-12-18 23:13:59 -05:00
Jeremy Ashkenas
d73ff9a79f patched up array comprehensions somewhat. Parens are still a necessary evil, and there's still probably plenty of edge cases 2009-12-18 22:30:09 -05:00
Jeremy Ashkenas
7ec6300a48 little fixes more examples 2009-12-18 09:55:31 -05:00
Jeremy Ashkenas
42c84fc54b adding css for syntax highlighting 2009-12-18 08:36:20 -05:00
Jeremy Ashkenas
f0a790d624 todo to-done 2009-12-18 07:40:26 -05:00
Jeremy Ashkenas
fdcff7aaf0 finished the first draft of the parser test 2009-12-18 07:28:26 -05:00
Jeremy Ashkenas
ab2362e372 adding comprehensive attr_readers to the AST for testing 2009-12-18 07:21:59 -05:00
Jeremy Ashkenas
98cf9f5af2 parser test raises some minor improvements (remove unnecessary ValueNode arrays, etc 2009-12-18 07:11:01 -05:00
Jeremy Ashkenas
e74af51a7d adding an initial lexer test 2009-12-18 06:59:06 -05:00
Jeremy Ashkenas
35b5d8c630 after a lot of grammar wrestling, got the if-else chains to parse unambiguously. Now you only need a single period to close chains of any length. 2009-12-18 00:49:23 -05:00
Jeremy Ashkenas
8575d91c66 finally got the function/object/variable assignment indentation straightened out, I think 2009-12-17 23:45:24 -05:00
Jeremy Ashkenas
8338f124be compiling if-else chains into nice flat ones 2009-12-17 23:34:52 -05:00
Jeremy Ashkenas
be19f7ad4f first major rework of the nodes -- still need more comments and templatish cleanup, but character tagging is all settled 2009-12-17 23:22:02 -05:00
Jeremy Ashkenas
5c737d29ab renamed Nodes to Expressions 2009-12-17 22:58:40 -05:00
Jeremy Ashkenas
92c59ea4a5 finished commenting everything but the nodes -- they're up next 2009-12-17 22:54:24 -05:00
Jeremy Ashkenas
dd28074436 finished commenting the grammar 2009-12-17 22:22:35 -05:00
Jeremy Ashkenas
f8ab30fa42 many more comments, plus a fix for inner-assignment indentation 2009-12-17 22:13:29 -05:00
Jeremy Ashkenas
581ad8ba1e commented the command-line interface 2009-12-17 21:57:21 -05:00
Jeremy Ashkenas
5703c1ed6d moved the TextMate bundle into the gem, added a command to install it 2009-12-17 21:46:12 -05:00
Jeremy Ashkenas
a71a3cdf3f added the 'delete' operator 2009-12-17 21:21:07 -05:00
Jeremy Ashkenas
f5d31b78e6 removed the 'default' keyword in favor of an 'else' 2009-12-17 21:14:36 -05:00
Jeremy Ashkenas
e1e6bb72c6 removed class checks in favor of statement? 2009-12-17 21:10:49 -05:00
Jeremy Ashkenas
d89ca33cdb number examples 2009-12-17 21:00:31 -05:00
Jeremy Ashkenas
58ecfeb815 added exponential and hex numbers 2009-12-17 20:59:19 -05:00
Jeremy Ashkenas
955d01a302 added a nice --watch mode to continually recompile or relint (or reprint) your coffeescripts 2009-12-17 20:37:39 -05:00
Jeremy Ashkenas
f8a5f7595d cleanups getting underscore to compile 2009-12-17 10:33:57 -05:00
Jeremy Ashkenas
9f33cf19ad added nice syntax errors 2009-12-17 10:04:43 -05:00
Jeremy Ashkenas
6deb85e083 passing through values with line number information that look and act like Ruby natives 2009-12-17 09:37:42 -05:00
Jeremy Ashkenas
9ad108281e cleaned up lexer in order to add line numbers 2009-12-17 09:29:49 -05:00
Jeremy Ashkenas
cd0091c045 added the ability to super() 2009-12-17 09:07:42 -05:00
Jeremy Ashkenas
01ecae2c55 allowing inner slashes in regexes 2009-12-17 08:29:19 -05:00
Jeremy Ashkenas
cef4bcd756 supporting escaped quotes in strings 2009-12-17 08:26:46 -05:00
Jeremy Ashkenas
ad50bd7154 supporting escaped quotes in strings 2009-12-17 08:26:20 -05:00
Jeremy Ashkenas
b97f9cf5ec multiline strings 2009-12-17 08:23:17 -05:00
Jeremy Ashkenas
2d65d3d73b multiline strings 2009-12-17 08:23:07 -05:00
Jeremy Ashkenas
733a76fdba built the first gem -- works just fine 2009-12-16 23:10:03 -05:00
Jeremy Ashkenas
fd63698005 completely reorganized for a gem and the 'coffee-script' command 2009-12-16 22:42:53 -05:00
97 changed files with 7510 additions and 3059 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
parser.output
parser.output
*.gem

22
LICENSE Normal file
View 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
View 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

59
Rakefile Normal file
View File

@@ -0,0 +1,59 @@
require 'erb'
require 'fileutils'
require 'rake/testtask'
desc "Run all tests"
task :test do
$LOAD_PATH.unshift(File.expand_path('test'))
require 'redgreen' if Gem.available?('redgreen')
require 'test/unit'
Dir['test/*/**/test_*.rb'].each {|test| require test }
end
namespace :build do
desc "Recompile the Racc parser (pass -v and -g for verbose debugging)"
task :parser, :extra_args do |t, args|
sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y"
end
desc "Compile the Narwhal interface for --interactive and --run"
task :narwhal do
sh "bin/coffee-script lib/coffee_script/narwhal/*.cs -o lib/coffee_script/narwhal/js"
end
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
View File

@@ -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

View File

@@ -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
View 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
View File

@@ -0,0 +1,26 @@
Gem::Specification.new do |s|
s.name = 'coffee-script'
s.version = '0.1.3' # Keep version in sync with coffee-script.rb
s.date = '2009-12-25'
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

View File

@@ -0,0 +1,7 @@
launch() if ignition is on
volume: 10 if band isnt spinal_tap
let_the_wild_rumpus_begin() unless answer is no
if car.speed < speed_limit then accelerate().

View 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.

View File

@@ -0,0 +1,2 @@
greeting: "Hello CoffeeScript"
difficulty: 0.5

View 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()

View File

@@ -0,0 +1,4 @@
hi: `function() {
return [document.title, "Hello JavaScript"].join(": ");
}`

View 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".

View File

@@ -0,0 +1,2 @@
square: x => x * x.
cube: x => square(x) * x.

View File

@@ -0,0 +1,3 @@
# CoffeeScript on the left, JS on the right.
square: x => x * x.

View File

@@ -0,0 +1,6 @@
song: ["do", "re", "mi", "fa", "so"]
ages: {
max: 10
ida: 9
tim: 11
}

View 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.

View 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.

View File

@@ -0,0 +1,5 @@
num: 1
change_numbers: =>
num: 2
new_num: 3.
new_num: change_numbers()

View File

@@ -0,0 +1,2 @@
nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
three_to_six: nums[3, 6]

View 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
View File

@@ -0,0 +1,25 @@
Animal: => .
Animal.prototype.move: meters =>
alert(this.name + " moved " + meters + "m.").
Snake: name => this.name: name.
Snake extends new Animal()
Snake.prototype.move: =>
alert("Slithering...")
super(5).
Horse: name => this.name: name.
Horse extends 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()

View 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
View File

@@ -0,0 +1,7 @@
try
all_hell_breaks_loose()
cats_and_dogs_living_together()
catch error
print(error)
finally
clean_up().

View File

@@ -0,0 +1,5 @@
while demand > supply
sell()
restock().
while supply > demand then buy().

147
documentation/css/amy.css Normal file
View 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;
}

View 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;
}

View 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;
}

View File

@@ -0,0 +1,485 @@
<%
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;">&#9749;</sub> CoffeeScript</h1>
<p>
CoffeeScript is a little language that compiles into JavaScript. Think
of it as JavaScript's less ostentatious kid brother &mdash; 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 &mdash; 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="#inheritance">Inheritance, and 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. In conjunction with
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee-script</tt>
command also provides direct evaluation and an interactive REPL.
When compiling to JavaScript, <tt>coffee-script</tt> writes the output
as <tt>.js</tt> files in the same directory by default, but output
can be customized with the following options:
</p>
<table>
<tr>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session.
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute the CoffeeScripts without saving the intermediate
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
</td>
</tr>
<tr>
<td><code>-o, --output [DIR]</code></td>
<td>
Write out all compiled JavaScript files into the specified directory.
</td>
</tr>
<tr>
<td><code>-w, --watch</code></td>
<td>
Watch the modification times of the coffee-scripts, recompiling as
soon as a change occurs.
</td>
</tr>
<tr>
<td><code>-p, --print</code></td>
<td>
Instead of writing out the JavaScript as a file, print it
directly to <b>stdout</b>.
</td>
</tr>
<tr>
<td><code>-l, --lint</code></td>
<td>
If the <tt>jsl</tt> (JavaScript Lint) command is installed, use it
to check the compilation of a CoffeeScript file. (Handy in
conjunction with <tt>--watch</tt>)
</td>
</tr>
<tr>
<td><code>-e, --eval</code></td>
<td>
Compile and print a little snippet of CoffeeScript directly from the
command line (or from <b>stdin</b>). For example:<br /><tt>coffee-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>-n, --no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper
or var declarations, for situations where you want to add every
variable to global scope.
</td>
</tr>
<tr>
<td><code>--install-bundle</code></td>
<td>
Install the TextMate bundle for CoffeeScript syntax highlighting.
</td>
</tr>
</table>
<p>
<b>Examples:</b>
</p>
<pre>
coffee-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 &mdash; 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>&amp;&amp;:</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>isnt</tt> into <tt>!==</tt>.
</p>
<p>
You can use <tt>not</tt> as an alias for <tt>!</tt>.
</p>
<p>
For logic, <tt>and</tt> compiles to <tt>&amp;&amp;</tt>, and <tt>or</tt>
into <tt>||</tt>.
</p>
<p>
Instead of a newline or semicolon, <tt>then</tt> can be used to separate
conditions from expressions, in <b>while</b>,
<b>if</b>/<b>else</b>, and <b>switch</b>/<b>when</b> statements.
</p>
<p>
As in <a href="http://yaml.org/">YAML</a>, <tt>on</tt> and <tt>yes</tt>
are the same as boolean <tt>true</tt>, while <tt>off</tt> and <tt>no</tt> are boolean <tt>false</tt>.
</p>
<p>
For single-line statements, <tt>unless</tt> can be used as the inverse of <tt>if</tt>.
</p>
<%= code_for('aliases') %>
<p id="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="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b>
JavaScript's prototypal inheritance has always been a bit of a
brain-bender, with a whole family tree of libraries that provide a cleaner
syntax for classical inheritance on top of JavaScript's prototypes:
<a href="http://code.google.com/p/base2/">Base2</a>,
<a href="http://prototypejs.org/">Prototype.js</a>,
<a href="http://jsclass.jcoglan.com/">JS.Class</a>, etc.
The libraries provide syntactic sugar, but the built-in inheritance would
be completely usable if it weren't for a couple of small exceptions:
it's awkward to call <b>super</b> (the prototype object's
implementation of the current function), and it's awkward to correctly
set the prototype chain. CoffeeScript provides <tt>extends</tt>
to help with prototype setup, and converts
<tt>super()</tt> calls into calls against the immediate ancestor's
method of the same name.
</p>
<%= code_for('super', true) %>
<p id="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 &mdash; 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: 20px;">0.1.3</b>
The <tt>coffee-script</tt> command now includes <tt>--interactive</tt>,
which launches an interactive CoffeeScript session, and <tt>--run</tt>,
which directly compiles and executes a script. Both options depend on a
working installation of Narwhal.
The <tt>aint</tt> keyword has been replaced by <tt>isnt</tt>, which goes
together a little smoother with <tt>is</tt>.
Quoted strings are now allowed as identifiers within object literals: eg.
<tt>{"5+5": 10}</tt>.
All assignment operators now use a colon: <tt>+:</tt>, <tt>-:</tt>,
<tt>*:</tt>, etc.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.1.2</b>
Fixed a bug with calling <tt>super()</tt> through more than one level of
inheritance, with the re-addition of the <tt>extends</tt> keyword.
Added experimental <a href="http://narwhaljs.org/">Narwhal</a>
support (as a Tusk package), contributed by
<a href="http://tlrobinson.net/">Tom Robinson</a>, including
<b>bin/cs</b> as a CoffeeScript REPL and interpreter.
New <tt>--no-wrap</tt> option to suppress the safety function
wrapper.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.1.1</b>
Added <tt>instanceof</tt> and <tt>typeof</tt> as operators.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.1.0</b>
Initial CoffeeScript release.
</p>
</div>
</body>
</html>

View 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;
})();

View 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;
}
})();

View File

@@ -0,0 +1,4 @@
(function(){
var greeting = "Hello CoffeeScript";
var difficulty = 0.5;
})();

View 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();
})();

View File

@@ -0,0 +1,5 @@
(function(){
var hi = function() {
return [document.title, "Hello JavaScript"].join(": ");
};
})();

View 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";
})();

View File

@@ -0,0 +1,8 @@
(function(){
var square = function(x) {
return x * x;
};
var cube = function(x) {
return square(x) * x;
};
})();

View File

@@ -0,0 +1,7 @@
(function(){
// CoffeeScript on the left, JS on the right.
var square = function(x) {
return x * x;
};
})();

View File

@@ -0,0 +1,8 @@
(function(){
var song = ["do", "re", "mi", "fa", "so"];
var ages = {
max: 10,
ida: 9,
tim: 11
};
})();

View 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;
})();

View 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;
})();

View 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();
})();

View 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);
})();

View 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
View 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.__proto__ = new Animal();
Snake.prototype.move = function() {
alert("Slithering...");
return Snake.prototype.__proto__.move.call(this, 5);
};
var Horse = function(name) {
this.name = name;
return this.name;
};
Horse.prototype.__proto__ = new Animal();
Horse.prototype.move = function() {
alert("Galloping...");
return Horse.prototype.__proto__.move.call(this, 45);
};
var sam = new Snake("Sammy the Python");
var tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
})();

View 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
View File

@@ -0,0 +1,10 @@
(function(){
try {
all_hell_breaks_loose();
cats_and_dogs_living_together();
} catch (error) {
print(error);
} finally {
clean_up();
}
})();

View File

@@ -0,0 +1,9 @@
(function(){
while (demand > supply) {
sell();
restock();
}
while (supply > demand) {
buy();
}
})();

View File

@@ -1,5 +1,3 @@
# TODO: Add range indexing: array[5..7] => array.slice(5, 7)
# Functions:
square: x => x * x.
@@ -7,7 +5,7 @@ sum: x, y => x + y.
odd: x => x % 2 is 0.
even: x => x % 2 aint 0.
even: x => x % 2 isnt 0.
run_loop: =>
fire_events( e => e.stopPropagation(). )
@@ -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 extends new Animal()
Snake.prototype.move: =>
alert('Slithering...')
super(5).
Horse: name => this.name: name.
Horse extends new 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

View File

@@ -81,9 +81,9 @@ Creature : {
hit: damage =>
p_up: Math.rand( this.charisma )
if p_up % 9 is 7
this.life += p_up / 4
this.life +: p_up / 4
puts( "[" + this.name + " magick powers up " + p_up + "!]" ).
this.life -= damage
this.life -: damage
if this.life <= 0 then puts( "[" + this.name + " has died.]" )..
# This method takes one turn in a fight.
@@ -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
View 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
View 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 isnt breaker.
obj.
# Return the results of applying the iterator to each element. Use JavaScript
# 1.6's version of map, if possible.
_.map: obj, iterator, context =>
return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
results: []
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;
# };
#
# ()

298
grammar.y
View File

@@ -1,298 +0,0 @@
class Parser
# Declare tokens produced by the lexer
token IF ELSE THEN UNLESS
token NUMBER STRING REGEX
token TRUE FALSE NULL
token IDENTIFIER PROPERTY_ACCESS
token CODE PARAM NEW RETURN
token TRY CATCH FINALLY THROW
token BREAK CONTINUE
token FOR IN WHILE
token SWITCH CASE DEFAULT
token NEWLINE
token JS
# Declare order of operations.
prechigh
nonassoc UMINUS NOT '!'
left '*' '/' '%'
left '+' '-'
left '<=' '<' '>' '>='
right '==' '!=' IS AINT
left '&&' '||' AND OR
left ':'
right '-=' '+=' '/=' '*=' '||=' '&&='
nonassoc IF
left UNLESS
right RETURN THROW FOR WHILE
nonassoc "."
preclow
# We expect 2 shift/reduce errors for optional syntax.
# There used to be 252 -- greatly improved.
expect 2
rule
# All parsing will end in this rule, being the trunk of the AST.
Root:
/* nothing */ { result = Nodes.new([]) }
| Terminator { result = Nodes.new([]) }
| Expressions { result = val[0] }
;
# Any list of expressions or method body, seperated by line breaks or semis.
Expressions:
Expression { result = Nodes.new(val) }
| Expressions Terminator Expression { result = val[0] << val[2] }
| Expressions Terminator { result = val[0] }
| Terminator Expressions { result = val[1] }
;
# All types of expressions in our language
Expression:
Literal
| Value
| Call
| Assign
| Code
| Operation
| If
| Try
| Throw
| Return
| While
| For
| Switch
;
# All tokens that can terminate an expression
Terminator:
"\n"
| ";"
;
# All tokens that can serve to begin the second block
Then:
THEN
| Terminator
;
# All hard-coded values
Literal:
NUMBER { result = LiteralNode.new(val[0]) }
| STRING { result = LiteralNode.new(val[0]) }
| JS { result = LiteralNode.new(val[0]) }
| REGEX { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(true) }
| FALSE { result = LiteralNode.new(false) }
| NULL { result = LiteralNode.new(nil) }
| BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) }
;
# Assign to a variable
Assign:
Value ":" Expression { result = AssignNode.new(val[0], val[2]) }
;
# Assignment within an object literal.
AssignObj:
IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
;
# A Return statement.
Return:
RETURN Expression { result = ReturnNode.new(val[1]) }
;
# Arithmetic and logical operators
# For Ruby's Operator precedence, see:
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
Operation:
'!' Expression { result = OpNode.new(val[0], val[1]) }
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AINT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
;
# Method definition
Code:
ParamList "=>" Expressions "." { result = CodeNode.new(val[0], val[2]) }
| "=>" Expressions "." { result = CodeNode.new([], val[1]) }
;
ParamList:
PARAM { result = val }
| ParamList "," PARAM { result = val[0] << val[2] }
;
Value:
IDENTIFIER { result = ValueNode.new(val) }
| Array { result = ValueNode.new(val) }
| Object { result = ValueNode.new(val) }
| Parenthetical { result = ValueNode.new(val) }
| Value Accessor { result = val[0] << val[1] }
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
;
Accessor:
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
| Index { result = val[0] }
| Slice { result = val[0] }
;
Index:
"[" Expression "]" { result = IndexNode.new(val[1]) }
;
Slice:
"[" Expression "," Expression "]" { result = SliceNode.new(val[1], val[3]) }
;
Object:
"{" AssignList "}" { result = ObjectNode.new(val[1]) }
;
AssignList:
/* nothing */ { result = []}
| AssignObj { result = val }
| AssignList "," AssignObj { result = val[0] << val[2] }
| AssignList Terminator AssignObj { result = val[0] << val[2] }
;
# A method call.
Call:
Invocation { result = val[0] }
| NEW Invocation { result = val[1].new_instance }
;
Invocation:
Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
;
# An Array.
Array:
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
;
# A list of arguments to a method call.
ArgList:
/* nothing */ { result = [] }
| Expression { result = val }
| ArgList "," Expression { result = val[0] << val[2] }
| ArgList Terminator Expression { result = val[0] << val[2] }
;
If:
IF Expression
Then Expressions "." { result = IfNode.new(val[1], val[3]) }
| IF Expression
Then Expressions
ELSE Expressions "." { result = IfNode.new(val[1], val[3], val[5]) }
| Expression IF Expression { result = IfNode.new(val[2], Nodes.new([val[0]])) }
| Expression UNLESS Expression { result = IfNode.new(val[2], Nodes.new([val[0]]), nil, :invert) }
;
Try:
TRY Expressions CATCH IDENTIFIER
Expressions "." { result = TryNode.new(val[1], val[3], val[4]) }
| TRY Expressions FINALLY
Expressions "." { result = TryNode.new(val[1], nil, nil, val[3]) }
| TRY Expressions CATCH IDENTIFIER
Expressions
FINALLY Expressions "." { result = TryNode.new(val[1], val[3], val[4], val[6]) }
;
Throw:
THROW Expression { result = ThrowNode.new(val[1]) }
;
Parenthetical:
"(" Expressions ")" { result = ParentheticalNode.new(val[1]) }
;
While:
WHILE Expression Then
Expressions "." { result = WhileNode.new(val[1], val[3]) }
;
For:
Expression FOR IDENTIFIER
IN Expression "." { result = ForNode.new(val[0], val[4], val[2]) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN Expression "." { result = ForNode.new(val[0], val[6], val[2], val[4]) }
| Expression FOR IDENTIFIER
IN Expression
IF Expression "." { result = ForNode.new(IfNode.new(val[6], Nodes.new([val[0]])), val[4], val[2]) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN Expression
IF Expression "." { result = ForNode.new(IfNode.new(val[8], Nodes.new([val[0]])), val[6], val[2], val[4]) }
;
Switch:
SWITCH Expression Then
Cases "." { result = val[3].rewrite_condition(val[1]) }
| SWITCH Expression Then
Cases DEFAULT Expressions "." { result = val[3].rewrite_condition(val[1]).add_default(val[5]) }
;
Cases:
Case { result = val[0] }
| Cases Case { result = val[0] << val[1] }
;
Case:
CASE Expression Then Expressions { result = IfNode.new(val[1], val[3]) }
;
end
---- header
require "lexer"
require "nodes"
---- inner
def parse(code, show_tokens=false)
# @yydebug = true
@tokens = Lexer.new.tokenize(code)
puts @tokens.inspect if show_tokens
do_parse
end
def next_token
@tokens.shift
end

864
index.html Normal file
View File

@@ -0,0 +1,864 @@
<!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;">&#9749;</sub> CoffeeScript</h1>
<p>
CoffeeScript is a little language that compiles into JavaScript. Think
of it as JavaScript's less ostentatious kid brother &mdash; 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 &mdash; 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="#inheritance">Inheritance, and 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">=&gt;</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">=&gt;</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">&lt;</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. In conjunction with
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee-script</tt>
command also provides direct evaluation and an interactive REPL.
When compiling to JavaScript, <tt>coffee-script</tt> writes the output
as <tt>.js</tt> files in the same directory by default, but output
can be customized with the following options:
</p>
<table>
<tr>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session.
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute the CoffeeScripts without saving the intermediate
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
</td>
</tr>
<tr>
<td><code>-o, --output [DIR]</code></td>
<td>
Write out all compiled JavaScript files into the specified directory.
</td>
</tr>
<tr>
<td><code>-w, --watch</code></td>
<td>
Watch the modification times of the coffee-scripts, recompiling as
soon as a change occurs.
</td>
</tr>
<tr>
<td><code>-p, --print</code></td>
<td>
Instead of writing out the JavaScript as a file, print it
directly to <b>stdout</b>.
</td>
</tr>
<tr>
<td><code>-l, --lint</code></td>
<td>
If the <tt>jsl</tt> (JavaScript Lint) command is installed, use it
to check the compilation of a CoffeeScript file. (Handy in
conjunction with <tt>--watch</tt>)
</td>
</tr>
<tr>
<td><code>-e, --eval</code></td>
<td>
Compile and print a little snippet of CoffeeScript directly from the
command line (or from <b>stdin</b>). For example:<br /><tt>coffee-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>-n, --no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper
or var declarations, for situations where you want to add every
variable to global scope.
</td>
</tr>
<tr>
<td><code>--install-bundle</code></td>
<td>
Install the TextMate bundle for CoffeeScript syntax highlighting.
</td>
</tr>
</table>
<p>
<b>Examples:</b>
</p>
<pre>
coffee-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">=&gt;</span> x <span class="Keyword">*</span> x.
<span class="FunctionName">cube</span><span class="Keyword">:</span> <span class="FunctionArgument">x</span> <span class="Storage">=&gt;</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">&quot;</span>Hello CoffeeScript<span class="String">&quot;</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">&quot;</span>Hello CoffeeScript<span class="String">&quot;</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">&quot;</span>do<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>re<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>mi<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>fa<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>so<span class="String">&quot;</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">&quot;</span>do<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>re<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>mi<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>fa<span class="String">&quot;</span></span>, <span class="String"><span class="String">&quot;</span>so<span class="String">&quot;</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 &mdash; 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">=&gt;</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">&amp;</span><span class="Keyword">&amp;</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>&amp;&amp;:</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">=&gt;</span>
<span class="Keyword">if</span> student.excellent_work
<span class="String"><span class="String">&quot;</span>A+<span class="String">&quot;</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">&quot;</span>B<span class="String">&quot;</span></span> <span class="Keyword">else</span> <span class="String"><span class="String">&quot;</span>B-<span class="String">&quot;</span></span>.
<span class="Keyword">else</span>
<span class="String"><span class="String">&quot;</span>C<span class="String">&quot;</span></span>..
eldest<span class="Keyword">:</span> <span class="Keyword">if</span> <span class="Number">24</span> <span class="Keyword">&gt;</span> <span class="Number">21</span> <span class="Keyword">then</span> <span class="String"><span class="String">&quot;</span>Liz<span class="String">&quot;</span></span> <span class="Keyword">else</span> <span class="String"><span class="String">&quot;</span>Ike<span class="String">&quot;</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">&quot;</span>A+<span class="String">&quot;</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">&quot;</span>B<span class="String">&quot;</span></span> : <span class="String"><span class="String">&quot;</span>B-<span class="String">&quot;</span></span>;
} <span class="Keyword">else</span> {
<span class="Keyword">return</span> <span class="String"><span class="String">&quot;</span>C<span class="String">&quot;</span></span>;
}
};
<span class="Storage">var</span> eldest <span class="Keyword">=</span> <span class="Number">24</span> <span class="Keyword">&gt;</span> <span class="Number">21</span> ? <span class="String"><span class="String">&quot;</span>Liz<span class="String">&quot;</span></span> : <span class="String"><span class="String">&quot;</span>Ike<span class="String">&quot;</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>isnt</tt> into <tt>!==</tt>.
</p>
<p>
You can use <tt>not</tt> as an alias for <tt>!</tt>.
</p>
<p>
For logic, <tt>and</tt> compiles to <tt>&amp;&amp;</tt>, and <tt>or</tt>
into <tt>||</tt>.
</p>
<p>
Instead of a newline or semicolon, <tt>then</tt> can be used to separate
conditions from expressions, in <b>while</b>,
<b>if</b>/<b>else</b>, and <b>switch</b>/<b>when</b> statements.
</p>
<p>
As in <a href="http://yaml.org/">YAML</a>, <tt>on</tt> and <tt>yes</tt>
are the same as boolean <tt>true</tt>, while <tt>off</tt> and <tt>no</tt> are boolean <tt>false</tt>.
</p>
<p>
For single-line statements, <tt>unless</tt> can be used as the inverse of <tt>if</tt>.
</p>
<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">isnt</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">&lt;</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">&lt;</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">&gt;</span> supply
sell()
restock().
<span class="Keyword">while</span> supply <span class="Keyword">&gt;</span> demand <span class="Keyword">then</span> buy().
</pre><pre class="idle"><span class="Keyword">while</span> (demand <span class="Keyword">&gt;</span> supply) {
sell();
restock();
}
<span class="Keyword">while</span> (supply <span class="Keyword">&gt;</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">&lt;</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">&lt;</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="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b>
JavaScript's prototypal inheritance has always been a bit of a
brain-bender, with a whole family tree of libraries that provide a cleaner
syntax for classical inheritance on top of JavaScript's prototypes:
<a href="http://code.google.com/p/base2/">Base2</a>,
<a href="http://prototypejs.org/">Prototype.js</a>,
<a href="http://jsclass.jcoglan.com/">JS.Class</a>, etc.
The libraries provide syntactic sugar, but the built-in inheritance would
be completely usable if it weren't for a couple of small exceptions:
it's awkward to call <b>super</b> (the prototype object's
implementation of the current function), and it's awkward to correctly
set the prototype chain. CoffeeScript provides <tt>extends</tt>
to help with prototype setup, and converts
<tt>super()</tt> calls into calls against the immediate ancestor's
method of the same name.
</p>
<div class='code'><pre class="idle"><span class="FunctionName">Animal</span><span class="Keyword">:</span> <span class="Storage">=&gt;</span> .
<span class="FunctionName">Animal.prototype.move</span><span class="Keyword">:</span> <span class="FunctionArgument">meters</span> <span class="Storage">=&gt;</span>
alert(<span class="Variable">this</span>.name <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>).
<span class="FunctionName">Snake</span><span class="Keyword">:</span> <span class="FunctionArgument">name</span> <span class="Storage">=&gt;</span> <span class="Variable">this</span>.name<span class="Keyword">:</span> name.
Snake <span class="Variable">extends</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">=&gt;</span>
alert(<span class="String"><span class="String">&quot;</span>Slithering...<span class="String">&quot;</span></span>)
<span class="Variable">super</span>(<span class="Number">5</span>).
<span class="FunctionName">Horse</span><span class="Keyword">:</span> <span class="FunctionArgument">name</span> <span class="Storage">=&gt;</span> <span class="Variable">this</span>.name<span class="Keyword">:</span> name.
Horse <span class="Variable">extends</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">=&gt;</span>
alert(<span class="String"><span class="String">&quot;</span>Galloping...<span class="String">&quot;</span></span>)
<span class="Variable">super</span>(<span class="Number">45</span>).
sam<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Snake</span>(<span class="String"><span class="String">&quot;</span>Sammy the Python<span class="String">&quot;</span></span>)
tom<span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Horse</span>(<span class="String"><span class="String">&quot;</span>Tommy the Palomino<span class="String">&quot;</span></span>)
sam.move()
tom.move()
</pre><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">&quot;</span> moved <span class="String">&quot;</span></span> <span class="Keyword">+</span> meters <span class="Keyword">+</span> <span class="String"><span class="String">&quot;</span>m.<span class="String">&quot;</span></span>);
};
<span class="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="FunctionName">__proto__</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">&quot;</span>Slithering...<span class="String">&quot;</span></span>);
<span class="Keyword">return</span> Snake.<span class="LibraryConstant">prototype</span>.__proto__.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="FunctionName">__proto__</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">&quot;</span>Galloping...<span class="String">&quot;</span></span>);
<span class="Keyword">return</span> Horse.<span class="LibraryConstant">prototype</span>.__proto__.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">&quot;</span>Sammy the Python<span class="String">&quot;</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">&quot;</span>Tommy the Palomino<span class="String">&quot;</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.__proto__ = new Animal();
Snake.prototype.move = function() {
alert("Slithering...");
return Snake.prototype.__proto__.move.call(this, 5);
};
var Horse = function(name) {
this.name = name;
return this.name;
};
Horse.prototype.__proto__ = new Animal();
Horse.prototype.move = function() {
alert("Galloping...");
return Horse.prototype.__proto__.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, &quot;Hello JavaScript&quot;].join(&quot;: &quot;);</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">&quot;</span>Hello JavaScript<span class="String">&quot;</span></span>].<span class="LibraryFunction">join</span>(<span class="String"><span class="String">&quot;</span>: <span class="String">&quot;</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">&quot;</span>Tuesday<span class="String">&quot;</span></span> <span class="Keyword">then</span> eat_breakfast()
when <span class="String"><span class="String">&quot;</span>Wednesday<span class="String">&quot;</span></span> <span class="Keyword">then</span> go_to_the_park()
when <span class="String"><span class="String">&quot;</span>Saturday<span class="String">&quot;</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">&quot;</span>Sunday<span class="String">&quot;</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">&quot;</span>Tuesday<span class="String">&quot;</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">&quot;</span>Wednesday<span class="String">&quot;</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">&quot;</span>Saturday<span class="String">&quot;</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">&quot;</span>Sunday<span class="String">&quot;</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">&quot;</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">&quot;</span></span>
</pre><pre class="idle"><span class="Storage">var</span> moby_dick <span class="Keyword">=</span> <span class="String"><span class="String">&quot;</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">&quot;</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 &mdash; 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: 20px;">0.1.3</b>
The <tt>coffee-script</tt> command now includes <tt>--interactive</tt>,
which launches an interactive CoffeeScript session, and <tt>--run</tt>,
which directly compiles and executes a script. Both options depend on a
working installation of Narwhal.
The <tt>aint</tt> keyword has been replaced by <tt>isnt</tt>, which goes
together a little smoother with <tt>is</tt>.
Quoted strings are now allowed as identifiers within object literals: eg.
<tt>{"5+5": 10}</tt>.
All assignment operators now use a colon: <tt>+:</tt>, <tt>-:</tt>,
<tt>*:</tt>, etc.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.1.2</b>
Fixed a bug with calling <tt>super()</tt> through more than one level of
inheritance, with the re-addition of the <tt>extends</tt> keyword.
Added experimental <a href="http://narwhaljs.org/">Narwhal</a>
support (as a Tusk package), contributed by
<a href="http://tlrobinson.net/">Tom Robinson</a>, including
<b>bin/cs</b> as a CoffeeScript REPL and interpreter.
New <tt>--no-wrap</tt> option to suppress the safety function
wrapper.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.1.1</b>
Added <tt>instanceof</tt> and <tt>typeof</tt> as operators.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.1.0</b>
Initial CoffeeScript release.
</p>
</div>
</body>
</html>

140
lexer.rb
View File

@@ -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

View File

@@ -1,2 +0,0 @@
require "lexer"
p Lexer.new.tokenize(File.read('code.cs'))

20
lib/coffee-script.rb Normal file
View 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.3' # Keep in sync with the gemspec.
# Compile a script (String or IO) to JavaScript.
def self.compile(script, options={})
script = script.read if script.respond_to?(:read)
Parser.new.parse(script).compile(options)
end
end

View File

@@ -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>

View File

@@ -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: =&gt; … </string>
<key>match</key>
<string>([a-zA-Z_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=&gt;)</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 =&gt; … </string>
<key>match</key>
<string>([a-zA-Z_?., $]*)\s*(=&gt;)</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|extends)\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>!|\$|%|&amp;|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|&lt;=|&gt;=|&lt;&lt;=|&gt;&gt;=|&gt;&gt;&gt;=|&lt;&gt;|&lt;|&gt;|!|&amp;&amp;|\?|\|\||\:|\*:|(?&lt;!\()/=|%:|\+:|\-:|&amp;=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|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>(?&lt;=[=(:]|^|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>

View 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>

View File

@@ -0,0 +1,211 @@
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 launch_repl if @options[:interactive]
return eval_scriptlet if @options[:eval]
check_sources
return run_scripts if @options[:run]
@sources.each {|source| compile_javascript(source) }
watch_coffee_scripts if @options[:watch]
end
# The "--help" usage message.
def usage
puts "\n#{@option_parser}\n"
exit
end
private
# Compiles (or partially compiles) the source CoffeeScript file, returning
# the desired JS, tokens, or lint results.
def compile_javascript(source)
script = File.read(source)
return tokens(script) if @options[:tokens]
js = compile(script, source)
return unless js
return puts(js) if @options[:print]
return lint(js) if @options[:lint]
File.open(path_for(source), 'w+') {|f| f.write(js) }
end
# Spins up a watcher thread to keep track of the modification times of the
# source files, recompiling them whenever they're saved.
def watch_coffee_scripts
watch_thread = Thread.start do
loop do
@sources.each do |source|
mtime = File.stat(source).mtime
@mtimes[source] ||= mtime
if mtime > @mtimes[source]
@mtimes[source] = mtime
compile_javascript(source)
end
end
sleep WATCH_INTERVAL
end
end
Signal.trap("INT") { watch_thread.kill }
watch_thread.join
end
# Ensure that all of the source files exist.
def check_sources
usage if @sources.empty?
missing = @sources.detect {|s| !File.exists?(s) }
if missing
STDERR.puts("File not found: '#{missing}'")
exit(1)
end
end
# Pipe compiled JS through JSLint (requires a working 'jsl' command).
def lint(js)
stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin')
stdin.write(js)
stdin.close
puts stdout.read.tr("\n", '')
errs = stderr.read.chomp
puts errs unless errs.empty?
stdout.close and stderr.close
end
# Eval a little piece of CoffeeScript directly from the command line.
def eval_scriptlet
script = STDIN.tty? ? @sources.join(' ') : STDIN.read
return tokens(script) if @options[:tokens]
js = compile(script)
return lint(js) if @options[:lint]
puts js
end
# Use Narwhal to run an interactive CoffeeScript session.
def launch_repl
exec "narwhal lib/coffee_script/narwhal/js/launcher.js"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed to use the interactive REPL."
exit(1)
end
# Use Narwhal to compile and execute CoffeeScripts.
def run_scripts
sources = @sources.join(' ')
exec "narwhal lib/coffee_script/narwhal/js/launcher.js #{sources}"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
exit(1)
end
# Print the tokens that the lexer generates from a source script.
def tokens(script)
puts Lexer.new.tokenize(script).inspect
end
# Compile a single source file to JavaScript.
def compile(script, source='')
begin
CoffeeScript.compile(script, :no_wrap => @options[:no_wrap])
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('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i|
@options[:interactive] = true
end
opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r|
@options[:run] = true
end
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
@options[:output] = d
FileUtils.mkdir_p(d) unless File.exists?(d)
end
opts.on('-w', '--watch', 'watch scripts for changes, and recompile') do |w|
@options[:watch] = true
end
opts.on('-p', '--print', 'print the compiled JavaScript to stdout') do |d|
@options[:print] = true
end
opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l|
@options[:lint] = true
end
opts.on('-e', '--eval', 'compile a cli scriptlet or read from stdin') do |e|
@options[:eval] = true
end
opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t|
@options[:tokens] = true
end
opts.on('-v', '--verbose', 'print at every step of code generation') do |v|
ENV['VERBOSE'] = 'true'
end
opts.on('-n', '--no-wrap', 'raw output, no safety wrapper or vars') do |n|
@options[:no_wrap] = true
end
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
install_bundle
exit
end
opts.on_tail('--version', 'display 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

413
lib/coffee_script/grammar.y Normal file
View File

@@ -0,0 +1,413 @@
class Parser
# Declare tokens produced by the lexer
token IF ELSE THEN UNLESS
token NUMBER STRING REGEX
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 WHEN
token DELETE INSTANCEOF TYPEOF
token SUPER EXTENDS
token NEWLINE
token COMMENT
token JS
# Declare order of operations.
prechigh
nonassoc UMINUS NOT '!' '!!' '~' '++' '--'
left '*' '/' '%'
left '+' '-'
left '<<' '>>' '>>>'
left '&' '|' '^'
left '<=' '<' '>' '>='
right '==' '!=' IS ISNT
left '&&' '||' AND OR
right '-:' '+:' '/:' '*:' '%:'
right DELETE INSTANCEOF TYPEOF
left "."
right THROW FOR IN WHILE NEW SUPER
left UNLESS IF ELSE EXTENDS
left ":" '||:' '&&:'
right RETURN
preclow
# We expect 4 shift/reduce errors for optional syntax.
# There used to be 252 -- greatly improved.
expect 4
rule
# All parsing will end in this rule, being the trunk of the AST.
Root:
/* nothing */ { result = Expressions.new([]) }
| Terminator { result = Expressions.new([]) }
| Expressions { result = val[0] }
;
# Any list of expressions or method body, seperated by line breaks or semis.
Expressions:
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.
Expression:
PureExpression
| Statement
;
# The parts that are natural JavaScript expressions.
PureExpression:
Literal
| Value
| Call
| Code
| Operation
;
# We have to take extra care to convert these statements into expressions.
Statement:
Assign
| If
| Try
| Throw
| Return
| While
| For
| Switch
| Extends
| Comment
;
# All tokens that can terminate an expression.
Terminator:
"\n"
| ";"
;
# All tokens that can serve to begin the second block of a multi-part expression.
Then:
THEN
| Terminator
;
# All hard-coded values.
Literal:
NUMBER { result = LiteralNode.new(val[0]) }
| STRING { result = LiteralNode.new(val[0]) }
| JS { result = LiteralNode.new(val[0]) }
| REGEX { result = LiteralNode.new(val[0]) }
| BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(true) }
| FALSE { result = LiteralNode.new(false) }
| YES { result = LiteralNode.new(true) }
| NO { result = LiteralNode.new(false) }
| ON { result = LiteralNode.new(true) }
| OFF { result = LiteralNode.new(false) }
;
# Assignment to a variable.
Assign:
Value ":" Expression { result = AssignNode.new(val[0], val[2]) }
;
# Assignment within an object literal.
AssignObj:
IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
| STRING ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
| Comment { result = val[0] }
;
# A return statement.
Return:
RETURN Expression { result = ReturnNode.new(val[1]) }
;
# A comment.
Comment:
COMMENT { result = CommentNode.new(val[0]) }
;
# Arithmetic and logical operators
# For Ruby's Operator precedence, see:
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
Operation:
'!' Expression { result = OpNode.new(val[0], val[1]) }
| '!!' Expression { result = OpNode.new(val[0], val[1]) }
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| '~' Expression { result = OpNode.new(val[0], val[1]) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '*:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&:' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| 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]) }
;
# Function definition.
Code:
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[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 }
| AssignList "," AssignObj { result = val[0] << val[2] }
| AssignList Terminator AssignObj { result = val[0] << val[2] }
;
# All flavors of function call (instantiation, super, and regular).
Call:
Invocation { result = val[0] }
| NEW Invocation { result = val[1].new_instance }
| Super { result = val[0] }
;
# Extending an object's prototype.
Extends:
Value EXTENDS Expression { result = ExtendsNode.new(val[0], val[2]) }
;
# A generic function invocation.
Invocation:
Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
;
# 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, or as the contents of an array.
ArgList:
/* nothing */ { result = [] }
| Expression { result = val }
| ArgList "," Expression { result = val[0] << val[2] }
| ArgList Terminator Expression { result = val[0] << val[2] }
;
# Try/catch/finally exception handling blocks.
Try:
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 PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) }
| Expression FOR IDENTIFIER
IN PureExpression
IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN PureExpression
IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) }
;
# Switch/When blocks.
Switch:
SWITCH Expression Then
Whens "." { result = val[3].rewrite_condition(val[1]) }
| SWITCH Expression Then
Whens ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
;
# The inner list of whens.
Whens:
When { result = val[0] }
| Whens When { result = val[0] << val[1] }
;
# An individual when.
When:
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
module CoffeeScript
---- inner
# Lex and parse a CoffeeScript.
def parse(code)
# Uncomment the following line to enable grammar debugging, in combination
# with the -g flag in the Rake build task.
# @yydebug = true
@tokens = Lexer.new.tokenize(code)
do_parse
end
# Retrieve the next token from the list.
def next_token
@tokens.shift
end
# Raise a custom error class that knows about line numbers.
def on_error(error_token_id, error_value, value_stack)
raise ParseError.new(token_to_str(error_token_id), error_value, value_stack)
end
---- footer
end

187
lib/coffee_script/lexer.rb Normal file
View 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", "isnt", "not",
"new", "return",
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "while",
"switch", "when",
"super", "extends",
"delete", "instanceof", "typeof"]
# Token matching regexes.
IDENTIFIER = /\A([a-zA-Z$_]\w*)/
NUMBER = /\A\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b/i
STRING = /\A(""|''|"(.*?)[^\\]"|'(.*?)[^\\]')/m
JS = /\A(``|`(.*?)[^\\]`)/m
OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/
WHITESPACE = /\A([ \t\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

View File

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

View File

@@ -0,0 +1,65 @@
(function(){
// This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.cs Executes the `coffee-script` Ruby program to convert from CoffeeScript
// to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies.
var OS = require('os');
var File = require('file');
var Readline = require('readline');
// The path to the CoffeeScript Compiler.
var coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee-script');
// Our general-purpose error handler.
var checkForErrors = function(coffeeProcess) {
if (coffeeProcess.wait() === 0) {
return true;
}
system.stderr.print(coffeeProcess.stderr.read());
throw new Error("coffee-script compile error");
};
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
// command.
exports.run = function(args) {
args.shift();
if (args.length) {
return require(File.absolute(args[0]));
}
while (true) {
try {
system.stdout.write('cs> ').flush();
var result = exports.evalCS(Readline.readline());
if (result !== undefined) {
print(result);
}
} catch (e) {
print(e);
}
}
};
// Compile a given CoffeeScript file into JavaScript.
exports.compileFile = function(path) {
var coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]);
checkForErrors(coffee);
return coffee.stdout.read();
};
// Compile a string of CoffeeScript into JavaScript.
exports.compile = function(source) {
var coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]);
coffee.stdin.write(source).flush().close();
checkForErrors(coffee);
return coffee.stdout.read();
};
// Evaluating a string of CoffeeScript first compiles it externally.
exports.evalCS = function(source) {
return eval(exports.compile(source));
};
// Make a factory for the CoffeeScript environment.
exports.makeNarwhalFactory = function(path) {
var code = exports.compileFile(path);
var factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}";
if (system.engine === "rhino") {
return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null);
} else {
// eval requires parenthesis, but parenthesis break compileFunction.
return eval("(" + factoryText + ")");
}
};
})();

View File

@@ -0,0 +1,3 @@
(function(){
require("coffee-script").run(system.args);
})();

View File

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

View File

@@ -0,0 +1 @@
require("coffee-script").run(system.args)

View File

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

706
lib/coffee_script/nodes.rb Normal file
View File

@@ -0,0 +1,706 @@
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(o={})
indent = o[:no_wrap] ? '' : TAB
code = compile(o.merge(:indent => indent, :scope => Scope.new))
code.gsub!(STRIP_TRAILING_WHITESPACE, '')
o[:no_wrap] ? code : "(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(options) 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, '')
arg_part = args.empty? ? '' : ", #{args}"
"#{o[:proto_assign]}.prototype.__proto__.#{methname}.call(this#{arg_part})"
end
end
# Node to extend an object's prototype with an ancestor object.
class ExtendsNode < Node
attr_reader :sub_object, :super_object
def initialize(sub_object, super_object)
@sub_object, @super_object = sub_object, super_object
end
def compile(o={})
"#{@sub_object.compile(o)}.prototype.__proto__ = #{@super_object.compile(o)}"
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+/
PROTO_ASSIGN = /\A(\S+)\.prototype/
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.to_s
last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s
proto = name[PROTO_ASSIGN, 1]
o = o.merge(:assign => name, :last_assign => last, :proto_assign => proto)
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? || o[:no_wrap] ? "" : "var #{name};\n#{o[:indent]}"
return write(def_part + @value.compile(o)) if @value.custom_assign?
def_part = defined || o[:no_wrap] ? 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' => '===',
"isnt" => "!==",
'not' => '!',
'+:' => '+=',
'-:' => '-=',
'*:' => '*=',
'/:' => '/=',
'%:' => '%='
}
CONDITIONALS = ['||:', '&&:']
PREFIX_OPERATORS = ['typeof', 'delete']
attr_reader :operator, :first, :second
def initialize(operator, first, second=nil, flip=false)
@first, @second, @flip = first, second, flip
@operator = CONVERSIONS[operator] || 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)
o.delete(:no_wrap)
@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)
o.delete(:return)
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

View 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

2087
lib/coffee_script/parser.rb Normal file

File diff suppressed because it is too large Load Diff

View 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

View 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
View File

@@ -1,455 +0,0 @@
class Scope
attr_reader :parent, :temp_variable
def initialize(parent=nil)
@parent = parent
@variables = {}
@temp_variable = @parent ? @parent.temp_variable : 'a'
end
# Look up a variable in lexical scope, or declare it if not found.
def find(name, remote=false)
found = check(name, remote)
return found if found || remote
@variables[name] = true
found
end
# Just check for the pre-definition of a variable.
def check(name, remote=false)
return true if @variables[name]
@parent && @parent.find(name, true)
end
# Find an available, short variable name.
def free_variable
@temp_variable.succ! while check(@temp_variable)
@variables[@temp_variable] = true
@temp_variable.dup
end
end
class Node
# Tabs are two spaces for pretty-printing.
TAB = ' '
def flatten; self; end
def line_ending; ';'; end
def statement?; false; end
def custom_return?; false; end
def custom_assign?; false; end
def compile(indent='', scope=nil, opts={}); end
end
# Collection of nodes each one representing an expression.
class Nodes < Node
attr_reader :nodes
def self.wrap(node)
node.is_a?(Nodes) ? node : Nodes.new([node])
end
def initialize(nodes)
@nodes = nodes
end
def <<(node)
@nodes << node
self
end
def flatten
@nodes.length == 1 ? @nodes.first : self
end
def begin_compile
"(function(){\n#{compile(TAB, Scope.new)}\n})();"
end
# Fancy to handle pushing down returns recursively to the final lines of
# inner statements (to make expressions out of them).
def compile(indent='', scope=nil, opts={})
return begin_compile unless scope
@nodes.map { |n|
if opts[:return] && n == @nodes.last
if n.statement? || n.custom_return?
"#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}"
else
"#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}"
end
else
"#{indent}#{n.compile(indent, scope)}#{n.line_ending}"
end
}.join("\n")
end
end
# Literals are static values that have a Ruby representation, eg.: a string, a number,
# true, false, nil, etc.
class LiteralNode < Node
def initialize(value)
@value = value
end
def compile(indent, scope, opts={})
@value.to_s
end
end
class ReturnNode < Node
def initialize(expression)
@expression = expression
end
def custom_return?
true
end
def compile(indent, scope, opts={})
compiled = @expression.compile(indent, scope)
@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}"
end
end
# Node of a method call or local variable access, can take any of these forms:
#
# method # this form can also be a local variable
# method(argument1, argument2)
# receiver.method
# receiver.method(argument1, argument2)
#
class CallNode < Node
def initialize(variable, arguments=[])
@variable, @arguments = variable, arguments
end
def new_instance
@new = true
self
end
def compile(indent, scope, opts={})
args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ')
prefix = @new ? "new " : ''
"#{prefix}#{@variable.compile(indent, scope)}(#{args})"
end
end
class ValueNode < Node
def initialize(name, properties=[])
@name, @properties = name, properties
end
def <<(other)
@properties << other
self
end
def properties?
return !@properties.empty?
end
def compile(indent, scope, opts={})
[@name, @properties].flatten.map { |v|
v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s
}.join('')
end
end
class AccessorNode
def initialize(name)
@name = name
end
def compile(indent, scope, opts={})
".#{@name}"
end
end
class IndexNode
def initialize(index)
@index = index
end
def compile(indent, scope, opts={})
"[#{@index.compile(indent, scope)}]"
end
end
class SliceNode
def initialize(from, to)
@from, @to = from, to
end
def compile(indent, scope, opts={})
".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)"
end
end
# Setting the value of a local variable.
class AssignNode < Node
def initialize(variable, value, context=nil)
@variable, @value, @context = variable, value, context
end
def custom_return?
true
end
def compile(indent, scope, opts={})
value = @value.compile(indent + TAB, scope)
return "#{@variable}: #{value}" if @context == :object
name = @variable.compile(indent, scope)
return "#{name} = #{value}" if @variable.properties?
defined = scope.find(name)
postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : ''
def_part = defined ? "" : "var #{name};\n#{indent}"
return def_part + @value.compile(indent, scope, opts.merge(:assign => name)) if @value.custom_assign?
def_part = defined ? name : "var #{name}"
"#{def_part} = #{@value.compile(indent, scope)}#{postfix}"
end
end
# Simple Arithmetic and logical operations
class OpNode < Node
CONVERSIONS = {
"==" => "===",
"!=" => "!==",
'and' => '&&',
'or' => '||',
'is' => '===',
"aint" => "!==",
'not' => '!',
}
CONDITIONALS = ['||=', '&&=']
def initialize(operator, first, second=nil)
@first, @second = first, second
@operator = CONVERSIONS[operator] || operator
end
def unary?
@second.nil?
end
def compile(indent, scope, opts={})
return compile_conditional(indent, scope) if CONDITIONALS.include?(@operator)
return compile_unary(indent, scope) if unary?
"#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}"
end
def compile_conditional(indent, scope)
first, second = @first.compile(indent, scope), @second.compile(indent, scope)
sym = @operator[0..1]
"#{first} = #{first} #{sym} #{second}"
end
def compile_unary(indent, scope)
"#{@operator}#{@first.compile(indent, scope)}"
end
end
# Method definition.
class CodeNode < Node
def initialize(params, body)
@params = params
@body = body
end
def compile(indent, scope, opts={})
code = @body.compile(indent + TAB, Scope.new(scope), {:return => true})
"function(#{@params.join(', ')}) {\n#{code}\n#{indent}}"
end
end
class ObjectNode < Node
def initialize(properties = [])
@properties = properties
end
def compile(indent, scope, opts={})
props = @properties.map {|p| indent + TAB + p.compile(indent, scope) }.join(",\n")
"{\n#{props}\n#{indent}}"
end
end
class ArrayNode < Node
def initialize(objects=[])
@objects = objects
end
def compile(indent, scope, opts={})
objects = @objects.map {|o| o.compile(indent, scope) }.join(', ')
"[#{objects}]"
end
end
# "if-else" control structure. Look at this node if you want to implement other control
# structures like while, for, loop, etc.
class IfNode < Node
FORCE_STATEMENT = [Nodes, ReturnNode, AssignNode, IfNode]
def initialize(condition, body, else_body=nil, tag=nil)
@condition = condition
@body = body && body.flatten
@else_body = else_body && else_body.flatten
@condition = OpNode.new("!", @condition) if tag == :invert
end
def <<(else_body)
eb = else_body.flatten
@else_body ? @else_body << eb : @else_body = eb
self
end
# Rewrite a chain of IfNodes with their switch condition for equality.
def rewrite_condition(expression)
@condition = OpNode.new("is", expression, @condition)
@else_body.rewrite_condition(expression) if chain?
self
end
# Rewrite a chain of IfNodes to add a default case as the final else.
def add_default(expressions)
chain? ? @else_body.add_default(expressions) : @else_body = expressions
self
end
def chain?
@chain ||= @else_body && @else_body.is_a?(IfNode)
end
def statement?
@is_statement ||= (FORCE_STATEMENT.include?(@body.class) || FORCE_STATEMENT.include?(@else_body.class))
end
def line_ending
statement? ? '' : ';'
end
def compile(indent, scope, opts={})
statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope)
end
def compile_statement(indent, scope, opts)
if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}"
else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : ''
if_part + else_part
end
def compile_ternary(indent, scope)
if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}"
else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null'
"#{if_part} : #{else_part}"
end
end
class WhileNode < Node
def initialize(condition, body)
@condition, @body = condition, body
end
def line_ending
''
end
def statement?
true
end
def compile(indent, scope, opts={})
"while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}"
end
end
class ForNode < Node
def initialize(body, source, name, index=nil)
@body, @source, @name, @index = body, source, name, index
end
def line_ending
''
end
def custom_return?
true
end
def custom_assign?
true
end
def compile(indent, scope, opts={})
svar = scope.free_variable
ivar = scope.free_variable
lvar = scope.free_variable
name_part = scope.find(@name) ? @name : "var #{@name}"
index_name = @index ? (scope.find(@index) ? @index : "var #{@index}") : nil
source_part = "var #{svar} = #{@source.compile(indent, scope)};"
for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
var_part = "\n#{indent + TAB}#{name_part} = #{svar}[#{ivar}];\n"
index_part = @index ? "#{indent + TAB}#{index_name} = #{ivar};\n" : ''
set_result = ''
save_result = ''
return_result = ''
if opts[:return] || opts[:assign]
rvar = scope.free_variable
set_result = "var #{rvar} = [];\n#{indent}"
save_result = "#{rvar}[#{ivar}] = "
return_result = rvar
return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign]
return_result = "return #{return_result}" if opts[:return]
return_result = "\n#{indent}#{return_result}"
end
body = @body.compile(indent + TAB, scope)
"#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body};\n#{indent}}#{return_result}"
end
end
class TryNode < Node
def initialize(try, error, recovery, finally=nil)
@try, @error, @recovery, @finally = try, error, recovery, finally
end
def line_ending
''
end
def statement?
true
end
def compile(indent, scope, opts={})
catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}"
finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}"
"try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}"
end
end
class ThrowNode < Node
def initialize(expression)
@expression = expression
end
def compile(indent, scope, opts={})
"throw #{@expression.compile(indent, scope)}"
end
end
class ParentheticalNode < Node
def initialize(expressions)
@expressions = expressions
end
def compile(indent, scope, opts={})
compiled = @expressions.flatten.compile(indent, scope)
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
opts[:no_paren] ? compiled : "(#{compiled})"
end
end

9
package.json Normal file
View File

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

1475
parser.rb

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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 isnt breaker.
obj.

31
test/fixtures/each.js vendored Normal file
View 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
View 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"], [:ISNT, "isnt"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]]

29
test/fixtures/each_no_wrap.js vendored Normal file
View File

@@ -0,0 +1,29 @@
// 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;
};

View File

@@ -0,0 +1,4 @@
nums: n * n for n in [1, 2, 3] if n % 2 isnt 0.
result: n * 2 for n in nums.
print(result.join(',') is '2,18')

View File

@@ -0,0 +1,6 @@
result: try
nonexistent * missing
catch error
true.
print(result)

View File

@@ -0,0 +1,23 @@
Base: => .
Base.prototype.func: string =>
'zero/' + string.
FirstChild: => .
FirstChild extends new Base()
FirstChild.prototype.func: string =>
super('one/') + string.
SecondChild: => .
SecondChild extends new FirstChild()
SecondChild.prototype.func: string =>
super('two/') + string.
ThirdChild: => .
ThirdChild extends new SecondChild()
ThirdChild.prototype.func: string =>
super('three/') + string.
result: (new ThirdChild()).func('four')
print(result is 'zero/one/two/three/four')

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

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

View 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: l for l in c.text.split('') if l is '-'.
c.single: c.list[1, 1][0].
print(func() == '-')

11
test/fixtures/execution/test_switch.cs vendored Normal file
View 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)

15
test/fixtures/inner_comments.cs vendored Normal file
View 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
View 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
View File

@@ -0,0 +1,5 @@
require 'lib/coffee-script'
class Test::Unit::TestCase
include CoffeeScript
end

View File

@@ -0,0 +1,23 @@
require 'test_helper'
class ExecutionTest < Test::Unit::TestCase
NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/
ALLS_WELL = /\A\n?(true\n)+\Z/
def test_execution_of_coffeescript
sources = ['test/fixtures/execution/*.cs'].join(' ')
assert `bin/coffee-script -r #{sources}`.match(ALLS_WELL)
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
View 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

80
test/unit/test_parser.rb Normal file
View File

@@ -0,0 +1,80 @@
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
def test_no_wrap
nodes = @par.parse(File.read('test/fixtures/each.cs'))
assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/each_no_wrap.js')
end
end

View File

@@ -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;
};
()