]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
For shell extension writers, reduce boilerplate (mimicing SQLITE_EXTENSION_INIT#...
authorlarrybr <larrybr@noemail.net>
Mon, 4 Apr 2022 17:27:59 +0000 (17:27 +0000)
committerlarrybr <larrybr@noemail.net>
Mon, 4 Apr 2022 17:27:59 +0000 (17:27 +0000)
FossilOrigin-Name: 761208132d99ad602b80c3fe2f0780020c54a3a4ca5be3d2b69b2e3c25e83bc1

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

index 28e76320a9c03532f1322581c360cba83d9e526b..aec4319366ce38d17070e283ccef62050b083ad2 100644 (file)
@@ -100,11 +100,14 @@ static const char * const zTclHelp =
 
 #include "shext_linkage.h"
 
-static struct ShExtAPI *pShExtApi = 0;
-static struct ExtHelpers *pExtHelpers = 0;
-
+/* Extension boiler-plate to dynamically link into host's SQLite library */
 SQLITE_EXTENSION_INIT1;
 
+/* Extension boiler-plate for a function to get ShellExtensionLink pointer
+ * from db passed to extension init() and define a pair of static API refs.
+ */
+SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
+
 /* This is not found in the API pointer table published for extensions: */
 #define sqlite3_enable_load_extension pExtHelpers->enable_load_extension
 
@@ -384,114 +387,116 @@ static void copy_complaint(char **pzErr, Tcl_Interp *pi){
 # define TCL_REPL 3
 #endif
 
-static const char * const zREPL =
+
 #if TCL_REPL==1 /* a fallback for debug */
-  "set line {}\n"
-  "while {![eof stdin]} {\n"
-    "if {$line!=\"\"} {\n"
-      "puts -nonewline \"> \"\n"
-    "} else {\n"
-      "puts -nonewline \"% \"\n"
-    "}\n"
-    "flush stdout\n"
-    "append line [gets stdin]\n"
-    "if {$line eq \".\"} break\n"
-    "if {[info complete $line]} {\n"
-      "if {[catch {uplevel #0 $line} result]} {\n"
-        "puts stderr \"Error: $result\"\n"
-      "} elseif {$result!=\"\"} {\n"
-        "puts $result\n"
-      "}\n"
-      "set line {}\n"
-    "} else {\n"
-      "append line \\n\n"
-    "}\n"
-  "}\n"
-  "if {$line ne \".\"} {puts {}}\n"
-  "read stdin 0\n"
+TCL_CSTR_LITERAL(static const char * const zREPL = ){
+  set line {}
+  while {![eof stdin]} {
+    if {$line!=""} {
+      puts -nonewline "> "
+    } else {
+      puts -nonewline "% "
+    }
+    flush stdout
+    append line [gets stdin]
+    if {$line eq "."} break
+    if {[info complete $line]} {
+      if {[catch {uplevel #0 $line} result]} {
+        puts stderr "Error: $result"
+      } elseif {$result!=""} {
+        puts $result
+      }
+      set line {}
+    } else {
+      append line \n
+    }
+  }
+  if {$line ne "."} {puts {}}
+  read stdin 0
+};
 #elif TCL_REPL==2 /* minimal use of shell's read */
-  "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 \"tcl% \"\n"
-      "}\n"
-    "}\n"
-    "flush stdout\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 ::REPL::at_end 1\n"
-    "} else {\n" 
-      "append ::REPL::line $::REPL::li\n"
-      "if {[string trim $::REPL::line] eq \"\"} {\n"
-        "set ::REPL::line \"\"\n"
-        "continue\n"
-      "}\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 ::REPL::line {}\n"
-      "}\n"
-    "}\n"
-  "}\n"
-  "if {$::REPL::prompting && $::REPL::li ne \".\\n\"} {puts {}}\n"
-  "namespace delete ::REPL\n"
-  "read stdin 0\n"
+TCL_CSTR_LITERAL(static const char * const zREPL = ){
+  namespace eval ::REPL {
+    variable line {}
+    variable at_end 0
+    variable prompting [now_interactive]
+  }
+  while {!$::REPL::at_end} {
+    if {$::REPL::prompting} {
+      if {$::REPL::line!=""} {
+        puts -nonewline "...> "
+      } else {
+        puts -nonewline "tcl% "
+      }
+    }
+    flush stdout
+    set ::REPL::li [get_input_line]
+    if {$::REPL::li eq ""} {
+      set ::REPL::at_end 1
+    } elseif {[string trimright $::REPL::li] eq "."} {
+      if {$::REPL::line ne ""} {
+        throw {NONE} {incomplete input at EOF}
+      }
+      set ::REPL::at_end 1
+    } else {
+      append ::REPL::line $::REPL::li
+      if {[string trim $::REPL::line] eq ""} {
+        set ::REPL::line ""
+        continue
+      }
+      if {[info complete $::REPL::line]} {
+        set ::REPL::rc [catch {uplevel #0 $::REPL::line} ::REPL::result]
+        if {$::REPL::rc == 0} {
+          if {$::REPL::result!="" && $::REPL::prompting} {
+            puts $::REPL::result
+          }
+        } elseif {$::REPL::rc == 1} {
+          puts stderr "Error: $::REPL::result"
+        } elseif {$::REPL::rc == 2} {
+          set ::REPL::at_end 1
+        }
+        set ::REPL::line {}
+      }
+    }
+  }
+  if {$::REPL::prompting && $::REPL::li ne ".\n"} {puts {}}
+  namespace delete ::REPL
+  read stdin 0
+};
 #elif TCL_REPL==3
-# define SHELL_REPL_CMDNAME "sqlite_shell_REPL"
-  /* using shell's input collection with line editing (if configured) */
-  SHELL_REPL_CMDNAME
+/* using shell's input collection with line editing (if configured) */
+static const char * const zREPL = "sqlite_shell_REPL";
+
+TCL_CSTR_LITERAL(static const char * const zDefineREPL = ){
+  proc sqlite_shell_REPL {} {
+    set interactive [now_interactive]
+    while {1} {
+      foreach {group ready} [get_tcl_group] {}
+      set trimmed [string trim $group]
+      if {$group eq "" && !$ready} break
+      if {$trimmed eq ""} continue
+      if {!$ready && $trimmed ne ""} {
+        throw {NONE} {incomplete input at EOF}
+      }
+      if {$trimmed eq "."} break
+      set rc [catch {uplevel #0 $group} result]
+      if {$rc == 0} {
+        if {$result != "" && $interactive} {
+          puts $result
+        }
+      } elseif {$rc == 1} {
+        puts stderr "Error: $result"
+      } elseif {$rc == 2} {
+        return -code 2
+      }
+    }
+    if {$interactive && $trimmed ne "."} {puts {}}
+    read stdin 0
+  }
+};
 #else
   "throw {NONE} {not built for REPL}\n"
 #endif
-  ; /* zREPL */
-
-static const char * const zDefineREPL =
-  "proc "SHELL_REPL_CMDNAME" {} {\n"
-    "set interactive [now_interactive]\n"
-    "while {1} {\n"
-      "foreach {group ready} [get_tcl_group] {}\n"
-      "set trimmed [string trim $group]\n"
-      "if {$group eq \"\" && !$ready} break\n"
-      "if {$trimmed eq \"\"} continue\n"
-      "if {!$ready && $trimmed ne \"\"} {\n"
-        "throw {NONE} {incomplete input at EOF}\n"
-      "}\n"
-      "if {$trimmed eq \".\"} break\n"
-      "set rc [catch {uplevel #0 $group} result]\n"
-      "if {$rc == 0} {\n"
-        "if {$result != \"\" && $interactive} {\n"
-          "puts $result\n"
-        "}\n"
-      "} elseif {$rc == 1} {\n"
-        "puts stderr \"Error: $result\"\n"
-      "} elseif {$rc == 2} {\n"
-        "return -code 2\n"
-      "}\n"
-    "}\n"
-    "if {$interactive && $trimmed ne \".\"} {puts {}}\n"
-    "read stdin 0\n"
-  "}\n"
-  ;
 
 /* Enter the preferred REPL */
 static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg){
@@ -525,6 +530,7 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4,
     return runTclREPL(getInterp(), pzErrMsg);
   }
 }
+
 DERIVED_METHOD(DotCmdRC, execute, MetaCommand,UnkCmd, 4,
              (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
   Tcl_Interp *interp = getInterp();
@@ -593,9 +599,9 @@ INSTANCE_END(TclSS) tclss = {
   &tclss_methods
 };
 
+#if TCL_REPL==2
 #define GETLINE_MAXLEN 1000
 
-#if TCL_REPL==2
 /* C implementation of TCL proc, get_input_line */
 static int getInputLine(void *pvSS, Tcl_Interp *interp,
                         int nArgs, const char *azArgs[]){
@@ -985,8 +991,8 @@ static const int numDbNames = 2;
 static int UserDbObjCmd(void *cd, Tcl_Interp *interp,
                         int objc, Tcl_Obj * const * objv){
   UserDb *pudb = (UserDb*)cd;
-  static const char *azDoHere[] = { "close", ".eval", 0 };
-  enum DbDoWhat { DDW_Close, DDW_ShellEval };
+  static const char *azDoHere[] = { "close", 0 };
+  enum DbDoWhat { DDW_Close };
   int doWhat;
   int whichDb = -1;
   const char *zMoan;
@@ -1004,10 +1010,6 @@ static int UserDbObjCmd(void *cd, Tcl_Interp *interp,
     case DDW_Close:
       zMoan = " close is disallowd. It is a wrapped DB.\n";
       goto complain_fail;
-    case DDW_ShellEval:
-      Tcl_AppendResult(interp, "Faking .eval ...\n", 0);
-      //... ToDo: Implement this.
-      return TCL_OK;
     }
   }
   if( pudb->numSdb==0 || whichDb<0 ){
@@ -1040,9 +1042,6 @@ static int userDbInit(Tcl_Interp *interp, ShellExState *psx){
   return TCL_ERROR;
 }
 
-/* Extension boiler-plate to grab ShellExtensionLink pointer from db. */
-DEFINE_SHDB_TO_SHEXTLINK(shext_link);
-
 /*
 ** Extension load function.
 */
@@ -1054,10 +1053,16 @@ int sqlite3_tclshext_init(
   char **pzErrMsg,
   const sqlite3_api_routines *pApi
 ){
-  ShellExtensionLink *pShExtLink;
   SQLITE_EXTENSION_INIT2(pApi);
-  pShExtLink = shext_link(db);
-  if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=5 ){
+  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
+
+  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
+  if( SHELL_EXTENSION_LOADFAIL(pShExtLink, 5, 10) ){
+    *pzErrMsg
+      = sqlite3_mprintf("No ShellExtensionLink or shell API is too old.\n"
+                        "Use '.load tclshext -shext' or update the shell.\n");
+    return SQLITE_ERROR;
+  }else{
     ShellExState *psx = pShExtLink->pSXS;
     Tcl_Obj *targv = Tcl_NewListObj(0, NULL);
     const char *zAppName = "tclshext";
@@ -1065,12 +1070,6 @@ int sqlite3_tclshext_init(
     int ldTk = 0;
     int rc = 0;
 
-    pShExtApi = & pShExtLink->pShellExtensionAPI->api.named;
-    pExtHelpers = & pShExtLink->pShellExtensionAPI->pExtHelpers->helpers.named;
-    if( pShExtLink->pShellExtensionAPI->pExtHelpers->helperCount < 10 ){
-      *pzErrMsg = sqlite3_mprintf("Shell version mismatch");
-      return SQLITE_ERROR;
-    }
     if( pShExtLink->nLoadArgs>0 ){
       int ila;
       for( ila=0; ila<pShExtLink->nLoadArgs; ++ila ){
@@ -1220,9 +1219,4 @@ int sqlite3_tclshext_init(
     }
     return rc;
   }
-  else{
-    *pzErrMsg
-      = sqlite3_mprintf("Bad ShellExtensionLink or registration API.\n");
-    return SQLITE_ERROR;
-  }
 }
index 7c230e2aaf80449249c6e9c8c6be5ebbc82caff6..0858fb3c0d06bc811a34490f3fab69895b55b6f6 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C For\sTCL\sextension:\sAdjust\sprovided\s"gui"\scommand,\sand\sdocument\sit.\nFor\sshell:\sSeparate\sshell\svariables\sfrom\sbinding\sparameters,\smainly\sso\sthey\slive\slonger,\sin\sthe\sshell\sDB.\nAdd\s.vars\sdot\scommand\sto\sreflect\sthis\sseparation,\sand\sspecialized\sfor\sshell\svariables.\nMuch\scode\sshuffling\sto\sshare\scode\sbetween\s.parameters\sand\s.vars\scommands.\n
-D 2022-04-04T06:33:43.022
+C For\sshell\sextension\swriters,\sreduce\sboilerplate\s(mimicing\sSQLITE_EXTENSION_INIT#\smacros)
+D 2022-04-04T17:27:59.089
 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 05768ea49bd04d3b1ca6287b0769ca63fa7453544db6c3c58d28ce7469ba2c4b
+F ext/misc/tclshext.c.in b09d1d38698f0d89fdfaae91231bf1ef017a56335b153f1bfb6d44e09d8d6674
 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
 F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@ -557,7 +557,7 @@ F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c c366c05e48e836ea04f8ecefb9c1225745dc250c3f01bdb39e9cbb0dc25e3610
 F src/shell.c.in 37740ab1661dcd54ec9577f632afc968692aa86001a7ce21f839a6077ec3c36c x
-F src/shext_linkage.h a07c4eaaeff29386a2be06773b779ff4a2a299fda966fd432b7258f191666fdc
+F src/shext_linkage.h 307e241b9fdc42ca02387303b0abdffd5afd04a5a8540807a5061a97fb2c26cd
 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 4f11e639f02e6ebe5ec9268cf066b83e6992599c049f497ebed704614b870706
-R de816fd352883164601c9ffe394f900e
+P fa492ff57ca9d89ac623734e8ed0411e29ed6a926c2f534f2a91e41fad994b46
+R 79ea17b79c197cddd70007ce0ce4c0bc
 U larrybr
-Z c0273a0e4655d42ef0fee3956260570a
+Z 9520f9c90493a0493ae4b54df22d1805
 # Remove this line to create a well-formed Fossil manifest.
index ca7ee226672a55660462aa4cb28d377305dbb432..b36e21ac637780b3153db906ab8893437f5ab0b7 100644 (file)
@@ -1 +1 @@
-fa492ff57ca9d89ac623734e8ed0411e29ed6a926c2f534f2a91e41fad994b46
\ No newline at end of file
+761208132d99ad602b80c3fe2f0780020c54a3a4ca5be3d2b69b2e3c25e83bc1
\ No newline at end of file
index 107f41035dd3896ff005dcaec8314e35e29db2fa..77cf8033796dc6089b9462c40dc2bb1cb66462b3 100644 (file)
@@ -362,7 +362,7 @@ typedef struct ShellExtensionAPI {
                                  ExtensionId eid, MetaCommand *pMC);
       /* Register query result data display (or other disposition) mode */
       int (*registerExporter)(ShellExState *p,
-                              ExtensionId eid, ExportHandler *pOMH);
+                              ExtensionId eid, ExportHandler *pEH);
       /* Register an import variation from (various sources) for .import */
       int (*registerImporter)(ShellExState *p,
                               ExtensionId eid, ImportHandler *pIH);
@@ -425,8 +425,8 @@ typedef struct ShellExtensionLink {
  * pointer to a ShellExtensionLink instance during an extension's *init*()
  * call (during shell extension load) or 0 (during SQLite extension load.)
  */
-#define DEFINE_SHDB_TO_SHEXTLINK(func_name) \
- static ShellExtensionLink * func_name(sqlite3 * db){ \
+#define DEFINE_SHDB_TO_SHEXTLINK(link_func_name) \
+ static ShellExtensionLink * link_func_name(sqlite3 * db){ \
   ShellExtensionLink *rv = 0; sqlite3_stmt *pStmt = 0; \
   if( SQLITE_OK==sqlite3_prepare_v2(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \
       && SQLITE_ROW == sqlite3_step(pStmt) ) \
@@ -435,6 +435,47 @@ typedef struct ShellExtensionLink {
   sqlite3_finalize(pStmt); return rv; \
  }
 
+/*
+ * Define boilerplate macros analogous to SQLITE_EXTENSION_INIT#
+ */
+/* Place at file scope prior to usage of the arguments by extension code. */
+#define SHELL_EXTENSION_INIT1( shell_api_ptr, ext_helpers_ptr, link_func ) \
+  static struct ShExtAPI *shell_api_ptr = 0; \
+  static struct ExtHelpers *ext_helpers_ptr = 0; \
+  DEFINE_SHDB_TO_SHEXTLINK(link_func)
+
+/* Place within sqlite3_x_init() among its local variable declarations. */
+#define SHELL_EXTENSION_INIT2( link_ptr, link_func, db_ptr ) \
+  ShellExtensionLink * link_ptr = link_func(db_ptr)
+
+/* Place within sqlite3_x_init() code prior to usage of the *_ptr arguments. */
+#define SHELL_EXTENSION_INIT3( shell_api_ptr, ext_helpers_ptr, link_ptr ) \
+ if( (link_ptr)!=0 ){ \
+  shell_api_ptr = &link_ptr->pShellExtensionAPI->api.named; \
+  ext_helpers_ptr = &link_ptr->pShellExtensionAPI->pExtHelpers->helpers.named; \
+ }
+
+/* This test may be used within sqlite3_x_init() after SHELL_EXTENSION_INIT3 */
+#define SHELL_EXTENSION_LINKED(link_ptr) ((link_ptr)!=0)
+/* These *_COUNT() macros help determine version compatibility.
+ * They should only be used when the above test yields true.
+ */
+#define SHELL_API_COUNT(link_ptr) \
+  (link_ptr->pShellExtensionAPI->numRegistrars)
+#define SHELL_HELPER_COUNT(link_ptr) \
+  (link_ptr->pShellExtensionAPI->pExtHelpers->helperCount)
+
+/* Combining the above, safely, to provide a single test for extensions to
+ * use for assurance that: (1) the load was as a shell extension (with the
+ * -shext flag rather than bare .load); and (2) the loading host provides
+ * stated minimum extension API and helper counts.
+ */
+#define SHELL_EXTENSION_LOADFAIL(link_ptr, minNumApi, minNumHelpers) \
+  (!SHELL_EXTENSION_LINKED(link_ptr) \
+   || SHELL_API_COUNT(link_ptr)<(minNumApi) \
+   || SHELL_HELPER_COUNT(link_ptr)<(minNumHelpers) \
+  )
+
 #ifdef __cplusplus
 } // extern "C"
 #endif