mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
Add floor division //
and modulo %%
operators, and compound forms of the new operators
Also kill the empty regex :(
This commit is contained in:
parent
08b59aef8a
commit
22e8856b4d
6 changed files with 140 additions and 47 deletions
|
@ -248,8 +248,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);
|
||||
|
@ -260,18 +259,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 = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/');
|
||||
|
@ -832,7 +834,7 @@
|
|||
|
||||
HEREDOC = /^("""|''')([\s\S]*?)(?:\n[^\n\S]*)?\1/;
|
||||
|
||||
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?(\.|::)|\.{2,3}|\*\*)/;
|
||||
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/;
|
||||
|
||||
WHITESPACE = /^[^\n\S]+/;
|
||||
|
||||
|
@ -862,7 +864,7 @@
|
|||
|
||||
TRAILING_SPACES = /\s+$/;
|
||||
|
||||
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
|
||||
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '**=', '//=', '%%='];
|
||||
|
||||
UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO'];
|
||||
|
||||
|
@ -874,7 +876,7 @@
|
|||
|
||||
COMPARE = ['==', '!=', '<', '>', '<=', '>='];
|
||||
|
||||
MATH = ['*', '/', '%'];
|
||||
MATH = ['*', '/', '%', '//', '%%'];
|
||||
|
||||
RELATION = ['IN', 'OF', 'INSTANCEOF'];
|
||||
|
||||
|
|
|
@ -1561,7 +1561,7 @@
|
|||
};
|
||||
|
||||
Assign.prototype.compileNode = function(o) {
|
||||
var answer, compiledName, isValue, match, name, val, varBase, _ref4, _ref5, _ref6, _ref7;
|
||||
var answer, compiledName, isValue, match, name, val, varBase, _ref4, _ref5, _ref6, _ref7, _ref8;
|
||||
if (isValue = this.variable instanceof Value) {
|
||||
if (this.variable.isArray() || this.variable.isObject()) {
|
||||
return this.compilePatternMatch(o);
|
||||
|
@ -1572,6 +1572,9 @@
|
|||
if ((_ref4 = this.context) === '||=' || _ref4 === '&&=' || _ref4 === '?=') {
|
||||
return this.compileConditional(o);
|
||||
}
|
||||
if ((_ref5 = this.context) === '**=' || _ref5 === '//=' || _ref5 === '%%=') {
|
||||
return this.compileSpecialMath(o);
|
||||
}
|
||||
}
|
||||
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
|
||||
name = fragmentsToText(compiledName);
|
||||
|
@ -1592,7 +1595,7 @@
|
|||
if (match[1]) {
|
||||
this.value.klass = match[1];
|
||||
}
|
||||
this.value.name = (_ref5 = (_ref6 = (_ref7 = match[2]) != null ? _ref7 : match[3]) != null ? _ref6 : match[4]) != null ? _ref5 : match[5];
|
||||
this.value.name = (_ref6 = (_ref7 = (_ref8 = match[2]) != null ? _ref8 : match[3]) != null ? _ref7 : match[4]) != null ? _ref6 : match[5];
|
||||
}
|
||||
val = this.value.compileToFragments(o, LEVEL_LIST);
|
||||
if (this.context === 'object') {
|
||||
|
@ -1715,6 +1718,12 @@
|
|||
return new Op(this.context.slice(0, -1), left, new Assign(right, this.value, '=')).compileToFragments(o);
|
||||
};
|
||||
|
||||
Assign.prototype.compileSpecialMath = function(o) {
|
||||
var left, right, _ref4;
|
||||
_ref4 = this.variable.cacheReference(o), left = _ref4[0], right = _ref4[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, _ref4, _ref5, _ref6;
|
||||
_ref4 = this.variable.properties.pop().range, from = _ref4.from, to = _ref4.to, exclusive = _ref4.exclusive;
|
||||
|
@ -2268,7 +2277,7 @@
|
|||
};
|
||||
|
||||
Op.prototype.compileNode = function(o) {
|
||||
var answer, isChain, _ref4, _ref5;
|
||||
var answer, isChain, lhs, rhs, _ref4, _ref5;
|
||||
isChain = this.isChainable() && this.first.isChainable();
|
||||
if (!isChain) {
|
||||
this.first.front = this.front;
|
||||
|
@ -2285,17 +2294,24 @@
|
|||
if (isChain) {
|
||||
return this.compileChain(o);
|
||||
}
|
||||
if (this.operator === '?') {
|
||||
return this.compileExistence(o);
|
||||
}
|
||||
if (this.operator === '**') {
|
||||
return this.compilePower(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2353,6 +2369,19 @@
|
|||
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);
|
||||
};
|
||||
|
@ -3005,6 +3034,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';
|
||||
},
|
||||
|
|
|
@ -237,21 +237,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 = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
|
||||
|
@ -768,10 +768,9 @@ 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
|
||||
| \*\* # power
|
||||
) ///
|
||||
|
||||
WHITESPACE = /^[^\n\S]+/
|
||||
|
@ -818,7 +817,8 @@ TRAILING_SPACES = /\s+$/
|
|||
|
||||
# Compound assignment tokens.
|
||||
COMPOUND_ASSIGN = [
|
||||
'-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='
|
||||
'-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>='
|
||||
'&=', '^=', '|=', '**=', '//=', '%%='
|
||||
]
|
||||
|
||||
# Unary tokens.
|
||||
|
@ -836,7 +836,7 @@ SHIFT = ['<<', '>>', '>>>']
|
|||
COMPARE = ['==', '!=', '<', '>', '<=', '>=']
|
||||
|
||||
# Mathematical tokens.
|
||||
MATH = ['*', '/', '%']
|
||||
MATH = ['*', '/', '%', '//', '%%']
|
||||
|
||||
# Relational tokens that are negatable with `not` prefix.
|
||||
RELATION = ['IN', 'OF', 'INSTANCEOF']
|
||||
|
|
|
@ -1131,6 +1131,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
|
||||
|
@ -1239,6 +1240,12 @@ exports.Assign = class Assign extends Base
|
|||
if "?" in @context then o.isExistentialEquals = true
|
||||
new Op(@context[...-1], left, new Assign(right, @value, '=') ).compileToFragments o
|
||||
|
||||
# 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) ->
|
||||
|
@ -1622,11 +1629,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 '?'
|
||||
return @compilePower 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:
|
||||
|
@ -1673,6 +1685,15 @@ exports.Op = class Op extends Base
|
|||
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
|
||||
|
||||
|
@ -2131,6 +2152,10 @@ UTILITIES =
|
|||
[].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(a, b) { return (a % b + b) % b; }
|
||||
"""
|
||||
|
||||
# Shortcuts to speed up the lookup time for native functions.
|
||||
hasProp: -> '{}.hasOwnProperty'
|
||||
slice : -> '[].slice'
|
||||
|
|
|
@ -307,7 +307,47 @@ test "power operator has higher precedence than other maths operators", ->
|
|||
eq 0, (!2) ** 2
|
||||
eq -2, ~1 ** 5
|
||||
|
||||
#test "power operator has lower precedence than"
|
||||
|
||||
test "power operator is right associative", ->
|
||||
eq 2, 2 ** 1 ** 3
|
||||
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) ->
|
||||
res = a %% b
|
||||
# Don't use eq because it treats 0 as different to -0.
|
||||
ok res == expected or isNaN(res) and isNaN(expected),
|
||||
"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
|
||||
|
|
|
@ -55,9 +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
|
||||
|
|
Loading…
Reference in a new issue