I've been working on translating a Perl code base into Fexl. The code is very detailed with many thousands of reproducible results which must be maintained exactly. The question is how do I go about doing that? I certainly don't want to start entirely from scratch.
So I devised a systematic way to translate all the old Perl code, guaranteeing that I will eventually reach and test every piece of the code — or, if I never reach a piece of old code, I'll know that it's unreachable and I can delete it.
Here is an example to illustrate the technique. Let's say my top level Perl program is called "my_program":
my_program:
#!/usr/bin/env perl use strict; use my_module; my $x1 = "ABC" my $x2 = "DEF" my_module::do_thing_1($x1); my_module::do_thing_2($x1,$x2);
Note that it uses a separate file "my_module.pm" defined here:
my_module.pm:
sub do_thing_1
{
my $x1 = shift;
# ... do something with $x1
}
sub do_thing_2
{
my $x1 = shift;
my $x2 = shift;
# ... do something with $x1 and $x2
}
return 1;
I create a file called "perl.fxl" which defines two routines perl_do and perl_defer:
perl.fxl:
\perl_do=
(\text
\msg=(slice text 0 80)
trace "==== TODO perl_do:"
trace msg
die
)
\perl_defer=
(\text
)
define "perl_do" perl_do
define "perl_defer" perl_defer
The perl_do routine takes a Perl program text as an input, shows you the first 80 characters of the text, and dies. This is the routine you use to guide you into translating every piece of Perl code that is actually called.
The perl_defer routine simply ignores the text. This is useful for blocks of Perl code that you want to preserve for a while as a reference, but aren't crucial.
Next I convert my_program into a Fexl program which calls perl_do on the full text of the Perl code:
my_program:
#!/usr/bin/env fexl use "perl.fxl" perl_do ~~ use strict; use my_module; my $x1 = "ABC" my $x2 = "DEF" my_module::do_thing_1($x1); my_module::do_thing_2($x1,$x2); ~~
When I run that program, it dies after showing me some of the Perl code I need to translate. The perl_do routine serves as a placeholder for the next bit of translation work I need to do.
Note that if I could write a full Perl interpreter as a Fexl routine called "perl_do", then I wouldn't even need to translate the code. Just kidding, the whole point is to eliminate the old Perl code entirely.
The job of translation is to eliminate all calls to perl_do by manually translating all the Perl code in the text block. I do this pretty much one line at a time, and there are specific techniques I use for handling loops, conditionals, and Perl data structures which I won't get into here.
The first thing I do is rename "my_module.pm" as "my_module.fxl" and modify it like this:
my_module.fxl:
perl_do
~~
sub do_thing_1
{
my $x1 = shift;
# ... do something with $x1
}
sub do_thing_2
{
my $x1 = shift;
my $x2 = shift;
# ... do something with $x1 and $x2
}
return 1;
~~
Then I make each subroutine into a separate call to perl_do, and define the fully qualified names for export:
my_module.fxl:
\do_thing_1=
(\x1
perl_do
~~
# TODO sub do_thing_1
# ... do something with $x1
~~
)
\do_thing_2=
(\x1\x2
perl_do
~~
# TODO sub do_thing_2
# ... do something with $x1 and $x2
~~
)
define "my_module::do_thing_1" do_thing_1
define "my_module::do_thing_2" do_thing_2
Then the main program translates like this:
my_program:
#!/usr/bin/env fexl use "perl.fxl" use "my_module.fxl"; \x1="ABC" \x2="DEF" my_module::do_thing_1 x1 my_module::do_thing_2 x1 x2
When I run it, the first thing it will do is call perl_do on the definition of do_thing_1, and the code dies. So I translate that routine line by line.
Then the same thing happens for do_thing_2, and I translate that line by line.
Voila, I'm done. No more calls to perl_do or perl_defer. I can now delete the "perl.fxl" file and the line that uses it.
If you translate all your code and all your test cases pass, but you still have some calls to perl_do, then that might be unreachable code that you can delete. Or you can rig up a test case that reaches it.
As I mentioned, there are specific techniques I use for handling loops, conditionals, and Perl data structures including lists and hashes. These are well-established, but I'm not taking the time to describe them here yet.