/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  command line argument parser
*
****************************************************************************/

//#include <stdarg.h>
#include <stddef.h>
#include <ctype.h>

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "msgtext.h"
#include "dbgcv.h"
#include "cmdline.h"
#include "myassert.h"
#include "input.h" /* GetFNamePart() */

//#ifdef __OSI__
//  #include "ostype.h"
//#endif

#if defined(__UNIX__) || defined(__CYGWIN__) || defined(__DJGPP__)

#define HANDLECTRLZ 0
#define SWITCHCHAR 0

#else

#define HANDLECTRLZ 1
#define SWITCHCHAR 1

#endif

#ifdef __I86__
#define OPTQUAL __near
#else
#define OPTQUAL
#endif

extern char     banner_printed;

struct global_options Options = {
    /* quiet            */          FALSE,
    /* line_numbers     */          FALSE,
    /* debug_symbols    */          0,
    /* debug_ext        */          0, /* v2.10 */
    /* floating_point   */          FPO_NO_EMULATION,

    /* error_limit      */          50,
    /* no error display */          FALSE,
    /* warning_level    */          2,
    /* warning_error    */          FALSE,
#ifdef DEBUG_OUT
    /* debug            */          FALSE,
    /* nobackpatch      */          FALSE,
#if FASTPASS
    /* nofastpass       */          FALSE,
    /* print_linestore  */          FALSE,
#endif
    /* max_passes       */          0,
    /* skip_preprocessor */         0,
    /* log_all_files    */          0,
    /* dump_reswords    */          FALSE,
    /* dump_reswords_hash */        FALSE,
    /* dump_symbols     */          FALSE,
    /* dump_symbols_hash */         FALSE,
    /* display_memalloc */          FALSE,
#endif
    /* names            */          {
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
#if BUILD_TARGET
        NULL,
#endif
#if MANGLERSUPP
        NULL
#endif
    },
    /* queues           */          { NULL, NULL, NULL },
#if COCTALS
    /* allow_c_octals        */     FALSE,
#endif
    /* no_comment_data_in_code_records */   FALSE,
    /* no_opt_farcall        */     FALSE,
    /* no_dependencies       */ //    FALSE,
#if COFF_SUPPORT
    /* no_file_entry         */     FALSE,
    /* no_static_procs       */     FALSE,
    /* no_section_aux_entry  */     FALSE,
#endif
    /* no_cdecl_decoration   */     FALSE,
    /* stdcall_decoration    */     STDCALL_FULL,
    /* no_export_decoration  */     FALSE,
    /* entry_decorated       */     FALSE,
    /* write_listing         */     FALSE,
    /* write_impdef          */     FALSE,
    /* case_sensitive        */     FALSE,
    /* convert_uppercase     */     FALSE,
    /* preprocessor_stdout   */     FALSE,
    /* masm51_compat         */     FALSE,
    /* strict_masm_compat    */     FALSE,
    /* masm_compat_gencode   */     FALSE,
    /* masm8_proc_visibility */     FALSE,

    /* listif                */     FALSE,
    /* list_generated_code   */     FALSE,
    /* list_macro            */     LM_LISTMACRO,
    /* no_symbol_listing     */     FALSE,
    /* first_pass_listing    */     FALSE,
#ifndef __I86__
    /* no_final_msg_listing  */     FALSE, /* v2.13 */
#endif
    /* all_symbols_public    */     FALSE,
    /* safeseh               */     FALSE,
    /* ignore_include        */     FALSE,
    /* output_format         */     OFORMAT_OMF,
    /* sub_format            */     SFORMAT_NONE,
    /* alignment_default     */     0,
    /* langtype              */     LANG_NONE,
    /* model                 */     MODEL_NONE,
    /* cpu                   */     P_86,
    /* fastcall type         */     FCT_MSC,
    /* syntax check only     */     FALSE,
#if MANGLERSUPP
    /* naming_convention*/          NC_DO_NOTHING,
#endif
};

char *DefaultDir[NUM_FILE_TYPES] = { NULL, NULL, NULL, NULL };
//static char *DefaultExt[NUM_FILE_TYPES] = { OBJ_EXT, LST_EXT, ERR_EXT };

#ifdef __I86__
#define MAX_RSP_NESTING 3
#else
#define MAX_RSP_NESTING 15  /* max nesting level of response files */
#endif

static unsigned         OptValue;  /* value of option's numeric argument  */
static char             *OptName;  /* value of option's name argument     */
static const char       *cmdbuffers[MAX_RSP_NESTING]; /* response file stack */
static const char       *cmdsave[MAX_RSP_NESTING];    /* saved old cmdline */
static int              rspidx = 0; /* current response file index */

/* array for options -0 ... -10 */
static const enum cpu_info cpuoption[] = {
    P_86, P_186, P_286, P_386,          /* 0-3 */
    P_486, P_586, P_686, P_686 | P_MMX, /* 4-7 */
    P_686 | P_MMX | P_SSE1,             /* 8   */
    P_686 | P_MMX | P_SSE1 | P_SSE2,    /* 9   */
#if AMD64_SUPPORT
    P_64,                               /* 10  */
#endif
};

#if 0 /* v2.10: removed, because factually never called */
static void StripQuotes( char *fname )
/************************************/
{
    char *s;
    char *d;

    if( *fname == '"' ) {
        /* string will shrink so we can reduce in place */
        d = fname;
        for( s = d + 1; *s && *s != '"'; ++s ) {
            /* collapse double backslashes, only then look for escaped quotes */
            if( s[0] == '\\' && s[1] == '\\' ) {
                ++s;
            } else if( s[0] == '\\' && s[1] == '"' ) {
                ++s;
            }
            *d++ = *s;
        }
        *d = '\0';
    }
}

static char *GetAFileName( void )
/*******************************/
{
    DebugMsg(("GetAFileName() enter, OptName=>%s<\n", OptName ));
    StripQuotes( OptName );
    return( OptName );
}
#else
#define GetAFileName() OptName
#endif

#if BUILD_TARGET /* obsolete */

static void SetTargName( char *name, unsigned len )
/*************************************************/
{
    if( Options.names[OPTN_BUILD_TARGET] != NULL ) {
        MemFree( Options.names[OPTN_BUILD_TARGET] );
        Options.names[OPTN_BUILD_TARGET] = NULL;
    }
    if( name == NULL || len == 0 )
        return;
    Options.names[OPTN_BUILD_TARGET] = MemAlloc( len + 1 );
    strcpy( Options.names[OPTN_BUILD_TARGET], name );
    _strupr( Options.names[OPTN_BUILD_TARGET] );
}

#define MAX_OS_NAME_SIZE 7

static void set_default_build_target( void )
/******************************************/
{

    if( Options.names[OPTN_BUILD_TARGET] == NULL ) {
        Options.names[OPTN_BUILD_TARGET] = MemAlloc( MAX_OS_NAME_SIZE + 1 );
#if defined(__OSI__)
        if( __OS == OS_DOS ) {
            strcpy( Options.names[OPTN_BUILD_TARGET], "DOS" );
        } else if( __OS == OS_OS2 ) {
            strcpy( Options.names[OPTN_BUILD_TARGET], "OS2" );
        } else if( __OS == OS_NT ) {
            strcpy( Options.names[OPTN_BUILD_TARGET], "NT" );
        } else if( __OS == OS_WIN ) {
            strcpy( Options.names[OPTN_BUILD_TARGET], "WINDOWS" );
        } else {
            strcpy( Options.names[OPTN_BUILD_TARGET], "XXX" );
        }
#elif defined(__QNX__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "QNX" );
#elif defined(__LINUX__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "LINUX" );
#elif defined(__BSD__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "BSD" );
#elif defined(__OSX__) || defined(__APPLE__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "OSX" );
#elif defined(__DOS__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "DOS" );
#elif defined(__OS2__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "OS2" );
#elif defined(__NT__)
        strcpy( Options.names[OPTN_BUILD_TARGET], "NT" );
#else
        #error unknown host OS
#endif
    }
    return;
}
#endif

/* called by -0, -1, ... argument */

static void SetCpuCmdline( enum cpu_info value, const char *parm )
/****************************************************************/
{

    Options.cpu &= ~(P_CPU_MASK | P_EXT_MASK | P_PM);
    Options.cpu |= value;

    for( ; *parm ; parm++ ) {
        if( *parm == 'p' && Options.cpu >= P_286 ) {
            Options.cpu |= P_PM;      /* set privileged mode */
#if MANGLERSUPP
        } else if( *parm == '"' ) {       /* set default mangler */
            char *dest;
            parm++;
            dest = strchr( parm, '"' );
            if( Options.names[OPTN_DEFNAME_MANGLER] != NULL ) {
                MemFree( Options.names[OPTN_DEFNAME_MANGLER );
            }
            Options.names[OPTN_DEFNAME_MANGLER = MemAlloc( dest - parm + 1 );
            dest = Options.names[OPTN_DEFNAME_MANGLER];
            for( ; *parm != '"'; dest++, parm++ ) {
                *dest = *parm;
            }
            *dest = NULLC;
#endif
        } else {
            EmitWarn( 1, CPU_OPTION_INVALID, parm );
            break;
        }
    }
}

/* queue a text macro, include path or "forced" include files.
   this is called for cmdline options -D, -I and -Fi
 */

static void queue_item( int i, const char *string )
/*************************************************/
{
    struct qitem *p;
    struct qitem *q;

    DebugMsg(("queue_item(%u, %s) enter\n", i, string));
    p = MemAlloc( sizeof(struct qitem) + strlen( string ) );
    p->next = NULL;
    strcpy( p->value, string );
    q = Options.queues[i];
    if ( q ) {
        for ( ; q->next; q = q->next );
        q->next = p;
    } else
        Options.queues[i] = p;
    return;
}

static void get_fname( int type, const char *token )
/**************************************************/
/*
 * called by -Fo, -Fw or -Fl (for .OBJ, .ERR or .LST filenames ).
 * also called by -Fd; in this case type is > NUM_FILE_TYPES!
 * v2.12: _splitpath()/_makepath() removed.
 */
{
    const char  *pName;
    char        name [ FILENAME_MAX ];

    DebugMsg(("get_fname( type=%u, >%s< ) enter\n", type, token ));
    //_splitpath( token, drive, dir, fname, ext );
    pName = GetFNamePart( token );
    /*
     * If name's ending with a '\' (or '/' in Unix), it's supposed
     * to be a directory name only.
     */
    if( *pName == NULLC ) {
        DebugMsg(("get_fname(%u, >%s< ) name is empty or a directory\n", type, token ));
        /* v2.10: ensure type is < NUM_FILE_TYPES */
        if ( type < NUM_FILE_TYPES ) {
            if ( DefaultDir[type] )
                MemFree( DefaultDir[type]);
            DefaultDir[type] = MemAlloc( strlen( token ) + 1 );
            strcpy( DefaultDir[type], token );
        }
        return;
    }
    /* v2.10: ensure type is < NUM_FILE_TYPES */
    //if ( drive[0] == NULLC && dir[0] == NULLC && type < NUM_FILE_TYPES && DefaultDir[type] ) {
    name[0] = NULLC;
    if ( pName == token && type < NUM_FILE_TYPES && DefaultDir[type] ) {
        DebugMsg(("get_fname: default drive+dir used: %s\n" ));
        //_splitpath( DefaultDir[type], drive, dir, NULL, NULL );
        strcpy( name, DefaultDir[type] );
    }
    strcat( name, token );
#if 0 /* v2.12: extension will be set in SetFileNames() */
    if( type && type < NUM_FILE_TYPES ) {
        char *pExt = GetExtPart( name );
        if ( *pExt == NULLC ) {
            *pExt++ = '.';
            strcpy( pExt, DefaultExt[type-1] );
        }
    }
#endif
    //_makepath( name, drive, dir, fname, pExt );
    if( Options.names[type] != NULL ) {
        MemFree( Options.names[type] );
    }
    Options.names[type] = MemAlloc( strlen( name ) + 1 );
    strcpy( Options.names[type], name );
}

static void set_option_n_name( int idx, const char *name )
/********************************************************/
/* option -n: set name of
 * - nd: data seg
 * - nm: module name
 * - nt: text seg
 * - nc: code class
 */
{
    if ( *name != '.' && !is_valid_id_char( *name ) ) {
        EmitError( N_OPTION_NEEDS_A_NAME_PARAMETER );
        return;
    }

    if( Options.names[idx] != NULL ) {
        MemFree( Options.names[idx] );
    }
    Options.names[idx] = MemAlloc( strlen( name ) + 1 );
    strcpy( Options.names[idx], name );
}

//static void OPTQUAL Ignore( void ) {};

#if BUILD_TARGET
static void OPTQUAL Set_bt( void ) { SetTargName( OptName,  strlen(OptName) ); }
#endif

static void OPTQUAL Set_c( void ) { }

#ifdef DEBUG_OUT
static void OPTQUAL Set_ce( void ) { rspidx = 1 / rspidx; }
#endif

static void OPTQUAL Set_Cp( void ) { Options.case_sensitive = TRUE;   Options.convert_uppercase = FALSE; }
static void OPTQUAL Set_Cu( void ) { Options.case_sensitive = FALSE;  Options.convert_uppercase = TRUE;  }
static void OPTQUAL Set_Cx( void ) { Options.case_sensitive = FALSE;  Options.convert_uppercase = FALSE; }

static void OPTQUAL Set_Zd( void ) { Options.line_numbers = TRUE; }
static void OPTQUAL Set_Zi( void )
{
    Set_Zd();
    Options.debug_symbols = CV_SIGNATURE;
    /* v2.10: added optional numeric argument for -Zi */
    if ( OptValue <= CVEX_MAX )
        Options.debug_ext = OptValue;
    else
        EmitWarn( 1, INVALID_CMDLINE_VALUE, "Zi" );
}

static void OPTQUAL Set_Zp( void )
/********************************/
{
    uint_8 power;
    for ( power = 0; (1 << power) <= MAX_STRUCT_ALIGN; power++ )
        if ( ( 1 << power ) == OptValue ) {
            Options.fieldalign = power;
            return;
        }
    EmitWarn( 1, INVALID_CMDLINE_VALUE, "Zp" );
    return;
}

static void OPTQUAL Set_D( void )  { queue_item( OPTQ_MACRO,    GetAFileName() ); }
static void OPTQUAL Set_Fi( void ) { queue_item( OPTQ_FINCLUDE, GetAFileName() ); }
static void OPTQUAL Set_I( void )  { queue_item( OPTQ_INCPATH,  GetAFileName() ); }

static void OPTQUAL Set_e( void ) { Options.error_limit = OptValue; }

static void OPTQUAL Set_nologo( void ) { banner_printed = TRUE; }
static void OPTQUAL Set_q( void )      { Set_nologo(); Options.quiet = TRUE; }
static void OPTQUAL Set_EP( void ) { Options.preprocessor_stdout = TRUE; Set_q(); }

#if DLLIMPORT
static void OPTQUAL Set_Fd( void ) { get_fname( OPTN_LNKDEF_FN, GetAFileName() ); Options.write_impdef = TRUE;}
#endif
static void OPTQUAL Set_Fw( void ) { get_fname( OPTN_ERR_FN, GetAFileName() ); }
static void OPTQUAL Set_Fl( void ) { get_fname( OPTN_LST_FN, GetAFileName() ); Options.write_listing = TRUE;}
static void OPTQUAL Set_Fo( void ) { get_fname( OPTN_OBJ_FN, GetAFileName() ); }

static void OPTQUAL Set_fp( void ) { Options.cpu &= ~P_FPU_MASK; Options.cpu = OptValue; }
static void OPTQUAL Set_FPx( void ) { Options.floating_point = OptValue; }
static void OPTQUAL Set_G( void ) { Options.langtype = OptValue; }

static void OPTQUAL Set_Sa( void )
/********************************/
{
    Options.listif = TRUE;
    Options.list_generated_code = TRUE;
    Options.list_macro = LM_LISTMACROALL;
}

static void OPTQUAL Set_True( void )
/**********************************/
{
    char *p = ((char *)&Options) + OptValue;
    *p = TRUE;
}

static void OPTQUAL Set_m( void ) { Options.model = OptValue; }
static void OPTQUAL Set_n( void ) { set_option_n_name( OptValue, OptName ); }

#ifdef DEBUG_OUT
static void OPTQUAL Set_pm( void ) { Options.max_passes = OptValue; };
#endif

static void OPTQUAL Set_WX( void ) { Options.warning_error = TRUE; }

static void OPTQUAL Set_w( void ) { Set_WX(); Options.warning_level = 0; }

static void OPTQUAL Set_W( void )
/*******************************/
{
    if ( OptValue <= 4 )
        Options.warning_level = OptValue;
    else
        EmitWarn( 1, INVALID_CMDLINE_VALUE, "W" );
}

static void OPTQUAL Set_ofmt( void )
/**********************************/
{
    Options.output_format = OptValue & 0xff;
    Options.sub_format = OptValue >> 8;
}

static void OPTQUAL Set_zcm( void ) { Options.no_cdecl_decoration = FALSE; }
#if OWFC_SUPPORT
static void OPTQUAL Set_zf( void )  { Options.fctype = OptValue; }
#endif

static void OPTQUAL Set_zt( void ) { Options.stdcall_decoration = OptValue; }
#ifndef __SW_BD
static void OPTQUAL Set_h( void ) {  PrintUsage();  exit(1); }
#endif

#ifdef DEBUG_OUT
static void OPTQUAL Set_dm( void )
{
    int i;
    for ( i = 0; i < MSG_LAST; i++ ) {
        printf("%3u: %s\n", i, MsgGetEx(i) );
    }
}
static void OPTQUAL Set_dt( void )
/********************************/
{
    Options.debug = TRUE;
    ModuleInfo.cref = TRUE; /* enable debug displays */
    DebugMsg(( "debugging output on\n" ));
}
#if FASTPASS
static void OPTQUAL Set_nfp( void )
/*********************************/
{
    Options.nofastpass = TRUE;
    DebugMsg(( "FASTPASS disabled\n" ));
}
#endif
static void OPTQUAL Set_nbp( void )
/*********************************/
{
    Options.nobackpatch = TRUE;
    DebugMsg(( "backpatching disabled\n" ));
}
#endif

struct  cmdloption {
    const char  *name;
    unsigned    value;
    void OPTQUAL (*function)( void );
};

#define optofs( x ) offsetof( struct global_options, x )

/*
 * '#': collect a number
 * '$': collect an identifer[=value] (for -D, -nc, -nd, -nm, -nt)
 * '@': collect a filename
 * '=': collect an optional '='
 * '^': skip spaces before argument (for -D, -Fi, -Fo, -Fw, -I)
 */
static struct cmdloption const cmdl_options[] = {
#ifndef __SW_BD
    { "?",      0,        Set_h },
#endif
#ifdef DEBUG_OUT
    { "af",     optofs( log_all_files ), Set_True },
#endif
#if BIN_SUPPORT
    { "bin",    OFORMAT_BIN | (SFORMAT_NONE << 8), Set_ofmt },
#endif
#if BUILD_TARGET
    { "bt=$",   0,        Set_bt },
#endif
    { "Cp",     0,        Set_Cp },
    { "Cu",     0,        Set_Cu },
    { "Cx",     0,        Set_Cx },
#ifdef DEBUG_OUT
    { "ce",     0,        Set_ce },
#endif
#if COFF_SUPPORT
    { "coff",   OFORMAT_COFF | (SFORMAT_NONE << 8), Set_ofmt },
#endif
    { "c",      0,        Set_c },
#if COFF_SUPPORT && DJGPP_SUPPORT
    { "djgpp",  OFORMAT_COFF | (SFORMAT_DJGPP << 8), Set_ofmt },
#endif
#ifdef DEBUG_OUT
    { "dma",    optofs( display_memalloc ), Set_True },
    { "dm",     0,        Set_dm },
    { "drh",    optofs( dump_reswords_hash ), Set_True },
    { "dr",     optofs( dump_reswords ),    Set_True },
    { "dsh",    optofs( dump_symbols_hash ), Set_True },
    { "ds",     optofs( dump_symbols ),     Set_True },
    { "dt",     0,        Set_dt },
#endif
    { "D^$",    0,        Set_D },
#if ELF_SUPPORT
#if AMD64_SUPPORT
    { "elf64",  OFORMAT_ELF | (SFORMAT_64BIT << 8), Set_ofmt },
#endif
    { "elf",    OFORMAT_ELF | (SFORMAT_NONE << 8), Set_ofmt },
#endif
    { "EP",     0,        Set_EP },
    { "eq",     optofs( no_error_disp ),        Set_True },
    { "e=#",    0,        Set_e },
#if DLLIMPORT
    { "Fd=@",   0,        Set_Fd },
#endif
    { "Fi=^@",  0,        Set_Fi },
    { "Fl=@",   0,        Set_Fl },
    { "Fo=^@",  0,        Set_Fo },
    { "FPi87",  FPO_NO_EMULATION, Set_FPx },
    { "FPi",    FPO_EMULATION,    Set_FPx },
    { "fp0",    P_87,     Set_fp },
    { "fp2",    P_287,    Set_fp },
    { "fp3",    P_387,    Set_fp },
#if 0
    { "fp5",    P_387,    Set_fp },
    { "fp6",    P_387,    Set_fp },
#endif
    { "fpc",    P_NO87,   Set_fp },
    { "Fw=^@",  0,        Set_Fw },
    { "Gc",     LANG_PASCAL,  Set_G },
    { "Gd",     LANG_C,       Set_G },
    { "Gr",     LANG_FASTCALL,Set_G },
    { "Gz",     LANG_STDCALL, Set_G },
#ifndef __SW_BD
    { "h",      0,        Set_h },
#endif
    { "I=^@",   0,        Set_I },
#ifdef DEBUG_OUT
#if FASTPASS
    { "ls",     optofs( print_linestore ), Set_True },
#endif
#endif
    { "mc",     MODEL_COMPACT, Set_m },
    { "mf",     MODEL_FLAT,    Set_m },
    { "mh",     MODEL_HUGE,    Set_m },
    { "ml",     MODEL_LARGE,   Set_m },
    { "mm",     MODEL_MEDIUM,  Set_m },
    { "ms",     MODEL_SMALL,   Set_m },
    { "mt",     MODEL_TINY,    Set_m },
#if BIN_SUPPORT
#if MZ_SUPPORT
    { "mz",     OFORMAT_BIN | (SFORMAT_MZ << 8), Set_ofmt },
#endif
#endif
#ifdef DEBUG_OUT
    { "nbp",    0,                  Set_nbp },
#endif
    { "nc=$",   OPTN_CODE_CLASS,    Set_n },
    { "nd=$",   OPTN_DATA_SEG,      Set_n },
#if FASTPASS && defined(DEBUG_OUT)
    { "nfp",    0,                  Set_nfp },
#endif
    { "nm=$",   OPTN_MODULE_NAME,   Set_n },
    { "nologo", 0,                  Set_nologo },
    { "nt=$",   OPTN_TEXT_SEG,      Set_n },
    { "omf",    OFORMAT_OMF | (SFORMAT_NONE << 8), Set_ofmt },
#if COCTALS
    { "o",      optofs( allow_c_octals ),   Set_True },
#endif
#if PE_SUPPORT
    { "pe",     OFORMAT_BIN | (SFORMAT_PE << 8), Set_ofmt },
#endif
#ifdef DEBUG_OUT
    { "pm=#",   0,        Set_pm },
#endif
    { "q",      0,        Set_q },
    { "Sa",     0,        Set_Sa },
    { "Sf",     optofs( first_pass_listing  ), Set_True },
    { "Sg",     optofs( list_generated_code ), Set_True },
    { "Sn",     optofs( no_symbol_listing   ), Set_True },
    { "Sx",     optofs( listif              ), Set_True },
#ifndef __I86__
    { "Sz",     optofs( no_final_msg_listing), Set_True }, /* v2.13 */
#endif
#if COFF_SUPPORT
    { "safeseh",optofs( safeseh ),        Set_True },
#endif
#ifdef DEBUG_OUT
    { "sp",     optofs( skip_preprocessor ), Set_True },
#endif
    { "WX",     0,        Set_WX },
    { "W=#",    0,        Set_W },
#if AMD64_SUPPORT
    { "win64",  OFORMAT_COFF | (SFORMAT_64BIT << 8), Set_ofmt },
#endif
    { "w",      0,        Set_w },
    { "X",      optofs( ignore_include ), Set_True },
    { "Zd",     0,        Set_Zd },
    { "Zf",     optofs( all_symbols_public ),  Set_True },
    { "Zg",     optofs( masm_compat_gencode ), Set_True },
    { "Zi=#",   CVEX_NORMAL, Set_Zi },
    { "Zm",     optofs( masm51_compat ),      Set_True },
    { "Zne",    optofs( strict_masm_compat ), Set_True },
    { "Zp=#",   0,        Set_Zp },
    { "zcm",    0,        Set_zcm },
    { "zcw",    optofs( no_cdecl_decoration ), Set_True },
#if OWFC_SUPPORT
    { "zf0",    FCT_MSC,     Set_zf },
    { "zf1",    FCT_WATCOMC, Set_zf },
#endif
    { "zlc",    optofs( no_comment_data_in_code_records ), Set_True },
    { "zld",    optofs( no_opt_farcall ),       Set_True },
#if COFF_SUPPORT
    { "zlf",    optofs( no_file_entry ),        Set_True },
    { "zlp",    optofs( no_static_procs ),      Set_True }, /* v2.10: added */
    { "zls",    optofs( no_section_aux_entry ), Set_True },
#endif
    { "Zs",     optofs( syntax_check_only ), Set_True },
    { "zt0",    STDCALL_NONE, Set_zt },
    { "zt1",    STDCALL_HALF, Set_zt },
    { "zt2",    STDCALL_FULL, Set_zt },
    { "Zv8",    optofs( masm8_proc_visibility ), Set_True },
    { "zze",    optofs( no_export_decoration ),  Set_True },
#if COFF_SUPPORT
    { "zzs",    optofs( entry_decorated ),       Set_True },
#endif
//    { NULL,     0,        0 }
};

enum nametype {
    NTYPE_SIMPLE = '0',      /* simple item ( -0..-10 ) */
    NTYPE_FILENAME = '@',    /* filename ( -Fd, -Fi, -Fl, -Fo, -Fw, -I ) */
    NTYPE_IDENTIFIER = '$'   /* (macro) identifier [=value] ( -D, -nc, -nd, -nm, -nt ) */
};

/*  get a filename, identifier, simple item */

static const char *GetNameToken( char *dst, const char *str, int max, enum nametype type )
/****************************************************************************************/
{
    bool equatefound = FALSE;

    DebugMsg(("GetNameToken( %.*s, %u, type=%c ) enter, rspidx=%u\n", max, str, max, type, rspidx ));
    //while( isspace( *str ) ) ++str;  /* no spaces allowed! */
is_quote:
    if( *str == '"' ) {
        ++str;
        for( ; max && *str; max-- ) {
            if ( *str == '"' ) {
                ++str;
                break;
            }
            /* handle the \" case */
            if ( *str == '\\' && *(str+1) == '"' ) {
                ++str;
            }
            *dst++ = *str++;
        }
    } else {
        for( ; max; max-- ) {
            /* v2.10: don't stop for white spaces */
            //if ( *str == NULLC || *str == ' ' || *str == '\t' )
            if ( *str == NULLC )
                break;
            /* v2.10: don't stop for white spaces if type = '@' (filename expected) and true cmdline is parsed
             * v2.19: don't stop for white spaces if type != NTYPE_SIMPLE ; useful for -D option
             */
            //if ( ( *str == ' ' || *str == '\t' ) && ( rspidx || type != NTYPE_FILENAME ) )
            if ( ( *str == ' ' || *str == '\t' ) && ( rspidx || type == NTYPE_SIMPLE ) )
                break;
            if ( type == 0 )
                if ( *str == '-'
#if SWITCHCHAR
                    || *str == '/'
#endif
                   )
                    break;
            if ( *str == '=' && type == '$' && equatefound == FALSE ) {
                equatefound = TRUE;
                *dst++ = *str++;
                if (*str == '"')
                    goto is_quote;
            }
            *dst++ = *str++;
        }
    }
    *dst = NULLC;
    return( str );
}

/*
 * A '@' was found in the cmdline. It's not an environment variable,
 * so check if it is a file and, if yes, read it.
 */

static char *ReadRspFile( const char *name )
/******************************************/
{
    char        *env;
    char        *str;
    FILE        *file;
    int         len;
    char        ch;

    DebugMsg(("ReadRspFile(%s) enter\n"));

    env = NULL;
    file = fopen( name, "rb" );
    if( file == NULL ) {
        /* v2.10: changed to fatal error */
        //EmitErr( CANNOT_OPEN_FILE, name, ErrnoStr() );
        Fatal( CANNOT_OPEN_FILE, name, ErrnoStr() );
        return( NULL );  /* Fatal() shouldn't return, but to be safe ... */
    }
    len = 0;
    if ( fseek( file, 0, SEEK_END ) == 0 ) {
        len = ftell( file );
        rewind( file );
        env = MemAlloc( len + 1 );
#if defined(__GNUC__) /* gcc warns if return value of fread() is "ignored" */
        if ( fread( env, 1, len, file ) )
            ; /* 2.12: semi-colon needs extra line to avoid warning msg */
#else
        fread( env, 1, len, file );
#endif
        env[len] = NULLC;
    }
    fclose( file );
    if ( len == 0)
        return( NULL );
    /* zip through characters changing \r, \n etc into ' ' */
    str = env;
    while( *str ) {
        ch = *str;
        if( ch == '\r' || ch == '\n' ) {
            *str = ' ';
        }
#if HANDLECTRLZ
        if( ch == 0x1A ) {      /* if end of file */
            *str = '\0';        /* - mark end of str */
            break;
        }
#endif
        ++str;
    }
    return( env );
}

/* current cmdline string is done, get the next one! */

static const char *getnextcmdstring( const char **cmdline )
/*********************************************************/
{
    const char **src;
    const char **dst;

    /* something onto the response file stack? */
    if ( rspidx ) {
        rspidx--;
        if ( cmdbuffers[rspidx] )
            MemFree( (void *)cmdbuffers[rspidx] );
        return( cmdsave[rspidx] );
    }
    for ( dst = cmdline, src = cmdline+1; *src; )
        *dst++ = *src++;
    *dst = *src;
    return( *cmdline );
}

static const char *GetNumberArg( const char *p )
/**********************************************/
{
    OptValue = 0;
    for( ;*p >= '0' && *p <= '9'; p++ )
        OptValue = OptValue * 10 + *p - '0';
    return( p );
}

#if SWITCHCHAR
#define IsOptionDelimiter( x ) (x == ' ' || x == '-' || x == '/' || x == NULLC || x == '\t' )
#else
#define IsOptionDelimiter( x ) (x == ' ' || x == '-' || x == NULLC || x == '\t' )
#endif

/* scan option table and if option is known, process it */

static void ProcessOption( const char **cmdline, char *buffer )
/*************************************************************/
{
    int   i;
    int   j;
    const char *p = *cmdline;
    const char *opt;
    //char  c;

    DebugMsg(("ProcessOption(%s)\n", p ));

    /* numeric option (-0, -1, ... ) handled separately since
     * the value can be >= 10.
     */
    if ( *p >= '0' && *p <= '9' ) {
        p = GetNumberArg( p ); /* get number value into OptValue */
        if ( OptValue < sizeof(cpuoption) / sizeof(cpuoption[0]) ) {
            p = GetNameToken( buffer, p, 16, NTYPE_SIMPLE ); /* get optional 'p' */
            *cmdline = p;
            SetCpuCmdline( cpuoption[OptValue], buffer );
            return;
        }
        p = *cmdline; /* v2.11: restore option pointer */
    }

    /* scan option table */

    for( i = 0; i < ( sizeof(cmdl_options) / sizeof(cmdl_options[0]) ); i++ ) {
        //DebugMsg(("ProcessOption(%s): %s\n", p, cmdl_options[i].name ));
        if( *p == *cmdl_options[i].name ) {
            for ( opt = cmdl_options[i].name+1, j = 1 ; isalnum(*opt) && *opt == p[j]; opt++, j++ );
            /* end of option name reached? */
            if ( isalnum(*opt) )
                continue;
            /* ok, option found */
            p += j;
            OptValue = cmdl_options[i].value;
            //DebugMsg(("ProcessOption(%s): Option found\n", p ));
            for( ;; opt++) {
                switch ( *opt ) {
                //case '*': /* don't know what this was supposed to do? */
                case NULLC:
                    if ( !IsOptionDelimiter( *p ) )
                        goto opt_error_exit;
                    *cmdline = p;
                    cmdl_options[i].function();
                    return; /* option processed successfully */
                    break;
                case '#':             /* collect a number */
                    if( *p >= '0' && *p <= '9' )
                        p = GetNumberArg( p );
                    break;
                case '$':      /* collect an identifer+value */
                case '@':      /* collect a filename */
                    OptName = buffer;
                    /* v2.10: spaces in filename now handled inside GetNameToken() */
                    p = GetNameToken( buffer, p, FILENAME_MAX - 1, *opt );
                    break;
                case '=':    /* collect an optional '=' */
                    if ( *p == '=' || *p == '#' )
                        p++;
                    break;
                case '^':  /* skip spaces before argument */
                    while ( isspace(*p) ) p++;
                    if ( *p == NULLC ) {
                        p = getnextcmdstring( cmdline );
                        if ( p == NULL ) {
                            EmitWarn( 1, MISSING_ARGUMENT_FOR_CMDLINE_OPTION );
                            return;
                        }
                    }
                    break;
                default:
                    /* internal error: unknown format of option item! */
                    DebugMsg(( "ProcessOption: unknown option specifier: %s\n", opt ));
                    /**/myassert( 0 );
                    break;
                }
            }
        }
    }
opt_error_exit:
    EmitWarn( 1, INVALID_CMDLINE_OPTION, *cmdline - 1 );
    *cmdline = "";
    return;
}

/* parse cmdline:
 * - process cmdline options
 * - get filename argument
 * - handle (nested) response files
 */

char * EXPQUAL ParseCmdline( const char **cmdline, int *pCntArgs )
/****************************************************************/
{
    int i;
    const char *str = *cmdline;
    char paramfile[FILENAME_MAX];

    for ( i = 0; i < NUM_FILE_TYPES; i++ )
        if ( Options.names[i] != NULL ) {
            MemFree( Options.names[i] );
            Options.names[i] = NULL;
        }

    /* enable next line if debug log is to be active, but -dt cannot be set */
    //Set_dt();

    for( ; str; ) {
        switch( *str ) {
        case ' ':
        case '\t':
            str++;
            break;
        case NULLC:
            str = getnextcmdstring( cmdline );
            break;
        case '-':
#if SWITCHCHAR
        case '/':
#endif
            str++;
            *cmdline = str;
            ProcessOption( cmdline, paramfile );
            (*pCntArgs)++;
            str = *cmdline;
            break;
        case '@':
            if ( rspidx >= MAX_RSP_NESTING ) {
                EmitErr( NESTING_LEVEL_TOO_DEEP );
                *cmdline = "";
                return( NULL );
            }
            str++;
#if 0       /* v2.19: back to 0, obsolete */
            /* v2.06: was 0 in v2.05, now 1 again since it didn't work with quoted names */
            if ( rspidx ) {
                cmdsave[rspidx] = GetNameToken( paramfile, str, sizeof( paramfile ) - 1, NTYPE_FILENAME );
            } else {
                strcpy( paramfile, str ); /* fixme: no overflow check */
                cmdsave[rspidx] = str + strlen(str);
            }
#else
            cmdsave[rspidx] = GetNameToken( paramfile, str, sizeof( paramfile ) - 1, NTYPE_FILENAME );
#endif
            cmdbuffers[rspidx] = NULL;
            str = NULL;

            /* first it's checked if the name is an environment variable;
             * to be removed, since NOT Masm-compatible.
             */
            if ( paramfile[0] )
                str = getenv( paramfile );
            if( str == NULL ) {
                /* name isn't an environment variable - so assume it's a file name */
                str = ReadRspFile( paramfile );
                cmdbuffers[rspidx] = str;
                if ( str == NULL ) {
                    str = cmdsave[rspidx];
                    break;
                }
            }
            rspidx++;
            DebugMsg(("ParseCmdLine: env_var/rsp_file(%s), index=%u\n", paramfile, rspidx ));
            break;

        case '"': /* v2.19: if response file, handle options in double quotes */
            if ( rspidx && ( *(str+1) == '-' || *(str+1) == '/' )) {
                char *p;
                str++;str++;
                for ( p = (char *)str; *p && *p != '"'; p++ )
                    if ( *p == '\\' && *(p+1) != NULLC )
                        p++;
                if ( *p == '"' ) {
                    int i = rspidx;
                    *p = NULLC;
                    *cmdline = str;
                    rspidx = 0; /* don't stop if white spaces detected */
                    ProcessOption( cmdline, paramfile );
                    rspidx = i;
                    (*pCntArgs)++;
                    *p = ' ';
                    str = *cmdline;
                    break;
                }
            }
            /* fall thru */

        default: /* collect  file name */
#if BUILD_TARGET
            set_default_build_target();
#endif
#if 0       /* v2.19: back to 0, obsolete */
            /* v2.06: activated (was removed in v2.05). Needed for quoted filenames */
            if ( rspidx ) {
                str = GetNameToken( paramfile, str, sizeof( paramfile ) - 1, NTYPE_FILENAME );
                get_fname( OPTN_ASM_FN, paramfile );
            } else {
                int len;
                len = strlen( str );
                get_fname( OPTN_ASM_FN, str );
                str += len;
            }
#else
            str = GetNameToken( paramfile, str, sizeof( paramfile ) - 1, NTYPE_FILENAME );
            get_fname( OPTN_ASM_FN, paramfile );
#endif
            DebugMsg(("ParseCmdLine: file=>%s< rest=>%s<\n", Options.names[ASM], str ? str : "NULL" ));
            (*pCntArgs)++;
            *cmdline = str;
            return( Options.names[ASM] );
        }
    }
    *cmdline = str;
    return( NULL );
}

void EXPQUAL CmdlineFini( void )
/******************************/
/* Free resources allocated by cmdline options */
{
    int i;
    DebugMsg(("CmdLineFini enter\n" ));
    for ( i = 0; i < NUM_FILE_TYPES; i++ ) {
        if ( DefaultDir[i] != NULL ) {
            MemFree( DefaultDir[i] );
            DefaultDir[i] = NULL;
        }
    }
    for ( i = 0; i < OPTN_LAST; i++ )
        if ( Options.names[i] != NULL ) {
            MemFree( Options.names[i] );
            Options.names[i] = NULL;
        }
    for ( i = 0; i < OPTQ_LAST; i++ ) {
        struct qitem *p;
        struct qitem *q;
        for ( q = Options.queues[i]; q; ) {
            p = q->next;
            MemFree( q );
            q = p;
        }
        Options.queues[i] = NULL;
    }
    DebugMsg(("CmdLineFini exit\n" ));
    return;
}

