Go to the documentation of this file.00001 package MMAgic::Trace;
00002
00003 =head1 NAME
00004
00005 MMAgic::Trace - Perl filter used to (de)activate trace statements.
00006
00007 =head1 SYNOPSIS
00008
00009 use MMAgic::Trace;
00010
00011 # ...or...
00012
00013 use MMAgic::Trace qw(XXX); # set XXX tag
00014
00015 #!# this line is a trace statement
00016 #*# include this trace statement (easy one-line include)
00017 #$# include trace statements to end of subroutine
00018 #!# include this if use Trace qw(XXX); #[XXX]
00019 #!# include this if one of tags set #[XXX YYY]
00020
00021 =head1 DESCRIPTION
00022
00023 The C<MMAgic::Trace> source filter works by I<changing> the source code
00024 early in the compilation process.
00025 Certain special comment sequences are used to indicate lines of code
00026 that contain trace statements.
00027
00028 Unlike many trace mechanisms, the trace statements that are not activated
00029 are never executing at run-time.
00030 There is a minor compile-time penalty for using the source filter but once
00031 the program is running the un-activated statements are just comments and
00032 take up no CPU cycles whatsoever.
00033
00034 It is possible to use this filter for anything, not (just) trace statements.
00035 For other uses it may be necessary to write a more specific filter.
00036
00037 =head2 Special Comments
00038
00039 Special comment sequences are used to mark lines that will potentially be
00040 activated by the filter.
00041 The first three characters of the line are C<#?#> where the center
00042 character (between the two octothorpes) determines the behavior of the line.
00043 The basic sequence is C<#!#> which indicates an unactivated line.
00044
00045 Statements can be activated by changing them slightly, replacing C<#!#>
00046 with C<#*#> or C<#$#> to activate one or more lines.
00047 C<#*#> just activates that line, whereas C<#$#> activates that line and
00048 any other C<#!#> lines until the end of the current subroutine.
00049
00050 There is no way to activate lines at this level of granularity without
00051 editing the source file to change the comment sequences.
00052 It is possible, of course, to activate lines that have additional control
00053 behavior that will be executed at run-time, gaining the best of both worlds.
00054
00055 =head2 Tag Groups
00056
00057 It is also possible to mark one or more lines with tags, grouping lines
00058 together so that they can be (de)activated at one time.
00059 These tags only apply to lines that begin with a comment sequence such as C<#!#>.
00060 The tags are added to the I<end> of such lines using a sequence C<#[TAG]> where
00061 C<TAG> is whatever tag is desired.
00062 Any number of lines can be marked with the same tag.
00063 Multiple tags can be added to a line within the square brackets separated by spaces.
00064
00065 There are two means of activating lines by tag.
00066 The first is to add the desired tags onto the C<use> statement in a list which
00067 will be processed by the MMAgic::Trace::import() function.
00068 This requires editing of the source file.
00069
00070 The second method is to use one or more command-line parameters of the form
00071 C<--trace=TAG>, allowing them to be activated I<at run-time>.
00072 These are parsed during the MMAgic::Trace::import() function and activate
00073 the specified tag(s) for all files that use those tags.
00074 This allows the tagged lines to be activated at run-time.
00075 This parsing occurs prior to changes made by C<Getopt::Long>.
00076
00077 =head1 METHODS
00078
00079 =over
00080
00081 =cut
00082
00083
00084 use strict;
00085 use warnings;
00086
00087 use Filter::Util::Call;
00088
00089 ###########################################################################
00090 ###########################################################################
00091
00092 =item C<import($class [ , @tags ])>
00093
00094 Activate and configure trace filter.
00095
00096 Using the package activates the source filter.
00097 Special comment sequences are recognized and converted to normal code lines
00098 based on the comment sequence and/or tags specified therein.
00099
00100 The C<use> statement allows optional specification of tags to activate.
00101 When tags are activated the appropriately tagged comments are converted to
00102 code throughout, so that categories of trace statements can be activated
00103 all at once.
00104
00105 =cut
00106
00107 #=| @param @tags tags to be activated
00108
00109 sub import
00110 {
00111 my $self = bless { }, shift;
00112 my @tags = @_;
00113
00114 map { # grab tags from special application arguments:
00115 /^--trace=(.*)$/ &&
00116 map { push @tags, $_ } split(',', $1);
00117 # TBD:
00118 # tags are 'global', don't currently know how to figure out
00119 # per-package tags or any such nonsense -- quick'n'dirty
00120 # also doesn't handle the way DOS batch files remove '='
00121 # and separate into two arguments, though the basic
00122 # getopt package does understand the resulting format
00123 } @ARGV;
00124
00125 $self->{_stack_} = [ ];
00126 $self->{'*'} = 1;
00127
00128 map {
00129 if (/^no(.*)$/) { undef $self->{$1} }
00130 else { $self->{$_} = 1 }
00131 } @tags;
00132
00133 # Activate the filter:
00134 filter_add($self);
00135 }
00136
00137 ###########################################################################
00138
00139 =item C<filter($self)>
00140
00141 Filter source to activate trace statements.
00142
00143 This is the basic mechanism used by C<Filter::Util::Call>.
00144 Should be called once for each line of code.
00145
00146 Some might prefer C<Filter::Simple>, but this works fine for now.
00147 It is not all the complicated.
00148
00149 =cut
00150
00151 sub filter
00152 {
00153 my $self = shift;
00154 my $code = filter_read;
00155 my $func = undef;
00156
00157 return $code unless $code > 0;
00158
00159 # Here is where the filtering occurs:
00160
00161 if (/^#([-+])#/) # TBD: this never seemed to work properly, why?
00162 {
00163 push @{$self->{_stack_}}, $1 if $1 eq '-';
00164 pop @{$self->{_stack_}} if $1 eq '+' && @{$self->{_stack_}};
00165 }
00166
00167 elsif (@{$self->{_stack_}})
00168 {
00169 $_ = '';
00170 }
00171
00172 elsif (/^#[\!\$\*]#/)
00173 {
00174 # Found a line that needs processing:
00175 my $doit = $self->{all};
00176
00177 if ($doit) { }
00178 elsif (/^#\*#/) { $doit = 1; }
00179 else
00180 {
00181 # Allow functions to be activated on-site:
00182 $self->{$self->{_function_}} = 1
00183 if /^#\$#/
00184 && $self->{_function_};
00185
00186 # Must check and see whether to include line or not:
00187 my @tags = ( );
00188
00189 # Collect tags from line and from state parsed from file:
00190 push @tags, split(/\s+/, $1) if /#\[\s*(.*)\s*\]\s*$/;
00191 push @tags, $self->{_function_} if $self->{_function_};
00192 push @tags, $self->{_package_} if $self->{_package_};
00193
00194 for my $tag (@tags)
00195 {
00196 next unless $self->{$tag};
00197 $doit = 1;
00198 last;
00199 }
00200
00201 # If no tags apply, just do it:
00202 $doit = 1 unless $doit || @tags;
00203 }
00204
00205 # Fix line:
00206 s/^#[\!\$\*]#/ / if $doit;
00207 }
00208
00209 elsif (/^[^#]*package\s*(\S*).*;/)
00210 {
00211 # Keep track of what package we're (supposedly) in:
00212 $self->{_package_} = "$1::";
00213 # (this isn't going to be completely accurate)
00214 }
00215
00216 elsif (/^[^#]*sub\s+(\w+)/)
00217 {
00218 # Keep track of what subroutine we're (supposedly) in:
00219 $self->{_function_} = "::$1";
00220 # (this isn't going to be completely accurate)
00221 }
00222
00223 $code # always return status code
00224 }
00225
00226 ###########################################################################
00227 ###########################################################################
00228
00229 1
00230
00231 __END__
00232
00233 =back
00234
00235 =head1 AUTHOR
00236
00237 Marc M. Adkins, F<mailto:Perl@Doorways.org>
00238
00239 =head1 COPYRIGHT AND LICENSE
00240
00241 Copyright 2001-2008 by Marc M. Adkins
00242
00243 =cut