source: branches/streams/abcl/src/org/armedbear/lisp/Interpreter.java

Last change on this file was 14591, checked in by Mark Evenson, 11 years ago

Correctly initialize/restore *BACKQUOTE-COUNT* special when loading FASLs.

Throw intelligible errors from problems encountered by SHARPSIGN-DOT
macros passed through '--eval' arguments.

Fixes #334.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 25.2 KB
Line 
1/*
2 * Interpreter.java
3 *
4 * Copyright (C) 2002-2006 Peter Graves
5 * $Id: Interpreter.java 14591 2013-12-27 15:58:16Z mevenson $
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
34package org.armedbear.lisp;
35
36import static org.armedbear.lisp.Lisp.*;
37
38import java.io.BufferedReader;
39import java.io.File;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.InputStreamReader;
43import java.io.OutputStream;
44
45public 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
60    public static synchronized Interpreter getInstance()
61    {
62        return interpreter;
63    }
64
65    // Interface.
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
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
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
137    public static boolean initialized() {
138        return initialized;
139    }
140
141    private Interpreter()
142    {
143        jlisp = false;
144        inputStream = null;
145        outputStream = null;
146    }
147
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(new Pathname(initialDirectory));
159    }
160
161    // Interface.
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
169    public static synchronized void initializeLisp()
170    {
171        if (!initialized) {
172            Load.loadSystemFile("boot.lisp", false, false, false);
173            initialized = true;
174        }
175    }
176
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
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
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
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*
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.
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(new Pathname(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")
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
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
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
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
487    public synchronized void dispose()
488    {
489        Debug.trace("Interpreter.dispose");
490        Debug.assertTrue(interpreter == this);
491        interpreter = null;
492    }
493
494    @Override
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
504        UnhandledCondition(LispObject condition) {
505            this.condition = condition;
506        }
507
508        public LispObject getCondition() {
509            return condition;
510        }
511
512        @Override
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
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     */
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();
631                reader.close();
632            }
633            catch (IOException e) {}
634        }
635        build = s;
636    }
637
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    }
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}
Note: See TracBrowser for help on using the repository browser.