our
$VERSION
=
'0.38'
;
my
$filtered
= {};
my
$digest_map
= {};
sub
pmc_caller_stack_frame { 0 };
sub
pmc_is_compiler_module { 1 };
sub
new {
return
bless
{},
shift
;
}
sub
pmc_use_means_no { 0 }
sub
pmc_use_means_now { 0 }
sub
import
{
my
(
$class
) =
@_
;
return
if
$class
->pmc_use_means_no;
goto
&{
$class
->can(
'pmc_import'
)};
}
sub
unimport {
my
(
$class
) =
@_
;
return
unless
$class
->pmc_use_means_no;
goto
&{
$class
->can(
'pmc_import'
)};
}
sub
pmc_import {
my
(
$class
,
@args
) =
@_
;
$class
->pmc_set_base(
@args
) and
return
;
my
(
$module
,
$line
) = (
caller
(
$class
->pmc_caller_stack_frame))[1, 2];
return
if
$filtered
->{
$module
}++;
my
$callback
=
sub
{
my
(
$class
,
$content
,
$data
) =
@_
;
my
$output
=
$class
->pmc_template(
$module
,
$content
,
$data
);
$class
->pmc_output(
$module
,
$output
);
};
$class
->pmc_check_compiled_file(
$module
);
$class
->pmc_filter(
$module
,
$line
,
$callback
);
return
;
}
sub
pmc_check_compiled_file {
my
(
$class
,
$file
) =
@_
;
if
(
defined
$file
and
$file
!~ /\.pm$/i) {
my
$pmc
=
$file
.
'c'
;
$class
->pmc_run_compiled_file(
$pmc
),
die
if
-s
$pmc
and (-M
$pmc
<= -M
$file
);
}
}
sub
pmc_run_compiled_file {
my
(
$class
,
$pmc
) =
@_
;
my
(
$package
) =
caller
(
$class
->pmc_file_caller_frame());
eval
"package $package; do \$pmc"
;
die
$@
if
$@;
exit
0;
}
sub
pmc_file_caller_frame { 2 }
sub
pmc_set_base {
my
(
$class
,
$flag
) =
@_
;
if
(
$class
->isa(__PACKAGE__) and
defined
$flag
and
$flag
eq
'-base'
) {
my
$descendant
= (
caller
1)[0];;
no
strict
'refs'
;
push
@{
$descendant
.
'::ISA'
},
$class
;
return
1;
}
return
0;
}
sub
pmc_template {
my
(
$class
,
$module
,
$content
,
$data
) =
@_
;
my
$base
= __PACKAGE__;
my
$check
=
$class
->freshness_check(
$module
);
my
$version
=
$class
->VERSION ||
'0'
;
return
join
"\n"
,
"# Generated by $class $version ($base $VERSION) - do not edit!"
,
"$check$content$data"
;
}
sub
freshness_check {
my
(
$class
,
$module
) =
@_
;
my
$sum
=
sprintf
(
'%08X'
,
do
{
local
$/;
open
my
$fh
,
"<"
,
$module
or
die
"Cannot open $module: $!"
;
binmode
(
$fh
,
':crlf'
);
unpack
(
'%32N*'
, <
$fh
>);
});
return
<<
"..."
;
BEGIN {
use
5.006;
local
(
*F
, \$/); (\
$F
= __FILE__) =~ s!c\$!!;
open
(F)
or
die
"Cannot open \$F: \$!"
;
binmode
(F,
':crlf'
);
if
(
unpack
(
'%32N*'
,
filter_add(
sub
{ filter_del(); 1
while
&filter_read
; \
$_
= \
$f
; 1; })}}
...
}
sub
pmc_output {
my
(
$class
,
$module
,
$output
) =
@_
;
$class
->pmc_can_output(
$module
)
or
return
0;
my
$pmc
=
$module
.
'c'
;
open
my
$fh
,
">"
,
$pmc
or
return
0;
local
$@;
eval
{
print
$fh
$output
or
die
;
close
$fh
or
die
;
};
if
(
my
$e
= $@ ) {
if
( -e
$pmc
) {
unlink
$pmc
or
die
"Can't delete errant $pmc: $!"
;
}
return
0;
}
return
1;
}
sub
pmc_can_output {
my
(
$class
,
$file_path
) =
@_
;
return
1;
}
sub
pmc_filter {
my
(
$class
,
$module
,
$line_number
,
$post_process
) =
@_
;
open
my
$fh
,
$module
or
die
"Can't open $module for input:\n$!"
;
my
$module_content
=
do
{
local
$/; <
$fh
> };
close
$fh
;
my
$folded_content
=
$class
->pmc_fold_blocks(
$module_content
);
my
$folded_data
=
''
;
if
(
$folded_content
=~ s/^((?:__(?:DATA|END)__$).*)//ms) {
$folded_data
= $1;
}
my
$real_content
=
$class
->pmc_unfold_blocks(
$folded_content
);
my
$real_data
=
$class
->pmc_unfold_blocks(
$folded_data
);
my
@lines
= (
$real_content
=~ /(.*\n)/g);
my
$lines_to_skip
=
@lines
;
$lines_to_skip
-=
$line_number
;
my
$done
= 0;
Filter::Util::Call::filter_add(
sub
{
return
0
if
$done
;
my
$data_line
=
''
;
while
(1) {
my
$status
= Filter::Util::Call::filter_read();
last
unless
$status
;
return
$status
if
$status
< 0;
next
if
$lines_to_skip
-- > 0;
if
(/^__(?:END|DATA)__$/) {
$data_line
=
$_
;
last
;
}
}
continue
{
$_
=
''
;
}
$real_content
=~ s/\r//g;
my
$filtered_content
=
$class
->pmc_process(
$real_content
);
$class
->
$post_process
(
$filtered_content
,
$real_data
);
$filtered_content
=~ s/(.*\n){
$line_number
}//;
$_
=
$filtered_content
.
$data_line
;
$done
= 1;
});
}
sub
pmc_process {
my
$class
=
shift
;
my
$data
=
shift
;
my
@blocks
=
$class
->pmc_parse_blocks(
$data
);
while
(
@blocks
=
$class
->pmc_reduce(
@blocks
)) {
if
(
@blocks
== 1 and @{
$blocks
[0][CLASSES]} == 0) {
my
$content
=
$blocks
[0][TEXT];
$content
.=
"\n"
unless
$content
=~ /\n\z/;
return
$content
;
}
}
die
"How did I get here?!?"
;
}
sub
pmc_reduce {
my
$class
=
shift
;
my
@blocks
;
my
$prev
;
while
(
@_
) {
my
$block
=
shift
;
my
$next
=
$_
[TEXT];
if
(
$next
and
"@{$block->[CLASSES]}"
eq
"@{$next->[CLASSES]}"
) {
shift
;
$block
->[TEXT] .=
$next
->[TEXT];
}
elsif
(
(not
$prev
or @{
$prev
->[CLASSES]} < @{
$block
->[CLASSES]}) and
(not
$next
or @{
$next
->[CLASSES]} < @{
$block
->[CLASSES]})
) {
my
$prev_len
=
$prev
? @{
$prev
->[CLASSES]} : 0;
my
$next_len
=
$next
? @{
$next
->[CLASSES]} : 0;
my
$offset
= (
$prev_len
>
$next_len
) ?
$prev_len
:
$next_len
;
my
$length
= @{
$block
->[CLASSES]} -
$offset
;
$class
->pmc_call(
$block
,
$offset
,
$length
);
}
push
@blocks
,
$block
;
$prev
=
$block
;
}
return
@blocks
;
}
sub
pmc_call {
my
$class
=
shift
;
my
$block
=
shift
;
my
$offset
=
shift
;
my
$length
=
shift
;
my
$text
=
$block
->[TEXT];
my
$context
=
$block
->[CONTEXT];
my
@classes
=
splice
(@{
$block
->[CLASSES]},
$offset
,
$length
);
for
my
$klass
(
@classes
) {
local
$_
=
$text
;
my
$return
=
$klass
->pmc_compile(
$text
, (
$context
->{
$klass
} || {}));
$text
= (
defined
$return
and
$return
!~ /^\d+\z/)
?
$return
:
$_
;
}
$block
->[TEXT] =
$text
;
}
sub
pmc_parse_blocks {
my
$class
=
shift
;
my
$data
=
shift
;
my
@blocks
= ();
my
@classes
= ();
my
$context
= {};
my
$text
=
''
;
my
@parts
=
split
/^([^\S\n]*(?:
use
|
no
)[^\S\n]+[\w\:\']+[^\n]*\n)/m,
$data
;
for
my
$part
(
@parts
) {
if
(
$part
=~ /^[^\S\n]*(
use
|
no
)[^\S\n]+([\w\:\']+)[^\n]*\n/) {
my
(
$use
,
$klass
,
$file
) = ($1, $2, $2);
$file
=~ s{(?:::|')}{/}g;
if
(
$klass
=~ /^\d+$/) {
$text
.=
$part
;
next
;
}
{
local
$@;
eval
{
require
"$file.pm"
};
die
$@
if
$@ and
"$@"
!~ /^Can't locate /;
}
if
(
$klass
->can(
'pmc_is_compiler_module'
) and
$klass
->pmc_is_compiler_module) {
push
@blocks
, [
$text
, {
%$context
}, [
@classes
]];
$text
=
''
;
@classes
=
grep
{
$_
ne
$klass
}
@classes
;
if
((
$use
eq
'use'
) xor
$klass
->pmc_use_means_no) {
push
@classes
,
$klass
;
$context
->{
$klass
} = {%{
$context
->{
$klass
} || {}}};
$context
->{
$klass
}{
use
} =
$part
;
if
(
$klass
->pmc_use_means_now) {
push
@blocks
, [
''
, {
%$context
}, [
@classes
]];
@classes
=
grep
{
$_
ne
$klass
}
@classes
;
delete
$context
->{
$klass
};
}
}
else
{
delete
$context
->{
$klass
};
}
}
else
{
$text
.=
$part
;
}
}
else
{
$text
.=
$part
;
}
}
push
@blocks
, [
$text
, {
%$context
}, [
@classes
]]
if
length
$text
;
return
@blocks
;
}
sub
pmc_compile {
my
(
$class
,
$source_code_string
,
$context_hashref
) =
@_
;
return
$source_code_string
;
}
my
$re_here
=
qr/
(?: # Heredoc starting line
^ # Start of some line
((?-s:.*?)) # $2 - text before heredoc marker
<<(?!=) # heredoc marker
[\t\x20]* # whitespace between marker and quote
((?>['"]?)) # $3 - possible left quote
([\w\-\.]*) # $4 - heredoc terminator
(\3 # $5 - possible right quote
(?-s:.*\n)) # and rest of the line
(.*?\n) # $6 - Heredoc content
(?<!\n[0-9a-fA-F]{40}\n) # Not another digest
(\4\n) # $7 - Heredoc terminating line
)
/
xsm;
my
$re_pod
=
qr/
(?:
(?-s:^=(?!cut\b)\w+.*\n) # Pod starter line
.*? # Pod lines
(?:(?-s:^=cut\b.*\n)|\z) # Pod terminator
)
/
xsm;
my
$re_comment
=
qr/
(?:
(?m-s:^[^\S\n]*\#.*\n)+ # one or more comment lines
)
/
xsm;
my
$re_data
=
qr/
(?:
^(?:__END__|__DATA__)\n # DATA starter line
.* # Rest of lines
)
/
xsm;
sub
pmc_fold_blocks {
my
(
$class
,
$source
) =
@_
;
$source
=~ s/(~{3,})/$1~/g;
$source
=~ s/(^
'{3,})/$1'
/gm;
$source
=~ s/(^`{3,})/$1`/gm;
$source
=~ s/(^={3,})/$1=/gm;
while
(1) {
no
warnings;
$source
=~ s/
(
$re_pod
|
$re_comment
|
$re_here
|
$re_data
)
/
my
$result
= $1;
$result
=~ m{\A(
$re_data
)} ?
$class
->pmc_fold_data() :
$result
=~ m{\A(
$re_pod
)} ?
$class
->pmc_fold_pod() :
$result
=~ m{\A(
$re_comment
)} ?
$class
->pmc_fold_comment() :
$result
=~ m{\A(
$re_here
)} ?
$class
->pmc_fold_here() :
die
"'$result' didn't match '$re_comment'"
;
/ex or
last
;
}
$source
=~ s/(?<!~)~~~(?!~)/<</g;
$source
=~ s/^
''
'(?!'
) /__DATA__\n/gm;
$source
=~ s/^```(?!`)/
$source
=~ s/^===(?!=)/=/gm;
$source
=~ s/^(={3,})=/$1/gm;
$source
=~ s/^(
'{3,})'
/$1/gm;
$source
=~ s/^(`{3,})`/$1/gm;
$source
=~ s/(~{3,})~/$1/g;
return
$source
;
}
sub
pmc_unfold_blocks {
my
(
$class
,
$source
) =
@_
;
$source
=~ s/
(
^__DATA__\n[0-9a-fA-F]{40}\n
|
^=pod\s[0-9a-fA-F]{40}\n=cut\n
)
/
my
$match
= $1;
$match
=~ s!.*?([0-9a-fA-F]{40}).*!$1!s or
die
;
$digest_map
->{
$match
}
/xmeg;
return
$source
;
}
sub
pmc_fold_here {
my
$class
=
shift
;
my
$result
=
"$2~~~$3$4$5"
;
my
$preface
=
''
;
my
$text
= $6;
my
$stop
= $7;
while
(1) {
if
(
$text
=~ s!^(([0-9a-fA-F]{40})\n.*\n)!!) {
if
(
defined
$digest_map
->{$2}) {
$preface
.= $1;
next
;
}
else
{
$text
= $1 .
$text
;
last
;
}
}
last
;
}
my
$digest
=
$class
->pmc_fold(
$text
);
$result
=
"$result$preface$digest\n$stop"
;
$result
;
}
sub
pmc_fold_pod {
my
$class
=
shift
;
my
$text
= $1;
my
$digest
=
$class
->pmc_fold(
$text
);
return
qq{===pod $digest\n===cut\n}
;
}
sub
pmc_fold_comment {
my
$class
=
shift
;
my
$text
= $1;
my
$digest
=
$class
->pmc_fold(
$text
);
return
qq{``` $digest\n}
;
}
sub
pmc_fold_data {
my
$class
=
shift
;
my
$text
= $1;
my
$digest
=
$class
->pmc_fold(
$text
);
return
qq{''' $digest\n}
;
}
sub
pmc_fold {
my
(
$class
,
$text
) =
@_
;
my
$digest
= Digest::SHA1::sha1_hex(
$text
);
$digest_map
->{
$digest
} =
$text
;
return
$digest
;
}
sub
pmc_unfold {
my
(
$class
,
$digest
) =
@_
;
return
$digest_map
->{
$digest
};
}
1;