-- -*- coding: utf-8 -*-
--		Copyright 1993-2009 by Daniel R. Grayson
-- layout.m2, includes the former contents of ../m2/startup.m2
-- this file gets incorporated into the executable file bin/M2 as the string 'startupString'
-- we want to ignore the --datarootdir setting, whatever that means, so here we we mention it: @datarootdir@

interpreterDepth = loadDepth = errorDepth = 0
debuggingMode = true
stopIfError = false
notify = false
debugWarningHashcode = null
gotarg := arg -> any(commandLine, s -> s === arg)
randomSeed = -- initializing this here so the startup is reproducible
if gotarg "--no-randomize" then 0 else (currentTime() << 16) + processID()
if gotarg "--notify" then notify = true
if gotarg "--stop" then stopIfError = true
if gotarg "--int"  then handleInterrupts = false

firstTime := class PackageDictionary === Symbol

if firstTime then (
    -- we do this bit *before* "debug Core", so that Core (the symbol, not the package),
    -- which may not be there yet, ends up in the right dictionary
    match = x -> null =!= regex x;
    assert' = x -> (
	if class x =!= Boolean then error "assert: expected true or false";
	if not x then error "assertion failed");
    printerr = msg -> endl(printString(stderr, concatenate(loadDepth, "-- ", msg)));
    value = value'; -- TODO: is there a different way to get --no-core to work?
    --
    PackageDictionary = new Dictionary;
    dictionaryPath = append(dictionaryPath, PackageDictionary);
    assert'( not isGlobalSymbol "Core" );
    PackageDictionary#("Package$Core") = getGlobalSymbol(PackageDictionary, "Core");
    )
-- we can't make this an else-clause, because then "Core" will be in the wrong dictionary
disassemble ( () -> debug Core )
if not firstTime then debug Core -- we need access to the private symbols (we remove the Core private dictionary later.)

toString := value' getGlobalSymbol if firstTime then "simpleToString" else "toString"

local exe
local topBuilddir

-- this next bit has to be *parsed* after the "debug" above, to prevent the symbols from being added to the User dictionary
if firstTime then (
     -- all global definitions here are set to read-only
     if notify then printerr("loading startup.m2 for the first time");

     filesLoaded = new MutableHashTable;
     loadedFiles = new MutableHashTable;
     nobanner = false;
     texmacsmode = false;
     restarting = false;
     restarted = false;
     srcdirs = {};

     markLoaded = (filename, filepath, filetime, notify) -> (
	 filepath = minimizeFilename filepath;
	 filesLoaded#filename = (filepath, filetime);
	 loadedFiles##loadedFiles = realpath toAbsolutePath filepath;
	 if notify then printerr("loaded ", filepath));
     tryLoad = (filename, filepath, loadfun, notify) -> if fileExists filepath then (
	 filetime := fileTime filepath;
	 if notify then printerr("loading ", filename);
	 ret := loadfun filepath;
	 markLoaded(filename, filepath, filetime, notify);
	 (true, ret)) else (false, null);
     trySimpleLoad := (filename, filepath) -> first tryLoad(filename, filepath, simpleLoad, notify);

     normalPrompts = () -> (
	  lastprompt := "";
	  ZZ#{Standard,InputPrompt} = lineno -> concatenate(newline, lastprompt = concatenate(interpreterDepth:"i", toString lineno, " : "));
	  ZZ#{Standard,InputContinuationPrompt} = lineno -> #lastprompt; -- will print that many blanks, see interp.d
	  symbol currentPrompts <- normalPrompts;	    -- this avoids the warning about redefining a function
	  );
     normalPrompts();
     noPrompts = () -> (
	  ZZ#{Standard,InputPrompt} = lineno -> "";
	  ZZ#{Standard,InputContinuationPrompt} = lineno -> "";
	  symbol currentPrompts <- noPrompts;
	  );

     startFunctions := {};
     addStartFunction = f -> ( startFunctions = append(startFunctions,f); f);
     runStartFunctions = () -> scan(startFunctions, f -> f());

     endFunctions := {};
     addEndFunction = f -> ( endFunctions = append(endFunctions,f); f);
     runEndFunctions = () -> (
	  save := endFunctions;
	  endFunctions = {};
	  scan(save, f -> f());
	  endFunctions = save;
	  );

     simpleExit := exit;
     exit = ret -> ( runEndFunctions(); simpleExit ret );

     File << Thing  := File => (x,y) -> printString(x,toString y);
     File << Net := File << Symbol := File << String := printString;
     << Thing := x -> stdio << x;
     String | String := String => concatenate;
     Function _ Thing := Function => (f,x) -> y -> f splice (x,y);
     String | ZZ := String => (s,i) -> concatenate(s,toString i);
     ZZ | String := String => (i,s) -> concatenate(toString i,s);

     new HashTable from List := HashTable => (O,v) -> hashTable v;

     Manipulator = new Type of BasicList;
     Manipulator.synonym = "manipulator";
     new Manipulator from Function := Manipulator => (Manipulator,f) -> new Manipulator from {f};
     Manipulator Database := Manipulator File := Manipulator NetFile := (m,o) -> m#0 o;

     Manipulator Nothing := (m,null) -> null;
     File << Manipulator := File => (o,m) -> m#0 o;
     NetFile << Manipulator := File => (o,m) -> m#0 o;
     Nothing << Manipulator := (null,m) -> null;

     TeXmacsBegin = ascii 2;
     TeXmacsEnd   = ascii 5;

     close = new Manipulator from close;
     closeIn = new Manipulator from closeIn;
     closeOut = new Manipulator from closeOut;
     flush = new Manipulator from flush;
     endl = new Manipulator from endl;

     Thing#{Standard,Print} = x ->  (
	  << newline << concatenate(interpreterDepth:"o") << lineNumber << " = ";
	  try << x;
	  << newline << flush;
	  );

     first = x -> x#0;
     last = x -> x#-1;
     lines = x -> (
	  l := separate x;
	  if l#-1 === "" then drop(l,-1) else l);

     re := "/";						    -- /foo/bar
     if version#"operating system" === "MicrosoftWindows" 
     then re = re | "|.:/";				    -- "C:/FOO/BAR"
     re = re | "|\\$";					    -- $www.uiuc.edu:80
     re = re | "|!";					    -- !date
     re = re | "|~";					    -- ~user/foo/bar
     isAbsolutePathRegexp := "^(" | re | ")";		    -- whether the path will work from any directory and get to the same file
     re = re | "|\\./";					    -- ./foo/bar
     re = re | "|\\.\\./";				    -- ../foo/bar
     isStablePathRegexp   := "^(" | re | ")";               -- whether we should search only in the current directory (or current file directory)
     -- whether to search the path for an executable, based on execvp(3)
     isFixedExecPath = filename -> match("/", filename);
     isAbsolutePath = filename -> match(isAbsolutePathRegexp, filename);
     isStablePath = filename -> match(isStablePathRegexp, filename);
     concatPath = (dir, name) -> if isAbsolutePath name then name else concatenate(
	 dir, if not match("/$", dir) then "/", name);

     toAbsolutePath = pth -> if pth =!= "stdio" and not isAbsolutePath pth then "/" | relativizeFilename("/", pth) else pth;

     copyright = (
	  "Macaulay2, version " | version#"VERSION" | newline
	  | "Copyright 2019 Daniel R. Grayson and Michael E. Stillman:" | newline
     	  | "  source code licensed under the GNU General Public License version 2 or 3;" | newline
     	  | "  this binary code licensed under the GNU General Public License version 3." | newline
     	  -- our binary code is linked with :
	  --    mpir 2.4.0, which is under LGPL 2.1 or later
     	  --    mpfr 3.0.1, which is under LGPL 3 or later
	  -- so it cannot be licensed under GPL 2
     	  -- There is another license statement in ../../INSTALL 
	  | "For Macaulay2 packages, see their source code for licensing terms." | newline
     	  | "Compiled with the following libraries:" | newline
	  | "  GC " | version#"gc version" | ", by H. Boehm and Alan J. Demers" | newline
	  | "  Singular-Factory " | version#"factory version" | ", by G.-M. Greuel et al." | newline
	  |    ( 
	       if version#"frobby version" =!= "not present" 
	       then ("  Frobby " | version#"frobby version" | ", by B. H. Roune" | newline)
	       else ""
	       )
	  | "  NTL " | version#"ntl version" | ", by V. Shoup" | newline
	  | "  FLINT " | version#"flint version" | ", by W. Hart et al." | newline
	  |    (
	       if version#"gmp version" =!= "not present"
	       then "  GNU MP " | version#"gmp version" | ", by T. Granlund et al." | newline
	       else ""
	       )
     	  |    (
	       if version#"mpir version" =!= "not present"
	       then "  MPIR " | version#"mpir version" | ", by Free Software Foundation et al." | newline
	       else ""
	       )
          | "  Boost " | version#"boost version" | ", by the Boost community" | newline
	  | "  Givaro " | version#"givaro version" | ", by the LinBox Team" | newline
	  | "  FFLAS-FFPACK " | version#"fflas_ffpack version" | ", by the LinBox Team" | newline
     	  | "  MPFR " | version#"mpfr version" | ", by Free Software Foundation" | newline
	  | "  BLAS and LAPACK " | version#"lapack version" | ", by J. Dongarra et al." | newline
	  | "  MPSolve " | version#"mpsolve version" | ", by D. Bini et al." | newline
	  | "  Eigen " | version#"eigen version" | ", by B. Jacob et al." | newline
	  | "  TBB " | version#"tbb version" | ", by Intel" | newline
          | "  Memtailor " | version#"memtailor version" | ", by B. Roune" | newline
          | "  Mathic " | version#"mathic version" | ", by B. Roune and M. Stillman" | newline
          | "  MathicGB " | version#"mathicgb version" | ", by B. Roune and M. Stillman"
	  );

     use = x -> x;				  -- temporary, until methods.m2

     Attributes = new MutableHashTable;
     -- values are hash tables with keys Symbol, String, Net (as symbols); replaces ReverseDictionary and PrintNames
     setAttribute = (val,attr,x) -> (
	  if Attributes#?val then Attributes#val else Attributes#val = new MutableHashTable
	  )#attr = x;
     hasAnAttribute = (val) -> Attributes#?val;
     hasAttribute = (val,attr) -> Attributes#?val and Attributes#val#?attr;
     getAttribute = (val,attr) -> Attributes#val#attr;
     getAttributes = (attr0) -> (
	  r := new MutableHashTable;
	  scan(values Attributes, tab -> scan(pairs tab, (attr,x) -> if attr === attr0 then r#x = true));
	  keys r);
     removeAttribute = (val,attr) -> (
	  a := Attributes#val;
	  remove(a,attr);
	  if #a === 0 then remove(Attributes,val);
	  );
     protect PrintNet;
     protect PrintNames;
     protect ReverseDictionary;

     globalAssign = (s,v) -> if v =!= value' s then (
	  X := class value' s;
	  m := lookup(GlobalReleaseHook,X);
	  if m =!= null then m(s,value' s);
	  Y := class v;
	  n := lookup(GlobalAssignHook,Y);
	  if n =!= null then n(s,v);
	  s <- v);
     globalAssignFunction = (X,x) -> (
	  if not instance(X,Symbol) then error("globalAssignFunction: expected a symbol: ", toString X);
	  if not hasAttribute(x,ReverseDictionary) then setAttribute(x,ReverseDictionary,X);
	  use x;
	  );
     globalReleaseFunction = (X,x) -> (
	  if not instance(X,Symbol) then error("globalAssignFunction: expected a symbol: ", toString X);
	  if hasAttribute(x,ReverseDictionary)
	  and getAttribute(x,ReverseDictionary) === X
	  then removeAttribute(x,ReverseDictionary)
	  );
     globalAssignment = X -> (
	  if instance(X, VisibleList) then apply(X,globalAssignment)
	  else if instance(X,Type) then (
	       X.GlobalAssignHook = globalAssignFunction; 
	       X.GlobalReleaseHook = globalReleaseFunction;
	       )
	  else error "expected a type";
	  );
     globalAssignment {Type,Function,GlobalDictionary,Manipulator};
     scan((symbol true,symbol false,symbol stdio, symbol stderr), sym -> globalAssignFunction(sym, value' sym));
     scan(dictionaryPath, dict -> (
	       scan(pairs dict, (nm,sym) -> (
			 x := value' sym;
			 f := lookup(GlobalAssignHook, class x);
			 if f =!= null then f(sym,x);
			 ))));
     applicationDirectorySuffix = if version#"operating system" === "Darwin" then "Library/Application Support/Macaulay2/" else ".Macaulay2/";
     applicationDirectory = () -> (
	  if getenv "HOME" =!= ""
	  then (
	       getenv "HOME" | "/" |
     	       -- we allow the user to change applicationDirectorySuffix to a string or a function
	       if instance(applicationDirectorySuffix, Function)
	       then applicationDirectorySuffix()
	       else applicationDirectorySuffix
	       )
	  else error "environment variable HOME not set"
	  );

     initlayout := () -> (
	  if regex(".*/","/aa/bb") =!= {(0, 4)}
	  or regex("a|b","a") =!= {(0,1)}
	  or regex("^a+$"," \naaa\n ") =!= {(2,3)}
	  or regex("$a","$a") =!= null
	  or regex(".*","a\nb") =!= {(0, 1)}
	  or replace("a","b","-a-a-") =!= "-b-b-"
	  or select("a+","$&","aaa aaaa") =!= {"aaa","aaaa"}
	  then error "regex regular expression library not working";
	  t := hashTable {
	       "exec" => "${exec_prefix}/",
	       "common" => "${prefix}/",
	       "bin" => "@bindir@/",
	       "lib" => "@libdir@/",
	       "info" => "@infodir@/",
	       "data" => "@datadir@/",
	       "man" => "@mandir@/",
	       "emacs" => "@emacsdir@/",
	       "doc" => "@docdir@/",
	       "packages" => "@packagesdir@/",
	       "docdir" => "@docdir@/",
	       "libraries" => "@librariesdir@/",
	       "programs" => "@programsdir@/",
	       "program licenses" => "@licensesdir@/",
	       "package" => "@packagesdir@/PKG/",
	       "factory gftables" =>  "@GFTABLESDIR@",
	       "packagedoc" => "@docdir@/PKG/",
	       "packageimages" => "@docdir@/PKG/images/",
	       "packagetests" => "@docdir@/PKG/tests/",
	       "packagehtml" => "@docdir@/PKG/html/",
	       "packagecache" => "@libm2dir@/PKG/cache/",
	       "packagelib" => "@libm2dir@/PKG/",
	       "packageexampleoutput" => "@docdir@/PKG/example-output/"
	       };
	  Layout = hashTable {
	       1 => applyValues( t, x -> replace("^\\$\\{(pre_)?(exec_)?prefix\\}/", "", x)),
	       2 => applyValues( t, x -> 
		    replace("^\\$\\{(pre_)?prefix\\}", "common",				-- as in configure.ac
			 replace("^\\$\\{(pre_)?exec_prefix\\}", version#"machine", x)))};	-- as in configure.ac
	  finalPrefix = "@prefix@";
	  if last finalPrefix != "/" then finalPrefix = finalPrefix | "/";
	  );
     initlayout();
     )

dir  := s -> ( m := regex(".*/",s); if m === null or 0 === #m then "./" else substring(m#0#0,m#0#1-1,s))
base := s -> ( m := regex(".*/",s); if m === null or 0 === #m then s    else substring(m#0#1,      s))
initcurrentlayout := () -> (
     issuffix := (s,t) -> t =!= null and s === substring(t,-#s);
     bindir = dir exe | "/";
     currentLayout = (					    -- see also currentLayoutTable
	  if issuffix(Layout#2#"bin","/"|bindir) then Layout#2 else
	  if issuffix(Layout#1#"bin","/"|bindir) then Layout#1
	  );
     prefixDirectory = if currentLayout =!= null then realpath substring(bindir,0,#bindir-#currentLayout#"bin");
     -- we use "realpath" to produce real paths, because Cygwin-style symbolic links are not understood by native Windows applications
     if notify then printerr("prefixDirectory = ", prefixDirectory);
     if readlink exe =!= null then (
	  exe2 := concatPath(realpath dir exe, readlink exe);
	  bindir2 := dir exe2 | "/";
	  currentLayout2 := (
	       if issuffix(Layout#2#"bin",bindir2) then Layout#2 else
	       if issuffix(Layout#1#"bin",bindir2) then Layout#1
	       );
	  );
     prefixDirectory2 := if currentLayout2 =!= null then substring(bindir2,0,#bindir2-#currentLayout2#"bin");
     if prefixDirectory2 =!= null
	and isDirectory(prefixDirectory2|currentLayout2#"packages")
	and (currentLayout === null or not isDirectory(prefixDirectory|currentLayout#"packages"))
     then (
	  prefixDirectory = prefixDirectory2;
	  currentLayout = currentLayout2;
	  prefixPath = { prefixDirectory });
     usrDist := "usr-dist/";
     topBuilddir = (
	  if issuffix(usrDist,prefixDirectory) then substring(prefixDirectory,0,#prefixDirectory-#usrDist)
	  else
	  if issuffix(usrDist,prefixDirectory2) then substring(prefixDirectory2,0,#prefixDirectory2-#usrDist));
     if notify then printerr("topBuilddir     = ", topBuilddir);
     topSrcdir = if topBuilddir =!= null and fileExists(topBuilddir|"srcdir") then (
	  sdir := first lines get(topBuilddir|"srcdir");
	  minimizeFilename concatPath(topBuilddir,sdir));
     if notify then printerr("topSrcdir       = ", topSrcdir);
     )

prefixDirectory = null					    -- prefix directory, after installation, e.g., "/usr/local/"
prefixPath = {}

fullCopyright := false
matchpart := (pat,i,s) -> substring_((regex(pat, s))#i) s
notdir := s -> matchpart("[^/]*$",0,s)
nocore = false
noinitfile = false
interpreter := commandInterpreter

-*
getRealPath := fn -> (					    -- use this later if realpath doesn't work
     local s;
     while ( s = readlink fn; s =!= null ) do fn = if isAbsolutePath s then s else minimizeFilename(fn|"/../"|s);
     fn)
*-

pathsearch := e -> (
     if not isFixedExecPath e then (
	  -- we search the path, but we don't do it the same way execvp does, too bad.
	  PATH := separate(":",if "" =!= getenv "PATH" then getenv "PATH" else ".:/bin:/usr/bin");
	  PATH = apply(PATH, x -> if x === "" then "." else x);
	  scan(PATH, p -> if fileExists (p|"/"|e) then (e = p|"/"|e; break));
	  );
     e)

progname := notdir commandLine#0
usage := arg -> (
     << "usage:"             << newline
     << "    " << progname << " [option ...] [file ...]" << newline
     << "options:"  << newline
     << "    --help             print this brief help message and exit" << newline
     << "    --no-backtrace     print no backtrace after error" << newline
     << "    --copyright        display full copyright messasge" << newline
     << "    --no-debug         do not enter debugger upon error" << newline
     << "    --debug n          debug warning message n by signalling an error" << newline
     << "    --int              don't handle interrupts" << newline -- handled by M2lib.c
     << "    --notify           notify when loading files during initialization" << newline
     << "                       and when evaluating command line arguments" << newline
     << "    --prefix DIR       set prefixDirectory" << newline
     << "    --print-width n    set printWidth=n (the default is the window width)" << newline
     << "    --restarted        used internally to indicate this is a restart" << newline
     << "    --read-only-files  disallow file and socket creation and writing" << newline
     << "    --script           as first argument, interpret second argument as name of a script" << newline
     << "                       implies --stop, --no-debug, --silent and -q" << newline
     << "                       see scriptCommandLine" << newline
     << "    --silent           no startup banner" << newline
     << "    --stop             exit on error" << newline
     << "    --texmacs          TeXmacs session mode" << newline
     << "    --version          print version number and exit" << newline
     << "    --webapp           WebApp session mode" << newline
     << "    --no-core          don't load the Core" << newline
     << "    --no-preload       don't load the preloaded packages" << newline;
     << "    --no-prompts       print no input prompts" << newline;
     << "    --no-randomize     don't set the random number seed based on the date" << newline
     << "    --no-readline      don't use readline" << newline;
     << "    --no-threads       don't start any additional threads (or tasks)" << newline
     << "    --no-time          arrange for the current time to be always 0" << newline;
     << "    --no-tty           do not treat stdin and stdout as tty's" << newline
     << "    --no-tvalues       do not load tvalues.m2" << newline
     << "    -q                 don't load user's init.m2 file or use packages in home directory" << newline
     << "    -E '...'           evaluate expression '...' before initialization" << newline
     << "    -e '...'           evaluate expression '...' after initialization" << newline
     << "    --srcdir '...'     add top source or build tree '...' to initial path" << newline
     << "    --check n          run tests to level n" << newline
     << "                           n=1: basic tests" << newline
     << "                           n=2: test Core" << newline
     << "                           n=3: test all packages" << newline
     << "environment:"       << newline
     << "    EDITOR             default text editor" << newline
     << "    GC_INITIAL_HEAP_SIZE      (gc: initial heap size in bytes)" << newline
     << "    GC_PRINT_STATS            (gc: turn on logging)" << newline
     << "    GC_PRINT_VERBOSE_STATS    (gc: turn on more verbose logging)" << newline
     << "    GC_LOG_FILE               (gc: name of log file)" << newline
     << "    GC_DUMP_REGULARLY         (gc: dump state regularly)" << newline
     << "    GC_NPROCS                 (gc: number of threads to use, linux)" << newline
     ;)

showMaps := () -> (
     if version#"operating system" === "SunOS" then (
	  stack lines get ("!/usr/bin/pmap "|processID())
	  )
     else if version#"operating system" === "Linux" and fileExists("/proc/"|toString processID()|"/maps") then (
	  stack lines get("/proc/"|toString processID()|"/maps")
	  )
     else "memory maps not available"
     )

phase := 1

obsolete :=  arg     -> (stderr << "error: command line option " << arg << " is obsolete."                        << endl << endl; usage(); exit 1)
replaced := (arg, x) -> (stderr << "error: command line option " << arg << " has been replaced with " << x << "." << endl << endl; usage(); exit 1)
notyeterr := arg     -> (stderr << "error: command line option " << arg << " has not been re-implemented yet.";   << endl << endl; usage(); exit 1)
notyet := arg -> if phase == 1 then (stderr << "warning: command line option " << arg << " has not been re-implemented yet." << endl << flush)

action := hashTable {
     "-h" => arg -> (usage(); exit 0),
     "-mpwprompt" => notyeterr,
     "-n" => obsolete,
     "-q" => arg -> noinitfile = true,
     "-s" => obsolete,
     "-silent" => obsolete,
     "-tty" => notyet,
     "--copyright" => arg -> if phase == 1 then fullCopyright = true,
     "--help" => arg -> (usage(); exit 0),
     "--int" => arg -> arg,
     "--no-backtrace" => arg -> if phase == 1 then backtrace = false,
     "--no-debug" => arg -> debuggingMode = false,
     "--no-randomize" => arg -> arg,
     "--no-time" => arg -> arg,
     "--no-threads" => arg -> arg,
     "--no-preload" => arg -> arg,
     "--no-prompts" => arg -> if phase == 3 then noPrompts(),
     "--no-readline" => arg -> arg,			    -- handled in d/stdio.d
     "--no-setup"        => arg -> replaced(arg, "--no-core"),
     "--no-core"         => arg -> if phase == 1 then nocore = true,
     "--notify" => arg -> if phase <= 2 then notify = true,
     "--no-tty" => arg -> arg,			    -- handled in d/stdio.d
     "--no-tvalues" => arg -> arg,		    -- handled in m2/typicalvalues.m2
     "--read-only-files" => arg -> arg,		    -- handled in d/stdio.d
     "--script" => arg -> error "script option should be first argument, of two",
     "--silent" => arg -> nobanner = true,
     "--stop" => arg -> (if phase == 1 then stopIfError = true; debuggingMode = false;), -- see also M2lib.c and tokens.d
     "--restarted" => arg -> restarted = true,
     "--texmacs" => arg -> (
	  if phase == 1 then (
	       topLevelMode = global TeXmacs;
	       printWidth = 80;
	       )
	  else if phase == 3 then (
	       topLevelMode = global TeXmacs;
	       printWidth = 80;
	       )
	  else if phase == 4 then (
	       texmacsmode = true;
	       topLevelMode = TeXmacs;
	       addEndFunction(() -> if texmacsmode then (
			 if restarting 
			 then stderr << "Macaulay2 restarting..." << endl << endl << flush
			 else (
			      stderr << "Macaulay2 exiting" << flush;
			      << TeXmacsEnd << endl << flush)));
	       )
	  ),
     "--version" => arg -> ( << version#"VERSION" << newline; exit 0; ),
     "--webapp" => arg -> ( printWidth=0; topLevelMode = global WebApp; )
     };

argno := 1

valueNotify := arg -> (
     if notify then printerr("evaluating command line argument ", toString argno, ": ", format arg);
     value' arg)

action2 := hashTable {
     "--srcdir" => arg -> if phase == 2 then srcdirs = append(srcdirs, arg),
     "-E" => arg -> if phase == 3 then valueNotify arg,
     "-e" => arg -> if phase == 4 then valueNotify arg,
     "--print-width" => arg -> if phase == 3 then printWidth = value' arg,
     "--debug" => arg -> debugWarningHashcode = value' arg,
     "--prefix" => arg -> if phase == 1 or phase == 3 then (
	  prefixPath = { prefixDirectory = concatPath(arg, "") }),
     "--check" => arg -> if arg != "1" and arg != "2" and arg != "3" then
	       error ("--check ",arg,": expected 1, 2, or 3")
	  else if phase == 1 and arg == "1" then
	       runBasicTests()
	  -- FIXME: doesn't work with --no-core
	  else if phase == 4 and arg == "2" then (
	       argumentMode = defaultMode - SetCaptureErr - SetUlimit -
		    if noinitfile then 0 else ArgQ;
	       if runString(///check("Core", Verbose => true)///, Core, false)
		    then exit 0 else exit 1
	  ) else if phase == 4 and arg == "3" then exit checkAllPackages()
     }

scriptCommandLine = {}

processCommandLineOptions := phase0 -> (			    -- 3 passes
     phase = phase0;
     argno = 1;
     if commandLine#?1 and commandLine#1 == "--script" then (
	  if phase <= 2 then (
	       clearEcho stdio;
	       debuggingMode = false;
	       stopIfError = noinitfile = nobanner = true;
	       )
	  else if phase == 4 then (
	       if not commandLine#?2 then error "script file name missing";
	       arg := commandLine#2;
 	       -- "load" uses "./" to mean relative to the current file's directory, but right now, the current file is startup.m2.in,
	       -- which may not even be on the user's machine.  So we convert it to an absolute path
	       if match("^(\\./|\\../)",arg) then arg = toAbsolutePath arg;
	       scriptCommandLine = drop(commandLine,2);
	       if instance(value' global load, Function) then load arg else simpleLoad arg;
	       exit 0))
     else (
	  if notify then printerr("phase ", toString phase);
	  while argno < #commandLine do (
	       arg = commandLine#argno;
	       if action#?arg then action#arg arg
	       else if action2#?arg then (
		    argno = argno + 1;
		    if argno < #commandLine
		    then action2#arg commandLine#argno
		    else error("command line option ", arg, " missing argument")
		    )
	       else if arg#0 == "-" then (
		    stderr << "error: unrecognized command line option: " << arg << endl;
		    usage();
		    exit 1;
		    )
	       else if phase == 4 then (
		    if not isStablePath arg and instance(load, Function) then load arg
		    else simpleLoad arg;
		    );
	       argno = argno+1;
	       );
	  ))

if firstTime then processCommandLineOptions 1

exe = minimizeFilename (
     -*
     -- this can be a reliable way to get the executable in linux
     -- but we don't want to use it because we don't want to chase symbolic links and it does that for us
     processExe := "/proc/self/exe";
     if fileExists processExe and readlink processExe =!= null then readlink processExe
     else 
     *-
     if isAbsolutePath commandLine#0 then commandLine#0 else
     if isStablePath commandLine#0 then concatenate(currentDirectory()|commandLine#0)
     else pathsearch commandLine#0)
if not isAbsolutePath exe then exe = currentDirectory() | exe ;
exe = concatenate(realpath dir exe, base exe)
if notify then printerr("executable = ", exe)

if firstTime and not nobanner then (
     if topLevelMode === TeXmacs then stderr << TeXmacsBegin << "verbatim:";
     stderr << (if fullCopyright then copyright else first separate copyright) << newline << flush;
     if topLevelMode === TeXmacs then stderr << TeXmacsEnd << flush)

scan(commandLine, arg -> if arg === "-q" then noinitfile = true)
homeDirectory = getenv "HOME" | "/"

initcurrentlayout()

if firstTime and fileExists(prefixDirectory | currentLayout#"program licenses") then
copyright = copyright | newline | "For licenses see " | prefixDirectory | currentLayout#"program licenses"

path = {}
pkgpath = {}
corepath = {}
userpath = {
    applicationDirectory() | "code/",
    applicationDirectory() | "local/" | Layout#1#"packages"
    }

if prefixDirectory =!= null then (
    if topBuilddir =!= null then (
	if topSrcdir =!= null then (
	    pkgpath  = append(pkgpath,  topSrcdir | "Macaulay2/packages/");
	    corepath = append(corepath, topSrcdir | "Macaulay2/m2/"));
	corepath = append(corepath, topBuilddir | "Macaulay2/m2/"));
    pkgpath  = append(pkgpath,  prefixDirectory | currentLayout#"packages");
    corepath = append(corepath, prefixDirectory | replace("PKG", "Core", currentLayout#"package")))

if firstTime then normalPrompts()

printWidth = fileWidth stdio

processCommandLineOptions 2				    -- just for path to core files and packages

-- add source directories to the path, in the appropriate order
corepath = join(apply(srcdirs, dir -> concatPath(dir, "Macaulay2/m2/")),      corepath)
pkgpath =  join(apply(srcdirs, dir -> concatPath(dir, "Macaulay2/packages/")), pkgpath)
path = select(join(corepath, pkgpath, path), isDirectory)

-- a local way to use private global symbols after endPackage is called on Core
core := nm -> value if nocore then getGlobalSymbol nm else Core#"private dictionary"#nm

describePath = path -> (
    if #path == 0 then printerr "file search path empty" else (
	printerr "file search path directories:";
	for d in path do printerr("   ", d)));

loadCore = path -> (
    if notify then describePath path;
    if not any(path, prefix -> trySimpleLoad("Core.m2", minimizeFilename(prefix | "Core.m2"))) then (
	if not notify then describePath path;
	error "Core.m2 was not found on path"));

-- Here the Core is loaded
(loadDepth = 1; if firstTime and not nocore then loadCore path; loadDepth = 0)

(loadDepth = 3; processCommandLineOptions 3; loadDepth = 0)
(core "runStartFunctions")()

if class Core =!= Symbol and not core "noinitfile" then (
    -- the location of init.m2 is documented in the node "initialization file"
    trySimpleLoad("init.m2", applicationDirectory() | "init.m2"));

loadDepth = 0;
( loadDepth = 3;
     processCommandLineOptions 4;
     n := 0;
     errorDepth = loadDepth = 3;
     n = interpreter();
     if class n === ZZ and 0 <= n and n < 128 then exit n;
     if n === null then exit 0;
     debuggingMode = false;
     stopIfError = true;
     stderr << "error: can't interpret return value as an exit code" << endl;
     exit 1;
     )
-- Local Variables:
-- compile-command: "make -C $M2BUILDDIR/Macaulay2/d startup.m2 all && make -C $M2BUILDDIR/Macaulay2/bin all "
-- End:
