/***********************************************************************
 *
 *	Main Module
 *
 ***********************************************************************/

/***********************************************************************
 *
 * Copyright (C) 1990, 1991 Free Software Foundation, Inc.
 * Written by Steve Byrne.
 *
 * This file is part of GNU Smalltalk.
 *
 * GNU Smalltalk 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 1, or (at your option) any later 
 * version.
 * 
 * GNU Smalltalk 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
 * GNU Smalltalk; see the file COPYING.  If not, write to the Free Software
 * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  
 *
 ***********************************************************************/


/*
 *    Change Log
 * ============================================================================
 * Author      Date       Change 
 * sbb	     12 Sep 91	  Fxied -I argument parsing code to properly gobble up
 *			  the file name.
 *
 * sbyrne    22 May 90	  Improved on Doug's mapping with macro to improve
 *			  readability.
 *
 * sbyrne    22 May 90	  Short name stuff added, thanks to Doug McCallum.
 *
 * sbyrne    25 Mar 90	  ProcessorScheduler is too long of a name for the
 *			  Atari; there are uniqueness problems.  Shortened to
 *			  ProcScheduler.   Also, fixed quietExecution; wasn't
 *			  set when reading from the terminal; should have been
 *			  set to false (since the loading of the quiet things
 *			  is over).
 *
 * sbyrne    15 Oct 89	  Added support for creating an "installed" version of
 *			  Smalltalk.  There is now an include file that the
 *			  installer can customize for his site that provides
 *			  default locations to be checked for the kernel .st
 *			  files and the binary image file, but these can be
 *			  overidden in two ways: a) by a file of the same
 *			  name in the user's current directory, or b)
 *			  environment variables SMALLTALK_KERNEL and
 *			  SMALLTALK_IMAGE.
 *
 * sbyrne     4 Jul 89	  Added support for user init files (in ~/.stinit),
 *			  which are invoked on every startup.  Also, added
 *			  support for initBlocks, which are blocks that are
 *			  stored in the system and invoked on each startup
 *			  (these could be used, for example, as an interim
 *			  measure for declaring C callouts until the callout
 *			  descriptor is converted to a Smalltalk object).
 *
 * sbyrne    10 Mar 89	  Added support for automatically loading image file if
 *			  it's newer than and of the system source files.
 *
 * sbyrne    27 Dec 88	  Created.
 *
 */


#include "mst.h"
#include "msttab.h"
#include "mstintrp.h"
#include "mstcomp.h"
#include "mstsave.h"
#include "mstsym.h"		/* for symbol table profiling */
#include "mstoop.h"		/* indirectly defines oopAt for sym tab prof */
#include "mstpaths.h"
#include "mstmain.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#if defined(USG)
#include <unistd.h>
#endif

#ifndef MAXPATHLEN
#define MAXPATHLEN		1024 /* max length of a file and path */
#endif

#define SHORTNAMES 1  /* SPECIAL - ACTIVATE FOR DOS */

#ifdef SHORTNAMES
#define MAP_FILE(long, short)	short
#else
#define MAP_FILE(long, short)	long
#endif


#ifndef atarist
#define INIT_FILE_NAME		".stinit"
#else
#define INIT_FILE_NAME		".tinit"
#endif


extern int		yydebug, lexDebug, numFreeOOPs;
extern YYSTYPE		yylval;
extern char		*getenv();

#ifdef symbol_table_profiling
extern int		adds, reused, reprobes, hitsOn[];
#endif /* symbol_table_profiling */

/* When true, this flag suppresses the printing of execution-related
 * messages, such as the number of byte codes executed by the
 * last expression, etc.
 */
Boolean			quietExecution;

char			*kernelFileDefaultPath, *imageFileDefaultPath;

/* This string contains the printed representation of the Smalltalk version
 * number
 */
char			versionString[50];

static Boolean 		processFile(), okToLoadBinary();
static void		loadStandardFiles(), loadUserInitFile(), initPaths(),
			findKernelFile(), parseArgs(), makeVersionString();
static unsigned long	getFileModifyTime();


/* Set by cmd line flag.  If true, Smalltalk is more verbose about what it's
 * doing.
 */
static Boolean		verbose = false;

/* If true, even both kernel and user method definitions are shown as
 * they are compiled.
 */
static Boolean		traceKernelDeclarations;

/* If true, execution tracing is performed when loading kernel method
 * definitions
 */
static Boolean		traceKernelExecution;

/* If true, skip date checking of kernel files vs. binary image; pretend
 * that binary image does not exist
 */
static Boolean		ignoreImage;


/* If non-nil, this is the name of the binary image to load, and overrides
 * the checking of the dates of the kernel source files against the image
 * file date.
 */
static char		*binaryImageName = nil;


/* Set by command line flag.  When this is true, the interpreter 
 * does not print out things like "execution begins" or information
 * about the number of byte codes executed.
 */
static Boolean		runQuietly = false;


/* The complete list of "kernel" class and method definitions.  Each of
 * these files is loaded, in the order given below.  Their last modification
 * dates are compared against that of the image file; if any are newer,
 * the image file is ignored, these files are loaded, and a new image file
 * is created.
 */
static char		*standardFiles[] = {
  "builtins.st", 
  "Object.st",
  "Message.st",
  "Magnitud.st",
  "Charactr.st",
  "Date.st",
  "Time.st",
  "Number.st",
  "Float.st",
  "Integer.st",
  "LookupKy.st",
  "Assoc.st",
  "Link.st",
  "Process.st",
  "Collectn.st",
  MAP_FILE("SequenceableCollection.st", "SeqColl.st"),
  "LnkdList.st",
  "Semaphor.st",
  MAP_FILE("ArrayedCollection.st", "ArryColl.st"),
  "Array.st",
  "String.st",
  "Symbol.st",
  "ByteArry.st",
  MAP_FILE("CompiledMethod.st", "CmpldMtd.st"),
  "Interval.st",
  MAP_FILE("OrderedCollection.st", "OrdrdCol.st"),
  MAP_FILE("SortedCollection.st", "SortdCol.st"),
  "Bag.st",
  MAP_FILE("MappedCollection.st", "MapdColl.st"),
  "Set.st",
  "Dictiony.st",
  MAP_FILE("IdentityDictionary.st", "IdentDic.st"),
  MAP_FILE("SystemDictionary.st", "SysDict.st"),
  "Stream.st",
  MAP_FILE("PositionableStream.st", "Postream.st"),
  "ReadStrm.st",
  "WritStrm.st",
  MAP_FILE("ReadWriteStream.st", "RWStream.st"),
  "FileStrm.st",
  "TokenStr.st",
  "Random.st",
  MAP_FILE("UndefinedObject.st", "UndefObj.st"),
  "Boolean.st",
  "False.st",
  "True.st",
#if !defined(USG) && !defined(atarist)
  "ProcSch.st",
#else
  "ProcSched.st",
#endif
  "Delay.st",
  "SharedQ.st",
  "Behavior.st",
  MAP_FILE("ClassDescription.st", "ClasDesc.st"),
  "Class.st",
  "Metaclas.st",
  MAP_FILE("MethodContext.st", "MthdCntx.st"),
  MAP_FILE("BlockContext.st", "BlkContx.st"),
  "Memory.st",
  "WordMem.st",
  "ByteMem.st",
  "MthdInfo.st",
  "FileSeg.st",
  "SymLink.st",
  "initialz.st",
  "CFuncs.st",
  "Autoload.st",
  nil
};

#ifdef atarist
long _stksize = -1L;		/* what does this do? */
#endif
main(argc, argv)
int	argc;
char 	**argv;
{
  Boolean	loadBinary, traceUserDeclarations, traceUserExecution;
  int		filesProcessed;
  char		*imageName;

#ifdef USE_MONCONTROL
  moncontrol(0);		/* don't monitor the initial stuff */
#endif /* USE_MONCONTROL */

  yydebug = 0;
  traceKernelDeclarations = declareTracing = false;
  traceKernelExecution = executionTracing = false;
  regressionTesting = false;
  ignoreImage = false;
  verbose = false;

  initSignals();
  initMem();
  initCFuncs();
  initPaths();
  makeVersionString();
#if defined(USG)
  tzset();
#endif

  parseArgs(argc, argv);

  imageName = defaultImageName;

  if (binaryImageName) {
    loadBinary = true;
    imageName = binaryImageName;
  } else if (ignoreImage) {
    loadBinary = false;
  } else {
    loadBinary = okToLoadBinary(defaultImageName);
  }

  if (loadBinary && loadFromFile(imageName)) {
    initDefaultCompilationEnvironment();
    initSTDIOObjects();
    initInterpreter();
    gcOn();
  } else {
    initOOPTable();
    initDictionary();
    initSymbols();
    initInterpreter();

    installInitialMethods();

    traceUserDeclarations = declareTracing;
    traceUserExecution = executionTracing;
    if (!traceKernelDeclarations) {
      declareTracing = false;
    }
    if (!traceKernelExecution) {
      executionTracing = false;
    }

    gcOn();
    loadStandardFiles();

    declareTracing = traceUserDeclarations;
    executionTracing = traceUserExecution;

/* ***    gcOn(); *** */
    saveToFile(defaultImageName);
  }

#ifdef preserved /* Sun Mar 26 23:36:15 1989 */
/**/#ifdef profiling
/**/  monitor(0);
/**/
/**/  printf("results %d/%d\n", hits, misses);
/**/  { int i;
/**/    for (i = 0; i < 10240; i++) {
/**/      if (hitsOn[i]) {
/**/	printf("hitsOn[%d] = %4d\t%d\t%4x\t%8o", i, hitsOn[i],
/**/	       i, i, i);
/**/	printObject(oopAt(i));
/**/	printf("\n");
/**/      }
/**/    }
/**/  }
/**/#endif /* profiling */
#endif /* preserved Sun Mar 26 23:36:15 1989 */

#ifdef USE_MONCONTROL
  moncontrol(1);
#endif /* USE_MONCONTROL */

  invokeInitBlocks();
  loadUserInitFile();

#ifdef symbol_table_profiling
  printf("%d adds, %d reused %d reprobes\n", adds, reused, reprobes);

  { int i;
    for (i = 0; i < OOP_TABLE_SIZE; i++) {
      if (hitsOn[i]) {
	printf("hitsOn[%d] = %4d\t%d\t%4x\t%8o", i, hitsOn[i],
	       i, i, i);
	printObject(oopAt(i));
	printf("\n");
      }
    }
  }
#endif /* symbol_table_profiling */

  if (regressionTesting) {
    printf("Smalltalk Ready\n\n");
  } else {
    printf("Smalltalk %s Ready\n\n", versionString);
  }

  quietExecution = runQuietly || emacsProcess;
#ifndef LEXDEBUG
  for (filesProcessed = 0; *++argv; ) {
    if (argv[0][0] != '-') {
      processFile(argv[0], quietExecution);
      filesProcessed++;
    } else if (argv[0][1] == '-' || argv[0][1] == '\0') {
      /* either - by itself or -- indicates standard input */
      initLexer(false);
#ifdef USE_READLINE
      pushReadlineString();
#else
      pushUNIXFile(stdin, "stdin");
#endif /* USE_READLINE */
      yyparse();
      popStream(true);
    }
  }

  if (filesProcessed == 0) {	/* didn't do any from cmd line, so read stdin*/
    initLexer(false);
#ifdef USE_READLINE
    pushReadlineString();
#else
    pushUNIXFile(stdin, "stdin");
#endif /* USE_READLINE */
    yyparse();
  }

#ifdef USE_MONCONTROL
   moncontrol(0);
#endif /* USE_MONCONTROL */

#else /* debugging the lexer */

#ifdef USE_READLINE
    pushReadlineString();
#else
    pushUNIXFile(stdin, "stdin");
#endif /* USE_READLINE */

/********* THIS IS NOT UP TO DATE ... BEWARE ************/

  lexDebug = 1;
  while(!feof(infile)) {
    switch (yylex()) {
    case DOT:
      printf("DOT\n");
      break;
    case BANG:
      printf("BANG\n");
      break;
    case COLON:
      printf("COLON\n");
      break;
    case VERTICAL_BAR:
      printf("VERTICAL_BAR\n");
      break;
    case UPARROW:
      printf("UPARROW\n");
      break;
    case ASSIGN:
      printf("ASSIGN\n");
      break;
    case SHARP:
      printf("SHARP\n");
      break;
    case SEMICOLON:
      printf("SEMICOLON\n");
      break;
    case OPEN_PAREN:
      printf("OPEN_PAREN\n");
      break;
    case CLOSE_PAREN:
      printf("CLOSE_PAREN\n");
      break;
    case OPEN_BRACKET:
      printf("OPEN_BRACKET\n");
      break;
    case CLOSE_BRACKET:
      printf("CLOSE_BRACKET\n");
      break;
    case IDENTIFIER:
      printf("IDENTIFIER: %s\n", yylval.sval);
      break;
    case INTEGER_LITERAL:
      printf("INTEGER_LITERAL: %d\n", yylval.ival);
      break;
    case FLOATING_LITERAL:
      printf("FLOATING_LITERAL: %g\n", yylval.fval);
      break;
    case CHAR_LITERAL:
      printf("CHAR_LITERAL: %c\n", yylval.cval);
      break;
    case STRING_LITERAL:
      printf("STRING_LITERAL: %s\n", yylval.sval);
      break;
    case BINOP:
      printf("BINOP: %d\n", yylval.op);
      break;
    }
  }
#endif /* LEXDEBUG */
}

/*
 *	static void initPaths()
 *
 * Description
 *
 *	Sets up the paths for the kernel source directory and for where the
 *	saved Smalltalk binary image lives.  Uses environment variables
 *	SMALLTALK_KERNEL and SMALLTALK_IMAGE if they are set, otherwise uses
 *	the paths assigned in mstpaths.h.
 *
 */
static void initPaths()
{
  if ((kernelFileDefaultPath = (char *)getenv("SMALLTALK_KERNEL")) == nil) {
    kernelFileDefaultPath = KERNEL_PATH;
  }

  if ((imageFileDefaultPath = (char *)getenv("SMALLTALK_IMAGE")) == nil) {
    imageFileDefaultPath = IMAGE_PATH;
  }
}

static Boolean okToLoadBinary(imageFileName)
char	*imageFileName;
{
  unsigned long	imageFileTime;
  char		**fileNames, fullFileName[MAXPATHLEN],
  		fullImageName[MAXPATHLEN];

  findImageFile(imageFileName, fullImageName);
  imageFileTime = getFileModifyTime(fullImageName);

  if ((long)imageFileTime == -1) { /* not found */
    return (false);
  }

  for (fileNames = standardFiles; *fileNames; fileNames++) {
    findKernelFile(*fileNames, fullFileName);
    if (imageFileTime < getFileModifyTime(fullFileName)) {
      return (false);
    }
  }

  return (true);
}

static unsigned long getFileModifyTime(fileName)
char	*fileName;
{
  struct stat	st;

  if (stat(fileName, &st) < 0) {
    return ((unsigned long) -1);
  } else {
    return (st.st_mtime);
  }
}

/*
 *	static void loadStandardFiles()
 *
 * Description
 *
 *	Loads the kernel Smalltalk files.  It uses a vector of file names, and
 *	loads each file individually.  To provide for greater flexibility, if a
 *	one of the files exists in the current directory, that is used in
 *	preference to one in the default location.  The default location can be
 *	overridden at runtime by setting the SMALLTALK_KERNEL environment
 *	variable. 
 *
 */
static void loadStandardFiles()
{
  char		**fileNames, fullFileName[MAXPATHLEN];
  
  for (fileNames = standardFiles; *fileNames; fileNames++) {
    findKernelFile(*fileNames, fullFileName);
    if (!processFile(fullFileName, true)) {
      fprintf(stderr,
	      "Can't find system file '%s', proceeding without it.\n",
	      fullFileName);
    }
  }
}


/*
 *	static void findKernelFile(fileName, fullFileName)
 *
 * Description
 *
 *	Attempts to find a viable kernel Smalltalk file (.st file).  First
 *	tries the current directory to allow for overriding installed kernel
 *	files.  If that isn't found, the full path name of the installed kernel
 *	file is stored in fullFileName.  Note that the directory part of the
 *	kernel file name in this second case can be overridden by defining the
 *	SMALLTALK_KERNEL environment variable to be the directory that should
 *	serve as the kernel directory instead of the installed one.
 *
 * Inputs
 *
 *	fileName: 
 *		A simple file name, sans directory.
 *	fullFileName: 
 *		The file name to use for the particular kernel file is returned
 *		in this variable (which must be a string large enough for any
 *		file name).  If there is a file in the current directory with
 *		name "fileName", that is returned; otherwise the kernel path is
 *		prepended to fileName (separated by a slash, of course) and
 *		that is stored in the string pointed to by "fullFileName".
 *
 */
static void findKernelFile(fileName, fullFileName)
char *fileName, *fullFileName;
{
  if (access(fileName, 4) == 0) {
    strcpy(fullFileName, fileName);
  } else {
    sprintf(fullFileName, "%s/%s", kernelFileDefaultPath, fileName);
  }
}


static void loadUserInitFile()
{
  char		fileName[MAXPATHLEN], *home;

  if ((home = (char *)getenv("HOME")) != nil) {
    sprintf(fileName, "%s/%s", home, INIT_FILE_NAME);
    processFile(fileName, quietExecution);
  }
}

static Boolean processFile(fileName, quiet)
char	*fileName;
Boolean	quiet;
{
  FILE		*file;

  file = fopen(fileName, "r");
  if (file == NULL) {
    return (false);
  }

  if (verbose) {
    printf("Processing %s\n", fileName);
  }

  quietExecution = quiet;
  initLexer(false);
  pushUNIXFile(file, fileName);
  yyparse();
  popStream(true);

  return (true);
}

/*
 *	static void findImageFile(fileName, fullFileName)
 *
 * Description
 *
 *	Attempts to find a viable Smalltalk image file.  First
 *	tries the current directory to allow for overriding installed image
 *	files.  If that isn't found, the full path name of the installed image
 *	file is stored in fullFileName.  Note that the directory part of the
 *	image file name in this second case can be overridden by defining the
 *	SMALLTALK_IMAGE environment variable to be the directory that should
 *	serve as the image directory instead of the installed one.
 *
 * Inputs
 *
 *	fileName: 
 *		A simple file name, sans directory.
 *	fullFileName: 
 *		The file name to use for the particular image file is returned
 *		in this variable (which must be a string large enough for any
 *		file name).  If there is a file in the current directory with
 *		name "fileName", that is returned; otherwise the kernel path is
 *		prepended to fileName (separated by a slash, of course) and
 *		that is stored in the string pointed to by "fullFileName".
 *
 */
void findImageFile(fileName, fullFileName)
char *fileName, *fullFileName;
{
  if (access(fileName, 4) == 0) {
    strcpy(fullFileName, fileName);
  } else {
    sprintf(fullFileName, "%s/%s", imageFileDefaultPath, fileName);
  }
}


/*
 *	static void parseArgs(argc, argv)
 *
 * Description
 *
 *	This routine scans the command line arguments, accumulating information
 *	and setting flags.  This will probably be replaced by getopt in a
 *	future version of Smalltalk.
 *
 * Inputs
 *
 *	argc  : The number of arguments present
 *	argv  : Vector of strings that are the arguments.
 *
 */
static void parseArgs(argc, argv)
int	argc;
char	**argv;
{
  char		**hp, **av;
  char		*flags;

  static char	*helpText[] = {
"GNU Smalltalk usage:",
"",
"    mst [ flag ... ] [ file ...]",
"",
"Flags can appear either as -xyz or as -x -y -z.  The currently",
"defined set of flags is:",
"  -c\tDump core on fatal signal",
"  -d\tTrace compilation of user specified files",
"  -D\tTrace compilation of kernel and user files",
"  -e\tTrace execution of files specified on command line",
"  -E\tTrace execution of kernel and user files",
"  -H -h -?  Print this message and exit",
"  -i\tIgnore the image file; rebuild it from scratch",
"  -I file\tUse 'file' as the image file, instead of 'mst.im'",
"  -p\tRun Smalltalk as a 'process', i.e. from within GNU Emacs",
"  -q\tRun Smalltalk without printing execution information",
"  -r\tRun in regression test mode (printed messages are made constant)",
"  -v\tPrint the Smalltalk version number",
"  -V\tEnable verbose mode",
"  -y\tTurn on debugging in the parser",
"  - --\tRead input from standard input explicitly",
"",
"Files are loaded one after the other.  After the last one is loaded,",
"Smalltalk will exit.  If no files are specified, Smalltalk reads from",
"the terminal, with prompts.",
NULL
};


  for ( ; *++argv; ) {
    if (argv[0][0] == '-') {
      for (flags = &argv[0][1]; *flags; flags++) {
	switch (*flags) {
	case 'c':
	  makeCoreFile = true;
	  break;
	case 'D':
	  traceKernelDeclarations = true; /* fall thru */
	case 'd':
	  declareTracing = true;
	  break;
	case 'E':
	  traceKernelExecution = true; /* fall thru */
	case 'e':
	  executionTracing = true;
	  break;
	case 'h': case 'H': case '?':
	default:
	  for (hp = helpText; *hp != NULL; hp++) {
	    printf("%s\n", *hp);
	  }
          exit(0);
	case 'I':
	  binaryImageName = argv[1];
	  for (av = argv+1; *av; av++) {	/* remove this argument */
	    *av = *(av+1);
	  }
	  break;
	case 'i':
	  ignoreImage = true;
	  break;
	case 'p':
	  emacsProcess = true;
	  break;
	case 'q':
	  runQuietly = true;
	  break;
	case 'r':
	  regressionTesting = true;
	  break;
	case 'V':
	  verbose = true;
	  break;
	case 'v':
	  printf("GNU Smalltalk version %s\n", versionString);
	  printf("Copyright (C) 1990, 1991 Free Software Foundation, Inc.\n");
	  printf("Written by Steve Byrne.\n");
	  printf("Using kernel path: %s\n", kernelFileDefaultPath);
	  printf("Using image path : %s\n", imageFileDefaultPath);
	  break;
	case 'y':
	  yydebug = 1;
	  break;
	case '-':		/* this means standard input, so it's ok */
	  break;
	}
      }
    }
  }

  if (regressionTesting) {
    traceKernelDeclarations = declareTracing = false;
    traceKernelExecution = executionTracing = false;
    verbose = false;
  }
}

static void makeVersionString()
{
  if (sysVersEdit != 0) {
    sprintf(versionString, "%d.%d.%d", sysVersMajor, sysVersMinor,
	    sysVersEdit);
  } else {
    sprintf(versionString, "%d.%d", sysVersMajor, sysVersMinor);
  }
}
