use strict;
package pcg;
BEGIN {
use File::Basename;
push(@INC, dirname($0));
}
my $script_results;
{
use IO::String;
my $buffer;
my $old_fh;
my $scriptlet_begin;
sub before_script {
$buffer = IO::String->new();
$old_fh = select($buffer);
}
sub before_scriptlet {
my $output = $buffer->string_ref();
$pcg::scriplet_begin = length($$output);
print "\n";
}
sub after_scriptlet {
my $output = $buffer->string_ref();
print "\n" unless (length($$output) > $pcg::scriplet_begin &&
$$output =~ /\n$/);
}
sub after_script {
select($old_fh);
my $output = $buffer->string_ref();
$pcg::script_results = $$output;
}
}
sub tokenize {
my $contents = shift;
my @tokens = ();
while ($contents =~ /(<<<pcg)(.*?)(>>>[^\n]*)/s) {
push(@tokens, ['Literally' => $` . $1 . $2 . $3]);
push(@tokens, ['Perl' => $2]);
$contents = $';
if ($contents =~ /([^\n]*<<<\/pcg>>>)/s) {
push(@tokens, ['Skip' => ($` =~ y/\n/\n/)]);
push(@tokens, ['Literally' => $1]);
$contents = $';
} else {
die('<<<pcg>>> unclosed');
}
}
push(@tokens, ['Literally' => $contents]);
return @tokens;
}
sub chopped_dump {
my $val = shift;
use Data::Dumper;
$Data::Dumper::Terse = 1;
$Data::Dumper::Useqq = 1;
$val = Dumper($val);
chop($val);
return $val;
}
sub scriptize {
my @tokens = @_;
my $script = 'pcg::before_script; $_ = ' .
chopped_dump(join('', map {
($_->[0] eq 'Literally' ? $_->[1] :
$_->[0] eq 'Skip' ? "\n" : '')
} @tokens)) . ';';
my $newlines = 0;
foreach my $token (@tokens) {
{
'Literally' => sub {
my $literally = shift;
$newlines += ($literally =~ y/\n/\n/);
my $tmp = pcg::chopped_dump($literally);
$script .= "print $tmp;";
},
'Perl' => sub {
my $perl = shift;
$newlines -= ($perl =~ y/\n/\n/);
$script .= ("\n" x $newlines) .
'pcg::before_scriptlet; ' . $perl . ' pcg::after_scriptlet;';
$newlines = 0;
},
'Skip' => sub {
$newlines += shift;
}
}->{$token->[0]}->($token->[1]);
}
$script .= ' pcg::after_script;';
return $script;
}
sub read_unprocessed {
my $filename = shift;
my $file_handle;
open($file_handle, $filename) || die($!);
my $contents = join('', <$file_handle>);
my $script = pcg::scriptize(pcg::tokenize($contents));
close($file_handle);
return $script;
}
sub write_processed {
my $filename = shift;
my $file_handle;
open($file_handle, '>' . $filename) || die($!);
print $file_handle $pcg::script_results;
close($file_handle);
}
package main;
@ARGV == 1 || die('usage: ' . $0 . ' <filename>
* Perl code goes between:
* "<<<pcg>>>", stuff, newline
* More stuff, which will be overwritten
* Newline (possibly shared), stuff, "<<</pcg>>>"
* $_ contains the unprocessed script (unless you overwrite it)
* Elisp code to process buffers exists in pcg.el
');
print STDERR 'PCG: ' . $ARGV[0] . "\n";
local $_ = pcg::read_unprocessed($ARGV[0]);
eval($_) || die($@);
pcg::write_processed($ARGV[0]);