You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
5.4 KiB
CoffeeScript
196 lines
5.4 KiB
CoffeeScript
###
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2013-2016, Reactive Sets
|
|
|
|
equals( a, b )
|
|
|
|
Returns true if a and b are deeply equal, false otherwise.
|
|
|
|
Parameters:
|
|
- a (Any type): value to compare to b
|
|
- b (Any type): value compared to a
|
|
|
|
Implementation:
|
|
'a' is considered equal to 'b' if all scalar values in a and b are strictly equal as
|
|
compared with operator '==' except for these two special cases:
|
|
- 0 == -0 but are not equal.
|
|
- NaN is not == to itself but is equal.
|
|
|
|
RegExp objects are considered equal if they have the same lastIndex, i.e. both regular
|
|
expressions have matched the same number of times.
|
|
|
|
Functions must be identical, so that they have the same closure context.
|
|
"undefined" is a valid value, including in Objects
|
|
###
|
|
equals = ( a, b ) ->
|
|
return a == b && # strict equality should be enough unless zero
|
|
a != 0 || # because 0 == -0, requires test by _equals()
|
|
_equals( a, b ) # handles not strictly equal or zero values
|
|
|
|
_equals = ( a, b ) ->
|
|
# a and b have already failed test for strict equality or are zero
|
|
# They should have the same toString() signature
|
|
if ( ( s = toString.call( a ) ) != toString.call( b ) )
|
|
return false
|
|
|
|
switch s
|
|
when '[object Number]'
|
|
# Converts Number instances into primitive values
|
|
# This is required also for NaN test bellow
|
|
a = +a
|
|
b = +b
|
|
|
|
if a
|
|
return a == b
|
|
else
|
|
if a == a # a is 0 or -O
|
|
return 1/a == 1/b # 1/0 != 1/-0 because Infinity != -Infinity
|
|
return b != b # NaN, the only Number not equal to itself!
|
|
when '[object RegExp]'
|
|
return a.source == b.source &&
|
|
a.global == b.global &&
|
|
a.ignoreCase == b.ignoreCase &&
|
|
a.multiline == b.multiline &&
|
|
a.lastIndex == b.lastIndex
|
|
when '[object Function]'
|
|
return false # functions should be strictly equal because of closure context
|
|
when '[object Array]'
|
|
if ( ( l = a.length ) != b.length )
|
|
return false
|
|
# Both have as many elements
|
|
|
|
while ( l-- )
|
|
if ( ( x = a[ l ] ) == ( y = b[ l ] ) && x != 0 || _equals( x, y ) )
|
|
continue
|
|
return false
|
|
return true
|
|
when '[object Object]'
|
|
l = 0 # counter of own properties
|
|
|
|
for p in a
|
|
if ( a.hasOwnProperty( p ) )
|
|
++l
|
|
|
|
if ( ( x = a[ p ] ) == ( y = b[ p ] ) && x != 0 || _equals( x, y ) )
|
|
continue
|
|
|
|
return false
|
|
|
|
# Check if 'b' has as not more own properties than 'a'
|
|
for p in b
|
|
if ( b.hasOwnProperty( p ) && --l < 0 )
|
|
return false
|
|
|
|
return true
|
|
else # Boolean, Date, String
|
|
return a.valueOf() == b.valueOf()
|
|
|
|
partial = (func) ->
|
|
boundArgs = arguments #slice.call(arguments, 1)
|
|
bound = () ->
|
|
position = 0
|
|
length = boundArgs.length
|
|
args = Array(length)
|
|
for i in [0..length]
|
|
args[i] = boundArgs[i]
|
|
while (position < arguments.length)
|
|
args.push(arguments[position++]);
|
|
return executeBound(func, bound, this, this, args)
|
|
return bound
|
|
|
|
TAG_COMPARISON =
|
|
TOTAL: 1
|
|
PARTIAL: 0
|
|
MISMATCH: -1
|
|
|
|
compareTags = (a, b) ->
|
|
# Returns a TAG_COMPARISON value
|
|
if (equals(a, b))
|
|
return TAG_COMPARISON.TOTAL
|
|
# If the tags are unequal but have the same length, it stands to reason
|
|
# there is a mismatch.
|
|
if (a.length == b.length)
|
|
return TAG_COMPARISON.MISMATCH
|
|
if a < b
|
|
[shorter, longer] = [a, b]
|
|
else
|
|
[shorter, longer] = [b, a]
|
|
for x, index in shorter
|
|
if x != longer[index]
|
|
return TAG_COMPARISON.MISMATCH
|
|
return TAG_COMPARISON.PARTIAL
|
|
|
|
# Ensures that the group and model don't have any mismatched tags
|
|
mismatchFilterSub = (group, model) ->
|
|
if not group.tags?
|
|
return 0
|
|
result = group.tags.find((groupTag) ->
|
|
# Look for a mismatch.
|
|
matched = model.tags.find (modelTag) =>
|
|
modelTag[0] == groupTag[0]
|
|
if not matched?
|
|
return false
|
|
if compareTags(groupTag, matched) == TAG_COMPARISON.MISMATCH
|
|
return true;
|
|
return false
|
|
)
|
|
if not result?
|
|
return 0
|
|
return null
|
|
|
|
bonusCompare = (mode, bonus = 1, cumulative = false) ->
|
|
return (group, model) ->
|
|
results = group.tags.filter((groupTag) ->
|
|
matched = model.tags.find((modelTag) =>
|
|
modelTag[0] == groupTag[0]
|
|
)
|
|
if not matched?
|
|
return false
|
|
if compareTags(groupTag, matched) == mode
|
|
return true
|
|
return false
|
|
)
|
|
if (results.length)
|
|
if cumulative
|
|
return bonus * results.length
|
|
else
|
|
return bonus
|
|
return 0
|
|
|
|
filters = {}
|
|
|
|
filters.mismatchFilter = () ->
|
|
return mismatchFilterSub
|
|
|
|
filters.partialBonus = partial(bonusCompare, TAG_COMPARISON.PARTIAL)
|
|
|
|
filters.fullBonus = partial(bonusCompare, TAG_COMPARISON.TOTAL)
|
|
|
|
filters.dryness = () ->
|
|
return (group) ->
|
|
that = this
|
|
newPhrases = []
|
|
for phrase in group.phrases
|
|
if (that.history.indexOf(phrase) == -1 && phrase != null)
|
|
newPhrases.push(phrase)
|
|
newGroup = Object.create(group)
|
|
newGroup.phrases = newPhrases
|
|
return [0, newGroup]
|
|
|
|
filters.unmentioned = (bonus = 1) ->
|
|
return (group) ->
|
|
if (!Array.isArray(group.tags))
|
|
return 0
|
|
if (group.tags.length == 0)
|
|
return 0
|
|
that = this
|
|
for t in group.tags
|
|
# Return true if the tag is "novel".
|
|
for u in that.tagHistory
|
|
if u[0] == t[0]
|
|
return bonus
|
|
return 0
|
|
|
|
Improv.filters = filters
|