Merge pull request #2887 from epidemian/more-math-operators

Add new mathematical operators
This commit is contained in:
Michael Ficarra 2014-01-24 13:18:29 -08:00
commit d687d52f9e
11 changed files with 220 additions and 91 deletions

View File

@ -542,14 +542,16 @@
Operation: [
o('UNARY Expression', function() {
return new Op($1, $2);
}), o('UNARY_MATH Expression', function() {
return new Op($1, $2);
}), o('- Expression', (function() {
return new Op('-', $2);
}), {
prec: 'UNARY'
prec: 'UNARY_MATH'
}), o('+ Expression', (function() {
return new Op('+', $2);
}), {
prec: 'UNARY'
prec: 'UNARY_MATH'
}), o('-- SimpleAssignable', function() {
return new Op('--', $2);
}), o('++ SimpleAssignable', function() {
@ -566,6 +568,8 @@
return new Op('-', $1, $3);
}), o('Expression MATH Expression', function() {
return new Op($2, $1, $3);
}), o('Expression ** Expression', function() {
return new Op($2, $1, $3);
}), o('Expression SHIFT Expression', function() {
return new Op($2, $1, $3);
}), o('Expression COMPARE Expression', function() {
@ -590,7 +594,7 @@
]
};
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', 'LOGIC'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS'], ['left', 'POST_IF']];
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', 'LOGIC'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS'], ['left', 'POST_IF']];
tokens = [];

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.3
(function() {
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, invertLiterate, key, last, locationDataToString, repeat, starts, throwSyntaxError, _ref, _ref1,
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, UNARY_MATH, WHITESPACE, compact, count, invertLiterate, key, last, locationDataToString, repeat, starts, throwSyntaxError, _ref, _ref1,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
_ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES;
@ -244,8 +244,7 @@
if (this.chunk.charAt(0) !== '/') {
return 0;
}
if (match = HEREGEX.exec(this.chunk)) {
length = this.heregexToken(match);
if (length = this.heregexToken()) {
return length;
}
prev = last(this.tokens);
@ -256,18 +255,21 @@
return 0;
}
_ref3 = match, match = _ref3[0], regex = _ref3[1], flags = _ref3[2];
if (regex === '//') {
return 0;
}
if (regex.slice(0, 2) === '/*') {
this.error('regular expressions cannot begin with `*`');
}
if (regex === '//') {
regex = '/(?:)/';
}
this.token('REGEX', "" + regex + flags, 0, match.length);
return match.length;
};
Lexer.prototype.heregexToken = function(match) {
var body, flags, flagsOffset, heregex, plusToken, prev, re, tag, token, tokens, value, _i, _len, _ref2, _ref3, _ref4;
Lexer.prototype.heregexToken = function() {
var body, flags, flagsOffset, heregex, match, plusToken, prev, re, tag, token, tokens, value, _i, _len, _ref2, _ref3, _ref4;
if (!(match = HEREGEX.exec(this.chunk))) {
return 0;
}
heregex = match[0], body = match[1], flags = match[2];
if (0 > body.indexOf('#{')) {
re = this.escapeLines(body.replace(HEREGEX_OMIT, '$1$2').replace(/\//g, '\\/'), true);
@ -465,6 +467,8 @@
tag = 'COMPOUND_ASSIGN';
} else if (__indexOf.call(UNARY, value) >= 0) {
tag = 'UNARY';
} else if (__indexOf.call(UNARY_MATH, value) >= 0) {
tag = 'UNARY_MATH';
} else if (__indexOf.call(SHIFT, value) >= 0) {
tag = 'SHIFT';
} else if (__indexOf.call(LOGIC, value) >= 0 || value === '?' && (prev != null ? prev.spaced : void 0)) {
@ -761,7 +765,7 @@
Lexer.prototype.unfinished = function() {
var _ref2;
return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS');
return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === 'UNARY_MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === '**' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS');
};
Lexer.prototype.removeNewlines = function(str) {
@ -859,7 +863,7 @@
HEREDOC = /^("""|''')((?:\\[\s\S]|[^\\])*?)(?:\n[^\n\S]*)?\1/;
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?(\.|::)|\.{2,3})/;
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/;
WHITESPACE = /^[^\n\S]+/;
@ -889,9 +893,11 @@
TRAILING_SPACES = /\s+$/;
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '**=', '//=', '%%='];
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO'];
UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO'];
UNARY_MATH = ['!', '~'];
LOGIC = ['&&', '||', '&', '|', '^'];
@ -899,7 +905,7 @@
COMPARE = ['==', '!=', '<', '>', '<=', '>='];
MATH = ['*', '/', '%'];
MATH = ['*', '/', '%', '//', '%%'];
RELATION = ['IN', 'OF', 'INSTANCEOF'];

View File

@ -1591,7 +1591,7 @@
};
Assign.prototype.compileNode = function(o) {
var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4;
var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4, _ref5;
if (isValue = this.variable instanceof Value) {
if (this.variable.isArray() || this.variable.isObject()) {
return this.compilePatternMatch(o);
@ -1602,6 +1602,9 @@
if ((_ref2 = this.context) === '||=' || _ref2 === '&&=' || _ref2 === '?=') {
return this.compileConditional(o);
}
if ((_ref3 = this.context) === '**=' || _ref3 === '//=' || _ref3 === '%%=') {
return this.compileSpecialMath(o);
}
}
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
name = fragmentsToText(compiledName);
@ -1622,7 +1625,7 @@
if (match[2]) {
this.value.klass = match[1];
}
this.value.name = (_ref3 = (_ref4 = match[3]) != null ? _ref4 : match[4]) != null ? _ref3 : match[5];
this.value.name = (_ref4 = (_ref5 = match[3]) != null ? _ref5 : match[4]) != null ? _ref4 : match[5];
}
val = this.value.compileToFragments(o, LEVEL_LIST);
if (this.context === 'object') {
@ -1766,6 +1769,12 @@
}
};
Assign.prototype.compileSpecialMath = function(o) {
var left, right, _ref2;
_ref2 = this.variable.cacheReference(o), left = _ref2[0], right = _ref2[1];
return new Assign(left, new Op(this.context.slice(0, -1), right, this.value)).compileToFragments(o);
};
Assign.prototype.compileSplice = function(o) {
var answer, exclusive, from, fromDecl, fromRef, name, to, valDef, valRef, _ref2, _ref3, _ref4;
_ref2 = this.variable.properties.pop().range, from = _ref2.from, to = _ref2.to, exclusive = _ref2.exclusive;
@ -2352,7 +2361,7 @@
};
Op.prototype.compileNode = function(o) {
var answer, isChain, _ref2, _ref3;
var answer, isChain, lhs, rhs, _ref2, _ref3;
isChain = this.isChainable() && this.first.isChainable();
if (!isChain) {
this.first.front = this.front;
@ -2369,14 +2378,24 @@
if (isChain) {
return this.compileChain(o);
}
if (this.operator === '?') {
return this.compileExistence(o);
}
answer = [].concat(this.first.compileToFragments(o, LEVEL_OP), this.makeCode(' ' + this.operator + ' '), this.second.compileToFragments(o, LEVEL_OP));
if (o.level <= LEVEL_OP) {
return answer;
} else {
return this.wrapInBraces(answer);
switch (this.operator) {
case '?':
return this.compileExistence(o);
case '**':
return this.compilePower(o);
case '//':
return this.compileFloorDivision(o);
case '%%':
return this.compileModulo(o);
default:
lhs = this.first.compileToFragments(o, LEVEL_OP);
rhs = this.second.compileToFragments(o, LEVEL_OP);
answer = [].concat(lhs, this.makeCode(" " + this.operator + " "), rhs);
if (o.level <= LEVEL_OP) {
return answer;
} else {
return this.wrapInBraces(answer);
}
}
};
@ -2428,6 +2447,25 @@
return this.joinFragmentArrays(parts, '');
};
Op.prototype.compilePower = function(o) {
var pow;
pow = new Value(new Literal('Math'), [new Access(new Literal('pow'))]);
return new Call(pow, [this.first, this.second]).compileToFragments(o);
};
Op.prototype.compileFloorDivision = function(o) {
var div, floor;
floor = new Value(new Literal('Math'), [new Access(new Literal('floor'))]);
div = new Op('/', this.first, this.second);
return new Call(floor, [div]).compileToFragments(o);
};
Op.prototype.compileModulo = function(o) {
var mod;
mod = new Value(new Literal(utility('modulo')));
return new Call(mod, [this.first, this.second]).compileToFragments(o);
};
Op.prototype.toString = function(idt) {
return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator);
};
@ -3037,6 +3075,9 @@
indexOf: function() {
return "[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }";
},
modulo: function() {
return "function(a, b) { return (a % b + +b) % b; }";
},
hasProp: function() {
return '{}.hasOwnProperty';
},

File diff suppressed because one or more lines are too long

View File

@ -468,7 +468,7 @@
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_UNSPACED_CALL = ['+', '-'];

View File

@ -533,8 +533,9 @@ grammar =
# rules are necessary.
Operation: [
o 'UNARY Expression', -> new Op $1 , $2
o '- Expression', (-> new Op '-', $2), prec: 'UNARY'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY'
o 'UNARY_MATH Expression', -> new Op $1 , $2
o '- Expression', (-> new Op '-', $2), prec: 'UNARY_MATH'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY_MATH'
o '-- SimpleAssignable', -> new Op '--', $2
o '++ SimpleAssignable', -> new Op '++', $2
@ -548,6 +549,7 @@ grammar =
o 'Expression - Expression', -> new Op '-' , $1, $3
o 'Expression MATH Expression', -> new Op $2, $1, $3
o 'Expression ** Expression', -> new Op $2, $1, $3
o 'Expression SHIFT Expression', -> new Op $2, $1, $3
o 'Expression COMPARE Expression', -> new Op $2, $1, $3
o 'Expression LOGIC Expression', -> new Op $2, $1, $3
@ -584,6 +586,8 @@ operators = [
['nonassoc', '++', '--']
['left', '?']
['right', 'UNARY']
['right', '**']
['right', 'UNARY_MATH']
['left', 'MATH']
['left', '+', '-']
['left', 'SHIFT']

View File

@ -234,21 +234,21 @@ exports.Lexer = class Lexer
# JavaScript and Ruby.
regexToken: ->
return 0 if @chunk.charAt(0) isnt '/'
if match = HEREGEX.exec @chunk
length = @heregexToken match
return length
return length if length = @heregexToken()
prev = last @tokens
return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
return 0 unless match = REGEX.exec @chunk
[match, regex, flags] = match
# Avoid conflicts with floor division operator.
return 0 if regex is '//'
if regex[..1] is '/*' then @error 'regular expressions cannot begin with `*`'
if regex is '//' then regex = '/(?:)/'
@token 'REGEX', "#{regex}#{flags}", 0, match.length
match.length
# Matches multiline extended regular expressions.
heregexToken: (match) ->
heregexToken: ->
return 0 unless match = HEREGEX.exec @chunk
[heregex, body, flags] = match
if 0 > body.indexOf '#{'
re = @escapeLines body.replace(HEREGEX_OMIT, '$1$2').replace(/\//g, '\\/'), yes
@ -414,6 +414,7 @@ exports.Lexer = class Lexer
else if value in COMPARE then tag = 'COMPARE'
else if value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN'
else if value in UNARY then tag = 'UNARY'
else if value in UNARY_MATH then tag = 'UNARY_MATH'
else if value in SHIFT then tag = 'SHIFT'
else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
else if prev and not prev.spaced
@ -682,8 +683,8 @@ exports.Lexer = class Lexer
# Are we in the midst of an unfinished expression?
unfinished: ->
LINE_CONTINUER.test(@chunk) or
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-',
'**', 'SHIFT', 'RELATION', 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
# Remove newlines from beginning and (non escaped) from end of string literals.
removeNewlines: (str) ->
@ -787,7 +788,7 @@ OPERATOR = /// ^ (
| [-+*/%<>&|^!?=]= # compound assign / compare
| >>>=? # zero-fill right shift
| ([-+:])\1 # doubles
| ([&|<>])\2=? # logic / shift
| ([&|<>*/%])\2=? # logic / shift / power / floor division / modulo
| \?(\.|::) # soak access
| \.{2,3} # range or splat
) ///
@ -840,23 +841,26 @@ TRAILING_SPACES = /\s+$/
# Compound assignment tokens.
COMPOUND_ASSIGN = [
'-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='
'-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>='
'&=', '^=', '|=', '**=', '//=', '%%='
]
# Unary tokens.
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO']
UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO']
UNARY_MATH = ['!', '~']
# Logical tokens.
LOGIC = ['&&', '||', '&', '|', '^']
LOGIC = ['&&', '||', '&', '|', '^']
# Bit-shifting tokens.
SHIFT = ['<<', '>>', '>>>']
SHIFT = ['<<', '>>', '>>>']
# Comparison tokens.
COMPARE = ['==', '!=', '<', '>', '<=', '>=']
# Mathematical tokens.
MATH = ['*', '/', '%']
MATH = ['*', '/', '%', '//', '%%']
# Relational tokens that are negatable with `not` prefix.
RELATION = ['IN', 'OF', 'INSTANCEOF']

View File

@ -1157,6 +1157,7 @@ exports.Assign = class Assign extends Base
return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?=']
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
compiledName = @variable.compileToFragments o, LEVEL_LIST
name = fragmentsToText compiledName
unless @context
@ -1279,6 +1280,12 @@ exports.Assign = class Assign extends Base
fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
if o.level <= LEVEL_LIST then fragments else @wrapInBraces fragments
# Convert special math assignment operators like `a **= b` to the equivalent
# extended form `a = a ** b` and then compiles that.
compileSpecialMath: (o) ->
[left, right] = @variable.cacheReference o
new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
# Compile the assignment from an array splice literal, using JavaScript's
# `Array#splice` method.
compileSplice: (o) ->
@ -1687,10 +1694,16 @@ exports.Op = class Op extends Base
@error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
return @compileUnary o if @isUnary()
return @compileChain o if isChain
return @compileExistence o if @operator is '?'
answer = [].concat @first.compileToFragments(o, LEVEL_OP), @makeCode(' ' + @operator + ' '),
@second.compileToFragments(o, LEVEL_OP)
if o.level <= LEVEL_OP then answer else @wrapInBraces answer
switch @operator
when '?' then @compileExistence o
when '**' then @compilePower o
when '//' then @compileFloorDivision o
when '%%' then @compileModulo o
else
lhs = @first.compileToFragments o, LEVEL_OP
rhs = @second.compileToFragments o, LEVEL_OP
answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
if o.level <= LEVEL_OP then answer else @wrapInBraces answer
# Mimic Python's chained comparisons when multiple comparison operators are
# used sequentially. For example:
@ -1733,6 +1746,20 @@ exports.Op = class Op extends Base
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compilePower: (o) ->
# Make a Math.pow call
pow = new Value new Literal('Math'), [new Access new Literal 'pow']
new Call(pow, [@first, @second]).compileToFragments o
compileFloorDivision: (o) ->
floor = new Value new Literal('Math'), [new Access new Literal 'floor']
div = new Op '/', @first, @second
new Call(floor, [div]).compileToFragments o
compileModulo: (o) ->
mod = new Value new Literal utility 'modulo'
new Call(mod, [@first, @second]).compileToFragments o
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
@ -2166,6 +2193,10 @@ UTILITIES =
}
"
modulo: -> """
function(a, b) { return (a % b + +b) % b; }
"""
# Shortcuts to speed up the lookup time for native functions.
hasProp: -> '{}.hasOwnProperty'
slice : -> '[].slice'

View File

@ -469,8 +469,8 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL = [
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'SUPER'
'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY',
'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
]
IMPLICIT_UNSPACED_CALL = ['+', '-']

View File

@ -300,3 +300,60 @@ test "#2567: Optimization of negated existential produces correct result", ->
test "#2508: Existential access of the prototype", ->
eq NonExistent?::nothing, undefined
ok Object?::toString
test "power operator", ->
eq 27, 3 ** 3
test "power operator has higher precedence than other maths operators", ->
eq 55, 1 + 3 ** 3 * 2
eq -4, -2 ** 2
eq false, !2 ** 2
eq 0, (!2) ** 2
eq -2, ~1 ** 5
test "power operator is right associative", ->
eq 2, 2 ** 1 ** 3
test "power operator compound assignment", ->
a = 2
a **= 3
eq 8, a
test "floor division operator", ->
eq 2, 7 // 3
eq -3, -7 // 3
eq NaN, 0 // 0
test "floor division operator compound assignment", ->
a = 7
a //= 2
eq 3, a
test "modulo operator", ->
check = (a, b, expected) ->
eq expected, a %% b, "expected #{a} %%%% #{b} to be #{expected}"
check 0, 1, 0
check 0, -1, -0
check 1, 0, NaN
check 1, 2, 1
check 1, -2, -1
check 1, 3, 1
check 2, 3, 2
check 3, 3, 0
check 4, 3, 1
check -1, 3, 2
check -2, 3, 1
check -3, 3, 0
check -4, 3, 2
check 5.5, 2.5, 0.5
check -5.5, 2.5, 2.0
test "modulo operator compound assignment", ->
a = -2
a %%= 5
eq 3, a
test "modulo operator converts arguments to numbers", ->
eq 1, 1 %% '42'
eq 1, '1' %% 42
eq 1, '1' %% '42'

View File

@ -55,25 +55,3 @@ test "an empty heregex will compile to an empty, non-capturing group", ->
test "#1724: regular expressions beginning with `*`", ->
throws -> CoffeeScript.compile '/// * ///'
test "empty regular expressions with flags", ->
fn = (x) -> x
a = "" + //i
fn ""
eq '/(?:)/i', a
test "#3059: don't remove escaped whitespace", ->
eq /// One\ cannot [\ ] escape \ \destiny. ///.source,
/One cannot[ ]escape \destiny./.source
test "#2238: don't escape already escaped slashes", ->
eq /// \\\/ \/ ///.source, /\\\/\//.source
test "escaped slashes don't close heregex", ->
eq /// \/// ///.source, /\/\/\//.source
eq /// \\\////.source, /\\\//.source
test "escaped linebreaks", ->
eq /// \n\
\
///.source, /\n\n\n/.source