]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Upgrade ext/wasm/c-pp-lite.c to its newer sibling because we've reached the older...
authorstephan <stephan@noemail.net>
Sun, 8 Mar 2026 16:03:08 +0000 (16:03 +0000)
committerstephan <stephan@noemail.net>
Sun, 8 Mar 2026 16:03:08 +0000 (16:03 +0000)
FossilOrigin-Name: 2e2339bd9e4293bad04ece7673a3048b99c2143cf9573ade2ec082d95744b981

26 files changed:
ext/wasm/GNUmakefile
ext/wasm/api/EXPORTED_FUNCTIONS.c-pp
ext/wasm/api/extern-post-js.c-pp.js
ext/wasm/api/opfs-common-inline.c-pp.js
ext/wasm/api/opfs-common-shared.c-pp.js
ext/wasm/api/pre-js.c-pp.js
ext/wasm/api/sqlite3-api-glue.c-pp.js
ext/wasm/api/sqlite3-api-oo1.c-pp.js
ext/wasm/api/sqlite3-api-worker1.c-pp.js
ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js
ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js
ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js
ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
ext/wasm/api/sqlite3-worker1.c-pp.js
ext/wasm/c-pp-lite.c [deleted file]
ext/wasm/demo-worker1-promiser.c-pp.html
ext/wasm/demo-worker1-promiser.c-pp.js
ext/wasm/fiddle/index.c-pp.html
ext/wasm/libcmpp.c [new file with mode: 0644]
ext/wasm/tester1-worker.c-pp.html
ext/wasm/tester1.c-pp.html
ext/wasm/tester1.c-pp.js
manifest
manifest.uuid

index 1b7c5269e87a817925ea22d8dcf3406ae3607e9e..05375d42bdf8093015ff6f5ec57f9cdb63098206 100644 (file)
@@ -566,12 +566,14 @@ WASM_CUSTOM_INSTANTIATE = 1
 # -D... flags which should be included in all invocations should be
 # appended to $(b.c-pp.target.flags).
 #
-bin.c-pp = ./c-pp-lite
-$(bin.c-pp): c-pp-lite.c $(sqlite3.c) $(MAKEFILE)
-       $(CC) -O0 -o $@ c-pp-lite.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \
+bin.c-pp = ./c-pp
+$(bin.c-pp): libcmpp.c $(sqlite3.c) $(MAKEFILE)
+       $(CC) -O0 -o $@ libcmpp.c $(dir.top)/sqlite3.c -I$(dir.top) \
                -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \
                -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \
-               -DSQLITE_TEMP_STORE=3
+               -DSQLITE_TEMP_STORE=3 \
+               '-DCMPP_DEFAULT_DELIM="//#"' -DCMPP_MAIN -DCMPP_OMIT_D_MODULE \
+               -DCMPP_OMIT_D_PIPE
 DISTCLEAN_FILES += $(bin.c-pp)
 b.c-pp.target.flags ?=
 ifeq (1,$(SQLITE_C_IS_SEE))
index a43a7b02535b53235d417933c8c6c5f2c6b9376a..2b8397a6d700d3c4e372bc3a8117a5efb70120b7 100644 (file)
@@ -226,14 +226,14 @@ _sqlite3session_object_config
 _sqlite3session_patchset
 _sqlite3session_patchset_strm
 _sqlite3session_table_filter
-//#endif not bare-bones
+//#/if not bare-bones
 //#if enable-see
 _sqlite3_key
 _sqlite3_key_v2
 _sqlite3_rekey
 _sqlite3_rekey_v2
 _sqlite3_activate_see
-//#endif enable-see
+//#/if enable-see
 //#if fiddle
 _fiddle_db_arg
 _fiddle_db_filename
@@ -245,4 +245,4 @@ _fiddle_reset_db
 _fiddle_db_handle
 _fiddle_db_vfs
 _fiddle_export_db
-//#endif fiddle
+//#/if fiddle
index cac6e4ab4f8dd4eeec8f8f3196f36f179a0f6b64..b2e760d6a0860539c5cf6d6ee23bfc392dd21559 100644 (file)
@@ -14,7 +14,7 @@
 */
 //#if target:es6-module
 const toExportForESM =
-//#endif
+//#/if
 (function(){
   //console.warn("this is extern-post-js");
   /**
@@ -97,7 +97,7 @@ const toExportForESM =
         //console.warn("sqlite3InitModule() returning E-module.",EmscriptenModule);
         return EmscriptenModule;
       }
-//#endif
+//#/if
       return s;
     }).catch((e)=>{
       console.error("Exception loading sqlite3 module:",e);
@@ -128,10 +128,10 @@ const toExportForESM =
   }
   /* AMD modules get injected in a way we cannot override,
      so we can't handle those here. */
-//#endif // !target:es6-module
+//#/if // !target:es6-module
   return sIM;
 })();
 //#if target:es6-module
 sqlite3InitModule = toExportForESM;
 export default sqlite3InitModule;
-//#endif
+//#/if
index 04ae17d555eee30be1010df2bfd5e635225b910e..74e911e56d913ea49e91a14e627a47fc06e390a2 100644 (file)
@@ -1,4 +1,4 @@
-//#if nope
+//#if 0
 /**
    This file is for preprocessor #include into the "opfs" and
    "opfs-wl" impls, as well as their async-proxy part. It must be
@@ -7,9 +7,9 @@
    (B) it references an object which is local to each of those files
    but which has a 99% identical structure for each.
 */
-//#endif
+//#/if
 //#// vfs.metrics.enable is a refactoring crutch.
-//#define vfs.metrics.enable=0
+//#define vfs.metrics.enable 0
 const initS11n = function(){
   /**
      This proxy de/serializes cross-thread function arguments and
@@ -92,7 +92,7 @@ const initS11n = function(){
   state.s11n.deserialize = function(clear=false){
 //#if vfs.metrics.enable
     ++metrics.s11n.deserialize.count;
-//#endif
+//#/if
     const t = performance.now();
     const argc = viewU8[0];
     const rc = argc ? [] : null;
@@ -120,7 +120,7 @@ const initS11n = function(){
     //log("deserialize:",argc, rc);
 //#if vfs.metrics.enable
     metrics.s11n.deserialize.time += performance.now() - t;
-//#endif
+//#/if
     return rc;
   };
 
@@ -140,7 +140,7 @@ const initS11n = function(){
     const t = performance.now();
 //#if vfs.metrics.enable
     ++metrics.s11n.serialize.count;
-//#endif
+//#/if
     if(args.length){
       //log("serialize():",args);
       const typeIds = [];
@@ -173,7 +173,7 @@ const initS11n = function(){
     }
 //#if vfs.metrics.enable
     metrics.s11n.serialize.time += performance.now() - t;
-//#endif
+//#/if
   };
 
 //#if defined opfs-async-proxy
@@ -184,7 +184,7 @@ const initS11n = function(){
       }
     })
     : ()=>{};
-//#endif
+//#/if
 
   return state.s11n;
 //#undef vfs.metrics.enable
index 11acf40bdc81631ad9693c85d4446dd3e43f67f3..24ae2632fb35a4ec1f27eac40bb60d73f7052a54 100644 (file)
@@ -1118,7 +1118,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         new Worker(new URL(proxyUri, import.meta.url));
 //#else
         new Worker(proxyUri);
-//#endif
+//#/if
         let zombieTimer = setTimeout(()=>{
           /* At attempt to work around a browser-specific quirk in which
              the Worker load is failing in such a way that we neither
@@ -1140,7 +1140,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         };
 
         const opRun = opfsVfs.opRun;
-//#if nope
+//#if 0
         /**
            Not part of the public API. Only for test/development use.
         */
@@ -1154,7 +1154,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
             W.postMessage({type: 'opfs-async-restart'});
           }
         };
-//#endif
+//#/if
 
         const sanityCheck = function(){
           const scope = wasm.scopedAllocPush();
@@ -1298,4 +1298,4 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   }/*createVfsState()*/;
 
 }/*sqlite3ApiBootstrap.initializers*/);
-//#endif target:node
+//#/if target:node
index 3910cb000bb50e22d1dcb86c7c7a7b25200a609d..fbb48f9eacefa9d5863bebe19d3f483465caa2c7 100644 (file)
@@ -17,9 +17,9 @@
 /**
    This file was preprocessed using:
 
-//#@policy error
+//#@ policy error
    @c-pp::argv@
-//#@policy off
+//#@ policy off
 */
 //#if unsupported-build
 /**
    load. It may not work properly. Only builds _directly_ targeting
    browser environments ("vanilla" JS and ESM modules) are supported
    and tested. Builds which _indirectly_ target browsers (namely
-   bundler-friendly builds) are not supported deliverables.
+   bundler-friendly builds and any node builds) are not supported
+   deliverables.
 */
-//#endif
+//#/if
 //#if not target:es6-bundler-friendly
 (function(Module){
   const sIMS =
       "result =", theFile
     );
     return theFile;
-//#endif target:es6-module
+//#/if target:es6-module
   }.bind(sIMS);
 
 //#if Module.instantiateWasm and not wasmfs and not target:node
           .then(finalThen)
     return loadWasm();
   }.bind(sIMS);
-//#endif Module.instantiateWasm and not wasmfs
+//#/if Module.instantiateWasm and not wasmfs
 })(Module);
-//#endif not target:es6-bundler-friendly
+//#/if not target:es6-bundler-friendly
 /* END FILE: api/pre-js.js. */
index a51176e9e47061640e4b39785fb11cb2ec1f4d14..9c525bd4c759f4b271d8b1cd1101db0c6909566e 100644 (file)
@@ -122,11 +122,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       ["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
       ["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
       ["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
-//#define proxy-text-apis=1
+//#define proxy-text-apis 1
 //#if not proxy-text-apis
 /* Search this file for tag:proxy-text-apis to see what this is about. */
       ["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
-//#endif
+//#/if
       ["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
       ["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"],
       ["sqlite3_commit_hook", "void*", [
@@ -326,7 +326,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       ["sqlite3_value_subtype", "int", "sqlite3_value*"],
 //#if not proxy-text-apis
       ["sqlite3_value_text", "string", "sqlite3_value*"],
-//#endif
+//#/if
       ["sqlite3_value_type", "int", "sqlite3_value*"],
       ["sqlite3_vfs_find", "*", "string"],
       ["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"],
@@ -502,7 +502,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       ["sqlite3_activate_see", undefined, "string"]
     );
   }
-//#endif enable-see
+//#/if enable-see
 
   if( wasm.bigIntEnabled && !!wasm.exports.sqlite3_declare_vtab ){
     bindingSignatures.int64.push(
@@ -1696,7 +1696,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         : null;
     };
   }/*text-return-related bindings*/
-//#endif proxy-text-apis
+//#/if proxy-text-apis
 
   {/* sqlite3_config() */
     /**
index 9338eef336270f0fae8eca6dc460ea3d4532a97b..13aa4271945ac4a5cd25eab2ff355962173e164c 100644 (file)
@@ -215,7 +215,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       if(stmt) stmt.finalize();
     }
   };
-//#endif enable-see
+//#/if enable-see
 
   /**
      A proxy for DB class constructors. It must be called with the
@@ -301,7 +301,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       try{
 //#if enable-see
         dbCtorApplySEEKey(this,opt);
-//#endif
+//#/if
         // Check for per-VFS post-open SQL/callback...
         const pVfs = capi.sqlite3_js_db_vfs(pDb)
               || toss3("Internal error: cannot get VFS for new db handle.");
@@ -453,7 +453,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      is supplied and the database is encrypted, execution of the
      post-initialization SQL will fail, causing the constructor to
      throw.
-//#endif enable-see
+//#/if enable-see
 
      The `filename` and `vfs` arguments may be either JS strings or
      C-strings allocated via WASM. `flags` is required to be a JS
@@ -1141,7 +1141,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       return arg.returnVal();
     }/*exec()*/,
 
-//#if nope
+//#if 0
     /**
        Experimental and untested - do not use.
 
@@ -1265,7 +1265,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       }
       return this;
     }/*forEachStmt()*/,
-//#endif nope
+//#/if nope
 
     /**
        Creates a new UDF (User-Defined Function) which is accessible
@@ -2426,4 +2426,4 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
 });
 //#else
 /* Built with the omit-oo1 flag. */
-//#endif if not omit-oo1
+//#/if if not omit-oo1
index 25262abf85d11596a6551267ed5257ae61dc7da2..d2103ca85054055cdd48bd00893d98df12425899 100644 (file)
@@ -677,4 +677,4 @@ sqlite3.initWorker1API = function(){
 });
 //#else
 /* Built with the omit-oo1 flag. */
-//#endif if not omit-oo1
+//#/if if not omit-oo1
index 9aed973e267575a483bfdc215f41958537715c65..a3a86067d4ecb6a28a5f5620329a415e5ea6d8db 100644 (file)
@@ -811,11 +811,11 @@ const installAsyncProxy = function(){
     const slotWhichOp = opIds.whichOp;
     const idleWaitTime = state.asyncIdleWaitTime;
     const hasWaitAsync = !!Atomics.waitAsync;
-//#if nope
+//#if 0
     error("waitLoop init: isWebLocker",isWebLocker,
           "idleWaitTime",idleWaitTime,
           "hasWaitAsync",hasWaitAsync);
-//#endif
+//#/if
     while(!flagAsyncShutdown){
       try {
         let opId;
@@ -882,7 +882,7 @@ const installAsyncProxy = function(){
                   operation */
         ) || [];
         //error("waitLoop() whichOp =",opId, f.opHandlers[opId].key, args);
-//#if nope
+//#if 0
         if( isWebLocker && (opId==opIds.xLock || opIds==opIds.xUnlock) ){
           /* An expert suggests that this introduces a race condition,
              but my eyes aren't seeing it. The hope was that this
@@ -891,7 +891,7 @@ const installAsyncProxy = function(){
           hnd(...args);
           continue;
         }
-//#endif
+//#/if
         await hnd(...args);
       }catch(e){
         error('in waitLoop():', e);
index 88f81226a63ae56027db4782dc38eb5eb7e24cfb..0db303bc46c7c2b717a6dc6f849be8d207877558 100644 (file)
@@ -25,9 +25,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   delete sqlite3.capi.KVVfsFile;
 }
 //#else
-//#@policy error
+//#@ policy error
 //#savepoint begin
-//#define kvvfs-v2-added-in=3.52.0
+//#define kvvfs-v2-added-in "3.52.0"
 
 /**
    kvvfs - the Key/Value VFS - is an SQLite3 VFS which delegates
@@ -581,7 +581,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         ? cache.storagePool[zClass]
         : cache.storagePool[wasm.cstrToJs(zClass)];
 
-//#if nope
+//#if 0
   // fileForDb() works but we don't have a current need for it.
   /**
      Expects an (sqlite3*). Uses sqlite3_file_control() to extract its
@@ -624,7 +624,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       throw e;
     }
   };
-//#endif nope
+//#/if
 
   const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKey;
   /**
@@ -968,7 +968,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         return 0;
       }
 
-//#if nope
+//#if 0
       // these impls work but there's currently no pressing need _not_ use
       // the native impls.
       xCurrentTime: function(pVfs,pOut){
@@ -980,7 +980,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         wasm.poke64(pOut, (2440587.5 * 86400000) + Date.now());
         return 0;
       }
-//#endif
+//#/if
     }/*.vfs*/,
 
     /**
@@ -1076,7 +1076,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         }
       },
 
-//#if not nope
+//#if 0
       // We override xRead/xWrite only for logging/debugging. They
       // should otherwise be disabled (it's faster that way).
       xRead: function(pFile,pTgt,n,iOff64){
@@ -1107,9 +1107,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
           return cache.setError(e);
         }
       },
-//#endif nope
+//#/if
 
-//#if nope
+//#if 0
       xTruncate: function(pFile,i64){},
       xFileSize: function(pFile,pi64Out){},
       xLock: function(pFile,iLock){},
@@ -1117,7 +1117,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       xCheckReservedLock: function(pFile,piOut){},
       xSectorSize: function(pFile){},
       xDeviceCharacteristics: function(pFile){}
-//#endif
+//#/if
     }/*.ioDb*/,
 
     ioJrnl:{
@@ -1125,7 +1125,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
          are copied as-is from the ioDb objects. Others are specific
          to journal files. */
       xClose: true,
-//#if nope
+//#if 0
       xRead: function(pFile,pTgt,n,iOff64){},
       xWrite: function(pFile,pSrc,n,iOff64){},
       xTruncate: function(pFile,i64){},
@@ -1137,15 +1137,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       xCheckReservedLock: true,
       xSectorSize: true,
       xDeviceCharacteristics: true
-//#endif
+//#/if
     }/*.ioJrnl*/
   }/*methodOverrides*/;
 
-//#if nope
+//#if 0
   debug("pVfs and friends", pVfs, pIoDb, pIoJrnl,
         kvvfsMethods, capi.sqlite3_file.structInfo,
         KVVfsFile.structInfo);
-//#endif
+//#/if
 
   try {
     util.assert( cache.buffer.n>1024*129, "Heap buffer is not large enough"
@@ -1235,7 +1235,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      limitation which has since been overcome, but removal of
      JsStorageDb.prototype.clearStorage() would be a backwards compatibility
      break, so this function permits wiping the storage for those two
-     cases even if they are opened. Use with case.
+     cases even if they are opened. Use with care.
   */
   const sqlite3_js_kvvfs_clear = function callee(which){
     if( ''===which ){
@@ -1844,7 +1844,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       return jdb.storageSize(this.affirmOpen().dbFilename(), true);
     };
   }/*sqlite3.oo1.JsStorageDb*/
-//#endif not omit-oo1
+//#/if not omit-oo1
 
   if( sqlite3.__isUnderTest && sqlite3.vtab ){
     /**
@@ -2008,7 +2008,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
 
   }/* virtual table */
 
-//#if nope
+//#if 0
   /**
      The idea here is a simpler wrapper for listening to kvvfs
      changes.  Clients would override its onXyz() event methods
@@ -2095,8 +2095,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     async onOpen(count){}
     async onClose(count){}
   }/*KvvfsListener*/;
-//#endif nope
+//#/if nope
 
 })/*globalThis.sqlite3ApiBootstrap.initializers*/;
 //#savepoint rollback
-//#endif not omit-kvvfs
+//#/if not omit-kvvfs
index 689f53d4a7db87f631e368eef26d88b8a7fe4cd3..2990fb147097f9e16a416cbd841f0a2824a09168 100644 (file)
@@ -1471,4 +1471,4 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   The OPFS SAH Pool VFS parts are elided from builds targeting
   node.js.
 */
-//#endif target:node
+//#/if target:node
index dbd786410a1feea99c344c5ec45a910d03c02738..30b24869d66b36ab79d634cd96a4af64eb721da9 100644 (file)
@@ -130,4 +130,4 @@ globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
   });
 });
 }/*sqlite3ApiBootstrap.initializers.push()*/);
-//#endif target:node
+//#/if target:node
index 53619bfc929c8ae945dea9ab52f0b84866dbe6c7..8edfab4dabe713d661f0a1b69e5ae9904e195041 100644 (file)
@@ -177,4 +177,4 @@ globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
   })
 });
 }/*sqlite3ApiBootstrap.initializers.push()*/);
-//#endif target:node
+//#/if target:node
index b282c5e6e1a810ab2a5e2f4c01c093a1a60683e2..bcbf3fa9f8f221b7f46e673f8ddc549c0f715361 100644 (file)
@@ -24,7 +24,7 @@
 */
 //#if not defined target:es6-module
 'use strict';
-//#endif
+//#/if
 /**
    Configures an sqlite3 Worker API #1 Worker such that it can be
    manipulated via a Promise-based interface and returns a factory
@@ -278,13 +278,13 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = {
       }
     }
     return new Worker(theJs + globalThis.location.search);
-//#endif
+//#/if
   }
 //#if not target:es6-module
   .bind({
     currentScript: globalThis?.document?.currentScript
   })
-//#endif
+//#/if
   ,
   onerror: (...args)=>console.error('sqlite3Worker1Promiser():',...args)
 }/*defaultConfig*/;
@@ -349,7 +349,7 @@ globalThis.sqlite3Worker1Promiser.v2.defaultConfig =
 */
 export default sqlite3Worker1Promiser.v2;
 delete globalThis.sqlite3Worker1Promiser;
-//#endif /* target:es6-module */
+//#/if /* target:es6-module */
 //#else
 /* Built with the omit-oo1 flag. */
-//#endif if not omit-oo1
+//#/if if not omit-oo1
index db27c8fc0e2c3565c731b777c382466b24bbd857..046243baa53820a1e14e72f97e92d97f9454ef2a 100644 (file)
@@ -49,8 +49,8 @@ import sqlite3InitModule from './sqlite3.mjs';
   //console.warn("worker1 theJs =",theJs);
   importScripts(theJs);
 }
-//#endif
+//#/if
 sqlite3InitModule().then(sqlite3 => sqlite3.initWorker1API());
 //#else
 /* Built with the omit-oo1 flag. */
-//#endif if not omit-oo1
+//#/if if not omit-oo1
diff --git a/ext/wasm/c-pp-lite.c b/ext/wasm/c-pp-lite.c
deleted file mode 100644 (file)
index b8d67f6..0000000
+++ /dev/null
@@ -1,2804 +0,0 @@
-/*
-** 2022-11-12:
-**
-** The author disclaims copyright to this source code.  In place of
-** a legal notice, here is a blessing:
-**
-**  * May you do good and not evil.
-**  * May you find forgiveness for yourself and forgive others.
-**  * May you share freely, never taking more than you give.
-**
-************************************************************************
-**
-** The C-minus Preprocessor: a truly minimal C-like preprocessor.
-** Why? Because C preprocessors _can_ process non-C code but generally make
-** quite a mess of it. The purpose of this application is an extremely
-** minimal preprocessor with only the most basic functionality of a C
-** preprocessor, namely.
-**
-** The supported preprocessor directives are documented in the
-** README.md hosted with this file.
-**
-** Any mention of "#" in the docs, e.g. "#if", is symbolic. The
-** directive delimiter is configurable and defaults to "##". Define
-** CMPP_DEFAULT_DELIM to a string when compiling to define the default
-** at build-time.
-**
-** This preprocessor has only minimal support for replacement of tokens
-** which live in the "content" blocks of inputs (that is, the pieces
-** which are not prepocessor lines).
-**
-** See this file's README.md for details.
-**
-** Design note: this code makes use of sqlite3. Though not _strictly_
-** needed in order to implement it, this tool was specifically created
-** for use with the sqlite3 project's own JavaScript code, so there's
-** no reason not to make use of it to do some of the heavy lifting. It
-** does not require any cutting-edge sqlite3 features and should be
-** usable with any version which supports `WITHOUT ROWID`.
-**
-** Author(s):
-**
-** - Stephan Beal <https://wanderinghorse.net/home/stephan/>
-**
-** Canonical homes:
-**
-** - https://fossil.wanderinghorse.net/r/c-pp
-** - https://sqlite.org/src/file/ext/wasm/c-pp.c
-**
-** With the former hosting this app's SCM and the latter being the
-** single known deployment of c-pp.c, where much of its development
-** happens.
-*/
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <ctype.h>
-
-#include "sqlite3.h"
-
-#if defined(_WIN32) || defined(WIN32)
-#  include <io.h>
-#  include <fcntl.h>
-#  ifndef access
-#    define access(f,m) _access((f),(m))
-#  endif
-#else
-#  include <unistd.h>
-#endif
-
-#ifndef CMPP_DEFAULT_DELIM
-#define CMPP_DEFAULT_DELIM "##"
-#endif
-
-#ifndef CMPP_ATSIGN
-#define CMPP_ATSIGN (unsigned char)'@'
-#endif
-
-#if 1
-#  define CMPP_NORETURN __attribute__((noreturn))
-#else
-#  define CMPP_NORETURN
-#endif
-
-/* Fatally exits the app with the given printf-style message. */
-static CMPP_NORETURN void fatalv__base(char const *zFile, int line,
-                                      char const *zFmt, va_list);
-static CMPP_NORETURN void fatal__base(char const *zFile, int line,
-                                      char const *zFmt, ...);
-#define fatalv(...) fatalv__base(__FILE__,__LINE__,__VA_ARGS__)
-#define fatal(...) fatal__base(__FILE__,__LINE__,__VA_ARGS__)
-
-/** Proxy for free(), for symmetry with cmpp_realloc(). */
-static void cmpp_free(void *p);
-/** A realloc() proxy which dies fatally on allocation error. */
-static void * cmpp_realloc(void * p, unsigned n);
-#if 0
-/** A malloc() proxy which dies fatally on allocation error. */
-static void * cmpp_malloc(unsigned n);
-#endif
-
-static void check__oom2(void const *p, char const *zFile, int line){
-  if(!p) fatal("Alloc failed at %s:%d", zFile, line);
-}
-#define check__oom(P) check__oom2((P), __FILE__, __LINE__)
-
-/*
-** If p is stdin or stderr then this is a no-op, else it is a
-** proxy for fclose(). This is a no-op if p is NULL.
-*/
-static void FILE_close(FILE *p);
-/*
-** Works like fopen() but accepts the special name "-" to mean either
-** stdin (if zMode indicates a real-only mode) or stdout. Fails
-** fatally on error.
-*/
-static FILE * FILE_open(char const *zName, const char * zMode);
-/*
-** Reads the entire contents of the given file, allocating it in a
-** buffer which gets assigned to `*pOut`. `*nOut` gets assigned the
-** length of the output buffer. Fails fatally on error.
-*/
-static void FILE_slurp(FILE *pFile, unsigned char **pOut,
-                       unsigned * nOut);
-
-/*
-** Intended to be passed an sqlite3 result code. If it's a non-0 value
-** other than SQLITE_ROW or SQLITE_DONE then it emits a fatal error
-** message which contains both the given string and the
-** sqlite3_errmsg() from the application's database instance.
-*/
-static void db_affirm_rc(int rc, const char * zMsg);
-
-/*
-** Proxy for sqlite3_str_finish() which fails fatally if that
-** routine returns NULL.
-*/
-static char * db_str_finish(sqlite3_str *s, int * n);
-/*
-** Proxy for sqlite3_str_new() which fails fatally if that
-** routine returns NULL.
-*/
-static sqlite3_str * db_str_new(void);
-
-/*
-** Proxy for sqlite3_step() which fails fatally if the result
-** is anything other than SQLITE_ROW or SQLITE_DONE.
-*/
-static int db_step(sqlite3_stmt *pStmt);
-/*
-** Proxy for sqlite3_bind_int() which fails fatally on error.
-*/
-static void db_bind_int(sqlite3_stmt *pStmt, int col, int val);
-/*
-** Proxy for sqlite3_bind_null() which fails fatally on error.
-*/
-static void db_bind_null(sqlite3_stmt *pStmt, int col);
-/*
-** Proxy for sqlite3_bind_text() which fails fatally on error.
-*/
-static void db_bind_text(sqlite3_stmt *pStmt, int col, const char * zStr);
-/*
-** Proxy for sqlite3_bind_text() which fails fatally on error.
-*/
-static void db_bind_textn(sqlite3_stmt *pStmt, int col, const char * zStr, int len);
-#if 0
-/*
-** Proxy for sqlite3_bind_text() which fails fatally on error. It uses
-** sqlite3_str_vappendf() so supports all of its formatting options.
-*/
-static void db_bind_textv(sqlite3_stmt *pStmt, int col, const char * zFmt, ...);
-#endif
-/*
-** Proxy for sqlite3_free(), to be passed any memory which is allocated
-** by sqlite3_malloc().
-*/
-static void db_free(void *m);
-
-/*
-** Returns true if the first nKey bytes of zKey are a legal string. If
-** it returns false and zErrPos is not null, *zErrPos is set to the
-** position of the illegal character. If nKey is negative, strlen() is
-** used to calculate it.
-*/
-static int cmpp_is_legal_key(char const *zKey, int nKey, char const **zErrPos);
-
-/*
-** Fails fatally if !cmpp_is_legal_key(zKey).
-*/
-static void cmpp_affirm_legal_key(char const *zKey, int nKey);
-
-/*
-** Adds the given `#define` macro name to the list of macros, ignoring
-** any duplicates. Fails fatally on error.
-**
-** If zVal is NULL then zKey may contain an '=', from which the value
-** will be extracted. If zVal is not NULL then zKey may _not_ contain
-** an '='.
-*/
-static void db_define_add(const char * zKey, char const *zVal);
-
-/*
-** Returns true if the given key is already in the `#define` list,
-** else false. Fails fatally on db error.
-**
-** nName is the length of the key part of zName (which might have
-** a following =y part. If it's negative, strlen() is used to
-** calculate it.
-*/
-static int db_define_has(const char * zName, int nName);
-
-/*
-** Returns true if the given key is already in the `#define` list, and
-** it has a truthy value (is not empty and not equal to '0'), else
-** false. Fails fatally on db error.
-**
-** nName is the length of zName, or <0 to use strlen() to figure
-** it out.
-*/
-static int db_define_get_bool(const char * zName, int nName);
-
-/*
-** Searches for a define where (k GLOB zName). If one is found, a copy
-** of it is assigned to *zVal (the caller must eventually db_free()
-** it)), *nVal (if nVal is not NULL) is assigned its strlen, and
-** returns non-0. If no match is found, 0 is returned and neither
-** *zVal nor *nVal are modified. If more than one result matches, a
-** fatal error is triggered.
-**
-** It is legal for *zVal to be NULL (and *nVal to be 0) if it returns
-** non-0. That just means that the key was defined with no value part.
-*/
-static int db_define_get(const char * zName, int nName, char **zVal, unsigned int *nVal);
-
-/*
-** Removes the given `#define` macro name from the list of
-** macros. Fails fatally on error.
-*/
-static void db_define_rm(const char * zKey);
-/*
-** Adds the given filename to the list of being-`#include`d files,
-** using the given source file name and line number of error reporting
-** purposes. If recursion is later detected.
-*/
-static void db_including_add(const char * zKey, const char * zSrc, int srcLine);
-/*
-** Adds the given dir to the list of includes. They are checked in the
-** order they are added.
-*/
-static void db_include_dir_add(const char * zKey);
-/*
-** Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of
-** the `#include` dirs (db_include_dir_add()). If no file match is
-** found, NULL is returned. Memory must eventually be passed to
-** db_free() to free it.
-*/
-static char * db_include_search(const char * zKey);
-/*
-** Removes the given key from the `#include` list.
-*/
-static void db_include_rm(const char * zKey);
-/*
-** A proxy for sqlite3_prepare() which fails fatally on error.
-*/
-static void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...);
-
-/*
-** Opens the given file and processes its contents as c-pp, sending
-** all output to the global c-pp output channel. Fails fatally on
-** error. If bRaw is true then the file's contents are passed through
-** verbatim, rather than being preprocessed.
-*/
-static void cmpp_process_file(const char * zName, int bRaw);
-
-/*
-** Operator policy for cmpp_kvp_parse().
-*/
-enum cmpp_key_op_e {
-  /* Fail if the key contains an operator. */
-  cmpp_key_op_none,
-  /* Accept only '='. */
-  cmpp_key_op_eq1
-};
-typedef enum cmpp_key_op_e cmpp_key_op_e;
-
-/*
-** Operators and operator policies for use with X=Y-format keys.
-*/
-#define cmpp_kvp_op_map(E) \
-  E(none,"")               \
-  E(eq1,"=")               \
-  E(eq2,"==")              \
-  E(lt,"<")                \
-  E(le,"<=")               \
-  E(gt,">")                \
-  E(ge,">=")
-
-enum cmpp_kvp_op_e {
-#define E(N,S) cmpp_kvp_op_ ## N,
-  cmpp_kvp_op_map(E)
-#undef E
-};
-typedef enum cmpp_kvp_op_e cmpp_kvp_op_e;
-
-/*
-** A snippet from a string.
-*/
-struct cmpp_snippet {
-  char const *z;
-  unsigned int n;
-};
-typedef struct cmpp_snippet cmpp_snippet;
-#define cmpp_snippet_empty_m {0,0}
-
-/*
-** Result type for cmpp_kvp_parse().
-*/
-struct cmpp_kvp {
-  cmpp_snippet k;
-  cmpp_snippet v;
-  cmpp_kvp_op_e op;
-};
-
-typedef struct cmpp_kvp cmpp_kvp;
-#define cmpp_kvp_empty_m \
-  {cmpp_snippet_empty_m,cmpp_snippet_empty_m,cmpp_kvp_op_none}
-static const cmpp_kvp cmpp_kvp_empty = cmpp_kvp_empty_m;
-
-/*
-** Parses X or X=Y into p. Fails fatally on error.
-**
-** If nKey is negative then strlen() is used to calculate it.
-**
-** The third argument specifies whether/how to permit/treat the '='
-** part of X=Y.
-*/
-static void cmpp_kvp_parse(cmpp_kvp * p,
-                           char const *zKey, int nKey,
-                           cmpp_kvp_op_e opPolicy);
-
-/*
-** Wrapper around a FILE handle.
-*/
-typedef struct FileWrapper FileWrapper;
-struct FileWrapper {
-  /* File's name. */
-  char const *zName;
-  /* FILE handle. */
-  FILE * pFile;
-  /* Where FileWrapper_slurp() stores the file's contents. */
-  unsigned char * zContent;
-  /* Size of this->zContent, as set by FileWrapper_slurp(). */
-  unsigned nContent;
-  /* See Global::pFiles. */
-  FileWrapper * pTail;
-};
-#define FileWrapper_empty_m {0,0,0,0,0}
-static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
-
-/*
-** Proxy for FILE_close() and frees all memory owned by p. A no-op if
-** p is already closed.
-*/
-static void FileWrapper_close(FileWrapper * p);
-/* Proxy for FILE_open(). Closes p first if it's currently opened. */
-static void FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
-/* Proxy for FILE_slurp(). */
-static void FileWrapper_slurp(FileWrapper * p);
-/*
-** If p->zContent ends in \n or \r\n, that part is replaced with 0 and
-** p->nContent is adjusted. Returns true if it chomps, else false.
-*/
-int FileWrapper_chomp(FileWrapper * p);
-
-/*
-** Outputs a printf()-formatted message to stderr.
-*/
-static void g_stderr(char const *zFmt, ...);
-/*
-** Outputs a printf()-formatted message to stderr.
-*/
-static void g_stderrv(char const *zFmt, va_list);
-#define g_debug(lvl,pfexpr)                                          \
-  if(lvl<=g.flags.doDebug) g_stderr("%s @ %s():%d: ",g.zArgv0,__func__,__LINE__); \
-  if(lvl<=g.flags.doDebug) g_stderr pfexpr
-
-#define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
-#define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg)
-
-void cmpp_free(void *p){
-  sqlite3_free(p);
-}
-
-void * cmpp_realloc(void * p, unsigned n){
-  void * const rc = sqlite3_realloc(p, n);
-  if(!rc) fatal("realloc(P,%u) failed", n);
-  return rc;
-}
-
-#if 0
-void * cmpp_malloc(unsigned n){
-  void * const rc = sqlite3_alloc(n);
-  if(!rc) fatal("malloc(%u) failed", n);
-  return rc;
-}
-#endif
-
-FILE * FILE_open(char const *zName, const char * zMode){
-  FILE * p;
-  if('-'==zName[0] && 0==zName[1]){
-    p = strstr(zMode,"w") ? stdout : stdin;
-  }else{
-    p = fopen(zName, zMode);
-    if(!p) fatal("Cannot open file [%s] with mode [%s]", zName, zMode);
-  }
-  return p;
-}
-
-void FILE_close(FILE *p){
-  if(p && p!=stdout && p!=stderr){
-    fclose(p);
-  }
-}
-
-void FILE_slurp(FILE *pFile, unsigned char **pOut,
-                unsigned * nOut){
-  unsigned char zBuf[1024 * 8];
-  unsigned char * pDest = 0;
-  unsigned nAlloc = 0;
-  unsigned nOff = 0;
-  /* Note that this needs to be able to work on non-seekable streams,
-  ** thus we read in chunks instead of doing a single alloc and
-  ** filling it in one go. */
-  while( !feof(pFile) ){
-    size_t const n = fread(zBuf, 1, sizeof(zBuf), pFile);
-    if(n>0){
-      if(nAlloc < nOff + n + 1){
-        nAlloc = nOff + n + 1;
-        pDest = cmpp_realloc(pDest, nAlloc);
-      }
-      memcpy(pDest + nOff, zBuf, n);
-      nOff += n;
-    }
-  }
-  if(pDest) pDest[nOff] = 0;
-  *pOut = pDest;
-  *nOut = nOff;
-}
-
-void FileWrapper_close(FileWrapper * p){
-  if(p->pFile) FILE_close(p->pFile);
-  if(p->zContent) cmpp_free(p->zContent);
-  *p = FileWrapper_empty;
-}
-
-void FileWrapper_open(FileWrapper * p, const char * zName,
-                      const char * zMode){
-  FileWrapper_close(p);
-  p->pFile = FILE_open(zName, zMode);
-  p->zName = zName;
-}
-
-void FileWrapper_slurp(FileWrapper * p){
-  assert(!p->zContent);
-  assert(p->pFile);
-  FILE_slurp(p->pFile, &p->zContent, &p->nContent);
-}
-
-int FileWrapper_chomp(FileWrapper * p){
-  if( p->nContent && '\n'==p->zContent[p->nContent-1] ){
-    p->zContent[--p->nContent] = 0;
-    if( p->nContent && '\r'==p->zContent[p->nContent-1] ){
-      p->zContent[--p->nContent] = 0;
-    }
-    return 1;
-  }
-  return 0;
-}
-
-enum CmppParseState {
-TS_Start = 1,
-TS_If,
-TS_IfPassed,
-TS_Else,
-TS_Error
-};
-typedef enum CmppParseState CmppParseState;
-enum CmppTokenType {
-
-#define CmppToken_map(E) \
-  E(Invalid,0)           \
-  E(Assert,"assert")     \
-  E(AtPolicy,"@policy")  \
-  E(Comment,"//")        \
-  E(Define,"define")     \
-  E(Elif,"elif")         \
-  E(Else,"else")         \
-  E(Endif,"endif")       \
-  E(Error,"error")       \
-  E(If,"if")             \
-  E(Include,"include")   \
-  E(Line,0)              \
-  E(Opaque,0)            \
-  E(Pragma,"pragma")     \
-  E(Savepoint,"savepoint") \
-  E(Stderr,"stderr")     \
-  E(Undef,"undef")
-
-#define E(N,TOK) TT_ ## N,
-  CmppToken_map(E)
-#undef E
-};
-typedef enum CmppTokenType CmppTokenType;
-
-/*
-** Map of directive (formerly keyword) names and their token types.
-*/
-static const struct {
-#define E(N,TOK) struct cmpp_snippet N;
-  CmppToken_map(E)
-#undef E
-} DStrings = {
-#define E(N,TOK) .N = {TOK,sizeof(TOK)-1},
-  CmppToken_map(E)
-#undef E
-};
-
-//static
-char const * TT_cstr(int tt){
-  switch(tt){
-#define E(N,TOK) case TT_ ## N: return DStrings.N.z;
-    CmppToken_map(E)
-#undef E
-  }
-  return NULL;
-}
-
-struct CmppToken {
-  CmppTokenType ttype;
-  /* Line number of this token in the source file. */
-  unsigned lineNo;
-  /* Start of the token. */
-  unsigned char const * zBegin;
-  /* One-past-the-end byte of the token. */
-  unsigned char const * zEnd;
-};
-typedef struct CmppToken CmppToken;
-#define CmppToken_empty_m {TT_Invalid,0,0,0}
-static const CmppToken CmppToken_empty = CmppToken_empty_m;
-
-/*
-** CmppLevel represents one "level" of tokenization, starting at the
-** top of the main input, incrementing once for each level of `#if`,
-** and decrementing for each `#endif`.
-** pushes a level.
-*/
-typedef struct CmppLevel CmppLevel;
-struct CmppLevel {
-  unsigned short flags;
-  /*
-  ** Used for controlling which parts of an if/elif/...endif chain
-  ** should get output.
-  */
-  unsigned short skipLevel;
-  /* The token which started this level (an 'if' or 'include'). */
-  CmppToken token;
-  CmppParseState pstate;
-};
-#define CmppLevel_empty_m {0U,0U,CmppToken_empty_m,TS_Start}
-static const CmppLevel CmppLevel_empty = CmppLevel_empty_m;
-enum CmppLevel_Flags {
-/* Max depth of nested `#if` constructs in a single tokenizer. */
-CmppLevel_Max = 10,
-/* Max number of keyword arguments. */
-CmppArgs_Max = 15,
-/* Directive line buffer size */
-CmppArgs_BufSize = 1024,
-/* Flag indicating that output for a CmpLevel should be elided. */
-CmppLevel_F_ELIDE = 0x01,
-/*
-** Mask of CmppLevel::flags which are inherited when CmppLevel_push()
-** is used.
-*/
-CmppLevel_F_INHERIT_MASK = CmppLevel_F_ELIDE
-};
-
-typedef struct CmppTokenizer CmppTokenizer;
-typedef struct CmppKeyword CmppKeyword;
-typedef void (*cmpp_keyword_f)(CmppKeyword const * pKw, CmppTokenizer * t);
-struct CmppKeyword {
-  const char *zName;
-  unsigned nName;
-  int bTokenize;
-  CmppTokenType ttype;
-  cmpp_keyword_f xCall;
-};
-
-static CmppKeyword const * CmppKeyword_search(const char *zName);
-static void cmpp_process_keyword(CmppTokenizer * const t);
-
-/*
-** Tokenizer for c-pp input files.
-*/
-struct CmppTokenizer {
-  const char * zName;            /* Input (file) name for error reporting */
-  unsigned const char * zBegin;  /* start of input */
-  unsigned const char * zEnd;    /* one-after-the-end of input */
-  unsigned const char * zPos;    /* current position */
-  unsigned int lineNo;           /* line # of current pos */
-  unsigned nSavepoint;
-  CmppParseState pstate;
-  CmppToken token;               /* current token result */
-  struct {
-    unsigned ndx;
-    CmppLevel stack[CmppLevel_Max];
-  } level;
-  /* Args for use in cmpp_keyword_f() impls. */
-  struct {
-    CmppKeyword const * pKw;
-    int argc;
-    const unsigned char * argv[CmppArgs_Max];
-    unsigned char lineBuf[CmppArgs_BufSize];
-  } args;
-};
-#define CT_level(t) (t)->level.stack[(t)->level.ndx]
-#define CT_pstate(t) CT_level(t).pstate
-#define CT_skipLevel(t) CT_level(t).skipLevel
-#define CLvl_skip(lvl) ((lvl)->skipLevel || ((lvl)->flags & CmppLevel_F_ELIDE))
-#define CT_skip(t) CLvl_skip(&CT_level(t))
-#define CmppTokenizer_empty_m {         \
-    .zName=0, .zBegin=0, .zEnd=0,       \
-    .zPos=0,                            \
-    .lineNo=1U,                         \
-    .pstate = TS_Start,                 \
-    .token = CmppToken_empty_m,         \
-    .level = {0U,{CmppLevel_empty_m}},  \
-    .args = {0,0,{0},{0}}               \
-  }
-static const CmppTokenizer CmppTokenizer_empty = CmppTokenizer_empty_m;
-
-static void CmppTokenizer_cleanup(CmppTokenizer * const t);
-
-static void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n);
-/*static void cmpp_t_outf(CmppTokenizer * t, char const *zFmt, ...);*/
-
-/*
-** Pushes a new level into the given tokenizer. Fails fatally if
-** it's too deep.
-*/
-static void CmppLevel_push(CmppTokenizer * const t);
-/*
-** Pops a level from the tokenizer. Fails fatally if the top
-** level is popped.
-*/
-static void CmppLevel_pop(CmppTokenizer * const t);
-/*
-** Returns the current level object.
-*/
-static CmppLevel * CmppLevel_get(CmppTokenizer * const t);
-
-/*
-** Policies for how to handle undefined @tokens@ when performing
-** content filtering.
-*/
-enum AtPolicy {
-  AT_invalid = -1,
-  /** Turn off @foo@ parsing. */
-  AT_OFF = 0,
-  /** Retain undefined @foo@ - emit it as-is. */
-  AT_RETAIN,
-  /** Elide undefined @foo@. */
-  AT_ELIDE,
-  /** Error for undefined @foo@. */
-  AT_ERROR,
-  AT_DEFAULT = AT_ERROR
-};
-typedef enum AtPolicy AtPolicy;
-
-static AtPolicy AtPolicy_fromStr(char const *z, int bEnforce){
-  if( 0==strcmp(z, "retain") ) return AT_RETAIN;
-  if( 0==strcmp(z, "elide") ) return AT_ELIDE;
-  if( 0==strcmp(z, "error") ) return AT_ERROR;
-  if( 0==strcmp(z, "off") ) return AT_OFF;
-  if( bEnforce ){
-    fatal("Invalid @ policy value: %s. "
-          "Try one of retain|elide|error|off.", z);
-  }
-  return AT_invalid;
-}
-
-/*
-** Global app state singleton.
-*/
-static struct Global {
-  /* main()'s argv[0]. */
-  const char * zArgv0;
-  /* App's db instance. */
-  sqlite3 * db;
-  /* Current tokenizer (for error reporting purposes). */
-  CmppTokenizer const * tok;
-  /*
-  ** We use a linked-list of these to keep track of our opened
-  ** files so that we can clean then up via atexit() in the case of
-  ** fatal error (to please valgrind).
-  */
-  FileWrapper * pFiles;
-  /* Output channel. */
-  FileWrapper out;
-  struct {
-    /*
-    ** Bytes of the keyword delimiter/prefix. Owned
-    ** elsewhere.
-    */
-    const char * z;
-    /* Byte length of this->zDelim. */
-    unsigned short n;
-    /*
-    ** The @token@ delimiter.
-    **
-    ** Potential TODO is replace this with a pair of opener/closer
-    ** strings, e.g. "{{" and "}}".
-    */
-    const unsigned char chAt;
-  } delim;
-  struct {
-#define CMPP_SAVEPOINT_NAME "_cmpp_"
-#define GStmt_map(E)               \
-    E(defIns,"INSERT OR REPLACE INTO def(k,v) VALUES(?,?)") \
-    E(defDel,"DELETE FROM def WHERE k GLOB ?")         \
-    E(defHas,"SELECT 1 FROM def WHERE k GLOB ?")       \
-    E(defGet,"SELECT k,v FROM def WHERE k GLOB ?")     \
-    E(defGetBool,                                      \
-      "SELECT 1 FROM def WHERE k = ?1"                 \
-      " AND v IS NOT NULL"                             \
-      " AND '0'!=v AND ''!=v")                         \
-    E(defSelAll,"SELECT k,v FROM def ORDER BY k")      \
-    E(inclIns,"INSERT OR FAIL INTO incl(file,srcFile," \
-      "srcLine) VALUES(?,?,?)")                        \
-    E(inclDel,"DELETE FROM incl WHERE file=?")         \
-    E(inclHas,"SELECT 1 FROM incl WHERE file=?")       \
-    E(inclPathAdd,"INSERT OR FAIL INTO "               \
-      "inclpath(seq,dir) VALUES(?,?)")                 \
-    E(inclSearch,                                      \
-      "SELECT ?1 fn WHERE fileExists(fn) "             \
-      "UNION ALL SELECT * FROM ("                      \
-      "SELECT replace(dir||'/'||?1, '//','/') AS fn "  \
-      "FROM inclpath WHERE fileExists(fn) ORDER BY seq"\
-      ")")                                             \
-    E(spBegin,"SAVEPOINT " CMPP_SAVEPOINT_NAME)        \
-    E(spRollback,"ROLLBACK TO SAVEPOINT "              \
-      CMPP_SAVEPOINT_NAME)                             \
-    E(spRelease,"RELEASE SAVEPOINT " CMPP_SAVEPOINT_NAME)
-
-#define E(N,S) sqlite3_stmt * N;
-    GStmt_map(E)
-#undef E
-  } stmt;
-  struct {
-    FILE * pFile;
-    int expandSql;
-  } sqlTrace;
-  struct {
-    AtPolicy atPolicy;
-    /* If true, enables certain debugging output. */
-    char doDebug;
-    /* If true, chomp() files read via -Fx=file. */
-    char chompF;
-  } flags;
-} g = {
-  .zArgv0 = "?",
-  .db = 0,
-  .tok = 0,
-  .pFiles = 0,
-  .out = FileWrapper_empty_m,
-  .delim = {
-    .z = CMPP_DEFAULT_DELIM,
-    .n = (unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1,
-    .chAt = '@'
-  },
-  .stmt = {
-    .defIns =   0,
-    .defDel = 0,
-    .defHas = 0,
-    .defGet = 0,
-    .defGetBool = 0,
-    .inclIns =   0,
-    .inclDel =   0,
-    .inclHas =   0,
-    .inclPathAdd =   0,
-    .inclSearch =   0
-  },
-  .sqlTrace = {
-    .pFile = 0,
-    .expandSql = 0
-  },
-  .flags = {
-    .atPolicy = AT_OFF,
-    .doDebug = 0,
-    .chompF = 0
-  }
-};
-
-/** Distinct IDs for each g.stmt member. */
-enum GStmt_e {
-  GStmt_none = 0,
-#define E(N,S) GStmt_ ## N,
-  GStmt_map(E)
-#undef E
-};
-
-/*
-** Returns the g.stmt.X corresponding to `which`, initializing it if
-** needed. It does not return NULL - it fails fatally on error.
-*/
-static sqlite3_stmt * g_stmt(enum GStmt_e which){
-  sqlite3_stmt ** q = 0;
-  char const * zSql = 0;
-  switch(which){
-    case GStmt_none:
-      fatal("GStmt_none is not a valid statement handle");
-      return NULL;
-#define E(N,S) case GStmt_ ## N: zSql = S; q = &g.stmt.N; break;
-    GStmt_map(E)
-#undef E
-  }
-  assert( q );
-  assert( zSql && *zSql );
-  if( !*q ){
-    db_prepare(q, "%s", zSql);
-    assert( *q );
-  }
-  return *q;
-}
-static void g_stmt_reset(sqlite3_stmt * const q){
-  sqlite3_clear_bindings(q);
-  sqlite3_reset(q);
-}
-
-#if 0
-/*
-** Outputs a printf()-formatted message to c-pp's global output
-** channel.
-*/
-static void g_outf(char const *zFmt, ...);
-void g_outf(char const *zFmt, ...){
-  va_list va;
-  va_start(va, zFmt);
-  vfprintf(g.out.pFile, zFmt, va);
-  va_end(va);
-}
-#endif
-
-/* Outputs n bytes from z to c-pp's global output channel. */
-static void g_out(void const *z, unsigned int n);
-void g_out(void const *z, unsigned int n){
-  if(g.out.pFile && 1!=fwrite(z, n, 1, g.out.pFile)){
-    int const err = errno;
-    fatal("fwrite() output failed with errno #%d", err);
-  }
-}
-
-void g_stderrv(char const *zFmt, va_list va){
-  if( g.out.pFile==stdout ){
-    fflush(g.out.pFile);
-  }
-  vfprintf(stderr, zFmt, va);
-}
-
-void g_stderr(char const *zFmt, ...){
-  va_list va;
-  va_start(va, zFmt);
-  g_stderrv(zFmt, va);
-  va_end(va);
-}
-
-/*
-** Emits n bytes of z if CT_skip(t) is false.
-*/
-void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n){
-  g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t)));
-  g_debug(3,("CT_skip() ?= %d\n",CT_skip(t)));
-  if(!CT_skip(t)) g_out(z, n);
-}
-
-void CmppLevel_push(CmppTokenizer * const t){
-  CmppLevel * pPrev;
-  CmppLevel * p;
-  if(t->level.ndx+1 == (unsigned)CmppLevel_Max){
-    fatal("%sif nesting level is too deep. Max=%d\n",
-          g.delim.z, CmppLevel_Max);
-  }
-  pPrev = &CT_level(t);
-  g_debug(3,("push from tokenizer level=%u flags=%04x\n",
-             t->level.ndx, pPrev->flags));
-  p = &t->level.stack[++t->level.ndx];
-  *p = CmppLevel_empty;
-  p->token = t->token;
-  p->flags = (CmppLevel_F_INHERIT_MASK & pPrev->flags);
-  if(CLvl_skip(pPrev)) p->flags |= CmppLevel_F_ELIDE;
-  g_debug(3,("push to tokenizer level=%u flags=%04x\n",
-             t->level.ndx, p->flags));
-}
-
-void CmppLevel_pop(CmppTokenizer * const t){
-  if(!t->level.ndx){
-    fatal("Internal error: CmppLevel_pop() at the top of the stack");
-  }
-  g_debug(3,("pop from tokenizer level=%u, flags=%04x skipLevel?=%d\n",
-             t->level.ndx,
-             t->level.stack[t->level.ndx].flags, CT_skipLevel(t)));
-  g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t)));
-  g_debug(3,("CT_skip() ?= %d\n",CT_skip(t)));
-  t->level.stack[t->level.ndx--] = CmppLevel_empty;
-  g_debug(3,("pop to tokenizer level=%u, flags=%04x\n", t->level.ndx,
-             t->level.stack[t->level.ndx].flags));
-  g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t)));
-  g_debug(3,("CT_skip() ?= %d\n",CT_skip(t)));
-}
-
-CmppLevel * CmppLevel_get(CmppTokenizer * const t){
-  return &t->level.stack[t->level.ndx];
-}
-
-
-void db_affirm_rc(int rc, const char * zMsg){
-  switch(rc){
-    case 0:
-    case SQLITE_DONE:
-    case SQLITE_ROW:
-      break;
-    default:
-      assert( g.db );
-      fatal("Db error #%d %s: %s", rc, zMsg,
-            sqlite3_errmsg(g.db));
-  }
-}
-
-int db_step(sqlite3_stmt *pStmt){
-  int const rc = sqlite3_step(pStmt);
-  switch( rc ){
-    case SQLITE_ROW:
-    case SQLITE_DONE:
-      break;
-    default:
-      db_affirm_rc(rc, "from db_step()");
-  }
-  return rc;
-}
-
-static sqlite3_str * db_str_new(void){
-  sqlite3_str * rc = sqlite3_str_new(g.db);
-  if(!rc) fatal("Alloc failed for sqlite3_str_new()");
-  return rc;
-}
-
-static char * db_str_finish(sqlite3_str *s, int * n){
-  int const rc = sqlite3_str_errcode(s);
-  if(rc) fatal("Error #%d from sqlite3_str_errcode()", rc);
-  if(n) *n = sqlite3_str_length(s);
-  char * z = sqlite3_str_finish(s);
-  if(!z) fatal("Alloc failed for sqlite3_str_new()");
-  return z;
-}
-
-void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...){
-  int rc;
-  sqlite3_str * str = db_str_new();
-  char * z = 0;
-  int n = 0;
-  va_list va;
-  va_start(va, zSql);
-  sqlite3_str_vappendf(str, zSql, va);
-  va_end(va);
-  rc = sqlite3_str_errcode(str);
-  if(rc) fatal("sqlite3_str_errcode() = %d", rc);
-  z = db_str_finish(str, &n);
-  rc = sqlite3_prepare_v2(g.db, z, n, pStmt, 0);
-  if(rc) fatal("Error #%d (%s) preparing: %s",
-               rc, sqlite3_errmsg(g.db), z);
-  sqlite3_free(z);
-}
-
-void db_bind_int(sqlite3_stmt *pStmt, int col, int val){
-  db_affirm_rc(sqlite3_bind_int(pStmt, col, val),
-               "from db_bind_int()");
-}
-
-void db_bind_null(sqlite3_stmt *pStmt, int col){
-  db_affirm_rc(sqlite3_bind_null(pStmt, col),
-               "from db_bind_null()");
-}
-
-void db_bind_textn(sqlite3_stmt *pStmt, int col,
-                   const char * zStr, int n){
-  db_affirm_rc(
-    (zStr && n)
-    ? sqlite3_bind_text(pStmt, col, zStr, n, SQLITE_TRANSIENT)
-    : sqlite3_bind_null(pStmt, col),
-    "from db_bind_textn()"
-  );
-}
-
-void db_bind_text(sqlite3_stmt *pStmt, int col,
-                  const char * zStr){
-  db_bind_textn(pStmt, col, zStr, -1);
-}
-
-#if 0
-void db_bind_textv(sqlite3_stmt *pStmt, int col,
-                   const char * zFmt, ...){
-  int rc;
-  sqlite3_str * str = db_str_new();
-  int n = 0;
-  char * z;
-  va_list va;
-  va_start(va,zFmt);
-  sqlite3_str_vappendf(str, zFmt, va);
-  va_end(va);
-  z = db_str_finish(str, &n);
-  rc = sqlite3_bind_text(pStmt, col, z, n, sqlite3_free);
-  db_affirm_rc(rc,"from db_bind_textv()");
-}
-#endif
-
-void db_free(void *m){
-  sqlite3_free(m);
-}
-
-void db_define_add(const char * zKey, char const *zVal){
-  cmpp_kvp kvp = cmpp_kvp_empty;
-  cmpp_kvp_parse(&kvp, zKey, -1,
-    zVal
-    ? cmpp_key_op_none
-    : cmpp_key_op_eq1
-  );
-  if( kvp.v.z ){
-    if( zVal ){
-      assert(!"cannot happen - cmpp_key_op_none will prevent it");
-      fatal("Cannot assign two values to [%.*s] [%.*s] [%s]",
-            kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal);
-    }
-  }else{
-    kvp.v.z = zVal;
-    kvp.v.n = zVal ? (int)strlen(zVal) : 0;
-  }
-  sqlite3_stmt * const q = g_stmt(GStmt_defIns);
-  //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
-  db_bind_textn(q, 1, kvp.k.z, kvp.k.n);
-  if( kvp.v.z ){
-    if( kvp.v.n ){
-      db_bind_textn(q, 2, kvp.v.z, (int)kvp.v.n);
-    }else{
-      db_bind_null(q, 2);
-    }
-  }else{
-    db_bind_int(q, 2, 1);
-  }
-  db_step(q);
-  g_debug(2,("define: %s%s%s\n",
-             zKey,
-             zVal ? " with value " : "",
-             zVal ? zVal : ""));
-  sqlite3_reset(q);
-}
-
-static void db_define_add_file(const char * zKey){
-  cmpp_kvp kvp = cmpp_kvp_empty;
-  cmpp_kvp_parse(&kvp, zKey, -1, cmpp_kvp_op_eq1);
-  if( !kvp.v.z || !kvp.v.n ){
-    fatal("Invalid filename: %s", zKey);
-  }
-  sqlite3_stmt * q = 0;
-  FileWrapper fw = FileWrapper_empty;
-  FileWrapper_open(&fw, kvp.v.z, "r");
-  FileWrapper_slurp(&fw);
-  q = g_stmt(GStmt_defIns);
-  //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
-  db_bind_textn(q, 1, kvp.k.z, (int)kvp.k.n);
-  if( g.flags.chompF ){
-    FileWrapper_chomp(&fw);
-  }
-  if( fw.nContent ){
-    db_affirm_rc(
-      sqlite3_bind_text(q, 2,
-                        (char const *)fw.zContent,
-                        (int)fw.nContent, sqlite3_free),
-      "binding file content");
-    fw.zContent = 0 /* transfered ownership */;
-    fw.nContent = 0;
-  }else{
-    db_affirm_rc( sqlite3_bind_null(q, 2),
-                  "binding empty file content");
-  }
-  FileWrapper_close(&fw);
-  db_step(q);
-  g_stmt_reset(q);
-  g_debug(2,("define: %s%s%s\n",
-             kvp.k.z,
-             kvp.v.z ? " with value " : "",
-             kvp.v.z ? kvp.v.z : ""));
-}
-
-#define ustr_c(X) ((unsigned char const *)X)
-
-static inline unsigned int cmpp_strlen(char const *z, int n){
-  return n<0 ? (int)strlen(z) : (unsigned)n;
-}
-
-
-int db_define_has(const char * zName, int nName){
-  int rc;
-  sqlite3_stmt * const q = g_stmt(GStmt_defHas);
-  nName = cmpp_strlen(zName, nName);
-  db_bind_textn(q, 1, zName, nName);
-  rc = db_step(q);
-  if(SQLITE_ROW == rc){
-    rc = 1;
-  }else{
-    assert(SQLITE_DONE==rc);
-    rc = 0;
-  }
-  g_debug(1,("defined [%s] ?= %d\n",zName, rc));
-  g_stmt_reset(q);
-  return rc;
-}
-
-int db_define_get_bool(const char * zName, int nName){
-  sqlite3_stmt * const q = g_stmt(GStmt_defGetBool);
-  int rc = 0;
-  nName = cmpp_strlen(zName, nName);
-  db_bind_textn(q, 1, zName, nName);
-  rc = db_step(q);
-  if(SQLITE_ROW == rc){
-    if( SQLITE_ROW==sqlite3_step(q) ){
-      fatal("Key is ambiguous: %s", zName);
-    }
-    rc = 1;
-  }else{
-    assert(SQLITE_DONE==rc);
-    rc = 0;
-  }
-  g_stmt_reset(q);
-  return rc;
-}
-
-int db_define_get(const char * zName, int nName,
-                  char **zVal, unsigned int *nVal){
-  sqlite3_stmt * q = g_stmt(GStmt_defGet);
-  nName = cmpp_strlen(zName, nName);
-  db_bind_textn(q, 1, zName, nName);
-  int n = 0;
-  int rc = db_step(q);
-  if(SQLITE_ROW == rc){
-    const unsigned char * z = sqlite3_column_text(q, 1);
-    n = sqlite3_column_bytes(q,1);
-    if( nVal ) *nVal = (unsigned)n;
-    *zVal = sqlite3_mprintf("%.*s", n, z);
-    if( n && z ) check__oom(*zVal);
-    if( SQLITE_ROW==sqlite3_step(q) ){
-      db_free(*zVal);
-      *zVal = 0;
-      fatal("Key is ambiguous: %.*s\n",
-            nName, zName);
-    }
-    rc = 1;
-  }else{
-    assert(SQLITE_DONE==rc);
-    rc = 0;
-  }
-  g_debug(1,("define [%.*s] ?= %d %.*s\n",
-             nName, zName, rc,
-             *zVal ? n : 0,
-             *zVal ? *zVal : "<NULL>"));
-  g_stmt_reset(q);
-  return rc;
-}
-
-void db_define_rm(const char * zKey){
-  int rc;
-  int n = 0;
-  sqlite3_stmt * const q = g_stmt(GStmt_defDel);
-  db_bind_text(q, 1, zKey);
-  rc = db_step(q);
-  if(SQLITE_DONE != rc){
-    db_affirm_rc(rc, "Stepping DELETE on def");
-  }
-  g_debug(2,("undefine: %.*s\n",n, zKey));
-  g_stmt_reset(q);
-}
-
-void db_including_add(const char * zKey, const char * zSrc, int srcLine){
-  int rc;
-  sqlite3_stmt * const q = g_stmt(GStmt_inclIns);
-  db_bind_text(q, 1, zKey);
-  db_bind_text(q, 2, zSrc);
-  db_bind_int(q, 3, srcLine);
-  rc = db_step(q);
-  if(SQLITE_DONE != rc){
-    db_affirm_rc(rc, "Stepping INSERT on incl");
-  }
-  g_debug(2,("is-including-file add [%s] from [%s]:%d\n", zKey, zSrc, srcLine));
-  g_stmt_reset(q);
-}
-
-void db_include_rm(const char * zKey){
-  int rc;
-  sqlite3_stmt * const q = g_stmt(GStmt_inclDel);
-  db_bind_text(q, 1, zKey);
-  rc = db_step(q);
-  if(SQLITE_DONE != rc){
-    db_affirm_rc(rc, "Stepping DELETE on incl");
-  }
-  g_debug(2,("inclpath rm [%s]\n", zKey));
-  g_stmt_reset(q);
-}
-
-char * db_include_search(const char * zKey){
-  char * zName = 0;
-  sqlite3_stmt * const q = g_stmt(GStmt_inclSearch);
-  db_bind_text(q, 1, zKey);
-  if(SQLITE_ROW==db_step(q)){
-    const unsigned char * z = sqlite3_column_text(q, 0);
-    zName = z ? sqlite3_mprintf("%s", z) : 0;
-    if(!zName) fatal("Alloc failed");
-  }
-  g_stmt_reset(q);
-  return zName;
-}
-
-static int db_including_has(const char * zName){
-  int rc;
-  sqlite3_stmt * const q = g_stmt(GStmt_inclHas);
-  db_bind_text(q, 1, zName);
-  rc = db_step(q);
-  if(SQLITE_ROW == rc){
-    rc = 1;
-  }else{
-    assert(SQLITE_DONE==rc);
-    rc = 0;
-  }
-  g_debug(2,("inclpath has [%s] = %d\n",zName, rc));
-  g_stmt_reset(q);
-  return rc;
-}
-
-#if 0
-/*
-** Fails fatally if the `#include` list contains the given key.
-*/
-static void db_including_check(const char * zKey);
-void db_including_check(const char * zName){
-  if(db_including_has(zName)){
-    fatal("Recursive include detected: %s\n", zName);
-  }
-}
-#endif
-
-void db_include_dir_add(const char * zDir){
-  static int seq = 0;
-  int rc;
-  sqlite3_stmt * const q = g_stmt(GStmt_inclPathAdd);
-  db_bind_int(q, 1, ++seq);
-  db_bind_text(q, 2, zDir);
-  rc = db_step(q);
-  if(SQLITE_DONE != rc){
-    db_affirm_rc(rc, "Stepping INSERT on inclpath");
-  }
-  g_debug(2,("inclpath add #%d: %s\n",seq, zDir));
-  g_stmt_reset(q);
-}
-
-void g_FileWrapper_link(FileWrapper *fp){
-  assert(!fp->pTail);
-  fp->pTail = g.pFiles;
-  g.pFiles = fp;
-}
-
-void g_FileWrapper_close(FileWrapper *fp){
-  assert(fp);
-  assert(fp->pTail || g.pFiles==fp);
-  g.pFiles = fp->pTail;
-  fp->pTail = 0;
-  FileWrapper_close(fp);
-}
-
-static void g_cleanup(int bCloseFileChain){
-  if( g.db ){
-#define E(N,S) sqlite3_finalize(g.stmt.N); g.stmt.N = 0;
-  GStmt_map(E)
-#undef E
-  }
-  if( bCloseFileChain ){
-    FileWrapper * fpNext = 0;
-    for( FileWrapper * fp=g.pFiles; fp; fp=fpNext ){
-      fpNext = fp->pTail;
-      fp->pTail = 0;
-      FileWrapper_close(fp);
-    }
-  }
-  FileWrapper_close(&g.out);
-  if(g.db){
-    sqlite3_close(g.db);
-    g.db = 0;
-  }
-}
-
-static void cmpp_atexit(void){
-  g_cleanup(1);
-}
-
-int cmpp_is_legal_key(char const *zKey, int nKey, char const **zAt){
-  char const * z = zKey;
-  nKey = cmpp_strlen(zKey, nKey);
-  if( !nKey ){
-    if( zAt ) *zAt = z;
-    return 0;
-  }
-  char const * const zEnd = z ? z + nKey : NULL;
-  for( ; z < zEnd; ++z ){
-    switch( (0x80 & *z) ? 0 : *z ){
-      case 0:
-      case '_':
-        continue;
-      case '-':
-      case '.':
-      case '/':
-      case ':':
-      case '=':
-      case '0': case '1': case '2': case '3': case '4':
-      case '5': case '6': case '7': case '8': case '9':
-        if( z==zKey ) break;
-        continue;
-      default:
-        if( isalpha((int)*z) ) continue;
-    }
-    if( zAt ) *zAt = z;
-    return 0;
-  }
-  assert( z==zEnd );
-  return 1;
-}
-
-void cmpp_affirm_legal_key(char const *zKey, int nKey){
-  char const *zAt = 0;
-  nKey = cmpp_strlen(zKey, nKey);
-  if( !cmpp_is_legal_key(zKey, nKey, &zAt) ){
-    assert( zAt );
-    fatal("Illegal character 0x%02x in key [%.*s]\n",
-          (int)*zAt, nKey, zKey);
-  }
-}
-
-/*
-** sqlite3 UDF which returns true if its argument refers to an
-** accessible file, else false.
-*/
-static void udf_file_exists(
-  sqlite3_context *context,
-  int argc,
-  sqlite3_value **argv
-){
-  const char *zName;
-  (void)(argc);  /* Unused parameter */
-  zName = (const char*)sqlite3_value_text(argv[0]);
-  if( zName==0 ) return;
-  sqlite3_result_int(context, 0==access(zName, 0));
-}
-
-/**
- ** This sqlite3_trace_v2() callback outputs tracing info using
- ** g.sqlTrace.pFile.
-*/
-static int cmpp__db_sq3TraceV2(unsigned t,void*c,void*p,void*x){
-  static unsigned int counter = 0;
-  switch(t){
-    case SQLITE_TRACE_STMT:{
-      FILE * const fp = g.sqlTrace.pFile;
-      if( fp ){
-        char const * const zSql = (char const *)x;
-        char * const zExp = g.sqlTrace.expandSql
-          ? sqlite3_expanded_sql((sqlite3_stmt*)p)
-          : 0;
-        fprintf(fp, "SQL TRACE #%u: %s\n",
-                ++counter, zExp ? zExp : zSql);
-        sqlite3_free(zExp);
-      }
-      break;
-    }
-  }
-  return 0;
-}
-
-/* Initialize g.db, failing fatally on error. */
-static void cmpp_initdb(void){
-  int rc;
-  char * zErr = 0;
-  const char * zSchema =
-    "CREATE TABLE def("
-    /* ^^^ defines */
-      "k TEXT PRIMARY KEY NOT NULL,"
-      "v TEXT DEFAULT NULL"
-    ") WITHOUT ROWID;"
-    "CREATE TABLE incl("
-    /* ^^^ files currently being included */
-      "file TEXT PRIMARY KEY NOT NULL,"
-      "srcFile TEXT DEFAULT NULL,"
-      "srcLine INTEGER DEFAULT 0"
-    ") WITHOUT ROWID;"
-    "CREATE TABLE inclpath("
-    /* ^^^ include path */
-      "seq INTEGER UNIQUE ON CONFLICT IGNORE, "
-      "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
-    ");"
-    "BEGIN;"
-    ;
-  assert(0==g.db);
-  if(g.db) return;
-  rc = sqlite3_open_v2(":memory:", &g.db, SQLITE_OPEN_READWRITE, 0);
-  if(rc) fatal("Error opening :memory: db.");
-  sqlite3_trace_v2(g.db, SQLITE_TRACE_STMT, cmpp__db_sq3TraceV2, 0);
-  rc = sqlite3_exec(g.db, zSchema, 0, 0, &zErr);
-  if(rc) fatal("Error initializing database: %s", zErr);
-  rc = sqlite3_create_function(g.db, "fileExists", 1,
-                               SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
-                               udf_file_exists, 0, 0);
-  db_affirm_rc(rc, "UDF registration failed.");
-}
-
-/*
-** For position zPos, which must be in the half-open range
-** [zBegin,zEnd), returns g.delim.n if it is at the start of a line and
-** starts with g.delim.z, else returns 0.
-*/
-//static
-unsigned short cmpp_is_delim(unsigned char const *zBegin,
-                                    unsigned char const *zEnd,
-                                    unsigned char const *zPos){
-  assert(zEnd>zBegin);
-  assert(zPos<zEnd);
-  assert(zPos>=zBegin);
-  if(zPos>zBegin &&
-     ('\n'!=*(zPos - 1)
-      || ((unsigned)(zEnd - zPos) <= g.delim.n))){
-    return 0;
-  }else if(0==memcmp(zPos, g.delim.z, g.delim.n)){
-    return g.delim.n;
-  }else{
-    return 0;
-  }
-}
-
-static void cmpp_t_out_expand(CmppTokenizer * const t,
-                              unsigned char const * zFrom,
-                              unsigned int n);
-
-static inline int cmpp__isspace(int ch){
-  return ' '==ch || '\t'==ch;
-}
-
-static inline unsigned cmpp__strlenu(unsigned char const *z, int n){
-  return n<0 ? (unsigned)strlen((char const *)z) : (unsigned)n;
-}
-
-static inline void cmpp__skip_space_c( unsigned char const **p,
-                                       unsigned char const *zEnd ){
-  unsigned char const * z = *p;
-  while( z<zEnd && cmpp__isspace(*z) ) ++z;
-  *p = z;
-}
-
-#define ustr_c(X) ((unsigned char const *)X)
-//#define ustr_nc(X) ((unsigned char *)X)
-
-/**
-   Scan [t->zPos,t->zEnd) for a derective delimiter. Emits any
-   non-delimiter output found along the way.
-
-   This updtes t->zPos and t->lineNo as it goes.
-
-   If a delimiter is found, it updates t->token and returns 0.
-   On no match returns 0.
-*/
-static
-int CmppTokenizer__delim_search(CmppTokenizer * const t){
-  if(!t->zPos) t->zPos = t->zBegin;
-  if( t->zPos>=t->zEnd ){
-    return 0;
-  }
-  assert( (t->zPos==t->zBegin || t->zPos[-1]=='\n')
-          && "Else we've mismanaged something.");
-  char const * const zD = g.delim.z;
-  unsigned short const nD = g.delim.n;
-  unsigned char const * const zEnd = t->zEnd;
-  unsigned char const * zLeft = t->zPos;
-  unsigned char const * z = zLeft;
-
-  assert( 0==*zEnd && "Else we'll misinteract with strcspn()" );
-  if( *zEnd ){
-    fatal("Input must be NUL-terminated.");
-    return 0;
-  }
-#define tflush                                        \
-  if(z>zEnd) z=zEnd;                                  \
-  if( z>zLeft ) {                                     \
-    cmpp_t_out_expand(t, zLeft, (unsigned)(z-zLeft)); \
-  } zLeft = z
-  while(z < zEnd){
-    size_t nNlTotal = 0;
-    unsigned char const * zNl;
-    size_t nNl2 = strcspn((char const *)z, "\n");
-    zNl = (z + nNl2 >= zEnd ? zEnd : z + nNl2);
-    if( nNl2 >= CmppArgs_BufSize /* too long */
-        //|| '\n'!=(char)*zNl   /* end of input */
-        /* ^^^ we have to accept a missing trailing EOL for the
-           sake of -e scripts. */
-    ){
-      /* we'd like to error out here, but only if we know we're
-         reading reading a directive line. */
-      ++t->lineNo;
-      z = zNl + 1;
-      tflush;
-      continue;
-    }
-    nNlTotal += nNl2;
-    assert( '\n'==*zNl || !*zNl );
-    assert( '\n'==*zNl || zNl==zEnd );
-    //g_stderr("input: zNl=%d z=<<<%.*s>>>", (int)*zNl, (zNl-z), z);
-    unsigned char const * const zBOL = z;
-    cmpp__skip_space_c(&z, zNl);
-    if( z+nD < zNl && 0==memcmp(z, zD, nD) ){
-      /* Found a directive delimiter. */
-      if( zBOL!=z ){
-        /* Do not emit space from the same line which preceeds a
-           delimiter */
-        zLeft = z;
-      }
-      while( zNl>z && zNl<zEnd
-             && '\n'==*zNl && '\\'==zNl[-1] ){
-        /* Backslash-escaped newline: extend the token
-           to consume it all. */
-        ++t->lineNo;
-        ++zNl;
-        nNl2 = strcspn((char const *)zNl, "\n");
-        if( !nNl2 ) break;
-        nNlTotal += nNl2;
-        zNl += nNl2;
-      }
-      assert( zNl<=zEnd && "Else our input was not NUL-terminated");
-      if( nNlTotal >= CmppArgs_BufSize ){
-        fatal("Directive line is too long (%u)",
-              (unsigned)(zNl-z));
-        break;
-      }
-      tflush;
-      t->token.zBegin = z + nD;
-      t->token.zEnd = zNl;
-      cmpp__skip_space_c(&t->token.zBegin, t->token.zEnd);
-      t->token.ttype = TT_Line;
-      t->token.lineNo = t->lineNo++;
-      t->zPos = t->token.zEnd + 1;
-      if( 0 ){
-        g_stderr("token=<<%.*s>>", (t->token.zEnd - t->token.zBegin),
-                 t->token.zBegin);
-      }
-      return 1;
-    }
-    z = zNl+1;
-    ++t->lineNo;
-    tflush;
-    //g_stderr("line #%d no match\n",(int)t->lineNo);
-  }
-  tflush;
-  t->zPos = z;
-  return 0;
-#undef tflush
-}
-
-void cmpp_kvp_parse(cmpp_kvp * p, char const *zKey, int nKey,
-                    cmpp_kvp_op_e opPolicy){
-  char chEq = 0;
-  char opLen = 0;
-  *p = cmpp_kvp_empty;
-  p->k.z = zKey;
-  p->k.n = cmpp_strlen(zKey, nKey);
-  switch( opPolicy ){
-    case cmpp_kvp_op_none: break;
-    case cmpp_kvp_op_eq1:
-      chEq = '=';
-      opLen = 1;
-      break;
-    default:
-      assert(!"don't use these yet");
-      /* todo: ==, !=, <=, <, >, >= */
-      chEq = '=';
-      opLen = 1;
-      break;
-  }
-  assert( chEq );
-  p->op = cmpp_kvp_op_none;
-  const char * const zEnd = p->k.z + p->k.n;
-  for(const char * zPos = p->k.z ; *zPos && zPos<zEnd ; ++zPos) {
-    if( chEq==*zPos ){
-      if( cmpp_kvp_op_none==opPolicy ){
-        fatal("Illegal operator in key: %s", zKey);
-      }
-      p->op = cmpp_kvp_op_eq1;
-      p->k.n = (unsigned)(zPos - zKey);
-      zPos += opLen;
-      assert( zPos <= zEnd );
-      p->v.z = zPos;
-      p->v.n = (unsigned)(zEnd - zPos);
-      break;
-    }
-  }
-  cmpp_affirm_legal_key(p->k.z, p->k.n);
-}
-
-static void cmpp_t_out_expand(CmppTokenizer * const t,
-                              unsigned char const * zFrom,
-                              unsigned int n){
-  unsigned char const *zLeft = zFrom;
-  unsigned char const * const zEnd = zFrom + n;
-  unsigned char const *z = AT_OFF==g.flags.atPolicy ? zEnd : zLeft;
-  unsigned char const chEol = (unsigned char)'\n';
-  int state = 0 /* 0==looking for opening @
-                ** 1==looking for closing @ */;
-  if( 0 ){
-    g_warn("zLeft=%d %c", (int)*zLeft, *zLeft);
-  }
-#define tflush \
-  if(z>zEnd) z=zEnd; \
-  if(zLeft<z){ cmpp_t_out(t, zLeft, (z-zLeft)); } zLeft = z
-  for( ; z<zEnd; ++z ){
-    zLeft = z;
-    for( ;z<zEnd; ++z ){
-      if( chEol==*z ){
-        state = 0;
-        continue;
-      }
-      if( g.delim.chAt==*z ){
-        if( 0==state ){
-          tflush;
-          state = 1;
-        }else{ /* process chAt...chAt */
-          char *zVal = 0;
-          unsigned int nVal = 0;
-          assert( 1==state );
-          assert( g.delim.chAt==*zLeft );
-          assert( zLeft<z );
-          if( z==zLeft+1 ){
-            tflush;
-          }else{
-            char const *zKey = (char const*)zLeft+1;
-            int const nKey = (z-zLeft-1);
-            if( db_define_get(zKey, nKey, &zVal, &nVal) ){
-              if( nVal ){
-                cmpp_t_out(t, (unsigned char const*)zVal, nVal);
-              }else{
-                /* Elide it */
-              }
-              zLeft = z+1/*skip closing g.delim.chAt*/;
-              db_free(zVal);
-            }else{
-              assert( !zVal );
-              /* No match. Emit it as-is. */
-              switch( g.flags.atPolicy ){
-                case AT_RETAIN:
-                  tflush;
-                  break;
-                case AT_ERROR:
-                  fatal("Undefined key: %c%.*s%c",
-                        g.delim.chAt, nKey, zKey, g.delim.chAt );
-                  break;
-                case AT_ELIDE:
-                  zLeft = z+1;
-                  break;
-                case AT_invalid:
-                case AT_OFF:
-                  fatal("Unhandled g.flags.atPolicy #%d!",
-                        g.flags.atPolicy);
-                  break;
-              }
-            }
-            state = 0;
-          }
-        }/* process chAt...chAt */
-      }/*g.delim.chAt*/
-    }/*per-line loop*/
-  }/*outer loop*/
-  tflush;
-#undef tflush
-  return;
-}
-
-/*
-** Scans t to the next keyword line, emitting all input before that
-** which is _not_ a keyword line unless it's elided due to being
-** inside a block which elides its content. Returns 0 if no keyword
-** line was found, in which case the end of the input has been
-** reached, else returns a truthy value and sets up t's state for use
-** with cmpp_process_keyword(), which should then be called.
-*/
-static int cmpp_next_keyword_line(CmppTokenizer * const t){
-  CmppToken * const tok = &t->token;
-
-  assert(t->zBegin);
-  assert(t->zEnd > t->zBegin);
-  if(!t->zPos) t->zPos = t->zBegin;
-  t->args.pKw = 0;
-  t->args.argc = 0;
-  *tok = CmppToken_empty;
-  if( !CmppTokenizer__delim_search(t) ){
-    return 0;
-  }
-  /* Split t->token into arguments for the line's keyword */
-  int i, argc = 0, prevChar = 0;
-  const unsigned tokLen = (unsigned)(tok->zEnd - tok->zBegin);
-  unsigned char * zKwd;
-  unsigned char * zEsc;
-  unsigned char * zz;
-
-  assert(TT_Line==tok->ttype);
-  g_debug(2,("token @ line %u len=%u [[[%.*s]]]\n",
-             tok->lineNo, tokLen, tokLen, tok->zBegin));
-  zKwd = &t->args.lineBuf[0];
-  memcpy(zKwd, tok->zBegin, tokLen);
-  memset(zKwd + tokLen, 0, sizeof(t->args.lineBuf) - tokLen);
-  for( zEsc = 0, zz = zKwd; *zz; ++zz ){
-    /* Convert backslash-escaped newlines to whitespace */
-    switch((int)*zz){
-      case (int)'\\':
-        if(zEsc) zEsc = 0;
-        else zEsc = zz;
-        break;
-      case (int)'\n':
-        assert(zEsc && "Should not have an unescaped newline?");
-        if(zEsc==zz-1){
-          *zEsc = (unsigned char)' ';
-          /* FIXME?: memmove() lnBuf content one byte to the left here
-          ** to collapse backslash and newline into a single
-          ** byte. Also consider collapsing all leading space on the
-          ** next line. (Much later: or just collapse the output as we go,
-          ** effectively shrinking the line.) */
-        }
-        zEsc = 0;
-        *zz = (unsigned char)' ';
-        break;
-      default:
-        zEsc = 0;
-        break;
-    }
-  }
-  t->args.argv[argc++] = zKwd;
-  for( zz = zKwd; *zz; ++zz ){
-    if(isspace(*zz)){
-      *zz = 0;
-      break;
-    }
-  }
-  t->args.pKw = CmppKeyword_search((char const *)zKwd);
-  if(!t->args.pKw){
-    fatal("Unknown keyword '%s' at line %u\n", (char const *)zKwd,
-          tok->lineNo);
-  }
-  for( ++zz ; *zz && isspace(*zz); ++zz ){}
-  if(t->args.pKw->bTokenize){
-    for( ; *zz; prevChar = *zz, ++zz ){
-      /* Split string into word-shaped tokens.
-      ** TODO ?= quoted strings, for the sake of the
-      ** #error keyword. */
-      if(isspace(*zz)){
-        assert(zz!=zKwd && "Leading space was stripped earlier.");
-        *zz = 0;
-      }else{
-        if(argc == (int)CmppArgs_Max){
-          fatal("Too many arguments @ line %u: %.*s",
-                tok->lineNo, tokLen, tok->zBegin);
-        }else if(zz>zKwd && !prevChar){
-          t->args.argv[argc++] = zz;
-        }
-      }
-    }
-  }else{
-    /* Treat rest of line as one token */
-    if(*zz) t->args.argv[argc++] = zz;
-  }
-  tok->ttype = t->args.pKw->ttype;
-  if(g.flags.doDebug>1){
-    for(i = 0; i < argc; ++i){
-      g_debug(0,("line %u arg #%d=%s\n",
-                 tok->lineNo, i,
-                 (char const *)t->args.argv[i]));
-    }
-  }
-  t->args.argc = argc;
-  return 1;
-}
-
-/* Internal error reporting helper for cmpp_keyword_f() impls. */
-static CMPP_NORETURN void cmpp_kwd__err_(char const *zFile, int line,
-                                         CmppKeyword const * pKw,
-                                         CmppTokenizer const *t,
-                                         char const *zFmt, ...){
-  va_list va;
-  g_stderr("%s @ %s line %u:",
-           pKw->zName, t->zName, t->token.lineNo);
-  va_start(va, zFmt);
-  g.tok = 0 /* stop fatalv__base() from duplicating the file info */;
-  fatalv__base(zFile, line, zFmt, va);
-  /* not reached */
-  va_end(va);
-}
-#define cmpp_kwd__err(...) cmpp_kwd__err_(__FILE__,__LINE__, __VA_ARGS__)
-#define cmpp_t__err(T,...) cmpp_kwd__err_(__FILE__,__LINE__, (T)->args.pKw, (T), __VA_ARGS__)
-
-/* No-op cmpp_keyword_f() impl. */
-static void cmpp_kwd_noop(CmppKeyword const * pKw, CmppTokenizer *t){
-  (void)pKw;
-  (void)t;
-}
-
-/* #error impl. */
-static void cmpp_kwd_error(CmppKeyword const * pKw, CmppTokenizer *t){
-  if(CT_skip(t)) return;
-  else{
-    assert(t->args.argc < 3);
-    const char *zBegin = t->args.argc>1
-      ? (const char *)t->args.argv[1] : 0;
-    cmpp_t__err(t, "%s", zBegin ? zBegin : "(no additional info)");
-  }
-}
-
-/* Impl. for #define, #undef */
-static void cmpp_kwd_define(CmppKeyword const * pKw, CmppTokenizer *t){
-  if(CT_skip(t)) return;
-  if(t->args.argc<2){
-    cmpp_kwd__err(pKw, t, "Expecting one or more arguments");
-  }else{
-    int i = 1;
-    for( ; i < t->args.argc; ++i){
-      char const * const zArg = (char const *)t->args.argv[i];
-      cmpp_affirm_legal_key(zArg, -1);
-      if( TT_Define==pKw->ttype ){
-        db_define_add( zArg, NULL );
-      }else{
-        db_define_rm( zArg );
-      }
-    }
-  }
-}
-
-static int cmpp_val_matches(char const *zGlob, char const *zRhs){
-  return 0==sqlite3_strglob(zGlob, zRhs);
-}
-
-typedef int (*cmpp_vcmp_f)(char const *zLhs, char const *zRhs);
-
-/*
-** Accepts a key in the form X or X=Y. In the former case, it uses
-** db_define_get_bool(kvp->k) to determine its truthiness, else it
-** compares the kvp->v part to kvp->k's defined value to determine
-** truthiness.
-**
-** Unless...
-**
-** If bCheckDefined is true is true then (A) it returns true if the
-** value is defined and (B) fails fatally if given an X=Y-format key.
-**
-** Returns true if zKey evals to true, else false.
-*/
-//static
-int cmpp_kvp_truth(CmppKeyword const * const pKw,
-                   CmppTokenizer const * const t,
-                   cmpp_kvp const * const kvp,
-                   int bCheckDefined){
-  int buul = 0;
-  if( kvp->v.z ){
-    if( bCheckDefined ){
-      cmpp_kwd__err(pKw, t, "Value part is not legal for "
-                    "is-defined checks: %.s",
-                    kvp->k.n, kvp->k.z);
-    }
-    char * zVal = 0;
-    unsigned int nVal = 0;
-    buul = db_define_get(kvp->k.z, (int)kvp->k.n, &zVal, &nVal);
-      //g_debug(0,("checking key[%.*s]=%.*s\n", (zEq-zKey), zKey, nVal, zVal));
-    if( kvp->v.n && nVal ){
-      /* FIXME? do this with a query */
-      /*g_debug(0,("if get-define [%.*s]=[%.*s] zValPart=%s\n",
-        (zEq-zKey), zKey,
-        nVal, zVal, zValPart));*/
-      buul = cmpp_val_matches(kvp->v.z, zVal);
-      //g_debug(0,("buul=%d\n", buul));
-    }else{
-      assert( 0==kvp->v.n || 0==nVal );
-      buul = kvp->v.n == nVal;
-    }
-    db_free(zVal);
-  }else{
-    if( bCheckDefined ){
-      buul = db_define_has(kvp->k.z, kvp->k.n);
-    }else{
-      buul = db_define_get_bool(kvp->k.z, kvp->k.n);
-    }
-  }
-  return buul;
-}
-
-#if 0
-/*
-** A thin proxy for cmpp_kvp_truth().
-*/
-static int cmpp_key_truth(CmppKeyword const * pKw,
-                          CmppTokenizer const * t,
-                          char const *zKey, int bCheckDefined){
-  cmpp_kvp kvp = cmpp_kvp_empty;
-  cmpp_kvp_parse(&kvp, zKey, -1, cmpp_kvp_op_eq1);
-  return cmpp_kvp_truth(pKw, t, &kvp, bCheckDefined);
-}
-#endif
-
-//static
-cmpp_kvp_op_e cmpp_t_is_op(CmppTokenizer const * t, int arg){
-  if( t->args.argc > arg ){
-    char const * const z = (char const *)t->args.argv[arg];
-#define E(N,S) if( strcmp(S,z) ) return cmpp_kvp_op_ ## N; else
-  cmpp_kvp_op_map(E)
-#undef E
-    if(0) {}
-  }
-  return cmpp_kvp_op_none;
-}
-
-/*
-** A single part of an #if-type expression. They are parsed from
-** CmppTokenizer::args in this form:
-**
-**  not* defined{0,1} key[=[value]]
-*/
-struct CmppExprDef {
-  /* The key part of the input. */
-  cmpp_kvp kvp;
-  struct {
-    int ndx;
-    int next;
-  } arg;
-  CmppTokenizer const * tizer;
-  /* Set to 0 or 1 depending how many "not" are parsed. */
-  unsigned char bNegated;
-  /* Set to 1 if "defined" is parsed. */
-  unsigned char bCheckDefined;
-};
-typedef struct CmppExprDef CmppExprDef;
-#define CmppExprDef_empty_m {cmpp_kvp_empty_m,{0,0},0,0,0}
-static const CmppExprDef CmppExprDef_empty = CmppExprDef_empty_m;
-
-/*
-** Evaluate cep to true or false and return that value:
-**
-** If cep->bCheckDefined, return the result of db_define_has().
-**
-** Else if cep->kvp.v.z is not NULL then fetch the define's value
-** and return the result of cmpp_val_matches(cep->kvp.v.z,thatValue).
-**
-** Else return the result of db_define_get_bool().
-**
-** The returned result accounts for cep->bNegated.
-*/
-static int CmppExprDef_eval(CmppExprDef const * cep){
-  int buul = 0;
-
-  if( cep->bCheckDefined ){
-    assert( !cep->kvp.v.n );
-    buul = db_define_has(cep->kvp.k.z, (int)cep->kvp.k.n);
-  }else if( cep->kvp.v.z ){
-    unsigned nVal = 0;
-    char * zVal = 0;
-    buul = db_define_get(cep->kvp.k.z, cep->kvp.k.n, &zVal, &nVal);
-    if( nVal ){
-      buul = cmpp_val_matches(cep->kvp.v.z, zVal);
-    }
-    db_free(zVal);
-  }else{
-    buul = db_define_get_bool(cep->kvp.k.z, cep->kvp.k.n);
-  }
-  return cep->bNegated ? !buul : buul;
-}
-
-/*
-** Expects t->args, starting at t->args.argv[startArg], to parse to
-** one CmmpExprDef. It clears cep and repopulates it with info about
-** the parse. Fails fatally on a parse error.
-**
-** Returns true if it reads one, false if it doesn't, and fails fatally
-** if what it tries to parse is not empty but is not a CmppExprDef.
-**
-** Specifically, it parses:
-**
-**   not+ defined? Word[=value]
-**
-*/
-static int CmppExprDef_read_one(CmppKeyword const * pKw,
-                                CmppTokenizer const * t,
-                                int startArg, CmppExprDef * cep){
-  char const *zKey = 0;
-  *cep = CmppExprDef_empty;
-  cep->arg.ndx = startArg;
-  assert( t->args.pKw );
-  assert( t->args.pKw==pKw );
-  cep->tizer = t;
-  for(int i = startArg; !zKey && i<t->args.argc; ++i ){
-    char const * z = (char const *)t->args.argv[i];
-    if( 0==strcmp(z, "not") ){
-      cep->bNegated = !cep->bNegated;
-    }else if( 0==strcmp(z,"defined") ){
-      if( cep->bCheckDefined ){
-        cmpp_kwd__err(pKw, t,
-                      "Cannot use 'defined' more than once");
-      }
-      cep->bCheckDefined = 1;
-    }else{
-      assert( !zKey );
-      cmpp_kvp_parse(&cep->kvp, z, -1, cmpp_kvp_op_eq1);
-      if( cep->bCheckDefined && cep->kvp.v.z ){
-        cmpp_kwd__err(pKw, t, "Cannot use X=Y keys with 'defined'");
-        cep->arg.next = ++i;
-      }
-      return 1;
-    }
-  }
-  return 0;
-}
-
-/*
-** Evals pStart and then proceeds to process any remaining arguments
-** in t->args as RHS expressions. Returns the result of the expression
-** as a bool.
-**
-** Specifically, it parses:
-**
-**   and|or CmppExprDef
-**
-** Where CmppExprDef is the result of CmppExprDef_read_one().
-*/
-static int CmppExprDef_parse_cond(CmppKeyword const *pKw,
-                                  CmppTokenizer *t,
-                                  CmppExprDef const * pStart){
-  enum { Op_none = 0, Op_And, Op_Or };
-  int lhs = CmppExprDef_eval(pStart);
-  int op = Op_none;
-  int i = pStart->arg.next;
-  for( ; i < t->args.argc; ++i ){
-    CmppExprDef eNext = CmppExprDef_empty;
-    char const *z = (char const *)t->args.argv[i];
-    if( 0==strcmp("and",z) ){
-      if( Op_none!=op ) goto multiple_ops;
-      op = Op_And;
-      continue;
-    }else if( 0==strcmp("or",z) ){
-      if( Op_none!=op ) goto multiple_ops;
-      op = Op_Or;
-      continue;
-    }else if( !CmppExprDef_read_one(pKw, t, i, &eNext) ){
-      if( Op_none!=op ){
-        cmpp_t__err(t, "Stray operator: %s",z);
-      }
-    }
-    assert( eNext.kvp.k.z );
-    int const rhs = CmppExprDef_eval(&eNext);
-    switch( op ){
-      case Op_none: break;
-      case Op_And: lhs = lhs && rhs; break;
-      case Op_Or:  lhs = lhs || rhs; break;
-      default:
-        assert(!"cannot happen");
-        fatal("this cannot happen");
-    }
-    op = Op_none;
-  }
-  if( Op_none!=op ){
-    cmpp_t__err(t, "Extra operator at end of expression");
-  }else if( i < t->args.argc ){
-    assert(!"cannot happen");
-    cmpp_kwd__err(t->args.pKw, t, "Unhandled extra arguments");
-  }else{
-    return lhs;
-  }
-  assert(!"not reached");
-multiple_ops:
-  cmpp_t__err(t,"Cannot have multiple operators");
-  return 0 /* not reached */;
-}
-
-/* Impl. for #if, #elif, #assert. */
-static void cmpp_kwd_if(CmppKeyword const * pKw, CmppTokenizer *t){
-  CmppParseState tmpState = TS_Start;
-  CmppExprDef cep = CmppExprDef_empty;
-  //int buul = 0;
-  assert( TT_If==pKw->ttype
-          || TT_Elif==pKw->ttype
-          || TT_Assert==pKw->ttype);
-  if(t->args.argc<2){
-    cmpp_kwd__err(pKw, t, "Expecting an argument");
-  }
-  CmppExprDef_read_one(pKw, t, 1, &cep);
-  if( !cep.kvp.k.z ){
-    cmpp_kwd__err(pKw, t, "Missing key argument");
-  }
-  /*g_debug(0,("%s %s level %u pstate=%d bNot=%d bCheckDefined=%d\n",
-             pKw->zName, zKey, t->level.ndx, (int)CT_pstate(t),
-             bNot, bCheckDefined));*/
-  switch(pKw->ttype){
-    case TT_Assert:
-      break;
-    case TT_Elif:
-      switch(CT_pstate(t)){
-        case TS_If: break;
-        case TS_IfPassed: CT_level(t).flags |= CmppLevel_F_ELIDE; return;
-        default:
-          cmpp_kwd__err(pKw, t, "'%s' used out of context",
-                        pKw->zName);
-      }
-      break;
-    case TT_If:
-      CmppLevel_push(t);
-      break;
-    default:
-      assert(!"cannot happen");
-      cmpp_kwd__err(pKw, t, "Unexpected keyword token type");
-      break;
-  }
-  if( CmppExprDef_parse_cond( pKw, t, &cep ) ){
-    CT_pstate(t) = tmpState = TS_IfPassed;
-    CT_skipLevel(t) = 0;
-  }else{
-    if( TT_Assert==pKw->ttype ){
-      cmpp_kwd__err(pKw, t, "Assertion failed: %s",
-                    /* fixme: emit the whole line. We don't have it
-                       handy in a readily-printable form. */
-                    cep.kvp.k.z);
-    }
-    CT_pstate(t) = TS_If /* also for TT_Elif */;
-    CT_skipLevel(t) = 1;
-    g_debug(3,("setting CT_skipLevel = 1 @ level %d\n", t->level.ndx));
-  }
-  if( TT_If==pKw->ttype ){
-    unsigned const lvlIf = t->level.ndx;
-    CmppToken const lvlToken = CT_level(t).token;
-    while(cmpp_next_keyword_line(t)){
-      cmpp_process_keyword(t);
-      if(lvlIf > t->level.ndx){
-        assert(TT_Endif == t->token.ttype);
-        break;
-      }
-#if 0
-      if(TS_IfPassed==tmpState){
-        tmpState = TS_Start;
-        t->level.stack[lvlIf].flags |= CmppLevel_F_ELIDE;
-        g_debug(1,("Setting ELIDE for TS_IfPassed @ lv %d (lvlIf=%d)\n", t->level.ndx, lvlIf));
-      }
-#endif
-    }
-    if(lvlIf <= t->level.ndx){
-      cmpp_kwd__err(pKw, t,
-                    "Input ended inside an unterminated %sif "
-                    "opened at [%s] line %u",
-                    g.delim.z, t->zName, lvlToken.lineNo);
-    }
-  }
-}
-
-/* Impl. for #else. */
-static void cmpp_kwd_else(CmppKeyword const * pKw, CmppTokenizer *t){
-  if(t->args.argc>1){
-    cmpp_kwd__err(pKw, t, "Expecting no arguments");
-  }
-  switch(CT_pstate(t)){
-    case TS_IfPassed: CT_skipLevel(t) = 1; break;
-    case TS_If: CT_skipLevel(t) = 0; break;
-    default:
-      cmpp_kwd__err(pKw, t, "'%s' with no matching 'if'",
-                    pKw->zName);
-  }
-  /*g_debug(0,("else flags=0x%02x skipLevel=%u\n",
-    CT_level(t).flags, CT_level(t).skipLevel));*/
-  CT_pstate(t) = TS_Else;
-}
-
-/* Impl. for #endif. */
-static void cmpp_kwd_endif(CmppKeyword const * pKw, CmppTokenizer *t){
-  /* Maintenance reminder: we ignore all arguments after the endif
-  ** to allow for constructs like:
-  **
-  ** #endif // foo
-  **
-  ** in a manner which does not require a specific comment style */
-  switch(CT_pstate(t)){
-    case TS_Else:
-    case TS_If:
-    case TS_IfPassed:
-      break;
-    default:
-      cmpp_kwd__err(pKw, t, "'%s' with no matching 'if'",
-                    pKw->zName);
-  }
-  CmppLevel_pop(t);
-}
-
-/* Impl. for #include. */
-static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){
-  char const * zFile;
-  char * zResolved;
-  int bRaw = 0 /* -raw flag */;
-  if(CT_skip(t)) return;
-  else if(t->args.argc<2 || t->args.argc>3){
-    cmpp_kwd__err(pKw, t, "Expected args: ?-raw? filename");
-  }
-  if(t->args.argc==2){
-    zFile = (const char *)t->args.argv[1];
-  }else{
-    if( 0==strcmp("-raw", (char*)t->args.argv[1]) ){
-      bRaw = 1;
-    }else{
-      cmpp_kwd__err(pKw, t, "Unhandled argument: %s",
-                    t->args.argv[1]);
-    }
-    zFile = (const char *)t->args.argv[2];
-  }
-  if(!bRaw && db_including_has(zFile)){
-    /* Note that different spellings of the same filename
-    ** will elude this check, but that seems okay, as different
-    ** spellings means that we're not re-running the exact same
-    ** invocation. We might want some other form of multi-include
-    ** protection, rather than this, however. There may well be
-    ** sensible uses for recursion. */
-    cmpp_t__err(t, "Recursive include of file: %s", zFile);
-  }
-  zResolved = db_include_search(zFile);
-  if(zResolved){
-    if( bRaw ) db_including_add(zFile, t->zName, t->token.lineNo);
-    cmpp_process_file(zResolved, bRaw);
-    if( bRaw ) db_include_rm(zFile);
-    db_free(zResolved);
-  }else{
-    cmpp_t__err(t, "file not found: %s", zFile);
-  }
-}
-
-static void cmpp_dump_defines( FILE * fp, int bIndent ){
-  sqlite3_stmt * const q = g_stmt(GStmt_defSelAll);
-  while( SQLITE_ROW==sqlite3_step(q) ){
-    unsigned char const * zK = sqlite3_column_text(q, 0);
-    unsigned char const * zV = sqlite3_column_text(q, 1);
-    int const nK = sqlite3_column_bytes(q, 0);
-    int const nV = sqlite3_column_bytes(q, 1);
-    fprintf(fp, "%s%.*s = %.*s\n",
-            bIndent ? "\t" : "", nK, zK, nV, zV);
-  }
-  g_stmt_reset(q);
-}
-
-/* Impl. for #pragma. */
-static void cmpp_kwd_pragma(CmppKeyword const * pKw, CmppTokenizer *t){
-  const char * zArg;
-  if(CT_skip(t)) return;
-  else if(t->args.argc<2){
-    cmpp_kwd__err(pKw, t, "Expecting an argument");
-  }
-  zArg = (const char *)t->args.argv[1];
-#define M(X) 0==strcmp(zArg,X)
-  if(M("defines")){
-    cmpp_dump_defines(stderr, 1);
-  }
-  else if(M("chomp-F")){
-    g.flags.chompF = 1;
-  }else if(M("no-chomp-F")){
-    g.flags.chompF = 0;
-  }
-#if 0
-  /* now done by cmpp_kwd_at_policy() */
-  else if(M("@")){
-    if(t->args.argc>2){
-      g.flags.atPolicy =
-        AtPolicy_fromStr((char const *)t->args.argv[2], 1);
-    }else{
-      g.flags.atPolicy = AT_DEFAULT;
-    }
-  }else if(M("no-@")){
-    g.flags.atPolicy = AT_OFF;
-  }
-#endif
-  else{
-    cmpp_kwd__err(pKw, t, "Unknown pragma: %s", zArg);
-  }
-#undef M
-}
-
-static void db_step_reset(sqlite3_stmt * const q, char const * zErrTip){
-  db_affirm_rc(sqlite3_step(q), zErrTip);
-  g_stmt_reset(q);
-}
-
-static void cmpp_sp_begin(CmppTokenizer * const t){
-  db_step_reset(g_stmt(GStmt_spBegin), "Starting savepoint");
-  ++t->nSavepoint;
-}
-
-static void cmpp_sp_rollback(CmppTokenizer * const t){
-  if( !t->nSavepoint ){
-    cmpp_t__err(t, "Cannot roll back: no active savepoint");
-  }
-  db_step_reset(g_stmt(GStmt_spRollback),
-                "Rolling back savepoint");
-  db_step_reset(g_stmt(GStmt_spRelease),
-                "Releasing rolled-back savepoint");
-  --t->nSavepoint;
-}
-
-static void cmpp_sp_commit(CmppTokenizer * const t){
-  if( !t->nSavepoint ){
-    cmpp_t__err(t, "Cannot commit: no active savepoint");
-  }
-  db_step_reset(g_stmt(GStmt_spRelease), "Rolling back savepoint");
-  --t->nSavepoint;
-}
-
-void CmppTokenizer_cleanup(CmppTokenizer * const t){
-  while( t->nSavepoint ){
-    cmpp_sp_rollback(t);
-  }
-}
-
-/* Impl. for #savepoint. */
-static void cmpp_kwd_savepoint(CmppKeyword const * pKw, CmppTokenizer *t){
-  const char * zArg;
-  if(CT_skip(t)) return;
-  else if(t->args.argc!=2){
-    cmpp_kwd__err(pKw, t, "Expecting one argument");
-  }
-  zArg = (const char *)t->args.argv[1];
-#define M(X) 0==strcmp(zArg,X)
-  if(M("begin")){
-    cmpp_sp_begin(t);
-  }else if(M("rollback")){
-    cmpp_sp_rollback(t);
-  }else if(M("commit")){
-    cmpp_sp_commit(t);
-  }else{
-    cmpp_kwd__err(pKw, t, "Unknown savepoint option: %s", zArg);
-  }
-#undef SP_NAME
-#undef M
-}
-
-/* #stder impl. */
-static void cmpp_kwd_stderr(CmppKeyword const * pKw, CmppTokenizer *t){
-  if(CT_skip(t)) return;
-  else{
-    const char *zBegin = t->args.argc>1
-      ? (const char *)t->args.argv[1] : 0;
-    if(zBegin){
-      g_stderr("%s:%u: %s\n", t->zName, t->token.lineNo, zBegin);
-    }else{
-      g_stderr("%s:%u: (no %.*s%s argument)\n",
-               t->zName, t->token.lineNo,
-               g.delim.n, g.delim.z, pKw->zName);
-    }
-  }
-}
-
-/* Impl. for the @ policy. */
-static void cmpp_kwd_at_policy(CmppKeyword const * pKw, CmppTokenizer *t){
-  if(CT_skip(t)) return;
-  else if(t->args.argc<2){
-    g.flags.atPolicy = AT_DEFAULT;
-  }else{
-    g.flags.atPolicy = AtPolicy_fromStr((char const*)t->args.argv[1], 1);
-  }
-}
-
-
-#if 0
-/* Impl. for dummy placeholder. */
-static void cmpp_kwd_todo(CmppKeyword const * pKw, CmppTokenizer *t){
-  (void)t;
-  g_debug(0,("TODO: keyword handler for %s\n", pKw->zName));
-}
-#endif
-
-CmppKeyword aKeywords[] = {
-/* Keep these sorted by zName */
-#define S(NAME) DStrings.NAME.z, DStrings.NAME.n
-  {S(Comment), 0, TT_Comment, cmpp_kwd_noop},
-  {S(AtPolicy), 1, TT_AtPolicy, cmpp_kwd_at_policy},
-  {S(Assert),1, TT_Assert, cmpp_kwd_if},
-  {S(Define), 1, TT_Define, cmpp_kwd_define},
-  {S(Elif), 1, TT_Elif, cmpp_kwd_if},
-  {S(Else), 1, TT_Else, cmpp_kwd_else},
-  {S(Endif), 0, TT_Endif, cmpp_kwd_endif},
-  {S(Error), 0, TT_Error, cmpp_kwd_error},
-  {S(If), 1, TT_If, cmpp_kwd_if},
-  {S(Include), 1, TT_Include, cmpp_kwd_include},
-  {S(Pragma), 1, TT_Pragma, cmpp_kwd_pragma},
-  {S(Savepoint), 1, TT_Savepoint, cmpp_kwd_savepoint},
-  {S(Stderr), 0, TT_Stderr, cmpp_kwd_stderr},
-  {S(Undef), 1, TT_Undef, cmpp_kwd_define},
-#undef S
-  {0,0,TT_Invalid, 0}
-};
-
-static int cmpp_CmppKeyword(const void *p1, const void *p2){
-  char const * zName = (const char *)p1;
-  CmppKeyword const * kw = (CmppKeyword const *)p2;
-  return strcmp(zName, kw->zName);
-}
-
-CmppKeyword const * CmppKeyword_search(const char *zName){
-  return (CmppKeyword const *)bsearch(zName, &aKeywords[0],
-                                      sizeof(aKeywords)/sizeof(aKeywords[0]) - 1,
-                                      sizeof(aKeywords[0]),
-                                      cmpp_CmppKeyword);
-}
-
-void cmpp_process_keyword(CmppTokenizer * const t){
-  assert(t->args.pKw);
-  assert(t->args.argc);
-  t->args.pKw->xCall(t->args.pKw, t);
-  t->args.pKw = 0;
-  t->args.argc = 0;
-}
-
-void cmpp_process_string(const char * zName,
-                        unsigned char const * zIn,
-                        int nIn){
-  nIn = cmpp__strlenu(zIn, nIn);
-  if( !nIn ) return;
-  CmppTokenizer const * const oldTok = g.tok;
-  CmppTokenizer ct = CmppTokenizer_empty;
-  ct.zName = zName;
-  ct.zBegin = zIn;
-  ct.zEnd = zIn + nIn;
-  while(cmpp_next_keyword_line(&ct)){
-    cmpp_process_keyword(&ct);
-  }
-  if(0!=ct.level.ndx){
-    CmppLevel const * const lv = CmppLevel_get(&ct);
-    fatal("Input ended inside an unterminated nested construct "
-          "opened at [%s] line %u", zName, lv->token.lineNo);
-  }
-  CmppTokenizer_cleanup(&ct);
-  g.tok = oldTok;
-}
-
-void cmpp_process_file(const char * zName, int bRaw){
-  FileWrapper fw = FileWrapper_empty;
-  FileWrapper_open(&fw, zName, "r");
-  g_FileWrapper_link(&fw);
-  FileWrapper_slurp(&fw);
-  g_debug(1,("Read %u byte(s) from [%s]\n", fw.nContent, fw.zName));
-  if( fw.zContent ){
-    if( bRaw ){
-      FileWrapper fw = FileWrapper_empty;
-      FileWrapper_open(&fw, zName, "rb");
-      g_FileWrapper_link(&fw);
-      FileWrapper_slurp(&fw);
-      if( 1!=fwrite(fw.zContent, fw.nContent, 1, g.out.pFile) ){
-        fatal("fwrite() failed with errno %d\n", errno);
-      }
-      g_FileWrapper_close(&fw);
-    }else{
-      cmpp_process_string(zName, fw.zContent, fw.nContent);
-    }
-  }
-  g_FileWrapper_close(&fw);
-}
-
-
-void fatalv__base(char const *zFile, int line,
-                  char const *zFmt, va_list va){
-  FILE * const fp = stderr;
-  fflush(stdout);
-  fputc('\n', fp);
-  if( g.flags.doDebug ){
-    fprintf(fp, "%s: ", g.zArgv0);
-    if( zFile ){
-      fprintf(fp, "%s:%d ",zFile, line);
-    }
-  }
-  if( g.tok ){
-    fprintf(fp,"@%s:%d: ",
-            (g.tok->zName && 0==strcmp("-",g.tok->zName))
-            ? "<stdin>"
-            : g.tok->zName,
-            g.tok->lineNo);
-  }
-  if(zFmt && *zFmt){
-    vfprintf(fp, zFmt, va);
-  }
-  fputc('\n', fp);
-  fflush(fp);
-  exit(1);
-}
-
-void fatal__base(char const *zFile, int line,
-                 char const *zFmt, ...){
-  va_list va;
-  va_start(va, zFmt);
-  fatalv__base(zFile, line, zFmt, va);
-  va_end(va);
-}
-
-#undef CT_level
-#undef CT_pstate
-#undef CT_skipLevel
-#undef CT_skip
-#undef CLvl_skip
-
-static void usage(int isErr){
-  FILE * const fOut = isErr ? stderr : stdout;
-  fprintf(fOut, "Usage: %s [flags] [infile...]\n", g.zArgv0);
-  fprintf(fOut,
-          "Flags and filenames may be in any order and "
-          "they are processed in that order.\n"
-          "\nFlags:\n");
-#define GAP "     "
-#define arg(F,D) fprintf(fOut,"\n  %s\n" GAP "%s\n",F, D)
-  arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n"
-      GAP "Because arguments are processed in order, this should\n"
-      GAP "normally be given before -f.");
-  arg("-f|--file FILE","Process FILE (default=- (stdin)).\n"
-      GAP "All non-flag arguments are assumed to be the input files.");
-  arg("-DXYZ[=value]","Define XYZ to the given value (default=1).");
-  arg("-UXYZ","Undefine all defines matching glob XYZ.");
-  arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path.");
-  arg("-FXYZ=filename",
-      "Define XYZ to the raw contents of the given file.\n"
-      GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include\n"
-      GAP "Maybe it should be. Or maybe we need a new flag for that.");
-  arg("-d|--delimiter VALUE", "Set keyword delimiter to VALUE "
-      "(default=" CMPP_DEFAULT_DELIM ").");
-  arg("--@policy retain|elide|error|off",
-      "Specifies how to handle @tokens@ (default=off).\n"
-      GAP "off    = do not look for @tokens@\n"
-      GAP "retain = parse @tokens@ and retain any undefined ones\n"
-      GAP "elide  = parse @tokens@ and elide any undefined ones\n"
-      GAP "error  = parse @tokens@ and error out for any undefined ones"
-  );
-  arg("-@", "Equivalent to --@policy=error.");
-  arg("-no-@", "Equivalent to --@policy=off (the default).");
-  arg("--sql-trace", "Send a trace of all SQL to stderr.");
-  arg("--sql-trace-x",
-      "Like --sql-trace but expand all bound values in the SQL.");
-  arg("--no-sql-trace", "Disable SQL tracing (default).");
-  arg("--chomp-F", "One trailing newline is trimmed from files "
-      "read via -FXYZ=filename.");
-  arg("--no-chomp-F", "Disable --chomp-F (default).");
-#undef arg
-#undef GAP
-  fputs("\nFlags which require a value accept either "
-        "--flag=value or --flag value.\n\n",fOut);
-}
-
-/*
-** Expects that *ndx points to the current argv entry and that it is a
-** flag which expects a value. This function checks for --flag=val and
-** (--flag val) forms. If a value is found then *ndx is adjusted (if
-** needed) to point to the next argument after the value and *zVal is
-** pointed to the value. If no value is found then it fails fatally.
-*/
-static void get_flag_val(int argc, char const * const * argv, int * ndx,
-                         char const **zVal){
-  char const * zEq = strchr(argv[*ndx], '=');
-  if( zEq ){
-    *zVal = zEq+1;
-    return;
-  }
-  if(*ndx+1>=argc){
-    fatal("Missing value for flag '%s'", argv[*ndx]);
-  }
-  *zVal = argv[++*ndx];
-}
-
-static int arg_is_flag( char const *zFlag, char const *zArg,
-                        char const **zValIfEqX ){
-  *zValIfEqX = 0;
-  if( 0==strcmp(zFlag, zArg) ) return 1;
-  char const * z = strchr(zArg,'=');
-  if( z && z>zArg ){
-    /* compare the part before the '=' */
-    if( 0==strncmp(zFlag, zArg, z-zArg) ){
-      if( !zFlag[z-zArg] ){
-        *zValIfEqX = z+1;
-        return 1;
-      }
-      /* Else it was a prefix match. */
-    }
-  }
-  return 0;
-}
-
-static void define_argv(int argc, char const * const * argv){
-  sqlite3_str * const s = sqlite3_str_new(g.db);
-  sqlite3_str_append(s, "c-pp::argv=", 11);
-  for( int i = 0; i < argc; ++i ){
-    if( i ) sqlite3_str_appendchar(s, 1, ' ');
-    sqlite3_str_appendf(s, "%s", argv[i]);
-  }
-  char * const z = sqlite3_str_finish(s);
-  assert(z);
-  if(z){
-    db_define_add(z, NULL);
-    sqlite3_free(z);
-  }
-}
-
-int main(int argc, char const * const * argv){
-  int rc = 0;
-  int inclCount = 0;
-  int nFile = 0;
-  int ndxTrace = 0;
-  int expandMode = 0;
-  char const * zVal = 0;
-#define ARGVAL if( !zVal ) get_flag_val(argc, argv, &i, &zVal)
-#define M(X) arg_is_flag(X, zArg, &zVal)
-#define ISFLAG(X) else if(M(X))
-#define ISFLAG2(X,Y) else if(M(X) || M(Y))
-#define NOVAL if( zVal ) fatal("Unexpected value for %s", zArg)
-#define g_out_open \
-  if(!g.out.pFile) FileWrapper_open(&g.out, "-", "w");    \
-  if(!inclCount){ db_include_dir_add("."); ++inclCount; } (void)0
-
-  g.zArgv0 = argv[0];
-#define DOIT if(doIt)
-  for(int doIt = 0; doIt<2; ++doIt){
-    /**
-       Loop through the flags twice. The first time we just validate
-       and look for --help/-?. The second time we process the flags.
-       This approach allows us to easily chain multiple files and
-       flags:
-
-       ./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y
-    */
-    DOIT{
-      atexit(cmpp_atexit);
-      if( 1==ndxTrace ){
-        /* Ensure that we start with tracing in the early stage if
-           --sql-trace is the first arg, in order to log schema
-           setup. */
-        g.sqlTrace.pFile = stderr;
-        g.sqlTrace.expandSql = expandMode;
-      }
-      cmpp_initdb();
-      define_argv(argc, argv);
-    }
-    for(int i = 1; i < argc; ++i){
-      int negate = 0;
-      char const * zArg = argv[i];
-      //g_stderr("i=%d zArg=%s\n", i, zArg);
-      zVal = 0;
-      while('-'==*zArg) ++zArg;
-      if(zArg==argv[i]/*not a flag*/){
-        zVal = zArg;
-        goto do_infile;
-      }
-      if( 0==strncmp(zArg,"no-",3) ){
-        zArg += 3;
-        negate = 1;
-      }
-      ISFLAG2("?","help"){
-        NOVAL;
-        usage(0);
-        goto end;
-      }else if('D'==*zArg){
-        ++zArg;
-        if(!*zArg) fatal("Missing key for -D");
-        DOIT {
-          db_define_add(zArg, 0);
-        }
-      }else if('F'==*zArg){
-        ++zArg;
-        if(!*zArg) fatal("Missing key for -F");
-        DOIT {
-          db_define_add_file(zArg);
-        }
-      }else if('U'==*zArg){
-        ++zArg;
-        if(!*zArg) fatal("Missing key for -U");
-        DOIT {
-          db_define_rm(zArg);
-        }
-      }else if('I'==*zArg){
-        ++zArg;
-        if(!*zArg) fatal("Missing directory for -I");
-        DOIT {
-          db_include_dir_add(zArg);
-          ++inclCount;
-        }
-      }
-      ISFLAG2("o","outfile"){
-        ARGVAL;
-        DOIT {
-          FileWrapper_open(&g.out, zVal, "w");
-        }
-      }
-      ISFLAG2("f","file"){
-        ARGVAL;
-      do_infile:
-        DOIT {
-          ++nFile;
-          g_out_open;
-          cmpp_process_file(zVal, 0);
-        }
-      }
-      ISFLAG("e"){
-        ARGVAL;
-        DOIT {
-          ++nFile;
-          g_out_open;
-          cmpp_process_string("-e script", ustr_c(zVal), -1);
-        }
-      }
-      ISFLAG("@"){
-        NOVAL;
-        DOIT {
-          assert( AT_DEFAULT!=AT_OFF );
-          g.flags.atPolicy = negate ? AT_OFF : AT_DEFAULT;
-        }
-      }
-      ISFLAG("@policy"){
-        AtPolicy aup;
-        ARGVAL;
-        aup = AtPolicy_fromStr(zVal, 1);
-        DOIT {
-          g.flags.atPolicy = aup;
-        }
-      }
-      ISFLAG("debug"){
-        NOVAL;
-        g.flags.doDebug += negate ? -1 : 1;
-      }
-      ISFLAG("sql-trace"){
-        NOVAL;
-        /* Needs to be set before the start of the second pass, when
-           the db is inited. */
-        g.sqlTrace.expandSql = 0;
-        DOIT {
-          g.sqlTrace.pFile = negate ? (FILE*)0 : stderr;
-        }else if( !ndxTrace && !negate ){
-          ndxTrace = i;
-          expandMode = 0;
-        }
-      }
-      ISFLAG("sql-trace-x"){
-        NOVAL;
-        g.sqlTrace.expandSql = 1;
-        DOIT {
-          g.sqlTrace.pFile = negate ? (FILE*)0 : stderr;
-        }else if( !ndxTrace && !negate ){
-          ndxTrace = i;
-          expandMode = 1;
-        }
-      }
-      ISFLAG("chomp-F"){
-        NOVAL;
-        DOIT g.flags.chompF = !negate;
-      }
-      ISFLAG2("d","delimiter"){
-        ARGVAL;
-        if( !doIt ){
-          g.delim.z = zVal;
-          g.delim.n = (unsigned short)strlen(zVal);
-          if(!g.delim.n) fatal("Keyword delimiter may not be empty.");
-        }
-      }
-      ISFLAG2("dd", "dump-defines"){
-        DOIT {
-          FILE * const fp = stderr;
-          fprintf(fp, "All %sdefine entries:\n", g.delim.z);
-          cmpp_dump_defines(fp, 1);
-        }
-      }
-      else{
-        fatal("Unhandled flag: %s", argv[i]);
-      }
-    }
-    DOIT {
-      if(!nFile){
-        if(!g.out.zName) g.out.zName = "-";
-        if(!inclCount){
-          db_include_dir_add(".");
-          ++inclCount;
-        }
-        FileWrapper_open(&g.out, g.out.zName, "w");
-        cmpp_process_file("-", 0);
-      }
-    }
-  }
-  end:
-  g_cleanup(1);
-  return rc ? EXIT_FAILURE : EXIT_SUCCESS;
-}
index a1005beb934eaffc30b631ba68944b2ed9ea144c..c54b46aadbd9223445e91c3173187fa4d5a9d84e 100644 (file)
@@ -10,7 +10,7 @@
     <title>Worker1-promiser (ESM) tests</title>
 //#else
     <title>Worker1-promiser tests</title>
-//#endif
+//#/if
   </head>
   <body>
     <header id='titlebar'><span>worker-promise tests</span></header>
@@ -37,6 +37,6 @@
 //#else
     <script src="jswasm/sqlite3-worker1-promiser.js"></script>
     <script src="demo-worker1-promiser.js"></script>
-//#endif
+//#/if
   </body>
 </html>
index c129e212818f5bef0818d2a67a01eba64f2206dd..1521edfc177ab7f09e80609e90211121b2947c62 100644 (file)
@@ -19,7 +19,7 @@ import {default as promiserFactory} from "./jswasm/sqlite3-worker1-promiser.mjs"
 "use strict";
 const promiserFactory = globalThis.sqlite3Worker1Promiser.v2;
 delete globalThis.sqlite3Worker1Promiser;
-//#endif
+//#/if
 (async function(){
   const T = globalThis.SqliteTestUtil;
   const eOutput = document.querySelector('#test-output');
@@ -53,7 +53,7 @@ delete globalThis.sqlite3Worker1Promiser;
          before workerPromise is set. */
       console.warn("This is the v2 interface - you don't need an onready() function.");
     },
-//#endif
+//#/if
     debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args),
     onunhandled: function(ev){
       error("Unhandled worker message:",ev.data);
index 1f818286b5f1efed47c083a79efe15401497de6e..7fe9a12a770d643b9aafc89385eede3410649f32 100644 (file)
@@ -27,7 +27,7 @@
     -->
     <script src="jqterm/jquery.terminal.bundle.min.js"></script>
     <link rel="stylesheet" href="jqterm/jquery.terminal.min.css">
-//#endif
+//#/if
     <style>
       /* The following styles are for app-level use. */
       :root {
diff --git a/ext/wasm/libcmpp.c b/ext/wasm/libcmpp.c
new file mode 100644 (file)
index 0000000..717f56e
--- /dev/null
@@ -0,0 +1,16877 @@
+/**
+  This C file contains both the header and source file for c-pp,
+  a.k.a. libcmpp.
+*/
+#if !defined(NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED)
+#define NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED
+#if !defined(_POSIX_C_SOURCE)
+#  define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */
+#endif
+#define CMPP_AMALGAMATION
+#if !defined(NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED)
+/**
+  This is the auto-generated "amalgamation build" of libcmpp. It was amalgamated
+  using:
+
+  ./c-pp -I. -I./src -Dsrcdir=./src -Dsed=/usr/bin/sed -o libcmpp.h ./tool/libcmpp.c-pp.h -o libcmpp.c ./tool/libcmpp.c-pp.c
+
+  with libcmpp 2.0.x c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37 @ 2026-03-08 14:50:35.123 UTC
+*/
+#define CMPP_PACKAGE_NAME "libcmpp"
+#define CMPP_LIB_VERSION "2.0.x"
+#define CMPP_LIB_VERSION_HASH "c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37"
+#define CMPP_LIB_VERSION_TIMESTAMP "2026-03-08 14:50:35.123 UTC"
+#define CMPP_LIB_CONFIG_TIMESTAMP "2026-03-08 15:32 GMT"
+#define CMPP_VERSION CMPP_LIB_VERSION " " CMPP_LIB_VERSION_HASH " @ " CMPP_LIB_VERSION_TIMESTAMP
+#define CMPP_PLATFORM_EXT_DLL ".so"
+#define CMPP_MODULE_PATH ".:/usr/local/lib/cmpp"
+
+#if !defined(NET_WANDERINGHORSE_CMPP_H_INCLUDED_)
+#define NET_WANDERINGHORSE_CMPP_H_INCLUDED_
+/*
+** 2022-11-12:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+**
+** The C-minus Preprocessor: C-like preprocessor.  Why? Because C
+** preprocessors _can_ process non-C code but generally make quite a
+** mess of it. The purpose of this library is a customizable
+** preprocessor suitable for use with arbitrary UTF-8-encoded text.
+**
+** The supported preprocessor directives are documented in the
+** README.md hosted with this file (or see the link below).
+**
+** Any mention of "#" in the docs, e.g. "#if", is symbolic. The
+** directive delimiter is configurable and defaults to "##". Define
+** CMPP_DEFAULT_DELIM to a string when compiling to define the default
+** at build-time.
+**
+** This API is presented as a library but was evolved from a
+** monolithic app. Thus is library interface is likely still missing
+** some pieces needed to make it more readily usable as a library.
+**
+** Author(s):
+**
+** - Stephan Beal <https://wanderinghorse.net/home/stephan/>
+**
+** Canonical homes:
+**
+** - https://fossil.wanderinghorse.net/r/c-pp
+** - https://sqlite.org/src/file/ext/wasm/c-pp-lite.c
+**
+** With the former hosting this app's SCM and the latter being the
+** original deployment of c-pp.c, from which this library
+** evolved. SQLite uses a "lite" version of c-pp, whereas _this_ copy
+** is its much-heavier-weight fork.
+*/
+
+#if defined(CMPP_HAVE_AUTOCONFIG_H)
+#include "libcmpp-autoconfig.h"
+#endif
+#if defined(HAVE_AUTOCONFIG_H)
+#include "autoconfig.h"
+#endif
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#ifdef _WIN32
+# if defined(BUILD_libcmpp_static) || defined(CMPP_AMALGAMATION_BUILD)
+#  define CMPP_EXPORT extern
+# elif defined(BUILD_libcmpp)
+#  define CMPP_EXPORT extern __declspec(dllexport)
+# else
+#  define CMPP_EXPORT extern __declspec(dllimport)
+# endif
+#else
+# define CMPP_EXPORT extern
+#endif
+
+/**
+   cmpp_FILE is a portability hack for WASM builds, where we want to
+   elide the (FILE*)-using pieces to avoid having a dependency on
+   Emscripten's POSIX I/O proxies. In all non-WASM builds it is
+   guaranteed to be an alias for FILE. On WASM builds it is guaranteed
+   to be an alias for void and the cmpp APIs which use it become
+   inoperative in WASM builds.
+
+   That said: the code does not yet support completely compiling out
+   (FILE*) dependencies, and may not be able to because canonical
+   sqlite3 (upon which it is based) depends heavily on file
+   descriptors and slightly on FILE handles.
+*/
+#if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasi__)
+  typedef void cmpp_FILE;
+#  define CMPP_PLATFORM_IS_WASM 1
+#else
+  #include <stdio.h>
+  typedef FILE cmpp_FILE;
+#  define CMPP_PLATFORM_IS_WASM 0
+#endif
+
+#include <stdint.h>
+#include <inttypes.h> /* PRIu32 and friends */
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+   32-bit flag bitmask type. This typedef exists primarily to improve
+   legibility of function signatures and member structs by conveying
+   their intent for use as flags instead of result codes or lengths.
+*/
+typedef uint32_t cmpp_flag32_t;
+//typedef uint16_t cmpp_flag16_t;
+
+/**
+   An X-macro which invokes its argument (a macro name) to expand to
+   all possible values of cmpp_rc_e entries. The macro name passed to
+   it is invoked once for each entry and passed 3 arguments: the enum
+   entry's full name (CMPP_RC_...), its integer value, and a help-text
+   string.
+*/
+#define cmpp_rc_e_map(E) \
+  E(CMPP_RC_OK, 0,                                                      \
+    "The quintessential not-an-error value.")                           \
+  E(CMPP_RC_ERROR, 100,                                                 \
+    "Generic/unknown error.")                                           \
+  E(CMPP_RC_NYI, 101,                                                   \
+    "A placeholder return value for not yet implemented functions.")    \
+  E(CMPP_RC_OOM, 102,                                                   \
+    "Out of memory. Indicates that a resource allocation "              \
+    "request failed.")                                                  \
+  E(CMPP_RC_MISUSE, 103,                                                \
+    "API misuse (invalid args)")                                        \
+  E(CMPP_RC_RANGE, 104,                                                 \
+    "A range was violated.")                                            \
+  E(CMPP_RC_ACCESS, 105,                                                \
+    "Access to or locking of a resource was denied "                    \
+    "by some security mechanism or other.")                             \
+  E(CMPP_RC_IO, 106,                                                    \
+    "Indicates an I/O error. Whether it was reading or "                \
+    "writing is context-dependent.")                                    \
+  E(CMPP_RC_NOT_FOUND, 107,                                             \
+    "Requested resource not found.")                                    \
+  E(CMPP_RC_ALREADY_EXISTS, 108,                                        \
+    "Indicates that a to-be-created resource already exists.")          \
+  E(CMPP_RC_CORRUPT, 109,                                               \
+    "Data consistency problem.")                                        \
+  E(CMPP_RC_SYNTAX, 110,                                                \
+    "Some sort of syntax error.")                                       \
+  E(CMPP_RC_NOOP, 111,                                                  \
+    "Special sentinel value for some APIs.")                            \
+  E(CMPP_RC_UNSUPPORTED, 112,                                           \
+    "An unsupported operation was request.")                            \
+  E(CMPP_RC_DB, 113,                                                    \
+    "Indicates db-level error (e.g. statement prep failed). In such "   \
+    "cases, the error state of the related db handle (cmpp_db) "        \
+    "will be updated to contain more information directly from the "    \
+    "db driver.")                                                       \
+  E(CMPP_RC_NOT_DEFINED, 114,                                           \
+    "Failed to expand an undefined value.")                             \
+  E(CMPP_RC_ASSERT, 116, "An #assert failed.")                          \
+  E(CMPP_RC_TYPE, 118,                                                  \
+    "Indicates that some data type or logical type is incorrect.")      \
+  E(CMPP_RC_CANNOT_HAPPEN, 140,                                         \
+    "This is intended only for internal use, to "                       \
+    "report conditions which \"cannot possibly happen\".")              \
+  E(CMPP_RC_HELP, 141,                                                  \
+    "--help was used in the arguments to cmpp_process_argv()")          \
+  E(CMPP_RC_NO_DIRECTIVE, 142,                                          \
+    "A special case of CMPP_RC_NOT_FOUND needed to disambiguate.")      \
+  E(CMPP_RC_end,200,                                                    \
+    "Must be the final entry in the enum. Used for creating client-side " \
+    "result codes which are guaranteed to live outside of this one's "  \
+    "range.")
+
+/**
+   Most functions in this library which return an int return result
+   codes from the cmpp_rc_e enum.  None of these entries are
+   guaranteed to have a specific value across library versions except
+   for CMPP_RC_OK, which is guaranteed to always be 0 (and the API
+   guarantees that no other code shall have a value of zero).
+
+   The only reasons numbers are hard-coded to the values is to
+   simplify debugging during development. Clients may use
+   cmpp_rc_cstr() to get some human-readable (or programmer-readable)
+   form for any given value in this enum.
+*/
+enum cmpp_rc_e {
+#define E(N,V,H) N = V,
+  cmpp_rc_e_map(E)
+#undef E
+};
+typedef enum cmpp_rc_e cmpp_rc_e;
+
+/**
+   Returns the string form of the given cmpp_rc_e value or NULL if
+   it's not a member of that enum.
+*/
+char const * cmpp_rc_cstr(int rc);
+
+/**
+   CMPP_BITNESS specifies whether the library should use 32- or 64-bit
+   integer types for its size/length measurements. It's difficult to
+   envision use cases for a preprocessor which would require counters
+   or rangers larger than 32 bits provide for, so the default is 32
+   bits. Builds created with different CMPP_BITNESS values are
+   not binary-compatible.
+*/
+#define CMPP_BITNESS 32
+#if 32==CMPP_BITNESS
+/**
+   Unsigned integer type for string/stream lengths. 32 bits is
+   sufficient for all but the weirdest of inputs and outputs.
+*/
+typedef uint32_t cmpp_size_t;
+
+/**
+  A signed integer type indicating the maximum length of strings or
+  byte ranges in a stream. It is most frequently used in API
+  signatures where a negative value means "if it's negative then use
+  strlen() to count it".
+*/
+typedef int32_t cmpp_ssize_t;
+
+/**
+   The printf-format-compatible format letter (or group of letters)
+   appropriate for use with cmpp_size_t. Contrary to popular usage,
+   size_t cannot be portably used with printf(), without careful
+   casting, because it has neither a fixed size nor a standardized
+   printf/scanf format specifier (like the stdint.h types do).
+*/
+#define CMPP_SIZE_T_PFMT PRIu32
+#elif 64==CMPP_BITNESS
+typedef uint64_t cmpp_size_t;
+typedef int64_t cmpp_ssize_t;
+#define CMPP_SIZE_T_PFMT PRIu64
+#else
+#error "Invalid CMPP_BITNESS value. Expecting 32 or 64."
+#endif
+
+/**
+   Generic interface for streaming in data. Implementations must read
+   (at most) *n bytes from their input, copy it to dest, assign *n to
+   the number of bytes actually read, return 0 on success, and return
+   non-0 cmpp_rc_e value on error (e.g. CMPP_RC_IO).
+
+   When called, *n is the max length to read. On return, *n must be
+   set to the amount actually read. Implementations may need to
+   internally distinguish a short read due to EOF from a short read
+   due to an I/O error, e.g. using feof() and/or ferror(). A short
+   read for EOF is not an error but a short read for input failure is.
+   This library invariably treats a short read as EOF.
+
+   The state parameter is the implementation-specified input
+   file/buffer/whatever channel.
+*/
+typedef int (*cmpp_input_f)(void * state, void * dest, cmpp_size_t * n);
+
+/**
+   Generic interface for streaming out data. Implementations must
+   write n bytes from src to their destination channel and return 0 on
+   success, or a value from the cmpp_rc_e enum on error
+   (e.g. CMPP_RC_IO). The state parameter is the
+   implementation-specified output channel.
+
+   It is implementation-defined whether an n of 0 is legal. This
+   library refrains from passing 0 to these functions.
+
+   In the context of cmpp, the library makes no guarantees that output
+   will always end at a character boundary. It may send any given
+   multibyte character as the end resp. start of two calls to this
+   function.  If that is of a concern for implementors of these
+   functions (e.g. because they're appending the output to a UI
+   widget), they may need to buffer all of the output before applying
+   it (see cmpp_b), or otherwise account for partial characters.
+
+   That said: the core library, by an accident of design, will always
+   emit data at character boundaries, assuming that its input is
+   well-formed UTF-8 text (which cmpp does not validate to be the
+   case).  Custom cmpp_dx_f() implementations are not strictly
+   required to do so but, because of how cmpp is used, almost
+   certainly will. But relying on that is ill-advised.
+*/
+typedef int (*cmpp_output_f)(void * state, void const * src,
+                             cmpp_size_t n);
+
+/**
+   Generic interface for flushing arbitrary output streams.  Must
+   return 0 on success, a non-0 cmpp_rc_e value on error. When in
+   doubt, return CMPP_RC_IO on error. The interpretation of the state
+   parameter is implementation-specific.
+*/
+typedef int (*cmpp_flush_f)(void * state);
+
+typedef struct cmpp_pimpl cmpp_pimpl;
+typedef struct cmpp_api_thunk cmpp_api_thunk;
+typedef struct cmpp_outputer cmpp_outputer;
+
+/**
+   The library's primary class. Each one of these represents a
+   separate preprocessor instance.
+
+   See also: cmpp_dx (the class which client-side extensions interact
+   with the most).
+*/
+struct cmpp {
+
+  /**
+     API thunk object to support use via loadable modules. Client code
+     does not normally need to access this member, but it's exposed
+     here to give loadable modules more flexibility in how they use
+     the thunk.
+
+     This pointer is _always_ the same singleton object. The library
+     never exposes a cmpp object with a NULL api member.
+  */
+  cmpp_api_thunk const * const api;
+
+  /**
+     Private internal state.
+  */
+  cmpp_pimpl * const pimpl;
+};
+typedef struct cmpp cmpp;
+
+/**
+   Flags for use with cmpp_ctor_cfg::flags.
+*/
+enum cmpp_ctor_e {
+  /* Sentinel value. */
+  cmpp_ctor_F_none       = 0,
+  /* Disables #include. */
+  cmpp_ctor_F_NO_INCLUDE = 0x01,
+  /* Disables #pipe. */
+  cmpp_ctor_F_NO_PIPE    = 0x02,
+  /* Disables #attach, #detach, and #query. */
+  cmpp_ctor_F_NO_DB      = 0x04,
+  /* Disables #module. */
+  cmpp_ctor_F_NO_MODULE  = 0x08,
+  /**
+    Disable all built-in directives which may work with the filesystem
+    or invoke external processes. Client-defined directives with the
+    cmpp_d_F_NOT_IN_SAFEMODE flag are also disabled. Directives
+    disabled via the cmpp_ctor_F_NO_... flags (or equivalent library
+    built-time options) do not get registered, so will trigger
+    "unknown directive" errors rather than safe-mode violation errors.
+  */
+  cmpp_ctor_F_SAFEMODE   = 0x10,
+};
+
+/**
+   A configuration object for cmpp_ctor(). This type may be extended
+   as new construction-time customization opportunities are
+   discovered.
+*/
+struct cmpp_ctor_cfg {
+  /**
+     Bitmask from the cmpp_ctor_e enum.
+  */
+  cmpp_flag32_t flags;
+  /**
+     If not NULL then this must name either an existing SQLite3 db
+     file or the name of one which can be created on demand. If NULL
+     then an in-memory or temporary database is used (which one is
+     unspecified). The library copies these bytes, so they need not be
+     valid after a call to cmpp_ctor().
+  */
+  char const * dbFile;
+};
+typedef struct cmpp_ctor_cfg cmpp_ctor_cfg;
+
+/**
+   Assigns *pp to a new cmpp or NULL on OOM. Any non-NULL return
+   value must eventually be passed to cmpp_dtor() to free it.
+
+   The cfg argument, if not NULL, holds config info for the new
+   instance. If NULL, an instance with unspecified defaults is
+   used. These configuration pieces may not be modified after the
+   instance is created.
+
+   It returns 0 if *pp is ready to use and non-0 if either allocation
+   fails (in which case *pp will be set to 0) or initialization of *pp
+   failed (in which case cmpp_err_get() can be used to determine why
+   it failed). In either case, the caller must eventually pass *pp to
+   cmpp_dtor() to free it.
+
+   If the library is built with the symbol CMPP_CTOR_INSTANCE_INIT
+   defined, it must refer to a function with this signature:
+
+   int CMPP_CTOR_INSTANCE_INIT(cmpp *);
+
+   The library calls this before returning and arranges to call it
+   lazily if pp gets reset.  The intent is that the init function
+   installs custom directives using cmpp_d_register(). That
+   initialization, on error, is expected to set its argument's error
+   state with cmpp_err_set().
+*/
+CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_cfg const * cfg);
+
+/**
+   If pp is not NULL, it is passed to cmpp_reset() and then freed.
+*/
+CMPP_EXPORT void cmpp_dtor(cmpp *pp);
+
+
+/**
+   realloc(3)-compatible allocator used by the library.
+
+   This API very specifically uses sqlite3_realloc() as its basis.
+*/
+CMPP_EXPORT void * cmpp_mrealloc(void * p, size_t n);
+
+/**
+   malloc(3)-compatible allocator used by the library.
+
+   This API very specifically uses sqlite3_malloc() as its basis.
+*/
+CMPP_EXPORT void * cmpp_malloc(size_t n);
+
+/**
+   free(3)-compatible deallocator. It can also be used as a destructor
+   for cmpp_d_register() _if_ the memory in question is allocated by
+   cmpp_malloc(), cmpp_realloc(), or the sqlite3_malloc() family of
+   APIs.
+
+   This is not called cmpp_free() to try to avoid any confusion with
+   cmpp_dtor().
+*/
+CMPP_EXPORT void cmpp_mfree(void *);
+
+/**
+   If m is NULL then pp's persistent error code is set to CMPP_RC_OOM,
+   else this is a no-op. Returns pp's error code.
+
+   To simplify certain uses, pp may be NULL, in which case this
+   function returns CMPP_RC_OOM if m is NULL and 0 if it's not.
+*/
+CMPP_EXPORT int cmpp_check_oom(cmpp * const pp, void const * const m );
+
+/**
+   Re-initializes all state of pp. This saves some memory for reuse
+   but resets it all to default states. This closes the database and
+   will also reset any autoloader, policies, or delimiter
+   configurations to their compile-time defaults. It retains only a
+   small amount of state, like any configuration which was passed to
+   cmpp_ctor().
+
+   After calling this, pp is in a cleanly-initialized state and may be
+   re-used with the cmpp API. Its database will not be initialized
+   until an API which needs it is called, so pp can be used with
+   functions which may otherwise be prohibited after the db is
+   opened. (Do we still have any?)
+
+   As of this writing, this is the only way to reliably recover a cmpp
+   instance from any significant errors. Errors may do things like
+   leave savepoints out of balance, and this cleanup step resets all
+   of that state. However, it also loses state like the autoloader.
+
+   TODO?: we need(?) a partial-clear operation which keeps some of the
+   instance's state, most notably custom directives, the db handle,
+   and any cached prepared statements. See cmpp_err_set() for the
+   distinction between recoverable and non-recoverable errors.
+*/
+CMPP_EXPORT void cmpp_reset(cmpp *pp);
+
+#if 0
+Not yet;
+/**
+   If called before pp has initialized its database, this sets the
+   file name used for that database. If called afterwards, pp's error
+   state is updated and CMPP_RC_MISUSE is returned.  If called while
+   pp has error state set, that code is returned without side-effects.
+
+   This does not open the database. It is opened on demand when
+   processing starts.
+
+   On success it returns 0 and this function makes a copy of zName.
+
+   As a special case, zName may be NULL to use the default name, but
+   there is little reason to do so unless one changes their mind after
+   setting it to non-NULL.
+ */
+CMPP_EXPORT int cmpp_db_name_set(cmpp *pp, const char * zName);
+#endif
+
+/**
+   Returns true if the bytes in the range [zName, zName+n) comprise a
+   legal name for a directive or a define.
+
+   It disallows any control characters, spaces, and most punctuation,
+   but allows alphanumeric (but must not start with a number) as well
+   as any of: -./:_ (but it may not start with '-').  Any characters
+   with a high bit set are assumed to be UTF-8 and are permitted as
+   well.
+
+   The name's length  is limited, rather arbitrarily, to 64 bytes.
+
+   If the key is not legal then false is returned and if zErrPos is
+   not NULL then *zErrPos is set to the position in zName of the first
+   offending character. If validation fails because n is too long then
+   *zErrPos (if zErrPos is not NULL) will be set to 0.
+
+   Design note: this takes unsigned characters because it most
+   commonly takes input from cmpp_args::z strings.
+*/
+CMPP_EXPORT bool cmpp_is_legal_key(unsigned char const *zName,
+                                   cmpp_size_t n,
+                                   unsigned char const **zErrPos);
+
+/**
+   Adds the given `#define` macro name to the list of macros, overwriting
+   any previous value.
+
+   zKey must be NUL-terminated and legal as a key. The rules are the
+   same as for cmpp_is_legal_key() except that a '=' is also permitted
+   if it's not at the start of the string because...
+
+   If zVal is NULL then zKey may contain an '=', from which the value
+   will be extracted. If zVal is not NULL then zKey may _not_ contain
+   an '='.
+
+   The ability for zKey to contain a key=val was initially to
+   facilitate input from the CLI (e.g. -Dfoo=bar) because cmpp was
+   initially a CLI app (as opposed to a library). It's considered a
+   "legacy" feature, not recommended for most purposes, but it _is_
+   convenient for that particular purpose.
+
+   Returns 0 on success and updates pp's error state on error.
+
+   See: cmpp_define_v2()
+   See: cmpp_undef()
+*/
+CMPP_EXPORT int cmpp_define_legacy(cmpp *pp, const char * zKey,
+                                   char const *zVal);
+
+/**
+   Works like cmpp_define_legacy() except that it does not examine zKey to
+   see if it contains an '='.
+*/
+CMPP_EXPORT int cmpp_define_v2(cmpp *pp, const char * zKey, char const *zVal);
+
+/**
+   Removes the given `#define` macro name from the list of
+   macros. zKey is, in this case, treated as a GLOB pattern, and all
+   matching defines are deleted.
+
+   If nRemoved is not NULL then, on success, it is set to the number
+   of entries removed by this call.
+
+   Returns 0 on success and updates pp's error state on error. It is not
+   an error if no value was undefined.
+
+   This does _not_ affect defines made using cmpp_define_shadow().
+*/
+CMPP_EXPORT int cmpp_undef(cmpp *pp, const char * zKey,
+                           unsigned int *nRemoved);
+
+/**
+   This works similarly to cmpp_define_v2() except that:
+
+   - It does not permit its zKey argument to contain the value
+     part like that function does.
+
+   - The new define "shadows", rather than overwrites, an existing
+     define with the same name.
+
+   All APIs which look up define keys will get the value of the shadow
+   define.  The shadow can be uninstalled with cmpp_define_unshadow(),
+   effectively restoring its previous value (if any).  That function
+   should be called one time for each call to this one, passing the
+   same key to each call.  A given key may be shadowed any number of
+   times by this routine. Each one saves the internal ID of the shadow
+   into *pId (and pId must not be NULL). That value must be passed to
+   cmpp_define_unshadow() to ensure that the "shadow stack" stays
+   balanced in the face of certain error-handling paths.
+
+   cmpp_undef() will _not_ undefine an entry added through this
+   interface.
+
+   Returns pp's persistent error code (0 on success).
+
+   Design note: this function was added to support adding a define
+   named __FILE__ to input scripts which works like it does in a C
+   preprocessor. Alas, supporting __LINE__ would be much more costly,
+   as it would have to be updated in the db from several places, so
+   its cost would outweigh its meager benefits.
+*/
+CMPP_EXPORT int cmpp_define_shadow(cmpp *pp, char const *zKey,
+                                   char const *zVal,
+                                   int64_t * pId);
+
+/**
+   Removes the most shadow define matching the zKey and id values
+   which where previously passed to cmpp_define_shadow().  It is not
+   an error if no match is found, in which case this function has no
+   visible side-effects.
+
+   Unlike cmpp_undef(), zKey is matched precisely, not against a glob.
+
+   In order to keep the "shadow stack" properly balanced, this will
+   delete any shadow entries for the given key which have the same id
+   or a newer one (i.e. they were left over from a missed call to
+   cmpp_define_unshadow()).
+
+   Returns pp's persistent error code (0 on success).
+*/
+CMPP_EXPORT int cmpp_define_unshadow(cmpp *pp, char const *zKey,
+                                     int64_t id);
+
+/**
+   Adds the given dir to the list of includes. They are checked in the
+   order they are added.
+*/
+CMPP_EXPORT int cmpp_include_dir_add(cmpp *pp, const char * zKey);
+
+/**
+   Sets pp's default output channel. If pp already has a channel, it
+   is closed[^1].
+
+   The second argument, if not NULL, is _bitwise copied_, which has
+   implications for the ownership of out->state (see below). If it is
+   is NULL, cmpp_outputer_empty is copied in its place, which makes
+   further output a no-op.
+
+   The third argument is a symbolic name for the channel (perhaps its
+   file name). It is used in debugging and error messages.  cmpp does
+   _not_ copy it, so its bytes must outlive the cmpp instance. (In
+   practice, the byte names come from main()'s argv or scope-local
+   strings in the same scope as the cmpp instance.) This argument
+   should only be NULL if the second argument is.
+
+   cmpp_reset(), or opening another channel, will end up calling
+   out->cleanup() (if it's not NULL) and passing it a pointer to a
+   _different_ cmpp_outputer object, but with the _same_
+   cmpp_outputer::state pointer, which may invalidate out->state.
+
+   To keep cmpp from doing that, make a copy of the output object, set
+   the cleanup member of that copy to NULL, then pass that copy to this
+   function. It is then up to the client to call out->cleanup(out) when
+   the time is right.
+
+   For example:
+
+   ```
+   cmpp_outputer my = cmpp_outputer_FILE;
+   my.state = cmpp_fopen("/some/file", "wb");
+   cmpp_outputer tmp = my;
+   tmp.cleanup = NULL;
+   cmpp_outputer_set( pp, &tmp, "my file");
+   ...
+   my.cleanup(&my); // will cmpp_fclose(my.state)
+   ```
+
+   Potential TODO: internally store the output channel as a pointer.
+   It's not clear whether that would resolve the above grief or
+   compound it.
+
+   [^1]: depending on the output channel, it might not _actually_ be
+   closed, but pp is disassociated from it, in any case.
+*/
+CMPP_EXPORT
+void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName);
+
+/**
+   Treats the range (zIn,zIn+nIn] as a complete cmpp input and process
+   it appropriately. zName is the name of the input for purposes of
+   error messages. If nIn is negative, strlen() is used to calculate
+   it.
+
+   This is a no-op if pp has any error state set. It returns pp's
+   persistent error code.
+*/
+CMPP_EXPORT int cmpp_process_string(cmpp *pp, const char * zName,
+                                    unsigned char const * zIn,
+                                    cmpp_ssize_t nIn);
+
+/**
+   A thin proxy for cmpp_process_string() which reads its input from
+   the given file. Returns 0 on success, else returns pp's persistent
+   error code.
+*/
+CMPP_EXPORT int cmpp_process_file(cmpp *pp, const char * zName);
+
+/**
+   A thin proxy for cmpp_process_string() which reads its input from
+   the given input source, consuming it all before passing it
+   on. Returns 0 on success, else returns pp's persistent error code.
+*/
+CMPP_EXPORT int cmpp_process_stream(cmpp *pp, const char * zName,
+                                    cmpp_input_f src, void * srcState);
+
+/**
+   Process the given main()-style arguments list. When calling from
+   main(), be sure to pass it main()'s (argc+1, argv+1) to skip argv[0]
+   (the binary's name).
+
+   Each argument is expected to be one of the following:
+
+   1) One of --help or -?: causes this function to return CMPP_RC_HELP
+   without emitting any output.
+
+   2) -DX or -DX=Y: sets define X to 1 (if no "=" is used and no Y given) or to
+   Y.
+
+   3) -UX: unsets all defines matching glob X.
+
+   4) -FX=Y: works like -DX=Y but treats Y as a filename and sets X to
+   the contents of that file.
+
+   5) -IX: adds X to the "include path". If _no_ include path is
+   provided then cmpp assumes a path of ".", but if _any_ paths are
+   provided then it does not assume that "." is in the path.
+
+   6) --chomp-F: specifies whether subsequent -F flags should "chomp"
+   one trailing newline from their input files.
+
+   7) --delimiter|-d=X sets the directive delimiter to X. Its default
+   is a compile-time constant.
+
+   8) --output|-o=filename: sets the output channel to the given file.
+   A value of "-" means stdout. If no output channel is opened when
+   this is called, and files are to be processed, stdout is
+   assumed. (That's a historical artifact from earlier evolutions.)
+   To override that behavior use cmpp_outputer_set().
+
+   9) --file|-f=filename: sets the input channel to the given file.
+   A value of "-" means stdin.
+
+   10) -e=SCRIPT Treat SCRIPT as a complete c-pp input and process it.
+   Because it's difficult to pack multiple lines of text into this,
+   it's really of use for testing #expr and #assert.
+
+   11) --@policy=X sets the @token@ parsing policy. X must be
+   one of (retain, elide, error, off) and defaults to off.
+
+   12) -@: shorthand for --@policy=error.
+
+   13) --sql-trace: enables tracing of all SQL statements to stderr.
+   This is useful for seeing how a script interacts with the
+   database. Use --no-sql-trace to disable it.
+
+   14) --sql-trace-x: like --sql-trace but replaces bound parameter
+   placeholders with their SQL values. Use --no-sql-trace to disable
+   it.
+
+   15) --dump-defines: emit all defines to stdout. They should
+   arguably go to stderr but that interferes with automated testing.
+
+   Any argument which does not match one of the above flags, and does
+   not start with a "-", is treated as if it were passed to the --file
+   flag.
+
+   Flags may start with either 1 or 2 dashes - they are equivalent.
+
+   Flags which take a value may either be in the form X=Y or X Y, i.e.
+   may be a single argv entry or a pair of them.
+
+   It performs two passes on the arguments: the first is for validation
+   checking for --help/-?. No processing of the input(s) and output(s)
+   happens unless the first pass completes. Similarly, no validation of
+   whether any provided filename are actually readable is performed
+   until the second pass.
+
+   Arguments are processed in the order they are given. Thus the following
+   have completely different meanings:
+
+   1) -f foo.in -Dfoo
+   2) -Dfoo -f foo.in
+
+   The former will process foo.in before defining foo.
+
+   This behavior makes it possible to process multiple input files in
+   a single go:
+
+   --output foo.out foo.in -Dfoo foo.in -Ufoo --output bar.out -Dbar foo.in
+
+   It returns 0 on success. cmpp_err_get() can be used to fetch any
+   error message.
+*/
+CMPP_EXPORT int cmpp_process_argv(cmpp *pp, int argc, char const * const * argv);
+
+/**
+   Intended to be called if cmpp_process_argv() returns CMPP_RC_HELP.
+   It emits --help-style text to the given output stream.
+   As the first argument pass it either argv[0] or NULL. The second
+   should normally be stdout or stderr.
+
+   Reminder to self: this could take a (cmpp_output_f,void*) pair
+   instead, and should do so for the sake of WASI builds, but its impl
+   currently relies heavily on fprintf() formatting.
+*/
+CMPP_EXPORT void cmpp_process_argv_usage(char const *zAppName,
+                                         cmpp_FILE *os);
+
+/**
+   Returns pp's current error number (from the cmpp_rc_e enum) and
+   sets *zMsg (if zMsg is not NULL) to the error string. The bytes are
+   owned by pp and may be invalidated by any functions which take pp
+   as an argument.
+
+   See cmpp_err_get() for more information.
+
+*/
+CMPP_EXPORT int cmpp_err_get(cmpp *pp, char const **zMsg);
+
+/**
+   Sets or clears (if 0==rc) pp's persistent error state. zFmt may be
+   NULL or a format string compatible with sqlite3_mprintf().
+
+   To simplify certain uses, this is a no-op if pp is NULL, returning
+   rc without other side effects.
+
+   Returns rc with one exception: if allocation of a copy of the error
+   string fails then CMPP_RC_OOM will be returned (and pp will be
+   updated appropriately).
+
+   If pp is currently processing a script, the resulting error string
+   will be prefixed with the name of the current input script and the
+   line number of the directive which triggered the error.
+
+   It is legal for zFmt to be NULL or an empty string, in which case a
+   default, vague error message is used (without requiring allocation
+   of a new string).
+
+   Recoverable vs. unrecoverable errors:
+
+   Most cmpp APIs become no-ops if their cmpp object has error state
+   set, treating any error as unrecoverable. That approach simplifies
+   writing code for it by allowing multiple calls to be chained
+   without concern for whether the previous one succeeded.
+
+   ACHTUNG: simply clearing the error state by passing 0 as the 2nd
+   argument to this function is _not_ enough to recover from certain
+   errors. e.g. an error in the middle of a script may leave db
+   savepoints imbalanced. The only way to _fully_ recover from any
+   significant failures is to use cmpp_reset(), which resets all of
+   pp's state.
+
+   APIs which may set the error state but are recoverable by simply
+   clearing that state will document that. Errors from APIs which do
+   not claim to be recoverable in error cases must be treated as
+   unrecoverable.
+
+   See cmpp_err_get() for more information.
+
+   FIXME: we need a different variant for WASM builds, where variadics
+   aren't a usable thing.
+
+   Potential TODO: change the error-reporting interface to support
+   distinguishing from recoverable and non-recoverable errors.  "The
+   problem" is that no current uses need that - they simply quit and
+   free up the cmpp instance on error. Maybe that's the way it
+   _should_ be.
+*/
+CMPP_EXPORT int cmpp_err_set(cmpp *pp, int rc, char const *zFmt, ...);
+
+/**
+   A variant of cmpp_err_set() which is not variadic, as a consolation
+   for WASM builds. zMsg may be NULL. The given string, if not NULL,
+   is copied.
+*/
+CMPP_EXPORT int cmpp_err_set1(cmpp *pp, int rc, char const *zMsg);
+
+#if 0
+/**
+   Clears any error state in pp. Most cmpp APIs become no-ops if their
+   cmpp instance has its error flag set.
+
+   See cmpp_err_get() for important details about doing this.
+*/
+//CMPP_EXPORT void cmpp_err_clear(cmpp *pp);
+
+/**
+   This works like a combination of cmpp_err_get() and
+   cmpp_err_clear(), in that it clears pp's error state by transferring
+   ownership of it to the caller. If pp has any error state, *zMsg is
+   set to the error string and the error code is returned, else 0 is
+   returned and *zMsg is set to 0.
+
+   The string returned via *zMsg must eventually be passed to
+   cmpp_mfree() to free it.
+
+   This function is provided simply as an optimization to avoid
+   having to copy the error string in some cases.
+
+   ACHTUNG: see the ACHTUNG in cmpp_err_clear().
+*/
+CMPP_EXPORT int cmpp_err_take(cmpp *pp, char **zMsg);
+#endif
+
+/**
+   Returns pp's current error code, which will be 0 if it currently
+   has no error state.
+
+   To simplify certain uses, this is a no-op if pp is NULL, returning
+   0.
+*/
+CMPP_EXPORT int cmpp_err_has(cmpp const * pp);
+
+/**
+   Returns true if pp was initialized in "safe mode". That is: if the
+   cmpp_ctor_F_SAFEMODE flag was passed to cmpp_ctor().
+
+   To simplify certain uses, this is a no-op if pp is NULL, returning
+   false.
+*/
+CMPP_EXPORT bool cmpp_is_safemode(cmpp const * pp);
+
+/**
+   Starts a new SAVEPOINT in the database. Returns non-0, and updates
+   pp's persistent error state, on failure.
+
+   If this returns 0, the caller is obligated to later call either
+   cmpp_sp_commit() or cmpp_sp_rollback() later.
+*/
+CMPP_EXPORT int cmpp_sp_begin(cmpp *pp);
+
+/**
+   Commits the most recently-opened savepoint UNLESS pp's error state
+   is set, in which case this behaves like cmpp_sp_rollback().
+   Returns 0 on success.
+
+   A call to cmpp_sp_begin() which returns 0 obligates the caller to
+   call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
+   for either to be called in any other context.
+*/
+CMPP_EXPORT int cmpp_sp_commit(cmpp *pp);
+
+/**
+   Rolls back the most recently-opened savepoint.  Returns 0 on
+   success.
+
+   A call to cmpp_sp_begin() which returns 0 obligates the caller to
+   call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
+   for either to be called in any other context.
+*/
+CMPP_EXPORT int cmpp_sp_rollback(cmpp *pp);
+
+/**
+   A cmpp_output_f() impl which requires state to be a (FILE*), which
+   this function passes the call on to fwrite(). Returns 0 on
+   success, CMPP_RC_IO on error.
+
+   If state is NULL then stdout is used.
+*/
+CMPP_EXPORT int cmpp_output_f_FILE(void * state, void const * src, cmpp_size_t n);
+
+/**
+   A cmpp_output_f() impl which requires state to be a ([const] int*)
+   referring to a writable file descriptor, which this function
+   dereferences and passes to write(2).
+*/
+CMPP_EXPORT int cmpp_output_f_fd(void * state, void const * src, cmpp_size_t n);
+
+/**
+   A cmpp_input_f() implementation which requires that state be
+   a readable (FILE*) handle, which it passes to fread(3).
+*/
+CMPP_EXPORT int cmpp_input_f_FILE(void * state, void * dest, cmpp_size_t * n);
+
+/**
+   A cmpp_input_f() implementation which requires that state be a
+   readable file descriptor, in the form of an ([const] int*), which
+   this function passes to write(2).
+*/
+CMPP_EXPORT int cmpp_input_f_fd(void * state, void * dest, cmpp_size_t * n);
+
+/**
+   A cmpp_flush_f() impl which expects pFile to be-a (FILE*) opened
+   for writing, which this function passes the call on to
+   fflush(). If fflush() returns 0, so does this function, else it
+   returns non-0.
+*/
+CMPP_EXPORT int cmpp_flush_f_FILE(void * pFile);
+
+/**
+   A generic streaming routine which copies data from an
+   cmpp_input_f() to an cmpp_outpuf_f().
+
+   Reads all data from inF(inState,...) in chunks of an unspecified
+   size and passes them on to outF(outState,...). It reads until inF()
+   returns fewer bytes than requested or returns non-0. Returns the
+   result of the last call to outF() or (if reading fails) inF().
+   Results are undefined if either of inState or outState arguments
+   are NULL and their callbacks require non-NULL.  (This function
+   cannot know whether a NULL state argument is legal for the given
+   callbacks.)
+
+   Here is an example which basically does the same thing as the
+   cat(1) command on Unix systems:
+
+   ```
+   cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_FILE, stdout);
+   ```
+
+   Or copy a FILE to a string buffer:
+
+   ```
+   cmpp_b os = cmpp_b_empty;
+   FILE * f = cmpp_fopen(...);
+   rc = cmpp_stream(cmpp_input_f_FILE, f, cmpp_output_f_b, &os);
+   // On error os might be partially populated.
+   // Eventually clean up the buffer:
+   cmpp_b_clear(&os);
+   ```
+*/
+CMPP_EXPORT int cmpp_stream(cmpp_input_f inF, void * inState,
+                            cmpp_output_f outF, void * outState);
+
+/**
+   Reads the entire contents of the given input stream, allocating it
+   in a buffer. On success, returns 0, assigns *pOut to the buffer,
+   and *nOut to the number of bytes read (which will be fewer than are
+   allocated). It guarantees that on success it NUL-terminates the
+   buffer at one byte after the returned size, with one exception: if
+   the string has no input, both *pOut and *nOut will be set to 0.
+
+   On error it returns whatever code xIn() returns.
+*/
+CMPP_EXPORT int cmpp_slurp(cmpp_input_f xIn, void *stateIn,
+                           unsigned char **pOut, cmpp_size_t * nOut);
+
+/**
+   _Almost_ equivalent to fopen(3) but:
+
+   - If name=="-", it returns one of stdin or stdout, depending on the
+   mode string: stdout is returned if 'w' or '+' appear, otherwise
+   stdin.
+
+   If it returns NULL, the global errno "should" contain a description
+   of the problem unless the problem was argument validation.
+
+   If at all possible, use cmpp_fclose() (as opposed to fclose()) to
+   close these handles, as it has logic to skip closing the three
+   standard streams.
+*/
+CMPP_EXPORT cmpp_FILE * cmpp_fopen(char const * name, char const *mode);
+
+/**
+   Passes f to fclose(3) unless f is NULL or one of the C-standard
+   handles (stdin, stdout, stderr), in which cases it does nothing at
+   all.
+*/
+CMPP_EXPORT void cmpp_fclose(cmpp_FILE * f);
+
+/**
+   A cleanup callback interface for use with cmpp_outputer::cleanup().
+   Implementations must handle self->state appropriately for its type,
+   and clear self->state if appropriate, but must not free the self
+   object. It is implementation-specified whether self->state and/or
+   self->name are set to NULL by this function. Whether they should be
+   often depends on how they're used.
+*/
+typedef void (*cmpp_outputer_cleanup_f)(cmpp_outputer *self);
+
+/**
+   An interface which encapsulates data for managing a streaming
+   output destination, primarily intended for use with cmpp_stream()
+   but also used internally by cmpp for directing output to a buffer.
+*/
+struct cmpp_outputer {
+  /**
+     An optional descriptive name for the channel. The bytes
+     are owned elsewhere and are typically static or similarly
+     long-lived.
+  */
+  char const * name;
+
+  /**
+     Output channel.
+  */
+  cmpp_output_f out;
+
+  /**
+     flush() implementation. This may be NULL for most uses of this
+     class. Cases which specifically require it must document that
+     requirement so.
+  */
+  cmpp_flush_f flush;
+
+  /**
+     Optional: if not NULL, it must behave appropriately for its state
+     type, cleaning up any memory it owns.
+  */
+  cmpp_outputer_cleanup_f cleanup;
+
+  /**
+     State to be used when calling this->out() and this->flush(),
+     namely: this->out(this->state, ... ) and
+     this->flush(this->state).
+
+     Whether or not any given instance of this class owns the memory
+     pointed to by this member must be documented for their cleanup()
+     method.
+
+     Because cmpp_outputer instances frequently need to be stashed and
+     unstashed via bitwise copying, it is illegal to replace this
+     pointer after its initial assignment. The object it points to may
+     be mutated freely, but this pointer must stay stable for the life
+     of this object.
+  */
+  void * state;
+};
+
+/**
+   Empty-initialized cmpp_outputer instance, intended for
+   const-copy initialization.
+*/
+#define cmpp_outputer_empty_m                                 \
+  {.name=NULL, .out = NULL,.flush = NULL, .cleanup = NULL, .state =NULL}
+
+/**
+   Empty-initialized cmpp_outputer instance, intended for
+   non-const-copy initialization. These copies can, for purposes of
+   cmpp's output API, be used as-is to have cmpp process its inputs
+   but generate no output.
+*/
+CMPP_EXPORT const cmpp_outputer cmpp_outputer_empty;
+
+/**
+   If o->out is not NULL, the result of o->out(o->state,p,n) is
+   returned, else 0 is returned.
+*/
+CMPP_EXPORT int cmpp_outputer_out(cmpp_outputer *o, void const *p, cmpp_size_t n);
+
+/**
+   If o->flush is not NULL, the result of o->flush(o->state) is
+   returned, else 0 is returned.
+*/
+CMPP_EXPORT int cmpp_outputer_flush(cmpp_outputer *o);
+
+/**
+   If o->cleanup is not NULL, it is called, otherwise this is a no-op.
+*/
+CMPP_EXPORT void cmpp_outputer_cleanup(cmpp_outputer *o);
+
+/**
+   A cmpp_outputer initializer which uses cmpp_flush_f_FILE(),
+   cmpp_output_f_FILE(), and cmpp_outputer_cleanup_f_FILE() for its
+   implementation. After copying this, the state member must be
+   pointed to an opened-for-writing (FILE*).
+*/
+CMPP_EXPORT const cmpp_outputer cmpp_outputer_FILE;
+
+/**
+   The cmpp_outputer_cleanup_f() impl used by cmpp_outputer_FILE.  If
+   self->state is not NULL then it is passed to fclose() (_unless_ it
+   is stdin, stdout, or stderr) and set to NULL. self->name is
+   also set to NULL.
+*/
+CMPP_EXPORT void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self);
+
+/**
+   Sets pp's current directive delimiter to a copy of the
+   NUL-terminated zDelim. The delimiter is the sequence which starts
+   line and distinguishes cmpp directives from other input, in the
+   same way that C preprocessors use '#' as a delimiter.
+
+   If zDelim is NULL then the default delimiter is used. The default
+   delimiter can be set when compiling the library by defining
+   CMPP_DEFAULT_DELIM to a quoted string value.
+
+   zDelim is assumed to be in UTF-8 encoding. If any bytes in the
+   range (0,32) are found, CMPP_RC_MISUSE is returned and pp's
+   persistent error state is set.
+
+   The delimiter must be short and syntactically unambiguous for the
+   intended inputs. It has a rather arbitrary maximum length of 12,
+   but it's difficult to envision it being remotely human-friendly
+   with a delimiter longer than 3 bytes. It's conceivable, but
+   seemingly far-fetched, that longer delimiters might be interesting
+   in some machine-generated cases, e.g. using a random sequence as
+   the delimiter.
+
+   Returns 0 on success. Returns non-0 if called when the delimiter
+   stack is empty, if it cannot copy the string or zDelim is deemed
+   unsuitable for use as a delimiter. Calling this when the stack is
+   empty represents a serious API misuse (indicating that
+   cmpp_delimiter_pop() was used out of scope) and will trigger an
+   assert() in debug builds.  Except for that last case, errors from
+   this function are recoverable (see cmpp_err_set()).
+*/
+CMPP_EXPORT int cmpp_delimiter_set(cmpp *pp, char const *zDelim);
+
+/**
+   Fetches pp's current delimiter string, assigning it to *zDelim.
+   The string is owned by pp and will be invalidated by any call to
+   cmpp_delimiter_set() or the #delimiter script directive.
+
+   If, by some odd usage constellation, this is called after an
+   allocation of the delimiter stack has failed, this will set *zDelim
+   to the compile-time-default delimiter. That "cannot happen" in normal use because such a failure
+   would have been reacted to and this would not be called.
+*/
+CMPP_EXPORT void cmpp_delimiter_get(cmpp const *pp, char const **zDelim);
+
+/**
+   Pushes zDelim as the current directive delimiter. Returns 0 on
+   success and non-zero on error (invalid zDelim value or allocation
+   error).  If this returns 0 then the caller is obligated to
+   eventually call cmpp_delimiter_pop() one time. If it returns non-0
+   then they must _not_ call that function.
+ */
+CMPP_EXPORT int cmpp_delimiter_push(cmpp *pp, char const *zDelim);
+
+/**
+   Must be called one time for each successful call to
+   cmpp_delimiter_push(). It restores the directive delimimter to the
+   value it has when cmpp_delimiter_push() was last called.
+
+   Returns pp's current error code, and will set it to non-0 if called
+   when no cmpp_delimiter_push() is active. Popping an empty stack
+   represents a serious API misuse and may fail an assert() in debug
+   builds.
+*/
+CMPP_EXPORT int cmpp_delimiter_pop(cmpp *pp);
+
+/**
+   If z[*n] ends on a \n or \r\n pair, it/they are stripped,
+   *z is NUL-terminated there, and *n is adjusted downwards
+   by 1 or 2. Returns true if it chomped, else false.
+*/
+CMPP_EXPORT bool cmpp_chomp(unsigned char * z, cmpp_size_t * n);
+
+/**
+   A basic memory buffer class. This is primarily used with
+   cmpp_outputer_b to capture arbitrary output for later use.
+   It's also used for incrementally creating dynamic strings.
+
+   TODO: add the heuristic that an nAlloc of 0 with a non-NULL z
+   refers to externally-owned memory. This would change the
+   buffer-write APIs to automatically copy it before making any
+   changes. We have code for this in the trees this class derives
+   from, it just needs to be ported over. It would allow us to avoid
+   allocating in some cases where we need a buffer but it will always
+   (or commonly) be a copy of a static string, like a single space.
+*/
+struct cmpp_b {
+  /**
+     This buffer's memory, owned by this object. This library exclusively
+     uses sqlite3_realloc() and friends for memory management.
+
+     If this pointer is taken away from this object then it must
+     eventually be passed to cmpp_mfree().
+  */
+  unsigned char * z;
+  /**
+     Number of bytes of this->z which are in use, not counting any
+     automatic NUL terminator which this class's APIs may add.
+  */
+  cmpp_size_t n;
+  /**
+     Number of bytes allocated in this->z.
+
+     Potential TODO: use a value of zero here, with a non-zero
+     this->n, to mean that this->z is owned elsewhere. This would
+     cause cmpp_b_append() to copy its original source before
+     appending. Similarly, cmpp_b_clear() would necessarily _not_
+     free this->z. We've used that heuristic in a predecessor of this
+     class in another tree to good effect for years, but it's not
+     certain that we'd get the same level of utility out of that
+     capability as we do in that other project.
+  */
+  cmpp_size_t nAlloc;
+
+  /**
+     cmpp_b APIs which may fail will set this. Similarly, most
+     of the cmpp_b APIs become no-ops if this is non-0.
+  */
+  int errCode;
+};
+
+typedef struct cmpp_b cmpp_b;
+
+/**
+   An empty-initialized cmpp_b struct for use in const-copy
+   initialization.
+*/
+#define cmpp_b_empty_m {.z=0,.n=0,.nAlloc=0,.errCode=0}
+
+/**
+   An empty-initialized cmpp_b struct for use in non-copy copy
+   initialization.
+*/
+extern const cmpp_b cmpp_b_empty;
+
+/**
+   Frees s->z and zeroes out s but does not free s.
+*/
+CMPP_EXPORT void cmpp_b_clear(cmpp_b *s);
+
+/**
+   If s has content, s->nUsed is set to 0 and s->z is NUL-terminated
+   at its first byte, else this is a no-op. s->errCode is
+   set to 0. Returns s.
+ */
+CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *s);
+
+/**
+   Swaps all contents of the given buffers, including their persistent
+   error code.
+*/
+CMPP_EXPORT void cmpp_b_swap(cmpp_b * l, cmpp_b * r);
+
+/**
+   If s->errCode is 0 and s->nAlloc is less than n, s->z is
+   reallocated to have at least n bytes, else this is a no-op. Returns
+   0 on success, CMPP_RC_OOM on error.
+*/
+CMPP_EXPORT int cmpp_b_reserve(cmpp_b *s, cmpp_size_t n);
+
+/**
+   Works just like cmpp_b_reserve() but on allocation error it
+   updates pp's error state.
+*/
+CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n);
+
+/**
+   Appends n bytes from src to os, reallocating os as necessary.
+   Returns 0 on succes, CMPP_RC_OOM on allocation error.
+
+   Errors from this function, and the other cmpp_b_append...()
+   variants, are recoverable (see cmpp_err_set()).
+*/
+CMPP_EXPORT int cmpp_b_append(cmpp_b * os, void const *src,
+                              cmpp_size_t n);
+
+/**
+   Works just like cmpp_b_append() but on allocation error it
+   updates pp's error state.
+*/
+CMPP_EXPORT int cmpp_b_append4(cmpp * pp,
+                               cmpp_b * os,
+                               void const * src,
+                               cmpp_size_t n);
+
+/**
+   Appends ch to the end of os->z, expanding as necessary, and
+   NUL-terminates os. Returns os->errCode and is a no-op if that is
+   non-0 when this is called. This is slightly more efficient than
+   passing length-1 strings to cmpp_b_append() _if_ os's memory
+   is pre-allocated with cmpp_b_reserve(), otherwise it may be
+   less efficient because it may need to allocate frequently if used
+   repeatedly.
+*/
+CMPP_EXPORT int cmpp_b_append_ch(cmpp_b * os, char ch);
+
+/**
+   Appends a decimal string representation of d to os. Returns
+   os->errCode and is a no-op if that is non-0 when this is called.
+*/
+CMPP_EXPORT int cmpp_b_append_i32(cmpp_b * os, int32_t d);
+
+/** int64_t counterpart of cmpp_b_append_i32(). */
+CMPP_EXPORT int cmpp_b_append_i64(cmpp_b * os, int64_t d);
+
+/**
+   A thin wrapper around cmpp_chomp() which chomps b->z.
+*/
+CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b);
+
+/**
+   A cmpp_output_f() impl which requires that its first argument be a
+   (cmpp_b*) or be NULL. If buffer is not NULL then it appends n bytes
+   of src to buffer, reallocating as needed. Returns CMPP_RC_OOM in
+   reallocation error. On success it always NUL-terminates buffer->z.
+   A NULL buffer is treated as success but has no side effects.
+
+   Example usage:
+
+   ```
+   cmpp_b os = cmpp_b_empty;
+   int rc = cmpp_stream(cmpp_input_f_FILE, stdin,
+                        cmpp_output_f_b, &os);
+   ...
+   cmpp_b_clear(&os);
+   ```
+*/
+CMPP_EXPORT int cmpp_output_f_b(void * buffer, void const * src,
+                                cmpp_size_t n);
+
+/**
+   A cmpp_outputer_cleanup_f() implementation which requires that
+   self->state be either NULL or a cmpp_b pointer. This function
+   passes it to cmpp_b_clear(). It does _not_ set self->state or
+   self->name to NULL.
+*/
+CMPP_EXPORT void cmpp_outputer_cleanup_f_b(cmpp_outputer *self);
+
+/**
+   A cmpp_outputer prototype which can be copied to use a dynamic
+   string buffer as an output source. Its state member must be set (by
+   the client) to a cmpp_b instance. Its out() method is
+   cmpp_output_f_b().  Its cleanup() method is
+   cmpp_outputer_cleanup_f_b(). It has no flush() method.
+*/
+extern const cmpp_outputer cmpp_outputer_b;
+
+/**
+   Returns a string containing version information in an unspecified
+   format.
+*/
+CMPP_EXPORT char const * cmpp_version(void);
+
+/**
+   Type IDs for directive lines and argument-parsing tokens.
+
+   This is largely a historical artifact and work is underway
+   to factor this back out of the public API.
+*/
+enum cmpp_tt {
+
+/**
+   X-macro which defines token types. It invokes E(X,Y) for each
+   entry, where X is the base name part of the token type and Y is the
+   token name as it appears in input scripts (if any, else it's 0).
+
+   Maintenance reminder: their ordering in this map is insignificant
+   except that None must be first and must have the value 0.
+
+   Some of the more significant ones are:
+
+   - Word: an unquoted word-like token.
+
+   - String: a quoted string.
+
+   - StringAt: an @"..." string.
+
+   - GroupParen, GroupBrace, GroupSquiggly: (), [], and {}
+
+   - All which start with D_ are directives. D_Line is a transitional
+     state between "unparsed" and another D_... value.
+*/
+#define cmpp_tt_map(E)    \
+  E(None,         0)            \
+  E(RawLine,      0)            \
+  E(Unknown,      0)            \
+  E(Word,         0)            \
+  E(Noop,         0)            \
+  E(Int,          0)            \
+  E(Null,         0)            \
+  E(String,       0)            \
+  E(StringAt,     0)            \
+  E(GroupParen,   0)            \
+  E(GroupBrace,   0)            \
+  E(GroupSquiggly,0)            \
+  E(OpEq,         "=")          \
+  E(OpNeq,        "!=")         \
+  E(OpLt,         "<")          \
+  E(OpLe,         "<=")         \
+  E(OpGt,         ">")          \
+  E(OpGe,         ">=")         \
+  E(ArrowR,       "->")         \
+  E(ArrowL,       "<-")         \
+  E(Plus,         "+")          \
+  E(Minus,        "-")          \
+  E(ShiftR,       ">>")         \
+  E(ShiftL,       "<<")         \
+  E(ShiftL3,      "<<<")        \
+  E(OpNot,        "not")        \
+  E(OpAnd,        "and")        \
+  E(OpOr,         "or")         \
+  E(OpDefined,    "defined")    \
+  E(OpGlob,       "glob")       \
+  E(OpNotGlob,    "not glob")   \
+  E(AnyType,      0)            \
+  E(Eof,          0)
+
+#define E(N,TOK) cmpp_TT_ ## N,
+  cmpp_tt_map(E)
+#undef E
+  /** Used by cmpp_d_register() to assign new IDs. */
+  cmpp_TT__last
+};
+typedef enum cmpp_tt cmpp_tt;
+
+/**
+   For all of the cmpp_tt enum entries, returns a string form of the
+   enum entry name, e.g. "cmpp_TT_D_If". Returns NULL for any other
+   values
+*/
+CMPP_EXPORT char const * cmpp_tt_cstr(int tt);
+
+/**
+   Policies for how to handle undefined @tokens@ when performing
+   content filtering.
+*/
+enum cmpp_atpol_e {
+  /** Sentinel value. */
+  cmpp_atpol_invalid = -1,
+  /** Turn off @token@ parsing. */
+  cmpp_atpol_OFF = 0,
+  /** Retain undefined @token@ - emit it as-is. */
+  cmpp_atpol_RETAIN,
+  /** Elide undefined @token@. */
+  cmpp_atpol_ELIDE,
+  /** Error for undefined @token@. */
+  cmpp_atpol_ERROR,
+  /** A sentinel value for use with cmpp_dx_out_expand(). */
+  cmpp_atpol_CURRENT,
+  /**
+    This isn't _really_ the default. It's the default for the
+    --@policy CLI flag and #@pragma when it's given no value.
+  */
+  cmpp_atpol_DEFAULT_FOR_FLAG = cmpp_atpol_ERROR,
+  /**
+     The compile-time default for all cmpp instances.
+  */
+  cmpp_atpol_DEFAULT  = cmpp_atpol_OFF
+
+};
+typedef enum cmpp_atpol_e cmpp_atpol_e;
+
+/**
+   Policies describing how cmpp should react to attempts to use
+   undefined keys.
+*/
+enum cmpp_unpol_e {
+  /* Sentinel. */
+  cmpp_unpol_invalid,
+  /** Treat undefined keys as NULL/falsy. This is the default. */
+  cmpp_unpol_NULL,
+  /** Trigger an error for undefined keys. This should probably be the
+      default. */
+  cmpp_unpol_ERROR,
+  /**
+     The compile-time default for all cmpp instances.
+  */
+  cmpp_unpol_DEFAULT = cmpp_unpol_NULL
+};
+typedef enum cmpp_unpol_e cmpp_unpol_e;
+
+typedef struct cmpp_arg cmpp_arg;
+/**
+   A single argument for a directive. When a cmpp_d::flags have
+   cmpp_d_F_ARGS_V2 set then the part of the input immediately
+   following the directive (and on the same line) is parsed into a
+   cmpp_args, a container for these.
+*/
+struct cmpp_arg {
+  /** Token type. */
+  cmpp_tt ttype;
+  /**
+     The arg's string value, shorn of any opening/closing quotes or ()
+     or {} or []. The args-parsing process guarantees to NUL-terminate
+     this. The bytes are typically owned by a cmpp_args object, but
+     clients may direct them wherever the need to, so long as the
+     bytes are valid longer than this object is.
+  */
+  unsigned char const * z;
+  /**
+     The arg's effective length, in bytes, after opening/closing chars
+     are stripped. That is, its string form is the range [z,z+n).
+  */
+  unsigned n;
+  /**
+     The next argument in the list. It is owned by whatever code set
+     it up (typically cmpp_args_parse()).
+  */
+  cmpp_arg const * next;
+};
+
+/**
+   Empty-initialized cmpp_arg instance, intended for
+   const-copy initialization.
+*/
+#define cmpp_arg_empty_m {cmpp_TT_None,0,0,0}
+
+/**
+   Empty-initialized cmpp_outputer instance, intended for
+   non-const-copy initialization.
+*/
+extern const cmpp_arg cmpp_arg_empty;
+
+typedef struct cmpp_dx cmpp_dx;
+typedef struct cmpp_dx_pimpl cmpp_dx_pimpl;
+typedef struct cmpp_d cmpp_d;
+
+/**
+   Flags for use with cmpp_d::flags.
+*/
+enum cmpp_d_e {
+  /** Sentinel value. */
+  cmpp_d_F_none        = 0,
+  /**
+     cmpp_dx_next() will not parse the directive's arguments. Instead,
+     it makes cmpp_dx::arg0 encapsulate the whole line of the
+     directive (sans the directive's name) as a single argument. The
+     only transformation which is performed is the removal of
+     backslashes from backslash-escaped newlines. It is up to the
+     directive's callback to handle (or not) the arguments.
+  */
+  cmpp_d_F_ARGS_RAW    = 0x01,
+
+  /**
+     cmpp_dx_next() will parse the directive's arguments.
+     cmpp_dx::arg0 will point to the first argument in the list, or
+     NULL if there are no arguments.
+
+     If both cmpp_d_F_ARGS_LIST and cmpp_d_F_ARGS_RAW are specified,
+     cmpp_d_F_ARGS_LIST will win.
+  */
+  cmpp_d_F_ARGS_LIST   = 0x02,
+
+  /**
+     Indicates that the direction should not be available if the cmpp
+     instance is configured with any of the cmpp_ctor_F_SAFEMODE flags.
+     All directives when do any of the following are obligated to
+     set this flag:
+
+     - Filesystem or network access.
+     - Invoking external processes.
+
+     Or anything else which might be deamed "security-relevant".
+
+     When registering a directive which has both opener and closer
+     implementations, it is sufficient to set this only on the opener.
+
+     The library imposes this flag in the following places:
+
+     - Registration of a directive with this flag will fail if
+       cmpp_is_safemode() is true for that cmpp instance.
+
+     - cmpp_dx_process() will refuse to invoke a directive with this
+       flag when cmpp_is_safemode() is true.
+  */
+  cmpp_d_F_NOT_IN_SAFEMODE  = 0x04,
+
+  /**
+     Call-only directives are only usable in [directive ...]  "call"
+     contexts. They are not permitted to have a closing directive.
+  */
+  cmpp_d_F_CALL_ONLY        = 0x08,
+  /**
+     Indicates that the directive is incapable of working in a [call]
+     context and an error should be trigger if it is. _Most_
+     directives which have a closing directive should have this
+     flag. The exceptions are directives which only conditionally use
+     a closing directive, like #query.
+  */
+  cmpp_d_F_NO_CALL          = 0x10,
+
+  /**
+     Mask of the client-usable range for this enum. Values outside of
+     this mask are reserved for internal use and will be stripped from
+     registrations made with cmpp_d_register().
+  */
+  cmpp_d_F_MASK             = 0x0000ffff
+
+};
+
+/**
+   Callback type for cmpp_d::impl::callback(). cmpp directives are all
+   implemented as functions with this signature. Implementations are
+   called only by cmpp_dx_process() (and only after cmpp_dx_next() has
+   found a preprocessor line), passed the current context object.
+   These callbacks are only ever passed directives which were
+   specifically registered with them (see cmpp_d_register()).
+
+   The first rule of callback is: to report errors (all of which end
+   processing of the current input) call cmpp_dx_err_set(), passing it
+   the callback's only argument, then clean up any local resources,
+   then return. The library will recognize the error and propagate it.
+
+   dx's memory is only valid for the duration of this call. It must
+   not be held on to longer than that. dx->args.arg0 has slightly different
+   lifetime: if this callback does _not_ call back in to
+   cmpp_dx_next() then dx->args.arg0 and its neighbors will survive until
+   this call is completed. Calling cmpp_dx_next(), or any API which
+   invokes it, invalidates dx->args.arg0's memory. Thus directives which
+   call into that must _copy_ any data they need from their own
+   arguments before doing so, as their arguments list will be
+   invalidated.
+*/
+typedef void (*cmpp_dx_f)(cmpp_dx * dx);
+
+/**
+   A typedef for generic deallocation routines.
+*/
+typedef void (*cmpp_finalizer_f)(void *);
+
+/**
+   State specific to concrete cmpp_d implementations.
+
+   TODO: move this, except for the state pointer, out of cmpp_d
+   so that directives cannot invoke these callbacks directly. Getting
+   that to work requires moving the builtin directives into the
+   dynamic directives list.
+*/
+struct cmpp_d_impl {
+  /**
+     Callback func. If any API other othan cmpp_dx_process() invokes
+     this, behavior is undefined.
+  */
+  cmpp_dx_f callback;
+
+  /**
+     For custom directives with a non-NULL this->state, this will be
+     called, and passed that object, when the directive is cleaned
+     up. For directives with both an opening and a closing tag, this
+     destructor is only attached to the opening tag.
+
+     If any API other othan cmpp's internal cleanup routines invoke
+     this, behavior is undefined.
+  */
+  cmpp_finalizer_f dtor;
+
+  /**
+     State for the directive's callback. It is accessible in
+     cmpp_dx_f() impls via theDx->d->impl.state. For custom
+     directives with both an opening and closing directive, this
+     same state object gets assigned to both.
+  */
+  void * state;
+};
+typedef struct cmpp_d_impl cmpp_d_impl;
+#define cmpp_d_impl_empty_m {0,0,0}
+
+/**
+   Each c-pp "directive" is modeled by one of these.
+*/
+struct cmpp_d {
+
+  struct {
+    /**
+       The directive's name, as it must appear after the directive
+       delimiter. Its bytes are assumed to be static or otherwise
+       outlive this object.
+    */
+    const char *z;
+    /** Byte length of this->z. We record this to speed up searches.  */
+    unsigned n;
+  } name;
+
+  /**
+     Bitmask of flags from cmpp_d_e plus possibly internal flags.
+  */
+  cmpp_flag32_t flags;
+
+  /**
+     The directive which acts as this directive's closing element
+     element, or 0 if it has none.
+  */
+  cmpp_d const * closer;
+
+  /**
+     State specific to concrete implementations.
+  */
+  cmpp_d_impl impl;
+};
+
+/**
+   Each instance of the cmpp_dx class (a.k.a. "directive context")
+   manages a single input source. It's responsible for the
+   tokenization of all input, locating directives, and processing
+   ("running") directives.  The directive-specific work happens in
+   cmpp_dx_f() implementations, and this class internally manages the
+   setup, input traversal, and teardown.
+
+   These objects only exist while cmpp is actively processing
+   input. Client code interacts with them only through cmpp_dx_f()
+   implementations which the library invokes.
+
+   The process of filtering input to look for directives is to call
+   cmpp_dx_next() until it indicates either an error or that a
+   directive was found. In the latter case, the cmpp_dx object is
+   populated with info about the current directive. cmpp_dx_process()
+   will run that directive, but cmpp_dx_f() implementations sometimes
+   need to make decisions based on the located directive before doing
+   so (and sometimes they need to skip running it).
+
+   If cmpp_dx_next() finds no directive, the end of the input has been
+   reached and there is no further output to generate.
+
+   Content encountered before a directive is found is passed on to the
+   output stream via cmpp_dx_out_raw() or cmpp_dx_out_expand().
+*/
+struct cmpp_dx {
+  /**
+     The cmpp object which owns this context.
+  */
+  cmpp * const pp;
+
+  /**
+     The directive on whose behalf this context is active.
+  */
+  cmpp_d const *d;
+
+  /**
+     Name of the input for error reporting. Typically an input script
+     file name, but it need not refer to a file.
+  */
+  unsigned const char * const sourceName;
+
+  /**
+     State related to arguments passed to the current directive.
+
+     It is important to keep in mind that the memory for the members
+     of this sub-struct may be modified or reallocated
+     (i.e. invalidated) by any APIs which call in to cmpp_dx_next().
+     cmpp_dx_f() implementations must take care not to use any of this
+     memory after calling into that function, cmpp_dx_consume(), or
+     similar. If needed, it must be copied (e.g. using
+     cmpp_args_clone() to create a local copy of the parsed
+     arguments).
+  */
+  struct {
+    /**
+       Starting byte of unparsed arguments. This is for cmpp_d_f()
+       implementations which need custom argument parsing.
+    */
+    unsigned const char * z;
+
+    /**
+       The byte length of z.
+    */
+    cmpp_size_t nz;
+
+    /**
+       The parsed arg count for the this->arg0 list.
+    */
+    unsigned argc;
+
+    /**
+       The first parsed arg or NULL. How this is set up is affected by
+       cmpp_d::flags.
+
+       This is specifically _NOT_ defined as a sequential array and
+       using pointer math to traverse it invokes undefined behavior.
+
+       To traverse the list:
+
+       for( cmpp_arg const *a = dx->args.arg0; a; a=a->next ){
+         ...
+       }
+    */
+    cmpp_arg const * arg0;
+  } args;
+
+  /**
+     Private impl details.
+  */
+  cmpp_dx_pimpl * const pimpl;
+};
+
+/**
+   Thin proxy for cmpp_err_set(), replacing only the first argument.
+*/
+CMPP_EXPORT int cmpp_dx_err_set(cmpp_dx *dx, int rc,
+                                char const *zFmt, ...);
+
+
+/**
+   Returns true if dx's current call into the API is the result
+   of a function call, else false. Any APIs which recurse into
+   input processing will reset this to false, so it needs to be
+   evaluated before doing any such work.
+
+   Design note: this flag is actually tied to dx's arguments, which
+   get reset by APIs which consume from the input stream.
+*/
+CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx);
+
+/**
+   Returns true if dx->pp has error state, else false. If this
+   function returns true, cmpp_dx_f() implementations are required to
+   stop working, clean up any local resources, and return. Continuing
+   to use dx when it's in an error state may exacerbate the problem.
+*/
+#define cmpp_dx_err_check(DX) (DX)->pp->api->err_has((DX)->pp)
+
+/**
+   Scans dx to the next directive line, emitting all input before that
+   which is _not_ a directive line to dx->pp's output channel unless
+   it's elided due to being inside a block which elides its content
+   (e.g. #if).
+
+   Returns 0 if no errors were triggered, else a cmpp_rc_e code.  This
+   is a no-op if dx->pp has persistent error state set, and that error
+   code is returned.
+
+   If it returns 0 then it sets *pGotOne to true if a directive was
+   found and false if not (in which case the end of the input has
+   been reached and further calls to this function for the same input
+   source will be no-ops).  If it sets *pGotOne to true then it also
+   sets up dx's state for use with cmpp_dx_process(), which should
+   (normally) then be called.
+
+   ACHTUNG: calling this resets any argument-handling-related state of
+   dx. That is important for cmpp_dx_f() implemenations, which _must
+   not_ hold copies of any pointers from dx->args.arg0 or dx->args.z
+   beyond a call to this function. Any state they need must be
+   evaluated, potentially copied, before calling this function().
+*/
+CMPP_EXPORT int cmpp_dx_next(cmpp_dx * dx, bool * pGotOne);
+
+/**
+   This is only legal to call immediately after a successful call to
+   cmpp_dx_next(). It requires that cmpp_dx_next() has just located
+   the next directive. This function runs that directive.  Returns 0
+   on success and all that.
+
+   Design note: directive-Search and directive-process are two
+   distinctly separate steps because directives which have both
+   open/closing tags frequently discard the closing directive without
+   running it (it exists to tell the directive how far to read). Those
+   closing directives exist independently, though, and will trigger
+   errors when encountered outside of the context of their opening
+   directive tag (e.g. an "#/if" without an "#if").
+*/
+CMPP_EXPORT int cmpp_dx_process(cmpp_dx * dx);
+
+/**
+   A bitmask of flags for use with cmpp_dx_consume()
+*/
+enum cmpp_dx_consume_e {
+  /**
+     Tells cmpp_dx_consume() to process any directives it encounters
+     which are not in the specified set of closing directives. Its
+     default is to fail if another directive is seen.
+  */
+  cmpp_dx_consume_F_PROCESS_OTHER_D = 0x01,
+  /**
+     Tells cmpp_dx_consume() that non-directive content encountered
+     before the designated closing directive(s) must use an at-policy
+     of cmpp_atpol_OFF. That is: the output target of that function will
+     get the raw, unfiltered content. This is for cases where the
+     consumer will later re-emit that content, delaying @token@
+     parsing until a later step (e.g. #query does this).
+
+     This may misinteract in unpredictable ways when used with
+     cmpp_dx_consume_F_PROCESS_OTHER_D. Please report them as bugs.
+  */
+  cmpp_dx_consume_F_RAW           = 0x02
+};
+
+/**
+   A helper for cmpp_dx_f() implementations which read in their
+   blocked-off content instead of passing it through the normal output
+   channel.  e.g. `#define x <<` stores that content in a define named
+   "x".
+
+   This function runs a cmpp_dx_next() loop which does the following:
+
+   If the given output channel is not NULL then it first replaces the
+   output channel with the given one, such that all output which would
+   normally be produced will be sent there until this function
+   returns, at which point the output channel is restored. If the
+   given channel is NULL then output is not captured - it instead goes
+   dx's current output channel.
+
+   dClosers must be a list of legal closing tags nClosers entries
+   long. Typically this is the single closing directive/tag of the
+   current directive, available to the opening directive's cmpp_dx_f()
+   impl via dx->d->closer. Some special cases require multiple
+   candidates, however.
+
+   The flags argument may be 0 or a bitmask of values from the
+   cmpp_dx_consume_e enum.
+
+   If flags does not have the cmpp_dx_consume_F_PROCESS_OTHER_D bit set
+   then this function requires that the next directive in the input be
+   one specified by dClosers.  If the next directive is not one of
+   those, it will fail with code CMPP_RC_SYNTAX.
+
+   If flags has the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then it
+   will continue to search for and process directives until the
+   dCloser directive is found. Calling into other directives will
+   invalidate certain state that a cmpp_dx_f() has access to - see
+   below for details. If dCloser is not found before EOF, a
+   CMPP_RC_SYNTAX error is triggered.
+
+   Once one of dCloser is found, this function returns with dx->d
+   referring to the that directive.  In practice, the caller should
+   _not_ call cmpp_dx_process() at that point - the closing directive
+   is typically a no-op placeholder which exists only to mark the end
+   of the block.  If the closer has work to do, however, the caller of
+   this function should call cmpp_dx_process() at that point.
+
+   On success it returns 0, the input stream will have been consumed
+   between the directive dx and its closing tag, and dx->d will point
+   to the new directive.  If os is not NULL then os will have been
+   sent any content.
+
+   On error, processing of the directive must end immediately,
+   returning from the cmpp_dx_f() impl after cleaning up any local
+   resources.
+
+   ACHTUNG: since this invokes cmpp_dx_next(), it invalidates
+   dx->args.arg0. Its dx->d is also replaced but the previous value
+   remains valid until the cmpp instance is cleaned up.
+
+   Example from the context of a cmpp_dx_f() implementation
+
+   ```
+   // "dx" is the cmpp_dx arg to this function
+   cmpp_outputer oss = cmpp_outputer_b;
+   cmpp_b os = cmpp_b_empty;
+   oss.state = &os;
+   if( 0==cmpp_dx_consume(dx, &oss, dx->d->closer, 0) ){
+     cmpp_b_chomp( &os );
+     ... maybe modify the buffer or decorate the output in some way...
+     cmpp_dx_out_raw(dx, os.z, os.n);
+   }
+   cmpp_b_clear(&os);
+   ```
+
+   Design issue: this API does not currently have a way to handle
+   directives which have multiple potential waypoints/endpoints, in
+   the way that an #if may optionally have an #elif or #else before
+   the #/if. Such processing has to be done in the directive's
+   impl.
+*/
+CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os,
+                                cmpp_d const *const * dClosers,
+                                unsigned nClosers,
+                                cmpp_flag32_t flags);
+
+/**
+   Equivalent to cmpp_dx_consume(), capturing to the given buffer
+   instead of a cmpp_outputer object.
+*/
+CMPP_EXPORT int cmpp_dx_consume_b(cmpp_dx * dx, cmpp_b * b,
+                                  cmpp_d const * const * dClosers,
+                                  unsigned nClosers,
+                                  cmpp_flag32_t flags);
+
+/**
+   If arg is not NULL, cleans up any resources owned by
+   arg but does not free arg.
+
+   As of this writing, they own none and some code still requires
+   that. That is Olde Thynking, though.
+*/
+CMPP_EXPORT void cmpp_arg_cleanup(cmpp_arg *arg);
+
+/**
+   If arg is not NULL resets arg to be re-used. arg must have
+   initially been cleanly initialized by copying cmpp_arg_empty (or
+   equivalent, i.e. zeroing it out).
+*/
+CMPP_EXPORT void cmpp_arg_reuse(cmpp_arg *arg);
+
+/**
+   This is the core argument-parsing function used by the library's
+   provided directives. Its is available in the public API as a
+   convenience for custom cmpp_dx_f() implementations, but custom
+   implementations are not required to make use of it.
+
+   Populates a cmpp_arg object by parsing the next token from its
+   input source.
+
+   Expects *pzIn to point to the start of input for parsing arguments
+   and zInEnd to be the logical EOF of that range. This function
+   populates pOut with the info of the parse. Returns 0 on success,
+   non-0 (and updates pp's error state) on error.
+
+   Output (the parsed token) is written to *pzOut. zOutEnd must be the
+   logical EOF of *pzOut. *pzOut needs to be, at most,
+   (zInEnd-*pzIn)+1 bytes long. This function range checks the output
+   and will not write to or past zOutEnd, but that will trigger a
+   CMPP_RC_RANGE error.
+
+   On success, *pzIn will be set to 1 byte after the last one parsed
+   for pOut and *pzOut will be set to one byte after the final output
+   (NUL-terminated). pOut->z will point to the start of *pzOut and
+   pOut->n will be set to the byte-length of pOut->z.
+
+   When the end of the input is reached, this function returns 0
+   and sets pOut->ttype to cmpp_TT_EOF.
+
+   General tokenization rules:
+
+   Tokens come in the following flavors:
+
+   - Quoted strings: single- or double-quoted. cmpp_arg::ttype:
+     cmpp_TT_String.
+
+   - "At-strings": @"..." and @'...'. cmpp_arg::ttype value:
+     cmpp_TT_StringAt.
+
+   - Decimal integers with an optional sign. cmpp_arg::ttype value:
+     cmpp_TT_Int.
+
+   - Groups: (...), {...}, and [...]. cmpp_arg::ttype values:
+     cmpp_TT_GroupParen, cmpp_TT_GroupSquiggly, and
+     cmpp_TT_GroupBrace. These types do not automatically get parsed
+     recursively. To recurse into one of these, pass cmpp_arg_parse()
+     the grouping argument's bytes as the input range.
+
+   - Word: anything which doesn't look like one of these above.  Token
+     type IDs: cmpp_TT_Word. These are most often interpreted as
+     #define keys but cmpp_dx_f() implementations sometimes treat
+     them as literal values.
+
+   - A small subset of words and operator-like tokens, e.g. '=' and
+     '!=', get a very specific ttype, e.g. cmpp_TT_OpNeq, but these
+     can generally be treated as strings.
+
+   - Outside of strings and groups, spaces, tabs, carriage-returns,
+     and newlines are skipped.
+
+   These are explained in more detail in the user's manual
+   (a.k.a. README.md).
+
+   There are many other token types, mostly used internally.
+
+   This function supports _no_ backslash escape sequences in
+   tokens. All backslashes, with the obligatory exception of those
+   which make up backslash-escaped newlines in the input stream, are
+   retained as-is in all token types. That means, for example, that
+   strings may not contain their own quote character.
+
+   As an example of where this function is useful: cmpp_dx_f()
+   implementations which need to selectively parse a subset of the
+   directive's arguments can use this.  As input, their dx argument's
+   args.z and args.nz members delineate the current directive line's
+   arguments. See c-pp.c:cmpp_dx_f_pipe() for an example.
+*/
+CMPP_EXPORT int cmpp_arg_parse(cmpp_dx * dx,
+                               cmpp_arg *pOut,
+                               unsigned char const **pzIn,
+                               unsigned char const *zInEnd,
+                               unsigned char ** pzOut,
+                               unsigned char const * zOutEnd);
+
+/**
+   True if (cmpp_arg const *)ARG's contents match the string literal
+   STR, else false.
+*/
+#define cmpp_arg_equals(ARG,STR) \
+  (sizeof(STR)-1==(ARG)->n && 0==memcmp(STR,(ARG)->z,sizeof(STR)-1))
+
+/**
+   True if (cmpp_arg const *)ARG's contents match the string literal
+   STR or ("-" STR), else false. The intent is that "-flag" be passed
+   here to tolerantly accept either "-flag" or "--flag".
+*/
+#define cmpp_arg_isflag(ARG,STR) \
+  cmpp_arg_equals(ARG,STR) || cmpp_arg_equals(ARG, "-" STR)
+
+/**
+   Creates a copy of arg->z. If allocation fails then pp's persistent
+   error code is set to CMPP_RC_OOM. If pp's error code is not 0 when
+   this is called then this is a no-op and returns NULL. In other
+   words, if this function returns NULL, pp's error state was either
+   already set when this was called or it was set because allocation
+   failed.
+
+   Ownership of the returned memory is transferred to the caller, who
+   must eventually free it using cmpp_mfree().
+*/
+CMPP_EXPORT char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg);
+
+/**
+   Flag bitmasks for use with cmpp_arg_to_b(). With my apologies
+   for the long names (but consistency calls for them).
+*/
+enum cmpp_arg_to_b_e {
+  /**
+     Specifies that the argument's string value should be used as-is,
+     rather than expanding it (if the arg's ttype would normally cause
+     it to be expanded).
+  */
+  cmpp_arg_to_b_F_FORCE_STRING = 0x01,
+
+  /**
+     Tells cmpp_arg_to_b() to not expand arguments with type
+     cmpp_TT_Word, which it normally treats as define keys. It instead
+     treats these as strings.
+   */
+  cmpp_arg_to_b_F_NO_DEFINES = 0x02,
+
+  /**
+     If set, arguments with a ttype of cmpp_TT_GroupBrace will be
+     "called" by passing them to cmpp_call_arg(). The space-trimmed
+     result of the call becomes the output of the cmpp_arg_to_b()
+     call.
+
+     FIXME: make this opt-out instead of opt-in. We end up _almost_
+     always wanting this.
+  */
+  cmpp_arg_to_b_F_BRACE_CALL = 0x04,
+
+  /**
+     Explicitly disable [call] expansion even if
+     cmpp_arg_to_b_F_BRACE_CALL is set in the flags.
+  */
+  cmpp_arg_to_b_F_NO_BRACE_CALL = 0x08
+
+  /**
+     TODO? cmpp_arg_to_b_F_UNESCAPE
+  */
+};
+
+/**
+   Appends some form of arg to the given buffer.
+
+   arg->ttype values of cmpp_TT_Word (define keys) and
+   cmpp_TT_StringAt cause the value to be expanded appropriately (the
+   latter according to dx->pp's current at-policy). Others get emitted
+   as-is.
+
+   The flags argument influences the expansion decisions, as documented
+   in the cmpp_arg_to_b_e enum.
+
+   Returns 0 on success and all that.
+
+   See: cmpp_atpol_get(), cmpp_atpol_set()
+
+   Reminder to self: though this function may, via script-side
+   function call resolution, recurse into the library, any such
+   recursion gets its own cmpp_dx instance. In this context that's
+   significant because it means this call won't invalidate arg's
+   memory like cmpp_dx_consume() or cmpp_dx_next() can (depending on
+   where args came from - typically it's owned by dx but
+   cmpp_args_clone() exists solely to work around such potential
+   invalidation).
+*/
+CMPP_EXPORT int cmpp_arg_to_b(cmpp_dx * dx, cmpp_arg const *arg,
+                              cmpp_b * os, cmpp_flag32_t flags);
+
+/**
+   Flags for use with cmpp_call_str() and friends.
+*/
+enum cmpp_call_e {
+  /** Do not trim a newline from the result. */
+  cmpp_call_F_NO_TRIM    = 0x01,
+  /** Trim all leading and trailing space and newlines
+      from the result. */
+  cmpp_call_F_TRIM_ALL   = 0x02
+};
+
+/**
+   This assumes that arg->z holds a "callable" directive
+   string in the form:
+
+   directiveName ...args
+
+   This function composes a new cmpp input source from that line
+   (prefixed with dx's current directive prefix if it's not already
+   got one), processes it with cmpp_process_string(), redirecting the
+   output to dest (which gets appended to, so be sure to
+   cmpp_b_reuse() it if needed before calling this).
+
+   To simplify common expected usage, by default the output is trimmed
+   of a single newline. The flags argument, 0 or a bitmask of values
+   from the cmpp_call_e enum, can be used to modify that behavior.
+
+   This is the basis of "function calls" in cmpp.
+
+   Returns 0 on success.
+*/
+int cmpp_call_str(cmpp *dx,
+                  unsigned char const * z,
+                  cmpp_ssize_t n,
+                  cmpp_b * dest,
+                  cmpp_flag32_t flags);
+
+/**
+   Convert an errno value to a cmpp_rc_e approximation, defaulting to
+   dflt if no known match is found. This is intended for use by
+   cmpp_dx_f implementations which use errno-using APIs.
+*/
+CMPP_EXPORT int cmpp_errno_rc(int errNo, int dflt);
+
+/**
+   Configuration object for use with cmpp_d_register().
+*/
+struct cmpp_d_reg {
+  /**
+     The name of the directive as it will be used in
+     input scripts, e.g. "mydirective". It will be copied by
+     cmpp_d_register().
+  */
+  char const *name;
+  /**
+     A combination of bits from the cmpp_d_e enum.
+
+     These flags are currently applied only to this->opener.
+     this->closer, because of how it's typically used, assumes
+  */
+  struct {
+    /**
+       Callback for the directive's opening tag.
+    */
+    cmpp_dx_f f;
+    /**
+       Flags from cmpp_d_e. Typically one of cmpp_d_F_ARGS_LIST or
+       cmpp_d_F_ARGS_RAW.
+    */
+    cmpp_flag32_t flags;
+  } opener;
+  struct {
+    /**
+       Callback for the directive's closing tag, if any.
+
+       This is only relevant for directives which have both an open and
+       a closing tag (even if that closing tag is only needed in some
+       contexts, e.g. "#define X <<" (with a closer) vs "#define X Y"
+       (without)).  See cmpp_dx_f_dangling_closer() for a default
+       implementation which triggers an error if it's seen in the input
+       and not consumed by its counterpart opening directive. That
+       implementation has proven useful for #define, #pipe, and friends.
+
+       Design notes: it's as yet unclear how to model, in the public
+       interface, directives which have a whole family of cooperating
+       directives, namely #if/#elif/#else.
+    */
+    cmpp_dx_f f;
+    /**
+       Flags from cmpp_d_e. For closers this can typically be
+       left at 0.
+    */
+    cmpp_flag32_t flags;
+  } closer;
+  /**
+     If not NULL then it is assigned to the directive's opener part
+     and will be called by the library in either of the following
+     cases:
+
+     - When the custom directive is cleaned up.
+
+     - If cmpp_d_register() fails (returns non-0), regardless of how
+       it fails.
+
+     It is passed this->state.
+  */
+  cmpp_finalizer_f dtor;
+  /**
+     Implementation state for the callbacks.
+  */
+  void * state;
+};
+typedef struct cmpp_d_reg cmpp_d_reg;
+/**
+   Empty-initialized cmpp_d_reg instance, intended for const-copy
+   initialization.
+*/
+#define cmpp_d_reg_empty_m {0,{0,0},{0,0},0,0}
+/**
+   Empty-initialized instance, intended for non-const-copy
+   initialization.
+*/
+//extern const cmpp_d_reg cmpp_d_reg_empty;
+
+/**
+   Registers a new directive, or a pair of opening/closing directives,
+   with pp.
+
+   The semantics of r's members are documented in the cmpp_d_reg
+   class. r->name and r->opener.f are required. The remainder may be
+   0/NULL. Its members are copied - r need not live longer than this
+   call.
+
+   When the new directive is seen in a script, r->opener.f() will be
+   called. If the closing directive (if any) is seen in a script,
+   r->closer.f() is called. In both cases, the callback
+   implementation can get access to the r->state object via
+   cmdd_dx::d::impl::state (a.k.a dx->d->impl.state).
+
+   If r->closer.f is not NULL then the closing directive will be named
+   "/${zName}". (Design note: it is thought that forcing a common
+   end-directive syntax will lead to fewer issues than allowing
+   free-form closing tag names, e.g. fewer chances of a name collision
+   or not quite remembering the spelling of a given closing tag
+   (#endef vs #enddefine vs #/define).)
+
+   Returns 0 on success and updates pp's error state on error. Similarly,
+   this is a no-op if pp has an error code when this is called, in which
+   case it returns that result code without other side-effects.
+
+   On success, if pOut is not NULL then it is set to the directive
+   pointer, memory owned by pp until it cleans up its directives. This
+   is the only place in the API a non-const pointer to a directive can
+   be found, and it is provided only for very specific use-cases where
+   a directive needs to be manipulated (carefully) after
+   registration[^post-reg-manipulation]. If this function also
+   registered a closing directive, it is available as (*pOut)->closer.
+   pOut should normally be NULL.
+
+   Failure modes include:
+
+   - Returns CMPP_RC_RANGE if zName is not legal for use as a
+     directive name. See cmpp_is_legal_key().
+
+   - Returns CMPP_RC_OOM on an allocation error.
+
+   Errors from this function are recoverable (see cmpp_err_set()). A
+   failed registration, even one which doesn't fail until the
+   registration of the closing element, will leave pp in a
+   well-defined state (with neither of r's directives being
+   registered).
+
+   [^post-reg-manipulation]: The one known use case if the #if family
+   of directives, all of which use the same #/if closing
+   directive. The public registration API does not account for sharing
+   of closers that way, and whether it _should_ is still TBD. The
+   workaround, for this case, is to get the directives as they're
+   registered and point the cmpp_d::closer of each of #if, #elif, and
+   #else to #/if.
+*/
+CMPP_EXPORT int cmpp_d_register(cmpp * pp, cmpp_d_reg const * r,
+                                cmpp_d **pOut);
+
+/**
+   A cmpp_dx_f() impl which is intended to be used as a callback for
+   directive closing tags for directives in which the opening tag's
+   implementation consumes the input up to the closing tag.  This impl
+   triggers an error if called, indicating that the directive closing
+   was seen in the input without its accompanying directive opening.
+*/
+CMPP_EXPORT void cmpp_dx_f_dangling_closer(cmpp_dx *dx);
+
+/**
+   Writes the first n bytes of z to dx->pp's current output channel
+   without performing any @token@ parsing.
+
+   Returns dx->pp's persistent error code (0 on success) and sets that
+   code to non-0 on error. This is a no-op if dx->pp has a non-0 error
+   state, returning that code.
+
+   See: cmpp_dx_out_expand()
+*/
+CMPP_EXPORT int cmpp_dx_out_raw(cmpp_dx * dx, void const *z,
+                                cmpp_size_t n);
+
+/**
+   Sends [zFrom,zFrom+n) to pOut, performing @token@ expansion if the
+   given policy says to (else it passes the content through as-is, as
+   per cmpp_dx_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's
+   current policy. A policy of cmpp_atpol_OFF behaves exactly like
+   cmpp_dx_out_raw().
+
+   Returns dx->pp's persistent error code (0 on success) and sets that
+   code to non-0 on error. This is a no-op if dx->pp has a non-0 error
+   state, returning that code.
+
+   If pOut is NULL then dx->pp's default channel is used, with the
+   caveat that atPolicy's only legal value in that case is
+   cmpp_atpol_CURRENT. (The internals do not allow the at-policy to be
+   overridden for that particular output channel, to avoid accidental
+   filtering when it's not enabled. They do not impose that
+   restriction for other output channels, which are frequently used
+   for filtering intermediary results.)
+
+   See: cmpp_dx_out_raw()
+
+   Notes regarding how this is used internally:
+
+   - This function currently specifically does nothing when invoked in
+   skip-mode[^1]. Hypothetically it cannot ever be called in skip-mode
+   except when evaluating #elif expressions (previous #if/#elifs
+   having failed and put us in skip-mode), where it's expanding
+   expression operands. That part currently (as of 2025-10-21) uses
+   dx->pp's current policy, and it's not clear whether that is
+   sufficient or whether we need to force it to expand (and which
+   policy to use when doing so). We could possibly get away with
+   always using cmpp_atpol_ERROR for purposes of evaluating at-string
+   expression operands.
+
+   [^1]: Skip-mode is the internal mechanism which keeps directives
+   from running, and content from being emitted, within a falsy branch
+   of an #if/#elif block.  Only flow-control directives are ever run
+   when skip-mode is active, and client-provided directives cannot
+   easily provide flow-control support. Ergo, much of this paragraph
+   is not relevant for client-level code, but it is for this library's
+   own use of this function.
+*/
+CMPP_EXPORT int cmpp_dx_out_expand(cmpp_dx const * dx,
+                                   cmpp_outputer * pOut,
+                                   unsigned char const * zFrom,
+                                   cmpp_size_t n,
+                                   cmpp_atpol_e policy);
+
+/**
+   This creates a formatted string using sqlite3_mprintf() and emits it
+   using cmpp_dx_out_raw(). Returns CMPP_RC_OOM if allocation of the
+   string fails, else it returns whatever cmpp_dx_out_raw() returns.
+
+   This is a no-op if dx->pp is in an error state, returning
+   that code.
+*/
+CMPP_EXPORT int cmpp_dx_outf(cmpp_dx *dx, char const *zFmt, ...);
+
+/**
+   Convenience form of cmpp_delimiter_get() which returns the
+   delimiter which was active at the time when the currently-running
+   cmpp_dx_f() was called. This memory may be invalidated by any calls
+   into cmpp_dx_process() or cmpp_delimiter_set(), so a copy of this
+   pointer must not be retained past such a point.
+
+   This function is primarily intended for use in generating debug and
+   error messages.
+
+   If the delimiter stack is empty, this function returns NULL.
+*/
+CMPP_EXPORT char const * cmpp_dx_delim(cmpp_dx const *dx);
+
+/**
+   Borrows a buffer from pp's buffer recycling pool, allocating one if
+   needed. It returns NULL only on allocation error, in which case it
+   updates pp's error state.
+
+   This transfers ownership of the buffer to the caller, who is
+   obligated to eventually do ONE of the following:
+
+   - Pass it to cmpp_b_return() with the same dx argument.
+
+   - Pass it to cmpp_b_clear() then cmpp_mfree().
+
+   The purpose of this function is a memory reuse optimization.  Most
+   directives, and many internals, need to use buffers for something
+   or other and this gives them a way to reuse buffers.
+
+   Potential TODO: How this pool optimizes (or not) buffer allotment
+   is an internal detail. Maybe add an argument which provides a hint
+   about the buffer usage. e.g. argument-conversion buffers are
+   normally small but block content buffers can be arbitrarily large.
+*/
+CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *dx);
+
+/**
+   Returns a buffer borrowed from cmpp_b_borrow(), transferring
+   ownership back to pp. Passing a non-NULL b which was not returned
+   by cmpp_b_borrow() invoked undefined behavior (possibly delayed
+   until the list is cleaned up). To simplify usage, b may be NULL.
+
+   After calling this, b must be considered "freed" - it must not be
+   used again. This function is free (as it were) to immediately free
+   the object's memory instead of recycling it.
+*/
+CMPP_EXPORT void cmpp_b_return(cmpp *dx, cmpp_b *b);
+
+/**
+   If NUL-terminated z matches one of the strings listed below, its
+   corresponding cmpp_atpol_e entry is returned, else
+   cmpp_atpol_invalid is returned.
+
+   If pp is not NULL then (A) this also sets its current at-policy and
+   (B) it recognizes an additional string (see below).  In this case,
+   if z is not a valid string then pp's persistent error state is set.
+
+   Its accepted values each correspond to a like-named policy value:
+
+  - "off" (the default): no processing of `@` is performed.
+
+  - "error": fail if an undefined `X` is referenced in @token@
+    parsing.
+
+  - "retain": emit any unresolved `@X@` tokens as-is to the output
+    stream. i.e. `@X@` renders as `@X@`.
+
+    - "elide": omit unresolved `@X@` from the output, as if their values
+    were empty. i.e. `@X@` renders as an empty string, i.e. is not
+    emitted at all.
+
+  - "current": if pp!=NULL then it returns the current policy, else
+    this string resolves to cmpp_atpol_invalid.
+*/
+CMPP_EXPORT cmpp_atpol_e cmpp_atpol_from_str(cmpp * pp, char const *z);
+
+/**
+   Returns pp's current at-token policy.
+*/
+CMPP_EXPORT cmpp_atpol_e cmpp_atpol_get(cmpp const * const pp);
+
+/**
+   Sets pp's current at-token policy. Returns 0 if pol is valid, else
+   it updates pp's error state and returns CMPP_RC_RANGE. This is a
+   no-op if pp has error state, returning that code instead.
+
+   The policy cmpp_atpol_CURRENT is a no-op, permitted to simplify
+   certain client-side usage.
+*/
+CMPP_EXPORT int cmpp_atpol_set(cmpp * const pp, cmpp_atpol_e pol);
+
+/**
+   Pushes pol as the current at-policy. Returns 0 on success and
+   non-zero on error (bad pol value or allocation error).  If this
+   returns 0 then the caller is obligated to eventually call
+   cmpp_atpol_pop() one time. If it returns non-0 then they _must not_
+   call that function.
+*/
+CMPP_EXPORT int cmpp_atpol_push(cmpp *pp, cmpp_atpol_e pol);
+
+/**
+   Must be called one time for each successful call to
+   cmpp_atpol_push(). It restores the at-policy to the value it
+   has when cmpp_atpol_push() was last called.
+
+   If called when no cmpp_delimiter_push() is active then debug builds
+   will fail an assert(), else pp's error state is updated if it has
+   none already.
+*/
+CMPP_EXPORT void cmpp_atpol_pop(cmpp *pp);
+
+/**
+   The cmpp_unpol_e counterpart of cmpp_atpol_from_str(). It
+   behaves identically, just for a different policy group with
+   different names.
+
+   Its accepted values are: "null" and "error". The value "current" is
+   only legal if pp!=NULL, else it resolves to cmpp_unpol_invalid.
+*/
+CMPP_EXPORT cmpp_unpol_e cmpp_unpol_from_str(cmpp * pp, char const *z);
+
+/**
+   Returns pp's current policy regarding use of undefined define keys.
+*/
+CMPP_EXPORT cmpp_unpol_e cmpp_unpol_get(cmpp const * const pp);
+
+/**
+   Sets pp's current policy regarding use of undefined define keys.
+   Returns 0 if pol is valid, else it updates pp's error state and
+   returns CMPP_RC_RANGE.
+*/
+CMPP_EXPORT int cmpp_unpol_set(cmpp * const pp, cmpp_unpol_e pol);
+
+/**
+   The undefined-policy counterpart of cmpp_atpol_push().
+*/
+CMPP_EXPORT int cmpp_unpol_push(cmpp *pp, cmpp_unpol_e pol);
+
+/**
+   The undefined-policy counterpart of cmpp_atpol_pop().
+*/
+CMPP_EXPORT void cmpp_unpol_pop(cmpp *pp);
+
+/**
+   The at-token counterpart of cmpp_delimiter_get(). This sets *zOpen
+   (if zOpen is not NULL) to the opening delimiter and *zClose (if
+   zClose is not NULL) to the closing delimiter.  The memory is owned
+   by pp and may be invalidated by any calls to cmpp_atdelim_set(),
+   cmpp_atdelim_push(), or any APIs which consume input. Each string
+   is NUL-terminated and must be copied by the caller if they need
+   these strings past a point where they might be invalidated.
+
+   If called when the the delimiter stack is empty, debug builds with
+   fail an assert() and non-debug builds will behave as if the stack
+   contains the compile-time default delimiters.
+*/
+CMPP_EXPORT void cmpp_atdelim_get(cmpp const * pp,
+                                  char const **zOpen,
+                                  char const **zClose);
+/**
+   The `@token@`-delimiter counterpart of cmpp_delimeter_set().
+
+   This sets the delimiter for `@token@` content to the given opening
+   and closing strings (which the library makes a copy of).  If zOpen
+   is NULL then the compile-time default is assumed. If zClose is NULL
+   then zOpen is assumed.
+
+   Returns 0 on success. Returns non-0 if called when the delimiter
+   stack is empty, if it cannot copy the string or zDelim is deemed
+   unsuitable for use as a delimiter.
+
+   In debug builds this will trigger an assert if no `@token@`
+   delimiter has been set, but pp starts with one level in place, so
+   it is safe to call without having made an explicit
+   cmpp_atdelim_push() unless cmpp_atdelim_pop() has been misused.
+*/
+CMPP_EXPORT int cmpp_atdelim_set(cmpp * pp,
+                                 char const *zOpen,
+                                 char const *zClose);
+
+/**
+   The `@token@`-delimiter counterpart of cmpp_delimeter_push().
+
+   See cmpp_atdelim_set() for the semantics of the arguments.
+*/
+CMPP_EXPORT int cmpp_atdelim_push(cmpp *pp,
+                                  char const *zOpen,
+                                  char const *zClose);
+
+/**
+   The @token@-delimiter counterpart of cmpp_delimiter_pop().
+*/
+CMPP_EXPORT int cmpp_atdelim_pop(cmpp *pp);
+
+/**
+   Searches the given path (zPath), split on the given path separator
+   (pathSep), for the given file (zBaseName), optionally with the
+   given file extension (zExt).
+
+   If zBaseName or zBaseName+zExt are found as-is, without any
+   search path prefix, that will be the result, else the result
+   is either zBaseName or zBaseName+zExt prefixed by one of the
+   search directories.
+
+   On success, returns a new string, transfering ownership to the
+   caller (who must eventually pass it to cmpp_mfree() to deallocate).
+
+   If no match is found, or on error, returns NULL. On a genuine
+   error, pp's error state is updated and the error is unlikely to be
+   recoverable (see cmpp_err_set()).
+
+   This function is a no-op if called when pp's error state is set,
+   returning NULL.
+
+   Results are undefined (in the sense of whether it will work or not,
+   as opposed to whether it will crash or not) if pathSep is a control
+   character.
+
+   Design note: this is implemented as a Common Table Expression
+   query.
+*/
+CMPP_EXPORT char * cmpp_path_search(cmpp *pp,
+                                    char const *zPath,
+                                    char pathSep,
+                                    char const *zBaseName,
+                                    char const *zExt);
+
+/**
+   Scans [*zPos,zEnd) for the next chSep character. Sets *zPos to one
+   after the last consumed byte, so its result includes the separator
+   character unless EOF is hit before then. If pCounter is not NULL
+   then it does ++*pCounter when finding chSep.
+
+   Returns true if any input is consumed, else false (EOF). When it
+   returns false, *zPos will have the same value it had when this was
+   called. If it returns true, *zPos will be greater than it was
+   before this call and <= zEnd.
+
+   Usage:
+
+   ```
+   unsigned char const * zBegin = ...;
+   unsigned char const * const zEnd = zBegin + strlen(zBegin);
+   unsigned char const * zEol = zBegin;
+   cmpp_size_t nLn = 0;
+   while( cmpp_next_chunk(&zEol, zEnd, '\n', &nLn) ){
+     ...
+   }
+   ```
+*/
+CMPP_EXPORT
+bool cmpp_next_chunk(unsigned char const **zPos,
+                     unsigned char const *zEnd,
+                     unsigned char chSep,
+                     cmpp_size_t *pCounter);
+
+/**
+   Flags and constants related to the cmpp_args type.
+*/
+enum cmpp_args_e {
+  /**
+    cmpp_args_parse() flag which tells cmpp_args_parse() not to
+    dive into (...) group tokens. It insteads leaves them to be parsed
+    (or not) by downstream code. The only reason to parse them in
+    advance is to catch syntax errors sooner rather than later.
+  */
+  cmpp_args_F_NO_PARENS    = 0x01
+};
+
+/**
+   An internal detail of cmpp_args.
+*/
+typedef struct cmpp_args_pimpl cmpp_args_pimpl;
+
+/**
+   A container for parsing a line's worth of cmpp_arg
+   objects.
+
+   Instances MUST be cleanly initialized by bitwise-copying either
+   cmpp_args_empty or (depending on the context) cmpp_args_empty_m.
+
+   Instances MUST eventually be passed to cmpp_args_cleanup().
+
+   Design notes: this class is provided to the public API as a
+   convenience, not as a core/required component. It offers one of
+   many possible solutions for dealing with argument lists and is not
+   the End All/Be All of solutions. I didn't _really_ want to expose
+   this class in the public API at all but I also want client-side
+   directives to have the _option_ to to do some of the things
+   currently builtin directives can do which are (as of this writing)
+   unavailable in the public API, e.g. evaluate expressions (in that
+   limited form which this library supports). A stepping stone to
+   doing so is making this class public.
+*/
+struct cmpp_args {
+  /**
+     Number of parsed args. In the context of a cmpp_dx_f(), argument
+     lists do not include their directive's name as an argument.
+  */
+  unsigned argc;
+
+  /**
+     The list of args. This is very specifically NOT an array (or at
+     least not one which client code can rely on to behave
+     sensibly). Some internal APIs adjust a cmpp_args's arg list,
+     re-linking the entries via cmpp_arg::next and making array-style
+     traversal a foot-gun.
+
+     To loop over them:
+
+     for( cmpp_arg const * arg = args->arg0; arg; arg = arg->next ){...}
+
+     This really ought to be const but it currenty cannot be for
+     internal reasons. Client code really should not modify these
+     objects, though. Doing so invokes undefined behavior.
+
+     For directives with the cmpp_d_F_ARGS_RAW flag, this member will,
+     after a successful call to cmpp_dx_next(), point to a single
+     argument which holds the directive's entire argument string,
+     stripped of leading spaces.
+  */
+  cmpp_arg * arg0;
+
+  /**
+     Internal implementation details. This is initialized via
+     cmpp_args_parse() and freed via cmpp_args_cleanup().
+  */
+  cmpp_args_pimpl * pimpl;
+};
+typedef struct cmpp_args cmpp_args;
+
+/**
+   Empty-initialized cmpp_args instance, intended for const-copy
+   initialization.
+*/
+#define cmpp_args_empty_m { \
+  .argc = 0,                \
+  .arg0 = 0,                \
+  .pimpl = 0                \
+}
+
+/**
+   Empty-initialized instance, intended for non-const-copy
+   initialization.
+*/
+extern const cmpp_args cmpp_args_empty;
+
+/**
+   Parses the range [zInBegin,zInBegin+nIn) into a list of cmpp_arg
+   objects by iteratively processing that range with cmpp_arg_parse().
+   If nIn is negative, strlen() is used to calculate it.
+
+   Requires that arg be a cleanly-initialized instance (via
+   bitwise-copying cmpp_args_empty) or that it have been successfully
+   used with this function before. Behavior is undefined if pArgs was
+   not properly initialized.
+
+   The 3rd argument is an optional bitmask of flags from the
+   cmpp_args_e enum.
+
+   On success it populates arg, noting that an empty list is valid.
+   The memory pointed to by the arguments made available via
+   arg->arg0 is all owned by arg and will be invalidated by either a
+   subsequent call to this function (the memory will be overwritten or
+   reallocated) or cmpp_args_cleanup() (the memory will be freed).
+
+   On error, returns non-0 and updates pp's error state with info
+   about the problem.
+*/
+CMPP_EXPORT int cmpp_args_parse(cmpp_dx * dx,
+                                cmpp_args * pOut,
+                                unsigned char const * zInBegin,
+                                cmpp_ssize_t nIn,
+                                cmpp_flag32_t flags);
+
+/**
+   Frees any resources owned by its argument but does not free the
+   argument (which is typically stack-allocated). After calling this,
+   the object may again be used with cmpp_args_parse() (in which case
+   it eventually needs to be passed to this again).
+
+   This is a harmless no-op if `a` is already cleaned up but `a` must
+   not be NULL.
+*/
+CMPP_EXPORT void cmpp_args_cleanup(cmpp_args *a);
+
+/**
+   A wrapper around cmpp_args_parse() which uses dx->args.z as an
+   input source. This is sometimes convenient in cmpp_dx_f()
+   implementations which use cmpp_dx_next(), or similar, to read and
+   process custom directives, as doing so invalidates dx->arg's
+   memory.
+
+   On success, returns 0 and populates args. On error, returns non-0
+   and sets dx->pp's error state.
+
+   cmpp_dx_args_clone() does essentially the same thing, but is more
+   efficient when dx->args.arg0 is is already parsed.
+*/
+CMPP_EXPORT int cmpp_dx_args_parse(cmpp_dx *dx, cmpp_args *args);
+
+/**
+   Populates pOut, replacing any current content, with a copy of each
+   arg in dx->args.arg0 (traversing arg0->next).
+
+   *pOut MUST be cleanly initialized via copying cmpp_args_empty or it
+   must have previously been used with either cmpp_args_parse() (which
+   has the same initialization requirement) or this function has
+   undefined results.
+
+   On success, pOut->argc and pOut->arg0 will refer to pOut's copy
+   of the arguments.
+
+   Copying of arguments is necessary in cmpp_dx_f() implementations
+   which need to hold on to arguments for use _after_ calling
+   cmpp_dx_next() or any API which calls that (which most directives
+   don't do). See that function for why.
+*/
+CMPP_EXPORT int cmpp_dx_args_clone(cmpp_dx * dx, cmpp_args *pOut);
+
+/** Flags for cmpp_popen(). */
+enum cmpp_popen_e {
+  /**
+     Use execl[p](CMD, CMD,0) instead of
+     execl[p]("/bin/sh","-c",CMD,0).
+  */
+  cmpp_popen_F_DIRECT = 0x01,
+  /** Use execlp() or execvp() instead of execl() or execv(). */
+  cmpp_popen_F_PATH   = 0x02
+};
+
+/**
+   Result state for cmpp_popen() and friends.
+*/
+struct cmpp_popen_t {
+  /**
+     The child process ID.
+   */
+  int childPid;
+  /**
+     The child process's stdout.
+  */
+  int fdFromChild;
+  /**
+     If not NULL, cmpp_popen() will set *fpToChild to a FILE handle
+     mapped to the child process's stdin. If it is NULL, the child
+     process's stdin will be closed instead.
+  */
+  cmpp_FILE **fpToChild;
+};
+typedef struct cmpp_popen_t cmpp_popen_t;
+/**
+   Empty-initialized cmpp_popen_t instance, intended for const-copy
+   initialization.
+*/
+#define cmpp_popen_t_empty_m {-1,-1,0}
+/**
+   Empty-initialized instance, intended for non-const-copy
+   initialization.
+*/
+extern const cmpp_popen_t cmpp_popen_t_empty;
+
+/**
+   Uses fork()/exec() to run a command in a separate process and open
+   a two-way stream to it. It is provided in this API to facilitate
+   the creation of custom directives which shell out to external
+   processes.
+
+   zCmd must contain the NUL-terminated command to run and any flags
+   for that command, e.g. "myapp --flag --other-flag". It is passed as
+   the 4th argument to:
+
+     execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL)
+
+   The po object MUST be cleanly initialized before calling this by
+   bitwise copying cmpp_popen_t_empty or (depending on the context)
+   cmpp_popen_t_empty_m.
+
+   Flags:
+
+   - cmpp_popen_F_DIRECT: zCmd is passed to execl(zCmd, zCmd, NULL).
+     instead of exec(). That can only work if zCmd is a single command
+     without arguments.
+
+   - cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which
+     performs path lookup of its initial argument. Again, that can
+     only work if zCmd is a single command without arguments.
+
+   On success:
+
+   - po->childPid will be set to the PID of the child process.
+
+   - po->fdFromChild is set to the child's stdout file
+   descriptor. read(2) from it to read from the child.
+
+   - If po->fpToChild is not NULL then *po->fpToChild is set to a
+   buffered output handle to the child's stdin.  fwrite(3) to it to
+   send the child stuff. Be sure to fflush(3) and/or fclose(3) it to
+   keep it from hanging forever. If po->fpToChild is NULL then the
+   stdin of the child is closed. (Why buffered instead of unbuffered?
+   My attempts at getting unbuffered child stdin to work have all
+   failed when write() is called on it.)
+
+   On success, the caller is obligated to pass po to cmpp_pclose().
+   The caller may pass pi to cmpp_pclose() on error, if that's easier
+   for them, provided that the po argument was cleanly initialized
+   before passing it to this function.
+
+   If the caller fclose(3)s *po->fpToChild then they must set it to
+   NULL so that passing it to cmpp_pclose() knows not to close it.
+
+   On error: you know the drill. This function is a no-op if pp has
+   error state when it's called, and the current error code is
+   returned instead.
+
+   This function is only available on non-WASM Unix-like environments.
+   On others it will always trigger a CMPP_RC_UNSUPPORTED error.
+
+   Bugs: because the command is run via /bin/sh -c ...  we cannot tell
+   if it's actually found. All we can tell is that /bin/sh ran.
+
+   Also: this doesn't capture stderr, so commands should redirect
+   stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is
+   a potential TODO without a current use case.
+
+   See: cmpp_pclose()
+   See: cmpp_popenv()
+*/
+CMPP_EXPORT int cmpp_popen(cmpp *pp, unsigned char const *zCmd,
+                           cmpp_flag32_t flags, cmpp_popen_t *po);
+
+/**
+   Works like cmpp_popen() except that:
+
+   - It takes it arguments in the form of a main()-style array of
+     strings because it uses execv() instead of exec(). The
+     cmpp_popen_F_PATH flag causes it to use execvp().
+
+   - It does not honor the cmpp_popen_F_DIRECT flag because all
+     arguments have to be passed in via the arguments array.
+
+   As per execv()'s requirements: azCmd _MUST_ end with a NULL entry.
+*/
+CMPP_EXPORT int cmpp_popenv(cmpp *pp, char * const * azCmd,
+                            cmpp_flag32_t flags, cmpp_popen_t *po);
+
+/**
+   Closes handles returned by cmpp_popen() and zeroes out po. If the
+   caller fclose()d *po->fpToChild then they need to set it to NULL so
+   that this function does not double-close it.
+
+   Returns the result code of the child process.
+
+   After calling this, po may again be used as an argument to
+   cmpp_popen().
+*/
+CMPP_EXPORT int cmpp_pclose(cmpp_popen_t *po);
+
+/**
+   A cmpp_popenv() proxy which builds up an execv()-style array of
+   arguments from the given args. It has a hard, and mostly arbitrary,
+   upper limit on the number of args it can take in order to avoid
+   extra allocation.
+*/
+CMPP_EXPORT int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args,
+                                cmpp_popen_t *p);
+
+
+/**
+   Callback type for use with cmpp_kav_each().
+
+   cmpp_kav_each() calls this one time per key/value in such a list,
+   passing it the relevant key/value strings and lengths, plus the
+   opaque state pointer which is passed to cmpp_kav_each().
+
+   Must return 0 on success or update (or propagate) dx->pp's error
+   state on error.
+*/
+typedef int cmpp_kav_each_f(
+  cmpp_dx *dx,
+  unsigned char const *zKey, cmpp_size_t nKey,
+  unsigned char const *zVal, cmpp_size_t nVal,
+  void* callbackState
+);
+
+/**
+   Flag bitmask for use with cmpp_kav_each() and cmpp_str_each().
+*/
+enum cmpp_kav_each_e {
+  /**
+     The key argument should be expanded using cmpp_arg_to_b()
+     with a 0 flags value. This flag should normally not be used.
+  */
+  cmpp_kav_each_F_EXPAND_KEY = 0x01,
+  /**
+     The key argument should be expanded using cmpp_arg_to_b()
+     with a 0 flags value. This flag should normally be used.
+  */
+  cmpp_kav_each_F_EXPAND_VAL = 0x02,
+  /**
+     Treat (...) value tokens (ttype=cmpp_TT_GroupParen) as integer
+     expressions. Keys are never treated this way. Without this flag,
+     the token expands to the ... part of (...).
+  */
+  cmpp_kav_each_F_PARENS_EXPR = 0x04,
+  /**
+     Indicates that an empty input list is an error. If this flag is
+     not set and the list is empty, the callback will not be called
+     and no error will be triggered.
+  */
+  cmpp_kav_each_F_NOT_EMPTY   = 0x08,
+  /**
+     Indicates that the list does not have the '->' part(s).  That is,
+     the list needs to be in pairs of KEY VAL rather than triples of
+     KEY -> VALUE.
+  */
+  cmpp_kav_each_F_NO_ARROW    = 0x10,
+
+  /**
+     If set, keys get the cmpp_arg_to_b_F_BRACE_CALL flag added
+     to them. This implies cmpp_kav_each_F_EXPAND_KEY.
+  */
+  cmpp_kav_each_F_CALL_KEY    = 0x20,
+  /** Value counterpart of cmpp_kav_each_F_CALL_KEY. */
+  cmpp_kav_each_F_CALL_VAL    = 0x40,
+  /** Both cmpp_kav_each_F_CALL_KEY and cmpp_kav_each_F_CALL_VAL. */
+  cmpp_kav_each_F_CALL        = 0x60,
+
+  //TODO: append to defines which already exist
+  cmpp_kav_each_F_APPEND = 0,
+  cmpp_kav_each_F_APPEND_SPACE = 0,
+  cmpp_kav_each_F_APPEND_NL = 0
+};
+
+/**
+   A helper for cmpp_dx_f() implementations in processing directive
+   arguments which are lists in this form:
+
+   { a -> b c -> d ... }
+
+   ("kav" is short for "key arrow value".)
+
+   The range [zBegin,zBegin+nIn) contains the raw list (not including
+   any containing braces, parentheses, quotes, or the like). If nIn is
+   negative, strlen() is used to calculate it.
+
+   The range is parsed using cmpp_args_parse().
+
+   For each key/arrow/value triplet in that list, callback() is passed
+   the stringified form of the key and the value, plus the
+   callbackState pointer.
+
+   The flags argument controls whether the keys and values get
+   expanded or not. (Typically the keys should not be expanded but the
+   values should.)
+
+   Returns 0 on success. If the callback returns non-0, it is expected
+   to have updated dx's error state. callback() will never be called
+   when dx's error state is non-0.
+
+   Error results include:
+
+   - CMPP_RC_RANGE: the list is empty does not contain the correct
+   number of entries (groups of 3, or 2 if flags has
+   cmpp_kav_each_F_NO_ARROW).
+
+   - CMPP_RC_OOM: allocation error.
+
+   - Any value returned by cmpp_args_parse().
+
+   - Any number of errors can be triggered during expansion of
+     keys and values.
+*/
+CMPP_EXPORT int cmpp_kav_each(
+  cmpp_dx *dx,
+  unsigned char const *zBegin,
+  cmpp_ssize_t nIn,
+  cmpp_kav_each_f callback, void *callbackState,
+  cmpp_flag32_t flags
+);
+
+/**
+   This works like cmpp_kav_each() except that it treats each token in
+   the list as a single entry.
+
+   When the callback is triggered, the "key" part will be the raw
+   token and the "value" part will be the expanded form of that
+   value. Its flags may contain most of the cmpp_kav_each_F_... flags,
+   with the exception that cmpp_kav_each_F_EXPAND_KEY has no effect
+   here. If cmpp_kav_each_F_EXPAND_VAL is not in the flags then the
+   callback receives the same string for both the key and value.
+*/
+CMPP_EXPORT int cmpp_str_each(
+  cmpp_dx *dx,
+  unsigned char const *zBegin,
+  cmpp_ssize_t nIn,
+  cmpp_kav_each_f callback, void *callbackState,
+  cmpp_flag32_t flags
+);
+
+/**
+   An interface for clients to provide directives to the library
+   on-demand.
+
+   This is called when pp has encountered a directive name is does not
+   know. It is passed the cmpp object, the name of the directive, and
+   the opaque state pointer which was passed to cmpp_d_autoloader_et().
+
+   Implementations should compare dname to any directives they know
+   about.  If they find no match they must return CMPP_RC_NO_DIRECTIVE
+   _without_ using cmpp_err_set() to make the error persistent.
+
+   If they find a match, they must use cmpp_d_register() to register
+   it and (on success) return 0. The library will then look again in
+   the registered directive list for the directive before giving up.
+
+   If they find a match but registration fails then the result of that
+   failure must be returned.
+
+   For implementation-specific errors, e.g. trying to load a directive
+   from a DLL but the loading of the DLL fails, implementations are
+   expected to use cmpp_err_set() to report the error and to return
+   that result code after performing any necessary cleanup.
+
+   It is legal for an implementation to register multiple directives
+   in a single invocation (in particular a pair of opening/closing
+   directives), as well as to register directives other than the one
+   requested (if necessary). Regardless of which one(s) it registers,
+   it must return 0 only if it registers one named dname.
+*/
+typedef int (*cmpp_d_autoloader_f)(cmpp *pp, char const *dname, void *state);
+
+/**
+   A c-pp directive "autoloader". See cmpp_d_autoloader_set()
+   and cmpp_d_autoloader_take().
+*/
+struct cmpp_d_autoloader {
+  /** The autoloader callback. */
+  cmpp_d_autoloader_f f;
+  /**
+     Finalizer for this->state. After calling this, if there's any
+     chance that this object might be later used, then it is important
+     that this->state be set to 0 (which this finalizer cannot
+     do). "Best practice" is to bitwise copy cmpp_d_autoloader_empty
+     over any instances immediately after calling dtor().
+  */
+  cmpp_finalizer_f dtor;
+  /**
+     Implementation-specific state, to be passed as the final argument
+     to this->f and this->dtor.
+  */
+  void * state;
+};
+typedef struct cmpp_d_autoloader cmpp_d_autoloader;
+/**
+   Empty-initialized cmpp_d_autoloader instance, intended for
+   const-copy initialization.
+*/
+#define cmpp_d_autoloader_empty_m {.f=0,.dtor=0,.state=0}
+/**
+   Empty-initialized cmpp_d_autoloader instance, intended for
+   non-const-copy initialization.
+*/
+extern const cmpp_d_autoloader cmpp_d_autoloader_empty;
+
+/**
+   Sets pp's "directive autoloader". Each cmpp instance has but a
+   single autoloader but this API is provided so that several
+   instances may be chained from client-side code.
+
+   This function will call the existing autoloader's destructor (if
+   any), invalidating any pointers to its state object.
+
+   If pNew is not NULL then pp's autoloader is set to a bitwise copy
+   of *pNew, otherwise it is zeroed out. This transfers ownership of
+   pNew->state to pp.
+
+   See cmpp_d_autoloader_f()'s docs for how pNew must behave.
+
+   This function has no error conditions but downstream results are
+   undefined if if pNew and an existing autoloader refer to the same
+   dtor/state values (a gateway to double-frees).
+*/
+CMPP_EXPORT void cmpp_d_autoloader_set(cmpp *pp, cmpp_d_autoloader const * pNew);
+
+/**
+   Moves pp's current autoloader state into pOld, transerring
+   ownership of it to the caller.
+
+   This obligates the caller to eventually either pass that same
+   pointer to cmpp_d_autoloader_set() (to transfer ownership back to
+   pp) or to call pOld->dtor() (if it's not NULL), passing it it
+   pOld->state (even if pOld->state is NULL). In either case, all
+   contents of pOld are semantically invalidated and perhaps freed.
+
+   This would normally be a prelude to cmpp_d_autoloader_set() to
+   install a custom, perhaps chained, autoloader.
+*/
+CMPP_EXPORT void cmpp_d_autoloader_take(cmpp *pp, cmpp_d_autoloader * pOld);
+
+/**
+   True only for ' ' and '\t'.
+*/
+CMPP_EXPORT bool cmpp_isspace(int ch);
+
+/**
+   Reassigns *p to the address of the first non-space character at or
+   after the initial *p value. It stops looking if it reaches zEnd.
+
+   If `*p` does not point to memory before zEnd, or is not a part of
+   the same logical string, results are undefined.
+
+
+   Achtung: do not pass this the address of a cmpp_b::z,
+   or similar, as that will effectively corrupt the buffer's
+   memory. To trim a whole buffer, use something like:
+
+   ```
+   cmpp_b ob = cmpp_b_empty;
+   ... populate ob...;
+   // get the trimmed range:
+   unsigned char const *zB = ob.z;
+   unsigned char const *zE = zB + n;
+   cmpp_skip_snl(&zB, zE);
+   assert( zB<=zE );
+   cmpp_skip_snl_trailing(zB, &zE);
+   assert( zE>=zB );
+   printf("trimmed range: [%.*s]\n", (int)(zE-zB), zB);
+   ```
+
+   Those assert()s are not error handling - they're demonstrating
+   invariants of the calls made before them.
+*/
+CMPP_EXPORT void cmpp_skip_space( unsigned char const **p,
+                                  unsigned char const *zEnd );
+
+/**
+   Works just like cmpp_skip_space() but it also
+   skips newlines.
+
+   FIXME (2026-02-21): it does not recognize CRNL pairs as
+   atomic newlines.
+*/
+CMPP_EXPORT void cmpp_skip_snl( unsigned char const **p,
+                                unsigned char const *zEnd );
+
+/**
+   "Trims" trailing cmpp_isspace() characters from the range [zBegin,
+   *p). *p must initially point to one byte after the end of zBegin
+   (i.e. its NUL byte or virtual EOF). Upon return *p will be modified
+   leftwards (if at all) until a non-space is found or *p==zBegin.
+ */
+CMPP_EXPORT void cmpp_skip_space_trailing( unsigned char const *zBegin,
+                                           unsigned char const **p );
+
+/**
+   Works just like cmpp_skip_space_trailing() but
+   skips cmpp_skip_snl() characters.
+
+   FIXME (2026-02-21): it does not recognize CRNL pairs as
+   atomic newlines.
+*/
+CMPP_EXPORT void cmpp_skip_snl_trailing( unsigned char const *zBegin,
+                                         unsigned char const **p );
+
+
+/**
+   Generic array-of-T list memory-reservation routine.
+
+   *list is the input array-of-T. nDesired is the number of entries to
+   reserve (list entry count, not byte length). *nAlloc is the number
+   of entries allocated in the list. sizeOfEntry is the sizeof(T) for
+   each entry in *list. T may be either a value type or a pointer
+   type and sizeofEntry must match, i.e. it must be sizeof(T*) for a
+   list-of-pointers and sizeof(T) for a list-of-objects.
+
+   If pp is not NULL then this function updates pp's error state on
+   error, else it simply returns CMPP_RC_OOM on error. If pp is not
+   NULL then this function is a no-op if called when pp's error state
+   is set, returning that code without other side-effects.
+
+   If nDesired > *nAlloc then *list is reallocated to contain at least
+   nDesired entries, else this function returns without side effects.
+
+   On success *list is re-assigned to the reallocated list memory, all
+   newly-(re)allocated memory is zeroed out, and *nAlloc is updated to
+   the new allocation size of *list (the number of list entries, not
+   the number of bytes).
+
+   On failure neither *list nor *nAlloc are modified.
+
+   Returns 0 on success or CMPP_RC_OOM on error.  Errors generated by
+   this routine are, at least in principle, recoverable (see
+   cmpp_err_set()), though that simply means that the pp object is
+   left in a well-defined state, not that the app can necessarily
+   otherwise recover from an OOM.
+
+   This seemingly-out-of-API-scope routine is in the public API as a
+   convenience for client-level cmpp_dx_f() implementations[^1]. This API
+   internally has an acute need for basic list management and non-core
+   extensions inherit that as well.
+
+   [^1]: this project's own directives are written as if they were
+   client-side whenever feasible. Some require cmpp-internal state to
+   do their jobs, though.
+*/
+CMPP_EXPORT int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
+                                   cmpp_size_t * nAlloc, unsigned sizeOfEntry);
+
+
+/**
+   The current cmpp_api_thunk::apiVersion value.
+   See cmpp_api_thunk_map.
+*/
+#define cmpp_api_thunk_version 20260206
+
+/**
+   A helper for use with cmpp_api_thunk.
+
+   V() defines the API version number.  It invokes
+   V(NAME,TYPE,VERSION) once. NAME is the member name for the
+   cmpp_api_thunk struct. TYPE is an integer type.  VERSION is the
+   cmpp_api_thunk object version. This is initially 0 and will
+   eventually be given a number which increments which new members
+   appended. This is to enable DLLs to check whether their
+   cmpp_api_thunk object has the methods they're looking for.
+
+   Then it invokes F(NAME,RETTYPE,PARAMS) and O(NAME,TYPE)
+   once for each cmpp_api_thunk member in an unspecified order, and
+   and A(VERSION) an arbitrary number of times.
+
+   F() is for functions. O() is for objects, which are exposed here as
+   pointers to those objects so that we don't copy them. A() is
+   injected at each point where a new API version was introduced, and
+   that number (an integer) is its only argument.  A()'s definition
+   can normally be empty.
+
+   In all cases, NAME is the public API symbol name minus the "cmpp_"
+   prefix. RETTYPE is the function return type or object type. PARAMS
+   is the function parameters, wrapped in (...). For O(), TYPE is the
+   const-qualified type of the object referred to by
+   NAME. cmpp_api_thunk necessarily exposes those as pointers, but
+   that pointer is not part of the TYPE argument.
+
+   See cmpp_api_thunk for details.
+
+   In order to help DLLs to not inadvertently use invalid areas of the
+   API object by referencing members which they loading c-pp version
+   does not have, this list must only ever be modified by appending to
+   it. That enables DLLs to check their compile-time
+   cmpp_api_thunk_version against the dx->pp->api->apiVersion. If
+   the runtime version is older (less than) than their compile-time
+   version, the DLL must not access any methods added after
+   dx->pp->api->apiVersion.
+*/
+#define cmpp_api_thunk_map(A,V,F,O)                                   \
+  A(0)                                                                \
+  V(apiVersion,unsigned,cmpp_api_thunk_version)                       \
+  F(mrealloc,void *,(void * p, size_t n))                             \
+  F(malloc,void *,(size_t n))                                         \
+  F(mfree,void,(void *))                                              \
+  F(ctor,int,(cmpp **pp, cmpp_ctor_cfg const *))                      \
+  F(dtor,void,(cmpp *pp))                                             \
+  F(reset,void,(cmpp *pp))                                            \
+  F(check_oom,int,(cmpp * const pp, void const * m))                  \
+  F(is_legal_key,bool,(unsigned char const *, cmpp_size_t n,          \
+                       unsigned char const **))                       \
+  F(define_legacy,int,(cmpp *, const char *,char const *))            \
+  F(define_v2,int,(cmpp *, const char *, char const *))               \
+  F(undef,int,(cmpp *, const char *, unsigned int *))                 \
+  F(define_shadow,int,(cmpp *, char const *, char const *,            \
+                       int64_t *))                                    \
+  F(define_unshadow,int,(cmpp *, char const *, int64_t))              \
+  F(process_string,int,(cmpp *, const char *,                         \
+                        unsigned char const *, cmpp_ssize_t))         \
+  F(process_file,int,(cmpp *, const char *))                          \
+  F(process_stream,int,(cmpp *, const char *,                         \
+                        cmpp_input_f, void *))                        \
+  F(process_argv,int,(cmpp *, int, char const * const *))             \
+  F(err_get,int,(cmpp *, char const **))                              \
+  F(err_set,int,(cmpp *, int, char const *, ...))                     \
+  F(err_set1,int,(cmpp *, int, char const *))                         \
+  F(err_has,int,(cmpp const *))                                       \
+  F(is_safemode,bool,(cmpp const *))                                  \
+  F(sp_begin,int,(cmpp *))                                            \
+  F(sp_commit,int,(cmpp *))                                           \
+  F(sp_rollback,int,(cmpp *))                                         \
+  F(output_f_FILE,int,(void *, void const *, cmpp_size_t))            \
+  F(output_f_fd,int,(void *, void const *, cmpp_size_t))              \
+  F(input_f_FILE,int,(void *, void *, cmpp_size_t *))                 \
+  F(input_f_fd,int,(void *, void *, cmpp_size_t *))                   \
+  F(flush_f_FILE,int,(void *))                                        \
+  F(stream,int,(cmpp_input_f, void *,                                 \
+                cmpp_output_f, void *))                               \
+  F(slurp,int,(cmpp_input_f, void *,                                  \
+               unsigned char **, cmpp_size_t *))                      \
+  F(fopen,cmpp_FILE *,(char const *, char const *))                   \
+  F(fclose,void,(cmpp_FILE * ))                                       \
+  F(outputer_out,int,(cmpp_outputer *, void const *, cmpp_size_t))    \
+  F(outputer_flush,int,(cmpp_outputer *))                             \
+  F(outputer_cleanup,void,(cmpp_outputer *))                          \
+  F(outputer_cleanup_f_FILE,void,(cmpp_outputer *))                   \
+  F(delimiter_set,int,(cmpp *, char const *))                         \
+  F(delimiter_get,void,(cmpp const *, char const **))                 \
+  F(chomp,bool,(unsigned char *, cmpp_size_t *))                      \
+  F(b_clear,void,(cmpp_b *))                                          \
+  F(b_reuse,cmpp_b *,(cmpp_b *))                                      \
+  F(b_swap,void,(cmpp_b *, cmpp_b *))                                 \
+  F(b_reserve,int,(cmpp_b *, cmpp_size_t))                            \
+  F(b_reserve3,int,(cmpp *, cmpp_b *,cmpp_size_t))                    \
+  F(b_append,int,(cmpp_b *, void const *,cmpp_size_t))                \
+  F(b_append4,int,(cmpp *,cmpp_b *,void const *,                      \
+                   cmpp_size_t))                                      \
+  F(b_append_ch,  int,(cmpp_b *, char))                               \
+  F(b_append_i32,int,(cmpp_b *, int32_t))                             \
+  F(b_append_i64,int,(cmpp_b *, int64_t))                             \
+  F(b_chomp,bool,(cmpp_b *))                                          \
+  F(output_f_b,int,(void *, void const *,cmpp_size_t))                \
+  F(outputer_cleanup_f_b,void,(cmpp_outputer *self))                  \
+  F(version,char const *,(void))                                      \
+  F(tt_cstr,char const *,(int tt))                                    \
+  F(dx_err_set,int,(cmpp_dx *dx, int rc, char const *zFmt, ...))      \
+  F(dx_next,int,(cmpp_dx * dx, bool * pGotOne))                       \
+  F(dx_process,int,(cmpp_dx * dx))                                    \
+  F(dx_consume,int,(cmpp_dx *, cmpp_outputer *,                       \
+                    cmpp_d const *const *, unsigned, cmpp_flag32_t))  \
+  F(dx_consume_b,int,(cmpp_dx *, cmpp_b *, cmpp_d const * const *,    \
+                      unsigned, cmpp_flag32_t))                       \
+  F(arg_parse,int,(cmpp_dx * dx, cmpp_arg *,                          \
+                   unsigned char const **, unsigned char const *,     \
+                   unsigned char ** , unsigned char const * ))        \
+  F(arg_strdup,char *,(cmpp *pp, cmpp_arg const *arg))                \
+  F(arg_to_b,int,(cmpp_dx * dx, cmpp_arg const *arg,                  \
+                  cmpp_b * os, cmpp_flag32_t flags))                  \
+  F(errno_rc,int,(int errNo, int dflt))                               \
+  F(d_register,int,(cmpp * pp, cmpp_d_reg const * r, cmpp_d **pOut))  \
+  F(dx_f_dangling_closer,void,(cmpp_dx *dx))                          \
+  F(dx_out_raw,int,(cmpp_dx * dx, void const *z, cmpp_size_t n))      \
+  F(dx_out_expand,int,(cmpp_dx const * dx, cmpp_outputer * pOut,      \
+                       unsigned char const * zFrom,  cmpp_size_t n,   \
+                       cmpp_atpol_e policy))                          \
+  F(dx_outf,int,(cmpp_dx *dx, char const *zFmt, ...))                 \
+  F(dx_delim,char const *,(cmpp_dx const *dx))                        \
+  F(atpol_from_str,cmpp_atpol_e,(cmpp * pp, char const *z))           \
+  F(atpol_get,cmpp_atpol_e,(cmpp const * const pp))                   \
+  F(atpol_set,int,(cmpp * const pp, cmpp_atpol_e pol))                \
+  F(atpol_push,int,(cmpp * pp, cmpp_atpol_e pol))                     \
+  F(atpol_pop,void,(cmpp * pp))                                       \
+  F(unpol_from_str,cmpp_unpol_e,(cmpp * pp,char const *z))            \
+  F(unpol_get,cmpp_unpol_e,(cmpp const * const pp))                   \
+  F(unpol_set,int,(cmpp * const pp, cmpp_unpol_e pol))                \
+  F(unpol_push,int,(cmpp * pp, cmpp_unpol_e pol))                     \
+  F(unpol_pop,void,(cmpp * pp))                                       \
+  F(path_search,char *,(cmpp *pp, char const *zPath, char pathSep,    \
+                        char const *zBaseName, char const *zExt))     \
+  F(args_parse,int,(cmpp_dx * dx, cmpp_args * pOut,                   \
+                    unsigned char const * zInBegin,                   \
+                    cmpp_ssize_t nIn, cmpp_flag32_t flags))           \
+  F(args_cleanup,void,(cmpp_args *a))                                 \
+  F(dx_args_clone,int,(cmpp_dx * dx, cmpp_args *pOut))                \
+  F(popen,int,(cmpp *, unsigned char const *, cmpp_flag32_t,          \
+               cmpp_popen_t *))                                       \
+  F(popenv,int,(cmpp *pp, char * const * azCmd, cmpp_flag32_t flags,  \
+                cmpp_popen_t *po))                                    \
+  F(pclose,int,(cmpp_popen_t *po))                                    \
+  F(popen_args,int,(cmpp_dx *, cmpp_args const *, cmpp_popen_t *))    \
+  F(kav_each,int, (cmpp_dx *,unsigned char const *, cmpp_ssize_t,     \
+                   cmpp_kav_each_f, void *, cmpp_flag32_t))           \
+  F(d_autoloader_set,void,(cmpp *pp, cmpp_d_autoloader const * pNew)) \
+  F(d_autoloader_take,void,(cmpp *pp, cmpp_d_autoloader * pOld))      \
+  F(isspace,bool,(int ch))                                            \
+  F(skip_space,void,(unsigned char const **, unsigned char const *))  \
+  F(skip_snl,void,(unsigned char const **, unsigned char const *))    \
+  F(skip_space_trailing,void,(unsigned char const *zBegin,            \
+                              unsigned char const **p))               \
+  F(skip_snl_trailing,void,(unsigned char const *zBegin,              \
+                            unsigned char const **p))                 \
+  F(array_reserve,int,(cmpp *pp, void **list, cmpp_size_t nDesired,   \
+                       cmpp_size_t * nAlloc, unsigned sizeOfEntry))   \
+  F(module_load,int,(cmpp *, char const *,char const *))              \
+  F(module_dir_add,int,(cmpp *, const char *))                        \
+  O(outputer_FILE,cmpp_outputer const)                                \
+  O(outputer_b,cmpp_outputer const)                                   \
+  O(outputer_empty,cmpp_outputer const)                               \
+  O(b_empty,cmpp_b const)                                             \
+  A(20251116)                                                         \
+  F(next_chunk,bool,(unsigned char const **,unsigned char const *,    \
+                     unsigned char,cmpp_size_t*))                     \
+  A(20251118)                                                         \
+  F(atdelim_get,void,(cmpp const *,char const **,char const **))      \
+  F(atdelim_set,int,(cmpp *,char const *,char const *))               \
+  F(atdelim_push,int,(cmpp *,char const *,char const *))              \
+  F(atdelim_pop,int,(cmpp *))                                         \
+  A(20251224)                                                         \
+  F(dx_pos_save,void,(cmpp_dx const *, cmpp_dx_pos *))                \
+  F(dx_pos_restore,void,(cmpp_dx *, cmpp_dx_pos const *))             \
+  A(20260130)                                                         \
+  F(dx_is_call,bool,(cmpp_dx * const))                                \
+  A(20260206)                                                         \
+  F(b_borrow,cmpp_b *,(cmpp *dx))                                     \
+  F(b_return,void,(cmpp *dx, cmpp_b*))                                \
+  A(1+cmpp_api_thunk_version)
+
+
+/**
+   Callback signature for cmpp module import routines.
+
+   This is called by the library after having first encountering this
+   module (typically after looking for it in a DLL, but static
+   instances are supported).
+
+   The primary intended purpose of this interface is for
+   implementations to call cmpp_d_register() (any number of times). It
+   is also legal to use APIs which set or query defines. This
+   interface is not intended to interact with pp's I/O in any way
+   (that's the job of the directives which these functions
+   register). Violating that will invoke undefined results, perhaps
+   stepping on the toes of any being-processed directive which
+   triggered the dynamic load of this directive.
+
+   Errors in module initialization must be reported via cmpp_err_set()
+   and that code must be returned.
+
+   Implementations must typically call cmpp_api_init(pp) as their
+   first operation.
+
+   See the files named d-*.c in libcmpp's source tree for examples.
+*/
+typedef int (*cmpp_module_init_f)(cmpp * pp);
+
+/**
+   Holds information for mapping a cmpp_module_init_f to a name.
+   Its purpose is to get installed by the CMPP_MODULE_xxx family of
+   macros and referenced later via a module-loading mechanism.
+*/
+struct cmpp_module{
+  /**
+     Symbolic name of the module.
+  */
+  char const * name;
+
+  /**
+     The initialization routine for the module.
+  */
+  cmpp_module_init_f init;
+};
+
+/** Convenience typedef. */
+typedef struct cmpp_module cmpp_module;
+
+/** @def CMPP_MODULE_DECL
+
+   Declares an extern (cmpp_module*) symbol called
+   cmpp_module__#\#CNAME.
+
+   Use CMPP_MODULE_IMPL2() or CMPP_MODULE_IMPL3() to create the
+   matching implementation code.
+
+   This macro should be used in the C or H file for a loadable module.
+   It may be compined in a file with a single CMPP_MODULE_IMPL_SOLO()
+   declaration with the same name, such that the module can be loaded
+   both with and without the explicit symbol name.
+*/
+#define CMPP_MODULE_DECL(CNAME)                            \
+    extern const cmpp_module * cmpp_module__##CNAME
+
+/** @def CMPP_MODULE_IMPL
+
+   Intended to be used to implement module declarations.  If a module
+   has both C and H files, CMPP_MODULE_DECL(CNAME) should be used in the
+   H file and CMPP_MODULE_IMPL2() should be used in the C file. If the
+   DLL has only a C file (or no public H file), CMPP_MODULE_DECL is
+   unnecessary.
+
+   If the module's human-use name is a legal C identifier,
+   CMPP_MODULE_IMPL2() is slightly easier to use than this macro.
+
+   Implements a static cmpp_module object named
+   cmpp_module__#\#CNAME#\#_impl and a non-static
+   (cmpp_module*) named cmpp_module__#\#CNAME which points to
+   cmpp_module__#\#CNAME#\#_impl. (The latter symbol may optionally be
+   declared in a header file via CMPP_MODULE_DECL.) NAME is used as
+   the cmpp_module::name value.
+
+   INIT_F must be a cmpp_module_init_f() function pointer. That function
+   is called when cmpp_module_load() loads the module.
+
+   This macro may be combined in a file with a single
+   CMPP_MODULE_IMPL_SOLO() declaration using the same CNAME value,
+   such that the module can be loaded both with and without the
+   explicit symbol name.
+
+   Example usage, in a module's header file, if any:
+
+   ```
+   CMPP_MODULE_DECL(mymodule);
+   ```
+
+   (The declaration is not strictly necessary - it is more of a matter
+   of documentation.)
+
+   And in the C file:
+
+   ```
+   CMPP_MODULE_IMPL3(mymodule,"mymodule",mymodule_install);
+   // OR:
+   CMPP_MODULE_IMPL2(mymodule,mymodule_install);
+   ```
+
+   If it will be the only module in the target DLL, one can also add
+   this:
+
+   ```
+   CMPP_MODULE_IMPL2(mymodule,mymodule_install);
+   // _OR_ (every so slightly different):
+   CMPP_MODULE_STANDALONE_IMPL2(mymodule,mymodule_install);
+   ```
+
+   Which simplifies client-side module loading by allowing them to
+   leave out the module name when loading, but that approach only
+   works if modules are compiled one per DLL (as opposed to being
+   packaged together in one DLL).
+
+   @see CMPP_MODULE_DECL
+   @see CMPP_MODULE_IMPL_SOLO
+*/
+#define CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F)        \
+  static const cmpp_module                 \
+  cmpp_module__##CNAME##_impl = { NAME, INIT_F };   \
+  const cmpp_module *                      \
+  cmpp_module__##CNAME = &cmpp_module__##CNAME##_impl
+
+/** @def CMPP_MODULE_IMPL3
+
+    A simplier form of CMPP_MODULE_IMPL3() for cases where a module name
+    is a legal C symbol name.
+*/
+#define CMPP_MODULE_IMPL2(CNAME,INIT_F) \
+  CMPP_MODULE_IMPL3(CNAME,#CNAME,INIT_F)
+
+/** @def CMPP_MODULE_IMPL_SOLO
+
+   Implements a static cmpp_module symbol called
+   cmpp_module1_impl and a non-static (cmpp_module*) named
+   cmpp_module1 which points to cmpp_module1_impl
+
+   INIT_F must be a cmpp_module_init_f.
+
+   This macro must only be used in the C file for a loadable module
+   when that module is to be the only one in the resuling DLL. Do not
+   use it when packaging multiple modules into one DLL: use
+   CMPP_MODULE_IMPL for those cases (CMPP_MODULE_IMPL can also be used
+   together with this macro).
+
+   @see CMPP_MODULE_IMPL
+   @see CMPP_MODULE_DECL
+   @see CMPP_MODULE_STANDALONE_IMPL
+*/
+#define CMPP_MODULE_IMPL_SOLO(NAME,INIT_F)        \
+  static const cmpp_module               \
+  cmpp_module1_impl = { NAME, INIT_F };           \
+  const cmpp_module * cmpp_module1 = &cmpp_module1_impl
+/** @def CMPP_MODULE_STANDALONE_IMPL
+
+    CMPP_MODULE_STANDALONE_IMPL2() works like CMPP_MODULE_IMPL_SOLO()
+    but is only fully expanded if the preprocessor variable
+    CMPP_MODULE_STANDALONE is defined (to any value).  If
+    CMPP_MODULE_STANDALONE is not defined, this macro expands to a
+    dummy placeholder which does nothing (but has to expand to
+    something to avoid leaving a trailing semicolon in the C code,
+    which upsets the compiler (the other alternative would be to not
+    require a semicolon after the macro call, but that upsets emacs'
+    sense of indentation (and keeping emacs happy is more important
+    than keeping compilers happy (all of these parens are _not_ a
+    reference to emacs lisp, by the way)))).
+
+    This macro may be used in the same source file as
+    CMPP_MODULE_IMPL.
+
+    The intention is that DLLs prefer this option over
+    CMPP_MODULE_IMPL_SOLO, to allow that the DLLs can be built as
+    standalone DLLs, multi-plugin DLLs, and compiled directly into a
+    project (in which case the code linking it in needs to resolve and
+    call the cmpp_module entry for each built-in module).
+
+   @see CMPP_MODULE_IMPL_SOLO
+   @see CMPP_MODULE_REGISTER
+*/
+#if defined(CMPP_MODULE_STANDALONE)
+#  define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \
+  CMPP_MODULE_IMPL_SOLO(NAME,INIT_F)
+//arguably too much magic in one place:
+//#  if !defined(CMPP_API_THUNK)
+//#    define CMPP_API_THUNK
+//#  endif
+#else
+#  define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \
+  extern void cmpp_module__dummy_does_not_exist__(void)
+#endif
+
+/** @def CMPP_MODULE_REGISTER3
+
+   Performs all the necessary setup for registering a loadable module,
+   including declaration and definition. NAME is the stringified name
+   of the module. This is normally called immediately after defining
+   the plugin's init func (which is passed as the 3rd argument to this
+   macro).
+
+   See CMPP_MODULE_IMPL3() and CMPP_MODULE_STANDALONE_IMPL2() for
+   the fine details.
+*/
+#define CMPP_MODULE_REGISTER3(CNAME,NAME,INIT_F) \
+  CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F);          \
+  CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F)
+
+/**
+   Slight convenience form of CMPP_MODULE_REGISTER3() which assumes a
+   registration function name of cpp_ext_${CNAME}_register().
+*/
+#define CMPP_MODULE_REGISTER2(CNAME,NAME) \
+  CMPP_MODULE_REGISTER3(CNAME,NAME,cmpp_module__ ## CNAME ## _register)
+
+/**
+   Slight convenience form of CMPP_MODULE_REGISTER2() for cases when
+   CNAME and NAME are the same.
+*/
+#define CMPP_MODULE_REGISTER1(CNAME) \
+  CMPP_MODULE_REGISTER3(CNAME,#CNAME,cmpp_module__ ## CNAME ## _register)
+
+/**
+   This looks for a DLL file named fname.  If found, it is dlopen()ed
+   (or equivalent) and searched for a symbol named symName. If found,
+   it is assumed to be a cmpp_module instance and its init() method is
+   invoked.
+
+   If fname is NULL then the module is looked up in the
+   currently-running program.
+
+   If symName is NULL then the name "cmpp_module1" is assumed, which
+   is the name used by CMPP_MODULE_IMPL_SOLO() and friends (for use
+   when a module is the only one in its DLL).
+
+   If no match is found, or there's a problem loading the DLL or
+   resolving the name, non-0 is returned. Similarly, if the init()
+   method fails, non-0 is returned.
+
+   The file name is searched using the cmpp_module_dir_add() path, and
+   if fname is an exact match, or an exact when the system's
+   conventional DLL file extension is appended to it, that is used
+   rather than any potential match from the search path.
+
+   On error, pp's error state will contain more information. It's
+   indeterminate which errors from this API are recoverable.
+
+   This function is a no-op if called when pp's error state is set,
+   returning that code.
+
+   If built without module-loading support then this will always
+   fail with CMPP_RC_UNSUPPORTED.
+*/
+CMPP_EXPORT int cmpp_module_load(cmpp * pp, char const * fname,
+                                 char const * symName);
+
+/**
+   Adds the directory or directories listed in zDirs to the search
+   path used by cmpp_module_load(). The entries are expected to be
+   either colon- or semicolon-delimited, depending on the platform the
+   library was built for.
+
+   If zDirs is NULL and pp's library path is empty then it looks for
+   the environment variable CMPP_MODULE_PATH. If that is set, it is
+   used in place of zDirs, otherwise the library's compile-time
+   default is used (as set by the CMPP_MODULE_PATH compile-time value,
+   which defaults to ".:$prefix/lib/cmpp" in the canonical builds).
+   This should only be done once per cmpp instance, as the path will
+   otherwise be extended each time. (The current list structure does
+   not make it easy to recognize duplicates.)
+
+   Returns 0 on success or if zDirs is empty. Returns CMPP_RC_OOM on
+   allocation error (ostensibly recoverable - see cmpp_err_set()).
+
+   This is a no-op if called when pp has error state, returning that
+   code without other side-effects.
+
+   If modules are not enabled then this function is a no-op and always
+   returns CMPP_RC_UNSUPPORTED _without_ setting pp's error state (as
+   it's not an error, per se). That can typically be ignored as a
+   non-error.
+*/
+CMPP_EXPORT int cmpp_module_dir_add(cmpp *pp, const char * zDirs);
+
+
+/**
+   State for a cmpp_dx_pimpl which we need in order to snapshot the
+   parse position for purposes of restoring it later. This is
+   basically to support that #query can contain other #query
+   directives, but this same capability is required by any directives
+   which want to both process directives in their content block and
+   loop over the content block.
+*/
+struct cmpp_dx_pos {
+  /** Current parse pos. */
+  unsigned char const *z;
+  /** Current line number. */
+  cmpp_size_t lineNo;
+};
+typedef struct cmpp_dx_pos cmpp_dx_pos;
+#define cmpp_dx_pos_empty_m {.z=0,.lineNo=0U}//,.dline=CmppDLine_empty_m}
+
+/**
+   Stores dx's current input position into pos. pos gets completely
+   initialized by this routine - it need not (in contrast to many
+   other functions in this library) be cleanly initialized by the
+   caller first.
+*/
+CMPP_EXPORT void cmpp_dx_pos_save(cmpp_dx const * dx, cmpp_dx_pos *pos);
+
+/**
+   Restores dx's input position from pos. Results are undefined if pos
+   is not populated with the result of having passed the same dx/pos
+   pointer combination to cmpp_dx_pos_save().
+*/
+CMPP_EXPORT void cmpp_dx_pos_restore(cmpp_dx * dx, cmpp_dx_pos const * pos);
+
+/**
+   A "thunk" for use with loadable modules, encapsulating all of the
+   functions from the public cmpp API into an object. This allows
+   loadable modules to call into the cmpp API if the binary which
+   loads them not built in such a way that it exports libcmpp's
+   symbols to the DLL. (On Linux systems, that means if it's not
+   linked with -rdynamic.)
+
+   For every public cmpp function, this struct has a member with the
+   same signature and name, minus the "cmpp_" name prefix. Thus
+   cmpp_foo(...) is accessible as api->foo(...).
+
+   Object-type exports, e.g. cmpp_b_empty, are exposed here as
+   pointers instead of objects. The CMPP_API_THUNK-installed API
+   wrapper macros account for that.
+
+   There is only one instance of this class and it gets passed into
+   cmpp_module_init_f() methods. It is also assigned to the
+   cmpp_dx::api member of cmpp_dx instances which get passed to
+   cmpp_dx_f() implementations.
+
+   Loadable modules "should" use this interface to access the API,
+   rather than the global symbols. If they don't then the module may,
+   depending on how the loading application was linked, throw
+   unresolved symbols errors when loading.
+*/
+struct cmpp_api_thunk {
+#define A(VER)
+#define V(N,T,VER) T N;
+#define F(N,T,P) T (*N)P;
+#define O(N,T) T * const N;
+  cmpp_api_thunk_map(A,V,F,O)
+#undef F
+#undef O
+#undef V
+#undef A
+};
+
+/**
+   For loadable modules to be able portably access the cmpp API,
+   without requiring that their loading binary be linked with
+   -rdynamic, we need a "thunk". The API exposes cmpp_api_thunk
+   for that purpose. The following macros set up the thunk for
+   a given compilation unit. They are intended to only be used
+   by loadable modules, not generic client code.
+
+   Before including this header, define CMPP_API_THUNK with no value
+   and/or define CMPP_API_THUNK_NAME to a C symbol name.  The latter
+   macro implies the former and defines the name of the static symbol
+   to be the local cmpp_api_thunk instance, defaulting to cmppApi.
+
+   The first line of a module's registration function should then be:
+
+   cmpp_api_init(pp);
+
+   where pp is the name of the sole argument to the registration
+   callback. After that is done, the cmpp_...() APIs may be used via
+   the macros defined below, all of which route through the thunk
+   object.
+*/
+#if defined(CMPP_API_THUNK) || defined(CMPP_API_THUNK_NAME)
+#  if !defined(CMPP_API_THUNK)
+#    define CMPP_API_THUNK
+#  endif
+#  if !defined(CMPP_API_THUNK_NAME)
+#    define CMPP_API_THUNK_NAME cmppApi
+#  endif
+#  if !defined(CMPP_API_THUNK__defined)
+#  define CMPP_API_THUNK__defined
+static cmpp_api_thunk const * CMPP_API_THUNK_NAME = 0;
+#  endif
+/**
+   cmpp_api_init() must be invoked from the module's registration
+   function, passed the only argument to that function. It sets the
+   global symbol CMPP_API_THUNK_NAME to its argument. From that point
+   on, the thunk's API is accessible via cmpp_foo macros which proxy
+   theThunk->foo.
+
+   It is safe to call this from, e.g. a cmpp_dx_f() implementation, as
+   it will always have the same pointer, so long as it is not passed
+   NULL, which would make the next cmpp_...() call segfault.
+*/
+#  if !defined(CMPP_API_THUNK__assigned)
+#  define CMPP_API_THUNK__assigned
+#    define cmpp_api_init(PP) CMPP_API_THUNK_NAME = (PP)->api
+#  else
+#    define cmpp_api_init(PP) (void)(PP)/*CMPP_API_THUNK_NAME*/
+#  endif
+/* What follows is generated code from c-pp's (#pragma api-thunk). */
+/* Thunk APIs which follow are available as of version 0... */
+#define cmpp_mrealloc CMPP_API_THUNK_NAME->mrealloc
+#define cmpp_malloc CMPP_API_THUNK_NAME->malloc
+#define cmpp_mfree CMPP_API_THUNK_NAME->mfree
+#define cmpp_ctor CMPP_API_THUNK_NAME->ctor
+#define cmpp_dtor CMPP_API_THUNK_NAME->dtor
+#define cmpp_reset CMPP_API_THUNK_NAME->reset
+#define cmpp_check_oom CMPP_API_THUNK_NAME->check_oom
+#define cmpp_is_legal_key CMPP_API_THUNK_NAME->is_legal_key
+#define cmpp_define_legacy CMPP_API_THUNK_NAME->define_legacy
+#define cmpp_define_v2 CMPP_API_THUNK_NAME->define_v2
+#define cmpp_undef CMPP_API_THUNK_NAME->undef
+#define cmpp_define_shadow CMPP_API_THUNK_NAME->define_shadow
+#define cmpp_define_unshadow CMPP_API_THUNK_NAME->define_unshadow
+#define cmpp_process_string CMPP_API_THUNK_NAME->process_string
+#define cmpp_process_file CMPP_API_THUNK_NAME->process_file
+#define cmpp_process_stream CMPP_API_THUNK_NAME->process_stream
+#define cmpp_process_argv CMPP_API_THUNK_NAME->process_argv
+#define cmpp_err_get CMPP_API_THUNK_NAME->err_get
+#define cmpp_err_set CMPP_API_THUNK_NAME->err_set
+#define cmpp_err_set1 CMPP_API_THUNK_NAME->err_set1
+#define cmpp_err_has CMPP_API_THUNK_NAME->err_has
+#define cmpp_is_safemode CMPP_API_THUNK_NAME->is_safemode
+#define cmpp_sp_begin CMPP_API_THUNK_NAME->sp_begin
+#define cmpp_sp_commit CMPP_API_THUNK_NAME->sp_commit
+#define cmpp_sp_rollback CMPP_API_THUNK_NAME->sp_rollback
+#define cmpp_output_f_FILE CMPP_API_THUNK_NAME->output_f_FILE
+#define cmpp_output_f_fd CMPP_API_THUNK_NAME->output_f_fd
+#define cmpp_input_f_FILE CMPP_API_THUNK_NAME->input_f_FILE
+#define cmpp_input_f_fd CMPP_API_THUNK_NAME->input_f_fd
+#define cmpp_flush_f_FILE CMPP_API_THUNK_NAME->flush_f_FILE
+#define cmpp_stream CMPP_API_THUNK_NAME->stream
+#define cmpp_slurp CMPP_API_THUNK_NAME->slurp
+#define cmpp_fopen CMPP_API_THUNK_NAME->fopen
+#define cmpp_fclose CMPP_API_THUNK_NAME->fclose
+#define cmpp_outputer_out CMPP_API_THUNK_NAME->outputer_out
+#define cmpp_outputer_flush CMPP_API_THUNK_NAME->outputer_flush
+#define cmpp_outputer_cleanup CMPP_API_THUNK_NAME->outputer_cleanup
+#define cmpp_outputer_cleanup_f_FILE CMPP_API_THUNK_NAME->outputer_cleanup_f_FILE
+#define cmpp_delimiter_set CMPP_API_THUNK_NAME->delimiter_set
+#define cmpp_delimiter_get CMPP_API_THUNK_NAME->delimiter_get
+#define cmpp_chomp CMPP_API_THUNK_NAME->chomp
+#define cmpp_b_clear CMPP_API_THUNK_NAME->b_clear
+#define cmpp_b_reuse CMPP_API_THUNK_NAME->b_reuse
+#define cmpp_b_swap CMPP_API_THUNK_NAME->b_swap
+#define cmpp_b_reserve CMPP_API_THUNK_NAME->b_reserve
+#define cmpp_b_reserve3 CMPP_API_THUNK_NAME->b_reserve3
+#define cmpp_b_append CMPP_API_THUNK_NAME->b_append
+#define cmpp_b_append4 CMPP_API_THUNK_NAME->b_append4
+#define cmpp_b_append_ch CMPP_API_THUNK_NAME->b_append_ch
+#define cmpp_b_append_i32 CMPP_API_THUNK_NAME->b_append_i32
+#define cmpp_b_append_i64 CMPP_API_THUNK_NAME->b_append_i64
+#define cmpp_b_chomp CMPP_API_THUNK_NAME->b_chomp
+#define cmpp_output_f_b CMPP_API_THUNK_NAME->output_f_b
+#define cmpp_outputer_cleanup_f_b CMPP_API_THUNK_NAME->outputer_cleanup_f_b
+#define cmpp_version CMPP_API_THUNK_NAME->version
+#define cmpp_tt_cstr CMPP_API_THUNK_NAME->tt_cstr
+#define cmpp_dx_err_set CMPP_API_THUNK_NAME->dx_err_set
+#define cmpp_dx_next CMPP_API_THUNK_NAME->dx_next
+#define cmpp_dx_process CMPP_API_THUNK_NAME->dx_process
+#define cmpp_dx_consume CMPP_API_THUNK_NAME->dx_consume
+#define cmpp_dx_consume_b CMPP_API_THUNK_NAME->dx_consume_b
+#define cmpp_arg_parse CMPP_API_THUNK_NAME->arg_parse
+#define cmpp_arg_strdup CMPP_API_THUNK_NAME->arg_strdup
+#define cmpp_arg_to_b CMPP_API_THUNK_NAME->arg_to_b
+#define cmpp_errno_rc CMPP_API_THUNK_NAME->errno_rc
+#define cmpp_d_register CMPP_API_THUNK_NAME->d_register
+#define cmpp_dx_f_dangling_closer CMPP_API_THUNK_NAME->dx_f_dangling_closer
+#define cmpp_dx_out_raw CMPP_API_THUNK_NAME->dx_out_raw
+#define cmpp_dx_out_expand CMPP_API_THUNK_NAME->dx_out_expand
+#define cmpp_dx_outf CMPP_API_THUNK_NAME->dx_outf
+#define cmpp_dx_delim CMPP_API_THUNK_NAME->dx_delim
+#define cmpp_atpol_from_str CMPP_API_THUNK_NAME->atpol_from_str
+#define cmpp_atpol_get CMPP_API_THUNK_NAME->atpol_get
+#define cmpp_atpol_set CMPP_API_THUNK_NAME->atpol_set
+#define cmpp_atpol_push CMPP_API_THUNK_NAME->atpol_push
+#define cmpp_atpol_pop CMPP_API_THUNK_NAME->atpol_pop
+#define cmpp_unpol_from_str CMPP_API_THUNK_NAME->unpol_from_str
+#define cmpp_unpol_get CMPP_API_THUNK_NAME->unpol_get
+#define cmpp_unpol_set CMPP_API_THUNK_NAME->unpol_set
+#define cmpp_unpol_push CMPP_API_THUNK_NAME->unpol_push
+#define cmpp_unpol_pop CMPP_API_THUNK_NAME->unpol_pop
+#define cmpp_path_search CMPP_API_THUNK_NAME->path_search
+#define cmpp_args_parse CMPP_API_THUNK_NAME->args_parse
+#define cmpp_args_cleanup CMPP_API_THUNK_NAME->args_cleanup
+#define cmpp_dx_args_clone CMPP_API_THUNK_NAME->dx_args_clone
+#define cmpp_popen CMPP_API_THUNK_NAME->popen
+#define cmpp_popenv CMPP_API_THUNK_NAME->popenv
+#define cmpp_pclose CMPP_API_THUNK_NAME->pclose
+#define cmpp_popen_args CMPP_API_THUNK_NAME->popen_args
+#define cmpp_kav_each CMPP_API_THUNK_NAME->kav_each
+#define cmpp_d_autoloader_set CMPP_API_THUNK_NAME->d_autoloader_set
+#define cmpp_d_autoloader_take CMPP_API_THUNK_NAME->d_autoloader_take
+#define cmpp_isspace CMPP_API_THUNK_NAME->isspace
+#define cmpp_isnl CMPP_API_THUNK_NAME->isnl
+#define cmpp_issnl CMPP_API_THUNK_NAME->issnl
+#define cmpp_skip_space CMPP_API_THUNK_NAME->skip_space
+#define cmpp_skip_snl CMPP_API_THUNK_NAME->skip_snl
+#define cmpp_skip_space_trailing CMPP_API_THUNK_NAME->skip_space_trailing
+#define cmpp_skip_snl_trailing CMPP_API_THUNK_NAME->skip_snl_trailing
+#define cmpp_array_reserve CMPP_API_THUNK_NAME->array_reserve
+#define cmpp_module_load CMPP_API_THUNK_NAME->module_load
+#define cmpp_module_dir_add CMPP_API_THUNK_NAME->module_dir_add
+#define cmpp_outputer_FILE (*CMPP_API_THUNK_NAME->outputer_FILE)
+#define cmpp_outputer_b (*CMPP_API_THUNK_NAME->outputer_b)
+#define cmpp_outputer_empty (*CMPP_API_THUNK_NAME->outputer_empty)
+#define cmpp_b_empty (*CMPP_API_THUNK_NAME->b_empty)
+/* Thunk APIs which follow are available as of version 20251116... */
+#define cmpp_next_chunk CMPP_API_THUNK_NAME->next_chunk
+/* Thunk APIs which follow are available as of version 20251118... */
+#define cmpp_atdelim_get CMPP_API_THUNK_NAME->atdelim_get
+#define cmpp_atdelim_set CMPP_API_THUNK_NAME->atdelim_set
+#define cmpp_atdelim_push CMPP_API_THUNK_NAME->atdelim_push
+#define cmpp_atdelim_pop CMPP_API_THUNK_NAME->atdelim_pop
+/* Thunk APIs which follow are available as of version 20251224... */
+#define cmpp_dx_pos_save CMPP_API_THUNK_NAME->dx_pos_save
+#define cmpp_dx_pos_restore CMPP_API_THUNK_NAME->dx_pos_restore
+/* Thunk APIs which follow are available as of version 20260130... */
+#define cmpp_dx_is_call CMPP_API_THUNK_NAME->dx_is_call
+/* Thunk APIs which follow are available as of version 20260206... */
+#define cmpp_b_borrow CMPP_API_THUNK_NAME->b_borrow
+#define cmpp_b_return CMPP_API_THUNK_NAME->b_return
+
+
+#else /* not CMPP_API_THUNK */
+/**
+   cmpp_api_init() is a no-op when not including a file-local API
+   thunk.
+*/
+#  define cmpp_api_init(PP) (void)0
+#endif /* CMPP_API_THUNK */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+#endif /* include guard */
+#endif /* NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED */
+#if !defined(NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED)
+#define NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED
+/**
+   This file houses declarations and macros for the private/internal
+   libcmpp APIs.
+*/
+#include "sqlite3.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+
+/* write() and friends */
+#if defined(_WIN32) || defined(WIN32)
+#  include <io.h>
+#  include <fcntl.h>
+#  ifndef access
+#    define access(f,m) _access((f),(m))
+#  endif
+#else
+#  include <unistd.h>
+#  include <sys/wait.h>
+#endif
+
+#ifndef CMPP_DEFAULT_DELIM
+#define CMPP_DEFAULT_DELIM "##"
+#endif
+
+#ifndef CMPP_ATSIGN
+#define CMPP_ATSIGN (unsigned char)'@'
+#endif
+
+#ifndef CMPP_MODULE_PATH
+#define CMPP_MODULE_PATH "."
+#endif
+
+#if defined(NDEBUG)
+#define cmpp__staticAssert(NAME,COND) (void)1
+#else
+#define cmpp__staticAssert(NAME,COND) \
+  static const char staticAssert_ ## NAME[ (COND) ? 1 : -1 ] = {0}; \
+  (void)staticAssert_ ## NAME
+#endif
+
+#if defined(CMPP_OMIT_ALL_UNSAFE)
+#undef  CMPP_OMIT_D_PIPE
+#define CMPP_OMIT_D_PIPE
+#undef  CMPP_OMIT_D_DB
+#define CMPP_OMIT_D_DB
+#undef  CMPP_OMIT_D_INCLUDE
+#define CMPP_OMIT_D_INCLUDE
+#undef  CMPP_OMIT_D_MODULE
+#define CMPP_OMIT_D_MODULE
+#endif
+
+#if !defined(CMPP_VERSION)
+#error "exporting CMPP_VERSION to have been set up"
+#endif
+
+#define CMPP__DB_MAIN_NAME "cmpp"
+
+#if defined(CMPP_AMALGAMATION)
+#define CMPP_PRIVATE static
+#else
+#define CMPP_PRIVATE
+#endif
+
+#if CMPP_PLATFORM_IS_WASM
+#  define CMPP_PLATFORM_IS_WINDOWS 0
+#  define CMPP_PLATFORM_IS_UNIX 0
+#  define CMPP_PLATFORM_PLATFORM "wasm"
+#  define CMPP_PATH_SEPARATOR ':'
+#  define CMPP__EXPORT_NAMED(X) __attribute__((export_name(#X),used,visibility("default")))
+// See also:
+//__attribute__((export_name("theExportedName"), used, visibility("default")))
+#  define CMPP_OMIT_FILE_IO /* potential todo but with a large footprint */
+#  if !defined(CMPP_PLATFORM_EXT_DLL)
+#    define CMPP_PLATFORM_EXT_DLL ""
+#  endif
+#else
+//#  define CMPP_WASM_EXPORT
+#  define CMPP__EXPORT_NAMED(X)
+#  if defined(_WIN32) || defined(WIN32)
+#    define CMPP_PLATFORM_IS_WINDOWS 1
+#    define CMPP_PLATFORM_IS_UNIX 0
+#    define CMPP_PLATFORM_PLATFORM "windows"
+#    define CMPP_PATH_SEPARATOR ';'
+//#  include <io.h>
+#  elif defined(__MINGW32__) || defined(__MINGW64__)
+#    define CMPP_PLATFORM_IS_WINDOWS 1
+#    define CMPP_PLATFORM_IS_UNIX 0
+#    define CMPP_PLATFORM_PLATFORM "windows"
+#    define CMPP_PATH_SEPARATOR ':' /*?*/
+#  elif defined(__CYGWIN__)
+#    define CMPP_PLATFORM_IS_WINDOWS 0
+#    define CMPP_PLATFORM_IS_UNIX 1
+#    define CMPP_PLATFORM_PLATFORM "unix"
+#    define CMPP_PATH_SEPARATOR ':'
+#  else
+#    define CMPP_PLATFORM_IS_WINDOWS 0
+#    define CMPP_PLATFORM_IS_UNIX 1
+#    define CMPP_PLATFORM_PLATFORM "unix"
+#    define CMPP_PATH_SEPARATOR ':'
+#  endif
+#endif
+
+#define CMPP__EXPORT(RETTYPE,NAME) CMPP__EXPORT_NAMED(NAME) RETTYPE NAME
+
+#if !defined(CMPP_PLATFORM_EXT_DLL)
+#  error "Expecting CMPP_PLATFORM_EXT_DLL to have been set by the auto-configured bits"
+#  define CMPP_PLATFORM_EXT_DLL "???"
+#endif
+
+#if 1
+#  define CMPP_NORETURN __attribute__((noreturn))
+#else
+#  define CMPP_NORETURN
+#endif
+
+/** @def CMPP_HAVE_DLOPEN
+
+    If set to true, use dlopen() and friends. Requires
+    linking to -ldl on some platforms.
+
+    Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be
+    true.
+*/
+/** @def CMPP_HAVE_LTDLOPEN
+
+    If set to true, use lt_dlopen() and friends. Requires
+    linking to -lltdl on most platforms.
+
+    Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be
+    true.
+*/
+#if !defined(CMPP_HAVE_DLOPEN)
+#  if defined(HAVE_DLOPEN)
+#    define CMPP_HAVE_DLOPEN HAVE_DLOPEN
+#  else
+#    define CMPP_HAVE_DLOPEN 0
+#  endif
+#endif
+
+#if !defined(CMPP_HAVE_LTDLOPEN)
+#  if defined(HAVE_LTDLOPEN)
+#    define CMPP_HAVE_LTDLOPEN HAVE_LTDLOPEN
+#  else
+#    define CMPP_HAVE_LTDLOPEN 0
+#  endif
+#endif
+
+#if !defined(CMPP_ENABLE_DLLS)
+#  define CMPP_ENABLE_DLLS (CMPP_HAVE_LTDLOPEN || CMPP_HAVE_DLOPEN)
+#endif
+#if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE)
+#  define CMPP_D_MODULE 1
+#else
+#  define CMPP_D_MODULE 0
+#endif
+
+/**
+  Many years of practice have taught that it is literally impossible
+  to safely close DLLs because simply opening one may trigger
+  arbitrary code (at least for C++ DLLs) which "might" be used by the
+  application. e.g. some classloaders use DLL initialization to inject
+  new classes into the application without the app having to do
+  anything more than open the DLL. (That's precisely what the cmpp
+  port of this code is doing except that we don't call it classloading
+  here.)
+
+  So cmpp does not close DLLs. Except (...sigh...) to please valgrind.
+
+  When CMPP_CLOSE_DLLS is true then this API will keep track of DLL
+  handles so that they can be closed, and offers the ability for
+  higher-level clients to close them (all at once, not individually).
+*/
+#if !defined(CMPP_CLOSE_DLLS)
+#define CMPP_CLOSE_DLLS 1
+#endif
+
+/** Proxy for cmpp_malloc() which (A) is a no-op if ppCode
+    and (B) sets pp->err on OOM.
+*/
+CMPP_PRIVATE void * cmpp__malloc(cmpp* pp, cmpp_size_t n);
+
+/**
+   Internal-use-only flags for use with cmpp_d::flags.
+
+   These supplement the ones from the public API's cmpp_d_e.
+*/
+enum cmpp_d_ext_e {
+  /**
+     Mask of flag bits from this enum and cmpp_d_e which are for
+     internal use only and are disallowed in client-side directives.
+  */
+  cmpp_d_F_MASK_INTERNAL = ~cmpp_d_F_MASK,
+
+  /**
+     If true, and if cmpp_d_F_ARGS_LIST is set, then cmpp_args_parse()
+     will pass its results to cmpp_args__not_simplify(). Only
+     directives which eval cmpp_arg expressions need this, and the
+     library does not expose the pieces for evaluating such
+     expressions.  As such, this flag is for internal use only. This
+     only has an effect if cmpp_d_F_ARGS_LIST is also used.
+  */
+  cmpp_d_F_NOT_SIMPLIFY  = 0x10000,
+
+  /**
+     Most directives are inert when they are seen in the "falsy" part
+     of an if/else. The callbacks for such directives are skipped, as
+     opposed to requiring each directive's callback to check whether
+     they should be skipping. This flag indicates that a directive
+     must always be run, even when skipping content (e.g. inside of an
+     #if 0 block). Only flow-control directives may have the
+     FLOW_CONTROL bit set. The library API does not expose enough of
+     its internals for client-defined directives to make flow-control
+     decisions.
+
+     i really want to get rid of this flag but it seems to be a
+     necessary evil.
+  */
+  cmpp_d_F_FLOW_CONTROL  = 0x20000
+};
+
+/**
+   A single directive line from an input stream.
+*/
+struct CmppDLine {
+  /** Line number in the source input. */
+  cmpp_size_t lineNo;
+  /** Start of the line within its source input. */
+  unsigned char const * zBegin;
+  /** One-past-the-end byte of the line. A virtual EOF. It will only
+      actually be NUL-terminated if it is the last line of the input
+      and that input has no trailing newline. */
+  unsigned char const * zEnd;
+};
+typedef struct CmppDLine CmppDLine;
+#define CmppDLine_empty_m {0U,0,0}
+
+/**
+   A snippet from a string.
+*/
+struct CmppSnippet {
+  /* Start of the range. */
+  unsigned char const *z;
+  /* Number of bytes. */
+  unsigned int n;
+};
+typedef struct CmppSnippet CmppSnippet;
+#define CmppSnippet_empty_m {0,0}
+
+/**
+   CmppLvl represents one "level" of parsing, pushing one level
+   for each of `#if` and popping one for each `#/if`.
+
+   These pieces are ONLY for use with flow-control directives. It's
+   not proven that they can be of any use to more than a single
+   flow-control directive. e.g. if we had a hypothetical #foreach, we
+   may need to extend this.
+*/
+struct CmppLvl {
+#if 0
+  /**
+     The directive on whose behalf this level was opened.
+  */
+  cmpp_d const * d;
+  /**
+     Opaque directive-specific immutable state. It's provided as a way
+     for a directive to see whether the top of the stack is correct
+     after it processes inner directives.
+  */
+  void const * state;
+#endif
+  /**
+     Bitmask of CmppLvl_F_...
+  */
+  cmpp_flag32_t flags;
+  /**
+     The directive line number which started this level.  This is used for
+     reporting the starting lines of erroneously unclosed block
+     constructs.
+  */
+  cmpp_size_t lineNo;
+};
+typedef struct CmppLvl CmppLvl;
+#define CmppLvl_empty_m {/*.d=0, .state=0,*/ .flags=0U, .lineNo=0U}
+
+/**
+   Declares struct T as a container for a list-of-MT.  MT may be
+   pointer-qualified. cmpp__ListType_impl() with the same arguments
+   implements T_reserve() for basic list allocation. Cleanup, alas, is
+   MT-dependent.
+*/
+#define cmpp__ListType_decl(T,MT)                      \
+  struct T {                                           \
+    MT * list;                                         \
+    cmpp_size_t n;                                     \
+    cmpp_size_t nAlloc;                                \
+  };                                                   \
+  typedef struct T T;                                  \
+  int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min)
+#define CMPP__MAX(X,Y) ((X)<=(Y) ? (X) : (Y))
+#define cmpp__ListType_impl(T,MT)                             \
+  int T ## _reserve(cmpp *pp,struct T *li, cmpp_size_t min) { \
+    return cmpp_array_reserve(pp, (void**)&li->list, min,     \
+                              &li->nAlloc, sizeof(MT));       \
+  }
+
+#define cmpp__LIST_T_empty_m {.list=0,.n=0,.nAlloc=0}
+
+/**
+   A dynamically-allocated list of CmppLvl objects.
+*/
+cmpp__ListType_decl(CmppLvlList,CmppLvl*);
+#define CmppLvlList_empty_m cmpp__LIST_T_empty_m
+CMPP_PRIVATE CmppLvl * CmppLvl_push(cmpp_dx *dx);
+CMPP_PRIVATE CmppLvl * CmppLvl_get(cmpp_dx const *dx);
+CMPP_PRIVATE void CmppLvl_pop(cmpp_dx *dx, CmppLvl *lvl);
+CMPP_PRIVATE void CmppLvl_elide(CmppLvl *lvl, bool on);
+CMPP_PRIVATE bool CmppLvl_is_eliding(CmppLvl const *lvl);
+CMPP_PRIVATE bool cmpp_dx_is_eliding(cmpp_dx const *dx);
+
+/**
+   A dynamically-allocated list of cmpp_b objects.
+*/
+cmpp__ListType_decl(cmpp_b_list,cmpp_b*);
+#define cmpp_b_list_empty_m cmpp__LIST_T_empty_m
+extern const cmpp_b_list cmpp_b_list_empty;
+CMPP_PRIVATE void cmpp_b_list_cleanup(cmpp_b_list *li);
+//CMPP_PRIVATE cmpp_b * cmpp_b_list_push(cmpp_b_list *li);
+//CMPP_PRIVATE void cmpp_b_list_reuse(cmpp_b_list *li);
+/**
+   cmpp_b_list sorting policies. NULL entries must
+   always sort last.
+*/
+enum cmpp_b_list_e {
+  cmpp_b_list_UNSORTED,
+  /* Smallest first. */
+  cmpp_b_list_ASC,
+  /* Largest first. */
+  cmpp_b_list_DESC
+};
+
+/**
+   A dynamically-allocated list of cmpp_arg objects. Used by
+   cmmp_args.
+*/
+cmpp__ListType_decl(CmppArgList,cmpp_arg);
+#define CmppArgList_empty_m cmpp__LIST_T_empty_m
+
+/** Allocate a new arg, owned by li, and return it (cleanly zeroed
+    out). Returns NULL and updates pp->err on error. */
+CMPP_PRIVATE cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li);
+
+/**
+   The internal part of the cmpp_args interface.
+*/
+struct cmpp_args_pimpl {
+  /**
+     We need(?) a (cmpp*) here for finalization/recycling purposes.
+  */
+  cmpp *pp;
+  bool isCall;
+  /**
+     Next entry in the free-list.
+  */
+  cmpp_args_pimpl * nextFree;
+  /** Version 3 of the real args memory.  */
+  CmppArgList argli;
+  /**
+     cmpp_args_parse() copies each argument's bytes into here,
+     each one NUL-terminated.
+  */
+  cmpp_b argOut;
+};
+#define cmpp_args_pimpl_empty_m { \
+  .pp = 0,                        \
+  .isCall = false,                \
+  .nextFree = 0,                  \
+  .argli = CmppArgList_empty_m,   \
+  .argOut = cmpp_b_empty_m        \
+}
+extern const cmpp_args_pimpl cmpp_args_pimpl_empty;
+void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p);
+
+/**
+   The internal part of the cmpp_dx interface.
+*/
+struct cmpp_dx_pimpl {
+  /** Start of input. */
+  unsigned const char * zBegin;
+  /** One-after-the-end of input. */
+  unsigned const char * zEnd;
+  /**
+     Current input position. Generally speaking, only
+     cmpp_dx_delim_search() should update this, but it turns out that
+     the ability to rewind the input is necessary for looping
+     constructs, like #query, when they want to be able to include
+     other directives in their bodies.
+  */
+  cmpp_dx_pos pos;
+  /**
+     Currently input line.
+   */
+  CmppDLine dline;
+  /** Number of active #savepoints. */
+  unsigned nSavepoint;
+  /** Current directive's args. */
+  cmpp_args args;
+  /**
+     A stack of state used by #if and friends to inform the innards
+     that they must not generate output. This is largely historical
+     and could have been done differently had this code started as a
+     library instead of a monolithic app.
+
+     TODO is to figure out how best to move this state completely into
+     the #if handler, rather than fiddle with this all throughout the
+     processing. We could maybe move this stack into CmppIfState?
+  */
+  CmppLvlList dxLvl;
+  struct {
+    /**
+       A copy of this->d's input line which gets translated
+       slightly from its native form for futher processing.
+    */
+    cmpp_b line;
+    /**
+       Holds the semi-raw input line, stripped only of backslash-escaped
+       newlines and leading spaces. This is primarily for debug output
+       but also for custom arg parsing for some directives.
+    */
+    cmpp_b argsRaw;
+  } buf;
+
+  /**
+     Record IDs for/from cmpp_[un]define_shadow().
+  */
+  struct {
+    /** ID for __FILE__. */
+    int64_t sidFile;
+    /** Rowid for #include path entry. */
+    int64_t ridInclPath;
+  } shadow;
+
+  struct {
+    /**
+       Set when we're searching for directives so that we know whether
+       cmpp_out_expand() should count newlines.
+     */
+    unsigned short countLines;
+    /**
+       True if the next directive is the start of a [call].
+    */
+    bool nextIsCall;
+  } flags;
+};
+/**
+   Initializes or resets a. Returns non-0 on OOM.
+*/
+CMPP_PRIVATE int cmpp_args__init(cmpp *pp, cmpp_args *a);
+
+/**
+   If a has state then it's recycled for reuse, else this zeroes out a
+   except for a->pimpl, which is retained (but may be NULL).
+*/
+CMPP_PRIVATE void cmpp_args_reuse(cmpp_args *a);
+
+#define cmpp_dx_pimpl_empty_m {  \
+  .zBegin=0, .zEnd=0,            \
+  .pos=cmpp_dx_pos_empty_m,     \
+  .dline=CmppDLine_empty_m,      \
+  .nSavepoint=0,                 \
+  .args = cmpp_args_empty_m,     \
+  .dxLvl = CmppLvlList_empty_m,  \
+  .buf = {                       \
+    cmpp_b_empty_m,              \
+    cmpp_b_empty_m               \
+  },                             \
+  .shadow = {                    \
+    .sidFile = 0,                \
+    .ridInclPath = 0             \
+  },                             \
+  .flags = {                     \
+    .countLines = 0,             \
+    .nextIsCall = false          \
+  }                              \
+}
+
+/**
+   A level of indirection for CmppDList in order to be able to
+   manage ownership of their name (string) lifetimes.
+*/
+struct CmppDList_entry {
+  /** this->d.name.z points to this, which is owned by the CmppDList
+      which manages this object. */
+  char * zName;
+  /* Potential TODO: move d->id into here. That doesn't eliminate our
+     dependency on it, though. */
+  cmpp_d d;
+  //cmpp_d_reg reg;
+};
+typedef struct CmppDList_entry CmppDList_entry;
+#define CmppDList_entry_empty_m {0,cmpp_d_empty_m/*,cmpp_d_reg_empty_m*/}
+
+/**
+   A dynamically-allocated list of cmpp_arg objects. Used by CmmpArgs.
+*/
+cmpp__ListType_decl(CmppDList,CmppDList_entry*);
+#define CmppDList_empty_m cmpp__LIST_T_empty_m
+
+/**
+   State for keeping track of DLL handles, a.k.a. shared-object
+   handles, a.k.a. "soh".
+
+   Instances of this must be either cleanly initialized by bitwise
+   copying CmppSohList_empty, memset() (or equivalent) them to 0, or
+   allocating them with CmppSohList_new().
+*/
+cmpp__ListType_decl(CmppSohList,void*);
+#define CmppSohList_empty_m cmpp__LIST_T_empty_m
+
+/**
+   Closes all handles which have been CmppSohList_append()ed to soli
+   and frees any memory it owns, but does not free soli (which might
+   be stack-allocated or part of another struct).
+
+   Special case: if built without DLL-closing support then this
+   is no-op.
+*/
+CMPP_PRIVATE void CmppSohList_close(CmppSohList *soli);
+
+/**
+   Operators and operator policies for use with X=Y-format keys.  This
+   is legacy stuff, actually, but some of the #define management still
+   needs it.
+*/
+#define CmppKvp_op_map(E)  \
+  E(none,"")               \
+  E(eq1,"=")
+
+enum CmppKvp_op_e {
+#define E(N,S) CmppKvp_op_ ## N,
+  CmppKvp_op_map(E)
+#undef E
+};
+typedef enum CmppKvp_op_e CmppKvp_op_e;
+
+/**
+   Result type for CmppKvp_parse().
+*/
+struct CmppKvp {
+  /* Key part of the kvp. */
+  CmppSnippet k;
+  /* Key part of the kvp. Might be empty. */
+  CmppSnippet v;
+  /* Operator part of kvp, if any. */
+  CmppKvp_op_e op;
+};
+typedef struct CmppKvp CmppKvp;
+extern const CmppKvp CmppKvp_empty;
+
+/**
+   Parses X or X=Y into p. Sets pp's error state on error.
+
+   If nKey is negative then strlen() is used to calculate it.
+
+   The third argument specifies whether/how to permit/treat the '='
+   part of X=Y.
+*/
+CMPP_PRIVATE int CmppKvp_parse(cmpp *pp, CmppKvp * p,
+                               unsigned char const *zKey,
+                               cmpp_ssize_t nKey,
+                               CmppKvp_op_e opPolicy);
+
+
+/**
+   Stack of POD values. Intended for use with cmpp at-token and
+   undefined key policies.
+*/
+#define cmpp__PodList_decl(ST,ET)         \
+  struct ST {                              \
+    /* current stack index */              \
+    cmpp_size_t n;                         \
+    cmpp_size_t na;                        \
+    ET * stack;                            \
+  }; typedef struct ST ST;                 \
+  void ST ## _wipe(ST * s, ET v);          \
+  int ST ## _push(cmpp *pp, ST * s, ET v); \
+  void ST ## _set(ST * s, ET v);           \
+  void ST ## _finalize(ST * s);            \
+  void ST ## _pop(ST *s);                  \
+  int ST ## _reserve(cmpp *, ST *, cmpp_size_t min)
+
+#define cmpp__PodList_impl(ST,ET)                               \
+  void ST ## _wipe(ST * const s, ET v){                          \
+    if( s->na ) memset(s->stack, (int)v, sizeof(ET)*s->na);      \
+    s->n = 0;                                                    \
+  }                                                              \
+  int ST ## _reserve(cmpp * const pp, ST * const s,              \
+                     cmpp_size_t min){                           \
+    return cmpp_array_reserve(pp, (void**)&s->stack, min>0       \
+                              ? min : (s->n                      \
+                                     ? (s->n==s->na-1            \
+                                        ? s->na*2 : s->n+1)      \
+                                     : 8),                       \
+                              &s->na, sizeof(ET));               \
+  }                                                              \
+  int ST ## _push(cmpp * const pp, ST * s, ET v){                \
+    if( 0== ST ## _reserve(pp, s, 0) ) s->stack[++s->n] = v;     \
+    return ppCode;                                               \
+  }                                                              \
+  void ST ## _set(ST * s, ET v){                                 \
+    assert(s->n);                                                \
+    if( 0== ST ## _reserve(NULL, s, 0) ){                        \
+      s->stack[s->n] = v;                                        \
+    }                                                            \
+  }                                                              \
+  ET ST ## _get(ST const * const s){                             \
+    assert(s->na && s->na >=s->n);                               \
+    return s->stack[s->n];                                       \
+  }                                                              \
+  void ST ## _pop(ST *s){                                        \
+    assert(s->n);                                                \
+    if(s->n) --s->n;                                             \
+  }                                                              \
+  void ST ## _finalize(ST *s){                                   \
+    cmpp_mfree(s->stack);                                        \
+    s->stack = NULL;                                             \
+    s->n = s->na = 0;                                            \
+  }
+
+cmpp__PodList_decl(PodList__atpol,cmpp_atpol_e);
+cmpp__PodList_decl(PodList__unpol,cmpp_unpol_e);
+
+#define cmpp__epol(PP,WHICH) (PP)->pimpl->policy.WHICH
+#define cmpp__policy(PP,WHICH) \
+  cmpp__epol(PP,WHICH).stack[cmpp__epol(PP,WHICH).n]
+
+/**
+   A "delimiter" object. That is, the "#" referred to in the libcmpp
+   docs. It's also outfitted for a second delimiter so that it can be
+   used for the opening/closing delimiters of @tokens@.
+*/
+struct cmpp__delim {
+  /**
+     Bytes of the directive delimiter/prefix or the @token@ opening
+     delimiter. Owned elsewhere but often points at this->zOwns.
+  */
+  CmppSnippet open;
+  /**
+     Closing @token@ delimiter. This has no meaning for the directive
+     delimiter.
+  */
+  CmppSnippet close;
+  /**
+     Memory, owned by this object, for this->open and this->close.  In
+     the latter case, it's one string with both delimiters encoded in
+     it.
+  */
+  unsigned char * zOwns;
+};
+typedef struct cmpp__delim cmpp__delim;
+#define cmpp__delim_empty_m {              \
+  .open={                                  \
+    .z=(unsigned char*)CMPP_DEFAULT_DELIM, \
+    .n=sizeof(CMPP_DEFAULT_DELIM)-1        \
+  },                                       \
+  .close=CmppSnippet_empty_m,              \
+  .zOwns=0                                 \
+}
+
+extern const cmpp__delim cmpp__delim_empty;
+void cmpp__delim_cleanup(cmpp__delim *d);
+
+/**
+   A dynamically-allocated list of cmpp__delim objects.
+*/
+cmpp__ListType_decl(cmpp__delim_list,cmpp__delim);
+#define cmpp__delim_list_empty_m {0,0,0}
+extern const cmpp__delim_list cmpp__delim_list_empty;
+
+CMPP_PRIVATE cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li);
+static inline cmpp__delim * cmpp__delim_list_get(cmpp__delim_list const *li){
+  return li->n ? li->list+(li->n-1) : NULL;
+}
+static inline void cmpp__delim_list_pop(cmpp__delim_list *li){
+  assert(li->n);
+  if( li->n ) cmpp__delim_cleanup(li->list + --li->n);
+}
+static inline void cmpp__delim_list_reuse(cmpp__delim_list *li){
+  while( li->n ) cmpp__delim_cleanup(li->list + --li->n);
+}
+
+/**
+   An untested experiment: an output buffer proxy. Integrating this
+   fully would require some surgery, but it might also inspire me to
+   do the same with input and stream it rather than slurp it all at
+   once.
+*/
+#define CMPP__OBUF 0
+
+typedef struct cmpp__obuf cmpp__obuf;
+#if CMPP__OBUF
+/**
+   An untested experiment.
+*/
+struct cmpp__obuf {
+  /** Start of the output buffer. */
+  unsigned char * begin;
+  /** One-after-the-end of this->begin. Virtual EOF. */
+  unsigned char const * end;
+  /** Current write position. Must initially be
+      this->begin. */
+  unsigned char * cursor;
+  /**
+     True if this object owns this->begin, which must have been
+     allocated using cmpp_malloc() or cmpp_realloc().
+  */
+  bool ownsMemory;
+  /** Propagating result code. */
+  int rc;
+  /**
+     The output channel to buffer for. Flushing
+  */
+  cmpp_outputer dest;
+};
+
+#define cmpp__obuf_empty_m {                      \
+  .begin=0, .end=0, .cursor=0, .ownsMemory=false, \
+  .rc=0, .dest=cmpp_outputer_empty_m              \
+}
+extern const cmpp__obuf cmpp__obuf_empty;
+extern const cmpp_outputer cmpp_outputer_obuf;
+#endif /* CMPP__OBUF */
+/**
+   The main public-API context type for this library.
+*/
+struct cmpp_pimpl {
+  /* Internal workhorse. */
+  struct {
+    sqlite3 * dbh;
+    /**
+       Optional filename. Memory is owned by this object.
+    */
+    char * zName;
+  } db;
+  /**
+     Current directive context. It's const, primarily to help protect
+     cmpp_dx_f()'s from inadvertent side effects of changes which
+     lower-level APIs might make to it. Maybe it shouldn't be: if it
+     were not then we could update dx->zDelim from
+     cmpp__delimiter_set().
+  */
+  cmpp_dx const * dx;
+  /* Output channel. */
+  cmpp_outputer out;
+  /**
+     Delimiters version 2.
+   */
+  struct {
+    /**
+       Directive delimiter.
+    */
+    cmpp__delim_list d;
+    /**
+       @token@ delimiters.
+    */
+    cmpp__delim_list at;
+  } delim;
+  struct {
+
+#define CMPP__SEL_V_FROM(N)            \
+    "(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \
+    " ORDER BY source LIMIT 1)"
+
+    /**
+       One entry for each distinct query used by cmpp: E(X,SQL), where
+       X is the member's name and SQL is its SQL.
+    */
+#define CMPP_SAVEPOINT_NAME "_cmpp_"
+#define CmppStmt_map(E)                                \
+    E(sdefIns,                                         \
+      "INSERT INTO "                                   \
+      CMPP__DB_MAIN_NAME ".sdef"                       \
+      "(t,k,v) VALUES(?1,?2,?3) RETURNING id")         \
+    E(defIns,                                          \
+      "INSERT OR REPLACE INTO "                        \
+      CMPP__DB_MAIN_NAME ".def"                        \
+      "(t,k,v) VALUES(?1,?2,?3)")                      \
+    E(defDel,                                          \
+      "DELETE FROM "                                   \
+      CMPP__DB_MAIN_NAME ".def"                        \
+      " WHERE k GLOB ?1")                              \
+    E(sdefDel,                                         \
+      "DELETE FROM "                                   \
+      CMPP__DB_MAIN_NAME ".sdef"                       \
+      " WHERE k=?1 AND id>=?2")                        \
+    E(defHas,                                          \
+      "SELECT 1 FROM "                                 \
+      CMPP__DB_MAIN_NAME ".vdef"                       \
+      " WHERE k = ?1")                                 \
+    E(defGet,                                          \
+      "SELECT source,t,k,v FROM "                      \
+      CMPP__DB_MAIN_NAME ".vdef"                       \
+      " WHERE k = ?1 ORDER BY source LIMIT 1")         \
+    E(defGetBool,                                      \
+      "SELECT cmpp_truthy(v) FROM "                    \
+      CMPP__DB_MAIN_NAME ".vdef"                       \
+      " WHERE k = ?1"                                  \
+      " ORDER BY source LIMIT 1")                      \
+    E(defGetInt,                                       \
+      "SELECT CAST(v AS INTEGER)"                      \
+      " FROM " CMPP__DB_MAIN_NAME ".vdef"              \
+      " WHERE k = ?1"                                  \
+      " ORDER BY source LIMIT 1")                      \
+    E(defSelAll, "SELECT t,k,v"                        \
+      " FROM " CMPP__DB_MAIN_NAME ".vdef"              \
+      " ORDER BY source, k")                           \
+    E(inclIns," INSERT OR FAIL INTO "                  \
+      CMPP__DB_MAIN_NAME ".incl("                      \
+      " file,srcFile, srcLine"                         \
+      ") VALUES(?,?,?)")                               \
+    E(inclDel, "DELETE FROM "                          \
+      CMPP__DB_MAIN_NAME ".incl WHERE file=?")         \
+    E(inclHas, "SELECT 1 FROM "                        \
+      CMPP__DB_MAIN_NAME ".incl WHERE file=?")         \
+    E(inclPathAdd, "INSERT INTO "                      \
+      CMPP__DB_MAIN_NAME ".inclpath(priority,dir) "    \
+      "VALUES(coalesce(?1,0),?2) "                     \
+      "ON CONFLICT DO NOTHING "                        \
+      "RETURNING rowid /*xlates to 0 on conflict*/")   \
+    E(inclPathRmId, "DELETE FROM "                     \
+      CMPP__DB_MAIN_NAME ".inclpath WHERE rowid=?1 "   \
+      "RETURNING rowid")                               \
+    E(inclSearch,                                      \
+      "SELECT ?1 fn WHERE cmpp_file_exists(fn) "       \
+      "UNION ALL SELECT fn FROM ("                     \
+      " SELECT replace(dir||'/'||?1, '//','/') AS fn " \
+      " FROM " CMPP__DB_MAIN_NAME ".inclpath"          \
+      " WHERE cmpp_file_exists(fn) "                   \
+      " ORDER BY priority DESC, rowid LIMIT 1"         \
+      ")")                                             \
+    E(cmpVV, "SELECT cmpp_compare(?1,?2)")             \
+    E(cmpDV,                                           \
+      "SELECT cmpp_compare("                           \
+      CMPP__SEL_V_FROM(1) ", ?2"                       \
+      ")")                                             \
+    E(cmpVD,                                           \
+      "SELECT cmpp_compare("                           \
+      "?1," CMPP__SEL_V_FROM(2)                        \
+      ")")                                             \
+    E(cmpDD,                                           \
+      "SELECT cmpp_compare("                           \
+      CMPP__SEL_V_FROM(1)                              \
+      ","                                              \
+      CMPP__SEL_V_FROM(2)                              \
+      ")")                                             \
+    E(dbAttach,                                        \
+      "ATTACH ?1 AS ?2")                               \
+    E(dbDetach,                                        \
+      "DETACH ?1")                                     \
+    E(spBegin, "SAVEPOINT " CMPP_SAVEPOINT_NAME)       \
+    E(spRollback,                                      \
+      "ROLLBACK TO SAVEPOINT " CMPP_SAVEPOINT_NAME)    \
+    E(spRelease,                                       \
+      "RELEASE SAVEPOINT " CMPP_SAVEPOINT_NAME)        \
+    E(insTtype,                                        \
+      "INSERT INTO " CMPP__DB_MAIN_NAME ".ttype"       \
+      "(t,n,s) VALUES(?1,?2,?3)")                      \
+    E(selPathSearch,                                   \
+   /* sqlite.org/forum/forumpost/840c98a8e87c2207 */   \
+      "WITH path(basename, sep, ext, path) AS (\n"     \
+      "  select\n"                                     \
+      "  ?1 basename,\n"                               \
+      "  ?2 sep,\n"                                    \
+      "  ?3 ext,\n"                                    \
+      "  ?4 path\n"                                    \
+      "),\n"                                           \
+      "pathsplit(i, l, c, r) AS (\n"                   \
+      "--   i = sequential ID\n"                       \
+      "--   l = Length remaining\n"                    \
+      "--   c = text remaining\n"                      \
+      "--   r = current unpacked value\n"              \
+      "  SELECT 1,\n"                                  \
+      "         length(p.path)+length(p.sep),\n"       \
+      "         p.path||p.sep, ''\n"                   \
+      "  FROM path p\n"                                \
+      "  UNION ALL\n"                                  \
+      "  SELECT i+1, instr( c, p.sep ) l,\n"           \
+      "    substr( c, instr( c, p.sep ) + 1) c,\n"     \
+      "    trim( substr( c, 1,\n"                      \
+      "            instr( c, p.sep) - 1) ) r\n"        \
+      "  FROM pathsplit, path p\n"                     \
+      "  WHERE l > 0\n"                                \
+      "),\n"                                           \
+      "thefile (f) AS (\n"                             \
+      "  select basename f FROM path\n"                \
+      "  union all\n"                                  \
+      "  select basename||ext\n"                       \
+      "  from path where ext is not null\n"            \
+      ")\n"                                            \
+      "select 0 i, replace(f,'//','/') AS fn\n"        \
+      "from thefile where cmpp_file_exists(fn)\n"      \
+      "union all\n"                                    \
+      "select i, replace(r||'/'||f,'//','/') fn\n"     \
+      "from pathsplit, thefile\n"                      \
+      "where r<>'' and cmpp_file_exists(fn)\n"         \
+      "order by i\n"                                   \
+      "limit 1;")
+
+    /* trivia: selPathSearch (^^^) was generated using
+       cmpp's #c-code directive. */
+
+#define E(N,S) sqlite3_stmt * N;
+    CmppStmt_map(E)
+#undef E
+
+  } stmt;
+
+  /** Error state. */
+  struct {
+    /** Result code. */
+    int code;
+    /** Error string owned by this object. */
+    char * zMsg;
+    /** Either this->zMsg or an external error string. */
+    char const * zMsgC;
+  } err;
+
+  /** State for SQL tracing. */
+  struct {
+    bool expandSql;
+    cmpp_size_t counter;
+    cmpp_outputer out;
+  } sqlTrace;
+
+  struct {
+    /** If set properly, cmpp_dtor() will free this
+        object, else it will not. */
+    void const * allocStamp;
+    /**
+       How many dirs we believe are in the #include search list. We
+       only do this for the sake of the historical "if no path was
+       added, assume '.'" behavior. This really ought to go away.
+    */
+    unsigned nIncludeDir;
+    /**
+       The current depth of cmpp_process_string() calls. We do this so
+       the directory part of #include'd files can get added to the
+       #include path and be given a higher priority than previous
+       include path entries in the stack.
+    */
+    int nDxDepth;
+    /* Number of active #savepoints. */
+    unsigned nSavepoint;
+    /* If >0, enables certain debugging output. */
+    char doDebug;
+    /* If true, chomp() files read via -Fx=file. */
+    unsigned char chompF;
+    /* Flags passed to cmpp_ctor(). */
+    cmpp_flag32_t newFlags;
+
+    /**
+       An ugly hack for getting cmpp_d_register() to get
+       syntactically-illegal directive names, like "@policy",
+       to register.
+     */
+    bool isInternalDirectiveReg;
+    /**
+       True if the next directive is the start of a [call]. This is
+       used for:
+
+       1) To set cmpp_dx::isCall, which is useful in certain
+          directives.
+
+       2) So that the cmpp_dx object created for the call can inherit
+          the line number from its parent context. That's significant
+          for error reporting.
+
+       3) So that #2's cmpp_dx object can communicate that flag to
+          cmpp_dx_next().
+    */
+    bool nextIsCall;
+
+    /**
+       True until the cmpp's (sometimes) lazy init has been run. This
+       is essentially a kludge to work around a wrench cmpp_reset()
+       throws into cmpp state. Maybe we should just remove
+       cmpp_reset() from the interface, since error recovery in this
+       context is not really a thing.
+    */
+    bool needsLazyInit;
+  } flags;
+
+  /** Policies. */
+  struct {
+    /** @token@-parsing policy. */
+    PodList__atpol at;
+    /** Policy towards referencing undefined symbols. */
+    PodList__unpol un;
+  } policy;
+
+  /**
+     Directive state.
+  */
+  struct {
+    /**
+       Runtime-installed directives.
+    */
+    CmppDList list;
+    /**
+       Directive autoloader/auto-registerer.
+    */
+    cmpp_d_autoloader autoload;
+  } d;
+
+  struct {
+    /**
+       List of DLL handles opened by cmpp_module_extract().
+    */
+    CmppSohList sohList;
+    /**
+       Search path for DLLs, delimited by this->pathSep.
+    */
+    cmpp_b path;
+    /**
+       File extension for DLLs.
+    */
+    char const * soExt;
+    /** Separator char for this->path. */
+    char pathSep;
+  } mod;
+
+  struct {
+    /**
+       Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return().
+    */
+    cmpp_b_list buf;
+    /** How/whether this->list is sorted. */
+    enum cmpp_b_list_e bufSort;
+    /**
+       Head of the free-list.
+     */
+    cmpp_args_pimpl * argPimpl;
+  } recycler;
+};
+
+/** IDs Distinct for each cmpp::stmt member. */
+enum CmppStmt_e {
+  CmppStmt_none = 0,
+#define E(N,S) CmppStmt_ ## N,
+  CmppStmt_map(E)
+#undef E
+};
+
+static inline cmpp__delim * cmpp__pp_delim(cmpp const *pp){
+  return cmpp__delim_list_get(&pp->pimpl->delim.d);
+}
+static inline char const * cmpp__pp_zdelim(cmpp const *pp){
+  cmpp__delim const * const d = cmpp__pp_delim(pp);
+  return d ? (char const *)d->open.z : NULL;
+}
+#define cmpp__dx_delim(DX) cmpp__pp_delim(DX->pp)
+#define cmpp__dx_zdelim(DX) cmpp__pp_zdelim(DX->pp)
+
+/**
+   Emit [z,(char*)z+n) to the given output channel if
+   (A) pOut->out is not NULL and (B) pp has no error state and (C)
+   n>0.  On error, pp's error state is updated. Returns pp->err.code.
+
+   Skip level is not honored.
+*/
+CMPP_PRIVATE int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n);
+
+CMPP_PRIVATE void cmpp__err_clear(cmpp *pp);
+
+
+/**
+   Initialize pp->db.dbh. If it's already open or ppCode!=0
+   then ppCode is returned.
+*/
+int cmpp__db_init(cmpp *pp);
+
+/**
+  Returns the pp->pimpl->stmt.X corresponding to `which`, initializing it if
+  needed. If it returns NULL then either this was called when pp has
+  its error state set or this function will set the error state.
+
+  If prepEvenIfErr is true then the ppCode check is bypassed, but it
+  will still fail if pp->pimpl->db is not opened or if the preparation itself
+  fails.
+*/
+sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
+                          bool prepEvenIfErr);
+
+/**
+   Reminder to self: this must return an SQLITE_... code, not a
+   CMPP_RC_... code.
+
+   On success it returns 0, SQLITE_ROW, or SQLITE_DONE. On error it
+   returns another non-0 SQLITE_... code and updates pp->pimpl->err.
+
+   This is a no-op if called when pp has an error set, returning
+   SQLITE_ERROR.
+
+   If resetIt is true, q is passed to cmpp__stmt_reset(), else the
+   caller must eventually reset it.
+*/
+int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt);
+
+/** Resets and clear bindings from q (if q is not NULL). */
+void cmpp__stmt_reset(sqlite3_stmt * const q);
+
+/**
+   Expects an SQLite result value. If it's SQLITE_OK, SQLITE_ROW, or
+   SQLITE_DONE, 0 is returned without side-effects, otherwise pp->err
+   is updated with pp->db's current error state. zMsgSuffix is an
+   optional prefix for the error message.
+*/
+int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsgSuffix);
+
+/* Proxy for sqlite3_bind_int64(). */
+int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
+
+/**
+   Proxy for cmpp__bind_text() which encodes val as a string.
+
+   For queries which compare values, it's important that they all have
+   the same type, so some cases where we might want an int needs to be
+   bound as text instead. See #query for one such case.
+*/
+int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
+
+/* Proxy for sqlite3_bind_null(). */
+int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col);
+
+/* Proxy for sqlite3_bind_text() which updates pp->err on error. */
+int cmpp__bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col,
+                    unsigned const char * zStr);
+
+/* Proxy for sqlite3_bind_text() which updates pp->err on error. */
+int cmpp__bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col,
+                     unsigned const char *zStr, cmpp_ssize_t len);
+
+/**
+   Adds zDir to the include path, using the given priority value (use
+   0 except for the implicit cwd path which #include should (but does
+   not yet) set). If pRowid is not NULL then *pRowid gets set to
+   either 0 (if zDir was already in the path) or the row id of the
+   newly-inserted record, which can later be used to delete just that
+   entry.
+
+   If this returns a non-zero value via pRowid, the caller is
+   obligated to eventually pass *pRowid to cmpp__include_dir_rm_id(),
+   even if pp is in an error state.
+
+   TODO: normalize zDir (at least remove trailing slashes) before
+   insertion to avoid that both a/b and a/b/ get be inserted.
+*/
+int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid);
+
+/**
+   Deletes the include path entry with the given rowid. This will make
+   make the attempt even if pp is in an error state but also retains
+   any existing error rather than overwriting it if this operation
+   somehow fails. Returns pp's error code.
+
+   It is not an error for the given entry to not exist.
+*/
+int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid);
+
+
+#if 0
+/**
+   Proxy for sqlite3_bind_text(). It uses sqlite3_str_vappendf() so
+   supports all of its formatting options.
+*/
+int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col,
+                     const char * zFmt, ...);
+#endif
+
+/**
+   Proxy for sqlite3_str_finish() which updates pp's error state if s
+   has error state. Returns s's string on success and NULL on
+   error. The returned string must eventualy be passed to
+   cmpp_mfree(). It also, it turns out, returns NULL if s is empty, so
+   callers must check pp->err to see if NULL is an error.
+
+   If n is not NULL then on success it is set to the byte length of
+   the returned string.
+*/
+char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n);
+
+/**
+   Searches pp's list of directives. If found, return it else return
+   NULL. See cmpp__d_search3().
+*/
+cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName);
+
+/**
+   Flags for use with the final argument to
+   cmpp__d_search3().
+*/
+enum cmpp__d_search3_e {
+  /** Internal delayed-registered directives. */
+  cmpp__d_search3_F_DELAYED    = 0x01,
+  /** Directive autoloader. */
+  cmpp__d_search3_F_AUTOLOADER = 0x02,
+  /** Search for a DLL. */
+  cmpp__d_search3_F_DLL        = 0x04,
+  /** Options which do not trigger DLL lookup. */
+  cmpp__d_search3_F_NO_DLL    = 0
+  | cmpp__d_search3_F_DELAYED
+  | cmpp__d_search3_F_AUTOLOADER,
+  /** All options. */
+  cmpp__d_search3_F_ALL        = 0
+  | cmpp__d_search3_F_DELAYED
+  | cmpp__d_search3_F_AUTOLOADER
+  | cmpp__d_search3_F_DLL
+};
+
+/**
+   Like cmpp__d_search() but if no match is found then it will search
+   through its other options and, if found, register it.
+
+   The final argument specifies where to search. cmpp__d_search()
+   always checked first. After that, depending on "what", the search
+   order is: (1) internal delayed-load modules, (2) autoloader, (3)
+   DLL.
+
+   This may update pp's error state, in which case it will return
+   NULL.
+*/
+cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
+                               cmpp_flag32_t what);
+
+/**
+   Sets pp's error state (A) if it's not set already and (B) if
+   !cmpp_is_legal_key(zKey). If permitEqualSign is true then '=' is
+   legal (to support legacy CLI pieces). Returns ppCode.
+*/
+int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
+                          cmpp_ssize_t nKey,
+                          bool permitEqualSign);
+
+/**
+   Appends DLL handle soh to soli. Returns 0 on success, CMPP_RC_OOM
+   on error. If pp is not NULL then its error state is updated as
+   well.
+
+   Results are undefined if soli was not cleanly initialized (by
+   copying CmppSohList_empty or using CmppSohList_new()).
+
+   Special case: if built without DLL-closing support, this is a no-op
+   returning 0.
+*/
+int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh);
+
+/** True if arg is of type cmpp_TT_Word and it looks like it
+    _might_ be a filename or flag argument. Might. */
+bool cmpp__arg_wordIsPathOrFlag(cmpp_arg const * const arg);
+
+/**
+   Helper for #query and friends. Binds aVal's value to column bindNdx
+   of q.
+
+   It expands cmpp_TT_StringAt and cmpp_TT_Word aVal. cmpp_TT_String
+   and cmpp_TT_Int are bound as strings. A cmpp_TT_GroupParen aVal is
+   eval'ed as an integer and that int gets bound as a string.
+
+   This function strictly binds everything as strings, even if the
+   value being bound is of type cmpp_TT_Int or cmpp_TT_GroupParen, so that
+   comparison queries will work as expected.
+
+   Returns ppCode.
+*/
+int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * q,
+                   int bindNdx, cmpp_arg const * aVal);
+
+/**
+   Helper for #query's bind X part, where aGroup is that X.
+
+   A wrapper around cmpp__bind_arg(). Requires aGroup->ttype to be
+   either cmpp_TT_GroupBrace or cmpp_TT_GroupSquiggly and to have
+   non-empty content. cmpp_TT_GroupBrace treats it as a list of values
+   to bind. cmpp_TT_GroupSquiggly expects sets of 3 tokens per stmt
+   column in one of these forms:
+
+   :bindName -> value
+   $bindName -> value
+
+   Each LHS refers to a same-named binding in q's SQL, including the
+   ':' or '$' prefix. (SQLite supports an '@' prefix but we don't
+   allow it here to avoid confusion with cmpp_TT_StringAt tokens.)
+
+   Each bound value is passed to cmpp__bind_arg() for processing.
+
+   On success, each aGroup entry is bound to q. On error q's state is
+   unspecified. Returns ppCode.
+
+   See cmpp__bind_arg() for notes about the bind data type.
+*/
+int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q,
+                     cmpp_arg const * const aGroup);
+
+/**
+   Returns true if the given key is already in the `#define` list,
+   else false. Sets pp's error state on db error.
+
+   nName is the length of the key part of zName (which might have
+   a following =y part. If it's negative, strlen() is used to
+   calculate it.
+*/
+int cmpp_has(cmpp *pp, const char * zName, cmpp_ssize_t nName);
+
+/**
+   Returns true if the given key is already in the `#define` list, and
+   it has a truthy value (is not empty and not equal to '0'), else
+   false. If zName contains an '=' then only the part preceding that
+   is used as the key.
+
+   nName is the length of zName, or <0 to use strlen() to figure
+   it out.
+
+   Updates ppCode on error.
+*/
+int cmpp__get_bool(cmpp *pp, unsigned const char * zName,
+                   cmpp_ssize_t nName);
+
+/**
+   Fetches the given define. If found, sets *pOut to it, else pOut is
+   unmodified. Returns pp->err.code. If bRequire is true and no entry
+   is found p->err.code is updated.
+*/
+int cmpp__get_int(cmpp *pp, unsigned const char * zName,
+                  cmpp_ssize_t nName, int *pOut);
+
+/**
+   Searches for a define where (k GLOB zName). If one is found, a copy
+   of it is assigned to *zVal (the caller must eventually db_free()
+   it), *nVal (if nVal is not NULL) is assigned its strlen, and
+   returns non-0. If no match is found, 0 is returned and neither
+   *zVal nor *nVal are modified. If more than one result matches, a
+   fatal error is triggered.
+
+   It is legal for *zVal to be NULL (and *nVal to be 0) if it returns
+   non-0. That just means that the key was defined with no value part.
+
+   Fixme: return 0 on success and set output *gotOne=0|1.
+*/
+int cmpp__get(cmpp *pp, unsigned const char * zName,
+              cmpp_ssize_t nName,
+              unsigned char **zVal, unsigned int *nVal);
+
+/**
+   Like cmp__get() but puts its output in os.
+*/
+int cmpp__get_b(cmpp *pp, unsigned const char * zName,
+                cmpp_ssize_t nName, cmpp_b * os,
+                bool enforceUndefPolicy);
+
+
+/**
+   Helper for #query and friends.
+
+   It expects that q has just been stepped.  For each column in the
+   row, it sets a define named after the column.  If q has row data
+   then the values come from there. If q has no row then: if
+   defineIfNoRow is true then it defines each column name to an empty
+   value else it defines nothing.
+*/
+int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
+                          bool defineIfNoRow);
+
+/** Start a new savepoint for dx. */
+int cmpp__dx_sp_begin(cmpp_dx * const dx);
+/** Commit and close dx's current savepoint. */
+int cmpp__dx_sp_commit(cmpp_dx * const dx);
+/** Roll back and close dx's current savepoint. */
+int cmpp__dx_sp_rollback(cmpp_dx * const dx);
+
+/**
+   Append's dx's file/line information to sstr. It returns void
+   because that's how sqlite3_str_appendf() and friends work.
+*/
+void cmpp__dx_append_script_info(cmpp_dx const * dx,
+                                 sqlite3_str * sstr);
+
+/**
+   If zName matches one of the delayed-load directives, that directive
+   is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
+   no match is found, but pp's error state is not updated in that
+   case. If a match is found and registration fails, that result code
+   will propagate via pp.
+*/
+int cmpp__d_delayed_load(cmpp *pp, char const *zName);
+
+void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent);
+
+/**
+   Like cmpp_tt_cstr(), but if bSymbolName is false then it returns
+   the higher-level token name, which is NULL for most token types.
+*/
+char const * cmpp__tt_cstr(int tt, bool bSymbolName);
+
+/**
+   Expects **zPos to be one of ('(' '{' '[' '"' '\'') and zEnd to be
+   the logical EOF for *zPos.
+
+   This looks for a matching closing token, accounting for nesting. On
+   success, returns 0 and sets *zPos to the closing character.
+
+   On error it update's pp's error state and returns that code. pp may
+   be NULL.
+
+   If pNl is not NULL then *pNl is incremented for each '\n' character
+   seen while looking for the closing character.
+*/
+int cmpp__find_closing2(cmpp *pp,
+                        unsigned char const **zPos,
+                        unsigned char const *zEnd,
+                        cmpp_size_t *pNl);
+
+#define cmpp__find_closing(PP,Z0,Z1) \
+  cmpp__find_closing2(PP, Z0, Z1, NULL)
+
+static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_ssize_t n){
+  return n<0 ? (cmpp_size_t)strlen(z) : (cmpp_size_t)n;
+}
+static inline cmpp_size_t cmpp__strlenu(unsigned char const *z, cmpp_ssize_t n){
+  return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n;
+}
+
+/**
+   If ppCode is not set and pol resolves to cmpp_atpol_OFF then this
+   updates ppCode with a message about the lack of support for
+   at-strings. If cmpp_atpol_CURRENT==pol then pp's current policy is
+   checked. Returns ppCode.
+*/
+int cmpp__StringAtIsOk(cmpp * const pp, cmpp_atpol_e pol);
+
+/**
+   "define"s zKey to zVal, recording the value type as tType.
+*/
+int cmpp__define2(cmpp *pp,
+                  unsigned char const * zKey,
+                  cmpp_ssize_t nKey,
+                  unsigned char const *zVal,
+                  cmpp_ssize_t nVal,
+                  cmpp_tt tType);
+
+/**
+   Evals pArgs's arguments as an integer expression. On success, sets
+   *pResult to the value.
+
+   Returns ppCode.
+*/
+int cmpp__args_evalToInt(cmpp_dx * dx, cmpp_args const *pArgs,
+                         int * pResult);
+
+/** Passes the contents of arg through to cmpp__args_evalToInt(). */
+int cmpp__arg_evalSubToInt(cmpp_dx *dx, cmpp_arg const *arg,
+                           int * pResult);
+
+/**
+   Evaluated arg as an integer/bool, placing the result in *pResult
+   and setting *pNext to the first argument to arg's right which this
+   routine did not consume. Non-0 on error and all that.
+*/
+int cmpp__arg_toBool(cmpp_dx * dx, cmpp_arg const *arg,
+                     int * pResult, cmpp_arg const **pNext);
+
+/**
+   If thisTtype==cmpp_TT_AnyType or thisTtype==arg->ttype and arg->z
+   looks like it might contain an at-string then os is re-used to hold
+   the @token@-expanded version of arg's content. os is unconditionally
+   passed to cmpp_b_reuse() before it begines work.
+
+   It uses the given atPolicy to determine whether or not the content
+   is expanded, as per cmpp_dx_out_expand().
+
+   Returns 0 on success. If it expands content then *pExp is set to
+   os->z, else *pExp is set to arg->z. If nExp is not NULL then *nExp
+   gets set to the length of *pExp (geither os->n or arg->n).
+
+   Returns ppCode.
+
+   Much later: what does this give us that cmpp_arg_to_b()
+   doesn't? Oh - that one calls into this one. i.e. this one is
+   lower-level.
+*/
+int cmpp__arg_expand_ats(cmpp_dx const * const dx,
+                         cmpp_b * os,
+                         cmpp_atpol_e atPolicy,
+                         cmpp_arg const * const arg,
+                         cmpp_tt thisTtype,
+                         unsigned char const **pExp,
+                         cmpp_size_t * nExp);
+
+typedef struct cmpp_argOp cmpp_argOp;
+typedef void (*cmpp_argOp_f)(cmpp_dx *dx,
+                             cmpp_argOp const *op,
+                             cmpp_arg const *vLhs,
+                             cmpp_arg const **pvRhs,
+                             int *pResult);
+struct cmpp_argOp {
+  int ttype;
+  /* 1 or 2 */
+  unsigned char arity;
+  /* 0=none/left, 1=right (unary ops only) */
+  signed char   assoc;
+  cmpp_argOp_f   xCall;
+};
+
+cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt);
+
+
+bool cmpp__is_int(unsigned char const *z, unsigned n,
+                  int *pOut);
+bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut);
+
+char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p);
+char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p);
+
+/**
+   Uncerimoniously bitwise-replaces pp's output channel with oNew. It
+   does _not_ clean up the previous channel, on the assumption that
+   the caller is taking any necessary measures.
+
+   Apropos necessary measures for cleanup: if oPrev is not NULL,
+   *oPrev is set to a bitwise copy of the previous channel.
+
+   Intended usage:
+
+   ```
+   cmpp_outputer oMine = cmpp_outputer_b;
+   cmpp_b bMine = cmpp_b_empty;
+   cmpp_outputer oOld = {0};
+   oMine.state = &bMine;
+   cmpp_outputer_swap(pp, &myOut, &oOld);
+   ...do some work then ALWAYS do...
+   cmpp_outputer_swap(pp, &oOld, &oMine);
+   ```
+
+   Because this involves bitwise copying, care must be taken with
+   stream state, e.g. bMine.z (above) can be reallocated, so we have
+   to be sure to swap it back before using bMine again.
+*/
+void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
+                         cmpp_outputer *oPrev);
+
+/**
+   Init code which is usually run as part of the ctor but may have to
+   be run later, after cmpp_reset(). We can't run it from cmpp_reset()
+   because that could leave post-reset in an error state, which is
+   icky. This call is a no-op after the first.
+*/
+int cmpp__lazy_init(cmpp *pp);
+
+CMPP_NORETURN void cmpp__fatalv_base(char const *zFile, int line,
+                                     char const *zFmt, va_list);
+#define cmpp__fatalv(...) cmpp__fatalv_base(__FILE__,__LINE__,__VA_ARGS__)
+CMPP_NORETURN void cmpp__fatal_base(char const *zFile, int line,
+                                    char const *zFmt, ...);
+#define cmpp__fatal(...) cmpp__fatal_base(__FILE__,__LINE__,__VA_ARGS__)
+
+/**
+   Outputs a printf()-formatted message to stderr.
+*/
+void g_stderr(char const *zFmt, ...);
+#define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg)
+#if 0
+#define g_debug(PP,lvl,pfexpr) (void)0
+#else
+#define g_debug(PP,lvl,pfexpr)                         \
+  if(lvl<=(PP)->pimpl->flags.doDebug) {                \
+    if( (PP)->pimpl->dx ){                             \
+      g_stderr("%s:%" CMPP_SIZE_T_PFMT ": ",           \
+               (PP)->pimpl->dx->sourceName,            \
+               (PP)->pimpl->dx->pimpl->dline.lineNo);  \
+    }                                                  \
+    g_stderr("%s():%d: ",                              \
+             __func__,__LINE__);                       \
+    g_stderr pfexpr;                                   \
+  } (void)0
+#endif
+
+/** Returns true if zFile is readable, else false. */
+bool cmpp__file_is_readable(char const *zFile);
+
+#define ustr_c(X) ((unsigned char const *)X)
+#define ustr_nc(X) ((unsigned char *)X)
+#define ppCode pp->pimpl->err.code
+#define dxppCode dx->ppCode
+#define cmpp__pi(PP) cmpp_pimpl * const pi = PP->pimpl
+#define cmpp__dx_pi(DX) cmpp_dx_pimpl * const dpi = DX->pimpl
+#define serr(...) cmpp_err_set(pp, CMPP_RC_SYNTAX, __VA_ARGS__)
+#define dxserr(...) cmpp_err_set(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__)
+#define cmpp__li_reserve1_size(li,nInitial)                       \
+  (li->n ? (li->n==li->nAlloc ? li->nAlloc * 2 : li->n+1) : nInitial)
+
+#define MARKER(pfexp)                                               \
+  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
+    printf pfexp;                                                   \
+  } while(0)
+
+#endif /* include guard */
+/*
+** 2022-11-12:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the core of libcmpp (it used to house all of it,
+** but the library grew beyond the confines of a single file).
+**
+** See the accompanying c-pp.h and README.md and/or c-pp.h for more
+** details.
+*/
+#include "sqlite3.h"
+
+char const * cmpp_version(void){ return CMPP_VERSION; }
+
+const cmpp__delim cmpp__delim_empty = cmpp__delim_empty_m;
+const cmpp__delim_list cmpp__delim_list_empty = cmpp__delim_list_empty_m;
+const cmpp_outputer cmpp_outputer_empty = cmpp_outputer_empty_m;
+const cmpp_outputer cmpp_outputer_FILE = {
+  .out = cmpp_output_f_FILE,
+  .flush = cmpp_flush_f_FILE,
+  .cleanup = cmpp_outputer_cleanup_f_FILE,
+  .state = NULL
+};
+const cmpp_b_list cmpp_b_list_empty =
+  cmpp_b_list_empty_m;
+const cmpp_outputer cmpp_outputer_b = {
+  .out = cmpp_output_f_b,
+  .flush = 0,
+  .cleanup = cmpp_outputer_cleanup_f_b,
+  .state = NULL
+};
+
+/**
+   Default delimiters for @tokens@.
+*/
+static const cmpp__delim delimAtDefault = {
+  .open  = { .z = ustr_c("@"), .n = 1 },
+  .close = { .z = ustr_c("@"), .n = 1 },
+  .zOwns = NULL
+};
+
+static const cmpp_api_thunk cmppApiMethods = {
+#define A(V)
+#define V(N,T,V) .N = V,
+#define F(N,T,P) .N = cmpp_ ##N,
+#define O(N,T) .N = &cmpp_ ##N,
+  cmpp_api_thunk_map(A,V,F,O)
+#undef F
+#undef O
+#undef V
+#undef A
+};
+
+/* Fatally exits the app with the given printf-style message. */
+
+CMPP__EXPORT(bool, cmpp_isspace)(int ch){
+  return ' '==ch || '\t'==ch;
+}
+
+//CMPP__EXPORT(int, cmpp_isnl)(char const * z, char const *zEnd){}
+static inline int cmpp_isnl(unsigned char const * z, unsigned char const *zEnd){
+  //assert(z<zEnd && "Caller-level pointer mis-traversal");
+  switch( z<zEnd ? *z : 0 ){
+    case 0: return 0;
+    case '\r': return ((z+1<zEnd) && '\n'==z[1]) ? 2 : 0;
+    default: return '\n'==*z;
+  }
+}
+
+static inline bool cmpp_issnl(int ch){
+  /* TODO: replace this in line with cmpp_isnl(), but it needs
+     a new interface for that. It's only used in two places and they
+     have different traversal directions, so we can probably
+     get rid of this and do the direct CRNL check in each of those
+     places. */
+  return ' '==ch || '\t'==ch || '\n'==ch;
+}
+
+CMPP__EXPORT(void, cmpp_skip_space)(
+  unsigned char const **p,
+  unsigned char const *zEnd
+){
+  assert( *p <= zEnd );
+  unsigned char const * z = *p;
+  while( z<zEnd && cmpp_isspace(*z) ) ++z;
+  *p = z;
+}
+
+CMPP__EXPORT(void, cmpp_skip_snl)( unsigned char const **p,
+                                   unsigned char const *zEnd ){
+  unsigned char const * z = *p;
+  /* FIXME: CRNL. */
+  while( z<zEnd && cmpp_issnl(*z) ) ++z;
+  *p = z;
+}
+
+CMPP__EXPORT(void, cmpp_skip_space_trailing)( unsigned char const *zBegin,
+                                              unsigned char const **p ){
+  assert( *p >= zBegin );
+  unsigned char const * z = *p;
+  while( z>zBegin && cmpp_isspace(z[-1]) ) --z;
+  *p = z;
+}
+
+CMPP__EXPORT(void, cmpp_skip_snl_trailing)( unsigned char const *zBegin,
+                                            unsigned char const **p ){
+  assert( *p >= zBegin );
+  unsigned char const * z = *p;
+  /* FIXME: CRNL. */
+  while( z>zBegin && cmpp_issnl(*z) ) --z;
+  *p = z;
+}
+
+/* Set pp's error state. */
+static int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list);
+/**
+   Sets pp's error state.
+*/
+CMPP__EXPORT(int, cmpp_err_set)(cmpp *pp, int rc, char const *zFmt, ...);
+#define cmpp__err cmpp_err_set
+#define cmpp_dx_err cmpp_dx_err_set
+
+/* Open/close pp's output channel. */
+static int cmpp__out_fopen(cmpp *pp, const char *zName);
+static void cmpp__out_close(cmpp *pp);
+
+#define CmppKvp_empty_m \
+  {CmppSnippet_empty_m,CmppSnippet_empty_m,CmppKvp_op_none}
+const CmppKvp CmppKvp_empty = CmppKvp_empty_m;
+
+/* Wrapper around a cmpp_FILE handle. Legacy stuff from when we just
+   supported cmpp_FILE input. */
+typedef struct FileWrapper FileWrapper;
+struct FileWrapper {
+  /* File's name. */
+  char const *zName;
+  /* cmpp_FILE handle. */
+  cmpp_FILE * pFile;
+  /* Where FileWrapper_slurp() stores the file's contents. */
+  unsigned char * zContent;
+  /* Size of this->zContent, as set by FileWrapper_slurp(). */
+  cmpp_size_t nContent;
+};
+#define FileWrapper_empty_m {0,0,0,0}
+static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
+
+/**
+   Proxy for cmpp_fclose() and frees all memory owned by p. It is not
+   an error if p is already closed.
+*/
+static void FileWrapper_close(FileWrapper * p);
+
+/** Proxy for cmpp_fopen(). Closes p first if it's currently opened. */
+static int FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
+
+/* Populates p->zContent and p->nContent from p->pFile. */
+//static int FileWrapper_slurp(FileWrapper * p, int bCloseFile );
+
+/**
+   If p->zContent ends in \n or \r\n, that part is replaced with 0 and
+   p->nContent is adjusted. Returns true if it chomps, else false.
+*/
+static bool FileWrapper_chomp(FileWrapper * p);
+
+/*
+** Outputs a printf()-formatted message to stderr.
+*/
+static void g_stderrv(char const *zFmt, va_list);
+
+CMPP__EXPORT(char const *, cmpp_rc_cstr)(int rc){
+  switch((cmpp_rc_e)rc){
+#define E(N,V,H) case N: return # N;
+    cmpp_rc_e_map(E)
+#undef E
+  }
+  return NULL;
+}
+
+CMPP__EXPORT(void, cmpp_mfree)(void *p){
+  /* This MUST be a proxy for sqlite3_free() because allocate memory
+     exclusively using sqlite3_malloc() and friends. */
+  sqlite3_free(p);
+}
+
+CMPP__EXPORT(void *, cmpp_mrealloc)(void * p, size_t n){
+  return sqlite3_realloc64(p, n);
+}
+
+CMPP__EXPORT(void *, cmpp_malloc)(size_t n){
+#if 1
+  return sqlite3_malloc64(n);
+#else
+  void * p = sqlite3_malloc64(n);
+  if( p ) memset(p, 0, n);
+  return p;
+#endif
+}
+
+cmpp_FILE * cmpp_fopen(const char *zName, const char *zMode){
+  cmpp_FILE *f;
+  if(zName && ('-'==*zName && !zName[1])){
+    f = (strchr(zMode, 'w') || strchr(zMode,'+'))
+      ? stdout
+      : stdin
+      ;
+  }else{
+    f = fopen(zName, zMode);
+  }
+  return f;
+}
+
+void cmpp_fclose( cmpp_FILE * f ){
+  if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
+    fclose(f);
+  }
+}
+
+int cmpp_slurp(cmpp_input_f fIn, void *sIn,
+               unsigned char **pOut, cmpp_size_t * nOut){
+  unsigned char zBuf[1024 * 16];
+  unsigned char * pDest = 0;
+  unsigned nAlloc = 0;
+  unsigned nOff = 0;
+  int rc = 0;
+  cmpp_size_t nr = 0;
+  while( 0==rc ){
+    nr = sizeof(zBuf);
+    if( (rc = fIn(sIn, zBuf, &nr)) ){
+      break;
+    }
+    if(nr>0){
+      if(nAlloc < nOff + nr + 1){
+        nAlloc = nOff + nr + 1;
+        pDest = cmpp_mrealloc(pDest, nAlloc);
+      }
+      memcpy(pDest + nOff, zBuf, nr);
+      nOff += nr;
+    }else{
+      break;
+    }
+  }
+  if( 0==rc ){
+    if(pDest) pDest[nOff] = 0;
+    *pOut = pDest;
+    *nOut = nOff;
+  }else{
+    cmpp_mfree(pDest);
+  }
+  return rc;
+}
+
+void FileWrapper_close(FileWrapper * p){
+  if(p->pFile) cmpp_fclose(p->pFile);
+  if(p->zContent) cmpp_mfree(p->zContent);
+  *p = FileWrapper_empty;
+}
+
+int FileWrapper_open(FileWrapper * p, const char * zName,
+                     const char * zMode){
+  FileWrapper_close(p);
+  if( (p->pFile = cmpp_fopen(zName, zMode)) ){
+    p->zName = zName;
+    return 0;
+  }else{
+    return cmpp_errno_rc(errno, CMPP_RC_IO);
+  }
+}
+
+int FileWrapper_slurp(FileWrapper * p, int bCloseFile){
+  assert(!p->zContent);
+  assert(p->pFile);
+  int const rc = cmpp_slurp(cmpp_input_f_FILE, p->pFile,
+                            &p->zContent, &p->nContent);
+  if( bCloseFile ){
+    cmpp_fclose(p->pFile);
+    p->pFile = 0;
+  }
+  return rc;
+}
+
+CMPP__EXPORT(bool, cmpp_chomp)(unsigned char * z, cmpp_size_t * n){
+  if( *n && '\n'==z[*n-1] ){
+    z[--*n] = 0;
+    if( *n && '\r'==z[*n-1] ){
+      z[--*n] = 0;
+    }
+    return true;
+  }
+  return false;
+}
+
+bool FileWrapper_chomp(FileWrapper * p){
+  return cmpp_chomp(p->zContent, &p->nContent);
+}
+
+
+#if 0
+/**
+   Returns the number newline characters between the given starting
+   point and inclusive ending point. Results are undefined if zFrom is
+   greater than zTo.
+*/
+static unsigned cmpp__count_lines(unsigned char const * zFrom,
+                                  unsigned char const *zTo);
+
+unsigned cmpp__count_lines(unsigned char const * zFrom,
+                           unsigned char const *zTo){
+  unsigned ln = 0;
+  assert(zFrom && zTo);
+  assert(zFrom <= zTo);
+  for(; zFrom < zTo; ++zFrom){
+    if((unsigned char)'\n' == *zFrom) ++ln;
+  }
+  return ln;
+}
+#endif
+
+char const * cmpp__tt_cstr(int tt, bool bSymbolName){
+  switch(tt){
+#define E(N,TOK) case cmpp_TT_ ## N: \
+    return bSymbolName ? "cmpp_TT_" #N : TOK;
+    cmpp_tt_map(E)
+#undef E
+  }
+  return NULL;
+}
+
+char const * cmpp_tt_cstr(int tt){
+  return cmpp__tt_cstr(tt, true);
+}
+
+/** Flags and constants related to CmppLvl. */
+enum CmppLvl_e {
+  /**
+     Flag indicating that all ostensible output for a CmpLevel should
+     be elided. This also suppresses non-flow-control directives from
+     being processed.
+  */
+  CmppLvl_F_ELIDE = 0x01,
+  /**
+     Mask of CmppLvl::flags which are inherited when
+     CmppLvl_push() is used.
+  */
+  CmppLvl_F_INHERIT_MASK = CmppLvl_F_ELIDE
+};
+
+//static const CmppDLine CmppDLine_empty = CmppDLine_empty_m;
+
+/** Free all memory owned by li but does not free li. */
+static void CmppLvlList_cleanup(CmppLvlList *li);
+
+/**
+   Allocate a list entry, owned by li, and return it (cleanly zeroed
+   out). Returns NULL and updates pp->err on error.  It is expected
+   that the caller will populate the entry's zName using
+   sqlite3_mprintf() or equivalent.
+*/
+static CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li);
+
+/** Returns the most-recently-appended element of li back to li's
+    free-list. It expects to receive that value as a sanity-checking
+    measure and may fail fatally of that's not upheld. */
+static void CmppLvlList_pop(cmpp *pp, CmppLvlList *li, CmppLvl * lvl);
+
+static const cmpp_dx_pimpl cmpp_dx_pimpl_empty =
+  cmpp_dx_pimpl_empty_m;
+
+#define cmpp_dx_empty_m { \
+  .pp=0,                  \
+  .d=0,                   \
+  .sourceName=0,          \
+  .args={                 \
+    .z=0, .nz=0,          \
+    .argc=0, .arg0=0      \
+  },                      \
+  .pimpl = 0              \
+}
+
+const cmpp_dx cmpp_dx_empty = cmpp_dx_empty_m;
+#define cmpp_d_empty_m {{0,0},0,0,cmpp_d_impl_empty_m}
+//static const cmpp_d cmpp_d_empty = cmpp_d_empty_m;
+
+static const CmppDList_entry CmppDList_entry_empty =
+  CmppDList_entry_empty_m;
+
+/** Free all memory owned by li but does not free li. */
+static void CmppDList_cleanup(CmppDList *li);
+/**
+   Allocate a list entry, owned by li, and return it (cleanly zeroed
+   out). Returns NULL and updates pp->err on error.  It is expected
+   that the caller will populate the entry's zName using
+   sqlite3_mprintf() or equivalent.
+*/
+static CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li);
+/** Returns the most-recently-appended element of li back to li's
+    free-list. */
+static void CmppDList_unappend(CmppDList *li);
+/** Resets li's list for re-use but does not free it. Returns li. */
+//static CmppDList * CmppDList_reuse(CmppDList *li);
+static CmppDList_entry * CmppDList_search(CmppDList const * li,
+                                          char const *zName);
+
+/** Reset dx and free any memory it may own. */
+static void cmpp_dx_cleanup(cmpp_dx * const dx);
+/**
+   Reset some of dx's parsing-related state in prep for fetching the
+   next line.
+*/
+static void cmpp_dx__reset(cmpp_dx * const dx);
+
+/* Returns dx's current directive. */
+static inline cmpp_d const * cmpp_dx_d(cmpp_dx const * const dx){
+  return dx->d;
+}
+
+static const cmpp_pimpl cmpp_pimpl_empty = {
+  .db = {
+    .dbh = 0,
+    .zName = 0
+  },
+  .dx = 0,
+  .out = cmpp_outputer_empty_m,
+  .delim = {
+    .d = cmpp__delim_list_empty_m,
+    .at = cmpp__delim_list_empty_m
+  },
+  .stmt = {
+#define E(N,S) .N = 0,
+  CmppStmt_map(E)
+#undef E
+  },
+  .err = {
+    .code = 0,
+    .zMsg = 0,
+    .zMsgC = 0
+  },
+  .sqlTrace = {
+    .expandSql = false,
+    .counter = 0,
+    .out = cmpp_outputer_empty_m
+  },
+  .flags = {
+    .allocStamp = 0,
+    .nIncludeDir = 0,
+    .nDxDepth = 0,
+    .nSavepoint = 0,
+    .doDebug = 0,
+    .chompF = 0,
+    .newFlags = 0,
+    .isInternalDirectiveReg = false,
+    .nextIsCall = false,
+    .needsLazyInit = true
+  },
+  .policy = {
+    .at = {0,0,0},
+    .un = {0,0,0}
+  },
+  .d = {
+    .list = CmppDList_empty_m,
+    .autoload = cmpp_d_autoloader_empty_m
+  },
+  .mod = {
+    .sohList = CmppSohList_empty_m,
+    .path = cmpp_b_empty_m,
+    .soExt = CMPP_PLATFORM_EXT_DLL,
+    /* Yes, '*'. It makes sense in context. */
+    .pathSep = '*'
+    // 0x1e /* "record separator" */ doesn't work. Must be non-ctrl.
+  },
+  .recycler = {
+    .buf = cmpp_b_list_empty_m,
+    .bufSort = cmpp_b_list_UNSORTED,
+    .argPimpl = NULL
+  }
+};
+
+#if 0
+static inline int cmpp__out(cmpp *pp, void const *z, cmpp_size_t n){
+  return cmpp__out2(pp, &pp->out, z, n);
+}
+#endif
+
+/**
+   Returns an approximate cmpp_tt for the given SQLITE_... value from
+   sqlite3_column_type() or sqlite3_value_type().
+*/
+static cmpp_tt cmpp__tt_for_sqlite(int sqType);
+
+/**
+   Init code which is usually run as part of the ctor but may have to
+   be run later, after cmpp_reset(). We can't run it from cmpp_reset()
+   because that could leave post-reset in an error state, which is
+   icky.
+*/
+int cmpp__lazy_init(cmpp *pp){
+  if( !ppCode && pp->pimpl->flags.needsLazyInit ){
+    pp->pimpl->flags.needsLazyInit = false;
+    cmpp__delim_list * li = &pp->pimpl->delim.d;
+    if( !li->n ) cmpp_delimiter_push(pp, NULL);
+    li = &pp->pimpl->delim.at;
+    if( !li->n ) cmpp_atdelim_push(pp, NULL, NULL);
+#if defined(CMPP_CTOR_INSTANCE_INIT)
+    if( !ppCode ){
+      extern int CMPP_CTOR_INSTANCE_INIT(cmpp*);
+      int const rc = CMPP_CTOR_INSTANCE_INIT(pp);
+      if( rc && !ppCode ){
+        cmpp__err(pp, rc,
+                  "Initialization via CMPP_CTOR_INSTANCE_INIT() failed "
+                  "with code %d/%s.", rc, cmpp_rc_cstr(rc) );
+      }
+    }
+#endif
+  }
+  return ppCode;
+}
+
+static void cmpp__wipe_policies(cmpp *pp){
+  if( 0==ppCode ){
+    PodList__atpol_reserve(pp, &cmpp__epol(pp,at), 0);
+    PodList__unpol_reserve(pp, &cmpp__epol(pp,un), 0);
+    if( 0==ppCode ){
+      PodList__atpol_wipe(&cmpp__epol(pp,at), cmpp_atpol_DEFAULT);
+      PodList__unpol_wipe(&cmpp__epol(pp,un), cmpp_unpol_DEFAULT);
+    }
+  }
+}
+
+CMPP__EXPORT(int, cmpp_ctor)(cmpp **pOut, cmpp_ctor_cfg const * cfg){
+  cmpp_pimpl * pi = 0;
+  cmpp * pp = 0;
+  void * const mv = cmpp_malloc(sizeof(cmpp) + sizeof(*pi));
+  if( mv ){
+    if( !cfg ){
+      static const cmpp_ctor_cfg dfltCfg = {0};
+      cfg = &dfltCfg;
+    }
+    cmpp const x = {
+      .api = &cmppApiMethods,
+      .pimpl = (cmpp_pimpl*)((unsigned char *)mv + sizeof(cmpp))
+      /* ^^^ (T const * const) members */
+    };
+    memcpy(mv, &x, sizeof(x))
+      /* FWIW, i'm convinced that this is a legal way to transfer
+         these const-pointers-to-const.  If not, we'll need to change
+         those cmpp members from (T const * const) to (T const *). */;
+    pp = mv;
+    assert(pp->api == &cmppApiMethods);
+    assert(pp->pimpl);
+    pi = pp->pimpl;
+    *pOut = pp;
+    *pi = cmpp_pimpl_empty;
+    assert( pi->flags.needsLazyInit );
+    pi->flags.newFlags = cfg->flags;
+    pi->flags.allocStamp = &cmpp_pimpl_empty;
+    if( cfg->dbFile ){
+      pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile);
+      cmpp_check_oom(pp, pi->db.zName);
+    }
+    if( 0==ppCode ){
+      cmpp__wipe_policies(pp);
+      cmpp__lazy_init(pp);
+    }
+  }
+  return pp ? ppCode : CMPP_RC_OOM;
+}
+
+CMPP__EXPORT(void, cmpp_reset)(cmpp *pp){
+  cmpp__pi(pp);
+  cmpp_outputer_cleanup(&pi->sqlTrace.out);
+  pi->sqlTrace.out = cmpp_outputer_empty;
+  if( pi->d.autoload.dtor ){
+    pi->d.autoload.dtor(pi->d.autoload.state);
+  }
+  pi->d.autoload = cmpp_pimpl_empty.d.autoload;
+  cmpp_b_clear(&pi->mod.path);
+  if( pi->stmt.spRelease && pi->stmt.spRollback ){
+    /* Cleanly kill all savepoint levels. This is truly superfluous,
+       as they'll all be rolled back (if the db is persistent) or
+       nuked (if using a :memory: db) momentarily. However, we'll
+       eventually need this for a partial-clear operation which leaves
+       the db and custom directives intact. For now it lives here but
+       will eventually move to wherever that ends up being.
+
+       2025-11-16: or not. It's fine here, really.
+    */
+    sqlite3_reset(pi->stmt.spRelease);
+    while( SQLITE_DONE==sqlite3_step(pi->stmt.spRelease) ){
+      sqlite3_reset(pi->stmt.spRollback);
+      sqlite3_step(pi->stmt.spRollback);
+      sqlite3_reset(pi->stmt.spRelease);
+    }
+  }
+  cmpp__out_close(pp);
+  CmppDList_cleanup(&pi->d.list);
+#define E(N,S) \
+  if(pi->stmt.N) {sqlite3_finalize(pi->stmt.N); pi->stmt.N = 0;}
+  CmppStmt_map(E) (void)0;
+#undef E
+  if( pi->db.dbh ){
+    if( SQLITE_TXN_WRITE==sqlite3_txn_state(pi->db.dbh, NULL) ){
+      sqlite3_exec(pi->db.dbh, "COMMIT;", 0, 0, NULL)
+        /* ignoring error */;
+    }
+    sqlite3_close(pi->db.dbh);
+    pi->db.dbh = 0;
+  }
+  cmpp__delim_list_reuse(&pi->delim.d);
+  cmpp__delim_list_reuse(&pi->delim.at);
+  //why? cmpp_b_list_reuse(&pi->cache.buf);
+  cmpp__err_clear(pp);
+  {/* Zero out pi but save some pieces for later, when pp is
+      cmpp_dtor()'d */
+    cmpp_pimpl const tmp = *pi;
+    *pi = cmpp_pimpl_empty;
+    pi->db = tmp.db /* restore db.zName */;
+    pi->recycler = tmp.recycler;
+    pi->policy = tmp.policy;
+    pi->delim = tmp.delim;
+    pi->mod.sohList = tmp.mod.sohList;
+    cmpp__wipe_policies(pp);
+    pi->flags.allocStamp = tmp.flags.allocStamp;
+    pi->flags.newFlags = tmp.flags.newFlags;
+    pi->flags.needsLazyInit = true;
+  }
+}
+
+static void cmpp__delim_list_cleanup(cmpp__delim_list *li);
+
+CMPP__EXPORT(void, cmpp_dtor)(cmpp *pp){
+  if( pp ){
+    cmpp__pi(pp);
+    cmpp_reset(pp);
+    cmpp_mfree(pi->db.zName);
+    PodList__atpol_finalize(&cmpp__epol(pp,at));
+    assert(!cmpp__epol(pp,at).na);
+    PodList__unpol_finalize(&cmpp__epol(pp,un));
+    assert(!cmpp__epol(pp,un).na);
+    cmpp_b_list_cleanup(&pi->recycler.buf);
+    cmpp__delim_list_cleanup(&pi->delim.d);
+    cmpp__delim_list_cleanup(&pi->delim.at);
+    for( cmpp_args_pimpl * apNext = 0,
+           * ap = pi->recycler.argPimpl;
+         ap; ap = apNext ){
+      apNext = ap->nextFree;
+      ap->nextFree = 0;
+      cmpp_args_pimpl_cleanup(ap);
+      cmpp_mfree(ap);
+    }
+    CmppSohList_close(&pi->mod.sohList);
+    if( &cmpp_pimpl_empty==pi->flags.allocStamp ){
+      pi->flags.allocStamp = 0;
+      cmpp_mfree(pp);
+    }
+  }
+}
+
+CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){
+  return pp ? 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) : false;
+}
+
+/** Sets ppCode if m is NULL. Returns ppCode. */
+CMPP__EXPORT(int, cmpp_check_oom)(cmpp * const pp, void const * const m ){
+  int rc;
+  if( pp ){
+    if( !m ){
+      //assert(!"oom");
+      cmpp__err(pp, CMPP_RC_OOM, 0);
+    }
+    rc = ppCode;
+  }else{
+    rc = m ? 0 : CMPP_RC_OOM;
+  }
+  return rc;
+}
+
+//CxMPP_WASM_EXPORT
+void *cmpp__malloc(cmpp *pp, cmpp_size_t n){
+  void *p = 0;
+  if( 0==ppCode ){
+    p = cmpp_malloc(n);
+    cmpp_check_oom(pp, p);
+  }
+  return p;
+}
+
+/**
+   If ppCode is not 0 then it flushes pp's output channel. If that
+   fails, it sets ppCode. Returns ppCode.
+*/
+static int cmpp__flush(cmpp *pp){
+  if( !ppCode && pp->pimpl->out.flush ){
+    int const rc = pp->pimpl->out.flush(pp->pimpl->out.state);
+    if( rc && !ppCode ){
+      cmpp_err_set(pp, rc, "Flush failed.");
+    }
+  }
+  return ppCode;
+}
+
+void cmpp__out_close(cmpp *pp){
+  cmpp__flush(pp)/*ignoring result*/;
+  cmpp_outputer_cleanup(&pp->pimpl->out);
+  pp->pimpl->out = cmpp_pimpl_empty.out;
+}
+
+int cmpp__out_fopen(cmpp *pp, const char *zName){
+  cmpp__out_close(pp);
+  if( !ppCode ){
+    cmpp_FILE * const f = cmpp_fopen(zName, "wb");
+    if( f ){
+      ppCode = 0;
+      pp->pimpl->out = cmpp_outputer_FILE;
+      pp->pimpl->out.state = f;
+      pp->pimpl->out.name = zName;
+    }else{
+      ppCode = cmpp__err(
+        pp, cmpp_errno_rc(errno, CMPP_RC_IO),
+        "Error opening file %s", zName
+      );
+    }
+  }
+  return ppCode;
+}
+
+static int cmpp__FileWrapper_open(cmpp *pp, FileWrapper * fw,
+                                  const char * zName,
+                                  const char * zMode){
+  int const rc = FileWrapper_open(fw, zName, zMode);
+  if( rc ){
+    cmpp__err(pp, rc, "Error %s opening file [%s] "
+                  "with mode [%s]",
+                  cmpp_rc_cstr(rc), zName, zMode);
+  }
+  return ppCode;
+}
+
+static int cmpp__FileWrapper_slurp(cmpp* pp, FileWrapper * fw){
+  assert( fw->pFile );
+  int const rc = FileWrapper_slurp(fw, 1);
+  if( rc ){
+    cmpp__err(pp, rc, "Error %s slurping file %s",
+              cmpp_rc_cstr(rc), fw->zName);
+  }
+  return ppCode;
+}
+
+void g_stderrv(char const *zFmt, va_list va){
+  vfprintf(0 ? stdout : stderr, zFmt, va);
+}
+
+void g_stderr(char const *zFmt, ...){
+  va_list va;
+  va_start(va, zFmt);
+  g_stderrv(zFmt, va);
+  va_end(va);
+}
+
+CMPP__EXPORT(char const *, cmpp_dx_delim)(cmpp_dx const *dx){
+  return (char const *)cmpp__dx_zdelim(dx);
+}
+
+int cmpp__out2(cmpp *pp, cmpp_outputer *pOut,
+               void const *z, cmpp_size_t n){
+  assert( pOut );
+  if( !ppCode && pOut->out && n ){
+    int const rc = pOut->out(pOut->state, z, n);
+    if( rc ){
+      cmpp__err(pp, rc,
+                "Write of %" CMPP_SIZE_T_PFMT
+                " bytes to output stream failed.", n);
+    }
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_out_raw)(cmpp_dx * dx, void const *z, cmpp_size_t n){
+  if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode;
+  return cmpp__out2(dx->pp, &dx->pp->pimpl->out, z, n);
+}
+
+CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){
+  assert( out );
+  if( !ppCode && zFmt && *zFmt && out->out ){
+    char * s = sqlite3_vmprintf(zFmt, va);
+    if( 0==cmpp_check_oom(pp, s) ){
+      cmpp__out2(pp, out, s, cmpp__strlen(s, -1));
+    }
+    cmpp_mfree(s);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_outf2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, ...){
+  assert( out );
+  if( !ppCode && zFmt && *zFmt && out->out ){
+    va_list va;
+    va_start(va, zFmt);
+    cmpp_outfv2(pp, out, zFmt, va);
+    va_end(va);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_outfv)(cmpp *pp, char const *zFmt, va_list va){
+  return cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
+}
+
+CMPP__EXPORT(int, cmpp_outf)(cmpp *pp, char const *zFmt, ...){
+  if( !ppCode ){
+    va_list va;
+    va_start(va, zFmt);
+    cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
+    va_end(va);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_outf)(cmpp_dx *dx, char const *zFmt, ...){
+  if( !dxppCode && zFmt && *zFmt && dx->pp->pimpl->out.out ){
+    va_list va;
+    va_start(va, zFmt);
+    cmpp_outfv(dx->pp, zFmt, va);
+    va_end(va);
+  }
+  return dxppCode;
+}
+
+static int cmpp__affirm_undef_policy(cmpp *pp,
+                                     unsigned char const *zName,
+                                     cmpp_size_t nName){
+  if( 0==ppCode
+      && cmpp_unpol_ERROR==cmpp__policy(pp,un) ){
+    cmpp__err(pp, CMPP_RC_NOT_DEFINED,
+              "Key '%.*s' was not found and the undefined-value "
+              "policy is 'error'.",
+              (int)nName, zName);
+  }
+  return ppCode;
+}
+
+static int cmpp__out_expand(cmpp * pp, cmpp_outputer * pOut,
+                            unsigned char const * zFrom,
+                            cmpp_size_t n, cmpp_atpol_e atPolicy){
+  enum state_e {
+    /* looking for @token@ opening @ */
+    state_opening,
+    /* looking for @token@ closing @ */
+    state_closing
+  };
+  cmpp__pi(pp);
+  if( ppCode ) return ppCode;
+  if( cmpp_atpol_CURRENT==atPolicy ) atPolicy = cmpp__policy(pp,at);
+  assert( cmpp_atpol_invalid!=atPolicy );
+  unsigned char const *zLeft = zFrom;
+  unsigned char const * const zEnd = zFrom + n;
+  unsigned char const *z =
+    (cmpp_atpol_OFF==atPolicy || cmpp_atpol_invalid==atPolicy)
+    ? zEnd
+    : zLeft;
+  unsigned char const chEol = (unsigned char)'\n';
+  cmpp__delim const * delim =
+    cmpp__delim_list_get(&pp->pimpl->delim.at);
+  if( !delim && z<zEnd ){
+    return cmpp_err_set(pp, CMPP_RC_ERROR,
+                        "@token@ delimiter stack is empty.");
+  }
+  enum state_e state = state_opening;
+  cmpp_b * const bCall = cmpp_b_borrow(pp);
+  cmpp_b * const bVal = cmpp_b_borrow(pp);
+  if( !bCall || !bVal ) return ppCode;
+  if(0) g_warn("looking to expand from %d bytes: [%.*s]", (int)n,
+               (int)n,zLeft);
+  if( !pOut ){
+    if( 0 && atPolicy!=cmpp__policy(pp,at) ){
+      /* This might be too strict. It was initially in place to ensure
+         that we did not _accidentally_ do @token@ parsing to the main
+         output channel. We frequently use it internally on other
+         output channels to buffer/build results.
+
+         Advantage to removing this check: #query could impose
+         its own at-policy without having to use an intermediary
+         buffer.
+
+         Disadvantage: less protection against accidentally
+         @-filtering the output when we shouldn't.
+      */
+      return cmpp_err_set(pp, CMPP_RC_MISUSE,
+                         "%s(): when sending to the default "
+                         "output channel, the @policy must be "
+                         "cmpp_atpol_CURRENT.", __func__);
+    }
+    pOut = &pi->out;
+  }
+  assert( pi->dx ? !cmpp_dx_is_eliding(pi->dx) : 1 );
+
+#define tflush                                                          \
+  if(z>zEnd) z=zEnd;                                                    \
+  if(zLeft<z){                                                          \
+    if(0) g_warn("flush %d [%.*s]", (int)(z-zLeft), (int)(z-zLeft), zLeft); \
+    cmpp__out2(pp, pOut, zLeft, (z-zLeft));                             \
+  } zLeft = z
+  cmpp_dx_pimpl * const dxp = pp->pimpl->dx ? pp->pimpl->dx->pimpl : NULL;
+  for( ; z<zEnd && 0==ppCode; ++z ){
+    zLeft = z;
+    for( ;z<zEnd && 0==ppCode; ++z ){
+    again:
+      if( chEol==*z ){
+#if 0
+        broken;
+        if( dxp && dxp->flags.countLines ){
+          ++dxp->lineNo;
+        }
+#endif
+        state = state_opening;
+        continue;
+      }
+      if( state_opening==state ){
+        if( z + delim->open.n < zEnd
+            && 0==memcmp(z, delim->open.z, delim->open.n) ){
+          tflush;
+          z += delim->open.n;
+          if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft);
+          if( 0 ){
+            g_warn("\nzLeft..=[%s]\nz=[%s]", zLeft, z);
+          }
+          state = state_closing;
+#if 1
+          /* Handle call of @[directive ...args]@
+
+             i'm not a huge fan of this syntax, but that may go away
+             if we replace the single-char separator with a pair of
+             opening/closing delimiters.
+          */
+          if( z<zEnd && '['==*z ){
+            unsigned char const * zb = z;
+            cmpp_size_t nl = 0;
+            //g_warn("Scanning: <<%.*s>>", (int)(zEnd-zb), zb);
+            if( cmpp__find_closing2(pp, &zb, zEnd, &nl) ){
+              break;
+            }
+            //g_warn("Found: <<%.*s>>", (int)(zb+1-z), z);
+            if( zb + delim->close.n >= zEnd
+                || 0!=memcmp(zb+1, delim->close.z, delim->close.n) ){
+              serr("Expecting '%s' after closing ']'.", delim->close.z);
+              break;
+            }
+            if( nl && dxp && dxp->flags.countLines ){
+              dxp->pos.lineNo +=nl;
+            }
+            cmpp_call_str(pp, z+delim->open.n,
+                          (zb - z - delim->open.n),
+                          cmpp_b_reuse(bCall), 0);
+            if( 0==ppCode ){
+              cmpp__out2(pp, pOut, bCall->z, bCall->n);
+              state = state_opening;
+              zLeft = z = zb + delim->close.n + 1;
+              //g_warn("post-@[]@ z=%.*s", (int)(zEnd-z), z);
+            }
+          }
+#endif
+          if( z>=zEnd ) break;
+          goto again /* avoid adjusting z again */;
+        }
+      }else{/*we're looking for delim->closer*/
+        assert( state_closing==state );
+        if( z + delim->close.n <= zEnd
+            && 0==memcmp(z, delim->close.z, delim->close.n ) ){
+          /* process the ... part of @...@ */
+          assert( state_closing==state );
+          assert( zLeft<z );
+          assert( z<=zEnd );
+          if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft);
+          if( 0 ) g_warn("zLeft..=%s", zLeft);
+          assert( 0==memcmp(zLeft, delim->open.z, delim->open.n) );
+          unsigned char const *zKey =
+            zLeft + delim->open.n;
+          cmpp_ssize_t const nKey = z - zLeft - delim->open.n;
+          if( 0 ) g_warn("nKey=%d zKey=[%.*s]", nKey, nKey, zKey);
+          assert( nKey>= 0 );
+          if( !nKey ){
+            serr("Empty key is not permitted in %s...%s.",
+                 delim->open.z, delim->close.z);
+            break;
+          }
+          if( cmpp__get_b(pp, zKey, nKey, cmpp_b_reuse(bVal), true) ){
+            if(0){
+              g_warn("nVal=%d zVal=[%.*s]", (int)bVal->n,
+                     (int)bVal->n, bVal->z);
+            }
+            if( bVal->n ){
+              cmpp__out2(pp, pOut, bVal->z, bVal->n);
+            }else{
+              /* Elide it */
+            }
+            zLeft = z + delim->close.n;
+            assert( zLeft<=zEnd );
+          }else if( !ppCode ){
+            assert( !bVal->n );
+            /* No matching define . */
+            switch( atPolicy ){
+              case cmpp_atpol_ELIDE:  zLeft = z + delim->close.n; break;
+              case cmpp_atpol_RETAIN: tflush; break;
+              case cmpp_atpol_ERROR:
+                cmpp__err(pp, CMPP_RC_NOT_DEFINED,
+                          "Undefined %skey%s: %.*s",
+                          delim->open.z, delim->close.z, nKey, zKey);
+                break;
+              case cmpp_atpol_invalid:
+              case cmpp_atpol_CURRENT:
+              case cmpp_atpol_OFF:
+                assert(!"this shouldn't be reachable" );
+                cmpp__err(pp, CMPP_RC_ERROR, "Unhandled atPolicy #%d",
+                          atPolicy);
+                break;
+            }
+          }/* process @...@ */
+          state = state_opening;
+          assert( z<=zEnd );
+        }/*matched a closer*/
+      }/*state_closer==state*/
+      assert( z<=zEnd );
+    }/*per-line loop*/
+  }/*outer loop*/
+#if 0
+  if( 0==ppCode && state_closer==state ){
+    serr("Opening '%s' found without a closing '%s'.",
+         delim->open.z, delim->close.z);
+  }
+#endif
+  tflush;
+#undef tflush
+  cmpp_b_return(pp, bCall);
+  cmpp_b_return(pp, bVal);
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_out_expand)(cmpp_dx const * const dx,
+                                      cmpp_outputer * pOut,
+                                      unsigned char const * zFrom,
+                                      cmpp_size_t n,
+                                      cmpp_atpol_e atPolicy){
+  if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode;
+  return cmpp__out_expand(dx->pp, pOut, zFrom, n, atPolicy);
+}
+
+CmppLvl * CmppLvl_get(cmpp_dx const *dx){
+  return dx->pimpl->dxLvl.n
+    ? dx->pimpl->dxLvl.list[dx->pimpl->dxLvl.n-1]
+    : 0;
+}
+
+static const CmppLvl CmppLvl_empty = CmppLvl_empty_m;
+
+CmppLvl * CmppLvl_push(cmpp_dx *dx){
+  CmppLvl * p = 0;
+  if( !dxppCode ){
+    CmppLvl * const pPrev = CmppLvl_get(dx);
+    p = CmppLvlList_push(dx->pp, &dx->pimpl->dxLvl);
+    if( p ){
+      *p = CmppLvl_empty;
+      p->lineNo = dx->pimpl->dline.lineNo;
+      //p->d = dx->d;
+      if( pPrev ){
+        p->flags = (CmppLvl_F_INHERIT_MASK & pPrev->flags);
+        //if(CLvl_isSkip(pPrev)) p->flags |= CmppLvl_F_ELIDE;
+      }
+    }
+  }
+  return p;
+}
+
+void CmppLvl_pop(cmpp_dx *dx, CmppLvl * lvl){
+  CmppLvlList_pop(dx->pp, &dx->pimpl->dxLvl, lvl);
+}
+
+void CmppLvl_elide(CmppLvl *lvl, bool on){
+  if( on ) lvl->flags |=  CmppLvl_F_ELIDE;
+  else     lvl->flags &= ~CmppLvl_F_ELIDE;
+}
+
+bool CmppLvl_is_eliding(CmppLvl const *lvl){
+  return lvl && !!(lvl->flags & CmppLvl_F_ELIDE);
+}
+
+#if 0
+void cmpp_dx_elide_mode(cmpp_dx *dx, bool on){
+  CmppLvl_elide(CmppLvl_get(dx), on);
+}
+#endif
+
+bool cmpp_dx_is_eliding(cmpp_dx const *dx){
+  return CmppLvl_is_eliding(CmppLvl_get(dx));
+}
+
+
+char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n){
+  char * z = 0;
+  int const rc = sqlite3_str_errcode(s);
+  cmpp__db_rc(pp, rc, "sqlite3_str_errcode()");
+  if(0==rc){
+    int const nStr = sqlite3_str_length(s);
+    if(n) *n = nStr;
+    z = sqlite3_str_finish(s);
+    if( !z ){
+      assert( 0==nStr && "else rc!=0" );
+    }
+  }else{
+    cmpp_mfree( sqlite3_str_finish(s) );
+  }
+  return z;
+}
+
+int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val){
+  return ppCode
+    ? ppCode
+    : cmpp__db_rc(pp, sqlite3_bind_int64(pStmt, col, val),
+                     "from cmpp__bind_int()");
+}
+
+int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col,
+                        int64_t val){
+  unsigned char buf[32];
+  snprintf((char *)buf, sizeof(buf), "%" PRIi64, val);
+  return cmpp__bind_textn(pp, pStmt, col, buf, -1);
+}
+
+int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col){
+  return ppCode
+    ? ppCode
+    : cmpp__db_rc(pp, sqlite3_bind_null(pStmt, col),
+                     "from cmpp__bind_null()");
+}
+
+static int cmpp__bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col,
+                            unsigned const char * zStr, cmpp_ssize_t n,
+                            void (*dtor)(void *)){
+  if( 0==ppCode ){
+    cmpp__db_rc(
+      pp, (zStr && n)
+      ? sqlite3_bind_text(pStmt, col,
+                          (char const *)zStr,
+                          (int)n, dtor)
+      : sqlite3_bind_null(pStmt, col),
+      sqlite3_sql(pStmt)
+    );
+  }
+  return ppCode;
+}
+
+int cmpp__bind_textn(cmpp *pp, sqlite3_stmt *pStmt, int col,
+                     unsigned const char * zStr, cmpp_ssize_t n){
+  return cmpp__bind_textx(pp, pStmt, col, zStr, (int)n,
+                          SQLITE_TRANSIENT);
+}
+
+int cmpp__bind_text(cmpp *pp, sqlite3_stmt *pStmt, int col,
+                    unsigned const char * zStr){
+  return cmpp__bind_textn(pp, pStmt, col, zStr, -1);
+}
+
+#if 0
+int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col,
+                     const char * zFmt, ...){
+  if( 0==p->err.code ){
+    int rc;
+    sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
+    int n = 0;
+    char * z;
+    va_list va;
+    if( !str ) return ppCode;
+    va_start(va,zFmt);
+    sqlite3_str_vappendf(str, zFmt, va);
+    va_end(va);
+    z = cmpp_str_finish(str, &n);
+    cmpp__db_rc(
+      pp, z
+      ? sqlite3_bind_text(pStmt, col, z, n, sqlite3_free)
+      : sqlite3_bind_null(pStmt, col),
+      sqlite3_sql(pStmt)
+    );
+    cmpp_mfree(z);
+  }
+  return p->err.code;
+}
+#endif
+
+void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out,
+                       char const *zName){
+  cmpp__pi(pp);
+  cmpp_outputer_cleanup(&pi->out);
+  if( out ) pi->out = *out;
+  else pi->out = cmpp_outputer_empty;
+  pi->out.name = zName;
+}
+
+void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
+                        cmpp_outputer *oPrev){
+  if( oPrev ){
+    *oPrev = pp->pimpl->out;
+  }
+  pp->pimpl->out = *oNew;
+}
+
+#if 0
+static void delim__list_dump(cmpp const *pp){
+  cmpp__delim_list const *li = &pp->pimpl->delim.d;
+  if( li->n ){
+    g_warn0("delimiter stack:");
+    for(cmpp_size_t i = 0; i < li->n; ++i ){
+      g_warn("#%d: %s", (int)i, li->list[i].z);
+    }
+  }
+
+}
+#endif
+
+static bool cmpp__valid_delim(cmpp * const pp,
+                                char const *z,
+                                char const *zEnd){
+  char const * const zB = z;
+  for( ; z < zEnd; ++z ){
+    if( *z<33 || 127==*z ){
+      cmpp_err_set(pp, CMPP_RC_SYNTAX,
+                   "Delimiters may not contain "
+                   "control characters.");
+      return false;
+    }
+  }
+  if( zB==z ){
+    cmpp_err_set(pp, CMPP_RC_SYNTAX,
+                 "Delimiters may not be empty.");
+  }
+  return z>zB;
+}
+
+CMPP__EXPORT(int, cmpp_delimiter_set)(cmpp *pp, char const *zDelim){
+  if( ppCode ) return ppCode;
+  unsigned n;
+  if( zDelim ){
+    n = cmpp__strlen(zDelim, -1);
+    if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){
+      return ppCode;
+    }else if( n>12 /* arbitrary but seems sensible enough */ ){
+      return cmpp__err(pp, CMPP_RC_MISUSE,
+                       "Invalid delimiter (too long): %s", zDelim);
+    }
+  }
+  cmpp__pi(pp);
+  if( pi->delim.d.n ){
+    cmpp__delim * const delim = cmpp__pp_delim(pp);
+    if( !cmpp_check_oom(pp, delim) ){
+      cmpp__delim_cleanup(delim);
+      if( zDelim ){
+        delim->open.n = n;
+        delim->open.z = delim->zOwns =
+          (unsigned char*)sqlite3_mprintf("%.*s", n, zDelim);
+        cmpp_check_oom(pp, delim->zOwns);
+      }else{
+        assert( delim->open.z );
+        assert( !delim->zOwns );
+        assert( delim->open.n==sizeof(CMPP_DEFAULT_DELIM)-1 );
+      }
+    }
+  }else{
+    assert(!"Cannot set delimiter on an empty stack!");
+    cmpp_err_set(pp, CMPP_RC_MISUSE,
+                 "Directive delimter stack is empty.");
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(void, cmpp_delimiter_get)(cmpp const *pp, char const **zDelim){
+  cmpp__delim const * d = cmpp__pp_delim(pp);
+  if( !d ) d = &cmpp__delim_empty;
+  *zDelim = (char const *)d->open.z;
+}
+
+CMPP__EXPORT(int, cmpp_delimiter_push)(cmpp *pp, char const *zDelim){
+  cmpp__delim * const d =
+    cmpp__delim_list_push(pp, &pp->pimpl->delim.d);
+  if( d && cmpp_delimiter_set(pp, zDelim) ){
+    cmpp__delim_list_pop(&pp->pimpl->delim.d);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_delimiter_pop)(cmpp *pp){
+  cmpp__delim_list * const li = &pp->pimpl->delim.d;
+  if( li->n ){
+    //g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
+    cmpp__delim_list_pop(li);
+    if( 0 && li->n ){
+      g_warn("restored delimiter: %s", cmpp__pp_zdelim(pp));
+    }
+  }else if( !ppCode ){
+    assert(!"Attempt to pop an empty delimiter stack.");
+    cmpp_err_set(pp, CMPP_RC_MISUSE,
+                 "Cannot pop an empty delimiter stack.");
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_atdelim_set)(cmpp * const pp,
+                                    char const *zOpen,
+                                    char const *zClose){
+  if( 0==ppCode ){
+    cmpp__pi(pp);
+    cmpp__delim * const d = pi->delim.at.n
+      ? &pi->delim.at.list[pi->delim.at.n-1]
+      : NULL;
+    assert( d );
+    if( !d ){
+      return cmpp__err(pp, CMPP_RC_MISUSE,
+                       "@token@ delimiter stack is currently empty.");
+    }
+    if( 0==zOpen ){
+      zOpen = (char const *)delimAtDefault.open.z;
+      zClose = (char const *)delimAtDefault.close.z;
+    }else if( 0==zClose ){
+      zClose = zOpen;
+    }
+    cmpp_size_t const nO = cmpp__strlen(zOpen, -1);
+    cmpp_size_t const nC = cmpp__strlen(zClose, -1);
+    assert( zOpen && zClose );
+    if( !cmpp__valid_delim(pp, zOpen, zOpen+nO)
+        || !cmpp__valid_delim(pp, zClose, zClose+nC) ){
+      return ppCode;
+    }
+    cmpp_b b = cmpp_b_empty
+      /* Don't use cmpp_b_borrow() here because we'll unconditionally
+         transfer ownership of b.z to d. */;
+    if( 0==cmpp_b_reserve3(pp, &b, nO + nC + 2) ){
+#ifndef NDEBUG
+      unsigned char const * const zReallocCheck = b.z;
+#endif
+      /* Copy the open/close tokens to a single string to simplify
+         management. */
+      cmpp_b_append4(pp, &b, zOpen, nO);
+      cmpp_b_append_ch(&b, '\0');
+      cmpp_b_append4(pp, &b, zClose, nC);
+      assert( zReallocCheck==b.z
+              && "Else buffer was not properly pre-sized" );
+      cmpp__delim_cleanup(d);
+      d->open.z = b.z;
+      d->open.n = nO;
+      d->close.z = d->open.z + nO + 1/*NUL*/;
+      d->close.n = nC;
+      d->zOwns = b.z;
+      b = cmpp_b_empty /* transfer memory ownership */;
+    }
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_atdelim_push)(cmpp *pp, char const *zOpen,
+                                     char const *zClose){
+  cmpp__delim * const d =
+    cmpp__delim_list_push(pp, &pp->pimpl->delim.at);
+  if( d && cmpp_atdelim_set(pp, zOpen, zClose) ){
+    cmpp__delim_list_pop(&pp->pimpl->delim.at);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_atdelim_pop)(cmpp *pp){
+  cmpp__delim_list * const li = &pp->pimpl->delim.at;
+  if( li->n ){
+    //g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
+    cmpp__delim_list_pop(li);
+  }else if( !ppCode ){
+    assert(!"Attempt to pop an empty @token@ delim stack.");
+    cmpp_err_set(pp, CMPP_RC_MISUSE,
+                 "Cannot pop an empty @token@ delimiter stack.");
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(void, cmpp_atdelim_get)(cmpp const * const pp,
+                                     char const **zOpen,
+                                     char const **zClose){
+  cmpp__delim const * d
+    = cmpp__delim_list_get(&pp->pimpl->delim.at);
+  assert( d );
+  if( !d ) d = &delimAtDefault;
+  if( zClose ) *zClose = (char const *)d->close.z;
+  if( zOpen ) *zOpen = (char const *)d->open.z;
+}
+
+#define cmpp__scan_int2(SZ,PFMT,Z,N,TGT)        \
+  (N<SZ) && 1==sscanf((char const *)Z, "%" #SZ PFMT, TGT)
+
+bool cmpp__is_int(unsigned char const *z, unsigned n,
+                  int *pOut){
+  int d = 0;
+  return cmpp__scan_int2(16, PRIi32, z, n, pOut ? pOut : &d);
+}
+
+bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut){
+  int64_t d = 0;
+  return cmpp__scan_int2(24, PRIi64, z, n, pOut ? pOut : &d);
+}
+#undef cmpp__scan_int2
+
+/**
+   Impl for the -Fx=filename flag.
+
+   TODO?: refactor to take an optional zVal to make it suitable for
+   the public API. This impl requires that zKey contain
+   "key=filename".
+*/
+static int cmpp__set_file(cmpp *pp, unsigned const char * zKey,
+                          cmpp_ssize_t nKey){
+  if(ppCode) return ppCode;
+  CmppKvp kvp = CmppKvp_empty;
+  nKey = cmpp__strlenu(zKey, nKey);
+  if( CmppKvp_parse(pp, &kvp, zKey, nKey, CmppKvp_op_eq1) ){
+    return ppCode;
+  }
+  sqlite3_stmt * q = 0;
+  FileWrapper fw = FileWrapper_empty;
+  if( cmpp__FileWrapper_open(pp, &fw, (char const *)kvp.v.z, "rb") ){
+    assert(ppCode);
+    return ppCode;
+  }
+  cmpp__FileWrapper_slurp(pp, &fw);
+  q = cmpp__stmt(pp, CmppStmt_defIns, false);
+  if( q && 0==cmpp__bind_textn(pp, q, 2, kvp.k.z, (int)kvp.k.n) ){
+    //g_warn("zKey=%.*s", (int)kvp.k.n, kvp.k.z);
+    if( pp->pimpl->flags.chompF ){
+      FileWrapper_chomp(&fw);
+    }
+    if( fw.nContent ){
+      cmpp__bind_textx(pp, q, 3, fw.zContent,
+                       (cmpp_ssize_t)fw.nContent, sqlite3_free);
+      fw.zContent = 0 /* transferred ownership */;
+      fw.nContent = 0;
+    }else{
+      cmpp__bind_null(pp, q, 2);
+    }
+    cmpp__step(pp, q, true);
+    g_debug(pp,2,("define: %s%s%s\n",
+                  kvp.k.z,
+                  kvp.v.z ? " with value " : "",
+                  kvp.v.z ? (char const *)kvp.v.z : ""));
+  }
+  FileWrapper_close(&fw);
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_has)(cmpp *pp, const char * zName, cmpp_ssize_t nName){
+  int rc = 0;
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false);
+  if( q ){
+    nName = cmpp__strlen(zName, nName);
+    cmpp__bind_textn(pp, q, 1, ustr_c(zName), nName);
+    if(SQLITE_ROW == cmpp__step(pp, q, true)){
+      rc = 1;
+    }else{
+      rc = 0;
+    }
+    g_debug(pp,1,("has [%s] ?= %d\n",zName, rc));
+  }
+  return rc;
+}
+
+int cmpp__get_bool(cmpp *pp, unsigned const char *zName, cmpp_ssize_t nName){
+  int rc = 0;
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false);
+  if( q ){
+    nName = cmpp__strlenu(zName, nName);
+    cmpp__bind_textn(pp, q, 1, zName, nName);
+    assert(0==ppCode);
+    if(SQLITE_ROW == cmpp__step(pp, q, false)){
+      rc = sqlite3_column_int(q, 0);
+    }else{
+      rc = 0;
+      cmpp__affirm_undef_policy(pp, zName, nName);
+    }
+    cmpp__stmt_reset(q);
+  }
+  return rc;
+}
+
+int cmpp__get_int(cmpp *pp, unsigned const char * zName,
+                  cmpp_ssize_t nName, int *pOut ){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false);
+  if( q ){
+    nName = cmpp__strlenu(zName, nName);
+    cmpp__bind_textn(pp, q, 1, zName, nName);
+    assert(0==ppCode);
+    if(SQLITE_ROW == cmpp__step(pp, q, false)){
+      *pOut = sqlite3_column_int(q,0);
+    }else{
+      cmpp__affirm_undef_policy(pp, zName, nName);
+    }
+    cmpp__stmt_reset(q);
+  }
+  return ppCode;
+}
+
+int cmpp__get_b(cmpp *pp, unsigned const char * zName,
+                cmpp_ssize_t nName, cmpp_b * os, bool enforceUndefPolicy){
+  int rc = 0;
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false);
+  if( q ){
+    nName = cmpp__strlenu(zName, nName);
+    cmpp__bind_textn(pp, q, 1, zName, nName);
+    int n = 0;
+    if(SQLITE_ROW == cmpp__step(pp, q, false)){
+      const unsigned char * z = sqlite3_column_text(q, 3);
+      n = sqlite3_column_bytes(q, 3);
+      cmpp_b_append4(pp, os, z, (cmpp_size_t)n);
+      rc = 1;
+    }else{
+      if( enforceUndefPolicy ){
+        cmpp__affirm_undef_policy(pp, zName, nName);
+      }
+      rc = 0;
+    }
+    cmpp__stmt_reset(q);
+    g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n",
+                  nName, zName, rc, os->n, os->z));
+  }
+  return rc;
+}
+
+int cmpp__get(cmpp *pp, unsigned const char * zName,
+              cmpp_ssize_t nName, unsigned char **zVal,
+              unsigned int *nVal){
+  int rc = 0;
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false);
+  if( q ){
+    nName = cmpp__strlenu(zName, nName);
+    cmpp__bind_textn(pp, q, 1, zName, nName);
+    int n = 0;
+    if(SQLITE_ROW == cmpp__step(pp, q, false)){
+      const unsigned char * z = sqlite3_column_text(q, 3);
+      n = sqlite3_column_bytes(q, 3);
+      if( nVal ) *nVal = (unsigned)n;
+      *zVal = ustr_nc(sqlite3_mprintf("%.*s", n, z))
+        /* TODO? Return NULL for the n==0 case? */;
+      if( n && cmpp_check_oom(pp, *zVal) ){
+        assert(!*zVal);
+      }else{
+        rc = 1;
+      }
+    }else{
+      cmpp__affirm_undef_policy(pp, zName, nName);
+      rc = 0;
+    }
+    cmpp__stmt_reset(q);
+    g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n",
+                  nName, zName, rc,
+                  *zVal ? n : 0,
+                  *zVal ? (char const *)*zVal : "<NULL>"));
+  }
+  return rc;
+}
+
+CMPP__EXPORT(int, cmpp_undef)(cmpp *pp, const char * zKey,
+                                unsigned int *nRemoved){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDel, false);
+  if( q ){
+    unsigned int const n = strlen(zKey);
+    cmpp__bind_textn(pp, q, 1, ustr_c(zKey), (cmpp_ssize_t)n);
+    cmpp__step(pp, q, true);
+    if( nRemoved ){
+      *nRemoved = (unsigned)sqlite3_changes(pp->pimpl->db.dbh);
+    }
+    g_debug(pp,2,("undefine: %.*s\n",n, zKey));
+  }
+  return ppCode;
+}
+
+int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid){
+  if( pRowid ) *pRowid = 0;
+  if( !ppCode && zDir && *zDir ){
+    sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathAdd, false);
+    if( q ){
+      /* TODO: normalize zDir before insertion so that a/b and a/b/
+         are equivalent.  The relavent code is in another tree,
+         awaiting a decision on whether to import it or re-base cmpp
+         on top of that library (which would, e.g., replace cmpp_b
+         with that one, which is more mature).
+      */
+      cmpp__bind_int(pp, q, 1, priority);
+      cmpp__bind_textn(pp, q, 2, ustr_c(zDir), -1);
+      int const rc = cmpp__step(pp, q, false);
+      if( SQLITE_ROW==rc ){
+        ++pp->pimpl->flags.nIncludeDir;
+        if( pRowid ){
+          *pRowid = sqlite3_column_int64(q, 0);
+        }
+      }
+      cmpp__stmt_reset(q);
+      /*g_warn("inclpath add: rc=%d rowid=%" PRIi64 " prio=%d %s",
+        rc, pRowid ? *pRowid : 0, priority, zDir);*/
+      g_debug(pp,2,("inclpath add: prio=%d %s\n", priority, zDir));
+    }
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_include_dir_add)(cmpp *pp, const char * zDir){
+  return cmpp__include_dir_add(pp, zDir, 0, NULL);
+}
+
+int cmpp__include_dir_rm_id(cmpp *pp, int64_t rowid){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
+  if( q ){
+    /* Hoop-jumping to allow this to work even if pp's in an error
+       state. */
+    int rc = sqlite3_bind_int64(q, 1, rowid);
+    if( 0==rc ){
+      rc = sqlite3_step(q);
+      if( SQLITE_ROW==rc ){
+        --pp->pimpl->flags.nIncludeDir;
+        rc = 0;
+      }else if( SQLITE_DONE==rc ){
+        rc = 0;
+      }
+    }
+    if( rc && !ppCode ){
+      cmpp__db_rc(pp, rc, sqlite3_sql(q));
+    }
+    cmpp__stmt_reset(q);
+    g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid));
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_module_dir_add)(cmpp *pp, const char * zDirs){
+#if CMPP_ENABLE_DLLS
+  if( !ppCode ){
+    cmpp_b * const ob = &pp->pimpl->mod.path;
+    if( !zDirs && !ob->n ){
+      zDirs = getenv("CMPP_MODULE_PATH");
+      if( !zDirs ){
+        zDirs = CMPP_MODULE_PATH;
+      }
+    }
+    if( !zDirs || !*zDirs ) return 0;
+    char const * z = zDirs;
+    char const * const zEnd = zDirs + strlen(zDirs);
+    if( 0==cmpp_b_reserve3(pp, ob, ob->n + (zEnd - z) + 3) ){
+      unsigned char * zo = ob->z + ob->n;
+      unsigned i = 0;
+      for( ; z < zEnd && !ppCode; ++z ){
+        switch( *z ){
+          case CMPP_PATH_SEPARATOR:
+            *zo++ = pp->pimpl->mod.pathSep;
+            break;
+          default:
+            if( 1==++i && ob->n ){
+              cmpp_b_append_ch(ob, pp->pimpl->mod.pathSep);
+            }
+            *zo++ = *z;
+            break;
+        }
+      }
+      *zo = 0;
+      ob->n = (zo - ob->z);
+    }
+  }
+  return ppCode;
+#else
+  return CMPP_RC_UNSUPPORTED;
+#endif
+}
+
+CMPP__EXPORT(int, cmpp_db_name_set)(cmpp *pp, const char * zName){
+  if( 0==ppCode ){
+    cmpp__pi(pp);
+    if( pi->db.dbh ){
+      return cmpp__err(pp, CMPP_RC_MISUSE,
+                       "DB name cannot be set after db initialization.");
+    }
+    if( zName ){
+      char * const z = sqlite3_mprintf("%s", zName);
+      if( 0==cmpp_check_oom(pp, z) ){
+        cmpp_mfree(pi->db.zName);
+        pi->db.zName = z;
+      }
+    }else{
+      cmpp_mfree(pi->db.zName);
+      pi->db.zName = 0;
+    }
+  }
+  return ppCode;
+}
+
+bool cmpp__is_legal_key(unsigned char const *zName,
+                        cmpp_size_t n,
+                        unsigned char const **zErrPos,
+                        bool equalIsLegal){
+  if( !n || n>64/*arbitrary*/ ){
+    if( zErrPos ) *zErrPos = 0;
+    return false;
+  }
+  unsigned char const * z = zName;
+  unsigned char const * const zEnd = zName + n;
+  for( ; z<zEnd; ++z ){
+    if( !((*z>='a' && *z<='z')
+          || (*z>='A' && *z<='Z')
+          || (z>zName &&
+              ('-'==*z
+               /* This is gonna bite us if we extend the expresions to
+                  support +/-. Expressions currently parse X=Y (no
+                  spaces) as the three tokens X = Y, but we'd need to
+                  require a space between X-Y in expressions because
+                  '-' is a legal symbol character. i've looked at
+                  making '-' illegal but it's just too convenient for
+                  use in define keys.  Once one is used to
+                  tcl-style-naming of stuff, it's painful to have to go
+                  back to snake_case.
+               */
+               || (*z>='0' && *z<='9')))
+          || (*z>='.' && *z<='/')
+          || (*z==':')
+          || (*z=='_')
+          || (equalIsLegal && z>zName && '='==*z)
+          || (*z & 0x80)
+        ) ){
+      if( zErrPos ) *zErrPos = z;
+      return false;
+    }
+  }
+  return true;
+}
+
+bool cmpp_is_legal_key(unsigned char const *zName,
+                       cmpp_size_t n,
+                       unsigned char const **zErrPos){
+  return cmpp__is_legal_key(zName, n, zErrPos, false);
+}
+
+int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
+                          cmpp_ssize_t nKey, bool permitEqualSign){
+  if( !ppCode ){
+    unsigned char const *zAt = 0;
+    nKey = cmpp__strlenu(zKey, nKey);
+    if( !cmpp__is_legal_key(zKey, nKey, &zAt, permitEqualSign) ){
+      cmpp__err(pp, CMPP_RC_SYNTAX,
+                "Illegal character 0x%02x in key [%.*s]",
+                (int)*zAt, nKey, zKey);
+    }
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(bool, cmpp_next_chunk)(unsigned char const **zPos,
+                     unsigned char const *zEnd,
+                     unsigned char chSep,
+                     cmpp_size_t *pCounter){
+  assert( zPos );
+  assert( *zPos );
+  assert( zEnd );
+  if( *zPos >= zEnd ) return false;
+  unsigned char const * z = *zPos;
+  while( z<zEnd ){
+    if( chSep==*z ){
+      ++z;
+      if( pCounter ) ++*pCounter;
+      break;
+    }
+    ++z;
+  }
+  if( *zPos==z ) return false;
+  *zPos = z;
+  return true;
+}
+
+/**
+   Scans dx for the next newline. It updates ln to contain the result,
+   which includes the trailing newline unless EOF is hit before a
+   newline.
+
+   Returns true if it has input, false at EOF. It has no error
+   conditions beyond invalid dx->pimpl state, which "doesn't happen".
+*/
+//static
+bool cmpp__dx_next_line(cmpp_dx * const dx, CmppDLine *ln){
+  assert( !dxppCode );
+  cmpp_dx_pimpl * const dxp = dx->pimpl;
+  if(!dxp->pos.z) dxp->pos.z = dxp->zBegin;
+  assert( dxp->zEnd );
+  if( dxp->pos.z>=dxp->zEnd ){
+    return false;
+  }
+  assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n')
+          && "Else we've mismanaged something.");
+  cmpp__dx_pi(dx);
+  ln->lineNo = dpi->pos.lineNo;
+  ln->zBegin = dpi->pos.z;
+  ln->zEnd = ln->zBegin;
+  return cmpp_next_chunk(&ln->zEnd, dpi->zEnd, (unsigned char)'\n',
+                         &dpi->pos.lineNo);
+}
+
+/**
+   Scans [dx->pos.z,dx->zEnd) for a directive delimiter. Emits any
+   non-delimiter output found along the way to dx->pp's output
+   channel.
+
+   This updates dx->pimpl->pos.z and dx->pimpl->pos.lineNo as it goes.
+
+   If a delimiter is found, it sets *gotOne to true and updates
+   dx->pimpl->dline to point to the remainder of that line. On no match
+   *gotOne will be false and EOF will have been reached.
+
+   Returns dxppCode. If it returns non-0 then the state of dx's
+   tokenization pieces are unspecified. i.e. it's illegal to call this
+   again without a reset.
+*/
+static int cmpp_dx_delim_search(cmpp_dx * const dx, bool * gotOne){
+  if( dxppCode ) return dxppCode;
+  cmpp_dx_pimpl * const dxp = dx->pimpl;
+  if(!dxp->pos.z) dxp->pos.z = dxp->zBegin;
+  if( dxp->pos.z>=dxp->zEnd ){
+    *gotOne = false;
+    return 0;
+  }
+  assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n')
+          && "Else we've mismanaged something.");
+  cmpp__pi(dx->pp);
+  cmpp__delim const * const delim = cmpp__dx_delim(dx);
+  if(!delim) {
+    return cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                           "The directive delimiter stack is empty.");
+  }
+  unsigned char const * const zD = delim->open.z;
+  unsigned short const nD = delim->open.n;
+  unsigned char const * const zEnd = dxp->zEnd;
+  unsigned char const * zLeft = dxp->pos.z;
+  unsigned char const * z = zLeft;
+  assert(zD);
+  assert(nD);
+#if 0
+  assert( 0==*zEnd && "Else we'll misinteract with strcspn()" );
+  if( *zEnd ){
+    return cmpp_dx_err(dx, CMPP_RC_RANGE,
+                      "Input must be NUL-terminated.");
+  }
+#endif
+  ++dxp->flags.countLines;
+  while( z<zEnd && '\n'==*z ){
+    /* Skip leading newlines. We have to delay the handling of
+       leading whitepace until later so that:
+
+       |  #if
+       |^^ those two spaces do not get emitted.
+    */
+    ++dxp->pos.lineNo;
+    ++z;
+  }
+#define tflush                                              \
+  if( z>zEnd ) z=zEnd;                                      \
+  if( z>zLeft && cmpp_dx_out_expand(dx, &pi->out, zLeft,    \
+                                    (cmpp_size_t)(z-zLeft), \
+                                    cmpp_atpol_CURRENT) ){  \
+    --dxp->flags.countLines;                                \
+    return dxppCode;                                        \
+  } zLeft = z
+
+  CmppDLine * const dline = &dxp->dline;
+  bool atBOL = true /* At the start of a line? Successful calls to
+                       this always end at either BOL or EOF. */;
+  if( 0 ){
+    g_warn("scanning... <<%.*s...>>",
+           (zEnd-z)>20?20:(zEnd-z), z);
+  }
+  while( z<zEnd && !dxppCode ){
+    if( !atBOL ){
+      /* We're continuing the scan of a line on which the first bytes
+         didn't match a delimiter. */
+      while( z<zEnd ){
+        while((z<zEnd && '\n'==*z)
+              || (z+1<zEnd && '\r'==*z && '\n'==z[1]) ){
+          ++dxp->pos.lineNo;
+          z += 1 + ('\r'==*z);
+          atBOL = true;
+        }
+        if( atBOL ){
+          break;
+        }
+        ++z;
+      }
+      if( !atBOL ) break;
+    }
+    if( 0 ){
+      g_warn("at BOL... <<%.*s...>>",
+             (zEnd-z) > 20 ? 20 : (zEnd-z), z);
+    }
+
+    /* We're at BOL. Check for a delimiter with optional leading
+       spaces. */
+    tflush;
+    cmpp_skip_space(&z, zEnd);
+    int const skip = cmpp_isnl(z, zEnd);
+    if( skip ){
+      /* Special case: a line comprised solely of whitespace. If we
+         don't catch this here, we won't recognize a delimiter which
+         starts on the next line. */
+      tflush;
+      z += skip;
+      ++dxp->pos.lineNo;
+      continue;
+    }
+    if( 0 ){
+      g_warn("at BOL... <<%.*s...>>",
+             (zEnd-z) > 20 ? 20 : (zEnd-z), z);
+    }
+    if( z + nD>zEnd ){
+      /* Too short for a delimiter. We'll catch the z+nD==zEnd corner
+         case in a moment. */
+      z = zEnd;
+      break;
+    }
+    if( memcmp(z, zD, nD) ){
+      /* Not a delimiter. Keep trying. */
+      atBOL = false;
+      ++z;
+      continue;
+    }
+
+    /* z now points to a delimiter which sits at the start of a line
+       (ignoring leading spaces). */
+    z += nD /* skip the delimiter */;
+    cmpp_skip_space(&z, zEnd) /* skip spaces immediately following
+                                    the delimiter. */;
+    if( z>=zEnd || cmpp_isnl(z, zEnd) ){
+      dxserr("No directive name found after %s.", zD);
+      /* We could arguably treat this as no match and pass this line
+         through as-is but that currently sounds like a pothole. */
+      break;
+    }
+    /* Set up dx->pimpl->dline to encompass the whole directive line sans
+       delimiter and leading spaces. */
+    dline->zBegin = z
+      /* dx->pimpl->dline starts at the directive name and extends until the
+         next EOL/EOF. We don't yet know if it's a legal directive
+         name - cmpp_dx_next() figures that part out. */;
+    dline->lineNo = dxp->pos.lineNo;
+    /* Now find the end of the line or EOF, accounting for
+       backslash-escaped newlines and _not_ requiring backslashes to
+       escape newlines inside of {...}, (...), or [...]. We could also
+       add the double-quotes to this, but let's start without that. */
+    bool keepGoing = true;
+    zLeft = z;
+    while( keepGoing && z<zEnd ){
+      switch( *z ){
+        case '(': case '{': case '[':{
+          zLeft = z;
+          if( cmpp__find_closing2(dx->pp, &z, zEnd, &dxp->pos.lineNo) ){
+            --dxp->flags.countLines;
+            return dxppCode;
+          }
+          ++z /* group-closing character */;
+          /*
+            Sidebar: this only checks top-level groups. It is
+            possible that an inner group is malformed, e.g.:
+
+            { ( }
+
+            It's also possible that that's perfectly legal for a
+            specific use case.
+
+            Such cases will, if they're indeed syntax errors, be
+            recognized as such in the arguments-parsing
+            steps. Catching them here would require that we
+            recursively validate all of [zLeft,z) for group
+            constructs, whereas that traversal happens as a matter of
+            course in argument parsing. It would also require the
+            assumption that such constructs are not legal, which is
+            invalid once we start dealing with free-form input like
+            #query SQL.
+          */
+          break;
+        }
+        case '\n':
+          assert( z!=dline->zBegin && "Checked up above" );
+          if( '\\'==z[-1]
+              || (z>zLeft+1 && '\r'==z[-1] && '\\'==z[-2]) ){
+            /* Backslash-escaped newline. */
+            ++z;
+          }else{
+            /* EOL for this directive. */
+            keepGoing = false;
+          }
+          ++dxp->pos.lineNo;
+          break;
+        default:
+          ++z;
+      }
+    }
+    assert( z==zEnd || '\n'==*z );
+    dline->zEnd = z;
+    dxp->pos.z = dline->zEnd + 1
+      /* For the next call to this function, skip the trailing newline
+         or EOF */;
+    assert( dline->zBegin < dline->zEnd && "Was checked above" );
+    if( 0 ){
+      g_warn("line= %u <<%.*s>>", (dline->zEnd-dline->zBegin),
+             (dline->zEnd-dline->zBegin), dline->zBegin);
+    }
+    *gotOne = true;
+    assert( !dxppCode );
+    --dxp->flags.countLines;
+    return 0;
+  }
+  /* No directives found. We're now at EOL or EOF. Flush any pending
+     LHS content. */
+  tflush;
+  dx->pimpl->pos.z = z;
+  *gotOne = false;
+  return dxppCode;
+#undef tflush
+}
+
+int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey,
+                   cmpp_ssize_t nKey, CmppKvp_op_e opPolicy){
+  if(ppCode) return ppCode;
+  char chEq = 0;
+  char opLen = 0;
+  *p = CmppKvp_empty;
+  p->k.z = zKey;
+  p->k.n = cmpp__strlenu(zKey, nKey);
+  switch( opPolicy ){
+    case CmppKvp_op_none:// break;
+    case CmppKvp_op_eq1:
+      chEq = '=';
+      opLen = 1;
+      break;
+    default:
+      assert(!"don't use these");
+      /* no longer todo: ==, !=, <=, <, >, >= */
+      chEq = '=';
+      opLen = 1;
+      break;
+  }
+  assert( chEq );
+  p->op = CmppKvp_op_none;
+  unsigned const char * const zEnd = p->k.z + p->k.n;
+  for(unsigned const char * zPos = p->k.z ; *zPos && zPos<zEnd ; ++zPos) {
+    if( chEq==*zPos ){
+      if( CmppKvp_op_none==opPolicy ){
+        cmpp__err(pp, CMPP_RC_SYNTAX,
+                      "Illegal operator in key: %s", zKey);
+      }else{
+        p->op = CmppKvp_op_eq1;
+        p->k.n = (unsigned)(zPos - ustr_c(zKey));
+        zPos += opLen;
+        assert( zPos <= zEnd );
+        p->v.z = zPos;
+        p->v.n = (unsigned)(zEnd - zPos);
+      }
+      break;
+    }
+  }
+  cmpp__legal_key_check(pp, p->k.z, p->k.n, false);
+  return ppCode;
+}
+
+int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
+                      cmpp_size_t * nAlloc, unsigned sizeOfEntry){
+  int rc = pp ? ppCode : 0;
+  if( 0==rc && nDesired > *nAlloc ){
+    cmpp_size_t const nA = nDesired < 10 ? 10 : nDesired;
+    void * const p = cmpp_mrealloc(*list, sizeOfEntry * nA);
+    rc = cmpp_check_oom(pp, p);
+    if( p ){
+      memset((unsigned char *)p +
+             (sizeOfEntry * *nAlloc), 0,
+             sizeOfEntry * (nA - *nAlloc));
+      *list = p;
+      *nAlloc = nA;
+    }
+  }
+  return rc;
+}
+
+CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li){
+  CmppLvl * p = 0;
+  assert( li->list ? li->nAlloc : 0==li->nAlloc );
+  if( 0==ppCode
+      && 0==CmppLvlList_reserve(pp, li,
+                                cmpp__li_reserve1_size(li,5)) ){
+    p = li->list[li->n];
+    if( !p ){
+      p = cmpp__malloc(pp, sizeof(*p));
+    }
+    if( p ){
+      li->list[li->n++] = p;
+      *p = CmppLvl_empty;
+    }
+  }
+  return p;
+}
+
+void CmppLvlList_pop(cmpp * const pp, CmppLvlList * const li,
+                     CmppLvl * const lvl){
+  assert( li->n );
+  if( li->n ){
+    if( lvl==li->list[li->n-1] ){
+      *lvl = CmppLvl_empty;
+      cmpp_mfree(lvl);
+      li->list[--li->n] = 0;
+    }else{
+      if( pp ){
+        cmpp_err_set(pp, CMPP_RC_ASSERT,
+                     "Misuse of %s(): not passed the top of the stack. "
+                     "The CmppLvl stack is now out of whack.",
+                     __func__);
+      }else{
+        cmpp__fatal("Misuse of %s(): not passed the top of the stack",
+              __func__);
+      }
+      /* do not free it - CmppLvlList_cleanup() will catch it. */
+    }
+  }
+}
+
+void CmppLvlList_cleanup(CmppLvlList *li){
+  const CmppLvlList CmppLvlList_empty = CmppLvlList_empty_m;
+  while( li->nAlloc ){
+    cmpp_mfree( li->list[--li->nAlloc] );
+  }
+  cmpp_mfree(li->list);
+  *li = CmppLvlList_empty;
+}
+
+static inline void CmppDList_entry_clean(CmppDList_entry * const e){
+  if( e->d.impl.dtor ){
+    e->d.impl.dtor( e->d.impl.state );
+  }
+  cmpp_mfree(e->zName);
+  *e = CmppDList_entry_empty;
+}
+
+#if 0
+CmppDList * CmppDList_reuse(CmppDList *li){
+  while( li->n ){
+    CmppDList_entry_clean( li->list[--li->n] );
+  }
+  return li;
+}
+#endif
+
+void CmppDList_cleanup(CmppDList *li){
+  static const CmppDList CmppDList_empty = CmppDList_empty_m;
+  while( li->n ){
+    CmppDList_entry_clean( li->list[--li->n] );
+    cmpp_mfree( li->list[li->n] );
+    li->list[li->n] = 0;
+  }
+  cmpp_mfree(li->list);
+  *li = CmppDList_empty;
+}
+
+void CmppDList_unappend(CmppDList *li){
+  assert( li->n );
+  if( li->n ){
+    CmppDList_entry_clean(li->list[--li->n]);
+  }
+}
+
+
+/** bsearch()/qsort() comparison for (cmpp_d**), sorting by name. */
+static
+int CmppDList_entry_cmp_pp(const void *p1, const void *p2){
+  CmppDList_entry const * eL = *(CmppDList_entry const * const *)p1;
+  CmppDList_entry const * eR = *(CmppDList_entry const * const *)p2;
+  return eL->d.name.n==eR->d.name.n
+    ? memcmp(eL->d.name.z, eR->d.name.z, eL->d.name.n)
+    : strcmp((char const *)eL->d.name.z,
+             (char const *)eR->d.name.z);
+}
+
+static void CmppDList_sort(CmppDList * const li){
+  if( li->n>1 ){
+    qsort(li->list, li->n, sizeof(CmppDList_entry*),
+          CmppDList_entry_cmp_pp);
+  }
+}
+
+CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li){
+  CmppDList_entry * p = 0;
+  assert( li->list ? li->nAlloc : 0==li->nAlloc );
+  if( 0==ppCode
+      && 0==cmpp_array_reserve(pp, (void **)&li->list,
+                               cmpp__li_reserve1_size(li, 15),
+                               &li->nAlloc, sizeof(p)) ){
+    p = li->list[li->n];
+    if( !p ){
+      li->list[li->n] = p = cmpp__malloc(pp, sizeof(*p));
+    }
+    if( p ){
+      ++li->n;
+      *p = CmppDList_entry_empty;
+    }
+  }
+  return p;
+}
+
+CmppDList_entry * CmppDList_search(CmppDList const * li,
+                                       char const *zName){
+  if( li->n > 2 ){
+    CmppDList_entry const key = {
+      .d = {
+        .name = {
+          .z = zName,
+          .n = strlen(zName)
+        }
+      }
+    };
+    CmppDList_entry const * pKey = &key;
+    CmppDList_entry ** pRv
+      = bsearch(&pKey, li->list, li->n, sizeof(li->list[0]),
+                CmppDList_entry_cmp_pp);
+    //g_warn("search in=%s out=%s", zName, (pRv ? (*pRv)->d.name.z : "<null>"));
+    return pRv ? *pRv : 0;
+  }else{
+    cmpp_size_t const nName = cmpp__strlen(zName, -1);
+    for( cmpp_size_t i = 0; i < li->n; ++i ){
+      CmppDList_entry * const e = li->list[i];
+      if( nName==e->d.name.n && 0==strcmp(zName, e->d.name.z) ){
+        //g_warn("search in=%s out=%s", zName, e->d.name.z);
+        return e;
+      }
+    }
+    return 0;
+  }
+}
+
+void cmpp__delim_cleanup(cmpp__delim *d){
+  cmpp__delim const dd = cmpp__delim_empty_m;
+  cmpp_mfree(d->zOwns);
+  *d = dd;
+  assert(!d->zOwns);
+  assert(d->open.z);
+  assert(0==strcmp((char*)d->open.z, CMPP_DEFAULT_DELIM));
+  assert(d->open.n == sizeof(CMPP_DEFAULT_DELIM)-1);
+}
+
+cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li){
+  cmpp__delim * p = 0;
+  assert( li->list ? li->nAlloc : 0==li->nAlloc );
+  if( 0==ppCode
+      && 0==cmpp_array_reserve(pp, (void **)&li->list,
+                               cmpp__li_reserve1_size(li,4),
+                               &li->nAlloc, sizeof(cmpp__delim)) ){
+    p = &li->list[li->n++];
+    *p = cmpp__delim_empty;
+  }
+  return p;
+}
+
+void cmpp__delim_list_cleanup(cmpp__delim_list *li){
+  while( li->nAlloc ) cmpp__delim_cleanup(li->list + --li->nAlloc);
+  cmpp_mfree(li->list);
+  *li = cmpp__delim_list_empty;
+}
+
+CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){
+  if( dxppCode ) return dxppCode;
+
+  CmppDLine * const tok = &dx->pimpl->dline;
+  if( !dx->pimpl->zBegin ){
+    *pGotOne = false;
+    return 0;
+  }
+  assert(dx->pimpl->zEnd);
+  assert(dx->pimpl->zEnd > dx->pimpl->zBegin);
+  *pGotOne = false;
+  cmpp_dx__reset(dx);
+  bool foundDelim = false;
+  if( cmpp_dx_delim_search(dx, &foundDelim) || !foundDelim ){
+    return dxppCode;
+  }
+  if( cmpp_args__init(dx->pp, &dx->pimpl->args) ){
+    return dxppCode;
+  }
+  cmpp_skip_space( &tok->zBegin, tok->zEnd );
+  g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n",
+                   tok->lineNo,
+                   (int)(tok->zEnd-tok->zBegin), tok->zBegin));
+  /* Normalize the directive's line and parse arguments */
+  const unsigned lineLen = (unsigned)(tok->zEnd - tok->zBegin);
+  if(!lineLen){
+    return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
+                       "Line #%u has no directive after %s",
+                       tok->lineNo, cmpp_dx_delim(dx));
+  }
+  unsigned char const * zi = tok->zBegin /* Start of input */;
+  unsigned char const * ziEnd = tok->zEnd /* Input EOF */;
+  cmpp_b * const bufLine =
+    cmpp_b_reuse(&dx->pimpl->buf.line)
+    /* Slightly-transformed copy of the input. */;
+  if( cmpp_b_reserve3(dx->pp, bufLine, lineLen+1) ){
+    return dxppCode;
+  }
+  unsigned char * zo = bufLine->z /* Start of output */;
+  unsigned char const * const zoEnd =
+    zo + bufLine->nAlloc /* Output EOF. */;
+  g_debug(dx->pp,2,("Directive @ line %u len=%u <<%.*s>>\n",
+                    tok->lineNo, lineLen, lineLen, tok->zBegin));
+  //memset(bufLine->z, 0, bufLine->nAlloc);
+#define out(CH) if(zo==zoEnd) break; (*zo++)=CH
+  /*
+      bufLine is now populated with a copy of the whole input line.
+      Now normalize that buffer a bit before trying to parse it.
+  */
+  unsigned char const * zEsc = 0;
+  cmpp_dx_pimpl * const pimpl = dx->pimpl;
+  for( ; zi<ziEnd && *zi && zo<zoEnd;
+       ++zi ){
+    /* Write the line to bufLine for the upcoming args parsing to deal
+       with. Strip backslashes from backslash-escaped newlines.  We
+       leave the newlines intact so that downstream error reporting
+       can get more precise location info. Backslashes which do not
+       precede a newline are retained.
+    */
+    switch((int)*zi){
+      case (int)'\\':
+        if( !zi[1] || zi==ziEnd-1 ){
+          // special case: ending input with a backslash
+          out(*zi);
+          zEsc = 0;
+        }else if( zEsc ){
+          assert( zEsc==zi-1 );
+          /* Put them both back. */
+          out(*zEsc);
+          out(*zi);
+          zEsc = 0;
+        }else{
+          zEsc = zi;
+        }
+        break;
+      case (int)'\n':
+        out(*zi);
+        zEsc = 0;
+        break;
+      default:
+        if(zEsc){
+          assert( zEsc==zi-1 );
+          out(*zEsc);
+          zEsc = 0;
+        }
+        out(*zi);
+        break;
+    }
+  }
+  if( zo>=zoEnd ){
+    return cmpp_dx_err(dx, CMPP_RC_RANGE,
+                      "Ran out of argument-processing space.");
+  }
+  *zo = 0;
+#undef out
+  bufLine->n = (cmpp_size_t)(zo - bufLine->z);
+  if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z);
+  /* Line has now been normalized into bufLine->z. */
+  for( zo = bufLine->z; zo<zoEnd && *zo; ++zo ){
+    /* NULL-terminate the directive so we can search for it. */
+    if( cmpp_isspace(*zo) ){
+      *zo = 0;
+      break;
+    }
+  }
+  unsigned char * const zDirective = bufLine->z;
+  dx->d = cmpp__d_search3(dx->pp, (char const *)zDirective,
+                          cmpp__d_search3_F_ALL);
+  if( dxppCode ){
+    return dxppCode;
+  }else if(!dx->d){
+    return cmpp_dx_err(dx, CMPP_RC_NOT_FOUND,
+                       "Unknown directive at line %"
+                       CMPP_SIZE_T_PFMT ": %.*s\n",
+                       (unsigned)tok->lineNo,
+                       (int)bufLine->n, bufLine->z);
+  }
+  assert( zDirective == bufLine->z );
+  const bool isCall
+    = dx->pimpl->args.pimpl->isCall
+    = dx->pimpl->flags.nextIsCall;
+  dx->pimpl->flags.nextIsCall = false;
+  if( isCall ){
+    if( cmpp_d_F_NO_CALL & dx->d->flags ){
+      return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
+                         "%s%s cannot be used in a [call] context.",
+                         cmpp_dx_delim(dx),
+                         dx->d->name.z);
+    }
+  }else if( cmpp_d_F_CALL_ONLY & dx->d->flags ){
+    return cmpp_dx_err(dx, CMPP_RC_TYPE,
+                       "'%s' is a call-only directive, "
+                       "not legal here.", dx->d->name.z);
+  }
+  if( bufLine->n > dx->d->name.n ){
+    dx->args.z = zDirective + dx->d->name.n + 1;
+    assert( dx->args.z > bufLine->z );
+    assert( dx->args.z <= bufLine->z+bufLine->n );
+    dx->args.nz = cmpp__strlenu(dx->args.z, -1);
+    assert( bufLine->nAlloc > dx->args.nz );
+  }else{
+    dx->args.z = ustr_c("\0");
+    dx->args.nz = 0;
+  }
+  if( 0 ){
+    g_warn("bufLine.n=%u zArgs offset=%u line=<<%s>>\nzArgs=<<%s>>",
+           bufLine->n, (dx->args.z - zDirective), bufLine->z, dx->args.z);
+  }
+  cmpp_skip_snl(&dx->args.z, dx->args.z + dx->args.nz);
+  if(0){
+    g_warn("zArgs %u = <<%.*s>>", (int)dx->args.nz,
+           (int)dx->args.nz, dx->args.z);
+  }
+  assert( !pimpl->buf.argsRaw.n );
+  if( dx->args.nz ){
+    if( 0 ){
+      g_warn("lineLen=%u zargs len=%u: [%.*s]\n",
+             (unsigned)lineLen,
+             (int)dx->args.nz, (int)dx->args.nz,
+             dx->args.z
+      );
+    }
+    if( cmpp_b_append4(dx->pp, &pimpl->buf.argsRaw,
+                            dx->args.z, dx->args.nz) ){
+      return dxppCode;
+    }
+  }
+  assert( !pimpl->args.arg0 );
+  assert( !pimpl->args.argc );
+  assert( !pimpl->args.pimpl->argOut.n );
+  assert( !pimpl->args.pimpl->argli.n );
+  assert( dx->args.z );
+  if( //1 || //pleases valgrind. Well, it did at one point.
+      !cmpp_dx_is_eliding(dx) || 0!=(cmpp_d_F_FLOW_CONTROL & dx->d->flags) ){
+    if( cmpp_d_F_ARGS_LIST & dx->d->flags ){
+      cmpp_dx_args_parse(dx, &pimpl->args);
+    }else if( cmpp_d_F_ARGS_RAW & dx->d->flags ){
+      /* Treat rest of line as one token */
+      cmpp_arg * const arg =
+        CmppArgList_append(dx->pp, &pimpl->args.pimpl->argli);
+      if( !arg ) return dxppCode;
+      pimpl->args.arg0 = arg;
+      pimpl->args.argc = 1;
+      arg->ttype = cmpp_TT_RawLine;
+      arg->z = pimpl->buf.argsRaw.z;
+      arg->n = pimpl->buf.argsRaw.n;
+      //g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z);
+    }
+  }
+  if( 0==dxppCode ){
+    dx->args.arg0 = pimpl->args.arg0;
+    dx->args.argc = pimpl->args.argc;
+  }
+  *pGotOne = true;
+  return dxppCode;
+}
+
+CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx){
+  return dx->pimpl->args.pimpl->isCall;
+}
+
+CMPP__EXPORT(int, cmpp_d_register)(cmpp * pp, cmpp_d_reg const * r,
+                    cmpp_d ** dOut){
+  CmppDList_entry * e1 = 0, * e2 = 0;
+  bool const isCallOnly =
+    (cmpp_d_F_CALL_ONLY & r->opener.flags);
+  if( ppCode ){
+    goto end;
+  }
+  if( (cmpp_d_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags))
+      && (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) ){
+    cmpp__err(pp, CMPP_RC_ACCESS,
+              "Directive %s%s flag cmpp_d_F_NOT_IN_SAFE_MODE is set "
+              "and the preprocessor is running in safe mode.",
+              cmpp__pp_zdelim(pp), r->name);
+    goto end;
+  }
+  if( isCallOnly && r->closer.f ){
+    cmpp__err(pp, CMPP_RC_MISUSE,
+              "Call-only directives may not have a closing directive.");
+    goto end;
+  }
+#if 0
+  if( pp->pimpl->dx ){
+    cmpp__err(pp, CMPP_RC_MISUSE,
+              "Directives may not be added while a "
+              "directive is running."
+              /* because that might reallocate being-run directives.
+                 2025-10-25: that's since been resolved but we need a
+                 use case before enabling this.
+              */);
+    goto end;
+  }
+#endif
+  if( !pp->pimpl->flags.isInternalDirectiveReg
+      && !cmpp_is_legal_key(ustr_c(r->name),
+                            cmpp__strlen(r->name,-1), NULL) ){
+    cmpp__err(pp, CMPP_RC_RANGE,
+              "\"%s\" is not a legal directive name.", r->name);
+    goto end;
+  }
+  if( cmpp__d_search(pp, r->name) ){
+    cmpp__err(pp, CMPP_RC_ALREADY_EXISTS,
+              "Directive name '%s' is already in use.",
+              r->name);
+    goto end;
+  }
+  e1 = CmppDList_append(pp, &pp->pimpl->d.list);
+  if( !e1 ) goto end;
+  e1->d.impl.callback = r->opener.f;
+  e1->d.impl.state = r->state;
+  e1->d.impl.dtor = r->dtor;
+  if( pp->pimpl->flags.isInternalDirectiveReg ){
+    e1->d.flags = r->opener.flags;
+  }else{
+    e1->d.flags = r->opener.flags & cmpp_d_F_MASK;
+  }
+  e1->zName = sqlite3_mprintf("%s", r->name);
+  if( 0==cmpp_check_oom(pp, e1->zName) ){
+    //e1->reg = *r; e1->reg.zName = e1->zName;
+    e1->d.name.z = e1->zName;
+    e1->d.name.n = strlen(e1->zName);
+    if( r->closer.f
+        && (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){
+      e2->d.impl.callback = r->closer.f;
+      e2->d.impl.state = r->state;
+      if( pp->pimpl->flags.isInternalDirectiveReg ){
+        e2->d.flags = r->closer.flags;
+      }else{
+        e2->d.flags = r->closer.flags & cmpp_d_F_MASK;
+      }
+      e1->d.closer = &e2->d;
+      e2->zName = sqlite3_mprintf("/%s", r->name);
+      if( 0==cmpp_check_oom(pp, e2->zName) ){
+        e2->d.name.z = e2->zName;
+        e2->d.name.n = e1->d.name.n + 1;
+      }
+    }
+  }
+
+end:
+  if( ppCode ){
+    if( e2 ) CmppDList_unappend(&pp->pimpl->d.list);
+    if( e1 ) CmppDList_unappend(&pp->pimpl->d.list);
+    else if( r->dtor ){
+      r->dtor( r->state );
+    }
+  }else{
+    CmppDList_sort(&pp->pimpl->d.list);
+    if( dOut ){
+      *dOut = &e1->d;
+    }
+    if( 0 ){
+      g_warn("Registered: %s%s%s", e1->zName,
+             e2 ? " and " : "",
+             e2 ? e2->zName : "");
+    }
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_consume)(cmpp_dx * const dx, cmpp_outputer * const os,
+                    cmpp_d const * const * const dClosers,
+                    unsigned nClosers,
+                    cmpp_flag32_t flags){
+  assert( !dxppCode );
+  bool gotOne = false;
+  cmpp_outputer const oldOut = dx->pp->pimpl->out;
+  bool const allowOtherDirectives =
+    (flags & cmpp_dx_consume_F_PROCESS_OTHER_D);
+  cmpp_d const * const d = cmpp_dx_d(dx);
+  cmpp_size_t const lineNo = dx->pimpl->dline.lineNo;
+  bool const pushAt = (cmpp_dx_consume_F_RAW & flags);
+  if( pushAt && cmpp_atpol_push(dx->pp, cmpp_atpol_OFF) ){
+    return dxppCode;
+  }
+  if( os ){
+    dx->pp->pimpl->out = *os;
+  }
+  while( 0==dxppCode
+         && 0==cmpp_dx_next(dx, &gotOne)
+         /* ^^^^^^^ resets dx->d, dx->pimpl->args and friends */ ){
+    if( !gotOne ){
+      dxserr("No closing directive found for "
+             "%s%s opened on line %" CMPP_SIZE_T_PFMT ".",
+             cmpp_dx_delim(dx), d->name.z, lineNo);
+    }else{
+      cmpp_d const * const d2 = cmpp_dx_d(dx);
+      gotOne = false;
+      for( unsigned i = 0; !gotOne && i < nClosers; ++i ){
+        gotOne = d2==dClosers[i];
+      }
+      //g_warn("gotOne=%d d2=%s", gotOne, d2->name.z);
+      if( gotOne ) break;
+      else if( !allowOtherDirectives ){
+        dxserr("%s%s at line %" CMPP_SIZE_T_PFMT
+               " may not contain %s%s.",
+               cmpp_dx_delim(dx), d->name.z, lineNo,
+               cmpp_dx_delim(dx), d2->name.z);
+      }else{
+        cmpp_dx_process(dx);
+      }
+    }
+  }
+  if( pushAt ){
+    cmpp_atpol_pop(dx->pp);
+  }
+  if( os ){
+    dx->pp->pimpl->out = oldOut;
+  }
+  return dxppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_consume_b)(cmpp_dx * const dx, cmpp_b * const b,
+                                     cmpp_d const * const * dClosers,
+                                     unsigned nClosers, cmpp_flag32_t flags){
+  cmpp_outputer oss = cmpp_outputer_b;
+  oss.state = b;
+  return cmpp_dx_consume(dx, &oss, dClosers, nClosers, flags);
+}
+
+char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p){
+again:
+  switch(p){
+    case cmpp_atpol_CURRENT:{
+      if( pp ){
+        assert( p!=cmpp__policy(pp, at) );
+        p = cmpp__policy(pp, at);
+        pp = 0;
+        goto again;
+      }
+      return NULL;
+    }
+    case cmpp_atpol_invalid: return NULL;
+    case cmpp_atpol_OFF: return "off";
+    case cmpp_atpol_RETAIN: return "retain";
+    case cmpp_atpol_ELIDE: return "elide";
+    case cmpp_atpol_ERROR: return "error";
+  }
+  return NULL;
+}
+
+cmpp_atpol_e cmpp_atpol_from_str(cmpp * const pp, char const *z){
+  cmpp_atpol_e rv = cmpp_atpol_invalid;
+  if( 0==strcmp(z,      "retain") )  rv = cmpp_atpol_RETAIN;
+  else if( 0==strcmp(z, "elide") )   rv = cmpp_atpol_ELIDE;
+  else if( 0==strcmp(z, "error") )   rv = cmpp_atpol_ERROR;
+  else if( 0==strcmp(z, "off") )     rv = cmpp_atpol_OFF;
+  if( pp ){
+    if( cmpp_atpol_invalid==rv
+        && 0==strcmp(z, "current") ){
+      rv = cmpp__policy(pp,at);
+    }else if( cmpp_atpol_invalid==rv ){
+      cmpp__err(pp, CMPP_RC_RANGE,
+                "Invalid @ policy value: %s."
+                " Try one of retain|elide|error|off|current.", z);
+    }else{
+      cmpp__policy(pp,at) = rv;
+    }
+  }
+  return rv;
+}
+
+int cmpp__StringAtIsOk(cmpp * pp, cmpp_atpol_e pol){
+  if( 0==ppCode ){
+    if( pol==cmpp_atpol_CURRENT ) pol=cmpp__policy(pp,at);
+    if(cmpp_atpol_OFF==pol ){
+      cmpp_err_set(pp, CMPP_RC_UNSUPPORTED,
+                   "@policy is \"off\", so cannot use @\"strings\".");
+    }
+  }
+  return ppCode;
+}
+
+cmpp__PodList_impl(PodList__atpol,cmpp_atpol_e)
+cmpp__PodList_impl(PodList__unpol,cmpp_unpol_e)
+
+int cmpp_atpol_push(cmpp * pp, cmpp_atpol_e pol){
+  if( cmpp_atpol_CURRENT==pol ) pol = cmpp__policy(pp,at);
+  assert( cmpp_atpol_CURRENT!=pol && "Else internal mismanagement." );
+  if( 0==PodList__atpol_push(pp, &cmpp__epol(pp,at), pol)
+      && 0!=cmpp_atpol_set(pp, pol)/*for validation*/ ){
+    PodList__atpol_pop(&cmpp__epol(pp,at));
+  }
+  return ppCode;
+}
+
+void cmpp_atpol_pop(cmpp * pp){
+  assert( cmpp__epol(pp,at).n );
+  if( cmpp__epol(pp,at).n ){
+    PodList__atpol_pop(&cmpp__epol(pp,at));
+  }else if( !ppCode ){
+    cmpp_err_set(pp, CMPP_RC_MISUSE,
+                 "%s() called when no cmpp_atpol_push() is active.",
+                 __func__);
+  }
+}
+
+int cmpp_unpol_push(cmpp * pp, cmpp_unpol_e pol){
+  if( 0==PodList__unpol_push(pp, &cmpp__epol(pp,un), pol)
+      && cmpp_unpol_set(pp, pol)/*for validation*/ ){
+    PodList__unpol_pop(&cmpp__epol(pp,un));
+  }
+  return ppCode;
+}
+
+void cmpp_unpol_pop(cmpp * pp){
+  assert( cmpp__epol(pp,un).n );
+  if( cmpp__epol(pp,un).n ){
+    PodList__unpol_pop(&cmpp__epol(pp,un));
+  }else if( !ppCode ){
+    cmpp_err_set(pp, CMPP_RC_MISUSE,
+                 "%s() called when no cmpp_unpol_push() is active.",
+                 __func__);
+  }
+}
+
+CMPP__EXPORT(cmpp_atpol_e, cmpp_atpol_get)(cmpp const * const pp){
+  return cmpp__epol(pp,at).na
+    ? cmpp__policy(pp,at) : cmpp_atpol_DEFAULT;
+}
+
+CMPP__EXPORT(int, cmpp_atpol_set)(cmpp * const pp, cmpp_atpol_e pol){
+  if( 0==ppCode ){
+    switch(pol){
+      case cmpp_atpol_OFF:
+      case cmpp_atpol_RETAIN:
+      case cmpp_atpol_ELIDE:
+      case cmpp_atpol_ERROR:
+        assert(cmpp__epol(pp,at).na);
+        cmpp__policy(pp,at) = pol;
+        break;
+      case cmpp_atpol_CURRENT:
+        break;
+      default:
+        cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
+                  (int)pol);
+    }
+  }
+  return ppCode;
+}
+
+
+char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p){
+  (void)pp;
+  switch(p){
+    case cmpp_unpol_NULL: return "null";
+    case cmpp_unpol_ERROR: return "error";
+    case cmpp_unpol_invalid: return NULL;
+  }
+  return NULL;
+}
+
+cmpp_unpol_e cmpp_unpol_from_str(cmpp * const pp,
+                                         char const *z){
+  cmpp_unpol_e rv = cmpp_unpol_invalid;
+  if( 0==strcmp(z, "null") )       rv = cmpp_unpol_NULL;
+  else if( 0==strcmp(z, "error") ) rv = cmpp_unpol_ERROR;
+  if( pp ){
+    if( cmpp_unpol_invalid==rv
+        && 0==strcmp(z, "current") ){
+      rv = cmpp__policy(pp,un);
+    }else if( cmpp_unpol_invalid==rv ){
+      cmpp__err(pp, CMPP_RC_RANGE,
+                "Invalid undefined key policy value: %s."
+                " Try one of null|error.", z);
+    }else{
+      cmpp_unpol_set(pp, rv);
+    }
+  }
+  return rv;
+}
+
+CMPP__EXPORT(cmpp_unpol_e, cmpp_unpol_get)(cmpp const * const pp){
+  return cmpp__epol(pp,un).na
+    ? cmpp__policy(pp,un) : cmpp_unpol_DEFAULT;
+}
+
+CMPP__EXPORT(int, cmpp_unpol_set)(cmpp * const pp, cmpp_unpol_e pol){
+  if( 0==ppCode ){
+    switch(pol){
+      case cmpp_unpol_NULL:
+      case cmpp_unpol_ERROR:
+        cmpp__policy(pp,un) = pol;
+        break;
+      default:
+        cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
+                  (int)pol);
+    }
+  }
+  return ppCode;
+}
+
+/**
+   Reminders to self re. savepoint tracking:
+
+   cmpp_dx tracks per-input-source savepoints. We always want
+   savepoints which are created via scripts to be limited to that
+   script. cmpp instances, on the other hand, don't care about that.
+
+   Thus we have two different APIs for starting/ending savepoints.
+*/
+CMPP__EXPORT(int, cmpp_sp_begin)(cmpp *pp){
+  if( 0==ppCode ){
+    sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spBegin, true);
+    assert( q || !"db init would have otherwise failed");
+    if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
+      ++pp->pimpl->flags.nSavepoint;
+    }
+  }
+  return ppCode;
+}
+
+int cmpp__dx_sp_begin(cmpp_dx * const dx){
+  if( 0==dxppCode && 0==cmpp_sp_begin(dx->pp) ){
+    ++dx->pimpl->nSavepoint;
+  }
+  return dxppCode;
+}
+
+CMPP__EXPORT(int, cmpp_sp_rollback)(cmpp *const pp){
+  /* Remember that rollback must (mostly) ignore the
+     pending error state. */
+  if( !pp->pimpl->flags.nSavepoint ){
+    if( 0==ppCode ){
+      cmpp__err(pp, CMPP_RC_MISUSE,
+                "Cannot roll back: no active savepoint");
+    }
+  }else{
+    sqlite3_stmt * q = cmpp__stmt(pp, CmppStmt_spRollback, true);
+    assert( q || !"db init would have otherwise failed");
+    if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
+      q = cmpp__stmt(pp, CmppStmt_spRelease, true);
+      if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
+          --pp->pimpl->flags.nSavepoint;
+      }
+    }
+  }
+  return ppCode;
+}
+
+int cmpp__dx_sp_rollback(cmpp_dx * const dx){
+  /* Remember that rollback must (mostly) ignore the pending error state. */
+  if( !dx->pimpl->nSavepoint ){
+    if( 0==dxppCode ){
+      cmpp_dx_err(dx, CMPP_RC_MISUSE,
+                  "Cannot roll back: no active savepoint");
+    }
+  }else{
+    cmpp_sp_rollback(dx->pp);
+    --dx->pimpl->nSavepoint;
+  }
+  return dxppCode;
+}
+
+CMPP__EXPORT(int, cmpp_sp_commit)(cmpp * const pp){
+  if( 0==ppCode ){
+    if( !pp->pimpl->flags.nSavepoint ){
+      cmpp__err(pp, CMPP_RC_MISUSE,
+                "Cannot commit: no active savepoint");
+    }else{
+      sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spRelease, true);
+      assert( q || !"db init would have otherwise failed");
+      if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
+        --pp->pimpl->flags.nSavepoint;
+      }
+    }
+  }else{
+    cmpp_sp_rollback(pp);
+  }
+  return ppCode;
+}
+
+int cmpp__dx_sp_commit(cmpp_dx * const dx){
+  if( 0==dxppCode ){
+    if( !dx->pimpl->nSavepoint ){
+      cmpp_dx_err(dx, CMPP_RC_MISUSE,
+                  "Cannot commit: no active savepoint");
+    }else if( 0==cmpp_sp_commit(dx->pp) ){
+      --dx->pimpl->nSavepoint;
+    }
+  }
+  return dxppCode;
+}
+
+static void cmpp_dx_pimpl_reuse(cmpp_dx_pimpl *p){
+#if 0
+  /* no: we need most of the state to remain
+     intact. */
+  cmpp_dx_pimpl const tmp = *p;
+  *p = cmpp_dx_pimpl_empty;
+  p->buf = tmp.buf;
+  p->args = tmp.args;
+#endif
+  cmpp_b_reuse(&p->buf.line);
+  cmpp_b_reuse(&p->buf.argsRaw);
+  cmpp_args_reuse(&p->args);
+}
+
+void cmpp_dx_pimpl_cleanup(cmpp_dx_pimpl *p){
+  cmpp_b_clear(&p->buf.line);
+  cmpp_b_clear(&p->buf.argsRaw);
+  cmpp_args_cleanup(&p->args);
+  *p = cmpp_dx_pimpl_empty;
+}
+
+void cmpp_dx__reset(cmpp_dx * const dx){
+  dx->args = cmpp_dx_empty.args;
+  cmpp_dx_pimpl_reuse(dx->pimpl);
+  dx->d = 0;
+  //no: dx->sourceName = 0;
+}
+
+void cmpp_dx_cleanup(cmpp_dx * const dx){
+  unsigned prev = 0;
+  CmppLvlList_cleanup(&dx->pimpl->dxLvl);
+  while( dx->pimpl->nSavepoint && prev!=dx->pimpl->nSavepoint ){
+    prev = dx->pimpl->nSavepoint;
+    cmpp__dx_sp_rollback(dx);
+  }
+  cmpp_dx_pimpl_cleanup(dx->pimpl);
+  memset(dx, 0, sizeof(*dx));
+}
+
+int cmpp__find_closing2(cmpp *pp,
+                        unsigned char const **zPos,
+                        unsigned char const *zEnd,
+                        cmpp_size_t * pNl){
+  unsigned char const * z = *zPos;
+  unsigned char const opener = *z;
+  unsigned char closer = 0;
+  switch(opener){
+    case '(':  closer = ')';  break;
+    case '[':  closer = ']';  break;
+    case '{':  closer = '}';  break;
+    case '"':  case '\'': closer = opener; break;
+    default:
+      return cmpp__err(pp, CMPP_RC_MISUSE,
+                       "Invalid starting char (0x%x) for %s()",
+                       (int)opener, __func__);
+  }
+  int count = 1;
+  for( ++z; z < zEnd; ++z ){
+    if( closer == *z && 0==--count ){
+      /* Have to check this first for the case of "" and ''. */
+      break;
+    }else if( opener == *z ){
+      ++count;
+    }else if( pNl && '\n'==*z ){
+      ++*pNl;
+    }
+  }
+  if( closer!=*z ){
+    if( 0 ){
+      g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z,
+             (zEnd - *zPos), *zPos);
+    }
+    //assert(!"here");
+    cmpp__err(pp, CMPP_RC_SYNTAX,
+              "Unbalanced %c%c: %.*s",
+              opener, closer,
+              (int)(z-*zPos), *zPos);
+  }else{
+    if( 0 ){
+      g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos);
+    }
+    *zPos = z;
+  }
+  return ppCode;
+}
+
+cmpp_tt cmpp__tt_for_sqlite(int sqType){
+  cmpp_tt rv;
+  switch( sqType ){
+    case SQLITE_INTEGER: rv = cmpp_TT_Int;    break;
+    case SQLITE_NULL:    rv = cmpp_TT_Null;   break;
+    default:             rv = cmpp_TT_String; break;
+  }
+  return rv;
+}
+
+int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
+                          bool defineIfNoRow){
+  if( 0==ppCode ){
+    int const nCol = sqlite3_column_count(q);
+    assert( sqlite3_data_count(q)>0 || defineIfNoRow);
+    /* Create a #define for each column */
+    bool const hasRow = sqlite3_data_count(q)>0;
+    for( int i = 0; !ppCode && i < nCol; ++i ){
+      char const * const zCol = sqlite3_column_name(q, i);
+      if( hasRow ){
+        unsigned char const * const zVal = sqlite3_column_text(q, i);
+        int const nVal = sqlite3_column_bytes(q, i);
+        cmpp_tt const ttype =
+          cmpp__tt_for_sqlite(sqlite3_column_type(q,i));
+        cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype);
+      }else if(defineIfNoRow){
+        cmpp__define2(pp, ustr_c(zCol), -1, ustr_c(""), 0, cmpp_TT_Null);
+      }else{
+        break;
+      }
+    }
+  }
+  return ppCode;
+}
+
+cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName){
+  cmpp_d const * d = 0;//cmpp__d_search(zName);
+  if( !d ){
+    CmppDList_entry const * e =
+      CmppDList_search(&pp->pimpl->d.list, zName);
+    if( e ) d = &e->d;
+  }
+  return d;
+}
+
+cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
+                               cmpp_flag32_t what){
+  cmpp_d const * d = cmpp__d_search(pp, zName);
+  if( !d ){
+    CmppDList_entry const * e = 0;
+    if( cmpp__d_search3_F_DELAYED & what ){
+      int rc = cmpp__d_delayed_load(pp, zName);
+      if( 0==rc ){
+        e = CmppDList_search(&pp->pimpl->d.list, zName);
+      }else if( CMPP_RC_NO_DIRECTIVE!=rc ){
+        assert( ppCode );
+        return NULL;
+      }
+    }
+    if( !e
+        && (cmpp__d_search3_F_AUTOLOADER & what)
+        && pp->pimpl->d.autoload.f
+        && 0==pp->pimpl->d.autoload.f(pp, zName, pp->pimpl->d.autoload.state) ){
+      e = CmppDList_search(&pp->pimpl->d.list, zName);
+    }
+#if CMPP_D_MODULE
+    if( !e
+        && !ppCode
+        && (cmpp__d_search3_F_DLL & what) ){
+      char * z = sqlite3_mprintf("libcmpp-d-%s", zName);
+      cmpp_check_oom(pp, z);
+      int rc = cmpp_module_load(pp, z, NULL);
+      sqlite3_free(z);
+      if( rc ){
+        if( CMPP_RC_NOT_FOUND==rc ){
+          cmpp__err_clear(pp);
+        }
+        return NULL;
+      }
+      e = CmppDList_search(&pp->pimpl->d.list, zName);
+    }
+#endif
+    if( e ) d = &e->d;
+  }
+  return d;
+}
+
+int cmpp_dx_process(cmpp_dx * const dx){
+  if( 0==dxppCode ){
+    cmpp_d const * const d = cmpp_dx_d(dx);
+    assert( d );
+    if( !cmpp_dx_is_eliding(dx) || (d->flags & cmpp_d_F_FLOW_CONTROL) ){
+      if( (cmpp_d_F_NOT_IN_SAFEMODE & d->flags)
+          && (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.newFlags) ){
+        cmpp_dx_err(dx, CMPP_RC_ACCESS,
+                    "Directive %s%s is disabled by safe mode.",
+                    cmpp_dx_delim(dx), dx->d->name.z);
+      }else{
+        assert(d->impl.callback);
+        d->impl.callback(dx);
+      }
+    }
+  }
+  return dxppCode;
+}
+
+
+static void cmpp_dx__setup_include_path(cmpp_dx * dx){
+  /* Add the leading dir part of dx->sourceName as the
+     highest-priority include path. It gets removed
+     in cmpp_dx__teardown(). */
+  assert( dx->sourceName );
+  enum { BufSize = 512 * 4 };
+  unsigned char buf[BufSize] = {0};
+  unsigned char *z = &buf[0];
+  cmpp_size_t n = cmpp__strlenu(dx->sourceName, -1);
+  if( n > (unsigned)BufSize-1 ) return;
+  memcpy(z, dx->sourceName, n);
+  buf[n] = 0;
+  cmpp_ssize_t i = n - 1;
+  for( ; i > 0; --i ){
+    if( '/'==z[i] || '\\'==z[i] ){
+      z[i] = 0;
+      n = i;
+      break;
+    }
+  }
+  if( n>(cmpp_size_t)i ){
+    /* No path separator found. Assuming '.'. This is intended to
+       replace the historical behavior of automatically adding '.'  if
+       no -I flags are used. Potential TODO is getcwd() here instead
+       of using '.' */
+    n = 1;
+    buf[0] = '.';
+    buf[1] = 0;
+  }
+  int64_t rowid = 0;
+  cmpp__include_dir_add(dx->pp, (char const*)buf,
+                        dx->pp->pimpl->flags.nDxDepth,
+                        &rowid);
+  if( rowid ){
+    //g_warn("Adding #include path #%" PRIi64 ": %s", rowid, z);
+    dx->pimpl->shadow.ridInclPath = rowid;
+  }
+}
+
+static int cmpp_dx__setup(cmpp *pp, cmpp_dx *dx,
+                          unsigned char const * zIn,
+                          cmpp_ssize_t nIn){
+  if( 0==ppCode ){
+    assert( dx->sourceName );
+    assert( dx->pimpl );
+    assert( pp==dx->pp );
+    nIn = cmpp__strlenu(zIn, nIn);
+    if( !nIn ) return 0;
+    pp->pimpl->dx = dx;
+    dx->pimpl->zBegin = zIn;
+    dx->pimpl->zEnd = zIn + nIn;
+    cmpp_define_shadow(pp, "__FILE__", (char const *)dx->sourceName,
+                       &dx->pimpl->shadow.sidFile);
+    ++dx->pp->pimpl->flags.nDxDepth;
+    cmpp_dx__setup_include_path(dx);
+  }
+  return ppCode;
+}
+
+static void cmpp_dx__teardown(cmpp_dx *dx){
+  if( dx->pimpl->shadow.ridInclPath>0 ){
+    cmpp__include_dir_rm_id(dx->pp, dx->pimpl->shadow.ridInclPath);
+    dx->pimpl->shadow.ridInclPath = 0;
+  }
+  if( dx->pimpl->shadow.sidFile ){
+    cmpp_define_unshadow(dx->pp, "__FILE__",
+                         dx->pimpl->shadow.sidFile);
+  }
+  --dx->pp->pimpl->flags.nDxDepth;
+  cmpp_dx_cleanup(dx);
+}
+
+CMPP__EXPORT(int, cmpp_process_string)(
+  cmpp *pp, const char * zName,
+  unsigned char const * zIn,
+  cmpp_ssize_t nIn
+){
+  if( !zName ) zName = "";
+  if( 0==cmpp__db_init(pp) ){
+    cmpp_dx const * const oldDx = pp->pimpl->dx;
+    cmpp_dx_pimpl dxp = cmpp_dx_pimpl_empty;
+    cmpp_dx dx = {
+      .pp = pp,
+      .sourceName = ustr_c(zName),
+      .args = cmpp_dx_empty.args,
+      .pimpl = &dxp
+    };
+    dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall;
+    pp->pimpl->flags.nextIsCall = false;
+    if( dxp.flags.nextIsCall ){
+      assert( pp->pimpl->dx );
+      dxp.pos.lineNo = pp->pimpl->dx->pimpl->pos.lineNo;
+    }
+    bool gotOne = false;
+    (void)cmpp__stmt(pp, CmppStmt_sdefIns, true);
+    (void)cmpp__stmt(pp, CmppStmt_inclPathAdd, true);
+    (void)cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
+    (void)cmpp__stmt(pp, CmppStmt_sdefDel, true)
+      /* hack: ensure that those queries are allocated now, as an
+         error in processing may keep them from being created
+         later. We might want to rethink the
+         prepare-no-statements-on-error bits, but will have to go back
+         and fix routines which currently rely on that. */;
+    cmpp_dx__setup(pp, &dx, zIn, nIn);
+    while(0==ppCode
+          && 0==cmpp_dx_next(&dx, &gotOne)
+          && gotOne){
+      cmpp_dx_process(&dx);
+    }
+    if(0==ppCode && 0!=dx.pimpl->dxLvl.n){
+      CmppLvl const * const lv = CmppLvl_get(&dx);
+      cmpp_dx_err(&dx, CMPP_RC_SYNTAX,
+                 "Input ended inside an unterminated nested construct "
+                 "opened at [%s] line %" CMPP_SIZE_T_PFMT ".", zName,
+                  lv ? lv->lineNo : (cmpp_size_t)0);
+    }
+    cmpp_dx__teardown(&dx);
+    pp->pimpl->dx = oldDx;
+  }
+  if( !ppCode ){
+    cmpp_outputer_flush(&pp->pimpl->out)
+      /* We're going to ignore a result code just this once. */;
+  }
+  return ppCode;
+}
+
+int cmpp_process_file(cmpp *pp, const char * zName){
+  if( 0==ppCode ){
+    FileWrapper fw = FileWrapper_empty;
+    if( 0==cmpp__FileWrapper_open(pp, &fw, zName, "rb")
+        && 0==cmpp__FileWrapper_slurp(pp, &fw) ){
+      cmpp_process_string(pp, zName, fw.zContent, fw.nContent);
+    }
+    FileWrapper_close(&fw);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_process_stream)(cmpp *pp, const char * zName,
+                        cmpp_input_f src, void * srcState){
+  if( 0==ppCode ){
+    cmpp_b * const os = cmpp_b_borrow(pp);
+    int const rc = os
+      ? cmpp_stream(src, srcState, cmpp_output_f_b, os)
+      : ppCode;
+    if( 0==rc ){
+      cmpp_process_string(pp, zName, os->z, os->n);
+    }else{
+      cmpp__err(pp, rc, "Error reading from input stream '%s'.", zName);
+    }
+    cmpp_b_return(pp, os);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_call_str)(
+  cmpp *pp, unsigned char const * z, cmpp_ssize_t n,
+  cmpp_b * dest, cmpp_flag32_t flags
+){
+  if( ppCode ) return ppCode;
+  cmpp_args args = cmpp_args_empty;
+  cmpp_b * const b = cmpp_b_borrow(pp);
+  cmpp_b * const bo = cmpp_b_borrow(pp);
+  cmpp_outputer oB = cmpp_outputer_b;
+  if( !b || !bo ) return ppCode;
+  cmpp__pi(pp);
+  oB.state = bo;
+  oB.name = pi->out.name;//"[call]";
+  n = cmpp__strlenu(z, n);
+  //g_warn("calling: <<%.*s>>", (int)n, z);
+  unsigned char const * zEnd = z+n;
+  cmpp_skip_snl(&z, zEnd);
+  cmpp_skip_snl_trailing(z, &zEnd);
+  n = (zEnd-z);
+  if( !n ){
+    cmpp_err_set(pp, CMPP_RC_SYNTAX,
+                 "Empty [call] is not permitted.");
+    goto end;
+  }
+  //g_warn("calling: <<%.*s>>", (int)n, z);
+  cmpp__delim const * const delim = cmpp__pp_delim(pp);
+  assert(delim);
+  if( (cmpp_size_t)n<=delim->open.n
+      || 0!=memcmp(z, delim->open.z, delim->open.n) ){
+    /* If it doesn't start with the current delimiter,
+       prepend one. */
+    cmpp_b_reserve3(pp, b, delim->open.n + n + 2);
+    cmpp_b_append4(pp, b, delim->open.z, delim->open.n);
+  }
+  cmpp_b_append4(pp, b, z, n);
+  if( !ppCode ){
+    cmpp_outputer oOld = cmpp_outputer_empty;
+    pi->flags.nextIsCall = true
+      /* Convey (indirectly) that the first cmpp_dx_next() call made
+         via cmpp_process_string() is a call context. */;
+    cmpp__outputer_swap(pp, &oB, &oOld);
+    cmpp_process_string(pp, (char*)b->z, b->z, b->n);
+    cmpp__outputer_swap(pp, &oOld, &oB);
+    assert( !pi->flags.nextIsCall || ppCode );
+    pi->flags.nextIsCall = false;
+  }
+  if( !ppCode ){
+    unsigned char const * zz = bo->z;
+    unsigned char const * zzEnd = bo->z + bo->n;
+    if( cmpp_call_F_TRIM_ALL & flags ){
+      cmpp_skip_snl(&zz, zzEnd);
+      cmpp_skip_snl_trailing(zz, &zzEnd);
+    }else if( 0==(cmpp_call_F_NO_TRIM & flags) ){
+      cmpp_b_chomp(bo);
+      zzEnd = bo->z + bo->n;
+    }
+    if( (zzEnd-zz) ){
+      cmpp_b_append4(pp, dest, zz, (zzEnd-zz));
+    }
+  }
+end:
+  cmpp_b_return(pp, b);
+  cmpp_b_return(pp, bo);
+  cmpp_args_cleanup(&args);
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_errno_rc)(int errNo, int dflt){
+  switch(errNo){
+    /* Please expand on this as tests/use cases call for it... */
+    case 0:
+      return 0;
+    case EINVAL:
+      return CMPP_RC_MISUSE;
+    case ENOMEM:
+      return CMPP_RC_OOM;
+    case EROFS:
+    case EACCES:
+    case EBUSY:
+    case EPERM:
+    case EDQUOT:
+    case EAGAIN:
+    case ETXTBSY:
+      return CMPP_RC_ACCESS;
+    case EISDIR:
+    case ENOTDIR:
+      return CMPP_RC_TYPE;
+    case ENAMETOOLONG:
+    case ELOOP:
+    case ERANGE:
+      return CMPP_RC_RANGE;
+    case ENOENT:
+    case ESRCH:
+      return CMPP_RC_NOT_FOUND;
+    case EEXIST:
+    case ENOTEMPTY:
+      return CMPP_RC_ALREADY_EXISTS;
+    case EIO:
+      return CMPP_RC_IO;
+    default:
+      return dflt;
+  }
+}
+
+int cmpp_flush_f_FILE(void * _FILE){
+  return fflush(_FILE) ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
+}
+
+int cmpp_output_f_FILE( void * state,
+                        void const * src, cmpp_size_t n ){
+  return (1 == fwrite(src, n, 1, state ? (cmpp_FILE*)state : stdout))
+    ? 0 : CMPP_RC_IO;
+}
+
+int cmpp_output_f_fd( void * state, void const * src, cmpp_size_t n ){
+  int const fd = *((int*)state);
+  ssize_t const wn = write(fd, src, n);
+  return wn<0 ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
+}
+
+int cmpp_input_f_FILE( void * state, void * dest, cmpp_size_t * n ){
+  cmpp_FILE * f = state;
+  cmpp_size_t const rn = *n;
+  *n = (cmpp_size_t)fread(dest, 1, rn, f);
+  return *n==rn ? 0 : (feof(f) ? 0 : CMPP_RC_IO);
+}
+
+int cmpp_input_f_fd( void * state, void * dest, cmpp_size_t * n ){
+  int const fd = *((int*)state);
+  ssize_t const rn = read(fd, dest, *n);
+  if( rn<0 ){
+    return cmpp_errno_rc(errno, CMPP_RC_IO);
+  }else{
+    *n = (cmpp_size_t)rn;
+    return 0;
+  }
+}
+
+void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self){
+  if( self->state ){
+    cmpp_fclose( self->state );
+    self->name = NULL;
+    self->state = NULL;
+  }
+}
+
+CMPP__EXPORT(void, cmpp_outputer_cleanup_f_b)(cmpp_outputer *self){
+  if( self->state ) cmpp_b_clear(self->state);
+}
+
+CMPP__EXPORT(int, cmpp_outputer_out)(cmpp_outputer *o, void const *p, cmpp_size_t n){
+  return o->out ? o->out(o->state, p, n) : 0;
+}
+
+CMPP__EXPORT(int, cmpp_outputer_flush)(cmpp_outputer *o){
+  return o->flush ? o->flush(o->state) : 0;
+}
+
+CMPP__EXPORT(void, cmpp_outputer_cleanup)(cmpp_outputer *o){
+  if( o->cleanup ){
+    o->cleanup( o );
+  }
+}
+
+CMPP__EXPORT(int, cmpp_stream)( cmpp_input_f inF, void * inState,
+                 cmpp_output_f outF, void * outState ){
+  int rc = 0;
+  enum { BufSize = 1024 * 4 };
+  unsigned char buf[BufSize];
+  cmpp_size_t rn = BufSize;
+  while( 0==rc
+         && (rn==BufSize)
+         && (0==(rc=inF(inState, buf, &rn))) ){
+    if(rn) rc = outF(outState, buf, rn);
+  }
+  return rc;
+}
+
+void cmpp__fatalv_base(char const *zFile, int line,
+                  char const *zFmt, va_list va){
+  cmpp_FILE * const fp = stderr;
+  fflush(stdout);
+  fprintf(fp, "\n%s:%d: ", zFile, line);
+  if(zFmt && *zFmt){
+    vfprintf(fp, zFmt, va);
+    fputc('\n', fp);
+  }
+  fflush(fp);
+  exit(1);
+}
+
+void cmpp__fatal_base(char const *zFile, int line,
+                 char const *zFmt, ...){
+  va_list va;
+  va_start(va, zFmt);
+  cmpp__fatalv_base(zFile, line, zFmt, va);
+  va_end(va);
+}
+
+CMPP__EXPORT(int, cmpp_err_get)(cmpp *pp, char const **zMsg){
+  if( zMsg && ppCode ) *zMsg = pp->pimpl->err.zMsg;
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){
+  int const rc = ppCode;
+  if( rc ){
+    *zMsg = pp->pimpl->err.zMsg;
+    pp->pimpl->err = cmpp_pimpl_empty.err;
+  }
+  return rc;
+}
+
+//CMPP_WASM_EXPORT
+void cmpp__err_clear(cmpp *pp){
+  cmpp_mfree(pp->pimpl->err.zMsg);
+  pp->pimpl->err = cmpp_pimpl_empty.err;
+}
+
+CMPP__EXPORT(int, cmpp_err_has)(cmpp const * pp){
+  return pp ? pp->pimpl->err.code : 0;
+}
+
+CMPP__EXPORT(void, cmpp_dx_pos_save)(cmpp_dx const * dx, cmpp_dx_pos *pos){
+  *pos = dx->pimpl->pos;
+}
+
+CMPP__EXPORT(void, cmpp_dx_pos_restore)(cmpp_dx * dx, cmpp_dx_pos const * pos){
+  dx->pimpl->pos = *pos;
+}
+
+
+//CMPP_WASM_EXPORT
+void cmpp__dx_append_script_info(cmpp_dx const * dx,
+                                 sqlite3_str * const sstr){
+  sqlite3_str_appendf(
+    sstr,
+    "%s%s@ %s line %" CMPP_SIZE_T_PFMT,
+    dx->d ? dx->d->name.z : "",
+    dx->d ? " " : "",
+    (dx->sourceName
+     && 0==strcmp("-", (char const *)dx->sourceName))
+    ? "<stdin>"
+    : (char const *)dx->sourceName,
+    dx->pimpl->dline.lineNo
+  );
+}
+
+int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list va){
+  if( pp ){
+    cmpp__err_clear(pp);
+    ppCode = rc;
+    if( 0==rc ) return rc;
+    if( CMPP_RC_OOM==rc ){
+    oom:
+      pp->pimpl->err.zMsgC = "An allocation failed.";
+      return pp->pimpl->err.code = CMPP_RC_OOM;
+    }
+    assert( !pp->pimpl->err.zMsg );
+    if( pp->pimpl->dx || (zFmt && *zFmt) ){
+      sqlite3_str * sstr = 0;
+      sstr = sqlite3_str_new(pp->pimpl->db.dbh);
+      if( pp->pimpl->dx ){
+        cmpp__dx_append_script_info(pp->pimpl->dx, sstr);
+        sqlite3_str_append(sstr, ": ", 2);
+      }
+      if( zFmt && *zFmt ){
+        sqlite3_str_vappendf(sstr, zFmt, va);
+      }else{
+        sqlite3_str_appendf(sstr, "No error info provided.");
+      }
+      pp->pimpl->err.zMsgC =
+        pp->pimpl->err.zMsg = sqlite3_str_finish(sstr);
+      if( !pp->pimpl->err.zMsg ){
+        goto oom;
+      }
+    }else{
+      pp->pimpl->err.zMsgC = "No error info provided.";
+    }
+    rc = ppCode;
+  }
+  return rc;
+}
+
+//CMPP_WASM_EXPORT no - variadic
+int cmpp_err_set(cmpp *pp, int rc,
+                 char const *zFmt, ...){
+  if( pp ){
+    va_list va;
+    va_start(va, zFmt);
+    rc = cmpp__errv(pp, rc, zFmt, va);
+    va_end(va);
+  }
+  return rc;
+}
+
+const cmpp_d_autoloader cmpp_d_autoloader_empty =
+  cmpp_d_autoloader_empty_m;
+
+CMPP__EXPORT(void, cmpp_d_autoloader_set)(cmpp *pp, cmpp_d_autoloader const * pNew){
+  if( pp->pimpl->d.autoload.dtor ) pp->pimpl->d.autoload.dtor(pp->pimpl->d.autoload.state);
+  if( pNew ) pp->pimpl->d.autoload = *pNew;
+  else pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
+}
+
+CMPP__EXPORT(void, cmpp_d_autoloader_take)(cmpp *pp, cmpp_d_autoloader * pOld){
+  *pOld = pp->pimpl->d.autoload;
+  pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
+}
+
+//CMPP_WASM_EXPORT no - variadic
+int cmpp_dx_err_set(cmpp_dx *dx, int rc,
+                    char const *zFmt, ...){
+  va_list va;
+  va_start(va, zFmt);
+  rc = cmpp__errv(dx->pp, rc, zFmt, va);
+  va_end(va);
+  return rc;
+}
+
+CMPP__EXPORT(int, cmpp_err_set1)(cmpp *pp, int rc, char const *zMsg){
+  return cmpp_err_set(pp, rc, (zMsg && *zMsg) ? "%s" : 0, zMsg);
+}
+
+//no: CMPP_WASM_EXPORT
+char * cmpp_path_search(cmpp *pp,
+                        char const *zPath,
+                        char pathSep,
+                        char const *zBaseName,
+                        char const *zExt){
+  char * zrc = 0;
+  if( !ppCode ){
+    sqlite3_stmt * const q =
+      cmpp__stmt(pp, CmppStmt_selPathSearch, false);
+    if( q ){
+      unsigned char sep[2] = {pathSep, 0};
+      cmpp__bind_text(pp, q, 1, ustr_c(zBaseName));
+      cmpp__bind_text(pp, q, 2, sep);
+      cmpp__bind_text(pp, q, 3, ustr_c((zExt ? zExt : "")));
+      cmpp__bind_text(pp, q, 4, ustr_c((zPath ? zPath: "")));
+      int const dbrc = cmpp__step(pp, q, false);
+      if( SQLITE_ROW==dbrc ){
+        unsigned char const * s = sqlite3_column_text(q, 1);
+        zrc = sqlite3_mprintf("%s", s);
+        cmpp_check_oom(pp, zrc);
+      }
+      cmpp__stmt_reset(q);
+    }
+  }
+  return zrc;
+}
+
+#if CMPP__OBUF
+int cmpp__obuf_flush(cmpp__obuf * b){
+  if( 0==b->rc && b->cursor > b->begin ){
+    if( b->dest.out ){
+      b->rc = b->dest.out(b->dest.state, b->begin,
+                          b->cursor-b->begin);
+    }
+    b->cursor = b->begin;
+  }
+  if( 0==b->rc && b->dest.flush ){
+    b->rc = b->dest.flush(b->dest.state);
+  }
+  return b->rc;
+}
+
+void cmpp__obuf_cleanup(cmpp__obuf * b){
+  if( b ){
+    cmpp__obuf_flush(b);/*ignoring result*/;
+    if( b->ownsMemory ){
+      cmpp_mfree(b->begin);
+    }
+    *b = cmpp__obuf_empty;
+  }
+}
+
+int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n){
+  assert( b );
+  if( n && !b->rc && b->dest.out ){
+    assert( b->end );
+    assert( b->cursor );
+    assert( b->cursor <= b->end );
+    assert( b->end>b->begin );
+    if( b->cursor + n >= b->end ){
+      if( 0==cmpp_flush_f_obuf(b) ){
+        if( b->cursor + n >= b->end ){
+          /* Go ahead and write it all */
+          b->rc = b->dest.out(b->dest.state, src, n);
+        }else{
+          goto copy_it;
+        }
+      }
+    }else{
+    copy_it:
+      memcpy(b->cursor, src, n);
+      b->cursor += n;
+    }
+  }
+  return b->rc;
+}
+
+int cmpp_flush_f_obuf(void * b){
+  return cmpp__obuf_flush(b);
+}
+
+int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n){
+  return cmpp__obuf_write(state, src, n);
+}
+
+void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o){
+  cmpp__obuf_cleanup(o->state);
+}
+#endif /* CMPP__OBUF */
+
+//cmpp__ListType_impl(cmpp__delim_list,cmpp__delim)
+//cmpp__ListType_impl(CmppDList,CmppDList_entry*)
+//cmpp__ListType_impl(CmppSohList,void*)
+cmpp__ListType_impl(CmppArgList,cmpp_arg)
+cmpp__ListType_impl(cmpp_b_list,cmpp_b*)
+cmpp__ListType_impl(CmppLvlList,CmppLvl*)
+
+/**
+   Expects that *ndx points to the current argv entry and that it is a
+   flag which expects a value. This function checks for --flag=val and
+   (--flag val) forms. If a value is found then *ndx is adjusted (if
+   needed) to point to the next argument after the value and *zVal is
+   * pointed to the value. If no value is found then it returns false.
+*/
+static bool get_flag_val(int argc,
+                        char const * const * argv, int * ndx,
+                        char const **zVal){
+  char const * zEq = strchr(argv[*ndx], '=');
+  if( zEq ){
+    *zVal = zEq+1;
+    return 1;
+  }else if(*ndx+1>=argc){
+    return 0;
+  }else{
+    *zVal = argv[++*ndx];
+    return 1;
+  }
+}
+
+static
+bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
+                        char const **zValIfEqX );
+bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
+                        char const **zValIfEqX ){
+  if( zValIfEqX ) *zValIfEqX = 0;
+  if( 0==strcmp(zFlag, zArg) ) return true;
+  char const * z = strchr(zArg,'=');
+  if( z && z>zArg ){
+    /* compare the part before the '=' */
+    if( 0==strncmp(zFlag, zArg, z-zArg) ){
+      if( !zFlag[z-zArg] ){
+        if( zValIfEqX ) *zValIfEqX = z+1;
+        return true;
+      }
+      /* Else it was a prefix match. */
+    }
+  }
+  return false;
+}
+
+void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defSelAll, false);
+  if( q ){
+    while( SQLITE_ROW==sqlite3_step(q) ){
+      int const tt = sqlite3_column_int(q, 0);
+      unsigned char const * zK = sqlite3_column_text(q, 1);
+      unsigned char const * zV = sqlite3_column_text(q, 2);
+      int const nK = sqlite3_column_bytes(q, 1);
+      int const nV = sqlite3_column_bytes(q, 2);
+      char const * zTt = cmpp__tt_cstr(tt, true);
+      if( tt && zTt ) zTt += 3;
+      else zTt = "String";
+      fprintf(fp, "%s%.*s = [%s] %.*s\n", bIndent ? "\t" : "",
+              nK, zK, zTt, nV, zV);
+    }
+    cmpp__stmt_reset(q);
+  }
+}
+
+/**
+   This is what was originally the main() of cmpp v1, back when it was
+   a monolithic app. It still serves as the driver for main() but is
+   otherwise unused.
+*/
+CMPP__EXPORT(int, cmpp_process_argv)(cmpp *pp, int argc,
+                                     char const * const * argv){
+  if( ppCode ) return ppCode;
+  int nFile = 0    /* number of files/-e scripts seen */;
+
+#define ARGVAL if( !zVal && !get_flag_val(argc, argv, &i, &zVal) ){ \
+    cmpp__err(pp, CMPP_RC_MISUSE, "Missing value for flag '%s'", \
+                  argv[i]);                                          \
+    break;                                                           \
+  }
+#define M(X) cmpp__arg_is_flag(X, zArg, &zVal)
+#define ISFLAG(X) else if(M(X))
+#define ISFLAG2(X,Y) else if(M(X) || M(Y))
+#define NOVAL if( zVal ){ \
+    cmpp__err(pp,CMPP_RC_MISUSE,"Unexpected value for %s", zArg); \
+    break; \
+  } (void)0
+
+#define open_output_if_needed                       \
+  if( !pp->pimpl->out.out && cmpp__out_fopen(pp, "-") ) break
+
+  cmpp__staticAssert(TT_None,0==(int)cmpp_TT_None);
+  cmpp__staticAssert(Mask1,  cmpp_d_F_MASK_INTERNAL & cmpp_d_F_FLOW_CONTROL);
+  cmpp__staticAssert(Mask2,  cmpp_d_F_MASK_INTERNAL & cmpp_d_F_NOT_SIMPLIFY);
+  cmpp__staticAssert(Mask3,  0==(cmpp_d_F_MASK_INTERNAL & cmpp_d_F_MASK));
+
+  for(int doIt = 0; doIt<2 && 0==ppCode; ++doIt){
+    /**
+       Loop through the flags twice. The first time we just validate
+       and look for --help/-?. The second time we process the flags.
+       This approach allows us to easily chain multiple files and
+       flags:
+
+       ./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y
+
+       Which, it turns out, is a surprisingly useful way to work.
+    */
+#define DOIT if(1==doIt)
+    for(int i = 0; i < argc && 0==ppCode; ++i){
+      char const * zVal = 0;
+      int isNoFlag = 0;
+      char const * zArg = argv[i];
+      //g_stderr("i=%d zArg=%s\n", i, zArg);
+      zVal = 0;
+      while('-'==*zArg) ++zArg;
+      if(zArg==argv[i]/*not a flag*/){
+        zVal = zArg;
+        goto do_infile;
+      }
+      //g_warn("zArg=%s", zArg);
+      if( 0==strncmp(zArg,"no-",3) ){
+        zArg += 3;
+        isNoFlag = 1;
+      }
+      if( M("?") || M("help") ){
+        NOVAL;
+        cmpp__err(pp, CMPP_RC_HELP, "%s", argv[i]);
+        break;
+      }else if('D'==*zArg){
+        ++zArg;
+        if(!*zArg){
+          cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -D");
+        }else DOIT {
+            cmpp_define_legacy(pp, zArg, 0);
+        }
+      }else if('F'==*zArg){
+        ++zArg;
+        if(!*zArg){
+          cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -F");
+        }else DOIT {
+          cmpp__set_file(pp, ustr_c(zArg), -1);
+        }
+      }
+      ISFLAG("e"){
+        ARGVAL;
+        DOIT {
+          ++nFile;
+          open_output_if_needed;
+          cmpp_process_string(pp, "-e script",
+                              (unsigned char const *)zVal, -1);
+        }
+      }else if('U'==*zArg){
+        ++zArg;
+        if(!*zArg){
+          cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -U");
+        }else DOIT {
+            cmpp_undef(pp, zArg, NULL);
+        }
+      }else if('I'==*zArg){
+        ++zArg;
+        if(!*zArg){
+          cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -I");
+        }else DOIT {
+          cmpp_include_dir_add(pp, zArg);
+        }
+      }else if('L'==*zArg){
+        ++zArg;
+        if(!*zArg){
+          cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -L");
+        }else DOIT {
+          cmpp_module_dir_add(pp, zArg);
+        }
+      }
+      ISFLAG2("o","outfile"){
+        ARGVAL;
+        DOIT {
+          cmpp__out_fopen(pp, zVal);
+        }
+      }
+      ISFLAG2("f","file"){
+        ARGVAL;
+      do_infile:
+        DOIT {
+          if( !pp->pimpl->mod.path.z ){
+            cmpp_module_dir_add(pp, NULL);
+          }
+          ++nFile;
+          if( 0
+              && !pp->pimpl->flags.nIncludeDir
+              && cmpp_include_dir_add(pp, ".") ){
+            break;
+          }
+          open_output_if_needed;
+          cmpp_process_file(pp, zVal);
+        }
+      }
+      ISFLAG("@"){
+        NOVAL;
+        DOIT {
+          assert( cmpp_atpol_DEFAULT_FOR_FLAG!=cmpp_atpol_OFF );
+          cmpp_atpol_set(pp, isNoFlag
+                         ? cmpp_atpol_OFF
+                         : cmpp_atpol_DEFAULT_FOR_FLAG);
+        }
+      }
+      ISFLAG("@policy"){
+        ARGVAL;
+        cmpp_atpol_from_str(pp, zVal);
+      }
+      ISFLAG("debug"){
+        NOVAL;
+        DOIT {
+          pp->pimpl->flags.doDebug += isNoFlag ? -1 : 1;
+        }
+      }
+      ISFLAG2("u","undefined-policy"){
+        ARGVAL;
+        cmpp_unpol_from_str(pp, zVal);
+      }
+      ISFLAG("sql-trace"){
+        NOVAL;
+        /* Needs to be set before the start of the second pass, when
+           the db is inited. */
+        DOIT {
+          pp->pimpl->sqlTrace.expandSql = false;
+        do_trace_flag:
+          cmpp_outputer_cleanup(&pp->pimpl->sqlTrace.out);
+          if( isNoFlag ){
+            pp->pimpl->sqlTrace.out = cmpp_outputer_empty;
+          }else{
+            pp->pimpl->sqlTrace.out = cmpp_outputer_FILE;
+            pp->pimpl->sqlTrace.out.state = stderr;
+          }
+        }
+      }
+      ISFLAG("sql-trace-x"){
+        NOVAL;
+        DOIT {
+          pp->pimpl->sqlTrace.expandSql = true;
+          goto do_trace_flag;
+        }
+      }
+      ISFLAG("chomp-F"){
+        NOVAL;
+        DOIT pp->pimpl->flags.chompF = !isNoFlag;
+      }
+      ISFLAG2("d","delimiter"){
+        ARGVAL;
+        DOIT {
+          cmpp_delimiter_set(pp, zVal);
+        }
+      }
+      ISFLAG2("dd", "dump-defines"){
+        DOIT {
+          cmpp_FILE * const fp =
+            /* tcl's exec treats output to stderr as failure.
+               If we use [exec -ignorestderr] then it instead replaces
+               stderr's output with its own message, invalidating
+               test expectations. */
+            1 ? stdout : stderr;
+          fprintf(fp, "All %sdefine entries:\n",
+                  cmpp__pp_zdelim(pp));
+          cmpp__dump_defines(pp, fp, 1);
+        }
+      }
+#if !defined(CMPP_OMIT_D_DB)
+      ISFLAG2("db", "db-file"){
+        /* Undocumented flag used for testing purposes. */
+        ARGVAL;
+        DOIT {
+          cmpp_db_name_set(pp, zVal);
+        }
+      }
+#endif
+      ISFLAG("version"){
+        NOVAL;
+#if !defined(CMPP_OMIT_FILE_IO)
+        fprintf(stdout, "c-pp version %s\nwith SQLite %s %s\n",
+                cmpp_version(),
+                sqlite3_libversion(),
+                sqlite3_sourceid());
+#endif
+        doIt = 100;
+        break;
+      }
+#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
+      ISFLAG("safe-mode"){
+        if( i>0 ){
+          cmpp_err_set(pp, CMPP_RC_MISUSE,
+                       "--%s, if used, must be the first argument.",
+                       zArg);
+          break;
+        }
+      }
+#endif
+      else{
+        cmpp__err(pp,CMPP_RC_MISUSE,
+                  "Unhandled flag: %s", argv[i]);
+      }
+    }
+    DOIT {
+      if(!nFile){
+        /* We got no file arguments, so read from stdin. */
+        if(0
+           && !pp->pimpl->flags.nIncludeDir
+           && cmpp_include_dir_add(pp, ".") ){
+          break;
+        }
+        open_output_if_needed;
+        cmpp_process_file(pp, "-");
+      }
+    }
+#undef DOIT
+  }
+  return ppCode;
+#undef ARGVAL
+#undef M
+#undef ISFLAG
+#undef ISFLAG2
+#undef NOVAL
+#undef open_output_if_needed
+}
+
+void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *fOut){
+#if defined(CMPP_OMIT_FILE_IO)
+  (void)zAppName; (void)fOut;
+#else
+  fprintf(fOut, "%s version %s\nwith SQLite %s %s\n",
+          zAppName ? zAppName : "c-pp",
+          cmpp_version(),
+          sqlite3_libversion(),
+          sqlite3_sourceid());
+  fprintf(fOut, "Usage: %s [flags] [infile...]\n", zAppName);
+  fprintf(fOut,
+          "Flags and filenames may be in any order and "
+          "they are processed in that order.\n"
+          "\nFlags:\n");
+#define GAP "     "
+#define arg(F,D) fprintf(fOut,"\n  %s\n" GAP "%s\n",F, D)
+#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
+  arg("--safe-mode",
+      "Disables preprocessing directives which use the filesystem "
+      "or invoke external processes. If used, it must be the first "
+      "argument.");
+#endif
+
+  arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n"
+      GAP "Because arguments are processed in order, this should\n"
+      GAP "normally be given before -f.");
+  arg("-f|--file FILE","Process FILE (default=- (stdin)).\n"
+      GAP "All non-flag arguments are assumed to be the input files.");
+  arg("-e SCRIPT",
+      "Treat SCRIPT as a complete c-pp input and process it.\n"
+      GAP "Doing anything marginally useful with this requires\n"
+      GAP "using it several times, once per directive. It will not\n"
+      GAP "work with " CMPP_DEFAULT_DELIM "if but is fine for "
+      CMPP_DEFAULT_DELIM "expr, "
+      CMPP_DEFAULT_DELIM "assert, and "
+      CMPP_DEFAULT_DELIM "define.");
+  arg("-DXYZ[=value]","Define XYZ to the given value (default=1).");
+  arg("-UXYZ","Undefine all defines matching glob XYZ.");
+  arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path.");
+  arg("-LXYZ","Add dir XYZ to the loadable module search path.");
+  arg("-FXYZ=filename",
+      "Define XYZ to the raw contents of the given file.\n"
+      GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include.\n"
+      GAP "Maybe it should be. Or maybe we need a new flag for that.");
+  arg("-d|--delimiter VALUE", "Set directive delimiter to VALUE "
+      "(default=" CMPP_DEFAULT_DELIM ").");
+  arg("--@policy retain|elide|error|off",
+      "Specifies how to handle @tokens@ (default=off).\n"
+      GAP "off    = do not look for @tokens@\n"
+      GAP "retain = parse @tokens@ and retain any undefined ones\n"
+      GAP "elide  = parse @tokens@ and elide any undefined ones\n"
+      GAP "error  = parse @tokens@ and error out for any undefined ones"
+  );
+  arg("-u|--undefined-policy NAME",
+      "Sets the policy for how to handle references to undefined key:\n"
+      GAP "null  = treat them as empty/falsy. This is the default.\n"
+      GAP "error = trigger an error. This should probably be "
+      "the default."
+  );
+  arg("-@", "Equivalent to --@policy=error.");
+  arg("-no-@", "Equivalent to --@policy=off (the default).");
+  arg("--sql-trace", "Send a trace of all SQL to stderr.");
+  arg("--sql-trace-x",
+      "Like --sql-trace but expand all bound values in the SQL.");
+  arg("--no-sql-trace", "Disable SQL tracing (default).");
+  arg("--chomp-F", "One trailing newline is trimmed from files "
+      "read via -FXYZ=filename.");
+  arg("--no-chomp-F", "Disable --chomp-F (default).");
+#undef arg
+#undef GAP
+  fputs("\nFlags which require a value accept either "
+        "--flag=value or --flag value. "
+        "The exceptions are that the -D... and -F... flags "
+        "require their '=' to be part of the flag (because they "
+        "are parsed elsewhere).\n\n",fOut);
+#endif /*CMPP_OMIT_FILE_IO*/
+}
+
+#if defined(CMPP_MAIN) /* add main() */
+int main(int argc, char const * const * argv){
+  int rc = 0;
+  cmpp * pp = 0;
+  cmpp_flag32_t newFlags = 0
+#if defined(CMPP_MAIN_SAFEMODE)
+    | cmpp_ctor_F_SAFEMODE
+#endif
+    ;
+  cmpp_b bArgs = cmpp_b_empty;
+  sqlite3_config(SQLITE_CONFIG_URI,1);
+  {
+    /* Copy argv to a string so we can #define it. This has proven
+       helpful in testing, debugging, and output validation. */
+    for( int i = 0; i < argc; ++i ){
+      if( i ) cmpp_b_append_ch(&bArgs,' ');
+      cmpp_b_append(&bArgs, argv[i], strlen(argv[i]));
+    }
+    if( (rc = bArgs.errCode) ) goto end;
+    if( argc>1 && cmpp__arg_is_flag("--safe-mode", argv[1], NULL) ){
+      newFlags |= cmpp_ctor_F_SAFEMODE;
+      --argc;
+      ++argv;
+    }
+  }
+  cmpp_ctor_cfg const cfg = {
+    .flags = newFlags
+  };
+  rc = cmpp_ctor(&pp, &cfg);
+  if( rc ) goto end;
+  /**
+     Define CMPP_MAIN_INIT to the name of a function with the signature
+
+     int (*)(cmpp*)
+
+     to have it called here. The intent is that custom directives can
+     be installed this way without having to edit this code.
+  */
+#if defined(CMPP_MAIN_INIT)
+  extern int CMPP_MAIN_INIT(cmpp*);
+  if( 0!=(rc = CMPP_MAIN_INIT(pp)) ){
+    g_warn0("Initialization via CMPP_MAIN_INIT() failed");
+    goto end;
+  }
+#endif
+#if defined(CMPP_MAIN_AUTOLOADER)
+  {
+    extern int CMPP_MAIN_AUTOLOADER(cmpp*,char const *,void*);
+    cmpp_d_autoloader al = cmpp_d_autoloader_empty;
+    al.f = CMPP_MAIN_AUTOLOADER;
+    cmpp_d_autoloader_set(pp, &al);
+  }
+#endif
+  if( cmpp_define_v2(pp, "c-pp::argv", (char*)bArgs.z) ) goto end;
+  cmpp_b_clear(&bArgs);
+  rc = cmpp_process_argv(pp, argc-1, argv+1);
+  switch( rc ){
+    case 0: break;
+    case CMPP_RC_HELP:
+      rc = 0;
+      cmpp_process_argv_usage(argv[0], stdout);
+      break;
+    default:
+      break;
+  }
+end:
+  cmpp_b_clear(&bArgs);
+  if( pp ){
+    char const *zErr = 0;
+    rc = cmpp_err_get(pp, &zErr);
+    if( rc && CMPP_RC_HELP!=rc ){
+      g_warn("error %s: %s", cmpp_rc_cstr(rc), zErr);
+    }
+    cmpp_dtor(pp);
+  }else if( rc && CMPP_RC_HELP!=rc ){
+    g_warn("error #%d/%s", rc, cmpp_rc_cstr(rc));
+  }
+  sqlite3_shutdown();
+  return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+#endif /* CMPP_MAIN */
+/*
+** 2022-11-12:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the cmpp_b-related parts of libcmpp.
+*/
+
+const cmpp_b cmpp_b_empty = cmpp_b_empty_m;
+
+CMPP__EXPORT(int, cmpp_b_append4)(cmpp * const pp,
+                                  cmpp_b * const os,
+                                  void const * src,
+                                  cmpp_size_t n){
+  if( !ppCode && cmpp_b_append(os, src, n) ){
+    cmpp_check_oom(pp, 0);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_b_reserve3)(cmpp * const pp,
+                                   cmpp_b * const os,
+                                   cmpp_size_t n){
+  if( !ppCode && cmpp_b_reserve(os, n) ){
+    cmpp_check_oom(pp, 0);
+  }
+  return ppCode;
+}
+
+
+CMPP__EXPORT(void, cmpp_b_clear)(cmpp_b *s){
+  if( s->z ) cmpp_mfree(s->z);
+  *s = cmpp_b_empty;
+}
+
+CMPP__EXPORT(cmpp_b *, cmpp_b_reuse)(cmpp_b * const s){
+  if( s->z ){
+#if 1
+    memset(s->z, 0, s->nAlloc)
+      /* valgrind pushes for this, which is curious because
+       cmpp_b_reserve[3]() memset()s new space to 0.
+
+       Try the following without this block using one commit after
+       [5f9c31d1da1d] (that'll be the commit that this comment and #if
+       block were added):
+
+       ##define foo
+       ##if not defined a
+       ##/if
+       ##query define {select ?1 a} bind [1]
+
+       There's a misuse complaint about a jump depending on
+       uninitialized memory deep under cmpp__is_int(), in strlen(), on
+       the "define" argument of the ##query.  It does not appear if
+       the lines above it are removed, which indicates that it's at
+       least semi-genuine. gcc v13.3.0, if it matters.
+    */;
+#else
+    s->z[0] = 0;
+#endif
+    s->n = 0;
+  }
+  s->errCode = 0;
+  return s;
+}
+
+CMPP__EXPORT(void, cmpp_b_swap)(cmpp_b * const l, cmpp_b * const r){
+  if( l!=r ){
+    cmpp_b const x = *l;
+    *l = *r;
+    *r = x;
+  }
+}
+
+CMPP__EXPORT(int, cmpp_b_reserve)(cmpp_b *s, cmpp_size_t n){
+  if( 0==s->errCode && s->nAlloc < n ){
+    void * const m = cmpp_mrealloc(s->z, s->nAlloc + n);
+    if( m ){
+      memset((unsigned char *)m + s->nAlloc, 0, (n - s->nAlloc))
+        /* valgrind convincingly recommends this. */;
+      s->z = m;
+      s->nAlloc += n;
+    }else{
+      s->errCode = CMPP_RC_OOM;
+    }
+  }
+  return s->errCode;
+}
+
+CMPP__EXPORT(int, cmpp_b_append)(cmpp_b * os, void const *src,
+                                 cmpp_size_t n){
+  if(0==os->errCode){
+    cmpp_size_t const nNeeded = os->n + n + 1;
+    if( nNeeded>=os->nAlloc && cmpp_b_reserve(os, nNeeded) ){
+      assert( CMPP_RC_OOM==os->errCode );
+      return os->errCode;
+    }
+    memcpy(os->z + os->n, src, n);
+    os->n += n;
+    os->z[os->n] = 0;
+    if( 0 ) {
+      g_warn("n=%u z=[%.*s] nUsed=%d", (unsigned)n, (int)n,
+             (char const*) src, (int)os->n);
+    }
+  }
+  return os->errCode;
+}
+
+CMPP__EXPORT(int, cmpp_b_append_ch)(cmpp_b * os, char ch){
+  if( 0==os->errCode
+      && (os->n+1<os->nAlloc
+          || 0==cmpp_b_reserve(os, os->n+2)) ){
+    os->z[os->n++] = (unsigned char)ch;
+    os->z[os->n] = 0;
+  }
+  return os->errCode;
+}
+
+CMPP__EXPORT(int, cmpp_b_append_i32)(cmpp_b * os, int32_t d){
+  if( 0==os->errCode ){
+    char buf[16] = {0};
+    int const n = snprintf(buf, sizeof(buf), "%" PRIi32, d);
+    cmpp_b_append(os, buf, (unsigned)n);
+  }
+  return os->errCode;
+}
+
+CMPP__EXPORT(int, cmpp_b_append_i64)(cmpp_b * os, int64_t d){
+  if( 0==os->errCode ){
+    char buf[32] = {0};
+    int const n = snprintf(buf, sizeof(buf), "%" PRIi64, d);
+    cmpp_b_append(os, buf, (unsigned)n);
+  }
+  return os->errCode;
+}
+
+CMPP__EXPORT(bool, cmpp_b_chomp)(cmpp_b * b){
+  return cmpp_chomp(b->z, &b->n);
+}
+
+CMPP__EXPORT(void, cmpp_b_list_cleanup)(cmpp_b_list *li){
+  while( li->nAlloc ){
+    cmpp_b * const b = li->list[--li->nAlloc];
+    if(b){
+      cmpp_b_clear(b);
+      cmpp_mfree(b);
+    }
+  }
+  cmpp_mfree(li->list);
+  *li = cmpp_b_list_empty;
+}
+
+CMPP__EXPORT(void, cmpp_b_list_reuse)(cmpp_b_list *li){
+  while( li->n ){
+    cmpp_b * const b = li->list[li->n--];
+    if(b) cmpp_b_reuse(b);
+  }
+}
+
+static cmpp_b * cmpp_b_list_push(cmpp_b_list *li){
+  cmpp_b * p = 0;
+  assert( li->list ? li->nAlloc : 0==li->nAlloc );
+  if( !cmpp_b_list_reserve(NULL, li,
+                           cmpp__li_reserve1_size(li, 20)) ){
+    p = li->list[li->n];
+    if( p ){
+      cmpp_b_reuse(p);
+    }else{
+      p = cmpp_malloc(sizeof(*p));
+      if( p ){
+        li->list[li->n++] = p;
+        *p = cmpp_b_empty;
+      }
+    }
+  }
+  return p;
+}
+
+/**
+   bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
+   largest first and empty slots last.
+*/
+static int cmpp_b__cmp_desc(const void *p1, const void *p2){
+  cmpp_b const * const eL = *(cmpp_b const **)p1;
+  cmpp_b const * const eR = *(cmpp_b const **)p2;
+  if( eL==eR ) return 0;
+  else if( !eL ) return 1;
+  else if (!eR ) return -1;
+  return (int)(/*largest first*/eL->nAlloc - eR->nAlloc);
+}
+
+/**
+   bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
+   smallest first and empty slots last.
+*/
+static int cmpp_b__cmp_asc(const void *p1, const void *p2){
+  cmpp_b const * const eL = *(cmpp_b const **)p1;
+  cmpp_b const * const eR = *(cmpp_b const **)p2;
+  if( eL==eR ) return 0;
+  else if( !eL ) return 1;
+  else if (!eR ) return -1;
+  return (int)(/*smallest first*/eR->nAlloc - eL->nAlloc);
+}
+
+/**
+   Sort li's buffer list using the given policy. NULL entries always
+   sort last. This is a no-op of how == cmpp_b_list_UNSORTED or
+   li->n<2.
+*/
+static void cmpp_b_list__sort(cmpp_b_list * const li,
+                              enum cmpp_b_list_e how){
+  switch( li->n<2 ? cmpp_b_list_UNSORTED : how ){
+    case cmpp_b_list_UNSORTED:
+      break;
+    case cmpp_b_list_DESC:
+      qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_desc);
+      break;
+    case cmpp_b_list_ASC:
+      qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_asc);
+      break;
+  }
+}
+
+CMPP__EXPORT(cmpp_b *, cmpp_b_borrow)(cmpp *pp){
+  cmpp__pi(pp);
+  cmpp_b_list * const li = &pi->recycler.buf;
+  cmpp_b * b = 0;
+  if( cmpp_b_list_UNSORTED==pi->recycler.bufSort ){
+    pi->recycler.bufSort = cmpp_b_list_DESC;
+    cmpp_b_list__sort(li, pi->recycler.bufSort);
+    assert( cmpp_b_list_UNSORTED!=pi->recycler.bufSort
+            || pi->recycler.buf.n<2 );
+  }
+  for( cmpp_size_t i = 0; i < li->n; ++i ){
+    b = li->list[i];
+    if( b ){
+      li->list[i] = 0;
+      assert( !b->n &&
+              "Someone wrote to a buffer after giving it back" );
+      if( i < li->n-1 ){
+        pi->recycler.bufSort = cmpp_b_list_UNSORTED;
+      }
+      return cmpp_b_reuse(b);
+    }
+  }
+  /**
+     Allocate the list entry now and then remove the buffer from it to
+     "borrow" it. We allocate now, instead of in cmpp_b_return(), so
+     that that function has no OOM condition (handling it properly in
+     higher-level code would be a mess).
+  */
+  b = cmpp_b_list_push(li);
+  if( 0==cmpp_check_oom(pp, b) ) {
+    assert( b==li->list[li->n-1] );
+    li->list[li->n-1] = 0;
+  }
+  return b;
+}
+
+CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){
+  if( !b ) return;
+  cmpp__pi(pp);
+  cmpp_b_list * const li = &pi->recycler.buf;
+  for( cmpp_size_t i = 0; i < li->n; ++i ){
+    if( !li->list[i] ){
+      li->list[i] = cmpp_b_reuse(b);
+      pi->recycler.bufSort = cmpp_b_list_UNSORTED;
+      return;
+    }
+  }
+  assert( !"This shouldn't be possible - no slot in recycler.buf" );
+  cmpp_b_clear(b);
+  cmpp_mfree(b);
+}
+
+CMPP__EXPORT(int, cmpp_output_f_b)(
+  void * state, void const * src, cmpp_size_t n
+){
+  if( state ){
+    return cmpp_b_append(state, src, n);
+  }
+  return 0;
+}
+
+#if CMPP__OBUF
+int cmpp__obuf_flush(cmpp__obuf * b);
+int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n);
+void cmpp__obuf_cleanup(cmpp__obuf * b);
+int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n);
+int cmpp_flush_f_obuf(void * state);
+void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o);
+const cmpp__obuf cmpp__obuf_empty = cmpp__obuf_empty_m;
+const cmpp_outputer cmpp_outputer_obuf = {
+  .out = cmpp_output_f_obuf,
+  .flush = cmpp_flush_f_obuf,
+  .cleanup = cmpp_outputer_cleanup_f_obuf
+};
+#endif
+/*
+** 2025-11-07:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the db-related pieces of libcmpp.
+*/
+
+/**
+   A proxy for sqlite3_prepare() which updates pp->pimpl->err on error.
+*/
+static int cmpp__prepare(cmpp *pp, sqlite3_stmt **pStmt,
+                         const char * zSql, ...){
+  /* We need for pp->pimpl->stmt.sp* to work regardless of pending errors so
+     that we can, when appropriate, create the rollback statements. */
+  sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
+  char * z = 0;
+  int n = 0;
+  va_list va;
+  assert( pp->pimpl->db.dbh );
+  va_start(va, zSql);
+  sqlite3_str_vappendf(str, zSql, va);
+  va_end(va);
+  z = cmpp_str_finish(pp, str, &n);
+  if( z ){
+    int const rc = sqlite3_prepare_v2(pp->pimpl->db.dbh, z, n, pStmt, 0);
+    cmpp__db_rc(pp, rc, z);
+    sqlite3_free(z);
+  }
+  return ppCode;
+}
+
+sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
+                          bool prepEvenIfErr){
+  if( !pp->pimpl->db.dbh && cmpp__db_init(pp) ) return NULL;
+  sqlite3_stmt ** q = 0;
+  char const * zSql = 0;
+  switch(which){
+    default:
+      cmpp__fatal("Maintenance required: not a valid CmppStmt ID: %d", which);
+      return NULL;
+#define E(N,S) case CmppStmt_ ## N: zSql = S; q = &pp->pimpl->stmt.N; break;
+    CmppStmt_map(E)
+#undef E
+  }
+  assert( q );
+  assert( zSql && *zSql );
+  if( !*q && (!ppCode || prepEvenIfErr) ){
+    cmpp__prepare(pp, q, "%s", zSql);
+  }
+  return *q;
+}
+
+void cmpp__stmt_reset(sqlite3_stmt * const q){
+  if( q ){
+    sqlite3_clear_bindings(q);
+    sqlite3_reset(q);
+  }
+}
+
+static inline int cmpp__stmt_is_sp(cmpp const * const pp,
+                                   sqlite3_stmt const * const q){
+  return q==pp->pimpl->stmt.spBegin
+    || q==pp->pimpl->stmt.spRelease
+    || q==pp->pimpl->stmt.spRollback;
+}
+
+int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt){
+  int rc = SQLITE_ERROR;
+  assert( q );
+  if( !ppCode || cmpp__stmt_is_sp(pp,q) ){
+    rc = sqlite3_step(q);
+    cmpp__db_rc(pp, rc, sqlite3_sql(q));
+  }
+  if( resetIt /* even if ppCode!=0 */ ) cmpp__stmt_reset(q);
+  assert( 0!=rc );
+  return rc;
+}
+
+
+/**
+   Expects an SQLITE_... result code and returns an approximate match
+   from cmpp_rc_e. It specifically treats SQLITE_ROW and SQLITE_DONE
+   as non-errors, returning 0 for those.
+*/
+static int cmpp__db_errcode(sqlite3 * const db, int sqliteCode);
+int cmpp__db_errcode(sqlite3 * const db, int sqliteCode){
+  (void)db;
+  int rc = 0;
+  switch(sqliteCode & 0xff){
+    case SQLITE_ROW:
+    case SQLITE_DONE:
+    case SQLITE_OK: rc = 0; break;
+    case SQLITE_NOMEM: rc = CMPP_RC_OOM; break;
+    case SQLITE_CORRUPT: rc = CMPP_RC_CORRUPT; break;
+    case SQLITE_TOOBIG:
+    case SQLITE_FULL:
+    case SQLITE_RANGE: rc = CMPP_RC_RANGE; break;
+    case SQLITE_NOTFOUND: rc = CMPP_RC_NOT_FOUND; break;
+    case SQLITE_PERM:
+    case SQLITE_AUTH:
+    case SQLITE_BUSY:
+    case SQLITE_LOCKED:
+    case SQLITE_READONLY: rc = CMPP_RC_ACCESS; break;
+    case SQLITE_CANTOPEN:
+    case SQLITE_IOERR: rc = CMPP_RC_IO; break;
+    case SQLITE_NOLFS: rc = CMPP_RC_UNSUPPORTED; break;
+    default:
+      //MARKER(("sqlite3_errcode()=0x%04x\n", rc));
+      rc = CMPP_RC_DB; break;
+  }
+  return rc;
+}
+
+int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsg){
+  switch(dbRc){
+    case 0:
+    case SQLITE_DONE:
+    case SQLITE_ROW:
+      return 0;
+    default:
+      return cmpp_err_set(
+        pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc),
+        "SQLite error #%d: %s%s%s",
+        dbRc,
+                       pp->pimpl->db.dbh
+        ? sqlite3_errmsg(pp->pimpl->db.dbh)
+        : "<no db handle>",
+        zMsg ? ": " : "",
+        zMsg ? zMsg : ""
+      );
+  }
+}
+
+/**
+   The base "define" impl.  Requires q to be an INSERT for one of the
+   define tables and have the (t,k,v) columns set up to bind to ?1,
+   ?2, and ?3.
+*/
+static
+int cmpp__define_impl(cmpp * const pp,
+                      sqlite3_stmt * const q,
+                      unsigned char const * zKey,
+                      cmpp_ssize_t nKey,
+                      unsigned char const *zVal,
+                      cmpp_ssize_t nVal,
+                      int tType,
+                      bool resetStmt){
+  if( 0==ppCode){
+    assert( q );
+    nKey = cmpp__strlenu(zKey, nKey);
+    nVal = cmpp__strlenu(zVal, nVal);
+    if( 0==cmpp__bind_textn(pp, q, 2, zKey, (int)nKey)
+        && 0==cmpp__bind_int(pp, q, 1, tType) ){
+      //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
+      /* TODO? if tType==cmpp_TT_Blob, bind it as a blob */
+      if( zVal ){
+        if( nVal ){
+          cmpp__bind_textn(pp, q, 3, zVal, (int)nVal);
+        }else{
+          /* Arguable */
+          cmpp__bind_null(pp, q, 3);
+        }
+      }else{
+        cmpp__bind_int(pp, q, 3, 1);
+      }
+      cmpp__step(pp, q, resetStmt);
+      g_debug(pp,2,("define: %s [%s]=[%.*s]\n",
+                    cmpp_tt_cstr(tType), zKey, (int)nVal, zVal));
+    }
+  }
+  return ppCode;
+}
+
+int cmpp__define2(cmpp *pp,
+                  unsigned char const * zKey,
+                  cmpp_ssize_t nKey,
+                  unsigned char const *zVal,
+                  cmpp_ssize_t nVal,
+                  cmpp_tt tType){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
+  if( q ){
+    cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, tType, true);
+  }
+  return ppCode;
+}
+
+/**
+   The legacy variant of define() which accepts X=Y in zKey. This
+   continues to exist because it's convenient for passing args from
+   main().
+*/
+static int cmpp__define_legacy(cmpp *pp, const char * zKey, char const *zVal,
+                               cmpp_tt ttype ){
+
+  if(ppCode) return ppCode;
+  CmppKvp kvp = CmppKvp_empty;
+  if( CmppKvp_parse(pp, &kvp, ustr_c(zKey), -1,
+                     zVal
+                     ? CmppKvp_op_none
+                     : CmppKvp_op_eq1) ) {
+    return ppCode;
+  }
+  if( kvp.v.z ){
+    if( zVal ){
+      assert(!"cannot happen - CmppKvp_op_none will prevent it");
+      return cmpp_err_set(pp, CMPP_RC_MISUSE,
+                          "Cannot assign two values to [%.*s] [%.*s] [%s]",
+                          kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal);
+    }
+  }else{
+    kvp.v.z = (unsigned char const *)zVal;
+    kvp.v.n = zVal ? (int)strlen(zVal) : 0;
+  }
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
+  if( !q ) return ppCode;
+  int64_t intCheck = 0;
+  switch( ttype ){
+    case cmpp_TT_Unknown:
+      if(kvp.v.n){
+        if( cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
+          ttype = cmpp_TT_Int;
+          if( '+'==*kvp.v.z ){
+            ++kvp.v.z;
+            --kvp.v.n;
+          }
+        }else{
+          ttype = cmpp_TT_String;
+        }
+      }else if( kvp.v.z ){
+        ttype = cmpp_TT_String;
+      }else{
+        ttype = cmpp_TT_Int;
+        intCheck = 1 /* No value ==> value of 1. */;
+      }
+      break;
+    case cmpp_TT_Int:
+      if( !cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
+        ttype = cmpp_TT_String;
+      }
+      break;
+    default:
+      break;
+  }
+  if( 0==cmpp__bind_textn(pp, q, 2, kvp.k.z, kvp.k.n)
+      && 0==cmpp__bind_int(pp, q, 1, ttype) ){
+    //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
+    switch( ttype ){
+      case cmpp_TT_Int:
+        cmpp__bind_int(pp, q, 3, intCheck);
+        break;
+      case cmpp_TT_Null:
+        cmpp__bind_null(pp, q, 3);
+        break;
+      default:
+        cmpp__bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n);
+        break;
+    }
+    cmpp__step(pp, q, true);
+    g_debug(pp,2,("define: [%.*s]=[%.*s]\n",
+                  kvp.k.n, kvp.k.z,
+                  kvp.v.n, kvp.v.z));
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_define_legacy)(cmpp *pp, const char * zKey, char const *zVal){
+  return cmpp__define_legacy(pp, zKey, zVal, cmpp_TT_Unknown);
+}
+
+CMPP__EXPORT(int, cmpp_define_v2)(cmpp *pp, const char * zKey, char const *zVal){
+  return cmpp__define2(pp, ustr_c(zKey), -1, ustr_c(zVal), -1,
+                       cmpp_TT_String);
+}
+
+static
+int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey,
+                        cmpp_ssize_t nKey,
+                        unsigned char const *zVal,
+                        cmpp_ssize_t nVal,
+                        int ttype,
+                        int64_t * pId){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefIns, false);
+  if( q ){
+    if( 0==cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, ttype, false)
+        && pId ){
+      *pId = sqlite3_column_int64(q, 0);
+      assert( *pId );
+    }
+    cmpp__stmt_reset(q);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_define_shadow)(cmpp *pp, char const *zKey,
+                       char const *zVal, int64_t *pId){
+  assert( pId );
+  return cmpp__define_shadow(pp, ustr_c(zKey), -1,
+                             ustr_c(zVal), -1, cmpp_TT_String, pId);
+}
+
+static
+int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey,
+                          cmpp_ssize_t nKey, int64_t id){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false);
+  if( q ){
+    cmpp__bind_textn(pp, q, 1, zKey, (int)nKey);
+    cmpp__bind_int(pp, q, 2, id);
+    cmpp__step(pp, q, true);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_define_unshadow)(cmpp *pp, char const *zKey, int64_t id){
+  return cmpp__define_unshadow(pp, ustr_c(zKey), -1, id);
+}
+
+/*
+** This sqlite3_trace_v2() callback outputs tracing info using
+** ((cmpp*)c)->sqlTrace.pFile.
+*/
+static int cmpp__db_sq3TraceV2(unsigned dx,void*c,void*p,void*x){
+  switch(dx){
+    case SQLITE_TRACE_STMT:{
+      char const * const zSql = x;
+      cmpp * const pp = c;
+      cmpp__pi(pp);
+      if(pi->sqlTrace.out.out){
+        char * const zExp = pi->sqlTrace.expandSql
+          ? sqlite3_expanded_sql((sqlite3_stmt*)p)
+          : 0;
+        sqlite3_str * const s = sqlite3_str_new(pi->db.dbh);
+        if( pi->dx ){
+          cmpp__dx_append_script_info(pi->dx, s);
+          sqlite3_str_appendchar(s, 1, ':');
+          sqlite3_str_appendchar(s, 1, ' ');
+        }
+        sqlite3_str_appendall(s, zExp ? zExp : zSql);
+        sqlite3_str_appendchar(s, 1, '\n');
+        int const n = sqlite3_str_length(s);
+        if( n ){
+          char * const z = sqlite3_str_finish(s);
+          if( z ){
+            cmpp__out2(pp, &pi->sqlTrace.out, z, (cmpp_size_t)n);
+            sqlite3_free(z);
+          }
+        }
+        sqlite3_free(zExp);
+      }
+      break;
+    }
+  }
+  return 0;
+}
+
+#include <sys/stat.h>
+/*
+** sqlite3 UDF which returns true if its argument refers to an
+** accessible file, else false.
+*/
+static void cmpp__udf_file_exists(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  const char *zName;
+  (void)(argc);  /* Unused parameter */
+  zName = (const char*)sqlite3_value_text(argv[0]);
+  if( 0!=zName ){
+    struct stat sb;
+    sqlite3_result_int(context, stat(zName, &sb)
+                       ? 0
+                       : S_ISREG(sb.st_mode));
+  }
+}
+
+static void cmpp__udf_truthy(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  (void)(argc);  /* Unused parameter */
+  assert(1==argc);
+  int buul = 0;
+  sqlite3_value * const sv = argv[0];
+  switch( sqlite3_value_type(sv) ){
+    case SQLITE_NULL:
+      break;
+    case SQLITE_FLOAT:
+      buul = 0.0!=sqlite3_value_double(sv);
+      break;
+    case SQLITE_INTEGER:
+      buul = 0!=sqlite3_value_int(sv);
+      break;
+    case SQLITE_TEXT:
+    case SQLITE_BLOB:{
+      int const n = sqlite3_value_bytes(sv);
+      if( n>1 ) buul = 1;
+      else if( 1==n ){
+        const char *z =
+          (const char*)sqlite3_value_text(sv);
+        buul = z
+          ? 0!=strcmp(z,"0")
+          : 0;
+      }
+    }
+  }
+  sqlite3_result_int(context, buul);
+}
+
+/**
+   SQLite3 UDF which compares its two arguments using memcmp()
+   semantics. NULL will compare equal to NULL, but less than anything
+   else.
+*/
+static void cmpp__udf_compare(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  (void)(argc);  /* Unused parameter */
+  assert(2==argc);
+  sqlite3_value * const v1 = argv[0];
+  sqlite3_value * const v2 = argv[1];
+  unsigned char const * const z1 = sqlite3_value_text(v1);
+  unsigned char const * const z2 = sqlite3_value_text(v2);
+  int const n1 = sqlite3_value_bytes(v1);
+  int const n2 = sqlite3_value_bytes(v2);
+  int rv;
+  if( !z1 ){
+    rv = z2 ? -1 : 0;
+  }else if( !z2 ){
+    rv = 1;
+  }else{
+    rv = strncmp((char const *)z1, (char const *)z2, n1>n2 ? n1 : n2);
+  }
+  if(0) g_stderr("udf_compare (%s,%s) = %d\n", z1, z2, rv);
+  sqlite3_result_int(context, rv);
+}
+
+int cmpp__db_init(cmpp *pp){
+  cmpp__pi(pp);
+  if( pi->db.dbh || ppCode ) return ppCode;
+  int rc;
+  char * zErr = 0;
+  const char * zDrops =
+    "BEGIN EXCLUSIVE;"
+    "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".def;"
+    "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".incl;"
+    "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".inclpath;"
+    "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".predef;"
+    "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".ttype;"
+    "DROP VIEW  IF EXISTS "  CMPP__DB_MAIN_NAME ".vdef;"
+    "COMMIT;"
+    ;
+  const char * zSchema =
+    "BEGIN EXCLUSIVE;"
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".def("
+    /* ^^^ defines */
+      "t INTEGER DEFAULT NULL,"
+      /*^^ type: cmpp_tt or NULL */
+      "k TEXT PRIMARY KEY NOT NULL,"
+      "v TEXT DEFAULT NULL"
+    ") WITHOUT ROWID;"
+
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".incl("
+    /* ^^^ files currently being included */
+      "file TEXT PRIMARY KEY NOT NULL,"
+      "srcFile TEXT DEFAULT NULL,"
+      "srcLine INTEGER DEFAULT 0"
+    ") WITHOUT ROWID;"
+
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".inclpath("
+    /* ^^^ include path. We use (ORDER BY priority DESC, rowid) to
+       make their priority correct. priority should only be set by the
+       #include directive for its cwd entry. */
+      "priority INTEGER DEFAULT 0," /* higher sorts first */
+      "dir TEXT UNIQUE NOT NULL ON CONFLICT IGNORE"
+    ");"
+
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".modpath("
+    /* ^^^ module path. We use ORDER BY ROWID to make their
+       priority correct. */
+      "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
+    ");"
+
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".predef("
+    /* ^^^ pre-defines */
+      "t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
+      "k TEXT PRIMARY KEY NOT NULL,"
+      "v TEXT DEFAULT NULL"
+    ") WITHOUT ROWID;"
+    "INSERT INTO " CMPP__DB_MAIN_NAME ".predef (t,k,v)"
+    " VALUES(NULL,'cmpp::version','" CMPP_VERSION "')"
+    ";"
+
+    /**
+       sdefs - "scoped defines" or "shadow defines". The problem these
+       solve is the one of supporting a __FILE__ define in cmpp input
+       sources, such that it remains valid both before and after an
+       #include, but has a new name in the scope of an #include. We
+       can't use savepoints for that because they're a nuclear option
+       affecting _all_ #defines in the #include'd file, whereas we
+       normally want #defines to stick around across files.
+
+       See cmpp_define_shadow() and cmpp_define_unshadow().
+    */
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".sdef("
+     "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+      "t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
+      "k TEXT NOT NULL,"
+      "v TEXT DEFAULT NULL"
+    ");"
+
+    /**
+       vdef is a view consolidating the various #define stores. It's
+       intended to be used for all general-purpose fetching of defines
+       and it orders the results such that the library's defines
+       supercede all others, then scoped keys, then client-level
+       defines.
+
+       To push a new sdef we simply insert into sdef. Then vdef will
+       order the newest sdef before any entry from the def table.
+    */
+    "CREATE VIEW " CMPP__DB_MAIN_NAME ".vdef(source,t,k,v) AS"
+    " SELECT NULL,t,k,v FROM " CMPP__DB_MAIN_NAME ".predef"
+    /* ------^^^^ sorts before numbers */
+    " UNION ALL"
+    " SELECT -rowid,t,k,v FROM " CMPP__DB_MAIN_NAME ".sdef"
+    /* ^^^^ sorts newest of matching keys first */
+    " UNION ALL"
+    " SELECT 0,t,k,v FROM " CMPP__DB_MAIN_NAME ".def"
+    " ORDER BY 1, 3"
+    ";"
+
+#if 0
+    "CREATE TABLE " CMPP__DB_MAIN_NAME ".ttype("
+    /* ^^^ token types */
+      "t INTEGER PRIMARY KEY NOT NULL,"
+      /*^^ type: cmpp_tt */
+      "n TEXT NOT NULL,"
+      /*^^ cmpp_TT_... name. */
+      "s TEXT DEFAULT NULL"
+      /* Symbolic or directive name, if any. */
+    ");"
+#endif
+
+    "COMMIT;"
+    "BEGIN EXCLUSIVE;"
+    ;
+  cmpp__err_clear(pp);
+  int openFlags = SQLITE_OPEN_READWRITE;
+  if( pi->db.zName ){
+    openFlags |= SQLITE_OPEN_CREATE;
+  }
+  rc = sqlite3_open_v2(
+    pi->db.zName ? pi->db.zName : ":memory:",
+    &pi->db.dbh, openFlags, 0);
+  if(rc){
+    cmpp__db_rc(pp, rc, pi->db.zName
+                ? pi->db.zName
+                : ":memory:");
+    sqlite3_close(pi->db.dbh);
+    pi->db.dbh = 0;
+    assert(ppCode);
+    return rc;
+  }
+  sqlite3_busy_timeout(pi->db.dbh, 5000);
+  sqlite3_db_config(pi->db.dbh, SQLITE_DBCONFIG_MAINDBNAME,
+                    CMPP__DB_MAIN_NAME);
+  rc = sqlite3_trace_v2(pi->db.dbh, SQLITE_TRACE_STMT,
+                        cmpp__db_sq3TraceV2, pp);
+  if( cmpp__db_rc(pp, rc, "Installing tracer failed") ){
+    goto end;
+  }
+  //g_warn("Schema:\n%s\n",zSchema);
+  struct {
+    /* SQL UDFs */
+    char const * const zName;
+    void (*xUdf)(sqlite3_context *,int,sqlite3_value **);
+    int arity;
+    int flags;
+  } aFunc[] = {
+    {
+      .zName = "cmpp_file_exists",
+      .xUdf  = cmpp__udf_file_exists,
+      .arity = 1,
+      .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY
+    },
+    {
+      .zName = "cmpp_truthy",
+      .xUdf  = cmpp__udf_truthy,
+      .arity = 1,
+      .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
+    },
+    {
+      .zName = "cmpp_compare",
+      .xUdf  = cmpp__udf_compare,
+      .arity = 2,
+      .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
+    }
+  };
+  assert( 0==rc );
+  for( unsigned int i = 0; 0==rc && i < sizeof(aFunc)/sizeof(aFunc[0]); ++i ){
+    rc = sqlite3_create_function(
+      pi->db.dbh, aFunc[i].zName, aFunc[i].arity,
+      aFunc[i].flags, 0, aFunc[i].xUdf, 0, 0
+    );
+  }
+  if( cmpp__db_rc(pp, rc, "UDF registration failed.") ){
+    return ppCode;
+  }
+  if( pi->db.zName ){
+    /* Drop all cmpp tables when using a persistent db so that we are
+       not beholden to a given structure.  TODO: a config flag to
+       toggle this. */
+    rc = sqlite3_exec(pi->db.dbh, zDrops, 0, 0, &zErr);
+  }
+  if( !rc ){
+    rc = sqlite3_exec(pi->db.dbh, zSchema, 0, 0, &zErr);
+  }
+
+  if( !rc ){
+    extern int sqlite3_series_init(sqlite3 *, char **, const sqlite3_api_routines *);
+    rc = sqlite3_series_init(pi->db.dbh, &zErr, NULL);
+  }
+
+  if(rc){
+    if( zErr ){
+      cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc),
+                   "SQLite error #%d initializing DB: %s", rc, zErr);
+      sqlite3_free(zErr);
+    }else{
+      cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc),
+                   "SQLite error #%d initializing DB", rc);
+    }
+    goto end;
+  }
+
+  while(0){
+    /* Insert the ttype mappings. We don't yet make use of this but
+       only for lack of a use case ;). */
+    sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_insTtype, false);
+    if( !q ) goto end;
+#define E(N,STR)                                                      \
+    cmpp__bind_int(pp, q, 1, cmpp_TT_ ## N);                               \
+    cmpp__bind_textn(pp, q, 2,                                        \
+                     ustr_c("cmpp_TT_ " # N), sizeof("cmpp_TT_" # N)-1);        \
+    if( STR ) cmpp__bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \
+    else cmpp__bind_null(pp, q, 3);                                   \
+    if( SQLITE_DONE!=cmpp__step(pp, q, true) ) return ppCode;
+    cmpp_tt_map(E)
+#undef E
+    sqlite3_finalize(q);
+    pi->stmt.insTtype = 0;
+    break;
+  }
+
+end:
+  if( !ppCode ){
+    /*
+    ** Keep us from getting in the situation later that delayed
+    ** preparation if one of the savepoint statements fails (e.g. due
+    ** to OOM or memory corruption).
+    */
+    cmpp__stmt(pp, CmppStmt_spBegin, false);
+    cmpp__stmt(pp, CmppStmt_spRelease, false);
+    cmpp__stmt(pp, CmppStmt_spRollback, false);
+    cmpp__lazy_init(pp);
+  }
+  return ppCode;
+}
+/*
+** 2022-11-12:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the core cmpp_dx_f() implementations of libcmpp.
+*/
+
+static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){
+  return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' may only be used once.",
+                         arg->z);
+}
+
+/* No-op cmpp_dx_f() impl. */
+static void cmpp_dx_f_noop(cmpp_dx *dx){
+  (void)dx;
+}
+
+/**
+   cmpp_kav_each_f() impl for use by #define {k->v}.
+*/
+static int cmpp_kav_each_f_define__group(
+  cmpp_dx *dx,
+  unsigned char const *zKey, cmpp_size_t nKey,
+  unsigned char const *zVal, cmpp_size_t nVal,
+  void* callbackState
+){
+  if( (callbackState==dx)
+      && cmpp_has(dx->pp, (char const*)zKey, nKey) ){
+    return dxppCode;
+  }
+  return cmpp__define2(dx->pp, zKey, nKey, zVal, nVal, cmpp_TT_String);
+}
+
+/* #error impl. */
+static void cmpp_dx_f_error(cmpp_dx *dx){
+  const char *zBegin = (char const *)dx->args.z;
+  unsigned n = (unsigned)dx->args.nz;
+  if( n>2 && (('"' ==*zBegin || '\''==*zBegin) && zBegin[n-1]==*zBegin) ){
+    ++zBegin;
+    n -= 2;
+  }
+  if( n ){
+    cmpp_dx_err_set(dx, CMPP_RC_ERROR, "%.*s", n, zBegin);
+  }else{
+    cmpp_dx_err_set(dx, CMPP_RC_ERROR, "(no additional info)");
+  }
+}
+
+/* Impl. for #define. */
+static void cmpp_dx_f_define(cmpp_dx *dx){
+  cmpp_d const * const d = dx->d;
+  assert(d);
+  if( !dx->args.arg0 ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                "Expecting one or more arguments");
+    return;
+  }
+  cmpp_arg const * aKey = 0;
+  int argNdx = 0;
+  int nChomp = 0;
+  unsigned nHeredoc = 0;
+  unsigned char acHeredoc[128] = {0} /* TODO: cmpp_args_clone() */;
+  bool ifNotDefined = false /* true if '?' arg */;
+  cmpp_arg const *aAppend = 0;
+#define checkIsDefined(ARG) \
+    if(ifNotDefined && (cmpp_has(dx->pp, (char const*)ARG->z, ARG->n) \
+                        || dxppCode)) break
+
+  for( cmpp_arg const * arg = dx->args.arg0;
+       0==dxppCode && arg;
+       arg = arg->next, ++argNdx ){
+    //g_warn("arg=%s", arg->z);
+    if( 0==argNdx && cmpp_arg_equals(arg, "?") ){
+      /* Only set the key if it's not already defined. */
+      ifNotDefined = true;
+      continue;
+    }
+    switch( arg->ttype ){
+      case cmpp_TT_ShiftL3:
+        ++nChomp;
+        /* fall through */
+      case cmpp_TT_ShiftL:
+        if( arg->next || argNdx<1 ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "Ill-placed '%s'.", arg->z);
+        }else if( arg->n >= sizeof(acHeredoc)-1 ){
+          cmpp_dx_err_set(dx, CMPP_RC_RANGE,
+                      "Heredoc name is too large.");
+        }else if( !aKey ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "Missing key before %s.",
+                          cmpp__tt_cstr(cmpp_TT_ShiftL, false));
+        }else{
+          assert( aKey );
+          nHeredoc = aKey->n;
+          memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/);
+        }
+        break;
+      case cmpp_TT_OpEq:
+        if( 1 /*seenEq || argNdx!=1 || !arg->next*/ ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "Ill-placed '%s'.", arg->z);
+          break;
+        }
+        continue;
+      case cmpp_TT_StringAt:
+        if( cmpp__StringAtIsOk(dx->pp, cmpp_atpol_CURRENT) ){
+          break;
+        }
+        /* fall through */
+      case cmpp_TT_Int:
+      case cmpp_TT_String:
+      case cmpp_TT_Word:
+        if( cmpp_arg_isflag(arg,"-chomp") ){
+          ++nChomp;
+          break;
+        }
+        if( cmpp_arg_isflag(arg,"-append")
+            || cmpp_arg_isflag(arg,"-a") ){
+          aAppend = arg->next;
+          if( !aAppend ){
+            cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                            "Expecting argument for %s",
+                            arg->z);
+          }
+          arg = aAppend;
+          break;
+        }
+        if( aKey ){
+          /* This is the second arg - the value */
+          checkIsDefined(aKey);
+          cmpp_b * const os = cmpp_b_borrow(dx->pp);
+          cmpp_b * const ba = aAppend ? cmpp_b_borrow(dx->pp) : 0;
+          while( os ){
+            if( ba ){
+              cmpp__get_b(dx->pp, aKey->z, aKey->n, ba, false);
+              if( dxppCode ) break;
+              if( 0 ){
+                g_warn("key=%s\n", aKey->z);
+                g_warn("ba=%u %.*s\n", ba->n, ba->n, ba->z);
+              }
+            }
+            if( cmpp_arg_to_b(dx, arg, os,
+                              cmpp_arg_to_b_F_BRACE_CALL) ) break;
+            cmpp_b * const which = (ba && ba->n) ? ba : os;
+            if( which==ba && os->n ){
+              if( ba->n ) cmpp_b_append4(dx->pp, ba, aAppend->z, aAppend->n);
+              cmpp_b_append4(dx->pp, ba, os->z, os->n);
+            }
+            cmpp__define2(dx->pp, aKey->z, aKey->n, which->z, which->n,
+                          arg->ttype);
+            if( 0 ){
+              g_warn("aKey=%u z=[%.*s]\n", aKey->n, (int)aKey->n, aKey->z);
+              g_warn("nExp=%u z=[%.*s]\n", which->n, (int)which->n, which->z);
+            }
+            break;
+          }
+          cmpp_b_return(dx->pp, os);
+          cmpp_b_return(dx->pp, ba);
+          aKey = 0;
+        }else if( cmpp_TT_Word!=arg->ttype ){
+          cmpp_dx_err_set(dx, CMPP_RC_TYPE,
+                      "Expecting a define-name token here.");
+        }else if( arg->next ){
+          aKey = arg;
+        }else{
+          /* No value = a value of 1. */
+          checkIsDefined(arg);
+          cmpp__define2(dx->pp, arg->z, arg->n,
+                        ustr_c("1"), 1, cmpp_TT_Int);
+        }
+        break;
+      case cmpp_TT_GroupSquiggly:
+        assert( !acHeredoc[0] );
+        if( (ifNotDefined ? argNdx>1 : argNdx>0) || arg->next ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "{...} must be the only argument.")
+            /* This is for simplicity's sake. */;
+        }else{
+          cmpp_kav_each(dx, arg->z, arg->n,
+                        cmpp_kav_each_f_define__group,
+                        ifNotDefined ? dx : NULL,
+                        cmpp_kav_each_F_NOT_EMPTY
+                        | cmpp_kav_each_F_CALL_VAL
+                        | cmpp_kav_each_F_PARENS_EXPR
+                        //TODO cmpp_kav_each_F_IF_UNDEF
+          );
+        }
+        aKey = 0;
+        break;
+      case cmpp_TT_GroupParen:{
+        if( !aKey ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "(...) is not permitted as a key.");
+          break;
+        }
+        checkIsDefined(aKey);
+        int d = 0;
+        if( 0==cmpp__arg_evalSubToInt(dx, arg, &d) ){
+          char exprBuf[32] = {0};
+          cmpp_size_t nVal =
+            (cmpp_size_t)snprintf(&exprBuf[0],
+                                  sizeof(exprBuf), "%d", d);
+          assert(nVal>0);
+          cmpp__define2(dx->pp, aKey->z, aKey->n,
+                        ustr_c(&exprBuf[0]), nVal, cmpp_TT_Int);
+        }
+        break;
+      }
+      case cmpp_TT_GroupBrace:{
+        if( !aKey ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "[...] is not permitted as a key.");
+          break;
+        }
+        checkIsDefined(aKey);
+        cmpp_b * const b = cmpp_b_borrow(dx->pp);
+        if( b && 0==cmpp_call_str(dx->pp, arg->z, arg->n, b, 0) ){
+          cmpp__define2(dx->pp, aKey->z, aKey->n,
+                        b->z, b->n, cmpp_TT_AnyType);
+        }
+        cmpp_b_return(dx->pp, b);
+        break;
+      }
+      default:
+        // TODO: treat (...) as an expression
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Unhandled arg type %s: %s",
+                        cmpp__tt_cstr(arg->ttype, true), arg->z);
+        break;
+    }
+  }
+  if( 0==nHeredoc && nChomp ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "-chomp can only be used with <<.");
+  }
+  if( 0==dxppCode && nHeredoc ){
+    // Process (#define KEY <<)
+    cmpp_b * const os = cmpp_b_borrow(dx->pp);
+    assert( dx->d->closer );
+    if( os &&
+        0==cmpp_dx_consume_b(dx, os, &dx->d->closer, 1,
+                             cmpp_dx_consume_F_PROCESS_OTHER_D) ){
+      while( nChomp-- && cmpp_b_chomp(os) ){}
+      g_debug(dx->pp,2,("define  heredoc: [%s]=[%.*s]\n",
+                       acHeredoc, (int)os->n, os->z));
+      if( !ifNotDefined
+          || !cmpp_has(dx->pp, (char const*)acHeredoc, nHeredoc) ){
+        cmpp__define2(
+          dx->pp, acHeredoc, nHeredoc, os->z, os->n, cmpp_TT_String
+        );
+      }
+    }
+    cmpp_b_return(dx->pp, os);
+  }
+#undef checkIsDefined
+  return;
+}
+
+/* Impl. for #undef */
+static void cmpp_dx_f_undef(cmpp_dx *dx){
+  if( !dx->args.arg0 ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                "Expecting one or more arguments");
+    return;
+  }
+  cmpp_d const * const d = dx->d;
+  for( cmpp_arg const * arg = dx->args.arg0;
+       0==dxppCode && arg;
+       arg = arg->next ){
+    if( 0 ){
+      g_stderr("  %s: %s %p n=%d %.*s\n", d->name.z,
+               cmpp__tt_cstr(arg->ttype, true), arg->z,
+               (int)arg->n, (int)arg->n, arg->z);
+    }
+    if( cmpp_TT_Word==arg->ttype ){
+#if 0
+      /* Too strict? */
+      if( 0==cmpp__legal_key_check(dx->pp, arg->z,
+                                   (cmpp_ssize_t)arg->n, false) ) {
+        cmpp_undef(dx->pp, (char const *)arg->z);
+      }
+#else
+      cmpp_undef(dx->pp, (char const *)arg->z, NULL);
+#endif
+    }else{
+      cmpp_err_set(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s",
+                d->name.z, arg->z);
+    }
+  }
+}
+
+/* Impl. for #once. */
+static void cmpp_dx_f_once(cmpp_dx *dx){
+  cmpp_d const * const d = dx->d;
+  assert(d);
+  assert(d->closer);
+  if( dx->args.arg0 ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Expecting no arguments");
+    return;
+  }
+  cmpp_dx_pimpl * const dxp = dx->pimpl;
+  cmpp_b * const b = cmpp_b_borrow(dx->pp);
+  if( !b ) return;
+  cmpp_b_append_ch(b, '#');
+  cmpp_b_append4(dx->pp, b, d->name.z, d->name.n);
+  cmpp_b_append_ch(b, ':');
+  cmpp__get_b(dx->pp, ustr_c("__FILE__"), 8, b, true)
+    /* Wonky return semantics. */;
+  if( b->errCode
+      || dxppCode
+      || cmpp_b_append_ch(b, ':')
+      || cmpp_b_append_i32(b, (int)dxp->pos.lineNo) ){
+    goto end;
+  }
+  //g_debug(dx->pp,1,("#once key: %s", b->z));
+  int const had = cmpp_has(dx->pp, (char const *)b->z, b->n);
+  if( dxppCode ) goto end;
+  else if( had ){
+    CmppLvl * const lvl = CmppLvl_push(dx);
+    if( lvl ){
+      CmppLvl_elide(lvl, true);
+      cmpp_outputer devNull = cmpp_outputer_empty;
+      cmpp_dx_consume(dx, &devNull, &d->closer, 1,
+                      cmpp_dx_consume_F_PROCESS_OTHER_D);
+      CmppLvl_pop(dx, lvl);
+    }
+  }else if( !cmpp_define_v2(dx->pp, (char const*)b->z, "1") ){
+    cmpp_dx_consume(dx, NULL, &d->closer, 1,
+                    cmpp_dx_consume_F_PROCESS_OTHER_D);
+  }
+end:
+  cmpp_b_return(dx->pp, b);
+  return;
+}
+
+
+/* Impl. for #/define, /#query, /#pipe. */
+CMPP__EXPORT(void, cmpp_dx_f_dangling_closer)(cmpp_dx *dx){
+  cmpp_d const * const d = dx->d;
+  char const * const zD = cmpp_dx_delim(dx);
+  dxserr("%s%s used without its opening directive.",
+         zD, d->name.z);
+}
+
+#ifndef CMPP_OMIT_D_INCLUDE
+static int cmpp__including_has(cmpp *pp, unsigned const char * zName){
+  int rc = 0;
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclHas, false);
+  if( q && 0==cmpp__bind_text(pp, q, 1, zName) ){
+    if(SQLITE_ROW == cmpp__step(pp, q, true)){
+      rc = 1;
+    }else{
+      rc = 0;
+    }
+    g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc));
+  }
+  return rc;
+}
+
+/**
+   Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of
+   the `#include` dirs (cmpp_include_dir_add()). If no file match is
+   found, NULL is returned. Memory must eventually be passed to
+   cmpp_mfree() to free it.
+*/
+static char * cmpp__include_search(cmpp *pp, unsigned const char * zKey,
+                                   int * nVal){
+  char * zName = 0;
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false);
+  if( nVal ) *nVal = 0;
+  if( q && 0==cmpp__bind_text(pp, q, 1, zKey) ){
+    int const rc = cmpp__step(pp, q, false);
+    if(SQLITE_ROW==rc){
+      const unsigned char * z = sqlite3_column_text(q, 0);
+      int const n = sqlite3_column_bytes(q,0);
+      zName = n ? sqlite3_mprintf("%.*s", n, z) : 0;
+      if( n ) cmpp_check_oom(pp, zName);
+      if( nVal ) *nVal = n;
+    }
+    cmpp__stmt_reset(q);
+  }
+  return zName;
+}
+
+/**
+   Removes zKey from the currently-being-`#include`d list
+   list.
+*/
+static int cmpp__include_rm(cmpp *pp, unsigned const char * zKey){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclDel, false);
+  if( q ){
+    cmpp__bind_text(pp, q, 1, ustr_c(zKey));
+    cmpp__step(pp, q, true);
+    g_debug(pp,2,("incl rm [%s]\n", zKey));
+  }
+  return ppCode;
+}
+
+#if 0
+/*
+** Sets pp's error state if the `#include` list contains the given
+** key.
+*/
+static int cmpp__including_check(cmpp *pp, const char * zKey);
+int cmpp__including_check(cmpp *pp, const char * zName){
+  if( !ppCode ){
+    if(cmpp__including_has(pp, zName)){
+      cmpp__err(pp, CMPP_RC_MISUSE,
+                    "Recursive include detected: %s\n", zName);
+    }
+  }
+  return ppCode;
+}
+#endif
+
+
+/**
+   Adds the given filename to the list of being-`#include`d files,
+   using the given source file name and line number of error reporting
+   purposes. If recursion is later detected.
+*/
+static int cmpp__including_add(cmpp *pp, unsigned const char * zKey,
+                               unsigned const char * zSrc, cmpp_size_t srcLine){
+  sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclIns, false);
+  if( q ){
+    cmpp__bind_text(pp, q, 1, zKey);
+    cmpp__bind_text(pp, q, 2, zSrc);
+    cmpp__bind_int(pp, q, 3, srcLine);
+    cmpp__step(pp, q, true);
+    g_debug(pp,2,("is-including-file add [%s] from [%s]:%"
+                  CMPP_SIZE_T_PFMT "\n", zKey, zSrc, srcLine));
+  }
+  return ppCode;
+}
+
+/* Impl. for #include. */
+static void cmpp_dx_f_include(cmpp_dx *dx){
+  char * zResolved = 0;
+  int nResolved = 0;
+  cmpp_b * const ob = cmpp_b_borrow(dx->pp);
+  bool raw = false;
+  cmpp_args args = cmpp_args_empty;
+  if( !ob || cmpp_dx_args_clone(dx, &args) ){
+    goto end;
+  }
+  assert(args.pimpl && args.pimpl->pp==dx->pp);
+  cmpp_arg const * arg = args.arg0;
+  for( ; arg; arg = arg->next){
+#define FLAG(X)if( cmpp_arg_isflag(arg, X) )
+    FLAG("-raw"){
+      raw = true;
+      continue;
+    }
+    break;
+#undef FLAG
+  }
+  if( !arg ){
+    cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
+                "Expecting at least one filename argument.");
+  }
+  for( ; !dxppCode && arg; arg = arg->next ){
+    cmpp_flag32_t a2bf = cmpp_arg_to_b_F_BRACE_CALL;
+    if( cmpp_TT_Word==arg->ttype && cmpp__arg_wordIsPathOrFlag(arg) ){
+      a2bf |= cmpp_arg_to_b_F_NO_DEFINES;
+    }
+    if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(ob), a2bf) ){
+      break;
+    }
+    //g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved);
+    if(!raw && cmpp__including_has(dx->pp, ob->z)){
+      /* Note that different spellings of the same filename
+      ** will elude this check, but that seems okay, as different
+      ** spellings means that we're not re-running the exact same
+      ** invocation. We might want some other form of multi-include
+      ** protection, rather than this, however. There may well be
+      ** sensible uses for recursion. */
+      cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Recursive include of file: %s",
+                  ob->z);
+      break;
+    }
+    cmpp_mfree(zResolved);
+    nResolved = 0;
+    zResolved = cmpp__include_search(dx->pp, ob->z, &nResolved);
+    if(!zResolved){
+      if( !dxppCode ){
+        cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "file not found: %s", ob->z);
+      }
+      break;
+    }
+    if( raw ){
+      if( !dx->pp->pimpl->out.out ) break;
+      FILE * const fp = cmpp_fopen(zResolved, "r");
+      if( fp ){
+        int const rc = cmpp_stream(cmpp_input_f_FILE, fp,
+                                   dx->pp->pimpl->out.out,
+                                   dx->pp->pimpl->out.state);
+        if( rc ){
+          cmpp_dx_err_set(dx, rc, "Unknown error streaming file %s.",
+                      arg->z);
+        }
+        cmpp_fclose(fp);
+      }else{
+        cmpp_dx_err_set(dx, cmpp_errno_rc(errno, CMPP_RC_IO),
+                    "Unknown error opening file %s.", arg->z);
+      }
+    }else{
+      cmpp__including_add(dx->pp, ob->z, ustr_c(dx->sourceName),
+                          dx->pimpl->dline.lineNo);
+      cmpp_process_file(dx->pp, zResolved);
+      cmpp__include_rm(dx->pp, ob->z);
+    }
+  }
+end:
+  cmpp_mfree(zResolved);
+  cmpp_args_cleanup(&args);
+  cmpp_b_return(dx->pp, ob);
+}
+#endif /* #ifndef CMPP_OMIT_D_INCLUDE */
+
+/**
+   cmpp_dx_f() callback state for cmpp_dx_f_if(): pointers to the
+   various directives of that family.
+*/
+struct CmppIfState {
+  cmpp_d * dIf;
+  cmpp_d * dElif;
+  cmpp_d * dElse;
+  cmpp_d const * dEndif;
+};
+typedef struct CmppIfState CmppIfState;
+
+/* Version 2 of #if. */
+static void cmpp_dx_f_if(cmpp_dx *dx){
+  /* Reminder to self:
+
+     We need to be able to recurse, even in skip mode, for #if nesting
+     to work.  That's not great because it means we are evaluating
+     stuff we ideally should be skipping over, but it's keeping the
+     current tests working as-is. We can/do, however, avoid evaluating
+     expressions and such when recursing via skip mode. If we can
+     eliminate that here, by keeping track of the #if stack depth,
+     then we can possibly eliminate the whole CmppLvl_F_ELIDE
+     flag stuff.
+
+     The more convoluted version 1 #if (which this replaced not hours
+     ago) kept track of the skip state across a separate directive
+     function for #if and #/if. That was more complex but did avoid
+     having to recurse into #if in order to straighten out #elif and
+     #else. Update: tried a non-recursive variant involving moving
+     this function's gotTruth into the CmppLvl object() and
+     managing the CmppLvl stack here, but it just didn't want to
+     work for me and i was too tired to figure out why.
+  */
+  int gotTruth = 0 /*expr result*/;
+  CmppIfState const * const cis = dx->d->impl.state;
+  cmpp_d const * dClosers[] = {
+    cis->dElif, cis->dElse, cis->dEndif
+  };
+  CmppLvl * lvl = 0;
+  CmppDLine const dline = dx->pimpl->dline;
+  cmpp_args args = cmpp_args_empty;
+  char delim[20] = {0};
+#define skipOn CmppLvl_elide((lvl), true)
+#define skipOff CmppLvl_elide((lvl), false)
+
+  assert( dx->d==cis->dIf );
+  if( !dx->args.arg0 ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an expression.");
+    return;
+  }
+  snprintf(delim, sizeof(delim), "%s", cmpp_dx_delim(dx));
+  delim[sizeof(delim)-1] = 0;
+  lvl = CmppLvl_push(dx);
+  if( !lvl ) goto end;
+  if( cmpp_dx_is_eliding(dx) ){
+    gotTruth = 1;
+  }else if( cmpp__args_evalToInt(dx, &dx->pimpl->args, &gotTruth) ){
+    goto end;
+  }else if( !gotTruth ){
+    skipOn;
+  }
+
+  cmpp_d const * dPrev = dx->d;
+  cmpp_outputer devNull = cmpp_outputer_empty;
+  while( !dxppCode ){
+    dPrev = dx->d;
+    bool const isFinal = dPrev==cis->dElse
+      /* true if expecting an #/if. */;
+    if( cmpp_dx_consume(dx,
+                        CmppLvl_is_eliding(lvl) ? &devNull : NULL,
+                        isFinal ? &cis->dEndif : dClosers,
+                        isFinal ? 1 : sizeof(dClosers)/sizeof(dClosers[0]),
+                        cmpp_dx_consume_F_PROCESS_OTHER_D) ){
+      break;
+    }
+    cmpp_d const * const d2 = dx->d;
+    if( !d2 ){
+      dxserr("Reached end of input in an untermined %s%s opened "
+             "at line %" CMPP_SIZE_T_PFMT ".",
+             delim, cis->dIf->name.z, dline.lineNo);
+    }
+    if( d2==cis->dEndif ){
+      break;
+    }else if( isFinal ){
+      assert(!"cannot happen - caught by consume()");
+      dxserr("Expecting %s%s to close %s%s.",
+             delim, cis->dEndif->name.z,
+             delim, dPrev->name.z);
+      break;
+    }else if( gotTruth ){
+      skipOn;
+      continue;
+    }else if( d2==cis->dElif ){
+      if( 0==cmpp_dx_args_parse(dx, &args)
+          && 0==cmpp__args_evalToInt(dx, &args, &gotTruth) ){
+        if( gotTruth ) skipOff;
+        else skipOn;
+      }
+      continue;
+    }else{
+      assert( d2==cis->dElse
+              && "Else (haha!) we cannot have gotten here" );
+      skipOff;
+      continue;
+    }
+    assert(!"unreachable");
+  }
+
+#undef skipOff
+#undef skipOn
+end:
+  cmpp_args_cleanup(&args);
+  if( lvl ){
+    bool const lvlIsOk = CmppLvl_get(dx)==lvl;
+    CmppLvl_pop(dx, lvl);
+    if( !lvlIsOk && !dxppCode ){
+      assert(!"i naively believe that this is not possible");
+      cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
+                      "Mis-terminated %s%s opened at line "
+                      "%" CMPP_SIZE_T_PFMT ".",
+                      delim, cis->dIf->name.z, dline.lineNo);
+    }
+  }
+  return;
+}
+
+/* Version 2 of #elif, #else, and #/if. */
+static void cmpp_dx_f_if_dangler(cmpp_dx *dx){
+  CmppIfState const * const cis = dx->d->impl.state;
+  char const *zDelim = cmpp_dx_delim(dx);
+  cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
+              "%s%s with no matching %s%s",
+              zDelim, dx->d->name.z,
+              zDelim, cis->dIf->name.z);
+}
+
+static void cmpp__dump_sizeofs(cmpp_dx*dx){
+  (void)dx;
+#define SO(X) printf("sizeof(" # X ") = %u\n", (unsigned)sizeof(X))
+  SO(cmpp);
+  SO(cmpp_api_thunk);
+  SO(cmpp_arg);
+  SO(cmpp_args);
+  SO(cmpp_args_pimpl);
+  SO(cmpp_b);
+  SO(cmpp_d);
+  SO(cmpp_d_reg);
+  SO(cmpp__delim);
+  SO(cmpp__delim_list);
+  SO(cmpp_dx);
+  SO(cmpp_dx_pimpl);
+  SO(cmpp_outputer);
+  SO(cmpp_pimpl);
+  SO(((cmpp_pimpl*)0)->stmt);
+  SO(((cmpp_pimpl*)0)->policy);
+  SO(CmppArgList);
+  SO(CmppDLine);
+  SO(CmppDList);
+  SO(CmppDList_entry);
+  SO(CmppLvl);
+  SO(CmppSnippet);
+  SO(PodList__atpol);
+  printf("cmpp_TT__last   = %d\n",
+         cmpp_TT__last);
+#undef SO
+}
+
+
+/* Impl. for #pragma. */
+static void cmpp_dx_f_pragma(cmpp_dx *dx){
+  cmpp_arg const * arg = dx->args.arg0;
+  if(!arg){
+    cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting an argument");
+    return;
+  }else if(arg->next){
+    cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Too many arguments");
+    return;
+  }
+  const char * const zArg = (char const *)arg->z;
+#define M(X) 0==strcmp(zArg,X)
+  if(M("defines")){
+    cmpp__dump_defines(dx->pp, stderr, 1);
+  }else if(M("sizeof")){
+    cmpp__dump_sizeofs(dx);
+  }else if(M("chomp-F")){
+    dx->pp->pimpl->flags.chompF = 1;
+  }else if(M("no-chomp-F")){
+    dx->pp->pimpl->flags.chompF = 0;
+  }else if(M("api-thunk")){
+    /* Generate macros for CMPP_API_THUNK and friends from
+       cmpp_api_thunk_map. */
+    char const * zName = "CMPP_API_THUNK_NAME";
+    char buf[256];
+#define out(FMT,...) snprintf(buf, sizeof(buf), FMT,__VA_ARGS__); \
+    cmpp_dx_out_raw(dx, buf, strlen(buf))
+    if( 0 ){
+      out("/* libcmpp API thunk. */\n"
+          "static cmpp_api_thunk const * %s = 0;\n"
+          "#define cmpp_api_init(PP) %s = (PP)->api\n", zName, zName);
+    }
+#define A(V)                                          \
+    if(V<=cmpp_api_thunk_version) {                   \
+      out("/* Thunk APIs which follow are available as of " \
+        "version %d... */\n",V);                      \
+    }
+#define V(N,T,V)
+#define F(N,T,P) out("#define cmpp_%s %s->%s\n", # N, zName, # N);
+#define O(N,T) out("#define cmpp_%s (*%s->%s)\n", # N, zName, # N);
+cmpp_api_thunk_map(A,V,F,O)
+#undef V
+#undef F
+#undef O
+#undef A
+#undef out
+  }else{
+    cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg);
+  }
+#undef M
+}
+
+/* Impl. for #savepoint. */
+static void cmpp_dx_f_savepoint(cmpp_dx *dx){
+  if(!dx->args.arg0 || dx->args.arg0->next){
+    cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting one argument");
+  }else{
+    const char * const zArg = (const char *)dx->args.arg0->z;
+#define M(X) else if( 0==strcmp(zArg,X) )
+    if( 0 ){}
+    M("begin"){
+      cmpp__dx_sp_begin(dx);
+    }
+    M("rollback"){
+      cmpp__dx_sp_rollback(dx);
+    }M("commit"){
+      cmpp__dx_sp_commit(dx);
+    }else{
+      cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                  "Unknown savepoint option: %s", zArg);
+    }
+  }
+#undef M
+}
+
+/* #stderr impl. */
+static void cmpp_dx_f_stderr(cmpp_dx *dx){
+  if(dx->args.z){
+    g_stderr("%s:%" CMPP_SIZE_T_PFMT ": %.*s\n", dx->sourceName,
+             dx->pimpl->dline.lineNo,
+             (int)dx->args.nz, dx->args.z);
+  }else{
+    cmpp_d const * d = dx->d;
+    g_stderr("%s:%" CMPP_SIZE_T_PFMT ": (no %s%s argument)\n",
+             dx->sourceName, dx->pimpl->dline.lineNo,
+             cmpp_dx_delim(dx), d->name.z);
+  }
+}
+
+/**
+   Manages both the @token@ policy and the delimiters.
+
+   #@ ?push? policy NAME ?<<?
+   #@ ?push? delimiter OPEN CLOSE ?<<?
+   #@ ?push? policy NAME delimiter OPEN CLOSE ?<<?
+   #@ pop policy
+   #@ pop delimiter
+   #@ pop policy delimiter
+   #@ pop both
+
+   Function call forms:
+
+   [@ policy]
+   [@ delimiter]
+*/
+static void cmpp_dx_f_at(cmpp_dx *dx){
+  cmpp_arg const * arg = dx->args.arg0;
+  if( !arg ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Expecting arguments.");
+    return;
+  }
+  enum ops { op_none, op_set, op_push, op_pop, op_heredoc };
+  enum popWhichE { pop_policy = 0x01, pop_delim = 0x02,
+                   pop_both = pop_policy | pop_delim };
+  enum ops op = op_none          /* what to do */;
+  int popWhich = 0               /* what to pop */;
+  bool gotPolicy = false;
+  bool checkedCallForm = !cmpp_dx_is_call(dx);
+  cmpp_arg const * argDelimO = 0 /* @token@ opener */;
+  cmpp_arg const * argDelimC = 0 /* @token@ closer */;
+  cmpp__pi(dx->pp);
+  cmpp_atpol_e polNew = cmpp_atpol_get(dx->pp);
+  for( ; arg; arg = arg ? arg->next : NULL ){
+    //g_warn("arg=%s", arg->z);
+    if( !checkedCallForm ){
+      assert( cmpp_dx_is_call(dx) );
+      checkedCallForm = true;
+      if( cmpp_arg_equals(arg, "policy") ){
+        char const * z =
+          cmpp__atpol_name(dx->pp, cmpp__policy(dx->pp,at));
+        if( z ){
+          cmpp_dx_out_raw(dx, z, strlen(z));
+        }
+      }else if( cmpp_arg_equals(arg, "delimiter") ){
+        char const * zO = 0;
+        char const * zC = 0;
+        cmpp_atdelim_get(dx->pp, &zO, &zC);
+        if( zC ){
+          cmpp_dx_out_raw(dx, zO, strlen(zO));
+          cmpp_dx_out_raw(dx, " ", 1);
+          cmpp_dx_out_raw(dx, zC, strlen(zC));
+        }
+        goto end;
+      }else{
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "In call form, '%s' expects one of "
+                        "'policy' or delimiter'.");
+      }
+      goto end;
+    }/* checkedCallForm */
+    if( !argDelimC && op_none==op ){
+      /* Look for push|pop. */
+      if( cmpp_arg_equals(arg, "pop") ){
+        arg = arg->next;
+        if( !arg ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "'pop' expects arguments of 'policy' "
+                          "and/or 'delimiter' and/or 'both'.");
+          goto end;
+        }
+        for( ; arg; arg = arg->next ){
+          if( 0==(pop_policy & popWhich)
+              && cmpp_arg_equals(arg, "policy") ){
+            popWhich |= pop_policy;
+          }else if( 0==(pop_delim & popWhich)
+                    && cmpp_arg_equals(arg, "delimiter") ){
+            popWhich |= pop_delim;
+          }else if( 0==(pop_both & popWhich)
+                    && cmpp_arg_equals(arg, "both") ){
+            popWhich |= pop_both;
+          }else{
+            cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                            "Invalid argument to 'pop': ", arg->z);
+            goto end;
+          }
+        }
+        assert( !arg );
+        op = op_pop;
+        break;
+      }/* pop */
+      if( cmpp_arg_equals(arg, "push") ){
+        op = op_push;
+        continue;
+      }
+      if( cmpp_arg_equals(arg, "set") ){
+        /* set is implied if neither of push/pop are and we get
+           a policy name. */
+        op = op_set;
+        continue;
+      }
+      /* Fall through */
+    }/* !argDelimC && op_none==op */
+    if( !gotPolicy && cmpp_arg_equals(arg, "policy") ){
+      arg = arg->next;
+      if( !arg ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "'policy' requires a policy name argument.");
+        goto end;
+      }
+      polNew = cmpp_atpol_from_str(NULL, (char const*)arg->z);
+      if( cmpp_atpol_invalid==polNew ){
+        cmpp_atpol_from_str(dx->pp, (char const*)arg->z)
+          /* Will set the error state to something informative. */;
+        goto end;
+      }
+      if( op_none==op ) op = op_set;
+      gotPolicy = true;
+      continue;
+    }
+    if( !argDelimC && cmpp_arg_equals(arg, "delimiter") ){
+      assert( !argDelimO && !argDelimC );
+      argDelimO = arg->next;
+      argDelimC = argDelimO ? argDelimO->next : NULL;
+      if( !argDelimC ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "'delimiter' requires two arguments.");
+        goto end;
+      }
+      arg = argDelimC->next;
+      continue;
+    }
+    if( op_pop!=op ){
+      if( cmpp_arg_equals(arg,"<<") ){
+        if( arg->next ) {
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "'%s' must be the final argument.",
+                          arg->z);
+          goto end;
+        }
+        op = op_heredoc;
+        break;
+      }
+    }
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z);
+    return;
+  }/*arg collection*/
+
+  assert( !dxppCode );
+  assert( cmpp_atpol_invalid!=polNew );
+
+#define popcheck(LIST)     \
+    if(dxppCode) goto end; \
+    if(!LIST.n) goto bad_pop
+
+  if( op_pop==op ){
+    assert( popWhich>0 && popWhich<=3 );
+    if( pop_policy & popWhich ){
+      popcheck(pi->policy.at);
+      cmpp_atpol_pop(dx->pp);
+    }
+    if( pop_delim & popWhich ){
+      popcheck(pi->delim.at);
+      cmpp_atdelim_pop(dx->pp);
+    }
+    goto end;
+  }
+
+  assert( op_set==op || op_push==op || op_heredoc==op );
+  if( argDelimC ){
+    /* Push or set the @token@ delimiters */
+    if( 0 ){
+      g_warn("%s @delims@: %s %s", (op_set==op) ? "set" : "push",
+             argDelimO->z, argDelimC->z);
+    }
+    if( op_push==op || op_heredoc==op ){
+      if( cmpp_atdelim_push(dx->pp, (char const*)argDelimO->z,
+                            (char const*)argDelimC->z) ){
+        goto end;
+      }
+      argDelimO = 0 /* Re-use argDelimC as a flag in case we need to
+                       roll this back on an error below. */;
+    }else{
+      assert( op_set==op );
+      if( cmpp_atdelim_set(dx->pp, (char const*)argDelimO->z,
+                           (char const*)argDelimC->z) ){
+        goto end;
+      }
+      argDelimO = argDelimC = 0;
+    }
+  }
+
+  assert( !dxppCode );
+  assert( !argDelimO );
+  if( op_heredoc==op ){
+    if( cmpp_atpol_push(dx->pp, polNew) ){
+      if( argDelimC ){
+        popcheck(pi->delim.at);
+        cmpp_atdelim_pop(dx->pp);
+      }
+    }else{
+      bool const pushedDelim = NULL!=argDelimC;
+      assert( dx->d->closer );
+      cmpp_dx_consume(dx, NULL, &dx->d->closer, 1,
+                      cmpp_dx_consume_F_PROCESS_OTHER_D)
+        /* !Invalidates argDelimO and argDelimC! */;
+      popcheck(pi->policy.at);
+      cmpp_atpol_pop(dx->pp);
+      if( pushedDelim ) cmpp_atdelim_pop(dx->pp);
+    }
+  }else if( op_push==op ){
+    if( cmpp_atpol_push(dx->pp, polNew) && argDelimC ){
+      /* Roll back delimiter push */
+      cmpp_atdelim_pop(dx->pp);
+    }
+  }else{
+     assert( op_set==op );
+     if( cmpp__policy(dx->pp,at)!=polNew ){
+       cmpp_atpol_set(dx->pp, polNew);
+     }
+  }
+end:
+  return;
+bad_pop:
+  cmpp_dx_err_set(dx, CMPP_RC_RANGE,
+                  "Cannot pop an empty stack.");
+#undef popcheck
+}
+
+
+static void cmpp_dx_f_expr(cmpp_dx *dx){
+  int rv = 0;
+  assert( dx->args.z );
+  if( 0 ){
+    g_stderr("%s() argc=%d arg0 [%.*s]\n", __func__, dx->args.argc,
+             dx->args.arg0->n, dx->args.arg0->z);
+    g_stderr("%s() dx->args.z [%.*s]\n", __func__,
+             (int)dx->args.nz, dx->args.z);
+  }
+  if( !dx->args.argc ){
+    dxserr("An empty expression is not permitted.");
+    return;
+  }
+#if 0
+  for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){
+    g_stderr("got type=%s n=%u z=%.*s\n",
+             cmpp__tt_cstr(a->ttype, true),
+             (unsigned)a->n, (int)a->n, a->z);
+  }
+#endif
+  if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){
+    if( 'a'==dx->d->name.z[0] ){
+      if( !rv ){
+        cmpp_dx_err_set(dx, CMPP_RC_ASSERT, "Assertion failed: %s",
+                   dx->pimpl->buf.argsRaw.z);
+      }
+    }else{
+      char buf[60];
+      snprintf(buf, sizeof(buf), "%d\n", rv);
+      cmpp_dx_out_raw(dx, buf, strlen(buf));
+    }
+  }
+}
+
+static void cmpp_dx_f_undef_policy(cmpp_dx *dx){
+  cmpp_unpol_e up = cmpp_unpol_invalid;
+  int nSeen = 0;
+  cmpp_arg const * arg = dx->args.arg0;
+  enum ops { op_set, op_push, op_pop };
+  enum ops op = op_set;
+  if( !dx->args.argc ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Expecting one of: error, null");
+    return;
+  }
+again:
+  ++nSeen;
+  if( cmpp_arg_equals(arg,"error") ) up = cmpp_unpol_ERROR;
+  else if( cmpp_arg_equals(arg,"null") ) up = cmpp_unpol_NULL;
+  else if( 1==nSeen ){
+    if( cmpp_arg_equals(arg, "push") ){
+      if( !arg->next ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Expecting argument to 'push'.");
+        return;
+      }
+      op = op_push;
+      arg = arg->next;
+      goto again;
+    }else if( cmpp_arg_equals(arg, "pop") ){
+      if( arg->next ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Extra argument after 'pop': %s",
+                        arg->next->z);
+        return;
+      }
+      op = op_pop;
+    }
+  }
+  if( op_pop!=op && cmpp_unpol_invalid==up ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Unhandled undefined-policy '%s'."
+                    " Try one of: error, null",
+                    arg->z);
+  }else if( op_set==op ){
+    cmpp_unpol_set(dx->pp, up);
+  }else if( op_push==op ){
+    cmpp_unpol_push(dx->pp, up);
+  }else{
+    assert( op_pop==op );
+    if( !cmpp__epol(dx->pp,un).n ){
+      cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                      "No %s%s push is active.",
+                      cmpp_dx_delim(dx), dx->d->name.z);
+    }else{
+      cmpp_unpol_pop(dx->pp);
+    }
+  }
+}
+
+#ifndef CMPP_OMIT_D_DB
+/* Impl. for #attach. */
+static void cmpp_dx_f_attach(cmpp_dx *dx){
+  if( 3!=dx->args.argc ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                "%s expects: STRING as NAME", dx->d->name.z);
+    return;
+  }
+  cmpp_arg const * pNext = 0;
+  cmpp_b osDbFile = cmpp_b_empty;
+  cmpp_b osSchema = cmpp_b_empty;
+  for( cmpp_arg const * arg = dx->args.arg0;
+       0==dxppCode && arg;
+       arg = pNext ){
+    pNext = arg->next;
+    if( !osDbFile.n ){
+      if( 0==cmpp_arg_to_b(dx, arg, &osDbFile,
+                           cmpp_arg_to_b_F_BRACE_CALL)
+          && !osDbFile.n ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Empty db file name is not permitted. "
+                        "If '%s' is intended as a value, "
+                        "it should be quoted.", arg->z);
+        break;
+      }
+      assert( pNext );
+      if( !pNext || !cmpp_arg_equals(pNext, "as") ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Expecting 'as' after db file name.");
+        break;
+      }
+      pNext = pNext->next;
+    }else if( !osSchema.n ){
+      if( 0==cmpp_arg_to_b(dx, arg, &osSchema,
+                           cmpp_arg_to_b_F_BRACE_CALL)
+          && !osSchema.n ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Empty db schema name is not permitted."
+                        "If '%s' is intended as a value, "
+                        "it should be quoted.",
+                        arg->z);
+        break;
+      }
+    }
+  }
+  if( dxppCode ) goto end;
+  if( !osSchema.n ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Missing schema name.");
+    goto end;
+  }
+  sqlite3_stmt * const q =
+    cmpp__stmt(dx->pp, CmppStmt_dbAttach, false);
+  if( q ){
+    cmpp__bind_textn(dx->pp, q, 1, osDbFile.z, osDbFile.n);
+    cmpp__bind_textn(dx->pp, q, 2, osSchema.z, osSchema.n);
+    cmpp__step(dx->pp, q, true);
+  }
+end:
+  cmpp_b_clear(&osDbFile);
+  cmpp_b_clear(&osSchema);
+}
+
+/* Impl. for #detach. */
+static void cmpp_dx_f_detach(cmpp_dx *dx){
+  cmpp_d const * d = dx->d;
+  if( 1!=dx->args.argc ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "%s expects: NAME", d->name.z);
+    return;
+  }
+  cmpp_arg const * const arg = dx->args.arg0;
+  cmpp_b os = cmpp_b_empty;
+  if( cmpp_arg_to_b(dx, arg, &os, cmpp_arg_to_b_F_BRACE_CALL) ){
+    goto end;
+  }
+  if( !os.n ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Empty db schema name is not permitted.");
+    goto end;
+  }
+  sqlite3_stmt * const q =
+    cmpp__stmt(dx->pp, CmppStmt_dbDetach, false);
+  if( q ){
+    cmpp__bind_textn(dx->pp, q, 1, os.z, os.n);
+    cmpp__step(dx->pp, q, true);
+  }
+end:
+  cmpp_b_clear(&os);
+}
+#endif /* #ifndef CMPP_OMIT_D_DB */
+
+static void cmpp_dx_f_delimiter(cmpp_dx *dx){
+  cmpp_arg const * arg = dx->args.arg0;
+  enum ops { op_none, op_set, op_push, op_pop };
+  enum ops op = op_none;
+  cmpp_arg const * argD = 0;
+  bool doHeredoc = false;
+  bool const isCall = cmpp_dx_is_call(dx);
+  for( ; arg; arg = arg->next ){
+    if( op_none==op ){
+      /* Look for push|pop. */
+      if( cmpp_arg_equals(arg, "push") ){
+        op = op_push;
+        continue;
+      }else if( cmpp_arg_equals(arg, "pop") ){
+        if( arg->next ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "'pop' expects no arguments.");
+          return;
+        }
+        op = op_pop;
+        break;
+      }
+      /* Fall through */
+    }
+    if( !argD ){
+      if( op_none==op ) op = op_set;
+      argD = arg;
+      continue;
+    }else if( !doHeredoc && cmpp_arg_equals(arg,"<<") ){
+      if( isCall ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "'%s' is not legal in [call] form.", arg->z);
+        return;
+      }else if( arg->next ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "'%s' must be the final argument.", arg->z);
+          return;
+      }
+      op = op_push;
+      doHeredoc = true;
+      continue;
+    }
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
+    return;
+  }
+  if( op_pop==op ){
+    cmpp_delimiter_pop(dx->pp);
+  }else if( !argD ){
+    if( isCall ){
+      cmpp__delim const * const del = cmpp__dx_delim(dx);
+      if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n);
+    }else{
+      cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "No delimiter specified.");
+    }
+    return;
+  }else{
+    char const * const z =
+      (0==strcmp("default",(char*)argD->z))
+      ? NULL
+      : (char const*)argD->z;
+    if( op_push==op ){
+      cmpp_delimiter_push(dx->pp, (char const*)argD->z);
+    }else{
+      assert( op_set==op );
+      if( doHeredoc ) cmpp_delimiter_push(dx->pp, z);
+      else cmpp_delimiter_set(dx->pp, z);
+    }
+  }
+  if( !cmpp_dx_err_check(dx) ){
+    if( isCall ){
+      cmpp__delim const * const del = cmpp__dx_delim(dx);
+      if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n);
+    }else if( doHeredoc ){
+      assert( op_push==op );
+      cmpp_dx_consume(dx, NULL, &dx->d->closer, 1,
+                      cmpp_dx_consume_F_PROCESS_OTHER_D);
+      cmpp_delimiter_pop(dx->pp);
+    }
+  }
+}
+
+#ifndef NDEBUG
+/* Experimenting grounds. */
+static void cmpp_dx_f_experiment(cmpp_dx *dx){
+  void * st = dx->d->impl.state;
+  (void)st;
+  g_warn("raw args: %s", dx->pimpl->buf.argsRaw.z);
+  g_warn("argc=%u", dx->args.argc);
+  g_warn("isCall=%d\n", cmpp_dx_is_call(dx));
+  if( 1 ){
+    for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){
+      g_stderr("got type=%s n=%u z=%.*s\n",
+               cmpp__tt_cstr(a->ttype, true),
+               (unsigned)a->n, (int)a->n, a->z);
+    }
+  }
+  if( 0 ){
+    int rv = 0;
+    if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){
+      g_stderr("expr result: %d\n", rv);
+    }
+  }
+
+  if( 0 ){
+    char const * zIn = "a strspn test @# and @";
+    g_stderr("strlen  : %u\n", (unsigned)strlen(zIn));
+    g_stderr("strspn 1:  %u, %u\n",
+             (unsigned)strspn(zIn, "#@"),
+             (unsigned)strspn(zIn, "@#"));
+    g_stderr("strcspn 2: %u, %u\n",
+             (unsigned)strcspn(zIn, "#@"),
+             (unsigned)strcspn(zIn, "@#"));
+    g_stderr("strcspn 3: %u, %u\n",
+             (unsigned)strcspn(zIn, "a strspn"),
+             (unsigned)strcspn(zIn, "nope"));
+  }
+
+  if( 1 ){
+    cmpp__dump_sizeofs(dx);
+  }
+}
+#endif /* #ifndef NDEBUG */
+
+#ifndef CMPP_OMIT_D_DB
+
+/**
+   Helper for #query and friends. Expects arg to be an SQL value. If
+   arg->next is "bind" then this consumes the following two arguments(
+   "bind" BIND_ARG), where BIND_ARG must be one of either
+   cmpp_TT_GroupSquiggly or cmpp_TT_GroupBrace.
+
+   If it returns 0 then:
+
+   - If "bind" was found then *pBind is set to the BIND_ARG argument
+     and *pNext is set to the one after that.
+
+   - Else *pBind is set to NULL and and *pNext is set to
+     arg->next.
+
+   In either case, *pNext may be set to NULL.
+*/
+static
+int cmpp__consume_sql_args(cmpp *pp, cmpp_arg const *arg,
+                           cmpp_arg const **pBind,
+                           cmpp_arg const **pNext){
+  if( 0==ppCode ){
+    *pBind = 0;
+    cmpp_arg const *pN = arg->next;
+    if( pN && cmpp_arg_equals(pN, "bind") ){
+      pN = pN->next;
+      if( !pN || (
+            cmpp_TT_GroupSquiggly!=pN->ttype
+            && cmpp_TT_GroupBrace!=pN->ttype
+          ) ){
+        return serr("Expecting {...} or [...] after 'bind'.");
+      }
+      *pBind = pN;
+      *pNext = pN->next;
+    } else {
+      *pBind = 0;
+      *pNext = pN;
+    }
+  }
+  return ppCode;
+}
+
+/**
+   cmpp_kav_each_f() impl for used by #query's `bind {...}` argument.
+*/
+static int cmpp_kav_each_f_query__bind(
+  cmpp_dx * const dx,
+  unsigned char const * const zKey, cmpp_size_t nKey,
+  unsigned char const * const zVal, cmpp_size_t nVal,
+  void * const callbackState
+){
+  /* Expecting: :bindName -> bindValue */
+  if( ':'!=zKey[0] && '$'!=zKey[0] /*&& '@'!=zKey[0]*/ ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Bind keys must start with ':' or '$'.");
+  }else{
+    sqlite3_stmt * const q = callbackState;
+    assert( q );
+    int const bindNdx =
+      sqlite3_bind_parameter_index(q, (char const*)zKey);
+    if( bindNdx ){
+      cmpp__bind_textn(dx->pp, q, bindNdx, zVal, nVal);
+    }else{
+      cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Invalid bind name: %.*s",
+                   (int)nKey, zKey);
+    }
+  }
+  return dxppCode;
+}
+
+int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q,
+                     cmpp_arg const * const aGroup){
+  if( dxppCode ) return dxppCode;
+  if( cmpp_TT_GroupSquiggly==aGroup->ttype ){
+    return cmpp_kav_each(
+      dx, aGroup->z, aGroup->n,
+      cmpp_kav_each_f_query__bind, q,
+      cmpp_kav_each_F_NOT_EMPTY
+      | cmpp_kav_each_F_CALL_VAL
+      | cmpp_kav_each_F_PARENS_EXPR
+    );
+  }
+  if( cmpp_TT_GroupBrace!=aGroup->ttype ){
+    return cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                           "Expecting {...} or [...] "
+                           "for SQL binding list.");
+  }
+  int bindNdx = 0;
+  cmpp_args args = cmpp_args_empty;
+  cmpp_args_parse(dx, &args, aGroup->z, aGroup->n, 0);
+  if( !args.argc && !dxppCode ){
+    cmpp_err_set(dx->pp, CMPP_RC_RANGE,
+                 "Empty SQL bind list is not permitted.");
+    /* Keep going so we can clean up a partially-parsed args. */
+  }
+  for( cmpp_arg const * aVal = args.arg0;
+       !dxppCode && aVal;
+       aVal = aVal->next ){
+    ++bindNdx;
+    if( 0 ){
+      g_warn("bind #%d %s <<%s>>", bindNdx,
+             cmpp__tt_cstr(aVal->ttype, true), aVal->z);
+    }
+    cmpp__bind_arg(dx, q, bindNdx, aVal);
+  }
+  cmpp_args_cleanup(&args);
+  return dxppCode;
+}
+
+/** #query impl */
+static void cmpp_dx_f_query(cmpp_dx *dx){
+  //cmpp_d const * d = cmpp_dx_d(dx);
+  if( !dx->args.arg0 ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                "Expecting one or more arguments");
+    return;
+  }
+  cmpp * const pp = dx->pp;
+  sqlite3_stmt * q = 0;
+  cmpp_b * const obBody = cmpp_b_borrow(dx->pp);
+  cmpp_b * const sql = cmpp_b_borrow(dx->pp);
+  cmpp_outputer obNull = cmpp_outputer_empty;
+  //cmpp_b obBindArgs = cmpp_b_empty;
+  cmpp_args args = cmpp_args_empty
+    /* We need to copy the args or do some arg-type-specific work to
+       copy the memory for specific cases. */;
+  int nChomp = 0;
+  bool spStarted = false;
+  bool seenDefine = false;
+  bool batchMode = false;
+  cmpp_arg const * pNext = 0;
+  cmpp_arg const * aBind = 0;
+  cmpp_d const * const dNoRows = dx->d->impl.state;
+  cmpp_d const * const dClosers[2] = {dx->d->closer, dNoRows};
+
+  if( !obBody || !sql ) goto cleanup;
+
+  assert( dNoRows );
+  if( cmpp_dx_args_clone(dx, &args) ){
+    goto cleanup;
+  }
+  //g_warn("args.argc=%d", args.argc);
+  for( cmpp_arg const * arg = args.arg0;
+       0==dxppCode && arg;
+       arg = pNext ){
+    //g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z);
+    pNext = arg->next;
+    if( cmpp_arg_equals(arg, "define") ){
+      if( seenDefine ){
+        cmpp__dx_err_just_once(dx, arg);
+        goto cleanup;
+      }
+      seenDefine = true;
+      continue;
+    }
+    if( cmpp_arg_equals(arg, "-chomp") ){
+      ++nChomp;
+      continue;
+    }
+    if( cmpp_arg_equals(arg, "-batch") ){
+      if( batchMode ){
+        cmpp__dx_err_just_once(dx, arg);
+        goto cleanup;
+      }
+      batchMode = true;
+      continue;
+    }
+    if( !sql->n ){
+      if( cmpp__consume_sql_args(pp, arg, &aBind, &pNext) ){
+        goto cleanup;
+      }
+      if( cmpp_arg_to_b(dx, arg, sql, cmpp_arg_to_b_F_BRACE_CALL) ){
+        goto cleanup;
+      }
+      //g_warn("SQL: <<%s>>", sql->z);
+      continue;
+    }
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
+    goto cleanup;
+  }
+  if( ppCode ) goto cleanup;
+  if( seenDefine ){
+    if( nChomp ){
+      serr("-chomp and define may not be used together.");
+      goto cleanup;
+    }else if( batchMode ){
+      serr("-batch and define may not be used together.");
+      goto cleanup;
+    }
+  }
+  if( !sql->n ){
+    serr("Expecting an SQL-string argument.");
+    goto cleanup;
+  }
+
+  if( batchMode ){
+    if( aBind ){
+      serr("Bindable values may not be used with -batch.");
+      goto cleanup;
+    }
+    char *zErr = 0;
+    cmpp__pi(dx->pp);
+    int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr);
+    rc = cmpp__db_rc(dx->pp, rc, zErr);
+    sqlite3_free(zErr);
+    goto cleanup;
+  }
+
+  if( cmpp__db_rc(pp, sqlite3_prepare_v2(
+                    pp->pimpl->db.dbh, (char const *)sql->z,
+                    (int)sql->n, &q, 0), 0) ){
+    goto cleanup;
+  }else if( !q ){
+    cmpp_err_set(pp, CMPP_RC_RANGE,
+              "Empty SQL is not permitted.");
+    goto cleanup;
+  }
+  //g_warn("SQL via stmt: <<%s>>", sqlite3_sql(q));
+  int const nCol = sqlite3_column_count(q);
+  if( !nCol ){
+    cmpp_err_set(pp, CMPP_RC_RANGE,
+              "SQL does not have any result columns.");
+    goto cleanup;
+  }
+  if( !seenDefine ){
+    if( cmpp_sp_begin(pp) ) goto cleanup;
+    spStarted = true;
+  }
+
+  if( aBind && cmpp__bind_group(dx, q, aBind) ){
+    goto cleanup;
+  }
+
+  bool gotARow = false;
+  cmpp_dx_pos dxPosStart;
+  cmpp_flag32_t const consumeFlags = cmpp_dx_consume_F_PROCESS_OTHER_D;
+  cmpp_dx_pos_save(dx, &dxPosStart);
+  int const nChompOrig = nChomp;
+  while( 0==ppCode ){
+    int const dbrc = cmpp__step(pp, q, false);
+    if( SQLITE_ROW==dbrc ){
+      nChomp = nChompOrig;
+      gotARow = true;
+      if( cmpp__define_from_row(pp, q, false) ) break;
+      if( seenDefine ) break;
+      cmpp_dx_pos_restore(dx, &dxPosStart);
+      cmpp_b_reuse(obBody);
+      /* If it weren't for -chomp, we wouldn't need to
+         buffer this. */
+      if( cmpp_dx_consume_b(dx, obBody, dClosers,
+                            sizeof(dClosers)/sizeof(dClosers[0]),
+                            consumeFlags) ){
+        goto cleanup;
+      }
+      assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
+      while( nChomp-- && cmpp_b_chomp(obBody) ){}
+      if( obBody->n && cmpp_dx_out_raw(dx, obBody->z, obBody->n) ) break;
+      if( dx->d == dNoRows ){
+        if( cmpp_dx_consume(dx, &obNull, dClosers, 1/*one!*/,
+                            consumeFlags) ){
+          goto cleanup;
+        }
+        assert( dx->d == dClosers[0] );
+        /* TODO? chomp? */
+      }
+      continue;
+    }
+    if( 0==ppCode && seenDefine ){
+      /* If we got here, there was no result row. */
+      cmpp__define_from_row(pp, q, true);
+    }
+    break;
+  }/*result row loop*/
+  cmpp__stmt_reset(q);
+  if( ppCode ) goto cleanup;
+
+  while( !seenDefine && !gotARow ){
+    /* No result rows. Skip past the body, emitting the #query:no-rows
+       content, if any. We disable @token processing for that first
+       step because (A) the output is not going anywhere, so no need
+       to expand it (noting that expanding may have side effects via
+       @[call...]@) and (B) the @tokens@ referring to this query's
+       results will not have been set because there was no row to set
+       them from, so @expanding@ them would fail. */
+    cmpp_atpol_e const atpol = cmpp_atpol_get(dx->pp);
+    if( cmpp_atpol_set(dx->pp, cmpp_atpol_OFF) ) break;
+    cmpp_dx_consume(dx, &obNull, dClosers,
+                    sizeof(dClosers)/sizeof(dClosers[0]),
+                    consumeFlags);
+    cmpp_atpol_set(dx->pp, atpol);
+    if( dxppCode ) break;
+    assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
+    if( dx->d == dNoRows ){
+      if( cmpp_dx_consume(dx, 0, dClosers, 1/*one!*/,
+                          consumeFlags) ){
+        break;
+      }
+      assert( dx->d == dClosers[0] );
+      /* TODO? chomp? */
+    }
+    break;
+  }
+
+cleanup:
+  cmpp_args_cleanup(&args);
+  cmpp_b_return(dx->pp, obBody);
+  cmpp_b_return(dx->pp, sql);
+  sqlite3_finalize(q);
+  if( spStarted ) cmpp_sp_rollback(pp);
+}
+#endif /* #ifndef CMPP_OMIT_D_DB */
+
+#ifndef CMPP_OMIT_D_PIPE
+/** #pipe impl. */
+static void cmpp_dx_f_pipe(cmpp_dx *dx){
+  //cmpp_d const * d = cmpp_dx_d(dx);
+  unsigned char const * zArgs = dx->args.z;
+  assert( dx->args.arg0->n == dx->args.nz );
+  unsigned char const * const zArgsEnd = zArgs + dx->args.nz;
+  if( zArgs==zArgsEnd ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+               "Expecting a command and arguments to pipe.");
+    return;
+  }
+  cmpp_FILE * fpToChild = 0;
+  int nChompIn = 0, nChompOut = 0;
+  cmpp_b * const chout = cmpp_b_borrow(dx->pp);
+  cmpp_b * const cmd = cmpp_b_borrow(dx->pp);
+  cmpp_b * const body = cmpp_b_borrow(dx->pp);
+  cmpp_b * const bArg = cmpp_b_borrow(dx->pp)
+    /* arg parsing and the initial command name part of the
+       external command. */;
+  cmpp_args cmdArgs = cmpp_args_empty;
+  /* TODOs and FIXMEs:
+
+     We need flags to optionally @token@-parse before and/or after
+     filtering.
+  */
+  bool seenDD = false /* true if seen "--" or [...] */;
+  bool doCapture = true /* true if we need a closing /pipe */;
+  bool argsAsGroup = false /* true if args is [...] */;
+  bool dumpDebug = false;
+  cmpp_flag32_t popenFlags = 0;
+  cmpp_popen_t po = cmpp_popen_t_empty;
+  if( cmpp_b_reserve3(dx->pp, cmd, zArgsEnd-zArgs + 1)
+      || cmpp_b_reserve3(dx->pp, bArg, cmd->nAlloc) ){
+    goto cleanup;
+  }
+
+  unsigned char * zOut = bArg->z;
+  unsigned char const * const zOutEnd = bArg->z + bArg->nAlloc - 1;
+  while( 0==dxppCode ){
+    cmpp_arg arg = cmpp_arg_empty;
+    zOut = bArg->z;
+    if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd,
+                       &zOut, zOutEnd) ){
+      goto cleanup;
+    }
+    if( cmpp_arg_equals(&arg, "--") ){
+      zOut = bArg->z;
+      if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd,
+                         &zOut, zOutEnd) ){
+        goto cleanup;
+      }
+      if( !arg.n ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Expecting external command name "
+                        "or [...] after --.");
+        goto cleanup;
+      }
+    do_arg_list:
+      seenDD = true;
+      cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
+      if( cmpp_TT_GroupBrace==arg.ttype ){
+        argsAsGroup = true;
+        a2bFlags |= cmpp_arg_to_b_F_NO_BRACE_CALL;
+      }else if( cmpp__arg_wordIsPathOrFlag(&arg) ){
+        /* If it looks like it is a path, do not
+           expand it as a word. */
+        arg.ttype = cmpp_TT_String;
+      }
+      if( cmpp_arg_to_b(dx, &arg, cmd, a2bFlags)
+          || (!argsAsGroup && cmpp_b_append_ch(cmd, ' ')) ){
+        goto cleanup;
+      }
+      //g_warn("command: [%s]=>%s", arg.z, cmd->z);
+      if( cmd->n<2 ){
+        cmpp_dx_err_set(dx, CMPP_RC_RANGE,
+                        "Command name '%s' resolves to empty. "
+                        "This is most commonly caused by not "
+                        "quoting it but it can also mean that it "
+                        "is an unknown define key.", arg.z);
+        goto cleanup;
+      }
+      //g_warn("arg=%s", arg.z);
+      //g_warn("cmd=%s", cmd->z);
+      break;
+    }
+    if( cmpp_TT_GroupBrace==arg.ttype ){
+      goto do_arg_list;
+    }
+#define FLAG(X)if( cmpp_arg_isflag(&arg, X) )
+    FLAG("-no-input"){
+      doCapture = false;
+      continue;
+    }
+    FLAG("-chomp-output"){
+      ++nChompOut;
+      continue;
+    }
+    FLAG("-chomp"){
+      ++nChompIn;
+      continue;
+    }
+    FLAG("-exec-direct"){
+      popenFlags |= cmpp_popen_F_DIRECT;
+      continue;
+    }
+    FLAG("-path"){
+      popenFlags |= cmpp_popen_F_PATH;
+      continue;
+    }
+    FLAG("-debug"){
+      dumpDebug = true;
+      continue;
+    }
+#undef FLAG
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Unhandled argument: %s. %s%s requires -- "
+                    "before its external command name.",
+                    arg.z, cmpp_dx_delim(dx),
+                    dx->d->name.z);
+    goto cleanup;
+  }
+
+  if( !seenDD ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "%s%s requires a -- before the name of "
+                    "its external app.",
+                    cmpp_dx_delim(dx), dx->d->name.z);
+    goto cleanup;
+  }
+
+  //g_warn("zArgs n=%u zArgs=%s", (unsigned)(zArgsEnd-zArgs), zArgs);
+  /* dx->pimpl->args gets overwritten by cmpp_dx_consume(), so we have to copy
+     the args. */
+  if( argsAsGroup ){
+    assert( cmd->z );
+    if( cmpp_args_parse(dx, &cmdArgs, cmd->z, cmd->n, 0) ){
+      goto cleanup;
+    }
+  }else{
+    /* zArgs can have newlines in it. We need to strip those out
+       before passing it on. We elide them entirely, as opposed to
+       replacing them with a space. */
+    cmpp_skip_snl(&zArgs, zArgsEnd);
+    if( cmpp_b_reserve3(dx->pp, cmd, cmd->n + (zArgsEnd-zArgs) + 1) ){
+      goto cleanup;
+    }
+    unsigned char * zo = cmd->z + cmd->n;
+    unsigned char const *zi = zArgs;
+#if !defined(NDEBUG)
+    unsigned char const * zoEnd = cmd->z + cmd->nAlloc;
+#endif
+    for( ; zi<zArgsEnd; ++zi){
+      if( '\n'!=*zi && '\r'!=*zi ) *zo++ = *zi;
+    }
+    assert( zoEnd > zo );
+    *zo = 0;
+    cmd->n = zo - cmd->z;
+  }
+  assert( !dxppCode );
+
+  if( doCapture ){
+    assert( dx->d->closer );
+    if( cmpp_dx_consume_b(dx, body, &dx->d->closer, 1,
+                          cmpp_dx_consume_F_PROCESS_OTHER_D) ){
+      goto cleanup;
+    }
+    while( nChompIn-- && cmpp_b_chomp(body) ){}
+    po.fpToChild = &fpToChild;
+  }
+
+  if( dumpDebug ){
+    g_warn("%s%s -debug: cmd argsAsGroup=%d n=%u z=%s",
+           cmpp_dx_delim(dx), dx->d->name.z,
+           (int)argsAsGroup,
+           (unsigned)cmd->n, cmd->z);
+  }
+  if( argsAsGroup ){
+    cmpp_popen_args(dx, &cmdArgs, &po);
+  }else{
+    unsigned char const * z = cmd->z;
+    //cmpp_skip_snl(&z, cmd->z + cmd->n);
+    cmpp_popen(dx->pp, z, popenFlags, &po);
+  }
+  if( dxppCode ) goto cleanup;
+  int rc = 0;
+  if( doCapture ){
+    /* Bug: if body is too bug (no idea how much that is), this will
+       block while waiting on input from the child. This can easily
+       happen with #include -raw. */
+#if 0
+    /* Failed attempt to work around it. */
+    assert( fpToChild );
+    enum { BufSize = 128 };
+    unsigned char buf[BufSize];
+    cmpp_size_t nLeft = body->n;
+    unsigned char const * z = body->z;
+    while( nLeft>0 && !dxppCode ){
+      cmpp_size_t nWrite = nLeft < BufSize ? nLeft : BufSize;
+      g_warn("writing %u to child...", (unsigned)nWrite);
+      rc = cmpp_output_f_FILE(fpToChild, z, nWrite);
+      if( rc ){
+        cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process.");
+        break;
+      }
+      z += nWrite;
+      nLeft -= nWrite;
+      fflush(fpToChild);
+      cmpp_size_t nRead = BufSize;
+      rc = cmpp_input_f_fd(&po.fdFromChild, &buf[0], &nRead);
+      if( rc ) goto err_reading;
+      cmpp_b_append4(dx->pp, &chout, buf, nRead);\
+    }
+    if( !dxppCode ){
+      g_warn0("reading from child...");
+      rc = cmpp_stream( cmpp_input_f_fd, &po.fdFromChild,
+                        cmpp_output_f_b, chout );
+      if( rc ) goto err_reading;
+    }
+    g_warn0("I/O done");
+#else
+    //g_warn("writing %u bytes to child...", (unsigned)body->n);
+    rc = cmpp_output_f_FILE(fpToChild, body->z, body->n);
+    if( rc ){
+      cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process.");
+      goto cleanup;
+    }
+    //g_warn("wrote %u bytes to child.", (unsigned)body->n);
+    fclose(fpToChild);
+    fpToChild = 0;
+    if( dxppCode ) goto cleanup;
+    goto stream_chout;
+#endif
+  }else{
+  stream_chout:
+    //g_warn0("waiting on child...");
+    rc = cmpp_stream(cmpp_input_f_fd, &po.fdFromChild,
+                     cmpp_output_f_b, chout);
+    //g_warn0("I/O done");
+    if( rc ){
+      //err_reading:
+      cmpp_dx_err_set(dx, rc, "Error reading stdout from piped process.");
+      goto cleanup;
+    }
+  }
+  while( nChompOut-- && cmpp_b_chomp(chout) ){}
+  //g_warn("Read in:\n%.*s", (int)chout->n, chout->z);
+  cmpp_dx_out_raw(dx, chout->z, chout->n);
+
+cleanup:
+  cmpp_args_cleanup(&cmdArgs);
+  cmpp_b_return(dx->pp, chout);
+  cmpp_b_return(dx->pp, cmd);
+  cmpp_b_return(dx->pp, body);
+  cmpp_b_return(dx->pp, bArg);
+  cmpp_pclose(&po);
+}
+#endif /* #ifndef CMPP_OMIT_D_PIPE */
+
+/**
+   #sum ...args
+
+   Emits the sum of its arguments, treating each as an
+   integer. Non-integer arguments are silently skipped.
+*/
+static void cmpp_dx_f_sum(cmpp_dx *dx){
+  int64_t n = 0, i = 0;
+  cmpp_b b = cmpp_b_empty;
+  for( cmpp_arg const * arg = dx->args.arg0;
+       arg && !cmpp_dx_err_check(dx); arg = arg->next ){
+    if( 0==cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&b),
+                         cmpp_arg_to_b_F_BRACE_CALL)
+        && cmpp__is_int64(b.z, b.n, &i) ){
+      n += i;
+    }
+  }
+  cmpp_b_append_i64(cmpp_b_reuse(&b), n);
+  cmpp_dx_out_raw(dx, b.z, b.n);
+  cmpp_b_clear(&b);
+}
+
+/**
+   #arg ?flags? the-arg
+
+   -trim-left
+   -trim-right
+   -trim: trim both sides
+
+   It sends its arg to cmpp_arg_to_b() to expand it, optionally
+   trims the result, and emits that value.
+
+   This directive is not expected to be useful except, perhaps in
+   testing cmpp itself. Its trim flags, in particular, aren't commonly
+   useful because #arg is only useful in a function call context and
+   those unconditionally trim their output.
+*/
+static void cmpp_dx_f_arg(cmpp_dx *dx){
+  cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
+  bool trimL = false, trimR = false;
+  cmpp_arg const * arg = dx->args.arg0;
+  for( ; arg && !cmpp_dx_err_check(dx); arg = arg->next ){
+#define FLAG(X)if( cmpp_arg_isflag(arg, X) )
+    FLAG("-raw") {
+      a2bFlags = cmpp_arg_to_b_F_FORCE_STRING;
+      continue;
+    }
+    FLAG("-trim-left")  { trimL=true; continue; }
+    FLAG("-trim-right") { trimR=true; continue; }
+    FLAG("-trim")       { trimL=trimR=true; continue; }
+#undef FLAG
+    break;
+  }
+  if( arg ){
+    cmpp_b * const b = cmpp_b_borrow(dx->pp);
+    if( b && 0==cmpp_arg_to_b(dx, arg, b, a2bFlags) ){
+      unsigned char const * zz = b->z;
+      unsigned char const * zzEnd = b->z + b->n;
+      if( trimL ) cmpp_skip_snl(&zz, zzEnd);
+      if( trimR ) cmpp_skip_snl_trailing(zz, &zzEnd);
+      if( zzEnd-zz ){
+        cmpp_dx_out_raw(dx, zz, zzEnd-zz);
+      }
+    }
+    cmpp_b_return(dx->pp, b);
+  }else if( !cmpp_dx_err_check(dx) ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument.");
+  }
+}
+
+/**
+   #join ?flags? ...args
+
+   -s SEPARATOR: sets the separator for its RHS arguments. Default=space.
+
+   -nl: append a newline (will be stripped by [call]s!). This is the default
+   when !cmpp_dx_is_call(dx).
+
+   -nonl: do not append a newline. Default when dx->isCall.
+*/
+static void cmpp_dx_f_join(cmpp_dx *dx){
+  cmpp_b * const b = cmpp_b_borrow(dx->pp);
+  cmpp_b * const bSep = cmpp_b_borrow(dx->pp);
+  cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
+  bool addNl = !cmpp_dx_is_call(dx);
+  int n = 0;
+  if( !b || !bSep ) goto end;
+  if( !dx->args.argc ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "%s%s expects ?flags? ...args",
+                    cmpp_dx_delim(dx), dx->d->name.z);
+    goto end;
+  }
+  cmpp_b_append_ch(bSep, ' ');
+  cmpp_check_oom(dx->pp, bSep->z);
+  for( cmpp_arg const * arg = dx->args.arg0; arg
+         && !b->errCode
+         && !bSep->errCode
+         && !cmpp_dx_err_check(dx);
+       arg = arg->next ){
+#define FLAG(X)if( cmpp_arg_isflag(arg, X) )
+    FLAG("-s"){
+      if( !arg->next ){
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Missing SEPARATOR argument to -s.");
+        break;
+      }
+      cmpp_arg_to_b(dx, arg->next,
+                    cmpp_b_reuse(bSep),
+                    cmpp_arg_to_b_F_BRACE_CALL);
+      arg = arg->next;
+      continue;
+    }
+    //FLAG("-nl"){ addNl=true; continue; }
+    FLAG("-nonl"){ addNl=false; continue; }
+#undef FLAG
+    if( n++ && cmpp_dx_out_raw(dx, bSep->z, bSep->n) ){
+      break;
+    }
+    if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b), a2bFlags) ){
+      break;
+    }
+    cmpp_dx_out_raw(dx, b->z, b->n);
+  }
+  if( !cmpp_dx_err_check(dx) ){
+    if( !n ){
+      cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                      "Expecting at least one argument.");
+    }else if( addNl ){
+      cmpp_dx_out_raw(dx, "\n", 1);
+    }
+  }
+end:
+  cmpp_b_return(dx->pp, b);
+  cmpp_b_return(dx->pp, bSep);
+}
+
+
+/* Impl. for #file */
+static void cmpp_dx_f_file(cmpp_dx *dx){
+  if( !dx->args.arg0 ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Expecting one or more arguments");
+    return;
+  }
+  cmpp_d const * const d = dx->d;
+  enum e_op {
+    op_none, op_exists, op_join
+  };
+  cmpp_b * const b0 = cmpp_b_borrow(dx->pp);
+  if( !b0 ) goto end;
+  enum e_op op = op_none;
+  cmpp_arg const * opArg = 0;
+  cmpp_arg const * arg = 0;
+  for( arg = dx->args.arg0;
+       0==dxppCode && arg;
+       arg = arg->next ){
+    if( op_none==op ){
+      if( cmpp_arg_equals(arg, "exists") ){
+        op = op_exists;
+        opArg = arg->next;
+        arg = opArg->next;
+        if( arg ){
+          cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                          "%s%s exists: too many arguments",
+                          cmpp_dx_delim(dx), d->name.z);
+          goto end;
+        }
+        break;
+      }else if( cmpp_arg_equals(arg, "join") ){
+        op = op_join;
+        if( !arg->next ) goto missing_arg;
+        arg = arg->next;
+        break;
+      }else{
+        cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                        "Unknown %s%s command: %s",
+                        cmpp_dx_delim(dx), d->name.z, arg->z);
+        goto end;
+      }
+      cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s unhandled argument: %s",
+                      cmpp_dx_delim(dx), d->name.z, arg->z);
+      goto end;
+    }
+  }
+  switch( op ){
+    case op_none: goto missing_arg;
+    case op_join: {
+      int i = 0;
+      cmpp_flag32_t const bFlags = cmpp_arg_to_b_F_BRACE_CALL;
+      for( ; arg; arg = arg->next, ++i ){
+        if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b0), bFlags)
+            || (i && cmpp_dx_out_raw(dx, "/", 1))
+            || (b0->n && cmpp_dx_out_raw(dx, b0->z, b0->n)) ){
+          break;
+        }
+      }
+      cmpp_dx_out_raw(dx, "\n", 1);
+      break;
+    }
+    case op_exists: {
+      assert( opArg );
+      bool const b = cmpp__file_is_readable((char const *)opArg->z);
+      cmpp_dx_out_raw(dx, b ? "1\n" : "0\n", 2);
+      break;
+    }
+  }
+end:
+  cmpp_b_return(dx->pp, b0);
+  return;
+missing_arg:
+  if( arg ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s %s: missing argument",
+                    cmpp_dx_delim(dx), d->name.z, arg->z );
+  }else{
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s: missing subcommand",
+                    cmpp_dx_delim(dx), d->name.z);
+  }
+  goto end;
+}
+
+
+/**
+   #cmp LHS op RHS
+*/
+static void cmpp_dx_f_cmp(cmpp_dx *dx){
+  cmpp_b * const bL = cmpp_b_borrow(dx->pp);
+  cmpp_b * const bR = cmpp_b_borrow(dx->pp);
+  cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
+  if( !bL || !!bR ) goto end;
+  for( cmpp_arg const * arg = dx->args.arg0; arg
+         && !cmpp_dx_err_check(dx);
+       arg = arg->next ){
+    if( !bL->z ){
+      cmpp_arg_to_b(dx, arg, bL, a2bFlags);
+      continue;
+    }
+    if( !bR->z ){
+      cmpp_arg_to_b(dx, arg, bR, a2bFlags);
+      continue;
+    }
+    goto usage;
+  }
+
+  if( cmpp_dx_err_check(dx) ) goto end;
+  if( !bL->z || !bR->z ){
+  usage:
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Usage: LHS RHS");
+    goto end;
+  }
+  assert( bL->z );
+  assert( bR->z );
+  char cbuf[20];
+  int const cmp = strcmp((char*)bL->z, (char*)bR->z);
+  int const n = snprintf(cbuf, sizeof(cbuf), "%d", cmp);
+  assert(n>0);
+  cmpp_dx_out_raw(dx, cbuf, (cmpp_size_t)n);
+
+end:
+  cmpp_b_return(dx->pp, bL);
+  cmpp_b_return(dx->pp, bR);
+}
+
+
+#if 0
+/* Impl. for dummy placeholder. */
+static void cmpp_dx_f_todo(cmpp_dx *dx){
+  cmpp_d const * d = cmpp_dx_d(dx);
+  g_warn("TODO: directive handler for %s", d->name.z);
+}
+#endif
+
+/**
+   If zName matches one of the delayed-load directives, that directive
+   is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
+   no match is found, but pp's error state is not updated in that
+   case. If a match is found and registration fails, that result code
+   will propagate via pp.
+*/
+int cmpp__d_delayed_load(cmpp *pp, char const *zName){
+  if( ppCode ) return ppCode;
+  int rc = CMPP_RC_NO_DIRECTIVE;
+  unsigned const nName = strlen(zName);
+
+  pp->pimpl->flags.isInternalDirectiveReg = true;
+
+#define M(NAME) (nName==sizeof(NAME)-1 && 0==strcmp(zName,NAME))
+#define M_OC(NAME) (M(NAME) || M("/" NAME))
+#define M_IF(NAME) if( M(NAME) )
+#define CF(X) cmpp_d_F_ ## X
+#define F_A_RAW CF(ARGS_RAW)
+#define F_A_LIST CF(ARGS_LIST)
+#define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY)
+#define F_UNSAFE cmpp_d_F_NOT_IN_SAFEMODE
+#define F_NC cmpp_d_F_NO_CALL
+#define F_CALL cmpp_d_F_CALL_ONLY
+#define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \
+  cmpp_d_reg SYMNAME = {  \
+    .name = NAME,         \
+    .opener = {           \
+      .f = OPENER,        \
+      .flags = OFLAGS     \
+    },                    \
+    .closer = {           \
+      .f = CLOSER,        \
+      .flags = CFLAGS     \
+    },                    \
+    .dtor = 0,            \
+    .state = 0            \
+  }
+
+#define DREG(NAME, OPENER, OFLAGS, CLOSER, CFLAGS )         \
+  DREG0(const rReg, NAME, OPENER, OFLAGS, CLOSER, CFLAGS ); \
+  rc = cmpp_d_register(pp, &rReg, NULL);                    \
+  goto end
+
+  /* The #if family requires some hand-holding... */
+  if( M_OC("if") || M("elif") || M("else") ) {
+    DREG0(rIf,   "if",
+          cmpp_dx_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL),
+          cmpp_dx_f_if_dangler, 0);
+    DREG0(rElif, "elif",
+          cmpp_dx_f_if_dangler, F_NC,
+          0, 0);
+    DREG0(rElse, "else",
+          cmpp_dx_f_if_dangler, F_NC,
+          0, 0);
+    CmppIfState * const cis = cmpp__malloc(pp, sizeof(*cis));
+    if( !cis ) goto end;
+    memset(cis, 0, sizeof(*cis));
+    rIf.state = cis;
+    rIf.dtor  = cmpp_mfree;
+    if( cmpp_d_register(pp, &rIf, &cis->dIf)
+        /* rIf must be first to avoid leaking cis on error */
+        || cmpp_d_register(pp, &rElif, &cis->dElif)
+        || cmpp_d_register(pp, &rElse, &cis->dElse) ){
+      rc = ppCode;
+    }else{
+      assert( cis->dIf && cis->dElif && cis->dElse );
+      assert( !cis->dEndif );
+      assert( cis == cis->dIf->impl.state );
+      assert( cmpp_mfree==cis->dIf->impl.dtor );
+      cis->dElif->impl.state
+        = cis->dElse->impl.state
+        = cis;
+      cis->dElif->closer
+        = cis->dElse->closer
+        = cis->dEndif
+        = cis->dIf->closer;
+      rc = 0;
+    }
+    goto end;
+  }/* #if and friends */
+
+  /* Basic core directives... */
+#define M_IF_CORE(N,OPENER,OFLAGS,CLOSER,CFLAGS)  \
+  if( M_OC(N) ){                                  \
+    DREG(N, OPENER, OFLAGS, CLOSER, CFLAGS);      \
+  } (void)0
+
+  M_IF_CORE("@",                cmpp_dx_f_at,           F_A_LIST,
+                                cmpp_dx_f_dangling_closer, 0);
+  M_IF_CORE("arg",              cmpp_dx_f_arg,          F_A_LIST, 0, 0);
+  M_IF_CORE("assert",           cmpp_dx_f_expr,         F_EXPR, 0, 0);
+  M_IF_CORE("cmp",              cmpp_dx_f_cmp,          F_A_LIST, 0, 0);
+  M_IF_CORE("define",           cmpp_dx_f_define,       F_A_LIST,
+                                cmpp_dx_f_dangling_closer, 0);
+  M_IF_CORE("delimiter",        cmpp_dx_f_delimiter,    F_A_LIST,
+                                cmpp_dx_f_dangling_closer, 0);
+  M_IF_CORE("error",            cmpp_dx_f_error,        F_A_RAW, 0, 0);
+  M_IF_CORE("expr",             cmpp_dx_f_expr,         F_EXPR, 0, 0);
+  M_IF_CORE("join",             cmpp_dx_f_join,         F_A_LIST, 0, 0);
+  M_IF_CORE("once",             cmpp_dx_f_once,         F_A_LIST | F_NC,
+                                cmpp_dx_f_dangling_closer, 0);
+  M_IF_CORE("pragma",           cmpp_dx_f_pragma,       F_A_LIST, 0, 0);
+  M_IF_CORE("savepoint",        cmpp_dx_f_savepoint,    F_A_LIST, 0, 0);
+  M_IF_CORE("stderr",           cmpp_dx_f_stderr,       F_A_RAW, 0, 0);
+  M_IF_CORE("sum",              cmpp_dx_f_sum,          F_A_LIST, 0, 0);
+  M_IF_CORE("undef",            cmpp_dx_f_undef,        F_A_LIST, 0, 0);
+  M_IF_CORE("undefined-policy", cmpp_dx_f_undef_policy, F_A_LIST, 0, 0);
+  M_IF_CORE("//",               cmpp_dx_f_noop,         F_A_RAW, 0, 0);
+  M_IF_CORE("file",             cmpp_dx_f_file,
+                                F_A_LIST | F_UNSAFE, 0, 0);
+
+#undef M_IF_CORE
+
+
+  /* Directives which can be disabled via build flags or
+   flags to cmpp_ctor()... */
+#define M_IF_FLAGGED(NAME,FLAG,OPENER,OFLAGS,CLOSER,CFLAGS) \
+  M_IF(NAME) {                                \
+    if( 0==(FLAG & pp->pimpl->flags.newFlags) ) {    \
+      DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \
+    }                                         \
+    goto end;                                 \
+  }
+
+#ifndef CMPP_OMIT_D_INCLUDE
+  M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE,
+               cmpp_dx_f_include, F_A_LIST | F_UNSAFE,
+               0, 0);
+#endif
+
+#ifndef CMPP_OMIT_D_PIPE
+  M_IF_FLAGGED("pipe", cmpp_ctor_F_NO_PIPE,
+               cmpp_dx_f_pipe, F_A_RAW | F_UNSAFE,
+               cmpp_dx_f_dangling_closer, 0);
+#endif
+
+#ifndef CMPP_OMIT_D_DB
+  M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB,
+               cmpp_dx_f_attach, F_A_LIST | F_UNSAFE,
+               0, 0);
+  M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB,
+               cmpp_dx_f_detach, F_A_LIST | F_UNSAFE,
+               0, 0);
+  if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.newFlags)
+      && (M_OC("query") || M("query:no-rows")) ){
+    DREG0(rQ, "query", cmpp_dx_f_query, F_A_LIST | F_UNSAFE,
+          cmpp_dx_f_dangling_closer, 0);
+    cmpp_d * dQ = 0;
+    rc = cmpp_d_register(pp, &rQ, &dQ);
+    if( 0==rc ){
+      /*
+        It would be preferable to delay registration of query:no-rows
+        until we need it, but doing so causes an error when:
+
+        |#if 0
+        |#query
+        |...
+        |#query:no-rows   HERE
+        |...
+        |#/query
+        |#/if
+
+        Because query:no-rows won't have been registered, and unknown
+        directives are an error even in skip mode. Maybe they
+        shouldn't be. Maybe we should just skip them in skip mode.
+        That's only been an issue since doing delayed registration of
+        directives, so it's not come up until recently (as of
+        2025-10-27). i was so hoping to be able to get _rid_ of skip
+        mode at some point.
+      */
+      cmpp_d * dNoRows = 0;
+      cmpp_d_reg const rNR = {
+        .name = "query:no-rows",
+        .opener = {
+          .f = cmpp_dx_f_dangling_closer,
+          .flags = F_NC
+        }
+      };
+      rc = cmpp_d_register(pp, &rNR, &dNoRows);
+      if( 0==rc ){
+        dNoRows->closer = dQ->closer;
+        assert( !dQ->impl.state );
+        dQ->impl.state = dNoRows;
+      }
+    }
+    goto end;
+  }
+#endif /*CMPP_OMIT_D_DB*/
+
+#if CMPP_D_MODULE
+  extern void cmpp_dx_f_module(cmpp_dx *);
+  M_IF_FLAGGED("module", cmpp_ctor_F_NO_MODULE,
+               cmpp_dx_f_module, F_A_LIST | F_UNSAFE,
+               0, 0);
+#endif
+
+#undef M_IF_FLAGGED
+
+#ifndef NDEBUG
+  M_IF("experiment"){
+    DREG("experiment", cmpp_dx_f_experiment,
+         F_A_LIST | F_UNSAFE, 0, 0);
+  }
+#endif
+
+end:
+#undef DREG
+#undef DREG0
+#undef F_EXPR
+#undef F_A_RAW
+#undef F_A_LIST
+#undef F_UNSAFE
+#undef F_NC
+#undef F_CALL
+#undef CF
+#undef M
+#undef M_OC
+#undef M_IF
+  pp->pimpl->flags.isInternalDirectiveReg = false;
+  return ppCode ? ppCode : rc;
+}
+/*
+** 2026-02-07:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses filesystem-related APIs libcmpp.
+*/
+
+#include <unistd.h>
+
+/**
+   There are APIs i'd _like_ to have here, but the readily-available
+   code for them BSD license, so can't be pasted in here. Examples:
+
+   - Filename canonicalization.
+
+   - Cross-platform getcwd() (see below).
+
+   - Windows support. This requires, in addition to the different
+   filesystem APIs, converting strings into something it can use.
+
+   All of that adds up to infrastructure... which already exists
+   elsewhere but can't be copied here while retaining this project's
+   license.
+*/
+
+bool cmpp__file_is_readable(char const *zFile){
+  return 0==access(zFile, R_OK);
+}
+
+#if 0
+FILE *cmpp__fopen(const char *zName, const char *zMode){
+  FILE *f;
+  if(zName && ('-'==*zName && !zName[1])){
+    f = (strchr(zMode, 'w') || strchr(zMode,'+'))
+      ? stdout
+      : stdin
+      ;
+  }else{
+    f = fopen(zName, zMode);
+  }
+  return f;
+}
+
+void cmpp__fclose( FILE * f ){
+  if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
+    fclose(f);
+  }
+}
+#endif
+/*
+** 2025-11-07:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the arguments-handling-related pieces for libcmpp.
+*/
+
+const cmpp_args_pimpl cmpp_args_pimpl_empty =
+  cmpp_args_pimpl_empty_m;
+const cmpp_args cmpp_args_empty = cmpp_args_empty_m;
+const cmpp_arg cmpp_arg_empty = cmpp_arg_empty_m;
+
+//just in case these ever get dynamic state
+void cmpp_arg_cleanup(cmpp_arg *arg){
+  if( arg ) *arg = cmpp_arg_empty;
+}
+
+//just in case these ever get dynamic state
+void cmpp_arg_reuse(cmpp_arg *arg){
+  if( arg ) *arg = cmpp_arg_empty;
+}
+
+/** Resets li's list for re-use but does not free it. Returns li. */
+static CmppArgList * CmppArgList_reuse(CmppArgList *li){
+  for(cmpp_size_t n = li->nAlloc; n; ){
+    cmpp_arg_reuse( &li->list[--n] );
+    assert( !li->list[n].next );
+  }
+  li->n = 0;
+  return li;
+}
+
+/** Free all memory owned by li but does not free li. */
+void CmppArgList_cleanup(CmppArgList *li){
+  const CmppArgList CmppArgList_empty = CmppArgList_empty_m;
+  while( li->nAlloc ){
+    cmpp_arg_cleanup( &li->list[--li->nAlloc] );
+  }
+  cmpp_mfree(li->list);
+  *li = CmppArgList_empty;
+}
+
+/** Returns the most-recently-appended arg of li back to li's
+    free-list. */
+static void CmppArgList_unappend(CmppArgList *li){
+  assert( li->n );
+  if( li->n ){
+    cmpp_arg_reuse( &li->list[--li->n] );
+  }
+}
+
+cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){
+  cmpp_arg * p = 0;
+  assert( li->list ? li->nAlloc : 0==li->nAlloc );
+  if( 0==ppCode
+      && 0==CmppArgList_reserve(pp, li,
+                                cmpp__li_reserve1_size(li,10)) ){
+    p = &li->list[li->n++];
+    cmpp_arg_reuse( p );
+  }
+  return p;
+}
+
+void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p){
+  assert( !p->nextFree );
+  cmpp_b_clear(&p->argOut);
+  CmppArgList_cleanup(&p->argli);
+  *p = cmpp_args_pimpl_empty;
+}
+
+static void cmpp_args_pimpl_reuse(cmpp_args_pimpl *p){
+  assert( !p->nextFree );
+  cmpp_b_reuse(&p->argOut);
+  CmppArgList_reuse(&p->argli);
+  assert( !p->argOut.n );
+  assert( !p->argli.n );
+}
+
+static void cmpp_args_pimpl_return(cmpp *pp, cmpp_args_pimpl *p){
+  if( p ){
+    assert( p->pp );
+    cmpp__pi(pp);
+    assert( !p->nextFree );
+    cmpp_args_pimpl_reuse(p);
+    p->nextFree = pi->recycler.argPimpl;
+    pi->recycler.argPimpl = p;
+  }
+}
+
+static cmpp_args_pimpl * cmpp_args_pimpl_borrow(cmpp *pp){
+  cmpp__pi(pp);
+  cmpp_args_pimpl * p = 0;
+  if( pi->recycler.argPimpl ){
+    p = pi->recycler.argPimpl;
+    pi->recycler.argPimpl = p->nextFree;
+    p->nextFree = 0;
+    p->pp = pp;
+    assert( !p->argOut.n && "Buffer was used when not borrowed" );
+  }else{
+    p = cmpp__malloc(pp, sizeof(*p));
+    if( 0==cmpp_check_oom(pp, p) ) {
+      *p = cmpp_args_pimpl_empty;
+      p->pp = pp;
+    }
+  }
+  return p;
+}
+
+CMPP__EXPORT(void, cmpp_args_cleanup)(cmpp_args *a){
+  if( a ){
+    if( a->pimpl ){
+      cmpp * const pp = a->pimpl->pp;
+      assert( pp );
+      if( pp ){
+        cmpp_args_pimpl_return(pp, a->pimpl);
+      }else{
+        cmpp_args_pimpl_cleanup(a->pimpl);
+        cmpp_mfree(a->pimpl);
+      }
+    }
+    *a = cmpp_args_empty;
+  }
+}
+
+CMPP__EXPORT(void, cmpp_args_reuse)(cmpp_args *a){
+  cmpp_args_pimpl * const p = a->pimpl;
+  if( p ) cmpp_args_pimpl_reuse(p);
+  *a = cmpp_args_empty;
+  a->pimpl = p;
+}
+
+int cmpp_args__init(cmpp * pp, cmpp_args * a){
+  if( 0==ppCode ){
+    if( a->pimpl ){
+      assert( a->pimpl->pp == pp );
+      cmpp_args_reuse(a);
+      assert(! a->pimpl->argOut.n );
+      assert( a->pimpl->pp == pp );
+    }else{
+      a->pimpl = cmpp_args_pimpl_borrow(pp);
+      assert( !a->pimpl || a->pimpl->pp==pp );
+    }
+  }
+  return ppCode;
+}
+
+/**
+   Declare cmpp_argOp_f_NAME().
+*/
+#define cmpp_argOp_decl(NAME)                                \
+  static void cmpp_argOp_f_ ## NAME (cmpp_dx *dx,            \
+                                     cmpp_argOp const *op,   \
+                                     cmpp_arg const *vLhs,   \
+                                     cmpp_arg const **pvRhs, \
+                                     int *pResult)
+cmpp_argOp_decl(compare);
+
+#if 0
+cmpp_argOp_decl(logical1);
+cmpp_argOp_decl(logical2);
+cmpp_argOp_decl(defined);
+#endif
+
+static const struct {
+  const cmpp_argOp opAnd;
+  const cmpp_argOp opOr;
+  const cmpp_argOp opGlob;
+  const cmpp_argOp opNotGlob;
+  const cmpp_argOp opNot;
+  const cmpp_argOp opDefined;
+#define cmpp_argOps_cmp_map(E) E(Eq) E(Neq) E(Lt) E(Le) E(Gt) E(Ge)
+#define E(NAME) const cmpp_argOp op ## NAME;
+  cmpp_argOps_cmp_map(E)
+#undef E
+} cmpp_argOps = {
+  .opAnd = {
+    .ttype = cmpp_TT_OpAnd,
+    .arity = 2,
+    .assoc = 0,
+    .xCall = 0//cmpp_argOp_f_logical2
+  },
+  .opOr = {
+    .ttype = cmpp_TT_OpOr,
+    .arity = 2,
+    .assoc = 0,
+    .xCall = 0//cmpp_argOp_f_logical2
+  },
+  .opGlob = {
+    .ttype = cmpp_TT_OpGlob,
+    .arity = 2,
+    .assoc = 0,
+    .xCall = 0//cmpp_argOp_f_glob
+  },
+  .opNotGlob = {
+    .ttype = cmpp_TT_OpNotGlob,
+    .arity = 2,
+    .assoc = 0,
+    .xCall = 0//cmpp_argOp_f_glob
+  },
+  .opNot = {
+    .ttype = cmpp_TT_OpNot,
+    .arity = 1,
+    .assoc = 1,
+    .xCall = 0//cmpp_argOp_f_logical1
+  },
+  .opDefined = {
+    .ttype = cmpp_TT_OpDefined,
+    .arity = 1,
+    .assoc = 1,
+    .xCall = 0//cmpp_argOp_f_defined
+  },
+  /* Comparison ops... */
+#define E(NAME) .op ## NAME = { \
+  .ttype = cmpp_TT_Op ## NAME, .arity = 2, .assoc = 0, \
+  .xCall = cmpp_argOp_f_compare },
+  cmpp_argOps_cmp_map(E)
+#undef E
+};
+
+cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt){
+  switch(tt){
+    case cmpp_TT_OpAnd:     return &cmpp_argOps.opAnd;
+    case cmpp_TT_OpOr:      return &cmpp_argOps.opOr;
+    case cmpp_TT_OpGlob:     return &cmpp_argOps.opGlob;
+    case cmpp_TT_OpNot:     return &cmpp_argOps.opNot;
+    case cmpp_TT_OpDefined: return &cmpp_argOps.opDefined;
+#define E(NAME) case cmpp_TT_Op ## NAME: return &cmpp_argOps.op ## NAME;
+  cmpp_argOps_cmp_map(E)
+#undef E
+    default: return NULL;
+  }
+}
+#define argOp(ARG) cmpp_argOp_for_tt((ARG)->ttype)
+
+#if 0
+cmpp_argOp_decl(logical1){
+  assert( cmpp_TT_OpNot==op->ttype );
+  assert( !vRhs );
+  assert( vLhs );
+  if( 0==cmpp__arg_toBool(dx, vLhs, pResult) ){
+    *pResult = !*pResult;
+  }
+}
+
+cmpp_argOp_decl(logical2){
+  assert( vRhs );
+  assert( vLhs );
+  int vL = 0;
+  int vR = 0;
+  if( 0==cmpp__arg_toBool(dx, vLhs, &vL)
+      && 0==cmpp__arg_toBool(dx, vRhs, &vR) ){
+    switch( op->ttype ){
+      case cmpp_TT_OpAnd: *pResult = vL && vR; break;
+      case cmpp_TT_OpOr:  *pResult = vL || vR; break;
+      default:
+        cmpp__fatal("Cannot happen: illegal op mapping");
+    }
+  }
+}
+
+cmpp_argOp_decl(defined){
+  assert( cmpp_TT_OpDefined==op->ttype );
+  assert( !vRhs );
+  assert( vLhs );
+  if( cmpp_TT_Word==vLhs->ttype ){
+    *pResult = cmpp_has(pp, (char const *)vLhs->z, vLhs->n);
+    if( !*pResult && vLhs->n>1 && '#'==vLhs->z[0] ){
+      *pResult = !!cmpp__d_search3(pp, vLhs->z+1,
+                                   cmpp__d_search3_F_NO_DLL);
+    }
+  }else{
+    cmpp__err(pp, CMPP_RC_TYPE, "Invalid token type %s for %s",
+              cmpp__tt_cstr(vLhs->ttype, true),
+              cmpp__tt_cstr(op->ttype, false));
+  }
+}
+#endif
+
+#if 0
+static cmpp_argOp const * cmpp_argOp_isCompare(cmpp_tt tt){
+  cmpp_argOp const * const p = cmpp_argOp_for_tt(tt);
+  switch( p ? p->ttype : cmpp_TT_None ){
+#define E(NAME) case cmpp_TT_Op ## NAME: return p;
+  cmpp_argOps_cmp_map(E)
+#undef E
+      return p;
+    case cmpp_TT_None:
+    default:
+      return NULL;
+  }
+}
+#endif
+
+/**
+   An internal helper for cmpp_argOp_...(). It binds some value of
+   *paArg to column bindNdx of query q and sets *paArg to the next
+   argument to be consumed. This function expects that q is set up to
+   do the right thing when *paArg is a Word-type value (see
+   cmpp_argOp_f_compare()).
+*/
+static void cmpp_argOp__cmp_bind(cmpp_dx * const dx,
+                                 sqlite3_stmt * const q,
+                                 int bindNdx,
+                                 cmpp_arg const ** paArg){
+  cmpp_arg const * const arg = *paArg;
+  assert(arg);
+  switch( dxppCode ? 0 : arg->ttype ){
+    case 0: break;
+    case cmpp_TT_Word:
+      /* In this case, q is supposed to be set up to use
+         CMPP__SEL_V_FROM(bindNdx), i.e. it expects the verbatim word
+         and performs the expansion to its value in the query. */
+      cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, arg->n);
+      *paArg = arg->next;
+      break;
+    case cmpp_TT_StringAt:
+    case cmpp_TT_String:
+    case cmpp_TT_Int:{
+      cmpp__bind_arg(dx, q, bindNdx, arg);
+      *paArg = arg->next;
+      break;
+    }
+    case cmpp_TT_OpNot:
+    case cmpp_TT_OpDefined:
+    case cmpp_TT_GroupParen:{
+      int rv = 0;
+      if( 0==cmpp__arg_toBool(dx, arg, &rv, paArg) ){
+        cmpp__bind_int(dx->pp, q, bindNdx, rv);
+      }
+      *paArg = arg->next;
+      break;
+    }
+      /* TODO? cmpp_TT_GroupParen */
+    default:
+      cmpp_dx_err_set(dx, CMPP_RC_TYPE,
+                      "Invalid argument type (%s) for the comparison "
+                      "queries: %s",
+                      cmpp_tt_cstr(arg->ttype), arg->z);
+  }
+}
+
+/**
+   Internal helper for cmp_argOp_...().
+
+   Expects q to be a query with an integer in result column 0.  This
+   steps/resets the query and applies the given comparison operator's
+   logic to column 0's value, placing the result of the operator in
+   *pResult.
+
+   If q has no result row, a default value of 0 is assumed.
+*/
+static void cmpp_argOp__cmp_apply(cmpp * const pp,
+                                 cmpp_argOp const * const op,
+                                 sqlite3_stmt * const q,
+                                 int * const pResult){
+  if( 0==ppCode ){
+    int rc = cmpp__step(pp, q, false);
+    assert( SQLITE_ROW==rc || ppCode );
+    if( SQLITE_ROW==rc ){
+      rc = sqlite3_column_int(q, 0);
+    }else{
+      rc = 0;
+    }
+    switch( op->ttype ){
+      case 0: break;
+      case cmpp_TT_OpEq:  *pResult = 0==rc; break;
+      case cmpp_TT_OpNeq: *pResult = 0!=rc; break;
+      case cmpp_TT_OpLt:  *pResult = rc<0;  break;
+      case cmpp_TT_OpLe:  *pResult = rc<=0; break;
+      case cmpp_TT_OpGt:  *pResult = rc>0;  break;
+      case cmpp_TT_OpGe:  *pResult = rc>=0; break;
+      default:
+        cmpp__fatal("Cannot happen: invalid arg mapping");
+    }
+  }
+  cmpp__stmt_reset(q);
+}
+
+/**
+   Applies *paRhs as the RHS of an integer binary operator, the LHS of
+   which is the lhs argument. The result is put in *pResult. On
+   success *paRhs is set to the next argument for the expression to
+   parse.
+*/
+static void cmpp_argOp_applyTo(cmpp_dx *dx,
+                               cmpp_argOp const * const op,
+                               int lhs,
+                               cmpp_arg const ** paRhs,
+                               int * pResult){
+  sqlite3_stmt * q = 0;
+  cmpp_arg const * aRhs = *paRhs;
+  assert(aRhs);
+  q = cmpp_TT_Word==aRhs->ttype
+    ? cmpp__stmt(dx->pp, CmppStmt_cmpVD, false)
+    : cmpp__stmt(dx->pp, CmppStmt_cmpVV, false);
+  if( q ){
+    char numbuf[32];
+    int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs);
+    cmpp__bind_textn(dx->pp, q, 1, ustr_c(numbuf), nNum);
+    cmpp_argOp__cmp_bind(dx, q, 2, paRhs);
+    cmpp_argOp__cmp_apply(dx->pp, op, q, pResult);
+  }
+}
+
+cmpp_argOp_decl(compare){
+  cmpp_arg const * const vRhs = *pvRhs;
+  sqlite3_stmt * q = 0;
+  /* Select which query to use, depending on whether each
+     of the LHS/RHS are Word tokens. For Word tokens
+     the corresponding query columns get bound to
+     a subquery which resolves the word. Non-word
+     tokens get bound as-is. */
+  if( cmpp_TT_Word==vLhs->ttype ){
+    q = cmpp_TT_Word==vRhs->ttype
+      ? cmpp__stmt(dx->pp, CmppStmt_cmpDD, false)
+      : cmpp__stmt(dx->pp, CmppStmt_cmpDV, false);
+    if(0){
+      g_warn("\nvLhs=%s %s\nvRhs=%s %s\n",
+             cmpp_tt_cstr(vLhs->ttype), vLhs->z,
+             cmpp_tt_cstr(vRhs->ttype), vRhs->z);
+    }
+  }else if( cmpp_TT_Word==vRhs->ttype ){
+    q = cmpp__stmt(dx->pp, CmppStmt_cmpVD, false);
+  }else{
+    q = cmpp__stmt(dx->pp, CmppStmt_cmpVV, false);
+  }
+  if( q ){
+    //cmpp__bind_textn(pp, q, 1, vLhs->z, vLhs->n);
+    cmpp_argOp__cmp_bind(dx, q, 1, &vLhs);
+    cmpp_argOp__cmp_bind(dx, q, 2, pvRhs);
+    cmpp_argOp__cmp_apply(dx->pp, op, q, pResult);
+  }
+}
+
+#undef cmpp_argOp_decl
+
+#if 0
+static inline int cmpp_dxt_isBinOp(cmpp_tt tt){
+  cmpp_argOp const * const a = cmpp_argOp_for_tt(tt);
+  return a ? 2==a->arity : 0;
+}
+
+static inline int cmpp_dxt_isUnaryOp(cmpp_tt tt){
+  return tt==cmpp_TT_OpNot || cmpp_TT_OpDefined;
+}
+
+static inline int cmpp_dxt_isGroup(cmpp_tt tt){
+  return tt==cmpp_TT_GroupParen || tt==cmpp_TT_GroupBrace || cmpp_TT_GroupSquiggly;
+}
+#endif
+
+int cmpp__arg_evalSubToInt(cmpp_dx *dx,
+                           cmpp_arg const *arg,
+                           int * pResult){
+  cmpp_args sub = cmpp_args_empty;
+  if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){
+    cmpp__args_evalToInt(dx, &sub, pResult);
+  }
+  cmpp_args_cleanup(&sub);
+  return dxppCode;
+}
+
+int cmpp__args_evalToInt(cmpp_dx * const dx,
+                        cmpp_args const *pArgs,
+                        int * pResult){
+  if( dxppCode ) return dxppCode;
+
+  cmpp_arg const * pNext = 0;
+  cmpp_arg const * pPrev = 0;
+  int result = *pResult;
+  cmpp_b osL = cmpp_b_empty;
+  cmpp_b osR = cmpp_b_empty;
+  static int level = 0;
+  ++level;
+
+#define lout(fmt,...) if(0) g_stderr("%.*c" fmt, level*2, ' ', __VA_ARGS__)
+
+  //lout("START %s(): %s\n", __func__, pArgs->pimpl->buf.argsRaw.z);
+  for( cmpp_arg const *arg = pArgs->arg0;
+       arg && 0==dxppCode;
+       pPrev = arg, arg = pNext ){
+    pNext = arg->next;
+    if( cmpp_TT_Noop==arg->ttype ){
+      arg = pPrev /* help the following arg to DTRT */;
+      continue;
+    }
+    cmpp_argOp const * const thisOp = argOp(arg);
+    cmpp_argOp const * const nextOp = pNext ? argOp(pNext) : 0;
+    if( 0 ){
+      lout("arg: %s @%p %s\n",
+           cmpp__tt_cstr(arg->ttype, true), arg, arg->z);
+      if(1){
+        if( pPrev ) lout("  prev arg: %s %s\n",
+                         cmpp__tt_cstr(pPrev->ttype, true), pPrev->z);
+        if( pNext ) lout("  next arg: %s %s\n",
+                         cmpp__tt_cstr(pNext->ttype, true), pNext->z);
+      }
+    }
+    if( thisOp ){ /* Basic validation */
+      if( !pNext ){
+        dxserr("Missing '%s' RHS.",
+               cmpp__tt_cstr(thisOp->ttype, false));
+        break;
+      }else if( !pPrev && 2==thisOp->arity  ){
+        dxserr("Missing %s LHS.",
+               cmpp__tt_cstr(thisOp->ttype, false));
+        break;
+      }
+      if( nextOp && nextOp->arity>1 ){
+        dxserr("Invalid '%s' RHS: %s", arg->z, pNext->z);
+        break;
+      }
+    }
+
+    switch( arg->ttype ){
+
+      case cmpp_TT_OpNot:
+      case cmpp_TT_OpDefined:
+        if( pPrev && !argOp(pPrev) ){
+          cmpp_dx_err_set(dx, CMPP_RC_CANNOT_HAPPEN,
+                          "We expected to have consumed '%s' by "
+                          "this point.",
+                          pPrev->z);
+        }else{
+          cmpp__arg_toBool(dx, arg, &result, &pNext);
+        }
+        break;
+
+      case cmpp_TT_OpAnd:
+      case cmpp_TT_OpOr:{
+        assert( pNext );
+        assert( pPrev );
+        /* Reminder to self: we can't add short-circuiting of the RHS
+           right now because the handling of chained unary ops on the
+           RHS is handled via cmpp__arg_toBool(). */
+        int rv = 0;
+        if( 0==cmpp__arg_toBool(dx, pNext, &rv, &pNext) ){
+          if( cmpp_TT_OpAnd==arg->ttype ) result = result && rv;
+          else result = result || rv;
+        }
+        //g_warn("post-and/or pNext=%s\n", pNext ? pNext->z : 0);
+        break;
+      }
+
+      case cmpp_TT_OpNotGlob:
+      case cmpp_TT_OpGlob:{
+        assert( pNext );
+        assert( pPrev );
+        assert( pNext!=arg );
+        assert( pPrev!=arg );
+        if( cmpp_arg_to_b(dx, pNext, &osL, 0) ){
+          break;
+        }
+        unsigned char const * const zGlob = osL.z;
+        if( 0==cmpp_arg_to_b(dx, pPrev, &osR, 0) ){
+          if( 0 ){
+            g_warn("zGlob=[%s] z=[%s]", zGlob, osR.z);
+          }
+          result = 0==sqlite3_strglob((char const *)zGlob,
+                                      (char const *)osR.z);
+          if( cmpp_TT_OpNotGlob==arg->ttype ){
+            result = !result;
+          }
+          //g_warn("\nzGlob=%s\nz=%s\nresult=%d", zGlob, z, result);
+        }
+        pNext = pNext->next;
+        break;
+      }
+
+#define E(NAME) case cmpp_TT_Op ## NAME:
+      cmpp_argOps_cmp_map(E) {
+        cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0;
+        if( prevOp ){
+          /* Chained operators */
+          cmpp_argOp_applyTo(dx, thisOp, result, &pNext, &result);
+        }else{
+          assert( pNext );
+          assert( pPrev );
+          assert( thisOp );
+          assert( thisOp->xCall );
+          thisOp->xCall(dx, thisOp, pPrev, &pNext, &result);
+        }
+        break;
+      }
+#undef E
+
+#define checkConsecutiveNonOps                             \
+      if( pPrev && !argOp(pPrev) ){                        \
+        dxserr("Illegal consecutive non-operators: %s %s", \
+               pPrev->z, arg->z);                          \
+        break;                                             \
+      }(void)0
+
+      case cmpp_TT_Int:
+      case cmpp_TT_String:
+        checkConsecutiveNonOps;
+        if( !cmpp__is_int(arg->z, arg->n, &result) ){
+          /* This is mostly for and/or ops. glob will reach back and
+             grab arg->z. */
+          result = 0;
+        }
+        break;
+      case cmpp_TT_Word:
+        checkConsecutiveNonOps;
+        cmpp__get_int(dx->pp, arg->z, arg->n, &result);
+        break;
+      case cmpp_TT_GroupParen:{
+        checkConsecutiveNonOps;
+        cmpp_args sub = cmpp_args_empty;
+        if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){
+          cmpp__args_evalToInt(dx, &sub, &result);
+        }
+        cmpp_args_cleanup(&sub);
+        break;
+      }
+      case cmpp_TT_GroupBrace:{
+        checkConsecutiveNonOps;
+        cmpp_b b = cmpp_b_empty;
+        if( 0==cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0) ){
+          cmpp__is_int(b.z, b.n, &result);
+        }
+        cmpp_b_clear(&b);
+        break;
+      }
+#undef checkConsecutiveNonOps
+      default:
+        assert( arg->z );
+        dxserr("Illegal expression token %s: %s",
+               cmpp__tt_cstr(arg->ttype, true), arg->z);
+    }/*switch(arg->ttype)*/
+  }/* foreach arg */
+  if( 0 ){
+    lout("END   %s() result=%d\n",  __func__, result);
+  }
+  --level;
+  if( !dxppCode ){
+    *pResult = result;
+  }
+  cmpp_b_clear(&osL);
+  cmpp_b_clear(&osR);
+  return dxppCode;
+#undef lout
+}
+
+#undef argOp
+#undef cmpp_argOp_decl
+
+static inline cmpp_tt cmpp_dxt_is_group(cmpp_tt ttype){
+  switch(ttype){
+    case cmpp_TT_GroupParen:
+    case cmpp_TT_GroupBrace:
+    case cmpp_TT_GroupSquiggly:
+      return ttype;
+    default:
+      return cmpp_TT_None;
+  }
+}
+
+int cmpp_args_parse(cmpp_dx * const dx,
+                    cmpp_args * const pArgs,
+                    unsigned char const * const zInBegin,
+                    cmpp_ssize_t nIn,
+                    cmpp_flag32_t flags){
+  assert( zInBegin );
+  unsigned char const * const zInEnd =
+    zInBegin + cmpp__strlenu(zInBegin, nIn);
+
+  if( cmpp_args__init(dx->pp, pArgs) ) return dxppCode;
+  if( 0 ){
+    g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin),
+           zInBegin);
+  }
+  unsigned char const * zPos = zInBegin;
+  cmpp_size_t const nBuffer =
+    /* Buffer size for our copy of the args. We need to know the
+       size before we start so that we can have each arg reliably
+       point back into this without it being reallocated during
+       parsing. */
+    (cmpp_size_t)(zInEnd - zInBegin)
+    /* Plus we need one final NUL and one NUL byte per argument, but
+       we don't yet know how many arguments we will have, so let's
+       estimate... */
+    + ((cmpp_size_t)(zInEnd - zInBegin))/3
+    + 5/*fudge room*/;
+  cmpp_b * const buffer = &pArgs->pimpl->argOut;
+  assert( !buffer->n );
+  if( cmpp_b_reserve3(dx->pp, buffer, nBuffer) ){
+    return dxppCode;
+  }
+  unsigned char * zOut = buffer->z;
+  unsigned char const * const zOutEnd = zOut + buffer->nAlloc - 1;
+  cmpp_arg * prevArg = 0;
+#if !defined(NDEBUG)
+  unsigned char const * const zReallocCheck = buffer->z;
+#endif
+
+  if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin),
+               zInBegin);
+  pArgs->arg0 = NULL;
+  pArgs->argc = 0;
+  for( int i = 0; zPos<zInEnd; ++i){
+    //g_stderr("i=%d prevArg=%p\n",i, prevArg);
+    cmpp_arg * const arg =
+      CmppArgList_append(dx->pp, &pArgs->pimpl->argli);
+    if( !arg ) return dxppCode;
+    assert( pArgs->pimpl->argli.n );
+    if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos);
+    if( cmpp_arg_parse(dx, arg, &zPos, zInEnd, &zOut, zOutEnd) ){
+      if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos);
+      break;
+    }
+    if( 0 ){
+      g_warn("#%d zPos=<<%.*s>>", i, (int)(zInEnd-zPos), zPos);
+      g_warn("#%d arg n=%u z=<<%.*s>> %s", i, (int)arg->n, (int)arg->n, arg->z, arg->z);
+    }
+    assert( zPos<=zInEnd );
+    if( 0 ){
+      g_stderr("ttype=%d %s n=%u z=%.*s\n", arg->ttype,
+               cmpp__tt_cstr(arg->ttype, true),
+               (unsigned)arg->n, (int)arg->n, arg->z);
+    }
+    if( cmpp_TT_Eof==arg->ttype ){
+      CmppArgList_unappend(&pArgs->pimpl->argli);
+      break;
+    }
+    switch( 0==(flags & cmpp_args_F_NO_PARENS)
+            ? cmpp_dxt_is_group( arg->ttype )
+            : 0 ){
+      case cmpp_TT_GroupParen:{
+        /* Sub-expression. We tokenize it here just to ensure that we
+           can, so we can fail earlier rather than later. This is why
+           we need a recycler for the cmpp_args buffer memory. */
+        cmpp_args sub = cmpp_args_empty;
+        cmpp_args_parse(dx, &sub, arg->z, arg->n, flags);
+        //g_stderr("Parsed sub-expr: %s\n", sub.buffer.z);
+        cmpp_args_cleanup(&sub);
+        break;
+      }
+      case cmpp_TT_GroupBrace:
+      case cmpp_TT_GroupSquiggly:
+      default: break;
+    }
+    if( dxppCode ) break;
+    if( prevArg ){
+      assert( !prevArg->next );
+      prevArg->next = arg;
+    }
+    prevArg = arg;
+  }/*foreach input char*/
+  //g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(dxppCode), pArgs->args.n);
+  if( 0==dxppCode ){
+    pArgs->argc = pArgs->pimpl->argli.n;
+    assert( !pArgs->arg0 );
+    if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list;
+    if( zOut<zInEnd ) *zOut = 0;
+    if( 0 ){
+      for( cmpp_arg const * a = pArgs->arg0; a; a = a->next ){
+        g_stderr("  got: %s %.*s\n", cmpp__tt_cstr(a->ttype, true),
+                 a->n, a->z);
+      }
+    }
+  }
+  assert(zReallocCheck==buffer->z
+         && "Else buffer was reallocated, invalidating argN->z");
+  return dxppCode;
+}
+
+CMPP__EXPORT(int, cmpp_args_clone)(cmpp *pp, cmpp_arg const * const a0,
+                                        cmpp_args * const dest){
+  if( cmpp_args__init(pp, dest) || !a0 ) return ppCode;
+  cmpp_b * const ob = &dest->pimpl->argOut;
+  CmppArgList * const argli = &dest->pimpl->argli;
+  unsigned int i = 0;
+  cmpp_size_t nReserve = 0 /* arg buffer mem to preallocate */;
+
+  assert( !ob->n );
+  assert( !dest->arg0 );
+  assert( !dest->argc );
+  assert( !argli->n );
+
+  /* Preallocate ob->z to fit a copy of a0's args. */
+  for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
+    nReserve += a->n + 1/*NUL byte*/;
+  }
+  if( cmpp_b_reserve3(pp, ob, nReserve+1)
+      || CmppArgList_reserve(pp, argli, i) ){
+    goto end;
+  }
+  assert( argli->nAlloc>=i );
+  i = 0;
+#ifndef NDEBUG
+  unsigned char const * const zReallocCheck = ob->z;
+#endif
+  for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
+    cmpp_arg * const aNew = &argli->list[i];
+    aNew->n = a->n;
+    aNew->z = ob->z + ob->n;
+    aNew->ttype = a->ttype;
+    if( i ) argli->list[i-1].next = aNew;
+    assert( !a->z[a->n] && "Expecting a NUL byte there" );
+    cmpp_b_append4(pp, ob, a->z, a->n+1/*NUL byte*/);
+    if( 0 ){
+      g_warn("arg#%d=%s <<<%.*s>>> %s", i, cmpp_tt_cstr(a->ttype),
+             (int)a->n, a->z, a->z);
+    }
+    assert( zReallocCheck==ob->z
+            && "This cannot fail: ob->z was pre-allocated" );
+  }
+  dest->argc = i;
+  dest->arg0 = i ? &argli->list[0] : 0;
+end:
+  if( ppCode ){
+    cmpp_args_reuse(dest);
+  }
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_args_clone)(cmpp_dx * dx, cmpp_args *pOut){
+  return cmpp_args_clone(dx->pp, dx->args.arg0, pOut);
+}
+
+char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg){
+  char * z = 0;
+  if( 0==ppCode ){
+    z = sqlite3_mprintf("%s",arg->z);
+    cmpp_check_oom(pp, z);
+  }
+  return z;
+}
+
+static cmpp_tt cmpp_tt_forWord(unsigned char const *z, unsigned n,
+                               cmpp_tt dflt){
+  static const struct {
+#define E(NAME,STR) struct CmppSnippet NAME;
+    cmpp_tt_map(E)
+#undef E
+  } ttStr = {
+#define E(NAME,STR)                                   \
+    .NAME = {(unsigned char const *)STR,sizeof(STR)-1},
+    cmpp_tt_map(E)
+#undef E
+  };
+#define CASE(NAME) if( 0==memcmp(ttStr.NAME.z, z, n) ) return cmpp_TT_ ## NAME
+  switch( n ){
+    case 1:
+      CASE(OpEq);
+      CASE(Plus);
+      CASE(Minus);
+      break;
+    case 2:
+      CASE(OpOr);
+      CASE(ShiftL);
+      //CASE(ShiftR);
+      //CASE(ArrowL);
+      CASE(ArrowR);
+      CASE(OpNeq);
+      CASE(OpLt);
+      CASE(OpLe);
+      CASE(OpGt);
+      CASE(OpGe);
+      break;
+    case 3:
+      CASE(OpAnd);
+      CASE(OpNot);
+      CASE(ShiftL3);
+      break;
+    case 4:
+      CASE(OpGlob);
+      break;
+    case 7:
+      CASE(OpDefined);
+      break;
+#undef CASE
+  }
+#if 0
+  bool b = cmpp__is_int(z, n, NULL);
+  if( 1|| !b ){
+    g_warn("is_int(%s)=%d", z, b);
+  }
+  return b ? cmpp_TT_Int : dflt;
+#else
+  return cmpp__is_int(z, n, NULL) ? cmpp_TT_Int : dflt;
+#endif
+}
+
+int cmpp_arg_parse(cmpp_dx * const dx, cmpp_arg *pOut,
+                   unsigned char const **pzIn,
+                   unsigned char const *zInEnd,
+                   unsigned char ** pzOut,
+                   unsigned char const * zOutEnd){
+  unsigned char const * zi = *pzIn;
+  unsigned char * zo = *pzOut;
+  cmpp_tt ttype = cmpp_TT_None;
+
+#if 0
+  // trying to tickle valgrind
+  for(unsigned char const *x = zi; x < zInEnd; ++x ){
+    assert(*x);
+  }
+#endif
+  cmpp_arg_reuse( pOut );
+  cmpp_skip_snl( &zi, zInEnd );
+  if( zi>=zInEnd ){
+    *pzIn = zi;
+    pOut->ttype = cmpp_TT_Eof;
+    return 0;
+  }
+#define out(CH) if(zo>=zOutEnd) goto notEnoughOut; *zo++ = CH
+#define eot_break if( cmpp_TT_None!=ttype ){ keepGoing = 0; break; } (void)0
+  pOut->z = zo;
+  bool keepGoing = true;
+  for( ; keepGoing
+         && 0==dxppCode
+         && zi<zInEnd
+         && zo<zOutEnd; ){
+    cmpp_tt ttOverride = cmpp_TT_None;
+    switch( (int)*zi ){
+      case 0: keepGoing = false; break;
+      case ' ': case '\t': case '\n': case '\r':
+        eot_break;
+        cmpp_skip_snl( &zi, zInEnd );
+        break;
+      case '-':
+        if ('>'==zi[1] ){
+          ttype = cmpp_TT_ArrowR;
+          out(*zi++);
+          out(*zi++);
+          keepGoing = false;
+        }else{
+          goto do_word;
+        }
+        break;
+      case '=':
+        eot_break; keepGoing = false; ttype = cmpp_TT_OpEq; out(*zi++); break;
+#define opcmp(CH,TT,TTEQ,TTSHIFT,TTARROW)                        \
+      case CH: eot_break; keepGoing = false; ttype = TT; out(*zi++); \
+        if( zi<zInEnd && '='==*zi ){ out(*zi++); ttype = TTEQ; } \
+        else if( zi<zInEnd && CH==*zi ){ out(*zi++); ttype = TTSHIFT; } \
+        else if( (int)TTARROW && zi<zInEnd && '-'==*zi ){ out(*zi++); ttype = TTARROW; }
+
+        opcmp('>',cmpp_TT_OpGt,cmpp_TT_OpGe,cmpp_TT_ShiftR,0) break;
+        opcmp('<',cmpp_TT_OpLt,cmpp_TT_OpLe,cmpp_TT_ShiftL,cmpp_TT_ArrowL)
+        if( cmpp_TT_ShiftL==ttype && zi<zInEnd && '<'==zi[0] ) {
+          out(*zi++);
+          ttype = cmpp_TT_ShiftL3;
+        }
+        break;
+#undef opcmp
+      case '!':
+        eot_break;
+        keepGoing = false;
+        out(*zi++);
+        if( zi < zInEnd && '='==*zi ){
+          ttype = cmpp_TT_OpNeq;
+          out('=');
+          ++zi;
+        }else{
+          while( zi < zInEnd && '!'==*zi ){
+            out(*zi++);
+          }
+          ttype = cmpp_TT_OpNot;
+        }
+        break;
+      case '@':
+        if( zi+2 >= zInEnd || ('"'!=zi[1] && '\''!=zi[1]) ){
+          goto do_word;
+        }
+        //if( cmpp__StringAtIsOk(dx->pp) ) break;
+        ttOverride = cmpp_TT_StringAt;
+        ++zi /* consume opening '@' */;
+        //g_stderr("@-string override\n");
+        /* fall through */
+      case '"':
+      case '\'': {
+        /* Parse a string. We do not support backslash-escaping of any
+           sort here. Strings which themselves must contain quotes
+           should use the other quote type. */
+        keepGoing = false;
+        if( cmpp_TT_None!=ttype ){
+          cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
+                          "Misplaced quote character near: %.*s",
+                          (int)(zi+1 - *pzIn), *pzIn);
+          break;
+        }
+        unsigned char const * zQuoteAt = zi;
+        if( cmpp__find_closing(dx->pp, &zQuoteAt, zInEnd) ){
+          break;
+        }
+        assert( zi+1 <= zQuoteAt );
+        assert( *zi == *zQuoteAt );
+        if( (zQuoteAt - zi - 2) >= (zOutEnd-zo) ){
+          goto notEnoughOut;
+        }
+        memcpy(zo, zi+1, zQuoteAt - zi - 1);
+        //g_warn("string=<<%.*s>>", (zQuoteAt-zi-1), zo);
+        zo += zQuoteAt - zi - 1;
+        zi = zQuoteAt + 1/* closing quote */;
+        ttype = (cmpp_TT_None==ttOverride ? cmpp_TT_String : ttOverride);
+        break;
+      }
+      case '[':
+      case '{':
+      case '(': {
+        /* Slurp these as a single token for later sub-parsing */
+        keepGoing = false;
+        unsigned char const * zAt = zi;
+        if( cmpp__find_closing(dx->pp, &zi, zInEnd) ) break;
+        /* Transform the output, eliding the open/close characters and
+           trimming spaces. We need to keep newlines intact, as the
+           content may be free-form, intended for other purposes, e.g.
+           the #pipe or #query directives. */
+        ttype = ('('==*zAt
+                 ? cmpp_TT_GroupParen
+                 : ('['==*zAt
+                    ? cmpp_TT_GroupBrace
+                    : cmpp_TT_GroupSquiggly));
+        ++zAt /* consume opening brace */;
+        /* Trim leading and trailing space, but retain tabs and all but
+           the first and last newline. */
+        cmpp_skip_space(&zAt, zi);
+        if( zAt<zInEnd ){
+          if( '\n'==*zAt ) ++zAt;
+          else if(zAt+1<zInEnd && '\r'==*zAt && '\n'==zAt[1]) zAt+=2;
+        }
+        for( ; zAt<zi; ++zAt ){
+          out(*zAt);
+        }
+        if(0) g_warn("parse1: group n=%u [%.*s]\n",
+                     (zi-zAt), (zi-zAt), zAt);
+        while( zo>*pzOut && ' '==zo[-1] ) *--zo = 0;
+        if( zo>*pzOut && '\n'==zo[-1] ){
+          *--zo = 0;
+          if( zo>*pzOut && '\r'==zo[-1] ){
+            *--zo = 0;
+          }
+        }
+        ++zi /* consume the closer */;
+        break;
+      }
+      default:
+        ; do_word:
+        out(*zi++);
+        ttype = cmpp_TT_Word;
+        break;
+    }
+    //g_stderr("kg=%d char=%d %c\n", keepGoing, (int)*zi, *zi);
+  }
+  if( dxppCode ){
+    /* problem already reported */
+  }else if( zo>=zOutEnd-1 ){
+  notEnoughOut:
+    cmpp_dx_err_set(dx, CMPP_RC_RANGE,
+                    "Ran out of output space (%u bytes) while "
+                    "parsing an argument", (unsigned)(zOutEnd-*pzOut));
+  }else{
+    pOut->n = (zo - *pzOut);
+    if( cmpp_TT_None==ttype ){
+      pOut->ttype = cmpp_TT_Eof;
+    }else if( cmpp_TT_Word==ttype && pOut->n ){
+      pOut->ttype = cmpp_tt_forWord(pOut->z, pOut->n, ttype);
+    }else{
+      pOut->ttype = ttype;
+    }
+    *zo++ = 0;
+    *pzIn = zi;
+    *pzOut = zo;
+    switch( pOut->ttype ){
+      case cmpp_TT_Int:
+        if( '+'==*pOut->z ){ /* strip leading + */
+          ++pOut->z;
+          --pOut->n;
+        }
+        break;
+      default:
+        break;
+    }
+    if(0){
+      g_stderr("parse1: %s n=%u <<%.*s>>",
+               cmpp__tt_cstr(pOut->ttype, true), pOut->n,
+               pOut->n, pOut->z);
+    }
+  }
+#undef out
+#undef eot_break
+  return dxppCode;
+}
+
+int cmpp__arg_toBool(cmpp_dx * const dx, cmpp_arg const *arg,
+                    int * pResult, cmpp_arg const **pNext){
+  switch( dxppCode ? 0 : arg->ttype ){
+    case 0: break;
+
+    case cmpp_TT_Word:
+      *pNext = arg->next;
+      *pResult = cmpp__get_bool(dx->pp, arg->z, arg->n);
+      break;
+
+    case cmpp_TT_Int:
+      *pNext = arg->next;
+      cmpp__is_int(arg->z, arg->n, pResult)/*was already validated*/;
+      break;
+
+    case cmpp_TT_String:
+    case cmpp_TT_StringAt:{
+      unsigned char const * z = 0;
+      cmpp_size_t n = 0;
+      cmpp_b os = cmpp_b_empty;
+      if( 0==cmpp__arg_expand_ats(dx, &os, cmpp_atpol_CURRENT,
+                                 arg, cmpp_TT_StringAt, &z, &n) ){
+        *pNext = arg->next;
+        *pResult = n>0 && 0!=memcmp("0\0", z, 2);
+      }
+      cmpp_b_clear(&os);
+      break;
+    }
+
+    case cmpp_TT_GroupParen:{
+      *pNext = arg->next;
+      cmpp_args sub = cmpp_args_empty;
+      if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){
+        cmpp__args_evalToInt(dx, &sub, pResult);
+      }
+      cmpp_args_cleanup(&sub);
+      break;
+    }
+
+    case cmpp_TT_OpDefined:
+      if( !arg->next ){
+        dxserr("Missing '%s' RHS.", arg->z);
+      }else if( cmpp_TT_Word!=arg->next->ttype ){
+        dxserr( "Invalid '%s' RHS: %s", arg->z, arg->next->z);
+      }else{
+        cmpp_arg const * aOperand = arg->next;
+        *pNext = aOperand->next;
+        if( aOperand->n>1
+            && '#'==aOperand->z[0]
+            && !!cmpp__d_search3(dx->pp, (char const*)aOperand->z+1,
+                                 cmpp__d_search3_F_NO_DLL) ){
+          *pResult = 1;
+        }else{
+          *pResult = cmpp_has(dx->pp, (char const *)aOperand->z,
+                              aOperand->n);
+        }
+      }
+      break;
+
+    case cmpp_TT_OpNot:{
+      assert( arg->next && "See cmpp_args__not_simplify()");
+      assert( cmpp_TT_OpNot!=arg->next->ttype && "See cmpp_args__not_simplify()");
+      if( 0==cmpp__arg_toBool(dx, arg->next, pResult, pNext) ){
+        *pResult = !*pResult;
+      }
+      break;
+    }
+
+    default:
+      dxserr("Invalid token type %s for %s(): %s",
+             cmpp__tt_cstr(arg->ttype, true), __func__, arg->z);
+      break;
+  }
+  return dxppCode;
+}
+
+CMPP__EXPORT(int, cmpp_arg_to_b)(cmpp_dx * const dx, cmpp_arg const *arg,
+                                 cmpp_b * ob, cmpp_flag32_t flags){
+  /**
+     Reminder to self: this function specifically does not do any
+     expression evaluation of its arguments. Please avoid the
+     temptation to make it do so. Unless it proves necessary.  Or
+     useful. Even then, though, consider the implications deeply
+     before doing so.
+  */
+  switch( dxppCode
+          ? 0
+          : ((cmpp_arg_to_b_F_FORCE_STRING & flags)
+             ? cmpp_TT_String : arg->ttype) ){
+
+    case 0:
+      break;
+    case cmpp_TT_Word:
+      if( 0==(flags & cmpp_arg_to_b_F_NO_DEFINES) ){
+        cmpp__get_b(dx->pp, arg->z, arg->n, ob, true);
+        break;
+      }
+      goto theDefault;
+    case cmpp_TT_StringAt:{
+      unsigned char const * z = 0;
+      cmpp_size_t n = 0;
+      if( 0 ){
+        g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z);
+      }
+      if( 0==cmpp__arg_expand_ats(dx, ob, cmpp_atpol_CURRENT, arg,
+                                  cmpp_TT_StringAt, &z, &n)
+          && 0 ){
+        g_warn("expanded at [%.*s] [%s]", (int)n, z, z);
+        g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z);
+      }
+      break;
+    }
+    case cmpp_TT_GroupBrace:
+      if( !(cmpp_arg_to_b_F_NO_BRACE_CALL & flags)
+          && (cmpp_arg_to_b_F_BRACE_CALL & flags) ){
+        cmpp_call_str(dx->pp, arg->z, arg->n, ob, 0);
+        break;
+      }
+      /* fall through */
+    default: {
+      theDefault: ;
+      cmpp_outputer oss = cmpp_outputer_b;
+      oss.state = ob;//no: cmpp_b_reuse(ob); Append instead.
+      cmpp__out2(dx->pp, &oss, arg->z, arg->n);
+      break;
+    }
+  }
+  return dxppCode;
+}
+
+int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * const q,
+                   int bindNdx, cmpp_arg const * const arg){
+
+  if( 0 ){
+    g_warn("bind #%d %s <<%.*s>>", bindNdx,
+           cmpp__tt_cstr(arg->ttype, true),
+           (int)arg->n, arg->z);
+  }
+  switch( arg->ttype ){
+    default:
+    case cmpp_TT_Int:
+    case cmpp_TT_String:
+      cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, (int)arg->n);
+      break;
+
+    case cmpp_TT_Word:
+    case cmpp_TT_StringAt:{
+      cmpp_b os = cmpp_b_empty;
+      if( 0==cmpp_arg_to_b(dx, arg, &os, 0) ){
+        if( 0 ){
+          g_warn("bind #%d <<%s>> => <<%.*s>>",
+                 bindNdx, arg->z, (int)os.n, os.z);
+        }
+        cmpp__bind_textn(dx->pp, q, bindNdx, os.z, (int)os.n);
+      }
+      cmpp_b_clear(&os);
+      break;
+    }
+
+    case cmpp_TT_GroupParen:{
+      cmpp_args sub = cmpp_args_empty;
+      int i = 0;
+      if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0)
+          && 0==cmpp__args_evalToInt(dx, &sub, &i) ){
+        /* See comment above about cmpp_TT_Int. */
+        cmpp__bind_int_text(dx->pp, q, bindNdx, i);
+      }
+      cmpp_args_cleanup(&sub);
+      break;
+    }
+
+    case cmpp_TT_GroupBrace:{
+      cmpp_b b = cmpp_b_empty;
+      cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0);
+      cmpp__bind_textn(dx->pp, q, bindNdx, b.z, b.n);
+      cmpp_b_clear(&b);
+      break;
+    }
+
+  }
+  return dxppCode;
+}
+
+/**
+   If a is in li->list, return its non-const pointer from li->list
+   (O(1)), else return NULL.
+*/
+static cmpp_arg * CmppArgList_arg_nc(CmppArgList *li, cmpp_arg const * a){
+  if( li->nAlloc && a>=li->list && a<(li->list + li->nAlloc) ){
+    return li->list + (a - li->list);
+  }
+  return NULL;
+}
+
+/**
+   To be called only by cmpp_dx_args_parse() and only if the current
+   directive asked for it via cmpp_d::flags cmpp_d_F_NOT_SIMPLIFY.
+
+   Filter chains of "not" operators from pArgs, removing unnecessary
+   ones. Also collapse "not glob" into a single cmpp_TT_OpNotGlob argument.
+   Performs some basic validation as well to simplify downstream
+   operations.  Returns p->err.code and is a no-op if that's set
+   before this is called.
+*/
+static int cmpp_args__not_simplify(cmpp * const pp, cmpp_args *pArgs){
+  cmpp_arg * pPrev = 0;
+  cmpp_arg * pNext = 0;
+  CmppArgList * const ali = &pArgs->pimpl->argli;
+  pArgs->argc = 0;
+  for( cmpp_arg * arg = ali->n ? &ali->list[0] : NULL;
+       arg && !ppCode;
+       pPrev=arg, arg = pNext ){
+    pNext = CmppArgList_arg_nc(ali, arg->next);
+    assert( pNext || !arg->next );
+    if( cmpp_TT_OpNot==arg->ttype ){
+      if( !pNext ){
+        serr("Missing '%s' RHS", arg->z);
+        break;
+      }
+      cmpp_argOp const * const nop = cmpp_argOp_for_tt(pNext->ttype);
+      if( nop && nop->arity>1 && cmpp_TT_OpGlob!=nop->ttype ){
+        serr("Illegal '%s' RHS: binary '%s' operator",
+             arg->z, pNext->z);
+        break;
+      }
+      int bNeg = 1;
+      if( '!'==*arg->z ){
+        /* odd number of ! == negate */
+        bNeg = arg->n & 1;
+      }
+      while( pNext && cmpp_TT_OpNot==pNext->ttype ){
+        bNeg = !bNeg;
+        arg->next = pNext = CmppArgList_arg_nc(ali, pNext->next);
+      }
+      if( pNext && cmpp_TT_OpGlob==pNext->ttype ){
+        /* Transform it to a cmpp_TT_OpNotGlob or cmpp_TT_OpGlob. */
+        assert( pNext->z > arg->z + arg->n );
+        arg->n = pNext->z + pNext->n - arg->z;
+        arg->next = pNext->next;
+        arg->ttype = bNeg
+          ? cmpp_TT_OpNotGlob
+          : pNext->ttype;
+        ++pArgs->argc;
+      }else if( pPrev ){
+        if( bNeg ){
+          ++pArgs->argc;
+        }else{
+          /* Snip this node out. */
+          pPrev->next = pNext;
+        }
+      }else{
+        assert( 0==pArgs->argc );
+        ++pArgs->argc;
+        if( !bNeg ){
+          arg->ttype = cmpp_TT_Noop;
+        }
+      }
+      /* Potential bug in waiting/fixme: by eliding all nots we are
+      ** changing the behavior from forced coercion to bool to
+      ** coercion to whatever the LHS wants. */
+    }else{
+      ++pArgs->argc;
+    }
+  }
+  pArgs->arg0 = pArgs->argc ? &ali->list[0] : NULL;
+  return ppCode;
+}
+
+CMPP__EXPORT(int, cmpp_dx_args_parse)(cmpp_dx *dx,
+                                           cmpp_args *args){
+  if( !dxppCode
+      && 0==cmpp_args_parse(dx, args, dx->args.z, dx->args.nz,
+                            cmpp_args_F_NO_PARENS)
+      && (cmpp_d_F_NOT_SIMPLIFY & dx->d->flags) ){
+    cmpp_args__not_simplify(dx->pp, args);
+  }
+  return dxppCode;
+}
+
+/* Helper for cmpp_kav_each() and friends. */
+static
+int cmpp__each_parse_args(cmpp_dx *dx,
+                          cmpp_args *args,
+                          unsigned char const *zBegin,
+                          cmpp_ssize_t nz,
+                          cmpp_flag32_t flags){
+  if( 0==cmpp_args_parse(dx, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){
+    if( !args->argc
+        && (cmpp_kav_each_F_NOT_EMPTY & flags) ){
+      cmpp_err_set(dx->pp, CMPP_RC_RANGE,
+                   "Empty list is not permitted here.");
+    }
+  }
+  return dxppCode;
+}
+
+/* Helper for cmpp_kav_each() and friends. */
+static
+int cmpp__each_paren_expr(cmpp_dx *dx, cmpp_arg const * arg,
+                          unsigned char * pOut, size_t nOut){
+  cmpp_args sub = cmpp_args_empty;
+  int rc = cmpp_args_parse(dx, &sub, arg->z, arg->n, 0);
+  if( 0==rc ){
+    int d = 0;
+    rc = cmpp__args_evalToInt(dx, &sub, &d);
+    if( 0==rc ){
+      snprintf((char *)pOut, nOut, "%d", d);
+    }
+  }
+  cmpp_args_cleanup(&sub);
+  return rc;
+}
+
+CMPP__EXPORT(int, cmpp_kav_each)(cmpp_dx *dx,
+                                 unsigned char const *zBegin,
+                                 cmpp_ssize_t nIn,
+                                 cmpp_kav_each_f callback,
+                                 void *callbackState,
+                                 cmpp_flag32_t flags){
+  if( dxppCode ) return dxppCode;
+  /* Reminder to self: we cannot reuse internal buffers here because a
+     callback could recurse into this or otherwise use APIs which use
+     those same buffers. */
+  cmpp_b bKey = cmpp_b_empty;
+  cmpp_b bVal = cmpp_b_empty;
+  bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags);
+  cmpp_args args = cmpp_args_empty;
+  unsigned char exprBuf[32] = {0};
+  cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
+  unsigned char const * const zEnd = zBegin + nz;
+  cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_to_b() flags*/;
+  assert( zBegin );
+  assert( zEnd );
+  assert( zEnd>=zBegin );
+
+  if( cmpp__each_parse_args(dx, &args, zBegin, nz,  flags) ){
+    goto cleanup;
+  }else if( reqArrow && 0!=args.argc%3 ){
+    cmpp_err_set(dx->pp, CMPP_RC_RANGE,
+                 "Expecting a list of 3 tokens per entry: "
+                 "KEY -> VALUE");
+  }else if( !reqArrow && 0!=args.argc%2 ){
+    cmpp_err_set(dx->pp, CMPP_RC_RANGE,
+                 "Expecting a list of 2 tokens per entry: "
+                 "KEY VALUE");
+  }
+  if( cmpp_kav_each_F_CALL_KEY & flags ){
+    a2bK |= cmpp_arg_to_b_F_BRACE_CALL;
+    flags |= cmpp_kav_each_F_EXPAND_KEY;
+  }
+  if( cmpp_kav_each_F_CALL_VAL & flags ){
+    a2bV |= cmpp_arg_to_b_F_BRACE_CALL;
+    flags |= cmpp_kav_each_F_EXPAND_VAL;
+  }
+  cmpp_arg const * aNext = 0;
+  for( cmpp_arg const * aKey = args.arg0;
+       !dxppCode && aKey;
+       aKey = aNext ){
+    aNext = aKey->next;
+    cmpp_arg const * aVal = aKey->next;
+    if( !aVal ){
+      dxserr("Expecting %s after key '%s'.",
+             (reqArrow ? "->" : "a value"),
+             aKey->z);
+      break;
+    }
+    if( reqArrow ){
+      if( cmpp_TT_ArrowR!=aVal->ttype ){
+        dxserr("Expecting -> after key '%s'.", aKey->z);
+        break;
+      }
+      aVal = aVal->next;
+      if( !aVal ){
+        dxserr("Expecting a value after '%s' ->.", aKey->z);
+        break;
+      }
+    }
+    //g_warn("\nkey=[%s]\nval=[%s]", aKey->z, aVal->z);
+    /* Expand the key/value parts if needed... */
+    unsigned char const *zKey;
+    unsigned char const *zVal;
+    cmpp_size_t nKey, nVal;
+    if( cmpp_kav_each_F_EXPAND_KEY & flags ){
+      if( cmpp_arg_to_b(dx, aKey, cmpp_b_reuse(&bKey),
+                             a2bK) ){
+        break;
+      }
+      zKey = bKey.z;
+      nKey = bKey.n;
+    }else{
+      zKey = aKey->z;
+      nKey = aKey->n;
+    }
+    if( cmpp_TT_GroupParen==aVal->ttype
+        && (cmpp_kav_each_F_PARENS_EXPR & flags) ){
+      if( cmpp__each_paren_expr(dx, aVal, &exprBuf[0],
+                                sizeof(exprBuf)-1) ){
+        break;
+      }
+      zVal = &exprBuf[0];
+      nVal = cmpp__strlenu(zVal, -1);
+    }else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
+      if( cmpp_arg_to_b(dx, aVal, cmpp_b_reuse(&bVal),
+                             a2bV) ){
+        break;
+      }
+      zVal = bVal.z;
+      nVal = bVal.n;
+    }else{
+      zVal = aVal->z;
+      nVal = aVal->n;
+    }
+    aNext = aVal->next;
+    if( 0!=callback(dx, zKey, nKey, zVal, nVal, callbackState) ){
+      break;
+    }
+  }
+cleanup:
+  cmpp_b_clear(&bKey);
+  cmpp_b_clear(&bVal);
+  cmpp_args_cleanup(&args);
+  return dxppCode;
+}
+
+CMPP__EXPORT(int, cmpp_str_each)(cmpp_dx *dx,
+                                 unsigned char const *zBegin,
+                                 cmpp_ssize_t nIn,
+                                 cmpp_kav_each_f callback, void *callbackState,
+                                 cmpp_flag32_t flags){
+  g_warn0("UNTESTED!");
+  if( dxppCode ) return dxppCode;
+  /* Reminder to self: we cannot reuse internal buffers here because a
+     callback could recurse into this or otherwise use APIs which use
+     those same buffers. */
+  cmpp_b ob = cmpp_b_empty;
+  cmpp_args args = cmpp_args_empty;
+  unsigned char exprBuf[32] = {0};
+  cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
+  unsigned char const * const zEnd = zBegin + nz;
+  assert( zBegin );
+  assert( zEnd );
+  assert( zEnd>=zBegin );
+
+  if( cmpp__each_parse_args(dx, &args, zBegin, nz,  flags) ){
+    goto cleanup;
+  }
+  cmpp_arg const * aNext = 0;
+  for( cmpp_arg const * arg = args.arg0;
+       !dxppCode && arg;
+       arg = aNext ){
+    aNext = arg->next;
+    //g_warn("\nkey=[%s]\nval=[%s]", arg->z, aVal->z);
+    /* Expand the key/value parts if needed... */
+    unsigned char const *zVal;
+    cmpp_size_t nVal;
+    if( cmpp_TT_GroupParen==arg->ttype
+        && (cmpp_kav_each_F_PARENS_EXPR & flags) ){
+      if( cmpp__each_paren_expr(dx, arg, &exprBuf[0],
+                                sizeof(exprBuf)-1) ){
+        break;
+      }
+      zVal = &exprBuf[0];
+      nVal = cmpp__strlenu(zVal, -1);
+    }else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
+      if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&ob), 0) ){
+        break;
+      }
+      zVal = ob.z;
+      nVal = ob.n;
+    }else{
+      zVal = arg->z;
+      nVal = arg->n;
+    }
+    if( 0!=callback(dx, arg->z, arg->n, zVal, nVal, callbackState) ){
+      break;
+    }
+  }
+cleanup:
+  cmpp_b_clear(&ob);
+  cmpp_args_cleanup(&args);
+  return dxppCode;
+}
+
+/**
+   Returns true if z _might_ be a cmpp_TT_StringAt, else false. It may have
+   false positives but won't have false negatives.
+
+   This is only intended to be used on NUL-terminated strings, not a
+   pointer into a cmpp input source.
+*/
+static bool cmpp__might_be_atstring(unsigned char const *z){
+  char const * const x = strchr((char const *)z, '@');
+  return x && !!strchr(x+1, '@');
+}
+
+int cmpp__arg_expand_ats(cmpp_dx const * const dx,
+                         cmpp_b * os,
+                         cmpp_atpol_e atPolicy,
+                         cmpp_arg const * const arg,
+                         cmpp_tt thisTtype,
+                         unsigned char const **pExp,
+                         cmpp_size_t * nExp){
+  assert( os );
+  cmpp_b_reuse(os);
+  if( 0==dxppCode
+      && (cmpp_TT_AnyType==thisTtype || thisTtype==arg->ttype)
+      && cmpp__might_be_atstring(arg->z)
+      && 0==cmpp__StringAtIsOk(dx->pp, atPolicy) ){
+#if 0
+    if( !os->nAlloc ){
+      cmpp_b_reserve3(os, 128);
+    }
+#endif
+    cmpp_outputer oos = cmpp_outputer_b;
+    oos.state = os;
+    assert( !os->n );
+    if( !cmpp_dx_out_expand(dx, &oos, arg->z, arg->n,
+                            atPolicy ) ){
+      *pExp = os->z;
+      if( nExp ) *nExp = os->n;
+      if( 0 ){
+        g_warn("os->n=%u os->z=[%.*s]\n", os->n, (int)os->n,
+               os->z);
+      }
+
+    }
+  }else if( !dxppCode ){
+    *pExp = arg->z;
+    if( nExp ) *nExp = arg->n;
+  }
+  return dxppCode;
+}
+
+bool cmpp__arg_wordIsPathOrFlag(
+  cmpp_arg const * const arg
+){
+  return cmpp_TT_Word==arg->ttype
+    && ('-'==(char)arg->z[0]
+        || strchr((char*)arg->z, '.')
+        || strchr((char*)arg->z, '-')
+        || strchr((char*)arg->z, '/')
+        || strchr((char*)arg->z, '\\'));
+}
+/*
+** 2022-11-12:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the cmpp_popen() pieces.
+*/
+#if !defined(_POSIX_C_SOURCE)
+#  define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */
+#endif
+#include <signal.h>
+
+const cmpp_popen_t cmpp_popen_t_empty = cmpp_popen_t_empty_m;
+
+#if CMPP_PLATFORM_IS_UNIX
+#include <signal.h>
+static int cmpp__err_errno(cmpp *pp, int errNo, char const *zContext){
+  return cmpp_err_set(pp, cmpp_errno_rc(errNo, CMPP_RC_ERROR),
+                      "errno #%d: %s", errNo, zContext);
+}
+#endif
+
+/**
+   Uses fork()/exec() to run a command in a separate process and open
+   a two-way stream to it.
+
+   If azCmd is NULL then zCmd must contain the command to run and
+   any flags. It is passed as the 4th argument to
+   execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL).
+
+   If azCmd is not NULL then it must be suitable for use as the 2nd
+   argument to execv(2). execv(X, azCmd) is used in this case, where
+   X is (zCmd ? zCmd : azCmd[0]).
+
+   Flags:
+
+   - cmpp_popen_F_DIRECT: if azCmd is NULL and flags has this bit set then
+     zCmd is instead passed to execl(zCmd, zCmd, NULL). That can only
+     work if zCmd is a single command without arguments.
+     cmpp_popen_F_DIRECT has no effect if azCmd is not NULL.
+
+   - cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which
+     performs path lookup of its initial argument.
+
+   On success:
+
+   - po->fdFromChild is the child's stdout. Read from it to read from
+   the child.
+
+   - If po->fpToChild is not NULL then *po->fpToChild is set to the
+   child's stdin. Write to it to send the child stuff. Be sure to
+   flush() and/or close() it to keep it from hanging forever. If
+   po->fpToChild is NULL then the stdin of the child is closed.
+
+   - po->childPid will be set to the PID of the child process.
+
+   On error: you know the drill.
+
+   After calling this, the caller is obligated to pass po to
+   cmpp_pclose(). If the caller fcloses() *po->fpToChild then they
+   must set it to NULL so that passing it to cmpp_pclose() knows not
+   to close it.
+
+   Bugs: because the command is run via /bin/sh -c ...  we cannot tell
+   if it's actually found. All we can tell is that /bin/sh ran.
+
+   Also: this doesn't capture stderr, so commands should redirect
+   stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is
+   a potential TODO without a current use case.
+*/
+static
+int cmpp__popen_impl(cmpp *pp, unsigned char const *zCmd,
+                     char * const * azCmd, cmpp_flag32_t flags,
+                     cmpp_popen_t *po){
+#if !CMPP_PLATFORM_IS_UNIX
+  return cmpp__err(pp, CMPP_RC_UNSUPPORTED,
+                   "Piping is not supported in this build.");
+#else
+  if( ppCode ) return ppCode;
+#define shut(P,N) close(P[N])
+  /** Attribution: this impl is derived from one found in
+      the Fossil SCM. */
+  int pin[2];
+  int pout[2];
+
+  po->fdFromChild = -1;
+  if( po->fpToChild ) *po->fpToChild = 0;
+  if( pipe(pin)<0 ){
+    return cmpp__err_errno(pp, errno, "pipe(in) failed");
+  }
+  if( pipe(pout)<0 ){
+    int const rc = cmpp__err_errno(pp, errno,
+                                   "pipe(out) failed");
+    shut(pin,0);
+    shut(pin,1);
+    return rc;
+  }
+  po->childPid = fork();
+  if( po->childPid<0 ){
+    int const rc = cmpp__err_errno(pp, errno, "fork() failed");
+    shut(pin,0);
+    shut(pin,1);
+    shut(pout,0);
+    shut(pout,1);
+    return rc;
+  }
+  signal(SIGPIPE,SIG_IGN);
+  if( po->childPid==0 ){
+    /* The child process. */
+    int fd;
+    close(0);
+    fd = dup(pout[0]);
+    if( fd!=0 ) {
+      cmpp__fatal("Error opening file descriptor 0.");
+    };
+    shut(pout,0);
+    shut(pout,1);
+    close(1);
+    fd = dup(pin[1]);
+    if(fd!=1) {
+      cmpp__fatal("Error opening file descriptor 1.");
+    };
+    shut(pin,0);
+    shut(pin,1);
+    if( azCmd ){
+      if( pp->pimpl->flags.doDebug>1 ){
+        for( int i = 0; azCmd[i]; ++i ){
+          g_warn("execv arg[%d]=%s", i, azCmd[i]);
+        }
+      }
+      int (*exc)(const char *, char *const []) =
+        (cmpp_popen_F_PATH & flags) ? execvp : execv;
+      exc(zCmd ? (char*)zCmd : azCmd[0], azCmd);
+      cmpp__fatal("execv() failed");
+    }else{
+      g_debug(pp,2,("zCmd=%s\n", zCmd));
+      int (*exc)(const char *, char const *, ...) =
+        (cmpp_popen_F_PATH & flags) ? execlp : execl;
+      if( cmpp_popen_F_DIRECT & flags ){
+        exc((char*)zCmd, (char*)zCmd, (char*)0);
+      }else{
+        exc("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0);
+      }
+      cmpp__fatal("execl() failed");
+    }
+    /* not reached */
+  }else{
+    /* The parent process. */
+    //cmpp_outputer_flush(&pp->pimpl->out.ch);
+    po->fdFromChild = pin[0];
+    shut(pin,1);
+    shut(pout,0);
+    if( po->fpToChild ){
+      *po->fpToChild = fdopen(pout[1], "w");
+      if( !*po->fpToChild ){
+        shut(pin,0);
+        shut(pout,1);
+        po->fdFromChild = -1;
+        cmpp__err_errno(pp, errno,
+                        "Error opening child process's stdin "
+                        "FILE handle from its descriptor.");
+      }
+    }else{
+      shut(pout,1);
+    }
+    return ppCode;
+  }
+#undef shut
+#endif
+}
+
+int cmpp_popen(cmpp *pp, unsigned char const *zCmd,
+                cmpp_flag32_t flags, cmpp_popen_t *po){
+  return cmpp__popen_impl(pp, zCmd, NULL, flags, po);
+}
+
+int cmpp_popenv(cmpp *pp, char * const * azCmd,
+                cmpp_flag32_t flags, cmpp_popen_t *po){
+  return cmpp__popen_impl(pp, NULL, azCmd, flags, po);
+}
+
+int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args,
+                    cmpp_popen_t *po){
+#if !CMPP_PLATFORM_IS_UNIX
+  return cmpp__popen_impl(dx->pp, NULL, 0, po) /* will fail */;
+#else
+  if( dxppCode ) return dxppCode;
+  enum { MaxArgs = 128 };
+  char * argv[MaxArgs] = {0};
+  cmpp_size_t offsets[MaxArgs] = {0};
+  cmpp_b osAll = cmpp_b_empty;
+  cmpp_b os1 = cmpp_b_empty;
+  if( args->argc >= MaxArgs ){
+    return cmpp_dx_err_set(dx, CMPP_RC_RANGE,
+                           "Too many arguments (%d). Max is %d.",
+                           args->argc, (int)MaxArgs);
+  }
+  int i = 0;
+  for(cmpp_arg const * a = args->arg0;
+      a; ++i, a = a->next ){
+    offsets[i] = osAll.n;
+    cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
+    if( cmpp__arg_wordIsPathOrFlag(a) ){
+      a2bFlags |= cmpp_arg_to_b_F_FORCE_STRING;
+    }
+    if( cmpp_arg_to_b(dx, a, cmpp_b_reuse(&os1), a2bFlags)
+        || cmpp_b_append4(dx->pp, &osAll, os1.z, os1.n+1/*NUL*/) ){
+      goto end;
+    }
+    assert( osAll.n > offsets[i] );
+    if( 0 ){
+      g_warn("execv arg[%d] = %s => %s", i, a->z,
+             osAll.z+offsets[i]);
+    }
+  }
+  argv[i] = 0;
+  for( --i; i >= 0; --i ){
+    argv[i] = (char*)(osAll.z + offsets[i]);
+    if( 0 ){
+      g_warn("execv arg[%d] = %s", i, argv[i]);
+    }
+  }
+end:
+  if( 0==dxppCode ){
+    cmpp__popen_impl(dx->pp, NULL, argv, 0, po);
+  }
+  cmpp_b_clear(&osAll);
+  cmpp_b_clear(&os1);
+  return dxppCode;
+#endif
+}
+
+int cmpp_pclose(cmpp_popen_t *po){
+#if CMPP_PLATFORM_IS_UNIX
+  if( po->fdFromChild>=0 ) close(po->fdFromChild);
+  if( po->fpToChild && *po->fpToChild ) fclose(*po->fpToChild);
+  int const childPid = po->childPid;
+  *po = cmpp_popen_t_empty;
+#if 1
+  int wp, rc = 0;
+  if( childPid>0 ){
+    //kill(childPid, SIGINT); // really needed?
+    do{
+      wp = waitpid(childPid, &rc, WNOHANG);
+      if( wp>0 ){
+        if( WIFEXITED(rc) ){
+          rc = WEXITSTATUS(rc);
+        }else if( WIFSIGNALED(rc) ){
+          rc = WTERMSIG(rc);
+        }else{
+          rc = 0/*???*/;
+        }
+      }
+    } while( wp>0 );
+  }
+  return rc;
+#elif 0
+  while( waitpid(childPid, NULL, WNOHANG)>0 ){}
+#else
+  if( childPid>0 ){
+    kill(childPid, SIGINT); // really needed?
+    waitpid((pid_t)childPid, NULL, WNOHANG);
+  }else{
+    while( waitpid( (pid_t)0, NULL, WNOHANG)>0 ){}
+  }
+#endif
+#endif
+}
+/*
+** 2022-11-12:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+** This file houses the module-loading pieces libcmpp.
+*/
+
+#if CMPP_ENABLE_DLLS
+static const CmppSohList CmppSohList_empty =
+  CmppSohList_empty_m;
+#endif
+
+#if CMPP_ENABLE_DLLS
+/**
+   If compiled without CMPP_ENABLE_DLLS defined to a true value
+   then this function always returns CMPP_RC_UNSUPPORTED and updates
+   the error state of its first argument with information about that
+   code.
+
+   Its first argument is the controlling cmpp. It can actually be
+   NULL - it's only used for reporting error details.
+
+   Its second argument is the name of a DLL file.
+
+   Its third argument is the name of a symbol in the given DLL which
+   resolves to a cmpp_module pointer. This name may be NULL,
+   in which case a default symbol name of "cmpp_module1" is used
+   (which is only useful when plugins are built one per DLL).
+
+   The fourth argument is the output pointer to store the
+   resulting module handle in.
+
+   The fifth argument is an optional list to append the DLL's
+   native handle to. It may be NULL.
+
+   This function tries to open a DLL named fname using the system's
+   DLL loader. If none is found, CMPP_RC_NOT_FOUND is returned and the
+   cmpp's error state is populated with info about the error. If
+   one is found, it looks for a symbol in the DLL: if symName is not
+   NULL and is not empty then the symbol "cmpp_module_symName" is
+   sought, else "cmpp_module". (e.g. if symName is "foo" then it
+   searches for a symbol names "cmpp_module_foo".) If no such symbol is
+   found then CMPP_RC_NOT_FOUND (again) is returned and the
+   cmpp's error state is populated, else the symbol is assumed to
+   be a (cmpp_module*) and *mod is assigned to it.
+
+   All errors update pp's error state but all are recoverable.
+
+   Returns 0 on success.
+
+   On success:
+
+  - `*mod` is set to the module object. Its ownship is kinda murky: it
+    lives in memory made available via the module loader. It remains
+    valid memory until the DLL is closed. The module might also
+    actually be statically linked with the application, in which case
+    it will live as long as the app.
+
+  - If soli is not NULL then the native DLL handle is appended to it.
+    Allocation errors when appending the DLL handle to the target list
+    are ignored - failure to retain a DLL handle for closing later is
+    not considered critical (and it would be extraordinarily rare (and
+    closing them outside of late-/post-main() cleanup is ill-advised,
+    anyway)).
+
+   @see cmpp_module_load()
+   @see CMPP_MODULE_DECL
+   @see CMPP_MODULE_IMPL2
+   @see CMPP_MODULE_IMPL3
+   @see CMPP_MODULE_IMPL_SOLO
+   @see CMPP_MODULE_REGISTER2
+   @see CMPP_MODULE_REGISTER3
+*/
+static
+int cmpp__module_extract(cmpp * pp,
+                         char const * dllFileName,
+                         char const * symName,
+                         cmpp_module const ** mod);
+#endif
+
+#if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE)
+#  define CMPP_D_MODULE 1
+#else
+#  define CMPP_D_MODULE 0
+#endif
+
+#if CMPP_D_MODULE
+/**
+   The #module directive:
+
+   #module dll ?moduleName?
+
+   Uses cmpp_module_load(dx, dll, moduleName||NULL) to try to load a
+   directive module.
+*/
+//static
+void cmpp_dx_f_module(cmpp_dx *dx) {
+  cmpp_arg const * aName = 0;
+  cmpp_b obDll = cmpp_b_empty;
+  for( cmpp_arg const *arg = dx->args.arg0;
+       arg; arg = arg->next ){
+    //MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z));
+    if( cmpp_dx_err_check(dx) ) goto end;
+    else if( !obDll.z ){
+      cmpp_arg_to_b(
+        dx, arg, &obDll,
+        0//cmpp_arg_to_b_F_NO_DEFINES
+      );
+      continue;
+    }else if( !aName ){
+      aName = arg;
+      continue;
+    }
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Unhandled argument: %s", arg->z);
+    goto end;
+  }
+  if( !obDll.z ){
+    cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
+                    "Expecting a DLL name argument.");
+    goto end;
+  }
+  cmpp_module_load(dx->pp, (char const *)obDll.z,
+                   aName ? (char const *)aName->z : NULL);
+end:
+  cmpp_b_clear(&obDll);
+  return;
+#if 0
+  missing_arg:
+  cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument after %s.",
+                  arg->z);
+  return;
+#endif
+}
+#endif /* #module */
+
+/**
+   Module loader pedantic licensing note: Most of cmpp's
+   module-loading code was copied verbatim from another project[^1],
+   but was written by the same author who relicenses it in
+   cmpp.
+
+   [^1]: https://fossil.wanderinghorse.net/r/cwal
+*/
+#if CMPP_ENABLE_DLLS
+#if CMPP_HAVE_DLOPEN
+typedef void * cmpp_soh;
+#  include <dlfcn.h> /* this actually has a different name on some platforms! */
+#elif CMPP_HAVE_LTDLOPEN
+#  include <ltdl.h>
+typedef lt_dlhandle cmpp_soh;
+#elif CMPP_ENABLE_DLLS
+#  error "We have no dlopen() impl for this configuration."
+#endif
+
+static cmpp_soh cmpp__dlopen(char const * fname,
+                             char const **errMsg){
+  static int once  = 0;
+  cmpp_soh soh = 0;
+  if(!once && ++once){
+#if CMPP_HAVE_DLOPEN
+    dlopen( 0, RTLD_NOW | RTLD_GLOBAL );
+#elif CMPP_HAVE_LTDLOPEN
+    lt_dlinit();
+    lt_dlopen( 0 );
+#endif
+  }
+#if CMPP_HAVE_DLOPEN
+  soh = dlopen(fname, RTLD_NOW | RTLD_GLOBAL);
+#elif CMPP_HAVE_LTDLOPEN
+  soh = lt_dlopen(fname);
+#endif
+  if(!soh && errMsg){
+#if CMPP_HAVE_DLOPEN
+    *errMsg = dlerror();
+#elif CMPP_HAVE_LTDLOPEN
+    *errMsg = lt_dlerror();
+#endif
+  }
+  return soh;
+}
+
+static
+cmpp_module const * cmpp__dlsym(cmpp_soh soh,
+                                         char const * mname){
+  cmpp_module const ** sym =
+#if CMPP_HAVE_DLOPEN
+    dlsym(soh, mname)
+#elif CMPP_HAVE_LTDLOPEN
+    lt_dlsym(soh, mname)
+#else
+    NULL
+#endif
+    ;
+  return sym ? *sym : NULL;
+}
+
+static void cmpp__dlclose(cmpp_soh soh){
+  if( soh ) {
+#if CMPP_CLOSE_DLLS
+    /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */
+#if CMPP_HAVE_DLOPEN
+    dlclose(soh);
+#elif CMPP_HAVE_LTDLOPEN
+    lt_dlclose(soh);
+#endif
+#endif
+  }
+}
+#endif /* CMPP_ENABLE_DLLS */
+
+#define CmppSohList_works (CMPP_ENABLE_DLLS && CMPP_CLOSE_DLLS)
+
+int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){
+#if CmppSohList_works
+  int const rc = cmpp_array_reserve(pp, (void**)&soli->list,
+                                    soli->n
+                                    ? (soli->n==soli->nAlloc
+                                       ? soli->nAlloc*2
+                                       : soli->n+1)
+                                    : 8,
+                                    &soli->nAlloc, sizeof(void*));
+  if( 0==rc ){
+    soli->list[soli->n++] = soh;
+  }
+  return rc;
+#else
+  (void)pp; (void)soli; (void)soh;
+  return 0;
+#endif
+}
+
+void CmppSohList_close(CmppSohList *s){
+#if CmppSohList_works
+  while( s->nAlloc ){
+    if( s->list[--s->nAlloc] ){
+      //MARKER(("closing soh %p\n", s->list[s->nAlloc]));
+      cmpp__dlclose(s->list[s->nAlloc]);
+      s->list[s->nAlloc] = 0;
+    }
+  }
+  cmpp_mfree(s->list);
+  *s = CmppSohList_empty;
+#else
+  (void)s;
+#endif
+}
+
+#if 0
+/**
+   Passes soli to CmppSohList_close() then frees soli. Results are
+   undefined if soli is not NULL but was not returned from
+   CmppSohList_new().
+
+   Special case: if built without DLL-closing support, this is a no-op.
+*/
+//static void CmppSohList_free(CmppSohList *soli);
+void CmppSohList_free(CmppSohList *s){
+  if( s ){
+#if CmppSohList_works
+    CmppSohList_close(s);
+    cmpp_mfree(s);
+#endif
+  }
+}
+
+/**
+   Returns a new, cleanly-initialized CmppSohList or NULL
+   on allocation error. The returned instance must eventually be
+   passed to CmppSohList_free().
+
+   Special case: if built without DLL-closing support, this returns a
+   no-op singleton instance.
+*/
+//static CmppSohList * CmppSohList_new(void);
+CmppSohList * CmppSohList_new(void){
+#if CmppSohList_works
+  CmppSohList * s = cmpp_malloc(sizeof(*s));
+  if( s ) *s = CmppSohList_empty;
+  return s;
+#else
+  static CmppSohList soli = CmppSohList_empty;
+  return &soli;
+#endif
+}
+#endif
+
+#undef CmppSohList_works
+
+#if CMPP_ENABLE_DLLS
+/**
+   Default entry point symbol name for loadable modules.  This must
+   match the symbolic name defined by CMPP_MODULE_IMPL_SOLO().
+*/
+static char const * const cmppModDfltSym = "cmpp_module1";
+
+/**
+   Looks for a symbol in the given DLL handle. If symName is NULL or
+   empty, the symbol "cmpp_module" is used, else the symbols
+   ("cmpp_module__" + symName) is used. If it finds one, it casts it to
+   cmpp_module and returns it. On error it may update pp's
+   error state with the error information if pp is not NULL.
+
+   Errors:
+
+   - symName is too long.
+
+   - cmpp__dlsym() lookup failure.
+*/
+static cmpp_module const *
+cmpp__module_fish_out_entry_pt(cmpp * pp,
+                               cmpp_soh soh,
+                               char const * symName){
+  enum { MaxLen = 128 };
+  char buf[MaxLen] = {0};
+  cmpp_size_t const slen = symName ? strlen(symName) : 0;
+  cmpp_module const * mod = 0;
+  if(slen > (MaxLen-20)){
+    cmpp_err_set(pp, CMPP_RC_RANGE,
+                 "DLL symbol name '%.*s' is too long. Max is %d.",
+                 (int)slen, symName, (int)MaxLen-20);
+  }else{
+    if(symName && *symName){
+      snprintf(buf, MaxLen,"cmpp_module__%s", symName);
+      symName = &buf[0];
+    }else{
+      symName = cmppModDfltSym;
+    }
+    mod = cmpp__dlsym(soh, symName);
+  }
+  /*MARKER(("%s() [%s] ==> %p\n",__func__, symName,
+    (void const *)mod));*/
+  return mod;
+}
+#endif/*CMPP_ENABLE_DLLS*/
+
+#if CMPP_ENABLE_DLLS
+/**
+   Tries to dlsym() the given cmpp_module symbol from the given
+   DLL handle. On success, 0 is returned and *mod is assigned to the
+   memory. On error, non-0 is returned and pp's error state may be
+   updated.
+
+   Ownership of the returned module ostensibly lies with the first
+   argument, but that's not entirely true. If CMPP_CLOSE_DLLS is true
+   then a copy of the module's pointer is stored in the engine for
+   later closing. The memory itself is owned by the module loader, and
+   "should" stay valid until the DLL is closed.
+*/
+static int cmpp__module_get_sym(cmpp * pp,
+                                cmpp_soh soh,
+                                char const * symName,
+                                cmpp_module const ** mod){
+
+  cmpp_module const * lm = 0;
+  int rc = cmpp_err_has(pp);
+  if( 0==rc ){
+    lm = cmpp__module_fish_out_entry_pt(pp, soh, symName);
+    rc = cmpp_err_has(pp);
+  }
+  if(0==rc){
+    if(lm){
+      *mod = lm;
+    }else{
+      cmpp__dlclose(soh);
+      rc = cmpp_err_set(pp, CMPP_RC_NOT_FOUND,
+                        "Did not find module entry point symbol '%s'.",
+                        symName ? symName : cmppModDfltSym);
+    }
+  }
+  return rc;
+}
+#endif/*CMPP_ENABLE_DLLS*/
+
+#if !CMPP_ENABLE_DLLS
+static int cmpp__err_no_dlls(cmpp * const pp){
+  return cmpp_err_set(pp, CMPP_RC_UNSUPPORTED,
+                      "No dlopen() equivalent is installed "
+                      "for this build configuration.");
+}
+#endif
+
+#if CMPP_ENABLE_DLLS
+//no: CMPP_WASM_EXPORT
+int cmpp__module_extract(cmpp * pp,
+                         char const * fname,
+                         char const * symName,
+                         cmpp_module const ** mod){
+  int rc = cmpp_err_has(pp);
+  if( rc ) return rc;
+  else if( cmpp_is_safemode(pp) ){
+    return cmpp_err_set(pp, CMPP_RC_ACCESS,
+                        "Cannot use DLLs in safe mode.");
+  }else{
+    cmpp_soh soh;
+    char const * errMsg = 0;
+    soh = cmpp__dlopen(fname, &errMsg);
+    if(soh){
+      if( pp ){
+        CmppSohList_append(NULL/*alloc error here can be ignored*/,
+                           &pp->pimpl->mod.sohList, soh);
+      }
+      cmpp_module const * x = 0;
+      rc = cmpp__module_get_sym(pp, soh, symName, &x);
+      if(!rc && mod) *mod = x;
+      return rc;
+    }else{
+      return errMsg
+        ? cmpp_err_set(pp, CMPP_RC_ERROR, "DLL open failed: %s",
+                       errMsg)
+        : cmpp_err_set(pp, CMPP_RC_ERROR,
+                       "DLL open failed for unknown reason.");
+    }
+  }
+}
+#endif
+
+//no: CMPP_WASM_EXPORT
+int cmpp_module_load(cmpp * pp, char const * fname,
+                     char const * symName){
+#if CMPP_ENABLE_DLLS
+  if( ppCode ){
+    /* fall through */
+  }else if( cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags ){
+    cmpp_err_set(pp, CMPP_RC_ACCESS,
+                        "%s() is disallowed in safe-mode.");
+  }else{
+    cmpp__pi(pp);
+    char * zName = 0;
+    if( fname ){
+      zName = cmpp_path_search(pp, (char const *)pi->mod.path.z,
+                               pi->mod.pathSep, fname,
+                               pi->mod.soExt);
+      if( !zName ){
+        return cmpp_err_set(pp, CMPP_RC_NOT_FOUND,
+                            "Did not find [%s] or [%s%s] "
+                            "in search path [%s].",
+                            fname, fname, pi->mod.soExt,
+                            pi->mod.path.z);
+      }
+    }
+    cmpp_module const * mod = 0;
+    if( 0==cmpp__module_extract(pp, zName, symName, &mod) ){
+      assert(mod);
+      assert(mod->init);
+      int const rc = mod->init(pp);
+      if( rc && !ppCode ){
+        cmpp_err_set(pp, CMPP_RC_ERROR,
+                     "Module %s::init() failed with code #%d/%s "
+                     "without providing additional info.",
+                     symName ? symName : "cmpp_module",
+                     rc, cmpp_rc_cstr(rc));
+      }
+      cmpp_mfree(zName);
+    }
+  }
+  return ppCode;
+#else
+  (void)fname; (void)symName;
+  return cmpp__err_no_dlls(pp);
+#endif
+}
+/*
+** 2015-08-18, 2023-04-28
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file demonstrates how to create a table-valued-function using
+** a virtual table.  This demo implements the generate_series() function
+** which gives the same results as the eponymous function in PostgreSQL,
+** within the limitation that its arguments are signed 64-bit integers.
+**
+** Considering its equivalents to generate_series(start,stop,step): A
+** value V[n] sequence is produced for integer n ascending from 0 where
+**  ( V[n] == start + n * step  &&  sgn(V[n] - stop) * sgn(step) >= 0 )
+** for each produced value (independent of production time ordering.)
+**
+** All parameters must be either integer or convertable to integer.
+** The start parameter is required.
+** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
+** The step parameter defaults to 1 and 0 is treated as 1.
+**
+** Examples:
+**
+**      SELECT * FROM generate_series(0,100,5);
+**
+** The query above returns integers from 0 through 100 counting by steps
+** of 5.  In other words, 0, 5, 10, 15, ..., 90, 95, 100.  There are a total
+** of 21 rows.
+**
+**      SELECT * FROM generate_series(0,100);
+**
+** Integers from 0 through 100 with a step size of 1.  101 rows.
+**
+**      SELECT * FROM generate_series(20) LIMIT 10;
+**
+** Integers 20 through 29.  10 rows.
+**
+**      SELECT * FROM generate_series(0,-100,-5);
+**
+** Integers 0 -5 -10 ... -100.  21 rows.
+**
+**      SELECT * FROM generate_series(0,-1);
+**
+** Empty sequence.
+**
+** HOW IT WORKS
+**
+** The generate_series "function" is really a virtual table with the
+** following schema:
+**
+**     CREATE TABLE generate_series(
+**       value,
+**       start HIDDEN,
+**       stop HIDDEN,
+**       step HIDDEN
+**     );
+**
+** The virtual table also has a rowid which is an alias for the value.
+**
+** Function arguments in queries against this virtual table are translated
+** into equality constraints against successive hidden columns.  In other
+** words, the following pairs of queries are equivalent to each other:
+**
+**    SELECT * FROM generate_series(0,100,5);
+**    SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5;
+**
+**    SELECT * FROM generate_series(0,100);
+**    SELECT * FROM generate_series WHERE start=0 AND stop=100;
+**
+**    SELECT * FROM generate_series(20) LIMIT 10;
+**    SELECT * FROM generate_series WHERE start=20 LIMIT 10;
+**
+** The generate_series virtual table implementation leaves the xCreate method
+** set to NULL.  This means that it is not possible to do a CREATE VIRTUAL
+** TABLE command with "generate_series" as the USING argument.  Instead, there
+** is a single generate_series virtual table that is always available without
+** having to be created first.
+**
+** The xBestIndex method looks for equality constraints against the hidden
+** start, stop, and step columns, and if present, it uses those constraints
+** to bound the sequence of generated values.  If the equality constraints
+** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step.
+** xBestIndex returns a small cost when both start and stop are available,
+** and a very large cost if either start or stop are unavailable.  This
+** encourages the query planner to order joins such that the bounds of the
+** series are well-defined.
+**
+** Update on 2024-08-22:
+** xBestIndex now also looks for equality and inequality constraints against
+** the value column and uses those constraints as additional bounds against
+** the sequence range.  Thus, a query like this:
+**
+**     SELECT value FROM generate_series($SA,$EA)
+**      WHERE value BETWEEN $SB AND $EB;
+**
+** Is logically the same as:
+**
+**     SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB));
+**
+** Constraints on the value column can server as substitutes for constraints
+** on the hidden start and stop columns.  So, the following two queries
+** are equivalent:
+**
+**     SELECT value FROM generate_series($S,$E);
+**     SELECT value FROM generate_series WHERE value BETWEEN $S and $E;
+**
+*/
+#if 0
+#include "sqlite3ext.h"
+#else
+#include "sqlite3.h"
+#endif
+#include <assert.h>
+#include <string.h>
+#include <limits.h>
+#include <math.h>
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/* series_cursor is a subclass of sqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result.
+**
+** iOBase, iOTerm, and iOStep are the original values of the
+** start=, stop=, and step= constraints on the query.  These are
+** the values reported by the start, stop, and step columns of the
+** virtual table.
+**
+** iBase, iTerm, iStep, and bDescp are the actual values used to generate
+** the sequence.  These might be different from the iOxxxx values.
+** For example in
+**
+**   SELECT value FROM generate_series(1,11,2)
+**    WHERE value BETWEEN 4 AND 8;
+**
+** The iOBase is 1, but the iBase is 5.  iOTerm is 11 but iTerm is 7.
+** Another example:
+**
+**   SELECT value FROM generate_series(1,15,3) ORDER BY value DESC;
+**
+** The cursor initialization for the above query is:
+**
+**   iOBase = 1        iBase = 13
+**   iOTerm = 15       iTerm = 1
+**   iOStep = 3        iStep = 3      bDesc = 1
+**
+** The actual step size is unsigned so that can have a value of
+** +9223372036854775808 which is needed for querys like this:
+**
+**   SELECT value
+**     FROM generate_series(9223372036854775807,
+**                          -9223372036854775808,
+**                          -9223372036854775808)
+**    ORDER BY value ASC;
+**
+** The setup for the previous query will be:
+**
+**   iOBase = 9223372036854775807    iBase = -1
+**   iOTerm = -9223372036854775808   iTerm = 9223372036854775807
+**   iOStep = -9223372036854775808   iStep = 9223372036854775808  bDesc = 0
+*/
+typedef unsigned char u8;
+typedef struct series_cursor series_cursor;
+struct series_cursor {
+  sqlite3_vtab_cursor base;  /* Base class - must be first */
+  sqlite3_int64 iOBase;      /* Original starting value ("start") */
+  sqlite3_int64 iOTerm;      /* Original terminal value ("stop") */
+  sqlite3_int64 iOStep;      /* Original step value */
+  sqlite3_int64 iBase;       /* Starting value to actually use */
+  sqlite3_int64 iTerm;       /* Terminal value to actually use */
+  sqlite3_uint64 iStep;      /* The step size */
+  sqlite3_int64 iValue;      /* Current value */
+  u8 bDesc;                  /* iStep is really negative */
+  u8 bDone;                  /* True if stepped past last element */
+};
+
+/*
+** Computed the difference between two 64-bit signed integers using a
+** convoluted computation designed to work around the silly restriction
+** against signed integer overflow in C.
+*/
+static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){
+  assert( a>=b );
+  return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b);
+}  
+
+/*
+** Add or substract an unsigned 64-bit integer from a signed 64-bit integer
+** and return the new signed 64-bit integer.
+*/
+static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){
+  sqlite3_uint64 x = *(sqlite3_uint64*)&a;
+  x += b;
+  return *(sqlite3_int64*)&x;
+}
+static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){
+  sqlite3_uint64 x = *(sqlite3_uint64*)&a;
+  x -= b;
+  return *(sqlite3_int64*)&x;
+}
+
+/*
+** The seriesConnect() method is invoked to create a new
+** series_vtab that describes the generate_series virtual table.
+**
+** Think of this routine as the constructor for series_vtab objects.
+**
+** All this routine needs to do is:
+**
+**    (1) Allocate the series_vtab object and initialize all fields.
+**
+**    (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
+**        result set of queries against generate_series will look like.
+*/
+static int seriesConnect(
+  sqlite3 *db,
+  void *pUnused,
+  int argcUnused, const char *const*argvUnused,
+  sqlite3_vtab **ppVtab,
+  char **pzErrUnused
+){
+  sqlite3_vtab *pNew;
+  int rc;
+
+/* Column numbers */
+#define SERIES_COLUMN_ROWID (-1)
+#define SERIES_COLUMN_VALUE 0
+#define SERIES_COLUMN_START 1
+#define SERIES_COLUMN_STOP  2
+#define SERIES_COLUMN_STEP  3
+
+  (void)pUnused;
+  (void)argcUnused;
+  (void)argvUnused;
+  (void)pzErrUnused;
+  rc = sqlite3_declare_vtab(db,
+     "CREATE TABLE x(value,start hidden,stop hidden,step hidden)");
+  if( rc==SQLITE_OK ){
+    pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
+    if( pNew==0 ) return SQLITE_NOMEM;
+    memset(pNew, 0, sizeof(*pNew));
+    sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+  }
+  return rc;
+}
+
+/*
+** This method is the destructor for series_cursor objects.
+*/
+static int seriesDisconnect(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return SQLITE_OK;
+}
+
+/*
+** Constructor for a new series_cursor object.
+*/
+static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){
+  series_cursor *pCur;
+  (void)pUnused;
+  pCur = sqlite3_malloc( sizeof(*pCur) );
+  if( pCur==0 ) return SQLITE_NOMEM;
+  memset(pCur, 0, sizeof(*pCur));
+  *ppCursor = &pCur->base;
+  return SQLITE_OK;
+}
+
+/*
+** Destructor for a series_cursor.
+*/
+static int seriesClose(sqlite3_vtab_cursor *cur){
+  sqlite3_free(cur);
+  return SQLITE_OK;
+}
+
+
+/*
+** Advance a series_cursor to its next row of output.
+*/
+static int seriesNext(sqlite3_vtab_cursor *cur){
+  series_cursor *pCur = (series_cursor*)cur;
+  if( pCur->iValue==pCur->iTerm ){
+    pCur->bDone = 1;
+  }else if( pCur->bDesc ){
+    pCur->iValue = sub64(pCur->iValue, pCur->iStep);
+    assert( pCur->iValue>=pCur->iTerm );
+  }else{
+    pCur->iValue = add64(pCur->iValue, pCur->iStep);
+    assert( pCur->iValue<=pCur->iTerm );
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the series_cursor
+** is currently pointing.
+*/
+static int seriesColumn(
+  sqlite3_vtab_cursor *cur,   /* The cursor */
+  sqlite3_context *ctx,       /* First argument to sqlite3_result_...() */
+  int i                       /* Which column to return */
+){
+  series_cursor *pCur = (series_cursor*)cur;
+  sqlite3_int64 x = 0;
+  switch( i ){
+    case SERIES_COLUMN_START:  x = pCur->iOBase;     break;
+    case SERIES_COLUMN_STOP:   x = pCur->iOTerm;     break;
+    case SERIES_COLUMN_STEP:   x = pCur->iOStep;     break;
+    default:                   x = pCur->iValue;     break;
+  }
+  sqlite3_result_int64(ctx, x);
+  return SQLITE_OK;
+}
+
+#ifndef LARGEST_UINT64
+#define LARGEST_INT64  ((sqlite3_int64)0x7fffffffffffffffLL)
+#define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL)
+#define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL)
+#endif
+
+/*
+** The rowid is the same as the value.
+*/
+static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+  series_cursor *pCur = (series_cursor*)cur;
+  *pRowid = pCur->iValue;
+  return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int seriesEof(sqlite3_vtab_cursor *cur){
+  series_cursor *pCur = (series_cursor*)cur;
+  return pCur->bDone;
+}
+
+/* True to cause run-time checking of the start=, stop=, and/or step=
+** parameters.  The only reason to do this is for testing the
+** constraint checking logic for virtual tables in the SQLite core.
+*/
+#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY
+# define SQLITE_SERIES_CONSTRAINT_VERIFY 0
+#endif
+
+/*
+** Return the number of steps between pCur->iBase and pCur->iTerm if
+** the step width is pCur->iStep.
+*/
+static sqlite3_uint64 seriesSteps(series_cursor *pCur){
+  if( pCur->bDesc ){
+    assert( pCur->iBase >= pCur->iTerm );
+    return span64(pCur->iBase, pCur->iTerm)/pCur->iStep;
+  }else{
+    assert( pCur->iBase <= pCur->iTerm );
+    return span64(pCur->iTerm, pCur->iBase)/pCur->iStep;
+  }
+}
+
+#if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32)
+/*
+** Case 1 (the most common case):
+** The standard math library is available so use ceil() and floor() from there.
+*/
+static double seriesCeil(double r){ return ceil(r); }
+static double seriesFloor(double r){ return floor(r); }
+#elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
+/*
+** Case 2 (2nd most common): Use GCC/Clang builtins
+*/
+static double seriesCeil(double r){ return __builtin_ceil(r); }
+static double seriesFloor(double r){ return __builtin_floor(r); }
+#else
+/*
+** Case 3 (rarely happens): Use home-grown ceil() and floor() routines.
+*/
+static double seriesCeil(double r){
+  sqlite3_int64 x;
+  if( r!=r ) return r;
+  if( r<=(-4503599627370496.0) ) return r;
+  if( r>=(+4503599627370496.0) ) return r;
+  x = (sqlite3_int64)r;
+  if( r==(double)x ) return r;
+  if( r>(double)x ) x++;
+  return (double)x;
+}
+static double seriesFloor(double r){
+  sqlite3_int64 x;
+  if( r!=r ) return r;
+  if( r<=(-4503599627370496.0) ) return r;
+  if( r>=(+4503599627370496.0) ) return r;
+  x = (sqlite3_int64)r;
+  if( r==(double)x ) return r;
+  if( r<(double)x ) x--;
+  return (double)x;
+}
+#endif
+
+/*
+** This method is called to "rewind" the series_cursor object back
+** to the first row of output.  This method is always called at least
+** once prior to any call to seriesColumn() or seriesRowid() or
+** seriesEof().
+**
+** The query plan selected by seriesBestIndex is passed in the idxNum
+** parameter.  (idxStr is not used in this implementation.)  idxNum
+** is a bitmask showing which constraints are available:
+**
+**   0x0001:    start=VALUE
+**   0x0002:    stop=VALUE
+**   0x0004:    step=VALUE
+**   0x0008:    descending order
+**   0x0010:    ascending order
+**   0x0020:    LIMIT  VALUE
+**   0x0040:    OFFSET  VALUE
+**   0x0080:    value=VALUE
+**   0x0100:    value>=VALUE
+**   0x0200:    value>VALUE
+**   0x1000:    value<=VALUE
+**   0x2000:    value<VALUE
+**
+** This routine should initialize the cursor and position it so that it
+** is pointing at the first row, or pointing off the end of the table
+** (so that seriesEof() will return true) if the table is empty.
+*/
+static int seriesFilter(
+  sqlite3_vtab_cursor *pVtabCursor,
+  int idxNum, const char *idxStrUnused,
+  int argc, sqlite3_value **argv
+){
+  series_cursor *pCur = (series_cursor *)pVtabCursor;
+  int iArg = 0;                         /* Arguments used so far */
+  int i;                                /* Loop counter */
+  sqlite3_int64 iMin = SMALLEST_INT64;  /* Smallest allowed output value */
+  sqlite3_int64 iMax = LARGEST_INT64;   /* Largest allowed output value */
+  sqlite3_int64 iLimit = 0;             /* if >0, the value of the LIMIT */
+  sqlite3_int64 iOffset = 0;            /* if >0, the value of the OFFSET */
+
+  (void)idxStrUnused;
+
+  /* If any constraints have a NULL value, then return no rows.
+  ** See ticket https://sqlite.org/src/info/fac496b61722daf2
+  */
+  for(i=0; i<argc; i++){
+    if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
+      goto series_no_rows;
+    }
+  }
+
+  /* Capture the three HIDDEN parameters to the virtual table and insert
+  ** default values for any parameters that are omitted.
+  */
+  if( idxNum & 0x01 ){
+    pCur->iOBase = sqlite3_value_int64(argv[iArg++]);
+  }else{
+    pCur->iOBase = 0;
+  }
+  if( idxNum & 0x02 ){
+    pCur->iOTerm = sqlite3_value_int64(argv[iArg++]);
+  }else{
+    pCur->iOTerm = 0xffffffff;
+  }
+  if( idxNum & 0x04 ){
+    pCur->iOStep = sqlite3_value_int64(argv[iArg++]);
+    if( pCur->iOStep==0 ) pCur->iOStep = 1;
+  }else{
+    pCur->iOStep = 1;
+  }
+
+  /* If there are constraints on the value column but there are
+  ** no constraints on  the start, stop, and step columns, then
+  ** initialize the default range to be the entire range of 64-bit signed
+  ** integers.  This range will contracted by the value column constraints
+  ** further below.
+  */
+  if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){
+    pCur->iOBase = SMALLEST_INT64;
+  }
+  if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){
+    pCur->iOTerm = LARGEST_INT64;
+  }
+  pCur->iBase = pCur->iOBase;
+  pCur->iTerm = pCur->iOTerm;
+  if( pCur->iOStep>0 ){  
+    pCur->iStep = pCur->iOStep;
+  }else if( pCur->iOStep>SMALLEST_INT64 ){
+    pCur->iStep = -pCur->iOStep;
+  }else{
+    pCur->iStep = LARGEST_INT64;
+    pCur->iStep++;
+  }
+  pCur->bDesc = pCur->iOStep<0;
+  if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){
+    goto series_no_rows;
+  }
+  if( pCur->bDesc!=0 && pCur->iBase<pCur->iTerm ){
+    goto series_no_rows;
+  }
+
+  /* Extract the LIMIT and OFFSET values, but do not apply them yet.
+  ** The range must first be constrained by the limits on value.
+  */
+  if( idxNum & 0x20 ){
+    iLimit = sqlite3_value_int64(argv[iArg++]);
+    if( idxNum & 0x40 ){
+      iOffset = sqlite3_value_int64(argv[iArg++]);
+    }
+  }
+
+  /* Narrow the range of iMin and iMax (the minimum and maximum outputs)
+  ** based on equality and inequality constraints on the "value" column.
+  */
+  if( idxNum & 0x3380 ){
+    if( idxNum & 0x0080 ){    /* value=X */
+      if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
+        double r = sqlite3_value_double(argv[iArg++]);
+        if( r==seriesCeil(r)
+         && r>=(double)SMALLEST_INT64
+         && r<=(double)LARGEST_INT64
+        ){
+          iMin = iMax = (sqlite3_int64)r;
+        }else{
+          goto series_no_rows;
+        }
+      }else{
+        iMin = iMax = sqlite3_value_int64(argv[iArg++]);
+      }
+    }else{
+      if( idxNum & 0x0300 ){  /* value>X or value>=X */
+        if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
+          double r = sqlite3_value_double(argv[iArg++]);
+          if( r<(double)SMALLEST_INT64 ){
+            iMin = SMALLEST_INT64;
+          }else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){
+            iMin = (sqlite3_int64)seriesCeil(r+1.0);
+          }else{
+            iMin = (sqlite3_int64)seriesCeil(r);
+          }
+        }else{
+          iMin = sqlite3_value_int64(argv[iArg++]);
+          if( (idxNum & 0x0200)!=0 ){
+            if( iMin==LARGEST_INT64 ){
+              goto series_no_rows;
+            }else{
+              iMin++;
+            }
+          }
+        }
+      }
+      if( idxNum & 0x3000 ){   /* value<X or value<=X */
+        if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
+          double r = sqlite3_value_double(argv[iArg++]);
+          if( r>(double)LARGEST_INT64 ){
+            iMax = LARGEST_INT64;
+          }else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){
+            iMax = (sqlite3_int64)(r-1.0);
+          }else{
+            iMax = (sqlite3_int64)seriesFloor(r);
+          }
+        }else{
+          iMax = sqlite3_value_int64(argv[iArg++]);
+          if( idxNum & 0x2000 ){
+            if( iMax==SMALLEST_INT64 ){
+              goto series_no_rows;
+            }else{
+              iMax--;
+            }
+          }
+        }
+      }
+      if( iMin>iMax ){
+        goto series_no_rows;
+      }
+    }
+
+    /* Try to reduce the range of values to be generated based on
+    ** constraints on the "value" column.
+    */
+    if( pCur->bDesc==0 ){
+      if( pCur->iBase<iMin ){
+        sqlite3_uint64 span = span64(iMin,pCur->iBase);
+        pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep);
+        if( pCur->iBase<iMin ){
+          if( pCur->iBase > sub64(LARGEST_INT64, pCur->iStep) ){
+            goto series_no_rows;
+          }
+          pCur->iBase = add64(pCur->iBase, pCur->iStep);
+        }
+      }
+      if( pCur->iTerm>iMax ){
+        pCur->iTerm = iMax;
+      }
+    }else{
+      if( pCur->iBase>iMax ){
+        sqlite3_uint64 span = span64(pCur->iBase,iMax);
+        pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep);
+        if( pCur->iBase>iMax ){
+          if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){
+            goto series_no_rows;
+          }
+          pCur->iBase = sub64(pCur->iBase, pCur->iStep);
+        }
+      }
+      if( pCur->iTerm<iMin ){
+        pCur->iTerm = iMin;
+      }
+    }
+  }
+
+  /* Adjust iTerm so that it is exactly the last value of the series.
+  */
+  if( pCur->bDesc==0 ){
+    if( pCur->iBase>pCur->iTerm ){
+      goto series_no_rows;
+    }
+    pCur->iTerm = sub64(pCur->iTerm,
+                        span64(pCur->iTerm,pCur->iBase) % pCur->iStep);
+  }else{
+    if( pCur->iBase<pCur->iTerm ){
+      goto series_no_rows;
+    }
+    pCur->iTerm = add64(pCur->iTerm,
+                        span64(pCur->iBase,pCur->iTerm) % pCur->iStep);
+  }
+
+  /* Transform the series generator to output values in the requested
+  ** order.
+  */
+  if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0)
+   || ((idxNum & 0x0010)!=0 && pCur->bDesc!=0)
+  ){
+    sqlite3_int64 tmp = pCur->iBase;
+    pCur->iBase = pCur->iTerm;
+    pCur->iTerm = tmp;
+    pCur->bDesc = !pCur->bDesc;
+  }
+
+  /* Apply LIMIT and OFFSET constraints, if any */
+  assert( pCur->iStep!=0 );
+  if( idxNum & 0x20 ){
+    if( iOffset>0 ){
+      if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){
+        goto series_no_rows;
+      }else if( pCur->bDesc ){
+        pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset);
+      }else{
+        pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset);
+      }
+    }
+    if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){
+      pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep);
+    }
+  }
+  pCur->iValue = pCur->iBase;
+  pCur->bDone = 0;
+  return SQLITE_OK;
+
+series_no_rows:
+  pCur->iBase = 0;
+  pCur->iTerm = 0;
+  pCur->iStep = 1;
+  pCur->bDesc = 0;
+  pCur->bDone = 1;
+  return SQLITE_OK;  
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the generate_series virtual table.  This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+**
+** In this implementation idxNum is used to represent the
+** query plan.  idxStr is unused.
+**
+** The query plan is represented by bits in idxNum:
+**
+**   0x0001  start = $num
+**   0x0002  stop = $num
+**   0x0004  step = $num
+**   0x0008  output is in descending order
+**   0x0010  output is in ascending order
+**   0x0020  LIMIT $num
+**   0x0040  OFFSET $num
+**   0x0080  value = $num
+**   0x0100  value >= $num
+**   0x0200  value > $num
+**   0x1000  value <= $num
+**   0x2000  value < $num
+**
+** Only one of 0x0100 or 0x0200 will be returned.  Similarly, only
+** one of 0x1000 or 0x2000 will be returned.  If the 0x0080 is set, then
+** none of the 0xff00 bits will be set.
+**
+** The order of parameters passed to xFilter is as follows:
+**
+**    * The argument to start= if bit 0x0001 is in the idxNum mask
+**    * The argument to stop= if bit 0x0002 is in the idxNum mask
+**    * The argument to step= if bit 0x0004 is in the idxNum mask
+**    * The argument to LIMIT if bit 0x0020 is in the idxNum mask
+**    * The argument to OFFSET if bit 0x0040 is in the idxNum mask
+**    * The argument to value=, or value>= or value> if any of
+**      bits 0x0380 are in the idxNum mask
+**    * The argument to value<= or value< if either of bits 0x3000
+**      are in the mask
+**
+*/
+static int seriesBestIndex(
+  sqlite3_vtab *pVTab,
+  sqlite3_index_info *pIdxInfo
+){
+  int i, j;              /* Loop over constraints */
+  int idxNum = 0;        /* The query plan bitmask */
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
+  int bStartSeen = 0;    /* EQ constraint seen on the START column */
+#endif
+  int unusableMask = 0;  /* Mask of unusable constraints */
+  int nArg = 0;          /* Number of arguments that seriesFilter() expects */
+  int aIdx[7];           /* Constraints on start, stop, step, LIMIT, OFFSET,
+                         ** and value.  aIdx[5] covers value=, value>=, and
+                         ** value>,  aIdx[6] covers value<= and value< */
+  const struct sqlite3_index_constraint *pConstraint;
+
+  /* This implementation assumes that the start, stop, and step columns
+  ** are the last three columns in the virtual table. */
+  assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
+  assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
+
+  aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = aIdx[5] = aIdx[6] = -1;
+  pConstraint = pIdxInfo->aConstraint;
+  for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+    int iCol;    /* 0 for start, 1 for stop, 2 for step */
+    int iMask;   /* bitmask for those column */
+    int op = pConstraint->op;
+    if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT
+     && op<=SQLITE_INDEX_CONSTRAINT_OFFSET
+    ){
+      if( pConstraint->usable==0 ){
+        /* do nothing */
+      }else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){
+        aIdx[3] = i;
+        idxNum |= 0x20;
+      }else{
+        assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
+        aIdx[4] = i;
+        idxNum |= 0x40;
+      }
+      continue;
+    }
+    if( pConstraint->iColumn<SERIES_COLUMN_START ){
+      if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
+           pConstraint->iColumn==SERIES_COLUMN_ROWID)
+       && pConstraint->usable
+      ){
+        switch( op ){
+          case SQLITE_INDEX_CONSTRAINT_EQ:
+          case SQLITE_INDEX_CONSTRAINT_IS: {
+            idxNum |=  0x0080;
+            idxNum &= ~0x3300;
+            aIdx[5] = i;
+            aIdx[6] = -1;
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
+            bStartSeen = 1;
+#endif
+            break;
+          }
+          case SQLITE_INDEX_CONSTRAINT_GE: {
+            if( idxNum & 0x0080 ) break;
+            idxNum |=  0x0100;
+            idxNum &= ~0x0200;
+            aIdx[5] = i;
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
+            bStartSeen = 1;
+#endif
+            break;
+          }
+          case SQLITE_INDEX_CONSTRAINT_GT: {
+            if( idxNum & 0x0080 ) break;
+            idxNum |=  0x0200;
+            idxNum &= ~0x0100;
+            aIdx[5] = i;
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
+            bStartSeen = 1;
+#endif
+            break;
+          }
+          case SQLITE_INDEX_CONSTRAINT_LE: {
+            if( idxNum & 0x0080 ) break;
+            idxNum |=  0x1000;
+            idxNum &= ~0x2000;
+            aIdx[6] = i;
+            break;
+          }
+          case SQLITE_INDEX_CONSTRAINT_LT: {
+            if( idxNum & 0x0080 ) break;
+            idxNum |=  0x2000;
+            idxNum &= ~0x1000;
+            aIdx[6] = i;
+            break;
+          }
+        }
+      }
+      continue;
+    }
+    iCol = pConstraint->iColumn - SERIES_COLUMN_START;
+    assert( iCol>=0 && iCol<=2 );
+    iMask = 1 << iCol;
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
+    if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){
+      bStartSeen = 1;
+    }
+#endif
+    if( pConstraint->usable==0 ){
+      unusableMask |=  iMask;
+      continue;
+    }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){
+      idxNum |= iMask;
+      aIdx[iCol] = i;
+    }
+  }
+  if( aIdx[3]==0 ){
+    /* Ignore OFFSET if LIMIT is omitted */
+    idxNum &= ~0x60;
+    aIdx[4] = 0;
+  }
+  for(i=0; i<7; i++){
+    if( (j = aIdx[i])>=0 ){
+      pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
+      pIdxInfo->aConstraintUsage[j].omit =
+         !SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3;
+    }
+  }
+  /* The current generate_column() implementation requires at least one
+  ** argument (the START value).  Legacy versions assumed START=0 if the
+  ** first argument was omitted.  Compile with -DZERO_ARGUMENT_GENERATE_SERIES
+  ** to obtain the legacy behavior */
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
+  if( !bStartSeen ){
+    sqlite3_free(pVTab->zErrMsg);
+    pVTab->zErrMsg = sqlite3_mprintf(
+        "first argument to \"generate_series()\" missing or unusable");
+    return SQLITE_ERROR;
+  }
+#endif
+  if( (unusableMask & ~idxNum)!=0 ){
+    /* The start, stop, and step columns are inputs.  Therefore if there
+    ** are unusable constraints on any of start, stop, or step then
+    ** this plan is unusable */
+    return SQLITE_CONSTRAINT;
+  }
+  if( (idxNum & 0x03)==0x03 ){
+    /* Both start= and stop= boundaries are available.  This is the 
+    ** the preferred case */
+    pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
+    pIdxInfo->estimatedRows = 1000;
+    if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){
+      if( pIdxInfo->aOrderBy[0].desc ){
+        idxNum |= 0x08;
+      }else{
+        idxNum |= 0x10;
+      }
+      pIdxInfo->orderByConsumed = 1;
+    }
+  }else if( (idxNum & 0x21)==0x21 ){
+    /* We have start= and LIMIT */
+    pIdxInfo->estimatedRows = 2500;
+  }else{
+    /* If either boundary is missing, we have to generate a huge span
+    ** of numbers.  Make this case very expensive so that the query
+    ** planner will work hard to avoid it. */
+    pIdxInfo->estimatedRows = 2147483647;
+  }
+  pIdxInfo->idxNum = idxNum;
+#ifdef SQLITE_INDEX_SCAN_HEX
+  pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_HEX;
+#endif
+  return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the 
+** generate_series virtual table.
+*/
+static sqlite3_module seriesModule = {
+  0,                         /* iVersion */
+  0,                         /* xCreate */
+  seriesConnect,             /* xConnect */
+  seriesBestIndex,           /* xBestIndex */
+  seriesDisconnect,          /* xDisconnect */
+  0,                         /* xDestroy */
+  seriesOpen,                /* xOpen - open a cursor */
+  seriesClose,               /* xClose - close a cursor */
+  seriesFilter,              /* xFilter - configure scan constraints */
+  seriesNext,                /* xNext - advance a cursor */
+  seriesEof,                 /* xEof - check for end of scan */
+  seriesColumn,              /* xColumn - read data */
+  seriesRowid,               /* xRowid - read data */
+  0,                         /* xUpdate */
+  0,                         /* xBegin */
+  0,                         /* xSync */
+  0,                         /* xCommit */
+  0,                         /* xRollback */
+  0,                         /* xFindMethod */
+  0,                         /* xRename */
+  0,                         /* xSavepoint */
+  0,                         /* xRelease */
+  0,                         /* xRollbackTo */
+  0,                         /* xShadowName */
+  0                          /* xIntegrity */
+};
+
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_series_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  int rc = SQLITE_OK;
+  (void)pApi;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){
+    *pzErrMsg = sqlite3_mprintf(
+        "generate_series() requires SQLite 3.8.12 or later");
+    return SQLITE_ERROR;
+  }
+  rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
+#endif
+  return rc;
+}
+#define CMPP_D_DEMO
+/*
+** 2025-10-18:
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**  * May you do good and not evil.
+**  * May you find forgiveness for yourself and forgive others.
+**  * May you share freely, never taking more than you give.
+**
+************************************************************************
+**
+** This file contains demonstration client-side directives for the
+** c-pp API.
+*/
+#if defined(CMPP_D_DEMO)
+/* Only when building with the main c-pp app. */
+#elif !defined(CMPP_MODULE_REGISTER1)
+/**
+  Assume a standalone module build. Arrange for the default module
+  entry point to be installed so that cmpp_module_load() does not
+  require that the user know the entry point name.
+*/
+#define CMPP_MODULE_STANDALONE
+#define CMPP_API_THUNK
+#include "libcmpp.h"
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+/**
+   cmpp_d_autoload_f() impl for this file's directives and its close
+   friends.
+*/
+int cmpp_d_autoload_f_demos(cmpp *pp, char const *dname, void *state);
+/**
+   Registers demo and utility directives with pp.
+*/
+int cmpp_module__demo_register(cmpp *pp);
+
+/**
+   Simply says hello and emits info about its arguments.
+*/
+static void cmpp_dx_f_demo1(cmpp_dx *dx){
+  cmpp_dx_outf(dx, "Hello from %s%s\n",
+               cmpp_dx_delim(dx), dx->d->name.z);
+  for( cmpp_arg const * a = dx->args.arg0;
+       0==cmpp_dx_err_check(dx) && a;
+       a = a->next ){
+    cmpp_dx_outf(dx, "arg type=%s n=%u z=%.*s\n",
+                 cmpp_tt_cstr(a->ttype),
+                 (unsigned)a->n, (int)a->n, a->z);
+  }
+}
+
+/**
+   Internal helper for other directives.  Emits an HTML <div> tag. If
+   passed any arguments, each is assumed to be a CSS class name and is
+   applied to the DIV. This does _not_ emit the lcosing DIV
+   tag. Returns 0 on success.
+*/
+static int divOpener(cmpp_dx *dx){
+  cmpp_dx_out_raw(dx, "<div", 4);
+  int nClass = 0;
+  for( cmpp_arg const * a = dx->args.arg0;
+       0==cmpp_dx_err_check(dx) && a;
+       a = a->next ){
+    if( 1==++nClass ){
+      cmpp_dx_out_raw(dx, " class='", 8);
+    }else{
+      cmpp_dx_out_raw(dx, " ", 1);
+    }
+    cmpp_dx_out_raw(dx, a->z, a->n);
+  }
+  return cmpp_dx_out_raw(dx, nClass ? "'>" : ">", 1 + !!nClass);
+}
+
+/**
+   Opens an HTML DIV tag, as per divOpener().
+*/
+static void cmpp_dx_f_divOpen(cmpp_dx *dx){
+  if( 0==divOpener(dx) ){
+    int * const nDiv = dx->d->impl.state;
+    assert( nDiv );
+    ++*nDiv;
+  }
+}
+
+/**
+   Closes an HTML DIV tag which was opened by cmpp_dx_f_divOpen().
+*/
+static void cmpp_dx_f_divClose(cmpp_dx *dx){
+  int * const nDiv = dx->d->impl.state;
+  assert( nDiv );
+  if( *nDiv > 0 ){
+    --*nDiv;
+  }else{
+    char const * const zDelim = cmpp_dx_delim(dx);
+    cmpp_dx_err_set(
+      dx, CMPP_RC_MISUSE,
+      "%s/%s was used without an opening %s%s directive",
+      zDelim, dx->d->name.z, zDelim, dx->d->name.z
+    );
+  }
+}
+
+/**
+   Another HTML DIV-inspired wrapper which consumes a block of input
+   and wraps it in a DIV. This is functionally the same as the
+   divOpen/divClose examples but demonstrates how to slurp up the
+   content between the open/close directives from within the opening
+   directive's callback.
+*/
+static void cmpp_dx_f_divWrapper(cmpp_dx *dx){
+  if( divOpener(dx) ) return;
+  cmpp_b os = {0};
+  if( 0==cmpp_dx_consume_b(
+        dx, &os, &dx->d->closer, 1,
+        cmpp_dx_consume_F_PROCESS_OTHER_D
+      )
+      /* ^^^ says "read to the matching #/div" accounting for, and
+         processing, nested directives). The #/div closing tag is
+         identified by dx->d->closer. */
+  ){
+    cmpp_b_chomp( &os );
+    /* Recall that most cmpp APIs become no-ops if dx->pp has an error
+       set, so we don't strictly need to error-check these calls: */
+    cmpp_dx_out_raw(dx, os.z, os.n);
+    cmpp_dx_out_raw(dx, "</div>\n", 7);
+  }
+  cmpp_b_clear(&os);
+}
+
+/**
+   A cmpp_d_autoload_f() impl for testing and demonstration
+   purposes.
+*/
+int cmpp_d_autoload_f_demos(cmpp *pp, char const *dname, void *state){
+  (void)state;
+  cmpp_api_init(pp);
+
+#define M(NAME) (0==strcmp(NAME,dname))
+#define MOC(NAME) (M(NAME) || M("/"NAME))
+
+#define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS)  \
+  cmpp_d_reg SYMNAME = {  \
+    .name = NAME,         \
+    .opener = {           \
+      .f = OPENER,        \
+      .flags = OFLAGS     \
+    },                    \
+    .closer = {           \
+      .f = CLOSER,        \
+      .flags = CFLAGS     \
+    },                    \
+    .dtor = 0,            \
+    .state = 0            \
+  }
+
+#define CHECK(NAME,CHECKCLOSER,OPENER,OFLAGS,CLOSER,CFLAGS) \
+  if( M(NAME) || (CHECKCLOSER && M("/"NAME)) ){             \
+    DREG0(reg,NAME,OPENER,OFLAGS,CLOSER,CFLAGS);            \
+    return cmpp_d_register(pp, &reg, NULL);     \
+  } (void)0
+
+
+  CHECK("demo1", 0, cmpp_dx_f_demo1, cmpp_d_F_ARGS_LIST, 0, 0);
+
+  CHECK("demo-div-wrapper", 1, cmpp_dx_f_divWrapper,
+        cmpp_d_F_ARGS_LIST | cmpp_d_F_NO_CALL,
+        cmpp_dx_f_dangling_closer, 0);
+
+  if( MOC("demo-div") ){
+    cmpp_d_reg const r = {
+      .name = "demo-div",
+      .opener = {
+        .f = cmpp_dx_f_divOpen,
+        .flags = cmpp_d_F_ARGS_LIST | cmpp_d_F_NO_CALL
+      },
+      .closer = {
+        .f = cmpp_dx_f_divClose
+      },
+      .state = cmpp_malloc(sizeof(int)),
+      .dtor = cmpp_mfree
+    };
+    /* State for one of the custom directives. */;
+    int const rc = cmpp_d_register(pp, &r, NULL);
+    if( 0==rc ){
+      *((int*)r.state) = 0
+        /* else reg.state was freed by cmpp_d_register() */;
+    }
+    return rc;
+  }
+
+#undef M
+#undef MOC
+#undef CHECK
+#undef DREG0
+  return CMPP_RC_NO_DIRECTIVE;
+}
+
+int cmpp_module__demo_register(cmpp *pp){
+  cmpp_api_init(pp);
+  int rc;
+#define X(D) \
+  rc = cmpp_d_autoload_f_demos(pp, D, NULL);    \
+  if( rc && CMPP_RC_NO_DIRECTIVE!=rc ) goto end
+  X("demo1");
+  X("demo-div");
+  X("demo-div-wrapper");
+#undef X
+end:
+  return cmpp_err_get(pp, NULL);
+}
+CMPP_MODULE_REGISTER1(demo);
+#endif /* include guard */
index 71d827a35fff05a8c077363635405032af3e4097..37153d84fd27c117f42fe239099574f76a7ac193 100644 (file)
@@ -1,5 +1,5 @@
 <!doctype html>
-//#@policy error
+//#@ policy error
 <html lang="en-us">
   <head>
     <meta charset="utf-8">
index 4bb53ee56975b0965db90986f3af8afda6672a8e..8424cc85c190d3988395aaf2af5aabb8a90e34b2 100644 (file)
@@ -1,5 +1,5 @@
 <!doctype html>
-//#@policy error
+//#@ policy error
 <html lang="en-us">
   <head>
     <meta charset="utf-8">
@@ -39,6 +39,6 @@
 //#else
     <script src="@sqlite3.js@"></script>
     <script src="@tester1.js@"></script>
-//#endif
+//#/if
   </body>
 </html>
index 961c8d8b627f6ec0970784d3f3c89bdb5f7d08da..72b9ec47fd6d64698a3b016514b9718e73341b44 100644 (file)
 
      ./c-pp -f tester1.c-pp.js -o tester1-esm.mjs -Dtarget:es6-module
 */
-//#@policy error
+//#@ policy error
 //#if target:es6-module
 import {default as sqlite3InitModule} from "@sqlite3.js@";
 globalThis.sqlite3InitModule = sqlite3InitModule;
 //#else
 'use strict';
-//#endif
+//#/if
 (function(self){
   /**
      Set up our output channel differently depending
@@ -452,7 +452,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
     tryKey('hexkey', hexFoo, 6);
     dbUnlink();
   };
-//#endif enable-see
+//#/if enable-see
 
   /* Tests common to "opfs" and "opfs-wl".  These tests manipulate
      "this" and must run in order.
@@ -3605,7 +3605,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
                        ()=>JDb.clearStorage('session'));
       }
     })/*kvvfs with SEE*/
-//#endif enable-see
+//#/if enable-see
   ;/* end kvvfs tests */
 
   ////////////////////////////////////////////////////////////////////////
@@ -3926,7 +3926,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
         );
       }
     })
-//#endif enable-see
+//#/if enable-see
   ;/* end OPFS tests */
 
   ////////////////////////////////////////////////////////////////////////
@@ -3960,7 +3960,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
         );
       }
     })
-//#endif enable-see
+//#/if enable-see
   ;/* end OPFS tests */
 
   ////////////////////////////////////////////////////////////////////////
@@ -4170,7 +4170,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
         poolUtil.removeVfs();
       }
     })/*opfs-sahpool with SEE*/
-//#endif enable-see
+//#/if enable-see
   ;
 
   ////////////////////////////////////////////////////////////////////////
@@ -4532,7 +4532,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
     }
     importScripts(sqlite3Js);
   }
-//#endif
+//#/if
   globalThis.sqlite3InitModule.__isUnderTest =
     true /* disables certain API-internal cleanup so that we can
             test internal APIs from here */;
index 3f24be31305cd48f03b5f6d794f7e6a1b788fef2..419c40ae45e0cb736e8f69043e457bd7cf49a18e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C JS\sdoc\supdates.
-D 2026-03-08T12:42:34.028
+C Upgrade\sext/wasm/c-pp-lite.c\sto\sits\snewer\ssibling\sbecause\swe've\sreached\sthe\solder\sone's\slimits.\sThis\srenames\sc-pp-lite.c\sto\slibcmpp.c\sto\smaintain\sboth\sthe\sSCM-\sand\scode\slineage\sbut\sthat\smay\send\sup\slooking\sa\sbit\sweird\sbecause\sthe\sdiff\sbetween\sthe\stwo\sis\svast.
+D 2026-03-08T16:03:08.690
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -572,7 +572,7 @@ F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009e
 F ext/session/sqlite3session.c 6ebd02be470f36d41c4bd78927f39d507b62051ba025eacaed9936c769902a07
 F ext/session/sqlite3session.h 7404723606074fcb2afdc6b72c206072cdb2b7d8ba097ca1559174a80bc26f7a
 F ext/session/test_session.c 190110e3bd9463717248dec1272b44fe9943e57b7646d0b4200dcf11e4dccee6
-F ext/wasm/GNUmakefile 4496669ca5785c7bfc7af76565f1dd45e8f9ec7529b58c485fd374088162bef1
+F ext/wasm/GNUmakefile 68c750f173106d9d63f12c1edf1256c6f4bad9894b155da5db64322f4912de4b
 F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
 F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259
 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff
@@ -580,31 +580,30 @@ F ext/wasm/SQLTester/SQLTester.mjs 6b3c52ed36a5573ca4883176f326332a8d4c0cecf5efd
 F ext/wasm/SQLTester/SQLTester.run.mjs 57f2adb33f43f2784abbf8026c1bfd049d8013af1998e7dcb8b50c89ffc332e0
 F ext/wasm/SQLTester/index.html 64f3435084c7d6139b08d1f2a713828a73f68de2ae6a3112cbb5980d991ba06f
 F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536
-F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp 6ad5ace0a16b3300e8d40d4e596eee1c773424e22d57f9b8d37cbe87a101638a
+F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp de2ce128aebeff9ef4161cff7a0ff0089be866121bcb8941ad44a38a65f1d436
 F ext/wasm/api/README.md a905d5c6bfc3e2df875bd391d6d6b7b48d41b43bdee02ad115b47244781a7e81
-F ext/wasm/api/extern-post-js.c-pp.js d9f42ecbedc784c0d086bc37800e52946a14f7a21600b291daa3f963c314f930
+F ext/wasm/api/extern-post-js.c-pp.js 80accc53cc6ea1e61c721595f42ba95baa7c7ea636807d9507e69403301f8c54
 F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
-F ext/wasm/api/opfs-common-inline.c-pp.js 5be8d6d91963849e218221b48206ae55612630bb2cd7f30b1b6fcf7a9e374b76
-F ext/wasm/api/opfs-common-shared.c-pp.js 49f4b044dfb3becd8c926ef42b7494da680e5ac878c6d10ef510b66f74ad55a1
+F ext/wasm/api/opfs-common-inline.c-pp.js 496ca858af09b7fef2efaece467960611d35f57254078424bcdeac42ded9e85d
+F ext/wasm/api/opfs-common-shared.c-pp.js d0bff21d34fa05a7eb975c4ee608165f96387332101cb05e976953c77434374d
 F ext/wasm/api/post-js-footer.js a50c1a2c4d008aede7b2aa1f18891a7ee71437c2f415b8aeb3db237ddce2935b
 F ext/wasm/api/post-js-header.js f35d2dcf1ab7f22a93d565f8e0b622a2934fc4e743edf3b708e4dd8140eeff55
-F ext/wasm/api/pre-js.c-pp.js 9234ea680a2f6a2a177e8dcd934bdc5811a9f8409165433a252b87f4c07bba6f
-F ext/wasm/api/sqlite3-api-glue.c-pp.js 7afb3da3510facafd94ce31133ec847d0d4db5b2b5e4325941803fd3bca07c16
-F ext/wasm/api/sqlite3-api-oo1.c-pp.js 45454631265d9ce82685f1a64e1650ee19c8e121c41db98a22b534c15e543cfa
+F ext/wasm/api/pre-js.c-pp.js d6bf82f83f60caa2904bddb95a29cb738b310f672d2796cdc5fe54463ab0d6cd
+F ext/wasm/api/sqlite3-api-glue.c-pp.js 31a721ada7225838a61310a9f3f797fa5275353f8e9b0ae769d85b437be061f5
+F ext/wasm/api/sqlite3-api-oo1.c-pp.js 5f203f5bb5d48a9e43ec51e791dc411c24dca825842bfb6a2d979d871bf8cfaf
 F ext/wasm/api/sqlite3-api-prologue.js 4336f02ac24ba58e68e355adb9da2804a1eef15a4aee961fa813c7d812a56b54
-F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667430f9d45167569633ffc7567938
+F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1fa34e9b0e3b90a8898e4f700d7125e44c81877f182627bb8564b97989bc6e78
 F ext/wasm/api/sqlite3-license-version-header.js 98d90255a12d02214db634e041c8e7f2f133d9361a8ebf000ba9c9af4c6761cc
-F ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js c2236c9037ccac368be6eeed3e5f6ed3cefed0d20539c9092139f27a76aaf1ba
+F ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js 3f85af55609f1452b29c476e59893cbeed417c59560795f50911318e916083b1
 F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d
-F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js f907fa712d1c9d98a3c08393f3e97a664857782da11670e129d39e383f45f968
-F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js cf5fba74978d3e0983f68505d9695ee7836853855590a92ccc5a96e27db5350b
-F ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js 743650cfefed0df900171971065c2d618e5652177a98844ddb6695c70149968e
-F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 4b02d7063d00e2d4f42d4ca79a8a0bca1b275257e135bc154d35b701ed1d234a
+F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js 27fb135ba3b805b66c90a7333b56080345bf1db79335c3e48b6d01ad7aa09607
+F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 1c742ed92c0515c8ef97d2b132feb01168252bfd847fed5d52607d8a42d23c6a
+F ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js 0d95dc35dfb518f0cd7bac993289a7f0753fe39c60fd6f0a5742b3f55c6e6347
+F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 3da8fe72dc9e76614a9c102b92e777ce03f81d788b607701c828d8fcbac44b06
 F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 366596d8ff73d4cefb938bbe95bc839d503c3fab6c8335ce4bf52f0d8a7dee81
 F ext/wasm/api/sqlite3-wasm.c ddf9d435b2e901eaceb805ff694e9609c2f32b5cf89a4f164e734a6fa303fdd2
-F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js aa9715f661fb700459a5a6cb1c32a4d6a770723b47aa9ac0e16c2cf87d622a66
-F ext/wasm/api/sqlite3-worker1.c-pp.js bd0655687090e3b1657268a6a9cacde1ea2a734079d194e16dbbed9083e51b38
-F ext/wasm/c-pp-lite.c f38254fba42561728c2e4764a7ba8d68700091e7c2f4418112868c0daba16783
+F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 46919740f8176c10f691431a81c769c35657e3b537e2ed2bde691f9017b0ee8a
+F ext/wasm/api/sqlite3-worker1.c-pp.js 21b20d3b8d43471d1647123616a7e5435b1e4b6fa447714f91ffc7f950ea01e8
 F ext/wasm/common/SqliteTestUtil.js dae753b95e72248c4395d8de8359e0d055cd9928488e8dd84aef89e46d23b32e
 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
 F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f
@@ -615,18 +614,19 @@ F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b823
 F ext/wasm/demo-123.js c7b3cca50c55841c381a9ca4f9396e5bbdc6114273d0b10a43e378e32e7be5bf
 F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e
 F ext/wasm/demo-jsstorage.js 467cb4126ff679ebcdb112d100d073af26b9808d0a0b52d66a40e28f59c5099b
-F ext/wasm/demo-worker1-promiser.c-pp.html f73b0b98457e7fdad40d8353cb9b2919391da180f49549a86f3d58b4e5a010eb
-F ext/wasm/demo-worker1-promiser.c-pp.js f40ec65810048e368896be71461028bd10de01e24277208c59266edf23bb9f52
+F ext/wasm/demo-worker1-promiser.c-pp.html 38e917d4dafa75b4aa0e9e1663abe734c1f701f6da141318bd01ffe3f6e47a77
+F ext/wasm/demo-worker1-promiser.c-pp.js d210aa1e8a74ea6244fe2290de5710bb55b3a9ed681aaba04e38ceb66550f417
 F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d
 F ext/wasm/demo-worker1.js fdfa90aa9d6b402bfed802cf1595fe4da6cc834ac38c8ff854bf1ee01f5ff9bb
 F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f
 F ext/wasm/fiddle/fiddle-worker.js 6c72acac2d381480bc9f5eb538e3f2faf2c1f72dd4fcbd05d3b409818a9a8fd5
 F ext/wasm/fiddle/fiddle.js 84fd75967e0af8b69d3dd849818342227d0f81d13db92e0dcbc63649b31a4893
-F ext/wasm/fiddle/index.c-pp.html 72c7e5517217960b3809648429ea396a7cbad0ffb2c92f6a2f5703abecb27317
+F ext/wasm/fiddle/index.c-pp.html 02f063ef30b8124f311029855c4439e77bc6505d1bf65a163d88c064a63ee9d9
 F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290
 F ext/wasm/index.html 5bf6cf1b0a3c8b9f5f54d77f2219d7ae87a15162055ce308109c49b1dcab4239
 F ext/wasm/jaccwabyt/jaccwabyt.js 4e2b797dc170851c9c530c3567679f4aa509eec0fab73b466d945b00b356574b
 F ext/wasm/jaccwabyt/jaccwabyt.md 6aa90fa1a973d0ad10d077088bea163b241d8470c75eafdef87620a1de1dea41
+F ext/wasm/libcmpp.c 0e2cec7de7c9cbe7941e6a4dc0c123cec736578a3eeae7828ac18706acd3cea6 w ext/wasm/c-pp-lite.c
 F ext/wasm/mkdist.sh f8883b077a2ca47cf92e6f0ce305fbf72ca648c3501810125056c4b09c2d5554 x
 F ext/wasm/mkwasmbuilds.c d4479af2774a43104b819d9286961b7c09793ca39440e20191e693ceff21f911
 F ext/wasm/module-symbols.html e54f42112e0aac2a31f850ab33e7f2630a2ea4f63496f484a12469a2501e07e2
@@ -642,9 +642,9 @@ F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d826
 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
 F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f
 F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
-F ext/wasm/tester1-worker.c-pp.html d0032241d0b24d996cf1c4dd0dde364189693af9b5c986e48af7d3d720fcd244
-F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb
-F ext/wasm/tester1.c-pp.js e26e78fe450375b43a88a5da41946b8997612201a592770f19ec827b2bf96cc0
+F ext/wasm/tester1-worker.c-pp.html 7171022e7f4da8f46e5f50ea81dd6ce840b9235c47653a5deeb3764ccc2fe472
+F ext/wasm/tester1.c-pp.html bd927ccf51ddd65e924660a0487add99e1b044afe03950e49d87ccf44efdddb6
+F ext/wasm/tester1.c-pp.js 604e3e8e01a734f0d2e176cd6c7674c64a808923aac6624fc8a257de07e1aefd
 F ext/wasm/tests/opfs/concurrency/index.html c8ac239f6fb45440adbdddb33a0fde2c61a20799189d60b8926be702a27dd226
 F ext/wasm/tests/opfs/concurrency/test.js 46c772bc18abb0fcbb058d57b5aaee9e7938f948ecdd802c6ca0850ad3519f92
 F ext/wasm/tests/opfs/concurrency/worker.js 704d82c5e287e47f612349e027765943a58ad967dcf178fb5a1c3a8eaafb09af
@@ -2192,8 +2192,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 0152b445ff0cdf560fc028938e1d43a56685d7c046fad84fa90166cd01d71279
-R c2a756d084a38515683f88a003d1705b
+P 699295f84ef2c4da363f13126abde6af07697d0959896cd868b14513d4278964
+R c74e99e5f80136211b846c84cc94f61a
 U stephan
-Z d7173eb7134af12db84591911d0c0ce4
+Z 6f57cbae6afc2acefab069dcb231bf44
 # Remove this line to create a well-formed Fossil manifest.
index 6eb4e7d5766ce9c57ecf388d6200eecee2bd5a37..fffe7c0ed3b78a170bd2a590142ac97510c6a8ea 100644 (file)
@@ -1 +1 @@
-699295f84ef2c4da363f13126abde6af07697d0959896cd868b14513d4278964
+2e2339bd9e4293bad04ece7673a3048b99c2143cf9573ade2ec082d95744b981