sku-pg
Version:
Skulpt is a Javascript implementation of Python 2.x. Python that runs in your browser!
689 lines (610 loc) • 25 kB
JavaScript
if(Sk.builtin === undefined) {
Sk.builtin = {};
}
/**
* Maps Python dunder names to the Skulpt Javascript function names that
* implement them.
*
* Note: __add__, __mul__, and __rmul__ can be used for either numeric or
* sequence types. Here, they default to the numeric versions (i.e. nb$add,
* nb$multiply, and nb$reflected_multiply). This works because Sk.abstr.binary_op_
* checks for the numeric shortcuts and not the sequence shortcuts when computing
* a binary operation.
*
* Because many of these functions are used in contexts in which Skulpt does not
* [yet] handle suspensions, the assumption is that they must not suspend. However,
* some of these built-in functions are acquiring 'canSuspend' arguments to signal
* where this is not the case. These need to be spliced out of the argument list before
* it is passed to python. Array values in this map contain [dunderName, argumentIdx],
* where argumentIdx specifies the index of the 'canSuspend' boolean argument.
*
* @type {Object}
*/
Sk.dunderToSkulpt = {
"__eq__": "ob$eq",
"__ne__": "ob$ne",
"__lt__": "ob$lt",
"__le__": "ob$le",
"__gt__": "ob$gt",
"__ge__": "ob$ge",
"__hash__": "tp$hash",
"__abs__": "nb$abs",
"__neg__": "nb$negative",
"__pos__": "nb$positive",
"__int__": "nb$int_",
"__long__": "nb$lng",
"__float__": "nb$float_",
"__add__": "nb$add",
"__radd__": "nb$reflected_add",
"__sub__": "nb$subtract",
"__rsub__": "nb$reflected_subtract",
"__mul__": "nb$multiply",
"__rmul__": "nb$reflected_multiply",
"__div__": "nb$divide",
"__rdiv__": "nb$reflected_divide",
"__floordiv__": "nb$floor_divide",
"__rfloordiv__": "nb$reflected_floor_divide",
"__mod__": "nb$remainder",
"__rmod__": "nb$reflected_remainder",
"__divmod__": "nb$divmod",
"__rdivmod__": "nb$reflected_divmod",
"__pow__": "nb$power",
"__rpow__": "nb$reflected_power",
"__contains__": "sq$contains",
"__len__": ["sq$length", 1],
"__get__": ["tp$descr_get", 3],
"__set__": ["tp$descr_set", 3]
};
/**
*
* @constructor
*
* @param {*} name name or object to get type of, if only one arg
*
* @param {Sk.builtin.tuple=} bases
*
* @param {Object=} dict
*
*
* This type represents the type of `type'. *Calling* an instance of
* this builtin type named "type" creates class objects. The resulting
* class objects will have various tp$xyz attributes on them that allow
* for the various operations on that object.
*
* calling the type or calling an instance of the type? or both?
*/
Sk.builtin.type = function (name, bases, dict) {
var mro;
var obj;
var klass;
var v;
if (bases === undefined && dict === undefined) {
// 1 arg version of type()
// the argument is an object, not a name and returns a type object
obj = name;
return obj.ob$type;
} else {
// argument dict must be of type dict
if(dict.tp$name !== "dict") {
throw new Sk.builtin.TypeError("type() argument 3 must be dict, not " + Sk.abstr.typeName(dict));
}
// checks if name must be string
if(!Sk.builtin.checkString(name)) {
throw new Sk.builtin.TypeError("type() argument 1 must be str, not " + Sk.abstr.typeName(name));
}
// argument bases must be of type tuple
if(bases.tp$name !== "tuple") {
throw new Sk.builtin.TypeError("type() argument 2 must be tuple, not " + Sk.abstr.typeName(bases));
}
// type building version of type
// dict is the result of running the classes code object
// (basically the dict of functions). those become the prototype
// object of the class).
/**
* The constructor is a stub, that gets called from object.__new__
* @constructor
*/
klass = function (args, kws) {
var args_copy;
// Call up through the chain in case there's a built-in object
// whose constructor we need to initialise
if (klass.prototype.tp$base !== undefined) {
if (klass.prototype.tp$base.sk$klass) {
klass.prototype.tp$base.call(this, args, kws);
} else {
// Call super constructor if subclass of a builtin
args_copy = args.slice();
args_copy.unshift(klass, this);
Sk.abstr.superConstructor.apply(undefined, args_copy);
}
}
this["$d"] = new Sk.builtin.dict([]);
this["$d"].mp$ass_subscript(new Sk.builtin.str("__dict__"), this["$d"]);
};
var _name = Sk.ffi.remapToJs(name); // unwrap name string to js for latter use
var inheritsBuiltin = false;
// Invoking the class object calls __new__() to generate a new instance,
// then __init__() to initialise it
klass.tp$call = function(args, kws) {
var newf = Sk.builtin.type.typeLookup(klass, "__new__"), newargs;
var self;
args = args || [];
kws = kws || [];
if (newf === undefined || newf === Sk.builtin.object.prototype["__new__"]) {
// No override -> just call the constructor
self = new klass(args, kws);
newf = undefined;
} else {
newargs = args.slice();
newargs.unshift(klass);
self = Sk.misceval.applyOrSuspend(newf, undefined, undefined, kws, newargs);
}
return Sk.misceval.chain(self, function(s) {
var init = Sk.builtin.type.typeLookup(s.ob$type, "__init__");
self = s; // in case __new__ suspended
if (init !== undefined) {
args.unshift(self);
return Sk.misceval.applyOrSuspend(init, undefined, undefined, kws, args);
} else if (newf === undefined && (args.length !== 0 || kws.length !== 0) && !inheritsBuiltin) {
// We complain about spurious constructor arguments if neither __new__
// nor __init__ were overridden
throw new Sk.builtin.TypeError("__init__() got unexpected argument(s)");
}
}, function(r) {
if (r !== Sk.builtin.none.none$ && r !== undefined) {
throw new Sk.builtin.TypeError("__init__() should return None, not " + Sk.abstr.typeName(r));
} else {
return self;
}
});
};
if (bases.v.length === 0 && Sk.python3) {
// new style class, inherits from object by default
Sk.abstr.setUpInheritance(_name, klass, Sk.builtin.object);
}
var parent, it, firstAncestor, builtin_bases = [];
// Set up inheritance from any builtins
for (it = bases.tp$iter(), parent = it.tp$iternext(); parent !== undefined; parent = it.tp$iternext()) {
if (firstAncestor === undefined) {
firstAncestor = parent;
}
while (parent.sk$klass && parent.prototype.tp$base) {
parent = parent.prototype.tp$base;
}
if (!parent.sk$klass && builtin_bases.indexOf(parent) < 0) {
builtin_bases.push(parent);
inheritsBuiltin = true;
}
}
if (builtin_bases.length > 1) {
throw new Sk.builtin.TypeError("Multiple inheritance with more than one builtin type is unsupported");
}
// Javascript does not support multiple inheritance, so only the first
// base (if any) will directly inherit in Javascript
if (firstAncestor !== undefined) {
goog.inherits(klass, firstAncestor);
if (firstAncestor.prototype instanceof Sk.builtin.object || firstAncestor === Sk.builtin.object) {
klass.prototype.tp$base = firstAncestor;
}
}
klass.prototype.tp$name = _name;
klass.prototype.ob$type = Sk.builtin.type.makeIntoTypeObj(_name, klass);
// set __module__ if not present (required by direct type(name, bases, dict) calls)
var module_lk = new Sk.builtin.str("__module__");
if(dict.mp$lookup(module_lk) === undefined) {
dict.mp$ass_subscript(module_lk, Sk.globals["__name__"]);
}
// copy properties into our klass object
// uses python iter methods
var k;
for (it = dict.tp$iter(), k = it.tp$iternext(); k !== undefined; k = it.tp$iternext()) {
v = dict.mp$subscript(k);
if (v === undefined) {
v = null;
}
klass.prototype[k.v] = v;
klass[k.v] = v;
}
klass["__class__"] = klass;
klass["__name__"] = name;
klass.sk$klass = true;
klass.prototype["$r"] = function () {
var cname;
var mod;
var reprf = this.tp$getattr("__repr__");
if (reprf !== undefined && reprf.im_func !== Sk.builtin.object.prototype["__repr__"]) {
return Sk.misceval.apply(reprf, undefined, undefined, undefined, []);
}
if ((klass.prototype.tp$base !== undefined) &&
(klass.prototype.tp$base !== Sk.builtin.object) &&
(klass.prototype.tp$base.prototype["$r"] !== undefined)) {
// If subclass of a builtin which is not object, use that class' repr
return klass.prototype.tp$base.prototype["$r"].call(this);
} else {
// Else, use default repr for a user-defined class instance
mod = dict.mp$subscript(module_lk); // lookup __module__
cname = "";
if (mod) {
cname = mod.v + ".";
}
return new Sk.builtin.str("<" + cname + _name + " object>");
}
};
klass.prototype.tp$setattr = function(name, data, canSuspend) {
var r, /** @type {(Object|undefined)} */ setf = Sk.builtin.object.prototype.GenericGetAttr.call(this, "__setattr__");
if (setf !== undefined) {
r = Sk.misceval.callsimOrSuspend(/** @type {Object} */ (setf), new Sk.builtin.str(name), data);
return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
}
return Sk.builtin.object.prototype.GenericSetAttr.call(this, name, data);
};
klass.prototype.tp$getattr = function(name, canSuspend) {
var r, /** @type {(Object|undefined)} */ getf;
// Convert AttributeErrors back into 'undefined' returns to match the tp$getattr
// convention
var callCatchUndefined = function() {
return Sk.misceval.tryCatch(function() {
return Sk.misceval.callsimOrSuspend(/** @type {Object} */ (getf), new Sk.builtin.str(name));
}, function (e) {
if (e instanceof Sk.builtin.AttributeError) {
return undefined;
} else {
throw e;
}
});
};
getf = Sk.builtin.object.prototype.GenericGetAttr.call(this, "__getattribute__");
if (getf !== undefined) {
r = callCatchUndefined();
} else {
r = Sk.builtin.object.prototype.GenericGetAttr.call(this, name);
if (r === undefined) {
getf = Sk.builtin.object.prototype.GenericGetAttr.call(this, "__getattr__");
if (getf !== undefined) {
r = callCatchUndefined();
}
}
}
return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
};
klass.prototype.tp$str = function () {
var strf = this.tp$getattr("__str__");
if (strf !== undefined && strf.im_func !== Sk.builtin.object.prototype["__str__"]) {
return Sk.misceval.apply(strf, undefined, undefined, undefined, []);
}
if ((klass.prototype.tp$base !== undefined) &&
(klass.prototype.tp$base !== Sk.builtin.object) &&
(klass.prototype.tp$base.prototype.tp$str !== undefined)) {
// If subclass of a builtin which is not object, use that class' repr
return klass.prototype.tp$base.prototype.tp$str.call(this);
}
return this["$r"]();
};
klass.prototype.tp$length = function (canSuspend) {
var r = Sk.misceval.chain(Sk.abstr.gattr(this, "__len__", canSuspend), function(lenf) {
return Sk.misceval.applyOrSuspend(lenf, undefined, undefined, undefined, []);
});
return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
};
klass.prototype.tp$call = function (args, kw) {
return Sk.misceval.chain(Sk.abstr.gattr(this, "__call__", true), function(callf) {
return Sk.misceval.applyOrSuspend(callf, undefined, undefined, kw, args);
});
};
klass.prototype.tp$iter = function () {
var iterf = Sk.abstr.gattr(this, "__iter__", false);
return Sk.misceval.callsim(iterf);
};
klass.prototype.tp$iternext = function (canSuspend) {
var self = this;
var r = Sk.misceval.chain(
Sk.misceval.tryCatch(function() {
return Sk.abstr.gattr(self, "next", canSuspend);
}, function(e) {
if (e instanceof Sk.builtin.AttributeError) {
throw new Sk.builtin.TypeError("'" + Sk.abstr.typeName(self) + "' object is not iterable");
} else {
throw e;
}
}),
function(/** {Object} */ iternextf) {
return Sk.misceval.tryCatch(function() {
return Sk.misceval.callsimOrSuspend(iternextf);
}, function(e) {
if (e instanceof Sk.builtin.StopIteration) {
return undefined;
} else {
throw e;
}
});
});
return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
};
klass.prototype.tp$getitem = function (key, canSuspend) {
var getf = Sk.abstr.gattr(this, "__getitem__", canSuspend), r;
if (getf !== undefined) {
r = Sk.misceval.applyOrSuspend(getf, undefined, undefined, undefined, [key]);
return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
}
throw new Sk.builtin.TypeError("'" + Sk.abstr.typeName(this) + "' object does not support indexing");
};
klass.prototype.tp$setitem = function (key, value, canSuspend) {
var setf = Sk.abstr.gattr(this, "__setitem__", canSuspend), r;
if (setf !== undefined) {
r = Sk.misceval.applyOrSuspend(setf, undefined, undefined, undefined, [key, value]);
return canSuspend ? r : Sk.misceval.retryOptionalSuspensionOrThrow(r);
}
throw new Sk.builtin.TypeError("'" + Sk.abstr.typeName(this) + "' object does not support item assignment");
};
if (bases) {
//print("building mro for", name);
//for (var i = 0; i < bases.length; ++i)
//print("base[" + i + "]=" + bases[i].tp$name);
klass["$d"] = new Sk.builtin.dict([]);
klass["$d"].mp$ass_subscript(Sk.builtin.type.basesStr_, bases);
mro = Sk.builtin.type.buildMRO(klass);
klass["$d"].mp$ass_subscript(Sk.builtin.type.mroStr_, mro);
klass.tp$mro = mro;
//print("mro result", Sk.builtin.repr(mro).v);
}
// fix for class attributes
klass.tp$setattr = Sk.builtin.type.prototype.tp$setattr;
var shortcutDunder = function (skulpt_name, magic_name, magic_func, canSuspendIdx) {
klass.prototype[skulpt_name] = function () {
var args = Array.prototype.slice.call(arguments), canSuspend;
args.unshift(magic_func, this);
if (canSuspendIdx !== null) {
canSuspend = args[canSuspendIdx+1];
args.splice(canSuspendIdx+1, 1);
if (canSuspend) {
return Sk.misceval.callsimOrSuspend.apply(undefined, args);
}
}
return Sk.misceval.callsim.apply(undefined, args);
};
};
// Register skulpt shortcuts to magic methods defined by this class.
// Dynamically deflined methods (eg those returned by __getattr__())
// cannot be used by these magic functions; this is consistent with
// how CPython handles "new-style" classes:
// https://docs.python.org/2/reference/datamodel.html#special-method-lookup-for-old-style-classes
var dunder, skulpt_name, canSuspendIdx;
for (dunder in Sk.dunderToSkulpt) {
skulpt_name = Sk.dunderToSkulpt[dunder];
if (typeof(skulpt_name) === "string") {
canSuspendIdx = null;
} else {
canSuspendIdx = skulpt_name[1];
skulpt_name = skulpt_name[0];
}
if (klass[dunder]) {
// scope workaround
shortcutDunder(skulpt_name, dunder, klass[dunder], canSuspendIdx);
}
}
return klass;
}
};
/**
*
*/
Sk.builtin.type.makeTypeObj = function (name, newedInstanceOfType) {
Sk.builtin.type.makeIntoTypeObj(name, newedInstanceOfType);
return newedInstanceOfType;
};
Sk.builtin.type.makeIntoTypeObj = function (name, t) {
goog.asserts.assert(name !== undefined);
goog.asserts.assert(t !== undefined);
t.ob$type = Sk.builtin.type;
t.tp$name = name;
t["$r"] = function () {
var ctype;
var mod = t.__module__;
var cname = "";
if (mod) {
cname = mod.v + ".";
}
ctype = "class";
if (!mod && !t.sk$klass && !Sk.python3) {
ctype = "type";
}
return new Sk.builtin.str("<" + ctype + " '" + cname + t.tp$name + "'>");
};
t.tp$str = undefined;
t.tp$getattr = Sk.builtin.type.prototype.tp$getattr;
t.tp$setattr = Sk.builtin.object.prototype.GenericSetAttr;
t.tp$richcompare = Sk.builtin.type.prototype.tp$richcompare;
t.sk$type = true;
return t;
};
Sk.builtin.type.ob$type = Sk.builtin.type;
Sk.builtin.type.tp$name = "type";
Sk.builtin.type["$r"] = function () {
if(Sk.python3) {
return new Sk.builtin.str("<class 'type'>");
} else {
return new Sk.builtin.str("<type 'type'>");
}
};
//Sk.builtin.type.prototype.tp$descr_get = function() { print("in type descr_get"); };
//Sk.builtin.type.prototype.tp$name = "type";
// basically the same as GenericGetAttr except looks in the proto instead
Sk.builtin.type.prototype.tp$getattr = function (name, canSuspend) {
var res;
var tp = this;
var descr;
var f;
if (this["$d"]) {
res = this["$d"].mp$lookup(new Sk.builtin.str(name));
if (res !== undefined) {
return res;
}
}
descr = Sk.builtin.type.typeLookup(tp, name);
//print("type.tpgetattr descr", descr, descr.tp$name, descr.func_code, name);
if (descr !== undefined && descr !== null && descr.ob$type !== undefined) {
f = descr.tp$descr_get;
// todo;if (f && descr.tp$descr_set) // is a data descriptor if it has a set
// return f.call(descr, this, this.ob$type);
}
if (f) {
// non-data descriptor
return f.call(descr, Sk.builtin.none.none$, tp, canSuspend);
}
if (descr !== undefined) {
return descr;
}
return undefined;
};
Sk.builtin.type.prototype.tp$setattr = function (name, value) {
// class attributes are direct properties of the object
this[name] = value;
};
Sk.builtin.type.typeLookup = function (type, name) {
var mro = type.tp$mro;
var pyname = new Sk.builtin.str(name);
var base;
var res;
var i;
// todo; probably should fix this, used for builtin types to get stuff
// from prototype
if (!mro) {
if (type.prototype) {
return type.prototype[name];
}
return undefined;
}
for (i = 0; i < mro.v.length; ++i) {
base = mro.v[i];
if (base.hasOwnProperty(name)) {
return base[name];
}
res = base["$d"].mp$lookup(pyname);
if (res !== undefined) {
return res;
}
if (base.prototype && base.prototype[name] !== undefined) {
return base.prototype[name];
}
}
return undefined;
};
Sk.builtin.type.mroMerge_ = function (seqs) {
/*
var tmp = [];
for (var i = 0; i < seqs.length; ++i)
{
tmp.push(new Sk.builtin.list(seqs[i]));
}
print(Sk.builtin.repr(new Sk.builtin.list(tmp)).v);
*/
var seq;
var i;
var next;
var k;
var sseq;
var j;
var cand;
var cands;
var res = [];
for (; ;) {
for (i = 0; i < seqs.length; ++i) {
seq = seqs[i];
if (seq.length !== 0) {
break;
}
}
if (i === seqs.length) { // all empty
return res;
}
cands = [];
for (i = 0; i < seqs.length; ++i) {
seq = seqs[i];
//print("XXX", Sk.builtin.repr(new Sk.builtin.list(seq)).v);
if (seq.length !== 0) {
cand = seq[0];
//print("CAND", Sk.builtin.repr(cand).v);
OUTER:
for (j = 0; j < seqs.length; ++j) {
sseq = seqs[j];
for (k = 1; k < sseq.length; ++k) {
if (sseq[k] === cand) {
break OUTER;
}
}
}
// cand is not in any sequences' tail -> constraint-free
if (j === seqs.length) {
cands.push(cand);
}
}
}
if (cands.length === 0) {
throw new Sk.builtin.TypeError("Inconsistent precedences in type hierarchy");
}
next = cands[0];
// append next to result and remove from sequences
res.push(next);
for (i = 0; i < seqs.length; ++i) {
seq = seqs[i];
if (seq.length > 0 && seq[0] === next) {
seq.splice(0, 1);
}
}
}
};
Sk.builtin.type.buildMRO_ = function (klass) {
// MERGE(klass + mro(bases) + bases)
var i;
var bases;
var all = [
[klass]
];
//Sk.debugout("buildMRO for", klass.tp$name);
var kbases = klass["$d"].mp$subscript(Sk.builtin.type.basesStr_);
for (i = 0; i < kbases.v.length; ++i) {
all.push(Sk.builtin.type.buildMRO_(kbases.v[i]));
}
bases = [];
for (i = 0; i < kbases.v.length; ++i) {
bases.push(kbases.v[i]);
}
all.push(bases);
return Sk.builtin.type.mroMerge_(all);
};
/*
* C3 MRO (aka CPL) linearization. Figures out which order to search through
* base classes to determine what should override what. C3 does the "right
* thing", and it's what Python has used since 2.3.
*
* Kind of complicated to explain, but not really that complicated in
* implementation. Explanations:
*
* http://people.csail.mit.edu/jrb/goo/manual.43/goomanual_55.html
* http://www.python.org/download/releases/2.3/mro/
* http://192.220.96.201/dylan/linearization-oopsla96.html
*
* This implementation is based on a post by Samuele Pedroni on python-dev
* (http://mail.python.org/pipermail/python-dev/2002-October/029176.html) when
* discussing its addition to Python.
*/
Sk.builtin.type.buildMRO = function (klass) {
return new Sk.builtin.tuple(Sk.builtin.type.buildMRO_(klass));
};
Sk.builtin.type.prototype.tp$richcompare = function (other, op) {
var r2;
var r1;
if (other.ob$type != Sk.builtin.type) {
return undefined;
}
if (!this["$r"] || !other["$r"]) {
return undefined;
}
r1 = new Sk.builtin.str(this["$r"]().v.slice(1,6));
r2 = new Sk.builtin.str(other["$r"]().v.slice(1,6));
if (this["$r"]().v.slice(1,6) !== "class") {
r1 = this["$r"]();
r2 = other["$r"]();
}
return r1.tp$richcompare(r2, op);
};