#include #include #include #include #include #include #include "ucore/string.h" #include "ucore/strvec.h" #include "ucore/utils.h" //max words in a single command #define UC_CMD_MAX_WORDS 32 enum UCCmdRc { /**Executed ok */ UC_CMDRC_OK, /**Executed with warning */ UC_CMDRC_WARNING, /*Not executed, incomplete command */ UC_CMDRC_INCOMPLETE, /*Command cann't uniquely be distinguished */ UC_CMDRC_AMBIGUOUS, /*Not executed, generic error */ UC_CMDRC_ERROR, }; typedef enum UCCmdRc (*uc_cmd_handler)(const struct UCStrv *args); enum UCCmdType { UC_CMD_ROOT = 1, UC_CMD_KEYWORD, UC_CMD_LIST, UC_CMD_STRING, UC_CMD_END, }; struct UCCmdNode { enum UCCmdType type; uint32_t flags; void *data; struct UCCmdNode *next; //next at same level struct UCCmdNode *child; }; //for UC_CMD_LIST, struct UCCmdList { char *word; struct UCCmdList *next; }; struct UCCmdRunner { struct UCCmdNode *root_node; struct UCCmdNode _root; }; struct UCCmdDef { const char *cmd; uc_cmd_handler callback; uint32_t flags; struct UCCmdDef *next; }; struct UCCmdTokenizer { char *cmd_copy; char *cmd_words[UC_CMD_MAX_WORDS]; int num_words; }; 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); } else if (n->type == UC_CMD_LIST) { uc_strv_destroy(n->data); free(n->data); } else if (n->type == UC_CMD_STRING) { free(n->data); } 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; } struct UCCmdNode *uc_cmd_node_alloc(enum UCCmdType type, void *data, uint32_t flags) { struct UCCmdNode *n; n = calloc(1, sizeof *n); if (n == NULL) { return NULL; } n->type = type; n->data = data; n->flags = flags; return n; } 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) { struct UCStrv *v = malloc(sizeof *v); uc_strv_init(v); for (int i = 0 ; i < t.num_words; i++) { uc_strv_append(v, t.cmd_words[i]); } node = uc_cmd_node_alloc(UC_CMD_LIST, v, 0); } uc_cmd_tokenize_destroy(&t); } else if (isupper(word[0])) { node = uc_cmd_node_alloc(UC_CMD_STRING, strdup(word), 0); } else { node = uc_cmd_node_alloc(UC_CMD_KEYWORD, strdup(word), 0); } return node; } static int uc_cmd_node_match(const struct UCCmdNode *n, const char *word, struct UCStrv *args) { size_t word_len; if (word == NULL || word[0] == 0) { return 0; } word_len = strlen(word); if (n->type == UC_CMD_KEYWORD) { if (strncmp(word, n->data, word_len) == 0) { return 1; } } else if (n->type == UC_CMD_LIST) { struct UCStrv *v = n->data; 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]); } return 1; } } } else if (n->type == UC_CMD_STRING) { if (args != NULL) { uc_strv_append(args, word); } return 1; } return 0; } 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 (new_node->type != UC_CMD_KEYWORD && 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(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) { return -2; } } //TODO, check for duplicate command here. new_node = uc_cmd_node_alloc(UC_CMD_END, cmd, 0); if (new_node == NULL) { return -1; } new_node->next = node->child; node->child = new_node; uc_cmd_tokenize_destroy(&t); return 0; } enum UCCmdRc uc_cmd_run(struct UCCmdRunner *r, const char *cmd_line) { struct UCCmdNode *node = r->root_node->child; struct UCCmdTokenizer t; enum UCCmdRc rc = UC_CMDRC_ERROR; struct UCStrv args = UC_STRV_INITIALIZER; //arguments to the callback function //make a copy since getfields destroys the original string uc_cmd_tokenize(&t, cmd_line, NULL); if (t.num_words <= 0) { uc_cmd_tokenize_destroy(&t); return rc; } //match the commands towards the command tree // 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)) { found = node; n_found++; } node = node->next; } if (n_found == 1) { node = found->child; } else if (n_found > 1) { uc_strv_destroy(&args); uc_cmd_tokenize_destroy(&t); return UC_CMDRC_AMBIGUOUS; } } // Now check the current level for any UC_CMD_END // which indicates a complete command while (node) { if (node->type == UC_CMD_END) { break; } node = node->next; } if (node != NULL) { struct UCCmdDef *cmd = node->data; rc = cmd->callback(&args); } else { rc = UC_CMDRC_INCOMPLETE; } uc_strv_destroy(&args); uc_cmd_tokenize_destroy(&t); return rc; } void print_args(const struct UCStrv *args) { for (size_t i = 0; i < args->cnt; i++) { printf("arg %zu:%s\n", i, args->strings[i]); } } enum UCCmdRc cmd1_cb(const struct UCStrv *args) { puts(__func__); print_args(args); return UC_CMDRC_OK; } enum UCCmdRc cmd2_cb(const struct UCStrv *args) { puts(__func__); print_args(args); return UC_CMDRC_OK; } enum UCCmdRc cmd3_cb(const struct UCStrv *args) { puts(__func__); print_args(args); return UC_CMDRC_OK; } enum UCCmdRc cmd4_cb(const struct UCStrv *args) { puts(__func__); print_args(args); return UC_CMDRC_OK; } int main(int argc, char *argv[]) { char line[128]; struct UCCmdRunner r; struct UCCmdDef cmd1 = { "show all", cmd1_cb, 0, NULL }; struct UCCmdDef cmd2 = { "show empty", cmd2_cb, 0, NULL }; struct UCCmdDef cmd3 = { "show empty (bottle|glass)", cmd3_cb, 0, NULL }; struct UCCmdDef cmd4 = { "delete file NAME", 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 g"); /* 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_runner_destroy(&r); return 1; }