123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- #!/usr/bin/perl
- # Author: Daniel "Trizen" Șuteu
- # License: GPLv3
- # Date: 14 April 2014
- # Website: https://github.com/trizen
- # A basic A.I. concept, inspired by HAL9000.
- use utf8;
- use 5.014;
- use autodie;
- use warnings;
- no if $] >= 5.018, warnings => "experimental::smartmatch";
- # For saving the memory
- use Data::Dump qw(pp);
- # For contracting the words ("I am" into "I'm")
- use Lingua::EN::Contraction qw(contraction);
- # Stemming of words
- use Lingua::Stem qw(stem);
- # For correcting common mistakes
- use Lingua::EN::CommonMistakes qw(%MISTAKES_COMMON);
- use Lingua::EN::CommonMistakes qw(:no-defaults :american %MISTAKES_GB_TO_US);
- # UTF-8 ready
- use open IO => ':utf8';
- # Constants
- use constant {
- NAME => 'HAL8212',
- MEMORY_FILE => 'HAL8212.memory',
- };
- # For getting STDIN
- require Term::ReadLine;
- my $term = Term::ReadLine->new(NAME);
- # For tagging words
- require Lingua::EN::Tagger;
- my $ltag = Lingua::EN::Tagger->new;
- # For /dev/null
- use File::Spec qw();
- # Save memory
- sub save_mem {
- my ($memory) = @_;
- open my $fh, '>', MEMORY_FILE;
- print {$fh} <<"HEADER", "scalar ", pp($memory), "\n";
- #!/usr/bin/perl
- # This file is part of the ${\NAME} program.
- # Don't edit this file, unless you know what are you doing!
- # Updated on: ${\scalar localtime}
- # by: $0
- HEADER
- close $fh;
- }
- # Create the memory if doesn't exist
- if (not -e MEMORY_FILE) {
- save_mem(scalar {});
- }
- # Load the memory
- my $MEM = (do MEMORY_FILE);
- # Read or create memories
- sub hal {
- my ($items, $ref) = @_;
- foreach my $item (@{$items}) {
- $ref = ($ref->{$item} //= {});
- }
- return $ref;
- }
- # Speak the text (with espeak)
- sub speak {
- my ($text) = @_;
- state $null = File::Spec->devnull;
- `espeak -ven-us \Q$text\E 2>$null`;
- }
- # Transform GB to US (colour -> color)
- sub gb_to_us {
- my ($word) = @_;
- if (defined(my $us_word = $MISTAKES_GB_TO_US{$word})) {
- return $us_word;
- }
- return $word;
- }
- # Fix common mistakes
- sub fix_word {
- my ($word) = @_;
- if (defined(my $fixed_word = $MISTAKES_COMMON{$word})) {
- return $fixed_word;
- }
- return $word =~ s/^i('|$)/I$1/gr;
- }
- # Ask for a question
- sub ask_question {
- state $one = 'a';
- my $q = "Ask me $one question: ";
- if ($one eq 'a') {
- speak($q), $one = 'another';
- }
- my $question = $term->readline("\n[?] " . $q);
- if (not defined $question or $question eq '') {
- say "[!] Insert 'q' if you're bored already...";
- }
- elsif ($question eq 'q') {
- return;
- }
- return contraction($question =~ s/[<>]+//gr);
- }
- sub not_a_question {
- say "[*] This is not a question! :-)";
- speak("This is not a question!");
- }
- # Split a question into words
- sub get_words {
- my ($text) = @_;
- my @words;
- foreach my $word (split(' ', $text)) {
- my @ws;
- if ($word =~ s/([[:punct:]]+)\z//) {
- push @ws, $1;
- }
- push @words, gb_to_us(fix_word($word)), @ws;
- }
- return @words;
- }
- sub untag_word {
- my ($word) = @_;
- return scalar {$word =~ /^<([^>]+)>(.*?)<[^>]+>/s};
- }
- sub locate {
- my ($couple, $pairs, $pos) = @_;
- foreach my $i ($pos .. $#{$pairs}) {
- if (exists $pairs->[$i]{$couple->[0]}) {
- if (exists $couple->[1]) {
- if ($pairs->[$i]{$couple->[0]} eq $couple->[1]) {
- return $i;
- }
- }
- else {
- return $i;
- }
- }
- }
- return;
- }
- sub flip_pers {
- my (@pairs) = @_;
- my @output;
- foreach my $pair (@pairs) {
- my $val;
- if (defined($val = $pair->{prps})) {
- given (lc $val) {
- when ('your') {
- push @output, 'my';
- }
- when ('my') {
- push @output, 'your';
- }
- default {
- push @output, $val;
- }
- }
- }
- elsif (defined($val = $pair->{prp})) {
- given (lc $val) {
- when ('mine') {
- push @output, 'yours';
- }
- when ('yours') {
- push @output, 'mine';
- }
- when ('you') {
- push @output, 'I';
- }
- when ('I') {
- push @output, 'you';
- }
- default {
- push @output, $val;
- }
- }
- }
- elsif (defined($val = $pair->{vbp})) {
- given (lc $val) {
- when (['are', "'re"]) {
- push @output, 'am';
- }
- default {
- push @output, $val;
- }
- }
- }
- else {
- push @output, values %{$pair};
- }
- }
- return @output;
- }
- sub INIT {
- print <<"EOF";
- ********************************************************************************
- Hello there! My name is ${\NAME}.
- I'm a "Heuristically programmed ALgorithmic computer", a descendant of HAL9000.
- In this training program, I'm ready to answer and learn new things about your
- awesome world. So, please, don't hesitate and ask me anything. I'll try my best.
- ********************************************************************************
- EOF
- speak("Hello!");
- }
- while (1) {
- # Get a question
- my $question = ask_question() // last;
- # Split the question into words
- my @words = get_words($question);
- # Stem words
- my @s_words = grep { $_ ne '' } @{stem(@words)};
- # On empty questions, do this:
- @words || next;
- say join('--', @words);
- say join('==', @s_words);
- #say join('~~', $ltag->get_words($question));
- #my $xml = $ltag->add_tags(join(" ", @words));
- my $correct_q = join(' ', @words);
- my @pairs = map { untag_word($_) }
- split(' ', $ltag->add_tags($correct_q));
- pp \@pairs;
- my @requestion = flip_pers(@pairs);
- pp \@requestion;
- my $answer = 'yes'; # let's just assume
- =cut
- my @question;
- if (defined(my $i = locate([wp => 'what'], \@pairs, 0))) {
- if (defined(locate([vbz => "'s"], \@pairs, $i))) { # what is
- if (defined(my $j = locate(['prps'], \@pairs, $i))) { # what is your
- if ($pairs[$j]{prps} eq 'yours') {
- push @question, "my";
- while (defined(my $k = locate(['jj'], \@pairs, $j))) {
- push @question, $pairs[$k]{jj};
- $j = $k+1;
- }
- #if (defined(my $k = locate(['nn'], \@pairs,
- }
- }
- }
- }
- =cut
- =cut
- if (exists $pairs[0]{wp}) {
- if( $pairs[0]{wp} eq 'what'){
- if (exists $pairs[1]{vbz}) {
- if ($pairs[1]{vbz} eq "'s") { # what is
- }
- }
- }
- }
- =cut
- #say $xml;
- #pp \@pairs;
- =cut
- my $tags = xml2hash($xml);
- while (my ($key, $value) = each %{$tags}) {
- if (ref $value ne 'ARRAY') {
- $tags->{$key} = [$value];
- }
- }
- if (not exists $tags->{pp} or $tags->{pp}[-1] ne '?') {
- not_a_question();
- next;
- }
- pp $tags;
- =cut
- ##### NEEDS WORK #####
- =cut
- my $requestion = $question;
- $requestion =~ s/\byour\b/my/g; # your => my
- $requestion =~ s/\bare\b/am/g; # are => am
- $requestion =~ s/\byou\b/I/g; # you => I
- $requestion =~ s/\byours\b/mine/g; # yours => mine
- my $answer = $requestion;
- my $q_suffix = '';
- if ($answer =~ s/^what\h+//) {
- if ($answer =~ /am\b/) { } # ok
- elsif ($answer =~ s/^(\w+)\h*//) {
- $q_suffix = " $1";
- }
- }
- my $an_suffix = '';
- if ($answer =~ s/^how\h+//) {
- if ($answer =~ /^am\b/) { } # ok
- elsif ($answer =~ s/^(\w+)\h*//) {
- $an_suffix = " $1";
- }
- }
- $answer =~ s/^where\b\h*//;
- $answer =~ s/\bam\h+I\b/I am/g;
- $answer =~ s/\?+\z//;
- #$answer =~ s/^does\b\h*//;
- my @input = quotewords(qr/\s+/o, 0, $question);
- next if scalar(@input) == 0;
- my $ref = hal(\@input, $MEM);
- if (exists $ref->{ANSWER}) {
- print "[*] ";
- my $ans;
- if ($ref->{ANSWER} =~ /^(yes|no)[[:punct:]]?\z/i) {
- $ans = "\u\L$1\E!";
- }
- else {
- $ans = "\u$answer$q_suffix $ref->{ANSWER}$an_suffix.";
- }
- say $ans;
- speak($ans);
- }
- else {
- say "\n[*] I don't know... :(";
- speak("I don't know...");
- speak($requestion);
- my $input = $term->readline("[?] \u$requestion ");
- speak("Are you sure?");
- if ($term->readline("[!] Are you sure? ") =~ /^y/i) {
- $ref->{ANSWER} = $input;
- speak("Roger that!");
- }
- }
- =cut
- }
- # Save what we learned
- save_mem($MEM);
|