Давайте начнем с воображения мира, которым мы хотели бы существовать.
#! /usr/bin/env perl
use strict;
use warnings;
use Convert q(
type1, type2, type3
aax, ert, ddd
asx, eer, kkk
xkk, fff, lll
xxj, vtt, lle
);
С этим небольшим количеством фронта мы должны иметь возможность вызывать несколько удобных функций:
use Test::More;
diag type1_to_type2("aax");
diag type1_to_type3("asx");
diag type2_to_type3("fff");
diag type3_to_type1("lle");
Результаты должны соответствовать тому, что в таблице.
my @tests = (
[ qw/ type1_to_type2 aax ert / ],
[ qw/ type1_to_type3 asx kkk / ],
[ qw/ type2_to_type3 fff lll / ],
[ qw/ type3_to_type1 lle xxj / ],
[ qw/ type2_to_type1 ert aax / ],
);
my %sub_ok;
for (@tests) {
my($name,$from,$expect) = @$_;
my $sub;
{ no strict 'refs';
unless ($sub_ok{$name}++) {
ok defined &$name, "$name defined"
or next;
}
$sub = \&$name;
}
is $sub->($from), $expect, "$name($from)";
}
done_testing;
Чтобы это произошло, модуль Convert должен взять спецификацию и сгенерировать соответствующие подпрограммы.
Код в Convert.pm
начинается со знакомого шаблона.
package Convert;
use strict;
use warnings;
Согласно документации perlfunc , use Module LIST
эквивалентно
BEGIN { require Module; Module->import( LIST ); }
так что import
Конверта нужно взять таблицу в качестве одного из аргументов. (Первая, которую мы игнорируем, это строка "Convert"
, потому что import
вызывается как метод класса.)
sub import {
my(undef,$spec) = @_;
my %map;
my @names;
_populate(\%map, \@names, $spec);
my $pkg = caller;
foreach my $n1 (@names) {
foreach my $n2 (@names) {
next if $n1 eq $n2;
my $sub = sub {
my($preimage) = @_;
return unless exists $map{$n1}{$n2}{$preimage};
$map{$n1}{$n2}{$preimage};
};
my $name = $pkg . "::" . $n1 . "_to_" . $n2;
{ no strict 'refs'; *$name = $sub; }
}
}
}
С помощью _populate
, объясненного ниже, мы создаем хеш, ключи которого
- из-имя
- к имени
- прообраз
Например, первая строка данных в спецификации (aax, ert, ddd) соответствует шести (= 3 P 2 ) записей:
$map{type1}{type2}{aax} = "ert"
$map{type1}{type3}{aax} = "ddd"
$map{type2}{type1}{ert} = "aax"
$map{type2}{type3}{ert} = "ddd"
$map{type3}{type1}{ddd} = "aax"
$map{type3}{type2}{ddd} = "ert"
Имея хеш, мы затем устанавливаем подпрограммы ( например, , type1_to_type2
) в пространство имен вызывающего, где каждый ищет свой аргумент в соответствующем слоте и возвращает отображенное изображение , если оно существует.
В _populate
мы берем имена столбцов из первой непустой строки. Для оставшихся строк данных каждая пара значений входит в карту.
sub _populate {
my($map,$names,$spec) = @_;
my $line;
for (split /\s*\n\s*/, $spec) {
++$line;
my @fields = split /\s*,\s*/;
next unless @fields;
if (@$names) {
my %f;
@f{@$names} = @fields;
unless (@fields == @$names) {
warn "$0: line $line: number of fields and columns do not match!\n";
next;
}
foreach my $n1 (@$names) {
foreach my $n2 (@$names) {
next if $n1 eq $n2;
my($f1,$f2) = @f{$n1,$n2};
my $slot = \$map->{$n1}{$n2}{$f1};
warn "$0: line $line: discarding $$slot ($n1 -> $n2)\n"
if defined $$slot;
$$slot = $f2;
}
}
}
else {
@$names = @fields;
}
}
}
Не забудьте, чтобы модуль возвращал истинное значение в конце.
1;
Наконец, вывод!
# ert
# kkk
# lll
# xxj
ok 1 - type1_to_type2 defined
ok 2 - type1_to_type2(aax)
ok 3 - type1_to_type3 defined
ok 4 - type1_to_type3(asx)
ok 5 - type2_to_type3 defined
ok 6 - type2_to_type3(fff)
ok 7 - type3_to_type1 defined
ok 8 - type3_to_type1(lle)
ok 9 - type2_to_type1 defined
ok 10 - type2_to_type1(ert)
1..10