source: trunk/abcl/src/org/armedbear/lisp/java/swing/REPLConsole.java

Last change on this file was 15421, checked in by Mark Evenson, 4 years ago

Explicitly scope java.lang.Thread.yield() invocation

Apparently needed for the FreeBSD version of openjdk15.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 10.0 KB
Line 
1/*
2 * ConsoleDocument.java
3 *
4 * Copyright (C) 2008-2009 Alessio Stalla
5 *
6 * $Id: REPLConsole.java 15421 2020-10-20 06:45:20Z mevenson $
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21 *
22 * As a special exception, the copyright holders of this library give you
23 * permission to link this library with independent modules to produce an
24 * executable, regardless of the license terms of these independent
25 * modules, and to copy and distribute the resulting executable under
26 * terms of your choice, provided that you also meet, for each linked
27 * independent module, the terms and conditions of the license of that
28 * module.  An independent module is a module which is not derived from
29 * or based on this library.  If you modify this library, you may extend
30 * this exception to your version of the library, but you are not
31 * obligated to do so.  If you do not wish to do so, delete this
32 * exception statement from your version.
33 */
34
35package org.armedbear.lisp.java.swing;
36
37import java.awt.Window;
38import java.awt.event.WindowAdapter;
39import java.awt.event.WindowEvent;
40import java.io.BufferedReader;
41import java.io.BufferedWriter;
42import java.lang.RuntimeException;
43import java.io.Reader;
44import java.io.Writer;
45
46import javax.swing.JFrame;
47import javax.swing.JScrollPane;
48import javax.swing.JTextArea;
49import javax.swing.SwingUtilities;
50import javax.swing.event.DocumentEvent;
51import javax.swing.event.DocumentListener;
52import javax.swing.text.AttributeSet;
53import javax.swing.text.BadLocationException;
54import javax.swing.text.DefaultStyledDocument;
55import javax.swing.text.JTextComponent;
56import org.armedbear.lisp.Function;
57import org.armedbear.lisp.Interpreter;
58import org.armedbear.lisp.LispObject;
59import org.armedbear.lisp.LispThread;
60import org.armedbear.lisp.SpecialBindingsMark;
61import org.armedbear.lisp.Stream;
62import org.armedbear.lisp.Symbol;
63import org.armedbear.lisp.TwoWayStream;
64
65import static org.armedbear.lisp.Lisp.*;
66
67public class REPLConsole extends DefaultStyledDocument {
68
69  private StringBuffer inputBuffer = new StringBuffer();
70 
71  private Reader reader = new Reader() {
72
73      @Override
74      public void close() throws RuntimeException {}
75
76      @Override
77      public synchronized int read(char[] cbuf, int off, int len) throws RuntimeException {
78        try {
79          int length = Math.min(inputBuffer.length(), len);
80          while(length <= 0) {
81            wait();
82            length = Math.min(inputBuffer.length(), len);
83          }
84          inputBuffer.getChars(0, length, cbuf, off);
85          inputBuffer.delete(0, length);
86          return length;
87        } catch (InterruptedException e) {
88          throw new RuntimeException(e);
89        }
90      }
91    };
92 
93  private Writer writer = new Writer() {
94     
95      @Override
96      public void close() throws RuntimeException {}
97
98      @Override
99      public void flush() throws RuntimeException {}
100
101      @Override
102      public void write(final char[] cbuf, final int off, final int len) throws RuntimeException {
103        try {
104          final int insertOffs;
105          synchronized(reader) {
106            if(inputBuffer.toString().matches("^\\s*$")) {
107              int length = inputBuffer.length();
108              inputBuffer.delete(0, length);
109            }
110            insertOffs = getLength() - inputBuffer.length();
111            reader.notifyAll();
112          }
113          Runnable r = new Runnable() {
114              public void run() {
115                synchronized(reader) {
116                  try {
117                    superInsertString(insertOffs,
118                                      new String(cbuf, off, len),
119                                      null);
120                  } catch(Exception e) {
121                    assert(false); //BadLocationException should not happen here
122                  }
123                }
124              }
125            };
126          SwingUtilities.invokeAndWait(r);
127        }  catch (Exception e) {
128          throw new RuntimeException(e);
129        }
130      }
131    };
132 
133  private boolean disposed = false;
134  private final Thread replThread;
135 
136  public REPLConsole(LispObject replFunction) {
137    final LispObject replWrapper = makeReplWrapper(new Stream(Symbol.SYSTEM_STREAM, new BufferedReader(reader)),
138                                                   new Stream(Symbol.SYSTEM_STREAM, new BufferedWriter(writer)),
139                                                   replFunction);
140    replThread = new Thread("REPL-thread-" + System.identityHashCode(this)) {
141        public void run() {
142          while(true) {
143            replWrapper.execute();
144            java.lang.Thread.yield();
145          }
146        }
147      };
148    replThread.start();
149  }
150
151  @Override
152  public void insertString(int offs, String str, AttributeSet a)
153    throws BadLocationException {
154    synchronized(reader) {
155      int bufferStart = getLength() - inputBuffer.length();
156      if(offs < bufferStart) {
157        throw new BadLocationException("Can only insert after " + bufferStart, offs);
158      }
159      superInsertString(offs, str, a);
160      inputBuffer.insert(offs - bufferStart, str);
161      if(processInputP(inputBuffer, str)) {
162        reader.notifyAll();
163      }
164    }
165  }
166 
167  protected void superInsertString(int offs, String str, AttributeSet a)
168    throws BadLocationException {
169    super.insertString(offs, str, a);
170  }
171 
172  /**
173   * Guaranteed to run with exclusive access to the buffer.
174   * @param sb NB sb MUST NOT be destructively modified!!
175   * @return
176   */
177  protected boolean processInputP(StringBuffer sb, String str) {
178    if(str.indexOf("\n") == -1) {
179      return false;
180    }
181    int parenCount = 0;
182    int len = sb.length();
183    for(int i = 0; i < len; i++) {
184      char c = sb.charAt(i);
185      if(c == '(') {
186        parenCount++;
187      } else if(c == ')') {
188        parenCount--;
189        if(parenCount == 0) {
190          return true;
191        }
192      }
193    }
194    return parenCount <= 0;
195  }
196 
197  @Override
198  public void remove(int offs, int len) throws BadLocationException {
199    synchronized(reader) {
200      int bufferStart = getLength() - inputBuffer.length();
201      if(offs < bufferStart) {
202        throw new BadLocationException("Can only remove after " + bufferStart, offs);
203      }
204      super.remove(offs, len);
205      inputBuffer.delete(offs - bufferStart, offs - bufferStart + len);
206    }
207  }
208 
209  public Reader getReader() {
210    return reader;
211  }
212 
213  public Writer getWriter() {
214    return writer;
215  }
216 
217  public void setupTextComponent(final JTextComponent txt) {
218    addDocumentListener(new DocumentListener() {
219
220        //      @Override
221        public void changedUpdate(DocumentEvent e) {
222        }
223
224        // @Override
225        public void insertUpdate(DocumentEvent e) {
226          int len = getLength();
227          if(len - e.getLength() == e.getOffset()) { //The insert was at the end of the document
228            txt.setCaretPosition(getLength());
229          }
230        }
231
232        // @Override
233        public void removeUpdate(DocumentEvent e) {
234        }
235      });
236    txt.setCaretPosition(getLength());
237  }
238 
239  public void dispose() {
240    disposed = true;
241    for(DocumentListener listener : getDocumentListeners()) {
242      removeDocumentListener(listener);
243    }
244    try {
245      reader.close();
246      writer.close();
247    } catch (Exception e) {
248      throw new RuntimeException(e);
249    }
250    replThread.interrupt(); //really?
251  }
252 
253  private final LispObject debuggerHook = new Function() {
254     
255      @Override
256      public LispObject execute(LispObject condition, LispObject debuggerHook) {
257        if(disposed) {
258          return PACKAGE_SYS.findSymbol("%DEBUGGER-HOOK-FUNCTION").execute(condition, debuggerHook);
259        } else {
260          return NIL;
261        }
262      }
263     
264    };
265 
266  public LispObject makeReplWrapper(final Stream in, final Stream out, final LispObject fn) {
267    return new Function() { 
268      @Override
269      public LispObject execute() {
270        SpecialBindingsMark lastSpecialBinding = LispThread.currentThread().markSpecialBindings();
271        try {
272          TwoWayStream ioStream = new TwoWayStream(in, out);
273          LispThread.currentThread().bindSpecial(Symbol.DEBUGGER_HOOK, debuggerHook);
274          LispThread.currentThread().bindSpecial(Symbol.STANDARD_INPUT, in);
275          LispThread.currentThread().bindSpecial(Symbol.STANDARD_OUTPUT, out);
276          LispThread.currentThread().bindSpecial(Symbol.ERROR_OUTPUT, out);
277          LispThread.currentThread().bindSpecial(Symbol.TERMINAL_IO, ioStream);
278          LispThread.currentThread().bindSpecial(Symbol.DEBUG_IO, ioStream);
279          LispThread.currentThread().bindSpecial(Symbol.QUERY_IO, ioStream);
280          return fn.execute();
281        } finally {
282          LispThread.currentThread().resetSpecialBindings(lastSpecialBinding);
283        }
284      }
285     
286    };
287  }
288 
289  public void disposeOnClose(final Window parent) {
290    parent.addWindowListener(new WindowAdapter() {
291        @Override
292        public void windowClosing(WindowEvent e) {
293          dispose();
294          parent.removeWindowListener(this);
295        }
296      });
297  }
298 
299  public static void main(String[] args) {
300    LispObject repl = null;
301    try {   
302      repl = Interpreter.createInstance().eval("#'top-level::top-level-loop");
303    } catch (Throwable e) {
304      e.printStackTrace();
305      System.exit(1);  // Ok. We haven't done anything useful yet.
306    }
307    final REPLConsole d = new REPLConsole(repl);
308    final JTextComponent txt = new JTextArea(d);
309    d.setupTextComponent(txt);
310    JFrame f = new JFrame();
311    f.add(new JScrollPane(txt));
312    d.disposeOnClose(f);
313    f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
314    f.pack();
315    f.setVisible(true);
316  }
317 
318}
Note: See TracBrowser for help on using the repository browser.