Compare commits

..

318 Commits
0.3.0 ... 0.5.4

Author SHA1 Message Date
Jeremy Ashkenas
44398d044f Updating docs for CoffeeScript 0.5.4. Tag it and bag it. 2010-03-03 23:01:53 -05:00
Jeremy Ashkenas
3feb874b1e Merge branch 'path_fix' of git://github.com/cehoffman/coffee-script 2010-03-03 20:17:58 -05:00
Chris Hoffman
f7183e6918 Use new node api for resolving a chain of symlinks 2010-03-03 16:34:17 -06:00
Samuel Reis
707cd2d734 updated command_line.js 2010-03-03 14:32:28 +01:00
Samuel Reis
c11c3ed2f2 fix: process.watchFile has moved to fs.watchFile 2010-03-03 14:31:30 +01:00
Jeremy Ashkenas
5fd0972b5d improvement to comment handling that should ensure that they have no effect on indentation 2010-03-02 19:23:21 -05:00
Jeremy Ashkenas
70cb195e6f rebuilding extras/coffee-script.js 2010-03-02 16:52:55 -05:00
Jeremy Ashkenas
c219adffd5 removing special rule from rewriter for naked functions in arrays 2010-03-02 00:43:01 -05:00
Jeremy Ashkenas
cd6dd5abfd couple more tweaks to lexer.coffee 2010-02-28 21:39:07 -05:00
Jeremy Ashkenas
29ece0e6ba better commenting the coffeescript lexer as the first trial for docco 2010-02-28 20:44:33 -05:00
Jeremy Ashkenas
45bad556ab defining __filename and __dirname correctly as local variables for eval'd scripts 2010-02-28 19:56:00 -05:00
Jeremy Ashkenas
30cf63ec92 updating Underscore.coffee to Underscore.js version 0.6.0 2010-02-28 15:12:18 -05:00
Jeremy Ashkenas
969c2e528d a number of refactors to the Lexer. It should be a good bit clearer to read now. 2010-02-28 13:34:52 -05:00
Jeremy Ashkenas
bb2bf7ce57 allowing chaining of property accesses by indentation level (really nice for Node and jQuery work) ticket #221 2010-02-28 12:49:37 -05:00
Jeremy Ashkenas
2969e156c7 fixing require paths in the Cakefile so that build:jison will work, even if you don't have it installed. 2010-02-28 10:37:12 -05:00
Jeremy Ashkenas
b08995cbcc fixing heredocs with multiple double quotes (broken regex from the Ruby translation), with tests. 2010-02-28 10:29:30 -05:00
Jeremy Ashkenas
47f71f9193 updating docs to say 'Node.js greater than 0.1.30' 2010-02-28 10:11:01 -05:00
Jeremy Ashkenas
fec2eaef7e removing the (now-unused) inherits helper, and adding a helper for creating LiteralNodes 2010-02-28 00:22:06 -05:00
Jeremy Ashkenas
56eb474bf3 If you don't specify a constructor, one will be provided for you by the state. 2010-02-28 00:13:17 -05:00
Jeremy Ashkenas
62b2ab29cd CoffeeScript 0.5.3, with classes 2010-02-27 20:21:46 -05:00
Jeremy Ashkenas
a35693554c updating documentation for classes 2010-02-27 20:12:47 -05:00
Jeremy Ashkenas
f7427259ee reserving __extends and __hasProp 2010-02-27 20:03:57 -05:00
Jeremy Ashkenas
e02ab76edf converting the remainder of the CoffeeScript compiler (Rewriter, Scope, Optparse) to use classes 2010-02-27 19:46:45 -05:00
Jeremy Ashkenas
9f46c306e5 super is now possible in nodes/Expressions, where it wasn't possible before. 2010-02-27 19:42:10 -05:00
Jeremy Ashkenas
b5c9d779bd updating the Lexer to use classes and some of the older documentation 2010-02-27 19:40:53 -05:00
Jeremy Ashkenas
d2cb1f321e making sure that the body of extends only gets defined once per file. 2010-02-27 19:29:34 -05:00
Jeremy Ashkenas
8f871a8218 converting the nodes.coffee AST to use the new class system 2010-02-27 19:19:53 -05:00
Jeremy Ashkenas
4ec7514d10 making inner comments work within class definitions 2010-02-27 19:03:23 -05:00
Jeremy Ashkenas
1c7e4c4203 first draft of adding classes to CoffeeScript 2010-02-27 18:57:45 -05:00
Jeremy Ashkenas
7d39fe1c56 fixing multiple evaluation of splat sources, when it's an invoked function 2010-02-27 15:15:26 -05:00
Jeremy Ashkenas
afa26c37f1 fixing regexp literals versus division, with tests 2010-02-27 14:30:14 -05:00
Jeremy Ashkenas
2f658ba925 fixing multiple single-line function forms on the same line 2010-02-27 11:03:43 -05:00
Jeremy Ashkenas
0ab810e4cb fancy new fullscreen version of 'Try CoffeeScript' 2010-02-27 02:21:26 -05:00
Jeremy Ashkenas
37d086b670 rebuilding the browser code in anticipation of a page push 2010-02-27 01:38:50 -05:00
Jeremy Ashkenas
5e7f5f390a adding a traverse method to the AST, so we can do fancy processing from external scripts. 2010-02-27 01:22:21 -05:00
Jeremy Ashkenas
f4cd0bdf29 making --run the default option for consistency. If you want to save the file, use -c or --compile. 2010-02-27 00:38:04 -05:00
Jeremy Ashkenas
723ea53585 adding the long-ago-needed documentation for constructors with 'return this' 2010-02-26 20:09:49 -05:00
Jeremy Ashkenas
93f644fae2 finishing the second half of prefix installs. Using readLink to refer to the CoffeeScript installation reliably. 2010-02-26 19:49:12 -05:00
Jeremy Ashkenas
82951a469b adding favicon to docs 2010-02-26 19:10:56 -05:00
Jeremy Ashkenas
d2d5f649d3 caching the length property lookup for vanilla array comprehensions and rebuilding docs 2010-02-25 23:39:14 -05:00
Jeremy Ashkenas
5c7526a741 moving some of the fs methods over to sync methods, where it's alright and where it makes things clearer 2010-02-25 21:53:42 -05:00
Jeremy Ashkenas
17ea48c543 first draft of options for Cakefiles, using optparse.coffee, as well as a cake install task that takes --prefix. Still need to fix the lib/bin scripts 2010-02-25 21:43:42 -05:00
Jeremy Ashkenas
55ed202957 no outline on the Try CoffeeScript input for safari 2010-02-25 21:10:32 -05:00
Jeremy Ashkenas
d5df5505f9 hide the error div on initial page load 2010-02-25 20:48:54 -05:00
Jeremy Ashkenas
dc7f4b4be0 new live 'Try CoffeeScript' 2010-02-25 20:39:34 -05:00
Jeremy Ashkenas
406a18067d allowing merged short flags in optparse.coffee, via normalize_arguments 2010-02-25 19:06:08 -05:00
Jeremy Ashkenas
3ae2ebe5ea Merge branch 'fix_example_webserver' of git://github.com/hugs/coffee-script 2010-02-25 18:56:11 -05:00
Jeremy Ashkenas
a23dc6b753 raising an error on unrecognized options 2010-02-25 18:54:08 -05:00
Jeremy Ashkenas
5f1d3fd775 updating docs with the new flags 2010-02-25 18:43:33 -05:00
Jeremy Ashkenas
9d4e06e8a8 moving -tr --tree to -n --nodes, and --no-wrap gives up its -n short flag. 2010-02-25 18:42:35 -05:00
Jason Huggins
6bc61ec1a1 Fixed web_server example to be compatible with Node v0.1.30 2010-02-25 17:42:05 -06:00
Jeremy Ashkenas
c62f93f930 improving errors for undefined options, and error messages for compile attempts on nonexistent files 2010-02-25 18:36:43 -05:00
Jeremy Ashkenas
213ae1430e using text instead of html to escape entities in the rendered JS for Try CoffeeScript 2010-02-25 18:20:15 -05:00
Jeremy Ashkenas
ee5d738827 doc update 2010-02-25 17:18:28 -05:00
Jeremy Ashkenas
eab9bbf04f adding a test for nested pattern matching 2010-02-25 07:31:33 -05:00
Jeremy Ashkenas
4ed51536bb fixing patternmatched assigns within assigns within calls 2010-02-25 07:28:48 -05:00
Jeremy Ashkenas
b32a60585b improving CoffeeScript in browser script activation, and updating docs 2010-02-25 06:26:27 -05:00
Jeremy Ashkenas
66a6568fe7 cleaning and shrinking the option parser 2010-02-25 06:15:58 -05:00
Jeremy Ashkenas
fe32146adc cleaning and commenting cake.coffee 2010-02-25 01:17:43 -05:00
Jeremy Ashkenas
69feac3a01 adding return values for destructuring assignment. 2010-02-25 00:43:02 -05:00
Jeremy Ashkenas
05d95acfc3 docs for CoffeeScript 0.5.2, which is now out. 2010-02-25 00:26:59 -05:00
Jeremy Ashkenas
22674bc536 removing CoffeeScript.activate() simply including the tag will do for text/coffeescript 2010-02-24 23:57:39 -05:00
Jeremy Ashkenas
23c5ebb00f call it 'CoffeeScript' in the command_line, so that --run scripts may access it as such 2010-02-24 22:30:22 -05:00
Jeremy Ashkenas
c14869f008 implementing the inline javascript in the documentation page in text/coffeescript, switching from the closure compiler to the yui compressor for building the browser version -- the closure compiler had a bug for our input -- fixable by hand but not worth the tiny savings 2010-02-24 20:41:56 -05:00
Jeremy Ashkenas
c1427d6558 adding a minified combined coffee-script.js. Include it on the page, after any text/coffeescript tags, and call CoffeeScript.activate(); to run it 2010-02-24 19:57:29 -05:00
Jeremy Ashkenas
2a46e13d33 moving print_tokens (the pretty printer) from coffee_script to command_line 2010-02-24 18:56:32 -05:00
Jeremy Ashkenas
b26e577244 adding documentation for --stdio 2010-02-24 18:27:10 -05:00
Jeremy Ashkenas
9f8710b631 adding compilation over stdin/stdout. Use --stdio or -s, and pipe away. 2010-02-24 18:18:29 -05:00
Jeremy Ashkenas
aba8cb1b08 upgrading the optparse library to avoid having to register callbacks for each argument. It just returns a simple options hash. 2010-02-24 17:57:58 -05:00
Jeremy Ashkenas
92cd80226c bumping to 0.5.1 2010-02-24 00:54:07 -05:00
Jeremy Ashkenas
10d335ccb1 adding existence soaks for indexed-lookup property accesses: obj?['property'] 2010-02-24 00:06:01 -05:00
Jeremy Ashkenas
4eeb8c4bd2 adding conditional while loops with while ... when 2010-02-23 22:53:43 -05:00
Jeremy Ashkenas
4d146bacb1 fixing throwing an error string in the Rewriter, when it should have been an Error object 2010-02-23 21:59:29 -05:00
Jeremy Ashkenas
7de4caffca removing a shift-reduce error from the grammar that worked its way in 2010-02-23 21:22:28 -05:00
Jeremy Ashkenas
8db0cb9fa5 rebuilding with new for spacing 2010-02-23 21:12:44 -05:00
Jeremy Ashkenas
c30b3d3c48 updating to the latest Jison 2010-02-23 21:03:05 -05:00
Jeremy Ashkenas
52db4fbf8c merging Tim-Smart's branch 2010-02-23 20:52:03 -05:00
Jeremy Ashkenas
5cd8f2c52c Merge branch 'master' of git://github.com/Tim-Smart/coffee-script 2010-02-23 20:51:29 -05:00
Jeremy Ashkenas
5a1aa44393 going back to == undefined instead of == null to appease the angry JSLint. 2010-02-22 22:19:17 -05:00
Tim
432696d6eb nodes.coffee: ForNode: Small whitespace change in for_part 2010-02-22 20:19:00 -07:00
Jeremy Ashkenas
3df7bd98f4 fixing issue 196, better handling of soak/existence chains 2010-02-22 22:11:47 -05:00
Jeremy Ashkenas
1f870911c9 Merge branch 'master' of git://github.com/cehoffman/coffee-script 2010-02-22 20:28:41 -05:00
Jeremy Ashkenas
4bb9392753 adding warning about Node.js/Windows to the docs. 2010-02-22 20:25:15 -05:00
Chris Hoffman
fdffacfb40 Make trailing else on switch fix pass on to multiple when switches 2010-02-22 19:17:54 -06:00
Jeremy Ashkenas
a64afe6162 fixing the trailing-else-in-switch-getting-sucked-in-bug, Issue 195. 2010-02-22 19:22:09 -05:00
Jeremy Ashkenas
15b86a5f7a doc updates 2010-02-22 17:14:51 -05:00
Jeremy Ashkenas
9b78fb67cf allowing checked out versions of the source to use bin/cake and bin/coffee from any directory. 2010-02-22 09:12:52 -05:00
Jeremy Ashkenas
4817b96bac fixing the build:ultraviolet task in the Cakefile 2010-02-22 09:09:35 -05:00
Jeremy Ashkenas
6985802eb3 making the docs a little less jumpy with more stable menus 2010-02-21 23:29:31 -05:00
Jeremy Ashkenas
aad0ce162d ensuring that we don't write --eval scriptlets out to disk -- they should print, if nothing else 2010-02-21 22:41:19 -05:00
Chris Hoffman
f582b73035 Keep the correct state for watching files so the appropriate one is recompiled 2010-02-21 19:00:48 -06:00
Jeremy Ashkenas
e795f41bd2 wrapping up loose ends for 0.5.0 2010-02-21 19:28:12 -05:00
Jeremy Ashkenas
5d541232ef adding /usr/local/lib hardcoded versions of coffee and cake for the install script 2010-02-21 19:19:58 -05:00
Jeremy Ashkenas
07513ba928 documentation for Cake and Cakefiles 2010-02-21 18:37:52 -05:00
Jeremy Ashkenas
aded80c101 list padding for the docs 2010-02-21 17:59:17 -05:00
Jeremy Ashkenas
5d893947ea new version of the docs for 0.5.0 is done, or close to it. 2010-02-21 17:30:41 -05:00
Jeremy Ashkenas
535cf28220 one more documentation waypoint, now with running for Try CoffeeScript, and a recompiled Underscore.coffee -- wordsmithing next 2010-02-21 16:36:34 -05:00
Jeremy Ashkenas
dbe5328c33 removing the 'var' declaration from arguments-to-array conversions 2010-02-21 16:15:01 -05:00
Jeremy Ashkenas
6e15a4da0e waypoint on new coffeescript docs --they're coming along nicely 2010-02-21 16:13:09 -05:00
Jeremy Ashkenas
fc51f0ef6c first working draft of the new documentation 2010-02-21 14:30:21 -05:00
Jeremy Ashkenas
1f2c8df5fa updating to latest Node (0.1.3) API -- callbacks, not promises 2010-02-21 14:06:01 -05:00
Jeremy Ashkenas
bea40a7a92 re-enabling the --no-wrap flag, and cleaning up an unused method in command_line 2010-02-21 13:48:38 -05:00
Jeremy Ashkenas
f679590bef rengenerating documentation 2010-02-21 11:46:24 -05:00
Jeremy Ashkenas
bd61131f5c regenerating documentation 2010-02-21 11:45:03 -05:00
Jeremy Ashkenas
ff25361896 removing the build:parser -> build dependency in the Cakefile -- the asynchrony was breaking it anyway 2010-02-21 11:40:52 -05:00
Jeremy Ashkenas
08dcc7e107 removing commented-out parseerror 2010-02-21 11:22:49 -05:00
Jeremy Ashkenas
b027b5cf0d Allowing @[property] syntax. 2010-02-20 20:09:52 -05:00
Jeremy Ashkenas
0f2a2ee11e Improving soaks to avoid uncessesary parentheses. 2010-02-20 20:00:07 -05:00
Jeremy Ashkenas
a93229b14d fixing an off-by-one error in Splat compilation 2010-02-20 18:25:36 -05:00
Jeremy Ashkenas
2d3f6b80c1 Revert "fixing api changes (no more promises) for node v.0.1.29"
This reverts commit 9503ea3040.
2010-02-20 18:12:09 -05:00
alunny
9503ea3040 fixing api changes (no more promises) for node v.0.1.29 2010-02-20 14:45:16 -08:00
Jeremy Ashkenas
08539a156e merging cehoffman/master 2010-02-19 18:30:47 -05:00
Jeremy Ashkenas
b183b091ee removing redundant compile from command_line.coffee 2010-02-19 18:29:24 -05:00
Jeremy Ashkenas
c39415da44 using the new fs.writeFile API instead of fs.open -- much, much nicer 2010-02-19 18:27:50 -05:00
Chris Hoffman
2c461f6474 Simpler write method that assumes utf8 output like input reading does 2010-02-19 16:03:37 -06:00
Chris Hoffman
e213964793 No need to recompile the script for writing 2010-02-19 16:02:58 -06:00
Jeremy Ashkenas
dd753d3b78 Removing the notion of a ThisNode so that we don't have to worry about the special cases. Fixes Issue 180 2010-02-19 07:51:52 -05:00
Jeremy Ashkenas
45c0d4c2ea fixing standalone @ references 2010-02-18 21:50:42 -05:00
Jeremy Ashkenas
4f89f90dab adding syntax highlighting for standalone @ts 2010-02-18 21:32:54 -05:00
Jeremy Ashkenas
0270e48a01 using '== null' to check for soaked accessor chains instead of '== undefined' -- shorter, and slightly safer 2010-02-18 21:04:41 -05:00
Jeremy Ashkenas
a278d8f018 adding an extra pair of braces to the code produced by object comprehensions, so as to squeak by in JSLint's good graces 2010-02-18 20:22:53 -05:00
Jeremy Ashkenas
d4a180c413 carefully categorizing JS keywords from JS reserved words from Coffee keywords, so that we can throw syntax errors at compile time if JS keywords are getting assigned to. 2010-02-18 20:09:41 -05:00
Jeremy Ashkenas
28a8c05513 light refactor for repl.coffee 2010-02-17 23:51:43 -05:00
Jeremy Ashkenas
879b3d76bd light refactors to scope.coffee 2010-02-17 23:37:39 -05:00
Jeremy Ashkenas
138692183b removing the 'remote' parameter from Scope::find -- it wasn't used anymore 2010-02-17 23:26:03 -05:00
Jeremy Ashkenas
95f3e2f79f just namespacing the coffeescript compiler 2010-02-17 23:25:17 -05:00
Jeremy Ashkenas
dec9950649 Revert "namespacing CoffeeScript in the browser, and sniffing the 'require' function instead of the 'exports' object"
This reverts commit 2f6b69b580.
2010-02-17 23:22:05 -05:00
Jeremy Ashkenas
2f6b69b580 namespacing CoffeeScript in the browser, and sniffing the 'require' function instead of the 'exports' object 2010-02-17 23:15:37 -05:00
Jeremy Ashkenas
ff1fd97924 removing traces of Ruby from coffee-script.coffee, redoing narwhal support to use the new compiler (but untested) 2010-02-17 22:37:56 -05:00
Jeremy Ashkenas
87e60dccf0 starting to cache fancy switch values -- fixing issue #171 2010-02-17 21:23:59 -05:00
Jeremy Ashkenas
8ff977dc65 making 'no such task' errors a little nicer 2010-02-17 20:37:30 -05:00
Jeremy Ashkenas
fbfa12c733 moving 'throw' to a slightly higher level of precedence 2010-02-17 19:35:34 -05:00
Jeremy Ashkenas
6a45d25777 adding a test for parenthesized throws 2010-02-17 19:29:37 -05:00
Jeremy Ashkenas
2b5d596e10 allowing parenthetical nodes to wrap statements without necessarily expression-izing them -- tests are passing ... we'll see how this works in practice. 2010-02-17 19:19:51 -05:00
Jeremy Ashkenas
e2a71d3c2c removing the '--' from /usr/bin/env node, until we can find a workaround for linux. 2010-02-17 19:05:22 -05:00
Jeremy Ashkenas
807e0b479d add a note about installing Node.js first 2010-02-17 10:29:13 -05:00
Jeremy Ashkenas
dfa63839bb Updating fs module to the latest Node.js -- that's fs.readFile, not fs.cat, with string flags for fs.open 2010-02-17 08:51:27 -05:00
Jeremy Ashkenas
0490cb2920 added experimental (but working) method to install CoffeeScript: 'sudo bin/cake install' -- once you've done that, you can take cake and coffee out of their bin/ 2010-02-17 01:24:02 -05:00
Jeremy Ashkenas
2d0ad73af8 reorganizing lib/coffee_script to plain ol' lib 2010-02-17 00:55:56 -05:00
Jeremy Ashkenas
5a81fcd42e we don't need relative require's because the paths are set up correctly -- removing the unused runner.coffee script 2010-02-17 00:50:08 -05:00
Jeremy Ashkenas
6446e0004c The Great Purge. Removing the Ruby compiler, and all of its accoutrements. bin/coffee is now CoffeeScript-in-CoffeeScript 2010-02-17 00:33:55 -05:00
Jeremy Ashkenas
edf5f4947e removing all tasks from the Rakefile except for 'doc' ... we might need to keep that around for a while 2010-02-17 00:26:03 -05:00
Jeremy Ashkenas
b674163a40 adding build:ultraviolet and build:underscore tasks to the Cakefile 2010-02-17 00:22:06 -05:00
Jeremy Ashkenas
9f2badb3e9 got a really nice --watch flag for bin/node_coffee going, thanks to Node.js' process.watchFile. Can be used with --print, --lint, --tokens, --run, or whatever your needs may be 2010-02-16 23:59:32 -05:00
Jeremy Ashkenas
0610e20a3c fixing the extends keyword when the expressions are complex, and should only be run once -- not that it's good style -- ticket #143 2010-02-16 23:23:43 -05:00
Jeremy Ashkenas
bedc005d67 Adding a CoffeeScript equivalent to Rake/Make/Jake (Cake, naturally), and implementing all of our build and test tasks in the Cakefile. Run bin/cake to see the tasks. 2010-02-16 20:42:10 -05:00
Jeremy Ashkenas
a8a46257ae finished converting the test suite, to run it, do: bin/node_coffee -r tasks.coffee -- test 2010-02-16 19:45:25 -05:00
Jeremy Ashkenas
b41afe79b4 removed ruby tests, and started the process of converting the CoffeeScript tests over to use Node's assert() module 2010-02-16 19:38:52 -05:00
Jeremy Ashkenas
448ed36cd2 got a build script for rebuilding the compiler, too 2010-02-16 19:17:57 -05:00
Jeremy Ashkenas
c4d19cd1fa removing underscore.js from lib, and from the docs page 2010-02-16 18:39:31 -05:00
Jeremy Ashkenas
495ca64c46 removing underscore as a dependency for nodes.coffee -- let's be minimal 2010-02-16 18:38:03 -05:00
Jeremy Ashkenas
0f2cf552e9 using node's process.compile for better filename reporting on errors 2010-02-16 18:16:58 -05:00
Jeremy Ashkenas
f6a1f16146 Merge branch 'master' of git://github.com/olsonjeffery/coffee-script 2010-02-16 18:06:29 -05:00
Jeremy Ashkenas
db6bc0ba02 Implementing sethaurus' suggestion for better temp variable names -- getting rid of the numbers. 2010-02-16 18:00:40 -05:00
Jeremy Ashkenas
e4bb6c91e7 adding the list of javascript reserved words as syntax errors in CoffeeScript, so that no one uses them accidentally. 2010-02-16 17:41:38 -05:00
Jeremy Ashkenas
561a02c35f updating rewrite rules for jots multiline function case 2010-02-16 14:56:17 -05:00
Jeremy Ashkenas
51e80484e2 fixing an overzealous access in the rewriter 2010-02-16 10:12:40 -05:00
Jeremy Ashkenas
79fa4723ab use a temp var for range expansion, instead of 'i' 2010-02-16 08:58:29 -05:00
Jeremy Ashkenas
a3c8c0b492 allowing keywords as identifiers, when used as identifiers, because we've got more keywords than JavaScript does. 2010-02-16 08:43:58 -05:00
Jeremy Ashkenas
2f389f1d51 beginnings of a build script 2010-02-16 01:04:48 -05:00
Jeffery Olson
85e5dffad5 adding updated coffee.vim syntax file 2010-02-15 21:32:15 -08:00
Jeremy Ashkenas
fa63288f52 fixed the while-loop-condition-with-implicit-function-call bug 2010-02-15 23:05:54 -05:00
Jeremy Ashkenas
4ea8be8e0b switched from alphabetical __a __b temporary variables, to numeric _1, _2, which will be shorter in most cases 2010-02-15 21:55:57 -05:00
Jeremy Ashkenas
48c501a7a2 print_tokens should acutally, y'know, print 2010-02-15 20:46:36 -05:00
Jeremy Ashkenas
63c2b2bc64 adding paren wrappers for property accesses directly on object literals 2010-02-15 19:13:08 -05:00
Jeremy Ashkenas
639be2ff09 updating for Node.js' change from 'posix' to 'fs' 2010-02-15 19:08:14 -05:00
Jeremy Ashkenas
8b1b3ea402 updating docs with #coffeescript channel 2010-02-15 18:58:27 -05:00
Jeremy Ashkenas
9c3040b704 moving print_tokens into the actual coffee-script module, so we can use it separately from the command-line 2010-02-15 18:09:01 -05:00
Jeremy Ashkenas
e7291f57ba fixing allowing empty functions like () -> 2010-02-15 18:03:00 -05:00
Jeremy Ashkenas
3f6eceac77 whoops, screwed up the definition order in the inheritance example 2010-02-15 13:55:28 -05:00
Jeremy Ashkenas
ba7a454f92 adding a note to the docs about colon/equals equality 2010-02-15 13:43:35 -05:00
Jeremy Ashkenas
3092d74a08 updating the readme with a warning about the self-compiler 2010-02-14 23:40:18 -05:00
Jeremy Ashkenas
ff8e0c9751 added hugs' webserver example 2010-02-14 23:20:53 -05:00
Jeremy Ashkenas
3e518e3cf9 fixing long-flag-with-optional-part in optparse.coffee, and hiding bin/node_coffee --watch until we implement it 2010-02-14 20:50:45 -05:00
Jeremy Ashkenas
7667e16732 implementing grayrest's suggested cleanup for object comprehensions 2010-02-14 17:35:14 -05:00
Jeremy Ashkenas
e110042275 updated the rewriter to use @prop instead of this.prop -- compiled JS doesn't change 2010-02-14 16:09:11 -05:00
Jeremy Ashkenas
06677b0545 update the highlighted underscore to 0.5.8 2010-02-14 16:04:10 -05:00
Jeremy Ashkenas
20d105ba4e rename the repl id to try_coffe 2010-02-14 15:41:08 -05:00
Jeremy Ashkenas
e77f4f61aa removing the vendored optparse in favor of a pure-coffeescript optparse library 2010-02-14 15:16:33 -05:00
Jeremy Ashkenas
9de729e825 enabling the --eval option for the self-compiler 2010-02-13 23:27:13 -05:00
Jeremy Ashkenas
c39c2e3599 that's max-height 2010-02-13 19:14:28 -05:00
Jeremy Ashkenas
ecfa212189 updating Jison 2010-02-13 18:19:59 -05:00
Jeremy Ashkenas
97fd126a7f the improved error messages are still-to-be-improved 2010-02-13 16:28:07 -05:00
Jeremy Ashkenas
844c756940 working 'Try CoffeeScript' in the docs 2010-02-13 16:23:03 -05:00
Jeremy Ashkenas
a90bf75395 got coffeescript compiling in the browser 2010-02-13 15:25:04 -05:00
Jeremy Ashkenas
79bb0da153 add a -- to let us parse our own flags 2010-02-13 10:45:05 -05:00
Jeremy Ashkenas
c88b1f6a15 got the CoffeeScript-in-CoffeeScript REPL running, and boy is she fast 2010-02-13 10:27:18 -05:00
Jeremy Ashkenas
b224d58a36 don't make the command-line so OOP-y -- it's just a script 2010-02-13 10:16:28 -05:00
Jeremy Ashkenas
7d348b5eae display the usage message when called without arguments 2010-02-13 10:07:59 -05:00
Jeremy Ashkenas
02ac3edebf Nicer-looking --tokenize, more in line with what Ruby's doing 2010-02-13 09:59:13 -05:00
Jeremy Ashkenas
4bad3e0f4f nicer --tree printing, with values inlines to the right 2010-02-13 09:51:52 -05:00
Jeremy Ashkenas
8147ef554a after correctly recording objectnode's children, bin/node_coffee --tree is working pretty nicely 2010-02-13 09:46:07 -05:00
Jeremy Ashkenas
785c4fb5a0 recording else_body as a child of IfNode, when added after the face 2010-02-13 09:44:12 -05:00
Jeremy Ashkenas
13b2dc8d31 subtle bug in the order of the rewriter rules was causing some if/else chains to get confused by implicit indentation 2010-02-13 09:39:25 -05:00
Jeremy Ashkenas
a62923ff97 use Underscore's flatten -- start to make a nicer tree printer 2010-02-13 09:13:50 -05:00
Jeremy Ashkenas
dd6be80fca Use _.extend instead of the hand-rolled merge 2010-02-13 08:50:29 -05:00
Jeremy Ashkenas
8c077f0f65 using underscore's clone() for code generation 2010-02-13 02:32:17 -05:00
Jeremy Ashkenas
2c4c4cc93e using Underscore's compact in the code generation, insstead of our home-rolled one. 2010-02-13 02:30:47 -05:00
Jeremy Ashkenas
1ab3b183a8 Using underscore for an any() function. 2010-02-13 02:29:37 -05:00
Jeremy Ashkenas
e6a53bd852 fixing the lexer for indented files 2010-02-13 02:16:31 -05:00
Jeremy Ashkenas
b983b3fcdc moving Underscore.coffee from /examples to /src 2010-02-13 02:05:00 -05:00
Jeremy Ashkenas
89cac4071e updating underscore.coffee to 0.5.8 2010-02-13 02:03:48 -05:00
Jeremy Ashkenas
506ea8aa52 adding an if/else/chain test 2010-02-13 02:00:39 -05:00
Jeremy Ashkenas
b965fcf32d The moment of truth: CoffeeScript has now compiled itself ten times over. 2010-02-13 01:13:08 -05:00
Jeremy Ashkenas
126f6c2d88 use parseInt to make octal numbers to keep JSLint happy 2010-02-13 01:04:53 -05:00
Jeremy Ashkenas
3dc456572b finally, think we have this if/else/if/else/else thing licke 2010-02-13 01:02:10 -05:00
Jeremy Ashkenas
0f26072ad0 alright, really fixing the comments this time 2010-02-13 00:28:16 -05:00
Jeremy Ashkenas
dc9cec2611 fixing object/comment printing 2010-02-13 00:24:26 -05:00
Jeremy Ashkenas
c9aeae757b pulling the lexer and rewriter updates without the grammar change 2010-02-13 00:15:34 -05:00
Jeremy Ashkenas
094c2682bd Revert "one more bit to pass -- futzing with the if/else chains"
This reverts commit 066ee52615.
2010-02-13 00:11:14 -05:00
Jeremy Ashkenas
066ee52615 one more bit to pass -- futzing with the if/else chains 2010-02-12 23:35:03 -05:00
Jeremy Ashkenas
ee1c9b284a typo 2010-02-12 23:10:51 -05:00
Jeremy Ashkenas
d9fba94983 added coffee --lint to the self-compiler's resume 2010-02-12 23:09:57 -05:00
Jeremy Ashkenas
e02bedcf82 adding the ability to write out compiled coffeescript to the command line 2010-02-12 22:59:21 -05:00
Jeremy Ashkenas
1552470413 Happy Birthday, CoffeeScript-in-CoffeeScript, you just passed the entire test suite. 2010-02-12 22:24:57 -05:00
Jeremy Ashkenas
e2f3c2259b fix prototype_access lexing 2010-02-12 22:21:22 -05:00
Jeremy Ashkenas
249bd99656 fixing if/else rules 2010-02-12 22:16:26 -05:00
Jeremy Ashkenas
b36196286a safer lexing for ruby, too 2010-02-12 21:06:00 -05:00
Jeremy Ashkenas
b21780b738 safer lexing at the start of the files 2010-02-12 21:04:33 -05:00
Jeremy Ashkenas
207ec81821 waypoint ... somethings still a little off with the parser 2010-02-12 19:45:20 -05:00
Jeremy Ashkenas
d61aaf393a little further 2010-02-12 17:31:23 -05:00
Jeremy Ashkenas
19c44c9b62 self-compiling the arguments test 2010-02-12 16:44:29 -05:00
Jeremy Ashkenas
4deabf5e01 passing the funky comment test 2010-02-12 16:26:28 -05:00
Jeremy Ashkenas
1a6194e9f0 self-compiling switch statements 2010-02-12 16:23:52 -05:00
Jeremy Ashkenas
156a0b13d9 self-compiling closures as expressions 2010-02-12 14:26:01 -05:00
Jeremy Ashkenas
61a7f7a567 self-compiling the existential operator 2010-02-12 14:15:14 -05:00
Jeremy Ashkenas
dbcb9df22b correctly self-compiling @references 2010-02-12 13:56:58 -05:00
Jeremy Ashkenas
e2ad1190ac Array comprehensions are alive and well on the self-compiler 2010-02-12 13:52:57 -05:00
Jeremy Ashkenas
c0f9058f15 range comprehensions are working smooth 2010-02-12 13:51:44 -05:00
Jeremy Ashkenas
12859e575a recompiling with latest jison (modified) and passing codenode generation 2010-02-12 13:35:34 -05:00
Jeremy Ashkenas
3f765c356a Revert "upgrading to latest jison, rebuilding the parser"
This reverts commit fd8b540a66.
2010-02-12 00:15:09 -05:00
Jeremy Ashkenas
fd8b540a66 upgrading to latest jison, rebuilding the parser 2010-02-12 00:12:46 -05:00
Jeremy Ashkenas
29267593c2 fixed heredoc lexing regex for JS, now passing test_heredocs.coffee 2010-02-12 00:06:41 -05:00
Jeremy Ashkenas
1e74805aa4 test_operations.coffee is now compiling successfully 2010-02-11 23:59:56 -05:00
Jeremy Ashkenas
12685aa54a test_functions.coffee compiles and runs successfully. 2010-02-11 23:57:31 -05:00
Jeremy Ashkenas
04f07f4c15 empty functions are legal code 2010-02-11 23:39:25 -05:00
Jeremy Ashkenas
df386a3b3f updated to latest jison, rebuilt parser 2010-02-11 23:34:45 -05:00
Jeremy Ashkenas
13c49ad865 waypoint -- jison has a reverse order of operations from yacc 2010-02-11 23:29:12 -05:00
Jeremy Ashkenas
7c01bba4f4 added the ability to print the parse tree 2010-02-11 23:11:05 -05:00
Jeremy Ashkenas
950d1199c2 self-compiling ranges and expressions 2010-02-11 20:20:59 -05:00
Jeremy Ashkenas
38d1381c02 self-compiling pattern matching correctly 2010-02-11 20:11:11 -05:00
Jeremy Ashkenas
98f15d001f got pattern matching working 2010-02-11 18:44:00 -05:00
Jeremy Ashkenas
a379530d41 disabling traces in the jison parser fixes the performance problems -- ahh, much better 2010-02-11 18:34:30 -05:00
Jeremy Ashkenas
e19b67cb79 still not there 2010-02-11 02:52:41 -05:00
Jeremy Ashkenas
e883a559ca uncomment test 2010-02-11 02:41:24 -05:00
Jeremy Ashkenas
713f6f32e1 done for now 2010-02-11 02:39:57 -05:00
Jeremy Ashkenas
872b36c11d things are in motion -- bin/node_coffee is the new JS-only command line ... it can pass some of the tests 2010-02-11 01:57:33 -05:00
Jeremy Ashkenas
f761c25dcd It's beginning to come alive. 2010-02-10 23:24:05 -05:00
Jeremy Ashkenas
38e1991f82 and now with if/else statements, CoffeeScript-in-CoffeeScript is language-complete -- now for the shakedown cruise 2010-02-10 21:40:10 -05:00
Jeremy Ashkenas
13d3b3a3ce now self-compiling array/object/range comprehensions. 2010-02-10 20:19:59 -05:00
Jeremy Ashkenas
0c2a13b468 self-compiler handles throws, the existential operator, and parentheticals. 2010-02-10 19:13:11 -05:00
Jeremy Ashkenas
4e7408dc25 self-compiler: handles try/catch/finally blocks 2010-02-10 18:33:03 -05:00
Jeremy Ashkenas
76dac9c09c got about as far as I think I can with operator nodes for now, without zach's optimizations 2010-02-10 00:05:56 -05:00
Jeremy Ashkenas
9339058fc3 raising the precedence level of delete, instanceof, and typeof 2010-02-09 22:20:04 -05:00
Jeremy Ashkenas
8e3e06a6e9 Self-compiler: now does while loops 2010-02-09 22:05:17 -05:00
Jeremy Ashkenas
fd80d784f4 Self-compiler can compile splats. 2010-02-09 21:44:34 -05:00
Jeremy Ashkenas
ae4f6309e8 Self-compiler: can now compile functions. 2010-02-09 21:31:07 -05:00
Jeremy Ashkenas
c466537a26 Self-compiler: array literals 2010-02-09 21:01:25 -05:00
Jeremy Ashkenas
001c915c21 Self-compiler: object literals. 2010-02-09 20:53:25 -05:00
Jeremy Ashkenas
91a7102f11 Self-compiler: array slice literals. 2010-02-09 19:30:28 -05:00
Jeremy Ashkenas
a451e90374 upgrading the test harness 2010-02-09 08:15:11 -05:00
Jeremy Ashkenas
c6a6788694 removing the constructor safety check -- it wasn't safe enough (Issue 36) 2010-02-09 07:59:48 -05:00
Jeremy Ashkenas
522df2a355 CoffeeScript-in-CoffeeScript can compile @property references. 2010-02-08 23:51:34 -05:00
Jeremy Ashkenas
863de88671 CoffeeScript-in-CoffeeScript can compile object[indexes] 2010-02-08 23:45:46 -05:00
Jeremy Ashkenas
cb57a1ca1f CoffeeScript-in-CoffeeScript can compile dotted accessors 2010-02-08 23:42:03 -05:00
Jeremy Ashkenas
32098e5a13 CoffeeScript-in-CoffeeScript is now able to compile subClass extends superClass 2010-02-08 23:16:29 -05:00
Jeremy Ashkenas
210d673ef0 CoffeeScript-in-CoffeeScript is compiling function calls 2010-02-08 22:55:56 -05:00
Jeremy Ashkenas
0b5b6113ee CoffeeScript-in-CoffeeScript just had it's first self-compiled snippet. 2010-02-08 22:22:59 -05:00
Jeremy Ashkenas
d5c98165ea removed an old now-impossible branch of execution from ReturnNode 2010-02-08 21:16:14 -05:00
Jeremy Ashkenas
b8d22bc572 finished up the CoffeeScript version of the Scope object 2010-02-08 21:10:48 -05:00
Jeremy Ashkenas
69808ba523 trying out a new organizational scheme, with a quickie 'inherits' helper function 2010-02-08 20:20:11 -05:00
Jeremy Ashkenas
aabfba9599 nodes.coffee is continuing to roll along -- maybe a tenth implemented 2010-02-08 19:49:39 -05:00
Jeremy Ashkenas
135620b14a bumping up the existential operator to the highest precedence level 2010-02-08 11:56:46 -05:00
Jeremy Ashkenas
78a4974de9 allowing numbers to be used as object keys 2010-02-08 11:48:43 -05:00
Jeremy Ashkenas
e2d75e6771 CoffeeScript 0.3.2, just in time for the Github feature 2010-02-08 10:58:49 -05:00
Jeremy Ashkenas
1aa966bba6 merging jeff olson's work 2010-02-07 15:45:05 -05:00
Jeremy Ashkenas
a347183f3d waypoint -- parser.coffee can parse basic functions 2010-02-07 15:37:05 -05:00
Jeremy Ashkenas
56499984ca waypoint -- it's beginning to parser 2010-02-07 15:15:36 -05:00
Jeremy Ashkenas
7ec0a8d653 merging node into master -- you can now pass the --narwhal flag to use narwhal instead. All tests are executing successfully against both Node.js and Narwhal/Rhino backends 2010-02-07 12:52:07 -05:00
Jeremy Ashkenas
293c2ffb5b spacing 2010-02-07 11:59:19 -05:00
Jeremy Ashkenas
5ec096e40d merging all narwhal integration into a single file, so we can merge the node branch without breaking narwhal compatibility 2010-02-07 11:54:01 -05:00
Jeremy Ashkenas
47bc1d5fda added a blocks example 2010-02-07 11:33:29 -05:00
Jeremy Ashkenas
6a59c5c9a9 merging master 2010-02-06 10:15:03 -05:00
Jeremy Ashkenas
decaea0f5f adding 'by' to array comprehensions 2010-02-06 10:12:57 -05:00
Jeremy Ashkenas
0a1873dc42 adding assign and return like they should have been 2010-02-05 22:39:39 -05:00
Jeremy Ashkenas
96eb7e2339 merging master 2010-02-05 22:02:11 -05:00
Jeremy Ashkenas
b795ae7fe1 removing arguments as a keyword -- we can detect its use at code-generation time. 2010-02-05 22:01:11 -05:00
Jeffery Olson
74b9545dc8 work on nodes.coffee and adding scope.coffee 2010-02-04 10:36:33 -08:00
Jeremy Ashkenas
0d56b89d12 Merge branch 'master' into node 2010-02-03 18:16:43 -05:00
Jeremy Ashkenas
dc7d0f1568 fixing assigning to @properties within an expression 2010-02-03 18:16:31 -05:00
Jeremy Ashkenas
9b7cfe87b5 remove parens 2010-02-02 20:44:25 -05:00
Jeremy Ashkenas
1587901367 remove parens 2010-02-02 20:44:10 -05:00
Jeremy Ashkenas
df670d47d2 merging in master 2010-02-02 20:38:44 -05:00
Jeremy Ashkenas
cb7a1033fa adding @property for this.property 2010-02-02 20:36:46 -05:00
Jeremy Ashkenas
df588bc9e8 it's puts in node, not print 2010-02-02 10:43:23 -05:00
Jeremy Ashkenas
9648ae2de1 merged in master 2010-02-02 10:41:19 -05:00
Jeremy Ashkenas
c5c841f2fc fixing bug with mixed dot and soak accessors 2010-02-02 10:39:44 -05:00
Jeffery Olson
c8ac7f0533 starting port of nodes.rb to coffee-script.. Node only, so far 2010-02-01 20:57:03 -08:00
Jeremy Ashkenas
681d4f44f4 simplifying order of operations a bit 2010-02-01 18:31:23 -05:00
Jeremy Ashkenas
db00cd6ed4 adding precedence to the Jison parser 2010-01-31 12:55:00 -05:00
Jeremy Ashkenas
3a748755df removing the peg grammar 2010-01-31 01:25:07 -05:00
Jeremy Ashkenas
a0572f161d the parser seems too big to compile 2010-01-30 23:17:36 -05:00
Jeremy Ashkenas
bad50c9aee the rewriter is done 2010-01-30 18:29:53 -05:00
Jeremy Ashkenas
c6457e010d getting there with the rewriter 2010-01-30 17:47:19 -05:00
Jeremy Ashkenas
557cdbba71 rewriter is halfway done, and working 2010-01-30 17:24:48 -05:00
Jeremy Ashkenas
84feab3492 first little piece of the rewriter 2010-01-30 17:02:05 -05:00
Jeremy Ashkenas
e755188878 mixing in sys again, for the tests 2010-01-30 16:14:13 -05:00
Jeremy Ashkenas
babeebcc1a more progress with the lexer, perhaps it's done 2010-01-30 15:56:40 -05:00
Jeremy Ashkenas
f19360c6b9 waypoint on lexing... parses basic strings, no indentation yet 2010-01-30 14:00:23 -05:00
Jeremy Ashkenas
eff2f4b520 a little further on with the lexer 2010-01-30 00:37:38 -05:00
Jeremy Ashkenas
854c796fd6 first little bit of the lexer 2010-01-30 00:08:15 -05:00
Jeremy Ashkenas
b0ecb39e9f made the path handling a little more robust 2010-01-29 23:41:18 -05:00
Jeremy Ashkenas
f5a37035cf node conversion finished, narwhal removed. 2010-01-29 23:30:54 -05:00
Jeremy Ashkenas
e08e99a403 implementing the Node REPL. 2010-01-29 22:53:44 -05:00
Jeremy Ashkenas
ba2d9df25f first draft of node.js REPL 2010-01-29 22:51:51 -05:00
Jeremy Ashkenas
83285fe170 updating resources section with rack-coffee 2010-01-27 16:38:49 -05:00
Jeremy Ashkenas
5b2ab36246 CoffeeScript 0.3.1, quick patch for 'instanceof' 2010-01-27 08:01:18 -05:00
Jeremy Ashkenas
2f3a94678f fixing instanceof, with a quick test 2010-01-27 07:55:40 -05:00
222 changed files with 17181 additions and 7577 deletions

73
Cakefile Normal file
View File

@@ -0,0 +1,73 @@
fs: require 'fs'
coffee: require 'coffee-script'
# Run a CoffeeScript through our node/coffee interpreter.
run: (args) ->
proc: process.createChildProcess 'bin/coffee', args
proc.addListener 'error', (err) -> if err then puts err
option '-p', '--prefix [DIR]', 'set the installation prefix for `cake install`'
task 'install', 'install CoffeeScript into /usr/local (or --prefix)', (options) ->
base: options.prefix or '/usr/local'
lib: base + '/lib/coffee-script'
exec([
'mkdir -p ' + lib
'cp -rf bin lib LICENSE README package.json src vendor ' + lib
'ln -sf ' + lib + '/bin/coffee ' + base + '/bin/coffee'
'ln -sf ' + lib + '/bin/cake ' + base + '/bin/cake'
].join(' && '), (err, stdout, stderr) ->
if err then print stderr
)
task 'build', 'build the CoffeeScript language from source', ->
files: fs.readdirSync 'src'
files: 'src/' + file for file in files when file.match(/\.coffee$/)
run ['-c', '-o', 'lib'].concat(files)
task 'build:parser', 'rebuild the Jison parser (run build first)', ->
require.paths.unshift 'vendor/jison/lib'
parser: require('grammar').parser
js: parser.generate()
parser_path: 'lib/parser.js'
fs.writeFile parser_path, js
task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter', ->
exec 'plist2syntax extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage', (err) ->
exec 'sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax'
task 'build:underscore', 'rebuild the Underscore.coffee documentation page', ->
exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html'
task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
exec 'rake browser', (err) ->
throw err if err
task 'doc', 'watch and continually rebuild the documentation', ->
exec 'rake doc'
task 'test', 'run the CoffeeScript language test suite', ->
process.mixin require 'assert'
test_count: 0
start_time: new Date()
[original_ok, original_throws]: [ok, throws]
process.mixin {
ok: (args...) -> test_count += 1; original_ok(args...)
throws: (args...) -> test_count += 1; original_throws(args...)
}
process.addListener 'exit', ->
time: ((new Date() - start_time) / 1000).toFixed(2)
puts '\033[0;32mpassed ' + test_count + ' tests in ' + time + ' seconds\033[0m'
fs.readdir 'test', (err, files) ->
for file in files
fs.readFile 'test/' + file, (err, source) ->
js: coffee.compile source
process.compile js, file

15
README
View File

@@ -22,20 +22,21 @@
CoffeeScript is a little language that compiles into JavaScript.
Install the compiler:
gem install coffee-script
Install Node.js, and then the CoffeeScript compiler:
sudo bin/cake install
Compile a script:
coffee /path/to/script.coffee
For documentation, usage, and examples, see:
http://jashkenas.github.com/coffee-script/
http://coffeescript.org/
To suggest a feature or report a bug:
To suggest a feature, report a bug, or general discussion:
http://github.com/jashkenas/coffee-script/issues/
If you'd like to chat, drop by #coffeescript on Freenode IRC,
or on webchat.freenode.net.
The source repository:
git://github.com/jashkenas/coffee-script.git
To build CoffeeScript from source, install the "racc" gem and
run "rake build:parser". Then bin/coffee will work.

View File

@@ -1,48 +1,15 @@
require 'erb'
require 'fileutils'
require 'rake/testtask'
desc "Run all tests"
task :test do
$LOAD_PATH.unshift(File.expand_path('test'))
require 'redgreen' if Gem.available?('redgreen')
require 'test/unit'
Dir['test/*/**/test_*.rb'].each {|test| require test }
end
namespace :build do
desc "Recompile the Racc parser (pass -v and -g for verbose debugging)"
task :parser, :racc_args do |t, args|
sh "racc #{args[:racc_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y"
end
desc "Compile the Narwhal interface for --interactive and --run"
task :narwhal do
sh "bin/coffee lib/coffee_script/narwhal/*.coffee -o lib/coffee_script/narwhal/lib/coffee-script"
sh "mv lib/coffee_script/narwhal/lib/coffee-script/coffee-script.js lib/coffee_script/narwhal/lib/coffee-script.js"
end
desc "Compile and install the Ultraviolet syntax highlighter"
task :ultraviolet do
sh "plist2syntax extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage"
sh "sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax"
end
desc "Rebuild the Underscore.coffee documentation page"
task :underscore do
sh "uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html"
end
end
require 'rubygems'
require 'yui/compressor'
desc "Build the documentation page"
task :doc do
source = 'documentation/index.html.erb'
child = fork { exec "bin/coffee documentation/coffee/*.coffee -o documentation/js -w" }
child = fork { exec "bin/coffee -c documentation/coffee/*.coffee -o documentation/js -w" }
at_exit { Process.kill("INT", child) }
Signal.trap("INT") { exit }
# `uv -t idle -s coffeescript -h examples/underscore.coffee > documentation/underscore.html`
loop do
mtime = File.stat(source).mtime
if !@mtime || mtime > @mtime
@@ -54,21 +21,11 @@ task :doc do
end
end
namespace :gem do
desc 'Build and install the coffee-script gem'
task :install do
verbose = "lib/coffee_script/parser.output"
FileUtils.rm(verbose) if File.exists?(verbose)
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
desc "Build the single concatenated and minified script for the browser"
task :browser do
sources = %w(rewriter.js lexer.js parser.js scope.js nodes.js coffee-script.js)
code = sources.map {|s| File.read('lib/' + s) }.join('')
code = YUI::JavaScriptCompressor.new.compress(code)
File.open('extras/coffee-script.js', 'w+') {|f| f.write(code) }
end
task :default => :test

9
bin/cake Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env node
process.mixin(require('sys'));
var path = require('path');
var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require.paths.unshift(lib);
require('cake').run();

View File

@@ -1,5 +1,9 @@
#!/usr/bin/env ruby
#!/usr/bin/env node
require "#{File.dirname(__FILE__)}/../lib/coffee_script/command_line.rb"
process.mixin(require('sys'));
var path = require('path');
var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
CoffeeScript::CommandLine.new
require.paths.unshift(lib);
require('command_line').run();

View File

@@ -1,27 +0,0 @@
Gem::Specification.new do |s|
s.name = 'coffee-script'
s.version = '0.3.0' # Keep version in sync with coffee-script.rb
s.date = '2010-1-26'
s.homepage = "http://jashkenas.github.com/coffee-script/"
s.summary = "The CoffeeScript Compiler"
s.description = <<-EOS
CoffeeScript is a little language that compiles into JavaScript. Think
of it as JavaScript's less ostentatious kid brother -- the same genes,
roughly the same height, but a different sense of style. Apart from a
handful of bonus goodies, statements in CoffeeScript correspond
one-to-one with their equivalent in JavaScript, it's just another
way of saying it.
EOS
s.authors = ['Jeremy Ashkenas']
s.email = 'jashkenas@gmail.com'
s.rubyforge_project = 'coffee-script'
s.has_rdoc = false
s.require_paths = ['lib']
s.executables = ['coffee']
s.files = Dir['bin/*', 'examples/*', 'extras/**/*', 'lib/**/*',
'coffee-script.gemspec', 'LICENSE', 'README', 'package.json']
end

View File

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

View File

@@ -1,5 +1,5 @@
# Eat lunch.
lunch: eat(food) for food in ['toast', 'cheese', 'wine']
lunch: eat food for food in ['toast', 'cheese', 'wine']
# Naive collision detection.
for roid in asteroids

View File

@@ -0,0 +1,5 @@
process.mixin require 'assert'
task 'test', 'run each of the unit tests', ->
for test in test_files
fs.readFile test, (err, code) -> eval coffee.compile code

View File

@@ -0,0 +1,29 @@
class Animal
move: (meters) ->
alert @name + " moved " + meters + "m."
class Snake extends Animal
constructor: (name) ->
@name: name
move: ->
alert "Slithering..."
super 5
class Horse extends Animal
constructor: (name) ->
@name: name
move: ->
alert "Galloping..."
super 45
sam: new Snake "Sammy the Python"
tom: new Horse "Tommy the Palomino"
sam.move()
tom.move()

View File

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

View File

@@ -0,0 +1,2 @@
String::dasherize: ->
this.replace(/_/g, "-")

View File

@@ -1,25 +0,0 @@
Animal: ->
Animal::move: (meters) ->
alert this.name + " moved " + meters + "m."
Snake: (name) -> this.name: name
Snake extends Animal
Snake::move: ->
alert "Slithering..."
super 5
Horse: (name) -> this.name: name
Horse extends Animal
Horse::move: ->
alert "Galloping..."
super 45
sam: new Snake "Sammy the Python"
tom: new Horse "Tommy the Palomino"
sam.move()
tom.move()

View File

@@ -1,147 +0,0 @@
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

@@ -1,29 +1,29 @@
body {
font-size: 14px;
line-height: 20px;
background: #f3f3f9;
color: #191933;
font-family: Arial, Helvetica, sans-serif;
}
div.container {
width: 950px;
margin: 50px 0 50px 50px;
margin: 100px 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;
}
ul {
padding-left: 20px;
}
b.header {
color: #000055;
display: block;
@@ -31,7 +31,7 @@ b.header {
font-size: 16px;
}
li {
margin-bottom: 7px;
margin-bottom: 10px;
}
table {
margin: 16px 0 0 13px; padding: 0;
@@ -43,7 +43,7 @@ table {
td {
padding: 9px 15px 9px 0;
}
code, pre, tt {
code, pre, tt, textarea {
font-family: Monaco, Consolas, "Lucida Console", monospace;
font-size: 12px;
line-height: 18px;
@@ -63,10 +63,15 @@ code, pre, tt {
padding: 3px 0 3px 12px;
font-size: 12px;
}
pre.no_bar {
border-left: 0;
margin-left: 0;
padding-left: 0;
}
div.code {
position: relative;
border: 1px solid #cacaca;
background: #fff;
background: #fafaff;
padding: 7px 0 10px 0;
-moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;
-webkit-box-shadow: 0px 0px 7px #cacaca;
@@ -75,13 +80,187 @@ div.code {
position: absolute;
right: 8px; bottom: 8px;
}
div.code pre {
div.code pre, div.code textarea {
float: left;
width: 450px;
background: #fafaff;
border-left: 1px dotted #559;
padding: 0 0 0 12px;
margin: 0;
}
div.code pre:first-child {
border-left: 0;
}
}
#fadeout {
z-index: 50;
position: fixed;
left: 0; top: 0; right: 0;
height: 100px;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 255)), to(rgba(255, 255, 255, 0)));
background: -moz-linear-gradient(top, rgba(255, 255, 255, 255), rgba(255, 255, 255, 0));
}
#flybar {
position: fixed;
z-index: 100;
height: 50px;
left: 40px; right: 40px; top: 25px;
background: #ddd;
padding-left: 235px;
background: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#d0d0d0));
background: -moz-linear-gradient(top, #f5f5f5, #d0d0d0);
filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr='#F5F5F5', EndColorStr='#D0D0D0');
-webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
}
#logo {
display: block;
width: 215px; height: 50px;
background: url('../images/logo.png');
position: absolute;
top: 0px; left: 10px;
}
#error {
position: absolute;
-webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px;
right: 15px; top: 15px; left: 565px;
height: 15px;
padding: 2px 5px;
background: #fdcdcc;
color: #864544;
border: 1px solid #864544;
font: 10px/15px Arial;
overflow: hidden;
text-transform: uppercase;
}
.navigation {
height: 50px;
font: bold 11px/50px Arial;
text-transform: uppercase;
position: relative;
float: left;
padding: 0 20px;
border: 1px solid #bbb;
border-top: 0; border-bottom: 0;
cursor: pointer;
}
body.full_screen .navigation {
position: static;
}
.navigation.try {
border-left: 0;
}
.navigation:hover,
.navigation.active {
background: #d0d0d0;
background: -webkit-gradient(linear, left top, left bottom, from(#f0f0f0), to(#c0c0c0));
background: -moz-linear-gradient(top, #f0f0f0, #c0c0c0);
filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr='#F0F0F0', EndColorStr='#C0C0C0');
}
.navigation .contents {
display: none;
position: absolute;
background: #fff;
top: 50px; left: 0;
padding: 5px 0;
-webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px;
-webkit-border-top-left-radius: 0; -moz-border-radius-topleft: 0;
-webkit-border-top-right-radius: 0; -moz-border-radius-topright: 0;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
}
.navigation.active .contents {
display: block;
}
.navigation .contents.repl_wrapper {
left: -162px;
width: 700px;
padding: 0;
}
body.full_screen .navigation .contents.repl_wrapper {
position: fixed;
width: auto; height: auto;
left: 60px; top: 75px; right: 60px; bottom: 30px;
}
.navigation .contents.repl_wrapper .code {
-webkit-box-shadow: none; -moz-box-shadow: none;
background: transparent;
border: 0;
position: static;
}
body.full_screen .navigation .contents.repl_wrapper .code {
height: 100%;
padding: 0; margin: 0;
}
.navigation .code button {
bottom: 10px;
text-transform: none;
}
.navigation .full_screen, .navigation .minimize {
right: auto;
left: 10px;
display: none;
}
body.minimized .full_screen, body.full_screen .minimize {
display: inline;
}
.navigation .contents a {
display: block;
width: 300px;
text-transform: none;
text-decoration: none;
font-weight: normal;
height: 12px;
line-height: 12px;
padding: 4px 10px;
}
.navigation .contents a:hover {
background: #f0f0f0;
}
.bookmark {
display: block;
width: 0; height: 0;
position: relative;
top: -90px;
}
#repl_source, #repl_results {
background: transparent;
outline: none;
margin: 5px 0 20px;
}
#repl_source_wrap {
margin-left: 5px;
height: 250px;
width: 307px;
position: relative;
float: left;
}
#repl_source {
width: 96%;
height: 100%;
border: 0;
resize: none;
}
#repl_results {
font-family: Monaco, Consolas, "Lucida Console", monospace;
text-transform: none;
font-weight: normal;
height: 260px;
margin-bottom: 25px;
overflow-y: auto;
width: 370px;
}
body.full_screen #repl_results, body.full_screen #repl_source_wrap {
width: auto; height: auto;
position: absolute;
margin-bottom: 0;
top: 10px; left: 10px; right: 10px; bottom: 40px;
}
body.full_screen #repl_source_wrap {
right: 50%;
}
body.full_screen #repl_results {
left: 50%;
}

View File

@@ -57,6 +57,7 @@ pre.idle .FunctionName {
color: #21439C;
}
pre.idle .Variable {
color: #A535AE;
}
pre.idle .Comment {
color: #919191;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,7 +1,7 @@
<%
require 'uv'
def code_for(file, executable=false)
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\Z|^ )/
@stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\n*\Z|^ )/
return '' unless File.exists?("documentation/js/#{file}.js")
cs = File.read("documentation/coffee/#{file}.coffee")
js = File.read("documentation/js/#{file}.js").gsub(@stripper, '')
@@ -22,12 +22,73 @@
<title>CoffeeScript</title>
<link rel="stylesheet" type="text/css" href="documentation/css/docs.css" />
<link rel="stylesheet" type="text/css" href="documentation/css/idle.css" />
<link rel="shortcut icon" href="documentation/images/favicon.ico" />
</head>
<body>
<body class="minimized">
<div id="fadeout"></div>
<div id="flybar">
<a id="logo" href="#top"> </a>
<div class="navigation">
<div class="button">
Table of Contents
</div>
<div class="contents">
<a href="#overview">Mini Overview</a>
<a href="#installation">Installation and Usage</a>
<a href="#language">Language Reference</a>
<a href="#whitespace">Significant Whitespace</a>
<a href="#functions">Functions and Invocation</a>
<a href="#assignment">Assignment</a>
<a href="#objects_and_arrays">Objects and Arrays</a>
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a>
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a>
<a href="#aliases">Aliases</a>
<a href="#splats">Splats...</a>
<a href="#arguments">Arguments are Arrays</a>
<a href="#while">While Loops</a>
<a href="#comprehensions">Comprehensions (Arrays, Objects, and Ranges)</a>
<a href="#slice_splice">Array Slicing and Splicing with Ranges</a>
<a href="#expressions">Everything is an Expression</a>
<a href="#existence">The Existential Operator</a>
<a href="#classes">Classes, Inheritance, and Super</a>
<a href="#pattern_matching">Pattern Matching</a>
<a href="#fat_arrow">Function Binding</a>
<a href="#embedded">Embedded JavaScript</a>
<a href="#switch">Switch/When/Else</a>
<a href="#try">Try/Catch/Finally</a>
<a href="#comparisons">Chained Comparisons</a>
<a href="#strings">Multiline Strings and Heredocs</a>
<a href="#cake">Cake, and Cakefiles</a>
<a href="#scripts">"text/coffeescript" Script Tags</a>
<a href="#resources">Resources</a>
<a href="#change_log">Change Log</a>
</div>
</div>
<div class="navigation try">
<div class="button">
Try CoffeeScript
</div>
<div class="contents repl_wrapper">
<div class="code">
<div id="repl_source_wrap"><textarea id="repl_source">reverse: (string) ->
string.split('').reverse().join ''
alert reverse '!tpircseeffoC'</textarea></div>
<pre id="repl_results"></pre>
<button class="full_screen">go full screen</button>
<button class="minimize">minimize</button>
<button class="run">run</button>
<br class="clear" />
</div>
</div>
</div>
<div id="error" style="display:none;"></div>
</div>
<div class="container">
<h1><sub style="font-size: 100px;">&#9749;</sub> CoffeeScript</h1>
<span class="bookmark" id="top"></span>
<p>
CoffeeScript is a little language that compiles into JavaScript. Think
@@ -39,8 +100,7 @@
<p>
<b>Disclaimer:</b>
CoffeeScript is just for fun and seriously alpha. I'm sure that there are still
plenty of holes in the walls and leaks in the roof. <i>There are no guarantees
CoffeeScript is just for fun. Until it reaches 1.0, <i>there are no guarantees
that the syntax won't change between versions.</i> That said,
it compiles into clean JavaScript (the good parts) that can use existing
JavaScript libraries seamlessly, and passes through
@@ -51,41 +111,13 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.3.0</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>
</p>
<h2>Table of Contents</h2>
<p>
<a href="#overview">Mini Overview</a><br />
<a href="#installation">Installation and Usage</a><br />
<a href="#whitespace">Significant Whitespace</a><br />
<a href="#functions">Functions and Invocation</a><br />
<a href="#assignment">Assignment</a><br />
<a href="#objects_and_arrays">Objects and Arrays</a><br />
<a href="#lexical_scope">Lexical Scoping and Variable Safety</a><br />
<a href="#conditionals">Conditionals, Ternaries, and Conditional Assignment</a><br />
<a href="#aliases">Aliases</a><br />
<a href="#splats">Splats...</a><br />
<a href="#arguments">Arguments are Arrays</a><br />
<a href="#while">While Loops</a><br />
<a href="#comprehensions">Comprehensions (Arrays, Objects, and Ranges)</a><br />
<a href="#slice_splice">Array Slicing and Splicing with Ranges</a><br />
<a href="#expressions">Everything is an Expression</a><br />
<a href="#existence">The Existential Operator</a><br />
<a href="#inheritance">Inheritance, and Calling Super from a Subclass</a><br />
<a href="#pattern_matching">Pattern Matching</a><br />
<a href="#fat_arrow">Function Binding</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="#comparisons">Chained Comparisons</a><br />
<a href="#strings">Multiline Strings and Heredocs</a><br />
<a href="#resources">Resources</a><br />
<a href="#change_log">Change Log</a><br />
</p>
<h2 id="overview">Mini Overview</h2>
<h2>
<span id="overview" class="bookmark"></span>
Mini Overview
</h2>
<p><i>CoffeeScript on the left, compiled JavaScript output on the right.</i></p>
@@ -104,22 +136,40 @@
<a href="http://github.com/jashkenas/coffee-script/tree/master/examples/">examples</a> folder.
</p>
<h2 id="installation">Installation and Usage</h2>
<h2>
<span id="installation" class="bookmark"></span>
Installation and Usage
</h2>
<p>
The CoffeeScript compiler is written in pure Ruby, and is available
as a Ruby Gem.
The CoffeeScript compiler is written in pure CoffeeScript, using a
<a href="http://github.com/jashkenas/coffee-script/blob/master/src/grammar.coffee">small DSL</a>
on top of the <a href="http://github.com/zaach/jison">Jison parser generator</a>, and is available
as a <a href="http://nodejs.org/">Node.js</a> utility. The core compiler however,
does not depend on Node, and can be run in other server-side-JavaScript environments,
or in the browser (see "Try CoffeeScript", above). This may be helpful,
as Node only run on flavors of nix, and not Windows, for the time being.
</p>
<p>
To install, first make sure you have a working version of
<a href="http://nodejs.org/">Node.js</a> greater than version 0.1.30 (Node
moves quickly, using the latest master is your best bet).
Then clone the CoffeeScript
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
from GitHub, or download the latest
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
<pre>
gem install coffee-script</pre>
sudo bin/cake install</pre>
<p>
Installing the gem provides the <tt>coffee</tt> command, which can
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
well as debug them. In conjunction with
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee</tt>
command also provides direct evaluation and an interactive REPL.
This provides the <tt>coffee</tt> command, which will execute CoffeeScripts
under Node.js by default, but is also used to compile CoffeeScript
<tt>.coffee</tt> files into JavaScript, or to run an an interactive REPL.
When compiling to JavaScript, <tt>coffee</tt> writes the output
as <tt>.js</tt> files in the same directory by default, but output
can be customized with the following options:
@@ -127,23 +177,23 @@ gem install coffee-script</pre>
<table>
<tr>
<td width="25%"><code>-i, --interactive</code></td>
<td><code>-c, --compile</code></td>
<td>
Launch an interactive CoffeeScript session.
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
Compile a <tt>.coffee</tt> script into a <tt>.js</tt> JavaScript file
of the same name.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Compile and execute scripts without saving the intermediate
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
Launch an interactive CoffeeScript session to try short snippets.
</td>
</tr>
<tr>
<td><code>-o, --output [DIR]</code></td>
<td>
Write out all compiled JavaScript files into the specified directory.
Use in conjunction with <tt>--compile</tt> or <tt>--watch</tt>.
</td>
</tr>
<tr>
@@ -163,51 +213,55 @@ gem install coffee-script</pre>
<tr>
<td><code>-l, --lint</code></td>
<td>
If the <tt>jsl</tt> (JavaScript Lint) command is installed, use it
If the <tt>jsl</tt>
(<a href="http://www.javascriptlint.com/">JavaScript Lint</a>)
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>-s, --stdio</code></td>
<td>
Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
Good for use with processes written in other languages. An example:<br />
<tt>cat src/cake.coffee | coffee -s</tt>
</td>
</tr>
<tr>
<td><code>-e, --eval</code></td>
<td>
Compile and print a little snippet of CoffeeScript directly from the
command line (or from <b>stdin</b>). For example:<br /><tt>coffee -e "square: (x) -> x * x"</tt>
command line. For example:<br /><tt>coffee -e "square: (x) -> x * x"</tt>
</td>
</tr>
<tr>
<td><code>--no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper.
(Used for CoffeeScript as a Node.js module.)
</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> ...
token stream: <tt>[IDENTIFIER square] [ASSIGN :] [PARAM_START (]</tt> ...
</td>
</tr>
<tr>
<td><code>-v, --verbose</code></td>
<td><code>-n, --nodes</code></td>
<td>
As the JavaScript is being generated, print out every step of code
generation, including lexical scope and the nodes in the
AST.
</td>
</tr>
<tr>
<td><code>-n, --no-wrap</code></td>
<td>
Compile the JavaScript without the top-level function safety wrapper.
(Used for CoffeeScript as a Narwhal module.)
</td>
</tr>
<tr>
<td><code>-g, --globals</code></td>
<td>
Suppress all variable declarations at the top-level, effectively adding
those variables to the global scope. (Used by the REPL.)
</td>
</tr>
<tr>
<td><code>--install-bundle</code></td>
<td>
Install the TextMate bundle for CoffeeScript syntax highlighting.
Instead of compiling the CoffeeScript, just lex and parse it, and print
out the parse tree:
<pre class="no_bar">
Expressions
Assign
Value "square"
Code "x"
Op *
Value "x"
Value "x"</pre>
</td>
</tr>
</table>
@@ -217,12 +271,15 @@ gem install coffee-script</pre>
</p>
<pre>
coffee path/to/script.coffee
coffee -c path/to/script.coffee
coffee --interactive
coffee --watch --lint experimental.coffee
coffee --print app/scripts/*.coffee > concatenation.js</pre>
<h2>Language Reference</h2>
<h2>
<span id="language" class="bookmark"></span>
Language Reference
</h2>
<p>
<i>
@@ -234,7 +291,14 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</i>
</p>
<p id="whitespace">
<p>
<i>
Many of the examples can be run (where it makes sense) by pressing the "run"
button towards the bottom right. You can also paste examples into
"Try CoffeeScript" in the toolbar, and play with them from there.
</i>
<p>
<span id="whitespace" class="bookmark"></span>
<b class="header">Significant Whitespace</b>
CoffeeScript uses Python-style significant whitespace: You don't need to
use semicolons <tt>;</tt> to terminate expressions, ending
@@ -247,40 +311,46 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<p>
You don't need to use parentheses to invoke a function, if you're passing
You don't need to use parentheses to invoke a function if you're passing
arguments:<br /><tt>print "coffee"</tt>
</p>
<p>
You can use newlines to break up your expression into smaller pieces,
as long as CoffeeScript can determine that the line hasn't finished yet.
as long as CoffeeScript can determine that the line hasn't finished yet,
because it ends with an operator or a dot.
</p>
<p id="functions">
<p>
<span id="functions" class="bookmark"></span>
<b class="header">Functions and Invocation</b>
Functions are defined by a list of parameters, an arrow, and the
function body. The empty function looks like this: <tt>-></tt>. All
function body. The empty function looks like this: <tt>-></tt> All
functions in CoffeeScript are named by default, for easier debugging.
</p>
<%= code_for('functions', 'cube(5)') %>
<p>
If you'd like to create an anonymous function, just wrap it in parentheses:
If you'd like to assign a function literal to a variable, but not have
it be named, just wrap the function definition in parentheses:
<tt>((x) -> x * x)</tt>
</p>
<p id="assignment">
<p>
<span id="assignment" class="bookmark"></span>
<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.
mathy things. While colons are preferred, the two may be used interchangeably,
even within object literals.
</p>
<%= code_for('assignment', 'greeting') %>
<p>
Declaration of new variables are pushed up to the top of the nearest
All declaration of new variables is pushed up to the top of the nearest
lexical scope, so that assignment may always be performed within expressions.
</p>
<p id="objects_and_arrays">
<p>
<span id="objects_and_arrays" class="bookmark"></span>
<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
@@ -290,7 +360,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('objects_and_arrays', 'song.join(",")') %>
<p id="lexical_scope">
<p>
<span id="lexical_scope" class="bookmark"></span>
<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
@@ -320,7 +391,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
CommonJS and the browser: <tt>root: exports ? this</tt>
</p>
<p id="conditionals">
<p>
<span id="conditionals" class="bookmark"></span>
<b class="header">Conditionals, Ternaries, and Conditional Assignment</b>
<b>If/else</b> statements can be written without the use of parentheses and
curly brackets. As with functions and other block expressions,
@@ -339,7 +411,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
truthy variables.
</p>
<p id="aliases">
<p>
<span id="aliases" class="bookmark"></span>
<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,
@@ -367,9 +440,13 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<p>
For single-line statements, <tt>unless</tt> can be used as the inverse of <tt>if</tt>.
</p>
<p>
As a shortcut for <tt>this.property</tt>, you can use <tt>@property</tt>.
</p>
<%= code_for('aliases') %>
<p id="splats">
<p>
<span id="splats" class="bookmark"></span>
<b class="header">Splats...</b>
The JavaScript <b>arguments object</b> is a useful way to work with
functions that accept variable numbers of arguments. CoffeeScript provides
@@ -378,7 +455,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('splats', true) %>
<p id="arguments">
<p>
<span id="arguments" class="bookmark"></span>
<b class="header">Arguments are Arrays</b>
If you reference the <b>arguments object</b> directly, it will be converted
into a real Array, making all of the
@@ -387,7 +465,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('arguments', true) %>
<p id="while">
<p>
<span id="while" class="bookmark"></span>
<b class="header">While Loops</b>
The only low-level loop that CoffeeScript provides is the <b>while</b> loop. The
main difference from JavaScript is that the <b>while</b> loop can be used
@@ -402,7 +481,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
<b>each</b> (<b>forEach</b>) style iterators, or...
</p>
<p id="comprehensions">
<p>
<span id="comprehensions" class="bookmark"></span>
<b class="header">Comprehensions (Arrays, Objects, and Ranges)</b>
For your looping needs, CoffeeScript provides array comprehensions
similar to Python's. They replace (and compile into) <b>for</b> loops, with
@@ -426,7 +506,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('object_comprehensions', 'ages.join(", ")') %>
<p id="slice_splice">
<p>
<span id="slice_splice" class="bookmark"></span>
<b class="header">Array Slicing and Splicing with Ranges</b>
CoffeeScript borrows Ruby's
<a href="http://ruby-doc.org/core/classes/Range.html">range syntax</a>
@@ -442,7 +523,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('splices', 'numbers') %>
<p id="expressions">
<p>
<span id="expressions" class="bookmark"></span>
<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.
@@ -480,8 +562,9 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
and <tt>return</tt>. If you make use of them within a block of code,
CoffeeScript won't try to perform the conversion.
</p>
<p id="existence">
<p>
<span id="existence" class="bookmark"></span>
<b class="header">The Existential Operator</b>
It's a little difficult to check for the existence of a variable in
JavaScript. <tt>if (variable) ...</tt> comes close, but fails for zero,
@@ -504,14 +587,15 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('soaks') %>
<p>
Soaking up nulls is similar to Ruby's
<a href="http://andand.rubyforge.org/">andand gem</a>, and to the
Soaking up nulls is similar to Ruby's
<a href="http://andand.rubyforge.org/">andand gem</a>, and to the
<a href="http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator%28%3F.%29">safe navigation operator</a>
in Groovy.
</p>
<p id="inheritance">
<b class="header">Inheritance, and Calling Super from a Subclass</b>
<p>
<span id="classes" class="bookmark"></span>
<b class="header">Classes, Inheritance, and Super</b>
JavaScript's prototypal inheritance has always been a bit of a
brain-bender, with a whole family tree of libraries that provide a cleaner
syntax for classical inheritance on top of JavaScript's prototypes:
@@ -525,14 +609,23 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
set the prototype chain.
</p>
<p>
CoffeeScript provides <tt>extends</tt>
to help with prototype setup, <tt>::</tt> for quick access to an
object's prototype, and converts <tt>super()</tt> into a call against
the immediate ancestor's method of the same name.
Instead of repetitively attaching functions to a prototype, CoffeeScript
provides a basic <tt>class</tt> structure that allows you to name your class,
set the superclass, assign prototypal properties, and define the constructor,
in a single assignable expression.
</p>
<%= code_for('super', true) %>
<%= code_for('classes', true) %>
<p>
If structuring your prototypes classically isn't your cup of tea, CoffeeScript
provides a couple of lower-level conveniences. The <tt>extends</tt> operator
helps with proper prototype setup, as seen above, <tt>::</tt> gives you
quick access to an object's prototype, and <tt>super()</tt>
is converted into a call against the immediate ancestor's method of the same name.
</p>
<%= code_for('prototypes', '"one_two".dasherize()') %>
<p id="pattern_matching">
<p>
<span id="pattern_matching" class="bookmark"></span>
<b class="header">Pattern Matching (Destructuring Assignment)</b>
To make extracting values from complex arrays and objects more convenient,
CoffeeScript implements ECMAScript Harmony's proposed
@@ -554,7 +647,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('object_extraction', 'poet + " — " + street') %>
<p id="fat_arrow">
<p>
<span id="fat_arrow" class="bookmark"></span>
<b class="header">Function binding</b>
The fat arrow <tt>=></tt> can be used to both define a function, and to bind
it to the current value of <tt>this</tt>, right on the spot. This is helpful
@@ -565,7 +659,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('fat_arrow') %>
<p id="embedded">
<p>
<span id="embedded" class="bookmark"></span>
<b class="header">Embedded JavaScript</b>
Hopefully, you'll never need to use it, but if you ever need to intersperse
snippets of JavaScript within your CoffeeScript, you can
@@ -573,7 +668,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('embedded', 'hi()') %>
<p id="switch">
<p>
<span id="switch" class="bookmark"></span>
<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
@@ -590,14 +686,16 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('switch') %>
<p id="try">
<p>
<span id="try" class="bookmark"></span>
<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="comparisons">
<p>
<span id="comparisons" class="bookmark"></span>
<b class="header">Chained Comparisons</b>
CoffeeScript borrows
<a href="http://docs.python.org/reference/expressions.html#notin">chained comparisons</a>
@@ -606,7 +704,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('comparisons', 'healthy') %>
<p id="strings">
<p>
<span id="strings" class="bookmark"></span>
<b class="header">Multiline Strings and Heredocs</b>
Multiline strings are allowed in CoffeeScript.
</p>
@@ -619,43 +718,132 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('heredocs') %>
<h2 id="resources">Resources</h2>
<h2>
<span id="cake" class="bookmark"></span>
Cake, and Cakefiles
</h2>
<p>
CoffeeScript includes a simple build system similar to Make and Rake. Naturally,
it's called Cake, and is used for the build and test tasks for the CoffeeScript
language itself. Tasks are defined in a file named <tt>Cakefile</tt>, and
can be invoked by running <tt>cake taskname</tt> from within the directory.
To print a list of all the tasks, just run <tt>cake</tt>.
</p>
<p>
Task definitions are written in CoffeeScript, so you can put arbitrary code
in your Cakefile. Define a task with a name, a long description, and the
function to invoke when the task is run. Here's a hypothetical task
that uses the Node.js API.
</p>
<%= code_for('cake_tasks') %>
<h2>
<span id="scripts" class="bookmark"></span>
"text/coffeescript" Script Tags
</h2>
<p>
While it's not recommended for serious use, CoffeeScripts may be included
directly within the browser using <tt>&lt;script type="text/coffeescript"&gt;</tt>
tags. The source includes a compressed and minified version of the compiler
(<a href="extras/coffee-script.js">Download current version here, 43k when gzipped</a>)
as <tt>extras/coffee-script.js</tt>. Include this file on a page with
inline CoffeeScript tags, and it will compile and evaluate them in order.
</p>
<p>
In fact, the little bit of glue script that runs "Try CoffeeScript" above,
as well as jQuery for the menu, is implemented in just this way.
View source and look at the bottom of the page to see the example.
Including the script also gives you access to <tt>CoffeeScript.compile()</tt>
so you can pop open Firebug and try compiling some strings.
</p>
<p>
The usual caveats about CoffeeScript apply &mdash; your inline scripts will
run within a closure wrapper, so if you want to expose global variables or
functions, attach them to the <tt>window</tt> object.
</p>
<h2>
<span id="resources" class="bookmark"></span>
Resources
</h2>
<ul>
<li>
<a href="http://github.com/jashkenas/coffee-script/">Source Code</a><br />
After checking out the source, make sure to run <tt>rake build:parser</tt>
to generate an up-to-date version of the Racc parser.
Use <tt>bin/coffee</tt> to test your changes,
<tt>rake test</tt> to run the test suite,
and <tt>rake gem:install</tt> to
create and install a custom version of the gem.
Use <tt>bin/coffee</tt> to test your changes,<br />
<tt>bin/cake test</tt> to run the test suite,<br />
<tt>bin/cake build</tt> to rebuild the CoffeeScript compiler, and <br />
<tt>bin/cake build:parser</tt> to regenerate the Jison parser if you're
working on the grammar.
</li>
<li>
<a href="http://github.com/jashkenas/coffee-script/issues">Bugs, Feature Requests, and General Discussion</a>
<a href="http://github.com/jashkenas/coffee-script/issues">CoffeeScript Issues</a><br />
Bugs reports, feature requests, and general discussion all belong here.
</li>
<li>
<a href="http://github.com/creationix/coffeepot">CoffeePot</a><br />
An implementation of CoffeeScript, written in CoffeeScript, by
<a href="http://github.com/creationix">Tim Caswell</a>. Compiles just
a limited subset at this point.
</li>
<li>
<a href="http://github.com/jnicklas/bistro_car">BistroCar</a><br />
A Rails plugin by
<a href="http://github.com/jnicklas">Jonas Nicklas</a>
that includes CoffeeScript helpers,
bundling and minification.
</li>
<li>
<a href="http://github.com/inem/coffee-haml-filter">coffee-haml-filter</a><br />
A custom <a href="http://haml-lang.com/">HAML</a> filter, by
<a href="http://github.com/inem">Ivan Nemytchenko</a>, that embeds
snippets of CoffeeScript within your HAML templates.
If you'd like to chat, stop by <tt>#coffeescript</tt> on Freenode in the
IRC client of your choice, or on
<a href="http://webchat.freenode.net/">webchat.freenode.net</a>.
</li>
</ul>
<h2 id="change_log">Change Log</h2>
<h2>
<span id="change_log" class="bookmark"></span>
Change Log
</h2>
<p>
<b class="header" style="margin-top: 20px;">0.5.4</b>
Bugfix that corrects the Node.js global constants <tt>__filename</tt> and
<tt>__dirname</tt>. Tweaks for more flexible parsing of nested function
literals and improperly-indented comments. Updates for the latest Node.js API.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.3</b>
CoffeeScript now has a syntax for defining classes. Many of the core
components (Nodes, Lexer, Rewriter, Scope, Optparse) are using them.
Cakefiles can use <tt>optparse.coffee</tt> to define options for tasks.
<tt>--run</tt> is now the default flag for the <tt>coffee</tt> command,
use <tt>--compile</tt> to save JavaScripts. Bugfix for an ambiguity between
RegExp literals and chained divisions.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.2</b>
Added a compressed version of the compiler for inclusion in web pages as
<br /><tt>extras/coffee-script.js</tt>. It'll automatically run any script tags
with type <tt>text/coffeescript</tt> for you. Added a <tt>--stdio</tt> option
to the <tt>coffee</tt> command, for piped-in compiles.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.1</b>
Improvements to null soaking with the existential operator, including
soaks on indexed properties. Added conditions to <tt>while</tt> loops,
so you can use them as filters with <tt>when</tt>, in the same manner as
comprehensions.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.0</b>
CoffeeScript 0.5.0 is a major release, While there are no language changes,
the Ruby compiler has been removed in favor of a self-hosting
compiler written in pure CoffeeScript.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.3.2</b>
<tt>@property</tt> is now a shorthand for <tt>this.property</tt>.<br />
Switched the default JavaScript engine from Narwhal to Node.js. Pass
the <tt>--narwhal</tt> flag if you'd like to continue using it.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.3.0</b>
@@ -805,5 +993,60 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</div>
<script type="text/coffeescript">
# Set up the compilation function, to run when you stop typing.
compile_source: ->
source: $('#repl_source').val()
window.compiled_js: ''
try
window.compiled_js: CoffeeScript.compile source, {no_wrap: true}
$('#repl_results').text window.compiled_js
$('#error').hide();
catch error
$('#error').text(error.message).show();
# Listen for keypresses and recompile.
$('#repl_source').keyup -> compile_source()
# Eval the compiled js.
$('button.run').click ->
try
eval window.compiled_js
catch error then alert error
current_nav: null
# Helper to hide the menus.
close_menus: ->
if current_nav
current_nav.removeClass 'active'
document.body.className: 'minimized'
current_nav: null
# Bind navigation buttons to open the menus.
$('.navigation').click (e) ->
return if e.target.tagName.toLowerCase() is 'a'
if this isnt (current_nav and current_nav[0])
close_menus();
current_nav: $(this)
current_nav.addClass 'active'
false
$(document.body).click -> close_menus()
$('.navigation .full_screen').click ->
document.body.className: 'full_screen'
$('.navigation .minimize').click ->
document.body.className: 'minimized'
compile_source()
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script src="extras/coffee-script.js"></script>
</body>
</html>

View File

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

View File

@@ -1,8 +1,8 @@
(function(){
var backwards;
backwards = function backwards() {
var arguments = Array.prototype.slice.call(arguments, 0);
arguments = Array.prototype.slice.call(arguments, 0);
return alert(arguments.reverse());
};
backwards("stairway", "to", "heaven");
})();
})();

View File

@@ -1,21 +1,21 @@
(function(){
var __a, __b, __c, __d, __e, __f, __g, food, lunch, roid, roid2;
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, food, lunch, roid, roid2;
// Eat lunch.
lunch = (function() {
__a = []; __b = ['toast', 'cheese', 'wine'];
for (__c = 0; __c < __b.length; __c++) {
food = __b[__c];
__a.push(eat(food));
_a = []; _b = ['toast', 'cheese', 'wine'];
for (_c = 0, _d = _b.length; _c < _d; _c++) {
food = _b[_c];
_a.push(eat(food));
}
return __a;
return _a;
}).call(this);
// Naive collision detection.
__d = asteroids;
for (__e = 0; __e < __d.length; __e++) {
roid = __d[__e];
__f = asteroids;
for (__g = 0; __g < __f.length; __g++) {
roid2 = __f[__g];
_e = asteroids;
for (_f = 0, _g = _e.length; _f < _g; _f++) {
roid = _e[_f];
_h = asteroids;
for (_i = 0, _j = _h.length; _i < _j; _i++) {
roid2 = _h[_i];
if (roid !== roid2) {
if (roid.overlaps(roid2)) {
roid.explode();
@@ -23,4 +23,4 @@
}
}
}
})();
})();

View File

@@ -2,4 +2,4 @@
var difficulty, greeting;
greeting = "Hello CoffeeScript";
difficulty = 0.5;
})();
})();

View File

@@ -0,0 +1,14 @@
(function(){
process.mixin(require('assert'));
task('test', 'run each of the unit tests', function() {
var _a, _b, _c, _d, test;
_a = []; _b = test_files;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
test = _b[_c];
_a.push(fs.readFile(test, function(err, code) {
return eval(coffee.compile(code));
}));
}
return _a;
});
})();

View File

@@ -1,33 +1,30 @@
(function(){
var Animal, Horse, Snake, __a, __b, sam, tom;
var Animal, Horse, Snake, sam, tom;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
};
Animal = function Animal() { };
Animal.prototype.move = function move(meters) {
return alert(this.name + " moved " + meters + "m.");
};
Snake = function Snake(name) {
var __a;
__a = this.name = name;
return Snake === this.constructor ? this : __a;
this.name = name;
return this;
};
__a = function(){};
__a.prototype = Animal.prototype;
Snake.__superClass__ = Animal.prototype;
Snake.prototype = new __a();
Snake.prototype.constructor = Snake;
__extends(Snake, Animal);
Snake.prototype.move = function move() {
alert("Slithering...");
return Snake.__superClass__.move.call(this, 5);
};
Horse = function Horse(name) {
var __b;
__b = this.name = name;
return Horse === this.constructor ? this : __b;
this.name = name;
return this;
};
__b = function(){};
__b.prototype = Animal.prototype;
Horse.__superClass__ = Animal.prototype;
Horse.prototype = new __b();
Horse.prototype.constructor = Horse;
__extends(Horse, Animal);
Horse.prototype.move = function move() {
alert("Galloping...");
return Horse.__superClass__.move.call(this, 45);
@@ -36,4 +33,4 @@
tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
})();
})();

View File

@@ -2,4 +2,4 @@
var cholesterol, healthy;
cholesterol = 127;
healthy = (200 > cholesterol) && (cholesterol > 60);
})();
})();

View File

@@ -9,4 +9,4 @@
}
date = friday ? sue : jill;
expensive = expensive || do_the_math();
})();
})();

View File

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

View File

@@ -4,4 +4,4 @@
solipsism = true;
}
speed = (typeof speed !== "undefined" && speed !== null) ? speed : 140;
})();
})();

View File

@@ -10,4 +10,4 @@
}
};
eldest = 24 > 21 ? "Liz" : "Ike";
})();
})();

View File

@@ -1,4 +1,4 @@
(function(){
var one, six, three, two;
six = ((one = 1)) + ((two = 2)) + ((three = 3));
})();
})();

View File

@@ -1,14 +1,12 @@
(function(){
var __a, __b, globals, name;
var _a, _b, globals, name;
var __hasProp = Object.prototype.hasOwnProperty;
// The first ten global properties.
globals = ((function() {
__a = []; __b = window;
for (name in __b) {
if (__hasProp.call(__b, name)) {
__a.push(name);
}
}
return __a;
}).call(this)).slice(0, 10);
})();
globals = (function() {
_a = []; _b = window;
for (name in _b) { if (__hasProp.call(_b, name)) {
_a.push(name);
}}
return _a;
}).call(this).slice(0, 10);
})();

View File

@@ -6,4 +6,4 @@
return "And the error is ... " + error;
}
}).call(this));
})();
})();

View File

@@ -1,10 +1,9 @@
(function(){
var Account;
Account = function Account(customer, cart) {
var __a;
this.customer = customer;
this.cart = cart;
__a = $('.shopping_cart').bind('click', (function(__this) {
return $('.shopping_cart').bind('click', (function(__this) {
var __func = function(event) {
return this.customer.purchase(this.cart);
};
@@ -12,6 +11,5 @@
return __func.apply(__this, arguments);
});
})(this));
return Account === this.constructor ? this : __a;
};
})();
})();

View File

@@ -6,4 +6,4 @@
cube = function cube(x) {
return square(x) * x;
};
})();
})();

View File

@@ -1,4 +1,4 @@
(function(){
var html;
html = "<strong>\n cup of coffeescript\n</strong>";
})();
})();

View File

@@ -1,11 +1,11 @@
(function(){
var __a, city, forecast, temp, weather_report;
var _a, city, forecast, temp, weather_report;
weather_report = function weather_report(location) {
// Make an Ajax request to fetch the weather...
return [location, 72, "Mostly Sunny"];
};
__a = weather_report("Berkeley, CA");
city = __a[0];
temp = __a[1];
forecast = __a[2];
})();
_a = weather_report("Berkeley, CA");
city = _a[0];
temp = _a[1];
forecast = _a[2];
})();

View File

@@ -1,5 +1,5 @@
(function(){
var __a, __b, age, ages, child, years_old;
var _a, _b, age, ages, child, years_old;
var __hasProp = Object.prototype.hasOwnProperty;
years_old = {
max: 10,
@@ -7,13 +7,11 @@
tim: 11
};
ages = (function() {
__a = []; __b = years_old;
for (child in __b) {
age = __b[child];
if (__hasProp.call(__b, child)) {
__a.push(child + " is " + age);
}
}
return __a;
_a = []; _b = years_old;
for (child in _b) { if (__hasProp.call(_b, child)) {
age = _b[child];
_a.push(child + " is " + age);
}}
return _a;
}).call(this);
})();
})();

View File

@@ -1,5 +1,5 @@
(function(){
var __a, __b, __c, city, futurists, poet, street;
var _a, _b, _c, city, futurists, poet, street;
futurists = {
sculptor: "Umberto Boccioni",
painter: "Vladimir Burliuk",
@@ -8,10 +8,10 @@
address: ["Via Roma 42R", "Bellagio, Italy 22021"]
}
};
__a = futurists;
__b = __a.poet;
poet = __b.name;
__c = __b.address;
street = __c[0];
city = __c[1];
})();
_a = futurists;
_b = _a.poet;
poet = _b.name;
_c = _b.address;
street = _c[0];
city = _c[1];
})();

View File

@@ -7,4 +7,4 @@
tim: 11
};
matrix = [1, 0, 1, 0, 0, 1, 1, 1, 0];
})();
})();

View File

@@ -1,5 +1,5 @@
(function(){
var __a, __b, __c, cubed_list, list, math, num, number, opposite_day, race, square;
var _a, _b, _c, _d, cubed_list, list, math, num, number, opposite_day, race, square;
// Assignment:
number = 42;
opposite_day = true;
@@ -33,11 +33,11 @@
}
// Array comprehensions:
cubed_list = (function() {
__a = []; __b = list;
for (__c = 0; __c < __b.length; __c++) {
num = __b[__c];
__a.push(math.cube(num));
_a = []; _b = list;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
num = _b[_c];
_a.push(math.cube(num));
}
return __a;
return _a;
}).call(this);
})();
})();

View File

@@ -1,8 +1,8 @@
(function(){
var __a, and_switch, bait;
var _a, and_switch, bait;
bait = 1000;
and_switch = 0;
__a = [and_switch, bait];
bait = __a[0];
and_switch = __a[1];
})();
_a = [and_switch, bait];
bait = _a[0];
and_switch = _a[1];
})();

5
documentation/js/prototypes.js vendored Normal file
View File

@@ -0,0 +1,5 @@
(function(){
String.prototype.dasherize = function dasherize() {
return this.replace(/_/g, "-");
};
})();

View File

@@ -1,21 +1,21 @@
(function(){
var __a, __b, __c, __d, __e, countdown, egg_delivery, num;
var _a, _b, _c, _d, _e, countdown, egg_delivery, num;
countdown = (function() {
__a = []; __d = 10; __e = 1;
for (__c=0, num=__d; (__d <= __e ? num <= __e : num >= __e); (__d <= __e ? num += 1 : num -= 1), __c++) {
__a.push(num);
_a = []; _d = 10; _e = 1;
for (_c = 0, num = _d; (_d <= _e ? num <= _e : num >= _e); (_d <= _e ? num += 1 : num -= 1), _c++) {
_a.push(num);
}
return __a;
return _a;
}).call(this);
egg_delivery = function egg_delivery() {
var __f, __g, __h, __i, __j, dozen_eggs, i;
__f = []; __i = 0; __j = eggs.length;
for (__h=0, i=__i; (__i <= __j ? i < __j : i > __j); (__i <= __j ? i += 12 : i -= 12), __h++) {
__f.push((function() {
var _f, _g, _h, _i, _j, dozen_eggs, i;
_f = []; _i = 0; _j = eggs.length;
for (_h = 0, i = _i; (_i <= _j ? i < _j : i > _j); (_i <= _j ? i += 12 : i -= 12), _h++) {
_f.push((function() {
dozen_eggs = eggs.slice(i, i + 12);
return deliver(new egg_carton(dozen));
}).call(this));
}
return __f;
return _f;
};
})();
})();

View File

@@ -7,4 +7,4 @@
return num = 10;
};
new_num = change_numbers();
})();
})();

View File

@@ -3,4 +3,4 @@
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
three_to_six = numbers.slice(3, 6 + 1);
numbers_copy = numbers.slice(0, numbers.length);
})();
})();

View File

@@ -1,4 +1,4 @@
(function(){
var __a;
((__a = lottery.draw_winner()) == undefined ? undefined : __a.address == undefined ? undefined : __a.address.zipcode);
})();
var _a;
(_a = lottery.draw_winner()) == undefined ? undefined : _a.address == undefined ? undefined : _a.address.zipcode;
})();

View File

@@ -13,4 +13,4 @@
alert("Gold: " + gold);
alert("Silver: " + silver);
alert("The Field: " + the_field);
})();
})();

View File

@@ -2,4 +2,4 @@
var numbers;
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6]));
})();
})();

View File

@@ -6,4 +6,4 @@ or no money in my purse, and nothing particular \
to interest me on shore, I thought I would sail \
about a little and see the watery part of the \
world...";
})();
})();

View File

@@ -15,4 +15,4 @@
} else {
go_to_work();
}
})();
})();

View File

@@ -7,4 +7,4 @@
} finally {
clean_up();
}
})();
})();

View File

@@ -1,5 +1,5 @@
(function(){
var __a, lyrics, num;
var _a, lyrics, num;
// Econ 101
if (this.studying_economics) {
while (supply > demand) {
@@ -12,11 +12,11 @@
// Nursery Rhyme
num = 6;
lyrics = (function() {
__a = [];
_a = [];
while (num -= 1) {
__a.push(num + " little monkeys, jumping on the bed. \
_a.push(num + " little monkeys, jumping on the bed. \
One fell out and bumped his head.");
}
return __a;
return _a;
}).call(this);
})();
})();

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Quickie CoffeeScript Speed Tests</title>
<script type="text/javascript" src="http://www.broofa.com/Tools/JSLitmus/JSLitmus.js"></script>
</head>
<body>
<h1>Quickie CoffeeScript Speed Tests</h1>
<script type="text/javascript">
var num = 1000;
var arr = [];
while (num--) arr.push(num);
var f1 = function f1() {
return arr;
};
JSLitmus.test('regular function', function() {
f1();
});
var __this = this;
var f2 = function f2() {
return (function() {
return arr;
}).apply(__this, arguments);
};
JSLitmus.test('bound function', function() {
f2();
});
var f3 = (function() {
__b = function() {
return arr;
};
return (function f2() {
return __b.apply(__this, arguments);
});
})();
JSLitmus.test('prebound function', function() {
f3();
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

57
examples/blocks.coffee Normal file
View File

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

View File

@@ -140,24 +140,28 @@ sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna
aliquam erat volutpat. Ut wisi enim ad."
# Inheritance and calling super.
Animal: ->
Animal::move: (meters) ->
alert(this.name + " moved " + meters + "m.")
class Animal
move: (meters) ->
alert this.name + " moved " + meters + "m."
Snake: (name) -> this.name: name
Snake extends Animal
Snake::move: ->
alert('Slithering...')
super(5)
class Snake extends Animal
constructor: (name) ->
@name: name
Horse: (name) -> this.name: name
Horse extends Animal
Horse::move: ->
alert('Galloping...')
super(45)
move: ->
alert 'Slithering...'
super 5
sam: new Snake("Sammy the Snake")
tom: new Horse("Tommy the Horse")
class Horse extends Animal
constructor: (name) ->
@name: name
move: ->
alert 'Galloping...'
super 45
sam: new Snake "Sammy the Snake"
tom: new Horse "Tommy the Horse"
sam.move()
tom.move()

View File

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

View File

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

View File

@@ -1,106 +1,108 @@
# "Classic" linked list implementation that doesn't keep track of its size.
LinkedList: ->
this._head: null # Pointer to the first item in the list.
class LinkedList
constructor: ->
this._head: null # Pointer to the first item in the list.
# Appends some data to the end of the list. This method traverses the existing
# list and places the value at the end in a new node.
LinkedList::add: (data) ->
# Appends some data to the end of the list. This method traverses the existing
# list and places the value at the end in a new node.
add: (data) ->
# Create a new node object to wrap the data.
node: {data: data, next: null}
# Create a new node object to wrap the data.
node: {data: data, next: null}
current: this._head ||= node
current: this._head ||= node
if this._head isnt node
current: current.next while current.next
current.next: node
if this._head isnt node
current: current.next while current.next
current.next: node
this
this
# Retrieves the data at the given position in the list.
LinkedList::item: (index) ->
# Retrieves the data at the given position in the list.
item: (index) ->
# Check for out-of-bounds values.
return null if index < 0
# Check for out-of-bounds values.
return null if index < 0
current: this._head or null
i: -1
current: this._head or null
i: -1
# Advance through the list.
current: current.next while current and index > (i += 1)
# Advance through the list.
current: current.next while current and index > (i += 1)
# Return null if we've reached the end.
current and current.data
# Return null if we've reached the end.
current and current.data
# Remove the item from the given location in the list.
LinkedList::remove: (index) ->
# Remove the item from the given location in the list.
remove: (index) ->
# Check for out-of-bounds values.
return null if index < 0
# Check for out-of-bounds values.
return null if index < 0
current: this._head or null
i: -1
current: this._head or null
i: -1
# Special case: removing the first item.
if index is 0
this._head: current.next
else
# Special case: removing the first item.
if index is 0
this._head: current.next
else
# Find the right location.
[previous, current]: [current, current.next] while index > (i += 1)
# Find the right location.
[previous, current]: [current, current.next] while index > (i += 1)
# Skip over the item to remove.
previous.next: current.next
# Skip over the item to remove.
previous.next: current.next
# Return the value.
current and current.data
# Return the value.
current and current.data
# Calculate the number of items in the list.
LinkedList::size: ->
current: this._head
count: 0
# Calculate the number of items in the list.
size: ->
current: this._head
count: 0
while current
count += 1
current: current.next
while current
count += 1
current: current.next
count
count
# Convert the list into an array.
LinkedList::toArray: ->
result: []
current: this._head
# Convert the list into an array.
toArray: ->
result: []
current: this._head
while current
result.push(current.data)
current: current.next
while current
result.push(current.data)
current: current.next
result
result
# The string representation of the linked list.
LinkedList::toString: -> this.toArray().toString()
# The string representation of the linked list.
toString: -> this.toArray().toString()
# Tests.
list: new LinkedList()
list.add("Hi")
print(list.size() is 1)
print(list.item(0) is "Hi")
print(list.item(1) is null)
puts(list.size() is 1)
puts(list.item(0) is "Hi")
puts(list.item(1) is null)
list: new LinkedList()
list.add("zero").add("one").add("two")
print(list.size() is 3)
print(list.item(2) is "two")
print(list.remove(1) is "one")
print(list.item(0) is "zero")
print(list.item(1) is "two")
print(list.size() is 2)
print(list.item(-10) is null)
puts(list.size() is 3)
puts(list.item(2) is "two")
puts(list.remove(1) is "one")
puts(list.item(0) is "zero")
puts(list.item(1) is "two")
puts(list.size() is 2)
puts(list.item(-10) is null)

View File

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

View File

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

View File

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

View File

@@ -53,16 +53,16 @@ for key, val of {dog: 'canine', cat: 'feline', fox: 'vulpine'}
# Person print = ():
# ('My name is ', /name, '.') join print.
Person: ->
Person::print: ->
print('My name is ' + this.name + '.')
class Person
print: ->
print 'My name is ' + this.name + '.'
# p = Person ()
# p /name string print
p: new Person()
print(p.name)
print p.name
# Policeman = Person class (rank): /rank = rank.
@@ -71,12 +71,13 @@ print(p.name)
#
# Policeman ('Constable') print
Policeman: (rank) -> this.rank: rank
Policeman extends Person
Policeman::print: ->
print('My name is ' + this.name + " and I'm a " + this.rank + '.')
class Policeman extends Person
constructor: (rank) ->
@rank: rank
print: ->
print 'My name is ' + this.name + " and I'm a " + this.rank + '.'
print(new Policeman('Constable'))
print new Policeman 'Constable'
# app = [window (width=200, height=400)

View File

@@ -18,36 +18,55 @@
previousUnderscore: root._
# If Underscore is called as a function, it returns a wrapped object that
# can be used OO-style. This wrapper holds altered versions of all the
# underscore functions. Wrapped objects may be chained.
wrapper: (obj) ->
this._wrapped: obj
this
# Establish the object that gets thrown to break out of a loop iteration.
breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
# Create a safe reference to the Underscore object forreference below.
_: root._: (obj) -> new wrapper(obj)
# Quick regexp-escaping function, because JS doesn't have RegExp.escape().
escapeRegExp: (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
# Save bytes in the minified (but not gzipped) version:
ArrayProto: Array.prototype
ObjProto: Object.prototype
#Create quick reference variables for speed access to core prototypes.
slice: ArrayProto.slice
unshift: ArrayProto.unshift
toString: ObjProto.toString
hasOwnProperty: ObjProto.hasOwnProperty
propertyIsEnumerable: ObjProto.propertyIsEnumerable
# All ECMA5 native implementations we hope to use are declared here.
nativeForEach: ArrayProto.forEach
nativeMap: ArrayProto.map
nativeReduce: ArrayProto.reduce
nativeReduceRight: ArrayProto.reduceRight
nativeFilter: ArrayProto.filter
nativeEvery: ArrayProto.every
nativeSome: ArrayProto.some
nativeIndexOf: ArrayProto.indexOf
nativeLastIndexOf: ArrayProto.lastIndexOf
nativeIsArray: Array.isArray
nativeKeys: Object.keys
# Create a safe reference to the Underscore object for use below.
_: (obj) -> new wrapper(obj)
# Export the Underscore object for CommonJS.
if typeof(exports) != 'undefined' then exports._: _
# Create quick reference variables for speed access to core prototypes.
slice: Array::slice
unshift: Array::unshift
toString: Object::toString
hasOwnProperty: Object::hasOwnProperty
propertyIsEnumerable: Object::propertyIsEnumerable
# Export Underscore to global scope.
root._: _
# Current version.
_.VERSION: '0.5.7'
_.VERSION: '0.6.0'
# ------------------------ Collection Functions: ---------------------------
@@ -55,12 +74,13 @@
# The cornerstone, an each implementation.
# Handles objects implementing forEach, arrays, and raw objects.
_.each: (obj, iterator, context) ->
index: 0
try
return obj.forEach(iterator, context) if obj.forEach
if _.isArray(obj) or _.isArguments(obj)
return iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
iterator.call(context, val, key, obj) for key, val of obj
if nativeForEach and obj.forEach is nativeForEach
obj.forEach iterator, context
else if _.isNumber obj.length
iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
else
iterator.call(context, val, key, obj) for key, val of obj
catch e
throw e if e isnt breaker
obj
@@ -69,28 +89,28 @@
# Return the results of applying the iterator to each element. Use JavaScript
# 1.6's version of map, if possible.
_.map: (obj, iterator, context) ->
return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
results: []
_.each obj, (value, index, list) ->
results.push(iterator.call(context, value, index, list))
results.push iterator.call context, value, index, list
results
# Reduce builds up a single result from a list of values. Also known as
# inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
_.reduce: (obj, memo, iterator, context) ->
return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce))
return obj.reduce(_.bind(iterator, context), memo) if nativeReduce and obj.reduce is nativeReduce
_.each obj, (value, index, list) ->
memo: iterator.call(context, memo, value, index, list)
memo: iterator.call context, memo, value, index, list
memo
# The right-associative version of reduce, also known as foldr. Uses
# JavaScript 1.8's version of reduceRight, if available.
_.reduceRight: (obj, memo, iterator, context) ->
return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight))
return obj.reduceRight(_.bind(iterator, context), memo) if nativeReduceRight and obj.reduceRight is nativeReduceRight
_.each _.clone(_.toArray(obj)).reverse(), (value, index) ->
memo: iterator.call(context, memo, value, index, obj)
memo: iterator.call context, memo, value, index, obj
memo
@@ -98,7 +118,7 @@
_.detect: (obj, iterator, context) ->
result: null
_.each obj, (value, index, list) ->
if iterator.call(context, value, index, list)
if iterator.call context, value, index, list
result: value
_.breakLoop()
result
@@ -106,11 +126,11 @@
# Return all the elements that pass a truth test. Use JavaScript 1.6's
# filter(), if it exists.
_.select: (obj, iterator, context) ->
if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context)
_.filter: (obj, iterator, context) ->
return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
results: []
_.each obj, (value, index, list) ->
results.push(value) if iterator.call(context, value, index, list)
results.push value if iterator.call context, value, index, list
results
@@ -118,15 +138,15 @@
_.reject: (obj, iterator, context) ->
results: []
_.each obj, (value, index, list) ->
results.push(value) if not iterator.call(context, value, index, list)
results.push value if not iterator.call context, value, index, list
results
# Determine whether all of the elements match a truth test. Delegate to
# JavaScript 1.6's every(), if it is present.
_.all: (obj, iterator, context) ->
_.every: (obj, iterator, context) ->
iterator ||= _.identity
return obj.every(iterator, context) if obj and _.isFunction(obj.every)
return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
result: true
_.each obj, (value, index, list) ->
_.breakLoop() unless (result: result and iterator.call(context, value, index, list))
@@ -135,9 +155,9 @@
# Determine if at least one element in the object matches a truth test. Use
# JavaScript 1.6's some(), if it exists.
_.any: (obj, iterator, context) ->
_.some: (obj, iterator, context) ->
iterator ||= _.identity
return obj.some(iterator, context) if obj and _.isFunction(obj.some)
return obj.some iterator, context if nativeSome and obj.some is nativeSome
result: false
_.each obj, (value, index, list) ->
_.breakLoop() if (result: iterator.call(context, value, index, list))
@@ -147,7 +167,7 @@
# Determine if a given value is included in the array or object,
# based on '==='.
_.include: (obj, target) ->
return _.indexOf(obj, target) isnt -1 if _.isArray(obj)
return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
for key, val of obj
return true if val is target
false
@@ -155,13 +175,13 @@
# Invoke a method with arguments on every item in a collection.
_.invoke: (obj, method) ->
args: _.rest(arguments, 2)
args: _.rest arguments, 2
(if method then val[method] else val).apply(val, args) for val in obj
# Convenience version of a common use case of map: fetching a property.
_.pluck: (obj, key) ->
_.map(obj, ((val) -> val[key]))
_.map(obj, (val) -> val[key])
# Return the maximum item or (item-based computation).
@@ -184,7 +204,7 @@
result.value
# Sort the object's values by a criteria produced by an iterator.
# Sort the object's values by a criterion produced by an iterator.
_.sortBy: (obj, iterator, context) ->
_.pluck(((_.map obj, (value, index, list) ->
{value: value, criteria: iterator.call(context, value, index, list)}
@@ -198,7 +218,8 @@
# be inserted so as to maintain order. Uses binary search.
_.sortedIndex: (array, obj, iterator) ->
iterator ||= _.identity
low: 0; high: array.length
low: 0
high: array.length
while low < high
mid: (low + high) >> 1
if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid
@@ -240,36 +261,36 @@
# Trim out all falsy values from an array.
_.compact: (array) -> array[i] for i in [0...array.length] when array[i]
_.compact: (array) -> item for item in array when item
# Return a completely flattened version of an array.
_.flatten: (array) ->
_.reduce array, [], (memo, value) ->
return memo.concat(_.flatten(value)) if _.isArray(value)
memo.push(value)
return memo.concat(_.flatten(value)) if _.isArray value
memo.push value
memo
# Return a version of the array that does not contain the specified value(s).
_.without: (array) ->
values: _.rest(arguments)
val for val in _.toArray(array) when not _.include(values, val)
values: _.rest arguments
val for val in _.toArray(array) when not _.include values, val
# Produce a duplicate-free version of the array. If the array has already
# been sorted, you have the option of using a faster algorithm.
_.uniq: (array, isSorted) ->
memo: []
for el, i in _.toArray(array)
memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
for el, i in _.toArray array
memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
memo
# Produce an array that contains every item shared between all the
# passed-in arrays.
_.intersect: (array) ->
rest: _.rest(arguments)
rest: _.rest arguments
_.select _.uniq(array), (item) ->
_.all rest, (other) ->
_.indexOf(other, item) >= 0
@@ -278,10 +299,10 @@
# Zip together multiple lists into a single array -- elements that share
# an index go together.
_.zip: ->
length: _.max(_.pluck(arguments, 'length'))
results: new Array(length)
length: _.max _.pluck arguments, 'length'
results: new Array length
for i in [0...length]
results[i]: _.pluck(arguments, String(i))
results[i]: _.pluck arguments, String i
results
@@ -289,7 +310,7 @@
# we need this function. Return the position of the first occurence of an
# item in an array, or -1 if the item is not included in the array.
_.indexOf: (array, item) ->
return array.indexOf(item) if array.indexOf
return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
i: 0; l: array.length
while l - i
if array[i] is item then return i else i++
@@ -299,7 +320,7 @@
# Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
# if possible.
_.lastIndexOf: (array, item) ->
return array.lastIndexOf(item) if array.lastIndexOf
return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
i: array.length
while i
if array[i] is item then return i else i--
@@ -312,12 +333,12 @@
_.range: (start, stop, step) ->
a: arguments
solo: a.length <= 1
i: start: if solo then 0 else a[0];
stop: if solo then a[0] else a[1];
i: start: if solo then 0 else a[0]
stop: if solo then a[0] else a[1]
step: a[2] or 1
len: Math.ceil((stop - start) / step)
return [] if len <= 0
range: new Array(len)
range: new Array len
idx: 0
while true
return range if (if step > 0 then i - stop else stop - i) >= 0
@@ -331,36 +352,36 @@
# Create a function bound to a given object (assigning 'this', and arguments,
# optionally). Binding with arguments is also known as 'curry'.
_.bind: (func, obj) ->
args: _.rest(arguments, 2)
-> func.apply(obj or root, args.concat(arguments))
args: _.rest arguments, 2
-> func.apply obj or root, args.concat arguments
# Bind all of an object's methods to that object. Useful for ensuring that
# all callbacks defined on an object belong to it.
_.bindAll: (obj) ->
funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
_.each(funcs, (f) -> obj[f]: _.bind(obj[f], obj))
_.each funcs, (f) -> obj[f]: _.bind obj[f], obj
obj
# Delays a function for the given number of milliseconds, and then calls
# it with the arguments supplied.
_.delay: (func, wait) ->
args: _.rest(arguments, 2)
args: _.rest arguments, 2
setTimeout((-> func.apply(func, args)), wait)
# Defers a function, scheduling it to run after the current call stack has
# cleared.
_.defer: (func) ->
_.delay.apply(_, [func, 1].concat(_.rest(arguments)))
_.delay.apply _, [func, 1].concat _.rest arguments
# Returns the first function passed as an argument to the second,
# allowing you to adjust arguments, run code before and after, and
# conditionally execute the original function.
_.wrap: (func, wrapper) ->
-> wrapper.apply(wrapper, [func].concat(arguments))
-> wrapper.apply wrapper, [func].concat arguments
# Returns a function that is the composition of a list of functions, each
@@ -377,38 +398,37 @@
# ------------------------- Object Functions: ----------------------------
# Retrieve the names of an object's properties.
_.keys: (obj) ->
return _.range(0, obj.length) if _.isArray(obj)
_.keys: nativeKeys or (obj) ->
return _.range 0, obj.length if _.isArray(obj)
key for key, val of obj
# Retrieve the values of an object's properties.
_.values: (obj) ->
_.map(obj, _.identity)
_.map obj, _.identity
# Return a sorted list of the function names available in Underscore.
_.functions: (obj) ->
_.select(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
_.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
# Extend a given object with all of the properties in a source object.
_.extend: (destination, source) ->
for key, val of source
destination[key]: val
(destination[key]: val) for key, val of source
destination
# Create a (shallow-cloned) duplicate of an object.
_.clone: (obj) ->
return obj.slice(0) if _.isArray(obj)
_.extend({}, obj)
return obj.slice 0 if _.isArray obj
_.extend {}, obj
# Invokes interceptor with the obj, and then returns obj.
# The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
_.tap: (obj, interceptor) ->
interceptor(obj)
interceptor obj
obj
@@ -444,12 +464,15 @@
# Different object sizes?
return false if aKeys.length isnt bKeys.length
# Recursive comparison of contents.
# for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
return true
(return false) for key, val of a when !_.isEqual(val, b[key])
true
# Is a given array or object empty?
_.isEmpty: (obj) -> _.keys(obj).length is 0
_.isEmpty: (obj) ->
return obj.length is 0 if _.isArray obj
(return false) for key of obj when hasOwnProperty.call(obj, key)
true
# Is a given value a DOM element?
@@ -457,7 +480,7 @@
# Is a given value an array?
_.isArray: (obj) -> !!(obj and obj.concat and obj.unshift)
_.isArray: nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift)
# Is a given variable an arguments object?
@@ -477,6 +500,10 @@
_.isNumber: (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
# Is a given value a boolean?
_.isBoolean: (obj) -> obj is true or obj is false
# Is a given value a Date?
_.isDate: (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
@@ -511,10 +538,22 @@
_.identity: (value) -> value
# Run a function n times.
_.times: (n, iterator, context) ->
iterator.call(context, i) for i in [0...n]
# Break out of the middle of an iteration.
_.breakLoop: -> throw breaker
# Add your own custom functions to the Underscore object, ensuring that
# they're correctly added to the OOP wrapper as well.
_.mixin: (obj) ->
for name in _.functions(obj)
addToWrapper name, _[name]: obj[name]
# Generate a unique integer id (unique within the entire client session).
# Useful for temporary DOM ids.
idCounter: 0
@@ -533,14 +572,15 @@
# JavaScript templating a-la ERB, pilfered from John Resig's
# "Secrets of the JavaScript Ninja", page 83.
# Single-quotea fix from Rick Strahl's version.
# Single-quote fix from Rick Strahl's version.
_.template: (str, data) ->
c: _.templateSettings
endMatch: new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
fn: new Function 'obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' +
str.replace(/[\r\t\n]/g, " ")
.replace(new RegExp("'(?=[^"+c.end[0]+"]*"+c.end+")","g"),"\t")
.replace(endMatch,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(c.interpolate, "',$1,'")
@@ -555,27 +595,39 @@
_.forEach: _.each
_.foldl: _.inject: _.reduce
_.foldr: _.reduceRight
_.filter: _.select
_.every: _.all
_.some: _.any
_.select: _.filter
_.all: _.every
_.any: _.some
_.head: _.first
_.tail: _.rest
_.methods: _.functions
# /*------------------------ Setup the OOP Wrapper: --------------------------*/
# ------------------------ Setup the OOP Wrapper: --------------------------
# If Underscore is called as a function, it returns a wrapped object that
# can be used OO-style. This wrapper holds altered versions of all the
# underscore functions. Wrapped objects may be chained.
wrapper: (obj) ->
this._wrapped: obj
this
# Helper function to continue chaining intermediate results.
result: (obj, chain) ->
if chain then _(obj).chain() else obj
# Add all of the Underscore functions to the wrapper object.
_.each _.functions(_), (name) ->
method: _[name]
# A method to easily add functions to the OOP wrapper.
addToWrapper: (name, func) ->
wrapper.prototype[name]: ->
unshift.call(arguments, this._wrapped)
result(method.apply(_, arguments), this._chain)
args: _.toArray arguments
unshift.call args, this._wrapped
result func.apply(_, args), this._chain
# Add all of the Underscore functions to the wrapper object.
_.mixin _
# Add all mutator Array functions to the wrapper.

View File

@@ -0,0 +1,12 @@
# Contributed by Jason Huggins
http: require 'http'
server: http.createServer (req, res) ->
res.sendHeader 200, {'Content-Type': 'text/plain'}
res.write 'Hello, World!'
res.close()
server.listen 3000
puts "Server running at http://localhost:3000/"

View File

@@ -72,6 +72,12 @@
<key>name</key>
<string>constant.numeric.coffee</string>
</dict>
<dict>
<key>match</key>
<string>(@)([a-zA-Z_$]\w*)?</string>
<key>name</key>
<string>variable.other.readwrite.instance.coffee</string>
</dict>
<dict>
<key>name</key>
<string>string.quoted.heredoc.coffee</string>
@@ -282,7 +288,7 @@
</dict>
<dict>
<key>match</key>
<string>\b(super|this|extends)\b</string>
<string>\b(super|this|extends|class)\b</string>
<key>name</key>
<string>variable.language.coffee</string>
</dict>

View File

@@ -1,8 +1,16 @@
This folder includes rough cuts of CoffeeScript syntax highlighters for
EXTRAS:
"extras/coffee-script.js" is a concatenated and compressed version of the
CoffeeScript compiler. To use it in the browser, include the script after any
inline script tags of type "text/coffeescript" on the page. It will compile
and evaluate all of the scripts in order.
This folder also includes rough cuts of CoffeeScript syntax highlighters for
TextMate and Vim. Improvements to their lexing ability are always welcome.
To install the TextMate bundle, run `bin/coffee --install-bundle`, or drop it
into "~/Library/Application Support/TextMate/Bundles".
To install the TextMate bundle, drop it into:
~/Library/Application Support/TextMate/Bundles
To install the Vim highlighter, copy "coffee.vim" into the "syntax" directory of
your vim72, and enable it in either of the following two ways:

1
extras/coffee-script.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -28,11 +28,14 @@ syn match coffeeSpecialCharacter "'\\.'"
syn match coffeeNumber "-\=\<\d\+L\=\>\|0[xX][0-9a-fA-F]\+\>"
syn region coffeeRegexpString start=+/[^/*]+me=e-1 skip=+\\\\\|\\/+ end=+/[gi]\{0,2\}\s*$+ end=+/[gi]\{0,2\}\s*[;.,)\]}]+me=e-1 contains=@htmlPreproc oneline
syn match coffeeFunctionParams "([^)]*)\s*->"
syn match coffeeBindFunctionParams "([^)]*)\s*=>"
syn match coffeePrototypeAccess "::"
syn match coffeeBindFunction "=[1]>[1]"
syn match coffeeFunction "->"
syn keyword coffeeExtends extends
syn keyword coffeeConditional if else switch then
syn keyword coffeeConditional if else switch then not
syn keyword coffeeRepeat while for in of
syn keyword coffeeBranch break continue
syn keyword coffeeOperator delete instanceof typeof
@@ -81,7 +84,10 @@ if version >= 508 || !exists("did_coffee_syn_inits")
HiLink coffeeOperator Operator
HiLink coffeeType Type
HiLink coffeeStatement Statement
HiLink coffeeBindFunctionParams Function
HiLink coffeeFunctionParams Function
HiLink coffeeFunction Function
HiLink coffeeBindFunction Function
HiLink coffeeBraces Function
HiLink coffeeError Error
HiLink coffeeScrParenError coffeeError

File diff suppressed because it is too large Load Diff

86
lib/cake.js Executable file
View File

@@ -0,0 +1,86 @@
(function(){
var coffee, fs, no_such_task, oparse, options, optparse, path, print_tasks, switches, tasks;
var __hasProp = Object.prototype.hasOwnProperty;
// `cake` is a simplified version of Make (Rake, Jake) for CoffeeScript.
// You define tasks with names and descriptions in a Cakefile, and can call them
// from the command line, or invoke them from other tasks.
fs = require('fs');
path = require('path');
coffee = require('coffee-script');
optparse = require('optparse');
tasks = {};
options = {};
switches = [];
oparse = null;
// Mixin the top-level Cake functions for Cakefiles to use.
process.mixin({
// Define a task with a name, a description, and the action itself.
task: function task(name, description, action) {
return tasks[name] = {
name: name,
description: description,
action: action
};
},
// Define an option that the Cakefile accepts.
option: function option(letter, flag, description) {
return switches.push([letter, flag, description]);
},
// Invoke another task in the Cakefile.
invoke: function invoke(name) {
if (!(tasks[name])) {
no_such_task(name);
}
return tasks[name].action(options);
}
});
// Running `cake` runs the tasks you pass asynchronously (node-style), or
// prints them out, with no arguments.
exports.run = function run() {
return path.exists('Cakefile', function(exists) {
var _a, _b, _c, _d, arg, args;
if (!(exists)) {
throw new Error('Cakefile not found in ' + process.cwd());
}
args = process.ARGV.slice(2, process.ARGV.length);
eval(coffee.compile(fs.readFileSync('Cakefile')));
oparse = new optparse.OptionParser(switches);
if (!(args.length)) {
return print_tasks();
}
options = oparse.parse(args);
_a = []; _b = options.arguments;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
arg = _b[_c];
_a.push(invoke(arg));
}
return _a;
});
};
// Display the list of Cake tasks.
print_tasks = function print_tasks() {
var _a, _b, _c, _d, _e, _f, i, name, spaces, task;
puts('');
_a = tasks;
for (name in _a) { if (__hasProp.call(_a, name)) {
task = _a[name];
spaces = 20 - name.length;
spaces = spaces > 0 ? (function() {
_b = []; _e = 0; _f = spaces;
for (_d = 0, i = _e; (_e <= _f ? i <= _f : i >= _f); (_e <= _f ? i += 1 : i -= 1), _d++) {
_b.push(' ');
}
return _b;
}).call(this).join('') : '';
puts("cake " + name + spaces + ' # ' + task.description);
}}
if (switches.length) {
return puts('\n' + oparse.help() + '\n');
}
};
// Print an error and exit when attempting to all an undefined task.
no_such_task = function no_such_task(task) {
process.stdio.writeError('No such task: "' + task + '"\n');
return process.exit(1);
};
})();

68
lib/coffee-script.js Normal file
View File

@@ -0,0 +1,68 @@
(function(){
var lexer, parser, path, process_scripts;
// Set up for both the browser and the server.
if ((typeof process !== "undefined" && process !== null)) {
process.mixin(require('nodes'));
path = require('path');
lexer = new (require('lexer').Lexer)();
parser = require('parser').parser;
} else {
lexer = new Lexer();
parser = exports.parser;
this.exports = (this.CoffeeScript = {});
}
// Thin wrapper for Jison compatibility around the real lexer.
parser.lexer = {
lex: function lex() {
var token;
token = this.tokens[this.pos] || [""];
this.pos += 1;
this.yylineno = token[2];
this.yytext = token[1];
return token[0];
},
setInput: function setInput(tokens) {
this.tokens = tokens;
return this.pos = 0;
},
upcomingInput: function upcomingInput() {
return "";
},
showPosition: function showPosition() {
return this.pos;
}
};
exports.VERSION = '0.5.4';
// Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile = function compile(code, options) {
return (parser.parse(lexer.tokenize(code))).compile(options);
};
// Just the tokens.
exports.tokens = function tokens(code) {
return lexer.tokenize(code);
};
// Just the nodes.
exports.nodes = function nodes(code) {
return parser.parse(lexer.tokenize(code));
};
// Activate CoffeeScript in the browser by having it compile and eval
// all script tags with a content-type of text/coffeescript.
if ((typeof document !== "undefined" && document !== null) && document.getElementsByTagName) {
process_scripts = function process_scripts() {
var _a, _b, _c, _d, tag;
_a = []; _b = document.getElementsByTagName('script');
for (_c = 0, _d = _b.length; _c < _d; _c++) {
tag = _b[_c];
if (tag.type === 'text/coffeescript') {
_a.push(eval(exports.compile(tag.innerHTML)));
}
}
return _a;
};
if (window.addEventListener) {
window.addEventListener('load', process_scripts, false);
} else if (window.attachEvent) {
window.attachEvent('onload', process_scripts);
}
}
})();

View File

@@ -1,21 +0,0 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
require "coffee_script/lexer"
require "coffee_script/parser"
require "coffee_script/nodes"
require "coffee_script/value"
require "coffee_script/scope"
require "coffee_script/rewriter"
require "coffee_script/parse_error"
# Namespace for all CoffeeScript internal classes.
module CoffeeScript
VERSION = '0.3.0' # Keep in sync with the gemspec.
# Compile a script (String or IO) to JavaScript.
def self.compile(script, options={})
script = script.read if script.respond_to?(:read)
Parser.new.parse(script).compile(options)
end
end

View File

@@ -1,229 +0,0 @@
require 'optparse'
require 'fileutils'
require 'open3'
begin
require File.expand_path(File.dirname(__FILE__) + '/../coffee-script')
rescue LoadError => e
puts(e.message)
puts("use \"rake build:parser\" to regenerate parser.rb")
exit(1)
end
module CoffeeScript
# The CommandLine handles all of the functionality of the `coffee`
# utility.
class CommandLine
BANNER = <<-EOS
coffee compiles CoffeeScript source files into JavaScript.
Usage:
coffee path/to/script.coffee
EOS
# Seconds to pause between checks for changed source files.
WATCH_INTERVAL = 0.5
# Path to the root of the CoffeeScript install.
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
# Command to execute in Narwhal
LAUNCHER = "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
# Run the CommandLine off the contents of ARGV.
def initialize
@mtimes = {}
parse_options
return launch_repl if @options[:interactive]
return eval_scriptlet if @options[:eval]
check_sources
return run_scripts if @options[:run]
@sources.each {|source| compile_javascript(source) }
watch_coffee_scripts if @options[:watch]
end
# The "--help" usage message.
def usage
puts "\n#{@option_parser}\n"
exit
end
private
# Compiles (or partially compiles) the source CoffeeScript file, returning
# the desired JS, tokens, or lint results.
def compile_javascript(source)
script = File.read(source)
return tokens(script) if @options[:tokens]
js = compile(script, source)
return unless js
return puts(js) if @options[:print]
return lint(js) if @options[:lint]
File.open(path_for(source), 'w+') {|f| f.write(js) }
end
# Spins up a watcher thread to keep track of the modification times of the
# source files, recompiling them whenever they're saved.
def watch_coffee_scripts
watch_thread = Thread.start do
loop do
@sources.each do |source|
mtime = File.stat(source).mtime
@mtimes[source] ||= mtime
if mtime > @mtimes[source]
@mtimes[source] = mtime
compile_javascript(source)
end
end
sleep WATCH_INTERVAL
end
end
Signal.trap("INT") { watch_thread.kill }
watch_thread.join
end
# Ensure that all of the source files exist.
def check_sources
usage if @sources.empty?
missing = @sources.detect {|s| !File.exists?(s) }
if missing
STDERR.puts("File not found: '#{missing}'")
exit(1)
end
end
# Pipe compiled JS through JSLint (requires a working 'jsl' command).
def lint(js)
stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin')
stdin.write(js)
stdin.close
puts stdout.read.tr("\n", '')
errs = stderr.read.chomp
puts errs unless errs.empty?
stdout.close and stderr.close
end
# Eval a little piece of CoffeeScript directly from the command line.
def eval_scriptlet
script = STDIN.tty? ? @sources.join(' ') : STDIN.read
return tokens(script) if @options[:tokens]
js = compile(script)
return lint(js) if @options[:lint]
puts js
end
# Use Narwhal to run an interactive CoffeeScript session.
def launch_repl
exec "#{LAUNCHER}"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed to use the interactive REPL."
exit(1)
end
# Use Narwhal to compile and execute CoffeeScripts.
def run_scripts
sources = @sources.join(' ')
exec "#{LAUNCHER} #{sources}"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
exit(1)
end
# Print the tokens that the lexer generates from a source script.
def tokens(script)
puts Lexer.new.tokenize(script).inspect
end
# Compile a single source file to JavaScript.
def compile(script, source='error')
begin
options = {}
options[:no_wrap] = true if @options[:no_wrap]
options[:globals] = true if @options[:globals]
CoffeeScript.compile(script, options)
rescue CoffeeScript::ParseError => e
STDERR.puts "#{source}: #{e.message}"
exit(1) unless @options[:watch]
nil
end
end
# Write out JavaScript alongside CoffeeScript unless an output directory
# is specified.
def path_for(source)
filename = File.basename(source, File.extname(source)) + '.js'
dir = @options[:output] || File.dirname(source)
File.join(dir, filename)
end
# Install the CoffeeScript TextMate bundle to ~/Library.
def install_bundle
bundle_dir = File.expand_path('~/Library/Application Support/TextMate/Bundles/')
FileUtils.cp_r("#{ROOT}/extras/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 function safety wrapper') do |n|
@options[:no_wrap] = true
end
opts.on('-g', '--globals', 'attach all top-level variable as globals') do |n|
@options[:globals] = true
end
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
install_bundle
exit
end
opts.on_tail('--version', 'display CoffeeScript version') do
puts "CoffeeScript version #{CoffeeScript::VERSION}"
exit
end
opts.on_tail('-h', '--help', 'display this help message') do
usage
end
end
@option_parser.banner = BANNER
begin
@option_parser.parse!(ARGV)
rescue OptionParser::InvalidOption => e
puts e.message
exit(1)
end
@sources = ARGV
end
end
end

View File

@@ -1,479 +0,0 @@
class Parser
# Declare terminal tokens produced by the lexer.
token IF ELSE UNLESS
token NUMBER STRING REGEX
token TRUE FALSE YES NO ON OFF
token IDENTIFIER PROPERTY_ACCESS PROTOTYPE_ACCESS SOAK_ACCESS
token CODE PARAM_START PARAM PARAM_END NEW RETURN
token CALL_START CALL_END INDEX_START INDEX_END
token TRY CATCH FINALLY THROW
token BREAK CONTINUE
token FOR IN OF BY WHEN WHILE
token SWITCH LEADING_WHEN
token DELETE INSTANCEOF TYPEOF
token SUPER EXTENDS
token ARGUMENTS
token NEWLINE
token COMMENT
token JS
token INDENT OUTDENT
# Declare order of operations.
prechigh
left '?'
nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
left '*' '/' '%'
left '+' '-'
left '<<' '>>' '>>>'
left '&' '|' '^'
left '<=' '<' '>' '>='
right '==' '!=' IS ISNT
left '&&' '||' AND OR
right '-=' '+=' '/=' '*=' '%='
right DELETE INSTANCEOF TYPEOF
left '.'
right INDENT
left OUTDENT
right WHEN LEADING_WHEN IN OF BY
right THROW FOR NEW SUPER
left EXTENDS
left '||=' '&&=' '?='
right ASSIGN RETURN
right '->' '=>' UNLESS IF ELSE WHILE
preclow
rule
# All parsing will end in this rule, being the trunk of the AST.
Root:
/* nothing */ { result = Expressions.new }
| Terminator { result = Expressions.new }
| Expressions { result = val[0] }
| Block Terminator { result = val[0] }
;
# Any list of expressions or method body, seperated by line breaks or semis.
Expressions:
Expression { result = Expressions.wrap(val) }
| Expressions Terminator Expression { result = val[0] << val[2] }
| Expressions Terminator { result = val[0] }
;
# All types of expressions in our language. The basic unit of CoffeeScript
# is the expression.
Expression:
Value
| Call
| Code
| Operation
| Assign
| If
| Try
| Throw
| Return
| While
| For
| Switch
| Extends
| Splat
| Existence
| Comment
;
# A block of expressions. Note that the Rewriter will convert some postfix
# forms into blocks for us, by altering the token stream.
Block:
INDENT Expressions OUTDENT { result = val[1] }
| INDENT OUTDENT { result = Expressions.new }
;
# Tokens that can terminate an expression.
Terminator:
"\n"
| ";"
;
# All hard-coded values. These can be printed straight to JavaScript.
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]) }
| ARGUMENTS { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(Value.new(true)) }
| FALSE { result = LiteralNode.new(Value.new(false)) }
| YES { result = LiteralNode.new(Value.new(true)) }
| NO { result = LiteralNode.new(Value.new(false)) }
| ON { result = LiteralNode.new(Value.new(true)) }
| OFF { result = LiteralNode.new(Value.new(false)) }
;
# Assignment to a variable (or index).
Assign:
Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
;
# Assignment within an object literal (can be quoted).
AssignObj:
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
| Comment { result = val[0] }
;
# A return statement.
Return:
RETURN Expression { result = ReturnNode.new(val[1]) }
| RETURN { result = ReturnNode.new(ValueNode.new(Value.new('null'))) }
;
# 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]) }
| '+' Expression = UPLUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| '~' Expression { result = OpNode.new(val[0], val[1]) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
;
# The existence operator.
Existence:
Expression '?' { result = ExistenceNode.new(val[0]) }
;
# Function definition.
Code:
PARAM_START ParamList PARAM_END
FuncGlyph Block { result = CodeNode.new(val[1], val[4], val[3]) }
| FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
;
# The symbols to signify functions, and bound functions.
FuncGlyph:
'->' { result = :func }
| '=>' { result = :boundfunc }
;
# The parameters to a function definition.
ParamList:
Param { result = val }
| ParamList "," Param { result = val[0] << val[2] }
;
# A Parameter (or ParamSplat) in a function definition.
Param:
PARAM
| PARAM "." "." "." { result = SplatNode.new(val[0]) }
;
# A regular splat.
Splat:
Expression "." "." "." { result = SplatNode.new(val[0]) }
;
# Expressions that can be treated as values.
Value:
IDENTIFIER { result = ValueNode.new(val[0]) }
| Literal { result = ValueNode.new(val[0]) }
| Array { result = ValueNode.new(val[0]) }
| Object { result = ValueNode.new(val[0]) }
| Parenthetical { result = ValueNode.new(val[0]) }
| Range { 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]) }
| PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :prototype) }
| SOAK_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :soak) }
| Index { result = val[0] }
| Slice { result = SliceNode.new(val[0]) }
;
# Indexing into an object or array.
Index:
INDEX_START Expression INDEX_END { result = IndexNode.new(val[1]) }
;
# An object literal.
Object:
"{" AssignList "}" { result = ObjectNode.new(val[1]) }
;
# Assignment within an object literal (comma or newline separated).
AssignList:
/* nothing */ { result = [] }
| AssignObj { result = val }
| AssignList "," AssignObj { result = val[0] << val[2] }
| AssignList Terminator AssignObj { result = val[0] << val[2] }
| AssignList ","
Terminator AssignObj { result = val[0] << val[3] }
| INDENT AssignList OUTDENT { result = val[1] }
;
# All flavors of function call (instantiation, super, and regular).
Call:
Invocation { result = val[0] }
| NEW Invocation { result = val[1].new_instance }
| Super { result = val[0] }
;
# Extending an object's prototype.
Extends:
Value EXTENDS Value { result = ExtendsNode.new(val[0], val[2]) }
;
# A generic function invocation.
Invocation:
Value Arguments { result = CallNode.new(val[0], val[1]) }
| Invocation Arguments { result = CallNode.new(val[0], val[1]) }
;
# The list of arguments to a function invocation.
Arguments:
CALL_START ArgList CALL_END { result = val[1] }
;
# Calling super.
Super:
SUPER CALL_START ArgList CALL_END { result = CallNode.new(Value.new('super'), val[2]) }
;
# The range literal.
Range:
"[" Expression
"." "." Expression "]" { result = RangeNode.new(val[1], val[4]) }
| "[" Expression
"." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) }
;
# The slice literal.
Slice:
INDEX_START Expression "." "."
Expression INDEX_END { result = RangeNode.new(val[1], val[4]) }
| INDEX_START Expression "." "." "."
Expression INDEX_END { result = RangeNode.new(val[1], val[5], true) }
;
# The array literal.
Array:
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
;
# A list of arguments to a method call, or as the contents of an array.
ArgList:
/* nothing */ { result = [] }
| Expression { result = val }
| INDENT Expression { result = [val[1]] }
| ArgList "," Expression { result = val[0] << val[2] }
| ArgList Terminator Expression { result = val[0] << val[2] }
| ArgList "," Terminator Expression { result = val[0] << val[3] }
| ArgList "," INDENT Expression { result = val[0] << val[3] }
| ArgList OUTDENT { result = val[0] }
;
# Just simple, comma-separated, required arguments (no fancy syntax).
SimpleArgs:
Expression { result = val[0] }
| SimpleArgs "," Expression { result = ([val[0]] << val[2]).flatten }
;
# Try/catch/finally exception handling blocks.
Try:
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
| TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) }
| TRY Block Catch
FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
;
# A catch clause.
Catch:
CATCH IDENTIFIER Block { result = [val[1], val[2]] }
;
# Throw an exception.
Throw:
THROW Expression { result = ThrowNode.new(val[1]) }
;
# Parenthetical expressions.
Parenthetical:
"(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) }
;
# The while loop. (there is no do..while).
While:
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
| WHILE Expression { result = WhileNode.new(val[1], nil) }
| Expression WHILE Expression { result = WhileNode.new(val[2], Expressions.wrap(val[0])) }
;
# Array comprehensions, including guard and current index.
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
For:
Expression FOR
ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) }
| FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) }
;
# An array comprehension has variables for the current element and index.
ForVariables:
IDENTIFIER { result = val }
| IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] }
;
# The source of the array comprehension can optionally be filtered.
ForSource:
IN Expression { result = {:source => val[1]} }
| OF Expression { result = {:source => val[1], :object => true} }
| ForSource
WHEN Expression { result = val[0].merge(:filter => val[2]) }
| ForSource
BY Expression { result = val[0].merge(:step => val[2]) }
;
# Switch/When blocks.
Switch:
SWITCH Expression INDENT
Whens OUTDENT { result = val[3].rewrite_condition(val[1]) }
| SWITCH Expression INDENT
Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
;
# The inner list of whens.
Whens:
When { result = val[0] }
| Whens When { result = val[0] << val[1] }
;
# An individual when.
When:
LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| LEADING_WHEN SimpleArgs Block
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| Comment Terminator When { result = val[2].add_comment(val[0]) }
;
# The most basic form of "if".
IfBlock:
IF Expression Block { result = IfNode.new(val[1], val[2]) }
;
# An elsif portion of an if-else block.
ElsIf:
ELSE IfBlock { result = val[1].force_statement }
;
# Multiple elsifs can be chained together.
ElsIfs:
ElsIf { result = val[0] }
| ElsIfs ElsIf { result = val[0].add_else(val[1]) }
;
# Terminating else bodies are strictly optional.
ElseBody
/* nothing */ { result = nil }
| ELSE Block { result = val[1] }
;
# All the alternatives for ending an if-else block.
IfEnd:
ElseBody { result = val[0] }
| ElsIfs ElseBody { result = val[0].add_else(val[1]) }
;
# The full complement of if blocks, including postfix one-liner ifs and unlesses.
If:
IfBlock IfEnd { result = val[0].add_else(val[1]) }
| Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) }
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) }
;
end
---- header
module CoffeeScript
---- inner
# Lex and parse a CoffeeScript.
def parse(code)
# Uncomment the following line to enable grammar debugging, in combination
# with the -g flag in the Rake build task.
# @yydebug = true
@tokens = Lexer.new.tokenize(code)
do_parse
end
# Retrieve the next token from the list.
def next_token
@tokens.shift
end
# Raise a custom error class that knows about line numbers.
def on_error(error_token_id, error_value, value_stack)
raise ParseError.new(token_to_str(error_token_id), error_value, value_stack)
end
---- footer
end

View File

@@ -1,274 +0,0 @@
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", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends",
"arguments"]
# 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
HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?([ \t]*)"{3}|'{3}\n?(.*?)\n?([ \t]*)'{3})/m
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
OPERATOR = /\A([+\*&|\/\-%=<>:!?]+)/
WHITESPACE = /\A([ \t]+)/
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
CODE = /\A((-|=)>)/
REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/
LAST_DENT = /\n([ \t]*)/
ASSIGNMENT = /\A(:|=)\Z/
# Token cleaning regexes.
JS_CLEANER = /(\A`|`\Z)/
MULTILINER = /\n/
STRING_NEWLINES = /\n[ \t]*/
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/
NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
HEREDOC_INDENT = /^[ \t]+/
# Tokens which a regular expression will never immediately follow, but which
# a division operator might.
# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
NOT_REGEX = [
:IDENTIFIER, :NUMBER, :REGEX, :STRING,
')', '++', '--', ']', '}',
:FALSE, :NULL, :TRUE
]
# Tokens which could legitimately be invoked or indexed.
CALLABLE = [:IDENTIFIER, :SUPER, ')', ']', '}', :STRING]
# Scan by attempting to match tokens one character at a time. Slow and steady.
def tokenize(code)
@code = code.chomp # Cleanup code by remove extra line breaks
@i = 0 # Current character position we're parsing
@line = 1 # The current line.
@indent = 0 # The current indent level.
@indents = [] # The stack of all indent levels we are currently within.
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
@spaced = nil # The last value that has a space following it.
while @i < @code.length
@chunk = @code[@i..-1]
extract_next_token
end
puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE']
close_indentation
Rewriter.new.rewrite(@tokens)
end
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed.
def extract_next_token
return if identifier_token
return if number_token
return if heredoc_token
return if string_token
return if js_token
return if regex_token
return if indent_token
return if comment_token
return if whitespace_token
return literal_token
end
# Tokenizers ==========================================================
# Matches identifying literals: variables, keywords, method names, etc.
def identifier_token
return false unless identifier = @chunk[IDENTIFIER, 1]
# Keywords are special identifiers tagged with their own name,
# 'if' will result in an [:IF, "if"] token.
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
@tokens[-1][0] = :PROTOTYPE_ACCESS if tag == :IDENTIFIER && last_value == '::'
if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2] && @tokens[-2][1] == '.')
if @tokens[-2][0] == "?"
@tokens[-1][0] = :SOAK_ACCESS
@tokens.delete_at(-2)
else
@tokens[-1][0] = :PROPERTY_ACCESS
end
end
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(STRING_NEWLINES, " \\\n")
token(:STRING, escaped)
@line += string.count("\n")
@i += string.length
end
# Matches heredocs, adjusting indentation to the correct level.
def heredoc_token
return false unless match = @chunk.match(HEREDOC)
doc = match[2] || match[4]
indent = doc.scan(HEREDOC_INDENT).min
doc.gsub!(/^#{indent}/, "")
doc.gsub!("\n", "\\n")
doc.gsub!('"', '\\"')
token(:STRING, "\"#{doc}\"")
@line += match[1].count("\n")
@i += match[1].length
end
# Matches interpolated JavaScript.
def js_token
return false unless script = @chunk[JS, 1]
token(:JS, script.gsub(JS_CLEANER, ''))
@i += script.length
end
# Matches regular expression literals.
def regex_token
return false unless regex = @chunk[REGEX, 1]
return false if NOT_REGEX.include?(last_tag)
token(:REGEX, regex)
@i += regex.length
end
# Matches and consumes comments.
def comment_token
return false unless comment = @chunk[COMMENT, 1]
@line += comment.scan(MULTILINER).length
token(:COMMENT, comment.gsub(COMMENT_CLEANER, '').split(MULTILINER))
token("\n", "\n")
@i += comment.length
end
# Record tokens for indentation differing from the previous line.
def indent_token
return false unless indent = @chunk[MULTI_DENT, 1]
@line += indent.scan(MULTILINER).size
@i += indent.size
next_character = @chunk[MULTI_DENT, 4]
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && @tokens[-2][0] != '.' && !last_value.match(CODE))
return suppress_newlines(indent) if no_newlines
size = indent.scan(LAST_DENT).last.last.length
return newline_token(indent) if size == @indent
if size > @indent
token(:INDENT, size - @indent)
@indents << (size - @indent)
else
outdent_token(@indent - size)
end
@indent = size
end
# Record an oudent token or tokens, if we're moving back inwards past
# multiple recorded indents.
def outdent_token(move_out)
while move_out > 0 && !@indents.empty?
last_indent = @indents.pop
token(:OUTDENT, last_indent)
move_out -= last_indent
end
token("\n", "\n")
end
# Matches and consumes non-meaningful whitespace.
def whitespace_token
return false unless whitespace = @chunk[WHITESPACE, 1]
@spaced = last_value
@i += whitespace.length
end
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
def newline_token(newlines)
token("\n", "\n") unless last_value == "\n"
true
end
# Tokens to explicitly escape newlines are removed once their job is done.
def suppress_newlines(newlines)
@tokens.pop if last_value == "\\"
true
end
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# the proper order of operations.
def literal_token
value = @chunk[OPERATOR, 1]
tag_parameters if value && value.match(CODE)
value ||= @chunk[0,1]
tag = value.match(ASSIGNMENT) ? :ASSIGN : value
if !@spaced.equal?(last_value) && CALLABLE.include?(last_tag)
tag = :CALL_START if value == '('
tag = :INDEX_START if value == '['
end
token(tag, value)
@i += value.length
end
# Helpers ==========================================================
# Add a token to the results, taking note of the line number, and
# immediately-preceding comment.
def token(tag, value)
@tokens << [tag, Value.new(value, @line)]
end
# Peek at the previous token's value.
def last_value
@tokens.last && @tokens.last[1]
end
# Peek at the previous token's tag.
def last_tag
@tokens.last && @tokens.last[0]
end
# A source of ambiguity in our grammar was parameter lists in function
# definitions (as opposed to argument lists in function calls). Tag
# parameter identifiers in order to avoid this. Also, parameter lists can
# make use of splats.
def tag_parameters
return if last_tag != ')'
i = 0
loop do
i -= 1
tok = @tokens[i]
return if !tok
case tok[0]
when :IDENTIFIER then tok[0] = :PARAM
when ')' then tok[0] = :PARAM_END
when '(' then return tok[0] = :PARAM_START
end
end
end
# Close up all remaining open blocks. IF the first token is an indent,
# axe it.
def close_indentation
outdent_token(@indent)
end
end
end

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
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
TOKEN_MAP = {
'INDENT' => 'indent',
'OUTDENT' => 'outdent',
"\n" => 'newline'
}
def initialize(token_id, value, stack=nil, message=nil)
@token_id, @value, @stack, @message = token_id, value, stack, message
end
def message
line = @value.respond_to?(:line) ? @value.line : "END"
line_part = "line #{line}:"
id_part = @token_id != @value.to_s ? " unexpected #{@token_id.to_s.downcase}" : ""
val_part = @message || "for #{TOKEN_MAP[@value.to_s] || "'#{@value}'"}"
"#{line_part} syntax error, #{val_part}#{id_part}"
end
alias_method :inspect, :message
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,289 +0,0 @@
module CoffeeScript
# In order to keep the grammar simple, the stream of tokens that the Lexer
# emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
# indentation, and single-line flavors of expressions.
class Rewriter
# Tokens that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT],
[:PARAM_START, :PARAM_END], [:CALL_START, :CALL_END], [:INDEX_START, :INDEX_END]]
# Tokens that signal the start of a balanced pair.
EXPRESSION_START = BALANCED_PAIRS.map {|pair| pair.first }
# Tokens that signal the end of a balanced pair.
EXPRESSION_TAIL = BALANCED_PAIRS.map {|pair| pair.last }
# Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL
# Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC = [:IDENTIFIER, :SUPER, ')', :CALL_END, ']', :INDEX_END]
IMPLICIT_END = [:IF, :UNLESS, :FOR, :WHILE, "\n", :OUTDENT]
IMPLICIT_CALL = [:IDENTIFIER, :NUMBER, :STRING, :JS, :REGEX, :NEW, :PARAM_START,
:TRY, :DELETE, :INSTANCEOF, :TYPEOF, :SWITCH, :ARGUMENTS,
:TRUE, :FALSE, :YES, :NO, :ON, :OFF, '!', '!!', :NOT,
'->', '=>', '[', '(', '{']
# The inverse mappings of token pairs we're trying to fix up.
INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair|
memo[pair.first] = pair.last
memo[pair.last] = pair.first
memo
end
# Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = [:ELSE, "->", "=>", :TRY, :FINALLY, :THEN]
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN, :PARAM_START]
# Rewrite the token stream in multiple passes, one logical filter at
# a time. This could certainly be changed into a single pass through the
# stream, with a big ol' efficient switch, but it's much nicer like this.
def rewrite(tokens)
@tokens = tokens
adjust_comments
remove_leading_newlines
remove_mid_expression_newlines
move_commas_outside_outdents
close_open_calls_and_indexes
add_implicit_parentheses
add_implicit_indentation
ensure_balance(*BALANCED_PAIRS)
rewrite_closing_parens
@tokens
end
# Rewrite the token stream, looking one token ahead and behind.
# Allow the return value of the block to tell us how many tokens to move
# forwards (or backwards) in the stream, to make sure we don't miss anything
# as the stream changes length under our feet.
def scan_tokens
i = 0
loop do
break unless @tokens[i]
move = yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
i += move
end
end
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on their own line.
def adjust_comments
scan_tokens do |prev, token, post, i|
next 1 unless token[0] == :COMMENT
before, after = @tokens[i - 2], @tokens[i + 2]
if before && after &&
((before[0] == :INDENT && after[0] == :OUTDENT) ||
(before[0] == :OUTDENT && after[0] == :INDENT)) &&
before[1] == after[1]
@tokens.delete_at(i + 2)
@tokens.delete_at(i - 2)
next 0
elsif prev[0] == "\n" && [:INDENT].include?(after[0])
@tokens.delete_at(i + 2)
@tokens[i - 1] = after
next 1
elsif !["\n", :INDENT, :OUTDENT].include?(prev[0])
@tokens.insert(i, ["\n", Value.new("\n", token[1].line)])
next 2
else
next 1
end
end
end
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
def remove_leading_newlines
@tokens.shift if @tokens[0][0] == "\n"
end
# Some blocks occur in the middle of expressions -- when we're expecting
# this, remove their trailing newlines.
def remove_mid_expression_newlines
scan_tokens do |prev, token, post, i|
next 1 unless post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n"
@tokens.delete_at(i)
next 0
end
end
# Make sure that we don't accidentally break trailing commas, which need
# to go on the outside of expression closers.
def move_commas_outside_outdents
scan_tokens do |prev, token, post, i|
if token[0] == :OUTDENT && prev[0] == ','
@tokens.delete_at(i)
@tokens.insert(i - 1, token)
end
next 1
end
end
# We've tagged the opening parenthesis of a method call, and the opening
# bracket of an indexing operation. Match them with their close.
def close_open_calls_and_indexes
parens, brackets = [0], [0]
scan_tokens do |prev, token, post, i|
case token[0]
when :CALL_START then parens.push(0)
when :INDEX_START then brackets.push(0)
when '(' then parens[-1] += 1
when '[' then brackets[-1] += 1
when ')'
if parens.last == 0
parens.pop
token[0] = :CALL_END
else
parens[-1] -= 1
end
when ']'
if brackets.last == 0
brackets.pop
token[0] = :INDEX_END
else
brackets[-1] -= 1
end
end
next 1
end
end
# Because our grammar is LALR(1), it can't handle some single-line
# expressions that lack ending delimiters. Use the lexer to add the implicit
# blocks, so it doesn't need to.
# ')' can close a single-line block, but we need to make sure it's balanced.
def add_implicit_indentation
scan_tokens do |prev, token, post, i|
next 1 unless SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT &&
!(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks.
starter = token[0]
line = token[1].line
@tokens.insert(i + 1, [:INDENT, Value.new(2, line)])
idx = i + 1
parens = 0
loop do
idx += 1
tok = @tokens[idx]
if (!tok || SINGLE_CLOSERS.include?(tok[0]) ||
(tok[0] == ')' && parens == 0)) &&
!(starter == :ELSE && tok[0] == :ELSE)
insertion = @tokens[idx - 1][0] == "," ? idx - 1 : idx
@tokens.insert(insertion, [:OUTDENT, Value.new(2, line)])
break
end
parens += 1 if tok[0] == '('
parens -= 1 if tok[0] == ')'
end
next 1 unless token[0] == :THEN
@tokens.delete_at(i)
next 0
end
end
# Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them.
def add_implicit_parentheses
stack = [0]
scan_tokens do |prev, token, post, i|
stack.push(0) if token[0] == :INDENT
if token[0] == :OUTDENT
last = stack.pop
stack[-1] += last
end
if stack.last > 0 && (IMPLICIT_END.include?(token[0]) || post.nil?)
idx = token[0] == :OUTDENT ? i + 1 : i
stack.last.times { @tokens.insert(idx, [:CALL_END, Value.new(')', token[1].line)]) }
size, stack[-1] = stack[-1] + 1, 0
next size
end
next 1 unless IMPLICIT_FUNC.include?(prev[0]) && IMPLICIT_CALL.include?(token[0])
@tokens.insert(i, [:CALL_START, Value.new('(', token[1].line)])
stack[-1] += 1
next 2
end
end
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
def ensure_balance(*pairs)
puts "\nbefore ensure_balance: #{@tokens.inspect}" if ENV['VERBOSE']
levels, lines = Hash.new(0), Hash.new
scan_tokens do |prev, token, post, i|
pairs.each do |pair|
open, close = *pair
levels[open] += 1 if token[0] == open
levels[open] -= 1 if token[0] == close
lines[token[0]] = token[1].line
raise ParseError.new(token[0], token[1], nil) if levels[open] < 0
end
next 1
end
unclosed = levels.detect {|k, v| v > 0 }
sym = unclosed && unclosed[0]
raise ParseError.new(sym, Value.new(sym, lines[sym]), nil, "unclosed '#{sym}'") if unclosed
end
# We'd like to support syntax like this:
# el.click((event) ->
# el.hide())
# In order to accomplish this, move outdents that follow closing parens
# inwards, safely. The steps to accomplish this are:
#
# 1. Check that all paired tokens are balanced and in order.
# 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
# to the stack. If you see an ')' or OUTDENT, pop the stack and replace
# it with the inverse of what we've just popped.
# 3. Keep track of "debt" for tokens that we fake, to make sure we end
# up balanced in the end.
#
def rewrite_closing_parens
verbose = ENV['VERBOSE']
stack, debt = [], Hash.new(0)
stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" }
puts "rewrite_closing_original: #{@tokens.inspect}" if verbose
scan_tokens do |prev, token, post, i|
tag, inv = token[0], INVERSES[token[0]]
# Push openers onto the stack.
if EXPRESSION_START.include?(tag)
stack.push(token)
puts "pushing #{tag} #{stack_stats[]}" if verbose
next 1
# The end of an expression, check stack and debt for a pair.
elsif EXPRESSION_TAIL.include?(tag)
puts @tokens[i..-1].inspect if verbose
# If the tag is already in our debt, swallow it.
if debt[inv] > 0
debt[inv] -= 1
@tokens.delete_at(i)
puts "tag in debt #{tag} #{stack_stats[]}" if verbose
next 0
else
# Pop the stack of open delimiters.
match = stack.pop
mtag = match[0]
# Continue onwards if it's the expected tag.
if tag == INVERSES[mtag]
puts "expected tag #{tag} #{stack_stats[]}" if verbose
next 1
else
# Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1
puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose
val = mtag == :INDENT ? match[1] : INVERSES[mtag]
@tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)])
next 1
end
end
else
# Uninteresting token:
next 1
end
end
end
end
end

View File

@@ -1,91 +0,0 @@
module CoffeeScript
# Scope objects form a tree corresponding to the shape of the function
# definitions present in the script. They provide lexical scope, to determine
# whether a variable has been seen before or if it needs to be declared.
class Scope
attr_reader :parent, :expressions, :function, :variables, :temp_variable
# Initialize a scope with its parent, for lookups up the chain,
# as well as the Expressions body where it should declare its variables,
# and the function that it wraps.
def initialize(parent, expressions, function)
@parent, @expressions, @function = parent, expressions, function
@variables = {}
@temp_variable = @parent ? @parent.temp_variable.dup : '__a'
end
# Look up a variable in lexical scope, or declare it if not found.
def find(name, remote=false)
found = check(name)
return found if found || remote
@variables[name.to_sym] = :var
found
end
# Define a local variable as originating from a parameter in current scope
# -- no var required.
def parameter(name)
@variables[name.to_sym] = :param
end
# Just check to see if a variable has already been declared.
def check(name)
return true if @variables[name.to_sym]
!!(@parent && @parent.check(name))
end
# You can reset a found variable on the immediate scope.
def reset(name)
@variables[name.to_sym] = false
end
# Find an available, short, name for a compiler-generated variable.
def free_variable
@temp_variable.succ! while check(@temp_variable)
@variables[@temp_variable.to_sym] = :var
Value.new(@temp_variable.dup)
end
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
def assign(name, value, top=false)
return @parent.assign(name, value, top) if top && @parent
@variables[name.to_sym] = Value.new(value)
end
def declarations?(body)
!declared_variables.empty? && body == @expressions
end
def assignments?(body)
!assigned_variables.empty? && body == @expressions
end
# Return the list of variables first declared in current scope.
def declared_variables
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
end
# Return the list of variables that are supposed to be assigned at the top
# of scope.
def assigned_variables
@variables.select {|k, v| v.is_a?(Value) }.sort_by {|pair| pair[0].to_s }
end
def compiled_declarations
declared_variables.join(', ')
end
def compiled_assignments
assigned_variables.map {|name, val| "#{name} = #{val}"}.join(', ')
end
def inspect
"<Scope:#{__id__} #{@variables.inspect}>"
end
end
end

View File

@@ -1,64 +0,0 @@
module CoffeeScript
# Instead of producing raw Ruby objects, the Lexer produces values of this
# class, wrapping native objects tagged with line number information.
# Values masquerade as both strings and nodes -- being used both as nodes in
# the AST, and as literally-interpolated values in the generated code.
class Value
attr_reader :value, :line
def initialize(value, line=nil)
@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 compile(o={})
to_s
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
def match(regex)
@value.match(regex)
end
def children
[]
end
def statement_only?
false
end
def contains?
false
end
end
end

198
lib/command_line.js Normal file
View File

@@ -0,0 +1,198 @@
(function(){
var BANNER, CoffeeScript, SWITCHES, compile_options, compile_script, compile_scripts, compile_stdio, fs, lint, option_parser, options, optparse, parse_options, path, print_tokens, sources, usage, version, watch_scripts, write_js;
fs = require('fs');
path = require('path');
optparse = require('optparse');
CoffeeScript = require('coffee-script');
BANNER = "coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee";
SWITCHES = [['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-e', '--eval', 'compile a string from the command line'], ['--no-wrap', 'compile without the top-level function wrapper'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-n', '--nodes', 'print the parse tree that Jison produces'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
options = {};
sources = [];
option_parser = null;
// The CommandLine handles all of the functionality of the `coffee` utility.
exports.run = function run() {
var flags, separator;
parse_options();
if (options.help) {
return usage();
}
if (options.version) {
return version();
}
if (options.interactive) {
return require('repl');
}
if (options.stdio) {
return compile_stdio();
}
if (options.eval) {
return compile_script('unknown', sources[0]);
}
if (!(sources.length)) {
return usage();
}
separator = sources.indexOf('--');
flags = [];
if (separator >= 0) {
flags = sources.slice((separator + 1), sources.length);
sources = sources.slice(0, separator);
}
process.ARGV = flags;
if (options.watch) {
watch_scripts();
}
compile_scripts();
return this;
};
// The "--help" usage message.
usage = function usage() {
puts('\n' + option_parser.help() + '\n');
return process.exit(0);
};
// The "--version" message.
version = function version() {
puts("CoffeeScript version " + CoffeeScript.VERSION);
return process.exit(0);
};
// Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
// or JSLint results.
compile_scripts = function compile_scripts() {
var _a, _b, _c, _d, compile, source;
compile = function compile(source) {
return path.exists(source, function(exists) {
if (!(exists)) {
throw new Error('File not found: ' + source);
}
return fs.readFile(source, function(err, code) {
return compile_script(source, code);
});
});
};
_a = []; _b = sources;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
source = _b[_c];
_a.push(compile(source));
}
return _a;
};
// Compile a single source script, containing the given code, according to the
// requested options. Both compile_scripts and watch_scripts share this method.
compile_script = function compile_script(source, code) {
var __dirname, __filename, js, o;
o = options;
try {
if (o.tokens) {
return print_tokens(CoffeeScript.tokens(code));
} else if (o.nodes) {
return puts(CoffeeScript.nodes(code).toString());
} else {
js = CoffeeScript.compile(code, compile_options());
if (o.compile) {
return write_js(source, js);
} else if (o.lint) {
return lint(js);
} else if (o.print || o.eval) {
return print(js);
} else {
__filename = source;
__dirname = path.dirname(source);
return eval(js);
}
}
} catch (err) {
if (o.watch) {
return puts(err.message);
} else {
throw err;
}
}
};
// Listen for and compile scripts over stdio.
compile_stdio = function compile_stdio() {
var code;
code = '';
process.stdio.open();
process.stdio.addListener('data', function(string) {
if (string) {
return code += string;
}
});
return process.stdio.addListener('close', function() {
return process.stdio.write(CoffeeScript.compile(code, compile_options()));
});
};
// Watch a list of source CoffeeScript files, recompiling them every time the
// files are updated.
watch_scripts = function watch_scripts() {
var _a, _b, _c, _d, source, watch;
watch = function watch(source) {
return fs.watchFile(source, {
persistent: true,
interval: 500
}, function(curr, prev) {
if (curr.mtime.getTime() === prev.mtime.getTime()) {
return null;
}
return fs.readFile(source, function(err, code) {
return compile_script(source, code);
});
});
};
_a = []; _b = sources;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
source = _b[_c];
_a.push(watch(source));
}
return _a;
};
// Write out a JavaScript source file with the compiled code.
write_js = function write_js(source, js) {
var dir, filename, js_path;
filename = path.basename(source, path.extname(source)) + '.js';
dir = options.output || path.dirname(source);
js_path = path.join(dir, filename);
return fs.writeFile(js_path, js);
};
// Pipe compiled JS through JSLint (requires a working 'jsl' command).
lint = function lint(js) {
var jsl;
jsl = process.createChildProcess('jsl', ['-nologo', '-stdin']);
jsl.addListener('output', function(result) {
if (result) {
return puts(result.replace(/\n/g, ''));
}
});
jsl.addListener('error', function(result) {
if (result) {
return puts(result);
}
});
jsl.write(js);
return jsl.close();
};
// Pretty-print a token stream.
print_tokens = function print_tokens(tokens) {
var _a, _b, _c, _d, strings, token;
strings = (function() {
_a = []; _b = tokens;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
token = _b[_c];
_a.push('[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']');
}
return _a;
}).call(this);
return puts(strings.join(' '));
};
// Use OptionParser for all the options.
parse_options = function parse_options() {
option_parser = new optparse.OptionParser(SWITCHES, BANNER);
options = option_parser.parse(process.ARGV);
return sources = options.arguments.slice(2, options.arguments.length);
};
// The options to pass to the CoffeeScript compiler.
compile_options = function compile_options() {
return options['no-wrap'] ? {
no_wrap: true
} : {};
};
})();

580
lib/grammar.js Normal file
View File

@@ -0,0 +1,580 @@
(function(){
var Parser, _a, _b, _c, _d, _e, _f, _g, _h, bnf, grammar, name, non_terminal, o, operators, option, part, tokens, unwrap;
var __hasProp = Object.prototype.hasOwnProperty;
Parser = require('jison').Parser;
// DSL ===================================================================
// Detect functions: [
unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/;
// Quickie DSL for Jison access.
o = function o(pattern_string, func, options) {
var match;
if (func) {
func = (match = (func + "").match(unwrap)) ? match[1] : '(' + func + '())';
return [pattern_string, '$$ = ' + func + ';', options];
} else {
return [pattern_string, '$$ = $1;', options];
}
};
// Precedence ===========================================================
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
// Grammar ==============================================================
grammar = {
// All parsing will end in this rule, being the trunk of the AST.
Root: [o("", function() {
return new Expressions();
}), o("TERMINATOR", function() {
return new Expressions();
}), o("Expressions", function() {
return $1;
}), o("Block TERMINATOR", function() {
return $1;
})
],
// Any list of expressions or method body, seperated by line breaks or semis.
Expressions: [o("Expression", function() {
return Expressions.wrap([$1]);
}), o("Expressions TERMINATOR Expression", function() {
return $1.push($3);
}), o("Expressions TERMINATOR", function() {
return $1;
})
],
// All types of expressions in our language. The basic unit of CoffeeScript
// is the expression.
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("Throw"), o("Return"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence"), o("Comment")],
// A block of expressions. Note that the Rewriter will convert some postfix
// forms into blocks for us, by altering the token stream.
Block: [o("INDENT Expressions OUTDENT", function() {
return $2;
}), o("INDENT OUTDENT", function() {
return new Expressions();
})
],
Identifier: [o("IDENTIFIER", function() {
return new LiteralNode(yytext);
})
],
AlphaNumeric: [o("NUMBER", function() {
return new LiteralNode(yytext);
}), o("STRING", function() {
return new LiteralNode(yytext);
})
],
// All hard-coded values. These can be printed straight to JavaScript.
Literal: [o("AlphaNumeric", function() {
return $1;
}), o("JS", function() {
return new LiteralNode(yytext);
}), o("REGEX", function() {
return new LiteralNode(yytext);
}), o("BREAK", function() {
return new LiteralNode(yytext);
}), o("CONTINUE", function() {
return new LiteralNode(yytext);
}), o("TRUE", function() {
return new LiteralNode(true);
}), o("FALSE", function() {
return new LiteralNode(false);
}), o("YES", function() {
return new LiteralNode(true);
}), o("NO", function() {
return new LiteralNode(false);
}), o("ON", function() {
return new LiteralNode(true);
}), o("OFF", function() {
return new LiteralNode(false);
})
],
// Assignment to a variable (or index).
Assign: [o("Value ASSIGN Expression", function() {
return new AssignNode($1, $3);
})
],
// Assignment within an object literal (can be quoted).
AssignObj: [o("Identifier ASSIGN Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("AlphaNumeric ASSIGN Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("Comment")
],
// A return statement.
Return: [o("RETURN Expression", function() {
return new ReturnNode($2);
}), o("RETURN", function() {
return new ReturnNode(new ValueNode(new LiteralNode('null')));
})
],
// A comment.
Comment: [o("COMMENT", function() {
return new CommentNode(yytext);
})
],
// Arithmetic and logical operators
// For Ruby's Operator precedence, see: [
// https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
Operation: [o("! Expression", function() {
return new OpNode('!', $2);
}), o("!! Expression", function() {
return new OpNode('!!', $2);
}), o("- Expression", (function() {
return new OpNode('-', $2);
}), {
prec: 'UMINUS'
}), o("+ Expression", (function() {
return new OpNode('+', $2);
}), {
prec: 'UPLUS'
}), o("NOT Expression", function() {
return new OpNode('not', $2);
}), o("~ Expression", function() {
return new OpNode('~', $2);
}), o("-- Expression", function() {
return new OpNode('--', $2);
}), o("++ Expression", function() {
return new OpNode('++', $2);
}), o("DELETE Expression", function() {
return new OpNode('delete', $2);
}), o("TYPEOF Expression", function() {
return new OpNode('typeof', $2);
}), o("Expression --", function() {
return new OpNode('--', $1, null, true);
}), o("Expression ++", function() {
return new OpNode('++', $1, null, true);
}), o("Expression * Expression", function() {
return new OpNode('*', $1, $3);
}), o("Expression / Expression", function() {
return new OpNode('/', $1, $3);
}), o("Expression % Expression", function() {
return new OpNode('%', $1, $3);
}), o("Expression + Expression", function() {
return new OpNode('+', $1, $3);
}), o("Expression - Expression", function() {
return new OpNode('-', $1, $3);
}), o("Expression << Expression", function() {
return new OpNode('<<', $1, $3);
}), o("Expression >> Expression", function() {
return new OpNode('>>', $1, $3);
}), o("Expression >>> Expression", function() {
return new OpNode('>>>', $1, $3);
}), o("Expression & Expression", function() {
return new OpNode('&', $1, $3);
}), o("Expression | Expression", function() {
return new OpNode('|', $1, $3);
}), o("Expression ^ Expression", function() {
return new OpNode('^', $1, $3);
}), o("Expression <= Expression", function() {
return new OpNode('<=', $1, $3);
}), o("Expression < Expression", function() {
return new OpNode('<', $1, $3);
}), o("Expression > Expression", function() {
return new OpNode('>', $1, $3);
}), o("Expression >= Expression", function() {
return new OpNode('>=', $1, $3);
}), o("Expression == Expression", function() {
return new OpNode('==', $1, $3);
}), o("Expression != Expression", function() {
return new OpNode('!=', $1, $3);
}), o("Expression IS Expression", function() {
return new OpNode('is', $1, $3);
}), o("Expression ISNT Expression", function() {
return new OpNode('isnt', $1, $3);
}), o("Expression && Expression", function() {
return new OpNode('&&', $1, $3);
}), o("Expression || Expression", function() {
return new OpNode('||', $1, $3);
}), o("Expression AND Expression", function() {
return new OpNode('and', $1, $3);
}), o("Expression OR Expression", function() {
return new OpNode('or', $1, $3);
}), o("Expression ? Expression", function() {
return new OpNode('?', $1, $3);
}), o("Expression -= Expression", function() {
return new OpNode('-=', $1, $3);
}), o("Expression += Expression", function() {
return new OpNode('+=', $1, $3);
}), o("Expression /= Expression", function() {
return new OpNode('/=', $1, $3);
}), o("Expression *= Expression", function() {
return new OpNode('*=', $1, $3);
}), o("Expression %= Expression", function() {
return new OpNode('%=', $1, $3);
}), o("Expression ||= Expression", function() {
return new OpNode('||=', $1, $3);
}), o("Expression &&= Expression", function() {
return new OpNode('&&=', $1, $3);
}), o("Expression ?= Expression", function() {
return new OpNode('?=', $1, $3);
}), o("Expression INSTANCEOF Expression", function() {
return new OpNode('instanceof', $1, $3);
}), o("Expression IN Expression", function() {
return new OpNode('in', $1, $3);
})
],
// The existence operator.
Existence: [o("Expression ?", function() {
return new ExistenceNode($1);
})
],
// Function definition.
Code: [o("PARAM_START ParamList PARAM_END FuncGlyph Block", function() {
return new CodeNode($2, $5, $4);
}), o("FuncGlyph Block", function() {
return new CodeNode([], $2, $1);
})
],
// The symbols to signify functions, and bound functions.
FuncGlyph: [o("->", function() {
return 'func';
}), o("=>", function() {
return 'boundfunc';
})
],
// The parameters to a function definition.
ParamList: [o("", function() {
return [];
}), o("Param", function() {
return [$1];
}), o("ParamList , Param", function() {
return $1.concat([$3]);
})
],
// A Parameter (or ParamSplat) in a function definition.
Param: [o("PARAM", function() {
return new LiteralNode(yytext);
}), o("Param . . .", function() {
return new SplatNode($1);
})
],
// A regular splat.
Splat: [o("Expression . . .", function() {
return new SplatNode($1);
})
],
// Expressions that can be treated as values.
Value: [o("Identifier", function() {
return new ValueNode($1);
}), o("Literal", function() {
return new ValueNode($1);
}), o("Array", function() {
return new ValueNode($1);
}), o("Object", function() {
return new ValueNode($1);
}), o("Parenthetical", function() {
return new ValueNode($1);
}), o("Range", function() {
return new ValueNode($1);
}), o("This", function() {
return $1;
}), o("Value Accessor", function() {
return $1.push($2);
}), o("Invocation Accessor", function() {
return new ValueNode($1, [$2]);
})
],
// Accessing into an object or array, through dot or index notation.
Accessor: [o("PROPERTY_ACCESS Identifier", function() {
return new AccessorNode($2);
}), o("PROTOTYPE_ACCESS Identifier", function() {
return new AccessorNode($2, 'prototype');
}), o("SOAK_ACCESS Identifier", function() {
return new AccessorNode($2, 'soak');
}), o("Index"), o("Slice", function() {
return new SliceNode($1);
})
],
// Indexing into an object or array.
Index: [o("INDEX_START Expression INDEX_END", function() {
return new IndexNode($2);
}), o("SOAKED_INDEX_START Expression SOAKED_INDEX_END", function() {
return new IndexNode($2, 'soak');
})
],
// An object literal.
Object: [o("{ AssignList }", function() {
return new ObjectNode($2);
}), o("{ IndentedAssignList }", function() {
return new ObjectNode($2);
})
],
// A class literal.
Class: [o("CLASS Value", function() {
return new ClassNode($2);
}), o("CLASS Value EXTENDS Value", function() {
return new ClassNode($2, $4);
}), o("CLASS Value IndentedAssignList", function() {
return new ClassNode($2, null, $3);
}), o("CLASS Value EXTENDS Value IndentedAssignList", function() {
return new ClassNode($2, $4, $5);
})
],
// Assignment within an object literal (comma or newline separated).
AssignList: [o("", function() {
return [];
}), o("AssignObj", function() {
return [$1];
}), o("AssignList , AssignObj", function() {
return $1.concat([$3]);
}), o("AssignList TERMINATOR AssignObj", function() {
return $1.concat([$3]);
}), o("AssignList , TERMINATOR AssignObj", function() {
return $1.concat([$4]);
})
],
// A list of assignments in a block indentation.
IndentedAssignList: [o("INDENT AssignList OUTDENT", function() {
return $2;
})
],
// All flavors of function call (instantiation, super, and regular).
Call: [o("Invocation", function() {
return $1;
}), o("NEW Invocation", function() {
return $2.new_instance();
}), o("Super", function() {
return $1;
})
],
// Extending an object's prototype.
Extends: [o("Value EXTENDS Value", function() {
return new ExtendsNode($1, $3);
})
],
// A generic function invocation.
Invocation: [o("Value Arguments", function() {
return new CallNode($1, $2);
}), o("Invocation Arguments", function() {
return new CallNode($1, $2);
})
],
// The list of arguments to a function invocation.
Arguments: [o("CALL_START ArgList CALL_END", function() {
return $2;
})
],
// Calling super.
Super: [o("SUPER CALL_START ArgList CALL_END", function() {
return new CallNode('super', $3);
})
],
// This references, either naked or to a property.
This: [o("@", function() {
return new ValueNode(new LiteralNode('this'));
}), o("@ Identifier", function() {
return new ValueNode(new LiteralNode('this'), [new AccessorNode($2)]);
})
],
// The range literal.
Range: [o("[ Expression . . Expression ]", function() {
return new RangeNode($2, $5);
}), o("[ Expression . . . Expression ]", function() {
return new RangeNode($2, $6, true);
})
],
// The slice literal.
Slice: [o("INDEX_START Expression . . Expression INDEX_END", function() {
return new RangeNode($2, $5);
}), o("INDEX_START Expression . . . Expression INDEX_END", function() {
return new RangeNode($2, $6, true);
})
],
// The array literal.
Array: [o("[ ArgList ]", function() {
return new ArrayNode($2);
})
],
// A list of arguments to a method call, or as the contents of an array.
ArgList: [o("", function() {
return [];
}), o("Expression", function() {
return [$1];
}), o("INDENT Expression", function() {
return [$2];
}), o("ArgList , Expression", function() {
return $1.concat([$3]);
}), o("ArgList TERMINATOR Expression", function() {
return $1.concat([$3]);
}), o("ArgList , TERMINATOR Expression", function() {
return $1.concat([$4]);
}), o("ArgList , INDENT Expression", function() {
return $1.concat([$4]);
}), o("ArgList OUTDENT", function() {
return $1;
})
],
// Just simple, comma-separated, required arguments (no fancy syntax).
SimpleArgs: [o("Expression", function() {
return $1;
}), o("SimpleArgs , Expression", function() {
return $1 instanceof Array ? $1.concat([$3]) : [$1].concat([$3]);
})
],
// Try/catch/finally exception handling blocks.
Try: [o("TRY Block Catch", function() {
return new TryNode($2, $3[0], $3[1]);
}), o("TRY Block FINALLY Block", function() {
return new TryNode($2, null, null, $4);
}), o("TRY Block Catch FINALLY Block", function() {
return new TryNode($2, $3[0], $3[1], $5);
})
],
// A catch clause.
Catch: [o("CATCH Identifier Block", function() {
return [$2, $3];
})
],
// Throw an exception.
Throw: [o("THROW Expression", function() {
return new ThrowNode($2);
})
],
// Parenthetical expressions.
Parenthetical: [o("( Expression )", function() {
return new ParentheticalNode($2);
})
],
// The condition for a while loop.
WhileSource: [o("WHILE Expression", function() {
return new WhileNode($2);
}), o("WHILE Expression WHEN Expression", function() {
return new WhileNode($2, {
filter: $4
});
})
],
// The while loop. (there is no do..while).
While: [o("WhileSource Block", function() {
return $1.add_body($2);
}), o("Expression WhileSource", function() {
return $2.add_body($1);
})
],
// Array comprehensions, including guard and current index.
// Looks a little confusing, check nodes.rb for the arguments to ForNode.
For: [o("Expression FOR ForVariables ForSource", function() {
return new ForNode($1, $4, $3[0], $3[1]);
}), o("FOR ForVariables ForSource Block", function() {
return new ForNode($4, $3, $2[0], $2[1]);
})
],
// An array comprehension has variables for the current element and index.
ForVariables: [o("Identifier", function() {
return [$1];
}), o("Identifier , Identifier", function() {
return [$1, $3];
})
],
// The source of the array comprehension can optionally be filtered.
ForSource: [o("IN Expression", function() {
return {
source: $2
};
}), o("OF Expression", function() {
return {
source: $2,
object: true
};
}), o("ForSource WHEN Expression", function() {
$1.filter = $3;
return $1;
}), o("ForSource BY Expression", function() {
$1.step = $3;
return $1;
})
],
// Switch/When blocks.
Switch: [o("SWITCH Expression INDENT Whens OUTDENT", function() {
return $4.rewrite_condition($2);
}), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() {
return $4.rewrite_condition($2).add_else($6, true);
})
],
// The inner list of whens.
Whens: [o("When", function() {
return $1;
}), o("Whens When", function() {
return $1.push($2);
})
],
// An individual when.
When: [o("LEADING_WHEN SimpleArgs Block", function() {
return new IfNode($2, $3, null, {
statement: true
});
}), o("LEADING_WHEN SimpleArgs Block TERMINATOR", function() {
return new IfNode($2, $3, null, {
statement: true
});
}), o("Comment TERMINATOR When", function() {
$3.comment = $1;
return $3;
})
],
// The most basic form of "if".
IfStart: [o("IF Expression Block", function() {
return new IfNode($2, $3);
}), o("IfStart ElsIfs", function() {
return $1.add_else($2);
})
],
IfBlock: [o("IfStart", function() {
return $1;
}), o("IfStart ELSE Block", function() {
return $1.add_else($3);
})
],
// Multiple elsifs can be chained together.
ElsIfs: [o("ELSE IF Expression Block", function() {
return (new IfNode($3, $4)).force_statement();
}), o("ElsIfs ElsIf", function() {
return $1.add_else($2);
})
],
// The full complement of if blocks, including postfix one-liner ifs and unlesses.
If: [o("IfBlock", function() {
return $1;
}), o("Expression IF Expression", function() {
return new IfNode($3, Expressions.wrap([$1]), null, {
statement: true
});
}), o("Expression UNLESS Expression", function() {
return new IfNode($3, Expressions.wrap([$1]), null, {
statement: true,
invert: true
});
})
]
};
// Helpers ==============================================================
// Make the Jison parser.
bnf = {};
tokens = [];
_a = grammar;
for (name in _a) { if (__hasProp.call(_a, name)) {
non_terminal = _a[name];
bnf[name] = (function() {
_b = []; _c = non_terminal;
for (_d = 0, _e = _c.length; _d < _e; _d++) {
option = _c[_d];
_b.push((function() {
_f = option[0].split(" ");
for (_g = 0, _h = _f.length; _g < _h; _g++) {
part = _f[_g];
!grammar[part] ? tokens.push(part) : null;
}
name === "Root" ? (option[1] = "return " + option[1]) : null;
return option;
}).call(this));
}
return _b;
}).call(this);
}}
tokens = tokens.join(" ");
exports.parser = new Parser({
tokens: tokens,
bnf: bnf,
operators: operators.reverse(),
startSymbol: 'Root'
}, {
debug: false
});
})();

472
lib/lexer.js Normal file
View File

@@ -0,0 +1,472 @@
(function(){
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, compact, count, include;
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
// matches against the beginning of the source code. When a match is found,
// a token is produced, we consume the match, and start again. Tokens are in the
// form:
// [tag, value, line_number]
// Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
// Set up the Lexer for both Node.js and the browser, depending on where we are.
if ((typeof process !== "undefined" && process !== null)) {
Rewriter = require('./rewriter').Rewriter;
} else {
this.exports = this;
Rewriter = this.Rewriter;
}
// Constants
// ---------
// Keywords that CoffeeScript shares in common with JavaScript.
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends", "class"];
// CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
// be used standalone, but you can reference them as an attached property.
COFFEE_KEYWORDS = ["then", "unless", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "of", "by", "where", "when"];
// The combined list of keywords is the superset that gets passed verbatim to
// the parser.
KEYWORDS = JS_KEYWORDS.concat(COFFEE_KEYWORDS);
// The list of keywords that are reserved by JavaScript, but not used, or are
// used by CoffeeScript internally. We throw an error when these are encountered,
// to avoid having a JavaScript error at runtime.
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "const", "let", "debugger", "enum", "export", "import", "native", "__extends", "__hasProp"];
// The superset of both JavaScript keywords and reserved words, none of which may
// be used as identifiers or properties.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
// Token matching regexes.
IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/;
NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i;
STRING = /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/;
HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/;
JS = /^(``|`([\s\S]*?)([^\\]|\\\\)`)/;
OPERATOR = /^([+\*&|\/\-%=<>:!?]+)/;
WHITESPACE = /^([ \t]+)/;
COMMENT = /^(((\n?[ \t]*)?#[^\n]*)+)/;
CODE = /^((-|=)>)/;
REGEX = /^(\/(\S.*?)?([^\\]|\\\\)\/[imgy]{0,4})/;
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
LAST_DENTS = /\n([ \t]*)/g;
LAST_DENT = /\n([ \t]*)/;
ASSIGNMENT = /^(:|=)$/;
// Token cleaning regexes.
JS_CLEANER = /(^`|`$)/g;
MULTILINER = /\n/g;
STRING_NEWLINES = /\n[ \t]*/g;
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/mg;
NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/;
HEREDOC_INDENT = /^[ \t]+/mg;
// Tokens which a regular expression will never immediately follow, but which
// a division operator might.
// See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
// Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE'];
// Tokens which could legitimately be invoked or indexed. A opening
// parentheses or bracket following these tokens will be recorded as the start
// of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@'];
// Tokens that indicate an access -- keywords immediately following will be
// treated as identifiers.
ACCESSORS = ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@'];
// Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
// occurs at the start of a line. We disambiguate these from trailing whens to
// avoid an ambiguity in the grammar.
BEFORE_WHEN = ['INDENT', 'OUTDENT', 'TERMINATOR'];
// The Lexer Class
// ---------------
// The Lexer class reads a stream of CoffeeScript and divvys it up into tagged
// tokens. A minor bit of the ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (function() {
Lexer = function Lexer() { };
// Scan by attempting to match tokens one at a time. Slow and steady.
Lexer.prototype.tokenize = function tokenize(code) {
this.code = code;
// The remainder of the source code.
this.i = 0;
// Current character position we're parsing.
this.line = 0;
// The current line.
this.indent = 0;
// The current indent level.
this.indents = [];
// The stack of all indent levels we are currently within.
this.tokens = [];
// Collection of all parsed tokens in the form ['TOKEN_TYPE', value, line]
while (this.i < this.code.length) {
this.chunk = this.code.slice(this.i);
this.extract_next_token();
}
this.close_indentation();
return (new Rewriter()).rewrite(this.tokens);
};
// At every position, run through this list of attempted matches,
// short-circuiting if any of them succeed.
Lexer.prototype.extract_next_token = function extract_next_token() {
if (this.identifier_token()) {
return null;
}
if (this.number_token()) {
return null;
}
if (this.heredoc_token()) {
return null;
}
if (this.string_token()) {
return null;
}
if (this.js_token()) {
return null;
}
if (this.regex_token()) {
return null;
}
if (this.comment_token()) {
return null;
}
if (this.line_token()) {
return null;
}
if (this.whitespace_token()) {
return null;
}
return this.literal_token();
};
// Tokenizers
// ----------
// Matches identifying literals: variables, keywords, method names, etc.
Lexer.prototype.identifier_token = function identifier_token() {
var id, tag;
if (!((id = this.match(IDENTIFIER, 1)))) {
return false;
}
this.name_access_type();
tag = 'IDENTIFIER';
if (include(KEYWORDS, id) && !(include(ACCESSORS, this.tag(0)) && !this.prev().spaced)) {
tag = id.toUpperCase();
}
if (include(RESERVED, id)) {
this.identifier_error(id);
}
if (tag === 'WHEN' && include(BEFORE_WHEN, this.tag())) {
tag = 'LEADING_WHEN';
}
this.token(tag, id);
this.i += id.length;
return true;
};
// Matches numbers, including decimals, hex, and exponential notation.
Lexer.prototype.number_token = function number_token() {
var number;
if (!((number = this.match(NUMBER, 1)))) {
return false;
}
this.token('NUMBER', number);
this.i += number.length;
return true;
};
// Matches strings, including multi-line strings.
Lexer.prototype.string_token = function string_token() {
var escaped, string;
if (!((string = this.match(STRING, 1)))) {
return false;
}
escaped = string.replace(STRING_NEWLINES, " \\\n");
this.token('STRING', escaped);
this.line += count(string, "\n");
this.i += string.length;
return true;
};
// Matches heredocs, adjusting indentation to the correct level.
Lexer.prototype.heredoc_token = function heredoc_token() {
var doc, match;
if (!((match = this.chunk.match(HEREDOC)))) {
return false;
}
doc = this.sanitize_heredoc(match[2] || match[4]);
this.token('STRING', '"' + doc + '"');
this.line += count(match[1], "\n");
this.i += match[1].length;
return true;
};
// Matches interpolated JavaScript.
Lexer.prototype.js_token = function js_token() {
var script;
if (!((script = this.match(JS, 1)))) {
return false;
}
this.token('JS', script.replace(JS_CLEANER, ''));
this.i += script.length;
return true;
};
// Matches regular expression literals.
Lexer.prototype.regex_token = function regex_token() {
var regex;
if (!((regex = this.match(REGEX, 1)))) {
return false;
}
if (include(NOT_REGEX, this.tag())) {
return false;
}
this.token('REGEX', regex);
this.i += regex.length;
return true;
};
// Matches and conumes comments.
Lexer.prototype.comment_token = function comment_token() {
var comment, lines;
if (!((comment = this.match(COMMENT, 1)))) {
return false;
}
this.line += (comment.match(MULTILINER) || []).length;
lines = comment.replace(COMMENT_CLEANER, '').split(MULTILINER);
this.token('COMMENT', compact(lines));
this.token('TERMINATOR', "\n");
this.i += comment.length;
return true;
};
// Matches newlines, indents, and outdents, and determines which is which.
Lexer.prototype.line_token = function line_token() {
var diff, indent, next_character, no_newlines, prev, size;
if (!((indent = this.match(MULTI_DENT, 1)))) {
return false;
}
this.line += indent.match(MULTILINER).length;
this.i += indent.length;
prev = this.prev(2);
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
next_character = this.chunk.match(MULTI_DENT)[4];
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
if (size === this.indent) {
if (no_newlines) {
return this.suppress_newlines(indent);
}
return this.newline_token(indent);
} else if (size > this.indent) {
if (no_newlines) {
return this.suppress_newlines(indent);
}
diff = size - this.indent;
this.token('INDENT', diff);
this.indents.push(diff);
} else {
this.outdent_token(this.indent - size, no_newlines);
}
this.indent = size;
return true;
};
// Record an outdent token or tokens, if we happen to be moving back inwards
// past multiple recorded indents.
Lexer.prototype.outdent_token = function outdent_token(move_out, no_newlines) {
var last_indent;
while (move_out > 0 && this.indents.length) {
last_indent = this.indents.pop();
this.token('OUTDENT', last_indent);
move_out -= last_indent;
}
if (!(this.tag() === 'TERMINATOR' || no_newlines)) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Matches and consumes non-meaningful whitespace. Tag the previous token
// as being "spaced", because there are some cases where it makes a difference.
Lexer.prototype.whitespace_token = function whitespace_token() {
var prev, space;
if (!((space = this.match(WHITESPACE, 1)))) {
return false;
}
prev = this.prev();
if (prev) {
prev.spaced = true;
}
this.i += space.length;
return true;
};
// Generate a newline token. Multiple newlines get merged together.
Lexer.prototype.newline_token = function newline_token(newlines) {
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Use a `\` at a line-ending to suppress the newline.
// The slash is removed here once its job is done.
Lexer.prototype.suppress_newlines = function suppress_newlines(newlines) {
if (this.value() === "\\") {
this.tokens.pop();
}
return true;
};
// We treat all other single characters as a token. Eg.: `( ) , . !`
// Multi-character operators are also literal tokens, so that Jison can assign
// the proper order of operations.
Lexer.prototype.literal_token = function literal_token() {
var match, not_spaced, tag, value;
match = this.chunk.match(OPERATOR);
value = match && match[1];
if (value && value.match(CODE)) {
this.tag_parameters();
}
value = value || this.chunk.substr(0, 1);
not_spaced = !this.prev() || !this.prev().spaced;
tag = value;
if (value.match(ASSIGNMENT)) {
tag = 'ASSIGN';
if (include(JS_FORBIDDEN, this.value)) {
this.assignment_error();
}
} else if (value === ';') {
tag = 'TERMINATOR';
} else if (value === '[' && this.tag() === '?' && not_spaced) {
tag = 'SOAKED_INDEX_START';
this.soaked_index = true;
this.tokens.pop();
} else if (value === ']' && this.soaked_index) {
tag = 'SOAKED_INDEX_END';
this.soaked_index = false;
} else if (include(CALLABLE, this.tag()) && not_spaced) {
if (value === '(') {
tag = 'CALL_START';
}
if (value === '[') {
tag = 'INDEX_START';
}
}
this.token(tag, value);
this.i += value.length;
return true;
};
// Token Manipulators
// ------------------
// As we consume a new `IDENTIFIER`, look at the previous token to determine
// if it's a special kind of accessor.
Lexer.prototype.name_access_type = function name_access_type() {
if (this.value() === '::') {
this.tag(1, 'PROTOTYPE_ACCESS');
}
if (this.value() === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
return this.tokens.splice(-2, 1);
} else {
return this.tag(1, 'PROPERTY_ACCESS');
}
}
};
// Sanitize a heredoc by escaping double quotes and erasing all external
// indentation on the left-hand side.
Lexer.prototype.sanitize_heredoc = function sanitize_heredoc(doc) {
var indent;
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(/"/g, '\\"');
};
// A source of ambiguity in our grammar was parameter lists in function
// definitions (as opposed to argument lists in function calls). Tag
// parameter identifiers in order to avoid this. Also, parameter lists can
// make use of splats.
Lexer.prototype.tag_parameters = function tag_parameters() {
var _a, i, tok;
if (this.tag() !== ')') {
return null;
}
i = 0;
while (true) {
i += 1;
tok = this.prev(i);
if (!tok) {
return null;
}
if ((_a = tok[0]) === 'IDENTIFIER') {
tok[0] = 'PARAM';
} else if (_a === ')') {
tok[0] = 'PARAM_END';
} else if (_a === '(') {
return (tok[0] = 'PARAM_START');
}
}
return true;
};
// Close up all remaining open blocks at the end of the file.
Lexer.prototype.close_indentation = function close_indentation() {
return this.outdent_token(this.indent);
};
// Error for when you try to use a forbidden word in JavaScript as
// an identifier.
Lexer.prototype.identifier_error = function identifier_error(word) {
throw new Error('SyntaxError: Reserved word "' + word + '" on line ' + this.line);
};
// Error for when you try to assign to a reserved word in JavaScript,
// like "function" or "default".
Lexer.prototype.assignment_error = function assignment_error() {
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
};
// Helpers
// -------
// Add a token to the results, taking note of the line number.
Lexer.prototype.token = function token(tag, value) {
return this.tokens.push([tag, value, this.line]);
};
// Peek at a tag in the current token stream.
Lexer.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof tag !== "undefined" && tag !== null)) {
return (tok[0] = tag);
}
return tok[0];
};
// Peek at a value in the current token stream.
Lexer.prototype.value = function value(index, val) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
return (tok[1] = val);
}
return tok[1];
};
// Peek at a previous token, entire.
Lexer.prototype.prev = function prev(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Attempt to match a string against the current chunk, returning the indexed
// match if successful, and `false` otherwise.
Lexer.prototype.match = function match(regex, index) {
var m;
if (!((m = this.chunk.match(regex)))) {
return false;
}
return m ? m[index] : false;
};
return Lexer;
}).call(this);
// Utility Functions
// -----------------
// Does a list include a value?
include = function include(list, value) {
return list.indexOf(value) >= 0;
};
// Trim out all falsy values from an array.
compact = function compact(array) {
var _a, _b, _c, _d, item;
_a = []; _b = array;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
item = _b[_c];
if (item) {
_a.push(item);
}
}
return _a;
};
// Count the number of occurences of a character in a string.
count = function count(string, letter) {
var num, pos;
num = 0;
pos = string.indexOf(letter);
while (pos !== -1) {
num += 1;
pos = string.indexOf(letter, pos + 1);
}
return num;
};
})();

44
lib/narwhal.js Executable file
View File

@@ -0,0 +1,44 @@
(function(){
var coffee, factories, file, loader, os, puts;
// The Narwhal-compatibility wrapper for CoffeeScript.
// Require external dependencies.
os = require('os');
file = require('file');
coffee = require('./coffee-script');
// Alias print to "puts", for Node.js compatibility:
puts = print;
// Compile a string of CoffeeScript into JavaScript.
exports.compile = function compile(source) {
return coffee.compile(source);
};
// Compile a given CoffeeScript file into JavaScript.
exports.compileFile = function compileFile(path) {
return coffee.compile(file.read(path));
};
// Make a factory for the CoffeeScript environment.
exports.makeNarwhalFactory = function makeNarwhalFactory(path) {
var code, factoryText;
code = exports.compileFile(path);
factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}";
if (system.engine === "rhino") {
return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null);
} else {
// eval requires parentheses, but parentheses break compileFunction.
return eval("(" + factoryText + ")");
}
};
// The Narwhal loader for '.coffee' files.
factories = {};
loader = {};
// Reload the coffee-script environment from source.
loader.reload = function reload(topId, path) {
return factories[topId] = function() {
return exports.makeNarwhalFactory(path);
};
};
// Ensure that the coffee-script environment is loaded.
loader.load = function load(topId, path) {
return factories[topId] = factories[topId] || this.reload(topId, path);
};
require.loader.loaders.unshift([".coffee", loader]);
})();

1453
lib/nodes.js Normal file

File diff suppressed because it is too large Load Diff

122
lib/optparse.js Executable file
View File

@@ -0,0 +1,122 @@
(function(){
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, build_rule, build_rules, normalize_arguments;
// Create an OptionParser with a list of valid options, in the form:
// [short-flag (optional), long-flag, description]
// And an optional banner for the usage help.
exports.OptionParser = (function() {
OptionParser = function OptionParser(rules, banner) {
this.banner = banner;
this.rules = build_rules(rules);
return this;
};
// Parse the argument array, populating an options object with all of the
// specified options, and returning it. options.arguments will be an array
// containing the remaning non-option arguments.
OptionParser.prototype.parse = function parse(args) {
var _a, _b, _c, arg, is_option, matched_rule, options, rule;
arguments = Array.prototype.slice.call(arguments, 0);
options = {
arguments: []
};
args = normalize_arguments(args);
while (arg = args.shift()) {
is_option = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG));
matched_rule = false;
_a = this.rules;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
rule = _a[_b];
if (rule.letter === arg || rule.flag === arg) {
options[rule.name] = rule.has_argument ? args.shift() : true;
matched_rule = true;
break;
}
}
if (is_option && !matched_rule) {
throw new Error("unrecognized option: " + arg);
}
if (!(is_option)) {
options.arguments.push(arg);
}
}
return options;
};
// Return the help text for this OptionParser, for --help and such.
OptionParser.prototype.help = function help() {
var _a, _b, _c, _d, _e, _f, _g, _h, i, let_part, lines, rule, spaces;
lines = ['Available options:'];
if (this.banner) {
lines.unshift(this.banner + '\n');
}
_a = this.rules;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
rule = _a[_b];
spaces = 15 - rule.flag.length;
spaces = spaces > 0 ? (function() {
_d = []; _g = 0; _h = spaces;
for (_f = 0, i = _g; (_g <= _h ? i <= _h : i >= _h); (_g <= _h ? i += 1 : i -= 1), _f++) {
_d.push(' ');
}
return _d;
}).call(this).join('') : '';
let_part = rule.letter ? rule.letter + ', ' : ' ';
lines.push(' ' + let_part + rule.flag + spaces + rule.description);
}
return lines.join('\n');
};
return OptionParser;
}).call(this);
// Regex matchers for option flags.
LONG_FLAG = /^(--\w[\w\-]+)/;
SHORT_FLAG = /^(-\w)/;
MULTI_FLAG = /^-(\w{2,})/;
OPTIONAL = /\[(.+)\]/;
// Build rules from a list of valid switch tuples in the form:
// [letter-flag, long-flag, help], or [long-flag, help].
build_rules = function build_rules(rules) {
var _a, _b, _c, _d, tuple;
_a = []; _b = rules;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
tuple = _b[_c];
_a.push((function() {
if (tuple.length < 3) {
tuple.unshift(null);
}
return build_rule.apply(this, tuple);
}).call(this));
}
return _a;
};
// Build a rule from a short-letter-flag, long-form-flag, and help text.
build_rule = function build_rule(letter, flag, description) {
var match;
match = flag.match(OPTIONAL);
flag = flag.match(LONG_FLAG)[1];
return {
name: flag.substr(2),
letter: letter,
flag: flag,
description: description,
has_argument: !!(match && match[1])
};
};
// Normalize arguments by expanding merged flags into multiple flags.
normalize_arguments = function normalize_arguments(args) {
var _a, _b, _c, _d, _e, _f, arg, l, match, result;
args = args.slice(0);
result = [];
_a = args;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
arg = _a[_b];
if ((match = arg.match(MULTI_FLAG))) {
_d = match[1].split('');
for (_e = 0, _f = _d.length; _e < _f; _e++) {
l = _d[_e];
result.push('-' + l);
}
} else {
result.push(arg);
}
}
return result;
};
})();

548
lib/parser.js Executable file

File diff suppressed because one or more lines are too long

32
lib/repl.js Normal file
View File

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

380
lib/rewriter.js Normal file
View File

@@ -0,0 +1,380 @@
(function(){
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, pair;
var __hasProp = Object.prototype.hasOwnProperty;
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
// Tokens that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']];
// Tokens that signal the start of a balanced pair.
EXPRESSION_START = (function() {
_a = []; _b = BALANCED_PAIRS;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
pair = _b[_c];
_a.push(pair[0]);
}
return _a;
}).call(this);
// Tokens that signal the end of a balanced pair.
EXPRESSION_TAIL = (function() {
_e = []; _f = BALANCED_PAIRS;
for (_g = 0, _h = _f.length; _g < _h; _g++) {
pair = _f[_g];
_e.push(pair[1]);
}
return _e;
}).call(this);
// Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL);
// Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END'];
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ','];
IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT', 'OUTDENT'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '@', '->', '=>', '[', '(', '{'];
// The inverse mappings of token pairs we're trying to fix up.
INVERSES = {};
_i = BALANCED_PAIRS;
for (_j = 0, _k = _i.length; _j < _k; _j++) {
pair = _i[_j];
INVERSES[pair[0]] = pair[1];
INVERSES[pair[1]] = pair[0];
}
// Single-line flavors of block expressions that have unclosed endings.
// The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
// In order to keep the grammar simple, the stream of tokens that the Lexer
// emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
// indentation, and single-line flavors of expressions.
exports.Rewriter = (function() {
Rewriter = function Rewriter() { };
// Rewrite the token stream in multiple passes, one logical filter at
// a time. This could certainly be changed into a single pass through the
// stream, with a big ol' efficient switch, but it's much nicer like this.
Rewriter.prototype.rewrite = function rewrite(tokens) {
this.tokens = tokens;
this.adjust_comments();
this.remove_leading_newlines();
this.remove_mid_expression_newlines();
this.move_commas_outside_outdents();
this.close_open_calls_and_indexes();
this.add_implicit_indentation();
this.add_implicit_parentheses();
this.ensure_balance(BALANCED_PAIRS);
this.rewrite_closing_parens();
return this.tokens;
};
// Rewrite the token stream, looking one token ahead and behind.
// Allow the return value of the block to tell us how many tokens to move
// forwards (or backwards) in the stream, to make sure we don't miss anything
// as the stream changes length under our feet.
Rewriter.prototype.scan_tokens = function scan_tokens(block) {
var i, move;
i = 0;
while (true) {
if (!(this.tokens[i])) {
break;
}
move = block(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i);
i += move;
}
return true;
};
// Massage newlines and indentations so that comments don't have to be
// correctly indented, or appear on their own line.
Rewriter.prototype.adjust_comments = function adjust_comments() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var after;
if (!(token[0] === 'COMMENT')) {
return 1;
}
after = this.tokens[i + 2];
if (after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens.splice(i, 0, after);
return 1;
} else if (prev && prev[0] !== 'TERMINATOR' && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
return 2;
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Leading newlines would introduce an ambiguity in the grammar, so we
// dispatch them here.
Rewriter.prototype.remove_leading_newlines = function remove_leading_newlines() {
if (this.tokens[0][0] === 'TERMINATOR') {
return this.tokens.shift();
}
};
// Some blocks occur in the middle of expressions -- when we're expecting
// this, remove their trailing newlines.
Rewriter.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (!(post && EXPRESSION_CLOSE.indexOf(post[0]) >= 0 && token[0] === 'TERMINATOR')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Make sure that we don't accidentally break trailing commas, which need
// to go on the outside of expression closers.
Rewriter.prototype.move_commas_outside_outdents = function move_commas_outside_outdents() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (token[0] === 'OUTDENT' && prev[0] === ',') {
this.tokens.splice(i, 1, token);
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// We've tagged the opening parenthesis of a method call, and the opening
// bracket of an indexing operation. Match them with their close.
Rewriter.prototype.close_open_calls_and_indexes = function close_open_calls_and_indexes() {
var brackets, parens;
parens = [0];
brackets = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _l;
if ((_l = token[0]) === 'CALL_START') {
parens.push(0);
} else if (_l === 'INDEX_START') {
brackets.push(0);
} else if (_l === '(') {
parens[parens.length - 1] += 1;
} else if (_l === '[') {
brackets[brackets.length - 1] += 1;
} else if (_l === ')') {
if (parens[parens.length - 1] === 0) {
parens.pop();
token[0] = 'CALL_END';
} else {
parens[parens.length - 1] -= 1;
}
} else if (_l === ']') {
if (brackets[brackets.length - 1] === 0) {
brackets.pop();
token[0] = 'INDEX_END';
} else {
brackets[brackets.length - 1] -= 1;
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Methods may be optionally called without parentheses, for simple cases.
// Insert the implicit parentheses here, so that the parser doesn't have to
// deal with them.
Rewriter.prototype.add_implicit_parentheses = function add_implicit_parentheses() {
var stack;
stack = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _l, _m, _n, _o, idx, last, size, stack_pointer, tag, tmp;
tag = token[0];
if (tag === 'INDENT') {
stack.push(0);
}
if (tag === 'OUTDENT') {
last = stack.pop();
stack[stack.length - 1] += last;
}
if (IMPLICIT_END.indexOf(tag) >= 0 || !(typeof post !== "undefined" && post !== null)) {
if (tag === 'INDENT' && prev && IMPLICIT_BLOCK.indexOf(prev[0]) >= 0) {
return 1;
}
if (stack[stack.length - 1] > 0 || tag === 'INDENT') {
idx = tag === 'OUTDENT' ? i + 1 : i;
stack_pointer = tag === 'INDENT' ? 2 : 1;
_n = 0; _o = stack[stack.length - stack_pointer];
for (_m = 0, tmp = _n; (_n <= _o ? tmp < _o : tmp > _o); (_n <= _o ? tmp += 1 : tmp -= 1), _m++) {
this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
}
size = stack[stack.length - stack_pointer] + 1;
stack[stack.length - stack_pointer] = 0;
return size;
}
}
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(tag) >= 0)) {
return 1;
}
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
stack[stack.length - 1] += 1;
return 2;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Because our grammar is LALR(1), it can't handle some single-line
// expressions that lack ending delimiters. Use the lexer to add the implicit
// blocks, so it doesn't need to.
// ')' can close a single-line block, but we need to make sure it's balanced.
Rewriter.prototype.add_implicit_indentation = function add_implicit_indentation() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var idx, insertion, parens, pre, starter, tok;
if (!(SINGLE_LINERS.indexOf(token[0]) >= 0 && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) {
return 1;
}
starter = token[0];
this.tokens.splice(i + 1, 0, ['INDENT', 2, token[2]]);
idx = i + 1;
parens = 0;
while (true) {
idx += 1;
tok = this.tokens[idx];
pre = this.tokens[idx - 1];
if ((!tok || (SINGLE_CLOSERS.indexOf(tok[0]) >= 0 && tok[1] !== ';') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
insertion = pre[0] === "," ? idx - 1 : idx;
this.tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]]);
break;
}
if (tok[0] === '(') {
parens += 1;
}
if (tok[0] === ')') {
parens -= 1;
}
}
if (!(token[0] === 'THEN')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Ensure that all listed pairs of tokens are correctly balanced throughout
// the course of the token stream.
Rewriter.prototype.ensure_balance = function ensure_balance(pairs) {
var _l, _m, key, levels, unclosed, value;
levels = {};
this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var _l, _m, _n, _o, close, open;
_l = pairs;
for (_m = 0, _n = _l.length; _m < _n; _m++) {
pair = _l[_m];
_o = pair;
open = _o[0];
close = _o[1];
levels[open] = levels[open] || 0;
if (token[0] === open) {
levels[open] += 1;
}
if (token[0] === close) {
levels[open] -= 1;
}
if (levels[open] < 0) {
throw new Error("too many " + token[1]);
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
unclosed = (function() {
_l = []; _m = levels;
for (key in _m) { if (__hasProp.call(_m, key)) {
value = _m[key];
if (value > 0) {
_l.push(key);
}
}}
return _l;
}).call(this);
if (unclosed.length) {
throw new Error("unclosed " + unclosed[0]);
}
};
// We'd like to support syntax like this:
// el.click((event) ->
// el.hide())
// In order to accomplish this, move outdents that follow closing parens
// inwards, safely. The steps to accomplish this are:
// 1. Check that all paired tokens are balanced and in order.
// 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
// to the stack. If you see an ')' or OUTDENT, pop the stack and replace
// it with the inverse of what we've just popped.
// 3. Keep track of "debt" for tokens that we fake, to make sure we end
// up balanced in the end.
Rewriter.prototype.rewrite_closing_parens = function rewrite_closing_parens() {
var _l, debt, key, stack, val;
stack = [];
debt = {};
_l = INVERSES;
for (key in _l) { if (__hasProp.call(_l, key)) {
val = _l[key];
((debt[key] = 0));
}}
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var inv, match, mtag, tag;
tag = token[0];
inv = INVERSES[token[0]];
// Push openers onto the stack.
if (EXPRESSION_START.indexOf(tag) >= 0) {
stack.push(token);
return 1;
// The end of an expression, check stack and debt for a pair.
} else if (EXPRESSION_TAIL.indexOf(tag) >= 0) {
// If the tag is already in our debt, swallow it.
if (debt[inv] > 0) {
debt[inv] -= 1;
this.tokens.splice(i, 1);
return 0;
} else {
// Pop the stack of open delimiters.
match = stack.pop();
mtag = match[0];
// Continue onwards if it's the expected tag.
if (tag === INVERSES[mtag]) {
return 1;
} else {
// Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1;
val = mtag === 'INDENT' ? match[1] : INVERSES[mtag];
this.tokens.splice(i, 0, [INVERSES[mtag], val]);
return 1;
}
}
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
return Rewriter;
}).call(this);
})();

116
lib/scope.js Normal file
View File

@@ -0,0 +1,116 @@
(function(){
var Scope;
var __hasProp = Object.prototype.hasOwnProperty;
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
// Scope objects form a tree corresponding to the shape of the function
// definitions present in the script. They provide lexical scope, to determine
// whether a variable has been seen before or if it needs to be declared.
// Initialize a scope with its parent, for lookups up the chain,
// as well as the Expressions body where it should declare its variables,
// and the function that it wraps.
exports.Scope = (function() {
Scope = function Scope(parent, expressions, method) {
var _a;
_a = [parent, expressions, method];
this.parent = _a[0];
this.expressions = _a[1];
this.method = _a[2];
this.variables = {};
this.temp_var = this.parent ? this.parent.temp_var : '_a';
return this;
};
// Look up a variable in lexical scope, or declare it if not found.
Scope.prototype.find = function find(name) {
if (this.check(name)) {
return true;
}
this.variables[name] = 'var';
return false;
};
// Define a local variable as originating from a parameter in current scope
// -- no var required.
Scope.prototype.parameter = function parameter(name) {
return this.variables[name] = 'param';
};
// Just check to see if a variable has already been declared.
Scope.prototype.check = function check(name) {
if (this.variables[name]) {
return true;
}
return !!(this.parent && this.parent.check(name));
};
// You can reset a found variable on the immediate scope.
Scope.prototype.reset = function reset(name) {
return delete this.variables[name];
};
// Find an available, short, name for a compiler-generated variable.
Scope.prototype.free_variable = function free_variable() {
var ordinal;
while (this.check(this.temp_var)) {
ordinal = 1 + parseInt(this.temp_var.substr(1), 36);
this.temp_var = '_' + ordinal.toString(36).replace(/\d/g, 'a');
}
this.variables[this.temp_var] = 'var';
return this.temp_var;
};
// Ensure that an assignment is made at the top of scope (or top-level
// scope, if requested).
Scope.prototype.assign = function assign(name, value, top_level) {
if (top_level && this.parent) {
return this.parent.assign(name, value, top_level);
}
return this.variables[name] = {
value: value,
assigned: true
};
};
// Does this scope reference any variables that need to be declared in the
// given function body?
Scope.prototype.has_declarations = function has_declarations(body) {
return body === this.expressions && this.declared_variables().length;
};
// Does this scope reference any assignments that need to be declared at the
// top of the given function body?
Scope.prototype.has_assignments = function has_assignments(body) {
return body === this.expressions && this.assigned_variables().length;
};
// Return the list of variables first declared in current scope.
Scope.prototype.declared_variables = function declared_variables() {
var _a, _b, key, val;
return (function() {
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val === 'var') {
_a.push(key);
}
}}
return _a;
}).call(this).sort();
};
// Return the list of variables that are supposed to be assigned at the top
// of scope.
Scope.prototype.assigned_variables = function assigned_variables() {
var _a, _b, key, val;
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val.assigned) {
_a.push(key + ' = ' + val.value);
}
}}
return _a;
};
// Compile the string representing all of the declared variables for this scope.
Scope.prototype.compiled_declarations = function compiled_declarations() {
return this.declared_variables().join(', ');
};
// Compile the string performing all of the variable assignments for this scope.
Scope.prototype.compiled_assignments = function compiled_assignments() {
return this.assigned_variables().join(', ');
};
return Scope;
}).call(this);
})();

Some files were not shown because too many files have changed in this diff Show More