Files
libucore/src/cmd/cmd.c
T
2015-12-10 18:52:09 +01:00

451 lines
9.3 KiB
C

#include <stdint.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#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;
}