mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
Merge pull request #2887 from epidemian/more-math-operators
Add new mathematical operators
This commit is contained in:
commit
d687d52f9e
11 changed files with 220 additions and 91 deletions
|
@ -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 = [];
|
||||
|
||||
|
|
|
@ -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'];
|
||||
|
||||
|
|
|
@ -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,15 +2378,25 @@
|
|||
if (isChain) {
|
||||
return this.compileChain(o);
|
||||
}
|
||||
if (this.operator === '?') {
|
||||
switch (this.operator) {
|
||||
case '?':
|
||||
return this.compileExistence(o);
|
||||
}
|
||||
answer = [].concat(this.first.compileToFragments(o, LEVEL_OP), this.makeCode(' ' + this.operator + ' '), this.second.compileToFragments(o, LEVEL_OP));
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Op.prototype.compileChain = function(o) {
|
||||
|
@ -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
|
@ -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 = ['+', '-'];
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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,11 +841,14 @@ 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 = ['&&', '||', '&', '|', '^']
|
||||
|
@ -856,7 +860,7 @@ SHIFT = ['<<', '>>', '>>>']
|
|||
COMPARE = ['==', '!=', '<', '>', '<=', '>=']
|
||||
|
||||
# Mathematical tokens.
|
||||
MATH = ['*', '/', '%']
|
||||
MATH = ['*', '/', '%', '//', '%%']
|
||||
|
||||
# Relational tokens that are negatable with `not` prefix.
|
||||
RELATION = ['IN', 'OF', 'INSTANCEOF']
|
||||
|
|
|
@ -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,9 +1694,15 @@ 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)
|
||||
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
|
||||
|
@ -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'
|
||||
|
|
|
@ -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 = ['+', '-']
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue