text: /*
   * %CopyrightBegin%
   *
   * Copyright Ericsson AB 1997-2011. All Rights Reserved.
   *
   * The contents of this file are subject to the Erlang Public License,
   * Version 1.1, (the "License"); you may not use this file except in
   * compliance with the License. You should have received a copy of the
   * Erlang Public License along with this software. If not, it can be
   * retrieved online at http://www.erlang.org/.
   *
   * Software distributed under the License is distributed on an "AS IS"
   * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   * the License for the specific language governing rights and limitations
   * under the License.
   *
   * %CopyrightEnd%
   */
  /*
   * Purpose: Common compiler front-end.
   */
  #ifdef HAVE_CONFIG_H
  #  include "config.h"
  #endif
  
  #include "sys.h"
  #ifdef __WIN32__
  #include <winbase.h>
  /* FIXE ME config_win32.h? */
  #define HAVE_STRERROR 1
  #define snprintf _snprintf
  #endif
  
  #include <ctype.h>
  
  #define NO 0
  #define YES 1
  
  #define ASIZE(a) (sizeof(a)/sizeof(a[0]))
  
  static int debug = 0;		/* Bit flags for debug printouts. */
  
  static char** eargv_base;	/* Base of vector. */
  static char** eargv;		/* First argument for erl. */
  
  static int eargc;		/* Number of arguments in eargv. */
  
  #ifdef __WIN32__
  #  define QUOTE(s) possibly_quote(s)
  #  define IS_DIRSEP(c) ((c) == '/' || (c) == '\\')
  #  define ERL_NAME "erl.exe"
  #else
  #  define QUOTE(s) s
  #  define IS_DIRSEP(c) ((c) == '/')
  #  define ERL_NAME "erl"
  #endif
  
  #define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(s)
  #define PUSH(s) eargv[eargc++] = QUOTE(s)
  #define PUSH2(s, t) PUSH(s); PUSH(t)
  #define PUSH3(s, t, u) PUSH2(s, t); PUSH(u)
  
  static char* output_type = NULL; /* Type of output file. */
  #ifdef __WIN32__
  static int pause_after_execution = 0;
  #endif
  
  /*
   * Local functions.
   */
  
  static char* process_opt(int* pArgc, char*** pArgv, int offset);
  static void error(char* format, ...);
  static void usage(void);
  static char* emalloc(size_t size);
  static char* strsave(char* string);
  static void push_words(char* src);
  static int run_erlang(char* name, char** argv);
  static char* get_default_emulator(char* progname);
  #ifdef __WIN32__
  static char* possibly_quote(char* arg);
  #endif
  
  /*
   * Supply a strerror() function if libc doesn't.
   */
  #ifndef HAVE_STRERROR
  
  extern int sys_nerr;
  
  #ifndef SYS_ERRLIST_DECLARED
  extern const char * const sys_errlist[];
  #endif /* !SYS_ERRLIST_DECLARED */
  
  char *strerror(int errnum)
  {
    static char *emsg[1024];
  
    if (errnum != 0) {
      if (errnum > 0 && errnum < sys_nerr) 
        sprintf((char *) &emsg[0], "(%s)", sys_errlist[errnum]);
      else 
        sprintf((char *) &emsg[0], "errnum = %d ", errnum);
    }
    else {
      emsg[0] = '\0';
    }
    return (char *) &emsg[0];
  }
  #endif /* !HAVE_STRERROR */
  
  static char *
  get_env(char *key)
  {
  #ifdef __WIN32__
      DWORD size = 32;
      char *value = NULL;
      while (1) {
  	DWORD nsz;
  	if (value)
  	    free(value);
  	value = emalloc(size);
  	SetLastError(0);
  	nsz = GetEnvironmentVariable((LPCTSTR) key, (LPTSTR) value, size);
  	if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
  	    free(value);
  	    return NULL;
  	}
  	if (nsz <= size)
  	    return value;
  	size = nsz;
      }
  #else
      return getenv(key);
  #endif
  }
  
  static void
  free_env_val(char *value)
  {
  #ifdef __WIN32__
      if (value)
  	free(value);
  #endif
  }
  
  
  int
  main(int argc, char** argv)
  {
      char cwd[MAXPATHLEN];	/* Current working directory. */
      int eargv_size;
      int eargc_base;		/* How many arguments in the base of eargv. */
      char* emulator;
      char *env;
  
      env = get_env("ERLC_EMULATOR");
      emulator = env ? env : get_default_emulator(argv[0]);
  
      if (strlen(emulator) >= MAXPATHLEN)
          error("Value of environment variable ERLC_EMULATOR is too large");
  
      /*
       * Allocate the argv vector to be used for arguments to Erlang.
       * Arrange for starting to pushing information in the middle of
       * the array, to allow easy adding of emulator options (like -pa)
       * before '-s erlcompile compile_cmdline...'.
       *
       * Oh, by the way, we will push the compiler command in the
       * base of the eargv vector, and move it up later.
       */
  
      eargv_size = argc*6+100;
      eargv_base = (char **) emalloc(eargv_size*sizeof(char*));
      eargv = eargv_base;
      eargc = 0;
      push_words(emulator);
      eargc_base = eargc;
      eargv = eargv + eargv_size/2;
      eargc = 0;
  
      free_env_val(env);
  
      /*
       * Push initial arguments.
       */
  
      PUSH("-noinput");
      PUSH2("-mode", "minimal");
      PUSH2("-boot", "start_clean");
      PUSH3("-s", "erl_compile", "compile_cmdline");
  
      /*
       * Push standard arguments to Erlang.
       *
       * The @cwd argument was once needed, but from on R13B02 is optional.
       * For maximum compatibility between erlc and erl of different versions,
       * still provide the @cwd argument, unless it is too long to be
       * represented as an atom.
       */
      if (getcwd(cwd, sizeof(cwd)) == NULL)
  	error("Failed to get current working directory: %s", strerror(errno));
  #ifdef __WIN32__
      (void) GetShortPathName(cwd, cwd, sizeof(cwd));
  #endif    
      if (strlen(cwd) < 256) {
  	PUSH2("@cwd", cwd);
      }
  
      /*
       * Parse all command line switches.
       */
  
      while (argc > 1 && (argv[1][0] == '-' || argv[1][0] == '+')) {
  
  	/*
  	 * Options starting with '+' are passed on to Erlang.
  	 */
  
  	if (argv[1][0] == '+') {
  	    PUSH2("@option", argv[1]+1);
  	} else {
  	    /*
  	     * Interpret options starting with '-'.
  	     */
  	    
  	    switch (argv[1][1]) {
  	    case 'b':
  		output_type = process_opt(&argc, &argv, 0);
  		PUSH2("@output_type", output_type);
  		break;
  	    case 'c':		/* Allowed for compatibility with 'erl'. */
  		if (strcmp(argv[1], "-compile") != 0)
  		    goto error;
  		break;
  	    case 'd':
  		debug = 1;
  		break;
  	    case 'D':
  		{
  		    char* def = process_opt(&argc, &argv, 0);
  		    char* equals;
  		    
  		    def = strsave(def);	/* Do not clobber original. */
  		    if ((equals = strchr(def, '=')) == NULL) {
  			PUSH2("@d", def);
  		    } else {
  			*equals = '\0';
  			equals++;
  			PUSH3("@dv", def, equals);
  		    }
  		}
  		break;
  	    case 'h':
  		if (strcmp(argv[1], "-hybrid") == 0) {
  		    UNSHIFT(argv[1]);
  		} else {
  		    usage();
  		}
  		break;
  	    case 'I':
  		PUSH2("@i", process_opt(&argc, &argv, 0));
  		break;
  	    case 'M':
  		{
  		    char *buf, *key, *val;
  		    size_t buf_len;
  
  		    if (argv[1][2] == '\0') { /* -M */
  			/* Push the following options:
  			 *   o  'makedep'
  			 *   o  {makedep_output, standard_io}
  			 */
  			buf = strsave("makedep");
  			PUSH2("@option", buf);
  
  			key = "makedep_output";
  			val = "standard_io";
  			buf_len = 1 + strlen(key) + 1 + strlen(val) + 1 + 1;
  			buf = emalloc(buf_len);
  			snprintf(buf, buf_len, "{%s,%s}", key, val);
  			PUSH2("@option", buf);
  		    } else if (argv[1][3] == '\0') {
  			switch(argv[1][2]) {
  			case 'D': /* -MD */
  			    /* Push the following options:
  			     *   o  'makedep'
  			     */
  			    buf = strsave("makedep");
  			    PUSH2("@option", buf);
  			    break;
  			case 'F': /* -MF <file> */
  			    /* Push the following options:
  			     *   o  'makedep'
  			     *   o  {makedep_output, <file>}
  			     */
  			    buf = strsave("makedep");
  			    PUSH2("@option", buf);
  
  			    key = "makedep_output";
  			    val = process_opt(&argc, &argv, 1);
  			    buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1;
  			    buf = emalloc(buf_len);
  			    snprintf(buf, buf_len, "{%s,\"%s\"}", key, val);
  			    PUSH2("@option", buf);
  			    break;
  			case 'T': /* -MT <target> */
  			    /* Push the following options:
  			     *   o  {makedep_target, <target>}
  			     */
  			    key = "makedep_target";
  			    val = process_opt(&argc, &argv, 1);
  			    buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1;
  			    buf = emalloc(buf_len);
  			    snprintf(buf, buf_len, "{%s,\"%s\"}", key, val);
  			    PUSH2("@option", buf);
  			    break;
  			case 'Q': /* -MQ <target> */
  			    /* Push the following options:
  			     *   o  {makedep_target, <target>}
  			     *   o  makedep_quote_target
  			     */
  			    key = "makedep_target";
  			    val = process_opt(&argc, &argv, 1);
  			    buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1;
  			    buf = emalloc(buf_len);
  			    snprintf(buf, buf_len, "{%s,\"%s\"}", key, val);
  			    PUSH2("@option", buf);
  
  			    buf = strsave("makedep_quote_target");
  			    PUSH2("@option", buf);
  			    break;
  			case 'G': /* -MG */
  			    /* Push the following options:
  			     *   o  makedep_add_missing
  			     */
  			    buf = strsave("makedep_add_missing");
  			    PUSH2("@option", buf);
  			    break;
  			case 'P': /* -MP */
  			    /* Push the following options:
  			     *   o  makedep_phony
  			     */
  			    buf = strsave("makedep_add_missing");
  			    PUSH2("@option", buf);
  			    break;
  			default:
  			    goto error;
  			}
  		    }
  		}
  		break;
  	    case 'o':
  		PUSH2("@outdir", process_opt(&argc, &argv, 0));
  		break;
  	    case 'O':
  		PUSH("@optimize");
  		if (argv[1][2] == '\0')
  		    PUSH("1");
  		else
  		    PUSH(argv[1]+2);
  		break;
  	    case 'p':
  		{
  		    int c = argv[1][2];
  		    
  		    if (c != 'a' && c != 'z') {
  			goto error;
  #ifdef __WIN32__
  		    } else if (strcmp(argv[1], "-pause") == 0) {
  			pause_after_execution = 1;
  #endif
  		    } else {
  			char option[4];
  
  			UNSHIFT(process_opt(&argc, &argv, 1));
  			option[0] = '-';
  			option[1] = 'p';
  			option[2] = c;
  			option[3] = '\0';
  			UNSHIFT(strsave(option));
  		    }
  		}
  		break;
  	    case 's':
  		if (strcmp(argv[1], "-smp") == 0) {
  		    UNSHIFT(argv[1]);
  		} else {
  		    goto error;
  		}
  		break;
  	    case 'v':		/* Verbose. */
  		PUSH2("@verbose", "true");
  		break;
  	    case 'V':
  		/** XXX Version perhaps, but of what? **/
  		break;
  	    case 'W':		/* Enable warnings. */
  		if (strcmp(argv[1]+2, "all") == 0) {
  		    PUSH2("@warn", "999");
  		} else if (strcmp(argv[1]+2, "error") == 0) {
  		    PUSH2("@option", "warnings_as_errors");
  		} else if (isdigit((int)argv[1][2])) {
  		    PUSH2("@warn", argv[1]+2);
  		} else {
  		    PUSH2("@warn", "1");
  		}
  		break;
  	    case 'E':
  	    case 'S':
  	    case 'P':
  		{
  		    char* buf;
  
  		    /* 
  		     * From the given upper-case letter, construct
  		     * a quoted atom.  This is a convenience for the
  		     * Erlang compiler, to avoid fighting with the shell's
  		     * quoting.
  		     */
  
  		    buf = emalloc(4);
  		    buf[0] = '\'';
  		    buf[1] = argv[1][1];
  		    buf[2] = '\'';
  		    buf[3] = '\0';
  
  		    PUSH2("@option", buf);
  		}
  		break;
  
  	    case '-':
  		goto no_more_options;
  
  	    default:
  	    error:
  		usage();
  		break;
  	    }
  	}
  	argc--, argv++;
      }
  
   no_more_options:
  
      if (argc <= 1) {
  	/*
  	 * To avoid starting an Erlang system unless absolutely needed
  	 * exit if no files were specified on the command line.
  	 */
  	exit(0);
      }
  
      /*
       * The rest of the command line must be filenames.  Simply push them.
       */
  
      PUSH("@files");
      while (argc > 1) {
  	PUSH(argv[1]);
  	argc--, argv++;
      }
  
      /*
       * Move up the commands for invoking the emulator and adjust eargv
       * accordingly.
       */
  
      while (--eargc_base >= 0) {
  	UNSHIFT(eargv_base[eargc_base]);
      }
      
      /*
       * Invoke Erlang with the collected options.
       */
  
      PUSH(NULL);
      return run_erlang(eargv[0], eargv);
  }
  
  static char*
  process_opt(int* pArgc, char*** pArgv, int offset)
  {
      int argc = *pArgc;
      char** argv = *pArgv;
      int c = argv[1][1];
      
      if (argv[1][2+offset] != '\0') {
  	/*
  	 * The option was given as -x<value>.
  	 */
  	return argv[1]+2+offset;
      }
  
      /*
       * Look at the next argument.
       */
  
      argc--, argv++;
      if (argc < 2 || argv[1][0] == '-')
  	error("No value given to -%c option", c);
      *pArgc = argc;
      *pArgv = argv;
      return argv[1];
  }
  
  static void
  push_words(char* src)
  {
      char sbuf[MAXPATHLEN];
      char* dst;
  
      dst = sbuf;
      while ((*dst++ = *src++) != '\0') {
  	if (isspace((int)*src)) {
  	    *dst = '\0';
  	    PUSH(strsave(sbuf));
  	    dst = sbuf;
  	    do {
  		src++;
  	    } while (isspace((int)*src));
  	}
      }
      if (sbuf[0])
  	PUSH(strsave(sbuf));
  }
  #ifdef __WIN32__
  char *make_commandline(char **argv)
  {
      static char *buff = NULL;
      static int siz = 0;
      int num = 0;
      char **arg, *p;
  
      if (*argv == NULL) { 
  	return "";
      }
      for (arg = argv; *arg != NULL; ++arg) {
  	num += strlen(*arg)+1;
      }
      if (!siz) {
  	siz = num;
  	buff = malloc(siz*sizeof(char));
      } else if (siz < num) {
  	siz = num;
  	buff = realloc(buff,siz*sizeof(char));
      }
      p = buff;
      for (arg = argv; *arg != NULL; ++arg) {
  	strcpy(p,*arg);
  	p+=strlen(*arg);
  	*p++=' ';
      }
      *(--p) = '\0';
  
      if (debug) {
  	printf("Processed commandline:%s\n",buff);
      }
      return buff;
  }
  
  int my_spawnvp(char **argv)
  {
      STARTUPINFO siStartInfo;
      PROCESS_INFORMATION piProcInfo;
      DWORD ec;
  
      memset(&siStartInfo,0,sizeof(STARTUPINFO));
      siStartInfo.cb = sizeof(STARTUPINFO); 
      siStartInfo.dwFlags = STARTF_USESTDHANDLES;
      siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
      siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
      siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
      siStartInfo.wShowWindow = SW_HIDE;
      siStartInfo.dwFlags |= STARTF_USESHOWWINDOW;
  
  
      if (!CreateProcess(NULL, 
  		       make_commandline(argv),
  		       NULL, 
  		       NULL, 
  		       TRUE, 
  		       0,
  		       NULL, 
  		       NULL, 
  		       &siStartInfo, 
  		       &piProcInfo)) {
  	return -1;
      }
      CloseHandle(piProcInfo.hThread);
  
      WaitForSingleObject(piProcInfo.hProcess,INFINITE);
      if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) {
  	return 0;
      }
      return (int) ec;
  }    
  #endif /* __WIN32__ */
  
  
  static int
  run_erlang(char* progname, char** argv)
  {
  #ifdef __WIN32__
      int status;
  #endif
  
      if (debug) {
  	int i = 0;
  	while (argv[i] != NULL)
  	    printf(" %s", argv[i++]);
  	printf("\n");
      }
  
  #ifdef __WIN32__
      /*
       * Alas, we must wait here for the program to finish.
       * Otherwise, the shell from which we was executed will think
       * we are finished and print a prompt and read keyboard input.
       */
  
      status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/;
      if (status == -1) {
  	fprintf(stderr, "erlc: Error executing '%s': %d", progname, 
  		GetLastError());
      }
      if (pause_after_execution) {
  	fprintf(stderr, "Press ENTER to continue . . .\n");
  	while (getchar() != '\n')
  	    ;
      }
      return status;
  #else
      execvp(progname, argv);
      error("Error %d executing \'%s\'.", errno, progname);
      return 2;
  #endif
  }
  
  static void
  usage(void)
  {
      static struct {
  	char* name;
  	char* desc;
      } options[] = {
  	{"-b type", "type of output file (e.g. jam or beam)"},
  	{"-d", "turn on debugging of erlc itself"},
  	{"-Dname", "define name"},
  	{"-Dname=value", "define name to have value"},
  	{"-hybrid", "compile using hybrid-heap emulator"},
  	{"-help", "shows this help text"},
  	{"-I path", "where to search for include files"},
  	{"-M", "generate a rule for make(1) describing the dependencies"},
  	{"-MF file", "write the dependencies to 'file'"},
  	{"-MT target", "change the target of the rule emitted by dependency "
  		"generation"},
  	{"-MQ target", "same as -MT but quote characters special to make(1)"},
  	{"-MG", "consider missing headers as generated files and add them to "
  		"the dependencies"},
  	{"-MP", "add a phony target for each dependency"},
  	{"-MD", "same as -M -MT file (with default 'file')"},
  	{"-o name", "name output directory or file"},
  	{"-pa path", "add path to the front of Erlang's code path"},
  	{"-pz path", "add path to the end of Erlang's code path"},
  	{"-smp", "compile using SMP emulator"},
  	{"-v", "verbose compiler output"},
  	{"-Werror", "make all warnings into errors"},
  	{"-W0", "disable warnings"},
  	{"-Wnumber", "set warning level to number"},
  	{"-Wall", "enable all warnings"},
  	{"-W", "enable warnings (default; same as -W1)"},
  	{"-E", "generate listing of expanded code (Erlang compiler)"},
  	{"-S", "generate assembly listing (Erlang compiler)"},
  	{"-P", "generate listing of preprocessed code (Erlang compiler)"},
  	{"+term", "pass the Erlang term unchanged to the compiler"},
      };
      int i;
  
      fprintf(stderr, "Usage:\terlc [options] file.ext ...\n");
      fprintf(stderr, "Options:\n");
      for (i = 0; i < sizeof(options)/sizeof(options[0]); i++) {
  	fprintf(stderr, "%-14s %s\n", options[i].name, options[i].desc);
      }
      exit(1);
  }
  
  static void
  error(char* format, ...)
  {
      char sbuf[1024];
      va_list ap;
      
      va_start(ap, format);
      erts_vsnprintf(sbuf, sizeof(sbuf), format, ap);
      va_end(ap);
      fprintf(stderr, "erlc: %s\n", sbuf);
      exit(1);
  }
  
  static char*
  emalloc(size_t size)
  {
    char *p = malloc(size);
    if (p == NULL)
      error("Insufficient memory");
    return p;
  }
  
  static char*
  strsave(char* string)
  {
    char* p = emalloc(strlen(string)+1);
    strcpy(p, string);
    return p;
  }
  
  static char*
  get_default_emulator(char* progname)
  {
      char sbuf[MAXPATHLEN];
      char* s;
  
      if (strlen(progname) >= sizeof(sbuf))
          return ERL_NAME;
  
      strcpy(sbuf, progname);
      for (s = sbuf+strlen(sbuf); s >= sbuf; s--) {
  	if (IS_DIRSEP(*s)) {
  	    strcpy(s+1, ERL_NAME);
  #ifdef __WIN32__
  	    if (_access(sbuf, 0) != -1) {
  		return strsave(sbuf);
  	    }
  #else
  	    if (access(sbuf, 1) != -1) {
  		return strsave(sbuf);
  	    }
  #endif
  	    break;
  	}
      }
      return ERL_NAME;
  }
  
  #ifdef __WIN32__
  static char*
  possibly_quote(char* arg)
  {
      int mustQuote = NO;
      int n = 0;
      char* s;
      char* narg;
  
      if (arg == NULL) {
  	return arg;
      }
  
      /*
       * Scan the string to find out if it needs quoting and return
       * the original argument if not.
       */
  
      for (s = arg; *s; s++, n++) {
  	switch(*s) {
  	case ' ':
  	    mustQuote = YES;
  	    continue;
  	case '"':
  	    mustQuote = YES;
  	    n++;
  	    continue;
  	case '\\':
  	    if(s[1] == '"')
  		n++;
  	    continue;
  	default:
  	    continue;
  	}
      }
      if (!mustQuote) {
  	return arg;
      }
  
      /*
       * Insert the quotes and put a backslash in front of every quote
       * inside the string.
       */
  
      s = narg = emalloc(n+2+1);
      for (*s++ = '"'; *arg; arg++, s++) {
  	if (*arg == '"' || (*arg == '\\' && arg[1] == '"')) {
  	    *s++ = '\\';
  	}
  	*s = *arg;
      }
      if (s[-1] == '\\') {
  	*s++ ='\\';
      }
      *s++ = '"';
      *s = '\0';
      return narg;
  }
  #endif /* __WIN32__ */
  