Search code examples
regexperlprintingeval

Conflict between eval and print in Perl


I'm creating a subroutine in my Perl script and can evaluate it nicely and it works. I would also like to print the content of the subroutine for debugging purposes. However, the subroutine, which is constructed in code, is really huge and is hard to read and understand it by simply printing it. I would like to find a way to be able to print it in a semi-indented way.

Here is piece of code generation:

$code .= "if (\$ct=~/^\\s*\$/x  || \$Im < \$Ix) {push(\@min, $b); push(\@max, $b);} if (\$Im > \$Ix) {push(\@min, $a); push(\@max, $a);}"

And I would like to print it something like this:

if (\$ct=~/^\\s*\$/x  || \$Im < \$Ix)
    {push(\@min, $b); push(\@max, $b);}
if (\$Im > \$Ix)
    {push(\@min, $a); push(\@max, $a);}

I know that the straight way to do this is to write another script to parse it and put some \n and \t into the appropriate places in code and then print it. Is there a smarter way to that?

Like putting \n somewhere in code without subverting evaling it (i.e., something visible to print but invisible to eval).

NOTE: I have a lot of regexes in my subroutine and I want to avoid running them every time. That's why I need to have the code stored in a string and then eval it to increase my script performance.


Solution

  • Ignoring the reasons why you may have code in a string...

    Perl::Tidy is the tool that you need to reformat your code.

    Normally, one uses this tool via the command line on source files. However, I've hacked together a little script that will output your code string to a temporary file so that it can be reformatted. Note, this currently assumes that your code is well-formed and that there aren't any obvious syntax errors in it as formatting broken code is outside the purview of this tool.

    use strict;
    use warnings;
    use autodie;
    
    my $code = <<'END_CODE';
    # It hurts to write ugly code, but I'll see what I can do
    sub { my @vars = @_;
     my $count = scalar(@vars); print "Hello World.  Vars = $count"; return; }
    END_CODE
    
    print pretty_code($code);
    
    sub pretty_code {
        my $code = shift;
    
        require File::Temp;
        require Perl::Tidy;
    
        my ($fh, $filename) = File::Temp::tempfile();
        print $fh $code;
        close $fh;
    
        Perl::Tidy::perltidy(
            source => $filename,
        );
    
        my $output = do {
            open my $fh, '<', "$filename.tdy";
            local $/;
            <$fh>
        };
    
        unlink $_ for ($filename, "$filename.tdy");
    
        return $output;
    }
    

    Outputs:

    # It hurts to write ugly code, but I'll see what I can do
    sub {
        my @vars  = @_;
        my $count = scalar(@vars);
        print "Hello World.  Vars = $count";
        return;
      }
    

    Update

    There is no need to use a temporary file, particularly as Perl::Tidy accumulates the tidied code in memory before dumping it to disk. If you prefer, this program does the same thing without writing the result to disk.

    use strict;
    use warnings;
    
    use Perl::Tidy 'perltidy';
    
    my $code = <<'END_CODE';
    # It hurts to write ugly code, but I'll see what I can do
    sub { my @vars = @_; my $count = scalar(@vars); print "Hello World.  Vars = $count"; return; }
    END_CODE
    
    print pretty_code($code);
    
    sub pretty_code {
       my ($code) = @_;
       my $pretty;
    
       perltidy(
          source      => \$code,
          destination => \$pretty,
       );
    
       $pretty;
    }
    

    output

    # It hurts to write ugly code, but I'll see what I can do
    sub {
      my @vars  = @_;
      my $count = scalar(@vars);
      print "Hello World.  Vars = $count";
      return;
        }
    

    I'm not clear at present why the closing brace is indented further, but I am certain that the result is better than the original.