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