mirror of
https://github.com/sqlite/sqlite.git
synced 2025-12-21 13:38:01 +03:00
1007 lines
35 KiB
JavaScript
1007 lines
35 KiB
JavaScript
/**
|
|
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 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(' '))};
|
|
|
|
if(!(config.heap instanceof WebAssembly.Memory)
|
|
&& !(config.heap instanceof Function)){
|
|
toss("config.heap must be WebAssembly.Memory instance or a function which returns one.");
|
|
}
|
|
['alloc','dealloc'].forEach(function(k){
|
|
(config[k] instanceof Function) ||
|
|
toss("Config option '"+k+"' must be a function.");
|
|
});
|
|
const __heap = config.heap;
|
|
const SBF = StructBinderFactory;
|
|
const heap = __heap ? __heap : ()=>new Uint8Array(__heap.buffer),
|
|
alloc = config.alloc,
|
|
dealloc = config.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 = alloc(1);
|
|
const ptrIR = config.pointerIR
|
|
|| config.ptrIR/*deprecated*/
|
|
|| ('bigint'===typeof ptr ? 'i64' : 'i32');
|
|
/* Undocumented (on purpose) config options: */
|
|
const ptrSize = config.ptrSize/*deprecated*/
|
|
|| ('i32'===ptrIR ? 4 : 8);
|
|
dealloc(ptr);
|
|
ptr = undefined;
|
|
//console.warn("ptrIR =",ptrIR,"ptrSize =",ptrSize);
|
|
|
|
if(ptrIR!=='i32' && ptrIR!=='i64') toss("Invalid pointer representation:",ptrIR);
|
|
if(ptrSize!==4 && ptrSize!==8) toss("Invalid pointer size:",ptrSize);
|
|
|
|
/** 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;
|
|
};
|
|
|
|
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-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 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);
|
|
};
|
|
/** Returns the WASM sizeof of the Emscripten-conventional 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 their WASM pointers. The pointers are
|
|
accessible via boundObject.pointer, which is gated behind a
|
|
property interceptor, but are not exposed anywhere else in the
|
|
object.
|
|
|
|
This approach means we cannot proxy arrays, or any type which
|
|
might be realloced, as that pointer could change out from under
|
|
us. That's not an issue for nested structs, but it might be for a
|
|
struct they're embedded in. In the case of nested structs we
|
|
"could" record their top-most parent object and their offset into
|
|
that object, instead of storing the pointer itself. We could that
|
|
by allowing a function instead of a pointer in this map, that
|
|
function returning the (lazily-calculated) address. Hmm.
|
|
*/
|
|
const __instancePointerMap = new WeakMap();
|
|
|
|
const getInstancePtr = (obj)=>__instancePointerMap.get(obj);
|
|
|
|
/** Property name for the pointer-is-external marker. */
|
|
const xPtrPropName = Symbol('(pointer-is-external)');
|
|
|
|
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;
|
|
|
|
/** Frees the obj.pointer memory and clears the pointer
|
|
property. */
|
|
const __freeStruct = function(ctor, obj, m){
|
|
if(!m) m = getInstancePtr(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(__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:",(obj[xPtrPropName]?"EXTERNAL":""),
|
|
ctor.structName,"instance:",
|
|
ctor.structInfo.sizeof,"bytes @"+m);
|
|
}
|
|
if(!obj[xPtrPropName]){
|
|
if( ctor.structInfo.zeroOnFree ){
|
|
heap().fill(0, Number(m), Number(m)+ctor.structInfo.sizeof);
|
|
}
|
|
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, Number(m), Number(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(Number(p), Number(p) + this.structInfo.sizeof))
|
|
: null;
|
|
};
|
|
|
|
/** 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){
|
|
/*if( looksLikeASig(key) ){
|
|
toss("Getter adaptor's name (",key,") collides with a data type signature.");
|
|
}*/
|
|
return __adaptor(this, 0, key, ...args);
|
|
};
|
|
|
|
// StructBinder::adaptSet()
|
|
const __adaptSet = function(key, ...args){
|
|
if( looksLikeASig(key) ){
|
|
toss("Setter adaptor's name (",key,") collides with a data type signature.");
|
|
}
|
|
return __adaptor(this, 1, key, ...args);
|
|
};
|
|
|
|
// StructBinder::adaptStruct()
|
|
const __adaptStruct = function(key, ...args){
|
|
return __adaptor(this, 2, key, ...args);
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
const __ptrPropDescriptor = {
|
|
configurable: false, enumerable: false,
|
|
get: function(){return getInstancePtr(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){
|
|
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 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
|
|
});
|
|
|
|
/**
|
|
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.zeroOnFree 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 = si.constructor.prototype.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()*/;
|
|
|
|
/**
|
|
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 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){
|
|
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 */;
|
|
});
|
|
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.prototype.structName,key);
|
|
const dbg = ctor.prototype.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(){
|
|
if(dbg.getter){
|
|
log("debug.getter:",f._.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._.getters[sigGlyph]](0, isLittleEndian);
|
|
if(getterProxy) rc = getterProxy.apply(this,[rc,key]);
|
|
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){
|
|
if(dbg.setter){
|
|
log("debug.setter:",f._.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 struct instance.");
|
|
}
|
|
if( setterProxy ) v = setterProxy.apply(this,[v]);
|
|
if( null===v || undefined===v ) v = __NullPtr;
|
|
else while( isPtrSig(si.signature) && !__isPtr(v) ){
|
|
if(isAutoPtrSig(si.signature) && (v instanceof StructType)){
|
|
// It's a struct instance: let's store its pointer value!
|
|
v = v.pointer || __NullPtr;
|
|
if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v);
|
|
break;
|
|
}
|
|
toss("Invalid value for pointer-type",xPropName+'.');
|
|
}
|
|
(
|
|
new DataView(heap().buffer, Number(this.pointer) + si.offset,
|
|
si.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. 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.
|
|
*/
|
|
const StructCtor = function StructCtor(externalMemory){
|
|
externalMemory = __asPtrType(externalMemory);
|
|
//console.warn("externalMemory",externalMemory,arguments[0]);
|
|
if(!(this instanceof StructCtor)){
|
|
toss("The",structName,"constructor may only be called via 'new'.");
|
|
}else if(arguments.length){
|
|
if( !__isPtr(externalMemory) ){
|
|
toss("Invalid pointer value",arguments[0],"for",structName,"constructor.");
|
|
}
|
|
__allocStruct(StructCtor, this, externalMemory);
|
|
}else{
|
|
__allocStruct(StructCtor, this);
|
|
}
|
|
};
|
|
const self = this;
|
|
/**
|
|
"Convert" struct description x to a struct description, if
|
|
needed.
|
|
*/
|
|
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 ){
|
|
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)
|
|
});
|
|
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*/
|
|
});
|
|
|
|
|
|
let lastMember = false;
|
|
let offset = 0;
|
|
//console.warn(structName,"si =",si);
|
|
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 ){
|
|
m.offset = offset;
|
|
}
|
|
si.members[k] = m;
|
|
if(!lastMember || lastMember.offset < m.offset) lastMember = m;
|
|
makeMemberWrapper.call(self, StructCtor, k, m)
|
|
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);
|
|
}
|
|
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;
|
|
if(!StructBinder.debugFlags){
|
|
StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags);
|
|
}
|
|
return StructBinder;
|
|
}/*StructBinderFactory*/;
|