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 )