arthurg has asked for the wisdom of the Perl Monks concerning the following question:

CTable is a nifty Perl module that manipulates tables. It loads tables from files, operates on them in memory and then, if desired, saves them to disk.

It has a cool method calc that operates on each row of a table. calc works as follows (excerpted from CTable documentation):

## Calculate a new field's values based on two others $t->calc($Sub) ## Run $Sub for each row, with ## fields bound to local vars $t->calc($Sub, $Sel) ## Use these row nums $t->calc($Sub, undef, $Fields) ## Use only these fields my $Col = $t->calc($Sub) ## Gather return vals in vector o o o ## Example 2: Create empty column; fill fields 1 by 1 $t->col('PersonID'); $t->calc(sub{no strict 'vars'; $PersonID = "$Last$First"});

calc creates a variable for each column name (field) into main. However, it doesn't save and restore them. Here's the code, from the CTable download:

sub calc { ## We operate in package main for this subroutine so the local ## object and row number can be available to the caller's $Sub. ## The following local vars will be available to $Sub ## $_r ($main::_r) -- the row number in the entire table ## $_s ($main::_s) -- the row number in selection or $Recs ## $_t ($main::_t) -- the table object itself package main; use vars qw($_r $_s $_t); local ($_r, $_s, $_t); $_t = shift; my ($Sub, $Recs, $Fields) = @_; ## These optional params default to current field and current sel $Recs ||= $_t->selection(); $Fields ||= $_t->fieldlist_all(); ## Local copy of symbol table. Didn't seem to help. Odd. ## local %main::; ## We'll build a column of return values from $Sub if needed. my $WantVals = defined(wantarray); my $Vals = $_t->col_empty() if $WantVals; ## Call col() on each field in list to make sure it exists. foreach (@$Fields) {$_t->col($_)}; foreach $_s (0..$#$Recs) { $_r = $Recs->[$_s]; ## Bind $FieldName1, $FieldName2, (etc. for each field name in ## $Fields) point to address of the current value for that ## field in this record. no strict 'refs'; foreach my $F (@$Fields) {*{$F} = \ $_t->{$F}->[$_r]}; ## Now $Sub may refer to $_r, $_s, $_t, and ${any field name} ## Call $Sub and capture return values iff caller wants them ($WantVals ? $Vals->[$_r] = &$Sub() : &$Sub()); } ## Return scalar column ref unless return context is undef return($WantVals ? $Vals : ()); }

I want to save the values of the fields in @$Fields to a buffer before looping over the records, and then restore them after. But there's some confusing aliasing, so that restoring the buffer changes the values of fields in the last row of the table in this attempt:

## Don't overwrite fields in package main. my @saveFields; no strict 'refs'; package main; foreach my $F (@$Fields) { push @saveFields, ${$F}; } foreach $_s (0..$#$Recs) { $_r = $Recs->[$_s]; ## Bind $FieldName1, $FieldName2, (etc. for each field name in ## $Fields) point to address of the current value for that ## field in this record. no strict 'refs'; foreach my $F (@$Fields) {*{$F} = \ $_t->{$F}->[$_r]}; ## Now $Sub may refer to $_r, $_s, $_t, and ${any field name} ## Call $Sub and capture return values iff caller wants them ($WantVals ? $Vals->[$_r] = &$Sub() : &$Sub()); } ## Restore fields in main no strict 'refs'; package main; foreach my $F (@$Fields) { ${$F} = shift @saveFields; };

What's the right way to save and restore the values in package main? I attach test code.

Regards
Arthur
#!/usr/bin/perl ## Unit testing script for the Data::CTable module # Before `make install' is performed this script should be runnable wi +th # `make test'. After `make install' it should work as `perl test.pl' use strict; use Test; BEGIN { plan tests => 30, todo => [] } use Data::CTable; use Data::CTable::Script; use Data::CTable::Listing; use Data::CTable::ProgressLogger; ## Suppress automatic built-in progress during testing in order to ## acheive nice unobscured testing output when all goes well. ## (Will still test the progress() features directly with specific ## subclasses that gather the messages into memory instead of printing ## them out.) Data::CTable->progress_class(0); ## Note whether we're on Unix... my $OnUnix = ((-d "/") && (-d "/tmp")); ## Figure out some platform-specific path details... my ($Sep, $Up, $Cur) = @{Data::CTable->path_info()}{qw(sep up cur)}; my $TestDir = "test$Sep"; ## "test/" ## First test is to read a file that we'll make use of in many of the ## other tests. my $People1 = Data::CTable->new("${TestDir}people.tabs.txt") and ok(1) + or die; ## Now for tests 2..onward, run unit-tests of specific feature ## groups... ok(test_calc()); ## Calc method ok(test_calc_restore_main_vars()); ## check that calc restores Main va +rs 1; sub test_calc { my $People2 = Data::CTable->new("${TestDir}people.tabs.txt") or di +e; package FooBar; no strict 'vars'; package main; $People2->calc(sub{$First = "\U$First\E/@{[$_t->length()]}/$_r/$_s +"}); return(0) unless ("@{$People2->col('First')}" eq "CHRIS/3/0/0 MARCO/3/1/1 PEARL/3/2/2"); return(1); } sub test_calc_restore_main_vars { my $People2 = Data::CTable->new("${TestDir}people.tabs.txt") or di +e; no strict 'vars'; package main; # make sure that calc doesn't overwrite fields in Main # especially important when calc is called repeatedly on tables wi +th intersecting field names $First = 'TEST'; $People2->calc(sub{$Last}); package main; return(0) if ( $First ne 'TEST' ); return(1); }

Replies are listed 'Best First'.
Re: Symbol table manipulation problem
by ig (Vicar) on May 31, 2011 at 17:30 UTC

    It will probably help you to read perlref carefully, and maybe even http://blob.perl.org/tpc/1998/Perl_Language_and_Modules/Perl%20Illustrated/ and perlguts, though the latter is a bit difficult.

    The lines

    no strict 'refs'; foreach my $F (@$Fields) {*{$F} = \ $_t->{$F}->[$_r]};

    alias global variables in package main. After this, those variables are aliases to the elements of the table data structure. When you restore the saved values, you are updating the elements of the table data structure, via the aliases. This is why you see the last row of your table modified (and I am guessing not only the last row, but I didn't run your code).

    What you need to do (I think - but test carefully) is make aliases to the original scalars and restore them to the globs (again, see perlref for what this means).

    Here is an example of how you might do this:

    use strict; use warnings; use Devel::Peek; our ($a, $b, $c); my @fields = qw(a b); print "\nBefore setting a value for \$a:\n"; Dump($a); $a = 'Test'; $b = 'Unused'; print "\nAfter setting a value for \$a:\n"; Dump($a); $c = 'Clobbered'; print "\nAnd \$c is:\n"; Dump($c); print "\nGlobref \*a is:\n"; Dump(*a); { no strict 'refs'; my %tmp; $tmp{$_} = \${$_} for(@fields); print "\nAfter saving references \\\%tmp is:\n"; Dump(\%tmp); *{'a'} = \$c; print "\nAfter aliasing \$a to \$c, \$a is:\n"; Dump($a); print "\nAfter aliasing \$a to \$c, \*a is:\n"; Dump(*a); *{$_} = $tmp{$_} for(@fields); print "\nAfter restoring \*a, \*a is:\n"; Dump(*a); } print "\nFinally, \$a is:\n"; Dump($a);

    Which produces:

    Before setting a value for $a: SV = NULL(0x0) at 0x8ecfa60 REFCNT = 1 FLAGS = () After setting a value for $a: SV = PV(0x8ebe0c0) at 0x8ecfa60 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x8ed8618 "Test"\0 CUR = 4 LEN = 8 And $c is: SV = PV(0x8ebe0d0) at 0x8edf228 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x8ed4b88 "Clobbered"\0 CUR = 9 LEN = 12 Globref *a is: SV = PVGV(0x8ef2840) at 0x8ecfa50 REFCNT = 10 FLAGS = (MULTI) NAME = "a" NAMELEN = 1 GvSTASH = 0x8ec0048 "main" GP = 0x8ed8c28 SV = 0x8ecfa60 REFCNT = 1 IO = 0x0 FORM = 0x0 AV = 0x0 HV = 0x0 CV = 0x0 CVGEN = 0x0 LINE = 8 FILE = "./test.pl" FLAGS = 0x2 EGV = 0x8ecfa50 "a" After saving references \%tmp is: SV = IV(0x8ec0e14) at 0x8ec0e18 REFCNT = 1 FLAGS = (TEMP,ROK) RV = 0x8f01bf8 SV = PVHV(0x8ec5454) at 0x8f01bf8 REFCNT = 2 FLAGS = (PADMY,SHAREKEYS) ARRAY = 0x8ecf330 (0:6, 1:2) hash quality = 125.0% KEYS = 2 FILL = 2 MAX = 7 RITER = -1 EITER = 0x0 Elt "a" HASH = 0xca2e9442 SV = IV(0x8ec0e24) at 0x8ec0e28 REFCNT = 1 FLAGS = (ROK) RV = 0x8ecfa60 SV = PV(0x8ebe0c0) at 0x8ecfa60 REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x8ed8618 "Test"\0 CUR = 4 LEN = 8 Elt "b" HASH = 0xdb819b SV = IV(0x8ec0e44) at 0x8ec0e48 REFCNT = 1 FLAGS = (ROK) RV = 0x8ecfa90 SV = PV(0x8ebe0c8) at 0x8ecfa90 REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x8ee98b8 "Unused"\0 CUR = 6 LEN = 8 After aliasing $a to $c, $a is: SV = PV(0x8ebe0d0) at 0x8edf228 REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x8ed4b88 "Clobbered"\0 CUR = 9 LEN = 12 After aliasing $a to $c, *a is: SV = PVGV(0x8ef2840) at 0x8ecfa50 REFCNT = 10 FLAGS = (MULTI) NAME = "a" NAMELEN = 1 GvSTASH = 0x8ec0048 "main" GP = 0x8ed8c28 SV = 0x8edf228 REFCNT = 1 IO = 0x0 FORM = 0x0 AV = 0x0 HV = 0x0 CV = 0x0 CVGEN = 0x0 LINE = 8 FILE = "./test.pl" FLAGS = 0x2 EGV = 0x8ecfa50 "a" After restoring *a, *a is: SV = PVGV(0x8ef2840) at 0x8ecfa50 REFCNT = 10 FLAGS = (MULTI) NAME = "a" NAMELEN = 1 GvSTASH = 0x8ec0048 "main" GP = 0x8ed8c28 SV = 0x8ecfa60 REFCNT = 1 IO = 0x0 FORM = 0x0 AV = 0x0 HV = 0x0 CV = 0x0 CVGEN = 0x0 LINE = 8 FILE = "./test.pl" FLAGS = 0x2 EGV = 0x8ecfa50 "a" Finally, $a is: SV = PV(0x8ebe0c0) at 0x8ecfa60 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x8ed8618 "Test"\0 CUR = 4 LEN = 8

    Have a read (and a play with) Devel::Peek, which I recommend to you as an excellent tool to test your understanding of globs and references, then work your way through the output of the test program. You will see that in the end $a refers not only to a string with the same content, but to the actual original string. This would be important if the originals had magic (tied variables, for example).