# ---------------------------------------------------------------------------
# 
# File:    PARSE_CL.pm
#
# Copyright (c) Programming Research, 2004.
#
# Written: C.Waddingham 22-June-03
#
# Version: 1.30
#
# Description: 
#          Provides functions for parsing QAC/PP command-line options.
#
# History: 
#   Date      Version By   Comment
#   --------  ------- ---- ----------------------------------------------
#   04-01-12  1.30    CW   - Reverted to GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS()
#                            in LOAD_OPTION_FILE()
#   21-03-11  1.29    CW   - cargs handled as a +cargs option.
#                          - HANDLE_ARG_IN_CTARGS_SECTION(),
#                            LOAD_OPTION_FILE() and
#                            GET_ALL_OPTIONS_IN()
#                            updated to use SPLIT_COMMAND_LINE_STRING().
#                          - OPTION_HAS_PATH_PARAMETER updated to use ! marker.
#                          - -rem handling removed.
#                          - --inline etc handled in -cargs section.
#   08-06-09  1.28    CW   CR13174 completed for -si and -i.
#   22-04-09  1.27    CW   CR13174 Edits applied to prevent addition of trailing \ for 
#                          -fi and -q.
#   07-02-08  1.26    CW   CR12691. Enable RP/EV handling.
#   15-03-07  1.25    CW   CR12135. Edit 1.23 reverted and change made to qaw.pl instead.
#   08-03-07  1.24    CW   Default afe arg string extended to include CPP etc.
#   26-02-07  1.23    CW   Quotes supplied (as command line arg) are now (re-)escaped.
#   24-10-06  1.22    CW   Arg parsing of -sat option changed to allow --Debug etc.
#                          CR11553. Only warn on unreconised file if
#                            $ctarg_encountered (from first pass) is false;
#                            Downgrade messages from Error to Warning.
#   28-03-06  1.21    CW   Changed to increase allowable filename character set.
#   18-10-05  1.20    CW   -afe and -dafe added.
#   19-09-05  1.19    CW   SEARCH_OPTION_SET_FOR_LAST_INSTANCE corrected.
#   24-08-05  1.18    CW   Module variable notation tidied.
#   06-06-05  1.17    CW   File types: .i .I .ipp .ixx .i++ added.
#                          Output filenames in mixed sort order.
#   09-05-05  1.16    CW   -op handling improved. 
#                          Removed -op in WRITE_[PURE_]SETTINGS_FILE.
#                          Illegal alias handling improved.
#   20-04-05  1.15    CW   WRITE_PURE_SETTINGS_FILE added.
#   25-03-05  1.14    CW   DIEX used.
#   24-03-05  1.13    CW   Reverted changes to FULL_OPTION.
#   16-03-05  1.12    CW   Added SEARCH_OPTION_SET_FOR_LAST_INSTANCE
#                          $ar_options_list added to INITIALISE.
#   20-02-05  1.11    CW   -targs implemented.
#                          -op handling brought in-line with PRJDSP.
#   12-01-05  1.10    CW   Handling for Cygwin added.
#   02-11-04  1.9     CW   Added: -mrtnd, -maxrecursivetemplatenestingdepth.
#                                 -ss, -sr, -mll
#   14-08-04  1.8     CW   Split out of QAW and merged to form 1 master.
#   10-05-04  1.7     CW   Brought in line with QAW.
#   30-04-04  1.6     CW   FULL_OPTION() updated.
#   14-04-04  1.5     CW   Brought in line with QAW.
#   08-04-04  1.4     CW   Brought in line with QAW.
#   30-03-04  1.3     CW   Brought in line with QAW.
#   01-02-04  1.2     CW   Brought in line with QAW.
#   19-11-03  1.1     CW   Following options added:
#                            -accd -allowconstconvdeduction,
#                            -atncrb -allowtempnonconstrefbind,
#                            -duc99dilt -dontusec99decintlittypes,
#                            -sig -stdisglobal.
#   26-06-03  1.0     CW   Initial version taken from qaw
# ---------------------------------------------------------------------------

package PARSE_CL;

require Exporter;
our @ISA = qw( Exporter );

#----------------------------------------------------------------------------

BEGIN
{
   use integer;
   use strict;

   require GEN;

   # Return code constants:
   #
   our $die_config_error = 10;

   # @all_options gives the list of options that can be recognised.
   # It must be set by calling INITIALISE.
   #
   our @all_options;

   # Set by INITIALISE.
   #
   our %options_with_path_parameter;

   # %ENV has case insensitive keys, hence create own hash for -alias values.
   #
   our %alii = ();

   # For Windows, need to be able to accept options that are prefixed
   # with / as opposed to -.  i.e. allow /i and -i.
   #

   our $flag_regex;

   if ( GEN::PLATFORM_IS_WINDOWS() )
   {
      $flag_regex = "[-\/]";
   }
   else
   {
      $flag_regex = "-";
   }
   # Default settings for -afe and -dafe
   #
   our $default_afe_arg;
   our $default_dafe_arg;

   our $root_path_static = undef;

   our $zzz_marker = "-zzz_marker";
}

#----------------------------------------------------------------------------

sub SET_ROOT_PATH
{
   # Usage: SET_ROOT_PATH( $path );
   #
   my $path = shift;

   if ( ! defined( $path ) )
   {
      $QPARSE::root_path_static = undef;
   }
   else
   {
      $QPARSE::root_path_static = GEN::APPLY_FINAL_SEP( $path );
   }
}

#----------------------------------------------------------------------------

sub CLEAR_ROOT_PATH
{
   # Usage: CLEAR_ROOT_PATH();
   #
   SET_ROOT_PATH( undef );
}

#----------------------------------------------------------------------------

sub GET_ROOT_PATH
{
   # Usage: $root_path = GET_ROOT_PATH();
   #
   return $QPARSE::root_path_static;
}

#----------------------------------------------------------------------------

sub INITIALISE
{
   # Usage: PARSE_CL::INITIALISE( $options_file_name, $ar_options_list );
   #
   # NB parameters are mutually exclusive and are acted on as follows:
   #
   # $options_file_name,  $ar_options_list,  Action
   # ---------------------------------------------------------------------
   # ""                   []                 Load from default file.
   # ""                   [ <values> ]       Use $ar_options_list values.
   # <value>              []                 Load from specified file.
   # <value>              [ <values> ]       ERROR cannot specify both.
   #
   # $ar_options_list contains the list of option specifiers to be used.
   #
   # $options_file_name specifies where @all_options is to be read from.
   # $options_file_name may be "", in which case $0.options is used.
   # ($0 is the name of the currently running program, e.g. qaw.exe)
   #
   # @all_options lists all allowable QAC/PP options.  If an option is supplied
   # that is not in this list GET_OPT will return an error message saying
   # that opt is illegal.
   #
   # Order of options in @all_options is important.  @all_options must
   # have the property that longer names are found before any name that
   # is a truncation of the name.
   #
   # The + or - prefix indicates whether the option is a simple option
   # like -met+|- ie it has no parameter that may be in the next
   # comamnd line argument, or options that do have an argument.
   # Options with a + prefix have parameters.
   # Options with a - prefix only have a +|- parameter.
   # Options with a ! prefix have a parameter that is a path.
   #
   # Note the following args are consumed within this package, i.e. they
   # are not returned:
   #    -alias
   #    -cargs
   #    -sat
   #    -setargtranslations
   #    -targs
   #
   # Prior to QAW version 2.0, the following hard-coded settings were used:
   #
   #   our @all_options = (
   #      "-accd",
   #      "-allowconstconvdeduction",
   #      "-atncrb",
   #      "-allowtempnonconstrefbind",
   #      "+alias",
   #      "+align",
   #      "-arithrsh",
   #      "-ar",
   #      "+a",
   #      "-bitsigned",
   #      "-bits",
   #      "+calling",
   #      "+callpack",
   #      "-cargs",
   #      "+canal",
   #      "+cmaf",
   #      "+comment",
   #      "+co",
   #      "-createpph",
   #      "-cpph",
   #      "+crep",
   #      "-dpft",
   #      "-delayparsefntemplates",
   #      "-debug",
   #      "-deb",
   #      "-disp",
   #      "-dontusec99decintlittypes",
   #      "-duc99dilt",
   #      "+d",
   #      "+echo",
   #      "-ehtml",
   #      "+encoding",
   #      "+en",
   #      "-etxt",
   #      "+exec",
   #      "-expandfilenames",
   #      "-ef",
   #      "+extensions",
   #      "+ex",
   #      "+e",
   #      "+fdisp",
   #      "+forceinclude",
   #      "+file",
   #      "+fi",
   #      "+forgetall",
   #      "+forget",
   #      "+format",
   #      "-foldplainchar",
   #      "-fpc",
   #      "-glog",
   #      "+glob",
   #      "-hdrsuppress",
   #      "-hdr",
   #      "-hidemacrowarnings",
   #      "-hmw",
   #      "-hiddenwarnings",
   #      "-hw",
   #      "-html",
   #      "+il",
   #      "+indentlevel",
   #      "-ifn",
   #      "-introducefriendnames",
   #      "+it",
   #      "+intrinsictype",
   #      "+i",
   #      "+k+r",
   #      "+layoutfile",
   #      "+lf",
   #      "+list",
   #      "-lih",
   #      "-layoutignoreheaders",
   #      "+layoutindentwidth",
   #      "+liw",
   #      "-line",
   #      "+logmsgs",
   #      "+logerrs",
   #      "-luhi",
   #      "-layoutusehangingindent",
   #      "+libraryusagefile",
   #      "+luf",
   #      "-l",
   #      "+maxcount",
   #      "+maxerrors",
   #      "+maxerr",
   #      "+maxrecursivetemplatenestingdepth",
   #      "+max",
   ##      "+msgref",
   #      "-metrics",
   #      "-met",
   #      "-messagesonly",
   #      "+mll",
   #      "+mode",
   #      "+mrtnd",
   #      "-m",
   #      "+namelength",
   #      "+namerulefile",
   #      "+na",
   #      "+nomsg",
   #      "-nosort",
   #      "+no",
   #      "+n",
   #      "+only",
   #      "-onelineonly",
   #      "-one",
   #      "+op",
   #      "+outputpath",
   #      "-overwrite",
   #      "-over",
   #      "+o",
   #      "-pdspd",
   #      "+pdsp",
   #      "-ppfilename",
   #      "-ppf",
   #      "+pphversion",
   #      "+pphv",
   #      "-pplist",
   #      "-ppl",
   #      "-ppmetrics",
   #      "-ppm",
   #      "-pponly",
   #      "-ppo",
   #      "-propagatetemplateparameters",
   #      "-ptp",
   #      "-plog",
   #      "+quiet",
   #      "+q",
   #      "+remark",
   #      "+rem",
   #      "-references",
   #      "-ref",
   #      "-sapf",
   #      "+sat",
   #      "-sdep",
   #      "-searchdependentbaseclasses",
   #      "+systemdefine",
   #      "+sd",
   #      "+setargtranslation",
   #      "-settings",
   #      "-set",
   #      "+systeminclude",
   #      "+size",
   #      "-sig",
   #      "+si",
   #      "-slashwhite",
   #      "-sl",
   #      "+spragma",
   #      "+skippragma",
   #      "-sr",
   #      "-ss",
   #      "-stoponfail",
   #      "+style",
   #      "+sty",
   #      "-stdisglobal",
   #      "-standard",
   ##      "-strict",
   ##      "-str",
   #      "-st",
   #      "-summary",
   #      "-summ",
   #      "+suppresslvl",
   #      "+suppressioncategoryfilter",
   #      "+suppressioncategory",
   #      "+suppressiondescriptiontext",
   #      "+suppressionoutpath",
   #      "+su",
   #      "+s",
   #      "-tablesummary",
   #      "+tabstop",
   #      "+tab",
   #      "-targs",
   #      "-textonly",
   #      "-text",
   #      "+threshold",
   #      "+thresh",
   #      "-total",
   #      "-trace",
   #      "-tsumm",
   #      "+t",
   #      "-unsignedchar",
   #      "+up",
   #      "+usrpath",
   #      "+usrfile",
   #      "+usr",
   #      "-u",
   #      "-version",
   #      "-ver",
   #      "+via",
   #      "-view",
   #      "+wc",
   #      "+warncall",
   #      "-xcase",
   #      "-xc",
   #      "+xnamelength",
   #      "+xn" );
   #
   
   my $options_file_name = shift;
   my $ar_options_list   = shift;

   if ( ( $options_file_name ne "" ) &&
        ( $#$ar_options_list != -1 )
      )
   {
      GEN::DIEX( ": PARSE_CL:INITIALISE Illegal parameters",
                 $PARSE_CL::die_config_error );
   }

   if ( $#$ar_options_list != -1 )
   {
      # Here when options list is passed directly
      #
      @PARSE_CL::all_options = @$ar_options_list;
   }
   else
   {
      # Here when options list is loaded from file.
      #
      if ( $options_file_name eq "" )
      {
         # Here when default file is to be used.
         # NB default file is <parent program full file name>.options
         #
         my $this_program_full_path = GEN::WHICH( $0 );
         $this_program_full_path = GEN::NORMALISE_CYGWIN_PATH( $this_program_full_path );
   
         $options_file_name = "$this_program_full_path.options";
      }
   
      @PARSE_CL::all_options = GEN::LOAD_FILE_AS_ARRAY( $options_file_name );
   }

   # Remove comments.  i.e. lines starting with *
   # And all lines containing only white-space.
   #
   @PARSE_CL::all_options = grep( ! /^(\*.*|\s*)$/o, @PARSE_CL::all_options );

   # Remove eols.  This deals with both Unix and DOS eolns.
   #
   foreach my $option ( @PARSE_CL::all_options )
   {
      $option =~ s/\s+$//go;
   }

   # Add special marker option
   #
   push @PARSE_CL::all_options, "+" . GEN::RIGHT2( $PARSE_CL::zzz_marker, 1 );

   # Set up options_with_path_parameter
   #
   for my $opt ( @PARSE_CL::all_options )
   {
      if ( GEN::LEFT( $opt, 1 ) eq "!" )
      {
         $PARSE_CL::options_with_path_parameter{ GEN::RIGHT2( $opt, 1 ) } = "any";
      }
   }

   # Here with @PARSE_CL::all_options set up.

   # Default settings for -afe and -dafe
   #
   $PARSE_CL::default_afe_arg  = ".c.C.cc.CC.cpp.CPP.cxx.CXX.c++.C++.h.H.hpp.HPP.hxx.HXX.h++.H++.i.I.ipp.IPP.ixx.IXX.i++.I++";
   $PARSE_CL::default_dafe_arg = "+";
}

#== Simple I/F for post-analysis etc. =======================================

sub PARSE_COMMAND_LINE
{
   # Usage:( $ar_parse_options,
   #         $ar_parse_file_names,
   #         $ar_parse_outputpaths ) =
   #           PARSE_COMMAND_LINE( $ar_arg_list );
   #
   # This is the 'simple' interface used by post-analysis programs etc.
   # NB In parameter changed to reference at version 1.8.
   #
   # Relies on -op being set before 1st file arg.  Does not fail if last
   # -op trails after last file arg, despite the fact that the -op has no
   # effect and is ignored.
   # 
   # Does not take QAC[PP]OUTPATH into account, therefore caller should
   # prefix -op QAC[PP]OUTPATH onto $ar_arg_list.
   #
   my $ar_arg_list = shift;                    # - List of options, filenames etc.
                                               #   to parse.
                                               
   my $ar_pass_1_tokens          = [];         # - All tokens returned by pass 1.
                                               #   These are the input for pass 2.
   my $ar_parse_cargs_parameters = [];         # - List of all items in $ar_arg_list
                                               #   after -cargs.
   my $hr_arg_translations       = {};         # - Hash of all specified parameter
                                               #   translations.  i.e. those 
                                               #   specified by -sat.
   my $in_ctargs_section = 0;  # false         # - False since at beginning of args
                                               #   and hence have not yet reached
                                               #   -cargs / -targs.
                                               #   Otherwise set to:
                                               #   <IN CARGS> or <IN TARGS>
   my $carg_compiler_name = "";                # - On encountering -cargs the next
                                               #   non-option token value is assigned
                                               #   to $carg_compiler_name.
                                               #   This should be the compiler name,
                                               #   e.g. gcc
   my $this_file_name = "";                    # - Name of -via or -list file being
                                               #   processed.  Set to "" for outer
                                               #   i.e. commmand-line level.
   my $hr_included_files         = {};         # - Hash of files included at higher
                                               #   levels.
                                               #
   my $afe_arg = $PARSE_CL::default_afe_arg;   # - String of allowable file extensions.
                                               #   Default setting for -afe option.
                                               #
   my $dafe_arg = $PARSE_CL::default_dafe_arg; # - Default setting for -dafe option.

   ( $ar_pass_1_tokens,
     $ar_parse_cargs_parameters,
     $hr_arg_translations,
     $in_ctargs_section,
     $carg_compiler_name,
     $afe_arg,
     $dafe_arg ) =
         PARSE_ARGS_RECURSIVE_PASS_1
         ( $ar_arg_list,
           $ar_pass_1_tokens,
           $ar_parse_cargs_parameters,
           $hr_arg_translations,
           $in_ctargs_section,
           $carg_compiler_name,
           $this_file_name,
           $hr_included_files,
           $afe_arg,
           $dafe_arg );

   #  $ar_parse_options            # - List of all qac/pp settings in
   #                               #   $ar_arg_list / $ar_pass_1_tokens
   #  $ar_parse_file_names         # - List of all files for analysis
   #  $ar_parse_outputpaths        # - List of op for each file in 
   #                               #   $ar_parse_file_names
   #  $no_op_before_first_file     # - True if no -op(s) before first
   #                               #   file arg in $ar_arg_list.
   #  $op_trails_after_file_args   # - True if last -op is after last
   #                               #   file arg in $ar_arg_list.
   #
   my $qa_outpath = "";

   my ( $ar_parse_options,
        $ar_parse_file_names,
        $ar_parse_outputpaths,
        $no_op_before_first_file,
        $op_trails_after_file_args ) =
           PARSE_ARGS_PASS_2( $ar_pass_1_tokens, $qa_outpath, $afe_arg, $dafe_arg );

   return ( $ar_parse_options,
            $ar_parse_file_names,
            $ar_parse_outputpaths );
}

# == QAW I/F ================================================================

sub QAW_PARSE_COMMAND_LINE
{
   # Usage: ( $ar_parse_options,
   #          $ar_parse_file_names,
   #          $ar_parse_outputpaths,
   #          $ar_parse_cargs_parameters ) =
   #        QAW_PARSE_COMMAND_LINE( $ar_arg_list, $qa_program, $qa_outpath );
   #
   my $ar_arg_list = shift;                    # - List of options, filenames etc.
                                               #   to parse.
   my $qa_program  = shift;                    # - QAC or QACPP.
   my $qa_outpath  = shift;                    # - Value of QACOUTPATH or
                                               #   QACPPOUTPATH, or "" if not set.
                                               
   my $ar_pass_1_tokens          = [];         # - All tokens returned by pass 1.
                                               #   These are the input for pass 2.
   my $ar_parse_cargs_parameters = [];         # - List of all items in $ar_arg_list
                                               #   after -cargs.
   my $hr_arg_translations       = {};         # - Hash of all specified parameter
                                               #   translations.  i.e. those 
                                               #   specified by -sat.
   my $in_ctargs_section = 0;  # false         # - False since at beginning of args
                                               #   and hence have not yet reached
                                               #   -cargs / -targs.
                                               #   Otherwise set to:
                                               #   <IN CARGS> or <IN TARGS>
   my $carg_compiler_name = "";                # - On encountering -cargs the next
                                               #   non-option token value is assigned
                                               #   to $carg_compiler_name.
                                               #   This should be the compiler name,
                                               #   e.g. gcc
   my $this_file_name = "";                    # - Name of -via or -list file being
                                               #   processed.  Set to "" for outer
                                               #   i.e. commmand-line level.
   my $hr_included_files         = {};         # - Hash of files included at higher
                                               #   levels.
                                               #
   my $afe_arg = $PARSE_CL::default_afe_arg;   # - String of allowable file extensions.
                                               #   Default setting for -afe option.
                                               #
   my $dafe_arg = $PARSE_CL::default_dafe_arg; # - Default setting for -dafe option.

   ( $ar_pass_1_tokens,
     $ar_parse_cargs_parameters,
     $hr_arg_translations,
     $in_ctargs_section,
     $carg_compiler_name,
     $afe_arg,
     $dafe_arg ) =
         PARSE_ARGS_RECURSIVE_PASS_1
         ( $ar_arg_list,
           $ar_pass_1_tokens,
           $ar_parse_cargs_parameters,
           $hr_arg_translations,
           $in_ctargs_section,
           $carg_compiler_name,
           $this_file_name,
           $hr_included_files,
           $afe_arg,
           $dafe_arg );

   #  $ar_parse_options            # - List of all qac/pp settings in
   #                               #   $ar_arg_list / $ar_pass_1_tokens
   #  $ar_parse_file_names         # - List of all files for analysis
   #  $ar_parse_outputpaths        # - List of op for each file in 
   #                               #   $ar_parse_file_names
   #  $no_op_before_first_file     # - True if no -op(s) before first
   #                               #   file arg in $ar_arg_list.
   #  $op_trails_after_file_args   # - True if last -op is after last
   #                               #   file arg in $ar_arg_list.
   #

   my ( $ar_parse_options,
        $ar_parse_file_names,
        $ar_parse_outputpaths,
        $no_op_before_first_file,
        $op_trails_after_file_args ) =
           PARSE_ARGS_PASS_2( $ar_pass_1_tokens, $qa_outpath, $afe_arg, $dafe_arg );

   # If files have been read, then check for illegal -op cases:
   #
   if ( $#$ar_parse_file_names != -1 )
   {
      # Handle case where -op after last file arg in $ar_arg_list.
      #
      if ( $op_trails_after_file_args )
      {
         GEN::DIEX( ": ERROR: Illegal trailing -op specification(s). " .
                    "Ensure -op specifications precede relevant files.",
                    $PARSE_CL::die_config_error );
      }
   
      # Handle case where -op does not precede first file arg in $ar_arg_list.
      #
      if ( $no_op_before_first_file )
      {
         if ( $qa_outpath eq "" )
         {
            GEN::DIEX( ": -op not specified and " . uc( $qa_program ) . "OUTPATH not set.",
                       $PARSE_CL::die_config_error );
         }
         else
         {
            LOG_WARNING( "No -op specification before first file, " . uc( $qa_program ) .
                         "OUTPATH used (i.e. \"$qa_outpath\")." );
         }
   
         $ar_parse_options = [ FULL_OPTION( "-op", $qa_outpath ), @$ar_parse_options ];
      }
   }

   return ( $ar_parse_options,
            $ar_parse_file_names,
            $ar_parse_outputpaths,
            $ar_parse_cargs_parameters );
}

# ===========================================================================

sub PARSE_ARGS_RECURSIVE_PASS_1
{
   # Usage:
   #   ( $ar_pass_1_tokens,
   #     $ar_parse_cargs_parameters,
   #     $hr_arg_translations,
   #     $in_ctargs_section,
   #     $carg_compiler_name,
   #     $afe_arg,
   #     $dafe_arg ) =
   #         PARSE_ARGS_RECURSIVE_PASS_1
   #         ( $ar_arg_list,
   #           $ar_pass_1_tokens,
   #           $ar_parse_cargs_parameters,
   #           $hr_arg_translations,
   #           $in_ctargs_section,
   #           $carg_compiler_name,
   #           $this_file_name,
   #           $hr_included_files,
   #           $afe_arg,
   #           $dafe_arg );
   #
   # Parses command line arguments separating them into 'options', 'filenames',
   # 'output paths' on a one per file basis, and 'cargs arguments' / 'targs arguments'.
   # Dynamically evaluate any -alias settings as args are read.
   # NB A -alias option has scope to the end of the current file and any -via
   # included files.  NB Also that a -alias cannot be redefined whilst in scope.
   #
   # This sub works in two passes.
   # 1. Read all tokens on command line,
   #       - expand environment and -alias vars.
   #       - expand all -via and -list.
   #       - evaluate all -sat.  i.e. build $hr_arg_translations and
   #         do substitutions on tokens after -cargs or -targs.
   #       - evaluate file globs.
   #       - build $ar_parse_cargs_parameters.
   #       - build $ar_parse_rem_lines.
   #       - collect all resulting tokens into $ar_pass_1_tokens.
   # 2. Parse $ar_parse_1_args and build:
   #       - $ar_parse_options.
   #       - $ar_parse_file_names.
   #       - $ar_parse_outputpaths.
   #
   # $ar_pass_1_tokens output contains all of its parameters within each item
   # as a single arg.
   # Quotes are removed on output but embedded spaces are preserved.
   #
   my $ar_arguments              = shift;
   my $ar_pass_1_tokens          = shift;
   my $ar_parse_cargs_parameters = shift;
   my $hr_arg_translations       = shift;
   my $in_ctargs_section         = shift;
   my $carg_compiler_name        = shift;
   my $this_file_name            = shift;
   my $hr_included_files         = shift;
   my $afe_arg                   = shift;
   my $dafe_arg                  = shift;

   my $terminate = 0; # false    # - Indicates error in $ar_arguments when set.

   my $this_file_ref;

   if ( $this_file_name ne "" )
   {
      $this_file_ref = GEN::FILE_REF( $this_file_name );

      if ( exists( $$hr_included_files{ $this_file_ref } ) )
      {
         GEN::DIEX( ": Recursive include of file \"$this_file_name\"",
                    $PARSE_CL::die_config_error );
      }
   }
   else
   {
      $this_file_ref = "";
   }

   #-- Pass 1 ---------------------------------------------------------------
   #

   my $option_immediately_after_cargs_found = 0;   #false

   my $i_arg = 0;

   IARG:
   while ( $i_arg <= $#$ar_arguments )
   {
      # Expand any alii first
      #
      $$ar_arguments[ $i_arg ] = EXPAND_VARS( $$ar_arguments[ $i_arg ] );
   
      my $arg = $$ar_arguments[ $i_arg ];

      if ( $arg eq "" )
      {
         ++$i_arg;

         next IARG;
      }

      my $first_ch = substr( $arg, 0, 1 );

      # Test for -cargs / -targs already encountered.
      #
      if ( $in_ctargs_section )
      {
         if ( $in_ctargs_section eq "<IN CARGS>" )
         {
            # Save arg to carg list.
            #
            push @$ar_parse_cargs_parameters, $arg;
         }

         # Check whether $arg is an option, and if so whether it can be -sat
         # translated.
         #   1. If $arg can be -sat translated then save translated token(s) in
         #      $ar_pass_1_tokens for handling in pass 2.
         #   2. Otherwise: No translation of $arg => nothing written to
         #      $ar_pass_1_tokens for this token.
         #      $arg is a compiler opt which is not to be translated (since no
         #      -sat is specified for this).  This $arg may also be a QAC/PP
         #      option, however cannot act on this (e.g. give a warning to the
         #      user informing him that the option will be ignored for QAC/PP
         #      analysis purposes).  This is because the arg may be a valid
         #      compiler arg (e.g. -I).
         #
         if ( $first_ch =~ /$PARSE_CL::flag_regex/ )
         {
            # Here when $arg is an option, since it starts with '-' or '/'.
            #

            # Try for translation.  NB -sat options are not allowed within
            # the cargs / targs section.
            #
            my $lc_arg_nmem = lc( substr( $arg, 1 ) );
   
            if ( $lc_arg_nmem =~ /^(sat|setargtranslation)/ )
            {
               # Here with -sat option in -cargs / -targs section (which is illegal)
               #
               if ( $in_ctargs_section eq "<IN CARGS>" )
               {
                  LOG_ERROR( "\"$arg\" option not allowed after -cargs" );
               }
               if ( $in_ctargs_section eq "<IN TARGS>" )
               {
                  LOG_ERROR( "\"$arg\" option not allowed after -targs" );
               }

               $terminate = 1;   # true

               # Just skip this one token.  Hence may get spurious
               # warnings on next token.
               #
               ++$i_arg;

               next IARG;
            }
            else
            {
               # Here with an option in the -cargs / -targs section.
               # Check whether we have a -sat translation for this and,
               # if so, keep option for pass 2 inclusion in $ar_parse_options.
               #
               my ( $dn_arg, $ar_tokens ) = HANDLE_ARG_IN_CTARGS_SECTION
                         ( $arg, $hr_arg_translations );

               if ( $#$ar_tokens >= 0 )
               {
                  push @$ar_pass_1_tokens, @$ar_tokens;
               }

               # NB rely on $dn_arg being +ve here
               #
               $i_arg += $dn_arg;
              
               next IARG;
            }
         }
      }

      # Here with $arg tokens which are options in the -cargs / -targs section,
      # handled above.  Have also handled the first non-option token after -cargs,
      # i.e. the compiler name (which must immediately follow the -cargs option).
      #
      # Hence $arg at this point is either:
      # 1. A file name or project spec before the -cargs / -targs section.
      # 2. A QAC/PP option or file name or project spec in the section before
      #    -cargs / -targs.
      #
      if ( $first_ch =~ /$PARSE_CL::flag_regex/ )
      {
         # Here when $arg is an option which can only be a QAC/PP option as all
         # options in the -cargs /-targs section have already been processed.
         #
         my ( $opt, $param, $dn_arg, $error ) = GET_OPT( $i_arg, $ar_arguments );

         # -ve $dn_arg implies that argument is illegal.
         #
         if ( $dn_arg < 0 )
         {
            # Here with an option which is before the -cargs / -targs section
            # (since all options in the -cargs / -targs section were handled above).
            # Here options must be QAC/PP options.  Current option is not a legal
            # QAC/PP option, hence fail.
            #
            LOG_ERROR( $error );

            $terminate = 1;   # true

            $i_arg += abs( $dn_arg );
            
            next IARG;
         }

         my $lc_opt = lc( $opt );

         $param = EXPAND_VARS( $param );

         if ( OPTION_HAS_PATH_PARAMETER( $lc_opt ) )
         {
            # Expand param of option
            #
            $param = EXPAND_WINDOWS_ENV_VARS( $param );
         }

         if ( $lc_opt =~ /^${PARSE_CL::flag_regex}afe$/ )
         {
            $afe_arg = $param;

            # Save the -afe option as QAW needs to allow this option
            # to be propagated for project file based analysis.
            #
            push @$ar_pass_1_tokens, FULL_OPTION( $opt, $param );
         }
         elsif ( $lc_opt =~ /^${PARSE_CL::flag_regex}dafe$/ )
         {
            $dafe_arg = $param;

            # Save the -dafe option as QAW needs to allow this option
            # to be propagated for project file based analysis.
            #
            push @$ar_pass_1_tokens, FULL_OPTION( $opt, $param );
         }
         elsif ( $lc_opt =~ /^${PARSE_CL::flag_regex}cargs$/ )
         {
            $in_ctargs_section = "<IN CARGS>";   # true

            push @$ar_parse_cargs_parameters, $param;

            push @$ar_pass_1_tokens, FULL_OPTION( "-cargs", "" );

            if ( $param =~ /^${PARSE_CL::flag_regex}/ )
            {
               LOG_ERROR( "\"$param\" option not allowed immediately after -cargs." .
                          " Expect the compiler name." );

               # Skip -cargs and its parameter token.
               # May get spurious warnings on next token.
               #
               $i_arg += 2;

               next IARG;
            }
         }
         elsif ( $lc_opt =~ /^${PARSE_CL::flag_regex}targs$/ )
         {
            $in_ctargs_section = "<IN TARGS>";   # true

            push @$ar_pass_1_tokens, FULL_OPTION( "-targs", "" );
         }
         elsif ( $lc_opt =~ /^$PARSE_CL::flag_regex(via|list)$/ )
         {
            # Here with -via include file or -list list of files
            #
            # Can treat -via and -list in same way as both give a list
            # of command line settings:  -via gives options, 
            # -list gives files and -op.
            #
            # NB -via and -list parameter can be a wildcard filename.
            #

            # At this stage $param may be a wildcarded filename, full explicit
            # filename, or may be a relative path to be prefixed with the root path
            # of the project file (if defined).
            # 
            if ( $param !~ /[\*\?]/o )
            {
               # Here when param not wildcarded
               #
               $param = RESOLVE_FOR_POSSIBLE_ROOT_PATH_PREFIX( $param );
            }

            my ( $ar_filenames, $error ) = GEN::GET_GLOB_FILE_LIST( $param );

            if ( $error ne "" )
            {
               LOG_WARNING( $error );
            }
            else
            {
               # Ensure that order of any personality files is: .p_s, .p_a, .p_c
               #

               my @p_s  = grep( /\.p_s$/oi, @$ar_filenames );
               my @p_a  = grep( /\.p_a$/oi, @$ar_filenames );
               my @p_c  = grep( /\.p_c$/oi, @$ar_filenames );
               my @rest = grep( !/\.p_[sac]$/oi, @$ar_filenames );

               $ar_filenames = [ @p_s, @p_a, @p_c, @rest ];

               foreach my $filename ( @$ar_filenames )
               {
                  my ( $ar_args, $term ) = 
                     LOAD_OPTION_FILE( $filename, $terminate, $in_ctargs_section );

                  $terminate |= $term;

                  # In order to allow glob filespecs on the command line (e.g. *.c)
                  # must strip any comments from $ar_args now.
                  # NB Comments are lines beginning * in column 1.
                  #
                  @$ar_args = grep( !/^\*/o, @$ar_args );
                  
                  # %inc_files is kept as a recursion guard.        
                  # Create local list of included file names.
                  #
                  my %inc_files = %$hr_included_files;
                  
                  # Register the filename at this call of PARSE_ARGS_RECURSIVE_PASS_1().
                  #
                  $inc_files{ $this_file_ref } = "anything";
                  
                  # $incl_file_name is the name of the file being included (by -via|-list)
                  #
                  my $incl_file_name = $filename;
                  
                  # Handle the options / file names in the included file.
                  #
                  ( $ar_pass_1_tokens,
                    $ar_parse_cargs_parameters,
                    $hr_arg_translations,
                    $in_ctargs_section,
                    $carg_compiler_name,
                    $afe_arg,
                    $dafe_arg ) =
                           PARSE_ARGS_RECURSIVE_PASS_1
                           ( $ar_args,
                             $ar_pass_1_tokens,
                             $ar_parse_cargs_parameters,
                             $hr_arg_translations,
                             $in_ctargs_section,
                             $carg_compiler_name,
                             $incl_file_name,
                             \%inc_files,
                             $afe_arg,
                             $dafe_arg );
               }
            }
         }
         elsif ( $lc_opt =~ /^$PARSE_CL::flag_regex(sat|setargtranslation)$/ )
         {
            # The -sat options are not returned by [QAW_]PARSE_COMMAND_LINE()
            # as they have direct meaning for other options in the -cargs / -targs
            # command set only.
            #
            # Typical setting is -sat "MD=-d _MT -d _DLL"
            #
            # This has the effect of causing any '-MD' or '/MD' (Windows only)
            # to be replaced with '-d _MT' and '-d _DLL' in the returned
            # $ar_parse_options list.
            #
            # Rules for -sat are:
            # 1. A -sat rhs may specify any number of QAC/PP options, e.g.
            #    -sat "MD=-d _MT -d _DLL"
            # 2. -sat rhs may have embedded quotes provided they are escaped
            #    with '\' 
            # 3. -sat lhs cannot start with '-' or '/'
            # 4. -sat definitions are only instantiated once.  Recursive rhs
            #    definitions will not be expanded.
            #
            if ( index( $param, "=" ) == -1 )
            {
               # Here with -sat arg with no '='
               #
               LOG_ERROR( "Illegal argument translation \"$arg\"" );

               $terminate = 1;   # true
            }
            else
            {
               my ( $lhs, $rhs ) = split( "=", $param, 2 );

               ## Need to allow args like --diag_suppress 174   
               ## Hence replace: 
               ##                    if ( ( $lhs eq "" ) ||
               ##                         ( $lhs =~ /^[\-\/]/ )
               ##                       )
               if ( $lhs eq "" )
               {
                  # Here with illegal '/' or '-' in lhs.
                  # Or lhs is empty.
                  # Should be of form 'MD' etc and not
                  # '/MD' or '-MD'.
                  #
                  LOG_ERROR( "Illegal lhs \"$lhs\" in \"$arg\"" );

                  $terminate = 1;   # true
               }
               elsif ( $rhs eq "" )
               {
                  # Here with rhs empty.
                  #
                  LOG_ERROR( "Illegal rhs \"\" in \"$arg\"" );

                  $terminate = 1;   # true
               }
               else
               {
                  # The -sat definitions can have quotes around the
                  # rhs, and escaped quotes within the rhs.
                  # Un-escape any quotes within $rhs.
                  #
                  $rhs =~ s/\\"/"/go;

                  # Here with ok -sat, save the definition.
                  #
                  $$hr_arg_translations{ $lhs } = $rhs;
               }
            }
         }
         elsif ( $lc_opt =~ /^${PARSE_CL::flag_regex}alias$/ )
         {
            # Here with alias statement
            #
            my ( $name, $value ) = split( "=", $param, 2 );
   
            SET_ALII( $name, $value );
         }
         else
         {
            # Here with plain option
            #
            push @$ar_pass_1_tokens, FULL_OPTION( $opt, $param );
         }

         $i_arg += $dn_arg;
      }
      else
      {
         # Here with non-option token.
         # This may be a simple file (or project spec), or file glob,
         # or parameter to preceding option (which will be handled in pass 2),
         # or a parameter to a -cargs / -targs option.
         # e.g. args to /MD or other options which have been translated
         # in this pass (pass 1).
         #
         # Filenames can be globbed.  
         # So can project specs for the first term only.
         #
         push @$ar_pass_1_tokens, $arg;

         ++$i_arg;
      }
   }

   if ( $terminate )
   {
      GEN::DIEX( ": Error(s) in parameters.", $PARSE_CL::die_config_error );
   }

   return ( $ar_pass_1_tokens,
            $ar_parse_cargs_parameters,
            $hr_arg_translations,
            $in_ctargs_section,
            $carg_compiler_name,
            $afe_arg,
            $dafe_arg );
}

#----------------------------------------------------------------------------

sub PARSE_ARGS_PASS_2
{
   # Usage: ( $ar_parse_options,
   #          $ar_parse_file_names,
   #          $ar_parse_outputpaths,
   #          $no_op_before_first_file,
   #          $op_trails_after_file_args ) =
   #              PARSE_ARGS_PASS_2( $ar_pass_1_tokens,
   #                                 $qa_outpath,
   #                                 $afe_arg,
   #                                 $dafe_arg );
   #
   # This sub does the 2nd pass of the arguments.
   # Parse $ar_pass_1_tokens and build:
   #       - $ar_parse_options.
   #       - $ar_parse_file_names.
   #       - $ar_parse_outputpaths.
   #
   # Parses command line arguments separating them into 'options', 'filenames' /
   # project specs, 'output paths' on a one per file basis.  'cargs arguments',
   # 'targs arguments' and -rem arguments were derived in pass 1.
   #
   # $ar_parse_options output contains all of its parameters within each item
   # as a single arg.
   # Quotes are removed on output but embedded spaces are preserved.
   #
   # -- -op handling --------------------------------------------------------
   #
   # The -outputpath (-op) option has special properties which differ
   # between programs like QAC and PRJDSP.
   #
   # For QAC the last -op on the command-line becomes the value used for
   # analysing the specified source file (e.g. f.c).  This is true whether
   # the -op occurs before or after the source file (e.g. f.c)
   #
   # For PRJDSP the last -op immediately preceding a file supplied for
   # analysis, whether it is listed on the command-line or whether it
   # appears in a -list option, is used as the -op value.  -op settings
   # that follow a file do not affect any files preceding them, they only
   # affect subsequent files.
   #
   # QAW uses the following rules to resolve this conflict:
   # 
   # Use Prjdsp rule throughout and give following warnings:
   # 
   #    WARNING: No -op specification before first file, QACOUTPATH used.
   # 
   #    ERROR: Illegal trailing -op specification(s).  Ensure
   #           -op specifications precede relevant files.
   # 
   #  Case 1:   qacw  1.c  -opA  -opB  2.c  -opC  3.c -opD  4.c
   #   
   #   => WARNING: No -op specification before first file, QACOUTPATH used.
   #   +
   #   1.c      ->      QACOUTPATH
   #   2.c      ->      -opB
   #   3.c      ->      -opC
   #   4.c      ->      -opD
   #
   #  Case 2:   qacw  -opA  1.c  -opB
   # 
   #   => ERROR: Illegal trailing -op specification(s).  Ensure -op
   #      specifications precede relevant files.
   # 
   #  Case 3:   qacw  1.c  -opA
   # 
   #   => WARNING: No -op specification before first file, QACOUTPATH used.
   #
   #   => ERROR: Illegal trailing -op specification(s).  Ensure -op
   #      specifications precede relevant files.
   # 
   #  Case 4:   qacw  1.c  -opA  2.c
   # 
   #   => WARNING: No -op specification before first file, QACOUTPATH used.
   #   +
   #   1.c      ->      QACOUTPATH
   #   2.c      ->      -opA
   # 
   #  Case 5:   qacw  1.c  2.c
   #  
   #   => WARNING: No -op specification before first file, QACOUTPATH used.
   #   +
   #   1.c      ->      QACOUTPATH
   #   2.c      ->      QACOUTPATH
   # 
   #  Case 8:   qacw  1.c  2.c  -listLST
   # 
   #   => WARNING: No -op specification before first file, QACOUTPATH used.
   #   +
   #   1.c      ->      QACOUTPATH
   #   2.c      ->      QACOUTPATH
   #   LST      ->      QACOUTPATH unless -op setting(s) in LST
   # 
   #  Case 9:   qacw  1.c -listLST  2.c
   # 
   #   => WARNING: No -op specification before first file, QACOUTPATH used.
   #   +
   #   1.c      ->      QACOUTPATH
   #   LST      ->      QACOUTPATH unless -op setting(s) in LST
   #   2.c      ->      QACOUTPATH / last -op setting(s) in LST
   #
   # Implemented by the running $current_outputpath.
   #
   # $current_outputpath is initialised to "" as -op cannot return a param
   # of "".  This is then updated on encountering each -op.
   #
   # On finding -op option, simply reset the $current_outputpath value for
   # subsequent file names.
   #
   # In order to be able to give the warning/error use:
   #   $first_op_encountered == false until -op encountered.
   #   $op_is_trailing       == true when -op encountered since last file 
   #                            encountered
   #
   my $ar_pass_1_tokens = shift;
   my $qa_outpath       = shift;
   my $afe_arg          = shift;
   my $dafe_arg         = shift;

   #-- Pass 2 ---------------------------------------------------------------
   #

   my $terminate = 0; # false  # - Indicates error in $ar_pass_1_tokens when set.

   my $ar_parse_options     = [];
   my $ar_parse_file_names  = [];
   my $ar_parse_outputpaths = [];

   # $current_outputpath is initialised to QAC[PP]OUTPATH.
   # This is then updated on encountering each -op.
   #
   my $current_outputpath = $qa_outpath;

   # In order to check for -op warning/error initialise flags:
   #
   my $ctarg_encountered             = 0;  # false
   my $first_op_encountered          = 0;  # false
   my $no_op_before_first_file       = 0;  # false (ie ok)
   my $op_is_trailing                = 0;  # false (ie ok)
   my $count_of_fileargs_encountered = 0;

   # Initialise filespec counter
   #
   my $count = 0;

   # Setup list of allowable extensions
   #
   my $ar_code_or_project_file_extensions = [ GET_CODE_FILE_EXTNS( $afe_arg ), ".prj" ];

   my $i_arg = 0;

   IARG:
   while ( $i_arg <= $#$ar_pass_1_tokens )
   {
      my $arg = $$ar_pass_1_tokens[ $i_arg ];

      if ( $arg eq "" )
      {
         ++$i_arg;

         next IARG;
      }

      # Filter out any supplied -zzz_marker terms
      #
      if ( GEN::LEFT( $arg, length( $PARSE_CL::zzz_marker ) ) eq $PARSE_CL::zzz_marker )
      {
         ++$i_arg;

         next IARG;
      }

      if ( ( $arg eq "-cargs" ) ||
           ( $arg eq "-targs" )
         )
      {
         $ctarg_encountered = 1;  # true

         ++$i_arg;

         next IARG;
      }

      my $first_ch = substr( $arg, 0, 1 );

      # If on Windows, shouldn't have '/' preceding an arg at this point.
      # i.e. all options starting with '/' should have been processed,
      # and file name arguments cannot start with '/' on Windows.
      #
      # For Unix, can have a file name starting '/'.
      #
      if ( $first_ch eq "/" )
      {
         if ( GEN::PLATFORM_IS_WINDOWS() )
         {
            GEN::DIE( ": ASSERT FAILURE.  Unexpected '/' option prefix in PARSE_ARGS_PASS_2." );
         }
      }

      if ( $first_ch eq "-" )
      {
         # Here when arg is an option
         #
         my ( $opt, $param, $dn_arg, $error ) = GET_OPT( $i_arg, $ar_pass_1_tokens );

         # -ve $dn_arg implies that argument is illegal, i.e. not a 
         # recognised QAC/PP option.
         #
         if ( $dn_arg < 0 )
         {
            LOG_ERROR( $error );

            $terminate = 1;  # true

            $i_arg += abs( $dn_arg );
            
            next IARG;
         }

         # Handle arg for opt that has a path param.
         # Resolve that param if relative path
         #
         if ( OPTION_HAS_PATH_PARAMETER( $opt ) )
         {
            $param = RESOLVE_FOR_POSSIBLE_ROOT_PATH_PREFIX( $param );
         }

         my $lc_opt = lc( $opt );

         if ( ( $lc_opt eq "-op" ) || ( $lc_opt eq "-outputpath" ) )
         {
            push @$ar_parse_options, FULL_OPTION( $opt, $param );

            # Found -op option.
            # Set $current_outputpath for next and subsequent
            # $ar_parse_file_names.
            #
            $current_outputpath = GEN::UNQUOTE( $param );

            # Encountered a -op so ensure following flag is set:
            # 
            $first_op_encountered = 1;  # true

            # At this point this -op is after all file args encountered
            # so far:
            #
            $op_is_trailing = 1;  # true
         }
         else
         {
            # Here with plain option
            #
            push @$ar_parse_options, FULL_OPTION( $opt, $param );
         }

         $i_arg += $dn_arg;

         next IARG;
      }
      else
      {
         # Here when $arg is not an option, and must be a filename or
         # a project spec.
         #
         
         # Filenames can be globbed.  
         # Project specs can be globbed for the first term only.
         # i.e Can have
         #     *.prj
         # But can't have following if *.prj expands to more than one file
         #     *.prj#folder
         #     *.prj#folder#<filespec>
         # 
         # It can't be anything else, hence can allow messages to be
         # generated if any number of '#'s found.
         #
         # Project file specifications are decoded by RUN::PROCESS_PROJECT_FILE()
         # and RUN::WRITE_FILE_LIST().
         #
         
         # $arg should be a filename.
         #
         # Ensure $arg is a valid file spec.
         #
         my $file_spec = GEN::BASE_NAME( $arg );
         
         #28-03-06: Previous criteria too harsh.
         #Prev:     if ( $file_spec =~ tr/!\"\$\%\^\&\(\)\;\@\'\~\#\<\>\,// )
         #
         # Now use minimal characterset for OS:
         # /                 is illegal Unix filename character.
         # \ / : * ? " < > | are illegal Windows filename characters.
         # However * and ? are valid characters for wildcard expressions.
         
         # Following tr/// replaces any of the listed character in $file_spec
         # with null and returns the count of the number of replacements.
         # Hence if any of the characters are matched a true result is returned
         # otherwise 0 (false) is returned.
         #
         my $invalid_filename;
         if ( GEN::PLATFORM_IS_WINDOWS() )
         {
            # $invalid_filename = ( $file_spec =~ tr/\\\/\:\"\<\>\|// );
            # As of 5-4-2011, $file_spec can be a .prj spec, hence ':' is valied.
            #
            $invalid_filename = ( $file_spec =~ tr/\\\/\"\<\>\|// );
         }
         else
         {
            $invalid_filename = ( $file_spec =~ tr/\/// );
         }
         
         if ( $invalid_filename )
         {
            LOG_ERROR( "\"$arg\" is not a valid file name." );
         
            $terminate = 1;   # true
         }
         else
         {
            # Here with arg may be wildcarded filename, or may be an explicit
            # full filename, or may be a relative path to be prefixed with the
            # root path of the project file if defined.  If the root path is not 
            # defined, pass as is.
         
            if ( $arg !~ /[\*\?]/o )
            {
               # Here when arg is not wildcarded
               #
               $arg = RESOLVE_FOR_POSSIBLE_ROOT_PATH_PREFIX( $arg );
            }
         
            my $ar_tokens;
            my $error = "";
         
            ( $ar_tokens, $error ) = GEN::GET_GLOB_FILE_LIST( $arg );
      
            if ( ( $#$ar_tokens == -1 ) &&
                 ( index( $arg, "#" ) != -1 )
               )
            {
               # Here with prj filespec
               #
               $ar_tokens = [ GEN::GET_FILE_PATH_ABSOLUTE( $arg ) ];

               # Reset $error to avoid giving warning for failure of
               # above GEN::GET_GLOB_FILE_LIST to return any files
               #
               $error = "";
            }
         
            if ( $error ne "" )
            {
               LOG_WARNING( $error );
            }
            else
            {            
               # Save filename in list of files to be processed if it is: 
               #   *.c or *.C
               #   *.cc or *.CC
               #   *.cpp or *.cxx or *.c++
               #   *.h or *.H
               #   *.hpp or *.hxx or *.h++
               #   *.i or *.I
               #   *.ipp or *.ixx or *.i++
               #   *.prj
               # ETC.
               #
         
               foreach my $filename ( @$ar_tokens )
               {
                  # This statement deals with extn of .prj and others
                  #
                  my $extn = GEN::FILE_EXTENSION( $filename );
         
                  # This statement deals with a prj filespec with a # separator and
                  # following folderspec and or filespec.
                  #
                  my $is_prj_filespec = index( lc( $filename ), ".prj#" ) != -1;
                  
                  if ( EXTN_OK( $extn, $ar_code_or_project_file_extensions ) ||
                       $is_prj_filespec
                     )
                  {
                     push @$ar_parse_file_names, $filename;
                     push @$ar_parse_outputpaths, $current_outputpath;
                    
                     if ( ( $extn eq ".prj" ) ||
                          $is_prj_filespec
                        )
                     {
                        push @$ar_parse_options, FULL_OPTION( $PARSE_CL::zzz_marker, $count );
                        ++$count;
                     }
                     else
                     {
                        # Here with file arg.
                        # Perform -op related actions
                        #
         
                        ++$count_of_fileargs_encountered;
               
                        # Toggle $op_is_trailing as current arg is a filearg and
                        # hence $op_is_trailing is now false.
                        #
                        $op_is_trailing = 0;  # false
               
                        # Check setting of $no_op_before_first_file flag.
                        #
                        if ( $count_of_fileargs_encountered == 1 )
                        {
                           # Here when at first filearg encountered.
                           # Set warning flag if no -op has been encountered.
                           #
                           if ( ! $first_op_encountered )
                           {
                              $no_op_before_first_file = 1;  # true
                           }
                        }
                     } 
                  }
                  else
                  {
                     if ( ( $dafe_arg ne "-" ) &&
                          ( ! $ctarg_encountered )
                        )
                     {
                        LOG_WARNING( "Ignoring unrecognised file \"$filename\"" );
                     }
                  }
               }
            }
         }
         ++$i_arg;
      }
   }

   if ( $terminate )
   {
      GEN::DIEX( ": Error(s) in parameters.", $PARSE_CL::die_config_error );
   }

   return ( $ar_parse_options,
            $ar_parse_file_names,
            $ar_parse_outputpaths,
            $no_op_before_first_file,
            $op_is_trailing );
}

#----------------------------------------------------------------------------

sub RESOLVE_FOR_POSSIBLE_ROOT_PATH_PREFIX
{
   # Usage: $resolved_path = RESOLVE_FOR_POSSIBLE_ROOT_PATH_PREFIX( $path );
   #
   my $path = shift;

   $path = GEN::UNQUOTE( $path );

   if ( GEN::IS_FILE_PATH_ABSOLUTE( $path ) )
   {
      # Here when path is abs.
      #
      return $path;
   }
   else
   {
      # Here when path is relative.
      # Prefix with root path if poss
      
      my $root_path = GET_ROOT_PATH();

      if ( defined( $root_path ) )
      {
         return $root_path . $path;
      }
      else
      {
         # Return unchanged rel path
         #
         return $path;
      }
   }
}

#----------------------------------------------------------------------------

sub EXPAND_WINDOWS_ENV_VARS
{
   # Usage: $expanded_string = EXPAND_WINDOWS_ENV_VARS( $string );
   #
   # Assume that there is only one env var and that this is 
   # marked by the only instance of %...%.
   # Therefore only try and expand the first % enclosed string.
   # If any % characters are left after the expand, die.
   #
   my $string = shift;

   my $expanded_string = $string;

   if ( GEN::PLATFORM_IS_WINDOWS() )
   {
      if ( index( $expanded_string, "%" ) != -1 )
      {
         my $env_var_name;
         ( $env_var_name = $expanded_string ) =~ s/^.*?\%(.*?)\%.*/$1/o;
   
         if ( exists( $ENV{ $env_var_name } ) )
         {
            $expanded_string =~ s/\%$env_var_name\%/$ENV{ $env_var_name }/;
         }
      }
   
      if ( index( $expanded_string, "%" ) != -1 )
      {
         GEN::DIEX( ": Illegal environment variable in \"$string\".",
                    $PARSE_CL::die_config_error );
      }
   }

   return $expanded_string;
}

#----------------------------------------------------------------------------

sub GET_CODE_FILE_EXTNS
{
   # Usage: @code_or_project_file_extensions = GET_CODE_FILE_EXTNS( $afe_arg );
   #
   my $afe_arg = shift;

   my @extns = split( /\./o, $afe_arg );

   my @list = ();

   foreach my $extn ( @extns )
   {
      # Each $extn is to be a regex for matching individual extensions.
      # However $afe_arg may contain regex meta characters that are to
      # be matched verbatim, e.g. ".c++".  Therefore need to escape out
      # (i.e. disable) + etc, but convert '?' to '.?' and '*' to '.*'
      # (in order to handle wildcars in $afe_arg).
      #

      # To be safe, escape the following, since they are not legal 
      # filename characters, but the user could still put them in
      # $afe_arg: \, |, (, )
      #
      $extn =~ s/\\/\\\\/go;
      $extn =~ s/\|/\\\|/go;
      $extn =~ s/\(/\\\(/go;
      $extn =~ s/\)/\\\)/go;

      # Manually escape: [, {, ^, $, +, 
      #
      $extn =~ s/\[/\\\[/go;
      $extn =~ s/\{/\\\{/go;
      $extn =~ s/\^/\\\^/go;
      $extn =~ s/\$/\\\$/go;
      $extn =~ s/\+/\\\+/go;

      # Note that . cannot appear in a $extn term (as it is the split delimiter).
      #

      # Must convert ? to .? and * to .* for valid regex.
      #
      $extn =~ s/\?/\.\?/go;
      $extn =~ s/\*/\.\*/go;

      if ( $extn ne "" )
      {
         push @list, "\." . $extn;
      }
   }

   return @list;
}

#----------------------------------------------------------------------------

sub EXTN_OK
{
   # Usage: $bool_ok = EXTN_OK( $extn, $ar_extn_list );
   #
   my $extn         = shift;
   my $ar_extn_list = shift;

   foreach my $ex ( @$ar_extn_list )
   {
      if ( $extn =~ /^$ex$/ )
      {
         return 1;   # true;
      }
   }

   return 0;   # false;
}

#----------------------------------------------------------------------------

sub HANDLE_ARG_IN_CTARGS_SECTION
{
   # Usage: ( $dn_arg, $ar_tokens ) = HANDLE_ARG_IN_CTARGS_SECTION
   #              ( $arg,
   #                $hr_arg_translations );
   #        
   # This sub handles and resolves args in the -cargs / -targs section
   # using -sat options.  N.B. Only args that have a -sat translation
   # are considered, these are translated and the translation tokens
   # (i.e. QAC/PP args) are returned for writing to $ar_parse_options.
   #
   # A key aspect of -sat defined options, is that options in the 
   # command set _after_ -cargs / -targs are not written to $ar_parse_options
   # unless they have been defined in a previous -sat option.
   # 
   # Hence "I=-I" has the effect of making an option in the -cargs / -targs
   # set like -IC:\usr\lib become included in the QAW parameters used
   # for analysis.  i.e. -Ic:\usr\lib is written to $ar_parse_options.
   # 
   # Have problem that don't want to split /MD into -M D in -cargs / -targs
   # section.  VC++ appears to separate option specifiers (e.g. /I) from their
   # param by space(s).  Can this be relied on for gcc etc? !!!
   # 
   # Therefore apply rules that:
   # 1. Only do -sat translation in -cargs / -targs section.
   # 2. Look for longest matching %hr_arg_translations value and if found
   #    split off remainder as the param.
   #
   # Example $arg values are:
   #    $arg == "/I\Freedom"
   # or $arg == "-IFreedom"
   # or $arg == "/MD"
   #
   my $arg                 = shift;
   my $hr_arg_translations = shift;

   my $ar_tokens = [];

   # Will always consume one command line token.
   # i.e. $arg can either be a full option (e.g. /MD),
   # or an option with its param (e.g. /I\C:Fred).
   #
   # NB if arg has parameter, then this is returned as last
   # element of $ar_tokens.
   # 
   my $dn_arg = 1;

   # Perform argument translation defined by current -sat opts in
   # the current arguments set.
   #

   # Trim off the leading - or /
   # 
   ( my $arg_nmemonic = $arg ) =~ s/^(\/|-+)//o;

   my ( $translated_arg, $param ) =
         TRANSLATE_ARG( $arg_nmemonic, $hr_arg_translations );

   if ( $translated_arg ne "" )
   {
      # Example $translated_arg is: "-d _MT -d _DLL"
      # Parsing these sub options is done in PARSE_ARGS_PASS_2
      # Hence reduce to set of tokens for pass 2.
      #
#      @$ar_tokens = GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS( " ", $translated_arg );
      @$ar_tokens = GEN_STRING::SPLIT_COMMAND_LINE_STRING( $translated_arg );
   }

   if ( $param ne "" )
   {
      push @$ar_tokens, $param;
   }

   return ( $dn_arg, $ar_tokens );
}

#----------------------------------------------------------------------------

sub TRANSLATE_ARG
{
   # Usage: ( $translated_arg, $param ) =
   #              TRANSLATE_ARG( $arg_nmem, $hr_arg_translations );
   #
   my $arg_nmem            = shift;
   my $hr_arg_translations = shift;

   # Find longest translation in $hr_arg_translations that matches $arg_nmem
   #

   my $sat_opt_len_max = 0;
   my $rhs             = "";
   my $param           = "";

   my $arg_nmem_len = length( $arg_nmem );

   foreach my $sat_opt ( keys %$hr_arg_translations )
   {
      my $sat_opt_len = length( $sat_opt );
      
      if ( $sat_opt_len <= $arg_nmem_len )
      {
         my $arg_nmem_slice = substr( $arg_nmem, 0, $sat_opt_len );

         if ( $arg_nmem_slice eq $sat_opt )
         {
            if ( $sat_opt_len > $sat_opt_len_max )
            {
               # $param is the trailling characters after the matched part.
               #
               $param = substr( $arg_nmem, $sat_opt_len );

               $sat_opt_len_max = $sat_opt_len;

               $rhs = $$hr_arg_translations{ $sat_opt };
            }
         }
      }
   }
   
   return ( $rhs, GEN::TRIM( $param ) );
}

#----------------------------------------------------------------------------

sub SET_ALII
{
   # Usage: SET_ALII( $name, $value );
   #
   # Stores -alias setting
   #
   my $name  = shift;
   my $value = shift;

   if ( ! defined( $name ) )
   {
      GEN::DIE( ":PARSE_CL::SET_ALII - \$name is undefined." );
   }

   if ( ! defined( $value ) )
   {
      # Avoid program failure caused by wild call to SET_ALII.
      #
      return;
   }

   # Must expand rhs, i.e. $value, prior to saving.
   #
   $value = EXPAND_VARS( $value );

   if ( exists( $PARSE_CL::alii{ $name } ) )
   {
      if ( $PARSE_CL::alii{ $name } ne $value )
      { 
         LOG_WARNING( "Alias \"$name\" redefined to \"$value\"." );
      }
   }

   $PARSE_CL::alii{ $name } = $value;

#   # Set envirnoment variable for benefit of called progs like QAC
#   #
#   $ENV{ $name } = $value;
}

#----------------------------------------------------------------------------

sub EXPAND_VARS
{
   # Usage: $string = EXPAND_VARS( $string );
   #
   # Searches $string for "$".
   # If "$" is immediately followed by "{" then substitute the text upto "}"
   # Otherwise substitute text upto .[^\w]
   # NB If name is not found, then do nothing.
   # 
   my $string = shift;

   my $pos = index( $string, "\$", 0 );

   while ( $pos != -1 )
   {
      if ( $pos == length( $string ) - 1 )
      {
         return $string;
      }

      my $next_ch = substr( $string, $pos + 1, 1 );
      my $end;
      my $name;
 
      if ( $next_ch eq "{" )
      {
         # Get name to }
         #
         $end = index( $string, "}", $pos );

         $name = substr( $string, $pos + 2, $end - ( $pos + 2 ) );
      }
      else
      {
         # Get name to first non-identifier character 
         #
         $end = $pos;

         IDENTIFIER_CHAR:
         for ( my $i = $pos + 1; $i < length( $string ); ++$i )
         {
            last IDENTIFIER_CHAR if ( substr( $string, $i, 1 ) =~ /\W/o );

            $end = $i;
         }

         $name = substr( $string, $pos + 1, $end - $pos );
      }

      # Set to full $name initially for case when no value for $name 
      #
      my $value = substr( $string, $pos, $end - $pos + 1 );
 
      if ( exists( $PARSE_CL::alii{ $name } ) )
      {
         # Here when alias found
         #
         $value = $PARSE_CL::alii{ $name };
      }
      else
      {
         # Alias not found, check for env var
         #
         if ( defined( $ENV{ $name } ) )
         {
            $value = $ENV{ $name };
         }
      }

      substr( $string, $pos, $end - $pos + 1 ) = $value;

      # Search for next $
      #
      $pos = index( $string, "\$", $pos + length( $value ) );
   }

   return $string;
}

# -------------------------------------------------------------------------

sub GET_OPT
{
   # Usage: ( $opt, $param, $n_arg, $error ) = GET_OPT( $i_arg, $ar_arg_list )
   #
   # Returns:
   #   $opt   - e.g. "-max" or maybe "/max"
   #   $param - e.g. "25"
   #   $n_arg - number of tokens comprising option
   #   $error - "" if ok, or error message string.
   #
   # Looks at element $i_arg in $ar_arg_list and returns that as $opt
   # if it is a -option (preceded by '-' or '/') otherwise returns $opt = "".
   # If $opt is an option with an argument, then $param is returned as
   # that parameter, otherwise $param is returned as "".
   # Dependent on whether $param is returned and whether it is in the
   # same $ar_arg_list element, or the next element, $n_arg returns the
   # number of elements of $ar_arg_list used by $opt and $param.
   # NB $n_arg is returned as the number of command line $ar_arguments * -1
   # if the argument is illegal.
   #
   # Note as of qaw version 1.11, this function is required to handle
   # elements in $ar_arg_list which contain the option (e.g. -i) and also
   # its parameter, separated by a space.  e.g. "-t 4".
   #
   my $i_arg       = shift;
   my $ar_arg_list = shift;

   my $no_error = "";   # constant

   my $arg = $$ar_arg_list[ $i_arg ];

   # Have to allow arguments like '-i' and '/i' i.e. DOS compatible.
   # - See $PARSE_CL::flag_regex.
   #    
   if ( ( substr( $arg, 0, 1 ) !~ /$PARSE_CL::flag_regex/ ) ||
        ( $i_arg > $#$ar_arg_list )
      )
   {
      # Here when not an option.
      # Return file_name type argument.
      #
      return ( $arg, "", -1, "\"$arg\" is not an option." );
   }

   # Look for single letter options, particularly -o and -n as these 
   # are by far the most frequent options.
   # single_char_opts = qw( -a -d -i -n -o -q -s -t );
   #

   if ( $arg =~ /^$PARSE_CL::flag_regex[adinoqst]\b/oi )
   {
      # Here when option is single character option, e.g. -o or /o
      #
      my $opt = substr( $arg, 0, 2 );

      if ( length( $arg ) > 2 )
      {
          # Here when option has trailing parameter in $arg
         
          # Get parameter by stripping from $arg
          # 
          my $param              = GEN::TRIM( substr( $arg, 2 ) );
          my ( $error, $dn_arg ) = CHECK_OPT( $arg, $param, 1, "+" );

          return ( $opt, $param, $dn_arg, $error );
      }
      else
      {
         # Here with -o etc and parameter is in next @$ar_arg_list element
         #
         if ( $i_arg < $#$ar_arg_list )
         {
            my $param              = $$ar_arg_list[ $i_arg + 1 ];
            my ( $error, $dn_arg ) = CHECK_OPT( $arg, $param, 2, "+" );

            return ( $opt, $param, $dn_arg, $error );
         }
         else
         {
            # Here when want parameter in next element, but we
            # are already at the end of the list.
            # So print a warning and return $n_arg as -1 to 
            # indicate the error.
            #
            return ( "", "", -1, "No parameter for \"$arg\"." );
         }
      }
   } 

   # Here with option.  Strip off the first character as this may be '-' or '/'
   #
   my $arg_nmemonic = lc( substr( $arg, 1 ) );
   my $arg_nmem_len = length( $arg_nmemonic );
   
   foreach my $opt ( @PARSE_CL::all_options )
   {
      my $opt_nmemonic = substr( $opt, 1 );
      my $opt_nmem_len = length( $opt_nmemonic );
   
      if ( substr( $arg_nmemonic, 0, $opt_nmem_len ) eq $opt_nmemonic ) 
      {
         # Here when recognised $arg.
         # Now need to see if $arg has a parameter.
         # Do this because parameter may be in next @$ar_arg_list element.
         # $opt like "+d", ie that starts with + or !, is marker for arg
         # with a parameter.
         # Or may be in $arg with space separation.
         #
         my $option  = substr( $arg, 0, $opt_nmem_len + 1 );
         my $opt_ch1 = substr( $opt, 0, 1 );

         if ( ( $opt_ch1 eq "+" ) ||
              ( $opt_ch1 eq "!" )
            )
         {
            # Here when $arg has a parameter other than +|-.
            # Test whether the path parameter follows in the next @$ar_arg_list
            # element.  i.e. there is a space between the option and its
            # parameter(s).
            # The presence of a space may manifest in 2 ways:
            # - parameter may be in next @$ar_arg_list element,
            # - or parameter may be in this element, separated by a space.
            #
            if ( $arg_nmem_len == $opt_nmem_len )
            {
               # Here when parameter follows in the next @$ar_arg_list element.
               #
               if ( $i_arg < $#$ar_arg_list )
               {
                  my $param              = $$ar_arg_list[ $i_arg + 1 ];
                  my ( $error, $dn_arg ) = CHECK_OPT( $arg, $param, 2, "+" );

                  return ( $option, $param, $dn_arg, $error );
               }
               else
               {
                  # Here when want parameter in next element, but we
                  # are already at the end of the list.
                  # So print a warning and return $n_arg as -1 to 
                  # indicate the error.
                  #
                  return ( "", "", -1, "No parameter for \"$arg\"." );
               }
            }
            else
            {
               # This option has its parameter directly concatenated.
               # - Possibly with space separator.
               #
               my $param = substr( $arg, $opt_nmem_len + 1 );
               ( $param = $param ) =~ s/^\s*//o;

               my ( $error, $dn_arg ) = CHECK_OPT( $arg, $param, 1, "+" );

               return ( $option, $param, 1, $error );
            } 
         }
         else
         {
            # Here when $arg is a simple argument like -met+|- 
            # ie parameter is +|- which always immediately follows the
            # option.
            #
            my $param              = substr( $arg, $opt_nmem_len + 1 );
            my ( $error, $dn_arg ) = CHECK_OPT( $arg, $param, 1, "-" );

            return ( $option, $param, $dn_arg, $error );
         }
      }
   }

   # Fall through loop over valid args means invalid $arg
   # Signal this by returning $n_arg as -1
   #
   return ( "", "", -1, "\"$arg\" not recognised." );
}

#----------------------------------------------------------------------------

sub CHECK_OPT
{
   # Usage: ( $error_string, $dn_arg ) = 
   #           CHECK_OPT( $arg, $param, $dn_arg, $opt_ch1 );
   #
   my $arg     = shift;
   my $param   = shift;
   my $dn_arg  = shift;
   my $opt_ch1 = shift;

   my $no_error = "";   # constant

   if ( $opt_ch1 eq "-" )
   {
      # Here when $param can only be '+' or '-'
      #
      if ( ( $param ne "-" ) &&
           ( $param ne "+" ) &&
           ( $param ne "" )
         )
      {
         return ( "Illegal option \"$arg\", expect parameter of +|-.", -$dn_arg );
      }
   }
   else
   {
      if ( $param eq "" )
      {
         return ( "Option \"$arg\" has no parameter.", -$dn_arg );
      }
   }

   return ( $no_error, $dn_arg );
}

#----------------------------------------------------------------------------

sub LOAD_OPTION_FILE
{
   # Usage ( $ar_args, $terminate ) =
   #            LOAD_OPTION_FILE
   #               ( $file_name,
   #                 $terminate,
   #                 $in_ctargs_section );
   #
   # Note: Syntax of options in the file that is loaded must follow standard
   # option format. i.e. should have lines like:
   #
   # -rem "goble goble"
   # -i "c:\program files"
   #
   # And not:
   #
   # -rem goble goble
   # etc.
   #
   my $file_name         = shift;
   my $terminate         = shift;
   my $in_ctargs_section = shift;

   my @args = ();

   my $open_ok = open FILE, "<$file_name";

   if ( ! $open_ok )
   {
      if ( ! $in_ctargs_section )
      {
         GEN::DIEX( ": Cannot open file \"$file_name\"\n$!",
                    $PARSE_CL::die_config_error );
      }
      else
      {
         return ( \@args, $terminate );
      }
   }

   LINE:
   while ( my $line = <FILE> )
   {
      # Remove eols, catering for both Unix and DOS eolns
      #
      $line = GEN::TRIM( $line );

      next LINE if ( $line eq "" );
   
      $line = EXPAND_VARS( $line );

      my ( $first_token, $ignore ) = split( " ", $line, 2 );
   
      my $first_ch = substr( $first_token, 0, 1 );
      if ( $first_ch eq "*" )
      {
         # Here with comment - skip.
         #
      }
      elsif ( ( $first_ch eq "/" ) &&
              GEN::PLATFORM_IS_WINDOWS()
            )
      {
         LOG_ERROR( "Option \"$first_token\" must have \"-\" prefix in file \"$file_name\"" );
       
         $terminate = 1;   # true
      }
      elsif ( lc( substr( $first_token, 0, 6 ) ) eq "-alias" )
      {
         # Here with alias statement
         #
         my $param            = GEN::TRIM( substr( $line, 6 ) );
         my ( $name, $value ) = split( "=", $param, 2 );
      
         if ( exists( $PARSE_CL::alii{ $name } ) )
         {
            if ( $PARSE_CL::alii{ $name } ne $value )
            {
               LOG_WARNING( "Alias \"$name\" redefined to \"$value\" in file \"$file_name\"." );
            }
         }

         $PARSE_CL::alii{ $name } = $value;

         # Store the option for subsequent parsing of the file options
         #
         # Here when reading a QAC/PP .via style file.
         # Don't use command line splitting of string as may have options like:
         #  -rem "ShellParams=%Q %F -nrf %S -forget cmaf -po "extension::header=['h','cpp','']""
         # i.e. need to handle embedded " and '
         # Use GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS() for this.
         #
         push @args, GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS( " ", $line );
      }
      else
      {
         push @args, GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS( " ", $line );

         # Here when reading a QAC/PP .via style file.
         # Don't use command line splitting of string as may have options like:
         #  -rem "ShellParams=%Q %F -nrf %S -forget cmaf -po "extension::header=['h','cpp','']""
         # i.e. need to handle embedded " and '
         # Use GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS() for this.
      }
   }
   close( FILE );
   
   return ( \@args, $terminate );
}

# ---------------------------------------------------------------------------

sub FULL_OPTION
{
   # Usage: $full_option = FULL_OPTION( $opt, $param );
   #
   # Sole routine for assembling an option with its parameter.
   # NB Could have options like -dfred, but this can cause failures if
   # original option supplied by the user had a space (after the -d say)
   # which, when removed, changes the nature of the option.
   #
   my $opt   = shift;
   my $param = shift;

   # Force first ch to '-;
   #
   substr( $opt, 0, 1 ) = "-";

   if ( ( $param eq "-" ) || 
        ( $param eq "+" ) ||
        ( $param eq "" )
      )
   {
      return $opt . $param;
   }

   my $lc_opt = lc( $opt );

   # settings.via can't have -opC:\prqa\qac-5.0\temp\
   # settings.via can have -op"C:\prqa\qac-5.0\temp\" but this is not
   # suitable as an argument to a Perl program.
   # Therefore ensure that trailing \ is maintained and that param is
   # quoted regardless of whether there are any spaces.  
   #

   # Browser fails if path is relative, so make absolute
   # Also comparison of paths fails if relative in QAC/QACPP.
   #
   if ( OPTION_HAS_PATH_PARAMETER( $opt ) &&
        # Option is not -fi, -si, -i or -q :
        ( ( $lc_opt ne "-forceinclude" ) &&
          ( $lc_opt ne "-fi" ) &&
          ( $lc_opt ne "-systeminclude" ) &&
          ( $lc_opt ne "-si" ) &&
          ( $lc_opt ne "-i" ) &&
          ( $lc_opt ne "-quiet" ) &&
          ( $lc_opt ne "-q" ) 
        )
      )
   {
      $param = GEN::UNQUOTE( $param );

      if ( index( $param, " " ) == -1 )
      {
         # Here when no spaces in $param, so don't need to quote.
         # Therefore append trailing \ to allow for -i C:\ etc.
         #
         return $opt . " " . GEN::APPLY_FINAL_SEP( $param );
      }
      else
      {
         # Here with embedded spaces.  So must quote.
         # Cannot allow a param like "xx yy\" as the \ escapes the ".
         # Hence must remove any trailing \.
         #
         $param = GEN::REMOVE_FINAL_SEP( $param );

         # NB fact that there are spaces means that this cannot be a
         # top level directory case (e.g. -i c:\) so ok not to append \.
         #
         return "$opt \"$param\"";
      }
   }
   else
   {
      # Problematic case of an option with a path parameter ending in \ 
      # (e.g. -i c:\) is handled immediately above.
      #
      return $opt . " " . GEN::QUOTE_ARG( $param );
   }
}

# ---------------------------------------------------------------------------

sub LOG_ERROR
{
   # Usage: LOG_ERROR( $error_message );
   #
   # For QAW should call OPC::PUT_ERR_LINE( $error_message );
   # 
   # However as this (PARSE_CL.pm) is used outside of QAW and can't rely on
   # presence of OPC.pm, code it explicitly here.
   # In doing so can't use $err_use_stdout, $err_use_stderr or $use_logging,
   # hence write to STDERR only.
   #
   my $error_message = shift;

   GEN::PRINT_STDERR( "ERROR: $error_message\n" );
}

# ---------------------------------------------------------------------------

sub LOG_WARNING
{
   # Usage: LOG_WARNING( $warning_message );
   #
   # For QAW should call OPC::PUT_ERR_LINE( $warning_message );
   # 
   # However as this (PARSE_CL.pm) is used outside of QAW and can't rely on
   # presence of OPC.pm, code it explicitly here.
   # In doing so can't use $err_use_stdout, $err_use_stderr or $use_logging,
   # hence write to STDERR only.
   #
   my $warning_message = shift;

   GEN::PRINT_STDERR( "WARNING: $warning_message\n" );
}

# ---------------------------------------------------------------------------

sub OPTION_HAS_PATH_PARAMETER
{
   # Usage: $opt_has_path_param = OPTION_HAS_PATH_PARAMETER( $option );
   #
   my $option = shift;

   my $lc_opt = lc( $option );

   return ( exists( $PARSE_CL::options_with_path_parameter{ GEN::RIGHT2( $lc_opt, 1 ) } ) ) ||
          ( ( $lc_opt eq "-cmaf" ) ||
            ( $lc_opt eq "-cpph" ) ||
            ( $lc_opt eq "-createpph" ) ||
            ( $lc_opt eq "-crossmoduleanalysisfile" ) ||
            ( $lc_opt eq "-forceinclude" ) ||
            ( $lc_opt eq "-file" ) ||
            ( $lc_opt eq "-fi" ) ||
            ( $lc_opt eq "-i" ) ||
            ( $lc_opt eq "-luf" ) ||
            ( $lc_opt eq "-libraryusagefile" ) ||
            ( $lc_opt eq "-lf" ) ||
            ( $lc_opt eq "-layoutfile" ) ||
            ( $lc_opt eq "-nrf" ) ||
            ( $lc_opt eq "-namerulefile" ) ||
            ( $lc_opt eq "-outputpath" ) ||
            ( $lc_opt eq "-op" ) ||
            ( $lc_opt eq "-prj" ) ||
            ( $lc_opt eq "-projectfile" ) ||
            ( $lc_opt eq "-quiet" ) ||
            ( $lc_opt eq "-q" ) ||
            ( $lc_opt eq "-systeminclude" ) ||
            ( $lc_opt eq "-suppressionoutpath" ) ||
            ( $lc_opt eq "-suppressiondescriptiontext" ) ||
            ( $lc_opt eq "-sop" ) ||
            ( $lc_opt eq "-si" ) ||
            ( $lc_opt eq "-usrpath" ) ||
            ( $lc_opt eq "-up" ) ||
            ( $lc_opt eq "-via" )
          );
}

# ---------------------------------------------------------------------------

sub FORMAT_PRQA_PATH_PARAMETER
{
   # Usage: $formatted_path = FORMAT_PRQA_PATH_PARAMETER( $path );
   #
   # Write path parameters as follows:
   #
   # Actual              Written
   # ----------------    -------------------------
   # C:\                 "C:\"
   # "C:\"               "C:\"
   # C:\Fred             "C:\Fred\"
   # C:\Fred\            "C:\Fred\"
   # "C:\Fred\"          "C:\Fred\"
   # "C:\Fred Bloggs"    "C:\Fred Bloggs\"
   # "C:\Fred Bloggs\"   "C:\Fred Bloggs\"
   #
   # ( NB Core PRQA programs handling of -via files treats -op c:\temp\ as
   # introducing a continuation line.  Hence for core PRQA programs, 
   # settings.via use quotes.
   #      
   my $path = shift;

   $path = GEN::UNQUOTE( $path );

   $path = GEN::GET_FILE_PATH_ABSOLUTE( $path );

   $path = GEN::EVALUATE_RELATIVE_PATHS( $path );

   $path = GEN::APPLY_FINAL_SEP( $path );
         
   # Since path now has trailing backslashes and possibly embedded quotes,
   # now always need quotes.
   #
   $path = "\"$path\"";

   return $path;
}

# --------------------------------------------------------------------------

sub WRITE_SETTINGS_FILE
{
   # Usage: WRITE_SETTINGS_FILE( $filename, $ar_settings );
   #
   # NB @settings is an array of option settings (eg -op \usr) only.
   # It should not contain any null entries or non -... values.
   # Also, options supplied are in correct format spacewise after option.
   #
   my $filename    = shift;
   my $ar_settings = shift;

   open FILE, ">$filename" or GEN::DIEX( ": Cannot open file \"$filename\"\n$!",
                                         $PARSE_CL::die_config_error );

   my $i_setting = 0;

   SETTING:
   while ( $i_setting <= $#$ar_settings )
   {
      my $setting = $$ar_settings[ $i_setting ];

      if ( $setting eq "" )
      {
         GEN::DIE( "::WRITE_SETTINGS_FILE: Program error. Empty setting." );
      }

      my ( $opt, $param, $dn_setting, $error ) = GET_OPT( $i_setting, $ar_settings );

      if ( $dn_setting != 1 )
      {
         GEN::DIE( "::WRITE_SETTINGS_FILE: $error Bad setting \"$setting\"." );
      }

      my $lc_opt = lc( $opt );

      # Do not write -op settings to settings.via file
      #
      if ( ( $lc_opt eq "-op" ) ||
           ( $lc_opt eq "-outputpath" )
         )
      {
         $i_setting += $dn_setting;

         next SETTING;
      }

      # Browser fails if path is relative, so make absolute
      # Also comparison of paths fails if relative in QAC/QACPP.
      #
      # Standard handling of -via files treats -op c:\temp\ as introducing a
      # continuation line.  Hence for settings.via use quotes.
      # Also force append trailing \ as work-around for cases like -i c:\
      #      
      if ( OPTION_HAS_PATH_PARAMETER( $opt ) &&
           # Option is not -fi, -si, -i or -q :
           ( ( $lc_opt ne "-forceinclude" ) &&
             ( $lc_opt ne "-fi" ) &&
             ( $lc_opt ne "-systeminclude" ) &&
             ( $lc_opt ne "-si" ) &&
             ( $lc_opt ne "-i" ) &&
             ( $lc_opt ne "-quiet" ) &&
             ( $lc_opt ne "-q" ) 
           )
         )
      {
         # As a precaution/workaround for the problem where standard tools
         # treat \<eol> as introducing a continuation line, insert an extra 
         # space before <eol>
         #
         print FILE "$opt " . FORMAT_PRQA_PATH_PARAMETER( $param ) . " \n";
      }
      else
      {
         # Problematic case of an option with a path parameter ending in \ 
         # (e.g. -i c:\) is handled immediately above.
         #
         # As a precaution/workaround for the problem where standard tools
         # treat \<eol> as introducing a continuation line, insert an extra 
         # space before <eol>
         #
         print FILE FULL_OPTION( $opt, $param ) . " \n";
      }

      $i_setting += $dn_setting;
   }

   close FILE;
}

# ---------------------------------------------------------------------------

sub FORMAT_PURE_PATH_PARAMETER
{
   # Usage: $formatted_path = FORMAT_PURE_PATH_PARAMETER( $path );
   #
   # Write path parameters as follows:
   #
   # Actual              Written
   # ----------------    -------------------------
   # C:\                 C:\
   # "C:\"               C:\
   # C:\Fred\            C:\Fred
   # "C:\Fred\"          C:\Fred
   # "C:\Fred Bloggs"    "C:\Fred Bloggs"
   # "C:\Fred Bloggs\"   "C:\Fred Bloggs"
   #
   # ( NB Core programs handling of -via files treats -op c:\temp\ as
   # introducing a continuation line.  Hence for core programs, 
   # settings.via use quotes, BUT for PURE settings files do not write
   # ...\" as the \" is an escaped quote and does not terminate the 
   # parameter.
   #      
   my $path = shift;

   $path = GEN::UNQUOTE( $path );

   $path = GEN::NORMALISE_PATH_SEPARATORS( $path );

   # Test for <drive>:\ e.g. C:\
   #
   if ( $path =~ /^[a-zA-Z]+\:\\$/o )
   {
      # Here with something like C:\
      # Use this without modification.
      #
      return $path;
   }
   else
   {
      # Here with path that may end in \ (or /)
      # Remove the trailing separator.
      #
      my $actual_path = GEN::REMOVE_FINAL_SEP( $path );

      # Ensure path is absolute.
      #
      $actual_path = GEN::GET_FILE_PATH_ABSOLUTE( $actual_path ); 
      $actual_path = GEN::EVALUATE_RELATIVE_PATHS( $actual_path );

      # Quote if necessary
      #
      $actual_path = GEN::QUOTE_ARG( $actual_path );

      return $actual_path;
   }
}

# --------------------------------------------------------------------------

sub WRITE_PURE_SETTINGS_FILE
{
   # Usage: WRITE_PURE_SETTINGS_FILE( $filename, $ar_settings );
   #
   # NB @settings is an array of option settings (eg -op \usr) only.
   # It should not contain any null entries or non -... values.
   # Also, options supplied are in correct format spacewise after option.
   #
   # Write PURE settings via, this file does not have trailing \" in options
   # with path parameters.
   #
   # Note that SETTINGS_VIA is read by the core PRQA products: QAC, QACPP and
   # ERRDSP etc, PURE_SETTINGS_VIA is read by post-analysis and global-analysis
   # programs which specify the 'settings.via' file via %P[+] parameters.
   # 
   my $filename    = shift;
   my $ar_settings = shift;

   open FILE, ">$filename" or GEN::DIEX( ": Cannot open file \"$filename\"\n$!",
                                         $PARSE_CL::die_config_error );

   my $i_setting = 0;

   SETTING:
   while ( $i_setting <= $#$ar_settings )
   {
      my $setting = $$ar_settings[ $i_setting ];

      if ( $setting eq "" )
      {
         GEN::DIE( "::WRITE_PURE_SETTINGS_FILE: Program error. Empty setting." );
      }

      my ( $opt, $param, $dn_setting, $error ) = GET_OPT( $i_setting, $ar_settings );

      if ( $dn_setting != 1 )
      {
         GEN::DIE( "::WRITE_PURE_SETTINGS_FILE: $error Bad setting \"$setting\"." );
      }

      my $lc_opt = lc( $opt );

      # Do not write -op settings to settings.via file
      #
      if ( ( $lc_opt eq "-op" ) ||
           ( $lc_opt eq "-outputpath" )
         )
      {
         $i_setting += $dn_setting;

         next SETTING;
      }

      # Browser fails if path is relative, so make absolute
      # Also comparison of paths fails if relative in QAC/QACPP.
      #
      # Write path parameters as follows:
      #
      # Actual                  Written
      # --------------------    -------------------------
      # -op C:\                 -op C:\
      # -op "C:\"               -op C:\
      # -op C:\Fred\            -op C:\Fred
      # -op "C:\Fred\"          -op C:\Fred
      # -op "C:\Fred Bloggs"    -op "C:\Fred Bloggs"
      # -op "C:\Fred Bloggs\"   -op "C:\Fred Bloggs"
      #
      # ( NB Core programs handling of -via files treats -op c:\temp\ as
      # introducing a continuation line.  Hence for core programs, 
      # settings.via use quotes, BUT for PURE settings files do not write
      # ...\" as the \" is an escaped quote and does not terminate the 
      # parameter.
      #      
      if ( OPTION_HAS_PATH_PARAMETER( $opt ) &&
           # Do following check to ensure that handling of PURE and PRQA
           # files is same, despite the fact that it is not necessary for PURE.
           # Option is not -fi, -si, -i or -q :
           ( ( $lc_opt ne "-forceinclude" ) &&
             ( $lc_opt ne "-fi" ) &&
             ( $lc_opt ne "-systeminclude" ) &&
             ( $lc_opt ne "-si" ) &&
             ( $lc_opt ne "-i" ) &&
             ( $lc_opt ne "-quiet" ) &&
             ( $lc_opt ne "-q" ) 
           )
         )
      {
         print FILE "$opt " . FORMAT_PURE_PATH_PARAMETER( $param ) . "\n";
      }
      else
      {
         # Problematic case of an option with a path parameter ending in \ 
         # (e.g. -i c:\) is handled immediately above.
         #
         print FILE FULL_OPTION( $opt, $param ) . "\n";
      }

      $i_setting += $dn_setting;
   }

   close FILE;
}

# ---------------------------------------------------------------------------

sub SEARCH_OPTION_STRING_FOR_OPTION_RECURSIVE
{
   # Usage: $ar_options_found = 
   #           SEARCH_OPTION_STRING_FOR_OPTION_RECURSIVE
   #              ( $option, $has_param_bool, $options_string );
   #
   # Searches $options_string and all options ie -via, and -list
   # must have been evaluated, and returns all options (with parameter) in
   # $ar_options_found.
   #
   # $options_string is a string containing options separated by " ", "\n" and
   # "\t", and which may be quoted (ie with embedded spaces), e.g.
   #
   #  "-file filename -filefilename \"-ipath xx\""
   #
   # NB This sub is not intelligent enough to search for option names
   # where the name has >1 forms.  e.g. If supplying "-forget", then
   # options like -forgetall will be wrongly returned as the parameter
   # will start with "all".
   #
   # Also can't search for -via or -list.
   #
   my $option         = shift;
   my $has_param_bool = shift;
   my $options_string = shift;

   my $ar_options_total = GET_ALL_OPTIONS_IN( $options_string );

   my $ar_options_found = 
      SEARCH_OPTION_SET_FOR_OPTION
         ( $option,
           $has_param_bool,
           $ar_options_total );

   return $ar_options_found;
}

# ---------------------------------------------------------------------------

sub SEARCH_OPTION_SET_FOR_OPTION
{
   # Usage: $ar_options_found = 
   #           SEARCH_OPTION_SET_FOR_OPTION
   #              ( $option, $has_param_bool, $ar_options );
   #
   # Searches $ar_options for option $option, and returns all options
   # (with parameter) in $ar_options_found.
   #
   # $ar_options is as might have been provided by supplying \@ARGV
   # ie it contains elements like:
   #
   #  -file 
   #  filename
   #  -filefilename
   #  -ipath
   #
   # NB This sub is not intelligent enough to search for option names
   # where the name has >1 forms.  e.g. If supplying "-forget", then
   # options like -forgetall will be wrongly returned as the parameter
   # will start with "all".
   #
   my $option         = shift;
   my $has_param_bool = shift;
   my $ar_options     = shift;

   my $ar_options_found = [];

   my $i = 0;

   OPTION:
   while ( $i <= $#$ar_options )
   {   
      my $opt = GEN::TRIM( $$ar_options[ $i ] );

      if ( $opt =~ /^$option$/i )
      {
         my $param;

         if ( $has_param_bool )
         {
            last OPTION if ( $i == $#$ar_options );

            $param = " " . GEN::TRIM( $$ar_options[ ++$i ] );
         }
         else
         {
            $param = "";
         }

         push @$ar_options_found, $option . $param;
      }
      elsif ( $opt =~ /^$option/i )
      {
         if ( $has_param_bool )
         {
            push @$ar_options_found, $option . " " . GEN::TRIM( substr( $opt, length( $option ) ) );
         }
         else
         {
            GEN::DIE( "::SEARCH_OPTION_SET_FOR_OPTION: " .
                      "Found illegal parameter \"$opt\" when searching for \"$option\"." );
         }
      }

      ++$i;
   }

   return $ar_options_found;
}

# ---------------------------------------------------------------------------

sub SEARCH_OPTION_SET_FOR_LIST_OF_OPTIONS
{
   # Usage: $ar_options_found = 
   #           SEARCH_OPTION_SET_FOR_LIST_OF_OPTIONS
   #              ( $option_list, $has_param_bool, $ar_options );
   #
   # Searches $ar_options for options listed in $option_list, and returns
   # all options (with parameter) in $ar_options_found.
   #
   # Example $option_list: "-i,-si-systeminclude"
   #
   # $ar_options is as might have been provided by supplying \@ARGV
   # ie it contains elements like:
   #
   #  -file 
   #  filename
   #  -filefilename
   #  -ipath
   #
   # NB This sub is not intelligent enough to search for option names
   # where the name has >1 forms.  e.g. If supplying "-forget", then
   # options like -forgetall will be wrongly returned as the parameter
   # will start with "all".
   #
   my $option_list    = shift;
   my $has_param_bool = shift;
   my $ar_options     = shift;

   my @options = split( ",", $option_list );

   my $ar_options_found = [];

   my $i = 0;

   OPTION:
   while ( $i <= $#$ar_options )
   {   
      my $opt = GEN::TRIM( $$ar_options[ $i ] );

      foreach my $option ( @options )
      {
         if ( $opt =~ /^$option$/i )
         {
            my $param;
   
            if ( $has_param_bool )
            {
               last OPTION if ( $i == $#$ar_options );
   
               $param = " " . GEN::TRIM( $$ar_options[ ++$i ] );
            }
            else
            {
               $param = "";
            }
   
            push @$ar_options_found, $option . $param;
         }
         elsif ( $opt =~ /^$option/i )
         {
            if ( $has_param_bool )
            {
               push @$ar_options_found, $option . " " . 
                    GEN::TRIM( substr( $opt, length( $option ) ) );
            }
            else
            {
               GEN::DIE( "::SEARCH_OPTION_SET_FOR_LIST_OF_OPTIONS: " .
                         "Found illegal parameter \"$opt\" when searching for \"$option\"." );
            }
         }
      }

      ++$i;
   }

   return $ar_options_found;
}

# ---------------------------------------------------------------------------

sub SEARCH_OPTION_SET_FOR_LAST_INSTANCE
{
   # Usage: $option_parameter = 
   #           SEARCH_OPTION_SET_FOR_LAST_INSTANCE
   #              ( $option_with_variants_list, $has_param_bool, $ar_options );
   #
   # Searches $ar_options for option $option, and returns parameter part of
   # the last instance of $option in $ar_options.
   # Returns $option_parameter as undef if no options in
   # $option_with_variants_list options are found.
   #
   # $ar_options is as might have been provided by supplying \@ARGV
   # ie it contains elements like:
   #
   #  -file 
   #  filename
   #  -filefilename
   #  -ipath
   #
   # NB This sub is designed to lookup one option which may have a number of
   # different forms.  E.G.:
   #
   # LOOKUP_OPTION_FOR_LAST_INSTANCE( "-op,-outputpath", 1, $ar_options )
   #
   # Will return the parameter of last option, be it -op or -outputpath.
   #
   my $option_with_variants_list = shift;
   my $has_param_bool            = shift;
   my $ar_options                = shift;

   my @options = split( ",", $option_with_variants_list );

   my $param = undef;

   my $i = 0;

   OPTION:
   while ( $i <= $#$ar_options )
   {   
      my $opt = GEN::TRIM( $$ar_options[ $i ] );

      foreach my $option ( @options )
      {
         if ( $opt =~ /^$option$/i )
         {
            if ( $has_param_bool )
            {
               last OPTION if ( $i == $#$ar_options );
   
               $param = GEN::TRIM( $$ar_options[ ++$i ] );
            }
            else
            {
               $param = "";
            }
         }
         elsif ( $opt =~ /^$option/i )
         {
            if ( $has_param_bool )
            {
               $param = GEN::TRIM( substr( $opt, length( $option ) ) );
            }
            else
            {
               GEN::DIE( "::SEARCH_OPTION_SET_FOR_LAST_OPTION: " .
                         "Found illegal parameter \"$opt\" when searching for \"$option\"." );
            }
         }
      }

      ++$i;
   }

   if ( defined( $param ) )
   {
      $param = GEN::UNQUOTE( $param );
   }

   return $param;
}

# ---------------------------------------------------------------------------

sub GET_ALL_OPTIONS_IN
{
   # Usage: $ar_options_total = GET_ALL_OPTIONS_IN( $options_string );
   #
   # Searches $options_string and nesting options ie -via, -list
   # and returns all options in $ar_options_total.
   #
   # $options_string is a string containing options separated by " ", "\n" and
   # "\t", and which may be quoted (ie with embedded spaces), e.g.
   #
   #  "-file filename -filefilename \"-ipath xx\""
   #
   # NB This sub will not catch user errors where an option spans 2 files.
   # e.g. it allows file_a to end in "-file" with no parameter, the next token
   # read will be assumed to be the parameter to the -file option.
   #
   my $options_string = shift;

   # Must split on space \t and \n (particularly \n) which is not done by 
   # GEN::SPLIT_COMMAND_LINE_STRING( $options_string );
   #   my @options = GEN::SPLIT_COMMAND_LINE_STRING( $options_string );
   my @options = GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS( " \t\n", $options_string );

   my $ar_options_total = [];

   my $i = 0;

   OPTION:
   while ( $i <= $#options )
   {
      my $opt = GEN::TRIM( $options[ $i ] );

      my $nested_filename = "";

      if ( $opt =~ /^(-via|-list)$/oi )
      {
         last OPTION if ( $i == $#options );

         $nested_filename = $options[ ++$i ];
      }
      elsif ( $opt =~ /^(-via|-list)/oi )
      {
         $nested_filename = substr( $opt, length( $1 ) );
      }
      else
      {
         push @$ar_options_total, $opt;
      }

      if ( $nested_filename ne "" )
      {
         my $loaded_opts = GEN::LOAD_FILE_AS_STRING( $nested_filename );

         my $ar_options_found = GET_ALL_OPTIONS_IN( $loaded_opts );

         push @$ar_options_total, @$ar_options_found;
      }

      ++$i;
   }

   return $ar_options_total;
}

# -- eof PARSE_CL.pm --------------------------------------------------------
