Kero
Kero

Reputation: 1944

How to get input from the terminal in Perl

I am creating a simple chatterbot program like ELIZA.

I'm taking questions from the terminal and sending reply with dialog, but my program takes only the first input and repeats.

For example, when I run my script the output may be something like this:

[Eliza]: Hi, I'm a psychotherapist. What is your name?
user Input: hello my name is adam.
[Eliza]: hello adam, how are you?
[Eliza]: your name is adam
[Eliza]: your name is adam
[Eliza]: your name is adam
[Eliza]: your name is adam
[Eliza]: your name is adam

And it repeats endlessly.

I don't know where I am doing wrong. How can I get my program to read the next line from the keyboard?

sub hello {
    print "[Eliza]: Hi, I'm a psychotherapist. What is your name? \n";
}


sub getPatientName {
    my ($reply) = @_;

    my @responses = ( "my name is", "i'm", "i am", "my name's" );

    foreach my $response ( @responses ) {

        if ( lc($reply) =~ /$response/ ) {
            return  "$'";
        }
    }

    return lc($reply);
}

sub makeQuestion {
    my ($patient) = @_;

    my %reflections = (
        "am"    =>   "are",
        "was"   =>   "were",
        "i"     =>   "you",
        "i'd"   =>   "you would",
        "i've"  =>   "you have",
        "i'll"  =>   "you will",
        "my"    =>   "your",
        "are"   =>   "am",
        "you've"=>   "I have",
        "you'll"=>   "I will",
        "your"  =>   "my",
        "yours" =>   "mine",
        "you"   =>   "me",
        "me"    =>   "you"
    );

    if ( $count == 0 ) {
        $patientName = getPatientName($patient);
        $count += 1;
        print "Hello $patientName , How are you? \n";
    }

    my @toBes = keys %reflections;

    foreach my $toBe (@toBes) {

        if ($patient =~/$toBe/) {
            $patient=~ s/$toBe/$reflections{$toBe}/i;
            print "$patient? \n";
        }
    }
}

sub eliza {

    hello();

    my $answer = <STDIN>;

    while ($answer) {
        chomp $answer;
        #remove . ! ;
        $answer =~ s/[.!,;]/ /;
        makeQuestion($answer);
    }
}

eliza();

Upvotes: 5

Views: 4973

Answers (1)

zdim
zdim

Reputation: 66873

Your while loop never reads input. The $answer got assigned STDIN before the loop and presumably has a string, that evaluates true in the while condition. The regex in the loop could in principle change $answer to false (by removing everything from it, turning it into an empty string) but it doesn't fetch STDIN, nor does anything else in the loop body.

Thus not only is no new input assigned to $answer, but after the first iteration nothing at all changes in the loop. So it keeps running forever, printing the question based on the same $answer.

You need

while (my $answer = <STDIN>) {
    chomp $answer;
    # ...
}

instead.

Every time the condition of while (...) is evaluated the new input is read via <STDIN> and is assigned to $answer. Then each new question uses the new $answer. Note how a variable may be declared inside the while condition so to exist only inside the loop body (and in the condition after its declaration). This is a nice way to keep its scope restricted to where it is needed, inside the loop.

The filehandle read <...> returns undef when it gets EOF (or on error) and the loop terminates. See I/O Operators in perlop. A user at the terminal can normally achieve this by Ctrl-d.


For example, a regex like $answer =~ s/.//; removes a character each time it runs (except a newline, which . doesn't match without the /s modifier), so if applied repeatedly eventually $answer winds up being an empty string. Of course, the regex in the question doesn't do that but it changes the string only the first time it runs on it.

Upvotes: 8

Related Questions