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   68 use Moo;
  9         17  
  9         73  
4 9     9   3677 use MooX::LazierAttributes;
  9         20  
  9         84  
5              
6 9     9   7575 use Ref::Util qw(is_scalarref is_blessed_arrayref);
  9         25296  
  9         19118  
7              
8             attributes(
9             decomp_matches => [rw, [ ], {lzy}],
10             [qw/options last/] => [rw, nan, {lzy}],
11             );
12              
13             sub preprocess {
14 13     13 1 276417 my ($self, $string) = @_;
15 13         48 my @orig_words = split / /, $string;
16              
17 13         24 my @converted_words;
18 13         27 foreach my $word ( @orig_words ) {
19 32         119 $word =~ s{[?!,]|but}{.}g;
20 32         75 push @converted_words, $word;
21             }
22              
23 13         40 my $formated = join ' ', @converted_words;
24 13         40 @converted_words = split /\./, $formated;
25 13         49 return @converted_words;
26             }
27              
28             sub postprocess {
29 19     19 1 313189 my ($self, $string) = @_;
30 19 50       121 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         40 $string =~ tr/ / /s; # Eliminate any duplicate space characters.
36 13         73 $string =~ s/[ ][?]$/?/; # Eliminate any spaces before the question mark.
37             }
38 19         67 return $string;
39             }
40              
41             sub _test_quit {
42 19     19   363669 my ($self, $string) = @_;
43 19         31 foreach my $quitword (@{$self->options->data->quit}) {
  19         422  
44 29 100       434 return 1 if $string =~ m{$quitword}xms;
45             }
46             }
47              
48             sub _debug_memory {
49 6     6   312148 my $self = shift;
50 6         12 my @memory = @{$self->options->memory};
  6         146  
51 6         57 my $string = sprintf("%s item(s) in memory stack:\n", scalar @memory);
52 6         34 foreach my $msg (@memory) {
53 21         44 $string .= sprintf("\t\t->%s\n", $msg);
54             }
55 6         32 return $string;
56             }
57              
58             sub transform {
59 13     13 1 316716 my ($self, $string, $use_memory) = @_;
60              
61 13         26 my ($this_decomp, $reasmbkey);
62 13         38 my $options = $self->options;
63 13 50       39 $options->debug_text(sprintf("\t[Pulling string \"%s\" from memory.]\n", $string))
64             if $use_memory;
65              
66 13 100       36 if ($self->_test_quit($string)){
67 5         16 $self->last(1);
68 5         75 return $options->data->final->[ $options->myrand(scalar @{$options->data->final}) ];
  5         126  
69             }
70              
71             # Default to a really low rank.
72 8         161 my $rank = -2;
73 8         13 my $reasmb = "";
74 8         13 my $goto = "";
75              
76             # First run the string through preprocess.
77 8         25 my @string_parts = $self->preprocess( $string );
78              
79 8         182 $self->decomp_matches([]);
80             # Examine each part of the input string in turn.
81 8         48 foreach my $string_part (@string_parts) {
82              
83             # Run through the whole list of keywords.
84 8         11 KEYWORD: foreach my $keyword (keys %{$options->data->decomp}) {
  8         166  
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     622 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         271 $rank = $options->data->key->{$keyword};
97 6   50     267 $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         399 foreach my $decomp (@{$options->data->decomp->{$keyword}}) {
  6         132  
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         159 $this_decomp = $decomp;
109 6         39 $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       19 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         106 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       240 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         63 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         135  
137            
138 6         151 $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         76 $reasmbkey = join $;, $keyword, $decomp;
146             # Get the list of possible reassembly rules for this key.
147 6         10 my @these_reasmbs = @{$options->data->reasmb->{$reasmbkey}};
  6         98  
148              
149             # Pick out a reassembly rule at random :).
150 6         169 $reasmb = $these_reasmbs[ $options->myrand( scalar @these_reasmbs ) ];
151 6         135 $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       129 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         114 my $decomp_matches = $self->decomp_matches;
171 6         31 foreach my $match (@{$decomp_matches}) {
  6         13  
172 6         18 $match->{matches} = $self->postprocess( $match->{matches} );
173 6         17 for (my $i = 1; $i < 10; $i++) {
174 54         521 $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         25 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       38 $reasmb = $self->transform("xnone", "") if $reasmb eq "";
195            
196 8         46 $reasmb = $self->postprocess($reasmb);
197            
198 8 50       202 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       101 shift @{$options->memory} if scalar @{$options->memory} >= $options->max_memory_size;
  0         0  
  8         263  
202             # push in the current reasem string
203 8         327 push @{$options->memory}, $reasmb;
  8         145  
204              
205             $options->debug_text(sprintf("%s \t%d item(s) in memory.\n",
206 8         201 $options->debug_text, scalar @{$options->memory} ));
  8         184  
207             }
208              
209             # Save the return string so that forgetful calling programs
210             # can ask the bot what the last reply was.
211 8         387 $options->transform_text($reasmb);
212 8         124 return $reasmb;
213             }
214              
215             1;
216              
217             __END__