mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
41185ca7ac
* normalize :: syntax * fixes from code review
453 lines
10 KiB
CoffeeScript
453 lines
10 KiB
CoffeeScript
# Operators
|
|
# ---------
|
|
|
|
# * Operators
|
|
# * Existential Operator (Binary)
|
|
# * Existential Operator (Unary)
|
|
# * Aliased Operators
|
|
# * [not] in/of
|
|
# * Chained Comparison
|
|
|
|
test "binary (2-ary) math operators do not require spaces", ->
|
|
a = 1
|
|
b = -1
|
|
eq +1, a*-b
|
|
eq -1, a*+b
|
|
eq +1, a/-b
|
|
eq -1, a/+b
|
|
|
|
test "operators should respect new lines as spaced", ->
|
|
a = 123 +
|
|
456
|
|
eq 579, a
|
|
|
|
b = "1#{2}3" +
|
|
"456"
|
|
eq '123456', b
|
|
|
|
test "multiple operators should space themselves", ->
|
|
eq (+ +1), (- -1)
|
|
|
|
test "compound operators on successive lines", ->
|
|
a = 1
|
|
a +=
|
|
1
|
|
eq a, 2
|
|
|
|
test "bitwise operators", ->
|
|
eq 2, (10 & 3)
|
|
eq 11, (10 | 3)
|
|
eq 9, (10 ^ 3)
|
|
eq 80, (10 << 3)
|
|
eq 1, (10 >> 3)
|
|
eq 1, (10 >>> 3)
|
|
num = 10; eq 2, (num &= 3)
|
|
num = 10; eq 11, (num |= 3)
|
|
num = 10; eq 9, (num ^= 3)
|
|
num = 10; eq 80, (num <<= 3)
|
|
num = 10; eq 1, (num >>= 3)
|
|
num = 10; eq 1, (num >>>= 3)
|
|
|
|
test "`instanceof`", ->
|
|
ok new String instanceof String
|
|
ok new Boolean instanceof Boolean
|
|
# `instanceof` supports negation by prefixing the operator with `not`
|
|
ok new Number not instanceof String
|
|
ok new Array not instanceof Boolean
|
|
|
|
test "use `::` operator on keywords `this` and `@`", ->
|
|
nonce = {}
|
|
obj =
|
|
withAt: -> @::prop
|
|
withThis: -> this::prop
|
|
obj.prototype = prop: nonce
|
|
eq nonce, obj.withAt()
|
|
eq nonce, obj.withThis()
|
|
|
|
|
|
# Existential Operator (Binary)
|
|
|
|
test "binary existential operator", ->
|
|
nonce = {}
|
|
|
|
b = a ? nonce
|
|
eq nonce, b
|
|
|
|
a = null
|
|
b = undefined
|
|
b = a ? nonce
|
|
eq nonce, b
|
|
|
|
a = false
|
|
b = a ? nonce
|
|
eq false, b
|
|
|
|
a = 0
|
|
b = a ? nonce
|
|
eq 0, b
|
|
|
|
test "binary existential operator conditionally evaluates second operand", ->
|
|
i = 1
|
|
func = -> i -= 1
|
|
result = func() ? func()
|
|
eq result, 0
|
|
|
|
test "binary existential operator with negative number", ->
|
|
a = null ? - 1
|
|
eq -1, a
|
|
|
|
|
|
# Existential Operator (Unary)
|
|
|
|
test "postfix existential operator", ->
|
|
ok (if nonexistent? then false else true)
|
|
defined = true
|
|
ok defined?
|
|
defined = false
|
|
ok defined?
|
|
|
|
test "postfix existential operator only evaluates its operand once", ->
|
|
semaphore = 0
|
|
fn = ->
|
|
ok false if semaphore
|
|
++semaphore
|
|
ok(if fn()? then true else false)
|
|
|
|
test "negated postfix existential operator", ->
|
|
ok !nothing?.value
|
|
|
|
test "postfix existential operator on expressions", ->
|
|
eq true, (1 or 0)?, true
|
|
|
|
|
|
# `is`,`isnt`,`==`,`!=`
|
|
|
|
test "`==` and `is` should be interchangeable", ->
|
|
a = b = 1
|
|
ok a is 1 and b == 1
|
|
ok a == b
|
|
ok a is b
|
|
|
|
test "`!=` and `isnt` should be interchangeable", ->
|
|
a = 0
|
|
b = 1
|
|
ok a isnt 1 and b != 0
|
|
ok a != b
|
|
ok a isnt b
|
|
|
|
|
|
# [not] in/of
|
|
|
|
# - `in` should check if an array contains a value using `indexOf`
|
|
# - `of` should check if a property is defined on an object using `in`
|
|
test "in, of", ->
|
|
arr = [1]
|
|
ok 0 of arr
|
|
ok 1 in arr
|
|
# prefixing `not` to `in and `of` should negate them
|
|
ok 1 not of arr
|
|
ok 0 not in arr
|
|
|
|
test "`in` should be able to operate on an array literal", ->
|
|
ok 2 in [0, 1, 2, 3]
|
|
ok 4 not in [0, 1, 2, 3]
|
|
arr = [0, 1, 2, 3]
|
|
ok 2 in arr
|
|
ok 4 not in arr
|
|
# should cache the value used to test the array
|
|
arr = [0]
|
|
val = 0
|
|
ok val++ in arr
|
|
ok val++ not in arr
|
|
val = 0
|
|
ok val++ of arr
|
|
ok val++ not of arr
|
|
|
|
test "`of` and `in` should be able to operate on instance variables", ->
|
|
obj = {
|
|
list: [2,3]
|
|
in_list: (value) -> value in @list
|
|
not_in_list: (value) -> value not in @list
|
|
of_list: (value) -> value of @list
|
|
not_of_list: (value) -> value not of @list
|
|
}
|
|
ok obj.in_list 3
|
|
ok obj.not_in_list 1
|
|
ok obj.of_list 0
|
|
ok obj.not_of_list 2
|
|
|
|
test "#???: `in` with cache and `__indexOf` should work in argument lists", ->
|
|
eq 1, [Object() in Array()].length
|
|
|
|
test "#737: `in` should have higher precedence than logical operators", ->
|
|
eq 1, 1 in [1] and 1
|
|
|
|
test "#768: `in` should preserve evaluation order", ->
|
|
share = 0
|
|
a = -> share++ if share is 0
|
|
b = -> share++ if share is 1
|
|
c = -> share++ if share is 2
|
|
ok a() not in [b(),c()]
|
|
eq 3, share
|
|
|
|
test "#1099: empty array after `in` should compile to `false`", ->
|
|
eq 1, [5 in []].length
|
|
eq false, do -> return 0 in []
|
|
|
|
test "#1354: optimized `in` checks should not happen when splats are present", ->
|
|
a = [6, 9]
|
|
eq 9 in [3, a...], true
|
|
|
|
test "#1100: precedence in or-test compilation of `in`", ->
|
|
ok 0 in [1 and 0]
|
|
ok 0 in [1, 1 and 0]
|
|
ok not (0 in [1, 0 or 1])
|
|
|
|
test "#1630: `in` should check `hasOwnProperty`", ->
|
|
ok undefined not in length: 1
|
|
|
|
test "#1714: lexer bug with raw range `for` followed by `in`", ->
|
|
0 for [1..2]
|
|
ok not ('a' in ['b'])
|
|
|
|
0 for [1..2]; ok not ('a' in ['b'])
|
|
|
|
0 for [1..10] # comment ending
|
|
ok not ('a' in ['b'])
|
|
|
|
# lexer state (specifically @seenFor) should be reset before each compilation
|
|
CoffeeScript.compile "0 for [1..2]"
|
|
CoffeeScript.compile "'a' in ['b']"
|
|
|
|
test "#1099: statically determined `not in []` reporting incorrect result", ->
|
|
ok 0 not in []
|
|
|
|
test "#1099: make sure expression tested gets evaluted when array is empty", ->
|
|
a = 0
|
|
(do -> a = 1) in []
|
|
eq a, 1
|
|
|
|
# Chained Comparison
|
|
|
|
test "chainable operators", ->
|
|
ok 100 > 10 > 1 > 0 > -1
|
|
ok -1 < 0 < 1 < 10 < 100
|
|
|
|
test "`is` and `isnt` may be chained", ->
|
|
ok true is not false is true is not false
|
|
ok 0 is 0 isnt 1 is 1
|
|
|
|
test "different comparison operators (`>`,`<`,`is`,etc.) may be combined", ->
|
|
ok 1 < 2 > 1
|
|
ok 10 < 20 > 2+3 is 5
|
|
|
|
test "some chainable operators can be negated by `unless`", ->
|
|
ok (true unless 0==10!=100)
|
|
|
|
test "operator precedence: `|` lower than `<`", ->
|
|
eq 1, 1 | 2 < 3 < 4
|
|
|
|
test "preserve references", ->
|
|
a = b = c = 1
|
|
# `a == b <= c` should become `a === b && b <= c`
|
|
# (this test does not seem to test for this)
|
|
ok a == b <= c
|
|
|
|
test "chained operations should evaluate each value only once", ->
|
|
a = 0
|
|
ok 1 > a++ < 1
|
|
|
|
test "#891: incorrect inversion of chained comparisons", ->
|
|
ok (true unless 0 > 1 > 2)
|
|
ok (true unless (this.NaN = 0/0) < 0/0 < this.NaN)
|
|
|
|
test "#1234: Applying a splat to :: applies the splat to the wrong object", ->
|
|
nonce = {}
|
|
class C
|
|
method: -> @nonce
|
|
nonce: nonce
|
|
|
|
arr = []
|
|
eq nonce, C::method arr... # should be applied to `C::`
|
|
|
|
test "#1102: String literal prevents line continuation", ->
|
|
eq "': '", '' +
|
|
"': '"
|
|
|
|
test "#1703, ---x is invalid JS", ->
|
|
x = 2
|
|
eq (- --x), -1
|
|
|
|
test "Regression with implicit calls against an indented assignment", ->
|
|
eq 1, a =
|
|
1
|
|
|
|
eq a, 1
|
|
|
|
test "#2155 ... conditional assignment to a closure", ->
|
|
x = null
|
|
func = -> x ?= (-> if true then 'hi')
|
|
func()
|
|
eq x(), 'hi'
|
|
|
|
test "#2197: Existential existential double trouble", ->
|
|
counter = 0
|
|
func = -> counter++
|
|
func()? ? 100
|
|
eq counter, 1
|
|
|
|
test "#2567: Optimization of negated existential produces correct result", ->
|
|
a = 1
|
|
ok !(!a?)
|
|
ok !b?
|
|
|
|
test "#2508: Existential access of the prototype", ->
|
|
eq NonExistent?::nothing, undefined
|
|
eq(
|
|
NonExistent
|
|
?::nothing
|
|
undefined
|
|
)
|
|
ok Object?::toString
|
|
ok(
|
|
Object
|
|
?::toString
|
|
)
|
|
|
|
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 //= 1 + 1
|
|
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'
|
|
|
|
test "#3361: Modulo operator coerces right operand once", ->
|
|
count = 0
|
|
res = 42 %% valueOf: -> count += 1
|
|
eq 1, count
|
|
eq 0, res
|
|
|
|
test "#3363: Modulo operator coercing order", ->
|
|
count = 2
|
|
a = valueOf: -> count *= 2
|
|
b = valueOf: -> count += 1
|
|
eq 4, a %% b
|
|
eq 5, count
|
|
|
|
test "#3598: Unary + and - coerce the operand once when it is an identifier", ->
|
|
# Unary + and - do not generate `_ref`s when the operand is a number, for
|
|
# readability. To make sure that they do when the operand is an identifier,
|
|
# test that they are consistent with another unary operator as well as another
|
|
# complex expression.
|
|
# Tip: Making one of the tests temporarily fail lets you easily inspect the
|
|
# compiled JavaScript.
|
|
|
|
assertOneCoercion = (fn) ->
|
|
count = 0
|
|
value = valueOf: -> count++; 1
|
|
fn value
|
|
eq 1, count
|
|
|
|
eq 1, 1 ? 0
|
|
eq 1, +1 ? 0
|
|
eq -1, -1 ? 0
|
|
assertOneCoercion (a) ->
|
|
eq 1, +a ? 0
|
|
assertOneCoercion (a) ->
|
|
eq -1, -a ? 0
|
|
assertOneCoercion (a) ->
|
|
eq -2, ~a ? 0
|
|
assertOneCoercion (a) ->
|
|
eq 0.5, a / 2 ? 0
|
|
|
|
ok -2 <= 1 < 2
|
|
ok -2 <= +1 < 2
|
|
ok -2 <= -1 < 2
|
|
assertOneCoercion (a) ->
|
|
ok -2 <= +a < 2
|
|
assertOneCoercion (a) ->
|
|
ok -2 <= -a < 2
|
|
assertOneCoercion (a) ->
|
|
ok -2 <= ~a < 2
|
|
assertOneCoercion (a) ->
|
|
ok -2 <= a / 2 < 2
|
|
|
|
arrayEq [0], (n for n in [0] by 1)
|
|
arrayEq [0], (n for n in [0] by +1)
|
|
arrayEq [0], (n for n in [0] by -1)
|
|
assertOneCoercion (a) ->
|
|
arrayEq [0], (n for n in [0] by +a)
|
|
assertOneCoercion (a) ->
|
|
arrayEq [0], (n for n in [0] by -a)
|
|
assertOneCoercion (a) ->
|
|
arrayEq [0], (n for n in [0] by ~a)
|
|
assertOneCoercion (a) ->
|
|
arrayEq [0], (n for n in [0] by a * 2 / 2)
|
|
|
|
ok 1 in [0, 1]
|
|
ok +1 in [0, 1]
|
|
ok -1 in [0, -1]
|
|
assertOneCoercion (a) ->
|
|
ok +a in [0, 1]
|
|
assertOneCoercion (a) ->
|
|
ok -a in [0, -1]
|
|
assertOneCoercion (a) ->
|
|
ok ~a in [0, -2]
|
|
assertOneCoercion (a) ->
|
|
ok a / 2 in [0, 0.5]
|
|
|
|
test "'new' target", ->
|
|
nonce = {}
|
|
ctor = -> nonce
|
|
|
|
eq (new ctor), nonce
|
|
eq (new ctor()), nonce
|
|
|
|
ok new class
|
|
|
|
ctor = class
|
|
ok (new ctor) instanceof ctor
|
|
ok (new ctor()) instanceof ctor
|
|
|
|
# Force an executable class body
|
|
ctor = class then a = 1
|
|
ok (new ctor) instanceof ctor
|
|
|
|
get = -> ctor
|
|
ok (new get()) not instanceof ctor
|
|
ok (new (get())()) instanceof ctor
|
|
|
|
# classes must be called with `new`. In this case `new` applies to `get` only
|
|
throws -> new get()()
|