Fix #4877: Exponentiation operators (#4881)

* Passthrough exponentiation operator; remove tests that are invalid JavaScript

* Treat **= as a passthrough assignment

* Get tests passing in Node 6

* Improve scoping

* Move exponentiation tests into their own file, now that it's filtered out by Cakefile

* Restore original test
This commit is contained in:
Geoffrey Booth 2018-03-30 00:47:40 -07:00 committed by GitHub
parent e5aa758dda
commit 195a46ab77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 27 additions and 41 deletions

View File

@ -3318,11 +3318,11 @@
if ((ref1 = this.context) === '||=' || ref1 === '&&=' || ref1 === '?=') { if ((ref1 = this.context) === '||=' || ref1 === '&&=' || ref1 === '?=') {
return this.compileConditional(o); return this.compileConditional(o);
} }
if ((ref2 = this.context) === '**=' || ref2 === '//=' || ref2 === '%%=') { if ((ref2 = this.context) === '//=' || ref2 === '%%=') {
return this.compileSpecialMath(o); return this.compileSpecialMath(o);
} }
} }
if (!this.context) { if (!this.context || this.context === '**=') {
varBase = this.variable.unwrapAll(); varBase = this.variable.unwrapAll();
if (!varBase.isAssignable()) { if (!varBase.isAssignable()) {
this.variable.error(`'${this.variable.compile(o)}' can't be assigned`); this.variable.error(`'${this.variable.compile(o)}' can't be assigned`);
@ -3791,7 +3791,7 @@
} }
} }
// Convert special math assignment operators like `a **= b` to the equivalent // Convert special math assignment operators like `a //= b` to the equivalent
// extended form `a = a ** b` and then compiles that. // extended form `a = a ** b` and then compiles that.
compileSpecialMath(o) { compileSpecialMath(o) {
var left, right; var left, right;
@ -4800,8 +4800,6 @@
switch (this.operator) { switch (this.operator) {
case '?': case '?':
return this.compileExistence(o, this.second.isDefaultValue); return this.compileExistence(o, this.second.isDefaultValue);
case '**':
return this.compilePower(o);
case '//': case '//':
return this.compileFloorDivision(o); return this.compileFloorDivision(o);
case '%%': case '%%':
@ -4903,13 +4901,6 @@
return this.joinFragmentArrays(parts, ''); return this.joinFragmentArrays(parts, '');
} }
compilePower(o) {
var pow;
// Make a Math.pow call
pow = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('pow'))]);
return new Call(pow, [this.first, this.second]).compileToFragments(o);
}
compileFloorDivision(o) { compileFloorDivision(o) {
var div, floor, second; var div, floor, second;
floor = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('floor'))]); floor = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('floor'))]);

View File

@ -2222,9 +2222,9 @@ exports.Assign = class Assign extends Base
return @compileSplice o if @variable.isSplice() return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?='] return @compileConditional o if @context in ['||=', '&&=', '?=']
return @compileSpecialMath o if @context in ['**=', '//=', '%%='] return @compileSpecialMath o if @context in ['//=', '%%=']
unless @context if not @context or @context is '**='
varBase = @variable.unwrapAll() varBase = @variable.unwrapAll()
unless varBase.isAssignable() unless varBase.isAssignable()
@variable.error "'#{@variable.compile o}' can't be assigned" @variable.error "'#{@variable.compile o}' can't be assigned"
@ -2548,7 +2548,7 @@ exports.Assign = class Assign extends Base
fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments
# Convert special math assignment operators like `a **= b` to the equivalent # Convert special math assignment operators like `a //= b` to the equivalent
# extended form `a = a ** b` and then compiles that. # extended form `a = a ** b` and then compiles that.
compileSpecialMath: (o) -> compileSpecialMath: (o) ->
[left, right] = @variable.cacheReference o [left, right] = @variable.cacheReference o
@ -3253,7 +3253,6 @@ exports.Op = class Op extends Base
return @compileChain o if isChain return @compileChain o if isChain
switch @operator switch @operator
when '?' then @compileExistence o, @second.isDefaultValue when '?' then @compileExistence o, @second.isDefaultValue
when '**' then @compilePower o
when '//' then @compileFloorDivision o when '//' then @compileFloorDivision o
when '%%' then @compileModulo o when '%%' then @compileModulo o
else else
@ -3320,11 +3319,6 @@ exports.Op = class Op extends Base
parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
@joinFragmentArrays parts, '' @joinFragmentArrays parts, ''
compilePower: (o) ->
# Make a Math.pow call
pow = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'pow']
new Call(pow, [@first, @second]).compileToFragments o
compileFloorDivision: (o) -> compileFloorDivision: (o) ->
floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor'] floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
second = if @second.shouldCache() then new Parens @second else @second second = if @second.shouldCache() then new Parens @second else @second

View File

@ -0,0 +1,19 @@
# The `**` and `**=` operators are only supported in Node 7.5+, so the tests
# for these exponentiation operators are split out into their own file to be
# loaded only by supported runtimes.
test "exponentiation operator", ->
eq 27, 3 ** 3
test "exponentiation operator has higher precedence than other maths operators", ->
eq 55, 1 + 3 ** 3 * 2
eq -4, -2 ** 2
eq 0, (!2) ** 2
test "exponentiation operator is right associative", ->
eq 2, 2 ** 1 ** 3
test "exponentiation operator compound assignment", ->
a = 2
a **= 3
eq 8, a

View File

@ -168,7 +168,7 @@ test "`throw` can be yielded", ->
throws -> x.next() throws -> x.next()
test "symbolic operators has precedence over the `yield`", -> test "symbolic operators has precedence over the `yield`", ->
symbolic = '+ - * / << >> & | || && ** ^ // or and'.split ' ' symbolic = '+ - * / << >> & | || && ^ // or and'.split ' '
compound = ("#{op}=" for op in symbolic) compound = ("#{op}=" for op in symbolic)
relations = '< > == != <= >= is isnt'.split ' ' relations = '< > == != <= >= is isnt'.split ' '

View File

@ -139,7 +139,7 @@ eq 'multiline nested "interpolations" work', """multiline #{
eq 'function(){}', "#{->}".replace /\s/g, '' eq 'function(){}', "#{->}".replace /\s/g, ''
ok /^a[\s\S]+b$/.test "a#{=>}b" ok /^a[\s\S]+b$/.test "a#{=>}b"
ok /^a[\s\S]+b$/.test "a#{ (x) -> x ** 2 }b" ok /^a[\s\S]+b$/.test "a#{ (x) -> x %% 2 }b"
# Regular Expression Interpolation # Regular Expression Interpolation

View File

@ -305,24 +305,6 @@ test "#2508: Existential access of the prototype", ->
eq NonExistent?::nothing, undefined eq NonExistent?::nothing, undefined
ok Object?::toString 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", -> test "floor division operator", ->
eq 2, 7 // 3 eq 2, 7 // 3
eq -3, -7 // 3 eq -3, -7 // 3