The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Simple::Filter::Macro - Perl extension for expanding source code inline in a script or module.

SYNOPSIS

Use in script e.g. TestScript.pl or in module e.g. TestModules.pm

  use ExamplePackage::ExampleModules;

and in package e.g ExamplePackage in module e.g. ExampleModules.pm

  package ExamplePackage::ExampleModules;
  # Set the VERSION number.
  $VERSION = '0.01';
  # Use the magic module.
  use Simple::Filter::Macro; # <-- The magic is found here.
  # The lines below will be expanded into the caller's code.
  use strict;
  use warnings;
  use diagnostics;
  use sigtrap; 
  use ExamplePackage::ReadFile;
  use ExamplePackage::ModifyFile;
  use ExamplePackage::WriteFile;
  use re 'debug'
  no utf8;
  # Add as much use statements as you like.
  # Package terminator 1; will not be written to caller's code.
  1;   

DESCRIPTION

The module is expanding source code inline in a script or a module. This statement is valid, when the use statement is reachable while compilation. It will not work in e.g. a if statement or code block.

It is common practice to load modules using the use statement while compilation. Import of a subroutine imports of modules from e.g. ExamplePackage takes effect in that module but not in the calling script. The same thing is valid for Perl pragmas. They take effect in the script or in the module not in the calling script or calling module.

It's a kind of magic when it is possible to change the source code while compiling as it can be shown here. The package defined module as well as the script of any user e.g. TestScript.pl creates a compiled file with the file extension .plc. This is in our case TestScript.plc.

Line numbers in error messages as well as warning messages are unaffected by this module. They will still point to the correct file names and line numbers.

EXAMPLE

Original script and expanded script

A test script e.g. TestScript.pl which can look like

  #!/usr/bin/perl

  # Load the magic module.
  use ExamplePackage::ExampleModules;

  # Subroutine modfile()
  sub modfile {
      my $filename = "textfile.txt";
      my $old_text = read_file($filename);
      my $new_text = modify_file($old_text);
      my $retcode = write_file($filename, $new_text);
      return $retcode;
  };

  # Modify file.
  my $return_code = modfile();

will expand using a module e.g. ExampleModules.pm to

  # Generated by ExamplePackage::ExampleModules 0.01 (Module::Compile 0.38) - do not edit!
  ################((( 32-bit Checksum Validator III )))################
  #line 1
  BEGIN { use 5.006; local (*F, $/); ($F = __FILE__) =~ s!c$!!; open(F)
  or die "Cannot open $F: $!"; binmode(F, ':crlf'); if (unpack('%32N*',
  $F=readline(*F)) != 0x163B501C) { use Filter::Util::Call; my $f = $F;
  filter_add(sub { filter_del(); 1 while &filter_read; $_ = $f; 1; })}}
  #line 1
  # 8667e12ea286715fb3e93c82b3356305890112f1

  # 62f18508099e8285ab8f0df2ec70f446214f5586
  #line 5 /usr/local/share/perl/5.30.0/ExamplePackage/Modules.pm
  # cde5525f1abc2b435ac70f077ad2c766229a4804
  use ExamplePackage::Modules;
  use strict;
  use warnings;
  use diagnostics;
  use sigtrap; 
  use ExamplePackage::ReadFile;
  use ExamplePackage::ModifyFile;
  use ExamplePackage::WriteFile;
  use re 'debug'
  no utf8;
  # 75c36e1296f4d49d9f564fc70837f9a469b9f5b6

  #line 3 TestScript.pl

  # 150f53f5454d382c5edbd26cb3a5658dfa7cb24b
  sub modfile {
      my $filename = "textfile.txt";
      my $old_text = read_file($filename);
      my $new_text = modify_file($old_text);
      my $retcode = write_file($filename, $new_text);
      return $retcode;
  };

  # 9e5e807c25ba1b752ea5c8754a19572bec9724fe
  my $return_code = modfile();

Explanation of example

The source code is modified by the compiling procedure as follows. In Perl there is a compilation and a running phase. Keep in mind, that this is not a compiling as it is usual known. The changes during compilation phase is as follows. The existing Shebang in the script is replaced by a BEGIN block. Then the content of the module is inserted as it was intended. After this the remaining content of the script itself can be found.

The BEGIN block is enclosed between a comment line marked with a beginning #line 1 and an ending #line 1. As one can see the text of the comment lines from the original script become a comment line consisting of a hashed value. The inserted module content starts with the marker #line 5, which is the line of the use statement in the module. The start of the script body is marked with #line 3 which is the line of the use statement in the script.

Extension of the module procedure

The module Simple::Filter::MacroLite removes all comment lines and comments from the module before this content is inserted in the new script content. Then the comments from the original script content are stripped. It remains a new script with only comments from the used compiler modules.

By using the method SanitiseCompiled from the module Simple::Filter::SanitiseCompiled the previous code can be cleaned up in a way that comments as well as the BEGIN block are removed. The remaining code is as compact as possible and runable.

PROGRAMME TECHNICAL BACKGROUND

A outer filter is applied. In the outer filter section, the content of the given module is modified in a way, that comments in general are removed and that the module terminator 1; is stripped. The content for the export is beginning after the use statement.

Then a inner filter is applied. In the inner filter section the module content is concatenated to the content of the given script. Both are attached and written to the file with the head created applying the filtering procedure.

To get line numbers for the marker and get module and file names the Perl function caller() is used. For example one can write my ($package, $filename, $line) = caller($i) where $i is the level of interest. The elements of the array can be reached as it is known from arrays by e.g. caller(0)[0] which results the package name of level 0. caller(5) returns the data from the module and caller(6) returns the data from the script.

MODULE METHOD

No methods are defined within the module.

MODULE EXPORT

No methods are exported from the module.

MOTIVATION

I was looking for a module that would allow me to merge a number of use pragma declarations in the module and replace them with a single use pragma declaration. The module Filter::Macro that I found contains the right approaches for this, but is not executable under Perl v5.30 how I found out. After analysing the source code, I decided to create a new module based on the aforementioned approaches. The problem in the source code As I figured out is the use of the Perl command quotemeta. Since Perl v5.16 the use of quotemeta is obsolet as I suppose. The first attempts with basic changes in the source code were not without errors. In the meantime, the Perl code works as expected. The first two methods of this module are the result of my efforts.

KNOWN ISSUES

Running a script using Simple::Filter::Macro like perl test_script.pl might create an error message on the second run. If the script is running using ./test_script.pl no error occurs. Nevertheless running perl test_script.plc seems to be all the time working. This has to checked.

SEE ALSO

Perldoc function caller

Simple::Filter::MacroLite

Simple::Filter::SanitiseCompiled

Filter::Macro

Filter::Simple::Compile

Filter::Util::CALL

Filter::Include

ACKNOWLEDGMENT

Special thanks go to Audrey Tang, who wrote the Filter::Macro module, which contains the crucial approaches to implementing the present package and its modules and methods.

AUTHOR

Dr. Peter Netz, <ztenretep@cpan.org>

COPYRIGHT AND LICENSE

Copyright (C) 2022 by Dr. Peter Netz

This library is free software; you can redistribute it and/or modify it under the same terms of The MIT License. For more details, see the full text of the license in the attached file LICENSE in the main module folder. This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.