body:
+all_pages
front_matter:
s/::PAGENUM::/roman_numeral($page_number)/eg;
s/::COPYRIGHT::/Copyright 2004 blah, blah/g;
s/::PRINTING::/1st printing, Blah Blah Press/g;
+all_pages
index:
+all_pages
all_pages:
s/::PAGENUM::/$page_number/eg;
####
my $page_engine = make_regex_engine_from_spec( $spec_fh );
my $page_number = 1;
for my $page (@book_pages) {
print $page_engine->( @$page{'content','page_type'} ), "\n";
$page_number++;
}
##
##
sub make_regex_engine_from_spec
{
my $fh = shift; # filehandle contains spec
my %sections;
my $label;
# read in spec
while (<$fh>) {
chomp;
next unless /\S/; # skip blanks
if (/^(\w+):/) {
$label = $1;
}
else {
die "syntax error: need a section label\n"
unless $label;
push @{$sections{$label}}, $_;
}
}
# compile spec into code
my $interpret = sub {
local $_ = shift;
if ( /^ \s* \+ (\w+) /x ) {
if ($sections{$1}) {
return '$sections{'.$1.'}->();';
}
die "there is no section named '$1'";
}
return $_;
};
while (($label, my $section) = each %sections) {
my $generated_code =
join "\n", 'sub {',
(map $interpret->($_), @$section), "}\n";
$sections{$label} = eval $generated_code
or die "couldn't eval section $label: $@";
}
# return processor engine that embodies compiled spec
return sub {
# args: page content, page type
(local $_, my $page_type) = @_;
my $processor = $sections{$page_type};
$processor->() if $processor;
return $_;
}
}
##
##
my @book_pages = (
{ page_type => 'front_matter',
content => "This is the copyright page (::PAGENUM::).\n"
. "::COPYRIGHT::\n"
. "::PRINTING::\n" },
{ page_type => 'body',
content => "This is a body page (::PAGENUM::).\n" },
{ page_type => 'index',
content => "This an index page (::PAGENUM::).\n" },
);
##
##
This is the copyright page (i).
Copyright 2004 blah, blah
1st printing, Blah Blah Press
This is a body page (2).
This an index page (3).
##
##
#!/usr/bin/perl
use warnings;
use strict;
# Tom Moertel 2004-10-11
# here are some fake pages
my @book_pages = (
{ page_type => 'front_matter',
content => "This is the copyright page (::PAGENUM::).\n"
. "::COPYRIGHT::\n"
. "::PRINTING::\n" },
{ page_type => 'body',
content => "This is a body page (::PAGENUM::).\n" },
{ page_type => 'index',
content => "This an index page (::PAGENUM::).\n" },
);
# sample code that shows how to build
# an engine from spec and use it
my $page_number = 1;
my $page_engine = make_regex_engine_from_spec(\*DATA);
for my $page (@book_pages) {
print $page_engine->(@$page{'content','page_type'}), "\n";
$page_number++;
}
sub roman_numeral {
my $index = shift;
return (qw/0 i ii iii iv v ... /)[$index] || "?";
}
# the following code generates the worker code from the spec
sub make_regex_engine_from_spec
{
my $fh = shift;
my %sections;
my $label;
# read in spec
while (<$fh>) {
chomp;
next unless /\S/; # skip blanks
if (/^(\w+):/) {
$label = $1;
}
else {
die "syntax error: need a section label\n"
unless $label;
push @{$sections{$label}}, $_;
}
}
# compile spec into code
my $interpret = sub {
local $_ = shift;
if ( /^ \s* \+ (\w+) /x ) {
if ($sections{$1}) {
return '$sections{'.$1.'}->();';
}
die "there is no section named '$1'";
}
return $_;
};
while (($label, my $section) = each %sections) {
my $generated_code =
join "\n", 'sub {',
(map $interpret->($_), @$section), "}\n";
# uncomment below line to see generated code
# print STDERR "$label => $generated_code\n";
$sections{$label} = eval $generated_code
or die "couldn't eval section $label: $@";
}
# return processor engine that embodies compiled spec
return sub {
# args: page content, page type
(local $_, my $page_type) = @_;
my $processor = $sections{$page_type};
$processor->() if $processor;
return $_;
}
}
# our spec follows
__DATA__
body:
+all_pages
front_matter:
s/::PAGENUM::/roman_numeral($page_number)/eg;
s/::COPYRIGHT::/Copyright 2004 blah, blah/g;
s/::PRINTING::/1st printing, Blah Blah Press/g;
+all_pages
index:
+all_pages
all_pages:
s/::PAGENUM::/$page_number/eg;