in reply to Re^3: [challenge] Nested autocompletion -- results and some question
in thread [challenge] Nested autocompletion

Does this help understanding of the regexes that were used?

#!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11150326 use warnings; # constant autocomplete use List::Util qw( uniq ); use Term::ReadKey; $| = 1; my @animals = (qw( cow camel dog cat )); my @foods = (qw( fish pasture meat )); my @places = (qw( wood sea desert )); my $commands = { select => { completion_list => \@animals, commands => { give => { completion_list => \@foods, }, take_to => { completion_list => \@places, }, }, }, kill => { completion_list => \@animals, } }; my $completed = autocomplete( $commands, 'auto> ' ); print $completed ? "\nThe user entered: $completed\n" : "\nEscape\n"; exit; ###################################################################### sub autocomplete { my $commands = shift; my $prompt = shift // '> '; my $lines = ref $commands ? join "\n", lines($commands), '' : $comma +nds; my $input = ''; my ($clearline, $color, $reset) = ("\e[G\e[K", "\e[32m", "\e[m"); ReadMode 'raw'; eval { while() { $input = "$input\n$lines" =~ /^(.*).*\n(?:.*\n)*\1/ ? $1 : ''; #Trims back $input so it only contains a string that exists in one of #the valid lines. It's looking for the longest initial string that als +o #starts one of the other lines. The (?:.*\n)* allows for skipping over #any lines that do not match. $input = $lines =~ s/^(?!\Q$input\E).*\n//gmr =~ #Removes from $lines any line that does not start with $input. Returns #a multiline string where every line starts with $input. /^(.*).*\n(?:\1.*\n)*\z/ ? $1 : ''; #Finds the longest initial substring that starts every line. This will #extend the match until the next decision point that requires user inp +ut. my $words = join ' ', sort + uniq $lines =~ /^\Q$input\E ?(\S+)/ +gm; #Finds the next word after the matching part of each valid line. $lines =~ /^$input\n/m and $words = '*** Completed!'; #Matches $input against each valid line looking for a complete line ma +tch. my $backup = "\e[" . ( 2 + length $words ) . "D"; print "$clearline$prompt$input $color$words$reset$backup"; my $char = ReadKey 0; $char =~ tr/\e\cc// and $input = '', last; $char =~ tr/\n\r// and $lines =~ /^$input$/m ? last : next; $char =~ tr/ -~// and $input .= $char; if( $char =~ tr/\b\x7f// ) # backspace { my $match = 1 + ( () = $lines =~ /^\Q$input\E/gm ); #Counts how many lines are still valid matches, that is they are still #possibilities that require a user decision at this point. Adds 1 #because we need to go back to the previous decision point which will #(by definition) have at least one more valid match. $input = "$input\n$lines" =~ /^(.*).*\n(?:(?:(?!\1).*\n)*\1.*\n){$match}/ ? $1 : ''; #Finds the longest initial substring that also is the initial substrin +g #of $match following lines. Lines that do not start with the candidate #initial match are skipped by (?:(?!\1).*\n)* . This longest initial #substring is the previous decision point, and is then stored into $in +put. } } 1; }; my $error = $@; ReadMode 'restore'; print "$error\n"; return $input; } sub lines { my $cmd = shift or return ''; map s/ +$//r, map { my $key = $_; # fun triple map nesting map { my $prev = $_; map "$key $_ $prev", @{ $cmd->{$key}{completion_list} }; } lines( $cmd->{$key}{commands} ) } keys %$cmd; }
  • Comment on Re^4: [challenge] Nested autocompletion -- results and some question
  • Download Code

Replies are listed 'Best First'.
Re^5: [challenge] Nested autocompletion -- winner program unveiled to me
by Discipulus (Canon) on Feb 24, 2023 at 11:10 UTC
    Hello tybalt89 and..

    > Does this help understanding of the regexes that were used?

    ..thanks! yes it helps a lot.

    I have modified your code to emit debug informations if invoked with an argument. I left your precious comment about regexes and passed it to perltidy

    Here the output of perl autocomplete-nested-winner-unveiled.pl 1 if I enter the sequence: k BACKSPACE s c a m t ENTER d ENTER

    Some minor doubts:

    • what -~ in $char =~ tr/ -~// and $input .= $char are for?
    • $char =~ tr/\b\x7f// catches both backspace and delete choroba in the chat told some terminal mess them up. Is this the reason to catch them both?

    The code implementing the backspace is wizardry :)

    The multiline as only datastructure needed is very nice, I wonder how scalable can be, but works very nicely in this case.

    Thanks again!

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      $char =~ tr/ -~// and $input .= $char

      The '-' indicates a range, from ' ' to '~', which if you look at a basic ASCII chart, is all the plain printable (visible and take up one character position) characters.

      $char =~ tr/\b\x7f// is used because on my system the key labeled "Backspace" returns "\b" when running in xterm and "\x7f" when running on the Linux console. BTW, for your comment in the chatterbox, the \b in tr/\b\x7f// is an actual character "\x08" whereas the \b in a regex is a metacharacter and not an actual character.

      The code implementing the backspace is wizardry :)
      The idea is simple -> if you currently have N choices, find the longest initial substring that gives you more than N choices.
      The wizardry is the the design of regex itself, credit Larry and the implementors...
      The concept that regex finds the longest match is crucial in this program both for backspace and for advancing to the next decision point.