jaccwabyt.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. /**
  2. 2022-06-30
  3. The author disclaims copyright to this source code. In place of a
  4. legal notice, here is a blessing:
  5. * May you do good and not evil.
  6. * May you find forgiveness for yourself and forgive others.
  7. * May you share freely, never taking more than you give.
  8. ***********************************************************************
  9. The Jaccwabyt API is documented in detail in an external file,
  10. _possibly_ called jaccwabyt.md in the same directory as this file.
  11. Project homes:
  12. - https://fossil.wanderinghorse.net/r/jaccwabyt
  13. - https://sqlite.org/src/dir/ext/wasm/jaccwabyt
  14. */
  15. 'use strict';
  16. globalThis.Jaccwabyt = function StructBinderFactory(config){
  17. /* ^^^^ it is recommended that clients move that object into wherever
  18. they'd like to have it and delete the self-held copy ("self" being
  19. the global window or worker object). This API does not require the
  20. global reference - it is simply installed as a convenience for
  21. connecting these bits to other co-developed code before it gets
  22. removed from the global namespace.
  23. */
  24. /** Throws a new Error, the message of which is the concatenation
  25. all args with a space between each. */
  26. const toss = (...args)=>{throw new Error(args.join(' '))};
  27. /**
  28. Implementing function bindings revealed significant
  29. shortcomings in Emscripten's addFunction()/removeFunction()
  30. interfaces:
  31. https://github.com/emscripten-core/emscripten/issues/17323
  32. Until those are resolved, or a suitable replacement can be
  33. implemented, our function-binding API will be more limited
  34. and/or clumsier to use than initially hoped.
  35. */
  36. if(!(config.heap instanceof WebAssembly.Memory)
  37. && !(config.heap instanceof Function)){
  38. toss("config.heap must be WebAssembly.Memory instance or a function.");
  39. }
  40. ['alloc','dealloc'].forEach(function(k){
  41. (config[k] instanceof Function) ||
  42. toss("Config option '"+k+"' must be a function.");
  43. });
  44. const SBF = StructBinderFactory;
  45. const heap = (config.heap instanceof Function)
  46. ? config.heap : (()=>new Uint8Array(config.heap.buffer)),
  47. alloc = config.alloc,
  48. dealloc = config.dealloc,
  49. log = config.log || console.log.bind(console),
  50. memberPrefix = (config.memberPrefix || ""),
  51. memberSuffix = (config.memberSuffix || ""),
  52. bigIntEnabled = (undefined===config.bigIntEnabled
  53. ? !!globalThis['BigInt64Array'] : !!config.bigIntEnabled),
  54. BigInt = globalThis['BigInt'],
  55. BigInt64Array = globalThis['BigInt64Array'],
  56. /* Undocumented (on purpose) config options: */
  57. ptrSizeof = config.ptrSizeof || 4,
  58. ptrIR = config.ptrIR || 'i32'
  59. ;
  60. if(!SBF.debugFlags){
  61. SBF.__makeDebugFlags = function(deriveFrom=null){
  62. /* This is disgustingly overengineered. :/ */
  63. if(deriveFrom && deriveFrom.__flags) deriveFrom = deriveFrom.__flags;
  64. const f = function f(flags){
  65. if(0===arguments.length){
  66. return f.__flags;
  67. }
  68. if(flags<0){
  69. delete f.__flags.getter; delete f.__flags.setter;
  70. delete f.__flags.alloc; delete f.__flags.dealloc;
  71. }else{
  72. f.__flags.getter = 0!==(0x01 & flags);
  73. f.__flags.setter = 0!==(0x02 & flags);
  74. f.__flags.alloc = 0!==(0x04 & flags);
  75. f.__flags.dealloc = 0!==(0x08 & flags);
  76. }
  77. return f._flags;
  78. };
  79. Object.defineProperty(f,'__flags', {
  80. iterable: false, writable: false,
  81. value: Object.create(deriveFrom)
  82. });
  83. if(!deriveFrom) f(0);
  84. return f;
  85. };
  86. SBF.debugFlags = SBF.__makeDebugFlags();
  87. }/*static init*/
  88. const isLittleEndian = (function() {
  89. const buffer = new ArrayBuffer(2);
  90. new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
  91. // Int16Array uses the platform's endianness.
  92. return new Int16Array(buffer)[0] === 256;
  93. })();
  94. /**
  95. Some terms used in the internal docs:
  96. StructType: a struct-wrapping class generated by this
  97. framework.
  98. DEF: struct description object.
  99. SIG: struct member signature string.
  100. */
  101. /** True if SIG s looks like a function signature, else
  102. false. */
  103. const isFuncSig = (s)=>'('===s[1];
  104. /** True if SIG s is-a pointer signature. */
  105. const isPtrSig = (s)=>'p'===s || 'P'===s;
  106. const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/;
  107. const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0];
  108. /** Returns the WASM IR form of the Emscripten-conventional letter
  109. at SIG s[0]. Throws for an unknown SIG. */
  110. const sigIR = function(s){
  111. switch(sigLetter(s)){
  112. case 'c': case 'C': return 'i8';
  113. case 'i': return 'i32';
  114. case 'p': case 'P': case 's': return ptrIR;
  115. case 'j': return 'i64';
  116. case 'f': return 'float';
  117. case 'd': return 'double';
  118. }
  119. toss("Unhandled signature IR:",s);
  120. };
  121. const affirmBigIntArray = BigInt64Array
  122. ? ()=>true : ()=>toss('BigInt64Array is not available.');
  123. /** Returns the name of a DataView getter method corresponding
  124. to the given SIG. */
  125. const sigDVGetter = function(s){
  126. switch(sigLetter(s)) {
  127. case 'p': case 'P': case 's': {
  128. switch(ptrSizeof){
  129. case 4: return 'getInt32';
  130. case 8: return affirmBigIntArray() && 'getBigInt64';
  131. }
  132. break;
  133. }
  134. case 'i': return 'getInt32';
  135. case 'c': return 'getInt8';
  136. case 'C': return 'getUint8';
  137. case 'j': return affirmBigIntArray() && 'getBigInt64';
  138. case 'f': return 'getFloat32';
  139. case 'd': return 'getFloat64';
  140. }
  141. toss("Unhandled DataView getter for signature:",s);
  142. };
  143. /** Returns the name of a DataView setter method corresponding
  144. to the given SIG. */
  145. const sigDVSetter = function(s){
  146. switch(sigLetter(s)){
  147. case 'p': case 'P': case 's': {
  148. switch(ptrSizeof){
  149. case 4: return 'setInt32';
  150. case 8: return affirmBigIntArray() && 'setBigInt64';
  151. }
  152. break;
  153. }
  154. case 'i': return 'setInt32';
  155. case 'c': return 'setInt8';
  156. case 'C': return 'setUint8';
  157. case 'j': return affirmBigIntArray() && 'setBigInt64';
  158. case 'f': return 'setFloat32';
  159. case 'd': return 'setFloat64';
  160. }
  161. toss("Unhandled DataView setter for signature:",s);
  162. };
  163. /**
  164. Returns either Number of BigInt, depending on the given
  165. SIG. This constructor is used in property setters to coerce
  166. the being-set value to the correct size.
  167. */
  168. const sigDVSetWrapper = function(s){
  169. switch(sigLetter(s)) {
  170. case 'i': case 'f': case 'c': case 'C': case 'd': return Number;
  171. case 'j': return affirmBigIntArray() && BigInt;
  172. case 'p': case 'P': case 's':
  173. switch(ptrSizeof){
  174. case 4: return Number;
  175. case 8: return affirmBigIntArray() && BigInt;
  176. }
  177. break;
  178. }
  179. toss("Unhandled DataView set wrapper for signature:",s);
  180. };
  181. /** Returns the given struct and member name in a form suitable for
  182. debugging and error output. */
  183. const sPropName = (s,k)=>s+'::'+k;
  184. const __propThrowOnSet = function(structName,propName){
  185. return ()=>toss(sPropName(structName,propName),"is read-only.");
  186. };
  187. /**
  188. In order to completely hide StructBinder-bound struct
  189. pointers from JS code, we store them in a scope-local
  190. WeakMap which maps the struct-bound objects to their WASM
  191. pointers. The pointers are accessible via
  192. boundObject.pointer, which is gated behind an accessor
  193. function, but are not exposed anywhere else in the
  194. object. The main intention of that is to make it impossible
  195. for stale copies to be made.
  196. */
  197. const __instancePointerMap = new WeakMap();
  198. /** Property name for the pointer-is-external marker. */
  199. const xPtrPropName = '(pointer-is-external)';
  200. /** Frees the obj.pointer memory and clears the pointer
  201. property. */
  202. const __freeStruct = function(ctor, obj, m){
  203. if(!m) m = __instancePointerMap.get(obj);
  204. if(m) {
  205. __instancePointerMap.delete(obj);
  206. if(Array.isArray(obj.ondispose)){
  207. let x;
  208. while((x = obj.ondispose.shift())){
  209. try{
  210. if(x instanceof Function) x.call(obj);
  211. else if(x instanceof StructType) x.dispose();
  212. else if('number' === typeof x) dealloc(x);
  213. // else ignore. Strings are permitted to annotate entries
  214. // to assist in debugging.
  215. }catch(e){
  216. console.warn("ondispose() for",ctor.structName,'@',
  217. m,'threw. NOT propagating it.',e);
  218. }
  219. }
  220. }else if(obj.ondispose instanceof Function){
  221. try{obj.ondispose()}
  222. catch(e){
  223. /*do not rethrow: destructors must not throw*/
  224. console.warn("ondispose() for",ctor.structName,'@',
  225. m,'threw. NOT propagating it.',e);
  226. }
  227. }
  228. delete obj.ondispose;
  229. if(ctor.debugFlags.__flags.dealloc){
  230. log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
  231. ctor.structName,"instance:",
  232. ctor.structInfo.sizeof,"bytes @"+m);
  233. }
  234. if(!obj[xPtrPropName]) dealloc(m);
  235. }
  236. };
  237. /** Returns a skeleton for a read-only property accessor wrapping
  238. value v. */
  239. const rop = (v)=>{return {configurable: false, writable: false,
  240. iterable: false, value: v}};
  241. /** Allocates obj's memory buffer based on the size defined in
  242. ctor.structInfo.sizeof. */
  243. const __allocStruct = function(ctor, obj, m){
  244. let fill = !m;
  245. if(m) Object.defineProperty(obj, xPtrPropName, rop(m));
  246. else{
  247. m = alloc(ctor.structInfo.sizeof);
  248. if(!m) toss("Allocation of",ctor.structName,"structure failed.");
  249. }
  250. try {
  251. if(ctor.debugFlags.__flags.alloc){
  252. log("debug.alloc:",(fill?"":"EXTERNAL"),
  253. ctor.structName,"instance:",
  254. ctor.structInfo.sizeof,"bytes @"+m);
  255. }
  256. if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof);
  257. __instancePointerMap.set(obj, m);
  258. }catch(e){
  259. __freeStruct(ctor, obj, m);
  260. throw e;
  261. }
  262. };
  263. /** Gets installed as the memoryDump() method of all structs. */
  264. const __memoryDump = function(){
  265. const p = this.pointer;
  266. return p
  267. ? new Uint8Array(heap().slice(p, p+this.structInfo.sizeof))
  268. : null;
  269. };
  270. const __memberKey = (k)=>memberPrefix + k + memberSuffix;
  271. const __memberKeyProp = rop(__memberKey);
  272. /**
  273. Looks up a struct member in structInfo.members. Throws if found
  274. if tossIfNotFound is true, else returns undefined if not
  275. found. The given name may be either the name of the
  276. structInfo.members key (faster) or the key as modified by the
  277. memberPrefix and memberSuffix settings.
  278. */
  279. const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){
  280. let m = structInfo.members[memberName];
  281. if(!m && (memberPrefix || memberSuffix)){
  282. // Check for a match on members[X].key
  283. for(const v of Object.values(structInfo.members)){
  284. if(v.key===memberName){ m = v; break; }
  285. }
  286. if(!m && tossIfNotFound){
  287. toss(sPropName(structInfo.name,memberName),'is not a mapped struct member.');
  288. }
  289. }
  290. return m;
  291. };
  292. /**
  293. Uses __lookupMember(obj.structInfo,memberName) to find a member,
  294. throwing if not found. Returns its signature, either in this
  295. framework's native format or in Emscripten format.
  296. */
  297. const __memberSignature = function f(obj,memberName,emscriptenFormat=false){
  298. if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i');
  299. const m = __lookupMember(obj.structInfo, memberName, true);
  300. return emscriptenFormat ? f._(m.signature) : m.signature;
  301. };
  302. const __ptrPropDescriptor = {
  303. configurable: false, enumerable: false,
  304. get: function(){return __instancePointerMap.get(this)},
  305. set: ()=>toss("Cannot assign the 'pointer' property of a struct.")
  306. // Reminder: leaving `set` undefined makes assignments
  307. // to the property _silently_ do nothing. Current unit tests
  308. // rely on it throwing, though.
  309. };
  310. /** Impl of X.memberKeys() for StructType and struct ctors. */
  311. const __structMemberKeys = rop(function(){
  312. const a = [];
  313. for(const k of Object.keys(this.structInfo.members)){
  314. a.push(this.memberKey(k));
  315. }
  316. return a;
  317. });
  318. const __utf8Decoder = new TextDecoder('utf-8');
  319. const __utf8Encoder = new TextEncoder();
  320. /** Internal helper to use in operations which need to distinguish
  321. between SharedArrayBuffer heap memory and non-shared heap. */
  322. const __SAB = ('undefined'===typeof SharedArrayBuffer)
  323. ? function(){} : SharedArrayBuffer;
  324. const __utf8Decode = function(arrayBuffer, begin, end){
  325. return __utf8Decoder.decode(
  326. (arrayBuffer.buffer instanceof __SAB)
  327. ? arrayBuffer.slice(begin, end)
  328. : arrayBuffer.subarray(begin, end)
  329. );
  330. };
  331. /**
  332. Uses __lookupMember() to find the given obj.structInfo key.
  333. Returns that member if it is a string, else returns false. If the
  334. member is not found, throws if tossIfNotFound is true, else
  335. returns false.
  336. */
  337. const __memberIsString = function(obj,memberName, tossIfNotFound=false){
  338. const m = __lookupMember(obj.structInfo, memberName, tossIfNotFound);
  339. return (m && 1===m.signature.length && 's'===m.signature[0]) ? m : false;
  340. };
  341. /**
  342. Given a member description object, throws if member.signature is
  343. not valid for assigning to or interpretation as a C-style string.
  344. It optimistically assumes that any signature of (i,p,s) is
  345. C-string compatible.
  346. */
  347. const __affirmCStringSignature = function(member){
  348. if('s'===member.signature) return;
  349. toss("Invalid member type signature for C-string value:",
  350. JSON.stringify(member));
  351. };
  352. /**
  353. Looks up the given member in obj.structInfo. If it has a
  354. signature of 's' then it is assumed to be a C-style UTF-8 string
  355. and a decoded copy of the string at its address is returned. If
  356. the signature is of any other type, it throws. If an s-type
  357. member's address is 0, `null` is returned.
  358. */
  359. const __memberToJsString = function f(obj,memberName){
  360. const m = __lookupMember(obj.structInfo, memberName, true);
  361. __affirmCStringSignature(m);
  362. const addr = obj[m.key];
  363. //log("addr =",addr,memberName,"m =",m);
  364. if(!addr) return null;
  365. let pos = addr;
  366. const mem = heap();
  367. for( ; mem[pos]!==0; ++pos ) {
  368. //log("mem[",pos,"]",mem[pos]);
  369. };
  370. //log("addr =",addr,"pos =",pos);
  371. return (addr===pos) ? "" : __utf8Decode(mem, addr, pos);
  372. };
  373. /**
  374. Adds value v to obj.ondispose, creating ondispose,
  375. or converting it to an array, if needed.
  376. */
  377. const __addOnDispose = function(obj, ...v){
  378. if(obj.ondispose){
  379. if(!Array.isArray(obj.ondispose)){
  380. obj.ondispose = [obj.ondispose];
  381. }
  382. }else{
  383. obj.ondispose = [];
  384. }
  385. obj.ondispose.push(...v);
  386. };
  387. /**
  388. Allocates a new UTF-8-encoded, NUL-terminated copy of the given
  389. JS string and returns its address relative to heap(). If
  390. allocation returns 0 this function throws. Ownership of the
  391. memory is transfered to the caller, who must eventually pass it
  392. to the configured dealloc() function.
  393. */
  394. const __allocCString = function(str){
  395. const u = __utf8Encoder.encode(str);
  396. const mem = alloc(u.length+1);
  397. if(!mem) toss("Allocation error while duplicating string:",str);
  398. const h = heap();
  399. //let i = 0;
  400. //for( ; i < u.length; ++i ) h[mem + i] = u[i];
  401. h.set(u, mem);
  402. h[mem + u.length] = 0;
  403. //log("allocCString @",mem," =",u);
  404. return mem;
  405. };
  406. /**
  407. Sets the given struct member of obj to a dynamically-allocated,
  408. UTF-8-encoded, NUL-terminated copy of str. It is up to the caller
  409. to free any prior memory, if appropriate. The newly-allocated
  410. string is added to obj.ondispose so will be freed when the object
  411. is disposed.
  412. The given name may be either the name of the structInfo.members
  413. key (faster) or the key as modified by the memberPrefix and
  414. memberSuffix settings.
  415. */
  416. const __setMemberCString = function(obj, memberName, str){
  417. const m = __lookupMember(obj.structInfo, memberName, true);
  418. __affirmCStringSignature(m);
  419. /* Potential TODO: if obj.ondispose contains obj[m.key] then
  420. dealloc that value and clear that ondispose entry */
  421. const mem = __allocCString(str);
  422. obj[m.key] = mem;
  423. __addOnDispose(obj, mem);
  424. return obj;
  425. };
  426. /**
  427. Prototype for all StructFactory instances (the constructors
  428. returned from StructBinder).
  429. */
  430. const StructType = function ctor(structName, structInfo){
  431. if(arguments[2]!==rop){
  432. toss("Do not call the StructType constructor",
  433. "from client-level code.");
  434. }
  435. Object.defineProperties(this,{
  436. //isA: rop((v)=>v instanceof ctor),
  437. structName: rop(structName),
  438. structInfo: rop(structInfo)
  439. });
  440. };
  441. /**
  442. Properties inherited by struct-type-specific StructType instances
  443. and (indirectly) concrete struct-type instances.
  444. */
  445. StructType.prototype = Object.create(null, {
  446. dispose: rop(function(){__freeStruct(this.constructor, this)}),
  447. lookupMember: rop(function(memberName, tossIfNotFound=true){
  448. return __lookupMember(this.structInfo, memberName, tossIfNotFound);
  449. }),
  450. memberToJsString: rop(function(memberName){
  451. return __memberToJsString(this, memberName);
  452. }),
  453. memberIsString: rop(function(memberName, tossIfNotFound=true){
  454. return __memberIsString(this, memberName, tossIfNotFound);
  455. }),
  456. memberKey: __memberKeyProp,
  457. memberKeys: __structMemberKeys,
  458. memberSignature: rop(function(memberName, emscriptenFormat=false){
  459. return __memberSignature(this, memberName, emscriptenFormat);
  460. }),
  461. memoryDump: rop(__memoryDump),
  462. pointer: __ptrPropDescriptor,
  463. setMemberCString: rop(function(memberName, str){
  464. return __setMemberCString(this, memberName, str);
  465. })
  466. });
  467. // Function-type non-Property inherited members
  468. Object.assign(StructType.prototype,{
  469. addOnDispose: function(...v){
  470. __addOnDispose(this,...v);
  471. return this;
  472. }
  473. });
  474. /**
  475. "Static" properties for StructType.
  476. */
  477. Object.defineProperties(StructType, {
  478. allocCString: rop(__allocCString),
  479. isA: rop((v)=>v instanceof StructType),
  480. hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
  481. memberKey: __memberKeyProp
  482. });
  483. const isNumericValue = (v)=>Number.isFinite(v) || (v instanceof (BigInt || Number));
  484. /**
  485. Pass this a StructBinder-generated prototype, and the struct
  486. member description object. It will define property accessors for
  487. proto[memberKey] which read from/write to memory in
  488. this.pointer. It modifies descr to make certain downstream
  489. operations much simpler.
  490. */
  491. const makeMemberWrapper = function f(ctor,name, descr){
  492. if(!f._){
  493. /*cache all available getters/setters/set-wrappers for
  494. direct reuse in each accessor function. */
  495. f._ = {getters: {}, setters: {}, sw:{}};
  496. const a = ['i','c','C','p','P','s','f','d','v()'];
  497. if(bigIntEnabled) a.push('j');
  498. a.forEach(function(v){
  499. //const ir = sigIR(v);
  500. f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */;
  501. f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */;
  502. f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values
  503. for conversion */;
  504. });
  505. const rxSig1 = /^[ipPsjfdcC]$/,
  506. rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/;
  507. f.sigCheck = function(obj, name, key,sig){
  508. if(Object.prototype.hasOwnProperty.call(obj, key)){
  509. toss(obj.structName,'already has a property named',key+'.');
  510. }
  511. rxSig1.test(sig) || rxSig2.test(sig)
  512. || toss("Malformed signature for",
  513. sPropName(obj.structName,name)+":",sig);
  514. };
  515. }
  516. const key = ctor.memberKey(name);
  517. f.sigCheck(ctor.prototype, name, key, descr.signature);
  518. descr.key = key;
  519. descr.name = name;
  520. const sigGlyph = sigLetter(descr.signature);
  521. const xPropName = sPropName(ctor.prototype.structName,key);
  522. const dbg = ctor.prototype.debugFlags.__flags;
  523. /*
  524. TODO?: set prototype of descr to an object which can set/fetch
  525. its prefered representation, e.g. conversion to string or mapped
  526. function. Advantage: we can avoid doing that via if/else if/else
  527. in the get/set methods.
  528. */
  529. const prop = Object.create(null);
  530. prop.configurable = false;
  531. prop.enumerable = false;
  532. prop.get = function(){
  533. if(dbg.getter){
  534. log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph),
  535. xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof);
  536. }
  537. let rc = (
  538. new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
  539. )[f._.getters[sigGlyph]](0, isLittleEndian);
  540. if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
  541. return rc;
  542. };
  543. if(descr.readOnly){
  544. prop.set = __propThrowOnSet(ctor.prototype.structName,key);
  545. }else{
  546. prop.set = function(v){
  547. if(dbg.setter){
  548. log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph),
  549. xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v);
  550. }
  551. if(!this.pointer){
  552. toss("Cannot set struct property on disposed instance.");
  553. }
  554. if(null===v) v = 0;
  555. else while(!isNumericValue(v)){
  556. if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){
  557. // It's a struct instance: let's store its pointer value!
  558. v = v.pointer || 0;
  559. if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v);
  560. break;
  561. }
  562. toss("Invalid value for pointer-type",xPropName+'.');
  563. }
  564. (
  565. new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
  566. )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian);
  567. };
  568. }
  569. Object.defineProperty(ctor.prototype, key, prop);
  570. }/*makeMemberWrapper*/;
  571. /**
  572. The main factory function which will be returned to the
  573. caller.
  574. */
  575. const StructBinder = function StructBinder(structName, structInfo){
  576. if(1===arguments.length){
  577. structInfo = structName;
  578. structName = structInfo.name;
  579. }else if(!structInfo.name){
  580. structInfo.name = structName;
  581. }
  582. if(!structName) toss("Struct name is required.");
  583. let lastMember = false;
  584. Object.keys(structInfo.members).forEach((k)=>{
  585. // Sanity checks of sizeof/offset info...
  586. const m = structInfo.members[k];
  587. if(!m.sizeof) toss(structName,"member",k,"is missing sizeof.");
  588. else if(m.sizeof===1){
  589. (m.signature === 'c' || m.signature === 'C') ||
  590. toss("Unexpected sizeof==1 member",
  591. sPropName(structInfo.name,k),
  592. "with signature",m.signature);
  593. }else{
  594. // sizes and offsets of size-1 members may be odd values, but
  595. // others may not.
  596. if(0!==(m.sizeof%4)){
  597. console.warn("Invalid struct member description =",m,"from",structInfo);
  598. toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof);
  599. }
  600. if(0!==(m.offset%4)){
  601. console.warn("Invalid struct member description =",m,"from",structInfo);
  602. toss(structName,"member",k,"offset is not aligned. offset="+m.offset);
  603. }
  604. }
  605. if(!lastMember || lastMember.offset < m.offset) lastMember = m;
  606. });
  607. if(!lastMember) toss("No member property descriptions found.");
  608. else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){
  609. toss("Invalid struct config:",structName,
  610. "max member offset ("+lastMember.offset+") ",
  611. "extends past end of struct (sizeof="+structInfo.sizeof+").");
  612. }
  613. const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags));
  614. /** Constructor for the StructCtor. */
  615. const StructCtor = function StructCtor(externalMemory){
  616. if(!(this instanceof StructCtor)){
  617. toss("The",structName,"constructor may only be called via 'new'.");
  618. }else if(arguments.length){
  619. if(externalMemory!==(externalMemory|0) || externalMemory<=0){
  620. toss("Invalid pointer value for",structName,"constructor.");
  621. }
  622. __allocStruct(StructCtor, this, externalMemory);
  623. }else{
  624. __allocStruct(StructCtor, this);
  625. }
  626. };
  627. Object.defineProperties(StructCtor,{
  628. debugFlags: debugFlags,
  629. isA: rop((v)=>v instanceof StructCtor),
  630. memberKey: __memberKeyProp,
  631. memberKeys: __structMemberKeys,
  632. methodInfoForKey: rop(function(mKey){
  633. }),
  634. structInfo: rop(structInfo),
  635. structName: rop(structName)
  636. });
  637. StructCtor.prototype = new StructType(structName, structInfo, rop);
  638. Object.defineProperties(StructCtor.prototype,{
  639. debugFlags: debugFlags,
  640. constructor: rop(StructCtor)
  641. /*if we assign StructCtor.prototype and don't do
  642. this then StructCtor!==instance.constructor!*/
  643. });
  644. Object.keys(structInfo.members).forEach(
  645. (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name])
  646. );
  647. return StructCtor;
  648. };
  649. StructBinder.StructType = StructType;
  650. StructBinder.config = config;
  651. StructBinder.allocCString = __allocCString;
  652. if(!StructBinder.debugFlags){
  653. StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags);
  654. }
  655. return StructBinder;
  656. }/*StructBinderFactory*/;