]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Scripting and .parameter enhancements most in; all in working
authorlarrybr <larrybr@noemail.net>
Thu, 3 Feb 2022 20:57:47 +0000 (20:57 +0000)
committerlarrybr <larrybr@noemail.net>
Thu, 3 Feb 2022 20:57:47 +0000 (20:57 +0000)
FossilOrigin-Name: a1581118b07b111edbad8dc930a959ec0b98461b2737619d9976ef4a498b6571

1  2 
manifest
manifest.uuid
src/shell.c.in
test/shell3.test
test/shell4.test

diff --cc manifest
index 0f7e2c9f825299d0ed4e1e7b7b791c49a63185ec,c4baec7ba381131f0c60c6eaaba4eff903bff49e..e7750ac755c3f91534b7077a2e2bcc1d86cee7c0
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Take\sCLI's\swordwrap\sfrom\strunk
- D 2022-02-01T15:08:10.395
 -C A\sWIP\scheckin,\sprogress\stoward\swhat\s.help\spromises
 -D 2022-01-20T05:20:27.018
++C Scripting\sand\s.parameter\senhancements\smost\sin;\sall\sin\sworking
++D 2022-02-03T20:57:47.860
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -545,19 -544,19 +545,19 @@@ F src/parse.y 04f61db1cdd7036c6d74baad1
  F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b
  F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
  F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65
 -F src/pragma.c c536665ce8431c8b1efbf7e0a5c01852f49f7bf28f1954f8118b2d28e4a3797f
 +F src/pragma.c 7c024d690a3dc93f61830f11f900e4af2357f31d081b0c79099ca5e28919cba7
  F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad
 -F src/prepare.c 45fe7408eb78d80eca8392669070a6e5caf231b09e5c7b1ff65c1ad64a3734c5
 +F src/prepare.c a187dade741c1f09ae118fcbbf0302511807bfc0355880927d7152eb75b8260d
  F src/printf.c 975f1f5417f2526365b6e6d7f22332e3e11806dad844701d92846292b654ba9a
  F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 -F src/resolve.c 359bc0e445d427583d2ab6110433a5dc777f64a0ecdf8d24826d8b475233ead9
 +F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 -F src/select.c a4a23a70f0a24a1103ac9698f6be181a6ec7ff6c19e03e8899c43cb6d2af09d6
 -F src/shell.c.in 732a2ee32b11825b797754bacd77cb4b09ff813c2f77786fc5d9f3a803fa4997
 -F src/sqlite.h.in a5e0d6bd47e67aabf1475986d36bdcc7bfa9e06566790ebf8e3aa7fa551c9f99
 +F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2
- F src/shell.c.in 285e00069c9c1d0c55d70489cb13e7a75c08d44761e1015f7839f945a6a81cbd
++F src/shell.c.in af7f6c1140ad80505cdc7c5ba75399fae75d2512ee0202f1b5666805eb955929
 +F src/sqlite.h.in eaade58049152dac850d57415bcced885ca27ae9582f8aea2cfb7f1db78a521b
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 -F src/sqlite3ext.h 01eb85e4f2759a5ee79c183f4b2877889d4ffdc49d27ae74529c9579e3c8c0ef
 -F src/sqliteInt.h 21a31abf60222f50c1d654cdc27ad9d4040249f0341129dd8286b8b5b32bcd30
 +F src/sqlite3ext.h 5d54cf13d3406d8eb65d921a0d3c349de6126b732e695e79ecd4830ce86b4f8a
 +F src/sqliteInt.h 8ef2996e02476f73e41ba977f819bda0cc68b7ce238cf404b9b8930df57bc1d0
  F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
  F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
  F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@@ -1386,10 -1384,10 +1386,10 @@@ F test/sharedA.test 49d87ec54ab640fbbc3
  F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
  F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
  F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
 -F test/shell1.test 70f46b5d07776a107335c3c2c9cbd0431d44637bfeae1f6b9ded5e33b4c7c0bf
 +F test/shell1.test ce2f370886645f38fabdde44976c14a004400f166edea8fdd9741079b645fef6
  F test/shell2.test f00a0501c00583cbc46f7510e1d713366326b2b3e63d06d15937284171a8787c
- F test/shell3.test 3fed756f9e254d638b012fc018f5125e2f15bf79a824a4f474405b3aad9f0fb8
- F test/shell4.test 823b84d39d4dc7a78b7570342b7e43dc32805fa8ee92e5b40a89775c3170dac1
 -F test/shell3.test cb4b835a901742c9719437a89171172ecc4a8823ad97349af8e4e841e6f82566
 -F test/shell4.test 3ed6c4b42fd695efcbc25d69ef759dbb15855ca8e52ba6c5ee076f8b435f48be
++F test/shell3.test c073c2adda6d1aabe4450024ade4d45607f34d2fb76c2551cc89e70871191a3c
++F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115
  F test/shell5.test b85069bfcf3159b225228629ab2c3e69aa923d098fea8ea074b5dcd743522e2c
  F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
  F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
@@@ -1942,8 -1938,8 +1942,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P f51a17b6271a8dd7c48725e4ec2df1fde0460866c81c7225dc27216ab389591e 00b1b7020a564976da3237532434e47ccf17eb5d620e6ac45f3e70b5d5739200
- R de0a9f4bae0a768836bea9c7e678066d
 -P a94ab403eb836d3fcb9710d22da5129f58db05d3be145bc77ce1c017761e7894
 -R df6ba47c6584db70fd023a69e282f313
++P 768c70a926bd3d5338c4d7c07689e61f03973b4bc7d9d2c62c6f83a6c2b85829 4688e6dff88527dbff436b811512206d31a9695095776c4e1fdff95da0b0c4d4
++R b8ba5718d1d75fb62d099309ab94cde4
  U larrybr
- Z 216ac63c3bf7c9a422cebb513acf55e0
 -Z 2d0ff93e3043918f6083e9631f60e14c
++Z 6bf0561e73a5d6ab55c67a719c76d473
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 741bc1c157e142c2b9705b088d9706e7b1f38b57,a2de2b286902527a441fae7b171d918a1d797cb4..1ff7058c70e88d6af342029963319dd1b8443b8b
@@@ -1,1 -1,1 +1,1 @@@
- 768c70a926bd3d5338c4d7c07689e61f03973b4bc7d9d2c62c6f83a6c2b85829
 -4688e6dff88527dbff436b811512206d31a9695095776c4e1fdff95da0b0c4d4
++a1581118b07b111edbad8dc930a959ec0b98461b2737619d9976ef4a498b6571
diff --cc src/shell.c.in
index 0e403b433ab01fb28d98156e219eb5897c917224,712ffe47343fd236781fc06141fc57d5b54ed425..17685a1ccded20b1cf21b4b656a0ecbe9a0f7aba
  #define _CRT_SECURE_NO_WARNINGS
  #endif
  
++/* Define stringification of a bare word (or anything without commas.) */
++# define SHELL_STRINGIFY_(f) #f
++# define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f)
++
  /*
  ** Optionally #include a user-defined header, whereby compilation options
  ** may be set prior to where they take effect, but after platform setup. 
@@@ -24,9 -24,9 +28,7 @@@
  ** file. Note that this macro has a like effect on sqlite3.c compilation.
  */
  #ifdef SQLITE_CUSTOM_INCLUDE
--# define INC_STRINGIFY_(f) #f
--# define INC_STRINGIFY(f) INC_STRINGIFY_(f)
--# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE)
++# include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE)
  #endif
  
  /*
@@@ -403,7 -403,7 +405,7 @@@ static void endTimer(void)
  static int bail_on_error = 0;
  
  /*
--** Threat stdin as an interactive input if the following variable
++** Treat stdin as an interactive input if the following variable
  ** is true.  Otherwise, assume stdin is connected to a file or pipe.
  */
  static int stdin_is_interactive = 1;
@@@ -624,17 -624,15 +626,78 @@@ static FILE * openChrSource(const char 
  }
  
  /*
--** This routine reads a line of text from FILE in, stores
--** the text in memory obtained from malloc() and returns a pointer
--** to the text.  NULL is returned at end of file, or if malloc()
--** fails.
++** Arrange for shell input from either a FILE or a string.
++** For applicable invariants, see str_line_get(...) which is
++** the only modifier of this struct. (3rd and 4th members)
++** All other members are simply initialized to select the
++** input stream and track input sources, set by whatever
++** routines redirect the input.
++*/
++typedef struct InSource {
++  FILE *inFile;     /* Will be 0 when input is to be taken from string.  */
++  char *zStrIn;     /* Will be 0 when no input is available from string. */
++  int iReadOffset;  /* Offset into zStrIn where next "read" to be done.  */
++  int lineno;       /* How many lines have been read from this source    */
++  const char *zSourceSay; /* For complaints, keep a name for this source */
++  struct InSource *pFrom; /* and redirect tracking to aid unraveling.    */
++} InSource;
++#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom}
++#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom}
++#define INSOURCE_IS_INTERACTIVE(pIS) \
++  ((pIS)==&termInSource && stdin_is_interactive )
++
++/* This instance's address is taken as part of interactive input test. */
++static InSource termInSource = { 0 /*stdin*/, 0, 0, 0, "<terminal>", 0};
++static InSource stdInSource = { 0 /*stdin*/, 0, 0, 0, "<stdin>", 0};
++static void init_std_inputs( FILE *pIn ){
++  termInSource.inFile = pIn;
++  stdInSource.inFile = pIn;
++}
++
++static char *str_line_get( char *zBuf, int ncMax, InSource *pInSrc){
++  if( pInSrc->inFile!=0 ){
++    char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
++    if( zRet!=0 ){
++      int iRead = strlen30(zRet);
++      if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
++      /* Consider: record line length to avoid rescan for it. */
++      return zRet;
++    }
++  }
++  else if( pInSrc->zStrIn!=0 ){
++    char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
++    if( *zBegin!=0 ){
++      int iTake = 0;
++      char c;
++      ncMax -= 1;
++      while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
++        zBuf[iTake++] = c;
++        if( c=='\n' ){
++          ++pInSrc->lineno;
++          break;
++        }
++      }
++      if( ncMax>=0 ) zBuf[iTake] = 0;
++      pInSrc->iReadOffset += iTake;
++      /* Consider: record line length to avoid rescan for it. */
++      return zBuf;
++    }
++  }
++  return 0;
++}
++
++/*
++** This routine reads a line of text from designated stream source,
++** stores the text in memory obtained from malloc() and returns a
++** pointer to the text.  NULL is returned at end of file.
++** There will be no return if malloc() fails.
 +**
 +** The trailing newline (or other line-end chars) are stripped.
  **
  ** If zLine is not NULL then it is a malloced buffer returned from
  ** a previous call to this routine that may be reused.
  */
--static char *local_getline(char *zLine, FILE *in){
++static char *local_getline(char *zLine, InSource *pInSrc){
    int nLine = zLine==0 ? 0 : 100;
    int n = 0;
  
        zLine = realloc(zLine, nLine);
        shell_check_oom(zLine);
      }
--    if( fgets(&zLine[n], nLine - n, in)==0 ){
++    if( str_line_get(&zLine[n], nLine - n, pInSrc)==0 ){
        if( n==0 ){
          free(zLine);
          return 0;
  #if defined(_WIN32) || defined(WIN32)
    /* For interactive input on Windows systems, translate the
    ** multi-byte characterset characters into UTF-8. */
--  if( stdin_is_interactive && in==stdin ){
++  if( stdin_is_interactive && pInSrc==&stdInSource) ){
      char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
      if( zTrans ){
        int nTrans = strlen30(zTrans)+1;
    return zLine;
  }
  
- /*
- ** Arrange for shell input from either a FILE or a string.
- ** See getline_from(...) for applicable invariants. It is
- ** the only modifier of data within this struct, except for
- ** initialization which is either {0, zS} or {pF, 0}, (with
- ** the iReadOffset always 0-initialized), left to whatever
- ** routine is switching input sources.
- */
- typedef struct InSource {
-   FILE *inFile;     /* Will be 0 when input is to be taken from string. */
-   char *zStrIn;     /* Will be 0 when no input is available from string. */
-   int iReadOffset;  /* Offset into ZStrIn where next "read" to be done. */
-   const char *zSourceSay; /* For complaints, keep a name for this source */
-   struct InSource *pFrom; /* and redirect tracking to aid unraveling. */
- } InSource;
 +
  /*
- ** Read single lines of input text from source selected by pInSource.
- ** This function presents an interface similar to local_getline(...),
- ** to which it defers for FILE input. (This function may be considered
- ** to be an adapter except for the source parameter. With a rewrite,
- ** it and local_getline can merge, saving most of 26 LOC. )
- */
- static char *getline_from( char *zLine, InSource *pSource ){
-   if( pSource->inFile!=0 ){
-     return local_getline(zLine, pSource->inFile);
-   }else if( pSource->zStrIn!=0
-             && pSource->zStrIn[pSource->iReadOffset]!=0 ){
-     /* This code mostly copied from local_getline(); It begs for refactor. */
-     int nLine = zLine==0 ? 0 : 100;
-     int n = 0;
-     while( 1 ){
-       if( n+100>nLine ){
-         nLine = nLine*2 + 100;
-         zLine = realloc(zLine, nLine);
-         shell_check_oom(zLine);
-       }
-       {
-         /* This block is where fgets() became "read string". */
-         char *zBegin = pSource->zStrIn + pSource->iReadOffset;
-         int iTake = 0;
-         char c = zBegin[iTake];
-         if( c==0 ){
-           if( n==0 ){
-             free(zLine);
-             return 0;
-           }
-           zLine[n] = 0;
-           break;
-         }else{
-           while( iTake<nLine-n-1 && c!=0 ){
-             zLine[n+iTake++] = c;
-             if( c=='\n' ) break;
-             c = zBegin[iTake];
-           }
-           zLine[n+iTake] = 0;
-           pSource->iReadOffset += iTake;
-         }
-       }
-       /* Above block replaced this commented code: */
-       /* if( fgets(&zLine[n], nLine - n, in)==0 ){ */
-       /*   if( n==0 ){ */
-       /*     free(zLine); */
-       /*     return 0; */
-       /*   } */
-       /*   zLine[n] = 0; */
-       /*   break; */
-       /* } */
-       while( zLine[n] ) n++;
-       if( n>0 && zLine[n-1]=='\n' ){
-         n--;
-         if( n>0 && zLine[n-1]=='\r' ) n--;
-         zLine[n] = 0;
-         break;
-       }
-     }
-     return zLine;
-   }else{
-     return 0;
-   }
- }
- /*
--** Retrieve a single line of input text.
++** Retrieve a single line of input text from designated input source.
  **
--** If in==0 then read from standard input and prompt before each line.
--** If isContinuation is true, then a continuation prompt is appropriate.
--** If isContinuation is zero, then the main prompt should be used.
++** If the input is interactive, then issue either the continuation prompt
++** or the main prompt, per isContinuation, before reading a line from
++** standard input. Otherwise, just read a line from the specified source.
  **
  ** If zPrior is not NULL then it is a buffer from a prior call to this
  ** routine that can be reused.
  ** The result is stored in space obtained from malloc() and must either
  ** be freed by the caller or else passed back into this routine via the
  ** zPrior argument for reuse.
- ** has been passed in for resuse,  then the caller need not free it.
 +**
 +** If this function is called until it returns NULL, and the prior return
++** has been passed in for resuse, then the caller need/must not free it.
 +** Otherwise, (in case of an early termination of reading from the given
 +** input), the caller is responsible for freeing a prior, non-NULL return.
 +**
 +** The trailing newline (or its ilk), if any, is trimmed.
++** The input line number is adjusted (via delegation or directly.)
  */
 -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
 -  char *zPrompt;
 -  char *zResult;
 -  if( in!=0 ){
 -    zResult = local_getline(zPrior, in);
 +static char *one_input_line(InSource *pInSrc, char *zPrior, int isContinuation){
-   char *zPrompt;
-   char *zResult;
-   if( pInSrc!=0 ){
-     zResult = getline_from(zPrior, pInSrc);
++  if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
++    return local_getline(zPrior, pInSrc);
    }else{
--    zPrompt = isContinuation ? continuePrompt : mainPrompt;
++    char *zPrompt = isContinuation ? continuePrompt : mainPrompt;
  #if SHELL_USE_LOCAL_GETLINE
      printf("%s", zPrompt);
      fflush(stdout);
--    zResult = local_getline(zPrior, stdin);
++    return local_getline(zPrior, pInSrc);
  #else
++    char *zResult;
      free(zPrior);
      zResult = shell_readline(zPrompt);
++    ++pInSrc->lineno;
      if( zResult && *zResult ) shell_add_history(zResult);
++    return zResult;
  #endif
    }
--  return zResult;
  }
  
  
@@@ -1006,7 -914,7 +988,7 @@@ static void shellModuleSchema
    char *zFake;
    UNUSED_PARAMETER(nVal);
    zName = (const char*)sqlite3_value_text(apVal[0]);
--  zFake = zName ? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
++  zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
    if( zFake ){
      sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake),
                          -1, sqlite3_free);
@@@ -1158,58 -1066,6 +1140,55 @@@ struct EQPGraph 
    char zPrefix[100];    /* Graph prefix */
  };
  
- /* Input source switching is done through one of these (defined below) */
- typedef struct InSource InSource;
 +/* Selectively omit features with one PP variable. Value is true iff
 +** either x is not defined or defined with 0 in bitnum bit position.
 +*/
 +#define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
 +
 +/* Whether build will include extended input parsing option */
 +#define SHEXT_PARSING_BIT 0
 +#define SHELL_EXTENDED_PARSING \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
 +/* Whether build will include dynamic dot-command extension */
 +#define SHEXT_DYNCMDS_BIT 1
 +#define SHELL_DYNAMIC_COMMANDS \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNCMDS_BIT)
 +/* Whether build will include expansion of variables in dot-commands */
 +#define SHEXT_VAREXP_BIT 2
 +#define SHELL_VARIABLE_EXPANSION \
 +  NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
 +
 +#define SHELL_ALL_EXTENSIONS \
 +  (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNCMDS_BIT)+(1<<SHEXT_VAREXP_BIT)
 +#if !defined(SHELL_OMIT_EXTENSIONS)
 +# define SHELL_EXTENSIONS SHELL_ALL_EXTENSIONS
 +#else
 +# define SHELL_EXTENSIONS (~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS
 +#endif
 +
 +/* Runtime test for shell extended parsing, given ShellState pointer */
 +#if SHELL_EXTENDED_PARSING
 +# define SHEXT_PARSING(pSS) (pSS->bExtendedDotCmds & (1<<SHEXT_PARSING_BIT) !=0)
 +#else
 +# define SHEXT_PARSING(pSS) 0
 +#endif
 +
 +/* Runtime test for shell variable expansion, given ShellState pointer */
 +#if SHELL_EXTENDED_PARSING
 +# define SHEXT_VAREXP(pSS) (pSS->bExtendedDotCmds & (1<<SHEXT_VAREXP_BIT) !=0)
 +#else
 +# define SHEXT_VAREXP(pSS) 0
 +#endif
 +
 +/* Parameters affecting columnar mode result display (defaulting together) */
 +typedef struct ColModeOpts {
 +  int iWrap;            /* In columnar modes, wrap lines reaching this limit */
 +  u8 bQuote;            /* Quote results for .mode box and table */
 +  u8 bWordWrap;         /* In columnar modes, wrap at word boundaries  */
 +} ColModeOpts;
 +#define ColModeOpts_default { 60, 0, 0 }
 +#define ColModeOpts_default_qbox { 60, 1, 0 }
 +
  /*
  ** State information about the database connection is contained in an
  ** instance of the following structure.
@@@ -1228,18 -1084,13 +1207,17 @@@ struct ShellState 
    u8 eTraceType;         /* SHELL_TRACE_* value for type of trace */
    u8 bSafeMode;          /* True to prohibit unsafe operations */
    u8 bSafeModePersist;   /* The long-term value of bSafeMode */
 +  u8 bExtendedDotCmds;   /* Bits set to enable various shell extensions */
 +  ColModeOpts cmOpts;    /* Option values affecting columnar mode output */
    unsigned statsOn;      /* True to display memory stats before each finalize */
 +  u8 bQuote;             /* Quote results for .mode box and table */
 +  int iWrap;             /* In columnar modes, wrap lines reaching this limit */
    unsigned mEqpLines;    /* Mask of veritical lines in the EQP output graph */
 +  int inputNesting;      /* Track nesting level of .read and other redirects */
    int outCount;          /* Revert to stdout when reaching zero */
    int cnt;               /* Number of records displayed so far */
--  int lineno;            /* Line number of last line read from in */
    int openFlags;         /* Additional flags to open.  (SQLITE_OPEN_NOFOLLOW) */
 -  FILE *in;              /* Read commands from this stream */
 +  InSource *pInSource;   /* Read commands and SQL from this stream source */
    FILE *out;             /* Write results here */
    FILE *traceOut;        /* Output for sqlite3_trace() */
    int nErr;              /* Number of errors seen */
@@@ -1447,7 -1293,7 +1426,7 @@@ static void failIfSafeMode
      va_start(ap, zErrMsg);
      zMsg = sqlite3_vmprintf(zErrMsg, ap);
      va_end(ap);
--    raw_printf(stderr, "line %d: ", p->lineno);
++    raw_printf(stderr, "line %d: ", p->pInSource->lineno);
      utf8_printf(stderr, "%s\n", zMsg);
      exit(1);
    }
@@@ -2716,9 -2554,9 +2695,11 @@@ static char *shell_error_context(const 
    zCode = sqlite3_mprintf("%.*s", len, zSql);
    for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; }
    if( iOffset<25 ){
--    zMsg = sqlite3_mprintf("\n  %z\n  %*s^--- error here", zCode, iOffset, "");
++    zMsg = sqlite3_mprintf("\n  %z\n  %*s^--- error here",
++                           zCode, iOffset, "");
    }else{
--    zMsg = sqlite3_mprintf("\n  %z\n  %*serror here ---^", zCode, iOffset-14, "");
++    zMsg = sqlite3_mprintf("\n  %z\n  %*serror here ---^",
++                           zCode, iOffset-14, "");
    }
    return zMsg;
  }
@@@ -3192,38 -3030,29 +3173,45 @@@ static void restore_debug_trace_modes(v
    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace);
  }
  
- /* Create the TEMP table used to store parameter bindings */
- static void bind_table_init(ShellState *p){
 +/* DB schema protection for use by next two utility functions */
 +typedef struct DbProtectState {
 +  int wrSchema;
 +  int defensiveMode;
 +} DbProtectState;
 +
 +/* Allow system (sqlite_*) schema changes, return prior protection state. */
 +static DbProtectState allow_sys_schema_change(sqlite3 *db){
 +  DbProtectState rv;
 +  sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode);
 +  sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
 +  sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema);
 +  sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
 +  return rv;
 +}
 +
 +/* Restore protection state using allow_sys_schema_change() return. */
 +static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){
 +  sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0);
 +  sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0);
 +}
 +
 -enum ParamTableUses { PTU_Binding = 0, PTU_Script = 1 };
+ /* Partition the temp.sqlite_parameters key namespace according to use. */
 -  int wrSchema = 0;
 -  int defensiveMode = 0;
 -  sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode);
 -  sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
 -  sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema);
 -  sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
++typedef enum { PTU_Binding = 0, PTU_Script, PTU_Source } ParamTableUse;
+ #define PARAM_TABLE_NAME "sqlite_parameters"
+ #define PARAM_TABLE_SCHEMA "temp"
+ #define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME
+ /* Create the TEMP table used to store parameter bindings and SQL statements */
+ static void param_table_init(ShellState *p){
 +  DbProtectState dps = allow_sys_schema_change(p->db);
    sqlite3_exec(p->db,
--    "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n"
++    "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n"
      "  key TEXT PRIMARY KEY,\n"
-     "  value\n"
+     "  value,\n"
+     "  uses INT DEFAULT (0)" /* aka PTU_Binding */
      ") WITHOUT ROWID;",
      0, 0, 0);
 -  sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
 -  sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0);
 +  restore_sys_schema_protection( p->db, &dps );
  }
  
  /*
@@@ -3246,14 -3077,15 +3236,16 @@@ static void bind_prepared_stmt(ShellSta
  
    nVar = sqlite3_bind_parameter_count(pStmt);
    if( nVar==0 ) return;  /* Nothing to do */
--  if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters",
++  if( sqlite3_table_column_metadata(pArg->db, PARAM_TABLE_SCHEMA,
++                                    PARAM_TABLE_NAME,
                                      "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
      return; /* Parameter table does not exist */
    }
    rc = sqlite3_prepare_v2(pArg->db,
--          "SELECT value FROM temp.sqlite_parameters"
-           " WHERE key=?1", -1, &pQ, 0);
++          "SELECT value FROM "PARAM_TABLE_SNAME
+           " WHERE key=?1 AND uses=0", -1, &pQ, 0);
    if( rc || pQ==0 ) return;
+   assert( PTU_Binding==0 ); /* instead of working symbol value into query */
    for(i=1; i<=nVar; i++){
      char zNum[30];
      const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
  **           |
  **           3
  **
--** Each box characters has between 2 and 4 of the lines leading from
++** Each box character has between 2 and 4 of the lines leading from
  ** the center.  The characters are here identified by the numbers of
  ** their corresponding lines.
  */
@@@ -4553,6 -4186,18 +4549,18 @@@ static const char *(azHelp[]) = 
    "   Options:",
    "      --indent             Try to pretty-print the schema",
    "      --nosys              Omit objects whose names start with \"sqlite_\"",
 -  ".script CMD ...            Manage SQL statement store",
++  ".script CMD ...            Manage and use SQL statement store (some TBD)",
+   "   clear ?NAMES?           Erase some or all stored SQL statements",
+   "   edit NAME               Use edit() to create or alter statement NAME",
+   "   execute ?NAMES?         Execute the named SQL statements in order",
+   "   init                    Initialize the TEMP table that holds statements",
+   "   list                    List the currently stored statements",
+   "   load FILE ?NAMES?       Load some or all SQL statements from FILE",
+   "   save FILE ?NAMES?       Save some or all SQL statements into FILE",
+   "   set NAME VALUE          Give SQL statement NAME a value of VALUE",
+   "                           NAME must start with a letter, and effective",
+   "                           value is space-joined, following argument list",
+   "   unset ?NAMES?           Remove NAMES from statement store",
    ".selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
    "    Options:",
    "       --init               Create a new SELFTEST table",
    ".vfslist                 List all available VFSes",
    ".vfsname ?AUX?           Print the name of the VFS stack",
    ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 -  "     Negative values right-justify",
 +  "   Negative values right-justify",
 +  ".x NAME ...              Excecute content of some .param set variable(s)",
 +  "   Only variables whose name begins with a letter are eligible for this."
 +};
 +
 +/* Like azHelp[], but for undocumented commands. */
 +static const char *(azHelpUndoc[]) = {
 +  "These undocumented commands are for internal testing.\n"
 +  "They are subject to change without notice.\n"
 +  ".check GLOB        Fail if output since .testcase does not match",
 +  ".seeargs           Echo arguments separated or terminated by |",
 +  ".selftest-boolean  Check boolean translation",
 +  ".selftest-integer  Check integer conversion",
 +  ".testcase NAME     Begin output redirect to 'testcase-out.txt'",
 +  ".testctrl CMD ...  Run various sqlite3_test_control() operations",
 +  "   Run \".testctrl\" with no arguments for details",
+   ".x ?NAMES?               Execute the named stored SQL statements in order",
+   "   This is merely a shorter alias for .script execute ?NAMES?"
  };
  
- /* This literal's value and address is used for help's workings. */
++/* This literal's value AND address are used for help's workings. */
 +static const char *zHelpAll = "-all";
 +
  /*
  ** Output help text.
  **
@@@ -4863,36 -4481,35 +4873,38 @@@ int deduceDatabaseType(const char *zNam
  /*
  ** Reconstruct an in-memory database using the output from the "dbtotxt"
  ** program.  Read content from the file in p->aAuxDb[].zDbFilename.
- ** If p->aAuxDb[].zDbFilename is 0, then read from either the present
- ** input file, or standard input if there is no file open for input.
 -** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
++** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
  */
  static unsigned char *readHexDb(ShellState *p, int *pnData){
    unsigned char *a = 0;
--  int nLine;
    int n = 0;
    int pgsz = 0;
    int iOffset = 0;
--  int j, k;
++  int j, k, nlError;
    int rc;
-   FILE *in = 0;
 -  FILE *in;
++  static const char *zEndMarker = "| end ";
    const char *zDbFilename = p->pAuxDb->zDbFilename;
++  /* Need next two objects only if redirecting input to get the hex. */
++  InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, p->pInSource);
    unsigned int x[16];
    char zLine[1000];
    if( zDbFilename ){
--    in = fopen(zDbFilename, "r");
--    if( in==0 ){
++    inRedir.inFile = fopen(zDbFilename, "r");
++    if( inRedir.inFile==0 ){
        utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
        return 0;
      }
--    nLine = 0;
++    p->pInSource = &inRedir;
    }else{
-     if( p->pInSource!=0 ) in = p->pInSource->inFile;
 -    in = p->in;
--    nLine = p->lineno;
--    if( in==0 ) in = stdin;
++    /* Will read hex DB lines inline from present input, without redirect. */
++    if( INSOURCE_IS_INTERACTIVE(p->pInSource) ){
++      printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
++             p->pInSource->zSourceSay, zEndMarker);
++      fflush(stdout);
++    }
    }
    *pnData = 0;
--  nLine++;
--  if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
++  if( str_line_get(zLine,sizeof(zLine), p->pInSource)==0 ) goto readHexDb_error;
    rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
    if( rc!=2 ) goto readHexDb_error;
    if( n<0 ) goto readHexDb_error;
      utf8_printf(stderr, "invalid pagesize\n");
      goto readHexDb_error;
    }
--  for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
++  while( str_line_get(zLine,sizeof(zLine), p->pInSource)!=0 ){
      rc = sscanf(zLine, "| page %d offset %d", &j, &k);
      if( rc==2 ){
        iOffset = k;
        continue;
      }
--    if( strncmp(zLine, "| end ", 6)==0 ){
++    if( strncmp(zLine, zEndMarker, 6)==0 ){
        break;
      }
      rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
        }
      }
    }
--  *pnData = n;
-   if( zDbFilename ) fclose(in);
-   else p->lineno = nLine;
 -  if( in!=p->in ){
 -    fclose(in);
 -  }else{
 -    p->lineno = nLine;
++  *pnData = n; /* Record success and size. */
++ readHexDb_cleanup:
++  if( p->pInSource==&inRedir ){
++    fclose( inRedir.inFile );
++    p->pInSource = inRedir.pFrom;
+   }
    return a;
  
--readHexDb_error:
-   if( zDbFilename ){
 -  if( in!=p->in ){
--    fclose(in);
--  }else{
-     int nLinePass = nLine;
-     while( fgets(zLine, sizeof(zLine), in)!=0 ){
-       nLinePass++;
 -    while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
 -      nLine++;
--      if(strncmp(zLine, "| end ", 6)==0 ) break;
++ readHexDb_error:
++  nlError = p->pInSource->lineno;
++  if( p->pInSource!=&inRedir ){
++    /* Since taking input inline, consume through its end marker. */
++    while( str_line_get(zLine, sizeof(zLine), p->pInSource)!=0 ){
++      if(strncmp(zLine, zEndMarker, 6)==0 ) break;
      }
-     p->lineno = nLinePass;
 -    p->lineno = nLine;
    }
    sqlite3_free(a);
--  utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
--  return 0;
++  a = 0;
++  utf8_printf(stderr,"Error on line %d within --hexdb input\n", nlError);
++  goto readHexDb_cleanup;
  }
  #endif /* SQLITE_OMIT_DESERIALIZE */
  
@@@ -5397,11 -5009,14 +5410,11 @@@ static int booleanValue(const char *zAr
      for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
    }
    if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
 -  if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
 -    return 1;
 -  }
 -  if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
 -    return 0;
 +  for( i=0; zBoolNames[i]!=0; ++i ){
 +    if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
    }
    utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
--          zArg);
++              zArg);
    return 0;
  }
  
@@@ -6509,7 -6125,7 +6522,7 @@@ static void shellPrepare
  /*
  ** Create a prepared statement using printf-style arguments for the SQL.
  **
--** This routine is could be marked "static".  But it is not always used,
++** This routine could be marked "static".  But it is not always used,
  ** depending on compile-time options.  By omitting the "static", we avoid
  ** nuisance compiler warnings about "defined but not used".
  */
@@@ -8010,128 -7626,79 +8023,204 @@@ static int recoverDatabaseCmd(ShellStat
  }
  #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
  
-   if( sqlite3_table_column_metadata(p->db, "TEMP", "sqlite_parameters",
++/* 
++** The .shxopts command, for setting or listing shell extension options.
++ */
 +#if SHELL_EXTENSIONS
 +static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){
 +  static struct { const char *name; u8 mask; } shopts[] = {
 +#if SHELL_DYNAMIC_COMMANDS
 +    {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
 +#endif
 +#if SHELL_EXTENDED_PARSING
 +    {"parsing", 1<<SHEXT_PARSING_BIT},
 +#endif
 +#if SHELL_VARIABLE_EXPANSION
 +    {"dot_vars", 1<<SHEXT_VAREXP_BIT},
 +#endif
 +    {"all_opts", SHELL_ALL_EXTENSIONS}
 +  };
 +  const char *zMoan = 0, *zAbout = 0;
 +  int ia, io;
 +  if( nArg>1 ){
 +    for( ia=1; ia<nArg; ++ia ){
 +      char cs = azArg[ia][0];
 +      if( cs!='+' && cs!='-' ){
 +        zMoan = "arguments must have a sign prefix.";
 +        zAbout = azArg[0];
 +        goto moan_error;
 +      }
 +      for( io=0; io<ArraySize(shopts); ++io ){
 +        if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
 +          if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
 +          else p->bExtendedDotCmds &= ~shopts[io].mask;
 +          break;
 +        }
 +      }
 +      if( io==ArraySize(shopts) ){
 +        zAbout = azArg[ia];
 +        zMoan = "is not a recognized option name";
 +        goto moan_error;
 +      }
 +    }
 +  }else{
 +    raw_printf(p->out,
 +               "     name    value  \"-shxopts set\"\n"
 +               "   --------  -----  ---------------\n");
 +    for( io=0; io<ArraySize(shopts); ++io ){
 +      unsigned m = shopts[io].mask;
 +      unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
 +      raw_printf(p->out,
 +                 "  %9s   %2d    \"-shxopts 0x%02X\"\n",
 +                 shopts[io].name,  v, m);
 +    }
 +  }
 +  return 0;
 + moan_error:
 +  raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan);
 +  return 1;
 +}
 +#endif
 +
 +static int execute_variables(char *azArg[], int nArg, ShellState *p){
 +  int ia, rc, nErrors = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  open_db(p, 0);
 +  if( p->db==0 ){
 +    utf8_printf(stderr, ".x can only be done with a database open.\n");
 +    return 1;
 +  }
-     utf8_printf(stderr, "No temp.sqlite_parameters table exists.\n");
++  if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
++                                    PARAM_TABLE_NAME,
 +                                    "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
-     (p->db, "SELECT value FROM temp.sqlite_parameters WHERE key=$1",
++    utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n");
 +    return 1;
 +  }
 +  rc = sqlite3_prepare_v2
-     utf8_printf(stderr, "temp.sqlite_parameters is wrongly created.\n");
++    (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
++     " WHERE key=$1 AND uses=1",
 +     -1, &pStmt, 0);
 +  if( rc!=SQLITE_OK ){
-            * because it or some other character is likely to be put in its
-            * place during process_input() line or group handling, along
++    utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n");
 +    return 1;
 +  }
 +  for( ia=1; ia < nArg; ++ia ){
 +    if( isalpha(azArg[ia][0]) ){
 +      rc = sqlite3_reset(pStmt);
 +      rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
 +      rc = sqlite3_step(pStmt);
 +      if( rc==SQLITE_ROW ){
 +        const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
 +        int nb = sqlite3_column_bytes(pStmt, 0);
 +        while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
 +        if( nb>0 ){
 +          /* The trailing newline (or some other placeholder) is important
-            * land past the end of the allocation made at this next line.
++           * because one (or some other character) will likely be put in
++           * its place during process_input() line/group handling, along
 +           * with a terminating NUL character. Without it, the NULL could
-           InSource inSourceDivert =
-             {0, zSubmit, 0, azArg[ia], p->pInSource };
-           InSource *pInSrcSave = p->pInSource;
-           int savedLineno = p->lineno;
++           * land past the end of the allocation made just below.
 +           */
 +          int nle = zValue[nb-1]=='\n';
 +          char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
-           p->pInSource = &inSourceDivert;
++          InSource inRedir
++            = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
 +          shell_check_oom(zSubmit);
-           p->pInSource = pInSrcSave;
-           p->lineno = savedLineno;
++          p->pInSource = &inRedir;
 +          rc = process_input(p);
 +          sqlite3_free(zSubmit);
 -/* Edit one named parameter in the parameters table. If it does not
++          p->pInSource = inRedir.pFrom;
 +        }else{
 +          continue; /* All white, ignore. */
 +        }
 +      }else{
 +        utf8_printf(stderr,
 +                    "Skipping parameter '%s' (not set.)\n", azArg[ia]);
 +        ++nErrors;
 +      }
 +    }else{
 +      utf8_printf(stderr,
 +                  "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
 +      ++nErrors;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  return (rc==2)? 2 : nErrors>0;
 +}
 +
+ struct param_row { char * value; int uses; int hits; };
+ static int param_find_callback(void *pData, int nc, char **pV, char **pC){
+   assert(nc>=1);
+   assert(strcmp(pC[0],"value")==0);
+   struct param_row *pParam = (struct param_row *)pData;
+   assert(pParam->value==0); /* key values are supposedly unique. */
+   if( pParam->value!=0 ) sqlite3_free( pParam->value );
+   pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
+   if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
+   ++pParam->hits;
+   return 0;
+ }
 -
++
++/*
++ * Edit one named parameter in the parameters table. If it does not
+  * yet exist, create it. If eval is true, the value is treated as a
+  * bare expression, otherwise it is a text value. The uses argument
+  * sets the 3rd column in the parameters table, and may also serve
+  * to partition the key namespace. (This is not done now.)
+  */
+ static int edit_one_param(sqlite3 *db, char *name, int eval,
+                           int uses, const char * zEditor){
+   struct param_row paramVU = {0,0,0};
+   char * zSql = sqlite3_mprintf
+     ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
+   shell_check_oom(zSql);
+   sqlite3_exec(db, zSql, param_find_callback, &paramVU, 0);
+   sqlite3_free(zSql);
+   assert(paramVU.hits<2);
+   if( eval!=0 ){
+     /* ToDo: Run an eval-like update, leaving as-is if it's a bad expr. */
+     // int rv = edit_one_param(db, name, 0, uses);
+   }
+   if( paramVU.hits==1 && paramVU.uses==uses){
+     /* Editing an existing value of same kind. */
+     sqlite3_free(paramVU.value);
+     zSql = sqlite3_mprintf
+       ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
+        " key=%Q AND uses=%d", zEditor, name, uses);
+   }else{
+     /* Editing a new value of same kind. */
+     assert(paramVU.value==0);
+     zSql = sqlite3_mprintf
+       ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+        " VALUES (%Q,edit('-- %q%s', %Q),%d)",
+        name, name, "\n", zEditor, uses);
+   }
+   shell_check_oom(zSql);
+   sqlite3_exec(db, zSql, 0, 0, 0);
+   sqlite3_free(zSql);
+   return 0; /* ToDo: add some error checks */
+ }
+ static void append_in_clause(sqlite3_str *pStr, char **azBeg, char **azLim){
+   /* An empty IN list is the same as always true (for non-NULL LHS)
+    * for this clause, which assumes a trailing LHS operand and space.
+    * If that is not the right result, guard the call against it.
+    * This is used for .parameter and .script ?NAMES? options,
+    * where a missing list means all the qualifying entries.
+    */
+   if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
+   else{
+     char cSep = '(';
+     sqlite3_str_appendf(pStr, "IN");
+     while( azBeg<azLim ){
+       sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
+       cSep = ',';
+       ++azBeg;
+     }
+     sqlite3_str_appendf(pStr, ")");
+   }
+ }
  /*
  ** If an input line begins with "." then invoke this routine to
  ** process that line.
@@@ -9567,7 -9063,7 +9656,7 @@@ static int do_meta_command(char *zLine
        rc = 1;
      }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
        raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n",
--                 p->lineno, azArg[1]);
++                 p->pInSource->lineno, azArg[1]);
        exit(1);
      }else{
        p->bSafeMode = 0;
    }else
  
    if( (c=='o'
--        && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
--   || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
++       && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
++      || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
    ){
      char *zFile = 0;
      int bTxtMode = 0;
        int len = 0;
        rx = sqlite3_prepare_v2(p->db,
               "SELECT max(length(key)) "
--             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
++             "FROM "PARAM_TABLE_SNAME, -1, &pStmt, 0);
        if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
          len = sqlite3_column_int(pStmt, 0);
          if( len>40 ) len = 40;
        pStmt = 0;
        if( len ){
          rx = sqlite3_prepare_v2(p->db,
--             "SELECT key, quote(value) "
--             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
++             "SELECT key, quote(value), uses "
++             "FROM "PARAM_TABLE_SNAME, -1, &pStmt, 0);
          while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
--          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
--                      sqlite3_column_text(pStmt,1));
++          utf8_printf(p->out, "%-*s %s %d\n", len,
++                      sqlite3_column_text(pStmt,0),
++                      sqlite3_column_text(pStmt,1),
++                      sqlite3_column_int(pStmt,2));
          }
          sqlite3_finalize(pStmt);
        }
        sqlite3_stmt *pStmt;
        const char *zKey = azArg[2];
        const char *zValue = azArg[3];
-       bind_table_init(p);
++      u8 ptu = isalpha(*zKey)? PTU_Script : PTU_Binding;
+       param_table_init(p);
        zSql = sqlite3_mprintf(
--                  "REPLACE INTO temp.sqlite_parameters(key,value)"
--                  "VALUES(%Q,%s);", zKey, zValue);
++                  "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++                  "VALUES(%Q,%s,%d);", zKey, zValue, ptu);
        shell_check_oom(zSql);
        pStmt = 0;
        rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
          sqlite3_finalize(pStmt);
          pStmt = 0;
          zSql = sqlite3_mprintf(
--                   "REPLACE INTO temp.sqlite_parameters(key,value)"
--                   "VALUES(%Q,%Q);", zKey, zValue);
++                   "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++                   "VALUES(%Q,%Q,%d);", zKey, zValue, ptu);
          shell_check_oom(zSql);
          rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
          sqlite3_free(zSql);
      */
      if( nArg==3 && strcmp(azArg[1],"unset")==0 ){
        char *zSql = sqlite3_mprintf(
--          "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
++          "DELETE FROM "PARAM_TABLE_SNAME" WHERE key=%Q", azArg[2]);
        shell_check_oom(zSql);
        sqlite3_exec(p->db, zSql, 0, 0, 0);
        sqlite3_free(zSql);
        utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
        rc = 1;
      }else{
-       InSource *pInSrcSave = p->pInSource;
-       int savedLineno = p->lineno;
-       InSource inSourceDivert =
-         {inUse, 0, 0, azArg[1], p->pInSource};
-       p->pInSource = &inSourceDivert;
 +      fCloser = fclose;
 +    }
 +    if( inUse!=0 ){
++      InSource inSourceRedir
++        = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
++      p->pInSource = &inSourceRedir;
        rc = process_input(p);
 -      fclose(p->in);
 +      assert(fCloser!=0);
 +      fCloser(inUse);
-       p->pInSource = pInSrcSave;
-       p->lineno = savedLineno;
++      p->pInSource = p->pInSource->pFrom;
      }
 -    p->in = inSaved;
 -    p->lineno = savedLineno;
    }else
  
    if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
        }else if( zName==0 ){
          zName = azArg[ii];
        }else{
--        raw_printf(stderr, "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
++        raw_printf(stderr,
++                   "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
          rc = 1;
          goto meta_command_exit;
        }
  #ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
        if( zName ){
          appendText(&sSelect,
--           " UNION ALL SELECT shell_module_schema(name),"
--           " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
--        0);
++                   " UNION ALL SELECT shell_module_schema(name),"
++                   " 'table', name, name, name, 9e+99, 'main'"
++                   " FROM pragma_module_list", 0);
        }
  #endif
        appendText(&sSelect, ") WHERE ", 0);
      }
    }else
  
 +  if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){
 +    for( n=1; n<nArg; ++n ){
 +      raw_printf(p->out, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|");
 +    } 
 +  }else
 +
    if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){
--    unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
++    unsigned int x
++      = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
      sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
    }else
  
          }
        }
        if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
--        raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
++        raw_printf(stderr, "Maximum of %d sessions\n",
++                   ArraySize(pAuxDb->aSession));
          goto meta_command_exit;
        }
        pSession = &pAuxDb->aSession[pAuxDb->nSession];
      }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
        utf8_printf(stderr,
           "line %d: \".testctrl %s\" may not be used in safe mode\n",
--         p->lineno, aCtrl[iCtrl].zCtrlName);
++         p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
        exit(1);
      }else{
        switch(testctrl){
    }else
  
    if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
--    unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
++    unsigned int x
++      = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
      sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
    }else
  
@@@ -11384,16 -10915,6 +11544,16 @@@ meta_command_exit
      if( p->outCount==0 ) output_reset(p);
    }
    p->bSafeMode = p->bSafeModePersist;
-     /* Free any arguments that had to be allocated rather than tokenized in place. */
 +#if SHELL_VARIABLE_EXPANSION
 +  if( bExpVars ){
++    /* Free any arguments that are allocated rather than tokenized in place. */
 +    for( n=1; n<nArg; ++n ){
 +      int iArgOffset = azArg[n]-zLine;
 +      u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
 +      if( !bInPlace ) sqlite3_free(azArg[n]);
 +    }
 +  }
 +#endif
    return rc;
  }
  
@@@ -11574,395 -11088,110 +11734,398 @@@ static int runOneSqlLine(ShellState *p
    return 0;
  }
  
 -
 -/*
 -** Read input from *in and process it.  If *in==0 then input
 -** is interactive - the user is typing it it.  Otherwise, input
 -** is coming from a file or device.  A prompt is issued and history
 -** is saved only if input is interactive.  An interrupt signal will
 -** cause this routine to exit immediately, unless input is interactive.
 +#if SHELL_EXTENDED_PARSING
 +/* Resumable line classsifier for dot-commands
  **
 -** Return the number of errors.
 -*/
 -static int process_input(ShellState *p){
 -  char *zLine = 0;          /* A single input line */
 -  char *zSql = 0;           /* Accumulated SQL text */
 -  int nLine;                /* Length of current line */
 -  int nSql = 0;             /* Bytes of zSql[] used */
 -  int nAlloc = 0;           /* Allocated zSql[] space */
 -  int rc;                   /* Error code */
 -  int errCnt = 0;           /* Number of errors seen */
 -  int startline = 0;        /* Line number for start of current input */
 -  QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
 -
 -  p->lineno = 0;
 -  while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
 -    fflush(p->out);
 -    zLine = one_input_line(p->in, zLine, nSql>0);
 -    if( zLine==0 ){
 -      /* End of input */
 -      if( p->in==0 && stdin_is_interactive ) printf("\n");
 -      break;
 -    }
 -    if( seenInterrupt ){
 -      if( p->in!=0 ) break;
 -      seenInterrupt = 0;
 -    }
 -    p->lineno++;
 -    if( QSS_INPLAIN(qss)
 -        && line_is_command_terminator(zLine)
 -        && line_is_complete(zSql, nSql) ){
 -      memcpy(zLine,";",2);
 -    }
 -    qss = quickscan(zLine, qss);
 -    if( QSS_PLAINWHITE(qss) && nSql==0 ){
 -      if( ShellHasFlag(p, SHFLG_Echo) )
 -        printf("%s\n", zLine);
 -      /* Just swallow single-line whitespace */
 -      qss = QSS_Start;
 +** Determines if a dot-command is open, having either an unclosed
 +** quoted argument or an escape sequence opener ('\') at its end.
 +**
 +** The FSM design/behavior assumes/requires that a terminating '\'
 +** is not part of the character sequence being classified -- that
 +** it represents an escaped newline which is removed as physical
 +** lines are spliced to accumulate logical lines.
 +**
 +** The line or added line-portion is passed as zCmd.
 +** The pScanState pointer must reference an (opaque) DCmd_ScanState,
 +** which must be set to DCSS_Start to initialize the scanner state.
 +** Resumed scanning should always be done with zCmd logically just
 +** past the last non-0 char of the text previously passed in, with
 +** any previously scanned, trailing newline escape first trimmed.
 +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
 +** The following macros may be applied to the scan state:
 +*/
 +#define DCSS_InDarkArg(dcss) ((dcss)&argPosMask==inDqArg)
 +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
 +#define DCSS_IsOpen(dcss) (((dcss) & isOpenMask)!=0)
 +typedef enum {
 +  DCSS_Start = 0,
 +  twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
 +  endEscaped = 4, /* bit used */
 +  argPosMask = 3, /* bits used */
 +  isOpenMask = 1|4 /* bit test */
 +} DCmd_ScanState;
 +
 +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){
 +  DCmd_ScanState ss = *pScanState & ~endEscaped;
 +  char c = (ss&isOpenMask)? 1 : *zCmd++;
 +  while( c!=0 ){
 +    switch( ss ){
 +    twixt:
 +    case twixtArgs:
 +      while( IsSpace(c) ){
 +        if( (c=*zCmd++)==0 ) goto atEnd; 
 +      }
 +      switch( c ){
 +      case '\\':
 +        if( *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }else goto inDark;
 +      case '\'': ss = inSqArg; goto inSq;
 +      case '"': ss = inDqArg; goto inDq;
 +      default: ss = inDarkArg; goto inDark;
 +      }
 +    inSq:
 +    case inSqArg:
 +      while( (c=*zCmd++)!='\'' ){
 +        if( c==0 ) goto atEnd;
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
        continue;
 -    }
 -    if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
 -      if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
 -      if( zLine[0]=='.' ){
 -        rc = do_meta_command(zLine, p);
 -        if( rc==2 ){ /* exit requested */
 -          break;
 -        }else if( rc ){
 -          errCnt++;
 +    inDq:
 +    case inDqArg:
 +      do {
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +        if( c=='\\' ){
 +          if( (c=*zCmd++)==0 ){
 +            ss |= endEscaped;
 +            goto atEnd;
 +          }
 +          if( (c=*zCmd++)==0 ) goto atEnd;
          }
 +      } while( c!='"' );
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDark:
 +    case inDarkArg:
 +      while( !IsSpace(c) ){
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +        if( (c=*zCmd++)==0 ) goto atEnd; 
        }
 -      qss = QSS_Start;
 +      ss = twixtArgs;
 +      c = *zCmd++;
        continue;
      }
 -    /* No single-line dispositions remain; accumulate line(s). */
 -    nLine = strlen30(zLine);
 -    if( nSql+nLine+2>=nAlloc ){
 -      /* Grow buffer by half-again increments when big. */
 -      nAlloc = nSql+(nSql>>1)+nLine+100;
 -      zSql = realloc(zSql, nAlloc);
 -      shell_check_oom(zSql);
 +  }
 + atEnd:
 +  *pScanState = ss;
 +}
 +#else
 +# define dot_command_scan(x,y)
 +#endif
 +
 +/* Utility functions for process_input. */
 +
 +#if SHELL_EXTENDED_PARSING
 +/*
 +** Process dot-command line with its scan state to:
 +** 1. Setup for requested line-splicing; and
 +** 2. Say whether it is complete.
 +** The last two out parameters are the line's length, which may be
 +** adjusted, and the char to be used for joining a subsequent line.
 +** This is broken out of process_input() mainly for readability.
 +** The return is TRUE for dot-command ready to run, else false.
 +*/
 +static int line_join_ends(DCmd_ScanState dcss, char *zLine,
 +                          int *pnLength, char *pcLE){
 +  /* It is ready only if has no open argument or escaped newline. */
 +  int bOpen = DCSS_IsOpen(dcss);
 +  if( !DCSS_EndEscaped(dcss) ){
 +    *pcLE = '\n';
 +    return !bOpen;
 +  }else{
 +    *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
 +    /* Swallow the trailing escape character. */
 +    zLine[--*pnLength] = 0;
 +    return 0;
 +  }
 +}
 +#endif
 +
 +/* 
 +** Grow the accumulation line buffer to accommodate ncNeed chars.
 +** In/out parameters pz and pna reference the buffer and its size.
 +** The buffer must eventually be sqlite3_free()'ed by the caller.
 +*/
 +static void grow_line_buffer(char **pz, int *pna, int ncNeed){
 +  if( ncNeed > *pna ){
 +    *pna += *pna + (*pna>>1) + 100;
 +    *pz = sqlite3_realloc(*pz, *pna);
 +    shell_check_oom(*pz);
 +  }
 +}
 +
 +/*
 +** Read input from designated source (p->pInSource) and process it.
 +** If pInSource==0 then input is interactive - the user is typing it.
 +** Otherwise, input is coming from a file, stream device or string.
 +** Prompts issue and history is saved only for interactive input.
 +** An interrupt signal will cause this routine to exit immediately,
 +** with "exit demanded" code returned, unless input is interactive.
 +**
 +** Returns:
 +**   0 => no errors
 +**   1 => errors>0
 +**   2 => exit demanded, no errors.
 +**   3 => exit demanded, errors>0
 +*/
 +static int process_input(ShellState *p){
 +  char *zLineInput = 0;  /* a line-at-a-time input buffer or usable result */
 +  char *zLineAccum = 0;  /* accumulation buffer, used for multi-line input */
 +  /* Above two pointers could be local to the group handling loop, but are
 +   * not so that the number of memory allocations can be reduced. They are
 +   * reused from one incoming group to another, realloc()'ed as needed. */
 +  int naAccum = 0;       /* tracking how big zLineAccum buffer has become */
 +  /* Some flags for ending the overall group processing loop, always 1 or 0 */
 +  u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0;
 +  /* Flag to affect prompting and interrupt action */
-   u8 bInteractive = (p->pInSource==0 && stdin_is_interactive);
++  u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive);
 +  int nErrors = 0;       /* count of errors during execution or its prep */
 +
 +  /* Block overly-recursive or absurdly nested input redirects. */
 +  if( p->inputNesting==MAX_INPUT_NESTING ){
-     InSource *pInSrc = p->pInSource;
-     int i;
-     utf8_printf
-       (stderr, "Input nesting limit (%d) reached, from line %d of \"%s\",\n",
-        MAX_INPUT_NESTING, p->lineno, pInSrc->zSourceSay);
-     for( i=0; i<3 && (pInSrc=pInSrc->pFrom)!=0; ++i )
-       utf8_printf(stderr, " from \"%s\"", pInSrc->zSourceSay);
++    InSource *pInSrc = p->pInSource->pFrom;
++    const char *zLead = "Input nesting limit ("
++      SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
++    int i = 3;
++    assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
++    while( i-->0 && pInSrc!=0 ){
++      utf8_printf(stderr,
++                  "%s from line %d of \"%s\"",
++                  zLead, pInSrc->lineno, pInSrc->zSourceSay);
++      zLead = (i%2==0)? "\n" : "";
++      pInSrc=pInSrc->pFrom;
+     }
 -    if( nSql==0 ){
 -      int i;
 -      for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
 -      assert( nAlloc>0 && zSql!=0 );
 -      memcpy(zSql, zLine+i, nLine+1-i);
 -      startline = p->lineno;
 -      nSql = nLine-i;
 +    utf8_printf(stderr, " ...\nERROR: Check recursion.\n");
 +    return 1;
 +  }
 +  ++p->inputNesting;
-   p->lineno = 0;
 +
 +  /* line-group processing loop (per SQL block, dot-command or comment) */
 +  while( !bErrorBail && !bInputEnd && !bExitDemand && !bInterrupted ){
 +    int nGroupLines = 0;  /* count of lines belonging to this group */
 +    int ncLineIn = 0;     /* how many (non-zero) chars are in zLineInput  */
 +    int ncLineAcc = 0;    /* how many (non-zero) chars are in zLineAccum  */
 +    int iLastLine = 0;    /* index of last accumulated line start */
 +    /* Initialize resumable scanner(s). */
 +    SqlScanState sqScanState = SSS_Start; /* for SQL scan */
 +#if SHELL_EXTENDED_PARSING
 +    DCmd_ScanState dcScanState = DCSS_Start;  /* for dot-command scan */
 +    int ndcLeadWhite = 0; /* for skip over initial whitespace to . or # */
 +    char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
 +#else
 +# define ndcLeadWhite 0   /* For legacy parsing, no white before . or # . */
 +# define cLineEnd '\n'    /* For legacy parsing, this always joins lines. */
 +#endif
 +    /* An ordered enum to record kind of incoming line group. Its ordering
 +     * means than a value greater than Comment implies something runnable.
 +     */
 +    enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd;
 +    /* An enum signifying the group disposition state */
 +    enum {
 +      Incoming, Runnable, Dumpable, Erroneous, Ignore
 +    } disposition = Incoming;
 +    char **pzLineUse = &zLineInput;   /* ref line to be processed */
 +    int *pncLineUse = &ncLineIn;      /* ref that line's char count */
 +    int iStartline = 0;               /* starting line number of group */
 +
 +    fflush(p->out);
 +    zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
 +    if( zLineInput==0 ){
 +      bInputEnd = 1;
 +      inKind = Eof;
 +      disposition = Ignore;
 +      if( bInteractive ) printf("\n");
      }else{
 -      zSql[nSql++] = '\n';
 -      memcpy(zSql+nSql, zLine, nLine+1);
 -      nSql += nLine;
 -    }
 -    if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
 -      errCnt += runOneSqlLine(p, zSql, p->in, startline);
 -      nSql = 0;
 -      if( p->outCount ){
 -        output_reset(p);
 -        p->outCount = 0;
 -      }else{
 -        clearTempFile(p);
 +      ++nGroupLines;
-       p->lineno++;
-       iStartline = p->lineno;
++      iStartline = p->pInSource->lineno;
 +      ncLineIn = strlen30(zLineInput);
 +      if( seenInterrupt ){
 +        if( p->pInSource!=0 ) break;
 +        bInterrupted = 1; /* This will be honored, or not, later. */
 +        seenInterrupt = 0;
 +        disposition = Dumpable;
 +      }
 +      /* Classify and check for single-line dispositions, prep for more. */
 +#if SHELL_EXTENDED_PARSING
 +      ndcLeadWhite = (SHEXT_PARSING(p))
 +        ? skipWhite(zLineInput)-zLineInput
 +        : 0; /* Disallow leading whitespace for . or # in legacy mode. */
 +#endif
 +      switch( zLineInput[ndcLeadWhite] ){
 +      case '.':
 +        inKind = Cmd;
 +        dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
 +        break;
 +      case '#':
 +        inKind = Comment;
 +        break;
 +      default:
 +        /* Might be SQL, or a swallowable whole SQL comment. */
 +        sql_prescan(zLineInput, &sqScanState);
 +        if( SSS_PLAINWHITE(sqScanState) ){
 +          /* It's either all blank or a whole SQL comment. Swallowable. */
 +          inKind = Comment;
 +        }else{
 +          /* Something dark, not a # comment or dot-command. Must be SQL. */
 +          inKind = Sql;
 +        }
 +        break;
 +      } /* end classification switch */
 +    } /* end read/classify initial group input line */
 +
 +    /* Here, if not at end of input, the initial line of group is in, and
 +     * it has been scanned and classified. Next, do the processing needed
 +     * to recognize whether the initial line or accumulated group so far
 +     * is complete such that it may be run, and perform joining of more
 +     * lines into the group while it is not so complete. This loop ends
 +     * with the input group line(s) ready to be run, or if the input ends
 +     * before it is ready, with the group marked as erroneous.
 +     */
 +    while( disposition==Incoming ){
 +      /* Check whether more to accumulate, or ready for final disposition. */
 +      switch( inKind ){
 +      case Comment:
 +        disposition = Dumpable;
 +      case Cmd:
 +#if SHELL_EXTENDED_PARSING
 +        if( SHEXT_PARSING(p) ){
 +          if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
 +            disposition = Runnable;
 +          }
 +        }else
 +#endif
 +          disposition = Runnable; /* Legacy, any dot-command line is ready. */
 +        break;
 +      case Sql:
 +        /* Check to see if it is complete and ready to run. */
 +        if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){
 +          disposition = Runnable;
 +        }else if( SSS_PLAINWHITE(sqScanState) ){
 +          /* It is a leading single-line or multi-line comment. */
 +          disposition = Runnable;
 +          inKind = Comment;
 +        }else{
 +          char *zT = line_is_command_terminator(zLineInput);
 +          if( zT!=0  ){
 +            /* Last line is a lone go or / -- prep for running it. */
 +            if( nGroupLines>1 ){
 +              disposition = Runnable;
 +              memcpy(*pzLineUse+iLastLine,";\n",3);
 +              *pncLineUse = iLastLine + 2;
 +            }else{
 +              /* Unless nothing preceded it, then dump it. */
 +              disposition = Dumpable;
 +            }
 +          }
 +        }
 +        break;
 +      } /* end switch on inKind */
 +      /* Collect and accumulate more input if group not yet complete. */
 +      if( disposition==Incoming ){
 +        if( nGroupLines==1 ){
 +          grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
 +          /* Copy line just input */
 +          memcpy(zLineAccum, zLineInput, ncLineIn);
 +          ncLineAcc = ncLineIn;
 +          pzLineUse = &zLineAccum;
 +          pncLineUse = &ncLineAcc;
 +        }
 +        /* Read in next line of group, (if available.) */
 +        zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
 +        if( zLineInput==0 ){
 +          bInputEnd = 1;
 +          inKind = Eof;
 +          disposition = Erroneous;
 +          if( bInteractive ) printf("\n");
 +          continue;
 +        }
 +        ++nGroupLines;
-         p->lineno++;
 +        ncLineIn = strlen30(zLineInput);
 +        /* Scan line just input (if needed) and append to accumulation. */
 +        switch( inKind ){
 +        case Cmd:
 +          dot_command_scan(zLineInput, &dcScanState);
 +          break;
 +        case Sql:
 +          sql_prescan(zLineInput, &sqScanState);
 +          break;
 +        default:
 +          break;
 +        }
 +        grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
 +        /* Join lines as setup by exam of previous line(s). */
 +        if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
 +        cLineEnd = '\n'; /* reset to default after use */
 +        memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
 +        iLastLine = ncLineAcc;
 +        ncLineAcc += ncLineIn;
 +        zLineAccum[ncLineAcc] = 0;
 +      } /* end glom another line */
 +    } /* end group collection loop */
 +    /* Here, the group is fully collected or known to be incomplete forever. */
 +    switch( disposition ){
 +    case Dumpable:
 +      echo_group_input(p, *pzLineUse);
 +      break;
 +    case Runnable:
 +      switch( inKind ){
 +      case Sql:
 +        /* runOneSqlLine() does its own echo when requested. */
-         nErrors += runOneSqlLine(p, *pzLineUse, p->pInSource!=0, iStartline);
++        nErrors += runOneSqlLine(p, *pzLineUse,
++                                 INSOURCE_IS_INTERACTIVE(p->pInSource),
++                                 iStartline);
 +        if( bail_on_error && nErrors>0 ) bErrorBail = 1;
 +        break;
 +      case Cmd:
 +        echo_group_input(p, *pzLineUse);
 +        switch( do_meta_command(*pzLineUse, p) ){
 +        default: ++nErrors; /* fall thru */
 +        case 0: break;
 +        case 2: bExitDemand = 1; break;
 +        }
 +        break;
 +      default:
 +        assert(inKind!=Tbd);
 +        break;
        }
 -      p->bSafeMode = p->bSafeModePersist;
 -      qss = QSS_Start;
 -    }else if( nSql && QSS_PLAINWHITE(qss) ){
 -      if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
 -      nSql = 0;
 -      qss = QSS_Start;
 +      break;
 +    case Erroneous:
 +      utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n",
-                   p->lineno,
-                   (p->pInSource!=0)? p->pInSource->zSourceSay : "<stdin>");
++                  p->pInSource->lineno, p->pInSource->zSourceSay);
 +      ++nErrors;
 +      break;
 +    case Ignore:
 +      break;
 +    default: assert(0);
      }
 -  }
 -  if( nSql && QSS_PLAINDARK(qss) ){
 -    errCnt += runOneSqlLine(p, zSql, p->in, startline);
 -  }
 -  free(zSql);
 -  free(zLine);
 -  return errCnt>0;
 +    if( nErrors>0 && bail_on_error ) bErrorBail = 1;
 +  } /* end group consume/prep/(run, dump or complain) loop */
 +
 +  /* Cleanup and determine return value based on flags and error count. */
 +  free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
 +  sqlite3_free(zLineAccum);
 +
 +  return ((bErrorBail | bExitDemand)<<1) + (nErrors>0);
  }
  
  /*
@@@ -12060,21 -11290,13 +12223,19 @@@ static void process_sqliterc
      shell_check_oom(zBuf);
      sqliterc = zBuf;
    }
 -  p->in = fopen(sqliterc,"rb");
 -  if( p->in ){
 +  inUse = fopen(sqliterc,"rb");
 +  if( inUse!=0 ){
-     InSource *pInSrcSave = p->pInSource;
-     int savedLineno = p->lineno;
-     InSource inSourceDivert = {inUse, 0, 0, sqliterc, p->pInSource};
++    InSource inSourceRedir
++      = INSOURCE_FILE_REDIR(inUse, sqliterc, p->pInSource);
 +    int rc;
-     p->pInSource = &inSourceDivert;
++    p->pInSource = &inSourceRedir;
      if( stdin_is_interactive ){
        utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc);
      }
 -    if( process_input(p) && bail_on_error ) exit(1);
 -    fclose(p->in);
 +    rc = process_input(p);
 +    fclose(inUse);
-     p->pInSource = pInSrcSave;
-     p->lineno = savedLineno;
++    p->pInSource = inSourceRedir.pFrom; /* 0 when called by main() */
 +    if( rc!=0 && bail_on_error ) exit(1);
    }else if( sqliterc_override!=0 ){
      utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc);
      if( bail_on_error ) exit(1);
@@@ -12189,8 -11410,8 +12350,9 @@@ static void main_init(ShellState *data
    sqlite3_config(SQLITE_CONFIG_URI, 1);
    sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
    sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
--  sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
++  sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,        "sqlite> ");
    sqlite3_snprintf(sizeof(continuePrompt), continuePrompt,"   ...> ");
++  init_std_inputs(stdin);
  }
  
  /*
@@@ -12537,7 -11753,7 +12698,7 @@@ int SQLITE_CDECL wmain(int argc, wchar_
  
    /* Process the initialization file if there is one.  If no -init option
    ** is given on the command line, look for a file named ~/.sqliterc and
--  ** try to process it.
++  ** try to process it, without any quitting or bail-on-error.
    */
    process_sqliterc(&data,zInitFile);
  
      }else if( strcmp(z,"-ascii")==0 ){
        data.mode = MODE_Ascii;
        sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Unit);
--      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Record);
++      sqlite3_snprintf(sizeof(data.rowSeparator),data.rowSeparator, SEP_Record);
      }else if( strcmp(z,"-tabs")==0 ){
        data.mode = MODE_List;
        sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Tab);
  #elif HAVE_LINENOISE
        linenoiseSetCompletionCallback(linenoise_completion);
  #endif
-       data.pInSource = 0; /* read from stdin interactively */
 -      data.in = 0;
++      data.pInSource = &termInSource; /* read from stdin interactively */
        rc = process_input(&data);
        if( zHistory ){
          shell_stifle_history(2000);
          free(zHistory);
        }
      }else{
-       data.pInSource = &inputSource; /* read from stdin without prompts */
 -      data.in = stdin;
++      data.pInSource = &stdInSource; /* read from stdin without prompts */
        rc = process_input(&data);
      }
    }
index c9367f7fc19649ea93c8a6fb63cba92a3cdde271,243da976fa41dc75bf63dd0f3f951e60a6010bc6..b42f2d534dc580489387e35fc23ef2b1f8d27488
@@@ -98,7 -98,7 +98,7 @@@ do_test shell3-2.6 
  } {0 {}}
  do_test shell3-2.7 {
    catchcmd "foo.db" "CREATE TABLE"
- } {1 {Error: Input incomplete at line 1 of "stdin"}}
 -} {1 {Error: near line 1: in prepare, incomplete input (1)}}
++} {1 {Error: Input incomplete at line 1 of "<stdin>"}}
  
  
  #----------------------------------------------------------------------------
index 17ee3b8e30c819befbf1ce15586ef42dfb00e554,9e3c58fbec6d25539409dfa4c7ca66cc26aa1d1e..a7491148efdb1ee922045adfbbe5122a99ba58da
@@@ -139,13 -138,4 +139,13 @@@ do_test shell4-3.2 
    exec $::CLI :memory: --interactive ".read t1.txt"
  } {pound: £}
  
- } {1 {Input nesting limit (25) reached, from line 1 of "t1.txt",
-  from "t1.txt" from "t1.txt" from "t1.txt" ...
 +do_test shell4-4.1 {
 +  set fd [open t1.txt wb]
 +  puts $fd ".read t1.txt"
 +  close $fd
 +  catchcmd ":memory:" ".read t1.txt"
++} {1 {Input nesting limit (25) reached, from line 1 of "t1.txt"
++ from line 1 of "t1.txt" from line 1 of "t1.txt" ...
 +ERROR: Check recursion.}}
 +
  finish_test