001: /*
002: * Interpreter.java
003: *
004: * Copyright (C) 2002-2003 Peter Graves
005: * $Id: Interpreter.java,v 1.8 2003/11/15 11:03:32 beedlem Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.lisp;
023:
024: import java.io.BufferedReader;
025: import java.io.File;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.InputStreamReader;
029: import java.io.OutputStream;
030: import java.util.ArrayList;
031: import java.util.Iterator;
032: import java.util.List;
033:
034: public final class Interpreter extends Lisp {
035: private static final int MAX_HISTORY = 30;
036:
037: // There can only be one interpreter.
038: public static Interpreter interpreter;
039:
040: private static String ldArgs;
041: private static ArrayList history;
042: private static int commandNumber;
043:
044: private final boolean jlisp;
045: private final InputStream inputStream;
046: private final OutputStream outputStream;
047:
048: public static synchronized Interpreter getInstance() {
049: return interpreter;
050: }
051:
052: public static synchronized Interpreter createInstance() {
053: if (interpreter != null)
054: return null;
055: return interpreter = new Interpreter();
056: }
057:
058: public static synchronized Interpreter createInstance(
059: InputStream in, OutputStream out, String initialDirectory) {
060: if (interpreter != null)
061: return null;
062: return interpreter = new Interpreter(in, out, initialDirectory);
063: }
064:
065: private final Environment environment = new Environment();
066:
067: private Interpreter() {
068: jlisp = false;
069: inputStream = null;
070: outputStream = null;
071: }
072:
073: private Interpreter(InputStream inputStream,
074: OutputStream outputStream, String initialDirectory) {
075: jlisp = false;
076: this .inputStream = inputStream;
077: this .outputStream = outputStream;
078: resetIO(new CharacterInputStream(inputStream),
079: new CharacterOutputStream(outputStream));
080: }
081:
082: private static boolean lispInitialized;
083:
084: public static synchronized void initializeLisp(boolean jlisp) {
085: if (!lispInitialized) {
086: try {
087: Load._load("boot.lisp", true, false, false);
088: if (jlisp) {
089: Class.forName("org.armedbear.j.LispAPI");
090: Load._load("j.lisp");
091: }
092: } catch (ConditionThrowable c) {
093: reportError(c, LispThread.currentThread());
094: } catch (Throwable t) {
095: t.printStackTrace();
096: }
097: lispInitialized = true;
098: }
099: }
100:
101: private static boolean topLevelInitialized;
102:
103: private static synchronized void initializeTopLevel() {
104: if (!topLevelInitialized) {
105: try {
106: // Resolve top-level-loop autoload.
107: Symbol TOP_LEVEL_LOOP = intern("TOP-LEVEL-LOOP",
108: PACKAGE_TPL);
109: LispObject tplFun = TOP_LEVEL_LOOP.getSymbolFunction();
110: if (tplFun instanceof Autoload) {
111: Autoload autoload = (Autoload) tplFun;
112: autoload.load();
113: }
114: _LOAD_VERBOSE_.setSymbolValue(T);
115: _AUTOLOAD_VERBOSE_.setSymbolValue(T);
116: String userHome = System.getProperty("user.home");
117: File file = new File(userHome, ".ablrc");
118: if (file.isFile())
119: Load.load(file.getCanonicalPath());
120: else {
121: file = new File(userHome, ".ablisprc");
122: if (file.isFile()) {
123: String message = "Warning: use of .ablisprc is deprecated; use .ablrc instead.";
124: getStandardOutput().writeLine(message);
125: Load.load(file.getCanonicalPath());
126: }
127: }
128: } catch (Throwable t) {
129: t.printStackTrace();
130: }
131: topLevelInitialized = true;
132: }
133: }
134:
135: private boolean done = false;
136:
137: public void run() {
138: final LispThread thread = LispThread.currentThread();
139: history = new ArrayList();
140: commandNumber = 0;
141: done = false;
142: try {
143: CharacterOutputStream out = getStandardOutput();
144: out.writeString(banner());
145: out.flushOutput();
146: initializeLisp(jlisp);
147: initializeTopLevel();
148: Symbol TOP_LEVEL_LOOP = intern("TOP-LEVEL-LOOP",
149: PACKAGE_TPL);
150: LispObject tplFun = TOP_LEVEL_LOOP.getSymbolFunction();
151: if (tplFun instanceof Function) {
152: funcall0(tplFun, thread);
153: return;
154: }
155: while (true) {
156: try {
157: thread.resetStack();
158: thread.setDynamicEnvironment(null);
159: ++commandNumber;
160: out.writeString(prompt());
161: out.flushOutput();
162: char c = peekCharNonWhitespace(getStandardInput());
163: if (c == '\n') {
164: // Blank line.
165: getStandardInput().readChar(true, NIL);
166: --commandNumber;
167: continue;
168: }
169: LispObject object = null;
170: if (c == ':') {
171: LispObject input = getStandardInput().readLine(
172: false, EOF);
173: // Ignore multiple values.
174: thread.clearValues();
175: if (input == EOF)
176: break;
177: String s = LispString.getValue(input);
178: Object obj = getHistory(s);
179: if (obj instanceof String) {
180: s = (String) obj;
181: out.writeLine(s);
182: } else if (obj instanceof LispObject) {
183: object = (LispObject) obj;
184: s = null;
185: out.writeLine(object.toString());
186: }
187: if (s != null) {
188: if (doCommand(s))
189: addHistory(commandNumber, s);
190: if (done)
191: return;
192: else
193: continue;
194: }
195: }
196: if (object == null)
197: object = getStandardInput().read(false, EOF,
198: false); // Top level read.
199: if (object == EOF)
200: break;
201: addHistory(commandNumber, object);
202: out.setCharPos(0);
203: Symbol.MINUS.setSymbolValue(object);
204: LispObject result = eval(object, environment,
205: thread);
206: Debug.assertTrue(result != null);
207: Symbol.STAR_STAR_STAR
208: .setSymbolValue(Symbol.STAR_STAR
209: .getSymbolValue());
210: Symbol.STAR_STAR.setSymbolValue(Symbol.STAR
211: .getSymbolValue());
212: Symbol.STAR.setSymbolValue(result);
213: Symbol.PLUS_PLUS_PLUS
214: .setSymbolValue(Symbol.PLUS_PLUS
215: .getSymbolValue());
216: Symbol.PLUS_PLUS.setSymbolValue(Symbol.PLUS
217: .getSymbolValue());
218: Symbol.PLUS.setSymbolValue(Symbol.MINUS
219: .getSymbolValue());
220: if (done)
221: return;
222: out = getStandardOutput();
223: out.freshLine();
224: thread.checkStack();
225: LispObject[] values = thread.getValues();
226: Symbol.SLASH_SLASH_SLASH
227: .setSymbolValue(Symbol.SLASH_SLASH
228: .getSymbolValue());
229: Symbol.SLASH_SLASH.setSymbolValue(Symbol.SLASH
230: .getSymbolValue());
231: if (values != null) {
232: LispObject slash = NIL;
233: for (int i = values.length; i-- > 0;)
234: slash = new Cons(values[i], slash);
235: Symbol.SLASH.setSymbolValue(slash);
236: for (int i = 0; i < values.length; i++)
237: out.writeLine(String.valueOf(values[i]));
238: } else {
239: Symbol.SLASH.setSymbolValue(new Cons(result));
240: out.writeLine(String.valueOf(result));
241: }
242: out.flushOutput();
243: } catch (StackOverflowError e) {
244: getStandardInput().clearInput();
245: out.writeLine("Stack overflow");
246: } catch (ConditionThrowable c) {
247: reportError(c, thread);
248: } catch (Throwable t) {
249: getStandardInput().clearInput();
250: out.printStackTrace(t);
251: thread.backtrace();
252: }
253: }
254: } catch (Throwable t) {
255: t.printStackTrace();
256: }
257: }
258:
259: private static void reportError(ConditionThrowable c,
260: LispThread thread) {
261: try {
262: getStandardInput().clearInput();
263: CharacterOutputStream out = getStandardOutput();
264: out.freshLine();
265: String message = c.getCondition().getMessage();
266: if (message != null)
267: out.writeLine("Error: " + message + ".");
268: else
269: out.writeLine("Error: unhandled condition: "
270: + c.getCondition());
271: thread.backtrace();
272: } catch (Throwable t) {
273: ;
274: }
275: }
276:
277: // Skip whitespace except for newline and peek at the next
278: // character.
279: private char peekCharNonWhitespace(CharacterInputStream stream)
280: throws ConditionThrowable {
281: while (true) {
282: LispCharacter character = (LispCharacter) stream.readChar(
283: true, NIL);
284: char c = character.getValue();
285: if (!Character.isWhitespace(c) || c == '\n') {
286: stream.unreadChar(character);
287: return c;
288: }
289: }
290: }
291:
292: private static Object getHistory(String s)
293: throws ConditionThrowable {
294: s = s.trim();
295: if (s.startsWith(":"))
296: s = s.substring(1);
297: String command, args;
298: int index = s.indexOf(' ');
299: if (index >= 0) {
300: command = s.substring(0, index);
301: args = s.substring(index).trim();
302: } else {
303: command = s;
304: args = null;
305: }
306: if (command == null || command.length() == 0)
307: return null;
308: if (Character.isDigit(command.charAt(0))) {
309: try {
310: int n = Integer.parseInt(command);
311: for (int i = history.size(); i-- > 0;) {
312: HistoryEntry entry = (HistoryEntry) history.get(i);
313: if (entry.commandNumber == n)
314: return entry.obj;
315: }
316: } catch (NumberFormatException e) {
317: Debug.trace(e);
318: }
319: }
320: return null;
321: }
322:
323: private static boolean doCommand(String s)
324: throws ConditionThrowable {
325: s = s.trim();
326: String command, args;
327: int index = s.indexOf(' ');
328: if (index >= 0) {
329: command = getCommand(s.substring(0, index));
330: args = s.substring(index).trim();
331: } else {
332: command = getCommand(s);
333: args = null;
334: }
335: if (command == null || command.length() == 0)
336: return false;
337: if (command.equals("ld")) {
338: if (args == null || args.length() == 0) {
339: if (ldArgs != null)
340: args = ldArgs;
341: else
342: throw new ConditionThrowable(new LispError(
343: "ld: no previous file"));
344: }
345: if (args != null && args.length() > 0) {
346: ldArgs = args;
347: LispObject result = NIL;
348: List tokens = tokenize(args);
349: for (Iterator it = tokens.iterator(); it.hasNext();) {
350: String filename = (String) it.next();
351: result = eval(list2(Symbol.LOAD, new LispString(
352: filename)), new Environment(), LispThread
353: .currentThread());
354: if (result == NIL)
355: break;
356: }
357: }
358: return true;
359: }
360: if (command.equals("exit")) {
361: exit();
362: return true;
363: }
364: if (command.equals("pwd")) {
365: LispString string = checkString(_DEFAULT_PATHNAME_DEFAULTS_
366: .getSymbolValue());
367: getStandardOutput().writeLine(string.getValue());
368: return true;
369: }
370: if (command.equals("cd")) {
371: if (args == null || args.length() == 0)
372: args = System.getProperty("user.home");
373: String oldDir = LispString
374: .getValue(_DEFAULT_PATHNAME_DEFAULTS_
375: .getSymbolValue());
376: File file;
377: if (Utilities.isFilenameAbsolute(args))
378: file = new File(args);
379: else
380: file = new File(oldDir, args);
381: if (file.isDirectory()) {
382: try {
383: _DEFAULT_PATHNAME_DEFAULTS_
384: .setSymbolValue(new LispString(file
385: .getCanonicalPath()));
386: } catch (IOException e) {
387: throw new ConditionThrowable(new LispError(e
388: .getMessage()));
389: }
390: }
391: LispString string = checkString(_DEFAULT_PATHNAME_DEFAULTS_
392: .getSymbolValue());
393: getStandardOutput().writeLine(string.getValue());
394: return true;
395: }
396: if (command.equals("apropos")) {
397: if (args != null && args.length() > 0) {
398: if (args.length() > 1 && args.charAt(0) == '"'
399: && args.charAt(args.length() - 1) == '"') {
400: ;
401: } else {
402: StringBuffer sb = new StringBuffer();
403: sb.append('"');
404: int i = args.indexOf(' ');
405: if (i < 0) {
406: sb.append(args);
407: sb.append('"');
408: } else {
409: // Two args to APROPOS.
410: String arg1 = args.substring(0, i);
411: String arg2 = args.substring(i + 1).trim();
412: sb.append(arg1);
413: sb.append('"');
414: sb.append(' ');
415: sb.append(arg2);
416: }
417: args = sb.toString();
418: }
419: getStandardOutput().setCharPos(0);
420: StringInputStream stream = new StringInputStream(
421: "(apropos " + args + ")");
422: LispObject obj = stream.read(false, EOF, false);
423: if (obj == EOF)
424: throw new ConditionThrowable(new EndOfFile());
425: eval(obj, new Environment(), LispThread.currentThread());
426: getStandardOutput().freshLine();
427: return true;
428: }
429: return false;
430: }
431: if (command.equals("describe")) {
432: if (args != null && args.length() > 0) {
433: getStandardOutput().setCharPos(0);
434: StringInputStream stream = new StringInputStream(
435: "(describe " + args + ")");
436: LispObject obj = stream.read(false, EOF, false);
437: if (obj == EOF)
438: throw new ConditionThrowable(new EndOfFile());
439: eval(obj, new Environment(), LispThread.currentThread());
440: return true;
441: }
442: return false;
443: }
444: if (command.equals("package")) {
445: if (args == null || args.length() == 0) {
446: Package pkg = (Package) _PACKAGE_.getSymbolValue();
447: getStandardOutput().writeString("The ");
448: getStandardOutput().writeString(pkg.getName());
449: getStandardOutput().writeLine(" package is current.");
450: } else {
451: if (args.charAt(0) == ':')
452: args = args.substring(1);
453: args = args.toUpperCase();
454: Package pkg = Packages.findPackage(args);
455: if (pkg != null) {
456: Environment dynEnv = LispThread.currentThread()
457: .getDynamicEnvironment();
458: if (dynEnv != null) {
459: Binding binding = dynEnv.getBinding(_PACKAGE_);
460: if (binding != null) {
461: binding.value = pkg;
462: return true;
463: }
464: }
465: // No dynamic binding.
466: _PACKAGE_.setSymbolValue(pkg);
467: } else {
468: getStandardOutput().writeString("Unknown package ");
469: getStandardOutput().writeLine(s);
470: }
471: }
472: return true;
473: }
474: if (command.equals("history")) {
475: addHistory(commandNumber, s);
476: for (int i = history.size(); i-- > 0;) {
477: HistoryEntry entry = (HistoryEntry) history.get(i);
478: StringBuffer sb = new StringBuffer();
479: sb.append(entry.commandNumber);
480: while (sb.length() < 6)
481: sb.append(' ');
482: sb.append(entry.obj);
483: getStandardOutput().writeLine(sb.toString());
484: }
485: return false;
486: }
487: return false;
488: }
489:
490: private static String getCommand(String s) {
491: if (s == null || s.length() == 0)
492: return null;
493: Debug.assertTrue(s.charAt(0) == ':');
494: s = s.substring(1);
495: if (s.length() == 0)
496: return null;
497: String command = s.toLowerCase();
498: if (command.equals("ld") || command.equals("cd")
499: || command.equals("pwd"))
500: return command;
501: if (command.length() >= 3) {
502: if ("history".startsWith(command))
503: return "history";
504: }
505: if (command.length() >= 2) {
506: if ("apropos".startsWith(command))
507: return "apropos";
508: if ("describe".startsWith(command))
509: return "describe";
510: if ("package".startsWith(command))
511: return "package";
512: if ("exit".startsWith(command))
513: return "exit";
514: }
515: return null;
516: }
517:
518: private static List tokenize(String s) {
519: ArrayList list = new ArrayList();
520: StringBuffer sb = new StringBuffer();
521: boolean inQuote = false;
522: final int limit = s.length();
523: for (int i = 0; i < limit; i++) {
524: char c = s.charAt(i);
525: switch (c) {
526: case ' ':
527: if (inQuote)
528: sb.append(c);
529: else if (sb.length() > 0) {
530: list.add(sb.toString());
531: sb.setLength(0);
532: }
533: break;
534: case '"':
535: if (inQuote) {
536: if (sb.length() > 0) {
537: list.add(sb.toString());
538: sb.setLength(0);
539: }
540: inQuote = false;
541: }
542: break;
543: default:
544: sb.append(c);
545: break;
546: }
547: }
548: if (sb.length() > 0)
549: list.add(sb.toString());
550: return list;
551: }
552:
553: public void kill() {
554: if (jlisp) {
555: try {
556: inputStream.close();
557: } catch (IOException e) {
558: Debug.trace(e);
559: }
560: try {
561: outputStream.close();
562: } catch (IOException e) {
563: Debug.trace(e);
564: }
565: } else
566: System.exit(0);
567: }
568:
569: public synchronized void dispose() {
570: Debug.trace("Interpreter.dispose");
571: Debug.assertTrue(interpreter == this );
572: interpreter = null;
573: }
574:
575: protected void finalize() throws Throwable {
576: System.err.println("Interpreter.finalize");
577: }
578:
579: // Used only by org.armedbear.j.Editor.executeCommand().
580: public static LispObject evaluate(String s)
581: throws ConditionThrowable {
582: if (!lispInitialized)
583: initializeLisp(true);
584: StringInputStream stream = new StringInputStream(s);
585: LispObject obj = stream.read(false, EOF, false);
586: if (obj == EOF)
587: throw new ConditionThrowable(new EndOfFile());
588: return eval(obj, new Environment(), LispThread.currentThread());
589: }
590:
591: // Used only by the JUnit test suite (Tests.java).
592: public static final String ERROR = "Error";
593:
594: // Used only by the JUnit test suite (Tests.java).
595: public static String evalString(String s) {
596: StringInputStream stream = new StringInputStream(s);
597: StringBuffer sb = new StringBuffer();
598: while (true) {
599: try {
600: LispObject obj = stream.read(false, EOF, false);
601: if (obj == EOF)
602: break;
603: final LispThread thread = LispThread.currentThread();
604: LispObject result = eval(obj, new Environment(), thread);
605: LispObject[] values = thread.getValues();
606: if (values != null) {
607: for (int i = 0; i < values.length; i++) {
608: if (i > 0)
609: sb.append(", ");
610: sb.append(String.valueOf(values[i]));
611: }
612: } else
613: sb.append(String.valueOf(result));
614: } catch (Throwable t) {
615: return ERROR;
616: }
617: }
618: return sb.toString();
619: }
620:
621: private static final String build;
622:
623: static {
624: String s = null;
625: InputStream in = Interpreter.class.getResourceAsStream("build");
626: if (in != null) {
627: try {
628: BufferedReader reader = new BufferedReader(
629: new InputStreamReader(in));
630: s = reader.readLine();
631: reader.close();
632: } catch (IOException e) {
633: }
634: }
635: build = s;
636: }
637:
638: private static String banner() {
639: final String sep = System.getProperty("line.separator");
640: StringBuffer sb = new StringBuffer("Armed Bear Lisp ");
641: sb.append(Version.getVersion());
642: if (build != null) {
643: sb.append(" (built ");
644: sb.append(build);
645: sb.append(')');
646: }
647: sb.append(sep);
648: sb.append("Java ");
649: sb.append(System.getProperty("java.version"));
650: sb.append(' ');
651: sb.append(System.getProperty("java.vendor"));
652: sb.append(sep);
653: String vm = System.getProperty("java.vm.name");
654: if (vm != null) {
655: sb.append(vm);
656: sb.append(sep);
657: }
658: return sb.toString();
659: }
660:
661: private static String prompt() {
662: Package pkg = (Package) _PACKAGE_.getSymbolValue();
663: String pkgName = pkg.getNickname();
664: if (pkgName == null)
665: pkgName = pkg.getName();
666: StringBuffer sb = new StringBuffer();
667: sb.append(pkgName);
668: sb.append('(');
669: sb.append(commandNumber);
670: sb.append(")> ");
671: return sb.toString();
672: }
673:
674: // History.
675: private static void addHistory(int commandNumber, Object obj) {
676: history.add(0, new HistoryEntry(commandNumber, obj));
677: while (history.size() > MAX_HISTORY)
678: history.remove(history.size() - 1);
679: }
680:
681: // A history entry consists of a command number and either a String or a
682: // LispObject.
683: private static final class HistoryEntry {
684: int commandNumber;
685: Object obj;
686:
687: HistoryEntry(int commandNumber, Object obj) {
688: this.commandNumber = commandNumber;
689: this.obj = obj;
690: }
691: }
692: }
|