From 723aea0582ff6905c9eebbe8d724d7b1a1d44cf1 Mon Sep 17 00:00:00 2001 From: Adrian Cochrane Date: Sun, 18 Oct 2020 11:58:58 +1300 Subject: [PATCH] Prepare for navigating links: Compute levenshtein distances. Read lines from stdin. --- src/main.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/src/main.c b/src/main.c index b02f35f..1dc3928 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "HsFFI.h" #include @@ -135,15 +136,80 @@ void speak(char *ssml, char *mark, char* fallback) { espeak_Synth_Mark(ssml, strlen(ssml)+1, fallback, 0, flags, NULL, NULL); else espeak_Synth(ssml, strlen(ssml)+1, 0, POS_CHARACTER, 0, flags, NULL, NULL); } + +/* Utilities */ +#define SWAP(type, a, b) {type temp = a; a = b; b = temp;} +int min(int a, int b) {return a < b ? a : b;} + +/* Keyboard input */ +int levenshtein_distance(const char *a, const char* b) { + int a_len = strlen(a); + int b_len = strlen(b); + if (a_len < b_len) { + SWAP(int, a_len, b_len); + SWAP(const char*, a, b); + } + int *v0 = malloc(sizeof(int)*b_len); + int *v1 = malloc(sizeof(int)*b_len); + + // Initialize v0 for an edit distance of an empty a + for (int i = 0; i < b_len; i++) v0[i] = i; + + // The core algorithm + for (int i = 0; i < a_len; i++) { + // Edit distance from empty string is delete i characters. + v1[0] = i; + + for (int j = 0; j < b_len - 1; j++) { + int deletion_cost = v0[j+1] + 1; // above cell + deletion + int insertion_cost = v1[j] + 1; // left cell + insertion + // top left cell + maybe substitution + int substitution_cost = v0[j] + (a[i] == b[j] ? 0 : 1); + + v1[j+1] = min(deletion_cost, min(insertion_cost, substitution_cost)); + } + + // Progress to next row + SWAP(int*, v0, v1); + } + return v0[b_len-1]; +} + +int MAX_DIST = 3; // Is this ideal? +char *select_link(char **links, char *command) { + // Pass 1, min distance + int score = INT_MAX; + for (int i = 0; strcmp(links[i], " ") != 0; i++) + score = min(score, levenshtein_distance(command, links[i])); + + // Pass 2: Is ambiguous? + int is_ambiguous = 0; + for (int i = 0; strcmp(links[i], " ") != 0; i++) + is_ambiguous |= score >= levenshtein_distance(command, links[i]) - MAX_DIST; + + // Pass 3: Retrieve answer + for (int i = 0; strcmp(links[i], " ") != 0; i += 3) { + if (score < levenshtein_distance(command, links[i]) && + score < levenshtein_distance(command, links[i+1]) && + score < levenshtein_distance(command, links[i+2])) continue; + if (!is_ambiguous) return links[i+2]; + + // TODO Apply formatting! + speak(links[i+2], NULL, NULL); speak(links[i], NULL, NULL); speak(links[i+1], NULL, NULL); + } + + return ""; +} + +struct termios stored_settings, no_echo; int read_keyboard = 0; int speak_finalize(char *ssml) { while (read_keyboard) { - char c = getc(stdin); - if (c == '\033') { + if (getc(stdin) == '\033') { char mark[200]; char fallback[200]; espeak_Cancel(); - c = getc(stdin); + char c = getc(stdin); if (c == 0 || c == -1 || c == '\033' || c == 'q') goto close; // skip [ switch (getc(stdin)) { case 'A': @@ -201,6 +267,15 @@ int speak_finalize(char *ssml) { speak(ssml, paragraph_no > 0 ? mark : NULL, NULL); break; } + } else { + // Read in a line + tcsetattr(0, TCSANOW, &stored_settings); + char *line = NULL; + size_t len = 0; + if (getline(&line, &len, stdin) < 0) { + fprintf(stderr, "Failed to read stdin line!\n"); + } else printf("%s\n", line); // TODO Do something with this! + tcsetattr(0, TCSANOW, &no_echo); } } espeak_ng_STATUS result = espeak_ng_Synchronize(); @@ -232,7 +307,6 @@ FILE *parse_opt_file() { return ret; } -struct termios stored_settings; int main(int argc, char **argv) { int speak_err = 0; hs_init(&argc, &argv); @@ -270,12 +344,12 @@ int main(int argc, char **argv) { case 'k': read_keyboard = 1; // Read input character by character, not line by line. - struct termios new_settings = stored_settings; - new_settings.c_lflag &= (~ICANON); - new_settings.c_lflag &= (~ECHO); - new_settings.c_cc[VTIME] = 1; - new_settings.c_cc[VMIN] = 0; - tcsetattr(0, TCSANOW, &new_settings); + no_echo = stored_settings; + no_echo.c_lflag &= (~ICANON); + no_echo.c_lflag &= (~ECHO); + no_echo.c_cc[VTIME] = 1; + no_echo.c_cc[VMIN] = 0; + tcsetattr(0, TCSANOW, &no_echo); break; case 'w': use_espeak = 1; -- 2.30.2