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