├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin └── coffee ├── lib ├── browser.js ├── cli.js ├── compiler.js ├── functional-helpers.js ├── helpers.js ├── js-nodes.js ├── module.js ├── nodes.js ├── optimiser.js ├── parser.js ├── pegjs-coffee-plugin.js ├── preprocessor.js ├── register.js ├── repl.js └── run.js ├── package.json ├── register.js ├── src ├── browser.coffee ├── cli.coffee ├── compiler.coffee ├── functional-helpers.coffee ├── grammar.pegcoffee ├── helpers.coffee ├── js-nodes.coffee ├── module.coffee ├── nodes.coffee ├── optimiser.coffee ├── parser.coffee ├── pegjs-coffee-plugin.coffee ├── preprocessor.coffee ├── register.coffee ├── repl.coffee └── run.coffee └── test ├── _setup.coffee ├── arrays.coffee ├── assignment.coffee ├── booleans.coffee ├── classes.coffee ├── cli-eval-errors-files ├── 0.coffee └── 1.coffee ├── cli-eval-errors.coffee ├── cluster.coffee ├── cluster ├── cluster.coffee └── cluster.litcoffee ├── comprehensions.coffee ├── debugger.coffee ├── error-messages.coffee ├── function-invocation.coffee ├── functions.coffee ├── literate.coffee ├── macros.coffee ├── member-access.coffee ├── objects.coffee ├── operators.coffee ├── optimisations.coffee ├── parser.coffee ├── poe.coffee ├── ranges.coffee ├── regexps.coffee ├── repl.coffee ├── scope.coffee ├── scope.litcoffee ├── shakespeare.coffee ├── side-effects.coffee ├── slices.coffee ├── splices.coffee.disabled ├── string-interpolation.coffee ├── truthiness.coffee └── try-catch-finally.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib/bootstrap 3 | coffee-script-redux-*.tgz 4 | node_modules 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coffee-script-redux-*.tgz 2 | .travis.yml 3 | .gitignore 4 | lib/bootstrap 5 | dist 6 | src 7 | test 8 | Makefile 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.11" 6 | before_install: 7 | git submodule update --init 8 | before_script: 9 | make build 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Michael Ficarra 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of the project nor the names of its contributors may be 12 | used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | This software is provided by the copyright holders and contributors "as is" and 16 | any express or implied warranties, including, but not limited to, the implied 17 | warranties of merchantability and fitness for a particular purpose are 18 | disclaimed. In no event shall the copyright holder be liable for any direct, 19 | indirect, incidental, special, exemplary, or consequential damages (including, 20 | but not limited to, procurement of substitute goods or services; loss of use, 21 | data, or profits; or business interruption) however caused and on any theory of 22 | liability, whether in contract, strict liability, or tort (including negligence 23 | or otherwise) arising in any way out of the use of this software, even if 24 | advised of the possibility of such damage. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: all 2 | 3 | SRC = $(wildcard src/*.coffee | sort) 4 | LIB = $(SRC:src/%.coffee=lib/%.js) lib/parser.js 5 | BOOTSTRAPS = $(SRC:src/%.coffee=lib/bootstrap/%.js) lib/bootstrap/parser.js 6 | LIBMIN = $(LIB:lib/%.js=lib/%.min.js) 7 | TEST = $(wildcard test/*.coffee | sort) 8 | ROOT = $(shell pwd) 9 | 10 | COFFEE = bin/coffee --js --bare 11 | PEGJS = node_modules/.bin/pegjs --cache --plugin ./lib/pegjs-coffee-plugin 12 | MOCHA = node_modules/.bin/mocha --compilers coffee:./register -u tdd 13 | CJSIFY = node_modules/.bin/cjsify --export CoffeeScript 14 | MINIFIER = node_modules/.bin/esmangle 15 | 16 | all: $(LIB) 17 | build: all 18 | parser: lib/parser.js 19 | browser: dist/coffee-script-redux.min.js 20 | min: minify 21 | minify: $(LIBMIN) 22 | # TODO: test-browser 23 | # TODO: doc 24 | # TODO: bench 25 | 26 | 27 | lib: 28 | mkdir lib/ 29 | lib/bootstrap: lib 30 | mkdir -p lib/bootstrap 31 | 32 | 33 | lib/parser.js: src/grammar.pegcoffee bootstraps lib lib/pegjs-coffee-plugin.js 34 | $(PEGJS) <"$<" >"$@.tmp" && mv "$@.tmp" "$@" 35 | lib/bootstrap/parser.js: src/grammar.pegcoffee lib/bootstrap lib/pegjs-coffee-plugin.js 36 | $(PEGJS) <"$<" >"$@" 37 | lib/bootstrap/%.js: src/%.coffee lib/bootstrap 38 | $(COFFEE) -i "$<" >"$@" 39 | bootstraps: $(BOOTSTRAPS) lib/bootstrap 40 | cp lib/bootstrap/* lib 41 | lib/%.js: src/%.coffee lib/bootstrap/%.js bootstraps lib 42 | $(COFFEE) -i "$<" >"$@.tmp" && mv "$@.tmp" "$@" 43 | 44 | 45 | dist: 46 | mkdir dist/ 47 | 48 | dist/coffee-script-redux.js: lib/browser.js dist 49 | $(CJSIFY) src/browser.coffee -vx CoffeeScript \ 50 | -a /src/register.coffee: \ 51 | -a /src/parser.coffee:/lib/parser.js \ 52 | --source-map "$@.map" > "$@" 53 | 54 | dist/coffee-script-redux.min.js: lib/browser.js dist 55 | $(CJSIFY) src/browser.coffee -vmx CoffeeScript \ 56 | -a /src/register.coffee: \ 57 | -a /src/parser.coffee:/lib/parser.js \ 58 | --source-map "$@.map" > "$@" 59 | 60 | 61 | lib/%.min.js: lib/%.js lib/coffee-script 62 | $(MINIFIER) <"$<" >"$@" 63 | 64 | 65 | .PHONY: default all build parser browser min minify test coverage install loc clean 66 | 67 | test: 68 | $(MOCHA) -R dot test/*.coffee 69 | 70 | # TODO: use Constellation/ibrik for coverage 71 | coverage: 72 | @which jscoverage || (echo "install node-jscoverage"; exit 1) 73 | rm -rf instrumented 74 | jscoverage -v lib instrumented 75 | $(MOCHA) -R dot 76 | $(MOCHA) -r instrumented/compiler -R html-cov > coverage.html 77 | @xdg-open coverage.html &> /dev/null 78 | 79 | install: 80 | npm install -g . 81 | 82 | loc: 83 | wc -l src/* 84 | 85 | clean: 86 | rm -rf instrumented 87 | rm -f coverage.html 88 | rm -rf lib 89 | rm -rf dist 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CoffeeScript II: The Wrath of Khan 2 | ================================== 3 | 4 | ``` 5 | { 6 | } } { 7 | { { } } 8 | } }{ { 9 | { }{ } } _____ __ __ 10 | ( }{ }{ { ) / ____| / _|/ _| 11 | .- { { } { }} -. | | ___ | |_| |_ ___ ___ 12 | ( ( } { } { } } ) | | / _ \| _| _/ _ \/ _ \ 13 | |`-..________ ..-'| | |___| (_) | | | || __/ __/ 14 | | | \_____\___/|_| |_| \___|\___| .-''-. 15 | | ;--. .' .-. ) 16 | | (__ \ _____ _ _ / .' / / 17 | | | ) ) / ____| (_) | | (_/ / / 18 | | |/ / | (___ ___ _ __ _ _ __ | |_ / / 19 | | ( / \___ \ / __| '__| | '_ \| __| / / 20 | | |/ ____) | (__| | | | |_) | |_ . ' 21 | | | |_____/ \___|_| |_| .__/ \__| / / _.-') 22 | `-.._________..-' | | .' ' _.'.-'' 23 | |_| / /.-'_.' 24 | / _.' 25 | ( _.-' 26 | ``` 27 | 28 | ### Status 29 | 30 | Complete enough to use for nearly every project. See the [roadmap to 2.0](https://github.com/michaelficarra/CoffeeScriptRedux/wiki/Roadmap). 31 | 32 | ### Getting Started 33 | 34 | npm install -g coffee-script-redux 35 | coffee --help 36 | coffee --js output.js 37 | 38 | Before transitioning from Jeremy's compiler, see the 39 | [intentional deviations from jashkenas/coffee-script](https://github.com/michaelficarra/CoffeeScriptRedux/wiki/Intentional-Deviations-From-jashkenas-coffee-script) 40 | wiki page. 41 | 42 | ### Development 43 | 44 | git clone git://github.com/michaelficarra/CoffeeScriptRedux.git && cd CoffeeScriptRedux && npm install 45 | make clean && git checkout -- lib && make -j build && make test 46 | 47 | ### Notable Contributors 48 | 49 | I'd like to thank the following financial contributors for their large 50 | donations to [the Kickstarter project](https://www.kickstarter.com/projects/michaelficarra/make-a-better-coffeescript-compiler) 51 | that funded the initial work on this compiler. 52 | Together, you donated over $10,000. Without you, I wouldn't have been able to do this. 53 | 54 | * [Groupon](https://www.groupon.com/), who is generously allowing me to work in their offices 55 | * [Trevor Burnham](http://trevorburnham.com) 56 | * [Shopify](https://www.shopify.com/) 57 | * [Abakas](http://abakas.com) 58 | * [37signals](http://37signals.com) 59 | * [Brightcove](https://www.brightcove.com/en/) 60 | * [Gaslight](https://teamgaslight.com/) 61 | * [Pantheon](https://pantheon.io/) 62 | * Benbria 63 | * Sam Stephenson 64 | * Bevan Hunt 65 | * Meryn Stol 66 | * Rob Tsuk 67 | * Dion Almaer 68 | * Andrew Davey 69 | * Thomas Burleson 70 | * Michael Kedzierski 71 | * Jeremy Kemper 72 | * Kyle Cordes 73 | * Jason R. Lauman 74 | * Martin Drenovac (Envizion Systems - Aust) 75 | * Julian Bilcke 76 | * Michael Edmondson 77 | 78 | And of course, thank you [Jeremy](https://github.com/jashkenas) (and all the other 79 | [contributors](https://github.com/jashkenas/coffeescript/graphs/contributors)) 80 | for making [the original CoffeeScript compiler](https://github.com/jashkenas/coffeescript). 81 | -------------------------------------------------------------------------------- /bin/coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require(require('path').join(__dirname, '..', 'lib', 'cli')); 3 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var CoffeeScript, runScripts; 3 | module.exports = CoffeeScript = require('./module'); 4 | CoffeeScript['eval'] = function (code, options) { 5 | if (null == options) 6 | options = {}; 7 | if (null != options.bare) 8 | options.bare; 9 | else 10 | options.bare = true; 11 | if (null != options.optimise) 12 | options.optimise; 13 | else 14 | options.optimise = true; 15 | return eval(CoffeeScript.cs2js(code, options)); 16 | }; 17 | CoffeeScript.run = function (code, options) { 18 | if (null == options) 19 | options = {}; 20 | options.bare = true; 21 | if (null != options.optimise) 22 | options.optimise; 23 | else 24 | options.optimise = true; 25 | return Function(CoffeeScript.cs2js(code, options))(); 26 | }; 27 | CoffeeScript.load = function (url, callback) { 28 | var xhr; 29 | xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest; 30 | xhr.open('GET', url, true); 31 | if ('overrideMimeType' in xhr) 32 | xhr.overrideMimeType('text/plain'); 33 | xhr.onreadystatechange = function () { 34 | if (!(xhr.readyState === xhr.DONE)) 35 | return; 36 | if (xhr.status === 0 || xhr.status === 200) { 37 | CoffeeScript.run(xhr.responseText); 38 | } else { 39 | throw new Error('Could not load ' + url); 40 | } 41 | if (callback) 42 | return callback(); 43 | }; 44 | return xhr.send(null); 45 | }; 46 | runScripts = function () { 47 | var coffees, execute, index, s, scripts; 48 | scripts = document.getElementsByTagName('script'); 49 | coffees = function (accum$) { 50 | for (var i$ = 0, length$ = scripts.length; i$ < length$; ++i$) { 51 | s = scripts[i$]; 52 | if (!(s.type === 'text/coffeescript')) 53 | continue; 54 | accum$.push(s); 55 | } 56 | return accum$; 57 | }.call(this, []); 58 | index = 0; 59 | (execute = function () { 60 | var script; 61 | if (!(script = coffees[index++])) 62 | return; 63 | if (script.src) { 64 | return CoffeeScript.load(script.src, execute); 65 | } else { 66 | CoffeeScript.run(script.innerHTML); 67 | return execute(); 68 | } 69 | })(); 70 | return null; 71 | }; 72 | if ('undefined' !== typeof addEventListener && null != addEventListener) { 73 | addEventListener('DOMContentLoaded', runScripts, false); 74 | } else if ('undefined' !== typeof attachEvent && null != attachEvent) { 75 | attachEvent('onload', runScripts); 76 | } 77 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var $0, additionalArgs, cache$, cache$1, cache$2, CoffeeScript, concat, cscodegen, escodegen, esmangle, foldl, fs, humanReadable, input, inputName, inputSource, inspect, knownOpts, nopt, numberLines, optAliases, Optimiser, option, options, output, parameter, path, pkg, positionalArgs, Preprocessor, processInput, Repl, runMain; 3 | fs = require('fs'); 4 | path = require('path'); 5 | cache$ = require('./functional-helpers'); 6 | concat = cache$.concat; 7 | foldl = cache$.foldl; 8 | cache$1 = require('./helpers'); 9 | numberLines = cache$1.numberLines; 10 | humanReadable = cache$1.humanReadable; 11 | Preprocessor = require('./preprocessor').Preprocessor; 12 | Optimiser = require('./optimiser').Optimiser; 13 | runMain = require('./run').runMain; 14 | CoffeeScript = require('./module'); 15 | Repl = require('./repl'); 16 | nopt = require('nopt'); 17 | cscodegen = function () { 18 | try { 19 | return require('cscodegen'); 20 | } catch (e$) { 21 | return; 22 | } 23 | }.call(this); 24 | escodegen = function () { 25 | try { 26 | return require('escodegen'); 27 | } catch (e$1) { 28 | return; 29 | } 30 | }.call(this); 31 | esmangle = function () { 32 | try { 33 | return require('esmangle'); 34 | } catch (e$2) { 35 | return; 36 | } 37 | }.call(this); 38 | inspect = function (o) { 39 | return require('util').inspect(o, false, 9e9, true); 40 | }; 41 | knownOpts = {}; 42 | option = function () { 43 | var o; 44 | for (var i$ = 0, length$ = arguments.length; i$ < length$; ++i$) { 45 | o = arguments[i$]; 46 | knownOpts[o] = Boolean; 47 | } 48 | }; 49 | parameter = function () { 50 | var p; 51 | for (var i$ = 0, length$ = arguments.length; i$ < length$; ++i$) { 52 | p = arguments[i$]; 53 | knownOpts[p] = String; 54 | } 55 | }; 56 | optAliases = { 57 | b: '--bare', 58 | c: '--compile', 59 | e: '--eval', 60 | f: '--cscodegen', 61 | I: '--require', 62 | i: '--input', 63 | j: '--js', 64 | l: '--literate', 65 | m: '--minify', 66 | o: '--output', 67 | p: '--parse', 68 | v: '--version', 69 | w: '--watch' 70 | }; 71 | option('parse', 'compile', 'optimise', 'debug', 'literate', 'raw', 'version', 'help'); 72 | parameter('cli', 'input', 'nodejs', 'output', 'watch'); 73 | if (null != escodegen) { 74 | option('bare', 'js', 'source-map', 'eval', 'repl'); 75 | parameter('source-map-file', 'require'); 76 | if (null != esmangle) 77 | option('minify'); 78 | } 79 | if (null != cscodegen) 80 | option('cscodegen'); 81 | options = nopt(knownOpts, optAliases, process.argv, 2); 82 | positionalArgs = options.argv.remain; 83 | delete options.argv; 84 | if (null != options.optimise) 85 | options.optimise; 86 | else 87 | options.optimise = true; 88 | options.sourceMap = options['source-map']; 89 | options.sourceMapFile = options['source-map-file']; 90 | if (!(options.compile || options.js || options.sourceMap || options.parse || options['eval'] || options.cscodegen)) 91 | if (!(null != escodegen)) { 92 | options.compile = true; 93 | } else if (positionalArgs.length) { 94 | options['eval'] = true; 95 | options.input = positionalArgs.shift(); 96 | additionalArgs = positionalArgs; 97 | } else { 98 | options.repl = true; 99 | } 100 | if (1 !== (null != options.parse ? options.parse : 0) + (null != options.compile ? options.compile : 0) + (null != options.js ? options.js : 0) + (null != options.sourceMap ? options.sourceMap : 0) + (null != options['eval'] ? options['eval'] : 0) + (null != options.cscodegen ? options.cscodegen : 0) + (null != options.repl ? options.repl : 0)) { 101 | console.error('Error: At most one of --parse (-p), --compile (-c), --js (-j), --source-map, --eval (-e), --cscodegen, or --repl may be used.'); 102 | process.exit(1); 103 | } 104 | if (1 < (null != options.input) + (null != options.watch) + (null != options.cli)) { 105 | console.error('Error: At most one of --input (-i), --watch (-w), or --cli may be used.'); 106 | process.exit(1); 107 | } 108 | if (null != options.require && !options['eval']) { 109 | console.error('Error: --require (-I) depends on --eval (-e)'); 110 | process.exit(1); 111 | } 112 | if (options.minify && !(options.js || options['eval'])) { 113 | console.error('Error: --minify does not make sense without --js or --eval'); 114 | process.exit(1); 115 | } 116 | if (options.bare && !(options.compile || options.js || options.sourceMap || options['eval'])) { 117 | console.error('Error: --bare does not make sense without --compile, --js, --source-map, or --eval'); 118 | process.exit(1); 119 | } 120 | if (options.sourceMapFile && !options.js) { 121 | console.error('Error: --source-map-file depends on --js'); 122 | process.exit(1); 123 | } 124 | if (null != options.input && fs.statSync(options.input).isDirectory() && (!(null != options.output) || (null != (cache$2 = fs.statSync(options.output)) ? cache$2.isFile() : void 0))) { 125 | console.error('Error: when --input is a directory, --output must be provided, and --output must not reference a file'); 126 | process.exit(1); 127 | } 128 | if (options.cscodegen && !(null != cscodegen)) { 129 | console.error('Error: cscodegen must be installed to use --cscodegen'); 130 | process.exit(1); 131 | } 132 | output = function (out) { 133 | if (options.output) { 134 | return fs.writeFile(options.output, '' + out + '\n', function (err) { 135 | if (null != err) 136 | throw err; 137 | }); 138 | } else { 139 | return process.stdout.write('' + out + '\n'); 140 | } 141 | }; 142 | if (options.help) { 143 | $0 = path.basename(process.argv[0]) === 'node' ? process.argv[1] : process.argv[0]; 144 | $0 = path.basename($0); 145 | console.log('\n Usage: (OPT is interpreted by ' + $0 + ', ARG is passed to FILE)\n\n ' + $0 + ' OPT* -{p,c,j,f} OPT*\n example: ' + $0 + ' --js --no-optimise output.js\n ' + $0 + ' [-e] FILE {OPT,ARG}* [-- ARG*]\n example: ' + $0 + ' myfile.coffee arg0 arg1\n ' + $0 + ' OPT* [--repl] OPT*\n example: ' + $0 + '\n\n -b, --bare omit the top-level function wrapper\n -c, --compile output a JSON-serialised AST representation of the output\n -e, --eval evaluate compiled JavaScript\n -f, --cscodegen output cscodegen-generated CoffeeScript code\n -i, --input FILE file to be used as input instead of STDIN\n -I, --require FILE require a library before a script is executed\n -j, --js generate JavaScript output\n -l, --literate treat the input as literate CoffeeScript code\n -m, --minify run compiled javascript output through a JS minifier\n -o, --output FILE file to be used as output instead of STDOUT\n -p, --parse output a JSON-serialised AST representation of the input\n -v, --version display the version number\n -w, --watch FILE watch the given file/directory for changes\n --cli INPUT pass a string from the command line as input\n --debug output intermediate representations on stderr for debug\n --help display this help message\n --nodejs OPTS pass options through to the node binary\n --optimise enable optimisations (default: on)\n --raw preserve source position and raw parse information\n --repl run an interactive CoffeeScript REPL\n --source-map generate source map\n --source-map-file FILE file used as output for source map when using --js\n\n Unless given --input or --cli flags, `' + $0 + '` will operate on stdin/stdout.\n When none of --{parse,compile,js,source-map,eval,cscodegen,repl} are given,\n If positional arguments were given\n * --eval is implied\n * the first positional argument is used as an input filename\n * additional positional arguments are passed as arguments to the script\n Else --repl is implied\n'); 146 | } else if (options.version) { 147 | pkg = require('./../package.json'); 148 | console.log('CoffeeScript version ' + pkg.version); 149 | } else if (options.repl) { 150 | CoffeeScript.register(); 151 | process.argv.shift(); 152 | Repl.start(); 153 | } else { 154 | input = ''; 155 | inputName = null != options.input ? options.input : options.cli && 'cli' || 'stdin'; 156 | inputSource = null != options.input ? fs.realpathSync(options.input) : options.cli && '(cli)' || '(stdin)'; 157 | processInput = function (err) { 158 | var cache$3, e, js, jsAST, preprocessed, result, sourceMap, sourceMappingUrl; 159 | if (null != err) 160 | throw err; 161 | result = null; 162 | input = input.toString(); 163 | if (65279 === input.charCodeAt(0)) 164 | input = input.slice(1); 165 | if (options.debug) 166 | try { 167 | console.error('### PREPROCESSED CS ###'); 168 | preprocessed = Preprocessor.process(input, { literate: options.literate }); 169 | console.error(numberLines(humanReadable(preprocessed))); 170 | } catch (e$3) { 171 | } 172 | try { 173 | result = CoffeeScript.parse(input, { 174 | optimise: false, 175 | raw: options.raw || options.sourceMap || options.sourceMapFile || options['eval'], 176 | inputSource: inputSource, 177 | literate: options.literate 178 | }); 179 | } catch (e$4) { 180 | e = e$4; 181 | console.error(e.message); 182 | process.exit(1); 183 | } 184 | if (options.debug && options.optimise && null != result) { 185 | console.error('### PARSED CS-AST ###'); 186 | console.error(inspect(result.toBasicObject())); 187 | } 188 | if (options.optimise && null != result) 189 | result = Optimiser.optimise(result); 190 | if (options.parse) 191 | if (null != result) { 192 | output(inspect(result.toBasicObject())); 193 | return; 194 | } else { 195 | process.exit(1); 196 | } 197 | if (options.debug && null != result) { 198 | console.error('### ' + (options.optimise ? 'OPTIMISED' : 'PARSED') + ' CS-AST ###'); 199 | console.error(inspect(result.toBasicObject())); 200 | } 201 | if (options.cscodegen) { 202 | try { 203 | result = cscodegen.generate(result); 204 | } catch (e$5) { 205 | e = e$5; 206 | console.error(e.stack || e.message); 207 | process.exit(1); 208 | } 209 | if (null != result) { 210 | output(result); 211 | return; 212 | } else { 213 | process.exit(1); 214 | } 215 | } 216 | jsAST = CoffeeScript.compile(result, { bare: options.bare }); 217 | if (options.compile) 218 | if (null != jsAST) { 219 | output(inspect(jsAST)); 220 | return; 221 | } else { 222 | process.exit(1); 223 | } 224 | if (options.debug && null != jsAST) { 225 | console.error('### COMPILED JS-AST ###'); 226 | console.error(inspect(jsAST)); 227 | } 228 | if (options.minify) 229 | try { 230 | jsAST = esmangle.mangle(esmangle.optimize(jsAST), { destructive: true }); 231 | } catch (e$6) { 232 | e = e$6; 233 | console.error(e.stack || e.message); 234 | process.exit(1); 235 | } 236 | if (options.sourceMap) { 237 | try { 238 | sourceMap = CoffeeScript.sourceMap(jsAST, inputName, { compact: options.minify }); 239 | } catch (e$7) { 240 | e = e$7; 241 | console.error(e.stack || e.message); 242 | process.exit(1); 243 | } 244 | if (null != sourceMap) { 245 | output('' + sourceMap); 246 | return; 247 | } else { 248 | process.exit(1); 249 | } 250 | } 251 | try { 252 | cache$3 = CoffeeScript.jsWithSourceMap(jsAST, inputName, { compact: options.minify }); 253 | js = cache$3.code; 254 | sourceMap = cache$3.map; 255 | cache$3; 256 | } catch (e$8) { 257 | e = e$8; 258 | console.error(e.stack || e.message); 259 | process.exit(1); 260 | } 261 | if (options.js) { 262 | if (options.sourceMapFile) { 263 | fs.writeFileSync(options.sourceMapFile, '' + sourceMap); 264 | sourceMappingUrl = options.output ? path.relative(path.dirname(options.output), options.sourceMapFile) : options.sourceMapFile; 265 | js = '' + js + '\n\n//# sourceMappingURL=' + sourceMappingUrl + ''; 266 | } 267 | output(js); 268 | return; 269 | } 270 | if (options['eval']) { 271 | CoffeeScript.register(); 272 | process.argv = [ 273 | process.argv[1], 274 | options.input 275 | ].concat(additionalArgs); 276 | runMain(input, js, jsAST, inputSource); 277 | return; 278 | } 279 | }; 280 | if (null != options.input) { 281 | fs.stat(options.input, function (err, stats) { 282 | if (null != err) 283 | throw err; 284 | if (stats.isDirectory()) 285 | options.input = path.join(options.input, 'index.coffee'); 286 | return fs.readFile(options.input, function (err, contents) { 287 | if (null != err) 288 | throw err; 289 | input = contents; 290 | return processInput(); 291 | }); 292 | }); 293 | } else if (null != options.watch) { 294 | options.watch; 295 | } else if (null != options.cli) { 296 | input = options.cli; 297 | processInput(); 298 | } else { 299 | process.stdin.on('data', function (data) { 300 | return input += data; 301 | }); 302 | process.stdin.on('end', processInput); 303 | process.stdin.setEncoding('utf8'); 304 | process.stdin.resume(); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /lib/functional-helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var concat, foldl, map, nub, span; 3 | this.any = function (list, fn) { 4 | var e; 5 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 6 | e = list[i$]; 7 | if (fn(e)) 8 | return true; 9 | } 10 | return false; 11 | }; 12 | this.all = function (list, fn) { 13 | var e; 14 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 15 | e = list[i$]; 16 | if (!fn(e)) 17 | return false; 18 | } 19 | return true; 20 | }; 21 | this.foldl = foldl = function (memo, list, fn) { 22 | var i; 23 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 24 | i = list[i$]; 25 | memo = fn(memo, i); 26 | } 27 | return memo; 28 | }; 29 | this.foldl1 = function (list, fn) { 30 | return foldl(list[0], list.slice(1), fn); 31 | }; 32 | this.map = map = function (list, fn) { 33 | var e; 34 | return function (accum$) { 35 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 36 | e = list[i$]; 37 | accum$.push(fn(e)); 38 | } 39 | return accum$; 40 | }.call(this, []); 41 | }; 42 | this.concat = concat = function (list) { 43 | var cache$; 44 | return (cache$ = []).concat.apply(cache$, [].slice.call(list)); 45 | }; 46 | this.concatMap = function (list, fn) { 47 | return concat(map(list, fn)); 48 | }; 49 | this.intersect = function (listA, listB) { 50 | var a; 51 | return function (accum$) { 52 | for (var i$ = 0, length$ = listA.length; i$ < length$; ++i$) { 53 | a = listA[i$]; 54 | if (!in$(a, listB)) 55 | continue; 56 | accum$.push(a); 57 | } 58 | return accum$; 59 | }.call(this, []); 60 | }; 61 | this.difference = function (listA, listB) { 62 | var a; 63 | return function (accum$) { 64 | for (var i$ = 0, length$ = listA.length; i$ < length$; ++i$) { 65 | a = listA[i$]; 66 | if (!!in$(a, listB)) 67 | continue; 68 | accum$.push(a); 69 | } 70 | return accum$; 71 | }.call(this, []); 72 | }; 73 | this.nub = nub = function (list) { 74 | var i, result; 75 | result = []; 76 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 77 | i = list[i$]; 78 | if (!!in$(i, result)) 79 | continue; 80 | result.push(i); 81 | } 82 | return result; 83 | }; 84 | this.union = function (listA, listB) { 85 | var b; 86 | return listA.concat(function (accum$) { 87 | for (var cache$ = nub(listB), i$ = 0, length$ = cache$.length; i$ < length$; ++i$) { 88 | b = cache$[i$]; 89 | if (!!in$(b, listA)) 90 | continue; 91 | accum$.push(b); 92 | } 93 | return accum$; 94 | }.call(this, [])); 95 | }; 96 | this.flip = function (fn) { 97 | return function (b, a) { 98 | return fn.call(this, a, b); 99 | }; 100 | }; 101 | this.owns = function (hop) { 102 | return function (a, b) { 103 | return hop.call(a, b); 104 | }; 105 | }({}.hasOwnProperty); 106 | this.span = span = function (list, f) { 107 | var cache$, ys, zs; 108 | if (list.length === 0) { 109 | return [ 110 | [], 111 | [] 112 | ]; 113 | } else if (f(list[0])) { 114 | cache$ = span(list.slice(1), f); 115 | ys = cache$[0]; 116 | zs = cache$[1]; 117 | return [ 118 | [list[0]].concat([].slice.call(ys)), 119 | zs 120 | ]; 121 | } else { 122 | return [ 123 | [], 124 | list 125 | ]; 126 | } 127 | }; 128 | this.divMod = function (a, b) { 129 | var c, div, mod; 130 | c = a % b; 131 | mod = c < 0 ? c + b : c; 132 | div = Math.floor(a / b); 133 | return [ 134 | div, 135 | mod 136 | ]; 137 | }; 138 | this.partition = function (list, fn) { 139 | var item, result; 140 | result = [ 141 | [], 142 | [] 143 | ]; 144 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 145 | item = list[i$]; 146 | result[+!fn(item)].push(item); 147 | } 148 | return result; 149 | }; 150 | function in$(member, list) { 151 | for (var i = 0, length = list.length; i < length; ++i) 152 | if (i in list && list[i] === member) 153 | return true; 154 | return false; 155 | } 156 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var beingDeclared, cache$, cleanMarkers, colourise, COLOURS, concat, concatMap, CS, difference, envEnrichments, envEnrichments_, foldl, humanReadable, map, nub, numberLines, pointToErrorLocation, SUPPORTS_COLOUR, usedAsExpression, usedAsExpression_; 3 | cache$ = require('./functional-helpers'); 4 | concat = cache$.concat; 5 | concatMap = cache$.concatMap; 6 | difference = cache$.difference; 7 | foldl = cache$.foldl; 8 | map = cache$.map; 9 | nub = cache$.nub; 10 | CS = require('./nodes'); 11 | COLOURS = { 12 | red: '\x1B[31m', 13 | green: '\x1B[32m', 14 | yellow: '\x1B[33m', 15 | blue: '\x1B[34m', 16 | magenta: '\x1B[35m', 17 | cyan: '\x1B[36m' 18 | }; 19 | SUPPORTS_COLOUR = ('undefined' !== typeof process && null != process && null != process.stderr ? process.stderr.isTTY : void 0) && !process.env.NODE_DISABLE_COLORS; 20 | colourise = function (colour, str) { 21 | if (SUPPORTS_COLOUR) { 22 | return '' + COLOURS[colour] + str + '\x1B[39m'; 23 | } else { 24 | return str; 25 | } 26 | }; 27 | this.numberLines = numberLines = function (input, startLine) { 28 | var currLine, i, line, lines, numbered, pad, padSize; 29 | if (null == startLine) 30 | startLine = 1; 31 | lines = input.split('\n'); 32 | padSize = ('' + (lines.length + startLine - 1)).length; 33 | numbered = function (accum$) { 34 | for (var i$ = 0, length$ = lines.length; i$ < length$; ++i$) { 35 | line = lines[i$]; 36 | i = i$; 37 | currLine = '' + (i + startLine); 38 | pad = Array(padSize + 1).join('0').slice(currLine.length); 39 | accum$.push('' + pad + currLine + ' : ' + lines[i]); 40 | } 41 | return accum$; 42 | }.call(this, []); 43 | return numbered.join('\n'); 44 | }; 45 | cleanMarkers = function (str) { 46 | return str.replace(/[\uEFEF\uEFFE\uEFFF]/g, ''); 47 | }; 48 | this.humanReadable = humanReadable = function (str) { 49 | return str.replace(/\uEFEF/g, '(INDENT)').replace(/\uEFFE/g, '(DEDENT)').replace(/\uEFFF/g, '(TERM)'); 50 | }; 51 | this.formatParserError = function (input, e) { 52 | var found, message, realColumn, unicode; 53 | realColumn = cleanMarkers(('' + input.split('\n')[e.line - 1] + '\n').slice(0, e.column)).length; 54 | if (!(null != e.found)) 55 | return 'Syntax error on line ' + e.line + ', column ' + realColumn + ': unexpected end of input'; 56 | found = JSON.stringify(humanReadable(e.found)); 57 | found = found.replace(/^"|"$/g, '').replace(/'/g, "\\'").replace(/\\"/g, '"'); 58 | unicode = e.found.charCodeAt(0).toString(16).toUpperCase(); 59 | unicode = '\\u' + '0000'.slice(unicode.length) + unicode; 60 | message = 'Syntax error on line ' + e.line + ', column ' + realColumn + ": unexpected '" + found + "' (" + unicode + ')'; 61 | return '' + message + '\n' + pointToErrorLocation(input, e.line, realColumn); 62 | }; 63 | this.pointToErrorLocation = pointToErrorLocation = function (source, line, column, numLinesOfContext) { 64 | var currentLineOffset, lines, numberedLines, padSize, postLines, preLines, startLine; 65 | if (null == numLinesOfContext) 66 | numLinesOfContext = 3; 67 | lines = source.split('\n'); 68 | if (!lines[lines.length - 1]) 69 | lines.pop(); 70 | currentLineOffset = line - 1; 71 | startLine = currentLineOffset - numLinesOfContext; 72 | if (startLine < 0) 73 | startLine = 0; 74 | preLines = lines.slice(startLine, +currentLineOffset + 1 || 9e9); 75 | preLines[preLines.length - 1] = colourise('yellow', preLines[preLines.length - 1]); 76 | postLines = lines.slice(currentLineOffset + 1, +(currentLineOffset + numLinesOfContext) + 1 || 9e9); 77 | numberedLines = numberLines(cleanMarkers([].slice.call(preLines).concat([].slice.call(postLines)).join('\n')), startLine + 1).split('\n'); 78 | preLines = numberedLines.slice(0, preLines.length); 79 | postLines = numberedLines.slice(preLines.length); 80 | column = cleanMarkers(('' + lines[currentLineOffset] + '\n').slice(0, column)).length; 81 | padSize = (currentLineOffset + 1 + postLines.length).toString(10).length; 82 | return [].slice.call(preLines).concat(['' + colourise('red', Array(padSize + 1).join('^')) + ' : ' + Array(column).join(' ') + colourise('red', '^')], [].slice.call(postLines)).join('\n'); 83 | }; 84 | this.beingDeclared = beingDeclared = function (assignment) { 85 | switch (false) { 86 | case !!(null != assignment): 87 | return []; 88 | case !assignment['instanceof'](CS.Identifiers): 89 | return [assignment.data]; 90 | case !assignment['instanceof'](CS.Rest): 91 | return beingDeclared(assignment.expression); 92 | case !assignment['instanceof'](CS.MemberAccessOps): 93 | return []; 94 | case !assignment['instanceof'](CS.DefaultParam): 95 | return beingDeclared(assignment.param); 96 | case !assignment['instanceof'](CS.ArrayInitialiser): 97 | return concatMap(assignment.members, beingDeclared); 98 | case !assignment['instanceof'](CS.ObjectInitialiser): 99 | return concatMap(assignment.vals(), beingDeclared); 100 | default: 101 | throw new Error('beingDeclared: Non-exhaustive patterns in case: ' + assignment.className); 102 | } 103 | }; 104 | this.declarationsFor = function (node, inScope) { 105 | var vars; 106 | vars = envEnrichments(node, inScope); 107 | return foldl(new CS.Undefined().g(), vars, function (expr, v) { 108 | return new CS.AssignOp(new CS.Identifier(v).g(), expr).g(); 109 | }); 110 | }; 111 | usedAsExpression_ = function (ancestors) { 112 | var grandparent, parent; 113 | parent = ancestors[0]; 114 | grandparent = ancestors[1]; 115 | switch (false) { 116 | case !!(null != parent): 117 | return true; 118 | case !parent['instanceof'](CS.Program, CS.Class): 119 | return false; 120 | case !parent['instanceof'](CS.SeqOp): 121 | return this === parent.right && usedAsExpression(parent, ancestors.slice(1)); 122 | case !(parent['instanceof'](CS.Block) && parent.statements.indexOf(this) !== parent.statements.length - 1): 123 | return false; 124 | case !(parent['instanceof'](CS.Functions) && parent.body === this && null != grandparent && grandparent['instanceof'](CS.Constructor)): 125 | return false; 126 | default: 127 | return true; 128 | } 129 | }; 130 | this.usedAsExpression = usedAsExpression = function (node, ancestors) { 131 | return usedAsExpression_.call(node, ancestors); 132 | }; 133 | envEnrichments_ = function (inScope) { 134 | var possibilities; 135 | if (null == inScope) 136 | inScope = []; 137 | possibilities = nub(function () { 138 | switch (false) { 139 | case !this['instanceof'](CS.AssignOp): 140 | return concat([ 141 | beingDeclared(this.assignee), 142 | envEnrichments(this.expression) 143 | ]); 144 | case !this['instanceof'](CS.Class): 145 | return concat([ 146 | beingDeclared(this.nameAssignee), 147 | envEnrichments(this.parent) 148 | ]); 149 | case !this['instanceof'](CS.ForIn, CS.ForOf): 150 | return concat([ 151 | beingDeclared(this.keyAssignee), 152 | beingDeclared(this.valAssignee), 153 | envEnrichments(this.target), 154 | envEnrichments(this.step), 155 | envEnrichments(this.filter), 156 | envEnrichments(this.body) 157 | ]); 158 | case !this['instanceof'](CS.Try): 159 | return concat([ 160 | beingDeclared(this.catchAssignee), 161 | envEnrichments(this.body), 162 | envEnrichments(this.catchBody), 163 | envEnrichments(this.finallyBody) 164 | ]); 165 | case !this['instanceof'](CS.Functions): 166 | return []; 167 | default: 168 | return concatMap(this.childNodes, function (this$) { 169 | return function (child) { 170 | if (in$(child, this$.listMembers)) { 171 | return concatMap(this$[child], function (m) { 172 | return envEnrichments(m, inScope); 173 | }); 174 | } else { 175 | return envEnrichments(this$[child], inScope); 176 | } 177 | }; 178 | }(this)); 179 | } 180 | }.call(this)); 181 | return difference(possibilities, inScope); 182 | }; 183 | this.envEnrichments = envEnrichments = function (node) { 184 | var args; 185 | args = arguments.length > 1 ? [].slice.call(arguments, 1) : []; 186 | if (null != node) { 187 | return envEnrichments_.apply(node, args); 188 | } else { 189 | return []; 190 | } 191 | }; 192 | function in$(member, list) { 193 | for (var i = 0, length = list.length; i < length; ++i) 194 | if (i in list && list[i] === member) 195 | return true; 196 | return false; 197 | } 198 | -------------------------------------------------------------------------------- /lib/js-nodes.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var ArrayExpression, AssignmentExpression, BinaryExpression, BlockStatement, cache$, cache$1, CallExpression, createNode, ctor, difference, exports, FunctionDeclaration, FunctionExpression, GenSym, handleLists, handlePrimitives, Identifier, isStatement, Literal, LogicalExpression, MemberExpression, NewExpression, node, nodeData, Nodes, ObjectExpression, params, Program, SequenceExpression, SwitchCase, SwitchStatement, TryStatement, UnaryExpression, UpdateExpression, VariableDeclaration; 3 | difference = require('./functional-helpers').difference; 4 | exports = null != ('undefined' !== typeof module && null != module ? module.exports : void 0) ? 'undefined' !== typeof module && null != module ? module.exports : void 0 : this; 5 | createNode = function (type, props) { 6 | return function (super$) { 7 | extends$(class$, super$); 8 | function class$() { 9 | var i, prop; 10 | for (var i$ = 0, length$ = props.length; i$ < length$; ++i$) { 11 | prop = props[i$]; 12 | i = i$; 13 | this[prop] = arguments[i]; 14 | } 15 | } 16 | class$.prototype.type = type; 17 | class$.prototype.childNodes = props; 18 | return class$; 19 | }(Nodes); 20 | }; 21 | this.Nodes = Nodes = function () { 22 | function Nodes() { 23 | } 24 | Nodes.prototype.listMembers = []; 25 | Nodes.prototype['instanceof'] = function () { 26 | var ctor, ctors; 27 | ctors = arguments.length > 0 ? [].slice.call(arguments, 0) : []; 28 | for (var i$ = 0, length$ = ctors.length; i$ < length$; ++i$) { 29 | ctor = ctors[i$]; 30 | if (!(this.type === ctor.prototype.type)) 31 | continue; 32 | return true; 33 | } 34 | return false; 35 | }; 36 | Nodes.prototype.toBasicObject = function () { 37 | var child, obj, p; 38 | obj = { type: this.type }; 39 | if (null != this.leadingComments) 40 | obj.leadingComments = this.leadingComments; 41 | for (var i$ = 0, length$ = this.childNodes.length; i$ < length$; ++i$) { 42 | child = this.childNodes[i$]; 43 | if (in$(child, this.listMembers)) { 44 | obj[child] = function (accum$) { 45 | for (var i$1 = 0, length$1 = this[child].length; i$1 < length$1; ++i$1) { 46 | p = this[child][i$1]; 47 | accum$.push('undefined' !== typeof p && null != p ? p.toBasicObject() : void 0); 48 | } 49 | return accum$; 50 | }.call(this, []); 51 | } else { 52 | obj[child] = null != this[child] ? this[child].toBasicObject() : void 0; 53 | } 54 | } 55 | if (null != this.line && null != this.column) 56 | obj.loc = { 57 | start: { 58 | line: this.line, 59 | column: this.column 60 | } 61 | }; 62 | if (null != this.offset) 63 | obj.range = [ 64 | this.offset, 65 | null != this.raw ? this.offset + this.raw.length : void 0 66 | ]; 67 | if (null != this.raw) 68 | obj.raw = this.raw; 69 | return obj; 70 | }; 71 | return Nodes; 72 | }(); 73 | nodeData = [ 74 | [ 75 | 'ArrayExpression', 76 | false, 77 | ['elements'] 78 | ], 79 | [ 80 | 'AssignmentExpression', 81 | false, 82 | [ 83 | 'operator', 84 | 'left', 85 | 'right' 86 | ] 87 | ], 88 | [ 89 | 'BinaryExpression', 90 | false, 91 | [ 92 | 'operator', 93 | 'left', 94 | 'right' 95 | ] 96 | ], 97 | [ 98 | 'BlockStatement', 99 | true, 100 | ['body'] 101 | ], 102 | [ 103 | 'BreakStatement', 104 | true, 105 | ['label'] 106 | ], 107 | [ 108 | 'CallExpression', 109 | false, 110 | [ 111 | 'callee', 112 | 'arguments' 113 | ] 114 | ], 115 | [ 116 | 'CatchClause', 117 | true, 118 | [ 119 | 'param', 120 | 'body' 121 | ] 122 | ], 123 | [ 124 | 'ConditionalExpression', 125 | false, 126 | [ 127 | 'test', 128 | 'consequent', 129 | 'alternate' 130 | ] 131 | ], 132 | [ 133 | 'ContinueStatement', 134 | true, 135 | ['label'] 136 | ], 137 | [ 138 | 'DebuggerStatement', 139 | true, 140 | [] 141 | ], 142 | [ 143 | 'DoWhileStatement', 144 | true, 145 | [ 146 | 'body', 147 | 'test' 148 | ] 149 | ], 150 | [ 151 | 'EmptyStatement', 152 | true, 153 | [] 154 | ], 155 | [ 156 | 'ExpressionStatement', 157 | true, 158 | ['expression'] 159 | ], 160 | [ 161 | 'ForInStatement', 162 | true, 163 | [ 164 | 'left', 165 | 'right', 166 | 'body' 167 | ] 168 | ], 169 | [ 170 | 'ForStatement', 171 | true, 172 | [ 173 | 'init', 174 | 'test', 175 | 'update', 176 | 'body' 177 | ] 178 | ], 179 | [ 180 | 'FunctionDeclaration', 181 | true, 182 | [ 183 | 'id', 184 | 'params', 185 | 'body' 186 | ] 187 | ], 188 | [ 189 | 'FunctionExpression', 190 | false, 191 | [ 192 | 'id', 193 | 'params', 194 | 'body' 195 | ] 196 | ], 197 | [ 198 | 'GenSym', 199 | false, 200 | [ 201 | 'ns', 202 | 'uniqueId' 203 | ] 204 | ], 205 | [ 206 | 'Identifier', 207 | false, 208 | ['name'] 209 | ], 210 | [ 211 | 'IfStatement', 212 | true, 213 | [ 214 | 'test', 215 | 'consequent', 216 | 'alternate' 217 | ] 218 | ], 219 | [ 220 | 'LabeledStatement', 221 | true, 222 | [ 223 | 'label', 224 | 'body' 225 | ] 226 | ], 227 | [ 228 | 'Literal', 229 | false, 230 | ['value'] 231 | ], 232 | [ 233 | 'LogicalExpression', 234 | false, 235 | [ 236 | 'operator', 237 | 'left', 238 | 'right' 239 | ] 240 | ], 241 | [ 242 | 'MemberExpression', 243 | false, 244 | [ 245 | 'computed', 246 | 'object', 247 | 'property' 248 | ] 249 | ], 250 | [ 251 | 'NewExpression', 252 | false, 253 | [ 254 | 'callee', 255 | 'arguments' 256 | ] 257 | ], 258 | [ 259 | 'ObjectExpression', 260 | false, 261 | ['properties'] 262 | ], 263 | [ 264 | 'Program', 265 | true, 266 | ['body'] 267 | ], 268 | [ 269 | 'Property', 270 | true, 271 | [ 272 | 'key', 273 | 'value' 274 | ] 275 | ], 276 | [ 277 | 'ReturnStatement', 278 | true, 279 | ['argument'] 280 | ], 281 | [ 282 | 'SequenceExpression', 283 | false, 284 | ['expressions'] 285 | ], 286 | [ 287 | 'SwitchCase', 288 | true, 289 | [ 290 | 'test', 291 | 'consequent' 292 | ] 293 | ], 294 | [ 295 | 'SwitchStatement', 296 | true, 297 | [ 298 | 'discriminant', 299 | 'cases' 300 | ] 301 | ], 302 | [ 303 | 'ThisExpression', 304 | false, 305 | [] 306 | ], 307 | [ 308 | 'ThrowStatement', 309 | true, 310 | ['argument'] 311 | ], 312 | [ 313 | 'TryStatement', 314 | true, 315 | [ 316 | 'block', 317 | 'handlers', 318 | 'finalizer' 319 | ] 320 | ], 321 | [ 322 | 'UnaryExpression', 323 | false, 324 | [ 325 | 'operator', 326 | 'argument' 327 | ] 328 | ], 329 | [ 330 | 'UpdateExpression', 331 | false, 332 | [ 333 | 'operator', 334 | 'prefix', 335 | 'argument' 336 | ] 337 | ], 338 | [ 339 | 'VariableDeclaration', 340 | true, 341 | [ 342 | 'kind', 343 | 'declarations' 344 | ] 345 | ], 346 | [ 347 | 'VariableDeclarator', 348 | true, 349 | [ 350 | 'id', 351 | 'init' 352 | ] 353 | ], 354 | [ 355 | 'WhileStatement', 356 | true, 357 | [ 358 | 'test', 359 | 'body' 360 | ] 361 | ], 362 | [ 363 | 'WithStatement', 364 | true, 365 | [ 366 | 'object', 367 | 'body' 368 | ] 369 | ] 370 | ]; 371 | for (var i$ = 0, length$ = nodeData.length; i$ < length$; ++i$) { 372 | { 373 | cache$ = nodeData[i$]; 374 | node = cache$[0]; 375 | isStatement = cache$[1]; 376 | params = cache$[2]; 377 | } 378 | exports[node] = ctor = createNode(node, params); 379 | ctor.prototype.isStatement = isStatement; 380 | ctor.prototype.isExpression = !isStatement; 381 | } 382 | cache$1 = exports; 383 | Program = cache$1.Program; 384 | BlockStatement = cache$1.BlockStatement; 385 | Literal = cache$1.Literal; 386 | Identifier = cache$1.Identifier; 387 | FunctionExpression = cache$1.FunctionExpression; 388 | CallExpression = cache$1.CallExpression; 389 | SequenceExpression = cache$1.SequenceExpression; 390 | ArrayExpression = cache$1.ArrayExpression; 391 | BinaryExpression = cache$1.BinaryExpression; 392 | UnaryExpression = cache$1.UnaryExpression; 393 | NewExpression = cache$1.NewExpression; 394 | VariableDeclaration = cache$1.VariableDeclaration; 395 | ObjectExpression = cache$1.ObjectExpression; 396 | MemberExpression = cache$1.MemberExpression; 397 | UpdateExpression = cache$1.UpdateExpression; 398 | AssignmentExpression = cache$1.AssignmentExpression; 399 | LogicalExpression = cache$1.LogicalExpression; 400 | GenSym = cache$1.GenSym; 401 | FunctionDeclaration = cache$1.FunctionDeclaration; 402 | VariableDeclaration = cache$1.VariableDeclaration; 403 | SwitchStatement = cache$1.SwitchStatement; 404 | SwitchCase = cache$1.SwitchCase; 405 | TryStatement = cache$1.TryStatement; 406 | handlePrimitives = function (ctor, primitives) { 407 | ctor.prototype.childNodes = difference(ctor.prototype.childNodes, primitives); 408 | return ctor.prototype.toBasicObject = function () { 409 | var obj, primitive; 410 | obj = Nodes.prototype.toBasicObject.call(this); 411 | for (var i$1 = 0, length$1 = primitives.length; i$1 < length$1; ++i$1) { 412 | primitive = primitives[i$1]; 413 | obj[primitive] = this[primitive]; 414 | } 415 | return obj; 416 | }; 417 | }; 418 | handlePrimitives(AssignmentExpression, ['operator']); 419 | handlePrimitives(BinaryExpression, ['operator']); 420 | handlePrimitives(LogicalExpression, ['operator']); 421 | handlePrimitives(GenSym, [ 422 | 'ns', 423 | 'uniqueId' 424 | ]); 425 | handlePrimitives(Identifier, ['name']); 426 | handlePrimitives(Literal, ['value']); 427 | handlePrimitives(MemberExpression, ['computed']); 428 | handlePrimitives(UnaryExpression, ['operator']); 429 | handlePrimitives(UpdateExpression, [ 430 | 'operator', 431 | 'prefix' 432 | ]); 433 | handlePrimitives(VariableDeclaration, ['kind']); 434 | handleLists = function (ctor, listProps) { 435 | return ctor.prototype.listMembers = listProps; 436 | }; 437 | handleLists(ArrayExpression, ['elements']); 438 | handleLists(BlockStatement, ['body']); 439 | handleLists(CallExpression, ['arguments']); 440 | handleLists(FunctionDeclaration, ['params']); 441 | handleLists(FunctionExpression, ['params']); 442 | handleLists(NewExpression, ['arguments']); 443 | handleLists(ObjectExpression, ['properties']); 444 | handleLists(Program, ['body']); 445 | handleLists(SequenceExpression, ['expressions']); 446 | handleLists(SwitchCase, ['consequent']); 447 | handleLists(SwitchStatement, ['cases']); 448 | handleLists(TryStatement, ['handlers']); 449 | handleLists(VariableDeclaration, ['declarations']); 450 | FunctionDeclaration.prototype.generated = FunctionExpression.prototype.generated = false; 451 | FunctionDeclaration.prototype.g = FunctionExpression.prototype.g = function () { 452 | this.generated = true; 453 | return this; 454 | }; 455 | function isOwn$(o, p) { 456 | return {}.hasOwnProperty.call(o, p); 457 | } 458 | function extends$(child, parent) { 459 | for (var key in parent) 460 | if (isOwn$(parent, key)) 461 | child[key] = parent[key]; 462 | function ctor() { 463 | this.constructor = child; 464 | } 465 | ctor.prototype = parent.prototype; 466 | child.prototype = new ctor; 467 | child.__super__ = parent.prototype; 468 | return child; 469 | } 470 | function in$(member, list) { 471 | for (var i = 0, length = list.length; i < length; ++i) 472 | if (i in list && list[i] === member) 473 | return true; 474 | return false; 475 | } 476 | -------------------------------------------------------------------------------- /lib/module.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var CoffeeScript, Compiler, cscodegen, escodegen, escodegenFormat, ext, formatParserError, Nodes, Optimiser, Parser, pkg, Preprocessor; 3 | formatParserError = require('./helpers').formatParserError; 4 | Nodes = require('./nodes'); 5 | Preprocessor = require('./preprocessor').Preprocessor; 6 | Parser = require('./parser'); 7 | Optimiser = require('./optimiser').Optimiser; 8 | Compiler = require('./compiler').Compiler; 9 | cscodegen = function () { 10 | try { 11 | return require('cscodegen'); 12 | } catch (e$) { 13 | return; 14 | } 15 | }.call(this); 16 | escodegen = function () { 17 | try { 18 | return require('escodegen'); 19 | } catch (e$1) { 20 | return; 21 | } 22 | }.call(this); 23 | pkg = require('./../package.json'); 24 | escodegenFormat = { 25 | indent: { 26 | style: ' ', 27 | base: 0 28 | }, 29 | renumber: true, 30 | hexadecimal: true, 31 | quotes: 'auto', 32 | parentheses: false 33 | }; 34 | CoffeeScript = { 35 | CoffeeScript: CoffeeScript, 36 | Compiler: Compiler, 37 | Optimiser: Optimiser, 38 | Parser: Parser, 39 | Preprocessor: Preprocessor, 40 | Nodes: Nodes, 41 | VERSION: pkg.version, 42 | parse: function (coffee, options) { 43 | var e, parsed, preprocessed; 44 | if (null == options) 45 | options = {}; 46 | try { 47 | preprocessed = Preprocessor.process(coffee, { literate: options.literate }); 48 | parsed = Parser.parse(preprocessed, { 49 | raw: options.raw, 50 | inputSource: options.inputSource 51 | }); 52 | if (options.optimise) { 53 | return Optimiser.optimise(parsed); 54 | } else { 55 | return parsed; 56 | } 57 | } catch (e$2) { 58 | e = e$2; 59 | if (!(e instanceof Parser.SyntaxError)) 60 | throw e; 61 | throw new Error(formatParserError(preprocessed, e)); 62 | } 63 | }, 64 | compile: function (csAst, options) { 65 | return Compiler.compile(csAst, options).toBasicObject(); 66 | }, 67 | cs: function (csAst, options) { 68 | }, 69 | jsWithSourceMap: function (jsAst, name, options) { 70 | var targetName; 71 | if (null == name) 72 | name = 'unknown'; 73 | if (null == options) 74 | options = {}; 75 | if (!(null != escodegen)) 76 | throw new Error('escodegen not found: run `npm install escodegen`'); 77 | if (!{}.hasOwnProperty.call(jsAst, 'type')) 78 | jsAst = jsAst.toBasicObject(); 79 | targetName = options.sourceMapFile || options.sourceMap && options.output.match(/^.*[\\\/]([^\\\/]+)$/)[1]; 80 | return escodegen.generate(jsAst, { 81 | comment: !options.compact, 82 | sourceMapWithCode: true, 83 | sourceMap: name, 84 | file: targetName || 'unknown', 85 | format: options.compact ? escodegen.FORMAT_MINIFY : null != options.format ? options.format : escodegenFormat 86 | }); 87 | }, 88 | js: function (jsAst, options) { 89 | return this.jsWithSourceMap(jsAst, null, options).code; 90 | }, 91 | sourceMap: function (jsAst, name, options) { 92 | return this.jsWithSourceMap(jsAst, name, options).map; 93 | }, 94 | cs2js: function (input, options) { 95 | var csAST, jsAST; 96 | if (null == options) 97 | options = {}; 98 | if (null != options.optimise) 99 | options.optimise; 100 | else 101 | options.optimise = true; 102 | csAST = CoffeeScript.parse(input, options); 103 | jsAST = CoffeeScript.compile(csAST, { bare: options.bare }); 104 | return CoffeeScript.js(jsAST, { compact: options.compact || options.minify }); 105 | } 106 | }; 107 | module.exports = CoffeeScript; 108 | if (null != (null != require.extensions ? require.extensions['.node'] : void 0)) { 109 | CoffeeScript.register = function () { 110 | return require('./register'); 111 | }; 112 | for (var cache$ = [ 113 | '.coffee', 114 | '.litcoffee' 115 | ], i$ = 0, length$ = cache$.length; i$ < length$; ++i$) { 116 | ext = cache$[i$]; 117 | if (null != require.extensions[ext]) 118 | require.extensions[ext]; 119 | else 120 | require.extensions[ext] = function () { 121 | throw new Error('Use CoffeeScript.register() or require the coffee-script-redux/register module to require ' + ext + ' files.'); 122 | }; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/pegjs-coffee-plugin.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var CoffeeScript, compile, eachCode; 3 | CoffeeScript = require('./module'); 4 | eachCode = require('pegjs-each-code'); 5 | compile = function (csCode, options) { 6 | var csAST, jsAST; 7 | if (null == options) 8 | options = {}; 9 | csAST = CoffeeScript.parse('-> ' + csCode.trimRight()); 10 | if (csAST.body.statements.length > 1) 11 | throw new Error('inconsistent base indentation'); 12 | jsAST = CoffeeScript.compile(csAST, { 13 | bare: true, 14 | inScope: options.inScope 15 | }); 16 | jsAST.leadingComments = []; 17 | jsAST.body = jsAST.body[0].expression.body.body.concat(jsAST.body.slice(1)); 18 | return CoffeeScript.js(jsAST); 19 | }; 20 | exports.use = function (config) { 21 | return config.passes.transform.unshift(function (ast) { 22 | ast.initializer.code = CoffeeScript.cs2js(ast.initializer.code, { bare: true }); 23 | return eachCode(ast, function (node, labels, ruleName) { 24 | var error; 25 | try { 26 | return node.code = compile(node.code, { inScope: labels }); 27 | } catch (e$) { 28 | error = e$; 29 | throw new Error("In the '" + ruleName + "' rule:\n" + error.message + ''); 30 | } 31 | }); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /lib/preprocessor.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var DEDENT, INDENT, pointToErrorLocation, Preprocessor, StringScanner, TERM, ws; 3 | pointToErrorLocation = require('./helpers').pointToErrorLocation; 4 | StringScanner = require('StringScanner'); 5 | this.Preprocessor = Preprocessor = function () { 6 | ws = '\\t\\x0B\\f\\r \\xA0\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000\\uFEFF'; 7 | INDENT = '\uEFEF'; 8 | DEDENT = '\uEFFE'; 9 | TERM = '\uEFFF'; 10 | function Preprocessor(param$) { 11 | if (null == param$) 12 | param$ = {}; 13 | this.options = param$; 14 | this.preprocessed = ''; 15 | this.base = null; 16 | this.indents = []; 17 | this.context = []; 18 | } 19 | Preprocessor.process = function (input, options) { 20 | if (null == options) 21 | options = {}; 22 | return new Preprocessor(options).process(input); 23 | }; 24 | Preprocessor.prototype.err = function (c) { 25 | var columns, context, lines, token; 26 | token = function () { 27 | switch (c) { 28 | case INDENT: 29 | return 'INDENT'; 30 | case DEDENT: 31 | return 'DEDENT'; 32 | case TERM: 33 | return 'TERM'; 34 | default: 35 | return '"' + c.replace(/"/g, '\\"') + '"'; 36 | } 37 | }.call(this); 38 | lines = this.ss.str.substr(0, this.ss.pos).split(/\n/) || ['']; 39 | columns = null != lines[lines.length - 1] ? lines[lines.length - 1].length : 0; 40 | context = pointToErrorLocation(this.ss.str, lines.length, columns); 41 | throw new Error('Unexpected ' + token + '\n' + context); 42 | }; 43 | Preprocessor.prototype.peek = function () { 44 | if (this.context.length) { 45 | return this.context[this.context.length - 1]; 46 | } else { 47 | return null; 48 | } 49 | }; 50 | Preprocessor.prototype.observe = function (c) { 51 | var top; 52 | top = this.peek(); 53 | switch (c) { 54 | case '"""': 55 | case "'''": 56 | case '"': 57 | case "'": 58 | case '###': 59 | case '`': 60 | case '///': 61 | case '/': 62 | if (top === c) { 63 | this.context.pop(); 64 | } else { 65 | this.context.push(c); 66 | } 67 | break; 68 | case INDENT: 69 | case '#': 70 | case '#{': 71 | case '[': 72 | case '(': 73 | case '{': 74 | case '\\': 75 | case 'regexp-[': 76 | case 'regexp-(': 77 | case 'regexp-{': 78 | case 'heregexp-#': 79 | case 'heregexp-[': 80 | case 'heregexp-(': 81 | case 'heregexp-{': 82 | this.context.push(c); 83 | break; 84 | case DEDENT: 85 | if (!(top === INDENT)) 86 | this.err(c); 87 | this.indents.pop(); 88 | this.context.pop(); 89 | break; 90 | case '\n': 91 | if (!(top === '#' || top === 'heregexp-#')) 92 | this.err(c); 93 | this.context.pop(); 94 | break; 95 | case ']': 96 | if (!(top === '[' || top === 'regexp-[' || top === 'heregexp-[')) 97 | this.err(c); 98 | this.context.pop(); 99 | break; 100 | case ')': 101 | if (!(top === '(' || top === 'regexp-(' || top === 'heregexp-(')) 102 | this.err(c); 103 | this.context.pop(); 104 | break; 105 | case '}': 106 | if (!(top === '#{' || top === '{' || top === 'regexp-{' || top === 'heregexp-{')) 107 | this.err(c); 108 | this.context.pop(); 109 | break; 110 | case 'end-\\': 111 | if (!(top === '\\')) 112 | this.err(c); 113 | this.context.pop(); 114 | break; 115 | default: 116 | throw new Error('undefined token observed: ' + c); 117 | } 118 | return this.context; 119 | }; 120 | Preprocessor.prototype.p = function (s) { 121 | if (null != s) 122 | this.preprocessed = '' + this.preprocessed + s; 123 | return s; 124 | }; 125 | Preprocessor.prototype.scan = function (r) { 126 | return this.p(this.ss.scan(r)); 127 | }; 128 | Preprocessor.prototype.consumeIndentation = function () { 129 | var context, indent, indentIndex, lineLen, lines, message; 130 | if (this.ss.bol() || this.scan(new RegExp('(?:[' + ws + ']*\\n)+'))) { 131 | this.scan(new RegExp('(?:[' + ws + ']*(\\#\\#?(?!\\#)[^\\n]*)?\\n)+')); 132 | if (null != this.base) { 133 | if (!(this.ss.eos() || null != this.scan(this.base))) { 134 | throw new Error('inconsistent base indentation'); 135 | } 136 | } else { 137 | this.base = new RegExp('' + this.scan(new RegExp('[' + ws + ']*')) + ''); 138 | } 139 | indentIndex = 0; 140 | while (indentIndex < this.indents.length) { 141 | indent = this.indents[indentIndex]; 142 | if (this.ss.check(new RegExp('' + indent + ''))) { 143 | this.scan(new RegExp('' + indent + '')); 144 | } else if (this.ss.eos() || this.ss.check(new RegExp('[^' + ws + ']'))) { 145 | --indentIndex; 146 | this.p('' + DEDENT + TERM); 147 | this.observe(DEDENT); 148 | } else { 149 | lines = this.ss.str.substr(0, this.ss.pos).split(/\n/) || ['']; 150 | message = 'Syntax error on line ' + lines.length + ': indentation is ambiguous'; 151 | lineLen = this.indents.reduce(function (l, r) { 152 | return l + r.length; 153 | }, 0); 154 | context = pointToErrorLocation(this.ss.str, lines.length, lineLen); 155 | throw new Error('' + message + '\n' + context); 156 | } 157 | ++indentIndex; 158 | } 159 | if (this.ss.check(new RegExp('[' + ws + ']+[^' + ws + '#]'))) { 160 | this.indents.push(this.scan(new RegExp('[' + ws + ']+'))); 161 | this.p(INDENT); 162 | return this.observe(INDENT); 163 | } 164 | } 165 | }; 166 | Preprocessor.prototype.introduceContext = function () { 167 | var impliedRegexp, lastChar, pos, spaceBefore, tok; 168 | if (tok = this.scan(/"""|'''|\/\/\/|###|["'`#[({\\]/)) { 169 | return this.observe(tok); 170 | } else if (tok = this.scan(/\//)) { 171 | pos = this.ss.position(); 172 | if (pos > 1) { 173 | lastChar = this.ss.string()[pos - 2]; 174 | spaceBefore = new RegExp('[' + ws + ']').test(lastChar); 175 | impliedRegexp = /[;,=><*%^&|[(+!~-]/.test(lastChar); 176 | } 177 | if (pos === 1 || impliedRegexp || spaceBefore && !this.ss.check(new RegExp('[' + ws + '=]')) && this.ss.check(/[^\r\n]*\//)) 178 | return this.observe('/'); 179 | } 180 | }; 181 | Preprocessor.prototype.process = function (input) { 182 | var tok; 183 | if (this.options.literate) 184 | input = input.replace(/^( {0,3}\S)/gm, ' #$1'); 185 | this.ss = new StringScanner(input); 186 | while (!this.ss.eos()) { 187 | switch (this.peek()) { 188 | case null: 189 | case INDENT: 190 | this.consumeIndentation(); 191 | this.scan(/[^\n'"\\\/#`[(){}\]]+/); 192 | if (this.ss.check(/[})\]]/)) { 193 | while (this.peek() === INDENT) { 194 | this.p('' + DEDENT + TERM); 195 | this.observe(DEDENT); 196 | } 197 | this.observe(this.scan(/[})\]]/)); 198 | } else { 199 | this.introduceContext(); 200 | } 201 | break; 202 | case '#{': 203 | case '{': 204 | this.scan(/[^\n'"\\\/#`[({}]+/); 205 | if (tok = this.scan(/\}/)) { 206 | this.observe(tok); 207 | } else { 208 | this.consumeIndentation(); 209 | this.introduceContext(); 210 | } 211 | break; 212 | case '[': 213 | this.scan(/[^\n'"\\\/#`[({\]]+/); 214 | if (tok = this.scan(/\]/)) { 215 | this.observe(tok); 216 | } else { 217 | this.consumeIndentation(); 218 | this.introduceContext(); 219 | } 220 | break; 221 | case '(': 222 | this.scan(/[^\n'"\\\/#`[({)]+/); 223 | if (tok = this.scan(/\)/)) { 224 | this.observe(tok); 225 | } else { 226 | this.consumeIndentation(); 227 | this.introduceContext(); 228 | } 229 | break; 230 | case '\\': 231 | if (this.scan(/[\s\S]/)) 232 | this.observe('end-\\'); 233 | break; 234 | case '"""': 235 | this.scan(/(?:[^"#\\]+|""?(?!")|#(?!{)|\\.)+/); 236 | this.ss.scan(/\\\n/); 237 | if (tok = this.scan(/#{|"""/)) { 238 | this.observe(tok); 239 | } else if (tok = this.scan(/#{|"""/)) { 240 | this.observe(tok); 241 | } 242 | break; 243 | case '"': 244 | this.scan(/(?:[^"#\\]+|#(?!{)|\\.)+/); 245 | this.ss.scan(/\\\n/); 246 | if (tok = this.scan(/#{|"/)) 247 | this.observe(tok); 248 | break; 249 | case "'''": 250 | this.scan(/(?:[^'\\]+|''?(?!')|\\.)+/); 251 | this.ss.scan(/\\\n/); 252 | if (tok = this.scan(/'''/)) 253 | this.observe(tok); 254 | break; 255 | case "'": 256 | this.scan(/(?:[^'\\]+|\\.)+/); 257 | this.ss.scan(/\\\n/); 258 | if (tok = this.scan(/'/)) 259 | this.observe(tok); 260 | break; 261 | case '###': 262 | this.scan(/(?:[^#]+|##?(?!#))+/); 263 | if (tok = this.scan(/###/)) 264 | this.observe(tok); 265 | break; 266 | case '#': 267 | this.scan(/[^\n]+/); 268 | if (tok = this.scan(/\n/)) 269 | this.observe(tok); 270 | break; 271 | case '`': 272 | this.scan(/[^`]+/); 273 | if (tok = this.scan(/`/)) 274 | this.observe(tok); 275 | break; 276 | case '///': 277 | this.scan(/(?:[^[/#\\]+|\/\/?(?!\/)|\\.)+/); 278 | if (tok = this.scan(/#{|\/\/\/|\\/)) { 279 | this.observe(tok); 280 | } else if (this.ss.scan(/#/)) { 281 | this.observe('heregexp-#'); 282 | } else if (tok = this.scan(/[\[]/)) { 283 | this.observe('heregexp-' + tok); 284 | } 285 | break; 286 | case 'heregexp-[': 287 | this.scan(/(?:[^\]\/\\]+|\/\/?(?!\/))+/); 288 | if (tok = this.scan(/[\]\\]|#{|\/\/\//)) 289 | this.observe(tok); 290 | break; 291 | case 'heregexp-#': 292 | this.ss.scan(/(?:[^\n/]+|\/\/?(?!\/))+/); 293 | if (tok = this.scan(/\n|\/\/\//)) 294 | this.observe(tok); 295 | break; 296 | case '/': 297 | this.scan(/[^[/\\]+/); 298 | if (tok = this.scan(/[\/\\]/)) { 299 | this.observe(tok); 300 | } else if (tok = this.scan(/\[/)) { 301 | this.observe('regexp-' + tok); 302 | } 303 | break; 304 | case 'regexp-[': 305 | this.scan(/[^\]\\]+/); 306 | if (tok = this.scan(/[\]\\]/)) 307 | this.observe(tok); 308 | } 309 | } 310 | this.scan(new RegExp('[' + ws + '\\n]*$')); 311 | while (this.context.length) { 312 | switch (this.peek()) { 313 | case INDENT: 314 | this.p('' + DEDENT + TERM); 315 | this.observe(DEDENT); 316 | break; 317 | case '#': 318 | this.p('\n'); 319 | this.observe('\n'); 320 | break; 321 | default: 322 | throw new Error('Unclosed "' + this.peek().replace(/"/g, '\\"') + '" at EOF'); 323 | } 324 | } 325 | return this.preprocessed; 326 | }; 327 | return Preprocessor; 328 | }(); 329 | -------------------------------------------------------------------------------- /lib/register.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var child_process, coffeeBinary, CoffeeScript, fork, fs, path, runModule; 3 | child_process = require('child_process'); 4 | fs = require('fs'); 5 | path = require('path'); 6 | CoffeeScript = require('./module'); 7 | runModule = require('./run').runModule; 8 | module.exports = !(null != require.extensions['.coffee']); 9 | require.extensions['.coffee'] = function (module, filename) { 10 | var csAst, input, js, jsAst; 11 | input = fs.readFileSync(filename, 'utf8'); 12 | csAst = CoffeeScript.parse(input, { raw: true }); 13 | jsAst = CoffeeScript.compile(csAst); 14 | js = CoffeeScript.js(jsAst); 15 | return runModule(module, js, jsAst, filename); 16 | }; 17 | require.extensions['.litcoffee'] = function (module, filename) { 18 | var csAst, input, js, jsAst; 19 | input = fs.readFileSync(filename, 'utf8'); 20 | csAst = CoffeeScript.parse(input, { 21 | raw: true, 22 | literate: true 23 | }); 24 | jsAst = CoffeeScript.compile(csAst); 25 | js = CoffeeScript.js(jsAst); 26 | return runModule(module, js, jsAst, filename); 27 | }; 28 | fork = child_process.fork; 29 | if (!fork.coffeePatched) { 30 | coffeeBinary = path.resolve('bin', 'coffee'); 31 | child_process.fork = function (file, args, options) { 32 | if (null == args) 33 | args = []; 34 | if (null == options) 35 | options = {}; 36 | if (in$(path.extname(file), [ 37 | '.coffee', 38 | '.litcoffee' 39 | ])) { 40 | if (!Array.isArray(args)) { 41 | args = []; 42 | options = args || {}; 43 | } 44 | options.execPath || (options.execPath = coffeeBinary); 45 | } 46 | return fork(file, args, options); 47 | }; 48 | child_process.fork.coffeePatched = true; 49 | } 50 | delete require.cache[__filename]; 51 | function in$(member, list) { 52 | for (var i = 0, length = list.length; i < length; ++i) 53 | if (i in list && list[i] === member) 54 | return true; 55 | return false; 56 | } 57 | -------------------------------------------------------------------------------- /lib/repl.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var addHistory, addMultilineHandler, CoffeeScript, CS, fs, getCommandId, merge, nodeREPL, path, vm; 3 | fs = require('fs'); 4 | path = require('path'); 5 | vm = require('vm'); 6 | nodeREPL = require('repl'); 7 | CoffeeScript = require('./module'); 8 | CS = require('./nodes'); 9 | merge = require('./helpers').merge; 10 | addMultilineHandler = function (repl) { 11 | var buffer, cache$, continuationPrompt, enabled, initialPrompt, inputStream, nodeLineListener, origPrompt, outputStream, rli; 12 | cache$ = repl; 13 | rli = cache$.rli; 14 | inputStream = cache$.inputStream; 15 | outputStream = cache$.outputStream; 16 | origPrompt = null != repl._prompt ? repl._prompt : repl.prompt; 17 | initialPrompt = origPrompt.replace(/^[^> ]*/, function (x) { 18 | return x.replace(/./g, '-'); 19 | }); 20 | continuationPrompt = origPrompt.replace(/^[^> ]*>?/, function (x) { 21 | return x.replace(/./g, '.'); 22 | }); 23 | enabled = false; 24 | buffer = ''; 25 | nodeLineListener = rli.listeners('line')[0]; 26 | rli.removeListener('line', nodeLineListener); 27 | rli.on('line', function (cmd) { 28 | if (enabled) { 29 | buffer += '' + cmd + '\n'; 30 | rli.setPrompt(continuationPrompt); 31 | rli.prompt(true); 32 | } else { 33 | rli.setPrompt(origPrompt); 34 | nodeLineListener(cmd); 35 | } 36 | }); 37 | return inputStream.on('keypress', function (char, key) { 38 | if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) 39 | return; 40 | if (enabled) { 41 | if (!buffer.match(/\n/)) { 42 | enabled = !enabled; 43 | rli.setPrompt(origPrompt); 44 | rli.prompt(true); 45 | return; 46 | } 47 | if (null != rli.line && !rli.line.match(/^\s*$/)) 48 | return; 49 | enabled = !enabled; 50 | rli.line = ''; 51 | rli.cursor = 0; 52 | rli.output.cursorTo(0); 53 | rli.output.clearLine(1); 54 | buffer = buffer.replace(/\n/g, '\uFF00'); 55 | rli.emit('line', buffer); 56 | buffer = ''; 57 | } else { 58 | enabled = !enabled; 59 | rli.setPrompt(initialPrompt); 60 | rli.prompt(true); 61 | } 62 | }); 63 | }; 64 | addHistory = function (repl, filename, maxSize) { 65 | var buffer, e, fd, lastLine, original_clear, readFd, size, stat; 66 | try { 67 | stat = fs.statSync(filename); 68 | size = Math.min(maxSize, stat.size); 69 | readFd = fs.openSync(filename, 'r'); 70 | buffer = new Buffer(size); 71 | if (size) 72 | fs.readSync(readFd, buffer, 0, size, stat.size - size); 73 | repl.rli.history = buffer.toString().split('\n').reverse(); 74 | if (stat.size > maxSize) 75 | repl.rli.history.pop(); 76 | if (repl.rli.history[0] === '') 77 | repl.rli.history.shift(); 78 | repl.rli.historyIndex = -1; 79 | } catch (e$) { 80 | e = e$; 81 | repl.rli.history = []; 82 | } 83 | fd = fs.openSync(filename, 'a'); 84 | lastLine = repl.rli.history[0]; 85 | repl.rli.addListener('line', function (code) { 86 | if (code && code !== lastLine) { 87 | lastLine = code; 88 | return fs.writeSync(fd, '' + code + '\n'); 89 | } 90 | }); 91 | repl.rli.on('exit', function () { 92 | return fs.closeSync(fd); 93 | }); 94 | original_clear = repl.commands[getCommandId(repl, 'clear')].action; 95 | repl.commands[getCommandId(repl, 'clear')].action = function () { 96 | repl.outputStream.write('Clearing history...\n'); 97 | repl.rli.history = []; 98 | fs.closeSync(fd); 99 | fd = fs.openSync(filename, 'w'); 100 | lastLine = void 0; 101 | return original_clear.call(this); 102 | }; 103 | return repl.commands[getCommandId(repl, 'history')] = { 104 | help: 'Show command history', 105 | action: function () { 106 | repl.outputStream.write('' + repl.rli.history.slice().reverse().join('\n') + '\n'); 107 | return repl.displayPrompt(); 108 | } 109 | }; 110 | }; 111 | getCommandId = function (repl, commandName) { 112 | if (null != repl.commands['.help']) { 113 | return '.' + commandName; 114 | } else { 115 | return commandName; 116 | } 117 | }; 118 | module.exports = { 119 | start: function (opts) { 120 | var repl; 121 | if (null == opts) 122 | opts = {}; 123 | opts.prompt || (opts.prompt = 'coffee> '); 124 | if (null != opts.ignoreUndefined) 125 | opts.ignoreUndefined; 126 | else 127 | opts.ignoreUndefined = true; 128 | if (null != opts.historyFile) 129 | opts.historyFile; 130 | else 131 | opts.historyFile = path.join(process.env.HOME, '.coffee_history'); 132 | if (null != opts.historyMaxInputSize) 133 | opts.historyMaxInputSize; 134 | else 135 | opts.historyMaxInputSize = 10 * 1024; 136 | opts['eval'] || (opts['eval'] = function (input, context, filename, cb) { 137 | var err, inputAst, js, jsAst, transformedAst; 138 | input = input.replace(/\uFF00/g, '\n'); 139 | input = input.replace(/^\(([\s\S]*)\n\)$/m, '$1'); 140 | input = input.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'); 141 | if (/^\s*$/.test(input)) 142 | return cb(null); 143 | try { 144 | inputAst = CoffeeScript.parse(input, { 145 | filename: filename, 146 | raw: true 147 | }); 148 | transformedAst = new CS.AssignOp(new CS.Identifier('_'), inputAst.body); 149 | jsAst = CoffeeScript.compile(transformedAst, { 150 | bare: true, 151 | inScope: Object.keys(context) 152 | }); 153 | js = CoffeeScript.js(jsAst); 154 | return cb(null, vm.runInContext(js, context, filename)); 155 | } catch (e$) { 156 | err = e$; 157 | return cb('\x1B[0;31m' + err.constructor.name + ': ' + err.message + '\x1B[0m'); 158 | } 159 | }); 160 | repl = nodeREPL.start(opts); 161 | repl.on('exit', function () { 162 | return repl.outputStream.write('\n'); 163 | }); 164 | addMultilineHandler(repl); 165 | if (opts.historyFile) 166 | addHistory(repl, opts.historyFile, opts.historyMaxInputSize); 167 | repl.commands[getCommandId(repl, 'load')].help = 'Load code from a file into this REPL session'; 168 | return repl; 169 | } 170 | }; 171 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.0.0-beta9-dev 2 | var CoffeeScript, formatSourcePosition, Module, patched, patchStackTrace, path, runMain, runModule, SourceMapConsumer; 3 | path = require('path'); 4 | Module = require('module'); 5 | CoffeeScript = require('./module'); 6 | SourceMapConsumer = require('source-map').SourceMapConsumer; 7 | patched = false; 8 | patchStackTrace = function () { 9 | if (patched) 10 | return; 11 | patched = true; 12 | if (null != Module._sourceMaps) 13 | Module._sourceMaps; 14 | else 15 | Module._sourceMaps = {}; 16 | return Error.prepareStackTrace = function (err, stack) { 17 | var frame, frames, getSourceMapping, sourceFiles; 18 | sourceFiles = {}; 19 | getSourceMapping = function (filename, line, column) { 20 | var mapString, sourceMap; 21 | mapString = 'function' === typeof Module._sourceMaps[filename] ? Module._sourceMaps[filename]() : void 0; 22 | if (mapString) { 23 | sourceMap = null != sourceFiles[filename] ? sourceFiles[filename] : sourceFiles[filename] = new SourceMapConsumer(mapString); 24 | return sourceMap.originalPositionFor({ 25 | line: line, 26 | column: column - 1 27 | }); 28 | } 29 | }; 30 | frames = function (accum$) { 31 | for (var i$ = 0, length$ = stack.length; i$ < length$; ++i$) { 32 | frame = stack[i$]; 33 | if (frame.getFunction() === exports.runMain) 34 | break; 35 | accum$.push(' at ' + formatSourcePosition(frame, getSourceMapping)); 36 | } 37 | return accum$; 38 | }.call(this, []); 39 | return '' + err.toString() + '\n' + frames.join('\n') + '\n'; 40 | }; 41 | }; 42 | formatSourcePosition = function (frame, getSourceMapping) { 43 | var as, column, fileLocation, fileName, functionName, isConstructor, isMethodCall, line, methodName, source, tp, typeName; 44 | fileName = void 0; 45 | fileLocation = ''; 46 | if (frame.isNative()) { 47 | fileLocation = 'native'; 48 | } else { 49 | if (frame.isEval()) { 50 | fileName = frame.getScriptNameOrSourceURL(); 51 | if (!fileName) 52 | fileLocation = '' + frame.getEvalOrigin() + ', '; 53 | } else { 54 | fileName = frame.getFileName(); 55 | } 56 | fileName || (fileName = ''); 57 | line = frame.getLineNumber(); 58 | column = frame.getColumnNumber(); 59 | source = getSourceMapping(fileName, line, column); 60 | fileLocation = source ? null != source.line ? '' + fileName + ':' + source.line + ':' + (source.column + 1) + ', :' + line + ':' + column : '' + fileName + ' :' + line + ':' + column : '' + fileName + ':' + line + ':' + column; 61 | } 62 | functionName = frame.getFunctionName(); 63 | isConstructor = frame.isConstructor(); 64 | isMethodCall = !(frame.isToplevel() || isConstructor); 65 | if (isMethodCall) { 66 | methodName = frame.getMethodName(); 67 | typeName = frame.getTypeName(); 68 | if (functionName) { 69 | tp = as = ''; 70 | if (typeName && functionName.indexOf(typeName)) 71 | tp = '' + typeName + '.'; 72 | if (methodName && functionName.indexOf('.' + methodName) !== functionName.length - methodName.length - 1) 73 | as = ' [as ' + methodName + ']'; 74 | return '' + tp + functionName + as + ' (' + fileLocation + ')'; 75 | } else { 76 | return '' + typeName + '.' + (methodName || '') + ' (' + fileLocation + ')'; 77 | } 78 | } else if (isConstructor) { 79 | return 'new ' + (functionName || '') + ' (' + fileLocation + ')'; 80 | } else if (functionName) { 81 | return '' + functionName + ' (' + fileLocation + ')'; 82 | } else { 83 | return fileLocation; 84 | } 85 | }; 86 | runMain = function (csSource, jsSource, jsAst, filename) { 87 | var mainModule; 88 | mainModule = new Module('.'); 89 | mainModule.filename = process.argv[1] = filename; 90 | process.mainModule = mainModule; 91 | Module._cache[mainModule.filename] = mainModule; 92 | mainModule.paths = Module._nodeModulePaths(path.dirname(filename)); 93 | return runModule(mainModule, jsSource, jsAst, filename); 94 | }; 95 | runModule = function (module, jsSource, jsAst, filename) { 96 | patchStackTrace(); 97 | Module._sourceMaps[filename] = function () { 98 | return '' + CoffeeScript.sourceMap(jsAst, filename); 99 | }; 100 | return module._compile(jsSource, filename); 101 | }; 102 | module.exports = { 103 | runMain: runMain, 104 | runModule: runModule 105 | }; 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffee-script-redux", 3 | "author": "Michael Ficarra", 4 | "version": "2.0.0-beta9-dev", 5 | "homepage": "https://github.com/michaelficarra/CoffeeScriptRedux", 6 | "bugs": "https://github.com/michaelficarra/CoffeeScriptRedux/issues", 7 | "description": "Unfancy JavaScript", 8 | "keywords": [ 9 | "coffeescript", 10 | "javascript", 11 | "language", 12 | "compiler" 13 | ], 14 | "main": "./lib/module", 15 | "bin": { 16 | "coffee": "./bin/coffee" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/michaelficarra/CoffeeScriptRedux.git" 21 | }, 22 | "scripts": { 23 | "build": "make -j build", 24 | "test": "make -j test" 25 | }, 26 | "devDependencies": { 27 | "mocha": "~1.12.0", 28 | "pegjs": "~0.8.0", 29 | "pegjs-each-code": "~0.2.0", 30 | "commonjs-everywhere": "~0.9.0", 31 | "cluster": "~0.7.7", 32 | "semver": "~2.1.0" 33 | }, 34 | "dependencies": { 35 | "StringScanner": "~0.0.3", 36 | "nopt": "~2.1.2" 37 | }, 38 | "optionalDependencies": { 39 | "esmangle": "~1.0.0", 40 | "source-map": "0.1.x", 41 | "escodegen": "~1.2.0", 42 | "cscodegen": "git+https://github.com/michaelficarra/cscodegen.git#73fd7202ac086c26f18c9d56f025b18b3c6f5383" 43 | }, 44 | "engines": { 45 | "node": "0.8.x || 0.10.x" 46 | }, 47 | "licenses": [ 48 | { 49 | "type": "3-clause BSD", 50 | "url": "https://raw.github.com/michaelficarra/CoffeeScriptRedux/master/LICENSE" 51 | } 52 | ], 53 | "license": "3-clause BSD" 54 | } 55 | -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | require('./lib/register'); 2 | -------------------------------------------------------------------------------- /src/browser.coffee: -------------------------------------------------------------------------------- 1 | module.exports = CoffeeScript = require './module' 2 | 3 | # Use standard JavaScript `eval` to eval code. 4 | CoffeeScript.eval = (code, options = {}) -> 5 | options.bare ?= on 6 | options.optimise ?= on 7 | eval CoffeeScript.cs2js code, options 8 | 9 | # Running code does not provide access to this scope. 10 | CoffeeScript.run = (code, options = {}) -> 11 | options.bare = on 12 | options.optimise ?= on 13 | do Function CoffeeScript.cs2js code, options 14 | 15 | # Load a remote script from the current domain via XHR. 16 | CoffeeScript.load = (url, callback) -> 17 | xhr = if window.ActiveXObject 18 | new window.ActiveXObject 'Microsoft.XMLHTTP' 19 | else 20 | new XMLHttpRequest 21 | xhr.open 'GET', url, true 22 | xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr 23 | xhr.onreadystatechange = -> 24 | return unless xhr.readyState is xhr.DONE 25 | if xhr.status in [0, 200] 26 | CoffeeScript.run xhr.responseText 27 | else 28 | throw new Error "Could not load #{url}" 29 | do 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 | do execute = -> 40 | return unless script = coffees[index++] 41 | if script.src 42 | CoffeeScript.load script.src, execute 43 | else 44 | CoffeeScript.run script.innerHTML 45 | do execute 46 | null 47 | 48 | # Listen for window load, both in browsers and in IE. 49 | if addEventListener? 50 | addEventListener 'DOMContentLoaded', runScripts, no 51 | else if attachEvent? 52 | attachEvent 'onload', runScripts 53 | -------------------------------------------------------------------------------- /src/cli.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | {concat, foldl} = require './functional-helpers' 4 | {numberLines, humanReadable} = require './helpers' 5 | {Preprocessor} = require './preprocessor' 6 | {Optimiser} = require './optimiser' 7 | {runMain} = require './run' 8 | CoffeeScript = require './module' 9 | Repl = require './repl' 10 | nopt = require 'nopt' 11 | cscodegen = try require 'cscodegen' 12 | escodegen = try require 'escodegen' 13 | esmangle = try require 'esmangle' 14 | 15 | inspect = (o) -> (require 'util').inspect o, no, 9e9, yes 16 | 17 | knownOpts = {} 18 | option = -> knownOpts[o] = Boolean for o in arguments; return 19 | parameter = -> knownOpts[p] = String for p in arguments; return 20 | 21 | optAliases = 22 | b: '--bare' 23 | c: '--compile' 24 | e: '--eval' 25 | f: '--cscodegen' 26 | I: '--require' 27 | i: '--input' 28 | j: '--js' 29 | l: '--literate' 30 | m: '--minify' 31 | o: '--output' 32 | p: '--parse' 33 | v: '--version' 34 | w: '--watch' 35 | 36 | option 'parse', 'compile', 'optimise', 'debug', 'literate', 'raw', 'version', 'help' 37 | parameter 'cli', 'input', 'nodejs', 'output', 'watch' 38 | 39 | if escodegen? 40 | option 'bare', 'js', 'source-map', 'eval', 'repl' 41 | parameter 'source-map-file', 'require' 42 | if esmangle? 43 | option 'minify' 44 | 45 | if cscodegen? 46 | option 'cscodegen' 47 | 48 | 49 | options = nopt knownOpts, optAliases, process.argv, 2 50 | positionalArgs = options.argv.remain 51 | delete options.argv 52 | 53 | # default values 54 | options.optimise ?= yes 55 | 56 | options.sourceMap = options['source-map'] 57 | options.sourceMapFile = options['source-map-file'] 58 | 59 | 60 | # input validation 61 | 62 | unless options.compile or options.js or options.sourceMap or options.parse or options.eval or options.cscodegen 63 | if not escodegen? 64 | options.compile = on 65 | else if positionalArgs.length 66 | options.eval = on 67 | options.input = positionalArgs.shift() 68 | additionalArgs = positionalArgs 69 | else 70 | options.repl = on 71 | 72 | # mutual exclusions 73 | # - p (parse), c (compile), j (js), source-map, e (eval), cscodegen, repl 74 | if 1 isnt (options.parse ? 0) + (options.compile ? 0) + (options.js ? 0) + (options.sourceMap ? 0) + (options.eval ? 0) + (options.cscodegen ? 0) + (options.repl ? 0) 75 | console.error "Error: At most one of --parse (-p), --compile (-c), --js (-j), --source-map, --eval (-e), --cscodegen, or --repl may be used." 76 | process.exit 1 77 | 78 | # - i (input), w (watch), cli 79 | if 1 < options.input? + options.watch? + options.cli? 80 | console.error 'Error: At most one of --input (-i), --watch (-w), or --cli may be used.' 81 | process.exit 1 82 | 83 | # dependencies 84 | # - I (require) depends on e (eval) 85 | if options.require? and not options.eval 86 | console.error 'Error: --require (-I) depends on --eval (-e)' 87 | process.exit 1 88 | 89 | # - m (minify) depends on escodegen and esmangle and (c (compile) or e (eval)) 90 | if options.minify and not (options.js or options.eval) 91 | console.error 'Error: --minify does not make sense without --js or --eval' 92 | process.exit 1 93 | 94 | # - b (bare) depends on escodegen and (c (compile) or e (eval) 95 | if options.bare and not (options.compile or options.js or options.sourceMap or options.eval) 96 | console.error 'Error: --bare does not make sense without --compile, --js, --source-map, or --eval' 97 | process.exit 1 98 | 99 | # - source-map-file depends on j (js) 100 | if options.sourceMapFile and not options.js 101 | console.error 'Error: --source-map-file depends on --js' 102 | process.exit 1 103 | 104 | # - i (input) depends on o (output) when input is a directory 105 | if options.input? and (fs.statSync options.input).isDirectory() and (not options.output? or (fs.statSync options.output)?.isFile()) 106 | console.error 'Error: when --input is a directory, --output must be provided, and --output must not reference a file' 107 | process.exit 1 108 | 109 | # - cscodegen depends on cscodegen 110 | if options.cscodegen and not cscodegen? 111 | console.error 'Error: cscodegen must be installed to use --cscodegen' 112 | process.exit 1 113 | 114 | 115 | output = (out) -> 116 | # --output 117 | if options.output 118 | fs.writeFile options.output, "#{out}\n", (err) -> 119 | throw err if err? 120 | else 121 | process.stdout.write "#{out}\n" 122 | 123 | 124 | # start processing options 125 | if options.help 126 | $0 = if path.basename(process.argv[0]) is 'node' then process.argv[1] else process.argv[0] 127 | $0 = path.basename $0 128 | 129 | console.log " 130 | Usage: (OPT is interpreted by #{$0}, ARG is passed to FILE) 131 | 132 | #{$0} OPT* -{p,c,j,f} OPT* 133 | example: #{$0} --js --no-optimise output.js 134 | #{$0} [-e] FILE {OPT,ARG}* [-- ARG*] 135 | example: #{$0} myfile.coffee arg0 arg1 136 | #{$0} OPT* [--repl] OPT* 137 | example: #{$0} 138 | 139 | -b, --bare omit the top-level function wrapper 140 | -c, --compile output a JSON-serialised AST representation of the output 141 | -e, --eval evaluate compiled JavaScript 142 | -f, --cscodegen output cscodegen-generated CoffeeScript code 143 | -i, --input FILE file to be used as input instead of STDIN 144 | -I, --require FILE require a library before a script is executed 145 | -j, --js generate JavaScript output 146 | -l, --literate treat the input as literate CoffeeScript code 147 | -m, --minify run compiled javascript output through a JS minifier 148 | -o, --output FILE file to be used as output instead of STDOUT 149 | -p, --parse output a JSON-serialised AST representation of the input 150 | -v, --version display the version number 151 | -w, --watch FILE watch the given file/directory for changes 152 | --cli INPUT pass a string from the command line as input 153 | --debug output intermediate representations on stderr for debug 154 | --help display this help message 155 | --nodejs OPTS pass options through to the node binary 156 | --optimise enable optimisations (default: on) 157 | --raw preserve source position and raw parse information 158 | --repl run an interactive CoffeeScript REPL 159 | --source-map generate source map 160 | --source-map-file FILE file used as output for source map when using --js 161 | 162 | Unless given --input or --cli flags, `#{$0}` will operate on stdin/stdout. 163 | When none of --{parse,compile,js,source-map,eval,cscodegen,repl} are given, 164 | If positional arguments were given 165 | * --eval is implied 166 | * the first positional argument is used as an input filename 167 | * additional positional arguments are passed as arguments to the script 168 | Else --repl is implied 169 | " 170 | 171 | else if options.version 172 | pkg = require './../package.json' 173 | console.log "CoffeeScript version #{pkg.version}" 174 | 175 | else if options.repl 176 | CoffeeScript.register() 177 | do process.argv.shift 178 | do Repl.start 179 | 180 | else 181 | # normal workflow 182 | 183 | input = '' 184 | inputName = options.input ? (options.cli and 'cli' or 'stdin') 185 | inputSource = 186 | if options.input? then fs.realpathSync options.input 187 | else options.cli and '(cli)' or '(stdin)' 188 | 189 | processInput = (err) -> 190 | 191 | throw err if err? 192 | result = null 193 | 194 | input = input.toString() 195 | # strip UTF BOM 196 | if 0xFEFF is input.charCodeAt 0 then input = input[1..] 197 | 198 | # preprocess 199 | if options.debug 200 | try 201 | console.error '### PREPROCESSED CS ###' 202 | preprocessed = Preprocessor.process input, literate: options.literate 203 | console.error numberLines humanReadable preprocessed 204 | 205 | # parse 206 | try 207 | result = CoffeeScript.parse input, 208 | optimise: no 209 | raw: options.raw or options.sourceMap or options.sourceMapFile or options.eval 210 | inputSource: inputSource 211 | literate: options.literate 212 | catch e 213 | console.error e.message 214 | process.exit 1 215 | if options.debug and options.optimise and result? 216 | console.error '### PARSED CS-AST ###' 217 | console.error inspect result.toBasicObject() 218 | 219 | # optimise 220 | if options.optimise and result? 221 | result = Optimiser.optimise result 222 | 223 | # --parse 224 | if options.parse 225 | if result? 226 | output inspect result.toBasicObject() 227 | return 228 | else 229 | process.exit 1 230 | 231 | if options.debug and result? 232 | console.error "### #{if options.optimise then 'OPTIMISED' else 'PARSED'} CS-AST ###" 233 | console.error inspect result.toBasicObject() 234 | 235 | # cs code gen 236 | if options.cscodegen 237 | try result = cscodegen.generate result 238 | catch e 239 | console.error (e.stack or e.message) 240 | process.exit 1 241 | if result? 242 | output result 243 | return 244 | else 245 | process.exit 1 246 | 247 | # compile 248 | jsAST = CoffeeScript.compile result, bare: options.bare 249 | 250 | # --compile 251 | if options.compile 252 | if jsAST? 253 | output inspect jsAST 254 | return 255 | else 256 | process.exit 1 257 | 258 | if options.debug and jsAST? 259 | console.error "### COMPILED JS-AST ###" 260 | console.error inspect jsAST 261 | 262 | # minification 263 | if options.minify 264 | try 265 | jsAST = esmangle.mangle (esmangle.optimize jsAST), destructive: yes 266 | catch e 267 | console.error (e.stack or e.message) 268 | process.exit 1 269 | 270 | if options.sourceMap 271 | # source map generation 272 | try sourceMap = CoffeeScript.sourceMap jsAST, inputName, compact: options.minify 273 | catch e 274 | console.error (e.stack or e.message) 275 | process.exit 1 276 | # --source-map 277 | if sourceMap? 278 | output "#{sourceMap}" 279 | return 280 | else 281 | process.exit 1 282 | 283 | # js code gen 284 | try 285 | {code: js, map: sourceMap} = CoffeeScript.jsWithSourceMap jsAST, inputName, compact: options.minify 286 | catch e 287 | console.error (e.stack or e.message) 288 | process.exit 1 289 | 290 | # --js 291 | if options.js 292 | if options.sourceMapFile 293 | fs.writeFileSync options.sourceMapFile, "#{sourceMap}" 294 | sourceMappingUrl = 295 | if options.output 296 | path.relative (path.dirname options.output), options.sourceMapFile 297 | else 298 | options.sourceMapFile 299 | js = """ 300 | #{js} 301 | 302 | //# sourceMappingURL=#{sourceMappingUrl} 303 | """ 304 | output js 305 | return 306 | 307 | # --eval 308 | if options.eval 309 | CoffeeScript.register() 310 | process.argv = [process.argv[1], options.input].concat additionalArgs 311 | runMain input, js, jsAST, inputSource 312 | return 313 | 314 | # choose input source 315 | 316 | if options.input? 317 | fs.stat options.input, (err, stats) -> 318 | throw err if err? 319 | if stats.isDirectory() 320 | options.input = path.join options.input, 'index.coffee' 321 | fs.readFile options.input, (err, contents) -> 322 | throw err if err? 323 | input = contents 324 | do processInput 325 | else if options.watch? 326 | options.watch # TODO: watch 327 | else if options.cli? 328 | input = options.cli 329 | do processInput 330 | else 331 | process.stdin.on 'data', (data) -> input += data 332 | process.stdin.on 'end', processInput 333 | process.stdin.setEncoding 'utf8' 334 | do process.stdin.resume 335 | -------------------------------------------------------------------------------- /src/functional-helpers.coffee: -------------------------------------------------------------------------------- 1 | @any = (list, fn) -> 2 | for e in list 3 | return yes if fn e 4 | no 5 | 6 | @all = (list, fn) -> 7 | for e in list 8 | return no unless fn e 9 | yes 10 | 11 | @foldl = foldl = (memo, list, fn) -> 12 | for i in list 13 | memo = fn memo, i 14 | memo 15 | 16 | @foldl1 = (list, fn) -> foldl list[0], list[1..], fn 17 | 18 | @map = map = (list, fn) -> fn e for e in list 19 | 20 | @concat = concat = (list) -> [].concat list... 21 | 22 | @concatMap = (list, fn) -> concat map list, fn 23 | 24 | @intersect = (listA, listB) -> a for a in listA when a in listB 25 | 26 | @difference = (listA, listB) -> a for a in listA when a not in listB 27 | 28 | @nub = nub = (list) -> 29 | result = [] 30 | result.push i for i in list when i not in result 31 | result 32 | 33 | @union = (listA, listB) -> 34 | listA.concat (b for b in (nub listB) when b not in listA) 35 | 36 | @flip = (fn) -> (b, a) -> fn.call this, a, b 37 | 38 | @owns = do (hop = {}.hasOwnProperty) -> (a, b) -> hop.call a, b 39 | 40 | @span = span = (list, f) -> 41 | if list.length is 0 then [[], []] 42 | else if f list[0] 43 | [ys, zs] = span list[1..], f 44 | [[list[0], ys...], zs] 45 | else [[], list] 46 | 47 | @divMod = (a, b) -> 48 | c = a % b 49 | mod = if c < 0 then c + b else c 50 | div = Math.floor a / b 51 | [div, mod] 52 | 53 | # The partition function takes a list and predicate fn and returns the pair of lists 54 | # of elements which do and do not satisfy the predicate, respectively. 55 | @partition = (list, fn) -> 56 | result = [[], []] 57 | result[+!fn item].push item for item in list 58 | result 59 | -------------------------------------------------------------------------------- /src/helpers.coffee: -------------------------------------------------------------------------------- 1 | {concat, concatMap, difference, foldl, map, nub} = require './functional-helpers' 2 | CS = require './nodes' 3 | 4 | 5 | COLOURS = 6 | red: '\x1B[31m' 7 | green: '\x1B[32m' 8 | yellow: '\x1B[33m' 9 | blue: '\x1B[34m' 10 | magenta: '\x1B[35m' 11 | cyan: '\x1B[36m' 12 | 13 | SUPPORTS_COLOUR = 14 | process?.stderr?.isTTY and not process.env.NODE_DISABLE_COLORS 15 | 16 | colourise = (colour, str) -> 17 | if SUPPORTS_COLOUR then "#{COLOURS[colour]}#{str}\x1B[39m" else str 18 | 19 | 20 | @numberLines = numberLines = (input, startLine = 1) -> 21 | lines = input.split '\n' 22 | padSize = "#{lines.length + startLine - 1}".length 23 | numbered = for line, i in lines 24 | currLine = "#{i + startLine}" 25 | pad = ((Array padSize + 1).join '0')[currLine.length..] 26 | "#{pad}#{currLine} : #{lines[i]}" 27 | numbered.join '\n' 28 | 29 | cleanMarkers = (str) -> str.replace /[\uEFEF\uEFFE\uEFFF]/g, '' 30 | 31 | @humanReadable = humanReadable = (str) -> 32 | ((str.replace /\uEFEF/g, '(INDENT)').replace /\uEFFE/g, '(DEDENT)').replace /\uEFFF/g, '(TERM)' 33 | 34 | @formatParserError = (input, e) -> 35 | realColumn = (cleanMarkers "#{(input.split '\n')[e.line - 1]}\n"[...e.column]).length 36 | unless e.found? 37 | return "Syntax error on line #{e.line}, column #{realColumn}: unexpected end of input" 38 | found = JSON.stringify humanReadable e.found 39 | found = ((found.replace /^"|"$/g, '').replace /'/g, '\\\'').replace /\\"/g, '"' 40 | unicode = ((e.found.charCodeAt 0).toString 16).toUpperCase() 41 | unicode = "\\u#{"0000"[unicode.length..]}#{unicode}" 42 | message = "Syntax error on line #{e.line}, column #{realColumn}: unexpected '#{found}' (#{unicode})" 43 | "#{message}\n#{pointToErrorLocation input, e.line, realColumn}" 44 | 45 | @pointToErrorLocation = pointToErrorLocation = (source, line, column, numLinesOfContext = 3) -> 46 | lines = source.split '\n' 47 | lines.pop() unless lines[lines.length - 1] 48 | # figure out which lines are needed for context 49 | currentLineOffset = line - 1 50 | startLine = currentLineOffset - numLinesOfContext 51 | if startLine < 0 then startLine = 0 52 | # get the context lines 53 | preLines = lines[startLine..currentLineOffset] 54 | preLines[preLines.length - 1] = colourise 'yellow', preLines[preLines.length - 1] 55 | postLines = lines[currentLineOffset + 1 .. currentLineOffset + numLinesOfContext] 56 | numberedLines = (numberLines (cleanMarkers [preLines..., postLines...].join '\n'), startLine + 1).split '\n' 57 | preLines = numberedLines[0...preLines.length] 58 | postLines = numberedLines[preLines.length...] 59 | # set the column number to the position of the error in the cleaned string 60 | column = (cleanMarkers "#{lines[currentLineOffset]}\n"[...column]).length 61 | padSize = ((currentLineOffset + 1 + postLines.length).toString 10).length 62 | [ 63 | preLines... 64 | "#{colourise 'red', (Array padSize + 1).join '^'} : #{(Array column).join ' '}#{colourise 'red', '^'}" 65 | postLines... 66 | ].join '\n' 67 | 68 | # these are the identifiers that need to be declared when the given value is 69 | # being used as the target of an assignment 70 | @beingDeclared = beingDeclared = (assignment) -> switch 71 | when not assignment? then [] 72 | when assignment.instanceof CS.Identifiers then [assignment.data] 73 | when assignment.instanceof CS.Rest then beingDeclared assignment.expression 74 | when assignment.instanceof CS.MemberAccessOps then [] 75 | when assignment.instanceof CS.DefaultParam then beingDeclared assignment.param 76 | when assignment.instanceof CS.ArrayInitialiser then concatMap assignment.members, beingDeclared 77 | when assignment.instanceof CS.ObjectInitialiser then concatMap assignment.vals(), beingDeclared 78 | else throw new Error "beingDeclared: Non-exhaustive patterns in case: #{assignment.className}" 79 | 80 | @declarationsFor = (node, inScope) -> 81 | vars = envEnrichments node, inScope 82 | foldl (new CS.Undefined).g(), vars, (expr, v) -> 83 | (new CS.AssignOp (new CS.Identifier v).g(), expr).g() 84 | 85 | # TODO: name change; this tests when a node is *being used as a value* 86 | usedAsExpression_ = (ancestors) -> 87 | parent = ancestors[0] 88 | grandparent = ancestors[1] 89 | switch 90 | when !parent? then yes 91 | when parent.instanceof CS.Program, CS.Class then no 92 | when parent.instanceof CS.SeqOp 93 | this is parent.right and 94 | usedAsExpression parent, ancestors[1..] 95 | when (parent.instanceof CS.Block) and 96 | (parent.statements.indexOf this) isnt parent.statements.length - 1 97 | no 98 | when (parent.instanceof CS.Functions) and 99 | parent.body is this and 100 | grandparent? and grandparent.instanceof CS.Constructor 101 | no 102 | else yes 103 | 104 | @usedAsExpression = usedAsExpression = (node, ancestors) -> 105 | usedAsExpression_.call node, ancestors 106 | 107 | # environment enrichments that occur when this node is evaluated 108 | # Note: these are enrichments of the *surrounding* environment; while function 109 | # parameters do enrich *an* environment, that environment is newly created 110 | envEnrichments_ = (inScope = []) -> 111 | possibilities = nub switch 112 | when @instanceof CS.AssignOp 113 | concat [ 114 | beingDeclared @assignee 115 | envEnrichments @expression 116 | ] 117 | when @instanceof CS.Class 118 | concat [ 119 | beingDeclared @nameAssignee 120 | envEnrichments @parent 121 | ] 122 | when @instanceof CS.ForIn, CS.ForOf 123 | concat [ 124 | beingDeclared @keyAssignee 125 | beingDeclared @valAssignee 126 | envEnrichments @target 127 | envEnrichments @step 128 | envEnrichments @filter 129 | envEnrichments @body 130 | ] 131 | when @instanceof CS.Try 132 | concat [ 133 | beingDeclared @catchAssignee 134 | envEnrichments @body 135 | envEnrichments @catchBody 136 | envEnrichments @finallyBody 137 | ] 138 | when @instanceof CS.Functions then [] 139 | else 140 | concatMap @childNodes, (child) => 141 | if child in @listMembers 142 | then concatMap this[child], (m) -> envEnrichments m, inScope 143 | else envEnrichments this[child], inScope 144 | difference possibilities, inScope 145 | 146 | @envEnrichments = envEnrichments = (node, args...) -> 147 | if node? then envEnrichments_.apply node, args else [] 148 | -------------------------------------------------------------------------------- /src/js-nodes.coffee: -------------------------------------------------------------------------------- 1 | {difference} = require './functional-helpers' 2 | exports = module?.exports ? this 3 | 4 | createNode = (type, props) -> 5 | class extends Nodes 6 | constructor: -> 7 | this[prop] = arguments[i] for prop, i in props 8 | type: type 9 | childNodes: props 10 | 11 | @Nodes = class Nodes 12 | listMembers: [] 13 | instanceof: (ctors...) -> 14 | # not a fold for efficiency's sake 15 | for ctor in ctors when @type is ctor::type 16 | return yes 17 | no 18 | toBasicObject: -> 19 | obj = {@type} 20 | obj.leadingComments = @leadingComments if @leadingComments? 21 | for child in @childNodes 22 | if child in @listMembers 23 | obj[child] = (p?.toBasicObject() for p in this[child]) 24 | else 25 | obj[child] = this[child]?.toBasicObject() 26 | if @line? and @column? 27 | obj.loc = start: {@line, @column} 28 | if @offset? 29 | obj.range = [ 30 | @offset 31 | if @raw? then @offset + @raw.length else undefined 32 | ] 33 | if @raw? then obj.raw = @raw 34 | obj 35 | 36 | nodeData = [ 37 | # constructor name, isStatement, construction parameters 38 | ['ArrayExpression' , no , ['elements']] 39 | ['AssignmentExpression' , no , ['operator', 'left', 'right']] 40 | ['BinaryExpression' , no , ['operator', 'left', 'right']] 41 | ['BlockStatement' , yes, ['body']] 42 | ['BreakStatement' , yes, ['label']] 43 | ['CallExpression' , no , ['callee', 'arguments']] 44 | ['CatchClause' , yes, ['param', 'body']] 45 | ['ConditionalExpression', no , ['test', 'consequent', 'alternate']] 46 | ['ContinueStatement' , yes, ['label']] 47 | ['DebuggerStatement' , yes, []] 48 | ['DoWhileStatement' , yes, ['body', 'test']] 49 | ['EmptyStatement' , yes, []] 50 | ['ExpressionStatement' , yes, ['expression']] 51 | ['ForInStatement' , yes, ['left', 'right', 'body']] 52 | ['ForStatement' , yes, ['init', 'test', 'update', 'body']] 53 | ['FunctionDeclaration' , yes, ['id', 'params', 'body']] 54 | ['FunctionExpression' , no , ['id', 'params', 'body']] 55 | ['GenSym' , no , ['ns', 'uniqueId']] 56 | ['Identifier' , no , ['name']] 57 | ['IfStatement' , yes, ['test', 'consequent', 'alternate']] 58 | ['LabeledStatement' , yes, ['label', 'body']] 59 | ['Literal' , no , ['value']] 60 | ['LogicalExpression' , no , ['operator', 'left', 'right']] 61 | ['MemberExpression' , no , ['computed', 'object', 'property']] 62 | ['NewExpression' , no , ['callee', 'arguments']] 63 | ['ObjectExpression' , no , ['properties']] 64 | ['Program' , yes, ['body']] 65 | ['Property' , yes, ['key', 'value']] 66 | ['ReturnStatement' , yes, ['argument']] 67 | ['SequenceExpression' , no , ['expressions']] 68 | ['SwitchCase' , yes, ['test', 'consequent']] 69 | ['SwitchStatement' , yes, ['discriminant', 'cases']] 70 | ['ThisExpression' , no , []] 71 | ['ThrowStatement' , yes, ['argument']] 72 | ['TryStatement' , yes, ['block', 'handlers', 'finalizer']] 73 | ['UnaryExpression' , no , ['operator', 'argument']] 74 | ['UpdateExpression' , no , ['operator', 'prefix', 'argument']] 75 | ['VariableDeclaration' , yes, ['kind', 'declarations']] 76 | ['VariableDeclarator' , yes, ['id', 'init']] 77 | ['WhileStatement' , yes, ['test', 'body']] 78 | ['WithStatement' , yes, ['object', 'body']] 79 | ] 80 | 81 | for [node, isStatement, params] in nodeData 82 | exports[node] = ctor = createNode node, params 83 | ctor::isStatement = isStatement 84 | ctor::isExpression = not isStatement 85 | 86 | 87 | { 88 | Program, BlockStatement, Literal, Identifier, FunctionExpression, 89 | CallExpression, SequenceExpression, ArrayExpression, BinaryExpression, 90 | UnaryExpression, NewExpression, VariableDeclaration, ObjectExpression, 91 | MemberExpression, UpdateExpression, AssignmentExpression, LogicalExpression, 92 | GenSym, FunctionDeclaration, VariableDeclaration, SwitchStatement, SwitchCase, 93 | TryStatement 94 | } = exports 95 | 96 | ## Nodes that contain primitive properties 97 | 98 | handlePrimitives = (ctor, primitives) -> 99 | ctor::childNodes = difference ctor::childNodes, primitives 100 | ctor::toBasicObject = -> 101 | obj = Nodes::toBasicObject.call this 102 | for primitive in primitives 103 | obj[primitive] = this[primitive] 104 | obj 105 | 106 | handlePrimitives AssignmentExpression, ['operator'] 107 | handlePrimitives BinaryExpression, ['operator'] 108 | handlePrimitives LogicalExpression, ['operator'] 109 | handlePrimitives GenSym, ['ns', 'uniqueId'] 110 | handlePrimitives Identifier, ['name'] 111 | handlePrimitives Literal, ['value'] 112 | handlePrimitives MemberExpression, ['computed'] 113 | handlePrimitives UnaryExpression, ['operator'] 114 | handlePrimitives UpdateExpression, ['operator', 'prefix'] 115 | handlePrimitives VariableDeclaration, ['kind'] 116 | 117 | ## Nodes that contain list properties 118 | 119 | handleLists = (ctor, listProps) -> ctor::listMembers = listProps 120 | 121 | handleLists ArrayExpression, ['elements'] 122 | handleLists BlockStatement, ['body'] 123 | handleLists CallExpression, ['arguments'] 124 | handleLists FunctionDeclaration, ['params'] 125 | handleLists FunctionExpression, ['params'] 126 | handleLists NewExpression, ['arguments'] 127 | handleLists ObjectExpression, ['properties'] 128 | handleLists Program, ['body'] 129 | handleLists SequenceExpression, ['expressions'] 130 | handleLists SwitchCase, ['consequent'] 131 | handleLists SwitchStatement, ['cases'] 132 | handleLists TryStatement, ['handlers'] 133 | handleLists VariableDeclaration, ['declarations'] 134 | 135 | # Functions need to be marked as generated when used as IIFE for converting 136 | # statements to expressions so they may be ignored when doing auto-declaration 137 | 138 | FunctionDeclaration::generated = FunctionExpression::generated = false 139 | FunctionDeclaration::g = FunctionExpression::g = -> 140 | @generated = yes 141 | this 142 | -------------------------------------------------------------------------------- /src/module.coffee: -------------------------------------------------------------------------------- 1 | {formatParserError} = require './helpers' 2 | Nodes = require './nodes' 3 | {Preprocessor} = require './preprocessor' 4 | Parser = require './parser' 5 | {Optimiser} = require './optimiser' 6 | {Compiler} = require './compiler' 7 | cscodegen = try require 'cscodegen' 8 | escodegen = try require 'escodegen' 9 | 10 | 11 | pkg = require './../package.json' 12 | 13 | escodegenFormat = 14 | indent: 15 | style: ' ' 16 | base: 0 17 | renumber: yes 18 | hexadecimal: yes 19 | quotes: 'auto' 20 | parentheses: no 21 | 22 | 23 | CoffeeScript = 24 | 25 | CoffeeScript: CoffeeScript 26 | Compiler: Compiler 27 | Optimiser: Optimiser 28 | Parser: Parser 29 | Preprocessor: Preprocessor 30 | Nodes: Nodes 31 | 32 | VERSION: pkg.version 33 | 34 | parse: (coffee, options = {}) -> 35 | try 36 | preprocessed = Preprocessor.process coffee, literate: options.literate 37 | parsed = Parser.parse preprocessed, 38 | raw: options.raw 39 | inputSource: options.inputSource 40 | if options.optimise then Optimiser.optimise parsed else parsed 41 | catch e 42 | throw e unless e instanceof Parser.SyntaxError 43 | throw new Error formatParserError preprocessed, e 44 | 45 | compile: (csAst, options) -> 46 | (Compiler.compile csAst, options).toBasicObject() 47 | 48 | # TODO 49 | cs: (csAst, options) -> 50 | # TODO: opt: format (default: nice defaults) 51 | 52 | jsWithSourceMap: (jsAst, name = 'unknown', options = {}) -> 53 | # TODO: opt: minify (default: no) 54 | throw new Error 'escodegen not found: run `npm install escodegen`' unless escodegen? 55 | unless {}.hasOwnProperty.call jsAst, 'type' 56 | jsAst = jsAst.toBasicObject() 57 | targetName = options.sourceMapFile or (options.sourceMap and (options.output.match /^.*[\\\/]([^\\\/]+)$/)[1]) 58 | escodegen.generate jsAst, 59 | comment: not options.compact 60 | sourceMapWithCode: yes 61 | sourceMap: name 62 | file: targetName or 'unknown' 63 | format: if options.compact then escodegen.FORMAT_MINIFY else options.format ? escodegenFormat 64 | 65 | js: (jsAst, options) -> (@jsWithSourceMap jsAst, null, options).code 66 | sourceMap: (jsAst, name, options) -> (@jsWithSourceMap jsAst, name, options).map 67 | 68 | # Equivalent to original CS compile 69 | cs2js: (input, options = {}) -> 70 | options.optimise ?= on 71 | csAST = CoffeeScript.parse input, options 72 | jsAST = CoffeeScript.compile csAST, bare: options.bare 73 | CoffeeScript.js jsAST, compact: options.compact or options.minify 74 | 75 | 76 | module.exports = CoffeeScript 77 | 78 | if require.extensions?['.node']? 79 | CoffeeScript.register = -> require './register' 80 | # Throw error with deprecation warning when depending upon implicit `require.extensions` registration 81 | for ext in ['.coffee', '.litcoffee'] 82 | require.extensions[ext] ?= -> 83 | throw new Error """ 84 | Use CoffeeScript.register() or require the coffee-script-redux/register module to require #{ext} files. 85 | """ 86 | -------------------------------------------------------------------------------- /src/parser.coffee: -------------------------------------------------------------------------------- 1 | # This file is a placeholder for browser bundling purposes. 2 | # See /src/grammar.pegcoffee for the file that creates /lib/parser.js 3 | -------------------------------------------------------------------------------- /src/pegjs-coffee-plugin.coffee: -------------------------------------------------------------------------------- 1 | CoffeeScript = require './module' 2 | eachCode = require 'pegjs-each-code' 3 | 4 | compile = (csCode, options = {}) -> 5 | csAST = CoffeeScript.parse "-> #{csCode.trimRight()}" 6 | if csAST.body.statements.length > 1 7 | throw new Error "inconsistent base indentation" 8 | jsAST = CoffeeScript.compile csAST, bare: yes, inScope: options.inScope 9 | jsAST.leadingComments = [] 10 | jsAST.body = jsAST.body[0].expression.body.body.concat jsAST.body[1..] 11 | CoffeeScript.js jsAST 12 | 13 | exports.use = (config) -> 14 | config.passes.transform.unshift (ast) -> 15 | ast.initializer.code = CoffeeScript.cs2js ast.initializer.code, bare: yes 16 | eachCode ast, (node, labels, ruleName) -> 17 | try 18 | node.code = compile node.code, inScope: labels 19 | catch error 20 | throw new Error """ 21 | In the '#{ruleName}' rule: 22 | #{error.message} 23 | """ 24 | -------------------------------------------------------------------------------- /src/preprocessor.coffee: -------------------------------------------------------------------------------- 1 | {pointToErrorLocation} = require './helpers' 2 | StringScanner = require 'StringScanner' 3 | 4 | 5 | # TODO: better comments 6 | # TODO: support win32-style line endings 7 | # TODO: now that the preprocessor doesn't support streaming input, optimise the `process` method 8 | 9 | @Preprocessor = class Preprocessor 10 | 11 | ws = '\\t\\x0B\\f\\r \\xA0\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000\\uFEFF' 12 | INDENT = '\uEFEF' 13 | DEDENT = '\uEFFE' 14 | TERM = '\uEFFF' 15 | 16 | constructor: (@options = {}) -> 17 | @preprocessed = '' 18 | # `base` is either `null` or a regexp that matches the base indentation 19 | @base = null 20 | # `indents` is an array of successive indentation characters. 21 | @indents = [] 22 | @context = [] 23 | 24 | @process: (input, options = {}) -> (new Preprocessor options).process input 25 | 26 | err: (c) -> 27 | token = 28 | switch c 29 | when INDENT 30 | 'INDENT' 31 | when DEDENT 32 | 'DEDENT' 33 | when TERM 34 | 'TERM' 35 | else 36 | "\"#{c.replace /"/g, '\\"'}\"" 37 | # This isn't perfect for error location tracking, but since we normally call this after a scan, it tends to work well. 38 | lines = @ss.str.substr(0, @ss.pos).split(/\n/) || [''] 39 | columns = if lines[lines.length-1]? then lines[lines.length-1].length else 0 40 | context = pointToErrorLocation @ss.str, lines.length, columns 41 | throw new Error "Unexpected #{token}\n#{context}" 42 | 43 | peek: -> if @context.length then @context[@context.length - 1] else null 44 | 45 | observe: (c) -> 46 | top = @peek() 47 | switch c 48 | # opening token is closing token 49 | when '"""', '\'\'\'', '"', '\'', '###', '`', '///', '/' 50 | if top is c then @context.pop() 51 | else @context.push c 52 | # strictly opening tokens 53 | when INDENT, '#', '#{', '[', '(', '{', '\\', 'regexp-[', 'regexp-(', 'regexp-{', 'heregexp-#', 'heregexp-[', 'heregexp-(', 'heregexp-{' 54 | @context.push c 55 | # strictly closing tokens 56 | when DEDENT 57 | (@err c) unless top is INDENT 58 | @indents.pop() 59 | @context.pop() 60 | when '\n' 61 | (@err c) unless top in ['#', 'heregexp-#'] 62 | @context.pop() 63 | when ']' 64 | (@err c) unless top in ['[', 'regexp-[', 'heregexp-['] 65 | @context.pop() 66 | when ')' 67 | (@err c) unless top in ['(', 'regexp-(', 'heregexp-('] 68 | @context.pop() 69 | when '}' 70 | (@err c) unless top in ['#{', '{', 'regexp-{', 'heregexp-{'] 71 | @context.pop() 72 | when 'end-\\' 73 | (@err c) unless top is '\\' 74 | @context.pop() 75 | else throw new Error "undefined token observed: " + c 76 | @context 77 | 78 | p: (s) -> 79 | if s? then @preprocessed = "#{@preprocessed}#{s}" 80 | s 81 | 82 | scan: (r) -> @p @ss.scan r 83 | 84 | consumeIndentation: -> 85 | if @ss.bol() or @scan /// (?:[#{ws}]* \n)+ /// 86 | @scan /// (?: [#{ws}]* (\#\#?(?!\#)[^\n]*)? \n )+ /// 87 | 88 | # consume base indentation 89 | if @base? 90 | if not (@ss.eos() or (@scan @base)?) 91 | throw new Error "inconsistent base indentation" 92 | else 93 | @base = /// #{@scan /// [#{ws}]* ///} /// 94 | 95 | # move through each level of indentation 96 | indentIndex = 0 97 | while indentIndex < @indents.length 98 | indent = @indents[indentIndex] 99 | if @ss.check /// #{indent} /// 100 | # an existing indent 101 | @scan /// #{indent} /// 102 | else if @ss.eos() or @ss.check /// [^#{ws}] /// 103 | # we lost an indent 104 | --indentIndex 105 | @p "#{DEDENT}#{TERM}" 106 | @observe DEDENT 107 | else 108 | # Some ambiguous dedent 109 | lines = @ss.str.substr(0, @ss.pos).split(/\n/) || [''] 110 | message = "Syntax error on line #{lines.length}: indentation is ambiguous" 111 | lineLen = @indents.reduce ((l, r) -> l + r.length), 0 112 | context = pointToErrorLocation @ss.str, lines.length, lineLen 113 | throw new Error "#{message}\n#{context}" 114 | ++indentIndex 115 | if @ss.check /// [#{ws}]+ [^#{ws}#] /// 116 | # an indent 117 | @indents.push @scan /// [#{ws}]+ /// 118 | @p INDENT 119 | @observe INDENT 120 | 121 | introduceContext: -> 122 | if tok = @scan /"""|'''|\/\/\/|###|["'`#[({\\]/ 123 | @observe tok 124 | else if tok = @scan /\// 125 | # unfortunately, we must look behind us to determine if this is a regexp or division 126 | pos = @ss.position() 127 | if pos > 1 128 | lastChar = @ss.string()[pos - 2] 129 | spaceBefore = ///[#{ws}]///.test lastChar 130 | impliedRegexp = /[;,=><*%^&|[(+!~-]/.test lastChar 131 | if pos is 1 or impliedRegexp or spaceBefore and not (@ss.check ///[#{ws}=]///) and @ss.check /[^\r\n]*\// 132 | @observe '/' 133 | 134 | process: (input) -> 135 | if @options.literate 136 | input = input.replace /^( {0,3}\S)/gm, ' #$1' 137 | @ss = new StringScanner input 138 | 139 | until @ss.eos() 140 | switch @peek() 141 | 142 | when null, INDENT 143 | @consumeIndentation() 144 | @scan /[^\n'"\\\/#`[(){}\]]+/ 145 | if @ss.check /[})\]]/ 146 | while @peek() is INDENT 147 | @p "#{DEDENT}#{TERM}" 148 | @observe DEDENT 149 | @observe @scan /[})\]]/ 150 | else 151 | @introduceContext() 152 | 153 | when '#{', '{' 154 | @scan /[^\n'"\\\/#`[({}]+/ 155 | if tok = @scan /\}/ 156 | @observe tok 157 | else 158 | @consumeIndentation() 159 | @introduceContext() 160 | when '[' 161 | @scan /[^\n'"\\\/#`[({\]]+/ 162 | if tok = @scan /\]/ 163 | @observe tok 164 | else 165 | @consumeIndentation() 166 | @introduceContext() 167 | when '(' 168 | @scan /[^\n'"\\\/#`[({)]+/ 169 | if tok = @scan /\)/ 170 | @observe tok 171 | else 172 | @consumeIndentation() 173 | @introduceContext() 174 | 175 | when '\\' 176 | if (@scan /[\s\S]/) then @observe 'end-\\' 177 | # TODO: somehow prevent indent tokens from being inserted after these newlines 178 | when '"""' 179 | @scan /(?:[^"#\\]+|""?(?!")|#(?!{)|\\.)+/ 180 | @ss.scan /\\\n/ 181 | if tok = @scan /#{|"""/ then @observe tok 182 | else if tok = @scan /#{|"""/ then @observe tok 183 | when '"' 184 | @scan /(?:[^"#\\]+|#(?!{)|\\.)+/ 185 | @ss.scan /\\\n/ 186 | if tok = @scan /#{|"/ then @observe tok 187 | when '\'\'\'' 188 | @scan /(?:[^'\\]+|''?(?!')|\\.)+/ 189 | @ss.scan /\\\n/ 190 | if tok = @scan /'''/ then @observe tok 191 | when '\'' 192 | @scan /(?:[^'\\]+|\\.)+/ 193 | @ss.scan /\\\n/ 194 | if tok = @scan /'/ then @observe tok 195 | when '###' 196 | @scan /(?:[^#]+|##?(?!#))+/ 197 | if tok = @scan /###/ then @observe tok 198 | when '#' 199 | @scan /[^\n]+/ 200 | if tok = @scan /\n/ then @observe tok 201 | when '`' 202 | @scan /[^`]+/ 203 | if tok = @scan /`/ then @observe tok 204 | when '///' 205 | @scan /(?:[^[/#\\]+|\/\/?(?!\/)|\\.)+/ 206 | if tok = @scan /#{|\/\/\/|\\/ then @observe tok 207 | else if @ss.scan /#/ then @observe 'heregexp-#' 208 | else if tok = @scan /[\[]/ then @observe "heregexp-#{tok}" 209 | when 'heregexp-[' 210 | @scan /(?:[^\]\/\\]+|\/\/?(?!\/))+/ 211 | if tok = @scan /[\]\\]|#{|\/\/\// then @observe tok 212 | when 'heregexp-#' 213 | @ss.scan /(?:[^\n/]+|\/\/?(?!\/))+/ 214 | if tok = @scan /\n|\/\/\// then @observe tok 215 | #when 'heregexp-(' 216 | # @scan /(?:[^)/[({#\\]+|\/\/?(?!\/))+/ 217 | # if tok = @ss.scan /#(?!{)/ then @observe 'heregexp-#' 218 | # else if tok = @scan /[)\\]|#{|\/\/\// then @observe tok 219 | # else if tok = @scan /[[({]/ then @observe "heregexp-#{tok}" 220 | #when 'heregexp-{' 221 | # @scan /(?:[^}/[({#\\]+|\/\/?(?!\/))+/ 222 | # if tok = @ss.scan /#(?!{)/ then @observe 'heregexp-#' 223 | # else if tok = @scan /[}/\\]|#{|\/\/\// then @observe tok 224 | # else if tok = @scan /[[({]/ then @observe "heregexp-#{tok}" 225 | when '/' 226 | @scan /[^[/\\]+/ 227 | if tok = @scan /[\/\\]/ then @observe tok 228 | else if tok = @scan /\[/ then @observe "regexp-#{tok}" 229 | when 'regexp-[' 230 | @scan /[^\]\\]+/ 231 | if tok = @scan /[\]\\]/ then @observe tok 232 | #when 'regexp-(' 233 | # @scan /[^)/[({\\]+/ 234 | # if tok = @scan /[)/\\]/ then @observe tok 235 | # else if tok = @scan /[[({]/ then @observe "regexp-#{tok}" 236 | #when 'regexp-{' 237 | # @scan /[^}/[({\\]+/ 238 | # if tok = @scan /[}/\\]/ then @observe tok 239 | # else if tok = @scan /[[({]/ then @observe "regexp-#{tok}" 240 | 241 | # reached the end of the file 242 | @scan /// [#{ws}\n]* $ /// 243 | while @context.length 244 | switch @peek() 245 | when INDENT 246 | @p "#{DEDENT}#{TERM}" 247 | @observe DEDENT 248 | when '#' 249 | @p '\n' 250 | @observe '\n' 251 | else 252 | # TODO: store offsets of tokens when inserted and report position of unclosed starting token 253 | throw new Error "Unclosed \"#{@peek().replace /"/g, '\\"'}\" at EOF" 254 | 255 | @preprocessed 256 | -------------------------------------------------------------------------------- /src/register.coffee: -------------------------------------------------------------------------------- 1 | child_process = require 'child_process' 2 | fs = require 'fs' 3 | path = require 'path' 4 | 5 | CoffeeScript = require './module' 6 | {runModule} = require './run' 7 | 8 | module.exports = not require.extensions['.coffee']? 9 | 10 | require.extensions['.coffee'] = (module, filename) -> 11 | input = fs.readFileSync filename, 'utf8' 12 | csAst = CoffeeScript.parse input, raw: yes 13 | jsAst = CoffeeScript.compile csAst 14 | js = CoffeeScript.js jsAst 15 | runModule module, js, jsAst, filename 16 | 17 | require.extensions['.litcoffee'] = (module, filename) -> 18 | input = fs.readFileSync filename, 'utf8' 19 | csAst = CoffeeScript.parse input, raw: yes, literate: yes 20 | jsAst = CoffeeScript.compile csAst 21 | js = CoffeeScript.js jsAst 22 | runModule module, js, jsAst, filename 23 | 24 | # patch child_process.fork to default to the coffee binary as the execPath for coffee/litcoffee files 25 | {fork} = child_process 26 | unless fork.coffeePatched 27 | coffeeBinary = path.resolve 'bin', 'coffee' 28 | child_process.fork = (file, args = [], options = {}) -> 29 | if (path.extname file) in ['.coffee', '.litcoffee'] 30 | if not Array.isArray args 31 | args = [] 32 | options = args or {} 33 | options.execPath or= coffeeBinary 34 | fork file, args, options 35 | child_process.fork.coffeePatched = yes 36 | 37 | delete require.cache[__filename] 38 | -------------------------------------------------------------------------------- /src/repl.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | vm = require 'vm' 4 | nodeREPL = require 'repl' 5 | CoffeeScript = require './module' 6 | CS = require './nodes' 7 | {merge} = require './helpers' 8 | 9 | addMultilineHandler = (repl) -> 10 | {rli, inputStream, outputStream} = repl 11 | # Node 0.11.12 changed API, prompt is now _prompt. 12 | origPrompt = repl._prompt ? repl.prompt 13 | initialPrompt = origPrompt.replace /^[^> ]*/, (x) -> x.replace /./g, '-' 14 | continuationPrompt = origPrompt.replace /^[^> ]*>?/, (x) -> x.replace /./g, '.' 15 | 16 | enabled = no 17 | buffer = '' 18 | 19 | # Proxy node's line listener 20 | nodeLineListener = (rli.listeners 'line')[0] 21 | rli.removeListener 'line', nodeLineListener 22 | rli.on 'line', (cmd) -> 23 | if enabled 24 | buffer += "#{cmd}\n" 25 | rli.setPrompt continuationPrompt 26 | rli.prompt true 27 | else 28 | rli.setPrompt origPrompt 29 | nodeLineListener cmd 30 | return 31 | 32 | # Handle Ctrl-v 33 | inputStream.on 'keypress', (char, key) -> 34 | return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v' 35 | if enabled 36 | # allow arbitrarily switching between modes any time before multiple lines are entered 37 | unless buffer.match /\n/ 38 | enabled = not enabled 39 | rli.setPrompt origPrompt 40 | rli.prompt true 41 | return 42 | # no-op unless the current line is empty 43 | return if rli.line? and not rli.line.match /^\s*$/ 44 | # eval, print, loop 45 | enabled = not enabled 46 | rli.line = '' 47 | rli.cursor = 0 48 | rli.output.cursorTo 0 49 | rli.output.clearLine 1 50 | # XXX: multiline hack 51 | buffer = buffer.replace /\n/g, '\uFF00' 52 | rli.emit 'line', buffer 53 | buffer = '' 54 | else 55 | enabled = not enabled 56 | rli.setPrompt initialPrompt 57 | rli.prompt true 58 | return 59 | 60 | # store and load command history from a file 61 | addHistory = (repl, filename, maxSize) -> 62 | try 63 | stat = fs.statSync filename 64 | size = Math.min maxSize, stat.size 65 | readFd = fs.openSync filename, 'r' 66 | buffer = new Buffer size 67 | # read last `size` bytes from the file 68 | fs.readSync readFd, buffer, 0, size, stat.size - size if size 69 | repl.rli.history = (buffer.toString().split '\n').reverse() 70 | # if the history file was truncated we should pop off a potential partial line 71 | do repl.rli.history.pop if stat.size > maxSize 72 | # shift off the final blank newline 73 | do repl.rli.history.shift if repl.rli.history[0] is '' 74 | repl.rli.historyIndex = -1 75 | catch e 76 | repl.rli.history = [] 77 | 78 | fd = fs.openSync filename, 'a' 79 | 80 | # like readline's history, we do not want any adjacent duplicates 81 | lastLine = repl.rli.history[0] 82 | 83 | # save new commands to the history file 84 | repl.rli.addListener 'line', (code) -> 85 | if code and code isnt lastLine 86 | lastLine = code 87 | fs.writeSync fd, "#{code}\n" 88 | 89 | repl.rli.on 'exit', -> fs.closeSync fd 90 | 91 | # .clear should also clear history 92 | original_clear = repl.commands[getCommandId(repl, 'clear')].action 93 | repl.commands[getCommandId(repl, 'clear')].action = -> 94 | repl.outputStream.write 'Clearing history...\n' 95 | repl.rli.history = [] 96 | fs.closeSync fd 97 | fd = fs.openSync filename, 'w' 98 | lastLine = undefined 99 | original_clear.call this 100 | 101 | # add a command to show the history stack 102 | repl.commands[getCommandId(repl, 'history')] = 103 | help: 'Show command history' 104 | action: -> 105 | repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n" 106 | do repl.displayPrompt 107 | 108 | 109 | getCommandId = (repl, commandName) -> 110 | # Node 0.11 changed API, a command such as '.help' is now stored as 'help' 111 | if repl.commands['.help']? then ".#{commandName}" else commandName 112 | 113 | module.exports = 114 | start: (opts = {}) -> 115 | # REPL defaults 116 | opts.prompt or= 'coffee> ' 117 | opts.ignoreUndefined ?= yes 118 | opts.historyFile ?= path.join process.env.HOME, '.coffee_history' 119 | opts.historyMaxInputSize ?= 10 * 1024 # 10KiB 120 | opts.eval or= (input, context, filename, cb) -> 121 | # XXX: multiline hack 122 | input = input.replace /\uFF00/g, '\n' 123 | # strip parens added by node 124 | input = input.replace /^\(([\s\S]*)\n\)$/m, '$1' 125 | # strip single-line comments 126 | input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3' 127 | # empty command 128 | return cb null if /^\s*$/.test input 129 | try 130 | inputAst = CoffeeScript.parse input, {filename, raw: yes} 131 | transformedAst = new CS.AssignOp (new CS.Identifier '_'), inputAst.body 132 | jsAst = CoffeeScript.compile transformedAst, bare: yes, inScope: Object.keys context 133 | js = CoffeeScript.js jsAst 134 | cb null, vm.runInContext js, context, filename 135 | catch err 136 | cb "\x1B[0;31m#{err.constructor.name}: #{err.message}\x1B[0m" 137 | 138 | repl = nodeREPL.start opts 139 | repl.on 'exit', -> repl.outputStream.write '\n' 140 | addMultilineHandler repl 141 | if opts.historyFile 142 | addHistory repl, opts.historyFile, opts.historyMaxInputSize 143 | # Adapt help inherited from the node REPL 144 | repl.commands[getCommandId(repl, 'load')].help = 'Load code from a file into this REPL session' 145 | repl 146 | -------------------------------------------------------------------------------- /src/run.coffee: -------------------------------------------------------------------------------- 1 | # Node.js-specific support: module loading, sourceMapping errors 2 | 3 | path = require 'path' 4 | Module = require 'module' 5 | CoffeeScript = require './module' 6 | {SourceMapConsumer} = require 'source-map' 7 | 8 | # NodeJS / V8 have no support for transforming positions in stack traces using 9 | # sourceMap, so we must monkey-patch Error to display CoffeeScript source 10 | # positions. 11 | 12 | # Ideally, this would happen in a way that is scalable to multiple compile-to- 13 | # JS languages trying to do the same thing in the same NodeJS process. We can 14 | # implement it as if there were an API, and then patch in support for that 15 | # API. The following maybe should be in its own npm module that multiple 16 | # compilers can include. 17 | 18 | patched = false 19 | patchStackTrace = -> 20 | return if patched 21 | patched = true 22 | 23 | # Map of filenames -> functions that return a sourceMap string. 24 | Module._sourceMaps ?= {} 25 | 26 | # (Assigning to a property of the Module object in the normal module cache is 27 | # unsuitable, because node deletes those objects from the cache if an 28 | # exception is thrown in the module body.) 29 | 30 | Error.prepareStackTrace = (err, stack) -> 31 | sourceFiles = {} 32 | 33 | getSourceMapping = (filename, line, column) -> 34 | mapString = Module._sourceMaps[filename]?() 35 | if mapString 36 | sourceMap = sourceFiles[filename] ?= new SourceMapConsumer mapString 37 | sourceMap.originalPositionFor {line, column: column - 1} 38 | 39 | frames = for frame in stack 40 | break if frame.getFunction() is exports.runMain 41 | " at #{formatSourcePosition frame, getSourceMapping}" 42 | 43 | "#{err.toString()}\n#{frames.join '\n'}\n" 44 | 45 | # Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js 46 | # Modified to handle sourceMap 47 | formatSourcePosition = (frame, getSourceMapping) -> 48 | fileName = undefined 49 | fileLocation = '' 50 | 51 | if frame.isNative() 52 | fileLocation = "native" 53 | else 54 | if frame.isEval() 55 | fileName = frame.getScriptNameOrSourceURL() 56 | fileLocation = "#{frame.getEvalOrigin()}, " unless fileName 57 | else 58 | fileName = frame.getFileName() 59 | 60 | fileName or= "" 61 | 62 | line = frame.getLineNumber() 63 | column = frame.getColumnNumber() 64 | 65 | # Check for a sourceMap position 66 | source = getSourceMapping fileName, line, column 67 | fileLocation = 68 | if source 69 | if source.line? 70 | "#{fileName}:#{source.line}:#{source.column + 1}, :#{line}:#{column}" 71 | else 72 | "#{fileName} :#{line}:#{column}" 73 | else 74 | "#{fileName}:#{line}:#{column}" 75 | 76 | functionName = frame.getFunctionName() 77 | isConstructor = frame.isConstructor() 78 | isMethodCall = not (frame.isToplevel() or isConstructor) 79 | 80 | if isMethodCall 81 | methodName = frame.getMethodName() 82 | typeName = frame.getTypeName() 83 | 84 | if functionName 85 | tp = as = '' 86 | if typeName and functionName.indexOf typeName 87 | tp = "#{typeName}." 88 | if methodName and functionName.indexOf(".#{methodName}") isnt functionName.length - methodName.length - 1 89 | as = " [as #{methodName}]" 90 | 91 | "#{tp}#{functionName}#{as} (#{fileLocation})" 92 | else 93 | "#{typeName}.#{methodName or ''} (#{fileLocation})" 94 | else if isConstructor 95 | "new #{functionName or ''} (#{fileLocation})" 96 | else if functionName 97 | "#{functionName} (#{fileLocation})" 98 | else 99 | fileLocation 100 | 101 | # Run JavaScript as a main program - resetting process.argv and module lookup paths 102 | runMain = (csSource, jsSource, jsAst, filename) -> 103 | mainModule = new Module '.' 104 | mainModule.filename = process.argv[1] = filename 105 | # Set it as the main module -- this is used for require.main 106 | process.mainModule = mainModule 107 | # Add the module to the cache 108 | Module._cache[mainModule.filename] = mainModule 109 | # Assign paths for node_modules loading 110 | mainModule.paths = Module._nodeModulePaths path.dirname filename 111 | runModule mainModule, jsSource, jsAst, filename 112 | 113 | runModule = (module, jsSource, jsAst, filename) -> 114 | do patchStackTrace 115 | Module._sourceMaps[filename] = -> "#{CoffeeScript.sourceMap jsAst, filename}" 116 | module._compile jsSource, filename 117 | 118 | module.exports = {runMain, runModule} 119 | -------------------------------------------------------------------------------- /test/_setup.coffee: -------------------------------------------------------------------------------- 1 | global.fs = require 'fs' 2 | global.path = require 'path' 3 | util = require 'util' 4 | inspect = (o) -> util.inspect o, no, 2, yes 5 | 6 | global[name] = func for name, func of require 'assert' 7 | 8 | # See http://wiki.ecmascript.org/doku.php?id=harmony:egal 9 | egal = (a, b) -> 10 | if a is b 11 | a isnt 0 or 1/a is 1/b 12 | else 13 | a isnt a and b isnt b 14 | 15 | # A recursive functional equivalence helper; uses egal for testing equivalence. 16 | arrayEgal = (a, b) -> 17 | if egal a, b then yes 18 | else if a instanceof Array and b instanceof Array 19 | return no unless a.length is b.length 20 | return no for el, idx in a when not arrayEgal el, b[idx] 21 | yes 22 | 23 | global.eq = (a, b, msg) -> ok (egal a, b), msg ? "#{inspect a} === #{inspect b}" 24 | global.neq = (a, b, msg) -> ok (not egal a, b), msg ? "#{inspect a} !== #{inspect b}" 25 | global.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg ? "#{inspect a} === #{inspect b}" 26 | 27 | 28 | global.CoffeeScript = require '..' 29 | global.CS = require "../lib/nodes" 30 | global.JS = require "../lib/js-nodes" 31 | global.Repl = require "../lib/repl" 32 | global.Parser = require "../lib/parser" 33 | {Optimiser: global.Optimiser} = require "../lib/optimiser" 34 | {Preprocessor} = require "../lib/preprocessor" 35 | 36 | global.parse = (input, options = {}) -> 37 | preprocessed = Preprocessor.process input, options 38 | Parser.parse preprocessed, options 39 | optimiser = new Optimiser 40 | global.optimise = (ast) -> optimiser.optimise ast 41 | -------------------------------------------------------------------------------- /test/arrays.coffee: -------------------------------------------------------------------------------- 1 | suite 'Arrays', -> 2 | 3 | suite 'Basic Literals', -> 4 | 5 | test 'simple arrays', -> 6 | eq 0, [].length 7 | eq 0, [ ].length 8 | eq 1, [0].length 9 | eq 1, [ 0 ].length 10 | eq 2, [0,0].length 11 | eq 2, [0, 0].length 12 | eq 2, [0 ,0].length 13 | eq 2, [ 0 , 0 ].length 14 | eq 3, [0,0,0].length 15 | eq 3, [0, 0, 0].length 16 | eq 3, [ 0 , 0 , 0 ].length 17 | eq k, v for v, k in [0, 1, 2, 3] 18 | eq k, v for v, k in [0, 1, 2, 3,] 19 | return 20 | 21 | test 'arrays spread over many lines', -> 22 | eq 0, [ 23 | ].length 24 | eq 1, [ 25 | 0 26 | ].length 27 | eq 1, [ 28 | 0, 29 | ].length 30 | eq 2, [ 31 | 0 32 | 0 33 | ].length 34 | eq 2, [ 35 | 0, 36 | 0 37 | ].length 38 | eq 2, [ 39 | 0, 40 | 0, 41 | ].length 42 | eq k, v for v, k in [ 43 | 0 44 | 1 45 | 2 46 | 3 47 | ] 48 | return 49 | 50 | test 'nested arrays', -> 51 | eq 1, [[]].length 52 | eq 0, [[]][0].length 53 | eq 1, [[0]].length 54 | eq 1, [[0]][0].length 55 | eq 2, [[0],[1]].length 56 | eq 0, [[0],[1]][0][0] 57 | eq 1, [[0],[1]][1][0] 58 | eq 3, [ 59 | [] 60 | [[], []] 61 | [ [[], []], [] ] 62 | ].length 63 | 64 | test 'mixed newline/comma separators', -> 65 | eq k, v for v, k in [ 66 | 0 67 | 1, 2, 3, 68 | 4, 5, 6 69 | 7, 8, 9, 70 | ] 71 | return 72 | 73 | test 'listed functions', -> 74 | a = [ 75 | (x) -> x * x 76 | -> 77 | (x) -> x 78 | ] 79 | ok a.length is 3 80 | b = [(x) -> x * x, ->, (x) -> x, ->] 81 | ok b.length is 4 82 | 83 | test.skip 'dedented comma style', -> # Currently syntax error. 84 | # eq 3, [ 85 | # 0 86 | # , 87 | # 0 88 | # , 89 | # 0 90 | # ].length 91 | 92 | test.skip 'jashkenas/coffee-script#1274: `[] = a()` compiles to `false` instead of `a()`', -> 93 | a = false 94 | fn = -> a = true 95 | [] = fn() 96 | ok a 97 | 98 | test 'mixed shorthand objects in array lists', -> 99 | 100 | arr = [ 101 | a:1 102 | 'b' 103 | c:1 104 | ] 105 | ok arr.length is 3 106 | ok arr[2].c is 1 107 | 108 | arr = [b: 1, a: 2, 100] 109 | eq arr[1], 100 110 | 111 | arr = [a:0, b:1, (1 + 1)] 112 | eq arr[1], 2 113 | 114 | arr = [a:1, 'a', b:1, 'b'] 115 | eq arr.length, 4 116 | eq arr[2].b, 1 117 | eq arr[3], 'b' 118 | 119 | 120 | suite 'Splats', -> 121 | 122 | test 'basic splats', -> 123 | a = [1, 2, 3] 124 | arrayEq [1, 2, 3], [a...] 125 | arrayEq [0, 1, 2, 3], [0, a...] 126 | arrayEq [1, 2, 3, 4], [a..., 4] 127 | arrayEq [1, 2, 3, 1, 2, 3], [a..., a...] 128 | arrayEq [1, 2, 3, [1, 2, 3]], [a..., [a]...] 129 | arrayEq [[0], 1, 2, 3], [[0], a...] 130 | arrayEq [0, 1, 2, 3], [[0]..., a...] 131 | b = [1, [2], 3] 132 | arrayEq [1, [2], 3], [b...] 133 | arrayEq [[0], 1, [2], 3], [[0], b...] 134 | 135 | test 'splats and member access', -> 136 | arr = [0, 1, 2] 137 | a = 'a' 138 | obj = {a: arr, prototype: {a: arr}} 139 | arrayEq arr, [obj.a...] 140 | arrayEq arr, [obj::a...] 141 | arrayEq arr, [obj[a]...] 142 | arrayEq arr, [obj::[a]...] 143 | arrayEq arr, [obj?.a...] 144 | arrayEq arr, [obj?::a...] 145 | arrayEq arr, [obj?[a]...] 146 | arrayEq arr, [obj?::[a]...] 147 | 148 | test 'splats and function invocation', -> 149 | arr = [1, 2, 3] 150 | fn = -> arr 151 | obj = {fn: fn} 152 | arrayEq arr, [(do fn)...] 153 | arrayEq arr, [fn()...] 154 | arrayEq arr, [(fn 0)...] 155 | arrayEq arr, [fn(0)...] 156 | arrayEq arr, [(do obj.fn)...] 157 | arrayEq arr, [obj.fn()...] 158 | arrayEq arr, [(obj.fn 0)...] 159 | arrayEq arr, [obj.fn(0)...] 160 | 161 | test 'splats and array-like objects', -> 162 | obj = {0: 1, 1: 2, 2: 3, length: 3} 163 | fn = -> 164 | arrayEq ([].slice.call arguments), [arguments...] 165 | arguments 166 | arrayEq [1, 2, 3], [obj...] 167 | arrayEq [0, 1, 2, 3, 4], [0, obj..., 4] 168 | arrayEq [1, 2, 3], [(fn 1, 2, 3)...] 169 | arrayEq [0, 1, 2, 3, 4], [0, (fn 1, 2, 3)..., 4] 170 | 171 | test 'array splat expansions with assignments', -> 172 | nums = [1, 2, 3] 173 | list = [a = 0, nums..., b = 4] 174 | eq 0, a 175 | eq 4, b 176 | arrayEq [0,1,2,3,4], list 177 | 178 | test 'array splats with nested arrays', -> 179 | nonce = {} 180 | a = [nonce] 181 | list = [1, 2, a...] 182 | eq list[0], 1 183 | eq list[2], nonce 184 | 185 | a = [[nonce]] 186 | list = [1, 2, a...] 187 | arrayEq list, [1, 2, [nonce]] 188 | -------------------------------------------------------------------------------- /test/assignment.coffee: -------------------------------------------------------------------------------- 1 | suite 'Assignment', -> 2 | # ---------- 3 | 4 | # * Assignment 5 | # * Compound Assignment 6 | # * Destructuring Assignment 7 | # * Context Property (@) Assignment 8 | # * Existential Assignment (?=) 9 | 10 | suite 'Regular Assignment', -> 11 | 12 | test 'assign to the result of an assignment', -> 13 | nonce = {} 14 | a = b = nonce 15 | eq nonce, a 16 | eq nonce, b 17 | 18 | test "context property assignment (using @)", -> 19 | nonce = {} 20 | addMethod = -> 21 | @method = -> nonce 22 | this 23 | eq nonce, addMethod.call({}).method() 24 | 25 | test "unassignable values", -> 26 | nonce = {} 27 | for nonref in ['', '""', '0', 'f()'].concat CoffeeScript.RESERVED 28 | eq nonce, (try CoffeeScript.compile "#{nonref} = v" catch e then nonce) 29 | 30 | suite 'Compound Assignment', -> 31 | 32 | test "boolean operators", -> 33 | nonce = {} 34 | 35 | a = 0 36 | a or= nonce 37 | eq nonce, a 38 | 39 | b = 1 40 | b or= nonce 41 | eq 1, b 42 | 43 | c = 0 44 | c and= nonce 45 | eq 0, c 46 | 47 | d = 1 48 | d and= nonce 49 | eq nonce, d 50 | 51 | # ensure that RHS is treated as a group 52 | e = f = false 53 | e and= f or true 54 | eq false, e 55 | 56 | test "compound assignment as a sub expression", -> 57 | [a, b, c] = [1, 2, 3] 58 | eq 6, a + (b += c) 59 | eq 1, a 60 | eq 5, b 61 | eq 3, c 62 | 63 | # *note: this test could still use refactoring* 64 | test.skip "compound assignment should be careful about caching variables", -> 65 | count = 0 66 | list = [] 67 | 68 | list[++count] or= 1 69 | eq 1, list[1] 70 | eq 1, count 71 | 72 | list[++count] ?= 2 73 | eq 2, list[2] 74 | eq 2, count 75 | 76 | list[count++] and= 6 77 | eq 6, list[2] 78 | eq 3, count 79 | 80 | base = -> 81 | ++count 82 | base 83 | 84 | base().four or= 4 85 | eq 4, base.four 86 | eq 4, count 87 | 88 | base().five ?= 5 89 | eq 5, base.five 90 | eq 5, count 91 | 92 | test "compound assignment with implicit objects", -> 93 | obj = undefined 94 | obj ?= 95 | one: 1 96 | 97 | eq 1, obj.one 98 | 99 | obj and= 100 | two: 2 101 | 102 | eq undefined, obj.one 103 | eq 2, obj.two 104 | 105 | test "compound assignment (math operators)", -> 106 | num = 10 107 | num -= 5 108 | eq 5, num 109 | 110 | num *= 10 111 | eq 50, num 112 | 113 | num /= 10 114 | eq 5, num 115 | 116 | num %= 3 117 | eq 2, num 118 | 119 | test "more compound assignment", -> 120 | a = {} 121 | val = undefined 122 | val ||= a 123 | val ||= true 124 | eq a, val 125 | 126 | b = {} 127 | val &&= true 128 | eq val, true 129 | val &&= b 130 | eq b, val 131 | 132 | c = {} 133 | val = null 134 | val ?= c 135 | val ?= true 136 | eq c, val 137 | 138 | 139 | suite 'Destructuring Assignment', -> 140 | 141 | test "empty destructuring assignment", -> 142 | {} = [] = undefined 143 | 144 | test "chained destructuring assignments", -> 145 | [a] = {0: b} = {'0': c} = [nonce={}] 146 | eq nonce, a 147 | eq nonce, b 148 | eq nonce, c 149 | 150 | test "variable swapping to verify caching of RHS values when appropriate", -> 151 | a = nonceA = {} 152 | b = nonceB = {} 153 | c = nonceC = {} 154 | [a, b, c] = [b, c, a] 155 | eq nonceB, a 156 | eq nonceC, b 157 | eq nonceA, c 158 | [a, b, c] = [b, c, a] 159 | eq nonceC, a 160 | eq nonceA, b 161 | eq nonceB, c 162 | fn = -> 163 | [a, b, c] = [b, c, a] 164 | arrayEq [nonceA,nonceB,nonceC], fn() 165 | eq nonceA, a 166 | eq nonceB, b 167 | eq nonceC, c 168 | 169 | test "#713", -> 170 | nonces = [nonceA={},nonceB={}] 171 | eq nonces, [a, b] = [c, d] = nonces 172 | eq nonceA, a 173 | eq nonceA, c 174 | eq nonceB, b 175 | eq nonceB, d 176 | 177 | test "destructuring assignment with splats", -> 178 | a = {}; b = {}; c = {}; d = {}; e = {} 179 | [x,y...,z] = [a,b,c,d,e] 180 | eq a, x 181 | arrayEq [b,c,d], y 182 | eq e, z 183 | 184 | test "deep destructuring assignment with splats", -> 185 | a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={} 186 | [u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i] 187 | eq a, u 188 | eq b, v 189 | arrayEq [c,d], w 190 | eq e, x 191 | arrayEq [f,g,h], y 192 | eq i, z 193 | 194 | test "destructuring assignment with objects", -> 195 | a={}; b={}; c={} 196 | obj = {a,b,c} 197 | {a:x, b:y, c:z} = obj 198 | eq a, x 199 | eq b, y 200 | eq c, z 201 | 202 | test "deep destructuring assignment with objects", -> 203 | a={}; b={}; c={}; d={} 204 | obj = {a, b: {'c': {d: [b, {e: c, f: d}]}}} 205 | {a: w, 'b': {c: {d: [x, {'f': z, e: y}]}}} = obj 206 | eq a, w 207 | eq b, x 208 | eq c, y 209 | eq d, z 210 | 211 | test "destructuring assignment with objects and splats", -> 212 | a={}; b={}; c={}; d={} 213 | obj = a: b: [a, b, c, d] 214 | {a: {b: [y, z...]}} = obj 215 | eq a, y 216 | arrayEq [b,c,d], z 217 | 218 | test "destructuring assignment against an expression", -> 219 | a={}; b={} 220 | [y, z] = if true then [a, b] else [b, a] 221 | eq a, y 222 | eq b, z 223 | 224 | test "bracket insertion when necessary", -> 225 | [a] = [0] ? [1] 226 | eq a, 0 227 | 228 | # for implicit destructuring assignment in comprehensions, see the comprehension tests 229 | 230 | test "destructuring assignment with context (@) properties", -> 231 | a={}; b={}; c={}; d={}; e={} 232 | obj = 233 | fn: -> 234 | local = [a, {b, c}, d, e] 235 | [@a, {b: @b, c: @c}, @d, @e] = local 236 | eq undefined, obj[key] for key in ['a','b','c','d','e'] 237 | obj.fn() 238 | eq a, obj.a 239 | eq b, obj.b 240 | eq c, obj.c 241 | eq d, obj.d 242 | eq e, obj.e 243 | 244 | test 'destructuring with statement-like RHS', -> 245 | c = false 246 | {a, b} = if c then {a: 0, b: 1} else {a: 2, b: 3} 247 | eq 2, a 248 | eq 3, b 249 | 250 | test "#1024", -> 251 | eq 2 * ([] = 3 + 5), 16 252 | 253 | test.skip "#1005: invalid identifiers allowed on LHS of destructuring assignment", -> 254 | disallowed = ['eval', 'arguments'].concat CoffeeScript.RESERVED 255 | throws (-> CoffeeScript.compile "[#{disallowed.join ', '}] = x"), null, 'all disallowed' 256 | throws (-> CoffeeScript.compile "[#{disallowed.join '..., '}...] = x"), null, 'all disallowed as splats' 257 | t = tSplat = null 258 | for v in disallowed when v isnt 'class' # `class` by itself is an expression 259 | throws (-> CoffeeScript.compile t), null, t = "[#{v}] = x" 260 | throws (-> CoffeeScript.compile tSplat), null, tSplat = "[#{v}...] = x" 261 | doesNotThrow -> 262 | for v in disallowed 263 | CoffeeScript.compile "[a.#{v}] = x" 264 | CoffeeScript.compile "[a.#{v}...] = x" 265 | CoffeeScript.compile "[@#{v}] = x" 266 | CoffeeScript.compile "[@#{v}...] = x" 267 | 268 | test "#2055: destructuring assignment with `new`", -> 269 | {length} = new Array 270 | eq 0, length 271 | 272 | 273 | suite 'Existential Assignment', -> 274 | 275 | test "existential assignment", -> 276 | nonce = {} 277 | a = false 278 | a ?= nonce 279 | eq false, a 280 | b = undefined 281 | b ?= nonce 282 | eq nonce, b 283 | c = null 284 | c ?= nonce 285 | eq nonce, c 286 | 287 | test.skip "#1627: prohibit conditional assignment of undefined variables", -> 288 | throws (-> CoffeeScript.compile "x ?= 10"), null, "prohibit (x ?= 10)" 289 | throws (-> CoffeeScript.compile "x ||= 10"), null, "prohibit (x ||= 10)" 290 | throws (-> CoffeeScript.compile "x or= 10"), null, "prohibit (x or= 10)" 291 | throws (-> CoffeeScript.compile "do -> x ?= 10"), null, "prohibit (do -> x ?= 10)" 292 | throws (-> CoffeeScript.compile "do -> x ||= 10"), null, "prohibit (do -> x ||= 10)" 293 | throws (-> CoffeeScript.compile "do -> x or= 10"), null, "prohibit (do -> x or= 10)" 294 | doesNotThrow (-> CoffeeScript.compile "x = null; x ?= 10"), "allow (x = null; x ?= 10)" 295 | doesNotThrow (-> CoffeeScript.compile "x = null; x ||= 10"), "allow (x = null; x ||= 10)" 296 | doesNotThrow (-> CoffeeScript.compile "x = null; x or= 10"), "allow (x = null; x or= 10)" 297 | doesNotThrow (-> CoffeeScript.compile "x = null; do -> x ?= 10"), "allow (x = null; do -> x ?= 10)" 298 | doesNotThrow (-> CoffeeScript.compile "x = null; do -> x ||= 10"), "allow (x = null; do -> x ||= 10)" 299 | doesNotThrow (-> CoffeeScript.compile "x = null; do -> x or= 10"), "allow (x = null; do -> x or= 10)" 300 | 301 | throws (-> CoffeeScript.compile "-> -> -> x ?= 10"), null, "prohibit (-> -> -> x ?= 10)" 302 | doesNotThrow (-> CoffeeScript.compile "x = null; -> -> -> x ?= 10"), "allow (x = null; -> -> -> x ?= 10)" 303 | 304 | test "more existential assignment", -> 305 | obj = {} 306 | obj.temp ?= 0 307 | eq obj.temp, 0 308 | obj.temp or= 100 309 | eq obj.temp, 100 310 | 311 | test "#1348, #1216: existential assignment compilation", -> 312 | nonce = {} 313 | a = nonce 314 | b = (a ?= 0) 315 | eq nonce, b 316 | #the first ?= compiles into a statement; the second ?= compiles to a ternary expression 317 | eq a ?= b ?= 1, nonce 318 | 319 | if a then a ?= 2 else a = 3 320 | eq a, nonce 321 | 322 | test "#1591, #1101: splatted expressions in destructuring assignment must be assignable", -> 323 | nonce = {} 324 | for nonref in ['', '""', '0', 'f()', '(->)'].concat CoffeeScript.RESERVED 325 | eq nonce, (try CoffeeScript.compile "[#{nonref}...] = v" catch e then nonce) 326 | 327 | test.skip "#1643: splatted accesses in destructuring assignments should not be declared as variables", -> 328 | nonce = {} 329 | accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'C::', 'f().a', 'o?.a', 'o?.a.b', 'f?().a'] 330 | for access in accesses 331 | for i,j in [1,2,3] #position can matter 332 | code = 333 | """ 334 | nonce = {}; nonce2 = {}; nonce3 = {}; 335 | @o = o = new (class C then a:{}); f = -> o 336 | [#{new Array(i).join('x,')}#{access}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3] 337 | unless #{access}[0] is nonce and #{access}[1] is nonce2 and #{access}[2] is nonce3 then throw new Error('[...]') 338 | """ 339 | eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce 340 | # subpatterns like `[[a]...]` and `[{a}...]` 341 | subpatterns = ['[sub, sub2, sub3]', '{0: sub, 1: sub2, 2: sub3}'] 342 | for subpattern in subpatterns 343 | for i,j in [1,2,3] 344 | code = 345 | """ 346 | nonce = {}; nonce2 = {}; nonce3 = {}; 347 | [#{new Array(i).join('x,')}#{subpattern}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3] 348 | unless sub is nonce and sub2 is nonce2 and sub3 is nonce3 then throw new Error('[sub...]') 349 | """ 350 | eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce 351 | 352 | test "#1838: Regression with variable assignment", -> 353 | name = 354 | 'dave' 355 | 356 | eq name, 'dave' 357 | 358 | test.skip 'jashkenas/coffee-script#2211: splats in destructured parameters', -> 359 | doesNotThrow -> CoffeeScript.compile '([a...]) ->' 360 | doesNotThrow -> CoffeeScript.compile '([a...],b) ->' 361 | doesNotThrow -> CoffeeScript.compile '([a...],[b...]) ->' 362 | throws -> CoffeeScript.compile '([a...,[a...]]) ->' 363 | doesNotThrow -> CoffeeScript.compile '([a...,[b...]]) ->' 364 | 365 | test 'jashkenas/coffee-script#2213: invocations within destructured parameters', -> 366 | throws -> CoffeeScript.compile '([a()])->' 367 | throws -> CoffeeScript.compile '([a:b()])->' 368 | throws -> CoffeeScript.compile '([a:b.c()])->' 369 | throws -> CoffeeScript.compile '({a()})->' 370 | throws -> CoffeeScript.compile '({a:b()})->' 371 | throws -> CoffeeScript.compile '({a:b.c()})->' 372 | 373 | test '#72: parsing assignment fails when the assignee is member access of a result of a call', -> 374 | f = (o) -> o 375 | g = -> this 376 | nonce = {} 377 | 378 | obj = {} 379 | f(obj).a = nonce 380 | eq nonce, obj.a 381 | 382 | obj = {g: g} 383 | obj.g().a = nonce 384 | eq nonce, obj.a 385 | -------------------------------------------------------------------------------- /test/booleans.coffee: -------------------------------------------------------------------------------- 1 | suite 'Boolean Literals', -> 2 | 3 | # TODO: add method invocation tests: true.toString() is "true" 4 | 5 | test "#764 Booleans should be indexable", -> 6 | toString = Boolean::toString 7 | 8 | eq toString, true['toString'] 9 | eq toString, false['toString'] 10 | eq toString, yes['toString'] 11 | eq toString, no['toString'] 12 | eq toString, on['toString'] 13 | eq toString, off['toString'] 14 | 15 | eq toString, true.toString 16 | eq toString, false.toString 17 | eq toString, yes.toString 18 | eq toString, no.toString 19 | eq toString, on.toString 20 | eq toString, off.toString 21 | -------------------------------------------------------------------------------- /test/cli-eval-errors-files/0.coffee: -------------------------------------------------------------------------------- 1 | console.log "0 is main", module is require.main 2 | 3 | exports.error = -> throw new Error("Test Error") 4 | -------------------------------------------------------------------------------- /test/cli-eval-errors-files/1.coffee: -------------------------------------------------------------------------------- 1 | console.log "1 is main", module is require.main 2 | 3 | test1 = require './0.coffee' 4 | test1.error() 5 | -------------------------------------------------------------------------------- /test/cli-eval-errors.coffee: -------------------------------------------------------------------------------- 1 | child_process = require 'child_process' 2 | 3 | suite 'Command line execution', -> 4 | test "--eval -i", (done) -> 5 | child_process.exec 'bin/coffee --eval -i test/cli-eval-errors-files/1.coffee', (error, stdout, stderr) -> 6 | # Executed module is require.main 7 | # Module path is relative to the file 8 | # Can include another CS module 9 | # Other module is not requires.main 10 | eq stdout, "1 is main true\n0 is main false\n" 11 | 12 | ok stderr.indexOf("cli-eval-errors-files/0.coffee:3:26, :4:9)") > 0 13 | ok stderr.indexOf("cli-eval-errors-files/1.coffee:4:7, :6:9)") > 0 14 | 15 | done() 16 | 17 | test "--eval --cli", (done) -> 18 | child_process.exec 'bin/coffee --eval --cli "require \'./test/cli-eval-errors-files/1.coffee\'"', (error, stdout, stderr) -> 19 | eq stdout, "1 is main false\n0 is main false\n" 20 | done() 21 | -------------------------------------------------------------------------------- /test/cluster.coffee: -------------------------------------------------------------------------------- 1 | child_process = require 'child_process' 2 | path = require 'path' 3 | 4 | semver = require 'semver' 5 | 6 | coffeeBinary = path.resolve 'bin', 'coffee' 7 | 8 | if semver.satisfies process.version, '>= 0.10.0' 9 | test "jashkenas/coffee-script#2737: cluster module can spawn coffee workers", (done) -> 10 | (child_process.spawn coffeeBinary, ['test/cluster/cluster.coffee']).on 'close', (code) -> 11 | eq 0, code 12 | do done 13 | return 14 | 15 | test "jashkenas/coffee-script#2737: cluster module can spawn litcoffee workers", (done) -> 16 | (child_process.spawn coffeeBinary, ['test/cluster/cluster.litcoffee']).on 'close', (code) -> 17 | eq 0, code 18 | do done 19 | return 20 | -------------------------------------------------------------------------------- /test/cluster/cluster.coffee: -------------------------------------------------------------------------------- 1 | cluster = require 'cluster' 2 | 3 | if cluster.isMaster 4 | cluster.once 'exit', (worker, code) -> 5 | process.exit code 6 | return 7 | cluster.fork() 8 | else 9 | process.exit 0 10 | -------------------------------------------------------------------------------- /test/cluster/cluster.litcoffee: -------------------------------------------------------------------------------- 1 | cluster = require 'cluster' 2 | 3 | if cluster.isMaster 4 | cluster.once 'exit', (worker, code) -> 5 | process.exit code 6 | return 7 | cluster.fork() 8 | else 9 | process.exit 0 10 | -------------------------------------------------------------------------------- /test/comprehensions.coffee: -------------------------------------------------------------------------------- 1 | suite 'Comprehensions', -> 2 | 3 | test 'comprehensions with no body produce `undefined` for each entry', -> 4 | arrayEq (undefined for a in [0..9]), for b in [0..9] then 5 | 6 | test '#66: `throw` as the final expression in the body of a comprehension', -> 7 | (->) -> for a in [0..9] then throw {} 8 | 9 | test 'comprehensions over static, integral ranges', -> 10 | arrayEq [0..9], (a for a in [0..9]) 11 | arrayEq [0...9], (a for a in [0...9]) 12 | 13 | test '#234: value may be omitted in for-in comprehensions', -> 14 | arrayEq [0, 0, 0, 0], (0 for in [0..3]) 15 | c = 0 16 | fn = -> c++ 17 | arrayEq [0..9], (fn() for in [0..9]) 18 | a = 0 19 | b = 9 20 | c = 0 21 | arrayEq [a..b], (fn() for in [a..b]) 22 | c = 0 23 | arrayEq [a...b], (fn() for in [a...b]) 24 | 25 | test 'filtered comprehensions', -> 26 | list = [0..5] 27 | arrayEq [1, 3, 5], (a for a in list when a & 1) 28 | arrayEq [0..3], (a for a in list when a < 4) 29 | 30 | test '#285: filtered comprehensions over ranges', -> 31 | arrayEq [1, 3, 5], (a for a in [0..5] when a & 1) 32 | arrayEq [0..3], (a for a in [0..5] when a < 4) 33 | 34 | test 'comprehension over range with index', -> 35 | arrayEq [0..3], (k for v, k in [5..8]) 36 | arrayEq [5..8], (v for v, k in [5..8]) 37 | 38 | test '#286: stepped loops', -> 39 | list = [1..7] 40 | arrayEq [1, 4, 7], (v for v in list by 3) 41 | arrayEq [1, 4, 7], (v for v in [1..7] by 3) 42 | arrayEq [0, 3, 6], (k for v, k in list by 3) 43 | arrayEq [0, 3, 6], (k for v, k in [1..7] by 3) 44 | arrayEq [0, 0, 0], (0 for in list by 3) 45 | arrayEq [0, 0, 0], (0 for in [1..7] by 3) 46 | 47 | test '#284: loops/comprehensions over decreasing ranges don\'t work', -> 48 | a = 2 49 | b = -2 50 | arrayEq [5, 4, 3, 2, 1], (n for n in [5..1]) 51 | arrayEq [5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5], (n for n in [5..-5]) 52 | arrayEq [2, 1, 0, -1, -2], (n for n in [a..b]) 53 | arrayEq [2, 1, 0, -1, -2], (n for n in [a..-2]) 54 | arrayEq [2, 1, 0, -1, -2], (n for n in [2..b]) 55 | 56 | arrayEq [5, 4, 3, 2], (n for n in [5...1]) 57 | arrayEq [2, 1, 0, -1], (n for n in [a...b]) 58 | arrayEq [2, 1, 0, -1], (n for n in [a...-2]) 59 | arrayEq [2, 1, 0, -1], (n for n in [2...b]) 60 | -------------------------------------------------------------------------------- /test/debugger.coffee: -------------------------------------------------------------------------------- 1 | suite 'Debugger', -> 2 | 3 | setup -> 4 | @shouldParse = (input) -> doesNotThrow -> parse input 5 | @shouldNotParse = (input) -> throws -> parse input 6 | 7 | test 'should parse', -> 8 | @shouldParse 'debugger' 9 | 10 | test 'cannot be used as value', -> 11 | @shouldNotParse 'x = debugger' 12 | 13 | test 'function with debugger as last statement', -> 14 | debugger 15 | 16 | test 'function with conditional debugger as last statement', -> 17 | x = true 18 | if x then debugger 19 | -------------------------------------------------------------------------------- /test/error-messages.coffee: -------------------------------------------------------------------------------- 1 | suite 'Error Messages', -> 2 | 3 | test 'patched stack trace prelude consistency with V8', -> 4 | err0 = new Error 5 | err1 = new Error 'message' 6 | eq 'Error\n', err0.stack[...6] 7 | eq 'Error: message\n', err1.stack[...15] 8 | -------------------------------------------------------------------------------- /test/functions.coffee: -------------------------------------------------------------------------------- 1 | suite 'Function Literals', -> 2 | 3 | suite 'Function Definition', -> 4 | 5 | test 'basic functions', -> 6 | 7 | fn = -> 3 8 | eq 'function', typeof fn 9 | ok fn instanceof Function 10 | eq 3, fn() 11 | 12 | test 'empty functions', -> 13 | fn = -> 14 | eq 'function', typeof fn 15 | eq undefined, fn() 16 | fn = () -> 17 | eq 'function', typeof fn 18 | eq undefined, fn() 19 | fn = (-> 20 | ) 21 | eq 'function', typeof fn 22 | eq undefined, fn() 23 | 24 | test 'multiple nested single-line functions', -> 25 | func = (x) -> (x) -> (x) -> x 26 | eq 3, func(1)(2)(3) 27 | 28 | test 'multiple nested single-line functions mixed with implicit calls', -> 29 | fn = (one) -> (two) -> three four, (five) -> six seven, eight, (nine) -> 30 | eq 'function', typeof fn 31 | 32 | test "self-referencing functions", -> 33 | changeMe = -> 34 | changeMe = 2 35 | eq 'function', typeof changeMe 36 | eq 2, changeMe() 37 | eq 2, changeMe 38 | 39 | test "#1859: inline function bodies shouldn't modify prior postfix ifs", -> 40 | list = [1, 2, 3] 41 | ok true if list.some (x) -> x is 2 42 | 43 | 44 | suite 'Bound Function Definition', -> 45 | 46 | test 'basic bound functions', -> 47 | obj = { 48 | bound: -> 49 | (=> this)() 50 | unbound: -> 51 | (-> this)() 52 | nested: -> 53 | (=> 54 | (=> 55 | (=> this)() 56 | )() 57 | )() 58 | } 59 | eq obj, obj.bound() 60 | ok obj isnt obj.unbound() 61 | eq obj, obj.nested() 62 | 63 | test "fancy bound functions", -> 64 | obj = { 65 | one: -> 66 | do => 67 | return this.two() 68 | two: -> 69 | do => 70 | do => 71 | do => 72 | return this.three 73 | three: 3 74 | } 75 | eq obj.one(), 3 76 | 77 | test "#1844: bound functions in nested comprehensions causing empty var statements", -> 78 | a = ((=>) for a in [0] for b in [0]) 79 | eq 1, a.length 80 | 81 | 82 | suite 'Parameter List Features', -> 83 | 84 | test "splats", -> 85 | arrayEq [0, 1, 2], (((splat...) -> splat) 0, 1, 2) 86 | arrayEq [2, 3], (((_, _1, splat...) -> splat) 0, 1, 2, 3) 87 | arrayEq [0, 1], (((splat..., _, _1) -> splat) 0, 1, 2, 3) 88 | arrayEq [2], (((_, _1, splat..., _2) -> splat) 0, 1, 2, 3) 89 | 90 | test "function length with splats", -> 91 | eq 0, ((splat...) ->).length 92 | eq 2, ((_, _1, splat...) ->).length 93 | eq 2, ((splat..., _, _1) ->).length 94 | eq 3, ((_, _1, splat..., _2) ->).length 95 | 96 | test "destructured splatted parameters", -> 97 | arr = [0,1,2] 98 | splatArray = ([a...]) -> a 99 | splatArrayRest = ([a...],b...) -> arrayEq(a,b); b 100 | arrayEq splatArray(arr), arr 101 | arrayEq splatArrayRest(arr,0,1,2), arr 102 | 103 | test "@-parameters: automatically assign an argument's value to a property of the context", -> 104 | nonce = {} 105 | 106 | ((@prop) ->).call context = {}, nonce 107 | eq nonce, context.prop 108 | 109 | ((splat..., @prop) ->).apply context = {}, [0, 0, nonce] 110 | eq nonce, context.prop 111 | 112 | #((@prop...) ->).call context = {}, 0, nonce, 0 113 | #eq nonce, context.prop[1] 114 | 115 | eq 0, ((@prop) -> @prop).call {}, 0 116 | eq 'undefined', ((@prop) -> typeof prop).call {}, 0 117 | 118 | test "@-parameters and splats with constructors", -> 119 | a = {} 120 | b = {} 121 | class Klass 122 | constructor: (@first, splat..., @last) -> 123 | 124 | obj = new Klass a, 0, 0, b 125 | eq a, obj.first 126 | eq b, obj.last 127 | 128 | #test "destructuring splats", -> 129 | # (([{a: [b], c}]...) -> 130 | # eq 1, b 131 | # eq 2, c 132 | # ) {a: [1], c: 2} 133 | 134 | test "default values", -> 135 | nonceA = {} 136 | nonceB = {} 137 | a = (_,_1,arg=nonceA) -> arg 138 | eq nonceA, a() 139 | eq nonceA, a(0) 140 | eq nonceB, a(0,0,nonceB) 141 | eq nonceA, a(0,0,undefined) 142 | eq nonceA, a(0,0,null) 143 | eq false , a(0,0,false) 144 | eq nonceB, a(undefined,undefined,nonceB,undefined) 145 | b = (_,arg=nonceA,_1,_2) -> arg 146 | eq nonceA, b() 147 | eq nonceA, b(0) 148 | eq nonceB, b(0,nonceB) 149 | eq nonceA, b(0,undefined) 150 | eq nonceA, b(0,null) 151 | eq false , b(0,false) 152 | eq nonceB, b(undefined,nonceB,undefined) 153 | c = (arg=nonceA,_,_1) -> arg 154 | eq nonceA, c() 155 | eq 0, c(0) 156 | eq nonceB, c(nonceB) 157 | eq nonceA, c(undefined) 158 | eq nonceA, c(null) 159 | eq false , c(false) 160 | eq nonceB, c(nonceB,undefined,undefined) 161 | 162 | test "default values with @-parameters", -> 163 | nonceA = {} 164 | nonceB = {} 165 | obj = {f: (q = nonceA, @p = nonceB) -> q} 166 | eq nonceA, obj.f() 167 | eq nonceB, obj.p 168 | eq nonceB, obj.f nonceB, nonceA 169 | eq nonceA, obj.p 170 | 171 | test "default values with splatted arguments", -> 172 | withSplats = (a = 2, b..., c = 3, d = 5) -> a * (b.length + 1) * c * d 173 | eq 30, withSplats() 174 | eq 15, withSplats(1) 175 | eq 5, withSplats(1,1) 176 | eq 1, withSplats(1,1,1) 177 | eq 2, withSplats(1,1,1,1) 178 | 179 | test "default values with function calls", -> 180 | counter = 0 181 | fn = -> ++counter 182 | eq 1, ((x = fn()) -> x)() 183 | eq fn, ((x = fn()) -> x) fn 184 | eq 0, ((x = fn) -> x()) -> 0 185 | eq 2, ((x = fn()) -> x)() 186 | 187 | test "#229: default values with destructuring", -> 188 | nonceA = {} 189 | nonceB = {} 190 | eq nonceA, (({a} = {a: nonceA}) -> a)() 191 | eq nonceB, (({a} = {a: nonceA}) -> a)({a: nonceB}) 192 | 193 | test "arguments vs parameters", -> 194 | nonce = {} 195 | f = (x) -> x() 196 | eq nonce, f (x) -> nonce 197 | g = -> f 198 | eq nonce, g(f) -> nonce 199 | 200 | test "#2258: allow whitespace-style parameter lists in function definitions", -> 201 | func = ( 202 | a, b, c 203 | ) -> c 204 | eq func(1, 2, 3), 3 205 | func = ( 206 | a 207 | b 208 | c 209 | ) -> b 210 | eq func(1, 2, 3), 2 211 | func = ( 212 | a, 213 | b, 214 | c 215 | ) -> b 216 | eq func(1, 2, 3), 2 217 | 218 | test '#66: functions whose final expression is `throw` should compile', -> 219 | (->) -> throw {} 220 | (->) -> 221 | a = Math.random() 222 | if a then throw {} 223 | 224 | test '#270: splat in parameter list should shadow outer variables', -> 225 | a = nonceA = {} 226 | b = nonceB = {} 227 | f0 = (a..., b) -> 228 | f1 = (a, b...) -> 229 | f0() 230 | f1() 231 | eq nonceA, a 232 | eq nonceB, b 233 | 234 | test '#273: destructuring in parameter list should shadow outer variables', -> 235 | x = nonce = {} 236 | f = ({x}) -> x 237 | f {x: 1} 238 | eq nonce, x 239 | 240 | test '#288: at-splat', -> 241 | (@a...) -> 242 | (a, @b...) -> 243 | (@a..., b) -> 244 | -------------------------------------------------------------------------------- /test/literate.coffee: -------------------------------------------------------------------------------- 1 | suite 'Literate Formatting', -> 2 | 3 | # This test passes in node 0.8, but has some infinite recursion issue outside this code in 0.10 4 | # So I've disabled it until the issue gets magically resolved at some point in the future 5 | #test 'jashkenas/coffee-script: src/scope.litcoffee', -> 6 | # litcoffee = "#{fs.readFileSync 'test/scope.litcoffee'}" 7 | # parse litcoffee, literate: yes 8 | -------------------------------------------------------------------------------- /test/macros.coffee: -------------------------------------------------------------------------------- 1 | suite 'Macros', -> 2 | 3 | test '__LINE__', -> 4 | eq 4, __LINE__ 5 | 6 | test '__DATE__', -> 7 | eq (new Date).toDateString()[4..], __DATE__ 8 | 9 | test '__TIME__', -> 10 | ok /^(\d\d:){2}\d\d$/.test __TIME__ 11 | 12 | test '__DATETIMEMS__', -> 13 | ok (-6e4 < (__DATETIMEMS__ - new Date) < 6e4) 14 | ok 1e12 < __DATETIMEMS__ < 1e13 15 | 16 | test '__COFFEE_VERSION__', -> 17 | eq (require '../package.json').version, __COFFEE_VERSION__ 18 | -------------------------------------------------------------------------------- /test/member-access.coffee: -------------------------------------------------------------------------------- 1 | suite 'Member Access', -> 2 | 3 | # TODO: all of the things 4 | 5 | test 'various basic member accesses', -> 6 | nonceA = {} 7 | nonceB = {a: nonceA} 8 | nonceA.b = nonceB 9 | nil = null 10 | obj = {a: nonceA, prototype: {b: nonceB}} 11 | a = 'a' 12 | b = 'b' 13 | # member access 14 | eq nonceA, obj.a 15 | eq nonceA, obj?.a 16 | eq nonceB, obj?.a.b 17 | eq nonceB, obj?.a[b] 18 | throws -> nil.a 19 | eq undefined, nil?.a 20 | eq undefined, nil?.a.b 21 | eq undefined, nil?.a[b] 22 | # dynamic member access 23 | eq nonceA, obj[a] 24 | eq nonceA, obj?[a] 25 | eq nonceB, obj?[a].b 26 | eq nonceB, obj?[a][b] 27 | throws -> nil[a] 28 | eq undefined, nil?[a] 29 | eq undefined, nil?[a].b 30 | eq undefined, nil?[a][b] 31 | # proto-member access 32 | eq nonceB, obj::b 33 | eq nonceB, obj?::b 34 | eq nonceA, obj?::b.a 35 | eq nonceA, obj?::b[a] 36 | throws -> nil::b 37 | eq undefined, nil?::b 38 | eq undefined, nil?::b.a 39 | eq undefined, nil?::b[a] 40 | # dynamic proto-member access 41 | eq nonceB, obj::[b] 42 | eq nonceB, obj?::[b] 43 | eq nonceA, obj?::[b].a 44 | eq nonceA, obj?::[b][a] 45 | throws -> nil::[b] 46 | eq undefined, nil?::[b] 47 | eq undefined, nil?::[b].a 48 | eq undefined, nil?::[b][a] 49 | 50 | # TODO: combinations of soaked member accesses 51 | 52 | test 'dynamically accessing non-identifierNames', -> 53 | nonceA = {} 54 | nonceB = {} 55 | obj = {'a-b': nonceA} 56 | eq nonceA, obj['a-b'] 57 | obj['c-d'] = nonceB 58 | eq nonceB, obj['c-d'] 59 | 60 | test '#171: dynamic member access on list comprehensions', -> 61 | eq 4, (x ** 2 for x in [0..4])[2] 62 | 63 | test 'indented member access', -> 64 | nonce = {} 65 | 66 | o = a: -> b: c: -> nonce 67 | eq nonce, o.a().b.c() 68 | eq nonce, o 69 | .a() 70 | .b 71 | .c() 72 | eq nonce, o 73 | .a() 74 | .b 75 | .c() 76 | eq nonce, o 77 | .a() 78 | .b 79 | .c() 80 | eq nonce, o 81 | .a() 82 | .b 83 | .c() 84 | eq nonce, o.a().b.c() 85 | 86 | o = a: -> b: c: nonce 87 | eq nonce, o.a().b.c 88 | eq nonce, o 89 | .a() 90 | .b 91 | .c 92 | eq nonce, o 93 | .a() 94 | .b 95 | .c 96 | eq nonce, o 97 | .a() 98 | .b 99 | .c 100 | #eq nonce, o 101 | # .a() 102 | # .b 103 | # .c 104 | eq nonce, o.a().b.c 105 | -------------------------------------------------------------------------------- /test/objects.coffee: -------------------------------------------------------------------------------- 1 | suite 'Object Literals', -> 2 | 3 | # TODO: refactor object literal tests 4 | # TODO: add indexing and method invocation tests: {a}['a'] is a, {a}.a() 5 | 6 | suite 'Basic Objects', -> 7 | 8 | test 'basic literals', -> 9 | nonce = {} 10 | eq nonce, {a:nonce}.a 11 | eq nonce, {a: nonce}.a 12 | eq nonce, { a : nonce }.a 13 | eq nonce, {a: nonce,}.a 14 | eq nonce, {0: nonce}[0] 15 | eq nonce, {0x0: nonce}[0] 16 | eq nonce, {'0x0': nonce}['0x0'] 17 | eq nonce, {1e3: nonce}[1e3] 18 | eq nonce, {a:0,b:nonce,c:0}.b 19 | eq nonce, {a: 0, b: nonce, c: 0}.b 20 | eq nonce, {a: 0, b: nonce, c: 0, }.b 21 | eq nonce, { a : 0 , b : nonce, c : 0 }.b 22 | eq nonce, {'a': nonce}.a 23 | eq nonce, {'s p a c e s': nonce}['s p a c e s'] 24 | 25 | test 'reserved words as keys', -> 26 | nonce = {} 27 | 28 | # CS reserved words 29 | obj = {not: nonce} 30 | eq nonce, obj.not 31 | 32 | # JS reserved words 33 | obj = {default: nonce} 34 | eq nonce, obj.default 35 | 36 | test 'listed functions', -> 37 | nonce = {} 38 | ok nonce, { 0: -> nonce }[0]() 39 | ok nonce, { 0: -> 0, 1: -> nonce, 2: -> 0 }[1]() 40 | 41 | test 'function context', -> 42 | nonce = {} 43 | eq nonce, { nonce: nonce, fn: -> @nonce }.fn() 44 | eq nonce, { nonce: nonce, fn: -> @nonce }['fn']() 45 | 46 | test 'implicit member shorthand', -> 47 | nonce = {} 48 | eq nonce, { nonce }.nonce 49 | (-> eq nonce, { @nonce }.nonce).call { nonce } 50 | 51 | test 'function calls in object literals', -> 52 | fn = (a, b, c) -> c 53 | nonce = {} 54 | eq nonce, { a: fn 0, 1, nonce, 2 }.a 55 | eq nonce, { a: -> fn 0, 1, nonce, 2 }.a() 56 | 57 | test 'jashkenas/coffee-script#542: leading objects need parentheses', -> 58 | a = false 59 | {f: -> a = true}.f() + 1 60 | ok a 61 | 62 | test.skip 'jashkenas/coffee-script#1274: `{} = a()` should not optimise away a()', -> 63 | a = false 64 | fn = -> a = true 65 | {} = fn() 66 | ok a 67 | 68 | test 'jashkenas/coffee-script#1436: `for` etc. work as normal property names', -> 69 | obj = {} 70 | ok 'for' not of obj 71 | obj.for = 'for' of obj 72 | ok 'for' of obj 73 | 74 | test.skip 'jashkenas/coffee-script#1513: Top level bare objects need to be wrapped in parens for unary and existence ops', -> 75 | doesNotThrow -> CoffeeScript.run '{}?', bare: true 76 | doesNotThrow -> CoffeeScript.run '{}.a++', bare: true 77 | 78 | suite 'Implicit Objects', -> 79 | 80 | test 'implicit object literals', -> 81 | obj = 82 | a: 1 83 | b: 2 84 | ok obj.a is 1 85 | ok obj.b is 2 86 | 87 | config = 88 | development: 89 | server: 'localhost' 90 | timeout: 10 91 | production: 92 | server: 'dreamboat' 93 | timeout: 1000 94 | eq config.development.server, 'localhost' 95 | eq config.production.server, 'dreamboat' 96 | eq config.development.timeout, 10 97 | eq config.production.timeout, 1000 98 | 99 | test 'implicit objects as part of chained calls', -> 100 | pluck = (x) -> x.a 101 | eq 100, pluck pluck pluck a: a: a: 100 102 | 103 | test 'explicit objects nested under implicit objects', -> 104 | 105 | test.skip 'invoking functions with implicit object literals', -> # Currently syntax error. 106 | # generateGetter = (prop) -> (obj) -> obj[prop] 107 | # getA = generateGetter 'a' 108 | # getArgs = -> arguments 109 | # a = b = 30 110 | # 111 | # result = getA 112 | # a: 10 113 | # eq 10, result 114 | # 115 | # result = getA 116 | # 'a': 20 117 | # eq 20, result 118 | # 119 | # result = getA a, 120 | # b:1 121 | # eq undefined, result 122 | # 123 | # result = getA b:1, 124 | # a:43 125 | # eq 43, result 126 | # 127 | # result = getA b:1, 128 | # a:62 129 | # eq undefined, result 130 | # 131 | # result = getA 132 | # b:1 133 | # a 134 | # eq undefined, result 135 | # 136 | # result = getA 137 | # a: 138 | # b:2 139 | # b:1 140 | # eq 2, result.b 141 | # 142 | # result = getArgs 143 | # a:1 144 | # b 145 | # c:1 146 | # ok result.length is 3 147 | # ok result[2].c is 1 148 | # 149 | # result = getA b: 13, a: 42, 2 150 | # eq 42, result 151 | # 152 | # result = getArgs a:1, (1 + 1) 153 | # ok result[1] is 2 154 | # 155 | # result = getArgs a:1, b 156 | # ok result.length is 2 157 | # ok result[1] is 30 158 | # 159 | # result = getArgs a:1, b, b:1, a 160 | # ok result.length is 4 161 | # ok result[2].b is 1 162 | # 163 | # throws -> CoffeeScript.compile 'a = b:1, c' 164 | 165 | test 'multiple dedentations in implicit object literals', -> 166 | nonce0 = {} 167 | nonce1 = {} 168 | obj = 169 | a: 170 | b: -> 171 | c: nonce0 172 | d: nonce1 173 | eq nonce0, obj.a.b().c 174 | eq nonce1, obj.d 175 | 176 | test.skip 'jashkenas/coffee-script#1871: Special case for IMPLICIT_END in the middle of an implicit object', -> 177 | result = 'result' 178 | ident = (x) -> x 179 | 180 | result = ident one: 1 if false 181 | 182 | eq result, 'result' 183 | 184 | result = ident 185 | one: 1 186 | two: 2 for i in [1..3] 187 | 188 | eq result.two.join(' '), '2 2 2' 189 | 190 | test 'jashkenas/coffee-script#1961, jashkenas/coffee-script#1974, regression with compound assigning to an implicit object', -> 191 | 192 | obj = null 193 | 194 | obj ?= 195 | one: 1 196 | two: 2 197 | 198 | eq obj.two, 2 199 | 200 | obj = null 201 | 202 | obj or= 203 | three: 3 204 | four: 4 205 | 206 | eq obj.four, 4 207 | 208 | test 'jashkenas/coffee-script#2207: Immediate implicit closes don not close implicit objects', -> 209 | func = -> 210 | key: for i in [1, 2, 3] then i 211 | eq func().key.join(' '), '1 2 3' 212 | 213 | test '#122 implicit object literal in conditional body', -> 214 | a = yes 215 | 216 | b = switch a 217 | when yes 218 | result: yes 219 | when no, 10 220 | result: no 221 | 222 | ok b.result 223 | 224 | c = if a 225 | result: yes 226 | 227 | ok c.result 228 | 229 | d = 42 230 | e = if 2 + 40 is d 231 | result: yes 232 | 233 | ok e.result 234 | 235 | f = unless a 236 | result: no 237 | else 238 | result: yes 239 | 240 | ok f.result 241 | 242 | g = 0 243 | h = 1 244 | while g < h 245 | result: yes 246 | g += 1 247 | 248 | eq g, 1 249 | 250 | i = 0 251 | j = 1 252 | unless i > j 253 | result: yes 254 | i += 1 255 | 256 | eq i, 1 257 | 258 | k = [0..3] 259 | for l in k 260 | result: yes 261 | 262 | eq l, 3 263 | 264 | m = [0..3] 265 | for n of m 266 | result: yes 267 | 268 | eq n, '3' 269 | 270 | test '#170: implicit object literals within explicit object literals', -> 271 | obj = { 272 | a: 0 273 | b: 1 274 | c: 275 | a: 2 276 | b: 3 277 | d: 4 278 | } 279 | eq 0, obj.a 280 | eq 1, obj.b 281 | eq 2, obj.c.a 282 | eq 3, obj.c.b 283 | eq 4, obj.d 284 | 285 | test '#266: inline implicit object literals within multiline implicit object literals', -> 286 | x = 287 | a: aa: 0 288 | b: 0 289 | eq 0, x.b 290 | eq 0, x.a.aa 291 | 292 | test '#258: object literals with a key named class', -> 293 | a = class: 'b' 294 | eq 'b', a.class 295 | 296 | test '#259: object literals inside a class with a key named class', -> 297 | class Bar 298 | a: false 299 | render: (x) -> 300 | 'rendered: ' + x 301 | 302 | class Foo extends Bar 303 | foo: 'bar' 304 | attributes: 305 | class: 'c' 306 | render: -> 307 | Bar::render.apply(this, arguments) 308 | 309 | otherRender = -> 310 | Bar::render.apply(this, arguments) 311 | 312 | f = new Foo 313 | 314 | eq f.attributes.class, 'c' 315 | eq f.render('baz'), 'rendered: baz' 316 | eq otherRender('baz'), 'rendered: baz' 317 | 318 | test '#253: indented value', -> 319 | nonce = {} 320 | o = { 321 | a: 322 | nonce 323 | } 324 | eq nonce, o.a 325 | o = 326 | a: 327 | nonce 328 | eq nonce, o.a 329 | -------------------------------------------------------------------------------- /test/operators.coffee: -------------------------------------------------------------------------------- 1 | suite 'Operators', -> 2 | 3 | # * Operators 4 | # * Existential Operator (Binary) 5 | # * Existential Operator (Unary) 6 | # * Aliased Operators 7 | # * [not] in/of 8 | # * Chained Comparison 9 | 10 | # TODO: sort these 11 | # TODO: basic functionality of all binary and unary operators 12 | 13 | test 'binary maths operators do not require spaces', -> 14 | a = 1 15 | b = -1 16 | eq 1, a*-b 17 | eq -1, a*b 18 | eq 1, a/-b 19 | eq -1, a/b 20 | 21 | test 'operators should respect new lines as spaced', -> 22 | a = 123 + 23 | 456 24 | eq 579, a 25 | 26 | b = "1#{2}3" + 27 | '456' 28 | eq '123456', b 29 | 30 | test 'multiple operators should space themselves', -> 31 | eq (+ +1), (- -1) 32 | 33 | test 'bitwise operators', -> 34 | eq 2, (10 & 3) 35 | eq 11, (10 | 3) 36 | eq 9, (10 ^ 3) 37 | eq 80, (10 << 3) 38 | eq 1, (10 >> 3) 39 | eq 1, (10 >>> 3) 40 | num = 10; eq 2, (num &= 3) 41 | num = 10; eq 11, (num |= 3) 42 | num = 10; eq 9, (num ^= 3) 43 | num = 10; eq 80, (num <<= 3) 44 | num = 10; eq 1, (num >>= 3) 45 | num = 10; eq 1, (num >>>= 3) 46 | 47 | test 'instanceof', -> 48 | ok new String instanceof String 49 | ok new Boolean instanceof Boolean 50 | 51 | test 'not instanceof', -> 52 | ok new Number not instanceof String 53 | ok new Array not instanceof Boolean 54 | 55 | test 'use `::` operator on keyword `this`', -> 56 | obj = prototype: prop: nonce = {} 57 | eq nonce, (-> this::prop).call obj 58 | 59 | test 'variously spaced divisions following a dynamic member access', -> 60 | nonce = 242424242 61 | p = 1 62 | o = {1: nonce} 63 | 64 | eq nonce, (o[1]/1) 65 | eq nonce, (o[p]/1) 66 | eq nonce, (o[1]/p) 67 | eq nonce, (o[p]/p) 68 | 69 | eq nonce, (o[1] /1) 70 | eq nonce, (o[p] /1) 71 | eq nonce, (o[1] /p) 72 | eq nonce, (o[p] /p) 73 | 74 | eq nonce, (o[1]/ 1) 75 | eq nonce, (o[p]/ 1) 76 | eq nonce, (o[1]/ p) 77 | eq nonce, (o[p]/ p) 78 | 79 | eq nonce, (o[1] / 1) 80 | eq nonce, (o[p] / 1) 81 | eq nonce, (o[1] / p) 82 | eq nonce, (o[p] / p) 83 | 84 | test 'jashkenas/coffee-script#2026: exponentiation operator via `**`', -> 85 | eq 27, 3 ** 3 86 | # precedence 87 | eq 55, 1 + 3 ** 3 * 2 88 | # right associativity 89 | eq 2, 2 ** 1 ** 3 90 | eq 2 ** 8, 2 ** 2 ** 3 91 | # compound assignment with exponentiation 92 | a = 2 93 | a **= 2 94 | eq 4, a 95 | 96 | 97 | suite 'Existential Operator (Binary)', -> 98 | 99 | test 'binary existential operator', -> 100 | nonce = {} 101 | 102 | b = a ? nonce 103 | eq nonce, b 104 | 105 | a = null 106 | b = undefined 107 | b = a ? nonce 108 | eq nonce, b 109 | 110 | a = false 111 | b = a ? nonce 112 | eq false, b 113 | 114 | a = 0 115 | b = a ? nonce 116 | eq 0, b 117 | 118 | test 'binary existential operator conditionally evaluates second operand', -> 119 | i = 1 120 | func = -> i -= 1 121 | result = func() ? func() 122 | eq result, 0 123 | 124 | test 'binary existential operator with negative number', -> 125 | a = null ? - 1 126 | eq -1, a 127 | 128 | test 'binary existential with statement LHS', -> 129 | nonce = {} 130 | a = null 131 | b = true 132 | c = -> nonce 133 | d = (if a then b else c()) ? c 134 | eq nonce, d 135 | 136 | test '#85: binary existential with cached LHS', -> 137 | a = {b: ->} 138 | c = true 139 | a?.b() ? c 140 | return 141 | 142 | 143 | suite 'Existential Operator (Unary)', -> 144 | 145 | test 'postfix existential operator', -> 146 | ok (if nonexistent? then false else true) 147 | defined = true 148 | ok defined? 149 | defined = false 150 | ok defined? 151 | 152 | test 'postfix existential operator only evaluates its operand once', -> 153 | semaphore = 0 154 | fn = -> 155 | ok false if semaphore 156 | ++semaphore 157 | ok(if fn()? then true else false) 158 | 159 | test 'negated postfix existential operator', -> 160 | ok !nothing?.value 161 | 162 | test 'postfix existential operator on expressions', -> 163 | eq true, (1 or 0)?, true 164 | 165 | 166 | suite '`is`,`isnt`,`==`,`!=`', -> 167 | 168 | test '`==` and `is` should be interchangeable', -> 169 | a = b = 1 170 | ok a is 1 and b == 1 171 | ok a == b 172 | ok a is b 173 | 174 | test '`!=` and `isnt` should be interchangeable', -> 175 | a = 0 176 | b = 1 177 | ok a isnt 1 and b != 0 178 | ok a != b 179 | ok a isnt b 180 | 181 | 182 | suite '[not] in/of', -> 183 | # - `in` should check if an array contains a value using `indexOf` 184 | # - `of` should check if a property is defined on an object using `in` 185 | 186 | test 'in, of', -> 187 | arr = [1] 188 | ok 0 of arr 189 | ok 1 in arr 190 | 191 | test 'not in, not of', -> 192 | arr = [1] 193 | ok 1 not of arr 194 | ok 0 not in arr 195 | 196 | test '`in` should be able to operate on an array literal', -> 197 | ok 2 in [0, 1, 2, 3] 198 | ok 4 not in [0, 1, 2, 3] 199 | arr = [0, 1, 2, 3] 200 | ok 2 in arr 201 | ok 4 not in arr 202 | # should cache the value used to test the array 203 | arr = [0] 204 | val = 0 205 | ok val++ in arr 206 | ok val++ not in arr 207 | val = 0 208 | ok val++ of arr 209 | ok val++ not of arr 210 | 211 | test '`in` with cache and `__indexOf` should work in argument lists', -> 212 | eq 1, [Object() in Array()].length 213 | 214 | test 'jashkenas/coffee-script#737: `in` should have higher precedence than logical operators', -> 215 | eq 1, 1 in [1] and 1 216 | 217 | test 'jashkenas/coffee-script#768: `in` should preserve evaluation order', -> 218 | share = 0 219 | a = -> share++ if share is 0 220 | b = -> share++ if share is 1 221 | c = -> share++ if share is 2 222 | ok a() not in [b(),c()] 223 | eq 3, share 224 | 225 | test 'jashkenas/coffee-script#1099: empty array after `in` should compile to `false`', -> 226 | eq 1, [5 in []].length 227 | eq false, do -> return 0 in [] 228 | 229 | test 'jashkenas/coffee-script#1354: optimized `in` checks should not happen when splats are present', -> 230 | a = [6, 9] 231 | eq 9 in [3, a...], true 232 | 233 | test 'jashkenas/coffee-script#1100: precedence in or-test compilation of `in`', -> 234 | ok 0 in [1 and 0] 235 | ok 0 in [1, 1 and 0] 236 | ok not (0 in [1, 0 or 1]) 237 | 238 | test 'jashkenas/coffee-script#1630: `in` should check `hasOwnProperty`', -> 239 | ok undefined not in {length: 1} 240 | 241 | test.skip 'jashkenas/coffee-script#1714: lexer bug with raw range `for` followed by `in`', -> # Currently syntax error. 242 | # 0 for [1..2] 243 | # ok not ('a' in ['b']) 244 | # 245 | # 0 for [1..2]; ok not ('a' in ['b']) 246 | # 247 | # 0 for [1..10] # comment ending 248 | # ok not ('a' in ['b']) 249 | 250 | test 'jashkenas/coffee-script#1099: statically determined `not in []` reporting incorrect result', -> 251 | ok 0 not in [] 252 | 253 | 254 | # Chained Comparison 255 | 256 | test 'chainable operators', -> 257 | ok 100 > 10 > 1 > 0 > -1 258 | ok -1 < 0 < 1 < 10 < 100 259 | 260 | test '`is` and `isnt` may be chained', -> 261 | ok true is not false is true is not false 262 | ok 0 is 0 isnt 1 is 1 263 | 264 | test 'different comparison operators (`>`,`<`,`is`,etc.) may be combined', -> 265 | ok 1 < 2 > 1 266 | ok 10 < 20 > 2+3 is 5 267 | 268 | test 'some chainable operators can be negated by `unless`', -> 269 | ok (true unless 0==10!=100) 270 | 271 | test 'operator precedence: `|` lower than `<`', -> 272 | eq 1, 1 | 2 < 3 < 4 273 | 274 | test 'preserve references', -> 275 | a = b = c = 1 276 | # `a == b <= c` should become `a === b && b <= c` 277 | # (this test does not seem to test for this) 278 | ok a == b <= c 279 | 280 | test 'chained operations should evaluate each value only once', -> 281 | a = 0 282 | ok 1 > a++ < 1 283 | 284 | test 'jashkenas/coffee-script#891: incorrect inversion of chained comparisons', -> 285 | ok (true unless 0 > 1 > 2) 286 | ok (true unless (NaN = 0/0) < 0/0 < NaN) 287 | 288 | test 'jashkenas/coffee-script#1234: Applying a splat to :: applies the splat to the wrong object', -> 289 | nonce = {} 290 | class C 291 | method: -> @nonce 292 | nonce: nonce 293 | 294 | arr = [] 295 | eq nonce, C::method arr... # should be applied to `C::` 296 | 297 | test 'jashkenas/coffee-script#1102: String literal prevents line continuation', -> 298 | eq "': '", '' + 299 | "': '" 300 | 301 | test 'jashkenas/coffee-script#1703, ---x is invalid JS', -> 302 | x = 2 303 | eq (- --x), -1 304 | 305 | test 'Regression with implicit calls against an indented assignment', -> 306 | eq 1, a = 307 | 1 308 | eq a, 1 309 | 310 | test 'jashkenas/coffee-script#2155: conditional assignment to a closure', -> 311 | x = null 312 | func = -> x ?= (-> if true then 'hi') 313 | func() 314 | eq x(), 'hi' 315 | 316 | test 'jashkenas/coffee-script#2197: Existential existential double trouble', -> 317 | counter = 0 318 | func = -> counter++ 319 | func()? ? 100 320 | eq counter, 1 321 | 322 | test '#85: operands of ExistsOp must be coerced to expressions', -> 323 | f = -> 324 | f (a ? a?.b()) 325 | f (a ? while 0 then) 326 | 327 | test '#89: extends operator has side effects and should not be optimised away', -> 328 | class A 329 | class B 330 | B extends A 331 | ok new B instanceof A 332 | 333 | # Loop Operators 334 | 335 | test '#195: "until" keyword should negate loop condition', -> 336 | x = 0 337 | x++ until x > 10 338 | eq x, 11 339 | -------------------------------------------------------------------------------- /test/optimisations.coffee: -------------------------------------------------------------------------------- 1 | suite 'Optimisations', -> 2 | 3 | # by definition, anything that is optimised away will not be detectable at 4 | # runtime, so we will have to do tests on the AST structure 5 | 6 | suite 'Non-optimisations', -> 7 | 8 | test 'do not optimise away indirect eval', -> 9 | do -> (1; eval) 'var thisShouldBeInTheGlobalScope = 0' 10 | eq 'number', typeof thisShouldBeInTheGlobalScope 11 | delete global.thisShouldBeInTheGlobalScope 12 | 13 | test 'do not optimise away declarations in conditionals', -> 14 | if 0 then a = 0 15 | eq undefined, a 16 | if 1 then 0 else b = 0 17 | eq undefined, b 18 | 19 | test 'do not optimise away declarations in while loops', -> 20 | while 0 then a = 0 21 | eq undefined, a 22 | 23 | test 'do not optimise away declarations in for-in loops', -> 24 | for a in [] then b = 0 25 | eq undefined, a 26 | eq undefined, b 27 | 28 | test 'do not optimise away declarations in for-of loops', -> 29 | for own a of {} then b = 0 30 | eq undefined, a 31 | eq undefined, b 32 | 33 | test 'do not optimise away declarations in logical not ops', -> 34 | not (a = 0) 35 | eq 0, a 36 | 37 | test '#71: assume JS literals have side effects, do not eliminate them', -> 38 | nonce = {} 39 | a = null 40 | `a = nonce` 41 | eq nonce, a 42 | 43 | test '#223: infinite loop when optimising ineffectful code with declarations followed by code with possible effects', -> 44 | b = [] 45 | c = -> 46 | 47 | a for a in b 48 | do c 49 | -------------------------------------------------------------------------------- /test/parser.coffee: -------------------------------------------------------------------------------- 1 | suite 'Parser', -> 2 | 3 | setup -> 4 | @shouldParse = (input) -> doesNotThrow -> parse input 5 | @shouldNotParse = (input) -> throws -> parse input 6 | @checkNodeRaw = (node, source) => 7 | rawAtOffset = source[node.offset...(node.offset + node.raw.length)] 8 | if node.raw isnt rawAtOffset 9 | fail "expected #{node.className} raw to equal #{JSON.stringify(rawAtOffset)}, but was #{JSON.stringify(node.raw)}" 10 | for own prop, child of node 11 | if Array.isArray child 12 | @checkNodeRaw element, source for element in child 13 | else if child instanceof CoffeeScript.Nodes.Nodes 14 | @checkNodeRaw child, source 15 | 16 | 17 | test 'empty program', -> @shouldParse '' 18 | test 'simple number', -> @shouldParse '0' 19 | 20 | test 'simple error', -> @shouldNotParse '0+' 21 | 22 | test 'jashkenas/coffee-script#1601', -> @shouldParse '@' 23 | 24 | test '#242: a very specifically spaced division, by itself', -> 25 | @shouldParse 'a[1]/ 1' 26 | 27 | test 'more oddly-spaced division', -> 28 | @shouldParse 'f(a /b)' 29 | 30 | test 'deeply nested expressions', -> 31 | @shouldParse '((((((((((((((((((((0))))))))))))))))))))' 32 | @shouldParse '++++++++++++++++++++0' 33 | 34 | test '#142 inconsistently indented object literal', -> 35 | inconsistently = 36 | indented: 37 | object: 38 | literal: yes 39 | eq inconsistently.indented.object.literal, yes 40 | 41 | test 'inconsistently indented if statement', -> 42 | nonceA = {} 43 | nonceB = {} 44 | 45 | fn = (b) -> 46 | if b 47 | nonceA 48 | else 49 | nonceB 50 | 51 | eq nonceA, fn 1 52 | eq nonceB, fn 0 53 | 54 | test 'inconsistent object literal dedent', -> 55 | @shouldNotParse ''' 56 | obj = 57 | foo: 5 58 | bar: 6 59 | ''' 60 | 61 | test 'inconsistent if statement dedent', -> 62 | @shouldNotParse ''' 63 | f = -> 64 | if something 65 | 'yup' 66 | else 67 | 'nope' 68 | ''' 69 | 70 | test 'windows line endings', -> 71 | @shouldParse 'if test\r\n fn a\r\n\r\n fn b' 72 | 73 | test 'strip leading spaces in heredocs', -> 74 | eq 'a\n b\nc', ''' 75 | a 76 | b 77 | c 78 | ''' 79 | eq 'a\n b\nc', ''' 80 | a 81 | b 82 | c 83 | ''' 84 | eq 'a\n b\nc', ''' 85 | a 86 | b 87 | c 88 | ''' 89 | eq ' a\nb\n c', ''' 90 | a 91 | b 92 | c 93 | ''' 94 | 95 | eq 'a\n b\nc', """ 96 | a 97 | b 98 | c 99 | """ 100 | eq 'a\n b\nc', """ 101 | a 102 | b 103 | c 104 | """ 105 | eq 'a\n b\nc', """ 106 | a 107 | b 108 | c 109 | """ 110 | eq ' a\nb\n c', """ 111 | a 112 | b 113 | c 114 | """ 115 | 116 | test 'strip leading spaces in heredocs with interpolations', -> 117 | a = 'd' 118 | b = 'e' 119 | c = 'f' 120 | 121 | eq 'd\n e\nf', """ 122 | #{a} 123 | #{b} 124 | #{c} 125 | """ 126 | eq 'd\n e\nf', """ 127 | #{a} 128 | #{b} 129 | #{c} 130 | """ 131 | eq 'd\n e\nf', """ 132 | #{a} 133 | #{b} 134 | #{c} 135 | """ 136 | eq ' d\ne\n f', """ 137 | #{a} 138 | #{b} 139 | #{c} 140 | """ 141 | 142 | eq "a\n e\nc", """ 143 | a 144 | #{b} 145 | c 146 | """ 147 | eq "a\n e\nc", """ 148 | a 149 | #{b} 150 | c 151 | """ 152 | eq "a\n e\nc", """ 153 | a 154 | #{b} 155 | c 156 | """ 157 | eq ' a\ne\n c', """ 158 | a 159 | #{b} 160 | c 161 | """ 162 | 163 | suite 'raw value preservation', -> 164 | 165 | test 'basic indentation', -> 166 | ast = parse ''' 167 | fn = -> 168 | body 169 | ''', raw: yes 170 | eq 'fn = ->\n body', ast.raw 171 | 172 | test 'numbers', -> 173 | ast = parse '0x0', raw: yes 174 | eq '0x0', ast.body.statements[0].raw 175 | 176 | test 'strings', -> 177 | ast = parse '"aaaaaa#{bbbbbb}cccccc"', raw: yes 178 | eq 'aaaaaa', ast.body.statements[0].left.left.raw 179 | eq 'cccccc', ast.body.statements[0].right.raw 180 | 181 | test 'empty string interpolation prefix', -> 182 | ast = parse '"#{0}"', raw: yes 183 | eq '', ast.body.statements[0].left.raw 184 | 185 | suite 'position/offset preservation', -> 186 | 187 | test 'basic indentation', -> 188 | source = ''' 189 | fn = -> 190 | body 191 | ''' 192 | ast = parse source, raw: yes 193 | @checkNodeRaw ast, source 194 | -------------------------------------------------------------------------------- /test/poe.coffee: -------------------------------------------------------------------------------- 1 | suite 'Edgar Allan Poe', -> 2 | 3 | test 'The Raven', -> 4 | CoffeeScript.parse ''' 5 | Once upon a midnight dreary while I pondered, weak and weary, 6 | Over many quaint and curious volume of forgotten lore - 7 | While I nodded, nearly napping, suddenly there came a tapping, 8 | As of some one gently rapping, rapping at my chamber door 9 | "'Tis some visiter". I muttered, "tapping at my chamber door" - 10 | "only this and nothing more." 11 | 12 | Ah distinctly I remember it was in the bleak December; 13 | And each separate dying ember wrought its ghost upon the floor. 14 | Eagerly I wished the morrow - vainly I had sought to borrow, 15 | From my books surcease of sorrow - sorrow For the lost Lenore - 16 | For the rare and radiant maiden whom the angels name Lenore - 17 | Nameless here For evermore 18 | ''' 19 | -------------------------------------------------------------------------------- /test/ranges.coffee: -------------------------------------------------------------------------------- 1 | suite 'Range Literals', -> 2 | 3 | test "basic inclusive ranges", -> 4 | arrayEq [1, 2, 3] , [1..3] 5 | arrayEq [0, 1, 2] , [0..2] 6 | arrayEq [0, 1] , [0..1] 7 | arrayEq [0] , [0..0] 8 | arrayEq [-1] , [-1..-1] 9 | arrayEq [-1, 0] , [-1..0] 10 | arrayEq [-1, 0, 1], [-1..1] 11 | 12 | test "basic exclusive ranges", -> 13 | arrayEq [1, 2, 3] , [1...4] 14 | arrayEq [0, 1, 2] , [0...3] 15 | arrayEq [0, 1] , [0...2] 16 | arrayEq [0] , [0...1] 17 | arrayEq [-1] , [-1...0] 18 | arrayEq [-1, 0] , [-1...1] 19 | arrayEq [-1, 0, 1], [-1...2] 20 | 21 | arrayEq [], [1...1] 22 | arrayEq [], [0...0] 23 | arrayEq [], [-1...-1] 24 | 25 | test "downward ranges", -> 26 | arrayEq [0..9], [9..0].reverse() 27 | arrayEq [5, 4, 3, 2] , [5..2] 28 | arrayEq [2, 1, 0, -1], [2..-1] 29 | 30 | arrayEq [3, 2, 1] , [3..1] 31 | arrayEq [2, 1, 0] , [2..0] 32 | arrayEq [1, 0] , [1..0] 33 | arrayEq [0] , [0..0] 34 | arrayEq [-1] , [-1..-1] 35 | arrayEq [0, -1] , [0..-1] 36 | arrayEq [1, 0, -1] , [1..-1] 37 | arrayEq [0, -1, -2], [0..-2] 38 | 39 | arrayEq [4, 3, 2], [4...1] 40 | arrayEq [3, 2, 1], [3...0] 41 | arrayEq [2, 1] , [2...0] 42 | arrayEq [1] , [1...0] 43 | arrayEq [] , [0...0] 44 | arrayEq [] , [-1...-1] 45 | arrayEq [0] , [0...-1] 46 | arrayEq [0, -1] , [0...-2] 47 | arrayEq [1, 0] , [1...-1] 48 | arrayEq [2, 1, 0], [2...-1] 49 | 50 | test "ranges with variables as enpoints", -> 51 | [a, b] = [1, 3] 52 | arrayEq [1, 2, 3], [a..b] 53 | arrayEq [1, 2] , [a...b] 54 | b = -2 55 | arrayEq [1, 0, -1, -2], [a..b] 56 | arrayEq [1, 0, -1] , [a...b] 57 | 58 | test "ranges with expressions as endpoints", -> 59 | [a, b] = [1, 3] 60 | arrayEq [2, 3, 4, 5, 6], [(a+1)..2*b] 61 | arrayEq [2, 3, 4, 5] , [(a+1)...2*b] 62 | 63 | test "large ranges are generated with looping constructs", -> 64 | down = [99..0] 65 | eq 100, (len = down.length) 66 | eq 0, down[len - 1] 67 | 68 | up = [0...100] 69 | eq 100, (len = up.length) 70 | eq 99, up[len - 1] 71 | 72 | test "#1012 slices with arguments object", -> 73 | expected = [0..9] 74 | argsAtStart = (-> [arguments[0]..9]) 0 75 | arrayEq expected, argsAtStart 76 | argsAtEnd = (-> [0..arguments[0]]) 9 77 | arrayEq expected, argsAtEnd 78 | argsAtBoth = (-> [arguments[0]..arguments[1]]) 0, 9 79 | arrayEq expected, argsAtBoth 80 | 81 | test '#257: do not reference `arguments` outside of function context', -> 82 | eq -1, (CoffeeScript.cs2js 'f [a..b]').indexOf 'arguments' 83 | neq -1, ((CoffeeScript.cs2js 'fn -> f arguments, [a..b]').replace 'arguments', 'a').indexOf 'arguments' 84 | 85 | test "indexing inclusive ranges", -> 86 | eq [1..4][0], 1 87 | eq [1..4][1], 2 88 | eq [1..4][2], 3 89 | eq [1..4][3], 4 90 | 91 | eq [-4..-1][0], -4 92 | eq [-4..-1][1], -3 93 | eq [-4..-1][2], -2 94 | eq [-4..-1][3], -1 95 | 96 | eq [1..10][-1], undefined 97 | eq [1..10][10], undefined 98 | 99 | eq [0..0][0], 0 100 | 101 | test "indexing exclusive ranges", -> 102 | eq [1...4][0], 1 103 | eq [1...4][1], 2 104 | eq [1...4][2], 3 105 | eq [1...4][3], undefined 106 | 107 | eq [-4...-1][0], -4 108 | eq [-4...-1][1], -3 109 | eq [-4...-1][2], -2 110 | eq [-4...-1][3], undefined 111 | 112 | eq [1...10][-1], undefined 113 | eq [1...10][10], undefined 114 | 115 | eq [0...0][0], undefined 116 | 117 | test "toString method invocation on ranges", -> 118 | eq [1..3].toString(), "1,2,3" 119 | eq [3..1].toString(), "3,2,1" 120 | eq [1..4].toString(), "1,2,3,4" 121 | eq [4..1].toString(), "4,3,2,1" 122 | 123 | eq [1...3].toString(), "1,2" 124 | eq [3...1].toString(), "3,2" 125 | eq [1...4].toString(), "1,2,3" 126 | eq [4...1].toString(), "4,3,2" 127 | 128 | eq [0..0].toString(), "0" 129 | -------------------------------------------------------------------------------- /test/regexps.coffee: -------------------------------------------------------------------------------- 1 | suite 'Regular Expressions', -> 2 | 3 | test 'differentiate regexps from division', -> 4 | a = -> 0 5 | a.valueOf = -> 1 6 | b = i = 1 7 | 8 | eq 1, a / b 9 | eq 1, a/ b 10 | eq 1, a/b 11 | eq 1, a / b / i 12 | eq 1, a/ b / i 13 | eq 1, a / b/ i 14 | eq 1, a / b /i 15 | eq 1, a/b / i 16 | eq 1, a/ b/ i 17 | eq 1, a/ b /i 18 | eq 1, a/b/ i 19 | eq 1, a/ b/i 20 | eq 1, a/b/i 21 | eq 1, b /= a 22 | eq 1, b/=a/i 23 | eq 1, b /=a/i 24 | eq 1, b /=a 25 | i=/a/i 26 | a[/a/] 27 | 28 | eq 0, a /b/i 29 | eq 0, a(/b/i) 30 | eq 0, a /b /i 31 | 32 | test 'regexps can start with spaces and = when unambiguous', -> 33 | a = -> 0 34 | eq 0, a(/ b/i) 35 | eq 0, a(/= b/i) 36 | eq 0, a a[/ b/i] 37 | eq 0, a(/ /) 38 | eq 1, +/ /.test ' ' 39 | eq 1, +/=/.test '=' 40 | 41 | test 'regexps can be empty', -> 42 | ok //.test '' 43 | 44 | test '#190: heregexen can contain 2 or fewer consecutive slashes', -> 45 | ok /// / // /// instanceof RegExp 46 | -------------------------------------------------------------------------------- /test/repl.coffee: -------------------------------------------------------------------------------- 1 | suite 'REPL', -> 2 | 3 | Stream = require 'stream' 4 | 5 | class MockInputStream extends Stream 6 | constructor: -> 7 | 8 | readable: true 9 | 10 | resume: -> 11 | 12 | emitLine: (val) -> 13 | @emit 'data', new Buffer "#{val}\n" 14 | 15 | class MockOutputStream extends Stream 16 | constructor: -> 17 | @written = [] 18 | 19 | writable: true 20 | 21 | write: (data) -> 22 | @written.push data 23 | 24 | lastWrite: (fromEnd) -> 25 | @written[@written.length - 1 - fromEnd].replace /\n$/, '' 26 | 27 | historyFile = path.join __dirname, 'coffee_history_test' 28 | console.dir historyFile 29 | process.on 'exit', -> fs.unlinkSync historyFile 30 | 31 | testRepl = (desc, fn, testFn = test) -> 32 | input = new MockInputStream 33 | output = new MockOutputStream 34 | repl = Repl.start {input, output, historyFile} 35 | testFn desc, -> fn input, output, repl 36 | repl.emit 'exit' 37 | 38 | testRepl.skip = (desc, fn) -> testRepl desc, fn, test.skip 39 | 40 | ctrlV = { ctrl: true, name: 'v'} 41 | 42 | 43 | testRepl 'starts with coffee prompt', (input, output) -> 44 | eq 'coffee> ', output.lastWrite 1 45 | 46 | testRepl 'writes eval to output', (input, output) -> 47 | input.emitLine '1+1' 48 | eq '2', output.lastWrite 1 49 | 50 | testRepl 'comments are ignored', (input, output) -> 51 | input.emitLine '1 + 1 #foo' 52 | eq '2', output.lastWrite 1 53 | 54 | testRepl 'output in inspect mode', (input, output) -> 55 | input.emitLine '"1 + 1\\n"' 56 | eq "'1 + 1\\n'", output.lastWrite 1 57 | 58 | testRepl "variables are saved", (input, output) -> 59 | input.emitLine 'foo = "foo"' 60 | input.emitLine 'foobar = "#{foo}bar"' 61 | eq "'foobar'", output.lastWrite 1 62 | 63 | testRepl 'empty command evaluates to undefined', (input, output) -> 64 | input.emitLine '' 65 | eq 'coffee> ', output.lastWrite 0 66 | eq 'coffee> ', output.lastWrite 2 67 | 68 | testRepl 'ctrl-v toggles multiline prompt', (input, output) -> 69 | input.emit 'keypress', null, ctrlV 70 | eq '------> ', output.lastWrite 0 71 | input.emit 'keypress', null, ctrlV 72 | eq 'coffee> ', output.lastWrite 0 73 | 74 | testRepl 'multiline continuation changes prompt', (input, output) -> 75 | input.emit 'keypress', null, ctrlV 76 | input.emitLine '' 77 | eq '....... ', output.lastWrite 0 78 | 79 | testRepl 'evaluates multiline', (input, output) -> 80 | # Stubs. Could assert on their use. 81 | output.cursorTo = output.clearLine = -> 82 | 83 | input.emit 'keypress', null, ctrlV 84 | input.emitLine 'do ->' 85 | input.emitLine ' 1 + 1' 86 | input.emit 'keypress', null, ctrlV 87 | eq '2', output.lastWrite 1 88 | 89 | testRepl 'variables in scope are preserved', (input, output) -> 90 | input.emitLine 'a = 1' 91 | input.emitLine 'do -> a = 2' 92 | input.emitLine 'a' 93 | eq '2', output.lastWrite 1 94 | 95 | testRepl 'existential assignment of previously declared variable', (input, output) -> 96 | input.emitLine 'a = null' 97 | input.emitLine 'a ?= 42' 98 | eq '42', output.lastWrite 1 99 | 100 | testRepl 'keeps running after runtime error', (input, output) -> 101 | input.emitLine 'a = b' 102 | ok 0 <= (output.lastWrite 1).indexOf 'ReferenceError: b is not defined' 103 | input.emitLine 'a' 104 | ok 0 <= (output.lastWrite 1).indexOf 'ReferenceError: a is not defined' 105 | input.emitLine '0' 106 | eq '0', output.lastWrite 1 107 | 108 | test 'reads history from persistence file', -> 109 | input = new MockInputStream 110 | output = new MockOutputStream 111 | fs.writeFileSync historyFile, '0\n1\n' 112 | repl = Repl.start {input, output, historyFile} 113 | arrayEq ['1', '0'], repl.rli.history 114 | 115 | testRepl.skip 'writes history to persistence file', (input, output, repl) -> # Fails in node <= 0.8. 116 | fs.writeFileSync historyFile, '' 117 | input.emitLine '2' 118 | input.emitLine '3' 119 | eq '2\n3\n', (fs.readFileSync historyFile).toString() 120 | 121 | testRepl '.history shows history', (input, output, repl) -> 122 | repl.rli.history = history = ['1', '2', '3'] 123 | fs.writeFileSync historyFile, "#{history.join '\n'}\n" 124 | input.emitLine '.history' 125 | eq (history.reverse().join '\n'), output.lastWrite 1 126 | 127 | testRepl.skip '.clear clears history', (input, output, repl) -> # Fails in node <= 0.8. 128 | input = new MockInputStream 129 | output = new MockOutputStream 130 | fs.writeFileSync historyFile, '' 131 | repl = Repl.start {input, output, historyFile} 132 | input.emitLine '0' 133 | input.emitLine '1' 134 | eq '0\n1\n', (fs.readFileSync historyFile).toString() 135 | #arrayEq ['1', '0'], repl.rli.history 136 | input.emitLine '.clear' 137 | eq '.clear\n', (fs.readFileSync historyFile).toString() 138 | #arrayEq ['.clear'], repl.rli.history 139 | -------------------------------------------------------------------------------- /test/scope.coffee: -------------------------------------------------------------------------------- 1 | suite 'Scope', -> 2 | 3 | test 'basics', -> 4 | a = true 5 | ok a 6 | fn = -> b = 0 7 | throws -> b 8 | eq 'undefined', typeof b 9 | 10 | test 'assignments within assignments', -> 11 | throws -> a 12 | throws -> b 13 | fn = -> 14 | a = b = 0 15 | eq 'number', typeof a 16 | eq 'number', typeof b 17 | throws -> a 18 | throws -> b 19 | 20 | test 'reassignments in a closure', -> 21 | a = false 22 | ok not a 23 | do -> a = true 24 | ok a 25 | 26 | b = false 27 | fn = -> b = true 28 | ok not b 29 | ok fn() 30 | ok b 31 | 32 | test 'vars are function-scoped, not block-scoped', -> 33 | fn = -> true 34 | if fn() 35 | a = 1 36 | else 37 | a = 0 38 | ok a 39 | 40 | test 'function params are added to scope', -> 41 | fn = (p) -> ok p 42 | fn true 43 | 44 | test 're-assignments of function params', -> 45 | nonce = {} 46 | fn = (p) -> 47 | eq nonce, p 48 | p = 0 49 | ok not p 50 | fn nonce 51 | 52 | test 're-assignments of function params in a loop', -> 53 | nonce = {} 54 | fn = (p) -> 55 | eq nonce, p 56 | a = 1 57 | while a-- 58 | p = 0 59 | ok not p 60 | fn nonce 61 | 62 | test 're-assignments of function params in a loop used as a value', -> 63 | nonce = {} 64 | fn = (p) -> 65 | eq nonce, p 66 | a = 1 67 | b = while a-- 68 | p = 0 69 | ok not p 70 | fn nonce 71 | 72 | test '#46: declarations in a loop used as a value', -> 73 | a = 0 74 | a = while a-- 75 | b = 1 76 | eq undefined, b 77 | 78 | test '#46: declarations in a switch used as a value', -> 79 | b = 0 80 | c = 1 81 | x = switch c 82 | when 0 then a = b 83 | else a = c 84 | eq 1, a 85 | 86 | test 'loop iterators available within the loop', -> 87 | for v, k in [1] 88 | ok v 89 | ok not k 90 | return 91 | 92 | test 'loop iterators available outside the loop (ew)', -> 93 | fn = -> 94 | for v, k in [1] 95 | fn() 96 | ok v 97 | ok not k 98 | 99 | test '`do` acts as `let`', -> 100 | outerNonce = nonce = {} 101 | do (nonce) -> 102 | eq outerNonce, nonce 103 | nonce = {} 104 | eq outerNonce, nonce 105 | -------------------------------------------------------------------------------- /test/scope.litcoffee: -------------------------------------------------------------------------------- 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 external scopes. 7 | 8 | Import the helpers we plan to use. 9 | 10 | {extend, last} = require './helpers' 11 | 12 | exports.Scope = class Scope 13 | 14 | The `root` is the top-level **Scope** object for a given file. 15 | 16 | @root: null 17 | 18 | Initialize a scope with its parent, for lookups up the chain, 19 | as well as a reference to the **Block** node it belongs to, which is 20 | where it should declare its variables, and a reference to the function that 21 | it belongs to. 22 | 23 | constructor: (@parent, @expressions, @method) -> 24 | @variables = [{name: 'arguments', type: 'arguments'}] 25 | @positions = {} 26 | Scope.root = this unless @parent 27 | 28 | Adds a new variable or overrides an existing one. 29 | 30 | add: (name, type, immediate) -> 31 | return @parent.add name, type, immediate if @shared and not immediate 32 | if Object::hasOwnProperty.call @positions, name 33 | @variables[@positions[name]].type = type 34 | else 35 | @positions[name] = @variables.push({name, type}) - 1 36 | 37 | When `super` is called, we need to find the name of the current method we're 38 | in, so that we know how to invoke the same method of the parent class. This 39 | can get complicated if super is being called from an inner function. 40 | `namedMethod` will walk up the scope tree until it either finds the first 41 | function object that has a name filled in, or bottoms out. 42 | 43 | namedMethod: -> 44 | return @method if @method?.name or !@parent 45 | @parent.namedMethod() 46 | 47 | Look up a variable name in lexical scope, and declare it if it does not 48 | already exist. 49 | 50 | find: (name) -> 51 | return yes if @check name 52 | @add name, 'var' 53 | no 54 | 55 | Reserve a variable name as originating from a function parameter for this 56 | scope. No `var` required for internal references. 57 | 58 | parameter: (name) -> 59 | return if @shared and @parent.check name, yes 60 | @add name, 'param' 61 | 62 | Just check to see if a variable has already been declared, without reserving, 63 | walks up to the root scope. 64 | 65 | check: (name) -> 66 | !!(@type(name) or @parent?.check(name)) 67 | 68 | Generate a temporary variable name at the given index. 69 | 70 | temporary: (name, index) -> 71 | if name.length > 1 72 | '_' + name + if index > 1 then index - 1 else '' 73 | else 74 | '_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a' 75 | 76 | Gets the type of a variable. 77 | 78 | type: (name) -> 79 | return v.type for v in @variables when v.name is name 80 | null 81 | 82 | If we need to store an intermediate result, find an available name for a 83 | compiler-generated variable. `_var`, `_var2`, and so on... 84 | 85 | freeVariable: (name, reserve=true) -> 86 | index = 0 87 | index++ while @check((temp = @temporary name, index)) 88 | @add temp, 'var', yes if reserve 89 | temp 90 | 91 | Ensure that an assignment is made at the top of this scope 92 | (or at the top-level scope, if requested). 93 | 94 | assign: (name, value) -> 95 | @add name, {value, assigned: yes}, yes 96 | @hasAssignments = yes 97 | 98 | Does this scope have any declared variables? 99 | 100 | hasDeclarations: -> 101 | !!@declaredVariables().length 102 | 103 | Return the list of variables first declared in this scope. 104 | 105 | declaredVariables: -> 106 | realVars = [] 107 | tempVars = [] 108 | for v in @variables when v.type is 'var' 109 | (if v.name.charAt(0) is '_' then tempVars else realVars).push v.name 110 | realVars.sort().concat tempVars.sort() 111 | 112 | Return the list of assignments that are supposed to be made at the top 113 | of this scope. 114 | 115 | assignedVariables: -> 116 | "#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned 117 | -------------------------------------------------------------------------------- /test/shakespeare.coffee: -------------------------------------------------------------------------------- 1 | suite 'William Shakespeare', -> 2 | 3 | test 'Hamlet', -> 4 | CoffeeScript.parse ''' 5 | To be or not to be, that is the question 6 | Whether tis Nobler in the mind to suffer 7 | The Slings and Arrows of outrageous Fortune, 8 | Or to take Arms against a Sea of troubles, 9 | And By opposing end them, to die, to sleep 10 | No more. and By a sleep, to say we end 11 | The heart-ache and the thousand Natural shocks 12 | That Flesh is heir to? 13 | ''' 14 | -------------------------------------------------------------------------------- /test/side-effects.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelficarra/CoffeeScriptRedux/a122b37ae8196820036769145149285e73d6df76/test/side-effects.coffee -------------------------------------------------------------------------------- /test/slices.coffee: -------------------------------------------------------------------------------- 1 | suite 'Slices', -> 2 | 3 | setup -> 4 | @shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 5 | 6 | test "basic slicing", -> 7 | arrayEq [7, 8, 9] , @shared[7..9] 8 | arrayEq [2, 3] , @shared[2...4] 9 | arrayEq [2, 3, 4, 5], @shared[2...6] 10 | 11 | test "slicing with variables as endpoints", -> 12 | [a, b] = [1, 4] 13 | arrayEq [1, 2, 3, 4], @shared[a..b] 14 | arrayEq [1, 2, 3] , @shared[a...b] 15 | 16 | test "slicing with expressions as endpoints", -> 17 | [a, b] = [1, 3] 18 | arrayEq [2, 3, 4, 5, 6], @shared[(a+1)..2*b] 19 | arrayEq [2, 3, 4, 5] , @shared[a+1...(2*b)] 20 | 21 | test "unbounded slicing", -> 22 | arrayEq [7, 8, 9] , @shared[7..] 23 | arrayEq [8, 9] , @shared[-2..] 24 | arrayEq [9] , @shared[-1...] 25 | arrayEq [0, 1, 2] , @shared[...3] 26 | arrayEq [0, 1, 2, 3], @shared[..-7] 27 | 28 | arrayEq @shared , @shared[..-1] 29 | arrayEq @shared[0..8], @shared[...-1] 30 | 31 | for a in [-@shared.length..@shared.length] 32 | arrayEq @shared[a..] , @shared[a...] 33 | for a in [-@shared.length+1...@shared.length] 34 | arrayEq @shared[..a][...-1] , @shared[...a] 35 | 36 | arrayEq [1, 2, 3], [1, 2, 3][..] 37 | 38 | test "#930, #835, #831, #746 #624: inclusive slices to -1 should slice to end", -> 39 | arrayEq @shared, @shared[0..-1] 40 | arrayEq @shared, @shared[..-1] 41 | arrayEq @shared.slice(1,@shared.length), @shared[1..-1] 42 | 43 | test "string slicing", -> 44 | str = "abcdefghijklmnopqrstuvwxyz" 45 | ok str[1...1] is "" 46 | ok str[1..1] is "b" 47 | ok str[1...5] is "bcde" 48 | ok str[0..4] is "abcde" 49 | ok str[-5..] is "vwxyz" 50 | 51 | test.skip "#1722: operator precedence in unbounded slice compilation", -> # Currently syntax error. 52 | # list = [0..9] 53 | # n = 2 # some truthy number in `list` 54 | # arrayEq [0..n], list[..n] 55 | # arrayEq [0..n], list[..n or 0] 56 | # arrayEq [0..n], list[..if n then n else 0] 57 | 58 | test "#2349: inclusive slicing to numeric strings", -> 59 | arrayEq [0, 1], [0..10][.."1"] 60 | -------------------------------------------------------------------------------- /test/splices.coffee.disabled: -------------------------------------------------------------------------------- 1 | suite 'Splices', -> 2 | 3 | test "basic splicing", -> 4 | ary = [0..9] 5 | ary[5..9] = [0, 0, 0] 6 | arrayEq [0, 1, 2, 3, 4, 0, 0, 0], ary 7 | 8 | ary = [0..9] 9 | ary[2...8] = [] 10 | arrayEq [0, 1, 8, 9], ary 11 | 12 | test "unbounded splicing", -> 13 | ary = [0..9] 14 | ary[3..] = [9, 8, 7] 15 | arrayEq [0, 1, 2, 9, 8, 7]. ary 16 | 17 | ary[...3] = [7, 8, 9] 18 | arrayEq [7, 8, 9, 9, 8, 7], ary 19 | 20 | ary[..] = [1, 2, 3] 21 | arrayEq [1, 2, 3], ary 22 | 23 | test "splicing with variables as endpoints", -> 24 | [a, b] = [1, 8] 25 | 26 | ary = [0..9] 27 | ary[a..b] = [2, 3] 28 | arrayEq [0, 2, 3, 9], ary 29 | 30 | ary = [0..9] 31 | ary[a...b] = [5] 32 | arrayEq [0, 5, 8, 9], ary 33 | 34 | test "splicing with expressions as endpoints", -> 35 | [a, b] = [1, 3] 36 | 37 | ary = [0..9] 38 | ary[ a+1 .. 2*b+1 ] = [4] 39 | arrayEq [0, 1, 4, 8, 9], ary 40 | 41 | ary = [0..9] 42 | ary[a+1...2*b+1] = [4] 43 | arrayEq [0, 1, 4, 7, 8, 9], ary 44 | 45 | test "splicing to the end, against a one-time function", -> 46 | ary = null 47 | fn = -> 48 | if ary 49 | throw 'err' 50 | else 51 | ary = [1, 2, 3] 52 | 53 | fn()[0..] = 1 54 | 55 | arrayEq ary, [1] 56 | 57 | test "the return value of a splice literal should be the RHS", -> 58 | ary = [0, 0, 0] 59 | eq (ary[0..1] = 2), 2 60 | 61 | ary = [0, 0, 0] 62 | eq (ary[0..] = 3), 3 63 | 64 | arrayEq [ary[0..0] = 0], [0] 65 | 66 | test "#1723: operator precedence in unbounded splice compilation", -> 67 | n = 4 # some truthy number in `list` 68 | 69 | list = [0..9] 70 | list[..n] = n 71 | arrayEq [n..9], list 72 | 73 | list = [0..9] 74 | list[..n or 0] = n 75 | arrayEq [n..9], list 76 | 77 | list = [0..9] 78 | list[..if n then n else 0] = n 79 | arrayEq [n..9], list 80 | -------------------------------------------------------------------------------- /test/string-interpolation.coffee: -------------------------------------------------------------------------------- 1 | suite 'String Interpolation', -> 2 | 3 | test 'interpolate one string variable', -> 4 | b = 'b' 5 | eq 'abc', "a#{b}c" 6 | 7 | test 'interpolate two string variables', -> 8 | b = 'b' 9 | c = 'c' 10 | eq 'abcd', "a#{b}#{c}d" 11 | 12 | test 'interpolate one numeric variable in the middle of the string', -> 13 | b = 0 14 | eq 'a0c', "a#{b}c" 15 | 16 | test 'interpolate one numeric variable at the start of the string', -> 17 | a = 0 18 | eq '0bc', "#{a}bc" 19 | 20 | test 'interpolate one numeric variable at the end of the string', -> 21 | c = 0 22 | eq 'ab0', "ab#{c}" 23 | 24 | test 'interpolations always produce a string', -> 25 | eq '0', "#{0}" 26 | eq 'string', typeof "#{0 + 1}" 27 | 28 | test 'interpolate a function call', -> 29 | b = -> 'b' 30 | eq 'abc', "a#{b()}c" 31 | eq 'abc', "a#{b 0}c" 32 | 33 | test 'interpolate a math expression (add)', -> 34 | eq 'a5c', "a#{2 + 3}c" 35 | 36 | test 'interpolate a math expression (subtract)', -> 37 | eq 'a2c', "a#{5 - 3}c" 38 | 39 | test 'interpolate a math expression (multiply)', -> 40 | eq 'a6c', "a#{2 * 3}c" 41 | 42 | test 'interpolate a math expression (divide)', -> 43 | eq 'a2c', "a#{4 / 2}c" 44 | 45 | test 'nested interpolation with double quotes', -> 46 | b = 'b' 47 | c = 'c' 48 | eq 'abcd', "a#{b + "#{c}"}d" 49 | 50 | test 'nested interpolation with single quotes (should not interpolate)', -> 51 | b = 'b' 52 | c = 'c' 53 | eq 'ab#{c}d', "a#{b + '#{c}'}d" 54 | 55 | test 'multiline interpolation', -> 56 | b = 'b' 57 | 58 | eq "a 59 | b 60 | c 61 | ", "a 62 | #{b} 63 | c 64 | " 65 | eq """ 66 | a 67 | b 68 | c 69 | """, """ 70 | a 71 | #{b} 72 | c 73 | """ 74 | -------------------------------------------------------------------------------- /test/truthiness.coffee: -------------------------------------------------------------------------------- 1 | suite 'Truthiness', -> 2 | 3 | setup -> 4 | @truthy = (ast) -> 5 | ok Optimiser.isTruthy ast 6 | ok not Optimiser.isFalsey ast 7 | @falsey = (ast) -> 8 | ok Optimiser.isFalsey ast 9 | ok not Optimiser.isTruthy ast 10 | @neither = (ast) -> 11 | ok not Optimiser.isTruthy ast 12 | ok not Optimiser.isFalsey ast 13 | 14 | test 'ints', -> 15 | @falsey new CS.Int 0 16 | @truthy new CS.Int 1 17 | @truthy new CS.Int 9e9 18 | 19 | test 'floats', -> 20 | @falsey new CS.Float 0.0 21 | @truthy new CS.Float 0.1 22 | @truthy new CS.Float 1.1 23 | @truthy new CS.Float 1.2e+3 24 | 25 | test 'strings', -> 26 | @falsey new CS.String '' 27 | @truthy new CS.String '0' 28 | 29 | test 'assignment', -> 30 | @truthy new CS.AssignOp (new CS.Identifier 'a'), new CS.Int 1 31 | @falsey new CS.AssignOp (new CS.Identifier 'a'), new CS.Int 0 32 | -------------------------------------------------------------------------------- /test/try-catch-finally.coffee: -------------------------------------------------------------------------------- 1 | suite 'Try/Catch/Finally', -> 2 | 3 | test 'simple try-catch-finally', -> 4 | t = c = f = 0 5 | try 6 | ++t 7 | throw {} 8 | catch e 9 | ++c 10 | finally 11 | ++f 12 | eq 1, t 13 | eq 1, c 14 | eq 1, f 15 | 16 | t = c = f = 0 17 | try 18 | ++t 19 | catch e 20 | # catch should not be executed if nothing is thrown 21 | ++c 22 | finally 23 | # but finally should always be executed 24 | ++f 25 | eq 1, t 26 | eq 0, c 27 | eq 1, f 28 | 29 | test 'try without catch just suppresses thrown errors', -> 30 | try throw {} 31 | 32 | test 'a try with a finally does not supress thrown errors', -> 33 | success = no 34 | try 35 | try throw {} finally success = no 36 | catch e 37 | success = yes 38 | ok success 39 | 40 | test 'catch variable is not let-scoped as in JS', -> 41 | nonce = {} 42 | try throw nonce 43 | catch e then 44 | eq nonce, e 45 | 46 | test 'destructuring in catch', -> 47 | nonce = {} 48 | try throw {nonce} 49 | catch {nonce: a} 50 | eq nonce, a 51 | 52 | test 'parameterless catch', -> 53 | try throw {} 54 | catch then ok yes 55 | 56 | test 'catch with empty body', -> 57 | try throw {} catch finally ok yes 58 | return 59 | --------------------------------------------------------------------------------