Summary

  This file contains 4 classes:
    ArgumentExpander
      which reads the command line arguments and stores them.
      It handles @file redirection so arguments can be in files.
    KeywordMap
      simple struct class that holds keyword information.
    OptionError
      simple class to store bad option information.
    Option
      storage class that reads the command line arguments from an
      ArgumentExpander class and sets class-public booleans.
  

Source

// $Id: option.cpp,v 1.7 1999/03/10 19:59:21 shields Exp $
copyright notice

#include <ctype.h>
#include "config.h"
#include "option.h"
#include "javasym.h"
#include "error.h"


ArgumentExpanded will interpret it's second argument as a filename and
will append arguments found in the file to the first argument. If the
second argument is NOT a file then this will return false.

bool ArgumentExpander::ArgumentExpanded(Tuple<char *> &arguments, char *file_name)
{
    struct stat status;
    FILE *afile = fopen(file_name, "r");
    if (afile && (::SystemStat(file_name, &status) == 0))
    {
        char *buffer = new char[status.st_size + 2];
        int file_size = fread(buffer, 1, status.st_size, afile);
        buffer[file_size] = '\n';
        for (int k = 0; k < file_size; k++)
        {
            //
            // isgraph(c) is true if c is any printing character except space.
            //
            while ((! isgraph(buffer[k])) && buffer[k] != '\n' && buffer[k] != '\r')
                k++;
            if (buffer[k] != '\n' && buffer[k] != '\r')
            {
                int n;
                for (n = k + 1; buffer[n] != '\n' && buffer[n] != '\r'; n++)
                    ;
                buffer[n] = U_NULL;
                char *str = new char[n - k + 1];
                strcpy(str, &buffer[k]);
                arguments.Next() = str;
                k = n;
            }
        }
        delete [] buffer;
        fclose(afile);

        return true;
    }

    return false;
}


ArgumentExpander is the primary constructor that processes command line
arguments. An example of use is:
  ArgumentExpander *arguments = new ArgumentExpander(argc, argv);
This will eventually be passed to the Option class constructor.

ArgumentExpander::ArgumentExpander(int argc_, char *argv_[])
{
    Tuple<char *> arguments(8192);
    for (int i = 0; i < argc_; i++)
    {
        char *argument = argv_[i];
        if (argument[0] != '@' || (! ArgumentExpanded(arguments, argument + 1)))
        { If the argument is not a file redirection (i.e. @file) 
            char *str = new char[strlen(argument) + 1];
            strcpy(str, argument);
            arguments.Next() = str;
        }
    }

    argc = arguments.Length();
    argv = new char*[argc];

    for (int k = 0; k < argc; k++)
        argv[k] = arguments[k];

    return;
}


ArgumentExpander can be used internally to parse a line that contains
command line style arguments. This would be useful for constructing argument
lists for further function calls.

ArgumentExpander::ArgumentExpander(Tuple<char> &line)
{
    Tuple<char *> arguments(8192);

    int end = 0; 
    do
    {
        for (; end < line.Length() && line[end] == U_SPACE; end++)
            ;
        if (end < line.Length())
        {
            int start = end;
            for (end++; end < line.Length() && line[end] != U_SPACE; end++)
                ;
            int length = end - start;
            char *argument = new char[length + 1];
            for (int i = 0, k = start; k < end; i++, k++)
                argument[i] = line[k];
            argument[length] = U_NULL;

            if (argument[0] == '@' && ArgumentExpanded(arguments, argument + 1))
                 delete argument;
            else arguments.Next() = argument;
        }
    } while(end < line.Length());

    argc = arguments.Length();
    argv = new char*[argc];

    for (int k = 0; k < argc; k++)
        argv[k] = arguments[k];

    return;
}


#ifdef WIN32_FILE_SYSTEM

void Option::SaveCurrentDirectoryOnDisk(char c)
{
    if (! current_directory[c])
    {
        char *disk_directory = NULL,
             disk[3] = { c, U_COLON, U_NULL },
             tmp[1];

        if (SetCurrentDirectory(disk))
        {
            DWORD directory_length = GetCurrentDirectory(0, tmp);  // first, get the right size
            disk_directory = new char[directory_length + 1];       // allocate the directory
            DWORD length = GetCurrentDirectory(directory_length, disk_directory);
            if (length <= directory_length)
            {
                for (char *ptr = disk_directory; *ptr; ptr++)
                    *ptr = (*ptr != U_BACKSLASH ? *ptr : (char) U_SLASH); // turn '\' to '/'.
            }
        }

        if (! disk_directory)
        {If we failed to set the disk_directory, make it "."
            disk_directory = new char[2];
            strcpy(disk_directory, StringConstant::U8S__DO_);
        }

        current_directory[Case::ToAsciiLower(c)] = disk_directory;
        current_directory[Case::ToAsciiUpper(c)] = disk_directory;
    }

    return;
}
#endif


Option::Option(ArgumentExpander &arguments) : default_path(NULL),
                                              classpath(NULL),
                                              makefile_name(NULL),
                                              debug_dump_lex(false),
                                              debug_dump_ast(false),
                                              debug_dump_class(false),
                                              debug_trap_op(false),
                                              applet_author(false),
                                              incremental(false),
                                              makefile(false),
                                              bytecode(true),
                                              full_check(false),
                                              unzip(false),
                                              dump_errors(false),
                                              errors(true),
                                              ascii(false),
                                              comments(false),
                                              pedantic(false),
                                              directory(NULL),
                                              first_file_index(arguments.argc),
                                              one_one(true),
                                              g(false),
                                              nowrite(false),
                                              deprecation(false),
                                              verbose(false),
                                              depend(false),
                                              nowarn(false),
                                              O(false),
                                              zero_defect(false)
{

The WIN32_FILE_SYSTEM has the notion of logical drives, each with it's
own drive letter from a thru z. In a fully expanded path the drive letter
is the first character of the path, followed by a colon. The path is a
series of symbols, possibly containing spaces, separated by backslashes thus:
   C:\Java\bin\java.exe
Note that case is ignored.

#ifdef WIN32_FILE_SYSTEM

current_directory is an array of the current directory on each logical
disk in a windows file system. First we make sure they are all null.

    for (int j = 0; j < 128; j++)
         current_directory[j] = NULL;

We need to dance a bit to get the current directory for current logical
disk at the time we are invoked. First we get the length of the directory
by asking for it using a zero length buffer. Then we allocate a string to
hold the result and we ask again, saving the result in main_current_directory.

    char tmp[1];
    DWORD directory_length = GetCurrentDirectory(0, tmp); // first, get the right size
    char *main_current_directory = new char[directory_length + 1];   // allocate the directory
    DWORD length = GetCurrentDirectory(directory_length, main_current_directory);
    if (length > directory_length)
    { the directory doesn't exist
        delete [] main_current_directory;
        main_current_directory = StringConstant::U8S__DO_;
        main_disk = 0;
    }
    else
    {
Now that we have the directory name we fix the backslashes. Then we chew
off the first character, which in windows is the logical drive letter and
call it main_disk. main_disk is used as an index for the current_directory
array. We store it twice, once using the lower case drive letter as an index
and once using the upper case letter as an index (type crufty but it works).

        for (char *ptr = main_current_directory; *ptr; ptr++)
            *ptr = (*ptr != U_BACKSLASH ? *ptr : (char) U_SLASH); // turn '\' to '/'.
        main_disk = main_current_directory[0]; // the first character
        current_directory[Case::ToAsciiLower(main_disk)] = main_current_directory;
        current_directory[Case::ToAsciiUpper(main_disk)] = main_current_directory;
    }
Finally we store the same information as the zero-th entry in the 
current_directory array. Note that if main_disk is zero then that is
NOT used as a current_directory index but is considered a flag that the
main_disk and main_current_directory are invalid.

    current_directory[0] = main_current_directory;
#endif

    Tuple<int> filename_index(2048);

arguments is a class object of type ArgumentExpander. This class is a 
container for the argument list given to main. argc is the number of strings
and argv is an array of strings.

    for (int i = 1; i < arguments.argc; i++)
    {
For each string in the array of arguments it must be one of three cases.
     case 1: It has a - prefix. It is a standard option, process it.
     case 2: It has a + prefix. This is jikes specific, process it. 
     case 3: It must be a filename.
processing the option usually sets a boolean flag. If the option has a
parameter associated with it (e.g. -classpath) we process the next string
as the parameter and increment the loop index inside the loop.

        if (arguments.argv[i][0] == '-')
        {
case 1: It has a - prefix
            if (strcmp(arguments.argv[i],"-classpath") == 0 && ((i + 1) < arguments.argc))
            {classpath as a command-line option will override both the 
             JIKESPATH environment variable and the CLASSPATH environment 
             variable. It was defaulted to NULL on the method signature line.
             classpath = arguments.argv[++i];
#ifdef EBCDIC
EBCDIC is a mainframe code similar to ASCII. Like IPLs and
VTOCs only IBMers know the whys and wherefores of EBCDIC.

                //
                //  Maintain CLASSPATH in ASCII and translate back to EBCDIC when building file name
                //
                for (int k = 0; k < strlen(classpath); k++)
                    classpath[k] = Code::ToASCII(classpath[k]);
#endif
            }
            else if (strcmp(arguments.argv[i], "-depend") == 0)
              We want to recompile all of the classes these classes depend on
                 depend = true;
            else if (strcmp(arguments.argv[i],"-verbose") == 0) 
              We want to list all of the files read and written.
                 verbose = true;
            else if (strcmp(arguments.argv[i],"-g") == 0)
              generate LocalVariableTable attribute (for debugging)
                 g = true;
            else if (strcmp(arguments.argv[i], "-O") == 0)
              do not generate LineNumberTable attribute
                 O = true;
            else if (strcmp(arguments.argv[i],"-deprecation") == 0)
            { We want complaints about Deprecated features (not implemented)

                 bad_options.Next() = new OptionError(SemanticError::UNSUPPORTED_OPTION, arguments.argv[i]);
                 deprecation = true;
            }
            else if (strcmp(arguments.argv[i],"-nowrite") == 0)
              We don't want to write the class files
                 nowrite = true;
            else if (strcmp(arguments.argv[i],"-nowarn") == 0)
              We want to suppress warning messages.
                 nowarn = true;
            else if (strcmp(arguments.argv[i], "-d") == 0 && ((i + 1) < arguments.argc))
            {We want to write class files to the specified directory
                ++i;
#ifdef UNIX_FILE_SYSTEM
in UNIX remember the current directory
                int length = strlen(arguments.argv[i]);
                directory = new char[length + 1];
                strcpy(directory, arguments.argv[i]);
#elif defined(WIN32_FILE_SYSTEM)
If the directory is prefixed by a new logical disk letter we need to update
our table of current directories for the logical disk and make windows 
aware that we want a new logical directory on the new logical drive.
                char disk = (strlen(arguments.argv[i]) >= 2 &&
                            Case::IsAsciiAlpha(arguments.argv[i][0]) &&
                            arguments.argv[i][1] == U_COLON
                                     ? arguments.argv[i][0]
                                     : 0);
                Save the directory where we currently are pointing
                SaveCurrentDirectoryOnDisk(disk);
                if (SetCurrentDirectory(arguments.argv[i]))
                {Move to the new directory and save it
                    char tmp[1];
                    DWORD directory_length = GetCurrentDirectory(0, tmp); // first, get the right size
                    directory = new char[directory_length + 1]; // allocate the directory
                    DWORD length = GetCurrentDirectory(directory_length, directory);
                    if (length > directory_length)
                    { the directory doesn't exist
                        delete [] directory;
                        directory = NULL;
                    }
                }
                Let windows know the current directory for the new logical disk
                ResetCurrentDirectoryOnDisk(disk); // reset the current directory on the disk
                And go back to the main logical disk again
                SetMainCurrentDirectory();       // reset the real current directory...

                if (! directory)
                    bad_options.Next() = new OptionError(SemanticError::INVALID_DIRECTORY, arguments.argv[i]);
#endif
#ifdef EBCDIC
NOTE: if EBCDIC and WIN32_FILE_SYSTEM get defined together this code will
fail because directory[k] will contain backslashes. This implies that the
two switches are mutually exclusive. Heaven forfend anyone should run
windows on an EBCDIC machine.
                //
                // need to translate directory name to ASCII
                //
                for (int k = 0; k < directory_length; k++)
                    directory[k] = Code::ToASCII(directory[k]);
#endif
                if (directory)
                {Replace the backslashes. Code should be moved up?
                    for (char *ptr = directory; *ptr; ptr++)
                        *ptr = (*ptr != U_BACKSLASH ? *ptr : (char) U_SLASH); // turn '\' to '/'.
                }
            }

            else bad_options.Next() = new OptionError(SemanticError::INVALID_OPTION, arguments.argv[i]);
            Somebody gave us an option prefixed with - we don't recognize
        }
        else if (arguments.argv[i][0] == '+')
        {
case 2: It has a + prefix
            if (strcmp(arguments.argv[i], "+AA") == 0)
                 applet_author = true;
            if (strcmp(arguments.argv[i], "+A") == 0)
                 debug_dump_ast = true;
#ifdef EBCDIC
            else if (strcmp(arguments.argv[i], "+ASCII") == 0)
                     ascii = true;
#endif
            else if (strcmp(arguments.argv[i], "+B") == 0)
              do not generate byte codes. skips a compiler step
                 bytecode = false;
            else if (strcmp(arguments.argv[i], "+c") == 0)
                 comments = true;
            else if (strcmp(arguments.argv[i], "+C") == 0)
                 debug_dump_class = true;
            else if (strcmp(arguments.argv[i],"+D") == 0)
            { List errors as they happen. Don't wait for compile complete
                 dump_errors = true;
              use Emacs error message form (+E option forced)
                 errors = false;
            }
            else if (strcmp(arguments.argv[i],"+E") == 0)
              use Emacs error message form
                 errors = false;
            else if (arguments.argv[i][0] == '+' && arguments.argv[i][1] == 'K')
            {add the symbol to the keyword list.
             (I don't understand why this option exists -- tpd)
                char *name = arguments.argv[i] + 2,
                     *image;
                for (image = name; *image && *image != '='; image++)
                    ;

                if (*image != '=')

                    bad_options.Next() = new OptionError(SemanticError::INVALID_K_OPTION, arguments.argv[i]);
                else
                {
                    int key = 0; // assume undefined
                    image++;

                    if (strcmp(image, "boolean") == 0)
                         key = TK_boolean;
                    else if (strcmp(image, "byte") == 0)
                         key = TK_byte;
                    else if (strcmp(image, "char") == 0)
                         key = TK_char;
                    else if (strcmp(image, "short") == 0)
                         key = TK_short;
                    else if (strcmp(image, "int") == 0)
                         key = TK_int;
                    else if (strcmp(image, "long") == 0)
                         key = TK_long;
                    else if (strcmp(image, "float") == 0)
                         key = TK_float;
                    else if (strcmp(image, "double") == 0)
                         key = TK_double;

                    else bad_options.Next() = new OptionError(SemanticError::INVALID_K_TARGET, image);

                    if (key != 0)
                    {
                        int i = keyword_map.NextIndex();
                        keyword_map[i].key = key;
                        keyword_map[i].length = image - name - 1;
                        keyword_map[i].name = new wchar_t[keyword_map[i].length];
                        for (int k = 0; k < keyword_map[i].length; k++)
                            keyword_map[i].name[k] = name[k]; 
                    }
                }
            }
            else if (strcmp(arguments.argv[i],"+1.0") == 0)
              recognize only the 1.0 level of the language, reporting as errors
	         any uses of features added in the 1.1 level.
                 one_one = false;
            else if (strcmp(arguments.argv[i],"+F") == 0)
	     do full dependence check except for Zip and Jar files
                 full_check = true;
            else if (strcmp(arguments.argv[i],"+M") == 0) 
            {generate makefiles with dependencies
                 makefile = true;
	     do full dependence check except for Zip and Jar files
                 full_check = true;
            }
            else if (strncmp(arguments.argv[i], "+M=", 3) == 0) 
            {generate makefiles with dependencies
                 makefile = true;
	     do full dependence check except for Zip and Jar files
                 full_check = true;
                 makefile_name = &arguments.argv[i][3];
            }
            else if (strcmp(arguments.argv[i], "+O") == 0)
            {
                 debug_trap_op = atoi(arguments.argv[i+1]);
                 i++;
            }
            else if (strcmp(arguments.argv[i],"+P") == 0)
              generate pedantic listing
                 pedantic = true;
            else if (arguments.argv[i][0] == U_PLUS && arguments.argv[i][1] == U_T)
            {set the tab size in listings (tabs are evil -- tpd)
                int tab_size = 0;
                char *image = arguments.argv[i] + 2,
                     *p;
                for (p = image; *p && Code::IsDigit(*p); p++)
                {
                    int digit = *p - U_0;
                    tab_size = tab_size * 10 + digit;
                }

                if (*p)
                     bad_options.Next() = new OptionError(SemanticError::INVALID_TAB_VALUE, image);
                Tab::SetTabSize(tab_size == 0 ? Tab::DEFAULT_TAB_SIZE : tab_size);
            }
            else if (strcmp(arguments.[i], "+L") == 0)
                 debug_dump_lex = true;
            else if (strcmp(arguments.argv[i],"+U") == 0)
            {
                 unzip = true;
	     do full dependence check except for Zip and Jar files
                 full_check = true;
            }
            else if (strcmp(arguments.argv[i],"++") == 0)
            {We want to keep the compiler running continuously
                 incremental = true;
	     do full dependence check except for Zip and Jar files
                 full_check = true;
            }
            else if (strcmp(arguments.argv[i], "+Z") == 0)
             treat cautions as errors
                 zero_defect = true;
            else bad_options.Next() = new OptionError(SemanticError::INVALID_OPTION, arguments.argv[i]);
        }
case 3: It is a filename
        else filename_index.Next() = i;
    }

classpath is set to:
  if specified on the command line by -classpath, use that
  else if JIKESPATH exists in the environment, use that
  else if CLASSPATH exists in the environment, use that
  else classpath is NULL.
    if (! classpath)
    {
        classpath = getenv("JIKESPATH");
        if (! classpath)
            classpath = getenv("CLASSPATH");

        if (classpath)
        {
#ifdef EBCDIC
            //
            //  Maintain CLASSPATH in ASCII and translate back to EBCDIC when building file name
            //
            for (int k = 0; k < strlen(classpath); k++)
                classpath[k] = Code::ToASCII(classpath[k]);
#endif
            while (isspace(*classpath))
                classpath++;

            if (*classpath == U_NULL)
                classpath = NULL;
        }

        if (! classpath)
        {
            default_path = new char[2];
            default_path[0] = '.';
            default_path[1] = U_NULL;
            classpath = default_path;
        }
    }

    //
    // Initially, first_file_index is set to argc. Since the array filename_index
    // contains the indices of all the input files in arguments in reverse order,
    // we reverse the order here again to get the original list...
    //
    for (int k = filename_index.Length() - 1; k >= 0; k--)
    {
        first_file_index--;

        int i = filename_index[k];
        char *temp = arguments.argv[i];
        arguments.argv[i] = arguments.argv[first_file_index];
        arguments.argv[first_file_index] = temp;
    }

    return;
}


Option::~Option()
{
    for (int i = 0; i < bad_options.Length(); i++)
        delete bad_options[i];

    delete [] default_path;
    delete [] directory;

#ifdef WIN32_FILE_SYSTEM
NOTE: doesn't delete all 128 entries. why?
    for (char c = 'a'; c <= 'z'; c++)
        delete [] current_directory[c];
#endif

    return;
}