IO-ReadPreProcess
view release on metacpan or search on metacpan
lib/IO/ReadPreProcess.pm view on Meta::CPAN
unless($arg =~ s/^(["'])((\\{2})*|(.*?[^\\](\\{2})*))\1\s*//);
my $m = $2;
$m =~ s/\\([\\'"])/$1/g; # Remove embedded escapes, eg: \" => "
push(@args, $m);
} else {
$arg =~ s/^(\S+)\s*//;
push(@args, $1);
}
}
@args = map { $self->ProcEscapes($_) } @args if($doEsc);
@args
}
# Read & store a sub or function to hash in $self->{subs}->{Name}
# Don't start a frame since we are just reading this in
# Return true if OK
sub readSub
{
my ($self, $direc, $InLine, $arg) = @_;
# Check that $self->{Frame}->{Fd} is an open file
my $code = { };
my @args;
return $self->SetError("Missing $direc name at $self->{Place}", 1) unless($arg ne '');
# Also need to check that name & args are IDs
return undef unless(@args = $self->SplitArgs($arg, 0));
# First is the name:
$code->{Name} = shift @args;
return $self->SetError("Error: bad sub name '$code->{Name}' at $self->{Place}")
unless($code->{Name} =~ /^\w+$/);
return $self->SetError("Error: Redefinition of sub '$code->{Name}' at $self->{Place}")
if(exists($self->{subs}->{$code->{Name}}));
$self->{subs}->{$code->{Name}} = $code;
$code->{ArgNames} = @args;
# sub args can have names:
$code->{ArgNames} = \@args if(@args);
$code->{Block} = $direc; # Info only
$self->ReadBlock($InLine, $code);
$code->{LastLine}--; # Remove .done
$code->{FirstLine}++; # Remove .sub
1
}
# $direct is while/until/for
# $arg is loop condition/rest-of-line
# May start: -innn to specify max iterations
# **** THINKS ****
# Loops are found in 2 ways:
# (1) Reading from a {Fd} - ie in getline()
# (2) When in a sub or an enclosing loop
# We always buffer a loop, so the only difference is where/how it is found
# The purpose of this sub is for case (1), need to initiate a buffer creation
# If (1) read into a buffer/code and return a ref to the code
# If (2) set up $code and return that
sub ReadLoop
{
my ($self, $direc, $InLine, $arg) = @_;
my $frame = $self->{Frame};
my $code = { Block => $direc };
$self->ReadBlock($InLine, $code);
$code
}
# Read a block (sub or loop) to arg \%code
# If this finds a loop - note it as within what we read -- works for sub & nested loops
# $InLine is the line just read
sub ReadBlock
{
my ($self, $InLine, $code) = @_;
# Record where this was found:
my $h={ FileName => $self->{Frame}->{Name}, FileLine => $self->{Frame}->{LineNumber}};
while (my($k,$v)= (each %$h)){
$code->{$k} = $v;
}
$code->{start} = "$code->{FileName}:$code->{FileLine}";
my $started = "started $code->{start}";
my @blocks;
my $frame = $self->{Frame};
my $lineNo; # when reading existing array
$code->{FirstLine} = 0;
$code->{Lines} = [];
my $lineCnt = 0;
while(1) {
my $line = { Txt => $InLine, '#' => $. };
# Quick return if it cannot be a directive line - or one that we recognise
# If not generating - skip to next
unless($InLine =~ /^($self->{DirStartRE})(\w+)\s*(.*)/ and
(defined($ctlDirectives{$2}) or defined($self->{subs}->{$2}))) {
push @{$code->{Lines}}, $line unless(defined $frame->{Code});
$lineCnt++;
} else {
my $leadin = $1; # String that identified the directive
lib/IO/ReadPreProcess.pm view on Meta::CPAN
}
$self->{Math}->EvalToScalar($iftree);
# Don't care what the result is
next;
}
# Return a line parsed for escapes
if($dir eq 'echo') {
$vh->{_CountGen}->[0]++;
return $self->ProcEscapes($arg) . $/;
}
# Start of loop
if(exists $loops{$dir}) { # 'while' 'until' 'for'
# Create a new frame with an indicator that this is a loop frame
# With 'for' execute the initialisation expression and record the loop expression
# For/while/until all look the same (until has a truth invert flag)
# On 'EOF' of the recorded array, detect that it is a loop frame:
# - execute any loop expression
# - evaluate the loop condition; closeFrame on false; reset CodeLine on true
my $code;
my @args = $self->SplitArgs($arg, 1);
my $oframe = $frame;
# First time:
unless($doneDone or $frame->{CondReRun}) {
$self->openFrame(Block => $dir);
$frame = $self->{Frame};
}
# If reading from a stream grab the loop to an array:
unless(exists $frame->{Code}) {
return $code unless($code = $self->ReadLoop($dir, $_, $arg));
$frame->{Code} = $code;
$frame->{CodeLine} = $code->{FirstLine} + 1;
delete $frame->{Fd};
}
# New loop, initialise it:
unless($doneDone or $frame->{CondReRun}) {
$replay = $frame->{Code}{Lines}->[$frame->{CodeLine} - 1];
$frame->{LoopMax} = $replay->{LoopMax};
$frame->{LoopCnt} = 0;
$frame->{LoopStart} = $replay->{LoopStart};
$frame->{LoopEnd} = $replay->{LoopEnd};
# Set CodeLine to Line after end - in parent frame (which might be from stream and ignore it)
$oframe->{CodeLine} = $frame->{LoopEnd};
# Evaluate any loop initialisation
$self->{Math}->ParseToScalar($replay->{Init}) if(exists $replay->{Init});
}
$doneDone = 0;
# Beware: might be here twice
unless($frame->{CondReRun}) {
# Trap run away loops:
return $self->SetError("Maximum iterations ($frame->{LoopMax}) exceeded at $frame->{FrameStart}", 1)
if($frame->{LoopMax} && ++$frame->{LoopCnt} > $frame->{LoopMax});
# evaluation loop expression (not on first time)
$self->{Math}->EvalToScalar($replay->{For3}) if(exists $replay->{For3} and $frame->{LoopCnt} != 1);
}
# Evaluate the loop condition - if true keep looping
my $bool = $self->EvalCond($replay, $dir, $place, $arg);
next if($frame->{CondReRun});
$self->closeFrame if( !$bool);
next;
}
# Should only be seen at end of loop - which is buffered
if($dir eq 'done') {
return $self->SetError("Unexpected '$leadin$dir' at $place", 1)
unless(exists $frame->{LoopMax});
# Next to run is loop start:
$frame->{CodeLine} = $frame->{LoopStart};
$doneDone = 1;
next;
}
if($dir eq 'break' or $dir eq 'last') {
my $loops = 1;
$loops = $1 if($arg =~ /\s*(\d+)/);
# Unwind until we find a LoopEnd, then close that frame
my $le;
do {
# Can't break out of sub:
return $self->SetError("'$leadin$dir' too many loops at $place", 1)
if(exists $self->{Frame}->{ReturnFrom});
$le = exists $self->{Frame}->{LoopEnd};
return undef
unless($self->closeFrame);
} until($le and --$loops <= 0);
next;
}
if($dir eq 'continue' or $dir eq 'next') {
my $loops = 1;
$loops = $1 if($arg =~ /\s*(\d+)/);
# Unwind until we find LoopStart, reset to that
my $ls;
while(1) {
return $self->SetError("'$leadin$dir' too many loops at $place", 1)
if(exists $self->{Frame}->{ReturnFrom});
if(($ls = exists $self->{Frame}->{LoopStart}) && --$loops <= 0) {
$self->{Frame}->{CodeLine} = $self->{Frame}->{LoopStart};
last;
}
return undef
unless($self->closeFrame);
lib/IO/ReadPreProcess.pm view on Meta::CPAN
A C<Math::Expression> object that will be used for expression evaluation.
If this is not given a new object will be instantiated with C<< PermitLoops => 1, EnablePrintf => 1 >>.
If you share a C<Math::Expression> object between different C<IO::ReadPreProcess> objects
then the different files being read will see the same variables.
=item C<DirStart> and C<DirStartRE>
C<DirStart> is the string at the start of a line that introduces a directive, the default is full stop C<.>.
If you wish to change this, provide this option. So to use directives like C<< #if >> go:
new IO::ReadPreProcess(File => 'fred', DirStart> => '#')
Before use the characters that are special in Regular Expressions will have a backslash C<\>
prepended, this string is stored in C<DirStartRE>.
If the option C<DirStartRE> is provided this transformation will not be done and the provided string will be used directly, thus more complex
start sequences can be used.
Eg: allow the start sequence to be either C<.> or C<%>:
new IO::ReadPreProcess(File => 'fred', DirStartRE> => '[.%]')
=item C<Raw>
If this is given and true then processing of directives does not happen, they are returned by C<getline>.
You may change this property as input is read but take care to avoid errors, eg: a C<.if> is read in Raw mode
but its C<.fi> in Cooked mode; a complaint will result as the C<.fi> did not have an C<.if>.
C<Raw> might set when in an C<.include>. When the end of that file is reached the previous file (that had
the C<.include> directive) will be returned to and lines read from there.
Default: 0
=item C<OnError>
What should happen when an error happens. Values:
=over 4
=item C<warn>
Print a message to C<STDERR> with C<warn>, this is the default.
=item C<die>
Print a message to C<STDERR> with C<die> which terminates the program.
=item C<>
Do nothing. The application should check the method C<error> and look at C<$IO::ReadPreProcess::errstr>.
=back
=item PipeOK
Pipes are only allowed with C<.include> if the property C<PipeOK> is true (default false).
=item MaxLoopCount
Loops (C<while>, C<until> and C<for>) will abort after this number of iterations.
The count restarts if the loop is restarted.
A value of C<0> disables this test.
This may be overridden on an individual loop with the C<-i> option.
Default 50.
=item OutStreams
This defines output streams that may be written to by C<.out> and C<.print -o>. The
streams can either be C<IO::File>, an array or a reference to a function (when the line will be passed as
the only argument).
The members C<STDOUT> and C<STDERR> are added if not passed, given values C<*STDOUT{IO}> and C<*STDERR{IO}>.
Names must match the RE C</\w+/>.
Eg:
my $lf = IO::File->new('logFile', 'w+');
my @lines;
sub func {
say "func called '$_[0]'";
}
OutStreams => { fun => \&func, log => $lf, buf => \@lines }
This provides the ability to write to multiple places, however the file (or function) must
be opened by the Perl script. C<IO::ReadPreProcess> does not provide the ability to
open new files.
C<.out> can create in-memory streams. These have names like C<@divert> (ie match C</@\w+/i>).
In-memory streams can be written to by C<.out> & C<.print -o> and read by C<.include> & C<.read>.
=back
=head1 PUBLIC PROPERTIES
The properties C<Trim>, C<OnError>, C<MaxLoopCount>, C<OutStreams>, C<PipeOK> and C<Raw> (see C<new>)
may be directly assigned to at any time.
Eg:
$fh->Raw = 1;
Also the following:
=over 4
=item C<Math>
Note that there are many useful values that you can get here,
some set by C<IO::ReadPreProcess> (see below), others by C<.let> directives.
You can thus communicate with the preprocessing layer.
Eg:
You can set C<Math> variables like this:
$fh->{Math}->VarSetScalar('FirstName', 'Henry');
lib/IO/ReadPreProcess.pm view on Meta::CPAN
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
See L<http://dev.perl.org/licenses/> for more information.
=head1 ABSTRACT
Provide an 'intelligent' bottom end read function for scripts.
=cut
Something to help you understand some of the data structures:
Property Frame of the object.
These are in FrameStk
Frames are created for:
* every active file
* every active sub
* every active loop
* if ?
Properties of Frame (not all of these at the same time):
Code Hash code properties; this is a CodeBuf (the name only exists in this description)
CodeLine Line # (array index: Lines) of next line to execute
Fd File handle of current file
FdLine A line of input instead of reading from Fd; will be deleted once read
PushedInput An array reference, lines may be pushed here and will be 'read' in preference
to input from Code or Fd.
binmode If true: to be applied to any file opened in the frame
Fd or CodeBuf is defined, not both
LocalVars Hash of varname => value - previous (pushed) values for varname, etc
This replaces VarStkFlag VarStk
Name Name of open file, name of sub or file where the code was read from
LineNumber Number of current line being executed - from File
FrameStart Name:LineNumber
Generate True if generating (think .if), inherited from previous.
DidGenerate Used to decide if a .else* should be run
Else Value is line number of a .else
If also has:
Type if/unless
type Used in ReadBlock() to check block open/end keywords.
CpMove If true: on frame close, any CodeLine to be copied to parent frame.
ReturnFrom If true a .return will unwind to here and return to the previous.
Other properties for loops
LoopMax Max iterations - 0 == no limit - this is copied from code->{LoopMax}
This is also used to identiy the frame as a loop frame
LoopCnt Count of iterations so far
Loop Just to note that it is a loop frame
CondReRun Rerun the condition in main loop, as: .if .subroutine/.directive
intDir Running an internal directive, eg: .if .read var
In the line:
SubCond .subroutine args -- when used as a condition, eg: .if .test -f xxxx
CodeBuf - for loops
Lines ref to array of Line that contains the code
FirstLine First line # in Lines
LastLine Last line # in Lines
Block 'while' 'until' 'for'
Subs read into hash, CodeBuf:
Lines ref to array of Line that contains the code
FirstLine First line # in Lines
LastLine Last line # in Lines
Block 'sub'
Name Name of Sub
FileName that the sub came from
FileLine line # of start of sub in FileName
ArgNames Optional array of argument names
Line - one line of buffered code:
Txt Text - string, the line of code/text
# Line # in File or Sub
Expr May be present, compiled .set/.if/.unless/.elsif/.elseif expression
and loop condition
Loop The while/for line will have this.
LoopBuf - ie first/lastLine
The line that is the first of a loop has something like:
Lines ref to array that contains the code, could be same as Subs:Lines
First # of first line of loop in Lines
Last # of last line of loop in Lines, this will be the one with .done
Init Loop initialisation
Expr Loop condition
For3 For Loop 3rd expression
Not Invert loop condition (ie it is '.until')
LoopEnd Line # after loop, ie after the .done -- NO - just pop the frame
LoopStart In the .done, the array index of the .while/.until/.for
LoopStart/LoopEnd are in the line of start of the loop, they are copied to the frame
( run in 0.606 second using v1.01-cache-2.11-cpan-71847e10f99 )