Disassembler.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. 'use strict';
  2. const {Duplex} = require('stream');
  3. class Pump {
  4. constructor(iterator, readable) {
  5. this.iterator = iterator;
  6. this.readable = readable;
  7. this.done = false;
  8. }
  9. start() {
  10. if (this.done) return Promise.resolve();
  11. for (;;) {
  12. const item = this.iterator.next();
  13. if (item.done) return Promise.resolve();
  14. if (!this.readable.push(item.value)) break;
  15. }
  16. return new Promise((resolve, reject) => {
  17. this.resolve = resolve;
  18. this.reject = reject;
  19. });
  20. }
  21. resume() {
  22. if (!this.resolve) return;
  23. for (;;) {
  24. const item = this.iterator.next();
  25. if (item.done) {
  26. this.done = true;
  27. this.resolve();
  28. return;
  29. }
  30. if (!this.readable.push(item.value)) break;
  31. }
  32. }
  33. }
  34. function* dump(value, options, processed) {
  35. if (!processed) {
  36. if (typeof value?.toJSON == 'function') {
  37. value = value.toJSON('');
  38. }
  39. if (options.replacer) {
  40. value = options.replacer('', value);
  41. }
  42. }
  43. switch (typeof value) {
  44. case 'function':
  45. case 'symbol':
  46. case 'undefined':
  47. return;
  48. case 'number':
  49. if (isNaN(value) || !isFinite(value)) {
  50. yield {name: 'nullValue', value: null};
  51. }
  52. value = String(value);
  53. if (options.streamNumbers) {
  54. yield {name: 'startNumber'};
  55. yield {name: 'numberChunk', value};
  56. yield {name: 'endNumber'};
  57. }
  58. if (options.packNumbers) {
  59. yield {name: 'numberValue', value};
  60. }
  61. return;
  62. case 'string':
  63. if (options.streamStrings) {
  64. yield {name: 'startString'};
  65. yield {name: 'stringChunk', value};
  66. yield {name: 'endString'};
  67. }
  68. if (options.packStrings) {
  69. yield {name: 'stringValue', value};
  70. }
  71. return;
  72. case 'boolean':
  73. yield value ? {name: 'trueValue', value: true} : {name: 'falseValue', value: false};
  74. return;
  75. case 'object':
  76. break;
  77. default:
  78. return; // skip anything else
  79. }
  80. // null
  81. if (value === null) {
  82. yield {name: 'nullValue', value: null};
  83. return;
  84. }
  85. // Array
  86. if (Array.isArray(value)) {
  87. yield {name: 'startArray'};
  88. for (let i = 0; i < value.length; ++i) {
  89. let v = value[i];
  90. if (typeof v?.toJSON == 'function') {
  91. v = v.toJSON(String(i));
  92. }
  93. if (options.replacer) {
  94. v = options.replacer(String(i), v);
  95. }
  96. switch (typeof v) {
  97. case 'function':
  98. case 'symbol':
  99. case 'undefined':
  100. v = null; // force null
  101. break;
  102. }
  103. yield* dump(v, options, true);
  104. }
  105. yield {name: 'endArray'};
  106. return;
  107. }
  108. // Object
  109. yield {name: 'startObject'};
  110. for (let [k, v] of Object.entries(value)) {
  111. if (options.dict && options.dict[k] !== 1) continue;
  112. if (typeof v?.toJSON == 'function') {
  113. v = v.toJSON(k);
  114. }
  115. if (options.replacer) {
  116. v = options.replacer(k, v);
  117. }
  118. switch (typeof v) {
  119. case 'function':
  120. case 'symbol':
  121. case 'undefined':
  122. continue;
  123. }
  124. if (options.streamKeys) {
  125. yield {name: 'startKey'};
  126. yield {name: 'stringChunk', value: k};
  127. yield {name: 'endKey'};
  128. }
  129. if (options.packKeys) {
  130. yield {name: 'keyValue', value: k};
  131. }
  132. yield* dump(v, options, true);
  133. }
  134. yield {name: 'endObject'};
  135. }
  136. const kOptions = Symbol('options'),
  137. kPump = Symbol('pump');
  138. class Disassembler extends Duplex {
  139. static make(options) {
  140. return new Disassembler(options);
  141. }
  142. constructor(options) {
  143. super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
  144. const opt = {packKeys: true, packStrings: true, packNumbers: true, streamKeys: true, streamStrings: true, streamNumbers: true};
  145. if (options) {
  146. 'packValues' in options && (opt.packKeys = opt.packStrings = opt.packNumbers = options.packValues);
  147. 'packKeys' in options && (opt.packKeys = options.packKeys);
  148. 'packStrings' in options && (opt.packStrings = options.packStrings);
  149. 'packNumbers' in options && (opt.packNumbers = options.packNumbers);
  150. 'streamValues' in options && (opt.streamKeys = opt.streamStrings = opt.streamNumbers = options.streamValues);
  151. 'streamKeys' in options && (opt.streamKeys = options.streamKeys);
  152. 'streamStrings' in options && (opt.streamStrings = options.streamStrings);
  153. 'streamNumbers' in options && (opt.streamNumbers = options.streamNumbers);
  154. if (typeof options.replacer == 'function') {
  155. opt.replacer = options.replacer;
  156. } else if (Array.isArray(options.replacer)) {
  157. opt.dict = options.replacer.reduce((acc, k) => ((acc[k] = 1), acc), {});
  158. }
  159. }
  160. !opt.packKeys && (opt.streamKeys = true);
  161. !opt.packStrings && (opt.streamStrings = true);
  162. !opt.packNumbers && (opt.streamNumbers = true);
  163. this[kOptions] = opt;
  164. }
  165. _read() {
  166. this[kPump]?.resume();
  167. }
  168. _write(chunk, _, callback) {
  169. const iterator = dump(chunk, this[kOptions]),
  170. pump = (this[kPump] = new Pump(iterator, this));
  171. pump.start().then(
  172. () => ((this[kPump] = null), callback()),
  173. error => ((this[kPump] = null), callback(error))
  174. );
  175. }
  176. _final(callback) {
  177. this.push(null);
  178. callback();
  179. }
  180. }
  181. Disassembler.disassembler = Disassembler.make;
  182. Disassembler.make.Constructor = Disassembler;
  183. module.exports = Disassembler;
  184. module.exports.dump = dump;