ChwanRen has asked for the wisdom of the Perl Monks concerning the following question:

Is there a way where I can unshift a string to <STDIN>? like:
# old STDIN: 1 2 3 while (<STDIN>) { if (some condition) unshift "hi test test" to STDIN # new STDIN hi test test 1 2 3 }
if some condition is met, the string "hi test test" will be unshifted onto STDIN. I'm using this to program 'functions' into my forth interpreter (written in perl of course).

Replies are listed 'Best First'.
Re: Manipulating STDIN
by davido (Cardinal) on Apr 11, 2004 at 06:14 UTC
    Unshifting, or putting something back onto STDIN after essentially popping/shifting it off is not a built-in capability of that filehandle. The only real choice is to somehow keep track of what you just read out of STDIN so that even if you don't need it immediately it's still available to you when you are ready for it in the next iteration.

    One thought I had while considering this question was that you could open a tied filehandle, tied to a class that hides the intricacies of allowing you to "unshift" data. The tied FH would read from STDIN, but would provide an OO interface on the side that allows you to do some funky things like $obj->push_fh($);.

    Here is a very simple implementation of this strategy. I wouldn't consider it complete, as it doesn't implement all of the required tied filehandle methods. But it does implement those necessary for the purpose of this example, and the result is executable code that seems to work fine in simple situations. Filling in the blanks to complete the rest of the required tied filehandle methods remains to be completed if you want to fully implement the solution. In particular, you'll need an EOF, PRINT, and GETC method, if I'm not mistaken. :)

    use strict; use warnings; package PushableFH; use strict; use warnings; sub TIEHANDLE { my $class = shift; my $rfh = shift; my $obj = {}; $obj->{FH} = $rfh; $obj->{PUSHED} = []; bless $obj, $class; } sub READLINE { my $self = shift; my $fh = $self->{FH}; return ( @{$self->{PUSHED}} > 0 ) ? pop @{$self->{PUSHED}} : <$fh> +; } sub push_fh { my $self = shift; push @{$self->{PUSHED}}, @_; } package main; use strict; use warnings; use vars qw/*FILEHANDLE/; my $fhobj = tie *FILEHANDLE, "PushableFH", \*DATA; my $line = <FILEHANDLE>; # Read the first line from the filehandle. print $line; $fhobj->push_fh("Hello world!\n"); # Push something onto the FH. $line = <FILEHANDLE>; # Read what was just pushed. print $line; $line = <FILEHANDLE>; # Read the next physical line from FH. print $line; __DATA__ This is the first line. This is the second line.

    You'll find that the output is:

    This is the first line.
    Hello world!
    This is the second line.

    So, as you can see, you now have a filehandle that reads from (in this case) DATA, but that allows you to push data back onto it for later use.

    Enjoy!


    Dave

Re: Manipulating STDIN
by tilly (Archbishop) on Apr 11, 2004 at 06:16 UTC
    I would personally manage the data internally, but you could always look at perltie and write an appropriate tie class that did it for you. Here is an example class:
    package Tie::Input::Insertable; sub PRINT { my $self = shift; $self->{buffer} = join '', @_, $self->{buffer}; } sub TIEHANDLE { my ($class, $fh) = @_; bless {fh => $fh, buffer => ""}, $class; } sub READLINE { my $self = shift; return undef if $self->{eof}; while (-1 == index($self->{buffer}, $/)) { my $fh = $self->{fh}; my $data = <$fh>; if (length($data)) { $self->{buffer} .= $data; } else { $self->{eof} = 1; return delete $self->{buffer}; } } my $pos = index($self->{buffer}, $/) + length($/); return substr($self->{buffer}, 0, $pos, ""); } sub EOF { my $self = shift; $self->{eof} ||= not length($self->{buffer}) or $self->{fh}->eof(); } 1;
    Save that in Tie/Input/Insertable.pm and then you can do the following:
    use Tie::Input::Insertable; tie *FOO, 'TIE::Input::Insertable', *STDIN; while (<FOO>) { print FOO "Hello, world\n" if /foo/; print $_; }
    Note that I made the tied handle not be the one that I was tying it to. If you do that, then you are playing on the edges of infinite recursion. Just make the handles different and you avoid lots of possible confusion.
      To tie directly STDIN you can hold the original STDIN in other GLOB, soo this will work:
      *ORIGINAL_STDIN = \*STDIN ; tie *STDIN, 'Tie::Input::Insertable', *ORIGINAL_STDIN; print "> " ; while (<STDIN>) { print STDIN "Hello, world\n" if /foo/; print "\n# $_\n" ; print "> " ; }
      And by the way, nice work in the TIEHANDLE. ;-P

      Graciliano M. P.
      "Creativity is the expression of the liberty".

Re: Manipulating STDIN
by Zaxo (Archbishop) on Apr 11, 2004 at 05:04 UTC

    Better to bring in the extra text from a new source,

    while (<STDIN>) { $_ = "hi test text" if some_condition; # etc }
    Any chance you'll post your forth interpreter here? I'd like to see it.

    Update: One way to slip in an extra STDIN is to localize the handle:

    while (<STDIN>) { do { local $_ = $_; open local(*STDIN), @other_args or die $!; # do stuff } if some_condition; # more stuff }
    That takes care of throwing away $_, too. BTW, your forth interpreter is 404 for me.

    After Compline,
    Zaxo

      The interpreter is here: http://yoyo.monash.edu.my/~chwanren/uploader/assignment1.txt. If I did that, wouldn't I be throwing away whatever is read from STDIN? I mean if $_ is originally 'push' and I substitute it with $_ = "hi test test", then 'push' will be ignored right? Thanks for the help :)
      Yeah I'm sorry about that. Had to take it down. Don't want to get plagiarised.

      Anyway to the other replies, the reason I want to unshift to STDIN is because when my forth interpreter reads the "call" function, I want to unshift the commands stored in the variable onto STDIN. I'll elaborate. When curly braces are encountered, everything in it until the closing brace is added to a string. This string is then stored into a variable declared earlier. Like:

      pop3 { pop pop pop } store
      the string "pop pop pop" is stored into pop3 (via hash tables)

      then, when i do pop3 call, I want to unshift "pop pop pop" onto STDIN to be read.

      I've already implemented this differently by reading it everything from STDIN until EOF into a list, then handling each element of the list as though it is read from STDIN. Thus, I can unshift "pop pop pop" onto the list.

      Is there any other way to do this more elegantly?
Re: Manipulating STDIN
by Anonymous Monk on Apr 11, 2004 at 17:03 UTC
    I checked CPAN and there is a module IO::Unread which works with another module PerlIO to allow you the ability to as IO::Unread says unread STDIN. Wags ;)
Re: Manipulating STDIN
by graff (Chancellor) on Apr 11, 2004 at 21:36 UTC
    The other replies have probably given more than enough to address the issue of unshifting data to STDIN, but there were a couple things about your original example pseudo-code that puzzled me:
    # old STDIN: 1 2 3 while (<STDIN>) { if (some condition) unshift "hi test test" to STDIN # new STDIN hi test test 1 2 3 }
    First: to produce the result that you say you want, you would need to unshift "hi test test $_", wouldn't you?

    Second: why go to the trouble of doing deep magic with the file handle? That is, why wouldn't the following approach, which yields the same basic result, do just as well (with a lot less code and complexity)?

    while (<STDIN>) { if (some condition) $_ = "hi test test " . $_; # ... }
    If the problem is that some other branch in the while loop is looking for "hi test test " to trigger some action, it would seem easy enough to arrange the logic so that this other branch will execute on the same iteration after the prefix has been added.

    If it's more complicated then that, maybe you should be looking at a two-stage process, where the first stage is a simple text filter, adding prefixes to lines where needed, etc; the filter's output is piped to the second stage, whose logic is now much simpler, because the stream is appropriate to its task -- two simple scripts are often better than one complicated script. If you want to keep it really simple for the command-line user, you could set up the second script to handle its input like this:

    # supposing that @ARGV contains file name(s) to be filtered and proces +sed: open( INP, "my_filter_script.pl @ARGV |" ) or die "can't filter: $!"; while (<INP>) { # prefixes and other source code filtering are already done # ... }
Re: Manipulating STDIN
by Revelation (Deacon) on Apr 11, 2004 at 23:11 UTC
    I don't know if this won't work for some reason, but why not:
    my @f; while ( $_ = scalar @f ? shift @f : <STDIN> ) { chomp; unshift(@f,"hi test test") if condition; }
    If you won't be shifting '0' or '', then even  $_ = shift @f || <STDIN> works. Both solutions are simpler than tie()-ing STDIN; just replace reads from STDIN to  shift @f || <STDIN> inside the loop for seamless transition. Becase I'm a fan of using a block, instead of while syntax, with longer conditons and assignments, I'll also give you an alternative control structure:
    my @f; { redo if $_ = shift @f || <STDIN>; }
    If you're only reading from STDIN at the beggining, or don't mind copying and pasting a bit, then both solutions are strong. They mean one less module to debug, and one less layer of complexity in your code. It also gives a speed bonus over tie()-ing. If you _must_ tie to *STDIN, gmpassos provides an elegant solution. I feel like that's an unnecessary convolution, though.

    Gyan Kapur
    Untested
    A reply falls below the community's threshold of quality. You may see it by logging in.