This is Part Four, the fifth part of a series of entries describing an implementation of the Prolog language in the NetBeans IDE.
Part Zero is here.
Part One is here.
Part Two is here.
Part Three is here.
What We'll Be Doing
So far we've created an editor for Prolog source code within the NetBeans IDE that supports navigation and syntax highlighting. Most of the previous stuff we've done can be found in other tutorials, but now we're going to branch out from there and explore some unknown territory. Specifically, we're going to build a visual predicate mapper for Prolog that will look like this when it's done:
Frankly, I'm not sure how useful the mapper really is in actual Prolog programming, but so far I have used it to find missing references in Prolog code I wrote and to get an overview of code others have written. For these use cases the diagram thing really does come in handy.
My starting point for this segment of the project was the excellent tutorial, "A Visual Database Explorer for NetBeans". Thanks to Toni Epple for providing a lot of useful information. I should also confess here that I'm far from an expert in the NetBeans Visual Library, but learning more about it is high on my to-do list. The library so far seems easy to use, featureful and well worth investigating further.
Creating A First-Cut Diagram
The first thing we'll do is create a static sample diagram in GeeWhiz using techniques described in the Visual Database Explorer tutorial, so let's create a new action in the project. This action will add an item to the main menu that opens our diagram window. Right-click on GeeWhiz in the Projects tab and select "New" "Action".

This starts the New Action Wizard. In the first screen of the wizard,

for action type click "Conditionally Enabled" and select "DataObject" from the drop-down box. This tells the wizard that we want our diagram associated with our Prolog DataObject, since it isn't relevant to other file types like Java source. Make sure "User Selects One Node" is checked, then click "Next".

In the next wizard screen, select "Other" for a category then check "Global Menu Item". For menu, select "View", and for position select "<separator> - HERE - Web Browser". Also check the "Separator After" box, then click "Next".
In the next screen of the wizard,

enter "ShowDiagram" for the class name and "Show Prolog Diagram" for the display name. For an icon, we're going to use a variation of the Black Star of Prolog that looks like this.
Depending on your browser you can probably right-click this image and save it on your computer somewhere other than in your project, then enter that path and file name in the Icon field of the wizard window. The wizard will copy the icon into the project. Make sure the package line is filled in with your package, then click the "Finish" button. Wait for NetBeans to add the new action to our project, then close any open documents.
Next we need to create a TopComponent, which is essentially a top-level window in the NetBeans IDE. Right-click the project again and select "New" "Window Component". This starts the New Window Wizard, as you might expect.

In the wizard, use VProlog for a class name prefix and this icon.
It is a copy of the icon we used earlier, so if you want you can just copy it on your computer. Enter the icon path and file name in the wizard and it will copy it into your project. Again, make sure the package name is filled in then click "Finish". The IDE will generate a bunch of files for your project, all of which you can close for now.
Now we need to edit some files, but first we need to add the Visual Library to our project. Right-click on the GeeWhiz project, select "Properties", and select "Libraries" as the category. To the right of the Module Dependencies window click the "Add" button to bring up the Add Module Dependencies window.

Find "Visual Library API" and select it, then click "OK". Note that this differs from the Visual Database Explorer tutorial because the Visual Library API is included in NetBeans 6.1 where it wasn't in version 5.5 used in the tutorial. Now your project libraries should look like this.

Click "OK" to close the project properties window. Next, open the ShowDialog.java file and replace the performAction method with this code:
protected void performAction(Node[] activatedNodes) {
DataObject dataObject;
VPrologTopComponent win;
dataObject = activatedNodes[0].getLookup().lookup(DataObject.class);
win = VPrologTopComponent.findInstance();
win.open();
win.requestActive();
// win.loadProlog(dataObject);
}
Notice that the last line is commented out. We'll add this back in later. Right-click in the class and select "Fix Imports" to fix up the import statements. Save and close the file.
Now we'll edit the top component. Open VPrologTopComponent.java. In the design view, click on "Scroll Pane" in the Swing Containers palette and drop it onto the frame. Make it fill the frame. Then click "Panel" in the palette and drop it onto the JScrollPane you just created. Right-click the JPanel and change the layout to BorderLayout. Now you should have a design view that looks like this.

Switch to source view and scroll down to the getPersistenceType method and change PERSIST_ALWAYS to PERSIST_NEVER. Save and close the file.
Now we're going to create a class to render the diagram. Create a new Java class called "VPrologGraphScene" and replace the empty class with the following code:
public class VPrologGraphScene extends VMDGraphScene {
private static final Image IMAGE_NODE =
Utilities.loadImage ("org/hulles/geewhiz/node.png"); // NOI18N
private static final Image IMAGE_EXTERNAL =
Utilities.loadImage ("org/hulles/geewhiz/external.png"); // NOI18N
private static final Image IMAGE_ITEM =
Utilities.loadImage ("org/hulles/geewhiz/item.gif"); // NOI18N
private static int pinID = 1;
private static int edgeID = 1;
/** Creates a new instance of VPrologGraphScene */
public VPrologGraphScene() { // demo
createNode (this, 100, 100, IMAGE_NODE, "Clause1", "Internal", null);
createPin (this, "Clause1", "start", IMAGE_ITEM, "Start", "Element");
createNode (this, 400, 100, IMAGE_NODE, "Clause2", "External",
Arrays.asList (IMAGE_EXTERNAL));
createPin (this, "Clause2", "ok", IMAGE_ITEM, "okCommand1", "Command");
createEdge (this, "start", "Clause2");
createEdge (this, "ok", "Clause1");
}
private void createNode (VMDGraphScene scene, int x, int y, Image image,
String name, String type, List<Image> glyphs) {
VMDNodeWidget widget;
widget = (VMDNodeWidget) scene.addNode (name);
widget.setPreferredLocation (new Point (x, y));
widget.setNodeProperties (image, name, type, glyphs);
scene.addPin (name, name + VMDGraphScene.PIN_ID_DEFAULT_SUFFIX);
}
private void createPin (VMDGraphScene scene, String nodeID, String pinID,
Image image, String name, String type) {
VMDPinWidget pinWidget;
pinWidget = (VMDPinWidget) scene.addPin (nodeID, pinID);
pinWidget.setProperties (name, null);
}
private void createEdge (VMDGraphScene scene, String sourcePinID,
String targetNodeID) {
String localEdgeId;
localEdgeId = "edge" + VPrologGraphScene.edgeID ++;
scene.addEdge (localEdgeId);
scene.setEdgeSource (localEdgeId, sourcePinID);
scene.setEdgeTarget (localEdgeId, targetNodeID +
VMDGraphScene.PIN_ID_DEFAULT_SUFFIX);
}
}
Fix imports (I love that thing!) then save and close the file. Next we need to add some icons to our project. The easiest way to do this is to download one of the project files at the end of this article and unzip / untar the archive to get the icons. Copy them directly into your project's /src/org/myorg/geewhiz directory.
As a final step, we're going to go into Bundle.properties in our project then right-click on one of the properties and select "Edit". This file is where descriptions and such are kept. Edit the descriptions of
the properties as follows (or make up your own):
CTL_ShowDiagram=Show Prolog Diagram
CTL_VPrologAction=Prolog Diagram
CTL_VPrologTopComponent=Prolog Diagram Window
HINT_VPrologTopComponent=This is a diagram of the current Prolog program
Testing The First-Cut Diagram
Now it's time to test our changes. Clean and build the main project then install it into the target IDE (see earlier steps for instructions on doing this). When the installation completes and your personal domain is created, open the BraveNewWorld project. Let it index its little heart out then open fib1.pro.
As a first step in testing, make sure everything that worked before still works. Click in fib1.pro and make sure the Navigator syncs with the editor pane and that the syntax is colored as it was before. Now go up to the menu and click "View". Our new menu item, "Show Prolog Diagram" should be there and be enabled, complete with icon.

Click on the selection. If it's not enabled, don't panic, you should still be able to click it. The selection should be contextually enabled (that is, it should be grayed-out when you don't have a Prolog file open and lit up when you do) but in my experience the contextual lighting up / graying out is a little iffy. I've always still been able to click on it to open the window though. You should get a window that looks like this.

Spiffy, eh? Experiment with moving the nodes around on the diagram and clicking the show/hide arrow on the nodes. Behold the awesome power of Visual Library VMD. When you're done, close the window. Now go to "Window" in the main toolbar and select "Prolog Diagram".

This should bring up the same sample window we saw a moment ago. This is VPrologAction.java functioning, the default action created when we made our top component. Eventually we'll get rid of it, but for testing we'll let it be for a while. When you're done testing / playing, close the target IDE.
Adding Prolog Structure Analysis To The Diagram
Now that we can display a sample visual model, let's add Prolog support to the project. First we're going to create a Java data structure class to represent a Prolog clause and its attributes. Create a new Java class off your package node in the project and call it PrologClause. Replace the empty class with this:
public class PrologClause {
private String name;
private Integer arity;
private List<PrologClause> body;
private StringBuffer textBuffer;
private Integer instanceCount;
public PrologClause(String name, Integer arity) {
this.name = name;
this.arity = arity;
this.body = new ArrayList<PrologClause>();
this.textBuffer = new StringBuffer();
this.instanceCount = 1;
}
public Integer getInstanceCount() {
return instanceCount;
}
public void incrementInstanceCount() {
instanceCount++;
}
public void setInstanceCount(Integer instanceCount) {
this.instanceCount = instanceCount;
}
public String getText() {
return textBuffer.toString();
}
public void setText(String text) {
this.textBuffer = new StringBuffer(text);
}
public void appendText(String text) {
this.textBuffer.append(text);
}
public void setBody (List<PrologClause> body) {
this.body = body;
}
public void appendBody(PrologClause clause) {
body.add(clause);
}
public Integer getArity() {
return arity;
}
public List<PrologClause> getBody() {
return body;
}
public String getName() {
return name;
}
public static PrologClause findClause(List<PrologClause> clauses,
PrologClause clause) {
for (PrologClause c : clauses) {
if (c.getName().equals(clause.getName())) {
if (c.getArity().equals(clause.getArity())) {
return c;
}
}
}
return null;
}
}
Fix imports and save it. The class we just created is pretty straightforward. A Prolog clause is identified by its name and its arity. Arity is a Prolog term that means argument count. All clauses with the same name and same arity are considered part of a single predicate, traditionally denoted as name/arity. Thus, all the clauses in our fib1.pro code are instances of a single predicate, fib/2. In our PrologClause class, we're storing the name, arity, text body, and a count of clauses that comprise the predicate in the source code. See what's wrong? I misnamed the class -- it should have been PrologPredicate, since we're not generating an instance for each clause, only for each predicate. I promise I'll go back and change it One Day Soon. But for now, let's go on. You will also see in this class a list of clauses mysteriously called body. This is another unfortunate name, but what it represents is a list of predicates that are "called" by this predicate in one or more of its clauses, either in the arguments to the clause or in the body of the clause itself.
To give you an example, take the following nonsense Prolog source code file:
some_list_function(A,B,C) :-
another_function(A,B),
do_something_else(B,C).
some_list_function(param_function(A,D),B,C) :-
B is C.
This would generate one PrologClause object, with a name "some_list_function", an arity of 3, an instance count of 2 (there are two clauses), the text that is typed above, and a body list consisting of another_function/2, do_something_else/2, and param_function/2. As you can see, the body list resembles a list of functions called by this predicate.
Now we need some code that creates the PrologClause objects we just defined. Create a new Java class and call it PrologAST. Copy the following code into it:
public class PrologAST {
private ASTNode rootNode = null;
private DataObject dataObject;
private OutputWriter outputWriter;
private static ASTNode saveNode;
public PrologAST(DataObject dataObject) {
InputOutput io;
// get an output window tab using
// starting example from I/O API javadoc
io = IOProvider.getDefault().getIO("Prolog", false);
io.select();
outputWriter = io.getOut();
this.dataObject = dataObject;
refresh();
}
private void refresh () {
// TODO: put this in a Runnable?
ASTNode root;
root = getASTRoot ();
if (root == rootNode) {
return;
}
rootNode = root;
}
private ASTNode getASTRoot() {
EditorCookie eCookie;
Document doc;
ParserManagerImpl pImpl;
eCookie = dataObject.getLookup ().lookup(EditorCookie.class);
if (eCookie == null) {
return null;
}
doc = eCookie.getDocument ();
if (doc == null || !(doc instanceof NbEditorDocument)) {
return null;
}
pImpl = ParserManagerImpl.getImpl(doc);
if (pImpl == null) {***REF(pl.gif)
return null;
}
return pImpl.getAST ();
}
public List<PrologClause> getClauses() {
List<PrologClause> clauses;
PrologClause clause;
PrologClause existingClause;
String fName;
Integer fArity;
List<ASTItem> children;
ASTNode node = null;
String nodeName;
ASTNode bodyNode;
String statementText;
clauses = new ArrayList<PrologClause>();
children = rootNode.getChildren();
if (children == null) {
return clauses;
}
dumpItems(children);
for (ASTItem item : children) {
if (item instanceof ASTNode) {
node = (ASTNode) item;
nodeName = node.getNT();
if (nodeName.equals("Statement")) {
statementText = node.getAsText();
bodyNode = node.getNode("ListOfStructures");
clause = getClause(node);
if (clause == null) {
continue;
}
existingClause = PrologClause.findClause(clauses, clause);
if (existingClause == null) {
clause.setText(statementText);
addReferences(clause, saveNode);
if (bodyNode != null) {
addReferences(clause, bodyNode);
}
clauses.add(clause);
} else {
existingClause.appendText(statementText);
addReferences(existingClause, saveNode);
if (bodyNode != null) {
addReferences(existingClause, bodyNode);
}
existingClause.incrementInstanceCount();
}
}
}
}
dumpClauses(clauses);
return clauses;
}
private PrologClause getClause(ASTNode node) {
PrologClause clause;
ASTNode functionNode;
ASTNode functorNode;
ASTNode argNode;
ASTToken token;
String functor;
Integer arity;
functionNode = findChildWithName(node, "Function");
if (functionNode == null) {
return null;
}
functorNode = functionNode.getNode("Functor");
if (functorNode == null) {
return null;
}
token = functorNode.getTokenType("identifier");
if (token == null) {
// built-in function
return null;
}
argNode = functionNode.getNode("ListOfStructures");
saveNode = argNode;
if (argNode == null) {
return null;
}
dumpNode(functorNode);
dumpItems(functorNode.getChildren());
functor = functorNode.getAsText();
arity = getArity(argNode);
clause = new PrologClause(functor, arity);
return clause;
}
private void addReferences(PrologClause clause, ASTNode node) {
List<ASTItem> children;
PrologClause embeddedClause;
ASTNode child;
children = node.getChildren();
if (children == null) {
return;
}
for (ASTItem item : children) {
if (item instanceof ASTNode) {
child = (ASTNode) item;
embeddedClause = getClause(child);
while (embeddedClause != null) {
if (PrologClause.findClause(clause.getBody(),
embeddedClause) == null) {
// only need one per customer
clause.appendBody(embeddedClause);
}
embeddedClause = getClause(saveNode);
}
}
}
return;
}
private ASTNode findChildWithName(ASTNode node, String name) {
// find the (depth-first search) FIRST ASTnode with name in node's
// descendants
List<ASTItem> children;
ASTNode child;
ASTNode retNode = null;
children = node.getChildren();
if (children == null) {
return null;
}
for (ASTItem item : children) {
if (item instanceof ASTNode) {
child = (ASTNode) item;
if (child.getNT().equals(name)) {
return child;
}
retNode = findChildWithName(child, name);
if (retNode != null) {
return retNode;
}
}
}
return null;
}
private void dumpItems(List<ASTItem> items) {
ASTNode node;
for (ASTItem item : items) {
if (item instanceof ASTNode) {
node = (ASTNode) item;
dumpNode(node);
} else {
/*
outputWriter.println("Item: " + item.toString());
outputWriter.println("\tclass: " + item.getClass());
outputWriter.println();
*/
}
}
}
private void dumpNode(ASTNode node) {
outputWriter.println("Node: " + node.getNT());
outputWriter.println("\ttext: " + node.getAsText());
// outputWriter.println("\tprint: " + node.print());
outputWriter.println();
}
private Integer getArity(ASTNode node) {
Integer arity = 0;
List<ASTItem> children;
ASTNode child;
children = node.getChildren();
if (children == null) {
return null;
}
for (ASTItem item : children) {
if (item instanceof ASTNode) {
child = (ASTNode) item;
if (child.getNT().equals("Structure")) {
arity++;
}
}
}
return arity;
}
private void dumpClauses(List<PrologClause> clauses) {
List<PrologClause> eClauses; // embedded clauses
for (PrologClause c : clauses) {
outputWriter.println("Predicate:" + c.getName());
outputWriter.println("Arity:" + c.getArity());
outputWriter.println("Instances:" + c.getInstanceCount());
outputWriter.println("Text:" + c.getText());
eClauses = c.getBody();
for (PrologClause e : eClauses) {
outputWriter.println("Embedded Predicate:" + e.getName() +
"/" + e.getArity());
}
outputWriter.println();
}
}
}
This is a obtuse little class, but in general what it does is search the AST for Prolog clauses and deconstructs them into predicates, then builds PrologClauses for them if they don't exist yet or increments the count if they do. Text for each clause in a predicate is added to the PrologClause, and any foreign clauses called with the current clause are added to the body list. When we're done searching the AST we have a nice little group of predicates ready to diagram.
Note: this AST is of course the same one that appears when you click "AST View" in "Window/Other" that we looked at earlier. If you want to compare the text output of PrologAST to the AST view of fib1.pro, the code makes a lot more sense. Plus you can see how I was able to write the class in the first place.
A couple of things in PrologAST are worth examining more closely. The IOProvider/OutputWriter stuff has not been introduced yet in this series, but what it does is create a "Prolog" output tab and writes to it. Technically I shouldn't be hanging on to the OutputWriter by declaring it at the class level, but it works for now and I'll probably end up getting rid of the text output entirely anyway. When I'm sure it works correctly. Probably the same day I rename PrologClause to PrologPredicate.
Another thing to note is the ParserManagerImpl class in getASTRoot. I needed to obtain the root AST node somehow, and the only way I could readily find is by using ParserManagerImpl, which unfortunately is not part of the API. This one line causes us to use an implementation version of the GLF, a process I have to describe below. If anyone knows of an API way to get the root AST node, please let me know so I can get rid of the versioning and use the public API. There is essentially no documentation for the whole GLF, so finding something like that is a non-trivial task. Trust me.
To get rid of the red squiggles in the source code, we need to add some module dependencies to our project. Right-click on the project, select libraries, and click "Add Module Dependencies". Add the "Editor," "Editor Library," and "IO APIs" libraries. (You have to add them one at a time.) Now, as mentioned above, we need to change the Generic Library Framework to use an implementation version, as opposed to the public API. In your project, find "Generic Libary Framework" in "Libraries", right-click it and select "Edit".

Click "Implementation Version", type "0" in the Major Version, and click "OK". Now PrologAST should be compilable once you fix imports again. Whew.
Now we need to edit VPrologGraphScene to use our new predicate objects. Open it up and add the following code right below the first constructor:
public VPrologGraphScene(DataObject dObj) {
PrologAST pTree;
List<PrologClause> clauses;
List<PrologClause> embedded;
String nodeID;
Integer instances;
String newPinID;
PrologClause existingClause;
String eNodeID;
List<String> eNodes;
pTree = new PrologAST(dObj);
clauses = pTree.getClauses();
// create all primary nodes first so they're available
// to create edges to....
for (PrologClause clause : clauses) {
instances = clause.getInstanceCount();
nodeID = makeNodeID(clause);
createNode(this, randXPoint(), randYPoint(), IMAGE_NODE,
nodeID, instances.toString() + " instances", null);
}
// now create pins and edges
eNodes = new ArrayList<String>();
for (PrologClause clause : clauses) {
nodeID = makeNodeID(clause);
embedded = clause.getBody();
for (PrologClause e : embedded) {
eNodeID = makeNodeID(e);
newPinID = "pin" + VPrologGraphScene.pinID++;
createPin(this, nodeID, newPinID, IMAGE_ITEM,
eNodeID, "Embedded");
existingClause = PrologClause.findClause(clauses, e);
if (existingClause == null) {
if (!eNodes.contains(eNodeID)) {
// externally defined (?)
createNode(this, randXPoint(), randYPoint(), IMAGE_NODE,
eNodeID, "External", Arrays.asList (IMAGE_EXTERNAL));
eNodes.add(eNodeID);
}
}
createEdge(this, newPinID, eNodeID);
}
}
}
private String makeNodeID(PrologClause clause) {
String name;
Integer arity;
name = clause.getName();
arity = clause.getArity();
return name + "/" + arity.toString();
}
private int randXPoint() {
return (int) (Math.random() * 800);
}
private int randYPoint() {
return (int) (Math.random() * 800);
}
The new constructor grabs the list of PrologClause (predicate) objects and constructs pretty little images on our diagram using the same createNode etc. methods as before. For the entire program, see the archive files at the bottom of the entry. Fix imports if necessary and save and close the file.
Next, we have to change VPrologTopComponent to call the new constructor with a passed DataObject. Open it and add the following method to the class:
public void loadProlog(DataObject dObj) {
scene = new VPrologGraphScene(dObj);
jPanel1.removeAll();
JComponent component = scene.createView();
jPanel1.add(component);
}
Notice that we still have the VPrologGraphScene constructor without arguments called in the top component constructor. Our old friend VPrologAction will still call that, so we can test the functionality of the modeling without invoking PrologAST if necessary.
Now we need to edit ShowDiagram, so open it up and remove the slashes on the win.loadProlog line to uncomment it. Now we should be ready to rock and roll.
Testing The Prolog Model
Now you should clean and build the main project then install it in the target IDE and open the BraveNewWorld project. Open up our old buddy fib1.pro and then go to the menu and select "View" "Show Prolog Diagram". You should see something like this.

Woohoo! This is correct. Unfortunately it isn't very interesting. We need a more complicated Prolog program. Create a new Prolog file (!) called sieve.pro in your project and replace the sample contents with this, a classic Prolog implementation of the Sieve of Erastosthenes:
/*
Sieve of Erastosthenes
primes(Limit, Ps) instantiates a list Ps containing all
primes between 1 and Limit
from Clocksin and Mellish, "Programming in Prolog"
*/
primes(Limit, Ps) :-
integers( 2, Limit, Is),
sift( Is, Ps ).
integers(Low, High, [Low|Rest]) :-
Low =< High,
!,
M is Low+1,
integers(M, High, Rest).
integers(_,_,[]).
sift([], []).
sift([I|Is], [I|Ps]) :-
remove(I,Is,New),
sift( New, Ps ).
remove(_, [], []).
remove(P, [I|Is], [I|Nis]) :-
not(0 is I mod P),
!,
remove(P, Is, Nis).
remove(P, [I|Is], Nis) :-
0 is I mod P,
remove(P, Is, Nis).
Save the file and perform "Show Prolog DIagram" from the menu again. You should get this.

For testing, you can use various public domain Prolog source files available on the Internet. Try "The Public Domain Prolog Library" for starters. When you're done, you can close the target IDE.
Well, we're done with this segment. We now have a Prolog IDE complete with fins and an automatic headlight dimmer. What we need next is in-place compilation so we don't have to continually switch between NetBeans and our Prolog compiler. You got it -- it's coming next. In the meantime, if you want you can alter PrologAST to not print the informative text, and you can delete VPrologAction.java. If you delete VPrologAction, be sure to also delete the actions in layer.xml ("this layer" then delete the instance in "Actions" "Window" and the shadow in "Menu" "Window").
Files From This Entry