├── 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 Jump To … browser.coffee cake.coffee coffee-script.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee repl.coffee rewriter.coffee scope.coffee index.coffee ¶ Loader for CoffeeScript as a Node.js library. exports[key] = val for key, val of require './coffee-script' 2 | 3 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------
Loader for CoffeeScript as a Node.js library.
exports[key] = val for key, val of require './coffee-script' 2 | 3 |