├── test ├── repl.coffee ├── javascript_literals.coffee ├── importing.coffee ├── booleans.coffee ├── scope.coffee ├── option_parser.coffee ├── numbers.coffee ├── regexps.coffee ├── compilation.coffee ├── arrays.coffee ├── strings.coffee ├── exception_handling.coffee ├── ranges.coffee ├── helpers.coffee ├── test.html ├── slicing_and_splicing.coffee ├── formatting.coffee ├── soaks.coffee ├── comments.coffee ├── interpolation.coffee ├── functions.coffee ├── objects.coffee ├── operators.coffee ├── assignment.coffee └── control_flow.coffee ├── documentation ├── coffee │ ├── soaks.coffee │ ├── functions.coffee │ ├── range_comprehensions.coffee │ ├── prototypes.coffee │ ├── expressions_assignment.coffee │ ├── comparisons.coffee │ ├── objects_reserved.coffee │ ├── scope.coffee │ ├── splices.coffee │ ├── block_comment.coffee │ ├── embedded.coffee │ ├── expressions_comprehension.coffee │ ├── patterns_and_splats.coffee │ ├── heredocs.coffee │ ├── parallel_assignment.coffee │ ├── object_comprehensions.coffee │ ├── default_args.coffee │ ├── expressions_try.coffee │ ├── slices.coffee │ ├── try.coffee │ ├── do.coffee │ ├── existence.coffee │ ├── interpolation.coffee │ ├── fat_arrow.coffee │ ├── multiple_return_values.coffee │ ├── expressions.coffee │ ├── conditionals.coffee │ ├── switch.coffee │ ├── aliases.coffee │ ├── while.coffee │ ├── strings.coffee │ ├── object_extraction.coffee │ ├── cake_tasks.coffee │ ├── objects_and_arrays.coffee │ ├── array_comprehensions.coffee │ ├── heregexes.coffee │ ├── classes.coffee │ ├── splats.coffee │ └── overview.coffee ├── js │ ├── heredocs.js │ ├── block_comment.js │ ├── expressions_assignment.js │ ├── objects_reserved.js │ ├── prototypes.js │ ├── embedded.js │ ├── heregexes.js │ ├── comparisons.js │ ├── functions.js │ ├── splices.js │ ├── soaks.js │ ├── try.js │ ├── slices.js │ ├── parallel_assignment.js │ ├── expressions_try.js │ ├── scope.js │ ├── array_comprehensions.js │ ├── interpolation.js │ ├── default_args.js │ ├── range_comprehensions.js │ ├── expressions_comprehension.js │ ├── multiple_return_values.js │ ├── conditionals.js │ ├── strings.js │ ├── patterns_and_splats.js │ ├── do.js │ ├── existence.js │ ├── fat_arrow.js │ ├── object_comprehensions.js │ ├── expressions.js │ ├── objects_and_arrays.js │ ├── object_extraction.js │ ├── aliases.js │ ├── cake_tasks.js │ ├── switch.js │ ├── while.js │ ├── splats.js │ ├── overview.js │ └── classes.js ├── images │ ├── logo.png │ ├── banding.png │ ├── favicon.ico │ ├── button_bg.png │ ├── background.png │ ├── screenshadow.png │ ├── button_bg_dark.gif │ └── button_bg_green.gif ├── css │ └── idle.css └── docs │ ├── index.html │ └── docco.css ├── src ├── index.coffee ├── browser.coffee ├── helpers.coffee ├── cake.coffee ├── scope.coffee ├── optparse.coffee ├── repl.coffee └── coffee-script.coffee ├── .npmignore ├── .gitignore ├── lib └── coffee-script │ ├── index.js │ ├── helpers.js │ ├── browser.js │ ├── cake.js │ ├── scope.js │ ├── optparse.js │ ├── repl.js │ └── coffee-script.js ├── examples ├── computer_science │ ├── README │ ├── bubble_sort.coffee │ ├── merge_sort.coffee │ ├── selection_sort.coffee │ ├── binary_search.coffee │ ├── luhn_algorithm.coffee │ └── linked_list.coffee ├── web_server.coffee ├── beautiful_code │ ├── quicksort_runtime.coffee │ ├── binary_search.coffee │ └── regular_expression_matcher.coffee ├── blocks.coffee ├── code.coffee ├── potion.coffee └── poignant.coffee ├── bin ├── cake └── coffee ├── package.json ├── LICENSE ├── Rakefile ├── extras └── jsl.conf ├── README └── Cakefile /test/repl.coffee: -------------------------------------------------------------------------------- 1 | # REPL 2 | # ---- 3 | 4 | # TODO: add tests 5 | -------------------------------------------------------------------------------- /documentation/coffee/soaks.coffee: -------------------------------------------------------------------------------- 1 | zip = lottery.drawWinner?().address?.zipcode 2 | -------------------------------------------------------------------------------- /documentation/coffee/functions.coffee: -------------------------------------------------------------------------------- 1 | square = (x) -> x * x 2 | cube = (x) -> square(x) * x 3 | -------------------------------------------------------------------------------- /documentation/coffee/range_comprehensions.coffee: -------------------------------------------------------------------------------- 1 | countdown = (num for num in [10..1]) 2 | 3 | -------------------------------------------------------------------------------- /documentation/js/heredocs.js: -------------------------------------------------------------------------------- 1 | var html; 2 | html = '\n cup of coffeescript\n'; -------------------------------------------------------------------------------- /documentation/coffee/prototypes.coffee: -------------------------------------------------------------------------------- 1 | String::dasherize = -> 2 | this.replace /_/g, "-" 3 | 4 | -------------------------------------------------------------------------------- /documentation/coffee/expressions_assignment.coffee: -------------------------------------------------------------------------------- 1 | six = (one = 1) + (two = 2) + (three = 3) 2 | 3 | 4 | -------------------------------------------------------------------------------- /documentation/coffee/comparisons.coffee: -------------------------------------------------------------------------------- 1 | cholesterol = 127 2 | 3 | healthy = 200 > cholesterol > 60 4 | 5 | 6 | -------------------------------------------------------------------------------- /documentation/js/block_comment.js: -------------------------------------------------------------------------------- 1 | /* 2 | CoffeeScript Compiler v1.1.2 3 | Released under the MIT License 4 | */ -------------------------------------------------------------------------------- /documentation/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/logo.png -------------------------------------------------------------------------------- /documentation/js/expressions_assignment.js: -------------------------------------------------------------------------------- 1 | var one, six, three, two; 2 | six = (one = 1) + (two = 2) + (three = 3); -------------------------------------------------------------------------------- /documentation/js/objects_reserved.js: -------------------------------------------------------------------------------- 1 | $('.account').attr({ 2 | "class": 'active' 3 | }); 4 | log(object["class"]); -------------------------------------------------------------------------------- /documentation/js/prototypes.js: -------------------------------------------------------------------------------- 1 | String.prototype.dasherize = function() { 2 | return this.replace(/_/g, "-"); 3 | }; -------------------------------------------------------------------------------- /documentation/coffee/objects_reserved.coffee: -------------------------------------------------------------------------------- 1 | $('.account').attr class: 'active' 2 | 3 | log object.class 4 | 5 | 6 | -------------------------------------------------------------------------------- /documentation/images/banding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/banding.png -------------------------------------------------------------------------------- /documentation/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/favicon.ico -------------------------------------------------------------------------------- /documentation/coffee/scope.coffee: -------------------------------------------------------------------------------- 1 | outer = 1 2 | changeNumbers = -> 3 | inner = -1 4 | outer = 10 5 | inner = changeNumbers() -------------------------------------------------------------------------------- /documentation/images/button_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/button_bg.png -------------------------------------------------------------------------------- /documentation/js/embedded.js: -------------------------------------------------------------------------------- 1 | var hi; 2 | hi = function() { 3 | return [document.title, "Hello JavaScript"].join(": "); 4 | }; -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | # Loader for CoffeeScript as a Node.js library. 2 | exports[key] = val for key, val of require './coffee-script' -------------------------------------------------------------------------------- /documentation/coffee/splices.coffee: -------------------------------------------------------------------------------- 1 | numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 | 3 | numbers[3..6] = [-3, -4, -5, -6] 4 | 5 | 6 | -------------------------------------------------------------------------------- /documentation/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/background.png -------------------------------------------------------------------------------- /documentation/images/screenshadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/screenshadow.png -------------------------------------------------------------------------------- /documentation/js/heregexes.js: -------------------------------------------------------------------------------- 1 | var OPERATOR; 2 | OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/; -------------------------------------------------------------------------------- /documentation/coffee/block_comment.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | CoffeeScript Compiler v1.1.2 3 | Released under the MIT License 4 | ### 5 | 6 | 7 | -------------------------------------------------------------------------------- /documentation/coffee/embedded.coffee: -------------------------------------------------------------------------------- 1 | hi = `function() { 2 | return [document.title, "Hello JavaScript"].join(": "); 3 | }` 4 | 5 | 6 | -------------------------------------------------------------------------------- /documentation/images/button_bg_dark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/button_bg_dark.gif -------------------------------------------------------------------------------- /documentation/js/comparisons.js: -------------------------------------------------------------------------------- 1 | var cholesterol, healthy; 2 | cholesterol = 127; 3 | healthy = (200 > cholesterol && cholesterol > 60); -------------------------------------------------------------------------------- /documentation/coffee/expressions_comprehension.coffee: -------------------------------------------------------------------------------- 1 | # The first ten global properties. 2 | 3 | globals = (name for name of window)[0...10] -------------------------------------------------------------------------------- /documentation/images/button_bg_green.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onilabs/coffee-script/master/documentation/images/button_bg_green.gif -------------------------------------------------------------------------------- /documentation/coffee/patterns_and_splats.coffee: -------------------------------------------------------------------------------- 1 | tag = "" 2 | 3 | [open, contents..., close] = tag.split("") 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /documentation/coffee/heredocs.coffee: -------------------------------------------------------------------------------- 1 | html = ''' 2 | 3 | cup of coffeescript 4 | 5 | ''' 6 | 7 | 8 | -------------------------------------------------------------------------------- /documentation/coffee/parallel_assignment.coffee: -------------------------------------------------------------------------------- 1 | theBait = 1000 2 | theSwitch = 0 3 | 4 | [theBait, theSwitch] = [theSwitch, theBait] 5 | 6 | 7 | -------------------------------------------------------------------------------- /documentation/coffee/object_comprehensions.coffee: -------------------------------------------------------------------------------- 1 | yearsOld = max: 10, ida: 9, tim: 11 2 | 3 | ages = for child, age of yearsOld 4 | "#{child} is #{age}" 5 | -------------------------------------------------------------------------------- /documentation/js/functions.js: -------------------------------------------------------------------------------- 1 | var cube, square; 2 | square = function(x) { 3 | return x * x; 4 | }; 5 | cube = function(x) { 6 | return square(x) * x; 7 | }; -------------------------------------------------------------------------------- /documentation/coffee/default_args.coffee: -------------------------------------------------------------------------------- 1 | fill = (container, liquid = "coffee") -> 2 | "Filling the #{container} with #{liquid}..." 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /documentation/coffee/expressions_try.coffee: -------------------------------------------------------------------------------- 1 | alert( 2 | try 3 | nonexistent / undefined 4 | catch error 5 | "And the error is ... #{error}" 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /documentation/coffee/slices.coffee: -------------------------------------------------------------------------------- 1 | numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 | 3 | copy = numbers[0...numbers.length] 4 | 5 | middle = copy[3..6] 6 | 7 | 8 | -------------------------------------------------------------------------------- /documentation/coffee/try.coffee: -------------------------------------------------------------------------------- 1 | try 2 | allHellBreaksLoose() 3 | catsAndDogsLivingTogether() 4 | catch error 5 | print error 6 | finally 7 | cleanUp() 8 | 9 | -------------------------------------------------------------------------------- /documentation/js/splices.js: -------------------------------------------------------------------------------- 1 | var numbers, _ref; 2 | numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 3 | [].splice.apply(numbers, [3, 4].concat(_ref = [-3, -4, -5, -6])), _ref; -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.coffee 2 | *.html 3 | .DS_Store 4 | .git* 5 | Cakefile 6 | documentation/ 7 | examples/ 8 | extras/coffee-script.js 9 | raw/ 10 | src/ 11 | test/ 12 | -------------------------------------------------------------------------------- /documentation/coffee/do.coffee: -------------------------------------------------------------------------------- 1 | for filename in list 2 | do (filename) -> 3 | fs.readFile filename, (err, contents) -> 4 | compile filename, contents.toString() -------------------------------------------------------------------------------- /documentation/js/soaks.js: -------------------------------------------------------------------------------- 1 | var zip, _ref; 2 | zip = typeof lottery.drawWinner === "function" ? (_ref = lottery.drawWinner().address) != null ? _ref.zipcode : void 0 : void 0; -------------------------------------------------------------------------------- /documentation/js/try.js: -------------------------------------------------------------------------------- 1 | try { 2 | allHellBreaksLoose(); 3 | catsAndDogsLivingTogether(); 4 | } catch (error) { 5 | print(error); 6 | } finally { 7 | cleanUp(); 8 | } -------------------------------------------------------------------------------- /documentation/coffee/existence.coffee: -------------------------------------------------------------------------------- 1 | solipsism = true if mind? and not world? 2 | 3 | speed ?= 75 4 | 5 | footprints = yeti ? "bear" 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /documentation/js/slices.js: -------------------------------------------------------------------------------- 1 | var copy, middle, numbers; 2 | numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 3 | copy = numbers.slice(0, numbers.length); 4 | middle = copy.slice(3, 7); -------------------------------------------------------------------------------- /documentation/js/parallel_assignment.js: -------------------------------------------------------------------------------- 1 | var theBait, theSwitch, _ref; 2 | theBait = 1000; 3 | theSwitch = 0; 4 | _ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1]; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | raw 2 | presentation 3 | test.coffee 4 | parser.output 5 | test/fixtures/underscore 6 | test/*.js 7 | examples/beautiful_code/parse.coffee 8 | *.gem 9 | /node_modules 10 | -------------------------------------------------------------------------------- /documentation/coffee/interpolation.coffee: -------------------------------------------------------------------------------- 1 | author = "Wittgenstein" 2 | quote = "A picture is a fact. -- #{ author }" 3 | 4 | sentence = "#{ 22 / 7 } is a decent approximation of π" 5 | 6 | 7 | -------------------------------------------------------------------------------- /documentation/js/expressions_try.js: -------------------------------------------------------------------------------- 1 | alert((function() { 2 | try { 3 | return nonexistent / void 0; 4 | } catch (error) { 5 | return "And the error is ... " + error; 6 | } 7 | })()); -------------------------------------------------------------------------------- /documentation/coffee/fat_arrow.coffee: -------------------------------------------------------------------------------- 1 | Account = (customer, cart) -> 2 | @customer = customer 3 | @cart = cart 4 | 5 | $('.shopping_cart').bind 'click', (event) => 6 | @customer.purchase @cart -------------------------------------------------------------------------------- /documentation/js/scope.js: -------------------------------------------------------------------------------- 1 | var changeNumbers, inner, outer; 2 | outer = 1; 3 | changeNumbers = function() { 4 | var inner; 5 | inner = -1; 6 | return outer = 10; 7 | }; 8 | inner = changeNumbers(); -------------------------------------------------------------------------------- /documentation/js/array_comprehensions.js: -------------------------------------------------------------------------------- 1 | var food, _i, _len, _ref; 2 | _ref = ['toast', 'cheese', 'wine']; 3 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 4 | food = _ref[_i]; 5 | eat(food); 6 | } -------------------------------------------------------------------------------- /documentation/js/interpolation.js: -------------------------------------------------------------------------------- 1 | var author, quote, sentence; 2 | author = "Wittgenstein"; 3 | quote = "A picture is a fact. -- " + author; 4 | sentence = "" + (22 / 7) + " is a decent approximation of π"; -------------------------------------------------------------------------------- /lib/coffee-script/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var key, val, _ref; 3 | _ref = require('./coffee-script'); 4 | for (key in _ref) { 5 | val = _ref[key]; 6 | exports[key] = val; 7 | } 8 | }).call(this); 9 | -------------------------------------------------------------------------------- /documentation/js/default_args.js: -------------------------------------------------------------------------------- 1 | var fill; 2 | fill = function(container, liquid) { 3 | if (liquid == null) { 4 | liquid = "coffee"; 5 | } 6 | return "Filling the " + container + " with " + liquid + "..."; 7 | }; -------------------------------------------------------------------------------- /examples/computer_science/README: -------------------------------------------------------------------------------- 1 | Ported from Nicholas Zakas' collection of computer science fundamentals, written 2 | in JavaScript. Originals available here: 3 | 4 | http://github.com/nzakas/computer-science-in-javascript 5 | -------------------------------------------------------------------------------- /bin/cake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); 6 | 7 | require(lib + '/coffee-script/cake').run(); 8 | -------------------------------------------------------------------------------- /bin/coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); 6 | 7 | require(lib + '/coffee-script/command').run(); 8 | -------------------------------------------------------------------------------- /documentation/js/range_comprehensions.js: -------------------------------------------------------------------------------- 1 | var countdown, num; 2 | countdown = (function() { 3 | var _results; 4 | _results = []; 5 | for (num = 10; num >= 1; num--) { 6 | _results.push(num); 7 | } 8 | return _results; 9 | })(); -------------------------------------------------------------------------------- /documentation/coffee/multiple_return_values.coffee: -------------------------------------------------------------------------------- 1 | weatherReport = (location) -> 2 | # Make an Ajax request to fetch the weather... 3 | [location, 72, "Mostly Sunny"] 4 | 5 | [city, temp, forecast] = weatherReport "Berkeley, CA" 6 | 7 | 8 | -------------------------------------------------------------------------------- /documentation/js/expressions_comprehension.js: -------------------------------------------------------------------------------- 1 | var globals, name; 2 | globals = ((function() { 3 | var _results; 4 | _results = []; 5 | for (name in window) { 6 | _results.push(name); 7 | } 8 | return _results; 9 | })()).slice(0, 10); -------------------------------------------------------------------------------- /documentation/coffee/expressions.coffee: -------------------------------------------------------------------------------- 1 | grade = (student) -> 2 | if student.excellentWork 3 | "A+" 4 | else if student.okayStuff 5 | if student.triedHard then "B" else "B-" 6 | else 7 | "C" 8 | 9 | eldest = if 24 > 21 then "Liz" else "Ike" -------------------------------------------------------------------------------- /documentation/coffee/conditionals.coffee: -------------------------------------------------------------------------------- 1 | mood = greatlyImproved if singing 2 | 3 | if happy and knowsIt 4 | clapsHands() 5 | chaChaCha() 6 | else 7 | showIt() 8 | 9 | date = if friday then sue else jill 10 | 11 | options or= defaults 12 | 13 | 14 | -------------------------------------------------------------------------------- /documentation/js/multiple_return_values.js: -------------------------------------------------------------------------------- 1 | var city, forecast, temp, weatherReport, _ref; 2 | weatherReport = function(location) { 3 | return [location, 72, "Mostly Sunny"]; 4 | }; 5 | _ref = weatherReport("Berkeley, CA"), city = _ref[0], temp = _ref[1], forecast = _ref[2]; -------------------------------------------------------------------------------- /documentation/js/conditionals.js: -------------------------------------------------------------------------------- 1 | var date, mood; 2 | if (singing) { 3 | mood = greatlyImproved; 4 | } 5 | if (happy && knowsIt) { 6 | clapsHands(); 7 | chaChaCha(); 8 | } else { 9 | showIt(); 10 | } 11 | date = friday ? sue : jill; 12 | options || (options = defaults); -------------------------------------------------------------------------------- /documentation/coffee/switch.coffee: -------------------------------------------------------------------------------- 1 | switch day 2 | when "Mon" then go work 3 | when "Tue" then go relax 4 | when "Thu" then go iceFishing 5 | when "Fri", "Sat" 6 | if day is bingoDay 7 | go bingo 8 | go dancing 9 | when "Sun" then go church 10 | else go work -------------------------------------------------------------------------------- /documentation/js/strings.js: -------------------------------------------------------------------------------- 1 | var mobyDick; 2 | mobyDick = "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..."; -------------------------------------------------------------------------------- /test/javascript_literals.coffee: -------------------------------------------------------------------------------- 1 | # Javascript Literals 2 | # ------------------- 3 | 4 | # TODO: refactor javascript literal tests 5 | # TODO: add indexing and method invocation tests: `[1]`[0] is 1, `function(){}`.call() 6 | 7 | eq '\\`', ` 8 | // Inline JS 9 | "\\\`" 10 | ` 11 | -------------------------------------------------------------------------------- /documentation/js/patterns_and_splats.js: -------------------------------------------------------------------------------- 1 | var close, contents, open, tag, _i, _ref; 2 | var __slice = Array.prototype.slice; 3 | tag = ""; 4 | _ref = tag.split(""), open = _ref[0], contents = 3 <= _ref.length ? __slice.call(_ref, 1, _i = _ref.length - 1) : (_i = 1, []), close = _ref[_i++]; -------------------------------------------------------------------------------- /documentation/coffee/aliases.coffee: -------------------------------------------------------------------------------- 1 | launch() if ignition is on 2 | 3 | volume = 10 if band isnt SpinalTap 4 | 5 | letTheWildRumpusBegin() unless answer is no 6 | 7 | if car.speed < limit then accelerate() 8 | 9 | winner = yes if pick in [47, 92, 13] 10 | 11 | print inspect "My name is #{@name}" 12 | -------------------------------------------------------------------------------- /documentation/coffee/while.coffee: -------------------------------------------------------------------------------- 1 | # Econ 101 2 | if this.studyingEconomics 3 | buy() while supply > demand 4 | sell() until supply > demand 5 | 6 | # Nursery Rhyme 7 | num = 6 8 | lyrics = while num -= 1 9 | "#{num} little monkeys, jumping on the bed. 10 | One fell out and bumped his head." 11 | -------------------------------------------------------------------------------- /documentation/coffee/strings.coffee: -------------------------------------------------------------------------------- 1 | mobyDick = "Call me Ishmael. Some years ago -- 2 | never mind how long precisely -- having little 3 | or no money in my purse, and nothing particular 4 | to interest me on shore, I thought I would sail 5 | about a little and see the watery part of the 6 | world..." 7 | 8 | 9 | -------------------------------------------------------------------------------- /documentation/js/do.js: -------------------------------------------------------------------------------- 1 | var filename, _fn, _i, _len; 2 | _fn = function(filename) { 3 | return fs.readFile(filename, function(err, contents) { 4 | return compile(filename, contents.toString()); 5 | }); 6 | }; 7 | for (_i = 0, _len = list.length; _i < _len; _i++) { 8 | filename = list[_i]; 9 | _fn(filename); 10 | } -------------------------------------------------------------------------------- /documentation/coffee/object_extraction.coffee: -------------------------------------------------------------------------------- 1 | futurists = 2 | sculptor: "Umberto Boccioni" 3 | painter: "Vladimir Burliuk" 4 | poet: 5 | name: "F.T. Marinetti" 6 | address: [ 7 | "Via Roma 42R" 8 | "Bellagio, Italy 22021" 9 | ] 10 | 11 | {poet: {name, address: [street, city]}} = futurists 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/web_server.coffee: -------------------------------------------------------------------------------- 1 | # Contributed by Jason Huggins 2 | 3 | http = require 'http' 4 | 5 | server = http.createServer (req, res) -> 6 | res.writeHeader 200, 'Content-Type': 'text/plain' 7 | res.write 'Hello, World!' 8 | res.end() 9 | 10 | server.listen 3000 11 | 12 | console.log "Server running at http://localhost:3000/" 13 | -------------------------------------------------------------------------------- /documentation/coffee/cake_tasks.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | option '-o', '--output [DIR]', 'directory for compiled code' 4 | 5 | task 'build:parser', 'rebuild the Jison parser', (options) -> 6 | require 'jison' 7 | code = require('./lib/grammar').parser.generate() 8 | dir = options.output or 'lib' 9 | fs.writeFile "#{dir}/parser.js", code -------------------------------------------------------------------------------- /documentation/coffee/objects_and_arrays.coffee: -------------------------------------------------------------------------------- 1 | song = ["do", "re", "mi", "fa", "so"] 2 | 3 | singers = {Jagger: "Rock", Elvis: "Roll"} 4 | 5 | bitlist = [ 6 | 1, 0, 1 7 | 0, 0, 1 8 | 1, 1, 0 9 | ] 10 | 11 | kids = 12 | brother: 13 | name: "Max" 14 | age: 11 15 | sister: 16 | name: "Ida" 17 | age: 9 18 | 19 | 20 | -------------------------------------------------------------------------------- /documentation/js/existence.js: -------------------------------------------------------------------------------- 1 | var footprints, solipsism; 2 | if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) { 3 | solipsism = true; 4 | } 5 | if (typeof speed === "undefined" || speed === null) { 6 | speed = 75; 7 | } 8 | footprints = typeof yeti !== "undefined" && yeti !== null ? yeti : "bear"; -------------------------------------------------------------------------------- /documentation/js/fat_arrow.js: -------------------------------------------------------------------------------- 1 | var Account; 2 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 3 | Account = function(customer, cart) { 4 | this.customer = customer; 5 | this.cart = cart; 6 | return $('.shopping_cart').bind('click', __bind(function(event) { 7 | return this.customer.purchase(this.cart); 8 | }, this)); 9 | }; -------------------------------------------------------------------------------- /documentation/js/object_comprehensions.js: -------------------------------------------------------------------------------- 1 | var age, ages, child, yearsOld; 2 | yearsOld = { 3 | max: 10, 4 | ida: 9, 5 | tim: 11 6 | }; 7 | ages = (function() { 8 | var _results; 9 | _results = []; 10 | for (child in yearsOld) { 11 | age = yearsOld[child]; 12 | _results.push("" + child + " is " + age); 13 | } 14 | return _results; 15 | })(); -------------------------------------------------------------------------------- /documentation/js/expressions.js: -------------------------------------------------------------------------------- 1 | var eldest, grade; 2 | grade = function(student) { 3 | if (student.excellentWork) { 4 | return "A+"; 5 | } else if (student.okayStuff) { 6 | if (student.triedHard) { 7 | return "B"; 8 | } else { 9 | return "B-"; 10 | } 11 | } else { 12 | return "C"; 13 | } 14 | }; 15 | eldest = 24 > 21 ? "Liz" : "Ike"; -------------------------------------------------------------------------------- /documentation/js/objects_and_arrays.js: -------------------------------------------------------------------------------- 1 | var bitlist, kids, singers, song; 2 | song = ["do", "re", "mi", "fa", "so"]; 3 | singers = { 4 | Jagger: "Rock", 5 | Elvis: "Roll" 6 | }; 7 | bitlist = [1, 0, 1, 0, 0, 1, 1, 1, 0]; 8 | kids = { 9 | brother: { 10 | name: "Max", 11 | age: 11 12 | }, 13 | sister: { 14 | name: "Ida", 15 | age: 9 16 | } 17 | }; -------------------------------------------------------------------------------- /documentation/coffee/array_comprehensions.coffee: -------------------------------------------------------------------------------- 1 | # Eat lunch. 2 | eat food for food in ['toast', 'cheese', 'wine'] 3 | 4 | # Fine dining 5 | courses = ['salad', 'entree', 'dessert'] 6 | menu course + 1, dish for dish, course in courses 7 | 8 | # Health conscious meal 9 | foods = ['broccoli', 'spinach', 'chocolate'] 10 | eat food for food in foods when food isnt 'chocolate' 11 | -------------------------------------------------------------------------------- /documentation/coffee/heregexes.coffee: -------------------------------------------------------------------------------- 1 | OPERATOR = /// ^ ( 2 | ?: [-=]> # function 3 | | [-+*/%<>&|^!?=]= # compound assign / compare 4 | | >>>=? # zero-fill right shift 5 | | ([-+:])\1 # doubles 6 | | ([&|<>])\2=? # logic / shift 7 | | \?\. # soak access 8 | | \.{2,3} # range or splat 9 | ) /// 10 | 11 | 12 | -------------------------------------------------------------------------------- /documentation/js/object_extraction.js: -------------------------------------------------------------------------------- 1 | var city, futurists, name, street, _ref, _ref2; 2 | futurists = { 3 | sculptor: "Umberto Boccioni", 4 | painter: "Vladimir Burliuk", 5 | poet: { 6 | name: "F.T. Marinetti", 7 | address: ["Via Roma 42R", "Bellagio, Italy 22021"] 8 | } 9 | }; 10 | _ref = futurists.poet, name = _ref.name, _ref2 = _ref.address, street = _ref2[0], city = _ref2[1]; -------------------------------------------------------------------------------- /documentation/js/aliases.js: -------------------------------------------------------------------------------- 1 | var volume, winner; 2 | if (ignition === true) { 3 | launch(); 4 | } 5 | if (band !== SpinalTap) { 6 | volume = 10; 7 | } 8 | if (answer !== false) { 9 | letTheWildRumpusBegin(); 10 | } 11 | if (car.speed < limit) { 12 | accelerate(); 13 | } 14 | if (pick === 47 || pick === 92 || pick === 13) { 15 | winner = true; 16 | } 17 | print(inspect("My name is " + this.name)); -------------------------------------------------------------------------------- /documentation/js/cake_tasks.js: -------------------------------------------------------------------------------- 1 | var fs; 2 | fs = require('fs'); 3 | option('-o', '--output [DIR]', 'directory for compiled code'); 4 | task('build:parser', 'rebuild the Jison parser', function(options) { 5 | var code, dir; 6 | require('jison'); 7 | code = require('./lib/grammar').parser.generate(); 8 | dir = options.output || 'lib'; 9 | return fs.writeFile("" + dir + "/parser.js", code); 10 | }); -------------------------------------------------------------------------------- /examples/beautiful_code/quicksort_runtime.coffee: -------------------------------------------------------------------------------- 1 | # Beautiful Code, Chapter 3. 2 | # Produces the expected runtime of Quicksort, for every integer from 1 to N. 3 | 4 | runtime = (N) -> 5 | [sum, t] = [0, 0] 6 | for n in [1..N] 7 | sum += 2 * t 8 | t = n - 1 + sum / n 9 | t 10 | 11 | console.log runtime(3) is 2.6666666666666665 12 | console.log runtime(5) is 7.4 13 | console.log runtime(8) is 16.92142857142857 14 | -------------------------------------------------------------------------------- /documentation/js/switch.js: -------------------------------------------------------------------------------- 1 | switch (day) { 2 | case "Mon": 3 | go(work); 4 | break; 5 | case "Tue": 6 | go(relax); 7 | break; 8 | case "Thu": 9 | go(iceFishing); 10 | break; 11 | case "Fri": 12 | case "Sat": 13 | if (day === bingoDay) { 14 | go(bingo); 15 | go(dancing); 16 | } 17 | break; 18 | case "Sun": 19 | go(church); 20 | break; 21 | default: 22 | go(work); 23 | } -------------------------------------------------------------------------------- /documentation/js/while.js: -------------------------------------------------------------------------------- 1 | var lyrics, num; 2 | if (this.studyingEconomics) { 3 | while (supply > demand) { 4 | buy(); 5 | } 6 | while (!(supply > demand)) { 7 | sell(); 8 | } 9 | } 10 | num = 6; 11 | lyrics = (function() { 12 | var _results; 13 | _results = []; 14 | while (num -= 1) { 15 | _results.push("" + num + " little monkeys, jumping on the bed. One fell out and bumped his head."); 16 | } 17 | return _results; 18 | })(); -------------------------------------------------------------------------------- /examples/computer_science/bubble_sort.coffee: -------------------------------------------------------------------------------- 1 | # A bubble sort implementation, sorting the given array in-place. 2 | bubble_sort = (list) -> 3 | for i in [0...list.length] 4 | for j in [0...list.length - i] 5 | [list[j], list[j+1]] = [list[j+1], list[j]] if list[j] > list[j+1] 6 | list 7 | 8 | 9 | # Test the function. 10 | console.log bubble_sort([3, 2, 1]).join(' ') is '1 2 3' 11 | console.log bubble_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9' -------------------------------------------------------------------------------- /documentation/coffee/classes.coffee: -------------------------------------------------------------------------------- 1 | class Animal 2 | constructor: (@name) -> 3 | 4 | move: (meters) -> 5 | alert @name + " moved #{meters}m." 6 | 7 | class Snake extends Animal 8 | move: -> 9 | alert "Slithering..." 10 | super 5 11 | 12 | class Horse extends Animal 13 | move: -> 14 | alert "Galloping..." 15 | super 45 16 | 17 | sam = new Snake "Sammy the Python" 18 | tom = new Horse "Tommy the Palomino" 19 | 20 | sam.move() 21 | tom.move() 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/importing.coffee: -------------------------------------------------------------------------------- 1 | # Importing 2 | # --------- 3 | 4 | unless window? or testingBrowser? 5 | test "coffeescript modules can be imported and executed", -> 6 | 7 | magicKey = __filename 8 | magicValue = 0xFFFF 9 | 10 | if global[magicKey]? 11 | if exports? 12 | local = magicValue 13 | exports.method = -> local 14 | else 15 | global[magicKey] = {} 16 | if require?.extensions? or require?.registerExtension? 17 | ok require(__filename).method() is magicValue 18 | delete global[magicKey] 19 | -------------------------------------------------------------------------------- /documentation/coffee/splats.coffee: -------------------------------------------------------------------------------- 1 | gold = silver = rest = "unknown" 2 | 3 | awardMedals = (first, second, others...) -> 4 | gold = first 5 | silver = second 6 | rest = others 7 | 8 | contenders = [ 9 | "Michael Phelps" 10 | "Liu Xiang" 11 | "Yao Ming" 12 | "Allyson Felix" 13 | "Shawn Johnson" 14 | "Roman Sebrle" 15 | "Guo Jingjing" 16 | "Tyson Gay" 17 | "Asafa Powell" 18 | "Usain Bolt" 19 | ] 20 | 21 | awardMedals contenders... 22 | 23 | alert "Gold: " + gold 24 | alert "Silver: " + silver 25 | alert "The Field: " + rest 26 | 27 | 28 | -------------------------------------------------------------------------------- /documentation/coffee/overview.coffee: -------------------------------------------------------------------------------- 1 | # Assignment: 2 | number = 42 3 | opposite = true 4 | 5 | # Conditions: 6 | number = -42 if opposite 7 | 8 | # Functions: 9 | square = (x) -> x * x 10 | 11 | # Arrays: 12 | list = [1, 2, 3, 4, 5] 13 | 14 | # Objects: 15 | math = 16 | root: Math.sqrt 17 | square: square 18 | cube: (x) -> x * square x 19 | 20 | # Splats: 21 | race = (winner, runners...) -> 22 | print winner, runners 23 | 24 | # Existence: 25 | alert "I knew it!" if elvis? 26 | 27 | # Array comprehensions: 28 | cubes = (math.cube num for num in list) 29 | -------------------------------------------------------------------------------- /examples/beautiful_code/binary_search.coffee: -------------------------------------------------------------------------------- 1 | # Beautiful Code, Chapter 6. 2 | # The implementation of binary search that is tested. 3 | 4 | # Return the index of an element in a sorted list. (or -1, if not present) 5 | index = (list, target) -> 6 | [low, high] = [0, list.length] 7 | while low < high 8 | mid = (low + high) >> 1 9 | val = list[mid] 10 | return mid if val is target 11 | if val < target then low = mid + 1 else high = mid 12 | return -1 13 | 14 | console.log 2 is index [10, 20, 30, 40, 50], 30 15 | console.log 4 is index [-97, 35, 67, 88, 1200], 1200 16 | console.log 0 is index [0, 45, 70], 0 -------------------------------------------------------------------------------- /test/booleans.coffee: -------------------------------------------------------------------------------- 1 | # Boolean Literals 2 | # ---------------- 3 | 4 | # TODO: add method invocation tests: true.toString() is "true" 5 | 6 | test "#764 Booleans should be indexable", -> 7 | toString = Boolean::toString 8 | 9 | eq toString, true['toString'] 10 | eq toString, false['toString'] 11 | eq toString, yes['toString'] 12 | eq toString, no['toString'] 13 | eq toString, on['toString'] 14 | eq toString, off['toString'] 15 | 16 | eq toString, true.toString 17 | eq toString, false.toString 18 | eq toString, yes.toString 19 | eq toString, no.toString 20 | eq toString, on.toString 21 | eq toString, off.toString 22 | -------------------------------------------------------------------------------- /examples/computer_science/merge_sort.coffee: -------------------------------------------------------------------------------- 1 | # Sorts an array in ascending natural order using merge sort. 2 | merge_sort = (list) -> 3 | 4 | return list if list.length is 1 5 | 6 | result = [] 7 | pivot = Math.floor list.length / 2 8 | left = merge_sort list.slice 0, pivot 9 | right = merge_sort list.slice pivot 10 | 11 | while left.length and right.length 12 | result.push(if left[0] < right[0] then left.shift() else right.shift()) 13 | 14 | result.concat(left).concat(right) 15 | 16 | 17 | # Test the function. 18 | console.log merge_sort([3, 2, 1]).join(' ') is '1 2 3' 19 | console.log merge_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9' -------------------------------------------------------------------------------- /documentation/js/splats.js: -------------------------------------------------------------------------------- 1 | var awardMedals, contenders, gold, rest, silver; 2 | var __slice = Array.prototype.slice; 3 | gold = silver = rest = "unknown"; 4 | awardMedals = function() { 5 | var first, others, second; 6 | first = arguments[0], second = arguments[1], others = 3 <= arguments.length ? __slice.call(arguments, 2) : []; 7 | gold = first; 8 | silver = second; 9 | return rest = others; 10 | }; 11 | contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"]; 12 | awardMedals.apply(null, contenders); 13 | alert("Gold: " + gold); 14 | alert("Silver: " + silver); 15 | alert("The Field: " + rest); -------------------------------------------------------------------------------- /examples/computer_science/selection_sort.coffee: -------------------------------------------------------------------------------- 1 | # An in-place selection sort. 2 | selection_sort = (list) -> 3 | len = list.length 4 | 5 | # For each item in the list. 6 | for i in [0...len] 7 | 8 | # Set the minimum to this position. 9 | min = i 10 | 11 | # Check the rest of the array to see if anything is smaller. 12 | min = k for v, k in list[i+1...] when v < list[min] 13 | 14 | # Swap if a smaller item has been found. 15 | [list[i], list[min]] = [list[min], list[i]] if i isnt min 16 | 17 | # The list is now sorted. 18 | list 19 | 20 | 21 | # Test the function. 22 | console.log selection_sort([3, 2, 1]).join(' ') is '1 2 3' 23 | console.log selection_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9' -------------------------------------------------------------------------------- /examples/computer_science/binary_search.coffee: -------------------------------------------------------------------------------- 1 | # Uses a binary search algorithm to locate a value in the specified array. 2 | binary_search = (items, value) -> 3 | 4 | start = 0 5 | stop = items.length - 1 6 | pivot = Math.floor (start + stop) / 2 7 | 8 | while items[pivot] isnt value and start < stop 9 | 10 | # Adjust the search area. 11 | stop = pivot - 1 if value < items[pivot] 12 | start = pivot + 1 if value > items[pivot] 13 | 14 | # Recalculate the pivot. 15 | pivot = Math.floor (stop + start) / 2 16 | 17 | # Make sure we've found the correct value. 18 | if items[pivot] is value then pivot else -1 19 | 20 | 21 | # Test the function. 22 | console.log 2 is binary_search [10, 20, 30, 40, 50], 30 23 | console.log 4 is binary_search [-97, 35, 67, 88, 1200], 1200 24 | console.log 0 is binary_search [0, 45, 70], 0 25 | console.log(-1 is binary_search [0, 45, 70], 10) -------------------------------------------------------------------------------- /test/scope.coffee: -------------------------------------------------------------------------------- 1 | # Scope 2 | # ----- 3 | 4 | # * Variable Safety 5 | # * Variable Shadowing 6 | # * Auto-closure (`do`) 7 | # * Global Scope Leaks 8 | 9 | test "reference `arguments` inside of functions", -> 10 | sumOfArgs = -> 11 | sum = (a,b) -> a + b 12 | sum = 0 13 | sum += num for num in arguments 14 | sum 15 | eq 10, sumOfArgs(0, 1, 2, 3, 4) 16 | 17 | test "assignment to an Object.prototype-named variable should not leak to outer scope", -> 18 | # FIXME: fails on IE 19 | (-> 20 | constructor = 'word' 21 | )() 22 | ok constructor isnt 'word' 23 | 24 | test "siblings of splat parameters shouldn't leak to surrounding scope", -> 25 | x = 10 26 | oops = (x, args...) -> 27 | oops(20, 1, 2, 3) 28 | eq x, 10 29 | 30 | test "catch statements should introduce their argument to scope", -> 31 | try throw '' 32 | catch e 33 | do -> e = 5 34 | eq 5, e 35 | -------------------------------------------------------------------------------- /documentation/js/overview.js: -------------------------------------------------------------------------------- 1 | var cubes, list, math, num, number, opposite, race, square; 2 | var __slice = Array.prototype.slice; 3 | number = 42; 4 | opposite = true; 5 | if (opposite) { 6 | number = -42; 7 | } 8 | square = function(x) { 9 | return x * x; 10 | }; 11 | list = [1, 2, 3, 4, 5]; 12 | math = { 13 | root: Math.sqrt, 14 | square: square, 15 | cube: function(x) { 16 | return x * square(x); 17 | } 18 | }; 19 | race = function() { 20 | var runners, winner; 21 | winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 22 | return print(winner, runners); 23 | }; 24 | if (typeof elvis !== "undefined" && elvis !== null) { 25 | alert("I knew it!"); 26 | } 27 | cubes = (function() { 28 | var _i, _len, _results; 29 | _results = []; 30 | for (_i = 0, _len = list.length; _i < _len; _i++) { 31 | num = list[_i]; 32 | _results.push(math.cube(num)); 33 | } 34 | return _results; 35 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffee-script", 3 | "description": "Unfancy JavaScript", 4 | "keywords": ["javascript", "language", "coffeescript", "compiler"], 5 | "author": "Jeremy Ashkenas", 6 | "version": "1.1.3-pre", 7 | "licenses": [{ 8 | "type": "MIT", 9 | "url": "http://github.com/jashkenas/coffee-script/raw/master/LICENSE" 10 | }], 11 | "engines": { 12 | "node": ">=0.4.0" 13 | }, 14 | "directories" : { 15 | "lib" : "./lib/coffee-script" 16 | }, 17 | "main" : "./lib/coffee-script/coffee-script", 18 | "bin": { 19 | "coffee": "./bin/coffee", 20 | "cake": "./bin/cake" 21 | }, 22 | "homepage": "http://coffeescript.org", 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/jashkenas/coffee-script.git" 26 | }, 27 | "devDependencies": { 28 | "uglify-js": "1.0.6", 29 | "jison": "0.2.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/option_parser.coffee: -------------------------------------------------------------------------------- 1 | # Option Parser 2 | # ------------- 3 | 4 | # TODO: refactor option parser tests 5 | 6 | # Ensure that the OptionParser handles arguments correctly. 7 | return unless require? 8 | {OptionParser} = require './../lib/coffee-script/optparse' 9 | 10 | opt = new OptionParser [ 11 | ['-r', '--required [DIR]', 'desc required'] 12 | ['-o', '--optional', 'desc optional'] 13 | ['-l', '--list [FILES*]', 'desc list'] 14 | ] 15 | 16 | result = opt.parse ['one', 'two', 'three', '-r', 'dir'] 17 | 18 | ok result.arguments.length is 5 19 | ok result.arguments[3] is '-r' 20 | 21 | result = opt.parse ['--optional', '-r', 'folder', 'one', 'two'] 22 | 23 | ok result.optional is true 24 | ok result.required is 'folder' 25 | ok result.arguments.join(' ') is 'one two' 26 | 27 | result = opt.parse ['-l', 'one.txt', '-l', 'two.txt', 'three'] 28 | 29 | ok result.list instanceof Array 30 | ok result.list.join(' ') is 'one.txt two.txt' 31 | ok result.arguments.join(' ') is 'three' 32 | 33 | -------------------------------------------------------------------------------- /examples/computer_science/luhn_algorithm.coffee: -------------------------------------------------------------------------------- 1 | # Use the Luhn algorithm to validate a numeric identifier, such as credit card 2 | # numbers, national insurance numbers, etc. 3 | # See: http://en.wikipedia.org/wiki/Luhn_algorithm 4 | 5 | is_valid_identifier = (identifier) -> 6 | 7 | sum = 0 8 | alt = false 9 | 10 | for i in [identifier.length - 1..0] by -1 11 | 12 | # Get the next digit. 13 | num = parseInt identifier.charAt(i), 10 14 | 15 | # If it's not a valid number, abort. 16 | return false if isNaN(num) 17 | 18 | # If it's an alternate number... 19 | if alt 20 | num *= 2 21 | num = (num % 10) + 1 if num > 9 22 | 23 | # Flip the alternate bit. 24 | alt = !alt 25 | 26 | # Add to the rest of the sum. 27 | sum += num 28 | 29 | # Determine if it's valid. 30 | sum % 10 is 0 31 | 32 | 33 | # Tests. 34 | console.log is_valid_identifier("49927398716") is true 35 | console.log is_valid_identifier("4408041234567893") is true 36 | console.log is_valid_identifier("4408041234567890") is false 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jeremy Ashkenas 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/numbers.coffee: -------------------------------------------------------------------------------- 1 | # Number Literals 2 | # --------------- 3 | 4 | # * Decimal Integer Literals 5 | # * Octal Integer Literals 6 | # * Hexadecimal Integer Literals 7 | # * Scientific Notation Integer Literals 8 | # * Scientific Notation Non-Integer Literals 9 | # * Non-Integer Literals 10 | 11 | 12 | # Decimal Integer Literals 13 | 14 | test "call methods directly on numbers", -> 15 | eq 4, 4.valueOf() 16 | eq '11', 4.toString 3 17 | 18 | eq -1, 3 -4 19 | 20 | #764: Numbers should be indexable 21 | eq Number::toString, 42['toString'] 22 | 23 | eq Number::toString, 42.toString 24 | 25 | 26 | # Non-Integer Literals 27 | 28 | # Decimal number literals. 29 | value = .25 + .75 30 | ok value is 1 31 | value = 0.0 + -.25 - -.75 + 0.0 32 | ok value is 0.5 33 | 34 | #764: Numbers should be indexable 35 | eq Number::toString, 4['toString'] 36 | eq Number::toString, 4.2['toString'] 37 | eq Number::toString, .42['toString'] 38 | eq Number::toString, (4)['toString'] 39 | 40 | eq Number::toString, 4.toString 41 | eq Number::toString, 4.2.toString 42 | eq Number::toString, .42.toString 43 | eq Number::toString, (4).toString 44 | 45 | test '#1168: leading floating point suppresses newline', -> 46 | eq 1, do -> 47 | 1 48 | .5 + 0.5 49 | -------------------------------------------------------------------------------- /test/regexps.coffee: -------------------------------------------------------------------------------- 1 | # Regular Expression Literals 2 | # --------------------------- 3 | 4 | # TODO: add method invocation tests: /regex/.toString() 5 | 6 | # * Regexen 7 | # * Heregexen 8 | 9 | test "basic regular expression literals", -> 10 | ok 'a'.match(/a/) 11 | ok 'a'.match /a/ 12 | ok 'a'.match(/a/g) 13 | ok 'a'.match /a/g 14 | 15 | test "division is not confused for a regular expression", -> 16 | eq 2, 4 / 2 / 1 17 | 18 | a = 4 19 | b = 2 20 | g = 1 21 | eq 2, a / b/g 22 | 23 | a = 10 24 | b = a /= 4 / 2 25 | eq a, 5 26 | 27 | obj = method: -> 2 28 | two = 2 29 | eq 2, (obj.method()/two + obj.method()/two) 30 | 31 | i = 1 32 | eq 2, (4)/2/i 33 | eq 1, i/i/i 34 | 35 | test "#764: regular expressions should be indexable", -> 36 | eq /0/['source'], ///#{0}///['source'] 37 | 38 | test "#584: slashes are allowed unescaped in character classes", -> 39 | ok /^a\/[/]b$/.test 'a//b' 40 | 41 | 42 | # Heregexe(n|s) 43 | 44 | test "a heregex will ignore whitespace and comments", -> 45 | eq /^I'm\x20+[a]\s+Heregex?\/\/\//gim + '', /// 46 | ^ I'm \x20+ [a] \s+ 47 | Heregex? / // # or not 48 | ///gim + '' 49 | 50 | test "an empty heregex will compile to an empty, non-capturing group", -> 51 | eq /(?:)/ + '', /// /// + '' 52 | -------------------------------------------------------------------------------- /documentation/css/idle.css: -------------------------------------------------------------------------------- 1 | pre.idle .InheritedClass { 2 | } 3 | pre.idle .TypeName { 4 | color: #21439C; 5 | } 6 | pre.idle .Number { 7 | } 8 | pre.idle .LibraryVariable { 9 | color: #A535AE; 10 | } 11 | pre.idle .Storage { 12 | color: #FF5600; 13 | } 14 | pre.idle .line-numbers { 15 | background-color: #BAD6FD; 16 | color: #000000; 17 | } 18 | pre.idle { 19 | background-color: #FFFFFF; 20 | color: #000000; 21 | } 22 | pre.idle .StringInterpolation { 23 | color: #990000; 24 | } 25 | pre.idle .TagName { 26 | } 27 | pre.idle .LibraryConstant { 28 | color: #A535AE; 29 | } 30 | pre.idle .FunctionArgument { 31 | color: #0076ad; 32 | } 33 | pre.idle .BuiltInConstant { 34 | color: #A535AE; 35 | } 36 | pre.idle .Invalid { 37 | background-color: #990000; 38 | color: #FFFFFF; 39 | } 40 | pre.idle .LibraryClassType { 41 | color: #A535AE; 42 | } 43 | pre.idle .LibraryFunction { 44 | color: #A535AE; 45 | } 46 | pre.idle .TagAttribute { 47 | } 48 | pre.idle .Keyword { 49 | color: #FF5600; 50 | } 51 | pre.idle .UserDefinedConstant { 52 | } 53 | pre.idle .String { 54 | color: #00A33F; 55 | } 56 | pre.idle .FunctionName { 57 | color: #21439C; 58 | } 59 | pre.idle .Variable { 60 | color: #A535AE; 61 | } 62 | pre.idle .Comment { 63 | color: #919191; 64 | } 65 | -------------------------------------------------------------------------------- /examples/blocks.coffee: -------------------------------------------------------------------------------- 1 | # After wycats' http://yehudakatz.com/2010/02/07/the-building-blocks-of-ruby/ 2 | 3 | # Sinatra. 4 | get '/hello', -> 5 | 'Hello World' 6 | 7 | 8 | # Append. 9 | append = (location, data) -> 10 | path = new Pathname location 11 | throw new Error("Location does not exist") unless path.exists() 12 | 13 | File.open path, 'a', (file) -> 14 | file.console.log YAML.dump data 15 | 16 | data 17 | 18 | 19 | # Rubinius' File.open implementation. 20 | File.open = (path, mode, block) -> 21 | io = new File path, mode 22 | 23 | return io unless block 24 | 25 | try 26 | block io 27 | finally 28 | io.close() unless io.closed() 29 | 30 | 31 | # Write. 32 | write = (location, data) -> 33 | path = new Pathname location 34 | raise "Location does not exist" unless path.exists() 35 | 36 | File.open path, 'w', (file) -> 37 | return false if Digest.MD5.hexdigest(file.read()) is data.hash() 38 | file.console.log YAML.dump data 39 | true 40 | 41 | 42 | # Rails' respond_to. 43 | index = -> 44 | people = Person.find 'all' 45 | 46 | respond_to (format) -> 47 | format.html() 48 | format.xml -> render xml: people.xml() 49 | 50 | 51 | # Synchronization. 52 | synchronize = (block) -> 53 | lock() 54 | try block() finally unlock() 55 | -------------------------------------------------------------------------------- /examples/beautiful_code/regular_expression_matcher.coffee: -------------------------------------------------------------------------------- 1 | # Beautiful Code, Chapter 1. 2 | # Implements a regular expression matcher that supports character matches, 3 | # '.', '^', '$', and '*'. 4 | 5 | # Search for the regexp anywhere in the text. 6 | match = (regexp, text) -> 7 | return match_here(regexp.slice(1), text) if regexp[0] is '^' 8 | while text 9 | return true if match_here(regexp, text) 10 | text = text.slice(1) 11 | false 12 | 13 | # Search for the regexp at the beginning of the text. 14 | match_here = (regexp, text) -> 15 | [cur, next] = [regexp[0], regexp[1]] 16 | if regexp.length is 0 then return true 17 | if next is '*' then return match_star(cur, regexp.slice(2), text) 18 | if cur is '$' and not next then return text.length is 0 19 | if text and (cur is '.' or cur is text[0]) then return match_here(regexp.slice(1), text.slice(1)) 20 | false 21 | 22 | # Search for a kleene star match at the beginning of the text. 23 | match_star = (c, regexp, text) -> 24 | loop 25 | return true if match_here(regexp, text) 26 | return false unless text and (text[0] is c or c is '.') 27 | text = text.slice(1) 28 | 29 | console.log match("ex", "some text") 30 | console.log match("s..t", "spit") 31 | console.log match("^..t", "buttercup") 32 | console.log match("i..$", "cherries") 33 | console.log match("o*m", "vrooooommm!") 34 | console.log match("^hel*o$", "hellllllo") -------------------------------------------------------------------------------- /documentation/js/classes.js: -------------------------------------------------------------------------------- 1 | var Animal, Horse, Snake, sam, tom; 2 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 3 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 4 | function ctor() { this.constructor = child; } 5 | ctor.prototype = parent.prototype; 6 | child.prototype = new ctor; 7 | child.__super__ = parent.prototype; 8 | return child; 9 | }; 10 | Animal = (function() { 11 | function Animal(name) { 12 | this.name = name; 13 | } 14 | Animal.prototype.move = function(meters) { 15 | return alert(this.name + (" moved " + meters + "m.")); 16 | }; 17 | return Animal; 18 | })(); 19 | Snake = (function() { 20 | __extends(Snake, Animal); 21 | function Snake() { 22 | Snake.__super__.constructor.apply(this, arguments); 23 | } 24 | Snake.prototype.move = function() { 25 | alert("Slithering..."); 26 | return Snake.__super__.move.call(this, 5); 27 | }; 28 | return Snake; 29 | })(); 30 | Horse = (function() { 31 | __extends(Horse, Animal); 32 | function Horse() { 33 | Horse.__super__.constructor.apply(this, arguments); 34 | } 35 | Horse.prototype.move = function() { 36 | alert("Galloping..."); 37 | return Horse.__super__.move.call(this, 45); 38 | }; 39 | return Horse; 40 | })(); 41 | sam = new Snake("Sammy the Python"); 42 | tom = new Horse("Tommy the Palomino"); 43 | sam.move(); 44 | tom.move(); -------------------------------------------------------------------------------- /test/compilation.coffee: -------------------------------------------------------------------------------- 1 | # Compilation 2 | # ----------- 3 | 4 | # helper to assert that a string should fail compilation 5 | cantCompile = (code) -> 6 | throws -> CoffeeScript.compile code 7 | 8 | 9 | test "ensure that carriage returns don't break compilation on Windows", -> 10 | doesNotThrow -> CoffeeScript.compile 'one\r\ntwo', bare: on 11 | 12 | test "--bare", -> 13 | eq -1, CoffeeScript.compile('x = y', bare: on).indexOf 'function' 14 | ok 'passed' is CoffeeScript.eval '"passed"', bare: on, filename: 'test' 15 | 16 | test "multiple generated references", -> 17 | a = {b: []} 18 | a.b[true] = -> this == a.b 19 | c = 0 20 | d = [] 21 | ok a.b[0<++c<2] d... 22 | 23 | test "splat on a line by itself is invalid", -> 24 | cantCompile "x 'a'\n...\n" 25 | 26 | test "Issue 750", -> 27 | 28 | cantCompile 'f(->' 29 | 30 | cantCompile 'a = (break)' 31 | 32 | cantCompile 'a = (return 5 for item in list)' 33 | 34 | cantCompile 'a = (return 5 while condition)' 35 | 36 | cantCompile 'a = for x in y\n return 5' 37 | 38 | test "Issue #986: Unicode identifiers", -> 39 | λ = 5 40 | eq λ, 5 41 | 42 | test "don't accidentally stringify keywords", -> 43 | ok (-> this == 'this')() is false 44 | 45 | test "#1026", -> 46 | cantCompile ''' 47 | if a 48 | b 49 | else 50 | c 51 | else 52 | d 53 | ''' 54 | 55 | test "#1050", -> 56 | cantCompile "### */ ###" 57 | 58 | test "#1106: __proto__ compilation", -> 59 | object = eq 60 | @["__proto__"] = true 61 | ok __proto__ 62 | -------------------------------------------------------------------------------- /test/arrays.coffee: -------------------------------------------------------------------------------- 1 | # Array Literals 2 | # -------------- 3 | 4 | # * Array Literals 5 | # * Splats in Array Literals 6 | 7 | # TODO: add indexing and method invocation tests: [1][0] is 1, [].toString() 8 | 9 | test "trailing commas", -> 10 | trailingComma = [1, 2, 3,] 11 | ok (trailingComma[0] is 1) and (trailingComma[2] is 3) and (trailingComma.length is 3) 12 | 13 | trailingComma = [ 14 | 1, 2, 3, 15 | 4, 5, 6 16 | 7, 8, 9, 17 | ] 18 | (sum = (sum or 0) + n) for n in trailingComma 19 | 20 | a = [((x) -> x), ((x) -> x * x)] 21 | ok a.length is 2 22 | 23 | test "incorrect indentation without commas", -> 24 | result = [['a'] 25 | {b: 'c'}] 26 | ok result[0][0] is 'a' 27 | ok result[1]['b'] is 'c' 28 | 29 | 30 | # Splats in Array Literals 31 | 32 | test "array splat expansions with assignments", -> 33 | nums = [1, 2, 3] 34 | list = [a = 0, nums..., b = 4] 35 | eq 0, a 36 | eq 4, b 37 | arrayEq [0,1,2,3,4], list 38 | 39 | 40 | test "mixed shorthand objects in array lists", -> 41 | 42 | arr = [ 43 | a:1 44 | 'b' 45 | c:1 46 | ] 47 | ok arr.length is 3 48 | ok arr[2].c is 1 49 | 50 | arr = [b: 1, a: 2, 100] 51 | eq arr[1], 100 52 | 53 | arr = [a:0, b:1, (1 + 1)] 54 | eq arr[1], 2 55 | 56 | arr = [a:1, 'a', b:1, 'b'] 57 | eq arr.length, 4 58 | eq arr[2].b, 1 59 | eq arr[3], 'b' 60 | 61 | 62 | test "array splats with nested arrays", -> 63 | nonce = {} 64 | a = [nonce] 65 | list = [1, 2, a...] 66 | eq list[0], 1 67 | eq list[2], nonce 68 | 69 | a = [[nonce]] 70 | list = [1, 2, a...] 71 | arrayEq list, [1, 2, [nonce]] 72 | 73 | test "#1274: `[] = a()` compiles to `false` instead of `a()`", -> 74 | a = false 75 | fn = -> a = true 76 | [] = fn() 77 | ok a 78 | -------------------------------------------------------------------------------- /lib/coffee-script/helpers.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var extend, flatten; 3 | exports.starts = function(string, literal, start) { 4 | return literal === string.substr(start, literal.length); 5 | }; 6 | exports.ends = function(string, literal, back) { 7 | var len; 8 | len = literal.length; 9 | return literal === string.substr(string.length - len - (back || 0), len); 10 | }; 11 | exports.compact = function(array) { 12 | var item, _i, _len, _results; 13 | _results = []; 14 | for (_i = 0, _len = array.length; _i < _len; _i++) { 15 | item = array[_i]; 16 | if (item) _results.push(item); 17 | } 18 | return _results; 19 | }; 20 | exports.count = function(string, substr) { 21 | var num, pos; 22 | num = pos = 0; 23 | if (!substr.length) return 1 / 0; 24 | while (pos = 1 + string.indexOf(substr, pos)) { 25 | num++; 26 | } 27 | return num; 28 | }; 29 | exports.merge = function(options, overrides) { 30 | return extend(extend({}, options), overrides); 31 | }; 32 | extend = exports.extend = function(object, properties) { 33 | var key, val; 34 | for (key in properties) { 35 | val = properties[key]; 36 | object[key] = val; 37 | } 38 | return object; 39 | }; 40 | exports.flatten = flatten = function(array) { 41 | var element, flattened, _i, _len; 42 | flattened = []; 43 | for (_i = 0, _len = array.length; _i < _len; _i++) { 44 | element = array[_i]; 45 | if (element instanceof Array) { 46 | flattened = flattened.concat(flatten(element)); 47 | } else { 48 | flattened.push(element); 49 | } 50 | } 51 | return flattened; 52 | }; 53 | exports.del = function(obj, key) { 54 | var val; 55 | val = obj[key]; 56 | delete obj[key]; 57 | return val; 58 | }; 59 | exports.last = function(array, back) { 60 | return array[array.length - (back || 0) - 1]; 61 | }; 62 | }).call(this); 63 | -------------------------------------------------------------------------------- /src/browser.coffee: -------------------------------------------------------------------------------- 1 | # Override exported methods for non-Node.js engines. 2 | 3 | CoffeeScript = require './coffee-script' 4 | CoffeeScript.require = require 5 | 6 | # Use standard JavaScript `eval` to eval code. 7 | CoffeeScript.eval = (code, options) -> 8 | eval CoffeeScript.compile code, options 9 | 10 | # Running code does not provide access to this scope. 11 | CoffeeScript.run = (code, options = {}) -> 12 | options.bare = on 13 | Function(CoffeeScript.compile code, options)() 14 | 15 | # If we're not in a browser environment, we're finished with the public API. 16 | return unless window? 17 | 18 | # Load a remote script from the current domain via XHR. 19 | CoffeeScript.load = (url, callback) -> 20 | xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP') 21 | xhr.open 'GET', url, true 22 | xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr 23 | xhr.onreadystatechange = -> 24 | if xhr.readyState is 4 25 | if xhr.status in [0, 200] 26 | CoffeeScript.run xhr.responseText 27 | else 28 | throw new Error "Could not load #{url}" 29 | callback() if callback 30 | xhr.send null 31 | 32 | # Activate CoffeeScript in the browser by having it compile and evaluate 33 | # all script tags with a content-type of `text/coffeescript`. 34 | # This happens on page load. 35 | runScripts = -> 36 | scripts = document.getElementsByTagName 'script' 37 | coffees = (s for s in scripts when s.type is 'text/coffeescript') 38 | index = 0 39 | length = coffees.length 40 | do execute = -> 41 | script = coffees[index++] 42 | if script?.type is 'text/coffeescript' 43 | if script.src 44 | CoffeeScript.load script.src, execute 45 | else 46 | CoffeeScript.run script.innerHTML 47 | execute() 48 | null 49 | 50 | # Listen for window load, both in browsers and in IE. 51 | if window.addEventListener 52 | addEventListener 'DOMContentLoaded', runScripts, no 53 | else 54 | attachEvent 'onload', runScripts 55 | -------------------------------------------------------------------------------- /test/strings.coffee: -------------------------------------------------------------------------------- 1 | # String Literals 2 | # --------------- 3 | 4 | # TODO: refactor string literal tests 5 | # TODO: add indexing and method invocation tests: "string"["toString"] is String::toString, "string".toString() is "string" 6 | 7 | # * Strings 8 | # * Heredocs 9 | 10 | test "backslash escapes", -> 11 | eq "\\/\\\\", /\/\\/.source 12 | 13 | eq '(((dollars)))', '\(\(\(dollars\)\)\)' 14 | eq 'one two three', "one 15 | two 16 | three" 17 | eq "four five", 'four 18 | 19 | five' 20 | 21 | #647 22 | eq "''Hello, World\\''", ''' 23 | '\'Hello, World\\\'' 24 | ''' 25 | eq '""Hello, World\\""', """ 26 | "\"Hello, World\\\"" 27 | """ 28 | eq 'Hello, World\n', ''' 29 | Hello, World\ 30 | 31 | ''' 32 | 33 | a = """ 34 | basic heredoc 35 | on two lines 36 | """ 37 | ok a is "basic heredoc\non two lines" 38 | 39 | a = ''' 40 | a 41 | "b 42 | c 43 | ''' 44 | ok a is "a\n \"b\nc" 45 | 46 | a = """ 47 | a 48 | b 49 | c 50 | """ 51 | ok a is "a\n b\n c" 52 | 53 | a = '''one-liner''' 54 | ok a is 'one-liner' 55 | 56 | a = """ 57 | out 58 | here 59 | """ 60 | ok a is "out\nhere" 61 | 62 | a = ''' 63 | a 64 | b 65 | c 66 | ''' 67 | ok a is " a\n b\nc" 68 | 69 | a = ''' 70 | a 71 | 72 | 73 | b c 74 | ''' 75 | ok a is "a\n\n\nb c" 76 | 77 | a = '''more"than"one"quote''' 78 | ok a is 'more"than"one"quote' 79 | 80 | a = '''here's an apostrophe''' 81 | ok a is "here's an apostrophe" 82 | 83 | # The indentation detector ignores blank lines without trailing whitespace 84 | a = """ 85 | one 86 | two 87 | 88 | """ 89 | ok a is "one\ntwo\n" 90 | 91 | eq ''' line 0 92 | should not be relevant 93 | to the indent level 94 | ''', ' 95 | line 0\n 96 | should not be relevant\n 97 | to the indent level 98 | ' 99 | 100 | eq ''' '\\\' ''', " '\\' " 101 | eq """ "\\\" """, ' "\\" ' 102 | 103 | eq ''' <- keep these spaces -> ''', ' <- keep these spaces -> ' 104 | 105 | 106 | test "#1046, empty string interpolations", -> 107 | eq "#{ }", '' 108 | -------------------------------------------------------------------------------- /src/helpers.coffee: -------------------------------------------------------------------------------- 1 | # This file contains the common helper functions that we'd like to share among 2 | # the **Lexer**, **Rewriter**, and the **Nodes**. Merge objects, flatten 3 | # arrays, count characters, that sort of thing. 4 | 5 | # Peek at the beginning of a given string to see if it matches a sequence. 6 | exports.starts = (string, literal, start) -> 7 | literal is string.substr start, literal.length 8 | 9 | # Peek at the end of a given string to see if it matches a sequence. 10 | exports.ends = (string, literal, back) -> 11 | len = literal.length 12 | literal is string.substr string.length - len - (back or 0), len 13 | 14 | # Trim out all falsy values from an array. 15 | exports.compact = (array) -> 16 | item for item in array when item 17 | 18 | # Count the number of occurrences of a string in a string. 19 | exports.count = (string, substr) -> 20 | num = pos = 0 21 | return 1/0 unless substr.length 22 | num++ while pos = 1 + string.indexOf substr, pos 23 | num 24 | 25 | # Merge objects, returning a fresh copy with attributes from both sides. 26 | # Used every time `Base#compile` is called, to allow properties in the 27 | # options hash to propagate down the tree without polluting other branches. 28 | exports.merge = (options, overrides) -> 29 | extend (extend {}, options), overrides 30 | 31 | # Extend a source object with the properties of another object (shallow copy). 32 | extend = exports.extend = (object, properties) -> 33 | for key, val of properties 34 | object[key] = val 35 | object 36 | 37 | # Return a flattened version of an array. 38 | # Handy for getting a list of `children` from the nodes. 39 | exports.flatten = flatten = (array) -> 40 | flattened = [] 41 | for element in array 42 | if element instanceof Array 43 | flattened = flattened.concat flatten element 44 | else 45 | flattened.push element 46 | flattened 47 | 48 | # Delete a key from an object, returning the value. Useful when a node is 49 | # looking for a particular method in an options hash. 50 | exports.del = (obj, key) -> 51 | val = obj[key] 52 | delete obj[key] 53 | val 54 | 55 | # Gets the last item of an array(-like) object. 56 | exports.last = (array, back) -> array[array.length - (back or 0) - 1] 57 | -------------------------------------------------------------------------------- /lib/coffee-script/browser.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var CoffeeScript, runScripts; 3 | CoffeeScript = require('./coffee-script'); 4 | CoffeeScript.require = require; 5 | CoffeeScript.eval = function(code, options) { 6 | return eval(CoffeeScript.compile(code, options)); 7 | }; 8 | CoffeeScript.run = function(code, options) { 9 | if (options == null) options = {}; 10 | options.bare = true; 11 | return Function(CoffeeScript.compile(code, options))(); 12 | }; 13 | if (typeof window === "undefined" || window === null) return; 14 | CoffeeScript.load = function(url, callback) { 15 | var xhr; 16 | xhr = new (window.ActiveXObject || XMLHttpRequest)('Microsoft.XMLHTTP'); 17 | xhr.open('GET', url, true); 18 | if ('overrideMimeType' in xhr) xhr.overrideMimeType('text/plain'); 19 | xhr.onreadystatechange = function() { 20 | var _ref; 21 | if (xhr.readyState === 4) { 22 | if ((_ref = xhr.status) === 0 || _ref === 200) { 23 | CoffeeScript.run(xhr.responseText); 24 | } else { 25 | throw new Error("Could not load " + url); 26 | } 27 | if (callback) return callback(); 28 | } 29 | }; 30 | return xhr.send(null); 31 | }; 32 | runScripts = function() { 33 | var coffees, execute, index, length, s, scripts; 34 | scripts = document.getElementsByTagName('script'); 35 | coffees = (function() { 36 | var _i, _len, _results; 37 | _results = []; 38 | for (_i = 0, _len = scripts.length; _i < _len; _i++) { 39 | s = scripts[_i]; 40 | if (s.type === 'text/coffeescript') _results.push(s); 41 | } 42 | return _results; 43 | })(); 44 | index = 0; 45 | length = coffees.length; 46 | (execute = function() { 47 | var script; 48 | script = coffees[index++]; 49 | if ((script != null ? script.type : void 0) === 'text/coffeescript') { 50 | if (script.src) { 51 | return CoffeeScript.load(script.src, execute); 52 | } else { 53 | CoffeeScript.run(script.innerHTML); 54 | return execute(); 55 | } 56 | } 57 | })(); 58 | return null; 59 | }; 60 | if (window.addEventListener) { 61 | addEventListener('DOMContentLoaded', runScripts, false); 62 | } else { 63 | attachEvent('onload', runScripts); 64 | } 65 | }).call(this); 66 | -------------------------------------------------------------------------------- /test/exception_handling.coffee: -------------------------------------------------------------------------------- 1 | # Exception Handling 2 | # ------------------ 3 | 4 | # shared nonce 5 | nonce = {} 6 | 7 | 8 | # Throw 9 | 10 | test "basic exception throwing", -> 11 | throws (-> throw 'error'), 'error' 12 | 13 | 14 | # Empty Try/Catch/Finally 15 | 16 | test "try can exist alone", -> 17 | try 18 | 19 | test "try/catch with empty try, empty catch", -> 20 | try 21 | # nothing 22 | catch err 23 | # nothing 24 | 25 | test "single-line try/catch with empty try, empty catch", -> 26 | try catch err 27 | 28 | test "try/finally with empty try, empty finally", -> 29 | try 30 | # nothing 31 | finally 32 | # nothing 33 | 34 | test "single-line try/finally with empty try, empty finally", -> 35 | try finally 36 | 37 | test "try/catch/finally with empty try, empty catch, empty finally", -> 38 | try 39 | catch err 40 | finally 41 | 42 | test "single-line try/catch/finally with empty try, empty catch, empty finally", -> 43 | try catch err then finally 44 | 45 | 46 | # Try/Catch/Finally as an Expression 47 | 48 | test "return the result of try when no exception is thrown", -> 49 | result = try 50 | nonce 51 | catch err 52 | undefined 53 | finally 54 | undefined 55 | eq nonce, result 56 | 57 | test "single-line result of try when no exception is thrown", -> 58 | result = try nonce catch err then undefined 59 | eq nonce, result 60 | 61 | test "return the result of catch when an exception is thrown", -> 62 | fn = -> 63 | try 64 | throw -> 65 | catch err 66 | nonce 67 | doesNotThrow fn 68 | eq nonce, fn() 69 | 70 | test "single-line result of catch when an exception is thrown", -> 71 | fn = -> 72 | try throw (->) catch err then nonce 73 | doesNotThrow fn 74 | eq nonce, fn() 75 | 76 | test "optional catch", -> 77 | fn = -> 78 | try throw -> 79 | nonce 80 | doesNotThrow fn 81 | eq nonce, fn() 82 | 83 | 84 | # Try/Catch/Finally Interaction With Other Constructs 85 | 86 | test "try/catch with empty catch as last statement in a function body", -> 87 | fn = -> 88 | try nonce 89 | catch err 90 | eq nonce, fn() 91 | 92 | 93 | # Catch leads to broken scoping: #1595 94 | 95 | test "try/catch with a reused variable name.", -> 96 | do -> 97 | try 98 | inner = 5 99 | catch inner 100 | # nothing 101 | eq typeof inner, 'undefined' 102 | 103 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'erb' 3 | require 'fileutils' 4 | require 'rake/testtask' 5 | require 'json' 6 | 7 | desc "Build the documentation page" 8 | task :doc do 9 | source = 'documentation/index.html.erb' 10 | child = fork { exec "bin/coffee -bcw -o documentation/js documentation/coffee/*.coffee" } 11 | at_exit { Process.kill("INT", child) } 12 | Signal.trap("INT") { exit } 13 | loop do 14 | mtime = File.stat(source).mtime 15 | if !@mtime || mtime > @mtime 16 | rendered = ERB.new(File.read(source)).result(binding) 17 | File.open('index.html', 'w+') {|f| f.write(rendered) } 18 | end 19 | @mtime = mtime 20 | sleep 1 21 | end 22 | end 23 | 24 | desc "Build coffee-script-source gem" 25 | task :gem do 26 | require 'rubygems' 27 | require 'rubygems/package' 28 | 29 | gemspec = Gem::Specification.new do |s| 30 | s.name = 'coffee-script-source' 31 | s.version = JSON.parse(File.read('package.json'))["version"] 32 | s.date = Time.now.strftime("%Y-%m-%d") 33 | 34 | s.homepage = "http://jashkenas.github.com/coffee-script/" 35 | s.summary = "The CoffeeScript Compiler" 36 | s.description = <<-EOS 37 | CoffeeScript is a little language that compiles into JavaScript. 38 | Underneath all of those embarrassing braces and semicolons, 39 | JavaScript has always had a gorgeous object model at its heart. 40 | CoffeeScript is an attempt to expose the good parts of JavaScript 41 | in a simple way. 42 | EOS 43 | 44 | s.files = [ 45 | 'lib/coffee_script/coffee-script.js', 46 | 'lib/coffee_script/source.rb' 47 | ] 48 | 49 | s.authors = ['Jeremy Ashkenas'] 50 | s.email = 'jashkenas@gmail.com' 51 | s.rubyforge_project = 'coffee-script-source' 52 | end 53 | 54 | file = File.open("coffee-script-source.gem", "w") 55 | Gem::Package.open(file, 'w') do |pkg| 56 | pkg.metadata = gemspec.to_yaml 57 | 58 | path = "lib/coffee_script/source.rb" 59 | contents = <<-ERUBY 60 | module CoffeeScript 61 | module Source 62 | def self.bundled_path 63 | File.expand_path("../coffee-script.js", __FILE__) 64 | end 65 | end 66 | end 67 | ERUBY 68 | pkg.add_file_simple(path, 0644, contents.size) do |tar_io| 69 | tar_io.write(contents) 70 | end 71 | 72 | contents = File.read("extras/coffee-script.js") 73 | path = "lib/coffee_script/coffee-script.js" 74 | pkg.add_file_simple(path, 0644, contents.size) do |tar_io| 75 | tar_io.write(contents) 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /test/ranges.coffee: -------------------------------------------------------------------------------- 1 | # Range Literals 2 | # -------------- 3 | 4 | # TODO: add indexing and method invocation tests: [1..4][0] is 1, [0...3].toString() 5 | 6 | # shared array 7 | shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 8 | 9 | test "basic inclusive ranges", -> 10 | arrayEq [1, 2, 3] , [1..3] 11 | arrayEq [0, 1, 2] , [0..2] 12 | arrayEq [0, 1] , [0..1] 13 | arrayEq [0] , [0..0] 14 | arrayEq [-1] , [-1..-1] 15 | arrayEq [-1, 0] , [-1..0] 16 | arrayEq [-1, 0, 1], [-1..1] 17 | 18 | test "basic exclusive ranges", -> 19 | arrayEq [1, 2, 3] , [1...4] 20 | arrayEq [0, 1, 2] , [0...3] 21 | arrayEq [0, 1] , [0...2] 22 | arrayEq [0] , [0...1] 23 | arrayEq [-1] , [-1...0] 24 | arrayEq [-1, 0] , [-1...1] 25 | arrayEq [-1, 0, 1], [-1...2] 26 | 27 | arrayEq [], [1...1] 28 | arrayEq [], [0...0] 29 | arrayEq [], [-1...-1] 30 | 31 | test "downward ranges", -> 32 | arrayEq shared, [9..0].reverse() 33 | arrayEq [5, 4, 3, 2] , [5..2] 34 | arrayEq [2, 1, 0, -1], [2..-1] 35 | 36 | arrayEq [3, 2, 1] , [3..1] 37 | arrayEq [2, 1, 0] , [2..0] 38 | arrayEq [1, 0] , [1..0] 39 | arrayEq [0] , [0..0] 40 | arrayEq [-1] , [-1..-1] 41 | arrayEq [0, -1] , [0..-1] 42 | arrayEq [1, 0, -1] , [1..-1] 43 | arrayEq [0, -1, -2], [0..-2] 44 | 45 | arrayEq [4, 3, 2], [4...1] 46 | arrayEq [3, 2, 1], [3...0] 47 | arrayEq [2, 1] , [2...0] 48 | arrayEq [1] , [1...0] 49 | arrayEq [] , [0...0] 50 | arrayEq [] , [-1...-1] 51 | arrayEq [0] , [0...-1] 52 | arrayEq [0, -1] , [0...-2] 53 | arrayEq [1, 0] , [1...-1] 54 | arrayEq [2, 1, 0], [2...-1] 55 | 56 | test "ranges with variables as enpoints", -> 57 | [a, b] = [1, 3] 58 | arrayEq [1, 2, 3], [a..b] 59 | arrayEq [1, 2] , [a...b] 60 | b = -2 61 | arrayEq [1, 0, -1, -2], [a..b] 62 | arrayEq [1, 0, -1] , [a...b] 63 | 64 | test "ranges with expressions as endpoints", -> 65 | [a, b] = [1, 3] 66 | arrayEq [2, 3, 4, 5, 6], [(a+1)..2*b] 67 | arrayEq [2, 3, 4, 5] , [(a+1)...2*b] 68 | 69 | test "large ranges are generated with looping constructs", -> 70 | down = [99..0] 71 | eq 100, (len = down.length) 72 | eq 0, down[len - 1] 73 | 74 | up = [0...100] 75 | eq 100, (len = up.length) 76 | eq 99, up[len - 1] 77 | 78 | test "#1012 slices with arguments object", -> 79 | expected = [0..9] 80 | argsAtStart = (-> [arguments[0]..9]) 0 81 | arrayEq expected, argsAtStart 82 | argsAtEnd = (-> [0..arguments[0]]) 9 83 | arrayEq expected, argsAtEnd 84 | argsAtBoth = (-> [arguments[0]..arguments[1]]) 0, 9 85 | arrayEq expected, argsAtBoth 86 | 87 | test "#1409: creating large ranges outside of a function body", -> 88 | CoffeeScript.eval '[0..100]' 89 | -------------------------------------------------------------------------------- /lib/coffee-script/cake.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var CoffeeScript, cakefileDirectory, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks; 3 | fs = require('fs'); 4 | path = require('path'); 5 | helpers = require('./helpers'); 6 | optparse = require('./optparse'); 7 | CoffeeScript = require('./coffee-script'); 8 | tasks = {}; 9 | options = {}; 10 | switches = []; 11 | oparse = null; 12 | helpers.extend(global, { 13 | task: function(name, description, action) { 14 | var _ref; 15 | if (!action) { 16 | _ref = [description, action], action = _ref[0], description = _ref[1]; 17 | } 18 | return tasks[name] = { 19 | name: name, 20 | description: description, 21 | action: action 22 | }; 23 | }, 24 | option: function(letter, flag, description) { 25 | return switches.push([letter, flag, description]); 26 | }, 27 | invoke: function(name) { 28 | if (!tasks[name]) missingTask(name); 29 | return tasks[name].action(options); 30 | } 31 | }); 32 | exports.run = function() { 33 | var arg, args, _i, _len, _ref, _results; 34 | global.__originalDirname = fs.realpathSync('.'); 35 | process.chdir(cakefileDirectory(__originalDirname)); 36 | args = process.argv.slice(2); 37 | CoffeeScript.run(fs.readFileSync('Cakefile').toString(), { 38 | filename: 'Cakefile' 39 | }); 40 | oparse = new optparse.OptionParser(switches); 41 | if (!args.length) return printTasks(); 42 | options = oparse.parse(args); 43 | _ref = options.arguments; 44 | _results = []; 45 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 46 | arg = _ref[_i]; 47 | _results.push(invoke(arg)); 48 | } 49 | return _results; 50 | }; 51 | printTasks = function() { 52 | var desc, name, spaces, task; 53 | console.log(''); 54 | for (name in tasks) { 55 | task = tasks[name]; 56 | spaces = 20 - name.length; 57 | spaces = spaces > 0 ? Array(spaces + 1).join(' ') : ''; 58 | desc = task.description ? "# " + task.description : ''; 59 | console.log("cake " + name + spaces + " " + desc); 60 | } 61 | if (switches.length) return console.log(oparse.help()); 62 | }; 63 | missingTask = function(task) { 64 | console.log("No such task: \"" + task + "\""); 65 | return process.exit(1); 66 | }; 67 | cakefileDirectory = function(dir) { 68 | var parent; 69 | if (path.existsSync(path.join(dir, 'Cakefile'))) return dir; 70 | parent = path.normalize(path.join(dir, '..')); 71 | if (parent !== dir) return cakefileDirectory(parent); 72 | throw new Error("Cakefile not found in " + (process.cwd())); 73 | }; 74 | }).call(this); 75 | -------------------------------------------------------------------------------- /test/helpers.coffee: -------------------------------------------------------------------------------- 1 | # Helpers 2 | # ------- 3 | 4 | # pull the helpers from `CoffeeScript.helpers` into local variables 5 | {starts, ends, compact, count, merge, extend, flatten, del, last} = CoffeeScript.helpers 6 | 7 | 8 | # `starts` 9 | 10 | test "the `starts` helper tests if a string starts with another string", -> 11 | ok starts('01234', '012') 12 | ok not starts('01234', '123') 13 | 14 | test "the `starts` helper can take an optional offset", -> 15 | ok starts('01234', '34', 3) 16 | ok not starts('01234', '01', 1) 17 | 18 | 19 | # `ends` 20 | 21 | test "the `ends` helper tests if a string ends with another string", -> 22 | ok ends('01234', '234') 23 | ok not ends('01234', '012') 24 | 25 | test "the `ends` helper can take an optional offset", -> 26 | ok ends('01234', '012', 2) 27 | ok not ends('01234', '234', 6) 28 | 29 | 30 | # `compact` 31 | 32 | test "the `compact` helper removes falsey values from an array, preserves truthy ones", -> 33 | allValues = [1, 0, false, obj={}, [], '', ' ', -1, null, undefined, true] 34 | truthyValues = [1, obj, [], ' ', -1, true] 35 | arrayEq truthyValues, compact(allValues) 36 | 37 | 38 | # `count` 39 | 40 | test "the `count` helper counts the number of occurances of a string in another string", -> 41 | eq 1/0, count('abc', '') 42 | eq 0, count('abc', 'z') 43 | eq 1, count('abc', 'a') 44 | eq 1, count('abc', 'b') 45 | eq 2, count('abcdc', 'c') 46 | eq 2, count('abcdabcd','abc') 47 | 48 | 49 | # `merge` 50 | 51 | test "the `merge` helper makes a new object with all properties of the objects given as its arguments", -> 52 | ary = [0, 1, 2, 3, 4] 53 | obj = {} 54 | merged = merge obj, ary 55 | ok merged isnt obj 56 | ok merged isnt ary 57 | for own key, val of ary 58 | eq val, merged[key] 59 | 60 | 61 | # `extend` 62 | 63 | test "the `extend` helper performs a shallow copy", -> 64 | ary = [0, 1, 2, 3] 65 | obj = {} 66 | # should return the object being extended 67 | eq obj, extend(obj, ary) 68 | # should copy the other object's properties as well (obviously) 69 | eq 2, obj[2] 70 | 71 | 72 | # `flatten` 73 | 74 | test "the `flatten` helper flattens an array", -> 75 | success = yes 76 | (success and= typeof n is 'number') for n in flatten [0, [[[1]], 2], 3, [4]] 77 | ok success 78 | 79 | 80 | # `del` 81 | 82 | test "the `del` helper deletes a property from an object and returns the deleted value", -> 83 | obj = [0, 1, 2] 84 | eq 1, del(obj, 1) 85 | ok 1 not of obj 86 | 87 | 88 | # `last` 89 | 90 | test "the `last` helper returns the last item of an array-like object", -> 91 | ary = [0, 1, 2, 3, 4] 92 | eq 4, last(ary) 93 | 94 | test "the `last` helper allows one to specify an optional offset", -> 95 | ary = [0, 1, 2, 3, 4] 96 | eq 2, last(ary, 2) 97 | -------------------------------------------------------------------------------- /examples/computer_science/linked_list.coffee: -------------------------------------------------------------------------------- 1 | # "Classic" linked list implementation that doesn't keep track of its size. 2 | class LinkedList 3 | 4 | -> 5 | this._head = null # Pointer to the first item in the list. 6 | 7 | 8 | # Appends some data to the end of the list. This method traverses the existing 9 | # list and places the value at the end in a new node. 10 | add: (data) -> 11 | 12 | # Create a new node object to wrap the data. 13 | node = data: data, next: null 14 | 15 | current = this._head or= node 16 | 17 | if this._head isnt node 18 | (current = current.next) while current.next 19 | current.next = node 20 | 21 | this 22 | 23 | 24 | # Retrieves the data at the given position in the list. 25 | item: (index) -> 26 | 27 | # Check for out-of-bounds values. 28 | return null if index < 0 29 | 30 | current = this._head or null 31 | i = -1 32 | 33 | # Advance through the list. 34 | (current = current.next) while current and index > (i += 1) 35 | 36 | # Return null if we've reached the end. 37 | current and current.data 38 | 39 | 40 | # Remove the item from the given location in the list. 41 | remove: (index) -> 42 | 43 | # Check for out-of-bounds values. 44 | return null if index < 0 45 | 46 | current = this._head or null 47 | i = -1 48 | 49 | # Special case: removing the first item. 50 | if index is 0 51 | this._head = current.next 52 | else 53 | 54 | # Find the right location. 55 | ([previous, current] = [current, current.next]) while index > (i += 1) 56 | 57 | # Skip over the item to remove. 58 | previous.next = current.next 59 | 60 | # Return the value. 61 | current and current.data 62 | 63 | 64 | # Calculate the number of items in the list. 65 | size: -> 66 | current = this._head 67 | count = 0 68 | 69 | while current 70 | count += 1 71 | current = current.next 72 | 73 | count 74 | 75 | 76 | # Convert the list into an array. 77 | toArray: -> 78 | result = [] 79 | current = this._head 80 | 81 | while current 82 | result.push current.data 83 | current = current.next 84 | 85 | result 86 | 87 | 88 | # The string representation of the linked list. 89 | toString: -> this.toArray().toString() 90 | 91 | 92 | # Tests. 93 | list = new LinkedList 94 | 95 | list.add("Hi") 96 | console.log list.size() is 1 97 | console.log list.item(0) is "Hi" 98 | console.log list.item(1) is null 99 | 100 | list = new LinkedList 101 | list.add("zero").add("one").add("two") 102 | console.log list.size() is 3 103 | console.log list.item(2) is "two" 104 | console.log list.remove(1) is "one" 105 | console.log list.item(0) is "zero" 106 | console.log list.item(1) is "two" 107 | console.log list.size() is 2 108 | console.log list.item(-10) is null 109 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CoffeeScript Test Suite 6 | 7 | 18 | 19 | 20 | 21 |

CoffeeScript Test Suite

22 |

 23 | 
 24 |   
116 | 
117 | 
118 | 
119 | 


--------------------------------------------------------------------------------
/documentation/docs/index.html:
--------------------------------------------------------------------------------
1 |       index.coffee              


--------------------------------------------------------------------------------
/test/slicing_and_splicing.coffee:
--------------------------------------------------------------------------------
  1 | # Slicing and Splicing
  2 | # --------------------
  3 | 
  4 | # * Slicing
  5 | # * Splicing
  6 | 
  7 | # shared array
  8 | shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  9 | 
 10 | # Slicing
 11 | 
 12 | test "basic slicing", ->
 13 |   arrayEq [7, 8, 9]   , shared[7..9]
 14 |   arrayEq [2, 3]      , shared[2...4]
 15 |   arrayEq [2, 3, 4, 5], shared[2...6]
 16 | 
 17 | test "slicing with variables as endpoints", ->
 18 |   [a, b] = [1, 4]
 19 |   arrayEq [1, 2, 3, 4], shared[a..b]
 20 |   arrayEq [1, 2, 3]   , shared[a...b]
 21 | 
 22 | test "slicing with expressions as endpoints", ->
 23 |   [a, b] = [1, 3]
 24 |   arrayEq [2, 3, 4, 5, 6], shared[(a+1)..2*b]
 25 |   arrayEq [2, 3, 4, 5]   , shared[a+1...(2*b)]
 26 | 
 27 | test "unbounded slicing", ->
 28 |   arrayEq [7, 8, 9]   , shared[7..]
 29 |   arrayEq [8, 9]      , shared[-2..]
 30 |   arrayEq [9]         , shared[-1...]
 31 |   arrayEq [0, 1, 2]   , shared[...3]
 32 |   arrayEq [0, 1, 2, 3], shared[..-7]
 33 | 
 34 |   arrayEq shared      , shared[..-1]
 35 |   arrayEq shared[0..8], shared[...-1]
 36 | 
 37 |   for a in [-shared.length..shared.length]
 38 |     arrayEq shared[a..] , shared[a...]
 39 |   for a in [-shared.length+1...shared.length]
 40 |     arrayEq shared[..a][...-1] , shared[...a]
 41 | 
 42 | test "#930, #835, #831, #746 #624: inclusive slices to -1 should slice to end", ->
 43 |   arrayEq shared, shared[0..-1]
 44 |   arrayEq shared, shared[..-1]
 45 |   arrayEq shared.slice(1,shared.length), shared[1..-1]
 46 | 
 47 | test "string slicing", ->
 48 |   str = "abcdefghijklmnopqrstuvwxyz"
 49 |   ok str[1...1] is ""
 50 |   ok str[1..1] is "b"
 51 |   ok str[1...5] is "bcde"
 52 |   ok str[0..4] is "abcde"
 53 |   ok str[-5..] is "vwxyz"
 54 | 
 55 | 
 56 | # Splicing
 57 | 
 58 | test "basic splicing", ->
 59 |   ary = [0..9]
 60 |   ary[5..9] = [0, 0, 0]
 61 |   arrayEq [0, 1, 2, 3, 4, 0, 0, 0], ary
 62 | 
 63 |   ary = [0..9]
 64 |   ary[2...8] = []
 65 |   arrayEq [0, 1, 8, 9], ary
 66 | 
 67 | test "unbounded splicing", ->
 68 |   ary = [0..9]
 69 |   ary[3..] = [9, 8, 7]
 70 |   arrayEq [0, 1, 2, 9, 8, 7]. ary
 71 | 
 72 |   ary[...3] = [7, 8, 9]
 73 |   arrayEq [7, 8, 9, 9, 8, 7], ary
 74 | 
 75 | test "splicing with variables as endpoints", ->
 76 |   [a, b] = [1, 8]
 77 | 
 78 |   ary = [0..9]
 79 |   ary[a..b] = [2, 3]
 80 |   arrayEq [0, 2, 3, 9], ary
 81 | 
 82 |   ary = [0..9]
 83 |   ary[a...b] = [5]
 84 |   arrayEq [0, 5, 8, 9], ary
 85 | 
 86 | test "splicing with expressions as endpoints", ->
 87 |   [a, b] = [1, 3]
 88 | 
 89 |   ary = [0..9]
 90 |   ary[ a+1 .. 2*b+1 ] = [4]
 91 |   arrayEq [0, 1, 4, 8, 9], ary
 92 | 
 93 |   ary = [0..9]
 94 |   ary[a+1...2*b+1] = [4]
 95 |   arrayEq [0, 1, 4, 7, 8, 9], ary
 96 | 
 97 | test "splicing to the end, against a one-time function", ->
 98 |   ary = null
 99 |   fn = ->
100 |     if ary
101 |       throw 'err'
102 |     else
103 |       ary = [1, 2, 3]
104 | 
105 |   fn()[0..] = 1
106 | 
107 |   arrayEq ary, [1]
108 | 
109 | test "the return value of a splice literal should be the RHS", ->
110 |   ary = [0, 0, 0]
111 |   eq (ary[0..1] = 2), 2
112 | 
113 |   ary = [0, 0, 0]
114 |   eq (ary[0..] = 3), 3
115 | 
116 |   arrayEq [ary[0..0] = 0], [0]
117 | 


--------------------------------------------------------------------------------
/src/cake.coffee:
--------------------------------------------------------------------------------
 1 | # `cake` is a simplified version of [Make](http://www.gnu.org/software/make/)
 2 | # ([Rake](http://rake.rubyforge.org/), [Jake](http://github.com/280north/jake))
 3 | # for CoffeeScript. You define tasks with names and descriptions in a Cakefile,
 4 | # and can call them from the command line, or invoke them from other tasks.
 5 | #
 6 | # Running `cake` with no arguments will print out a list of all the tasks in the
 7 | # current directory's Cakefile.
 8 | 
 9 | # External dependencies.
10 | fs           = require 'fs'
11 | path         = require 'path'
12 | helpers      = require './helpers'
13 | optparse     = require './optparse'
14 | CoffeeScript = require './coffee-script'
15 | 
16 | # Keep track of the list of defined tasks, the accepted options, and so on.
17 | tasks     = {}
18 | options   = {}
19 | switches  = []
20 | oparse    = null
21 | 
22 | # Mixin the top-level Cake functions for Cakefiles to use directly.
23 | helpers.extend global,
24 | 
25 |   # Define a Cake task with a short name, an optional sentence description,
26 |   # and the function to run as the action itself.
27 |   task: (name, description, action) ->
28 |     [action, description] = [description, action] unless action
29 |     tasks[name] = {name, description, action}
30 | 
31 |   # Define an option that the Cakefile accepts. The parsed options hash,
32 |   # containing all of the command-line options passed, will be made available
33 |   # as the first argument to the action.
34 |   option: (letter, flag, description) ->
35 |     switches.push [letter, flag, description]
36 | 
37 |   # Invoke another task in the current Cakefile.
38 |   invoke: (name) ->
39 |     missingTask name unless tasks[name]
40 |     tasks[name].action options
41 | 
42 | # Run `cake`. Executes all of the tasks you pass, in order. Note that Node's
43 | # asynchrony may cause tasks to execute in a different order than you'd expect.
44 | # If no tasks are passed, print the help screen. Keep a reference to the 
45 | # original directory name, when running Cake tasks from subdirectories. 
46 | exports.run = ->
47 |   global.__originalDirname = fs.realpathSync '.'
48 |   process.chdir cakefileDirectory __originalDirname
49 |   args = process.argv.slice 2
50 |   CoffeeScript.run fs.readFileSync('Cakefile').toString(), filename: 'Cakefile'
51 |   oparse = new optparse.OptionParser switches
52 |   return printTasks() unless args.length
53 |   options = oparse.parse(args)
54 |   invoke arg for arg in options.arguments
55 | 
56 | # Display the list of Cake tasks in a format similar to `rake -T`
57 | printTasks = ->
58 |   console.log ''
59 |   for name, task of tasks
60 |     spaces = 20 - name.length
61 |     spaces = if spaces > 0 then Array(spaces + 1).join(' ') else ''
62 |     desc   = if task.description then "# #{task.description}" else ''
63 |     console.log "cake #{name}#{spaces} #{desc}"
64 |   console.log oparse.help() if switches.length
65 | 
66 | # Print an error and exit when attempting to call an undefined task.
67 | missingTask = (task) ->
68 |   console.log "No such task: \"#{task}\""
69 |   process.exit 1
70 | 
71 | # When `cake` is invoked, search in the current and all parent directories 
72 | # to find the relevant Cakefile.
73 | cakefileDirectory = (dir) ->
74 |   return dir if path.existsSync path.join dir, 'Cakefile'
75 |   parent = path.normalize path.join dir, '..'
76 |   return cakefileDirectory parent unless parent is dir
77 |   throw new Error "Cakefile not found in #{process.cwd()}"
78 | 


--------------------------------------------------------------------------------
/extras/jsl.conf:
--------------------------------------------------------------------------------
 1 | # JavaScriptLint configuration file for CoffeeScript.
 2 | 
 3 | +no_return_value              # function {0} does not always return a value
 4 | +duplicate_formal             # duplicate formal argument {0}
 5 | -equal_as_assign              # test for equality (==) mistyped as assignment (=)?{0}
 6 | +var_hides_arg                # variable {0} hides argument
 7 | +redeclared_var               # redeclaration of {0} {1}
 8 | -anon_no_return_value         # anonymous function does not always return a value
 9 | +missing_semicolon            # missing semicolon
10 | +meaningless_block            # meaningless block; curly braces have no impact
11 | -comma_separated_stmts        # multiple statements separated by commas (use semicolons?)
12 | +unreachable_code             # unreachable code
13 | +missing_break                # missing break statement
14 | -missing_break_for_last_case  # missing break statement for last case in switch
15 | -comparison_type_conv         # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
16 | -inc_dec_within_stmt          # increment (++) and decrement (--) operators used as part of greater statement
17 | -useless_void                 # use of the void type may be unnecessary (void is always undefined)
18 | +multiple_plus_minus          # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
19 | +use_of_label                 # use of label
20 | -block_without_braces         # block statement without curly braces
21 | +leading_decimal_point        # leading decimal point may indicate a number or an object member
22 | +trailing_decimal_point       # trailing decimal point may indicate a number or an object member
23 | +octal_number                 # leading zeros make an octal number
24 | +nested_comment               # nested comment
25 | +misplaced_regex              # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma
26 | +ambiguous_newline            # unexpected end of line; it is ambiguous whether these lines are part of the same statement
27 | +empty_statement              # empty statement or extra semicolon
28 | -missing_option_explicit      # the "option explicit" control comment is missing
29 | +partial_option_explicit      # the "option explicit" control comment, if used, must be in the first script tag
30 | +dup_option_explicit          # duplicate "option explicit" control comment
31 | +useless_assign               # useless assignment
32 | +ambiguous_nested_stmt        # block statements containing block statements should use curly braces to resolve ambiguity
33 | +ambiguous_else_stmt          # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent)
34 | -missing_default_case         # missing default case in switch statement
35 | +duplicate_case_in_switch     # duplicate case in switch statements
36 | +default_not_at_end           # the default case is not at the end of the switch statement
37 | +legacy_cc_not_understood     # couldn't understand control comment using /*@keyword@*/ syntax
38 | +jsl_cc_not_understood        # couldn't understand control comment using /*jsl:keyword*/ syntax
39 | +useless_comparison           # useless comparison; comparing identical expressions
40 | +with_statement               # with statement hides undeclared variables; use temporary variable instead
41 | +trailing_comma_in_array      # extra comma is not recommended in array initializers
42 | +assign_to_function_call      # assignment to a function call
43 | +parseint_missing_radix       # parseInt missing radix parameter
44 | +lambda_assign_requires_semicolon
45 | 


--------------------------------------------------------------------------------
/src/scope.coffee:
--------------------------------------------------------------------------------
 1 | # The **Scope** class regulates lexical scoping within CoffeeScript. As you
 2 | # generate code, you create a tree of scopes in the same shape as the nested
 3 | # function bodies. Each scope knows about the variables declared within it,
 4 | # and has a reference to its parent enclosing scope. In this way, we know which
 5 | # variables are new and need to be declared with `var`, and which are shared
 6 | # with the outside.
 7 | 
 8 | # Import the helpers we plan to use.
 9 | {extend, last} = require './helpers'
10 | 
11 | exports.Scope = class Scope
12 | 
13 |   # The top-level **Scope** object.
14 |   @root: null
15 | 
16 |   # Initialize a scope with its parent, for lookups up the chain,
17 |   # as well as a reference to the **Block** node it belongs to, which is
18 |   # where it should declare its variables, and a reference to the function that
19 |   # it wraps.
20 |   constructor: (@parent, @expressions, @method) ->
21 |     @variables = [{name: 'arguments', type: 'arguments'}]
22 |     @positions = {}
23 |     Scope.root = this unless @parent
24 | 
25 |   # Adds a new variable or overrides an existing one.
26 |   add: (name, type, immediate) ->
27 |     return @parent.add name, type, immediate if @shared and not immediate
28 |     if typeof (pos = @positions[name]) is 'number'
29 |       @variables[pos].type = type
30 |     else
31 |       @positions[name] = @variables.push({name, type}) - 1
32 | 
33 |   # Look up a variable name in lexical scope, and declare it if it does not
34 |   # already exist.
35 |   find: (name, options) ->
36 |     return yes if @check name, options
37 |     @add name, 'var'
38 |     no
39 | 
40 |   # Reserve a variable name as originating from a function parameter for this
41 |   # scope. No `var` required for internal references.
42 |   parameter: (name) ->
43 |     return if @shared and @parent.check name, yes
44 |     @add name, 'param'
45 | 
46 |   # Just check to see if a variable has already been declared, without reserving,
47 |   # walks up to the root scope.
48 |   check: (name, immediate) ->
49 |     found = !!@type(name)
50 |     return found if found or immediate
51 |     !!@parent?.check name
52 | 
53 |   # Generate a temporary variable name at the given index.
54 |   temporary: (name, index) ->
55 |     if name.length > 1
56 |       '_' + name + if index > 1 then index else ''
57 |     else
58 |       '_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a'
59 | 
60 |   # Gets the type of a variable.
61 |   type: (name) ->
62 |     return v.type for v in @variables when v.name is name
63 |     null
64 | 
65 |   # If we need to store an intermediate result, find an available name for a
66 |   # compiler-generated variable. `_var`, `_var2`, and so on...
67 |   freeVariable: (type) ->
68 |     index = 0
69 |     index++ while @check((temp = @temporary type, index))
70 |     @add temp, 'var', yes
71 |     temp
72 | 
73 |   # Ensure that an assignment is made at the top of this scope
74 |   # (or at the top-level scope, if requested).
75 |   assign: (name, value) ->
76 |     @add name, value: value, assigned: true
77 |     @hasAssignments = yes
78 | 
79 |   # Does this scope have any declared variables?
80 |   hasDeclarations: ->
81 |     !!@declaredVariables().length
82 | 
83 |   # Return the list of variables first declared in this scope.
84 |   declaredVariables: ->
85 |     realVars = []
86 |     tempVars = []
87 |     for v in @variables when v.type is 'var'
88 |       (if v.name.charAt(0) is '_' then tempVars else realVars).push v.name
89 |     realVars.sort().concat tempVars.sort()
90 | 
91 |   # Return the list of assignments that are supposed to be made at the top
92 |   # of this scope.
93 |   assignedVariables: ->
94 |     "#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned
95 | 


--------------------------------------------------------------------------------
/test/formatting.coffee:
--------------------------------------------------------------------------------
  1 | # Formatting
  2 | # ----------
  3 | 
  4 | # TODO: maybe this file should be split up into their respective sections:
  5 | #   operators -> operators
  6 | #   array literals -> array literals
  7 | #   string literals -> string literals
  8 | #   function invocations -> function invocations
  9 | 
 10 | # * Line Continuation
 11 | #   * Property Accesss
 12 | #   * Operators
 13 | #   * Array Literals
 14 | #   * Function Invocations
 15 | #   * String Literals
 16 | 
 17 | doesNotThrow -> CoffeeScript.compile "a = then b"
 18 | 
 19 | test "multiple semicolon-separated statements in parentheticals", ->
 20 |   nonce = {}
 21 |   eq nonce, (1; 2; nonce)
 22 |   eq nonce, (-> return (1; 2; nonce))()
 23 | 
 24 | # Line Continuation
 25 | 
 26 | # Property Access
 27 | 
 28 | test "chained accesses split on period/newline, backwards and forwards", ->
 29 |   str = 'abc'
 30 |   result = str.
 31 |     split('').
 32 |     reverse().
 33 |     reverse().
 34 |     reverse()
 35 |   arrayEq ['c','b','a'], result
 36 |   arrayEq ['c','b','a'], str.
 37 |     split('').
 38 |     reverse().
 39 |     reverse().
 40 |     reverse()
 41 |   result = str
 42 |     .split('')
 43 |     .reverse()
 44 |     .reverse()
 45 |     .reverse()
 46 |   arrayEq ['c','b','a'], result
 47 |   arrayEq ['c','b','a'], str
 48 |     .split('')
 49 |     .reverse()
 50 |     .reverse()
 51 |     .reverse()
 52 |   arrayEq ['c','b','a'], str.
 53 |     split('')
 54 |     .reverse().
 55 |     reverse()
 56 |     .reverse()
 57 | 
 58 | # Operators
 59 | 
 60 | test "newline suppression for operators", ->
 61 |   six =
 62 |     1 +
 63 |     2 +
 64 |     3
 65 |   eq 6, six
 66 | 
 67 | test "`?.` and `::` should continue lines", ->
 68 |   ok not Date
 69 |   ::
 70 |   ?.foo
 71 |   #eq Object::toString, Date?.
 72 |   #prototype
 73 |   #::
 74 |   #?.foo
 75 | 
 76 | doesNotThrow -> CoffeeScript.compile """
 77 |   oh. yes
 78 |   oh?. true
 79 |   oh:: return
 80 |   """
 81 | 
 82 | doesNotThrow -> CoffeeScript.compile """
 83 |   a?[b..]
 84 |   a?[...b]
 85 |   a?[b..c]
 86 |   """
 87 | 
 88 | # Array Literals
 89 | 
 90 | test "indented array literals don't trigger whitespace rewriting", ->
 91 |   getArgs = -> arguments
 92 |   result = getArgs(
 93 |     [[[[[],
 94 |                   []],
 95 |                 [[]]]],
 96 |       []])
 97 |   eq 1, result.length
 98 | 
 99 | # Function Invocations
100 | 
101 | doesNotThrow -> CoffeeScript.compile """
102 |   obj = then fn 1,
103 |     1: 1
104 |     a:
105 |       b: ->
106 |         fn c,
107 |           d: e
108 |     f: 1
109 |   """
110 | 
111 | # String Literals
112 | 
113 | test "indented heredoc", ->
114 |   result = ((_) -> _)(
115 |                 """
116 |                 abc
117 |                 """)
118 |   eq "abc", result
119 | 
120 | # Nested blocks caused by paren unwrapping
121 | test "#1492: Nested blocks don't cause double semicolons", ->
122 |   js = CoffeeScript.compile '(0;0)'
123 |   eq -1, js.indexOf ';;'
124 | 
125 | test "#1195 Ignore trailing semicolons (before newlines or as the last char in a program)", ->
126 |     preNewline = (numSemicolons) ->
127 |       """
128 |       nonce = {}; nonce2 = {}
129 |       f = -> nonce#{Array(numSemicolons+1).join(';')}
130 |       nonce2
131 |       unless f() is nonce then throw new Error('; before linebreak should = newline')
132 |       """
133 |     CoffeeScript.run(preNewline(n), bare: true) for n in [1,2,3]
134 |     
135 |     lastChar = '-> lastChar;'
136 |     doesNotThrow -> CoffeeScript.compile lastChar, bare: true
137 | 
138 | test "#1299: Disallow token misnesting", ->
139 |   try
140 |     CoffeeScript.compile '''
141 |       [{
142 |          ]}
143 |     '''
144 |     ok no
145 |   catch e
146 |     eq 'unmatched ] on line 2', e.message
147 | 


--------------------------------------------------------------------------------
/test/soaks.coffee:
--------------------------------------------------------------------------------
  1 | # Soaks
  2 | # -----
  3 | 
  4 | # * Soaked Property Access
  5 | # * Soaked Method Invocation
  6 | # * Soaked Function Invocation
  7 | 
  8 | 
  9 | # Soaked Property Access
 10 | 
 11 | test "soaked property access", ->
 12 |   nonce = {}
 13 |   obj = a: b: nonce
 14 |   eq nonce    , obj?.a.b
 15 |   eq nonce    , obj?['a'].b
 16 |   eq nonce    , obj.a?.b
 17 |   eq nonce    , obj?.a?['b']
 18 |   eq undefined, obj?.a?.non?.existent?.property
 19 | 
 20 | test "soaked property access caches method calls", ->
 21 |   nonce ={}
 22 |   obj = fn: -> a: nonce
 23 |   eq nonce    , obj.fn()?.a
 24 |   eq undefined, obj.fn()?.b
 25 | 
 26 | test "soaked property access chaching", ->
 27 |   nonce = {}
 28 |   counter = 0
 29 |   fn = ->
 30 |     counter++
 31 |     'self'
 32 |   obj =
 33 |     self: -> @
 34 |     prop: nonce
 35 |   eq nonce, obj[fn()]()[fn()]()[fn()]()?.prop
 36 |   eq 3, counter
 37 | 
 38 | test "method calls on soaked methods", ->
 39 |   nonce = {}
 40 |   obj = null
 41 |   eq undefined, obj?.a().b()
 42 |   obj = a: -> b: -> nonce
 43 |   eq nonce    , obj?.a().b()
 44 | 
 45 | test "postfix existential operator mixes well with soaked property accesses", ->
 46 |   eq false, nonexistent?.property?
 47 | 
 48 | test "function invocation with soaked property access", ->
 49 |   id = (_) -> _
 50 |   eq undefined, id nonexistent?.method()
 51 | 
 52 | test "if-to-ternary should safely parenthesize soaked property accesses", ->
 53 |   ok (if nonexistent?.property then false else true)
 54 | 
 55 | test "#726", ->
 56 |   # TODO: check this test, looks like it's not really testing anything
 57 |   eq undefined, nonexistent?[Date()]
 58 | 
 59 | test "#756", ->
 60 |   # TODO: improve this test
 61 |   a = null
 62 |   ok isNaN      a?.b.c +  1
 63 |   eq undefined, a?.b.c += 1
 64 |   eq undefined, ++a?.b.c
 65 |   eq undefined, delete a?.b.c
 66 | 
 67 | test "operations on soaked properties", ->
 68 |   # TODO: improve this test
 69 |   a = b: {c: 0}
 70 |   eq 1,   a?.b.c +  1
 71 |   eq 1,   a?.b.c += 1
 72 |   eq 2,   ++a?.b.c
 73 |   eq yes, delete a?.b.c
 74 | 
 75 | 
 76 | # Soaked Method Invocation
 77 | 
 78 | test "soaked method invocation", ->
 79 |   nonce = {}
 80 |   counter = 0
 81 |   obj =
 82 |     self: -> @
 83 |     increment: -> counter++; @
 84 |   eq obj      , obj.self?()
 85 |   eq undefined, obj.method?()
 86 |   eq nonce    , obj.self?().property = nonce
 87 |   eq undefined, obj.method?().property = nonce
 88 |   eq obj      , obj.increment().increment().self?()
 89 |   eq 2        , counter
 90 | 
 91 | test "#733", ->
 92 |   a = b: {c: null}
 93 |   eq a.b?.c?(), undefined
 94 |   a.b?.c or= (it) -> it
 95 |   eq a.b?.c?(1), 1
 96 |   eq a.b?.c?([2, 3]...), 2
 97 | 
 98 | 
 99 | # Soaked Function Invocation
100 | 
101 | test "soaked function invocation", ->
102 |   nonce = {}
103 |   id = (_) -> _
104 |   eq nonce    , id?(nonce)
105 |   eq nonce    , (id? nonce)
106 |   eq undefined, nonexistent?(nonce)
107 |   eq undefined, (nonexistent? nonce)
108 | 
109 | test "soaked function invocation with generated functions", ->
110 |   nonce = {}
111 |   id = (_) -> _
112 |   maybe = (fn, arg) -> if typeof fn is 'function' then () -> fn(arg)
113 |   eq maybe(id, nonce)?(), nonce
114 |   eq (maybe id, nonce)?(), nonce
115 |   eq (maybe false, nonce)?(), undefined
116 | 
117 | test "soaked constructor invocation", ->
118 |   eq 42       , +new Number? 42
119 |   eq undefined,  new Other?  42
120 | 
121 | test "soaked constructor invocations with caching and property access", ->
122 |   semaphore = 0
123 |   nonce = {}
124 |   class C
125 |     constructor: ->
126 |       ok false if semaphore
127 |       semaphore++
128 |     prop: nonce
129 |   eq nonce, (new C())?.prop
130 |   eq 1, semaphore
131 | 
132 | test "soaked function invocation safe on non-functions", ->
133 |   eq undefined, 0?(1)
134 |   eq undefined, 0? 1, 2
135 | 


--------------------------------------------------------------------------------
/test/comments.coffee:
--------------------------------------------------------------------------------
  1 | # Comments
  2 | # --------
  3 | 
  4 | # * Single-Line Comments
  5 | # * Block Comments
  6 | 
  7 | # Note: awkward spacing seen in some tests is likely intentional.
  8 | 
  9 | test "comments in objects", ->
 10 |   obj1 = {
 11 |   # comment
 12 |     # comment
 13 |       # comment
 14 |     one: 1
 15 |   # comment
 16 |     two: 2
 17 |       # comment
 18 |   }
 19 | 
 20 |   ok Object::hasOwnProperty.call(obj1,'one')
 21 |   eq obj1.one, 1
 22 |   ok Object::hasOwnProperty.call(obj1,'two')
 23 |   eq obj1.two, 2
 24 | 
 25 | test "comments in YAML-style objects", ->
 26 |   obj2 =
 27 |   # comment
 28 |     # comment
 29 |       # comment
 30 |     three: 3
 31 |   # comment
 32 |     four: 4
 33 |       # comment
 34 | 
 35 |   ok Object::hasOwnProperty.call(obj2,'three')
 36 |   eq obj2.three, 3
 37 |   ok Object::hasOwnProperty.call(obj2,'four')
 38 |   eq obj2.four, 4
 39 | 
 40 | test "comments following operators that continue lines", ->
 41 |   sum =
 42 |     1 +
 43 |     1 + # comment
 44 |     1
 45 |   eq 3, sum
 46 | 
 47 | test "comments in functions", ->
 48 |   fn = ->
 49 |   # comment
 50 |     false
 51 |     false   # comment
 52 |     false
 53 |     # comment
 54 | 
 55 |   # comment
 56 |     true
 57 | 
 58 |   ok fn()
 59 | 
 60 |   fn2 = -> #comment
 61 |     fn()
 62 |     # comment
 63 | 
 64 |   ok fn2()
 65 | 
 66 | test "trailing comment before an outdent", ->
 67 |   nonce = {}
 68 |   fn3 = ->
 69 |     if true
 70 |       undefined # comment
 71 |     nonce
 72 | 
 73 |   eq nonce, fn3()
 74 | 
 75 | test "comments in a switch", ->
 76 |   nonce = {}
 77 |   result = switch nonce #comment
 78 |     # comment
 79 |     when false then undefined
 80 |     # comment
 81 |     when null #comment
 82 |       undefined
 83 |     else nonce # comment
 84 | 
 85 |   eq nonce, result
 86 | 
 87 | test "comment with conditional statements", ->
 88 |   nonce = {}
 89 |   result = if false # comment
 90 |     undefined
 91 |   #comment
 92 |   else # comment
 93 |     nonce
 94 |     # comment
 95 |   eq nonce, result
 96 | 
 97 | test "spaced comments with conditional statements", ->
 98 |   nonce = {}
 99 |   result = if false
100 |     undefined
101 | 
102 |   # comment
103 |   else if false
104 |     undefined
105 | 
106 |   # comment
107 |   else
108 |     nonce
109 | 
110 |   eq nonce, result
111 | 
112 | 
113 | # Block Comments
114 | 
115 | ###
116 |   This is a here-comment.
117 |   Kind of like a heredoc.
118 | ###
119 | 
120 | test "block comments in objects", ->
121 |   a = {}
122 |   b = {}
123 |   obj = {
124 |     a: a
125 |     ###
126 |     comment
127 |     ###
128 |     b: b
129 |   }
130 | 
131 |   eq a, obj.a
132 |   eq b, obj.b
133 | 
134 | test "block comments in YAML-style", ->
135 |   a = {}
136 |   b = {}
137 |   obj =
138 |     a: a
139 |     ###
140 |     comment
141 |     ###
142 |     b: b
143 | 
144 |   eq a, obj.a
145 |   eq b, obj.b
146 | 
147 | 
148 | test "block comments in functions", ->
149 |   nonce = {}
150 | 
151 |   fn1 = ->
152 |     true
153 |     ###
154 |     false
155 |     ###
156 | 
157 |   ok fn1()
158 | 
159 |   fn2 =  ->
160 |     ###
161 |     block comment
162 |     ###
163 |     nonce
164 | 
165 |   eq nonce, fn2()
166 | 
167 |   fn3 = ->
168 |     nonce
169 |   ###
170 |   block comment
171 |   ###
172 | 
173 |   eq nonce, fn3()
174 | 
175 |   fn4 = ->
176 |     one = ->
177 |       ###
178 |         block comment
179 |       ###
180 |       two = ->
181 |         three = ->
182 |           nonce
183 | 
184 |   eq nonce, fn4()()()()
185 | 
186 | test "block comments inside class bodies", ->
187 |   class A
188 |     a: ->
189 | 
190 |     ###
191 |     Comment
192 |     ###
193 |     b: ->
194 | 
195 |   ok A.prototype.b instanceof Function
196 | 
197 |   class B
198 |     ###
199 |     Comment
200 |     ###
201 |     a: ->
202 |     b: ->
203 | 
204 |   ok B.prototype.a instanceof Function
205 | 


--------------------------------------------------------------------------------
/lib/coffee-script/scope.js:
--------------------------------------------------------------------------------
  1 | (function() {
  2 |   var Scope, extend, last, _ref;
  3 |   _ref = require('./helpers'), extend = _ref.extend, last = _ref.last;
  4 |   exports.Scope = Scope = (function() {
  5 |     Scope.root = null;
  6 |     function Scope(parent, expressions, method) {
  7 |       this.parent = parent;
  8 |       this.expressions = expressions;
  9 |       this.method = method;
 10 |       this.variables = [
 11 |         {
 12 |           name: 'arguments',
 13 |           type: 'arguments'
 14 |         }
 15 |       ];
 16 |       this.positions = {};
 17 |       if (!this.parent) Scope.root = this;
 18 |     }
 19 |     Scope.prototype.add = function(name, type, immediate) {
 20 |       var pos;
 21 |       if (this.shared && !immediate) return this.parent.add(name, type, immediate);
 22 |       if (typeof (pos = this.positions[name]) === 'number') {
 23 |         return this.variables[pos].type = type;
 24 |       } else {
 25 |         return this.positions[name] = this.variables.push({
 26 |           name: name,
 27 |           type: type
 28 |         }) - 1;
 29 |       }
 30 |     };
 31 |     Scope.prototype.find = function(name, options) {
 32 |       if (this.check(name, options)) return true;
 33 |       this.add(name, 'var');
 34 |       return false;
 35 |     };
 36 |     Scope.prototype.parameter = function(name) {
 37 |       if (this.shared && this.parent.check(name, true)) return;
 38 |       return this.add(name, 'param');
 39 |     };
 40 |     Scope.prototype.check = function(name, immediate) {
 41 |       var found, _ref2;
 42 |       found = !!this.type(name);
 43 |       if (found || immediate) return found;
 44 |       return !!((_ref2 = this.parent) != null ? _ref2.check(name) : void 0);
 45 |     };
 46 |     Scope.prototype.temporary = function(name, index) {
 47 |       if (name.length > 1) {
 48 |         return '_' + name + (index > 1 ? index : '');
 49 |       } else {
 50 |         return '_' + (index + parseInt(name, 36)).toString(36).replace(/\d/g, 'a');
 51 |       }
 52 |     };
 53 |     Scope.prototype.type = function(name) {
 54 |       var v, _i, _len, _ref2;
 55 |       _ref2 = this.variables;
 56 |       for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
 57 |         v = _ref2[_i];
 58 |         if (v.name === name) return v.type;
 59 |       }
 60 |       return null;
 61 |     };
 62 |     Scope.prototype.freeVariable = function(type) {
 63 |       var index, temp;
 64 |       index = 0;
 65 |       while (this.check((temp = this.temporary(type, index)))) {
 66 |         index++;
 67 |       }
 68 |       this.add(temp, 'var', true);
 69 |       return temp;
 70 |     };
 71 |     Scope.prototype.assign = function(name, value) {
 72 |       this.add(name, {
 73 |         value: value,
 74 |         assigned: true
 75 |       });
 76 |       return this.hasAssignments = true;
 77 |     };
 78 |     Scope.prototype.hasDeclarations = function() {
 79 |       return !!this.declaredVariables().length;
 80 |     };
 81 |     Scope.prototype.declaredVariables = function() {
 82 |       var realVars, tempVars, v, _i, _len, _ref2;
 83 |       realVars = [];
 84 |       tempVars = [];
 85 |       _ref2 = this.variables;
 86 |       for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
 87 |         v = _ref2[_i];
 88 |         if (v.type === 'var') {
 89 |           (v.name.charAt(0) === '_' ? tempVars : realVars).push(v.name);
 90 |         }
 91 |       }
 92 |       return realVars.sort().concat(tempVars.sort());
 93 |     };
 94 |     Scope.prototype.assignedVariables = function() {
 95 |       var v, _i, _len, _ref2, _results;
 96 |       _ref2 = this.variables;
 97 |       _results = [];
 98 |       for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
 99 |         v = _ref2[_i];
100 |         if (v.type.assigned) _results.push("" + v.name + " = " + v.type.value);
101 |       }
102 |       return _results;
103 |     };
104 |     return Scope;
105 |   })();
106 | }).call(this);
107 | 


--------------------------------------------------------------------------------
/examples/code.coffee:
--------------------------------------------------------------------------------
  1 | # Functions:
  2 | square = (x) -> x * x
  3 | 
  4 | sum = (x, y) -> x + y
  5 | 
  6 | odd = (x) -> x % 2 isnt 0
  7 | 
  8 | even = (x) -> x % 2 is 0
  9 | 
 10 | run_loop = ->
 11 |   fire_events((e) -> e.stopPropagation())
 12 |   listen()
 13 |   wait()
 14 | 
 15 | # Objects:
 16 | dense_object_literal = {one: 1, two: 2, three: 3}
 17 | 
 18 | spaced_out_multiline_object =
 19 |   pi: 3.14159
 20 |   list: [1, 2, 3, 4]
 21 |   regex: /match[ing](every|thing|\/)/gi
 22 |   three: new Idea
 23 | 
 24 |   inner_obj:
 25 |     freedom: -> _.freedom()
 26 | 
 27 | # Arrays:
 28 | stooges = [{moe: 45}, {curly: 43}, {larry: 46}]
 29 | 
 30 | exponents = [((x) -> x), ((x) -> x * x), ((x) -> x * x * x)]
 31 | 
 32 | empty = []
 33 | 
 34 | multiline = [
 35 |   'line one'
 36 |   'line two'
 37 | ]
 38 | 
 39 | # Conditionals and ternaries.
 40 | if submarine.shields_up
 41 |   full_speed_ahead()
 42 |   fire_torpedos()
 43 | else if submarine.sinking
 44 |   abandon_ship()
 45 | else
 46 |   run_away()
 47 | 
 48 | eldest = if 25 > 21 then liz else marge
 49 | 
 50 | decoration = medal_of_honor if war_hero
 51 | 
 52 | go_to_sleep() unless coffee
 53 | 
 54 | # Returning early:
 55 | race = ->
 56 |   run()
 57 |   walk()
 58 |   crawl()
 59 |   if tired then return sleep()
 60 |   race()
 61 | 
 62 | # Conditional assignment:
 63 | good or= evil
 64 | wine and= cheese
 65 | 
 66 | # Nested property access and calls.
 67 | ((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position['top'].offset('x')
 68 | 
 69 | a = b = c = 5
 70 | 
 71 | # Embedded JavaScript.
 72 | callback(
 73 |   `function(e) { e.stop(); }`
 74 | )
 75 | 
 76 | # Try/Catch/Finally/Throw.
 77 | try
 78 |   all_hell_breaks_loose()
 79 |   dogs_and_cats_living_together()
 80 |   throw "up"
 81 | catch error
 82 |   print(error)
 83 | finally
 84 |   clean_up()
 85 | 
 86 | try all_hell_breaks_loose() catch error then print(error) finally clean_up()
 87 | 
 88 | # While loops, break and continue.
 89 | while demand > supply
 90 |   sell()
 91 |   restock()
 92 | 
 93 | while supply > demand then buy()
 94 | 
 95 | loop
 96 |   break if broken
 97 |   continue if continuing
 98 | 
 99 | # Unary operators.
100 | !!true
101 | 
102 | # Lexical scoping.
103 | v_1 = 5
104 | change_a_and_set_b = ->
105 |   v_1 = 10
106 |   v_2 = 15
107 | v_2 = 20
108 | 
109 | # Array comprehensions.
110 | supper = food.capitalize() for food in ['toast', 'cheese', 'wine']
111 | 
112 | drink bottle for bottle, i in ['soda', 'wine', 'lemonade'] when even i
113 | 
114 | # Switch statements ("else" serves as a default).
115 | activity = switch day
116 |   when "Tuesday"   then eat_breakfast()
117 |   when "Sunday"    then go_to_church()
118 |   when "Saturday"  then go_to_the_park()
119 |   when "Wednesday"
120 |     if day is bingo_day
121 |       go_to_bingo()
122 |     else
123 |       eat_breakfast()
124 |       go_to_work()
125 |       eat_dinner()
126 |   else go_to_work()
127 | 
128 | # Semicolons can optionally be used instead of newlines.
129 | wednesday = -> eat_breakfast(); go_to_work(); eat_dinner()
130 | 
131 | # Multiline strings with inner quotes.
132 | story = "Lorem ipsum dolor \"sit\" amet, consectetuer adipiscing elit,
133 | sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna
134 | aliquam erat volutpat. Ut wisi enim ad."
135 | 
136 | # Inheritance and calling super.
137 | class Animal
138 |   (@name) ->
139 | 
140 |   move: (meters) ->
141 |     alert this.name + " moved " + meters + "m."
142 | 
143 | class Snake extends Animal
144 |   move: ->
145 |     alert 'Slithering...'
146 |     super 5
147 | 
148 | class Horse extends Animal
149 |   move: ->
150 |     alert 'Galloping...'
151 |     super 45
152 | 
153 | sam = new Snake "Sammy the Snake"
154 | tom = new Horse "Tommy the Horse"
155 | 
156 | sam.move()
157 | tom.move()
158 | 
159 | # Numbers.
160 | a_googol =  1e100
161 | hex      =  0xff0000
162 | negative =  -1.0
163 | infinity =  Infinity
164 | nan      =  NaN
165 | 
166 | # Deleting.
167 | delete secret.identity


--------------------------------------------------------------------------------
/lib/coffee-script/optparse.js:
--------------------------------------------------------------------------------
  1 | (function() {
  2 |   var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments;
  3 |   exports.OptionParser = OptionParser = (function() {
  4 |     function OptionParser(rules, banner) {
  5 |       this.banner = banner;
  6 |       this.rules = buildRules(rules);
  7 |     }
  8 |     OptionParser.prototype.parse = function(args) {
  9 |       var arg, i, isOption, matchedRule, options, rule, value, _i, _len, _len2, _ref;
 10 |       options = {
 11 |         arguments: [],
 12 |         literals: []
 13 |       };
 14 |       args = normalizeArguments(args);
 15 |       for (i = 0, _len = args.length; i < _len; i++) {
 16 |         arg = args[i];
 17 |         if (arg === '--') {
 18 |           options.literals = args.slice(i + 1);
 19 |           break;
 20 |         }
 21 |         isOption = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG));
 22 |         matchedRule = false;
 23 |         _ref = this.rules;
 24 |         for (_i = 0, _len2 = _ref.length; _i < _len2; _i++) {
 25 |           rule = _ref[_i];
 26 |           if (rule.shortFlag === arg || rule.longFlag === arg) {
 27 |             value = rule.hasArgument ? args[i += 1] : true;
 28 |             options[rule.name] = rule.isList ? (options[rule.name] || []).concat(value) : value;
 29 |             matchedRule = true;
 30 |             break;
 31 |           }
 32 |         }
 33 |         if (isOption && !matchedRule) {
 34 |           throw new Error("unrecognized option: " + arg);
 35 |         }
 36 |         if (!isOption) {
 37 |           options.arguments = args.slice(i);
 38 |           break;
 39 |         }
 40 |       }
 41 |       return options;
 42 |     };
 43 |     OptionParser.prototype.help = function() {
 44 |       var letPart, lines, rule, spaces, _i, _len, _ref;
 45 |       lines = [];
 46 |       if (this.banner) lines.unshift("" + this.banner + "\n");
 47 |       _ref = this.rules;
 48 |       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
 49 |         rule = _ref[_i];
 50 |         spaces = 15 - rule.longFlag.length;
 51 |         spaces = spaces > 0 ? Array(spaces + 1).join(' ') : '';
 52 |         letPart = rule.shortFlag ? rule.shortFlag + ', ' : '    ';
 53 |         lines.push('  ' + letPart + rule.longFlag + spaces + rule.description);
 54 |       }
 55 |       return "\n" + (lines.join('\n')) + "\n";
 56 |     };
 57 |     return OptionParser;
 58 |   })();
 59 |   LONG_FLAG = /^(--\w[\w\-]+)/;
 60 |   SHORT_FLAG = /^(-\w)/;
 61 |   MULTI_FLAG = /^-(\w{2,})/;
 62 |   OPTIONAL = /\[(\w+(\*?))\]/;
 63 |   buildRules = function(rules) {
 64 |     var tuple, _i, _len, _results;
 65 |     _results = [];
 66 |     for (_i = 0, _len = rules.length; _i < _len; _i++) {
 67 |       tuple = rules[_i];
 68 |       if (tuple.length < 3) tuple.unshift(null);
 69 |       _results.push(buildRule.apply(null, tuple));
 70 |     }
 71 |     return _results;
 72 |   };
 73 |   buildRule = function(shortFlag, longFlag, description, options) {
 74 |     var match;
 75 |     if (options == null) options = {};
 76 |     match = longFlag.match(OPTIONAL);
 77 |     longFlag = longFlag.match(LONG_FLAG)[1];
 78 |     return {
 79 |       name: longFlag.substr(2),
 80 |       shortFlag: shortFlag,
 81 |       longFlag: longFlag,
 82 |       description: description,
 83 |       hasArgument: !!(match && match[1]),
 84 |       isList: !!(match && match[2])
 85 |     };
 86 |   };
 87 |   normalizeArguments = function(args) {
 88 |     var arg, l, match, result, _i, _j, _len, _len2, _ref;
 89 |     args = args.slice(0);
 90 |     result = [];
 91 |     for (_i = 0, _len = args.length; _i < _len; _i++) {
 92 |       arg = args[_i];
 93 |       if (match = arg.match(MULTI_FLAG)) {
 94 |         _ref = match[1].split('');
 95 |         for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
 96 |           l = _ref[_j];
 97 |           result.push('-' + l);
 98 |         }
 99 |       } else {
100 |         result.push(arg);
101 |       }
102 |     }
103 |     return result;
104 |   };
105 | }).call(this);
106 | 


--------------------------------------------------------------------------------
/src/optparse.coffee:
--------------------------------------------------------------------------------
  1 | # A simple **OptionParser** class to parse option flags from the command-line.
  2 | # Use it like so:
  3 | #
  4 | #     parser  = new OptionParser switches, helpBanner
  5 | #     options = parser.parse process.argv
  6 | #
  7 | # The first non-option is considered to be the start of the file (and file
  8 | # option) list, and all subsequent arguments are left unparsed.
  9 | exports.OptionParser = class OptionParser
 10 | 
 11 |   # Initialize with a list of valid options, in the form:
 12 |   #
 13 |   #     [short-flag, long-flag, description]
 14 |   #
 15 |   # Along with an an optional banner for the usage help.
 16 |   constructor: (rules, @banner) ->
 17 |     @rules = buildRules rules
 18 | 
 19 |   # Parse the list of arguments, populating an `options` object with all of the
 20 |   # specified options, and return it. `options.arguments` will be an array
 21 |   # containing the remaining non-option arguments. `options.literals` will be
 22 |   # an array of options that are meant to be passed through directly to the
 23 |   # executing script. This is a simpler API than many option parsers that allow
 24 |   # you to attach callback actions for every flag. Instead, you're responsible
 25 |   # for interpreting the options object.
 26 |   parse: (args) ->
 27 |     options = arguments: [], literals: []
 28 |     args    = normalizeArguments args
 29 |     for arg, i in args
 30 |       if arg is '--'
 31 |         options.literals = args[(i + 1)..]
 32 |         break
 33 |       isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
 34 |       matchedRule = no
 35 |       for rule in @rules
 36 |         if rule.shortFlag is arg or rule.longFlag is arg
 37 |           value = if rule.hasArgument then args[i += 1] else true
 38 |           options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
 39 |           matchedRule = yes
 40 |           break
 41 |       throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
 42 |       if not isOption
 43 |         options.arguments = args.slice i
 44 |         break
 45 |     options
 46 | 
 47 |   # Return the help text for this **OptionParser**, listing and describing all
 48 |   # of the valid options, for `--help` and such.
 49 |   help: ->
 50 |     lines = []
 51 |     lines.unshift "#{@banner}\n" if @banner
 52 |     for rule in @rules
 53 |       spaces  = 15 - rule.longFlag.length
 54 |       spaces  = if spaces > 0 then Array(spaces + 1).join(' ') else ''
 55 |       letPart = if rule.shortFlag then rule.shortFlag + ', ' else '    '
 56 |       lines.push '  ' + letPart + rule.longFlag + spaces + rule.description
 57 |     "\n#{ lines.join('\n') }\n"
 58 | 
 59 | # Helpers
 60 | # -------
 61 | 
 62 | # Regex matchers for option flags.
 63 | LONG_FLAG  = /^(--\w[\w\-]+)/
 64 | SHORT_FLAG = /^(-\w)/
 65 | MULTI_FLAG = /^-(\w{2,})/
 66 | OPTIONAL   = /\[(\w+(\*?))\]/
 67 | 
 68 | # Build and return the list of option rules. If the optional *short-flag* is
 69 | # unspecified, leave it out by padding with `null`.
 70 | buildRules = (rules) ->
 71 |   for tuple in rules
 72 |     tuple.unshift null if tuple.length < 3
 73 |     buildRule tuple...
 74 | 
 75 | # Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
 76 | # description of what the option does.
 77 | buildRule = (shortFlag, longFlag, description, options = {}) ->
 78 |   match     = longFlag.match(OPTIONAL)
 79 |   longFlag  = longFlag.match(LONG_FLAG)[1]
 80 |   {
 81 |     name:         longFlag.substr 2
 82 |     shortFlag:    shortFlag
 83 |     longFlag:     longFlag
 84 |     description:  description
 85 |     hasArgument:  !!(match and match[1])
 86 |     isList:       !!(match and match[2])
 87 |   }
 88 | 
 89 | # Normalize arguments by expanding merged flags into multiple flags. This allows
 90 | # you to have `-wl` be the same as `--watch --lint`.
 91 | normalizeArguments = (args) ->
 92 |   args = args.slice 0
 93 |   result = []
 94 |   for arg in args
 95 |     if match = arg.match MULTI_FLAG
 96 |       result.push '-' + l for l in match[1].split ''
 97 |     else
 98 |       result.push arg
 99 |   result
100 | 


--------------------------------------------------------------------------------
/test/interpolation.coffee:
--------------------------------------------------------------------------------
  1 | # Interpolation
  2 | # -------------
  3 | 
  4 | # * String Interpolation
  5 | # * Regular Expression Interpolation
  6 | 
  7 | # String Interpolation
  8 | 
  9 | # TODO: refactor string interpolation tests
 10 | 
 11 | eq 'multiline nested "interpolations" work', """multiline #{
 12 |   "nested #{
 13 |     ok true
 14 |     "\"interpolations\""
 15 |   }"
 16 | } work"""
 17 | 
 18 | # Issue #923: Tricky interpolation.
 19 | eq "#{ "{" }", "{"
 20 | eq "#{ '#{}}' } }", '#{}} }'
 21 | eq "#{"'#{ ({a: "b#{1}"}['a']) }'"}", "'b1'"
 22 | 
 23 | # Issue #1150: String interpolation regression
 24 | eq "#{'"/'}",                '"/'
 25 | eq "#{"/'"}",                "/'"
 26 | eq "#{/'"/}",                '/\'"/'
 27 | eq "#{"'/" + '/"' + /"'/}",  '\'//"/"\'/'
 28 | eq "#{"'/"}#{'/"'}#{/"'/}",  '\'//"/"\'/'
 29 | eq "#{6 / 2}",               '3'
 30 | eq "#{6 / 2}#{6 / 2}",       '33' # parsed as division
 31 | eq "#{6 + /2}#{6/ + 2}",     '6/2}#{6/2' # parsed as a regex
 32 | eq "#{6/2}
 33 |     #{6/2}",                 '3    3' # newline cannot be part of a regex, so it's division
 34 | eq "#{/// "'/'"/" ///}",     '/"\'\\/\'"\\/"/' # heregex, stuffed with spicy characters
 35 | eq "#{/\\'/}",               "/\\\\'/"
 36 | 
 37 | hello = 'Hello'
 38 | world = 'World'
 39 | ok '#{hello} #{world}!' is '#{hello} #{world}!'
 40 | ok "#{hello} #{world}!" is 'Hello World!'
 41 | ok "[#{hello}#{world}]" is '[HelloWorld]'
 42 | ok "#{hello}##{world}" is 'Hello#World'
 43 | ok "Hello #{ 1 + 2 } World" is 'Hello 3 World'
 44 | ok "#{hello} #{ 1 + 2 } #{world}" is "Hello 3 World"
 45 | 
 46 | [s, t, r, i, n, g] = ['s', 't', 'r', 'i', 'n', 'g']
 47 | ok "#{s}#{t}#{r}#{i}#{n}#{g}" is 'string'
 48 | ok "\#{s}\#{t}\#{r}\#{i}\#{n}\#{g}" is '#{s}#{t}#{r}#{i}#{n}#{g}'
 49 | ok "\#{string}" is '#{string}'
 50 | 
 51 | ok "\#{Escaping} first" is '#{Escaping} first'
 52 | ok "Escaping \#{in} middle" is 'Escaping #{in} middle'
 53 | ok "Escaping \#{last}" is 'Escaping #{last}'
 54 | 
 55 | ok "##" is '##'
 56 | ok "#{}" is ''
 57 | ok "#{}A#{} #{} #{}B#{}" is 'A  B'
 58 | ok "\\\#{}" is '\\#{}'
 59 | 
 60 | ok "I won ##{20} last night." is 'I won #20 last night.'
 61 | ok "I won ##{'#20'} last night." is 'I won ##20 last night.'
 62 | 
 63 | ok "#{hello + world}" is 'HelloWorld'
 64 | ok "#{hello + ' ' + world + '!'}" is 'Hello World!'
 65 | 
 66 | list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 67 | ok "values: #{list.join(', ')}, length: #{list.length}." is 'values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, length: 10.'
 68 | ok "values: #{list.join ' '}" is 'values: 0 1 2 3 4 5 6 7 8 9'
 69 | 
 70 | obj = {
 71 |   name: 'Joe'
 72 |   hi: -> "Hello #{@name}."
 73 |   cya: -> "Hello #{@name}.".replace('Hello','Goodbye')
 74 | }
 75 | ok obj.hi() is "Hello Joe."
 76 | ok obj.cya() is "Goodbye Joe."
 77 | 
 78 | ok "With #{"quotes"}" is 'With quotes'
 79 | ok 'With #{"quotes"}' is 'With #{"quotes"}'
 80 | 
 81 | ok "Where is #{obj["name"] + '?'}" is 'Where is Joe?'
 82 | 
 83 | ok "Where is #{"the nested #{obj["name"]}"}?" is 'Where is the nested Joe?'
 84 | ok "Hello #{world ? "#{hello}"}" is 'Hello World'
 85 | 
 86 | ok "Hello #{"#{"#{obj["name"]}" + '!'}"}" is 'Hello Joe!'
 87 | 
 88 | a = """
 89 |     Hello #{ "Joe" }
 90 |     """
 91 | ok a is "Hello Joe"
 92 | 
 93 | a = 1
 94 | b = 2
 95 | c = 3
 96 | ok "#{a}#{b}#{c}" is '123'
 97 | 
 98 | result = null
 99 | stash = (str) -> result = str
100 | stash "a #{ ('aa').replace /a/g, 'b' } c"
101 | ok result is 'a bb c'
102 | 
103 | foo = "hello"
104 | ok "#{foo.replace("\"", "")}" is 'hello'
105 | 
106 | val = 10
107 | a = """
108 |     basic heredoc #{val}
109 |     on two lines
110 |     """
111 | b = '''
112 |     basic heredoc #{val}
113 |     on two lines
114 |     '''
115 | ok a is "basic heredoc 10\non two lines"
116 | ok b is "basic heredoc \#{val}\non two lines"
117 | 
118 | eq 'multiline nested "interpolations" work', """multiline #{
119 |   "nested #{(->
120 |     ok yes
121 |     "\"interpolations\""
122 |   )()}"
123 | } work"""
124 | 
125 | 
126 | # Regular Expression Interpolation
127 | 
128 | # TODO: improve heregex interpolation tests
129 | 
130 | test "heregex interpolation", ->
131 |   eq /\\#{}\\\"/ + '', ///
132 |    #{
133 |      "#{ '\\' }" # normal comment
134 |    }
135 |    # regex comment
136 |    \#{}
137 |    \\ \"
138 |   /// + ''
139 | 


--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
  1 | First cut at a Stratified CoffeeScript compiler.
  2 | ================================================
  3 | 
  4 | - Compiles to StratifiedJS (http://onilabs.com/stratifiedjs)
  5 | 
  6 | - Supports the following constructs:
  7 | 
  8 | Stratified CoffeeScript  -->     StratifiedJS
  9 | -----------------------          ------------
 10 | 
 11 | waitfor                  -->     waitfor () {
 12 |   asyncFunc resume                 asyncFunc(resume)
 13 |                                  }
 14 | 
 15 | waitfor rv               -->     waitfor (rv) {
 16 |   asyncFunc resume                 asyncFunc(resume)
 17 |                                  }
 18 | 
 19 | waitfor                  -->     waitfor {
 20 |   foo a                            foo(a);
 21 | then                             }
 22 |   bar x                          and {
 23 |                                    bar(x);
 24 |                                  }
 25 | 
 26 | waitfor                  -->     waitfor {
 27 |   foo a                            foo(a);
 28 | else                             }
 29 |   bar x                          or {
 30 |                                    bar(x);
 31 |                                  }
 32 | 
 33 | try                      -->     try {
 34 |   foo a                            foo(a);
 35 | retract                          }
 36 |   cleanup a                      retract {
 37 |                                    cleanup(a);
 38 |                                  }
 39 | 
 40 | - You can use catch/retract/finally also directly after waitfor, e.g.:
 41 | 
 42 | waitfor                   -->   waitfor () {
 43 |   id = setTimeout resume,         var id = setTimeout(resume,1000);
 44 |          1000                   }
 45 | retract                         retract {
 46 |   cancelTimeout id                cancelTimeout(id);
 47 |                                 }
 48 | 
 49 | - NOTE THE USE OF 'then' AND 'else' INSTEAD OF 'and'/'or'. Ideally we
 50 |   would like 'and'/'or' also in CoffeeScript, but they are kinda hard to
 51 |   parse, because CoffeeScript uses them for other stuff as well.
 52 | 
 53 | 
 54 | 
 55 | ORIGINAL README CONTENTS:
 56 | =========================
 57 | 
 58 |             {
 59 |          }   }   {
 60 |         {   {  }  }
 61 |          }   }{  {
 62 |         {  }{  }  }                    _____       __  __
 63 |        ( }{ }{  { )                   / ____|     / _|/ _|
 64 |      .- { { }  { }} -.               | |     ___ | |_| |_ ___  ___
 65 |     (  ( } { } { } }  )              | |    / _ \|  _|  _/ _ \/ _ \
 66 |     |`-..________ ..-'|              | |___| (_) | | | ||  __/  __/
 67 |     |                 |               \_____\___/|_| |_| \___|\___|
 68 |     |                 ;--.
 69 |     |                (__  \            _____           _       _
 70 |     |                 | )  )          / ____|         (_)     | |
 71 |     |                 |/  /          | (___   ___ _ __ _ _ __ | |_
 72 |     |                 (  /            \___ \ / __| '__| | '_ \| __|
 73 |     |                 |/              ____) | (__| |  | | |_) | |_
 74 |     |                 |              |_____/ \___|_|  |_| .__/ \__|
 75 |      `-.._________..-'                                  | |
 76 |                                                         |_|
 77 | 
 78 | 
 79 |   CoffeeScript is a little language that compiles into JavaScript.
 80 | 
 81 |   Install Node.js, and then the CoffeeScript compiler:
 82 |   sudo bin/cake install
 83 | 
 84 |   Or, if you have the Node Package Manager installed:
 85 |   npm install -g coffee-script
 86 |   (Leave off the -g if you don't wish to install globally.)
 87 | 
 88 |   Execute a script:
 89 |   coffee /path/to/script.coffee
 90 | 
 91 |   Compile a script:
 92 |   coffee -c /path/to/script.coffee
 93 | 
 94 |   For documentation, usage, and examples, see:
 95 |   http://coffeescript.org/
 96 | 
 97 |   To suggest a feature, report a bug, or general discussion:
 98 |   http://github.com/jashkenas/coffee-script/issues/
 99 | 
100 |   If you'd like to chat, drop by #coffeescript on Freenode IRC,
101 |   or on webchat.freenode.net.
102 | 
103 |   The source repository:
104 |   git://github.com/jashkenas/coffee-script.git
105 | 
106 |   All contributors are listed here:
107 |   http://github.com/jashkenas/coffee-script/contributors
108 | 


--------------------------------------------------------------------------------
/examples/potion.coffee:
--------------------------------------------------------------------------------
  1 | # Examples from _why's Potion, the Readme and "Potion: A Short Pamphlet".
  2 | 
  3 | # 5 times: "Odelay!" print.
  4 | 
  5 | print "Odelay!" for i in [1..5]
  6 | 
  7 | 
  8 | # add = (x, y): x + y.
  9 | # add(2, 4) string print
 10 | 
 11 | add = (x, y) -> x + y
 12 | print add 2, 4
 13 | 
 14 | 
 15 | # loop: 'quaff' print.
 16 | 
 17 | loop print 'quaff'
 18 | 
 19 | 
 20 | # ('cheese', 'bread', 'mayo') at (1) print
 21 | 
 22 | print ['cheese', 'bread', 'mayo'][1]
 23 | 
 24 | 
 25 | # (language='Potion', pointless=true) at (key='language') print
 26 | 
 27 | print {language: 'Potion', pointless: true}['language']
 28 | 
 29 | 
 30 | # minus = (x, y): x - y.
 31 | # minus (y=10, x=6)
 32 | 
 33 | minus = (x, y) -> x - y
 34 | minus 6, 10
 35 | 
 36 | 
 37 | # foods = ('cheese', 'bread', 'mayo')
 38 | # foods (2)
 39 | 
 40 | foods = ['cheese', 'bread', 'mayo']
 41 | foods[2]
 42 | 
 43 | 
 44 | # (dog='canine', cat='feline', fox='vulpine') each (key, val):
 45 | #   (key, ' is a ', val) join print.
 46 | 
 47 | for key, val of {dog: 'canine', cat: 'feline', fox: 'vulpine'}
 48 |   print key + ' is a ' + val
 49 | 
 50 | 
 51 | # Person = class: /name, /age, /sex.
 52 | # Person print = ():
 53 | #   ('My name is ', /name, '.') join print.
 54 | 
 55 | class Person
 56 |   print: ->
 57 |     print 'My name is ' + @name + '.'
 58 | 
 59 | 
 60 | # p = Person ()
 61 | # p /name string print
 62 | 
 63 | p = new Person
 64 | print p.name
 65 | 
 66 | 
 67 | # Policeman = Person class (rank): /rank = rank.
 68 | # Policeman print = ():
 69 | #   ('My name is ', /name, ' and I'm a ', /rank, '.') join print.
 70 | #
 71 | # Policeman ('Constable') print
 72 | 
 73 | class Policeman extends Person
 74 |   (@rank) ->
 75 | 
 76 |   print: ->
 77 |     print 'My name is ' + @name + " and I'm a " + @rank + '.'
 78 | 
 79 | print new Policeman 'Constable'
 80 | 
 81 | 
 82 | # app = [window (width=200, height=400)
 83 | #         [para 'Welcome.', button 'OK']]
 84 | # app first name
 85 | 
 86 | app =
 87 |   window:
 88 |     width: 200
 89 |     height: 200
 90 |   para:    'Welcome.'
 91 |   button:  'OK'
 92 | 
 93 | app.window
 94 | 
 95 | 
 96 | # x = 1
 97 | # y = 2
 98 | #
 99 | # x = 1, y = 2
100 | 
101 | x = 1
102 | y = 2
103 | 
104 | x = 1; y = 2
105 | 
106 | 
107 | # table = (language='Potion'
108 | #           pointless=true)
109 | 
110 | table =
111 |   language: 'Potion'
112 |   pointless: yes
113 | 
114 | 
115 | # # this foul business...
116 | # String length = (): 10.
117 | 
118 | # this foul business...
119 | String::length = -> 10
120 | 
121 | 
122 | # block = :
123 | #   'potion' print.
124 | 
125 | block = ->
126 |   print 'potion'
127 | 
128 | 
129 | # if (age > 100): 'ancient'.
130 | 
131 | if age > 100 then 'ancient'
132 | 
133 | 
134 | # author =
135 | #   if (title == 'Jonathan Strange & Mr. Norrell'):
136 | #     'Susanna Clarke'.
137 | #   elsif (title == 'The Star Diaries'):
138 | #     'Stanislaw Lem'.
139 | #   elsif (title == 'The Slynx'):
140 | #     'Tatyana Tolstaya'.
141 | #   else:
142 | #     '... probably Philip K. Dick'.
143 | 
144 | switch author
145 |   when 'Jonathan Strange & Mr. Norrell'
146 |     'Susanna Clarke'
147 |   when 'The Star Diaries'
148 |     'Stanislaw Lem'
149 |   when 'The Slynx'
150 |     'Tatyana Tolstaya'
151 |   else
152 |     '... probably Philip K. Dick'
153 | 
154 | 
155 | # count = 8
156 | # while (count > 0):
157 | #   'quaff' print
158 | #   count--.
159 | 
160 | count = 8
161 | while count > 0
162 |   print 'quaff'
163 |   count--
164 | 
165 | 
166 | # 1 to 5 (a):
167 | #   a string print.
168 | 
169 | print a for a in [1..5]
170 | 
171 | 
172 | # if (3 ?gender):
173 | #   "Huh? Numbers are sexed? That's amazing." print.
174 | 
175 | if 3.gender?
176 |   print "Huh? Numbers are sexed? That's amazing."
177 | 
178 | 
179 | # HomePage get = (url):
180 | #   session = url query ? at ('session').
181 | 
182 | HomePage::get = (url) ->
183 |   session = url.query.session if url.query?
184 | 
185 | 
186 | # BTree = class: /left, /right.
187 | # b = BTree ()
188 | # b /left = BTree ()
189 | # b /right = BTree ()
190 | 
191 | BTree   = ->
192 | b       = new BTree
193 | b.left  = new BTree
194 | b.right = new BTree
195 | 
196 | 
197 | # BTree = class: /left, /right.
198 | # b = BTree ()
199 | #
200 | # if (b ? /left):
201 | #   'left path found!' print.
202 | 
203 | BTree = ->
204 | b = new BTree
205 | 
206 | print 'left path found!' if b.left?
207 | 


--------------------------------------------------------------------------------
/src/repl.coffee:
--------------------------------------------------------------------------------
  1 | # A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript
  2 | # and evaluates it. Good for simple tests, or poking around the **Node.js** API.
  3 | # Using it looks like this:
  4 | #
  5 | #     coffee> console.log "#{num} bottles of beer" for num in [99..1]
  6 | 
  7 | # Require the **coffee-script** module to get access to the compiler.
  8 | CoffeeScript = require './coffee-script'
  9 | readline     = require 'readline'
 10 | {inspect}    = require 'util'
 11 | {Script}     = require 'vm'
 12 | Module       = require 'module'
 13 | 
 14 | # REPL Setup
 15 | 
 16 | # Config
 17 | REPL_PROMPT = 'coffee> '
 18 | REPL_PROMPT_CONTINUATION = '......> '
 19 | enableColours = no
 20 | unless process.platform is 'win32'
 21 |   enableColours = not process.env.NODE_DISABLE_COLORS
 22 | 
 23 | # Start by opening up `stdin` and `stdout`.
 24 | stdin = process.openStdin()
 25 | stdout = process.stdout
 26 | 
 27 | # Log an error.
 28 | error = (err) ->
 29 |   stdout.write (err.stack or err.toString()) + '\n\n'
 30 | 
 31 | # The current backlog of multi-line code.
 32 | backlog = ''
 33 | 
 34 | # The REPL context; must be visible outside `run` to allow for tab completion
 35 | sandbox = Script.createContext()
 36 | nonContextGlobals = [
 37 |   'Buffer', 'console', 'process'
 38 |   'setInterval', 'clearInterval'
 39 |   'setTimeout', 'clearTimeout'
 40 | ]
 41 | sandbox[g] = global[g] for g in nonContextGlobals
 42 | sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
 43 | 
 44 | # The main REPL function. **run** is called every time a line of code is entered.
 45 | # Attempt to evaluate the command. If there's an exception, print it out instead
 46 | # of exiting.
 47 | run = (buffer) ->
 48 |   if !buffer.toString().trim() and !backlog
 49 |     repl.prompt()
 50 |     return
 51 |   code = backlog += buffer
 52 |   if code[code.length - 1] is '\\'
 53 |     backlog = "#{backlog[...-1]}\n"
 54 |     repl.setPrompt REPL_PROMPT_CONTINUATION
 55 |     repl.prompt()
 56 |     return
 57 |   repl.setPrompt REPL_PROMPT
 58 |   backlog = ''
 59 |   try
 60 |     _ = sandbox._
 61 |     returnValue = CoffeeScript.eval "_=(#{code}\n)", {
 62 |       sandbox,
 63 |       filename: 'repl'
 64 |       modulename: 'repl'
 65 |     }
 66 |     if returnValue is undefined
 67 |       sandbox._ = _
 68 |     else
 69 |       process.stdout.write inspect(returnValue, no, 2, enableColours) + '\n'
 70 |   catch err
 71 |     error err
 72 |   repl.prompt()
 73 | 
 74 | ## Autocompletion
 75 | 
 76 | # Regexes to match complete-able bits of text.
 77 | ACCESSOR  = /\s*([\w\.]+)(?:\.(\w*))$/
 78 | SIMPLEVAR = /\s*(\w*)$/i
 79 | 
 80 | # Returns a list of completions, and the completed text.
 81 | autocomplete = (text) ->
 82 |   completeAttribute(text) or completeVariable(text) or [[], text]
 83 | 
 84 | # Attempt to autocomplete a chained dotted attribute: `one.two.three`.
 85 | completeAttribute = (text) ->
 86 |   if match = text.match ACCESSOR
 87 |     [all, obj, prefix] = match
 88 |     try
 89 |       val = Script.runInContext obj, sandbox
 90 |     catch error
 91 |       return
 92 |     completions = getCompletions prefix, Object.getOwnPropertyNames val
 93 |     [completions, prefix]
 94 | 
 95 | # Attempt to autocomplete an in-scope free variable: `one`.
 96 | completeVariable = (text) ->
 97 |   free = (text.match SIMPLEVAR)?[1]
 98 |   if free?
 99 |     vars = Script.runInContext 'Object.getOwnPropertyNames(this)', sandbox
100 |     keywords = (r for r in CoffeeScript.RESERVED when r[0..1] isnt '__')
101 |     possibilities = vars.concat keywords
102 |     completions = getCompletions free, possibilities
103 |     [completions, free]
104 | 
105 | # Return elements of candidates for which `prefix` is a prefix.
106 | getCompletions = (prefix, candidates) ->
107 |   (el for el in candidates when el.indexOf(prefix) is 0)
108 | 
109 | # Make sure that uncaught exceptions don't kill the REPL.
110 | process.on 'uncaughtException', error
111 | 
112 | # Create the REPL by listening to **stdin**.
113 | if readline.createInterface.length < 3
114 |   repl = readline.createInterface stdin, autocomplete
115 |   stdin.on 'data', (buffer) -> repl.write buffer
116 | else
117 |   repl = readline.createInterface stdin, stdout, autocomplete
118 | 
119 | repl.on 'attemptClose', ->
120 |   if backlog
121 |     backlog = ''
122 |     process.stdout.write '\n'
123 |     repl.setPrompt REPL_PROMPT
124 |     repl.prompt()
125 |   else
126 |     repl.close()
127 | 
128 | repl.on 'close', ->
129 |   process.stdout.write '\n'
130 |   stdin.destroy()
131 | 
132 | repl.on 'line', run
133 | 
134 | repl.setPrompt REPL_PROMPT
135 | repl.prompt()
136 | 


--------------------------------------------------------------------------------
/test/functions.coffee:
--------------------------------------------------------------------------------
  1 | # Function Literals
  2 | # -----------------
  3 | 
  4 | # TODO: add indexing and method invocation tests: (->)[0], (->).call()
  5 | 
  6 | # * Function Definition
  7 | # * Bound Function Definition
  8 | # * Parameter List Features
  9 | #   * Splat Parameters
 10 | #   * Context (@) Parameters
 11 | #   * Parameter Destructuring
 12 | #   * Default Parameters
 13 | 
 14 | # Function Definition
 15 | 
 16 | x = 1
 17 | y = {}
 18 | y.x = -> 3
 19 | ok x is 1
 20 | ok typeof(y.x) is 'function'
 21 | ok y.x instanceof Function
 22 | ok y.x() is 3
 23 | 
 24 | # The empty function should not cause a syntax error.
 25 | ->
 26 | () ->
 27 | 
 28 | # Multiple nested function declarations mixed with implicit calls should not
 29 | # cause a syntax error.
 30 | (one) -> (two) -> three four, (five) -> six seven, eight, (nine) ->
 31 | 
 32 | # with multiple single-line functions on the same line.
 33 | func = (x) -> (x) -> (x) -> x
 34 | ok func(1)(2)(3) is 3
 35 | 
 36 | # Make incorrect indentation safe.
 37 | func = ->
 38 |   obj = {
 39 |           key: 10
 40 |         }
 41 |   obj.key - 5
 42 | eq func(), 5
 43 | 
 44 | # Ensure that functions with the same name don't clash with helper functions.
 45 | del = -> 5
 46 | ok del() is 5
 47 | 
 48 | 
 49 | # Bound Function Definition
 50 | 
 51 | obj =
 52 |   bound: ->
 53 |     (=> this)()
 54 |   unbound: ->
 55 |     (-> this)()
 56 |   nested: ->
 57 |     (=>
 58 |       (=>
 59 |         (=> this)()
 60 |       )()
 61 |     )()
 62 | eq obj, obj.bound()
 63 | ok obj isnt obj.unbound()
 64 | eq obj, obj.nested()
 65 | 
 66 | 
 67 | test "self-referencing functions", ->
 68 |   changeMe = ->
 69 |     changeMe = 2
 70 | 
 71 |   changeMe()
 72 |   eq changeMe, 2
 73 | 
 74 | 
 75 | # Parameter List Features
 76 | 
 77 | test "splats", ->
 78 |   arrayEq [0, 1, 2], (((splat...) -> splat) 0, 1, 2)
 79 |   arrayEq [2, 3], (((_, _, splat...) -> splat) 0, 1, 2, 3)
 80 |   arrayEq [0, 1], (((splat..., _, _) -> splat) 0, 1, 2, 3)
 81 |   arrayEq [2], (((_, _, splat..., _) -> splat) 0, 1, 2, 3)
 82 | 
 83 | test "@-parameters: automatically assign an argument's value to a property of the context", ->
 84 |   nonce = {}
 85 | 
 86 |   ((@prop) ->).call context = {}, nonce
 87 |   eq nonce, context.prop
 88 | 
 89 |   # allow splats along side the special argument
 90 |   ((splat..., @prop) ->).apply context = {}, [0, 0, nonce]
 91 |   eq nonce, context.prop
 92 | 
 93 |   # allow the argument itself to be a splat
 94 |   ((@prop...) ->).call context = {}, 0, nonce, 0
 95 |   eq nonce, context.prop[1]
 96 | 
 97 |   # the argument should still be able to be referenced normally
 98 |   eq nonce, (((@prop) -> prop).call {}, nonce)
 99 | 
100 | test "@-parameters and splats with constructors", ->
101 |   a = {}
102 |   b = {}
103 |   class Klass
104 |     constructor: (@first, splat..., @last) ->
105 | 
106 |   obj = new Klass a, 0, 0, b
107 |   eq a, obj.first
108 |   eq b, obj.last
109 | 
110 | test "destructuring in function definition", ->
111 |   (([{a: [b], c}]...) ->
112 |     eq 1, b
113 |     eq 2, c
114 |   ) {a: [1], c: 2}
115 | 
116 | test "default values", ->
117 |   nonceA = {}
118 |   nonceB = {}
119 |   a = (_,_,arg=nonceA) -> arg
120 |   eq nonceA, a()
121 |   eq nonceA, a(0)
122 |   eq nonceB, a(0,0,nonceB)
123 |   eq nonceA, a(0,0,undefined)
124 |   eq nonceA, a(0,0,null)
125 |   eq false , a(0,0,false)
126 |   eq nonceB, a(undefined,undefined,nonceB,undefined)
127 |   b = (_,arg=nonceA,_,_) -> arg
128 |   eq nonceA, b()
129 |   eq nonceA, b(0)
130 |   eq nonceB, b(0,nonceB)
131 |   eq nonceA, b(0,undefined)
132 |   eq nonceA, b(0,null)
133 |   eq false , b(0,false)
134 |   eq nonceB, b(undefined,nonceB,undefined)
135 |   c = (arg=nonceA,_,_) -> arg
136 |   eq nonceA, c()
137 |   eq      0, c(0)
138 |   eq nonceB, c(nonceB)
139 |   eq nonceA, c(undefined)
140 |   eq nonceA, c(null)
141 |   eq false , c(false)
142 |   eq nonceB, c(nonceB,undefined,undefined)
143 | 
144 | test "default values with @-parameters", ->
145 |   a = {}
146 |   b = {}
147 |   obj = f: (q = a, @p = b) -> q
148 |   eq a, obj.f()
149 |   eq b, obj.p
150 | 
151 | test "default values with splatted arguments", ->
152 |   withSplats = (a = 2, b..., c = 3, d = 5) -> a * (b.length + 1) * c * d
153 |   eq 30, withSplats()
154 |   eq 15, withSplats(1)
155 |   eq  5, withSplats(1,1)
156 |   eq  1, withSplats(1,1,1)
157 |   eq  2, withSplats(1,1,1,1)
158 | 
159 | test "default values with function calls", ->
160 |   doesNotThrow -> CoffeeScript.compile "(x = f()) ->"
161 | 
162 | test "arguments vs parameters", ->
163 |   doesNotThrow -> CoffeeScript.compile "f(x) ->"
164 |   f = (g) -> g()
165 |   eq 5, f (x) -> 5
166 | 


--------------------------------------------------------------------------------
/lib/coffee-script/repl.js:
--------------------------------------------------------------------------------
  1 | (function() {
  2 |   var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, g, getCompletions, inspect, nonContextGlobals, readline, repl, run, sandbox, stdin, stdout, _i, _len;
  3 |   CoffeeScript = require('./coffee-script');
  4 |   readline = require('readline');
  5 |   inspect = require('util').inspect;
  6 |   Script = require('vm').Script;
  7 |   Module = require('module');
  8 |   REPL_PROMPT = 'coffee> ';
  9 |   REPL_PROMPT_CONTINUATION = '......> ';
 10 |   enableColours = false;
 11 |   if (process.platform !== 'win32') {
 12 |     enableColours = !process.env.NODE_DISABLE_COLORS;
 13 |   }
 14 |   stdin = process.openStdin();
 15 |   stdout = process.stdout;
 16 |   error = function(err) {
 17 |     return stdout.write((err.stack || err.toString()) + '\n\n');
 18 |   };
 19 |   backlog = '';
 20 |   sandbox = Script.createContext();
 21 |   nonContextGlobals = ['Buffer', 'console', 'process', 'setInterval', 'clearInterval', 'setTimeout', 'clearTimeout'];
 22 |   for (_i = 0, _len = nonContextGlobals.length; _i < _len; _i++) {
 23 |     g = nonContextGlobals[_i];
 24 |     sandbox[g] = global[g];
 25 |   }
 26 |   sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox;
 27 |   run = function(buffer) {
 28 |     var code, returnValue, _;
 29 |     if (!buffer.toString().trim() && !backlog) {
 30 |       repl.prompt();
 31 |       return;
 32 |     }
 33 |     code = backlog += buffer;
 34 |     if (code[code.length - 1] === '\\') {
 35 |       backlog = "" + backlog.slice(0, -1) + "\n";
 36 |       repl.setPrompt(REPL_PROMPT_CONTINUATION);
 37 |       repl.prompt();
 38 |       return;
 39 |     }
 40 |     repl.setPrompt(REPL_PROMPT);
 41 |     backlog = '';
 42 |     try {
 43 |       _ = sandbox._;
 44 |       returnValue = CoffeeScript.eval("_=(" + code + "\n)", {
 45 |         sandbox: sandbox,
 46 |         filename: 'repl',
 47 |         modulename: 'repl'
 48 |       });
 49 |       if (returnValue === void 0) {
 50 |         sandbox._ = _;
 51 |       } else {
 52 |         process.stdout.write(inspect(returnValue, false, 2, enableColours) + '\n');
 53 |       }
 54 |     }
 55 |     catch (err) {
 56 |       error(err);
 57 |     }
 58 |     return repl.prompt();
 59 |   };
 60 |   ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/;
 61 |   SIMPLEVAR = /\s*(\w*)$/i;
 62 |   autocomplete = function(text) {
 63 |     return completeAttribute(text) || completeVariable(text) || [[], text];
 64 |   };
 65 |   completeAttribute = function(text) {
 66 |     var all, completions, match, obj, prefix, val;
 67 |     if (match = text.match(ACCESSOR)) {
 68 |       all = match[0], obj = match[1], prefix = match[2];
 69 |       try {
 70 |         val = Script.runInContext(obj, sandbox);
 71 |       }
 72 |       catch (error) {
 73 |         return;
 74 |       }
 75 |       completions = getCompletions(prefix, Object.getOwnPropertyNames(val));
 76 |       return [completions, prefix];
 77 |     }
 78 |   };
 79 |   completeVariable = function(text) {
 80 |     var completions, free, keywords, possibilities, r, vars, _ref;
 81 |     free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0;
 82 |     if (free != null) {
 83 |       vars = Script.runInContext('Object.getOwnPropertyNames(this)', sandbox);
 84 |       keywords = (function() {
 85 |         var _j, _len2, _ref2, _results;
 86 |         _ref2 = CoffeeScript.RESERVED;
 87 |         _results = [];
 88 |         for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
 89 |           r = _ref2[_j];
 90 |           if (r.slice(0, 2) !== '__') _results.push(r);
 91 |         }
 92 |         return _results;
 93 |       })();
 94 |       possibilities = vars.concat(keywords);
 95 |       completions = getCompletions(free, possibilities);
 96 |       return [completions, free];
 97 |     }
 98 |   };
 99 |   getCompletions = function(prefix, candidates) {
100 |     var el, _j, _len2, _results;
101 |     _results = [];
102 |     for (_j = 0, _len2 = candidates.length; _j < _len2; _j++) {
103 |       el = candidates[_j];
104 |       if (el.indexOf(prefix) === 0) _results.push(el);
105 |     }
106 |     return _results;
107 |   };
108 |   process.on('uncaughtException', error);
109 |   if (readline.createInterface.length < 3) {
110 |     repl = readline.createInterface(stdin, autocomplete);
111 |     stdin.on('data', function(buffer) {
112 |       return repl.write(buffer);
113 |     });
114 |   } else {
115 |     repl = readline.createInterface(stdin, stdout, autocomplete);
116 |   }
117 |   repl.on('attemptClose', function() {
118 |     if (backlog) {
119 |       backlog = '';
120 |       process.stdout.write('\n');
121 |       repl.setPrompt(REPL_PROMPT);
122 |       return repl.prompt();
123 |     } else {
124 |       return repl.close();
125 |     }
126 |   });
127 |   repl.on('close', function() {
128 |     process.stdout.write('\n');
129 |     return stdin.destroy();
130 |   });
131 |   repl.on('line', run);
132 |   repl.setPrompt(REPL_PROMPT);
133 |   repl.prompt();
134 | }).call(this);
135 | 


--------------------------------------------------------------------------------
/lib/coffee-script/coffee-script.js:
--------------------------------------------------------------------------------
  1 | (function() {
  2 |   var Lexer, RESERVED, compile, fs, lexer, parser, path, _ref;
  3 |   var __hasProp = Object.prototype.hasOwnProperty;
  4 |   fs = require('fs');
  5 |   path = require('path');
  6 |   _ref = require('./lexer'), Lexer = _ref.Lexer, RESERVED = _ref.RESERVED;
  7 |   parser = require('./parser').parser;
  8 |   if (require.extensions) {
  9 |     require.extensions['.coffee'] = function(module, filename) {
 10 |       var content;
 11 |       content = compile(fs.readFileSync(filename, 'utf8'), {
 12 |         filename: filename
 13 |       });
 14 |       return module._compile(content, filename);
 15 |     };
 16 |   } else if (require.registerExtension) {
 17 |     require.registerExtension('.coffee', function(content) {
 18 |       return compile(content);
 19 |     });
 20 |   }
 21 |   exports.VERSION = '1.1.3-pre';
 22 |   exports.RESERVED = RESERVED;
 23 |   exports.helpers = require('./helpers');
 24 |   exports.compile = compile = function(code, options) {
 25 |     if (options == null) options = {};
 26 |     try {
 27 |       return (parser.parse(lexer.tokenize(code))).compile(options);
 28 |     }
 29 |     catch (err) {
 30 |       if (options.filename) {
 31 |         err.message = "In " + options.filename + ", " + err.message;
 32 |       }
 33 |       throw err;
 34 |     }
 35 |   };
 36 |   exports.tokens = function(code, options) {
 37 |     return lexer.tokenize(code, options);
 38 |   };
 39 |   exports.nodes = function(source, options) {
 40 |     if (typeof source === 'string') {
 41 |       return parser.parse(lexer.tokenize(source, options));
 42 |     } else {
 43 |       return parser.parse(source);
 44 |     }
 45 |   };
 46 |   exports.run = function(code, options) {
 47 |     var Module, mainModule;
 48 |     mainModule = require.main;
 49 |     mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.';
 50 |     mainModule.moduleCache && (mainModule.moduleCache = {});
 51 |     if (process.binding('natives').module) {
 52 |       Module = require('module').Module;
 53 |       mainModule.paths = Module._nodeModulePaths(path.dirname(options.filename));
 54 |     }
 55 |     if (path.extname(mainModule.filename) !== '.coffee' || require.extensions) {
 56 |       return mainModule._compile(compile(code, options), mainModule.filename);
 57 |     } else {
 58 |       return mainModule._compile(code, mainModule.filename);
 59 |     }
 60 |   };
 61 |   exports.eval = function(code, options) {
 62 |     var Module, Script, js, k, o, r, sandbox, v, _i, _len, _module, _ref2, _ref3, _require;
 63 |     if (options == null) options = {};
 64 |     if (!(code = code.trim())) return;
 65 |     Script = require('vm').Script;
 66 |     if (Script) {
 67 |       sandbox = Script.createContext();
 68 |       sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox;
 69 |       if (options.sandbox != null) {
 70 |         if (options.sandbox instanceof sandbox.constructor) {
 71 |           sandbox = options.sandbox;
 72 |         } else {
 73 |           _ref2 = options.sandbox;
 74 |           for (k in _ref2) {
 75 |             if (!__hasProp.call(_ref2, k)) continue;
 76 |             v = _ref2[k];
 77 |             sandbox[k] = v;
 78 |           }
 79 |         }
 80 |       }
 81 |       sandbox.__filename = options.filename || 'eval';
 82 |       sandbox.__dirname = path.dirname(sandbox.__filename);
 83 |       if (!(sandbox.module || sandbox.require)) {
 84 |         Module = require('module');
 85 |         sandbox.module = _module = new Module(options.modulename || 'eval');
 86 |         sandbox.require = _require = function(path) {
 87 |           return Module._load(path, _module);
 88 |         };
 89 |         _module.filename = sandbox.__filename;
 90 |         _ref3 = Object.getOwnPropertyNames(require);
 91 |         for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
 92 |           r = _ref3[_i];
 93 |           if (r !== 'paths') _require[r] = require[r];
 94 |         }
 95 |         _require.paths = _module.paths = Module._nodeModulePaths(process.cwd());
 96 |         _require.resolve = function(request) {
 97 |           return Module._resolveFilename(request, _module);
 98 |         };
 99 |       }
100 |     }
101 |     o = {};
102 |     for (k in options) {
103 |       if (!__hasProp.call(options, k)) continue;
104 |       v = options[k];
105 |       o[k] = v;
106 |     }
107 |     o.bare = true;
108 |     js = compile(code, o);
109 |     if (Script) {
110 |       return Script.runInContext(js, sandbox);
111 |     } else {
112 |       return eval(js);
113 |     }
114 |   };
115 |   lexer = new Lexer;
116 |   parser.lexer = {
117 |     lex: function() {
118 |       var tag, _ref2;
119 |       _ref2 = this.tokens[this.pos++] || [''], tag = _ref2[0], this.yytext = _ref2[1], this.yylineno = _ref2[2];
120 |       return tag;
121 |     },
122 |     setInput: function(tokens) {
123 |       this.tokens = tokens;
124 |       return this.pos = 0;
125 |     },
126 |     upcomingInput: function() {
127 |       return "";
128 |     }
129 |   };
130 |   parser.yy = require('./nodes');
131 | }).call(this);
132 | 


--------------------------------------------------------------------------------
/src/coffee-script.coffee:
--------------------------------------------------------------------------------
  1 | # CoffeeScript can be used both on the server, as a command-line compiler based
  2 | # on Node.js/V8, or to run CoffeeScripts directly in the browser. This module
  3 | # contains the main entry functions for tokenizing, parsing, and compiling
  4 | # source CoffeeScript into JavaScript.
  5 | #
  6 | # If included on a webpage, it will automatically sniff out, compile, and
  7 | # execute all scripts present in `text/coffeescript` tags.
  8 | 
  9 | fs               = require 'fs'
 10 | path             = require 'path'
 11 | {Lexer,RESERVED} = require './lexer'
 12 | {parser}         = require './parser'
 13 | 
 14 | # TODO: Remove registerExtension when fully deprecated.
 15 | if require.extensions
 16 |   require.extensions['.coffee'] = (module, filename) ->
 17 |     content = compile fs.readFileSync(filename, 'utf8'), {filename}
 18 |     module._compile content, filename
 19 | else if require.registerExtension
 20 |   require.registerExtension '.coffee', (content) -> compile content
 21 | 
 22 | # The current CoffeeScript version number.
 23 | exports.VERSION = '1.1.3-pre'
 24 | 
 25 | # Words that cannot be used as identifiers in CoffeeScript code
 26 | exports.RESERVED = RESERVED
 27 | 
 28 | # Expose helpers for testing.
 29 | exports.helpers = require './helpers'
 30 | 
 31 | # Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
 32 | # compiler.
 33 | exports.compile = compile = (code, options = {}) ->
 34 |   try
 35 |     (parser.parse lexer.tokenize code).compile options
 36 |   catch err
 37 |     err.message = "In #{options.filename}, #{err.message}" if options.filename
 38 |     throw err
 39 | 
 40 | # Tokenize a string of CoffeeScript code, and return the array of tokens.
 41 | exports.tokens = (code, options) ->
 42 |   lexer.tokenize code, options
 43 | 
 44 | # Parse a string of CoffeeScript code or an array of lexed tokens, and
 45 | # return the AST. You can then compile it by calling `.compile()` on the root,
 46 | # or traverse it by using `.traverse()` with a callback.
 47 | exports.nodes = (source, options) ->
 48 |   if typeof source is 'string'
 49 |     parser.parse lexer.tokenize source, options
 50 |   else
 51 |     parser.parse source
 52 | 
 53 | # Compile and execute a string of CoffeeScript (on the server), correctly
 54 | # setting `__filename`, `__dirname`, and relative `require()`.
 55 | exports.run = (code, options) ->
 56 |   mainModule = require.main
 57 | 
 58 |   # Set the filename.
 59 |   mainModule.filename = process.argv[1] =
 60 |       if options.filename then fs.realpathSync(options.filename) else '.'
 61 | 
 62 |   # Clear the module cache.
 63 |   mainModule.moduleCache and= {}
 64 | 
 65 |   # Assign paths for node_modules loading
 66 |   if process.binding('natives').module
 67 |     {Module} = require 'module'
 68 |     mainModule.paths = Module._nodeModulePaths path.dirname options.filename
 69 | 
 70 |   # Compile.
 71 |   if path.extname(mainModule.filename) isnt '.coffee' or require.extensions
 72 |     mainModule._compile compile(code, options), mainModule.filename
 73 |   else
 74 |     mainModule._compile code, mainModule.filename
 75 | 
 76 | # Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
 77 | # The CoffeeScript REPL uses this to run the input.
 78 | exports.eval = (code, options = {}) ->
 79 |   return unless code = code.trim()
 80 |   {Script} = require 'vm'
 81 |   if Script
 82 |     sandbox = Script.createContext()
 83 |     sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
 84 |     if options.sandbox?
 85 |       if options.sandbox instanceof sandbox.constructor
 86 |         sandbox = options.sandbox
 87 |       else
 88 |         sandbox[k] = v for own k, v of options.sandbox
 89 |     sandbox.__filename = options.filename || 'eval'
 90 |     sandbox.__dirname  = path.dirname sandbox.__filename
 91 |     # define module/require only if they chose not to specify their own
 92 |     unless sandbox.module or sandbox.require
 93 |       Module = require 'module'
 94 |       sandbox.module  = _module  = new Module(options.modulename || 'eval')
 95 |       sandbox.require = _require = (path) -> Module._load path, _module
 96 |       _module.filename = sandbox.__filename
 97 |       _require[r] = require[r] for r in Object.getOwnPropertyNames require when r isnt 'paths'
 98 |       # use the same hack node currently uses for their own REPL
 99 |       _require.paths = _module.paths = Module._nodeModulePaths process.cwd()
100 |       _require.resolve = (request) -> Module._resolveFilename request, _module
101 |   o = {}
102 |   o[k] = v for own k, v of options
103 |   o.bare = on # ensure return value
104 |   js = compile code, o
105 |   if Script
106 |     Script.runInContext js, sandbox
107 |   else
108 |     eval js
109 | 
110 | # Instantiate a Lexer for our use here.
111 | lexer = new Lexer
112 | 
113 | # The real Lexer produces a generic stream of tokens. This object provides a
114 | # thin wrapper around it, compatible with the Jison API. We can then pass it
115 | # directly as a "Jison lexer".
116 | parser.lexer =
117 |   lex: ->
118 |     [tag, @yytext, @yylineno] = @tokens[@pos++] or ['']
119 |     tag
120 |   setInput: (@tokens) ->
121 |     @pos = 0
122 |   upcomingInput: ->
123 |     ""
124 | 
125 | parser.yy = require './nodes'
126 | 


--------------------------------------------------------------------------------
/examples/poignant.coffee:
--------------------------------------------------------------------------------
  1 | # Examples from the Poignant Guide.
  2 | 
  3 | # ['toast', 'cheese', 'wine'].each { |food| print food.capitalize }
  4 | 
  5 | ['toast', 'wine', 'cheese'].each (food) -> print food.capitalize()
  6 | 
  7 | 
  8 | 
  9 | # class LotteryTicket
 10 | #   def picks;           @picks;            end
 11 | #   def picks=(var);     @picks = var;      end
 12 | #   def purchased;       @purchased;        end
 13 | #   def purchased=(var); @purchased = var;  end
 14 | # end
 15 | 
 16 | LotteryTicket =
 17 |   get_picks:      -> @picks
 18 |   set_picks:      (@picks) ->
 19 |   get_purchased:  -> @purchase
 20 |   set_purchased:  (@purchased) ->
 21 | 
 22 | 
 23 | 
 24 | # class << LotteryDraw
 25 | #   def play
 26 | #     result = LotteryTicket.new_random
 27 | #     winners = {}
 28 | #     @@tickets.each do |buyer, ticket_list|
 29 | #       ticket_list.each do |ticket|
 30 | #         score = ticket.score( result )
 31 | #         next if score.zero?
 32 | #         winners[buyer] ||= []
 33 | #         winners[buyer] << [ ticket, score ]
 34 | #       end
 35 | #     end
 36 | #     @@tickets.clear
 37 | #     winners
 38 | #   end
 39 | # end
 40 | 
 41 | LotteryDraw =
 42 |   play: ->
 43 |     result  = LotteryTicket.new_random()
 44 |     winners = {}
 45 |     this.tickets.each (buyer, ticket_list) ->
 46 |       ticket_list.each (ticket) ->
 47 |         score = ticket.score result
 48 |         return if score is 0
 49 |         winners[buyer] or= []
 50 |         winners[buyer].push [ticket, score]
 51 |     this.tickets = {}
 52 |     winners
 53 | 
 54 | 
 55 | 
 56 | # module WishScanner
 57 | #   def scan_for_a_wish
 58 | #     wish = self.read.detect do |thought|
 59 | #       thought.index( 'wish: ' ) == 0
 60 | #     end
 61 | #     wish.gsub( 'wish: ', '' )
 62 | #   end
 63 | # end
 64 | 
 65 | WishScanner =
 66 |   scan_for_a_wish: ->
 67 |     wish = this.read().detect (thought) -> thought.index('wish: ') is 0
 68 |     wish.replace 'wish: ', ''
 69 | 
 70 | 
 71 | 
 72 | # class Creature
 73 | #
 74 | #   # This method applies a hit taken during a fight.
 75 | #   def hit( damage )
 76 | #     p_up = rand( charisma )
 77 | #     if p_up % 9 == 7
 78 | #       @life += p_up / 4
 79 | #       console.log "[#{ self.class } magick powers up #{ p_up }!]"
 80 | #     end
 81 | #     @life -= damage
 82 | #     console.log "[#{ self.class } has died.]" if @life <= 0
 83 | #   end
 84 | #
 85 | #   # This method takes one turn in a fight.
 86 | #   def fight( enemy, weapon )
 87 | #     if life <= 0
 88 | #       console.log "[#{ self.class } is too dead to fight!]"
 89 | #       return
 90 | #     end
 91 | #
 92 | #     # Attack the opponent
 93 | #     your_hit = rand( strength + weapon )
 94 | #     console.log "[You hit with #{ your_hit } points of damage!]"
 95 | #     enemy.hit( your_hit )
 96 | #
 97 | #     # Retaliation
 98 | #     p enemy
 99 | #     if enemy.life > 0
100 | #       enemy_hit = rand( enemy.strength + enemy.weapon )
101 | #       console.log "[Your enemy hit with #{ enemy_hit } points of damage!]"
102 | #       self.hit( enemy_hit )
103 | #     end
104 | #   end
105 | #
106 | # end
107 | 
108 | Creature =
109 | 
110 |   # This method applies a hit taken during a fight.
111 |   hit: (damage) ->
112 |     p_up = Math.rand this.charisma
113 |     if p_up % 9 is 7
114 |       this.life += p_up / 4
115 |       console.log "[" + this.name + " magick powers up " + p_up + "!]"
116 |     this.life -= damage
117 |     if this.life <= 0 then console.log "[" + this.name + " has died.]"
118 | 
119 |   # This method takes one turn in a fight.
120 |   fight: (enemy, weapon) ->
121 |     if this.life <= 0 then return console.log "[" + this.name + "is too dead to fight!]"
122 | 
123 |     # Attack the opponent.
124 |     your_hit = Math.rand this.strength + weapon
125 |     console.log "[You hit with " + your_hit + "points of damage!]"
126 |     enemy.hit your_hit
127 | 
128 |     # Retaliation.
129 |     console.log enemy
130 |     if enemy.life > 0
131 |       enemy_hit = Math.rand enemy.strength + enemy.weapon
132 |       console.log "[Your enemy hit with " + enemy_hit + "points of damage!]"
133 |       this.hit enemy_hit
134 | 
135 | 
136 | 
137 | # # Get evil idea and swap in code words
138 | # print "Enter your new idea: "
139 | # idea = gets
140 | # code_words.each do |real, code|
141 | #   idea.gsub!( real, code )
142 | # end
143 | #
144 | # # Save the jibberish to a new file
145 | # print "File encoded.  Please enter a name for this idea: "
146 | # idea_name = gets.strip
147 | # File::open( "idea-" + idea_name + ".txt", "w" ) do |f|
148 | #   f << idea
149 | # end
150 | 
151 | # Get evil idea and swap in code words
152 | print "Enter your new idea: "
153 | idea = gets()
154 | code_words.each (real, code) -> idea.replace(real, code)
155 | 
156 | # Save the jibberish to a new file
157 | print "File encoded. Please enter a name for this idea: "
158 | idea_name = gets().strip()
159 | File.open "idea-" + idea_name + '.txt', 'w', (file) -> file.write idea
160 | 
161 | 
162 | 
163 | # def wipe_mutterings_from( sentence )
164 | #   unless sentence.respond_to? :include?
165 | #     raise ArgumentError,
166 | #       "cannot wipe mutterings from a #{ sentence.class }"
167 | #   end
168 | #   while sentence.include? '('
169 | #     open = sentence.index( '(' )
170 | #     close = sentence.index( ')', open )
171 | #     sentence[open..close] = '' if close
172 | #   end
173 | # end
174 | 
175 | wipe_mutterings_from = (sentence) ->
176 |   throw new Error "cannot wipe mutterings" unless sentence.indexOf
177 |   while sentence.indexOf('(') >= 0
178 |     open     = sentence.indexOf('(') - 1
179 |     close    = sentence.indexOf(')') + 1
180 |     sentence = sentence.slice(0, open) + sentence.slice(close, sentence.length)
181 |     sentence
182 | 


--------------------------------------------------------------------------------
/test/objects.coffee:
--------------------------------------------------------------------------------
  1 | # Object Literals
  2 | # ---------------
  3 | 
  4 | # TODO: refactor object literal tests
  5 | # TODO: add indexing and method invocation tests: {a}['a'] is a, {a}.a()
  6 | 
  7 | trailingComma = {k1: "v1", k2: 4, k3: (-> true),}
  8 | ok trailingComma.k3() and (trailingComma.k2 is 4) and (trailingComma.k1 is "v1")
  9 | 
 10 | ok {a: (num) -> num is 10 }.a 10
 11 | 
 12 | moe = {
 13 |   name:  'Moe'
 14 |   greet: (salutation) ->
 15 |     salutation + " " + @name
 16 |   hello: ->
 17 |     @['greet'] "Hello"
 18 |   10: 'number'
 19 | }
 20 | ok moe.hello() is "Hello Moe"
 21 | ok moe[10] is 'number'
 22 | moe.hello = ->
 23 |   this['greet'] "Hello"
 24 | ok moe.hello() is 'Hello Moe'
 25 | 
 26 | obj = {
 27 |   is:     -> yes,
 28 |   'not':  -> no,
 29 | }
 30 | ok obj.is()
 31 | ok not obj.not()
 32 | 
 33 | ### Top-level object literal... ###
 34 | obj: 1
 35 | ### ...doesn't break things. ###
 36 | 
 37 | # Object literals should be able to include keywords.
 38 | obj = {class: 'höt'}
 39 | obj.function = 'dog'
 40 | ok obj.class + obj.function is 'hötdog'
 41 | 
 42 | # Implicit objects as part of chained calls.
 43 | pluck = (x) -> x.a
 44 | eq 100, pluck pluck pluck a: a: a: 100
 45 | 
 46 | 
 47 | test "YAML-style object literals", ->
 48 |   obj =
 49 |     a: 1
 50 |     b: 2
 51 |   eq 1, obj.a
 52 |   eq 2, obj.b
 53 | 
 54 |   config =
 55 |     development:
 56 |       server: 'localhost'
 57 |       timeout: 10
 58 | 
 59 |     production:
 60 |       server: 'dreamboat'
 61 |       timeout: 1000
 62 | 
 63 |   ok config.development.server  is 'localhost'
 64 |   ok config.production.server   is 'dreamboat'
 65 |   ok config.development.timeout is 10
 66 |   ok config.production.timeout  is 1000
 67 | 
 68 | obj =
 69 |   a: 1,
 70 |   b: 2,
 71 | ok obj.a is 1
 72 | ok obj.b is 2
 73 | 
 74 | # Implicit objects nesting.
 75 | obj =
 76 |   options:
 77 |     value: yes
 78 |   fn: ->
 79 |     {}
 80 |     null
 81 | ok obj.options.value is yes
 82 | ok obj.fn() is null
 83 | 
 84 | # Implicit objects with wacky indentation:
 85 | obj =
 86 |   'reverse': (obj) ->
 87 |     Array.prototype.reverse.call obj
 88 |   abc: ->
 89 |     @reverse(
 90 |       @reverse @reverse ['a', 'b', 'c'].reverse()
 91 |     )
 92 |   one: [1, 2,
 93 |     a: 'b'
 94 |   3, 4]
 95 |   red:
 96 |     orange:
 97 |           yellow:
 98 |                   green: 'blue'
 99 |     indigo: 'violet'
100 |   misdent: [[],
101 |   [],
102 |                   [],
103 |       []]
104 | ok obj.abc().join(' ') is 'a b c'
105 | ok obj.one.length is 5
106 | ok obj.one[4] is 4
107 | ok obj.one[2].a is 'b'
108 | ok (key for key of obj.red).length is 2
109 | ok obj.red.orange.yellow.green is 'blue'
110 | ok obj.red.indigo is 'violet'
111 | ok obj.misdent.toString() is ',,,'
112 | 
113 | #542: Objects leading expression statement should be parenthesized.
114 | {f: -> ok yes }.f() + 1
115 | 
116 | # String-keyed objects shouldn't suppress newlines.
117 | one =
118 |   '>!': 3
119 | six: -> 10
120 | ok not one.six
121 | 
122 | # Shorthand objects with property references.
123 | obj =
124 |   ### comment one ###
125 |   ### comment two ###
126 |   one: 1
127 |   two: 2
128 |   object: -> {@one, @two}
129 |   list:   -> [@one, @two]
130 | result = obj.object()
131 | eq result.one, 1
132 | eq result.two, 2
133 | eq result.two, obj.list()[1]
134 | 
135 | third = (a, b, c) -> c
136 | obj =
137 |   one: 'one'
138 |   two: third 'one', 'two', 'three'
139 | ok obj.one is 'one'
140 | ok obj.two is 'three'
141 | 
142 | test "invoking functions with implicit object literals", ->
143 |   generateGetter = (prop) -> (obj) -> obj[prop]
144 |   getA = generateGetter 'a'
145 |   getArgs = -> arguments
146 |   a = b = 30
147 | 
148 |   result = getA
149 |     a: 10
150 |   eq 10, result
151 | 
152 |   result = getA
153 |     "a": 20
154 |   eq 20, result
155 | 
156 |   result = getA a,
157 |     b:1
158 |   eq undefined, result
159 | 
160 |   result = getA b:1
161 |   a:43
162 |   eq 43, result
163 | 
164 |   result = getA b:1,
165 |     a:62
166 |   eq undefined, result
167 | 
168 |   result = getA
169 |     b:1
170 |     a
171 |   eq undefined, result
172 | 
173 |   result = getA
174 |     a:
175 |       b:2
176 |     b:1
177 |   eq 2, result.b
178 | 
179 |   result = getArgs
180 |     a:1
181 |     b
182 |     c:1
183 |   ok result.length is 3
184 |   ok result[2].c is 1
185 | 
186 |   result = getA b: 13, a: 42, 2
187 |   eq 42, result
188 | 
189 |   result = getArgs a:1, (1 + 1)
190 |   ok result[1] is 2
191 | 
192 |   result = getArgs a:1, b
193 |   ok result.length is 2
194 |   ok result[1] is 30
195 | 
196 |   result = getArgs a:1, b, b:1, a
197 |   ok result.length is 4
198 |   ok result[2].b is 1
199 | 
200 |   throws -> CoffeeScript.compile "a = b:1, c"
201 | 
202 | test "some weird indentation in YAML-style object literals", ->
203 |   two = (a, b) -> b
204 |   obj = then two 1,
205 |     1: 1
206 |     a:
207 |       b: ->
208 |         fn c,
209 |           d: e
210 |     f: 1
211 |   eq 1, obj[1]
212 | 
213 | test "#1274: `{} = a()` compiles to `false` instead of `a()`", ->
214 |   a = false
215 |   fn = -> a = true
216 |   {} = fn()
217 |   ok a
218 | 
219 | test "#1436: `for` etc. work as normal property names", ->
220 |   obj = {}
221 |   eq no, obj.hasOwnProperty 'for'
222 |   obj.for = 'foo' of obj
223 |   eq yes, obj.hasOwnProperty 'for'
224 | 
225 | test "#1322: implicit call against implicit object with block comments", ->
226 |   ((obj, arg) ->
227 |     eq obj.x * obj.y, 6
228 |     ok not arg
229 |   )
230 |     ###
231 |     x
232 |     ###
233 |     x: 2
234 |     ### y ###
235 |     y: 3
236 | 
237 | test "#1513: Top level bare objs need to be wrapped in parens for unary and existence ops", ->
238 |   doesNotThrow -> CoffeeScript.run "{}?", bare: true
239 |   doesNotThrow -> CoffeeScript.run "{}.a++", bare: true
240 | 


--------------------------------------------------------------------------------
/test/operators.coffee:
--------------------------------------------------------------------------------
  1 | # Operators
  2 | # ---------
  3 | 
  4 | # * Operators
  5 | # * Existential Operator (Binary)
  6 | # * Existential Operator (Unary)
  7 | # * Aliased Operators
  8 | # * [not] in/of
  9 | # * Chained Comparison
 10 | 
 11 | test "binary (2-ary) math operators do not require spaces", ->
 12 |   a = 1
 13 |   b = -1
 14 |   eq +1, a*-b
 15 |   eq -1, a*+b
 16 |   eq +1, a/-b
 17 |   eq -1, a/+b
 18 | 
 19 | test "operators should respect new lines as spaced", ->
 20 |   a = 123 +
 21 |   456
 22 |   eq 579, a
 23 | 
 24 |   b = "1#{2}3" +
 25 |   "456"
 26 |   eq '123456', b
 27 | 
 28 | test "multiple operators should space themselves", ->
 29 |   eq (+ +1), (- -1)
 30 | 
 31 | test "bitwise operators", ->
 32 |   eq  2, (10 &   3)
 33 |   eq 11, (10 |   3)
 34 |   eq  9, (10 ^   3)
 35 |   eq 80, (10 <<  3)
 36 |   eq  1, (10 >>  3)
 37 |   eq  1, (10 >>> 3)
 38 |   num = 10; eq  2, (num &=   3)
 39 |   num = 10; eq 11, (num |=   3)
 40 |   num = 10; eq  9, (num ^=   3)
 41 |   num = 10; eq 80, (num <<=  3)
 42 |   num = 10; eq  1, (num >>=  3)
 43 |   num = 10; eq  1, (num >>>= 3)
 44 | 
 45 | test "`instanceof`", ->
 46 |   ok new String instanceof String
 47 |   ok new Boolean instanceof Boolean
 48 |   # `instanceof` supports negation by prefixing the operator with `not`
 49 |   ok new Number not instanceof String
 50 |   ok new Array not instanceof Boolean
 51 | 
 52 | test "use `::` operator on keywords `this` and `@`", ->
 53 |   nonce = {}
 54 |   obj =
 55 |     withAt:   -> @::prop
 56 |     withThis: -> this::prop
 57 |   obj.prototype = prop: nonce
 58 |   eq nonce, obj.withAt()
 59 |   eq nonce, obj.withThis()
 60 | 
 61 | 
 62 | # Existential Operator (Binary)
 63 | 
 64 | test "binary existential operator", ->
 65 |   nonce = {}
 66 | 
 67 |   b = a ? nonce
 68 |   eq nonce, b
 69 | 
 70 |   a = null
 71 |   b = undefined
 72 |   b = a ? nonce
 73 |   eq nonce, b
 74 | 
 75 |   a = false
 76 |   b = a ? nonce
 77 |   eq false, b
 78 | 
 79 |   a = 0
 80 |   b = a ? nonce
 81 |   eq 0, b
 82 | 
 83 | test "binary existential operator conditionally evaluates second operand", ->
 84 |   i = 1
 85 |   func = -> i -= 1
 86 |   result = func() ? func()
 87 |   eq result, 0
 88 | 
 89 | test "binary existential operator with negative number", ->
 90 |   a = null ? - 1
 91 |   eq -1, a
 92 | 
 93 | 
 94 | # Existential Operator (Unary)
 95 | 
 96 | test "postfix existential operator", ->
 97 |   ok (if nonexistent? then false else true)
 98 |   defined = true
 99 |   ok defined?
100 |   defined = false
101 |   ok defined?
102 | 
103 | test "postfix existential operator only evaluates its operand once", ->
104 |   semaphore = 0
105 |   fn = ->
106 |     ok false if semaphore
107 |     ++semaphore
108 |   ok(if fn()? then true else false)
109 | 
110 | test "negated postfix existential operator", ->
111 |   ok !nothing?.value
112 | 
113 | test "postfix existential operator on expressions", ->
114 |   eq true, (1 or 0)?, true
115 | 
116 | 
117 | # `is`,`isnt`,`==`,`!=`
118 | 
119 | test "`==` and `is` should be interchangeable", ->
120 |   a = b = 1
121 |   ok a is 1 and b == 1
122 |   ok a == b
123 |   ok a is b
124 | 
125 | test "`!=` and `isnt` should be interchangeable", ->
126 |   a = 0
127 |   b = 1
128 |   ok a isnt 1 and b != 0
129 |   ok a != b
130 |   ok a isnt b
131 | 
132 | 
133 | # [not] in/of
134 | 
135 | # - `in` should check if an array contains a value using `indexOf`
136 | # - `of` should check if a property is defined on an object using `in`
137 | test "in, of", ->
138 |   arr = [1]
139 |   ok 0 of arr
140 |   ok 1 in arr
141 |   # prefixing `not` to `in and `of` should negate them
142 |   ok 1 not of arr
143 |   ok 0 not in arr
144 | 
145 | test "`in` should be able to operate on an array literal", ->
146 |   ok 2 in [0, 1, 2, 3]
147 |   ok 4 not in [0, 1, 2, 3]
148 |   arr = [0, 1, 2, 3]
149 |   ok 2 in arr
150 |   ok 4 not in arr
151 |   # should cache the value used to test the array
152 |   arr = [0]
153 |   val = 0
154 |   ok val++ in arr
155 |   ok val++ not in arr
156 |   val = 0
157 |   ok val++ of arr
158 |   ok val++ not of arr
159 | 
160 | test "`of` and `in` should be able to operate on instance variables", ->
161 |   obj = {
162 |     list: [2,3]
163 |     in_list: (value) -> value in @list
164 |     not_in_list: (value) -> value not in @list
165 |     of_list: (value) -> value of @list
166 |     not_of_list: (value) -> value not of @list
167 |   }
168 |   ok obj.in_list 3
169 |   ok obj.not_in_list 1
170 |   ok obj.of_list 0
171 |   ok obj.not_of_list 2
172 | 
173 | test "#???: `in` with cache and `__indexOf` should work in argument lists", ->
174 |   eq 1, [Object() in Array()].length
175 | 
176 | test "#737: `in` should have higher precedence than logical operators", ->
177 |   eq 1, 1 in [1] and 1
178 | 
179 | test "#768: `in` should preserve evaluation order", ->
180 |   share = 0
181 |   a = -> share++ if share is 0
182 |   b = -> share++ if share is 1
183 |   c = -> share++ if share is 2
184 |   ok a() not in [b(),c()]
185 |   eq 3, share
186 | 
187 | test "#1099: empty array after `in` should compile to `false`", ->
188 |   eq 1, [5 in []].length
189 |   eq false, do -> return 0 in []
190 | 
191 | test "#1354: optimized `in` checks should not happen when splats are present", ->
192 |   a = [6, 9]
193 |   eq 9 in [3, a...], true
194 | 
195 | test "#1100: precedence in or-test compilation of `in`", ->
196 |   ok 0 in [1 and 0]
197 |   ok 0 in [1, 1 and 0]
198 |   ok not (0 in [1, 0 or 1])
199 | 
200 | test "#1630: `in` should check `hasOwnProperty`", ->
201 |   ok undefined not in length: 1
202 | 
203 | 
204 | # Chained Comparison
205 | 
206 | test "chainable operators", ->
207 |   ok 100 > 10 > 1 > 0 > -1
208 |   ok -1 < 0 < 1 < 10 < 100
209 | 
210 | test "`is` and `isnt` may be chained", ->
211 |   ok true is not false is true is not false
212 |   ok 0 is 0 isnt 1 is 1
213 | 
214 | test "different comparison operators (`>`,`<`,`is`,etc.) may be combined", ->
215 |   ok 1 < 2 > 1
216 |   ok 10 < 20 > 2+3 is 5
217 | 
218 | test "some chainable operators can be negated by `unless`", ->
219 |   ok (true unless 0==10!=100)
220 | 
221 | test "operator precedence: `|` lower than `<`", ->
222 |   eq 1, 1 | 2 < 3 < 4
223 | 
224 | test "preserve references", ->
225 |   a = b = c = 1
226 |   # `a == b <= c` should become `a === b && b <= c`
227 |   # (this test does not seem to test for this)
228 |   ok a == b <= c
229 | 
230 | test "chained operations should evaluate each value only once", ->
231 |   a = 0
232 |   ok 1 > a++ < 1
233 | 
234 | test "#891: incorrect inversion of chained comparisons", ->
235 |   ok (true unless 0 > 1 > 2)
236 |   ok (true unless (NaN = 0/0) < 0/0 < NaN)
237 | 
238 | test "#1234: Applying a splat to :: applies the splat to the wrong object", ->
239 |   nonce = {}
240 |   class C
241 |     method: -> @nonce
242 |     nonce: nonce
243 | 
244 |   arr = []
245 |   eq nonce, C::method arr... # should be applied to `C::`
246 | 
247 | test "#1102: String literal prevents line continuation", ->
248 |   eq "': '", '' +
249 |      "': '"
250 | 
251 | test "#1703, ---x is invalid JS", ->
252 |   x = 2
253 |   eq (- --x), -1


--------------------------------------------------------------------------------
/documentation/docs/docco.css:
--------------------------------------------------------------------------------
  1 | /*--------------------- Layout and Typography ----------------------------*/
  2 | body {
  3 |   font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
  4 |   font-size: 15px;
  5 |   line-height: 22px;
  6 |   color: #252519;
  7 |   margin: 0; padding: 0;
  8 | }
  9 | a {
 10 |   color: #261a3b;
 11 | }
 12 |   a:visited {
 13 |     color: #261a3b;
 14 |   }
 15 | p {
 16 |   margin: 0 0 15px 0;
 17 | }
 18 | h1, h2, h3, h4, h5, h6 {
 19 |   margin: 0px 0 15px 0;
 20 | }
 21 |   h1 {
 22 |     margin-top: 40px;
 23 |   }
 24 | #container {
 25 |   position: relative;
 26 | }
 27 | #background {
 28 |   position: fixed;
 29 |   top: 0; left: 525px; right: 0; bottom: 0;
 30 |   background: #f5f5ff;
 31 |   border-left: 1px solid #e5e5ee;
 32 |   z-index: -1;
 33 | }
 34 | #jump_to, #jump_page {
 35 |   background: white;
 36 |   -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
 37 |   -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
 38 |   font: 10px Arial;
 39 |   text-transform: uppercase;
 40 |   cursor: pointer;
 41 |   text-align: right;
 42 | }
 43 | #jump_to, #jump_wrapper {
 44 |   position: fixed;
 45 |   right: 0; top: 0;
 46 |   padding: 5px 10px;
 47 | }
 48 |   #jump_wrapper {
 49 |     padding: 0;
 50 |     display: none;
 51 |   }
 52 |     #jump_to:hover #jump_wrapper {
 53 |       display: block;
 54 |     }
 55 |     #jump_page {
 56 |       padding: 5px 0 3px;
 57 |       margin: 0 0 25px 25px;
 58 |     }
 59 |       #jump_page .source {
 60 |         display: block;
 61 |         padding: 5px 10px;
 62 |         text-decoration: none;
 63 |         border-top: 1px solid #eee;
 64 |       }
 65 |         #jump_page .source:hover {
 66 |           background: #f5f5ff;
 67 |         }
 68 |         #jump_page .source:first-child {
 69 |         }
 70 | table td {
 71 |   border: 0;
 72 |   outline: 0;
 73 | }
 74 |   td.docs, th.docs {
 75 |     max-width: 450px;
 76 |     min-width: 450px;
 77 |     min-height: 5px;
 78 |     padding: 10px 25px 1px 50px;
 79 |     overflow-x: hidden;
 80 |     vertical-align: top;
 81 |     text-align: left;
 82 |   }
 83 |     .docs pre {
 84 |       margin: 15px 0 15px;
 85 |       padding-left: 15px;
 86 |     }
 87 |     .docs p tt, .docs p code {
 88 |       background: #f8f8ff;
 89 |       border: 1px solid #dedede;
 90 |       font-size: 12px;
 91 |       padding: 0 0.2em;
 92 |     }
 93 |     .pilwrap {
 94 |       position: relative;
 95 |     }
 96 |       .pilcrow {
 97 |         font: 12px Arial;
 98 |         text-decoration: none;
 99 |         color: #454545;
100 |         position: absolute;
101 |         top: 3px; left: -20px;
102 |         padding: 1px 2px;
103 |         opacity: 0;
104 |         -webkit-transition: opacity 0.2s linear;
105 |       }
106 |         td.docs:hover .pilcrow {
107 |           opacity: 1;
108 |         }
109 |   td.code, th.code {
110 |     padding: 14px 15px 16px 25px;
111 |     width: 100%;
112 |     vertical-align: top;
113 |     background: #f5f5ff;
114 |     border-left: 1px solid #e5e5ee;
115 |   }
116 |     pre, tt, code {
117 |       font-size: 12px; line-height: 18px;
118 |       font-family: Monaco, Consolas, "Lucida Console", monospace;
119 |       margin: 0; padding: 0;
120 |     }
121 | 
122 | 
123 | /*---------------------- Syntax Highlighting -----------------------------*/
124 | td.linenos { background-color: #f0f0f0; padding-right: 10px; }
125 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
126 | body .hll { background-color: #ffffcc }
127 | body .c { color: #408080; font-style: italic }  /* Comment */
128 | body .err { border: 1px solid #FF0000 }         /* Error */
129 | body .k { color: #954121 }                      /* Keyword */
130 | body .o { color: #666666 }                      /* Operator */
131 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
132 | body .cp { color: #BC7A00 }                     /* Comment.Preproc */
133 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */
134 | body .cs { color: #408080; font-style: italic } /* Comment.Special */
135 | body .gd { color: #A00000 }                     /* Generic.Deleted */
136 | body .ge { font-style: italic }                 /* Generic.Emph */
137 | body .gr { color: #FF0000 }                     /* Generic.Error */
138 | body .gh { color: #000080; font-weight: bold }  /* Generic.Heading */
139 | body .gi { color: #00A000 }                     /* Generic.Inserted */
140 | body .go { color: #808080 }                     /* Generic.Output */
141 | body .gp { color: #000080; font-weight: bold }  /* Generic.Prompt */
142 | body .gs { font-weight: bold }                  /* Generic.Strong */
143 | body .gu { color: #800080; font-weight: bold }  /* Generic.Subheading */
144 | body .gt { color: #0040D0 }                     /* Generic.Traceback */
145 | body .kc { color: #954121 }                     /* Keyword.Constant */
146 | body .kd { color: #954121; font-weight: bold }  /* Keyword.Declaration */
147 | body .kn { color: #954121; font-weight: bold }  /* Keyword.Namespace */
148 | body .kp { color: #954121 }                     /* Keyword.Pseudo */
149 | body .kr { color: #954121; font-weight: bold }  /* Keyword.Reserved */
150 | body .kt { color: #B00040 }                     /* Keyword.Type */
151 | body .m { color: #666666 }                      /* Literal.Number */
152 | body .s { color: #219161 }                      /* Literal.String */
153 | body .na { color: #7D9029 }                     /* Name.Attribute */
154 | body .nb { color: #954121 }                     /* Name.Builtin */
155 | body .nc { color: #0000FF; font-weight: bold }  /* Name.Class */
156 | body .no { color: #880000 }                     /* Name.Constant */
157 | body .nd { color: #AA22FF }                     /* Name.Decorator */
158 | body .ni { color: #999999; font-weight: bold }  /* Name.Entity */
159 | body .ne { color: #D2413A; font-weight: bold }  /* Name.Exception */
160 | body .nf { color: #0000FF }                     /* Name.Function */
161 | body .nl { color: #A0A000 }                     /* Name.Label */
162 | body .nn { color: #0000FF; font-weight: bold }  /* Name.Namespace */
163 | body .nt { color: #954121; font-weight: bold }  /* Name.Tag */
164 | body .nv { color: #19469D }                     /* Name.Variable */
165 | body .ow { color: #AA22FF; font-weight: bold }  /* Operator.Word */
166 | body .w { color: #bbbbbb }                      /* Text.Whitespace */
167 | body .mf { color: #666666 }                     /* Literal.Number.Float */
168 | body .mh { color: #666666 }                     /* Literal.Number.Hex */
169 | body .mi { color: #666666 }                     /* Literal.Number.Integer */
170 | body .mo { color: #666666 }                     /* Literal.Number.Oct */
171 | body .sb { color: #219161 }                     /* Literal.String.Backtick */
172 | body .sc { color: #219161 }                     /* Literal.String.Char */
173 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
174 | body .s2 { color: #219161 }                     /* Literal.String.Double */
175 | body .se { color: #BB6622; font-weight: bold }  /* Literal.String.Escape */
176 | body .sh { color: #219161 }                     /* Literal.String.Heredoc */
177 | body .si { color: #BB6688; font-weight: bold }  /* Literal.String.Interpol */
178 | body .sx { color: #954121 }                     /* Literal.String.Other */
179 | body .sr { color: #BB6688 }                     /* Literal.String.Regex */
180 | body .s1 { color: #219161 }                     /* Literal.String.Single */
181 | body .ss { color: #19469D }                     /* Literal.String.Symbol */
182 | body .bp { color: #954121 }                     /* Name.Builtin.Pseudo */
183 | body .vc { color: #19469D }                     /* Name.Variable.Class */
184 | body .vg { color: #19469D }                     /* Name.Variable.Global */
185 | body .vi { color: #19469D }                     /* Name.Variable.Instance */
186 | body .il { color: #666666 }                     /* Literal.Number.Integer.Long */


--------------------------------------------------------------------------------
/test/assignment.coffee:
--------------------------------------------------------------------------------
  1 | # Assignment
  2 | # ----------
  3 | 
  4 | # * Assignment
  5 | # * Compound Assignment
  6 | # * Destructuring Assignment
  7 | # * Context Property (@) Assignment
  8 | # * Existential Assignment (?=)
  9 | 
 10 | test "context property assignment (using @)", ->
 11 |   nonce = {}
 12 |   addMethod = ->
 13 |     @method = -> nonce
 14 |     this
 15 |   eq nonce, addMethod.call({}).method()
 16 | 
 17 | test "unassignable values", ->
 18 |   nonce = {}
 19 |   for nonref in ['', '""', '0', 'f()'].concat CoffeeScript.RESERVED
 20 |     eq nonce, (try CoffeeScript.compile "#{nonref} = v" catch e then nonce)
 21 | 
 22 | test "compound assignments should not declare", ->
 23 |   # TODO: make description more clear
 24 |   # TODO: remove reference to Math
 25 |   eq Math, (-> Math or= 0)()
 26 | 
 27 | 
 28 | # Compound Assignment
 29 | 
 30 | test "boolean operators", ->
 31 |   nonce = {}
 32 | 
 33 |   a  = 0
 34 |   a or= nonce
 35 |   eq nonce, a
 36 | 
 37 |   b  = 1
 38 |   b or= nonce
 39 |   eq 1, b
 40 | 
 41 |   c = 0
 42 |   c and= nonce
 43 |   eq 0, c
 44 | 
 45 |   d = 1
 46 |   d and= nonce
 47 |   eq nonce, d
 48 | 
 49 |   # ensure that RHS is treated as a group
 50 |   e = f = false
 51 |   e and= f or true
 52 |   eq false, e
 53 | 
 54 | test "compound assignment as a sub expression", ->
 55 |   [a, b, c] = [1, 2, 3]
 56 |   eq 6, (a + b += c)
 57 |   eq 1, a
 58 |   eq 5, b
 59 |   eq 3, c
 60 | 
 61 | # *note: this test could still use refactoring*
 62 | test "compound assignment should be careful about caching variables", ->
 63 |   count = 0
 64 |   list = []
 65 | 
 66 |   list[++count] or= 1
 67 |   eq 1, list[1]
 68 |   eq 1, count
 69 | 
 70 |   list[++count] ?= 2
 71 |   eq 2, list[2]
 72 |   eq 2, count
 73 | 
 74 |   list[count++] and= 6
 75 |   eq 6, list[2]
 76 |   eq 3, count
 77 | 
 78 |   base = ->
 79 |     ++count
 80 |     base
 81 | 
 82 |   base().four or= 4
 83 |   eq 4, base.four
 84 |   eq 4, count
 85 | 
 86 |   base().five ?= 5
 87 |   eq 5, base.five
 88 |   eq 5, count
 89 | 
 90 | test "compound assignment with implicit objects", ->
 91 |   obj = undefined
 92 |   obj ?=
 93 |     one: 1
 94 | 
 95 |   eq 1, obj.one
 96 | 
 97 |   obj and=
 98 |     two: 2
 99 | 
100 |   eq undefined, obj.one
101 |   eq         2, obj.two
102 | 
103 | test "compound assignment (math operators)", ->
104 |   num = 10
105 |   num -= 5
106 |   eq 5, num
107 | 
108 |   num *= 10
109 |   eq 50, num
110 | 
111 |   num /= 10
112 |   eq 5, num
113 | 
114 |   num %= 3
115 |   eq 2, num
116 | 
117 | test "more compound assignment", ->
118 |   a = {}
119 |   val = undefined
120 |   val ||= a
121 |   val ||= true
122 |   eq a, val
123 | 
124 |   b = {}
125 |   val &&= true
126 |   eq val, true
127 |   val &&= b
128 |   eq b, val
129 | 
130 |   c = {}
131 |   val = null
132 |   val ?= c
133 |   val ?= true
134 |   eq c, val
135 | 
136 | 
137 | # Destructuring Assignment
138 | 
139 | test "empty destructuring assignment", ->
140 |   {} = [] = undefined
141 | 
142 | test "chained destructuring assignments", ->
143 |   [a] = {0: b} = {'0': c} = [nonce={}]
144 |   eq nonce, a
145 |   eq nonce, b
146 |   eq nonce, c
147 | 
148 | test "variable swapping to verify caching of RHS values when appropriate", ->
149 |   a = nonceA = {}
150 |   b = nonceB = {}
151 |   c = nonceC = {}
152 |   [a, b, c] = [b, c, a]
153 |   eq nonceB, a
154 |   eq nonceC, b
155 |   eq nonceA, c
156 |   [a, b, c] = [b, c, a]
157 |   eq nonceC, a
158 |   eq nonceA, b
159 |   eq nonceB, c
160 |   fn = ->
161 |     [a, b, c] = [b, c, a]
162 |   arrayEq [nonceA,nonceB,nonceC], fn()
163 |   eq nonceA, a
164 |   eq nonceB, b
165 |   eq nonceC, c
166 | 
167 | test "#713", ->
168 |   nonces = [nonceA={},nonceB={}]
169 |   eq nonces, [a, b] = [c, d] = nonces
170 |   eq nonceA, a
171 |   eq nonceA, c
172 |   eq nonceB, b
173 |   eq nonceB, d
174 | 
175 | test "destructuring assignment with splats", ->
176 |   a = {}; b = {}; c = {}; d = {}; e = {}
177 |   [x,y...,z] = [a,b,c,d,e]
178 |   eq a, x
179 |   arrayEq [b,c,d], y
180 |   eq e, z
181 | 
182 | test "deep destructuring assignment with splats", ->
183 |   a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={}
184 |   [u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i]
185 |   eq a, u
186 |   eq b, v
187 |   arrayEq [c,d], w
188 |   eq e, x
189 |   arrayEq [f,g,h], y
190 |   eq i, z
191 | 
192 | test "destructuring assignment with objects", ->
193 |   a={}; b={}; c={}
194 |   obj = {a,b,c}
195 |   {a:x, b:y, c:z} = obj
196 |   eq a, x
197 |   eq b, y
198 |   eq c, z
199 | 
200 | test "deep destructuring assignment with objects", ->
201 |   a={}; b={}; c={}; d={}
202 |   obj = {
203 |     a
204 |     b: {
205 |       'c': {
206 |         d: [
207 |           b
208 |           {e: c, f: d}
209 |         ]
210 |       }
211 |     }
212 |   }
213 |   {a: w, 'b': {c: d: [x, {'f': z, e: y}]}} = obj
214 |   eq a, w
215 |   eq b, x
216 |   eq c, y
217 |   eq d, z
218 | 
219 | test "destructuring assignment with objects and splats", ->
220 |   a={}; b={}; c={}; d={}
221 |   obj = a: b: [a, b, c, d]
222 |   {a: b: [y, z...]} = obj
223 |   eq a, y
224 |   arrayEq [b,c,d], z
225 | 
226 | test "destructuring assignment against an expression", ->
227 |   a={}; b={}
228 |   [y, z] = if true then [a, b] else [b, a]
229 |   eq a, y
230 |   eq b, z
231 | 
232 | test "bracket insertion when necessary", ->
233 |   [a] = [0] ? [1]
234 |   eq a, 0
235 | 
236 | # for implicit destructuring assignment in comprehensions, see the comprehension tests
237 | 
238 | test "destructuring assignment with context (@) properties", ->
239 |   a={}; b={}; c={}; d={}; e={}
240 |   obj =
241 |     fn: () ->
242 |       local = [a, {b, c}, d, e]
243 |       [@a, {b: @b, c: @c}, @d, @e] = local
244 |   eq undefined, obj[key] for key in ['a','b','c','d','e']
245 |   obj.fn()
246 |   eq a, obj.a
247 |   eq b, obj.b
248 |   eq c, obj.c
249 |   eq d, obj.d
250 |   eq e, obj.e
251 | 
252 | test "#1024", ->
253 |   eq 2 * [] = 3 + 5, 16
254 | 
255 | test "#1005: invalid identifiers allowed on LHS of destructuring assignment", ->
256 |   disallowed = ['eval', 'arguments'].concat CoffeeScript.RESERVED
257 |   throws (-> CoffeeScript.compile "[#{disallowed.join ', '}] = x"), null, 'all disallowed'
258 |   throws (-> CoffeeScript.compile "[#{disallowed.join '..., '}...] = x"), null, 'all disallowed as splats'
259 |   t = tSplat = null
260 |   for v in disallowed when v isnt 'class' # `class` by itself is an expression
261 |     throws (-> CoffeeScript.compile t), null, t = "[#{v}] = x"
262 |     throws (-> CoffeeScript.compile tSplat), null, tSplat = "[#{v}...] = x"
263 |   doesNotThrow ->
264 |     for v in disallowed
265 |       CoffeeScript.compile "[a.#{v}] = x"
266 |       CoffeeScript.compile "[a.#{v}...] = x"
267 |       CoffeeScript.compile "[@#{v}] = x"
268 |       CoffeeScript.compile "[@#{v}...] = x"
269 | 
270 | 
271 | # Existential Assignment
272 | 
273 | test "existential assignment", ->
274 |   nonce = {}
275 |   a = false
276 |   a ?= nonce
277 |   eq false, a
278 |   b = undefined
279 |   b ?= nonce
280 |   eq nonce, b
281 |   c = null
282 |   c ?= nonce
283 |   eq nonce, c
284 |   d ?= nonce
285 |   eq nonce, d
286 | 
287 | test "#1348, #1216: existential assignment compilation", ->
288 |   nonce = {}
289 |   a = nonce
290 |   b = (a ?= 0)
291 |   eq nonce, b
292 |   #the first ?= compiles into a statement; the second ?= compiles to a ternary expression
293 |   eq a ?= b ?= 1, nonce
294 |   
295 |   e ?= f ?= g ?= 1
296 |   eq e + g, 2
297 |   
298 |   #need to ensure the two vars are not defined, hence the strange names;
299 |   # broke earlier when using c ?= d ?= 1 because `d` is declared elsewhere
300 |   eq und1_1348 ?= und2_1348 ?= 1, 1
301 |   
302 |   if a then a ?= 2 else a = 3
303 |   eq a, nonce
304 | 
305 | test "#1591, #1101: splatted expressions in destructuring assignment must be assignable", ->
306 |   nonce = {}
307 |   for nonref in ['', '""', '0', 'f()', '(->)'].concat CoffeeScript.RESERVED
308 |     eq nonce, (try CoffeeScript.compile "[#{nonref}...] = v" catch e then nonce)
309 | 
310 | test "#1643: splatted accesses in destructuring assignments should not be declared as variables", -> 
311 |   nonce = {}
312 |   accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'C::', 'f().a', 'o?.a', 'o?.a.b', 'f?().a']
313 |   for access in accesses
314 |     for i,j in [1,2,3] #position can matter
315 |       code = 
316 |         """
317 |         nonce = {}; nonce2 = {}; nonce3 = {}; 
318 |         @o = o = new (class C then a:{}); f = -> o
319 |         [#{new Array(i).join('x,')}#{access}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3]
320 |         unless #{access}[0] is nonce and #{access}[1] is nonce2 and #{access}[2] is nonce3 then throw new Error('[...]')
321 |         """
322 |       eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce
323 |   # subpatterns like `[[a]...]` and `[{a}...]`
324 |   subpatterns = ['[sub, sub2, sub3]', '{0: sub, 1: sub2, 2: sub3}']
325 |   for subpattern in subpatterns
326 |     for i,j in [1,2,3]
327 |       code =
328 |         """
329 |         nonce = {}; nonce2 = {}; nonce3 = {}; 
330 |         [#{new Array(i).join('x,')}#{subpattern}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3]
331 |         unless sub is nonce and sub2 is nonce2 and sub3 is nonce3 then throw new Error('[sub...]')
332 |         """
333 |       eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce
334 | 


--------------------------------------------------------------------------------
/test/control_flow.coffee:
--------------------------------------------------------------------------------
  1 | # Control Flow
  2 | # ------------
  3 | 
  4 | # * Conditionals
  5 | # * Loops
  6 | #   * For
  7 | #   * While
  8 | #   * Until
  9 | #   * Loop
 10 | # * Switch
 11 | 
 12 | # TODO: make sure postfix forms and expression coercion are properly tested
 13 | 
 14 | # shared identity function
 15 | id = (_) -> if arguments.length is 1 then _ else Array::slice.call(arguments)
 16 | 
 17 | # Conditionals
 18 | 
 19 | test "basic conditionals", ->
 20 |   if false
 21 |     ok false
 22 |   else if false
 23 |     ok false
 24 |   else
 25 |     ok true
 26 | 
 27 |   if true
 28 |     ok true
 29 |   else if true
 30 |     ok false
 31 |   else
 32 |     ok true
 33 | 
 34 |   unless true
 35 |     ok false
 36 |   else unless true
 37 |     ok false
 38 |   else
 39 |     ok true
 40 | 
 41 |   unless false
 42 |     ok true
 43 |   else unless false
 44 |     ok false
 45 |   else
 46 |     ok true
 47 | 
 48 | test "single-line conditional", ->
 49 |   if false then ok false else ok true
 50 |   unless false then ok true else ok false
 51 | 
 52 | test "nested conditionals", ->
 53 |   nonce = {}
 54 |   eq nonce, (if true
 55 |     unless false
 56 |       if false then false else
 57 |         if true
 58 |           nonce)
 59 | 
 60 | test "nested single-line conditionals", ->
 61 |   nonce = {}
 62 | 
 63 |   a = if false then undefined else b = if 0 then undefined else nonce
 64 |   eq nonce, a
 65 |   eq nonce, b
 66 | 
 67 |   c = if false then undefined else (if 0 then undefined else nonce)
 68 |   eq nonce, c
 69 | 
 70 |   d = if true then id(if false then undefined else nonce)
 71 |   eq nonce, d
 72 | 
 73 | test "empty conditional bodies", ->
 74 |   eq undefined, (if false
 75 |   else if false
 76 |   else)
 77 | 
 78 | test "conditional bodies containing only comments", ->
 79 |   eq undefined, (if true
 80 |     ###
 81 |     block comment
 82 |     ###
 83 |   else
 84 |     # comment
 85 |   )
 86 | 
 87 |   eq undefined, (if false
 88 |     # comment
 89 |   else if true
 90 |     ###
 91 |     block comment
 92 |     ###
 93 |   else)
 94 | 
 95 | test "return value of if-else is from the proper body", ->
 96 |   nonce = {}
 97 |   eq nonce, if false then undefined else nonce
 98 | 
 99 | test "return value of unless-else is from the proper body", ->
100 |   nonce = {}
101 |   eq nonce, unless true then undefined else nonce
102 | 
103 | test "assign inside the condition of a conditional statement", ->
104 |   nonce = {}
105 |   if a = nonce then 1
106 |   eq nonce, a
107 |   1 if b = nonce
108 |   eq nonce, b
109 | 
110 | 
111 | # Interactions With Functions
112 | 
113 | test "single-line function definition with single-line conditional", ->
114 |   fn = -> if 1 < 0.5 then 1 else -1
115 |   ok fn() is -1
116 | 
117 | test "function resturns conditional value with no `else`", ->
118 |   fn = ->
119 |     return if false then true
120 |   eq undefined, fn()
121 | 
122 | test "function returns a conditional value", ->
123 |   a = {}
124 |   fnA = ->
125 |     return if false then undefined else a
126 |   eq a, fnA()
127 | 
128 |   b = {}
129 |   fnB = ->
130 |     return unless false then b else undefined
131 |   eq b, fnB()
132 | 
133 | test "passing a conditional value to a function", ->
134 |   nonce = {}
135 |   eq nonce, id if false then undefined else nonce
136 | 
137 | test "unmatched `then` should catch implicit calls", ->
138 |   a = 0
139 |   trueFn = -> true
140 |   if trueFn undefined then a++
141 |   eq 1, a
142 | 
143 | 
144 | # if-to-ternary
145 | 
146 | test "if-to-ternary with instanceof requires parentheses", ->
147 |   nonce = {}
148 |   eq nonce, (if {} instanceof Object
149 |     nonce
150 |   else
151 |     undefined)
152 | 
153 | test "if-to-ternary as part of a larger operation requires parentheses", ->
154 |   ok 2, 1 + if false then 0 else 1
155 | 
156 | 
157 | # Odd Formatting
158 | 
159 | test "if-else indented within an assignment", ->
160 |   nonce = {}
161 |   result =
162 |     if false
163 |       undefined
164 |     else
165 |       nonce
166 |   eq nonce, result
167 | 
168 | test "suppressed indentation via assignment", ->
169 |   nonce = {}
170 |   result =
171 |     if      false then undefined
172 |     else if no    then undefined
173 |     else if 0     then undefined
174 |     else if 1 < 0 then undefined
175 |     else               id(
176 |          if false then undefined
177 |          else          nonce
178 |     )
179 |   eq nonce, result
180 | 
181 | test "tight formatting with leading `then`", ->
182 |   nonce = {}
183 |   eq nonce,
184 |   if true
185 |   then nonce
186 |   else undefined
187 | 
188 | test "#738", ->
189 |   nonce = {}
190 |   fn = if true then -> nonce
191 |   eq nonce, fn()
192 | 
193 | test "#748: trailing reserved identifiers", ->
194 |   nonce = {}
195 |   obj = delete: true
196 |   result = if obj.delete
197 |     nonce
198 |   eq nonce, result
199 | 
200 | 
201 | test "basic `while` loops", ->
202 | 
203 |   i = 5
204 |   list = while i -= 1
205 |     i * 2
206 |   ok list.join(' ') is "8 6 4 2"
207 | 
208 |   i = 5
209 |   list = (i * 3 while i -= 1)
210 |   ok list.join(' ') is "12 9 6 3"
211 | 
212 |   i = 5
213 |   func   = (num) -> i -= num
214 |   assert = -> ok i < 5 > 0
215 |   results = while func 1
216 |     assert()
217 |     i
218 |   ok results.join(' ') is '4 3 2 1'
219 | 
220 |   i = 10
221 |   results = while i -= 1 when i % 2 is 0
222 |     i * 2
223 |   ok results.join(' ') is '16 12 8 4'
224 | 
225 | 
226 | test "Issue 759: `if` within `while` condition", ->
227 | 
228 |   2 while if 1 then 0
229 | 
230 | 
231 | test "assignment inside the condition of a `while` loop", ->
232 | 
233 |   nonce = {}
234 |   count = 1
235 |   a = nonce while count--
236 |   eq nonce, a
237 |   count = 1
238 |   while count--
239 |     b = nonce
240 |   eq nonce, b
241 | 
242 | 
243 | test "While over break.", ->
244 | 
245 |   i = 0
246 |   result = while i < 10
247 |     i++
248 |     break
249 |   arrayEq result, []
250 | 
251 | 
252 | test "While over continue.", ->
253 | 
254 |   i = 0
255 |   result = while i < 10
256 |     i++
257 |     continue
258 |   arrayEq result, []
259 | 
260 | 
261 | test "Basic `until`", ->
262 | 
263 |   value = false
264 |   i = 0
265 |   results = until value
266 |     value = true if i is 5
267 |     i++
268 |   ok i is 6
269 | 
270 | 
271 | test "Basic `loop`", ->
272 | 
273 |   i = 5
274 |   list = []
275 |   loop
276 |     i -= 1
277 |     break if i is 0
278 |     list.push i * 2
279 |   ok list.join(' ') is '8 6 4 2'
280 | 
281 | 
282 | test "break at the top level", ->
283 |   for i in [1,2,3]
284 |     result = i
285 |     if i == 2
286 |       break
287 |   eq 2, result
288 | 
289 | test "break *not* at the top level", ->
290 |   someFunc = ->
291 |     i = 0
292 |     while ++i < 3
293 |       result = i
294 |       break if i > 1
295 |     result
296 |   eq 2, someFunc()
297 | 
298 | 
299 | test "basic `switch`", ->
300 | 
301 |   num = 10
302 |   result = switch num
303 |     when 5 then false
304 |     when 'a'
305 |       true
306 |       true
307 |       false
308 |     when 10 then true
309 | 
310 | 
311 |     # Mid-switch comment with whitespace
312 |     # and multi line
313 |     when 11 then false
314 |     else false
315 | 
316 |   ok result
317 | 
318 | 
319 |   func = (num) ->
320 |     switch num
321 |       when 2, 4, 6
322 |         true
323 |       when 1, 3, 5
324 |         false
325 | 
326 |   ok func(2)
327 |   ok func(6)
328 |   ok !func(3)
329 |   eq func(8), undefined
330 | 
331 | 
332 | test "Ensure that trailing switch elses don't get rewritten.", ->
333 | 
334 |   result = false
335 |   switch "word"
336 |     when "one thing"
337 |       doSomething()
338 |     else
339 |       result = true unless false
340 | 
341 |   ok result
342 | 
343 |   result = false
344 |   switch "word"
345 |     when "one thing"
346 |       doSomething()
347 |     when "other thing"
348 |       doSomething()
349 |     else
350 |       result = true unless false
351 | 
352 |   ok result
353 | 
354 | 
355 | test "Should be able to handle switches sans-condition.", ->
356 | 
357 |   result = switch
358 |     when null                     then 0
359 |     when !1                       then 1
360 |     when '' not of {''}           then 2
361 |     when [] not instanceof Array  then 3
362 |     when true is false            then 4
363 |     when 'x' < 'y' > 'z'          then 5
364 |     when 'a' in ['b', 'c']        then 6
365 |     when 'd' in (['e', 'f'])      then 7
366 |     else ok
367 | 
368 |   eq result, ok
369 | 
370 | 
371 | test "Should be able to use `@properties` within the switch clause.", ->
372 | 
373 |   obj = {
374 |     num: 101
375 |     func: ->
376 |       switch @num
377 |         when 101 then '101!'
378 |         else 'other'
379 |   }
380 | 
381 |   ok obj.func() is '101!'
382 | 
383 | 
384 | test "Should be able to use `@properties` within the switch cases.", ->
385 | 
386 |   obj = {
387 |     num: 101
388 |     func: (yesOrNo) ->
389 |       result = switch yesOrNo
390 |         when yes then @num
391 |         else 'other'
392 |       result
393 |   }
394 | 
395 |   ok obj.func(yes) is 101
396 | 
397 | 
398 | test "Switch with break as the return value of a loop.", ->
399 | 
400 |   i = 10
401 |   results = while i > 0
402 |     i--
403 |     switch i % 2
404 |       when 1 then i
405 |       when 0 then break
406 | 
407 |   eq results.join(', '), '9, 7, 5, 3, 1'
408 | 
409 | 
410 | test "Issue #997. Switch doesn't fallthrough.", ->
411 | 
412 |   val = 1
413 |   switch true
414 |     when true
415 |       if false
416 |         return 5
417 |     else
418 |       val = 2
419 | 
420 |   eq val, 1
421 | 


--------------------------------------------------------------------------------
/Cakefile:
--------------------------------------------------------------------------------
  1 | fs            = require 'fs'
  2 | path          = require 'path'
  3 | {extend}      = require './lib/coffee-script/helpers'
  4 | CoffeeScript  = require './lib/coffee-script'
  5 | {spawn, exec} = require 'child_process'
  6 | 
  7 | # ANSI Terminal Colors.
  8 | bold  = '\033[0;1m'
  9 | red   = '\033[0;31m'
 10 | green = '\033[0;32m'
 11 | reset = '\033[0m'
 12 | 
 13 | # Built file header.
 14 | header = """
 15 |   /**
 16 |    * CoffeeScript Compiler v#{CoffeeScript.VERSION}
 17 |    * http://coffeescript.org
 18 |    *
 19 |    * Copyright 2011, Jeremy Ashkenas
 20 |    * Released under the MIT License
 21 |    */
 22 | """
 23 | 
 24 | sources = [
 25 |   'coffee-script', 'grammar', 'helpers'
 26 |   'lexer', 'nodes', 'rewriter', 'scope'
 27 | ].map (filename) -> "src/#{filename}.coffee"
 28 | 
 29 | # Run a CoffeeScript through our node/coffee interpreter.
 30 | run = (args, cb) ->
 31 |   proc =         spawn 'bin/coffee', args
 32 |   proc.stderr.on 'data', (buffer) -> console.log buffer.toString()
 33 |   proc.on        'exit', (status) ->
 34 |     process.exit(1) if status != 0
 35 |     cb() if typeof cb is 'function'
 36 | 
 37 | # Log a message with a color.
 38 | log = (message, color, explanation) ->
 39 |   console.log color + message + reset + ' ' + (explanation or '')
 40 | 
 41 | option '-p', '--prefix [DIR]', 'set the installation prefix for `cake install`'
 42 | 
 43 | task 'install', 'install CoffeeScript into /usr/local (or --prefix)', (options) ->
 44 |   base = options.prefix or '/usr/local'
 45 |   lib  = "#{base}/lib/coffee-script"
 46 |   bin  = "#{base}/bin"
 47 |   node = "~/.node_libraries/coffee-script"
 48 |   console.log   "Installing CoffeeScript to #{lib}"
 49 |   console.log   "Linking to #{node}"
 50 |   console.log   "Linking 'coffee' to #{bin}/coffee"
 51 |   exec([
 52 |     "mkdir -p #{lib} #{bin}"
 53 |     "cp -rf bin lib LICENSE README package.json src #{lib}"
 54 |     "ln -sfn #{lib}/bin/coffee #{bin}/coffee"
 55 |     "ln -sfn #{lib}/bin/cake #{bin}/cake"
 56 |     "mkdir -p ~/.node_libraries"
 57 |     "ln -sfn #{lib}/lib/coffee-script #{node}"
 58 |   ].join(' && '), (err, stdout, stderr) ->
 59 |     if err then console.log stderr.trim() else log 'done', green
 60 |   )
 61 | 
 62 | 
 63 | task 'build', 'build the CoffeeScript language from source', build = (cb) ->
 64 |   files = fs.readdirSync 'src'
 65 |   files = ('src/' + file for file in files when file.match(/\.coffee$/))
 66 |   run ['-c', '-o', 'lib/coffee-script'].concat(files), cb
 67 | 
 68 | 
 69 | task 'build:full', 'rebuild the source twice, and run the tests', ->
 70 |   build ->
 71 |     build ->
 72 |       csPath = './lib/coffee-script'
 73 |       delete require.cache[require.resolve csPath]
 74 |       unless runTests require csPath
 75 |         process.exit 1
 76 | 
 77 | 
 78 | task 'build:parser', 'rebuild the Jison parser (run build first)', ->
 79 |   extend global, require('util')
 80 |   require 'jison'
 81 |   parser = require('./lib/coffee-script/grammar').parser
 82 |   fs.writeFile 'lib/coffee-script/parser.js', parser.generate()
 83 | 
 84 | 
 85 | task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter', ->
 86 |   exec 'plist2syntax ../coffee-script-tmbundle/Syntaxes/CoffeeScript.tmLanguage', (err) ->
 87 |     throw err if err
 88 |     exec 'sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax'
 89 | 
 90 | 
 91 | task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
 92 |   code = ''
 93 |   for name in ['helpers', 'rewriter', 'lexer', 'parser', 'scope', 'nodes', 'coffee-script', 'browser']
 94 |     code += """
 95 |       require['./#{name}'] = new function() {
 96 |         var exports = this;
 97 |         #{fs.readFileSync "lib/coffee-script/#{name}.js"}
 98 |       };
 99 |     """
100 |   code = """
101 |     this.CoffeeScript = function() {
102 |       function require(path){ return require[path]; }
103 |       #{code}
104 |       return require['./coffee-script']
105 |     }()
106 |   """
107 |   unless process.env.MINIFY is 'false'
108 |     {parser, uglify} = require 'uglify-js'
109 |     code = uglify.gen_code uglify.ast_squeeze uglify.ast_mangle parser.parse code
110 |   fs.writeFileSync 'extras/coffee-script.js', header + '\n' + code
111 |   console.log "built ... running browser tests:"
112 |   invoke 'test:browser'
113 | 
114 | 
115 | task 'doc:site', 'watch and continually rebuild the documentation for the website', ->
116 |   exec 'rake doc', (err) ->
117 |     throw err if err
118 | 
119 | 
120 | task 'doc:source', 'rebuild the internal documentation', ->
121 |   exec 'docco src/*.coffee && cp -rf docs documentation && rm -r docs', (err) ->
122 |     throw err if err
123 | 
124 | 
125 | task 'doc:underscore', 'rebuild the Underscore.coffee documentation page', ->
126 |   exec 'docco examples/underscore.coffee && cp -rf docs documentation && rm -r docs', (err) ->
127 |     throw err if err
128 | 
129 | task 'bench', 'quick benchmark of compilation time', ->
130 |   {Rewriter} = require './lib/coffee-script/rewriter'
131 |   co     = sources.map((name) -> fs.readFileSync name).join '\n'
132 |   fmt    = (ms) -> " #{bold}#{ "   #{ms}".slice -4 }#{reset} ms"
133 |   total  = 0
134 |   now    = Date.now()
135 |   time   = -> total += ms = -(now - now = Date.now()); fmt ms
136 |   tokens = CoffeeScript.tokens co, rewrite: false
137 |   console.log "Lex    #{time()} (#{tokens.length} tokens)"
138 |   tokens = new Rewriter().rewrite tokens
139 |   console.log "Rewrite#{time()} (#{tokens.length} tokens)"
140 |   nodes  = CoffeeScript.nodes tokens
141 |   console.log "Parse  #{time()}"
142 |   js     = nodes.compile bare: true
143 |   console.log "Compile#{time()} (#{js.length} chars)"
144 |   console.log "total  #{ fmt total }"
145 | 
146 | task 'loc', 'count the lines of source code in the CoffeeScript compiler', ->
147 |   exec "cat #{ sources.join(' ') } | grep -v '^\\( *#\\|\\s*$\\)' | wc -l | tr -s ' '", (err, stdout) ->
148 |     console.log stdout.trim()
149 | 
150 | 
151 | # Run the CoffeeScript test suite.
152 | runTests = (CoffeeScript) ->
153 |   startTime   = Date.now()
154 |   currentFile = null
155 |   passedTests = 0
156 |   failures    = []
157 | 
158 |   # Make "global" reference available to tests
159 |   global.global = global
160 | 
161 |   # Mix in the assert module globally, to make it available for tests.
162 |   addGlobal = (name, func) ->
163 |     global[name] = ->
164 |       passedTests += 1
165 |       func arguments...
166 | 
167 |   addGlobal name, func for name, func of require 'assert'
168 | 
169 |   # Convenience aliases.
170 |   global.eq = global.strictEqual
171 |   global.CoffeeScript = CoffeeScript
172 | 
173 |   # Our test helper function for delimiting different test cases.
174 |   global.test = (description, fn) ->
175 |     try
176 |       fn.test = {description, currentFile}
177 |       fn.call(fn)
178 |     catch e
179 |       e.description = description if description?
180 |       e.source      = fn.toString() if fn.toString?
181 |       failures.push filename: currentFile, error: e
182 | 
183 |   # A recursive functional equivalence helper; uses egal for testing equivalence.
184 |   # See http://wiki.ecmascript.org/doku.php?id=harmony:egal
185 |   arrayEqual = (a, b) ->
186 |     if a is b
187 |       # 0 isnt -0
188 |       a isnt 0 or 1/a is 1/b
189 |     else if a instanceof Array and b instanceof Array
190 |       return no unless a.length is b.length
191 |       return no for el, idx in a when not arrayEqual el, b[idx]
192 |       yes
193 |     else
194 |       # NaN is NaN
195 |       a isnt a and b isnt b
196 | 
197 |   global.arrayEq = (a, b, msg) -> ok arrayEqual(a,b), msg
198 | 
199 |   # When all the tests have run, collect and print errors.
200 |   # If a stacktrace is available, output the compiled function source.
201 |   process.on 'exit', ->
202 |     time = ((Date.now() - startTime) / 1000).toFixed(2)
203 |     message = "passed #{passedTests} tests in #{time} seconds#{reset}"
204 |     return log(message, green) unless failures.length
205 |     log "failed #{failures.length} and #{message}", red
206 |     for fail in failures
207 |       {error, filename}  = fail
208 |       jsFilename         = filename.replace(/\.coffee$/,'.js')
209 |       match              = error.stack?.match(new RegExp(fail.file+":(\\d+):(\\d+)"))
210 |       match              = error.stack?.match(/on line (\d+):/) unless match
211 |       [match, line, col] = match if match
212 |       console.log ''
213 |       log "  #{error.description}", red if error.description
214 |       log "  #{error.stack}", red
215 |       log "  #{jsFilename}: line #{line ? 'unknown'}, column #{col ? 'unknown'}", red
216 |       console.log "  #{error.source}" if error.source
217 |     return
218 | 
219 |   # Run every test in the `test` folder, recording failures.
220 |   files = fs.readdirSync 'test'
221 |   for file in files when file.match /\.coffee$/i
222 |     currentFile = filename = path.join 'test', file
223 |     code = fs.readFileSync filename
224 |     try
225 |       CoffeeScript.run code.toString(), {filename}
226 |     catch error
227 |       failures.push {filename, error}
228 |   return !failures.length
229 | 
230 | 
231 | task 'test', 'run the CoffeeScript language test suite', ->
232 |   runTests CoffeeScript
233 | 
234 | 
235 | task 'test:browser', 'run the test suite against the merged browser script', ->
236 |   source = fs.readFileSync 'extras/coffee-script.js', 'utf-8'
237 |   result = {}
238 |   global.testingBrowser = yes
239 |   (-> eval source).call result
240 |   runTests result.CoffeeScript
241 | 


--------------------------------------------------------------------------------