//---------------------------------------------------------------------------// // ==>/[_]\ fxlink: A community communication tool for CASIO calculators. // // |:::| Made by Lephe' as part of the fxSDK. // // \___/ License: MIT // //---------------------------------------------------------------------------// #include "tui.h" #include "command-util.h" #include #include #include #include //--- // Command parsing utilities //--- struct fxlink_tui_cmd fxlink_tui_cmd_parse(char const *input) { struct fxlink_tui_cmd cmd; cmd.argc = 0; cmd.argv = NULL; cmd.data = malloc(strlen(input) + 1); if(!cmd.data) return cmd; char const *escapes1 = "\\nter"; char const *escapes2 = "\\\n\t\e\r"; /* Whether a new word needs to be created at the next character */ bool word_finished = true; /* Offset into cmd.data */ int i = 0; /* Read words eagerly, appending to cmd.data as we go */ for(int j = 0; input[j]; j++) { int c = input[j]; /* Stop words at spaces */ if(isspace(c)) { if(!word_finished) cmd.data[i++] = 0; word_finished = true; continue; } /* Translate escapes */ if(c == '\\') { char *p = strchr(escapes1, input[j+1]); if(p) { c = escapes2[p - escapes1]; j++; } } /* Add a new word if necessary */ if(word_finished) { cmd.argv = realloc(cmd.argv, (++cmd.argc) * sizeof *cmd.argv); cmd.argv[cmd.argc - 1] = cmd.data + i; word_finished = false; } /* Copy literals */ cmd.data[i++] = c; } cmd.data[i++] = 0; cmd.argv = realloc(cmd.argv, (cmd.argc + 1) * sizeof *cmd.argv); cmd.argv[cmd.argc] = 0; return cmd; } void fxlink_tui_cmd_dump(struct fxlink_tui_cmd const *cmd) { print(TUI.wConsole, "[%d]", cmd->argc); for(int i = 0; i < cmd->argc; i++) { char const *arg = cmd->argv[i]; print(TUI.wConsole, " '%s'(%d)", arg, (int)strlen(arg)); } print(TUI.wConsole, "\n"); } void fxlink_tui_cmd_free(struct fxlink_tui_cmd const *cmd) { free(cmd->argv); free(cmd->data); } static struct fxlink_device *find_connected_device(void) { /* TODO: Use the "selected" device */ for(int i = 0; i < TUI.devices.count; i++) { if(TUI.devices.devices[i].status == FXLINK_FDEV_STATUS_CONNECTED) return &TUI.devices.devices[i]; } return NULL; } bool fxlink_tui_parse_args(int argc, char const **argv, char const *fmt, ...) { va_list args; va_start(args, fmt); int i = 0; char const *spec = fmt; bool got_variadic = false; for(; *spec; i++, spec++) { /* Implicit/silent specifiers */ if(*spec == 'd') { struct fxlink_device **ptr = va_arg(args, struct fxlink_device **); *ptr = find_connected_device(); if(!*ptr) { fprint(TUI.wConsole, FMT_RED, "error: "); print(TUI.wConsole, "no device connected\n"); goto failure; } /* Bad */ i--; continue; } /* No specifier that consumes stuff allowed after '*' */ if(got_variadic) { fprint(TUI.wConsole, FMT_RED, "error: "); print(TUI.wConsole, "got specifiers '%s' after '*'\n", spec); goto failure; } /* Specifiers allowed even when there is no argument left */ if(*spec == '*') { char const ***ptr = va_arg(args, char const ***); *ptr = argv + i; got_variadic = true; continue; } /* Argument required beyond this point */ if(i >= argc && *spec != '*') { fprint(TUI.wConsole, FMT_RED, "error: "); print(TUI.wConsole, "too few arguments\n"); goto failure; } /* Standard specifiers */ if(*spec == 's') { char const **ptr = va_arg(args, char const **); *ptr = argv[i]; } else if(*spec == 'i') { int *ptr = va_arg(args, int *); char *endptr; long l = strtol(argv[i], &endptr, 0); if(*endptr) { fprint(TUI.wConsole, FMT_RED, "error: "); print(TUI.wConsole, "not a valid integer: '%s'\n", argv[i]); goto failure; } *ptr = l; } } va_end(args); return true; failure: va_end(args); return false; } //--- // Command tree //--- struct node { /* Command or subtree name */ char *name; /* true if tree node, false if raw command */ bool is_tree; union { struct node *children; /* is_subtree = true */ int (*func)(int argc, char const **argv); /* is_subtree = false */ }; /* Next sibling */ struct node *next; }; static struct node *node_mkcmd(char const *name, int (*func)()) { assert(name); struct node *cmd = calloc(1, sizeof *cmd); cmd->name = strdup(name); cmd->func = func; return cmd; } static struct node *node_mktree(char const *name) { assert(name); struct node *tree = calloc(1, sizeof *tree); tree->is_tree = true; tree->name = strdup(name); return tree; } static void node_free(struct node *node); static void node_free_chain(struct node *node) { struct node *next; while(node) { next = node->next; node_free(node); node = next; } } static void node_free(struct node *node) { free(node->name); if(node->is_tree) { node_free_chain(node->children); free(node); } } static void node_tree_add(struct node *tree, struct node *node) { assert(tree->is_tree); node->next = tree->children; tree->children = node; } static struct node *node_tree_get(struct node const *tree, char const *name) { assert(tree->is_tree); for(struct node *n = tree->children; n; n = n->next) { if(!strcmp(n->name, name)) return n; } return NULL; } static struct node *node_tree_get_or_make_subtree(struct node *tree, char const *name) { assert(tree->is_tree); struct node *n = node_tree_get(tree, name); if(n) return n; n = node_mktree(name); node_tree_add(tree, n); return n; } static struct node *node_tree_get_path(struct node *tree, char const **path, int *path_end_index) { assert(tree->is_tree); struct node *n = node_tree_get(tree, path[0]); if(!n) return NULL; (*path_end_index)++; if(!n->is_tree) return n; if(!path[1]) { fprint(TUI.wConsole, FMT_RED, "error: "); print(TUI.wConsole, "'%s' takes a sub-command argument\n", path[0]); return NULL; } return node_tree_get_path(n, path+1, path_end_index); } static void node_insert_command(struct node *tree, char const **path, int (*func)(), int i) { assert(tree->is_tree); if(!path[i]) { fprintf(stderr, "error: cannot register empty command!\n"); return; } else if(!path[i+1]) { struct node *cmd = node_tree_get(tree, path[i]); if(cmd) { fprintf(stderr, "error: '%s' already registred!\n", path[i]); return; } node_tree_add(tree, node_mkcmd(path[i], func)); } else { struct node *subtree = node_tree_get_or_make_subtree(tree, path[i]); if(!subtree->is_tree) { fprintf(stderr, "error: '%s' is not a category!\n", path[i]); return; } return node_insert_command(subtree, path, func, i+1); } } static void node_dump(struct node const *node, int indent) { print(TUI.wConsole, "%*s", 2*indent, ""); if(node->is_tree) { print(TUI.wConsole, "%s\n", node->name); struct node *child = node->children; while(child) { node_dump(child, indent+1); child = child->next; } } else { print(TUI.wConsole, "%s: %p\n", node->name, node->func); } } static struct node *cmdtree = NULL; void fxlink_tui_register_cmd(char const *name, int (*func)(int argc, char const **argv)) { int i = 0; while(name[i] && (isalpha(name[i]) || strchr("?/-_ ", name[i]))) i++; if(name[i] != 0) { fprintf(stderr, "error: invalid command path '%s'\n", name); return; } if(!cmdtree) cmdtree = node_mktree("(root)"); /* Parse as a command because why not */ struct fxlink_tui_cmd path = fxlink_tui_cmd_parse(name); node_insert_command(cmdtree, path.argv, func, 0); fxlink_tui_cmd_free(&path); } __attribute__((destructor)) static void free_command_tree(void) { node_free(cmdtree); cmdtree = NULL; } void TUI_execute_command(char const *command) { struct fxlink_tui_cmd cmd = fxlink_tui_cmd_parse(command); if(cmd.argc < 1) goto end; int args_index = 0; struct node *node = node_tree_get_path(cmdtree, cmd.argv, &args_index); if(node) { node->func(cmd.argc - args_index, cmd.argv + args_index); /* ignore return code? */ } else { fprint(TUI.wConsole, FMT_RED, "error: "); print(TUI.wConsole, "unrecognized command: "); fxlink_tui_cmd_dump(&cmd); } end: fxlink_tui_cmd_free(&cmd); } FXLINK_COMMAND("?cmdtree") { node_dump(cmdtree, 0); return 0; }