]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Commencing dynamic extensibility transition. (a WIP, may not build)
authorlarrybr <larrybr@noemail.net>
Thu, 23 Sep 2021 17:27:17 +0000 (17:27 +0000)
committerlarrybr <larrybr@noemail.net>
Thu, 23 Sep 2021 17:27:17 +0000 (17:27 +0000)
FossilOrigin-Name: 5ea71afe96ebe32641024aa8324b3ddd73da3ba35de204669130f8136cc1ba85

manifest
manifest.uuid
src/shell.c.in
src/shext_linkage.h [new file with mode: 0644]
tool/mkshellc.tcl

index 700bfc8ba9f8431576d28ae28f462241d3597b1f..0918c9450f031d3bfeabd5c525d89a6d6b43da43 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Complete\sshell.c\smaker's\smigration\sto\sTCL\sv.8.4
-D 2021-09-05T18:45:38.604
+C Commencing\sdynamic\sextensibility\stransition.\s(a\sWIP,\smay\snot\sbuild)
+D 2021-09-23T17:27:17.965
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -545,7 +545,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c 42b94d37a54200707a95566eff4f7e8a380e32d080016b699f23bd79a73a5028
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 63077c0243ded1432d97c90c1a4c3419b3a574b36634c674599a68bfe4c3bdc2
-F src/shell.c.in 729b233614ebf5e3a7f7ca1fcbefd0043439192962ae200e41822b2de88dcada
+F src/shell.c.in d9227a100a050eef557789d0fbe79f6b990171179633ff0213be966845702ff3
+F src/shext_linkage.h adf6b1c6a918b3c695a2181f1cec9cc452afd89fdbb9f476d4cd495e6a7c9aa7
 F src/sqlite.h.in 43fcf0fe2af04081f420a906fc020bde1243851ba44b0aa567a27f94bf8c3145
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h e97f4e9b509408fea4c4e9bef5a41608dfac343b4d3c7a990dedde1e19af9510
@@ -1854,7 +1855,7 @@ F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61
 F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd
 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
 F tool/mkpragmatab.tcl 7f6db47d1995bc08247255622524567b2ab8962d98063f8aef97e35c3c54e3b8
-F tool/mkshellc.tcl 411eec479747ed1ab3083cfb1f6ad5adc6e0513dbb43457d1ecbb38f185fb0f7
+F tool/mkshellc.tcl 1f6105dc731a32eb49c76fc60672bb1de3f3e1f44d632094e5ee4249bf51b28d
 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@ -1920,7 +1921,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P c60f4f90c954eee6a2b644c50aca9a4ed7616d89177fd1c6acb997a362d9abff
-R f8fde1e6d9349d728c3d53f4be4fa502
+P e4b9b5b14bcb7b4c19ba47a7d5c74764b9122d36be82f52053cafcee144c581e
+R 66ca892bbdf4f099a713b813eb8e1d1a
 U larrybr
-Z 465b5f34f9ce5fabad331f9659d67e22
+Z 079c8921973fd093a59246d0d2430319
index 2e1cd96e0653c0f4fa023f1ca0e18b5bc39836eb..e188ac772801aaf1a41034ae6c41a0a4e18572ec 100644 (file)
@@ -1 +1 @@
-e4b9b5b14bcb7b4c19ba47a7d5c74764b9122d36be82f52053cafcee144c581e
\ No newline at end of file
+5ea71afe96ebe32641024aa8324b3ddd73da3ba35de204669130f8136cc1ba85
\ No newline at end of file
index 5e8e25eedc1fe3e7b4d4a65757111ac8b6a0c925..fce26ff09a4f4eb074d8d21e2cad9488d94d3804 100644 (file)
@@ -3937,74 +3937,85 @@ static int run_schema_dump_query(
   return rc;
 }
 
+/* Configure help text generation to have coalesced secondary
+ * help lines with trailing newlines on all help lines.
+ */
+DISPATCH_CONFIG[
+  HELP_COALESCE=1
+];
+#define HELP_TEXT_FMT "%s"
+/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
+ * Alternative is 0 and "%s\n" .
+ */
+
 /*
-** Output help text.
-**
-** zPattern describes the set of commands for which help text is provided.
-** If zPattern is NULL, then show all commands, but only give a one-line
-** description of each.
+** Output various subsets of help text. These 5 are defined:
+** 1. For all commands, primary help text only.
+** 2. For all commands, complete help text.
+** 3. For multiple commands matching a pattern, primary help text only.
+** 4. For a single matched command, complete help text.
+** 5. For commands whose help contains a pattern, complete help text.
+** These variations are indicated thusly:
+** 1. zPattern is NULL
+** 2. zPattern is ""
+** 3. zPattern is a prefix matching more than one command
+** 4. zPattern is a word or prefix matching just one command
+** 5. zPattern is neither case 3 or 4 but is found in complete help text
 **
 ** Return the number of matches.
 */
 static int showHelp(FILE *out, const char *zPattern){
-  int n = 0;
-  char *zPat;
+  int npm = 0;
+  char *zPat = sqlite3_mprintf(".%s*", zPattern? zPattern : "");
   const char **pzHxtra;
   const char **pzH;
-  if( zPattern==0
-   || zPattern[0]=='0'
-   || strcmp(zPattern,"-a")==0
-   || strcmp(zPattern,"-all")==0
-   || strcmp(zPattern,"--all")==0
-  ){
-    /* Show all commands, but only one line per command */
-    if( zPattern==0 ) zPattern = "";
-    for(pzH = azHelp; *pzH != 0; ++pzH){
-      if( *pzH[0]=='.' || zPattern[0] ){
-        utf8_printf(out, "%s\n", *pzH);
-        ++n;
-      }
-    }
-  }else{
-    /* Look for commands that for which zPattern is an exact prefix */
-    zPat = sqlite3_mprintf(".%s*", zPattern);
-    for(pzH = azHelp; *pzH != 0; ++pzH){
-      if( sqlite3_strglob(zPat, *pzH)==0 ){
-        utf8_printf(out, "%s\n", *pzH);
+  int nma = 0;
+  if( zPat==0 ) shell_out_of_memory();
+  for(pzH = azHelp; *pzH != 0; ++pzH){
+    /* Look for all commands or those for which zPattern is an exact prefix */
+    if( *pzH[0]=='.' ){
+      if ( sqlite3_strglob(zPat, *pzH)==0 ){
+        utf8_printf(out, HELP_TEXT_FMT, *pzH);
         pzHxtra = pzH + 1;
-        n++;
+        ++npm;
       }
+    }else if( zPattern && *zPattern==0 ){
+      utf8_printf(out, HELP_TEXT_FMT, *pzH);
     }
-    sqlite3_free(zPat);
-    if( n ){
-      if( n==1 ){
-        /* when zPattern is a prefix of exactly one command, then include the
-        ** details of that command, which should begin at *pzHxtra */
-        while( *pzHxtra !=0 && *pzHxtra[0]!='.' ){
-          utf8_printf(out, "%s\n", *pzHxtra++);
-        }
-      }
-      return n;
-    }
-    /* Look for commands that contain zPattern anywhere.  Show the complete
-    ** text of all commands that match. */
-    zPat = sqlite3_mprintf("%%%s%%", zPattern);
-    for(pzH = azHelp; *pzH != 0;){
-      if( *pzH[0]=='.' ) pzHxtra = pzH;
-      if( sqlite3_strlike(zPat, *pzHxtra, 0)==0 ){
-        utf8_printf(out, "%s\n", *pzHxtra++);
-        while( *pzHxtra != 0 && *pzHxtra[0]!='.' ){
-          utf8_printf(out, "%s\n", *pzHxtra++);
-        }
-        pzH = pzHxtra;
-        n++;
-      }else{
-        ++pzH;
-      }
+  }
+  sqlite3_free(zPat);
+  if( npm==1 ){
+    /* When zPattern is a prefix of exactly one command, then include
+    ** the secondary help of that command, (beginning at *pzHxtra.) */
+    while( *pzHxtra !=0 && *pzHxtra[0]!='.' ){
+      utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++);
     }
-    sqlite3_free(zPat);
   }
-  return n;
+  if( npm>0 )
+    return npm;
+
+  /* Having failed to match a command, look for commands whose help contains
+   * zPattern anywhere. Show the complete text of all such commands.
+   */
+  zPat = sqlite3_mprintf("%%%s%%", zPattern);
+  if( zPat==0 ) shell_out_of_memory();
+  for(pzH = azHelp; *pzH != 0;){
+    if( *pzH[0]=='.' ){
+      pzHxtra = pzH;
+      nma = 0;
+    }
+    if( sqlite3_strlike(zPat, *pzH, 0)==0 )
+      ++nma;
+    ++pzH;
+    if( nma>0 && (*pzH==0 || *pzH[0]=='.') ){
+      ++npm;
+      while( pzHxtra < pzH )
+        utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++);
+    }
+  }
+  sqlite3_free(zPat);
+
+  return npm;
 }
 
 /* Forward reference */
@@ -6954,6 +6965,10 @@ static int writeDb( char *azArg[], int nArg, ShellState *p ){
   return rc;
 }
 
+#ifndef OBJECTIFY_COMMANDS
+# define OBJECTIFY_COMMANDS 1
+#endif
+
 /* Meta-command implementation functions are defined in this section.
 COMMENT  Define meta-commands and provide for their dispatch and .help text.
 COMMENT  These should be kept in command name order for coding convenience
@@ -6963,7 +6978,7 @@ COMMENT  effect of this configuration can be seen in generated output or by
 COMMENT  executing tool/mkshellc.tcl --parameters (or --details or --help).
 COMMENT  Generally, this section defines dispatchable functions inline and
 COMMENT  causes collection of dispatch and help table entries, to be later
-COMMENT  emitted by the EMIT_DISPATCH and EMIT_HELP_TEXT macros further on.
+COMMENT  emitted by certain macros. (See EMIT_* further on.)
 */
 DISPATCH_CONFIG[
   RETURN_TYPE=int
@@ -6981,7 +6996,6 @@ DISPATCH_CONFIG[
   DC_ARG_COUNT=7
 ];
 
-
 CONDITION_COMMAND(seeargs defined(SQLITE_GIMME_SEEARGS));
 /*****************
  * The .seeargs command
@@ -7857,15 +7871,24 @@ DISPATCHABLE_COMMAND( headers 6 1 2 ){
  * The .help command
  */
 COLLECT_HELP_TEXT[
-  ".help ?-all? ?PATTERN?   Show help text, just for PATTERN if given",
+  ".help ?(PATTERN|-all)? Show help text for some or all command(s)",
+  "   PATTERN                Show help for matching command(s)",
+  "   -all                   Show all help for all commands",
 ];
-DISPATCHABLE_COMMAND( help 3 0 0 ){
-  if( nArg>=2 ){
-    if( showHelp(p->out, azArg[1])==0 ){
-      utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
+DISPATCHABLE_COMMAND( help 3 1 2 ){
+  const char *zPat = 0;
+  if( nArg>1 ){
+    char *z = azArg[1];
+    if( strcmp(z,"-a")==0
+        || strcmp(z,"-all")==0
+        || strcmp(z,"--all")==0 ){
+      zPat = "";
+    }else{
+      zPat = z;
     }
-  }else{
-    showHelp(p->out, 0);
+  }
+  if( showHelp(p->out, zPat)==0 ){
+    utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
   }
   /* Help pleas never fail! */
   return 0;
@@ -10069,10 +10092,11 @@ static int showTableLike(char *azArg[], int nArg, ShellState *p, char ot){
 
 COLLECT_HELP_TEXT[
 #ifndef LEGACY_TABLES_LISTING
-  ".tables ?FLAG? ?TABLE?   List names of tables/views matching LIKE pattern TABLE",
-  "   FLAG can be -t, -v or -s to list tables or views only, or system tables only",
+  ".tables ?FLAG? ?TVLIKE?  List names of tables and/or views",
+  "   FLAG may be -t, -v or -s to list only tables, views or system tables",
+  "   TVLIKE may restrict the listing to names matching given LIKE pattern",
 #else
-  ".tables ?TABLE?          List names of tables/views matching LIKE pattern TABLE",
+  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 #endif
 ];
 DISPATCHABLE_COMMAND( tables 2 1 3 ){
@@ -10137,7 +10161,7 @@ CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
 COLLECT_HELP_TEXT[
   ".testcase NAME           Begin redirecting output to 'testcase-out.txt'",
   ".testctrl CMD ...        Run various sqlite3_test_control() operations",
-  "                           Run \".testctrl\" with no arguments for details",
+  "                            Run \".testctrl\" with no arguments for details",
   ".timeout MS              Try opening locked tables for MS milliseconds",
   ".timer on|off            Turn SQL timer on or off",
   ".trace ?OPTIONS?         Output each SQL statement as it is run",
@@ -10620,17 +10644,27 @@ COMMENT  section to define new or altered meta-commands and their help text.
 */
 INCLUDE( COMMAND_CUSTOMIZE );
 
+#if OBJECTIFY_COMMANDS
+INCLUDE shext_linkage.h 
+#endif
+typedef struct MetaCommand MetaCommand;
+
 /* Define and populate command dispatch table. */
-static struct DispatchEntry {
+static struct CommandInfo {
   const char * cmdName;
   int (*cmdDoer)(char *azArg[], int nArg, ShellState *);
   unsigned char minLen, minArgs, maxArgs;
+#if OBJECTIFY_COMMANDS
+  const char *azHelp[2]; /* primary and secondary help text */
+  void * pCmdData;
+#endif
 } command_table[] = {
   COMMENT Emit the dispatch table entries generated and collected above.
   EMIT_DISPATCH(2);
   { 0, 0, 0, -1, -1 }
 };
-static unsigned numCommands = sizeof(command_table)/sizeof(struct DispatchEntry) - 1;
+static unsigned numCommands
+  = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
 
 COMMENT  This help text is set seperately from meta-command definition section
 COMMENT  for the always-built-in, non-customizable commands with visible help.
@@ -10666,7 +10700,7 @@ int dispatchCommand(char *azArg[], int nArg, ShellState *pSS)
 {
   const char *cmdName = azArg[0];
   int cmdLen = strlen30(cmdName);
-  struct DispatchEntry *pde = 0;
+  struct CommandInfo *pci = 0;
   int ixb = 0, ixe = numCommands-1;
   while( ixb <= ixe ){
     int ixm = (ixb+ixe)/2;
@@ -10679,17 +10713,17 @@ int dispatchCommand(char *azArg[], int nArg, ShellState *pSS)
       if( command_table[ixm].minLen > cmdLen ){
         return NO_SUCH_COMMAND;
       }
-      pde = &command_table[ixm];
+      pci = &command_table[ixm];
       break;
     }
   }
-  if( 0==pde ){
+  if( 0==pci ){
     return NO_SUCH_COMMAND;
   }
-  if((pde->minArgs > 0 && pde->minArgs > nArg)||(pde->maxArgs > 0 && pde->maxArgs < nArg)){
+  if((pci->minArgs > 0 && pci->minArgs > nArg)||(pci->maxArgs > 0 && pci->maxArgs < nArg)){
     return INVALID_ARGS;
   }
-  return (pde->cmdDoer)(azArg, nArg, pSS);
+  return (pci->cmdDoer)(azArg, nArg, pSS);
 }
 
 
diff --git a/src/shext_linkage.h b/src/shext_linkage.h
new file mode 100644 (file)
index 0000000..0a9e511
--- /dev/null
@@ -0,0 +1,172 @@
+#ifndef SQLITE3SHX_H
+#define SQLITE3SHX_H
+#include "sqlite3ext.h"
+
+typedef struct ShellState ShellState;
+
+/* This function pointer has the same signature as the sqlite3_X_init()
+ * function that is called as SQLite3 completes loading an extension.
+ */
+typedef int (*ExtensionId)
+  (sqlite3 *, char **, const struct sqlite3_api_routines *);
+
+/* An instance of below struct, possibly extended/subclassed, is registered
+ * with the shell to make new or altered meta-commands available to it.
+ */
+typedef struct MetaCommand {
+  struct MetaCommandVtable *pMCV;
+} MetaCommand;
+
+/* This vtable is for meta-command implementation and help linkage to shell.
+ */
+typedef struct MetaCommandVtable {
+  void (*destruct_free)(MetaCommand *);
+  const char * (*name)(MetaCommand *);
+  const char * (*help)(MetaCommand *, int more);
+  void (*argsRange)(MetaCommand *, int * pMinArgs, int * pMaxArgs);
+  int (*execute)
+    (MetaCommand *, ShellState *, char **pzErrMsg, char *azArgs[], int nArgs);
+} MetaCommandVtable;
+
+/* See "Shell Extensions, Programming" for purposes and usage of the following
+ * structs supporting extended meta-commands and import and output modes.
+ */
+
+/* Convey data to, from and/or between I/O handlers. */
+typedef struct {
+  char *zFieldSeparator;
+  char *zRecordSeparator;
+  char *zRecordLead;
+  char *zRecordTrail;
+  char *zNullValue;
+  char *zSQL;
+  int  numWidths;
+  int  *pWantWidths;
+  int  *pHaveWidths;
+  void *pvHandlerData; /* Lifetime is from mid-openX() to mid-closeX(). */
+} FormatInfo;
+
+/* An instance of below struct, possibly extended/subclassed, is registered
+ * with the shell to make new or altered output modes available to it.
+ */
+typedef struct OutModeHandler {
+  struct OutModeHandlerVtable *pOMV;
+} OutModeHandler;
+
+typedef struct OutModeHandlerVtable {
+  void (*destruct_free)(OutModeHandler * pROS);
+  const char * (*name)(OutModeHandler *);
+  const char * (*help)(OutModeHandler *, int more);
+  int (*openResultsOutStream)
+    (OutModeHandler * pROS, FormatInfo *pFI, char **pzErr,
+     const char * zLocus, const char * zName);
+  int (*prependResultsOut)
+    (OutModeHandler * pROS, FormatInfo *pFI, char **pzErr,
+     sqlite3_stmt * pStmt);
+  int (*rowResultsOut)
+    (OutModeHandler * pROS, FormatInfo *pFI, char **pzErr,
+     sqlite3_stmt * pStmt);
+  int (*appendResultsOut)
+    (OutModeHandler * pROS, FormatInfo *pFI, char **pzErr,
+     sqlite3_stmt * pStmt);
+  int (*closeResultsOutStream)
+    (OutModeHandler * pROS, FormatInfo *pFI, char **pzErr);
+} OutModeHandlerVtable;
+
+/* An instance of below struct, possibly extended/subclassed, is registered
+ * with the shell to make new or altered data importers available to it.
+ */
+typedef struct ImportHandler {
+  struct ImportHandlerVtable *pIHV;
+} ImportHandler;
+
+typedef struct ImportHandlerVtable {
+  void (*destruct_free)(ImportHandler * pIH);
+  const char * (*name)(ImportHandler *);
+  const char * (*help)(ImportHandler *, int more);
+  int (*openDataInStream)
+    (ImportHandler *pIH, FormatInfo *pFI, char **pzErr,
+     const char * zLocus, const char * zName);
+  int (*prepareDataInput)
+    (ImportHandler *pIH, FormatInfo *pFI, char **pzErr, sqlite3_stmt * pStmt);
+  int (*rowDataInput)
+    (ImportHandler *pIH, FormatInfo *pFI, char **pzErr, sqlite3_stmt * pStmt);
+  int (*finishDataInput)
+    (ImportHandler *pIH, FormatInfo *pFI, char **pzErr, sqlite3_stmt * pStmt);
+  int (*closeDataInStream)
+    (ImportHandler *pIH, FormatInfo *pFI, char **pzErr);
+} ImportHandlerVtable;
+
+#define SHELLEXT_VALIDITY_MARK "ExtensibleShell"
+
+typedef struct ShellExtensionLink {
+  char validityMark[16]; /* Preset to contain "ExtensibleShell\x00" */
+  char *zErrMsg;         /* Extension puts error message here if any. */
+  int sizeOfThis;           /* sizeof(struct ShellExtensionLink) */
+  const char *shellVersion; /* Preset to "3.??.??\x00" or similar */
+
+  /* An init "out" parameter, used as the loaded extension ID. Unless
+   * this is set within sqlite3_X_init() prior to register*() calls,
+   * the extension cannot be unloaded. 
+   */
+  ExtensionId eid;
+
+  /* Another init "out" parameter, a destructor for extension overall.
+   * Set to 0 on input and may be left so if no destructor is needed.
+   */
+  void (*extensionDtor)(void *);
+
+  /* Various shell extension feature registration functions
+   */
+  union ShellExtensionAPI {
+    struct ShExtAPI {
+      /* Register a meta-command */
+      int (*registerMetaCommand)(ExtensionId eid, MetaCommand *pMC);
+      /* Register an output data display (or other disposition) mode */
+      int (*registerOutMode)(ExtensionId eid, OutModeHandler *pOMH);
+      /* Register an import variation from (various sources) for .import */
+      int (*registerImporter)(ExtensionId eid, ImportHandler *pIH);
+      /* Preset to 0 at extension load, a sentinel for expansion */
+      void (*pExtra)(void); 
+    } *named;
+    void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */
+  } api;
+} ShellExtensionLink;
+
+/* Test whether a char ** references a ShellExtensionLink instance's
+ * validityMark, and if so return the instance's address, else return 0.
+ * This macro may be used from a shell extension's sqlite3_X_init() function
+ * to obtain a pointer to the ShellExtensionLink struct, derived from the
+ * error message pointer (pzErrMsg) passed as the 2nd argument. This enables
+ * the extension to incorporate its features into a running shell process.
+ */
+#define EXTENSION_LINKAGE_PTR(pzem) ( \
+  pzem != 0 && *pzem != 0 && strcmp(*pzem, SHELLEXT_VALIDITY_MARK) == 0 \
+  && *pzem == (char *)pzem \
+  + offsetof(ShellExtensionLink, validityMark) \
+  - offsetof(ShellExtensionLink, zErrMsg) ) \
+  ? (ShellExtensionLink *) \
+    ((char *)pzem-offsetof(ShellExtensionLink,zErrMsg)) \
+  : 0
+
+/* String used with SQLite "Pointer Passing Interfaces" as a type marker. 
+ * That API subset is used by the shell to pass its extension API to the
+ * sqlite3_X_init() function of extensions, via the DB parameter.
+ */
+#define SHELLEXT_API_POINTERS "shellext_api_pointers"
+
+/* Pre-write a function to retrieve a ShellExtensionLink pointer from the
+ * shell's DB. This is an alternative to use of the EXTENSION_LINKAGE_PTR
+ * macro above. It takes some more code, replicated across extensions.
+ */
+#define DEFINE_SHDB_TO_SHEXT_API(func_name) \
+ static ShellExtensionLink * func_name(sqlite3 * db){ \
+  ShellExtensionLink *rv; sqlite3_stmt *pStmt; \
+  if( SQLITE_OK!=sqlite3_prepare(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \
+      || SQLITE_ROW != sqlite3_step(pStmt) )  return 0; \
+  rv = (ShellExtensionLink *)sqlite3_value_pointer \
+    (sqlite3_column_value(pStmt, 0), SHELLEXT_API_POINTERS); \
+  sqlite3_finalize(pStmt);  return rv; \
+ }
+
+#endif /* !defined(SQLITE3SHX_H) */
index 4ec2d86a9c164d5e56732902298cc885001445bc..3090a623115e1935bd5611fa17b94b92eff65894 100644 (file)
@@ -1,19 +1,54 @@
 #!/usr/bin/tclsh
 #
-# Run this script to generate the "shell.c" source file from 
-# constituent parts.
+# Run this script to generate the "shell.c" source file from its
+# constituent parts located normally within the SQLite source.
 #
 # No arguments are required.  This script determines the location
 # of its input files relative to the location of the script itself.
-# This script should be tool/mkshellc.tcl.  If the directory holding
-# the script is $DIR, then the component parts are located in $DIR/../src
-# and $DIR/../ext/misc.
+# This script is assumed to be in <project root>/tool/mkshellc.tcl.
+# By default, shell.c's constituent parts, named in INCLUDE macros,
+# are located in <project root>/src and <project root>/ext/misc .
+# By default, the input src/shell.c.in is read and processed.
 #
-set topdir [file dir [file dir [file normal $argv0]]]
+# To see other execution options, run this with a --help option.
+# This script may also be used for shell extensions, as described
+# at https://sqlite.org/shell_extend.html . ToDo
+#########1#########2#########3#########4#########5#########6#########7#########8
 
-set out stdout
+set ::help {
+ mkshellc.tcl <options>
+  <options> may be either --help, --details, --parameters or any sequence of:
+    <input_filename>
+    -ignored <signed_command_list>
+    -inc-type <inc_type>=<include_filename>
+    -source-tags <tags_degree>
+    -top-dir <project_root>
+    -tcl
+ If no input files are specified, <PROJECT_ROOT>/src/shell.c.in is read.
+ Input files are read and processed in order, producing output to sdout.
+
+ The -ignored option affects a list of commands which, during processing,
+ will be ignored and generate no output. The list starts empty.
+
+ The -inc-type option associates a filename with an <inc_type> word which
+ may be used during execution of INCLUDE(...) directives in the input.
 
-set headComment {/* DO NOT EDIT!
+ The -source-tags option sets the degree of #line directive emission via
+ the <tags_degree> value. 0 turns tagging off. 1, which is the default,
+ yields tagging only on non-macro code as it is scanned. 2 adds much more
+ tagging, (about 3x), on individual dispatch and help table entries, and
+ on conditional compilation preprocessor directives.
+
+ Input files may include macro lines or line sequences matching any of:
+  INCUDE <file_name>\
+}
+# MACRO_DOSTUFF ...
+set ::helpMore {
+ Use --details option for detailed effects of these macros.
+ Use --parameters option for CONFIGURE_DISPATCH parameter names and effects.
+}
+
+set ::headComment {/* DO NOT EDIT!
 ** This file is automatically generated by the script in the canonical
 ** SQLite source tree at tool/mkshellc.tcl.  That script combines and
 ** transforms code from various constituent source files of SQLite into
@@ -33,48 +68,66 @@ set headComment {/* DO NOT EDIT!
 ** then rerun the tool/mkshellc.tcl script.
 */}
 
-set customRun 0
-set lineDirectives 1
+set ::headCommentLines [expr 1+[regexp -all "\n" $::headComment]]
+
+set ::topdir [file dir [file dir [file normal $argv0]]]
+set runMode normal
+set ::lineDirectives 1
+set ::tclGenerate 0
+set ::verbosity 0
 set infiles {}
-array set ::incTypes [list "*" {}]
+array set ::incTypes [list "*" "$::topdir/src/shell.c.in"]
+array set ::ignoringCommands [list]
 
 while  {[llength $argv] > 0} {
-  set opt [lindex $argv 0]
-  set argv [lreplace $argv 0 0]
+  set argv [lassign $argv opt]
   if {[regexp {^-{1,2}((help)|(details)|(parameters))$} $opt ma ho]} {
-    switch $ho {
-      help {set customRun 2}
-      details {set customRun 3}
-      parameters {set customRun 4}
-    }
+    set runMode $ho
   } elseif {[regexp {^-it$} $opt]} {
-    set nextOpt [lindex $argv 0]
-    set argv [lreplace $argv 0 0]
+    set argv [lassign $argv nextOpt]
     if {![regexp {^(\w+)=(.+)$} $nextOpt ma k v]} {
       puts stderr "Get help with --help."
       exit 1 
     }
     set ::incTypes($k) $v
+  } elseif {$opt eq "-top-dir"} {
+    set argv [lassign $argv ::topdir]
+    if {::topdir eq ""} { set ::topdir . }
+  } elseif {$opt eq "-source-tags"} {
+    set argv [lassign $argv nextOpt]
+    if {![regexp {^\d$} $nextOpt ::lineDirectives]} {
+      puts stderr "Argument following -source-tags must be a digit."
+    }
   } elseif {$opt eq "-tcl"} {
-    puts stderr "Tcl extension not yet implemented." ; exit 1
-  } elseif {[regexp {^--?no-line-directives$} $opt]} {
-    set lineDirectives 0
+    puts stderr "Warning: Tcl extension not wholly implemented."
+    set ::tclGenerate 1
+  } elseif {$opt eq "-v"} {
+    incr ::verbosity
   } elseif {[regexp {^[^-]} $opt]} {
     lappend infiles $opt
-    set customRun 1
   } else {
     puts stderr "Skipping unknown option: $opt"
   }
 }
-if {[llength $infiles] == 0} {
-  set in [open $topdir/src/shell.c.in r]
-} else {
-  set infile [lindex $infiles 0]
-  set infiles [lreplace $infiles 0 0]
-  set in [open $infile r]
+if {$runMode eq "normal"} {
+  if {[llength $infiles] == 0} {
+    lappend infiles $::incTypes(*)
+  }
+  fconfigure stdout -translation {auto lf}
+  set out stdout
 }
 fconfigure $in -translation auto
 
+if {$::lineDirectives >= 2} {
+  # These k/v stores hold {filename lineNum} lists keyed by meta-command,
+  # used to get #line directives on all dispatch and help table entries,
+  # and any conditionals affecting their compilation.
+  array set ::cmd_help_tags {}
+  array set ::cmd_dispatch_tags {}
+  array set ::cmd_conditional_tags {}
+}
+proc lineDirective {filename lineNum} {return "#line $lineNum \"${filename}\""}
+
 array set ::cmd_help {}
 array set ::cmd_dispatch {}
 array set ::cmd_condition {}
@@ -96,6 +149,7 @@ set ::parametersHelp {
      (which is needed to permit them to be emitted in lexical order by name.)
    DC_ARG_COUNT sets the effective argument count for DISPATCHABLE_COMMAND().
    DC_ARG#_DEFAULT sets a default value, DISPATCHABLE_COMMAND() #'th argument.
+   HELP_COALESCE sets whether to coalesce secondary help text and add newlines.
   Within values set for ARGS_SIGNATURE, DISPATCHEE_NAME, and DISPATCH_ENTRY
   parameters, the variables $cmd and $arg# (where # is an integer) may appear,
   to be replaced by the meta-command name or the #'th effective argument to
@@ -113,6 +167,7 @@ array set ::dispCfg [list \
    "{ \"\$cmd\", \${cmd}Command, \$arg1,\$arg2,\$arg3 }," \
   DISPATCHEE_NAME {${cmd}Command} \
   CMD_CAPTURE_RE "^\\s*$::lb\\s*\"(\\w+)\"" \
+  HELP_COALESCE 0 \
 ]
 # Other config keys:
 #  DC_ARG_COUNT=<number of arguments to DISPATCHABLE_COMMAND()>
@@ -133,10 +188,22 @@ proc condition_command {cmd pp_expr} {
   set ::cmd_condition($cmd) $pp_expr
 }
 
-proc emit_conditionally {cmd lines ostrm {indent ""}} {
+proc emit_conditionally {cmd lines ostrm {indent ""} {cmdTagStore {}}} {
   set wrapped [info exists ::cmd_condition($cmd)]
+  set iPut 0
   if {$wrapped} {
+    if {$::lineDirectives >= 2} {
+      puts $ostrm [lineDirective $::cmd_conditional_tags($cmd)]
+      incr iPut
+    }
     puts $ostrm $::cmd_condition($cmd)
+    incr iPut
+  }
+  if {$::lineDirectives >= 2} {
+
+    set fnln subst[[subst "\$$cmdTagStore(\$cmd)"]]
+    puts $ostrm [lineDirective {*}$fnln]
+    incr iPut
   }
   if {[regexp {^\s*(\d+)\s*$} $indent ma inum]} {
     set lead [string repeat " " $inum]
@@ -146,9 +213,29 @@ proc emit_conditionally {cmd lines ostrm {indent ""}} {
   } else {
     puts $ostrm [join $lines "\n"]
   }
+  incr iPut [llength $lines]
   if {$wrapped} {
     puts $ostrm "#endif"
+    incr iPut
   }
+  return $iPut
+}
+
+# Coalesce secondary help text lines using C's string literal concatenation
+# and arrange that each command's help has one primary (leading '.') help
+# text line and one secondary help text line-set even if it is empty.
+proc coalesce_help {htin} {
+  set htrv {}
+  foreach hl $htin {
+    if {[regexp {^\s*"\.\w+} $hl]} { ;# "
+      lappend htrv [regsub {"\s*,\s*$} $hl {\n",}]
+    } elseif {[regexp {^\s*#\s*\w+} $hl]} {
+      lappend htrv $hl
+    } else {
+      lappend htrv [regsub {"\s*,\s*$} $hl {\n"}]
+    }
+  }
+  lappend htrv {"",}
 }
 
 # Convert list of help text lines into a key-value list.
@@ -196,6 +283,11 @@ proc chunkify_help {htin} {
       incr ::iShuffleErrors
     }
   }
+  if {$::dispCfg(HELP_COALESCE)} {
+    foreach cmd_seen [array names rv] {
+      set rv($cmd_seen) [coalesce_help $rv($cmd_seen)]
+    }
+  }
   return [array get rv]
 }
 
@@ -208,30 +300,119 @@ array set ::macroTailREs [list \
   DISPATCHABLE_COMMAND {^\(([\w\? ]+)\)(\S)\s*$} \
   EMIT_DISPATCH {^\((\d*)\)} \
   EMIT_HELP_TEXT {^\((\d*)\)} \
-  INCLUDE {^\(\s*(\w+)\s*\)} \
+  INCLUDE {^(?:\(\s*(\w+)\s*\))|(?:\s+([\w./\\]+)\M)} \
+  IGNORE_COMMANDS {^\(\s*([-+\w ]*)\)\s*;\s*} \
 ]
+# Names of the subcaptures as formal parameter to macro procs.
+# COMMENT tailCapture_Commentary
+# CONDITION_COMMAND tailCapture_Cmd_Condition
+# CONFIGURE_DISPATCH tailCapture_Empty
+# COLLECT_DISPATCH tailCapture_Cmd
+# COLLECT_HELP_TEXT tailCapture_Empty
+# DISPATCHABLE_COMMAND tailCapture_ArgsGlom_TrailChar
+# EMIT_DISPATCH tailCapture_Indent
+# EMIT_HELP_TEXT tailCapture_Indent
+# IGNORED_COMMANDS tailCapture_SignedCmdGlom
+# INCLUDE tailCapture_IncType_Filename
+
 array set ::macroUsages [list \
   COLLECT_DISPATCH "\[\n   <dispatch table entry lines>\n  \];" \
   COLLECT_HELP_TEXT "\[\n   <help text lines>\n  \];" \
   COMMENT " <arbitrary characters to end of line>" \
   CONDITION_COMMAND "( name pp_expr );" \
   DISPATCH_CONFIG "\[\n   <NAME=value lines>\n  \];" \
-  DISPATCHABLE_COMMAND "( name args... ){\n   <implementation code lines>\n  }" \
+  DISPATCHABLE_COMMAND \
+      "( name args... ){\n   <implementation code lines>\n  }" \
   EMIT_DISPATCH "( indent );" \
   EMIT_HELP_TEXT "( indent );" \
   INCLUDE {( <inc_type> )} \
+  SKIP_COMMANDS "( <signed_names> );" \
 ]
 # RE for early discard of non-macro lines, matching all above keywords
-set ::macroKeywordTailRE {^\s{0,8}((?:(?:CO)|(?:DI)|(?:EM)|(?:IN))[A-Z_]+)\M(.+)$}
+set ::macroKeywordTailRE \
+ {^\s{0,8}((?:(?:CO)|(?:DI)|(?:EM)|(?:IN)|(?:SK))[A-Z_]+)\M(.+)$}
 
-# All macro processor procs return the count of extra input lines consumed.
+# RE to recognize macros which may emit and probably will.
+set ::emitterMacrosRE {^[DEI]}
+# RE to recognize macros which certainly will not emit.
+set ::consumerMacrosRE {^[CS]}
+# RE to recognize macros which have gather/scatter operation, and will emit.
+set ::shufflerMacrosRE {^E}
+# Above 3 RE's are used to trigger needed #line emits and avoid useless ones.
 
-proc COLLECT_DISPATCH {hFile tailCapture ostrm} {
-  # Collect dispatch table entries, along with ordering info.
+set ::splat15 [string repeat * 15]
+set ::sharp15 "//[string repeat # 13]"
+
+# Put marker and possibly a #line directive signifying end of an inclusion.
+# Return number of lines emitted.
+proc includeEnd {fromFile returnFile lineNum ostrm} {
+  if {$returnFile eq ""} {
+  } else {
+    set rsay ", resume $returnFile"
+  }
+  if {$::tclGenerate} {
+    puts $ostrm "$::sharp15 End $fromFile$rsay $::sharp15"
+  } else {
+    puts $ostrm "/$::splat15 End $fromFile$rsay ${::splat15}/"
+  }
+  # Skip #line directives if not doing them, at end of outer includer,
+  # or processing Tcl. (At end of outer includer, #line is pointless.)
+  if {$::lineDirectives && !$::tclGenerate && $returnFile ne ""} {
+    puts $ostrm "#line $lineNum \"${returnFile}\""
+    return 2
+  }
+  return 1
+}
+# Possibly put a #line directive within the middle of an includee's output,
+# whether during input scan or upon deferred output.
+# Return number of lines emitted.
+proc includeMiddle {withinFile lineNum ostrm} {
+  if {$::lineDirectives && !$::tclGenerate} {
+    puts "#line $lineNum \"${withinFile}\""
+    return 1
+  }
+  return 0
+}
+# Put marker and possibly a #line directive signifying top of an inclusion.
+# Return number of lines emitted.
+proc includeBegin {startFile ostrm} {
+  if {$::tclGenerate} {
+    puts $ostrm "$::sharp25 Begin $startFile $::sharp25"
+  } else {
+    puts $ostrm "/$::splat25 Begin $startFile ${::splat25}/"
+  }
+  if {$::lineDirectives && !$::tclGenerate} {
+    puts $ostrm "#line 1 \"${startFile}\""
+    return 2
+  }
+  return 1
+}
+
+proc IGNORED_COMMANDS {inSrc tcSignedCmdGlom ostrm} {
+  # Cause the listed commands to be ignored or allowed to generate, as set
+  # by a preceeding + or - respectively in the list. This may be useful
+  # when statically extending the shell to avoid duplicate implementation.
+  # Commands never mentioned within this macro are allowed to generate.
+  set sign ""
+  foreach {. o} [regexp -inline -all {\s*([\-\+]|[\w]+)\s*} $tcSignedCmdGlom] {
+    if {![regexp {[\+\-\?]} $o . sign]} {
+      if {$sign eq "+"} {
+      } else {
+      }
+    }
+  }
+  return [list 0 0]
+  
+}
+
+proc COLLECT_DISPATCH {inSrc tailCaptureCmdOrStar ostrm} {
+  # Collect dispatch table entries, along with cmd(s) as ordering info.
+  foreach {infile istrm inLineNum} $inSrc {}
+  foreach {cmd} $tailCaptureCmdOrStar {}
   set iAte 0
-  set cmd [lindex $tailCapture 0]
-  set lx [gets $hFile]
-  while {![eof $hFile] && ![regexp {^\s*\];} $lx]} {
+  set lx [gets $istrm]
+  set disp_frag {}
+  while {![eof $istrm] && ![regexp {^\s*\];} $lx]} {
     lappend disp_frag $lx
     set grabCmd $::dispCfg(CMD_CAPTURE_RE)
     if {![regexp $grabCmd $lx ma dcmd]} {
@@ -243,11 +424,11 @@ proc COLLECT_DISPATCH {hFile tailCapture ostrm} {
     } else {
       set ::cmd_dispatch($dcmd) [list $lx]
     }
-    set lx [gets $hFile]
+    set lx [gets $istrm]
     incr iAte
   }
   incr iAte
-  return $iAte
+  return [list $iAte 0]
 }
 
 proc COMMENT {hFile tailCaptureIgnore ostrm} {