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