Mercurial > repos > blastem
comparison debug.c @ 2395:ebca8ab02701
Basic string support in debugger language
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Wed, 13 Dec 2023 20:09:18 -0800 |
parents | 340299a76db7 |
children | bf4f1a8d1d48 |
comparison
equal
deleted
inserted
replaced
2394:340299a76db7 | 2395:ebca8ab02701 |
---|---|
163 return NULL; | 163 return NULL; |
164 } | 164 } |
165 return arrays + val.v.u32; | 165 return arrays + val.v.u32; |
166 } | 166 } |
167 | 167 |
168 static debug_string **debug_strings; | |
169 static uint32_t num_debug_strings; | |
170 static uint32_t debug_string_storage; | |
171 static debug_val new_debug_string(char *str) | |
172 { | |
173 if (num_debug_strings == debug_string_storage) { | |
174 debug_string_storage = debug_string_storage ? 2 * debug_string_storage : 4; | |
175 debug_strings = realloc(debug_strings, debug_string_storage * sizeof(debug_string*)); | |
176 } | |
177 debug_string *string = calloc(1, sizeof(debug_string)); | |
178 string->size = string->storage = strlen(str); | |
179 string->buffer = calloc(1, string->size + 1); | |
180 memcpy(string->buffer, str, string->size + 1); | |
181 debug_strings[num_debug_strings] = string; | |
182 return (debug_val){ | |
183 .type = DBG_VAL_STRING, | |
184 .v = { | |
185 .u32 = num_debug_strings++ | |
186 } | |
187 }; | |
188 } | |
189 | |
190 static debug_string* get_string(debug_val val) | |
191 { | |
192 if (val.type != DBG_VAL_STRING) { | |
193 return NULL; | |
194 } | |
195 return debug_strings[val.v.u32]; | |
196 } | |
197 | |
198 static char* get_cstring(debug_val val) | |
199 { | |
200 debug_string *str = get_string(val); | |
201 if (!str) { | |
202 return NULL; | |
203 } | |
204 return str->buffer; | |
205 } | |
206 | |
168 static uint8_t debug_cast_int(debug_val val, uint32_t *out) | 207 static uint8_t debug_cast_int(debug_val val, uint32_t *out) |
169 { | 208 { |
170 if (val.type == DBG_VAL_U32) { | 209 if (val.type == DBG_VAL_U32) { |
171 *out = val.v.u32; | 210 *out = val.v.u32; |
172 return 1; | 211 return 1; |
371 "TOKEN_OPER", | 410 "TOKEN_OPER", |
372 "TOKEN_SIZE", | 411 "TOKEN_SIZE", |
373 "TOKEN_LBRACKET", | 412 "TOKEN_LBRACKET", |
374 "TOKEN_RBRACKET", | 413 "TOKEN_RBRACKET", |
375 "TOKEN_LPAREN", | 414 "TOKEN_LPAREN", |
376 "TOKEN_RPAREN" | 415 "TOKEN_RPAREN", |
416 "TOKEN_STRING" | |
377 }; | 417 }; |
378 | 418 |
419 static char *parse_string_literal(char *start, char **end) | |
420 { | |
421 uint32_t length = 0; | |
422 uint8_t is_escape = 0; | |
423 char *cur; | |
424 for (cur = start; *cur && *cur != '"'; cur++) | |
425 { | |
426 if (is_escape) { | |
427 switch (*cur) | |
428 { | |
429 case 't': | |
430 case 'n': | |
431 case 'r': | |
432 case '\\': | |
433 break; | |
434 default: | |
435 fprintf(stderr, "Unsupported escape character %c\n", *cur); | |
436 return NULL; | |
437 } | |
438 is_escape = 0; | |
439 } else if (*cur == '\\') { | |
440 is_escape = 1; | |
441 continue; | |
442 } | |
443 length++; | |
444 } | |
445 if (!*cur) { | |
446 fprintf(stderr, "Unterminated string literal: %s\n", start); | |
447 return NULL; | |
448 } | |
449 *end = cur + 1; | |
450 char *ret = calloc(1, length + 1); | |
451 char *dst = ret; | |
452 is_escape = 0; | |
453 for (cur = start; *cur != '"'; ++cur) | |
454 { | |
455 if (is_escape) { | |
456 switch (*cur) | |
457 { | |
458 case 't': | |
459 *(dst++) = '\t'; | |
460 break; | |
461 case 'n': | |
462 *(dst++) = '\n'; | |
463 break; | |
464 case 'r': | |
465 *(dst++) = '\r'; | |
466 break; | |
467 case '\\': | |
468 *(dst++) = '\\'; | |
469 break; | |
470 } | |
471 is_escape = 0; | |
472 } else if (*cur == '\\') { | |
473 is_escape = 1; | |
474 continue; | |
475 } else { | |
476 *(dst++) = *cur; | |
477 } | |
478 } | |
479 *dst = 0; | |
480 return ret; | |
481 } | |
482 | |
379 static token parse_token(char *start, char **end) | 483 static token parse_token(char *start, char **end) |
380 { | 484 { |
381 while(*start && isblank(*start) && *start != '\n' && *start != '\r') | 485 while(*start && isblank(*start) && *start != '\n' && *start != '\r') |
382 { | 486 { |
383 ++start; | 487 ++start; |
384 } | 488 } |
385 if (!*start || *start == '\n' || *start == '\r') { | 489 if (!*start || *start == '\n' || *start == '\r') { |
490 *end = start; | |
386 return (token){ | 491 return (token){ |
387 .type = TOKEN_NONE | 492 .type = TOKEN_NONE |
388 }; | 493 }; |
389 *end = start; | |
390 } | 494 } |
391 if (*start == '$' || (*start == '0' && start[1] == 'x')) { | 495 if (*start == '$' || (*start == '0' && start[1] == 'x')) { |
392 return (token) { | 496 return (token) { |
393 .type = TOKEN_INT, | 497 .type = TOKEN_INT, |
394 .v = { | 498 .v = { |
395 .num = strtol(start + (*start == '$' ? 1 : 2), end, 16) | 499 .num = strtol(start + (*start == '$' ? 1 : 2), end, 16) |
500 } | |
501 }; | |
502 } | |
503 if (*start == '"') { | |
504 char *str = parse_string_literal(start + 1, end); | |
505 if (!str) { | |
506 *end = start; | |
507 return (token){ | |
508 .type = TOKEN_NONE | |
509 }; | |
510 } | |
511 return (token){ | |
512 .type = TOKEN_STRING, | |
513 .v = { | |
514 .str = str | |
396 } | 515 } |
397 }; | 516 }; |
398 } | 517 } |
399 if (isdigit(*start)) { | 518 if (isdigit(*start)) { |
400 uint32_t ipart = strtol(start, end, 10); | 519 uint32_t ipart = strtol(start, end, 10); |
541 } | 660 } |
542 free(e->right); | 661 free(e->right); |
543 } else { | 662 } else { |
544 free_expr(e->right); | 663 free_expr(e->right); |
545 } | 664 } |
546 if (e->op.type == TOKEN_NAME || e->op.type == TOKEN_ARRAY) { | 665 if (e->op.type == TOKEN_NAME || e->op.type == TOKEN_ARRAY || e->op.type == TOKEN_STRING) { |
547 free(e->op.v.str); | 666 free(e->op.v.str); |
548 } | 667 } |
549 } | 668 } |
550 | 669 |
551 static void free_expr(expr *e) | 670 static void free_expr(expr *e) |
695 free_expr(ret); | 814 free_expr(ret); |
696 return NULL; | 815 return NULL; |
697 } | 816 } |
698 return ret; | 817 return ret; |
699 } | 818 } |
700 if (first.type != TOKEN_INT && first.type != TOKEN_DECIMAL && first.type != TOKEN_NAME) { | 819 if (first.type != TOKEN_INT && first.type != TOKEN_DECIMAL && first.type != TOKEN_NAME && first.type != TOKEN_STRING) { |
701 fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); | 820 fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); |
702 return NULL; | 821 return NULL; |
703 } | 822 } |
704 token second = parse_token(after_first, end); | 823 token second = parse_token(after_first, end); |
705 if (second.type != TOKEN_SIZE) { | 824 if (second.type != TOKEN_SIZE) { |
887 free_expr(ret); | 1006 free_expr(ret); |
888 return NULL; | 1007 return NULL; |
889 } | 1008 } |
890 return maybe_muldiv(ret, *end, end); | 1009 return maybe_muldiv(ret, *end, end); |
891 } | 1010 } |
892 if (first.type != TOKEN_INT && first.type != TOKEN_DECIMAL && first.type != TOKEN_NAME) { | 1011 if (first.type != TOKEN_INT && first.type != TOKEN_DECIMAL && first.type != TOKEN_NAME && first.type != TOKEN_STRING) { |
893 fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); | 1012 fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); |
894 return NULL; | 1013 return NULL; |
895 } | 1014 } |
896 char *after_second; | 1015 char *after_second; |
897 token second = parse_token(after_first, &after_second); | 1016 token second = parse_token(after_first, &after_second); |
1053 free_expr(ret); | 1172 free_expr(ret); |
1054 return NULL; | 1173 return NULL; |
1055 } | 1174 } |
1056 return maybe_binary(ret, *end, end); | 1175 return maybe_binary(ret, *end, end); |
1057 } | 1176 } |
1058 if (first.type != TOKEN_INT && first.type != TOKEN_DECIMAL && first.type != TOKEN_NAME) { | 1177 if (first.type != TOKEN_INT && first.type != TOKEN_DECIMAL && first.type != TOKEN_NAME && first.type != TOKEN_STRING) { |
1059 fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); | 1178 fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); |
1060 return NULL; | 1179 return NULL; |
1061 } | 1180 } |
1062 char *after_second; | 1181 char *after_second; |
1063 token second = parse_token(after_first, &after_second); | 1182 token second = parse_token(after_first, &after_second); |
1149 *out = var->get(var); | 1268 *out = var->get(var); |
1150 return 1; | 1269 return 1; |
1151 } else if (e->op.type == TOKEN_INT) { | 1270 } else if (e->op.type == TOKEN_INT) { |
1152 *out = debug_int(e->op.v.num); | 1271 *out = debug_int(e->op.v.num); |
1153 return 1; | 1272 return 1; |
1273 } else if (e->op.type == TOKEN_DECIMAL){ | |
1274 *out = debug_float(e->op.v.f); | |
1275 return 1; | |
1154 } else { | 1276 } else { |
1155 *out = debug_float(e->op.v.f); | 1277 *out = new_debug_string(e->op.v.str); |
1156 return 1; | 1278 return 1; |
1157 } | 1279 } |
1158 case EXPR_UNARY: | 1280 case EXPR_UNARY: |
1159 if (!eval_expr(root, e->left, out)) { | 1281 if (!eval_expr(root, e->left, out)) { |
1160 return 0; | 1282 return 0; |
2388 return 1; | 2510 return 1; |
2389 } | 2511 } |
2390 | 2512 |
2391 static uint8_t cmd_printf(debug_root *root, parsed_command *cmd) | 2513 static uint8_t cmd_printf(debug_root *root, parsed_command *cmd) |
2392 { | 2514 { |
2393 char *param = cmd->raw; | 2515 char *fmt = get_cstring(cmd->args[0].value); |
2394 if (!param) { | 2516 if (!fmt) { |
2395 fputs("printf requires at least one parameter\n", stderr); | 2517 fprintf(stderr, "First parameter to printf must be a string\n"); |
2396 return 1; | 2518 return 1; |
2397 } | 2519 } |
2398 while (isblank(*param)) | 2520 char *cur = fmt; |
2399 { | |
2400 ++param; | |
2401 } | |
2402 if (*param != '"') { | |
2403 fprintf(stderr, "First parameter to printf must be a string, found '%s'\n", param); | |
2404 return 1; | |
2405 } | |
2406 ++param; | |
2407 char *fmt = strdup(param); | |
2408 char *cur = param, *out = fmt; | |
2409 while (*cur && *cur != '"') | |
2410 { | |
2411 if (*cur == '\\') { | |
2412 switch (cur[1]) | |
2413 { | |
2414 case 't': | |
2415 *(out++) = '\t'; | |
2416 break; | |
2417 case 'n': | |
2418 *(out++) = '\n'; | |
2419 break; | |
2420 case 'r': | |
2421 *(out++) = '\r'; | |
2422 break; | |
2423 case '\\': | |
2424 *(out++) = '\\'; | |
2425 break; | |
2426 default: | |
2427 fprintf(stderr, "Unsupported escape character %c in string %s\n", cur[1], fmt); | |
2428 free(fmt); | |
2429 return 1; | |
2430 } | |
2431 cur += 2; | |
2432 } else { | |
2433 *(out++) = *(cur++); | |
2434 } | |
2435 } | |
2436 *out = 0; | |
2437 ++cur; | |
2438 param = cur; | |
2439 cur = fmt; | |
2440 char format_str[3] = {'%', 'd', 0}; | 2521 char format_str[3] = {'%', 'd', 0}; |
2522 uint32_t cur_param = 1; | |
2441 while (*cur) | 2523 while (*cur) |
2442 { | 2524 { |
2443 if (*cur == '%') { | 2525 if (*cur == '%') { |
2444 switch(cur[1]) | 2526 switch(cur[1]) |
2445 { | 2527 { |
2450 case 's': | 2532 case 's': |
2451 case 'f': | 2533 case 'f': |
2452 break; | 2534 break; |
2453 default: | 2535 default: |
2454 fprintf(stderr, "Unsupported format character %c\n", cur[1]); | 2536 fprintf(stderr, "Unsupported format character %c\n", cur[1]); |
2455 free(fmt); | |
2456 return 1; | 2537 return 1; |
2457 } | 2538 } |
2458 format_str[1] = cur[1]; | 2539 format_str[1] = cur[1]; |
2459 expr *arg = parse_expression(param, ¶m); | 2540 if (cur_param == cmd->num_args) { |
2460 if (!arg) { | 2541 fprintf(stderr, "Not enough arguments for format char %c\n", *cur); |
2461 free(fmt); | |
2462 return 1; | 2542 return 1; |
2463 } | 2543 } |
2464 debug_val val; | 2544 debug_val val = cmd->args[cur_param++].value; |
2465 if (!eval_expr(root, arg, &val)) { | |
2466 free(fmt); | |
2467 return 1; | |
2468 } | |
2469 if (cur[1] == 's') { | 2545 if (cur[1] == 's') { |
2470 if (val.type == DBG_VAL_STRING) { | 2546 if (val.type == DBG_VAL_STRING) { |
2471 //TODO: implement me | 2547 printf(format_str, get_cstring(val)); |
2472 } else { | 2548 } else { |
2473 char tmp[128]; | 2549 char tmp[128]; |
2474 uint32_t address; | 2550 uint32_t address; |
2475 if (!debug_cast_int(val, &address)) { | 2551 if (!debug_cast_int(val, &address)) { |
2476 fprintf(stderr, "Format char 's' accepts only integers and strings\n"); | 2552 fprintf(stderr, "Format char 's' accepts only integers and strings\n"); |
2477 free(fmt); | |
2478 return 1; | 2553 return 1; |
2479 } | 2554 } |
2480 int j; | 2555 int j; |
2481 for (j = 0; j < sizeof(tmp)-1; j++, address++) | 2556 for (j = 0; j < sizeof(tmp)-1; j++, address++) |
2482 { | 2557 { |
2493 } | 2568 } |
2494 } else if (cur[1] == 'f') { | 2569 } else if (cur[1] == 'f') { |
2495 float fval; | 2570 float fval; |
2496 if (!debug_cast_float(val, &fval)) { | 2571 if (!debug_cast_float(val, &fval)) { |
2497 fprintf(stderr, "Format char '%c' only accepts floats\n", cur[1]); | 2572 fprintf(stderr, "Format char '%c' only accepts floats\n", cur[1]); |
2498 free(fmt); | |
2499 return 1; | 2573 return 1; |
2500 } | 2574 } |
2501 printf(format_str, fval); | 2575 printf(format_str, fval); |
2502 } else { | 2576 } else { |
2503 uint32_t ival; | 2577 uint32_t ival; |
2504 if (!debug_cast_int(val, &ival)) { | 2578 if (!debug_cast_int(val, &ival)) { |
2505 fprintf(stderr, "Format char '%c' only accepts integers\n", cur[1]); | 2579 fprintf(stderr, "Format char '%c' only accepts integers\n", cur[1]); |
2506 free(fmt); | |
2507 return 1; | 2580 return 1; |
2508 } | 2581 } |
2509 printf(format_str, ival); | 2582 printf(format_str, ival); |
2510 } | 2583 } |
2511 cur += 2; | 2584 cur += 2; |
2904 return 0; | 2977 return 0; |
2905 } | 2978 } |
2906 | 2979 |
2907 static uint8_t cmd_bindup(debug_root *root, parsed_command *cmd) | 2980 static uint8_t cmd_bindup(debug_root *root, parsed_command *cmd) |
2908 { | 2981 { |
2909 if (!bind_up(cmd->raw)) { | 2982 char *bind = get_cstring(cmd->args[0].value); |
2910 fprintf(stderr, "%s is not a valid binding name\n", cmd->raw); | 2983 if (!bind) { |
2984 fprintf(stderr, "Argument to bindup must be a string\n"); | |
2985 return 1; | |
2986 } | |
2987 if (!bind_up(bind)) { | |
2988 fprintf(stderr, "%s is not a valid binding name\n", bind); | |
2911 } | 2989 } |
2912 return 1; | 2990 return 1; |
2913 } | 2991 } |
2914 | 2992 |
2915 static uint8_t cmd_binddown(debug_root *root, parsed_command *cmd) | 2993 static uint8_t cmd_binddown(debug_root *root, parsed_command *cmd) |
2916 { | 2994 { |
2917 if (!bind_down(cmd->raw)) { | 2995 char *bind = get_cstring(cmd->args[0].value); |
2918 fprintf(stderr, "%s is not a valid binding name\n", cmd->raw); | 2996 if (!bind) { |
2997 fprintf(stderr, "Argument to binddown must be a string\n"); | |
2998 return 1; | |
2999 } | |
3000 if (!bind_down(bind)) { | |
3001 fprintf(stderr, "%s is not a valid binding name\n", bind); | |
2919 } | 3002 } |
2920 return 1; | 3003 return 1; |
2921 } | 3004 } |
2922 | 3005 |
2923 static uint8_t cmd_condition(debug_root *root, parsed_command *cmd) | 3006 static uint8_t cmd_condition(debug_root *root, parsed_command *cmd) |
2968 printf("$%X\n", (uint32_t)val.intval); | 3051 printf("$%X\n", (uint32_t)val.intval); |
2969 } | 3052 } |
2970 | 3053 |
2971 static uint8_t cmd_symbols(debug_root *root, parsed_command *cmd) | 3054 static uint8_t cmd_symbols(debug_root *root, parsed_command *cmd) |
2972 { | 3055 { |
2973 char *filename = cmd->raw ? strip_ws(cmd->raw) : NULL; | 3056 if (cmd->num_args) { |
2974 if (filename && *filename) { | 3057 char *filename = get_cstring(cmd->args[0].value); |
3058 if (!filename) { | |
3059 fprintf(stderr, "Argument to symbols must be a string if provided\n"); | |
3060 return 1; | |
3061 } | |
2975 FILE *f = fopen(filename, "r"); | 3062 FILE *f = fopen(filename, "r"); |
2976 if (!f) { | 3063 if (!f) { |
2977 fprintf(stderr, "Failed to open %s for reading\n", filename); | 3064 fprintf(stderr, "Failed to open %s for reading\n", filename); |
2978 return 1; | 3065 return 1; |
2979 } | 3066 } |
3007 char size = cmd->format ? cmd->format[0] : 'b'; | 3094 char size = cmd->format ? cmd->format[0] : 'b'; |
3008 if (size != 'b' && size != 'w' && size != 'l') { | 3095 if (size != 'b' && size != 'w' && size != 'l') { |
3009 fprintf(stderr, "Invalid size %s\n", cmd->format); | 3096 fprintf(stderr, "Invalid size %s\n", cmd->format); |
3010 return 1; | 3097 return 1; |
3011 } | 3098 } |
3012 FILE *f = fopen(cmd->args[0].raw, "wb"); | 3099 if (!eval_expr(root, cmd->args[0].parsed, &cmd->args[0].value)) { |
3100 fprintf(stderr, "Failed to eval %s\n", cmd->args[0].raw); | |
3101 return 1; | |
3102 } | |
3103 char *fname = get_cstring(cmd->args[0].value); | |
3104 if (!fname) { | |
3105 fprintf(stderr, "First argument to save must be a string\n"); | |
3106 return 1; | |
3107 } | |
3108 FILE *f = fopen(fname, "wb"); | |
3013 if (!f) { | 3109 if (!f) { |
3014 fprintf(stderr, "Failed to open %s for writing\n", cmd->args[0].raw); | 3110 fprintf(stderr, "Failed to open %s for writing\n", fname); |
3015 return 1; | 3111 return 1; |
3016 } | 3112 } |
3017 uint32_t start = 0; | 3113 uint32_t start = 0; |
3018 debug_val val; | 3114 debug_val val; |
3019 debug_array * arr = NULL; | 3115 debug_array * arr = NULL; |
3161 char size = cmd->format ? cmd->format[0] : 'b'; | 3257 char size = cmd->format ? cmd->format[0] : 'b'; |
3162 if (size != 'b' && size != 'w' && size != 'l') { | 3258 if (size != 'b' && size != 'w' && size != 'l') { |
3163 fprintf(stderr, "Invalid size %s\n", cmd->format); | 3259 fprintf(stderr, "Invalid size %s\n", cmd->format); |
3164 return 1; | 3260 return 1; |
3165 } | 3261 } |
3166 FILE *f = fopen(cmd->args[0].raw, "rb"); | 3262 if (!eval_expr(root, cmd->args[0].parsed, &cmd->args[0].value)) { |
3263 fprintf(stderr, "Failed to eval %s\n", cmd->args[0].raw); | |
3264 return 1; | |
3265 } | |
3266 char *fname = get_cstring(cmd->args[0].value); | |
3267 if (!fname) { | |
3268 fprintf(stderr, "First argument to load must be a string\n"); | |
3269 return 1; | |
3270 } | |
3271 FILE *f = fopen(fname, "rb"); | |
3167 if (!f) { | 3272 if (!f) { |
3168 fprintf(stderr, "Failed to open %s for reading\n", cmd->args[0].raw); | 3273 fprintf(stderr, "Failed to open %s for reading\n", fname); |
3169 return 1; | 3274 return 1; |
3170 } | 3275 } |
3171 uint32_t start = 0; | 3276 uint32_t start = 0; |
3172 debug_val val; | 3277 debug_val val; |
3173 debug_array * arr = NULL; | 3278 debug_array * arr = NULL; |
3774 }, | 3879 }, |
3775 .usage = "printf FORMAT EXPRESSION...", | 3880 .usage = "printf FORMAT EXPRESSION...", |
3776 .desc = "Print a string with C-style formatting specifiers replaced with the value of the remaining arguments", | 3881 .desc = "Print a string with C-style formatting specifiers replaced with the value of the remaining arguments", |
3777 .impl = cmd_printf, | 3882 .impl = cmd_printf, |
3778 .min_args = 1, | 3883 .min_args = 1, |
3779 .max_args = -1, | 3884 .max_args = -1 |
3780 .raw_args = 1 | |
3781 }, | 3885 }, |
3782 { | 3886 { |
3783 .names = (const char *[]){ | 3887 .names = (const char *[]){ |
3784 "softreset", "sr", NULL | 3888 "softreset", "sr", NULL |
3785 }, | 3889 }, |
3901 }, | 4005 }, |
3902 .usage = "bindup NAME", | 4006 .usage = "bindup NAME", |
3903 .desc = "Simulate a keyup for binding NAME", | 4007 .desc = "Simulate a keyup for binding NAME", |
3904 .impl = cmd_bindup, | 4008 .impl = cmd_bindup, |
3905 .min_args = 1, | 4009 .min_args = 1, |
3906 .max_args = 1, | 4010 .max_args = 1 |
3907 .raw_args = 1 | |
3908 }, | 4011 }, |
3909 { | 4012 { |
3910 .names = (const char *[]){ | 4013 .names = (const char *[]){ |
3911 "binddown", NULL | 4014 "binddown", NULL |
3912 }, | 4015 }, |
3913 .usage = "bindown NAME", | 4016 .usage = "bindown NAME", |
3914 .desc = "Simulate a keydown for binding NAME", | 4017 .desc = "Simulate a keydown for binding NAME", |
3915 .impl = cmd_binddown, | 4018 .impl = cmd_binddown, |
3916 .min_args = 1, | 4019 .min_args = 1, |
3917 .max_args = 1, | 4020 .max_args = 1 |
3918 .raw_args = 1 | |
3919 }, | 4021 }, |
3920 { | 4022 { |
3921 .names = (const char *[]){ | 4023 .names = (const char *[]){ |
3922 "condition", NULL | 4024 "condition", NULL |
3923 }, | 4025 }, |
3958 }, | 4060 }, |
3959 .usage = "symbols [FILENAME]", | 4061 .usage = "symbols [FILENAME]", |
3960 .desc = "Loads a list of symbols from the file indicated by FILENAME or lists currently loaded symbols if FILENAME is omitted", | 4062 .desc = "Loads a list of symbols from the file indicated by FILENAME or lists currently loaded symbols if FILENAME is omitted", |
3961 .impl = cmd_symbols, | 4063 .impl = cmd_symbols, |
3962 .min_args = 0, | 4064 .min_args = 0, |
3963 .max_args = 1, | 4065 .max_args = 1 |
3964 .raw_args = 1 | |
3965 }, | 4066 }, |
3966 { | 4067 { |
3967 .names = (const char *[]){ | 4068 .names = (const char *[]){ |
3968 "save", NULL | 4069 "save", NULL |
3969 }, | 4070 }, |