package ProjectCreator;

# ************************************************************
# Description   : Base class for all project creators
# Author        : Chad Elliott
# Create Date   : 3/13/2002
# ************************************************************

# ************************************************************
# Pragmas
# ************************************************************

use strict;
use FileHandle;
use File::Path;
use File::Compare;

use Creator;
use TemplateInputReader;
use TemplateParser;
use FeatureParser;

use vars qw(@ISA);
@ISA = qw(Creator);

# ************************************************************
# Data Section
# ************************************************************

my($BaseClassExtension)      = 'mpb';
my($ProjectCreatorExtension) = 'mpc';
my($TemplateExtension)       = 'mpd';
my($TemplateInputExtension)  = 'mpt';

## Valid names for assignments within a project
## Bit Meaning
## 0   Preserve the order for additions (1) or invert it (0)
## 1   Add this value to template input value (if there is one)
## 2   Preserve <% %> settings for evaluation within the template
my(%validNames) = ('exename'         => 1,
                   'sharedname'      => 1,
                   'staticname'      => 1,
                   'libpaths'        => 3,
                   'install'         => 1,
                   'includes'        => 3,
                   'after'           => 1,
                   'custom_only'     => 1,
                   'libs'            => 2,
                   'lit_libs'        => 2,
                   'pure_libs'       => 2,
                   'pch_header'      => 1,
                   'pch_source'      => 1,
                   'prebuild'        => 5,
                   'postbuild'       => 5,
                   'dllout'          => 1,
                   'libout'          => 1,
                   'dynamicflags'    => 3,
                   'staticflags'     => 3,
                   'version'         => 1,
                   'recurse'         => 1,
                   'requires'        => 3,
                   'avoids'          => 3,
                   'tagname'         => 1,
                   'tagchecks'       => 1,
                   'macros'          => 3,
                  );

## Custom definitions only
## Bit  Meaning
## 0    Value is always an array
## 1    Value is an array and name gets 'outputext' converted to 'files'
## 2    Value is always scalar
## 3    Name can also be used in an 'optional' clause
## 4    Needs <%...%> conversion
my(%customDefined) = ('automatic'                   => 0x04,
                      'dependent'                   => 0x14,
                      'command'                     => 0x14,
                      'commandflags'                => 0x14,
                      'precommand'                  => 0x14,
                      'postcommand'                 => 0x14,
                      'inputext'                    => 0x01,
                      'libpath'                     => 0x04,
                      'output_option'               => 0x14,
                      'pch_postrule'                => 0x04,
                      'pre_extension'               => 0x08,
                      'source_pre_extension'        => 0x08,
                      'template_pre_extension'      => 0x08,
                      'header_pre_extension'        => 0x08,
                      'inline_pre_extension'        => 0x08,
                      'documentation_pre_extension' => 0x08,
                      'resource_pre_extension'      => 0x08,
                      'generic_pre_extension'       => 0x08,
                      'pre_filename'                => 0x08,
                      'source_pre_filename'         => 0x08,
                      'template_pre_filename'       => 0x08,
                      'header_pre_filename'         => 0x08,
                      'inline_pre_filename'         => 0x08,
                      'documentation_pre_filename'  => 0x08,
                      'resource_pre_filename'       => 0x08,
                      'generic_pre_filename'        => 0x08,
                      'source_outputext'            => 0x0a,
                      'template_outputext'          => 0x0a,
                      'header_outputext'            => 0x0a,
                      'inline_outputext'            => 0x0a,
                      'documentation_outputext'     => 0x0a,
                      'resource_outputext'          => 0x0a,
                      'generic_outputext'           => 0x0a,
                     );

## Custom sections as well as definitions
## Value  Meaning
## 0    No modifications
## 1    Needs <%...%> conversion
my(%custom) = ('command'       => 1,
               'commandflags'  => 1,
               'dependent'     => 1,
               'gendir'        => 0,
               'precommand'    => 1,
               'postcommand'   => 1,
              );

## All matching assignment arrays will get these keywords
my(@default_matching_assignments) = ('recurse',
                                    );

## These matching assingment arrays will get added, but only to the
## specific project component types.
my(%default_matching_assignments) = ('source_files' => ['buildflags'],
                                    );

## Deal with these components in a special way
my(%specialComponents) = ('header_files'   => 1,
                          'inline_files'   => 1,
                          'template_files' => 1,
                         );
my(%sourceComponents)  = ('source_files'   => 1,
                          'template_files' => 1,
                         );

my($defgroup)    = 'default_group';
my($grouped_key) = 'grouped_';

## Matches with generic_outputext
my($generic_key) = 'generic_files';

# ************************************************************
# C++ Specific Component Settings
# ************************************************************

## Valid component names within a project along with the valid file extensions
my(%cppvc) = ('source_files'        => [ "\\.cpp", "\\.cxx", "\\.cc", "\\.c", "\\.C", ],
              'template_files'      => [ "_T\\.cpp", "_T\\.cxx", "_T\\.cc", "_T\\.c", "_T\\.C", ],
              'header_files'        => [ "\\.h", "\\.hpp", "\\.hxx", "\\.hh", ],
              'inline_files'        => [ "\\.i", "\\.ipp", "\\.inl", ],
              'documentation_files' => [ "README", "readme", "\\.doc", "\\.txt", "\\.html" ],
              'resource_files'      => [ "\\.rc", ],
             );

## Exclude these extensions when auto generating the component values
my(%cppec) = ('source_files' => $cppvc{'template_files'},
             );

# ************************************************************
# C# Specific Component Settings
# ************************************************************

## Valid component names within a project along with the valid file extensions
my(%csvc) = ('source_files'        => [ "\\.cs" ],
             'config_files'        => [ "\\.config" ],
             'resx_files'          => [ "\\.resx" ],
             'aspx_files'          => [ "\\.aspx" ],
             'ico_files'           => [ "\\.ico" ],
             'documentation_files' => [ "README", "readme", "\\.doc", "\\.txt", "\\.html" ],
            );

my(%csma) = ('source_files' => [ 'subtype' ],
            );

# ************************************************************
# Java Specific Component Settings
# ************************************************************

## Valid component names within a project along with the valid file extensions
my(%jvc) = ('source_files'        => [ "\\.java" ],
            'documentation_files' => [ "README", "readme", "\\.doc", "\\.txt", "\\.html" ],
           );

# ************************************************************
# Visual Basic Specific Component Settings
# ************************************************************

## Valid component names within a project along with the valid file extensions
my(%vbvc) = ('source_files'        => [ "\\.vb" ],
             'config_files'        => [ "\\.config" ],
             'resx_files'          => [ "\\.resx" ],
             'aspx_files'          => [ "\\.aspx" ],
             'ico_files'           => [ "\\.ico" ],
             'documentation_files' => [ "README", "readme", "\\.doc", "\\.txt", "\\.html" ],
            );

my(%vbma) = ('source_files' => [ 'subtype' ],
            );

# ************************************************************
# Language Specific Component Settings
# ************************************************************

# Index Description
# ----- -----------
# 0     File types
# 1     Files automatically excluded from source_files
# 2     Assignments available in standard file types
# 3     The entry point for executables
# 4     The language uses a C preprocessor
my(%language) = ('cplusplus' => [ \%cppvc, \%cppec, {}    , 'main', 1 ],
                 'csharp'    => [ \%csvc,  {},      \%csma, 'Main', 0 ],
                 'java'      => [ \%jvc,   {},      {}    , 'main', 0 ],
                 'vb'        => [ \%vbvc,  {},      \%vbma, 'Main', 0 ],
                );

# ************************************************************
# Subroutine Section
# ************************************************************

sub new {
  my($class)      = shift;
  my($global)     = shift;
  my($inc)        = shift;
  my($template)   = shift;
  my($ti)         = shift;
  my($dynamic)    = shift;
  my($static)     = shift;
  my($relative)   = shift;
  my($addtemp)    = shift;
  my($addproj)    = shift;
  my($progress)   = shift;
  my($toplevel)   = shift;
  my($baseprojs)  = shift;
  my($gfeature)   = shift;
  my($feature)    = shift;
  my($features)   = shift;
  my($hierarchy)  = shift;
  my($exclude)    = shift;
  my($makeco)     = shift;
  my($nmod)       = shift;
  my($applypj)    = shift;
  my($genins)     = shift;
  my($into)       = shift;
  my($language)   = shift;
  my($use_env)    = shift;
  my($expandvars) = shift;
  my($self)       = $class->SUPER::new($global, $inc,
                                       $template, $ti, $dynamic, $static,
                                       $relative, $addtemp, $addproj,
                                       $progress, $toplevel, $baseprojs,
                                       $feature, $features,
                                       $hierarchy, $nmod, $applypj,
                                       $into, $language, $use_env,
                                       $expandvars,
                                       'project');

  $self->{$self->{'type_check'}}   = 0;
  $self->{'feature_defined'}       = 0;
  $self->{'project_info'}          = [];
  $self->{'lib_locations'}         = {};
  $self->{'reading_parent'}        = [];
  $self->{'dexe_template_input'}   = undef;
  $self->{'lexe_template_input'}   = undef;
  $self->{'lib_template_input'}    = undef;
  $self->{'dll_template_input'}    = undef;
  $self->{'flag_overrides'}        = {};
  $self->{'custom_special_output'} = {};
  $self->{'custom_special_depend'} = {};
  $self->{'special_supplied'}      = {};
  $self->{'pctype'}                = $self->extractType("$self");
  $self->{'verbatim'}              = {};
  $self->{'verbatim_accessed'}     = {$self->{'pctype'} => {}};
  $self->{'defaulted'}             = {};
  $self->{'custom_types'}          = {};
  $self->{'parents_read'}          = {};
  $self->{'inheritance_tree'}      = {};
  $self->{'remove_files'}          = {};
  $self->{'expanded'}              = {};

  my($typefeaturef) = $self->mpc_dirname($gfeature) . '/' .
                      $self->{'pctype'} . '.features';
  $typefeaturef = undef if (! -r $typefeaturef);
  $self->{'feature_parser'}        = new FeatureParser($features,
                                                       $gfeature,
                                                       $typefeaturef,
                                                       $feature);
  $self->{'sort_files'}            = $self->sort_files();
  $self->{'source_callback'}       = undef;
  $self->{'dollar_special'}        = $self->dollar_special();
  $self->{'generate_ins'}          = $genins;
  $self->{'addtemp_state'}         = undef;
  $self->{'command_subs'}          = $self->get_command_subs();
  $self->{'escape_spaces'}         = $self->escape_spaces();
  $self->{'case_insensitive'}      = $self->case_insensitive();

  $self->add_default_matching_assignments();
  $self->reset_generating_types();

  return $self;
}


sub is_keyword {
  my($self) = shift;
  my($name) = shift;
  return $self->{'valid_names'}->{$name};
}


sub read_global_configuration {
  my($self)   = shift;
  my($input)  = $self->get_global_cfg();
  my($status) = 1;

  if (defined $input) {
    ## If it doesn't contain a path, search the include path
    if ($input !~ /[\/\\]/) {
      $input = $self->search_include_path($input);
      if (!defined $input) {
        $input = $self->get_global_cfg();
      }
    }

    ## Read and parse the global project file
    $self->{'reading_global'} = 1;
    $status = $self->parse_file($input);
    $self->{'reading_global'} = 0;
  }

  return $status;
}


sub process_assignment {
  my($self)   = shift;
  my($name)   = shift;
  my($value)  = shift;
  my($assign) = shift;

  ## Support the '*' mechanism as in the project name, to allow
  ## the user to correctly depend on another project within the same
  ## directory.
  if (defined $value) {
    if ($name eq 'after' && index($value, '*') >= 0) {
      $value = $self->fill_type_name($value,
                                     $self->get_default_project_name());
    }
    if (!$self->{'dollar_special'} && index($value, '$$') >= 0) {
      $value =~ s/\$\$/\$/g;
    }
    if (defined $self->{'valid_names'}->{$name} &&
        ($self->{'valid_names'}->{$name} & 0x04) == 0 &&
        index($value, '<%') >= 0) {
      $value = $self->replace_parameters($value, $self->{'command_subs'});
    }
  }
  $self->SUPER::process_assignment($name, $value, $assign);

  ## Support keyword mapping here only at the project level scope. The
  ## scoped keyword mapping is done through the parse_scoped_assignment()
  ## method.
  if (!defined $assign || $assign == $self->get_assignment_hash()) {
    my($mapped) = $self->{'valid_names'}->{$name};
    if (defined $mapped && UNIVERSAL::isa($mapped, 'ARRAY')) {
      $self->parse_scoped_assignment($$mapped[0], '=',
                                     $$mapped[1], $value,
                                     $self->{'generated_exts'}->{$$mapped[0]});
    }
  }
}


sub get_assignment_for_modification {
  my($self)        = shift;
  my($name)        = shift;
  my($assign)      = shift;
  my($subtraction) = shift;

  ## If we weren't passed an assignment hash, then we need to
  ## look one up that may possibly correctly deal with keyword mappings
  if (!defined $assign) {
    my($mapped) = $self->{'valid_names'}->{$name};

    if (defined $mapped && UNIVERSAL::isa($mapped, 'ARRAY')) {
      $name   = $$mapped[1];
      $assign = $self->{'generated_exts'}->{$$mapped[0]};
    }
  }

  ## Get the assignment value
  my($value) = $self->get_assignment($name, $assign);

  ## If we are involved in a subtraction, we get back a value and
  ## it's a scoped or mapped assignment, then we need to possibly
  ## expand any template variables.  Otherwise, the subtractions
  ## may not work correctly.
  if ($subtraction && defined $value && defined $assign) {
    $value = $self->relative($value, 1);
  }

  return $value;
}


sub begin_project {
  my($self)    = shift;
  my($parents) = shift;
  my($status)  = 1;
  my($error)   = undef;

  ## Deal with the inheritance hierarchy first
  ## Add in the base projects from the command line
  if (!$self->{'reading_global'} &&
      !defined $self->{'reading_parent'}->[0]) {
    my($baseprojs) = $self->get_baseprojs();

    if (defined $parents) {
      foreach my $base (@$baseprojs) {
        my($found) = 0;
        foreach my $parent (@$parents) {
          if ($base eq $parent) {
            $found = 1;
            last;
          }
        }
        if (!$found) {
          push(@$parents, $base);
        }
      }
    }
    else {
      $parents = $baseprojs;
    }
  }

  if (defined $parents) {
    foreach my $parent (@$parents) {
      ## Read in the parent onto ourself
      my($file) = $self->search_include_path(
                           "$parent.$BaseClassExtension");
      if (!defined $file) {
        $file = $self->search_include_path(
                             "$parent.$ProjectCreatorExtension");
      }

      if (defined $file) {
        if (defined $self->{'reading_parent'}->[0]) {
          foreach my $currently (@{$self->{'reading_parent'}}) {
            if ($currently eq $file) {
              $status = 0;
              $error = 'Cyclic inheritance detected: ' . $parent;
            }
          }
        }

        if ($status) {
          if (!defined $self->{'parents_read'}->{$file}) {
            $self->{'parents_read'}->{$file} = 1;

            ## Push the base project file onto the parent stack
            push(@{$self->{'reading_parent'}}, $file);

            ## Collect up some information about the inheritance tree
            my($tree) = $self->{'current_input'};
            if (!defined $self->{'inheritance_tree'}->{$tree}) {
              $self->{'inheritance_tree'}->{$tree} = {};
            }
            my($hash) = $self->{'inheritance_tree'}->{$tree};
            foreach my $p (@{$self->{'reading_parent'}}) {
              if (!defined $$hash{$p}) {
                $$hash{$p} = {};
              }
              $hash = $$hash{$p};
            }

            ## Begin reading the parent
            $status = $self->parse_file($file);

            ## Take the base project file off of the parent stack
            pop(@{$self->{'reading_parent'}});

            if (!$status) {
              $error = "Invalid parent: $parent";
            }
          }
          else {
            ## The base project has already been read.  So, if
            ## we are reading the original project (not a parent base
            ## project), then the current base project is redundant.
            if (!defined $self->{'reading_parent'}->[0]) {
              $file =~ s/\.[^\.]+$//;
              $self->information('Inheriting from \'' .
                                 $self->mpc_basename($file) .
                                 '\' in ' . $self->{'current_input'} .
                                 ' is redundant at line ' .
                                 $self->get_line_number() . '.');
            }
          }
        }
      }
      else {
        $status = 0;
        $error = "Unable to locate parent: $parent";
      }
    }
  }

  ## Copy each value from global_assign into assign
  if (!$self->{'reading_global'}) {
    foreach my $key (keys %{$self->{'global_assign'}}) {
      if (!defined $self->{'assign'}->{$key}) {
        $self->{'assign'}->{$key} = $self->{'global_assign'}->{$key};
      }
    }
  }

  return $status, $error;
}


sub get_process_project_type {
  my($self)    = shift;
  my($types)   = shift;
  my($type)    = '';
  my($defcomp) = $self->get_default_component_name();

  foreach my $t (split(/\s*,\s*/, $types)) {
    my($not) = ($t =~ s/^!\s*//);
    if ($not) {
      if ($t eq $self->{'pctype'}) {
        $type = '';
        last;
      }
      else {
        $type = $self->{'pctype'};
      }
    }
    elsif ($t eq $self->{'pctype'} || $t eq $defcomp) {
      $type = $t;
      last;
    }
  }

  return $type;
}


sub parse_line {
  my($self)   = shift;
  my($ih)     = shift;
  my($line)   = shift;
  my($status,
     $errorString,
     @values) = $self->parse_known($line);

  ## parse_known() passes back an array of values
  ## that make up the contents of the line parsed.
  ## The array can have 0 to 3 items.  The first,
  ## if defined, is always an identifier of some
  ## sort.

  if ($status && defined $values[0]) {
    if ($values[0] eq $self->{'grammar_type'}) {
      my($name)      = $values[1];
      my($typecheck) = $self->{'type_check'};
      if (defined $name && $name eq '}') {
        ## Project Ending
        my($rp) = $self->{'reading_parent'};
        if (!defined $$rp[0] && !$self->{'reading_global'}) {
          ## Fill in all the default values
          $self->generate_defaults();

          ## Perform any additions, subtractions
          ## or overrides for the project values.
          my($addproj) = $self->get_addproj();
          foreach my $ap (keys %$addproj) {
            if (defined $self->{'valid_names'}->{$ap}) {
              my($val) = $$addproj{$ap};
              if ($$val[0] > 0) {
                $self->process_assignment_add($ap, $$val[1]);
              }
              elsif ($$val[0] < 0) {
                $self->process_assignment_sub($ap, $$val[1]);
              }
              else {
                $self->process_assignment($ap, $$val[1]);
              }
            }
            else {
              $errorString = 'Invalid ' .
                             "assignment modification name: $ap";
              $status = 0;
            }
          }

          if ($status) {
            ## Generate default target names after all source files are added
            ## and after we've added in all of the options from the
            ## command line.  If the user set exename on the command line
            ## and no "main" is found, sharedname will be set too and
            ## most templates do not handle that well.
            $self->generate_default_target_names();

            ## End of project; Write out the file.
            ($status, $errorString) = $self->write_project();

            ## write_project() can return 0 for error, 1 for project
            ## was written and 2 for project was skipped
            if ($status == 1) {
              ## Save the library name and location
              foreach my $name ('sharedname', 'staticname') {
                my($val) = $self->get_assignment($name);
                if (defined $val) {
                  my($cwd)   = $self->getcwd();
                  my($start) = $self->getstartdir();
                  my($amount) = 0;
                  if ($cwd eq $start) {
                    $amount = length($start);
                  }
                  elsif (index($cwd, $start) == 0) {
                    $amount = length($start) + 1;
                  }
                  $self->{'lib_locations'}->{$val} =
                      substr($cwd, $amount);
                  last;
                }
              }

              ## Check for unused verbatim markers
              foreach my $key (keys %{$self->{'verbatim'}}) {
                if (defined $self->{'verbatim_accessed'}->{$key}) {
                  foreach my $ikey (keys %{$self->{'verbatim'}->{$key}}) {
                    if (!defined $self->{'verbatim_accessed'}->{$key}->{$ikey}) {
                      $self->warning("Marker $ikey does not exist.");
                    }
                  }
                }
              }
            }

            ## Reset all of the project specific data
            foreach my $key (keys %{$self->{'valid_components'}}) {
              delete $self->{$key};
              $self->{'defaulted'}->{$key} = 0;
            }
            if (defined $self->{'addtemp_state'}) {
              $self->restore_state($self->{'addtemp_state'}, 'addtemp');
              $self->{'addtemp_state'} = undef;
            }
            $self->{'assign'}                = {};
            $self->{'verbatim'}              = {};
            $self->{'verbatim_accessed'}     = {$self->{'pctype'} => {}};
            $self->{'special_supplied'}      = {};
            $self->{'flag_overrides'}        = {};
            $self->{'parents_read'}          = {};
            $self->{'inheritance_tree'}      = {};
            $self->{'remove_files'}          = {};
            $self->{'custom_special_output'} = {};
            $self->{'custom_special_depend'} = {};
            $self->{'expanded'}              = {};
            $self->reset_generating_types();
          }
        }
        $self->{$typecheck} = 0;
      }
      else {
        ## Project Beginning
        ($status, $errorString) = $self->begin_project($values[2]);

        ## Set up the default project name
        if ($status) {
          if (defined $name) {
            if ($name =~ /[\/\\]/) {
              $status = 0;
              $errorString = 'Projects can not have a slash ' .
                             'or a back slash in the name';
            }
            else {
              ## We should only set the project name if we are not
              ## reading in a parent project.
              if (!defined $self->{'reading_parent'}->[0]) {
                $name =~ s/^\(\s*//;
                $name =~ s/\s*\)$//;
                $name = $self->transform_file_name($name);

                ## Replace any *'s with the default name
                if (index($name, '*') >= 0) {
                  $name = $self->fill_type_name(
                                    $name,
                                    $self->get_default_project_name());
                }

                $self->set_project_name($name);
              }
              else {
                $self->warning("Ignoring project name in a base project.");
              }
            }
          }
        }

        if ($status) {
          ## Signify that we have a valid project
          $self->{$typecheck} = 1;
        }
      }
    }
    elsif ($values[0] eq '=') {
      my($name)  = $values[1];
      my($value) = $values[2];
      if (defined $self->{'valid_names'}->{$name}) {
        $self->process_assignment($name, $value);
      }
      else {
        $errorString = "Invalid assignment name: $name";
        $status = 0;
      }
    }
    elsif ($values[0] eq '+=') {
      my($name)  = $values[1];
      my($value) = $values[2];
      if (defined $self->{'valid_names'}->{$name}) {
        $self->process_assignment_add($name, $value);
      }
      else {
        $errorString = "Invalid addition name: $name";
        $status = 0;
      }
    }
    elsif ($values[0] eq '-=') {
      my($name)  = $values[1];
      my($value) = $values[2];
      if (defined $self->{'valid_names'}->{$name}) {
        $self->process_assignment_sub($name, $value);
      }
      else {
        $errorString = "Invalid subtraction name: $name";
        $status = 0;
      }
    }
    elsif ($values[0] eq 'component') {
      my($comp) = $values[1];
      my($name) = $values[2];
      if (defined $name) {
        $name =~ s/^\(\s*//;
        $name =~ s/\s*\)$//;
      }
      else {
        $name = $self->get_default_component_name();
      }

      my($vc) = $self->{'valid_components'};
      if (defined $$vc{$comp}) {
        ($status, $errorString) = $self->parse_components($ih, $comp, $name);
      }
      else {
        if ($comp eq 'verbatim') {
          my($type, $loc) = split(/\s*,\s*/, $name);
          ($status, $errorString) = $self->parse_verbatim($ih, $type, $loc);
        }
        elsif ($comp eq 'specific') {
          my($type) = $self->get_process_project_type($name);
          if ($type eq $self->{'pctype'} ||
              $type eq $self->get_default_component_name()) {
            ($status, $errorString) = $self->parse_scope(
                                        $ih, $comp, $type,
                                        $self->{'valid_names'},
                                        $self->get_assignment_hash(),
                                        {});
          }
          else {
            ## We still need to parse the scope, but we will be
            ## throwing away whatever is processed.  However, it
            ## could still be invalid code that will cause an error.
            ($status, $errorString) = $self->parse_scope(
                                        $ih, $comp, undef,
                                        $self->{'valid_names'},
                                        undef,
                                        $self->get_assignment_hash());
          }
        }
        elsif ($comp eq 'define_custom') {
          ($status, $errorString) = $self->parse_define_custom($ih, $name);
        }
        elsif ($comp eq 'expand') {
          ($status, $errorString) = $self->parse_scope($ih, $comp, $name);
        }
        else {
          $errorString = "Invalid component name: $comp";
          $status = 0;
        }
      }
    }
    elsif ($values[0] eq 'feature') {
      $self->{'feature_defined'} = 1;
      $self->process_feature($ih, $values[1], $values[2]);
      if ($self->{'feature_defined'}) {
        $errorString = "Did not find the end of the feature";
        $status = 0;
      }
    }
    else {
      $errorString = "Unrecognized line: $line";
      $status = 0;
    }
  }
  elsif ($status == -1) {
    $status = 0;
  }

  return $status, $errorString;
}


sub parse_scoped_assignment {
  my($self)   = shift;
  my($tag)    = shift;
  my($type)   = shift;
  my($name)   = shift;
  my($value)  = shift;
  my($flags)  = shift;
  my($over)   = {};
  my($status) = 0;

  ## Map the assignment name on a scoped assignment
  my($mapped) = $self->{'valid_names'}->{$name};
  if (defined $mapped && UNIVERSAL::isa($mapped, 'ARRAY')) {
    $name = $$mapped[1];
  }

  if (defined $self->{'matching_assignments'}->{$tag}) {
    foreach my $possible (@{$self->{'matching_assignments'}->{$tag}}) {
      if ($possible eq $name) {
        $status = 1;
        last;
      }
    }
  }

  if ($status) {
    if (defined $self->{'flag_overrides'}->{$tag}) {
      $over = $self->{'flag_overrides'}->{$tag};
    }
    else {
      $self->{'flag_overrides'}->{$tag} = $over;
    }

    if ($type eq '=') {
      $self->process_assignment($name, $value, $flags);
    }
    elsif ($type eq '+=') {
      ## If there is no value in $$flags, then we need to get
      ## the outer scope value and put it in there.
      if (!defined $self->get_assignment($name, $flags)) {
        my($outer) = $self->get_assignment($name);
        $self->process_assignment($name, $outer, $flags);
      }
      $self->process_assignment_add($name, $value, $flags);
    }
    elsif ($type eq '-=') {
      ## If there is no value in $$flags, then we need to get
      ## the outer scope value and put it in there.
      if (!defined $self->get_assignment($name, $flags)) {
        my($outer) = $self->get_assignment($name);
        $self->process_assignment($name, $outer, $flags);
      }
      $self->process_assignment_sub($name, $value, $flags);
    }
  }
  return $status;
}


sub handle_unknown_assignment {
  my($self)   = shift;
  my($type)   = shift;
  my(@values) = @_;

  ## Unknown assignments within a 'specific' section are handled as
  ## template value modifications.  These are handled exactly as the
  ## -value_template option in Options.pm.

  ## If $type is not defined, then we are skipping this section
  if (defined $type) {
    ## Save the addtemp state if we haven't done so before
    if (!defined $self->{'addtemp_state'}) {
      my(%state) = $self->save_state('addtemp');
      $self->{'addtemp_state'} = \%state;
    }

    ## Now modify the addtemp values
    $self->information("'$values[1]' was used as a template modifier.");
    if ($values[0] eq '+=') {
      $values[0] = 1;
    }
    elsif ($values[0] eq '-=') {
      $values[0] = -1;
    }
    else {
      $values[0] = 0;
    }

    if (!defined $self->get_addtemp()->{$values[1]}) {
      $self->get_addtemp()->{$values[1]} = [];
    }
    push(@{$self->get_addtemp()->{$values[1]}}, [$values[0], $values[2]]);
  }

  return 1, undef;
}


sub handle_scoped_unknown {
  my($self)  = shift;
  my($fh)    = shift;
  my($type)  = shift;
  my($flags) = shift;
  my($line)  = shift;

  if (defined $type) {
    if ($type eq $self->get_default_component_name()) {
      return 0, 'Can not set expansion in this context';
    }
    else {
      if (!defined $self->{'expanded'}->{$type}) {
        my($ok) = 1;
        while($line =~ /\$(\w+)/) {
          my($name) = $1;
          my($val)  = '';
          if ($name eq 'PWD') {
            $val = $self->getcwd();
          }
          elsif (defined $ENV{$name}) {
            $val = $ENV{$name};
          }
          else {
            $ok = undef;
            last;
          }
          $line =~ s/\$\w+/$val/;
        }
        $self->{'expanded'}->{$type} = $line if ($ok);
      }
      return 1, undef;
    }
  }

  ## If the type is not defined, then this is something other than an
  ## assignment in a 'specific' section and should be flagged as an error
  return 0, "Unrecognized line: $line";
}

sub process_component_line {
  my($self)    = shift;
  my($tag)     = shift;
  my($line)    = shift;
  my($flags)   = shift;
  my($grname)  = shift;
  my($current) = shift;
  my($excarr)  = shift;
  my($comps)   = shift;
  my($count)   = shift;
  my($status)  = 1;
  my($error)   = undef;
  my(%exclude) = ();
  my(@values)  = ();

  ## If this returns true, then we've found an assignment
  if ($self->parse_assignment($line, \@values)) {
    $status = $self->parse_scoped_assignment($tag, @values, $flags);
    if (!$status) {
      $error = 'Unknown keyword: ' . $values[1];
    }
  }
  else {
    ## If we successfully remove a '!' from the front, then
    ## the file(s) listed are to be excluded
    my($rem) = ($line =~ s/^\^\s*//);
    my($exc) = $rem || ($line =~ s/^!\s*//);

    ## Convert any $(...) in this line before we process any
    ## wild card characters.  If we do not, scoped assignments will
    ## not work nor will we get the correct wild carded file list.
    ## We also need to make sure that any back slashes are converted to
    ## slashes to ensure that later flag_overrides checks will happen
    ## correctly.
    $line = $self->relative($line);
    $line =~ s/\\/\//g if ($self->{'convert_slashes'});

    ## Now look for specially listed files
    if ((index($line, '>>') >= 0 || index($line, '<<') >= 0) &&
        $line =~ /(.*)\s+(>>|<<)\s+(.*)/) {
      $line    = $1;
      my($oop) = $2;
      my($iop) = ($oop eq '>>' ? '<<' : '>>');
      my($out) = ($oop eq '>>' ? $3 : undef);
      my($dep) = ($oop eq '<<' ? $3 : undef);

      $line =~ s/\s+$//;
      if (index($line, $iop) >= 0 && $line =~ /(.*)\s+$iop\s+(.*)/) {
        $line = $1;
        $out  = ($iop eq '>>' ? $2 : $out);
        $dep  = ($iop eq '<<' ? $2 : $dep);
        $line =~ s/\s+$//;
      }

      ## Check for both possible error conditions
      if (index($line, $oop) >= 0) {
        $status = 0;
        $error  = "Duplicate $oop used";
      }
      elsif (index($line, $iop) >= 0) {
        $status = 0;
        $error  = "Duplicate $iop used";
      }

      ## Keys used internally to MPC need to be in forward slash format.
      my($key) = $line;
      $key =~ s/\\/\//g if ($self->{'convert_slashes'});
      if (defined $out) {
        if (!defined $self->{'custom_special_output'}->{$tag}) {
          $self->{'custom_special_output'}->{$tag} = {};
        }
        $self->{'custom_special_output'}->{$tag}->{$key} = $self->create_array($out);
      }
      if (defined $dep) {
        $self->{'custom_special_depend'}->{$key} = $self->create_array($dep);
      }
    }

    ## Set up the files array.  If the line contains a wild card
    ## character use CORE::glob() to get the files specified.
    my(@files) = ();
    if ($line =~ /^"([^"]+)"$/) {
      push(@files, $1);
    }
    elsif ($line =~ /[\?\*\[\]]/) {
      @files = $self->mpc_glob($line);
    }
    else {
      push(@files, $line);
    }

    ## If we want to remove these files at the end too, then
    ## add them to our remove_files hash array.
    if ($rem) {
      if (!defined $self->{'remove_files'}->{$tag}) {
        $self->{'remove_files'}->{$tag} = {};
      }
      foreach my $file (@files) {
        $self->{'remove_files'}->{$tag}->{$file} = 1;
      }
    }

    ## If we're excluding these files, then put them in the hash
    if ($exc) {
      $$grname = $current;
      @exclude{@files} = (@files);
      @$excarr = @files;
    }
    else {
      ## Set the flag overrides for each file
      my($over) = $self->{'flag_overrides'}->{$tag};
      if (defined $over) {
        foreach my $file (@files) {
          $$over{$file} = $flags;
        }
      }

      foreach my $file (@files) {
        ## Add the file if we're not excluding it
        if (!defined $exclude{$file}) {
          push(@{$$comps{$current}}, $file);
        }

        ## The user listed a file explicitly, whether we
        ## excluded it or not.
        ++$$count;
      }
    }
  }

  return $status, $error;
}


sub parse_conditional {
  my($self)    = shift;
  my($fh)      = shift;
  my($types)   = shift;
  my($tag)     = shift;
  my($flags)   = shift;
  my($grname)  = shift;
  my($current) = shift;
  my($exclude) = shift;
  my($comps)   = shift;
  my($count)   = shift;
  my($status)  = 1;
  my($error)   = undef;
  my($add)     = 0;
  my($type)    = $self->get_process_project_type($types);

  if ($type eq $self->{'pctype'}) {
    $add = 1;
  }

  while(<$fh>) {
    my($line) = $self->preprocess_line($fh, $_);

    if ($line eq '') {
    }
    elsif ($line =~ /^}\s*else\s*{$/) {
      $add ^= 1;
    }
    elsif ($line =~ /^}$/) {
      last;
    }
    elsif ($add) {
      ($status, $error) = $self->process_component_line(
                                              $tag, $line, $flags,
                                              $grname, $current,
                                              $exclude, $comps, $count);
      if (!$status) {
        last;
      }
    }
  }

  return $status, $error;
}

sub parse_components {
  my($self)    = shift;
  my($fh)      = shift;
  my($tag)     = shift;
  my($name)    = shift;
  my($current) = $defgroup;
  my($status)  = 1;
  my($error)   = undef;
  my($names)   = {};
  my($comps)   = {};
  my($set)     = undef;
  my(%flags)   = ();
  my(@exclude) = ();
  my($custom)  = defined $self->{'generated_exts'}->{$tag};
  my($grtag)   = $grouped_key . $tag;
  my($grname)  = undef;

  if ($custom) {
    ## For the custom scoped assignments, we want to put a copy of
    ## the original custom defined values in our flags associative array.
    foreach my $key (keys %custom) {
      if (defined $self->{'generated_exts'}->{$tag}->{$key}) {
        $flags{$key} = $self->{'generated_exts'}->{$tag}->{$key};
      }
    }
  }

  if (defined $self->{$tag}) {
    $names = $self->{$tag};
  }
  else {
    $self->{$tag} = $names;
  }
  if (defined $$names{$name}) {
    $comps = $$names{$name};
  }
  else {
    $$names{$name} = $comps;
  }
  if (!defined $$comps{$current}) {
    $$comps{$current} = [];
  }

  my($count) = 0;
  if (defined $specialComponents{$tag}) {
    $self->{'special_supplied'}->{$tag} = 1;
  }

  while(<$fh>) {
    my($line) = $self->preprocess_line($fh, $_);

    if ($line eq '') {
    }
    elsif ($line =~ /^(\w+)\s*{$/) {
      if (!$set) {
        $current = $1;
        $set = 1;
        if (!defined $$comps{$current}) {
          $$comps{$current} = [];
        }
      }
      else {
        $status = 0;
        $error  = 'Can not nest groups';
        last;
      }
    }
    elsif ($line =~ /^conditional\s*(\(([^\)]+)\))\s*{$/) {
      ($status, $error) = $self->parse_conditional(
                                         $fh, $2, $tag, \%flags, \$grname,
                                         $current, \@exclude, $comps,
                                         \$count);
      if (!$status) {
        last;
      }
    }
    elsif ($line =~ /^}$/) {
      if (!defined $$comps{$current}->[0] && !defined $exclude[0]) {
        ## The default components name was never used
        ## so we remove it from the components
        delete $$comps{$current};
      }
      else {
        ## It was used, so we need to add that name to
        ## the set of group names unless it's already been added.
        $self->process_assignment_add($grtag, $current);
      }
      if ($set) {
        $current = $defgroup;
        $set = undef;
      }
      else {
        ## We are at the end of a component.  If the only group
        ## we added was the default group, then we need to remove
        ## the group setting altogether.
        my($groups) = $self->get_assignment($grtag);
        if (defined $groups) {
          my($grarray) = $self->create_array($groups);
          if (scalar(@$grarray) == 1 && $$grarray[0] eq $defgroup) {
            $self->process_assignment($grtag, undef);
          }
        }

        ## This is not an error,
        ## this is the end of the components
        last;
      }
    }
    else {
      ($status, $error) = $self->process_component_line($tag, $line, \%flags,
                                                        \$grname, $current,
                                                        \@exclude, $comps,
                                                        \$count);
      if (!$status) {
        last;
      }
    }
  }

  ## If we didn't encounter an error, didn't have any files explicitly
  ## listed and we attempted to exclude files, then we need to find the
  ## set of files that don't match the excluded files and add them.
  if ($status && defined $exclude[0] && defined $grname) {
    my($alldir)  = $self->get_assignment('recurse') || $flags{'recurse'};
    my(%checked) = ();
    my(@files)   = ();
    foreach my $exc (@exclude) {
      my($dname) = $self->mpc_dirname($exc);
      if (!defined $checked{$dname}) {
        $checked{$dname} = 1;
        push(@files, $self->generate_default_file_list($dname,
                                                       \@exclude,
                                                       undef, $alldir));
      }
    }

    $self->sift_files(\@files,
                      $self->{'valid_components'}->{$tag},
                      $self->get_assignment('pch_header'),
                      $self->get_assignment('pch_source'),
                      $tag,
                      $$comps{$grname});
  }

  return $status, $error;
}


sub parse_verbatim {
  my($self) = shift;
  my($fh)   = shift;
  my($type) = shift;
  my($loc)  = shift;

  if (!defined $loc) {
    return 0, 'You must provide a location parameter to verbatim';
  }

  ## All types are lower case
  $type = lc($type);

  if (!defined $self->{'verbatim'}->{$type}) {
    $self->{'verbatim'}->{$type} = {};
  }
  $self->{'verbatim'}->{$type}->{$loc} = [];
  my($array) = $self->{'verbatim'}->{$type}->{$loc};

  while(<$fh>) {
    my($line) = $self->preprocess_line($fh, $_);

    if ($line =~ /^}$/) {
      ## This is not an error,
      ## this is the end of the verbatim
      last;
    }
    else {
      push(@$array, $line);
    }
  }

  return 1, undef;
}


sub process_feature {
  my($self)    = shift;
  my($fh)      = shift;
  my($names)   = shift;
  my($parents) = shift;
  my($status)  = 1;
  my($error)   = undef;

  my($requires) = '';
  my($avoids)   = '';
  foreach my $name (@$names) {
    if ($name =~ /^!\s*(.*)$/) {
      if ($avoids ne '') {
        $avoids .= ' ';
      }
      $avoids .= $1;
    }
    else {
      if ($requires ne '') {
        $requires .= ' ';
      }
      $requires .= $name;
    }
  }

  if ($self->check_features($requires, $avoids)) {
    ## The required features are enabled, so we say that
    ## a project has been defined and we allow the parser to
    ## find the data held within the feature.
    ($status, $error) = $self->begin_project($parents);
    if ($status) {
      $self->{'feature_defined'} = 0;
      $self->{$self->{'type_check'}} = 1;
    }
  }
  else {
    ## Otherwise, we read in all the lines until we find the
    ## closing brace for the feature and it appears to the parser
    ## that nothing was defined.
    my($curly) = 1;
    while(<$fh>) {
      my($line) = $self->preprocess_line($fh, $_);

      ## This is a very simplistic way of finding the end of
      ## the feature definition.  It will work as long as no spurious
      ## open curly braces are counted.
      if ($line =~ /{$/) {
        ++$curly;
      }
      if ($line =~ /^}/) {
        --$curly;
      }
      if ($curly == 0) {
        $self->{'feature_defined'} = 0;
        last;
      }
    }
  }

  return $status, $error;
}


sub process_array_assignment {
  my($self)  = shift;
  my($aref)  = shift;
  my($type)  = shift;
  my($array) = shift;

  if (!defined $$aref || $type eq '=') {
    if ($type ne '-=') {
      $$aref = $array;
    }
  }
  else {
    if ($type eq '+=') {
      push(@{$$aref}, @$array);
    }
    elsif ($type eq '-=') {
      my($count) = scalar(@{$$aref});
      for(my $i = 0; $i < $count; ++$i) {
        foreach my $val (@$array) {
          if ($$aref->[$i] eq $val) {
            splice(@{$$aref}, $i, 1);
            --$i;
            --$count;
            last;
          }
        }
      }
    }
  }
}


sub parse_define_custom {
  my($self)        = shift;
  my($fh)          = shift;
  my($tag)         = shift;
  my($status)      = 0;
  my($errorString) = "Unable to process $tag";

  ## Make the tag something _files
  $tag = lc($tag) . '_files';

  if ($tag eq $generic_key) {
    $errorString = "$tag is reserved";
  }
  elsif (defined $self->{'valid_components'}->{$tag}) {
    $errorString = "$tag has already been defined";
  }
  else {
    ## Update the custom_types assignment
    $self->process_assignment_add('custom_types', $tag);

    if (!defined $self->{'matching_assignments'}->{$tag}) {
      my(@keys) = keys %custom;
      push(@keys, @default_matching_assignments);
      $self->{'matching_assignments'}->{$tag} = \@keys;
    }

    ## Set up the 'optional' hash table
    $self->{'generated_exts'}->{$tag}->{'optional'} = {};

    my($optname) = undef;
    my($inscope) = 0;
    while(<$fh>) {
      my($line) = $self->preprocess_line($fh, $_);

      if ($line eq '') {
      }
      elsif ($line =~ /optional\s*\(([^\)]+)\)\s*{/) {
        $optname = $1;
        $optname =~ s/^\s+//;
        $optname =~ s/\s+$//;
        if (defined $customDefined{$optname} &&
            ($customDefined{$optname} & 0x08) != 0) {
          ++$inscope;
          if ($inscope != 1) {
            $status = 0;
            $errorString = 'Can not nest \'optional\' sections';
            last;
          }
        }
        else {
          $status = 0;
          $errorString = "Invalid optional name: $optname";
          last;
        }
      }
      elsif ($inscope) {
        if ($line =~ /^}$/) {
          $optname = undef;
          --$inscope;
        }
        else {
          if ($line =~ /(\w+)\s*\(([^\)]+)\)\s*\+=\s*(.*)/) {
            my($name) = lc($1);
            my($opt)  = $2;
            my(@val)  = split(/\s*,\s*/, $3);

            ## Fix $opt spacing
            $opt =~ s/(\&\&|\|\|)/ $1 /g;
            $opt =~ s/!\s+/!/g;

            if (!defined $self->{'generated_exts'}->{$tag}->
                                {'optional'}->{$optname}) {
              $self->{'generated_exts'}->{$tag}->
                     {'optional'}->{$optname} = {};
            }
            if (!defined $self->{'generated_exts'}->{$tag}->
                                {'optional'}->{$optname}->{$name}) {
              $self->{'generated_exts'}->{$tag}->
                     {'optional'}->{$optname}->{$name} = {};
            }
            if (!defined $self->{'generated_exts'}->{$tag}->
                                {'optional'}->{$optname}->{$name}->{$opt}) {
              $self->{'generated_exts'}->{$tag}->
                     {'optional'}->{$optname}->{$name}->{$opt} = [];
            }
            push(@{$self->{'generated_exts'}->{$tag}->{'optional'}->
                    {$optname}->{$name}->{$opt}}, @val);
          }
        }
      }
      elsif ($line =~ /^}$/) {
        $status = 1;
        $errorString = undef;

        ## Propagate the custom defined values into the mapped values
        foreach my $key (keys %{$self->{'valid_names'}}) {
          if (UNIVERSAL::isa($self->{'valid_names'}->{$key}, 'ARRAY')) {
            my($value) = $self->{'generated_exts'}->{$tag}->{
                                   $self->{'valid_names'}->{$key}->[1]};
            if (defined $value) {
              ## Bypass the process_assignment() defined in this class
              ## to avoid unwanted keyword mapping.
              $self->SUPER::process_assignment($key, $value);
            }
          }
        }

        ## Set some defaults (if they haven't already been set)
        if (!defined $self->{'generated_exts'}->{$tag}->{'pre_filename'}) {
          $self->{'generated_exts'}->{$tag}->{'pre_filename'} = [ '' ];
        }
        if (!defined $self->{'generated_exts'}->{$tag}->{'pre_extension'}) {
          $self->{'generated_exts'}->{$tag}->{'pre_extension'} = [ '' ];
        }
        if (!defined $self->{'generated_exts'}->{$tag}->{'automatic'}) {
          $self->{'generated_exts'}->{$tag}->{'automatic'} = 1;
        }
        if (!defined $self->{'valid_components'}->{$tag}) {
          $self->{'valid_components'}->{$tag} = [];
        }
        last;
      }
      else {
        my(@values) = ();
        ## If this returns true, then we've found an assignment
        if ($self->parse_assignment($line, \@values)) {
          my($type)  = $values[0];
          my($name)  = $values[1];
          my($value) = $values[2];
          if (defined $customDefined{$name}) {
            if (($customDefined{$name} & 0x01) != 0) {
              $value = $self->escape_regex_special($value);
              my(@array) = split(/\s*,\s*/, $value);
              $self->process_array_assignment(
                        \$self->{'valid_components'}->{$tag}, $type, \@array);
            }
            else {
              if (!defined $self->{'generated_exts'}->{$tag}) {
                $self->{'generated_exts'}->{$tag} = {};
              }
              ## Try to convert the value into a relative path
              $value = $self->relative($value);

              if (($customDefined{$name} & 0x04) != 0) {
                if ($type eq '=') {
                  $self->process_assignment(
                                     $name, $value,
                                     $self->{'generated_exts'}->{$tag});
                }
                elsif ($type eq '+=') {
                  $self->process_assignment_add(
                                     $name, $value,
                                     $self->{'generated_exts'}->{$tag});
                }
                elsif ($type eq '-=') {
                  $self->process_assignment_sub(
                                     $name, $value,
                                     $self->{'generated_exts'}->{$tag});
                }
              }
              else {
                if (($customDefined{$name} & 0x02) != 0) {
                  ## Transform the name from something outputext to
                  ## something files.  We expect this to match the
                  ## names of valid_assignments.
                  $name =~ s/outputext/files/g;
                }

                ## Get it ready for regular expressions
                $value = $self->escape_regex_special($value);

                ## Process the array assignment
                my(@array) = split(/\s*,\s*/, $value);
                $self->process_array_assignment(
                            \$self->{'generated_exts'}->{$tag}->{$name},
                            $type, \@array);
              }
            }
          }
          else {
            $status = 0;
            $errorString = "Invalid assignment name: $name";
            last;
          }
        }
        elsif ($line =~ /^(\w+)\s+(\w+)(\s*=\s*(\w+)?)?/) {
          ## Check for keyword mapping here
          my($keyword) = $1;
          my($newkey)  = $2;
          my($mapkey)  = $4;
          if ($keyword eq 'keyword') {
            if (defined $self->{'valid_names'}->{$newkey}) {
              $status = 0;
              $errorString = "Cannot map $newkey onto an " .
                             "existing keyword";
              last;
            }
            elsif (!defined $mapkey) {
              $self->{'valid_names'}->{$newkey} = 1;
            }
            elsif ($newkey ne $mapkey) {
              if (defined $customDefined{$mapkey}) {
                $self->{'valid_names'}->{$newkey} = [ $tag, $mapkey ];
              }
              else {
                $status = 0;
                $errorString = "Cannot map $newkey to an " .
                               "undefined custom keyword: $mapkey";
                last;
              }
            }
            else {
              $status = 0;
              $errorString = "Cannot map $newkey to $mapkey";
              last;
            }
          }
          else {
            $status = 0;
            $errorString = "Unrecognized line: $line";
            last;
          }
        }
        else {
          $status = 0;
          $errorString = "Unrecognized line: $line";
          last;
        }
      }
    }
  }

  return $status, $errorString;
}


sub remove_duplicate_addition {
  my($self)  = shift;
  my($name)  = shift;
  my($value) = shift;
  my($nval)  = shift;

  if (defined $nval) {
    ## If we are modifying the libs, libpaths, macros or includes
    ## assignment with either addition or subtraction, we are going to
    ## perform a little fix on the value to avoid multiple
    ## libraries and to try to insure the correct linking order
    if ($name eq 'macros' || $name eq 'libpaths' ||
        $name eq 'includes' || $name =~ /libs$/ ||
        index($name, $grouped_key) == 0) {
      my($allowed) = '';
      my(%parts)   = ();

      ## Convert the array into keys for a hash table
      @parts{@{$self->create_array($nval)}} = ();

      ## In order to ensure that duplicates are correctly removed, we
      ## need to get the modified assignment value before we attempt to
      ## do so.
      $value = $self->modify_assignment_value($name, $value);
      foreach my $val (@{$self->create_array($value)}) {
        if (!exists $parts{$val}) {
          my($qt) = ($val =~ /\s/ ? '"' : '');
          $allowed .= $qt . $val . $qt . ' ';
        }
      }
      $allowed =~ s/\s+$//;
      return $allowed;
    }
  }

  return $value;
}


sub read_template_input {
  my($self)        = shift;
  my($status)      = 1;
  my($errorString) = undef;
  my($file)        = undef;
  my($tag)         = undef;
  my($ti)          = $self->get_ti_override();
  my($override)    = undef;

  if ($self->exe_target()) {
    if ($self->get_static() == 1) {
      $tag = 'lexe_template_input';
      if (!defined $self->{$tag}) {
        if (defined $$ti{'lib_exe'}) {
          $file = $$ti{'lib_exe'};
          $override = 1;
        }
        else {
          $file = $self->get_lib_exe_template_input_file();
        }
      }
    }
    else {
      $tag = 'dexe_template_input';
      if (!defined $self->{$tag}) {
        if (defined $$ti{'dll_exe'}) {
          $file = $$ti{'dll_exe'};
          $override = 1;
        }
        else {
          $file = $self->get_dll_exe_template_input_file();
        }
      }
    }
  }
  else {
    if ($self->get_static() == 1) {
      $tag = 'lib_template_input';
      if (!defined $self->{$tag}) {
        if (defined $$ti{'lib'}) {
          $file = $$ti{'lib'};
          $override = 1;
        }
        else {
          $file = $self->get_lib_template_input_file();
        }
      }
    }
    else {
      $tag = 'dll_template_input';
      if (!defined $self->{$tag}) {
        if (defined $$ti{'dll'}) {
          $file = $$ti{'dll'};
          $override = 1;
        }
        else {
          $file = $self->get_dll_template_input_file();
        }
      }
    }
  }

  if (defined $file) {
    my($file) = $self->search_include_path("$file.$TemplateInputExtension");
    if (defined $file) {
      $self->{$tag} = new TemplateInputReader($self->get_include_path());
      ($status, $errorString) = $self->{$tag}->cached_file_read($file);
    }
    else {
      if ($override) {
        $status = 0;
        $errorString = 'Unable to locate template input file.';
      }
    }
  }

  if ($status && defined $self->{$tag}) {
    ## Put the features into the template input set
    my($features) = $self->{'feature_parser'}->get_names();
    $self->{$tag}->parse_line(undef, "features = @$features");
  }

  return $status, $errorString;
}


sub already_added {
  my($self)  = shift;
  my($array) = shift;
  my($name)  = shift;

  ## This method expects that the file name will be unix style
  $name =~ s/\\/\//g if ($self->{'convert_slashes'});

  ## Remove the leading ./
  $name =~ s/^\.\///;
  my($dsname) = "./$name";

  foreach my $file (@$array) {
    return 1 if ($file eq $name || $file eq $dsname);
  }

  return 0;
}


sub get_applied_custom_keyword {
  my($self)  = shift;
  my($name)  = shift;
  my($type)  = shift;
  my($file)  = shift;
  my($value) = undef;

  if (defined $self->{'flag_overrides'}->{$type}->{$file}->{$name}) {
    $value = $self->{'flag_overrides'}->{$type}->{$file}->{$name};
  }
  else {
    $value = $self->get_assignment($name,
                                   $self->{'generated_exts'}->{$type});
  }
  return $self->relative($value, 1);
}


sub evaluate_optional_option {
  my($self)  = shift;
  my($opt)   = shift;
  my($value) = shift;

  if ($opt =~ /^!\s*(.*)/) {
    return (!exists $$value{$1} ? 1 : 0);
  }
  else {
    return (exists $$value{$opt} ? 1 : 0);
  }

  return 0;
}


sub process_optional_option {
  my($self)   = shift;
  my($opt)    = shift;
  my($value)  = shift;
  my($status) = undef;
  my(@parts)  = grep(!/^$/, split(/\s+/, $opt));
  my($pcount) = scalar(@parts);

  for(my $i = 0; $i < $pcount; $i++) {
    if ($parts[$i] eq '&&' || $parts[$i] eq '||') {
      if (defined $status) {
        if (defined $parts[$i + 1]) {
          if ($parts[$i] eq '&&') {
            $status &&= $self->evaluate_optional_option($parts[$i + 1],
                                                        $value);
          }
          else {
            $status ||= $self->evaluate_optional_option($parts[$i + 1],
                                                        $value);
          }
        }
        else {
          $self->warning("Expected token in optional after $parts[$i]");
        }
      }
      else {
        $self->warning("Unexpected token in optional: $parts[$i]");
      }
      ++$i;
    }
    else {
      if (!defined $status) {
        $status = $self->evaluate_optional_option($parts[$i], $value);
      }
      else {
        $self->warning("Unexpected token in optional: $parts[$i]");
      }
    }
  }

  return $status;
}


sub add_optional_filename_portion {
  my($self)    = shift;
  my($gentype) = shift;
  my($tag)     = shift;
  my($file)    = shift;
  my($array)   = shift;

  foreach my $name (keys %{$self->{'generated_exts'}->{$gentype}->{'optional'}->{$tag}}) {
    foreach my $opt (keys %{$self->{'generated_exts'}->{$gentype}->{'optional'}->{$tag}->{$name}}) {
      ## Get the name value
      my($value) = $self->get_applied_custom_keyword($name,
                                                     $gentype, $file);

      ## Convert the value into a hash map for easy lookup
      my(%values) = ();
      if (defined $value) {
        @values{split(/\s+/, $value)} = ();
      }

      ## See if the option or options are contained in the value
      if ($self->process_optional_option($opt, \%values)) {
        ## Add the optional portion
        push(@$array, @{$self->{'generated_exts'}->{$gentype}->{'optional'}->{$tag}->{$name}->{$opt}});
      }
    }
  }
}


sub get_pre_keyword_array {
  my($self)    = shift;
  my($keyword) = shift;
  my($gentype) = shift;
  my($tag)     = shift;
  my($file)    = shift;

  ## Get the general pre extension array
  my(@array) = @{$self->{'generated_exts'}->{$gentype}->{$keyword}};

  ## Add the component specific pre extension array
  my(@additional) = ();
  $tag =~ s/files$/$keyword/;
  if (defined $self->{'generated_exts'}->{$gentype}->{$tag}) {
    push(@additional, @{$self->{'generated_exts'}->{$gentype}->{$tag}});
  }

  ## Add in any optional portion to the array
  foreach my $itag ($keyword, $tag) {
    $self->add_optional_filename_portion($gentype, $itag,
                                         $file, \@additional);
  }

  ## If the current array only has the default,
  ## then we need to remove it
  if (defined $additional[0]) {
    if ($#array == 0 && $array[0] eq '') {
      pop(@array);
    }
    push(@array, @additional);
  }

  return @array;
}


sub add_explicit_output {
  my($self)  = shift;
  my($file)  = shift;
  my($type)  = shift;
  my($tag)   = shift;
  my($array) = shift;

  if (defined $self->{'valid_components'}->{$tag} &&
      defined $self->{'custom_special_output'}->{$type} &&
      defined $self->{'custom_special_output'}->{$type}->{$file}) {
    my(@files) = ();
    foreach my $check (@{$self->{'custom_special_output'}->{$type}->{$file}}) {
      foreach my $regext (@{$self->{'valid_components'}->{$tag}}) {
        if ($check =~ /$regext$/) {
          my($add) = 1;
          if ($tag eq 'source_files') {
            foreach my $tregext (@{$self->{'valid_components'}->{'template_files'}}) {
              if ($check =~ /$tregext$/) {
                $add = undef;
                last;
              }
            }
          }
          if ($add) {
            ## If gendir was specified, then we need to account for that
            my($dir) = '';
            if (defined $self->{'flag_overrides'}->{$type} &&
                defined $self->{'flag_overrides'}->{$type}->{$file} &&
                defined $self->{'flag_overrides'}->{$type}->{$file}->{'gendir'}) {
              if ($self->{'flag_overrides'}->{$type}->{$file}->{'gendir'} eq '.') {
                $dir = '';
              }
              else {
                $dir = $self->{'flag_overrides'}->{$type}->{$file}->{'gendir'} . '/';
              }
            }

            push(@files, "$dir$check");
            last;
          }
        }
      }
    }
    if (defined $files[0]) {
      push(@$array, \@files);
    }
  }
}

sub generated_filename_arrays {
  my($self)  = shift;
  my($part)  = shift;
  my($type)  = shift;
  my($tag)   = shift;
  my($file)  = shift;
  my($noext) = shift;
  my(@array) = ();
  my(@pearr) = $self->get_pre_keyword_array('pre_extension',
                                            $type, $tag, $file);
  my(@pfarr) = $self->get_pre_keyword_array('pre_filename',
                                            $type, $tag, $file);
  my(@exts)  = (defined $self->{'generated_exts'}->{$type}->{$tag} ?
                  @{$self->{'generated_exts'}->{$type}->{$tag}} : ());

  if (!defined $exts[0]) {
    my($backtag) = $tag;
    if ($backtag =~ s/files$/outputext/) {
      $self->add_optional_filename_portion($type, $backtag,
                                           $file, \@exts);
    }
  }

  if (!defined $exts[0] && $#pearr == 0 && $#pfarr == 0 &&
      $pearr[0] eq '' && $pfarr[0] eq '') {
    ## If both arrays are defined to be the defaults, then there
    ## is nothing for us to do.
  }
  else {
    my($dir)  = '';
    my($base) = undef;

    ## Correctly deal with pre filename and directories
    if ($part =~ /(.*[\/\\])([^\/\\]+)$/) {
      $dir = $1;
      $base = $2;
    }
    else {
      $base = $part;
    }

    ## If gendir was specified, then we need to account for that
    if (defined $self->{'flag_overrides'}->{$type} &&
        defined $self->{'flag_overrides'}->{$type}->{$file} &&
        defined $self->{'flag_overrides'}->{$type}->{$file}->{'gendir'}) {
      if ($self->{'flag_overrides'}->{$type}->{$file}->{'gendir'} eq '.') {
        $dir = '';
      }
      else {
        $dir = $self->{'flag_overrides'}->{$type}->{$file}->{'gendir'} . '/';
      }
    }

    ## Loop through creating all of the possible file names
    foreach my $pe (@pearr) {
      push(@array, []);
      $pe =~ s/\\\././g;
      foreach my $pf (@pfarr) {
        $pf =~ s/\\\././g;
        if ($noext) {
          push(@{$array[$#array]}, "$dir$pf$base$pe");
        }
        else {
          foreach my $ext (@exts) {
            $ext =~ s/\\\././g;
            push(@{$array[$#array]}, "$dir$pf$base$pe$ext");
          }
        }
      }
    }
  }

  $self->add_explicit_output($file, $type, $tag, \@array);
  return @array;
}


sub generated_filenames {
  my($self)  = shift;
  my(@files) = ();

  foreach my $array ($self->generated_filename_arrays(@_)) {
    push(@files, @$array);
  }

  return @files;
}


sub add_generated_files {
  my($self)    = shift;
  my($gentype) = shift;
  my($tag)     = shift;
  my($group)   = shift;
  my($arr)     = shift;

  ## This method is called by list_default_generated.  It performs the
  ## actual file insertion and grouping.

  my($wanted) = $self->{'valid_components'}->{$gentype}->[0];
  if (defined $wanted) {
    ## Remove the escape sequences for the wanted extension.  It doesn't
    ## matter if the first valid extension is not the same as the actual
    ## input file (ex. input = car.y and first ext is .yy).  The extension
    ## is immediately removed in generated_filename_arrays.
    $wanted =~ s/\\//g;
  }
  else {
    $wanted = '';
  }

  ## Get the generated filenames
  my(@added) = ();
  foreach my $file (@$arr) {
    foreach my $gen ($self->generated_filenames($file, $gentype, $tag,
                                                "$file$wanted", 1)) {
      $self->list_generated_file($gentype, $tag, \@added, $gen, $file);
    }
  }

  if (defined $added[0]) {
    my($names) = $self->{$tag};

    ## Get all files in one list and save the directory
    ## and component group in a hashed array.
    my(@all) = ();
    my(%dircomp) = ();
    foreach my $name (keys %$names) {
      foreach my $key (keys %{$$names{$name}}) {
        push(@all, @{$$names{$name}->{$key}});
        foreach my $file (@{$$names{$name}->{$key}}) {
          $dircomp{$self->mpc_dirname($file)} = $key;
        }
      }
    }

    ## Create a small array of only the files we want to add.
    ## We put them all together so we can keep them in order when
    ## we put them at the front of the main file list.
    my(@oktoadd) = ();
    foreach my $file (@added) {
      if (!$self->already_added(\@all, $file)) {
        push(@oktoadd, $file);
      }
    }

    ## If we have files to add, make sure we add them to a group
    ## that has the same directory location as the files we're adding.
    if (defined $oktoadd[0]) {
      my($key) = (defined $group ? $group :
                          $dircomp{$self->mpc_dirname($oktoadd[0])});
      if (!defined $key) {
        my($check) = $oktoadd[0];
        foreach my $regext (@{$self->{'valid_components'}->{$tag}}) {
          if ($check =~ s/$regext$//) {
            last;
          }
        }
        foreach my $vc (keys %{$self->{'valid_components'}}) {
          if ($vc ne $tag) {
            foreach my $name (keys %{$self->{$vc}}) {
              foreach my $ckey (keys %{$self->{$vc}->{$name}}) {
                if ($ckey ne $defgroup) {
                  foreach my $ofile (@{$self->{$vc}->{$name}->{$ckey}}) {
                    my($file) = $ofile;
                    foreach my $regext (@{$self->{'valid_components'}->{$vc}}) {
                      if ($file =~ s/$regext//) {
                        last;
                      }
                    }
                    if ($file eq $check) {
                      $key = $ckey;
                      last;
                    }
                  }
                }
                last if (defined $key);
              }
            }
            last if (defined $key);
          }
        }
        if (!defined $key) {
          $key = $defgroup;
        }
      }
      foreach my $name (keys %$names) {
        if (!defined $$names{$name}->{$key}) {
          if ($key ne $defgroup &&
              defined $$names{$name}->{$defgroup} &&
              defined $$names{$name}->{$defgroup}->[0]) {
            $self->process_assignment_add($grouped_key . $tag, $defgroup);
          }
          $$names{$name}->{$key} = [];
          $self->process_assignment_add($grouped_key . $tag, $key);
        }
        unshift(@{$$names{$name}->{$key}}, @oktoadd);
      }
    }
  }
}


sub search_for_entry {
  my($self)    = shift;
  my($file)    = shift;
  my($main)    = shift;
  my($preproc) = shift;
  my($name)    = undef;
  my($fh)      = new FileHandle();

  if (open($fh, $file)) {
    my($poundifed) = 0;
    my($commented) = 0;

    while(<$fh>) {
      if (!$commented) {
        ## Remove c++ style comments
        $_ =~ s/\/\/.*//;
      }

      ## Remove one line c style comments
      $_ =~ s/\/\*.*\*\///g;

      if ($commented) {
        if (/\*\//) {
          ## Found the end of a multi-line c style comment
          --$commented;
        }
      }
      else {
        if (/\/\*/) {
          ## Found the beginning of a multi-line c style comment
          ++$commented;
        }
        elsif ($preproc) {
          ## If the current language supports a c preprocessor, we
          ## will perform a minimal check for #if 0
          if (/#\s*if\s+0/) {
            ## Found the beginning of a #if 0
            ++$poundifed;
          }
          elsif ($poundifed) {
            if (/#\s*if/) {
              ## We need to keep track of any other #if directives
              ## to be sure that when we see an #endif we don't
              ## count the wrong one.
              ++$poundifed;
            }
            elsif (/#\s*endif/) {
              ## Found a #endif, so decrement our count
              --$poundifed;
            }
          }
        }
      }

      ## Check for main; Make sure it's not #if 0'ed and not commented out
      if (!$poundifed && !$commented &&
          (/\s+$main\s*\(/ || /^\s*$main\s*\(/)) {
        ## If we've found a main, set the exename to the basename
        ## of the cpp file with the extension removed
        $name = $self->mpc_basename($file);
        $name =~ s/\.[^\.]+$//;
        last;
      }
    }
    close($fh);
  }
  return $name;
}


sub generate_default_target_names {
  my($self) = shift;

  if (!$self->exe_target()) {
    my($sharedname) = $self->get_assignment('sharedname');
    my($staticname) = $self->get_assignment('staticname');
    my($shared_empty) = undef;

    if (defined $sharedname) {
      if ($sharedname eq '') {
        $shared_empty = 1;
        $sharedname = undef;
        $self->process_assignment('sharedname', $sharedname);
      }
      elsif (!defined $staticname) {
        $staticname = $sharedname;
        $self->process_assignment('staticname', $staticname);
      }
    }
    if (defined $staticname && !$shared_empty && !defined $sharedname) {
      $sharedname = $staticname;
      $self->process_assignment('sharedname', $sharedname);
    }

    ## If it's neither an exe or library target, we will search
    ## through the source files for a main()
    if (!$self->lib_target()) {
      my($exename) = undef;
      my(@sources) = $self->get_component_list('source_files', 1);
      my($main)    = $language{$self->get_language()}->[3];
      my($preproc) = $language{$self->get_language()}->[4];

      foreach my $file (@sources) {
        $exename = $self->search_for_entry($file, $main, $preproc);

        ## Set the exename assignment
        if (defined $exename) {
          $self->process_assignment('exename', $exename);
          last;
        }
      }

      ## If we still don't have a project type, then we will
      ## default to a library if there are source or resource files
      if (!defined $exename) {
        if (!defined $sources[0]) {
          @sources = $self->get_component_list('resource_files', 1);
        }
        if (defined $sources[0]) {
          if (!$shared_empty) {
            $self->process_assignment('sharedname',
                                      $self->{'unmodified_project_name'});
          }
          $self->process_assignment('staticname',
                                    $self->{'unmodified_project_name'});
        }
      }
    }
  }

  ## If we are generating only static projects, then we need to
  ## unset the sharedname, so that we can insure that projects of
  ## various types only generate static targets.
  if ($self->get_static() == 1) {
    my($sharedname) = $self->get_assignment('sharedname');
    if (defined $sharedname) {
      $self->process_assignment('sharedname', undef);
    }
  }

  ## Check for the use of an asterisk in the name
  foreach my $key ('exename', 'sharedname', 'staticname') {
    my($value) = $self->get_assignment($key);
    if (defined $value && index($value, '*') >= 0) {
      $value = $self->fill_type_name($value,
                                     $self->{'unmodified_project_name'});
      $self->process_assignment($key, $value);
    }
  }
}


sub generate_default_pch_filenames {
  my($self)    = shift;
  my($files)   = shift;
  my($pchhdef) = (defined $self->get_assignment('pch_header'));
  my($pchcdef) = (defined $self->get_assignment('pch_source'));

  if (!$pchhdef || !$pchcdef) {
    my($pname)     = $self->get_assignment('project_name');
    my($hcount)    = 0;
    my($ccount)    = 0;
    my($hmatching) = undef;
    my($cmatching) = undef;
    foreach my $file (@$files) {
      ## If the file doesn't even contain _pch, then there's no point
      ## in looping through all of the extensions
      if (index($file, '_pch') >= 0) {
        if (!$pchhdef) {
          foreach my $ext (@{$self->{'valid_components'}->{'header_files'}}) {
            if ($file =~ /(.*_pch$ext)$/) {
              $self->process_assignment('pch_header', $1);
              ++$hcount;
              if (index($file, $pname) >= 0) {
                $hmatching = $file;
              }
              last;
            }
          }
        }
        if (!$pchcdef) {
          foreach my $ext (@{$self->{'valid_components'}->{'source_files'}}) {
            if ($file =~ /(.*_pch$ext)$/) {
              $self->process_assignment('pch_source', $1);
              ++$ccount;
              if (index($file, $pname) >= 0) {
                $cmatching = $file;
              }
              last;
            }
          }
        }
      }
    }
    if (!$pchhdef && $hcount > 1 && defined $hmatching) {
      $self->process_assignment('pch_header', $hmatching);
    }
    if (!$pchcdef && $ccount > 1 && defined $cmatching) {
      $self->process_assignment('pch_source', $cmatching);
    }
  }
}


sub fix_pch_filenames {
  my($self) = shift;
  foreach my $type ('pch_header', 'pch_source') {
    my($pch) = $self->get_assignment($type);
    if (defined $pch && $pch eq '') {
      $self->process_assignment($type, undef);
    }
  }
}


sub remove_extra_pch_listings {
  my($self) = shift;
  my(@pchs) = ('pch_header', 'pch_source');
  my(@tags) = ('header_files', 'source_files');

  for(my $j = 0; $j < 2; ++$j) {
    my($pch) = $self->get_assignment($pchs[$j]);

    if (defined $pch) {
      ## If we are converting slashes, then we need to
      ## convert the pch file back to forward slashes
      $pch =~ s/\\/\//g if ($self->{'convert_slashes'});

      ## Find out which files are duplicated
      my($names) = $self->{$tags[$j]};
      foreach my $name (keys %$names) {
        my($comps) = $$names{$name};
        foreach my $key (keys %$comps) {
          my($array) = $$comps{$key};
          my($count) = scalar(@$array);
          for(my $i = 0; $i < $count; ++$i) {
            if ($pch eq $$array[$i]) {
              splice(@$array, $i, 1);
              --$count;
            }
          }
        }
      }
    }
  }
}


sub sift_files {
  my($self)   = shift;
  my($files)  = shift;
  my($exts)   = shift;
  my($pchh)   = shift;
  my($pchc)   = shift;
  my($tag)    = shift;
  my($array)  = shift;
  my($alldir) = shift;
  my(@saved)  = ();
  my($ec)     = $self->{'exclude_components'};
  my($havec)  = (defined $$ec{$tag});

  foreach my $file (@$files) {
    foreach my $ext (@$exts) {
      ## Always exclude the precompiled header and cpp
      if ($file =~ /$ext$/ && (!defined $pchh || $file ne $pchh) &&
                              (!defined $pchc || $file ne $pchc)) {
        if ($havec) {
          my($exclude) = 0;
          foreach my $exc (@{$$ec{$tag}}) {
            if ($file =~ /$exc$/) {
              $exclude = 1;
              last;
            }
          }
          last if ($exclude);
        }
        elsif (!$alldir && $tag eq 'resource_files') {
          ## Save these files for later.  There may
          ## be more than one and we want to try and
          ## find the one that corresponds to this project
          push(@saved, $file);
          last;
        }

        if (!$self->already_added($array, $file)) {
          push(@$array, $file);
        }
        last;
      }
    }
  }

  ## Now deal with the saved files
  if (defined $saved[0]) {
    if (!defined $saved[1]) {
      ## Theres only one rc file, take it
      push(@$array, $saved[0]);
    }
    else {
      my($pjname) = $self->escape_regex_special(
                              $self->transform_file_name(
                                  $self->get_assignment('project_name')));
      ## Use a case insensitive search.
      ## After all, this is a Windows specific file type.
      foreach my $save (@saved) {
        if ($save =~ /$pjname/i) {
          if (!$self->already_added($array, $save)) {
            push(@$array, $save);
          }
        }
      }
    }
  }
}


sub sift_default_file_list {
  my($self)    = shift;
  my($tag)     = shift;
  my($file)    = shift;
  my($built)   = shift;
  my($exts)    = shift;
  my($recurse) = shift;
  my($pchh)    = shift;
  my($pchc)    = shift;
  my($alldir)  = $recurse ||
                 $self->{'flag_overrides'}->{$tag}->{$file}->{'recurse'};
  my(@gen)     = $self->generate_default_file_list($file, [], undef, $alldir);

  $self->sift_files(\@gen, $exts, $pchh, $pchc, $tag, $built, $alldir);
}


sub generate_default_components {
  my($self)    = shift;
  my($files)   = shift;
  my($passed)  = shift;
  my($vc)      = $self->{'valid_components'};
  my(@tags)    = (defined $passed ? $passed : keys %$vc);
  my($pchh)    = $self->get_assignment('pch_header');
  my($pchc)    = $self->get_assignment('pch_source');
  my($recurse) = $self->get_assignment('recurse');

  ## The order of @tags does make a difference in the way that generated
  ## files get added.  And since the tags are user definable, there may be
  ## a problem with that.
  foreach my $tag (@tags) {
    if (!defined $self->{'generated_exts'}->{$tag} ||
        $self->{'generated_exts'}->{$tag}->{'automatic'}) {
      my($exts) = $$vc{$tag};
      if (defined $$exts[0]) {
        if (defined $self->{$tag}) {
          ## If the tag is defined, then process directories
          my($names) = $self->{$tag};
          foreach my $name (keys %$names) {
            my($comps) = $$names{$name};
            foreach my $comp (keys %$comps) {
              my($array) = $$comps{$comp};
              if (defined $passed) {
                $self->sift_files($files, $exts, $pchh, $pchc, $tag, $array);
              }
              else {
                my(@built) = ();
                foreach my $file (@$array) {
                  if (-d $file) {
                    $self->sift_default_file_list($tag, $file, \@built,
                                                  $exts, $recurse, $pchh, $pchc);
                  }
                  else {
                    if (!$self->already_added(\@built, $file)) {
                      push(@built, $file);
                    }
                  }
                }
                $$comps{$comp} = \@built;
              }
            }
          }
        }
        else {
          ## Generate default values for undefined tags
          my($defcomp) = $self->get_default_component_name();
          $self->{$tag} = {};
          my($comps) = {};
          $self->{$tag}->{$defcomp} = $comps;
          $$comps{$defgroup} = [];
          my($array) = $$comps{$defgroup};

          $self->{'defaulted'}->{$tag} = 1;

          if (!defined $specialComponents{$tag}) {
            $self->sift_files($files, $exts, $pchh, $pchc, $tag, $array);
            if (defined $sourceComponents{$tag}) {
              my($grtag) = $grouped_key . $tag;
              foreach my $gentype (keys %{$self->{'generated_exts'}}) {
                ## If we are auto-generating the source_files, then
                ## we need to make sure that any generated source
                ## files that are added are put at the front of the list.
                my($newgroup) = undef;
                my(@input) = ();

                ## If I call keys %{$self->{$gentype}} using perl 5.6.1
                ## it returns nothing.  I have to put it in an
                ## intermediate variable to ensure that I get the keys.
                my($names) = $self->{$gentype};
                foreach my $name (keys %$names) {
                  foreach my $key (keys %{$$names{$name}}) {
                    push(@input, @{$$names{$name}->{$key}});
                    if ($key ne $defgroup) {
                      $newgroup = $key;
                    }
                  }
                }

                if (defined $input[0]) {
                  my(@front) = ();
                  my(@copy)  = @$array;

                  @$array = ();
                  foreach my $input (@input) {
                    my($part) = $self->remove_wanted_extension(
                                   $input,
                                   $self->{'valid_components'}->{$gentype});

                    my(@files) = $self->generated_filenames($part, $gentype,
                                                            $tag, $input);
                    if (defined $copy[0]) {
                      my($found) = 0;
                      foreach my $file (@files) {
                        for(my $i = 0; $i < scalar(@copy); $i++) {
                          my($re) = $self->escape_regex_special($copy[$i]);
                          if ($file eq $copy[$i] || $file =~ /[\/\\]$re$/) {
                            ## No need to check for previously added files
                            ## here since there are none.
                            $found = 1;
                            push(@front, $file);
                            splice(@copy, $i, 1);
                            last;
                          }
                        }
                        if ($found) {
                          last;
                        }
                      }
                      if (!$found) {
                        my($ext) = $$exts[0];
                        foreach my $file (@files) {
                          if ($file =~ /$ext$/) {
                            push(@front, $file);
                          }
                        }
                      }
                    }
                    else {
                      my($ext) = $$exts[0];
                      foreach my $file (@files) {
                        if ($file =~ /$ext$/) {
                          push(@front, $file);
                        }
                      }
                    }
                  }
                  if (defined $copy[0]) {
                    ## No need to check for previously added files
                    ## here since there are none.
                    push(@$array, @copy);
                    if (defined $self->get_assignment($grtag)) {
                      $self->process_assignment_add($grtag, $defgroup);
                    }
                  }
                  if (defined $front[0]) {
                    if (defined $newgroup) {
                      if (defined $copy[0]) {
                        $self->process_assignment_add($grtag, $defgroup);
                      }
                      if (!defined $self->{$tag}->{$defcomp}->{$newgroup}) {
                        $self->{$tag}->{$defcomp}->{$newgroup} = \@front;
                      }
                      else {
                        push(@{$self->{$tag}->{$defcomp}->{$newgroup}}, @front);
                      }
                      $self->process_assignment_add($grtag, $newgroup);
                    }
                    else {
                      unshift(@$array, @front);
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}


sub remove_duplicated_files {
  my($self)   = shift;
  my($dest)   = shift;
  my($source) = shift;
  my($names)  = $self->{$dest};
  my(@slist)  = $self->get_component_list($source, 1);
  my(%shash)  = ();

  ## Convert the array into keys for a hash table
  @shash{@slist} = ();

  ## Find out which source files are listed
  foreach my $name (keys %$names) {
    foreach my $key (keys %{$$names{$name}}) {
      my($array) = $$names{$name}->{$key};
      my($count) = scalar(@$array);
      for(my $i = 0; $i < $count; ++$i) {
        ## Is the source file in the component array?
        if (exists $shash{$$array[$i]}) {
          ## Remove the element and fix the index and count
          splice(@$array, $i, 1);
          --$count;
          --$i;
        }
      }
    }
  }
}


sub generated_source_listed {
  my($self)  = shift;
  my($gent)  = shift;
  my($tag)   = shift;
  my($arr)   = shift;
  my($sext)  = shift;
  my($names) = $self->{$tag};

  ## Find out which generated source files are listed
  foreach my $name (keys %$names) {
    my($comps) = $$names{$name};
    foreach my $key (keys %$comps) {
      foreach my $val (@{$$comps{$key}}) {
        foreach my $i (@$arr) {
          foreach my $wanted (@$sext) {
            ## Remove any escape characters from the extension
            my($oext) = $wanted;
            $oext =~ s/\\//g;
            foreach my $re ($self->generated_filenames($i, $gent,
                                                       $tag, "$i$oext")) {
              $re = $self->escape_regex_special($re);
              if ($val =~ /$re$/) {
                return 1;
              }
            }
          }
        }
      }
    }
  }

  return 0;
}


sub list_default_generated {
  my($self)    = shift;
  my($gentype) = shift;
  my($tags)    = shift;

  ## This method is called when the user has custom input files and has
  ## provided source files.  If the user defaults the component (i.e.
  ## source_files, resource_files, etc.) they are filled in by the
  ## generate_default_components method.

  if ($self->{'generated_exts'}->{$gentype}->{'automatic'}) {
    ## After all source and headers have been defaulted, see if we
    ## need to add the generated files
    if (defined $self->{$gentype}) {
      ## Build up the list of files
      my(@arr)   = ();
      my($names) = $self->{$gentype};
      my($group) = undef;
      foreach my $name (keys %$names) {
        foreach my $key (keys %{$$names{$name}}) {
          my($array) = $$names{$name}->{$key};

          if ($key ne $defgroup) {
            $group = $key;
          }

          foreach my $val (@$array) {
            my($f) = $self->remove_wanted_extension(
                              $val,
                              $self->{'valid_components'}->{$gentype});
            push(@arr, $f);
          }
        }
      }

      foreach my $type (@$tags) {
        ## Do not add generated files if they are "special"
        ## unless they haven't been explicitly supplied.
        if (!$specialComponents{$type} ||
            !$self->{'special_supplied'}->{$type}) {
          if (!$self->generated_source_listed(
                                $gentype, $type, \@arr,
                                $self->{'valid_components'}->{$gentype})) {
            $self->add_generated_files($gentype, $type, $group, \@arr);
          }
        }
      }
    }
  }
}


sub prepend_gendir {
  my($self)    = shift;
  my($created) = shift;
  my($ofile)   = shift;
  my($gentype) = shift;
  my($key)     = undef;

  foreach my $ext (@{$self->{'valid_components'}->{$gentype}}) {
    my($e) = $ext;
    $e =~ s/\\//g;
    $key = "$ofile$e";
    if (defined $self->{'flag_overrides'}->{$gentype}->{$key}) {
      last;
    }
    else {
      $key = undef;
    }
  }

  if (defined $key) {
    foreach my $ma (@{$self->{'matching_assignments'}->{$gentype}}) {
      if ($ma eq 'gendir') {
        my($dir) = $self->{'flag_overrides'}->{$gentype}->{$key}->{$ma};
        if (defined $dir) {
          ## Convert the file to unix style for basename
          $created =~ s/\\/\//g;
          return "$dir/" . $self->mpc_basename($created);
        }
      }
    }
  }

  return $created;
}


sub list_generated_file {
  my($self)    = shift;
  my($gentype) = shift;
  my($tag)     = shift;
  my($array)   = shift;
  my($file)    = shift;
  my($ofile)   = shift;
  my($count)   = 0;

  ## Save the length of the basename for later.  We can not convert the
  ## slashes on $file as it needs to keep the back slashes since that's
  ## what comes from generated_filenames() below.  This is, of course, if
  ## we are converting slashes in the first place.
  my($fcopy) = $file;
  $fcopy =~ s/.*[\/\\]//;
  my($blen) = length($fcopy);

  ## Turn it into a regular expression
  $file = $self->escape_regex_special($file);

  foreach my $gen ($self->get_component_list($gentype, 1)) {
    my($input) = $gen;
    foreach my $ext (@{$self->{'valid_components'}->{$gentype}}) {
      ## Remove the extension.
      ## If it works, then we can exit this loop.
      if ($gen =~ s/$ext$//) {
        last;
      }
    }

    ## If the user provided file does not match any of the
    ## extensions specified by the custom definition, we need
    ## to remove the extension or else this file will not be
    ## added to the project.
    if ($gen eq $input) {
      $gen =~ s/\.[^\.]+$//;
    }

    ## See if we need to add the file.  We only need to bother
    ## if the length of $gen is less than or equal to the length of
    ## $file because they couldn't possibly match if they weren't.
    if (length($self->mpc_basename($gen)) <= $blen) {
      foreach my $re ($self->generated_filenames($gen, $gentype,
                                                 $tag, $input)) {
        if ($re =~ /$file(.*)?$/) {
          my($created) = $re;
          if (defined $ofile) {
            $created = $self->prepend_gendir($created, $ofile, $gentype);
          }
          if (!$self->already_added($array, $created)) {
            push(@$array, $created);
            ++$count;
          }
          last;
        }
      }
    }
  }
  return $count;
}


sub add_corresponding_component_files {
  my($self)     = shift;
  my($filecomp) = shift;
  my($tag)      = shift;
  my($grname)   = $grouped_key . $tag;

  ## Create a hash array keyed off of the existing files of the type
  ## that we plan on adding.
  my($fexist)  = 0;
  my(%scfiles) = ();
  my($names)   = $self->{$tag};
  foreach my $name (keys %$names) {
    ## Check to see if files exist in the default group
    if (defined $$names{$name}->{$defgroup} &&
        defined $$names{$name}->{$defgroup}->[0]) {
      $fexist = 1;
    }
    foreach my $comp (keys %{$$names{$name}}) {
      @scfiles{@{$$names{$name}->{$comp}}} = ();
    }
  }

  ## Create an array of extensions for the files we want to add
  my(@exts) = ();
  foreach my $ext (@{$self->{'valid_components'}->{$tag}}) {
    push(@exts, $ext);
    $exts[$#exts] =~ s/\\//g;
  }

  ## Check each file against a possible new file addition
  my($adddefaultgroup) = 0;
  my($oktoadddefault)  = 0;
  foreach my $sfile (keys %$filecomp) {
    my($found) = 0;
    foreach my $ext (@exts) {
      if (exists $scfiles{"$sfile$ext"}) {
        $found = 1;
        last;
      }
    }

    if (!$found) {
      ## Get the array of files for the selected component name
      my($array) = [];
      my($comp)  = $$filecomp{$sfile};
      foreach my $name (keys %$names) {
        if (defined $$names{$name}->{$comp}) {
          $array = $$names{$name}->{$comp};
        }
      }

      ## First, see if it will be generated so that we can correctly
      ## deal with 'gendir' settings.
      foreach my $gentype (keys %{$self->{'generated_exts'}}) {
        $found += $self->list_generated_file($gentype, $tag, $array, $sfile);
      }

      ## Next check to see if the file exists
      if (!$found) {
        foreach my $ext (@exts) {
          if (-r "$sfile$ext") {
            my($file) = "$sfile$ext";
            if (!$self->already_added($array, $file)) {
              push(@$array, $file);
              ++$found;
            }
            last;
          }
        }
      }

      ## If we have any files at all in the component array, check
      ## to see if we need to add a new group name
      if (defined $$array[0]) {
        if ($comp eq $defgroup) {
          $adddefaultgroup = 1;
        }
        else {
          my($compexists) = undef;
          my($grval)      = $self->get_assignment($grname);
          if (defined $grval) {
            foreach my $grkey (@{$self->create_array($grval)}) {
              if ($grkey eq $comp) {
                $compexists = 1;
                last;
              }
            }
          }

          if (!$compexists) {
            $self->process_assignment_add($grname, $comp);
          }
          $oktoadddefault = 1;
          $adddefaultgroup |= $fexist;
        }

        ## Put the array back into the component list
        if ($found) {
          foreach my $name (keys %$names) {
            $$names{$name}->{$comp} = $array;
          }
        }
      }
    }
  }

  ## We only need to add the default group name if we wanted to
  ## add the default group when adding new files and we added a group
  ## by some other name.  Otherwise, defaulted files would always be
  ## in a group, which is not what we want.
  if ($adddefaultgroup && $oktoadddefault) {
    $self->process_assignment_add($grname, $defgroup);
  }
}


sub get_default_project_name {
  my($self) = shift;
  my($name) = $self->{'current_input'};

  if ($name eq '') {
    $name = $self->transform_file_name($self->base_directory());
  }
  else {
    ## Since files on UNIX can have back slashes, we transform them
    ## into underscores.
    $name =~ s/\\/_/g;

    ## Convert the name to a usable name
    $name = $self->transform_file_name($name);

    ## Take off the extension
    $name =~ s/\.[^\.]+$//;
  }

  return $name;
}


sub remove_excluded {
  my($self) = shift;
  my(@tags) = @_;

  ## Process each file type and remove the excluded files
  foreach my $tag (@tags) {
    my($names) = $self->{$tag};
    foreach my $name (keys %$names) {
      foreach my $comp (keys %{$$names{$name}}) {
        my($count) = scalar(@{$$names{$name}->{$comp}});
        for(my $i = 0; $i < $count; ++$i) {
          my($file) = $$names{$name}->{$comp}->[$i];
          if (defined $self->{'remove_files'}->{$tag}->{$file}) {
            splice(@{$$names{$name}->{$comp}}, $i, 1);
            --$i;
            --$count;
          }
        }
      }
    }
    delete $self->{'remove_files'}->{$tag};
  }
}


sub generate_defaults {
  my($self) = shift;

  ## Generate default project name
  if (!defined $self->get_assignment('project_name')) {
    $self->set_project_name($self->get_default_project_name());
  }

  ## Generate the default pch file names (if needed)
  my(@files) = $self->generate_default_file_list(
                                 '.', [],
                                 undef, $self->get_assignment('recurse'));
  $self->generate_default_pch_filenames(\@files);

  ## If the pch file names are empty strings then we need to fix that
  $self->fix_pch_filenames();

  ## Generate default components, but %specialComponents
  ## are skipped in the initial default components generation
  $self->generate_default_components(\@files);

  ## Remove source files that are also listed in the template files
  ## If we do not do this, then generated projects can be invalid.
  $self->remove_duplicated_files('source_files', 'template_files');

  ## If pch files are listed in header_files or source_files more than
  ## once, we need to remove the extras
  $self->remove_extra_pch_listings();

  ## Generate the default generated list of files only if we defaulted
  ## the generated file list.  I want to ensure that source_files comes
  ## first in the list to pick up group information (since source_files
  ## are most likely going to be grouped than anything else).
  my(@vc) = reverse sort { return 1 if $a eq 'source_files';
                           return $a cmp $b; } keys %{$self->{'valid_components'}};
  foreach my $gentype (keys %{$self->{'generated_exts'}}) {
    $self->list_default_generated($gentype, \@vc);
  }

  ## Now that all of the source files have been added
  ## we need to remove those that have need to be removed
  $self->remove_excluded('source_files');

  ## Collect up all of the source file that have already been listed
  ## with the extension removed.
  my(%sourcecomp) = ();
  foreach my $sourcetag (keys %sourceComponents) {
    my($names) = $self->{$sourcetag};
    foreach my $name (keys %$names) {
      foreach my $comp (keys %{$$names{$name}}) {
        foreach my $sfile (@{$$names{$name}->{$comp}}) {
          my($mod) = $sfile;
          $mod =~ s/\.[^\.]+$//;
          $sourcecomp{$mod} = $comp;
        }
      }
    }
  }

  ## Add %specialComponents files based on the
  ## source_components (i.e. .h and .i or .inl based on .cpp)
  foreach my $tag (keys %specialComponents) {
    $self->add_corresponding_component_files(\%sourcecomp, $tag);
  }

  ## Now, if the %specialComponents are still empty
  ## then take any file that matches the components extension
  foreach my $tag (keys %specialComponents) {
    if (!$self->{'special_supplied'}->{$tag}) {
      my($names) = $self->{$tag};
      if (defined $names) {
        ## We only want to generate default components if we have
        ## defaulted the source files or we have no files listed
        ## in the current special component.
        my($ok) = $self->{'defaulted'}->{'source_files'};
        if (!$ok) {
          my(@all) = ();
          foreach my $name (keys %$names) {
            foreach my $key (keys %{$$names{$name}}) {
              push(@all, @{$$names{$name}->{$key}});
            }
          }
          $ok = (!defined $all[0]);
        }
        if ($ok) {
          $self->generate_default_components(\@files, $tag);
        }
      }
    }
  }

  ## Now that all of the other files have been added
  ## we need to remove those that have need to be removed
  my(@rmkeys) = keys %{$self->{'remove_files'}};
  if (defined $rmkeys[0]) {
    $self->remove_excluded(@rmkeys);
  }
}


sub set_project_name {
  my($self) = shift;
  my($name) = shift;

  ## Save the unmodified project name so that when we
  ## need to determine the default target name, we can use
  ## what is expected by the user.
  $self->{'unmodified_project_name'} = $name;

  ## If we are applying the name modifier to the project
  ## then we will modify the project name
  if ($self->get_apply_project()) {
    my($nmod) = $self->get_name_modifier();

    if (defined $nmod) {
      $nmod =~ s/\*/$name/g;
      $name = $nmod;
    }
  }

  ## Set the project_name assignment so that the TemplateParser
  ## can get the project name.
  $self->process_assignment('project_name', $name);
}


sub project_name {
  my($self) = shift;
  return $self->get_assignment('project_name');
}


sub lib_target {
  my($self) = shift;
  return (defined $self->get_assignment('sharedname') ||
          defined $self->get_assignment('staticname'));
}


sub exe_target {
  my($self) = shift;
  return (defined $self->get_assignment('exename'));
}


sub get_component_list {
  my($self)      = shift;
  my($tag)       = shift;
  my($noconvert) = shift;
  my($names)     = $self->{$tag};
  my(@list)      = ();

  foreach my $name (keys %$names) {
    foreach my $key (keys %{$$names{$name}}) {
      push(@list, @{$$names{$name}->{$key}});
    }
  }

  ## By default, if 'convert_slashes' is true, then we convert slashes
  ## to backslashes.  There are cases where we do not want to convert
  ## the slashes, in that case get_component_list() was called with
  ## an additional parameter indicating this.
  if (!$noconvert && $self->{'convert_slashes'}) {
    foreach my $item (@list) {
      $item =~ s/\//\\/g;
    }
  }

  if ($self->{'sort_files'}) {
    @list = sort { $self->file_sorter($a, $b) } @list;
  }

  return @list;
}


sub check_custom_output {
  my($self)    = shift;
  my($based)   = shift;
  my($cinput)  = shift;
  my($ainput)  = shift;
  my($type)    = shift;
  my($comps)   = shift;
  my(@outputs) = ();

  foreach my $array ($self->generated_filename_arrays($cinput, $based,
                                                      $type, $ainput)) {
    foreach my $built (@$array) {
      if (@$comps == 0) {
        push(@outputs, $built);
        last;
      }
      elsif (defined $specialComponents{$type} &&
             !$self->{'special_supplied'}->{$type}) {
        push(@outputs, $built);
        last;
      }
      else {
        my($base) = $built;
        $base =~ s/\\/\//g if ($self->{'convert_slashes'});
        my($re) = $self->escape_regex_special($self->mpc_basename($base));
        foreach my $c (@$comps) {
          ## We only match if the built file name matches from
          ## beginning to end or from a slash to the end.
          if ($c =~ /^$re$/ || $c =~ /[\/\\]$re$/) {
            push(@outputs, $built);
            last;
          }
        }
      }
    }
  }

  return @outputs;
}


sub get_special_value {
  my($self)   = shift;
  my($type)   = shift;
  my($cmd)    = shift;
  my($based)  = shift;
  my(@params) = @_;


  if ($type eq 'feature') {
    return $self->get_feature_value($cmd, $based);
  }
  elsif (index($type, 'custom_type') == 0) {
    return $self->get_custom_value($cmd, $based, @params);
  }
  elsif (index($type, $grouped_key) == 0) {
    return $self->get_grouped_value($type, $cmd, $based);
  }

  return undef;
}


sub get_feature_value {
  my($self)   = shift;
  my($cmd)    = shift;
  my($based)  = shift;

  if ($cmd eq 'value') {
    my($val) = $self->{'feature_parser'}->get_value($based);
    if (defined $val && $val != 0) {
      return 1;
    }
  }

  return undef;
}


sub get_grouped_value {
  my($self)  = shift;
  my($type)  = shift;
  my($cmd)   = shift;
  my($based) = shift;
  my($value) = undef;

  ## Make it all lower case
  $type = lc($type);

  ## Remove the grouped_ part
  $type =~ s/^$grouped_key//;

  ## Add the s if it isn't there
  if ($type !~ /s$/) {
    $type .= 's';
  }

  my($names) = $self->{$type};
  if ($cmd eq 'files') {
    foreach my $name (keys %$names) {
      my($comps) = $$names{$name};
      foreach my $comp (keys %$comps) {
        if ($comp eq $based) {
          if ($self->{'convert_slashes'}) {
            my(@converted) = ();
            foreach my $file (@{$$comps{$comp}}) {
              push(@converted, $self->slash_to_backslash($file));
            }
            $value = \@converted;
          }
          else {
            $value = $$comps{$comp};
          }
          if ($self->{'sort_files'}) {
            my(@sorted) = sort { $self->file_sorter($a, $b) } @$value;
            $value = \@sorted;
          }
          last;
        }
      }
    }
  }
  elsif ($cmd eq 'component_name') {
    ## If there is more than one name, then we will need
    ## to deal with that at a later time.
    foreach my $name (keys %$names) {
      $value = $name;
    }
  }

  return $value;
}


sub get_command_subs {
  my($self)  = shift;
  my(%valid) = ();

  ## Add the built-in OS compatibility commands
  if (UNIVERSAL::isa($self, 'WinProjectBase')) {
    $valid{'cat'}   = 'type';
    $valid{'cmp'}   = 'fc';
    $valid{'cp'}    = 'copy /y';
    $valid{'mkdir'} = 'mkdir';
    $valid{'mv'}    = 'move /y';
    $valid{'os'}    = 'win32';
    $valid{'rm'}    = 'del /f/s/q';
    $valid{'nul'}   = 'nul';
  }
  else {
    $valid{'cat'}   = 'cat';
    $valid{'cmp'}   = 'cmp';
    $valid{'cp'}    = 'cp -f';
    $valid{'mkdir'} = 'mkdir -p';
    $valid{'mv'}    = 'mv -f';
    $valid{'os'}    = 'unix';
    $valid{'rm'}    = 'rm -rf';
    $valid{'nul'}   = '/dev/null';
  }

  ## Add the project specific compatibility commands
  $valid{'gt'}    = $self->get_gt_symbol();
  $valid{'lt'}    = $self->get_lt_symbol();
  $valid{'and'}   = $self->get_and_symbol();
  $valid{'or'}    = $self->get_or_symbol();
  $valid{'quote'} = $self->get_quote_symbol();

  return \%valid;
}


sub replace_parameters {
  my($self)   = shift;
  my($str)    = shift;
  my($valid)  = shift;
  my($nowarn) = shift;
  my($input)  = shift;
  my($output) = shift;

  while ($str =~ /<%(\w+)(\(\w+\))?%>/) {
    my($name)     = $1;
    my($modifier) = $2;
    if (defined $modifier) {
      my($tmp) = $name;
      $name = $modifier;
      $name =~ s/[\(\)]//g;
      $modifier = $tmp;
    }

    if (exists $$valid{$name}) {
      if (defined $$valid{$name}) {
        my($replace) = $$valid{$name};
        if (defined $modifier) {
          if ($modifier eq 'noextension') {
            $replace =~ s/\.[^\.]+$//;
          }
          else {
            $self->warning("Uknown parameter modifier $modifier.");
          }
        }
        $str =~ s/<%\w+(\(\w+\))?%>/$replace/;
      }
      else {
        $str =~ s/<%\w+(\(\w+\))?%>//;
      }
    }
    else {
      $str =~ s/<%\w+(\(\w+\))?%>//;

      ## We only want to warn the user that we did not recognize the
      ## pseudo template parameter if there was an input and an output
      ## file passed to this function.  If this variable was used
      ## without the parenthesis (as in an if statement), then we don't
      ## want to warn the user.
      if (defined $input && defined $output) {
        if (!defined $$nowarn{$name}) {
          $self->warning("<%$name%> was not recognized.");
        }

        ## If we didn't recognize the pseudo template parameter then
        ## we don't want to return anything back.
        return undef;
      }
    }
  }

  return $str;
}


sub convert_command_parameters {
  my($self)   = shift;
  my($str)    = shift;
  my($input)  = shift;
  my($output) = shift;
  my(%nowarn) = ();
  my(%valid)  = %{$self->{'command_subs'}};

  ## Add in the values that change for every call to this function
  $valid{'temporary'} = 'temp.$$$$.' . int(rand(0xffffffff));

  if (defined $input) {
    $valid{'input'}          = $input;
    $valid{'input_basename'} = $self->mpc_basename($input);
    $valid{'input_noext'}    = $input;
    $valid{'input_noext'}    =~ s/(\.[^\.]+)$//;
    $valid{'input_ext'}      = $1;
  }

  if (defined $output) {
    my($first) = 1;
    $valid{'output'} = "@$output";
    foreach my $out (@$output) {
      my($noext) = $out;
      $noext =~ s/(\.[^\.]+)$//;

      $valid{'output_ext'}       = $1;
      $valid{'output_noext'}    .= (!$first ? ' ' : '') . $noext;
      $valid{'output_basename'} .= (!$first ? ' ' : '') .
                                   $self->mpc_basename($out);
      $first = 0;
    }
  }

  ## Add in the specific types of output files
  if (defined $output) {
    foreach my $type (keys %{$self->{'valid_components'}}) {
      my($key) = $type;
      $key =~ s/s$//gi;
      $nowarn{$key} = 1;
      $nowarn{$key . '_noext'} = 1;
      foreach my $ext (@{$self->{'valid_components'}->{$type}}) {
        foreach my $out (@$output) {
          if ($out =~ /$ext$/) {
            $valid{$key} = $out;
            $valid{$key . '_noext'} = $out;
            $valid{$key . '_noext'} =~ s/\.[^\.]+$//;
            last;
          }
        }
      }
    }
  }

  return $self->replace_parameters($str, \%valid, \%nowarn, $input, $output);
}


sub get_custom_value {
  my($self)   = shift;
  my($cmd)    = shift;
  my($based)  = shift;
  my(@params) = @_;
  my($value)  = undef;

  if ($cmd eq 'input_files') {
    ## Get the component list for the component type
    my(@array) = $self->get_component_list($based);

    ## Check for directories in the component list.  If the component
    ## type is not automatic, we may have directories here and will need
    ## to get the file list for that type.
    my($once) = undef;
    for(my $i = 0; $i < scalar(@array); ++$i) {
      if (-d $array[$i]) {
        if (!defined $once) {
          $once = {'recurse' => $self->get_assignment('recurse'),
                   'pchh'    => $self->get_assignment('pch_header'),
                   'pchc'    => $self->get_assignment('pch_source'),
                  };
        }
        my(@built) = ();
        $self->sift_default_file_list($based, $array[$i], \@built,
                                      $self->{'valid_components'}->{$based},
                                      $$once{'recurse'},
                                      $$once{'pchh'}, $$once{'pchc'});
        splice(@array, $i, 1, @built);
        $i += $#built;
      }
    }

    $value = \@array;

    $self->{'custom_output_files'} = {};
    my(%vcomps) = ();
    foreach my $vc (keys %{$self->{'valid_components'}}) {
      my(@comps) = $self->get_component_list($vc);
      $vcomps{$vc} = \@comps;
    }
    $vcomps{$generic_key} = [];

    foreach my $input (@array) {
      my(@outputs) = ();
      my($ainput)  = $input;
      my($cinput)  = $input;

      ## Remove the extension
      $cinput =~ s/\.[^\.]+$//;

      ## If we are converting slashes,
      ## change them back for this parameter
      $ainput =~ s/\\/\//g if ($self->{'convert_slashes'});

      ## Add all of the output files
      foreach my $vc (keys %{$self->{'valid_components'}}, $generic_key) {
        push(@outputs,
             $self->check_custom_output($based, $cinput,
                                        $ainput, $vc, $vcomps{$vc}));
      }

      ## Add specially listed files avoiding duplicates
      if (defined $self->{'custom_special_output'}->{$based} &&
          defined $self->{'custom_special_output'}->{$based}->{$ainput}) {
        foreach my $file (@{$self->{'custom_special_output'}->{$based}->{$ainput}}) {
          my($found) = 0;
          foreach my $output (@outputs) {
            if ($output eq $file) {
              $found = 1;
              last;
            }
          }
          if (!$found) {
            push(@outputs, $file);
          }
        }
      }

      if ($self->{'sort_files'}) {
        @outputs = sort { $self->file_sorter($a, $b) } @outputs;
      }
      $self->{'custom_output_files'}->{$input} = \@outputs;
    }
  }
  elsif ($cmd eq 'output_files') {
    # Generate output files based on $based
    if (defined $self->{'custom_output_files'}) {
      $value = $self->{'custom_output_files'}->{$based};
    }
  }
  elsif ($cmd eq 'source_output_files') {
    # Generate source output files based on $based
    if (defined $self->{'custom_output_files'}) {
      $value = [];
      foreach my $file (@{$self->{'custom_output_files'}->{$based}}) {
        foreach my $ext (@{$self->{'valid_components'}->{'source_files'}}) {
          if ($file =~ /$ext$/) {
            ## We've found a file that matches one of the source file
            ## extensions.  Now we have to make sure that it doesn't
            ## match a template file extension.
            my($matched) = 0;
            foreach my $text (@{$self->{'valid_components'}->{'template_files'}}) {
              if ($file =~ /$text$/) {
                $matched = 1;
                last;
              }
            }
            if (!$matched) {
              push(@$value, $file);
            }
            last;
          }
        }
      }
    }
  }
  elsif ($cmd eq 'non_source_output_files') {
    # Generate non source output files based on $based
    if (defined $self->{'custom_output_files'}) {
      $value = [];
      foreach my $file (@{$self->{'custom_output_files'}->{$based}}) {
        my($source) = 0;
        foreach my $ext (@{$self->{'valid_components'}->{'source_files'}}) {
          if ($file =~ /$ext$/) {
            $source = 1;
            ## We've found a file that matches one of the source file
            ## extensions.  Now we have to make sure that it doesn't
            ## match a template file extension.
            foreach my $text (@{$self->{'valid_components'}->{'template_files'}}) {
              if ($file =~ /$text$/) {
                $source = 0;
                last;
              }
            }
            last if ($source);
          }
        }
        if (!$source) {
          push(@$value, $file);
        }
      }
    }
  }
  elsif ($cmd eq 'inputexts') {
    my(@array) = @{$self->{'valid_components'}->{$based}};
    foreach my $val (@array) {
      $val =~ s/\\\.//g;
    }
    $value = \@array;
  }
  elsif ($cmd eq 'dependencies') {
    ## If we are converting slashes, change them back for this parameter
    $based =~ s/\\/\//g if ($self->{'convert_slashes'});
    $value = $self->{'custom_special_depend'}->{$based};
  }
  elsif (defined $customDefined{$cmd} &&
         ($customDefined{$cmd} & 0x04) != 0) {
    $value = $self->get_assignment($cmd,
                                   $self->{'generated_exts'}->{$based});
    if (defined $value && ($customDefined{$cmd} & 0x10) != 0) {
      $value = $self->convert_command_parameters($value, @params);
    }
  }
  elsif (defined $custom{$cmd}) {
    $value = $self->get_assignment($cmd,
                                   $self->{'generated_exts'}->{$based});
  }

  return $value;
}


sub check_features {
  my($self)     = shift;
  my($requires) = shift;
  my($avoids)   = shift;
  my($info)     = shift;
  my($status)   = 1;
  my($why)      = undef;

  if (defined $requires) {
    foreach my $require (split(/\s+/, $requires)) {
      my($fval) = $self->{'feature_parser'}->get_value($require);

      ## By default, if the feature is not listed, then it is enabled.
      if (defined $fval && !$fval) {
        $why = "requires $require";
        $status = 0;
        last;
      }
    }
  }

  ## If it passes the requires, then check the avoids
  if ($status) {
    if (defined $avoids) {
      foreach my $avoid (split(/\s+/, $avoids)) {
        my($fval) = $self->{'feature_parser'}->get_value($avoid);

        ## By default, if the feature is not listed, then it is enabled.
        if (!defined $fval || $fval) {
          $why = "avoids $avoid";
          $status = 0;
          last;
        }
      }
    }
  }

  if ($info && !$status) {
    $self->details("Skipping " . $self->get_assignment('project_name') .
                   " ($self->{'current_input'}), it $why.");
  }

  return $status;
}


sub need_to_write_project {
  my($self) = shift;

  foreach my $key ('source_files', 'resource_files',
                   keys %{$self->{'generated_exts'}}) {
    my($names) = $self->{$key};
    foreach my $name (keys %$names) {
      foreach my $key (keys %{$names->{$name}}) {
        if (defined $names->{$name}->{$key}->[0]) {
          return 1;
        }
      }
    }
  }

  return 0;
}


sub write_output_file {
  my($self)     = shift;
  my($name)     = shift;
  my($status)   = 0;
  my($error)    = undef;
  my($tover)    = $self->get_template_override();
  my($template) = (defined $tover ? $tover : $self->get_template());

  ## If the template files does not end in the template extension
  ## then we will add it on.
  if ($template !~ /$TemplateExtension$/) {
    $template = $template . ".$TemplateExtension";
  }

  ## If the template file does not contain a full path, then we
  ## will search through the include paths for it.
  my($tfile) = undef;
  if ($template =~ /^([a-z]:)?[\/\\]/i) {
    $tfile = $template;
  }
  else {
    $tfile = $self->search_include_path($template);
  }

  if (defined $tfile) {
    ## Read in the template values for the
    ## specific target and project type
    ($status, $error) = $self->read_template_input();

    if ($status) {
      my($tp) = new TemplateParser($self);

      ## Set the project_file assignment for the template parser
      $self->process_assignment('project_file', $name);

      ($status, $error) = $tp->parse_file($tfile);

      if ($status) {
        if (defined $self->{'source_callback'}) {
          my($cb)     = $self->{'source_callback'};
          my($pjname) = $self->get_assignment('project_name');
          my(@list)   = $self->get_component_list('source_files');
          if (UNIVERSAL::isa($cb, 'ARRAY')) {
            my(@copy) = @$cb;
            my($s) = shift(@copy);
            &$s(@copy, $name, $pjname, @list);
          }
          elsif (UNIVERSAL::isa($cb, 'CODE')) {
            &$cb($name, $pjname, @list);
          }
          else {
            $self->warning("Ignoring callback: $cb.");
          }
        }

        if ($self->get_toplevel()) {
          my($outdir) = $self->get_outdir();
          my($oname)  = $name;

          $name = "$outdir/$name";

          my($fh)  = new FileHandle();
          my($dir) = $self->mpc_dirname($name);

          if ($dir ne '.') {
            mkpath($dir, 0, 0777);
          }

          if ($self->compare_output()) {
            ## First write the output to a temporary file
            my($tmp) = "$outdir/MPC$>.$$";
            my($different) = 1;
            if (open($fh, ">$tmp")) {
              my($lines) = $tp->get_lines();
              foreach my $line (@$lines) {
                print $fh $line;
              }
              close($fh);

              if (-r $name &&
                  -s $tmp == -s $name && compare($tmp, $name) == 0) {
                $different = 0;
              }
            }
            else {
              $error = "Unable to open $tmp for output.";
              $status = 0;
            }

            if ($status) {
              ## If they are different, then rename the temporary file
              if ($different) {
                unlink($name);
                if (rename($tmp, $name)) {
                  $self->post_file_creation($name);
                  $self->add_file_written($oname);
                }
                else {
                  $error = "Unable to open $name for output.";
                  $status = 0;
                }
              }
              else {
                ## We will pretend that we wrote the file
                unlink($tmp);
                $self->add_file_written($oname);
              }
            }
          }
          else {
            if (open($fh, ">$name")) {
              my($lines) = $tp->get_lines();
              foreach my $line (@$lines) {
                print $fh $line;
              }
              close($fh);
              $self->add_file_written($oname);
            }
            else {
              $error = "Unable to open $name for output.";
              $status = 0;
            }
          }
        }
      }
    }
  }
  else {
    $error = "Unable to locate the template file: $template.";
    $status = 0;
  }

  return $status, $error;
}


sub write_install_file {
  my($self)    = shift;
  my($fh)      = new FileHandle();
  my($insfile) = $self->transform_file_name(
                           $self->get_assignment('project_name')) .
                 '.ins';
  my($outdir)  = $self->get_outdir();

  $insfile = "$outdir/$insfile";

  unlink($insfile);
  if (open($fh, ">$insfile")) {
    foreach my $vc (keys %{$self->{'valid_components'}}) {
      my($names) = $self->{$vc};
      foreach my $name (keys %$names) {
        foreach my $key (keys %{$$names{$name}}) {
          my($array) = $$names{$name}->{$key};
          if (defined $$array[0]) {
            print $fh "$vc:\n";
            foreach my $file (@$array) {
              print $fh "$file\n";
            }
            print $fh "\n";
          }
        }
      }
    }
    if ($self->exe_target()) {
      my($install) = $self->get_assignment('install');
      print $fh "exe_output:\n",
                (defined $install ? $self->relative($install) : ''),
                ' ', $self->get_assignment('exename'), "\n";
    }
    elsif ($self->lib_target()) {
      my($shared) = $self->get_assignment('sharedname');
      my($static) = $self->get_assignment('staticname');
      my($dllout) = $self->relative($self->get_assignment('dllout'));
      my($libout) = $self->relative($self->get_assignment('libout'));

      print $fh "lib_output:\n";

      if (defined $shared && $shared ne '') {
        print $fh (defined $dllout ? $dllout : $libout), " $shared\n";
      }
      if ((defined $static && $static ne '') &&
          (defined $dllout || !defined $shared ||
               (defined $shared && $shared ne $static))) {
        print $fh "$libout $static\n";
      }
    }

    close($fh);
    return 1, undef;
  }

  return 0, 'Unable write to ' . $insfile;
}


sub write_project {
  my($self)      = shift;
  my($status)    = 1;
  my($error)     = undef;
  my($progress)  = $self->get_progress_callback();

  if (defined $progress) {
    &$progress();
  }

  if ($self->check_features($self->get_assignment('requires'),
                            $self->get_assignment('avoids'),
                            1)) {
    if ($self->need_to_write_project()) {
      if ($self->get_assignment('custom_only')) {
        $self->remove_non_custom_settings();
      }

      if ($self->{'escape_spaces'}) {
        foreach my $name ('exename', 'sharedname', 'staticname') {
          my($value) = $self->get_assignment($name);
          if (defined $value && $value =~ s/(\s)/\\$1/g) {
            $self->process_assignment($name, $value);
          }
        }
        foreach my $key (keys %{$self->{'valid_components'}}) {
          my($names) = $self->{$key};
          foreach my $name (keys %$names) {
            foreach my $key (keys %{$$names{$name}}) {
              foreach my $file (@{$$names{$name}->{$key}}) {
                $file =~ s/(\s)/\\$1/g;
              }
            }
          }
        }
      }

      ($status, $error) = $self->write_output_file(
                                   $self->transform_file_name(
                                            $self->project_file_name()));
      if ($self->{'generate_ins'} && $status) {
        ($status, $error) = $self->write_install_file();
      }
    }
    else {
      my($msg) = $self->transform_file_name($self->project_file_name()) .
                 " has no useful targets.";

      if ($self->{'current_input'} eq '') {
        $self->information($msg);
      }
      else {
        $self->warning($msg);
      }
    }
  }
  else {
    $status = 2;
  }

  return $status, $error;
}


sub get_project_info {
  my($self) = shift;
  return $self->{'project_info'};
}


sub get_lib_locations {
  my($self) = shift;
  return $self->{'lib_locations'};
}


sub get_inheritance_tree {
  my($self) = shift;
  return $self->{'inheritance_tree'};
}


sub set_component_extensions {
  my($self) = shift;
  my($vc)   = $self->{'valid_components'};
  my($ec)   = $self->{'exclude_components'};

  foreach my $key (keys %$vc) {
    my($ov) = $self->override_valid_component_extensions($key);
    if (defined $ov) {
      $$vc{$key} = $ov;
    }
  }

  foreach my $key (keys %$ec) {
    my($ov) = $self->override_exclude_component_extensions($key);
    if (defined $ov) {
      $$ec{$key} = $ov;
    }
  }
}


sub set_source_listing_callback {
  my($self) = shift;
  my($cb)   = shift;
  $self->{'source_callback'} = $cb;
}


sub reset_values {
  my($self) = shift;

  ## Only put data structures that need to be cleared
  ## out when the mpc file is done being read, not at the
  ## end of each project within the mpc file.  Those go in
  ## the closing curly brace section of parse_line().
  $self->{'project_info'}  = [];
  $self->{'lib_locations'} = {};
  $self->reset_generating_types();
}


sub add_default_matching_assignments {
  my($self) = shift;
  my($lang) = $self->get_language();

  if (defined $lang) {
    foreach my $key (keys %{$language{$lang}->[0]}) {
      if (!defined $language{$lang}->[2]->{$key}) {
        $language{$lang}->[2]->{$key} = [];
        if (defined $default_matching_assignments{$key}) {
          push(@{$language{$lang}->[2]->{$key}},
               @{$default_matching_assignments{$key}});
        }
        foreach my $keyword (@default_matching_assignments) {
          push(@{$language{$lang}->[2]->{$key}}, $keyword);
        }
      }
    }
  }
}


sub reset_generating_types {
  my($self)  = shift;
  my($lang)  = $self->get_language();

  if (defined $lang) {
    my(%reset) = ('valid_components'     => $language{$lang}->[0],
                  'custom_only_removed'  => $language{$lang}->[0],
                  'exclude_components'   => $language{$lang}->[1],
                  'matching_assignments' => $language{$lang}->[2],
                  'generated_exts'       => {},
                  'valid_names'          => \%validNames,
                 );

    foreach my $r (keys %reset) {
      $self->{$r} = {};
      foreach my $key (keys %{$reset{$r}}) {
        $self->{$r}->{$key} = $reset{$r}->{$key};
      }
    }
  }

  $self->{'custom_types'} = {};

  ## Allow subclasses to override the default extensions
  $self->set_component_extensions();
}


sub get_template_input {
  my($self) = shift;

  ## This follows along the same logic as read_template_input() by
  ## checking for exe target and then defaulting to a lib target
  if ($self->exe_target()) {
    if ($self->get_static() == 1) {
      return $self->{'lexe_template_input'};
    }
    else {
      return $self->{'dexe_template_input'};
    }
  }

  if ($self->get_static() == 1) {
    return $self->{'lib_template_input'};
  }
  else {
    return $self->{'dll_template_input'};
  }
}


sub update_project_info {
  my($self)    = shift;
  my($tparser) = shift;
  my($append)  = shift;
  my($names)   = shift;
  my($sep)     = shift || '';
  my($pi)      = $self->get_project_info();
  my($value)   = '';
  my($arr)     = ($append && defined $$pi[0] ? pop(@$pi) : []);

  ## Set up the hash table when we are starting a new project_info
  if (!$append) {
    $self->{'project_info_hash_table'} = {};
  }

  ## Append the values of all names into one string
  my($ncount) = scalar(@$names) - 1;
  for(my $i = 0; $i <= $ncount; $i++) {
    $value .= $self->translate_value(
                               $$names[$i],
                               $tparser->get_value_with_default($$names[$i]));
    $value .= $sep if ($i != $ncount);
  }

  ## If we haven't seen this value yet, put it on the array
  if (!defined $self->{'project_info_hash_table'}->{"@$names $value"}) {
    $self->{'project_info_hash_table'}->{"@$names $value"} = 1;
    push(@$arr, $value);
  }

  ## Always push the array back onto the project_info
  push(@$pi, $arr);

  return $value;
}


sub adjust_value {
  my($self)  = shift;
  my($names) = shift;
  my($value) = shift;
  my($atemp) = $self->get_addtemp();

  ## Perform any additions, subtractions
  ## or overrides for the template values.
  foreach my $name (@$names) {
    if (defined $name && defined $atemp->{lc($name)}) {
      my($base) = $name;
      $base =~ s/.*:://;
      my($replace) = (defined $self->{'valid_names'}->{$base} &&
                      ($self->{'valid_names'}->{$base} & 0x04) == 0);
      foreach my $val (@{$atemp->{lc($name)}}) {
        if ($replace && index($$val[1], '<%') >= 0) {
          $$val[1] = $self->replace_parameters($$val[1],
                                               $self->{'command_subs'});
        }
        my($arr) = $self->create_array($$val[1]);
        if ($$val[0] > 0) {
          if (!defined $value) {
            $value = '';
          }
          if (UNIVERSAL::isa($value, 'ARRAY')) {
            ## We need to make $value a new array reference ($arr)
            ## to avoid modifying the array reference pointed to by $value
            unshift(@$arr, @$value);
            $value = $arr;
          }
          else {
            $value .= " $$val[1]";
          }
        }
        elsif ($$val[0] < 0) {
          if (defined $value) {
            my($parts) = undef;
            if (UNIVERSAL::isa($value, 'ARRAY')) {
              $parts = $value;
            }
            else {
              $parts = $self->create_array($value);
            }

            $value = [];
            foreach my $part (@$parts) {
              if ($part ne '') {
                my($found) = 0;
                foreach my $ae (@$arr) {
                  if ($part eq $ae) {
                    $found = 1;
                    last;
                  }
                }
                if (!$found) {
                  push(@$value, $part);
                }
              }
            }
          }
        }
        else {
          ## If the user set the variable to empty, then we need to
          ## set the value to undef
          $value = (defined $$arr[0] ? $arr : undef);
        }
      }
      last;
    }
  }

  return $value;
}


sub expand_variables {
  my($self)            = shift;
  my($value)           = shift;
  my($rel)             = shift;
  my($expand_template) = shift;
  my($scope)           = shift;
  my($expand)          = shift;
  my($warn)            = shift;
  my($cwd)             = $self->getcwd();
  my($start)           = 0;

  ## Fix up the value for Windows switch the \\'s to /
  $cwd =~ s/\\/\//g if ($self->{'convert_slashes'});

  while(substr($value, $start) =~ /(\$\(([^)]+)\))/) {
    my($whole)  = $1;
    my($name)   = $2;
    my($val)    = $$rel{$name};

    if (defined $val) {
      if ($expand) {
        $val =~ s/\//\\/g if ($self->{'convert_slashes'});
        substr($value, $start) =~ s/\$\([^)]+\)/$val/;
        $whole = $val;
      }
      else {
        ## Fix up the value for Windows switch the \\'s to /
        $val =~ s/\\/\//g if ($self->{'convert_slashes'});

        my($icwd) = ($self->{'case_insensitive'} ? lc($cwd) : $cwd);
        my($ival) = ($self->{'case_insensitive'} ? lc($val) : $val);
        my($iclen) = length($icwd);
        my($ivlen) = length($ival);

        ## If the relative value contains the current working
        ## directory plus additional subdirectories, we must pull
        ## off the additional directories into a temporary where
        ## it can be put back after the relative replacement is done.
        my($append) = undef;
        if (index($ival, $icwd) == 0 && $iclen != $ivlen &&
            substr($ival, $iclen, 1) eq '/') {
          my($diff) = $ivlen - $iclen;
          $append = substr($ival, $iclen);
          substr($ival, $iclen, $diff) = '';
          $ivlen -= $diff;
        }

        if (index($icwd, $ival) == 0 &&
            ($iclen == $ivlen || substr($icwd, $ivlen, 1) eq '/')) {
          my($current) = $icwd;
          substr($current, 0, $ivlen) = '';

          my($dircount) = ($current =~ tr/\///);
          if ($dircount == 0) {
            $ival = '.';
          }
          else {
            $ival = '../' x $dircount;
            $ival =~ s/\/$//;
          }
          if (defined $append) {
            $ival .= $append;
          }
          $ival =~ s/\//\\/g if ($self->{'convert_slashes'});
          substr($value, $start) =~ s/\$\([^)]+\)/$ival/;
          $whole = $ival;
        }
      }
    }
    elsif ($expand_template ||
           $self->expand_variables_from_template_values()) {
      my($ti) = $self->get_template_input();
      if (defined $ti) {
        $val = $ti->get_value($name);
      }
      my($sname) = (defined $scope ? $scope . "::$name" : undef);
      my($arr) = $self->adjust_value([$sname, $name],
                                     (defined $val ? $val : []));
      if (defined $$arr[0]) {
        $val = "@$arr";
        $val =~ s/\//\\/g if ($self->{'convert_slashes'});
        substr($value, $start) =~ s/\$\([^)]+\)/$val/;

        ## We have replaced the template value, but that template
        ## value may contain a $() construct that may need to get
        ## replaced too.
        $whole = '';
      }
      else {
        if ($expand && $warn) {
          $self->warning("Unable to expand $name.");
        }
      }
    }
    $start += length($whole);
  }

  return $value;
}


sub relative {
  my($self)            = shift;
  my($value)           = shift;
  my($expand_template) = shift;
  my($scope)           = shift;

  if (defined $value) {
    if (UNIVERSAL::isa($value, 'ARRAY')) {
      my(@built) = ();
      foreach my $val (@$value) {
        my($rel) = $self->relative($val, $expand_template, $scope);
        if (UNIVERSAL::isa($rel, 'ARRAY')) {
          push(@built, @$rel);
        }
        else {
          push(@built, $rel);
        }
      }
      $value = \@built;
    }
    elsif (index($value, '$') >= 0) {
      my($ovalue) = $value;
      my(@keys) = keys %{$self->{'expanded'}};
      if (defined $keys[0]) {
        $value = $self->expand_variables($value,
                                         $self->{'expanded'},
                                         $expand_template, $scope, 1);
      }

      if ($ovalue eq $value) {
        my($rel) = ($self->get_use_env() ? \%ENV : $self->get_relative());
        @keys = keys %$rel;
        if (defined $keys[0]) {
          $value = $self->expand_variables($value, $rel,
                                           $expand_template, $scope,
                                           $self->get_expand_vars(), 1);
        }
      }
    }
  }

  ## Values that have strings enclosed in double quotes are to
  ## be interpreted as elements of an array
  if (defined $value && $value =~ /^"[^"]+"(\s+"[^"]+")+$/) {
    $value = $self->create_array($value);
  }

  return $value;
}


sub get_verbatim {
  my($self)   = shift;
  my($marker) = shift;
  my($str)    = undef;
  my($thash)  = $self->{'verbatim'}->{$self->{'pctype'}};

  if (defined $thash) {
    if (defined $thash->{$marker}) {
      my($crlf) = $self->crlf();
      foreach my $line (@{$thash->{$marker}}) {
        if (!defined $str) {
          $str = '';
        }
        $str .= $self->process_special($line) . $crlf;
      }
      if (defined $str) {
        $str .= $crlf;
        $self->{'verbatim_accessed'}->{$self->{'pctype'}}->{$marker} = 1;
      }
    }
  }

  return $str;
}


sub generate_recursive_input_list {
  my($self)    = shift;
  my($dir)     = shift;
  my($exclude) = shift;
  return $self->extension_recursive_input_list($dir,
                                               $exclude,
                                               $ProjectCreatorExtension);
}


sub get_modified_project_file_name {
  my($self) = shift;
  my($name) = shift;
  my($ext)  = shift;
  my($nmod) = $self->get_name_modifier();

  ## We don't apply the name modifier to the project file
  ## name if we have already applied it to the project name
  ## since the project file name comes from the project name.
  if (defined $nmod && !$self->get_apply_project()) {
    $nmod =~ s/\*/$name/g;
    $name = $nmod;
  }
  return "$name$ext";
}


sub get_valid_names {
  my($self) = shift;
  return $self->{'valid_names'};
}


sub preserve_assignment_order {
  my($self) = shift;
  my($name) = shift;
  my($mapped) = $self->{'valid_names'}->{$name};

  ## Only return the value stored in the valid_names hash map if it's
  ## defined and it's not an array reference.  The array reference is
  ## a keyword mapping and all mapped keywords should have preserved
  ## assignment order.
  if (defined $mapped && !UNIVERSAL::isa($mapped, 'ARRAY')) {
    return ($mapped & 1);
  }

  return 1;
}


sub add_to_template_input_value {
  my($self) = shift;
  my($name) = shift;
  my($mapped) = $self->{'valid_names'}->{$name};

  ## Only return the value stored in the valid_names hash map if it's
  ## defined and it's not an array reference.  The array reference is
  ## a keyword mapping and no mapped keywords should be added to
  ## template input variables.
  if (defined $mapped && !UNIVERSAL::isa($mapped, 'ARRAY')) {
    return ($mapped & 2);
  }

  return 0;
}


sub dependency_combined_static_library {
  #my($self) = shift;
  return defined $ENV{MPC_DEPENDENCY_COMBINED_STATIC_LIBRARY};
}


sub translate_value {
  my($self) = shift;
  my($key)  = shift;
  my($val)  = shift;

  if ($key eq 'after' && $val ne '') {
    my($arr) = $self->create_array($val);
    $val = '';

    if ($self->require_dependencies()) {
      foreach my $entry (@$arr) {
        if ($self->get_apply_project()) {
          my($nmod) = $self->get_name_modifier();
          if (defined $nmod) {
            $nmod =~ s/\*/$entry/g;
            $entry = $nmod;
          }
        }
        $val .= '"' . ($self->dependency_is_filename() ?
                          $self->project_file_name($entry) : $entry) . '" ';
      }
      $val =~ s/\s+$//;
    }
  }
  return $val;
}


sub requires_parameters {
  #my($self) = shift;
  #my($name) = shift;
  return $custom{$_[1]};
}


sub project_file_name {
  my($self) = shift;
  my($name) = shift;

  if (!defined $name) {
    $name = $self->get_assignment('project_name');
  }

  return $self->get_modified_project_file_name(
                                     $self->project_file_prefix() . $name,
                                     $self->project_file_extension());
}


sub remove_non_custom_settings {
  my($self) = shift;

  ## Remove any files that may have automatically been added
  ## to this project
  foreach my $key (keys %{$self->{'custom_only_removed'}}) {
    $self->{$key} = {};
  }

  ## Unset the exename, sharedname and staticname
  $self->process_assignment('exename',    undef);
  $self->process_assignment('sharedname', undef);
  $self->process_assignment('staticname', undef);
}


sub remove_wanted_extension {
  my($self)  = shift;
  my($name)  = shift;
  my($array) = shift;
  my($orig)  = $name;

  foreach my $wanted (@$array) {
    if ($name =~ s/$wanted$//) {
      last;
    }
  }

  ## If the user provided file does not match any of the
  ## extensions specified by the custom definition, we need
  ## to remove the extension or else this file will not be
  ## added to the project.
  if ($name eq $orig) {
    $name =~ s/\.[^\.]+$//;
  }

  return $name;
}

# ************************************************************
# Virtual Methods To Be Overridden
# ************************************************************

sub post_file_creation {
  #my($self) = shift;
  #my($file) = shift;
}


sub escape_spaces {
  #my($self) = shift;
  return 0;
}


sub validated_directory {
  my($self) = shift;
  my($dir)  = shift;
  return $dir;
}

sub get_quote_symbol {
  #my($self) = shift;
  return '\\"';
}

sub get_gt_symbol {
  #my($self) = shift;
  return '>';
}


sub get_lt_symbol {
  #my($self) = shift;
  return '<';
}


sub get_and_symbol {
  #my($self) = shift;
  return '&&';
}


sub get_or_symbol {
  #my($self) = shift;
  return '||';
}


sub dollar_special {
  #my($self) = shift;
  return 0;
}


sub expand_variables_from_template_values {
  #my($self) = shift;
  return 1;
}


sub require_dependencies {
  #my($self) = shift;
  return 1;
}


sub dependency_is_filename {
  #my($self) = shift;
  return 1;
}


sub fill_value {
  #my($self) = shift;
  #my($name) = shift;
  return undef;
}


sub project_file_prefix {
  #my($self) = shift;
  return '';
}


sub project_file_extension {
  #my($self) = shift;
  return '';
}


sub override_valid_component_extensions {
  #my($self) = shift;
  #my($comp) = shift;
  return undef;
}


sub override_exclude_component_extensions {
  #my($self) = shift;
  #my($comp) = shift;
  return undef;
}


sub get_dll_exe_template_input_file {
  #my($self) = shift;
  return undef;
}


sub get_lib_exe_template_input_file {
  my($self) = shift;
  return $self->get_dll_exe_template_input_file();
}


sub get_lib_template_input_file {
  my($self) = shift;
  return $self->get_dll_template_input_file();
}


sub get_dll_template_input_file {
  #my($self) = shift;
  return undef;
}


sub get_template {
  my($self) = shift;
  return $self->{'pctype'};
}


1;
