Trivial Prolog in Java

001: package ca.draisey.free.tprolog; // -*- java -*-
002: 
003: 
004: 
005: 
006: 
007: import java.awt.BorderLayout;
008: import java.awt.event.ActionEvent;
009: import java.awt.event.ActionListener;
010: import java.awt.event.WindowAdapter;
011: import java.awt.event.WindowEvent;
012: import javax.swing.Box;
013: import javax.swing.BoxLayout;
014: import javax.swing.JButton;
015: import javax.swing.JFrame;
016: import javax.swing.JLabel;
017: import javax.swing.JTextField;
018: import javax.swing.JTextArea;
019: import javax.swing.JTree;
020: import javax.swing.JPanel;
021: import javax.swing.JScrollPane;
022: import javax.swing.JSplitPane;
023: import javax.swing.Timer;
024: import javax.swing.border.BevelBorder;
025: import javax.swing.border.SoftBevelBorder;
026: import javax.swing.text.Document;
027: import javax.swing.text.PlainDocument;
028: import javax.swing.tree.TreeModel;
029: import javax.swing.tree.DefaultTreeModel;
030: import javax.swing.tree.TreeNode;
031: import javax.swing.tree.MutableTreeNode;
032: import javax.swing.tree.DefaultMutableTreeNode;
033: import javax.swing.tree.TreePath;
034: 
035: // -- the main wrapper --
036: public final class PrologGUI {
037: // ---- static definition and initialization
038:         // the main cheese
039:         public static void main( final String[] argv )
040:         {
041:                 // -- just setup those defaults and let the singleton instance do its stuff --
042:                 try {
043:                         final java.io.FileReader ginDefault = new java.io.FileReader( argv[0] );
044:                         final java.io.FileReader dinDefault = new java.io.FileReader( argv[1] );
045: 
046:                         new PrologGUI().mainLoop( ginDefault, dinDefault );
047:                 }
048:                 catch( java.io.FileNotFoundException x ) {
049:                 }
050:         }
051: 
052: // ---- singleton instance definition and initialization
053:         // the interface between the main thread and the GUI thread
054:         final Inter interpreter = new Inter();
055: 
056:         // the GUI
057:         // the window with all its window manager trimmings
058:         final JFrame mainFrame = new JFrame( "Trivial Prolog" );
059:         {
060:                 mainFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
061:                 mainFrame.setSize( 600, 400 );
062:         }
063:         // just the window contents
064:         final JPanel mainPanel = new JPanel();
065:         {
066:                 mainPanel.setLayout( new BorderLayout() );
067:                 mainFrame.getContentPane().add( mainPanel );
068:         }
069:         // topmost editable goal
070:         final JTextField goalText = new JTextField();
071:         {
072:                 goalText.setBorder( new SoftBevelBorder( BevelBorder.LOWERED ) );
073:                 goalText.addActionListener( new ActionListener() {
074:                         public void actionPerformed( ActionEvent e )
075:                         {
076:                                 interpreter.queryGoal( goalText.getText() );
077:                         }
078:                 } );
079:         }
080:         // query button at top right-hand corner
081:         final JButton querButton = new JButton( "Query!" );
082:         {
083:                 querButton.setBorder( new SoftBevelBorder( BevelBorder.RAISED ) );
084:                 querButton.addActionListener( new ActionListener() {
085:                         public void actionPerformed( ActionEvent e )
086:                         {
087:                                 interpreter.queryGoal( goalText.getText() );
088:                         }
089:                 } );
090:         }
091:         {
092:                 final Box goalPanel = Box.createHorizontalBox();
093:                 {
094:                         goalPanel.add( new JLabel( "Goal:" ) );
095:                         goalPanel.add( goalText );
096:                         goalPanel.add( querButton );
097:                 }
098:                 mainPanel.add( goalPanel, BorderLayout.NORTH );
099:         }
100:         // browsable Prolog database
101:         final JTree dictTree = new JTree( new DefaultMutableTreeNode( "empty" ) );
102:         {
103:                 //dictTree.setBorder( new SoftBevelBorder( BevelBorder.LOWERED ) );
104:                 dictTree.setRootVisible( false );
105:                 dictTree.setShowsRootHandles( true );
106:         }
107:         // browsable output tree
108:         final JTree querTree = new JTree( new DefaultMutableTreeNode() );
109:         {
110:                 //querTree.setBorder( new SoftBevelBorder( BevelBorder.LOWERED ) );
111:                 querTree.setRootVisible( false );
112:                 querTree.setShowsRootHandles( true );
113:         }
114:         // two simple helpers
115:         final DefaultTreeModel querTreeModel()
116:         {
117:                 return (DefaultTreeModel) querTree.getModel();
118:         }
119:         final void querTreeReroot( DefaultMutableTreeNode rootNode )
120:         {
121:                 querTreeModel().setRoot( rootNode );
122:         }
123:         // all other user controls in lower right-hand corner
124:         final JLabel querStatus = new JLabel( "ready." );
125:         {
126:                 querStatus.setBorder( new SoftBevelBorder( BevelBorder.LOWERED ) );
127:         }
128:         final JButton pausButton = new JButton( "Pause!" );
129:         final JButton aborButton = new JButton( "Abort!" );
130:         {
131:                 pausButton.setBorder( new SoftBevelBorder( BevelBorder.RAISED ) );
132:                 pausButton.setEnabled( false );
133:                 pausButton.addActionListener( new ActionListener() {
134:                         public void actionPerformed( ActionEvent e )
135:                         {
136:                                 interpreter.togglePaused();
137:                         }
138:                 } );
139:                 aborButton.setBorder( new SoftBevelBorder( BevelBorder.RAISED ) );
140:                 aborButton.setEnabled( false );
141:                 aborButton.addActionListener( new ActionListener() {
142:                         public void actionPerformed( ActionEvent e )
143:                         {
144:                                 interpreter.abort();
145:                         }
146:                 } );
147:         }
148:         final JButton nextButton = new JButton( "Next Result!" );
149:         final JButton omniButton = new JButton( "All Results!" );
150:         {
151:                 nextButton.setBorder( new SoftBevelBorder( BevelBorder.RAISED ) );
152:                 nextButton.setEnabled( false );
153:                 nextButton.addActionListener( new ActionListener() {
154:                         public void actionPerformed( ActionEvent e )
155:                         {
156:                                 interpreter.next();
157:                         }
158:                 } );
159:                 omniButton.setBorder( new SoftBevelBorder( BevelBorder.RAISED ) );
160:                 omniButton.setEnabled( false );
161:                 omniButton.addActionListener( new ActionListener() {
162:                         public void actionPerformed( ActionEvent e )
163:                         {
164:                                 interpreter.omni();
165:                         }
166:                 } );
167:         }
168:         // a simple helper to set the displayed state and enable/disable buttons
169:         final void guiStatus( String state, boolean q, boolean p, boolean r, boolean a, boolean n )
170:         {
171:                 querStatus.setText( state );
172:                 querButton.setEnabled( q );
173:                 pausButton.setEnabled( p );
174:                 if( r ) pausButton.setText( "Resume!" );
175:                 else pausButton.setText( "Pause!" );
176:                 aborButton.setEnabled( a );
177:                 nextButton.setEnabled( n );
178:                 omniButton.setEnabled( n );
179:         }
180:         // the central content
181:         {
182:                 final JSplitPane centerPanel = new JSplitPane();
183:                 centerPanel.setResizeWeight( 0.5 );
184:                 centerPanel.setOneTouchExpandable( true );
185:                 centerPanel.setContinuousLayout( true );
186:                 {
187:                         final JPanel dictPanel = new JPanel();
188:                         //dictPanel.setBorder( new SoftBevelBorder( BevelBorder.LOWERED ) );
189:                         dictPanel.setLayout( new BorderLayout() );
190:                         dictPanel.add( new JLabel( "Dictionary:" ), BorderLayout.NORTH );
191:                         dictPanel.add( new JScrollPane( dictTree ), BorderLayout.CENTER );
192:                         centerPanel.setLeftComponent( dictPanel );
193:                 }
194:                 {
195:                         final JPanel querPanel = new JPanel();
196:                         //querPanel.setBorder( new SoftBevelBorder( BevelBorder.LOWERED ) );
197:                         querPanel.setLayout( new BorderLayout() );
198:                         querPanel.add( new JLabel( "Result of Query:" ), BorderLayout.NORTH );
199:                         querPanel.add( new JScrollPane( querTree ), BorderLayout.CENTER );
200:                         {
201:                                 final Box querControls = Box.createHorizontalBox();
202:                                 querControls.add( Box.createGlue() );
203:                                 querControls.add( nextButton );
204:                                 querControls.add( omniButton );
205:                                 querPanel.add( querControls, BorderLayout.SOUTH );
206:                         }
207:                         centerPanel.setRightComponent( querPanel );
208:                 }
209:                 mainPanel.add( centerPanel, BorderLayout.CENTER );
210:         }
211:         // the status bar
212:         {
213:                 final Box statPanel = Box.createHorizontalBox();
214:                 statPanel.add( new JLabel( "Interpreter:" ) );
215:                 statPanel.add( querStatus );
216:                 statPanel.add( Box.createGlue() );
217:                 statPanel.add( pausButton );
218:                 statPanel.add( aborButton );
219:                 mainPanel.add( statPanel, BorderLayout.SOUTH );
220:         }
221: 
222: // ---- show the GUI and start the background interpreter cycle
223:         final void mainLoop( java.io.FileReader ginDefault, java.io.FileReader dinDefault )
224:         {
225:                 try {
226:                         Parse.database( dinDefault, interpreter.database );
227:                 }
228:                 catch( IllegalArgumentException x ) {
229:                 }
230:                 ( (DefaultTreeModel) dictTree.getModel() ).setRoot( interpreter.database.viewDatabase() );
231:                 expandALevel( dictTree );
232:                 interpreter.queryGoal( ginDefault );
233:                 mainFrame.setVisible( true );
234:                 interpreter.mainLoop();
235:         }
236: 
237: // ---- Inter == Interpreter Semaphore 
238:         // the syncronization between the main (interpreter) thread and the GUI thread
239:         final class Inter {
240: // ------------ singleton instance definition and initialization
241:                 // allocate the database singleton
242:                 final GUIDatabase database = new GUIDatabase();         // final but mutable
243: 
244:                 // the current query -- set by GUI -- cleared by main
245:                 Sentence runningQuery = null;
246: 
247:                 // get all results -- usually should be false
248:                 boolean shortcircuited = false;
249: 
250:                 // userState set by GUI -- cleared by main on completion of user action
251:                 int userState = READY;
252:                 // general state constants
253:                 static final int READY =        0;
254:                 static final int RUNNING =      1;
255:                 static final int WAITING =      2;
256:                 static final int PAUSED =       3;
257:                 // runningState set by main only
258:                 int runningState = READY;
259: 
260:                 // return from Prolog -- set by main -- cleared by GUI
261:                 DefaultMutableTreeNode successNode = null;
262: 
263:                 // the GUI polling timer
264:                 //        An ugly hack -- coundn't figure out from the standard docs
265:                 //        how to trigger GUI events from a non-GUI thread
266:                 final Timer guiPolling = new Timer( 100, new ActionListener() {
267:                         public void actionPerformed( final ActionEvent e )
268:                         {
269:                                 if( runningQuery == null ) guiPolling.stop(); // needn't be synced
270:                                 // note that the order here is necessary because of the lazy
271:                                 // locking used in the "out" routine given to "viewableQuery"
272:                                 if( successNode != null ) {
273:                                         final DefaultTreeModel qM = querTreeModel();
274:                                         final DefaultMutableTreeNode qR = (DefaultMutableTreeNode) qM.getRoot();
275:                                         qM.insertNodeInto( successNode, qR, qR.getChildCount() );
276:                                         querTree.expandPath( new TreePath( successNode.getPath() ) );
277:                                         successNode = null; // needn't be synced
278:                                         synchronized( Inter.this ) {
279:                                                 Inter.this.notifyAll();
280:                                         }
281:                                         guiPolling.restart(); // fast first tick
282:                                 }
283:                                 // save a small amount of GUI work -- sync isn't critical
284:                                 if( runningState != oldRunningState ) {
285:                                         guiState( runningState );
286:                                         oldRunningState = runningState;
287:                                         ticker = 0;
288:                                 }
289:                                 else if( runningState == RUNNING && ++ticker > 9 ) {
290:                                         querStatus.setText( querStatus.getText() + "." );
291:                                         ticker = 0;
292:                                 }
293:                         }
294:                         int oldRunningState = -1;
295:                         int ticker = 0;
296:                 } );
297:                 {
298:                         guiPolling.setInitialDelay( 50 );
299:                 }
300: 
301: // ------------ methods only called by the GUI thread -- high priority thread
302:                 // set goal and begin query instance methods
303:                 final void queryGoal( final String g )
304:                 {
305:                         queryGoal( new java.io.StringReader( g ) );
306:                 }
307:                 final void queryGoal( final java.io.Reader gin )
308:                 {
309:                         try {
310:                                 final Sentence query = Parse.goal( gin );
311:                                 goalText.setText( query.toString() );   // pretty print
312:                                 queryGoal( query );
313:                         }
314:                         catch( IllegalArgumentException x ) {
315:                         }
316:                 }
317:                 final synchronized void queryGoal( final Sentence query )
318:                 {
319:                         if( runningQuery == null ) {
320:                                 final DefaultMutableTreeNode qR = new DefaultMutableTreeNode();
321:                                 qR.add( new DefaultMutableTreeNode( query.toString(), false ) );
322:                                 querTreeReroot( qR );
323:                                 userState = RUNNING;
324:                                 runningQuery = query;
325:                                 notifyAll();
326:                                 guiPolling.start();
327:                         }
328:                         //expandALevel( querTree ); // this is a static helper procedure
329:                 }
330:                 final synchronized void togglePaused()  // logically requires sync
331:                 {
332:                         if( runningState == RUNNING ) {
333:                                 userState = PAUSED;
334:                         }
335:                         else if( runningState == PAUSED ) {
336:                                 userState = RUNNING;
337:                                 notifyAll();
338:                         }
339:                 }
340:                 final synchronized void abort()
341:                 {
342:                         userState = READY;
343:                         notifyAll();
344:                 }
345:                 final synchronized void next()
346:                 {
347:                         userState = RUNNING;
348:                         notifyAll();
349:                 }
350:                 final synchronized void omni()
351:                 {
352:                         userState = RUNNING;
353:                         shortcircuited = true;
354:                         notifyAll();
355:                 }
356:                 final void guiState( int state )
357:                 {
358:                         switch( state ) {
359:                         case READY:
360:                                 guiStatus( "ready.",   true,  false, false, false, false );
361:                                 return;
362:                         case RUNNING:
363:                                 guiStatus( "running.", false, true,  false, true,  false );
364:                                 return;
365:                         case WAITING:
366:                                 guiStatus( "waiting.", false, false, false, true,  true  );
367:                                 return;
368:                         case PAUSED:
369:                                 guiStatus( "paused.",  false, true,  true,  true,  false );
370:                                 return;
371:                         }
372:                 }
373: 
374: // ------------ methods only called by the main (interpreter) thread -- low priority thread
375:                 // interpreter main loop
376:                 final void mainLoop()
377:                 {
378:                         mainloop: for(;;) {
379:                                 synchronized( this ) {
380:                                         while( runningQuery == null ) uwait();
381:                                         runningState = RUNNING;
382:                                         shortcircuited = false;
383:                                 }
384:                                 database.viewableQuery( runningQuery, database.new GUISuccession() {
385:                                         // the goods returned
386:                                         void out( final int s, final DefaultMutableTreeNode snode )
387:                                         {
388:                                                 // lazy locking for better performance
389:                                                 synchronized( Inter.this ) {
390:                                                         while( successNode != null ) Inter.this.uwait();
391:                                                 }
392:                                                 successNode = snode;
393:                                         }
394:                                         // blocking wait-for-next
395:                                         void next()
396:                                         {
397:                                                 if( ! shortcircuited )
398:                                                 synchronized( Inter.this ) {
399:                                                         if( userState != READY ) {
400:                                                                 userState = runningState = WAITING;
401:                                                                 while( userState == WAITING ) Inter.this.uwait();
402:                                                         }
403:                                                 }
404:                                                 monitor();
405:                                         }
406:                                         // a heartmonitor placed into the core Prolog engine
407:                                         void monitor()
408:                                         {
409:                                                 Thread.yield();
410:                                                 if( userState == PAUSED ) {
411:                                                         synchronized( Inter.this ) {
412:                                                                 runningState = PAUSED;
413:                                                                 while( userState == PAUSED ) Inter.this.uwait();
414:                                                         }
415:                                                 }
416:                                                 if( userState == READY ) throw new MonitorException();
417:                                                 runningState = RUNNING;
418:                                         }
419:                                 } );
420:                                 synchronized( this ) {  // I don't think this needs sync
421:                                         userState = runningState = READY;
422:                                         runningQuery = null;
423:                                         shortcircuited = false;
424:                                 }
425:                         }
426:                         // endless mainloop
427:                 }
428:                 // wait while throwing away those annoying InterruptedExceptions
429:                 final void uwait()
430:                 {
431:                         try {
432:                                 wait();
433:                         }
434:                         catch( InterruptedException x ) {
435:                                 throw new RuntimeException( "Annoying InterruptedException" );
436:                         }
437:                 }
438:         }
439: 
440: // ---- static subroutines and convenience functions
441:         // expand the second level of a tree
442:         static void expandALevel( final JTree tree )
443:         {
444:                 final DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
445:                 for( int i = 0, n = root.getChildCount(); i < n; ++i ) {
446:                         //tree.expandPath( new TreePath(
447:                         //        ( (DefaultMutableTreeNode) root.getChildAt( i ) ).getPath()
448:                         //) );
449:                 }
450:         }
451: }
452: 
453: // fin