From 7edbff3372dfd387e4f6da45f43ba8ada6dfbe43 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Wed, 23 May 2018 19:18:14 +0300 Subject: gdbmtool: accept commands from command line as well as from file Commands can be supplied to gdbmtool in three ways: 1. From a file, using the --file (-f) option: gdbmtool -f comfile 2. From the command line, if first argument is the database name: gdbmtool test.db count \; fetch mykey \; avail Note the use of semicolon to delimit commands. 3. From the interactive shell, if neither of the above is used. * src/Makefile.am: Add new sources. * src/gdbmtool.c: Use new stream functions for input. * src/gdbmtool.h (setsource): Remove proto. (instream_t): New data type. (instream_name, instream_read) (instream_close, instream_interactive) (instream_eq): New functions. (instream_stdin_create) (instream_argv_create) (instream_file_create) (interactive, input_context_push): New protos. * src/gram.y: Accept ; in place of newline. * src/input-argv.c: New file. * src/input-file.c: New file. * src/input-rl.c: Rewrite to provide instream_t API. * src/input-std.c: Likewise. * src/lex.l: Rewrite. * tests/.gitignore: Update. * tests/Makefile.am: Add new tests. Incorporate DejaGNU tests. * tests/config/default.exp: New file. * tests/gdbmtool/base.exp: New file. * tests/gdbmtool00.at: New file. * tests/gdbmtool01.at: New file. * tests/gdbmtool02.at: New file. * tests/testsuite.at: Include new tests. --- doc/gdbm.texi | 1 + src/Makefile.am | 4 +- src/gdbmtool.c | 99 ++++++++++----- src/gdbmtool.h | 57 ++++++++- src/gram.y | 28 +++-- src/input-argv.c | 106 ++++++++++++++++ src/input-file.c | 88 +++++++++++++ src/input-rl.c | 153 +++++++++++++---------- src/input-std.c | 45 +++++-- src/lex.l | 315 +++++++++++++++++++++++------------------------ tests/.gitignore | 3 +- tests/Makefile.am | 37 +++++- tests/config/default.exp | 221 +++++++++++++++++++++++++++++++++ tests/gdbmtool/base.exp | 9 ++ tests/gdbmtool00.at | 37 ++++++ tests/gdbmtool01.at | 37 ++++++ tests/gdbmtool02.at | 31 +++++ tests/testsuite.at | 5 + 18 files changed, 995 insertions(+), 281 deletions(-) create mode 100644 src/input-argv.c create mode 100644 src/input-file.c create mode 100644 tests/config/default.exp create mode 100644 tests/gdbmtool/base.exp create mode 100644 tests/gdbmtool00.at create mode 100644 tests/gdbmtool01.at create mode 100644 tests/gdbmtool02.at diff --git a/doc/gdbm.texi b/doc/gdbm.texi index 2aac1c3..0876f8d 100644 --- a/doc/gdbm.texi +++ b/doc/gdbm.texi @@ -11,6 +11,7 @@ * GDBM: (gdbm). The GNU database manager. * gdbm_dump: (gdbm) gdbm_dump. Dump the GDBM database into a flat file. * gdbm_load: (gdbm) gdbm_load. Load the database from a flat file. +* gdbmtool: (gdbm) gdbmtool. Examine and modify a GDBM database. @end direntry @end ifinfo diff --git a/src/Makefile.am b/src/Makefile.am index aff6982..766dba8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -96,6 +96,8 @@ gdbmtool_LDADD = \ gdbmtool_SOURCES = \ datconv.c\ gram.y\ + input-argv.c\ + input-file.c\ lex.l\ gdbmtool.h\ gdbmtool.c\ @@ -109,7 +111,7 @@ else endif AM_YFLAGS = -dtv -#AM_LFLAGS = -d +AM_LFLAGS = -d gdbm_load_LDADD = ./libgdbmapp.a ./libgdbm.la gdbm_dump_LDADD = ./libgdbmapp.a ./libgdbm.la diff --git a/src/gdbmtool.c b/src/gdbmtool.c index b322260..c522ad0 100644 --- a/src/gdbmtool.c +++ b/src/gdbmtool.c @@ -78,7 +78,7 @@ opendb (char *dbname) if (open_mode == GDBM_NEWDB) { - if (interactive && variable_is_true ("confirm") && + if (interactive () && variable_is_true ("confirm") && access (dbname, F_OK) == 0) { if (!getyn (_("database %s already exists; overwrite"), dbname)) @@ -1106,9 +1106,10 @@ void source_handler (struct handler_param *param) { char *fname = tildexpand (PARAM_STRING (param, 0)); - if (setsource (fname, 0) == 0) - yyparse (); + instream_t istr = instream_file_create (fname); free (fname); + if (input_context_push (istr) == 0) + yyparse (); } @@ -1456,7 +1457,7 @@ command_lookup (const char *str, struct locus *loc, struct command **pcmd) break; case fcom_found: - if (!interactive) + if (!interactive ()) { state = fcom_abort; found = NULL; @@ -1479,9 +1480,8 @@ command_lookup (const char *str, struct locus *loc, struct command **pcmd) } if (state == fcom_init) - lerror (loc, - interactive ? _("Invalid command. Try ? for help.") : - _("Unknown command")); + lerror (loc, interactive () ? _("Invalid command. Try ? for help.") : + _("Unknown command")); if (!found) return T_BOGUS; @@ -1490,7 +1490,12 @@ command_lookup (const char *str, struct locus *loc, struct command **pcmd) } char *parseopt_program_doc = N_("examine and/or modify a GDBM database"); -char *parseopt_program_args = N_("DBFILE"); +char *parseopt_program_args = N_("DBFILE [COMMAND [ARG ...]]"); + +enum { + OPT_LEX_TRACE = 256, + OPT_GRAM_TRACE +}; struct gdbm_option optab[] = { { 'b', "block-size", N_("SIZE"), N_("set block size") }, @@ -1504,6 +1509,8 @@ struct gdbm_option optab[] = { { 'r', "read-only", NULL, N_("open database in read-only mode") }, { 's', "synchronize", NULL, N_("synchronize to disk after each write") }, { 'q', "quiet", NULL, N_("don't print initial banner") }, + { OPT_LEX_TRACE, "lex-trace", NULL, N_("enable lexical analyzer traces") }, + { OPT_GRAM_TRACE, "gram-trace", NULL, N_("enable grammar traces") }, { 0 } }; @@ -1786,7 +1793,7 @@ static struct gdbmarglist last_args; void run_last_command (void) { - if (interactive) + if (interactive ()) { if (last_cmd) { @@ -1846,7 +1853,7 @@ run_command (struct command *cmd, struct gdbmarglist *arglist) /* Optional argument */ break; - if (!interactive) + if (!interactive ()) { terror (_("%s: not enough arguments"), cmd->name); return 1; @@ -1896,7 +1903,7 @@ run_command (struct command *cmd, struct gdbmarglist *arglist) pagfp = NULL; expected_lines = 0; - expected_lines_ptr = (interactive && pager) ? &expected_lines : NULL; + expected_lines_ptr = (interactive () && pager) ? &expected_lines : NULL; if (!(cmd->begin && cmd->begin (¶m, expected_lines_ptr))) { if (pager && expected_lines > get_screen_lines ()) @@ -1938,12 +1945,13 @@ run_command (struct command *cmd, struct gdbmarglist *arglist) } static void -source_rcfile () +source_rcfile (void) { + instream_t istr = NULL; + if (access (GDBMTOOLRC, R_OK) == 0) { - if (setsource (GDBMTOOLRC, 0) == 0) - yyparse (); + istr = instream_file_create (GDBMTOOLRC); } else { @@ -1962,11 +1970,17 @@ source_rcfile () fname = mkfilename (p, GDBMTOOLRC, NULL); if (access (fname, R_OK) == 0) { - if (setsource (fname, 0) == 0) - yyparse (); + istr = instream_file_create (GDBMTOOLRC); } free (fname); } + + if (istr) + { + if (input_context_push (istr)) + exit (EXIT_FATAL); + yyparse (); + } } #if GDBM_DEBUG_ENABLE @@ -1984,12 +1998,12 @@ debug_printer (char const *fmt, ...) int main (int argc, char *argv[]) { - int intr; int opt; int bv; int norc = 0; int res; - char *source = "-"; + char *source = NULL; + instream_t input = NULL; set_progname (argv[0]); #if GDBM_DEBUG_ENABLE @@ -2005,8 +2019,6 @@ main (int argc, char *argv[]) sort_commands (); /* Initialize variables. */ - intr = isatty (0); - interactive = intr; /* Used early by input_init */ dsdef[DS_KEY] = dsegm_new_field (datadef_lookup ("string"), NULL, 1); dsdef[DS_CONTENT] = dsegm_new_field (datadef_lookup ("string"), NULL, 1); @@ -2014,6 +2026,7 @@ main (int argc, char *argv[]) variable_set ("pager", VART_STRING, getenv ("PAGER")); input_init (); + lex_trace (0); for (opt = parseopt_first (argc, argv, optab); opt != EOF; @@ -2022,7 +2035,6 @@ main (int argc, char *argv[]) { case 'f': source = optarg; - intr = 0; break; case 'l': @@ -2068,6 +2080,14 @@ main (int argc, char *argv[]) bv = 1; variable_set ("quiet", VART_BOOL, &bv); break; + + case OPT_LEX_TRACE: + lex_trace (1); + break; + + case OPT_GRAM_TRACE: + gram_trace (1); + break; default: terror (_("unknown option; try `%s -h' for more info"), @@ -2078,14 +2098,30 @@ main (int argc, char *argv[]) argc -= optind; argv += optind; - if (argc > 1) + if (source && strcmp (source, "-")) { - terror (_("too many arguments")); - exit (EXIT_USAGE); + input = instream_file_create (source); + if (!input) + exit (1); + } + + if (argc >= 1) + { + file_name = estrdup (argv[0]); + argc--; + argv++; + if (argc) + { + if (input) + { + terror (_("--file and and command cannot be used together")); + exit (EXIT_USAGE); + } + input = instream_argv_create (argc, argv); + if (!input) + exit (1); + } } - - if (argc == 1) - file_name = estrdup (argv[0]); signal (SIGPIPE, SIG_IGN); @@ -2093,13 +2129,16 @@ main (int argc, char *argv[]) argmax = 0; if (!norc) - source_rcfile (); + source_rcfile (); + + if (!input) + input = instream_stdin_create (); /* Welcome message. */ - if (intr && !variable_is_true ("quiet")) + if (instream_interactive (input) && !variable_is_true ("quiet")) printf (_("\nWelcome to the gdbm tool. Type ? for help.\n\n")); - if (setsource (source, intr)) + if (input_context_push (input)) exit (EXIT_FATAL); res = yyparse (); input_done (); diff --git a/src/gdbmtool.h b/src/gdbmtool.h index 8a7725c..c472276 100644 --- a/src/gdbmtool.h +++ b/src/gdbmtool.h @@ -23,6 +23,7 @@ #include #include #include +#include #ifndef GDBM_ARG_UNUSED # define GDBM_ARG_UNUSED __attribute__ ((__unused__)) @@ -107,19 +108,67 @@ void terror (const char *fmt, ...) char *make_prompt (void); -int setsource (const char *filename, int intr); - extern char *file_name; -extern int interactive; extern int open_mode; #define GDBMTOOLRC ".gdbmtoolrc" #define GDBMTOOL_DEFFILE "junk.gdbm" -ssize_t input_read (FILE *fp, char *buf, size_t size); +typedef struct instream *instream_t; + +struct instream +{ + char *in_name; /* Input stream name */ + int in_inter; /* True if this is an interactive stream */ + ssize_t (*in_read) (instream_t, char*, size_t); + /* Read from stream */ + void (*in_close) (instream_t); + /* Close the stream */ + int (*in_eq) (instream_t, instream_t); + /* Return true if both streams refer to the + same input */ +}; + +static inline char const * +instream_name (instream_t in) +{ + return in->in_name; +} + +static inline ssize_t +instream_read (instream_t in, char *buf, size_t size) +{ + return in->in_read (in, buf, size); +} + +static inline void +instream_close (instream_t in) +{ + in->in_close (in); +} + +static inline int +instream_interactive (instream_t in) +{ + return in->in_inter; +} + +static inline int +instream_eq (instream_t a, instream_t b) +{ + return a->in_eq (a, b); +} + void input_init (void); void input_done (void); +instream_t instream_stdin_create (void); +instream_t instream_argv_create (int argc, char **argv); +instream_t instream_file_create (char const *name); + +int interactive (void); +int input_context_push (instream_t); + struct handler_param; void input_history_handler (struct handler_param *param); int input_history_begin (struct handler_param *param, size_t *exp_count); diff --git a/src/gram.y b/src/gram.y index 57d90c5..5372795 100644 --- a/src/gram.y +++ b/src/gram.y @@ -71,20 +71,20 @@ stmtlist : stmt | stmtlist stmt ; -stmt : /* empty */ '\n' +stmt : /* empty */ eol { run_last_command (); } - | T_CMD arglist '\n' + | T_CMD arglist eol { - if (run_command ($1, &$2) && !interactive) + if (run_command ($1, &$2) && !interactive ()) exit (EXIT_USAGE); } - | set '\n' - | defn '\n' - | T_BOGUS '\n' + | set eol + | defn eol + | T_BOGUS eol { - if (interactive) + if (interactive ()) { yyclearin; yyerrok; @@ -92,9 +92,9 @@ stmt : /* empty */ '\n' else YYERROR; } - | error { end_def(); } '\n' + | error { end_def(); } eol { - if (interactive) + if (interactive ()) { yyclearin; yyerrok; @@ -104,6 +104,10 @@ stmt : /* empty */ '\n' } ; +eol : '\n' + | ';' + ; + arglist : /* empty */ { gdbmarglist_init (&$$, NULL); @@ -367,3 +371,9 @@ yyerror (char const *s) terror ("%s", s); return 0; } + +void +gram_trace (int n) +{ + yydebug = 1; +} diff --git a/src/input-argv.c b/src/input-argv.c new file mode 100644 index 0000000..278398c --- /dev/null +++ b/src/input-argv.c @@ -0,0 +1,106 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2018 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see . */ + +#include "gdbmtool.h" +#include + +struct instream_argv +{ + struct instream base; /* Base structure */ + int argc; /* Number of arguments */ + char **argv; /* Vector of arguments */ + int idx; /* Index of the current argument */ + char *cur; /* Current position in argv[idx] */ + int delim; /* True if cur points to a delimiter */ +}; + +static ssize_t +instream_argv_read (instream_t istr, char *buf, size_t size) +{ + size_t len, total = 0; + struct instream_argv *i = (struct instream_argv*)istr; + + while (total < size) + { + if (*i->cur == 0) + { + if (i->idx == i->argc) + { + if (!i->delim) + { + i->cur = "\n"; + i->delim = 1; + } + else + break; + } + else if (!i->delim) + { + i->cur = " "; + i->delim = 1; + } + else + { + i->cur = i->argv[i->idx++]; + i->delim = 0; + } + } + + len = strlen (i->cur); + if (len > size - total) + len = size - total; + memcpy (buf + total, i->cur, len); + i->cur += len; + total += len; + } + return total; +} + +static void +instream_argv_close (instream_t istr) +{ + struct instream_argv *i = (struct instream_argv *)istr; + free (i); +} + +static int +instream_argv_eq (instream_t a, instream_t b) +{ + return 0; +} + +instream_t +instream_argv_create (int argc, char **argv) +{ + struct instream_argv *istr; + + istr = emalloc (sizeof *istr); + istr->base.in_name = "argv"; + istr->base.in_inter = 0; + istr->base.in_read = instream_argv_read; + istr->base.in_close = instream_argv_close; + istr->base.in_eq = instream_argv_eq; + + istr->argc = argc; + istr->argv = argv; + istr->idx = 0; + istr->cur = ""; + istr->delim = 1; + + return (instream_t) istr; +} + + diff --git a/src/input-file.c b/src/input-file.c new file mode 100644 index 0000000..5cbb030 --- /dev/null +++ b/src/input-file.c @@ -0,0 +1,88 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2018 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see . */ + +#include "gdbmtool.h" + +struct instream_file +{ + struct instream base; /* Base structure */ + FILE *fp; /* Opened file */ + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ +}; + +static ssize_t +instream_file_read (instream_t istr, char *buf, size_t size) +{ + struct instream_file *file = (struct instream_file *)istr; + return fread (buf, 1, size, file->fp); +} + +static void +instream_file_close (instream_t istr) +{ + struct instream_file *file = (struct instream_file *)istr; + fclose (file->fp); + free (file->base.in_name); + free (file); +} + +static int +instream_file_eq (instream_t a, instream_t b) +{ + struct instream_file *file_a = (struct instream_file *)a; + struct instream_file *file_b = (struct instream_file *)b; + return file_a->dev == file_b->dev && file_a->ino == file_b->ino; +} + +instream_t +instream_file_create (char const *name) +{ + struct instream_file *istr; + struct stat st; + FILE *fp; + + if (stat (name, &st)) + { + terror (_("cannot open `%s': %s"), name, strerror (errno)); + return NULL; + } + else if (!S_ISREG (st.st_mode)) + { + terror (_("%s is not a regular file"), name); + return NULL; + } + + fp = fopen (name, "r"); + if (!fp) + { + terror (_("cannot open %s for reading: %s"), name, + strerror (errno)); + return NULL; + } + + istr = emalloc (sizeof *istr); + istr->base.in_name = estrdup (name); + istr->base.in_inter = 0; + istr->base.in_read = instream_file_read; + istr->base.in_close = instream_file_close; + istr->base.in_eq = instream_file_eq; + istr->fp = fp; + istr->dev = st.st_dev; + istr->ino = st.st_ino; + + return (instream_t) istr; +} diff --git a/src/input-rl.c b/src/input-rl.c index d09feff..695d351 100644 --- a/src/input-rl.c +++ b/src/input-rl.c @@ -62,64 +62,6 @@ retrieve_history (char *str) return 0; } -ssize_t -input_read (FILE *fp, char *buf, size_t size) -{ - static char *input_line; - static size_t input_length; - static size_t input_off; -#define input_ptr() (input_line + input_off) -#define input_size() (input_length - input_off) - - if (interactive) - { - size_t len = input_size (); - if (!len) - { - if (input_line) - { - newline: - free (input_line); - input_line = NULL; - buf[0] = '\n'; - return 1; - } - else - { - char *prompt; - again: - prompt = make_prompt (); - input_line = readline (prompt); - free (prompt); - if (!input_line) - return 0; - input_length = strlen (input_line); - input_off = 0; - if (input_length) - { - if (retrieve_history (input_line)) - { - free (input_line); - goto again; - } - } - else - goto newline; - len = input_size (); - add_history (input_line); - } - } - - if (len > size) - len = size; - memcpy (buf, input_ptr (), len); - input_off += len; - - return len; - } - return fread (buf, 1, size, fp); -} - struct history_param { int from; @@ -180,7 +122,7 @@ input_history_handler (struct handler_param *param) #define HISTFILE_SUFFIX "_history" static char * -get_history_file_name () +get_history_file_name (void) { static char *filename = NULL; @@ -222,14 +164,99 @@ input_init (void) rl_readline_name = (char *) progname; rl_attempted_completion_function = shell_completion; rl_pre_input_hook = pre_input; - if (interactive) - read_history (get_history_file_name ()); + read_history (get_history_file_name ()); } void input_done (void) { - if (interactive) - write_history (get_history_file_name ()); + write_history (get_history_file_name ()); +} + +static void +instream_stdin_close (instream_t istr) +{ + free (istr); +} + +static ssize_t +stdin_read_readline (instream_t istr, char *buf, size_t size) +{ + static char *input_line; + static size_t input_length; + static size_t input_off; +#define input_ptr() (input_line + input_off) +#define input_size() (input_length - input_off) + size_t len = input_size (); + if (!len) + { + if (input_line) + { + newline: + free (input_line); + input_line = NULL; + buf[0] = '\n'; + return 1; + } + else + { + char *prompt; + again: + prompt = make_prompt (); + input_line = readline (prompt); + free (prompt); + if (!input_line) + return 0; + input_length = strlen (input_line); + input_off = 0; + if (input_length) + { + if (retrieve_history (input_line)) + { + free (input_line); + goto again; + } + } + else + goto newline; + len = input_size (); + add_history (input_line); + } + } + + if (len > size) + len = size; + memcpy (buf, input_ptr (), len); + input_off += len; + + return len; +} + +static ssize_t +instream_stdin_read (instream_t istr, char *buf, size_t size) +{ + if (istr->in_inter) + return stdin_read_readline (istr, buf, size); + return fread (buf, 1, size, stdin); +} + +static int +instream_stdin_eq (instream_t a, instream_t b) +{ + return 0; } +instream_t +instream_stdin_create (void) +{ + struct instream *istr; + + istr = emalloc (sizeof *istr); + istr->in_name = "stdin"; + istr->in_inter = isatty (fileno (stdin)); + istr->in_read = instream_stdin_read; + istr->in_close = instream_stdin_close; + istr->in_eq = instream_stdin_eq; + + return istr; +} diff --git a/src/input-std.c b/src/input-std.c index e4f883d..45ae506 100644 --- a/src/input-std.c +++ b/src/input-std.c @@ -16,17 +16,42 @@ #include "gdbmtool.h" -ssize_t -input_read (FILE *fp, char *buf, size_t size) +static ssize_t +instream_stdin_read (instream_t istr, char *buf, size_t size) { - if (interactive) - { - print_prompt_at_bol (); - if (fgets (buf, size, fp) == NULL) - return 0; - return strlen (buf); - } - return fread (buf, 1, size, fp); + if (istr->in_inter) + print_prompt_at_bol (); + if (fgets (buf, size, stdin) == NULL) + return 0; + return strlen (buf); +} + +static void +instream_stdin_close (instream_t istr) +{ + free (istr); +} + +static int +instream_stdin_eq (instream_t a, instream_t b) +{ + return 0; +} + +instream_t +instream_stdin_create (void) +{ + struct instream_file *istr; + + istr = emalloc (sizeof *istr); + istr->base.in_name = "stdin"; + istr->base.in_inter = isatty (fileno (stdin)); + istr->base.in_read = instream_stdin_read; + istr->base.in_close = instream_stdin_close; + istr->base.in_eq = instream_stdin_eq; + istr->fp = stdin; + + return istr; } void diff --git a/src/lex.l b/src/lex.l index a2a8ee8..0318031 100644 --- a/src/lex.l +++ b/src/lex.l @@ -19,14 +19,23 @@ #include "gdbmtool.h" #include "gram.h" -struct point point; - +struct context /* Input context */ +{ + struct context *parent; /* Pointer to the parent context */ + struct locus locus; /* Locus */ + struct point point; + YY_BUFFER_STATE buf; /* Buffer */ + instream_t input; +}; + +static struct context *context_tos; + /* Advance locus to the next line */ void -advance_line () +advance_line (void) { - ++point.line; - point.col = 0; + ++context_tos->point.line; + context_tos->point.col = 0; } #define YY_USER_ACTION \ @@ -34,11 +43,11 @@ advance_line () { \ if (YYSTATE == 0) \ { \ - yylloc.beg = point; \ + yylloc.beg = context_tos->point; \ yylloc.beg.col++; \ } \ - point.col += yyleng; \ - yylloc.end = point; \ + context_tos->point.col += yyleng; \ + yylloc.end = context_tos->point; \ } \ while (0); @@ -46,7 +55,10 @@ advance_line () #define YY_INPUT(buf,result,max_size) \ do \ { \ - result = input_read (yyin, buf, max_size); \ + if (context_tos) \ + result = instream_read (context_tos->input, buf, max_size); \ + else \ + result = 0; \ } \ while (0); @@ -56,146 +68,112 @@ void string_addc (int c); char *string_end (void); int unescape (int c); -struct context /* Input context */ +int +interactive (void) { - struct context *parent; /* Pointer to the parent context */ - struct locus locus; /* Locus */ - struct point point; - int interactive; - ino_t ino; /* Inode number */ - dev_t dev; /* Device number */ - FILE *file; /* Input file */ - YY_BUFFER_STATE buf; /* Buffer */ -}; + return context_tos && instream_interactive (context_tos->input); +} + +static struct context * +input_context_lookup (instream_t istr) +{ + struct context *cp; -static struct context *context_tos; -static ino_t ino; -static dev_t dev; -int interactive; /* Are we running in interactive mode? */ -static int initialized; + for (cp = context_tos; cp; cp = cp->parent) + if (instream_eq (cp->input, istr)) + break; + return cp; +} -static void -context_push () +int +input_context_push (instream_t input) { - struct context *cp = ecalloc (1, sizeof (*cp)); + struct context *cp; + + cp = input_context_lookup (input); + if (cp) + { + terror (_("recursive sourcing")); + if (cp->parent) + lerror (&cp->locus, _("%s already sourced here"), + instream_name (input)); + return 1; + } + yy_switch_to_buffer (yy_create_buffer (NULL, YY_BUF_SIZE)); + + /* Create new context */ + + cp = ecalloc (1, sizeof (*cp)); cp->locus = yylloc; - cp->point = point; - cp->interactive = interactive; - cp->ino = ino; - cp->dev = dev; - cp->file = yyin; + cp->point.file = estrdup (instream_name (input)); + cp->point.line = 1; + cp->point.col = 0; + + cp->input = input; cp->buf = YY_CURRENT_BUFFER; cp->parent = context_tos; context_tos = cp; + + return 0; } +void +lex_trace (int n) +{ + yy_flex_debug = n; +} + int -context_pop () +input_context_pop (void) { - struct context *cp = context_tos; + struct context *cp; - fclose (yyin); - yyin = NULL; - free (point.file); - point.file = NULL; + if (!context_tos) + return 1; + instream_close (context_tos->input); + free (context_tos->point.file); memset (&yylloc, 0, sizeof (yylloc)); - + cp = context_tos->parent; + free (context_tos); + context_tos = cp; if (!cp) return 1; - context_tos = cp->parent; - yylloc = cp->locus; - point = cp->point; - interactive = cp->interactive; - ino = cp->ino; - dev = cp->dev; - yyin = cp->file; yy_delete_buffer (YY_CURRENT_BUFFER); yy_switch_to_buffer (cp->buf); return 0; } -static struct context * -findctx (struct stat *st) -{ - struct context *cp; - - for (cp = context_tos; cp; cp = cp->parent) - if (cp->dev == st->st_dev && cp->ino == st->st_ino) - break; - return cp; -} - -int -setsource (const char *name, int intr) +static int +t_num (int base) { - struct stat st; - struct context *cp; - FILE *fp; - - if (strcmp (name, "-") == 0) + long n; + errno = 0; + n = strtol (yytext, NULL, base); + if (errno) { - fp = stdin; - name = "stdin"; + lerror (&yylloc, "%s", strerror (errno)); + return T_BOGUS; } - else + if (n < INT_MIN || n > INT_MAX) { - if (stat (name, &st)) - { - terror (_("cannot open `%s': %s"), name, strerror (errno)); - return -1; - } - else if (!S_ISREG (st.st_mode)) - { - terror (_("%s is not a regular file"), name); - return -1; - } - - cp = findctx (&st); - if (cp) - { - terror (_("recursive sourcing")); - if (cp->parent) - lerror (&cp->locus, _("%s already sourced here"), name); - return 1; - } - - fp = fopen (name, "r"); - if (!fp) - { - terror (_("cannot open %s for reading: %s"), name, - strerror (errno)); - return 1; - } + lerror (&yylloc, "value out of range"); + return T_BOGUS; } - - if (yyin) - context_push (); - - yyin = fp; - yy_switch_to_buffer (yy_create_buffer (yyin, YY_BUF_SIZE)); - - interactive = intr; - dev = st.st_dev; - ino = st.st_ino; - - point.file = estrdup (name); - point.line = 1; - point.col = 0; - - initialized = 1; - - return 0; + yylval.num = n; + return T_NUM; } + %} %option noinput %option nounput +%option nodefault -%x STR MLSTR DEF +%x CMD STR MLSTR DEF WS [ \t][ \t]* IDENT [a-zA-Z_][a-zA-Z_0-9-]* @@ -238,67 +216,82 @@ O [0-7] REJECT; } if (file) - point.file = file; - point.line = line; - point.col = 0; + context_tos->point.file = file; + context_tos->point.line = line; + context_tos->point.col = 0; } #.*\n advance_line (); #.* /* end-of-file comment */; -off { return T_OFF; } -pad { return T_PAD; } -0[xX]{X}{X}* { yylval.num = strtoul (yytext, NULL, 16); - return T_NUM; }; -0{O}{O}* { yylval.num = strtoul (yytext, NULL, 8); - return T_NUM; }; -0|{P} { yylval.num = strtoul (yytext, NULL, 10); - return T_NUM; }; -^[ \t]*\? { return command_lookup ("help", &yylloc, &yylval.cmd); } -^[ \t]*{IDENT} { char *p = yytext + strspn (yytext, " \t"); - return command_lookup (p, &yylloc, &yylval.cmd); - } -{IDENT} { if ((yylval.type = datadef_lookup (yytext))) - return T_TYPE; - else - { - yylval.string = estrdup (yytext); - return T_IDENT; - } - } -{IDENT} { yylval.string = estrdup (yytext); - return T_IDENT; - } -[^ \"\t\n\[\]{},=]+ { yylval.string = estrdup (yytext); - return T_WORD; } -\"[^\\\"\n]*\" { yylval.string = emalloc (yyleng - 1); +{ +\? { BEGIN (CMD); + return command_lookup ("help", &yylloc, &yylval.cmd); } +{IDENT} { BEGIN (CMD); + return command_lookup (yytext, &yylloc, &yylval.cmd); } +{WS} ; +} + +{ +off { return T_OFF; } +pad { return T_PAD; } +0[xX]{X}{X}* { return t_num (8); }; +0{O}{O}* { return t_num (16); }; +0|{P} { return t_num (10); }; +{IDENT} { if ((yylval.type = datadef_lookup (yytext))) + return T_TYPE; + else + { + yylval.string = estrdup (yytext); + return T_IDENT; + } + } +[^ \"\t\n;\[\]{},=]+ { yylval.string = estrdup (yytext); return T_WORD; } +\n { advance_line (); } +{WS} ; +. return yytext[0]; +} + +{ +{IDENT} { yylval.string = estrdup (yytext); return T_IDENT; } +[^ \"\t\n;\[\]{},=]+ { yylval.string = estrdup (yytext); return T_WORD; } +\"[^\\\"\n]*\" { + yylval.string = emalloc (yyleng - 1); memcpy (yylval.string, yytext+1, yyleng-2); yylval.string[yyleng-2] = 0; return T_WORD; } -\"[^\\\"\n]*\\$ { string_begin (); +\"[^\\\"\n]*\\$ { + string_begin (); string_add (yytext + 1, yyleng - 2); BEGIN (MLSTR); } -\"[^\\\"\n]*\\. { string_begin (); +\"[^\\\"\n]*\\. { + string_begin (); string_add (yytext + 1, yyleng - 3); string_addc (unescape (yytext[yyleng-1])); BEGIN (STR); } -[^\\\"\n]*\" { if (yyleng > 1) - string_add (yytext, yyleng - 1); - yylval.string = string_end (); - BEGIN (INITIAL); - return T_WORD; } -[^\\\"\n]*\\$ { string_add (yytext, yyleng - 1); } -[^\\\"\n]*\\. { string_add (yytext, yyleng - 2); - string_addc (unescape (yytext[yyleng-1])); } -{WS} ; -\n { advance_line (); } -\n { advance_line (); return '\n'; } -. return yytext[0]; +; { BEGIN (INITIAL); return ';'; } +{WS} ; +} + +{ +[^\\\"\n]*\" { if (yyleng > 1) + string_add (yytext, yyleng - 1); + yylval.string = string_end (); + BEGIN (CMD); + return T_WORD; } +[^\\\"\n]*\\$ { string_add (yytext, yyleng - 1); } +[^\\\"\n]*\\. { string_add (yytext, yyleng - 2); + string_addc (unescape (yytext[yyleng-1])); } +} + +<*>\n { BEGIN (INITIAL); advance_line (); return '\n'; } + +. return yytext[0]; %% int -yywrap () +yywrap (void) { - return context_pop (); + return input_context_pop (); } void @@ -310,7 +303,7 @@ begin_def (void) void end_def (void) { - BEGIN (INITIAL); + BEGIN (CMD); } void @@ -427,9 +420,9 @@ escape (int c) void vlerror (struct locus *loc, const char *fmt, va_list ap) { - if (!interactive) + if (!interactive ()) fprintf (stderr, "%s: ", progname); - if (initialized && loc && loc->beg.file) + if (loc && loc->beg.file) { YY_LOCATION_PRINT (stderr, *loc); fprintf (stderr, ": "); diff --git a/tests/.gitignore b/tests/.gitignore index a339f8d..7aef06b 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -24,4 +24,5 @@ num2word package.m4 testsuite testsuite.dir -testsuite.log +*.log +*.sum diff --git a/tests/Makefile.am b/tests/Makefile.am index b85110d..409ce0b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,7 +22,6 @@ EXTRA_DIST = \ DISTCLEANFILES = atconfig MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE) - ## ------------ ## ## package.m4. ## ## ------------ ## @@ -73,6 +72,9 @@ TESTSUITE_AT = \ delete00.at\ delete01.at\ delete02.at\ + gdbmtool00.at\ + gdbmtool01.at\ + gdbmtool02.at\ fetch00.at\ fetch01.at\ setopt00.at\ @@ -135,4 +137,35 @@ dtfetch_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la dtdel_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la d_creat_ce_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la - +## -------------------- ## +## DejaGNU Test suite. ## +## -------------------- ## +AUTOMAKE_OPTIONS = dejagnu +DEJATOOL = gdbmtool +RUNTESTFLAGS = +CLEANFILES = *.log + +site.exp: Makefile + @echo 'Making a new site.exp file...' + @test ! -f site.bak || rm -f site.bak + @echo '## these variables are automatically generated by make ##' > $@-t + @echo '# Do not edit here. If you wish to override these values' >> $@-t + @echo '# edit the last section' >> $@-t + @echo 'set tool $(DEJATOOL)' >> $@-t + @echo "set top_srcdir `cd $(top_srcdir); pwd`" >> $@-t + @echo "set top_builddir `cd $(top_builddir); pwd`" >> $@-t + @echo "set srcdir `cd $(srcdir); pwd`" >> $@-t + @echo 'set objdir' `pwd` >> $@-t + @echo 'set host_alias "$(host_alias)"' >> $@-t + @echo 'set host_triplet $(host_triplet)' >> $@-t + @echo 'set target_alias "$(target_alias)"' >> $@-t + @echo 'set target_triplet $(target_triplet)' >> $@-t + @echo 'set build_alias "$(build_alias)"' >> $@-t + @echo 'set build_triplet $(build_triplet)' >> $@-t + @echo '## All variables above are generated by configure. Do Not Edit ##' >> $@-t + @test ! -f site.exp || sed '1,/^## All variables above are.*##/ d' site.exp >> $@-t + @test ! -f site.exp || mv site.exp site.bak + @mv $@-t site.exp + +EXTRA_DIST += \ + gdbmtool/base.exp diff --git a/tests/config/default.exp b/tests/config/default.exp new file mode 100644 index 0000000..7802ea6 --- /dev/null +++ b/tests/config/default.exp @@ -0,0 +1,221 @@ +verbose "STARTED" 1 + +# Make sure we use POSIX locale +set env(LC_ALL) "C" + +set gdbmtool_prompt "gdbmtool> " + +proc gdbmtool_start {args} { + global gdbmtool_spawn_id + global top_builddir + global gdbmtool_prompt + + set cmd "$top_builddir/src/gdbmtool -q $args" + verbose "running $cmd" 1 + + set gdbmtool_spawn_id [remote_spawn host $cmd] + if { $gdbmtool_spawn_id < 0 || $gdbmtool_spawn_id == "" } { + perror "Spawning $cmd failed." + return 1; + } + + remote_expect host 60 { + -re "\[\r\n\]?${gdbmtool_prompt}$" { + verbose "gdbmtool initialized." + } + default { + perror "gdbmtool not initialized" + return 1 + } + } + return 0 +} + +proc gdbmtool_stop {} { + verbose "Stopping gdbmtool" + gdbmtool_command "quit" + remote_close host +} + +proc gdbmtool_send {string} { + return [remote_send host "$string"] +} + +proc gdbmtool_command {cmd} { + set res [gdbmtool_send "$cmd\n"] + remote_expect host 60 { + -ex "\r\n" { } + default { + perror "gdbmtool_command for target failed"; + return -1 + } + } + verbose "RESULT: $res" 2 + return $res +} + +proc gdbmtool_test { command expcode } { + gdbmtool_command $command + uplevel remote_expect host 60 $expcode +# set code [catch \ +# {uplevel remote_expect host 60 $expcode} string]; +} + +# mu_test COMMAND PATTERN +# COMMAND - Command to send to the program +# PATTERN - A list of strings to expect in return +# Return value: +# -3 - eof +# -2 - timeout +# -1 - generic failure +# 1 - test fails +# 0 - test succeeds +proc seq_test { args } { + set command [lindex $args 0] + set pattern [lindex $args 1] + + set result -1 + if { "${command}" != "" } { + set res [gdbmtool_command "${command}"] + if { $res != "" } { + return $result; + } + } + + global timeout; + if [info exists timeout] { + set tmt $timeout; + } else { + set tmt 60; + } + + set result 0 + for {set i 0} {$result == 0 && $i < [llength $pattern]} {incr i} { + set regexp 0 + switch -regexp -- "[lindex ${pattern} $i]" { + ^-re.*$ { set regexp 1; incr i } + ^-- { incr i } + } + + regsub "\[ \t\]*$" [lindex ${pattern} $i] "" pat + verbose "i=$i, pat=$pat" 2 + + if {$regexp} { + verbose "REGEX for $pat / [llength $pat] " 3 + remote_expect host $tmt { + -re "$pat\[ \r\t\]*\r\n" { } + default { + set result 1 + break + } + timeout { + set result -2 + break + } + eof { + set result -3 + break + } + } + } else { + remote_expect host $tmt { + -ex "$pat" { + # go on + } + default { + set result 1 + break + } + timeout { + set result -2 + break + } + eof { + set result -3 + break + } + } + + if {$result == 0} { + remote_expect host $tmt { + -re "\[ \t]*\r\n" { } + default { set result 1 } + timeout { set result -2 } + eof { set result -3 } + } + } + } + } + return $result +} + +# mail_test [-message MESSAGE][-default (FAIL|XFAIL)][-noprompt] +# COMMAND PATTERN [PATTERN...] +# COMMAND - Command to send to mail. +# PATTERN - Sequence to expect in return. +# MESSAGE - [optional] message to output +proc gdbmtool_test { args } { + global gdbmtool_prompt + global suppress_flag; + upvar timeout timeout + + set default "" + set message "" + set wait_for_prompt 1 + for {set i 0} {$i < [llength $args]} {incr i} { + set a [lindex $args $i] + if {"$a" == "-default"} { + set default [lindex $args [expr $i + 1]] + incr i + } elseif {"$a" == "-message"} { + set message [lindex $args [expr $i + 1]] + incr i + } elseif {"$a" == "-noprompt"} { + set wait_for_prompt 0 + } else { + set args [lrange $args $i end] + break + } + } + + if {"$message" == ""} { + set message [lindex $args 0] + } + + verbose "Message is \"$message\"" 2 + + set command [lindex $args 0] + set pattern [lrange $args 1 end] + + set result [seq_test $command $pattern] + if {$wait_for_prompt} { + remote_expect host 30 { + -re "\[\r\n\]?${gdbmtool_prompt}$" {} + default { + perror "gdbmtool not initialized" + return 1 + } + } + } + + if {$result == 0} { + pass "$message" + } elseif {$result == 1} { + if { "$default" == "" || "$default" != "FAIL" } { + fail "$message" + } else { + xfail "$message" + set result 0 + } + } elseif {$result == -2} { + fail "$message (timeout)" + } elseif {$result == -3} { + fail "$message (eof)" + } else { + fail "$message" + } + return $result +} + + + diff --git a/tests/gdbmtool/base.exp b/tests/gdbmtool/base.exp new file mode 100644 index 0000000..6a831ac --- /dev/null +++ b/tests/gdbmtool/base.exp @@ -0,0 +1,9 @@ +gdbmtool_start +gdbmtool_test "status" \ + "No database name" \ + "Database is not open" \ + "define key string" \ + "define content string" +gdbmtool_test "version" \ + -re "^GDBM version .*" +gdbmtool_stop diff --git a/tests/gdbmtool00.at b/tests/gdbmtool00.at new file mode 100644 index 0000000..baec9a4 --- /dev/null +++ b/tests/gdbmtool00.at @@ -0,0 +1,37 @@ +# This file is part of GDBM. -*- autoconf -*- +# Copyright (C) 2018 Free Software Foundation, Inc. +# +# GDBM 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, or (at your option) +# any later version. +# +# GDBM 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 GDBM. If not, see . */ + +AT_SETUP([Input from file]) +AT_KEYWORDS([gdbmtool]) + +AT_CHECK([ + +AT_DATA([input],[ +status +version +quit +]) + +gdbmtool -f input | sed 's/^GDBM version.*/GDBM version/' +], +[0], +[No database name +Database is not open +define key string +define content string +GDBM version +]) +AT_CLEANUP diff --git a/tests/gdbmtool01.at b/tests/gdbmtool01.at new file mode 100644 index 0000000..91203f6 --- /dev/null +++ b/tests/gdbmtool01.at @@ -0,0 +1,37 @@ +# This file is part of GDBM. -*- autoconf -*- +# Copyright (C) 2018 Free Software Foundation, Inc. +# +# GDBM 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, or (at your option) +# any later version. +# +# GDBM 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 GDBM. If not, see . */ + +AT_SETUP([Input from stdin]) +AT_KEYWORDS([gdbmtool]) + +AT_CHECK([ + +AT_DATA([input],[ +status +version +quit +]) + +gdbmtool < input | sed 's/^GDBM version.*/GDBM version/' +], +[0], +[No database name +Database is not open +define key string +define content string +GDBM version +]) +AT_CLEANUP diff --git a/tests/gdbmtool02.at b/tests/gdbmtool02.at new file mode 100644 index 0000000..0c2d35f --- /dev/null +++ b/tests/gdbmtool02.at @@ -0,0 +1,31 @@ +# This file is part of GDBM. -*- autoconf -*- +# Copyright (C) 2018 Free Software Foundation, Inc. +# +# GDBM 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, or (at your option) +# any later version. +# +# GDBM 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 GDBM. If not, see . */ + +AT_SETUP([Input from command line]) +AT_KEYWORDS([gdbmtool]) + +AT_CHECK([ +gtload t.db < /dev/null +gdbmtool t.db status \; version | sed 's/^GDBM version.*/GDBM version/' +], +[0], +[Database file: t.db +Database is not open +define key string +define content string +GDBM version +]) +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 9488245..176a2d6 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -86,4 +86,9 @@ m4_include([dump02.at]) m4_include([dump03.at]) m4_include([dump04.at]) +AT_BANNER([gdbmtool]) +m4_include([gdbmtool00.at]) +m4_include([gdbmtool01.at]) +m4_include([gdbmtool02.at]) + # End of testsuite.at -- cgit v1.2.1