]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Get shell TCL extension more smoothly integrated.
authorlarrybr <larrybr@noemail.net>
Fri, 25 Mar 2022 06:46:20 +0000 (06:46 +0000)
committerlarrybr <larrybr@noemail.net>
Fri, 25 Mar 2022 06:46:20 +0000 (06:46 +0000)
FossilOrigin-Name: 29fd246fb2605a2731dd6327a354daedd8182225a97234939c79fc66e1e9e83e

ext/misc/tclshext.c.in
manifest
manifest.uuid
src/shell.c.in
src/shext_linkage.h
src/test_shellext.c

index 2ab9653684636a3d20e4d6cc046a7b115fbaf85b..2362e6bb9aa17f1934b2f6a263bcf7685ad8feaa 100644 (file)
@@ -83,8 +83,8 @@ DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(int more)){
       ".tcl ?FILES?   Run a TCL REPL or interpret files as TCL\n";
   case 1: return
       "   If FILES are provided, they name files to be read in as TCL.\n"
-      "   Otherwise, a read/evaluate/print loop is run until a lone \".\" is\n"
-      "   entered on an input line or end-of-stream is encountered.\n";
+      "   Otherwise, a read/evaluate/print loop is run until a lone \".\"\n"
+      "   is entered on an input line or end-of-stream is encountered.\n";
   default: return 0;
   }
 }
@@ -99,7 +99,7 @@ static Tcl_Interp *getInterp(TclCmd *ptc);
 static void copy_complaint(char **pzErr, Tcl_Interp *pi){
   if( pzErr ){
     Tcl_Obj *po = Tcl_GetObjResult(pi);
-    *pzErr = sqlite3_mprintf("%s", Tcl_GetStringFromObj(po,0));
+    *pzErr = sqlite3_mprintf("%s\n", Tcl_GetStringFromObj(po,0));
   }
 }
 
@@ -143,44 +143,51 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4,
       "if {$line ne \".\"} {puts {}}\n"
       "read stdin 0\n"
 #else
-      "set line {}\n"
-      "set at_end 0\n"
-      "set prompting [now_interactive]\n"
-      "while {!$at_end} {\n"
-        "if {$prompting} {\n"
-          "if {$line!=\"\"} {\n"
-            "puts -nonewline \"> \"\n"
+      "namespace eval ::REPL {\n"
+        "variable line {}\n"
+        "variable at_end 0\n"
+        "variable prompting [now_interactive]\n"
+      "}\n"
+      "while {!$::REPL::at_end} {\n"
+        "if {$::REPL::prompting} {\n"
+          "if {$::REPL::line!=\"\"} {\n"
+            "puts -nonewline \"...> \"\n"
           "} else {\n"
-            "puts -nonewline \"% \"\n"
+            "puts -nonewline \"tcl% \"\n"
           "}\n"
         "}\n"
         "flush stdout\n"
-        "set li [get_input_line]\n"
-        "if {$li eq \"\"} {\n"
-          "set at_end 1\n"
-        "} elseif {[string trimright $li] eq \".\"} {\n"
-          "if {$line ne \"\"} {\n"
+        "set ::REPL::li [get_input_line]\n"
+        "if {$::REPL::li eq \"\"} {\n"
+          "set ::REPL::at_end 1\n"
+        "} elseif {[string trimright $::REPL::li] eq \".\"} {\n"
+          "if {$::REPL::line ne \"\"} {\n"
             "throw {NONE} {incomplete input at EOF}\n"
           "}\n"
-          "set at_end 1\n"
+          "set ::REPL::at_end 1\n"
         "} else {\n" 
-          "append line $li\n"
-          "if {[string trim $line] eq \"\"} {\n"
-            "set line \"\"\n"
+          "append ::REPL::line $::REPL::li\n"
+          "if {[string trim $::REPL::line] eq \"\"} {\n"
+            "set ::REPL::line \"\"\n"
             "continue\n"
           "}\n" 
-          "if {[info complete $line]} {\n"
-            "if {[catch {uplevel #0 $line} result]} {\n"
-              "puts stderr \"Error: $result\"\n"
-            "} elseif {$result!=\"\" && $prompting} {\n"
-              "puts $result\n"
+          "if {[info complete $::REPL::line]} {\n"
+            "set ::REPL::rc [catch {uplevel #0 $::REPL::line} ::REPL::result]\n"
+            "if {$::REPL::rc == 0} {\n"
+              "if {$::REPL::result!=\"\" && $::REPL::prompting} {\n"
+                "puts $::REPL::result\n"
+              "}\n"
+            "} elseif {$::REPL::rc == 1} {\n"
+              "puts stderr \"Error: $::REPL::result\"\n"
+            "} elseif {$::REPL::rc == 2} {\n"
+              "set ::REPL::at_end 1\n"
             "}\n"
-            "set line {}\n"
+            "set ::REPL::line {}\n"
           "}\n"
         "}\n"
       "}\n"
-      "if {$prompting && $li ne \".\\n\"} {puts {}}\n"
-      "unset li line prompting at_end\n"
+      "if {$::REPL::prompting && $::REPL::li ne \".\\n\"} {puts {}}\n"
+      "namespace delete ::REPL\n"
       "read stdin 0\n"
 #endif
       ;
@@ -281,7 +288,7 @@ static int nowInteractive(void *pvSS, Tcl_Interp *interp,
 #define UNKNOWN_RENAME "::_original_unknown"
 
 /* C implementation of TCL ::unknown to delegate to dot commands */
-static int unknown_dot_delegate(void *pvSS, Tcl_Interp *interp,
+static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp,
                                 int nArgs, const char *azArgs[]){
   const char *name = (nArgs>1 && *azArgs[1]=='.')? azArgs[1]+1 : 0;
   ShellExState *psx = (ShellExState *)pvSS;
@@ -290,31 +297,27 @@ static int unknown_dot_delegate(void *pvSS, Tcl_Interp *interp,
   int ia, rc;
 
   if( name ) pmc = pExtHelpers->findMetaCommand(name, psx, &nFound);
-  if( pmc &&nFound==1 ){
+  if( pmc==(MetaCommand*)&tclcmd && nArgs==2 ){
+    /* Will not do a nested REPL, just silently semi-fake it. */
+    return TCL_OK;
+  }
+  if( pmc && nFound==1 ){
     /* Run the dot command and interpret its returns. */
-    char *zErr = 0;
-    DotCmdRC drc = pmc->pMethods->argsCheck(pmc, &zErr, nArgs-1,
-                                            (char **)azArgs+1);
-    if( drc==DCR_Ok ){
-      drc = pmc->pMethods->execute(pmc, psx, &zErr, nArgs-1,
-                                   (char **)azArgs+1);
-    }
-    assert(!(drc==DCR_Ok && zErr!=0));
+    DotCmdRC drc = pExtHelpers->runMetaCommand(pmc, (char **)azArgs+1,
+                                               nArgs-1, psx);
     if( drc==DCR_Ok ) return TCL_OK;
-    else{
-      /* ToDo: Try to indicate what went wrong as part of result. 
-       * This is deferred until some shell functionality helping
-       * with this is factored out and exposed for extensions. */
-      sqlite3_free(zErr);
+    else if( drc==DCR_Return ){
+      return TCL_RETURN;
+    }else{
+      Tcl_AppendResult(interp, "Execution of .", name, " failed.", 0);
       return TCL_ERROR;
     }
   }else{
     /* Defer to the TCL-default ::unknown command, or fail here. */
     int haveUnkCmd = (0!=Tcl_FindCommand(interp, UNKNOWN_RENAME,
                                          0, TCL_GLOBAL_ONLY));
-    Tcl_Obj **ppo;
     if( haveUnkCmd ){
-      ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*));
+      Tcl_Obj **ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*));
       if( ppo==0 ) return TCL_ERROR;
       ppo[0] = Tcl_NewStringObj(UNKNOWN_RENAME, -1);
       Tcl_IncrRefCount(ppo[0]);
@@ -394,7 +397,7 @@ int sqlite3_tclshext_init(
                           "now_interactive", nowInteractive, psx, 0);
         Tcl_Eval(tclcmd.interp, "rename unknown "UNKNOWN_RENAME);
         Tcl_CreateCommand(tclcmd.interp,
-                          "unknown", unknown_dot_delegate, psx, 0);
+                          "unknown", unknownDotDelegate, psx, 0);
         pShExtLink->eid = sqlite3_tclshext_init;
       }else{
         TclCmd_Takedown(&tclcmd);
index 6f7b09a6f61052fb2188185df7da73f1f1f597c8..b1f4be46ec718941cf634b2ff60d0667fa6ec0c6 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C TCL\sshell\sextension\sdelegates\sto\sdot\scommands
-D 2022-03-24T12:49:05.311
+C Get\sshell\sTCL\sextension\smore\ssmoothly\sintegrated.
+D 2022-03-25T06:46:20.823
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -329,7 +329,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
 F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
 F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a
-F ext/misc/tclshext.c.in 4c9e9c36877ea3a9bde62e4d4706e848fe0d56984e7ad36d5952de7f9d9cf308
+F ext/misc/tclshext.c.in d8f1a894edb7670341b8eb319686f4833f0d5545405c093d4d95b53966bbf5ff
 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
 F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@ -556,8 +556,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 4890a3cfee0bc60ff231c3a44db37968859ab0be156983dbcc0c096109832cdd
-F src/shell.c.in 840a4a72dcc39fafbcc82275babc77f928c3531cb5fe7217cb0a1596ef0a4555
-F src/shext_linkage.h 71b3600ba0e20f696fb226547e99413c67cfb27c5532701df16935e2a45c152a
+F src/shell.c.in 06593f6e93bbbcb5e58e29f1100ff1fa9e211642dc946d28ef1748be06383854
+F src/shext_linkage.h 511a218406b45240b3ad8a849a0898fac52ea64f7ad133548a9126d0f7d637cf
 F src/sqlite.h.in 5845213799feca09cd69d18ff841a85fe0df31021f46aaa1797e703e80dc1d70
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@ -606,7 +606,7 @@ F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
 F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
 F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
 F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
-F src/test_shellext.c 0d07a78ac1487532f39ac93eafff0240e861f8f3430205af62b937136b6dbd49
+F src/test_shellext.c 6cbc7cbc4c1c55a747fcc8367fb9b45fe3d2d253e1b4235c8d4c4e6bd667b5ce
 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
 F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
 F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
@@ -1950,8 +1950,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P abf0316b3f58646974ab8e4d3e68896c9fc03bdd338eb7dc7b2f5d4de7365298
-R ce874ae1862d105669b12cd18d25e5bc
+P 353943108b36cabb3b9c745aa7f4d9a42cd100b7d0444373184b45cc1715837e
+R b9914f8d3b5384fdfccd2318410fb67e
 U larrybr
-Z 4469eea4a17023c287722633d65da7c8
+Z 0021f50f48c3d55a3facb3cd03bb67af
 # Remove this line to create a well-formed Fossil manifest.
index 85e7c769770f9124875f30a3a85db51b59dd927e..6feb5de2bb088c7136b9712a0b0694f3ae826e19 100644 (file)
@@ -1 +1 @@
-353943108b36cabb3b9c745aa7f4d9a42cd100b7d0444373184b45cc1715837e
\ No newline at end of file
+29fd246fb2605a2731dd6327a354daedd8182225a97234939c79fc66e1e9e83e
\ No newline at end of file
index 566bace22bf0e8898e425ae11c40e0e1d6e2ab19..e0be6627c615744880ad787ee5897d795dc6c3f0 100644 (file)
@@ -5206,7 +5206,7 @@ static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
 #endif
 
 /*
-** Do C-language style dequoting.
+** Do C-language style escape sequence translation.
 **
 **    \a    -> alarm
 **    \b    -> backspace
@@ -7802,15 +7802,17 @@ static const char *shellStartupDir(void){
 
 static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
 static MetaCommand * findMetaCommand(const char *, ShellExState *, int *);
+static DotCmdRC runMetaCommand(MetaCommand*, char *[], int na, ShellExState*);
 
 static ExtensionHelpers extHelpers = {
-  10,
+  11,
   {
     failIfSafeMode,
     currentOutputFile,
     currentInputSource,
     strLineGet,
     findMetaCommand,
+    runMetaCommand,
     setColumnWidths,
     nowInteractive,
     shellInvokedAs,
@@ -7833,8 +7835,9 @@ static ShellExtensionAPI shellExtAPI = {
 /* This SQL function provides a way for a just-loaded shell extension to
  * obtain a ShellExtensionLink pointer from the shell core while using
  * the same sqlite3_load_extension API used for SQLite extensions.
- * This serves as an alternative to deriving the same pointer from
- * the pzErr argument passed into that API.
+ *
+ * (It is also useful for debugging a shell extension, as a breakpoint
+ * on it will be hit soon after loading and before real work is done.)
  */
 static void shell_linkage(
   sqlite3_context *context,
@@ -7842,17 +7845,25 @@ static void shell_linkage(
   sqlite3_value **argv
 ){
   int linkKind = 0;
+  void *pv;
   if( argc>0 ){
     linkKind = sqlite3_value_int(argv[0]);
   }
   switch (linkKind){
   case 0:
-    sqlite3_result_pointer(context, sqlite3_user_data(context),
-                           SHELLEXT_API_POINTERS, 0);
+    pv = sqlite3_user_data(context);
+    break;
+  case 1:
+    pv = &extHelpers;
+    break;
+  case 2:
+    pv = &shellExtAPI;
     break;
   default:
-    sqlite3_result_null(context);
+    pv = 0;
   }
+  if( pv==0 ) sqlite3_result_null(context);
+  else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0);
 }
 
 #ifndef SHELL_DB_FILE
@@ -8010,6 +8021,8 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
   ScriptHooks shSave = psi->scripting;
   ExtensionId siSave = psi->scriptXid;
   int rc;
+
+  if( pzErr ) *pzErr = 0;
   if( psx->dbShell==0 ){
     rc = begin_db_dispatch(psx);
     if( rc!=SQLITE_OK ) return rc;
@@ -8042,7 +8055,13 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
     psi->scriptXid = siSave;
   }
   psi->ixExtPending = 0;
-  return rc!=SQLITE_OK;
+  if( rc!=SQLITE_OK ){
+    if( rc==SQLITE_MISUSE && pzErr!=0 ){
+      *pzErr = sqlite3_mprintf("extension id mismatch %z\n", *pzErr);
+    }
+    rc = SQLITE_ERROR;
+  }
+  return rc;
 }
 #endif
 
@@ -13129,28 +13148,6 @@ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx,
   }
 }
 
-/*****************
-** Command dispatcher
-** After successful command lookup and (simple) argument checking,
-** invoke the found MetaCommand with the input arguments (except
-** that azArg[0] is replaced with the properly spelled command name.)
-** The return is either a dispatch error or whatever the dispatched
-** MetaCommand returns.
-*/
-static DotCmdRC dispatchCommand(char *azArg[], int nArg,
-                                ShellExState *psx, char **pzErr){
-  const char *cmdName = azArg[0];
-  int nFound = 0;
-  DotCmdRC argsCheck;
-  MetaCommand *pMC = findMetaCommand(cmdName, psx, &nFound);
-  if( 0==pMC || nFound>1 ) return (nFound>1)? DCR_Ambiguous: DCR_Unknown;
-  argsCheck = pMC->pMethods->argsCheck(pMC, pzErr, nArg, azArg);
-  if( argsCheck!=DCR_Ok ) return argsCheck;
-  /* Replace any user-shortened command name with its whole name. */
-  azArg[0] = (char *)(pMC->pMethods->name(pMC));
-  return pMC->pMethods->execute(pMC, psx, pzErr, nArg, azArg);
-}
-
 /*
 ** Output primary (single-line) help for a known command.
 */
@@ -13243,84 +13240,51 @@ static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){
   return npm;
 }
 
-/*
-** If an input line or line group begins with "." then invoke this routine
-** to process that line.
-**
-** Return sanitized DotCmdRC values, with invocation error codes
-** translated to DCR_Error, so that only these 8 returns are possible:
-** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error.
-*/
-static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
-  int h = 1;
-  int nArg = 0;
-  char *azArg[52];
-  char *zErr = 0;
-  DotCmdRC dispatchResult;
-#if SHELL_VARIABLE_EXPANSION
-  int ncLineIn = strlen30(zLine);
-  u8 bExpVars = SHEXT_VAREXP(ISS(psx));
-#endif
-
+/* Perform preparation needed prior to actually running any dot command. */
+static void command_prep(ShellInState *psi){
+  clearTempFile(psi);
 #ifndef SQLITE_OMIT_VIRTUALTABLE
-  if( ISS(psx)->expert.pExpert ){
-    expertFinish(ISS(psx), 1, 0);
+  if( psi->expert.pExpert ){
+    expertFinish(psi, 1, 0);
   }
 #endif
+}
 
-  /* Parse the input line into tokens.
-  */
-  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
-    while( IsSpace(zLine[h]) ){ h++; }
-    if( zLine[h]==0 ) break;
-    if( zLine[h]=='\'' || zLine[h]=='"' ){
-      int delim = zLine[h++];
-      azArg[nArg++] = &zLine[h];
-      while( zLine[h] && zLine[h]!=delim ){
-        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
-        h++;
-      }
-      if( zLine[h]==delim ){
-        zLine[h++] = 0;
-      }
-      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
-    }else{
-      azArg[nArg++] = &zLine[h];
-      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
-      if( zLine[h] ) zLine[h++] = 0;
-      resolve_backslashes(azArg[nArg-1]);
-    }
+/* Perform post-execution actions needed after running any dot command. */
+static void command_post(ShellInState *psi){
+  if( psi->outCount ){
+    psi->outCount--;
+    if( psi->outCount==0 ) output_reset(psi);
   }
-  azArg[nArg] = 0;
-
-  /* Process the input line.
-  */
-  if( nArg==0 ) return DCR_Ok; /* no tokens, no error, done */
-
-  clearTempFile(ISS(psx));
+  updateSafeMode(psi);
+}
 
-  dispatchResult = dispatchCommand(azArg, nArg, psx, &zErr);
+/* Issue errors per returned DotCmdRC and error message, handle certain
+ * exceptional returns, and translate return to the sanitized first 8.
+ */
+static DotCmdRC meta_command_errors(char *zErr, char *azArg[], int nArg,
+                                    DotCmdRC dcr, ShellExState *psx){
   if( psx->shellAbruptExit!=0 ){
-    if( psx->shellAbruptExit>0x1ff ) dispatchResult = DCR_AbortError;
-    else dispatchResult = DCR_Exit | (dispatchResult&DCR_Error);
+    if( psx->shellAbruptExit>0x1ff ) dcr = DCR_AbortError;
+    else dcr = DCR_Exit | (dcr & DCR_Error);
   }
-  if( dispatchResult==DCR_CmdErred ){
+  if( dcr==DCR_CmdErred ){
     /* Error message(s) already emitted. Just translate to execute error. */
-    dispatchResult = DCR_Error;
-  }else if( dispatchResult==DCR_SayUsage ){
+    dcr = DCR_Error;
+  }else if( dcr==DCR_SayUsage ){
     if( zErr ){
       utf8_printf(STD_ERR, "%s", zErr);
     }else{
       utf8_printf(STD_ERR, "Usage:\n");
       showPrimaryHelp(STD_ERR, azArg[0], psx);
     }
-    dispatchResult = DCR_Error;
-  }else if( dispatchResult > DCR_AbortError ){
+    dcr = DCR_Error;
+  }else if( dcr > DCR_AbortError ){
     /* Handle invocation errors. */
-    int ia = dispatchResult & DCR_ArgIxMask;
+    int ia = dcr & DCR_ArgIxMask;
     const char *pArg1st = (ia>=nArg)? "" : azArg[ia];
     const char *pArg2nd = 0;
-    int ec = dispatchResult & ~DCR_ArgIxMask;
+    int ec = dcr & ~DCR_ArgIxMask;
     int unknownCmd = 0;
     const char *z = 0;
     switch( ec ){
@@ -13366,10 +13330,11 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
       z = "?";
       break;
     }
-    if( zErr==0 ){
+    if( !zErr ){
       if( z!=0 ){
-        zErr = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd));
-        utf8_printf(STD_ERR, "%s", zErr);
+        char *ze = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd));
+        utf8_printf(STD_ERR, "%s", ze);
+        sqlite3_free(ze);
       }
     }else{
       const char *fmt
@@ -13384,17 +13349,17 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
         showPrimaryHelp(STD_ERR, azArg[0], psx);
       }
     }
-    /* If the shell DB is messed up, at least .quit will be doable. */
+    /* If the shell DB becomes messed up, at least .quit will be doable. */
     if( unknownCmd && psx->dbShell!=0
         && sqlite3_strnicmp(azArg[0],"quit",strlen30(azArg[0]))==0 ){
-      dispatchResult = DCR_Return;
+      dcr = DCR_Return;
     }else{
-      dispatchResult = DCR_Error;
+      dcr = DCR_Error;
     }
   }else{
     /* Handle execution errors. */
-    if( dispatchResult==DCR_AbortError ){
-      if( zErr!=0 ){
+    if( dcr==DCR_AbortError ){
+      if( zErr ){
         utf8_printf(STD_ERR, "Error: \".%s\" may not %s in -safe mode\n",
                     azArg[0], zErr);
       }else {
@@ -13403,10 +13368,10 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
       }
       psx->shellAbruptExit = 0x203;
     }else{
-      int error = dispatchResult & DCR_Error;
-      int action = dispatchResult & ~DCR_Error;
+      int error = dcr & DCR_Error;
+      int action = dcr & ~DCR_Error;
       if( error ){
-        if( zErr!=0 ){
+        if( zErr ){
           const char *fmt
             = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
           utf8_printf(STD_ERR, fmt, zErr);
@@ -13415,12 +13380,104 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
       }
     }
   }
-  if( zErr ) sqlite3_free(zErr);
-  if( ISS(psx)->outCount ){
-    ISS(psx)->outCount--;
-    if( ISS(psx)->outCount==0 ) output_reset(ISS(psx));
+  return dcr;
+}
+
+/* Argument-check and execute a found MetaCommand, wrapping execution
+ * with command_{prep,post}(...), and issue errors as made evident.
+ * Return one of the "Post-execute action and success/error status"
+ * codes from the DotCmdRC enum.
+ *
+ * Note that this function is exposed for shell extensions to use.
+ *
+ * This should be called for "top-level" dot command execution only.
+ * Should an extension wrap or use a MetaCommand object to effect its
+ * own functionality, that object's execute() method should be called
+ * directly, without going through this function.
+ */
+static DotCmdRC runMetaCommand(MetaCommand *pmc, char *azArg[], int nArg,
+                               ShellExState *psx){
+  char *arg0 = azArg[0];
+  char *zErr = 0;
+  DotCmdRC dcr = pmc->pMethods->argsCheck(pmc, &zErr, nArg, azArg);
+
+  command_prep(ISS(psx));
+  azArg[0] = (char *)(pmc->pMethods->name(pmc));
+  if( dcr==DCR_Ok ){
+    dcr = pmc->pMethods->execute(pmc, psx, &zErr, nArg, azArg);
+  }
+  if( dcr!=DCR_Ok ){
+    dcr = meta_command_errors(zErr, azArg, nArg, dcr, psx);
+  }
+  azArg[0] = arg0;
+  sqlite3_free(zErr);
+  command_post(ISS(psx));
+  return dcr;
+}
+
+/*
+** If an input line or line group begins with "." then invoke this routine
+** to process that line.
+**
+** Returns sanitized DotCmdRC values, with invocation error codes
+** translated to DCR_Error, so that only these 8 returns are possible:
+** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error.
+**
+** Any applicable error messages are issued along with output messages.
+*/
+static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
+  int h = 1; /* Passing over leading '.' */
+  int nArg = 0;
+  char *azArg[52];
+  DotCmdRC dcr = DCR_Ok;
+#if SHELL_VARIABLE_EXPANSION
+  int ncLineIn = strlen30(zLine);
+  u8 bExpVars = SHEXT_VAREXP(ISS(psx));
+#endif
+
+  /* Parse the input line into tokens which are 0-terminated and left in-place.
+  */
+  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
+    /* Future: Complain if this fixed argument count limit is hit. */
+    while( IsSpace(zLine[h]) ){ h++; }
+    if( zLine[h]==0 ) break;
+    if( zLine[h]=='\'' || zLine[h]=='"' ){
+      int delim = zLine[h++];
+      azArg[nArg++] = &zLine[h];
+      while( zLine[h] && zLine[h]!=delim ){
+        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
+        h++;
+      }
+      if( zLine[h]==delim ){
+        zLine[h++] = 0;
+      }
+      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
+    }else{
+      azArg[nArg++] = &zLine[h];
+      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
+      if( zLine[h] ) zLine[h++] = 0;
+      resolve_backslashes(azArg[nArg-1]);
+    }
+  }
+  azArg[nArg] = 0; /* No code here relies on this, but some extension might. */
+
+  /* Process the input line. If it was empty, do nothing and declare success.
+   * Note that "empty" includes a leading '.' followed by nothing else.
+   */
+  if( nArg>0 ){
+    int nFound;
+    MetaCommand *pmc = findMetaCommand(azArg[0], psx, &nFound);
+    if( pmc==0 || nFound>1 ){
+      dcr = (nFound>1)? DCR_Ambiguous: DCR_Unknown;
+      /* Issue error for an unknown or inadequately specified dot command. */
+      dcr = meta_command_errors(0, azArg, nArg, dcr, psx);
+    }
+    else{
+      /* Run found command and issue or handle any errors it may report. */
+      dcr = runMetaCommand(pmc, azArg, nArg, psx);
+    }
   }
-  updateSafeMode(ISS(psx));
+
 #if SHELL_VARIABLE_EXPANSION
   if( bExpVars ){
     /* Free any arguments that are allocated rather than tokenized in place. */
@@ -13431,7 +13488,7 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
     }
   }
 #endif
-  return dispatchResult;
+  return dcr;
 }
 
 /* Line scan result and intermediate states (supporting scan resumption)
index eaa8054b7db598013642c1eff7d7564ead6f541e..7f4c24b94f160f38c234ac249048a0126ec98814 100644 (file)
@@ -230,6 +230,8 @@ typedef struct ExtensionHelpers {
       char * (*strLineGet)(char *zBuf, int ncMax, struct InSource *pInSrc);
       MetaCommand * (*findMetaCommand)(const char *cmdName, ShellExState *p,
                                        /* out */ int *pnFound);
+      DotCmdRC (*runMetaCommand)(MetaCommand *pmc, char *azArg[], int nArg,
+                                 ShellExState *psx);
       void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths);
       int (*nowInteractive)(ShellExState *p);
       const char * (*shellInvokedAs)(void);
index 981e86512036ed84c974dc2fc210f972abaf32f7..5be936720ee9637a210e76b048c29eb6a0bd6ff6 100644 (file)
@@ -121,6 +121,7 @@ int sqlite3_testshellext_init(
     batty.pPrint = pExtHelpers->findMetaCommand("print", psx, &rc);
     rc = pShExtApi->registerMetaCommand(psx, sqlite3_testshellext_init,pmc);
     if( rc!=0 ) ++nErr;
+    pShExtLink->eid = sqlite3_testshellext_init;
   }
   else{
     printf("No ShellExtensionLink pointer or registration API.\n");