#include #include #include #include #include #include #include "ucore/string.h" #include "ucore/strvec.h" #include "ucore/utils.h" #include "ucore/backtrace.h" // enum UCCmdRc { UC_CMDRC_OK, /**Executed with warning */ UC_CMDRC_WARNING, /*incomplete command */ UC_CMDRC_INCOMPLETE, /*Command cann't uniquely be distinguished */ UC_CMDRC_AMBIGUOUS, /* Command doesn't exist*/ UC_CMDRC_NOT_FOUND, /*Not executed, generic error */ UC_CMDRC_ERROR, }; struct UCCmdArgs; typedef enum UCCmdRc (*UCCmdHandler)(const struct UCCmdArgs *args); enum UCCmdType { UC_CMD_ROOT = 1, UC_CMD_KEYWORD, UC_CMD_LIST, UC_CMD_STRING, UC_CMD_END, }; struct UCCmdWord { char *word; char *descr; }; struct UCCmdList { struct UCStrv words; struct UCStrv descrs; }; /** * Command are broken into words and inserted into a tree. * the 3 commands: * show all * date * show all bottles * Results in the tree: * * +--------+ child +--------+ child +-------+ child +-----+ * | root |------->| show |------->| all |------>| END | * +--------+ +--------+ +-------+ +-----+ * | next | next * V V * +--------+ child +-----+ +--------+ child +-----+ * | date |-----> | END | | bottles|------>| END | * +--------+ +-----+ +--------+ +-----+ * * END nodes are complete commands. Other child/next pointers will be nULL */ struct UCCmdNode { enum UCCmdType type; union { struct UCCmdWord word; struct UCCmdList list; struct UCCmdDef *cmd; } data; uint32_t flags; struct UCCmdNode *next; //next at same level struct UCCmdNode *child; }; struct UCCmdRunner { struct UCCmdNode *root_node; struct UCCmdNode _root; }; struct UCCmdDef { const char *cmd; const char *help; UCCmdHandler callback; uint32_t flags; }; //max words in a single command #define UC_CMD_MAX_WORDS 32 struct UCCmdTokenizer { char *cmd_copy; char *cmd_words[UC_CMD_MAX_WORDS]; int num_words; }; struct UCCmdArgs { struct UCStrv argv; struct UCCmdDef *cmd; }; void print_args(const struct UCStrv *args); static int uc_cmd_type_is_variable(enum UCCmdType type) { return type == UC_CMD_LIST || type == UC_CMD_STRING; } static void uc_cmd_tokenize(struct UCCmdTokenizer *t, const char *cmd, const char *delim) { t->num_words = 0; t->cmd_copy = strdup(cmd); if (t->cmd_copy == NULL) { assert(t->cmd_copy); return; } if (delim == NULL) { delim = "\r\n\t\v "; } t->num_words = getfields(t->cmd_copy, t->cmd_words, ARRAY_SIZE(t->cmd_words), 1, delim); } static void uc_cmd_tokenize_destroy(struct UCCmdTokenizer *t) { free(t->cmd_copy); t->num_words = 0; t->cmd_copy = NULL; } void uc_cmd_runner_init(struct UCCmdRunner *r) { memset(r, 0, sizeof *r); r->root_node = &r->_root; r->_root.type = UC_CMD_KEYWORD; } void uc_cmd_node_dealloc(struct UCCmdNode *n) { struct UCCmdNode *next; while (n) { uc_cmd_node_dealloc(n->child); next = n->next; if (n->type == UC_CMD_KEYWORD) { free(n->data.word.word); free(n->data.word.descr); } else if (n->type == UC_CMD_LIST) { uc_strv_destroy(&n->data.list.words); uc_strv_destroy(&n->data.list.descrs); } else if (n->type == UC_CMD_STRING) { free(n->data.word.word); free(n->data.word.descr); } free(n); n = next; } } void uc_cmd_runner_destroy(struct UCCmdRunner *r) { uc_cmd_node_dealloc(r->root_node->child); r->root_node->child = NULL; } static struct UCCmdNode * uc_cmd_node_alloc(enum UCCmdType type, uint32_t flags) { struct UCCmdNode *n; n = calloc(1, sizeof *n); if (n == NULL) { return NULL; } n->type = type; n->flags = flags; return n; } static struct UCCmdNode * uc_cmd_node_create(const char *word) { struct UCCmdNode *node = NULL; if (word[0] == '(') { //list (foo|bar|baz) struct UCCmdTokenizer t; uc_cmd_tokenize(&t, word, "() |\t\v"); if (t.num_words > 0) { node = uc_cmd_node_alloc(UC_CMD_LIST, 0); uc_strv_init(&node->data.list.words); uc_strv_init(&node->data.list.descrs); for (int i = 0 ; i < t.num_words; i++) { uc_strv_append(&node->data.list.words, t.cmd_words[i]); } } uc_cmd_tokenize_destroy(&t); } else if (isupper(word[0])) { node = uc_cmd_node_alloc(UC_CMD_STRING, 0); node->data.word.word = strdup(word); } else { node = uc_cmd_node_alloc(UC_CMD_KEYWORD, 0); node->data.word.word = strdup(word); } return node; } static int uc_cmd_node_match(const struct UCCmdNode *n, const char *word, struct UCStrv *args) { size_t word_len; int found = 0; if (word == NULL || word[0] == 0) { return 0; } word_len = strlen(word); if (n->type == UC_CMD_KEYWORD) { if (strncmp(word, n->data.word.word, word_len) == 0) { if (args != NULL) { uc_strv_append(args, n->data.word.word); } found = 1; } } else if (n->type == UC_CMD_LIST) { const struct UCStrv *v = &n->data.list.words; assert(v); for(size_t i = 0; i < v->cnt; i++) { if (strncmp(word, v->strings[i], word_len) == 0) { if (args != NULL) { uc_strv_append(args, v->strings[i]); } found = 1; } } } else if (n->type == UC_CMD_STRING) { if (args != NULL) { uc_strv_append(args, word); } found = 1; } return found; } static struct UCCmdNode * uc_cmd_add(struct UCCmdNode *parent, const char *word) { struct UCCmdNode *new_node; struct UCCmdNode **iter = &parent->child; assert(parent); new_node = uc_cmd_node_create(word); if (new_node == NULL) { return NULL; } while (*iter) { struct UCCmdNode *n = *iter; if (uc_cmd_type_is_variable(new_node->type) && n->type != UC_CMD_END) { //we don't support combinations of keywords and variables //at the same level. uc_cmd_node_dealloc(new_node); return NULL; } //TODO check for ambiguous word (e.g. add "short" when "shorter" already exists) if (new_node->type == UC_CMD_KEYWORD && uc_cmd_node_match(n, word, NULL)) { uc_cmd_node_dealloc(new_node); return *iter; } iter = &n->next; } *iter = new_node; return new_node; } int uc_cmd_install_noassert(struct UCCmdRunner *r, struct UCCmdDef *cmd) { struct UCCmdNode *node = r->root_node; struct UCCmdNode *new_node; struct UCCmdTokenizer t; uc_cmd_tokenize(&t, cmd->cmd, NULL); if (t.num_words <= 0) { uc_cmd_tokenize_destroy(&t); return -1; } for (int i = 0; i < t.num_words; i++) { node = uc_cmd_add(node, t.cmd_words[i]); if (node == NULL) { uc_cmd_tokenize_destroy(&t); return -2; } } //TODO, check for duplicate command here. new_node = uc_cmd_node_alloc(UC_CMD_END, 0); if (new_node == NULL) { uc_cmd_tokenize_destroy(&t); return -1; } new_node->data.cmd = cmd; new_node->next = node->child; node->child = new_node; uc_cmd_tokenize_destroy(&t); return 0; } int uc_cmd_install(struct UCCmdRunner *r, struct UCCmdDef *cmd) { int rc; rc = uc_cmd_install_noassert(r, cmd); UC_ASSERT(rc == 0); return rc; } enum UCCmdRc uc_cmd_find(struct UCCmdNode *first, const struct UCCmdTokenizer *t, struct UCStrv *args, struct UCCmdNode **result) { struct UCCmdNode *node = first; enum UCCmdRc rc = UC_CMDRC_NOT_FOUND; for (int i = 0; i < t->num_words && node; i++) { int n_found = 0; struct UCCmdNode *found = NULL; while (node) { if (uc_cmd_node_match(node, t->cmd_words[i], args)) { if (n_found == 0) { //keep the 1. matched found = node; } n_found++; } node = node->next; } if (n_found == 1) { node = found->child; rc = UC_CMDRC_OK; } else if (n_found > 1) { node = found; rc = UC_CMDRC_AMBIGUOUS; break; } } *result = node; if (node == NULL) { rc = UC_CMDRC_NOT_FOUND; } return rc; } enum UCCmdRc uc_cmd_parse_and_find(struct UCCmdRunner *r, const char *cmd_line, struct UCStrv *args, struct UCCmdNode **result) { struct UCCmdNode *node = r->root_node->child; struct UCCmdTokenizer t; enum UCCmdRc rc = UC_CMDRC_ERROR; //make a copy since getfields destroys the original string uc_cmd_tokenize(&t, cmd_line, NULL); if (t.num_words > 0) { //match the commands towards the command tree // rc = uc_cmd_find(node, &t, args, result); } uc_cmd_tokenize_destroy(&t); return rc; } enum UCCmdRc uc_cmd_complete(struct UCCmdRunner *r, const char *cmd_line) { struct UCStrv completions = UC_STRV_INITIALIZER; struct UCCmdNode *found = NULL; enum UCCmdRc rc; rc = uc_cmd_parse_and_find(r, cmd_line, NULL, &found); if (rc != UC_CMDRC_NOT_FOUND && found != NULL) { struct UCCmdNode *node = found; while (node) { if (node->type == UC_CMD_END) { uc_strv_append(&completions, ""); } else if (node->type == UC_CMD_LIST) { uc_strv_append_all(&completions, &node->data.list.words, 1); } else if (node->type == UC_CMD_STRING || node->type == UC_CMD_KEYWORD) { uc_strv_append(&completions, node->data.word.word); } node = node->next; } printf("Possible completions for '%s'\n", cmd_line); print_args(&completions); } else { printf("No possible completions\n"); } uc_strv_destroy(&completions); return rc; } enum UCCmdRc uc_cmd_run(struct UCCmdRunner *r, const char *cmd_line) { struct UCCmdArgs args = { .argv = UC_STRV_INITIALIZER, .cmd = NULL, }; struct UCCmdNode *found = NULL; enum UCCmdRc rc; rc = uc_cmd_parse_and_find(r, cmd_line, &args.argv, &found); if (rc == UC_CMDRC_OK) { // Now check the current level for any UC_CMD_END // which indicates a complete command while (found) { if (found->type == UC_CMD_END) { break; } found = found->next; } if (found != NULL) { struct UCCmdDef *cmd = found->data.cmd; args.cmd = cmd; rc = cmd->callback(&args); } else { rc = UC_CMDRC_INCOMPLETE; } } uc_strv_destroy(&args.argv); return rc; } void print_args(const struct UCStrv *args) { for (size_t i = 0; i < args->cnt; i++) { printf("arg %zu: '%s' ", i, args->strings[i]); } puts(""); } enum UCCmdRc cmd1_cb(const struct UCCmdArgs *args) { puts(__func__); print_args(&args->argv); return UC_CMDRC_OK; } enum UCCmdRc cmd2_cb(const struct UCCmdArgs *args) { puts(__func__); print_args(&args->argv); return UC_CMDRC_OK; } enum UCCmdRc cmd3_cb(const struct UCCmdArgs *args) { puts(__func__); print_args(&args->argv); return UC_CMDRC_OK; } enum UCCmdRc cmd4_cb(const struct UCCmdArgs *args) { puts(__func__); print_args(&args->argv); return UC_CMDRC_OK; } int main(int argc, char *argv[]) { char line[128]; struct UCCmdRunner r; struct UCCmdDef cmd1 = { "show all", "\n\n", cmd1_cb, 0, NULL }; struct UCCmdDef cmd2 = { "show empty", "\n\n", cmd2_cb, 0, NULL }; struct UCCmdDef cmd3 = { "show empty bottle", "\n\n\n", cmd3_cb, 0, NULL }; struct UCCmdDef cmd4 = { "delete file NAME", "\n\nName of the file\n", cmd4_cb, 0, NULL }; uc_cmd_runner_init(&r); uc_cmd_install(&r, &cmd1); uc_cmd_install(&r, &cmd2); uc_cmd_install(&r, &cmd3); uc_cmd_install(&r, &cmd4); uc_cmd_run(&r, "delete file /tmp/a"); uc_cmd_run(&r, "show all"); uc_cmd_run(&r, "show empty"); uc_cmd_run(&r, "sh emp b"); uc_cmd_run(&r, ""); /* while (fgets(line,sizeof line, stdin)) { enum UCCmdRc rc = uc_cmd_run(&r, line); if (rc != UC_CMDRC_OK) { printf("Command failed with code %d\n", rc); } } */ uc_cmd_complete(&r, "sho em"); uc_cmd_complete(&r, "show "); uc_cmd_complete(&r, ""); uc_cmd_runner_destroy(&r); return 1; }