Compare commits

...

214 Commits
0.3.2 ... 0.5.2

Author SHA1 Message Date
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
207 changed files with 14533 additions and 9071 deletions

65
Cakefile Normal file
View File

@@ -0,0 +1,65 @@
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
task 'install', 'install CoffeeScript into /usr/local', ->
exec([
'mkdir -p /usr/local/lib/coffee-script'
'cp -rf bin lib LICENSE README package.json src vendor /usr/local/lib/coffee-script'
'ln -sf /usr/local/lib/coffee-script/lib/bin/coffee /usr/local/bin/coffee'
'ln -sf /usr/local/lib/coffee-script/lib/bin/cake /usr/local/bin/cake'
].join(' && '))
task 'build', 'build the CoffeeScript language from source', ->
fs.readdir 'src', (err, files) ->
files: 'src/' + file for file in files when file.match(/\.coffee$/)
run ['-o', 'lib'].concat(files)
task 'build:parser', 'rebuild the Jison parser (run build first)', ->
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'
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

14
README
View File

@@ -22,20 +22,20 @@
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.
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,44 +1,8 @@
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"
task :narwhal do
sh "bin/coffee src/narwhal/*.coffee -o lib/coffee_script/narwhal"
end
desc "Continually compile the CoffeeScript/Node.js components with --watch"
task :node do
sh "bin/coffee -w src/*.coffee -o lib/coffee_script/"
end
desc "Compile and install the Ultraviolet syntax highlighter"
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
@@ -46,7 +10,6 @@ task :doc do
child = fork { exec "bin/coffee documentation/coffee/*.coffee -o documentation/js -w" }
at_exit { Process.kill("INT", child) }
Signal.trap("INT") { exit }
# `uv -t idle -s coffeescript -h examples/underscore.coffee > documentation/underscore.html`
loop do
mtime = File.stat(source).mtime
if !@mtime || mtime > @mtime
@@ -58,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

7
bin/cake Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
process.mixin(require('sys'));
require.paths.unshift(__dirname + '/../lib');
require('cake').run();

View File

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

View File

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

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

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

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,134 @@ 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('logo.png');
position: absolute;
top: 0px; left: 10px;
}
.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;
}
.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;
}
.navigation .contents.repl_wrapper .code {
-webkit-box-shadow: none; -moz-box-shadow: none;
background: transparent;
border: 0;
}
.navigation .code button {
bottom: 10px;
}
.navigation .compile {
left: 240px; right: auto;
}
.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;
}
#repl_source {
border: 0;
padding: 5px 7px;
margin-left: 5px;
min-height: 250px;
resize: none;
width: 295px;
}
#repl_results {
font-family: Monaco, Consolas, "Lucida Console", monospace;
text-transform: none;
font-weight: normal;
min-height: 260px;
width: 355px;
}

BIN
documentation/css/logo.png Normal file

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, '')
@@ -25,9 +25,67 @@
</head>
<body>
<div class="container">
<div id="fadeout"></div>
<h1><sub style="font-size: 100px;">&#9749;</sub> CoffeeScript</h1>
<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="#inheritance">Inheritance, and Calling Super from a Subclass</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">
<textarea id="repl_source">reverse: (string) ->
string.split('').reverse().join ''
alert reverse '!tpircseeffoC'</textarea>
<pre id="repl_results"></pre>
<button class="compile" onclick="javascript: repl_compile();">compile</button>
<button class="run" onclick="javascript: repl_run();">run</button>
<br class="clear" />
</div>
</div>
</div>
</div>
<div class="container">
<span class="bookmark" id="top"></span>
<p>
CoffeeScript is a little language that compiles into JavaScript. Think
@@ -39,8 +97,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 +108,13 @@
<p>
<b>Latest Version:</b>
<a href="http://gemcutter.org/gems/coffee-script">0.3.2</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.2">0.5.2</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,21 +133,38 @@
<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>, 0.1.30 or higher. 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.2">0.5.2</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
This 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://nodejs.org/">Node.js</a> (or <a href="http://narwhaljs.org/">Narwhal</a>), the <tt>coffee</tt>
well as debug them. The <tt>coffee</tt>
command also provides direct evaluation and an interactive REPL.
When compiling to JavaScript, <tt>coffee</tt> writes the output
as <tt>.js</tt> files in the same directory by default, but output
@@ -129,17 +175,14 @@ gem install coffee-script</pre>
<tr>
<td width="25%"><code>-i, --interactive</code></td>
<td>
Launch an interactive CoffeeScript session.
Requires <a href="http://nodejs.org/">Node.js</a>,
or <a href="http://narwhaljs.org/">Narwhal</a>, with <tt>--narwhal</tt>.
Launch an interactive CoffeeScript session to try short snippets.
</td>
</tr>
<tr>
<td><code>-r, --run</code></td>
<td>
Compile and execute scripts without saving the intermediate
JavaScript. Requires <a href="http://nodejs.org/">Node.js</a>,
or <a href="http://narwhaljs.org/">Narwhal</a>, with <tt>--narwhal</tt>.
Compile and execute a given CoffeeScript without saving the intermediate
JavaScript.
</td>
</tr>
<tr>
@@ -165,31 +208,26 @@ 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>
</td>
</tr>
<tr>
<td><code>-t, --tokens</code></td>
<td>
Instead of parsing the CoffeeScript, just lex it, and print out the
token stream: <tt>[:IDENTIFIER, "square"], [":", ":"], [:PARAM, "x"]</tt> ...
</td>
</tr>
<tr>
<td><code>-v, --verbose</code></td>
<td>
As the JavaScript is being generated, print out every step of code
generation, including lexical scope and the nodes in the
AST.
command line. For example:<br /><tt>coffee -e "square: (x) -> x * x"</tt>
</td>
</tr>
<tr>
@@ -200,16 +238,25 @@ gem install coffee-script</pre>
</td>
</tr>
<tr>
<td><code>-g, --globals</code></td>
<td><code>-t, --tokens</code></td>
<td>
Suppress all variable declarations at the top-level, effectively adding
those variables to the global scope. (Used by the REPL.)
Instead of parsing the CoffeeScript, just lex it, and print out the
token stream: <tt>[IDENTIFIER square] [ASSIGN :] [PARAM_START (]</tt> ...
</td>
</tr>
<tr>
<td><code>--install-bundle</code></td>
<td><code>-tr, --tree</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>
@@ -224,7 +271,10 @@ 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>
@@ -236,7 +286,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
@@ -249,40 +306,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
@@ -292,7 +355,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
@@ -322,7 +386,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,
@@ -341,7 +406,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,
@@ -374,7 +440,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</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
@@ -383,7 +450,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
@@ -392,7 +460,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
@@ -407,7 +476,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
@@ -431,7 +501,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>
@@ -447,7 +518,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.
@@ -485,8 +557,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,
@@ -509,13 +582,14 @@ 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">
<p>
<span id="inheritance" class="bookmark"></span>
<b class="header">Inheritance, and Calling Super from a Subclass</b>
JavaScript's prototypal inheritance has always been a bit of a
brain-bender, with a whole family tree of libraries that provide a cleaner
@@ -537,7 +611,8 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</p>
<%= code_for('super', true) %>
<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
@@ -559,7 +634,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
@@ -570,7 +646,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
@@ -578,7 +655,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
@@ -595,14 +673,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>
@@ -611,7 +691,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>
@@ -624,51 +705,107 @@ 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 codebase includes a compressed and minified version of the compiler
(<a href="extras/coffee-script.js">Download current version here, 43k when gzipped</a>).
Include <tt>coffee-script.js</tt> on the page <b>after</b> any <tt>text/coffeescript</tt> tags
with inline CoffeeScript, 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 />
Use <tt>bin/coffee</tt> to test your changes,
<tt>rake test</tt> to run the test suite,
<tt>rake build:parser</tt> to regenerate the Racc parser if you're
working on the grammar,
and <tt>rake gem:install</tt> to
create and install a custom version of the gem.
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">CoffeeScript Issues</a><br />
Bugs reports, feature requests, and general discussion all belong here.
</li>
<li>
<a href="http://github.com/mattly/rack-coffee">rack-coffee</a><br />
Rack middleware for serving CoffeeScripts as JavaScript directly to
the browser, without having to compile them first. From
<a href="http://github.com/mattly">Matt Lyon</a>.
</li>
<li>
<a href="http://github.com/jnicklas/bistro_car">BistroCar</a><br />
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.
</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.
If you'd like to chat, stop by <tt>#coffeescript</tt> on Freenode.
</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.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 />
@@ -824,5 +961,42 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
</div>
<script type="text/coffeescript">
window.repl_compile: ->
source: $('#repl_source').val()
window.compiled_js: ''
try
window.compiled_js: CoffeeScript.compile source, {no_wrap: true}
catch error then alert error
$('#repl_results').html window.compiled_js
window.repl_run: ->
try
eval window.compiled_js
catch error then alert error
nav: $('.navigation')
current_nav: null
close_menus: ->
current_nav.removeClass 'active' if current_nav
current_nav: null
nav.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()
</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

@@ -11,4 +11,4 @@
}
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, 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; _c < _b.length; _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];
_d = asteroids;
for (_e = 0; _e < _d.length; _e++) {
roid = _d[_e];
_f = asteroids;
for (_g = 0; _g < _f.length; _g++) {
roid2 = _f[_g];
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, test;
_a = []; _b = test_files;
for (_c = 0; _c < _b.length; _c++) {
test = _b[_c];
_a.push(fs.readFile(test, function(err, code) {
return eval(coffee.compile(code));
}));
}
return _a;
});
})();

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

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

View File

@@ -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, 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; _c < _b.length; _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];
})();

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

@@ -1,32 +1,30 @@
(function(){
var Animal, Horse, Snake, __a, __b, sam, tom;
var Animal, Horse, Snake, _a, _b, sam, tom;
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;
_a = function(){};
_a.prototype = Animal.prototype;
Snake.__superClass__ = Animal.prototype;
Snake.prototype = new __a();
Snake.prototype = new _a();
Snake.prototype.constructor = Snake;
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;
_b = function(){};
_b.prototype = Animal.prototype;
Horse.__superClass__ = Animal.prototype;
Horse.prototype = new __b();
Horse.prototype = new _b();
Horse.prototype.constructor = Horse;
Horse.prototype.move = function move() {
alert("Galloping...");
@@ -36,4 +34,4 @@
tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
})();
})();

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>

View File

@@ -1,23 +1,14 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<!DOCTYPE html>
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" media="screen,projection,print" href="css/idle.css" />
<title>Underscore.coffee</title>
<style type="text/css">
body {
margin: 0; padding: 0;
}
pre.idle {
font-family: "Monaco", "Consolas", monospace;
font-size: 12px;
}
</style>
</head>
<body>
<pre class="idle"><span class="line-numbers"> 1 </span>
<pre class="idle"><span class="line-numbers"> 1 </span>
<span class="line-numbers"> 2 </span> <span class="Comment"><span class="Comment">#</span> Underscore.coffee</span>
<span class="line-numbers"> 3 </span> <span class="Comment"><span class="Comment">#</span> (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.</span>
<span class="line-numbers"> 4 </span> <span class="Comment"><span class="Comment">#</span> Underscore is freely distributable under the terms of the MIT license.</span>
@@ -25,66 +16,66 @@
<span class="line-numbers"> 6 </span> <span class="Comment"><span class="Comment">#</span> Oliver Steele's Functional, and John Resig's Micro-Templating.</span>
<span class="line-numbers"> 7 </span> <span class="Comment"><span class="Comment">#</span> For all details and documentation:</span>
<span class="line-numbers"> 8 </span> <span class="Comment"><span class="Comment">#</span> http://documentcloud.github.com/underscore/</span>
<span class="line-numbers"> 9 </span>
<span class="line-numbers"> 10 </span>
<span class="line-numbers"> 9 </span>
<span class="line-numbers"> 10 </span>
<span class="line-numbers"> 11 </span> <span class="Comment"><span class="Comment">#</span> ------------------------- Baseline setup ---------------------------------</span>
<span class="line-numbers"> 12 </span>
<span class="line-numbers"> 12 </span>
<span class="line-numbers"> 13 </span> <span class="Comment"><span class="Comment">#</span> Establish the root object, &quot;window&quot; in the browser, or &quot;global&quot; on the server.</span>
<span class="line-numbers"> 14 </span> <span class="FunctionName">root</span><span class="Keyword">:</span> <span class="Variable">this</span>
<span class="line-numbers"> 15 </span>
<span class="line-numbers"> 16 </span>
<span class="line-numbers"> 15 </span>
<span class="line-numbers"> 16 </span>
<span class="line-numbers"> 17 </span> <span class="Comment"><span class="Comment">#</span> Save the previous value of the &quot;_&quot; variable.</span>
<span class="line-numbers"> 18 </span> <span class="FunctionName">previousUnderscore</span><span class="Keyword">:</span> root._
<span class="line-numbers"> 19 </span>
<span class="line-numbers"> 20 </span>
<span class="line-numbers"> 19 </span>
<span class="line-numbers"> 20 </span>
<span class="line-numbers"> 21 </span> <span class="Comment"><span class="Comment">#</span> If Underscore is called as a function, it returns a wrapped object that</span>
<span class="line-numbers"> 22 </span> <span class="Comment"><span class="Comment">#</span> can be used OO-style. This wrapper holds altered versions of all the</span>
<span class="line-numbers"> 23 </span> <span class="Comment"><span class="Comment">#</span> underscore functions. Wrapped objects may be chained.</span>
<span class="line-numbers"> 24 </span> <span class="FunctionName">wrapper</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 25 </span> <span class="FunctionName">this._wrapped</span><span class="Keyword">:</span> obj
<span class="line-numbers"> 26 </span> <span class="Variable">this</span>
<span class="line-numbers"> 27 </span>
<span class="line-numbers"> 28 </span>
<span class="line-numbers"> 27 </span>
<span class="line-numbers"> 28 </span>
<span class="line-numbers"> 29 </span> <span class="Comment"><span class="Comment">#</span> Establish the object that gets thrown to break out of a loop iteration.</span>
<span class="line-numbers"> 30 </span> <span class="FunctionName">breaker</span><span class="Keyword">:</span> <span class="Keyword">if</span> <span class="Keyword">typeof</span>(StopIteration) <span class="Keyword">is</span> <span class="String"><span class="String">'</span>undefined<span class="String">'</span></span> <span class="Keyword">then</span> <span class="String"><span class="String">'</span>__break__<span class="String">'</span></span> <span class="Keyword">else</span> StopIteration
<span class="line-numbers"> 31 </span>
<span class="line-numbers"> 32 </span>
<span class="line-numbers"> 31 </span>
<span class="line-numbers"> 32 </span>
<span class="line-numbers"> 33 </span> <span class="Comment"><span class="Comment">#</span> Create a safe reference to the Underscore object forreference below.</span>
<span class="line-numbers"> 34 </span> <span class="FunctionName">_</span><span class="Keyword">:</span> <span class="FunctionName">root._</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">new</span> <span class="TypeName">wrapper</span>(obj)
<span class="line-numbers"> 35 </span>
<span class="line-numbers"> 36 </span>
<span class="line-numbers"> 35 </span>
<span class="line-numbers"> 36 </span>
<span class="line-numbers"> 37 </span> <span class="Comment"><span class="Comment">#</span> Export the Underscore object for CommonJS.</span>
<span class="line-numbers"> 38 </span> <span class="Keyword">if</span> <span class="Keyword">typeof</span>(exports) <span class="Keyword">!</span><span class="Keyword">=</span> <span class="String"><span class="String">'</span>undefined<span class="String">'</span></span> <span class="Keyword">then</span> <span class="FunctionName">exports._</span><span class="Keyword">:</span> _
<span class="line-numbers"> 39 </span>
<span class="line-numbers"> 40 </span>
<span class="line-numbers"> 39 </span>
<span class="line-numbers"> 40 </span>
<span class="line-numbers"> 41 </span> <span class="Comment"><span class="Comment">#</span> Create quick reference variables for speed access to core prototypes.</span>
<span class="line-numbers"> 42 </span> <span class="FunctionName">slice</span><span class="Keyword">:</span> <span class="FunctionName">Array:</span><span class="Keyword">:</span>slice
<span class="line-numbers"> 43 </span> <span class="FunctionName">unshift</span><span class="Keyword">:</span> <span class="FunctionName">Array:</span><span class="Keyword">:</span>unshift
<span class="line-numbers"> 44 </span> <span class="FunctionName">toString</span><span class="Keyword">:</span> <span class="FunctionName">Object:</span><span class="Keyword">:</span>toString
<span class="line-numbers"> 45 </span> <span class="FunctionName">hasOwnProperty</span><span class="Keyword">:</span> <span class="FunctionName">Object:</span><span class="Keyword">:</span>hasOwnProperty
<span class="line-numbers"> 46 </span> <span class="FunctionName">propertyIsEnumerable</span><span class="Keyword">:</span> <span class="FunctionName">Object:</span><span class="Keyword">:</span>propertyIsEnumerable
<span class="line-numbers"> 47 </span>
<span class="line-numbers"> 48 </span>
<span class="line-numbers"> 47 </span>
<span class="line-numbers"> 48 </span>
<span class="line-numbers"> 49 </span> <span class="Comment"><span class="Comment">#</span> Current version.</span>
<span class="line-numbers"> 50 </span> <span class="FunctionName">_.VERSION</span><span class="Keyword">:</span> <span class="String"><span class="String">'</span>0.5.7<span class="String">'</span></span>
<span class="line-numbers"> 51 </span>
<span class="line-numbers"> 52 </span>
<span class="line-numbers"> 50 </span> <span class="FunctionName">_.VERSION</span><span class="Keyword">:</span> <span class="String"><span class="String">'</span>0.5.8<span class="String">'</span></span>
<span class="line-numbers"> 51 </span>
<span class="line-numbers"> 52 </span>
<span class="line-numbers"> 53 </span> <span class="Comment"><span class="Comment">#</span> ------------------------ Collection Functions: ---------------------------</span>
<span class="line-numbers"> 54 </span>
<span class="line-numbers"> 54 </span>
<span class="line-numbers"> 55 </span> <span class="Comment"><span class="Comment">#</span> The cornerstone, an each implementation.</span>
<span class="line-numbers"> 56 </span> <span class="Comment"><span class="Comment">#</span> Handles objects implementing forEach, arrays, and raw objects.</span>
<span class="line-numbers"> 57 </span> <span class="FunctionName">_.each</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 58 </span> <span class="FunctionName">index</span><span class="Keyword">:</span> <span class="Number">0</span>
<span class="line-numbers"> 59 </span> <span class="Keyword">try</span>
<span class="line-numbers"> 60 </span> <span class="Keyword">return</span> obj.forEach(iterator, context) <span class="Keyword">if</span> obj.forEach
<span class="line-numbers"> 61 </span> <span class="Keyword">if</span> _.isArray(obj) <span class="Keyword">or</span> _.isArguments(obj)
<span class="line-numbers"> 61 </span> <span class="Keyword">if</span> _.isNumber(obj.length)
<span class="line-numbers"> 62 </span> <span class="Keyword">return</span> iterator.call(context, obj[i], i, obj) <span class="Keyword">for</span> i <span class="Keyword">in</span> [<span class="Number">0</span>...obj.length]
<span class="line-numbers"> 63 </span> iterator.call(context, val, key, obj) <span class="Keyword">for</span> key, val <span class="Keyword">of</span> obj
<span class="line-numbers"> 64 </span> <span class="Keyword">catch</span> e
<span class="line-numbers"> 65 </span> <span class="Keyword">throw</span> e <span class="Keyword">if</span> e <span class="Keyword">isnt</span> breaker
<span class="line-numbers"> 66 </span> obj
<span class="line-numbers"> 67 </span>
<span class="line-numbers"> 68 </span>
<span class="line-numbers"> 67 </span>
<span class="line-numbers"> 68 </span>
<span class="line-numbers"> 69 </span> <span class="Comment"><span class="Comment">#</span> Return the results of applying the iterator to each element. Use JavaScript</span>
<span class="line-numbers"> 70 </span> <span class="Comment"><span class="Comment">#</span> 1.6's version of map, if possible.</span>
<span class="line-numbers"> 71 </span> <span class="FunctionName">_.map</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -93,8 +84,8 @@
<span class="line-numbers"> 74 </span> _.each obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 75 </span> results.push(iterator.call(context, value, index, list))
<span class="line-numbers"> 76 </span> results
<span class="line-numbers"> 77 </span>
<span class="line-numbers"> 78 </span>
<span class="line-numbers"> 77 </span>
<span class="line-numbers"> 78 </span>
<span class="line-numbers"> 79 </span> <span class="Comment"><span class="Comment">#</span> Reduce builds up a single result from a list of values. Also known as</span>
<span class="line-numbers"> 80 </span> <span class="Comment"><span class="Comment">#</span> inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.</span>
<span class="line-numbers"> 81 </span> <span class="FunctionName">_.reduce</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, memo, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -102,8 +93,8 @@
<span class="line-numbers"> 83 </span> _.each obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 84 </span> <span class="FunctionName">memo</span><span class="Keyword">:</span> iterator.call(context, memo, value, index, list)
<span class="line-numbers"> 85 </span> memo
<span class="line-numbers"> 86 </span>
<span class="line-numbers"> 87 </span>
<span class="line-numbers"> 86 </span>
<span class="line-numbers"> 87 </span>
<span class="line-numbers"> 88 </span> <span class="Comment"><span class="Comment">#</span> The right-associative version of reduce, also known as foldr. Uses</span>
<span class="line-numbers"> 89 </span> <span class="Comment"><span class="Comment">#</span> JavaScript 1.8's version of reduceRight, if available.</span>
<span class="line-numbers"> 90 </span> <span class="FunctionName">_.reduceRight</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, memo, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -111,8 +102,8 @@
<span class="line-numbers"> 92 </span> _.each _.clone(_.toArray(obj)).reverse(), <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 93 </span> <span class="FunctionName">memo</span><span class="Keyword">:</span> iterator.call(context, memo, value, index, obj)
<span class="line-numbers"> 94 </span> memo
<span class="line-numbers"> 95 </span>
<span class="line-numbers"> 96 </span>
<span class="line-numbers"> 95 </span>
<span class="line-numbers"> 96 </span>
<span class="line-numbers"> 97 </span> <span class="Comment"><span class="Comment">#</span> Return the first value which passes a truth test.</span>
<span class="line-numbers"> 98 </span> <span class="FunctionName">_.detect</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 99 </span> <span class="FunctionName">result</span><span class="Keyword">:</span> <span class="BuiltInConstant">null</span>
@@ -121,8 +112,8 @@
<span class="line-numbers"> 102 </span> <span class="FunctionName">result</span><span class="Keyword">:</span> value
<span class="line-numbers"> 103 </span> _.breakLoop()
<span class="line-numbers"> 104 </span> result
<span class="line-numbers"> 105 </span>
<span class="line-numbers"> 106 </span>
<span class="line-numbers"> 105 </span>
<span class="line-numbers"> 106 </span>
<span class="line-numbers"> 107 </span> <span class="Comment"><span class="Comment">#</span> Return all the elements that pass a truth test. Use JavaScript 1.6's</span>
<span class="line-numbers"> 108 </span> <span class="Comment"><span class="Comment">#</span> filter(), if it exists.</span>
<span class="line-numbers"> 109 </span> <span class="FunctionName">_.select</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -131,16 +122,16 @@
<span class="line-numbers"> 112 </span> _.each obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 113 </span> results.push(value) <span class="Keyword">if</span> iterator.call(context, value, index, list)
<span class="line-numbers"> 114 </span> results
<span class="line-numbers"> 115 </span>
<span class="line-numbers"> 116 </span>
<span class="line-numbers"> 115 </span>
<span class="line-numbers"> 116 </span>
<span class="line-numbers"> 117 </span> <span class="Comment"><span class="Comment">#</span> Return all the elements for which a truth test fails.</span>
<span class="line-numbers"> 118 </span> <span class="FunctionName">_.reject</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 119 </span> <span class="FunctionName">results</span><span class="Keyword">:</span> []
<span class="line-numbers"> 120 </span> _.each obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 121 </span> results.push(value) <span class="Keyword">if</span> <span class="Keyword">not</span> iterator.call(context, value, index, list)
<span class="line-numbers"> 122 </span> results
<span class="line-numbers"> 123 </span>
<span class="line-numbers"> 124 </span>
<span class="line-numbers"> 123 </span>
<span class="line-numbers"> 124 </span>
<span class="line-numbers"> 125 </span> <span class="Comment"><span class="Comment">#</span> Determine whether all of the elements match a truth test. Delegate to</span>
<span class="line-numbers"> 126 </span> <span class="Comment"><span class="Comment">#</span> JavaScript 1.6's every(), if it is present.</span>
<span class="line-numbers"> 127 </span> <span class="FunctionName">_.all</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -150,8 +141,8 @@
<span class="line-numbers"> 131 </span> _.each obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 132 </span> _.breakLoop() <span class="Keyword">unless</span> (<span class="FunctionName">result</span><span class="Keyword">:</span> result <span class="Keyword">and</span> iterator.call(context, value, index, list))
<span class="line-numbers"> 133 </span> result
<span class="line-numbers"> 134 </span>
<span class="line-numbers"> 135 </span>
<span class="line-numbers"> 134 </span>
<span class="line-numbers"> 135 </span>
<span class="line-numbers"> 136 </span> <span class="Comment"><span class="Comment">#</span> Determine if at least one element in the object matches a truth test. Use</span>
<span class="line-numbers"> 137 </span> <span class="Comment"><span class="Comment">#</span> JavaScript 1.6's some(), if it exists.</span>
<span class="line-numbers"> 138 </span> <span class="FunctionName">_.any</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -161,28 +152,28 @@
<span class="line-numbers"> 142 </span> _.each obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 143 </span> _.breakLoop() <span class="Keyword">if</span> (<span class="FunctionName">result</span><span class="Keyword">:</span> iterator.call(context, value, index, list))
<span class="line-numbers"> 144 </span> result
<span class="line-numbers"> 145 </span>
<span class="line-numbers"> 146 </span>
<span class="line-numbers"> 145 </span>
<span class="line-numbers"> 146 </span>
<span class="line-numbers"> 147 </span> <span class="Comment"><span class="Comment">#</span> Determine if a given value is included in the array or object,</span>
<span class="line-numbers"> 148 </span> <span class="Comment"><span class="Comment">#</span> based on '==='.</span>
<span class="line-numbers"> 149 </span> <span class="FunctionName">_.include</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, target</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 150 </span> <span class="Keyword">return</span> _.indexOf(obj, target) <span class="Keyword">isnt</span> <span class="Keyword">-</span><span class="Number">1</span> <span class="Keyword">if</span> _.isArray(obj)
<span class="line-numbers"> 150 </span> <span class="Keyword">return</span> _.indexOf(obj, target) <span class="Keyword">isnt</span> <span class="Keyword">-</span><span class="Number">1</span> <span class="Keyword">if</span> obj <span class="Keyword">and</span> _.isFunction(obj.indexOf)
<span class="line-numbers"> 151 </span> <span class="Keyword">for</span> key, val <span class="Keyword">of</span> obj
<span class="line-numbers"> 152 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span> <span class="Keyword">if</span> val <span class="Keyword">is</span> target
<span class="line-numbers"> 153 </span> <span class="BuiltInConstant">false</span>
<span class="line-numbers"> 154 </span>
<span class="line-numbers"> 155 </span>
<span class="line-numbers"> 154 </span>
<span class="line-numbers"> 155 </span>
<span class="line-numbers"> 156 </span> <span class="Comment"><span class="Comment">#</span> Invoke a method with arguments on every item in a collection.</span>
<span class="line-numbers"> 157 </span> <span class="FunctionName">_.invoke</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, method</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 158 </span> <span class="FunctionName">args</span><span class="Keyword">:</span> _.rest(arguments, <span class="Number">2</span>)
<span class="line-numbers"> 159 </span> (<span class="Keyword">if</span> method <span class="Keyword">then</span> val[method] <span class="Keyword">else</span> val).apply(val, args) <span class="Keyword">for</span> val <span class="Keyword">in</span> obj
<span class="line-numbers"> 160 </span>
<span class="line-numbers"> 161 </span>
<span class="line-numbers"> 160 </span>
<span class="line-numbers"> 161 </span>
<span class="line-numbers"> 162 </span> <span class="Comment"><span class="Comment">#</span> Convenience version of a common use case of map: fetching a property.</span>
<span class="line-numbers"> 163 </span> <span class="FunctionName">_.pluck</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, key</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 164 </span> _.map(obj, (<span class="FunctionArgument">(</span><span class="FunctionArgument">val</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> val[key]))
<span class="line-numbers"> 165 </span>
<span class="line-numbers"> 166 </span>
<span class="line-numbers"> 165 </span>
<span class="line-numbers"> 166 </span>
<span class="line-numbers"> 167 </span> <span class="Comment"><span class="Comment">#</span> Return the maximum item or (item-based computation).</span>
<span class="line-numbers"> 168 </span> <span class="FunctionName">_.max</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 169 </span> <span class="Keyword">return</span> Math.max.apply(Math, obj) <span class="Keyword">if</span> <span class="Keyword">not</span> iterator <span class="Keyword">and</span> _.isArray(obj)
@@ -191,8 +182,8 @@
<span class="line-numbers"> 172 </span> <span class="FunctionName">computed</span><span class="Keyword">:</span> <span class="Keyword">if</span> iterator <span class="Keyword">then</span> iterator.call(context, value, index, list) <span class="Keyword">else</span> value
<span class="line-numbers"> 173 </span> computed <span class="Keyword">&gt;=</span> result.computed <span class="Keyword">and</span> (<span class="FunctionName">result</span><span class="Keyword">:</span> {<span class="FunctionName">value</span><span class="Keyword">:</span> value, <span class="FunctionName">computed</span><span class="Keyword">:</span> computed})
<span class="line-numbers"> 174 </span> result.value
<span class="line-numbers"> 175 </span>
<span class="line-numbers"> 176 </span>
<span class="line-numbers"> 175 </span>
<span class="line-numbers"> 176 </span>
<span class="line-numbers"> 177 </span> <span class="Comment"><span class="Comment">#</span> Return the minimum element (or element-based computation).</span>
<span class="line-numbers"> 178 </span> <span class="FunctionName">_.min</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 179 </span> <span class="Keyword">return</span> Math.min.apply(Math, obj) <span class="Keyword">if</span> <span class="Keyword">not</span> iterator <span class="Keyword">and</span> _.isArray(obj)
@@ -201,8 +192,8 @@
<span class="line-numbers"> 182 </span> <span class="FunctionName">computed</span><span class="Keyword">:</span> <span class="Keyword">if</span> iterator <span class="Keyword">then</span> iterator.call(context, value, index, list) <span class="Keyword">else</span> value
<span class="line-numbers"> 183 </span> computed <span class="Keyword">&lt;</span> result.computed <span class="Keyword">and</span> (<span class="FunctionName">result</span><span class="Keyword">:</span> {<span class="FunctionName">value</span><span class="Keyword">:</span> value, <span class="FunctionName">computed</span><span class="Keyword">:</span> computed})
<span class="line-numbers"> 184 </span> result.value
<span class="line-numbers"> 185 </span>
<span class="line-numbers"> 186 </span>
<span class="line-numbers"> 185 </span>
<span class="line-numbers"> 186 </span>
<span class="line-numbers"> 187 </span> <span class="Comment"><span class="Comment">#</span> Sort the object's values by a criteria produced by an iterator.</span>
<span class="line-numbers"> 188 </span> <span class="FunctionName">_.sortBy</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, iterator, context</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 189 </span> _.pluck(((_.map obj, <span class="FunctionArgument">(</span><span class="FunctionArgument">value, index, list</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -211,8 +202,8 @@
<span class="line-numbers"> 192 </span> <span class="FunctionName">a</span><span class="Keyword">:</span> left.criteria; <span class="FunctionName">b</span><span class="Keyword">:</span> right.criteria
<span class="line-numbers"> 193 </span> <span class="Keyword">if</span> a <span class="Keyword">&lt;</span> b <span class="Keyword">then</span> <span class="Keyword">-</span><span class="Number">1</span> <span class="Keyword">else</span> <span class="Keyword">if</span> a <span class="Keyword">&gt;</span> b <span class="Keyword">then</span> <span class="Number">1</span> <span class="Keyword">else</span> <span class="Number">0</span>
<span class="line-numbers"> 194 </span> )), <span class="String"><span class="String">'</span>value<span class="String">'</span></span>)
<span class="line-numbers"> 195 </span>
<span class="line-numbers"> 196 </span>
<span class="line-numbers"> 195 </span>
<span class="line-numbers"> 196 </span>
<span class="line-numbers"> 197 </span> <span class="Comment"><span class="Comment">#</span> Use a comparator function to figure out at what index an object should</span>
<span class="line-numbers"> 198 </span> <span class="Comment"><span class="Comment">#</span> be inserted so as to maintain order. Uses binary search.</span>
<span class="line-numbers"> 199 </span> <span class="FunctionName">_.sortedIndex</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array, obj, iterator</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -222,8 +213,8 @@
<span class="line-numbers"> 203 </span> <span class="FunctionName">mid</span><span class="Keyword">:</span> (low <span class="Keyword">+</span> high) <span class="Keyword">&gt;</span><span class="Keyword">&gt;</span> <span class="Number">1</span>
<span class="line-numbers"> 204 </span> <span class="Keyword">if</span> iterator(array[mid]) <span class="Keyword">&lt;</span> iterator(obj) <span class="Keyword">then</span> <span class="FunctionName">low</span><span class="Keyword">:</span> mid <span class="Keyword">+</span> <span class="Number">1</span> <span class="Keyword">else</span> <span class="FunctionName">high</span><span class="Keyword">:</span> mid
<span class="line-numbers"> 205 </span> low
<span class="line-numbers"> 206 </span>
<span class="line-numbers"> 207 </span>
<span class="line-numbers"> 206 </span>
<span class="line-numbers"> 207 </span>
<span class="line-numbers"> 208 </span> <span class="Comment"><span class="Comment">#</span> Convert anything iterable into a real, live array.</span>
<span class="line-numbers"> 209 </span> <span class="FunctionName">_.toArray</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">iterable</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 210 </span> <span class="Keyword">return</span> [] <span class="Keyword">if</span> (<span class="Keyword">!</span>iterable)
@@ -231,51 +222,51 @@
<span class="line-numbers"> 212 </span> <span class="Keyword">return</span> iterable <span class="Keyword">if</span> (_.isArray(iterable))
<span class="line-numbers"> 213 </span> <span class="Keyword">return</span> slice.call(iterable) <span class="Keyword">if</span> (_.isArguments(iterable))
<span class="line-numbers"> 214 </span> _.values(iterable)
<span class="line-numbers"> 215 </span>
<span class="line-numbers"> 216 </span>
<span class="line-numbers"> 215 </span>
<span class="line-numbers"> 216 </span>
<span class="line-numbers"> 217 </span> <span class="Comment"><span class="Comment">#</span> Return the number of elements in an object.</span>
<span class="line-numbers"> 218 </span> <span class="FunctionName">_.size</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> _.toArray(obj).length
<span class="line-numbers"> 219 </span>
<span class="line-numbers"> 220 </span>
<span class="line-numbers"> 219 </span>
<span class="line-numbers"> 220 </span>
<span class="line-numbers"> 221 </span> <span class="Comment"><span class="Comment">#</span> -------------------------- Array Functions: ------------------------------</span>
<span class="line-numbers"> 222 </span>
<span class="line-numbers"> 222 </span>
<span class="line-numbers"> 223 </span> <span class="Comment"><span class="Comment">#</span> Get the first element of an array. Passing &quot;n&quot; will return the first N</span>
<span class="line-numbers"> 224 </span> <span class="Comment"><span class="Comment">#</span> values in the array. Aliased as &quot;head&quot;. The &quot;guard&quot; check allows it to work</span>
<span class="line-numbers"> 225 </span> <span class="Comment"><span class="Comment">#</span> with _.map.</span>
<span class="line-numbers"> 226 </span> <span class="FunctionName">_.first</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array, n, guard</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 227 </span> <span class="Keyword">if</span> n <span class="Keyword">and</span> <span class="Keyword">not</span> guard <span class="Keyword">then</span> slice.call(array, <span class="Number">0</span>, n) <span class="Keyword">else</span> array[<span class="Number">0</span>]
<span class="line-numbers"> 228 </span>
<span class="line-numbers"> 229 </span>
<span class="line-numbers"> 228 </span>
<span class="line-numbers"> 229 </span>
<span class="line-numbers"> 230 </span> <span class="Comment"><span class="Comment">#</span> Returns everything but the first entry of the array. Aliased as &quot;tail&quot;.</span>
<span class="line-numbers"> 231 </span> <span class="Comment"><span class="Comment">#</span> Especially useful on the arguments object. Passing an &quot;index&quot; will return</span>
<span class="line-numbers"> 232 </span> <span class="Comment"><span class="Comment">#</span> the rest of the values in the array from that index onward. The &quot;guard&quot;</span>
<span class="line-numbers"> 233 </span> <span class="Comment"><span class="Comment">#</span> check allows it to work with _.map.</span>
<span class="line-numbers"> 234 </span> <span class="FunctionName">_.rest</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array, index, guard</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 235 </span> slice.call(array, <span class="Keyword">if</span> _.isUndefined(index) <span class="Keyword">or</span> guard <span class="Keyword">then</span> <span class="Number">1</span> <span class="Keyword">else</span> index)
<span class="line-numbers"> 236 </span>
<span class="line-numbers"> 237 </span>
<span class="line-numbers"> 236 </span>
<span class="line-numbers"> 237 </span>
<span class="line-numbers"> 238 </span> <span class="Comment"><span class="Comment">#</span> Get the last element of an array.</span>
<span class="line-numbers"> 239 </span> <span class="FunctionName">_.last</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> array[array.length <span class="Keyword">-</span> <span class="Number">1</span>]
<span class="line-numbers"> 240 </span>
<span class="line-numbers"> 241 </span>
<span class="line-numbers"> 240 </span>
<span class="line-numbers"> 241 </span>
<span class="line-numbers"> 242 </span> <span class="Comment"><span class="Comment">#</span> Trim out all falsy values from an array.</span>
<span class="line-numbers"> 243 </span> <span class="FunctionName">_.compact</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> array[i] <span class="Keyword">for</span> i <span class="Keyword">in</span> [<span class="Number">0</span>...array.length] <span class="Keyword">when</span> array[i]
<span class="line-numbers"> 244 </span>
<span class="line-numbers"> 245 </span>
<span class="line-numbers"> 243 </span> <span class="FunctionName">_.compact</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> item <span class="Keyword">for</span> item <span class="Keyword">in</span> array <span class="Keyword">when</span> item
<span class="line-numbers"> 244 </span>
<span class="line-numbers"> 245 </span>
<span class="line-numbers"> 246 </span> <span class="Comment"><span class="Comment">#</span> Return a completely flattened version of an array.</span>
<span class="line-numbers"> 247 </span> <span class="FunctionName">_.flatten</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 248 </span> _.reduce array, [], <span class="FunctionArgument">(</span><span class="FunctionArgument">memo, value</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 249 </span> <span class="Keyword">return</span> memo.concat(_.flatten(value)) <span class="Keyword">if</span> _.isArray(value)
<span class="line-numbers"> 250 </span> memo.push(value)
<span class="line-numbers"> 251 </span> memo
<span class="line-numbers"> 252 </span>
<span class="line-numbers"> 253 </span>
<span class="line-numbers"> 252 </span>
<span class="line-numbers"> 253 </span>
<span class="line-numbers"> 254 </span> <span class="Comment"><span class="Comment">#</span> Return a version of the array that does not contain the specified value(s).</span>
<span class="line-numbers"> 255 </span> <span class="FunctionName">_.without</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 256 </span> <span class="FunctionName">values</span><span class="Keyword">:</span> _.rest(arguments)
<span class="line-numbers"> 257 </span> val <span class="Keyword">for</span> val <span class="Keyword">in</span> _.toArray(array) <span class="Keyword">when</span> <span class="Keyword">not</span> _.include(values, val)
<span class="line-numbers"> 258 </span>
<span class="line-numbers"> 259 </span>
<span class="line-numbers"> 258 </span>
<span class="line-numbers"> 259 </span>
<span class="line-numbers"> 260 </span> <span class="Comment"><span class="Comment">#</span> Produce a duplicate-free version of the array. If the array has already</span>
<span class="line-numbers"> 261 </span> <span class="Comment"><span class="Comment">#</span> been sorted, you have the option of using a faster algorithm.</span>
<span class="line-numbers"> 262 </span> <span class="FunctionName">_.uniq</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array, isSorted</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -283,8 +274,8 @@
<span class="line-numbers"> 264 </span> <span class="Keyword">for</span> el, i <span class="Keyword">in</span> _.toArray(array)
<span class="line-numbers"> 265 </span> memo.push(el) <span class="Keyword">if</span> i <span class="Keyword">is</span> <span class="Number">0</span> <span class="Keyword">||</span> (<span class="Keyword">if</span> isSorted <span class="Keyword">is</span> <span class="BuiltInConstant">true</span> <span class="Keyword">then</span> _.last(memo) <span class="Keyword">isnt</span> el <span class="Keyword">else</span> <span class="Keyword">not</span> _.include(memo, el))
<span class="line-numbers"> 266 </span> memo
<span class="line-numbers"> 267 </span>
<span class="line-numbers"> 268 </span>
<span class="line-numbers"> 267 </span>
<span class="line-numbers"> 268 </span>
<span class="line-numbers"> 269 </span> <span class="Comment"><span class="Comment">#</span> Produce an array that contains every item shared between all the</span>
<span class="line-numbers"> 270 </span> <span class="Comment"><span class="Comment">#</span> passed-in arrays.</span>
<span class="line-numbers"> 271 </span> <span class="FunctionName">_.intersect</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -292,8 +283,8 @@
<span class="line-numbers"> 273 </span> _.select _.uniq(array), <span class="FunctionArgument">(</span><span class="FunctionArgument">item</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 274 </span> _.all rest, <span class="FunctionArgument">(</span><span class="FunctionArgument">other</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 275 </span> _.indexOf(other, item) <span class="Keyword">&gt;=</span> <span class="Number">0</span>
<span class="line-numbers"> 276 </span>
<span class="line-numbers"> 277 </span>
<span class="line-numbers"> 276 </span>
<span class="line-numbers"> 277 </span>
<span class="line-numbers"> 278 </span> <span class="Comment"><span class="Comment">#</span> Zip together multiple lists into a single array -- elements that share</span>
<span class="line-numbers"> 279 </span> <span class="Comment"><span class="Comment">#</span> an index go together.</span>
<span class="line-numbers"> 280 </span> <span class="FunctionName">_.zip</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
@@ -302,8 +293,8 @@
<span class="line-numbers"> 283 </span> <span class="Keyword">for</span> i <span class="Keyword">in</span> [<span class="Number">0</span>...length]
<span class="line-numbers"> 284 </span> results[i]<span class="Keyword">:</span> _.pluck(arguments, String(i))
<span class="line-numbers"> 285 </span> results
<span class="line-numbers"> 286 </span>
<span class="line-numbers"> 287 </span>
<span class="line-numbers"> 286 </span>
<span class="line-numbers"> 287 </span>
<span class="line-numbers"> 288 </span> <span class="Comment"><span class="Comment">#</span> If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),</span>
<span class="line-numbers"> 289 </span> <span class="Comment"><span class="Comment">#</span> we need this function. Return the position of the first occurence of an</span>
<span class="line-numbers"> 290 </span> <span class="Comment"><span class="Comment">#</span> item in an array, or -1 if the item is not included in the array.</span>
@@ -313,8 +304,8 @@
<span class="line-numbers"> 294 </span> <span class="Keyword">while</span> l <span class="Keyword">-</span> i
<span class="line-numbers"> 295 </span> <span class="Keyword">if</span> array[i] <span class="Keyword">is</span> item <span class="Keyword">then</span> <span class="Keyword">return</span> i <span class="Keyword">else</span> i<span class="Keyword">++</span>
<span class="line-numbers"> 296 </span> <span class="Keyword">-</span><span class="Number">1</span>
<span class="line-numbers"> 297 </span>
<span class="line-numbers"> 298 </span>
<span class="line-numbers"> 297 </span>
<span class="line-numbers"> 298 </span>
<span class="line-numbers"> 299 </span> <span class="Comment"><span class="Comment">#</span> Provide JavaScript 1.6's lastIndexOf, delegating to the native function,</span>
<span class="line-numbers"> 300 </span> <span class="Comment"><span class="Comment">#</span> if possible.</span>
<span class="line-numbers"> 301 </span> <span class="FunctionName">_.lastIndexOf</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">array, item</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
@@ -323,16 +314,16 @@
<span class="line-numbers"> 304 </span> <span class="Keyword">while</span> i
<span class="line-numbers"> 305 </span> <span class="Keyword">if</span> array[i] <span class="Keyword">is</span> item <span class="Keyword">then</span> <span class="Keyword">return</span> i <span class="Keyword">else</span> i<span class="Keyword">--</span>
<span class="line-numbers"> 306 </span> <span class="Keyword">-</span><span class="Number">1</span>
<span class="line-numbers"> 307 </span>
<span class="line-numbers"> 308 </span>
<span class="line-numbers"> 307 </span>
<span class="line-numbers"> 308 </span>
<span class="line-numbers"> 309 </span> <span class="Comment"><span class="Comment">#</span> Generate an integer Array containing an arithmetic progression. A port of</span>
<span class="line-numbers"> 310 </span> <span class="Comment"><span class="Comment">#</span> the native Python range() function. See:</span>
<span class="line-numbers"> 311 </span> <span class="Comment"><span class="Comment">#</span> http://docs.python.org/library/functions.html#range</span>
<span class="line-numbers"> 312 </span> <span class="FunctionName">_.range</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">start, stop, step</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 313 </span> <span class="FunctionName">a</span><span class="Keyword">:</span> arguments
<span class="line-numbers"> 314 </span> <span class="FunctionName">solo</span><span class="Keyword">:</span> a.length <span class="Keyword">&lt;=</span> <span class="Number">1</span>
<span class="line-numbers"> 315 </span> <span class="FunctionName">i</span><span class="Keyword">:</span> <span class="FunctionName">start</span><span class="Keyword">:</span> <span class="Keyword">if</span> solo <span class="Keyword">then</span> <span class="Number">0</span> <span class="Keyword">else</span> a[<span class="Number">0</span>];
<span class="line-numbers"> 316 </span> <span class="FunctionName">stop</span><span class="Keyword">:</span> <span class="Keyword">if</span> solo <span class="Keyword">then</span> a[<span class="Number">0</span>] <span class="Keyword">else</span> a[<span class="Number">1</span>];
<span class="line-numbers"> 315 </span> <span class="FunctionName">i</span><span class="Keyword">:</span> <span class="FunctionName">start</span><span class="Keyword">:</span> <span class="Keyword">if</span> solo <span class="Keyword">then</span> <span class="Number">0</span> <span class="Keyword">else</span> a[<span class="Number">0</span>]
<span class="line-numbers"> 316 </span> <span class="FunctionName">stop</span><span class="Keyword">:</span> <span class="Keyword">if</span> solo <span class="Keyword">then</span> a[<span class="Number">0</span>] <span class="Keyword">else</span> a[<span class="Number">1</span>]
<span class="line-numbers"> 317 </span> <span class="FunctionName">step</span><span class="Keyword">:</span> a[<span class="Number">2</span>] <span class="Keyword">or</span> <span class="Number">1</span>
<span class="line-numbers"> 318 </span> <span class="FunctionName">len</span><span class="Keyword">:</span> Math.ceil((stop <span class="Keyword">-</span> start) <span class="Keyword">/</span> step)
<span class="line-numbers"> 319 </span> <span class="Keyword">return</span> [] <span class="Keyword">if</span> len <span class="Keyword">&lt;=</span> <span class="Number">0</span>
@@ -343,45 +334,45 @@
<span class="line-numbers"> 324 </span> range[idx]<span class="Keyword">:</span> i
<span class="line-numbers"> 325 </span> idx<span class="Keyword">++</span>
<span class="line-numbers"> 326 </span> i<span class="Keyword">+</span><span class="Keyword">=</span> step
<span class="line-numbers"> 327 </span>
<span class="line-numbers"> 328 </span>
<span class="line-numbers"> 327 </span>
<span class="line-numbers"> 328 </span>
<span class="line-numbers"> 329 </span> <span class="Comment"><span class="Comment">#</span> ----------------------- Function Functions: -----------------------------</span>
<span class="line-numbers"> 330 </span>
<span class="line-numbers"> 330 </span>
<span class="line-numbers"> 331 </span> <span class="Comment"><span class="Comment">#</span> Create a function bound to a given object (assigning 'this', and arguments,</span>
<span class="line-numbers"> 332 </span> <span class="Comment"><span class="Comment">#</span> optionally). Binding with arguments is also known as 'curry'.</span>
<span class="line-numbers"> 333 </span> <span class="FunctionName">_.bind</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">func, obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 334 </span> <span class="FunctionName">args</span><span class="Keyword">:</span> _.rest(arguments, <span class="Number">2</span>)
<span class="line-numbers"> 335 </span> <span class="Storage">-&gt;</span> func.apply(obj <span class="Keyword">or</span> root, args.concat(arguments))
<span class="line-numbers"> 336 </span>
<span class="line-numbers"> 337 </span>
<span class="line-numbers"> 336 </span>
<span class="line-numbers"> 337 </span>
<span class="line-numbers"> 338 </span> <span class="Comment"><span class="Comment">#</span> Bind all of an object's methods to that object. Useful for ensuring that</span>
<span class="line-numbers"> 339 </span> <span class="Comment"><span class="Comment">#</span> all callbacks defined on an object belong to it.</span>
<span class="line-numbers"> 340 </span> <span class="FunctionName">_.bindAll</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 341 </span> <span class="FunctionName">funcs</span><span class="Keyword">:</span> <span class="Keyword">if</span> arguments.length <span class="Keyword">&gt;</span> <span class="Number">1</span> <span class="Keyword">then</span> _.rest(arguments) <span class="Keyword">else</span> _.functions(obj)
<span class="line-numbers"> 342 </span> _.each(funcs, <span class="FunctionArgument">(</span><span class="FunctionArgument">f</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> obj[f]<span class="Keyword">:</span> _.bind(obj[f], obj))
<span class="line-numbers"> 343 </span> obj
<span class="line-numbers"> 344 </span>
<span class="line-numbers"> 345 </span>
<span class="line-numbers"> 344 </span>
<span class="line-numbers"> 345 </span>
<span class="line-numbers"> 346 </span> <span class="Comment"><span class="Comment">#</span> Delays a function for the given number of milliseconds, and then calls</span>
<span class="line-numbers"> 347 </span> <span class="Comment"><span class="Comment">#</span> it with the arguments supplied.</span>
<span class="line-numbers"> 348 </span> <span class="FunctionName">_.delay</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">func, wait</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 349 </span> <span class="FunctionName">args</span><span class="Keyword">:</span> _.rest(arguments, <span class="Number">2</span>)
<span class="line-numbers"> 350 </span> setTimeout((<span class="Storage">-&gt;</span> func.apply(func, args)), wait)
<span class="line-numbers"> 351 </span>
<span class="line-numbers"> 352 </span>
<span class="line-numbers"> 351 </span>
<span class="line-numbers"> 352 </span>
<span class="line-numbers"> 353 </span> <span class="Comment"><span class="Comment">#</span> Defers a function, scheduling it to run after the current call stack has</span>
<span class="line-numbers"> 354 </span> <span class="Comment"><span class="Comment">#</span> cleared.</span>
<span class="line-numbers"> 355 </span> <span class="FunctionName">_.defer</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">func</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 356 </span> _.delay.apply(_, [func, <span class="Number">1</span>].concat(_.rest(arguments)))
<span class="line-numbers"> 357 </span>
<span class="line-numbers"> 358 </span>
<span class="line-numbers"> 357 </span>
<span class="line-numbers"> 358 </span>
<span class="line-numbers"> 359 </span> <span class="Comment"><span class="Comment">#</span> Returns the first function passed as an argument to the second,</span>
<span class="line-numbers"> 360 </span> <span class="Comment"><span class="Comment">#</span> allowing you to adjust arguments, run code before and after, and</span>
<span class="line-numbers"> 361 </span> <span class="Comment"><span class="Comment">#</span> conditionally execute the original function.</span>
<span class="line-numbers"> 362 </span> <span class="FunctionName">_.wrap</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">func, wrapper</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 363 </span> <span class="Storage">-&gt;</span> wrapper.apply(wrapper, [func].concat(arguments))
<span class="line-numbers"> 364 </span>
<span class="line-numbers"> 365 </span>
<span class="line-numbers"> 364 </span>
<span class="line-numbers"> 365 </span>
<span class="line-numbers"> 366 </span> <span class="Comment"><span class="Comment">#</span> Returns a function that is the composition of a list of functions, each</span>
<span class="line-numbers"> 367 </span> <span class="Comment"><span class="Comment">#</span> consuming the return value of the function that follows.</span>
<span class="line-numbers"> 368 </span> <span class="FunctionName">_.compose</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
@@ -391,46 +382,46 @@
<span class="line-numbers"> 372 </span> <span class="Keyword">for</span> i <span class="Keyword">in</span> [(funcs.length <span class="Keyword">-</span> <span class="Number">1</span>)..<span class="Number">0</span>]
<span class="line-numbers"> 373 </span> <span class="FunctionName">args</span><span class="Keyword">:</span> [funcs[i].apply(<span class="Variable">this</span>, args)]
<span class="line-numbers"> 374 </span> args[<span class="Number">0</span>]
<span class="line-numbers"> 375 </span>
<span class="line-numbers"> 376 </span>
<span class="line-numbers"> 375 </span>
<span class="line-numbers"> 376 </span>
<span class="line-numbers"> 377 </span> <span class="Comment"><span class="Comment">#</span> ------------------------- Object Functions: ----------------------------</span>
<span class="line-numbers"> 378 </span>
<span class="line-numbers"> 378 </span>
<span class="line-numbers"> 379 </span> <span class="Comment"><span class="Comment">#</span> Retrieve the names of an object's properties.</span>
<span class="line-numbers"> 380 </span> <span class="FunctionName">_.keys</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 381 </span> <span class="Keyword">return</span> _.range(<span class="Number">0</span>, obj.length) <span class="Keyword">if</span> _.isArray(obj)
<span class="line-numbers"> 382 </span> key <span class="Keyword">for</span> key, val <span class="Keyword">of</span> obj
<span class="line-numbers"> 383 </span>
<span class="line-numbers"> 384 </span>
<span class="line-numbers"> 383 </span>
<span class="line-numbers"> 384 </span>
<span class="line-numbers"> 385 </span> <span class="Comment"><span class="Comment">#</span> Retrieve the values of an object's properties.</span>
<span class="line-numbers"> 386 </span> <span class="FunctionName">_.values</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 387 </span> _.map(obj, _.identity)
<span class="line-numbers"> 388 </span>
<span class="line-numbers"> 389 </span>
<span class="line-numbers"> 388 </span>
<span class="line-numbers"> 389 </span>
<span class="line-numbers"> 390 </span> <span class="Comment"><span class="Comment">#</span> Return a sorted list of the function names available in Underscore.</span>
<span class="line-numbers"> 391 </span> <span class="FunctionName">_.functions</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 392 </span> _.select(_.keys(obj), <span class="FunctionArgument">(</span><span class="FunctionArgument">key</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> _.isFunction(obj[key])).sort()
<span class="line-numbers"> 393 </span>
<span class="line-numbers"> 394 </span>
<span class="line-numbers"> 393 </span>
<span class="line-numbers"> 394 </span>
<span class="line-numbers"> 395 </span> <span class="Comment"><span class="Comment">#</span> Extend a given object with all of the properties in a source object.</span>
<span class="line-numbers"> 396 </span> <span class="FunctionName">_.extend</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">destination, source</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 397 </span> <span class="Keyword">for</span> key, val <span class="Keyword">of</span> source
<span class="line-numbers"> 398 </span> destination[key]<span class="Keyword">:</span> val
<span class="line-numbers"> 399 </span> destination
<span class="line-numbers"> 400 </span>
<span class="line-numbers"> 401 </span>
<span class="line-numbers"> 400 </span>
<span class="line-numbers"> 401 </span>
<span class="line-numbers"> 402 </span> <span class="Comment"><span class="Comment">#</span> Create a (shallow-cloned) duplicate of an object.</span>
<span class="line-numbers"> 403 </span> <span class="FunctionName">_.clone</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 404 </span> <span class="Keyword">return</span> obj.slice(<span class="Number">0</span>) <span class="Keyword">if</span> _.isArray(obj)
<span class="line-numbers"> 405 </span> _.extend({}, obj)
<span class="line-numbers"> 406 </span>
<span class="line-numbers"> 407 </span>
<span class="line-numbers"> 406 </span>
<span class="line-numbers"> 407 </span>
<span class="line-numbers"> 408 </span> <span class="Comment"><span class="Comment">#</span> Invokes interceptor with the obj, and then returns obj.</span>
<span class="line-numbers"> 409 </span> <span class="Comment"><span class="Comment">#</span> The primary purpose of this method is to &quot;tap into&quot; a method chain, in order to perform operations on intermediate results within the chain.</span>
<span class="line-numbers"> 410 </span> <span class="FunctionName">_.tap</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, interceptor</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 411 </span> interceptor(obj)
<span class="line-numbers"> 412 </span> obj
<span class="line-numbers"> 413 </span>
<span class="line-numbers"> 414 </span>
<span class="line-numbers"> 413 </span>
<span class="line-numbers"> 414 </span>
<span class="line-numbers"> 415 </span> <span class="Comment"><span class="Comment">#</span> Perform a deep comparison to check if two objects are equal.</span>
<span class="line-numbers"> 416 </span> <span class="FunctionName">_.isEqual</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">a, b</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 417 </span> <span class="Comment"><span class="Comment">#</span> Check object identity.</span>
@@ -465,82 +456,82 @@
<span class="line-numbers"> 446 </span> <span class="Comment"><span class="Comment">#</span> Recursive comparison of contents.</span>
<span class="line-numbers"> 447 </span> <span class="Comment"><span class="Comment">#</span> for (var key in a) if (!_.isEqual(a[key], b[key])) return false;</span>
<span class="line-numbers"> 448 </span> <span class="Keyword">return</span> <span class="BuiltInConstant">true</span>
<span class="line-numbers"> 449 </span>
<span class="line-numbers"> 450 </span>
<span class="line-numbers"> 449 </span>
<span class="line-numbers"> 450 </span>
<span class="line-numbers"> 451 </span> <span class="Comment"><span class="Comment">#</span> Is a given array or object empty?</span>
<span class="line-numbers"> 452 </span> <span class="FunctionName">_.isEmpty</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> _.keys(obj).length <span class="Keyword">is</span> <span class="Number">0</span>
<span class="line-numbers"> 453 </span>
<span class="line-numbers"> 454 </span>
<span class="line-numbers"> 453 </span>
<span class="line-numbers"> 454 </span>
<span class="line-numbers"> 455 </span> <span class="Comment"><span class="Comment">#</span> Is a given value a DOM element?</span>
<span class="line-numbers"> 456 </span> <span class="FunctionName">_.isElement</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> obj <span class="Keyword">and</span> obj.nodeType <span class="Keyword">is</span> <span class="Number">1</span>
<span class="line-numbers"> 457 </span>
<span class="line-numbers"> 458 </span>
<span class="line-numbers"> 457 </span>
<span class="line-numbers"> 458 </span>
<span class="line-numbers"> 459 </span> <span class="Comment"><span class="Comment">#</span> Is a given value an array?</span>
<span class="line-numbers"> 460 </span> <span class="FunctionName">_.isArray</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.concat <span class="Keyword">and</span> obj.unshift)
<span class="line-numbers"> 461 </span>
<span class="line-numbers"> 462 </span>
<span class="line-numbers"> 461 </span>
<span class="line-numbers"> 462 </span>
<span class="line-numbers"> 463 </span> <span class="Comment"><span class="Comment">#</span> Is a given variable an arguments object?</span>
<span class="line-numbers"> 464 </span> <span class="FunctionName">_.isArguments</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> obj <span class="Keyword">and</span> _.isNumber(obj.length) <span class="Keyword">and</span> <span class="Keyword">not</span> obj.concat <span class="Keyword">and</span>
<span class="line-numbers"> 465 </span> <span class="Keyword">not</span> obj.substr <span class="Keyword">and</span> <span class="Keyword">not</span> obj.apply <span class="Keyword">and</span> <span class="Keyword">not</span> propertyIsEnumerable.call(obj, <span class="String"><span class="String">'</span>length<span class="String">'</span></span>)
<span class="line-numbers"> 466 </span>
<span class="line-numbers"> 467 </span>
<span class="line-numbers"> 466 </span>
<span class="line-numbers"> 467 </span>
<span class="line-numbers"> 468 </span> <span class="Comment"><span class="Comment">#</span> Is the given value a function?</span>
<span class="line-numbers"> 469 </span> <span class="FunctionName">_.isFunction</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.constructor <span class="Keyword">and</span> obj.call <span class="Keyword">and</span> obj.apply)
<span class="line-numbers"> 470 </span>
<span class="line-numbers"> 471 </span>
<span class="line-numbers"> 470 </span>
<span class="line-numbers"> 471 </span>
<span class="line-numbers"> 472 </span> <span class="Comment"><span class="Comment">#</span> Is the given value a string?</span>
<span class="line-numbers"> 473 </span> <span class="FunctionName">_.isString</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">is</span> <span class="String"><span class="String">'</span><span class="String">'</span></span> <span class="Keyword">or</span> (obj <span class="Keyword">and</span> obj.charCodeAt <span class="Keyword">and</span> obj.substr))
<span class="line-numbers"> 474 </span>
<span class="line-numbers"> 475 </span>
<span class="line-numbers"> 474 </span>
<span class="line-numbers"> 475 </span>
<span class="line-numbers"> 476 </span> <span class="Comment"><span class="Comment">#</span> Is a given value a number?</span>
<span class="line-numbers"> 477 </span> <span class="FunctionName">_.isNumber</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> (obj <span class="Keyword">is</span> <span class="Keyword">+</span>obj) <span class="Keyword">or</span> toString.call(obj) <span class="Keyword">is</span> <span class="String"><span class="String">'</span>[object Number]<span class="String">'</span></span>
<span class="line-numbers"> 478 </span>
<span class="line-numbers"> 479 </span>
<span class="line-numbers"> 478 </span>
<span class="line-numbers"> 479 </span>
<span class="line-numbers"> 480 </span> <span class="Comment"><span class="Comment">#</span> Is a given value a Date?</span>
<span class="line-numbers"> 481 </span> <span class="FunctionName">_.isDate</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.getTimezoneOffset <span class="Keyword">and</span> obj.setUTCFullYear)
<span class="line-numbers"> 482 </span>
<span class="line-numbers"> 483 </span>
<span class="line-numbers"> 482 </span>
<span class="line-numbers"> 483 </span>
<span class="line-numbers"> 484 </span> <span class="Comment"><span class="Comment">#</span> Is the given value a regular expression?</span>
<span class="line-numbers"> 485 </span> <span class="FunctionName">_.isRegExp</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">!</span><span class="Keyword">!</span>(obj <span class="Keyword">and</span> obj.exec <span class="Keyword">and</span> (obj.ignoreCase <span class="Keyword">or</span> obj.ignoreCase <span class="Keyword">is</span> <span class="BuiltInConstant">false</span>))
<span class="line-numbers"> 486 </span>
<span class="line-numbers"> 487 </span>
<span class="line-numbers"> 486 </span>
<span class="line-numbers"> 487 </span>
<span class="line-numbers"> 488 </span> <span class="Comment"><span class="Comment">#</span> Is the given value NaN -- this one is interesting. NaN != NaN, and</span>
<span class="line-numbers"> 489 </span> <span class="Comment"><span class="Comment">#</span> isNaN(undefined) == true, so we make sure it's a number first.</span>
<span class="line-numbers"> 490 </span> <span class="FunctionName">_.isNaN</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> _.isNumber(obj) <span class="Keyword">and</span> window.isNaN(obj)
<span class="line-numbers"> 491 </span>
<span class="line-numbers"> 492 </span>
<span class="line-numbers"> 491 </span>
<span class="line-numbers"> 492 </span>
<span class="line-numbers"> 493 </span> <span class="Comment"><span class="Comment">#</span> Is a given value equal to null?</span>
<span class="line-numbers"> 494 </span> <span class="FunctionName">_.isNull</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> obj <span class="Keyword">is</span> <span class="BuiltInConstant">null</span>
<span class="line-numbers"> 495 </span>
<span class="line-numbers"> 496 </span>
<span class="line-numbers"> 495 </span>
<span class="line-numbers"> 496 </span>
<span class="line-numbers"> 497 </span> <span class="Comment"><span class="Comment">#</span> Is a given variable undefined?</span>
<span class="line-numbers"> 498 </span> <span class="FunctionName">_.isUndefined</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> <span class="Keyword">typeof</span> obj <span class="Keyword">is</span> <span class="String"><span class="String">'</span>undefined<span class="String">'</span></span>
<span class="line-numbers"> 499 </span>
<span class="line-numbers"> 500 </span>
<span class="line-numbers"> 499 </span>
<span class="line-numbers"> 500 </span>
<span class="line-numbers"> 501 </span> <span class="Comment"><span class="Comment">#</span> -------------------------- Utility Functions: --------------------------</span>
<span class="line-numbers"> 502 </span>
<span class="line-numbers"> 502 </span>
<span class="line-numbers"> 503 </span> <span class="Comment"><span class="Comment">#</span> Run Underscore.js in noConflict mode, returning the '_' variable to its</span>
<span class="line-numbers"> 504 </span> <span class="Comment"><span class="Comment">#</span> previous owner. Returns a reference to the Underscore object.</span>
<span class="line-numbers"> 505 </span> <span class="FunctionName">_.noConflict</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 506 </span> <span class="FunctionName">root._</span><span class="Keyword">:</span> previousUnderscore
<span class="line-numbers"> 507 </span> <span class="Variable">this</span>
<span class="line-numbers"> 508 </span>
<span class="line-numbers"> 509 </span>
<span class="line-numbers"> 508 </span>
<span class="line-numbers"> 509 </span>
<span class="line-numbers"> 510 </span> <span class="Comment"><span class="Comment">#</span> Keep the identity function around for default iterators.</span>
<span class="line-numbers"> 511 </span> <span class="FunctionName">_.identity</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">value</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span> value
<span class="line-numbers"> 512 </span>
<span class="line-numbers"> 513 </span>
<span class="line-numbers"> 512 </span>
<span class="line-numbers"> 513 </span>
<span class="line-numbers"> 514 </span> <span class="Comment"><span class="Comment">#</span> Break out of the middle of an iteration.</span>
<span class="line-numbers"> 515 </span> <span class="FunctionName">_.breakLoop</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span> <span class="Keyword">throw</span> breaker
<span class="line-numbers"> 516 </span>
<span class="line-numbers"> 517 </span>
<span class="line-numbers"> 516 </span>
<span class="line-numbers"> 517 </span>
<span class="line-numbers"> 518 </span> <span class="Comment"><span class="Comment">#</span> Generate a unique integer id (unique within the entire client session).</span>
<span class="line-numbers"> 519 </span> <span class="Comment"><span class="Comment">#</span> Useful for temporary DOM ids.</span>
<span class="line-numbers"> 520 </span> <span class="FunctionName">idCounter</span><span class="Keyword">:</span> <span class="Number">0</span>
<span class="line-numbers"> 521 </span> <span class="FunctionName">_.uniqueId</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">prefix</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 522 </span> (prefix <span class="Keyword">or</span> <span class="String"><span class="String">'</span><span class="String">'</span></span>) <span class="Keyword">+</span> idCounter<span class="Keyword">++</span>
<span class="line-numbers"> 523 </span>
<span class="line-numbers"> 524 </span>
<span class="line-numbers"> 523 </span>
<span class="line-numbers"> 524 </span>
<span class="line-numbers"> 525 </span> <span class="Comment"><span class="Comment">#</span> By default, Underscore uses ERB-style template delimiters, change the</span>
<span class="line-numbers"> 526 </span> <span class="Comment"><span class="Comment">#</span> following template settings to use alternative delimiters.</span>
<span class="line-numbers"> 527 </span> <span class="FunctionName">_.templateSettings</span><span class="Keyword">:</span> {
@@ -548,11 +539,11 @@
<span class="line-numbers"> 529 </span> <span class="FunctionName">end</span><span class="Keyword">:</span> <span class="String"><span class="String">'</span>%&gt;<span class="String">'</span></span>
<span class="line-numbers"> 530 </span> <span class="FunctionName">interpolate</span><span class="Keyword">:</span><span class="String"> <span class="String">/</span>&lt;%=(.+?)%&gt;<span class="String">/</span>g</span>
<span class="line-numbers"> 531 </span> }
<span class="line-numbers"> 532 </span>
<span class="line-numbers"> 533 </span>
<span class="line-numbers"> 532 </span>
<span class="line-numbers"> 533 </span>
<span class="line-numbers"> 534 </span> <span class="Comment"><span class="Comment">#</span> JavaScript templating a-la ERB, pilfered from John Resig's</span>
<span class="line-numbers"> 535 </span> <span class="Comment"><span class="Comment">#</span> &quot;Secrets of the JavaScript Ninja&quot;, page 83.</span>
<span class="line-numbers"> 536 </span> <span class="Comment"><span class="Comment">#</span> Single-quotea fix from Rick Strahl's version.</span>
<span class="line-numbers"> 536 </span> <span class="Comment"><span class="Comment">#</span> Single-quote fix from Rick Strahl's version.</span>
<span class="line-numbers"> 537 </span> <span class="FunctionName">_.template</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">str, data</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 538 </span> <span class="FunctionName">c</span><span class="Keyword">:</span> _.templateSettings
<span class="line-numbers"> 539 </span> <span class="FunctionName">fn</span><span class="Keyword">:</span> <span class="Keyword">new</span> <span class="TypeName">Function</span> <span class="String"><span class="String">'</span>obj<span class="String">'</span></span>,
@@ -567,10 +558,10 @@
<span class="line-numbers"> 548 </span> .split(c.end).join(<span class="String"><span class="String">&quot;</span>p.push('<span class="String">&quot;</span></span>) <span class="Keyword">+</span>
<span class="line-numbers"> 549 </span> <span class="String"><span class="String">&quot;</span>');}return p.join('');<span class="String">&quot;</span></span>
<span class="line-numbers"> 550 </span> <span class="Keyword">if</span> data <span class="Keyword">then</span> fn(data) <span class="Keyword">else</span> fn
<span class="line-numbers"> 551 </span>
<span class="line-numbers"> 552 </span>
<span class="line-numbers"> 551 </span>
<span class="line-numbers"> 552 </span>
<span class="line-numbers"> 553 </span> <span class="Comment"><span class="Comment">#</span> ------------------------------- Aliases ----------------------------------</span>
<span class="line-numbers"> 554 </span>
<span class="line-numbers"> 554 </span>
<span class="line-numbers"> 555 </span> <span class="FunctionName">_.forEach</span><span class="Keyword">:</span> _.each
<span class="line-numbers"> 556 </span> <span class="FunctionName">_.foldl</span><span class="Keyword">:</span> <span class="FunctionName">_.inject</span><span class="Keyword">:</span> _.reduce
<span class="line-numbers"> 557 </span> <span class="FunctionName">_.foldr</span><span class="Keyword">:</span> _.reduceRight
@@ -580,44 +571,44 @@
<span class="line-numbers"> 561 </span> <span class="FunctionName">_.head</span><span class="Keyword">:</span> _.first
<span class="line-numbers"> 562 </span> <span class="FunctionName">_.tail</span><span class="Keyword">:</span> _.rest
<span class="line-numbers"> 563 </span> <span class="FunctionName">_.methods</span><span class="Keyword">:</span> _.functions
<span class="line-numbers"> 564 </span>
<span class="line-numbers"> 565 </span>
<span class="line-numbers"> 566 </span> <span class="Comment"><span class="Comment">#</span> /*------------------------ Setup the OOP Wrapper: --------------------------*/</span>
<span class="line-numbers"> 567 </span>
<span class="line-numbers"> 564 </span>
<span class="line-numbers"> 565 </span>
<span class="line-numbers"> 566 </span> <span class="Comment"><span class="Comment">#</span> ------------------------ Setup the OOP Wrapper: --------------------------</span>
<span class="line-numbers"> 567 </span>
<span class="line-numbers"> 568 </span> <span class="Comment"><span class="Comment">#</span> Helper function to continue chaining intermediate results.</span>
<span class="line-numbers"> 569 </span> <span class="FunctionName">result</span><span class="Keyword">:</span> <span class="FunctionArgument">(</span><span class="FunctionArgument">obj, chain</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 570 </span> <span class="Keyword">if</span> chain <span class="Keyword">then</span> _(obj).chain() <span class="Keyword">else</span> obj
<span class="line-numbers"> 571 </span>
<span class="line-numbers"> 572 </span>
<span class="line-numbers"> 571 </span>
<span class="line-numbers"> 572 </span>
<span class="line-numbers"> 573 </span> <span class="Comment"><span class="Comment">#</span> Add all of the Underscore functions to the wrapper object.</span>
<span class="line-numbers"> 574 </span> _.each _.functions(_), <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 575 </span> <span class="FunctionName">method</span><span class="Keyword">:</span> _[name]
<span class="line-numbers"> 576 </span> wrapper.prototype[name]<span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 577 </span> unshift.call(arguments, <span class="Variable">this</span>._wrapped)
<span class="line-numbers"> 578 </span> result(method.apply(_, arguments), <span class="Variable">this</span>._chain)
<span class="line-numbers"> 579 </span>
<span class="line-numbers"> 580 </span>
<span class="line-numbers"> 579 </span>
<span class="line-numbers"> 580 </span>
<span class="line-numbers"> 581 </span> <span class="Comment"><span class="Comment">#</span> Add all mutator Array functions to the wrapper.</span>
<span class="line-numbers"> 582 </span> _.each [<span class="String"><span class="String">'</span>pop<span class="String">'</span></span>, <span class="String"><span class="String">'</span>push<span class="String">'</span></span>, <span class="String"><span class="String">'</span>reverse<span class="String">'</span></span>, <span class="String"><span class="String">'</span>shift<span class="String">'</span></span>, <span class="String"><span class="String">'</span>sort<span class="String">'</span></span>, <span class="String"><span class="String">'</span>splice<span class="String">'</span></span>, <span class="String"><span class="String">'</span>unshift<span class="String">'</span></span>], <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 583 </span> <span class="FunctionName">method</span><span class="Keyword">:</span> Array.prototype[name]
<span class="line-numbers"> 584 </span> wrapper.prototype[name]<span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 585 </span> method.apply(<span class="Variable">this</span>._wrapped, arguments)
<span class="line-numbers"> 586 </span> result(<span class="Variable">this</span>._wrapped, <span class="Variable">this</span>._chain)
<span class="line-numbers"> 587 </span>
<span class="line-numbers"> 588 </span>
<span class="line-numbers"> 587 </span>
<span class="line-numbers"> 588 </span>
<span class="line-numbers"> 589 </span> <span class="Comment"><span class="Comment">#</span> Add all accessor Array functions to the wrapper.</span>
<span class="line-numbers"> 590 </span> _.each [<span class="String"><span class="String">'</span>concat<span class="String">'</span></span>, <span class="String"><span class="String">'</span>join<span class="String">'</span></span>, <span class="String"><span class="String">'</span>slice<span class="String">'</span></span>], <span class="FunctionArgument">(</span><span class="FunctionArgument">name</span><span class="FunctionArgument">)</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 591 </span> <span class="FunctionName">method</span><span class="Keyword">:</span> Array.prototype[name]
<span class="line-numbers"> 592 </span> wrapper.prototype[name]<span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 593 </span> result(method.apply(<span class="Variable">this</span>._wrapped, arguments), <span class="Variable">this</span>._chain)
<span class="line-numbers"> 594 </span>
<span class="line-numbers"> 595 </span>
<span class="line-numbers"> 594 </span>
<span class="line-numbers"> 595 </span>
<span class="line-numbers"> 596 </span> <span class="Comment"><span class="Comment">#</span> Start chaining a wrapped Underscore object.</span>
<span class="line-numbers"> 597 </span> <span class="FunctionName">wrapper::chain</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span>
<span class="line-numbers"> 598 </span> <span class="FunctionName">this._chain</span><span class="Keyword">:</span> <span class="BuiltInConstant">true</span>
<span class="line-numbers"> 599 </span> <span class="Variable">this</span>
<span class="line-numbers"> 600 </span>
<span class="line-numbers"> 601 </span>
<span class="line-numbers"> 600 </span>
<span class="line-numbers"> 601 </span>
<span class="line-numbers"> 602 </span> <span class="Comment"><span class="Comment">#</span> Extracts the result from a wrapped and chained object.</span>
<span class="line-numbers"> 603 </span> <span class="FunctionName">wrapper::value</span><span class="Keyword">:</span> <span class="Storage">-&gt;</span> <span class="Variable">this</span>._wrapped
</pre> <p>
@@ -628,7 +619,7 @@
</a>
<a href="http://jigsaw.w3.org/css-validator/check?uri=referer">
<img style="border:0;width:88px;height:31px"
src="http://jigsaw.w3.org/css-validator/images/vcss"
src="http://jigsaw.w3.org/css-validator/images/vcss"
alt="Valid CSS!" />
</a>
</p>

View File

@@ -8,7 +8,7 @@ get '/hello', ->
# Append.
append: (location, data) ->
path: new Pathname location
throw "Location does not exist" unless path.exists()
throw new Error("Location does not exist") unless path.exists()
File.open path, 'a', (file) ->
file.puts YAML.dump data

View File

@@ -47,7 +47,7 @@
# Current version.
_.VERSION: '0.5.7'
_.VERSION: '0.5.8'
# ------------------------ Collection Functions: ---------------------------
@@ -58,7 +58,7 @@
index: 0
try
return obj.forEach(iterator, context) if obj.forEach
if _.isArray(obj) or _.isArguments(obj)
if _.isNumber(obj.length)
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
catch e
@@ -147,7 +147,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 obj and _.isFunction(obj.indexOf)
for key, val of obj
return true if val is target
false
@@ -240,7 +240,7 @@
# 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.
@@ -312,8 +312,8 @@
_.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
@@ -533,7 +533,7 @@
# 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
fn: new Function 'obj',
@@ -563,7 +563,7 @@
_.methods: _.functions
# /*------------------------ Setup the OOP Wrapper: --------------------------*/
# ------------------------ Setup the OOP Wrapper: --------------------------
# Helper function to continue chaining intermediate results.
result: (obj, chain) ->

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.sendBody 'Hello, World!'
res.finish()
server.listen 3000
puts "Server running at http://localhost:3000/"

View File

@@ -74,7 +74,7 @@
</dict>
<dict>
<key>match</key>
<string>(@)[a-zA-Z_$]\w*</string>
<string>(@)([a-zA-Z_$]\w*)?</string>
<key>name</key>
<string>variable.other.readwrite.instance.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

7
lib/bin/cake Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
process.mixin(require('sys'));
require.paths.unshift('/usr/local/lib/coffee-script/lib');
require('cake').run();

7
lib/bin/coffee Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
process.mixin(require('sys'));
require.paths.unshift('/usr/local/lib/coffee-script/lib');
require('command_line').run();

80
lib/cake.js Executable file
View File

@@ -0,0 +1,80 @@
(function(){
var coffee, fs, no_such_task, path, print_tasks, tasks;
var __hasProp = Object.prototype.hasOwnProperty;
// `cake` is a simplified version of Make (Rake, Jake) for CoffeeScript.
fs = require('fs');
path = require('path');
coffee = require('coffee-script');
tasks = {};
no_such_task = function no_such_task(task) {
process.stdio.writeError('No such task: "' + task + '"\n');
return process.exit(1);
};
// Mixin the Cake functionality.
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
};
},
// Invoke another task in the Cakefile.
invoke: function invoke(name) {
if (!(tasks[name])) {
no_such_task(name);
}
return tasks[name].action();
}
});
// Display the list of Cake tasks.
print_tasks = function print_tasks() {
var _a, _b, _c, _d, _e, _f, _g, i, name, spaces, task;
_a = []; _b = tasks;
for (name in _b) { if (__hasProp.call(_b, name)) {
task = _b[name];
_a.push((function() {
spaces = 20 - name.length;
spaces = spaces > 0 ? (function() {
_c = []; _f = 0; _g = spaces;
for (_e = 0, i=_f; (_f <= _g ? i <= _g : i >= _g); (_f <= _g ? i += 1 : i -= 1), _e++) {
_c.push(' ');
}
return _c;
}).call(this).join('') : '';
return puts("cake " + name + spaces + ' # ' + task.description);
}).call(this));
}}
return _a;
};
// 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 args;
if (!(exists)) {
throw new Error('Cakefile not found in ' + process.cwd());
}
args = process.ARGV.slice(2, process.ARGV.length);
return fs.readFile('Cakefile', function(err, source) {
var _a, _b, _c, arg;
eval(coffee.compile(source));
if (!(args.length)) {
return print_tasks();
}
_a = []; _b = args;
for (_c = 0; _c < _b.length; _c++) {
arg = _b[_c];
_a.push((function() {
if (!(tasks[arg])) {
no_such_task(arg);
}
return tasks[arg].action();
}).call(this));
}
return _a;
});
});
};
})();

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

@@ -0,0 +1,57 @@
(function(){
var _a, _b, lexer, parser, path, tag;
// 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.2';
// 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.tokenize = function tokenize(code) {
return lexer.tokenize(code);
};
// Just the nodes.
exports.tree = function tree(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) {
_a = document.getElementsByTagName('script');
for (_b = 0; _b < _a.length; _b++) {
tag = _a[_b];
tag.type === 'text/coffeescript' ? eval(exports.compile(tag.innerHTML)) : null;
}
}
})();

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

View File

@@ -1,235 +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__) + '/../..')
# Commands to execute CoffeeScripts.
RUNNERS = {
:node => "node #{ROOT}/lib/coffee_script/runner.js",
:narwhal => "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
}
# Run the CommandLine off the contents of ARGV.
def initialize
@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 Node.js or Narwhal to run an interactive CoffeeScript session.
def launch_repl
exec "#{RUNNERS[@options[:runner]]}"
rescue Errno::ENOENT
puts "Error: #{@options[:runner]} must be installed to use the interactive REPL."
exit(1)
end
# Use Node.js or Narwhal to compile and execute CoffeeScripts.
def run_scripts
sources = @sources.join(' ')
exec "#{RUNNERS[@options[:runner]]} #{sources}"
rescue Errno::ENOENT
puts "Error: #{@options[:runner]} must be installed in order to execute scripts."
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 = {:runner => :node}
@option_parser = OptionParser.new do |opts|
opts.on('-i', '--interactive', 'run an interactive CoffeeScript REPL') do |i|
@options[:interactive] = true
end
opts.on('-r', '--run', 'compile and run a CoffeeScript') do |r|
@options[:run] = true
end
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
@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('--narwhal', 'use Narwhal instead of Node.js') do |n|
@options[:runner] = :narwhal
end
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
install_bundle
exit
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,481 +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 ASSIGN RETURN
token NEWLINE
token COMMENT
token JS
token INDENT OUTDENT
# Declare order of operations.
prechigh
nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
left '*' '/' '%' '?' '.'
left '+' '-'
left '<<' '>>' '>>>' '&' '|' '^'
left '<=' '<' '>' '>='
right '==' '!=' IS ISNT
left '&&' '||' AND OR
right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
right DELETE INSTANCEOF TYPEOF
right INDENT
left OUTDENT
right WHEN LEADING_WHEN IN OF BY
right THROW FOR NEW SUPER
left EXTENDS
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]) }
| 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]) }
| This { 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]) }
;
# This references, either naked or to a property.
This:
'@' { result = ThisNode.new }
| '@' IDENTIFIER { result = ThisNode.new(val[1]) }
;
# 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,272 +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"]
# 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.
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,96 +0,0 @@
(function(){
var File, OS, Readline, checkForErrors, coffeePath, factories, loader, puts;
// The Narwhal-compatibility wrapper for CoffeeScript.
// Require external dependencies.
OS = require('os');
File = require('file');
Readline = require('readline');
// The path to the CoffeeScript Compiler.
coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().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");
};
// Alias print to "puts", for Node.js compatibility:
puts = print;
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
// command.
exports.run = function run(args) {
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 + ")");
}
};
// The Narwhal loader for '.coffee' files.
factories = {
};
loader = {
};
// Reload the coffee-script environment from source.
loader.reload = function reload(topId, path) {
return factories[topId] = function() {
return exports.makeNarwhalFactory(path);
};
};
// Ensure that the coffee-script environment is loaded.
loader.load = function load(topId, path) {
return factories[topId] = factories[topId] || this.reload(topId, path);
};
require.loader.loaders.unshift([".coffee", loader]);
})();

View File

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

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

View File

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

File diff suppressed because it is too large Load Diff

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, :TYPEOF, :SWITCH,
:TRUE, :FALSE, :YES, :NO, :ON, :OFF, '!', '!!', :NOT,
'@', '->', '=>', '[', '(', '{']
# The inverse mappings of token pairs we're trying to fix up.
INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair|
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
# Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them.
def add_implicit_parentheses
stack = [0]
scan_tokens do |prev, token, post, i|
stack.push(0) if token[0] == :INDENT
if token[0] == :OUTDENT
last = stack.pop
stack[-1] += last
end
if stack.last > 0 && (IMPLICIT_END.include?(token[0]) || post.nil?)
idx = token[0] == :OUTDENT ? i + 1 : i
stack.last.times { @tokens.insert(idx, [:CALL_END, Value.new(')', token[1].line)]) }
size, stack[-1] = stack[-1] + 1, 0
next size
end
next 1 unless IMPLICIT_FUNC.include?(prev[0]) && IMPLICIT_CALL.include?(token[0])
@tokens.insert(i, [:CALL_START, Value.new('(', token[1].line)])
stack[-1] += 1
next 2
end
end
# Because our grammar is LALR(1), it can't handle some single-line
# expressions that lack ending delimiters. Use the lexer to add the implicit
# blocks, so it doesn't need to.
# ')' 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
# 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,11 +0,0 @@
(function(){
var coffee, paths;
// Quickie script to compile and run all the files given as arguments.
process.mixin(require('sys'));
coffee = require('./coffee-script');
paths = process.ARGV;
paths = paths.slice(2, paths.length);
paths.length ? coffee.compile_files(paths, function(js) {
return eval(js);
}) : require('./repl');
})();

View File

@@ -1,73 +0,0 @@
(function(){
var dup;
var __hasProp = Object.prototype.hasOwnProperty;
dup = function dup(input) {
var __a, __b, __c, key, output, val;
output = null;
if (input instanceof Array) {
output = [];
__a = input;
for (__b = 0; __b < __a.length; __b++) {
val = __a[__b];
output.push(val);
}
} else {
output = {
};
__c = input;
for (key in __c) {
val = __c[key];
if (__hasProp.call(__c, key)) {
output.key = val;
}
}
output;
}
return output;
};
// scope objects form a tree corresponding to the shape of the function
// definitions present in the script. They provide lexical scope, to determine
// whether a variable has been seen before or if it needs to be declared.
exports.Scope = function Scope(parent, expressions, func) {
var __a;
// Initialize a scope with its parent, for lookups up the chain,
// as well as the Expressions body where it should declare its variables,
// and the function that it wraps.
this.parent = parent;
this.expressions = expressions;
this.function = func;
this.variables = {
};
__a = this.temp_variable = this.parent ? dup(this.parent.temp_variable) : '__a';
return Scope === this.constructor ? this : __a;
};
// Look up a variable in lexical scope, or declare it if not found.
exports.Scope.prototype.find = function find(name, rem) {
var found, remote;
remote = (typeof rem !== "undefined" && rem !== null) ? rem : false;
found = this.check(name);
if (found || remote) {
return found;
}
this.variables[name] = 'var';
return found;
};
// Define a local variable as originating from a parameter in current scope
// -- no var required.
exports.Scope.prototype.parameter = function parameter(name) {
return this.variables[name] = 'param';
};
// Just check to see if a variable has already been declared.
exports.Scope.prototype.check = function check(name) {
if ((typeof this.variables[name] !== "undefined" && this.variables[name] !== null)) {
return true;
}
// TODO: what does that ruby !! mean..? need to follow up
// .. this next line is prolly wrong ..
return !!(this.parent && this.parent.check(name));
};
// You can reset a found variable on the immediate scope.
exports.Scope.prototype.reset = function reset(name) {
return this.variables[name] = undefined;
};
})();

View File

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

191
lib/command_line.js Normal file
View File

@@ -0,0 +1,191 @@
(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 = [['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-r', '--run', 'compile and run a CoffeeScript'], ['-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'], ['-n', '--no-wrap', 'compile without the top-level function wrapper'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-tr', '--tree', '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, compile, source;
compile = function compile(source) {
return fs.readFile(source, function(err, code) {
return compile_script(source, code);
});
};
_a = []; _b = sources;
for (_c = 0; _c < _b.length; _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 js, o;
o = options;
try {
if (o.tokens) {
return print_tokens(CoffeeScript.tokenize(code));
} else if (o.tree) {
return puts(CoffeeScript.tree(code).toString());
} else {
js = CoffeeScript.compile(code, compile_options());
if (o.run) {
return eval(js);
} else if (o.lint) {
return lint(js);
} else if (o.print || o.eval) {
return print(js);
} else {
return write_js(source, 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, source, watch;
watch = function watch(source) {
return process.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; _c < _b.length; _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, strings, token;
strings = (function() {
_a = []; _b = tokens;
for (_c = 0; _c < _b.length; _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
} : {};
};
})();

564
lib/grammar.js Normal file
View File

@@ -0,0 +1,564 @@
(function(){
var Parser, _a, _b, _c, _d, _e, _f, 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'], ["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("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);
})
],
// 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]);
}), 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; _d < _c.length; _d++) {
option = _c[_d];
_b.push((function() {
_e = option[0].split(" ");
for (_f = 0; _f < _e.length; _f++) {
part = _e[_f];
!grammar[part] ? tokens.push(part) : null;
}
name === "Root" ? (option[1] = "return " + option[1]) : null;
return option;
}).call(this));
}
return _b;
}).call(this);
}}
tokens = tokens.join(" ");
exports.parser = new Parser({
tokens: tokens,
bnf: bnf,
operators: operators.reverse(),
startSymbol: 'Root'
}, {
debug: false
});
})();

View File

@@ -1,14 +1,28 @@
(function(){
var ASSIGNMENT, CALLABLE, CODE, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex;
Rewriter = require('./rewriter').Rewriter;
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, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex;
if ((typeof process !== "undefined" && process !== null)) {
Rewriter = require('./rewriter').Rewriter;
} else {
this.exports = this;
Rewriter = this.Rewriter;
}
// The lexer reads a stream of CoffeeScript and divvys it up into tagged
// tokens. A minor bit of the ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (lex = function lex() { });
// Constants ============================================================
// Keywords that CoffeScript shares in common with JS.
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends"];
// CoffeeScript-only keywords -- which we're more relaxed about allowing.
COFFEE_KEYWORDS = ["then", "unless", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "of", "by", "where", "when"];
// The list of keywords passed verbatim to the parser.
KEYWORDS = ["if", "else", "then", "unless", "true", "false", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "new", "return", "arguments", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "of", "by", "where", "while", "delete", "instanceof", "typeof", "switch", "when", "super", "extends"];
// Token matching regexes.
KEYWORDS = JS_KEYWORDS.concat(COFFEE_KEYWORDS);
// The list of keywords that are reserved by JavaScript, but not used, and aren't
// used by CoffeeScript. Using these will throw an error at compile time.
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "class", "const", "let", "debugger", "enum", "export", "import", "native"];
// JavaScript keywords and reserved words together, excluding CoffeeScript ones.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
// Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
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]*?)([^\\]|\\\\)')/;
@@ -16,7 +30,7 @@
JS = /^(``|`([\s\S]*?)([^\\]|\\\\)`)/;
OPERATOR = /^([+\*&|\/\-%=<>:!?]+)/;
WHITESPACE = /^([ \t]+)/;
COMMENT = /^(((\n?[ \t]*)?#.*$)+)/;
COMMENT = /^(((\n?[ \t]*)?#[^\n]*)+)/;
CODE = /^((-|=)>)/;
REGEX = /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/;
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
@@ -29,13 +43,18 @@
STRING_NEWLINES = /\n[ \t]*/g;
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/mg;
NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/;
HEREDOC_INDENT = /^[ \t]+/g;
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
NOT_REGEX = ['IDENTIFIER', 'NUMBER', 'REGEX', 'STRING', ')', '++', '--', ']', '}', 'FALSE', 'NULL', 'TRUE'];
// Tokens which could legitimately be invoked or indexed.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING'];
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 its leading.
BEFORE_WHEN = ['INDENT', 'OUTDENT', 'TERMINATOR'];
// Scan by attempting to match tokens one character at a time. Slow and steady.
lex.prototype.tokenize = function tokenize(code) {
this.code = code;
@@ -50,8 +69,6 @@
// The stack of all indent levels we are currently within.
this.tokens = [];
// Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
this.spaced = null;
// The last token that has a space following it.
while (this.i < this.code.length) {
this.chunk = this.code.slice(this.i);
this.extract_next_token();
@@ -98,23 +115,27 @@
if (!((id = this.match(IDENTIFIER, 1)))) {
return false;
}
// Keywords are special identifiers tagged with their own name,
// 'if' will result in an ['IF', "if"] token.
tag = KEYWORDS.indexOf(id) >= 0 ? id.toUpperCase() : 'IDENTIFIER';
if (tag === 'WHEN' && (this.tag() === 'OUTDENT' || this.tag() === 'INDENT')) {
tag = 'LEADING_WHEN';
if (this.value() === '::') {
this.tag(1, 'PROTOTYPE_ACCESS');
}
if (tag === 'IDENTIFIER' && this.value() === '::') {
this.tag(-1, 'PROTOTYPE_ACCESS');
}
if (tag === 'IDENTIFIER' && this.value() === '.' && !(this.value(-2) === '.')) {
if (this.tag(-2) === '?') {
this.tag(-1, 'SOAK_ACCESS');
if (this.value() === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
this.tokens.splice(-2, 1);
} else {
this.tag(-1, 'PROPERTY_ACCESS');
this.tag(1, 'PROPERTY_ACCESS');
}
}
tag = 'IDENTIFIER';
if (KEYWORDS.indexOf(id) >= 0 && !((ACCESSORS.indexOf(this.tag()) >= 0) && !this.prev().spaced)) {
tag = id.toUpperCase();
}
if (RESERVED.indexOf(id) >= 0) {
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + this.line);
}
if (tag === 'WHEN' && BEFORE_WHEN.indexOf(this.tag()) >= 0) {
tag = 'LEADING_WHEN';
}
this.token(tag, id);
this.i += id.length;
return true;
@@ -148,8 +169,8 @@
return false;
}
doc = match[2] || match[4];
indent = doc.match(HEREDOC_INDENT).sort()[0];
doc = doc.replace(new RegExp("^" + indent, 'g'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
doc = doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
this.token('STRING', '"' + doc + '"');
this.line += this.count(match[1], "\n");
this.i += match[1].length;
@@ -184,7 +205,7 @@
if (!((comment = this.match(COMMENT, 1)))) {
return false;
}
this.line += comment.match(MULTILINER).length;
this.line += (comment.match(MULTILINER) || []).length;
this.token('COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
this.token('TERMINATOR', "\n");
this.i += comment.length;
@@ -192,14 +213,15 @@
};
// Record tokens for indentation differing from the previous line.
lex.prototype.indent_token = function indent_token() {
var diff, indent, next_character, no_newlines, size;
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;
next_character = this.chunk.match(MULTI_DENT)[4];
no_newlines = next_character === '.' || (this.value().match(NO_NEWLINE) && this.tokens[this.tokens.length - 2][0] !== '.' && !this.value().match(CODE));
prev = this.prev(2);
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
if (no_newlines) {
return this.suppress_newlines(indent);
}
@@ -226,23 +248,28 @@
this.token('OUTDENT', last_indent);
move_out -= last_indent;
}
this.token('TERMINATOR', "\n");
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Matches and consumes non-meaningful whitespace.
lex.prototype.whitespace_token = function whitespace_token() {
var space;
var prev, space;
if (!((space = this.match(WHITESPACE, 1)))) {
return false;
}
this.spaced = this.value();
prev = this.prev();
if (prev) {
prev.spaced = true;
}
this.i += space.length;
return true;
};
// Multiple newlines get merged together.
// Use a trailing \ to escape newlines.
lex.prototype.newline_token = function newline_token(newlines) {
if (!(this.value() === "\n")) {
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
@@ -258,18 +285,30 @@
// Multi-character operators are also literal tokens, so that Racc can assign
// the proper order of operations.
lex.prototype.literal_token = function literal_token() {
var match, tag, value;
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);
tag = value.match(ASSIGNMENT) ? 'ASSIGN' : value;
if (value === ';') {
not_spaced = !this.prev() || !this.prev().spaced;
tag = value;
if (value.match(ASSIGNMENT)) {
tag = 'ASSIGN';
if (JS_FORBIDDEN.indexOf(this.value()) >= 0) {
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
}
} else if (value === ';') {
tag = 'TERMINATOR';
}
if (this.value() !== this.spaced && CALLABLE.indexOf(this.tag()) >= 0) {
} 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 (CALLABLE.indexOf(this.tag()) >= 0 && not_spaced) {
if (value === '(') {
tag = 'CALL_START';
}
@@ -284,13 +323,12 @@
// Helpers =============================================================
// Add a token to the results, taking note of the line number.
lex.prototype.token = function token(tag, value) {
return this.tokens.push([tag, value]);
// this.tokens.push([tag, Value.new(value, @line)])
return this.tokens.push([tag, value, this.line]);
};
// Look at a tag in the current token stream.
lex.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof tag !== "undefined" && tag !== null)) {
@@ -301,7 +339,7 @@
// Look at a value in the current token stream.
lex.prototype.value = function value(index, val) {
var tok;
if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
@@ -309,16 +347,20 @@
}
return tok[1];
};
// Look at a previous token.
lex.prototype.prev = function prev(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Count the occurences of a character in a string.
lex.prototype.count = function count(string, letter) {
var num, pos;
num = 0;
pos = string.indexOf(letter);
while (pos !== -1) {
count += 1;
num += 1;
pos = string.indexOf(letter, pos + 1);
}
return count;
return num;
};
// Attempt to match a string against the current chunk, returning the indexed
// match.
@@ -334,22 +376,22 @@
// parameter identifiers in order to avoid this. Also, parameter lists can
// make use of splats.
lex.prototype.tag_parameters = function tag_parameters() {
var i, tok;
var _a, i, tok;
if (this.tag() !== ')') {
return null;
}
i = 0;
while (true) {
i += 1;
tok = this.tokens[this.tokens.length - i];
tok = this.prev(i);
if (!tok) {
return null;
}
if (tok[0] === 'IDENTIFIER') {
if ((_a = tok[0]) === 'IDENTIFIER') {
tok[0] = 'PARAM';
} else if (tok[0] === ')') {
} else if (_a === ')') {
tok[0] = 'PARAM_END';
} else if (tok[0] === '(') {
} else if (_a === '(') {
return (tok[0] = 'PARAM_START');
}
}
@@ -360,4 +402,4 @@
lex.prototype.close_indentation = function close_indentation() {
return this.outdent_token(this.indent);
};
})();
})();

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

1328
lib/nodes.js Normal file

File diff suppressed because it is too large Load Diff

111
lib/optparse.js Executable file
View File

@@ -0,0 +1,111 @@
(function(){
var LONG_FLAG, OPTIONAL, SHORT_FLAG, build_rule, build_rules, op, spaces;
// Create an OptionParser with a list of valid options.
op = (exports.OptionParser = function OptionParser(rules, banner) {
this.banner = banner || 'Usage: [Options]';
this.options_title = 'Available options:';
this.rules = build_rules(rules);
return this;
});
// Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
op.prototype.parse = function parse(args) {
var _a, _b, arg, is_option, options, rule;
arguments = Array.prototype.slice.call(arguments, 0);
options = {
arguments: []
};
args = args.concat([]);
while (((arg = args.shift()))) {
is_option = false;
_a = this.rules;
for (_b = 0; _b < _a.length; _b++) {
rule = _a[_b];
if (rule.letter === arg || rule.flag === arg) {
options[rule.name] = rule.argument ? args.shift() : true;
is_option = true;
break;
}
}
if (!(is_option)) {
options.arguments.push(arg);
}
}
return options;
};
// Return the help text for this OptionParser, for --help and such.
op.prototype.help = function help() {
var _a, _b, _c, _d, has_shorts, lines, longest, rule, text;
longest = 0;
has_shorts = false;
lines = [this.banner, '', this.options_title];
_a = this.rules;
for (_b = 0; _b < _a.length; _b++) {
rule = _a[_b];
if (rule.letter) {
has_shorts = true;
}
if (rule.flag.length > longest) {
longest = rule.flag.length;
}
}
_c = this.rules;
for (_d = 0; _d < _c.length; _d++) {
rule = _c[_d];
has_shorts ? (text = rule.letter ? spaces(2) + rule.letter + ', ' : spaces(6)) : null;
text += spaces(longest, rule.flag) + spaces(3);
text += rule.description;
lines.push(text);
}
return lines.join('\n');
};
// Private:
// Regex matchers for option flags.
LONG_FLAG = /^(--[\w\-]+)/;
SHORT_FLAG = /^(-\w+)/;
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, tuple;
_a = []; _b = rules;
for (_c = 0; _c < _b.length; _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,
argument: !!(match && match[1])
};
};
// Space-pad a string with the specified number of characters.
spaces = function spaces(num, text) {
var builder;
builder = [];
if (text) {
if (text.length >= num) {
return text;
}
num -= text.length;
builder.push(text);
}
while (num -= 1) {
builder.push(' ');
}
return builder.join('');
};
})();

536
lib/parser.js Executable file

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,42 +1,45 @@
(function(){
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, __a, __b, __c, __d, __e, __f, __g, __h, pair, re;
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, pair, re;
var __hasProp = Object.prototype.hasOwnProperty;
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
// In order to keep the grammar simple, the stream of tokens that the Lexer
// emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
// indentation, and single-line flavors of expressions.
exports.Rewriter = (re = function re() { });
// Tokens that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']];
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; __c < __b.length; __c++) {
pair = __b[__c];
__a.push(pair[0]);
_a = []; _b = BALANCED_PAIRS;
for (_c = 0; _c < _b.length; _c++) {
pair = _b[_c];
_a.push(pair[0]);
}
return __a;
return _a;
}).call(this);
// Tokens that signal the end of a balanced pair.
EXPRESSION_TAIL = (function() {
__d = []; __e = BALANCED_PAIRS;
for (__f = 0; __f < __e.length; __f++) {
pair = __e[__f];
__d.push(pair[1]);
_d = []; _e = BALANCED_PAIRS;
for (_f = 0; _f < _e.length; _f++) {
pair = _e[_f];
_d.push(pair[1]);
}
return __d;
return _d;
}).call(this);
// Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL);
// Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END'];
IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'OUTDENT'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'ARGUMENTS', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '->', '=>', '[', '(', '{'];
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 = {
};
__g = BALANCED_PAIRS;
for (__h = 0; __h < __g.length; __h++) {
pair = __g[__h];
INVERSES = {};
_g = BALANCED_PAIRS;
for (_h = 0; _h < _g.length; _h++) {
pair = _g[_h];
INVERSES[pair[0]] = pair[1];
INVERSES[pair[1]] = pair[0];
}
@@ -54,8 +57,8 @@
this.remove_mid_expression_newlines();
this.move_commas_outside_outdents();
this.close_open_calls_and_indexes();
this.add_implicit_parentheses();
this.add_implicit_indentation();
this.add_implicit_parentheses();
this.ensure_balance(BALANCED_PAIRS);
this.rewrite_closing_parens();
return this.tokens;
@@ -91,11 +94,11 @@
this.tokens.splice(i + 2, 1);
this.tokens.splice(i - 2, 1);
return 0;
} else if (prev[0] === 'TERMINATOR' && after[0] === 'INDENT') {
} else if (prev && prev[0] === 'TERMINATOR' && after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens[i - 1] = after;
return 1;
} else if (prev[0] !== 'TERMINATOR' && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
} else if (prev && prev[0] !== 'TERMINATOR' && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
return 2;
} else {
@@ -153,22 +156,23 @@
brackets = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
if (token[0] === 'CALL_START') {
var _i;
if ((_i = token[0]) === 'CALL_START') {
parens.push(0);
} else if (token[0] === 'INDEX_START') {
} else if (_i === 'INDEX_START') {
brackets.push(0);
} else if (token[0] === '(') {
} else if (_i === '(') {
parens[parens.length - 1] += 1;
} else if (token[0] === '[') {
} else if (_i === '[') {
brackets[brackets.length - 1] += 1;
} else if (token[0] === ')') {
} else if (_i === ')') {
if (parens[parens.length - 1] === 0) {
parens.pop();
token[0] = 'CALL_END';
} else {
parens[parens.length - 1] -= 1;
}
} else if (token[0] === ']') {
} else if (_i === ']') {
if (brackets[brackets.length - 1] === 0) {
brackets.pop();
token[0] = 'INDEX_END';
@@ -191,28 +195,35 @@
stack = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var __i, __j, __k, __l, idx, last, size, tmp;
if (token[0] === 'INDENT') {
var _i, _j, _k, _l, idx, last, size, stack_pointer, tag, tmp;
tag = token[0];
if (tag === 'INDENT') {
stack.push(0);
}
if (token[0] === 'OUTDENT') {
if (tag === 'OUTDENT') {
last = stack.pop();
stack[stack.length - 1] += last;
}
if (stack[stack.length - 1] > 0 && (IMPLICIT_END.indexOf(token[0]) >= 0 || (typeof !post !== "undefined" && !post !== null))) {
idx = token[0] === 'OUTDENT' ? i + 1 : i;
__k = 0; __l = stack[stack.length - 1];
for (__j=0, tmp=__k; (__k <= __l ? tmp < __l : tmp > __l); (__k <= __l ? tmp += 1 : tmp -= 1), __j++) {
this.tokens.splice(idx, 0, ['CALL_END', ')']);
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;
_k = 0; _l = stack[stack.length - stack_pointer];
for (_j = 0, tmp=_k; (_k <= _l ? tmp < _l : tmp > _l); (_k <= _l ? tmp += 1 : tmp -= 1), _j++) {
this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
}
size = stack[stack.length - stack_pointer] + 1;
stack[stack.length - stack_pointer] = 0;
return size;
}
size = stack[stack.length - 1] + 1;
stack[stack.length - 1] = 0;
return size;
}
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(token[0]) >= 0)) {
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(tag) >= 0)) {
return 1;
}
this.tokens.splice(i, 0, ['CALL_START', '(']);
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
stack[stack.length - 1] += 1;
return 2;
};
@@ -233,15 +244,15 @@
return 1;
}
starter = token[0];
this.tokens.splice(i + 1, 0, ['INDENT', 2]);
this.tokens.splice(i + 1, 0, ['INDENT', 2, token[2]]);
idx = i + 1;
parens = 0;
while (true) {
idx += 1;
tok = this.tokens[idx];
if ((!tok || SINGLE_CLOSERS.indexOf(tok[0]) >= 0 || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
if ((!tok || (SINGLE_CLOSERS.indexOf(tok[0]) >= 0 && tok[1] !== ';') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
insertion = this.tokens[idx - 1][0] === "," ? idx - 1 : idx;
this.tokens.splice(insertion, 0, ['OUTDENT', 2]);
this.tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]]);
break;
}
if (tok[0] === '(') {
@@ -265,18 +276,17 @@
// Ensure that all listed pairs of tokens are correctly balanced throughout
// the course of the token stream.
re.prototype.ensure_balance = function ensure_balance(pairs) {
var __i, __j, key, levels, unclosed, value;
levels = {
};
var _i, _j, key, levels, unclosed, value;
levels = {};
this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var __i, __j, __k, close, open;
__i = pairs;
for (__j = 0; __j < __i.length; __j++) {
pair = __i[__j];
__k = pair;
open = __k[0];
close = __k[1];
var _i, _j, _k, close, open;
_i = pairs;
for (_j = 0; _j < _i.length; _j++) {
pair = _i[_j];
_k = pair;
open = _k[0];
close = _k[1];
levels[open] = levels[open] || 0;
if (token[0] === open) {
levels[open] += 1;
@@ -285,7 +295,7 @@
levels[open] -= 1;
}
if (levels[open] < 0) {
throw "too many " + token[1];
throw new Error("too many " + token[1]);
}
}
return 1;
@@ -295,19 +305,17 @@
});
})(this));
unclosed = (function() {
__i = []; __j = levels;
for (key in __j) {
value = __j[key];
if (__hasProp.call(__j, key)) {
if (value > 0) {
__i.push(key);
}
_i = []; _j = levels;
for (key in _j) { if (__hasProp.call(_j, key)) {
value = _j[key];
if (value > 0) {
_i.push(key);
}
}
return __i;
}}
return _i;
}).call(this);
if (unclosed.length) {
throw "unclosed " + unclosed[0];
throw new Error("unclosed " + unclosed[0]);
}
};
// We'd like to support syntax like this:
@@ -322,18 +330,16 @@
// it with the inverse of what we've just popped.
// 3. Keep track of "debt" for tokens that we fake, to make sure we end
// up balanced in the end.
//
re.prototype.rewrite_closing_parens = function rewrite_closing_parens() {
var __i, debt, key, stack, val;
var _i, debt, key, stack, val;
stack = [];
debt = {
};
__i = INVERSES;
for (key in __i) {
val = __i[key];
if (__hasProp.call(__i, key)) {
((debt[key] = 0));
}
}
debt = {};
_i = INVERSES;
for (key in _i) { if (__hasProp.call(_i, key)) {
val = _i[key];
((debt[key] = 0));
}}
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var inv, match, mtag, tag;
@@ -374,4 +380,4 @@
});
})(this));
};
})();
})();

114
lib/scope.js Normal file
View File

@@ -0,0 +1,114 @@
(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.
Scope = (exports.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(', ');
};
})();

View File

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

45
src/cake.coffee Normal file
View File

@@ -0,0 +1,45 @@
# `cake` is a simplified version of Make (Rake, Jake) for CoffeeScript.
fs: require 'fs'
path: require 'path'
coffee: require 'coffee-script'
tasks: {}
no_such_task: (task) ->
process.stdio.writeError('No such task: "' + task + '"\n')
process.exit(1)
# Mixin the Cake functionality.
process.mixin {
# Define a task with a name, a description, and the action itself.
task: (name, description, action) ->
tasks[name]: {name: name, description: description, action: action}
# Invoke another task in the Cakefile.
invoke: (name) ->
no_such_task name unless tasks[name]
tasks[name].action()
}
# Display the list of Cake tasks.
print_tasks: ->
for name, task of tasks
spaces: 20 - name.length
spaces: if spaces > 0 then (' ' for i in [0..spaces]).join('') else ''
puts "cake " + name + spaces + ' # ' + task.description
# Running `cake` runs the tasks you pass asynchronously (node-style), or
# prints them out, with no arguments.
exports.run: ->
path.exists 'Cakefile', (exists) ->
throw new Error('Cakefile not found in ' + process.cwd()) unless exists
args: process.ARGV[2...process.ARGV.length]
fs.readFile 'Cakefile', (err, source) ->
eval coffee.compile source
return print_tasks() unless args.length
for arg in args
no_such_task arg unless tasks[arg]
tasks[arg].action()

View File

@@ -1,45 +1,45 @@
# Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
path: require('path')
# Set up for both the browser and the server.
if process?
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: {}
# The path to the CoffeeScript executable.
compiler: path.normalize(path.dirname(__filename) + '/../../bin/coffee')
# Thin wrapper for Jison compatibility around the real lexer.
parser.lexer: {
lex: ->
token: @tokens[@pos] or [""]
@pos += 1
this.yylineno: token[2]
this.yytext: token[1]
token[0]
setInput: (tokens) ->
@tokens: tokens
@pos: 0
upcomingInput: -> ""
showPosition: -> @pos
}
exports.VERSION: '0.5.2'
# Compile a string over stdin, with global variables, for the REPL.
exports.compile: (code, callback) ->
js: ''
coffee: process.createChildProcess compiler, ['--eval', '--no-wrap', '--globals']
coffee.addListener 'output', (results) ->
js += results if results?
coffee.addListener 'exit', ->
callback(js)
coffee.write(code)
coffee.close()
# Compile a list of CoffeeScript files on disk.
exports.compile_files: (paths, callback) ->
js: ''
coffee: process.createChildProcess compiler, ['--print'].concat(paths)
coffee.addListener 'output', (results) ->
js += results if results?
# NB: we have to add a mutex to make sure it doesn't get called twice.
exit_ran: false
coffee.addListener 'exit', ->
return if exit_ran
exit_ran: true
callback(js)
coffee.addListener 'error', (message) ->
return unless message
puts message
throw new Error "CoffeeScript compile error"
# Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile: (code, options) ->
(parser.parse lexer.tokenize code).compile(options)
# Just the tokens.
exports.tokenize: (code) ->
lexer.tokenize code
# Just the nodes.
exports.tree: (code) ->
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 document? and document.getElementsByTagName
for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript'
eval exports.compile tag.innerHTML

134
src/command_line.coffee Normal file
View File

@@ -0,0 +1,134 @@
fs: require 'fs'
path: require 'path'
optparse: require 'optparse'
CoffeeScript: require 'coffee-script'
BANNER: '''
coffee compiles CoffeeScript source files into JavaScript.
Usage:
coffee path/to/script.coffee
'''
SWITCHES: [
['-i', '--interactive', 'run an interactive CoffeeScript REPL']
['-r', '--run', 'compile and run a CoffeeScript']
['-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']
['-n', '--no-wrap', 'compile without the top-level function wrapper']
['-t', '--tokens', 'print the tokens that the lexer produces']
['-tr','--tree', '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: ->
parse_options()
return usage() if options.help
return version() if options.version
return require 'repl' if options.interactive
return compile_stdio() if options.stdio
return compile_script 'unknown', sources[0] if options.eval
return usage() unless sources.length
separator: sources.indexOf '--'
flags: []
if separator >= 0
flags: sources[(separator + 1)...sources.length]
sources: sources[0...separator]
process.ARGV = flags
watch_scripts() if options.watch
compile_scripts()
this
# The "--help" usage message.
usage: ->
puts '\n' + option_parser.help() + '\n'
process.exit 0
# The "--version" message.
version: ->
puts "CoffeeScript version " + CoffeeScript.VERSION
process.exit 0
# Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
# or JSLint results.
compile_scripts: ->
compile: (source) ->
fs.readFile source, (err, code) -> compile_script(source, code)
compile(source) for source in sources
# 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: (source, code) ->
o: options
try
if o.tokens then print_tokens CoffeeScript.tokenize code
else if o.tree then puts CoffeeScript.tree(code).toString()
else
js: CoffeeScript.compile code, compile_options()
if o.run then eval js
else if o.lint then lint js
else if o.print or o.eval then print js
else write_js source, js
catch err
if o.watch then puts err.message else throw err
# Listen for and compile scripts over stdio.
compile_stdio: ->
code: ''
process.stdio.open()
process.stdio.addListener 'data', (string) ->
code += string if string
process.stdio.addListener 'close', ->
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: ->
watch: (source) ->
process.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
return if curr.mtime.getTime() is prev.mtime.getTime()
fs.readFile source, (err, code) -> compile_script(source, code)
watch(source) for source in sources
# Write out a JavaScript source file with the compiled code.
write_js: (source, js) ->
filename: path.basename(source, path.extname(source)) + '.js'
dir: options.output or path.dirname(source)
js_path: path.join dir, filename
fs.writeFile js_path, js
# Pipe compiled JS through JSLint (requires a working 'jsl' command).
lint: (js) ->
jsl: process.createChildProcess('jsl', ['-nologo', '-stdin'])
jsl.addListener 'output', (result) ->
puts result.replace(/\n/g, '') if result
jsl.addListener 'error', (result) ->
puts result if result
jsl.write js
jsl.close()
# Pretty-print a token stream.
print_tokens: (tokens) ->
strings: for token in tokens
'[' + token[0] + ' ' + token[1].toString().replace(/\n/, '\\n') + ']'
puts strings.join(' ')
# Use OptionParser for all the options.
parse_options: ->
option_parser: new optparse.OptionParser SWITCHES, BANNER
options: option_parser.parse(process.ARGV)
sources: options.arguments[2...options.arguments.length]
# The options to pass to the CoffeeScript compiler.
compile_options: ->
if options['no-wrap'] then {no_wrap: true} else {}

456
src/grammar.coffee Normal file
View File

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

View File

@@ -1,4 +1,8 @@
Rewriter: require('./rewriter').Rewriter
if process?
Rewriter: require('./rewriter').Rewriter
else
this.exports: this
Rewriter: this.Rewriter
# The lexer reads a stream of CoffeeScript and divvys it up into tagged
# tokens. A minor bit of the ambiguity in the grammar has been avoided by
@@ -7,21 +11,40 @@ exports.Lexer: lex: ->
# Constants ============================================================
# The list of keywords passed verbatim to the parser.
KEYWORDS: [
"if", "else", "then", "unless",
"true", "false", "yes", "no", "on", "off",
"and", "or", "is", "isnt", "not",
"new", "return", "arguments",
# Keywords that CoffeScript shares in common with JS.
JS_KEYWORDS: [
"if", "else",
"true", "false",
"new", "return",
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "of", "by", "where", "while",
"for", "in", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends"
"switch", "super", "extends"
]
# Token matching regexes.
# CoffeeScript-only keywords -- which we're more relaxed about allowing.
COFFEE_KEYWORDS: [
"then", "unless",
"yes", "no", "on", "off",
"and", "or", "is", "isnt", "not",
"of", "by", "where", "when"
]
# The list of keywords passed verbatim to the parser.
KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS
# The list of keywords that are reserved by JavaScript, but not used, and aren't
# used by CoffeeScript. Using these will throw an error at compile time.
RESERVED: [
"case", "default", "do", "function", "var", "void", "with", "class"
"const", "let", "debugger", "enum", "export", "import", "native"
]
# JavaScript keywords and reserved words together, excluding CoffeeScript ones.
JS_FORBIDDEN: JS_KEYWORDS.concat RESERVED
# Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
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]*?)([^\\]|\\\\)')/
@@ -29,7 +52,7 @@ HEREDOC : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?(
JS : /^(``|`([\s\S]*?)([^\\]|\\\\)`)/
OPERATOR : /^([+\*&|\/\-%=<>:!?]+)/
WHITESPACE : /^([ \t]+)/
COMMENT : /^(((\n?[ \t]*)?#.*$)+)/
COMMENT : /^(((\n?[ \t]*)?#[^\n]*)+)/
CODE : /^((-|=)>)/
REGEX : /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
@@ -43,7 +66,7 @@ MULTILINER : /\n/g
STRING_NEWLINES : /\n[ \t]*/g
COMMENT_CLEANER : /(^[ \t]*#|\n[ \t]*$)/mg
NO_NEWLINE : /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
HEREDOC_INDENT : /^[ \t]+/g
HEREDOC_INDENT : /^[ \t]+/mg
# Tokens which a regular expression will never immediately follow, but which
# a division operator might.
@@ -55,206 +78,230 @@ NOT_REGEX: [
]
# Tokens which could legitimately be invoked or indexed.
CALLABLE: ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING']
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 its leading.
BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']
# Scan by attempting to match tokens one character at a time. Slow and steady.
lex::tokenize: (code) ->
this.code : code # Cleanup code by remove extra line breaks, TODO: chomp
this.i : 0 # Current character position we're parsing
this.line : 1 # The current line.
this.indent : 0 # The current indent level.
this.indents : [] # The stack of all indent levels we are currently within.
this.tokens : [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
this.spaced : null # The last token that has a space following it.
while this.i < this.code.length
this.chunk: this.code.slice(this.i)
this.extract_next_token()
this.close_indentation()
(new Rewriter()).rewrite this.tokens
@code : code # Cleanup code by remove extra line breaks, TODO: chomp
@i : 0 # Current character position we're parsing
@line : 1 # The current line.
@indent : 0 # The current indent level.
@indents : [] # The stack of all indent levels we are currently within.
@tokens : [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
while @i < @code.length
@chunk: @code.slice(@i)
@extract_next_token()
@close_indentation()
(new Rewriter()).rewrite @tokens
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed.
lex::extract_next_token: ->
return if this.identifier_token()
return if this.number_token()
return if this.heredoc_token()
return if this.string_token()
return if this.js_token()
return if this.regex_token()
return if this.indent_token()
return if this.comment_token()
return if this.whitespace_token()
return this.literal_token()
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()
# Tokenizers ==========================================================
# Matches identifying literals: variables, keywords, method names, etc.
lex::identifier_token: ->
return false unless id: this.match IDENTIFIER, 1
# Keywords are special identifiers tagged with their own name,
# 'if' will result in an ['IF', "if"] token.
tag: if KEYWORDS.indexOf(id) >= 0 then id.toUpperCase() else 'IDENTIFIER'
tag: 'LEADING_WHEN' if tag is 'WHEN' and (this.tag() is 'OUTDENT' or this.tag() is 'INDENT')
this.tag(-1, 'PROTOTYPE_ACCESS') if tag is 'IDENTIFIER' and this.value() is '::'
if tag is 'IDENTIFIER' and this.value() is '.' and !(this.value(-2) is '.')
if this.tag(-2) is '?'
this.tag(-1, 'SOAK_ACCESS')
this.tokens.splice(-2, 1)
return false unless id: @match IDENTIFIER, 1
@tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
if @value() is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@tag(1, 'SOAK_ACCESS')
@tokens.splice(-2, 1)
else
this.tag(-1, 'PROPERTY_ACCESS')
this.token(tag, id)
this.i += id.length
@tag(1, 'PROPERTY_ACCESS')
tag: 'IDENTIFIER'
tag: id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
@token(tag, id)
@i += id.length
true
# Matches numbers, including decimals, hex, and exponential notation.
lex::number_token: ->
return false unless number: this.match NUMBER, 1
this.token 'NUMBER', number
this.i += number.length
return false unless number: @match NUMBER, 1
@token 'NUMBER', number
@i += number.length
true
# Matches strings, including multi-line strings.
lex::string_token: ->
return false unless string: this.match STRING, 1
return false unless string: @match STRING, 1
escaped: string.replace STRING_NEWLINES, " \\\n"
this.token 'STRING', escaped
this.line += this.count string, "\n"
this.i += string.length
@token 'STRING', escaped
@line += @count string, "\n"
@i += string.length
true
# Matches heredocs, adjusting indentation to the correct level.
lex::heredoc_token: ->
return false unless match = this.chunk.match(HEREDOC)
return false unless match = @chunk.match(HEREDOC)
doc: match[2] or match[4]
indent: doc.match(HEREDOC_INDENT).sort()[0]
doc: doc.replace(new RegExp("^" + indent, 'g'), '')
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace('"', '\\"')
this.token 'STRING', '"' + doc + '"'
this.line += this.count match[1], "\n"
this.i += match[1].length
@token 'STRING', '"' + doc + '"'
@line += @count match[1], "\n"
@i += match[1].length
true
# Matches interpolated JavaScript.
lex::js_token: ->
return false unless script: this.match JS, 1
this.token 'JS', script.replace(JS_CLEANER, '')
this.i += script.length
return false unless script: @match JS, 1
@token 'JS', script.replace(JS_CLEANER, '')
@i += script.length
true
# Matches regular expression literals.
lex::regex_token: ->
return false unless regex: this.match REGEX, 1
return false if NOT_REGEX.indexOf(this.tag()) >= 0
this.token 'REGEX', regex
this.i += regex.length
return false unless regex: @match REGEX, 1
return false if NOT_REGEX.indexOf(@tag()) >= 0
@token 'REGEX', regex
@i += regex.length
true
# Matches and conumes comments.
lex::comment_token: ->
return false unless comment: this.match COMMENT, 1
this.line += comment.match(MULTILINER).length
this.token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
this.token 'TERMINATOR', "\n"
this.i += comment.length
return false unless comment: @match COMMENT, 1
@line += (comment.match(MULTILINER) or []).length
@token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
@token 'TERMINATOR', "\n"
@i += comment.length
true
# Record tokens for indentation differing from the previous line.
lex::indent_token: ->
return false unless indent: this.match MULTI_DENT, 1
this.line += indent.match(MULTILINER).length
this.i += indent.length
next_character: this.chunk.match(MULTI_DENT)[4]
no_newlines: next_character is '.' or (this.value().match(NO_NEWLINE) and this.tokens[this.tokens.length - 2][0] isnt '.' and not this.value().match(CODE))
return this.suppress_newlines(indent) if no_newlines
return false unless indent: @match MULTI_DENT, 1
@line += indent.match(MULTILINER).length
@i += indent.length
next_character: @chunk.match(MULTI_DENT)[4]
prev: @prev(2)
no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
return @suppress_newlines(indent) if no_newlines
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
return this.newline_token(indent) if size is this.indent
if size > this.indent
diff: size - this.indent
this.token 'INDENT', diff
this.indents.push diff
return @newline_token(indent) if size is @indent
if size > @indent
diff: size - @indent
@token 'INDENT', diff
@indents.push diff
else
this.outdent_token this.indent - size
this.indent: size
@outdent_token @indent - size
@indent: size
true
# Record an oudent token or tokens, if we're moving back inwards past
# multiple recorded indents.
lex::outdent_token: (move_out) ->
while move_out > 0 and this.indents.length
last_indent: this.indents.pop()
this.token 'OUTDENT', last_indent
while move_out > 0 and @indents.length
last_indent: @indents.pop()
@token 'OUTDENT', last_indent
move_out -= last_indent
this.token 'TERMINATOR', "\n"
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Matches and consumes non-meaningful whitespace.
lex::whitespace_token: ->
return false unless space: this.match WHITESPACE, 1
this.spaced: this.value()
this.i += space.length
return false unless space: @match WHITESPACE, 1
prev: @prev()
prev.spaced: true if prev
@i += space.length
true
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
lex::newline_token: (newlines) ->
this.token 'TERMINATOR', "\n" unless this.value() is "\n"
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Tokens to explicitly escape newlines are removed once their job is done.
lex::suppress_newlines: (newlines) ->
this.tokens.pop() if this.value() is "\\"
@tokens.pop() if @value() is "\\"
true
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# the proper order of operations.
lex::literal_token: ->
match: this.chunk.match(OPERATOR)
match: @chunk.match(OPERATOR)
value: match and match[1]
this.tag_parameters() if value and value.match(CODE)
value ||= this.chunk.substr(0, 1)
tag: if value.match(ASSIGNMENT) then 'ASSIGN' else value
tag: 'TERMINATOR' if value == ';'
if this.value() isnt this.spaced and CALLABLE.indexOf(this.tag()) >= 0
@tag_parameters() if value and value.match(CODE)
value ||= @chunk.substr(0, 1)
not_spaced: not @prev() or not @prev().spaced
tag: value
if value.match(ASSIGNMENT)
tag: 'ASSIGN'
throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
else if value is ';'
tag: 'TERMINATOR'
else if value is '[' and @tag() is '?' and not_spaced
tag: 'SOAKED_INDEX_START'
@soaked_index: true
@tokens.pop()
else if value is ']' and @soaked_index
tag: 'SOAKED_INDEX_END'
@soaked_index: false
else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
tag: 'CALL_START' if value is '('
tag: 'INDEX_START' if value is '['
this.token tag, value
this.i += value.length
@token tag, value
@i += value.length
true
# Helpers =============================================================
# Add a token to the results, taking note of the line number.
lex::token: (tag, value) ->
this.tokens.push([tag, value])
# this.tokens.push([tag, Value.new(value, @line)])
@tokens.push([tag, value, @line])
# Look at a tag in the current token stream.
lex::tag: (index, tag) ->
return unless tok: this.tokens[this.tokens.length - (index || 1)]
return unless tok: @prev(index)
return tok[0]: tag if tag?
tok[0]
# Look at a value in the current token stream.
lex::value: (index, val) ->
return unless tok: this.tokens[this.tokens.length - (index || 1)]
return unless tok: @prev(index)
return tok[1]: val if val?
tok[1]
# Look at a previous token.
lex::prev: (index) ->
@tokens[@tokens.length - (index or 1)]
# Count the occurences of a character in a string.
lex::count: (string, letter) ->
num: 0
pos: string.indexOf(letter)
while pos isnt -1
count += 1
num += 1
pos: string.indexOf(letter, pos + 1)
count
num
# Attempt to match a string against the current chunk, returning the indexed
# match.
lex::match: (regex, index) ->
return false unless m: this.chunk.match(regex)
return false unless m: @chunk.match(regex)
if m then m[index] else false
# A source of ambiguity in our grammar was parameter lists in function
@@ -262,11 +309,11 @@ lex::match: (regex, index) ->
# parameter identifiers in order to avoid this. Also, parameter lists can
# make use of splats.
lex::tag_parameters: ->
return if this.tag() isnt ')'
return if @tag() isnt ')'
i: 0
while true
i += 1
tok: this.tokens[this.tokens.length - i]
tok: @prev(i)
return if not tok
switch tok[0]
when 'IDENTIFIER' then tok[0]: 'PARAM'
@@ -277,4 +324,4 @@ lex::tag_parameters: ->
# Close up all remaining open blocks. IF the first token is an indent,
# axe it.
lex::close_indentation: ->
this.outdent_token(this.indent)
@outdent_token(@indent)

42
src/narwhal.coffee Normal file
View File

@@ -0,0 +1,42 @@
# 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: (source) ->
coffee.compile source
# Compile a given CoffeeScript file into JavaScript.
exports.compileFile: (path) ->
coffee.compile file.read path
# 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 + ")"
# The Narwhal loader for '.coffee' files.
factories: {}
loader: {}
# Reload the coffee-script environment from source.
loader.reload: (topId, path) ->
factories[topId]: ->
exports.makeNarwhalFactory path
# Ensure that the coffee-script environment is loaded.
loader.load: (topId, path) ->
factories[topId] ||= this.reload topId, path
require.loader.loaders.unshift [".coffee", loader]

View File

@@ -1,77 +0,0 @@
# The Narwhal-compatibility wrapper for CoffeeScript.
# Require external dependencies.
OS: require 'os'
File: require 'file'
Readline: require 'readline'
# The path to the CoffeeScript Compiler.
coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().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"
# Alias print to "puts", for Node.js compatibility:
puts: print
# Run a simple REPL, round-tripping to the CoffeeScript compiler for every
# command.
exports.run: (args) ->
if args.length
for path, i in args
exports.evalCS File.read path
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 + ")"
# The Narwhal loader for '.coffee' files.
factories: {}
loader: {}
# Reload the coffee-script environment from source.
loader.reload: (topId, path) ->
factories[topId]: ->
exports.makeNarwhalFactory path
# Ensure that the coffee-script environment is loaded.
loader.load: (topId, path) ->
factories[topId] ||= this.reload topId, path
require.loader.loaders.unshift [".coffee", loader]

File diff suppressed because it is too large Load Diff

72
src/optparse.coffee Normal file
View File

@@ -0,0 +1,72 @@
# Create an OptionParser with a list of valid options.
op: exports.OptionParser: (rules, banner) ->
@banner: banner or 'Usage: [Options]'
@options_title: 'Available options:'
@rules: build_rules(rules)
this
# Parse the argument array, calling defined callbacks, returning the remaining non-option arguments.
op::parse: (args) ->
options: {arguments: []}
args: args.concat []
while (arg: args.shift())
is_option: false
for rule in @rules
if rule.letter is arg or rule.flag is arg
options[rule.name]: if rule.argument then args.shift() else true
is_option: true
break
options.arguments.push arg unless is_option
options
# Return the help text for this OptionParser, for --help and such.
op::help: ->
longest: 0
has_shorts: false
lines: [@banner, '', @options_title]
for rule in @rules
has_shorts: true if rule.letter
longest: rule.flag.length if rule.flag.length > longest
for rule in @rules
if has_shorts
text: if rule.letter then spaces(2) + rule.letter + ', ' else spaces(6)
text += spaces(longest, rule.flag) + spaces(3)
text += rule.description
lines.push text
lines.join('\n')
# Private:
# Regex matchers for option flags.
LONG_FLAG: /^(--[\w\-]+)/
SHORT_FLAG: /^(-\w+)/
OPTIONAL: /\[(.+)\]/
# Build rules from a list of valid switch tuples in the form:
# [letter-flag, long-flag, help], or [long-flag, help].
build_rules: (rules) ->
for tuple in rules
tuple.unshift(null) if tuple.length < 3
build_rule(tuple...)
# Build a rule from a short-letter-flag, long-form-flag, and help text.
build_rule: (letter, flag, description) ->
match: flag.match(OPTIONAL)
flag: flag.match(LONG_FLAG)[1]
{
name: flag.substr(2)
letter: letter
flag: flag
description: description
argument: !!(match and match[1])
}
# Space-pad a string with the specified number of characters.
spaces: (num, text) ->
builder: []
if text
return text if text.length >= num
num -= text.length
builder.push text
while num -= 1 then builder.push ' '
builder.join ''

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
this.exports: this unless process?
# 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.
@@ -5,7 +7,8 @@ exports.Rewriter: re: ->
# Tokens that must be balanced.
BALANCED_PAIRS: [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'],
['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']]
['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: pair[0] for pair in BALANCED_PAIRS
@@ -18,11 +21,12 @@ EXPRESSION_CLOSE: ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL)
# Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC: ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END']
IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'OUTDENT']
IMPLICIT_BLOCK:['->', '=>', '{', '[', ',']
IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT', 'OUTDENT']
IMPLICIT_CALL: ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START',
'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'ARGUMENTS',
'TRY', 'DELETE', 'TYPEOF', 'SWITCH',
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT',
'->', '=>', '[', '(', '{']
'@', '->', '=>', '[', '(', '{']
# The inverse mappings of token pairs we're trying to fix up.
INVERSES: {}
@@ -39,17 +43,17 @@ SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_W
# a time. This could certainly be changed into a single pass through the
# stream, with a big ol' efficient switch, but it's much nicer like this.
re::rewrite: (tokens) ->
this.tokens: tokens
this.adjust_comments()
this.remove_leading_newlines()
this.remove_mid_expression_newlines()
this.move_commas_outside_outdents()
this.close_open_calls_and_indexes()
this.add_implicit_parentheses()
this.add_implicit_indentation()
this.ensure_balance(BALANCED_PAIRS)
this.rewrite_closing_parens()
this.tokens
@tokens: tokens
@adjust_comments()
@remove_leading_newlines()
@remove_mid_expression_newlines()
@move_commas_outside_outdents()
@close_open_calls_and_indexes()
@add_implicit_indentation()
@add_implicit_parentheses()
@ensure_balance(BALANCED_PAIRS)
@rewrite_closing_parens()
@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
@@ -58,31 +62,31 @@ re::rewrite: (tokens) ->
re::scan_tokens: (block) ->
i: 0
while true
break unless this.tokens[i]
move: block(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i)
break unless @tokens[i]
move: block(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
i += move
true
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on their own line.
re::adjust_comments: ->
this.scan_tokens (prev, token, post, i) =>
@scan_tokens (prev, token, post, i) =>
return 1 unless token[0] is 'COMMENT'
before: this.tokens[i - 2]
after: this.tokens[i + 2]
before: @tokens[i - 2]
after: @tokens[i + 2]
if before and after and
((before[0] is 'INDENT' and after[0] is 'OUTDENT') or
(before[0] is 'OUTDENT' and after[0] is 'INDENT')) and
before[1] is after[1]
this.tokens.splice(i + 2, 1)
this.tokens.splice(i - 2, 1)
@tokens.splice(i + 2, 1)
@tokens.splice(i - 2, 1)
return 0
else if prev[0] is 'TERMINATOR' and after[0] is 'INDENT'
this.tokens.splice(i + 2, 1)
this.tokens[i - 1]: after
else if prev and prev[0] is 'TERMINATOR' and after and after[0] is 'INDENT'
@tokens.splice(i + 2, 1)
@tokens[i - 1]: after
return 1
else if prev[0] isnt 'TERMINATOR' and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]])
else if prev and prev[0] isnt 'TERMINATOR' and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
@tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]])
return 2
else
return 1
@@ -90,21 +94,21 @@ re::adjust_comments: ->
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
re::remove_leading_newlines: ->
this.tokens.shift() if this.tokens[0][0] is 'TERMINATOR'
@tokens.shift() if @tokens[0][0] is 'TERMINATOR'
# Some blocks occur in the middle of expressions -- when we're expecting
# this, remove their trailing newlines.
re::remove_mid_expression_newlines: ->
this.scan_tokens (prev, token, post, i) =>
@scan_tokens (prev, token, post, i) =>
return 1 unless post and EXPRESSION_CLOSE.indexOf(post[0]) >= 0 and token[0] is 'TERMINATOR'
this.tokens.splice(i, 1)
@tokens.splice(i, 1)
return 0
# Make sure that we don't accidentally break trailing commas, which need
# to go on the outside of expression closers.
re::move_commas_outside_outdents: ->
this.scan_tokens (prev, token, post, i) =>
this.tokens.splice(i, 1, token) if token[0] is 'OUTDENT' and prev[0] is ','
@scan_tokens (prev, token, post, i) =>
@tokens.splice(i, 1, token) if token[0] is 'OUTDENT' and prev[0] is ','
return 1
# We've tagged the opening parenthesis of a method call, and the opening
@@ -112,7 +116,7 @@ re::move_commas_outside_outdents: ->
re::close_open_calls_and_indexes: ->
parens: [0]
brackets: [0]
this.scan_tokens (prev, token, post, i) =>
@scan_tokens (prev, token, post, i) =>
switch token[0]
when 'CALL_START' then parens.push(0)
when 'INDEX_START' then brackets.push(0)
@@ -137,20 +141,24 @@ re::close_open_calls_and_indexes: ->
# deal with them.
re::add_implicit_parentheses: ->
stack: [0]
this.scan_tokens (prev, token, post, i) =>
stack.push(0) if token[0] is 'INDENT'
if token[0] is 'OUTDENT'
@scan_tokens (prev, token, post, i) =>
tag: token[0]
stack.push(0) if tag is 'INDENT'
if tag is 'OUTDENT'
last: stack.pop()
stack[stack.length - 1] += last
if stack[stack.length - 1] > 0 and (IMPLICIT_END.indexOf(token[0]) >= 0 or !post?)
idx: if token[0] is 'OUTDENT' then i + 1 else i
for tmp in [0...stack[stack.length - 1]]
this.tokens.splice(idx, 0, ['CALL_END', ')'])
size: stack[stack.length - 1] + 1
stack[stack.length - 1]: 0
return size
return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(token[0]) >= 0
this.tokens.splice(i, 0, ['CALL_START', '('])
if IMPLICIT_END.indexOf(tag) >= 0 or !post?
return 1 if tag is 'INDENT' and prev and IMPLICIT_BLOCK.indexOf(prev[0]) >= 0
if stack[stack.length - 1] > 0 or tag is 'INDENT'
idx: if tag is 'OUTDENT' then i + 1 else i
stack_pointer: if tag is 'INDENT' then 2 else 1
for tmp in [0...stack[stack.length - stack_pointer]]
@tokens.splice(idx, 0, ['CALL_END', ')', token[2]])
size: stack[stack.length - stack_pointer] + 1
stack[stack.length - stack_pointer]: 0
return size
return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(tag) >= 0
@tokens.splice(i, 0, ['CALL_START', '(', token[2]])
stack[stack.length - 1] += 1
return 2
@@ -159,42 +167,43 @@ re::add_implicit_parentheses: ->
# blocks, so it doesn't need to.
# ')' can close a single-line block, but we need to make sure it's balanced.
re::add_implicit_indentation: ->
this.scan_tokens (prev, token, post, i) =>
@scan_tokens (prev, token, post, i) =>
return 1 unless SINGLE_LINERS.indexOf(token[0]) >= 0 and post[0] isnt 'INDENT' and
not (token[0] is 'ELSE' and post[0] is 'IF')
starter: token[0]
this.tokens.splice(i + 1, 0, ['INDENT', 2])
@tokens.splice(i + 1, 0, ['INDENT', 2, token[2]])
idx: i + 1
parens: 0
while true
idx += 1
tok: this.tokens[idx]
if (not tok or SINGLE_CLOSERS.indexOf(tok[0]) >= 0 or
tok: @tokens[idx]
if (not tok or
(SINGLE_CLOSERS.indexOf(tok[0]) >= 0 and tok[1] isnt ';') or
(tok[0] is ')' && parens is 0)) and
not (starter is 'ELSE' and tok[0] is 'ELSE')
insertion: if this.tokens[idx - 1][0] is "," then idx - 1 else idx
this.tokens.splice(insertion, 0, ['OUTDENT', 2])
insertion: if @tokens[idx - 1][0] is "," then idx - 1 else idx
@tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]])
break
parens += 1 if tok[0] is '('
parens -= 1 if tok[0] is ')'
return 1 unless token[0] is 'THEN'
this.tokens.splice(i, 1)
@tokens.splice(i, 1)
return 0
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
re::ensure_balance: (pairs) ->
levels: {}
this.scan_tokens (prev, token, post, i) =>
@scan_tokens (prev, token, post, i) =>
for pair in pairs
[open, close]: pair
levels[open] ||= 0
levels[open] += 1 if token[0] is open
levels[open] -= 1 if token[0] is close
throw "too many " + token[1] if levels[open] < 0
throw new Error("too many " + token[1]) if levels[open] < 0
return 1
unclosed: key for key, value of levels when value > 0
throw "unclosed " + unclosed[0] if unclosed.length
throw new Error("unclosed " + unclosed[0]) if unclosed.length
# We'd like to support syntax like this:
# el.click((event) ->
@@ -213,7 +222,7 @@ re::rewrite_closing_parens: ->
stack: []
debt: {}
(debt[key]: 0) for key, val of INVERSES
this.scan_tokens (prev, token, post, i) =>
@scan_tokens (prev, token, post, i) =>
tag: token[0]
inv: INVERSES[token[0]]
# Push openers onto the stack.
@@ -225,7 +234,7 @@ re::rewrite_closing_parens: ->
# If the tag is already in our debt, swallow it.
if debt[inv] > 0
debt[inv] -= 1
this.tokens.splice(i, 1)
@tokens.splice(i, 1)
return 0
else
# Pop the stack of open delimiters.
@@ -238,7 +247,7 @@ re::rewrite_closing_parens: ->
# Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1
val: if mtag is 'INDENT' then match[1] else INVERSES[mtag]
this.tokens.splice(i, 0, [INVERSES[mtag], val])
@tokens.splice(i, 0, [INVERSES[mtag], val])
return 1
else
return 1

View File

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

View File

@@ -1,49 +1,75 @@
dup: (input) ->
output: null
if input instanceof Array
output: []
for val in input
output.push(val)
else
output: {}
for key, val of input
output.key: val
output
output
this.exports: this unless process?
# scope objects form a tree corresponding to the shape of the function
# Scope objects form a tree corresponding to the shape of the function
# definitions present in the script. They provide lexical scope, to determine
# whether a variable has been seen before or if it needs to be declared.
exports.Scope: (parent, expressions, func) ->
# Initialize a scope with its parent, for lookups up the chain,
# as well as the Expressions body where it should declare its variables,
# and the function that it wraps.
this.parent: parent
this.expressions: expressions
this.function: func
this.variables: {}
this.temp_variable: if this.parent then dup(this.parent.temp_variable) else '__a'
#
# 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.
Scope: exports.Scope: (parent, expressions, method) ->
[@parent, @expressions, @method]: [parent, expressions, method]
@variables: {}
@temp_var: if @parent then @parent.temp_var else '_a'
this
# Look up a variable in lexical scope, or declare it if not found.
exports.Scope::find: (name, rem) ->
remote: if rem? then rem else false
found: this.check(name)
return found if found || remote
this.variables[name]: 'var'
found
Scope::find: (name) ->
return true if @check name
@variables[name]: 'var'
false
# Define a local variable as originating from a parameter in current scope
# -- no var required.
exports.Scope::parameter: (name) ->
this.variables[name]: 'param'
Scope::parameter: (name) ->
@variables[name]: 'param'
# Just check to see if a variable has already been declared.
exports.Scope::check: (name) ->
return true if this.variables[name]?
# TODO: what does that ruby !! mean..? need to follow up
# .. this next line is prolly wrong ..
not not (this.parent and this.parent.check(name))
Scope::check: (name) ->
return true if @variables[name]
!!(@parent and @parent.check(name))
# You can reset a found variable on the immediate scope.
exports.Scope::reset: (name) ->
this.variables[name]: undefined
Scope::reset: (name) ->
delete @variables[name]
# Find an available, short, name for a compiler-generated variable.
Scope::free_variable: ->
while @check @temp_var
ordinal: 1 + parseInt @temp_var.substr(1), 36
@temp_var: '_' + ordinal.toString(36).replace(/\d/g, 'a')
@variables[@temp_var]: 'var'
@temp_var
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
Scope::assign: (name, value, top_level) ->
return @parent.assign(name, value, top_level) if top_level and @parent
@variables[name]: {value: value, assigned: true}
# Does this scope reference any variables that need to be declared in the
# given function body?
Scope::has_declarations: (body) ->
body is @expressions and @declared_variables().length
# Does this scope reference any assignments that need to be declared at the
# top of the given function body?
Scope::has_assignments: (body) ->
body is @expressions and @assigned_variables().length
# Return the list of variables first declared in current scope.
Scope::declared_variables: ->
(key for key, val of @variables when val is 'var').sort()
# Return the list of variables that are supposed to be assigned at the top
# of scope.
Scope::assigned_variables: ->
key + ' = ' + val.value for key, val of @variables when val.assigned
# Compile the string representing all of the declared variables for this scope.
Scope::compiled_declarations: ->
@declared_variables().join ', '
# Compile the string performing all of the variable assignments for this scope.
Scope::compiled_assignments: ->
@assigned_variables().join ', '

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