For posterity, here is the email I sent Ondrej late last night (so he can reply here, on-list, for all to see).
Sent: Wednesday, April 28, 2010 10:55 PM
To: '
ond...@certik.cz'
Subject: RE: Notification: py2js
Hello Ondrej Certik!
Great to hear from you! Yes, I think it would be beneficial for both of us to work together and merge our efforts.
I just looked over your code -- we have almost identical approaches. Our pylib.js corresponds to your builtins.js. Our jsmap.py corresponds to your name_map, bool_op, unary_op, binary_op and comparison_op dictionaries. Where your translation code is done in one file, fairly directly -- py2js.py -- ours is done in a less direct approach. First, in pyfrontend.py, the AST tree that python provides is transformed into a simpler tree (the various node types of the simpler tree are defined in parsetree.py). Then, in jsbackend.py, the tree is walked and Javascript is emitted.
Let me give you a brief background of our py2js project. It was started in October of 2008 by Niall McCarroll and he posted it here
http://www.mccarroll.net/py2js/index.html. It was under development for a few months, but then development trailed off. Jonathan Fine found out about it and brought it into js4py (
http://code.google.com/p/js4py/) -- a collection of various resources for pythonistas who were learning/using javascript.
I scoured the web, looking for similar projects -- and actually started one of my own using the ast module before I found this one and started working on it three days ago. Jonathan and Niall were very gracious, allowing me to start spearheading development, since the project hadn't seen much activity for a year. Jonathan even ran a jslint over my code and checked in some corrections.
Our code is in a significant state of upheaval. Niall had it tracking dependencies and only emitting the pieces of Javascript that were necessary -- I brought in the idea of having it depend on a stand-alone pylib.js (like your builtins.js), which has greatly simplified things. Also, we arrived at the same conclusion of creating list(), dict() and tuple() wrapper classes so I've been pushing on the javascript side, but the python side isn't yet generated the wrappers around literals.
There are still some spots where our python emits fairly verbose code -- for loops, for example -- but I hope to create some more concise looping functions in the pylib.js in order to make the emitted code cleaner.
I like what you've done with __len__ and __str__ and how you've implemented the various Error classes in Javascript -- I haven't gotten around to doing that. I'm not sure how much work it will be to merge the two projects, but for starters, merging the javascript libraries ought to be fairly straightforward, since they are so similar.
Both our dict wrappers wrap a javascript Object (we call it this._obj, you call it this._items), both our list wrappers wrap a javascript Array (we call it this._arr, you call it this._items). We haven't gotten around to implementing a Tuple class, it's nice to see that you have -- also nice to see the implementation sharing between the list and tuple prototypes. Perhaps at some point we could abstract it up to a base Sequence class or something.
We're putting everything behind closures and hanging everything off the $import object, to keep from cluttering the global javascript namespace -- a "keeping the music down for the neighbors" sort-of idea that Jonathan cares a lot about (and I like it too).
Something I just implemented this morning is args and kwargs unpacking -- both on the function calling side (sending in to a function $args(['item1', 'item2']) as JS equivalent of *['item1', 'item2'] -- and $kwargs() is the equivalent of **), and on the function definition side, if you declare an "args" argument, it will get a list of any extra arguments passed in -- if you declare a "kwargs" argument, it will get a dict of any extra arguments passed in (actually it's Array and Object right now, I need to wrap them in our list/dict classes).
I just started getting some basic doctests running in Javascript, you should be able to see them by running "python runtests.py", which should write a JSON file and fire up your default browser, pointing to the tests. In another project I have a bit more robust support for javascript doctests (multi-line tests, etc), so I'm hoping to bring that over. Also, I have it writing out ReST from the docstrings for sphinx-generated documentation, as you have. Here's what the Javascript doctests look like for the function wrapper I wrote this morning:
function def(func)
Wraps the supplied function in Pythonic goodness. The wrapper handles
unpacking **kwargs, unpacking *args, sending any extra arguments to the args
and kwargs parameters, sending `this` to the `self` parameter and setting the
`__name__` property.
Use the $args() wrapper instead of * to send in positional args for unpacking.
If the combination of regular arguments passed and $args() passed are more
than the number of declared arguments, the extra ones will be passed as a
list to the "args" parameter. If there is no "args" parameter defined on the
function, an error will be thrown.
>>> var fn1 = def(function(arg1, arg2) { return [arg1, arg2].join(', '); });
>>> fn1('test1', 'test2');
"test1, test2"
>>> fn1($args(['test1', 'test2']));
"test1, test2"
>>> fn1('test1', $args(['test2']));
"test1, test2"
>>> fn1('test1', $args(['test2', 'test3']));
Error: __name__() takes at most 2 arguments (3 given).
Use the $kwargs() wrapper instead of ** to pass in keyword arguments for
unpacking. If any kwargs are passed in that don't map to a declared
parameters, the extra ones will be passed in a dict to the "kwargs"
parameter. If the function doesn't declare a "kwargs" parameter, an error
will be thrown.
>>> fn1($kwargs({'arg2': 'test2', 'arg1': 'test1'}));
"test1, test2"
>>> fn1('test1', $kwargs({'arg2': 'test2'}));
"test1, test2"
>>> fn1($kwargs({'arg3': 'test3'}));
Error: __name__() got an unexpected keyword argument 'arg3'
Here is an example of declaring "args" and "kwargs" parameters to catch
extra arguments:
>>> var fn2 = def(function(arg1, args, kwargs) { return arg1 + ', ' + JSON.stringify(args) + ', ' + JSON.stringify(kwargs); });
>>> fn2($args(['test1']));
"test1, [], {}"
>>> fn2($args(['test1', 'test2']));
"test1, [\"test2\"], {}"
>>> fn2($kwargs({'arg1': 'test1', 'arg2': 'test2'}));
"test1, [], {\"arg2\":\"test2\"}"
I realized tonight at the Bellingham python user group that I missed an error case. In Python, if you define a function "def foo(arg1, arg2)" and pass in "foo('test1', arg1='something else')" it will throw a TypeError about multiple values for the same argument. In my Javascript implementation, the keyword-argument will override the positional one... I need to fix that.
Regarding the python side of things, I'm not sure there are any compelling advantages to our layered approach. I'll have to examine more closely what Niall is doing differently with the parsetree.py set of objects that couldn't be done more directly in visitor methods, as you're doing them. There is certainly an advantage to simplicity and not having too many layers, so if there's no compelling reason to keep it as is, I'll look at flattening our system so it looks more like yours and is easier to merge.
If you haven't noticed from my manifesto-style wiki page, I care a lot about generating clean-looking code that isn't too verbose. I like what you've done with for-loop implementation (using the .next() call and handling the StopIteration exception) and I'd like to do something like that, but I'd like to find a way to hide it a bit, so it doesn't bloat the generated code so much.
I've thrown around a couple approaches with a local pythonista, either something like this:
"for (var iter = iter(items); !(item instanceof StopIteration); item = trynext(iter))"
The "trynext" function would include the try/catch and return StopIteration as a return value -- this would allow us to not have a try/catch block in every emitted for loop, but also wouldn't properly handle the (extremely rare) case where the list we're iterating over actually has a StopIteration object in it.
Another option is to go the traditional iterms.forEach(function(item1, item2) { ... }) approach, which is a fairly clean syntax and I think FF3's native implementation may even support iterating over an object that throws a StopIteration exception -- yes, there's a native StopIteration exception in Javascript 1.7:
https://developer.mozilla.org/en/New_in_JavaScript_1.7.
Anyways, all that to say that that's what I'm currently thinking about and working on.
What example were you running that generated the 'Name' AttributeError? I'd like to run it myself and debug it.
Before I came to the project, the code it was generating wasn't exactly clean and beautiful -- it was just getting the job done. I've cleaned up a few areas (almost all the examples on the wiki page are passing now), but there are still lots of areas (list comprehensions, *args and **kwargs, etc) that I still need to clean up.
Looking forward to working with you,
-- Peter Rust
Developer, Cornerstone Systems
-----Original Message-----
From:
notificati...@bitbucket.org [mailto:
notificati...@bitbucket.org]
Sent: Wednesday, April 28, 2010 7:09 PM
To:
pe...@cornerstonenw.com
Subject: Notification: py2js
You have received a notification from certik.
Hi Peter,
we are developing this library:
http://mattpap.github.com/py2js/html/
using MIT license too, so I thought if we could work together on a common project. We accidentally call it py2js too and have the exact same goals. Do you have some better communication medium than this? My email is:
ond...@certik.cz
Accidentally, I was in Bellingham last week, but I didn't know about you...
I tried to run your py2js on our examples and it fails with:
File "/home/ondrej/repos/py2js-hg/pyfrontend.py", line 276, in Assign
expr = self.visit(ast.value)
File "/home/ondrej/repos/py2js-hg/pyfrontend.py", line 90, in visit
return getattr(self,name)(ast)
File "/home/ondrej/repos/py2js-hg/pyfrontend.py", line 225, in ListComp
generators.append(self.visit(g))
File "/home/ondrej/repos/py2js-hg/pyfrontend.py", line 90, in visit
return getattr(self,name)(ast)
File "/home/ondrej/repos/py2js-hg/pyfrontend.py", line 172, in comprehension
target = self.visit(e.left)
AttributeError: 'Name' object has no attribute 'left'
so I can't compare it yet. But in any case, I would like to join our forces.
Ondrej
http://ondrej.certik.cz
I am in this group:
http://hpfem.org/
You may read it and others on
http://bitbucket.org/notifications/
--
This is a notification from
bitbucket.org.
You are receiving this because you have email notifications enabled.