1 /*
2  * Interpreter.java
3  *
4  * Copyright (C) 2002-2006 Peter Graves
5  * $Id$
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * As a special exception, the copyright holders of this library give you
22  * permission to link this library with independent modules to produce an
23  * executable, regardless of the license terms of these independent
24  * modules, and to copy and distribute the resulting executable under
25  * terms of your choice, provided that you also meet, for each linked
26  * independent module, the terms and conditions of the license of that
27  * module.  An independent module is a module which is not derived from
28  * or based on this library.  If you modify this library, you may extend
29  * this exception to your version of the library, but you are not
30  * obligated to do so.  If you do not wish to do so, delete this
31  * exception statement from your version.
32  */
33 
34 package org.armedbear.lisp;
35 
36 import static org.armedbear.lisp.Lisp.*;
37 
38 import java.io.BufferedReader;
39 import java.io.File;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.OutputStream;
44 
45 public final class Interpreter
46 {
47     // There can only be one interpreter.
48     public static Interpreter interpreter;
49 
50     private final boolean jlisp;
51     private final InputStream inputStream;
52     private final OutputStream outputStream;
53 
54     private static boolean noinit = false;
55     private static boolean nosystem = false;
56     private static boolean noinform = false;
57     private static boolean help = false;
58     private static boolean doubledash = false;
59 
getInstance()60     public static synchronized Interpreter getInstance()
61     {
62         return interpreter;
63     }
64 
65     // Interface.
createInstance()66     public static synchronized Interpreter createInstance()
67     {
68         if (interpreter != null)
69             return null;
70         interpreter = new Interpreter();
71         _NOINFORM_.setSymbolValue(T);
72         initializeLisp();
73         return interpreter;
74     }
75 
createDefaultInstance(String[] args)76     public static synchronized Interpreter createDefaultInstance(String[] args)
77     {
78         if (interpreter != null)
79             return null;
80         interpreter = new Interpreter();
81 
82         if (args != null)
83             preprocessCommandLineArguments(args);
84         if (!noinform) {
85             Stream out = getStandardOutput();
86             out._writeString(banner());
87             out._finishOutput();
88         }
89         if (help) {
90             Stream out = getStandardOutput();
91             out._writeString(help());
92             out._finishOutput();
93             exit(0); // FIXME
94         }
95         if (noinform)
96             _NOINFORM_.setSymbolValue(T);
97         else {
98             double uptime = (System.currentTimeMillis() - Main.startTimeMillis) / 1000.0;
99             getStandardOutput()._writeString("Low-level initialization completed in " +
100                                              uptime + " seconds.\n");
101         }
102         initializeLisp();
103         initializeTopLevel();
104         if (!nosystem)
105             initializeSystem();
106         if (!noinit)
107             processInitializationFile();
108         doubledash = false;
109         if (args != null)
110             postprocessCommandLineArguments(args);
111 
112         return interpreter;
113     }
114 
createJLispInstance( InputStream in, OutputStream out, String initialDirectory, String version)115     public static synchronized Interpreter createJLispInstance(
116         InputStream in,
117         OutputStream out,
118         String initialDirectory,
119         String version)
120     {
121         if (interpreter != null)
122             return null;
123         interpreter = new Interpreter(in, out, initialDirectory);
124 
125         Stream stdout = getStandardOutput();
126         stdout._writeLine(version);
127         stdout._writeString(banner());
128         stdout._finishOutput();
129 
130         initializeJLisp();
131         initializeTopLevel();
132         initializeSystem();
133         processInitializationFile();
134         return interpreter;
135     }
136 
initialized()137     public static boolean initialized() {
138         return initialized;
139     }
140 
Interpreter()141     private Interpreter()
142     {
143         jlisp = false;
144         inputStream = null;
145         outputStream = null;
146     }
147 
Interpreter(InputStream inputStream, OutputStream outputStream, String initialDirectory)148     private Interpreter(InputStream inputStream, OutputStream outputStream,
149                         String initialDirectory)
150     {
151         jlisp = true;
152         this.inputStream = inputStream;
153         this.outputStream = outputStream;
154         resetIO(new Stream(Symbol.SYSTEM_STREAM, inputStream, Symbol.CHARACTER),
155                 new Stream(Symbol.SYSTEM_STREAM, outputStream, Symbol.CHARACTER));
156         if (!initialDirectory.endsWith(File.separator))
157             initialDirectory = initialDirectory.concat(File.separator);
158         Symbol.DEFAULT_PATHNAME_DEFAULTS.setSymbolValue(Pathname.create(initialDirectory));
159     }
160 
161     // Interface.
eval(String s)162     public LispObject eval(String s)
163     {
164         return Lisp.eval(new StringInputStream(s).read(true, NIL, false,
165                                                   LispThread.currentThread(),
166                                                   Stream.currentReadtable));
167     }
168 
initializeLisp()169     public static synchronized void initializeLisp()
170     {
171         if (!initialized) {
172             Load.loadSystemFile("boot.lisp", false, false, false);
173             initialized = true;
174         }
175     }
176 
initializeJLisp()177     public static synchronized void initializeJLisp()
178     {
179         if (!initialized) {
180             Symbol.FEATURES.setSymbolValue(new Cons(Keyword.J,
181                                                Symbol.FEATURES.getSymbolValue()));
182             Load.loadSystemFile("boot.lisp", false, false, false);
183 
184             try {
185                 Class.forName("org.armedbear.j.LispAPI");
186             }
187             catch (ClassNotFoundException e) { } // FIXME: what to do?
188 
189             Load.loadSystemFile("j.lisp", false); // not being autoloaded
190 
191             initialized = true;
192         }
193     }
194 
195     private static boolean topLevelInitialized;
196 
initializeTopLevel()197     private static synchronized void initializeTopLevel()
198     {
199         if (!topLevelInitialized) {
200             // Resolve top-level-loop autoload.
201             Symbol TOP_LEVEL_LOOP = intern("TOP-LEVEL-LOOP", PACKAGE_TPL);
202             LispObject tplFun = TOP_LEVEL_LOOP.getSymbolFunction();
203             if (tplFun instanceof Autoload) {
204                 Autoload autoload = (Autoload) tplFun;
205                 autoload.load();
206             }
207 
208             topLevelInitialized = true;
209         }
210     }
211 
processInitializationFile()212     private static synchronized void processInitializationFile()
213     {
214         try {
215             String userHome = System.getProperty("user.home");
216             File file = new File(userHome, ".abclrc");
217             if (file.isFile()) {
218                 final double startLoad = System.currentTimeMillis();
219                 Load.load(file.getCanonicalPath());
220                 if (!noinform) {
221                     final double loadtime
222                         = (System.currentTimeMillis() - startLoad) / 1000.0;
223                     getStandardOutput()
224                         ._writeString("Loading " + file + " completed in "
225                                       + loadtime + " seconds.\n");
226                 }
227                 return;
228             }
229         }
230         catch (IOException e) {
231             e.printStackTrace();
232         }
233     }
234 
initializeSystem()235     private static synchronized void initializeSystem()
236     {
237         Load.loadSystemFile("system", false); // not being autoloaded
238     }
239 
240     // Check for --noinit; verify that arguments are supplied for --load and
241     // --eval options.  Copy all unrecognized arguments into
242     // ext:*command-line-argument-list*
preprocessCommandLineArguments(String[] args)243     private static void preprocessCommandLineArguments(String[] args)
244     {
245         LispObject arglist = NIL;
246 
247         if (args != null) {
248             for (int i = 0; i < args.length; ++i) {
249                 String arg = args[i];
250                 if (doubledash) {
251                     arglist = new Cons(args[i], arglist);
252                 } else if (arg.equals("--")) {
253                     doubledash = true;
254                 } else if (arg.equals("--noinit")) {
255                     noinit = true;
256                 } else if (arg.equals("--nosystem")) {
257                     nosystem = true;
258                 } else if (arg.equals("--noinform")) {
259                     noinform = true;
260                 } else if (arg.equals("--help")) {
261                     help = true;
262                 } else if (arg.equals("--batch")) {
263                     _BATCH_MODE_.setSymbolValue(T);
264                 } else if (arg.equals("--eval")) {
265                     if (i + 1 < args.length) {
266                         ++i;
267                     } else {
268                         System.err.println("No argument supplied to --eval");
269                         exit(1); // FIXME
270                     }
271                 } else if (arg.equals("--load") ||
272                            arg.equals("--load-system-file")) {
273                     if (i + 1 < args.length) {
274                         ++i;
275                     } else {
276                         System.err.println("No argument supplied to --load");
277                         exit(1); // FIXME
278                     }
279                 } else {
280                     arglist = new Cons(args[i], arglist);
281                 }
282             }
283         }
284         arglist.nreverse();
285 
286         _COMMAND_LINE_ARGUMENT_LIST_.setSymbolValue(arglist);
287     }
288 
289     // Do the --load and --eval actions.
postprocessCommandLineArguments(String[] args)290     private static void postprocessCommandLineArguments(String[] args)
291 
292     {
293         if (args != null) {
294             for (int i = 0; i < args.length; ++i) {
295                 String arg = args[i];
296                 if (doubledash) {
297                     continue;
298                 } else if (arg.equals("--")) {
299                     doubledash = true;
300                 } else if (arg.equals("--eval")) {
301                     if (i + 1 < args.length) {
302                         try {
303                             evaluate(args[i + 1]);
304                         }
305                         catch (UnhandledCondition c) {
306                             final String separator =
307                                 System.getProperty("line.separator");
308                             StringBuilder sb = new StringBuilder();
309                             sb.append(separator);
310                             sb.append("Caught ");
311                             sb.append(c.getCondition().typeOf().printObject());
312                             sb.append(" while processing --eval option \"" +
313                                       args[i + 1] + "\":");
314                             sb.append(separator);
315                             sb.append("  ");
316                             final LispThread thread = LispThread.currentThread();
317                             thread.bindSpecial(Symbol.PRINT_ESCAPE, NIL);
318                             sb.append(c.getCondition().princToString());
319                             sb.append(separator);
320                             System.err.print(sb.toString());
321                             exit(2); // FIXME
322                         }
323                         ++i;
324                     } else {
325                         // Shouldn't happen.
326                         System.err.println("No argument supplied to --eval");
327                         exit(1); // FIXME
328                     }
329                 } else if (arg.equals("--load") ||
330                            arg.equals("--load-system-file")) {
331                     if (i + 1 < args.length) {
332                         if (arg.equals("--load"))
333                           Load.load(Pathname.mergePathnames((Pathname)Pathname.create(args[i + 1]),
334                                     checkPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.getSymbolValue())),
335                                     false, false, true);
336 
337                         else
338                             Load.loadSystemFile(args[i + 1], false); // not being autoloaded
339                         ++i;
340                     } else {
341                         // Shouldn't happen.
342                         System.err.println("No argument supplied to --load");
343                         exit(1);  // FIXME
344                     }
345                 }
346             }
347         }
348         if (_BATCH_MODE_.getSymbolValue() == T) {
349             exit(0); // FIXME
350         }
351     }
352 
353     @SuppressWarnings("CallToThreadDumpStack")
run()354     public void run()
355     {
356         final LispThread thread = LispThread.currentThread();
357         try {
358             Symbol TOP_LEVEL_LOOP = intern("TOP-LEVEL-LOOP", PACKAGE_TPL);
359             LispObject tplFun = TOP_LEVEL_LOOP.getSymbolFunction();
360             if (tplFun instanceof Function) {
361                 thread.execute(tplFun);
362                 return;
363             }
364         }
365         catch (ProcessingTerminated e) {
366             throw e;
367         }
368         catch (IntegrityError e) {
369             return;
370         }
371         catch (Throwable t) {
372             t.printStackTrace();
373             return;
374         }
375 
376         // We only arrive here if something went wrong and we weren't able
377         // to load top-level.lisp and run the normal top-level loop.
378         Stream out = getStandardOutput();
379         while (true) {
380             try {
381                 thread.resetStack();
382                 thread.clearSpecialBindings();
383                 out._writeString("* ");
384                 out._finishOutput();
385                 LispObject object =
386                     getStandardInput().read(false, EOF, false, thread,
387                                             Stream.currentReadtable);
388                 if (object == EOF)
389                     break;
390                 out.setCharPos(0);
391                 Symbol.MINUS.setSymbolValue(object);
392                 LispObject result = Lisp.eval(object, new Environment(), thread);
393                 Debug.assertTrue(result != null);
394                 Symbol.STAR_STAR_STAR.setSymbolValue(Symbol.STAR_STAR.getSymbolValue());
395                 Symbol.STAR_STAR.setSymbolValue(Symbol.STAR.getSymbolValue());
396                 Symbol.STAR.setSymbolValue(result);
397                 Symbol.PLUS_PLUS_PLUS.setSymbolValue(Symbol.PLUS_PLUS.getSymbolValue());
398                 Symbol.PLUS_PLUS.setSymbolValue(Symbol.PLUS.getSymbolValue());
399                 Symbol.PLUS.setSymbolValue(Symbol.MINUS.getSymbolValue());
400                 out = getStandardOutput();
401                 out.freshLine();
402                 LispObject[] values = thread.getValues();
403                 Symbol.SLASH_SLASH_SLASH.setSymbolValue(Symbol.SLASH_SLASH.getSymbolValue());
404                 Symbol.SLASH_SLASH.setSymbolValue(Symbol.SLASH.getSymbolValue());
405                 if (values != null) {
406                     LispObject slash = NIL;
407                     for (int i = values.length; i-- > 0;)
408                         slash = new Cons(values[i], slash);
409                     Symbol.SLASH.setSymbolValue(slash);
410                     for (int i = 0; i < values.length; i++)
411                         out._writeLine(values[i].printObject());
412                 } else {
413                     Symbol.SLASH.setSymbolValue(new Cons(result));
414                     out._writeLine(result.printObject());
415                 }
416                 out._finishOutput();
417             }
418             catch (StackOverflowError e) {
419                 getStandardInput().clearInput();
420                 out._writeLine("Stack overflow");
421             }
422             catch (ControlTransfer c) {
423                 // We're on the toplevel, if this occurs,
424                 // we're toast...
425                 reportError(c, thread);
426             }
427             catch (ProcessingTerminated e) {
428                 throw e;
429             }
430             catch (IntegrityError e) {
431                 return;
432             }
433             catch (Throwable t) {
434                 getStandardInput().clearInput();
435                 out.printStackTrace(t);
436                 thread.printBacktrace();
437             }
438         }
439     }
440 
reportError(ControlTransfer c, LispThread thread)441     private static void reportError(ControlTransfer c, LispThread thread)
442     {
443         getStandardInput().clearInput();
444         Stream out = getStandardOutput();
445         out.freshLine();
446         Condition condition = (Condition) c.getCondition();
447         out._writeLine("Error: unhandled condition: " +
448                        condition.princToString());
449         if (thread != null)
450             thread.printBacktrace();
451     }
452 
reportError(UnhandledCondition c, LispThread thread)453     private static void reportError(UnhandledCondition c, LispThread thread)
454     {
455         getStandardInput().clearInput();
456         Stream out = getStandardOutput();
457         out.freshLine();
458         Condition condition = (Condition) c.getCondition();
459         out._writeLine("Error: unhandled condition: " +
460                        condition.princToString());
461         if (thread != null)
462             thread.printBacktrace();
463     }
464 
kill(int status)465     public void kill(int status)
466     {
467         if (jlisp) {
468             try {
469                 inputStream.close();
470             }
471             catch (IOException e) {
472                 Debug.trace(e);
473             }
474             try {
475                 outputStream.close();
476             }
477             catch (IOException e) {
478                 Debug.trace(e);
479             }
480         } else {
481             ((Stream)Symbol.STANDARD_OUTPUT.getSymbolValue())._finishOutput();
482             ((Stream)Symbol.ERROR_OUTPUT.getSymbolValue())._finishOutput();
483             System.exit(status);
484         }
485     }
486 
dispose()487     public synchronized void dispose()
488     {
489         Debug.trace("Interpreter.dispose");
490         Debug.assertTrue(interpreter == this);
491         interpreter = null;
492     }
493 
494     @Override
finalize()495     protected void finalize() throws Throwable
496     {
497         System.err.println("Interpreter.finalize");
498     }
499 
500     public static final class UnhandledCondition extends Error
501     {
502         LispObject condition;
503 
UnhandledCondition(LispObject condition)504         UnhandledCondition(LispObject condition) {
505             this.condition = condition;
506         }
507 
getCondition()508         public LispObject getCondition() {
509             return condition;
510         }
511 
512         @Override
getMessage()513         public String getMessage() {
514             String conditionText;
515             LispThread thread = LispThread.currentThread();
516             SpecialBindingsMark mark = thread.markSpecialBindings();
517             thread.bindSpecial(Symbol.PRINT_ESCAPE, NIL);
518             try {
519                 conditionText = getCondition().princToString();
520             } catch (Throwable t) {
521                 conditionText = "<error printing Lisp condition>";
522             } finally {
523                 thread.resetSpecialBindings(mark);
524             }
525 
526             return "Unhandled lisp condition: " + conditionText;
527         }
528 
529 
530     };
531 
532     private static final Primitive _DEBUGGER_HOOK_FUNCTION =
533         new Primitive("%debugger-hook-function", PACKAGE_SYS, false)
534     {
535         @Override
536         public LispObject execute(LispObject first, LispObject second)
537         {
538             final LispObject condition = first;
539             if (interpreter == null) {
540                 final LispThread thread = LispThread.currentThread();
541                 final SpecialBindingsMark mark = thread.markSpecialBindings();
542                 thread.bindSpecial(Symbol.PRINT_ESCAPE, NIL);
543                 try {
544                     final LispObject truename =
545                         Symbol.LOAD_TRUENAME.symbolValue(thread);
546                     if (truename != NIL) {
547                         final LispObject stream =
548                             _LOAD_STREAM_.symbolValue(thread);
549                         if (stream instanceof Stream) {
550                             final int lineNumber =
551                                 ((Stream)stream).getLineNumber() + 1;
552                             final int offset =
553                                 ((Stream)stream).getOffset();
554                             Debug.trace("Error loading " +
555                                         truename.princToString() +
556                                         " at line " + lineNumber +
557                                         " (offset " + offset + ")");
558                         }
559                     }
560                     Debug.trace("Encountered unhandled condition of type " +
561                                 condition.typeOf().princToString() + ':');
562                     Debug.trace("  " + condition.princToString());
563                 }
564                 catch (Throwable t) {} // catch any exception to throw below
565                 finally {
566                     thread.resetSpecialBindings(mark);
567                 }
568             }
569             UnhandledCondition uc = new UnhandledCondition(condition);
570             if (condition.typep(Symbol.JAVA_EXCEPTION) != NIL)
571                 uc.initCause((Throwable)JavaException
572                         .JAVA_EXCEPTION_CAUSE.execute(condition).javaInstance());
573             throw uc;
574         }
575     };
576 
readFromString(String s)577     public static final LispObject readFromString(String s)
578     {
579         return new StringInputStream(s).read(true, NIL, false,
580                                              LispThread.currentThread(),
581                                              Stream.currentReadtable);
582     }
583 
584     // For j.
585     /** Runs its input string through the lisp reader and evaluates the result.
586      *
587      * @param s A string with a valid Common Lisp expression
588      * @return The result of the evaluation
589      * @exception UnhandledCondition in case the an error occurs which
590      *      should be passed to the Lisp debugger
591      */
evaluate(String s)592     public static LispObject evaluate(String s)
593     {
594         if (!initialized)
595             initializeJLisp();
596         StringInputStream stream = new StringInputStream(s);
597         final LispThread thread = LispThread.currentThread();
598         LispObject obj = null;
599 
600         final SpecialBindingsMark mark0 = thread.markSpecialBindings();
601         thread.bindSpecial(Symbol.DEBUGGER_HOOK, _DEBUGGER_HOOK_FUNCTION);
602         try {  // catch possible errors from use of SHARPSIGN_DOT macros in --eval stanzas
603           obj = stream.read(false, EOF, false, thread,
604                             Stream.currentReadtable);
605         } finally {
606           thread.resetSpecialBindings(mark0);
607         }
608         if (obj == EOF)
609             return error(new EndOfFile(stream));
610 
611         final SpecialBindingsMark mark = thread.markSpecialBindings();
612         thread.bindSpecial(Symbol.DEBUGGER_HOOK, _DEBUGGER_HOOK_FUNCTION);
613         try {
614             return Lisp.eval(obj, new Environment(), thread);
615         }
616         finally {
617             thread.resetSpecialBindings(mark);
618         }
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 =
629                     new BufferedReader(new InputStreamReader(in));
630                 s = reader.readLine();
reader.close()631                 reader.close();
632             }
633             catch (IOException e) {}
634         }
635         build = s;
636     }
637 
banner()638     private static String banner()
639     {
640         final String sep = System.getProperty("line.separator");
641         StringBuilder sb = new StringBuilder("Armed Bear Common Lisp ");
642         sb.append(Version.getVersion());
643         if (build != null) {
644             sb.append(" (built ");
645             sb.append(build);
646             sb.append(')');
647         }
648         sb.append(sep);
649         sb.append("Java ");
650         sb.append(System.getProperty("java.version"));
651         sb.append(' ');
652         sb.append(System.getProperty("java.vendor"));
653         sb.append(sep);
654         String vm = System.getProperty("java.vm.name");
655         if (vm != null) {
656             sb.append(vm);
657             sb.append(sep);
658         }
659         return sb.toString();
660     }
help()661     private static String help()
662     {
663         final String sep = System.getProperty("line.separator");
664         StringBuilder sb = new StringBuilder("Parameters:");
665         sb.append(sep);
666         sb.append("--help").append(sep)
667           .append("    Displays this message.");
668         sb.append(sep);
669         sb.append("--noinform").append(sep)
670           .append("    Suppresses the printing of startup information and banner.");
671         sb.append(sep);
672         sb.append("--noinit").append(sep)
673           .append("    Suppresses the loading of the '~/.abclrc' startup file.");
674         sb.append(sep);
675         sb.append("--nosystem").append(sep)
676           .append("    Suppresses loading the 'system.lisp' customization file. ");
677         sb.append(sep);
678         sb.append("--eval <FORM>").append(sep)
679           .append("    Evaluates the <FORM> before initializing REPL.");
680         sb.append(sep);
681         sb.append("--load <FILE>").append(sep)
682           .append("    Loads the file <FILE> before initializing REPL.");
683         sb.append(sep);
684         sb.append("--load-system-file <FILE>").append(sep)
685           .append("    Loads the system file <FILE> before initializing REPL.");
686         sb.append(sep);
687         sb.append("--batch").append(sep)
688           .append("    The process evaluates forms specified by arguments and possibly by those").append(sep)
689           .append("    by those in the intialization file '~/.abcl', and then exits.");
690         sb.append(sep);
691         sb.append(sep);
692         sb.append("The occurance of '--' copies the remaining arguments, unprocessed, into").append(sep)
693           .append("the variable EXTENSIONS:*COMMAND-LINE-ARGUMENT-LIST*.");
694         sb.append(sep);
695 
696         return sb.toString();
697     }
698 }
699