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 )