From 7c4d15a128aea4cf7ad6065707d9ff524f690bde Mon Sep 17 00:00:00 2001 From: larrybr Date: Wed, 23 Mar 2022 21:03:48 +0000 Subject: [PATCH] Scripting support roughed in, with a demo extension. More work is needed to make this truly useful. Tests are missing. FossilOrigin-Name: abf0316b3f58646974ab8e4d3e68896c9fc03bdd338eb7dc7b2f5d4de7365298 --- ext/misc/tclshext.c.in | 350 +++++++++++++++++++++++++++++++++++++++++ manifest | 22 +-- manifest.uuid | 2 +- src/loadext.c | 10 +- src/shell.c.in | 270 ++++++++++++++++++++++--------- src/shext_linkage.h | 122 ++++++++------ src/sqlite3ext.h | 9 ++ tool/mkshellc.tcl | 51 ++++-- 8 files changed, 693 insertions(+), 143 deletions(-) create mode 100644 ext/misc/tclshext.c.in mode change 100644 => 100755 tool/mkshellc.tcl diff --git a/ext/misc/tclshext.c.in b/ext/misc/tclshext.c.in new file mode 100644 index 0000000000..c7dba8107c --- /dev/null +++ b/ext/misc/tclshext.c.in @@ -0,0 +1,350 @@ +/* +** 2022 March 20 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement the "tclshext" shell extension +** for use with the extensible "sqlite3" CLI shell. On *Nix, build thusly: + tool/mkshellc.tcl ext/misc/tclshext.c.in > tclshext.c + 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 two features to the host shell: +** 1. The .tcl dot command is added. +** 2. TCL scripting support is added. +** +** The .tcl command can be run with 0 or more arguments. +** With no arguments, it does a REPL loop until the end of input is seen. +** The end of input is either an EOF condition or a lone '.' on a line. +** With more arguments, files they name are interpreted as TCL script. +** In either case, the TCL command return code is tranlated to a DotCmdRC. +** +** 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, one +** complete TCL command at a time. +** +** For any of these ways of providing TCL input, the same TCL interpreter +** is used, with its state maintained from one input to the next. In this +** way, .sqliterc or other preparatory shell scripts (or typing) can be +** made to provide a useful set of user-defined shell enhancements. +*/ + +#include +#include "shext_linkage.h" + +static struct ShExtAPI *pShExtApi = 0; +static struct ExtHelpers *pExtHelpers = 0; + +SQLITE_EXTENSION_INIT1; + +/* This is not found in the API pointer table published for extensions: */ +#define sqlite3_enable_load_extension pExtHelpers->enable_load_extension + +/* Control how tclsqlite.c compiles. (REPL is effected here, not there.) */ +#undef SQLITE_AMALGAMATION +#undef TCLSH +#include +INCLUDE tclsqlite.c + +#if defined(_WIN32) || defined(WIN32) +# define getDir(cArray) _getwd(cArray) +# define chdir(s) _chdir(s) +#else +# define getDir(cArray) getcwd(cArray, sizeof(cArray)) +#endif + +typedef struct TclCmd TclCmd; + +static void TclCmd_Takedown(TclCmd *ptc); + +/* These DERIVED_METHOD(...) macro calls' arguments were copied and + * pasted from the MetaCommand interface declaration in shext_linkage.h + */ +DERIVED_METHOD(void, destruct, MetaCommand,TclCmd, 0, ()){ + TclCmd_Takedown((TclCmd *)pThis); +} + +DERIVED_METHOD(const char *, name, MetaCommand,TclCmd, 0,()){ + return "tcl"; +} + +DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(int more)){ + switch( more ){ + case 0: return + ".tcl ?FILES? Run a TCL REPL or interpret files as TCL\n"; + case 1: return + " If FILES are provided, they name files to be read in as TCL.\n" + " Otherwise, a read/evaluate/print loop is run until a lone \".\" is\n" + " entered on an input line or end-of-stream is encountered.\n"; + default: return 0; + } +} + +DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3, + (char **pzErrMsg, int nArgs, char *azArgs[])){ + return DCR_Ok; +} + +static Tcl_Interp *getInterp(TclCmd *ptc); + +static void copy_complaint(char **pzErr, Tcl_Interp *pi){ + if( pzErr ){ + Tcl_Obj *po = Tcl_GetObjResult(pi); + *pzErr = sqlite3_mprintf("%s", Tcl_GetStringFromObj(po,0)); + } +} + +DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4, + (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){ + FILE *out = pExtHelpers->currentOutputFile(psx); + TclCmd *ptc = (TclCmd *)pThis; + DotCmdRC rv = DCR_Ok; + int rc = TCL_OK; + if( nArgs>1 ){ + /* Read named files into the interpreter. */ + int aix; + for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){ + rc = Tcl_EvalFile(getInterp(ptc), azArgs[aix+1]); + } + }else{ + /* Enter a REPL */ + static const char * const zREPL = +#ifdef REPL_STDIN_ONLY + "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" +#else + "set line {}\n" + "set at_end 0\n" + "set prompting [now_interactive]\n" + "while {!$at_end} {\n" + "if {$prompting} {\n" + "if {$line!=\"\"} {\n" + "puts -nonewline \"> \"\n" + "} else {\n" + "puts -nonewline \"% \"\n" + "}\n" + "}\n" + "flush stdout\n" + "set li [get_input_line]\n" + "if {$li eq \"\"} {\n" + "set at_end 1\n" + "} elseif {[string trimright $li] eq \".\"} {\n" + "if {$line ne \"\"} {\n" + "throw {NONE} {incomplete input at EOF}\n" + "}\n" + "set at_end 1\n" + "} else {\n" + "append line $li\n" + "if {[string trim $line] eq \"\"} {\n" + "set line \"\"\n" + "continue\n" + "}\n" + "if {[info complete $line]} {\n" + "if {[catch {uplevel #0 $line} result]} {\n" + "puts stderr \"Error: $result\"\n" + "} elseif {$result!=\"\" && $prompting} {\n" + "puts $result\n" + "}\n" + "set line {}\n" + "}\n" + "}\n" + "}\n" + "if {$prompting && $li ne \".\\n\"} {puts {}}\n" + "unset li line prompting at_end\n" + "read stdin 0\n" +#endif + ; + rc = Tcl_Eval(getInterp(ptc), zREPL); + clearerr(stdin); + } + if( rc!=TCL_OK ){ + copy_complaint(pzErrMsg, getInterp(ptc)); + rv = DCR_Error; + } + return rv; +} + +/* Define a MetaCommand v-table initialized to reference above methods. */ +MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods); + +INSTANCE_BEGIN(TclCmd); + Tcl_Interp *interp; +INSTANCE_END(TclCmd) tclcmd = { + &tclcmd_methods + , 0 /* interp pointer */ +}; + +static Tcl_Interp *getInterp(TclCmd *ptc){ + return ptc->interp; +} + +static void TclCmd_Takedown(TclCmd *ptc){ + Tcl_DeleteInterp(ptc->interp); + ptc->interp = 0; + Tcl_Finalize(); +} + +/* Say line is script lead-in iff its first dark is "..". */ +static int tclIsScriptLead(void *pvState, const char *zLineLead){ + char c; + (void)(pvState); + while( (c=*zLineLead++) && (c==' '||c=='\t') ) {} + return (c=='.' && *zLineLead=='.'); +} + +static int tclIsComplete(void *pvState, const char *zScript){ + (void)(pvState); + return Tcl_CommandComplete(zScript); +} + +static DotCmdRC tclRunScript(void *pvState, const char *zScript, + ShellExState *p, char **pzErrMsg){ + char c; + TclCmd *ptc = (TclCmd *)pvState; + while( (c=*zScript++) && (c==' '||c=='\t') ) {} + if( c=='.' && *zScript++=='.' ){ + int rc, nc = strlen(zScript); + rc = Tcl_EvalEx(ptc->interp, zScript, nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL); + if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp(ptc)); + return DCR_Ok|(rc!=TCL_OK); + } + return DCR_Error; +} + +#define GETLINE_MAXLEN 1000 + +/* C implementation of TCL proc, get_input_line */ +int getInputLine(void *pvSS, Tcl_Interp *interp, + int nArgs, const char *azArgs[]){ + if( nArgs==1 ){ + char buffer[GETLINE_MAXLEN+1]; + ShellExState *psx = (ShellExState *)pvSS; + struct InSource *pis = pExtHelpers->currentInputSource(psx); + if( pExtHelpers->strLineGet(buffer, GETLINE_MAXLEN, pis) ){ + Tcl_SetResult(interp, buffer, TCL_VOLATILE); + }else{ + Tcl_SetResult(interp, 0, 0); + } + return TCL_OK; + }else{ + Tcl_SetResult(interp, "too many arguments", TCL_STATIC); + return TCL_ERROR; + } +} + +/* C implementation of TCL proc, now_interactive */ +int nowInteractive(void *pvSS, Tcl_Interp *interp, + int nArgs, const char *azArgs[]){ + if( nArgs==1 ){ + ShellExState *psx = (ShellExState *)pvSS; + struct InSource *pis = pExtHelpers->currentInputSource(psx); + static const char * zAns[2] = { "0","1" }; + int iiix = (pExtHelpers->nowInteractive(psx) != 0); + Tcl_SetResult(interp, (char *)zAns[iiix], TCL_STATIC); + return TCL_OK; + }else{ + Tcl_SetResult(interp, "too many arguments", TCL_STATIC); + return TCL_ERROR; + } +} + +/* ToDo: C implementation of TCL ::unknown to reflect to dot commands */ + +/* ToDo: ... TCL db_user command, like a (TCL) sqlite3 object except that + * it defers to shell's db and treats close subcommand as an error. */ + +DEFINE_SHDB_TO_SHEXTLINK(shext_link); + +/* +** Extension load function. +*/ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_tclshext_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + ShellExtensionLink *pShExtLink; + SQLITE_EXTENSION_INIT2(pApi); + pShExtLink = shext_link(db); + if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){ + ShellExState *psx = pShExtLink->pSXS; + MetaCommand *pmc = (MetaCommand *)&tclcmd; + const char *zShellName, *zShellDir; + int rc; + + 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; + } + zShellName = pExtHelpers->shellInvokedAs(); + zShellDir = pExtHelpers->shellStartupDir(); + if( zShellDir!=0 ){ + char cwd[FILENAME_MAX+1]; + if( getDir(cwd) && 0==chdir(zShellDir) ){ + Tcl_FindExecutable(zShellName); + rc = chdir(cwd); /* result ignored, kept only to silence gcc */ + } + } + tclcmd.interp = Tcl_CreateInterp(); + if( 0==Tcl_OOInitStubs(tclcmd.interp) ){ + *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n"); + TclCmd_Takedown(&tclcmd); + return SQLITE_ERROR; + } + Tcl_SetSystemEncoding(tclcmd.interp, "utf-8"); + Sqlite3_Init(tclcmd.interp); + rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init, pmc); + if( rc==SQLITE_OK ){ + ScriptHooks sh = { pmc, tclIsScriptLead, tclIsComplete, tclRunScript }; + pShExtApi->hookScripting(psx, sqlite3_tclshext_init, &sh); + Tcl_CreateCommand(tclcmd.interp, + "get_input_line", getInputLine, psx, 0); + Tcl_CreateCommand(tclcmd.interp, + "now_interactive", nowInteractive, psx, 0); + pShExtLink->eid = sqlite3_tclshext_init; + }else{ + TclCmd_Takedown(&tclcmd); + } + return rc; + } + else{ + *pzErrMsg + = sqlite3_mprintf("Bad ShellExtensionLink or registration API.\n"); + return SQLITE_ERROR; + } +} diff --git a/manifest b/manifest index 9ca7448206..87bbba5b5a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C macro-ize\sextension\sboiler-plate,\simprove\sexit\sprocessing -D 2022-03-19T14:27:57.463 +C Scripting\ssupport\sroughed\sin,\swith\sa\sdemo\sextension.\sMore\swork\sis\sneeded\sto\smake\sthis\struly\suseful.\sTests\sare\smissing. +D 2022-03-23T21:03:48.356 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -329,6 +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 9e8909361fb8e26488920acd6629753b5bf38541cd0c4e6a16f6532b7bf367c6 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b @@ -516,7 +517,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c d9fd15b2bd030cb9bd3119b301dbdb2912f16fff76c6e3797296cfd1500faaf4 F src/json.c 24fcd7f5f9080b04b89722c343010d390f85e55b2ab560046cb567c9dd640f62 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c aa919a6a7884f8b34d7b791841b24d14b1b0ab43f45b3940f4851043b2855c0c +F src/loadext.c 2ecb1441f9b1c22e9e022ee0776e67d259facf34b56ba892b206f0a294ee6f8c F src/main.c 89dfd569b4fbcab65281b3c6d636b887b2cb23cbaa16f8c6b67062862144c927 F src/malloc.c fec841aa0a0400a6f7d20706178a5d8e8219a6bf562b6fe712c17f6c26813266 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 @@ -555,11 +556,11 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 4890a3cfee0bc60ff231c3a44db37968859ab0be156983dbcc0c096109832cdd -F src/shell.c.in acb97f8ad071498a35748d31b86d5797745ba236e042d54ae31adb8c23a27581 -F src/shext_linkage.h 6c75ac9690965ae6968b5b246f83bde6bac9f13a248e5ac0f5775d2ad8a35496 +F src/shell.c.in 840a4a72dcc39fafbcc82275babc77f928c3531cb5fe7217cb0a1596ef0a4555 +F src/shext_linkage.h 71b3600ba0e20f696fb226547e99413c67cfb27c5532701df16935e2a45c152a F src/sqlite.h.in 5845213799feca09cd69d18ff841a85fe0df31021f46aaa1797e703e80dc1d70 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 +F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e F src/sqliteInt.h 2ce7d868630ccd70ffd4b15d46b59ccf7daf89198993b62ed6e4a165d3511280 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 @@ -1883,7 +1884,7 @@ F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61 F tool/mkopcodeh.tcl 5dab48c49a25452257494e9601702ab63adaba6bd54a9b382615fa52661c8f8c F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d -F tool/mkshellc.tcl 8231651ce215baea7d003d7489d962d0dc286cbabc41a858790dd188fe889651 +F tool/mkshellc.tcl a8db284614942c48e35e58f2a3a2e762f3bae0262e81acd1dbfc4813fd304d85 x F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f @@ -1949,8 +1950,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 898088008e2d15f89941db433f743594aea1351f353c12ce3932a39902dfb161 -R 129774c9c2e8cac58d8036d38701569e +P 7996d3a359ab90ef9f42f501e0ddb2efa964a906739116836cb1eafe2a96a0ed +Q +da874180d35aacdeb9c06f5b425e8909d833e2765179c4337854d56b8a624fd5 +R c463fc7c0074cded5ea87643f5ddeef3 U larrybr -Z 76b79faefcf881b072c961370ce1a08d +Z cf7f7bb9e5e070c2442801336636b4c2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 9c7218fc0b..3924874b43 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7996d3a359ab90ef9f42f501e0ddb2efa964a906739116836cb1eafe2a96a0ed \ No newline at end of file +abf0316b3f58646974ab8e4d3e68896c9fc03bdd338eb7dc7b2f5d4de7365298 \ No newline at end of file diff --git a/src/loadext.c b/src/loadext.c index 603516e18a..cefe2eb94c 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -491,7 +491,15 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_vtab_distinct, sqlite3_vtab_in, sqlite3_vtab_in_first, - sqlite3_vtab_in_next + sqlite3_vtab_in_next, + /* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE + sqlite3_deserialize, + sqlite3_serialize +#else + 0, + 0 +#endif }; /* True if x is the directory separator character diff --git a/src/shell.c.in b/src/shell.c.in index c8e80184b0..566bace22b 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -456,6 +456,12 @@ static volatile int seenInterrupt = 0; ** in a number of other places, mostly for error messages. */ static char *Argv0; +static char startupDir[PATH_MAX+1] = {0}; +#if defined(_WIN32) || defined(WIN32) +# define initStartupDir() _getwd(startupDir) +#else +# define initStartupDir() getcwd(startupDir, sizeof(startupDir)) +#endif /* ** Prompt strings. Initialized in main. Settable with @@ -1229,6 +1235,7 @@ struct EQPGraph { typedef struct ShExtInfo { ExtensionId extId; /* The xInit function pointer */ void (*extDtor)(void *); /* Extension shutdown on exit or unload */ + void *pvExtObj; /* Passed to extDtor(...) at shutdown */ /* Each shell extension library registers 0 or more of its extension * implementations, interfaces to which are kept in below dynamic. * arrays. The dbShell DB keeps indices into these arrays and into @@ -1367,7 +1374,11 @@ typedef struct ShellInState { #if SHELL_DYNAMIC_EXTENSION int numExtLoaded; /* Number of extensions presently loaded or emulated */ ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */ + int ixExtPending; /* Index of pending extension load operations if !0 */ + ScriptHooks scripting; /* Hooks for scripting support from loaded extension */ + ExtensionId scriptXid; /* Id of extension which has set scripting hooks */ #endif + ShellExState *pSXS; /* Pointer to companion, exposed shell state */ } ShellInState; @@ -7687,38 +7698,41 @@ FROM (\ } #if SHELL_DYNAMIC_EXTENSION -/* Register a meta-command */ +/* Ensure there is room in loaded extension info list for one being loaded. + * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded. + */ +static ShExtInfo *pending_ext_info(ShellInState *psi){ + int ixpe = psi->ixExtPending; + assert(ixpe!=0); + if( ixpe >= psi->numExtLoaded ){ + psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded, + (ixpe+1)*sizeof(ShExtInfo)); + shell_check_oom(psi->pShxLoaded); + ++psi->numExtLoaded; + memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo)); + } + return &psi->pShxLoaded[ixpe]; +} + +/* Register a meta-command, to be called during extension load/init. */ static int register_meta_command(ShellExState *p, ExtensionId eid, MetaCommand *pMC){ ShellInState *psi = ISS(p); - ShExtInfo *psei = 0; + ShExtInfo *psei = pending_ext_info(psi); const char *zSql = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, ?, ?)"; - int nle = psi->numExtLoaded; - int ie; + int ie = psi->ixExtPending; + assert(psi->pShxLoaded!=0 && p->dbShell!=0); if( pMC==0 ) return SQLITE_ERROR; - assert(psi->pShxLoaded!=0 && nle>0 && p->dbShell!=0); - for( ie=0; iepShxLoaded[ie].extId==eid ){ - psei = &psi->pShxLoaded[ie]; - break; - } - } - if( psei==0 ){ - ShExtInfo sei = {eid,0}; - psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded, - (nle+1)*sizeof(ShExtInfo)); - shell_check_oom(psi->pShxLoaded); - psei = &psi->pShxLoaded[psi->numExtLoaded]; - psi->pShxLoaded[psi->numExtLoaded++] = sei; - ie = nle; - } - { + else{ const char *zName = pMC->pMethods->name(pMC); sqlite3_stmt *pStmt; - int rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0); int nc = psei->numMetaCommands; - if( rc!=SQLITE_OK ) return SQLITE_ERROR; + int rc; + if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE; + psei->extId = eid; + rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; psei->ppMetaCommands = sqlite3_realloc(psei->ppMetaCommands, (nc+1)*sizeof(MetaCommand *)); shell_check_oom(psei->ppMetaCommands); @@ -7750,6 +7764,22 @@ static int register_importer(ShellExState *p, return SQLITE_ERROR; } +static int hook_scripting(ShellExState *p, ExtensionId eid, ScriptHooks *pSH){ + 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 ){ + return SQLITE_MISUSE; + } + psi->scripting = *pSH; + psi->scriptXid = eid; + return SQLITE_OK; +} + + static FILE *currentOutputFile(ShellExState *p){ return ISS(p)->out; } @@ -7762,11 +7792,19 @@ static int nowInteractive(ShellExState *p){ return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource); } +static const char *shellInvokedAs(void){ + return Argv0; +} + +static const char *shellStartupDir(void){ + return startupDir; +} + static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths); static MetaCommand * findMetaCommand(const char *, ShellExState *, int *); static ExtensionHelpers extHelpers = { - 7, + 10, { failIfSafeMode, currentOutputFile, @@ -7775,15 +7813,19 @@ static ExtensionHelpers extHelpers = { findMetaCommand, setColumnWidths, nowInteractive, + shellInvokedAs, + shellStartupDir, + sqlite3_enable_load_extension, 0 } }; static ShellExtensionAPI shellExtAPI = { - &extHelpers, 3, { + &extHelpers, 4, { register_meta_command, register_out_mode, register_importer, + hook_scripting, 0 } }; @@ -7891,34 +7933,49 @@ static int begin_db_dispatch(ShellExState *psx){ return SQLITE_OK; } +/* Call one loaded extension's destructors, in reverse order + * of their objects' creation, then free the tracking dyna-arrays. + */ +static void free_one_shext_tracking(ShExtInfo *psei){ + int j; + if( psei->ppMetaCommands!=0 ){ + for( j=psei->numMetaCommands; j>0; --j ){ + MetaCommand *pmc = psei->ppMetaCommands[j-1]; + if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc); + } + 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); + } + sqlite3_free(psei->ppOutModeHandlers); + } + if( psei->ppImportHandlers!=0 ){ + for( j=psei->numImportHandlers; j>0; --j ){ + ImportHandler *pih = psei->ppImportHandlers[j-1]; + if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih); + } + sqlite3_free(psei->ppImportHandlers); + } + if( psei->extDtor!=0 ){ + psei->extDtor(psei->pvExtObj); + } +} + /* Call all existent loaded extension destructors, in reverse order * of their objects' creation, then free the tracking dyna-arrays. */ -static void free_shext_tracking(ShellInState *psi){ - int i, j; +static void free_all_shext_tracking(ShellInState *psi){ if( psi->pShxLoaded!=0 ){ - for( i=psi->numExtLoaded; i>0; --i ){ - ShExtInfo *psei = &psi->pShxLoaded[i-1]; - if( psei->ppMetaCommands!=0 ){ - for( j=psei->numMetaCommands; j>0; --j ){ - MetaCommand *pmc = psei->ppMetaCommands[j-1]; - if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc); - } - 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); - } - sqlite3_free(psei->ppOutModeHandlers); - } - if( psei->ppImportHandlers!=0 ){ - for( j=psei->numImportHandlers; j>0; --j ){ - ImportHandler *pih = psei->ppImportHandlers[j-1]; - if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih); - } - sqlite3_free(psei->ppImportHandlers); + int i = psi->numExtLoaded; + while( i>1 ){ + ShExtInfo *psei = &psi->pShxLoaded[--i]; + free_one_shext_tracking(psei); + if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){ + memset(&psi->scripting, 0, sizeof(ScriptHooks)); + psi->scriptXid = 0; } } sqlite3_free(psi->pShxLoaded); @@ -7945,14 +8002,20 @@ static int load_shell_extension(ShellExState *psx, const char *zFile, psx, /* pSXS */ 0, /* zErrMsg */ 0, /* ExtensionId */ - 0 /* Extension destructor */ - }; + 0, /* Extension destructor */ + 0 /* Extension data ref */ + }; //extDtor(pvExtObj) + ShellInState *psi = ISS(psx); + /* save script hooking state for possible fallback if load fails */ + ScriptHooks shSave = psi->scripting; + ExtensionId siSave = psi->scriptXid; int rc; if( psx->dbShell==0 ){ rc = begin_db_dispatch(psx); if( rc!=SQLITE_OK ) return rc; assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0); } + psi->ixExtPending = psi->numExtLoaded; sqlite3_create_function(psx->dbShell, "shext_pointer", 1, SQLITE_DIRECTONLY|SQLITE_UTF8, &shxLink, shell_linkage, 0, 0); @@ -7963,7 +8026,22 @@ static int load_shell_extension(ShellExState *psx, const char *zFile, if( pzErr!=0 ) *pzErr = shxLink.zErrMsg; if( rc==SQLITE_OK ){ /* Keep extension's id and destructor for later disposal. */ + ShExtInfo *psei = pending_ext_info(psi); + if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE; + psei->extId = shxLink.eid; + psei->extDtor = shxLink.extensionDestruct; + psei->pvExtObj = shxLink.pvExtensionObject; + }else{ + /* Release all resources extension might have registered before failing. */ + if( psi->ixExtPending < psi->numExtLoaded ){ + 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; } + psi->ixExtPending = 0; return rc!=SQLITE_OK; } #endif @@ -8991,7 +9069,7 @@ DISPATCHABLE_COMMAND( help 3 1 3 ){ if( nArg==3 && strcmp(z, zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){ /* Show the undocumented command help */ zPat = zHelpAll; - }else if( strcmp(z,"-a")==0 || strcmp(z,"-all")==0 || strcmp(z,"--all")==0 ){ + }else if( strcmp(z,"-a")==0 || optionMatch(z, "all") ){ zPat = ""; }else{ zPat = z; @@ -13755,7 +13833,11 @@ static DotCmdRC process_input(ShellInState *psi){ /* An ordered enum to record kind of incoming line group. Its ordering * means than a value greater than Comment implies something runnable. */ - enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd; + enum { Tbd = 0, Eof, Comment, Sql, Cmd +#if SHELL_DYNAMIC_EXTENSION + , Script +#endif + } inKind = Tbd; /* An enum signifying the group disposition state */ enum { Incoming, Runnable, Dumpable, Erroneous, Ignore @@ -13787,26 +13869,35 @@ static DotCmdRC process_input(ShellInState *psi){ ? skipWhite(zLineInput)-zLineInput : 0; /* Disallow leading whitespace for . or # in legacy mode. */ #endif - switch( zLineInput[ndcLeadWhite] ){ - case '.': - inKind = Cmd; - dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState); - break; - case '#': - inKind = Comment; - break; - default: - /* Might be SQL, or a swallowable whole SQL comment. */ - sql_prescan(zLineInput, &sqScanState); - if( SSS_PLAINWHITE(sqScanState) ){ - /* It's either all blank or a whole SQL comment. Swallowable. */ +#if SHELL_DYNAMIC_EXTENSION + if( psi->scripting.isScriptLeader!=0 + && psi->scripting.isScriptLeader(psi->scripting.pvScriptingState, + zLineInput+ndcLeadWhite) ){ + inKind = Script; + }else +#endif + { + switch( zLineInput[ndcLeadWhite] ){ + case '.': + inKind = Cmd; + dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState); + break; + case '#': inKind = Comment; - }else{ - /* Something dark, not a # comment or dot-command. Must be SQL. */ - inKind = Sql; + break; + default: + /* Might be SQL, or a swallowable whole SQL comment. */ + sql_prescan(zLineInput, &sqScanState); + if( SSS_PLAINWHITE(sqScanState) ){ + /* It's either all blank or a whole SQL comment. Swallowable. */ + inKind = Comment; + }else{ + /* Something dark, not a # comment or dot-command. Must be SQL. */ + inKind = Sql; + } + break; } - break; - } /* end classification switch */ + } } /* end read/classify initial group input line */ /* Here, if not at end of input, the initial line of group is in, and @@ -13832,6 +13923,15 @@ static DotCmdRC process_input(ShellInState *psi){ #endif disposition = Runnable; /* Legacy, any dot-command line is ready. */ break; +#if SHELL_DYNAMIC_EXTENSION + case Script: + if( psi->scripting.scriptIsComplete==0 + || psi->scripting.scriptIsComplete(psi->scripting.pvScriptingState, + *pzLineUse+ndcLeadWhite) ){ + disposition = Runnable; + } + break; +#endif case Sql: /* Check to see if it is complete and ready to run. */ if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){ @@ -13920,6 +14020,31 @@ static DotCmdRC process_input(ShellInState *psi){ if( dcr > termKind ) termKind = dcr; break; } +#if SHELL_DYNAMIC_EXTENSION + case Script: { + if( psi->scripting.runScript!=0 ){ + char *zErr = 0; + DotCmdRC dcr + = psi->scripting.runScript(psi->scripting.pvScriptingState, + *pzLineUse+ndcLeadWhite, + 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); + } + } + }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; + } +#endif default: assert(inKind!=Tbd); break; @@ -14352,6 +14477,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ assert( argc>=1 && argv && argv[0] ); Argv0 = argv[0]; + initStartupDir(); #ifdef SQLITE_SHELL_DBNAME_PROC { @@ -14844,7 +14970,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ clearTempFile(&data); sqlite3_free(data.zEditor); #if SHELL_DYNAMIC_EXTENSION - free_shext_tracking(&data); + free_all_shext_tracking(&data); sqlite3_close(datax.dbShell); #endif #if !SQLITE_SHELL_IS_UTF8 diff --git a/src/shext_linkage.h b/src/shext_linkage.h index b1d33c4751..eaa8054b7d 100644 --- a/src/shext_linkage.h +++ b/src/shext_linkage.h @@ -13,6 +13,46 @@ extern "C" { #endif +/***************** + * See "Shell Extensions, Programming" for purposes and usage of the following + * interfaces supporting extended meta-commands and import and output modes. + */ + +/* Define status codes returned by a meta-command, either during its argument + * checking or during its execution (to which checking may be deferred.) The + * code has 1 or 2 parts. The low-valued codes, below MCR_ArgIxMask, have an + * action part and an error flag. Higher-valued codes are bitwise-or'ed with + * a small integer and indicate problems with the meta-command itself. + */ +typedef enum DotCmdRC { + /* Post-execute action and success/error status (semi-ordered) */ + DCR_Ok = 0, /* ordinary success and continue */ + DCR_Error = 1, /* or'ed with low-valued codes upon error */ + DCR_Return = 2, /* return from present input source/script */ + DCR_ReturnError = 3, /* return with error */ + DCR_Exit = 4, /* exit shell ( process or pseudo-main() ) */ + DCR_ExitError = 5, /* exit with error */ + DCR_Abort = 6, /* abort for unrecoverable cause (OOM) */ + DCR_AbortError = 7, /* abort with error (blocked unsafe) */ + /* Above are in reverse-priority order for process_input() returns. */ + + /* Dispatch and argument errors */ + DCR_ArgIxMask = 0xfff, /* mask to retain/exclude argument index */ + /* Below codes may be or'ed with the offending argument index */ + DCR_Unknown = 0x1000, /* unknown command, subcommand or option */ + DCR_Ambiguous = 0x2000, /* ambiguous (sub)command (too abreviated) */ + DCR_Unpaired = 0x3000, /* option value indicated but missing */ + DCR_TooMany = 0x4000, /* excess arguments were provided */ + DCR_TooFew = 0x5000, /* insufficient arguments provided */ + DCR_Missing = 0x6000, /* required argument(s) missing */ + DCR_ArgWrong = 0x7000, /* non-specific argument error, nothing emitted */ + + /* This code indicates error and a usage message to be emitted to stderr. */ + DCR_SayUsage = 0x7ffd, /* usage is at *pzErr or is to be generated */ + /* This code indicates nothing more need be put to stderr (or stdout.) */ + DCR_CmdErred = 0x7fff /* non-specific error for which complaint is done */ +} DotCmdRC; + /* Convey data to, from and/or between I/O handlers and meta-commands. */ typedef struct ShellExState { /* A sizeof(*) to permit extensions to guard against too-old hosts */ @@ -81,46 +121,6 @@ typedef struct ShellExState { struct ShellInState *pSIS; /* Offset of this member is NOT STABLE. */ } ShellExState; -/***************** - * See "Shell Extensions, Programming" for purposes and usage of the following - * interfaces supporting extended meta-commands and import and output modes. - */ - -/* Define status codes returned by a meta-command, either during its argument - * checking or during its execution (to which checking may be deferred.) The - * code has 1 or 2 parts. The low-valued codes, below MCR_ArgIxMask, have an - * action part and an error flag. Higher-valued codes are bitwise-or'ed with - * a small integer and indicate problems with the meta-command itself. - */ -typedef enum DotCmdRC { - /* Post-execute action and success/error status (semi-ordered) */ - DCR_Ok = 0, /* ordinary success and continue */ - DCR_Error = 1, /* or'ed with low-valued codes upon error */ - DCR_Return = 2, /* return from present input source/script */ - DCR_ReturnError = 3, /* return with error */ - DCR_Exit = 4, /* exit shell ( process or pseudo-main() ) */ - DCR_ExitError = 5, /* exit with error */ - DCR_Abort = 6, /* abort for unrecoverable cause (OOM) */ - DCR_AbortError = 7, /* abort with error (blocked unsafe) */ - /* Above are in reverse-priority order for process_input() returns. */ - - /* Dispatch and argument errors */ - DCR_ArgIxMask = 0xfff, /* mask to retain/exclude argument index */ - /* Below codes may be or'ed with the offending argument index */ - DCR_Unknown = 0x1000, /* unknown command, subcommand or option */ - DCR_Ambiguous = 0x2000, /* ambiguous (sub)command (too abreviated) */ - DCR_Unpaired = 0x3000, /* option value indicated but missing */ - DCR_TooMany = 0x4000, /* excess arguments were provided */ - DCR_TooFew = 0x5000, /* insufficient arguments provided */ - DCR_Missing = 0x6000, /* required argument(s) missing */ - DCR_ArgWrong = 0x7000, /* non-specific argument error, nothing emitted */ - - /* This code indicates error and a usage message to be emitted to stderr. */ - DCR_SayUsage = 0x7ffd, /* usage is at *pzErr or is to be generated */ - /* This code indicates nothing more need be put to stderr (or stdout.) */ - DCR_CmdErred = 0x7fff /* non-specific error for which complaint is done */ -} DotCmdRC; - /* An object implementing below interface is registered with the * shell to make new or overriding meta-commands available to it. */ @@ -196,6 +196,30 @@ 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 + * one 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 ExtensionHelpers { int helperCount; /* Helper count, not including sentinel */ union { @@ -208,9 +232,12 @@ typedef struct ExtensionHelpers { /* out */ int *pnFound); void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths); int (*nowInteractive)(ShellExState *p); + const char * (*shellInvokedAs)(void); + const char * (*shellStartupDir)(void); + int (*enable_load_extension)(sqlite3 *db, int onoff); void (*sentinel)(void); } named ; - void (*nameless[5+1])(); /* Same as named but anonymous plus a sentinel. */ + void (*nameless[10+1])(); /* Same as named but anonymous plus a sentinel. */ } helpers; } ExtensionHelpers; @@ -220,7 +247,7 @@ typedef struct ShellExtensionAPI { ExtensionHelpers * pExtHelpers; /* Functions for extension to register its implementors with shell */ - const int numRegistrars; /* 3 for this version */ + const int numRegistrars; /* 4 for this version */ union { struct ShExtAPI { /* Register a meta-command */ @@ -232,6 +259,9 @@ typedef struct ShellExtensionAPI { /* 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); /* Preset to 0 at extension load, a sentinel for expansion */ void (*sentinel)(void); } named; @@ -256,10 +286,12 @@ typedef struct ShellExtensionLink { */ 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. + /* Two more init "out" parameters, a destructor for extension overall. + * Set to 0 on input and left so if no destructor is needed. Otherwise, + * upon exit or unload, extensionDestruct(pvExtensionObject) is called. */ - void (*extensionDestruct)(void *); + void (*extensionDestruct)(void *pvExtObj); + void *pvExtensionObject; } ShellExtensionLink; diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 2eac4f3f05..a75dd496e7 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -351,6 +351,11 @@ struct sqlite3_api_routines { int (*vtab_in)(sqlite3_index_info*,int,int); int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); }; /* @@ -669,6 +674,10 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_vtab_in sqlite3_api->vtab_in #define sqlite3_vtab_in_first sqlite3_api->vtab_in_first #define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl old mode 100644 new mode 100755 index 65432e9ca0..d847724cdd --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -53,19 +53,19 @@ 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 -** this single "shell.c" file to implement the SQLite command-line shell. +** this single OUT_FILE file to implement TARGET_PROGRAM. ** -** Most of the code found below comes from the "src/shell.c.in" file in +** Much of the code found below comes from the SOURCE_FILE file in ** the canonical SQLite source tree. That main file contains "INCLUDE" ** lines that specify other files in the canonical source tree that are ** inserted and transformed, (via macro invocations explained by running ** "tool/mkshellc.tcl --help"), to generate this complete program source. ** -** By means of this generation process, creating this single "shell.c" -** file, building the command-line program is made simpler and easier. +** By means of this generation process, creating this single OUT_FILE +** file, building the program from it is simplified. ** ** To modify this program, get a copy of the canonical SQLite source tree, -** edit file src/shell.c.in and/or some of the other files included by it, +** edit file SOURCE_FILE and/or some of the other files it includes, ** then rerun the tool/mkshellc.tcl script. */} @@ -79,30 +79,42 @@ set ::lineTags 0 ; # 0 => none, 1 => source change, 2 => line syncs, 3 => more set ::tclGenerate 0 set ::verbosity 0 set ::inFiles {} -array set ::incTypes [list "*" "$::topDir/src/shell.c.in"] +set ::topInfile "?" +set ::presumedOutfile "?" +set ::targetProgram "?" +set ::defaultInfile "src/shell.c.in" +array set ::incTypes [list "*" "$::topDir/$::defaultInfile"] array set ::ignoringCommands [list] +# Shift 0'th element out of named list and return it, modifying the named list. +proc lshift {lvar} { + upvar $lvar l + set rv [lindex $l 0] + set l [lrange $l 1 end] + return $rv +} + while {[llength $argv] > 0} { - foreach {opt} $arv { set argv [lreplace $argv 1 end] ; break } + set opt [lshift argv] if {[regexp {^-{1,2}((help)|(details)|(parameters))$} $opt ma ho]} { set runMode $ho } elseif {[regexp {^-it$} $opt]} { - foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break } + set nextOpt [lshift argv] if {![regexp {^(\w+)=(.+)$} $nextOpt ma k v]} { puts stderr "Get help with --help." exit 1 } set ::incTypes($k) $v } elseif {$opt eq "-top-dir"} { - foreach {::topDir} $arv { set argv [lreplace $argv 1 end] ; break } + set ::topDir [lshift argv] if {::topDir eq ""} { set ::topDir . } } elseif {$opt eq "-source-tags"} { - foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break } + set nextOpt [lshift argv] if {![regexp {^\d$} $nextOpt ::lineTags]} { puts stderr "Argument following -source-tags must be a digit." } } elseif {$opt eq "-tcl"} { - puts stderr "Warning: Tcl extension not wholly implemented." + puts stderr "Warning: Tcl extension not implemented." set ::tclGenerate 1 } elseif {$opt eq "-v"} { incr ::verbosity @@ -114,7 +126,14 @@ while {[llength $argv] > 0} { } if {$runMode eq "normal"} { if {[llength $::inFiles] == 0} { - lappend ::inFiles $::incTypes(*) + set ::targetProgram "the SQLite command-line shell" + set ::topInfile $::defaultInfile + set ::presumedOutfile {"shell.c"} + lappend ::inFiles $::topInfile + } else { + set ::targetProgram "this SQLite shell extension" + set ::topInfile [lindex $::inFiles 0] + set ::presumedOutfile "source" } fconfigure stdout -translation {auto lf} set ::outStrm stdout @@ -470,9 +489,9 @@ proc CONDITION_COMMAND {inSrc tailCap ostrm} { } proc DISPATCH_CONFIG {inSrc tailCaptureEmpty ostrm} { - foreach { srcFile istrm srcPrecLines } $inSrc break # Set parameters affecting generated dispatchable command function # signatures and generated dispatch table entries. + foreach { srcFile istrm srcPrecLines } $inSrc break set iAte 2 set def_disp {} set lx [gets $istrm] @@ -756,7 +775,11 @@ if {$runMode == "help"} { if {$runMode == "normal"} { fconfigure $outStrm -translation {auto lf} - emit_sync [list $::headComment] $outStrm $::headCommentLines + set infName [lindex $::inFiles 0] + set hc [regsub -all {OUT_FILE} $::headComment $::presumedOutfile] + set hc [regsub -all {SOURCE_FILE} $hc "\"$::topInfile\""] + set hc [regsub -all {TARGET_PROGRAM} $hc "$::targetProgram"] + emit_sync [list $hc] $outStrm $::headCommentLines foreach {f} $::inFiles { process_file $f $outStrm } -- 2.47.3