From: larrybr Date: Mon, 4 Apr 2022 17:27:59 +0000 (+0000) Subject: For shell extension writers, reduce boilerplate (mimicing SQLITE_EXTENSION_INIT#... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=82a3fe26f0d8775958af4d06fdca02c77e411add;p=thirdparty%2Fsqlite.git For shell extension writers, reduce boilerplate (mimicing SQLITE_EXTENSION_INIT# macros) FossilOrigin-Name: 761208132d99ad602b80c3fe2f0780020c54a3a4ca5be3d2b69b2e3c25e83bc1 --- diff --git a/ext/misc/tclshext.c.in b/ext/misc/tclshext.c.in index 28e76320a9..aec4319366 100644 --- a/ext/misc/tclshext.c.in +++ b/ext/misc/tclshext.c.in @@ -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; ilanLoadArgs; ++ila ){ @@ -1220,9 +1219,4 @@ int sqlite3_tclshext_init( } return rc; } - else{ - *pzErrMsg - = sqlite3_mprintf("Bad ShellExtensionLink or registration API.\n"); - return SQLITE_ERROR; - } } diff --git a/manifest b/manifest index 7c230e2aaf..0858fb3c0d 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index ca7ee22667..b36e21ac63 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fa492ff57ca9d89ac623734e8ed0411e29ed6a926c2f534f2a91e41fad994b46 \ No newline at end of file +761208132d99ad602b80c3fe2f0780020c54a3a4ca5be3d2b69b2e3c25e83bc1 \ No newline at end of file diff --git a/src/shext_linkage.h b/src/shext_linkage.h index 107f41035d..77cf803379 100644 --- a/src/shext_linkage.h +++ b/src/shext_linkage.h @@ -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