# ---------------------------------------------------------------------------
# 
# File:    RUN.pm
#
# Copyright (c) Programming Research, 2004.
#
# Written: C.Waddingham 20-Aug-04
#
# Version: 1.32
#
# Purpose: To provide QAC/PP and component tool execution for qaw.pl.
#
# History:
# 1.32   26-01-12  CW  - CR-14397 Incorporate Dataflow in .prj file.
# 1.31   23-08-11  CW  - CR14138 Global analysis not working.
# 1.30   21-03-11  CW  - EXECUTE_PRIMARY_ANALYSIS updated to only run errdsp check
#                        if old version of QAC.
#                      - DO_ENSURE_TXT and DO_ENSURE_HTML updated not to write
#                        .txt or .html file if it exists and outdates the .err file.
#                      - PARSE_PARAMETERS parameter handling restructured.
#                      - %O, %D, %J handling added/updated.
# 1.29   15-02-10  CW  - TSR9509. Allow search for file(s) in all folders of same name.
# 1.28   22-04-09  CW  - CR12636. Improve speed of -mode depend checking.
# 1.27   19-09-08  CW  - CR12970. -pdsp handling corrected.
# 1.26   24-07-08  CW  - CR12893. Project wide processing inhibited if analysis errors.
# 1.25   07-02-08  CW  - CR12691. Enable RP/EV handling.
# 1.24   01-11-07  CW  - 12558: Removed reading of qac[pp].cfg when 
#                        calling components.
# 1.23   21-02-07  CW  - Fixed problem deriving cmaf path when there are no op's.
# 1.22   24-10-06  CW  - CR11553. Corrected handling of no analysis files and -cargs.
#                      - CR11592,CR11593. System i/f to progs via 'array' where poss.
#                      - %F+ now expands to -outputpath instead of -op.
#                      - Handling of -forget corrected.
# 1.21   07-04-06  CW  - -etxt and -ehtml now always run (ie ignore possible
#                        execution savings due to timestamp dependencies.
# 1.20   09-02-06  CW  - Incorrect handling of parameter paths with spaces
#                        in options like -maseq, -saseq, -exec, -glob - corrected.
#                        Now can allow relative path in -file arg.
# 1.19   05-12-05  CW  - -op placed after -via persons for project file impl.
# 1.18   15-11-05  CW  - -cmaf <path> now included in QAC[PP] settings.via
#                        and settings.via used by errdsp, prjdsp, viewer, etc.
# 1.17   10-10-05  CW  - -cmaf files NOT deleted when -mode none.
#        19-10-05  CW  - -sfba option included to suppress file based analysis.
# 1.16   22-09-05  CW  - Ensured QA_BIN version of errdsp, prjdsp and viewer 
#                        called.
# 1.15   23-08-05  CW  - Modified to terminate on licencing error.
# 1.14   11-07-05  CW  - Filter added to post_anal_details and glob_anal_details.
#                      - Problem that .html concatenates corrected.
# 1.13   05-07-05  CW  - Count of proceesed files made per EXECUTE_ANALYSIS call.
# 1.12   03-06-05  CW  - Count of files processed output.
# 1.11   18-05-05  CW  - -pdsp[d] to use pure settings when getting -file opt.
# 1.10   06-05-05  CW  - <prj><folder>:<absolute path> handling added.
# 1.9    29-04-05  CW  - Settings.via trace updated.
# 1.8    24-04-05  CW  - PLOG file width enhanced.
# 1.7    18-04-05  CW  - GET_DISPLAY_LENGTH -> GET_DISPLAY_FORM_OF_FILES.
# 1.6    12-04-05  CW  - Order of global analysis steps updated.
# 1.5    27-03-05  CW  - Handling for -glob and -mode added.
# 1.4    24-03-05  CW  - EXECUTE_POST_ANALYSIS reworked re: %M, %E, %F & %F+.
#                      - Corrected WRITE_FILE_LIST. 
# 1.3    09-03-05  CW  - EXECUTE_ANALYSIS restructured.
# 1.2    23-02-05  CW  - PROCESS_PROJECT_FILE modified to inherit display
#                        options from the command line.
#                      - $post_processing_opts placed before params.
# 1.1    12-01-05  CW  - Handling of Cygwin added, including handling of
#                        Unix or Windows project file.
#                        Modified handling of -plog -format.
#                        -disp always runs irrespective of analysis errors.
#
# ---------------------------------------------------------------------------

package RUN; #===============================================================

use strict;

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

require QPARSE;
require MODE;
require GEN;
require OPC;
require PARSE_CL;
require PARAM;

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

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

   # Paths of option files containing -format option used by PERFORM_DIAGNOSTICS.
   #
   our $format_a = PARAM::QA_TEMP() . "format_a";
   our $format_b = PARAM::QA_TEMP() . "format_b";

   # File format_a has fixed content:
   #
   GEN::WRITE_FILE( $format_a, ( "-format \%g" ) );

   # $custom_format_specified set true if default content of file FORMAT_B
   # has been overwritten with a User specified -format option.
   # Force to false at initialisation, subsequent occurence of -format
   # in EXECUTE_ANALYSIS will set to true.
   # NB reinitialised at start of EXECUTE_ANALYSIS for each file-set.
   #
   our $custom_format_specified = 0;   # false

   # fdisp source control flag
   #
   our $fdisp_initialised = 0;

   # $filename_display_width controls the width of the -plog list display.
   # See FILENAME_DISPLAY_WIDTH().
   #
   our $filename_display_width = 56;

   # Count of number of files processed by EXECUTE_ANALYSIS().
   #
   our $processed_files_count;
}

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

sub FORMAT_A
{
   # Usage: $format_a = FORMAT_A();
   #
   return $RUN::format_a;
}

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

sub FORMAT_B
{
   # Usage: $format_b = FORMAT_B();
   #
   return $RUN::format_b;
}

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

sub SET_CUSTOM_FORMAT_SPECIFIED
{
   # Usage: SET_CUSTOM_FORMAT_SPECIFIED( $setting_bool );
   #
   $RUN::custom_format_specified = shift;
}

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

sub GET_CUSTOM_FORMAT_SPECIFIED
{
   # Usage: $selected_bool = GET_CUSTOM_FORMAT_SPECIFIED();
   #
   return $RUN::custom_format_specified;
}

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

sub SET_FILENAME_DISPLAY_WIDTH
{
   # Usage: SET_FILENAME_DISPLAY_WIDTH( $setting_int );
   #
   $RUN::filename_display_width = shift;
}

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

sub EXECUTE_ANALYSIS
{
   # Usage: EXECUTE_ANALYSIS( @arguments );
   #
   my @arguments = @_;

   $RUN::processed_files_count = 0;

   #-- Parse @arguments -----------------------------------------------------
   #
   # This is the first executed call to PARSE_PARAMETERS.
   # It reads the supplied command line and sets up ar_qa_opts for analysis of
   # files on the command line.
   # Projects on the command line call PARSE_PARAMETERS for each individual 
   # project spec.  These are passed $ar_qa_opts with embedded $PARSE_CL::zzz_marker $count
   # arguments to indicate the point in the command line at which they apply.
   # This is used to divide the options.  Note -EXEC, -SASEQ, -GLOB and -MASEQ
   # do not survive calls to PARSE_PARAMETERS, they are removed before by
   # PARSE_PARAMETERS and are not passed in $ar_qa_opts.
   #
   my ( $ar_qa_opts,
        $ar_global_analysis_details,
        $ar_post_analysis_details,
        $hr_qaw_options,
        $ar_cargs_parameters,
        $ar_file_names,
        $ar_file_outputpaths ) =
           PARSE_PARAMETERS( @arguments );

   if ( ( $#$ar_file_names == -1 ) &&
        ( $#$ar_cargs_parameters == -1 )
      )
   {
      # Here when no files supplied, therefore exit.
      #
      OPC::PUT_ERR_LINE( "No files specified"  );

      return;
   }

   # Here when supplying command line analysis parameters.
   # Want to default to special -cmaf setting for command line based analysis
   # i.e. not project file based analysis (which derives its own cmaf setting).
   # So prepend it here as follows.  NB If user has supplied his own -cmaf 
   # setting on the command line then that will override.
   #
   # Calculate the cmaf_path as a contrived path which is taken as the output
   # path of the first file being analysed.  IE:
   # <first file output path>/PARAM::CMAF_PROJECT_NAME().
   #
   my $cmaf_path;
   if ( $#$ar_file_outputpaths != -1 )
   {
      $cmaf_path = GEN::APPLY_FINAL_SEP( $$ar_file_outputpaths[ 0 ] ) .
      		      PARAM::CMAF_PROJECT_NAME();
   }
   else
   {
      # Following is not needed as we wont be processing any files with QAC/PP
      #
      $cmaf_path = PARAM::CMAF_PROJECT_NAME();
   }

   unshift @$ar_qa_opts, "-cmaf $cmaf_path";

   # Reset $cmaf_path to that actually used.
   #
   $cmaf_path = PARSE_CL::SEARCH_OPTION_SET_FOR_LAST_INSTANCE( "-cmaf", 1, $ar_qa_opts );

   #-- Commence processing of files -----------------------------------------

   if ( $#$ar_file_names != -1 )
   {
      # Write the settings for Primary Analysis purposes:
      #
      # Note that the settings are written in PROCESS_FILESET to include the
      # command line specified -cmaf setting (if present) or the default one
      # of PARAM::CMAF_PROJECT_NAME().
      #
      # Note that for Project File based analysis -cmaf is explicitly calculated
      # in PROCESS_PROJECT_FILE() and applied to the personality set of each folder
      # analysed as the last personality setting (therefore the effective setting).
      #
      # Note that the settings are also rewritten after Primary Analysis is complete
      # prior to Project Wide Analysis, hence enabling Project Wide Analysis to
      # use the correct cmaf setting (in the settings.via file).  
      #
      # Note that in Project Wide Analysis identical cmaf settings are written to
      # the settings.via and filelist.lst files.
   
      my $analysis_errors = EXECUTE_FILE_BASED_ANALYSIS_PROCESSING
   	 ( $ar_qa_opts,
   	   $ar_global_analysis_details,
   	   $ar_post_analysis_details,
   	   $hr_qaw_options,
   	   $ar_file_names,
   	   $ar_file_outputpaths );

      if ( $analysis_errors )
      {
         if ( FILE_COUNT_GT_ZERO( $ar_file_names ) )
         {
            GIVE_ANALYSIS_ERRORS_WARNING( $ar_global_analysis_details );
         }
      }

      #-- Analysis of file set complete.  Perform project wide processing ------
      #

      # Following settings are used for expansion of %O and %D for command line case.
      #
      my $root_source_path = "";
      my $root_output_path = "";

      EXECUTE_PROJECT_WIDE_PROCESSING
         ( $cmaf_path,
           $root_source_path,
           $root_output_path,
           $ar_qa_opts,
           $ar_global_analysis_details,
           $hr_qaw_options,
           $ar_file_names,
           $ar_file_outputpaths,
           ! $analysis_errors );
   }

   #-- QAC/PP analysis of files complete. Perform -cargs compilation -----
   #
   if ( $#$ar_cargs_parameters > -1 )
   {
      my $return_code = EXECUTE_CARGS_FILES( $ar_cargs_parameters );

      if ( $return_code )
      {
         GEN::EXIT( 255 );
      }
   }

   #-- Output number of files processed -------------------------------
   #
   OPC::INITIALISE_PLOG_BUFFER();
   
   my $n_files = $RUN::processed_files_count;
   
   if ( $n_files == 1 )
   {   
      OPC::PLOG( "1 File processed.\n" );
   }
   else
   {   
      OPC::PLOG( $n_files . " Files processed.\n" );
   }
   
   OPC::SHOW_PLOG();
}

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

sub PARSE_PARAMETERS
{
   # Usage: ( $ar_qa_opts,
   #          $ar_global_analysis_details,
   #          $ar_post_analysis_details,
   #          $hr_qaw_options,
   #          $ar_cargs_parameters,
   #          $ar_file_names,
   #          $ar_file_outputpaths ) =
   #             PARSE_PARAMETERS( @arguments );
   #
   # Process @arguments list, separating into:
   #   $ar_qa_opts                 - standard qac/pp options.
   #   $ar_global_analysis_details - global analysis program(s) and parameters.
   #   $ar_post_analysis_details   - post analysis program(s) and parameters.
   #   $hr_qaw_options             - QAW control options like -DISP,
   #   $ar_cargs_parameters        - tokens to right of -cargs (translated).
   #   $ar_file_names              - file names to be analysed.
   #   $ar_file_outputpaths        - -op settings for $ar_file_names.
   #
   # NB On exit ar_qa_opts is in supplied order form.
   # ar_qa_opts is later reordered and converted to preferred forms by 
   # QPARSE::REDUCE_PARSE_OPTIONS in:
   #   EXECUTE_PROJECT_WIDE_PROCESSING and
   #   PROCESS_FILESET.
   #
   # NB Also that on exit ar_qa_opts has paths resolved for RP/EV on Windows only.

   my @arguments = @_;

   #-- Parse @arguments for following ---------------------------------------
   #
   #  $ar_options
   #  $ar_file_names
   #  $ar_post_analysis_details
   #
   # $outputpath can differ for each source file listed on the command line, or
   # within -list files.
   # When analysing projects, having local $outputpath means
   # that the setting of $outputpath within a project is not 
   # carried through to the next analysis after the current
   # project.  Instead $outputpath is set to the command-line
   # setting.
   #
   # 
   # $ar_options;                # - List of qac/++ settings this analysis.
   # $ar_file_names;             # - List of files for this analysis.
   # $ar_file_outputpaths;       # - List of respective -op values for ar_file_names.
   # $ar_post_analysis_details;  # - List of post analysis settings this analysis.
   #                             #   Settings are post-analysis programs and 
   #                             #   their parameters.

   GEN::TRACE( "---------------------------------------------------------------------------\n" );
   GEN::TRACE( "Parsing Parameters:\n" .
               join( "\n", @arguments ) . "\n\n" );

   my ( $ar_options,
        $ar_file_names,
        $ar_file_outputpaths,
        $ar_cargs_parameters ) =
      PARSE_CL::QAW_PARSE_COMMAND_LINE
         ( \@arguments,
           PARAM::QA_PROGRAM(),
           PARAM::QA_OUTPATH() );

   #-- If no files, then do nothing -----------------------------------------
   #
   if ( $#$ar_file_names == -1 )
   {
      # Here when there are not any files to analyse
      #

      # Return empty arrays as behaviour of calling function on 
      # $ar_file_names empty is that result is to be treated as empty.
      #
      return ( [],
               [],
               [],
               {},
               $ar_cargs_parameters,
               [],
               [] );
   }        

   #-- Make file names and paths clean --------------------------------------
   #
   ( $ar_file_names, $ar_file_outputpaths, $ar_options ) =
        CLEAN_FILE_NAMES
           ( $ar_file_names,
             $ar_file_outputpaths,
             $ar_options );

   #-- Unescape any \" - That is, escaped quotes ----------------------------
   #
   # NB PARSE_CL accepts options with embedded quotes.  This is achieved by
   # the user escaping the quotes on the command-line ( e.g. \" ).  
   # PARSE_CL::FULL_OPTION then calls QUOTE_ARG to reassemble the options
   # and QUOTE_ARG has the effect of re-escaping any embedded quotes.
   # Hence remove those re-escaped quotes now.
   #
   # NB The potential problem with settings.via paths that end in ...\name\ or 
   # any path ending in ...\name\" is now ok.
   #
   # 17-Dec-04: Suppress this at this stage.  i.e. for all options ($ar_options)
   #            keep escaped quotes (made in PARSE_CL::FULL_OPTION).
   #            At later stage need to review the need to apply special
   #            handling of embedded quotes in 'parameters' section of -exec
   #            and -glob.
   #
   #   $ar_options = QPARSE::UNESCAPE_QUOTES( $ar_options );

   #-- Get QAW options that are to be propagated to project analysis --------
   #
   my $hr_qaw_options = QPARSE::GET_QAW_OPTS( $ar_options );

   #-- Set the message and error logging parameters -------------------------
   #
   OPC::SET_MESSAGE_LOGGING_PARAMETERS( $$hr_qaw_options{ "-logmsgs" } );
   OPC::SET_ERROR_LOGGING_PARAMETERS( $$hr_qaw_options{ "-logerrs" } );

   #-- Set the -plog settings -----------------------------------------------
   #
   OPC::SET_PLOG_SELECTED( exists $$hr_qaw_options{ "-plog" } );

   #-- Parse the option list for -exec and -glob options etc ----------------
   #
   my $ar_post_analysis_details = [];
   my $ar_global_analysis_details = [];

   # Handle -maseq, -saseq, -exec, -glob, -rem, -canal, -crep
   #
   ( $ar_options,
     $ar_post_analysis_details,
     $ar_global_analysis_details ) =
      QPARSE::PARSE_FOR_EXEC_AND_GLOB( $ar_options );

   #-- Ensure programs exist for specified options --------------------------
   #
   CHECK_SETTINGS( $ar_post_analysis_details, $hr_qaw_options );

   #-- Write option summaries to trace log ----------------------------------
   #
   if ( $#$ar_post_analysis_details != -1 )
   {
      if ( $#$ar_post_analysis_details == 0 )
      {
         GEN::TRACE( "POST PROCESS COMMAND:\n" );
      }
      else
      {
         GEN::TRACE( "POST PROCESS COMMANDS:\n" );
      }
   
      foreach my $post_anal_details ( @$ar_post_analysis_details )
      {
         my ( $prog, $params, $error_prog ) = split( "\n", $post_anal_details );
   
         GEN::TRACE( GEN::QUOTE_ARG( $prog ) . " " . $params . "\n" );
      }
   }
   GEN::TRACE( "\n" );

   #-- Set the mode option --------------------------------------------------
   #
   $$hr_qaw_options{ "-mode" } = MODE::GET_MODE_OPTION( $$hr_qaw_options{ "-mode" } );

   #-- Perform partial finalisation of analysis settings --------------------
   #
   my $ar_qa_opts = QPARSE::REMOVE_QAW_OPTIONS( $ar_options );

   return ( $ar_qa_opts,
            $ar_global_analysis_details,
            $ar_post_analysis_details,
            $hr_qaw_options,
            $ar_cargs_parameters,
            $ar_file_names,
            $ar_file_outputpaths );
}

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

sub WRITE_ERRDSP_MESSAGE_FORMAT_B
{
   # Usage WRITE_ERRDSP_MESSAGE_FORMAT_B( $ar_qa_opts );
   #
   my $ar_qa_opts = shift;

   #-- Write failure message format for errdsp control ----------------------
   #
   # See RUN::PERFORM_DIAGNOSTICS()
   #

   my $ar_format_options = PARSE_CL::SEARCH_OPTION_SET_FOR_OPTION
                     ( "-format", 1, $ar_qa_opts );

   if ( $#$ar_format_options == 0 )
   {
      # Here with exactly one -format option.
      # Use this option for the plog diagnostic output.
      #
      GEN::WRITE_FILE( FORMAT_B(), ( $$ar_format_options[ 0 ] ) );

      SET_CUSTOM_FORMAT_SPECIFIED( 1 );   # true
   }
   else
   {
      # Here when using QAW default format_b
      #
      GEN::WRITE_FILE( FORMAT_B(), ( "-format \"\%f;(\%l) \%N:\%t\"" ) );

      SET_CUSTOM_FORMAT_SPECIFIED( 0 );   # false
   }
}

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

sub WRITE_SETTINGS_FILES
{
   # Usage: WRITE_SETTINGS_FILES( $ar_qa_opts_supplied );
   #
   my $ar_qa_opts_supplied = shift;

   my $ar_qa_opts = [ REMOVE_GLOB_AND_POST_OPTS( @$ar_qa_opts_supplied ) ];

   PARAM::INCREMENT_SETTINGS_VIA_INSTANCE_NUMBER();

   unlink PARAM::SETTINGS_VIA();

   PARSE_CL::WRITE_SETTINGS_FILE( PARAM::SETTINGS_VIA(), $ar_qa_opts );

   GEN::TRACE( "Written settings.via file: \"" . PARAM::SETTINGS_VIA() . "\"\n\n" );

   # 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.
   # 

   unlink PARAM::PURE_SETTINGS_VIA();

   PARSE_CL::WRITE_PURE_SETTINGS_FILE( PARAM::PURE_SETTINGS_VIA(), $ar_qa_opts );

   GEN::TRACE( "Written *pure* settings.via file: \"" . PARAM::PURE_SETTINGS_VIA() . "\"\n\n" );
}

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

sub WRITE_FILE_LISTS
{
   # Usage: WRITE_FILE_LISTS( $ar_file_names,
   #        		      $ar_file_outputpaths,
   #        		      $cmaf_path );
   #
   my $ar_file_names       = shift;
   my $ar_file_outputpaths = shift;
   my $cmaf_path           = shift;

   PARAM::INCREMENT_FILE_LIST_INSTANCE_NUMBER();

   unlink PARAM::QA_FILELIST();

   my $ignore_file_count = WRITE_FILE_LIST( PARAM::QA_FILELIST(),
                     			    $ar_file_names,
                     			    $ar_file_outputpaths,
                     			    $cmaf_path );

   GEN::TRACE( "Written filelist.lst file: \"" . PARAM::QA_FILELIST() . "\"\n\n" );

   unlink PARAM::QA_PURE_FILELIST();

   my $ignore = WRITE_PURE_FILE_LIST( PARAM::QA_PURE_FILELIST(),
                                      $ar_file_names,
                                      $ar_file_outputpaths,
                                      $cmaf_path );

   GEN::TRACE( "Written *pure* filelist.lst file: \"" . PARAM::QA_PURE_FILELIST() . "\"\n\n" );

   return;
}

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

sub CHECK_SETTINGS
{
   # Usage: CHECK_SETTINGS( $ar_post_analysis_details,
   #                        $hr_qaw_options );
   #
   # Ensures that required programs exist for supplied options.
   #
   my $ar_post_analysis_details = shift;
   my $hr_qaw_options           = shift;

   #-- Check analyser program base-names are not 'overloaded' ---------------
   #
   # PARAM::ANALYSER_RETURN_CODE_LOOKUP() uses base name of analyser programs
   # (including post), to lookup for ( message, fail, perform_diagnostics,
   # code_recognised ).
   # Hence cannot have 2 analysers with same base-name but different
   # program (ie full path is different).
   # Check this now for all analysers (primary and post) for current
   # analysis.
   #
   DIE_IF_ANALYSER_NAMES_OVERLOADED( $ar_post_analysis_details );
      
   #-- If -view specified, check that this can be executed ------------------
   #
   if ( exists $$hr_qaw_options{ "-view" } )
   {
      # Check that viewer is available
      #
      if ( ! GEN::EXECUTABLE_EXISTS( PARAM::QA_BIN() . "viewer" ) )
      {
         GEN::DIE( ": -view selected but viewer not available" );
      }
   }

   #-- Check errdsp available if needed -------------------------------------
   #
   if ( ! PARAM::ERRDSP_EXISTS() )
   {
      if ( exists $$hr_qaw_options{ "-disp" } )
      {
         GEN::DIE( ": -disp selected but errdsp not available" );
      }
      if ( exists $$hr_qaw_options{ "-fdisp" } )
      {
         GEN::DIE( ": -fdisp selected but errdsp not available" );
      }
      if ( exists $$hr_qaw_options{ "-ehtml" } )
      {
         GEN::DIE( ": -ehtml selected but errdsp not available" );
      }
      if ( exists $$hr_qaw_options{ "-etxt" } )
      {
         GEN::DIE( ": -etxt selected but errdsp not available" );
      }
   }

   #-- Check prjdsp and errdsp available if needed --------------------------
   #
   if ( ( exists $$hr_qaw_options{ "-pdsp" } ) ||
        ( exists $$hr_qaw_options{ "-pdspd" } )
      )
   {
      if ( ! GEN::EXECUTABLE_EXISTS( PARAM::QA_BIN() . "prjdsp" ) )
      {
         GEN::DIE( ": -pdsp selected but prjdsp not available" );
      }

      if ( ! PARAM::ERRDSP_EXISTS() )
      {
         GEN::DIE( ": -pdsp selected but errdsp not avaliable" );
      }
   }
}

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

sub GIVE_ANALYSIS_ERRORS_WARNING
{
   # Usage: GIVE_ANALYSIS_ERRORS_WARNING( $ar_global_analysis_details );
   #
   my $ar_global_analysis_details = shift;

   OPC::INITIALISE_PLOG_BUFFER();
   OPC::PUT_ERR( "ERROR: Analysis errors" );
   if ( $#$ar_global_analysis_details != -1 )
   {
      OPC::PUT_ERR( " - No project wide analysis" );
   }
   OPC::PUT_ERR( ".\n\n" );
   OPC::SHOW_PLOG();
   OPC::INITIALISE_PLOG_BUFFER();
}

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

sub EXECUTE_FILE_BASED_ANALYSIS_PROCESSING
{
   # Usage:
   #   $analysis_errors = EXECUTE_FILE_BASED_ANALYSIS_PROCESSING
   #           ( $ar_qa_opts,
   #             $ar_global_analysis_details,
   #             $ar_post_analysis_details,
   #             $hr_qaw_options,
   #             $ar_file_names,
   #             $ar_file_outputpaths );
   #
   my $ar_qa_opts                 = shift;
   my $ar_global_analysis_details = shift;
   my $ar_post_analysis_details   = shift;
   my $hr_qaw_options             = shift;
   my $ar_file_names              = shift;
   my $ar_file_outputpaths        = shift;

   #-- Create list of post analyser version info ----------------------------
   #
   my $ar_post_analysers_spec = [];

   foreach my $post_anal_details ( @$ar_post_analysis_details )
   {
      my ( $prog, $params, $error_prog, $filter ) = split( "\n", $post_anal_details );

      my $analyser_spec = QPARSE::GET_POST_ANALYSER_SPEC( $prog, $error_prog );

      push @$ar_post_analysers_spec, $analyser_spec;
   }

   # Initialise return boolean:
   #
   my $analysis_errors = 0;  # false

   # Analyse project files first.
   #

   # $count is an indicator of which project file instance has been encountered
   # on the command line.
   # It is passed in the $ar_qa_opts as a $PARSE_CL::zzz_marker <number> option.
   #
   # Note that $ar_post_analysis_details are now passed as well.
   # -exec or -saseq options on the command line must also be merged into the
   # list of those before the project, the project specified ones, and those
   # after the project.
   # This must be done in the -rem handling code.
   #
   my $count = 0;

   foreach my $file_name ( @$ar_file_names )
   {
      if ( IS_PROJECT_FILE( $file_name ) )
      {
         #-- Here when analysing a project file -----------------------------
         #
         PROCESS_PROJECT_FILE
            ( $file_name,
              $hr_qaw_options,
              $ar_global_analysis_details,
              $ar_qa_opts,
              $ar_post_analysis_details,
              $count );

         ++$count;
      }
   }

   # Convert hash of qaw options into array of full form options
   #
   my @qaw_options = ();

   foreach my $opt ( keys %$hr_qaw_options )
   {
      push @qaw_options, PARSE_CL::FULL_OPTION( $opt, $$hr_qaw_options{ $opt } );
   }

   # Now analyse simple files, ie on command line or part of a project.
   #

   FILE_NAME:
   foreach my $file_name ( @$ar_file_names )
   {
      if ( ! IS_PROJECT_FILE( $file_name ) )
      {
         #-- Here when analysing individual files ---------------------------
         #
         $analysis_errors =
            PROCESS_FILESET( $hr_qaw_options,
                             $ar_qa_opts,
                             $ar_post_analysers_spec,
                             $ar_file_names,
                             $ar_file_outputpaths,
                             $ar_post_analysis_details,
                             $ar_global_analysis_details );

         last FILE_NAME;
      }
   }

   return $analysis_errors;
}

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

sub EXECUTE_PROJECT_WIDE_PROCESSING
{
   # Usage: EXECUTE_PROJECT_WIDE_PROCESSING
   #           ( $cmaf_path,
   #             $root_source_path,
   #             $root_output_path,
   #             $ar_qa_opts_supplied,
   #             $ar_global_analysis_details,
   #             $hr_qaw_options,
   #             $ar_file_names,
   #             $ar_file_outputpaths,
   #             $perform_global_analysis ); 
   #
   # NB The $cmaf_path caters for %J options within the global
   # analysis parameters.  i.e. %J is not expanded in QPARSE::PARSE_GLOB_OPT,
   # it is expanded in EXECUTE_GLOBAL_ANALYSIS_PROGRAMS().  To do this,
   # $cmaf_path is needed.
   # Likewise $root_source_path and $root_output_path are supplied purely
   # to satisfy %O and %D replacements in EXECUTE_GLOBAL_ANALYSIS_PROGRAMS().
   #
   my $cmaf_path                  = shift;
   my $root_source_path           = shift;
   my $root_output_path           = shift;
   my $ar_qa_opts_supplied        = shift;
   my $ar_global_analysis_details = shift;
   my $hr_qaw_options             = shift;
   my $ar_file_names              = shift;
   my $ar_file_outputpaths        = shift;
   my $perform_global_analysis    = shift;

   # Certainly when we have a mixture of projects and plain files on the
   # command line, then we can reach this point with some -zzz_markers in
   # $ar_qa_opts, so ensure they are filtered out now.
   #
   my $ar_qa_opts = [];
   for my $opt ( @$ar_qa_opts_supplied )
   {
      if ( GEN::LEFT( $opt, length( $PARSE_CL::zzz_marker ) ) ne $PARSE_CL::zzz_marker )
      {
         push @$ar_qa_opts, $opt;
      }
   }

   if ( $$hr_qaw_options{ "-mode" } ne "NONE" )
   {
      # Ensure any previous cmaf files are deleted - Unless first global
      # analysis program is pal.
      #
      if ( $#$ar_global_analysis_details != -1 )
      {
   	 my ( $glob_analysis_program,
   	      $glob_analysis_params,
   	      $glob_analysis_renderer,
   	      $glob_renderer_param,
   	      $glob_raw_param,
   	      $glob_filter ) = split( "\n", $$ar_global_analysis_details[ 0 ] );

         my $prog = lc( GEN::BASE_NAME( $glob_analysis_program ) );

         if ( ( $prog ne "pal" ) &&
              ( $prog ne "pal.exe" )
            )
         {
            GEN::DELETE_FILE_OR_DIRECTORY( "$cmaf_path.err" );
            GEN::DELETE_FILE_OR_DIRECTORY( "$cmaf_path.met" );
         }
      }
   }

   if ( ( FILE_COUNT_GT_ZERO( $ar_file_names ) ) &&
   	( ( exists $$hr_qaw_options{ "-ehtml" } ) ||
   	  ( exists $$hr_qaw_options{ "-etxt" } ) ||
   	  ( exists $$hr_qaw_options{ "-pdsp" } ) ||
   	  ( exists $$hr_qaw_options{ "-pdspd" } ) ||
   	  ( exists $$hr_qaw_options{ "-view" } ) ||
   	  ( $#$ar_global_analysis_details != -1 )
   	)
      )
   {
      # Force cmaf_path into settings file 
      #
      push @$ar_qa_opts, "-cmaf $cmaf_path";
      
      $ar_qa_opts = QPARSE::REDUCE_PARSE_OPTIONS( $ar_qa_opts );
      
      WRITE_SETTINGS_FILES( $ar_qa_opts );
      
      WRITE_ERRDSP_MESSAGE_FORMAT_B( $ar_qa_opts );
   
      WRITE_FILE_LISTS( $ar_file_names,
   			$ar_file_outputpaths,
   			$cmaf_path );

      my $stop_on_fail = exists( $$hr_qaw_options{ "-stoponfail" } );

      my $hr_display_files = GET_GLOBAL_ANALYSIS_DISPLAY_FORM_OF_FILES
                                ( $ar_file_names,
                                  $ar_global_analysis_details );

      if ( ( exists $$hr_qaw_options{ "-ehtml" } ) ||
           ( exists $$hr_qaw_options{ "-pdsp" } ) ||
           ( exists $$hr_qaw_options{ "-pdspd" } )
         )
      {
         DO_ENSURE_HTML
            ( $ar_file_names,
              $ar_file_outputpaths );
      }

      if ( exists $$hr_qaw_options{ "-etxt" } )
      {
         DO_ENSURE_TXT
            ( $ar_file_names,
              $ar_file_outputpaths );
      }

      if ( $perform_global_analysis )
      {
         EXECUTE_GLOBAL_ANALYSIS_PROGRAMS
            ( $cmaf_path,
              $root_source_path,
              $root_output_path,
              $ar_global_analysis_details,
              $stop_on_fail,
              $hr_display_files,
              $ar_qa_opts,
              $ar_file_names,
              $ar_file_outputpaths,
              $hr_qaw_options );
      }
     
      if ( exists $$hr_qaw_options{ "-pdsp" } )
      {
         INVOKE_PRJDSP
            ( $$hr_qaw_options{ "-pdsp" },
              $ar_qa_opts,
              $stop_on_fail,
              $hr_display_files );
      }

      if ( exists $$hr_qaw_options{ "-pdspd" } )
      {
         INVOKE_PRJDSP
            ( PARAM::BROWSER_NAME(),
              $ar_qa_opts,
              $stop_on_fail,
              $hr_display_files );
      }

      if ( exists $$hr_qaw_options{ "-view" } )
      {
         INVOKE_VIEWER();
      }
   }
}

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

sub PROCESS_FILESET
{
   # Usage: 
   #    $analysis_errors =
   #        PROCESS_FILESET( $hr_qaw_options,
   #                         $ar_qa_opts,
   #                         $ar_post_analysers_spec,
   #                         $ar_file_names,
   #                         $ar_file_outputpaths,
   #                         $ar_post_analysis_details,
   #                         $ar_global_analysis_details );
   #
   my $hr_qaw_options             = shift;
   my $ar_qa_opts_supplied        = shift;
   my $ar_post_analysers_spec     = shift;
   my $ar_file_names              = shift;
   my $ar_file_outputpaths        = shift;
   my $ar_post_analysis_details   = shift;
   my $ar_global_analysis_details = shift;

   my $analysis_errors = 0;  # false

   # -sfba present means suppress file based analysis, so only do file based
   # analysis if this option is absent.
   #
   if ( exists( $$hr_qaw_options{ "-sfba" } ) )
   {
      return $analysis_errors;
   }

   # Certainly when we have a mixture of projects and plain files on the
   # command line, then we can reach this point with some -zzz_markers in
   # $ar_qa_opts, so ensure they are filtered out now.
   #
   my $ar_qa_opts = [];
   for my $opt ( @$ar_qa_opts_supplied )
   {
      if ( GEN::LEFT( $opt, length( $PARSE_CL::zzz_marker ) ) ne $PARSE_CL::zzz_marker )
      {
         push @$ar_qa_opts, $opt;
      }
   }

   my $mode_option = $$hr_qaw_options{ "-mode" };

   # Read file name and -op in reverse order to create list of files
   # that have duplicate output files and therefore only the last file
   # should be analysed.  NB can have different source path with same
   # basename and op, hence want the results for the last duplicate  
   # encountered to stand.
   #

   my @analysis_results_subsequently_overwritten = ( 0 ) x scalar( @$ar_file_names );  # all false

   my %results_lookup = ();

   for ( my $i = $#$ar_file_names; $i >= 0; --$i )
   {
      my $file_name       = $$ar_file_names[ $i ];
      my $file_outputpath = $$ar_file_outputpaths[ $i ];

      my $base_file_name   = GEN::BASE_NAME( $file_name );
      my $result_base_file = GEN::APPLY_FINAL_SEP( $file_outputpath ) . $base_file_name;

      if ( ! exists( $results_lookup{ $result_base_file } ) )
      {
         $results_lookup{ $result_base_file } = "any";
      }
      else
      {
         # Mark this file as a duplicate.
         #
         $analysis_results_subsequently_overwritten[ $i ] = 1;  # true
      }
   }
          
   my $stop_on_fail = exists $$hr_qaw_options{ "-stoponfail" };

   my $hr_display_files  = GET_DISPLAY_FORM_OF_FILES( $ar_file_names );
   my $dummy_time_string = " " x ( 2 * length( GEN::TIME() ) + 4 );

   my $settings_written = 0; #false

   FILE_NAME:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name       = $$ar_file_names[ $i ];
      my $file_outputpath = $$ar_file_outputpaths[ $i ];

      if ( IS_PROJECT_FILE( $file_name ) )
      {
         # Here when we have a project file 
         # Project files are analysed in sub EXECUTE_ANALYSIS
         # So skip here
         #
         next FILE_NAME;
      }
      else
      {
         #-- Here when analysing a source file ------------------------------
         #

         # Increment count of files processed
         #
         ++$RUN::processed_files_count;
        
   	 # Ensure settings.via is written
   	 #
   	 if ( ! $settings_written )
   	 {
   	    $ar_qa_opts = QPARSE::REDUCE_PARSE_OPTIONS( $ar_qa_opts );
   	 
   	    WRITE_SETTINGS_FILES( $ar_qa_opts );
   	 
   	    WRITE_ERRDSP_MESSAGE_FORMAT_B( $ar_qa_opts );
   	 
   	    $settings_written = 1;  # true
   	 }

         # Ensure output directory exists:
         #
         GEN::MK_PATH( $file_outputpath );

         my $perform_analysis = 0;  # false

         if ( $mode_option eq "NONE" )
         {
            my $message = $dummy_time_string . " No analysis\n";

            OPC::INITIALISE_PLOG_BUFFER();
            OPC::PLOG( $$hr_display_files{ $file_name } . $message );
            OPC::SHOW_PLOG();
         }

         elsif ( $analysis_results_subsequently_overwritten[ $i ] )
         {
            my $message = $dummy_time_string . " Analysis subsequently overwritten\n";
            
            OPC::INITIALISE_PLOG_BUFFER();
            OPC::PLOG( $$hr_display_files{ $file_name } . $message );
            OPC::SHOW_PLOG();
         }

         else
         {
            # Compute whether analysis is required.
            #
            $perform_analysis = MODE::EVALUATE_ANALYSIS_REQUIREMENT
                                   ( $mode_option,
                                     $file_name,
                                     $file_outputpath,
                                     $ar_qa_opts,
                                     $ar_post_analysers_spec,
                                     $ar_post_analysis_details,
                                     $ar_global_analysis_details
                                   );

            if ( ! $perform_analysis )
            {
               my $message = $dummy_time_string . " Analysis already current\n";
               
               OPC::INITIALISE_PLOG_BUFFER();
               OPC::PLOG( $$hr_display_files{ $file_name } . $message );
               OPC::SHOW_PLOG();
            }
         }

         if ( $perform_analysis )
         {
            #-- Here when performing analysis -------------------------------
            #

            # Write the .opt file
            #
            MODE::WRITE_OPT_FILE
               ( $file_name,
                 $file_outputpath,
                 $ar_qa_opts,
                 $ar_post_analysers_spec,
                 $ar_post_analysis_details,
                 $ar_global_analysis_details );

            #-- Perform analysis --------------------------------------------
            #

            OPC::INITIALISE_PLOG_BUFFER();
            OPC::PLOG( $$hr_display_files{ $file_name } . " " . GEN::TIME() . " - " );
            OPC::SHOW_PLOG();
            OPC::INITIALISE_PLOG_BUFFER();

            my $post_analysis_specified = $#$ar_post_analysis_details != -1;

            my ( $failure_in_primary_analysis,
                 $primary_analysis_return_code,
                 $error_logged_for_primary_analysis ) =
               EXECUTE_PRIMARY_ANALYSIS
                  ( $file_name,
                    $file_outputpath,
                    $post_analysis_specified );

            my $inc_file_name = GEN::APPLY_FINAL_SEP( $file_outputpath ) .
                                GEN::BASE_NAME( $file_name ) . ".inc";

            $analysis_errors |= $error_logged_for_primary_analysis;

            if ( $error_logged_for_primary_analysis ||
                 $failure_in_primary_analysis 
               )
            {
               OPC::SHOW_PLOG();

               # Absence of .inc implies analysis is necessary,
               # therefore delete in file because of this analysis failure.
               #
               GEN::DELETE_FILE( $inc_file_name );
            }
            else
            {
               # Here when analysis ok (no syntax errors), so write the .inc file
               #

               my $met_file_name = GEN::APPLY_FINAL_SEP( $file_outputpath ) .
                                   GEN::BASE_NAME( $file_name ) . ".met";
               my @inc_file = ();

               if ( open ( MET, "<$met_file_name" ) )
               {
                  while ( my $line = <MET> )
                  {
                     if ( index( $line, "<R>I" ) == 0 )
                     {
                        # chomp line
                        $line =~ s/[\r\n]+$//o;

                        my ( $key, $including_file, $included_file, $line_no ) =
                           GEN::SPLIT_STRING_ALLOWING_QUOTED_TERMS( " ", $line );
                  
                        if ( ! defined( $line_no ) )
                        {
                           GEN::DIE( "::PROCESS_FILESET Bad file record \"$line\" in file \"$met_file_name\"." );
                        }

                        push @inc_file, "<R>I\t$including_file\t$included_file\t$line_no";
                     }
                  }

                  GEN::WRITE_FILE( $inc_file_name, @inc_file );
               }
               else
               {
                  GEN::DIE( "Cannot open \"$met_file_name\". $!" );
               }
            }

            # If $error_logged_for_primary_analysis, need to
            # check -stoponfail.
            #
            if ( $error_logged_for_primary_analysis )
            {
               if ( $stop_on_fail )
               {
                  OPC::PUT_ERR_LINE
                     ( "Error on file " . GEN::QUOTE_ARG( $file_name ) .
                       ".  -STOPONFAIL specified, hence " .
                       "\"" . PARAM::EXECUTABLE_NAME() . "\" terminated." );
         
                  GEN::EXIT( $primary_analysis_return_code );
               }
            }

            #-- Perform post-analysis ---------------------------------------
            #

            my $error_logged_for_post_analysis;

            if ( ! $failure_in_primary_analysis &&
                 ! $error_logged_for_primary_analysis 
               )
            {
               # Here with primary analysis ok
               #
               my $failure_in_post_analysis;
               my $post_analysis_return_code;

               ( $failure_in_post_analysis,
                 $post_analysis_return_code,
                 $error_logged_for_post_analysis ) = 
                   EXECUTE_POST_ANALYSIS
                     ( $file_name,
                       $file_outputpath,
                       $ar_post_analysis_details );
                  
               $analysis_errors |= $error_logged_for_post_analysis;

               if ( $error_logged_for_post_analysis ||
                    $failure_in_post_analysis 
                  )
               {
                  OPC::SHOW_PLOG();
               }

               # If $error_logged_for_post_analysis, need to
               # check -stoponfail.
               #
               if ( $error_logged_for_post_analysis )
               {
                  if ( $stop_on_fail )
                  {
                     OPC::PUT_ERR_LINE
                        ( "Error on file " . GEN::QUOTE_ARG( $file_name ) .
                          ".  -STOPONFAIL specified, hence " .
                          "\"" . PARAM::EXECUTABLE_NAME() . "\" terminated." );
            
                     GEN::EXIT( $post_analysis_return_code );
                  }
               }
            }

            if ( ! $error_logged_for_primary_analysis &&
                 ! $error_logged_for_post_analysis
               )
            {
               OPC::PLOG( GEN::TIME() . " Analysis complete\n" );
               OPC::SHOW_PLOG();
            } 
         }

         #-- Analysis of $file_name ok. Do display --------------------------
         #

         if ( exists $$hr_qaw_options{ "-disp" } )
         {
            # Output annotated source to standard output
            #
            WRITE_ANNOTATED_SOURCE( $file_name, $file_outputpath, "STDOUT" );
         }

         if ( exists $$hr_qaw_options{ "-fdisp" } )
         {
            # Output annotated source to stdout / stderr / file
            #
            
            if ( ! $RUN::fdisp_initialised )
            {
               $RUN::fdisp_initialised = 1;   # true
               
               GEN::DELETE_FILE_OR_DIRECTORY( $$hr_qaw_options{ "-fdisp" } );
            }

            WRITE_ANNOTATED_SOURCE
               ( $file_name, 
                 $file_outputpath,
                 $$hr_qaw_options{ "-fdisp" } );
         }

         # If -html+|- has been specified, then 
         #    if -html- write $file_name.txt or
         #    if -html+ write $file_name.html
         # 
         if ( grep( /^-html\+$/io, @$ar_qa_opts ) > 0 )
         {
            # Here with -html+ selected
            #

            my $output_filename = GEN::APPLY_FINAL_SEP( $file_outputpath ) .
                                  GEN::BASE_NAME( $file_name ) . ".html";

            # Ensure $output_filename does not exist to prevent concatenation.
            #
            GEN::DELETE_FILE_OR_DIRECTORY( $output_filename );

            WRITE_ANNOTATED_SOURCE
               ( $file_name,
                 $file_outputpath,
                 $output_filename );
         }

         if ( grep( /^-html-?$/io, @$ar_qa_opts ) > 0 )
         {
            # Here with -html- or -html selected
            #

            my $output_filename = GEN::APPLY_FINAL_SEP( $file_outputpath ) .
                                  GEN::BASE_NAME( $file_name ) . ".txt";

            # Ensure $output_filename does not exist to prevent concatenation.
            #
            GEN::DELETE_FILE_OR_DIRECTORY( $output_filename );

            WRITE_ANNOTATED_SOURCE
               ( $file_name,
                 $file_outputpath,
                 $output_filename );
         }
     
         #-- Output .opt file data ------------------------------------------
         #
         if ( exists $$hr_qaw_options{ "-sapf" } )
         {
            DISPLAY_SAPF_REPORT( $file_name, $file_outputpath );
         }
      }
   }

   return $analysis_errors;
}

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

sub REMOVE_GLOB_AND_POST_OPTS
{
   # Usage: @qaw_opts_except_glob_etc = REMOVE_GLOB_AND_POST_OPTS( @qaw_options );
   #
   my @qaw_options = @_;

   my @glob_etc = ( "-canal", "-crep", "-exec", "-glob", "-maseq", "-saseq" );

   my @new_qaw_opts = ();
   OPT:
   for my $opt ( @qaw_options )
   {
      for my $qaw_opt ( @glob_etc )
      {
         if ( GEN::LEFT( $opt, length( $qaw_opt ) ) eq $qaw_opt )
         {
            next OPT;
         }
      }

      push @new_qaw_opts, $opt;
   }

   return @new_qaw_opts;
}

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

sub PROCESS_PROJECT_FILE
{
   # Usage:
   #    PROCESS_PROJECT_FILE
   #           ( $project_file_parameter,
   #             $hr_qaw_options,
   #             $ar_global_analysis_details,
   #             $ar_qa_opts,
   #             $ar_post_analysis_details,
   #             $count );
   #
   # For details of $project_file_parameter see sub SPLIT_PROJECT_FILE_PARAMETER.
   #
   # Examples of $project_file_parameter are:
   #
   #   fred.prj            - ok
   #   fred.prj#folder     - ok
   #   fr*.prj#folder      - ok
   #   fred.prj#*          - not allowed on command line
   #   fred.prj##*.c       - ok - specifies files relative to current working directory
   #   fred.prj#folder#*.c - ok - specifies files relative to current working directory
   #   fred.prj#:*.c       - ok - specifies files relative to the project root folder
   #   fred.prj#folder:*.c - ok - specifies files relative to 'folder'
   #         
   # If no # parts eg *.prj, then glob will have been expanded by 
   # QAW_PARSE_COMMAND_LINE.
   #
   my $project_file_parameter     = shift;
   my $hr_qaw_options             = shift;
   my $ar_global_analysis_details = shift;
   my $ar_qa_opts                 = shift;
   my $ar_post_analysis_details   = shift;
   my $count                      = shift;

   # Split the $ar_qa_opts into those before and those after -FILEPSEC $count
   #

   my $ar_qa_opts_before = [];
   my $ar_qa_opts_after  = [];
   my $found = 0;   # false

   for my $opt ( @$ar_qa_opts )
   {
      if ( ! $found )
      {
         if ( GEN::LEFT( $opt, length( $PARSE_CL::zzz_marker ) ) eq $PARSE_CL::zzz_marker )
         {
            my $i_count = int( GEN::RIGHT2( $opt, length( $PARSE_CL::zzz_marker ) ) );
   
            if ( $i_count == $count )
            {
               $found = 1;  # true;
            }
         }
         else
         {
            push @$ar_qa_opts_before, $opt;
         }
      }
      else
      {
         # Note we filter out all $PARSE_CL::zzz_marker terms
         #
         if ( GEN::LEFT( $opt, length( $PARSE_CL::zzz_marker ) ) ne $PARSE_CL::zzz_marker )
         {
            push @$ar_qa_opts_after, $opt;
         }
      }
   }

   my ( $supplied_prj_filename, 
        $project_folder,
        $filespec,
        $filespath )
        = SPLIT_PROJECT_FILE_PARAMETER( $project_file_parameter );

   my $entire_project_specified = ( $project_folder eq "" ) && ( $filespec eq "" );

   # Get list of .prj files specified by $supplied_prj_filename.
   #
   my ( $ar_prj_file_list, $error ) = GEN::GET_GLOB_FILE_LIST( $supplied_prj_filename );

   if ( $error ne "" )
   {
      GEN::DIE( ": $error" );
   }

   my $display_prj_filename = ( scalar( @$ar_prj_file_list ) > 1 ) || 
                              ( index( $supplied_prj_filename, "*" ) != -1 ) ||
                              ( index( $supplied_prj_filename, "?" ) != -1 ) ||
                              ( ! $entire_project_specified );

   PRJ_FILENAME:
   foreach my $prj_filename ( @$ar_prj_file_list )
   {
      if ( lc( GEN::FILE_EXTENSION( $prj_filename ) ) ne ".prj" )
      {
         OPC::PUT_ERR_LINE( "Ignoring unrecognised file \"$prj_filename\"" );

         next PRJ_FILENAME;
      }

      if ( $display_prj_filename )
      {
         OPC::INITIALISE_PLOG_BUFFER();
         OPC::PLOG( "Project \"" . TRUNCATE_FILENAME( $prj_filename ) . "\" :\n" );
         OPC::SHOW_PLOG();
      }

      my $ar_project_folders;
      my $root_folder_name;
      my $project_properties_file;
      my $project_dataflow_file;

      # Need to save the root path for the current project for resolution of RP/EV
      #   
      PARSE_CL::SET_ROOT_PATH( GEN::DIR_NAME( GEN::GET_FILE_PATH_ABSOLUTE( $prj_filename ) ) );

      if ( GEN::PLATFORM_IS_WINDOWS() )
      {
         ( $ar_project_folders, $root_folder_name, $project_properties_file, $project_dataflow_file ) = 
            QPARSE::PARSE_WINDOWS_PROJECT_FILE( $prj_filename );
      }

      # Unix GUI for QAC-8.0 onward has the option of reading and writing Windows style
      # project files.
      #
      elsif ( GEN::PLATFORM_IS_CYGWIN() )
      {
         ( $ar_project_folders, $root_folder_name, $project_properties_file, $project_dataflow_file ) = 
             QPARSE::PARSE_WINDOWS_OR_UNIX_PROJECT_FILE( $prj_filename );
      }
      else  # Here with Unix platform
      {
         ( $ar_project_folders, $root_folder_name, $project_properties_file, $project_dataflow_file ) = 
            QPARSE::PARSE_UNIX_PROJECT_FILE( $prj_filename );
      }
   
      # Check that $ar_project_folders has non-null content throughout.
      #
      QPARSE::CHECK_PROJECT_FOLDERS_LOOK_OK( $ar_project_folders, $prj_filename );
   
      my @project_properties_option = ();

      if ( $project_properties_file ne "" )
      {
         if ( ! -e $project_properties_file )
         {
            GEN::DIEX( ": Illegal Project Config File \"$project_properties_file\"",
                       $RUN::die_config_error );
         }

         @project_properties_option = ( "-via", $project_properties_file );
      }

      if ( $project_dataflow_file ne "" )
      {
         if ( ! -e $project_dataflow_file )
         {
            GEN::DIEX( ": Illegal Project Dataflow File \"$project_dataflow_file\"",
                       $RUN::die_config_error );
         }

         push @project_properties_option, ( "-via", $project_dataflow_file );
      }

      # Derive cmaf path as the outputpath of root folder of project plus the name
      # of that folder.
      #

      my $hr_root_folder          = $$ar_project_folders[ 0 ];
      my $root_folder_output_path = $$hr_root_folder{ "<output_path>" };

      my $cmaf_path = GEN::APPLY_FINAL_SEP( $root_folder_output_path ) .
                         $root_folder_name;

      # Convert hash of qaw options into array of full form options
      #
      my @qaw_options = ();
   
      foreach my $opt ( keys %$hr_qaw_options )
      {
         push @qaw_options, PARSE_CL::FULL_OPTION( $opt, $$hr_qaw_options{ $opt } );
      }
   
      # Ensure that options in file $cfg_filename are read
      #
      # Load options in $cfg_filename.
      #
# 1-11-07: CR12558 - QAW no longer reads qac[pp].cfg
#      my @cfg_options = LOAD_CFG_FILE_OPTIONS( PARAM::CFG_FILENAME() );
   
      # Declare $analysis_errors to hold result of EXECUTE_FILE_BASED_ANALYSIS_PROCESSING
      #
      my $analysis_errors;

      # Here we have a choice of:
      # 1. Not allowing command line options inherited to have RP/EV handling
      #    And hence not resolving paths in options with a path parameter.
      # Or
      # 2. Allowing command line options to have RP/EV handling and hence 
      #    translate them wrt root path.
      #
      # For now assume 1.  Hence do nothing here.

      if ( $filespec eq "" )
      {
         # Here when analysing all files in project,
         # or all files in a specified folder.
         #
   
         my @project_wide_filelist     = ();
         my @root_folder_personalities = ();
         my $first_folder_found        = 0;   # false
         my $project_folder_found      = $project_folder eq "";
         my $first_pass_made           = 0;   # false
   
         # Use for the root folder source path and root folder output path for
         # use with %O and %D in EXECUTE_GLOBAL_ANALYSIS_PROGRAMS().
         #
         my $root_source_path;
         my $root_output_path;

         FOLDER:
         foreach my $hr_prj_folder ( @$ar_project_folders )
         {
            my $folder_name = $$hr_prj_folder{ "<folder_name>" };
   
            next FOLDER if ( ( $project_folder ne "" ) &&
                             ( $project_folder ne $folder_name )
                           );
   
            my $source_path     = $$hr_prj_folder{ "<source_path>" };
            my $output_path     = $$hr_prj_folder{ "<output_path>" };
            my $subset_person   = $$hr_prj_folder{ "<subset_person>" };
            my $analyser_person = $$hr_prj_folder{ "<analyser_person>" };
            my $compiler_person = $$hr_prj_folder{ "<compiler_person>" };
            my $ar_file_list    = $$hr_prj_folder{ "<file_list>" };

            if ( ! $first_pass_made )
            {
               $first_pass_made = 1;   # true
               $root_source_path = $source_path;
               $root_output_path = $output_path;
            }

            $project_folder_found = 1;  # true
   
            if ( $project_folder ne "" )
            {   
               OPC::INITIALISE_PLOG_BUFFER();
               OPC::PLOG( "Folder $folder_name, \"" . TRUNCATE_FILENAME( $source_path ) ."\" :\n" );
               OPC::SHOW_PLOG();
            }

            if ( $#$ar_file_list >= 0 )
            {
               my ( $ar_qa_opts,
                    $ar_prj_global_analysis_details,
                    $ar_prj_post_analysis_details,
                    $hr_qaw_options,
                    $ar_cargs_parameters,
                    $ar_file_names,
                    $ar_file_outputpaths ) =
                       PARSE_PARAMETERS
                         (
# 1-11-07: CR12558 - QAW no longer read qac[pp].cfg
#                           @cfg_options,
                           REMOVE_GLOB_AND_POST_OPTS( @qaw_options ),
                           @$ar_qa_opts_before,
                           "-via", $subset_person,
                           "-via", $analyser_person,
                           "-via", $compiler_person,
                           @project_properties_option,
                           "-cmaf", $cmaf_path,
                           "-op",  $output_path,
                           @$ar_qa_opts_after,
                           @$ar_file_list );

               $ar_prj_global_analysis_details = [ @$ar_global_analysis_details, @$ar_prj_global_analysis_details ];
               $ar_prj_post_analysis_details   = [ @$ar_post_analysis_details, @$ar_prj_post_analysis_details ];

               if ( $#$ar_file_names == -1 )
               {
                  OPC::PUT_MSG_LINE( "No files"  );
               }
               else
               {
                  $analysis_errors =
                     EXECUTE_FILE_BASED_ANALYSIS_PROCESSING
                        ( $ar_qa_opts,
                          $ar_prj_global_analysis_details,
                          $ar_prj_post_analysis_details,
                          $hr_qaw_options,
                          $ar_file_names,
                          $ar_file_outputpaths );
      
                  if ( $analysis_errors )
                  {
                     if ( FILE_COUNT_GT_ZERO( $ar_file_names ) )
                     {
                        GIVE_ANALYSIS_ERRORS_WARNING( $ar_global_analysis_details );
                     }
                  }

                  if ( ! $entire_project_specified )
                  {
                     EXECUTE_PROJECT_WIDE_PROCESSING
                        ( $cmaf_path,
                          $root_source_path,
                          $root_output_path,
                          $ar_qa_opts,
                          $ar_prj_global_analysis_details,
                          $hr_qaw_options,
                          $ar_file_names,
                          $ar_file_outputpaths,
                          ! $analysis_errors ); 
                  }
                  else
                  {
                     # Accumulate -op and -files for single (full) project
                     # wide analysis
                     #
                     push @project_wide_filelist, ( "-op", $output_path, @$ar_file_list );
      
                     if ( ! $first_folder_found )
                     {
                        $first_folder_found = 1;   # true;
               
                        @root_folder_personalities = ( 
# 1-11-07: CR12558 - QAW no longer read qac[pp].cfg
#                                                      @cfg_options, 
                                                       REMOVE_GLOB_AND_POST_OPTS( @qaw_options ),
                                                       @$ar_qa_opts_before,
                                                       "-via", $subset_person,
                                                       "-via", $analyser_person,
                                                       "-via", $compiler_person,
                                                       @project_properties_option,
                                                       @$ar_qa_opts_after,
                                                       "-cmaf", $cmaf_path );
                     }
                  }
               }
            }
         }
   
         if ( ! $project_folder_found )
         {
             GEN::DIE( ": \"$project_folder\" not found in \"$prj_filename\"." );
         }
   
         if ( $entire_project_specified )
         {
            if ( ( exists $$hr_qaw_options{ "-ehtml" } ) ||
            	 ( exists $$hr_qaw_options{ "-etxt" } ) ||
            	 ( exists $$hr_qaw_options{ "-pdsp" } ) ||
            	 ( exists $$hr_qaw_options{ "-pdspd" } ) ||
            	 ( exists $$hr_qaw_options{ "-view" } ) ||
            	 ( $#$ar_global_analysis_details != -1 )
               )
            {
   	       my ( $ar_qa_opts,
                    $ar_prj2_global_analysis_details,
                    $ar_prj2_post_analysis_details,
   		    $hr_qaw_options,
   		    $ar_cargs_parameters,
   		    $ar_file_names,
   		    $ar_file_outputpaths ) =
   			PARSE_PARAMETERS
                           (
   			     @root_folder_personalities,
   			     "-cmaf", $cmaf_path,
   			     @project_wide_filelist );

               $ar_prj2_global_analysis_details = [ @$ar_global_analysis_details, @$ar_prj2_global_analysis_details ];
               $ar_prj2_post_analysis_details   = [ @$ar_post_analysis_details, @$ar_prj2_post_analysis_details ];
      
   	       if ( $#$ar_file_names == -1 )
   	       {
   		  OPC::PUT_MSG_LINE( "No files"  );
   	       }
   	       else
   	       {
                  EXECUTE_PROJECT_WIDE_PROCESSING
                    ( $cmaf_path,
                      $root_source_path,
                      $root_output_path,
                      $ar_qa_opts,
                      $ar_prj2_global_analysis_details,
                      $hr_qaw_options,
                      $ar_file_names,
                      $ar_file_outputpaths,
                      ! $analysis_errors ); 
   	       }
   	    }
         }
      }
      else
      {
         # Here when only analysing specified files.
         #
   
         my $project_folder_found = $project_folder eq "";
   
         # Use for the root folder source path and root folder output path for
         # use with %O and %D in EXECUTE_GLOBAL_ANALYSIS_PROGRAMS().
         #
         my $root_source_path;
         my $root_output_path;
         my $first_pass_made = 0;   # false

         FOLDER_FOR_SPECIFIC_FILES:
         foreach my $hr_prj_folder ( @$ar_project_folders )
         {
            my $folder_name = $$hr_prj_folder{ "<folder_name>" };
   
            next FOLDER_FOR_SPECIFIC_FILES if ( ( $project_folder ne "" ) &&
                                                ( $project_folder ne $folder_name )
                                              );
   
            $project_folder_found = 1;  # true
   
            # Analyse fileset for each folder separately as separate folders
            # may have separate personalities which cannot be mixed in one
            # analysis.
            #
   
            my $source_path     = $$hr_prj_folder{ "<source_path>" };
            my $output_path     = $$hr_prj_folder{ "<output_path>" };
            my $subset_person   = $$hr_prj_folder{ "<subset_person>" };
            my $analyser_person = $$hr_prj_folder{ "<analyser_person>" };
            my $compiler_person = $$hr_prj_folder{ "<compiler_person>" };
      
            if ( ! $first_pass_made )
            {
               $first_pass_made = 1;   # true
               $root_source_path = $source_path;
               $root_output_path = $output_path;
            }

            # Here with $filespec as spec of files to be used. - These may have been
            # specified in the 2nd or 3rd parameter of the project file parameter.
            #
      
            # Get the list of files to be analysed.
            #
      
            my $ar_filespec;
      
            if ( $filespath eq "RELATIVE TO CURRENT DIR" )
            {
               # Here with $filespec is RELATIVE TO CURRENT DIR.
               # PWD is used as the prefix for $filespec, 
               # therefore using current working directory (PWD) ie do nothing.
               # 
      
               my $error;
      
               ( $ar_filespec, $error ) = GEN::GET_GLOB_FILE_LIST( $filespec );
      
               if ( $error ne "" )
               {
                  # Here when file lookup failed.
                  # Assume no files in current folder, and skip to next 
                  # folder looking for other folders of the same name.
                  #
                  next FOLDER_FOR_SPECIFIC_FILES;
               }
            }
            else
            {
               # Here when filespec was specified with the folder, ie with 
               # <folder>:<filespec>
               #
               my $new_filespec;
   
               if ( $filespath eq "RELATIVE TO FOLDER" )
               {
                  # Here with $filespec is relative to the specified folder.
                  # i.e. ':' used in spec of files in folder part.
                  # Need to 1. If $filespec is relative, prefix $filespec with folder
                  # path and 2. Ensure that resultant list of files are only files
                  # within the project folder and does not include any other files
                  # that are in the directory, but not in the project folder.
                  #
                  if ( GEN::IS_FILE_PATH_ABSOLUTE( $filespec ) )
                  {
                     $new_filespec = $filespec;
                  }
                  else
                  {
                     # Adjust the path
                     #
                     $new_filespec = GEN::APPLY_FINAL_SEP( $source_path ) . $filespec;
                  }
               }
               else
               {
                  GEN::DIE( "::RUN::PROCESS_PROJECT_FILE Program Error." );
               }
   
   
               # Get all files matching $filespec
               #
               my ( $ar_candidate_filespec, $error ) = GEN::GET_GLOB_FILE_LIST( $new_filespec );
         
               if ( $error ne "" )
               {
                  # Here when file lookup failed.
                  # Assume no files in current folder, and skip to next 
                  # folder looking for other folders of the same name.
                  #
                  next FOLDER_FOR_SPECIFIC_FILES;
               }
   
               # Collect only the files in the project folder in $ar_filespec.
               #
               my $ar_file_list = $$hr_prj_folder{ "<file_list>" };
               $ar_filespec = [];
      
               CANDIDATE_FILENAME:
               foreach my $candidate_filename ( @$ar_candidate_filespec )
               {
                  # Try simple comparison for filename equality.
                  #
                  if ( grep( /^\Q$candidate_filename\E$/, @$ar_file_list ) > 0 )
                  {
                     push @$ar_filespec, $candidate_filename;
      
                     next CANDIDATE_FILENAME;
                  }
      
                  # Here when simple comparison for filename equality failed.
                  # Try FILES_ARE_SAME comparison.
                  #
                  foreach my $folder_filename ( @$ar_file_list )
                  {
                     if ( GEN::FILES_ARE_SAME( $folder_filename, $candidate_filename ) )
                     {
                        push @$ar_filespec, $candidate_filename;
      
                        next CANDIDATE_FILENAME;
                     }
                  }
               }
            }

            # Sort the file list as will have lost the original project order
            # at this stage due to fact that list has been built up using
            # Perl 'glob'.
            #
            @$ar_filespec = GEN::SORT_ARRAY_OF_MIXED_TYPES( @$ar_filespec );
      
            # Note that -i $source_path is added here to allow
            # usage like:
            #
            #   qaw prj.prj##???.c
            #
            # i.e. to allow analysis of files relative to the current
            # directory, but accessing #included files within the 
            # relevant 'project folder' directory.
            # 
            my ( $ar_qa_opts,
                 $ar_prj3_global_analysis_details,
                 $ar_prj3_post_analysis_details,
                 $hr_qaw_options,
                 $ar_cargs_parameters,
                 $ar_file_names,
                 $ar_file_outputpaths ) =
                    PARSE_PARAMETERS
                      (
# 1-11-07: CR12558 - QAW no longer read qac[pp].cfg
#                         @cfg_options,
                         REMOVE_GLOB_AND_POST_OPTS( @qaw_options ),
                         @$ar_qa_opts_before,
                         "-i",   $source_path,
                         "-via", $subset_person,
                         "-via", $analyser_person,
                         "-via", $compiler_person,
                         @project_properties_option,
                         "-cmaf", $cmaf_path,
                         "-op",  $output_path,
                         @$ar_qa_opts_after,
                         @$ar_filespec );
   
            $ar_prj3_global_analysis_details = [ @$ar_global_analysis_details, @$ar_prj3_global_analysis_details ];
            $ar_prj3_post_analysis_details   = [ @$ar_post_analysis_details, @$ar_prj3_post_analysis_details ];
      
            OPC::INITIALISE_PLOG_BUFFER();
            OPC::PLOG( "Folder $folder_name, \"" . TRUNCATE_FILENAME( $source_path ) ."\" :\n" );
            OPC::SHOW_PLOG();
   
            if ( $#$ar_file_names == -1 )
            {
               OPC::PUT_MSG_LINE( "No files"  );
            }
            else
            {
               my $analysis_errors =
                  EXECUTE_FILE_BASED_ANALYSIS_PROCESSING
                     ( $ar_qa_opts,
                       $ar_prj3_global_analysis_details,
                       $ar_prj3_post_analysis_details,
                       $hr_qaw_options,
                       $ar_file_names,
                       $ar_file_outputpaths );

               if ( $analysis_errors )
               {
                  if ( FILE_COUNT_GT_ZERO( $ar_file_names ) )
                  {
                     GIVE_ANALYSIS_ERRORS_WARNING( $ar_global_analysis_details );
                  }
               }

               EXECUTE_PROJECT_WIDE_PROCESSING
                  ( $cmaf_path,
                    $root_source_path,
                    $root_output_path,
                    $ar_qa_opts,
                    $ar_prj3_global_analysis_details,
                    $hr_qaw_options,
                    $ar_file_names,
                    $ar_file_outputpaths,
                    ! $analysis_errors ); 
            }
         }
   
         if ( ! $project_folder_found )
         {
             GEN::DIE( ": \"$project_folder\" not found in \"$prj_filename\"." );
         }
      }

      # Finished with this project so clear root path
      #
      PARSE_CL::CLEAR_ROOT_PATH();
   }

   return;
}

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

sub SPLIT_PROJECT_FILE_PARAMETER
{
   # Usage: ( $prj_filename,
   #          $project_folder,
   #          $filespec, 
   #          $filespath ) =
   #        SPLIT_PROJECT_FILE_PARAMETER( $project_file_parameter );
   #
   # NB $project_file_parameter is command-line option of the form:
   #
   #   <prj file name>[ # [ <folder name> ] [ # <file spec> ] ]
   #
   #   e.g.
   #
   #   mcm_examples.prj#folder_x#file*.*
   #
   # NB. $project_file_parameter can only contain wildcarded .prj file names
   # provided there is no ambiguity and they expand to a single filename
   # - unless there is no file spec, in which case the .prj field can  
   # represent any number of .prj files.
   # NB. <folder name> cannot contain wildcards.
   #
   # This means the following matrix applies:
   #
   # FOR FILE SPEC SUPPLIED IN 3RD PARAMETER:
   # ---------------------------------------------------------------------------------------
   #    <prj file name>   <folder name>   <file spec>   Result
   #  1:     ""               .             .          - Illegal (.prj name must be provided)
   #  2:     x.prj            ""            ""         - All files in x.prj
   #  3:     x.prj            ""            y.c        - y.c using x.prj persons
   #  4:     x.prj            ""            *.c        - *.c using x.prj persons
   #  5:     x.prj            fldr          ""         - All files in x.prj, folder fldr
   #  6:     x.prj            fldr          y.c        - y.c using x.prj, fldr persons
   #  7:     x.prj            fldr          *.c          *.c using x.prj, fldr persons
   #  8:     x.prj            f*            .          - Illegal (cannot wildcard folder name)
   #  9:     *.pr*            ""            ""         - All files in projects *.pr*  (see '1 below)
   # 10:     *.pr*            ""            y.c        - All y.c using all persons in projects *.pr* 
   # 11:     *.pr*            ""            *.c        - All *.c using all persons in projects *.pr*
   # 12:     *.pr*            fldr          ""         - All files in projects *.pr*, folder fldr
   # 13:     *.pr*            fldr          y.c        - y.c using fldr persons in projects *.pr*
   # 14:     *.pr*            fldr          *.c        - *.c using fldr persons in projects *.pr*
   # 15:     *.pr*            f*            ""         - Illegal (cannot wildcard folder name)
   # 16:     *.pr*            f*            y.c        - Illegal (cannot wildcard folder name)
   # 17:     *.pr*            f*            *.c        - Illegal (cannot wildcard folder name)
   #
   # '1 - If no # parts eg *.prj, then glob will have been expanded by 
   #      QAW_PARSE_COMMAND_LINE.
   #
   # FOR FILE SPEC SUPPLIED AFTER FOLDER IE IN 2ND PARAMETER (e.g. mcm.prj#[folder]:filespec*.* )
   # NB HERE filespec refers to the files in the FOLDER directory.  The spec must be 
   # of file base names, eg rule_14.c, not ../rule_14.c or any other path, BUT a full absolute
   # filepath specification may be given.
   # ---------------------------------------------------------------------------------------
   #    <prj file name>   <folder name>  <:file spec>   Result
   #  1:     ""               .             .          - Illegal (.prj name must be provided)
   #  2:     x.prj            ""            ""         - Specifies all files in project
   #  3:     x.prj            ""            y.c        - y.c using x.prj persons
   #  4:     x.prj            ""            *.c        - *.c using x.prj persons
   #  5:     x.prj            fldr          ""         - Specifies all files in folder
   #  6:     x.prj            fldr          y.c        - y.c using x.prj, fldr persons
   #  7:     x.prj            fldr          *.c          *.c using x.prj, fldr persons
   #  8:     x.prj            f*            .          - Illegal (cannot wildcard folder name)
   #  9:     *.pr*            ""            ""         - All files in projects *.pr*  (see '1 below)
   # 10:     *.pr*            ""            y.c        - All y.c in, using all persons in, projects *.pr* 
   # 11:     *.pr*            ""            *.c        - All *.c in, using all persons in, projects *.pr*
   # 12:     *.pr*            fldr          ""         - All files in projects *.pr*, folder fldr
   # 13:     *.pr*            fldr          y.c        - y.c in, using fldr persons in, projects *.pr*
   # 14:     *.pr*            fldr          *.c        - *.c in, using fldr persons in, projects *.pr*
   # 15:     *.pr*            f*            ""         - Illegal (cannot wildcard folder name)
   # 16:     *.pr*            f*            y.c        - Illegal (cannot wildcard folder name)
   # 17:     *.pr*            f*            *.c        - Illegal (cannot wildcard folder name)
   #

   my $project_file_parameter = shift;

   my ( $prj_filename, 
        $project_folder,
        $filespec )
       = QPARSE::SPLIT_HASH_DELIMITED_STRING
            ( $project_file_parameter, 
              -3,
              ": Illegal project spec $project_file_parameter" );

   # Initialise $filespath - assuming that spec is <prj>#[ <folder> ]#<files>
   # Reset later if : <prj># [ <folder> ] ':' <files>
   #
   my $filespath = "RELATIVE TO CURRENT DIR";

   if ( ! defined( $prj_filename ) ||
        ( $prj_filename eq "" )
      )
   {
      GEN::DIE( ": No project filename in \"$project_file_parameter\"." );
   }
   else
   {
      # Check that $prj_filename only represents one file.
      #

      my ( $ar_file_list, $error ) = GEN::GET_GLOB_FILE_LIST( $prj_filename );

      if ( $error ne "" )
      {
         GEN::DIE( ": $error" );
      }

      if ( $#$ar_file_list == -1 )
      {
         # Here when $prj_filename is empty
         #
         $prj_filename = "";
      }
   }
      
   if ( ! defined( $project_folder ) )
   {
      $project_folder = "";
   }

   if ( ! defined( $filespec ) )
   {
      $filespec = "";
   }

   # Check $project_folder for ':' spec
   #
   if ( index( $project_folder, ":" ) != -1 )
   {
      # Here with files specified relative to the actual project folder.
       
      # Check for illegal case of 3rd parameter set
      #
      if ( $filespec ne "" ) 
      {
         GEN::DIE( ": Cannot specify files both within project folder ".
                   "\"$project_folder\" and relative to current directory " .
                   "\"$filespec\"." );
      }

      # Check case of empty file list in folder-filespec.
      #
      if ( $project_folder =~ /:$/o )
      {
         # Previously died here, but now simply set $filespec to "*"
         # 
         # GEN::DIE( ": No files specified in \"$project_folder\"" );
         # 
         $filespec = "*";
      }
      else
      {
         # Get the actual filespec string 
         #
         ( $filespec = $project_folder ) =~ s/^.*?://o;
      }

      # Trim filespec off of $project_folder to leave the actual folder only.
      #
      $project_folder =~ s/:.*$//o;

      # Ensure if path is specified then it is absolute.
      #
      my $separator = GEN::GET_PATH_SEPARATOR();

      if ( index( $filespec, $separator ) != -1 )
      {
         if ( ! GEN::IS_FILE_PATH_ABSOLUTE( $filespec ) )
         {
            GEN::DIE( ": \"$filespec\" must only specify files in folder " .
                      "\"$project_folder\" or be absolute." );
         }
      }

      # $filespec looks legal, so set the resultant list of files specification.
      # NB these are relative to the folder directory, and not the current dir.
      # Hence must prefix with folder path.  NB this will be reset if a full path
      # is given.
      #
      $filespath = "RELATIVE TO FOLDER";
   }

   if ( ( index( $project_folder, "*" ) != -1 ) ||
        ( index( $project_folder, "?" ) != -1 )
      )
   {
      GEN::DIE( ": Wildcard(s) not permitted in folder name " .
                "\"$project_file_parameter\"." );
   }

   return ( $prj_filename, $project_folder, $filespec, $filespath );
}

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

sub FILENAME_DISPLAY_WIDTH
{
   # Usage: $width = FILENAME_DISPLAY_WIDTH();
   #
   # This sub returns a constant which is the maximum number of characters,
   # ie the 'window' on a file path that can be displayed in the plog list.
   #
   # If +ve then value is treated as the fixed width of display for all
   # file paths no matter how short.
   #
   # If -ve then the abs value is the maximum width, but if all files in
   # the set to be displayed are shorter, then then longest file in the
   # set determines the display width.
   #
   return $RUN::filename_display_width;
}

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

sub PREFIX
{
   # Usage: $prefix = PREFIX();
   #
   return "... ";
}

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

sub TRUNCATE_FILENAME
{
   # Usage: $short_file_name = TRUNCATE_FILENAME( $filename );
   #
   my $filename = shift;

   my $prefix = PREFIX();

   my $display_length_of_files = FILENAME_DISPLAY_WIDTH();

   if ( $display_length_of_files < 0 )
   {
      # Here when FILENAME_DISPLAY_WIDTH() is -ve, this means
      # use the minimum filepath width, which in this case is
      # simply the file itself.
      #
      return $filename;
   }
   elsif ( length( $filename ) > $display_length_of_files )
   {
      my @path_elements = GEN::SPLIT_PATH( $filename );

      my $separator = GEN::GET_PATH_SEPARATOR();
      my $len       = 0;
      my $element   = pop @path_elements;
      $filename     = $element;
      $element      = pop @path_elements;

      while ( defined( $element ) && 
              ( length( $filename . $element ) < $display_length_of_files - length( $prefix ) )
            )
      {
         $filename = $element . $separator . $filename;
         $element  = pop @path_elements;
      }
      
      return $prefix . $filename;
   }             
   else
   {
      return $filename;
   }
}

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

sub GET_DISPLAY_FORM_OF_FILES
{
   # Usage: $hr_display_files = GET_DISPLAY_FORM_OF_FILES( $ar_file_names );
   #
   my $ar_file_names = shift;

   # This is the maximum allowable length of a file path.  Files longer
   # than this will have their names truncated in the plog window.
   #
   my $len = 0;

   foreach my $filename ( @$ar_file_names )
   {
      $len = GEN::MAX( $len, length( $filename ) );
   }

   my $max_len = abs( FILENAME_DISPLAY_WIDTH() );

   if ( $len <= $max_len )
   {
      # Here when all files fit so no need for truncation.
      #

      if ( FILENAME_DISPLAY_WIDTH() < 0 )
      {
         # Here with -ve max_len, this means use the minimum width, 
         # which will be less than 56 (where 56 is the normal value of
         # FILENAME_DISPLAY_WIDTH() ).
         #
         $max_len = $len;
      }

      my $hr_display_files = {};

      foreach my $filename ( @$ar_file_names )
      {
         $$hr_display_files{ $filename } = GEN::RPAD( $filename, $max_len );
      }

      return $hr_display_files;
   }
   else
   {
      # Here when files do not fit - perform truncation.
      #
      my $hr_display_paths = GET_TRUNCATED_DISPLAY_FORM_OF_FILES( $ar_file_names );

      # Insert the ... prefix
      #
      foreach my $filename ( keys %$hr_display_paths )
      {
         my $display_version = $$hr_display_paths{ $filename };

         if ( $display_version ne $filename )
         {
            $$hr_display_paths{ $filename } = GEN::RPAD( PREFIX() . $display_version, $max_len );
         }
         else
         {
            $$hr_display_paths{ $filename } = GEN::RPAD( $display_version, $max_len );
         }
      }

      return $hr_display_paths;
   }
}

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

sub GET_TRUNCATED_DISPLAY_FORM_OF_FILES
{
   # Usage: $hr_display_paths = 
   #           GET_TRUNCATED_DISPLAY_FORM_OF_FILES( $ar_file_names );
   #
   # This (recursive) sub is *always* dealing with paths some of which are
   # to be truncated.
   #
   my $ar_file_names_supplied = shift;

   my $ar_file_names = [];

   foreach my $filename ( @$ar_file_names_supplied )
   {
      push @$ar_file_names, GEN::TRIM( $filename );
   }

   my $max_len = abs( FILENAME_DISPLAY_WIDTH() ) - length( PREFIX() );

   my $len = 0;

   my $hr_display_files = {};

   foreach my $filename ( @$ar_file_names )
   {
      $len = GEN::MAX( $len, length( $filename ) );
   }

   if ( $len <= $max_len )
   {
      # Here when all files fit so no need for truncation.
      #
      foreach my $filename ( @$ar_file_names )
      {
         $$hr_display_files{ $filename } = $filename;
      }

      return $hr_display_files;
   }

   # Calc hash of filepaths with common root of paths details.
   #
   my $hr_paths = {};

   foreach my $filename ( @$ar_file_names )
   {
      my $len = length( $filename );
      my @path_elements = GEN::SPLIT_PATH( $filename );

      my $drive = "";
      my $path  = $filename;

      if ( index( $filename, ":" ) != -1 )
      {
         ( $drive, $path ) = split( ":", $filename );
      }

      my $root_dir;
      ( $root_dir = $path ) =~ s/^[\/\\]?(.*?)[\/\\].*$/$1/o;

      my $root_dir_hash = $drive . $root_dir;
      
      my $ar_path_details = [ $len, $filename, \@path_elements ];

      my $ar_rooted_paths;
      if ( exists( $$hr_paths{ $root_dir_hash } ) )
      {
         $ar_rooted_paths = $$hr_paths{ $root_dir_hash };
      }
      else
      {
         $ar_rooted_paths = [];
      }

      push @$ar_rooted_paths, $ar_path_details;

      $$hr_paths{ $root_dir_hash } = $ar_rooted_paths;
   }

   # Now calc showable section for each root
   #
   my $hr_display_paths = {};

   my $separator = GEN::GET_PATH_SEPARATOR();
 
   foreach my $root_dir_hash ( keys %$hr_paths )
   {
      my $ar_rooted_paths = $$hr_paths{ $root_dir_hash };
      my $lmax            = 0;

      PATH_DETAILS:
      foreach my $ar_path_details ( @$ar_rooted_paths )
      {
         my $len           = $$ar_path_details[ 0 ];
         my $base_filename = GEN::BASE_NAME( $$ar_path_details[ 1 ] );

         next PATH_DETAILS if ( length( $base_filename ) > $max_len );

         $lmax = GEN::MAX( $lmax, $len );
      }

      if ( $lmax > $max_len )
      {
         # Here with the subset of paths is too long.
         # Remove the first (root) element from the paths,
         # create a new sub-list and recurse.
         #
         my $ar_sub_file_names = [];
         my $ar_parent_file_names = [];

         foreach my $ar_path_details ( @$ar_rooted_paths )
         {
            my $filename                 = $$ar_path_details[ 1 ];
            my ( undef, @path_elements ) = @{ $$ar_path_details[ 2 ] };

            my $truncated_filename = join( $separator, @path_elements );

            push @$ar_parent_file_names, $filename;
            push @$ar_sub_file_names, $truncated_filename;
         }

         my $hr_sub_display_paths = 
               GET_TRUNCATED_DISPLAY_FORM_OF_FILES( $ar_sub_file_names );

         # Now insert the result into $hr_display_paths.
         #

         for ( my $i = 0; $i <= $#$ar_parent_file_names; ++$i )
         {
            my $filename     = $$ar_parent_file_names[ $i ];
            my $sub_filename = $$ar_sub_file_names[ $i ];

            $$hr_display_paths{ $filename } = $$hr_sub_display_paths{ $sub_filename };
         }
      }
      else
      {
         # Here with the subset is ok
         # Simply save the subset
         #
         foreach my $ar_path_details ( @$ar_rooted_paths )
         {
            my $filename = $$ar_path_details[ 1 ];

            $$hr_display_paths{ $filename } = $filename;
         }
      }
   }

   return $hr_display_paths;
}

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

sub GET_GLOBAL_ANALYSIS_DISPLAY_FORM_OF_FILES
{
   # Usage: $hr_display_files = 
   #            GET_GLOBAL_ANALYSIS_DISPLAY_FORM_OF_FILES
   #               ( $ar_file_names,
   #                 $ar_global_analysis_details );
   #
   my $ar_file_names              = shift;
   my $ar_global_analysis_details = shift;

   my $ar_display_file_names;
   @$ar_display_file_names = @$ar_file_names;

   my $ar_file_names_local;
   @$ar_file_names_local = @$ar_file_names;

   foreach my $glob_anal_details ( @$ar_global_analysis_details )
   {
      my ( $glob_analysis_program,
           $glob_analysis_params,
           $glob_analysis_renderer,
           $glob_renderer_param,
           $glob_raw_param,
           $glob_filter ) = split( "\n", $glob_anal_details );

      $glob_analysis_program = GEN::TRIM( $glob_analysis_program );

      my $displayed_program_name = GEN::STEM_NAME( $glob_analysis_program );
      
      if ( lc( $displayed_program_name ) eq "pal" )
      {
         $displayed_program_name = "CMA";
      }

      push @$ar_file_names_local, $glob_analysis_program;
      push @$ar_display_file_names, $displayed_program_name;
   }

   push @$ar_file_names_local, PARAM::PRJDSP_PROGRAM();
   push @$ar_display_file_names, PARAM::PRJDSP_PROGRAM();

   my $len = 0;

   foreach my $filename ( @$ar_display_file_names )
   {
      $len = GEN::MAX( $len, length( GEN::TRIM( $filename ) ) );
   }

   # This is the maximum allowable length of a file path.  Files longer
   # than this will have their names truncated in the plog window.
   #
   my $max_len = abs( FILENAME_DISPLAY_WIDTH() );

   if ( $len <= $max_len )
   {
      # Here when all files fit so no need for truncation.
      #

      if ( FILENAME_DISPLAY_WIDTH() < 0 )
      {
         # Here with -ve max_len, this means use the minimum width, 
         # which will be less than 56 (where 56 is the normal value of
         # FILENAME_DISPLAY_WIDTH() ).
         #
         $max_len = $len;
      }

      my $hr_display_files = {};

      for ( my $i = 0; $i <= $#$ar_file_names_local; ++$i )
      {
         $$hr_display_files{ $$ar_file_names_local[ $i ] } = 
            GEN::RPAD( GEN::TRIM( $$ar_display_file_names[ $i ] ), $max_len );
      }

      return $hr_display_files;
   }
   else
   {
      # Here when files do not fit - perform truncation.
      #
      my $hr_display_paths = GET_TRUNCATED_DISPLAY_FORM_OF_FILES( $ar_display_file_names );
 
      # Insert the ... prefix
      #
      for ( my $i = 0; $i <= $#$ar_file_names_local; ++$i )
      {
         my $real_filename   = GEN::TRIM( $$ar_file_names_local[ $i ] );
         my $disp_filename   = GEN::TRIM( $$ar_display_file_names[ $i ] );
         my $display_version = GEN::TRIM( $$hr_display_paths{ $disp_filename } );

         if ( $display_version ne $disp_filename )
         {
            $$hr_display_paths{ $real_filename } = GEN::RPAD( PREFIX() . $display_version, $max_len );
         }
         else
         {
            $$hr_display_paths{ $real_filename } = GEN::RPAD( $display_version, $max_len );
         }
      }

      return $hr_display_paths;
   }
}

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

sub IS_PROJECT_FILE
{
   # Usage: if ( IS_PROJECT_FILE( $project_file_parameter ) ) ...
   #
   # Test of whether a $project_file_parameter is a project file spec,
   # is that the first term ends in .prj
   # First term is upto first of '#' or end of $project_file_parameter.
   #
   my $project_file_parameter = shift;

   my $extn = GEN::FILE_EXTENSION( $project_file_parameter );
         
   # This statement deals with a prj filespec with a # separator and
   # following folderspec and or filespec.
   #
   my $is_prj_filespec = index( lc( $project_file_parameter ), ".prj#" ) != -1;
   
   return ( $extn eq ".prj" ) || $is_prj_filespec;
}

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

sub EXECUTE_PRQA_LICENCED_PROGRAM
{
   # Usage: $return_code = EXECUTE_PRQA_LICENCED_PROGRAM( $prog, $ar_args, $comment );
   #
   # Executes $prog $args using GEN_SYS method and interrogates the 
   # return code.  If PRQA_FlexLm_Licence_Error and QAC-6.0 or QACPP-2.0
   # onward, then terminates.
   #
   my $prog    = shift;
   my $ar_args = shift;
   my $comment = shift;

   GEN::TRACE( "$comment\n" );
   GEN::TRACE( GEN::QUOTE_ARG( $prog ) . " " . GEN::STRING_FORM( @$ar_args ) . "\n\n" );

   my $return_code = GEN::EXECUTE_ARGS_WITH_RESULT( $prog, @$ar_args );

   if ( PARAM::VERSION_IS_QAC6_OR_QACPP2_OR_LATER() )
   {
      # QAC-6.0 and QACPP-2.0 onward:
      #
      my $prqa_flexlm_licence_failure = 11;
   
      if ( $return_code == $prqa_flexlm_licence_failure )
      {
   	GEN::DIEX( ": Licence failure", $prqa_flexlm_licence_failure );
      }
   }

   return $return_code;
}

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

sub EXECUTE_PRIMARY_ANALYSIS
{
   # Usage: ( $fail, $return_code, $error_logged ) = 
   #           EXECUTE_PRIMARY_ANALYSIS
   #              ( $file_name,
   #                $file_outputpath,
   #                $post_analysis_specified
   #              );
   #
   my $file_name               = shift;
   my $file_outputpath         = shift;
   my $post_analysis_specified = shift;

   my $ar_args = [ "-via", PARAM::SETTINGS_VIA(),
                   "-op", $file_outputpath, 
                   $file_name ];

   my $return_code = EXECUTE_PRQA_LICENCED_PROGRAM
                        ( PARAM::QA_BIN() . PARAM::QA_ANALYSIS_PROGRAM(),
                          $ar_args,
                          "ANALYSIS COMMAND:"
                        );

   my $perform_diagnostics = 0;   # false
   my $fail                = 0;   # false
   my $error_logged        = 0;   # false

   if ( $return_code != 0 )
   {
      # Here with error.
      # Display message found by looking up known messages for the analyser.
      #
   
      my $analyser_program = PARAM::QA_BIN() . 
                             PARAM::QA_FULL_ANALYSIS_PROGRAM_NAME();

      my $error_message;
      my $code_recognised;

      ( $error_message, $fail, $perform_diagnostics, $code_recognised ) =
         PARAM::ANALYSER_RETURN_CODE_LOOKUP( $analyser_program, $return_code );
   
      if ( $post_analysis_specified )
      {
         $error_message .= " - No secondary analysis";
      }

      OPC::LOG_ERROR
         ( $error_message,
           $return_code,
           $file_name,
           $error_logged,
           $code_recognised );

      $error_logged = 1;   # true
   }
   else
   {
      # Here with complete analysis.  However may have level 9 errors.
      # FvdB avoid use of errdsp/diaglist features

      # QAC before 6.2 is fails to return an error code for level 9 errors etc.
      # It always returns an exit value of 0 regardless of whether it had
      # any hard errors.
      # As a workjaround, if we have hard errors then grep through
      # the output from errdsp!
      # NOTE: errdsp may not be licensed!
      #
      # So check QAC version and if before 6.2 then invoke errdsp
      #
      $perform_diagnostics = ! PARAM::VERSION_IS_LATER_THAN_QAC6_2();
   }

   # If possible give diagnostic messages by displaying errdsp output for
   # level 9 messages (only).
   #
   if ( $perform_diagnostics )
   {
      # PERFORM_DIAGNOSTICS uses errdsp.
      # If errdsp not available then can't do PERFORM_DIAGNOSTICS,
      # so give modified analysis status.
      #
      if ( PARAM::ERRDSP_EXISTS() )
      {
         $error_logged |= 
            PERFORM_DIAGNOSTICS
               ( $file_name,
                 $file_outputpath,
                 $error_logged,
                 $post_analysis_specified );
      }
      else
      {
         OPC::LOG_ERROR
            ( "Analysis status unknown",
              $return_code,
              $file_name,
              $error_logged,
              "<KNOWN CODE>" );

         $error_logged = 1;   # true
      }
   }

   return ( $fail, $return_code, $error_logged );
}

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

sub EXECUTE_POST_ANALYSIS
{
   # Usage: ( $fail, $return_code, $error_logged ) = 
   #           EXECUTE_POST_ANALYSIS
   #              ( $file_name,
   #                $file_outputpath,
   #                $ar_post_analysis_details );
   #
   my $file_name                = shift;
   my $file_outputpath          = shift;
   my $ar_post_analysis_details = shift;

   my $quoted_file_name  = GEN::QUOTE_ARG( $file_name );
   my $quoted_outputpath = GEN::QUOTE_ARG( $file_outputpath );

   POST_ANAL_DETAIL:
   foreach my $post_anal_details ( @$ar_post_analysis_details )
   {
      # NB $post_anal_error_prog is responsible solely for naming
      # the program to be used when interpretting return codes.
      # This is the script (if present), otherwise the main program.
      #
      my ( $post_anal_program,
           $post_anal_params,
           $post_anal_error_program,
           $filter ) = split( "\n", $post_anal_details );

      if ( $filter ne "" )
      {
         next POST_ANAL_DETAIL if ( ! FILENAME_MATCHES_FILTER( $file_name, $filter ) );
      }

      # Set QAC[PP]OUTPATH env var for qacuser etc ie allow post-analysis 
      # to rely on env var for output path instead of passing as a parameter.
      #
      $ENV{ PARAM::QA_PROGRAM() . "OUTPATH" } = $file_outputpath;
      
      # Handling of %P, %M and %E postponed in QPARSE::PARSE_EXEC_PARAMS to now.
      # %P is settings.via, %M is met file and %E is err file.
      #

      my $settings_via = GEN::QUOTE_ARG( PARAM::PURE_SETTINGS_VIA() );

      my $base_file = GEN::APPLY_FINAL_SEP( $file_outputpath ) .
                      GEN::BASE_NAME( $file_name );
      my $met_file = GEN::QUOTE_ARG( "$base_file.met" );
      my $err_file = GEN::QUOTE_ARG( "$base_file.err" );

      $post_anal_params =~ s/\%P/$settings_via/gi;
      $post_anal_params =~ s/\%M/$met_file/gi;
      $post_anal_params =~ s/\%E/$err_file/gi;

#!!! $post_processeing_opts removed at 1.14 hence following redundant.
#!!!
#!!!	  # Must order command-line with -op. and -trace etc (ie $post_processing_opts)
#!!!	  # immediately before first %F.  If %F not present then filename file must
#!!!	  # have been hard coded in $post_anal_params - In this case append
#!!!	  # $post_processing_opts after $post_anal_params, despite the fact that may
#!!!	  # really want the -op at an earlier point (before first file).
#!!!	  #
#!!!
#!!!	  my $offset = index( lc( $post_anal_params ), "\%f" );
#!!!
#!!!	  if ( $offset != -1 )
#!!!	  {
#!!!	     # Here with %F in $post_anal_params so insert $post_processing_opts.
#!!!	     # (NB Inserting before first %F.  It doesn't matter if there are 
#!!!	     # any number of following %F.)
#!!!	     #
#!!!	     $post_anal_params = GEN::LEFT( $post_anal_params, $offset ) .
#!!!				 " $post_processing_opts " .
#!!!				 GEN::RIGHT2( $post_anal_params, $offset );
#!!!	  }
#!!!	  else
#!!!	  {
#!!!	     # Here with no %F.
#!!!	     # Append $post_processing_opts onto $post_anal_params string.
#!!!	     # NB This risks -op. being last with no preceding -op, but
#!!!	     # do have the advantage that -op wont be inserted before %S 
#!!!	     # string.
#!!!	     #
#!!!	     $post_anal_params = "$post_anal_params $post_processing_opts";
#!!!	  }

      # Expand + part of %F+
      # (%F+ is really a Unix GUI only parameter)
      #
      $post_anal_params =~ s/\%F\+/-outputpath $quoted_outputpath \%F/gi;
   
      # Replace %F for filename thro
      #
      ( $post_anal_params = $post_anal_params ) =~ s/\%F/$quoted_file_name/gi;

      #-- Perform post-processing -------------------------------------------
      #
      GEN::TRACE( "POST PROCESS COMMAND:\n" );
      GEN::TRACE( GEN::QUOTE_ARG( $post_anal_program ) . " $post_anal_params\n\n" );

      my $post_analysis_return_code = 
         GEN::EXECUTE_PROGRAM_WITH_RESULT( $post_anal_program, $post_anal_params );

      #-- Handle errors -----------------------------------------------------
      #
      my $perform_diagnostics = 0;   # false
      my $fail                = 0;   # false
      my $error_logged        = 0;   # false

      if ( $post_analysis_return_code != 0 )
      {
         # Here with error.
         # Display message found by looking up known messages for the analyser.
         #
      
         my $analyser_program = $post_anal_error_program;
   
         my $error_message;
         my $code_recognised;

         ( $error_message, $fail, $perform_diagnostics, $code_recognised ) =
            PARAM::ANALYSER_RETURN_CODE_LOOKUP
               ( $analyser_program,
                 $post_analysis_return_code );
      
         OPC::LOG_ERROR
            ( $error_message,
              $post_analysis_return_code,
              $file_name,
              $error_logged,
              $code_recognised );

         $error_logged = 1;   # true
      }

      #-- If relevant for analyser, check for level 9 errors ----------------
      #
      # If relevant for $post_analysis_return_code, give diagnostic messages
      # by displaying errdsp output for level 9 messages (only).
      #
      if ( $perform_diagnostics )
      {
         # PERFORM_DIAGNOSTICS uses errdsp.
         # If errdsp not available then can't do PERFORM_DIAGNOSTICS,
         # so give modified analysis status.
         #
         if ( PARAM::ERRDSP_EXISTS() )
         {
            $error_logged |= PERFORM_DIAGNOSTICS
               ( $file_name,
                 $file_outputpath,
                 $error_logged,
                 0 # Post Analysis has been specified, so don't write " - No secondary analysis"
               );
         }
         else
         {
            OPC::LOG_ERROR
               ( "Analysis status unknown",
                 $post_analysis_return_code,
                 $file_name,
                 $error_logged,
                 "<KNOWN CODE>" );
   
            $error_logged = 1;   # true
         }
      }
   
      if ( $error_logged )
      {
         # Here with error.  Terminate post-analysis sequence.
         #
         return ( $fail, $post_analysis_return_code, $error_logged );
      }
   }

   # Here on completion of all post-analysis programs with no errors.
   #
   return ( 0, 0, 0 );   # i.e. return ( no_fail, no_error, no_errors_logged )
}

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

sub FILE_COUNT_GT_ZERO
{
   # Usage: $file_count_gt_zero_bool = FILE_COUNT_GT_ZERO( $ar_file_names );
   #
   my $ar_file_names = shift;

   LOF:
   foreach my $file_name ( @$ar_file_names )
   {
      # If file is a project file parameter then skip here as files in a 
      # project file are handled as a set in their own right.
      # 
      next LOF if ( IS_PROJECT_FILE( $file_name ) );

      # Here with $file_name is a code file, so return true.
      #  
      return 1;  # true
   }
   
   # Here with no code files 
   #
   my $file_count_gt_zero_bool = 0;   # false

   return $file_count_gt_zero_bool;
}

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

sub WRITE_FILE_LIST
{
   # Usage: $file_count = WRITE_FILE_LIST
   #                         ( $filename,
   #                           $ar_file_names,
   #                           $ar_file_outputpaths,
   #                           $cmaf_path );
   #
   # Writes file list, with trailing \" in options with path parameters
   # (ie -op and -cmaf).
   #
   # Note that QA_FILE_LIST is read by the core PRQA products: QAC, QACPP and
   # ERRDSP etc, QA_PURE_FILE_LIST is read by post-analysis and global-analysis
   # programs which specify the 'filelist' file via %L[+] parameters.
   # 

   my $filename            = shift;
   my $ar_file_names       = shift;
   my $ar_file_outputpaths = shift;
   my $cmaf_path           = shift;

   my @code_filenames = ();
   my @output_paths   = ();

   BUILD_LOF:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name = $$ar_file_names[ $i ];

      # If file is a project file parameter then skip here as files in a 
      # project file are handled as a set in their own right.
      # 
      next BUILD_LOF if ( IS_PROJECT_FILE( $file_name ) );

      push @code_filenames, $file_name;
      push @output_paths,   $$ar_file_outputpaths[ $i ];
   }

   my $file_count = 0;

   if ( $#code_filenames == -1 )
   {
      # Here with no files.  Do nothing, i.e. do not write temp/qaw_filelist
      #
      return $file_count;
   }

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

   my $op = "";

   for ( my $i = 0; $i <= $#code_filenames; ++$i )
   {
      my $file_name       = $code_filenames[ $i ];
      my $file_outputpath = $output_paths[ $i ];

      if ( $file_outputpath ne $op )
      {
         # When writing "-op $file_outputpath" use same method as used to
         # write settings.via.  i.e. always append \ and always use quotes.
         # This is done to allow -op c:\ and because can't allow path to end
         # in \ (as this introduces a continuation line), put in quotes.
         #
         # Also canot write relative paths, so make absolute.
         #
         print FILE "-op " . 
                  PARSE_CL::FORMAT_PRQA_PATH_PARAMETER( $file_outputpath ) . "\n";

         $op = $file_outputpath;
      }

      print FILE 
            GEN::QUOTE_ARG( GEN::GET_FILE_PATH_ABSOLUTE( $file_name ) ) . "\n"; 
      
      ++$file_count;
   }

   if ( PARAM::VERSION_IS_QAC6_OR_QACPP2_OR_LATER() )
   {
      print FILE "-cmaf " . 
               PARSE_CL::FORMAT_PRQA_PATH_PARAMETER( $cmaf_path ) . "\n";
   }

   close FILE;

   return $file_count;
}

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

sub WRITE_PURE_FILE_LIST
{
   # Usage: $file_count = WRITE_PURE_FILE_LIST
   #                         ( $filename,
   #                           $ar_file_names,
   #                           $ar_file_outputpaths,
   #                           $cmaf_path );
   #
   # Writes PURE file list, with no trailing \" in options with path parameters
   # (ie -op and -cmaf).
   #
   # Note that QA_FILE_LIST is read by the core PRQA products: QAC, QACPP and
   # ERRDSP etc, QA_PURE_FILE_LIST is read by post-analysis and global-analysis
   # programs which specify the 'filelist' file via %L[+] parameters.
   # 
   my $filename            = shift;
   my $ar_file_names       = shift;
   my $ar_file_outputpaths = shift;
   my $cmaf_path           = shift;

   my @code_filenames = ();
   my @output_paths   = ();

   BUILD_LOF:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name = $$ar_file_names[ $i ];

      # If file is a project file parameter then skip here as files in a 
      # project file are handled as a set in their own right.
      # 
      next BUILD_LOF if ( IS_PROJECT_FILE( $file_name ) );

      push @code_filenames, $file_name;
      push @output_paths,   $$ar_file_outputpaths[ $i ];
   }

   my $file_count = 0;

   if ( $#code_filenames == -1 )
   {
      # Here with no files.  Do nothing, i.e. do not write temp/qaw_filelist
      #
      return $file_count;
   }

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

   my $op = "";

   for ( my $i = 0; $i <= $#code_filenames; ++$i )
   {
      my $file_name       = $code_filenames[ $i ];
      my $file_outputpath = $output_paths[ $i ];

      if ( $file_outputpath ne $op )
      {
         
         # When writing "-op $file_outputpath" use same method as used to
         # write settings.via.  i.e. always append \ and always use quotes.
         # This is done to allow -op c:\ and because can't allow path to end
         # in \ (as this introduces a continuation line), put in quotes.
         #
         # Also cannot write relative paths, so make absolute.
         #
         print FILE "-op " . 
                  PARSE_CL::FORMAT_PURE_PATH_PARAMETER( $file_outputpath ) . "\n";

         $op = $file_outputpath;
      }

      print FILE 
            GEN::QUOTE_ARG( GEN::GET_FILE_PATH_ABSOLUTE( $file_name ) ) . "\n"; 
      
      ++$file_count;
   }

   if ( PARAM::VERSION_IS_QAC6_OR_QACPP2_OR_LATER() )
   {
      print FILE "-cmaf " . 
               PARSE_CL::FORMAT_PURE_PATH_PARAMETER( $cmaf_path ) . "\n";
   }

   close FILE;

   return $file_count;
}

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

sub PERFORM_DIAGNOSTICS
{
   # Usage: $syntax_errors_found = 
   #           PERFORM_DIAGNOSTICS
   #              ( $file_name,
   #                $file_outputpath,
   #                $error_logged,
   #                $post_analysis_specified );
   #
   my $file_name               = shift;
   my $file_outputpath         = shift;
   my $error_logged            = shift;
   my $post_analysis_specified = shift;

   my $result_file = PARAM::QA_TEMP() . "diagnostic_file";
   GEN::DELETE_FILE_OR_DIRECTORY( $result_file );

   my @settings = ( PARAM::QA_PROGRAM(), "-via", PARAM::SETTINGS_VIA(), "-forgetall", "su",
  		    "-SU", "0,1,2,3,4,5,6,7,8", "-SUMM-", "-HDR+", "-REF-", "-M+",
  		    "-ONE-", "-L-", "-TEXT-", "-ST-", "-MAX", "2",
  		    "-op", $file_outputpath );

   # Be certain that $result_file is absolute.
   #
   $result_file = GEN::GET_FILE_PATH_ABSOLUTE( $result_file );

   my $ar_args = [ @settings,
                   "-via", FORMAT_A(),
    	           $file_name, "-file", $result_file ];

   # Seems as though errdsp sometimes does not write an output file.
   # So ensure empty output file exists before running errdsp.
   #
   #   GEN::TRUNCATE_FILE( $result_file );

   my $return_code_ignored = 
      EXECUTE_PRQA_LICENCED_PROGRAM
         ( PARAM::QA_BIN() . PARAM::ERRDSP_PROGRAM(),
           $ar_args,
           "ERRDSP DIAGNOSTICS COMMAND:"
         );

   my @diagnostic_text = GEN::LOAD_FILE_AS_ARRAY( $result_file );

   my $syntax_errors_found = grep( /9/o, @diagnostic_text );

   my $post_analysis_message;
   if ( $post_analysis_specified && ! $error_logged )
   {
      $post_analysis_message = "- No secondary analysis";
   }
   else
   {
      $post_analysis_message = "";
   }

   if ( $syntax_errors_found )
   {
      # Here with syntax error(s) in $file_name.
      # Display level 9 messages in log window.
      #

      if ( OPC::GET_PLOG_SELECTED() )
      {
         if ( ! $error_logged )
         {
            OPC::PLOG( GEN::TIME() );
         }

         OPC::PLOG( " Analysis failed with syntax errors $post_analysis_message:\n\n" );
      }
      else
      {
         if ( $error_logged )
         {
            OPC::PUT_ERR_LINE( "- Analysis failed with syntax errors. $post_analysis_message" );
         }
         else
         {
            OPC::PUT_ERR_LINE
               ( PARAM::QA_PROGRAM() .
                 " analysis of " .
                 GEN::QUOTE_ARG( $file_name ) .
                 " - Analysis failed with syntax errors. $post_analysis_message" );
         }
      }

      $ar_args = [ @settings,
                   "-via", FORMAT_B(), "-HTML-", 
    	           $file_name, "-file", $result_file ];

      my $return_code_ignored =
         EXECUTE_PRQA_LICENCED_PROGRAM
   	    ( PARAM::QA_BIN() . PARAM::ERRDSP_PROGRAM(),
              $ar_args,
              "ERRDSP DIAGNOSTICS COMMAND:"
   	    );

      my @diagnostic_text = GEN::LOAD_FILE_AS_ARRAY( $result_file );

      LINE:
      foreach my $line ( @diagnostic_text )
      {   
         chomp $line;

         next LINE if ( $line eq "" );
         next LINE if ( $line =~ /^\s*\^\s*$/o );
         next LINE if ( $line =~ 
            /--\s*no\s*more\s*occurrences\s*of\s*this\s*message\s*will\s*be\s*reported./oi );

         if ( GET_CUSTOM_FORMAT_SPECIFIED() )
         {
            OPC::PLOG( "   $line\n" );
         }
         else
         {
            # Get full file name in $fname
            #
            my ( $fname, $the_rest ) = split( ";", $line, 2 );
   
            next LINE if ( ! defined( $the_rest ) );
   
            if ( $fname eq $file_name )
            {
               $fname = GEN::BASE_NAME( $fname );
            }
            else
            {
               $fname = TRUNCATE_FILENAME( $fname );
            }
            OPC::PLOG( "   $fname$the_rest\n" );
         }
      }
      OPC::PLOG( "\n" );
   }

   return $syntax_errors_found;
}

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

sub DIE_IF_ANALYSER_NAMES_OVERLOADED
{
   # Usage: DIE_IF_ANALYSER_NAMES_OVERLOADED( $ar_post_analysis_details );
   #
   # Die if different analysers share the same base-name.
   #
   my $ar_post_analysis_details = shift;

   my @prog_base_names = ( PARAM::QA_ANALYSIS_PROGRAM() );
   my @prog_full_paths = ( PARAM::QA_BIN() . PARAM::QA_ANALYSIS_PROGRAM() );

   PROGRAM:
   foreach my $post_anal_details ( @$ar_post_analysis_details )
   {
      my ( $prog, $params, $error_prog ) = split( "\n", $post_anal_details );

      my $prog_base_name = GEN::BASE_NAME( $error_prog );

      my $prog_full_path   = GEN::GET_FILE_PATH_ABSOLUTE( $error_prog );
      my $prog_full_path_t = GEN::EVALUATE_RELATIVE_PATHS( $prog_full_path );

      my @looked_up_value = ();

      if ( GEN::PLATFORM_IS_WINDOWS() )
      {
         $prog_base_name   = lc( $prog_base_name );
         $prog_full_path_t = lc( $prog_full_path_t );
      }

      my $lookup_index = GEN::SEARCH( \@prog_base_names, $prog_base_name );

      if ( $lookup_index == -1 )
      {
         # Here with $error_prog is 'new'.
         #
         push @prog_base_names, $prog_base_name;
         push @prog_full_paths, $prog_full_path_t;

         next PROGRAM;
      }

      # Here with $prog_base_name already exists.
      # This is only acceptable if the full path is the same.
      #
      # NB Each entry in @prog_base_names and also @prog_full_paths
      # has the property that it is unique.  This is because 
      # entries are only inserted (above) if the $prog_base_name does
      # not exist.
      #
      if ( ! GEN::FILES_ARE_SAME( $prog_full_paths[ $lookup_index ], $prog_full_path_t ) )
      {
         GEN::DIE( ": Use of different analysers with same name not permitted:\n" .
                   GEN::QUOTE_ARG( $prog_full_paths[ $lookup_index ] ) . " and\n" .
                   GEN::QUOTE_ARG( $prog_full_path ) );
      }
   }
}
      
# ---------------------------------------------------------------------------

sub DO_ENSURE_TXT
{
   # Usage: DO_ENSURE_TXT
   #           ( $ar_file_names,
   #             $ar_file_outputpaths );
   #
   # Ensures that current .txt files exist for each supplied file.
   #
   my $ar_file_names       = shift;
   my $ar_file_outputpaths = shift;

   # Set up the display names of the .txt files
   #
   my $ar_disp_filenames = [];

   FNAME:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name = $$ar_file_names[ $i ];
 
      next FNAME if ( IS_PROJECT_FILE( $file_name ) );

      my $file_outputpath = $$ar_file_outputpaths[ $i ];

      my $base_name = GEN::APPLY_FINAL_SEP( $file_outputpath ) . 
                      GEN::BASE_NAME( $file_name );

      my $txt_file  = $base_name . ".txt";

      push @$ar_disp_filenames, $txt_file;
   }

   my $hr_display_files = GET_DISPLAY_FORM_OF_FILES( $ar_disp_filenames );

   # Ensure all .txt files exist.
   #
   FILENAME:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name = $$ar_file_names[ $i ];
 
      next FILENAME if ( IS_PROJECT_FILE( $file_name ) );

      my $file_outputpath   = $$ar_file_outputpaths[ $i ];

      my $base_name = GEN::APPLY_FINAL_SEP( $file_outputpath ) . 
                      GEN::BASE_NAME( $file_name );
      my $txt_file  = $base_name . ".txt";
      my $err_file  = $base_name . ".err";

      OPC::INITIALISE_PLOG_BUFFER();
      OPC::PLOG( $$hr_display_files{ $txt_file } . " - " );
      OPC::SHOW_PLOG();
      OPC::INITIALISE_PLOG_BUFFER();

      # Analysis has completed therefore .err must exist.
      # NB .err may not exist if -mode none has been used,
      # so fail if this is the case.
      #
      if ( ! -e $err_file )
      {
         GEN::DIE( ": File \"$err_file\" not found." );
      }

      # errdsp can fail if $txt_file isn't absolute.
      #
      $txt_file = GEN::GET_FILE_PATH_ABSOLUTE( $txt_file );

      my $txt_file_timestamp = GEN::FILE_TIMESTAMP( $txt_file );
      my $err_file_timestamp  = GEN::FILE_TIMESTAMP( $err_file );
      
      if ( ( ! -e $txt_file ) ||
           ( $txt_file_timestamp < $err_file_timestamp )
         )
      {
         my $ar_args = [ PARAM::QA_PROGRAM(), $file_name,
                         "-via", PARAM::SETTINGS_VIA(),
                         "-html-", "-op", $file_outputpath, "-file", $txt_file ];
   
         # Note the errdsp return code reflects the highest message number level 
         # written by errdsp.
         # 
         my $return_code_ignored = 
            EXECUTE_PRQA_LICENCED_PROGRAM( PARAM::QA_BIN() . PARAM::ERRDSP_PROGRAM(), 
                                           $ar_args,
                                           "ERRDSP COMMAND:"
                                         );

         if ( -e $txt_file )
         {
            OPC::PLOG( " Written\n" );
         }
         else
         {
            OPC::PLOG( " Write error\n" );
         }
      }
      else
      {
         OPC::PLOG( " Up to date\n" );
      }

      OPC::SHOW_PLOG();
   }
}

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

sub DO_ENSURE_HTML
{
   # Usage: DO_ENSURE_HTML
   #           ( $ar_file_names,
   #             $ar_file_outputpaths );
   #
   # Ensures that current .html files exist for each supplied file.
   # This option supplements and is executed before -pdsp, -pdspd and -glob.
   #
   my $ar_file_names       = shift;
   my $ar_file_outputpaths = shift;

   # Set up the display names of the .html files
   #
   my $ar_disp_filenames = [];

   FNAME:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name = $$ar_file_names[ $i ];
 
      next FNAME if ( IS_PROJECT_FILE( $file_name ) );

      my $file_outputpath = $$ar_file_outputpaths[ $i ];

      my $base_name = GEN::APPLY_FINAL_SEP( $file_outputpath ) . 
                      GEN::BASE_NAME( $file_name );

      my $html_file = $base_name . ".html";

      push @$ar_disp_filenames, $html_file;
   }

   my $hr_display_files = GET_DISPLAY_FORM_OF_FILES( $ar_disp_filenames );

   # Ensure all .html files exist.
   #
   FILENAME:
   for ( my $i = 0; $i <= $#$ar_file_names; ++$i )
   {
      my $file_name = $$ar_file_names[ $i ];
 
      next FILENAME if ( IS_PROJECT_FILE( $file_name ) );

      my $file_outputpath = $$ar_file_outputpaths[ $i ];

      my $base_name = GEN::APPLY_FINAL_SEP( $file_outputpath ) . 
                      GEN::BASE_NAME( $file_name );
      my $html_file = $base_name . ".html";
      my $err_file  = $base_name . ".err";

      OPC::INITIALISE_PLOG_BUFFER();
      OPC::PLOG( $$hr_display_files{ $html_file } . " - " );
      OPC::SHOW_PLOG();
      OPC::INITIALISE_PLOG_BUFFER();

      # Analysis has completed therefore .err must exist.
      # NB .err may not exist if -mode none has been used,
      # so fail if this is the case.
      #
      if ( ! -e $err_file )
      {
         GEN::DIE( ": File \"$err_file\" not found." );
      }

      # errdsp can fail if $html_file isn't absolute.
      #
      $html_file = GEN::GET_FILE_PATH_ABSOLUTE( $html_file );

      my $html_file_timestamp = GEN::FILE_TIMESTAMP( $html_file );
      my $err_file_timestamp  = GEN::FILE_TIMESTAMP( $err_file );
      
      if ( ( ! -e $html_file ) ||
           ( $html_file_timestamp < $err_file_timestamp )
         )
      {
         my $ar_args = [ PARAM::QA_PROGRAM(), $file_name,
                         "-via", PARAM::SETTINGS_VIA(),
                         "-html+", "-op", $file_outputpath, "-file", 
                         $html_file ];

         # Note the errdsp return code reflects the highest message number level 
         # written by errdsp.
         # 
         my $return_code_ignored = 
            EXECUTE_PRQA_LICENCED_PROGRAM( PARAM::QA_BIN() . PARAM::ERRDSP_PROGRAM(),
                                           $ar_args,
                                           "ERRDSP COMMAND:"
                                         );

         if ( -e $html_file )
         {
            OPC::PLOG( " Written\n" );

            # Ensure timestamp is current
            #
            GEN::APPEND_FILE( $html_file, "" );
         }
         else
         {
            OPC::PLOG( " Write error\n" );
         }
      }
      else
      {
         OPC::PLOG( " Up to date\n" );
      }

      OPC::SHOW_PLOG();
   }
}

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

sub INVOKE_PRJDSP
{
   # Usage: INVOKE_PRJDSP
   #           ( $pdsp_renderer,
   #             $ar_qa_opts,
   #             $stop_on_fail,
   #             $hr_display_files );
   #
   # Syntax of supplied option is:
   #   -pdsp [ <renderer program> ]
   # or
   #   -pdspd
   # 
   # NB 1. For -pdspd,  <renderer program> is fixed as PARAM::BROWSER_NAME();
   #
   # NB 2. If there is a -file option in @$ar_qa_opts, then use the associated
   #       file as the $result_file.
   #       This is further complicated by the fact that if the parameter of
   #       -file is not an absolute path, then the combination of the
   #       supplied relative filename and the current -op setting are used
   #       thereby placing the file in the current output directory.
   #
   my $pdsp_renderer    = shift;
   my $ar_qa_opts       = shift;
   my $stop_on_fail     = shift;
   my $hr_display_files = shift;

   my $quoted_filelist = GEN::QUOTE_ARG( PARAM::QA_FILELIST() );
   my $quoted_settings = GEN::QUOTE_ARG( PARAM::SETTINGS_VIA() );

   my $qa_program      = PARAM::QA_PROGRAM();
   my $prjdsp_program  = PARAM::PRJDSP_PROGRAM();

   #-- Derive the $result_file to be used by prjdsp.exe ---------------------
   #

   # First check for -file option.  If found use that as the result file.
   # If no -file option use QAC|QACPP/temp/t.html
   #

   my $result_file;
   my $prjdsp_params;

   my @file_setting = grep( /^-file /o, @$ar_qa_opts );

   if ( scalar( @file_setting ) > 1 )
   {
      GEN::DIE( ":RUN::INVOKE_PRJDSP - Program error.  Multiple -file options." );
   }
   elsif ( scalar( @file_setting ) == 1 )
   {
      # Here with -file in parameters.
      # Get the associated filename parameter.
      #
      $result_file = CALC_PRJDSP_RESULT_FILE_PATH
                        ( $file_setting[ 0 ],
                          $ar_qa_opts,
                          "Program error." );

      # Setup the prjdsp program run parameters
      #
      $prjdsp_params = " $qa_program -via $quoted_settings" .
                       " -html -list $quoted_filelist" .
                       " -file " . GEN::QUOTE_ARG( $result_file );
   }
   else
   {
      # Here when no -file setting.  Hence create own temporary
      # output file.
      #
      $result_file = PARAM::QA_TEMP() . "t.html";

      # Setup the prjdsp program run parameters
      #
      $prjdsp_params = " $qa_program -via $quoted_settings" .
                       " -html -list $quoted_filelist -file " .
                       GEN::QUOTE_ARG( $result_file );
   }

   #-- Run the report program -----------------------------------------------
   #
   
   GEN::DELETE_FILE_OR_DIRECTORY( $result_file );

   GEN::TRACE( "PRJDSP COMMAND:\n" );

   my $prjdsp_return_code =
      EXECUTE_GLOBAL_ANALYSIS_PROG
         ( PARAM::QA_BIN() . $prjdsp_program,
           $prjdsp_params,
           $stop_on_fail,
           $hr_display_files );

   
   if ( $prjdsp_return_code != 0 )
   {
      # Exit here as cannot render result after errors.
      #
      return;
   }

   #-- Render the result ----------------------------------------------------
   #
   RENDER( $pdsp_renderer, $result_file, "PRJDSP" );
}

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

sub RENDER
{
   # Usage: RENDER( $prog, $rendered_file, $name );
   #
   # Invokes $prog as a renderer of args $params and puts $name to trace.
   #
   my $prog_supplied          = shift;
   my $rendered_file_supplied = shift;
   my $name_supplied          = shift;

   # 20-Aug-2011: Following line added
   $prog_supplied = GEN::UNQUOTE( $prog_supplied );

   if ( ( $prog_supplied eq "" ) ||
        ( ! GEN::IS_FILE_EXECUTABLE( $prog_supplied ) )
      )
   {
      return;
   }

   my $prog;
   my $ar_args;
   if ( GEN::PLATFORM_IS_WINDOWS() )
   {
      $prog    = "start";
      $ar_args = [ "/wait", $prog_supplied, $rendered_file_supplied ];
   }
   else
   {
      $prog    = $prog_supplied;
      $ar_args = [ $rendered_file_supplied ];
   }

   GEN::TRACE( "$name_supplied RENDERING COMMAND:\n" );
   GEN::TRACE( GEN::QUOTE_ARG( $prog ) . " " . GEN::STRING_FORM( @$ar_args ) . "\n\n" );
  
   GEN::EXECUTE_ARGS( $prog, @$ar_args );
}

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

sub CALC_PRJDSP_RESULT_FILE_PATH
{
   # Usage: $result_file = CALC_PRJDSP_RESULT_FILE_PATH
   #                          ( $file_option,
   #                            $ar_qa_opts,
   #                            $err_msg );
   #
   # Here with -file option like: "-file fred".
   # Get the associated filename parameter and derive the 
   # full file name used by prjdsp when writing the output file.
   # 
   my $file_option = shift;
   my $ar_qa_opts  = shift;
   my $err_msg     = shift;

   my $result_file;
   ( $result_file = $file_option ) =~ s/-file\s+//o;

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

   # Trim any trailling slashes
   #
   $result_file =~ s/[\\\/]*$//o;

   if ( ! GEN::IS_FILE_PATH_ABSOLUTE( $result_file ) )
   {
      # Here when path is not absolute.
      # prjdsp behaviour becomes odd if $result_file is a relative path.
      # Hence insist that $result_file is a simple filename.
      #
      my $base_name = GEN::BASE_NAME( $result_file );

      if ( $result_file ne $base_name )
      {
         # However, can allow $result_file like "./output_file"
         #
         my $permissible_path = "." . GEN::GET_PATH_SEPARATOR() . $base_name;

         if ( $result_file ne $permissible_path )
         {
            GEN::DIE( ": \"$file_option\" is illegal.  Must be a simple file name." );
         }
      }

      # Need to calc the actual path created by combining current
      # $result_file value with the -op parameter.  This is used as the parameter
      # to the renderer call.  Rely on prjdsp.exe creation of output file
      # from the settings.via parameters supplied to it, being as described above.
      #
      # NB When this sub has been called for the INVOKE_PRJDSP case,
      # PARSE_CL::QAW_PARSE_COMMAND_LINE() always sets -op in returned
      # parameter list, hence get that now.
      # When this sub is called for the -glob using prjdsp case, then
      # search the parameters provided in the -glob command for -op.
      #

      # Force -outputpath options to -op
      #

      my @qa_opts = ();

      foreach my $opt ( @$ar_qa_opts )
      {
         if ( GEN::LEFT( lc( $opt ), length( "-outputpath" ) ) eq "-outputpath" )
         {
            push @qa_opts, "-op" . substr( $opt, length( "-outputpath" ) );
         }
         else
         {
            push @qa_opts, $opt;
         }
      }

      # Get list of -op options
      #

      my $op_has_param = 1;   # true

      my $ar_op_settings = 
         PARSE_CL::SEARCH_OPTION_SET_FOR_OPTION
            ( "-op", $op_has_param, \@qa_opts );

      @$ar_op_settings = GEN::REMOVE_DUPLICATES( @$ar_op_settings );

      # Require exactly 1 -op setting
      #

      if ( $#$ar_op_settings == -1 )
      {
         GEN::DIE( ": No -op setting. $err_msg" );
      }

      if ( $#$ar_op_settings > 1 )
      {
         my $count = $#$ar_op_settings + 1;

         GEN::DIE( ": $count -op settings. $err_msg" );
      }

      # Here with desired -op setting.  Use this to create the full file name.
      #

      my $op_path;
      ( $op_path = $$ar_op_settings[ 0 ] ) =~ s/-op\s+//o;

      $result_file = GEN::APPLY_FINAL_SEP( GEN::UNQUOTE( $op_path ) ) .
                     $result_file;

      $result_file = GEN::EVALUATE_RELATIVE_PATHS( $result_file );
   }
   else
   {
      # Here when $result_file is absolute, hence do nothing.
   }

   return $result_file;
}

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

sub EXECUTE_GLOBAL_ANALYSIS_PROGRAMS
{
   # Usage: EXECUTE_GLOBAL_ANALYSIS_PROGRAMS
   #           ( $cmaf_path,
   #             $root_source_path,
   #             $root_output_path,
   #             $ar_global_analysis_details,
   #             $stop_on_fail,
   #             $hr_display_files,
   #             $ar_qa_opts,
   #             $ar_file_names,
   #             $ar_file_outputpaths,
   #             $$hr_qaw_options );
   #
   my $cmaf_path                  = shift;
   my $root_source_path           = shift;
   my $root_output_path           = shift;
   my $ar_global_analysis_details = shift;
   my $stop_on_fail               = shift;
   my $hr_display_files           = shift;
   my $ar_qa_opts                 = shift;
   my $ar_file_names              = shift;
   my $ar_file_outputpaths        = shift;
   my $hr_qaw_options             = shift;

   if ( scalar( @$ar_global_analysis_details ) == 0 )
   {
      return;
   }

   my $project_name = GEN::BASE_NAME( $cmaf_path );

   GLOB_ANAL_DETAILS:
   foreach my $glob_anal_details ( @$ar_global_analysis_details )
   {
      my ( $glob_analysis_program,
           $glob_analysis_params,
           $glob_analysis_renderer,
           $glob_renderer_param,
           $glob_raw_param,
           $glob_filter ) = split( "\n", $glob_anal_details );

      # Create temporary file list which has filtered files only.
      #

      my $ar_filtered_file_names = [];
      
      if ( $glob_filter eq "" )
      {
         $ar_filtered_file_names = $ar_file_names;
      }
      else
      {
         foreach my $file_name ( @$ar_file_names )
         {
            if ( FILENAME_MATCHES_FILTER( $file_name, $glob_filter ) )
            {
               push @$ar_filtered_file_names, $file_name;
            }
         }
      }         

      next GLOB_ANAL_DETAILS if ( $#$ar_filtered_file_names == -1 );

      # NB The $cmaf_path argument is supplied simply to cater for %J
      # options within the global analysis parameters.  i.e. %J is not 
      # expanded in QPARSE::PARSE_GLOB_OPT, it is expanded here.
      #
      # $glob_analysis_params =~ s/\%J\+/-cmaf \%J/gi;

      $glob_analysis_params =~ s/\%J/$project_name/gi;

      # NB The $root_source_path and $root_output_path arguments are 
      # supplied simply to cater for %O and %D.  i.e. %O and %D are
      # expanded here.
      #
      $glob_analysis_params =~ s/\%D/$root_source_path/gi;
      $glob_analysis_params =~ s/\%O/$root_output_path/gi;

      # Set %P and %L settings here as need to know program in order
      # to determine whether to use pure or standard files.
      # EG for pal.exe, need to use standard files.
      # For all Perl based global analysers, need to use 'pure' settings
      # as cannot handle settings like: -op "C:\dir\"
      # Problem is the last \ escapes the quote.
      #

      # Calc glob_analysis_params using 'standard' option handling.
      # i.e. paths written as:
      # -op "C:\"
      # -op "C:\Fred Bloggs\"
      # etc
      #

      my $standard_quoted_filelist     = GEN::QUOTE_ARG( PARAM::QA_FILELIST() );
      my $standard_quoted_settings_via = GEN::QUOTE_ARG( PARAM::SETTINGS_VIA() );

      my $standard_glob_analysis_params = $glob_analysis_params;
      $standard_glob_analysis_params =~ s/\%L/$standard_quoted_filelist/gi;
      $standard_glob_analysis_params =~ s/\%P/$standard_quoted_settings_via/gi;

      # Calc glob_analysis_params using 'pure' option handling.
      # i.e. paths written as:
      # -op C:\
      # -op "C:\Fred Bloggs"
      # etc
      #

      my $pure_quoted_filelist     = GEN::QUOTE_ARG( PARAM::QA_PURE_FILELIST() );
      my $pure_quoted_settings_via = GEN::QUOTE_ARG( PARAM::PURE_SETTINGS_VIA() );
      
      my $pure_glob_analysis_params = $glob_analysis_params;
      $pure_glob_analysis_params =~ s/\%L/$pure_quoted_filelist/gi;
      $pure_glob_analysis_params =~ s/\%P/$pure_quoted_settings_via/gi;

      # Use $glob_analysis_params appropriate to program being run.
      # NB For prjdsp will always use pure params to derive -file setting info.
      #
      if ( PARAM::PROGRAM_USES_STANDARD_SETTINGS_FILE( $glob_analysis_program ) )
      {
         $glob_analysis_params = $standard_glob_analysis_params;
      }
      else
      {
         $glob_analysis_params = $pure_glob_analysis_params;
      }

      # prjdsp has specific handling of -file - see User Guide.
      # Modify params here accordingly.
      #
      # NB -pdsp[d] options accept -file option from the main command line.
      # However, -glob parameters are solely within the 2nd parameter of the
      # -glob option.  Hence there is a difference between running prjdsp 
      # from -pdsp[d] and -glob "prjdsp#<params>#..>". 
      #    1. When running -pdsp[d] -file on command line is interrogated.
      #    2. When running prjdsp from -glob, -file on command line is ignored.
      #
      if ( lc ( GEN::STEM_NAME( $glob_analysis_program ) ) eq "prjdsp" )
      {
         $glob_renderer_param = 
            GET_RENDERER_PARAM_FOR_GLOB_PRJDSP_CASE( $pure_glob_analysis_params );
      }

      my $quoted_glob_analysis_program = GEN::QUOTE_ARG( $glob_analysis_program );

      # Perform global-processing
      #
      if ( $glob_renderer_param eq "STDOUT" )
      {
         # Here when outputing to STDOUT.
         # Note that $glob_analysis_params must have had a -file STDOUT setting.
         # -see QPARSE::PARSE_GLOB_OPT().
         #
         GEN::TRACE( "GLOBAL ANALYSIS COMMAND:\n" );

         # Write to output file named Temp\glob_result.html and
         # then print that file to stdout.
         #
         my $result_filename        = PARAM::QA_RESULTS_TEMP() . "glob_result.html";
         my $quoted_result_filename = GEN::QUOTE_ARG( $result_filename );
         
         my $global_analysis_return_code =
            EXECUTE_GLOBAL_ANALYSIS_PROG
               ( $glob_analysis_program,
                 $glob_analysis_params . " -file $quoted_result_filename",
                 $stop_on_fail,
                 $hr_display_files );

         if ( $global_analysis_return_code == 0 )
         {
            GEN::PRINT_FILE( $result_filename );
         }

         # Not rendering the result so move to next glob details
         #
         next GLOB_ANAL_DETAILS;
      }

      my ( $perform_global_analysis, $log_msg ) = 
         MODE::EVALUATE_GLOB_ANALYSIS_REQUIREMENT
            ( $$hr_qaw_options{ "-mode" },
              $glob_analysis_program,
              $glob_raw_param,
              $glob_renderer_param,
              $ar_qa_opts,
              $ar_filtered_file_names,
              $ar_file_outputpaths,
              $cmaf_path );

      if ( ! $perform_global_analysis )
      {
         my $dummy_time_string = " " x ( 2 * length( GEN::TIME() ) + 4 );

         OPC::INITIALISE_PLOG_BUFFER();
         OPC::PLOG( $$hr_display_files{ $glob_analysis_program } .
                    $dummy_time_string . " $log_msg\n" 
                  );
         OPC::SHOW_PLOG();
      }
      else
      {
         # Ensure that renderer file (if exists) is writable.
         #
         GEN::DELETE_FILE_OR_DIRECTORY( $glob_renderer_param );

         # NB Global analysis is run regardless of whether %R or -file was specified.
         # If -file or %R was specified, then the parameter will be within the
         # $glob_analysis_params string.
         #
         GEN::TRACE( "GLOBAL ANALYSIS COMMAND:\n" );
         
         my $global_analysis_return_code =
            EXECUTE_GLOBAL_ANALYSIS_PROG
               ( $glob_analysis_program,
                 $glob_analysis_params,
                 $stop_on_fail,
                 $hr_display_files );
         
         if ( $global_analysis_return_code != 0 )
         {
            # Skip here as cannot render result after errors.
            #
            next GLOB_ANAL_DETAILS;
         }
      }

      #-- Render the result -------------------------------------------------
      #
      if ( $glob_analysis_renderer eq "" )
      {
         $glob_analysis_renderer = PARAM::BROWSER_NAME();
      }

      if ( ( $glob_analysis_renderer ne "" ) &&
           ( $glob_renderer_param ne "" ) &&
           ( -e $glob_renderer_param )
         )
      {
         my $absolute_glob_renderer_param = 
            GEN::GET_FILE_PATH_ABSOLUTE( $glob_renderer_param );

         RENDER( $glob_analysis_renderer, $absolute_glob_renderer_param, "GLOBAL ANALYSIS" );
      }
   }
}

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

sub FILENAME_MATCHES_FILTER
{
   # Usage: $bool_true_on_match = FILENAME_MATCHES_FILTER( $file_name, $filter );
   #
   # $filter is a string containing match characters and 
   # ? - to match any individual char
   # * - to match any char sequence.
   #
   my $file_name = shift;
   my $filter    = shift;

   ( $filter = $filter ) =~ s/\*/\.\*/go;
   ( $filter = $filter ) =~ s/\?/\.?/go;

   return $file_name =~ /$filter/;
}

# ---------------------------------------------------------------------------
         
sub EXECUTE_GLOBAL_ANALYSIS_PROG
{
   # Usage: $return_code =
   #           EXECUTE_GLOBAL_ANALYSIS_PROG
   #              ( $program_name,
   #                $program_params,
   #                $stop_on_fail,
   #                $hr_display_files );
   #
   my $program_name     = shift;
   my $program_params   = shift;
   my $stop_on_fail     = shift;
   my $hr_display_files = shift;

   OPC::INITIALISE_PLOG_BUFFER();
   OPC::PLOG( $$hr_display_files{ $program_name } . " " . GEN::TIME() . " - " );
   OPC::SHOW_PLOG();
   OPC::INITIALISE_PLOG_BUFFER();

   GEN::TRACE( GEN::QUOTE_ARG( $program_name ) . " $program_params\n\n" );

   # Global analysis program may be pal or viewer etc, but may also be another
   # un PRQA licenced program, e.g. a user program etc.
   # Hence do not terminate on return-code == 11 (licence failures(?)).
   # ie Don't use sub EXECUTE_PRQA_LICENCED_PROGRAM use simple execute instead.
   #
   my $return_code = GEN::EXECUTE_PROGRAM_WITH_RESULT( $program_name, $program_params );

   OPC::PLOG( GEN::TIME() );
         
   if ( $return_code == 0 )
   {
      OPC::PLOG( " Analysis complete\n" );
      OPC::SHOW_PLOG();
   }
   else
   {
      OPC::PLOG( " Analysis failed. " );
      OPC::SHOW_PLOG();

      my ( $error_message, $fail, $perform_diagnostics, $code_recognised ) =
           PARAM::ANALYSER_RETURN_CODE_LOOKUP( $program_name, $return_code );
   
      if ( $code_recognised eq "<KNOWN CODE>" )
      {
         # Check whether already mentioned the return code:
         #
         if ( $error_message =~ /returns? code/oi )
         {
            # Here when return code already mentioned
            # So don't display again.
            #
            OPC::PUT_ERR( "$error_message." );
         }
         else
         {
            OPC::PUT_ERR
               ( "$error_message.  Return code = $return_code" );
         }
      }
      else
      {
         OPC::PUT_ERR( $error_message );
      }

      if ( $fail )
      {
         # Check -stoponfail.
         #
         if ( $stop_on_fail )
         {
            OPC::PUT_ERR_LINE
               ( " -STOPONFAIL specified, hence execution terminated." );
         
            GEN::EXIT( $return_code );
         }
      }
      else
      {
         OPC::PUT_ERR_LINE( "" );
      }
   }

   return $return_code;
}

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

sub GET_RENDERER_PARAM_FOR_GLOB_PRJDSP_CASE
{
   # Usage: $renderer_param = 
   #           GET_RENDERER_PARAM_FOR_GLOB_PRJDSP_CASE( $glob_analysis_params );
   #
   # prjdsp accepts a -file option.  If specified and absolute the given file
   # is used for prjdsp output.  If not absolute the supplied filename is
   # appended to the current -op setting and output written to that.
   #
   # In this sub, evaluate whether -file is specified in the $glob_analysis_params
   # directly, or indirectly via %P inclusion.
   #
   # Therefore need to parse the parameters to be passed to the glob program.
   # To do this, should use option definition file for the global analyser
   # as it may have any options, some of which being outside of the QAW set.
   # 
   # To avoid using glob program option files, parse the supplied 
   # glob_analysis_params for -file options only.
   #
   my $params = shift;

   my $result_file = "";

   my $ar_glob_options = PARSE_CL::GET_ALL_OPTIONS_IN( $params );

   my $file_opt_has_param = 1;   # true

   my $ar_file_settings = 
         PARSE_CL::SEARCH_OPTION_SET_FOR_OPTION
            ( "-file", $file_opt_has_param, $ar_glob_options );

   if ( scalar( @$ar_file_settings ) > 1 )
   {
      GEN::DIE( ": Illegal multiple -file options in -glob parameter \"$params\"." );
   }
   elsif ( scalar( @$ar_file_settings ) == 1 )
   {
      # Here with -file in parameters.
      # Get the associated filename parameter.
      #
      $result_file = CALC_PRJDSP_RESULT_FILE_PATH
         ( $$ar_file_settings[ 0 ],
           $ar_glob_options,
           "-glob for \"prjdsp\" has -file with non-absolute parameter " .
           "which needs exactly one -op setting." );
   }
   else
   {
      # Here when no -file setting.
      #
      $result_file = "";
   }

   return $result_file;
}

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

sub INVOKE_VIEWER
{
   # Usage: INVOKE_VIEWER();
   #

   my $prog = PARAM::QA_BIN() . "viewer";

   my $ar_args = [ PARAM::QA_PROGRAM(), 
                   "-via", PARAM::SETTINGS_VIA(),
                   "-list" , PARAM::QA_FILELIST() ];

   my $return_code_ignored = EXECUTE_PRQA_LICENCED_PROGRAM
                                ( $prog,
                                  $ar_args,
                                  "VIEWER COMMAND:"
                                );
}

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

sub EXECUTE_CARGS_FILES
{
   # Usage: $return_code = EXECUTE_CARGS_FILES( $ar_cargs_parameters );
   #
   my $ar_cargs_parameters = shift;

   # NB STRING_FORM calls QUOTE_ARG for each, hence elements of 
   # $ar_cargs_parameters with embedded " will be correctly escaped now.
   #
   my $command = GEN::STRING_FORM( @$ar_cargs_parameters );

   GEN::TRACE( "CARGS EXECUTION COMMAND:\n" );
   GEN::TRACE( "$command\n\n" );

   my $return_code = GEN::EXECUTE_SYSTEM_COMMAND_WITH_RESULT( $command );

   return $return_code;
}

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

sub LOAD_CFG_FILE_OPTIONS
{
   # Usage: @options = LOAD_CFG_FILE_OPTIONS( $cfg_filename );
   #
# 1-11-07: CR12558 - QAW no longer read qac[pp].cfg
# Hence this sub is no longer needed, but keep anyway
   my $cfg_filename = shift;

   my @options = ();

   open CFG_FILE, "<$cfg_filename" or GEN::DIE( ": Cannot open \"$cfg_filename\"\n$!" );

   # Skip to #options line
   #
   my $line = <CFG_FILE>;

   while ( ! eof( CFG_FILE ) && ( $line !~ /^#options\s*$/o ) )
   {
      $line = <CFG_FILE>;
   }

   # Skip the line containing #options
   #
   if ( ! eof( CFG_FILE ) )
   {
      $line = <CFG_FILE>;
   }

   # Read options part of cfg file
   #
   OPTION:
   while ( ! eof( CFG_FILE ) )
   {
      $line = <CFG_FILE>;

      if ( substr( $line, 0, 1 ) ne "*" )
      {
         chomp $line;
         push @options, $line;
      }
   }

   close CFG_FILE;

   return @options;
}

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

sub CLEAN_FILE_NAMES
{
   # Usage: ( $ar_file_names, $ar_file_outputpaths, $ar_options ) =
   #           CLEAN_FILE_NAMES
   #           ( $ar_file_names,
   #             $ar_file_outputpaths,
   #             $ar_qa_opts );
   #
   # Make absolute with no ../, and links followed if -ef+ is set.
   #
   # Also expand symbolic links if -ef+ is set.  NB this is the default
   # i.e. -ef+ applies if there are no -ef options.
   #
   # NB -ef+ means expand filenames.  This is the default.
   #    -ef- means don't expand filenames.
   #
   my $ar_file_names       = shift;
   my $ar_file_outputpaths = shift;
   my $ar_options          = shift;

   # -ef+|- controls whether QAC/PP and other tools expand symbolic links.
   # 
   # If -ef- is set, expansion is switched off and symbolic links to
   # files and paths are treated as though they are actual file.
   # Hence despite a file being a link, eg
   #    /usr/fred/file.c --> /usr/data/1242342.c
   # all QAC/PP programs will treat the file as though it is named
   #    /usr/fred/file.c
   #
   # If -ef+ is set, symbolic links are expanded by QAC/PP and other tools.
   # Hence errdsp etc and post-analysis will deal with the file:
   #    /usr/fred/file.c --> /usr/data/1242342.c 
   # by its real name, e.g.
   #    /usr/data/1242342.c
   # 
   # QAW did include the ability to expand symbolic links in this sub.
   # However this is now disabled as the best solution is to force the 
   # setting in qac.cfg as either -ef+ or -ef-.
   # The preferred setting is -ef- as this prevents symbolic links 
   # being expanded in QAC/PP etc.
   #
   my $ef_expand_is_on = 1;   # true (default)

   if ( grep( /^\-ef\-/oi, @$ar_options ) > 0 )
   {
      $ef_expand_is_on = 0;   # false
   }

   # 18-11-04: As mentioned in the above comment, disable any QAW
   # expansion of symbolic links by forcing $ef_expand_is_on to OFF.
   #
   $ef_expand_is_on = 0;   # REMOVE THIS STATEMENT TO ENABLE QAW
                           # EXPANSION OF SYMBOLIC LINKS.

   foreach my $file_name ( @$ar_file_names )
   {
      if ( ! IS_PROJECT_FILE( $file_name ) )
      {
         if ( $ef_expand_is_on )
         {
            $file_name = GEN::EVALUATE_SYMBOLIC_LINK( $file_name );
         }
         $file_name = GEN::GET_FILE_PATH_ABSOLUTE( $file_name );
         $file_name = GEN::EVALUATE_RELATIVE_PATHS( $file_name );
      }
   }

   foreach my $file_name ( @$ar_file_outputpaths )
   {
      if ( $ef_expand_is_on )
      {
         $file_name = GEN::EVALUATE_SYMBOLIC_LINK( $file_name );
      }
      $file_name = GEN::GET_FILE_PATH_ABSOLUTE( $file_name );
      $file_name = GEN::EVALUATE_RELATIVE_PATHS( $file_name );
   }

#!!!   foreach my $option ( @$ar_options )
#!!!   {
#!!!      my ( $opt, $param ) = split( " ", $option, 2 );
#!!!#!!! NEED TO HANDLE COMPOUND OPTIONS (like -exec, -glob, -saseq, -maseq) 
#!!!     BY EXPLICITLY CLEANING PARAMETERS INDIVIDUALLY
#!!!
#!!!     if $option is an option with a file name parameter, e.g. -op, -i, -cmaf...
#!!!        ! => Really need to code into .options.properties whether an option
#!!!        !    has a filename parameter (and also which parameter number).
#!!!      if ( $ef_expand_is_on )
#!!!      {
#!!!         $param = GEN::EVALUATE_SYMBOLIC_LINK( $param );
#!!!      }
#!!!      $param = GEN::GET_FILE_PATH_ABSOLUTE( $param );
#!!!      $param = GEN::EVALUATE_RELATIVE_PATHS( $param );
#!!!
#!!!      $option = "$opt $param";
#!!!   }

   return ( $ar_file_names, $ar_file_outputpaths, $ar_options );
}

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

sub WRITE_ANNOTATED_SOURCE
{
   # Usage: WRITE_ANNOTATED_SOURCE
   #    ( $analysed_filename,
   #      $analysis_outputpath,
   #      $output_filename );
   #
   # Runs errdsp on $settings_via settings and writes the result to
   # $output_filename.
   #
   my $analysed_filename   = shift;
   my $analysis_outputpath = shift;
   my $output_filename     = shift;

   my $errdsp_program = PARAM::QA_BIN() . PARAM::ERRDSP_PROGRAM();

   my $result_file = PARAM::QA_TEMP() . "errdsp_output";

   # Be certain that $result_file is absolute.
   #
   $result_file = GEN::GET_FILE_PATH_ABSOLUTE( $result_file );

   GEN::DELETE_FILE_OR_DIRECTORY( $result_file );

   my $ar_args = [ PARAM::QA_PROGRAM(),  "-via",
                   PARAM::SETTINGS_VIA(),
                   "-op", $analysis_outputpath,
                   $analysed_filename,
                   "-file", $result_file ];

   GEN::TRACE( "ERRDSP DISPLAY COMMAND:\n" );
   GEN::TRACE( GEN::QUOTE_ARG( $errdsp_program ) . " " . GEN::STRING_FORM( @$ar_args ) . "\n\n" );

   GEN::EXECUTE_ARGS( $errdsp_program, @$ar_args );

   my $output = GEN::LOAD_FILE_AS_STRING( $result_file );

   if ( lc( $output_filename ) eq "stdout" )
   {
      OPC::PUT_MSG( $output );
   }
   elsif ( lc( $output_filename ) eq "stderr" )
   {
      OPC::PUT_ERR( $output );
   }
   else
   {
      GEN::APPEND_FILE( $output_filename, split( "\n", $output ) );
   }
}

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

sub DISPLAY_SAPF_REPORT
{
   # Usage: DISPLAY_SAPF_REPORT( $file_name, $file_outputpath );
   #
   my $file_name       = shift;
   my $file_outputpath = shift;

   my $op            = GEN::APPLY_FINAL_SEP( $file_outputpath );
   my $base_name     = GEN::BASE_NAME( $file_name );
   my $opt_file_name = $op . $base_name . ".opt";

   my @opt_text = GEN::LOAD_FILE_AS_ARRAY( $opt_file_name );

   # Remove the comments
   #
   @opt_text = grep( ! /^\*/o, @opt_text );

   # Print the report
   #
   my $heading = "-- Analysis Options for $base_name --";
   $heading    .= "-" x ( 76 - length( $heading ) );

   OPC::PUT_MSG_LINE( "" );
   OPC::PUT_MSG_LINE( "$heading" );

   foreach my $line ( @opt_text )
   {
      chomp $line;
      OPC::PUT_MSG_LINE( $line );
   }

   my $ending = "-- End of Analysis Options for $base_name -";
   $ending    .= "-" x ( length( $heading ) - length( $ending ) );

   OPC::PUT_MSG_LINE( "$ending" );
   OPC::PUT_MSG_LINE( "" );
}

# -- eof RUN.pm -------------------------------------------------------------
