text: /**
   * @namespace Sk.abstr
   *
   */
  Sk.abstr = {};
  
  //
  // Number
  //
  
  Sk.abstr.typeName = function (v) {
      var vtypename;
      if (v.tp$name !== undefined) {
          vtypename = v.tp$name;
      } else {
          vtypename = "<invalid type>";
      }
      return vtypename;
  };
  
  Sk.abstr.binop_type_error = function (v, w, name) {
      var vtypename = Sk.abstr.typeName(v),
          wtypename = Sk.abstr.typeName(w);
  
      throw new Sk.builtin.TypeError("unsupported operand type(s) for " + name + ": '" + vtypename + "' and '" + wtypename + "'");
  };
  
  Sk.abstr.unop_type_error = function (v, name) {
      var vtypename = Sk.abstr.typeName(v),
          uop = {
              "UAdd"  : "+",
              "USub"  : "-",
              "Invert": "~"
          }[name];
  
      throw new Sk.builtin.TypeError("bad operand type for unary " + uop + ": '" + vtypename + "'");
  };
  
  /**
   * lookup and return the LHS object slot function method.  This coudl be either a builtin slot function or a dunder method defined by the user.
   * @param obj
   * @param name
   * @returns {Object|null|undefined}
   * @private
   */
  Sk.abstr.boNameToSlotFuncLhs_ = function (obj, name) {
      if (obj === null) {
          return undefined;
      }
  
      switch (name) {
      case "Add":
          return obj.nb$add ? obj.nb$add : obj["__add__"];
      case "Sub":
          return obj.nb$subtract ? obj.nb$subtract : obj["__sub__"];
      case "Mult":
          return obj.nb$multiply ? obj.nb$multiply : obj["__mul__"];
      case "Div":
          return obj.nb$divide ? obj.nb$divide : obj["__div__"];
      case "FloorDiv":
          return obj.nb$floor_divide ? obj.nb$floor_divide : obj["__floordiv__"];
      case "Mod":
          return obj.nb$remainder ? obj.nb$remainder : obj["__mod__"];
      case "DivMod":
          return obj.nb$divmod ? obj.nb$divmod : obj["__divmod__"];
      case "Pow":
          return obj.nb$power ? obj.nb$power : obj["__pow__"];
      case "LShift":
          return obj.nb$lshift ? obj.nb$lshift : obj["__lshift__"];
      case "RShift":
          return obj.nb$rshift ? obj.nb$rshift : obj["__rshift__"];
      case "BitAnd":
          return obj.nb$and ? obj.nb$and : obj["__and__"];
      case "BitXor":
          return obj.nb$xor ? obj.nb$xor : obj["__xor__"];
      case "BitOr":
          return obj.nb$or ? obj.nb$or : obj["__or__"];
      }
  };
  
  Sk.abstr.boNameToSlotFuncRhs_ = function (obj, name) {
      if (obj === null) {
          return undefined;
      }
  
      switch (name) {
      case "Add":
          return obj.nb$reflected_add ? obj.nb$reflected_add : obj["__radd__"];
      case "Sub":
          return obj.nb$reflected_subtract ? obj.nb$reflected_subtract : obj["__rsub__"];
      case "Mult":
          return obj.nb$reflected_multiply ? obj.nb$reflected_multiply : obj["__rmul__"];
      case "Div":
          return obj.nb$reflected_divide ? obj.nb$reflected_divide : obj["__rdiv__"];
      case "FloorDiv":
          return obj.nb$reflected_floor_divide ? obj.nb$reflected_floor_divide : obj["__rfloordiv__"];
      case "Mod":
          return obj.nb$reflected_remainder ? obj.nb$reflected_remainder : obj["__rmod__"];
      case "DivMod":
          return obj.nb$reflected_divmod ? obj.nb$reflected_divmod : obj["__rdivmod__"];
      case "Pow":
          return obj.nb$reflected_power ? obj.nb$reflected_power : obj["__rpow__"];
      case "LShift":
          return obj.nb$reflected_lshift ? obj.nb$reflected_lshift : obj["__rlshift__"];
      case "RShift":
          return obj.nb$reflected_rshift ? obj.nb$reflected_rshift : obj["__rrshift__"];
      case "BitAnd":
          return obj.nb$reflected_and ? obj.nb$reflected_and : obj["__rand__"];
      case "BitXor":
          return obj.nb$reflected_xor ? obj.nb$reflected_xor : obj["__rxor__"];
      case "BitOr":
          return obj.nb$reflected_or ? obj.nb$reflected_or : obj["__ror__"];
      }
  };
  
  Sk.abstr.iboNameToSlotFunc_ = function (obj, name) {
      switch (name) {
      case "Add":
          return obj.nb$inplace_add ? obj.nb$inplace_add : obj["__iadd__"];
      case "Sub":
          return obj.nb$inplace_subtract ? obj.nb$inplace_subtract : obj["__isub__"];
      case "Mult":
          return obj.nb$inplace_multiply ? obj.nb$inplace_multiply : obj["__imul__"];
      case "Div":
          return obj.nb$inplace_divide ? obj.nb$inplace_divide : obj["__idiv__"];
      case "FloorDiv":
          return obj.nb$inplace_floor_divide ? obj.nb$inplace_floor_divide : obj["__ifloordiv__"];
      case "Mod":
          return obj.nb$inplace_remainder;
      case "Pow":
          return obj.nb$inplace_power;
      case "LShift":
          return obj.nb$inplace_lshift ? obj.nb$inplace_lshift : obj["__ilshift__"];
      case "RShift":
          return obj.nb$inplace_rshift ? obj.nb$inplace_rshift : obj["__irshift__"];
      case "BitAnd":
          return obj.nb$inplace_and;
      case "BitOr":
          return obj.nb$inplace_or;
      case "BitXor":
          return obj.nb$inplace_xor ? obj.nb$inplace_xor : obj["__ixor__"];
      }
  };
  Sk.abstr.uoNameToSlotFunc_ = function (obj, name) {
      if (obj === null) {
          return undefined;
      }
      switch (name) {
      case "USub":
          return obj.nb$negative ? obj.nb$negative : obj["__neg__"];
      case "UAdd":
          return obj.nb$positive ? obj.nb$positive : obj["__pos__"];
      case "Invert":
          return obj.nb$invert ? obj.nb$invert : obj["__invert__"];
      }
  };
  
  Sk.abstr.binary_op_ = function (v, w, opname) {
      var wop;
      var ret;
      var vop;
  
      // All Python inheritance is now enforced with Javascript inheritance
      // (see Sk.abstr.setUpInheritance). This checks if w's type is a strict
      // subclass of v's type
      var w_is_subclass = w.constructor.prototype instanceof v.constructor;
  
      // From the Python 2.7 docs:
      //
      // "If the right operand’s type is a subclass of the left operand’s type and
      // that subclass provides the reflected method for the operation, this
      // method will be called before the left operand’s non-reflected method.
      // This behavior allows subclasses to override their ancestors’ operations."
      //
      // -- https://docs.python.org/2/reference/datamodel.html#index-92
  
      if (w_is_subclass) {
          wop = Sk.abstr.boNameToSlotFuncRhs_(w, opname);
          if (wop !== undefined) {
              if (wop.call) {
                  ret = wop.call(w, v);
              } else {
                  ret = Sk.misceval.callsimArray(wop, [w, v]);
              }
              if (ret !== undefined && ret !== Sk.builtin.NotImplemented.NotImplemented$) {
                  return ret;
              }
          }
      }
  
      vop = Sk.abstr.boNameToSlotFuncLhs_(v, opname);
      if (vop !== undefined) {
          if (vop.call) {
              ret = vop.call(v, w);
          } else {
              ret = Sk.misceval.callsimArray(vop, [v, w]);
          }
          if (ret !== undefined && ret !== Sk.builtin.NotImplemented.NotImplemented$) {
              return ret;
          }
      }
      // Don't retry RHS if failed above
      if (!w_is_subclass) {
          wop = Sk.abstr.boNameToSlotFuncRhs_(w, opname);
          if (wop !== undefined) {
              if (wop.call) {
                  ret = wop.call(w, v);
              } else {
                  ret = Sk.misceval.callsimArray(wop, [w, v]);
              }
              if (ret !== undefined && ret !== Sk.builtin.NotImplemented.NotImplemented$) {
                  return ret;
              }
          }
      }
      Sk.abstr.binop_type_error(v, w, opname);
  };
  
  Sk.abstr.binary_iop_ = function (v, w, opname) {
      var wop;
      var ret;
      var vop = Sk.abstr.iboNameToSlotFunc_(v, opname);
      if (vop !== undefined) {
          if (vop.call) {
              ret = vop.call(v, w);
          } else {  // assume that vop is an __xxx__ type method
              ret = Sk.misceval.callsimArray(vop, [v, w]);
          }
          if (ret !== undefined && ret !== Sk.builtin.NotImplemented.NotImplemented$) {
              return ret;
          }
      }
      // If there wasn't an in-place operation, fall back to the binop
      return Sk.abstr.binary_op_(v, w, opname);
  };
  Sk.abstr.unary_op_ = function (v, opname) {
      var ret;
      var vop = Sk.abstr.uoNameToSlotFunc_(v, opname);
      if (vop !== undefined) {
          if (vop.call) {
              ret = vop.call(v);
          } else {  // assume that vop is an __xxx__ type method
              ret = Sk.misceval.callsimArray(vop, [v]); //  added to be like not-in-place... is this okay?
          }
          if (ret !== undefined) {
              return ret;
          }
      }
      Sk.abstr.unop_type_error(v, opname);
  };
  
  //
  // handle upconverting a/b from number to long if op causes too big/small a
  // result, or if either of the ops are already longs
  Sk.abstr.numOpAndPromote = function (a, b, opfn) {
      var tmp;
      var ans;
      if (a === null || b === null) {
          return undefined;
      }
  
      if (typeof a === "number" && typeof b === "number") {
          ans = opfn(a, b);
          // todo; handle float   Removed RNL (bugs in lng, and it should be a question of precision, not magnitude -- this was just wrong)
          if ((ans > Sk.builtin.int_.threshold$ || ans < -Sk.builtin.int_.threshold$) && Math.floor(ans) === ans) {
              return [Sk.builtin.lng.fromInt$(a), Sk.builtin.lng.fromInt$(b)];
          } else {
              return ans;
          }
      } else if (a === undefined || b === undefined) {
          throw new Sk.builtin.NameError("Undefined variable in expression");
      }
  
      if (a.constructor === Sk.builtin.lng) {
          return [a, b];
      } else if ((a.constructor === Sk.builtin.int_ ||
                  a.constructor === Sk.builtin.float_) &&
                  b.constructor === Sk.builtin.complex) {
          // special case of upconverting nmber and complex
          // can we use here the Sk.builtin.checkComplex() method?
          tmp = new Sk.builtin.complex(a);
          return [tmp, b];
      } else if (a.constructor === Sk.builtin.int_ ||
                 a.constructor === Sk.builtin.float_) {
          return [a, b];
      } else if (typeof a === "number") {
          tmp = Sk.builtin.assk$(a);
          return [tmp, b];
      } else {
          return undefined;
      }
  };
  
  Sk.abstr.boNumPromote_ = {
      "Add"     : function (a, b) {
          return a + b;
      },
      "Sub"     : function (a, b) {
          return a - b;
      },
      "Mult"    : function (a, b) {
          return a * b;
      },
      "Mod"     : function (a, b) {
          var m;
          if (b === 0) {
              throw new Sk.builtin.ZeroDivisionError("division or modulo by zero");
          }
          m = a % b;
          return ((m * b) < 0 ? (m + b) : m);
      },
      "Div"     : function (a, b) {
          if (b === 0) {
              throw new Sk.builtin.ZeroDivisionError("division or modulo by zero");
          } else {
              return a / b;
          }
      },
      "FloorDiv": function (a, b) {
          if (b === 0) {
              throw new Sk.builtin.ZeroDivisionError("division or modulo by zero");
          } else {
              return Math.floor(a / b);
          } // todo; wrong? neg?
      },
      "Pow"     : Math.pow,
      "BitAnd"  : function (a, b) {
          var m = a & b;
          if (m < 0) {
              m = m + 4294967296; // convert back to unsigned
          }
          return m;
      },
      "BitOr"   : function (a, b) {
          var m = a | b;
          if (m < 0) {
              m = m + 4294967296; // convert back to unsigned
          }
          return m;
      },
      "BitXor"  : function (a, b) {
          var m = a ^ b;
          if (m < 0) {
              m = m + 4294967296; // convert back to unsigned
          }
          return m;
      },
      "LShift"  : function (a, b) {
          var m;
          if (b < 0) {
              throw new Sk.builtin.ValueError("negative shift count");
          }
          m = a << b;
          if (m > a) {
              return m;
          } else {
              // Fail, this will get recomputed with longs
              return a * Math.pow(2, b);
          }
      },
      "RShift"  : function (a, b) {
          var m;
          if (b < 0) {
              throw new Sk.builtin.ValueError("negative shift count");
          }
          m = a >> b;
          if ((a > 0) && (m < 0)) {
              // fix incorrect sign extension
              m = m & (Math.pow(2, 32 - b) - 1);
          }
          return m;
      }
  };
  
  Sk.abstr.numberBinOp = function (v, w, op) {
      var tmp;
      var numPromoteFunc = Sk.abstr.boNumPromote_[op];
      if (numPromoteFunc !== undefined) {
          tmp = Sk.abstr.numOpAndPromote(v, w, numPromoteFunc);
          if (typeof tmp === "number") {
              return tmp;
          } else if (tmp !== undefined && tmp.constructor === Sk.builtin.int_) {
              return tmp;
          } else if (tmp !== undefined && tmp.constructor === Sk.builtin.float_) {
              return tmp;
          } else if (tmp !== undefined && tmp.constructor === Sk.builtin.lng) {
              return tmp;
          } else if (tmp !== undefined) {
              v = tmp[0];
              w = tmp[1];
          }
      }
  
      return Sk.abstr.binary_op_(v, w, op);
  };
  Sk.exportSymbol("Sk.abstr.numberBinOp", Sk.abstr.numberBinOp);
  
  Sk.abstr.numberInplaceBinOp = function (v, w, op) {
      var tmp;
      var numPromoteFunc = Sk.abstr.boNumPromote_[op];
      if (numPromoteFunc !== undefined) {
          tmp = Sk.abstr.numOpAndPromote(v, w, numPromoteFunc);
          if (typeof tmp === "number") {
              return tmp;
          } else if (tmp !== undefined && tmp.constructor === Sk.builtin.int_) {
              return tmp;
          } else if (tmp !== undefined && tmp.constructor === Sk.builtin.float_) {
              return tmp;
          } else if (tmp !== undefined && tmp.constructor === Sk.builtin.lng) {
              return tmp;
          } else if (tmp !== undefined) {
              v = tmp[0];
              w = tmp[1];
          }
      }
  
      return Sk.abstr.binary_iop_(v, w, op);
  };
  Sk.exportSymbol("Sk.abstr.numberInplaceBinOp", Sk.abstr.numberInplaceBinOp);
  
  Sk.abstr.numberUnaryOp = function (v, op) {
      var value;
      if (op === "Not") {
          return Sk.misceval.isTrue(v) ? Sk.builtin.bool.false$ : Sk.builtin.bool.true$;
      } else if (v instanceof Sk.builtin.bool) {
          value = Sk.builtin.asnum$(v);
          if (op === "USub") {
              return new Sk.builtin.int_(-value);
          }
          if (op === "UAdd") {
              return new Sk.builtin.int_(value);
          }
          if (op === "Invert") {
              return new Sk.builtin.int_(~value);
          }
      } else {
          if (op === "USub" && v.nb$negative) {
              return v.nb$negative();
          }
          if (op === "UAdd" && v.nb$positive) {
              return v.nb$positive();
          }
          if (op === "Invert" && v.nb$invert) {
              return v.nb$invert();
          }
      }
  
      return Sk.abstr.unary_op_(v, op);
  };
  Sk.exportSymbol("Sk.abstr.numberUnaryOp", Sk.abstr.numberUnaryOp);
  
  //
  // Sequence
  //
  
  Sk.abstr.fixSeqIndex_ = function (seq, i) {
      i = Sk.builtin.asnum$(i);
      if (i < 0 && seq.sq$length) {
          i += seq.sq$length();
      }
      return i;
  };
  
  /**
   * @param {*} seq
   * @param {*} ob
   * @param {boolean=} canSuspend
   */
  Sk.abstr.sequenceContains = function (seq, ob, canSuspend) {
      var seqtypename;
      var special;
      var r;
  
      if (seq.sq$contains) {
          return seq.sq$contains(ob);
      }
  
      /**
       *  Look for special method and call it, we have to distinguish between built-ins and
       *  python objects
       */
      special = Sk.abstr.lookupSpecial(seq, Sk.builtin.str.$contains);
      if (special != null) {
          // method on builtin, provide this arg
          return Sk.misceval.isTrue(Sk.misceval.callsimArray(special, [seq, ob]));
      }
  
      if (!Sk.builtin.checkIterable(seq)) {
          seqtypename = Sk.abstr.typeName(seq);
          throw new Sk.builtin.TypeError("argument of type '" + seqtypename + "' is not iterable");
      }
  
      r = Sk.misceval.iterFor(Sk.abstr.iter(seq), function(i) {
          if (Sk.misceval.richCompareBool(i, ob, "Eq")) {
              return new Sk.misceval.Break(true);
          } else {
              return false;
          }
      }, false);
  
      return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
  };
  
  Sk.abstr.sequenceConcat = function (seq1, seq2) {
      var seq1typename;
      if (seq1.sq$concat) {
          return seq1.sq$concat(seq2);
      }
      seq1typename = Sk.abstr.typeName(seq1);
      throw new Sk.builtin.TypeError("'" + seq1typename + "' object can't be concatenated");
  };
  
  Sk.abstr.sequenceGetIndexOf = function (seq, ob) {
      var seqtypename;
      var i, it;
      var index;
      if (seq.index) {
          return Sk.misceval.callsimArray(seq.index, [seq, ob]);
      }
      if (Sk.builtin.checkIterable(seq)) {
          index = 0;
          for (it = Sk.abstr.iter(seq), i = it.tp$iternext();
               i !== undefined; i = it.tp$iternext()) {
              if (Sk.misceval.richCompareBool(ob, i, "Eq")) {
                  return new Sk.builtin.int_(index);
              }
              index += 1;
          }
          throw new Sk.builtin.ValueError("sequence.index(x): x not in sequence");
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("argument of type '" + seqtypename + "' is not iterable");
  };
  
  Sk.abstr.sequenceGetCountOf = function (seq, ob) {
      var seqtypename;
      var i, it;
      var count;
      if (seq.count) {
          return Sk.misceval.callsimArray(seq.count, [seq, ob]);
      }
      if (Sk.builtin.checkIterable(seq)) {
          count = 0;
          for (it = Sk.abstr.iter(seq), i = it.tp$iternext();
               i !== undefined; i = it.tp$iternext()) {
              if (Sk.misceval.richCompareBool(ob, i, "Eq")) {
                  count += 1;
              }
          }
          return new Sk.builtin.int_(count);
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("argument of type '" + seqtypename + "' is not iterable");
  };
  
  Sk.abstr.sequenceGetItem = function (seq, i, canSuspend) {
      var seqtypename;
      if (seq.mp$subscript) {
          return seq.mp$subscript(i);
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("'" + seqtypename + "' object is unsubscriptable");
  };
  
  Sk.abstr.sequenceSetItem = function (seq, i, x, canSuspend) {
      var seqtypename;
      if (seq.mp$ass_subscript) {
          return seq.mp$ass_subscript(i, x);
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("'" + seqtypename + "' object does not support item assignment");
  };
  
  Sk.abstr.sequenceDelItem = function (seq, i) {
      var seqtypename;
      if (seq.sq$del_item) {
          i = Sk.abstr.fixSeqIndex_(seq, i);
          seq.sq$del_item(i);
          return;
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("'" + seqtypename + "' object does not support item deletion");
  };
  
  Sk.abstr.sequenceRepeat = function (f, seq, n) {
      var ntypename;
      var count;
      n = Sk.builtin.asnum$(n);
      count = Sk.misceval.asIndex(n);
      if (count === undefined) {
          ntypename = Sk.abstr.typeName(n);
          throw new Sk.builtin.TypeError("can't multiply sequence by non-int of type '" + ntypename + "'");
      }
      return f.call(seq, n);
  };
  
  Sk.abstr.sequenceGetSlice = function (seq, i1, i2) {
      var seqtypename;
      if (seq.sq$slice) {
          i1 = Sk.abstr.fixSeqIndex_(seq, i1);
          i2 = Sk.abstr.fixSeqIndex_(seq, i2);
          return seq.sq$slice(i1, i2);
      } else if (seq.mp$subscript) {
          return seq.mp$subscript(new Sk.builtin.slice(i1, i2));
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("'" + seqtypename + "' object is unsliceable");
  };
  
  Sk.abstr.sequenceDelSlice = function (seq, i1, i2) {
      var seqtypename;
      if (seq.sq$del_slice) {
          i1 = Sk.abstr.fixSeqIndex_(seq, i1);
          i2 = Sk.abstr.fixSeqIndex_(seq, i2);
          seq.sq$del_slice(i1, i2);
          return;
      }
  
      seqtypename = Sk.abstr.typeName(seq);
      throw new Sk.builtin.TypeError("'" + seqtypename + "' doesn't support slice deletion");
  };
  
  Sk.abstr.sequenceSetSlice = function (seq, i1, i2, x) {
      var seqtypename;
      if (seq.sq$ass_slice) {
          i1 = Sk.abstr.fixSeqIndex_(seq, i1);
          i2 = Sk.abstr.fixSeqIndex_(seq, i2);
          seq.sq$ass_slice(i1, i2, x);
      } else if (seq.mp$ass_subscript) {
          seq.mp$ass_subscript(new Sk.builtin.slice(i1, i2), x);
      } else {
          seqtypename = Sk.abstr.typeName(seq);
          throw new Sk.builtin.TypeError("'" + seqtypename + "' object doesn't support slice assignment");
      }
  };
  
  // seq - Python object to unpack
  // n   - JavaScript number of items to unpack
  Sk.abstr.sequenceUnpack = function (seq, n) {
      var res = [];
      var it, i;
  
      if (!Sk.builtin.checkIterable(seq)) {
          throw new Sk.builtin.TypeError("'" + Sk.abstr.typeName(seq) + "' object is not iterable");
      }
  
      for (it = Sk.abstr.iter(seq), i = it.tp$iternext();
           (i !== undefined) && (res.length < n);
           i = it.tp$iternext()) {
          res.push(i);
      }
  
      if (res.length < n) {
          throw new Sk.builtin.ValueError("need more than " + res.length + " values to unpack");
      }
      if (i !== undefined) {
          throw new Sk.builtin.ValueError("too many values to unpack");
      }
  
      // Return Javascript array of items
      return res;
  };
  
  //
  // Object
  //
  
  Sk.abstr.objectFormat = function (obj, format_spec) {
      var meth; // PyObject
      var result; // PyObject
  
      // Find the (unbound!) __format__ method (a borrowed reference)
      meth = Sk.abstr.lookupSpecial(obj, Sk.builtin.str.$format);
      if (meth == null) {
          throw new Sk.builtin.TypeError("Type " + Sk.abstr.typeName(obj) + " doesn't define __format__");
      }
  
      // And call it
      result = Sk.misceval.callsimArray(meth, [obj, format_spec]);
      if (!Sk.builtin.checkString(result)) {
          throw new Sk.builtin.TypeError("__format__ must return a str, not " + Sk.abstr.typeName(result));
      }
  
      return result;
  };
  
  Sk.abstr.objectAdd = function (a, b) {
      var btypename;
      var atypename;
      if (a.nb$add) {
          return a.nb$add(b);
      }
  
      atypename = Sk.abstr.typeName(a);
      btypename = Sk.abstr.typeName(b);
      throw new Sk.builtin.TypeError("unsupported operand type(s) for +: '" + atypename + "' and '" + btypename + "'");
  };
  
  // in Python 2.6, this behaviour seems to be defined for numbers and bools (converts bool to int)
  Sk.abstr.objectNegative = function (obj) {
      var objtypename;
      var obj_asnum = Sk.builtin.asnum$(obj); // this will also convert bool type to int
  
      if (obj instanceof Sk.builtin.bool) {
          obj = new Sk.builtin.int_(obj_asnum);
      }
  
      if (obj.nb$negative) {
          return obj.nb$negative();
      }
  
      objtypename = Sk.abstr.typeName(obj);
      throw new Sk.builtin.TypeError("bad operand type for unary -: '" + objtypename + "'");
  };
  
  // in Python 2.6, this behaviour seems to be defined for numbers and bools (converts bool to int)
  Sk.abstr.objectPositive = function (obj) {
      var objtypename = Sk.abstr.typeName(obj);
      var obj_asnum = Sk.builtin.asnum$(obj); // this will also convert bool type to int
  
      if (obj instanceof Sk.builtin.bool) {
          obj = new Sk.builtin.int_(obj_asnum);
      }
  
      if (obj.nb$negative) {
          return obj.nb$positive();
      }
  
      throw new Sk.builtin.TypeError("bad operand type for unary +: '" + objtypename + "'");
  };
  
  Sk.abstr.objectDelItem = function (o, key) {
      var otypename;
      var keytypename;
      var keyValue;
      if (o !== null) {
          if (o.mp$del_subscript) {
              o.mp$del_subscript(key);
              return;
          }
          if (o.sq$ass_item) {
              keyValue = Sk.misceval.asIndex(key);
              if (keyValue === undefined) {
                  keytypename = Sk.abstr.typeName(key);
                  throw new Sk.builtin.TypeError("sequence index must be integer, not '" + keytypename + "'");
              }
              Sk.abstr.sequenceDelItem(o, keyValue);
              return;
          }
          // if o is a slice do something else...
      }
  
      otypename = Sk.abstr.typeName(o);
      throw new Sk.builtin.TypeError("'" + otypename + "' object does not support item deletion");
  };
  Sk.exportSymbol("Sk.abstr.objectDelItem", Sk.abstr.objectDelItem);
  
  Sk.abstr.objectGetItem = function (o, key, canSuspend) {
      var otypename;
      if (o !== null) {
          if (o.tp$getitem) {
              return o.tp$getitem(key, canSuspend);
          } else if (o.mp$subscript) {
              return o.mp$subscript(key, canSuspend);
          } else if (Sk.misceval.isIndex(key) && o.sq$item) {
              return Sk.abstr.sequenceGetItem(o, Sk.misceval.asIndex(key), canSuspend);
          }
      }
  
      otypename = Sk.abstr.typeName(o);
      throw new Sk.builtin.TypeError("'" + otypename + "' does not support indexing");
  };
  Sk.exportSymbol("Sk.abstr.objectGetItem", Sk.abstr.objectGetItem);
  
  Sk.abstr.objectSetItem = function (o, key, v, canSuspend) {
      var otypename;
      if (o !== null) {
          if (o.tp$setitem) {
              return o.tp$setitem(key, v, canSuspend);
          } else if (o.mp$ass_subscript) {
              return o.mp$ass_subscript(key, v, canSuspend);
          } else if (Sk.misceval.isIndex(key) && o.sq$ass_item) {
              return Sk.abstr.sequenceSetItem(o, Sk.misceval.asIndex(key), v, canSuspend);
          }
      }
  
      otypename = Sk.abstr.typeName(o);
      throw new Sk.builtin.TypeError("'" + otypename + "' does not support item assignment");
  };
  Sk.exportSymbol("Sk.abstr.objectSetItem", Sk.abstr.objectSetItem);
  
  
  Sk.abstr.gattr = function (obj, pyName, canSuspend) {
      var ret, f;
      var objname = Sk.abstr.typeName(obj);
      var jsName = pyName.$jsstr();
  
      if (obj === null) {
          throw new Sk.builtin.AttributeError("'" + objname + "' object has no attribute '" + jsName + "'");
      }
  
      if (obj.tp$getattr !== undefined) {
          ret = obj.tp$getattr(pyName, canSuspend);
      }
  
      ret = Sk.misceval.chain(ret, function(r) {
          if (r === undefined) {
              throw new Sk.builtin.AttributeError("'" + objname + "' object has no attribute '" + jsName + "'");
          }
          return r;
      });
  
      return canSuspend ? ret : Sk.misceval.retryOptionalSuspensionOrThrow(ret);
  };
  Sk.exportSymbol("Sk.abstr.gattr", Sk.abstr.gattr);
  
  
  Sk.abstr.sattr = function (obj, pyName, data, canSuspend) {
      var objname = Sk.abstr.typeName(obj), r, setf;
      var jsName = pyName.$jsstr();
  
      if (obj === null) {
          throw new Sk.builtin.AttributeError("'" + objname + "' object has no attribute '" + jsName + "'");
      }
  
      if (obj.tp$setattr !== undefined) {
          return obj.tp$setattr(pyName, data, canSuspend);
      } else {
          throw new Sk.builtin.AttributeError("'" + objname + "' object has no attribute '" + jsName + "'");
      }
  };
  Sk.exportSymbol("Sk.abstr.sattr", Sk.abstr.sattr);
  
  
  Sk.abstr.iternext = function (it, canSuspend) {
      return it.tp$iternext(canSuspend);
  };
  Sk.exportSymbol("Sk.abstr.iternext", Sk.abstr.iternext);
  
  
  /**
   * Get the iterator for a Python object  This iterator could be one of the following.
   * This is the preferred mechanism for consistently getting the correct iterator.  You should
   * not just use tp$iter because that could lead to incorrect behavior of a user created class.
   *
   * - tp$iter
   * - A user defined `__iter__` method
   * - A user defined `__getitem__` method
   *
   * @param obj
   *
   * @throws {Sk.builtin.TypeError}
   * @returns {Object}
   */
  
  Sk.abstr.iter = function(obj) {
      var iter;
      var getit;
      var ret;
  
      /**
       * Builds an iterator around classes that have a __getitem__ method.
       *
       * @constructor
       */
      var seqIter = function (obj) {
          this.idx = 0;
          this.myobj = obj;
          this.getitem = Sk.abstr.lookupSpecial(obj, Sk.builtin.str.$getitem);
          this.tp$iternext = function () {
              var ret;
              try {
                  ret = Sk.misceval.callsimArray(this.getitem, [this.myobj, Sk.ffi.remapToPy(this.idx)]);
              } catch (e) {
                  if (e instanceof Sk.builtin.IndexError || e instanceof Sk.builtin.StopIteration) {
                      return undefined;
                  } else {
                      throw e;
                  }
              }
              this.idx++;
              return ret;
          };
      };
  
      if (obj.tp$getattr) {
          iter =  Sk.abstr.lookupSpecial(obj, Sk.builtin.str.$iter);
          if (iter) {
              ret = Sk.misceval.callsimArray(iter, [obj]);
              if (ret.tp$iternext) {
                  return ret;
              }
          }
      }
      if (obj.tp$iter) {
          try {  // catch and ignore not iterable error here.
              ret = obj.tp$iter();
              if (ret.tp$iternext) {
                  return ret;
              }
          } catch (e) { }
      }
      getit = Sk.abstr.lookupSpecial(obj, Sk.builtin.str.$getitem);
      if (getit) {
          // create internal iterobject if __getitem__
          return new seqIter(obj);
      }
      throw new Sk.builtin.TypeError("'" + Sk.abstr.typeName(obj) + "' object is not iterable");
  };
  Sk.exportSymbol("Sk.abstr.iter", Sk.abstr.iter);
  
  /**
   * Special method look up. First try getting the method via
   * internal dict and getattr. If getattr is not present (builtins)
   * try if method is defined on the object itself
   *
   * @returns {null|Object} Return null if not found or the function
   */
  Sk.abstr.lookupSpecial = function(op, pyName) {
      var res;
      var obtp;
      if (op.ob$type) {
          obtp = op.ob$type;
      } else {
          return null;
      }
  
      return Sk.builtin.type.typeLookup(obtp, pyName);
  };
  Sk.exportSymbol("Sk.abstr.lookupSpecial", Sk.abstr.lookupSpecial);
  
  /**
   * Mark a class as unhashable and prevent its `__hash__` function from being called.
   * @param  {*} thisClass The class to mark as unhashable.
   * @return {undefined}
   */
  Sk.abstr.markUnhashable = function (thisClass) {
      var proto = thisClass.prototype;
      proto.__hash__ = Sk.builtin.none.none$;
      proto.tp$hash = Sk.builtin.none.none$;
  };
  
  /**
   * Code taken from goog.inherits
   *
   * Newer versions of the closure library add a "base"attribute,
   * which we don't want/need.  So, this code is the remainder of
   * the goog.inherits function.
   */
  Sk.abstr.inherits = function (childCtor, parentCtor) {
      /** @constructor */
      function tempCtor() {}
      tempCtor.prototype = parentCtor.prototype;
      childCtor.superClass_ = parentCtor.prototype;
      childCtor.prototype = new tempCtor();
      /** @override */
      childCtor.prototype.constructor = childCtor;
  };
  
  /**
   * Set up inheritance between two Python classes. This allows only for single
   * inheritance -- multiple inheritance is not supported by Javascript.
   *
   * Javascript's inheritance is prototypal. This means that properties must
   * be defined on the superclass' prototype in order for subclasses to inherit
   * them.
   *
   * ```
   * Sk.superclass.myProperty                 # will NOT be inherited
   * Sk.superclass.prototype.myProperty       # will be inherited
   * ```
   *
   * In order for a class to be subclassable, it must (directly or indirectly)
   * inherit from Sk.builtin.object so that it will be properly initialized in
   * {@link Sk.doOneTimeInitialization} (in src/import.js). Further, all Python
   * builtins should inherit from Sk.builtin.object.
   *
   * @param {string} childName The Python name of the child (subclass).
   * @param {*} child     The subclass.
   * @param {*} parent    The superclass.
   * @return {undefined}
   */
  Sk.abstr.setUpInheritance = function (childName, child, parent) {
      Sk.abstr.inherits(child, parent);
      child.prototype.tp$base = parent;
      child.prototype.tp$name = childName;
      child.prototype.ob$type = Sk.builtin.type.makeIntoTypeObj(childName, child);
  };
  
  /**
   * Call the super constructor of the provided class, with the object `self` as
   * the `this` value of that constructor. Any arguments passed to this function
   * after `self` will be passed as-is to the constructor.
   *
   * @param  {*} thisClass The subclass.
   * @param  {Object} self      The instance of the subclas.
   * @param  {...?} args Arguments to pass to the constructor.
   * @return {undefined}
   */
  Sk.abstr.superConstructor = function (thisClass, self, args) {
      var argumentsForConstructor = Array.prototype.slice.call(arguments, 2);
      thisClass.prototype.tp$base.apply(self, argumentsForConstructor);
  };
  