Sunday, September 22, 2013

In-browser Python


There are at least fourteen different in-browser implementations of Python. Dan Stromberg has put together a list that compares their basic features, here. Some of these implementations are simple AST compilers that translate a subset of Python into a subset of Javascript. Other implementations, like Skulpt, try to fully reimplement the Python language, intepreter and VM, and standard library.

Translation Test

a = 1
while a < 100:
  a += 1

Skulpt

Skulpt has one of the most complete implementations of Python, but this comes at a high price in terms of performance. Emulating the Python interpreter and VM in javascript produces alot of overhead. The translation test above translates into the following javascript.
Sk.execStart = new Date();
var $scope0=(function($modname){var $blk=0,$exc=[],$gbl={},$loc=$gbl,$err=undefined;$gbl.__name__=$modname;Sk.globals=$gbl;try { while(true){try{ switch($blk){

case 0: /* --- module entry --- */
//
// line 1:
// a=1
// ^
//

Sk.currLineNo = 1;
Sk.currColNo = 0
Sk.currFilename = '<stdin>.py';

$loc.a=new Sk.builtin.nmber(1,'int');
//
// line 2:
// while a < 100:
// ^
//

Sk.currLineNo = 2;
Sk.currColNo = 0
Sk.currFilename = '<stdin>.py';

if (Sk.execStart === undefined) {Sk.execStart=new Date()}if (Sk.execLimit != null && new Date() - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}$blk=1;/* jump */continue;throw new Sk.builtin.SystemError('internal error: unterminated block');

case 1: /* --- while test --- */
var $loadname1=$loc.a!==undefined?$loc.a:Sk.misceval.loadname('a',$gbl);var $compareres2=null;var $compare3=Sk.builtin.bool(Sk.misceval.richCompareBool($loadname1,new Sk.builtin.nmber(100,'int'),'Lt'));$compareres2=$compare3;var $jfalse4=($compare3===false||!Sk.misceval.isTrue($compare3));if (Sk.execStart === undefined) {Sk.execStart=new Date()}if (Sk.execLimit != null && new Date() - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}if($jfalse4){/*test failed */$blk=4;continue;}if (Sk.execStart === undefined) {Sk.execStart=new Date()}if (Sk.execLimit != null && new Date() - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}$blk=4;/* jump */continue;throw new Sk.builtin.SystemError('internal error: unterminated block');

case 2: /* --- after while --- */return $loc;throw new Sk.builtin.SystemError('internal error: unterminated block');

case 3: /* --- while body --- */
//
// line 3:
//   a += 1
//   ^
//

Sk.currLineNo = 3;
Sk.currColNo = 2
Sk.currFilename = '<stdin>.py';

var $loadname6=$loc.a!==undefined?$loc.a:Sk.misceval.loadname('a',$gbl);
var $inplbinop7=Sk.abstr.numberInplaceBinOp($loadname6,new Sk.builtin.nmber(1,'int'),'Add');
$loc.a=$inplbinop7;

if (Sk.execStart === undefined) {Sk.execStart=new Date()}if (Sk.execLimit != null && new Date() - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}$blk=1;/* jump */continue;throw new Sk.builtin.SystemError('internal error: unterminated block');

case 4: /* --- done --- */
var $jfalse5=($compareres2===false||!Sk.misceval.isTrue($compareres2));
if (Sk.execStart === undefined) {Sk.execStart=new Date()}if (Sk.execLimit != null && new Date() - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}if($jfalse5){/*test failed */$blk=2;continue;}if (Sk.execStart === undefined) {Sk.execStart=new Date()}if (Sk.execLimit != null && new Date() - Sk.execStart > Sk.execLimit) {throw new Sk.builtin.TimeLimitError(Sk.timeoutMsg())}$blk=3;/* jump */continue;throw new Sk.builtin.SystemError('internal error: unterminated block');} }catch(err){if ($exc.length>0) { $err = err; $blk=$exc.pop(); continue; } else { throw err; }} } }catch(err){ if (err instanceof Sk.builtin.SystemExit && !Sk.throwSystemExit) { Sk.misceval.print_(err.toString() + '\n'); return $loc; } else { throw err; } } });
$scope0(new Sk.builtin.str('__main__')); 

As we can clearly see above, Skulpt is not a practical option for performance critical applications.

PythonScript

PythonScript by Amirouche is a Python to Javascript compiler that can output very efficient javascript. This project is still in the early stages, but it has already has good support for a subset of Python. Compared to the other translators, PythonScript has simplest design, least lines of code, and is easy to modify. Amirouche states on his blog he implemented the first version in less than 25 hours! PythonScript is able to translate same test code used in the Skulpt test above into this fully optimized javascript.
a = 1;
while (a < 100) {
  a += 1;
}
Note that Amirouche's original PythonScript is missing support for inplace assignment, but this was very easy to add, get my fork here.

No comments:

Post a Comment