$A \land B \land C$

Propositional Logic Clause Parser (PLCParser)

This library can be used to parse string input that contains seven fundamental propositional logic symbols:

  1. $NOT$
  2. $AND$ <=> $NAND$
  3. $XOR$ <=> $XNOR$
  4. $OR$ <=> $NOR$

Library takes a string input and produces a multidimensional list of given literals with parseInput function. Structure of the nested output list is created to contain all information required to use it programmatically for boolean operations. Validate method is used to check if given input is in correct syntax form. You can deformat well formatted list structure back to literal representation. Furthermore evaluation of the given logic clause or parsed list structure can be done with an optional truth table.

Library is implemented in three programming languages: [Python](#Python-version), [PHP](#PHP-version) and [Javascript](#Javascript-version). For both PHP and Javascript version, see [PLCParser demo application](https://plcparser.herokuapp.com/) deployed in Heroku for live demonstration.

Python version

Download module pyPLCParser from: https://github.com/markomanninen/PLCParser


In [1]:
# load library
from pyPLCParser import parseInput
# set input
i = "(1 AND 0)"
# parse input
o = parseInput(i)
# print output
print(o)


[1, -2, 0]

From the output (['1', -2, '2']) we can notice that negative number -2 is used to mark $AND$ operator. You could also input: (1 & 2) or (1 ∧ 2) to get the same result. $AND$ keyword is case insensitive so use what ever format you like: AnD, and, anD, and so forth.

For an operator you can use either $AND, OR, NOT, XOR$ keywords or single ascii characters on the clause: $\&$, $|$, $\\!$, ^ respectively. These corresponding mathematical symbols are also accepted: $∧$, $∨$, $¬$, $⊕$.

Negative logical gate symbols are also supported: $NAND$ ($/$, $↑$), $XNOR$ ($=$, $↔$) and $NOR$ ($†$, $↓$). It is recommended that you use either word or mathematical symbols, because conventions of the character codes are different on the different programming systems.


In [2]:
# use | for OR and ⊕ for XOR
print(parseInput("(1 | 0)"), parseInput("(1 ⊕ 0)"))
# use NAND
print(parseInput("(1 ↑ 0)"))


[1, -4, 0] [1, -3, 0]
[1, -5, 0]

But say, you have a more complex nested clause in your hands, what is the outcome?


In [3]:
i = "(A or (B and (!C and (D xor E))))"
parseInput(i)


Out[3]:
['A', -4, ['B', -2, [[-1, 'C'], -2, ['D', -3, 'E']]]]

PLCParser tries to deformalize and interpret the boolean operator precedence. But sometimes it is better that you decide and choose correctly the format of the nested set / parentheses to get the right result. Order of the precedence differs by authors anyway. In PLCParser this order is used: $NOT$, $AND$, $XOR$, $OR$.

Without parentheses similar input would be interpreted like this:


In [4]:
from pyPLCParser import deformatInput
i = "A or B and !C and D or E"
deformatInput(parseInput(i))


Out[4]:
"( 'A' or ( ( 'B' and ( not 'C' ) ) and 'D' ) ) or 'E'"

Precedence

In case one needs to adjust the order of the precedence of the operators, it can be done via special argument. In that case you need to import all operator variables and use them to set the order:


In [5]:
from pyPLCParser import PLCParser, NOT_OPERATOR, AND_OPERATOR, XOR_OPERATOR, \
                        OR_OPERATOR, NAND_OPERATOR, XNOR_OPERATOR, NOR_OPERATOR

# input without nested parentheses
i = "(1 and 0 nand 1 or 1 nor 0)"
# init object
c = PLCParser()
# output default nesting
print(c.parse(i))

# set up your own operator predecence. note that usually not operator is the last
# while the least weight operators are given first.
operator_precedence = (OR_OPERATOR, NOR_OPERATOR, XOR_OPERATOR, XNOR_OPERATOR, 
                       AND_OPERATOR, NAND_OPERATOR, NOT_OPERATOR)
# use argument
c = PLCParser(operator_precedence=operator_precedence)
# output new nested set
print(c.parse(i))


[[[1, -2, 0], -5, [1, -4, 1]], -7, 0]
[[1, -2, [0, -5, 1]], -4, [1, -7, 0]]

Like you can see, the nesting of the lists is done a bit different on these two case.

Literals

In above examples only single letters and numbers were used. That is ok as long as they are not reserved keywords or the chosen parentheses and literal wrapper characters. To use sentences that contain spaces and special characters it is safer to make it this way:


In [6]:
i = "('Queen Elizabeth' & 'Philip, Duke of Edinburgh')"
parseInput(i)


Out[6]:
['Queen Elizabeth', -2, 'Philip, Duke of Edinburgh']

By default literals are expected to be wrapped with single ' or double " quotes. Parentheses are assumed to be ( for the right parentheses and ) for the left.

If default parentheses and literal wrappers are not suitable for your needs, you can change them and parse input accordingly:


In [7]:
from pyPLCParser import PLCParser

c = PLCParser(parentheses=['[', ']'], wrappers=['´'])

i = "[´Use´ and ´as you´ and wish]"

c.parse(i)


Out[7]:
[['Use', -2, 'as you'], -2, 'wish']

Negation

Using negation ($NOT$ , $!$ , $¬$) keywords do shape the structure of the output. $NOT$ will add $-1$ value to the result before the item or node.

As a unary operator, negation can be done for the item or to the group of items:


In [8]:
parseInput("""

(!A and !(B or C))

""")


Out[8]:
[[-1, 'A'], -2, [-1, ['B', -4, 'C']]]

Same input could be written many ways, for example:

(!A and !B and !C) is same as (!A !B !C)

Note that the meaning of !(A and B and C) however is different. It means the negation of a group where all items A, B and C exists. If only one or two of the group items existed, then negation wouldn't be true.

Multinary operations

In addition to unary and binary operations, this library also handles multinary operation by prefix notation:


In [9]:
# multiple and operands
i1 = '(& 1 1 1 0)'
o1 = parseInput(i1)
print(o1)
print(i1, " = ", deformatInput(o1))


[[[1, -2, 1], -2, 1], -2, 0]
(& 1 1 1 0)  =  ( ( 1 and 1 ) and 1 ) and 0

XOR operator

This brings us to the $XOR$ operator. $XOR$ is an exclusive or, which states that either A or B should exist, but not both at the same time. Same behaviour could be achieved by $OR$, $AND$, and $NOT$ clause groups. Let us demonstrate it by few examples:


In [10]:
# xor logic -> one of the group, but not all
i1 = '(A ^ B)' # or just (^(A B))
o1 = parseInput(i1)
print(o1)

# xor logic with and, or, and not operators #1
i2 = '((A or B) and !(A and B))'
o2 = parseInput(i2)
print(o2)

# xor logic with and, or, and not operators #2
i3 = '((A and !B) or (!A and B))'
o3 = parseInput(i3)
print(o3)

# xor logic with and, or, and not operators #3
i4 = '((A or !B) and (!A or B))'
o4 = parseInput(i4)
print(o4)


['A', -3, 'B']
[['A', -4, 'B'], -2, [-1, ['A', -2, 'B']]]
[['A', -2, [-1, 'B']], -4, [[-1, 'A'], -2, 'B']]
[['A', -4, [-1, 'B']], -2, [[-1, 'A'], -4, 'B']]

Apparently using $XOR$ can save a lot of space!

Validate

ValidateInput method is used to validate given clause in string format. It can be used to roughly check that parentheses and literals are correctly formed. Then it is safer to use parseInput and evaluateInput functions.


In [11]:
from pyPLCParser import validateInput

# input has extra )
print (validateInput('(A or B))'))
# input should be ok
print (validateInput('(A or B)'))


False
True

Deformat

Of cource it is good to have a method to deformat native list structure back to the string clause representation.

With an optional argument, one can use special character abbreviations for logic operators. By default formal keywords are used on output. Operator representation types are: word, char and math:


In [12]:
from pyPLCParser import PLCParser
c = PLCParser()
# set input (A and B)
i = ['A', -2, 'B']
print(c.deformat(i, operator_type="word"))
print(c.deformat(i, operator_type="char"))
print(c.deformat(i, operator_type="math"))


'A' and 'B'
'A' & 'B'
'A' ∧ 'B'

Evaluate

EvaluateInput function takes propositional logic clause in string or array format and evaluates it. Usually, if everything is correct on input, output is either true or false.


In [13]:
from pyPLCParser import evaluateInput

i = "(1 or 0)"
o = evaluateInput(i)
print("%s => %s" % (i, o))

i = "(1 and 0)"
o = evaluateInput(i)
print("%s => %s" % (i, o))

i = "(true and false)"
o = evaluateInput(i)
print("%s => %s" % (i, o))

i = "(p and q)"
truth_table = {'p': True, 'q': False}
o = evaluateInput(i, truth_table)
print("%s => %s" % (i, o))


(1 or 0) => True
(1 and 0) => False
(true and false) => False
(p and q) => False

The last example uses truth table to define, how different operands should be interpreted. By default only numbers 1 and 0, and booleans true and false, can be interpreted accordingly.

Last example demonstrates $NAND$, $XNOR$ and $NOR$ operators:


In [14]:
i = "(1 nand 1)"
o = evaluateInput(i)
print("%s => %s" % (i, o))

i = "(1 xnor 0)"
o = evaluateInput(i)
print("%s => %s" % (i, o))

i = "(1 nor 0)"
o = evaluateInput(i)
print("%s => %s" % (i, o))


(1 nand 1) => False
(1 xnor 0) => False
(1 nor 0) => False

PHP version

PHP version of the PLCParser class is practically same as Python having the same API methods and functionality. For example parseInput is called something like this:


In [15]:
%%html

require_once('./src/elonmedia/plcparser/php/PLCParser.php')
print_r(PLCParser::parseInput("('A' or 'B')"));


require_once('./src/elonmedia/plcparser/php/PLCParser.php') print_r(PLCParser::parseInput("('A' or 'B')"));

And output would be:


In [16]:
%%html
Array
(
    [0] => 
    [1] => Array
        (
            [0] => Array
                (
                    [0] => Array
                        (
                            [0] => A
                            [2] => B
                        )

                )

        )

)


Array ( [0] => [1] => Array ( [0] => Array ( [0] => Array ( [0] => A [2] => B ) ) ) )

Javascript version

Same applies to Javascript library. You need to include library first and then you can use same API methods (validateInput, parseInput, EvaluateInput, deformatInput) or build object fromthe PLCParser "class" for more specific usage:


In [17]:
%%html

<script src="./dist/PLCParser.min.js"></script>

<script>

var v = PLCParser.validateInput("(A and B (C | D) !F)")

console.log(v)

var p = PLCParser.parseInput("(A and B (C | D) !F)")

console.log(p)

var e = PLCParser.evaluateInput("(true or false)")

console.log(e)

var d = PLCParser.deformatInput([-1, ['A', 'B']])

console.log(d)

</script>


For both PHP and Javascript version, see PLCParser demo application deployed in Heroku for live testing.

The MIT License

Copyright © 2017 Marko Manninen