diff options
Diffstat (limited to 'command.y')
-rw-r--r-- | command.y | 1680 |
1 files changed, 1680 insertions, 0 deletions
diff --git a/command.y b/command.y new file mode 100644 index 00000000..6d203b9a --- /dev/null +++ b/command.y @@ -0,0 +1,1680 @@ +/* + * command.y - yacc/bison parser for debugger command + */ + +/* + * Copyright (C) 2004, 2010 the Free Software Foundation, Inc. + * + * This file is part of GAWK, the GNU implementation of the + * AWK Programming Language. + * + * GAWK is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GAWK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +%{ +#include "awk.h" +#include "cmd.h" + +#if 0 +#define YYDEBUG 12 +int yydebug = 2; +#endif + +static int yylex(void); +static void yyerror(const char *mesg, ...); + +static int find_command(const char *token, size_t toklen); + +static int want_nodeval = FALSE; + +static int cmd_idx = -1; /* index of current command in cmd table */ +static int repeat_idx = -1; /* index of last repeatable command in command table */ +static CMDARG *arg_list = NULL; /* list of arguments */ +static long errcount = 0; +static char *lexptr_begin = NULL; +static int in_commands = FALSE; +static int num_dim; + +static int in_eval = FALSE; +static const char start_EVAL[] = "function @eval(){"; +static const char end_EVAL[] = "}"; +static CMDARG *append_statement(CMDARG *alist, char *stmt); +static char *next_word(char *p, int len, char **endp); +static NODE *concat_args(CMDARG *a, int count); + +#ifdef HAVE_LIBREADLINE +static void history_expand_line(char **line); +static char *command_generator(const char *text, int state); +static char *srcfile_generator(const char *text, int state); +static char *argument_generator(const char *text, int state); +static char *variable_generator(const char *text, int state); +extern char *option_generator(const char *text, int state); +static int this_cmd = D_illegal; +#else +#define history_expand_line(p) /* nothing */ +static int rl_inhibit_completion; /* dummy variable */ +#endif + +struct argtoken { + const char *name; + enum argtype cmd; + enum nametypeval value; +}; + +/* + * These two should be static, but there are some compilers that + * don't like the static keyword with an empty size. Therefore give + * them names that are less likely to conflict with the rest of gawk. + */ +#define argtab zz_debug_argtab +#define cmdtab zz_debug_cmdtab + +extern struct argtoken argtab[]; +extern struct cmdtoken cmdtab[]; + +static CMDARG *mk_cmdarg(enum argtype type); +static void append_cmdarg(CMDARG *arg); +static int find_argument(CMDARG *arg); +#define YYSTYPE CMDARG * +%} + +%token D_BACKTRACE D_BREAK D_CLEAR D_CONTINUE D_DELETE D_DISABLE D_DOWN +%token D_ENABLE D_FINISH D_FRAME D_HELP D_IGNORE D_INFO D_LIST +%token D_NEXT D_NEXTI D_PRINT D_PRINTF D_QUIT D_RETURN D_RUN D_SET +%token D_STEP D_STEPI D_TBREAK D_UP D_UNTIL +%token D_DISPLAY D_UNDISPLAY D_WATCH D_UNWATCH +%token D_DUMP D_TRACE +%token D_INT D_STRING D_NODE D_VARIABLE +%token D_OPTION D_COMMANDS D_END D_SILENT D_SOURCE +%token D_SAVE D_EVAL D_CONDITION +%token D_STATEMENT + +%% + +input + : /* empty */ + | input line + { + cmd_idx = -1; + want_nodeval = FALSE; + if (lexptr_begin != NULL) { + if (input_from_tty && lexptr_begin[0] != '\0') + add_history(lexptr_begin); + efree(lexptr_begin); + lexptr_begin = NULL; + } + if (arg_list != NULL) { + free_cmdarg(arg_list); + arg_list = NULL; + } + } + ; + +line + : nls + | command nls + { + if (errcount == 0 && cmd_idx >= 0) { + Func_cmd cmdfunc; + int terminate = FALSE; + CMDARG *args; + int ctype = 0; + + ctype = cmdtab[cmd_idx].type; + + /* a blank line repeats previous command + * (list, next, nexti, step, stepi and continue without arguments). + * save the index in the command table; used in yylex + */ + if ((ctype == D_list + || ctype == D_next + || ctype == D_step + || ctype == D_nexti + || ctype == D_stepi + || ctype == D_continue) + && arg_list == NULL + && ! in_commands + && input_from_tty + ) + repeat_idx = cmd_idx; + else + repeat_idx = -1; + + /* call the command handler; reset the globals arg_list, cmd_idx, + * since this handler could invoke yyparse again. + * call do_commands for the list of commands in `commands'; + * arg_list isn't freed on return. + */ + + cmdfunc = cmdtab[cmd_idx].cf_ptr; + if (in_commands) + cmdfunc = do_commands; + cmd_idx = -1; + want_nodeval = FALSE; + + args = arg_list; + arg_list = NULL; + + terminate = (*cmdfunc)(args, ctype); + if (! in_commands || ctype == D_commands) + free_cmdarg(args); + if (terminate) + YYACCEPT; + } + } + | error nls + { + yyerrok; + } + ; + +control_cmd + : D_CONTINUE + | D_NEXT + | D_NEXTI + | D_STEP + | D_STEPI + ; + +d_cmd + : D_UNDISPLAY + | D_UNWATCH + | D_DISABLE + | D_DELETE + ; + +frame_cmd + : D_UP + | D_DOWN + | D_BACKTRACE + | D_FRAME + ; + +break_cmd + : D_BREAK + | D_TBREAK + ; + +/* mid-rule action buried in non-terminal to avoid conflict */ +set_want_nodeval + : { want_nodeval = TRUE; } + ; + +eval_prologue + : D_EVAL set_want_nodeval opt_param_list nls + { + if (errcount == 0) { + /* don't free arg_list; passed on to statement_list + * non-terminal (empty rule action). See below. + */ + if (input_from_tty) { + dPrompt = eval_Prompt; + fprintf(out_fp, _("Type (g)awk statement(s). End with the command \"end\"\n")); + rl_inhibit_completion = 1; + } + cmd_idx = -1; + in_eval = TRUE; + } + } + ; + +statement_list + : /* empty */ + { + $$ = append_statement(arg_list, (char *) start_EVAL); + if (read_a_line == read_commands_string) /* unserializing 'eval' in 'commands' */ + $$->a_string[0] = '\0'; + free_cmdarg(arg_list); + arg_list = NULL; + } + | statement_list D_STATEMENT { $$ = append_statement($1, lexptr_begin); } nls + { + $$ = $3; + } + ; + +eval_cmd + : eval_prologue statement_list D_END + { + arg_list = append_statement($2, (char *) end_EVAL); + if (read_a_line == read_commands_string) { /* unserializing 'eval' in 'commands' */ + char *str = arg_list->a_string; + size_t len = strlen(str); + assert(len > 2 && str[len - 2] == '}'); + str[len - 2] = '\0'; + } + if (input_from_tty) { + dPrompt = in_commands ? commands_Prompt : dgawk_Prompt; + rl_inhibit_completion = 0; + } + cmd_idx = find_command("eval", 4); + in_eval = FALSE; + } + | D_EVAL set_want_nodeval string_node + { + NODE *n; + CMDARG *arg; + n = $3->a_node; + arg = append_statement(NULL, (char *) start_EVAL); + (void) append_statement(arg, n->stptr); + (void) append_statement(arg, (char *) end_EVAL); + free_cmdarg(arg_list); + arg_list = arg; + } + ; + +command + : D_HELP help_args + | D_QUIT + | D_RUN + | D_FINISH + | control_cmd opt_plus_integer + | frame_cmd opt_integer + { + if (cmdtab[cmd_idx].class == D_FRAME + && $2 != NULL && $2->a_int < 0) + yyerror(_("invalid frame number: %d"), $2->a_int); + } + | D_INFO D_STRING + { + int idx = find_argument($2); + if (idx < 0) + yyerror(_("info: invalid option - \"%s\""), $2->a_string); + else { + efree($2->a_string); + $2->a_string = NULL; + $2->type = D_argument; + $2->a_argument = argtab[idx].value; + } + } + | D_IGNORE plus_integer D_INT + | D_ENABLE enable_args + | D_PRINT { want_nodeval = TRUE; } print_args + | D_PRINTF { want_nodeval = TRUE; } printf_args + | D_LIST list_args + | D_UNTIL location + | D_CLEAR location + | break_cmd break_args + | D_SET { want_nodeval = TRUE; } variable '=' node + | D_OPTION option_args + | D_RETURN { want_nodeval = TRUE; } opt_node + | D_DISPLAY { want_nodeval = TRUE; } opt_variable + | D_WATCH { want_nodeval = TRUE; } variable condition_exp + | d_cmd opt_integer_list + | D_DUMP opt_string + | D_SOURCE D_STRING + { + if (in_cmd_src($2->a_string)) + yyerror(_("source \"%s\": already sourced."), $2->a_string); + } + | D_SAVE D_STRING + { + if (! input_from_tty) + yyerror(_("save \"%s\": command not permitted."), $2->a_string); + } + | D_COMMANDS commands_arg + { + int type = 0; + int num; + + if ($2 != NULL) + num = $2->a_int; + + if (errcount != 0) + ; + else if (in_commands) + yyerror(_("Can't use command `commands' for breakpoint/watchpoint commands")); + else if ($2 == NULL && ! (type = has_break_or_watch_point(&num, TRUE))) + yyerror(_("no breakpoint/watchpoint has been set yet")); + else if ($2 != NULL && ! (type = has_break_or_watch_point(&num, FALSE))) + yyerror(_("invalid breakpoint/watchpoint number")); + if (type) { + in_commands = TRUE; + if (input_from_tty) { + dPrompt = commands_Prompt; + fprintf(out_fp, _("Type commands for when %s %d is hit, one per line.\n"), + (type == D_break) ? "breakpoint" : "watchpoint", num); + fprintf(out_fp, _("End with the command \"end\"\n")); + } + } + } + | D_END + { + if (! in_commands) + yyerror(_("`end' valid only in command `commands' or `eval'")); + else { + if (input_from_tty) + dPrompt = dgawk_Prompt; + in_commands = FALSE; + } + } + | D_SILENT + { + if (! in_commands) + yyerror(_("`silent' valid only in command `commands'")); + } + | D_TRACE D_STRING + { + int idx = find_argument($2); + if (idx < 0) + yyerror(_("trace: invalid option - \"%s\""), $2->a_string); + else { + efree($2->a_string); + $2->a_string = NULL; + $2->type = D_argument; + $2->a_argument = argtab[idx].value; + } + } + | D_CONDITION plus_integer { want_nodeval = TRUE; } condition_exp + { + int type; + int num = $2->a_int; + type = has_break_or_watch_point(&num, FALSE); + if (! type) + yyerror(_("condition: invalid breakpoint/watchpoint number")); + } + | eval_cmd + { + if (in_commands) { + /* Prepend command 'eval' to argument list */ + CMDARG *arg; + arg = mk_cmdarg(D_string); + arg->a_string = estrdup("eval", 4); + arg->next = arg_list; + arg_list = arg; + } + } + ; + +condition_exp + : opt_string_node + { + if ($1 != NULL) { + NODE *n = $1->a_node; + $1->type = D_string; + $1->a_string = n->stptr; + freenode(n); + } + $$ = $1; + } + ; + +commands_arg + : opt_plus_integer + | error + { $$ = NULL; } + ; + +opt_param_list + : /* empty */ + { $$ = NULL; } + | param_list + ; + +param_list + : D_VARIABLE + | param_list D_VARIABLE + | param_list ',' D_VARIABLE + | error + { $$ = NULL; } + ; + +opt_string_node + : /* empty */ + { $$ = NULL; } + | string_node + | error + { $$ = NULL; } + ; + +string_node + : D_NODE + { + NODE *n; + n = $1->a_node; + if ((n->flags & STRING) == 0) + yyerror(_("argument not a string")); + } + ; + +option_args + : /* empty */ + { $$ = NULL; } + | D_STRING + { + if (find_option($1->a_string) < 0) + yyerror(_("option: invalid parameter - \"%s\""), $1->a_string); + } + | D_STRING '=' D_STRING + { + if (find_option($1->a_string) < 0) + yyerror(_("option: invalid parameter - \"%s\""), $1->a_string); + } + ; + +func_name + : D_STRING + { + NODE *n; + n = lookup($1->a_string); + if (n == NULL || n->type != Node_func) + yyerror(_("no such function - \"%s\""), $1->a_string); + else { + $1->type = D_func; + efree($1->a_string); + $1->a_string = NULL; + $1->a_node = n; + } + } + ; + +location + : /* empty */ + { $$ = NULL; } + | plus_integer + | func_name + | D_STRING ':' plus_integer + | D_STRING ':' func_name + ; + +break_args + : /* empty */ + { $$ = NULL; } + | plus_integer { want_nodeval = TRUE; } condition_exp + | func_name + | D_STRING ':' plus_integer { want_nodeval = TRUE; } condition_exp + | D_STRING ':' func_name + ; + +opt_variable + : /* empty */ + { $$ = NULL; } + | variable + ; + +opt_string + : /* empty */ + { $$ = NULL; } + | D_STRING + ; + +opt_node + : /* empty */ + { $$ = NULL; } + | node + ; + +help_args + : /* empty */ + | D_STRING + ; + +enable_args + : opt_integer_list + | D_STRING opt_integer_list + { + int idx = find_argument($1); + if (idx < 0) + yyerror(_("enable: invalid option - \"%s\""), $1->a_string); + else { + efree($1->a_string); + $1->a_string = NULL; + $1->type = D_argument; + $1->a_argument = argtab[idx].value; + } + } + ; + +print_exp + : variable + | '@' D_VARIABLE + { + $2->type = D_array; /* dump all items */ + $2->a_count = 0; + } + | '@' D_VARIABLE subscript_list /* dump sub-array items*/ + { + $2->type = D_array; + $2->a_count = num_dim; + } + ; + +print_args + : print_exp + | print_args print_exp + | print_args ',' print_exp + | error + ; + +printf_exp + : D_NODE + | variable + ; + +printf_args + : printf_exp + | printf_args ',' printf_exp + | error + ; + +list_args + : /* empty */ + { $$ = NULL; } + | '+' + { $$ = NULL; } + | '-' + { + CMDARG *a; + a = mk_cmdarg(D_int); + a->a_int = -1; + append_cmdarg(a); + } + | plus_integer + | func_name + | integer_range + | D_STRING ':' plus_integer + | D_STRING ':' func_name + | D_STRING ':' integer_range + ; + +integer_range + : plus_integer '-' plus_integer + { + if ($1->a_int > $3->a_int) + yyerror(_("invalid range specification: %d - %d"), + $1->a_int, $3->a_int); + else + $1->type = D_range; + $$ = $1; + } + ; + +opt_integer_list + : /* empty */ + { $$ = NULL; } + | integer_list + | error + ; + +integer_list + : plus_integer + | integer_range + | integer_list plus_integer + | integer_list integer_range + ; + +exp_list + : node + { $$ = $1; } + | exp_list ',' node + { $$ = $1; } + | error + ; + +subscript + : '[' exp_list ']' + { + CMDARG *a; + NODE *subs; + int count = 0; + + for (a = $2; a != NULL; a = a->next) + count++; + subs =concat_args($2, count); + free_cmdarg($2->next); + $2->next = NULL; + $2->type = D_node; + $2->a_node = subs; + $$ = $2; + } + | '[' exp_list error + ; + +subscript_list + : subscript + { $$ = $1; num_dim = 1; } + | subscript_list subscript + { $$ = $1; num_dim++; } + ; + +variable + : D_VARIABLE + | '$' D_NODE + { + NODE *n = $2->a_node; + if ((n->flags & NUMBER) == 0) + yyerror(_("non-numeric value for field number")); + else + $2->type = D_field; + $$ = $2; + } + | D_VARIABLE subscript_list + { + /* a_string is array name, a_count is dimension count */ + $1->type = D_subscript; + $1->a_count = num_dim; + $$ = $1; + } + ; + +node + : D_NODE + { $$ = $1; } + | '+' D_NODE + { + NODE *n = $2->a_node; + if ((n->flags & NUMBER) == 0) + yyerror(_("non-numeric value found, numeric expected")); + $$ = $2; + } + | '-' D_NODE + { + NODE *n = $2->a_node; + if ((n->flags & NUMBER) == 0) + yyerror(_("non-numeric value found, numeric expected")); + else + $2->a_node->numbr = - n->numbr; + $$ = $2; + } + ; + +opt_plus_integer + : /* empty */ + { $$ = NULL; } + | plus_integer + { $$ = $1; } + ; + +opt_integer + : /* empty */ + { $$ = NULL; } + | integer + { $$ = $1; } + ; + +plus_integer + : D_INT + { + if ($1->a_int == 0) + yyerror(_("non-zero integer value")); + $$ = $1; + } + | '+' D_INT + { + if ($2->a_int == 0) + yyerror(_("non-zero integer value")); + $$ = $2; + } + ; + +integer + : D_INT + { $$ = $1; } + | '+' D_INT + { $$ = $2; } + | '-' D_INT + { + $2->a_int = - $2->a_int; + $$ = $2; + } + ; + +nls + : '\n' + { + if (lexptr_begin != NULL) { + if (input_from_tty && lexptr_begin[0] != '\0') + add_history(lexptr_begin); + efree(lexptr_begin); + lexptr_begin = NULL; + } + } + ; + +%% + + +/* append_statement --- append 'stmt' to the list of eval awk statements */ + +static CMDARG * +append_statement(CMDARG *alist, char *stmt) +{ + CMDARG *a, *arg; + char *s; + int len, slen, ssize; + +#define EVALSIZE 512 + + if (stmt == start_EVAL) { + len = sizeof(start_EVAL); + for (a = alist; a != NULL; a = a->next) + len += strlen(a->a_string) + 1; /* 1 for ',' */ + len += EVALSIZE; + + emalloc(s, char *, (len + 2) * sizeof(char), "append_statement"); + arg = mk_cmdarg(D_string); + arg->a_string = s; + arg->a_count = len; /* kludge */ + + slen = sizeof("function @eval(") - 1; + memcpy(s, start_EVAL, slen); + + for (a = alist; a != NULL; a = a->next) { + len = strlen(a->a_string); + memcpy(s + slen, a->a_string, len); + slen += len; + if (a->next != NULL) + s[slen++] = ','; + } + s[slen++] = ')'; + s[slen++] = '{'; + s[slen] = '\0'; + return arg; + } + + len = strlen(stmt) + 1; /* 1 for newline */ + s = alist->a_string; + slen = strlen(s); + ssize = alist->a_count; + if (len > ssize - slen) { + ssize = slen + len + EVALSIZE; + erealloc(s, char *, (ssize + 2) * sizeof(char), "append_statement"); + alist->a_string = s; + alist->a_count = ssize; + } + memcpy(s + slen, stmt, len); + slen += len; + if (slen >= 2 && s[slen - 2] != '\n') { + s[slen - 1] = '\n'; + s[slen] = '\0'; + } + + if (stmt == end_EVAL) + erealloc(alist->a_string, char *, slen + 2, "append_statement"); + return alist; + +#undef EVALSIZE +} + + +/* command names sorted in ascending order */ + +struct cmdtoken cmdtab[] = { +{ "backtrace", "bt", D_backtrace, D_BACKTRACE, do_backtrace, + gettext_noop("backtrace [N] - print trace of all or N innermost (outermost if N < 0) frames.") }, +{ "break", "b", D_break, D_BREAK, do_breakpoint, + gettext_noop("break [[filename:]N|function] - set breakpoint at the specified location.") }, +{ "clear", "", D_clear, D_CLEAR, do_clear, + gettext_noop("clear [[filename:]N|function] - delete breakpoints previously set.") }, +{ "commands", "", D_commands, D_COMMANDS, do_commands, + gettext_noop("commands [num] - starts a list of commands to be executed at a breakpoint(watchpoint) hit.") }, +{ "condition", "", D_condition, D_CONDITION, do_condition, + gettext_noop("condition num [expr] - set or clear breakpoint or watchpoint condition.") }, +{ "continue", "c", D_continue, D_CONTINUE, do_continue, + gettext_noop("continue [COUNT] - continue program being debugged.") }, +{ "delete", "d", D_delete, D_DELETE, do_delete_breakpoint, + gettext_noop("delete [breakpoints] [range] - delete specified breakpoints.") }, +{ "disable", "", D_disable, D_DISABLE, do_disable_breakpoint, + gettext_noop("disable [breakpoints] [range] - disable specified breakpoints.") }, +{ "display", "", D_display, D_DISPLAY, do_display, + gettext_noop("display [var] - print value of variable each time the program stops.") }, +{ "down", "", D_down, D_DOWN, do_down, + gettext_noop("down [N] - move N frames down the stack.") }, +{ "dump", "", D_dump, D_DUMP, do_dump_instructions, + gettext_noop("dump [filename] - dump instructions to file or stdout.") }, +{ "enable", "e", D_enable, D_ENABLE, do_enable_breakpoint, + gettext_noop("enable [once|del] [breakpoints] [range] - enable specified breakpoints.") }, +{ "end", "", D_end, D_END, do_commands, + gettext_noop("end - end a list of commands or awk statements.") }, +{ "eval", "", D_eval, D_EVAL, do_eval, + gettext_noop("eval stmt|[p1, p2, ...] - evaluate awk statement(s).") }, +{ "finish", "", D_finish, D_FINISH, do_finish, + gettext_noop("finish - execute until selected stack frame returns.") }, +{ "frame", "f", D_frame, D_FRAME, do_frame, + gettext_noop("frame [N] - select and print stack frame number N.") }, +{ "help", "h", D_help, D_HELP, do_help, + gettext_noop("help [command] - print list of commands or explanation of command.") }, +{ "ignore", "", D_ignore, D_IGNORE, do_ignore_breakpoint, + gettext_noop("ignore N COUNT - set ignore-count of breakpoint number N to COUNT.") }, +{ "info", "i", D_info, D_INFO, do_info, + gettext_noop("info topic - source|sources|variables|functions|break|frame|args|locals|display|watch.") }, +{ "list", "l", D_list, D_LIST, do_list, + gettext_noop("list [-|+|[filename:]lineno|function|range] - list specified line(s).") }, +{ "next", "n", D_next, D_NEXT, do_next, + gettext_noop("next [COUNT] - step program, proceeding through subroutine calls.") }, +{ "nexti", "ni", D_nexti, D_NEXTI, do_nexti, + gettext_noop("nexti [COUNT] - step one instruction, but proceed through subroutine calls.") }, +{ "option", "o", D_option, D_OPTION, do_option, + gettext_noop("option [name[=value]] - set or display debugger option(s).") }, +{ "print", "p", D_print, D_PRINT, do_print_var, + gettext_noop("print var [var] - print value of a variable or array.") }, +{ "printf", "", D_printf, D_PRINTF, do_print_f, + gettext_noop("printf format, [arg], ... - formatted output.") }, +{ "quit", "q", D_quit, D_QUIT, do_quit, + gettext_noop("quit - exit debugger.") }, +{ "return", "", D_return, D_RETURN, do_return, + gettext_noop("return [value] - make selected stack frame return to its caller.") }, +{ "run", "r", D_run, D_RUN, do_run, + gettext_noop("run - start or restart executing program.") }, +#ifdef HAVE_LIBREADLINE +{ "save", "", D_save, D_SAVE, do_save, + gettext_noop("save filename - save commands from the session to file.") }, +#endif +{ "set", "", D_set, D_SET, do_set_var, + gettext_noop("set var = value - assign value to a scalar variable.") }, +{ "silent", "", D_silent, D_SILENT, do_commands, + gettext_noop("silent - suspends usual message when stopped at a breakpoint/watchpoint.") }, +{ "source", "", D_source, D_SOURCE, do_source, + gettext_noop("source file - execute commands from file.") }, +{ "step", "s", D_step, D_STEP, do_step, + gettext_noop("step [COUNT] - step program until it reaches a different source line.") }, +{ "stepi", "si", D_stepi, D_STEPI, do_stepi, + gettext_noop("stepi [COUNT] - step one instruction exactly.") }, +{ "tbreak", "t", D_tbreak, D_TBREAK, do_tmp_breakpoint, + gettext_noop("tbreak [[filename:]N|function] - set a temporary breakpoint.") }, +{ "trace", "", D_trace, D_TRACE, do_trace_instruction, + gettext_noop("trace on|off - print instruction before executing.") }, +{ "undisplay", "", D_undisplay, D_UNDISPLAY, do_undisplay, + gettext_noop("undisplay [N] - remove variable(s) from automatic display list.") }, +{ "until", "u", D_until, D_UNTIL, do_until, + gettext_noop("until [[filename:]N|function] - execute until program reaches a different line or line N within current frame.") }, +{ "unwatch", "", D_unwatch, D_UNWATCH, do_unwatch, + gettext_noop("unwatch [N] - remove variable(s) from watch list.") }, +{ "up", "", D_up, D_UP, do_up, + gettext_noop("up [N] - move N frames up the stack.") }, +{ "watch", "w", D_watch, D_WATCH, do_watch, + gettext_noop("watch var - set a watchpoint for a variable.") }, +{ NULL, NULL, D_illegal, 0, (Func_cmd) 0, + NULL }, +}; + +struct argtoken argtab[] = { + { "args", D_info, A_ARGS }, + { "break", D_info, A_BREAK }, + { "del", D_enable, A_DEL }, + { "display", D_info, A_DISPLAY }, + { "frame", D_info, A_FRAME }, + { "functions", D_info, A_FUNCTIONS }, + { "locals", D_info, A_LOCALS }, + { "off", D_trace, A_TRACE_OFF }, + { "on", D_trace, A_TRACE_ON }, + { "once", D_enable, A_ONCE }, + { "source", D_info, A_SOURCE }, + { "sources", D_info, A_SOURCES }, + { "variables", D_info, A_VARIABLES }, + { "watch", D_info, A_WATCH }, + { NULL, D_illegal, 0 }, +}; + + +/* get_command --- return command handler function */ + +Func_cmd +get_command(int ctype) +{ + int i; + for (i = 0; cmdtab[i].name != NULL; i++) { + if (cmdtab[i].type == ctype) + return cmdtab[i].cf_ptr; + } + return (Func_cmd) 0; +} + +/* get_command_name --- return command name given it's type */ + +const char * +get_command_name(int ctype) +{ + int i; + for (i = 0; cmdtab[i].name != NULL; i++) { + if (cmdtab[i].type == ctype) + return cmdtab[i].name; + } + return NULL; +} + +/* mk_cmdarg --- make an argument for command */ + +static CMDARG * +mk_cmdarg(enum argtype type) +{ + CMDARG *arg; + emalloc(arg, CMDARG *, sizeof(CMDARG), "mk_cmdarg"); + memset(arg, 0, sizeof(CMDARG)); + arg->type = type; + return arg; +} + +/* append_cmdarg --- append ARG to the list of arguments for the current command */ + +static void +append_cmdarg(CMDARG *arg) +{ + static CMDARG *savetail; + + if (arg_list == NULL) + arg_list = arg; + else + savetail->next = arg; + savetail = arg; +} + +/* free_cmdarg --- free all arguments in LIST */ + +void +free_cmdarg(CMDARG *list) +{ + CMDARG *arg, *nexta; + + for (arg = list; arg != NULL; arg = nexta) { + nexta = arg->next; + + switch (arg->type) { + case D_variable: + case D_subscript: + case D_array: + case D_string: + if (arg->a_string != NULL) + efree(arg->a_string); + break; + case D_node: + case D_field: + unref(arg->a_node); + break; + default: + break; + } + efree(arg); + } +} + +/* yyerror --- print a syntax error message */ + +static void +yyerror(const char *mesg, ...) +{ + va_list args; + va_start(args, mesg); + fprintf(out_fp, _("error: ")); + vfprintf(out_fp, mesg, args); + fprintf(out_fp, "\n"); + va_end(args); + errcount++; + repeat_idx = -1; +} + + +/* yylex --- read a command and turn it into tokens */ + +static int +yylex(void) +{ + static char *lexptr = NULL; + static char *lexend; + int c; + char *tokstart; + size_t toklen; + + yylval = (CMDARG *) NULL; + + if (errcount > 0 && lexptr_begin == NULL) { + /* fake a new line */ + errcount = 0; + return '\n'; + } + + if (lexptr_begin == NULL) { +again: + lexptr_begin = read_a_line(dPrompt); + if (lexptr_begin == NULL) { /* EOF or error */ + if (get_eof_status() == EXIT_FATAL) + exit(EXIT_FATAL); + if (get_eof_status() == EXIT_FAILURE) { + static int seen_eof = 0; + + /* force a quit, and let do_quit (in debug.c) exit */ + if (! seen_eof) { + if (errno != 0) { + fprintf(stderr, _("can't read command (%s)"), strerror(errno)); + exit_val = EXIT_FAILURE; + } /* else + exit_val = EXIT_SUCCESS; */ + + seen_eof = 1; + return '\n'; /* end current command if any */ + } else if (seen_eof++ == 1) { + cmd_idx = find_command("quit", 4); + return D_QUIT; /* 'quit' token */ + } else + return '\n'; /* end command 'quit' */ + } + if (errno != 0) + d_error(_("can't read command (%s)"), strerror(errno)); + if (pop_cmd_src() == 0) + goto again; + exit(EXIT_FATAL); /* shouldn't happen */ + } + + if (! in_commands && ! in_eval /* history expansion off in 'commands' and 'eval' */ + && input_from_tty + ) + history_expand_line(&lexptr_begin); + + lexptr = lexptr_begin; + lexend = lexptr + strlen(lexptr); + if (*lexptr == '\0' /* blank line */ + && repeat_idx >= 0 + && input_from_tty + && ! in_eval + ) { +#ifdef HAVE_LIBREADLINE + HIST_ENTRY *h; + h = previous_history(); + if (h != NULL) + add_history(h->line); +#endif + cmd_idx = repeat_idx; + return cmdtab[cmd_idx].class; /* repeat last command */ + } + repeat_idx = -1; + } + + c = *lexptr; + + while (c == ' ' || c == '\t') + c = *++lexptr; + + if (! input_from_tty && c == '#') + return '\n'; + + tokstart = lexptr; + if (lexptr >= lexend) + return '\n'; + + if (cmd_idx < 0) { /* need a command */ + if (c == '?' && tokstart[1] == '\0' && ! in_eval) { + lexptr++; + cmd_idx = find_command("help", 4); + return D_HELP; + } + + while (c != '\0' && c != ' ' && c != '\t') { + if (! isalpha(c) && ! in_eval) { + yyerror(_("invalid character in command")); + return '\n'; + } + c = *++lexptr; + } + + toklen = lexptr - tokstart; + + if (in_eval) { + if (toklen == 3 + && tokstart[3] == '\0' + && tokstart[0] == 'e' + && tokstart[1] == 'n' + && tokstart[2] == 'd' + ) { + cmd_idx = find_command(tokstart, toklen); + return D_END; + } + lexptr = lexend; + return D_STATEMENT; + } + + cmd_idx = find_command(tokstart, toklen); + if (cmd_idx >= 0) { + if (in_commands && cmdtab[cmd_idx].type != D_eval) { + /* add the actual command string (lexptr_begin) to + * arg_list; command string for 'eval' prepended to the arg_list + * in the grammer above (see eval_cmd non-terminal). + */ + CMDARG *arg; + arg = mk_cmdarg(D_string); + arg->a_string = estrdup(lexptr_begin, lexend - lexptr_begin); + append_cmdarg(arg); + } + return cmdtab[cmd_idx].class; + } else { + yyerror(_("unknown command - \"%.*s\", try help"), toklen, tokstart); + return '\n'; + } + } + + c = *lexptr; + + if (cmdtab[cmd_idx].type == D_option) { + if (c == '=') + return *lexptr++; + } else if (c == '-' || c == '+' || c == ':' || c == '|') + return *lexptr++; + + if (c == '"') { + char *str, *p; + int flags = ALREADY_MALLOCED; + int esc_seen = FALSE; + + toklen = lexend - lexptr; + emalloc(str, char *, toklen + 2, "yylex"); + p = str; + + while ((c = *++lexptr) != '"') { + if (lexptr == lexend) { +err: + efree(str); + yyerror(_("unterminated string")); + return '\n'; + } + if (c == '\\') { + c = *++lexptr; + esc_seen = TRUE; + if (want_nodeval || c != '"') + *p++ = '\\'; + } + if (lexptr == lexend) + goto err; + *p++ = c; + } + lexptr++; + *p = '\0'; + + if (! want_nodeval) { + yylval = mk_cmdarg(D_string); + yylval->a_string = estrdup(str, p - str); + append_cmdarg(yylval); + return D_STRING; + } else { /* awk string */ + if (esc_seen) + flags |= SCAN; + yylval = mk_cmdarg(D_node); + yylval->a_node = make_str_node(str, p - str, flags); + append_cmdarg(yylval); + return D_NODE; + } + } + + if (! want_nodeval) { + while ((c = *++lexptr) != '\0' && c != ':' && c != '-' + && c != ' ' && c != '\t' && c != '=') + ; + + /* Is it an integer? */ + if (isdigit(tokstart[0]) && cmdtab[cmd_idx].type != D_option) { + char *end; + long l; + + errno = 0; + l = strtol(tokstart, &end, 0); + if (errno != 0) { + yyerror(_("%s"), strerror(errno)); + errno = 0; + return '\n'; + } + + if (lexptr == end) { + yylval = mk_cmdarg(D_int); + yylval->a_int = l; + append_cmdarg(yylval); + return D_INT; + } + } + + /* Must be string */ + yylval = mk_cmdarg(D_string); + yylval->a_string = estrdup(tokstart, lexptr - tokstart); + append_cmdarg(yylval); + return D_STRING; + } + + /* assert(want_nodval == TRUE); */ + + /* look for awk number */ + + if (isdigit(tokstart[0])) { + double d; + + errno = 0; + d = strtod(tokstart, &lexptr); + if (errno != 0) { + yyerror(strerror(errno)); + errno = 0; + return '\n'; + } + yylval = mk_cmdarg(D_node); + yylval->a_node = make_number(d); + append_cmdarg(yylval); + return D_NODE; + } + + c = *lexptr; + if (c == '$' || c == '@' + || c == '[' || c == ']' + || c == ',' || c == '=') + return *lexptr++; + + if (c != '_' && ! isalpha(c)) { + yyerror(_("invalid character")); + return '\n'; + } + + while (isalnum(c) || c == '_') + c = *++lexptr; + toklen = lexptr - tokstart; + + /* awk variable */ + yylval = mk_cmdarg(D_variable); + yylval->a_string = estrdup(tokstart, toklen); + append_cmdarg(yylval); + return D_VARIABLE; +} + +/* find_argument --- find index in 'argtab' for a command option */ + +static int +find_argument(CMDARG *arg) +{ + /* non-number argument */ + int idx; + char *name, *p; + size_t len; + assert(cmd_idx >= 0); + name = arg->a_string; + len = strlen(name); + for (idx = 0; (p = (char *) argtab[idx].name) != NULL; idx++) { + if (cmdtab[cmd_idx].type == argtab[idx].cmd + && *p == *name + && strlen(p) == len + && strncmp(p, name, len) == 0 + ) + return idx; + } + return -1; /* invalid option */ +} + +/* concat_args --- concatenate argument strings into a single string NODE */ + +static NODE * +concat_args(CMDARG *arg, int count) +{ + NODE *n; + NODE **tmp; + char *str, *subsep, *p; + long len, subseplen; + int i; + + if (count == 1) { + n = force_string(arg->a_node); + return dupnode(n); + } + + emalloc(tmp, NODE **, count * sizeof(NODE *), "concat_args"); + subseplen = SUBSEP_node->var_value->stlen; + subsep = SUBSEP_node->var_value->stptr; + len = -subseplen; + + for (i = 0; i < count; i++) { + n = force_string(arg->a_node); + len += n->stlen + subseplen; + tmp[i] = n; + arg = arg->next; + } + + emalloc(str, char *, len + 2, "concat_args"); + n = tmp[0]; + memcpy(str, n->stptr, n->stlen); + p = str + n->stlen; + for (i = 1; i < count; i++) { + if (subseplen == 1) + *p++ = *subsep; + else if (subseplen > 0) { + memcpy(p, subsep, subseplen); + p += subseplen; + } + + n = tmp[i]; + memcpy(p, n->stptr, n->stlen); + p += n->stlen; + } + str[len] = '\0'; + efree(tmp); + return make_str_node(str, len, ALREADY_MALLOCED); +} + +/* find_command --- find the index in 'cmdtab' using exact, + * abbreviation or unique partial match + */ + +static int +find_command(const char *token, size_t toklen) +{ + char *name, *abrv; + int i, k; + int try_exact = TRUE; + int abrv_match = -1; + int partial_match = -1; + +#if 'a' == 0x81 /* it's EBCDIC */ + /* make sure all lower case characters in token (sorting + * isn't the solution in this case) + */ + for (i = 0; i < toklen; i++) { + if (token[i] != tolower(token[i])) + return -1; + } +#endif + + k = sizeof(cmdtab)/sizeof(cmdtab[0]) - 1; + for (i = 0; i < k; i++) { + name = (char *) cmdtab[i].name; + if (try_exact && *token == *name + && toklen == strlen(name) + && strncmp(name, token, toklen) == 0 + ) + return i; + if (*name > *token) + try_exact = FALSE; + if (abrv_match < 0) { + abrv = cmdtab[i].abbrvn; + if (abrv[0] == token[0]) { + if (toklen == 1 && ! abrv[1]) + abrv_match = i; + else if (toklen == 2 && abrv[1] == token[1]) + abrv_match = i; + } + } + if (! try_exact && abrv_match >= 0) + return abrv_match; + if (partial_match < 0) { + if (*token == *name + && toklen < strlen(name) + && strncmp(name, token, toklen) == 0 + ) { + if ((i == k - 1 || strncmp(cmdtab[i + 1].name, token, toklen) != 0) + && (i == 0 || strncmp(cmdtab[i - 1].name, token, toklen) != 0) + ) + partial_match = i; + } + } + } + return partial_match; +} + +/* do_help -- help command */ + +int +do_help(CMDARG *arg, int cmd) +{ + int i; + if (arg == NULL) { + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) { + for (i = 0; cmdtab[i].name != NULL; i++) { + gprintf(out_fp, "%s:\n", cmdtab[i].name); + gprintf(out_fp, "\t%s\n", _(cmdtab[i].help_txt)); + } + } + } else if (arg->type == D_string) { + char *name; + name = arg->a_string; + i = find_command(name, strlen(name)); + if (i >= 0) { + fprintf(out_fp, "%s\n", cmdtab[i].help_txt); + if (STREQ(cmdtab[i].name, "option")) + option_help(); + } else + fprintf(out_fp, _("undefined command: %s\n"), name); + } + + return FALSE; +} + + +/* next_word --- find the next word in a line to complete + * (word seperation characters are space and tab). + */ + +static char * +next_word(char *p, int len, char **endp) +{ + char *q; + int i; + + if (p == NULL || len <= 0) + return NULL; + for (i = 0; i < len; i++, p++) + if (*p != ' ' && *p != '\t') + break; + if (i == len) + return NULL; + if (endp != NULL) { + for (i++, q = p + 1; i < len; i++, q++) + if (*q == ' ' || *q == '\t') + break; + *endp = q; + } + return p; +} + +#ifdef HAVE_LIBREADLINE + +/* command_completion --- attempt to complete based on the word number in line; + * try to complete on command names if this is the first word; for the next + * word(s), the type of completion depends on the command name (first word). + */ + +#ifndef RL_READLINE_VERSION /* < 4.2a */ +#define rl_completion_matches(x, y) completion_matches((char *) (x), (y)) +#endif + + +char ** +command_completion(const char *text, int start, int end) +{ + char *cmdtok, *e; + int idx; + int len; + + rl_attempted_completion_over = TRUE; /* no default filename completion please */ + + this_cmd = D_illegal; + len = start; + if ((cmdtok = next_word(rl_line_buffer, len, &e)) == NULL) /* no first word yet */ + return rl_completion_matches(text, command_generator); + len -= (e - rl_line_buffer); + + idx = find_command(cmdtok, e - cmdtok); + if (idx < 0) + return NULL; + this_cmd = cmdtab[idx].type; + + if (! next_word(e, len, NULL)) { + switch (this_cmd) { + case D_break: + case D_list: + case D_until: + case D_tbreak: + case D_clear: + return rl_completion_matches(text, srcfile_generator); + case D_info: + case D_enable: + case D_trace: + case D_help: + return rl_completion_matches(text, argument_generator); + case D_option: + return rl_completion_matches(text, option_generator); + case D_print: + case D_printf: + case D_set: + case D_display: + case D_watch: + return rl_completion_matches(text, variable_generator); + default: + return NULL; + } + } + if (this_cmd == D_print || this_cmd == D_printf) + return rl_completion_matches(text, variable_generator); + return NULL; +} + +/* command_generator --- generator function for command completion */ + +static char * +command_generator(const char *text, int state) +{ + static size_t textlen; + static int idx = 0; + char *name; + + if (! state) { /* first time */ + textlen = strlen(text); + idx = 0; + } + while ((name = (char *) cmdtab[idx].name) != NULL) { + idx++; + if (strncmp(name, text, textlen) == 0) + return estrdup(name, strlen(name)); + } + return NULL; +} + +/* srcfile_generator --- generator function for source file completion */ + +static char * +srcfile_generator(const char *text, int state) +{ + static size_t textlen; + static SRCFILE *s; + char *name; + extern SRCFILE *srcfiles; + + if (! state) { /* first time */ + textlen = strlen(text); + s = srcfiles->next; + } + while (s != srcfiles) { + if (s->stype != SRC_FILE && s->stype != SRC_INC) { + s = s->next; + continue; + } + name = s->src; + s = s->next; + if (strncmp(name, text, textlen) == 0) + return estrdup(name, strlen(name)); + } + return NULL; +} + +/* argument_generator --- generator function for non-number argument completion */ + +static char * +argument_generator(const char *text, int state) +{ + static size_t textlen; + static int idx; + char *name; + + if (! state) { /* first time */ + textlen = strlen(text); + idx = 0; + } + + if (this_cmd == D_help) { + while ((name = (char *) cmdtab[idx++].name) != NULL) { + if (strncmp(name, text, textlen) == 0) + return estrdup(name, strlen(name)); + } + } else { + while ((name = (char *) argtab[idx].name) != NULL) { + if (this_cmd != argtab[idx++].cmd) + continue; + if (strncmp(name, text, textlen) == 0) + return estrdup(name, strlen(name)); + } + } + return NULL; +} + +/* variable_generator --- generator function for variable name completion */ + +static char * +variable_generator(const char *text, int state) +{ + static size_t textlen; + static int idx = 0; + static char **pnames = NULL; + static NODE **var_table = NULL; + char *name; + NODE *hp; + + if (! state) { /* first time */ + textlen = strlen(text); + if (var_table != NULL) + efree(var_table); + var_table = get_varlist(); + idx = 0; + pnames = get_parmlist(); /* names of function params in + * current context; the array + * is NULL terminated in + * awkgram.y (func_install). + */ + } + + /* function params */ + while (pnames != NULL) { + name = pnames[idx]; + if (name == NULL) { + pnames = NULL; /* don't try to match params again */ + idx = 0; + break; + } + idx++; + if (strncmp(name, text, textlen) == 0) + return estrdup(name, strlen(name)); + } + + /* globals */ + while ((hp = var_table[idx]) != NULL) { + idx++; + if (hp->hvalue->type == Node_func) + continue; + if (strncmp(hp->hname, text, textlen) == 0) + return estrdup(hp->hname, hp->hlength); + } + return NULL; +} + +/* history_expand_line --- history expand the LINE */ + +static void +history_expand_line(char **line) +{ + int ret; + char *expansion; + + if (! *line || input_fd != 0 || ! input_from_tty) + return; + using_history(); + ret = history_expand(*line, &expansion); + if (ret < 0 || ret == 2) + efree(expansion); + else { + efree(*line); + *line = expansion; + } +} + +#endif + |