Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a50641f
Create an entry_point for transpile script
Jun 29, 2017
d0f5a63
Add basic logging and config parsing
Jun 29, 2017
3c53b3a
Make logging levels flexible/configurable
Jun 29, 2017
af90c89
Make the subprocess.run call transcrypt
Jun 29, 2017
220884f
Move transpile into etc directory as separate script
Jun 30, 2017
36505c6
Add commented out ast preprocessing attempt
Jul 3, 2017
0d422b5
Replace all super calls to 3.x style super()
Jul 3, 2017
50ff405
Add a template boolean.html that imports produced javascript
Jul 3, 2017
03ab850
Rewrite __nonzero__ and __bool__ with explicit def *(self):
Jul 4, 2017
e985f8e
FIXME: add boolean.js (result of transpilation)
Jul 5, 2017
0abbb51
Remove _wrap_type from BooleanAlgebra
Jul 6, 2017
6d5f946
Remove basestring: it causes a RuntimeError in algebra.parse
Jul 10, 2017
c6e334e
Add __javascript__ (generated by transcrypt) to .gitignore
Jul 11, 2017
ea3301f
Update transpile.py: add -b and -e 6
Jul 11, 2017
4815da5
FIXME: update boolean.js
Jul 11, 2017
dbe71d4
Avoid relying on the fact that dictionaries throw a KeyError
Jul 11, 2017
f92ecff
FIXME: Update transpiled boolean.js: string functions and .get for di…
Jul 12, 2017
e6c1557
Drop python 2.7 from .travis.yml
Jul 18, 2017
85f0cb0
Add '.toString' method to _TRUE, _FALSE, and Symbol
Aug 1, 2017
57fa7ef
Drop issubclass, use a simpler runtime check
Aug 10, 2017
52aa0a0
Rename etc -> transpile, like in license_expression
Aug 21, 2017
3a32592
Remove old transpilation location (boolean/__javascript__)
Aug 22, 2017
7f4dcfd
Update transpile.py to put transpilation into boolean.js/__javascript__
Aug 22, 2017
6fd765e
Add an index.html that allows debugging in the browser
Aug 22, 2017
0573c39
Add a package.json for the transpiled version
Aug 22, 2017
6eece14
Add first basic test for transpiled boolean.py
Aug 22, 2017
61fa426
Add tests for BaseElement
Aug 22, 2017
8a2679f
Add tests for Symbol
Aug 22, 2017
ca1019c
Add tests for BooleanAlgebra class
Aug 23, 2017
ce542fe
Add tests for AND/OR with negated literals (skipped)
Aug 23, 2017
b07b931
Add tests for .cancel() and .literalize() (half skipped)
Aug 23, 2017
55df069
Add .simplify() tests, rename a variable
Aug 23, 2017
f71c584
Add tests for .demorgan()
Aug 23, 2017
2796e43
Add .annihilator tests
Aug 23, 2017
2f44d6e
Add .identity tests (skipped)
Aug 23, 2017
ee8a0c8
Add node_modules, yarn.lock and .python-version to .gitignore
Aug 23, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
*.pyc
__javascript__
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, you both ignored this and committed code that is in the dir.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I did that on purpose: currently, having __javascript__/boolean.js let's you avoid the hassle of getting a vendored version of Transcrypt and making it work. Later I will remove __javascript__/boolean.js and the .gitignore will stay.

node_modules
.python-version
yarn.lock
/boolean.py.egg-info/
/build/
/.tox/
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ sudo: false
language: python

python:
- "2.7"
# - "2.7"
- "3.4"
- "3.5"
- "3.6"
Expand Down
25 changes: 25 additions & 0 deletions boolean.js/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>License Expression Sandbox</title>
</head>
<body>
<p>
If you would like to run boolean.py in the browser,
you should tell Transcrypt to transpile it for the browser:
</p>
<code>python transpile/transpile.py --browser</code>
<p>
This will define function <code>boolean</code> on the
<code>window</code> object.
</p>
<p>
Without the <code>--browser</code> flag, the <code>boolean</code>
function will be defined on the <code>module.exports</code> object,
which works for node.js
</p>
<p>To debug boolean.py in the browser, open developer tools.</p>
<script src="./__javascript__/boolean.js"></script>
</body>
</html>
108 changes: 63 additions & 45 deletions boolean/boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@
import inspect
import itertools

try:
basestring # Python 2
except NameError:
basestring = str # Python 3

# Set to True to enable tracing for parsing
TRACE_PARSE = False

Expand Down Expand Up @@ -121,23 +116,23 @@ def __init__(self, TRUE_class=None, FALSE_class=None, Symbol_class=None,
standard types.
"""
# TRUE and FALSE base elements are algebra-level "singleton" instances
self.TRUE = self._wrap_type(TRUE_class or _TRUE)
self.TRUE = TRUE_class or _TRUE
self.TRUE = self.TRUE()

self.FALSE = self._wrap_type(TRUE_class or _FALSE)
self.FALSE = TRUE_class or _FALSE
self.FALSE = self.FALSE()

# they cross-reference each other
self.TRUE.dual = self.FALSE
self.FALSE.dual = self.TRUE

# boolean operation types, defaulting to the standard types
self.NOT = self._wrap_type(NOT_class or NOT)
self.AND = self._wrap_type(AND_class or AND)
self.OR = self._wrap_type(OR_class or OR)
self.NOT = NOT_class or NOT
self.AND = AND_class or AND
self.OR = OR_class or OR

# class used for Symbols
self.Symbol = self._wrap_type(Symbol_class or Symbol)
self.Symbol = Symbol_class or Symbol

tf_nao = {'TRUE': self.TRUE, 'FALSE': self.FALSE,
'NOT': self.NOT, 'AND': self.AND, 'OR': self.OR,
Expand All @@ -147,12 +142,6 @@ def __init__(self, TRUE_class=None, FALSE_class=None, Symbol_class=None,
# attribute for every other types and objects, including themselves.
self._cross_refs(tf_nao)

def _wrap_type(self, base_class):
"""
Wrap the base class using its name as the name of the new type
"""
return type(base_class.__name__, (base_class,), {})

def _cross_refs(self, objects):
"""
Set every object as attributes of every object in an `objects` mapping
Expand All @@ -162,6 +151,9 @@ def _cross_refs(self, objects):
for name, value in objects.items():
setattr(obj, name, value)

def _is_function(self, obj):
return obj is self.AND or obj is self.OR or obj is self.NOT

def definition(self):
"""
Return a tuple of this algebra defined elements and types as:
Expand Down Expand Up @@ -196,7 +188,7 @@ def parse(self, expr, simplify=False):

precedence = {self.NOT: 5, self.AND: 10, self.OR: 15, TOKEN_LPAR: 20}

if isinstance(expr, basestring):
if isinstance(expr, str):
tokenized = self.tokenize(expr)
else:
tokenized = iter(expr)
Expand Down Expand Up @@ -277,7 +269,8 @@ def is_sym(_t):

# the parens are properly nested
# the top ast node should be a function subclass
if not (inspect.isclass(ast[1]) and issubclass(ast[1], Function)):
if not (inspect.isclass(ast[1]) and self._is_function(ast[1])):
print(ast[1])
raise ParseError(token, tokstr, position, PARSE_INVALID_NESTING)

subex = ast[1](*ast[2:])
Expand Down Expand Up @@ -340,7 +333,7 @@ def _start_operation(self, ast, operation, precedence):
if TRACE_PARSE: print(' start_op: prec == op_prec:', repr(ast))
return ast

if not (inspect.isclass(ast[1]) and issubclass(ast[1], Function)):
if not (inspect.isclass(ast[1]) and self._is_function(ast[1])):
# the top ast node should be a function subclass at this stage
raise ParseError(error_code=PARSE_INVALID_NESTING)

Expand Down Expand Up @@ -404,7 +397,7 @@ def tokenize(self, expr):
- True symbols: 1 and True
- False symbols: 0, False and None
"""
if not isinstance(expr, basestring):
if not isinstance(expr, str):
raise TypeError('expr must be string but it is %s.' % type(expr))

# mapping of lowercase token strings to a token type id for the standard
Expand Down Expand Up @@ -437,9 +430,10 @@ def tokenize(self, expr):
break
position -= 1

try:
yield TOKENS[tok.lower()], tok, position
except KeyError:
value = TOKENS.get(tok.lower())
if value:
yield value, tok, position
else:
if sym:
yield TOKEN_SYMBOL, tok, position
elif tok not in (' ', '\t', '\r', '\n'):
Expand Down Expand Up @@ -730,20 +724,23 @@ def __gt__(self, other):
def __and__(self, other):
return self.AND(self, other)

__mul__ = __and__
def __mul__(self, other):
return self.__and__(other)

def __invert__(self):
return self.NOT(self)

def __or__(self, other):
return self.OR(self, other)

__add__ = __or__
def __add__(self, other):
return self.__or__(other)

def __bool__(self):
raise TypeError('Cannot evaluate expression as a Python Boolean.')

__nonzero__ = __bool__
def __nonzero__(self):
return self.__bool__()


class BaseElement(Expression):
Expand All @@ -754,7 +751,7 @@ class BaseElement(Expression):
sort_order = 0

def __init__(self):
super(BaseElement, self).__init__()
super().__init__()
self.iscanonical = True

# The dual Base Element class for this element: TRUE.dual returns
Expand All @@ -767,7 +764,11 @@ def __lt__(self, other):
return self == self.FALSE
return NotImplemented

__nonzero__ = __bool__ = lambda s: None
def __bool__(self):
return None

def __nonzero__(self):
return None

def pretty(self, indent=0, debug=False):
"""
Expand All @@ -783,7 +784,7 @@ class _TRUE(BaseElement):
"""

def __init__(self):
super(_TRUE, self).__init__()
super().__init__()
# assigned at singleton creation: self.dual = FALSE

def __hash__(self):
Expand All @@ -798,7 +799,14 @@ def __str__(self):
def __repr__(self):
return 'TRUE'

__nonzero__ = __bool__ = lambda s: True
def toString(self):
return self.__str__()

def __bool__(self):
return True

def __nonzero__(self):
return True


class _FALSE(BaseElement):
Expand All @@ -808,7 +816,7 @@ class _FALSE(BaseElement):
"""

def __init__(self):
super(_FALSE, self).__init__()
super().__init__()
# assigned at singleton creation: self.dual = TRUE

def __hash__(self):
Expand All @@ -823,7 +831,14 @@ def __str__(self):
def __repr__(self):
return 'FALSE'

__nonzero__ = __bool__ = lambda s: False
def toString(self):
return self.__str__()

def __bool__(self):
return False

def __nonzero__(self):
return False


class Symbol(Expression):
Expand All @@ -843,7 +858,7 @@ class Symbol(Expression):
sort_order = 5

def __init__(self, obj):
super(Symbol, self).__init__()
super().__init__()
# Store an associated object. This object determines equality
self.obj = obj
self.iscanonical = True
Expand Down Expand Up @@ -873,9 +888,12 @@ def __str__(self):
return str(self.obj)

def __repr__(self):
obj = "'%s'" % self.obj if isinstance(self.obj, basestring) else repr(self.obj)
obj = "'%s'" % self.obj if isinstance(self.obj, str) else repr(self.obj)
return '%s(%s)' % (self.__class__.__name__, obj)

def toString(self):
return self.__str__()

def pretty(self, indent=0, debug=False):
"""
Return a pretty formatted representation of self.
Expand All @@ -884,7 +902,7 @@ def pretty(self, indent=0, debug=False):
if debug:
debug_details += '<isliteral=%r, iscanonical=%r>' % (self.isliteral, self.iscanonical)

obj = "'%s'" % self.obj if isinstance(self.obj, basestring) else repr(self.obj)
obj = "'%s'" % self.obj if isinstance(self.obj, str) else repr(self.obj)
return (' ' * indent) + ('%s(%s%s)' % (self.__class__.__name__, debug_details, obj))


Expand All @@ -898,7 +916,7 @@ class Function(Expression):
"""

def __init__(self, *args):
super(Function, self).__init__()
super().__init__()

# Specifies an infix notation of an operator for printing such as | or &.
self.operator = None
Expand Down Expand Up @@ -988,12 +1006,12 @@ class NOT(Function):
For example::
>>> class NOT2(NOT):
def __init__(self, *args):
super(NOT2, self).__init__(*args)
super().__init__(*args)
self.operator = '!'
"""

def __init__(self, arg1):
super(NOT, self).__init__(arg1)
super().__init__(arg1)
self.isliteral = isinstance(self.args[0], Symbol)
self.operator = '~'

Expand Down Expand Up @@ -1067,7 +1085,7 @@ def pretty(self, indent=1, debug=False):
pretty_literal = self.args[0].pretty(indent=0, debug=debug)
return (' ' * indent) + '%s(%s%s)' % (self.__class__.__name__, debug_details, pretty_literal)
else:
return super(NOT, self).pretty(indent=indent, debug=debug)
return super().pretty(indent=indent, debug=debug)


class DualBase(Function):
Expand All @@ -1080,7 +1098,7 @@ class DualBase(Function):
"""

def __init__(self, arg1, arg2, *args):
super(DualBase, self).__init__(arg1, arg2, *args)
super().__init__(arg1, arg2, *args)

# identity element for the specific operation.
# This will be TRUE for the AND operation and FALSE for the OR operation.
Expand Down Expand Up @@ -1394,14 +1412,14 @@ class AND(DualBase):
For example::
>>> class AND2(AND):
def __init__(self, *args):
super(AND2, self).__init__(*args)
super().__init__(*args)
self.operator = 'AND'
"""

sort_order = 10

def __init__(self, arg1, arg2, *args):
super(AND, self).__init__(arg1, arg2, *args)
super().__init__(arg1, arg2, *args)
self.identity = self.TRUE
self.annihilator = self.FALSE
self.dual = self.OR
Expand All @@ -1418,14 +1436,14 @@ class OR(DualBase):
For example::
>>> class OR2(OR):
def __init__(self, *args):
super(OR2, self).__init__(*args)
super().__init__(*args)
self.operator = 'OR'
"""

sort_order = 25

def __init__(self, arg1, arg2, *args):
super(OR, self).__init__(arg1, arg2, *args)
super().__init__(arg1, arg2, *args)
self.identity = self.FALSE
self.annihilator = self.TRUE
self.dual = self.AND
Expand Down
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "boolean.js",
"version": "3.4",
"description": "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL.",
"main": "boolean.js/__javascript__/boolean.js",
"repository": "[email protected]:all3fox/boolean.py",
"author": "Alexander Lisianoi <[email protected]>",
"license": "BSD-3-Clause",
"scripts": {
"test": "mocha tests.js"
},
"devDependencies": {
"mocha": "^3.5.0"
}
}
Loading