File Coverage

blib/lib/Eliza/Chatbot/Brain.pm
Criterion Covered Total %
statement 87 100 87.0
branch 17 24 70.8
condition 5 8 62.5
subroutine 8 8 100.0
pod 3 3 100.0
total 120 143 83.9


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