diff --git a/.gitignore b/.gitignore
index 3f8a666..bd5fac8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,8 @@
*.pyc
+__javascript__
+node_modules
+.python-version
+yarn.lock
/boolean.py.egg-info/
/build/
/.tox/
diff --git a/.travis.yml b/.travis.yml
index a4c1183..8985abc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ sudo: false
language: python
python:
- - "2.7"
+# - "2.7"
- "3.4"
- "3.5"
- "3.6"
diff --git a/boolean.js/index.html b/boolean.js/index.html
new file mode 100644
index 0000000..63eed0e
--- /dev/null
+++ b/boolean.js/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+ License Expression Sandbox
+
+
+
+ If you would like to run boolean.py in the browser,
+ you should tell Transcrypt to transpile it for the browser:
+
+ python transpile/transpile.py --browser
+
+ This will define function boolean
on the
+ window
object.
+
+
+ Without the --browser
flag, the boolean
+ function will be defined on the module.exports
object,
+ which works for node.js
+
+ To debug boolean.py in the browser, open developer tools.
+
+
+
diff --git a/boolean/boolean.py b/boolean/boolean.py
index 369f074..4f78c63 100644
--- a/boolean/boolean.py
+++ b/boolean/boolean.py
@@ -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
@@ -121,10 +116,10 @@ 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
@@ -132,12 +127,12 @@ def __init__(self, TRUE_class=None, FALSE_class=None, Symbol_class=None,
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,
@@ -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
@@ -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:
@@ -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)
@@ -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:])
@@ -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)
@@ -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
@@ -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'):
@@ -730,7 +724,8 @@ 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)
@@ -738,12 +733,14 @@ def __invert__(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):
@@ -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
@@ -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):
"""
@@ -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):
@@ -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):
@@ -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):
@@ -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):
@@ -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
@@ -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.
@@ -884,7 +902,7 @@ def pretty(self, indent=0, debug=False):
if debug:
debug_details += '' % (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))
@@ -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
@@ -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 = '~'
@@ -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):
@@ -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.
@@ -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
@@ -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
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ae3d954
--- /dev/null
+++ b/package.json
@@ -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": "git@github.com:all3fox/boolean.py",
+ "author": "Alexander Lisianoi ",
+ "license": "BSD-3-Clause",
+ "scripts": {
+ "test": "mocha tests.js"
+ },
+ "devDependencies": {
+ "mocha": "^3.5.0"
+ }
+}
diff --git a/tests.js/test_boolean.js b/tests.js/test_boolean.js
new file mode 100644
index 0000000..0deaf43
--- /dev/null
+++ b/tests.js/test_boolean.js
@@ -0,0 +1,519 @@
+let assert = require('assert')
+
+let boolean = require('../boolean.js/__javascript__/boolean.js')
+
+let Symbol = boolean.boolean.Symbol
+let BooleanAlgebra = boolean.boolean.BooleanAlgebra
+
+describe('BaseElement', function() {
+ let algebra
+
+ beforeEach(function() {
+ algebra = BooleanAlgebra()
+ })
+
+ it('TRUE and FALSE make sense', function() {
+ assert.equal(algebra.TRUE, algebra.TRUE)
+ assert.equal(algebra.FALSE, algebra.FALSE)
+
+ assert.ok(algebra.TRUE === algebra.TRUE)
+ assert.ok(algebra.FALSE === algebra.FALSE)
+
+ assert.notEqual(algebra.TRUE, algebra.FALSE)
+ assert.notEqual(algebra.FALSE, algebra.TRUE)
+
+ assert.ok(algebra.TRUE != algebra.FALSE)
+ assert.ok(algebra.FALSE != algebra.TRUE)
+ })
+
+ it('literals are empty sets', function() {
+ // algebra.TRUE.literals and algebra.FALSE.literals are weird objects
+ assert.ok(algebra.TRUE .literals().__class__.__name__ === 'set')
+ assert.ok(algebra.FALSE.literals().__class__.__name__ === 'set')
+ })
+
+ it('.literalize() works on basics', function() {
+ assert.equal(algebra.TRUE.literalize(), algebra.TRUE)
+ assert.equal(algebra.FALSE.literalize(), algebra.FALSE)
+
+ assert.notEqual(algebra.TRUE.literalize(), algebra.FALSE)
+ assert.notEqual(algebra.FALSE.literalize(), algebra.TRUE)
+ })
+
+ it('.simplify() works on basics', function() {
+ assert.equal(algebra.TRUE.simplify(), algebra.TRUE)
+ assert.equal(algebra.FALSE.simplify(), algebra.FALSE)
+
+ assert.notEqual(algebra.TRUE.simplify(), algebra.FALSE)
+ assert.notEqual(algebra.FALSE.simplify(), algebra.TRUE)
+ })
+
+ it('dual works on basics', function() {
+ assert.equal(algebra.TRUE.dual, algebra.FALSE)
+ assert.equal(algebra.FALSE.dual, algebra.TRUE)
+ })
+
+ it('order works on basics', function() {
+ assert.ok(algebra.FALSE < algebra.TRUE)
+ assert.ok(algebra.TRUE > algebra.FALSE)
+ })
+
+ it('converting to string makes sense', function() {
+ assert.ok(algebra.FALSE.toString() === '0')
+ assert.ok(algebra.TRUE .toString() === '1')
+
+ assert.ok(algebra.FALSE.__repr__() === 'FALSE')
+ assert.ok(algebra.TRUE .__repr__() === 'TRUE' )
+ })
+})
+
+describe('Symbol', function() {
+ let symbol, symbol1, symbol2, same0, same1
+
+ beforeEach(function() {
+ symbol0 = Symbol('string as a symbol')
+
+ symbol1 = Symbol(1)
+ symbol2 = Symbol(2)
+
+ same0 = Symbol('sibling symbol')
+ same1 = Symbol('sibling symbol')
+ })
+
+ it('isliteral is true by default', function() {
+ assert.equal(symbol1.isliteral, true)
+ })
+
+ it('symbol contains itself in .literals', function() {
+ assert.ok(symbol0.literals().indexOf(symbol0) !== -1)
+ })
+
+ it('symbols with same obj compare equal', function() {
+ assert.ok(same0.__eq__(same1))
+ assert.equal(same0.obj, same1.obj)
+
+ // Javascript will not let you overload !=, let alone !==
+ assert.ok(same0 != same1)
+ assert.notEqual(same0, same1)
+ })
+
+ it('.literalize() a symbol gives that symbol', function() {
+ assert.ok(symbol0.literalize() == symbol0)
+ assert.ok(symbol0.literalize() === symbol0)
+
+ assert.ok(symbol1.literalize() == symbol1)
+ assert.ok(symbol1.literalize() === symbol1)
+
+ assert.ok(symbol0.literalize() != symbol1)
+ assert.ok(symbol1.literalize() != symbol0)
+ })
+
+ it('.simplify() a symbol gives that symbol', function() {
+ assert.ok(symbol0.simplify() == symbol0)
+ assert.ok(symbol0.simplify() === symbol0)
+
+ assert.ok(symbol0.simplify() != symbol1)
+ assert.ok(symbol1.simplify() != symbol0)
+ })
+})
+
+describe('BooleanAlgebra', function() {
+ let algebra, expressions, variables
+
+ beforeEach(function() {
+ algebra = BooleanAlgebra()
+ variables = ['a', 'b', 'c', 'd', 'e', 'f']
+ })
+
+ it('parse a single variable', function() {
+ expression = algebra.parse('a')
+
+ assert.ok(expression.__name__ === 'Symbol')
+ assert.ok(expression.obj === 'a')
+ })
+
+ expressions = [
+ 'a or b', 'a OR b', 'a | b', 'a || b', 'a oR b', 'a oR b'
+ ]
+ for (let expression of expressions) {
+ it('parse ' + expression, function() {
+ expression = algebra.parse(expression)
+
+ assert.ok(expression.__name__ === 'OR')
+ assert.equal(expression.args.length, 2)
+
+ let fst = expression.args[0], snd = expression.args[1]
+
+ assert.equal(fst.__name__, 'Symbol')
+ assert.equal(snd.__name__, 'Symbol')
+
+ assert.equal(fst.obj, 'a')
+ assert.equal(snd.obj, 'b')
+ })
+ }
+
+ expressions = [
+ 'a and b', 'a AND b', 'a & b', 'a && b', 'a aND b', 'a aNd b'
+ ]
+ for (let expression of expressions) {
+ it('parse ' + expression, function() {
+ expression = algebra.parse(expression)
+
+ assert.ok(expression.__name__ === 'AND')
+ assert.equal(expression.args.length, 2)
+
+ let fst = expression.args[0], snd = expression.args[1]
+
+ assert.equal(fst.__name__, 'Symbol')
+ assert.equal(snd.__name__, 'Symbol')
+
+ assert.equal(fst.obj, 'a')
+ assert.equal(snd.obj, 'b')
+ })
+ }
+
+ expressions = ['not a', '~a', '!a', 'nOt a', 'nOT a']
+ for (let expression of expressions) {
+ it('parse ' + expression, function() {
+ expression = algebra.parse(expression)
+
+ assert.ok(expression.__name__ === 'NOT')
+ assert.equal(expression.args.length, 1)
+
+ assert.equal(expression.args[0].obj, 'a')
+ })
+ }
+
+ it.skip('parse empty parenthesis', function() {
+ expression = algebra.parse('()')
+ })
+
+ it('parse (a)', function() {
+ expression = algebra.parse('(a)')
+
+ assert.equal(expression.obj, 'a')
+ })
+
+ it('parse (a or b)', function() {
+ expression = algebra.parse('(a or b)')
+
+ assert.equal(expression.__name__, 'OR')
+ assert.equal(expression.args.length, 2)
+
+ assert.equal(expression.args[0], 'a')
+ assert.equal(expression.args[1], 'b')
+ })
+
+ it('parse (a and b)', function() {
+ expression = algebra.parse('(a and b)')
+
+ assert.equal(expression.__name__, 'AND')
+ assert.equal(expression.args.length, 2)
+
+ assert.equal(expression.args[0].obj, 'a')
+ assert.equal(expression.args[1].obj, 'b')
+ })
+
+ it('parse (not a)', function() {
+ expression = algebra.parse('(not a)')
+
+ assert.equal(expression.__name__, 'NOT')
+ assert.equal(expression.args.length, 1)
+
+ assert.equal(expression.args[0].obj, 'a')
+ })
+
+ expressions = ['not (a)', '!(a)', '! (a)', '~(a)', '~ (a)']
+ for (let expression of expressions) {
+ it('parse ' + expression, function() {
+ expression = algebra.parse(expression)
+
+ assert.equal(expression.__name__, 'NOT')
+ assert.equal(expression.args.length, 1)
+
+ assert.equal(expression.args[0].obj, 'a')
+ })
+ }
+
+ expressions = [
+ 'not a & not b', '~a & ~b', '!a & !b',
+ 'not a && not b', '~a && ~b', '!a && !b',
+ 'not a and not b', '~a and ~b', '!a and !b',
+ 'not a & not b & not c', '~a & ~b & ~c', '!a & !b & !c',
+ 'not a && not b && not c', '~a && ~b && ~c', '!a && !b && !c',
+ 'not a and not b and not c', '~a and ~b and ~c', '!a and !b and !c'
+ ]
+ expressions.forEach((expression, i) => {
+ it.skip('parse ' + expression, function() {
+ expression = algebra.parse(expression)
+
+ assert.equal(expression.__name__, 'AND')
+ if (i < 9) {
+ assert.equal(expression.args.length, 2)
+ } else {
+ assert.equal(expression.args.length, 3)
+ }
+ })
+ })
+
+ expressions = [
+ 'not a | not b', '~a | ~b', '!a | !b',
+ 'not a || not b', '~a || ~b', '!a || !b',
+ 'not a or not b', '~a or ~b', '!a or !b',
+ 'not a | not b | not c', '~a | ~b | ~c', '!a | !b | !c',
+ 'not a || not b && not c', '~a || ~b || ~c', '!a || !b || !c',
+ 'not a or not b or not c', '~a or ~b or ~c', '!a or !b or !c'
+ ]
+ expressions.forEach((expression, i) => {
+ it.skip('parse ' + expression, function() {
+ expression = algebra.parse(expression)
+
+ assert.equal(expression.__name__, 'OR')
+ if (i < 9) {
+ assert.equal(expression.args.length, 2)
+ } else {
+ assert.equal(expression.args.length, 3)
+ }
+ })
+ })
+
+ expressions = ['not a', '!a', '~a', 'not(a)', '(not a)', '!(a)']
+ for (let expression of expressions) {
+ it('literalize ' + expression, function() {
+ expression = algebra.parse(expression).literalize()
+
+ assert.equal(expression.__name__, 'NOT')
+ assert.equal(expression.args.length, 1)
+
+ assert.equal(expression.args[0].obj, 'a')
+ })
+ }
+
+ expressions = [
+ 'not (a | b)', '~(a | b)', '!(a | b)',
+ 'not (a || b)', '~(a || b)', '!(a || b)',
+ 'not (a or b)', '~(a or b)', '!(a or b)',
+ 'not (a | b | c)', '~(a | b | c)', '!(a | b | c)',
+ 'not (a || b || c)', '~(a || b || c)', '!(a || b || c)',
+ 'not (a or b or c)', '~(a or b or c)', '!(a or b or c)',
+ 'not (a | b || c)', '~(a | b or c)', '!(a or b || c)'
+ ]
+ expressions.forEach((expression, i) => {
+ it ('literalize ' + expression, function() {
+ expression = algebra.parse(expression).literalize()
+
+ assert.equal(expression.__name__, 'AND')
+ if (i < 9) {
+ assert.equal(expression.args.length, 2)
+ } else {
+ assert.equal(expression.args.length, 3)
+ }
+
+ for (let j = 0; j != expression.args.length; ++j) {
+ assert.equal(expression.args[j].__name__, 'NOT')
+
+ assert.equal(expression.args[j].args[0].obj, variables[j])
+ }
+ })
+ })
+
+ expressions = [
+ 'not (a & b)', '~(a & b)', '!(a & b)',
+ 'not (a && b)', '~(a && b)', '!(a && b)',
+ 'not (a and b)', '~(a and b)', '!(a and b)',
+ 'not (a & b & c)', '~(a & b & c)', '!(a & b & c)',
+ 'not (a && b && c)', '~(a && b && c)', '!(a && b && c)',
+ 'not (a and b and c)', '~(a and b and c)', '!(a and b and c)',
+ 'not (a & b && c)', '~(a & b and c)', '!(a && b and c)'
+ ]
+ expressions.forEach((expression, i) => {
+ it('literalize ' + expression, function() {
+ expression = algebra.parse(expression).literalize()
+
+ assert.equal(expression.__name__, 'OR')
+ if (i < 9) {
+ assert.equal(expression.args.length, 2)
+ } else {
+ assert.equal(expression.args.length, 3)
+ }
+
+ for (let j = 0; j != expression.args.length; ++j) {
+ assert.equal(expression.args[j].__name__, 'NOT')
+
+ assert.equal(expression.args[j].args[0].obj, variables[j])
+ }
+ })
+ })
+
+ expressions = [
+ '!(a and b)', '!(a & b)', '!(a && b)',
+ '~(a and b)', '~(a & b)', '~(a && b)',
+ 'not (a and b)', 'not (a & b)', 'not (a && b)',
+ ]
+ expressions.forEach((expression, i) => {
+ it('.demorgan() on ' + expression, function() {
+ expr = algebra.parse(expression).demorgan()
+
+ assert.equal(expr.__name__, 'OR')
+ assert.equal(expr.args.length, 2)
+
+ for (let j = 0; j != expr.args.length; ++j) {
+ assert.equal(expr.args[j].__name__, 'NOT')
+ assert.equal(expr.args[j].args.length, 1)
+
+ assert.equal(expr.args[j].args[0].obj, variables[j])
+ }
+ })
+ })
+
+ expressions = [
+ '!(a and b and c)', '!(a & b & c)', '!(a && b && c)',
+ '~(a and b and c)', '~(a & b & c)', '~(a && b && c)',
+ 'not (a and b and c)', 'not (a & b & c)', 'not (a && b && c)'
+ ]
+ expressions.forEach((expression, i) => {
+ it('.demorgan() on ' + expression, function() {
+ expr = algebra.parse(expression).demorgan()
+
+ assert.equal(expr.__name__, 'OR')
+ assert.equal(expr.args.length, 3)
+
+ for (let j = 0; j != expr.args.length; ++j) {
+ assert.equal(expr.args[j].__name__, 'NOT')
+ assert.equal(expr.args[j].args[0], variables[j])
+ }
+ })
+ })
+
+ expressions = [
+ '!(a or b)', '!(a | b)', '!(a || b)',
+ '~(a or b)', '~(a | b)', '~(a || b)',
+ 'not (a or b)', 'not (a | b)', 'not (a || b)'
+ ]
+ expressions.forEach((expression, i) => {
+ it('.demorgan() on ' + expression, function() {
+ expr = algebra.parse(expression).demorgan()
+
+ assert.equal(expr.__name__, 'AND')
+ assert.equal(expr.args.length, 2)
+
+ for (let j = 0; j != expr.args.length; ++j) {
+ assert.equal(expr.args[j].__name__, 'NOT')
+ assert.equal(expr.args[j].args[0], variables[j])
+ }
+ })
+ })
+
+ expressions = [
+ '!(a or b or c)', '!(a | b | c)', '!(a || b || c)',
+ '~(a or b or c)', '~(a | b | c)', '!(a || b || c)',
+ 'not (a or b or c)', 'not (a | b | c)', 'not (a || b || c)'
+ ]
+ expressions.forEach((expression, i) => {
+ it('.demorgan() on ' + expression, function() {
+ expr = algebra.parse(expression).demorgan()
+
+ assert.equal(expr.__name__, 'AND')
+ assert.equal(expr.args.length, 3)
+
+ for (let j = 0; j != expr.args.length; ++j) {
+ assert.equal(expr.args[j].__name__, 'NOT')
+ assert.equal(expr.args[j].args[0].obj, variables[j])
+ }
+ })
+ })
+
+ expressions = ['a and a', 'a & a', 'a && a']
+ expressions.forEach((expression, i) => {
+ it('annihilator is *not* set for ' + expression, function() {
+ expr = algebra.parse(expression)
+
+ assert.equal(expr.annihilator, false)
+ })
+ })
+
+ expressions = ['a or a', 'a | a', 'a || a']
+ expressions.forEach((expression, i) => {
+ it('annihilator is set for ' + expression, function() {
+ expr = algebra.parse(expression)
+
+ assert.equal(expr.annihilator, true)
+ })
+ })
+
+ it.skip('identity is set for a & b', function() {
+ expr = algebra.parse('a & b')
+
+ assert.equal(expr.identity, true)
+ })
+
+ it.skip('identity is *not* set for a | b', function() {
+ expr = algebra.parse('a | b')
+
+ assert.equal(expr.identity, false)
+ })
+})
+
+describe('NOT', function() {
+ let algebra, symbol, expressions
+
+ beforeEach(function() {
+ algebra = BooleanAlgebra()
+ })
+
+ expressions = [
+ 'not not a', '!!a', '~~a',
+ 'not !a', '! not a', '~ not a', 'not ~ a',
+ 'not not not not a', '!!!!a', '~~~~a'
+ ]
+ expressions.forEach((expression, i) => {
+ it('.cancel() on ' + expression, function() {
+ expr = algebra.parse(expression).cancel()
+
+ assert.equal(expr.obj, 'a')
+ })
+
+ it.skip('.literalize() on ' + expression, function() {
+ expr = algebra.parse(expression).literalize()
+
+ assert.equal(expr.obj, 'a')
+ })
+
+ it('.simplify() on ' + expression, function() {
+ expr = algebra.parse(expression).simplify()
+
+ assert.equal(expr.obj, 'a')
+ })
+ })
+
+ expressions = [
+ 'not a', '!a', '~a',
+ 'not not not a', '!!!a', '~~~a',
+ 'not !!a', 'not ! not a', '! not not a'
+ ]
+ expressions.forEach((expression, i) => {
+ it('.cancel() on ' + expression, function() {
+ expr = algebra.parse(expression).cancel()
+
+ assert.equal(expr.__name__, 'NOT')
+ assert.equal(expr.args.length, 1)
+ assert.equal(expr.args[0].obj, 'a')
+ })
+
+ it.skip('.literalize() on ' + expression, function() {
+ expr = algebra.parse(expression).literalize()
+
+ assert.equal(expression.__name__, 'NOT')
+ assert.equal(expr.args.length, 1)
+ assert.equal(expr.args[0].obj, 'a')
+ })
+
+ it('.simplify() on ' + expression, function() {
+ expr = algebra.parse(expression).simplify()
+
+ assert.equal(expr.__name__, 'NOT')
+ assert.equal(expr.args.length, 1)
+ assert.equal(expr.args[0].obj, 'a')
+ })
+ })
+})
diff --git a/transpile/requirements.txt b/transpile/requirements.txt
new file mode 100644
index 0000000..91a6c6a
--- /dev/null
+++ b/transpile/requirements.txt
@@ -0,0 +1,3 @@
+astor
+pyaml
+transcrypt
diff --git a/transpile/transpile.py b/transpile/transpile.py
new file mode 100755
index 0000000..f7308fe
--- /dev/null
+++ b/transpile/transpile.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+
+import argparse
+import logging
+import logging.config
+import shutil
+import subprocess
+import yaml
+import sys
+
+from argparse import ArgumentParser
+from pathlib import Path
+
+
+logger = logging.getLogger(__name__)
+
+def configure_logging(parent, args):
+ """
+ Configure logging (level, format, etc.) for this module
+
+ Sensible defaults come from 'transpile.yml' and can be changed
+ by values that come from console.
+
+ :param parent: -- directory where 'transpile.yml' resides
+ :param args: -- general arguments for transpile
+ """
+ with open(Path(parent, 'transpile.yml'), 'r') as config:
+ params = yaml.load(config)
+
+ logging.config.dictConfig(params['logging'])
+
+ if args.verbose == 0:
+ pass # use level specified by the config file
+ elif args.verbose == 1:
+ logger.setLevel(logging.INFO)
+ else:
+ logger.setLevel(logging.DEBUG)
+
+ if args.quiet:
+ logging.disable(logging.CRITICAL)
+ # Message below should not reach the user
+ logger.critical('logging is active despite --quiet')
+
+ logger.debug('configure_logging() done')
+
+def create_transcrypt_cmd(args, transcrypt_args):
+ """
+ Create a subprocess command that calls transcrypt
+
+ :param args: -- general arguments for transpile
+ :param transcrypt_args: -- arguments specific to transcrypt
+
+ :returns: a command line suitable for subprocess.run()
+ """
+ logger.debug('create_transcrypt_cmd() call')
+
+ # Assume transcrypt executable is available
+ cmd = ['transcrypt']
+
+ # You can specify '--' on the command line to pass parameters
+ # directly to transcrypt, example: transpile -- --help
+ # In this case '--' is also passed first, so need to remove it:
+ if transcrypt_args and transcrypt_args[0] == '--':
+ transcrypt_args = transcrypt_args[1:]
+
+ cmd += transcrypt_args
+
+ # If you are manually passing transcrypt arguments, please specify
+ # them all yourself. Otherwise, let me provide sensible defaults:
+ if not transcrypt_args:
+ # Force transpiling from scratch
+ cmd.append('-b')
+ # Force compatibility with Python truth-value testing.
+ # There is a warning that this switch will slow everything down a lot.
+ # This forces empty dictionaries, lists, and tuples to compare as false.
+ cmd.append('-t')
+ # Force EcmaScript 6 to enable generators
+ cmd.append('-e')
+ cmd.append('6')
+
+ if args.browser:
+ logger.debug('transpile license_expression for the browser')
+
+ pass
+ else:
+ logger.debug('transpile license_expression for node.js')
+ # Drop global 'window' object and prepare for node.js runtime instead
+ cmd.append('-p')
+ cmd.append('module.exports')
+
+ # Supply path to the python file to be transpiled
+ cmd.append(str(args.src[0]))
+
+ logger.info('constructed the following command')
+ logger.info(str(cmd))
+
+ logger.debug('create_transcrypt_cmd() done')
+ return cmd
+
+def transpile():
+ """
+ Call transcrypt to transpile boolean.py into JavaScript
+ """
+
+ fpath = Path(__file__).resolve()
+ parent = fpath.parent
+
+ parser = ArgumentParser(
+ prog=fpath.name,
+ description="Transpile boolean.py into JavaScript"
+ )
+
+ # file path to boolean.py, usually ../boolean/boolean.py
+ spath = Path(parent.parent, 'boolean')
+
+ # boolean.py path
+ bpath = Path(spath, 'boolean.py')
+ parser.add_argument(
+ '--src', nargs=1, default=[bpath],
+ help='start transpilation from here'
+ )
+
+ # javascript path, for output
+ jpath = Path(parent.parent, 'boolean.js', '__javascript__')
+ parser.add_argument(
+ '--dst', nargs=1, default=[jpath],
+ help='store produced javascript here'
+ )
+
+ parser.add_argument(
+ '-v', '--verbose', action='count',
+ help='print more output information'
+ )
+
+ parser.add_argument(
+ '--browser', action='store_true',
+ help='transpile boolean.py for the browser'
+ )
+
+ parser.add_argument(
+ '-q', '--quiet', action='store_true',
+ help='print nothing (transcrypt might print though)'
+ )
+
+ args, transcrypt_args = parser.parse_known_args()
+
+ configure_logging(parent, args)
+
+ # User can specify '--quiet' to suppress output. So, delay any
+ # logging calls until we know the desired verbosity level.
+ logger.debug('transpile() call')
+
+ logger.debug('.parse_known_args() call')
+ logger.debug('src : ' + str(args.src))
+ logger.debug('dst : ' + str(args.dst))
+ logger.debug('transcrypt_args: ' + str(transcrypt_args))
+ logger.debug('.parse_known_args() done')
+
+ cmd = create_transcrypt_cmd(args, transcrypt_args)
+
+ logger.debug('subprocess.run() call')
+ process = subprocess.run(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ logger.debug('subprocess.run() done')
+
+ if process.returncode != 0:
+ logger.warning('Transcrypt failed:')
+
+ for line in str(process.stdout).split('\\n'):
+ logger.warning(line)
+ for line in str(process.stderr).split('\\n'):
+ logger.warning(line)
+
+ sys.exit(1)
+
+ # Transcrypt always puts the transpiled result into __javascript__,
+ # move it to ./src/license_expression.js, create directories if necessary
+ stdout = [line for line in str(process.stdout).split('\\n') if line]
+ lines = list(
+ filter(lambda line: line.startswith('Saving result in:'), stdout)
+ )
+
+ if len(lines) != 1:
+ logger.warning('Transcrypt output format changed!')
+ logger.warning('Expected a path to __javascript__ result, instead got:')
+
+ for line in lines:
+ logger.warning(line)
+
+ src = Path(lines[0].split(': ')[1]).parent
+ dst = args.dst[0]
+
+ if src != dst:
+ logger.debug('Copy original __javascript__')
+ logger.debug('copy src: ' + str(src))
+ logger.debug('copy dst: ' + str(dst))
+
+ if dst.exists():
+ logger.debug('Remove previous __javascript__')
+ shutil.rmtree(str(dst))
+
+ shutil.copytree(str(src), str(dst))
+
+ if src.exists():
+ logger.debug('Remove original __javascript__')
+ shutil.rmtree(str(src))
+
+ logger.debug('transpile() done')
+
+if __name__ == "__main__":
+ transpile()
diff --git a/transpile/transpile.yml b/transpile/transpile.yml
new file mode 100644
index 0000000..1cd1da0
--- /dev/null
+++ b/transpile/transpile.yml
@@ -0,0 +1,15 @@
+logging:
+ version: 1
+ formatters:
+ simple:
+ format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+ handlers:
+ console:
+ class: logging.StreamHandler
+ level: DEBUG
+ formatter: simple
+ loggers:
+ __main__:
+ level: DEBUG
+ handlers: [console]
+ propagate: no