App-SimpleScan

 view release on metacpan or  search on metacpan

lib/App/SimpleScan/Substitution.pm  view on Meta::CPAN


# If the current thing has substitutions in it, do them and
# return the line objects created by this. Otherwise, just
# return the lines as they are.
sub expand {
  my($self, @lines) = @_;

  # No lines; do nothing.
  return () if @lines == 0;

  # A single line; find the variables in it and expand them.
  if (@lines == 1) {
    # Nothing to do if no vars found.
    return $lines[0] unless $self->find_vars_callback->($lines[0]);

    return map {"$_" } $self->_expand_variables(
      App::SimpleScan::Substitution::Line->new($lines[0])
    );
  }
  # Multiple lines; do them one at a time and return the results of 
  # doing them all.
  else {
    my @done;
    foreach my $line (@lines) {
      push @done, $self->_expand($line);
    }
    return map { "$_" } @done;
  }
}

# Actually do variable substitutions.
sub _expand_variables {
  # We get a Line object. This object has its own "fixed" dictionary
  # associated with it: this defines the variables that have already
  # been expanded once for this Line. We have this because it's possible
  # that a later expansion may insert one of these variables into the 
  # line again, and we want to have a consistent value for the variable(s)
  # we've already inserted once.
  my ($self, @line_objs) = @_;

  # No objects, no output.
  return unless @line_objs;

  # More than one: process each one separately.
  return map { $self->_expand_variables($_) } @line_objs
   if @line_objs > 1;

  # A single line object; process it.
  my $line_obj = $line_objs[0];

  # Clone the dictionary, because we're going to modify it with the
  # fixed values. This effectively prunes the substitution tree at the
  # points where we've already done substitutions.
  my %dictionary = (%{ $self->dictionary() }, $line_obj->fixed);
 
  # Localize the slot that contains the dictionary and replace it
  # with our newly-constructed, possibly-pruned one.
  local($self->{dictionary}) = \%dictionary;

  # Find the most-deeply-nested substitutions; we need to do those first;
  # prune out anything that looks like a variable, but isn't (because
  # there's no value for it in the dictionary).
  my @var_names = grep { defined $self->dictionary->{$_} }
                  $self->_deepest_substitution("$line_obj");

  # We have none.
  return $line_obj unless @var_names;

  # What we want to do is to get every possible combination of the
  # active variables in this line from the dictionary, and substitute
  # all these into the line.
  #
  # Since we have a situation where we don't know how many variables there
  # are, we can't just code this as a set of nested loops. What we do instead
  # is map each possible combination into a "combination index": think of it
  # as the decimal representation of a number in a number system where each
  # position in this system's representation maps into a specific variable.
  # The number of possible values for this "place" in the number corresponds
  # to the number of possible values for thr variable.
  #
  # It's easy for us to calculate the number of possible combinations: we
  # simply multiply the number of possible values of all of the variables,
  # and we get the maximum possible combination index. We can now iterate
  # from zero to this maxiumum index, converting the decimal number back into
  # a number in the combinatorial number system; the representation we get
  # from doing this exactly maps into the proper indexes into the possible
  # values for each variable.

  # Count the number of items for each substitution,
  # and calculate the maximum combination index from this.
  my %item_count_for;
  my $max_combination = 1;
  for my $var_name (sort @var_names) {
     $max_combination *= 
      $item_count_for{$var_name} = () = 
        $self->substitution_value($var_name);
  }

  # The done queue gets Line objects that don't expand further;
  # the expansion queue gets things that expanded at least once
  # (so they need to be checked again).
  my @done_queue;
  my @expansion_queue;

  for my $i (0 .. $max_combination-1) {

    # Get the values for the variables for the current combination index.
    my %current_value_of = $self->_comb_index($i, %item_count_for);

    # Clone the current line. This keeps the fixed items and copies
    # the line text.
    my $changed_line = $line_obj->clone();
    my $string_to_alter = $changed_line->line();
    my $a_substitution_happened;

    # Try to substitute each of the currently variables into the line.
    for my $var_name (@var_names) {
      my $current_variable_value = $current_value_of{$var_name};
      my($did_change, $new_string) = 
        $self->insert_value_callback->($string_to_alter, 
                                       $var_name, 



( run in 0.832 second using v1.01-cache-2.11-cpan-39bf76dae61 )