File Coverage

blib/lib/Eliza/Chatbot/Brain.pm
Criterion Covered Total %
statement 88 101 87.1
branch 17 24 70.8
condition 5 8 62.5
subroutine 8 8 100.0
pod 3 3 100.0
total 121 144 84.0


line stmt bran cond sub pod time code
1             package Eliza::Chatbot::Brain;
2              
3 9     9   65 use Moo;
  9         23  
  9         67  
4 9     9   3108 use MooX::LazierAttributes;
  9         32  
  9         63  
5              
6 9     9   6873 use Ref::Util qw(is_scalarref is_blessed_arrayref);
  9         15239  
  9         14209  
7              
8             attributes(
9             decomp_matches => [rw, [ ], {lzy}],
10             [qw/options last/] => [rw, nan, {lzy}],
11             );
12              
13             sub preprocess {
14 13     13 1 7791 my ($self, $string) = @_;
15 13         43 my @orig_words = split / /, $string;
16              
17 13         21 my @converted_words;
18 13         26 foreach my $word ( @orig_words ) {
19 32         91 $word =~ s{[?!,]|but}{.}g;
20 32         64 push @converted_words, $word;
21             }
22              
23 13         56 my $formated = join ' ', @converted_words;
24 13         38 @converted_words = split /\./, $formated;
25 13         44 return @converted_words;
26             }
27              
28             sub postprocess {
29 19     19 1 8014 my ($self, $string) = @_;
30 19 50       82 if ( is_blessed_arrayref($string) ) {
    100          
31 0         0 for (my $i = 1; $i < scalar @{$string}; $i++){
  0         0  
32 0         0 $string->[$i] =~ s/([,;?!]|\.*)$//;
33             }
34             } elsif ( is_scalarref(\$string) ) {
35 13         39 $string =~ tr/ / /s; # Eliminate any duplicate space characters.
36 13         35 $string =~ s/[ ][?]$/?/; # Eliminate any spaces before the question mark.
37             }
38 19         73 return $string;
39             }
40              
41             sub _test_quit {
42 19     19   10966 my ($self, $string) = @_;
43 19         35 foreach my $quitword (@{$self->options->data->quit}) {
  19         353  
44 29 100       453 return 1 if $string =~ m{$quitword}xms;
45             }
46             }
47              
48             sub _debug_memory {
49 6     6   4924 my $self = shift;
50 6         7 my @memory = @{$self->options->memory};
  6         99  
51 6         60 my $string = sprintf("%s item(s) in memory stack:\n", scalar @memory);
52 6         13 foreach my $msg (@memory) {
53 21         51 $string .= sprintf("\t\t->%s\n", $msg);
54             }
55 6         25 return $string;
56             }
57              
58             sub transform {
59 13     13 1 8619 my ($self, $string, $use_memory) = @_;
60              
61 13         24 my ($this_decomp, $reasmbkey);
62 13         41 my $options = $self->options;
63 13 50       31 $options->debug_text(sprintf("\t[Pulling string \"%s\" from memory.]\n", $string))
64             if $use_memory;
65              
66 13 100       34 if ($self->_test_quit($string)){
67 5         17 $self->last(1);
68 5         110 return $options->data->final->[ $options->myrand(scalar @{$options->data->final}) ];
  5         193  
69             }
70              
71             # Default to a really low rank.
72 8         157 my $rank = -2;
73 8         13 my $reasmb = "";
74 8         15 my $goto = "";
75              
76             # First run the string through preprocess.
77 8         21 my @string_parts = $self->preprocess( $string );
78              
79 8         153 $self->decomp_matches([]);
80             # Examine each part of the input string in turn.
81 8         103 foreach my $string_part (@string_parts) {
82              
83             # Run through the whole list of keywords.
84 8         12 KEYWORD: foreach my $keyword (keys %{$options->data->decomp}) {
  8         143  
85              
86             # Check to see if the input string contains a keyword
87             # which outranks any we have found previously
88             # (On first loop, rank is set to -2.)
89 24 100 66     585 if ( ($string_part =~ /\b$keyword\b/i or $keyword eq $goto)
      66        
90             and
91             $rank < $options->data->key->{$keyword}
92             )
93             {
94             # If we find one, then set $rank to equal
95             # the rank of that keyword.
96 6         247 $rank = $options->data->key->{$keyword};
97 6   50     240 $options->debug_text(
98             sprintf("%s \trank:%d keyword:%s",
99             ($options->debug_text // ''), $rank, $keyword)
100             );
101              
102             # Now let's check all the decomposition rules for that keyword.
103 6         216 foreach my $decomp (@{$options->data->decomp->{$keyword}}) {
  6         90  
104              
105             # Change '*' to '\b(.*)\b' in this decomposition rule,
106             # so we can use it for regular expressions. Later,
107             # we will want to isolate individual matches to each wildcard.
108 6         162 $this_decomp = $decomp;
109 6         33 $this_decomp =~ s/\s*\*\s*/\\b\(\.\*\)\\b/g;
110             # If this docomposition rule contains a word which begins with '@',
111             # then the script also contained some synonyms for that word.
112             # Find them all using %synon and generate a regular expression
113             # containing all of them.
114 6 50       32 if ($this_decomp =~ /\@/ ) {
115 0         0 $this_decomp =~ s/.*\@(\w*).*/$1/i;
116 0         0 my $synonyms = join ('|', @{$options->data->synon->{$this_decomp}} );
  0         0  
117 0         0 $this_decomp =~ s/(.*)\@$this_decomp(.*)/$1($this_decomp\|$synonyms)$2/g;
118             }
119              
120             $options->debug_text(
121 6         100 sprintf("%s\n\t\t: %s", $options->debug_text, $decomp)
122             );
123            
124             # Using the regular expression we just generated,
125             # match against the input string. Use empty "()"'s to
126             # eliminate warnings about uninitialized variables.
127 6 50       170 if ($string_part =~ /$this_decomp()()()()()()()()()()()/i) {
128              
129             # If this decomp rule matched the string,
130             # then create an array, so that we can refer to matches
131             # to individual wildcards. Use '0' as a placeholder
132             # (we don't want to refer to any "zeroth" wildcard).
133 6         70 my @decomp_matches = ("0", $1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
134            
135              
136 6         11 push @{$self->decomp_matches}, { matches => \@decomp_matches };
  6         109  
137            
138 6         148 $options->debug_text(
139             sprintf( "%s : %s \n",
140             $options->debug_text, join( ' ', @decomp_matches))
141             );
142            
143             # Using the keyword and the decomposition rule,
144             # reconstruct a key for the list of reassamble rules.
145 6         83 $reasmbkey = join $;, $keyword, $decomp;
146             # Get the list of possible reassembly rules for this key.
147 6         9 my @these_reasmbs = @{$options->data->reasmb->{$reasmbkey}};
  6         96  
148              
149             # Pick out a reassembly rule at random :).
150 6         174 $reasmb = $these_reasmbs[ $options->myrand( scalar @these_reasmbs ) ];
151 6         111 $options->debug_text(
152             sprintf("%s \t\t--> %s\n",
153             $options->debug_text, $reasmb )
154             );
155              
156             # If the reassembly rule we picked contains the word "goto",
157             # then we start over with a new keyword. Set $keyword to equal
158             # that word, and start the whole loop over.
159 6 50       79 if ($reasmb =~ m/^goto\s(\w*).*/i) {
160 0         0 $options->debug_text(sprintf("%s \$1 = $1\n",
161             $options->debug_text));
162 0         0 $goto = $keyword = $1;
163 0         0 $rank = -2;
164 0         0 redo KEYWORD;
165             }
166              
167             # Otherwise, using the matches to wildcards which we stored above,
168             # insert words from the input string back into the reassembly rule.
169             # [THANKS to Gidon Wise for submitting a bugfix here]
170 6         97 my $decomp_matches = $self->decomp_matches;
171 6         37 foreach my $match (@{$decomp_matches}) {
  6         12  
172 6         38 $match->{matches} = $self->postprocess( $match->{matches} );
173 6         21 for (my $i = 1; $i < 10; $i++) {
174 54         444 $reasmb =~ s/\($i\)/$match->{matches}->[$i]/g;
175             }
176             }
177              
178             # Move on to the next keyword. If no other keywords match,
179             # then we'll end up actually using the $reasmb string
180             # we just generated above.
181 6         27 next KEYWORD;
182              
183             } # End if ($string_part =~ /$this_decomp/i)
184              
185 0         0 $options->debug_text($options->debug_text . "\n");
186             } # End DECOMP: foreach $decomp (@{ $self->{decomplist}->{$keyword} })
187              
188             } # End if ( ($string_part =~ /\b$keyword\b/i or $keyword eq $goto)
189              
190             } # End KEYWORD: foreach $keyword (keys %{ $self->{decomplist})
191            
192             } # End STRING_PARTS: foreach $string_part (@string_parts) {
193              
194 8 100       41 $reasmb = $self->transform("xnone", "") if $reasmb eq "";
195            
196 8         48 $reasmb = $self->postprocess($reasmb);
197            
198 8 50       173 if ($options->memory_on) {
199             # Shift out the least-recent item from the bottom
200             # of the memory stack if the stack exceeds the max size.
201 8 50       128 shift @{$options->memory} if scalar @{$options->memory} >= $options->max_memory_size;
  0         0  
  8         145  
202             # push in the current reasem string
203 8         275 push @{$options->memory}, $reasmb;
  8         119  
204              
205             $options->debug_text(sprintf("%s \t%d item(s) in memory.\n",
206 8         160 $options->debug_text, scalar @{$options->memory} ));
  8         148  
207             }
208              
209             # Save the return string so that forgetful calling programs
210             # can ask the bot what the last reply was.
211 8         317 $options->transform_text($reasmb);
212 8         72 return $reasmb;
213             }
214              
215             1;
216              
217             __END__