bastikr / boolean.py

Implements boolean algebra in one module.
BSD 2-Clause "Simplified" License
76 stars 34 forks source link

Transpile boolean.py into javascript - Python3-only #72

Open alisianoi opened 7 years ago

alisianoi commented 7 years ago

This will be a long-running PR where you can monitor progress of actual transpilation.

This will be used to identify actual "blockers" (like missing modules, unsupported features) and these "blockers" will be dealt with in separate PR's and then this PR will be rebased accordingly. Or even in this PR if the "blocker" is super small.

So far I've set up an entry_point which in theory should allow the following approach:

pip install boolean.py
transpile

During development that looks like:

cd boolean.py
pip install -e . # install in development mode
transpile

In its simplest form this would generate a __javascript__ directory directly inside the package. However, there will be an ArgumentParser to deal with a) passing transcrypt related args further to transcrypt and b) configuring some other parameters for transpilation.

Current blocker: need to figure out a way to install transcrypt and pyaml as non-necessary dependencies. Probably somehow with extras_require in setup.py

alisianoi commented 7 years ago

Ok, extras_require are now in place, they will be installed if you do pip install -e .

Now need to interface into transcrypt to make the transpillation call.

alisianoi commented 7 years ago

And now transpile shows that super keyword is the current blocker. Will try to fix it with an automatic conversion first.

pombredanne commented 7 years ago

I am not sure this would need to be part of the main setup code. Especially since your code uses pathlib which is Python 3. I would be more comfy to have a separate scripts under some etc dir that handles the transpiling (and may have it its own requirements file) and not bring this in the main setup.py

alisianoi commented 7 years ago

Finally moved transpile.py into its own etc directory. The whole thing was crashing bizzarly because I forgot that transcrypt does not like it when it is asked to "look up" the directory structure:

transcrypt ../../some/file.py # crashes with a couple tracebacks and "unprintable error" as error description

Update: will quickly address the unit tests and then finally move on to hacking super

alisianoi commented 7 years ago

Tests are broken because of the 3.x super keyword. If you run pytest under a 3.x virtual environment, tests pass. Will probably adjust .travis.yml for this branch to run 3.x only. Some time later.

alisianoi commented 7 years ago

Ok, I've checked in boolean.js just in case you would like to reproduce the steps I am talking about without the need to:

  1. Get a dev version of Transcrypt that has inspect.isclass
  2. Get a dev version of boolean.py from this branch
  3. Transpile stuff yourself with ./etc/transpile.py

Current problem:

  1. Open up boolean.html, navigate to webbrowser console.
  2. Try to create a BooleanAlgebra object with let algebra = boolean.BooleanAlgebra()

That throws the following: TypeError: can't assign to properties of (new String("None")): not an object

Several hours of crawling with debugger over boolean.js (I am rubbish at using a debugger) suggest that the self._wrap_type() call returns a string (around line 2466 in BooleanAlgebra constructor) which is on the next line called like a function, which triggers the error.

This is my best guess right now and I am still looking at it :(

alisianoi commented 7 years ago

Finally! type in python can have more than one argument. Yet Transcrypt looks only at the 0th argument to type and ignores the rest (see compiler.py, lines 1410 - 1414). So, the boolean.py type call that should create a new type named foo.__name__ is translated into Transcrypt's instruction "what is the type of foo.__name__?". The answer to that question is "a string". And so self._wrap_type() goes downhill from there.

alisianoi commented 7 years ago

Ok, the generators work with -e 6 and retranspilation. Current runtime error is:

algebra = boolean.BooleanAlgebra()
expr = algebra.parse("x")
TypeError: tok.isalpha is not a function [Learn More]
alisianoi commented 7 years ago

Ok, expr0 = algebra.parse("apples and oranges") now works, let me point out what does not work:

  1. expr0.get_symbols() and expr0.get_literals() not working because itertools.chain not implemented.
  2. boolean.__eq__(expr0, algebra.AND(algebra.Symbol("apples"), algebra.Symbol("oranges"))) fails because frozenset is not defined
  3. expr0 == algebra.AND(algebra.Symbol("apples"), algebra.Symbol("oranges")) compares as false even though the object on the right is created alright. Same for ===
  4. expr0 = algebra.parse("apples and")does not complain (though it should) and the resulting expr0.args are [Object, undefined]
  5. algebra.Symbol("a") == algebra.Symbol("a") compares as false when should be true. Same for ===
  6. algebra.FALSE < algebra.TRUE throws a "cannot convert to String" error
  7. A more complex parse seems to fail: expr2 = algebra.parse("x and y or a and b") returns an "and" object and does not let (?) you go down to the structure of the expression. The topmost object should be "or". Probably some overwriting is taking place.

However, some comparison starts to work if you use dunder methods in boolean:

  1. boolean.__eq__(algebra.Symbol("a"), algebra.Symbol("a")) is true, with other symbols -- false.
pombredanne commented 7 years ago
  1. expr0.get_symbols() and expr0.get_literals() not working because itertools.chain not implemented.

Use something else.... chain is just a short hand for consuming nested loops.

  1. boolean.__eq__(expr0, algebra.AND(algebra.Symbol("apples"), algebra.Symbol("oranges"))) fails because frozenset is not defined

Use a plain set instead. Alternatively implementing an immutable set is quick enough.

  1. expr0 == algebra.AND(algebra.Symbol("apples"), algebra.Symbol("oranges")) compares as false even though the object on the right is created alright. Same for ===

What does JS do here for comparisons? is the __eq__ function called? ... and what does expr0 contain?

  1. expr0 = algebra.parse("apples and")does not complain (though it should) and the resulting expr0.args are [Object, undefined]

OK, so there is some validation code that is not handled at all then? The parse does not work? What is expr0 in this case?

  1. algebra.Symbol("a") == algebra.Symbol("a") compares as false when should be true. Same for ===

is the symbol __eq__ called? Do not hesitate to put debug prints

  1. algebra.FALSE < algebra.TRUE throws a "cannot convert to String" error

This is not a big problem. Doing this kinda of comparison is not super important I think, is it?

  1. A more complex parse seems to fail: expr2 = algebra.parse("x and y or a and b") returns an "and" object and does not let (?) you go down to the structure of the expression. The topmost object should be "or". Probably some overwriting is taking place.

ok. so still some stuff not right in the guts of parsing then?

However, some comparison starts to work if you use dunder methods in boolean:

  1. boolean.__eq__(algebra.Symbol("a"), algebra.Symbol("a")) is true, with other symbols -- false.

Hum, this likely means that dunder methods may not be used at all by Transcrypt?

pombredanne commented 6 years ago

I just rebased your branch and pushed this as alisianoi-transpile here. I could have but did not dare force push to your repo after the rebase! I might merge this rebased branch instead

pombredanne commented 6 years ago

When merged we will need to start having a Python2-only maintenance branch as this branch will only support Python3

bigbug commented 3 years ago

Hi, I was kinda bored and created a (partial) port of this library for javascript. You can check it out here: https://github.com/bigbug/boolean