Link to home
Start Free TrialLog in
Avatar of Dhope
DhopeFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Inheritance, interfaces...

Hi,

I've written a propositional logic grammar for a JavaCC parser that compiles fine.

JavaCC generates a SimpleNode class to represent the input.  I have methods to be added to the SimpleNode class but would prefer to leave SimpleNode as untouched as possible incase the JavaCC grammar ever needs to be modified, which would lead to the SimpleNode class being regenerated and potentially overwriting the added methods.

So far to get around this I've created a 'SimpleNodeSuperclass' that SimpleNode extends, and put the extra methods inside SimpleNodeSuperclass instead.
I then intend to create a subclass of SimpleNode, called Sentence.  Sentence will be where I put the logic methods eval(), isContradiction(), isTautology() etc.

So something like

[code]public interface Node {
      // Generated by JavaCC
      // Contains generic Node operations - adding children, traversing the tree...
}

public abstract class SimpleNodeSuperclass {
      // Added by me
      // Contains a few extra bits needed in SimpleNode - setting a text label for example.
}

public class SimpleNode extends SimpleNodeSuperclass implements Node {
      // Generated by JavaCC
      // Implements operations required by Node.
}

public class Sentence extends SimpleNode {
      // Added by me
      // Contains logic operations

      public Boolean eval() {...}
      public Boolean isContradiction() {...}
      public Boolean isTautology() {...}

      // etc.
}[/code]

I'm having problems when I try to create a new instance of Sentence though.  Previously, to create a SimpleNode, I would do the following

[code]PropLogicParser parser = new PropLogicParser(new StringReader("a & !b"));
SimpleNode root = parser.query();
root.dump();[/code]

Which would output

[code]Root
 And
  Atom
  Not
   Atom[/code]

as expected.

The problem seems to be that of the other JavaCC generated files refer to Nodes and SimpleNodes so the normal operations don't work with the 'Sentence' class.
I'm sure I'm just missing something obvious with my grasp of Abstract vs Interface vs Inheritance but my brain seems to have slowed to a crawl this evening :(

Cheers
Avatar of Dejan Pažin
Dejan Pažin
Flag of Austria image


>> The problem seems to be that of the other JavaCC generated files refer to Nodes and SimpleNodes so the normal operations don't work with the 'Sentence' class.

Show us exactly what doesnt work. I am only guessing here, cause you didnt post the code.

If the call parser.query() returns SimpleNode you can not assign a Sentence to the returned value (unless you cast the result to Sentence). Its because:

Sentence is a SimpleNode but SimpleNode is not a Sentence.
Avatar of Dhope

ASKER

When calling eval() for example.
The simple way of doing it would be to put eval() within SimpleNode.  I don't want to do that because SimpleNode is generated.  I considered making a Sentence class that has a SimpleNode.
(see first half of the code snippet)


The eval() method is all well and good, but because a SimpleNode's children are SimpleNodes too then you can't recursively call eval() on the children without first defining an eval for type SimpleNode - and if I did that then there'd be no point making one for Sentence in the first place.
Again, the reason I don't want to edit SimpleNode directly is because it's generated by JavaCC.

What I did before was just have a completely separate class that only contained helper methods, so I'd just use
LogicOperations.eval(mySimpleNode, myInterpretation);
instead of
mySimpleNode.eval(myInterpretation);
(see 2nd half of the code snippet)

package logic;
 
import parser.SimpleNode;
 
public class Sentence {
 
	final private int	ROOT		= 0;
	final private int	VOID		= 1;
	final private int	EQUIVALENT	= 2;
	final private int	IMPLIES		= 3;
	final private int	OR			= 4;
	final private int	AND			= 5;
	final private int	TRUE		= 6;
	final private int	FALSE		= 7;
	final private int	NOT			= 8;
	final private int	ATOM		= 9;
 
	private SimpleNode	node;
 
	public Boolean eval(Interpretation inter) {
		Boolean val;
		SimpleNode lhs = (SimpleNode) node.jjtGetChild(0);
		SimpleNode rhs = (SimpleNode) node.jjtGetChild(0);
 
		switch (node.getType()) {
			case ROOT:
				val = lhs.eval(inter);
				break;
			case VOID:
				val = null;
				break;
			case EQUIVALENT:
				val = (lhs.eval(inter) == rhs.eval(inter));
				break;
			case IMPLIES:
				val = (!lhs.eval(inter) || rhs.eval(inter));
				break;
			case OR:
				val = (lhs.eval(inter) || rhs.eval(inter));
				break;
			case AND:
				val = (lhs.eval(inter) && rhs.eval(inter));
				break;
			case NOT:
				val = !lhs.eval(inter);
				break;
			case ATOM:
				val = inter.get(getLabel());
				break;
			case FALSE:
				val = false;
				break;
			case TRUE:
				val = true;
				break;
			default:
				val = null;
				break;
		}
 
		return val;
	}
 
}
 
 
 
// --------------------------------------------------
// --------------------------------------------------
// --------------------------------------------------
// --------------------------------------------------
// --------------------------------------------------
 
 
package logic;
 
import java.util.*;
import parser.*;
 
public class LogicOperations {
 
	public static Boolean eval(SimpleNode root, Interpretation inter) {
		Boolean val = null;
		SimpleNode left = null;
		SimpleNode right = null;
 
		switch (root.jjtGetNumChildren()) {
		case 1:
			left = root.jjtGetChild(0);
			right = null;
			break;
		case 2:
			left = root.jjtGetChild(0);
			right = root.jjtGetChild(1);
			break;
		default:
			break;
		}
 
		switch (root.getID()) {
		case LogicParserTreeConstants.JJTROOT:
			val = eval(left, inter);
			break;
		case LogicParserTreeConstants.JJTVOID:
			val = null;
			break;
		case LogicParserTreeConstants.JJTCOIMP:
			val = (eval(left, inter) == eval(right, inter));
			break;
		case LogicParserTreeConstants.JJTIMP:
			val = (!eval(left, inter) || eval(right, inter));
			break;
		case LogicParserTreeConstants.JJTOR:
			val = (eval(left, inter) || eval(right, inter));
			break;
		case LogicParserTreeConstants.JJTAND:
			val = (eval(left, inter) && eval(right, inter));
			break;
		case LogicParserTreeConstants.JJTNOT:
			val = !eval(left, inter);
			break;
		case LogicParserTreeConstants.JJTATOM:
			val = inter.get(root.getLabel());
			break;
		case LogicParserTreeConstants.JJTCONSTFALSE:
			val = false;
			break;
		case LogicParserTreeConstants.JJTCONSTTRUE:
			val = true;
			break;
		default:
			val = null;
			break;
		}
 
		return val;
 
	}
 
}

Open in new window



>> I considered making a Sentence class that has a SimpleNode.

Thats the best way to go. The general rule is: preffer composition to inhertance. That means, you create a class Sentence which has a SimpleNode (or collection of SimpleNodes).

>> The simple way of doing it would be to put eval() within SimpleNode.  I don't want to do that because SimpleNode is generated.  I considered making a Sentence class that has a SimpleNode.

Yes, thats the approach I would use.

So, what is the question here really?


Avatar of Dhope

ASKER

The structure of a SimpleNode makes things awkward from what I can see.
A SimpleNode contains itself and 0 or more children.  The children are also SimpleNodes.  Standard tree structure.

If a Sentence has a SimpleNode and I want to evaluate the Sentence then I have to evaluate the SimpleNode it contains.  For the (Propositional Logic) Sentence "a & b" then I have a root SimpleNode of type AND with two children both of type ATOM.
To evaluate the root then I would call eval(children[0]) && eval(children[1])
and the atoms would then return whatever bool they were assigned at the time.

But because the children are also SimpleNodes then eval() needs to exist for SimpleNodes - but if I start writing ways to evaluate a SimpleNode then why am I bothering with the Sentence in the first place?
ASKER CERTIFIED SOLUTION
Avatar of Dejan Pažin
Dejan Pažin
Flag of Austria image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Dhope

ASKER

I ended up just writing a new Sentence class that takes a SimpleNode as a constructor argument and sets up the required values.  That way I'm free from having to work with my own classes without having to worry about SimpleNode etc.

private Sentence(SimpleNode node) {
 
	type = node.getType();
	label = node.getLabel();
 
	switch (node.getType()) {
		case 2:
			children = new Sentence[2];
			type = EQUIVALENT;
			break;
		case 3:
			children = new Sentence[2];
			type = IMPLIES;
			break;
		case 4:
			children = new Sentence[2];
			type = OR;
			break;
		case 5:
			children = new Sentence[2];
			type = AND;
			break;
		case 6:
			type = TRUE;
			break;
		case 7:
			type = FALSE;
			break;
		case 8:
			children = new Sentence[1];
			type = NOT;
			break;
		case 9:
			type = ATOM;
			break;
	}
		switch (node.jjtGetNumChildren()) {
		case 0:
			break;
		case 1:
			children[0] = new Sentence((SimpleNode) node.jjtGetChild(0));
			break;
		case 2:
			children[0] = new Sentence((SimpleNode) node.jjtgetChild(0));
			children[1] = new Sentence((SimpleNode) node.jjtGetChild(1));
			break;
		default:
			break;
	}
 
}

Open in new window