mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-29 13:03:37 +01:00
241 lines
6.3 KiB
C
241 lines
6.3 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 <string.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <ctype.h>
|
||
|
#include <stdarg.h>
|
||
|
|
||
|
//---
|
||
|
// Parsing utilities
|
||
|
//---
|
||
|
|
||
|
struct command {
|
||
|
int argc;
|
||
|
char const **argv;
|
||
|
char *data;
|
||
|
};
|
||
|
|
||
|
static struct command parse_command(char const *input)
|
||
|
{
|
||
|
struct command 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;
|
||
|
return cmd;
|
||
|
}
|
||
|
|
||
|
static void dump_command(struct command const *cmd)
|
||
|
{
|
||
|
print(TUI.wConsole, "command(%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");
|
||
|
}
|
||
|
|
||
|
static void free_command(struct command *cmd)
|
||
|
{
|
||
|
free(cmd->argv);
|
||
|
free(cmd->data);
|
||
|
}
|
||
|
|
||
|
/* Parse a list of arguments into strings/integers/etc. The format is a string
|
||
|
of argument specifiers, which can be:
|
||
|
s String
|
||
|
d Integer
|
||
|
* Other variadic arguments (integer holding first index)
|
||
|
(-- will probably be expanded later.)
|
||
|
Returns true if parsing succeeded, false otherwise (including if arguments
|
||
|
are missing) after printing an error message. */
|
||
|
static bool parse_arguments(int argc, char const **argv, char const *fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
|
||
|
int i = 1;
|
||
|
char const *spec = fmt;
|
||
|
|
||
|
while(*spec) {
|
||
|
if(i >= argc) {
|
||
|
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||
|
print(TUI.wConsole, "too few arguments\n");
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
/* Parse an argument specifier */
|
||
|
if(*spec == 's') {
|
||
|
char const **ptr = va_arg(args, char const **);
|
||
|
*ptr = argv[i];
|
||
|
}
|
||
|
else if(*spec == 'd') {
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
spec++;
|
||
|
}
|
||
|
|
||
|
va_end(args);
|
||
|
return true;
|
||
|
|
||
|
failure:
|
||
|
va_end(args);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool run_no_device(int argc, char const **argv)
|
||
|
{
|
||
|
(void)argc;
|
||
|
(void)argv;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static void run_gintctl_command(int argc, char const **argv,
|
||
|
struct fxlink_device *fdev)
|
||
|
{
|
||
|
if(!strcmp(argv[0], "read-long")) {
|
||
|
int count = 0;
|
||
|
if(!parse_arguments(argc, argv, "d", &count))
|
||
|
return;
|
||
|
if(count < 0 || count > 8192)
|
||
|
return;
|
||
|
|
||
|
uint32_t *data = malloc(count * 4);
|
||
|
for(int i = 0; i < count; i++)
|
||
|
data[i] = i;
|
||
|
fxlink_device_start_bulk_OUT(fdev,
|
||
|
"gintctl", "echo-bounds", data, count * 4, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool run_with_device(int argc, char const **argv,
|
||
|
struct fxlink_device *fdev)
|
||
|
{
|
||
|
if(!strcmp(argv[0], "/echo")) {
|
||
|
int l = 5, j = 5;
|
||
|
for(int i = 1; i < argc; i++)
|
||
|
l += strlen(argv[i]) + 1;
|
||
|
|
||
|
char *concat = malloc(l + 1);
|
||
|
strcpy(concat, "echo ");
|
||
|
for(int i = 1; i < argc; i++) {
|
||
|
strcpy(concat + j, argv[i]);
|
||
|
j += strlen(argv[i]);
|
||
|
concat[j++] = (i == argc - 1) ? '\n' : ' ';
|
||
|
}
|
||
|
concat[j] = '\0';
|
||
|
|
||
|
fxlink_device_start_bulk_OUT(fdev,
|
||
|
"fxlink", "command", concat, l, true);
|
||
|
}
|
||
|
else if(!strcmp(argv[0], "/identify")) {
|
||
|
fxlink_device_start_bulk_OUT(fdev,
|
||
|
"fxlink", "command", "identify", 8, false);
|
||
|
}
|
||
|
else if(!strcmp(argv[0], "gintctl")) {
|
||
|
if(argc <= 1) {
|
||
|
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||
|
print(TUI.wConsole, "gintctl command needs sub-command\n");
|
||
|
return true;
|
||
|
}
|
||
|
run_gintctl_command(argc-1, argv+1, fdev);
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void TUI_execute_command(char const *command)
|
||
|
{
|
||
|
struct command cmd = parse_command(command);
|
||
|
(void)dump_command;
|
||
|
|
||
|
/* Connected device (TODO: Use the "selected" device) */
|
||
|
struct fxlink_device *fdev = NULL;
|
||
|
for(int i = 0; i < TUI.devices.count; i++) {
|
||
|
if(TUI.devices.devices[i].status == FXLINK_FDEV_STATUS_CONNECTED) {
|
||
|
fdev = &TUI.devices.devices[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool b = run_no_device(cmd.argc, cmd.argv);
|
||
|
if(b) {
|
||
|
free_command(&cmd);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* The following commands require a connected device */
|
||
|
if(!fdev) {
|
||
|
print(TUI.wConsole, "no connected device!\n");
|
||
|
return;
|
||
|
}
|
||
|
print(TUI.wConsole, "using device %s (%s)\n",
|
||
|
fxlink_device_id(fdev), fdev->calc->serial);
|
||
|
|
||
|
b = run_with_device(cmd.argc, cmd.argv, fdev);
|
||
|
if(!b) {
|
||
|
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||
|
print(TUI.wConsole, "unrecognized command '%s'\n", cmd.argv[0]);
|
||
|
}
|
||
|
free_command(&cmd);
|
||
|
}
|