Finding flow while coding is one of the joys of programming.
Encountering simple syntactic bugs, however, can sometimes interrupt flow. A single missing semicolon, for example, can result in a “WAT!?” followed by a “DOH!”
Perl 6 helps you around the code -> run -> fix cycle by identifying the cause and location of a bug and often suggesting a solution. Take this program:
say "hello"
say "world";
When you run it, Perl 6 will suggest what’s wrong, where the problem is, and what to do next …
===SORRY!=== Error while compiling /home/nige/hello-world.pl
Two terms in a row across lines (missing semicolon or comma?)
at /home/nige/hello-world.pl:6
------> say "hello"⏏
That helps to keep things flowing.
Normally, at this point, it’s off to your $EDITOR to manually add the semicolon and the cycle repeats.
What if Perl 6 could suggest a fix and apply it for you?
Introducing perl6fix
Here is the beginnings of a command-line utility called, perl6fix, that takes the hint from Perl 6 and tries to speed up the code -> run -> fix cycle by applying the fix for you.

Let’s look at the code.
It needs a handle on bug descriptions found in Perl 6 output.
class Bug {
has Int $.line-number;
has SourceFile $.source-file;
has Str $.description;
has Str $.pre-context;
}
And a way to describe fixes:
class Fix {
has $.prompt;
has $.pattern;
has $.action;
method apply ($bug) {
$!action($bug);
}
method applies-to ($bug) {
return True without $.pattern;
return $bug.description ~~ $.pattern;
}
}
And a way to update the source file:
class SourceFile is IO::Path {
has @!content-lines = self.IO.slurp.lines;
method append-to-first-matching-line ($append-char, $text-to-match) {
return unless my $first-matching-index = @!content-lines.first(rx/$text-to-match/, :k);
@!content-lines[$first-matching-index] ~= $append-char;
self.save;
}
method swap-characters-on-line ($from-chars, $to-chars, $line-number) {
@!content-lines[$line-number - 1].subst-mutate(/$from-chars/, $to-chars);
self.save;
}
method save {
self.IO.spurt(@!content-lines.join("\n"));
}
}
Here is just some of the fixes I encountered while writing this program:
my @fixes = (
Fix.new(
prompt => 'add semicolon',
pattern => rx/'missing semicolon or comma?'/,
action => sub ($bug) {
$bug.source-file.append-to-first-matching-line(';', $bug.pre-context);
}
),
Fix.new(
prompt => 'add comma',
pattern => rx/'missing semicolon or comma?'/,
action => sub ($bug) {
$bug.source-file.append-to-first-matching-line(',', $bug.pre-context);
}
),
Fix.new(
prompt => 'convert . to ~',
pattern => rx/'Unsupported use of . to concatenate strings; in Perl 6 please use ~'/,
action => sub ($bug) {
$bug.source-file.swap-characters-on-line('.', '~', $bug.line-number);
}
),
Fix.new(
prompt => 'convert qr to rx',
pattern => rx/'Unsupported use of qr for regex quoting; in Perl 6 please use rx'/,
action => sub ($bug) {
$bug.source-file.swap-characters-on-line('qr', 'rx', $bug.line-number);
}
),
# ADD YOUR OWN FIXES HERE
);
There’s many more potential fixes (I’m just starting).
The perl6fix script wraps perl6, captures STDERR (if there is any), and then looks for a bug report in the output:
sub find-bug ($perl6-command) {
return unless my $error-output = capture-stderr($perl6-command);
# show the error
note($error-output);
# re-run the command again - this time grabbing a JSON version of the bug
# set RAKUDO_EXCEPTIONS_HANDLER env var to JSON
return unless my $error-as-json = capture-stderr('RAKUDO_EXCEPTIONS_HANDLER=JSON ' ~ $perl6-command);
return unless my $bug-description = from-json($error-as-json);
# just handle these exception types to start with
for 'X::Syntax::Confused', 'X::Obsolete' -> $bug-type {
next unless my $bug = $bug-description{$bug-type};
return Bug.new(
description => $bug<message>,
source-file => SourceFile.new($bug<filename>),
line-number => $bug<line>,
pre-context => $bug<pre>
);
}
}
The next step is to see if there are any fixes for this type of bug:
sub fix-bugs ($perl6-command-line) {
my $bug = find-bug($perl6-command-line);
unless $bug {
say 'No bugs found to fix.';
exit;
}
# find a potential list of fixes for this type of bug
my @found-fixes = @fixes.grep(*.applies-to($bug));
say $bug.description;
say $bug.source-file.path ~ ' ' ~ $bug.line-number;
say 'Suggested fixes found: ';
my $fix-count = 0;
for @found-fixes -> $fix {
$fix-count++;
my $option = ($fix-count == 1)
?? "*[1] " ~ $fix.prompt
!! " [$fix-count] " ~ $fix.prompt;
say ' ' ~ $option;
}
my $answer = prompt('apply fix [1]? ') || 1;
my $fix = @found-fixes[$answer - 1];
$fix.apply($bug);
# look for more bugs! until we're done
fix-bugs($perl6-command-line);
}
With the help of a shell alias you can even run it to fix the previous perl6 command. Like so:

Just add an alias to your bash or zsh profile. For example:
alias fix='/home/nige/perl6/perl6fix prev $(fc -ln -1)'
Now it’s your turn
As you can see this is just a start.
You’re welcome to take the full script and evolve your own set of automatic Perl 6 fixes.
You could even adapt the script to apply fixes for Perl 5 or other languages? What about a version using grammars? Or a macro-powered version integrated directly into Perl 6?
Well maybe that last one is something to look forward to next Christmas!
Hope you have a happy Christmas and a flowing, Perl 6 powered 2017!