├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin ├── coffee └── tcoffee ├── examples ├── classes.coffee ├── function.coffee ├── generics.coffee ├── nullable.coffee ├── require-extension │ ├── bar.typed.coffee │ ├── baz.coffee │ └── foo.typed.coffee └── struct.coffee ├── lib ├── browser.js ├── cli.js ├── compiler.js ├── functional-helpers.js ├── helpers.js ├── js-nodes.js ├── module.js ├── nodes.js ├── optimiser.js ├── parser.js ├── preprocessor.js ├── register.js ├── repl.js ├── reporter.js ├── run.js ├── type-checker.js ├── type-helpers.js ├── type-resolver.js ├── type-scope.js ├── type-walker.js └── types.js ├── package.json ├── register.js ├── src ├── browser.coffee ├── cli.coffee ├── compiler.coffee ├── functional-helpers.coffee ├── grammar.pegjs ├── helpers.coffee ├── js-nodes.coffee ├── module.coffee ├── nodes.coffee ├── optimiser.coffee ├── parser.coffee ├── preprocessor.coffee ├── register.coffee ├── repl.coffee ├── reporter.coffee ├── run.coffee ├── type-checker.coffee ├── type-helpers.coffee ├── type-resolver.coffee ├── type-scope.coffee ├── type-walker.coffee └── types.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 ├── module.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 ├── type-checker.coffee ├── type-walker.coffee └── types.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib/bootstrap 3 | coffee-script-redux-*.tgz 4 | node_modules 5 | build 6 | scratch.* 7 | check 8 | -------------------------------------------------------------------------------- /.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.9" 5 | - "0.10" 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 --self 11 | PEGJS = node_modules/.bin/pegjs --cache --export-var 'module.exports' 12 | MOCHA = node_modules/.bin/mocha --self --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.pegjs bootstraps lib 34 | $(PEGJS) <"$<" >"$@.tmp" && mv "$@.tmp" "$@" 35 | lib/bootstrap/parser.js: src/grammar.pegjs lib/bootstrap 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 | TypedCoffeeScript 2 | ================================== 3 | 4 | [![Build Status](https://drone.io/github.com/mizchi/TypedCoffeeScript/status.png)](https://drone.io/github.com/mizchi/TypedCoffeeScript/latest) 5 | 6 | CoffeeScript with Types. 7 | 8 | This repository is heavily under development and unstable. See below milestone. 9 | 10 | ## Concepts 11 | 12 | * Structual Subtyping 13 | * Superset of CoffeeScript 14 | * Easy to replace coffee (pass unannotated coffee) 15 | * Pessimistic type interfaces 16 | 17 | ## What is pessimistic type interface? 18 | 19 | To pass dynamic type system, TypedCoffeeScript expects symbol to `implicit` node by default. If compiler compares implicit node type and implicit node type and fails, it recover to `implicit` `Any` automatically. 20 | 21 | ## Getting Started 22 | 23 | Install and run! 24 | 25 | ``` 26 | $ npm install -g typed-coffee-script 27 | $ tcoffee -c foo.typed.coffee # compile 28 | $ tcoffee foo.typed.coffee # execute 29 | $ tcoffee # repl 30 | ``` 31 | 32 | ### Extensions you should know about TypedCoffeeScript 33 | 34 | - `.tcoffee` and `.typed.coffee` are compiled by TypedCoffeeScript compiler. 35 | - Compiler uses jashkenas/coffeescript in `require('./foo.coffee')` by default. 36 | - if you want to compile `.coffee` with TypedCoffeeScript, add `--self` option. 37 | 38 | ## Project Status 39 | 40 | Current biggest issues is implementation of typescript d.ts importer. 41 | 42 | TypeScript AST parser is ready. [mizchi/dts-parser](https://github.com/mizchi/dts-parser "mizchi/dts-parser") 43 | 44 | ### Current Tasks(v0.12) 45 | 46 | - module system 47 | - robust namespace resolver 48 | - splats argument such as `( args...: T[] ) -> ` 49 | - this scope in bound function 50 | 51 | #### Wip 52 | 53 | - TypeScript `*.d.ts` importer 54 | - typealias such as `typealias Bar = Foo[]` 55 | 56 | ## Milestone 57 | 58 | ### `v0.13` 59 | 60 | - Be stable(RC for 1.0) 61 | - Add more tests. 62 | - Coverage of types to symbol 63 | - Infer super arguments in class 64 | - (Fix CoffeeScriptRedux bugs if I can) 65 | 66 | ## Known bugs 67 | 68 | - Compiler can't resolve module namespace when namespace has more than three dots, such as `A.B.C.d` 69 | - Take over all coffee-script-redux problems 70 | - super with member access `super().member` 71 | - object literal parsing in class field 72 | 73 | ## How to contribute 74 | 75 | You can use this compiler without type annotation. All test by `CoffeeScriptRedux` passed. 76 | 77 | If you encounter bugs, such as type interface... parser..., please report as github issues or pull request to me. I also welcome new syntax proposal. 78 | 79 | I DON'T reccomend to use in production yet. 80 | 81 | ## CHANGE LOG 82 | 83 | ### `v0.11` 84 | 85 | - Generics 86 | - TypeArgument 87 | - Fix examples 88 | - Recognise extensions in require 89 | - Runnable by `tcoffee foo.typed.coffee` that has `require` 90 | - Class static member type interface 91 | - Struct with implements 92 | 93 | ### `v0.10` 94 | 95 | - Rewrite internal AST and type interfaces 96 | - Add new command line interface 97 | - Refactor 98 | - Nullable 99 | - MemberAccess in struct definition 100 | - Infer fuction return type with `return` in Block 101 | - Destructive Assignment 102 | - self hosting 103 | 104 | ### `~v0.9` 105 | 106 | - Implement basic concepts 107 | 108 | ## Examples 109 | 110 | ### Assigment with type 111 | 112 | ```coffee 113 | n :: Int = 3 114 | ``` 115 | 116 | ### Pre defined symbol 117 | 118 | ```coffee 119 | x :: Number 120 | x = 3.14 121 | ``` 122 | 123 | ### Nullable 124 | 125 | ```coffee 126 | x :: Number? 127 | x = 3.14 128 | x = null 129 | ``` 130 | 131 | ### Typed Array 132 | 133 | ```coffee 134 | list :: Int[] = [1..10] 135 | listWithNull :: Int?[] = [1, null, 3] 136 | ``` 137 | 138 | In `v0.10`, imperfect to struct. 139 | 140 | ### Struct 141 | 142 | ```coffee 143 | struct Point 144 | @name :: String 145 | x :: Number 146 | y :: Number 147 | p :: Point = {x: 3, y: 3} 148 | name :: String = Point.name 149 | 150 | struct Point3d implements Point 151 | z :: Number 152 | ``` 153 | 154 | ### Module 155 | 156 | TypedCoffeeScript has module system like TypeScript 157 | 158 | ```coffee 159 | module A.B 160 | class @C 161 | a :: Int 162 | abc :: A.B.C = new A.B.C 163 | ``` 164 | 165 | ### Typed Function 166 | 167 | ```coffee 168 | # pre define 169 | f1 :: Int -> Int 170 | f1 = (n) -> n 171 | 172 | # annotation 173 | f2 :: Number -> Point = (n) -> x: n, y: n * 2 174 | 175 | # multi arguments 176 | f3 :: (Int, Int) -> Int = (m, n) -> m * n 177 | 178 | # another form of arguments 179 | f4 :: Int * Int -> Int = (m, n) -> m * n 180 | 181 | # partial applying 182 | fc :: Int -> Int -> Int 183 | fc = (m) -> (n) -> m * n 184 | ``` 185 | 186 | ### Class with this scope 187 | 188 | ```coffee 189 | class X 190 | # bound to this 191 | num :: Number 192 | f :: Number -> Number 193 | 194 | f: (n) -> 195 | @num = n 196 | 197 | x :: X = new X 198 | n :: Number = x.f 3 199 | ``` 200 | 201 | ### Class with implements 202 | 203 | ```coffee 204 | class Point 205 | x :: Int 206 | y :: Int 207 | 208 | struct Size 209 | width :: Int 210 | height :: Int 211 | 212 | class Entity extends Point implements Size 213 | e :: {x :: Int, width :: Int} = new Entity 214 | ``` 215 | 216 | ### Generics and type arguments 217 | 218 | ```coffee 219 | # struct 220 | struct Value 221 | value :: U 222 | struct Id 223 | id :: Value 224 | obj :: Id = 225 | id: 226 | value: 'value' 227 | 228 | # function type arguments 229 | map :: T[] * (T -> U) -> U[] 230 | map = (list, fn) -> 231 | for i in list 232 | fn(i) 233 | list :: String[] = map [1..10], (n) -> 'i' 234 | 235 | # class type arguments 236 | class Class 237 | f :: Int -> Int 238 | constructor :: A -> () 239 | constructor: (a) -> 240 | c = new Class(1) 241 | ``` 242 | 243 | Forked from CoffeeScript II: The Wrath of Khan 244 | ================================== 245 | 246 | ``` 247 | { 248 | } } { 249 | { { } } 250 | } }{ { 251 | { }{ } } _____ __ __ 252 | ( }{ }{ { ) / ____| / _|/ _| 253 | .- { { } { }} -. | | ___ | |_| |_ ___ ___ 254 | ( ( } { } { } } ) | | / _ \| _| _/ _ \/ _ \ 255 | |`-..________ ..-'| | |___| (_) | | | || __/ __/ 256 | | | \_____\___/|_| |_| \___|\___| .-''-. 257 | | ;--. .' .-. ) 258 | | (__ \ _____ _ _ / .' / / 259 | | | ) ) / ____| (_) | | (_/ / / 260 | | |/ / | (___ ___ _ __ _ _ __ | |_ / / 261 | | ( / \___ \ / __| '__| | '_ \| __| / / 262 | | |/ ____) | (__| | | | |_) | |_ . ' 263 | | | |_____/ \___|_| |_| .__/ \__| / / _.-') 264 | `-.._________..-' | | .' ' _.'.-'' 265 | |_| / /.-'_.' 266 | / _.' 267 | ( _.-' 268 | ``` 269 | 270 | ### Status 271 | 272 | Complete enough to use for nearly every project. See the [roadmap to 2.0](https://github.com/michaelficarra/CoffeeScriptRedux/wiki/Roadmap). 273 | 274 | ### Getting Started 275 | 276 | npm install -g coffee-script-redux 277 | coffee --help 278 | coffee --js output.js 279 | 280 | Before transitioning from Jeremy's compiler, see the 281 | [intentional deviations from jashkenas/coffee-script](https://github.com/michaelficarra/CoffeeScriptRedux/wiki/Intentional-Deviations-From-jashkenas-coffee-script) 282 | wiki page. 283 | 284 | ### Development 285 | 286 | git clone git://github.com/michaelficarra/CoffeeScriptRedux.git && cd CoffeeScriptRedux && npm install 287 | make clean && git checkout -- lib && make -j build && make test 288 | 289 | ### Notable Contributors 290 | 291 | I'd like to thank the following financial contributors for their large 292 | donations to [the Kickstarter project](http://www.kickstarter.com/projects/michaelficarra/make-a-better-coffeescript-compiler) 293 | that funded the initial work on this compiler. 294 | Together, you donated over $10,000. Without you, I wouldn't have been able to do this. 295 | 296 | * [Groupon](http://groupon.com/), who is generously allowing me to work in their offices 297 | * [Trevor Burnham](http://trevorburnham.com) 298 | * [Shopify](http://www.shopify.com) 299 | * [Abakas](http://abakas.com) 300 | * [37signals](http://37signals.com) 301 | * [Brightcove](http://www.brightcove.com) 302 | * [Gaslight](http://gaslight.co) 303 | * [Pantheon](https://www.getpantheon.com) 304 | * Benbria 305 | * Sam Stephenson 306 | * Bevan Hunt 307 | * Meryn Stol 308 | * Rob Tsuk 309 | * Dion Almaer 310 | * Andrew Davey 311 | * Thomas Burleson 312 | * Michael Kedzierski 313 | * Jeremy Kemper 314 | * Kyle Cordes 315 | * Jason R. Lauman 316 | * Martin Drenovac (Envizion Systems - Aust) 317 | * Julian Bilcke 318 | * Michael Edmondson 319 | 320 | And of course, thank you [Jeremy](https://github.com/jashkenas) (and all the other 321 | [contributors](https://github.com/jashkenas/coffee-script/graphs/contributors)) 322 | for making [the original CoffeeScript compiler](https://github.com/jashkenas/coffee-script). 323 | -------------------------------------------------------------------------------- /bin/coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require(require('path').join(__dirname, '..', 'lib', 'cli')); 3 | -------------------------------------------------------------------------------- /bin/tcoffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require 'fs' 4 | path = require 'path' 5 | CoffeeScript = require '../lib/module' 6 | {exec, spawn} = require 'child_process' 7 | 8 | cscodegen = require 'cscodegen' 9 | escodegen = require 'escodegen' 10 | 11 | repl = require '../lib/repl' 12 | reporter = require '../lib/reporter' 13 | {debug} = require '../lib/helpers' 14 | 15 | {argv} = require('optimist') 16 | .boolean('compile') 17 | .boolean('bare') 18 | .boolean('scope') 19 | .boolean('self') 20 | .boolean('print') 21 | .alias('c', 'compile') 22 | .alias('e', 'execute') 23 | .alias('b', 'bare') 24 | .alias('o', 'out') 25 | .alias('j', 'join') 26 | .alias('d', 'debug') 27 | 28 | {runMain} = require '../lib/run' 29 | 30 | compileFromSourceToJS = (source, {parseOption, compileOption} = {}) -> 31 | cs_ast = CoffeeScript.parse source, parseOption ? { 32 | optimise: yes 33 | raw: true 34 | inputSource: source 35 | sourceMap: true 36 | literate 37 | } 38 | js_ast = CoffeeScript.compile cs_ast, compileOption ? bare: !!argv.bare 39 | escodegen.generate js_ast 40 | 41 | run = (fname, source, {parseOption, compileOption} = {}) -> 42 | cs_ast = CoffeeScript.parse source, parseOption ? { 43 | optimise: yes 44 | raw: true 45 | inputSource: source 46 | sourceMap: true 47 | literate 48 | } 49 | js_ast = CoffeeScript.compile cs_ast, compileOption ? bare: !!argv.bare 50 | js = escodegen.generate js_ast 51 | runMain source, js, js_ast, fname 52 | 53 | mode = switch 54 | when argv.c or argv.compile then 'compile' 55 | when argv.e or argv.execute then 'execute' 56 | when argv._.length > 0 then 'execute' 57 | else 'repl' 58 | 59 | moduleCode = 'var _include_,_module_;_module_=function(e){return function(t,n,r){var i,s,o,u,a,f,l;r==null&&(r=o),o=(f=typeof window!=="undefined"&&window!==null?window:global)!=null?f:e,r==null&&(r=o),i=[],l=t.split(".");for(u=0,a=l.length;u 62 | c = new Code 63 | from: (c.from for c in codes) 64 | to: to 65 | code: (c.code for c in codes).join('\n') 66 | c 67 | 68 | constructor: ({@from, @to, @code}) -> 69 | 70 | writeSync: -> 71 | err = fs.writeFileSync @to, @code 72 | throw err if err 73 | console.log 'compile:', @from, '->', @to 74 | 75 | write: (callback) -> 76 | if global._root_.hasModule 77 | @code = moduleCode + '\n' + @code 78 | fs.writeFile @to, @code, (err) => 79 | throw err if err 80 | console.log 'compile:', @from, '->', @to 81 | callback?() 82 | 83 | switch mode 84 | when 'compile' 85 | {Preprocessor} = require '../lib/preprocessor' 86 | 87 | literate = argv.literate 88 | filepaths = argv._ 89 | 90 | result = "" 91 | 92 | codes = [] 93 | for fpath in filepaths 94 | # TODO: check existence 95 | source = fs.readFileSync(fpath).toString() 96 | # preprocessed = Preprocessor.process source, {literate} 97 | try 98 | cs_ast = CoffeeScript.parse source, { 99 | optimise: no 100 | raw: true 101 | sourceMap: true 102 | inputSource: source 103 | literate 104 | } 105 | catch e 106 | console.error 'Error at', fpath 107 | throw e 108 | process.exit(1) 109 | 110 | if argv.csast 111 | console.log cs_ast 112 | return 113 | 114 | if argv.cscodegen 115 | console.log cscodegen.generate cs_ast 116 | return 117 | 118 | js_ast = CoffeeScript.compile cs_ast, bare: !!argv.bare 119 | code = escodegen.generate js_ast 120 | 121 | extReplacer = (name) -> 122 | name 123 | .replace('.typed.coffee', '.js') 124 | .replace('.tcoffee', '.js') 125 | .replace('.coffee', '.js') 126 | 127 | outpath = 128 | if argv.out 129 | name = extReplacer path.basename(fpath) 130 | path.join (argv.out or ''), name 131 | else 132 | extReplacer path.basename(fpath) 133 | codes.push new Code 134 | from: fpath 135 | to: outpath 136 | code: code 137 | 138 | # if argv.join and argv.browserify # TODO 139 | if argv.join 140 | outpath = path.join(argv.out or '', argv.join) 141 | joined = Code.join(codes, outpath) 142 | joined.write() 143 | else 144 | for code in codes 145 | if argv.print 146 | console.log code.code 147 | else 148 | code.write() 149 | 150 | when 'execute' 151 | fpath = argv.execute ? argv._[0] 152 | source = fs.readFileSync(fpath).toString() 153 | run fpath, '\`' + moduleCode + '\`\n' + source 154 | 155 | when 'repl' 156 | repl.start() 157 | -------------------------------------------------------------------------------- /examples/classes.coffee: -------------------------------------------------------------------------------- 1 | class Person 2 | name :: String 3 | age :: Int 4 | 5 | constructor :: String * Int -> () 6 | constructor: (name :: String, age :: Int) -> 7 | @name = name 8 | @age = age 9 | 10 | a :: Person = new Person 'mizchi', 26 11 | name :: String = a.name 12 | 13 | class Point 14 | x :: Int 15 | y :: Int 16 | 17 | struct Size 18 | width :: Int 19 | height :: Int 20 | 21 | class Region extends Point implements Size 22 | region :: {x :: Int, width :: Int} = new Region 23 | 24 | # class type arguments 25 | class Class 26 | f :: Int -> Int 27 | constructor :: A -> () 28 | constructor: (a) -> 29 | c = new Class(1) 30 | -------------------------------------------------------------------------------- /examples/function.coffee: -------------------------------------------------------------------------------- 1 | # basic function 2 | add :: Int * Int -> Int 3 | add = (x, y) -> x + y 4 | add 3, 5 5 | ret :: Int = add 3, 5 6 | 7 | # partial application 8 | partial :: Int -> Int -> Int 9 | partial = (m) -> (n) -> m + n 10 | partial(3)(2) 11 | 12 | # generics 13 | map :: T[] * (T -> U) -> U[] 14 | map = (list, fn) -> 15 | for i in list 16 | fn(i) 17 | list :: String[] = map [1..10], (n) -> '' 18 | 19 | # nullable return with null 20 | nullableFunc :: Int -> Int? 21 | nullableFunc = (n) -> 22 | if n > 10 23 | return n 24 | else 25 | return null 26 | 3 27 | nullableFunc 5 -------------------------------------------------------------------------------- /examples/generics.coffee: -------------------------------------------------------------------------------- 1 | # struct 2 | struct Value 3 | value :: U 4 | struct Id 5 | id :: Value 6 | obj :: Id = 7 | id: 8 | value: '' 9 | 10 | # function type arguments 11 | map :: T[] * (T -> U) -> U[] 12 | map = (list, fn) -> 13 | for i in list 14 | fn(i) 15 | list :: String[] = map [1..10], (n) -> 'i' 16 | 17 | # class type arguments 18 | class Class 19 | f :: Int -> Int 20 | constructor :: A -> () 21 | constructor: (a) -> 22 | c = new Class(1) 23 | -------------------------------------------------------------------------------- /examples/nullable.coffee: -------------------------------------------------------------------------------- 1 | a :: Int? 2 | a = 1 3 | a = null 4 | 5 | b :: Int = 1 6 | # b = a # can't assign nullable to non-nullable 7 | a = b 8 | 9 | listWithNull :: Int?[] = [1, 2, 3, null, 5] 10 | listMaybeNull :: Int?[]? = null 11 | listMaybeNull = listWithNull 12 | 13 | intByConditional :: Int = 14 | if Math.random() > 0.5 15 | 1 16 | else 17 | 2 18 | 19 | # can't be Int 20 | nullableIntByConditional :: Int? = 21 | if Math.random() > 0.5 22 | 1 23 | 24 | intBySwitch :: Int = 25 | switch ~~(Math.random()* 10) 26 | when 1 27 | 1 28 | when 2 29 | 2 30 | else 31 | 3 32 | 33 | # can't be Int 34 | nullableIntBySwitch :: Int? = 35 | switch ~~(Math.random()* 10) 36 | when 1 37 | 1 38 | when 2 39 | 2 40 | -------------------------------------------------------------------------------- /examples/require-extension/bar.typed.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 3 2 | -------------------------------------------------------------------------------- /examples/require-extension/baz.coffee: -------------------------------------------------------------------------------- 1 | 1 2 | console.log 'baz' 3 | 2 4 | # struct A 5 | # a :: Int 6 | 7 | -------------------------------------------------------------------------------- /examples/require-extension/foo.typed.coffee: -------------------------------------------------------------------------------- 1 | console.log(2) 2 | require('../../lib/module').register() 3 | bar = require './bar' 4 | console.log bar 5 | baz = require './baz' 6 | -------------------------------------------------------------------------------- /examples/struct.coffee: -------------------------------------------------------------------------------- 1 | struct User 2 | name :: String 3 | 4 | user :: User = {name: 'hello'} 5 | 6 | struct Group 7 | id :: String 8 | users :: User[] 9 | 10 | users :: Group = users: [user, user], id: 'group1' 11 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 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/functional-helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 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.find = function (list, fn) { 22 | var e; 23 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 24 | e = list[i$]; 25 | if (fn(e)) 26 | return e; 27 | } 28 | return null; 29 | }; 30 | this.foldl = foldl = function (memo, list, fn) { 31 | var i; 32 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 33 | i = list[i$]; 34 | memo = fn(memo, i); 35 | } 36 | return memo; 37 | }; 38 | this.foldl1 = function (list, fn) { 39 | return foldl(list[0], list.slice(1), fn); 40 | }; 41 | this.map = map = function (list, fn) { 42 | var e; 43 | return function (accum$) { 44 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 45 | e = list[i$]; 46 | accum$.push(fn(e)); 47 | } 48 | return accum$; 49 | }.call(this, []); 50 | }; 51 | this.concat = concat = function (list) { 52 | var cache$; 53 | return (cache$ = []).concat.apply(cache$, [].slice.call(list)); 54 | }; 55 | this.concatMap = function (list, fn) { 56 | return concat(map(list, fn)); 57 | }; 58 | this.intersect = function (listA, listB) { 59 | var a; 60 | return function (accum$) { 61 | for (var i$ = 0, length$ = listA.length; i$ < length$; ++i$) { 62 | a = listA[i$]; 63 | if (!in$(a, listB)) 64 | continue; 65 | accum$.push(a); 66 | } 67 | return accum$; 68 | }.call(this, []); 69 | }; 70 | this.difference = function (listA, listB) { 71 | var a; 72 | return function (accum$) { 73 | for (var i$ = 0, length$ = listA.length; i$ < length$; ++i$) { 74 | a = listA[i$]; 75 | if (!!in$(a, listB)) 76 | continue; 77 | accum$.push(a); 78 | } 79 | return accum$; 80 | }.call(this, []); 81 | }; 82 | this.nub = nub = function (list) { 83 | var i, result; 84 | result = []; 85 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 86 | i = list[i$]; 87 | if (!!in$(i, result)) 88 | continue; 89 | result.push(i); 90 | } 91 | return result; 92 | }; 93 | this.union = function (listA, listB) { 94 | var b; 95 | return listA.concat(function (accum$) { 96 | for (var cache$ = nub(listB), i$ = 0, length$ = cache$.length; i$ < length$; ++i$) { 97 | b = cache$[i$]; 98 | if (!!in$(b, listA)) 99 | continue; 100 | accum$.push(b); 101 | } 102 | return accum$; 103 | }.call(this, [])); 104 | }; 105 | this.flip = function (fn) { 106 | return function (b, a) { 107 | return fn.call(this, a, b); 108 | }; 109 | }; 110 | this.owns = function (hop) { 111 | return function (a, b) { 112 | return hop.call(a, b); 113 | }; 114 | }({}.hasOwnProperty); 115 | this.span = span = function (list, f) { 116 | var cache$, ys, zs; 117 | if (list.length === 0) { 118 | return [ 119 | [], 120 | [] 121 | ]; 122 | } else if (f(list[0])) { 123 | cache$ = span(list.slice(1), f); 124 | ys = cache$[0]; 125 | zs = cache$[1]; 126 | return [ 127 | [list[0]].concat([].slice.call(ys)), 128 | zs 129 | ]; 130 | } else { 131 | return [ 132 | [], 133 | list 134 | ]; 135 | } 136 | }; 137 | this.divMod = function (a, b) { 138 | var c, div, mod; 139 | c = a % b; 140 | mod = c < 0 ? c + b : c; 141 | div = Math.floor(a / b); 142 | return [ 143 | div, 144 | mod 145 | ]; 146 | }; 147 | this.partition = function (list, fn) { 148 | var item, result; 149 | result = [ 150 | [], 151 | [] 152 | ]; 153 | for (var i$ = 0, length$ = list.length; i$ < length$; ++i$) { 154 | item = list[i$]; 155 | result[+!fn(item)].push(item); 156 | } 157 | return result; 158 | }; 159 | this.first = function (list) { 160 | return list[0]; 161 | }; 162 | this.last = function (list) { 163 | return list[list.length - 1]; 164 | }; 165 | function in$(member, list) { 166 | for (var i = 0, length = list.length; i < length; ++i) 167 | if (i in list && list[i] === member) 168 | return true; 169 | return false; 170 | } 171 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var beingDeclared, 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 | this.debug = function (flag) { 193 | var args, argv, i, util; 194 | args = arguments.length > 1 ? [].slice.call(arguments, 1) : []; 195 | util = require('util'); 196 | argv = require('optimist').alias('d', 'debug').argv; 197 | if (arguments.length > 1) { 198 | if (!argv.debug || flag === argv.debug) { 199 | return function (accum$) { 200 | for (var i$ = 0, length$ = args.length; i$ < length$; ++i$) { 201 | i = args[i$]; 202 | console.error('' + flag + ' ------- ['); 203 | console.error(util.inspect(i, false, null, true)); 204 | accum$.push(console.error('] ~~~~~~~~~ ')); 205 | } 206 | return accum$; 207 | }.call(this, []); 208 | } 209 | } else { 210 | return console.error(flag); 211 | } 212 | }; 213 | function in$(member, list) { 214 | for (var i = 0, length = list.length; i < length; ++i) 215 | if (i in list && list[i] === member) 216 | return true; 217 | return false; 218 | } 219 | -------------------------------------------------------------------------------- /lib/module.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var CoffeeScript, Compiler, cscodegen, escodegen, escodegenFormat, ext, formatParserError, Nodes, Optimiser, Parser, pkg, Preprocessor, reporter, TypeError, TypeWalker; 3 | formatParserError = require('./helpers').formatParserError; 4 | Nodes = require('./nodes'); 5 | Preprocessor = require('./preprocessor').Preprocessor; 6 | Parser = require('./parser'); 7 | TypeWalker = require('./type-walker'); 8 | Optimiser = require('./optimiser').Optimiser; 9 | Compiler = require('./compiler').Compiler; 10 | reporter = require('./reporter'); 11 | TypeError = require('./type-helpers').TypeError; 12 | cscodegen = function () { 13 | try { 14 | return require('cscodegen'); 15 | } catch (e$) { 16 | return; 17 | } 18 | }.call(this); 19 | escodegen = function () { 20 | try { 21 | return require('escodegen'); 22 | } catch (e$1) { 23 | return; 24 | } 25 | }.call(this); 26 | pkg = require('./../package.json'); 27 | escodegenFormat = { 28 | indent: { 29 | style: ' ', 30 | base: 0 31 | }, 32 | renumber: true, 33 | hexadecimal: true, 34 | quotes: 'auto', 35 | parentheses: false 36 | }; 37 | CoffeeScript = { 38 | CoffeeScript: CoffeeScript, 39 | Compiler: Compiler, 40 | Optimiser: Optimiser, 41 | Parser: Parser, 42 | Preprocessor: Preprocessor, 43 | Nodes: Nodes, 44 | VERSION: pkg.version, 45 | parse: function (coffee, options) { 46 | var e, parsed, preprocessed; 47 | if (null == options) 48 | options = {}; 49 | try { 50 | preprocessed = Preprocessor.process(coffee, { literate: options.literate }); 51 | parsed = Parser.parse(preprocessed, { 52 | raw: options.raw, 53 | inputSource: options.inputSource 54 | }); 55 | TypeWalker.checkNodes(parsed); 56 | if (reporter.has_errors()) 57 | throw new TypeError(reporter.report()); 58 | return options.optimise ? Optimiser.optimise(parsed) : parsed; 59 | } catch (e$2) { 60 | e = e$2; 61 | if (e instanceof TypeError) 62 | throw e.message; 63 | if (!(e instanceof Parser.SyntaxError)) 64 | throw e; 65 | throw new Error(formatParserError(preprocessed, e)); 66 | } 67 | }, 68 | compile: function (csAst, options) { 69 | return Compiler.compile(csAst, options).toBasicObject(); 70 | }, 71 | compileTypedToCS: function (csAst, options) { 72 | return cscodegen(csAst); 73 | }, 74 | cs: function (csAst, options) { 75 | }, 76 | jsWithSourceMap: function (jsAst, name, options) { 77 | var targetName; 78 | if (null == name) 79 | name = 'unknown'; 80 | if (null == options) 81 | options = {}; 82 | if (!(null != escodegen)) 83 | throw new Error('escodegen not found: run `npm install escodegen`'); 84 | if (!{}.hasOwnProperty.call(jsAst, 'type')) 85 | jsAst = jsAst.toBasicObject(); 86 | targetName = options.sourceMapFile || options.sourceMap && options.output.match(/^.*[\\\/]([^\\\/]+)$/)[1]; 87 | return escodegen.generate(jsAst, { 88 | comment: !options.compact, 89 | sourceMapWithCode: true, 90 | sourceMap: name, 91 | file: targetName || 'unknown', 92 | format: options.compact ? escodegen.FORMAT_MINIFY : null != options.format ? options.format : escodegenFormat 93 | }); 94 | }, 95 | js: function (jsAst, options) { 96 | return this.jsWithSourceMap(jsAst, null, options).code; 97 | }, 98 | sourceMap: function (jsAst, name, options) { 99 | return this.jsWithSourceMap(jsAst, name, options).map; 100 | }, 101 | cs2js: function (input, options) { 102 | var csAST, jsAST; 103 | if (null == options) 104 | options = {}; 105 | if (null != options.optimise) 106 | options.optimise; 107 | else 108 | options.optimise = true; 109 | csAST = CoffeeScript.parse(input, options); 110 | jsAST = CoffeeScript.compile(csAST, { bare: options.bare }); 111 | return CoffeeScript.js(jsAST, { compact: options.compact || options.minify }); 112 | } 113 | }; 114 | module.exports = CoffeeScript; 115 | if (null != (null != require.extensions ? require.extensions['.node'] : void 0)) { 116 | CoffeeScript.register = function () { 117 | return require('./register'); 118 | }; 119 | for (var cache$ = [ 120 | '.coffee', 121 | '.litcoffee', 122 | '.tcoffee', 123 | '.typed.coffee' 124 | ], i$ = 0, length$ = cache$.length; i$ < length$; ++i$) { 125 | ext = cache$[i$]; 126 | if (null != require.extensions[ext]) 127 | require.extensions[ext]; 128 | else 129 | require.extensions[ext] = function () { 130 | throw new Error('Use CoffeeScript.register() or require the coffee-script-redux/register module to require ' + ext + ' files.'); 131 | }; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/register.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var argv, child_process, coffeeBinary, CoffeeScript, compile, compileWithOriginalCoffee, 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 | argv = require('optimist').boolean('self').argv; 10 | compile = function (module, filename, opts) { 11 | var csAst, input, js, jsAst; 12 | input = fs.readFileSync(filename, 'utf8'); 13 | csAst = CoffeeScript.parse(input, opts); 14 | jsAst = CoffeeScript.compile(csAst); 15 | js = CoffeeScript.js(jsAst); 16 | return runModule(module, js, jsAst, filename); 17 | }; 18 | compileWithOriginalCoffee = function (module, filename, opts) { 19 | var input, js, OriginalCoffee; 20 | if (null == opts) 21 | opts = {}; 22 | OriginalCoffee = require('coffee-script'); 23 | input = fs.readFileSync(filename, 'utf8'); 24 | js = OriginalCoffee.compile(input, opts); 25 | return runModule(module, js, null, filename); 26 | }; 27 | require.extensions['.typed.coffee'] = function (module, filename) { 28 | return compile(module, filename, { 29 | raw: true, 30 | typeCheck: true 31 | }); 32 | }; 33 | require.extensions['.coffee'] = function (module, filename) { 34 | if (argv.self) { 35 | return compile(module, filename, { raw: true }); 36 | } else { 37 | return compileWithOriginalCoffee(module, filename, { 38 | raw: true, 39 | bare: argv.bare 40 | }); 41 | } 42 | }; 43 | require.extensions['.litcoffee'] = function (module, filename) { 44 | return compile(module, filename, { 45 | raw: true, 46 | literate: true 47 | }); 48 | }; 49 | require.extensions['.tcoffee'] = function (module, filename) { 50 | return compile(module, filename, { 51 | raw: true, 52 | typeCheck: true 53 | }); 54 | }; 55 | fork = child_process.fork; 56 | if (!fork.coffeePatched) { 57 | coffeeBinary = path.resolve('bin', 'tcoffee'); 58 | child_process.fork = function (file, args, options) { 59 | if (null == args) 60 | args = []; 61 | if (null == options) 62 | options = {}; 63 | if (in$(path.extname(file), [ 64 | '.coffee', 65 | '.litcoffee', 66 | '.tcoffee', 67 | '.typed.coffee' 68 | ])) { 69 | if (!Array.isArray(args)) { 70 | args = []; 71 | options = args || {}; 72 | } 73 | options.execPath || (options.execPath = coffeeBinary); 74 | } 75 | return fork(file, args, options); 76 | }; 77 | child_process.fork.coffeePatched = true; 78 | } 79 | delete require.cache[__filename]; 80 | function in$(member, list) { 81 | for (var i = 0, length = list.length; i < length; ++i) 82 | if (i in list && list[i] === member) 83 | return true; 84 | return false; 85 | } 86 | -------------------------------------------------------------------------------- /lib/repl.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var addHistory, addMultilineHandler, CoffeeScript, CS, fs, merge, nodeREPL, path, reporter, 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 | reporter = require('./reporter'); 11 | addMultilineHandler = function (repl) { 12 | var buffer, cache$, continuationPrompt, enabled, initialPrompt, inputStream, nodeLineListener, outputStream, rli; 13 | cache$ = repl; 14 | rli = cache$.rli; 15 | inputStream = cache$.inputStream; 16 | outputStream = cache$.outputStream; 17 | initialPrompt = repl.prompt.replace(/^[^> ]*/, function (x) { 18 | return x.replace(/./g, '-'); 19 | }); 20 | continuationPrompt = repl.prompt.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 | nodeLineListener(cmd); 34 | } 35 | }); 36 | return inputStream.on('keypress', function (char, key) { 37 | if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) 38 | return; 39 | if (enabled) { 40 | if (!buffer.match(/\n/)) { 41 | enabled = !enabled; 42 | rli.setPrompt(repl.prompt); 43 | rli.prompt(true); 44 | return; 45 | } 46 | if (null != rli.line && !rli.line.match(/^\s*$/)) 47 | return; 48 | enabled = !enabled; 49 | rli.line = ''; 50 | rli.cursor = 0; 51 | rli.output.cursorTo(0); 52 | rli.output.clearLine(1); 53 | buffer = buffer.replace(/\n/g, '\uff00'); 54 | rli.emit('line', buffer); 55 | buffer = ''; 56 | } else { 57 | enabled = !enabled; 58 | rli.setPrompt(initialPrompt); 59 | rli.prompt(true); 60 | } 61 | }); 62 | }; 63 | addHistory = function (repl, filename, maxSize) { 64 | var buffer, e, fd, lastLine, original_clear, readFd, size, stat; 65 | try { 66 | stat = fs.statSync(filename); 67 | size = Math.min(maxSize, stat.size); 68 | readFd = fs.openSync(filename, 'r'); 69 | buffer = new Buffer(size); 70 | if (size) 71 | fs.readSync(readFd, buffer, 0, size, stat.size - size); 72 | repl.rli.history = buffer.toString().split('\n').reverse(); 73 | if (stat.size > maxSize) 74 | repl.rli.history.pop(); 75 | if (repl.rli.history[0] === '') 76 | repl.rli.history.shift(); 77 | repl.rli.historyIndex = -1; 78 | } catch (e$) { 79 | e = e$; 80 | repl.rli.history = []; 81 | } 82 | fd = fs.openSync(filename, 'a'); 83 | lastLine = repl.rli.history[0]; 84 | repl.rli.addListener('line', function (code) { 85 | if (code && code !== lastLine) { 86 | lastLine = code; 87 | return fs.writeSync(fd, '' + code + '\n'); 88 | } 89 | }); 90 | repl.rli.on('exit', function () { 91 | return fs.closeSync(fd); 92 | }); 93 | original_clear = repl.commands['.clear'].action; 94 | repl.commands['.clear'].action = function () { 95 | repl.outputStream.write('Clearing history...\n'); 96 | repl.rli.history = []; 97 | fs.closeSync(fd); 98 | fd = fs.openSync(filename, 'w'); 99 | lastLine = void 0; 100 | return original_clear.call(this); 101 | }; 102 | return repl.commands['.history'] = { 103 | help: 'Show command history', 104 | action: function () { 105 | repl.outputStream.write('' + repl.rli.history.slice().reverse().join('\n') + '\n'); 106 | return repl.displayPrompt(); 107 | } 108 | }; 109 | }; 110 | module.exports = { 111 | start: function (opts) { 112 | var repl; 113 | if (null == opts) 114 | opts = {}; 115 | opts.prompt || (opts.prompt = 'tcoffee> '); 116 | if (null != opts.ignoreUndefined) 117 | opts.ignoreUndefined; 118 | else 119 | opts.ignoreUndefined = true; 120 | if (null != opts.historyFile) 121 | opts.historyFile; 122 | else 123 | opts.historyFile = path.join(process.env.HOME, '.coffee_history'); 124 | if (null != opts.historyMaxInputSize) 125 | opts.historyMaxInputSize; 126 | else 127 | opts.historyMaxInputSize = 10 * 1024; 128 | opts['eval'] || (opts['eval'] = function (input, context, filename, cb) { 129 | var err, inputAst, js, jsAst, transformedAst; 130 | input = input.replace(/\uFF00/g, '\n'); 131 | input = input.replace(/^\(([\s\S]*)\n\)$/m, '$1'); 132 | input = input.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'); 133 | if (/^\s*$/.test(input)) 134 | return cb(null); 135 | try { 136 | inputAst = CoffeeScript.parse(input, { 137 | filename: filename, 138 | raw: true 139 | }); 140 | transformedAst = new CS.AssignOp(new CS.Identifier('_'), inputAst.body); 141 | jsAst = CoffeeScript.compile(transformedAst, { 142 | bare: true, 143 | inScope: Object.keys(context) 144 | }); 145 | js = CoffeeScript.js(jsAst); 146 | return cb(null, vm.runInContext(js, context, filename)); 147 | } catch (e$) { 148 | err = e$; 149 | cb('\x1b[0;31m' + err.constructor.name + ': ' + err.message + '\x1b[0m'); 150 | return reporter.clean(); 151 | } 152 | }); 153 | repl = nodeREPL.start(opts); 154 | repl.on('exit', function () { 155 | return repl.outputStream.write('\n'); 156 | }); 157 | addMultilineHandler(repl); 158 | if (opts.historyFile) 159 | addHistory(repl, opts.historyFile, opts.historyMaxInputSize); 160 | return repl; 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /lib/reporter.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var debug, pj, render, Reporter, TypeError; 3 | pj = function () { 4 | try { 5 | return require('prettyjson'); 6 | } catch (e$) { 7 | return; 8 | } 9 | }.call(this); 10 | render = function (obj) { 11 | if (null != pj) 12 | return pj.render(obj); 13 | }; 14 | TypeError = require('./type-helpers').TypeError; 15 | debug = require('./helpers').debug; 16 | require('colors'); 17 | Reporter = function () { 18 | function Reporter() { 19 | var instance$; 20 | instance$ = this; 21 | this.add_warning = function (a, b) { 22 | return Reporter.prototype.add_warning.apply(instance$, arguments); 23 | }; 24 | this.clean = function () { 25 | return Reporter.prototype.clean.apply(instance$, arguments); 26 | }; 27 | this.add_error = function (a, b) { 28 | return Reporter.prototype.add_error.apply(instance$, arguments); 29 | }; 30 | this.errors = []; 31 | this.warnings = []; 32 | } 33 | Reporter.prototype.has_errors = function () { 34 | return this.errors.length > 0; 35 | }; 36 | Reporter.prototype.has_warnings = function () { 37 | return this.warnings.length > 0; 38 | }; 39 | Reporter.prototype.report = function () { 40 | var errors; 41 | errors = this.errors.map(function (param$) { 42 | var cache$, node, text; 43 | { 44 | cache$ = param$; 45 | node = cache$[0]; 46 | text = cache$[1]; 47 | } 48 | return 'L' + node.line + ' ' + node.raw.inverse + ' ' + text.red; 49 | }); 50 | return '' + errors.join('\n') + ''; 51 | }; 52 | Reporter.prototype.add_error = function (node, text) { 53 | return this.errors.push([ 54 | node, 55 | text 56 | ]); 57 | }; 58 | Reporter.prototype.clean = function () { 59 | return this.errors = []; 60 | }; 61 | Reporter.prototype.add_warning = function (node) { 62 | var ws; 63 | ws = arguments.length > 1 ? [].slice.call(arguments, 1) : []; 64 | return this.warnings.push([ 65 | node, 66 | ws.join('') 67 | ]); 68 | }; 69 | Reporter.prototype.dump = function (node, prefix) { 70 | var key, next, prop, type, val; 71 | if (null == prefix) 72 | prefix = ''; 73 | console.error(prefix + ('[' + node.name + ']')); 74 | console.error(prefix, ' vars::'); 75 | for (key in node._vars) { 76 | val = node._vars[key]; 77 | console.error(prefix, ' +', key, '::', JSON.stringify(val)); 78 | } 79 | console.error(prefix, ' types::'); 80 | for (var i$ = 0, length$ = node._types.length; i$ < length$; ++i$) { 81 | type = node._types[i$]; 82 | console.error(prefix, ' +', type.identifier.typeName); 83 | if (type.properties) 84 | for (var i$1 = 0, length$1 = type.properties.length; i$1 < length$1; ++i$1) { 85 | prop = type.properties[i$1]; 86 | console.error(prefix, ' @', prop.identifier.typeName, prop.typeAnnotation); 87 | } 88 | } 89 | return function (accum$) { 90 | for (var i$2 = 0, length$2 = node.nodes.length; i$2 < length$2; ++i$2) { 91 | next = node.nodes[i$2]; 92 | accum$.push(this.dump(next, prefix + ' ')); 93 | } 94 | return accum$; 95 | }.call(this, []); 96 | }; 97 | return Reporter; 98 | }(); 99 | module.exports = new Reporter; 100 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 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 | if (jsAst) 98 | Module._sourceMaps[filename] = function () { 99 | return '' + CoffeeScript.sourceMap(jsAst, filename); 100 | }; 101 | return module._compile(jsSource, filename); 102 | }; 103 | module.exports = { 104 | runMain: runMain, 105 | runModule: runModule 106 | }; 107 | -------------------------------------------------------------------------------- /lib/type-helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var ArrayInterface, clone, NumberInterface, ObjectInterface, rewrite; 3 | this.clone = clone = function (obj) { 4 | var flags, key, newInstance; 5 | if (!(null != obj) || typeof obj !== 'object') 6 | return obj; 7 | if (obj instanceof Date) 8 | return new Date(obj.getTime()); 9 | if (obj instanceof RegExp) { 10 | flags = ''; 11 | if (null != obj.global) 12 | flags += 'g'; 13 | if (null != obj.ignoreCase) 14 | flags += 'i'; 15 | if (null != obj.multiline) 16 | flags += 'm'; 17 | if (null != obj.sticky) 18 | flags += 'y'; 19 | return new RegExp(obj.source, flags); 20 | } 21 | newInstance = new obj.constructor; 22 | for (key in obj) { 23 | newInstance[key] = clone(obj[key]); 24 | } 25 | return newInstance; 26 | }; 27 | this.rewrite = rewrite = function (obj, replacer) { 28 | var key, val; 29 | if (typeof obj === 'string' || typeof obj === 'number') 30 | return; 31 | return function (accum$) { 32 | for (key in obj) { 33 | val = obj[key]; 34 | accum$.push(typeof val === 'string' ? null != replacer[val] ? obj[key] = replacer[val] : void 0 : val instanceof Object ? rewrite(val, replacer) : void 0); 35 | } 36 | return accum$; 37 | }.call(this, []); 38 | }; 39 | this.TypeError = function () { 40 | function TypeError(param$) { 41 | this.message = param$; 42 | } 43 | return TypeError; 44 | }(); 45 | NumberInterface = { 46 | toString: { 47 | name: 'function', 48 | 'arguments': [], 49 | returnType: 'String' 50 | } 51 | }; 52 | ArrayInterface = { 53 | length: 'Number', 54 | push: { 55 | name: 'function', 56 | 'arguments': ['T'], 57 | returnType: 'void' 58 | }, 59 | unshift: { 60 | name: 'function', 61 | 'arguments': ['T'], 62 | returnType: 'void' 63 | }, 64 | shift: { 65 | name: 'function', 66 | 'arguments': [], 67 | returnType: 'T' 68 | }, 69 | toString: { 70 | name: 'function', 71 | 'arguments': [], 72 | returnType: 'String' 73 | } 74 | }; 75 | ObjectInterface = function () { 76 | return { 77 | toString: { 78 | name: 'function', 79 | 'arguments': [], 80 | returnType: 'String' 81 | }, 82 | keys: { 83 | name: 'function', 84 | 'arguments': ['Any'], 85 | returnType: { array: 'String' } 86 | } 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /lib/type-resolver.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var _, ClassScope, extendFunctionType, extendIdentifierType, extendMembers, extendType, FunctionScope, resolveType, rewriteType, Scope; 3 | _ = require('lodash'); 4 | cache$ = require('./type-scope'); 5 | Scope = cache$.Scope; 6 | ClassScope = cache$.ClassScope; 7 | FunctionScope = cache$.FunctionScope; 8 | rewriteType = function (scope, node, from, to) { 9 | var ann, arg, i, n, prop, resolved, typeArgs; 10 | if (node.nodeType === 'identifier') { 11 | if (null != node.identifier && null != node.identifier.typeArguments ? node.identifier.typeArguments.length : void 0) { 12 | typeArgs = function (accum$) { 13 | for (var i$ = 0, length$ = node.identifier.typeArguments.length; i$ < length$; ++i$) { 14 | i = node.identifier.typeArguments[i$]; 15 | n = i$; 16 | resolved = resolveType(scope, i); 17 | accum$.push(resolved.typeAnnotation); 18 | } 19 | return accum$; 20 | }.call(this, []); 21 | ann = scope.getTypeByNode(from.typeAnnotation); 22 | if (ann) 23 | extendType(scope, ann, typeArgs); 24 | } 25 | if ((null != node.identifier ? node.identifier.typeRef : void 0) === from.identifier.typeRef) 26 | return node.identifier.typeRef = to.identifier.typeRef; 27 | } else if (node.nodeType === 'members') { 28 | return function (accum$1) { 29 | for (var i$1 = 0, length$1 = node.properties.length; i$1 < length$1; ++i$1) { 30 | prop = node.properties[i$1]; 31 | accum$1.push(function () { 32 | switch (prop.typeAnnotation.nodeType) { 33 | case 'functionType': 34 | rewriteType(scope, prop.typeAnnotation.returnType, from, to); 35 | return function (accum$2) { 36 | for (var i$2 = 0, length$2 = prop.typeAnnotation['arguments'].length; i$2 < length$2; ++i$2) { 37 | arg = prop.typeAnnotation['arguments'][i$2]; 38 | accum$2.push(rewriteType(scope, arg, from, to)); 39 | } 40 | return accum$2; 41 | }.call(this, []); 42 | case 'identifier': 43 | if (null != prop.typeAnnotation && null != prop.typeAnnotation.identifier && null != prop.typeAnnotation.identifier.typeArguments ? prop.typeAnnotation.identifier.typeArguments.length : void 0) { 44 | typeArgs = function (accum$3) { 45 | for (var i$3 = 0, length$3 = prop.typeAnnotation.identifier.typeArguments.length; i$3 < length$3; ++i$3) { 46 | i = prop.typeAnnotation.identifier.typeArguments[i$3]; 47 | n = i$3; 48 | resolved = resolveType(scope, i); 49 | accum$3.push(resolved.typeAnnotation); 50 | } 51 | return accum$3; 52 | }.call(this, []); 53 | ann = scope.getTypeByNode(prop.typeAnnotation); 54 | if (ann) 55 | extendType(scope, ann, typeArgs); 56 | } 57 | if ((null != prop.typeAnnotation.identifier ? prop.typeAnnotation.identifier.typeRef : void 0) === from.identifier.typeRef) 58 | return prop.typeAnnotation.identifier.typeRef = to.identifier.typeRef; 59 | case 'members': 60 | return rewriteType(scope, prop.typeAnnotation, from, to); 61 | } 62 | }.call(this)); 63 | } 64 | return accum$1; 65 | }.call(this, []); 66 | } 67 | }; 68 | extendIdentifierType = function (scope, node) { 69 | var ann, from, to; 70 | ann = scope.getTypeByIdentifier(node.identifier); 71 | if (ann.nodeType === 'identifier') { 72 | if (!(null != ann.typeAnnotation)) 73 | throw new Error('identifier with annotation required'); 74 | from = node; 75 | to = ann.typeAnnotation; 76 | return rewriteType(scope, node, from, to); 77 | } 78 | }; 79 | extendFunctionType = function (scope, node) { 80 | var arg; 81 | extendIdentifierType(scope, node.returnType); 82 | for (var i$ = 0, length$ = node['arguments'].length; i$ < length$; ++i$) { 83 | arg = node['arguments'][i$]; 84 | if (arg.nodeType === 'identifier') { 85 | extendIdentifierType(scope, arg); 86 | } else if (arg.nodeType === 'functionType') { 87 | extendFunctionType(scope, arg); 88 | } 89 | } 90 | return node; 91 | }; 92 | extendMembers = function (scope, node, givenArgs) { 93 | var ann, arg, givenArg, n, typeArgs, typeScope; 94 | typeScope = new Scope(scope); 95 | if (null != node.identifier && null != node.identifier.typeArguments ? node.identifier.typeArguments.length : void 0) 96 | for (var i$ = 0, length$ = node.identifier.typeArguments.length; i$ < length$; ++i$) { 97 | arg = node.identifier.typeArguments[i$]; 98 | n = i$; 99 | givenArg = givenArgs[n]; 100 | if (null != givenArg && null != givenArg.identifier && null != givenArg.identifier.typeArguments ? givenArg.identifier.typeArguments.length : void 0) { 101 | typeArgs = givenArg.identifier.typeArguments; 102 | if (ann = scope.getTypeByNode(givenArg)) 103 | extendType(scope, ann, typeArgs); 104 | } 105 | typeScope.addType({ 106 | nodeType: 'identifier', 107 | identifier: _.cloneDeep(arg.identifier), 108 | typeAnnotation: { 109 | nodeType: 'identifier', 110 | identifier: _.cloneDeep(givenArg.identifier) 111 | } 112 | }); 113 | } 114 | return function (accum$) { 115 | for (var i$1 = 0, length$1 = node.identifier.typeArguments.length; i$1 < length$1; ++i$1) { 116 | arg = node.identifier.typeArguments[i$1]; 117 | n = i$1; 118 | givenArg = givenArgs[n]; 119 | accum$.push(rewriteType(typeScope, node, arg, givenArg)); 120 | } 121 | return accum$; 122 | }.call(this, []); 123 | }; 124 | extendType = function (scope, node, givenArgs) { 125 | if (node.nodeType === 'members') { 126 | extendMembers(scope, node, givenArgs); 127 | } else if (node.nodeType === 'functionType') { 128 | extendFunctionType(scope, node); 129 | } 130 | return node; 131 | }; 132 | resolveType = function (scope, node) { 133 | var ret; 134 | if (node.nodeType === 'identifier') { 135 | ret = scope.getTypeByNode(node); 136 | if (null != node.identifier && null != node.identifier.typeArguments ? node.identifier.typeArguments.length : void 0) 137 | ret = extendType(scope, _.cloneDeep(ret), node.identifier.typeArguments); 138 | if (!ret) { 139 | if (node.nodeType === 'identifier') 140 | return node; 141 | throw 'Type: ' + util.inspect(node.identifier.typeRef) + ' is not defined'; 142 | } 143 | return ret; 144 | } else if (node.nodeType === 'primitiveIdentifier') { 145 | return node; 146 | } else if (node.nodeType === 'members') { 147 | return node; 148 | } else if (node.nodeType === 'functionType') { 149 | return node; 150 | } else { 151 | throw (null != node ? node.nodeType : void 0) + ' is not registered nodeType'; 152 | } 153 | }; 154 | module.exports = { 155 | resolveType: resolveType, 156 | extendType: extendType 157 | }; 158 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | // Generated by TypedCoffeeScript 0.12.1 2 | var _, clone, debug, ImplicitAny, initializeGlobalTypes, primitives, rewrite; 3 | debug = require('./helpers').debug; 4 | cache$ = require('./type-helpers'); 5 | clone = cache$.clone; 6 | rewrite = cache$.rewrite; 7 | _ = require('lodash'); 8 | ImplicitAny = { 9 | implicit: true, 10 | isPrimitive: true, 11 | nodeType: 'primitiveIdentifier', 12 | identifier: { typeRef: 'Any' } 13 | }; 14 | primitives = { 15 | AnyType: { 16 | nodeType: 'primitiveIdentifier', 17 | isPrimitive: true, 18 | identifier: { typeRef: 'Any' } 19 | }, 20 | StringType: { 21 | nodeType: 'primitiveIdentifier', 22 | isPrimitive: true, 23 | identifier: { typeRef: 'String' } 24 | }, 25 | BooleanType: { 26 | nodeType: 'primitiveIdentifier', 27 | isPrimitive: true, 28 | identifier: { typeRef: 'Boolean' } 29 | }, 30 | IntType: { 31 | nodeType: 'primitiveIdentifier', 32 | isPrimitive: true, 33 | identifier: { typeRef: 'Int' } 34 | }, 35 | FloatType: { 36 | nodeType: 'primitiveIdentifier', 37 | isPrimitive: true, 38 | identifier: { typeRef: 'Float' }, 39 | heritages: { 40 | extend: { 41 | nodeType: 'identifier', 42 | identifier: { typeRef: 'Int' } 43 | } 44 | } 45 | }, 46 | NumberType: { 47 | nodeType: 'primitiveIdentifier', 48 | isPrimitive: true, 49 | identifier: { typeRef: 'Number' }, 50 | heritages: { 51 | extend: { 52 | nodeType: 'identifier', 53 | identifier: { typeRef: 'Float' } 54 | } 55 | } 56 | }, 57 | NullType: { 58 | nodeType: 'primitiveIdentifier', 59 | isPrimitive: true, 60 | identifier: { typeRef: 'Null' } 61 | }, 62 | UndefinedType: { 63 | nodeType: 'primitiveIdentifier', 64 | isPrimitive: true, 65 | identifier: { typeRef: 'Undefined' } 66 | }, 67 | VoidType: { 68 | nodeType: 'primitiveIdentifier', 69 | isPrimitive: true, 70 | identifier: { typeRef: 'Void' } 71 | } 72 | }; 73 | initializeGlobalTypes = function (node) { 74 | node.addPrimitiveType(primitives.AnyType); 75 | node.addPrimitiveType(primitives.StringType); 76 | node.addPrimitiveType(primitives.IntType); 77 | node.addPrimitiveType(primitives.FloatType); 78 | node.addPrimitiveType(primitives.NumberType); 79 | node.addPrimitiveType(primitives.BooleanType); 80 | node.addPrimitiveType(primitives.NullType); 81 | node.addPrimitiveType(primitives.UndefinedType); 82 | return node.addPrimitiveType(primitives.VoidType); 83 | }; 84 | module.exports = { 85 | initializeGlobalTypes: initializeGlobalTypes, 86 | primitives: primitives, 87 | ImplicitAny: ImplicitAny 88 | }; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-coffee-script", 3 | "author": "mizchi", 4 | "version": "0.12.2", 5 | "homepage": "https://github.com/mizchi/TypedCoffeeScript", 6 | "bugs": "https://github.com/mizchi/TypedCoffeeScript/issues", 7 | "description": "Unfancy JavaScript with Types", 8 | "keywords": [ 9 | "typedcoffeescript", 10 | "coffeescript", 11 | "javascript", 12 | "language", 13 | "compiler" 14 | ], 15 | "main": "./lib/module", 16 | "bin": { 17 | "tcoffee": "./bin/tcoffee" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/michaelficarra/CoffeeScriptRedux.git" 22 | }, 23 | "scripts": { 24 | "build": "make -j build", 25 | "test": "make -j test" 26 | }, 27 | "devDependencies": { 28 | "cluster": "~0.7.7", 29 | "commonjs-everywhere": "~0.9.0", 30 | "grunt": "^0.4.5", 31 | "grunt-contrib-coffee": "^0.10.1", 32 | "mocha": "~1.12.0", 33 | "pegjs": "git+https://github.com/dmajda/pegjs.git#bea6b1fde74c8aebf802f9bcc3380c65b241e1b7", 34 | "semver": "~2.1.0" 35 | }, 36 | "dependencies": { 37 | "StringScanner": "~0.0.3", 38 | "coffee-script": "^1.7.1", 39 | "colors": "^0.6.2", 40 | "lodash": "^2.4.1", 41 | "nopt": "~2.1.2", 42 | "optimist": "^0.6.1" 43 | }, 44 | "optionalDependencies": { 45 | "prettyjson": "*", 46 | "esmangle": "~0.0.8", 47 | "source-map": "0.1.11", 48 | "escodegen": "~0.0.24", 49 | "cscodegen": "git://github.com/michaelficarra/cscodegen.git#73fd7202ac086c26f18c9d56f025b18b3c6f5383" 50 | }, 51 | "engines": { 52 | "node": "0.8.x || 0.10.x" 53 | }, 54 | "licenses": [ 55 | { 56 | "type": "3-clause BSD", 57 | "url": "https://raw.github.com/michaelficarra/CoffeeScriptRedux/master/LICENSE" 58 | } 59 | ], 60 | "license": "3-clause BSD" 61 | } 62 | -------------------------------------------------------------------------------- /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/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 | @find = (list, fn) -> 12 | for e in list 13 | return e if fn(e) 14 | null 15 | 16 | @foldl = foldl = (memo, list, fn) -> 17 | for i in list 18 | memo = fn memo, i 19 | memo 20 | 21 | @foldl1 = (list, fn) -> foldl list[0], list[1..], fn 22 | 23 | @map = map = (list, fn) -> fn e for e in list 24 | 25 | @concat = concat = (list) -> [].concat list... 26 | 27 | @concatMap = (list, fn) -> concat map list, fn 28 | 29 | @intersect = (listA, listB) -> a for a in listA when a in listB 30 | 31 | @difference = (listA, listB) -> a for a in listA when a not in listB 32 | 33 | @nub = nub = (list) -> 34 | result = [] 35 | result.push i for i in list when i not in result 36 | result 37 | 38 | @union = (listA, listB) -> 39 | listA.concat (b for b in (nub listB) when b not in listA) 40 | 41 | @flip = (fn) -> (b, a) -> fn.call this, a, b 42 | 43 | @owns = do (hop = {}.hasOwnProperty) -> (a, b) -> hop.call a, b 44 | 45 | @span = span = (list, f) -> 46 | if list.length is 0 then [[], []] 47 | else if f list[0] 48 | [ys, zs] = span list[1..], f 49 | [[list[0], ys...], zs] 50 | else [[], list] 51 | 52 | @divMod = (a, b) -> 53 | c = a % b 54 | mod = if c < 0 then c + b else c 55 | div = Math.floor a / b 56 | [div, mod] 57 | 58 | # The partition function takes a list and predicate fn and returns the pair of lists 59 | # of elements which do and do not satisfy the predicate, respectively. 60 | @partition = (list, fn) -> 61 | result = [[], []] 62 | result[+!fn item].push item for item in list 63 | result 64 | 65 | @first = (list) -> list[0] 66 | @last = (list) -> list[list.length-1] 67 | -------------------------------------------------------------------------------- /src/helpers.coffee: -------------------------------------------------------------------------------- 1 | {concat, concatMap, difference, foldl, map, nub} = require './functional-helpers' 2 | CS = require './nodes' 3 | 4 | COLOURS = 5 | red: '\x1B[31m' 6 | green: '\x1B[32m' 7 | yellow: '\x1B[33m' 8 | blue: '\x1B[34m' 9 | magenta: '\x1B[35m' 10 | cyan: '\x1B[36m' 11 | 12 | SUPPORTS_COLOUR = 13 | process?.stderr?.isTTY and not process.env.NODE_DISABLE_COLORS 14 | 15 | colourise = (colour, str) -> 16 | if SUPPORTS_COLOUR then "#{COLOURS[colour]}#{str}\x1B[39m" else str 17 | 18 | 19 | @numberLines = numberLines = (input, startLine = 1) -> 20 | lines = input.split '\n' 21 | padSize = "#{lines.length + startLine - 1}".length 22 | numbered = for line, i in lines 23 | currLine = "#{i + startLine}" 24 | pad = ((Array padSize + 1).join '0')[currLine.length..] 25 | "#{pad}#{currLine} : #{lines[i]}" 26 | numbered.join '\n' 27 | 28 | cleanMarkers = (str) -> str.replace /[\uEFEF\uEFFE\uEFFF]/g, '' 29 | 30 | @humanReadable = humanReadable = (str) -> 31 | ((str.replace /\uEFEF/g, '(INDENT)').replace /\uEFFE/g, '(DEDENT)').replace /\uEFFF/g, '(TERM)' 32 | 33 | @formatParserError = (input, e) -> 34 | realColumn = (cleanMarkers "#{(input.split '\n')[e.line - 1]}\n"[...e.column]).length 35 | unless e.found? 36 | return "Syntax error on line #{e.line}, column #{realColumn}: unexpected end of input" 37 | found = JSON.stringify humanReadable e.found 38 | found = ((found.replace /^"|"$/g, '').replace /'/g, '\\\'').replace /\\"/g, '"' 39 | unicode = ((e.found.charCodeAt 0).toString 16).toUpperCase() 40 | unicode = "\\u#{"0000"[unicode.length..]}#{unicode}" 41 | message = "Syntax error on line #{e.line}, column #{realColumn}: unexpected '#{found}' (#{unicode})" 42 | "#{message}\n#{pointToErrorLocation input, e.line, realColumn}" 43 | 44 | @pointToErrorLocation = pointToErrorLocation = (source, line, column, numLinesOfContext = 3) -> 45 | lines = source.split '\n' 46 | lines.pop() unless lines[lines.length - 1] 47 | # figure out which lines are needed for context 48 | currentLineOffset = line - 1 49 | startLine = currentLineOffset - numLinesOfContext 50 | if startLine < 0 then startLine = 0 51 | # get the context lines 52 | preLines = lines[startLine..currentLineOffset] 53 | preLines[preLines.length - 1] = colourise 'yellow', preLines[preLines.length - 1] 54 | postLines = lines[currentLineOffset + 1 .. currentLineOffset + numLinesOfContext] 55 | numberedLines = (numberLines (cleanMarkers [preLines..., postLines...].join '\n'), startLine + 1).split '\n' 56 | preLines = numberedLines[0...preLines.length] 57 | postLines = numberedLines[preLines.length...] 58 | # set the column number to the position of the error in the cleaned string 59 | column = (cleanMarkers "#{lines[currentLineOffset]}\n"[...column]).length 60 | padSize = ((currentLineOffset + 1 + postLines.length).toString 10).length 61 | [ 62 | preLines... 63 | "#{colourise 'red', (Array padSize + 1).join '^'} : #{(Array column).join ' '}#{colourise 'red', '^'}" 64 | postLines... 65 | ].join '\n' 66 | 67 | # these are the identifiers that need to be declared when the given value is 68 | # being used as the target of an assignment 69 | @beingDeclared = beingDeclared = (assignment) -> switch 70 | when not assignment? then [] 71 | when assignment.instanceof CS.Identifiers then [assignment.data] 72 | when assignment.instanceof CS.Rest then beingDeclared assignment.expression 73 | when assignment.instanceof CS.MemberAccessOps then [] 74 | when assignment.instanceof CS.DefaultParam then beingDeclared assignment.param 75 | when assignment.instanceof CS.ArrayInitialiser then concatMap assignment.members, beingDeclared 76 | when assignment.instanceof CS.ObjectInitialiser then concatMap assignment.vals(), beingDeclared 77 | else throw new Error "beingDeclared: Non-exhaustive patterns in case: #{assignment.className}" 78 | 79 | @declarationsFor = (node, inScope) -> 80 | vars = envEnrichments node, inScope 81 | foldl (new CS.Undefined).g(), vars, (expr, v) -> 82 | (new CS.AssignOp (new CS.Identifier v).g(), expr).g() 83 | 84 | # TODO: name change; this tests when a node is *being used as a value* 85 | usedAsExpression_ = (ancestors) -> 86 | parent = ancestors[0] 87 | grandparent = ancestors[1] 88 | switch 89 | when !parent? then yes 90 | when parent.instanceof CS.Program, CS.Class then no 91 | when parent.instanceof CS.SeqOp 92 | this is parent.right and 93 | usedAsExpression parent, ancestors[1..] 94 | when (parent.instanceof CS.Block) and 95 | (parent.statements.indexOf this) isnt parent.statements.length - 1 96 | no 97 | when (parent.instanceof CS.Functions) and 98 | parent.body is this and 99 | grandparent? and grandparent.instanceof CS.Constructor 100 | no 101 | else yes 102 | 103 | @usedAsExpression = usedAsExpression = (node, ancestors) -> 104 | usedAsExpression_.call node, ancestors 105 | 106 | # environment enrichments that occur when this node is evaluated 107 | # Note: these are enrichments of the *surrounding* environment; while function 108 | # parameters do enrich *an* environment, that environment is newly created 109 | envEnrichments_ = (inScope = []) -> 110 | possibilities = nub switch 111 | when @instanceof CS.AssignOp 112 | concat [ 113 | beingDeclared @assignee 114 | envEnrichments @expression 115 | ] 116 | when @instanceof CS.Class 117 | concat [ 118 | beingDeclared @nameAssignee 119 | envEnrichments @parent 120 | ] 121 | when @instanceof CS.ForIn, CS.ForOf 122 | concat [ 123 | beingDeclared @keyAssignee 124 | beingDeclared @valAssignee 125 | envEnrichments @target 126 | envEnrichments @step 127 | envEnrichments @filter 128 | envEnrichments @body 129 | ] 130 | when @instanceof CS.Try 131 | concat [ 132 | beingDeclared @catchAssignee 133 | envEnrichments @body 134 | envEnrichments @catchBody 135 | envEnrichments @finallyBody 136 | ] 137 | when @instanceof CS.Functions then [] 138 | else 139 | concatMap @childNodes, (child) => 140 | if child in @listMembers 141 | then concatMap this[child], (m) -> envEnrichments m, inScope 142 | else envEnrichments this[child], inScope 143 | difference possibilities, inScope 144 | 145 | @envEnrichments = envEnrichments = (node, args...) -> 146 | if node? then envEnrichments_.apply node, args else [] 147 | 148 | @debug = (flag, args...) -> 149 | 150 | util = require 'util' 151 | {argv} = require('optimist') 152 | .alias('d', 'debug') 153 | 154 | if arguments.length > 1 155 | if !argv.debug or flag is argv.debug 156 | for i in args 157 | console.error "#{flag} ------- [" 158 | console.error util.inspect i, false, null, true 159 | console.error "] ~~~~~~~~~ " 160 | else 161 | console.error flag 162 | -------------------------------------------------------------------------------- /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 | TypeWalker = require './type-walker' 6 | {Optimiser} = require './optimiser' 7 | {Compiler} = require './compiler' 8 | reporter = require './reporter' 9 | {TypeError} = require './type-helpers' 10 | cscodegen = try require 'cscodegen' 11 | escodegen = try require 'escodegen' 12 | 13 | pkg = require './../package.json' 14 | 15 | escodegenFormat = 16 | indent: 17 | style: ' ' 18 | base: 0 19 | renumber: yes 20 | hexadecimal: yes 21 | quotes: 'auto' 22 | parentheses: no 23 | 24 | CoffeeScript = 25 | 26 | CoffeeScript: CoffeeScript 27 | Compiler: Compiler 28 | Optimiser: Optimiser 29 | Parser: Parser 30 | Preprocessor: Preprocessor 31 | Nodes: Nodes 32 | 33 | VERSION: pkg.version 34 | 35 | parse: (coffee, options = {}) -> 36 | try 37 | preprocessed = Preprocessor.process coffee, literate: options.literate 38 | parsed = Parser.parse preprocessed, 39 | raw: options.raw 40 | inputSource: options.inputSource 41 | # type check 42 | TypeWalker.checkNodes(parsed) 43 | if reporter.has_errors() 44 | throw new TypeError reporter.report() 45 | return (if options.optimise then Optimiser.optimise parsed else parsed) 46 | catch e 47 | throw e.message if e instanceof TypeError 48 | throw e unless e instanceof Parser.SyntaxError 49 | throw new Error formatParserError preprocessed, e 50 | 51 | compile: (csAst, options) -> 52 | (Compiler.compile csAst, options).toBasicObject() 53 | 54 | compileTypedToCS: (csAst, options) -> 55 | (cscodegen csAst) 56 | 57 | # TODO 58 | cs: (csAst, options) -> 59 | # TODO: opt: format (default: nice defaults) 60 | 61 | jsWithSourceMap: (jsAst, name = 'unknown', options = {}) -> 62 | # TODO: opt: minify (default: no) 63 | throw new Error 'escodegen not found: run `npm install escodegen`' unless escodegen? 64 | unless {}.hasOwnProperty.call jsAst, 'type' 65 | jsAst = jsAst.toBasicObject() 66 | targetName = options.sourceMapFile or (options.sourceMap and (options.output.match /^.*[\\\/]([^\\\/]+)$/)[1]) 67 | escodegen.generate jsAst, 68 | comment: not options.compact 69 | sourceMapWithCode: yes 70 | sourceMap: name 71 | file: targetName or 'unknown' 72 | format: if options.compact then escodegen.FORMAT_MINIFY else options.format ? escodegenFormat 73 | 74 | js: (jsAst, options) -> (@jsWithSourceMap jsAst, null, options).code 75 | sourceMap: (jsAst, name, options) -> (@jsWithSourceMap jsAst, name, options).map 76 | 77 | # Equivalent to original CS compile 78 | cs2js: (input, options = {}) -> 79 | options.optimise ?= on 80 | csAST = CoffeeScript.parse input, options 81 | jsAST = CoffeeScript.compile csAST, bare: options.bare 82 | CoffeeScript.js jsAST, compact: options.compact or options.minify 83 | 84 | 85 | module.exports = CoffeeScript 86 | 87 | if require.extensions?['.node']? 88 | CoffeeScript.register = -> require './register' 89 | # Throw error with deprecation warning when depending upon implicit `require.extensions` registration 90 | for ext in ['.coffee', '.litcoffee', '.tcoffee', '.typed.coffee'] 91 | require.extensions[ext] ?= -> 92 | throw new Error """ 93 | Use CoffeeScript.register() or require the coffee-script-redux/register module to require #{ext} files. 94 | """ 95 | -------------------------------------------------------------------------------- /src/parser.coffee: -------------------------------------------------------------------------------- 1 | # This file is a placeholder for browser bundling purposes. 2 | # See /src/grammar.pegjs for the file that creates /lib/parser.js 3 | -------------------------------------------------------------------------------- /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 | {argv} = require('optimist') 11 | .boolean('self') 12 | 13 | compile = (module, filename, opts) -> 14 | input = fs.readFileSync filename, 'utf8' 15 | csAst = CoffeeScript.parse input, opts 16 | jsAst = CoffeeScript.compile csAst 17 | js = CoffeeScript.js jsAst 18 | runModule module, js, jsAst, filename 19 | 20 | compileWithOriginalCoffee = (module, filename, opts = {}) -> 21 | OriginalCoffee = require 'coffee-script' 22 | 23 | input = fs.readFileSync filename, 'utf8' 24 | js = OriginalCoffee.compile input, opts 25 | runModule module, js, null, filename 26 | 27 | require.extensions['.typed.coffee'] = (module, filename) -> 28 | compile module, filename, raw: yes, typeCheck: true 29 | 30 | require.extensions['.coffee'] = (module, filename) -> 31 | if argv.self 32 | compile module, filename, raw: yes 33 | else 34 | compileWithOriginalCoffee module, filename, raw: yes, bare: argv.bare 35 | 36 | require.extensions['.litcoffee'] = (module, filename) -> 37 | compile module, filename, raw: yes, literate: yes 38 | 39 | require.extensions['.tcoffee'] = (module, filename) -> 40 | compile module, filename, raw: yes, typeCheck: true 41 | 42 | # patch child_process.fork to default to the coffee binary as the execPath for coffee/litcoffee files 43 | {fork} = child_process 44 | unless fork.coffeePatched 45 | coffeeBinary = path.resolve 'bin', 'tcoffee' 46 | child_process.fork = (file, args = [], options = {}) -> 47 | if (path.extname file) in ['.coffee', '.litcoffee', '.tcoffee', '.typed.coffee'] 48 | if not Array.isArray args 49 | args = [] 50 | options = args or {} 51 | options.execPath or= coffeeBinary 52 | fork file, args, options 53 | child_process.fork.coffeePatched = yes 54 | 55 | delete require.cache[__filename] 56 | -------------------------------------------------------------------------------- /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 | reporter = require './reporter' 9 | 10 | addMultilineHandler = (repl) -> 11 | {rli, inputStream, outputStream} = repl 12 | initialPrompt = repl.prompt.replace /^[^> ]*/, (x) -> x.replace /./g, '-' 13 | continuationPrompt = repl.prompt.replace /^[^> ]*>?/, (x) -> x.replace /./g, '.' 14 | 15 | enabled = no 16 | buffer = '' 17 | 18 | # Proxy node's line listener 19 | nodeLineListener = (rli.listeners 'line')[0] 20 | rli.removeListener 'line', nodeLineListener 21 | rli.on 'line', (cmd) -> 22 | if enabled 23 | buffer += "#{cmd}\n" 24 | rli.setPrompt continuationPrompt 25 | rli.prompt true 26 | else 27 | nodeLineListener cmd 28 | return 29 | 30 | # Handle Ctrl-v 31 | inputStream.on 'keypress', (char, key) -> 32 | return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v' 33 | if enabled 34 | # allow arbitrarily switching between modes any time before multiple lines are entered 35 | unless buffer.match /\n/ 36 | enabled = not enabled 37 | rli.setPrompt repl.prompt 38 | rli.prompt true 39 | return 40 | # no-op unless the current line is empty 41 | return if rli.line? and not rli.line.match /^\s*$/ 42 | # eval, print, loop 43 | enabled = not enabled 44 | rli.line = '' 45 | rli.cursor = 0 46 | rli.output.cursorTo 0 47 | rli.output.clearLine 1 48 | # XXX: multiline hack 49 | buffer = buffer.replace /\n/g, '\uFF00' 50 | rli.emit 'line', buffer 51 | buffer = '' 52 | else 53 | enabled = not enabled 54 | rli.setPrompt initialPrompt 55 | rli.prompt true 56 | return 57 | 58 | # store and load command history from a file 59 | addHistory = (repl, filename, maxSize) -> 60 | try 61 | stat = fs.statSync filename 62 | size = Math.min maxSize, stat.size 63 | readFd = fs.openSync filename, 'r' 64 | buffer = new Buffer size 65 | # read last `size` bytes from the file 66 | fs.readSync readFd, buffer, 0, size, stat.size - size if size 67 | repl.rli.history = (buffer.toString().split '\n').reverse() 68 | # if the history file was truncated we should pop off a potential partial line 69 | do repl.rli.history.pop if stat.size > maxSize 70 | # shift off the final blank newline 71 | do repl.rli.history.shift if repl.rli.history[0] is '' 72 | repl.rli.historyIndex = -1 73 | catch e 74 | repl.rli.history = [] 75 | 76 | fd = fs.openSync filename, 'a' 77 | 78 | # like readline's history, we do not want any adjacent duplicates 79 | lastLine = repl.rli.history[0] 80 | 81 | # save new commands to the history file 82 | repl.rli.addListener 'line', (code) -> 83 | if code and code isnt lastLine 84 | lastLine = code 85 | fs.writeSync fd, "#{code}\n" 86 | 87 | repl.rli.on 'exit', -> fs.closeSync fd 88 | 89 | # .clear should also clear history 90 | original_clear = repl.commands['.clear'].action 91 | repl.commands['.clear'].action = -> 92 | repl.outputStream.write 'Clearing history...\n' 93 | repl.rli.history = [] 94 | fs.closeSync fd 95 | fd = fs.openSync filename, 'w' 96 | lastLine = undefined 97 | original_clear.call this 98 | 99 | # add a command to show the history stack 100 | repl.commands['.history'] = 101 | help: 'Show command history' 102 | action: -> 103 | repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n" 104 | do repl.displayPrompt 105 | 106 | module.exports = 107 | start: (opts = {}) -> 108 | # REPL defaults 109 | opts.prompt or= 'tcoffee> ' 110 | opts.ignoreUndefined ?= yes 111 | opts.historyFile ?= path.join process.env.HOME, '.coffee_history' 112 | opts.historyMaxInputSize ?= 10 * 1024 # 10KiB 113 | opts.eval or= (input, context, filename, cb) -> 114 | # XXX: multiline hack 115 | input = input.replace /\uFF00/g, '\n' 116 | # strip parens added by node 117 | input = input.replace /^\(([\s\S]*)\n\)$/m, '$1' 118 | # strip single-line comments 119 | input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3' 120 | # empty command 121 | return cb null if /^\s*$/.test input 122 | try 123 | inputAst = CoffeeScript.parse input, {filename, raw: yes} 124 | transformedAst = new CS.AssignOp (new CS.Identifier '_'), inputAst.body 125 | jsAst = CoffeeScript.compile transformedAst, bare: yes, inScope: Object.keys context 126 | js = CoffeeScript.js jsAst 127 | cb null, vm.runInContext js, context, filename 128 | catch err 129 | cb "\x1B[0;31m#{err.constructor.name}: #{err.message}\x1B[0m" 130 | reporter.clean() 131 | 132 | repl = nodeREPL.start opts 133 | repl.on 'exit', -> repl.outputStream.write '\n' 134 | addMultilineHandler repl 135 | if opts.historyFile 136 | addHistory repl, opts.historyFile, opts.historyMaxInputSize 137 | repl 138 | -------------------------------------------------------------------------------- /src/reporter.coffee: -------------------------------------------------------------------------------- 1 | pj = try require 'prettyjson' 2 | render = (obj) -> pj?.render obj 3 | {TypeError} = require './type-helpers' 4 | {debug} = require './helpers' 5 | require('colors') 6 | 7 | class Reporter 8 | constructor: -> 9 | @errors = [] 10 | @warnings = [] 11 | 12 | has_errors: -> @errors.length > 0 13 | 14 | has_warnings: -> @warnings.length > 0 15 | 16 | # () -> String 17 | report: -> 18 | errors = @errors.map ([node, text])-> 19 | "L#{node.line} #{node.raw.inverse} #{text.red}" 20 | 21 | """ 22 | #{errors.join '\n'} 23 | """ 24 | 25 | add_error: (node, text) => 26 | @errors.push [node, text] 27 | 28 | clean: => 29 | @errors = [] 30 | 31 | add_warning: (node, ws...) => 32 | @warnings.push [node, ws.join ''] 33 | 34 | # for debug 35 | dump: (node, prefix = '') -> 36 | console.error prefix + "[#{node.name}]" 37 | console.error prefix, ' vars::' 38 | for key, val of node._vars 39 | console.error prefix, ' +', key, '::', JSON.stringify(val) 40 | 41 | console.error prefix, ' types::' 42 | for type in node._types 43 | console.error prefix, ' +', type.identifier.typeName 44 | if type.properties 45 | for prop in type.properties 46 | console.error prefix, ' @', prop.identifier.typeName, prop.typeAnnotation 47 | 48 | for next in node.nodes 49 | @dump next, prefix + ' ' 50 | 51 | module.exports = new Reporter -------------------------------------------------------------------------------- /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 | if jsAst 116 | Module._sourceMaps[filename] = -> "#{CoffeeScript.sourceMap jsAst, filename}" 117 | module._compile jsSource, filename 118 | 119 | module.exports = {runMain, runModule} 120 | -------------------------------------------------------------------------------- /src/type-checker.coffee: -------------------------------------------------------------------------------- 1 | # struct Node 2 | # nodeType :: String 3 | # 4 | # struct MemberAccess extends Node 5 | # left :: TypeRef 6 | # right :: TypeRef 7 | # 8 | # type TypeRef = String | MemberAccess 9 | # 10 | # type Struct exnteds Node 11 | # identifier :: TypeIdentifier 12 | # members :: PropertyTypeAnnotaiton 13 | # 14 | # struct TypeIdentifier 15 | # typeRef :: TypeRef 16 | # isArray :: Boolean? 17 | # typeArguments :: TypeRef[]? 18 | # 19 | # struct TypeAnnotation extends Node 20 | # implicit :: Boolean? 21 | # 22 | # struct IdentifierTypeAnnotation implements TypeAnnotation 23 | # identifier :: TypeIdentifier 24 | # 25 | # struct PropertyTypeAnnotation implements TypeAnnotation 26 | # properties :: TypeIdentifier[] 27 | # nullable :: Boolean 28 | # isArray :: Boolean 29 | 30 | {debug} = require './helpers' 31 | util = require 'util' 32 | reporter = require './reporter' 33 | CS = require './nodes' 34 | _ = require 'lodash' 35 | {ImplicitAny} = require './types' 36 | 37 | # correctExtends :: Scope * TypeAnnotation -> TypeAnnotation[] 38 | correctExtends = (scope, annotation) -> 39 | extendList = [annotation] 40 | cur = annotation 41 | while cur?.heritages?.extend? 42 | next = scope.getTypeByNode cur.heritages.extend 43 | if next 44 | extendList.push next 45 | cur = next 46 | else 47 | break 48 | extendList 49 | 50 | isAcceptableExtends = (scope, left, right) -> 51 | _.any correctExtends(scope, left).map (le) -> 52 | le.identifier.typeRef is right.identifier.typeRef 53 | 54 | # isAcceptablePrimitiveSymbol :: Scope * TypeAnnotation * TypeAnnotation -> Boolean 55 | isAcceptablePrimitiveSymbol = (scope, left, right, nullable = false, isArray = false) -> 56 | if left.nodeType isnt 'primitiveIdentifier' 57 | throw 'left is not primitive' 58 | 59 | return true if left.identifier.typeRef is 'Any' 60 | unless isAcceptableExtends(scope, left, right) 61 | if nullable and right.identifier.typeRef in ['Null', 'Undefined'] # nullable 62 | return true 63 | return false 64 | # array check 65 | if !!left.identifier.isArray 66 | if right?.identifier?.isArray? 67 | return false if !!right?.identifier?.isArray isnt true 68 | else 69 | return false 70 | else 71 | return false if !!right?.identifier?.isArray isnt false 72 | # TODO: typeArgument check 73 | true 74 | 75 | # isAcceptableStruct :: Scope * TypeAnnotation * TypeAnnotation -> Boolean 76 | isAcceptableStruct = (scope, left, right) -> 77 | _.all left.properties.map (lprop, n) => 78 | rprop = _.find right.properties, (rp) -> 79 | rp.identifier?.typeRef is lprop.identifier?.typeRef 80 | unless rprop? 81 | return lprop.typeAnnotation?.identifier?.nullable 82 | return isAcceptable scope, lprop.typeAnnotation, rprop.typeAnnotation 83 | 84 | # isAcceptableFunction :: Scope * TypeAnnotation * TypeAnnotation -> Boolean 85 | isAcceptableFunctionType = (scope, left, right) -> 86 | left.returnType ?= ImplicitAny 87 | right.returnType ?= ImplicitAny 88 | 89 | # debug 'isAcceptable functionType l', left 90 | # debug 'isAcceptable functionType r', right 91 | 92 | passArgs = _.all left.arguments.map (leftArg, n) -> 93 | leftArg = leftArg ? ImplicitAny 94 | rightArg = right.arguments[n] ? ImplicitAny 95 | isAcceptable scope, leftArg, rightArg 96 | 97 | return false unless passArgs 98 | 99 | if right.returnType?.identifier?.typeRef is 'Void' 100 | return true 101 | if left.returnType?.identifier?.typeRef is 'Void' 102 | return true 103 | isAcceptable(scope, left.returnType, right.returnType) 104 | 105 | # isAcceptable :: Types.Scope * TypeAnnotation * TypeAnnotaion -> Boolean 106 | isAcceptable = (scope, left, right) -> 107 | # debug 'acc left', left 108 | # debug 'acc right', right 109 | # FIXME 110 | return true if not left? or not right? 111 | [leftAnnotation, rightAnnotation] = [left, right].map (node) => 112 | {resolveType} = require './type-resolver' 113 | resolveType(scope, node) 114 | 115 | # debug 'left', leftAnnotation 116 | # debug 'right', rightAnnotation 117 | 118 | # typeArguments 119 | if leftAnnotation.identifier?.identifier?.typeArguments?.length 120 | console.error leftAnnotation.identifier.identifier.typeArguments 121 | 122 | # Grasp if left is any 123 | return true if not leftAnnotation? or not rightAnnotation? # FIXME 124 | 125 | if leftAnnotation.nodeType is 'primitiveIdentifier' 126 | if leftAnnotation.identifier.typeRef is 'Any' 127 | return true 128 | 129 | if left.identifier and right.identifier and rightAnnotation 130 | # leftNullable = left.identifier.nullable 131 | leftWholeNullable = left.identifier.wholeNullable 132 | if leftWholeNullable and rightAnnotation.identifier.typeRef in ['Undefined', 'Null'] 133 | return true 134 | 135 | isSameArrayFlag = !!left.identifier.isArray is !!right.identifier.isArray or !!left.identifier.isArray is !!rightAnnotation.isArray 136 | unless isSameArrayFlag 137 | # console.error 'fail at isSameArrayFlag' 138 | return false 139 | 140 | # ex type parameter class C 141 | if leftAnnotation.nodeType is 'identifier' 142 | if leftAnnotation.identifier.typeRef is rightAnnotation.identifier.typeRef 143 | return true 144 | else 145 | return false 146 | # if rightAnnotation.nodeType is 'primitiveIdentifier' 147 | # return false 148 | 149 | else if leftAnnotation.nodeType is 'members' 150 | if rightAnnotation.nodeType is 'members' 151 | ret = isAcceptableStruct scope, leftAnnotation, rightAnnotation 152 | return ret 153 | else 154 | return false 155 | if leftAnnotation.nodeType is 'primitiveIdentifier' 156 | if rightAnnotation.nodeType is 'primitiveIdentifier' 157 | if leftAnnotation.identifier.typeRef is 'Void' 158 | return true 159 | leftNullable = !! left.identifier.nullable 160 | rightNullable = !! right.identifier.nullable 161 | if not leftNullable and rightNullable 162 | # console.error 'fail at nullable check' 163 | return false 164 | return isAcceptablePrimitiveSymbol scope, leftAnnotation, rightAnnotation, leftNullable 165 | else 166 | return false 167 | 168 | if leftAnnotation.nodeType is 'functionType' 169 | if rightAnnotation.nodeType is 'functionType' 170 | return isAcceptableFunctionType scope, leftAnnotation, rightAnnotation 171 | else if leftAnnotation?.returnType?.implicit 172 | return true 173 | else 174 | return false 175 | true # FIXME: pass all unknow pattern 176 | 177 | 178 | util = require 'util' 179 | formatType = (node, prefix = '') -> 180 | if node.nodeType is 'members' 181 | lines = node.properties.map (prop) -> 182 | prefix + '- ' + formatType(prop, prefix + ' ') 183 | '[struct]'+ '\n' + lines.join '\n' 184 | else if node.nodeType is 'primitiveIdentifier' 185 | prefix + node.identifier.typeRef 186 | else if node.nodeType is 'identifier' 187 | 'identifier' 188 | array = if node.identifier.isArray then '[]' else '' 189 | if node.typeAnnotation? 190 | node.identifier.typeRef + array + ' :: ' + formatType(node.typeAnnotation, prefix) 191 | else 192 | node.identifier.typeRef + array 193 | else if node.nodeType is 'functionType' 194 | args = node.arguments.map (arg) -> prefix + '- ' + formatType(arg, prefix + ' ') 195 | joined = '\n' + args.join '\n' 196 | returnType = formatType(node.returnType, prefix + ' ') 197 | """ 198 | [function] 199 | #{prefix}[arguments] #{joined} 200 | #{prefix}[return] #{returnType} 201 | """ 202 | else 203 | util.inspect node, false, null 204 | 205 | typeErrorText = (left, right) -> 206 | header = "\nTypeError:" 207 | l = formatType left, ' ' 208 | r = formatType right, ' ' 209 | """ 210 | #{header} 211 | required: 212 | #{l} 213 | assignee: 214 | #{r} 215 | """ 216 | 217 | # checkType :: Scope * Node * Type * Type -> () 218 | checkType = (scope, node, left, right) -> 219 | ret = isAcceptable scope, left.typeAnnotation, right.typeAnnotation 220 | if ret 221 | return true 222 | else 223 | err = typeErrorText left.typeAnnotation, right.typeAnnotation 224 | if left.implicit and right.implicit 225 | reporter.add_warning node, err 226 | else 227 | reporter.add_error node, err 228 | return false 229 | 230 | # checkTypeAnnotation :: Scope * Node * Type * Type -> () 231 | checkTypeAnnotation = (scope, node, left, right) -> 232 | ret = isAcceptable scope, left, right 233 | if ret 234 | return true 235 | else 236 | err = typeErrorText left, right 237 | if left.implicit and right.implicit 238 | reporter.add_warning node, err 239 | else 240 | reporter.add_error node, err 241 | return false 242 | 243 | module.exports = { 244 | checkType, checkTypeAnnotation, isAcceptable 245 | } 246 | -------------------------------------------------------------------------------- /src/type-helpers.coffee: -------------------------------------------------------------------------------- 1 | # ref: http://coffeescriptcookbook.com/chapters/classes_and_objects/cloning 2 | @clone = clone = (obj) -> 3 | if not obj? or typeof obj isnt 'object' 4 | return obj 5 | 6 | if obj instanceof Date 7 | return new Date(obj.getTime()) 8 | 9 | if obj instanceof RegExp 10 | flags = '' 11 | flags += 'g' if obj.global? 12 | flags += 'i' if obj.ignoreCase? 13 | flags += 'm' if obj.multiline? 14 | flags += 'y' if obj.sticky? 15 | return new RegExp(obj.source, flags) 16 | 17 | newInstance = new obj.constructor() 18 | 19 | for key of obj 20 | newInstance[key] = clone obj[key] 21 | 22 | return newInstance 23 | 24 | # rewrite :: Object * Hash -> () 25 | @rewrite = rewrite = (obj, replacer) -> 26 | return if (typeof obj) in ['string', 'number'] 27 | for key, val of obj 28 | if (typeof val) is 'string' 29 | if replacer[val]? 30 | obj[key] = replacer[val] 31 | else if val instanceof Object 32 | rewrite(val, replacer) 33 | 34 | class @TypeError 35 | constructor: (@message) -> 36 | 37 | NumberInterface = 38 | toString: 39 | name: 'function' 40 | arguments: [] 41 | returnType: 'String' 42 | 43 | ArrayInterface = 44 | length: 'Number' 45 | push: 46 | name: 'function' 47 | arguments: ['T'] 48 | returnType: 'void' 49 | unshift: 50 | name: 'function' 51 | arguments: ['T'] 52 | returnType: 'void' 53 | shift: 54 | name: 'function' 55 | arguments: [] 56 | returnType: 'T' 57 | toString: 58 | name: 'function' 59 | arguments: [] 60 | returnType: 'String' 61 | 62 | ObjectInterface = -> 63 | toString: 64 | name: 'function' 65 | arguments: [] 66 | returnType: 'String' 67 | keys: 68 | name: 'function' 69 | arguments: ['Any'] 70 | returnType: 71 | array: 'String' 72 | 73 | -------------------------------------------------------------------------------- /src/type-resolver.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'lodash' 2 | {Scope, ClassScope, FunctionScope } = require './type-scope' 3 | 4 | rewriteType = (scope, node, from, to) -> 5 | if node.nodeType is 'identifier' 6 | # TODO: type arguments 7 | if node.identifier?.typeArguments?.length 8 | typeArgs = 9 | for i, n in node.identifier.typeArguments 10 | resolved = resolveType(scope, i) 11 | resolved.typeAnnotation 12 | ann = scope.getTypeByNode from.typeAnnotation # Check later 13 | if ann 14 | extendType scope, ann, typeArgs 15 | if node.identifier?.typeRef is from.identifier.typeRef 16 | node.identifier.typeRef = to.identifier.typeRef 17 | 18 | else if node.nodeType is 'members' 19 | for prop in node.properties 20 | switch prop.typeAnnotation.nodeType 21 | when 'functionType' 22 | rewriteType scope, prop.typeAnnotation.returnType, from, to 23 | for arg in prop.typeAnnotation.arguments 24 | rewriteType scope, arg, from, to 25 | # debug 'functionType rewriten', prop.typeAnnotation 26 | when 'identifier' 27 | if prop.typeAnnotation?.identifier?.typeArguments?.length 28 | typeArgs = 29 | for i, n in prop.typeAnnotation.identifier.typeArguments 30 | resolved = resolveType(scope, i) 31 | resolved.typeAnnotation 32 | ann = scope.getTypeByNode prop.typeAnnotation 33 | if ann 34 | extendType scope, ann, typeArgs 35 | 36 | if prop.typeAnnotation.identifier?.typeRef is from.identifier.typeRef 37 | prop.typeAnnotation.identifier.typeRef = to.identifier.typeRef 38 | when 'members' 39 | rewriteType scope, prop.typeAnnotation, from, to 40 | 41 | extendIdentifierType = (scope, node) -> 42 | ann = scope.getTypeByIdentifier(node.identifier) 43 | if ann.nodeType is 'identifier' 44 | unless ann.typeAnnotation? 45 | throw new Error 'identifier with annotation required' 46 | from = node 47 | to = ann.typeAnnotation 48 | rewriteType scope, node, from, to 49 | 50 | extendFunctionType = (scope, node) -> 51 | extendIdentifierType scope, node.returnType 52 | for arg in node.arguments 53 | if arg.nodeType is 'identifier' 54 | extendIdentifierType scope, arg 55 | else if arg.nodeType is 'functionType' 56 | extendFunctionType scope, arg 57 | node 58 | 59 | extendMembers = (scope, node, givenArgs) -> 60 | typeScope = new Scope scope 61 | if node.identifier?.typeArguments?.length 62 | for arg, n in node.identifier.typeArguments 63 | givenArg = givenArgs[n] 64 | 65 | if givenArg?.identifier?.typeArguments?.length 66 | typeArgs = givenArg.identifier.typeArguments 67 | if ann = scope.getTypeByNode givenArg 68 | extendType scope, ann, typeArgs 69 | 70 | typeScope.addType 71 | nodeType: 'identifier' 72 | identifier: _.cloneDeep arg.identifier 73 | typeAnnotation: 74 | nodeType: 'identifier' 75 | identifier: _.cloneDeep givenArg.identifier 76 | 77 | for arg, n in node.identifier.typeArguments 78 | givenArg = givenArgs[n] 79 | rewriteType typeScope, node, arg, givenArg 80 | 81 | extendType = (scope, node, givenArgs) -> 82 | if node.nodeType is 'members' 83 | extendMembers scope, node, givenArgs 84 | else if node.nodeType is 'functionType' 85 | extendFunctionType scope, node 86 | node 87 | 88 | resolveType = (scope, node) -> 89 | if node.nodeType is 'identifier' 90 | ret = scope.getTypeByNode(node) 91 | if node.identifier?.typeArguments?.length 92 | ret = extendType scope, _.cloneDeep(ret), node.identifier.typeArguments 93 | unless ret 94 | if node.nodeType is 'identifier' # TODO: consider nested type arguments 95 | return node 96 | throw 'Type: '+ util.inspect(node.identifier.typeRef) + ' is not defined' 97 | ret 98 | else if node.nodeType is 'primitiveIdentifier' 99 | node 100 | else if node.nodeType is 'members' 101 | node 102 | else if node.nodeType is 'functionType' 103 | node 104 | else 105 | throw node?.nodeType + " is not registered nodeType" 106 | 107 | module.exports = { 108 | resolveType, extendType 109 | } 110 | -------------------------------------------------------------------------------- /src/type-scope.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'lodash' 2 | {ImplicitAny} = require './types' 3 | {debug} = require './helpers' 4 | 5 | # Var and typeRef scope as node 6 | class Scope 7 | # constructor :: (Scope) -> Scope 8 | constructor: (@parent = null) -> 9 | @id = _.uniqueId() 10 | 11 | @name = '' 12 | 13 | # Scope vars 14 | @vars = [] #=> Type[] 15 | 16 | # Scope typeRefs 17 | @types = [] #=> Type[] 18 | 19 | # This scope 20 | @_this = [] 21 | 22 | # Module scope 23 | @_modules = [] 24 | 25 | @_returnables = [] #=> Type[] 26 | 27 | getPositionInScope: -> 28 | arr = [] 29 | cur = @ 30 | while cur 31 | arr.push cur.name 32 | cur = cur.parent 33 | arr.reverse() 34 | 35 | addReturnable: (typeRef) -> 36 | @_returnables.push typeRef 37 | 38 | getReturnables: -> _.cloneDeep @_returnables 39 | 40 | getRoot: -> 41 | return @ unless @parent 42 | root = @parent 43 | while true 44 | if root.parent 45 | root = root.parent 46 | else break 47 | root 48 | 49 | getParentModule: -> 50 | return @ if @ instanceof ModuleScope 51 | return @ unless @parent 52 | root = @parent 53 | while root 54 | return root if root instanceof ModuleScope 55 | return root unless root.parent? 56 | root = root.parent 57 | 58 | _findModuleById: (moduleId) -> 59 | for {scope} in @_modules 60 | if scope.id is moduleId 61 | return scope 62 | else 63 | if ret = scope._findModuleById(moduleId) 64 | return ret 65 | null 66 | 67 | findModuleById: (moduleId) -> 68 | root = @getRoot() 69 | root._findModuleById(moduleId) 70 | 71 | # addType :: Any * Object * Object -> Type 72 | addModule: (name) -> 73 | scope = new ModuleScope this 74 | scope.name = name 75 | mod = 76 | nodeType: 'module' 77 | identifier: 78 | typeRef: name 79 | scope: scope 80 | @_modules.push mod 81 | scope 82 | 83 | getModule: (name) -> 84 | (_.find @_modules, (mod) -> mod.identifier.typeRef is name)?.scope 85 | 86 | getModuleInScope: (name) -> 87 | @getModule(name) or @parent?.getModuleInScope(name) or undefined 88 | 89 | # resolveNamespace :: TypeRef -> Module 90 | resolveNamespace: (ref, autoCreate = false) -> 91 | ns = [] 92 | cur = ref 93 | while true 94 | if (typeof cur) is 'string' 95 | ns.unshift cur 96 | break 97 | else 98 | ns.unshift cur.right 99 | cur = cur.left 100 | # find or initialize module 101 | cur = @ 102 | for moduleName in ns when moduleName? 103 | mod = cur.getModuleInScope(moduleName) 104 | unless mod 105 | if autoCreate 106 | mod = cur.addModule(moduleName) 107 | else 108 | return null 109 | cur = mod 110 | cur 111 | 112 | addType: (node) -> 113 | @types.push node 114 | return node 115 | 116 | addPrimitiveType: (node) -> 117 | if node.nodeType isnt 'primitiveIdentifier' 118 | throw 'nodeType isnt primitiveIdentifier' 119 | @types.push node 120 | return node 121 | 122 | addStructType: (structNode) -> 123 | if structNode.nodeType isnt 'struct' 124 | throw 'node isnt structNode' 125 | 126 | ref = structNode.name.identifier.typeRef 127 | if _.isString ref 128 | mod = @ 129 | propName = ref 130 | else 131 | mod = @resolveNamespace ref.left, true 132 | propName = ref.right 133 | 134 | ann = _.clone structNode.expr 135 | ann.identifier = structNode.name.identifier 136 | ann.identifier.typeRef = propName 137 | mod.types.push ann 138 | 139 | # getTypeByString :: String -> Type 140 | getTypeByString: (typeName) -> 141 | _.find @types, (i, n) -> 142 | i.identifier.typeRef is typeName 143 | 144 | # getTypeByMemberAccess :: TypeRef -> Type 145 | getTypeByMemberAccess: (typeRef) -> 146 | ns = typeRef.left 147 | propName = typeRef.right 148 | mod = @resolveNamespace ns 149 | if mod 150 | ret = _.find mod.types, (node) => 151 | node.identifier.typeRef is propName 152 | return (if ret?.nodeType is 'struct' then ret?.members else ret) 153 | else 154 | null 155 | 156 | # getType :: TypeRef -> Type 157 | getType: (typeRef) -> 158 | if _.isString(typeRef) 159 | @getTypeByString(typeRef) 160 | else if typeRef?.nodeType is 'MemberAccess' 161 | @getTypeByMemberAccess(typeRef) 162 | 163 | # getTypeInScope :: TypeRef -> Type 164 | getTypeInScope: (typeRef) -> 165 | ret = @getType(typeRef) or @parent?.getTypeInScope(typeRef) or null 166 | ret 167 | 168 | # getTypoIdentifier :: TypoAnnotation -> TypeAnnotation 169 | getTypeByNode: (node) -> 170 | switch node?.nodeType 171 | when 'members' 172 | node 173 | when 'primitiveIdentifier' 174 | node 175 | when 'identifier' 176 | @getTypeInScope(node.identifier.typeRef) 177 | 178 | # getTypoIdentifier :: TypoAnnotation -> TypeAnnotation 179 | getTypeByIdentifier: (identifier) -> 180 | @getTypeInScope(identifier.typeRef) 181 | 182 | # addThis :: Type * TypeArgument[] -> () 183 | addThis: (type) -> 184 | @_this.push type 185 | 186 | getThis: (propName) -> 187 | _.find @_this, (v) -> v.identifier.typeRef is propName 188 | 189 | getThisByNode: (node) -> 190 | typeName = node.identifier.typeRef 191 | @getThis(typeName)?.typeAnnotation 192 | 193 | # addVar :: Type * TypeArgument[] -> () 194 | addVar: (type) -> 195 | @vars.push type 196 | 197 | # getVar :: String -> () 198 | getVar: (typeName) -> 199 | _.find @vars, (v) -> v.identifier.typeRef is typeName 200 | 201 | getVarInScope: (typeName) -> 202 | @getVar(typeName) or @parent?.getVarInScope(typeName) or undefined 203 | 204 | getTypeByVarNode: (node) -> 205 | typeName = node.identifier.typeRef 206 | @getVarInScope(typeName)?.typeAnnotation 207 | 208 | getTypeByVarName: (varName) -> 209 | @getVarInScope(varName)?.typeAnnotation 210 | 211 | checkAcceptableObject: (left, right) -> false 212 | 213 | getHighestCommonType: (list) -> 214 | [head, tail...] = list 215 | _.cloneDeep _.reduce tail, ((a, b) => 216 | @compareAsParent a, b 217 | ), head 218 | 219 | compareAsParent: (a, b) -> 220 | {isAcceptable} = require './type-checker' 221 | 222 | if a?.identifier?.typeRef in ['Undefined', 'Null'] 223 | b = _.cloneDeep(b) 224 | if b?.identifier? 225 | b.identifier.nullable = true 226 | return b 227 | 228 | if b?.identifier?.typeRef in ['Undefined', 'Null'] 229 | a = _.cloneDeep(a) 230 | if a?.identifier? 231 | a.identifier.nullable = true 232 | return a 233 | 234 | retA = isAcceptable @, a, b 235 | retB = isAcceptable @, b, a 236 | if retA and retB then b 237 | else if retA then a 238 | else if retB then b 239 | else ImplicitAny 240 | 241 | class ClassScope extends Scope 242 | getConstructorType: -> 243 | (_.find @_this, (v) -> v.identifier.typeRef is '_constructor_')?.typeAnnotation 244 | 245 | class ModuleScope extends Scope 246 | class FunctionScope extends Scope 247 | 248 | module.exports = { 249 | Scope, ClassScope, FunctionScope, ModuleScope 250 | } -------------------------------------------------------------------------------- /src/types.coffee: -------------------------------------------------------------------------------- 1 | {debug} = require './helpers' 2 | {clone, rewrite} = require './type-helpers' 3 | _ = require 'lodash' 4 | 5 | ImplicitAny = 6 | implicit: true 7 | isPrimitive: true 8 | nodeType: 'primitiveIdentifier' 9 | identifier: 10 | typeRef: 'Any' 11 | 12 | primitives = 13 | AnyType: 14 | nodeType: 'primitiveIdentifier' 15 | isPrimitive: true 16 | identifier: 17 | typeRef: 'Any' 18 | 19 | StringType: 20 | nodeType: 'primitiveIdentifier' 21 | isPrimitive: true 22 | identifier: 23 | typeRef: 'String' 24 | 25 | BooleanType: 26 | nodeType: 'primitiveIdentifier' 27 | isPrimitive: true 28 | identifier: 29 | typeRef: 'Boolean' 30 | 31 | IntType: 32 | nodeType: 'primitiveIdentifier' 33 | isPrimitive: true 34 | identifier: 35 | typeRef: 'Int' 36 | 37 | FloatType: 38 | nodeType: 'primitiveIdentifier' 39 | isPrimitive: true 40 | identifier: 41 | typeRef: 'Float' 42 | heritages: 43 | extend: 44 | nodeType: 'identifier' 45 | identifier: 46 | typeRef: 'Int' 47 | 48 | NumberType: 49 | nodeType: 'primitiveIdentifier' 50 | isPrimitive: true 51 | identifier: 52 | typeRef: 'Number' 53 | heritages: 54 | extend: 55 | nodeType: 'identifier' 56 | identifier: 57 | typeRef: 'Float' 58 | 59 | NullType: 60 | nodeType: 'primitiveIdentifier' 61 | isPrimitive: true 62 | identifier: 63 | typeRef: 'Null' 64 | 65 | UndefinedType: 66 | nodeType: 'primitiveIdentifier' 67 | isPrimitive: true 68 | identifier: 69 | typeRef: 'Undefined' 70 | 71 | VoidType: 72 | nodeType: 'primitiveIdentifier' 73 | isPrimitive: true 74 | identifier: 75 | typeRef: 'Void' 76 | 77 | initializeGlobalTypes = (node) -> 78 | node.addPrimitiveType primitives.AnyType 79 | node.addPrimitiveType primitives.StringType 80 | node.addPrimitiveType primitives.IntType 81 | node.addPrimitiveType primitives.FloatType 82 | node.addPrimitiveType primitives.NumberType 83 | node.addPrimitiveType primitives.BooleanType 84 | node.addPrimitiveType primitives.NullType 85 | node.addPrimitiveType primitives.UndefinedType 86 | node.addPrimitiveType primitives.VoidType 87 | 88 | module.exports = { 89 | initializeGlobalTypes, primitives, ImplicitAny 90 | } -------------------------------------------------------------------------------- /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/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.skip "--eval -i", (done) -> 5 | child_process.exec 'bin/coffee --eval -i --self 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 | # broken by new cli interface 18 | test.skip "--eval --cli", (done) -> 19 | child_process.exec 'bin/coffee --eval --cli --self "require \'./test/cli-eval-errors-files/1.coffee\'"', (error, stdout, stderr) -> 20 | eq stdout, "1 is main false\n0 is main false\n" 21 | done() 22 | -------------------------------------------------------------------------------- /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.skip "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 | -------------------------------------------------------------------------------- /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.skip "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/module.coffee: -------------------------------------------------------------------------------- 1 | root = window ? global ? this 2 | root._module_ = (ns, f, context = root) => 3 | context ?= root 4 | hist = [] 5 | for name in ns.split('.') 6 | unless context[name]? 7 | context[name] = {} 8 | context = context[name] 9 | hist.push context 10 | f.apply context, hist 11 | 12 | suite 'Module', -> 13 | test 'module', -> 14 | module X 15 | @x = 3 16 | eq X.x, 3 17 | 18 | test 'nested module', -> 19 | module X.Y 20 | @x = 3 21 | eq X.Y.x, 3 22 | -------------------------------------------------------------------------------- /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 | 7 | 8 | test 'empty program', -> @shouldParse '' 9 | test 'simple number', -> @shouldParse '0' 10 | 11 | test 'simple error', -> @shouldNotParse '0+' 12 | 13 | test 'jashkenas/coffee-script#1601', -> @shouldParse '@' 14 | 15 | test '#242: a very specifically spaced division, by itself', -> 16 | @shouldParse 'a[1]/ 1' 17 | 18 | test 'more oddly-spaced division', -> 19 | @shouldParse 'f(a /b)' 20 | 21 | test 'deeply nested expressions', -> 22 | @shouldParse '((((((((((((((((((((0))))))))))))))))))))' 23 | @shouldParse '++++++++++++++++++++0' 24 | 25 | test '#142 inconsistently indented object literal', -> 26 | inconsistently = 27 | indented: 28 | object: 29 | literal: yes 30 | eq inconsistently.indented.object.literal, yes 31 | 32 | test 'inconsistently indented if statement', -> 33 | nonceA = {} 34 | nonceB = {} 35 | 36 | fn = (b) -> 37 | if b 38 | nonceA 39 | else 40 | nonceB 41 | 42 | eq nonceA, fn 1 43 | eq nonceB, fn 0 44 | 45 | test 'inconsistent object literal dedent', -> 46 | @shouldNotParse ''' 47 | obj = 48 | foo: 5 49 | bar: 6 50 | ''' 51 | 52 | test 'inconsistent if statement dedent', -> 53 | @shouldNotParse ''' 54 | f = -> 55 | if something 56 | 'yup' 57 | else 58 | 'nope' 59 | ''' 60 | 61 | test 'windows line endings', -> 62 | @shouldParse 'if test\r\n fn a\r\n\r\n fn b' 63 | 64 | test 'strip leading spaces in heredocs', -> 65 | eq 'a\n b\nc', ''' 66 | a 67 | b 68 | c 69 | ''' 70 | eq 'a\n b\nc', ''' 71 | a 72 | b 73 | c 74 | ''' 75 | eq 'a\n b\nc', ''' 76 | a 77 | b 78 | c 79 | ''' 80 | eq ' a\nb\n c', ''' 81 | a 82 | b 83 | c 84 | ''' 85 | 86 | eq 'a\n b\nc', """ 87 | a 88 | b 89 | c 90 | """ 91 | eq 'a\n b\nc', """ 92 | a 93 | b 94 | c 95 | """ 96 | eq 'a\n b\nc', """ 97 | a 98 | b 99 | c 100 | """ 101 | eq ' a\nb\n c', """ 102 | a 103 | b 104 | c 105 | """ 106 | 107 | test 'strip leading spaces in heredocs with interpolations', -> 108 | a = 'd' 109 | b = 'e' 110 | c = 'f' 111 | 112 | eq 'd\n e\nf', """ 113 | #{a} 114 | #{b} 115 | #{c} 116 | """ 117 | eq 'd\n e\nf', """ 118 | #{a} 119 | #{b} 120 | #{c} 121 | """ 122 | eq 'd\n e\nf', """ 123 | #{a} 124 | #{b} 125 | #{c} 126 | """ 127 | eq ' d\ne\n f', """ 128 | #{a} 129 | #{b} 130 | #{c} 131 | """ 132 | 133 | eq "a\n e\nc", """ 134 | a 135 | #{b} 136 | c 137 | """ 138 | eq "a\n e\nc", """ 139 | a 140 | #{b} 141 | c 142 | """ 143 | eq "a\n e\nc", """ 144 | a 145 | #{b} 146 | c 147 | """ 148 | eq ' a\ne\n c', """ 149 | a 150 | #{b} 151 | c 152 | """ 153 | 154 | suite 'raw value preservation', -> 155 | 156 | test 'basic indentation', -> 157 | ast = parse ''' 158 | fn = -> 159 | body 160 | ''', raw: yes 161 | eq 'fn = ->\n body', ast.raw 162 | 163 | test 'numbers', -> 164 | ast = parse '0x0', raw: yes 165 | eq '0x0', ast.body.statements[0].raw 166 | 167 | suite 'position/offset preservation', -> 168 | 169 | test 'basic indentation', -> 170 | ast = parse ''' 171 | fn = -> 172 | body 173 | ''', raw: yes 174 | eq 3, ast.body.statements[0].expression.body.statements[0].column 175 | eq 11, ast.body.statements[0].expression.body.statements[0].offset 176 | -------------------------------------------------------------------------------- /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 | process.on 'exit', -> fs.unlinkSync historyFile 29 | 30 | testRepl = (desc, fn, testFn = test) -> 31 | input = new MockInputStream 32 | output = new MockOutputStream 33 | repl = Repl.start {input, output, historyFile} 34 | testFn desc, -> fn input, output, repl 35 | repl.emit 'exit' 36 | 37 | testRepl.skip = (desc, fn) -> testRepl desc, fn, test.skip 38 | 39 | ctrlV = { ctrl: true, name: 'v'} 40 | 41 | 42 | testRepl 'starts with coffee prompt', (input, output) -> 43 | eq 'tcoffee> ', output.lastWrite 1 44 | 45 | testRepl 'writes eval to output', (input, output) -> 46 | input.emitLine '1+1' 47 | eq '2', output.lastWrite 1 48 | 49 | testRepl 'comments are ignored', (input, output) -> 50 | input.emitLine '1 + 1 #foo' 51 | eq '2', output.lastWrite 1 52 | 53 | testRepl 'output in inspect mode', (input, output) -> 54 | input.emitLine '"1 + 1\\n"' 55 | eq "'1 + 1\\n'", output.lastWrite 1 56 | 57 | testRepl "variables are saved", (input, output) -> 58 | input.emitLine 'foo = "foo"' 59 | input.emitLine 'foobar = "#{foo}bar"' 60 | eq "'foobar'", output.lastWrite 1 61 | testRepl 'empty command evaluates to undefined', (input, output) -> 62 | input.emitLine '' 63 | eq 'tcoffee> ', output.lastWrite 0 64 | eq 'tcoffee> ', output.lastWrite 2 65 | 66 | testRepl 'ctrl-v toggles multiline prompt', (input, output) -> 67 | input.emit 'keypress', null, ctrlV 68 | # eq '------> ', output.lastWrite 0 # FIX LATER 69 | 70 | input.emit 'keypress', null, ctrlV 71 | eq 'tcoffee> ', output.lastWrite 0 72 | 73 | testRepl 'multiline continuation changes prompt', (input, output) -> 74 | input.emit 'keypress', null, ctrlV 75 | input.emitLine '' 76 | # eq '...... ', output.lastWrite 0 # FIX LATER 77 | 78 | testRepl 'evaluates multiline', (input, output) -> 79 | # Stubs. Could assert on their use. 80 | output.cursorTo = output.clearLine = -> 81 | 82 | input.emit 'keypress', null, ctrlV 83 | input.emitLine 'do ->' 84 | input.emitLine ' 1 + 1' 85 | input.emit 'keypress', null, ctrlV 86 | eq '2', output.lastWrite 1 87 | 88 | testRepl 'variables in scope are preserved', (input, output) -> 89 | input.emitLine 'a = 1' 90 | input.emitLine 'do -> a = 2' 91 | input.emitLine 'a' 92 | eq '2', output.lastWrite 1 93 | 94 | testRepl 'existential assignment of previously declared variable', (input, output) -> 95 | input.emitLine 'a = null' 96 | input.emitLine 'a ?= 42' 97 | eq '42', output.lastWrite 1 98 | 99 | testRepl 'keeps running after runtime error', (input, output) -> 100 | input.emitLine 'a = b' 101 | ok 0 <= (output.lastWrite 1).indexOf 'ReferenceError: b is not defined' 102 | input.emitLine 'a' 103 | ok 0 <= (output.lastWrite 1).indexOf 'ReferenceError: a is not defined' 104 | input.emitLine '0' 105 | eq '0', output.lastWrite 1 106 | 107 | test 'reads history from persistence file', -> 108 | input = new MockInputStream 109 | output = new MockOutputStream 110 | fs.writeFileSync historyFile, '0\n1\n' 111 | repl = Repl.start {input, output, historyFile} 112 | arrayEq ['1', '0'], repl.rli.history 113 | 114 | testRepl.skip 'writes history to persistence file', (input, output, repl) -> # Fails in node <= 0.8. 115 | fs.writeFileSync historyFile, '' 116 | input.emitLine '2' 117 | input.emitLine '3' 118 | eq '2\n3\n', (fs.readFileSync historyFile).toString() 119 | 120 | testRepl '.history shows history', (input, output, repl) -> 121 | repl.rli.history = history = ['1', '2', '3'] 122 | fs.writeFileSync historyFile, "#{history.join '\n'}\n" 123 | input.emitLine '.history' 124 | eq (history.reverse().join '\n'), output.lastWrite 1 125 | 126 | testRepl.skip '.clear clears history', (input, output, repl) -> # Fails in node <= 0.8. 127 | input = new MockInputStream 128 | output = new MockOutputStream 129 | fs.writeFileSync historyFile, '' 130 | repl = Repl.start {input, output, historyFile} 131 | input.emitLine '0' 132 | input.emitLine '1' 133 | eq '0\n1\n', (fs.readFileSync historyFile).toString() 134 | #arrayEq ['1', '0'], repl.rli.history 135 | input.emitLine '.clear' 136 | eq '.clear\n', (fs.readFileSync historyFile).toString() 137 | #arrayEq ['.clear'], repl.rli.history 138 | -------------------------------------------------------------------------------- /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/mizchi/TypedCoffeeScript/9580e64910f95519d61c9dffa2d9f0e3c34c11da/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 | -------------------------------------------------------------------------------- /test/type-checker.coffee: -------------------------------------------------------------------------------- 1 | { 2 | initializeGlobalTypes 3 | } = require '../lib/types' 4 | {Scope} = require '../lib/type-scope' 5 | 6 | {isAcceptable} = require '../lib/type-checker' 7 | 8 | suite 'TypeChecker', -> 9 | acceptable = (l, r) -> isAcceptable @scope, l, r 10 | 11 | setup -> 12 | @scope = new Scope 13 | initializeGlobalTypes(@scope) 14 | 15 | suite 'isAcceptable', -> 16 | test 'float accept int', -> 17 | left = 18 | nodeType: 'identifier' 19 | identifier: 20 | typeRef: 'Float' 21 | 22 | right = 23 | nodeType: 'identifier' 24 | identifier: 25 | typeRef: 'Int' 26 | ok isAcceptable @scope, left, right 27 | -------------------------------------------------------------------------------- /test/types.coffee: -------------------------------------------------------------------------------- 1 | # { 2 | # checkAcceptableObject, 3 | # initializeGlobalTypes, 4 | # Scope, 5 | # ArrayType 6 | # } = require '../src/types' 7 | 8 | # reporter = require '../src/reporter' 9 | # {ok} = require 'assert' 10 | # ng = (v) -> !v 11 | 12 | # root = new Scope 13 | # initializeGlobalTypes root 14 | 15 | # fail_typecheck = -> 16 | # ok reporter.has_errors() is true 17 | # reporter.errors = [] 18 | 19 | # suite 'Types', -> 20 | 21 | # suite '.checkAcceptableObject', -> 22 | 23 | # test 'string <> string', -> 24 | # ok false is checkAcceptableObject 'Number', 'Number', root 25 | 26 | # test 'Any <> string', -> 27 | # ok false is checkAcceptableObject 'Any', 'String', root 28 | 29 | # test 'String <> Possibities[String...]', -> 30 | # ok false is checkAcceptableObject 'String', possibilities: ['String', 'String'], root 31 | 32 | # test 'String <> Possibities[...]', -> 33 | # ok !!checkAcceptableObject 'String', possibilities: ['Number', 'String'], root 34 | 35 | # suite 'Object', -> 36 | # test 'throw Object <> string' 37 | # # checkAcceptableObject {}, 'Number' 38 | # # fail_typecheck() 39 | 40 | # test 'Fill all', -> 41 | # ok false is checkAcceptableObject {x: 'Number'}, {x: 'Number'}, root 42 | 43 | # test 'Fill all with unused right params', -> 44 | # ok false is checkAcceptableObject {x: 'Number'}, {x: 'Number', y: 'Number'}, root 45 | 46 | # test 'throw not filled right', -> 47 | # ok !!checkAcceptableObject {x: 'Number', y: 'Number'}, {x: 'Number'}, root 48 | 49 | # suite 'Array', -> 50 | # test 'pure array', -> 51 | # ok false is checkAcceptableObject "Array", (array: 'String'), root 52 | 53 | # test 'throw non-array definition', -> 54 | # ok !!checkAcceptableObject "Number", (array: 'String'), root 55 | 56 | # test 'fill array', -> 57 | # ok false is checkAcceptableObject (new ArrayType "Number"), (new ArrayType "Number"), root 58 | 59 | # test 'fill array with raw object', -> 60 | # ok false is checkAcceptableObject (array: {n: 'String'}), (array: {n:'String'}), root 61 | 62 | # test 'throw not filled array', -> 63 | # ok !!checkAcceptableObject (new ArrayType "Number"), (new ArrayType "String"), root 64 | 65 | # test 'fill all array possibilities ', -> 66 | # ok false is checkAcceptableObject (array: {n: 'String'}), (array:[{n:'String'}, {n: 'String'}]), root 67 | 68 | # test 'fill array with complecated object', -> 69 | # ok false is checkAcceptableObject (array: { 70 | # x: 'Number' 71 | # y: 'Number' 72 | # }), (array:[ 73 | # {x: 'Number', y: 'Number'}, 74 | # {x: 'Number', y: 'Number', name: 'String'} 75 | # ]), root 76 | 77 | # test 'throw not filled array(with complecated object)', -> 78 | # ok !!checkAcceptableObject (array: { 79 | # x: 'Number' 80 | # y: 'Number' 81 | # }), (array:[ 82 | # {x: 'Number', y: 'Number'}, 83 | # {x: 'Number', name: 'String'} 84 | # ]), root 85 | --------------------------------------------------------------------------------