Skip to content

Instantly share code, notes, and snippets.

@mariechatfield
Last active December 20, 2015 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mariechatfield/6178151 to your computer and use it in GitHub Desktop.
Save mariechatfield/6178151 to your computer and use it in GitHub Desktop.
Clean up Sk.misceval.richCompareBool and make behave like Python when comparing objects of different built-in types. Also improve error messages when attempting to compare invalid types.
Sk.abstr.typeName = function(v) {
var vtypename;
if (v instanceof Sk.builtin.nmber) {
vtypename = v.skType;
} else if (v.tp$name !== undefined) {
vtypename = v.tp$name;
} else {
vtypename = "<invalid type>";
};
return vtypename;
};
Sk.builtin.type = function(name, bases, dict)
{
if (bases === undefined && dict === undefined)
{
// 1 arg version of type()
// the argument is an object, not a name and returns a type object
var obj = name;
if (obj.constructor === Sk.builtin.nmber)
{
if (obj.skType === Sk.builtin.nmber.int$)
{
return Sk.builtin.int_.prototype.ob$type;
}
else
{
return Sk.builtin.float_.prototype.ob$type;
}
}
return obj.ob$type;
}
else
Sk.misceval.richCompareBool = function(v, w, op)
{
// v and w must be Python objects. will return Javascript true or false for internal use only
// if you want to return a value from richCompareBool to Python you must wrap as Sk.builtin.bool first
goog.asserts.assert((v !== null) && (v !== undefined), "passed undefined or null parameter to Sk.misceval.richCompareBool");
goog.asserts.assert((w !== null) && (w !== undefined), "passed undefined or null parameter to Sk.misceval.richCompareBool");
var v_type = new Sk.builtin.type(v);
var w_type = new Sk.builtin.type(w);
// Python has specific rules when comparing two different builtin types
// currently, this code will execute even if the objects are not builtin types
// but will fall through and not return anything in this section
if ((v_type !== w_type)
&& (op === 'GtE' || op === 'Gt' || op === 'LtE' || op === 'Lt'))
{
// note: sets are omitted here because they can only be compared to other sets
var numeric_types = [Sk.builtin.float_.prototype.ob$type,
Sk.builtin.int_.prototype.ob$type,
Sk.builtin.lng.prototype.ob$type,
Sk.builtin.bool.prototype.ob$type];
var sequence_types = [Sk.builtin.dict.prototype.ob$type,
Sk.builtin.enumerate.prototype.ob$type,
Sk.builtin.list.prototype.ob$type,
Sk.builtin.str.prototype.ob$type,
Sk.builtin.tuple.prototype.ob$type];
var v_num_type = numeric_types.indexOf(v_type);
var v_seq_type = sequence_types.indexOf(v_type);
var w_num_type = numeric_types.indexOf(w_type);
var w_seq_type = sequence_types.indexOf(w_type);
// NoneTypes are considered less than any other type in Python
// note: this only handles comparing NoneType with any non-NoneType.
// Comparing NoneType with NoneType is handled further down.
if (v_type === Sk.builtin.none.prototype.ob$type)
{
switch (op)
{
case 'Lt': return true;
case 'LtE': return true;
case 'Gt': return false;
case 'GtE': return false;
}
}
if (w_type === Sk.builtin.none.prototype.ob$type)
{
switch (op)
{
case 'Lt': return false;
case 'LtE': return false;
case 'Gt': return true;
case 'GtE': return true;
}
}
// numeric types are always considered smaller than sequence types in Python
if (v_num_type !== -1 && w_seq_type !== -1)
{
switch (op)
{
case 'Lt': return true;
case 'LtE': return true;
case 'Gt': return false;
case 'GtE': return false;
}
}
if (v_seq_type !== -1 && w_num_type !== -1)
{
switch (op)
{
case 'Lt': return false;
case 'LtE': return false;
case 'Gt': return true;
case 'GtE': return true;
}
}
// in Python, different sequence types are ordered alphabetically
// by name so that dict < list < str < tuple
if (v_seq_type !== -1 && w_seq_type !== -1)
{
switch (op)
{
case 'Lt': return v_seq_type < w_seq_type;
case 'LtE': return v_seq_type <= w_seq_type;
case 'Gt': return v_seq_type > w_seq_type;
case 'GtE': return v_seq_type >= w_seq_type;
}
}
}
// handle identity and membership comparisons
if (op === 'Is') {
if (v instanceof Sk.builtin.nmber && w instanceof Sk.builtin.nmber)
{
return (v.numberCompare(w) === 0) && (v.skType === w.skType);
}
else if (v instanceof Sk.builtin.lng && w instanceof Sk.builtin.lng)
{
return v.longCompare(w) === 0;
}
return v === w;
}
if (op === 'IsNot') {
if (v instanceof Sk.builtin.nmber && w instanceof Sk.builtin.nmber)
{
return (v.numberCompare(w) !== 0) || (v.skType !== w.skType);
}
else if (v instanceof Sk.builtin.lng && w instanceof Sk.builtin.lng)
{
return v.longCompare(w) !== 0;
}
return v !== w;
}
if (op === "In")
return Sk.abstr.sequenceContains(w, v);
if (op === "NotIn")
return !Sk.abstr.sequenceContains(w, v);
// use comparison methods if they are given for either object
var res;
if (v.tp$richcompare && (res = v.tp$richcompare(w, op)) !== undefined)
{
return res;
}
if (w.tp$richcompare && (res = w.tp$richcompare(v, Sk.misceval.swappedOp_[op])) !== undefined)
{
return res;
}
// depending on the op, try left:op:right, and if not, then
// right:reversed-top:left
var op2method = {
'Eq': '__eq__',
'NotEq': '__ne__',
'Gt': '__gt__',
'GtE': '__ge__',
'Lt': '__lt__',
'LtE': '__le__'
};
var method = op2method[op];
var swapped_method = op2method[Sk.misceval.swappedOp_[op]];
if (v[method])
{
return Sk.misceval.callsim(v[method], v, w);
}
else if (w[swapped_method])
{
return Sk.misceval.callsim(w[swapped_method], w, v);
}
if (v['__cmp__'])
{
var ret = Sk.misceval.callsim(v['__cmp__'], v, w);
ret = Sk.builtin.asnum$(ret);
if (op === 'Eq') return ret === 0;
else if (op === 'NotEq') return ret !== 0;
else if (op === 'Lt') return ret < 0;
else if (op === 'Gt') return ret > 0;
else if (op === 'LtE') return ret <= 0;
else if (op === 'GtE') return ret >= 0;
}
if (w['__cmp__'])
{
// note, flipped on return value and call
var ret = Sk.misceval.callsim(w['__cmp__'], w, v);
ret = Sk.builtin.asnum$(ret);
if (op === 'Eq') return ret === 0;
else if (op === 'NotEq') return ret !== 0;
else if (op === 'Lt') return ret > 0;
else if (op === 'Gt') return ret < 0;
else if (op === 'LtE') return ret >= 0;
else if (op === 'GtE') return ret <= 0;
}
// handle special cases for comparing None with None or Bool with Bool
if (((v instanceof Sk.builtin.none) && (w instanceof Sk.builtin.none))
|| ((v instanceof Sk.builtin.bool) && (w instanceof Sk.builtin.bool)))
{
// Javascript happens to return the same values when comparing null
// with null or true/false with true/false as Python does when
// comparing None with None or True/False with True/False
if (op === 'Eq')
return v.v === w.v;
if (op === 'NotEq')
return v.v !== w.v;
if (op === 'Gt')
return v.v > w.v;
if (op === 'GtE')
return v.v >= w.v;
if (op === 'Lt')
return v.v < w.v;
if (op === 'LtE')
return v.v <= w.v;
}
// handle equality comparisons for any remaining objects
if (op === 'Eq')
{
if ((v instanceof Sk.builtin.str) && (w instanceof Sk.builtin.str))
return v.v === w.v;
return v === w;
}
if (op === 'NotEq')
{
if ((v instanceof Sk.builtin.str) && (w instanceof Sk.builtin.str))
return v.v !== w.v;
return v !== w;
}
var vname = Sk.abstr.typeName(v);
var wname = Sk.abstr.typeName(w);
throw new Sk.builtin.ValueError("don't know how to compare '" + vname + "' and '" + wname + "'");
};
goog.exportSymbol("Sk.misceval.richCompareBool", Sk.misceval.richCompareBool);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment