The purpose of this assignment is to extend the small, logical language that you built last week to include the evaluation of logical expressions. In addition, we will begin exploring a much larger programming language built on a logical foundation: Prolog.
/cs/cs60/as/a7/Parser.java.solsand
/cs/cs60/as/a8/Parser.javaparses logical expressions, which may or may not include variables. Such logical expressions fall into 3 broad categories:
For this problem, you will want to start from your Parser.java file (from assignment 7). You may also start from the solutions file in /cs/cs60/as/a8/Parser.java. The problem is to extend the parser to determine if a given expression is a tautology, unsatisfiable, or satisfiable, but not a tautology. In addition to printing out the parse tree as before, now the program should output the appropriate string from those three possibilities.
For example, some correct I/O exchanges for this assignment include
Type an expression > a + a' (OR (var a) (NOT (var a))) tautology Type an expression > a * a' (AND (var a) (NOT (var a))) unsatisfiable Type an expression > a (var a) satisfiable, but not a tautology Type an expression > 0 > 1 (IMP (const false) (const true)) tautology Type an expression > 1 > 0 (IMP (const true) (const false)) unsatisfiable
You will want to keep the parsing code as is. One way to do this cleanly is to change only one thing in the Parser class, inside main: after the lines
System.out.println(parseTree); // Print the resulting parse tree
System.out.println(""); // Print an extra blank line
add the lines
// The next line prints one of the following:
// tautology
// unsatisfiable
// satisfiable, but not a tautology
System.out.println(parseTree.truthChecker());
System.out.println(""); // Print an extra blank line
All of the other changes can be in the hierarchy of expressions, i.e.,
the base class Expr and its many derived classes.
First, inside the abstract base class Expr you will want to
add a method with the signature
public String truthChecker()This truthChecker function will implement a procedure known as Boole-Shannon reduction. Basically, it will choose a variable from the invoking expression and then create two new expressions, one in which that variable has been set to true and one in which that variable has been set to false. Those two expressions are then recursively determined to be of one of the three types: (1) tautology, (2) unsatisfiable, or (3) satisfiable, but not a tautology. Depending on the combination of results obtained, the correct classification of the original expression is returned. Specifically, if both resulting expressions are tautologies, so is the original expression. If both resulting expressions are unsatisfiable, so is the original. Otherwise, the original expression is satisfiable, but not a tautology.
The suggested approach to this assignment is to exploit object-oriented programming in a fundamental way: each derived type of expression will handle its share of the work in the process of truth checking. I would add the following methods both to the Expr class and all of the derived classes in which they need to be overridden:
public String getAVar()
- e.getAVar() returns a String that is the variable name of some
variable appearing in the expression e, it returns null
if there are no variables in the expression.
public Expr substitute(boolean b, String varName)
- e.substitute(b,varName) returns a new expression in which all
occurrences of the VarExpr with variable name varName have
been substituted with with a ConstExpr whose value is the
boolean b.
public boolean evaluate()
- e.evaluate() returns a boolean which is the value of the
whole expression e. The expression e should have no
variables in it; otherwise this method should print an error
message. It is also possible to have this method return a String
(either "tautology" or "unsatisfiable"), though I've found
it easier to use the boolean value.
Because these methods distribute
their work throughout the hierarchy of Exprs, you avoid
having to write code to wade through parse trees! (In the
rex extra credit you do have to write that code, but it's comparatively short.)
Note that the code you write (if any) for these methods in the base Expr class itself will be called for a derived class if the derived class does not override the methods. This is a convenient way to provide default behavior, e.g., for printing errors. Even if you never expect Expr's methods to get called, you need to have the above methods in the Expr base class so that the compiler knows that they are available for all expressions of any type derived from Expr.
Let n represent the number of distinct variables in an expression. The Boole/Shannon reduction described above runs in exponential time, that is, it requires 2-to-the-nth-power calls to evaluate. Extra credit will be given to anyone who can optimize the truth-checking algorithm to require only polynomially many calls to evaluate, e.g., n squared, n cubed, or the like, or show that such a polynomial algorithm can't exist. Don't worry too much about this problem, however -- it's considered the most important open question in computer science!
In the /cs/cs60/assignments/a8 directory there are test files you can use to check that your application is working properly. They are named Test#.in, with # between 1 and 20. The expected output is in Test#.out.
Propositional Logic is covered in Chapter 9 of the text. Also, most (hopefully all) of what you'll need will be discussed in class.
Getting Started with prolog
?-
?- ['simpsons.pl'].
?- parent(homer,bart).
yes.
?- parent(maggie,X).
no.
?- parent(marge,X).
X = bart ; <-- typing a semicolon asks to see more valid bindings
X = lisa ;
X = maggie ;
no.
notSiblings(X,Y) :- person(X), person(Y), \+siblings(X,Y).Of course, for lists and numerical values, we don't have the luxury of a person predicate, so it's best to place negated predicates as late as possible in the code so that variables will be bound before reaching them.
To try it out, copy the file
/cs/cs60/as/a8/simpsons.plto your working directory. There you will see a large number of prolog predicates (rules) that describe the familty tree of the Simpsons. At the end of the file, you should add the following predicates:
Remember that you can trace your code by typing trace. at the Prolog prompt. You can turn off tracing by typing notrace. Admittedly, trace can be pretty cryptic, but if you encounter weird behavior, you might want to try turning tracing on to see what's happening.
You may add additional helper predicates that your predicates will use -- for example, the ones we discussed in class may help. Also, look for ways to reuse work from earlier predicates to write the later ones from the above list.
Checking your code:
You may check your code by including
predicates like ans1(S), ans2(S), ... from the file
/cs/cs60/as/a8/simpsonsCheck.plinto your simpsons.pl file and running those predicates at the prolog prompt. The answers are in comments in the simpsonsCheck.pl file.
You should submit your work for these two problems by running
cs60submit Parser.java cs60submit simpsons.pland even cs60submit truthChecker.rex if you decide to work on the (real) Extra Credit.
For the extra credit, you need to write a rex function named truthChecker(E) that does the same thing (basically) as the one you've written in your Parser.java file. The truthChecker function takes in a single input, E, an expression in the same form as output by Assignment 7's parsing program except the will be in Rex lists rather than S-expressions (S-expressions are the lists we've been using with all of the parentheses). Here are some example inputs:
Put your truthChecker function in a file named
truthChecker.rex; the command cs60submit truthChecker.rex
will submit it.
Hints
rex > truthChecker([AND, [var, a], [var, b]]);
satisfiable, but not a tautology
rex > truthChecker([NOT, [OR, [const, true], [var, a]]]);
unsatisfiable
rex > truthChecker([EQ, [var, b], [NOT, [var, b]]]);
unsatisfiable
rex > truthChecker([IMP, [var, p], [IMP, [var, q], [var, p]]]);
tautology