/**
  2022-06-30

  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 Jaccwabyt API is documented in detail in an external file,
  _possibly_ called jaccwabyt.md in the same directory as this file.

  Project homes:
  - https://fossil.wanderinghorse.net/r/jaccwabyt
  - https://sqlite.org/src/dir/ext/wasm/jaccwabyt

  This build was generated using:

  ./c-pp -o js/jaccwabyt.js -@policy=error jaccwabyt/jaccwabyt.c-pp.js

  by libcmpp 2.x 2fc4afc31f6505c27b9c34988973a2bd9b157d559247cdd26868ae75632c3a5e @ 2025-11-16 23:03:27.352 UTC
*/
'use strict';
globalThis.Jaccwabyt =
function StructBinderFactory(config){
  'use strict';
/* ^^^^ it is recommended that clients move that object into wherever
   they'd like to have it and delete the globalThis-held copy.  This
   API does not require the global reference - it is simply installed
   as a convenience for connecting these bits to other co-developed
   code before it gets removed from the global namespace.
*/

  /** Throws a new Error, the message of which is the concatenation
      all args with a space between each. */
  const toss = (...args)=>{throw new Error(args.join(' '))};

  {
    let h = config.heap;
    if( h instanceof WebAssembly.Memory ){
      h = function(){return new Uint8Array(this.buffer)}.bind(h);
    }else if( !(h instanceof Function) ){
      //console.warn("The bothersome StructBinderFactory config:",config);
      toss("config.heap must be WebAssembly.Memory instance or",
           "a function which returns one.");
    }
    config.heap = h;
  }
  ['alloc','dealloc'].forEach(function(k){
    (config[k] instanceof Function) ||
      toss("Config option '"+k+"' must be a function.");
  });
  const SBF = StructBinderFactory;
  const heap = config.heap,
        alloc = config.alloc,
        dealloc = config.dealloc,
        realloc = (config.realloc || function(){
          toss("This StructBinderFactory was configured without realloc()");
          /* We can't know the original memory's size from here unless
             we internally proxy alloc()/dealloc() to track all
             pointers (not going to happen), so we can't fall back to
             doing alloc()/copy/dealloc(). */
        }),
        log = config.log || console.debug.bind(console),
        memberPrefix = (config.memberPrefix || ""),
        memberSuffix = (config.memberSuffix || ""),
        BigInt = globalThis['BigInt'],
        BigInt64Array = globalThis['BigInt64Array'],
        bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array;

  //console.warn("config",config);
  let ptr;
  const ptrSize = config.pointerSize
        || config.ptrSize/*deprecated*/
        || ('bigint'===typeof (ptr = alloc(1)) ? 8 : 4);
  const ptrIR = config.pointerIR/*deprecated*/
        || config.ptrIR/*deprecated*/
        || (4===ptrSize ? 'i32' : 'i64');
  if( ptr ){
    dealloc(ptr);
    ptr = undefined;
  }
  //console.warn("ptrIR =",ptrIR,"ptrSize =",ptrSize);

  if(ptrSize!==4 && ptrSize!==8) toss("Invalid pointer size:",ptrSize);
  if(ptrIR!=='i32' && ptrIR!=='i64') toss("Invalid pointer representation:",ptrIR);

  /** Either BigInt or, if !bigIntEnabled, a function which
      throws complaining that BigInt is not enabled. */
  const __BigInt = (bigIntEnabled && BigInt)
        ? (v)=>BigInt(v || 0)
        : (v)=>toss("BigInt support is disabled in this build.");
  const __asPtrType = ('i32'==ptrIR) ? Number : __BigInt;
  const __NullPtr = __asPtrType(0);

  /**
     Expects any number of numeric arguments, each one of either type
     Number or BigInt. It sums them up (from an implicit starting
     point of 0 or 0n) and returns them as a number of the same type
     which target.asPtrType() uses.

     This is a workaround for not being able to mix Number/BigInt in
     addition/subtraction expressions (which we frequently need for
     calculating pointer offsets).
  */
  const __ptrAdd = function(...args){
    let rc = __NullPtr;
    for( let i = 0; i < args.length; ++i ){
      rc += __asPtrType(args[i]);
    }
    return rc;
  };

  const __ptrAddSelf = function(...args){
    return __ptrAdd(this.pointer,...args);
  };

  if(!SBF.debugFlags){
    SBF.__makeDebugFlags = function(deriveFrom=null){
      /* This is disgustingly overengineered. :/ */
      if(deriveFrom && deriveFrom.__flags) deriveFrom = deriveFrom.__flags;
      const f = function f(flags){
        if(0===arguments.length){
          return f.__flags;
        }
        if(flags<0){
          delete f.__flags.getter; delete f.__flags.setter;
          delete f.__flags.alloc; delete f.__flags.dealloc;
        }else{
          f.__flags.getter  = 0!==(0x01 & flags);
          f.__flags.setter  = 0!==(0x02 & flags);
          f.__flags.alloc   = 0!==(0x04 & flags);
          f.__flags.dealloc = 0!==(0x08 & flags);
        }
        return f._flags;
      };
      Object.defineProperty(f,'__flags', {
        iterable: false, writable: false,
        value: Object.create(deriveFrom)
      });
      if(!deriveFrom) f(0);
      return f;
    };
    SBF.debugFlags = SBF.__makeDebugFlags();
  }/*static init*/

  const isLittleEndian = true || (function() {
    const buffer = new ArrayBuffer(2);
    new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
    // Int16Array uses the platform's endianness.
    return new Int16Array(buffer)[0] === 256;
  })() /* WASM is, by definition, Little Endian */;

  /**
     Some terms used in the internal docs:

     StructType: a struct-wrapping class generated by this
     framework.

     DEF: struct description object.

     SIG: struct member signature string.
  */

  /** True if SIG s looks like a function signature, else
      false. */
  const isFuncSig = (s)=>'('===s[1];
  /** True if SIG s is-a pointer-type signature. */
  const isPtrSig = (s)=>'p'===s || 'P'===s || 's'===s;
  const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/;
  /** Returns p if SIG s is a function SIG, else returns s[0]. */
  const sigLetter = (s)=>s ? (isFuncSig(s) ? 'p' : s[0]) : undefined;

  /** Returns the WASM IR form of the letter at SIG s[0]. Throws for
      an unknown SIG. */
  const sigIR = function(s){
    switch(sigLetter(s)){
        case 'c': case 'C': return 'i8';
        case 'i': return 'i32';
        case 'p': case 'P': case 's': return ptrIR;
        case 'j': return 'i64';
        case 'f': return 'float';
        case 'd': return 'double';
    }
    toss("Unhandled signature IR:",s);
  };

  /** Returns the WASM sizeof of the letter at SIG s[0]. Throws for an
      unknown SIG. */
  const sigSize = function(s){
    switch(sigLetter(s)){
        case 'c': case 'C': return 1;
        case 'i': return 4;
        case 'p': case 'P': case 's': return ptrSize;
        case 'j': return 8;
        case 'f': return 4;
        case 'd': return 8;
    }
    toss("Unhandled signature sizeof:",s);
  };

  const affirmBigIntArray = BigInt64Array
        ? ()=>true : ()=>toss('BigInt64Array is not available.');

  /** Returns the name of a DataView getter method corresponding
      to the given SIG. */
  const sigDVGetter = function(s){
    switch(sigLetter(s)) {
        case 'p': case 'P': case 's': {
          switch(ptrSize){
              case 4: return 'getInt32';
              case 8: return affirmBigIntArray() && 'getBigInt64';
          }
          break;
        }
        case 'i': return 'getInt32';
        case 'c': return 'getInt8';
        case 'C': return 'getUint8';
        case 'j': return affirmBigIntArray() && 'getBigInt64';
        case 'f': return 'getFloat32';
        case 'd': return 'getFloat64';
    }
    toss("Unhandled DataView getter for signature:",s);
  };

  /** Returns the name of a DataView setter method corresponding
      to the given SIG. */
  const sigDVSetter = function(s){
    switch(sigLetter(s)){
        case 'p': case 'P': case 's': {
          switch(ptrSize){
              case 4: return 'setInt32';
              case 8: return affirmBigIntArray() && 'setBigInt64';
          }
          break;
        }
        case 'i': return 'setInt32';
        case 'c': return 'setInt8';
        case 'C': return 'setUint8';
        case 'j': return affirmBigIntArray() && 'setBigInt64';
        case 'f': return 'setFloat32';
        case 'd': return 'setFloat64';
    }
    toss("Unhandled DataView setter for signature:",s);
  };

  /**
     Returns a factory for either Number or BigInt, depending on the
     given SIG. This constructor is used in property setters to coerce
     the being-set value to the correct pointer size.
  */
  const sigDVSetWrapper = function(s){
    switch(sigLetter(s)) {
        case 'i': case 'f': case 'c': case 'C': case 'd': return Number;
        case 'j': return __BigInt;
        case 'p': case 'P': case 's':
          switch(ptrSize){
              case 4: return Number;
              case 8: return __BigInt;
          }
          break;
    }
    toss("Unhandled DataView set wrapper for signature:",s);
  };

  /** Returns the given struct and member name in a form suitable for
      debugging and error output. */
  const sPropName = (s,k)=>s+'::'+k;

  const __propThrowOnSet = function(structName,propName){
    return ()=>toss(sPropName(structName,propName),"is read-only.");
  };

  /**
     In order to completely hide StructBinder-bound struct pointers
     from JS code, we store them in a scope-local WeakMap which maps
     the struct-bound objects to an object with their metadata:

     {
       .p = the native pointer,
       .o = self (for an eventual reverse-mapping),
       .xb = extra bytes allocated for p,
       .zod = zeroOnDispose,
       .ownsPointer = true if this object owns p
     }

     The .p data are accessible via obj.pointer, which is gated behind
     a property interceptor, but are not exposed anywhere else in the
     public API.
  */
  const getInstanceHandle = function f(obj, create=true){
    let ii = f.map.get(obj);
    if( !ii && create ){
      f.map.set(obj, (ii=f.create(obj)));
    }
    return ii;
  };
  getInstanceHandle.map = new WeakMap;
  getInstanceHandle.create = (forObj)=>{
    return Object.assign(Object.create(null),{
      o: forObj,
      p: undefined/*native ptr*/,
      ownsPointer: false,
      zod: false/*zeroOnDispose*/,
      xb: 0/*extraBytes*/
    });
  };

  /**
     Remove the getInstanceHandle() mapping for obj.
  */
  const rmInstanceHandle = (obj)=>getInstanceHandle.map.delete(obj)
    /* If/when we have a reverse map of ptr-to-objects, we need to
       clean that here. */;

  const __isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0);
  const __isPtr64 = (ptr)=>(
    ('bigint'===typeof ptr && ptr >= 0) || __isPtr32(ptr)
  );

  /**
     isPtr() is an alias for isPtr32() or isPtr64(), depending on
     ptrSize.
  */
  const __isPtr = (4===ptrSize) ? __isPtr32 : __isPtr64;

  const __isNonNullPtr = (v)=>__isPtr(v) && (v>0);

  /** Frees the obj.pointer memory (a.k.a. m), handles obj.ondispose,
      and unmaps obj from its native resources. */
  const __freeStruct = function(ctor, obj, m){
    const ii = getInstanceHandle(obj, false);
    if( !ii ) return;
    rmInstanceHandle(obj);
    if( !m && !(m = ii.p) ){
      console.warn("Cannot(?) happen: __freeStruct() found no instanceInfo");
      return;
    }
    if(Array.isArray(obj.ondispose)){
      let x;
      while((x = obj.ondispose.pop())){
        try{
          if(x instanceof Function) x.call(obj);
          else if(x instanceof StructType) x.dispose();
          else if(__isPtr(x)) dealloc(x);
          // else ignore. Strings are permitted to annotate entries
          // to assist in debugging.
        }catch(e){
          console.warn("ondispose() for",ctor.structName,'@',
                       m,'threw. NOT propagating it.',e);
        }
      }
    }else if(obj.ondispose instanceof Function){
      try{obj.ondispose()}
      catch(e){
        /*do not rethrow: destructors must not throw*/
        console.warn("ondispose() for",ctor.structName,'@',
                     m,'threw. NOT propagating it.',e);
      }
    }
    delete obj.ondispose;
    if(ctor.debugFlags.__flags.dealloc){
      log("debug.dealloc:",(ii.ownsPointer?"":"EXTERNAL"),
          ctor.structName,"instance:",
          ctor.structInfo.sizeof,"bytes @"+m);
    }
    if(ii.ownsPointer){
      if( ii.zod || ctor.structInfo.zeroOnDispose ){
        heap().fill(0, Number(m),
                    Number(m) + ctor.structInfo.sizeof + ii.xb);
      }
      dealloc(m);
    }
  };

  /** Returns a skeleton for a read-only, non-iterable property
   * descriptor. */
  const rop0 = ()=>{return {configurable: false, writable: false,
                            iterable: false}};

  /** Returns a skeleton for a read-only property accessor wrapping
      value v. */
  const rop = (v)=>{return {...rop0(), value: v}};

  /** Allocates obj's memory buffer based on the size defined in
      ctor.structInfo.sizeof. */
  const __allocStruct = function f(ctor, obj, xm){
    let opt;
    const checkPtr = (ptr)=>{
      __isNonNullPtr(ptr) ||
        toss("Invalid pointer value",arguments[0],"for",ctor.structName,"constructor.");
    };
    if( arguments.length>=3 ){
      if( xm && ('object'===typeof xm) ){
        opt = xm;
        xm = opt?.wrap;
      }else{
        checkPtr(xm);
        opt = {wrap: xm};
      }
    }else{
      opt = {}
    }

    const fill = !xm /* true if we need to zero the memory */;
    let nAlloc = 0;
    let ownsPointer = false;
    if(xm){
      /* Externally-allocated memory. */
      checkPtr(xm);
      ownsPointer = !!opt?.takeOwnership;
    }else{
      const nX = opt?.extraBytes ?? 0;
      if( nX<0 || (nX!==(nX|0)) ){
        toss("Invalid extraBytes value:",opt?.extraBytes);
      }
      nAlloc = ctor.structInfo.sizeof + nX;
      xm = alloc(nAlloc)
        || toss("Allocation of",ctor.structName,"structure failed.");
      ownsPointer = true;
    }
    try {
      if( opt?.debugFlags ){
        /* specifically undocumented */
        obj.debugFlags(opt.debugFlags);
      }
      if(ctor./*prototype.???*/debugFlags.__flags.alloc){
        log("debug.alloc:",(fill?"":"EXTERNAL"),
            ctor.structName,"instance:",
            ctor.structInfo.sizeof,"bytes @"+xm);
      }
      if(fill){
        heap().fill(0, Number(xm), Number(xm) + nAlloc);
      }
      const ii = getInstanceHandle(obj);
      ii.p = xm;
      ii.ownsPointer = ownsPointer;
      ii.xb = nAlloc ? (nAlloc-ctor.structInfo.sizeof) : 0;
      ii.zod = !!opt?.zeroOnDispose;
      if( opt?.ondispose && opt.ondispose!==xm ){
        obj.addOnDispose( opt.ondispose );
      }
    }catch(e){
      __freeStruct(ctor, obj, xm);
      throw e;
    }
  };

  /** True if sig looks like an emscripten/jaccwabyt
      type signature, else false. */
  const looksLikeASig = function f(sig){
    f.rxSig1 ??= /^[ipPsjfdcC]$/;
    f.rxSig2 ??= /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/;
    return f.rxSig1.test(sig) || f.rxSig2.test(sig);
  };

  /** Returns a pair of adaptor maps (objects) in a length-3
      array specific to the given object. */
  const __adaptorsFor = function(who){
    let x = this.get(who);
    if( !x ){
      x = [ Object.create(null), Object.create(null), Object.create(null) ];
      this.set(who, x);
    }
    return x;
  }.bind(new WeakMap);

  /** Code de-duplifier for __adaptGet(), __adaptSet(), and
      __adaptStruct(). */
  const __adaptor = function(who, which, key, proxy){
    const a = __adaptorsFor(who)[which];
    if(3===arguments.length) return a[key];
    if( proxy ) return a[key] = proxy;
    return delete a[key];
  };

  const noopAdapter = (x)=>x;

  // StructBinder::adaptGet()
  const __adaptGet = function(key, ...args){
    return __adaptor(this, 0, key, ...args);
  };

  const __affirmNotASig = function(ctx,key){
    looksLikeASig(key) &&
      toss(ctx,"(",key,") collides with a data type signature.");
  };

  // StructBinder::adaptSet()
  const __adaptSet = function(key, ...args){
    __affirmNotASig('Setter adaptor',key);
    return __adaptor(this, 1, key, ...args);
  };

  // StructBinder::adaptStruct()
  const __adaptStruct = function(key, ...args){
    __affirmNotASig('Struct adaptor',key);
    return __adaptor(this, 2, key, ...args);
  };

  /**
     An internal counterpart of __adaptStruct().  If key is-a string,
     uses __adaptor(who) to fetch the struct-adaptor entry for key,
     else key is assumed to be a struct description object. If it
     resolves to an object, that's returned, else an exception is
     thrown.
  */
  const __adaptStruct2 = function(who,key){
    const si = ('string'===typeof key)
          ? __adaptor(who, 2, key) : key;
    if( 'object'!==typeof si ){
      toss("Invalid struct mapping object. Arg =",key,JSON.stringify(si));
    }
    return si;
  };

  const __memberKey = (k)=>memberPrefix + k + memberSuffix;
  const __memberKeyProp = rop(__memberKey);
  //const __adaptGetProp = rop(__adaptGet);

  /**
     Looks up a struct member in structInfo.members. Throws if found
     if tossIfNotFound is true, else returns undefined if not
     found. The given name may be either the name of the
     structInfo.members key (faster) or the key as modified by the
     memberPrefix and memberSuffix settings.
  */
  const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){
    let m = structInfo.members[memberName];
    if(!m && (memberPrefix || memberSuffix)){
      // Check for a match on members[X].key
      for(const v of Object.values(structInfo.members)){
        if(v.key===memberName){ m = v; break; }
      }
      if(!m && tossIfNotFound){
        toss(sPropName(structInfo.name || structInfo.structName, memberName),
             'is not a mapped struct member.');
      }
    }
    return m;
  };

  /**
     Uses __lookupMember(obj.structInfo,memberName) to find a member,
     throwing if not found. Returns its signature, either in this
     framework's native format or in Emscripten format.
  */
  const __memberSignature = function f(obj,memberName,emscriptenFormat=false){
    if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i');
    const m = __lookupMember(obj.structInfo, memberName, true);
    return emscriptenFormat ? f._(m.signature) : m.signature;
  };

  /** Impl of X.memberKeys() for StructType and struct ctors. */
  const __structMemberKeys = rop(function(){
    const a = [];
    for(const k of Object.keys(this.structInfo.members)){
      a.push(this.memberKey(k));
    }
    return a;
  });

  const __utf8Decoder = new TextDecoder('utf-8');
  const __utf8Encoder = new TextEncoder();
  /** Internal helper to use in operations which need to distinguish
      between SharedArrayBuffer heap memory and non-shared heap. */
  const __SAB = ('undefined'===typeof SharedArrayBuffer)
        ? function(){} : SharedArrayBuffer;
  const __utf8Decode = function(arrayBuffer, begin, end){
    if( 8===ptrSize ){
      begin = Number(begin);
      end = Number(end);
    }
    return __utf8Decoder.decode(
      (arrayBuffer.buffer instanceof __SAB)
        ? arrayBuffer.slice(begin, end)
        : arrayBuffer.subarray(begin, end)
    );
  };

  /**
     Uses __lookupMember() to find the given obj.structInfo key.
     Returns that member if it is a string, else returns false. If the
     member is not found, throws if tossIfNotFound is true, else
     returns false.
   */
  const __memberIsString = function(obj,memberName, tossIfNotFound=false){
    const m = __lookupMember(obj.structInfo, memberName, tossIfNotFound);
    return (m && 1===m.signature.length && 's'===m.signature[0]) ? m : false;
  };

  /**
     Given a member description object, throws if member.signature is
     not valid for assigning to or interpretation as a C-style string.
     It optimistically assumes that any signature of (i,p,s) is
     C-string compatible.
  */
  const __affirmCStringSignature = function(member){
    if('s'===member.signature) return;
    toss("Invalid member type signature for C-string value:",
         JSON.stringify(member));
  };

  /**
     Looks up the given member in obj.structInfo. If it has a
     signature of 's' then it is assumed to be a C-style UTF-8 string
     and a decoded copy of the string at its address is returned. If
     the signature is of any other type, it throws. If an s-type
     member's address is 0, `null` is returned.
  */
  const __memberToJsString = function f(obj,memberName){
    const m = __lookupMember(obj.structInfo, memberName, true);
    __affirmCStringSignature(m);
    const addr = obj[m.key];
    //log("addr =",addr,memberName,"m =",m);
    if(!addr) return null;
    let pos = addr;
    const mem = heap();
    for( ; mem[pos]!==0; ++pos ) {
      //log("mem[",pos,"]",mem[pos]);
    };
    //log("addr =",addr,"pos =",pos);
    return (addr===pos) ? "" : __utf8Decode(mem, addr, pos);
  };

  /**
     Adds value v to obj.ondispose, creating ondispose,
     or converting it to an array, if needed.
  */
  const __addOnDispose = function(obj, ...v){
    if(obj.ondispose){
      if(!Array.isArray(obj.ondispose)){
        obj.ondispose = [obj.ondispose];
      }
    }else{
      obj.ondispose = [];
    }
    obj.ondispose.push(...v);
  };

  /**
     Allocates a new UTF-8-encoded, NUL-terminated copy of the given
     JS string and returns its address relative to heap(). If
     allocation returns 0 this function throws. Ownership of the
     memory is transfered to the caller, who must eventually pass it
     to the configured dealloc() function.
  */
  const __allocCString = function(str){
    const u = __utf8Encoder.encode(str);
    const mem = alloc(u.length+1);
    if(!mem) toss("Allocation error while duplicating string:",str);
    const h = heap();
    //let i = 0;
    //for( ; i < u.length; ++i ) h[mem + i] = u[i];
    h.set(u, Number(mem));
    h[__ptrAdd(mem, u.length)] = 0;
    //log("allocCString @",mem," =",u);
    return mem;
  };

  /**
     Sets the given struct member of obj to a dynamically-allocated,
     UTF-8-encoded, NUL-terminated copy of str. It is up to the caller
     to free any prior memory, if appropriate. The newly-allocated
     string is added to obj.ondispose so will be freed when the object
     is disposed.

     The given name may be either the name of the structInfo.members
     key (faster) or the key as modified by the memberPrefix and
     memberSuffix settings.
  */
  const __setMemberCString = function(obj, memberName, str){
    const m = __lookupMember(obj.structInfo, memberName, true);
    __affirmCStringSignature(m);
    /* Potential TODO: if obj.ondispose contains obj[m.key] then
       dealloc that value and clear that ondispose entry */
    const mem = __allocCString(str);
    obj[m.key] = mem;
    __addOnDispose(obj, mem);
    return obj;
  };

  /**
     Prototype for all StructFactory instances (the constructors
     returned from StructBinder).
  */
  const StructType = function StructType(structName, structInfo){
    if(arguments[2]!==rop/*internal sentinel value*/){
      toss("Do not call the StructType constructor",
           "from client-level code.");
    }
    Object.defineProperties(this,{
      //isA: rop((v)=>v instanceof StructType),
      structName: rop(structName),
      structInfo: rop(structInfo)
    });
  };

  /**
     Properties inherited by struct-type-specific StructType instances
     and (indirectly) concrete struct-type instances.
  */
  StructType.prototype = Object.create(null, {
    dispose: rop(function(){__freeStruct(this.constructor, this)}),
    lookupMember: rop(function(memberName, tossIfNotFound=true){
      return __lookupMember(this.structInfo, memberName, tossIfNotFound);
    }),
    memberToJsString: rop(function(memberName){
      return __memberToJsString(this, memberName);
    }),
    memberIsString: rop(function(memberName, tossIfNotFound=true){
      return __memberIsString(this, memberName, tossIfNotFound);
    }),
    memberKey: __memberKeyProp,
    memberKeys: __structMemberKeys,
    memberSignature: rop(function(memberName, emscriptenFormat=false){
      return __memberSignature(this, memberName, emscriptenFormat);
    }),
    memoryDump: rop(function(){
      const p = this.pointer;
      return p
        ? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof))
        : null;
    }),
    extraBytes: {
      configurable: false, enumerable: false,
      get: function(){return getInstanceHandle(this, false)?.xb ?? 0;}
    },
    zeroOnDispose: {
      configurable: false, enumerable: false,
      get: function(){
        return getInstanceHandle(this, false)?.zod
          ?? !!this.structInfo.zeroOnDispose;
      }
    },
    pointer: {
      configurable: false, enumerable: false,
      get: function(){return getInstanceHandle(this, false)?.p},
      set: ()=>toss("Cannot assign the 'pointer' property of a struct.")
      // Reminder: leaving `set` undefined makes assignments
      // to the property _silently_ do nothing. Current unit tests
      // rely on it throwing, though.
    },
    setMemberCString: rop(function(memberName, str){
      return __setMemberCString(this, memberName, str);
    })
  });
  // Function-type non-Property inherited members
  Object.assign(StructType.prototype,{
    addOnDispose: function(...v){
      __addOnDispose(this,...v);
      return this;
    }
  });

  /**
     "Static" properties for StructType.
  */
  Object.defineProperties(StructType, {
    allocCString: rop(__allocCString),
    isA: rop((v)=>v instanceof StructType),
    hasExternalPointer: rop((v)=>{
      const ii = getInstanceHandle(v, false);
      return !!(ii?.p && !ii?.ownsPointer);
    }),
    memberKey: __memberKeyProp
    //ptrAdd = rop(__ptrAdd) no b/c one might think that it adds based on this.pointer.
  });

  /**
     If struct description object si has a getter proxy, return it (a
     function), else return undefined.
  */
  const memberGetterProxy = function(si){
    return si.get || (si.adaptGet
                      ? StructBinder.adaptGet(si.adaptGet)
                      : undefined);
  };

  /**
     If struct description object si has a setter proxy, return it (a
     function), else return undefined.
  */
  const memberSetterProxy = function(si){
    return si.set || (si.adaptSet
                      ? StructBinder.adaptSet(si.adaptSet)
                      : undefined);
  };

  /**
     To be called by makeMemberWrapper() when si has a 'members'
     member, i.e. is an embedded struct. This function sets up that
     struct like any other and also sets up property accessor for
     ctor.memberKey(name) which returns an instance of that new
     StructType when the member is accessed. That instance wraps the
     memory of the member's part of the containing C struct instance.

     That is, if struct Foo has member bar which is an inner struct
     then:

     const f = new Foo;
     const b = f.bar;
     assert( b is-a StructType object );
     assert( b.pointer === f.b.pointer );

     b will be disposed of when f() is. Calling b.dispose() will not
     do any permanent harm, as the wrapper object will be recreated
     when accessing f.bar, pointing to the same memory in f.

     The si.zeroOnDispose flag has no effect on embedded structs because
     they wrap "external" memory, so do not own it, and are thus never
     freed, as such.
  */
  const makeMemberStructWrapper = function callee(ctor, name, si){
    /**
       Where we store inner-struct member proxies. Keys are a
       combination of the parent object's pointer address and the
       property's name. The values are StructType instances.
    */
    const __innerStructs = (callee.innerStructs ??= new Map());
    const key = ctor.memberKey(name);
    if( undefined!==si.signature ){
      toss("'signature' cannot be used on an embedded struct (",
           ctor.structName,".",key,").");
    }
    if( memberSetterProxy(si) ){
      toss("'set' and 'adaptSet' are not permitted for nested struct members.");
    }
    //console.warn("si =",ctor.structName, name, JSON.stringify(si,'  '));
    si.structName ??= ctor.structName+'::'+name;
    si.key = key;
    si.name = name;
    si.constructor = this.call(this, si.structName, si);
    //console.warn("si.constructor =",si.constructor);
    //console.warn("si =",si,"ctor=",ctor);
    const getterProxy = memberGetterProxy(si);
    const prop = Object.assign(Object.create(null),{
      configurable: false,
      enumerable: false,
      set: __propThrowOnSet(ctor/*not si.constructor*/.structName, key),
      get: function(){
        const dbg = this.debugFlags.__flags;
        const p = this.pointer;
        const k = p+'.'+key;
        let s = __innerStructs.get(k);
        if(dbg.getter){ log("debug.getter: k =",k); }
        if( !s ){
          s = new si.constructor(__ptrAdd(p, si.offset));
          __innerStructs.set(k, s);
          this.addOnDispose(()=>s.dispose());
          s.addOnDispose(()=>__innerStructs.delete(k));
          //console.warn("Created",k,"proxy");
        }
        if(getterProxy) s = getterProxy.apply(this,[s,key]);
        if(dbg.getter) log("debug.getter: result =",s);
        return s;
      }
    });
    Object.defineProperty(ctor.prototype, key, prop);
  }/*makeMemberStructWrapper()*/;

  /**
     This is where most of the magic happens.

     Pass this a StructBinderImpl-generated constructor, a member
     property name, and the struct member description object. It will
     define property accessors for proto[memberKey] which read
     from/write to memory in this.pointer. It modifies si to make
     certain downstream operations simpler.
  */
  const makeMemberWrapper = function f(ctor, name, si){
    si = __adaptStruct2(this, si);
    if( si.members ){
      return makeMemberStructWrapper.call(this, ctor, name, si);
    }

    if(!f.cache){
      /* Cache all available getters/setters/set-wrappers for
         direct reuse in each accessor function. */
      f.cache = {getters: {}, setters: {}, sw:{}};
      const a = ['i','c','C','p','P','s','f','d','v()'];
      if(bigIntEnabled) a.push('j');
      a.forEach(function(v){
        f.cache.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */;
        f.cache.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */;
        f.cache.sw[v] = sigDVSetWrapper(v)  /* BigInt or Number ctor to wrap around values
                                           for conversion */;
      });
      f.sigCheck = function(obj, name, key,sig){
        if(Object.prototype.hasOwnProperty.call(obj, key)){
          toss(obj.structName,'already has a property named',key+'.');
        }
        looksLikeASig(sig)
          || toss("Malformed signature for",
                  sPropName(obj.structName,name)+":",sig);
      };
    }
    const key = ctor.memberKey(name);
    f.sigCheck(ctor.prototype, name, key, si.signature);
    si.key = key;
    si.name = name;
    const sigGlyph = sigLetter(si.signature);
    const xPropName = sPropName(ctor.structName,key);
    const dbg = ctor.debugFlags.__flags;
    /*
      TODO?: set prototype of si to an object which can set/fetch
      its preferred representation, e.g. conversion to string or mapped
      function. Advantage: we can avoid doing that via if/else if/else
      in the get/set methods.
    */
    const getterProxy = memberGetterProxy(si);
    const prop = Object.create(null);
    prop.configurable = false;
    prop.enumerable = false;
    prop.get = function(){
      /**
         This getter proxy reads its value from the appropriate pointer
         address in the heap. It knows where and how much to read based on
         this.pointer, si.offset, and si.sizeof.
      */
      if(dbg.getter){
        log("debug.getter:",f.cache.getters[sigGlyph],"for", sigIR(sigGlyph),
            xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof);
      }
      let rc = (
        new DataView(heap().buffer, Number(this.pointer) + si.offset, si.sizeof)
      )[f.cache.getters[sigGlyph]](0, isLittleEndian);

      if(getterProxy) rc = getterProxy.apply(this,[key,rc]);
      if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
      return rc;
    };
    if(si.readOnly){
      prop.set = __propThrowOnSet(ctor.prototype.structName,key);
    }else{
      const setterProxy = memberSetterProxy(si);
      prop.set = function(v){
        /**
           The converse of prop.get(), this encodes v into the appropriate
           spot in the WASM heap.
        */
        if(dbg.setter){
          log("debug.setter:",f.cache.setters[sigGlyph],"for", sigIR(sigGlyph),
              xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof, v);
        }
        if(!this.pointer){
          toss("Cannot set native property on a disposed",
               this.structName,"instance.");
        }
        if( setterProxy ) v = setterProxy.apply(this,[key,v]);
        if( null===v || undefined===v ) v = __NullPtr;
        else if( isPtrSig(si.signature) && !__isPtr(v) ){
          if(isAutoPtrSig(si.signature) && (v instanceof StructType)){
            // It's a struct instance: store its pointer value
            v = v.pointer || __NullPtr;
            if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v);
          }else{
            toss("Invalid value for pointer-type",xPropName+'.');
          }
        }
        (
          new DataView(heap().buffer, Number(this.pointer) + si.offset,
                       si.sizeof)
        )[f.cache.setters[sigGlyph]](0, f.cache.sw[sigGlyph](v), isLittleEndian);
      };
    }
    Object.defineProperty(ctor.prototype, key, prop);
  }/*makeMemberWrapper()*/;

  /**
     The main factory function which will be returned to the
     caller. The third argument is structly for internal use.

     This level of indirection is to avoid that clients can pass a
     third argument to this, as that's only for internal use.

     internalOpt options:

     - None right now. This is for potential use in recursion.

     Usages:

     StructBinder(string, obj [,optObj]);
     StructBinder(obj);
  */
  const StructBinderImpl = function StructBinderImpl(
    structName, si, opt = Object.create(null)
  ){
    /**
       StructCtor is the eventual return value of this function. We
       need to populate this early on so that we can do some trickery
       in feeding it through recursion.

       Uses:

       // heap-allocated:
       const x = new StructCtor;
       // externally-managed memory:
       const y = new StructCtor( aPtrToACompatibleCStruct );

       or, more recently:

       const z = new StructCtor({
         extraBytes: [int=0] extra bytes to allocate after the struct

         wrap: [aPtrToACompatibleCStruct=undefined]. If provided, this
         instance waps, but does not (by default) own the memory, else
         a new instance is allocated from the WASM heap.

         ownsPointer: true if this object takes over ownership of
         wrap.

         zeroOnDispose: [bool=StructCtor.structInfo.zeroOnDispose]

         autoCalcSizeOffset: [bool=false] Automatically calculate
         sizeof an offset. This is fine for pure-JS structs (which
         probably aren't useful beyond testing of Jaccwabyt) but it's
         dangerous to use with actual WASM objects because we cannot
         be guaranteed to have the same memory layout as an ostensibly
         matching C struct. This applies recursively to all children
         of the struct description.

         // TODO? Per-instance overrides of the struct-level flags?

         get: (k,v)=>v,
         set: (k,v)=>v,
         adaptGet: string,
         adaptSet: string

         // That wouldn't fit really well right now, apparently.
       });

    */
    const StructCtor = function StructCtor(arg){
      //console.warn("opt",opt,arguments[0]);
      if(!(this instanceof StructCtor)){
        toss("The",structName,"constructor may only be called via 'new'.");
      }
      __allocStruct(StructCtor, this, ...arguments);
    };
    const self = this;
    /**
      "Convert" struct description x to a struct description, if
      needed. This expands adaptStruct() mappings and transforms
      {memberName:signatureString} signature syntax to object form.
    */
    const ads = (x)=>{
      //console.warn("looksLikeASig(",x,") =",looksLikeASig(x));
      return (('string'===typeof x) && looksLikeASig(x))
        ? {signature: x} : __adaptStruct2(self,x);
    };
    if(1===arguments.length){
      si = ads(structName);
      structName = si.structName || si.name;
    }else if(2===arguments.length){
      si = ads(si);
      si.name ??= structName;
    }else{
      si = ads(si);
    }
    structName ??= si.structName;
    //console.warn("arguments =",JSON.stringify(arguments));
    structName ??= opt.structName;
    if( !structName ) toss("One of 'name' or 'structName' are required.");
    if( si.adapt ){
      /* Install adaptGet(), adaptSet(), and adaptStruct() proxies. */
      Object.keys(si.adapt.struct||{}).forEach((k)=>{
        __adaptStruct.call(StructBinderImpl, k, si.adapt.struct[k]);
      });
      Object.keys(si.adapt.set||{}).forEach((k)=>{
        __adaptSet.call(StructBinderImpl, k, si.adapt.set[k]);
      });
      Object.keys(si.adapt.get||{}).forEach((k)=>{
        __adaptGet.call(StructBinderImpl, k, si.adapt.get[k]);
      });
    }
    if(!si.members && !si.sizeof){
      si.sizeof = sigSize(si.signature);
    }

    const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags));
    Object.defineProperties(StructCtor,{
      debugFlags: debugFlags,
      isA: rop((v)=>v instanceof StructCtor),
      memberKey: __memberKeyProp,
      memberKeys: __structMemberKeys,
      //methodInfoForKey: rop(function(mKey){/*???*/}),
      structInfo: rop(si),
      structName: rop(structName),
      ptrAdd: rop(__ptrAdd)
    });
    StructCtor.prototype = new StructType(structName, si, rop);
    Object.defineProperties(StructCtor.prototype,{
      debugFlags: debugFlags,
      constructor: rop(StructCtor)
      /*if we assign StructCtor.prototype and don't do
        this then StructCtor!==instance.constructor*/,
      ptrAdd: rop(__ptrAddSelf)
    });
    let lastMember = false;
    let offset = 0;
    const autoCalc = !!si.autoCalcSizeOffset;
    //console.warn(structName,"si =",si);
    if( !autoCalc ){
      if( !si.sizeof ){
        toss(structName,"description is missing its sizeof property.");
      }
      /*if( undefined===si.offset ){
        toss(structName,"description is missing its offset property.");
      }*/
      si.offset ??= 0;
    }else{
      si.offset ??= 0;
    }
    Object.keys(si.members || {}).forEach((k)=>{
      // Sanity checks of sizeof/offset info...
      let m = ads(si.members[k]);
      if(!m.members && !m.sizeof){
        /* ^^^^ fixme: we need to recursively collect all sizeofs
           before updating that. */
        m.sizeof = sigSize(m.signature);
        if(!m.sizeof){
          toss(sPropName(structName,k), "is missing a sizeof property.",m);
        }
      }
      if( undefined===m.offset ){
        if( autoCalc ) m.offset = offset;
        else{
          toss(sPropName(structName,k),"is missing its offset.",
               JSON.stringify(m));
        }
        /* A missing offset on the initial child is okay (it's always
           zero), but we don't know for sure that the members are
           their natural order, so we don't know, at this point, which
           one is "first". */
      }
      si.members[k] = m /* in case ads() resolved it to something else */;
      if(!lastMember || lastMember.offset < m.offset) lastMember = m;
      const oldAutoCalc = !!m.autoCalc;
      if( autoCalc ) m.autoCalcSizeOffset = true;
      makeMemberWrapper.call(self, StructCtor, k, m);
      if( oldAutoCalc ) m.autoCalcSizeOffset = true;
      else delete m.autoCalcSizeOffset;
      offset += m.sizeof;
      //console.warn("offset",sPropName(structName,k),offset);
    });

    if( !lastMember ) toss("No member property descriptions found.");
    if( !si.sizeof ) si.sizeof = offset;
    if(si.sizeof===1){
      (si.signature === 'c' || si.signature === 'C') ||
        toss("Unexpected sizeof==1 member",
             sPropName(structName,k),
             "with signature",si.signature);
    }else{
      // sizes and offsets of size-1 members may be odd values, but
      // others may not.
      if(0!==(si.sizeof%4)){
        console.warn("Invalid struct member description",si);
        toss(structName,"sizeof is not aligned. sizeof="+si.sizeof);
      }
      if(0!==(si.offset%4)){
        console.warn("Invalid struct member description",si);
        toss(structName,"offset is not aligned. offset="+si.offset);
      }
    }
    if( si.sizeof < offset ){
      console.warn("Suspect struct description:",si,"offset =",offset);
      toss("Mismatch in the calculated vs. the provided sizeof/offset info.",
           "Expected sizeof",offset,"but got",si.sizeof,"for",si);
      /* It is legal for the native struct to be larger, so long as
         we're pointing to all the right offsets for the members
         exposed here. */
    }
    delete si.autoCalcSizeOffset;
    return StructCtor;
  }/*StructBinderImpl*/;

  const StructBinder = function StructBinder(structName, structInfo){
    return (1==arguments.length)
      ? StructBinderImpl.call(StructBinder, structName)
      : StructBinderImpl.call(StructBinder, structName, structInfo);
  };
  StructBinder.StructType = StructType;
  StructBinder.config = config;
  StructBinder.allocCString = __allocCString;
  StructBinder.adaptGet = __adaptGet;
  StructBinder.adaptSet = __adaptSet;
  StructBinder.adaptStruct = __adaptStruct;
  StructBinder.ptrAdd = __ptrAdd;
  if(!StructBinder.debugFlags){
    StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags);
  }
  return StructBinder;
}/*StructBinderFactory*/;
