/* This module handles dynamic changes to V9t9's state through the use of a generalized text parser. The configuration file is the most obvious use of this parser. */ #include #include #include "v9t9_common.h" #include "log.h" #define _L LOG_COMMANDS | LOG_INFO #include "command.h" #include "command_lexer.h" #include "command_parser.h" char *ca_types[] = { "void", "symbol", "number", "string", "filepath", "directory", "filename", "boolean" }; command_symbol_table *universe; static bool session_only; /***********************************/ /* We consider a symbol to match if 'name' is a prefix of an item in 'list'. 'list' can use '|' to separate distinct spellings. We are only allowed to use a prefix if it is at least five characters. Returns 0 if no match, 1 if a definite match, and -1 if a prefix match. */ static int symbol_match(const char *list, const char *name, int need_prefix) { const char *nptr = name; int match = 0; while (*list) { if (*list == '|') { /* aaa|bbb , aaabadab */ if (!*nptr) match = 1; nptr = name; list++; if (*list == '|') list++; } else if (!*nptr) { /* aaa|bbb , a */ if (!match && (!need_prefix || (nptr - name >= 5))) match = -1; nptr = name; } else if (tolower(*list) == tolower(*nptr)) { *list++; *nptr++; } else { while (*list && *list != '|') list++; if (*list) list++; nptr = name; } } if (!*nptr) match = 1; return match; } /* Returns 0 for no match, 1 for a definite match, and -1 for a prefix match */ int command_match_symbol(const command_symbol_table * table, const char *name, command_symbol ** sym) { command_symbol *match = NULL; int ret = 0; while (table != NULL) { command_symbol *lst = table->list; int subret; // note: comment out "ret != 1" to debug options while (lst != NULL && ret != 1) { subret = symbol_match(lst->name, name, 1); if (subret) { if (match) { // only report this if we don't already have a good idea if (subret < 0 && ret <= 0) parse_error ("%s: ambiguous identifier (collides with '%s')", name, lst->name); } else { match = lst; ret = subret; } } lst = lst->next; } // note: comment out "ret != 1" to debug options if (table->sub && ret != 1) { subret = command_match_symbol(table->sub, name, &lst); if (subret) { if (!match) { match = lst; ret = subret; } } } table = table->next; } if (match) logger(LOG_COMMANDS | L_1, "for '%s', matched '%s' (%d)\n", name, match->name, ret); *sym = match; return ret; } /* Return list of matching symbols. */ #define ADD_DELTA 16 static void add_match(command_symbol *** matches, int *nmatches, command_symbol * match) { if (*nmatches % ADD_DELTA == 0) { *matches = (command_symbol **) xrealloc(*matches, sizeof(command_symbol *) * (*nmatches + ADD_DELTA)); } (*matches)[(*nmatches)++] = match; } void command_match_symbols(const command_symbol_table * table, const char *name, command_symbol *** matches, int *nmatches) { while (table != NULL) { command_symbol *lst = table->list; int subret; while (lst != NULL) { subret = symbol_match(lst->name, name, 0); if (subret) add_match(matches, nmatches, lst); lst = lst->next; } if (table->sub) command_match_symbols(table->sub, name, matches, nmatches); table = table->next; } } command_symbol_table * command_symbol_table_new(char *name, char *help, command_symbol * list, command_symbol_table * sub, command_symbol_table * next) { command_symbol_table *tbl = (command_symbol_table *) xmalloc(sizeof(command_symbol_table)); tbl->name = name; tbl->help = help; tbl->list = list; tbl->sub = sub; tbl->next = next; return tbl; } command_symbol_table * command_symbol_table_add_subtable(command_symbol_table * parent, command_symbol_table * table) { command_symbol_table **ptr = &parent->sub; while (*ptr) ptr = &(*ptr)->next; *ptr = table; return parent; } command_symbol_table * command_symbol_table_add(command_symbol_table * parent, command_symbol * list) { command_symbol **ptr = &parent->list; while (*ptr) ptr = &(*ptr)->next; *ptr = list; return parent; } command_symbol * command_symbol_new(char *name, char *help, command_symbol_flags flags, command_symbol_action action, command_arg * ret, command_arg * args, command_symbol * next) { command_symbol *sym = (command_symbol *) xmalloc(sizeof(command_symbol)); sym->name = name ? name : ""; sym->help = help; // ? help : ""; sym->flags = flags; sym->action = action; sym->ret = ret == RET_FIRST_ARG ? args : ret; sym->args = args; sym->next = next; return sym; } command_arg * command_arg_new_num(char *name, char *help, command_arg_action action, int sz, void *mem, command_arg * next) { command_arg *sym = (command_arg *) xmalloc(sizeof(command_arg)); sym->name = name ? name : ""; sym->help = help ? help : "a number"; sym->action = action; sym->type = ca_NUM; sym->u.num.sz = sz; sym->u.num.mem = mem; sym->next = next; return sym; } command_arg * command_arg_new_string(char *name, char *help, command_arg_action action, int maxlen, void *mem, command_arg * next) { command_arg *sym = (command_arg *) xmalloc(sizeof(command_arg)); sym->name = name ? name : ""; sym->help = help ? help : "a string"; sym->action = action; sym->type = ca_STRING; sym->u.string.maxlen = maxlen; if (maxlen >= 0) sym->u.string.m.mem = (char *) mem; else sym->u.string.m.ptr = (char **) mem; sym->next = next; return sym; } command_arg * command_arg_new_spec(char *name, char *help, command_arg_action action, OSSpec * spec, command_arg * next) { command_arg *sym = (command_arg *) xmalloc(sizeof(command_arg)); sym->name = name ? name : ""; sym->help = help ? help : "a filespec"; sym->action = action; sym->type = ca_SPEC; sym->u.spec.mem = spec; sym->next = next; return sym; } command_arg * command_arg_new_pathspec(char *name, char *help, command_arg_action action, OSPathSpec * pathspec, command_arg * next) { command_arg *sym = (command_arg *) xmalloc(sizeof(command_arg)); sym->name = name ? name : ""; sym->help = help ? help : "a pathspec"; sym->action = action; sym->type = ca_PATHSPEC; sym->u.pathspec.mem = pathspec; sym->next = next; return sym; } command_arg * command_arg_new_namespec(char *name, char *help, command_arg_action action, OSNameSpec * namespec, command_arg * next) { command_arg *sym = (command_arg *) xmalloc(sizeof(command_arg)); sym->name = name ? name : ""; sym->help = help ? help : "a filename"; sym->action = action; sym->type = ca_NAMESPEC; sym->u.namespec.mem = namespec; sym->next = next; return sym; } command_arg * command_arg_new_toggle(char *name, char *help, command_arg_action action, int sz, void *mem, int val, command_arg * next) { command_arg *sym = (command_arg *) xmalloc(sizeof(command_arg)); sym->name = name ? name : ""; sym->help = help ? help : "a number"; sym->action = action; sym->type = ca_TOGGLE; sym->u.toggle.sz = sz; sym->u.toggle.mem = mem; sym->u.toggle.flag = val; sym->next = next; return sym; } /*************************************************/ void command_arg_read_num(command_arg *arg, int *val) { switch (arg->u.num.sz) { case 1: { u8 u8val; u8val = *(u8 *) arg->u.num.mem; *val = u8val; break; } case 2: { u16 u16val; u16val = *(u16 *) arg->u.num.mem; *val = u16val; break; } case 4: { u32 u32val; u32val = *(u32 *) arg->u.num.mem; *val = u32val; break; } default: logger(_L | LOG_FATAL, "Unhandled size in command_arg_read_num (%d)\n", arg->u.num.sz); break; } } int command_arg_get_num(command_arg * arg, int *val) { if (!arg) { *val = 0; return 0; } my_assert(arg->type == ca_NUM || arg->type == ca_TOGGLE); if (arg->action) if (!arg->action(arg, caa_READ)) return 0; command_arg_read_num(arg, val); return 1; } int command_arg_set_num(command_arg * arg, int val) { if (!arg) { return 0; } my_assert(arg->type == ca_NUM || arg->type == ca_TOGGLE); switch (arg->u.num.sz) { case 1: { u8 u8val = val; *(u8 *) arg->u.num.mem = u8val; break; } case 2: { u16 u16val = val; *(u16 *) arg->u.num.mem = u16val; break; } case 4: { u32 u32val = val; *(u32 *) arg->u.num.mem = u32val; break; } default: logger(_L | LOG_FATAL, "Unhandled size in command_arg_set_num (%d)\n", arg->u.num.sz); break; } if (arg->action) if (!arg->action(arg, caa_WRITE)) return 0; return 1; } void command_arg_read_string(command_arg * arg, char **str) { if (arg->u.string.maxlen >= 0) *str = arg->u.string.m.mem; else *str = *arg->u.string.m.ptr; } int command_arg_get_string(command_arg * arg, char **str) { if (!arg) { *str = 0L; return 0; } my_assert(arg->type == ca_STRING); if (arg->action) if (!arg->action(arg, caa_READ)) return 0; command_arg_read_string(arg, str); return 1; } int command_arg_set_string(command_arg * arg, const char *str) { if (!arg) { return 0; } my_assert(arg->type == ca_STRING); if (arg->u.string.maxlen >= 0) { if (str) { strncpy(arg->u.string.m.mem, str, arg->u.string.maxlen); arg->u.string.m.mem[arg->u.string.maxlen - 1] = 0; } else { *arg->u.string.m.mem = 0; } } else { if (str) { *arg->u.string.m.ptr = (char *) xrealloc(*arg->u.string.m.ptr, strlen(str) + 1); strcpy(*arg->u.string.m.ptr, str); } else { if (*arg->u.string.m.ptr) { xfree(arg->u.string.m.ptr); } *arg->u.string.m.ptr = 0L; } } if (arg->action) if (!arg->action(arg, caa_WRITE)) return 0; return 1; } void command_arg_read_spec(command_arg * arg, char **str) { if (arg->type == ca_SPEC) *str = OS_SpecToString1(arg->u.spec.mem); else if (arg->type == ca_PATHSPEC) *str = OS_PathSpecToString1(arg->u.pathspec.mem); else /* if (arg->type == ca_NAMESPEC) */ *str = OS_NameSpecToString1(arg->u.namespec.mem); } int command_arg_get_spec(command_arg * arg, char **str) { if (!arg) { *str = 0L; return 0; } my_assert(ca_ISSPEC(arg->type)); if (arg->action) if (!arg->action(arg, caa_READ)) return 0; command_arg_read_spec(arg, str); return 1; } int command_arg_set_spec(command_arg * arg, const char *str) { OSError err = OS_FNFERR; if (!arg) { return 0; } my_assert(ca_ISSPEC(arg->type)); if (str) { if (arg->type == ca_SPEC) err = OS_MakeSpec(str, arg->u.spec.mem, NULL); else if (arg->type == ca_PATHSPEC) err = OS_MakePathSpec(NULL, str, arg->u.pathspec.mem); else /* if (arg->type == ca_NAMESPEC) */ err = OS_MakeNameSpec(str, arg->u.namespec.mem); } if (err != OS_NOERR) { parse_error("argument '%s': cannot set to '%s' (%s)", arg->name, str, OS_GetErrText(err)); return 0; } if (arg->action) if (!arg->action(arg, caa_WRITE)) return 0; return 1; } void command_arg_read_toggle(command_arg * arg, int *val) { command_arg_read_num(arg, val); *val = (arg->u.toggle.flag & *val) != 0; } int command_arg_get_toggle(command_arg * arg, int *val) { if (!arg) { *val = 0L; return 0; } if (!command_arg_get_num(arg, val)) return 0; *val = (arg->u.toggle.flag & *val) != 0; return 1; } int command_arg_set_toggle(command_arg * arg, int val) { int curval; if (!arg) { return 0; } if (!command_arg_get_num(arg, &curval)) return 0; val = val ? (curval | arg->u.toggle.flag) : (curval & ~arg->u.toggle.flag); if (!command_arg_set_num(arg, val)) return 0; return 1; } /***************************************/ int command_get_val(command_symbol * sym, command_exprval * val) { command_arg *ret; if (sym->action) if (!sym->action(sym, csa_READ, 0)) return 0; ret = sym->ret; if (!ret) { val->type = ca_VOID; return 1; } else if (ret->type == ca_NUM) { val->type = ca_NUM; if (!command_arg_get_num(ret, &val->u.num)) return 0; } else if (ret->type == ca_STRING) { char *str; val->type = ca_STRING; if (!command_arg_get_string(ret, &str)) return 0; val->u.str = xstrdup(str); } else if (ca_ISSPEC(ret->type)) { char *str; val->type = ca_STRING; if (!command_arg_get_spec(ret, &str)) return 0; val->u.str = xstrdup(str); } else if (ret->type == ca_TOGGLE) { val->type = ca_NUM; if (!command_arg_get_toggle(ret, &val->u.num)) return 0; } else { logger(_L | LOG_FATAL, "Unhandled ca_XXXX (%s)\n", ca_types[ret->type]); } return 1; } int command_set_args(command_exprval * ret, command_symbol * sym, ...) { va_list ap; command_exprval *val; command_arg *arg; int argnum = 0; logger(_L | L_2, "sym->name='%s'\n", sym->name); // if (!(!(sym->flags & c_SESSION_ONLY) && session_only)) { arg = sym->args; va_start(ap, sym); while ((val = va_arg(ap, command_exprval *)) != NULL) { argnum++; if (arg == NULL) { parse_error("%s: unexpected additional %s argument #%d", sym->name, ca_types[val->type], argnum); return 0; } /* Look for implicit conversions */ if (val->type == ca_STRING && ca_ISSPEC(arg->type)) { if (!command_arg_set_spec(arg, val->u.str)) return 0; xfree(val->u.str); } else if (val->type == ca_NUM && arg->type == ca_TOGGLE) { if (!command_arg_set_toggle(arg, val->u.num)) return 0; } else if (val->type == ca_NUM && arg->type == ca_NUM) { if (!command_arg_set_num(arg, val->u.num)) return 0; } else if (val->type == ca_STRING && arg->type == ca_STRING) { if (!command_arg_set_string(arg, val->u.str)) return 0; xfree(val->u.str); } else if (val->type != arg->type) { parse_error("%s: type mismatch in argument '%s' (expected %s)", sym->name, arg->name, ca_types[arg->type]); return 0; } else logger(_L | LOG_FATAL, "command_set_args: cannot handle %s\n", ca_types[val->type]); arg = arg->next; } if (arg != NULL) { parse_error("%s: expected additional parameters", sym->name); return 0; } if (sym->action) if (!sym->action(sym, csa_WRITE, 0)) return 0; } if (ret) { ret->type = ca_VOID; if (sym->ret && !command_get_val(sym, ret)) return 0; } return 1; } /************************/ int command_lexer_setup_text(char *name, char *text, int len) { return lexer_push_text(name, text, len) != NULL; } int command_lexer_setup_file(char *filename) { return lexer_push_file(filename) != NULL; } /**********************/ void command_init(void) { universe = command_symbol_table_new("V9t9 Options", "This is the complete list of options and commands " "you may specify in a configuration file or command prompt.", NULL, NULL, NULL); session_only = false; } int command_parse_file(char *name) { if (!command_lexer_setup_file(name)) return 0; command_parse(universe); return 1; } int command_parse_text(char *str) { if (!command_lexer_setup_text("", str, strlen(str))) return 0; command_parse(universe); return 1; } bool command_get_session_filter(void) { return session_only; } void command_set_session_filter(bool allow_session_only) { session_only = allow_session_only; } /**********************/ #ifdef STANDALONE int a, b; OSPathSpec dir; char buf[4], buf2[16]; int c; int main(int argc, char **argv) { /* command_symbol_table *table = command_symbol_table_new(NULL, command_symbol_new("a", NULL, command_arg_new_num(sizeof(a), &a, NULL ), command_arg_new_num(sizeof(a), &a, NULL ), command_symbol_new("b", NULL, command_arg_new_num(sizeof(b), &b, NULL ), command_arg_new_num(sizeof(b), &b, NULL ), command_symbol_new("ni", NULL, command_arg_new_num(sizeof(c), &c, NULL ), command_arg_new_string(sizeof(buf), buf, command_arg_new_num(sizeof(c), &c, NULL )), command_symbol_new("c", NULL, command_arg_new_string(sizeof(buf), buf, NULL ), command_arg_new_string(sizeof(buf), buf, NULL ), command_symbol_new("d", NULL, command_arg_new_string(sizeof(buf2), buf2, NULL ), command_arg_new_string(sizeof(buf2), buf2, NULL ), NULL ))))) ); */ char *text = "#include \"test.cnf\""; // xminfo(); command_symbol_table_add(universe, command_symbol_new("DelayBetweenInstructions", "Sets a constant delay between instructions", NULL, NULL, command_arg_new_num("cycles", "number of cycles to count", NULL, ARG_NUM (a), NULL), command_symbol_new ("ModulesPath", "Set directory to search for module ROMs", NULL, NULL, command_arg_new_pathspec ("dir", "path to directory", NULL, &dir, NULL), NULL))); command_help(); if (!command_lexer_setup_text("test", text, strlen(text))) return -1; command_parse(universe); printf("a=%d, b=%d, buf=%s, buf2=%s, c=%d\n", a, b, buf, buf2, c); // xminfo(); } #endif