mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-29 13:03:37 +01:00
379 lines
9.4 KiB
C
379 lines
9.4 KiB
C
//---------------------------------------------------------------------------//
|
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
|
//---------------------------------------------------------------------------//
|
|
|
|
#include "tui.h"
|
|
#include "command-util.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
|
|
//---
|
|
// 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;
|
|
}
|