]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
For TCL extension: Add Tk and means to run it, optionally. Implement ScriptSupport...
authorlarrybr <larrybr@noemail.net>
Thu, 31 Mar 2022 03:45:52 +0000 (03:45 +0000)
committerlarrybr <larrybr@noemail.net>
Thu, 31 Mar 2022 03:45:52 +0000 (03:45 +0000)
For shell: Get scripting support thru an object interface like the others. Rename OutMode* to Export*.
For both: Provide a way to pass arguments to an extension upon load and get them into TCL's argv.

FossilOrigin-Name: d2446e50805cc3a46f9bcafccf017da60b0cf9fcd27695c214d71d1c5141d03f

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

index 55558167614418312b235d0568b0c1f0eed9baaa..9e237fdc2a9f601a8f8dab4030c197ba6ec5881c 100644 (file)
      gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 tclshext.c \
        -o tclshext.so -ltcl8.6
 ** Later TCL versions can be used if desired.
-**
-** This extension adds these features to the host shell:
-** 1. TCL scripting support is added.
-** 2. TCL commands added: udb shdb now_interactive get_tcl_group ..
-** 3. The .tcl and .unknown dot commands are added.
-**
-** TCL scripting support is added with a ShellExtensionAPI hookScripting()
-** call in the manner documented for it and the ScriptHooks struct. This
-** support lasts until the extension destructor is called. Until then,
-** shell input groups beginning with ".." are treated as TCL input, in
-** various ways, specifically: (1) When a bare ".." is entered, a TCL
-** REPL loop is run until the end of input is seen; (2) When "..X ..."
-** is entered, (where "X" is a dot-command name), the X dot command will
-** be run in its normal fashion, but its arguments will be collected
-** according to TCL parsing rules and will be expanded as is usual for
-** TCL commands; and (3) when ".. T ..." is entered, (where "T" is a TCL
-** command name), that TCL command and its arguments will be collected
-** and expanded according to TCL parsing rules, and the command will be
-** run in the TCL execution environment (in the root namespace), but the
-** shell execution environment remains in effect afterward. (Note that
-** cases 2 and 3 differ in whether space occurs after the leading "..".)
-**
-** The phrase "end of input" means either: end-of-file is seen on a file,
-** pipe or string stream input, or a lone "." on the first and only line
-** of an input line group is seen. This convention is useful in scripting
-** when it is expedient to switch execution environments from within the
-** same input stream. For example:
+  "TCL scripting support is added with a registerScripting() call in the\n"
+  "ShellExtensionAPI, as documented for ScriptingSupport interface. This\n"
+  "support lasts until the scripting object destructor is called. Until\n"
+*/
+static const char * const zTclHelp =
+  "This extension adds these features to the host shell:\n"
+  " 1. TCL scripting support is added.\n"
+  " 2. TCL commands are added: udb shdb now_interactive get_tcl_group ..\n"
+  " 3. The .tcl and .unknown dot commands are added.\n"
+  " 4. If built with Tk capability, a run_gui TCL command may be added if\n"
+  "  the extension is loaded by the shell via .load ... -shext -tk . Any\n"
+  "  other arguments beyond -shext are in TCL's argv variable.\n"
+  "Operation:\n"
+  " Shell input groups beginning with \"..\" are treated as TCL input, in\n"
+  " these ways: (1) When a bare \"..\" is entered, a TCL REPL loop is run\n"
+  " until the end of input is seen; (2) When \"..D ...\" is entered, (where\n"
+  " \"D\" is a dot-command name), the D dot command will be run in its normal\n"
+  " fashion, but its arguments will be collected according to TCL parsing\n"
+  " rules then expanded as usual for TCL commands; and (3) when \".. T ...\"\n"
+  " is entered, (where \"T\" is a TCL command name), that TCL command and its\n"
+  " arguments will be collected and expanded according to TCL parsing rules,\n"
+  " then run in the TCL execution environment (in its global namespace), but\n"
+  " the shell execution environment remains in effect afterward.\n"
+  "\n"
+  " Note that cases 2 and 3 differ in having space after the leading \"..\".\n"
+  "\n"
+  " The phrase \"end of input\" means either: end-of-file is seen on a file,\n"
+  " pipe or string stream input, or a lone \".\" on the first and only line\n"
+  " of an input line group is seen. This convention is useful in scripting\n"
+  " when it is expedient to switch execution environments from within the\n"
+  " same input stream. This could be input piped in from another process.\n"
+  "\n"
+  ;
+/*
+** For example:
      # From shell, enter TCL REPL:
      ..
      # Initialize some variables and insert into the DB:
@@ -81,7 +89,7 @@
 **
 ** The added .tcl dot command may be used to enter a TCL REPL, or with
 ** arguments, it will read files as TCL. (This is somewhat extraneous,
-** as the same can be done with TCL commands. This is more easily done
+** as the same can be done with TCL commands, but it is more easily done
 ** from the shell invocation, and the .tcl command's integration into
 ** the .help facility provides a way for users to get help for "..".)
 **
@@ -104,6 +112,9 @@ SQLITE_EXTENSION_INIT1;
 #undef SQLITE_AMALGAMATION
 #undef TCLSH
 #include <tclOO.h>
+#ifndef SHELL_OMIT_TK
+#include <tk.h> /* Only used if option -tk passed during load. */
+#endif
 INCLUDE tclsqlite.c
 
 #if defined(_WIN32) || defined(WIN32)
@@ -131,13 +142,14 @@ static void Tcl_TakeDown(void *pv){
   if( --interpKeep.nRefs==0 ){
     if( interpKeep.pInterp ){
       Tcl_DeleteInterp(interpKeep.pInterp);
+      Tcl_Release(interpKeep.pInterp);
       interpKeep.pInterp = 0;
       Tcl_Finalize();
     }
   }
 }
 
-static int Tcl_BringUp(char **pzErrMsg){
+static int Tcl_BringUp(int *pWithTk, char **pzErrMsg){
   if( ++interpKeep.nRefs==1 ){
     const char *zShellName = pExtHelpers->shellInvokedAs();
     const char *zShellDir = pExtHelpers->shellStartupDir();
@@ -152,6 +164,7 @@ static int Tcl_BringUp(char **pzErrMsg){
     interpKeep.pInterp = Tcl_CreateInterp();
     Tcl_SetSystemEncoding(interpKeep.pInterp, "utf-8");
     Sqlite3_Init(interpKeep.pInterp);
+    Tcl_Preserve(interpKeep.pInterp);
     if( 0==Tcl_OOInitStubs(interpKeep.pInterp) ){
       *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n");
       Tcl_TakeDown(&interpKeep);
@@ -161,20 +174,137 @@ static int Tcl_BringUp(char **pzErrMsg){
       *pzErrMsg = sqlite3_mprintf("Tcl interpreter startup failed.\n");
       Tcl_TakeDown(&interpKeep);
       return SQLITE_ERROR;
+    }else if( *pWithTk ){
+#ifndef SHELL_OMIT_TK
+      if( TCL_OK!=Tk_Init(interpKeep.pInterp) ){
+        fprintf(stderr, "Could not load/initialize Tk."
+                " (non-fatal, extension is loaded.)\n");
+        *pWithTk = 0;
+      }
+#else
+      fprintf(stderr, "This tclshext extension has no Tk support.\n");
+#endif
     }
   }
   return (interpKeep.pInterp!=0)? SQLITE_OK : SQLITE_ERROR;
 }
 
-/* These DERIVED_METHOD(...) macro calls' arguments were copied and
- * pasted from the MetaCommand interface declaration in shext_linkage.h
+static void copy_complaint(char **pzErr, Tcl_Interp *pi);
+static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg);
+
+/* Following DERIVED_METHOD(...) macro calls' arguments were copied and
+ * pasted from the respective interface declarations in shext_linkage.h
  */
+
+/* This is in the interface for anouncing what was just provided. */
+DERIVED_METHOD(const char *, name, ScriptSupport,TclSS, 0,()){
+  (void)(pThis);
+  return "TclTk";
+}
+
+/* Provide help for users of this scripting implementation. */
+DERIVED_METHOD(const char *, help, ScriptSupport,TclSS, 1,( int more )){
+  (void)(pThis);
+  switch( more ){
+  case 0:
+    return "Provides TCL scripting support for SQLite extensible shell.\n";
+  case 1: return zTclHelp; /* ToDo: Rewrite this help. */
+  }
+  return 0;
+}
+
+/* Not doing this yet. */
+DERIVED_METHOD(int,  configure, ScriptSupport,TclSS,
+               4,( ShellExState *pSES, char **pzErr,
+                   int numArgs, char *azArgs[] )){
+  (void)(pThis);
+  return 0;
+}
+
+/* Say line is script lead-in iff its first dark is "..".
+ * In combination with dot commands also being TCL commands and the
+ * special handling in the next three functions, this effects what is
+ * promised in this file's header text and by .tcl's help text.
+ */
+DERIVED_METHOD(int, isScriptLeader, ScriptSupport,TclSS,
+               1,( const char *zScriptLeadingLine )){
+  char c;
+  (void)(pThis);
+  while( (c=*zScriptLeadingLine++) && (c==' '||c=='\t') ) {}
+  return (c=='.' && *zScriptLeadingLine=='.');
+}
+
+/* Say line group is complete if it passes muster as ready-to-go TCL. */
+DERIVED_METHOD(int, scriptIsComplete, ScriptSupport,TclSS,
+               2,( const char *zScript, char **pzWhyNot )){
+  (void)(pThis);
+  (void)(pzWhyNot);
+  return Tcl_CommandComplete(zScript);
+}
+
+/* As we rely on Tcl_CommandComplete(), no resumable scanning is done. */
+DERIVED_METHOD(void, resetCompletionScan, ScriptSupport,TclSS, 0,()){
+  (void)(pThis);
+}
+
+/* Run as TCL after some jiggering with the leading dots. */
+DERIVED_METHOD(DotCmdRC, runScript, ScriptSupport,TclSS,
+               3,( const char *zScript, ShellExState *psx, char **pzErrMsg )){
+  char c;
+  Tcl_Interp *interp = getInterp();
+  (void)(pThis);
+  (void)(psx);
+
+  if( interp==0 ) return DCR_Error;
+  while( (c=*zScript++) && (c==' '||c=='\t') ) {}
+  if( c=='.' &&  *zScript++=='.' ){
+    int rc, nc = strlen30(zScript);
+    /* At this point, *zScript should fall into one of these cases: */
+    switch( *zScript ){
+    case '.':
+      /* Three dots, assume user meant to run a dot command. */
+    one_shot_tcl:
+      rc = Tcl_EvalEx(interp, zScript, /* needs no adjustment */
+                      nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
+      if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp());
+      break;
+    case ' ': case '\t':
+      /* Two dots then whitespace, it's a TCL one-shot command. */
+      while( (c = *zScript)!=0 && c==' ' || c=='\t' ) ++zScript, --nc;
+      if ( c!=0 ) goto one_shot_tcl;
+      /* It looks like "..", so run it that way via fall-thru. */
+    case 0:
+      /* Two lone dots, user wants to run TCL REPL. */
+      return runTclREPL(interp, pzErrMsg);
+    default:
+      /* Two dots then dark not dot, may be a dot command. */
+      if( *zScript>='a' && *zScript<='z' ){
+        --zScript, ++nc;
+        goto one_shot_tcl;
+      }
+      /* It cannot be a dot command; a user tip is apparently needed. */
+      if( pzErrMsg ){
+        *pzErrMsg = sqlite3_mprintf("Nothing valid begins with ..%c\n"
+                                    "Run .help tcl to see what is valid.\n",
+                                    *zScript);
+        return DCR_SayUsage;
+      }
+    }
+    return DCR_Ok|(rc!=TCL_OK);
+  }
+  return DCR_Error; /* Silent error because it should not happen. */
+}
+
 DERIVED_METHOD(void, destruct, MetaCommand,TclCmd, 0, ()){
+  /* Nothing to do, instance data is static. */
   (void)(pThis);
 }
 DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ()){
   (void)(pThis);
 }
+DERIVED_METHOD(void, destruct, ScriptSupport,TclSS, 0, ()){
+  (void)(pThis);
+}
 
 DERIVED_METHOD(const char *, name, MetaCommand,TclCmd, 0,()){
   return "tcl";
@@ -440,9 +570,11 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,UnkCmd, 4,
   }
 }
 
-/* Define MetaCommand v-tables initialized to reference above methods. */
+/* Define MetaCommand v-tables initialized to reference most above methods. */
 MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
 MetaCommand_IMPLEMENT_VTABLE(UnkCmd, unkcmd_methods);
+/* Define ScriptSupport v-table initialized to reference the others. */
+ScriptSupport_IMPLEMENT_VTABLE(TclSS, tclss_methods);
 
 /* Static instances are used because that suffices. */
 INSTANCE_BEGIN(TclCmd);
@@ -455,70 +587,11 @@ INSTANCE_BEGIN(UnkCmd);
 INSTANCE_END(UnkCmd) unkcmd = {
   &unkcmd_methods
 };
-
-/* Say line is script lead-in iff its first dark is "..".
- * In combination with dot commands also being TCL commands and the
- * special handling in the next three functions, this effects what is
- * promised in this file's header text and by .tcl's help text.
- */
-static int tclIsScriptLead(void *pvState, const char *zLineLead){
-  char c;
-  (void)(pvState);
-  while( (c=*zLineLead++) && (c==' '||c=='\t') ) {}
-  return (c=='.' && *zLineLead=='.');
-}
-
-/* Say line group is complete if it passes muster as ready-to-go TCL. */
-static int tclIsComplete(void *pvState, const char *zScript){
-  (void)(pvState);
-  return Tcl_CommandComplete(zScript);
-}
-
-/* Run as TCL after some jiggering with the leading dots. */
-static DotCmdRC tclRunScript(void *pvState, const char *zScript,
-                             ShellExState *p, char **pzErrMsg){
-  char c;
-  struct InterpManage *pim = (struct InterpManage *)pvState;
-  Tcl_Interp *interp = pim->pInterp;
-  if( interp==0 ) return DCR_Error;
-  while( (c=*zScript++) && (c==' '||c=='\t') ) {}
-  if( c=='.' &&  *zScript++=='.' ){
-    int rc, nc = strlen30(zScript);
-    /* At this point, *zScript should fall into one of these cases: */
-    switch( *zScript ){
-    case '.':
-      /* Three dots, assume user meant to run a dot command. */
-    one_shot_tcl:
-      rc = Tcl_EvalEx(interp, zScript, /* needs no adjustment */
-                      nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
-      if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp());
-      break;
-    case ' ': case '\t':
-      /* Two dots then whitespace, it's a TCL one-shot command. */
-      while( (c = *zScript)!=0 && c==' ' || c=='\t' ) ++zScript, --nc;
-      if ( c!=0 ) goto one_shot_tcl;
-      /* It looks like "..", so run it that way via fall-thru. */
-    case 0:
-      /* Two lone dots, user wants to run TCL REPL. */
-      return runTclREPL(interp, pzErrMsg);
-    default:
-      /* Two dots then dark not dot, may be a dot command. */
-      if( *zScript>='a' && *zScript<='z' ){
-        --zScript, ++nc;
-        goto one_shot_tcl;
-      }
-      /* It cannot be a dot command; a user tip is apparently needed. */
-      if( pzErrMsg ){
-        *pzErrMsg = sqlite3_mprintf("Nothing valid begins with ..%c\n"
-                                    "Run .help tcl to see what is valid.\n",
-                                    *zScript);
-        return DCR_SayUsage;
-      }
-    }
-    return DCR_Ok|(rc!=TCL_OK);
-  }
-  return DCR_Error; /* Silent error because it should not happen. */
-}
+INSTANCE_BEGIN(TclSS);
+  /* no instance data */
+INSTANCE_END(TclSS) tclss = {
+  &tclss_methods
+};
 
 #define GETLINE_MAXLEN 1000
 
@@ -613,6 +686,16 @@ static int nowInteractive(void *pvSS, Tcl_Interp *interp,
   }
 }
 
+#ifndef SHELL_OMIT_TK
+static int runTkGUI(void *pvSS, Tcl_Interp *interp,
+                    int nArgs, const char *azArgs[]){
+  ShellExState *psx = (ShellExState *)pvSS;
+  /* This runs without looking at stdin. So it cannot be a REPL, yet.
+   * Unless user has created something for it to do, it does nothing. */
+  Tk_MainLoop();
+}
+#endif
+
 #define UNKNOWN_RENAME "::_original_unknown"
 
 /* C implementation of TCL ::unknown to (maybe) delegate to dot commands */
@@ -923,8 +1006,12 @@ int sqlite3_tclshext_init(
   ShellExtensionLink *pShExtLink;
   SQLITE_EXTENSION_INIT2(pApi);
   pShExtLink = shext_link(db);
-  if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){
+  if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=5 ){
     ShellExState *psx = pShExtLink->pSXS;
+    Tcl_Obj *targv = Tcl_NewListObj(0, NULL);
+    const char *zAppName = "tclshext";
+    int tnarg = 0;
+    int ldTk = 0;
     int rc = 0;
 
     pShExtApi = & pShExtLink->pShellExtensionAPI->api.named;
@@ -933,21 +1020,31 @@ int sqlite3_tclshext_init(
       *pzErrMsg = sqlite3_mprintf("Shell version mismatch");
       return SQLITE_ERROR;
     }
+    if( pShExtLink->nLoadArgs>0 ){
+      int ila;
+      for( ila=0; ila<pShExtLink->nLoadArgs; ++ila ){
+        const char *zA = pShExtLink->azLoadArgs[ila];
+        if( strcmp(zA,"-tk")==0 ) ldTk = 1;
+        else {
+          /* Collect args not affecting init into the argv list. */
+          Tcl_ListObjAppendElement(NULL, targv, Tcl_NewStringObj(zA, -1));
+          ++tnarg;
+        }
+      }
+    }
     rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init,
                                         (MetaCommand *)&unkcmd);
     rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init,
                                         (MetaCommand *)&tclcmd);
-    if( rc==SQLITE_OK && (rc = Tcl_BringUp(pzErrMsg))==SQLITE_OK ){
+    if( rc==SQLITE_OK && (rc = Tcl_BringUp(&ldTk, pzErrMsg))==SQLITE_OK ){
       Tcl_Interp *interp = getInterp();
-      ScriptHooks sh = {
-        &interpKeep, tclIsScriptLead, tclIsComplete, tclRunScript
-      };
       if( TCL_OK==userDbInit(interp, psx) ){
         UserDb *pudb = udbCreate(interp, psx);
         pShExtLink->extensionDestruct = (void (*)(void*))udbCleanup;
         pShExtLink->pvExtensionObject = pudb;
       }
-      pShExtApi->hookScripting(psx, sqlite3_tclshext_init, &sh);
+      pShExtApi->registerScripting(psx, sqlite3_tclshext_init,
+                                   (ScriptSupport *)&tclss);
 #if TCL_REPL==1 || TCL_REPL==2
       Tcl_CreateCommand(interp, "get_input_line", getInputLine, psx, 0);
 #endif
@@ -962,6 +1059,19 @@ int sqlite3_tclshext_init(
       /* Define this proc so that ".." either gets to the TCL REPL loop
        * or does nothing (if already in it), as a user convenience. */
       Tcl_Eval(interp, "proc .. {} {}");
+#ifndef SHELL_OMIT_TK
+      if( ldTk ){
+        /* Create a command which wraps Tk_MainLoop(). It runs a GUI event
+         * loop, so does not return until all of its Tk windows are closed. */
+        Tcl_CreateCommand(interp, "run_gui", runTkGUI, psx, 0);
+        zAppName = "tclshext_tk";
+      }
+#endif
+      Tcl_SetVar2Ex(interp, "argv0", NULL,
+                    Tcl_NewStringObj(zAppName,-1), TCL_GLOBAL_ONLY);
+      Tcl_SetVar2Ex(interp, "argc", NULL,
+                    Tcl_NewIntObj(tnarg), TCL_GLOBAL_ONLY);
+      Tcl_SetVar2Ex(interp, "argv", NULL, targv, TCL_GLOBAL_ONLY);
       pShExtLink->eid = sqlite3_tclshext_init;
     }
     if( rc==SQLITE_OK ){
index 0ede31bc2a4874ae4fccd458b3747b16acb40f42..bd9a1e797e212658273f26ef2e7e7a0f5f66621b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C For\sTCL\sextension:\n\s\sCleanup\sTCL\slibrary\sinteraction\sand\sinterpreter\smanagement.\n\s\sGroup\scode\sinto\smore\spurpose\soriented\sfunctions.\n\s\sAdd\s.unknown\sdot\scommand,\sdelegating\sto\sTCL\scommands\swith\s"dot"\snames.\n\s\sDrop\splanned\s.eval\snew\sdot\scommand.\nFor\sshell:\n\s\sImplement\sundocumented\s.unknown\sdot\scommand,\sdoing\slittle.\n\s\sMake\sdispatcher\scall\s.unknown's\simplementation\sfor\sunknown\sdot\scommand.\n\s\sFix\sa\scommand\slookup\sbug\sexposed\sby\sabove\schange\sto\sunknown\shandling.\n\s\sAdd\s.eval\sdot\scommand.\n\s\sAdd\soptions\sto\s.x\scommand\saimed\sat\scontrol\sof\serrors\sin\scommand\ssequences.\s(a\sWIP)\n\s\sMake\sdispatcher\sreport\sambiguous\sdot\scommand\slookups,\seven\swith\sno\sextension.\n
-D 2022-03-29T21:11:30.607
+C For\sTCL\sextension:\sAdd\sTk\sand\smeans\sto\srun\sit,\soptionally.\sImplement\sScriptSupport\sinterface.\nFor\sshell:\sGet\sscripting\ssupport\sthru\san\sobject\sinterface\slike\sthe\sothers.\sRename\sOutMode*\sto\sExport*.\nFor\sboth:\sProvide\sa\sway\sto\spass\sarguments\sto\san\sextension\supon\sload\sand\sget\sthem\sinto\sTCL's\sargv.\n
+D 2022-03-31T03:45:52.869
 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 f3da6a8ff02e335a021ac16d0164e7dc4d4d38691811e37419ed07289e8b181a
+F ext/misc/tclshext.c.in 50eb7c99d8b9c4e7b5f1d066cb8d36400b7b250f18107ba1282f39314c590c14
 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 c366c05e48e836ea04f8ecefb9c1225745dc250c3f01bdb39e9cbb0dc25e3610
-F src/shell.c.in d0e2c2417ed895d40af402eea3a2a38d462c6b17a654dc8e33c417a9ff355d29 x
-F src/shext_linkage.h c70f95dd0738c2cd8452ab5c47c245d7fa3b99ec26cd160c67dfb4489d5dacf9
+F src/shell.c.in a0f9dc4a772ac1b9737936b69b5931d4087ccc3711bb031cc42742f8dea7332a x
+F src/shext_linkage.h ae26717af53bee705fcba3c0d755dc59f7a55aa18adbe8b89b9f166bc8e35e06
 F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@ -1951,8 +1951,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 4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
-R 9fd24b1d2a3d7020ba2ddb1e81bfc0a5
+P 7616a6f4abe20699e5aa66018233247aa94e3e87f3e6ebe1357f776a18115eb3
+R 51ec30730a18840f04b19c6edf2cb5f7
 U larrybr
-Z 7b481df3c4b2b2c70f760eb720278652
+Z 604240d043bafbbe5712cfcf2c93e52d
 # Remove this line to create a well-formed Fossil manifest.
index 9ad49d34f34245bffb87cdbd6683f073e850890f..f22197398e541d83855cc3e801f8c0e5fadb11d4 100644 (file)
@@ -1 +1 @@
-7616a6f4abe20699e5aa66018233247aa94e3e87f3e6ebe1357f776a18115eb3
\ No newline at end of file
+d2446e50805cc3a46f9bcafccf017da60b0cf9fcd27695c214d71d1c5141d03f
\ No newline at end of file
index d14e7654c4e3cdb8b2db08948b3a65cf201a3f96..ab1a26480269b6e26303e567fa985dfef75cacb0 100755 (executable)
@@ -1257,8 +1257,8 @@ typedef struct ShExtInfo {
    * of pointers to the implementations. */
   int numMetaCommands;
   MetaCommand **ppMetaCommands;
-  int numOutModeHandlers;
-  OutModeHandler **ppOutModeHandlers;
+  int numExportHandlers;
+  ExportHandler **ppExportHandlers;
   int numImportHandlers;
   ImportHandler **ppImportHandlers;
 } ShExtInfo;
@@ -1391,15 +1391,15 @@ typedef struct ShellInState {
   ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */
   int ixExtPending;      /* Index of pending extension load operations if !0  */
   /* scripting integration */
-  ScriptHooks scripting; /* Hooks for scripting support from loaded extension */
-  ExtensionId scriptXid; /* Id of extension which has set scripting hooks */
+  ScriptSupport *script; /* Scripting support, if any, from loaded extension */
+  ExtensionId scriptXid; /* Id of extension which is supporting scripting */
   /* shell event subscription list */
   int numSubscriptions;  /* Number of active entries in below list */
   struct EventSubscription {
     ExtensionId eid;
     void *pvUserData;
     ShellEventNotify eventHandler;
-  } *pSubscriptions;     /* The currently active shell event subscriptions */
+  } *pSubscriptions;     /* The current shell event subscriptions */
 #endif
 
   ShellExState *pSXS;    /* Pointer to companion, exposed shell state */
@@ -7795,8 +7795,8 @@ static int register_meta_command(ShellExState *p,
 }
 
 /* Register an output data display (or other disposition) mode */
-static int register_out_mode(ShellExState *p,
-                             ExtensionId eid, OutModeHandler *pOMH){
+static int register_exporter(ShellExState *p,
+                             ExtensionId eid, ExportHandler *pEH){
   return SQLITE_ERROR;
 }
 
@@ -7806,17 +7806,17 @@ static int register_importer(ShellExState *p,
   return SQLITE_ERROR;
 }
 
-static int hook_scripting(ShellExState *p, ExtensionId eid, ScriptHooks *pSH){
+static int register_scripting(ShellExState *p, ExtensionId eid,
+                              ScriptSupport *pSS){
   ShellInState *psi = ISS(p);
   if( psi->scriptXid!=0 ){
     /* Scripting support already provided. Only one provider is allowed. */
     return SQLITE_BUSY;
   }
-  if( eid==0 || pSH==0 || psi->ixExtPending==0
-      || pSH->scriptIsComplete==0 || pSH->runScript==0 ){
+  if( eid==0 || pSS==0 || psi->ixExtPending==0 ){
     return SQLITE_MISUSE;
   }
-  psi->scripting = *pSH;
+  psi->script = pSS;
   psi->scriptXid = eid;
   return SQLITE_OK;
 }
@@ -7947,9 +7947,9 @@ static ExtensionHelpers extHelpers = {
 static ShellExtensionAPI shellExtAPI = {
   &extHelpers, 5, {
     register_meta_command,
-    register_out_mode,
+    register_exporter,
     register_importer,
-    hook_scripting,
+    register_scripting,
     subscribe_events,
     0
   }
@@ -8010,15 +8010,15 @@ static int begin_db_dispatch(ShellExState *psx){
   psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
   sei.ppMetaCommands
     = (MetaCommand **)sqlite3_malloc((numCommands+2)*sizeof(MetaCommand *));
-  sei.ppOutModeHandlers
-    = (OutModeHandler **)sqlite3_malloc(2*sizeof(OutModeHandler *));
+  sei.ppExportHandlers
+    = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *));
   sei.ppImportHandlers
     = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
-  if( sei.ppMetaCommands==0||sei.ppOutModeHandlers==0||sei.ppImportHandlers==0
+  if( sei.ppMetaCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0
       || psi->pShxLoaded==0 ){
     shell_out_of_memory();
   }
-  sei.numOutModeHandlers = 0;
+  sei.numExportHandlers = 0;
   sei.numImportHandlers = 0;
   for( ic=0; ic<numCommands; ++ic ){
     sei.ppMetaCommands[ic] = builtInCommand(ic);
@@ -8079,12 +8079,12 @@ static void free_one_shext_tracking(ShExtInfo *psei){
     }
     sqlite3_free(psei->ppMetaCommands);
   }
-  if( psei->ppOutModeHandlers!=0 ){
-    for( j=psei->numOutModeHandlers; j>0; --j ){
-      OutModeHandler *pomh = psei->ppOutModeHandlers[j-1];
-      if( pomh->pMethods->destruct!=0 ) pomh->pMethods->destruct(pomh);
+  if( psei->ppExportHandlers!=0 ){
+    for( j=psei->numExportHandlers; j>0; --j ){
+      ExportHandler *peh = psei->ppExportHandlers[j-1];
+      if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh);
     }
-    sqlite3_free(psei->ppOutModeHandlers);
+    sqlite3_free(psei->ppExportHandlers);
   }
   if( psei->ppImportHandlers!=0 ){
     for( j=psei->numImportHandlers; j>0; --j ){
@@ -8098,8 +8098,9 @@ static void free_one_shext_tracking(ShExtInfo *psei){
   }
 }
 
-/* Call all existent loaded extension destructors, in reverse order
- * of their objects' creation, then free the tracking dyna-arrays.
+/* Call all existent loaded extension destructors, in reverse order of their
+ * objects' creation, except for scripting support which is done last,
+ * then free the tracking dyna-arrays.
  */
 static void free_all_shext_tracking(ShellInState *psi){
   if( psi->pShxLoaded!=0 ){
@@ -8108,7 +8109,11 @@ static void free_all_shext_tracking(ShellInState *psi){
       ShExtInfo *psei = &psi->pShxLoaded[--i];
       free_one_shext_tracking(psei);
       if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
-        memset(&psi->scripting, 0, sizeof(ScriptHooks));
+        assert(psi->script!=0);
+        if (psi->script->pMethods->destruct){
+          psi->script->pMethods->destruct(psi->script);
+        }
+        psi->script = 0;
         psi->scriptXid = 0;
       }
     }
@@ -8129,7 +8134,8 @@ static MetaCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
 }
 
 static int load_shell_extension(ShellExState *psx, const char *zFile,
-                                const char *zProc, char **pzErr){
+                                const char *zProc, char **pzErr,
+                                int nLoadArgs, char **azLoadArgs){
   ShellExtensionLink shxLink = {
     sizeof(ShellExtensionLink),
     &shellExtAPI,
@@ -8137,12 +8143,13 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
     0,   /* zErrMsg */
     0,   /* ExtensionId */
     0,   /* Extension destructor */
-    0    /* Extension data ref */
+    0,   /* Extension data ref */
+    nLoadArgs, azLoadArgs /* like-named members */
   }; //extDtor(pvExtObj)
   ShellInState *psi = ISS(psx);
-  /* save script hooking state for possible fallback if load fails */
-  ScriptHooks shSave = psi->scripting;
-  ExtensionId siSave = psi->scriptXid;
+  /* save script support state for possible fallback if load fails */
+  ScriptSupport *pssSave = psi->script;
+  ExtensionId ssiSave = psi->scriptXid;
   int rc;
 
   if( pzErr ) *pzErr = 0;
@@ -8173,9 +8180,10 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
       free_one_shext_tracking(psi->pShxLoaded+psi->ixExtPending);
       --psi->numExtLoaded;
     }
-    /* And unhook any scripting linkage it might have setup. */
-    psi->scripting = shSave;
-    psi->scriptXid = siSave;
+    /* And make it unwind any scripting linkage it might have setup. */
+    if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script);
+    psi->script = pssSave;
+    psi->scriptXid = ssiSave;
   }
   psi->ixExtPending = 0;
   if( rc!=SQLITE_OK ){
@@ -9580,7 +9588,7 @@ DISPATCHABLE_COMMAND( keyword ? 1 2 ){
 }
 
 /*****************
- * The .imposter, .iotrace, limit, lint, .load and .log commands
+ * The .imposter, .iotrace, limit, lint and .log commands
  */
 CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
@@ -9592,10 +9600,6 @@ COLLECT_HELP_TEXT[
   ".lint OPTIONS            Report potential schema issues.",
   "     Options:",
   "        fkey-indexes     Find missing foreign key indexes",
-  ".load FILE ?ENTRY?       Load an extension library",
-#if SHELL_DYNAMIC_EXTENSION
-  "   Option -shellext will load the library as a shell extension.",
-#endif
   ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
 ];
 DISPATCHABLE_COMMAND( imposter ? 3 3 ){
@@ -9948,36 +9952,6 @@ DISPATCHABLE_COMMAND( lint 3 1 0 ){
   return DCR_Ok|(rc!=0);
 }
 
-DISPATCHABLE_COMMAND( load ? 2 4 ){
-  const char *zFile = 0, *zProc = 0;
-  char *zErrMsg = 0;
-  int ai = 1, rc;
-#if SHELL_DYNAMIC_EXTENSION
-  u8 bLoadShellExt = 0;
-#endif
-  if( ISS(p)->bSafeMode ) return DCR_AbortError;
-  while( ai<nArg ){
-    const char *zA = azArg[ai++];
-#if SHELL_DYNAMIC_EXTENSION
-    if( optionMatch(zA, "shellext") ) bLoadShellExt = 1;
-    else
-#endif
-    if( zFile==0 ) zFile = zA;
-    else if( zProc==0 ) zProc = zA;
-    else return DCR_TooMany|ai;
-  }
-  open_db(p, 0);
-#if SHELL_DYNAMIC_EXTENSION
-  if( bLoadShellExt ){
-    rc = load_shell_extension(p, zFile, zProc, pzErr);
-  }else
-#endif
-  {
-    rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
-  }
-  return DCR_Ok|(rc!=SQLITE_OK);
-}
-
 DISPATCHABLE_COMMAND( log ? 2 2 ){
   const char *zFile = azArg[1];
   if( ISS(p)->bSafeMode ) return DCR_AbortError;
@@ -10052,6 +10026,66 @@ static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
   psi->mode = modeNominal;
 }
 
+/*****************
+ * The .load command
+ * This is out of order to alleviate code clarity concerns.
+ */
+#if SHELL_DYNAMIC_EXTENSION  /* Avoid confusing users or mkshellc.tcl here. */
+# define LDEXT_OPTS "?ENTRY? ?..?"
+# define LDEXT_MAXARGS 0
+#else
+# define LDEXT_OPTS "?ENTRY?     "
+# define LDEXT_MAXARGS 3
+#endif
+COLLECT_HELP_TEXT[
+  ".load FILE "LDEXT_OPTS"  Load an extension library",
+#if SHELL_DYNAMIC_EXTENSION
+  "   If option -shext follows the first 1 or 2 arguments, then the library",
+  "   will be loaded as a shell extension, and any subsequent arguments will",
+  "   be passed to the extension's init function. That function is named per",
+  "   the docs for sqlite3_load_extension(), with ENTRY taking zProc's role.",
+#else
+  "   If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
+  "   Otherwise, the entry point name is derived from the FILE's name.",
+#endif
+];
+DISPATCHABLE_COMMAND( load ? 2 LDEXT_MAXARGS ){
+  const char *zFile = 0, *zProc = 0;
+  char *zErrMsg = 0;
+  int ai = 1, rc;
+#if SHELL_DYNAMIC_EXTENSION
+  char **pzShext = 0;
+#else
+  if( nArg>3 ) return DCR_TooMany|3;
+#endif
+  if( ISS(p)->bSafeMode ) return DCR_AbortError;
+  while( ai<nArg ){
+    const char *zA = azArg[ai++];
+#if SHELL_DYNAMIC_EXTENSION
+    if( optionMatch(zA, "shext") ){
+      if( zFile==0 ) return DCR_Missing;
+      pzShext = azArg + ai;
+      break;
+    }else if( zFile==0 ) zFile = zA;
+    else if( zProc==0 ) zProc = zA;
+#else
+    if( zFile==0 ) zFile = zA;
+    else if( zProc==0 ) zProc = zA;
+    else return DCR_TooMany|ai;
+#endif
+  }
+  open_db(p, 0);
+#if SHELL_DYNAMIC_EXTENSION
+  if( pzShext ){
+    rc = load_shell_extension(p, zFile, zProc, pzErr, nArg-ai, pzShext);
+  }else
+#endif
+    {
+      rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
+    }
+  return DCR_Ok|(rc!=SQLITE_OK);
+}
+
 /*****************
  * The .mode command
  */
@@ -14077,7 +14111,7 @@ static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){
 ** 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,
+static int line_join_done(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);
@@ -14156,6 +14190,7 @@ static DotCmdRC process_input(ShellInState *psi){
 
   /* line-group processing loop (per SQL block, dot-command or comment) */
   while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){
+    ScriptSupport *pSS = psi->script;
     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  */
@@ -14164,10 +14199,10 @@ static DotCmdRC process_input(ShellInState *psi){
     SqlScanState sqScanState = SSS_Start; /* for SQL scan */
 #if SHELL_EXTENDED_PARSING
     DCmd_ScanState dcScanState = DCSS_Start;  /* for dot-command scan */
-    int ndcLeadWhite = 0; /* skips over initial whitespace to . or # */
+    int nLeadWhite = 0; /* skips 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 nLeadWhite 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
@@ -14206,22 +14241,20 @@ static DotCmdRC process_input(ShellInState *psi){
       }
       /* Classify and check for single-line dispositions, prep for more. */
 #if SHELL_EXTENDED_PARSING
-      ndcLeadWhite = (SHEXT_PARSING(psi))
+      nLeadWhite = (SHEXT_PARSING(psi))
         ? skipWhite(zLineInput)-zLineInput
         : 0; /* Disallow leading whitespace for . or # in legacy mode. */
 #endif
 #if SHELL_DYNAMIC_EXTENSION
-      if( psi->scripting.isScriptLeader!=0
-          && psi->scripting.isScriptLeader(psi->scripting.pvScriptingState,
-                                           zLineInput+ndcLeadWhite) ){
+      if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
         inKind = Script;
       }else
 #endif
       {
-        switch( zLineInput[ndcLeadWhite] ){
+        switch( zLineInput[nLeadWhite] ){
         case '.':
           inKind = Cmd;
-          dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
+          dot_command_scan(zLineInput+nLeadWhite, &dcScanState);
           break;
         case '#':
           inKind = Comment;
@@ -14257,7 +14290,7 @@ static DotCmdRC process_input(ShellInState *psi){
       case Cmd:
 #if SHELL_EXTENDED_PARSING
         if( SHEXT_PARSING(psi) ){
-          if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
+          if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
             disposition = Runnable;
           }
         }else
@@ -14266,9 +14299,8 @@ static DotCmdRC process_input(ShellInState *psi){
         break;
 #if SHELL_DYNAMIC_EXTENSION
       case Script:
-        if( psi->scripting.scriptIsComplete==0
-            || psi->scripting.scriptIsComplete(psi->scripting.pvScriptingState,
-                                               *pzLineUse+ndcLeadWhite) ){
+        if( pSS==0
+            || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
           disposition = Runnable;
         }
         break;
@@ -14344,6 +14376,9 @@ static DotCmdRC process_input(ShellInState *psi){
     switch( disposition ){
     case Dumpable:
       echo_group_input(psi, *pzLineUse);
+#if SHELL_DYNAMIC_EXTENSION
+      if( inKind==Script && pSS!=0 ) pSS->pMethods->restartCompletionScan(pSS);
+#endif
       break;
     case Runnable:
       switch( inKind ){
@@ -14356,7 +14391,7 @@ static DotCmdRC process_input(ShellInState *psi){
       case Cmd: {
         DotCmdRC dcr;
         echo_group_input(psi, *pzLineUse);
-        dcr = do_meta_command(*pzLineUse+ndcLeadWhite, XSS(psi));
+        dcr = do_meta_command(*pzLineUse+nLeadWhite, XSS(psi));
         nErrors += (dcr & DCR_Error);
         dcr &= ~DCR_Error;
         if( dcr > termKind ) termKind = dcr;
@@ -14364,25 +14399,19 @@ static DotCmdRC process_input(ShellInState *psi){
       }
 #if SHELL_DYNAMIC_EXTENSION
       case Script: {
-        if( psi->scripting.runScript!=0 ){
-          char *zErr = 0;
-          DotCmdRC dcr
-            = psi->scripting.runScript(psi->scripting.pvScriptingState,
-                                       *pzLineUse+ndcLeadWhite,
+        char *zErr = 0;
+        DotCmdRC dcr;
+        assert(pSS!=0);
+        pSS->pMethods->restartCompletionScan(pSS);
+        dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
                                        XSS(psi), &zErr);
-          if( dcr!=DCR_Ok || zErr!=0 ){
-            /* ToDo: Handle errors more informatively and like dot commands. */
-            nErrors += (dcr!=DCR_Ok);
-            if( zErr!=0 ){
-              utf8_printf(STD_ERR, "Error: %s\n", zErr);
-              sqlite3_free(zErr);
-            }
+        if( dcr!=DCR_Ok || zErr!=0 ){
+          /* ToDo: Handle errors more informatively and like dot commands. */
+          nErrors += (dcr!=DCR_Ok);
+          if( zErr!=0 ){
+            utf8_printf(STD_ERR, "Error: %s\n", zErr);
+            sqlite3_free(zErr);
           }
-        }else{
-          utf8_printf(STD_ERR, "Error: No script support;"
-                      " ignoring group at line %d of \"%s\"\n",
-                      psi->pInSource->lineno, psi->pInSource->zSourceSay);
-          ++nErrors;
         }
         break;
       }
@@ -14398,6 +14427,9 @@ static DotCmdRC process_input(ShellInState *psi){
     case Erroneous:
       utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
                   psi->pInSource->lineno, psi->pInSource->zSourceSay);
+#if SHELL_DYNAMIC_EXTENSION
+      if( inKind==Script && pSS!=0 ) pSS->pMethods->restartCompletionScan(pSS);
+#endif
       ++nErrors;
       break;
     case Ignore:
index 4793992af5f1587ee3735a3166aef68c34ff6c74..77c4b1e03b9be64ccb63f9ba8157f3ef8ad9da70 100644 (file)
@@ -141,21 +141,21 @@ INTERFACE_END( MetaCommand );
 /* An object implementing below interface is registered with the
  * shell to make new or overriding output modes available to it.
  */
-INTERFACE_BEGIN( OutModeHandler );
-PURE_VMETHOD(const char *, name, OutModeHandler, 0,());
-PURE_VMETHOD(const char *, help, OutModeHandler, 1,(int more));
-PURE_VMETHOD(int, openResultsOutStream, OutModeHandler,
+INTERFACE_BEGIN( ExportHandler );
+PURE_VMETHOD(const char *, name, ExportHandler, 0,());
+PURE_VMETHOD(const char *, help, ExportHandler, 1,(int more));
+PURE_VMETHOD(int, openResultsOutStream, ExportHandler,
              5,( ShellExState *pSES, char **pzErr,
                  int numArgs, char *azArgs[], const char * zName ));
-PURE_VMETHOD(int, prependResultsOut, OutModeHandler,
+PURE_VMETHOD(int, prependResultsOut, ExportHandler,
              3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
-PURE_VMETHOD(int, rowResultsOut, OutModeHandler,
+PURE_VMETHOD(int, rowResultsOut, ExportHandler,
              3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
-PURE_VMETHOD(int, appendResultsOut, OutModeHandler,
+PURE_VMETHOD(int, appendResultsOut, ExportHandler,
              3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
-PURE_VMETHOD(void, closeResultsOutStream, OutModeHandler,
+PURE_VMETHOD(void, closeResultsOutStream, ExportHandler,
              2,( ShellExState *pSES, char **pzErr ));
-INTERFACE_END( OutModeHandlerVtable );
+INTERFACE_END( ExportHandler );
 
 /* An object implementing below interface is registered with the
  * shell to make new or overriding data importers available to it.
@@ -174,8 +174,89 @@ PURE_VMETHOD(int, finishDataInput, ImportHandler,
              3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
 PURE_VMETHOD(void, closeDataInStream, ImportHandler,
              2,( ShellExState *pSES, char **pzErr ));
-INTERFACE_END( ImportHandlerVtable );
+INTERFACE_END( ImportHandler );
 
+/* An object implementing this next interface is registered with the shell
+ * to make scripting support available to it. Only one at a time can be used.
+ *
+ * If registerScripting() has been called to register an extension's support
+ * for scripting, then its methods are called, and must respond, as follows:
+ *
+ * When the initial line of an "execution group" is collected by the shell,
+ * it calls isScriptLeader(pObj, zLineLead) to determine whether the group
+ * should be considered as (eventually) being one for the script handler
+ * to execute. This does not indicate whether it is good input or runnable;
+ * it is only for classification (so that different parsing/collection rules
+ * may be applied for different categories of shell input.) The method should
+ * return true iff the group should be parsed and run by this handler. If it
+ * returns false, something else will be done with the group.
+ *
+ * As one or more lines of an "execution group" are collected by the shell,
+ * scriptIsComplete(pObj, zLineGroup, pzWhyNot) is called with the group as
+ * so far accumulated. If out parameter pzWhyNot is non-zero, the method may
+ * output a message indicating in what way the input is incomplete, which is
+ * then the shell's responsibility to pass to sqlite3_free(). The method must
+ * return true if the group is ready to be executed, otherwise false. This is
+ * not the time at which to execute the accumulated group.
+ *
+ * After the scriptIsComplete() method returns true, or whenever the script is
+ * being ignored (due to end-of-stream or interrupt), the resetCompletionScan()
+ * method is called. This may be used to reset the scanning state held across
+ * calls to scriptIsComplete() so that it need not scan the whole script text
+ * at each call. It might do other things; it is always called after a call to
+ * isScriptLeader() has returned true and scriptIsComplete() has been called.
+ *
+ * If a script group is complete (as above-determined), then runScript() may
+ * be called to execute that script group. (Or, it may not.) It must either
+ * execute it successfully and return DCR_Ok, suffer an ordinary failure and
+ * return DCR_Error, or return one of the codes DCR_{Return,Exit,Abort} or'ed
+ * with DCR_Error or not, to indicate extraordinary post-execute actions.
+ * DCR_Return is to indicate the present execution context should be left.
+ * DCR_Exit is for shell exit requests. DCR_Abort means exit with prejudice.
+ *
+ * An extension which has called registerScripting() should arrange to
+ * free associated resources upon exit or when its destructor runs.
+ */
+INTERFACE_BEGIN( ScriptSupport );
+PURE_VMETHOD(const char *, name, ScriptSupport, 0,());
+PURE_VMETHOD(const char *, help, ScriptSupport, 1,( int more ));
+PURE_VMETHOD(int,  configure, ScriptSupport,
+             4,( ShellExState *pSES, char **pzErr,
+                 int numArgs, char *azArgs[] ));
+PURE_VMETHOD(int, isScriptLeader, ScriptSupport,
+             1,( const char *zScript ));
+PURE_VMETHOD(int, scriptIsComplete, ScriptSupport,
+             2,( const char *zScript, char **pzWhyNot ));
+PURE_VMETHOD(void, resetCompletionScan, ScriptSupport, 0,());
+
+PURE_VMETHOD(DotCmdRC, runScript, ScriptSupport,
+             3,( const char *zScript, ShellExState *pSES, char **pzErr ));
+INTERFACE_END( ScriptSupport );
+
+/* Define a v-table implementation for ScriptSupport interface. */
+#define ScriptSupport_IMPLEMENT_VTABLE(Derived, vtname) \
+CONCRETE_BEGIN(ScriptSupport, Derived); \
+CONCRETE_METHOD(const char *, name, ScriptSupport, 0,()); \
+CONCRETE_METHOD(const char *, help, ScriptSupport, 1,( int more )); \
+CONCRETE_METHOD(int,  configure, ScriptSupport, \
+  4,( ShellExState *pSES, char **pzErr, int numArgs, char *azArgs[] )); \
+CONCRETE_METHOD(int, isScriptLeader, ScriptSupport, \
+  1,( const char *zScript )); \
+CONCRETE_METHOD(int, scriptIsComplete, ScriptSupport, \
+   2,( const char *zScript, char **pzWhyNot )); \
+CONCRETE_METHOD(void, resetCompletionScan, ScriptSupport, 0,()); \
+CONCRETE_METHOD(DotCmdRC, runScript, ScriptSupport, \
+  3,( const char *zScript, ShellExState *pSES, char **pzErr )); \
+CONCRETE_END(Derived) vtname = { \
+  DECORATE_METHOD(Derived,destruct), \
+  DECORATE_METHOD(Derived,name), \
+  DECORATE_METHOD(Derived,help), \
+  DECORATE_METHOD(Derived,configure), \
+  DECORATE_METHOD(Derived,isScriptLeader), \
+  DECORATE_METHOD(Derived,scriptIsComplete), \
+  DECORATE_METHOD(Derived,resetCompletionScan), \
+  DECORATE_METHOD(Derived,runScript) \
+}
 /* Define an implementation's v-table matching the MetaCommand interface.
  * Method signatures are copied and pasted from above interface declaration.
  */
@@ -202,30 +283,6 @@ CONCRETE_END(Derived) vtname = { \
 typedef int (*ExtensionId)
   (sqlite3 *, char **, const struct sqlite3_api_routines *);
 
-/* Hooks for scripting language integration.
- *
- * If hookScripting(...) has been called to register an extension's
- * scripting support, and isScriptLeader(pvSS, zLineLead) returns true,
- * (where zLineLead is an input group's leading line), then the shell
- * will collect input lines until scriptIsComplete(pvSS, zLineGroup)
- * returns non-zero, whereupon, the same group is submitted to be run
- * via runScript(pvSS, zLineGroup, ...). The default behaviors (when
- * any of the function pointers is 0) are: return false; return true;
- * and return DCR_Error after doing nothing.
- *
- * An extension which has called hookScripting() should arrange to
- * free associated resources upon exit or when its destructor runs.
- *
- * The 1st member, pvScriptingState, is an arbitrary, opaque pointer.
- */
-typedef struct ScriptHooks {
-  void *pvScriptingState; /* passed into below functions as pvSS */
-  int (*isScriptLeader)(void *pvSS, const char *zScript);
-  int (*scriptIsComplete)(void *pvSS, const char *zScript);
-  DotCmdRC (*runScript)(void *pvSS, const char *zScript,
-                        ShellExState *, char **pzErrMsg);
-} ScriptHooks;
-
 typedef struct Prompts {
   const char *zMain;
   const char *zContinue;
@@ -304,14 +361,14 @@ typedef struct ShellExtensionAPI {
       int (*registerMetaCommand)(ShellExState *p,
                                  ExtensionId eid, MetaCommand *pMC);
       /* Register query result data display (or other disposition) mode */
-      int (*registerOutMode)(ShellExState *p,
-                             ExtensionId eid, OutModeHandler *pOMH);
+      int (*registerExporter)(ShellExState *p,
+                              ExtensionId eid, ExportHandler *pOMH);
       /* Register an import variation from (various sources) for .import */
       int (*registerImporter)(ShellExState *p,
                               ExtensionId eid, ImportHandler *pIH);
-      /* Provide scripting support to host shell. (See ScriptHooks above.) */
-      int (*hookScripting)(ShellExState *p,
-                           ExtensionId eid, ScriptHooks *pSH);
+      /* Provide scripting support to host shell. (See ScriptSupport above.) */
+      int (*registerScripting)(ShellExState *p,
+                               ExtensionId eid, ScriptSupport *pSS);
       /* Subscribe to (or unsubscribe from) messages about various changes.
        * See above NoticeKind enum and ShellEventNotify callback typedef. */
       int (*subscribeEvents)(ShellExState *p, ExtensionId eid, void *pvUserData,
@@ -347,6 +404,14 @@ typedef struct ShellExtensionLink {
   void (*extensionDestruct)(void *pvExtObj);
   void *pvExtensionObject;
 
+  /* If extra arguments were provided to the .load command, they are
+   * available through these two members. Only azLoadArgs[0] through
+   * azLoadArgs[nLoadArgs-1] may be referenced. (That may be none.)
+   * If an extension keeps the argument values, copies must be made
+   * as the pointers in azLoadArgs[] become invalid after loading.
+   */
+  int nLoadArgs;
+  char **azLoadArgs;
 } ShellExtensionLink;
 
 /* String used with SQLite "Pointer Passing Interfaces" as a type marker.