/*
 * Load.java
 *
 * Copyright (C) 2002-2007 Peter Graves
 * $Id$
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on this library.  If you modify this library, you may extend
 * this exception to your version of the library, but you are not
 * obligated to do so.  If you do not wish to do so, delete this
 * exception statement from your version.
 */

package org.armedbear.lisp;

import static org.armedbear.lisp.Lisp.*;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;

/* This file holds ABCL's (FASL and non-FASL) loading behaviours.
 *
 * The loading process works like this:
 *   The loader associates the input filename with a special variable
 *   and starts evaluating the forms in the file.
 *
 *   If one of the forms is (INIT-FASL :VERSION <version>), from that
 *   point the file is taken to be a FASL.
 *   The FASL loader takes over and retrieves the file being loaded
 *   from the special variable and continues loading from there.
 *
 */
public final class Load
{
    public static final LispObject load(String filename)
    {
        final LispThread thread = LispThread.currentThread();
        return load((Pathname)Pathname.create(filename),
                    Symbol.LOAD_VERBOSE.symbolValue(thread) != NIL,
                    Symbol.LOAD_PRINT.symbolValue(thread) != NIL,
                    true);
    }
  
    /** @return Pathname of loadable file based on NAME, or null if
     * none can be determined. */
    private static final Pathname findLoadableFile(Pathname name) {
      LispObject truename  = Symbol.PROBE_FILE.execute(name);
      if (truename instanceof Pathname) {
        Pathname t = (Pathname)truename;
        if (t.getName() != NIL
            && t.getName() != null) {
          return t;
        }
      }
      final String COMPILE_FILE_TYPE = Lisp._COMPILE_FILE_TYPE_.symbolValue().getStringValue();
      if (name.getType() == NIL
          && (name.getName() != NIL || name.getName() != null)) {
        Pathname lispPathname = Pathname.create(name);
        lispPathname.setType(new SimpleString("lisp"));
        LispObject lisp = Symbol.PROBE_FILE.execute(lispPathname);
        Pathname abclPathname = Pathname.create(name);
        abclPathname.setType(new SimpleString(COMPILE_FILE_TYPE));
        LispObject abcl = Symbol.PROBE_FILE.execute(abclPathname);
        if (lisp instanceof Pathname && abcl instanceof Pathname) {
          lispPathname = (Pathname)lisp;
          abclPathname = (Pathname)abcl;
          long lispLastModified = lispPathname.getLastModified();
          long abclLastModified = abclPathname.getLastModified();
          if (abclLastModified > lispLastModified) {
            return abclPathname;  // fasl file is newer
          } else {
            return lispPathname;
          }
        } else if (abcl instanceof Pathname) {
          return (Pathname) abcl;
        } else if (lisp instanceof Pathname) { 
          return (Pathname) lisp;
        }
      }
      if (name.isJar()) {
        if (name.getType().equals(NIL)) {
          name.setType(COMPILE_FILE_INIT_FASL_TYPE);
          Pathname result = findLoadableFile(name);
          if (result != null) {
            return result;
          }
          name.setType(new SimpleString(COMPILE_FILE_TYPE));
          result = findLoadableFile(name);
          if (result != null) {
            return result;
          }
        }
      }
      return null;
    }
  
    public static final LispObject load(Pathname pathname,
                                        boolean verbose,
                                        boolean print,
                                        boolean ifDoesNotExist)
    {
        return load(pathname, verbose, print, ifDoesNotExist, false, Keyword.DEFAULT);
    }
  
    public static final LispObject load(InputStream in) 
    {
      return load(in, new SimpleString("UTF-8"));
    }
  
    public static  final LispObject load(InputStream in, LispObject format) {
        Stream stream = new Stream(Symbol.SYSTEM_STREAM, in, Symbol.CHARACTER, 
                                   format);
        final LispThread thread = LispThread.currentThread();
        return loadFileFromStream(null, 
                                  null,
                                  stream,
                                  Symbol.LOAD_VERBOSE.symbolValue(thread) != NIL,
                                  Symbol.LOAD_PRINT.symbolValue(thread) != NIL,
                                  false);
    }


    public static final LispObject load(final Pathname pathname,
                                        boolean verbose,
                                        boolean print,
                                        boolean ifDoesNotExist,
                                        boolean returnLastResult,
                                        LispObject externalFormat)
    {
        Pathname mergedPathname = null;
        if (!pathname.isAbsolute() && !pathname.isJar()) {
            Pathname pathnameDefaults 
                = coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue());
            mergedPathname = Pathname.mergePathnames(pathname, pathnameDefaults);
        }
        Pathname loadableFile = findLoadableFile(mergedPathname != null ? mergedPathname : pathname);
        Pathname truename = (loadableFile != null
                             ? (Pathname)Symbol.PROBE_FILE.execute(loadableFile)
                             : null);
        
        if (truename == null || truename.equals(NIL)) {
          if (ifDoesNotExist) {
            return error(new FileError("File not found: " + pathname.princToString(), pathname));
          } else {
            Debug.warn("Failed to load " + pathname.getNamestring());
            return NIL;
          }
        }

        if (ZipCache.checkZipFile(truename)) {
          if (truename instanceof JarPathname) {
            truename = JarPathname.createFromEntry((JarPathname)truename);
          } else {
            truename = JarPathname.createFromPathname(truename);
          }
          Pathname loader = Pathname.create("__loader__._"); // FIXME use constants
          mergedPathname = (Pathname)Symbol.MERGE_PATHNAMES.execute(loader, truename);

          LispObject initTruename = Symbol.PROBE_FILE.execute(mergedPathname);
          if (initTruename.equals(NIL)) {
            // Maybe the enclosing JAR has been renamed?
            Pathname p = Pathname.create(mergedPathname);
            p.setName(Keyword.WILD);
            LispObject result = Symbol.MATCH_WILD_JAR_PATHNAME.execute(p);
            
            if (result instanceof Cons
                && ((Cons)result).length() == 1
                && ((Cons)result).car() instanceof Pathname) {
              initTruename = (Pathname)result.car();
            } else {
              String errorMessage
                = "Loadable FASL not found for "
                + pathname.printObject() 
                + " in "
                + mergedPathname.printObject();
              if (ifDoesNotExist) {
                return error(new FileError(errorMessage, mergedPathname));
              } else {
                Debug.trace(errorMessage);
                return NIL;
              }
            }
          }
          truename = (Pathname)initTruename;
        } 
                                
        InputStream in = truename.getInputStream();
        Debug.assertTrue(in != null);
    
        try {
            return loadFileFromStream(pathname, truename,
                                      new Stream(Symbol.SYSTEM_STREAM, in, Symbol.CHARACTER, externalFormat),
                                      verbose, print, false, returnLastResult);
        }
        finally {
            if (in != null) {
                try {
                   in.close();
                }
                catch (IOException e) {
                    return error(new LispError(e.getMessage()));
                }
            }
        }
    }

    public static LispObject loadSystemFile(String filename, boolean auto)
    {
        LispThread thread = LispThread.currentThread();
        if (auto) {
            final SpecialBindingsMark mark = thread.markSpecialBindings();
            // Due to autoloading, we're not sure about the loader state.
            // Make sure that all reader relevant variables have known state.
            thread.bindSpecial(Symbol.CURRENT_READTABLE,
                               STANDARD_READTABLE.symbolValue(thread));
            thread.bindSpecial(Symbol.READ_BASE, Fixnum.constants[10]);
            thread.bindSpecial(Symbol.READ_SUPPRESS, NIL);
            thread.bindSpecial(Symbol.READ_EVAL, T);
            thread.bindSpecial(Symbol.READ_DEFAULT_FLOAT_FORMAT, Symbol.SINGLE_FLOAT);
            thread.bindSpecial(Symbol._PACKAGE_, PACKAGE_CL_USER);
            try {
                return loadSystemFile(filename,
                                      _AUTOLOAD_VERBOSE_.symbolValue(thread) != NIL,
                                      Symbol.LOAD_PRINT.symbolValue(thread) != NIL,
                                      auto);
            }
            finally {
                thread.resetSpecialBindings(mark);
            }
        } else {
            return loadSystemFile(filename,
                                  Symbol.LOAD_VERBOSE.symbolValue(thread) != NIL,
                                  Symbol.LOAD_PRINT.symbolValue(thread) != NIL,
                                  auto);
        }
    }

    private static final Symbol FASL_LOADER = PACKAGE_SYS.intern("*FASL-LOADER*");
  /** A file with this type in a packed FASL denotes the initial loader */
    static final LispObject COMPILE_FILE_INIT_FASL_TYPE = new SimpleString("_");

    private static final Pathname coercePathnameOrNull(LispObject p) {
        if (p == null) {
            return null;
        }
        Pathname result = null;
        try {
            result = (Pathname)p;
        } catch (Throwable t) { // XXX narrow me!
            return null;
        }
        return result;
    }
        

    public static final LispObject loadSystemFile(final String filename,
                                                  boolean verbose,
                                                  boolean print,
                                                  boolean auto)
    {
        InputStream in = null;
        Pathname pathname = null;
        Pathname truename = null;
        pathname = (Pathname)Pathname.create(filename);
        LispObject bootPath = Site.getLispHome();
        Pathname mergedPathname;
        if (bootPath instanceof Pathname) {
          mergedPathname = (Pathname)Symbol.MERGE_PATHNAMES.execute(pathname, bootPath);
          // So a PROBE-FILE won't attempt to merge when
          // *DEFAULT-PATHNAME-DEFAULTS* is a JAR
          if (mergedPathname.getDevice().equals(NIL)
              && !Utilities.isPlatformWindows) {
            mergedPathname.setDevice(Keyword.UNSPECIFIC);
          }
        } else {
          mergedPathname = pathname;
        }
        URL url = null;
        Pathname loadableFile = findLoadableFile(mergedPathname);
        if (loadableFile == null) {
          truename = null;
        } else {
          truename = (Pathname)Symbol.PROBE_FILE.execute(loadableFile);
        }
        
        final String COMPILE_FILE_TYPE 
          = Lisp._COMPILE_FILE_TYPE_.symbolValue().getStringValue();

        if (truename == null || truename.equals(NIL) || bootPath.equals(NIL)) {
            // Make an attempt to use the boot classpath
            String path = pathname.asEntryPath();
            url = Lisp.class.getResource(path);            
            if (url == null || url.toString().endsWith("/")) {
                url = Lisp.class.getResource(path.replace('-', '_') + "." + COMPILE_FILE_TYPE);
                if (url == null) {
                    url = Lisp.class.getResource(path + ".lisp");
                }
            }
            if (url == null) {
                return error(new LispError("Failed to find loadable system file "
                                           + "'" + path + "'"
                                           + " in boot classpath."));
            }                
            if (!bootPath.equals(NIL)) {
              Pathname urlPathname = (Pathname)URLPathname.create(url);
              loadableFile = findLoadableFile(urlPathname);
              truename = (Pathname)Symbol.PROBE_FILE.execute(loadableFile);
              if (truename.equals(NIL)) {
                return error(new LispError("Failed to find loadable system file in boot classpath "
                                           + "'" + url + "'"));
              }
            } else {
                truename = null; // We can't represent the FASL in a Pathname (q.v. OSGi)
            }
        }

        // Look for a init FASL inside a packed FASL
        if (truename != null
            && truename.getType().princToString().equals(COMPILE_FILE_TYPE) && ZipCache.checkZipFile(truename))  {
          Pathname init = (Pathname)Pathname.create(truename.getNamestring());
            init.setType(COMPILE_FILE_INIT_FASL_TYPE);
            init.setName(new SimpleString("__loader__"));
            LispObject t = Symbol.PROBE_FILE.execute(init);
            if (t instanceof Pathname) {
                truename = (Pathname)t;
            } else {
                return error (new LispError("Failed to find loadable init FASL in "
                                            + "'" + init.getNamestring() + "'"));
            }
        }

        if (truename != null) {
            in = truename.getInputStream();
        } else { 
            try {
                Debug.assertTrue(url != null);
                in = url.openStream();
            } catch (IOException e) {
                error(new FileError("Failed to load system file: " 
                                    + "'" + filename + "'"
                                    + " from URL: " 
                                    + "'" + url + "'"));
            } 
        }

        if (in != null) {
            final LispThread thread = LispThread.currentThread();
            final SpecialBindingsMark mark = thread.markSpecialBindings();
            thread.bindSpecial(_WARN_ON_REDEFINITION_, NIL);
            thread.bindSpecial(FASL_LOADER, NIL);
            try {
                Stream stream = new Stream(Symbol.SYSTEM_STREAM, in, Symbol.CHARACTER);
                return loadFileFromStream(pathname, truename, stream,
                                          verbose, print, auto);
            } finally {
                thread.resetSpecialBindings(mark);
                try {
                    in.close();
                }
                catch (IOException e) {
                    return error(new LispError(e.getMessage()));
                }
            }
        }
        return error(new FileError("Failed to load system file: " 
                                   + "'" + filename + "'"
                                   + " resolved as " 
                                   + "'" + mergedPathname + "'" , 
                                   truename));
    }

    // ### *fasl-version*
    // internal symbol
    static final Symbol _FASL_VERSION_ =
        exportConstant("*FASL-VERSION*", PACKAGE_SYS, Fixnum.getInstance(43));

    // ### *fasl-external-format*
    // internal symbol
    private static final Symbol _FASL_EXTERNAL_FORMAT_ =
        internConstant("*FASL-EXTERNAL-FORMAT*", PACKAGE_SYS,
                       new SimpleString("UTF-8"));

    // ### *fasl-uninterned-symbols*
    // internal symbol
    /**
     * This variable gets bound to NIL upon loading a FASL, but
     * gets set to a vector of symbols as one of the first actions
     * by the FASL itself.
     *
     */
    public static final Symbol _FASL_UNINTERNED_SYMBOLS_ =
        internSpecial("*FASL-UNINTERNED-SYMBOLS*", PACKAGE_SYS, NIL);

    // Function to access the uninterned symbols "array"
    public final static LispObject getUninternedSymbol(int n) {
        LispThread thread = LispThread.currentThread();
        LispObject uninternedSymbols =
            Load._FASL_UNINTERNED_SYMBOLS_.symbolValue(thread);

        if (! (uninternedSymbols instanceof Cons)) // it must be a vector
            return uninternedSymbols.AREF(n);

        // During normal loading, we won't get to this bit, however,
        // with eval-when processing, we may need to fall back to
        // *FASL-UNINTERNED-SYMBOLS* being an alist structure
        LispObject label = LispInteger.getInstance(n);
        while (uninternedSymbols != NIL)
            {
                LispObject item = uninternedSymbols.car();
                if (label.eql(item.cdr()))
                  return item.car();

                uninternedSymbols = uninternedSymbols.cdr();
            }
        return error(new LispError("No entry for uninterned symbol."));
    }


    // ### init-fasl &key version
    private static final Primitive INIT_FASL = new init_fasl();
    private static class init_fasl extends Primitive {
        init_fasl() {
            super("init-fasl", PACKAGE_SYS, true, "&key version");
        }
        @Override
        public LispObject execute(LispObject first, LispObject second)

        {
            final LispThread thread = LispThread.currentThread();
            if (first == Keyword.VERSION) {
                if (second.eql(_FASL_VERSION_.getSymbolValue())) {
                    // OK
                    thread.bindSpecial(_FASL_UNINTERNED_SYMBOLS_, NIL);
                    thread.bindSpecial(_SOURCE_, NIL);
                    return faslLoadStream(thread);
                }
            }
            return
                error(new SimpleError("FASL version mismatch; found '"
                        + second.princToString() + "' but expected '"
                        + _FASL_VERSION_.getSymbolValue().princToString()
                        + "' in "
                        + Symbol.LOAD_PATHNAME.symbolValue(thread).princToString()
                        + " (try recompiling the file)"));
        }
    }

    private static final LispObject loadFileFromStream(Pathname pathname,
                                                       Pathname truename,
                                                       Stream in,
                                                       boolean verbose,
                                                       boolean print,
                                                       boolean auto)
        {
            return loadFileFromStream(pathname == null ? NIL : pathname,
                                      truename == null ? NIL : truename,
                                      in, verbose, print, auto, false);
    }

    private static Symbol[] savedSpecials =
        new Symbol[] { // CLHS Specified
                       Symbol.CURRENT_READTABLE, Symbol._PACKAGE_,
                       // Compiler policy
                       _SPEED_, _SPACE_, _SAFETY_, _DEBUG_, _EXPLAIN_ };

    // A nil TRUENAME signals a load from stream which has no possible path
    private static final LispObject loadFileFromStream(LispObject pathname,
                                                       LispObject truename,
                                                       Stream in,
                                                       boolean verbose,
                                                       boolean print,
                                                       boolean auto,
                                                       boolean returnLastResult)

    {
        long start = System.currentTimeMillis();
        final LispThread thread = LispThread.currentThread();
        final SpecialBindingsMark mark = thread.markSpecialBindings();

        for (Symbol special : savedSpecials)
            thread.bindSpecialToCurrentValue(special);

        thread.bindSpecial(_BACKQUOTE_COUNT_, Fixnum.getInstance(0));
        int loadDepth = Fixnum.getValue(_LOAD_DEPTH_.symbolValue(thread));
        thread.bindSpecial(_LOAD_DEPTH_, Fixnum.getInstance(++loadDepth));
        final String prefix = getLoadVerbosePrefix(loadDepth);
        try {
            thread.bindSpecial(Symbol.LOAD_PATHNAME, pathname);

            // The motivation behind the following piece of complexity
            // is the need to preserve the semantics of
            // *LOAD-TRUENAME* as always containing the truename of
            // the current "Lisp file".  Since an ABCL packed FASL
            // actually has a Lisp file (aka "the init FASL") and one
            // or more Java classes from the compiler, we endeavor to
            // make *LOAD-TRUENAME* refer to the "overall" truename so
            // that a (LOAD *LOAD-TRUENAME*) would be equivalent to
            // reloading the complete current "Lisp file".  If this
            // value diverges from the "true" truename, we set the
            // symbol SYS::*LOAD-TRUENAME-FASL* to that divergent
            // value.  Currently the only code that uses this value is
            // Lisp.readFunctionBytes().
            Pathname truePathname = null;
            if (!truename.equals(NIL)) {
                if (truename instanceof Pathname) {
                  if (truename instanceof JarPathname) {
                    truePathname = new JarPathname();
                  } else if (truename instanceof URLPathname) {
                    truePathname = new URLPathname();
                  } else {
                    truePathname = new Pathname();
                  }
                  truePathname.copyFrom((Pathname)truename);
                } else if (truename instanceof AbstractString) {
                  truePathname = (Pathname)Pathname.create(truename.getStringValue());
                } else {
                    Debug.assertTrue(false);
                }
                if (truePathname.getType().equal(Lisp._COMPILE_FILE_TYPE_.symbolValue(thread))
                    || truePathname.getType().equal(COMPILE_FILE_INIT_FASL_TYPE)) {
                    Pathname truenameFasl = Pathname.create(truePathname);
                    thread.bindSpecial(Symbol.LOAD_TRUENAME_FASL, truenameFasl);
                }
                if (truePathname.getType().equal(COMPILE_FILE_INIT_FASL_TYPE)
                    && truePathname.isJar()) {
                  // We set *LOAD-TRUENAME* to the argument that a
                  // user would pass to LOAD.
                  LispObject possibleTruePathname = probe_file.PROBE_FILE.execute(pathname);
                  if (!possibleTruePathname.equals(NIL)) {
                    truePathname = (Pathname) possibleTruePathname;
                  }
                  /*
                  if (truePathname.getDevice().cdr() != NIL ) {
                    Pathname enclosingJar = (Pathname)truePathname.getDevice().cdr().car();
                    truePathname.setDevice(new Cons(truePathname.getDevice().car(), NIL));
                    truePathname.setHost(NIL);
                    truePathname.setDirectory(enclosingJar.getDirectory());
                    if (truePathname.getDirectory().car().equals(Keyword.RELATIVE)) {
                      truePathname.getDirectory().setCar(Keyword.ABSOLUTE);
                    }
                    truePathname.setName(enclosingJar.getName());
                    truePathname.setType(enclosingJar.getType());
                    } else {
                        // XXX There is something fishy in the asymmetry
                        // between the "jar:jar:http:" and "jar:jar:file:"
                        // cases but this currently passes the tests.
                        if (!(truePathname.device.car() instanceof AbstractString)) {
                          //                          assert truePathname.getDevice().car() instanceof Pathname;
                          //                          Pathname p = Pathname.create((Pathname)truePathname.getDevice().car());
                          truePathname 
                            = (Pathname) probe_file.PROBE_FILE.execute(pathname);
                        }
                    }
                  */
                  thread.bindSpecial(Symbol.LOAD_TRUENAME, truePathname);
                } else {
                    thread.bindSpecial(Symbol.LOAD_TRUENAME, truename);
                }
            } else {
                thread.bindSpecial(Symbol.LOAD_TRUENAME, truename);
            }
            thread.bindSpecial(_SOURCE_,
                               pathname != null ? pathname : NIL);
            if (verbose) {
                Stream out = getStandardOutput();
                out.freshLine();
                out._writeString(prefix);
                out._writeString(auto ? " Autoloading " : " Loading ");
                out._writeString(!truename.equals(NIL) ? truePathname.princToString() : "stream");
                out._writeLine(" ...");
                out._finishOutput();
                LispObject result = loadStream(in, print, thread, returnLastResult);
                long elapsed = System.currentTimeMillis() - start;
                out.freshLine();
                out._writeString(prefix);
                out._writeString(auto ? " Autoloaded " : " Loaded ");
                out._writeString(!truename.equals(NIL) ? truePathname.princToString() : "stream");
                out._writeString(" (");
                out._writeString(String.valueOf(((float)elapsed)/1000));
                out._writeLine(" seconds)");
                out._finishOutput();
                return result;
            } else
                return loadStream(in, print, thread, returnLastResult);
        }
        finally {
            thread.resetSpecialBindings(mark);
        }
    }

    public static String getLoadVerbosePrefix(int loadDepth)
    {
        StringBuilder sb = new StringBuilder(";");
        for (int i = loadDepth - 1; i-- > 0;)
            sb.append(' ');
        return sb.toString();
    }

    private static final LispObject loadStream(Stream in, boolean print,
                                               LispThread thread, boolean returnLastResult)

    {
        final SpecialBindingsMark mark = thread.markSpecialBindings();
        thread.bindSpecial(_LOAD_STREAM_, in);
        SpecialBinding sourcePositionBinding =
            thread.bindSpecial(_SOURCE_POSITION_, Fixnum.ZERO);
        try {
            final Environment env = new Environment();
            LispObject result = NIL;
            while (true) {
                sourcePositionBinding.value = Fixnum.getInstance(in.getOffset());
                LispObject obj = in.read(false, EOF, false,
                                         thread, Stream.currentReadtable);
                if (obj == EOF)
                    break;
                result = eval(obj, env, thread);
                if (print) {
                    Stream out =
                        checkCharacterOutputStream(Symbol.STANDARD_OUTPUT.symbolValue(thread));
                    out._writeLine(result.printObject());
                    out._finishOutput();
                }
            }
            if(returnLastResult) {
                return result;
            } else {
                return T;
            }
        }
        finally {
            thread.resetSpecialBindings(mark);
        }
    }

    static final LispObject faslLoadStream(LispThread thread)
    {
        Stream in = (Stream) _LOAD_STREAM_.symbolValue(thread);
        final Environment env = new Environment();
        final SpecialBindingsMark mark = thread.markSpecialBindings();
        LispObject result = NIL;
        try {
            // Same bindings are established in Lisp.readObjectFromString()
            thread.bindSpecial(Symbol.READ_BASE, LispInteger.getInstance(10));
            thread.bindSpecial(Symbol.READ_EVAL, Symbol.T);
            thread.bindSpecial(Symbol.READ_SUPPRESS, Nil.NIL);

            in.setExternalFormat(_FASL_EXTERNAL_FORMAT_.symbolValue(thread));
            while (true) {
                LispObject obj = in.read(false, EOF, false,  // should be 'true' once we
                                                             // have a FASL wide object table
                                         thread, Stream.faslReadtable);
                if (obj == EOF)
                    break;
                result = eval(obj, env, thread);
            }
        }
        finally {
            thread.resetSpecialBindings(mark);
        }
        return result;
        //There's no point in using here the returnLastResult flag like in
        //loadStream(): this function is only called from init-fasl, which is
        //only called from load, which already has its own policy for choosing
        //whether to return T or the last value.
    }


    // ### %load filespec verbose print if-does-not-exist external-format=> generalized-boolean
    private static final Primitive _LOAD = new _load();
    private static class _load extends Primitive {
        _load() {
            super("%load", PACKAGE_SYS, false,
                  "filespec verbose print if-does-not-exist external-format");
        }
        @Override
        public LispObject execute(LispObject filespec, LispObject verbose,
                                  LispObject print, LispObject ifDoesNotExist, 
                                  LispObject externalFormat)
        {
            return load(filespec, verbose, print, ifDoesNotExist, NIL, externalFormat);
        }
    }

    // ### %load-returning-last-result filespec verbose print if-does-not-exist external-format => object
    private static final Primitive _LOAD_RETURNING_LAST_RESULT = new _load_returning_last_result();
    private static class _load_returning_last_result extends Primitive {
        _load_returning_last_result() {
            super("%load-returning-last-result", PACKAGE_SYS, false,
                  "filespec verbose print if-does-not-exist external-format");
        }
        @Override
        public LispObject execute(LispObject filespec, LispObject verbose,
                                  LispObject print, LispObject ifDoesNotExist, 
                                  LispObject externalFormat) {
             return load(filespec, verbose, print, ifDoesNotExist, T, externalFormat);
        }
    }

    static final LispObject load(LispObject filespec,
                                 LispObject verbose,
                                 LispObject print,
                                 LispObject ifDoesNotExist,
                                 LispObject returnLastResult,
                                 LispObject externalFormat)
        {
        if (filespec instanceof Stream) {
            if (((Stream)filespec).isOpen()) {
                // !?! in this case the external-format specifier is ignored:  warn user?
                LispObject pathname;
                if (filespec instanceof FileStream)
                    pathname = ((FileStream)filespec).getPathname();
                else
                    pathname = NIL;
                LispObject truename;
                if (pathname instanceof Pathname)
                    truename = pathname;
                else
                    truename = NIL;
                return loadFileFromStream(pathname,
                                          truename,
                                          (Stream) filespec,
                                          verbose != NIL,
                                          print != NIL,
                                          false,
                                          returnLastResult != NIL);
            }
            // If stream is closed, fall through...
        }
        Pathname pathname = coerceToPathname(filespec);
        if (pathname instanceof LogicalPathname)
            pathname = LogicalPathname.translateLogicalPathname((LogicalPathname)pathname);
        return load(pathname,
                    verbose != NIL,
                    print != NIL,
                    ifDoesNotExist != NIL,
                    returnLastResult != NIL,
                    externalFormat);
    }

    // ### load-system-file
    public static final Primitive LOAD_SYSTEM_FILE = new pf_load_system_file();
    private static class pf_load_system_file extends Primitive {
        pf_load_system_file () {
            super("load-system-file", PACKAGE_SYS, true);
        }
        @Override
        public LispObject execute(LispObject arg)
        {
            final LispThread thread = LispThread.currentThread();
            return loadSystemFile(arg.getStringValue(),
                                  Symbol.LOAD_VERBOSE.symbolValue(thread) != NIL
                                  || System.getProperty("abcl.autoload.verbose") != null,
                                  Symbol.LOAD_PRINT.symbolValue(thread) != NIL,
                                  false);
        }
    }
}
