Search code examples
perlsubroutinedata-transfer

Call a subroutine defined as a variable


I am working on a program which uses different subroutines in separate files.

There are three parts

  • A text file with the name of the subroutine

  • A Perl program with the subroutine

  • The main program which extracts the name of the subroutine and launches it

The subroutine takes its data from a text file.

I need the user to choose the text file, the program then extracts the name of the subroutine.

The text file contains

cycle.name=cycle01

Here is the main program :

# !/usr/bin/perl -w

use strict;
use warnings;

use cycle01;

my $nb_cycle = 10;

# The user chooses a text file

print STDERR "\nfilename: ";

chomp($filename = <STDIN>);

# Extract the name of the cycle 

open (my $fh, "<", "$filename.txt") or die "cannot open $filename";

while ( <$fh> ) {

    if ( /cycle\.name/ ) {

        (undef, $cycleToUse) = split /\s*=\s*/;
    }
}

# I then try to launch the subroutine by passing variables.
# This fails because the subroutine is defined as a variable.

$cycleToUse($filename, $nb_cycle);

And here is the subroutine in another file

# !/usr/bin/perl

package cycle01;

use strict;
use warnings;

sub cycle01 {

    # Get the total number of arguments passed
    my ($filename, $nb_cycle) = @_;
    print "$filename, $nb_cycle";

Solution

  • Your code doesn't compile, because in the final call, you have mistyped the name of $nb_cycle. It's helpful if you post code that actually runs :-)

    Traditionally, Perl module names start with a capital letter, so you might want to rename your package to Cycle01.

    The quick and dirty way to do this is to use the string version of eval. But evaluating an arbitrary string containing code is dangerous, so I'm not going to show you that. The best way is to use a dispatch table - basically a hash where the keys are valid subroutine names and the values are references to the subroutines themselves. The best place to add this is in the Cycle01.pm file:

    our %subs = (
      cycle01 => \&cycle01,
    );
    

    Then, the end of your program becomes:

    if (exists $Cycle01::subs{$cycleToUse}) {
      $Cycle01::subs{$cycleToUse}->($filename, $nb_cycle);
    } else {
      die "$cycleToUse is not a valid subroutine name";
    }
    

    (Note that you'll also need to chomp() the lines as you read them in your while loop.)