| /** |
| 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 |
| |
| */ |
| 'use strict'; |
| globalThis.Jaccwabyt = function StructBinderFactory(config){ |
| /* ^^^^ it is recommended that clients move that object into wherever |
| they'd like to have it and delete the self-held copy ("self" being |
| the global window or worker object). 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(' '))}; |
| |
| /** |
| Implementing function bindings revealed significant |
| shortcomings in Emscripten's addFunction()/removeFunction() |
| interfaces: |
| |
| https://github.com/emscripten-core/emscripten/issues/17323 |
| |
| Until those are resolved, or a suitable replacement can be |
| implemented, our function-binding API will be more limited |
| and/or clumsier to use than initially hoped. |
| */ |
| if(!(config.heap instanceof WebAssembly.Memory) |
| && !(config.heap instanceof Function)){ |
| toss("config.heap must be WebAssembly.Memory instance or a function."); |
| } |
| ['alloc','dealloc'].forEach(function(k){ |
| (config[k] instanceof Function) || |
| toss("Config option '"+k+"' must be a function."); |
| }); |
| const SBF = StructBinderFactory; |
| const heap = (config.heap instanceof Function) |
| ? config.heap : (()=>new Uint8Array(config.heap.buffer)), |
| alloc = config.alloc, |
| dealloc = config.dealloc, |
| log = config.log || console.log.bind(console), |
| memberPrefix = (config.memberPrefix || ""), |
| memberSuffix = (config.memberSuffix || ""), |
| bigIntEnabled = (undefined===config.bigIntEnabled |
| ? !!globalThis['BigInt64Array'] : !!config.bigIntEnabled), |
| BigInt = globalThis['BigInt'], |
| BigInt64Array = globalThis['BigInt64Array'], |
| /* Undocumented (on purpose) config options: */ |
| ptrSizeof = config.ptrSizeof || 4, |
| ptrIR = config.ptrIR || 'i32' |
| ; |
| |
| 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 = (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; |
| })(); |
| /** |
| 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 signature. */ |
| const isPtrSig = (s)=>'p'===s || 'P'===s; |
| const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; |
| const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0]; |
| /** Returns the WASM IR form of the Emscripten-conventional 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); |
| }; |
| |
| 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(ptrSizeof){ |
| 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(ptrSizeof){ |
| 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 either Number of BigInt, depending on the given |
| SIG. This constructor is used in property setters to coerce |
| the being-set value to the correct size. |
| */ |
| const sigDVSetWrapper = function(s){ |
| switch(sigLetter(s)) { |
| case 'i': case 'f': case 'c': case 'C': case 'd': return Number; |
| case 'j': return affirmBigIntArray() && BigInt; |
| case 'p': case 'P': case 's': |
| switch(ptrSizeof){ |
| case 4: return Number; |
| case 8: return affirmBigIntArray() && 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 their WASM |
| pointers. The pointers are accessible via |
| boundObject.pointer, which is gated behind an accessor |
| function, but are not exposed anywhere else in the |
| object. The main intention of that is to make it impossible |
| for stale copies to be made. |
| */ |
| const __instancePointerMap = new WeakMap(); |
| |
| /** Property name for the pointer-is-external marker. */ |
| const xPtrPropName = '(pointer-is-external)'; |
| |
| /** Frees the obj.pointer memory and clears the pointer |
| property. */ |
| const __freeStruct = function(ctor, obj, m){ |
| if(!m) m = __instancePointerMap.get(obj); |
| if(m) { |
| __instancePointerMap.delete(obj); |
| if(Array.isArray(obj.ondispose)){ |
| let x; |
| while((x = obj.ondispose.shift())){ |
| try{ |
| if(x instanceof Function) x.call(obj); |
| else if(x instanceof StructType) x.dispose(); |
| else if('number' === typeof 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:",(obj[xPtrPropName]?"EXTERNAL":""), |
| ctor.structName,"instance:", |
| ctor.structInfo.sizeof,"bytes @"+m); |
| } |
| if(!obj[xPtrPropName]) dealloc(m); |
| } |
| }; |
| |
| /** Returns a skeleton for a read-only property accessor wrapping |
| value v. */ |
| const rop = (v)=>{return {configurable: false, writable: false, |
| iterable: false, value: v}}; |
| |
| /** Allocates obj's memory buffer based on the size defined in |
| ctor.structInfo.sizeof. */ |
| const __allocStruct = function(ctor, obj, m){ |
| let fill = !m; |
| if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); |
| else{ |
| m = alloc(ctor.structInfo.sizeof); |
| if(!m) toss("Allocation of",ctor.structName,"structure failed."); |
| } |
| try { |
| if(ctor.debugFlags.__flags.alloc){ |
| log("debug.alloc:",(fill?"":"EXTERNAL"), |
| ctor.structName,"instance:", |
| ctor.structInfo.sizeof,"bytes @"+m); |
| } |
| if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); |
| __instancePointerMap.set(obj, m); |
| }catch(e){ |
| __freeStruct(ctor, obj, m); |
| throw e; |
| } |
| }; |
| /** Gets installed as the memoryDump() method of all structs. */ |
| const __memoryDump = function(){ |
| const p = this.pointer; |
| return p |
| ? new Uint8Array(heap().slice(p, p+this.structInfo.sizeof)) |
| : null; |
| }; |
| |
| const __memberKey = (k)=>memberPrefix + k + memberSuffix; |
| const __memberKeyProp = rop(__memberKey); |
| |
| /** |
| 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,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; |
| }; |
| |
| const __ptrPropDescriptor = { |
| configurable: false, enumerable: false, |
| get: function(){return __instancePointerMap.get(this)}, |
| 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. |
| }; |
| |
| /** 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){ |
| 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, mem); |
| h[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 ctor(structName, structInfo){ |
| if(arguments[2]!==rop){ |
| toss("Do not call the StructType constructor", |
| "from client-level code."); |
| } |
| Object.defineProperties(this,{ |
| //isA: rop((v)=>v instanceof ctor), |
| 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(__memoryDump), |
| pointer: __ptrPropDescriptor, |
| 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)=>(v instanceof StructType) && !!v[xPtrPropName]), |
| memberKey: __memberKeyProp |
| }); |
| |
| const isNumericValue = (v)=>Number.isFinite(v) || (v instanceof (BigInt || Number)); |
| |
| /** |
| Pass this a StructBinder-generated prototype, 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 descr to make certain downstream |
| operations much simpler. |
| */ |
| const makeMemberWrapper = function f(ctor,name, descr){ |
| if(!f._){ |
| /*cache all available getters/setters/set-wrappers for |
| direct reuse in each accessor function. */ |
| f._ = {getters: {}, setters: {}, sw:{}}; |
| const a = ['i','c','C','p','P','s','f','d','v()']; |
| if(bigIntEnabled) a.push('j'); |
| a.forEach(function(v){ |
| //const ir = sigIR(v); |
| f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; |
| f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; |
| f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values |
| for conversion */; |
| }); |
| const rxSig1 = /^[ipPsjfdcC]$/, |
| rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; |
| f.sigCheck = function(obj, name, key,sig){ |
| if(Object.prototype.hasOwnProperty.call(obj, key)){ |
| toss(obj.structName,'already has a property named',key+'.'); |
| } |
| rxSig1.test(sig) || rxSig2.test(sig) |
| || toss("Malformed signature for", |
| sPropName(obj.structName,name)+":",sig); |
| }; |
| } |
| const key = ctor.memberKey(name); |
| f.sigCheck(ctor.prototype, name, key, descr.signature); |
| descr.key = key; |
| descr.name = name; |
| const sigGlyph = sigLetter(descr.signature); |
| const xPropName = sPropName(ctor.prototype.structName,key); |
| const dbg = ctor.prototype.debugFlags.__flags; |
| /* |
| TODO?: set prototype of descr 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 prop = Object.create(null); |
| prop.configurable = false; |
| prop.enumerable = false; |
| prop.get = function(){ |
| if(dbg.getter){ |
| log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), |
| xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof); |
| } |
| let rc = ( |
| new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) |
| )[f._.getters[sigGlyph]](0, isLittleEndian); |
| if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); |
| return rc; |
| }; |
| if(descr.readOnly){ |
| prop.set = __propThrowOnSet(ctor.prototype.structName,key); |
| }else{ |
| prop.set = function(v){ |
| if(dbg.setter){ |
| log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), |
| xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v); |
| } |
| if(!this.pointer){ |
| toss("Cannot set struct property on disposed instance."); |
| } |
| if(null===v) v = 0; |
| else while(!isNumericValue(v)){ |
| if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){ |
| // It's a struct instance: let's store its pointer value! |
| v = v.pointer || 0; |
| if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v); |
| break; |
| } |
| toss("Invalid value for pointer-type",xPropName+'.'); |
| } |
| ( |
| new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof) |
| )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); |
| }; |
| } |
| Object.defineProperty(ctor.prototype, key, prop); |
| }/*makeMemberWrapper*/; |
| |
| /** |
| The main factory function which will be returned to the |
| caller. |
| */ |
| const StructBinder = function StructBinder(structName, structInfo){ |
| if(1===arguments.length){ |
| structInfo = structName; |
| structName = structInfo.name; |
| }else if(!structInfo.name){ |
| structInfo.name = structName; |
| } |
| if(!structName) toss("Struct name is required."); |
| let lastMember = false; |
| Object.keys(structInfo.members).forEach((k)=>{ |
| // Sanity checks of sizeof/offset info... |
| const m = structInfo.members[k]; |
| if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); |
| else if(m.sizeof===1){ |
| (m.signature === 'c' || m.signature === 'C') || |
| toss("Unexpected sizeof==1 member", |
| sPropName(structInfo.name,k), |
| "with signature",m.signature); |
| }else{ |
| // sizes and offsets of size-1 members may be odd values, but |
| // others may not. |
| if(0!==(m.sizeof%4)){ |
| console.warn("Invalid struct member description =",m,"from",structInfo); |
| toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof); |
| } |
| if(0!==(m.offset%4)){ |
| console.warn("Invalid struct member description =",m,"from",structInfo); |
| toss(structName,"member",k,"offset is not aligned. offset="+m.offset); |
| } |
| } |
| if(!lastMember || lastMember.offset < m.offset) lastMember = m; |
| }); |
| if(!lastMember) toss("No member property descriptions found."); |
| else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){ |
| toss("Invalid struct config:",structName, |
| "max member offset ("+lastMember.offset+") ", |
| "extends past end of struct (sizeof="+structInfo.sizeof+")."); |
| } |
| const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags)); |
| /** Constructor for the StructCtor. */ |
| const StructCtor = function StructCtor(externalMemory){ |
| if(!(this instanceof StructCtor)){ |
| toss("The",structName,"constructor may only be called via 'new'."); |
| }else if(arguments.length){ |
| if(externalMemory!==(externalMemory|0) || externalMemory<=0){ |
| toss("Invalid pointer value for",structName,"constructor."); |
| } |
| __allocStruct(StructCtor, this, externalMemory); |
| }else{ |
| __allocStruct(StructCtor, this); |
| } |
| }; |
| Object.defineProperties(StructCtor,{ |
| debugFlags: debugFlags, |
| isA: rop((v)=>v instanceof StructCtor), |
| memberKey: __memberKeyProp, |
| memberKeys: __structMemberKeys, |
| methodInfoForKey: rop(function(mKey){ |
| }), |
| structInfo: rop(structInfo), |
| structName: rop(structName) |
| }); |
| StructCtor.prototype = new StructType(structName, structInfo, rop); |
| Object.defineProperties(StructCtor.prototype,{ |
| debugFlags: debugFlags, |
| constructor: rop(StructCtor) |
| /*if we assign StructCtor.prototype and don't do |
| this then StructCtor!==instance.constructor!*/ |
| }); |
| Object.keys(structInfo.members).forEach( |
| (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name]) |
| ); |
| return StructCtor; |
| }; |
| StructBinder.StructType = StructType; |
| StructBinder.config = config; |
| StructBinder.allocCString = __allocCString; |
| if(!StructBinder.debugFlags){ |
| StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags); |
| } |
| return StructBinder; |
| }/*StructBinderFactory*/; |