Write an anonymous sub in Perl to a file for later use
Asked Answered
M

2

7

I have a Perl program that generates parsing rules as subs from an input file. The subs are anonymously defined an put into a hash. Now, I want to export that hash, with all the subs and then load them again later to use with a different program.

How do I go about doing this? Is there some way to extract the code of each sub, or can I copy the block of memory that the hash exists in and then cast it as a hash when I load it again later?

Thanks in advance.

Mariko answered 4/2, 2010 at 17:8 Comment(3)
Instead of saving the actual compiled subs, you could save the text of the subs, and then re-eval them into coderefs in the new process.Carboxylase
Yeah, I thought of that. But I would like to be able to save the compiled subs for aesthetic reasons. :-pMariko
All techniques I know of use B::Deparse to translate the code reference back into Perl source code. This process may not give you back the same source code that you started with. I do not think this will buy you much over simply saving the actual original text.Snare
C
2

From the "Code References" section of the Storable documentation (with added emphasis):

Since Storable version 2.05, CODE references may be serialized with the help of B::Deparse. To enable this feature, set $Storable::Deparse to a true value. To enable deserialization, $Storable::Eval should be set to a true value. Be aware that deserialization is done through eval, which is dangerous if the Storable file contains malicious data.

In the demo below, a child process creates the hash of anonymous subs. Then the parent—in an entirely separate process and address space, so it can't see %dispatch—reads the output from freeze in the same way that you might from a file on disk.

#! /usr/bin/perl

use warnings;
use strict;

use Storable qw/ freeze thaw /;

my $pid = open my $fh, "-|";
die "$0: fork: $!" unless defined $pid;

if ($pid == 0) {
  # child process
  my %dispatch = (
    foo => sub { print "Yo!\n" },
    bar => sub { print "Hi!\n" },
    baz => sub { print "Holla!\n" },
  );

  local $Storable::Deparse = 1 || $Storable::Deparse;
  binmode STDOUT, ":bytes";
  print freeze \%dispatch;
  exit 0;
}
else {
  # parent process
  local $/;
  binmode $fh, ":bytes";
  my $frozen = <$fh>;

  local $Storable::Eval = 1 || $Storable::Eval;
  my $d = thaw $frozen;
  $d->{$_}() for keys %$d;
}

Output:

Hi!
Holla!
Yo!
Carleton answered 4/2, 2010 at 21:37 Comment(2)
and I can simply write $frozen to a text file? and don't worry, I will sanitize my inputs. I learned this from XKCD :DMariko
@Mechko Yes, in your code, you'd write the output to a file on disk and then read it in later. I changed the demo to resemble what both sides of your pipeline might look like.Carleton
W
4

KiokuDB can handle storing code references in addition to other Perl types. So can the various YAML modules, Data::Dump::Streamer, and even Data::Dumper.

Worth answered 4/2, 2010 at 17:17 Comment(0)
C
2

From the "Code References" section of the Storable documentation (with added emphasis):

Since Storable version 2.05, CODE references may be serialized with the help of B::Deparse. To enable this feature, set $Storable::Deparse to a true value. To enable deserialization, $Storable::Eval should be set to a true value. Be aware that deserialization is done through eval, which is dangerous if the Storable file contains malicious data.

In the demo below, a child process creates the hash of anonymous subs. Then the parent—in an entirely separate process and address space, so it can't see %dispatch—reads the output from freeze in the same way that you might from a file on disk.

#! /usr/bin/perl

use warnings;
use strict;

use Storable qw/ freeze thaw /;

my $pid = open my $fh, "-|";
die "$0: fork: $!" unless defined $pid;

if ($pid == 0) {
  # child process
  my %dispatch = (
    foo => sub { print "Yo!\n" },
    bar => sub { print "Hi!\n" },
    baz => sub { print "Holla!\n" },
  );

  local $Storable::Deparse = 1 || $Storable::Deparse;
  binmode STDOUT, ":bytes";
  print freeze \%dispatch;
  exit 0;
}
else {
  # parent process
  local $/;
  binmode $fh, ":bytes";
  my $frozen = <$fh>;

  local $Storable::Eval = 1 || $Storable::Eval;
  my $d = thaw $frozen;
  $d->{$_}() for keys %$d;
}

Output:

Hi!
Holla!
Yo!
Carleton answered 4/2, 2010 at 21:37 Comment(2)
and I can simply write $frozen to a text file? and don't worry, I will sanitize my inputs. I learned this from XKCD :DMariko
@Mechko Yes, in your code, you'd write the output to a file on disk and then read it in later. I changed the demo to resemble what both sides of your pipeline might look like.Carleton

© 2022 - 2024 — McMap. All rights reserved.