sku-pg
Version:
Skulpt is a Javascript implementation of Python 2.x. Python that runs in your browser!
2,350 lines • 74.7 kB
JavaScript
/** @param {...*} x */
var out;
Sk.gensymcount = 0;
/**
* @constructor
* @param {string} filename
* @param {SymbolTable} st
* @param {number} flags
* @param {boolean=} canSuspend whether compiled code can suspend
* @param {string=} sourceCodeForAnnotation used to add original source to listing if desired
*/
function Compiler (filename, st, flags, canSuspend, sourceCodeForAnnotation) {
this.filename = filename;
this.st = st;
this.flags = flags;
this.canSuspend = canSuspend;
this.interactive = false;
this.nestlevel = 0;
this.u = null;
this.stack = [];
this.result = [];
// this.gensymcount = 0;
this.allUnits = [];
this.source = sourceCodeForAnnotation ? sourceCodeForAnnotation.split("\n") : false;
}
/**
* @constructor
*
* Stuff that changes on entry/exit of code blocks. must be saved and restored
* when returning to a block.
*
* Corresponds to the body of a module, class, or function.
*/
function CompilerUnit () {
this.ste = null;
this.name = null;
this.canSuspend = false;
this.doesSuspend = false;
this.private_ = null;
this.firstlineno = 0;
this.lineno = 0;
this.linenoSet = false;
this.localnames = [];
this.localtemps = [];
this.tempsToSave = [];
this.blocknum = 0;
this.blocks = [];
this.curblock = 0;
this.scopename = null;
this.prefixCode = "";
this.varDeclsCode = "";
this.switchCode = "";
this.suffixCode = "";
// stack of where to go on a break
this.breakBlocks = [];
// stack of where to go on a continue
this.continueBlocks = [];
this.exceptBlocks = [];
this.finallyBlocks = [];
}
CompilerUnit.prototype.activateScope = function () {
var self = this;
out = function () {
var i;
var b = self.blocks[self.curblock];
if (b._next === null) {
for (i = 0; i < arguments.length; ++i) {
b.push(arguments[i]);
}
}
// TODO: Warn about unreachable code after an unconditional jump?
};
};
Compiler.prototype.getSourceLine = function (lineno) {
goog.asserts.assert(this.source);
return this.source[lineno - 1];
};
Compiler.prototype.annotateSource = function (ast) {
var i;
var col_offset;
var lineno;
if (this.source) {
lineno = ast.lineno;
col_offset = ast.col_offset;
out("\n//\n// line ", lineno, ":\n// ", this.getSourceLine(lineno), "\n// ");
for (i = 0; i < col_offset; ++i) {
out(" ");
}
out("^\n//\n");
out("$currLineNo = ", lineno, ";\n$currColNo = ", col_offset, ";\n\n");
}
};
Compiler.prototype.gensym = function (hint) {
hint = hint || "";
hint = "$" + hint;
hint += Sk.gensymcount++;
return hint;
};
Compiler.prototype.niceName = function (roughName) {
return this.gensym(roughName.replace("<", "").replace(">", "").replace(" ", "_"));
};
var reservedWords_ = {
"abstract": true,
"as": true,
"boolean": true,
"break": true,
"byte": true,
"case": true,
"catch": true,
"char": true,
"class": true,
"continue": true,
"const": true,
"debugger": true,
"default": true,
"delete": true,
"do": true,
"double": true,
"else": true,
"enum": true,
"export": true,
"extends": true,
"false": true,
"final": true,
"finally": true,
"float": true,
"for": true,
"function": true,
"goto": true,
"if": true,
"implements": true,
"import": true,
"in": true,
"instanceof": true,
"int": true,
"interface": true,
"is": true,
"long": true,
"namespace": true,
"native": true,
"new": true,
"null": true,
"package": true,
"private": true,
"protected": true,
"public": true,
"return": true,
"short": true,
"static": true,
"super": false,
"switch": true,
"synchronized": true,
"this": true,
"throw": true,
"throws": true,
"transient": true,
"true": true,
"try": true,
"typeof": true,
"use": true,
"var": true,
"void": true,
"volatile": true,
"while": true,
"with": true
};
function fixReservedWords (name) {
if (reservedWords_[name] !== true) {
return name;
}
return name + "_$rw$";
}
var reservedNames_ = {
"__defineGetter__": true,
"__defineSetter__": true,
"apply": true,
"call": true,
"eval": true,
"hasOwnProperty": true,
"isPrototypeOf": true,
"__lookupGetter__": true,
"__lookupSetter__": true,
"__noSuchMethod__": true,
"propertyIsEnumerable": true,
"toSource": true,
"toLocaleString": true,
"toString": true,
"unwatch": true,
"valueOf": true,
"watch": true,
"length": true
};
function fixReservedNames (name) {
if (reservedNames_[name]) {
return name + "_$rn$";
}
return name;
}
function mangleName (priv, ident) {
var name = ident.v;
var strpriv = null;
if (priv === null || name === null || name.charAt(0) !== "_" || name.charAt(1) !== "_") {
return ident;
}
// don't mangle __id__
if (name.charAt(name.length - 1) === "_" && name.charAt(name.length - 2) === "_") {
return ident;
}
// don't mangle classes that are all _ (obscure much?)
strpriv = priv.v;
strpriv.replace(/_/g, "");
if (strpriv === "") {
return ident;
}
strpriv = priv.v;
strpriv.replace(/^_*/, "");
strpriv = new Sk.builtin.str("_" + strpriv + name);
return strpriv;
}
/**
* @param {string} hint basename for gensym
* @param {...*} rest
*/
Compiler.prototype._gr = function (hint, rest) {
var i;
var v = this.gensym(hint);
this.u.localtemps.push(v);
out("var ", v, "=");
for (i = 1; i < arguments.length; ++i) {
out(arguments[i]);
}
out(";");
return v;
};
/**
* Function to test if an interrupt should occur if the program has been running for too long.
* This function is executed at every test/branch operation.
*/
Compiler.prototype.outputInterruptTest = function () { // Added by RNL
var output = "";
if (Sk.execLimit !== null || Sk.yieldLimit !== null && this.u.canSuspend) {
output += "var $dateNow = Date.now();";
if (Sk.execLimit !== null) {
output += "if ($dateNow - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}";
}
if (Sk.yieldLimit !== null && this.u.canSuspend) {
output += "if ($dateNow - Sk.lastYield > Sk.yieldLimit) {";
output += "var $susp = $saveSuspension({data: {type: 'Sk.yield'}, resume: function() {}}, '"+this.filename+"',$currLineNo,$currColNo);";
output += "$susp.$blk = $blk;";
output += "$susp.optional = true;";
output += "return $susp;";
output += "}";
this.u.doesSuspend = true;
}
}
return output;
};
Compiler.prototype._jumpfalse = function (test, block) {
var cond = this._gr("jfalse", "(", test, "===false||!Sk.misceval.isTrue(", test, "))");
out("if(", cond, "){/*test failed */$blk=", block, ";continue;}");
};
Compiler.prototype._jumpundef = function (test, block) {
out("if(", test, "===undefined){$blk=", block, ";continue;}");
};
Compiler.prototype._jumptrue = function (test, block) {
var cond = this._gr("jtrue", "(", test, "===true||Sk.misceval.isTrue(", test, "))");
out("if(", cond, "){/*test passed */$blk=", block, ";continue;}");
};
Compiler.prototype._jump = function (block) {
if (this.u.blocks[this.u.curblock]._next === null) {
out("$blk=", block, ";");
this.u.blocks[this.u.curblock]._next = block;
}
};
/**
* @param {Object=} e Object with keys 'lineno' and 'col_offset'
*/
Compiler.prototype._checkSuspension = function(e) {
var retblk;
if (this.u.canSuspend) {
retblk = this.newBlock("function return or resume suspension");
this._jump(retblk);
this.setBlock(retblk);
e = e || {lineno: "$currLineNo", col_offset: "$currColNo"};
out ("if ($ret && $ret.$isSuspension) { return $saveSuspension($ret,'"+this.filename+"',"+e.lineno+","+e.col_offset+"); }");
this.u.doesSuspend = true;
this.u.tempsToSave = this.u.tempsToSave.concat(this.u.localtemps);
} else {
out ("if ($ret && $ret.$isSuspension) { $ret = Sk.misceval.retryOptionalSuspensionOrThrow($ret); }");
}
};
Compiler.prototype.ctuplelistorset = function(e, data, tuporlist) {
var i;
var items;
goog.asserts.assert(tuporlist === "tuple" || tuporlist === "list" || tuporlist === "set");
if (e.ctx === Store) {
items = this._gr("items", "Sk.abstr.sequenceUnpack(" + data + "," + e.elts.length + ")");
for (i = 0; i < e.elts.length; ++i) {
this.vexpr(e.elts[i], items + "[" + i + "]");
}
}
else if (e.ctx === Load || tuporlist === "set") { //because set's can't be assigned to.
items = [];
for (i = 0; i < e.elts.length; ++i) {
items.push(this._gr("elem", this.vexpr(e.elts[i])));
}
return this._gr("load" + tuporlist, "new Sk.builtins['", tuporlist, "']([", items, "])");
}
};
Compiler.prototype.cdict = function (e) {
var v;
var i;
var items;
goog.asserts.assert(e.values.length === e.keys.length);
items = [];
for (i = 0; i < e.values.length; ++i) {
v = this.vexpr(e.values[i]); // "backwards" to match order in cpy
items.push(this.vexpr(e.keys[i]));
items.push(v);
}
return this._gr("loaddict", "new Sk.builtins['dict']([", items, "])");
};
Compiler.prototype.clistcomp = function(e) {
goog.asserts.assert(e instanceof ListComp);
var tmp = this._gr("_compr", "new Sk.builtins['list']([])"); // note: _ is impt. for hack in name mangling (same as cpy)
return this.ccompgen("list", tmp, e.generators, 0, e.elt, null, e);
};
Compiler.prototype.cdictcomp = function(e) {
goog.asserts.assert(e instanceof DictComp);
var tmp = this._gr("_dcompr", "new Sk.builtins.dict([])");
return this.ccompgen("dict", tmp, e.generators, 0, e.value, e.key, e);
};
Compiler.prototype.csetcomp = function(e) {
goog.asserts.assert(e instanceof SetComp);
var tmp = this._gr("_setcompr", "new Sk.builtins.set([])");
return this.ccompgen("set", tmp, e.generators, 0, e.elt, null, e);
};
Compiler.prototype.ccompgen = function (type, tmpname, generators, genIndex, value, key, e) {
var start = this.newBlock(type + " comp start");
var skip = this.newBlock(type + " comp skip");
var anchor = this.newBlock(type + " comp anchor");
var l = generators[genIndex];
var toiter = this.vexpr(l.iter);
var iter = this._gr("iter", "Sk.abstr.iter(", toiter, ")");
var lvalue;
var lkey;
var ifres;
var i;
var target;
var nexti;
var n;
this._jump(start);
this.setBlock(start);
// load targets
out("$ret = Sk.abstr.iternext(", iter, ", true);");
this._checkSuspension(e);
nexti = this._gr("next", "$ret");
this._jumpundef(nexti, anchor); // todo; this should be handled by StopIteration
target = this.vexpr(l.target, nexti);
n = l.ifs.length;
for (i = 0; i < n; ++i) {
ifres = this.vexpr(l.ifs[i]);
this._jumpfalse(ifres, start);
}
if (++genIndex < generators.length) {
this.ccompgen(type, tmpname, generators, genIndex, value, key, e);
}
if (genIndex >= generators.length) {
lvalue = this.vexpr(value);
if (type === "dict") {
lkey = this.vexpr(key);
out(tmpname, ".mp$ass_subscript(", lkey, ",", lvalue, ");");
}
else if (type === "list") {
out(tmpname, ".v.push(", lvalue, ");"); // todo;
}
else if (type === "set") {
out(tmpname, ".v.mp$ass_subscript(", lvalue, ", true);");
}
this._jump(skip);
this.setBlock(skip);
}
this._jump(start);
this.setBlock(anchor);
return tmpname;
};
Compiler.prototype.cyield = function(e)
{
if (this.u.ste.blockType !== FunctionBlock) {
throw new SyntaxError("'yield' outside function");
}
var val = "null",
nextBlock;
if (e.value) {
val = this.vexpr(e.value);
}
nextBlock = this.newBlock("after yield");
// return a pair: resume target block and yielded value
out("return [/*resume*/", nextBlock, ",/*ret*/", val, "];");
this.setBlock(nextBlock);
return "$gen.gi$sentvalue"; // will either be null if none sent, or the value from gen.send(value)
};
Compiler.prototype.ccompare = function (e) {
var res;
var rhs;
var i;
var fres;
var done;
var n;
var cur;
goog.asserts.assert(e.ops.length === e.comparators.length);
cur = this.vexpr(e.left);
n = e.ops.length;
done = this.newBlock("done");
fres = this._gr("compareres", "null");
for (i = 0; i < n; ++i) {
rhs = this.vexpr(e.comparators[i]);
out("$ret = Sk.builtin.bool(Sk.misceval.richCompareBool(", cur, ",", rhs, ",'", e.ops[i].prototype._astname, "', true));");
this._checkSuspension(e);
out(fres, "=$ret;");
this._jumpfalse("$ret", done);
cur = rhs;
}
this._jump(done);
this.setBlock(done);
return fres;
};
Compiler.prototype.ccall = function (e) {
var kwargs;
var starargs;
var keywords;
var i;
var kwarray;
var func = this.vexpr(e.func);
var args = this.vseqexpr(e.args);
//print(JSON.stringify(e, null, 2));
if (e.keywords.length > 0 || e.starargs || e.kwargs) {
kwarray = [];
for (i = 0; i < e.keywords.length; ++i) {
kwarray.push("'" + e.keywords[i].arg.v + "'");
kwarray.push(this.vexpr(e.keywords[i].value));
}
keywords = "[" + kwarray.join(",") + "]";
starargs = "undefined";
kwargs = "undefined";
if (e.starargs) {
starargs = this.vexpr(e.starargs);
}
if (e.kwargs) {
kwargs = this.vexpr(e.kwargs);
}
out ("$ret;"); // This forces a failure if $ret isn't defined
out ("$ret = Sk.misceval.callOrSuspend(", func, ",", kwargs, ",", starargs, ",", keywords, args.length > 0 ? "," : "", args, ");");
}
else {
out ("$ret;"); // This forces a failure if $ret isn't defined
out ("$ret = Sk.misceval.callsimOrSuspend(", func, args.length > 0 ? "," : "", args, ");");
}
this._checkSuspension(e);
return this._gr("call", "$ret");
};
Compiler.prototype.cslice = function (s) {
var step;
var high;
var low;
goog.asserts.assert(s instanceof Slice);
low = s.lower ? this.vexpr(s.lower) : s.step ? "Sk.builtin.none.none$" : "new Sk.builtin.int_(0)"; // todo;ideally, these numbers would be constants
high = s.upper ? this.vexpr(s.upper) : s.step ? "Sk.builtin.none.none$" : "new Sk.builtin.int_(2147483647)";
step = s.step ? this.vexpr(s.step) : "Sk.builtin.none.none$";
return this._gr("slice", "new Sk.builtins['slice'](", low, ",", high, ",", step, ")");
};
Compiler.prototype.eslice = function (dims) {
var i;
var dimSubs, subs;
goog.asserts.assert(dims instanceof Array);
dimSubs = [];
for (i = 0; i < dims.length; i++) {
dimSubs.push(this.vslicesub(dims[i]));
}
return this._gr("extslice", "new Sk.builtins['tuple']([", dimSubs, "])");
};
Compiler.prototype.vslicesub = function (s) {
var subs;
switch (s.constructor) {
case Index:
subs = this.vexpr(s.value);
break;
case Slice:
subs = this.cslice(s);
break;
case Ellipsis:
goog.asserts.fail("todo compile.js Ellipsis;");
break;
case ExtSlice:
subs = this.eslice(s.dims);
break;
default:
goog.asserts.fail("invalid subscript kind");
}
return subs;
};
Compiler.prototype.vslice = function (s, ctx, obj, dataToStore) {
var subs = this.vslicesub(s);
return this.chandlesubscr(ctx, obj, subs, dataToStore);
};
Compiler.prototype.chandlesubscr = function (ctx, obj, subs, data) {
if (ctx === Load || ctx === AugLoad) {
out("$ret = Sk.abstr.objectGetItem(", obj, ",", subs, ", true);");
this._checkSuspension();
return this._gr("lsubscr", "$ret");
}
else if (ctx === Store || ctx === AugStore) {
out("$ret = Sk.abstr.objectSetItem(", obj, ",", subs, ",", data, ", true);");
this._checkSuspension();
}
else if (ctx === Del) {
out("Sk.abstr.objectDelItem(", obj, ",", subs, ");");
}
else {
goog.asserts.fail("handlesubscr fail");
}
};
Compiler.prototype.cboolop = function (e) {
var expres;
var i;
var retval;
var n;
var s;
var end;
var ifFailed;
var jtype;
goog.asserts.assert(e instanceof BoolOp);
if (e.op === And) {
jtype = this._jumpfalse;
}
else {
jtype = this._jumptrue;
}
end = this.newBlock("end of boolop");
s = e.values;
n = s.length;
for (i = 0; i < n; ++i) {
expres = this.vexpr(s[i]);
if (i === 0) {
retval = this._gr("boolopsucc", expres);
}
out(retval, "=", expres, ";");
jtype.call(this, expres, end);
}
this._jump(end);
this.setBlock(end);
return retval;
};
/**
*
* compiles an expression. to 'return' something, it'll gensym a var and store
* into that var so that the calling code doesn't have avoid just pasting the
* returned name.
*
* @param {Object} e
* @param {string=} data data to store in a store operation
* @param {Object=} augvar var to load/store to for augmented assignments like '+='.
* (already vexpr'ed, so we can evaluate it once and reuse for both load and store ops)
* @param {Object=} augsubs precomputed subscript for augmented assignments like '+='.
* (already vexpr'ed, so we can evaluate it once and reuse for both load and store ops)
*/
Compiler.prototype.vexpr = function (e, data, augvar, augsubs) {
var mangled;
var val;
var result;
var nStr; // used for preserving signs for floats (zeros)
if (e.lineno > this.u.lineno) {
this.u.lineno = e.lineno;
this.u.linenoSet = false;
}
//this.annotateSource(e);
switch (e.constructor) {
case BoolOp:
return this.cboolop(e);
case BinOp:
return this._gr("binop", "Sk.abstr.numberBinOp(", this.vexpr(e.left), ",", this.vexpr(e.right), ",'", e.op.prototype._astname, "')");
case UnaryOp:
return this._gr("unaryop", "Sk.abstr.numberUnaryOp(", this.vexpr(e.operand), ",'", e.op.prototype._astname, "')");
case Lambda:
return this.clambda(e);
case IfExp:
return this.cifexp(e);
case Dict:
return this.cdict(e);
case ListComp:
return this.clistcomp(e);
case DictComp:
return this.cdictcomp(e);
case SetComp:
return this.csetcomp(e);
case GeneratorExp:
return this.cgenexp(e);
case Yield:
return this.cyield(e);
case Compare:
return this.ccompare(e);
case Call:
result = this.ccall(e);
// After the function call, we've returned to this line
this.annotateSource(e);
return result;
case Num:
if (typeof e.n === "number") {
return e.n;
}
else if (e.n instanceof Sk.builtin.int_) {
return "new Sk.builtin.int_(" + e.n.v + ")";
} else if (e.n instanceof Sk.builtin.float_) {
// Preserve sign of zero for floats
nStr = e.n.v === 0 && 1/e.n.v === -Infinity ? "-0" : e.n.v;
return "new Sk.builtin.float_(" + nStr + ")";
}
else if (e.n instanceof Sk.builtin.lng) {
// long uses the tp$str() method which delegates to nmber.str$ which preserves the sign
return "Sk.longFromStr('" + e.n.tp$str().v + "')";
}
else if (e.n instanceof Sk.builtin.complex) {
// preserve sign of zero here too
var real_val = e.n.real.v === 0 && 1/e.n.real.v === -Infinity ? "-0" : e.n.real.v;
var imag_val = e.n.imag.v === 0 && 1/e.n.imag.v === -Infinity ? "-0" : e.n.imag.v;
return "new Sk.builtin.complex(new Sk.builtin.float_(" + real_val + "), new Sk.builtin.float_(" + imag_val + "))";
}
goog.asserts.fail("unhandled Num type");
case Str:
return this._gr("str", "new Sk.builtins['str'](", e.s["$r"]().v, ")");
case Attribute:
if (e.ctx !== AugLoad && e.ctx !== AugStore) {
val = this.vexpr(e.value);
}
mangled = e.attr["$r"]().v;
mangled = mangled.substring(1, mangled.length - 1);
mangled = mangleName(this.u.private_, new Sk.builtin.str(mangled)).v;
mangled = fixReservedWords(mangled);
mangled = fixReservedNames(mangled);
switch (e.ctx) {
case AugLoad:
out("$ret = Sk.abstr.gattr(", augvar, ",'", mangled, "', true);");
this._checkSuspension(e);
return this._gr("lattr", "$ret");
case Load:
out("$ret = Sk.abstr.gattr(", val, ",'", mangled, "', true);");
this._checkSuspension(e);
return this._gr("lattr", "$ret");
case AugStore:
// To be more correct, we shouldn't sattr() again if the in-place update worked.
// At the time of writing (26/Feb/2015), Sk.abstr.numberInplaceBinOp never returns undefined,
// so this will never *not* execute. But it could, if Sk.abstr.numberInplaceBinOp were fixed.
out("$ret = undefined;");
out("if(", data, "!==undefined){");
out("$ret = Sk.abstr.sattr(", augvar, ",'", mangled, "',", data, ", true);");
out("}");
this._checkSuspension(e);
break;
case Store:
out("$ret = Sk.abstr.sattr(", val, ",'", mangled, "',", data, ", true);");
this._checkSuspension(e);
break;
case Del:
goog.asserts.fail("todo Del;");
break;
case Param:
default:
goog.asserts.fail("invalid attribute expression");
}
break;
case Subscript:
switch (e.ctx) {
case AugLoad:
out("$ret = Sk.abstr.objectGetItem(",augvar,",",augsubs,", true);");
this._checkSuspension(e)
return this._gr("gitem", "$ret");
case Load:
case Store:
case Del:
return this.vslice(e.slice, e.ctx, this.vexpr(e.value), data);
case AugStore:
// To be more correct, we shouldn't sattr() again if the in-place update worked.
// At the time of writing (26/Feb/2015), Sk.abstr.numberInplaceBinOp never returns undefined,
// so this will never *not* execute. But it could, if Sk.abstr.numberInplaceBinOp were fixed.
out("$ret=undefined;");
out("if(", data, "!==undefined){");
out("$ret=Sk.abstr.objectSetItem(",augvar,",",augsubs,",",data,", true)");
out("}");
this._checkSuspension(e);
break;
case Param:
default:
goog.asserts.fail("invalid subscript expression");
}
break;
case Name:
return this.nameop(e.id, e.ctx, data);
case List:
return this.ctuplelistorset(e, data, 'list');
case Tuple:
return this.ctuplelistorset(e, data, 'tuple');
case Set:
return this.ctuplelistorset(e, data, 'set');
default:
goog.asserts.fail("unhandled case in vexpr");
}
};
/**
* @param {Array.<Object>} exprs
* @param {Array.<string>=} data
*/
Compiler.prototype.vseqexpr = function (exprs, data) {
var i;
var ret;
goog.asserts.assert(data === undefined || exprs.length === data.length);
ret = [];
for (i = 0; i < exprs.length; ++i) {
ret.push(this.vexpr(exprs[i], data === undefined ? undefined : data[i]));
}
return ret;
};
Compiler.prototype.caugassign = function (s) {
var to;
var augsub;
var res;
var val;
var aug;
var auge;
var e;
goog.asserts.assert(s instanceof AugAssign);
e = s.target;
switch (e.constructor) {
case Attribute:
to = this.vexpr(e.value);
auge = new Attribute(e.value, e.attr, AugLoad, e.lineno, e.col_offset);
aug = this.vexpr(auge, undefined, to);
val = this.vexpr(s.value);
res = this._gr("inplbinopattr", "Sk.abstr.numberInplaceBinOp(", aug, ",", val, ",'", s.op.prototype._astname, "')");
auge.ctx = AugStore;
return this.vexpr(auge, res, to);
case Subscript:
// Only compile the subscript value once
to = this.vexpr(e.value);
augsub = this.vslicesub(e.slice);
auge = new Subscript(e.value, augsub, AugLoad, e.lineno, e.col_offset);
aug = this.vexpr(auge, undefined, to, augsub);
val = this.vexpr(s.value);
res = this._gr("inplbinopsubscr", "Sk.abstr.numberInplaceBinOp(", aug, ",", val, ",'", s.op.prototype._astname, "')");
auge.ctx = AugStore;
return this.vexpr(auge, res, to, augsub);
case Name:
to = this.nameop(e.id, Load);
val = this.vexpr(s.value);
res = this._gr("inplbinop", "Sk.abstr.numberInplaceBinOp(", to, ",", val, ",'", s.op.prototype._astname, "')");
return this.nameop(e.id, Store, res);
default:
goog.asserts.fail("unhandled case in augassign");
}
};
/**
* optimize some constant exprs. returns 0 if always false, 1 if always true or -1 otherwise.
*/
Compiler.prototype.exprConstant = function (e) {
switch (e.constructor) {
case Num:
return Sk.misceval.isTrue(e.n) ? 1 : 0;
case Str:
return Sk.misceval.isTrue(e.s) ? 1 : 0;
case Name:
// todo; do __debug__ test here if opt
default:
return -1;
}
};
Compiler.prototype.newBlock = function (name) {
var ret = this.u.blocknum++;
this.u.blocks[ret] = [];
this.u.blocks[ret]._name = name || "<unnamed>";
this.u.blocks[ret]._next = null;
return ret;
};
Compiler.prototype.setBlock = function (n) {
goog.asserts.assert(n >= 0 && n < this.u.blocknum);
this.u.curblock = n;
};
Compiler.prototype.pushBreakBlock = function (n) {
goog.asserts.assert(n >= 0 && n < this.u.blocknum);
this.u.breakBlocks.push(n);
};
Compiler.prototype.popBreakBlock = function () {
this.u.breakBlocks.pop();
};
Compiler.prototype.pushContinueBlock = function (n) {
goog.asserts.assert(n >= 0 && n < this.u.blocknum);
this.u.continueBlocks.push(n);
};
Compiler.prototype.popContinueBlock = function () {
this.u.continueBlocks.pop();
};
Compiler.prototype.pushExceptBlock = function (n) {
goog.asserts.assert(n >= 0 && n < this.u.blocknum);
this.u.exceptBlocks.push(n);
};
Compiler.prototype.popExceptBlock = function () {
this.u.exceptBlocks.pop();
};
Compiler.prototype.pushFinallyBlock = function (n) {
goog.asserts.assert(n >= 0 && n < this.u.blocknum);
this.u.finallyBlocks.push(n);
};
Compiler.prototype.popFinallyBlock = function () {
this.u.finallyBlocks.pop();
};
Compiler.prototype.setupExcept = function (eb) {
out("$exc.push(", eb, ");");
//this.pushExceptBlock(eb);
};
Compiler.prototype.endExcept = function () {
out("$exc.pop();");
};
Compiler.prototype.outputLocals = function (unit) {
var name;
var output;
var i;
var have = {};
//print("args", unit.name.v, JSON.stringify(unit.argnames));
for (i = 0; unit.argnames && i < unit.argnames.length; ++i) {
have[unit.argnames[i]] = true;
}
unit.localnames.sort();
output = [];
for (i = 0; i < unit.localnames.length; ++i) {
name = unit.localnames[i];
if (have[name] === undefined) {
output.push(name);
have[name] = true;
}
}
if (output.length > 0) {
return "var " + output.join(",") + "; /* locals */";
}
return "";
};
Compiler.prototype.outputSuspensionHelpers = function (unit) {
var i, t;
var localSaveCode = [];
var localsToSave = unit.localnames.concat(unit.tempsToSave);
var seenTemps = {};
var hasCell = unit.ste.blockType === FunctionBlock && unit.ste.childHasFree;
var output = (localsToSave.length > 0 ? ("var " + localsToSave.join(",") + ";") : "") +
"var $wakeFromSuspension = function() {" +
"var susp = "+unit.scopename+".$wakingSuspension; delete "+unit.scopename+".$wakingSuspension;" +
"$blk=susp.$blk; $loc=susp.$loc; $gbl=susp.$gbl; $exc=susp.$exc; $err=susp.$err;" +
"$currLineNo=susp.$lineno; $currColNo=susp.$colno; Sk.lastYield=Date.now();" +
(hasCell?"$cell=susp.$cell;":"");
for (i = 0; i < localsToSave.length; i++) {
t = localsToSave[i];
if (seenTemps[t]===undefined) {
output += t + "=susp.$tmps." + t + ";";
seenTemps[t] = true;
}
}
output += "try { $ret=susp.child.resume(); } catch(err) { if (!(err instanceof Sk.builtin.BaseException)) { err = new Sk.builtin.ExternalError(err); } err.traceback.push({lineno: $currLineNo, colno: $currColNo, filename: '"+this.filename+"'}); if($exc.length>0) { $err=err; $blk=$exc.pop(); } else { throw err; } }" +
"};";
output += "var $saveSuspension = function($child, $filename, $lineno, $colno) {" +
"var susp = new Sk.misceval.Suspension(); susp.child=$child;" +
"susp.resume=function(){"+unit.scopename+".$wakingSuspension=susp; return "+unit.scopename+"("+(unit.ste.generator?"$gen":"")+"); };" +
"susp.data=susp.child.data;susp.$blk=$blk;susp.$loc=$loc;susp.$gbl=$gbl;susp.$exc=$exc;susp.$err=$err;" +
"susp.$filename=$filename;susp.$lineno=$lineno;susp.$colno=$colno;" +
"susp.optional=susp.child.optional;" +
(hasCell ? "susp.$cell=$cell;" : "");
seenTemps = {};
for (i = 0; i < localsToSave.length; i++) {
t = localsToSave[i];
if (seenTemps[t]===undefined) {
localSaveCode.push("\"" + t + "\":" + t);
seenTemps[t]=true;
}
}
output += "susp.$tmps={" + localSaveCode.join(",") + "};" +
"return susp;" +
"};";
return output;
}
Compiler.prototype.outputAllUnits = function () {
var i;
var blocks;
var unit;
var j;
var ret = "";
var block;
var generatedBlocks;
for (j = 0; j < this.allUnits.length; ++j) {
unit = this.allUnits[j];
ret += unit.prefixCode;
ret += this.outputLocals(unit);
if (unit.doesSuspend) {
ret += this.outputSuspensionHelpers(unit);
}
ret += unit.varDeclsCode;
ret += unit.switchCode;
blocks = unit.blocks;
generatedBlocks = Object.create(null);
for (i = 0; i < blocks.length; ++i) {
block = i;
if (block in generatedBlocks)
continue;
while (true) {
generatedBlocks[block] = true;
ret += "case " + block + ": /* --- " + blocks[block]._name + " --- */";
ret += blocks[block].join("");
if (blocks[block]._next !== null) {
if (!(blocks[block]._next in generatedBlocks)) {
ret += "/* allowing case fallthrough */";
block = blocks[block]._next;
}
else {
ret += "/* jump */ continue;";
break;
}
}
else {
ret += "throw new Sk.builtin.SystemError('internal error: unterminated block');";
break;
}
}
}
ret += unit.suffixCode;
}
return ret;
};
Compiler.prototype.cif = function (s) {
var test;
var next;
var end;
var constant;
goog.asserts.assert(s instanceof If_);
constant = this.exprConstant(s.test);
if (constant === 0) {
if (s.orelse && s.orelse.length > 0) {
this.vseqstmt(s.orelse);
}
}
else if (constant === 1) {
this.vseqstmt(s.body);
}
else {
end = this.newBlock("end of if");
if (s.orelse && s.orelse.length > 0) {
next = this.newBlock("next branch of if");
}
test = this.vexpr(s.test);
if (s.orelse && s.orelse.length > 0) {
this._jumpfalse(test, next);
this.vseqstmt(s.body);
this._jump(end);
this.setBlock(next);
this.vseqstmt(s.orelse);
}
else {
this._jumpfalse(test, end);
this.vseqstmt(s.body);
}
this._jump(end);
this.setBlock(end);
}
};
Compiler.prototype.cwhile = function (s) {
var body;
var orelse;
var next;
var top;
var constant = this.exprConstant(s.test);
if (constant === 0) {
if (s.orelse) {
this.vseqstmt(s.orelse);
}
}
else {
top = this.newBlock("while test");
this._jump(top);
this.setBlock(top);
next = this.newBlock("after while");
orelse = s.orelse.length > 0 ? this.newBlock("while orelse") : null;
body = this.newBlock("while body");
this.annotateSource(s);
this._jumpfalse(this.vexpr(s.test), orelse ? orelse : next);
this._jump(body);
this.pushBreakBlock(next);
this.pushContinueBlock(top);
this.setBlock(body);
this.vseqstmt(s.body);
this._jump(top);
this.popContinueBlock();
this.popBreakBlock();
if (s.orelse.length > 0) {
this.setBlock(orelse);
this.vseqstmt(s.orelse);
this._jump(next);
}
this.setBlock(next);
}
};
Compiler.prototype.cfor = function (s) {
var target;
var nexti;
var iter;
var toiter;
var start = this.newBlock("for start");
var cleanup = this.newBlock("for cleanup");
var end = this.newBlock("for end");
this.pushBreakBlock(end);
this.pushContinueBlock(start);
// get the iterator
toiter = this.vexpr(s.iter);
if (this.u.ste.generator) {
// if we're in a generator, we have to store the iterator to a local
// so it's preserved (as we cross blocks here and assume it survives)
iter = "$loc." + this.gensym("iter");
out(iter, "=Sk.abstr.iter(", toiter, ");");
}
else {
iter = this._gr("iter", "Sk.abstr.iter(", toiter, ")");
this.u.tempsToSave.push(iter); // Save it across suspensions
}
this._jump(start);
this.setBlock(start);
// load targets
out ("$ret = Sk.abstr.iternext(", iter,(this.u.canSuspend?", true":", false"),");");
this._checkSuspension(s);
nexti = this._gr("next", "$ret");
this._jumpundef(nexti, cleanup); // todo; this should be handled by StopIteration
target = this.vexpr(s.target, nexti);
// execute body
this.vseqstmt(s.body);
// jump to top of loop
this._jump(start);
this.setBlock(cleanup);
this.popContinueBlock();
this.popBreakBlock();
this.vseqstmt(s.orelse);
this._jump(end);
this.setBlock(end);
};
Compiler.prototype.craise = function (s) {
var inst = "", exc;
if (s.inst) {
// handles: raise Error, arguments
inst = this.vexpr(s.inst);
out("throw ", this.vexpr(s.type), "(", inst, ");");
}
else if (s.type) {
if (s.type.func) {
// handles: raise Error(arguments)
out("throw ", this.vexpr(s.type), ";");
}
else {
// handles: raise Error OR raise someinstance
exc = this._gr("err", this.vexpr(s.type));
out("if(",exc," instanceof Sk.builtin.type) {",
"throw Sk.misceval.callsim(", exc, ");",
"} else if(typeof(",exc,") === 'function') {",
"throw ",exc,"();",
"} else {",
"throw ", exc, ";",
"}");
}
}
else {
// re-raise
out("throw $err;");
}
};
Compiler.prototype.ctryexcept = function (s) {
var check;
var next;
var handlertype;
var handler;
var end;
var orelse;
var unhandled;
var i;
var n = s.handlers.length;
// Create a block for each except clause
var handlers = [];
for (i = 0; i < n; ++i) {
handlers.push(this.newBlock("except_" + i + "_"));
}
unhandled = this.newBlock("unhandled");
orelse = this.newBlock("orelse");
end = this.newBlock("end");
this.setupExcept(handlers[0]);
this.vseqstmt(s.body);
this.endExcept();
this._jump(orelse);
for (i = 0; i < n; ++i) {
this.setBlock(handlers[i]);
handler = s.handlers[i];
if (!handler.type && i < n - 1) {
throw new SyntaxError("default 'except:' must be last");
}
if (handler.type) {
// should jump to next handler if err not isinstance of handler.type
handlertype = this.vexpr(handler.type);
next = (i == n - 1) ? unhandled : handlers[i + 1];
// var isinstance = this.nameop(new Sk.builtin.str("isinstance"), Load));
// var check = this._gr('call', "Sk.misceval.callsim(", isinstance, ", $err, ", handlertype, ")");
check = this._gr("instance", "Sk.misceval.isTrue(Sk.builtin.isinstance($err, ", handlertype, "))");
this._jumpfalse(check, next);
}
if (handler.name) {
this.vexpr(handler.name, "$err");
}
// Need to execute finally before leaving body if an exception is raised
this.vseqstmt(handler.body);
// Should jump to finally, but finally is not implemented yet
this._jump(end);
}
// If no except clause catches exception, throw it again
this.setBlock(unhandled);
// Should execute finally first
out("throw $err;");
this.setBlock(orelse);
this.vseqstmt(s.orelse);
// Should jump to finally, but finally is not implemented yet
this._jump(end);
this.setBlock(end);
};
Compiler.prototype.ctryfinally = function (s) {
out("/*todo; tryfinally*/");
// everything but the finally?
this.ctryexcept(s.body[0]);
};
Compiler.prototype.cassert = function (s) {
/* todo; warnings method
if (s.test instanceof Tuple && s.test.elts.length > 0)
Sk.warn("assertion is always true, perhaps remove parentheses?");
*/
var test = this.vexpr(s.test);
var end = this.newBlock("end");
this._jumptrue(test, end);
// todo; exception handling
// maybe replace with goog.asserts.fail?? or just an alert?
out("throw new Sk.builtin.AssertionError(", s.msg ? this.vexpr(s.msg) : "", ");");
this.setBlock(end);
};
Compiler.prototype.cimportas = function (name, asname, mod) {
var attr;
var src = name.v;
var dotLoc = src.indexOf(".");
//print("src", src);
//print("dotLoc", dotLoc);
var cur = mod;
if (dotLoc !== -1) {
// if there's dots in the module name, __import__ will have returned
// the top-level module. so, we need to extract the actual module by
// getattr'ing up through the names, and then storing the leaf under
// the name it was to be imported as.
src = src.substr(dotLoc + 1);
//print("src now", src);
while (dotLoc !== -1) {
dotLoc = src.indexOf(".");
attr = dotLoc !== -1 ? src.substr(0, dotLoc) : src;
cur = this._gr("lattr", "Sk.abstr.gattr(", cur, ",'", attr, "')");
src = src.substr(dotLoc + 1);
}
}
return this.nameop(asname, Store, cur);
};
Compiler.prototype.cimport = function (s) {
var lastDot;
var tmp;
var mod;
var alias;
var i;
var n = s.names.length;
for (i = 0; i < n; ++i) {
alias = s.names[i];
out("$ret = Sk.builtin.__import__(", alias.name["$r"]().v, ",$gbl,$loc,[]);");
this._checkSuspension(s);
mod = this._gr("module", "$ret");
if (alias.asname) {
this.cimportas(alias.name, alias.asname, mod);
}
else {
tmp = alias.name;
lastDot = tmp.v.indexOf(".");
if (lastDot !== -1) {
tmp = new Sk.builtin.str(tmp.v.substr(0, lastDot));
}
this.nameop(tmp, Store, mod);
}
}
};
Compiler.prototype.cfromimport = function (s) {
var storeName;
var got;
var alias;
var mod;
var i;
var n = s.names.length;
var names = [];
for (i = 0; i < n; ++i) {
names[i] = s.names[i].name["$r"]().v;
}
out("$ret = Sk.builtin.__import__(", s.module["$r"]().v, ",$gbl,$loc,[", names, "]);");
this._checkSuspension(s);
//out("print('__import__ returned ' + $ret);");
//out("for (var x in $ret) { print(x); }");
mod = this._gr("module", "$ret");
for (i = 0; i < n; ++i) {
alias = s.names[i];
if (i === 0 && alias.name.v === "*") {
goog.asserts.assert(n === 1);
out("Sk.importStar(", mod, ",$loc, $gbl);");
return;
}
//out("print(\"getting Sk.abstr.gattr(", mod, ",", alias.name["$r"]().v, ")\");");
got = this._gr("item", "Sk.abstr.gattr(", mod, ",", alias.name["$r"]().v, ")");
//out("print('got');");
storeName = alias.name;
if (alias.asname) {
storeName = alias.asname;
}
this.nameop(storeName, Store, got);
}
};
/**
* builds a code object (js function) for various constructs. used by def,
* lambda, generator expressions. it isn't used for class because it seemed
* different enough.
*
* handles:
* - setting up a new scope
* - decorators (if any)
* - defaults setup
* - setup for cell and free vars
* - setup and modification for generators
*
* @param {Object} n ast node to build for
* @param {Sk.builtin.str} coname name of code object to build
* @param {Array} decorator_list ast of decorators if any
* @param {arguments_} args arguments to function, if any
* @param {Function} callback called after setup to do actual work of function
*
* @returns the name of the newly created function or generator object.
*
*/
Compiler.prototype.buildcodeobj = function (n, coname, decorator_list, args, callback) {
var containingHasFree;
var frees;
var argnamesarr;
var argnames;
var start;
var kw;
var maxargs;
var minargs;
var id;
var argname;
var offset;
var cells;
var locals;
var i;
var funcArgs;
var entryBlock;
var hasCell;
var hasFree;
var isGenerator;
var scopename;
var decos = [];
var defaults = [];
var vararg = null;
var kwarg = null;
// decorators and defaults have to be evaluated out here before we enter
// the new scope. we output the defaults and attach them to this code
// object, but only once we know the name of it (so we do it after we've
// exited the scope near the end of this function).
if (decorator_list) {
decos = this.vseqexpr(decorator_list);
}
if (args && args.defaults) {
defaults = this.vseqexpr(args.defaults);
}
if (args && args.vararg) {
vararg = args.vararg;
}
if (args && args.kwarg) {
kwarg = args.kwarg;
}
//
// enter the new scope, and create the first block
//
scopename = this.enterScope(coname, n, n.lineno, this.canSuspend);
isGenerator = this.u.ste.generator;
hasFree = this.u.ste.hasFree;
hasCell = this.u.ste.childHasFree;
entryBlock = this.newBlock("codeobj entry");
//
// the header of the function, and arguments
//
this.u.prefixCode = "var " + scopename + "=(function " + this.niceName(coname.v) + "$(";
funcArgs = [];
if (isGenerator) {
if (kwarg) {
throw new SyntaxError(coname.v + "(): keyword arguments in generators not supported");
}
if (vararg) {
throw new SyntaxError(coname.v + "(): variable number of arguments in generators not supported");
}
funcArgs.push("$gen");
}
else {
if (kwarg) {
funcArgs.push("$kwa");
this.u.tempsToSave.push("$kwa");
}
for (i = 0; args && i < args.args.length; ++i) {
funcArgs.push(this.nameop(args.args[i].id, Param));
}
}
if (hasFree) {
if (vararg) {
this.u.varDeclsCode += "$free = arguments[arguments.length-1];"
} else {
funcArgs.push("$free");
this.u.tempsToSave.push("$free");
}
}
this.u.prefixCode += funcArgs.join(",");
this.u.prefixCode += "){";
if (isGenerator) {
this.u.prefixCode += "\n// generator\n";
}
if (hasFree) {
this.u.prefixCode += "\n// has free\n";
}
if (hasCell) {
this.u.prefixCode += "\n// has cell\n";
}
//
// set up standard dicts/variables
//
locals = "{}";
if (isGenerator) {
entryBlock = "$gen.gi$resumeat";
locals = "$gen.gi$locals";
}
cells = "";
if (hasCell) {
if (isGenerator) {
cells = ",$cell=$gen.gi$cells";
}
else {
cells = ",$cell={}";
}
}
// note special usage of 'this' to avoid having to slice globals into
// all function invocations in call
this.u.varDeclsCode += "var $blk=" + entryBlock + ",$exc=[],$loc=" + locals + cells + ",$gbl=this,$err=undefined,$ret=undefined,$currLineNo=undefined,$currColNo=undefined;";
if (Sk.execLimit !== null) {
this.u.varDeclsCode += "if (typeof Sk.execStart === 'undefined') {Sk.execStart = Date.now()}";
}
if (Sk.yieldLimit !== null && this.u.canSuspend) {
this.u.varDeclsCode += "if (typeof Sk.lastYield === 'undefined') {Sk.lastYield = Date.now()}";
}
//
// If there is a suspension, resume from it. Otherwise, initialise
// parameters appropriately.
//
this.u.varDeclsCode += "if ("+scopename+".$wakingSuspension!==undefined) { $wakeFromSuspension(); } else {";
//
// initialize default arguments. we store the values of the defaults to
// this code object as .$defaults just below after we exit this scope.
//
if (defaults.length > 0) {
// defaults have to be "right justified" so if there's less defaults
// than args we offset to make them match up (we don't need another
// correlation in the ast)
offset = args.args.length - defaults.length;
for (i = 0; i < defaults.length; ++i) {
argname = this.nameop(args.args[i + offset].id, Param);
this.u.varDeclsCode += "if(" + argname + "===undefined)" + argname + "=" + scopename + ".$defaults[" + i + "];";
}
}
//
// copy all parameters that are also cells into the cells dict. this is so
// they can be accessed correctly by nested scopes.
//
for (i = 0; args && i < args.args.length; ++i) {
id = args.args[i].id;
if (this.isCell(id)) {
this.u.varDeclsCode += "$cell." + id.v + "=" + id.v + ";";
}
}
//
// make sure correct number of arguments were passed (generators handled below)
//
if (!isGenerator) {
minargs = args ? args.args.length - defaults.length : 0;
maxargs = vararg ? Infinity : (args ? args.args.length : 0);
kw = kwarg ? true : false;
this.u.varDeclsCode += "Sk.builtin.pyCheckArgs(\"" + coname.v +
"\", arguments, " + minargs + ", " + maxargs + ", " + kw +
", " + hasFree + ");";
}
//
// initialize vararg, if any
//
if (vararg) {
start = funcArgs.length;
this.u.localnames.push(vararg.v);
this.u.varDeclsCode += vararg.v + "=new Sk.builtins['tuple'](Array.prototype.slice.call(arguments," + start + (hasFree ? ",-1)" : ")") + "); /*vararg*/";
}
//
// initialize kwarg, if any
//
if (kwarg) {
this.u.localnames.push(kwarg.v);
this.u.varDeclsCode += kwarg.v + "=new Sk.builtins['dict']($kwa);";
}
//
// close the else{} block from the wakingSuspension check
//
this.u.varDeclsCode += "}";
//
// finally, set up the block switch that the jump code expects
//
// Old switch code
// this.u.switchCode += "while(true){switch($blk){";
// this.u.suffixCode = "}break;}});";
// New switch code to catch exceptions
this.u.switchCode = "while(true){try{"
this.u.switchCode += this.outputInterruptTest();
this.u.switchCode += "switch($blk){";
this.u.suffixCode = "} }catch(err){ if (!(err instanceof Sk.builtin.BaseException)) { err = new Sk.builtin.ExternalError(err); } err.traceback.push({lineno: $currLineNo, colno: $currColNo, filename: '"+this.filename+"'}); if ($exc.length>0) { $err = err; $blk=$exc.pop(); continue; } else { throw err; }} }});";
//
// jump back to the handler so it can do the main actual work of the
// function
//
callback.call(this, scopename);
//
// get a list of all the argument names (used to attach to the code
// object, and also to allow us to declare only locals that aren't also
// parameters).
if (args && args.args.length > 0) {
argnamesarr = [];
for (i = 0; i < args.args.length; ++i) {
argnamesarr.push(args.args[i].id.v);
}
argnames = argnamesarr.join("', '");
// store to unit so we know what local variables not to declare
this.u.argnames = argnamesarr;
}
//
// and exit the code object scope
//
this.exitScope();
//
// attach the default values we evaluated at the beginning to the code
// object so that it can get at them to set any arguments that are left
// unset.
//
if (defaults.length > 0) {
out(scopename, ".$defaults=[", defaults.join(","), "];");
}
if (decos.length > 0) {
out(scopename, ".$decorators=[", decos.join(","), "];");
}
//
// attach co_varnames (only the argument names) for keyword argument
// binding.
//
if (argnames) {
out(scopename, ".co_varnames=['", argnames, "'];");
}
//
// attach flags
//
if (kwarg) {
out(scopename, ".co_kwargs=1;");
}
//
// build either a 'function' or 'generator'. the function is just a simple
// constructor call. the generator is more complicated. it needs to make a
// new generator every time it's called, so the thing that's returned is
// actually a function that makes the generator (and passes arguments to
// the function onwards to the generator). this should probably actually
// be a function object, rather than a js function like it is now. we also
// have to build the argument names to pass to the generator because it
// needs to store all locals into itself so that they're maintained across
// yields.
//
// todo; possibly this should be outside?
//
frees = "";
if (hasFree) {
frees = ",$cell";
// if the scope we're in where we're defining this one has free
// vars, they may also be cell vars, so we pass those to the
// closure too.
containingHasFree = this.u.ste.hasFree;
if (containingHasFree) {
frees += ",$free";
}
}
if (isGenerator)
// Keyword and variable arguments are not currently supported in generators.
// The call to pyCheckArgs assumes they can't be true.
{
if (args && args.args.length > 0) {
return this._gr("gener", "new Sk.builtins['function']((function(){var $origargs=Array.prototype.slice.call(arguments);Sk.builtin.pyCheckArgs(\"",
coname.v, "\",arguments,", args.args.length - defaults.length, ",", args.args.length,
");return new Sk.builtins['generator'](", scopename, ",$gbl,$origargs", frees, ");}))");
}
else {
return this._gr("gener", "new Sk.builtins['function']((function(){Sk.builtin.pyCheckArgs(\"", coname.v,
"\",arguments,0,0);return new Sk.builtins['generator'](", scopename, ",$gbl,[]", frees, ");}))");
}
}
else {
var res;
if (decos.length > 0) {
out("$ret = Sk.misceval.callsimOrSuspend(", scopename, ".$decorators[0], new Sk.builtins['function'](", scopename, ",$gbl", frees, "));");
this._checkSuspension();
return this._gr("funcobj", "$ret");
}
return this._gr("funcobj", "new Sk.builtins['function'](", scopename, ",$gbl", frees, ")");
}
};
Compiler.prototype.cfunction = function (s) {
var funcorgen;
goog.asserts.assert(s instanceof FunctionDef);
funcorgen = this.buildcodeobj(s, s.name, s.decorator_list, s.args, function (scopename) {
this.vseqstmt(s.body);
out("return Sk.builtin.none.none$;"); // if we fall off the bottom, we want the ret to be None
});
this.nameop(s.name, Store, funcorgen);
};
Compiler.prototype.clambda = function (e) {
var func;
goog.asserts.assert(e instanceof Lambda);
func = this.buildcodeobj(e, new Sk.builtin.str("<lambda>"), null, e.args, function (scopename) {
var val = this.vexpr(e.body);
out("return ", val, ";");
});
return func;
};
Compiler.prototype.cifexp = function (e) {
var next = this.newBlock("next of ifexp");
var end = this.newBlock("end of ifexp");
var ret = this._gr("res", "null");
var test = this.vexpr(e.test);
this._jumpfalse(test, next);
out(ret, "=", this.vexpr(e.body), ";");
this._jump(end);
this.setBlock(next);
out(ret, "=", this.vexpr(e.orelse), ";");
this._jump(end);
this.setBlock(end);
return ret;
};
Compiler.prototype.cgenexpgen = function (generators, genIndex, elt) {
var velt;
var ifres;
var i;
var n;
var target;
var nexti;
var toiter;
var start = this.newBlock("start for " + genIndex);
var skip = this.newBlock("skip for " + genIndex);
var ifCleanup = this.newBlock("if cleanup for " + genIndex);
var end = this.newBlock("end for " + genIndex);
var ge = generators[genIndex];
var iter;
if (genIndex === 0) {
// the outer most iterator is evaluated in the scope outside so we
// have to evaluate it outside and store it into the generator as a
// local, which we retrieve here.
iter = "$loc.$iter0";
}
else {
toiter = this.vexpr(ge.iter);
iter = "$loc." + this.gensym("iter");
out(iter, "=", "Sk.abstr.iter(", toiter, ");");
}
this._jump(start);
this.setBlock(start);
this.annotateSource(elt);
// load targets
out ("$ret = Sk.abstr.iternext(", iter,(this.u.canSuspend?", true":", false"),");");
this._checkSuspension(elt);
nexti = this._gr("next", "$ret");
this._jumpundef(nexti, end); // todo; this should be handled by StopIteration
target = this.vexpr(ge.target, nexti);
n = ge.ifs.length;
for (i = 0; i < n; ++i) {
this.annotateSource(ge.ifs[i]);
ifres = this.vexpr(ge.ifs[i]);
this._jumpfalse(ifres, start);
}
if (++genIndex < generators.length) {
this.cgenexpgen(generators, genIndex, elt);
}
if (genIndex >= generators.length) {
this.annotateSource(elt);
velt = this.vexpr(elt);
out("return [", skip, "/*resume*/,", velt, "/*ret*/];");
this.setBlock(skip);
}
this._jump(start);
this.setBlock(end);
if (genIndex === 1) {
out("return Sk.builtin.none.none$;");
}
};
Compiler.prototype.cgenexp = function (e) {
var gen = this.buildcodeobj(e, new Sk.builtin.str("<genexpr>"), null, null, function (scopename) {
this.cgenexpgen(e.generators, 0, e.elt);
});
// call the generator maker to get the generator. this is kind of dumb,
// but the code builder builds a wrapper that makes generators for normal
// function generators, so we just do it outside (even just new'ing it
// inline would be fine).
var gener = this._gr("gener", "Sk.misceval.callsim(", gen, ");");
// stuff the outermost iterator into the generator after evaluating it
// outside of the function. it's retrieved by the fixed name above.
out(gener, ".gi$locals.$iter0=Sk.abstr.iter(", this.vexpr(e.generators[0].iter), ");");
return gener;
};
Compiler.prototype.cclass = function (s) {
var wrapped;
var entryBlock;
var scopename;
var bases;
var decos;
goog.asserts.assert(s instanceof ClassDef);
decos = s.decorator_list;
// decorators and bases need to be eval'd out here
//this.vseqexpr(decos);
bases = this.vseqexpr(s.bases);
scopename = this.enterScope(s.name, s, s.lineno);
entryBlock = this.newBlock("class entry");
this.u.prefixCode = "var " + scopename + "=(function $" + s.name.v + "$class_outer($globals,$locals,$rest){var $gbl=$globals,$loc=$locals;";
this.u.switchCode += "(function $" + s.name.v + "$_closure(){";
this.u.switchCode += "var $blk=" + entryBlock + ",$exc=[],$ret=undefined,$currLineNo=undefined,$currColNo=undefined;"
if (Sk.execLimit !== null) {
this.u.switchCode += "if (typeof Sk.execStart === 'undefined') {Sk.execStart = Date.now()}";
}
if (Sk.yieldLimit !== null && this.u.canSuspend) {
this.u.switchCode += "if (typeof Sk.lastYield === 'undefined') {Sk.lastYield = Date.now()}";
}
this.u.switchCode += "while(true){try{";
this.u.switchCode += this.outputInterruptTest();
this.u.switchCode += "switch($blk){";
this.u.suffixCode = "}}catch(err){ if (!(err instanceof Sk.builtin.BaseException)) { err = new Sk.builtin.ExternalError(err); } err.traceback.push({lineno: $currLineNo, colno: $currColNo, filename: '"+this.filename+"'}); if ($exc.length>0) { $err = err; $blk=$exc.pop(); continue; } else { throw err; }}}"
this.u.suffixCode += "}).apply(null,$rest);});";
this.u.private_ = s.name;
this.cbody(s.body);
out("return;");
// build class
// apply decorators
this.exitScope();
// todo; metaclass
wrapped = this._gr("built", "Sk.misceval.buildClass($gbl,", scopename, ",", s.name["$r"]().v, ",[", bases, "])");
// store our new class under the right name
this.nameop(s.name, Store, wrapped);
};
Compiler.prototype.ccontinue = function (s) {
if (this.u.continueBlocks.length === 0) {
throw new SyntaxError("'continue' outside loop");
}
// todo; continue out of exception blocks
this._jump(this.u.continueBlocks[this.u.continueBlocks.length - 1]);
};
/**
* compiles a statement
*/
Compiler.prototype.vstmt = function (s) {
var i;
var val;
var n;
var debugBlock;
this.u.lineno = s.lineno;
this.u.linenoSet = false;
this.u.localtemps = [];
if (Sk.debugging && this.u.canSuspend) {
debugBlock = this.newBlock("debug breakpoint for line "+s.lineno);
out("if (Sk.breakpoints('"+this.filename+"',"+s.lineno+","+s.col_offset+")) {",
"var $susp = $saveSuspension({data: {type: 'Sk.debug'}, resume: function() {}}, '"+this.filename+"',"+s.lineno+","+s.col_offset+");",
"$susp.$blk = "+debugBlock+";",
"$susp.optional = true;",
"return $susp;",
"}");
this._jump(debugBlock);
this.setBlock(debugBlock);
this.u.doesSuspend = true;
}
this.annotateSource(s);
switch (s.constructor) {
case FunctionDef:
this.cfunction(s);
break;
case ClassDef:
this.cclass(s);
break;
case Return_:
if (this.u.ste.blockType !== FunctionBlock) {
throw new SyntaxError("'return' outside function");
}
if (s.value) {
out("return ", this.vexpr(s.value), ";");
}
else {
out("return Sk.builtin.none.none$;");
}
break;
case Delete_:
this.vseqexpr(s.targets);
break;
case Assign:
n = s.targets.length;
val = this.vexpr(s.value);
for (i = 0; i < n; ++i) {
this.vexpr(s.targets[i], val);
}
break;
case AugAssign:
return this.caugassign(s);
case Print:
this.cprint(s);
break;
case For_:
return this.cfor(s);
case While_:
return this.cwhile(s);
case If_:
return this.cif(s);
case Raise:
return this.craise(s);
case TryExcept:
return this.ctryexcept(s);
case TryFinally:
return this.ctryfinally(s);
case Assert:
return this.cassert(s);
case Import_:
return this.cimport(s);
case ImportFrom:
return this.cfromimport(s);
case Global:
break;
case Expr:
this.vexpr(s.value);
break;
case Pass:
break;
case Break_:
if (this.u.breakBlocks.length === 0) {
throw new SyntaxError("'break' outside loop");
}
this._jump(this.u.breakBlocks[this.u.breakBlocks.length - 1]);
break;
case Continue_:
this.ccontinue(s);
break;
case Debugger_:
out("debugger;");
break;
default:
goog.asserts.fail("unhandled case in vstmt");
}
};
Compiler.prototype.vseqstmt = function (stmts) {
var i;
for (i = 0; i < stmts.length; ++i) {
this.vstmt(stmts[i]);
}
};
var OP_FAST = 0;
var OP_GLOBAL = 1;
var OP_DEREF = 2;
var OP_NAME = 3;
var D_NAMES = 0;
var D_FREEVARS = 1;
var D_CELLVARS = 2;
Compiler.prototype.isCell = function (name) {
var mangled = mangleName(this.u.private_, name).v;
var scope = this.u.ste.getScope(mangled);
var dict = null;
return scope === CELL;
};
/**
* @param {Sk.builtin.str} name
* @param {Object} ctx
* @param {string=} dataToStore
*/
Compiler.prototype.nameop = function (name, ctx, dataToStore) {
var v;
var mangledNoPre;
var dict;
var scope;
var optype;
var op;
var mangled;
if ((ctx === Store || ctx === AugStore || ctx === Del) && name.v === "__debug__") {
throw new Sk.builtin.SyntaxError("can not assign to __debug__");
}
if ((ctx === Store || ctx === AugStore || ctx === Del) && name.v === "None") {
throw new Sk.builtin.SyntaxError("can not assign to None");
}
if (name.v === "None") {
return "Sk.builtin.none.none$";
}
if (name.v === "True") {
return "Sk.builtin.bool.true$";
}
if (name.v === "False") {
return "Sk.builtin.bool.false$";
}
if (name.v === "NotImplemented") {
return "Sk.builtin.NotImplemented.NotImplemented$";
}
mangled = mangleName(this.u.private_, name).v;
// Have to do this before looking it up in the scope
mangled = fixReservedNames(mangled);
op = 0;
optype = OP_NAME;
scope = this.u.ste.getScope(mangled);
dict = null;
switch (scope) {
case FREE:
dict = "$free";
optype = OP_DEREF;
break;
case CELL:
dict = "$cell";
optype = OP_DEREF;
break;
case LOCAL:
// can't do FAST in generators or at module/class scope
if (this.u.ste.blockType === FunctionBlock && !this.u.ste.generator) {
optype = OP_FAST;
}
break;
case GLOBAL_IMPLICIT:
if (this.u.ste.blockType === FunctionBlock) {
optype = OP_GLOBAL;
}
break;
case GLOBAL_EXPLICIT:
optype = OP_GLOBAL;
default:
break;
}
// have to do this after looking it up in the scope
mangled = fixReservedWords(mangled);
//print("mangled", mangled);
// TODO TODO TODO todo; import * at global scope failing here
goog.asserts.assert(scope || name.v.charAt(1) === "_");
// in generator or at module scope, we need to store to $loc, rather that
// to actual JS stack variables.
mangledNoPre = mangled;
if (this.u.ste.generator || this.u.ste.blockType !== FunctionBlock) {
mangled = "$loc." + mangled;
}
else if (optype === OP_FAST || optype === OP_NAME) {
this.u.localnames.push(mangled);
}
switch (optype) {
case OP_FAST:
switch (ctx) {
case Load:
case Param:
// Need to check that it is bound!
out("if (", mangled, " === undefined) { throw new Sk.builtin.UnboundLocalError('local variable \\\'", mangled, "\\\' referenced before assignment'); }\n");
return mangled;
case Store:
out(mangled, "=", dataToStore, ";");
break;
case Del:
out("delete ", mangled, ";");
break;
default:
goog.asserts.fail("unhandled");
}
break;
case OP_NAME:
switch (ctx) {
case Load:
// can't be || for loc.x = 0 or null
return this._gr("loadname", mangled, "!==undefined?", mangled, ":Sk.misceval.loadname('", mangledNoPre, "',$gbl);");
case Store:
out(mangled, "=", dataToStore, ";");
break;
case Del:
out("delete ", mangled, ";");
break;
case Param:
return mangled;
default:
goog.asserts.fail("unhandled");
}
break;
case OP_GLOBAL:
switch (ctx) {
case Load:
return this._gr("loadgbl", "Sk.misceval.loadname('", mangledNoPre, "',$gbl)");
case Store:
out("$gbl.", mangledNoPre, "=", dataToStore, ";");
break;
case Del:
out("delete $gbl.", mangledNoPre);
break;
default:
goog.asserts.fail("unhandled case in name op_global");
}
break;
case OP_DEREF:
switch (ctx) {
case Load:
return dict + "." + mangledNoPre;
case Store:
out(dict, ".", mangledNoPre, "=", dataToStore, ";");
break;
case Param:
return mangledNoPre;
default:
goog.asserts.fail("unhandled case in name op_deref");
}
break;
default:
goog.asserts.fail("unhandled case");
}
};
/**
* @param {Sk.builtin.str} name
* @param {Object} key
* @param {number} lineno
* @param {boolean=} canSuspend
*/
Compiler.prototype.enterScope = function (name, key, lineno, canSuspend) {
var scopeName;
var u = new CompilerUnit();
u.ste = this.st.getStsForAst(key);
u.name = name;
u.firstlineno = lineno;
u.canSuspend = canSuspend || false;
if (this.u && this.u.private_) {
u.private_ = this.u.private_;
}
this.stack.push(this.u);
this.allUnits.push(u);
scopeName = this.gensym("scope");
u.scopename = scopeName;
this.u = u;
this.u.activateScope();
this.nestlevel++;
return scopeName;
};
Compiler.prototype.exitScope = function () {
var mangled;
var prev = this.u;
this.nestlevel--;
if (this.stack.length - 1 >= 0) {
this.u = this.stack.pop();
}
else {
this.u = null;
}
if (this.u) {
this.u.activateScope();
}
if (prev.name.v !== "<module>") {// todo; hacky
mangled = prev.name["$r"]().v;
mangled = mangled.substring(1, mangled.length - 1);
mangled = fixReservedWords(mangled);
mangled = fixReservedNames(mangled);
out(prev.scopename, ".co_name=new Sk.builtins['str']('", mangled, "');");
}
};
Compiler.prototype.cbody = function (stmts) {
var i;
for (i = 0; i < stmts.length; ++i) {
this.vstmt(stmts[i]);
}
};
Compiler.prototype.cprint = function (s) {
var i;
var n;
var dest;
goog.asserts.assert(s instanceof Print);
dest = "null";
if (s.dest) {
dest = this.vexpr(s.dest);
}
n = s.values.length;
// todo; dest disabled
for (i = 0; i < n; ++i) {
out("Sk.misceval.print_(", /*dest, ',',*/ "new Sk.builtins['str'](", this.vexpr(s.values[i]), ").v);");
}
if (s.nl) {
out("Sk.misceval.print_(", /*dest, ',*/ "\"\\n\");");
}
};
Compiler.prototype.cmod = function (mod) {
//print("-----");
//print(Sk.astDump(mod));
var modf = this.enterScope(new Sk.builtin.str("<module>"), mod, 0, this.canSuspend);
var entryBlock = this.newBlock("module entry");
this.u.prefixCode = "var " + modf + "=(function($modname){";
this.u.varDeclsCode =
"var $gbl = {}, $blk=" + entryBlock +
",$exc=[],$loc=$gbl,$err=undefined;$gbl.__name__=$modname;$loc.__file__=new Sk.builtins.str('" + this.filename +
"');var $ret=undefined,$currLineNo=undefined,$currColNo=undefined;";
if (Sk.execLimit !== null) {
this.u.varDeclsCode += "if (typeof Sk.execStart === 'undefined') {Sk.execStart = Date.now()}";
}
if (Sk.yieldLimit !== null && this.u.canSuspend) {
this.u.varDeclsCode += "if (typeof Sk.lastYield === 'undefined') {Sk.lastYield = Date.now()}";
}
this.u.varDeclsCode += "if ("+modf+".$wakingSuspension!==undefined) { $wakeFromSuspension(); }" +
"if (Sk.retainGlobals) {" +
" if (Sk.globals) { $gbl = Sk.globals; Sk.globals = $gbl; $loc = $gbl; }" +
" else { Sk.globals = $gbl; }" +
"} else { Sk.globals = $gbl; }";
// Add the try block that pops the try/except stack if one exists
// Github Issue #38
// Google Code Issue: 109 / 114
// Old code:
//this.u.switchCode = "while(true){switch($blk){";
//this.u.suffixCode = "}}});";
// New Code:
this.u.switchCode = "while(true){try{";
this.u.switchCode += this.outputInterruptTest();
this.u.switchCode += "switch($blk){";
this.u.suffixCode = "}"
this.u.suffixCode += "}catch(err){ if (!(err instanceof Sk.builtin.BaseException)) { err = new Sk.builtin.ExternalError(err); } err.traceback.push({lineno: $currLineNo, colno: $currColNo, filename: '"+this.filename+"'}); if ($exc.length>0) { $err = err; $blk=$exc.pop(); continue; } else { throw err; }} } });";
// Note - this change may need to be adjusted for all the other instances of
// switchCode and suffixCode in this file. Not knowing how to test those
// other cases I left them alone. At least the changes to
// setupExcept and endExcept will insure that the generated JavaScript
// will be syntactically correct. The worst that will happen is that when
// code in a try block blows up, we will not know to run the except block.
// The other problem is that we might catch something that is really an internal
// error - it might be nice to add code in the above catch block that looked at
// the kind of exception and only popped the stack for exceptions that are
// from the original code rather than artifacts of some code generation or
// exeution environment error. We at least err on the side of exceptions
// being revealed to the user. drchuck - Wed Jan 23 19:20:18 EST 2013
switch (mod.constructor) {
case Module:
this.cbody(mod.body);
out("return $loc;");
break;
default:
goog.asserts.fail("todo; unhandled case in compilerMod");
}
this.exitScope();
this.result.push(this.outputAllUnits());
return modf;
};
/**
* @param {string} source the code
* @param {string} filename where it came from
* @param {string} mode one of 'exec', 'eval', or 'single'
* @param {boolean=} canSuspend if the generated code supports suspension
*/
Sk.compile = function (source, filename, mode, canSuspend) {
//print("FILE:", filename);
var parse = Sk.parse(filename, source);
var ast = Sk.astFromParse(parse.cst, filename, parse.flags);
// compilers flags, later we can add other ones too
var flags = {};
flags.cf_flags = parse.flags;
var st = Sk.symboltable(ast, filename);
var c = new Compiler(filename, st, flags.cf_flags, canSuspend, source); // todo; CO_xxx
var funcname = c.cmod(ast);
var ret = "$compiledmod = function() {" + c.result.join("") + "\nreturn " + funcname + ";}();";
return {
funcname: "$compiledmod",
code : ret
};
};
goog.exportSymbol("Sk.compile", Sk.compile);
Sk.resetCompiler = function () {
Sk.gensymcount = 0;
};
goog.exportSymbol("Sk.resetCompiler", Sk.resetCompiler);