Replace the sqlite3_memdb_ptr() interface with the more general

sqlite3_serialize() interface.

FossilOrigin-Name: 8cf2ed4eff6d2e0958656e23384b05ead2128b678b0b69a591878af4190cd077
This commit is contained in:
drh 2018-01-03 16:49:52 +00:00
parent 4dcac40e3d
commit cb7d541d3a
7 changed files with 250 additions and 83 deletions

View File

@ -1,5 +1,5 @@
C Simplify\sthe\s"sqlite3"\scommand\sin\sthe\sTCL\sinterface.\s\sThe\sfilename\sis\snow\noptional.\s\sThere\sis\sa\snew\s--memdb\soption\swith\san\sargument\sthat\sis\sthe\sblob\nto\swhich\sthe\sdatabase\scontent\sshould\sbe\sinitialized.
D 2018-01-03T13:20:02.897
C Replace\sthe\ssqlite3_memdb_ptr()\sinterface\swith\sthe\smore\sgeneral\nsqlite3_serialize()\sinterface.
D 2018-01-03T16:49:52.676
F Makefile.in 892bf253c48f3d2d8d4e4e89b44b71aa548a0eba11b148c338690cfb99822859
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc 3b7ee2ebaf579fe7d30bece93e61e52782c3ff836455cba3a192f7a8f6f269d6
@ -450,7 +450,7 @@ F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3
F src/mem3.c 8768ac94694f31ffaf8b4d0ea5dc08af7010a35a
F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944
F src/memdb.c 13c69aee0185614a25303e2be3802b73416e655e95e1f1c607a9f963b35a1def
F src/memdb.c 39dd9443f902759b858e598f17afc425c094b0d5f057b7ec988d224d812bd2a3
F src/memjournal.c 6f3d36a0a8f72f48f6c3c722f04301ac64f2515435fa42924293e46fc7994661
F src/msvc.h 4942752b6a253116baaa8de75256c51a459a5e81
F src/mutex.c b021263554c8a3995e9d53193b8194b96d1ed28e06c3b532dd7f7d29cf0c7d53
@ -481,14 +481,14 @@ F src/resolve.c bbee7e31d369a18a2f4836644769882e9c5d40ef4a3af911db06410b65cb3730
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c 8b22abe193e4d8243befa2038e4ae2405802fed1c446e5e502d11f652e09ba74
F src/shell.c.in a418ddceef7a2789f18bdc2bcdd481b2562fe4a7754b8009c8dd33d5a67da332
F src/sqlite.h.in 8b6cd7fd8b286d567bcc1ee3a750686cbb1c0962dc453b2cfa034684fe5808db
F src/sqlite.h.in 16bbb15a5fc53bfd725305ec9a201a7f57df7770749aa758b33a0636946d1bb4
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h c02d628cca67f3889c689d82d25c3eb45e2c155db08e4c6089b5840d64687d34
F src/sqliteInt.h 2567341e37050ad542d57f3027b8717cb9b14ff363fdfeecf85421e0d973787f
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
F src/status.c 9737ed017279a9e0c5da748701c3c7bf1e8ae0dae459aad20dd64fcff97a7e35
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
F src/tclsqlite.c 907133153d3194ddfd728e84f55fa106de696dc644c9508a054c72b9f833972e
F src/tclsqlite.c 322ad6b4e613aba84eee8cf6239eae668167f8b9891ca9e4aa53b78cd3aaff35
F src/test1.c 8ef15f7a357f85dfc41c6c748ce9c947b4f676e01bb5ae6a45bee4923dff8b51
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b
@ -1056,7 +1056,7 @@ F test/malloc_common.tcl aac62499b76be719fac31e7a3e54a7fd53272e7f
F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e
F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7
F test/memdb1.test 1d94a4bf153201f16a0a755a5a0a5d7f33b001c972e3086acb1f76ad3fb7e0e5
F test/memdb1.test 062cdf3b7de2f861878e4275d3a67b80982d9f792f693418c844fade15ec7537
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
F test/memsubsys1.test 9e7555a22173b8f1c96c281ce289b338fcba2abe8b157f8798ca195bbf1d347e
F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08
@ -1624,7 +1624,7 @@ F tool/mkkeywordhash.c 2e852ac0dfdc5af18886dc1ce7e9676d11714ae3df0a282dc7d90b3a0
F tool/mkmsvcmin.tcl 8baf26690b80d861d0ac341b29880eec6ade39e4f11fe690271ded9cb90563a3
F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
F tool/mkopcodeh.tcl 4ee2a30ccbd900dc4d5cdb61bdab87cd2166cd2affcc78c9cc0b8d22a65b2eee
F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
F tool/mkpragmatab.tcl 2144bc8550a6471a029db262a132d2df4b9e0db61b90398bf64f5b7b3f8d92cd
F tool/mkshellc.tcl 574307265b49d813301fba91ccd74e6a26d33f65f74b6891c320a0ffbee07895
F tool/mksourceid.c d458f9004c837bee87a6382228ac20d3eae3c49ea3b0a5aace936f8b60748d3b
@ -1690,7 +1690,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 e5c6ade8ee596eaffd98d1d7ce9c7c968cd2d45f206603b894026e7ad7ac15b6
R 66054801a2fc399d3dc7a916643e1819
P 47398ae77236a92f6b9345aa397361b6df127a9a2895c0771d506b0be10822b9
R bfc121db68c7d1b10b977a508d74da37
U drh
Z 2a01cfd70faa22757159c69df08c3c76
Z 6064e9ffbc398d82aebface075258c56

View File

@ -1 +1 @@
47398ae77236a92f6b9345aa397361b6df127a9a2895c0771d506b0be10822b9
8cf2ed4eff6d2e0958656e23384b05ead2128b678b0b69a591878af4190cd077

View File

@ -157,6 +157,7 @@ static int memdbRead(
MemFile *p = (MemFile *)pFile;
if( iOfst+iAmt>p->sz ){
memset(zBuf, 0, iAmt);
if( iOfst<p->sz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst);
return SQLITE_IOERR_SHORT_READ;
}
memcpy(zBuf, p->aData+iOfst, iAmt);
@ -449,20 +450,6 @@ static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){
return p;
}
/*
** Return a pointer to the memory used to hold the database.
** Return NULL if the arguments do not describe a memdb database.
*/
void *sqlite3_memdb_ptr(sqlite3 *db, const char *zSchema, sqlite3_int64 *pSz){
MemFile *p = memdbFromDbSchema(db, zSchema);
if( p==0 ){
*pSz = 0;
return 0;
}
*pSz = p->sz;
return p->aData;
}
/*
** Reconfigure a memdb database.
*/
@ -494,6 +481,72 @@ int sqlite3_memdb_config(
return SQLITE_OK;
}
/*
** Return the serialization of a database
*/
unsigned char *sqlite3_serialize(
sqlite3 *db, /* The database connection */
const char *zSchema, /* Which database within the connection */
sqlite3_int64 *piSize, /* Write size here, if not NULL */
unsigned int mFlags /* Maybe SQLITE_SERIALIZE_NOCOPY */
){
MemFile *p = memdbFromDbSchema(db, zSchema);
int iDb = sqlite3FindDbName(db, zSchema);
Btree *pBt;
sqlite3_int64 sz;
int szPage = 0;
sqlite3_stmt *pStmt = 0;
unsigned char *pOut;
char *zSql;
int rc;
if( piSize ) *piSize = -1;
if( iDb<0 ) return 0;
if( p ){
if( piSize ) *piSize = p->sz;
if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
pOut = p->aData;
}else{
pOut = sqlite3_malloc64( p->sz );
if( pOut ) memcpy(pOut, p->aData, p->sz);
}
return pOut;
}
pBt = db->aDb[iDb].pBt;
if( pBt==0 ) return 0;
szPage = sqlite3BtreeGetPageSize(pBt);
zSql = sqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema);
rc = zSql ? sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM;
sqlite3_free(zSql);
if( rc ) return 0;
sqlite3_step(pStmt);
sz = sqlite3_column_int64(pStmt, 0)*szPage;
if( piSize ) *piSize = sz;
if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
pOut = 0;
}else{
pOut = sqlite3_malloc64( sz );
if( pOut ){
int nPage = sqlite3_column_int(pStmt, 0);
Pager *pPager = sqlite3BtreePager(pBt);
int pgno;
for(pgno=1; pgno<=nPage; pgno++){
DbPage *pPage = 0;
unsigned char *pTo = pOut + szPage*(sqlite3_int64)(pgno-1);
rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pPage, 0);
if( rc==SQLITE_OK ){
memcpy(pTo, sqlite3PagerGetData(pPage), szPage);
}else{
memset(pTo, 0, szPage);
}
sqlite3PagerUnref(pPage);
}
}
}
sqlite3_finalize(pStmt);
return pOut;
}
/*
** This routine is called when the extension is loaded.
** Register the new VFS.

View File

@ -8758,22 +8758,6 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Retrieve the current MEMDB buffer
** EXPERIMENTAL
**
** This interface is only available when SQLite is compiled
** with SQLITE_ENABLE_MEMDB.
**
** The sqlite3_memdb_ptr(D,S,P) interface returns a pointer to the
** memory buffer that is the database file used for [database connection] D
** and schema S. If schema S of database connection D is not a MEMDB
** database, then this routine returns NULL. If P is not NULL, then it must
** be a pointer to a 64-bit signed integer into which the size of the
** database file is written.
*/
SQLITE_EXPERIMENTAL void *sqlite3_memdb_ptr(sqlite3*,const char*,sqlite3_int64*);
/*
** CAPI3REF: Set the current MEMDB buffer
** EXPERIMENTAL
@ -8785,7 +8769,7 @@ SQLITE_EXPERIMENTAL void *sqlite3_memdb_ptr(sqlite3*,const char*,sqlite3_int64*)
** The database identified by D and S must not be in active use when this
** interface is called, or [SQLITE_BUSY] is returned.
*/
SQLITE_EXPERIMENTAL int sqlite3_memdb_config(sqlite3*,const char*,void*,sqlite3_int64,sqlite3_int64,unsigned);
int sqlite3_memdb_config(sqlite3*,const char*,void*,sqlite3_int64,sqlite3_int64,unsigned);
/*
** CAPI3REF: Flags for configuring MEMDB databases
@ -8797,6 +8781,99 @@ SQLITE_EXPERIMENTAL int sqlite3_memdb_config(sqlite3*,const char*,void*,sqlite3_
#define SQLITE_MEMDB_FREEONCLOSE 0x001 /* Free the memory buffer on close */
#define SQLITE_MEMDB_RESIZEABLE 0x002 /* Resize using sqlite3_realloc64() */
/*
** CAPI3REF: Serialize a database
** EXPERIMENTAL
**
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory
** that is a serialization of the S database on [database connection] D.
** If P is not a NULL pointer, then the size of the database in bytes
** is written into *P.
**
** For an ordinary on-disk database file, the serialization is just a
** copy of the disk file. For an in-memory database or a "TEMP" database,
** the serialization is the same sequence of bytes which would be written
** to disk if that database where backed up to disk.
**
** The usual case is that sqlite3_serialize() copies the serialization of
** the database into memory obtained from [sqlite3_malloc64()] and returns
** a pointer to that memory. The caller is responsible for freeing the
** returned value to avoid a memory leak. However, if the F argument
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
** are made, and the sqlite3_serialize() function will return a pointer
** to the contiguous memory representation of the database that SQLite
** is currently using for that database, or NULL if the no such contiguous
** memory representation of the database exists. A contigous memory
** representation of the database will usually only exist if there has
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
** values of D and S.
** The size of the database is written into *P even if the
** SQLITE_SERIALIZE_NOCOPY bit is set but no contigious copy
** of the database exists.
**
** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the
** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
** allocation error occurs.
*/
unsigned char *sqlite3_serialize(
sqlite3 *db, /* The database connection */
const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */
sqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */
unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */
);
/*
** CAPI3REF: Flags for sqlite3_serialize
** EXPERIMENTAL
*/
#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */
/*
** CAPI3REF: Set the current MEMDB buffer
** EXPERIMENTAL
**
** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
** [database connection] D to disconnection from database S and then
** reopen S as an in-memory database based on the serialization contained
** in P. The serialized database P is N bytes in size. M is the size of
** the buffer P, which might be larger than N. If M is larger than N, and
** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
** permitted to add content to the in-memory database as long as the total
** size does not exceed M bytes.
**
** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
** invoke sqlite3_free() on the serialization buffer when the database
** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then
** SQLite will try to increase the buffer size using sqlite3_realloc64()
** if writes on the database cause it to grow larger than M bytes.
**
** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the
** database is currently in a read transaction or is involved in a backup
** operation.
**
** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the
** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
** [sqlite3_free()] is invoked on argument P prior to returning.
*/
int sqlite3_deserialize(
sqlite3 *db, /* The database connection */
const char *zSchema, /* Which DB to reopen with the deserialization */
unsigned char *pData, /* The serialized database content */
sqlite3_int64 szDb, /* Number bytes in the deserialization */
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
);
/*
** CAPI3REF: Flags for sqlite3_deserialize()
** EXPERIMENTAL
**
** The following are allowed values for the 6th argument (the "flags" or "F"
** argument) of the [sqlite3_deserialize()] interface.
*/
#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */
#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
/*
** Undo the hack that converts floating point types to integer for

View File

@ -1846,34 +1846,36 @@ static int SQLITE_TCLAPI DbObjCmd(
int choice;
int rc = TCL_OK;
static const char *DB_strs[] = {
"authorizer", "backup", "busy",
"cache", "changes", "close",
"collate", "collation_needed", "commit_hook",
"complete", "copy", "enable_load_extension",
"errorcode", "eval", "exists",
"function", "incrblob", "interrupt",
"last_insert_rowid", "memdb", "nullvalue",
"onecolumn", "preupdate", "profile",
"progress", "rekey", "restore",
"rollback_hook", "status", "timeout",
"total_changes", "trace", "trace_v2",
"transaction", "unlock_notify", "update_hook",
"version", "wal_hook", 0
"authorizer", "backup", "busy",
"cache", "changes", "close",
"collate", "collation_needed", "commit_hook",
"complete", "copy", "deserialize",
"enable_load_extension", "errorcode", "eval",
"exists", "function", "incrblob",
"interrupt", "last_insert_rowid", "memdb",
"nullvalue", "onecolumn", "preupdate",
"profile", "progress", "rekey",
"restore", "rollback_hook", "serialize",
"status", "timeout", "total_changes",
"trace", "trace_v2", "transaction",
"unlock_notify", "update_hook", "version",
"wal_hook", 0
};
enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
DB_CACHE, DB_CHANGES, DB_CLOSE,
DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION,
DB_ERRORCODE, DB_EVAL, DB_EXISTS,
DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
DB_LAST_INSERT_ROWID, DB_MEMDB, DB_NULLVALUE,
DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE,
DB_PROGRESS, DB_REKEY, DB_RESTORE,
DB_ROLLBACK_HOOK, DB_STATUS, DB_TIMEOUT,
DB_TOTAL_CHANGES, DB_TRACE, DB_TRACE_V2,
DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK,
DB_VERSION, DB_WAL_HOOK
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
DB_CACHE, DB_CHANGES, DB_CLOSE,
DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
DB_COMPLETE, DB_COPY, DB_DESERIALIZE,
DB_ENABLE_LOAD_EXTENSION, DB_ERRORCODE, DB_EVAL,
DB_EXISTS, DB_FUNCTION, DB_INCRBLOB,
DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_MEMDB,
DB_NULLVALUE, DB_ONECOLUMN, DB_PREUPDATE,
DB_PROFILE, DB_PROGRESS, DB_REKEY,
DB_RESTORE, DB_ROLLBACK_HOOK, DB_SERIALIZE,
DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
DB_TRACE, DB_TRACE_V2, DB_TRANSACTION,
DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION,
DB_WAL_HOOK
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
@ -2411,6 +2413,16 @@ static int SQLITE_TCLAPI DbObjCmd(
break;
}
/*
** $db deserialize ?DATABASE? VALUE
**
** Reopen DATABASE (default "main") using the content in $VALUE
*/
case DB_DESERIALIZE: {
rc = TCL_ERROR; /* TBD */
break;
}
/*
** $db enable_load_extension BOOLEAN
**
@ -2679,17 +2691,8 @@ static int SQLITE_TCLAPI DbObjCmd(
rc = TCL_ERROR;
#else
const char *zSchema = Tcl_GetString(objv[2]);
sqlite3_int64 sz = 0;
unsigned char *pData;
if( objc==3 ){
pData = sqlite3_memdb_ptr(pDb->db, zSchema, &sz);
if( pData==0 ){
Tcl_AppendResult(interp, "not a MEMDB database", (char*)0);
rc = TCL_ERROR;
}else{
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pData,sz));
}
}else if( objc==4 ){
if( objc==4 ){
int len = 0, xrc;
unsigned char *pBA = Tcl_GetByteArrayFromObj(objv[3], &len);
pData = sqlite3_malloc64( len );
@ -2933,6 +2936,39 @@ static int SQLITE_TCLAPI DbObjCmd(
break;
}
/*
** $db serialize ?DATABASE?
**
** Return a serialization of a database.
*/
case DB_SERIALIZE: {
#ifndef SQLITE_ENABLE_MEMDB
Tcl_AppendResult(interp, "MEMDB not available in this build",
(char*)0);
rc = TCL_ERROR;
#else
const char *zSchema = objc>=3 ? Tcl_GetString(objv[2]) : "main";
sqlite3_int64 sz = 0;
unsigned char *pData;
if( objc!=2 && objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE?");
rc = TCL_ERROR;
}else{
int needFree;
pData = sqlite3_serialize(pDb->db, zSchema, &sz, SQLITE_SERIALIZE_NOCOPY);
if( pData ){
needFree = 0;
}else{
pData = sqlite3_serialize(pDb->db, zSchema, &sz, 0);
needFree = 1;
}
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pData,sz));
if( needFree ) sqlite3_free(pData);
}
#endif
break;
}
/*
** $db status (step|sort|autoindex|vmstep)
**

View File

@ -27,8 +27,6 @@ ifcapable !memdb {
# Verify that the size of $::db1 is the same as the size of
# the database.
#
db close
sqlite3 db dummy -vfs memdb
unset -nocomplain db1
unset -nocomplain sz1
unset -nocomplain pgsz
@ -39,9 +37,12 @@ do_test 100 {
}
set ::pgsz [db one {PRAGMA page_size}]
set ::sz1 [expr {$::pgsz*[db one {PRAGMA page_count}]}]
set ::db1 [db memdb main]
set ::db1 [db serialize]
expr {[string length $::db1]==$::sz1}
} 1
set fd [open db1.db wb]
puts -nonewline $fd $db1
close $fd
# Create a new MEMDB and initialize it to the content of $::db1
# Verify that the content is the same.

View File

@ -11,7 +11,7 @@ while {![eof stdin]} {
if {$line==""} continue
regsub -all "\[ \t\n,\]+" [string trim $line] { } line
foreach token [split $line { }] {
if {![regexp {(([a-zA-Z]+)_)?([_a-zA-Z]+)} $token all px p2 name]} continue
if {![regexp {(([a-zA-Z]+)_)?([_a-zA-Z0-9]+)} $token all px p2 name]} continue
lappend namelist [string tolower $name]
if {$px!=""} {set prefix $p2}
}
@ -23,7 +23,7 @@ proc put_item x {
global col
if {$col==0} {puts -nonewline " "}
if {$col<2} {
puts -nonewline [format " %-21s" $x]
puts -nonewline [format " %-25s" $x]
incr col
} else {
puts $x