X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flanguage%2Fcommand.c;h=80df91432962506524e4509c8f8fb71b7bf1abdb;hb=5a6b751888278c8c849ab0f4adf99f1be610e610;hp=a6805898bafb751776f13d502905e2857b04ee62;hpb=325168fb62e51d5154e932abec91ba40dd081f3f;p=pspp-builds.git diff --git a/src/language/command.c b/src/language/command.c index a6805898..80df9143 100644 --- a/src/language/command.c +++ b/src/language/command.c @@ -28,6 +28,7 @@ #include "data/procedure.h" #include "data/settings.h" #include "data/variable.h" +#include "language/lexer/command-name.h" #include "language/lexer/lexer.h" #include "language/prompt.h" #include "libpspp/assertion.h" @@ -115,7 +116,6 @@ static const size_t command_cnt = sizeof commands / sizeof *commands; static bool in_correct_state (const struct command *, enum cmd_state); static bool report_state_mismatch (const struct command *, enum cmd_state); -static const struct command *find_command (const char *name); static void set_completion_state (enum cmd_state); /* Command parser. */ @@ -240,257 +240,23 @@ do_parse_command (struct lexer *lexer, return result; } -static size_t -match_strings (const char *a, size_t a_len, - const char *b, size_t b_len) -{ - size_t match_len = 0; - - while (a_len > 0 && b_len > 0) - { - /* Mismatch always returns zero. */ - if (toupper ((unsigned char) *a++) != toupper ((unsigned char) *b++)) - return 0; - - /* Advance. */ - a_len--; - b_len--; - match_len++; - } - - return match_len; -} - -/* Returns the first character in the first word in STRING, - storing the word's length in *WORD_LEN. If no words remain, - returns a null pointer and stores 0 in *WORD_LEN. Words are - sequences of alphanumeric characters or single - non-alphanumeric characters. Words are delimited by - spaces. */ -static const char * -find_word (const char *string, size_t *word_len) -{ - /* Skip whitespace and asterisks. */ - while (isspace ((unsigned char) *string)) - string++; - - /* End of string? */ - if (*string == '\0') - { - *word_len = 0; - return NULL; - } - - /* Special one-character word? */ - if (!isalnum ((unsigned char) *string)) - { - *word_len = 1; - return string; - } - - /* Alphanumeric word. */ - *word_len = 1; - while (isalnum ((unsigned char) string[*word_len])) - (*word_len)++; - - return string; -} - -/* Returns true if strings A and B can be confused based on - their first three letters. */ -static bool -conflicting_3char_prefixes (const char *a, const char *b) -{ - size_t aw_len, bw_len; - const char *aw, *bw; - - aw = find_word (a, &aw_len); - bw = find_word (b, &bw_len); - assert (aw != NULL && bw != NULL); - - /* Words that are the same don't conflict. */ - if (aw_len == bw_len && !buf_compare_case (aw, bw, aw_len)) - return false; - - /* Words that are otherwise the same in the first three letters - do conflict. */ - return ((aw_len > 3 && bw_len > 3) - || (aw_len == 3 && bw_len > 3) - || (bw_len == 3 && aw_len > 3)) && !buf_compare_case (aw, bw, 3); -} - -/* Returns true if CMD can be confused with another command - based on the first three letters of its first word. */ -static bool -conflicting_3char_prefix_command (const struct command *cmd) -{ - assert (cmd >= commands && cmd < commands + command_cnt); - - return ((cmd > commands - && conflicting_3char_prefixes (cmd[-1].name, cmd[0].name)) - || (cmd < commands + command_cnt - && conflicting_3char_prefixes (cmd[0].name, cmd[1].name))); -} - -/* Ways that a set of words can match a command name. */ -enum command_match - { - MISMATCH, /* Not a match. */ - PARTIAL_MATCH, /* The words begin the command name. */ - COMPLETE_MATCH /* The words are the command name. */ - }; - -/* Figures out how well the WORD_CNT words in WORDS match CMD, - and returns the appropriate enum value. If WORDS are a - partial match for CMD and the next word in CMD is a dash, then - *DASH_POSSIBLE is set to 1 if DASH_POSSIBLE is non-null; - otherwise, *DASH_POSSIBLE is unchanged. */ -static enum command_match -cmd_match_words (const struct command *cmd, - char *const words[], size_t word_cnt, - int *dash_possible) -{ - const char *word; - size_t word_len; - size_t word_idx; - - for (word = find_word (cmd->name, &word_len), word_idx = 0; - word != NULL && word_idx < word_cnt; - word = find_word (word + word_len, &word_len), word_idx++) - if (word_len != strlen (words[word_idx]) - || buf_compare_case (word, words[word_idx], word_len)) - { - size_t match_chars = match_strings (word, word_len, - words[word_idx], - strlen (words[word_idx])); - if (match_chars == 0) - { - /* Mismatch. */ - return MISMATCH; - } - else if (match_chars == 1 || match_chars == 2) - { - /* One- and two-character abbreviations are not - acceptable. */ - return MISMATCH; - } - else if (match_chars == 3) - { - /* Three-character abbreviations are acceptable - in the first word of a command if there are - no name conflicts. They are always - acceptable after the first word. */ - if (word_idx == 0 && conflicting_3char_prefix_command (cmd)) - return MISMATCH; - } - else /* match_chars > 3 */ - { - /* Four-character and longer abbreviations are - always acceptable. */ - } - } - - if (word == NULL && word_idx == word_cnt) - { - /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR"}. */ - return COMPLETE_MATCH; - } - else if (word == NULL) - { - /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR", "BAZ"}. */ - return MISMATCH; - } - else - { - /* cmd->name = "FOO BAR BAZ", words[] = {"FOO", "BAR"}. */ - if (word[0] == '-' && dash_possible != NULL) - *dash_possible = 1; - return PARTIAL_MATCH; - } -} - -/* Returns the number of commands for which the WORD_CNT words in - WORDS are a partial or complete match. If some partial match - has a dash as the next word, then *DASH_POSSIBLE is set to 1, - otherwise it is set to 0. */ static int -count_matching_commands (char *const words[], size_t word_cnt, - int *dash_possible) +find_best_match (struct substring s, const struct command **matchp) { const struct command *cmd; - int cmd_match_count; + struct command_matcher cm; + int missing_words; - cmd_match_count = 0; - *dash_possible = 0; - for (cmd = commands; cmd < commands + command_cnt; cmd++) - if (cmd_match_words (cmd, words, word_cnt, dash_possible) != MISMATCH) - cmd_match_count++; + command_matcher_init (&cm, s); + for (cmd = commands; cmd < &commands[command_cnt]; cmd++) + command_matcher_add (&cm, ss_cstr (cmd->name), CONST_CAST (void *, cmd)); - return cmd_match_count; -} - -/* Returns the command for which the WORD_CNT words in WORDS are - a complete match. Returns a null pointer if no such command - exists. */ -static const struct command * -get_complete_match (char *const words[], size_t word_cnt) -{ - const struct command *cmd; + *matchp = command_matcher_get_match (&cm); + missing_words = command_matcher_get_missing_words (&cm); - for (cmd = commands; cmd < commands + command_cnt; cmd++) - if (cmd_match_words (cmd, words, word_cnt, NULL) == COMPLETE_MATCH) - return cmd; + command_matcher_destroy (&cm); - return NULL; -} - -/* Returns the command with the given exact NAME. - Aborts if no such command exists. */ -static const struct command * -find_command (const char *name) -{ - const struct command *cmd; - - for (cmd = commands; cmd < commands + command_cnt; cmd++) - if (!strcmp (cmd->name, name)) - return cmd; - NOT_REACHED (); -} - -/* Frees the WORD_CNT words in WORDS. */ -static void -free_words (char *words[], size_t word_cnt) -{ - size_t idx; - - for (idx = 0; idx < word_cnt; idx++) - free (words[idx]); -} - -/* Flags an error that the command whose name is given by the - WORD_CNT words in WORDS is unknown. */ -static void -unknown_command_error (struct lexer *lexer, char *const words[], size_t word_cnt) -{ - if (word_cnt == 0) - lex_error (lexer, _("expecting command name")); - else - { - struct string s; - size_t i; - - ds_init_empty (&s); - for (i = 0; i < word_cnt; i++) - { - if (i != 0) - ds_put_byte (&s, ' '); - ds_put_cstr (&s, words[i]); - } - - msg (SE, _("Unknown command %s."), ds_cstr (&s)); - - ds_destroy (&s); - } + return missing_words; } /* Parse the command name and return a pointer to the corresponding @@ -499,93 +265,74 @@ unknown_command_error (struct lexer *lexer, char *const words[], size_t word_cnt static const struct command * parse_command_name (struct lexer *lexer) { - char *words[16]; - int word_cnt; - int complete_word_cnt; - int dash_possible; - - if (lex_token (lexer) == T_EXP || - lex_token (lexer) == '*' || lex_token (lexer) == '[') - return find_command ("COMMENT"); - - dash_possible = 0; - word_cnt = complete_word_cnt = 0; - while (lex_token (lexer) == T_ID || (dash_possible && lex_token (lexer) == '-')) - { - int cmd_match_cnt; + const struct command *command; + int missing_words; + struct string s; - assert (word_cnt < sizeof words / sizeof *words); - if (lex_token (lexer) == T_ID) - { - words[word_cnt] = ds_xstrdup (lex_tokstr (lexer)); - str_uppercase (words[word_cnt]); - } - else if (lex_token (lexer) == '-') - words[word_cnt] = xstrdup ("-"); - word_cnt++; + if (lex_token (lexer) == T_EXP + || lex_token (lexer) == '*' + || lex_token (lexer) == '[') + { + static const struct command c = { S_ANY, 0, "COMMENT", cmd_comment }; + return &c; + } - cmd_match_cnt = count_matching_commands (words, word_cnt, - &dash_possible); - if (cmd_match_cnt == 0) - break; - else if (cmd_match_cnt == 1) + command = NULL; + missing_words = 0; + ds_init_empty (&s); + for (;;) + { + if (lex_token (lexer) == '-') + ds_put_byte (&s, '-'); + else if (lex_token (lexer) == T_ID) { - const struct command *command = get_complete_match (words, word_cnt); - if (command != NULL) - { - if (!(command->flags & F_KEEP_FINAL_TOKEN)) - lex_get (lexer); - free_words (words, word_cnt); - return command; - } + if (!ds_is_empty (&s) && ds_last (&s) != '-') + ds_put_byte (&s, ' '); + ds_put_cstr (&s, lex_tokid (lexer)); } - else /* cmd_match_cnt > 1 */ + else if (lex_is_integer (lexer) && lex_integer (lexer) >= 0) { - /* Do we have a complete command name so far? */ - if (get_complete_match (words, word_cnt) != NULL) - complete_word_cnt = word_cnt; + if (!ds_is_empty (&s) && ds_last (&s) != '-') + ds_put_byte (&s, ' '); + ds_put_format (&s, "%ld", lex_integer (lexer)); } + else + break; + + missing_words = find_best_match (ds_ss (&s), &command); + if (missing_words <= 0) + break; + lex_get (lexer); } - /* If we saw a complete command name earlier, drop back to - it. */ - if (complete_word_cnt) + if (command == NULL && missing_words > 0) { - int pushback_word_cnt; - const struct command *command; - - /* Get the command. */ - command = get_complete_match (words, complete_word_cnt); - assert (command != NULL); - - /* Figure out how many words we want to keep. - We normally want to swallow the entire command. */ - pushback_word_cnt = complete_word_cnt + 1; - if (command->flags & F_KEEP_FINAL_TOKEN) - pushback_word_cnt--; - - /* FIXME: We only support one-token pushback. */ - assert (pushback_word_cnt + 1 >= word_cnt); - - while (word_cnt > pushback_word_cnt) - { - word_cnt--; - if (strcmp (words[word_cnt], "-")) - lex_put_back_id (lexer, words[word_cnt]); - else - lex_put_back (lexer, '-'); - free (words[word_cnt]); - } + ds_put_cstr (&s, " ."); + missing_words = find_best_match (ds_ss (&s), &command); + ds_truncate (&s, ds_length (&s) - 2); + } - free_words (words, word_cnt); - return command; + if (command == NULL) + { + if (ds_is_empty (&s)) + lex_error (lexer, _("expecting command name")); + else + msg (SE, _("Unknown command `%s'."), ds_cstr (&s)); + } + else if (missing_words == 0) + { + if (!(command->flags & F_KEEP_FINAL_TOKEN)) + lex_get (lexer); + } + else if (missing_words < 0) + { + assert (missing_words == -1); + assert (!(command->flags & F_KEEP_FINAL_TOKEN)); } - /* We didn't get a valid command name. */ - unknown_command_error (lexer, words, word_cnt); - free_words (words, word_cnt); - return NULL; + ds_destroy (&s); + return command; } /* Returns true if COMMAND is allowed in STATE,