tests are now passing on symbology

This commit is contained in:
Jeremy Ashkenas
2010-07-24 22:23:37 -07:00
parent 492ee57b8f
commit 88847df70b
42 changed files with 514 additions and 522 deletions

View File

@@ -1,33 +1,33 @@
fs: require 'fs'
{helpers}: require('./lib/helpers')
CoffeeScript: require './lib/coffee-script'
{spawn, exec}: require('child_process')
fs = require 'fs'
{helpers} = require './lib/helpers'
CoffeeScript = require './lib/coffee-script'
{spawn, exec} = require 'child_process'
# ANSI Terminal Colors.
red: '\033[0;31m'
green: '\033[0;32m'
reset: '\033[0m'
red = '\033[0;31m'
green = '\033[0;32m'
reset = '\033[0m'
# Run a CoffeeScript through our node/coffee interpreter.
run: (args) ->
proc: spawn 'bin/coffee', args
run = (args) ->
proc = spawn 'bin/coffee', args
proc.stderr.on 'data', (buffer) -> puts buffer.toString()
proc.on 'exit', (status) -> process.exit(1) if status != 0
# Log a message with a color.
log: (message, color, explanation) ->
log = (message, color, explanation) ->
puts "$color$message$reset ${explanation or ''}"
option '-p', '--prefix [DIR]', 'set the installation prefix for `cake install`'
task 'install', 'install CoffeeScript into /usr/local (or --prefix)', (options) ->
base: options.prefix or '/usr/local'
lib: "$base/lib/coffee-script"
bin: "$base/bin"
node: "~/.node_libraries/coffee-script"
puts "Installing CoffeeScript to $lib"
puts "Linking to $node"
puts "Linking 'coffee' to $bin/coffee"
base = options.prefix or '/usr/local'
lib = "$base/lib/coffee-script"
bin = "$base/bin"
node = "~/.node_libraries/coffee-script"
puts "Installing CoffeeScript to $lib"
puts "Linking to $node"
puts "Linking 'coffee' to $bin/coffee"
exec([
"mkdir -p $lib $bin"
"cp -rf bin lib LICENSE README package.json src $lib"
@@ -41,8 +41,8 @@ task 'install', 'install CoffeeScript into /usr/local (or --prefix)', (options)
task 'build', 'build the CoffeeScript language from source', ->
files: fs.readdirSync 'src'
files: 'src/' + file for file in files when file.match(/\.coffee$/)
files = fs.readdirSync 'src'
files = 'src/' + file for file in files when file.match(/\.coffee$/)
run ['-c', '-o', 'lib'].concat(files)
@@ -55,9 +55,9 @@ task 'build:full', 'rebuild the source twice, and run the tests', ->
task 'build:parser', 'rebuild the Jison parser (run build first)', ->
require 'jison'
parser: require('./lib/grammar').parser
js: parser.generate()
parserPath: 'lib/parser.js'
parser = require('./lib/grammar').parser
js = parser.generate()
parserPath = 'lib/parser.js'
fs.writeFile parserPath, js
@@ -87,23 +87,23 @@ task 'doc:underscore', 'rebuild the Underscore.coffee documentation page', ->
task 'loc', 'count the lines of source code in the CoffeeScript compiler', ->
sources: ['src/coffee-script.coffee', 'src/grammar.coffee', 'src/helpers.coffee', 'src/lexer.coffee', 'src/nodes.coffee', 'src/rewriter.coffee', 'src/scope.coffee']
sources = ['src/coffee-script.coffee', 'src/grammar.coffee', 'src/helpers.coffee', 'src/lexer.coffee', 'src/nodes.coffee', 'src/rewriter.coffee', 'src/scope.coffee']
exec "cat ${ sources.join(' ') } | grep -v '^\\( *#\\|\\s*$\\)' | wc -l | tr -s ' '", (err, stdout) ->
print stdout
task 'test', 'run the CoffeeScript language test suite', ->
helpers.extend global, require 'assert'
passedTests: failedTests: 0
startTime: new Date
originalOk: ok
passedTests = failedTests = 0
startTime = new Date
originalOk = ok
helpers.extend global, {
ok: (args...) -> passedTests += 1; originalOk(args...)
CoffeeScript: CoffeeScript
}
process.on 'exit', ->
time: ((new Date - startTime) / 1000).toFixed(2)
message: "passed $passedTests tests in $time seconds$reset"
time = ((new Date - startTime) / 1000).toFixed(2)
message = "passed $passedTests tests in $time seconds$reset"
if failedTests
log "failed $failedTests and $message", red
else
@@ -111,7 +111,7 @@ task 'test', 'run the CoffeeScript language test suite', ->
fs.readdir 'test', (err, files) ->
files.forEach (file) ->
return unless file.match(/\.coffee$/i)
source: path.join 'test', file
source = path.join 'test', file
fs.readFile source, (err, code) ->
try
CoffeeScript.run code.toString(), {source: source}

View File

@@ -76,16 +76,16 @@
})
],
Assign: [
o("Assignable ASSIGN Expression", function() {
o("Assignable = Expression", function() {
return new AssignNode($1, $3);
})
],
AssignObj: [
o("Identifier", function() {
return new ValueNode($1);
}), o("AlphaNumeric"), o("Identifier ASSIGN Expression", function() {
}), o("AlphaNumeric"), o("Identifier : Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("AlphaNumeric ASSIGN Expression", function() {
}), o("AlphaNumeric : Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("Comment")
],
@@ -225,7 +225,7 @@
ClassAssign: [
o("AssignObj", function() {
return $1;
}), o("ThisProperty ASSIGN Expression", function() {
}), o("ThisProperty : Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'this');
})
],
@@ -609,7 +609,7 @@
})
]
};
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '==', '!='], ["left", '&&', '||', 'OP?'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE']];
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '==', '!='], ["left", '&&', '||', 'OP?'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", '=', ':', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE']];
tokens = [];
_a = grammar;
for (name in _a) {

View File

@@ -1,5 +1,5 @@
(function(){
var ASSIGNED, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NEXT_CHARACTER, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, compact, count, helpers, include, starts;
var ASSIGNED, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NEXT_CHARACTER, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, compact, count, helpers, include, starts;
var __slice = Array.prototype.slice;
if (typeof process !== "undefined" && process !== null) {
_a = require('./rewriter');
@@ -309,12 +309,10 @@
value = value || this.chunk.substr(0, 1);
prevSpaced = this.prev() && this.prev().spaced;
tag = value;
if (value.match(ASSIGNMENT)) {
tag = 'ASSIGN';
if (include(JS_FORBIDDEN, this.value)) {
this.assignmentError();
}
} else if (value === ';') {
if (value === '=' && include(JS_FORBIDDEN, this.value)) {
this.assignmentError();
}
if (value === ';') {
tag = 'TERMINATOR';
} else if (value === '?' && prevSpaced) {
tag = 'OP?';
@@ -604,7 +602,6 @@
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
LAST_DENTS = /\n([ \t]*)/g;
LAST_DENT = /\n([ \t]*)/;
ASSIGNMENT = /^[:=]$/;
REGEX_START = /^\/[^\/ ]/;
REGEX_INTERPOLATION = /([^\\]\$[a-zA-Z_@]|[^\\]\$\{.*[^\\]\})/;
REGEX_END = /^(([imgy]{1,4})\b|\W|$)/;

File diff suppressed because one or more lines are too long

View File

@@ -44,7 +44,7 @@ helpers.extend global, {
# Run `cake`. Executes all of the tasks you pass, in order. Note that Node's
# asynchrony may cause tasks to execute in a different order than you'd expect.
# If no tasks are passed, print the help screen.
exports.run: ->
exports.run = ->
path.exists 'Cakefile', (exists) ->
throw new Error("Cakefile not found in ${process.cwd()}") unless exists
args = process.argv[2...process.argv.length]
@@ -55,7 +55,7 @@ exports.run: ->
invoke arg for arg in options.arguments
# Display the list of Cake tasks in a format similar to `rake -T`
printTasks: ->
printTasks = ->
puts ''
for all name, task of tasks
spaces = 20 - name.length
@@ -65,6 +65,6 @@ printTasks: ->
puts oparse.help() if switches.length
# Print an error and exit when attempting to all an undefined task.
missingTask: (task) ->
missingTask = (task) ->
puts "No such task: \"$task\""
process.exit 1

View File

@@ -30,7 +30,7 @@ lexer = new Lexer
# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
# compiler.
exports.compile = compile = (code, options) ->
options = or {}
options ||= {}
try
(parser.parse lexer.tokenize code).compile options
catch err
@@ -61,7 +61,7 @@ exports.run = ((code, options) ->
parser.lexer = {
lex: ->
token = @tokens[@pos] or [""]
@pos = + 1
@pos += 1
this.yylineno = token[2]
this.yytext = token[1]
token[0]

View File

@@ -107,7 +107,7 @@ compileStdio = ->
code = ''
stdin = process.openStdin()
stdin.on 'data', (buffer) ->
code = + buffer.toString() if buffer
code += buffer.toString() if buffer
stdin.on 'end', ->
compileScript 'stdio', code
@@ -156,7 +156,7 @@ printTokens = (tokens) ->
parseOptions = ->
optionParser = new optparse.OptionParser SWITCHES, BANNER
o = options = optionParser.parse(process.argv[2...process.argv.length])
options.compile = or !!o.output
options.compile ||= !!o.output
options.run = not (o.compile or o.print or o.lint)
options.print = !! (o.print or (o.eval or o.stdio and o.compile))
sources = options.arguments

View File

@@ -139,7 +139,7 @@ grammar = {
# Assignment of a variable, property, or index to a value.
Assign: [
o "Assignable ASSIGN Expression", -> new AssignNode $1, $3
o "Assignable = Expression", -> new AssignNode $1, $3
]
# Assignment when it happens within an object literal. The difference from
@@ -147,8 +147,8 @@ grammar = {
AssignObj: [
o "Identifier", -> new ValueNode $1
o "AlphaNumeric"
o "Identifier ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "AlphaNumeric ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "Identifier : Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "AlphaNumeric : Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "Comment"
]
@@ -279,7 +279,7 @@ grammar = {
# Assignments that can happen directly inside a class declaration.
ClassAssign: [
o "AssignObj", -> $1
o "ThisProperty ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'this'
o "ThisProperty : Expression", -> new AssignNode new ValueNode($1), $3, 'this'
]
# A list of assignments to a class.
@@ -575,7 +575,7 @@ grammar = {
# And not:
#
# (2 + 3) * 4
operators: [
operators = [
["left", '?']
["nonassoc", 'UMINUS', 'UPLUS', '!', '!!', '~', '++', '--']
["left", '*', '/', '%']
@@ -593,7 +593,7 @@ operators: [
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
["right", 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS']
["left", 'EXTENDS']
["right", 'ASSIGN', 'RETURN']
["right", '=', ':', 'RETURN']
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE']
]

View File

@@ -35,7 +35,7 @@ helpers.count = count = (string, letter) ->
num = 0
pos = indexOf string, letter
while pos isnt -1
num = + 1
num += 1
pos = indexOf string, letter, pos + 1
num

View File

@@ -82,7 +82,7 @@ exports.Lexer = class Lexer
# though `is` means `===` otherwise.
identifierToken: ->
return false unless id = @match IDENTIFIER, 1
@i = + id.length
@i += id.length
forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1
tag = 'IDENTIFIER'
tag = id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id))
@@ -258,13 +258,11 @@ exports.Lexer = class Lexer
value = match and match[1]
space = match and match[2]
@tagParameters() if value and value.match CODE
value = or @chunk.substr 0, 1
value ||= @chunk.substr 0, 1
prevSpaced = @prev() and @prev().spaced
tag = value
if value.match ASSIGNMENT
tag = 'ASSIGN'
@assignmentError() if include JS_FORBIDDEN, @value
else if value is ';'
@assignmentError() if value is '=' and include JS_FORBIDDEN, @value
if value is ';'
tag = 'TERMINATOR'
else if value is '?' and prevSpaced
tag = 'OP?'
@@ -325,7 +323,7 @@ exports.Lexer = class Lexer
return if @tag() isnt ')'
i = 0
loop
i = + 1
i += 1
tok = @prev i
return if not tok
switch tok[0]
@@ -353,27 +351,27 @@ exports.Lexer = class Lexer
# contents of the string. This method allows us to have strings within
# interpolations within strings, ad infinitum.
balancedString: (str, delimited, options) ->
options = or {}
options ||= {}
slash = delimited[0][0] is '/'
levels = []
i = 0
while i < str.length
if levels.length and starts str, '\\', i
i = + 1
i += 1
else
for pair in delimited
[open, close] = pair
if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
levels.pop()
i = + close.length - 1
i = + 1 unless levels.length
i += close.length - 1
i += 1 unless levels.length
break
else if starts str, open, i
levels.push(pair)
i = + open.length - 1
i += open.length - 1
break
break if not levels.length or slash and starts str, '\n', i
i = + 1
i += 1
if levels.length
return false if slash
throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}"
@@ -390,7 +388,7 @@ exports.Lexer = class Lexer
# new Lexer, tokenize the interpolated contents, and merge them into the
# token stream.
interpolateString: (str, options) ->
options = or {}
options ||= {}
if str.length < 3 or not starts str, '"'
@token 'STRING', str
else
@@ -400,13 +398,13 @@ exports.Lexer = class Lexer
[i, pi] = [1, 1]
while i < str.length - 1
if starts str, '\\', i
i = + 1
i += 1
else if match = str.substring(i).match INTERPOLATION
[group, interp] = match
interp = "this.${ interp.substring(1) }" if starts interp, '@'
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
tokens.push ['IDENTIFIER', interp]
i = + group.length - 1
i += group.length - 1
pi = i + 1
else if (expr = @balancedString str.substring(i), [['${', '}']])
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
@@ -419,9 +417,9 @@ exports.Lexer = class Lexer
tokens.push ['TOKENS', nested]
else
tokens.push ['STRING', "$quote$quote"]
i = + expr.length - 1
i += expr.length - 1
pi = i + 1
i = + 1
i += 1
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1
tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING'
interpolated = tokens.length > 1
@@ -524,7 +522,6 @@ CODE = /^((-|=)>)/
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/
LAST_DENTS = /\n([ \t]*)/g
LAST_DENT = /\n([ \t]*)/
ASSIGNMENT = /^[:=]$/
# Regex-matching-regexes.
REGEX_START = /^\/[^\/ ]/
@@ -547,9 +544,7 @@ NEXT_CHARACTER = /^\s*(\S)/
# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
#
# Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX = [
'NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']'
]
NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']']
# Tokens which could legitimately be invoked or indexed. A opening
# parentheses or bracket following these tokens will be recorded as the start

View File

@@ -76,7 +76,7 @@ exports.BaseNode = class BaseNode
idt: (tabs) ->
idt = @tab or ''
num = (tabs or 0) + 1
idt = + TAB while num = - 1
idt += TAB while num -= 1
idt
# Construct a node that returns the current node's result.
@@ -112,7 +112,7 @@ exports.BaseNode = class BaseNode
# `toString` representation of the node, for inspecting the parse tree.
# This is what `coffee --nodes` prints out.
toString: (idt, override) ->
idt = or ''
idt ||= ''
children = (child.toString idt + TAB for child in @collectChildren()).join('')
'\n' + idt + (override or @class) + children
@@ -180,7 +180,7 @@ exports.Expressions = class Expressions extends BaseNode
makeReturn: ->
idx = @expressions.length - 1
last = @expressions[idx]
last = @expressions[idx = - 1] if last instanceof CommentNode
last = @expressions[idx -= 1] if last instanceof CommentNode
return this if not last or last instanceof ReturnNode
@expressions[idx] = last.makeReturn()
this
@@ -193,7 +193,7 @@ exports.Expressions = class Expressions extends BaseNode
# An **Expressions** is the only node that can serve as the root.
compile: (o) ->
o = or {}
o ||= {}
if o.scope then super(o) else @compileRoot(o)
compileNode: (o) ->
@@ -229,7 +229,7 @@ exports.Expressions = class Expressions extends BaseNode
# Wrap up the given nodes as an **Expressions**, unless it already happens
# to be one.
Expressions.wrap: (nodes) ->
Expressions.wrap = (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
new Expressions(nodes)
@@ -354,7 +354,7 @@ exports.ValueNode = class ValueNode extends BaseNode
only = del o, 'onlyFirst'
op = del o, 'operation'
props = if only then @properties[0...@properties.length - 1] else @properties
o.chainRoot = or this
o.chainRoot ||= this
baseline = @base.compile o
baseline = "($baseline)" if @hasProperties() and (@base instanceof ObjectNode or @isNumber())
complete = @last = baseline
@@ -366,11 +366,11 @@ exports.ValueNode = class ValueNode extends BaseNode
temp = o.scope.freeVariable()
complete = "(${ baseline = temp } = ($complete))"
complete = "typeof $complete === \"undefined\" || $baseline" if i is 0 and @isStart(o)
complete = + @SOAK + (baseline = + prop.compile(o))
complete += @SOAK + (baseline += prop.compile(o))
else
part = prop.compile(o)
baseline = + part
complete = + part
baseline += part
complete += part
@last = part
if op and @wrapped then "($complete)" else complete
@@ -502,7 +502,7 @@ exports.AccessorNode = class AccessorNode extends BaseNode
compileNode: (o) ->
name = @name.compile o
o.chainRoot.wrapped = or @soakNode
o.chainRoot.wrapped ||= @soakNode
namePart = if name.match(IS_STRING) then "[$name]" else ".$name"
@prototype + namePart
@@ -518,7 +518,7 @@ exports.IndexNode = class IndexNode extends BaseNode
@index = index
compileNode: (o) ->
o.chainRoot.wrapped = or @soakNode
o.chainRoot.wrapped ||= @soakNode
idx = @index.compile o
prefix = if @proto then '.prototype' else ''
"$prefix[$idx]"
@@ -570,7 +570,7 @@ exports.RangeNode = class RangeNode extends BaseNode
[from, to] = [parseInt(@fromNum, 10), parseInt(@toNum, 10)]
idx = del o, 'index'
step = del o, 'step'
step = and "$idx += ${step.compile(o)}"
step &&= "$idx += ${step.compile(o)}"
if from <= to
"$idx = $from; $idx <$@equals $to; ${step or "$idx++"}"
else
@@ -721,8 +721,8 @@ exports.ClassNode = class ClassNode extends BaseNode
continue
if func instanceof CodeNode and func.bound
func.bound = false
constScope = or new Scope(o.scope, constructor.body, constructor)
me = or constScope.freeVariable()
constScope ||= new Scope(o.scope, constructor.body, constructor)
me ||= constScope.freeVariable()
pname = pvar.compile(o)
constructor.body.push new ReturnNode literal 'this' if constructor.body.empty()
constructor.body.unshift literal "this.${pname} = function(){ return ${className}.prototype.${pname}.apply($me, arguments); }"
@@ -883,7 +883,7 @@ exports.CodeNode = class CodeNode extends BaseNode
splat.trailings.push(param)
else
params.push(param)
i = + 1
i += 1
params = (param.compile(o) for param in params)
@body.makeReturn()
@body.rewriteThis() if @bound
@@ -902,7 +902,7 @@ exports.CodeNode = class CodeNode extends BaseNode
traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope
toString: (idt) ->
idt = or ''
idt ||= ''
children = (child.toString(idt + TAB) for child in @collectChildren()).join('')
"\n$idt$children"
@@ -1385,7 +1385,7 @@ exports.IfNode = class IfNode extends BaseNode
# The **IfNode** only compiles into a statement if either of its bodies needs
# to be a statement. Otherwise a ternary is safe.
isStatement: ->
@statement = or !!(@tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
@statement ||= !!(@tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
compileCondition: (o) ->
(cond.compile(o) for cond in flatten([@condition])).join(' || ')
@@ -1395,8 +1395,8 @@ exports.IfNode = class IfNode extends BaseNode
makeReturn: ->
if @isStatement()
@body = and @ensureExpressions(@body.makeReturn())
@elseBody = and @ensureExpressions(@elseBody.makeReturn())
@body &&= @ensureExpressions(@body.makeReturn())
@elseBody &&= @ensureExpressions(@elseBody.makeReturn())
this
else
new ReturnNode this

View File

@@ -30,7 +30,7 @@ exports.OptionParser = class OptionParser
matchedRule = no
for rule in @rules
if rule.shortFlag is arg or rule.longFlag is arg
options[rule.name] = if rule.hasArgument then args[i = + 1] else true
options[rule.name] = if rule.hasArgument then args[i += 1] else true
matchedRule = yes
break
throw new Error "unrecognized option: $arg" if isOption and not matchedRule

View File

@@ -46,7 +46,7 @@ exports.Rewriter = class Rewriter
loop
break unless @tokens[i]
move = block @tokens[i - 1], @tokens[i], @tokens[i + 1], i
i = + move
i += move
true
# Massage newlines and indentations so that comments don't have to be
@@ -94,20 +94,20 @@ exports.Rewriter = class Rewriter
switch token[0]
when 'CALL_START' then parens.push 0
when 'INDEX_START' then brackets.push 0
when '(' then parens[parens.length - 1] = + 1
when '[' then brackets[brackets.length - 1] = + 1
when '(' then parens[parens.length - 1] += 1
when '[' then brackets[brackets.length - 1] += 1
when ')'
if parens[parens.length - 1] is 0
parens.pop()
token[0] = 'CALL_END'
else
parens[parens.length - 1] = - 1
parens[parens.length - 1] -= 1
when ']'
if brackets[brackets.length - 1] == 0
brackets.pop()
token[0] = 'INDEX_END'
else
brackets[brackets.length - 1] = - 1
brackets[brackets.length - 1] -= 1
return 1
# Methods may be optionally called without parentheses, for simple cases.
@@ -123,12 +123,12 @@ exports.Rewriter = class Rewriter
size
@scanTokens (prev, token, post, i) =>
tag = token[0]
stack[stack.length - 2] = + stack.pop() if tag is 'OUTDENT'
stack[stack.length - 2] += stack.pop() if tag is 'OUTDENT'
open = stack[stack.length - 1] > 0
if prev and prev.spaced and include(IMPLICIT_FUNC, prev[0]) and include(IMPLICIT_CALL, tag) and
not (tag is '!' and (post[0] in ['IN', 'OF']))
@tokens.splice i, 0, ['CALL_START', '(', token[2]]
stack[stack.length - 1] = + 1
stack[stack.length - 1] += 1
stack.push 0 if include(EXPRESSION_START, tag)
return 2
if include(EXPRESSION_START, tag)
@@ -147,7 +147,7 @@ exports.Rewriter = class Rewriter
stack.pop() if tag isnt 'OUTDENT' and include EXPRESSION_END, tag
return size
if tag isnt 'OUTDENT' and include EXPRESSION_END, tag
stack[stack.length - 2] = + stack.pop()
stack[stack.length - 2] += stack.pop()
return 1
return 1
@@ -174,7 +174,7 @@ exports.Rewriter = class Rewriter
idx = i + 1
parens = 0
loop
idx = + 1
idx += 1
tok = @tokens[idx]
pre = @tokens[idx - 1]
if (not tok or
@@ -184,8 +184,8 @@ exports.Rewriter = class Rewriter
insertion = if pre[0] is "," then idx - 1 else idx
@tokens.splice insertion, 0, outdent
break
parens = + 1 if tok[0] is '('
parens = - 1 if tok[0] is ')'
parens += 1 if tok[0] is '('
parens -= 1 if tok[0] is ')'
return 1 unless token[0] is 'THEN'
@tokens.splice i, 1
return 0
@@ -198,11 +198,11 @@ exports.Rewriter = class Rewriter
@scanTokens (prev, token, post, i) =>
for pair in pairs
[open, close] = pair
levels[open] = or 0
levels[open] ||= 0
if token[0] is open
openLine[open] = token[2] if levels[open] == 0
levels[open] = + 1
levels[open] = - 1 if token[0] is close
levels[open] += 1
levels[open] -= 1 if token[0] is close
throw new Error("too many ${token[1]} on line ${token[2] + 1}") if levels[open] < 0
return 1
unclosed = key for key, value of levels when value > 0
@@ -239,7 +239,7 @@ exports.Rewriter = class Rewriter
return 1
else if include EXPRESSION_END, tag
if debt[inv] > 0
debt[inv] = - 1
debt[inv] -= 1
@tokens.splice i, 1
return 0
else
@@ -247,7 +247,7 @@ exports.Rewriter = class Rewriter
mtag = match[0]
oppos = INVERSES[mtag]
return 1 if tag is oppos
debt[mtag] = + 1
debt[mtag] += 1
val = [oppos, if mtag is 'INDENT' then match[1] else oppos]
if @tokens[i + 2]?[0] is mtag
@tokens.splice i + 3, 0, val

View File

@@ -1,8 +1,8 @@
area: (x, y, x1, y1) ->
area = (x, y, x1, y1) ->
(x - x1) * (x - y1)
x: y: 10
x1: y1: 20
x = y = 10
x1 = y1 = 20
ok area(x, y, x1, y1) is 100
@@ -17,9 +17,9 @@ ok(area(
) is 100)
sumOfArgs: ->
sum: 0
sum: + val for val in arguments
sumOfArgs = ->
sum = 0
sum += val for val in arguments
sum
ok sumOfArgs(1, 2, 3, 4, 5) is 15

View File

@@ -1,29 +1,29 @@
# Can assign the result of a try/catch block.
result: try
result = try
nonexistent * missing
catch error
true
result2: try nonexistent * missing catch error then true
result2 = try nonexistent * missing catch error then true
ok result is true and result2 is true
# Can assign a conditional statement.
getX: -> 10
getX = -> 10
if x: getX() then 100
if x = getX() then 100
ok x is 10
x: if getX() then 100
x = if getX() then 100
ok x is 100
# This-assignment.
tester: ->
@example: -> 'example function'
tester = ->
@example = -> 'example function'
this
ok tester().example() is 'example function'

View File

@@ -1,14 +1,14 @@
# Basic blocks.
results: [1, 2, 3].map (x) ->
results = [1, 2, 3].map (x) ->
x * x
ok results.join(' ') is '1 4 9'
# Chained blocks, with proper indentation levels:
results: []
results = []
counter: {
counter = {
tick: (func) ->
results.push func()
this
@@ -26,8 +26,8 @@ ok results.join(' ') is '3 2 1'
# Make incorrect indentation safe.
func: ->
obj: {
func = ->
obj = {
key: 10
}
obj.key - 5

View File

@@ -1,8 +1,8 @@
# Test with break at the top level.
array: [1,2,3]
callWithLambda: (l) -> null
array = [1,2,3]
callWithLambda = (l) -> null
for i in array
result: callWithLambda(->)
result = callWithLambda(->)
if i == 2
puts "i = 2"
else
@@ -12,10 +12,10 @@ ok result is null
# Test with break *not* at the top level.
someFunc: (input) ->
takesLambda: (l) -> null
someFunc = (input) ->
takesLambda = (l) -> null
for i in [1,2]
result: takesLambda(->)
result = takesLambda(->)
if input == 1
return 1
else

View File

@@ -1,16 +1,16 @@
# Basic chained function calls.
identityWrap: (x) ->
identityWrap = (x) ->
-> x
result: identityWrap(identityWrap(true))()()
result = identityWrap(identityWrap(true))()()
ok result
# Chained accesses split on period/newline, backwards and forwards.
str: 'god'
str = 'god'
result: str.
result = str.
split('').
reverse().
reverse().
@@ -18,7 +18,7 @@ result: str.
ok result.join('') is 'dog'
result: str
result = str
.split('')
.reverse()
.reverse()
@@ -28,7 +28,7 @@ ok result.join('') is 'dog'
# Newline suppression for operators.
six:
six =
1 +
2 +
3
@@ -37,7 +37,7 @@ ok six is 6
# Ensure that indented array literals don't trigger whitespace rewriting.
func: () ->
func = () ->
ok arguments.length is 1
func(

View File

@@ -16,13 +16,13 @@ class SecondChild extends FirstChild
class ThirdChild extends SecondChild
constructor: ->
@array: [1, 2, 3]
@array = [1, 2, 3]
# Gratuitous comment for testing.
func: (string) ->
super('three/') + string
result: (new ThirdChild).func 'four'
result = (new ThirdChild).func 'four'
ok result is 'zero/one/two/three/four'
ok Base.static('word') is 'static/word'
@@ -30,7 +30,7 @@ ok Base.static('word') is 'static/word'
class TopClass
constructor: (arg) ->
@prop: 'top-' + arg
@prop = 'top-' + arg
class SuperClass extends TopClass
constructor: (arg) ->
@@ -44,7 +44,7 @@ ok (new SubClass).prop is 'top-super-sub'
class OneClass
constructor: (name) -> @name: name
constructor: (name) -> @name = name
class TwoClass extends OneClass
@@ -52,45 +52,45 @@ ok (new TwoClass('three')).name is 'three'
# And now the same tests, but written in the manual style:
Base: ->
Base::func: (string) ->
Base = ->
Base::func = (string) ->
'zero/' + string
Base::['func-func']: (string) ->
Base::['func-func'] = (string) ->
"dynamic-$string"
FirstChild: ->
FirstChild = ->
FirstChild extends Base
FirstChild::func: (string) ->
FirstChild::func = (string) ->
super('one/') + string
SecondChild: ->
SecondChild = ->
SecondChild extends FirstChild
SecondChild::func: (string) ->
SecondChild::func = (string) ->
super('two/') + string
ThirdChild: ->
@array: [1, 2, 3]
ThirdChild = ->
@array = [1, 2, 3]
this
ThirdChild extends SecondChild
ThirdChild::func: (string) ->
ThirdChild::func = (string) ->
super('three/') + string
result: (new ThirdChild).func 'four'
result = (new ThirdChild).func 'four'
ok result is 'zero/one/two/three/four'
ok (new ThirdChild)['func-func']('thing') is 'dynamic-thing'
TopClass: (arg) ->
@prop: 'top-' + arg
TopClass = (arg) ->
@prop = 'top-' + arg
this
SuperClass: (arg) ->
SuperClass = (arg) ->
super 'super-' + arg
this
SubClass: ->
SubClass = ->
super 'sub'
this
@@ -105,18 +105,18 @@ class ClassName
amI: ->
@ instanceof ClassName
obj: new ClassName
obj = new ClassName
ok obj.amI()
# super() calls in constructors of classes that are defined as object properties.
class Hive
constructor: (name) -> @name: name
constructor: (name) -> @name = name
class Hive.Bee extends Hive
constructor: (name) -> super
maya: new Hive.Bee 'Maya'
maya = new Hive.Bee 'Maya'
ok maya.name is 'Maya'
@@ -125,7 +125,7 @@ class Class
class: 'class'
name: -> @class
instance: new Class
instance = new Class
ok instance.class is 'class'
ok instance.name() is 'class'
@@ -134,14 +134,14 @@ ok instance.name() is 'class'
class Dog
constructor: (name) ->
@name: name
@name = name
bark: =>
"$@name woofs!"
spark: new Dog('Spark')
fido: new Dog('Fido')
fido.bark: spark.bark
spark = new Dog('Spark')
fido = new Dog('Fido')
fido.bark = spark.bark
ok fido.bark() is 'Spark woofs!'
@@ -154,32 +154,32 @@ class Mini
=>
@num
m: new Mini
m = new Mini
ok (func() for func in m.generate()).join(' ') is '10 10 10'
# Testing a contructor called with varargs.
class Connection
constructor: (one, two, three) ->
[@one, @two, @three]: [one, two, three]
[@one, @two, @three] = [one, two, three]
out: ->
"$@one-$@two-$@three"
list: [3, 2, 1]
conn: new Connection list...
list = [3, 2, 1]
conn = new Connection list...
ok conn instanceof Connection
ok conn.out() is '3-2-1'
# Test calling super and passing along all arguments.
class Parent
method: (args...) -> @args: args
method: (args...) -> @args = args
class Child extends Parent
method: -> super
c: new Child
c = new Child
c.method 1, 2, 3, 4
ok c.args.join(' ') is '1 2 3 4'
@@ -188,15 +188,15 @@ ok c.args.join(' ') is '1 2 3 4'
class Base
@extended: (subclass) ->
for key, value of @
subclass[key]: value
subclass[key] = value
class Element extends Base
@fromHTML: (html) ->
node: "..."
node = "..."
new @(node)
constructor: (node) ->
@node: node
@node = node
ok Element.extended is Base.extended
ok Element.__superClass__ is Base.prototype

View File

@@ -6,7 +6,7 @@
# comment
func: ->
func = ->
# comment
false
false # comment
@@ -32,7 +32,7 @@ func
func
# Line3
obj: {
obj = {
# comment
# comment
# comment
@@ -42,12 +42,12 @@ obj: {
# comment
}
result: if true # comment
result = if true # comment
false
ok not result
result: if false
result = if false
false
else # comment
45
@@ -55,7 +55,7 @@ else # comment
ok result is 45
test:
test =
'test ' +
'test ' + # comment
'test'
@@ -67,30 +67,30 @@ ok test is 'test test test'
Kind of like a heredoc.
###
func: ->
func = ->
###
Another block comment.
###
code
func: ->
one: ->
two: ->
three: ->
func = ->
one = ->
two = ->
three = ->
###
block.
###
four: ->
four = ->
fn1: ->
oneLevel: null
fn1 = ->
oneLevel = null
###
This isn't fine.
###
ok ok
obj: {
obj = {
a: 'b'
###
comment
@@ -98,7 +98,7 @@ obj: {
c: 'd'
}
arr: [
arr = [
1, 2, 3,
###
four
@@ -107,7 +107,7 @@ arr: [
]
# Spaced comments in if / elses.
result: if false
result = if false
1
# comment
@@ -121,14 +121,14 @@ else
ok result is 3
result: switch 'z'
result = switch 'z'
when 'z' then 7
# comment
ok result is 7
# Trailing-line comment before an outdent.
func: ->
func = ->
if true
true # comment
7
@@ -137,10 +137,10 @@ ok func() is 7
# Trailing herecomment in a function.
fn: ->
fn = ->
code
###
debug code commented
###
fn2: ->
fn2 = ->

View File

@@ -1,13 +1,13 @@
# Ensure that carriage returns don't break compilation on Windows.
CoffeeScript: require('./../lib/coffee-script')
Lexer: require('./../lib/lexer')
CoffeeScript = require('./../lib/coffee-script')
Lexer = require('./../lib/lexer')
js: CoffeeScript.compile("one\r\ntwo", {noWrap: on})
js = CoffeeScript.compile("one\r\ntwo", {noWrap: on})
ok js is "one;\ntwo;"
global.resultArray: []
global.resultArray = []
CoffeeScript.run("resultArray.push i for i of global", {noWrap: on, globals: on, source: 'tests'})
ok 'setInterval' in global.resultArray

View File

@@ -1,45 +1,45 @@
num: 10
num: - 5
num = 10
num -= 5
ok num is 5
num: -3
num = -3
ok num is -3
num: +3
num = +3
ok num is 3
num = * 10
num *= 10
ok num is 30
num: / 10
num /= 10
ok num is 3
val: false
val: or 'value'
val = false
val ||= 'value'
ok val is 'value'
val = and 'other'
val &&= 'other'
ok val is 'other'
val: null
val: ? 'value'
val = null
val ?= 'value'
ok val is 'value'
val: 6
val: -(10)
val = 6
val = -(10)
ok val is -10
val: - (10)
val -= (10)
ok val is -20

View File

@@ -1,51 +1,51 @@
# Basic array comprehensions.
nums: n * n for n in [1, 2, 3] when n % 2 isnt 0
results: n * 2 for n in nums
nums = n * n for n in [1, 2, 3] when n % 2 isnt 0
results = n * 2 for n in nums
ok results.join(',') is '2,18'
# Basic object comprehensions.
obj: {one: 1, two: 2, three: 3}
names: prop + '!' for prop of obj
odds: prop + '!' for prop, value of obj when value % 2 isnt 0
obj = {one: 1, two: 2, three: 3}
names = prop + '!' for prop of obj
odds = prop + '!' for prop, value of obj when value % 2 isnt 0
ok names.join(' ') is "one! two! three!"
ok odds.join(' ') is "one! three!"
# Basic range comprehensions.
nums: i * 3 for i in [1..3]
nums = i * 3 for i in [1..3]
negs: x for x in [-20..-5*2]
negs: negs[0..2]
negs = x for x in [-20..-5*2]
negs = negs[0..2]
result: nums.concat(negs).join(', ')
result = nums.concat(negs).join(', ')
ok result is '3, 6, 9, -20, -19, -18'
# With range comprehensions, you can loop in steps.
results: x for x in [0..25] by 5
results = x for x in [0..25] by 5
ok results.join(' ') is '0 5 10 15 20 25'
# And can loop downwards, with a negative step.
results: x for x in [5..1]
results = x for x in [5..1]
ok results.join(' ') is '5 4 3 2 1'
ok results.join(' ') is [(10-5)..(-2+3)].join(' ')
results: x for x in [10..1]
results = x for x in [10..1]
ok results.join(' ') is [10..1].join(' ')
results: x for x in [10...0] by -2
results = x for x in [10...0] by -2
ok results.join(' ') is [10, 8, 6, 4, 2].join(' ')
# Multiline array comprehension with filter.
evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
evens = for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
num *= -1
num -= 2
num * -1
@@ -58,10 +58,10 @@ ok 2 of evens
# Ensure that the closure wrapper preserves local variables.
obj: {}
obj = {}
for method in ['one', 'two', 'three']
obj[method]: ->
obj[method] = ->
"I'm " + method
ok obj.one() is "I'm one"
@@ -70,9 +70,9 @@ ok obj.three() is "I'm three"
# Even when referenced in the filter.
list: ['one', 'two', 'three']
list = ['one', 'two', 'three']
methods: for num, i in list when num isnt 'two' and i isnt 1
methods = for num, i in list when num isnt 'two' and i isnt 1
-> num + ' ' + i
ok methods.length is 2
@@ -81,17 +81,17 @@ ok methods[1]() is 'three 2'
# Naked ranges are expanded into arrays.
array: [0..10]
array = [0..10]
ok(num % 2 is 0 for num in array by 2)
# Nested comprehensions.
multiLiner:
multiLiner =
for x in [3..5]
for y in [3..5]
[x, y]
singleLiner:
singleLiner =
[x, y] for y in [3..5] for x in [3..5]
ok multiLiner.length is singleLiner.length
@@ -100,29 +100,29 @@ ok 5 is singleLiner[2][2][1]
# Comprehensions within parentheses.
result: null
store: (obj) -> result: obj
result = null
store = (obj) -> result = obj
store (x * 2 for x in [3, 2, 1])
ok result.join(' ') is '6 4 2'
# Closure-wrapped comprehensions that refer to the "arguments" object.
expr: ->
result: item * item for item in arguments
expr = ->
result = item * item for item in arguments
ok expr(2, 4, 8).join(' ') is '4 16 64'
# Fast object comprehensions over all properties, including prototypal ones.
class Cat
constructor: -> @name: 'Whiskers'
constructor: -> @name = 'Whiskers'
breed: 'tabby'
hair: 'cream'
whiskers: new Cat
own: value for key, value of whiskers
all: value for all key, value of whiskers
whiskers = new Cat
own = value for key, value of whiskers
all = value for all key, value of whiskers
ok own.join(' ') is 'Whiskers'
ok all.sort().join(' ') is 'Whiskers cream tabby'

View File

@@ -1,13 +1,13 @@
ok(if mySpecialVariable? then false else true)
mySpecialVariable: false
mySpecialVariable = false
ok(if mySpecialVariable? then true else false)
# Existential assignment.
a: 5
a: null
a = 5
a = null
a ?= 10
b ?= 10
@@ -15,15 +15,15 @@ ok a is 10 and b is 10
# The existential operator.
z: null
x: z ? "EX"
z = null
x = z ? "EX"
ok z is null and x is "EX"
# Only evaluate once.
counter: 0
getNextNode: ->
counter = 0
getNextNode = ->
throw "up" if counter
counter++
@@ -31,7 +31,7 @@ ok(if getNextNode()? then true else false)
# Existence chains, soaking up undefined properties:
obj: {
obj = {
prop: "hello"
}
@@ -49,7 +49,7 @@ ok obj?['non']?['existent'].property is undefined
# Soaks and caches method calls as well.
arr: ["--", "----"]
arr = ["--", "----"]
ok arr.pop()?.length is 4
ok arr.pop()?.length is 2
@@ -59,19 +59,19 @@ ok arr.pop()?.length?.non?.existent()?.property is undefined
# Soaks method calls safely.
value: undefined
result: value?.toString().toLowerCase()
value = undefined
result = value?.toString().toLowerCase()
ok result is undefined
value: 10
result: value?.toString().toLowerCase()
value = 10
result = value?.toString().toLowerCase()
ok result is '10'
# Soaks constructor invocations.
a: 0
a = 0
class Foo
constructor: -> a += 1
bar: "bat"
@@ -81,20 +81,20 @@ ok a is 1
# Safely existence test on soaks.
result: not value?.property?
result = not value?.property?
ok result
# Safely calls values off of non-existent variables.
result: nothing?.value
result = nothing?.value
ok result is undefined
# Assign to the result of an exsitential operation with a minus.
x: null ? - 1
x = null ? - 1
ok x is - 1
# Things that compile to ternaries should force parentheses, like operators do.
duration: if options?.animated then 150 else 0
duration = if options?.animated then 150 else 0
ok duration is 0

View File

@@ -1,10 +1,10 @@
# Ensure that we don't wrap Nodes that are "pureStatement" in a closure.
items: [1, 2, 3, "bacon", 4, 5]
items = [1, 2, 3, "bacon", 4, 5]
for item in items
break if item is "bacon"
findit: (items) ->
findit = (items) ->
for item in items
return item if item is "bacon"
@@ -13,10 +13,10 @@ ok findit(items) is "bacon"
# When when a closure wrapper is generated for expression conversion, make sure
# that references to "this" within the wrapper are safely converted as well.
obj: {
obj = {
num: 5
func: ->
this.result: if false
this.result = if false
10
else
"a"

View File

@@ -1,6 +1,6 @@
x: 1
y: {}
y.x: -> 3
x = 1
y = {}
y.x = -> 3
ok x is 1
ok typeof(y.x) is 'function'
@@ -18,7 +18,7 @@ ok y.x() is 3
(one) -> (two) -> three four, (five) -> six seven, eight, (nine) ->
obj: {
obj = {
name: "Fred"
bound: ->
@@ -33,15 +33,15 @@ obj.bound()
# Python decorator style wrapper that memoizes any function
memoize: (fn) ->
cache: {}
self: this
memoize = (fn) ->
cache = {}
self = this
(args...) ->
key: args.toString()
key = args.toString()
return cache[key] if cache[key]
cache[key] = fn.apply(self, args)
Math: {
Math = {
Add: (a, b) -> a + b
AnonymousAdd: ((a, b) -> a + b)
FastAdd: memoize (a, b) -> a + b
@@ -57,21 +57,21 @@ ok 100 > 1 if 1 > 0
ok true unless false
ok true for i in [1..3]
okFunc: (f) -> ok(f())
okFunc = (f) -> ok(f())
okFunc -> true
# Optional parens can be used in a nested fashion.
call: (func) -> func()
call = (func) -> func()
result: call ->
inner: call ->
result = call ->
inner = call ->
Math.Add(5, 5)
ok result is 10
# More fun with optional parens.
fn: (arg) -> arg
fn = (arg) -> arg
ok fn(fn {prop: 101}).prop is 101
@@ -82,7 +82,7 @@ ok((fn (x) ->
# Multi-blocks with optional parens.
result: fn( ->
result = fn( ->
fn ->
"Wrapped"
)
@@ -91,29 +91,29 @@ ok result()() is 'Wrapped'
# And even with strange things like this:
funcs: [((x) -> x), ((x) -> x * x)]
result: funcs[1] 5
funcs = [((x) -> x), ((x) -> x * x)]
result = funcs[1] 5
ok result is 25
result: ("hello".slice) 3
result = ("hello".slice) 3
ok result is 'lo'
# And with multiple single-line functions on the same line.
func: (x) -> (x) -> (x) -> x
func = (x) -> (x) -> (x) -> x
ok func(1)(2)(3) is 3
# Ensure that functions with the same name don't clash with helper functions.
del: -> 5
del = -> 5
ok del() is 5
# Ensure that functions can have a trailing comma in their argument list
mult: (x, mids..., y) ->
x: * n for n in mids
x: * y
mult = (x, mids..., y) ->
x *= n for n in mids
x *= y
ok mult(1, 2,) is 2
ok mult(1, 2, 3,) is 6
@@ -121,22 +121,22 @@ ok mult(10,[1..6]...,) is 7200
# Test for inline functions with parentheses and implicit calls.
combine: (func, num) -> func() * num
result: combine (-> 1 + 2), 3
combine = (func, num) -> func() * num
result = combine (-> 1 + 2), 3
ok result is 9
# Test for calls/parens/multiline-chains.
f: (x) -> x
result: (f 1).toString()
f = (x) -> x
result = (f 1).toString()
.length
ok result is 1
# Test implicit calls in functions in parens:
result: ((val) ->
result = ((val) ->
[].push val
val
)(10)
@@ -145,12 +145,12 @@ ok result is 10
# More paren compilation tests:
reverse: (obj) -> obj.reverse()
reverse = (obj) -> obj.reverse()
ok reverse([1, 2].concat 3).join(' ') is '3 2 1'
# Passing multiple functions without paren-wrapping is legal, and should compile.
sum: (one, two) -> one() + two()
result: sum ->
sum = (one, two) -> one() + two()
result = sum ->
7 + 9
, ->
1 + 3
@@ -159,32 +159,32 @@ ok result is 20
# Implicit call with a trailing if statement as a param.
func: -> arguments[1]
result: func 'one', if false then 100 else 13
func = -> arguments[1]
result = func 'one', if false then 100 else 13
ok result is 13
# Test more function passing:
result: sum( ->
result = sum( ->
1 + 2
, ->
2 + 1
)
ok result is 6
sum: (a, b) -> a + b
result: sum(1
sum = (a, b) -> a + b
result = sum(1
, 2)
ok result is 3
# This is a crazy one.
x: (obj, func) -> func obj
ident: (x) -> x
x = (obj, func) -> func obj
ident = (x) -> x
result: x {one: ident 1}, (obj) ->
inner: ident(obj)
result = x {one: ident 1}, (obj) ->
inner = ident(obj)
ident inner
ok result.one is 1
@@ -192,7 +192,7 @@ ok result.one is 1
# Assignment to a Object.prototype-named variable should not leak to outer scope.
(->
constructor: 'word'
constructor = 'word'
)()
ok constructor isnt 'word'

View File

@@ -1,26 +1,26 @@
a: """
basic heredoc
on two lines
"""
a = """
basic heredoc
on two lines
"""
ok a is "basic heredoc\non two lines"
a: '''
a
"b
c
'''
a = '''
a
"b
c
'''
ok a is "a\n \"b\nc"
a: '''one-liner'''
a = '''one-liner'''
ok a is 'one-liner'
a: """
a = """
out
here
"""
@@ -28,16 +28,16 @@ a: """
ok a is "out\nhere"
a: '''
a
b
c
'''
a = '''
a
b
c
'''
ok a is " a\n b\nc"
a: '''
a = '''
a
@@ -47,26 +47,26 @@ b c
ok a is "a\n\n\nb c"
a: '''more"than"one"quote'''
a = '''more"than"one"quote'''
ok a is 'more"than"one"quote'
val: 10
val = 10
a: """
basic heredoc $val
on two lines
"""
a = """
basic heredoc $val
on two lines
"""
b: '''
basic heredoc $val
on two lines
'''
b = '''
basic heredoc $val
on two lines
'''
ok a is "basic heredoc 10\non two lines"
ok b is "basic heredoc \$val\non two lines"
a: '''here's an apostrophe'''
a = '''here's an apostrophe'''
ok a is "here's an apostrophe"

View File

@@ -1,7 +1,7 @@
a: b: d: true
c: false
a = b = d = true
c = false
result: if a
result = if a
if b
if c then false else
if d
@@ -10,13 +10,13 @@ result: if a
ok result
first: if false then false else second: if false then false else true
first = if false then false else second = if false then false else true
ok first
ok second
result: if false
result = if false
false
else if NaN
false
@@ -27,7 +27,7 @@ ok result
# Testing unless.
result: unless true
result = unless true
10
else
11
@@ -36,24 +36,24 @@ ok result is 11
# Nested inline if statements.
echo: (x) -> x
result: if true then echo((if false then 'xxx' else 'y') + 'a')
echo = (x) -> x
result = if true then echo((if false then 'xxx' else 'y') + 'a')
ok result is 'ya'
# Testing inline funcs with inline if-elses.
func: -> if 1 < 0.5 then 1 else -1
func = -> if 1 < 0.5 then 1 else -1
ok func() is -1
# Testing empty or commented if statements ... should compile:
result: if false
result = if false
else if false
else
ok result is undefined
result: if false
result = if false
# comment
else if true
# comment
@@ -63,7 +63,7 @@ ok result is undefined
# Return an if with no else.
func: ->
func = ->
return (if false then callback())
ok func() is null

View File

@@ -1,23 +1,23 @@
a: [((x) -> x), ((x) -> x * x)]
a = [((x) -> x), ((x) -> x * x)]
ok a.length is 2
regex: /match/i
words: "I think there is a match in here."
regex = /match/i
words = "I think there is a match in here."
ok !!words.match(regex)
neg: (3 -4)
neg = (3 -4)
ok neg is -1
# Decimal number literals.
value: .25 + .75
value = .25 + .75
ok value is 1
value: 0.0 + -.25 - -.75 + 0.0
value = 0.0 + -.25 - -.75 + 0.0
ok value is 0.5
# Decimals don't interfere with ranges.
@@ -29,44 +29,44 @@ ok [0...10].join(' ') is '0 1 2 3 4 5 6 7 8 9'
4.valueOf() is 4
func: ->
func = ->
return if true
ok func() is null
str: "\\"
reg: /\\/
str = "\\"
reg = /\\/
ok reg(str) and str is '\\'
trailingComma: [1, 2, 3,]
trailingComma = [1, 2, 3,]
ok (trailingComma[0] is 1) and (trailingComma[2] is 3) and (trailingComma.length is 3)
trailingComma: [
trailingComma = [
1, 2, 3,
4, 5, 6
7, 8, 9,
]
(sum: (sum or 0) + n) for n in trailingComma
(sum = (sum or 0) + n) for n in trailingComma
trailingComma: {k1: "v1", k2: 4, k3: (-> true),}
trailingComma = {k1: "v1", k2: 4, k3: (-> true),}
ok trailingComma.k3() and (trailingComma.k2 is 4) and (trailingComma.k1 is "v1")
multiline: {a: 15,
multiline = {a: 15,
b: 26}
ok multiline.b is 26
money$: 'dollars'
money$ = 'dollars'
ok money$ is 'dollars'
multiline: "one
two
three"
multiline = "one
two
three"
ok multiline is 'one two three'
@@ -74,7 +74,7 @@ ok multiline is 'one two three'
ok {a: (num) -> num is 10 }.a 10
moe: {
moe = {
name: 'Moe'
greet: (salutation) ->
salutation + " " + @name
@@ -86,13 +86,13 @@ moe: {
ok moe.hello() is "Hello Moe"
ok moe[10] is 'number'
moe.hello: ->
moe.hello = ->
this['greet'] "Hello"
ok moe.hello() is 'Hello Moe'
obj: {
obj = {
is: -> yes,
'not': -> no,
}
@@ -102,7 +102,7 @@ ok not obj.not()
# Funky indentation within non-comma-seperated arrays.
result: [['a']
result = [['a']
{b: 'c'}]
ok result[0][0] is 'a'
@@ -110,14 +110,14 @@ ok result[1]['b'] is 'c'
# Object literals should be able to include keywords.
obj: {class: 'höt'}
obj.function: 'dog'
obj = {class: 'höt'}
obj.function = 'dog'
ok obj.class + obj.function is 'hötdog'
# But keyword assignment should be smart enough not to stringify variables.
func: ->
func = ->
this == 'this'
ok func() is false

View File

@@ -1,5 +1,5 @@
# This file is imported by `testImporting.coffee`
local: "from over there"
local = "from over there"
exports.func: -> local
exports.func = -> local

View File

@@ -12,15 +12,15 @@ ok 50 > 10 > 5 is parseInt('5', 10)
# Make sure that each argument is only evaluated once, even if used
# more than once.
i: 0
func: -> i++
i = 0
func = -> i++
ok 1 > func() < 1
# `:` and `=` should be interchangeable, as should be `==` and `is`.
a: 1
b: 1
a = 1
b = 1
ok a is 1 and b is 1
ok a == b
@@ -29,30 +29,30 @@ ok a is b
# Ensure that chained operations don't cause functions to be evaluated more
# than once.
val: 0
func: -> val: + 1
val = 0
func = -> val = + 1
ok 2 > (func null) < 2
ok val is 1
# Allow "if x not in y"
obj: {a: true}
obj = {a: true}
ok 'a' of obj
ok 'b' not of obj
# And for "a in b" with array presence.
ok 100 in [100, 200, 300]
array: [100, 200, 300]
array = [100, 200, 300]
ok 100 in array
ok 1 not in array
list: [1, 2, 7]
result: if list[2] in [7, 10] then 100 else -1
list = [1, 2, 7]
result = if list[2] in [7, 10] then 100 else -1
ok result is 100
# And with array presence on an instance variable.
obj: {
obj = {
list: [1, 2, 3, 4, 5]
in_list: (value) -> value in @list
}
@@ -60,8 +60,8 @@ ok obj.in_list 4
ok not obj.in_list 0
# Non-spaced values still work.
x: 10
y: -5
x = 10
y = -5
ok x*-y is 50
ok x*+y is -50

View File

@@ -1,18 +1,18 @@
# Ensure that the OptionParser handles arguments correctly.
{OptionParser}: require './../lib/optparse'
{OptionParser} = require './../lib/optparse'
opt: new OptionParser [
opt = new OptionParser [
['-r', '--required [DIR]', 'desc required']
['-o', '--optional', 'desc optional']
]
result: opt.parse ['one', 'two', 'three', '-r', 'dir']
result = opt.parse ['one', 'two', 'three', '-r', 'dir']
ok result.arguments.length is 5
ok result.arguments[3] is '-r'
result: opt.parse ['--optional', '-r', 'folder', 'one', 'two']
result = opt.parse ['--optional', '-r', 'folder', 'one', 'two']
ok result.optional is true
ok result.required is 'folder'

View File

@@ -1,40 +1,40 @@
# Simple variable swapping.
a: -1
b: -2
a = -1
b = -2
[a, b]: [b, a]
[a, b] = [b, a]
ok a is -2
ok b is -1
func: ->
[a, b]: [b, a]
func = ->
[a, b] = [b, a]
ok func().join(' ') is '-1 -2'
noop: ->
noop = ->
noop [a,b]: [c,d]: [1,2]
noop [a,b] = [c,d] = [1,2]
ok a is 1 and b is 2
# Array destructuring, including splats.
arr: [1, 2, 3]
arr = [1, 2, 3]
[a, b, c]: arr
[a, b, c] = arr
ok a is 1
ok b is 2
ok c is 3
[x,y...,z]: [1,2,3,4,5]
[x,y...,z] = [1,2,3,4,5]
ok x is 1
ok y.length is 3
ok z is 5
[x, [y, mids..., last], z..., end]: [1, [10, 20, 30, 40], 2,3,4, 5]
[x, [y, mids..., last], z..., end] = [1, [10, 20, 30, 40], 2,3,4, 5]
ok x is 1
ok y is 10
@@ -45,15 +45,15 @@ ok end is 5
# Object destructuring.
obj: {x: 10, y: 20, z: 30}
obj = {x: 10, y: 20, z: 30}
{x: a, y: b, z: c}: obj
{x: a, y: b, z: c} = obj
ok a is 10
ok b is 20
ok c is 30
person: {
person = {
name: "Moe"
family: {
'elder-brother': {
@@ -68,12 +68,12 @@ person: {
}
}
{name: a, family: {'elder-brother': {addresses: [one, {city: b}]}}}: person
{name: a, family: {'elder-brother': {addresses: [one, {city: b}]}}} = person
ok a is "Moe"
ok b is "Moquasset NY, 10021"
test: {
test = {
person: {
address: [
"------"
@@ -84,13 +84,13 @@ test: {
}
}
{person: {address: [ignore, addr...]}}: test
{person: {address: [ignore, addr...]}} = test
ok addr.join(', ') is "Street 101, Apt 101, City 101"
# Pattern matching against an expression.
[a, b]: if true then [2, 1] else [1, 2]
[a, b] = if true then [2, 1] else [1, 2]
ok a is 2
ok b is 1
@@ -98,13 +98,13 @@ ok b is 1
# Pattern matching with object shorthand.
person: {
person = {
name: "Bob"
age: 26
dogs: ["Prince", "Bowie"]
}
{name, age, dogs: [first, second]}: person
{name, age, dogs: [first, second]} = person
ok name is "Bob"
ok age is 26
@@ -113,27 +113,27 @@ ok second is "Bowie"
# Pattern matching within for..loops
persons: {
persons = {
George: { name: "Bob" },
Bob: { name: "Alice" }
Christopher: { name: "Stan" }
}
join1: "$key: $name" for key, { name } of persons
join1 = "$key: $name" for key, { name } of persons
deepEqual join1, ["George: Bob", "Bob: Alice", "Christopher: Stan"]
persons: [
persons = [
{ name: "Bob", parent: { name: "George" } },
{ name: "Alice", parent: { name: "Bob" } },
{ name: "Stan", parent: { name: "Christopher" } }
]
join2: "$parent: $name" for { name, parent: { name: parent } } in persons
join2 = "$parent: $name" for { name, parent: { name: parent } } in persons
deepEqual join1, join2
persons: [['Bob', ['George']], ['Alice', ['Bob']], ['Stan', ['Christopher']]]
join3: "$parent: $name" for [name, [parent]] in persons
persons = [['Bob', ['George']], ['Alice', ['Bob']], ['Stan', ['Christopher']]]
join3 = "$parent: $name" for [name, [parent]] in persons
deepEqual join2, join3

View File

@@ -1,38 +1,38 @@
# Slice.
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a: array[7..9]
b: array[2...4]
a = array[7..9]
b = array[2...4]
result: a.concat(b).join(' ')
result = a.concat(b).join(' ')
ok result is "7 8 9 2 3"
a: [0, 1, 2, 3, 4, 5, 6, 7]
a = [0, 1, 2, 3, 4, 5, 6, 7]
deepEqual a[2...6], [2, 3, 4, 5]
# Ranges.
countdown: [10..1].join(' ')
countdown = [10..1].join(' ')
ok countdown is "10 9 8 7 6 5 4 3 2 1"
a: 1
b: 5
nums: [a...b]
a = 1
b = 5
nums = [a...b]
ok nums.join(' ') is '1 2 3 4'
b: -5
nums: [a..b]
b = -5
nums = [a..b]
ok nums.join(' ') is '1 0 -1 -2 -3 -4 -5'
# Expression-based range.
array: [(1+5)..1+9]
array = [(1+5)..1+9]
ok array.join(' ') is "6 7 8 9 10"
# String slicing (at least on Node).
hello: "Hello World"
hello = "Hello World"
ok hello[1...1] is ""
ok hello[1..1] is "e"
@@ -41,9 +41,9 @@ ok hello[0..4] is "Hello"
# Splice literals.
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array[5..10]: [0, 0, 0]
array[5..10] = [0, 0, 0]
ok array.join(' ') is '0 1 2 3 4 0 0 0'

View File

@@ -1,5 +1,5 @@
# Interpolate regular expressions.
name: 'Moe'
name = 'Moe'
ok not not '"Moe"'.match(/^"${name}"$/i)
ok '"Moe!"'.match(/^"${name}"$/i) is null
@@ -11,10 +11,10 @@ ok 'Moe!'.match(/${"${"${"$name"}"}"}/imgy)
ok '$a$b$c'.match(/\$A\$B\$C/i)
a: 1
b: 2
c: 3
a = 1
b = 2
c = 3
ok '123'.match(/$a$b$c/i)
[a, b, c]: [1, 2, /\d+/]
[a, b, c] = [1, 2, /\d+/]
ok (/$a$b$c$/i).toString() is '/12/\\d+/$/i'

View File

@@ -6,18 +6,18 @@ ok 'x'.match /x/
ok 4 / 2 / 1 is 2
y: 4
x: 2
g: 1
y = 4
x = 2
g = 1
ok y / x/g is 2
ok 'http://google.com'.match(/:\/\/goog/)
obj: {
obj = {
width: -> 10
height: -> 20
}
id: 2
id = 2
ok (obj.width()/id - obj.height()/id) is -5

View File

@@ -1,11 +1,11 @@
# Expression conversion under explicit returns.
first: ->
first = ->
return 'do' for x in [1,2,3]
second: ->
second = ->
return ['re' for x in [1,2,3]]
third: ->
third = ->
return ('mi' for x in [1,2,3])
ok first().join(' ') is 'do do do'
@@ -14,7 +14,7 @@ ok third().join(' ') is 'mi mi mi'
# Testing returns with multiple branches.
func: ->
func = ->
if false
for a in b
return c if d
@@ -25,7 +25,7 @@ ok func() is 'word'
# And with switches.
func: ->
func = ->
switch 'a'
when 'a' then 42
else return 23

View File

@@ -1,21 +1,21 @@
func: (first, second, rest...) ->
func = (first, second, rest...) ->
rest.join ' '
result: func 1, 2, 3, 4, 5
result = func 1, 2, 3, 4, 5
ok result is "3 4 5"
gold: silver: bronze: theField: last: null
gold = silver = bronze = theField = last = null
medalists: (first, second, third, rest..., unlucky) ->
gold: first
silver: second
bronze: third
theField: rest.concat([last])
last: unlucky
medalists = (first, second, third, rest..., unlucky) ->
gold = first
silver = second
bronze = third
theField = rest.concat([last])
last = unlucky
contenders: [
contenders = [
"Michael Phelps"
"Liu Xiang"
"Yao Ming"
@@ -49,24 +49,24 @@ medalists contenders..., 'Tim', 'Moe', 'Jim'
ok last is 'Jim'
obj: {
obj = {
name: 'moe'
accessor: (args...) ->
[@name].concat(args).join(' ')
getNames: ->
args: ['jane', 'ted']
args = ['jane', 'ted']
@accessor(args...)
}
ok obj.getNames() is 'moe jane ted'
crowd: [
crowd = [
contenders...
"Mighty Mouse"
]
bests: [
bests = [
"Mighty Mouse"
contenders[0..3]...
]
@@ -86,16 +86,16 @@ class Parent
class Child extends Parent
meth: ->
nums: [3, 2, 1]
nums = [3, 2, 1]
super nums...
ok (new Child).meth().join(' ') is '3 2 1'
# Functions with splats being called with too few arguments.
pen: null
method: (first, variable..., penultimate, ultimate) ->
pen: penultimate
pen = null
method = (first, variable..., penultimate, ultimate) ->
pen = penultimate
method 1, 2, 3, 4, 5, 6, 7, 8, 9
ok pen is 8
@@ -108,8 +108,8 @@ ok pen is 2
# Array splat expansions with assigns.
nums: [1, 2, 3]
list: [a: 0, nums..., b: 4]
nums = [1, 2, 3]
list = [a = 0, nums..., b = 4]
ok a is 0
ok b is 4
ok list.join(' ') is '0 1 2 3 4'

View File

@@ -1,5 +1,5 @@
hello: 'Hello'
world: 'World'
hello = 'Hello'
world = 'World'
ok '$hello $world!' is '$hello $world!'
ok '${hello} ${world}!' is '${hello} ${world}!'
ok "$hello $world!" is 'Hello World!'
@@ -12,7 +12,7 @@ ok "Hello ${ 1 + 2 } World" is 'Hello 3 World'
ok "$hello ${ 1 + 2 } $world" is "Hello 3 World"
[s, t, r, i, n, g]: ['s', 't', 'r', 'i', 'n', 'g']
[s, t, r, i, n, g] = ['s', 't', 'r', 'i', 'n', 'g']
ok "$s$t$r$i$n$g" is 'string'
ok "${s}${t}${r}${i}${n}${g}" is 'string'
ok "\$s\$t\$r\$i\$n\$g" is '$s$t$r$i$n$g'
@@ -47,12 +47,12 @@ ok "${hello + world}" is 'HelloWorld'
ok "${hello + ' ' + world + '!'}" is 'Hello World!'
list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ok "values: ${list.join(', ')}, length: ${list.length}." is 'values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, length: 10.'
ok "values: ${list.join ' '}" is 'values: 0 1 2 3 4 5 6 7 8 9'
obj: {
obj = {
name: 'Joe'
hi: -> "Hello $@name."
cya: -> "Hello $@name.".replace('Hello','Goodbye')
@@ -73,23 +73,23 @@ ok "Hello ${world ? "$hello"}" is 'Hello World'
ok "Hello ${"${"${obj["name"]}" + '!'}"}" is 'Hello Joe!'
a: """
Hello ${ "Joe" }
"""
a = """
Hello ${ "Joe" }
"""
ok a is "Hello Joe"
a: 1
b: 2
c: 3
a = 1
b = 2
c = 3
ok "$a$b$c" is '123'
result: null
stash: (str) -> result: str
result = null
stash = (str) -> result = str
stash "a ${ ('aa').replace /a/g, 'b' } c"
ok result is 'a bb c'
foo: "hello"
foo = "hello"
ok "${foo.replace("\"", "")}" is 'hello'

View File

@@ -1,6 +1,6 @@
num: 10
num = 10
result: switch num
result = switch num
when 5 then false
when 'a'
true
@@ -17,7 +17,7 @@ result: switch num
ok result
func: (num) ->
func = (num) ->
switch num
when 2, 4, 6
true
@@ -32,8 +32,8 @@ ok !func(8)
# Should cache the switch value, if anything fancier than a literal.
num: 5
result: switch num: + 5
num = 5
result = switch num += 5
when 5 then false
when 15 then false
when 10 then true
@@ -43,29 +43,29 @@ ok result
# Ensure that trailing switch elses don't get rewritten.
result: false
result = false
switch "word"
when "one thing"
doSomething()
else
result: true unless false
result = true unless false
ok result
result: false
result = false
switch "word"
when "one thing"
doSomething()
when "other thing"
doSomething()
else
result: true unless false
result = true unless false
ok result
# Should be able to handle switches sans-condition.
result: switch
result = switch
when null then 1
when 'truthful string' then 2
else 3
@@ -74,7 +74,7 @@ ok result is 2
# Should be able to use "@properties" within the switch clause.
obj: {
obj = {
num: 101
func: ->
switch @num

View File

@@ -1,17 +1,17 @@
# Basic exception throwing.
block: -> (throw 'up')
block = -> (throw 'up')
throws block, 'up'
# Basic try/catch.
result: try
result = try
10
finally
15
ok result is 10
result: try
result = try
throw 'up'
catch err
err.length
@@ -19,7 +19,7 @@ catch err
ok result is 2
result: try throw 'error' catch err then err.length
result = try throw 'error' catch err then err.length
ok result is 5

View File

@@ -1,48 +1,48 @@
i: 5
list: while i -= 1
i = 5
list = while i -= 1
i * 2
ok list.join(' ') is "8 6 4 2"
i: 5
list: (i * 3 while i -= 1)
i = 5
list = (i * 3 while i -= 1)
ok list.join(' ') is "12 9 6 3"
i: 5
func: (num) -> i -= num
assert: -> ok i < 5 > 0
i = 5
func = (num) -> i -= num
assert = -> ok i < 5 > 0
results: while func 1
results = while func 1
assert()
i
ok results.join(' ') is '4 3 2 1'
i: 10
results: while i -= 1 when i % 2 is 0
i = 10
results = while i -= 1 when i % 2 is 0
i * 2
ok results.join(' ') is '16 12 8 4'
value: false
i: 0
results: until value
value: true if i is 5
value = false
i = 0
results = until value
value = true if i is 5
i += 1
ok i is 6
# And, the loop form of while.
i: 5
list: []
i = 5
list = []
loop
i: - 1
i -= 1
break if i is 0
list.push i * 2