diff --git a/LICENSE b/LICENSE index bf7ea8c605..adfc24dead 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2010 Scott Graham +Copyright (c) 2009-2016 Scott Graham and contributors to the Skulpt Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25,7 +25,7 @@ Portions of the code were written with the benefit of viewing code that's in the official "CPython" and "pypy" distribution and/or translated from code that's in the official "CPython" and "pypy" distribution. As such, they are: - Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software + Copyright (c) 2001 -- 2016 Python Software Foundation; All Rights Reserved" per: diff --git a/skulpt.py b/skulpt.py index ab50139fd2..f7a0d6929d 100755 --- a/skulpt.py +++ b/skulpt.py @@ -69,6 +69,7 @@ def bowerProperty(name): ('support/closure-library/closure/goog/debug/error.js', FILE_TYPE_DIST), ('support/closure-library/closure/goog/asserts/asserts.js', FILE_TYPE_DIST), ('support/es6-promise-polyfill/promise-1.0.0.hacked.js', FILE_TYPE_DIST), + 'support/setImmediate/setImmediate.js', 'src/env.js', 'src/type.js', 'src/abstract.js', @@ -729,11 +730,19 @@ def dist(options): builtinfn = os.path.join(DIST_DIR, OUTFILE_LIB) debuggerfn = os.path.join(DIST_DIR, OUTFILE_DEBUGGER) + ret = test() + # Run tests on uncompressed. if options.verbose: - print ". Running tests on uncompressed..." + print ". Re-Running tests on uncompressed... with debug mode on to find suspension errors." - ret = test() + # turn the tests in debug mode off because they take too long + # # Run tests on uncompressed. + # if options.verbose: + # print ". Re-Running tests on uncompressed... with debug mode on to find suspension errors." + # + # + # ret = test(debug_mode=True) # turn the tests in debug mode off because they take too long # # Run tests on uncompressed. @@ -881,7 +890,7 @@ def run_in_browser(fn, options, debug_mode=False): with open('support/run_template.html') as tpfile: page = tpfile.read() - page = page % dict(code=prog,scripts=scripts,debug_mode=str(debug_mode).lower()) + page = page % dict(code=prog,scripts=scripts,debug_mode=str(debug_mode).lower(),root="") with open("{0}/run.html".format(RUN_DIR),"w") as htmlfile: htmlfile.write(page) diff --git a/src/builtin.js b/src/builtin.js index 532793d927..aad8b9c423 100644 --- a/src/builtin.js +++ b/src/builtin.js @@ -245,7 +245,7 @@ Sk.builtin.len = function len (item) { var int_ = function(i) { return new Sk.builtin.int_(i); }; if (item.sq$length) { - return Sk.misceval.chain(item.sq$length(), int_); + return Sk.misceval.chain(item.sq$length(true), int_); } if (item.mp$length) { @@ -559,7 +559,8 @@ Sk.builtin.dir = function dir (x) { var internal = [ "__bases__", "__mro__", "__class__", "__name__", "GenericGetAttr", "GenericSetAttr", "GenericPythonGetAttr", "GenericPythonSetAttr", - "pythonFunctions", "HashNotImplemented", "constructor"]; + "pythonFunctions", "HashNotImplemented", "constructor", "__dict__" + ]; if (internal.indexOf(k) !== -1) { return null; } @@ -670,10 +671,13 @@ Sk.builtin.repr = function repr (x) { Sk.builtin.open = function open (filename, mode, bufsize) { Sk.builtin.pyCheckArgs("open", arguments, 1, 3); if (mode === undefined) { - mode = new Sk.builtin.str("r"); + mode = new Sk.builtin.str("r"); } - return new Sk.builtin.file(filename, mode, bufsize); + + /*if (mode.v !== "r" && mode.v !== "b" && !Sk.nonreadopen) { + throw new Sk.builtin.ValueError("Hinweis: Bitte verwenden Sie PyCharm für dieses Beispiel. Das Schreiben von Dateien wird derzeit nicht online unterstützt."); + }*/ }; Sk.builtin.isinstance = function isinstance (obj, type) { @@ -754,11 +758,11 @@ Sk.builtin.hash = function hash (value) { value.$savedHash_ = value.tp$hash(); return value.$savedHash_; } else { - if (value.__id === undefined) { + if (value.__hash === undefined) { Sk.builtin.hashCount += 1; - value.__id = Sk.builtin.hashCount; + value.__hash = Sk.builtin.hashCount; } - return new Sk.builtin.int_(value.__id); + return new Sk.builtin.int_(value.__hash); } } else if (typeof value === "number" || value === null || value === true || value === false) { @@ -788,24 +792,31 @@ Sk.builtin.getattr = function getattr (obj, name, default_) { }; Sk.builtin.setattr = function setattr (obj, name, value) { - Sk.builtin.pyCheckArgs("setattr", arguments, 3, 3); - if (!Sk.builtin.checkString(name)) { - throw new Sk.builtin.TypeError("attribute name must be string"); - } - if (obj.tp$setattr) { - obj.tp$setattr(Sk.ffi.remapToJs(name), value); - } else { - throw new Sk.builtin.AttributeError("object has no attribute " + Sk.ffi.remapToJs(name)); + // cannot set or del attr from builtin type + if (obj === undefined || obj["$r"] === undefined || obj["$r"]().v.slice(1,5) !== "type") { + if (!Sk.builtin.checkString(name)) { + throw new Sk.builtin.TypeError("attribute name must be string"); + } + if (obj.tp$setattr) { + obj.tp$setattr(Sk.ffi.remapToJs(name), value); + } else { + throw new Sk.builtin.AttributeError("object has no attribute " + Sk.ffi.remapToJs(name)); + } + return Sk.builtin.none.none$; } - return Sk.builtin.none.none$; + throw new Sk.builtin.TypeError("can't set attributes of built-in/extension type '" + obj.tp$name + "'"); }; Sk.builtin.raw_input = function (prompt) { var sys = Sk.importModule("sys"); var lprompt = prompt ? prompt : ""; + if (Sk.inputfunTakesPrompt) { + return Sk.misceval.callsimOrSuspend(Sk.builtin.file.$readline, sys["$d"]["stdin"], null, lprompt); + } + return Sk.misceval.chain(undefined, function () { return Sk.misceval.callsimOrSuspend(sys["$d"]["stdout"]["write"], sys["$d"]["stdout"], new Sk.builtin.str(prompt)); }, function () { @@ -994,6 +1005,7 @@ Sk.builtin.filter = function filter (fun, iterable) { Sk.builtin.hasattr = function hasattr (obj, attr) { Sk.builtin.pyCheckArgs("hasattr", arguments, 2, 2); + var special, ret; if (!Sk.builtin.checkString(attr)) { throw new Sk.builtin.TypeError("hasattr(): attribute name must be string"); } @@ -1002,14 +1014,32 @@ Sk.builtin.hasattr = function hasattr (obj, attr) { if (obj.tp$getattr(attr.v)) { return Sk.builtin.bool.true$; } else { - return Sk.builtin.bool.false$; + special = Sk.abstr.lookupSpecial(obj, "__getattr__"); + if (special) { + ret = Sk.misceval.tryCatch(function () { + var val = Sk.misceval.callsim(special, obj, attr); + if (val) { + return Sk.builtin.bool.true$; + } else { + return Sk.builtin.bool.false$; + } + }, function(e) { + if (e instanceof Sk.builtin.AttributeError) { + return Sk.builtin.bool.false$; + } else { + throw e; + } + }); + return ret; + } else { + return Sk.builtin.bool.false$; + } } } else { throw new Sk.builtin.AttributeError("Object has no tp$getattr method"); } }; - Sk.builtin.pow = function pow (a, b, c) { var ret; var res; @@ -1207,6 +1237,17 @@ Sk.builtin.reversed = function reversed (seq) { } }; +Sk.builtin.id = function (obj) { + Sk.builtin.pyCheckArgs("id", arguments, 1, 1); + + if (obj.__id === undefined) { + Sk.builtin.idCount += 1; + obj.__id = Sk.builtin.idCount; + } + + return new Sk.builtin.int_(obj.__id); +}; + Sk.builtin.bytearray = function bytearray () { throw new Sk.builtin.NotImplementedError("bytearray is not yet implemented"); }; @@ -1221,9 +1262,37 @@ Sk.builtin.callable = function callable (obj) { return Sk.builtin.bool.false$; }; -Sk.builtin.delattr = function delattr () { - throw new Sk.builtin.NotImplementedError("delattr is not yet implemented"); +Sk.builtin.delattr = function delattr (obj, attr) { + Sk.builtin.pyCheckArgs("delattr", arguments, 2, 2); + if (obj["$d"][attr.v] !== undefined) { + var ret = Sk.misceval.tryCatch(function() { + var try1 = Sk.builtin.setattr(obj, attr, undefined); + return try1; + }, function(e) { + Sk.misceval.tryCatch(function() { + var try2 = Sk.builtin.setattr(obj["$d"], attr, undefined); + + return try2; + }, function(e) { + if (e instanceof Sk.builtin.AttributeError) { + throw new Sk.builtin.AttributeError(Sk.abstr.typeName(obj) + " instance has no attribute '"+ attr.v+ "'"); + } else { + throw e; + } + }); + }); + return ret; + } // cannot set or del attr from builtin type + if (obj["$r"]().v.slice(1,5) !== "type") { + if (obj.ob$type === Sk.builtin.type && obj[attr.v] !== undefined) { + obj[attr.v] = undefined; + return Sk.builtin.none.none$; + } + throw new Sk.builtin.AttributeError(Sk.abstr.typeName(obj) + " instance has no attribute '"+ attr.v+ "'"); + } + throw new Sk.builtin.TypeError("can't set attributes of built-in/extension type '" + obj.tp$name + "'"); }; + Sk.builtin.execfile = function execfile () { throw new Sk.builtin.NotImplementedError("execfile is not yet implemented"); }; @@ -1255,7 +1324,19 @@ Sk.builtin.iter = function iter (obj, sentinel) { }; Sk.builtin.locals = function locals () { - throw new Sk.builtin.NotImplementedError("locals is not yet implemented"); + /*global Sk.locals */ + if (Sk["locals"] != null) { + var localsDict = []; + for (var prop in Sk["locals"]) { + if (Sk["locals"].hasOwnProperty(prop)) { + localsDict.push(new Sk.builtin.str(prop)); + localsDict.push(Sk["locals"][prop]); + } + } + return new Sk.builtin.dict(localsDict); + } else { + throw new Sk.builtin.NotImplementedError("locals is not yet implemented"); + } }; Sk.builtin.memoryview = function memoryview () { throw new Sk.builtin.NotImplementedError("memoryview is not yet implemented"); @@ -1298,7 +1379,6 @@ Sk.builtin.intern = function intern () { throw new Sk.builtin.NotImplementedError("intern is not yet implemented"); }; - /* Sk.builtinFiles = {}; Sk.builtin.read = function read(x) { diff --git a/src/builtin/sys.js b/src/builtin/sys.js index 13e32ec160..93fd764c23 100644 --- a/src/builtin/sys.js +++ b/src/builtin/sys.js @@ -64,6 +64,25 @@ var $builtinmodule = function (name) { return Sk.builtin.none.none$; }); + sys.exit = new Sk.builtin.func(function(arg) { + Sk.builtin.pyCheckArgs("exit", arguments, 0, 1); + var exit_status; + if (arg == null || Sk.builtin.checkNone(arg)) { + exit_status = 0 + } else if (Sk.builtin.checkInt(arg)){ + exit_status = Sk.ffi.remapToJs(arg); + } else { + exit_status = 1; + } + + if (Sk.builtin.checkString(arg)) { + // print arg to console + Sk.output(Sk.ffi.remapToJs(arg)) + } + + throw new Sk.builtin.SystemExit(exit_status); + }); + sys.__stdout__ = new Sk.builtin.file(new Sk.builtin.str("/dev/stdout"), "w"); sys.__stdin__ = new Sk.builtin.file(new Sk.builtin.str("/dev/stdin"), "r"); diff --git a/src/builtindict.js b/src/builtindict.js index 6c9d934cb7..6b1e5c9b75 100644 --- a/src/builtindict.js +++ b/src/builtindict.js @@ -25,6 +25,7 @@ Sk.builtins = { "float_$rw$": Sk.builtin.float_, "int_$rw$" : Sk.builtin.int_, "hasattr" : Sk.builtin.hasattr, + "id" : Sk.builtin.id, "map" : Sk.builtin.map, "filter": Sk.builtin.filter, diff --git a/src/classmethod.py b/src/classmethod.py new file mode 100644 index 0000000000..b27b0ce6c3 --- /dev/null +++ b/src/classmethod.py @@ -0,0 +1,12 @@ +class classmethod(object): + "Emulate PyClassMethod_Type() in Objects/funcobject.c" + + def __init__(self, f): + self.f = f + + def __get__(self, obj, klass=None): + if klass is None: + klass = type(obj) + def newfunc(*args): + return self.f(klass, *args) + return newfunc diff --git a/src/compile.js b/src/compile.js index f5b72d97db..7aeae00859 100644 --- a/src/compile.js +++ b/src/compile.js @@ -108,6 +108,7 @@ Compiler.prototype.annotateSource = function (ast) { out("^\n//\n"); out("$currLineNo = ", lineno, ";\n$currColNo = ", col_offset, ";\n\n"); + //out("Sk.locals = $loc;\n\n"); } }; @@ -1092,7 +1093,23 @@ Compiler.prototype.cwhile = function (s) { this.pushContinueBlock(top); this.setBlock(body); + + if ((Sk.debugging || Sk.killableWhile) && this.u.canSuspend) { + var suspType = 'Sk.delay'; + var 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: '"+suspType+"'}, 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.vseqstmt(s.body); + this._jump(top); this.popContinueBlock(); @@ -1146,6 +1163,20 @@ Compiler.prototype.cfor = function (s) { this._jumpundef(nexti, cleanup); // todo; this should be handled by StopIteration target = this.vexpr(s.target, nexti); + if ((Sk.debugging || Sk.killableFor) && this.u.canSuspend) { + var suspType = 'Sk.delay'; + var 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: '"+suspType+"'}, 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; + } + // execute body this.vseqstmt(s.body); @@ -1471,9 +1502,14 @@ Compiler.prototype.buildcodeobj = function (n, coname, decorator_list, args, cal } } if (hasFree) { - funcArgs.push("$free"); - this.u.tempsToSave.push("$free"); + 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 += "){"; @@ -1565,8 +1601,9 @@ Compiler.prototype.buildcodeobj = function (n, coname, decorator_list, args, cal // 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 + ")); /*vararg*/"; + this.u.varDeclsCode += vararg.v + "=new Sk.builtins['tuple'](Array.prototype.slice.call(arguments," + start + (hasFree ? ",-1)" : ")") + "); /*vararg*/"; } // @@ -2263,6 +2300,7 @@ Compiler.prototype.cmod = function (mod) { this.u.varDeclsCode += "if ("+modf+".$wakingSuspension!==undefined) { $wakeFromSuspension(); }" + "if (Sk.retainGlobals) {" + " if (Sk.globals) { $gbl = Sk.globals; Sk.globals = $gbl; $loc = $gbl; }" + + " if (Sk.globals) { $gbl = Sk.globals; Sk.globals = $gbl; $loc = $gbl; $gbl.__name__=$modname;$loc.__file__=new Sk.builtins.str('" + this.filename + "');}" + " else { Sk.globals = $gbl; }" + "} else { Sk.globals = $gbl; }"; diff --git a/src/env.js b/src/env.js index d68d7410e0..0745bcba09 100644 --- a/src/env.js +++ b/src/env.js @@ -32,7 +32,19 @@ Sk.configure = function (options) { Sk.read = options["read"] || Sk.read; goog.asserts.assert(typeof Sk.read === "function"); - + + Sk.nonreadopen = options.nonreadopen || false; + goog.asserts.assert("boolean" === typeof Sk.nonreadopen); + + Sk.fileopen = options.fileopen || undefined; + goog.asserts.assert("function" === typeof Sk.fileopen || "undefined" === typeof Sk.fileopen); + + Sk.filewrite = options.filewrite || undefined; + goog.asserts.assert("function" === typeof Sk.filewrite || "undefined" === typeof Sk.filewrite); + + Sk.fileread = options.fileread || undefined; + goog.asserts.assert("function" === typeof Sk.fileread || "undefined" === typeof Sk.fileread); + Sk.timeoutMsg = options["timeoutMsg"] || Sk.timeoutMsg; goog.asserts.assert(typeof Sk.timeoutMsg === "function"); goog.exportSymbol("Sk.timeoutMsg", Sk.timeoutMsg); @@ -49,12 +61,21 @@ Sk.configure = function (options) { Sk.inputfun = options["inputfun"] || Sk.inputfun; goog.asserts.assert(typeof Sk.inputfun === "function"); + Sk.inputfunTakesPrompt = options["inputfunTakesPrompt"] || false; + goog.asserts.assert(typeof Sk.inputfunTakesPrompt === "boolean"); + Sk.retainGlobals = options["retainglobals"] || false; goog.asserts.assert(typeof Sk.retainGlobals === "boolean"); Sk.debugging = options["debugging"] || false; goog.asserts.assert(typeof Sk.debugging === "boolean"); + Sk.killableWhile = options["killableWhile"] || false; + goog.asserts.assert(typeof Sk.killableWhile === "boolean"); + + Sk.killableFor = options["killableFor"] || false; + goog.asserts.assert(typeof Sk.killableFor === "boolean"); + Sk.breakpoints = options["breakpoints"] || function() { return true; }; goog.asserts.assert(typeof Sk.breakpoints === "function"); diff --git a/src/errors.js b/src/errors.js index 8978620611..9b7892d2b0 100644 --- a/src/errors.js +++ b/src/errors.js @@ -213,6 +213,25 @@ Sk.builtin.KeyError = function (args) { }; Sk.abstr.setUpInheritance("KeyError", Sk.builtin.KeyError, Sk.builtin.StandardError); +/** +* Is thrown when the execution has been stopped by Ctrl+C or Del. The compiler +* checks for an Sk.keyboardinterrupt flag, if set the exception is thrown. +* @constructor +* @extends Sk.builtin.BaseException +* @param {...*} args +* +*/ +Sk.builtin.KeyboardInterrupt = function (args) { + var o; + if (!(this instanceof Sk.builtin.KeyboardInterrupt)) { + o = Object.create(Sk.builtin.KeyboardInterrupt.prototype); + o.constructor.apply(o, arguments); + return o; + } + Sk.builtin.BaseException.apply(this, arguments); +}; +Sk.abstr.setUpInheritance("KeyboardInterrupt", Sk.builtin.KeyboardInterrupt, Sk.builtin.BaseException); + /** * @constructor * @extends Sk.builtin.StandardError diff --git a/src/file.js b/src/file.js index 9e3f4a7740..0b0afc597c 100644 --- a/src/file.js +++ b/src/file.js @@ -12,7 +12,7 @@ Sk.builtin.file = function (name, mode, buffering) { return new Sk.builtin.file(name, mode, buffering); } - this.mode = mode; + this.mode = Sk.ffi.remapToJs(mode); this.name = Sk.ffi.remapToJs(name); this.closed = false; @@ -24,35 +24,38 @@ Sk.builtin.file = function (name, mode, buffering) { } else if (this.name === "/dev/stderr") { this.fileno = 2; } else { - if (Sk.inBrowser) { // todo: Maybe provide a replaceable function for non-import files - this.fileno = 10; - elem = document.getElementById(name.v); - if (elem == null) { + if (Sk.inBrowser) { + this.fileno = 11; + this.data$ = Sk["fileread"](this.name, this.mode); + + if (mode.v === "x" && this.data$ != null) { + throw new Sk.builtin.IOError("[Errno 2] File already exists: '" + name.v + "'"); + } + + if (this.data$ == null) { throw new Sk.builtin.IOError("[Errno 2] No such file or directory: '" + name.v + "'"); - } else { - if (elem.nodeName.toLowerCase() == "textarea") { - this.data$ = elem.value; - } else { - this.data$ = elem.textContent; - } } + + this.lineList = this.data$.split("\n"); + this.lineList = this.lineList.slice(0, -1); + + for (i in this.lineList) { + this.lineList[i] = this.lineList[i] + "\n"; + } + this.currentLine = 0; } else { this.fileno = 11; this.data$ = Sk.read(name.v); - } - - this.lineList = this.data$.split("\n"); - this.lineList = this.lineList.slice(0, -1); - for (i in this.lineList) { - this.lineList[i] = this.lineList[i] + "\n"; + if (this.data$ == null) { + throw new Sk.builtin.IOError("[Errno 2] No such file or directory: '" + name.v + "'"); + } } - this.currentLine = 0; } this.pos$ = 0; this.__class__ = Sk.builtin.file; - + Sk.fileopen && Sk.fileopen(this); return this; }; @@ -122,12 +125,15 @@ Sk.builtin.file.prototype["read"] = new Sk.builtin.func(function (self, size) { return ret; }); -Sk.builtin.file.prototype["readline"] = new Sk.builtin.func(function (self, size) { +Sk.builtin.file.$readline = function (self, size, prompt) { if (self.fileno === 0) { var x, resolution, susp; - var prompt = prompt ? prompt.v : ""; - x = Sk.inputfun(prompt); + var lprompt = prompt != null ? Sk.ffi.remapToJs(prompt) : ""; + + lprompt = lprompt ? lprompt : ""; + + x = Sk.inputfun(lprompt); if (x instanceof Promise) { susp = new Sk.misceval.Suspension(); @@ -159,6 +165,10 @@ Sk.builtin.file.prototype["readline"] = new Sk.builtin.func(function (self, size } return new Sk.builtin.str(line); } +}; + +Sk.builtin.file.prototype["readline"] = new Sk.builtin.func(function (self, size) { + return Sk.builtin.file.$readline(self, size, undefined); }); Sk.builtin.file.prototype["readlines"] = new Sk.builtin.func(function (self, sizehint) { @@ -194,13 +204,21 @@ Sk.builtin.file.prototype["truncate"] = new Sk.builtin.func(function (self, size goog.asserts.fail(); }); -Sk.builtin.file.prototype["write"] = new Sk.builtin.func(function (self, str) { +Sk.builtin.file.prototype.write = new Sk.builtin.func(function(self, str) { if (self.fileno === 1) { Sk.output(Sk.ffi.remapToJs(str)); + } else if (Sk.filewrite) { + if (self.closed) { + throw new Sk.builtin.ValueError("I/O operation on closed file"); + } + if ("w" === self.mode || "b" === self.mode || "a" === self.mode || "a+" === self.mode || "w+" === self.mode) { + Sk.filewrite(self, Sk.ffi.remapToJs(str)); + } else { + throw new Sk.builtin.IOError("File not open for writing"); + } } else { goog.asserts.fail(); } }); - goog.exportSymbol("Sk.builtin.file", Sk.builtin.file); diff --git a/src/float.js b/src/float.js index 3c8a1cc450..547dacb530 100644 --- a/src/float.js +++ b/src/float.js @@ -617,6 +617,11 @@ Sk.builtin.float_.prototype.nb$negative = function () { /** @override */ Sk.builtin.float_.prototype.nb$positive = function () { + // -0 -> 0 (but JavaScript behaves differently) + if (Sk.builtin.isNegativeZero(this.v)) { + return new Sk.builtin.float_(0); + } + return this.clone(); }; @@ -875,7 +880,7 @@ Sk.builtin.float_.prototype.str$ = function (base, sign) { } // restore negative zero sign - if(this.v === 0 && 1/this.v === -Infinity) { + if(Sk.builtin.isNegativeZero(this.v)) { tmp = "-" + tmp; } diff --git a/src/function.js b/src/function.js index 5964efe9b8..36698eecbc 100644 --- a/src/function.js +++ b/src/function.js @@ -113,6 +113,16 @@ Sk.builtin.checkCallable = function (obj) { return false; }; +Sk.builtin.isNegativeZero = function (arg) { + var val = Sk.builtin.asnum$(arg); + if (1 / val === Number.NEGATIVE_INFINITY) { + return true; + } + + return false; +}; +goog.exportSymbol("Sk.builtin.isNegativeZero", Sk.builtin.isNegativeZero); + Sk.builtin.checkNumber = function (arg) { return (arg !== null && (typeof arg === "number" || arg instanceof Sk.builtin.int_ || @@ -205,6 +215,13 @@ Sk.builtin.func = function (code, globals, closure, closure2) { } } this.func_closure = closure; + + if (Sk["builtin"] != null && Sk["builtin"]["str"] != null) { + this.__name__ = Object.create(Sk.builtin.str.prototype); + this.__name__.__class__ = Sk.builtin.str; + this.__name__.v = (this.func_code && this.func_code["co_name"] && this.func_code["co_name"].v) || ""; + } + return this; }; goog.exportSymbol("Sk.builtin.func", Sk.builtin.func); @@ -216,7 +233,7 @@ Sk.builtin.func.prototype.tp$descr_get = function (obj, objtype) { if (obj == null) { return this; } - return new Sk.builtin.method(this, obj); + return new Sk.builtin.method(this, obj, objtype); }; Sk.builtin.func.prototype.tp$call = function (args, kw) { var j; @@ -241,6 +258,7 @@ Sk.builtin.func.prototype.tp$call = function (args, kw) { args.push(undefined); } } + args.push(this.func_closure); } diff --git a/src/import.js b/src/import.js index 3f2cbc4c87..c31e21595c 100644 --- a/src/import.js +++ b/src/import.js @@ -218,6 +218,9 @@ Sk.doOneTimeInitialization = function () { if ((func.prototype instanceof Sk.builtin.object || func === Sk.builtin.object) && !func.sk$abstract) { setUpClass(func); + + // add __name__ for builtins, Sk.builtin.str may not be available + func["$d"].mp$ass_subscript(new Sk.builtin.str("__name__"), new Sk.builtin.str(x)); } } diff --git a/src/int.js b/src/int.js index e95395e5a4..1fbabea9ba 100644 --- a/src/int.js +++ b/src/int.js @@ -128,6 +128,8 @@ Sk.builtin.int_ = function (x, base) { return this; }; +Sk.builtin.int_.$shiftconsts = [0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296, 8589934592, 17179869184, 34359738368, 68719476736, 137438953472, 274877906944, 549755813888, 1099511627776, 2199023255552, 4398046511104, 8796093022208, 17592186044416, 35184372088832, 70368744177664, 140737488355328, 281474976710656, 562949953421312, 1125899906842624, 2251799813685248, 4503599627370496, 9007199254740992]; + Sk.abstr.setUpInheritance("int", Sk.builtin.int_, Sk.builtin.numtype); /* NOTE: See constants used for kwargs in constants.js */ @@ -624,6 +626,10 @@ Sk.builtin.int_.prototype.nb$reflected_xor = Sk.builtin.int_.prototype.nb$xor; Sk.builtin.int_.prototype.nb$lshift = function (other) { var thisAsLong; + if (this.v === 0) { + return this; + } + if (other instanceof Sk.builtin.int_) { var tmp; var shift = Sk.builtin.asnum$(other); @@ -632,10 +638,15 @@ Sk.builtin.int_.prototype.nb$lshift = function (other) { if (shift < 0) { throw new Sk.builtin.ValueError("negative shift count"); } - tmp = this.v << shift; - if (tmp <= this.v) { + + if (shift > 53) { + return new Sk.builtin.lng(this.v).nb$lshift(new Sk.builtin.int_(shift)); + } + + tmp = this.v * 2 * Sk.builtin.int_.$shiftconsts[shift]; + if (tmp > Sk.builtin.int_.threshold$ || tmp < -Sk.builtin.int_.threshold$) { // Fail, recompute with longs - return new Sk.builtin.lng(this.v).nb$lshift(other); + return new Sk.builtin.lng(tmp); } } diff --git a/src/lib/copy.py b/src/lib/copy.py index a880b71c70..3901c8ffb4 100644 --- a/src/lib/copy.py +++ b/src/lib/copy.py @@ -1 +1,286 @@ -raise NotImplementedError("copy is not yet implemented in Skulpt") +""" +This file was modified from CPython. +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved +""" +import types +class Error(Exception): + pass +error = Error +class _EmptyClass: + pass + +def copy(x): + cls = type(x) + if callable(x): + return x + copier = getattr(cls, "__copy__", None) + if copier: + return copier(x) + if cls in (type(None), int, float, bool, long, str, tuple, type): + return x + if (cls == list) or (cls == dict) or (cls == set) or (cls == slice): + return cls(x) + try: + getstate = getattr(x, "__getstate__", None) + setstate = getattr(x, "__setstate__", None) + initargs = getattr(x, "__getinitargs__", None) + except: + reductor = False + if getstate or setstate or initargs: + raise NotImplementedError("Skulpt does not yet support copying with user-defined __getstate__, __setstate__ or __getinitargs__()") + reductor = getattr(x, "__reduce_ex__", None) + if reductor: + rv = reductor(4) + else: + reductor = getattr(x, "__reduce__", None) + if reductor: + rv = reductor() + elif str(cls)[1:6] == "class": + copier = _copy_inst + return copier(x) + else: + raise Error("un(shallow)copyable object of type %s" % cls) + if isinstance(rv, str): + return x + return _reconstruct(x, rv, 0) + +def _copy_inst(x): + if hasattr(x, '__copy__'): + return x.__copy__() + if hasattr(x, '__getinitargs__'): + args = x.__getinitargs__() + y = x.__class__(*args) + else: + y = _EmptyClass() + y.__class__ = x.__class__ + if hasattr(x, '__getstate__'): + state = x.__getstate__() + else: + state = x.__dict__ + if hasattr(y, '__setstate__'): + y.__setstate__(state) + else: + y.__dict__.update(state) + return y + +d = _deepcopy_dispatch = {} + +def deepcopy(x, memo=None, _nil=[]): + """Deep copy operation on arbitrary Python objects. + See the module's __doc__ string for more info. + """ + if memo is None: + memo = {} + idx = id(x) + y = memo.get(idx, _nil) + if y is not _nil: + return y + cls = type(x) + try: + getstate = getattr(x, "__getstate__", None) + setstate = getattr(x, "__setstate__", None) + initargs = getattr(x, "__getinitargs__", None) + except: + reductor = False + if getstate or setstate or initargs: + raise NotImplementedError("Skulpt does not yet support copying with user-defined __getstate__, __setstate__ or __getinitargs__()") + copier = _deepcopy_dispatch.get(cls) + if copier: + y = copier(x, memo) + elif str(cls)[1:6] == "class": + copier = _deepcopy_dispatch["InstanceType"] + y = copier(x, memo) + else: + try: + issc = issubclass(cls, type) + except TypeError: # cls is not a class (old Boost; see SF #502085) + issc = 0 + if issc: + y = _deepcopy_atomic(x, memo) + else: + copier = getattr(x, "__deepcopy__", None) + if copier: + y = copier(memo) + else: + reductor = getattr(x, "__reduce_ex__", None) + if reductor: + rv = reductor(2) + else: + reductor = getattr(x, "__reduce__", None) + if reductor: + rv = reductor() + else: + raise Error( + "un(deep)copyable object of type %s" % cls) + y = _reconstruct(x, rv, 1, memo) + memo[idx] = y + _keep_alive(x, memo) # Make sure x lives at least as long as d + return y + +def _deepcopy_atomic(x, memo): + return x +d[type(None)] = _deepcopy_atomic +# d[type(Ellipsis)] = _deepcopy_atomic +d[type(NotImplemented)] = _deepcopy_atomic +d[int] = _deepcopy_atomic +d[float] = _deepcopy_atomic +d[bool] = _deepcopy_atomic +d[complex] = _deepcopy_atomic +# d[bytes] = _deepcopy_atomic +d[str] = _deepcopy_atomic +# try: +# d[types.CodeType] = _deepcopy_atomic +# except AttributeError: +# pass +d[type] = _deepcopy_atomic +# d[types.BuiltinFunctionType] = _deepcopy_atomic +d[types.FunctionType] = _deepcopy_atomic +# d[weakref.ref] = _deepcopy_atomic + +def _deepcopy_list(x, memo): + y = [] + memo[id(x)] = y + for a in x: + y.append(deepcopy(a, memo)) + return y +d[list] = _deepcopy_list + +def _deepcopy_set(x, memo): + result = set([]) # make empty set + memo[id(x)] = result # register this set in the memo for loop checking + for a in x: # go through elements of set + result.add(deepcopy(a, memo)) # add the copied elements into the new set + return result # return the new set +d[set] = _deepcopy_set + +def _deepcopy_tuple(x, memo): + y = [deepcopy(a, memo) for a in x] + # We're not going to put the tuple in the memo, but it's still important we + # check for it, in case the tuple contains recursive mutable structures. + try: + return memo[id(x)] + except KeyError: + pass + for k, j in zip(x, y): + if k is not j: + y = tuple(y) + break + else: + y = x + return y +d[tuple] = _deepcopy_tuple + +def _deepcopy_dict(x, memo): + y = {} + memo[id(x)] = y + for key, value in x.items(): + y[deepcopy(key, memo)] = deepcopy(value, memo) + return y +d[dict] = _deepcopy_dict + +def _deepcopy_method(x, memo): # Copy instance methods + y = type(x)(x.im_func, deepcopy(x.im_self, memo), x.im_class); + return y +d[types.MethodType] = _deepcopy_method + +def _deepcopy_inst(x, memo): + if hasattr(x, '__deepcopy__'): + return x.__deepcopy__(memo) + if hasattr(x, '__getinitargs__'): + args = x.__getinitargs__() + args = deepcopy(args, memo) + y = x.__class__(*args) + else: + y = _EmptyClass() + y.__class__ = x.__class__ + memo[id(x)] = y + if hasattr(x, '__getstate__'): + state = x.__getstate__() + else: + state = x.__dict__ + state = deepcopy(state, memo) + if hasattr(y, '__setstate__'): + y.__setstate__(state) + else: + y.__dict__.update(state) + return y +d["InstanceType"] = _deepcopy_inst + +def _keep_alive(x, memo): + """Keeps a reference to the object x in the memo. + Because we remember objects by their id, we have + to assure that possibly temporary objects are kept + alive by referencing them. + We store a reference at the id of the memo, which should + normally not be used unless someone tries to deepcopy + the memo itself... + """ + try: + memo[id(memo)].append(x) + except KeyError: + # aha, this is the first one :-) + memo[id(memo)]=[x] + +def _reconstruct(x, info, deep, memo=None): + if isinstance(info, str): + return x + assert isinstance(info, tuple) + if memo is None: + memo = {} + n = len(info) + assert n in (2, 3, 4, 5) + callable, args = info[:2] + if n > 2: + state = info[2] + else: + state = None + if n > 3: + listiter = info[3] + else: + listiter = None + if n > 4: + dictiter = info[4] + else: + dictiter = None + if deep: + args = deepcopy(args, memo) + y = callable(*args) + memo[id(x)] = y + + if state is not None: + if deep: + state = deepcopy(state, memo) + if hasattr(y, '__setstate__'): + y.__setstate__(state) + else: + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + else: + slotstate = None + if state is not None: + y.__dict__.update(state) + if slotstate is not None: + for key, value in slotstate.items(): + setattr(y, key, value) + + if listiter is not None: + for item in listiter: + if deep: + item = deepcopy(item, memo) + y.append(item) + if dictiter is not None: + for key, value in dictiter: + if deep: + key = deepcopy(key, memo) + value = deepcopy(value, memo) + y[key] = value + return y + +del d + +del types + +# Helper for instance creation without calling __init__ +class _EmptyClass: + pass \ No newline at end of file diff --git a/src/lib/inspect.js b/src/lib/inspect.js new file mode 100644 index 0000000000..ac02e4b1b6 --- /dev/null +++ b/src/lib/inspect.js @@ -0,0 +1,39 @@ +/** + * Internal SenseHat Module for reading and writing values from + * JavaScript World to the Python World. This modules set ups + * the commmunication and allows to read and write pixels. If the + * "Sk.sense_hat_emit" config is present, we emit events when + * values are changed: Python -> JavaScript + */ +var $builtinmodule = function (name) { + var mod = {}; + + mod.ismethod = new Sk.builtin.func(function (obj) { + if (obj && obj.im_self && obj.im_func) { + return Sk.builtin.bool.true$ + } else { + return Sk.builtin.bool.false$ + } + }); + + mod.isfunction = new Sk.builtin.func(function (obj) { + if (obj && obj.func_code) { + return Sk.builtin.bool.true$ + } else { + return Sk.builtin.bool.false$ + } + }); + + mod.isbuiltin = new Sk.builtin.func(function (obj) { + // Get the name of the obj + + console.info(obj); + //Sk.builtin + }); + + mod.getcallargs = new Sk.builtin.func(function (obj) { + consle.info(obj, arguments); + }); + + return mod; +}; \ No newline at end of file diff --git a/src/lib/inspect.py b/src/lib/inspect.py deleted file mode 100644 index 796862a53e..0000000000 --- a/src/lib/inspect.py +++ /dev/null @@ -1 +0,0 @@ -raise NotImplementedError("inspect is not yet implemented in Skulpt") diff --git a/src/lib/math.js b/src/lib/math.js index ac786c1175..0375465495 100644 --- a/src/lib/math.js +++ b/src/lib/math.js @@ -153,8 +153,9 @@ var $builtinmodule = function (name) { var res; var isNeg_x = _x < 0; - var isNeg_y = _x < 0; + var isNeg_y = _y < 0; + // ToDo: it seems that Python3 copysign(1, -0) return 1! // special case for floats with negative zero if(Sk.builtin.checkFloat(x)) { if(_x === 0) { @@ -170,14 +171,14 @@ var $builtinmodule = function (name) { // if both signs are equal, just return _y if((isNeg_x && isNeg_y) || (!isNeg_x && !isNeg_y)) { - res = _y; + res = _x; } else if((isNeg_x && !isNeg_y) || (!isNeg_x && isNeg_y)) { // if different, invert sign - if(y === 0) { + if(x === 0) { // special case for zero res = isNeg_x ? -0.0 : 0.0; } else { - res = _y * -1; + res = _x * -1; } } @@ -208,21 +209,90 @@ var $builtinmodule = function (name) { mod.log = new Sk.builtin.func(function (x, base) { Sk.builtin.pyCheckArgs("log", arguments, 1, 2); Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(x)); + var js_x = Sk.builtin.asnum$(x); + + // negative values for x should rais an ValueError + if(js_x < 0 || js_x == Number.NEGATIVE_INFINITY) { + throw new Sk.builtin.ValueError('math domain error'); + } + + if (Sk.misceval.callsim(mod.isnan, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_('nan'); + } + + if (Sk.misceval.callsim(mod.isinf, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_(x); + } if (base === undefined) { - return new Sk.builtin.float_(Math.log(Sk.builtin.asnum$(x))); + return new Sk.builtin.float_(Math.log(js_x)); } else { Sk.builtin.pyCheckType("base", "number", Sk.builtin.checkNumber(base)); - var ret = Math.log(Sk.builtin.asnum$(x)) / Math.log(Sk.builtin.asnum$(base)); + var ret = Math.log(js_x) / Math.log(Sk.builtin.asnum$(base)); + return new Sk.builtin.float_(ret); + } + }); + + mod.log1p = new Sk.builtin.func(function (x) { + Sk.builtin.pyCheckArgs("log1p", arguments, 1, 2); + Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(x)); + var js_x = Sk.builtin.asnum$(x); + + if (x == 0.0) { + return x; + } else { + var ret = Math.log(1.0 + Sk.builtin.asnum$(x)); return new Sk.builtin.float_(ret); } }); + mod.log2 = new Sk.builtin.func(function (x) { + Sk.builtin.pyCheckArgs("log2", arguments, 1, 1); + Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(x)); + var ret; + var js_x = Sk.builtin.asnum$(x); + + // negative values for x should rais an ValueError + if (js_x < 0 || js_x == Number.NEGATIVE_INFINITY) { + throw new Sk.builtin.ValueError('math domain error'); + } + + if (Sk.misceval.callsim(mod.isnan, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_('nan'); + } + + if (Sk.misceval.callsim(mod.isinf, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_(x); + } + + if (Math.log2 && typeof Math.log2 === 'function') { + ret = Math.log2(js_x); + } else { + ret = Math.log(js_x) / Math.LN2; + } + + return new Sk.builtin.float_(ret); + }); + mod.log10 = new Sk.builtin.func(function (x) { Sk.builtin.pyCheckArgs("log10", arguments, 1, 1); Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(x)); + var js_x = Sk.builtin.asnum$(x); + + // negative values for x should raise an ValueError + if (js_x < 0 || js_x == Number.NEGATIVE_INFINITY) { + throw new Sk.builtin.ValueError('math domain error'); + } - var ret = Math.log(Sk.builtin.asnum$(x)) / Math.log(10); + if (Sk.misceval.callsim(mod.isnan, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_('nan'); + } + + if (Sk.misceval.callsim(mod.isinf, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_(x); + } + + var ret = Math.log(js_x) / Math.log(10); return new Sk.builtin.float_(ret); }); @@ -245,7 +315,7 @@ var $builtinmodule = function (name) { Sk.builtin.pyCheckType("x", "float", Sk.builtin.checkFloat(x)); var _x = Sk.builtin.asnum$(x); - if(isNaN(_x)) { + if (isNaN(_x)) { return Sk.builtin.bool.true$; } else { return Sk.builtin.bool.false$; @@ -263,6 +333,40 @@ var $builtinmodule = function (name) { Sk.builtin.pyCheckArgs("pow", arguments, 2, 2); Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(x)); Sk.builtin.pyCheckType("y", "number", Sk.builtin.checkNumber(y)); + var js_x = Sk.builtin.asnum$(x); + var js_y = Sk.builtin.asnum$(y); + /* + if (js_x == 1) { + return new Sk.builtin.float_(1.0); + } + + if (js_x == 0 && js_y >= 0) { + return new Sk.builtin.float_(0.0); + } + + if (js_x == 0 && js_y == 0) { + return new Sk.builtin.float_(1.0); + } + + // negative values for x should raise an ValueError + if (js_x == 0 && (js_y < 0 || js_y == Number.NEGATIVE_INFINITY) && !Sk.builtin.checkInt(y)) { + throw new Sk.builtin.ValueError('math domain error'); + } + + // pow(x, y) should work for x negative, y an integer + if (js_x < 0 && js_y != Number.NEGATIVE_INFINITY && js_y == Number.INFINITY && !Sk.builtin.checkInt(y)) { + throw new Sk.builtin.ValueError('math domain error'); + } + + if (Sk.misceval.callsim(mod.isinf, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$) { + return x; + } + + if (Sk.misceval.callsim(mod.isnan, new Sk.builtin.float_(x)) == Sk.builtin.bool.true$ || + Sk.misceval.callsim(mod.isnan, new Sk.builtin.float_(y)) == Sk.builtin.bool.true$) { + return new Sk.builtin.float_('nan'); + }*/ + // change t439.real in line 39 to 0.0 to match real python behavior return new Sk.builtin.float_(Math.pow(Sk.builtin.asnum$(x), Sk.builtin.asnum$(y))); }); @@ -305,5 +409,56 @@ var $builtinmodule = function (name) { return new Sk.builtin.int_(r); }); + mod.frexp = new Sk.builtin.func(function (x) { + // taken from squeakjs vm + Sk.builtin.pyCheckArgs("frexp", arguments, 1, 1); + Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(x)); + var value = new Sk.builtin.float_(x); + value = Sk.ffi.remapToJs(x); // convert to float? + var mantissa; + var exponent; + var ret; + var data; + var bits; + + if (value == 0.0) { + mantissa = new Sk.builtin.float_(0.0); // zero is special + exponent = new Sk.builtin.int_(0); + } else { + data = new DataView(new ArrayBuffer(8)); + data.setFloat64(0, value); // for accessing IEEE-754 exponent bits + bits = (data.getUint32(0) >>> 20) & 0x7FF; + if (bits === 0) { // we have a subnormal float (actual zero was handled above) + // make it normal by multiplying a large number + data.setFloat64(0, value * Math.pow(2, 64)); + // access its exponent bits, and subtract the large number's exponent + bits = ((data.getUint32(0) >>> 20) & 0x7FF) - 64; + } + exponent = new Sk.builtin.int_(bits - 1022); // apply bias + mantissa = Sk.misceval.callsim(mod.ldexp, x, exponent.nb$negative()); + } + + ret = new Sk.builtin.tuple([mantissa, exponent]); + return ret; + }); + + mod.ldexp = new Sk.builtin.func(function(mantissa, exponent) { + Sk.builtin.pyCheckArgs("ldexp", arguments, 2, 2); + Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkNumber(mantissa)); + Sk.builtin.pyCheckType("x", "number", Sk.builtin.checkInt(exponent)); + + var _mantissa = Sk.ffi.remapToJs(mantissa); + var _exponent = Sk.ffi.remapToJs(exponent); + var ret; + // construct a float from mantissa and exponent + ret = _exponent > 1023 // avoid multiplying by infinity + ? _mantissa * Math.pow(2, 1023) * Math.pow(2, _exponent - 1023) + : _exponent < -1074 // avoid multiplying by zero + ? _mantissa * Math.pow(2, -1074) * Math.pow(2, _exponent + 1074) + : _mantissa * Math.pow(2, _exponent); + + return new Sk.builtin.float_(ret); + }); + return mod; } \ No newline at end of file diff --git a/src/lib/time.js b/src/lib/time.js index 59cff433dd..0e1a9be5c4 100644 --- a/src/lib/time.js +++ b/src/lib/time.js @@ -106,8 +106,37 @@ var $builtinmodule = function (name) { return date.getTimezoneOffset() < stdTimezoneOffset(); } + /** + * ToDo: This is broken since FireFox Version 47 on Windows 10, + * FIXED it by checking the result of the exec + * + * @param {any} date + * @returns + */ function timeZoneName(date) { - return /\((.*)\)/.exec(date.toString())[1]; + var result = /\((.*)\)/.exec(date.toString()); + var language; + + if (this.navigator != null) { + language = this.navigator.userLanguage || this.navigator.language; + } + + if (result && result.length > 1) { + return result[1]; + } else { + if (language === undefined) { + return null; + } + + // Try 2nd way, using the locale string, this does not work in Safari (26.07.2016) + try { + var localeString = date.toLocaleString(language, { timeZoneName: "short" }); + result = localeString.split(" "); + return result[result.length - 1]; + } catch (e) { + return null; + } + } } function timeZoneNames() { diff --git a/src/lib/turtle.js b/src/lib/turtle.js index ede6d0633a..4ee03db833 100644 --- a/src/lib/turtle.js +++ b/src/lib/turtle.js @@ -1502,7 +1502,7 @@ function generateTurtleModule(_target) { _anonymousTurtle = _module.Turtle(); } - return _anonymousTurtle; + return _anonymousTurtle.instance; } function getTarget() { diff --git a/src/lib/types.py b/src/lib/types.py index 1d8ae21b66..b385e2096a 100644 --- a/src/lib/types.py +++ b/src/lib/types.py @@ -1 +1,86 @@ -raise NotImplementedError("types is not yet implemented in Skulpt") +""" +This file was modified from CPython. +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved +""" +"""Define names for all type symbols known in the standard interpreter. +Types that are part of optional modules (e.g. array) are not listed. +""" +import sys + +# Iterators in Python aren't a matter of type but of protocol. A large +# and changing number of builtin types implement *some* flavor of +# iterator. Don't check the type! Use hasattr to check for both +# "__iter__" and "next" attributes instead. + +NoneType = type(None) +TypeType = type +ObjectType = object +IntType = int +LongType = long +FloatType = float +BooleanType = bool +try: + ComplexType = complex +except NameError: + pass +StringType = str + +# StringTypes is already outdated. Instead of writing "type(x) in +# types.StringTypes", you should use "isinstance(x, basestring)". But +# we keep around for compatibility with Python 2.2. +try: + UnicodeType = unicode + StringTypes = (StringType, UnicodeType) +except NameError: + StringTypes = (StringType,) + +BufferType = buffer + +TupleType = tuple +ListType = list +DictType = DictionaryType = dict + +def _f(): pass +FunctionType = type(_f) +LambdaType = type(lambda: None) # Same as FunctionType +CodeType = type(_f.func_code) + +def _g(): + yield 1 +GeneratorType = type(_g()) + +class _C: + def _m(self): pass +ClassType = type(_C) +UnboundMethodType = type(_C._m) # Same as MethodType +_x = _C() +InstanceType = type(_x) +MethodType = type(_x._m) +BuiltinFunctionType = type(len) +BuiltinMethodType = type([].append) # Same as BuiltinFunctionType + +ModuleType = type(sys) +FileType = file +XRangeType = xrange + +# try: +# raise TypeError +# except TypeError: +# tb = sys.exc_info()[2] +# TracebackType = type(tb) +# FrameType = type(tb.tb_frame) +# del tb + +SliceType = slice +# EllipsisType = type(Ellipsis) + +# DictProxyType = type(TypeType.__dict__) +NotImplementedType = type(NotImplemented) + +# For Jython, the following two types are identical +# GetSetDescriptorType = type(FunctionType.func_code) +# MemberDescriptorType = type(FunctionType.func_globals) + +del sys, _f, _g, _C, _x # Not for export +__all__ = list(n for n in globals() if n[:1] != '_') diff --git a/src/list.js b/src/list.js index 92b174bfdb..8e7d4e7b74 100644 --- a/src/list.js +++ b/src/list.js @@ -533,6 +533,13 @@ Sk.builtin.list.prototype["insert"] = new Sk.builtin.func(function (self, i, x) return Sk.builtin.none.none$; }); +Sk.builtin.list.prototype["copy"] = new Sk.builtin.func(function(self, s) { + var slice = new Sk.builtins["slice"](new Sk.builtin.int_(0),new Sk.builtin.int_(2147483647),Sk.builtin.none.none$); + var copy = Sk.abstr.objectGetItem(self, slice, true); + + return copy; +}); + Sk.builtin.list.prototype["extend"] = new Sk.builtin.func(function (self, b) { Sk.builtin.pyCheckArgs("extend", arguments, 2, 2); self.list_extend_(b); diff --git a/src/method.js b/src/method.js index bb52a71e89..ae7b3a6b6f 100644 --- a/src/method.js +++ b/src/method.js @@ -3,12 +3,31 @@ * * co_varnames and co_name come from generated code, must access as dict. */ -Sk.builtin.method = function (func, self) { +Sk.builtin.method = function (func, self, klass) { + if (!(this instanceof Sk.builtin.method)) { + Sk.builtin.pyCheckArgs("method", arguments, 3, 3); + if (!Sk.builtin.checkCallable(func)) { + throw new Sk.builtin.TypeError("First argument must be callable"); + } + if (self.ob$type === undefined) { + throw new Sk.builtin.TypeError("Second argument must be object of known type"); + } + return new Sk.builtin.method(func, self, klass); + } this.im_func = func; this.im_self = self; //print("constructing method", this.im_func.tp$name, this.im_self.tp$name); + var name = (this.im_func.func_code["co_name"] && this.im_func.func_code["co_name"].v) || ""; + this.__name__ = new Sk.builtin.str(name); + this.im_class = klass; + this["$d"] = { + im_func: func, + im_self: self, + im_class: klass + }; }; goog.exportSymbol("Sk.builtin.method", Sk.builtin.method); +Sk.abstr.setUpInheritance("instancemethod", Sk.builtin.method, Sk.builtin.object); Sk.builtin.method.prototype.tp$call = function (args, kw) { goog.asserts.assert(this.im_self, "should just be a function, not a method since there's no self?"); @@ -24,8 +43,6 @@ Sk.builtin.method.prototype.tp$call = function (args, kw) { return this.im_func.tp$call(args, kw); }; -Sk.builtin.method.prototype.tp$name = "instancemethod"; - Sk.builtin.method.prototype["$r"] = function () { var name = (this.im_func.func_code && this.im_func.func_code["co_name"] && this.im_func.func_code["co_name"].v) || ""; return new Sk.builtin.str("", + "GtE" : ">=", + "Is" : "is", + "IsNot": "is not", + "In_" : "in", + "NotIn": "not in" +}; + /** * @param{*} v * @param{*} w @@ -300,6 +313,10 @@ Sk.misceval.richCompareBool = function (v, w, op, canSuspend) { // numeric types are always considered smaller than sequence types in Python if (v_num_type !== -1 && w_seq_type !== -1) { + // in Python 3 numeric types cannot be compared with other types + if (Sk.python3) { + throw new Sk.builtin.TypeError("unorderable types: " + new Sk.builtin.str(v_type).v + " "+ Sk.misceval.compareOpToOp[op] +" " + new Sk.builtin.str(w_type).v); + } switch (op) { case "Lt": return true; @@ -894,7 +911,11 @@ Sk.misceval.asyncToPromise = function(suspendablefn, suspHandlers) { try { // jsh*nt insists these be defined outside the loop var resume = function() { - handleResponse(r.resume()); + try { + handleResponse(r.resume()); + } catch (e) { + reject(e); + } }; var resumeWithData = function resolved(x) { try { @@ -933,9 +954,13 @@ Sk.misceval.asyncToPromise = function(suspendablefn, suspHandlers) { } else if (r.data["type"] == "Sk.yield") { // Assumes all yields are optional, as Sk.setTimeout might // not be able to yield. - Sk.setTimeout(resume, 0); + //Sk.setTimeout(resume, 0); + setImmediate(resume); return; + } else if (r.data["type"] == "Sk.delay") { + setImmediate(resume); + return; } else if (r.optional) { // Unhandled optional suspensions just get // resumed immediately, and we go around the loop again. diff --git a/src/module.js b/src/module.js index 495e57b1f1..3b670a6a58 100644 --- a/src/module.js +++ b/src/module.js @@ -8,3 +8,17 @@ goog.exportSymbol("Sk.builtin.module", Sk.builtin.module); Sk.builtin.module.prototype.ob$type = Sk.builtin.type.makeIntoTypeObj("module", Sk.builtin.module); Sk.builtin.module.prototype.tp$getattr = Sk.builtin.object.prototype.GenericGetAttr; Sk.builtin.module.prototype.tp$setattr = Sk.builtin.object.prototype.GenericSetAttr; + +Sk.builtin.module.prototype["$r"] = function () { + var str = "= 0 ? end : self.v.length + end; } - normaltext = pat.v.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); - m = new RegExp(normaltext, "g"); slice = self.v.slice(start, end); - ctl = slice.match(m); - if (!ctl) { - return new Sk.builtin.int_(0); - } else { - return new Sk.builtin.int_(ctl.length); + subString = sub.v; + if (subString.length <= 0) { + return (slice.length + 1); } + var n = 0; + var pos = 0; + var step = subString.length; + + while (true) { + pos = slice.indexOf(subString, pos); + if (pos >= 0) { + ++n; + pos += step; + } else { + break; + } + } + + return new Sk.builtin.int_(n); }); Sk.builtin.str.prototype["ljust"] = new Sk.builtin.func(function (self, len, fillchar) { @@ -907,6 +919,7 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { var leftAdjust; var zeroPad; var i; + var percent; fieldWidth = Sk.builtin.asnum$(fieldWidth); precision = Sk.builtin.asnum$(precision); @@ -939,6 +952,10 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { alternateForm = conversionFlags.indexOf("#") !== -1; } + if("%".indexOf(conversionType) !== -1){ + percent = true; + } + if (precision) { precision = parseInt(precision.substr(1), 10); } @@ -973,7 +990,9 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { neg = n.nb$isnegative(); // neg = n.size$ < 0; RNL long.js change } - goog.asserts.assert(r !== undefined, "unhandled number format"); + if (r == null) { + throw new Sk.builtin.ValueError("unhandled number format"); + } precZeroPadded = false; @@ -1011,6 +1030,11 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { var prefix = args[0]; var r = args[1]; var j; + + if (percent){ + r = r +"%"; + } + if (fieldWidth) { fieldWidth = parseInt(fieldWidth, 10); totLen = r.length + prefix.length; @@ -1046,12 +1070,29 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { } base = 10; if (conversionType === "d" || conversionType === "i") { + // convert Value to int/long + if (Sk.python3) { + value = new Sk.builtin.int_(value).v; + } + return handleWidth(formatNumber(value, 10)); } else if (conversionType === "o") { + // convert Value to int/long + if (Sk.python3) { + value = new Sk.builtin.int_(value).v; + } return handleWidth(formatNumber(value, 8)); } else if (conversionType === "x") { + // convert Value to int/long + if (Sk.python3) { + value = new Sk.builtin.int_(value).v; + } return handleWidth(formatNumber(value, 16)); } else if (conversionType === "X") { + // convert Value to int/long + if (Sk.python3) { + value = new Sk.builtin.int_(value).v; + } return handleWidth(formatNumber(value, 16)).toUpperCase(); } else if (conversionType === "f" || conversionType === "F" || conversionType === "e" || conversionType === "E" || conversionType === "g" || conversionType === "G") { convValue = Sk.builtin.asnum$(value); @@ -1072,11 +1113,12 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { if (conversionType === "e" || conversionType === "E") { precision = 6; } else if (conversionType === "f" || conversionType === "F") { - precision = 7; + precision = Sk.python3 ? 6 : 7; } } result = (convValue)[convName](precision); // possible loose of negative zero sign + var neg = false; // apply sign to negative zeros, floats only! if(Sk.builtin.checkFloat(value)) { if(convValue === 0 && 1/convValue === -Infinity) { @@ -1084,6 +1126,17 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { } } + // add prefixes if required + var efgPrefix = ""; + + if (precedeWithSign && convValue >= 0 && result.indexOf("-") !== 0) { + efgPrefix = "+" + efgPrefix; + } else if (blankBeforePositive) { + efgPrefix = " " + efgPrefix; + } + + result = efgPrefix + result; + if ("EFG".indexOf(conversionType) !== -1) { result = result.toUpperCase(); } @@ -1119,6 +1172,8 @@ Sk.builtin.str.prototype.nb$remainder = function (rhs) { return r.v; } else if (conversionType === "%") { return "%"; + } else { + throw new Sk.builtin.ValueError("unsupported format character '" + conversionType + "' at index " + index); } }; diff --git a/src/type.js b/src/type.js index a858d4d0c9..d7c4b8b7d6 100644 --- a/src/type.js +++ b/src/type.js @@ -52,7 +52,7 @@ Sk.dunderToSkulpt = { "__pow__": "nb$power", "__rpow__": "nb$reflected_power", "__contains__": "sq$contains", - "__len__": ["sq$length", 0] + "__len__": ["sq$length", 1] }; /** @@ -119,6 +119,7 @@ Sk.builtin.type = function (name, bases, dict) { args = args || []; self["$d"] = new Sk.builtin.dict([]); + self["$d"].mp$ass_subscript(new Sk.builtin.str("__dict__"), self["$d"]); if (klass.prototype.tp$base !== undefined) { if (klass.prototype.tp$base.sk$klass) { @@ -351,9 +352,10 @@ Sk.builtin.type = function (name, bases, dict) { var args = Array.prototype.slice.call(arguments), canSuspend; args.unshift(magic_func, this); - if (canSuspendIdx) { + if (canSuspendIdx !== null) { canSuspend = args[canSuspendIdx+1]; args.splice(canSuspendIdx+1, 1); + if (canSuspend) { return Sk.misceval.callsimOrSuspend.apply(undefined, args); } @@ -627,13 +629,14 @@ Sk.builtin.type.prototype.tp$richcompare = function (other, op) { if (other.ob$type != Sk.builtin.type) { return undefined; } - if (!this["$r"] || !other["$r"]) { return undefined; } - - r1 = this["$r"](); - r2 = other["$r"](); - + 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); }; diff --git a/support/run_template.html b/support/run_template.html index 1ee96d80c4..850a5b7f5c 100644 --- a/support/run_template.html +++ b/support/run_template.html @@ -23,19 +23,38 @@

In Browser Testing Page

} -function builtinRead(x) -{ - if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined) +function builtinRead(x) { + console.log(x); + if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined) { throw "File not found: '" + x + "'"; - return Sk.builtinFiles["files"][x]; + } + + var file = Sk.builtinFiles["files"][x]; + + if (!file) { + return new Promise(function (accept, reject) { + function reqListener () { + console.log(this); + console.log(arguments) + accept(this.responseText); + } + + var oReq = new XMLHttpRequest(); + oReq.addEventListener("load", reqListener); + oReq.open("GET", "%(root)s" + file); + oReq.send(); + }); + } + + return file; } function runit(myDiv) { var prog = document.getElementById(myDiv+"_code").value; var mypre = document.getElementById(myDiv+"_pre"); - Sk.inputfun = function() { - return new Promise(function (resolve) { resolve(window.prompt()); }); + Sk.inputfun = function(prompt) { + return new Promise(function (resolve) { resolve(window.prompt(prompt)); }); }; //Sk.outputfun = alert; @@ -55,6 +74,7 @@

In Browser Testing Page

Sk.configure({ output:outf, read: builtinRead, + inputfunTakesPrompt: true, debugout: showjs, debugging: %(debug_mode)s }); diff --git a/support/setImmediate/setImmediate.js b/support/setImmediate/setImmediate.js new file mode 100644 index 0000000000..0b02c1a03b --- /dev/null +++ b/support/setImmediate/setImmediate.js @@ -0,0 +1,175 @@ +(function (global) { + "use strict"; + + if (global.setImmediate) { + return; + } + + var nextHandle = 1; // Spec says greater than zero + var tasksByHandle = {}; + var currentlyRunningATask = false; + var doc = global.document; + var setImmediate; + + function addFromSetImmediateArguments(args) { + tasksByHandle[nextHandle] = partiallyApplied.apply(undefined, args); + return nextHandle++; + } + + // This function accepts the same arguments as setImmediate, but + // returns a function that requires no arguments. + function partiallyApplied(handler, timeout) { + var args = [].slice.call(arguments, 1); + return function() { + if (typeof handler === "function") { + handler.apply(undefined, args); + } else { + (new Function("" + handler))(); + } + }; + } + + function runIfPresent(handle) { + // From the spec: "Wait until any invocations of this algorithm started before this one have completed." + // So if we're currently running a task, we'll need to delay this invocation. + if (currentlyRunningATask) { + // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a + // "too much recursion" error. + setTimeout(partiallyApplied(runIfPresent, handle), 0); + } else { + var task = tasksByHandle[handle]; + if (task) { + currentlyRunningATask = true; + try { + task(); + } finally { + clearImmediate(handle); + currentlyRunningATask = false; + } + } + } + } + + function clearImmediate(handle) { + delete tasksByHandle[handle]; + } + + function installNextTickImplementation() { + setImmediate = function() { + var handle = addFromSetImmediateArguments(arguments); + process.nextTick(partiallyApplied(runIfPresent, handle)); + return handle; + }; + } + + function canUsePostMessage() { + // The test against `importScripts` prevents this implementation from being installed inside a web worker, + // where `global.postMessage` means something completely different and can't be used for this purpose. + if (global.postMessage && !global.importScripts) { + var postMessageIsAsynchronous = true; + var oldOnMessage = global.onmessage; + global.onmessage = function() { + postMessageIsAsynchronous = false; + }; + global.postMessage("", "*"); + global.onmessage = oldOnMessage; + return postMessageIsAsynchronous; + } + } + + function installPostMessageImplementation() { + // Installs an event handler on `global` for the `message` event: see + // * https://developer.mozilla.org/en/DOM/window.postMessage + // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages + + var messagePrefix = "setImmediate$" + Math.random() + "$"; + var onGlobalMessage = function(event) { + if (event.source === global && + typeof event.data === "string" && + event.data.indexOf(messagePrefix) === 0) { + runIfPresent(+event.data.slice(messagePrefix.length)); + } + }; + + if (global.addEventListener) { + global.addEventListener("message", onGlobalMessage, false); + } else { + global.attachEvent("onmessage", onGlobalMessage); + } + + setImmediate = function() { + var handle = addFromSetImmediateArguments(arguments); + global.postMessage(messagePrefix + handle, "*"); + return handle; + }; + } + + function installMessageChannelImplementation() { + var channel = new MessageChannel(); + channel.port1.onmessage = function(event) { + var handle = event.data; + runIfPresent(handle); + }; + + setImmediate = function() { + var handle = addFromSetImmediateArguments(arguments); + channel.port2.postMessage(handle); + return handle; + }; + } + + function installReadyStateChangeImplementation() { + var html = doc.documentElement; + setImmediate = function() { + var handle = addFromSetImmediateArguments(arguments); + // Create a