array.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { defineProperties, isError, assert, assertArgument, assertArgumentCount } from "../../utils/index.js";
  2. import { Typed } from "../typed.js";
  3. import { Coder, Result, WordSize, Writer } from "./abstract-coder.js";
  4. import { AnonymousCoder } from "./anonymous.js";
  5. /**
  6. * @_ignore
  7. */
  8. export function pack(writer, coders, values) {
  9. let arrayValues = [];
  10. if (Array.isArray(values)) {
  11. arrayValues = values;
  12. }
  13. else if (values && typeof (values) === "object") {
  14. let unique = {};
  15. arrayValues = coders.map((coder) => {
  16. const name = coder.localName;
  17. assert(name, "cannot encode object for signature with missing names", "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
  18. assert(!unique[name], "cannot encode object for signature with duplicate names", "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
  19. unique[name] = true;
  20. return values[name];
  21. });
  22. }
  23. else {
  24. assertArgument(false, "invalid tuple value", "tuple", values);
  25. }
  26. assertArgument(coders.length === arrayValues.length, "types/value length mismatch", "tuple", values);
  27. let staticWriter = new Writer();
  28. let dynamicWriter = new Writer();
  29. let updateFuncs = [];
  30. coders.forEach((coder, index) => {
  31. let value = arrayValues[index];
  32. if (coder.dynamic) {
  33. // Get current dynamic offset (for the future pointer)
  34. let dynamicOffset = dynamicWriter.length;
  35. // Encode the dynamic value into the dynamicWriter
  36. coder.encode(dynamicWriter, value);
  37. // Prepare to populate the correct offset once we are done
  38. let updateFunc = staticWriter.writeUpdatableValue();
  39. updateFuncs.push((baseOffset) => {
  40. updateFunc(baseOffset + dynamicOffset);
  41. });
  42. }
  43. else {
  44. coder.encode(staticWriter, value);
  45. }
  46. });
  47. // Backfill all the dynamic offsets, now that we know the static length
  48. updateFuncs.forEach((func) => { func(staticWriter.length); });
  49. let length = writer.appendWriter(staticWriter);
  50. length += writer.appendWriter(dynamicWriter);
  51. return length;
  52. }
  53. /**
  54. * @_ignore
  55. */
  56. export function unpack(reader, coders) {
  57. let values = [];
  58. let keys = [];
  59. // A reader anchored to this base
  60. let baseReader = reader.subReader(0);
  61. coders.forEach((coder) => {
  62. let value = null;
  63. if (coder.dynamic) {
  64. let offset = reader.readIndex();
  65. let offsetReader = baseReader.subReader(offset);
  66. try {
  67. value = coder.decode(offsetReader);
  68. }
  69. catch (error) {
  70. // Cannot recover from this
  71. if (isError(error, "BUFFER_OVERRUN")) {
  72. throw error;
  73. }
  74. value = error;
  75. value.baseType = coder.name;
  76. value.name = coder.localName;
  77. value.type = coder.type;
  78. }
  79. }
  80. else {
  81. try {
  82. value = coder.decode(reader);
  83. }
  84. catch (error) {
  85. // Cannot recover from this
  86. if (isError(error, "BUFFER_OVERRUN")) {
  87. throw error;
  88. }
  89. value = error;
  90. value.baseType = coder.name;
  91. value.name = coder.localName;
  92. value.type = coder.type;
  93. }
  94. }
  95. if (value == undefined) {
  96. throw new Error("investigate");
  97. }
  98. values.push(value);
  99. keys.push(coder.localName || null);
  100. });
  101. return Result.fromItems(values, keys);
  102. }
  103. /**
  104. * @_ignore
  105. */
  106. export class ArrayCoder extends Coder {
  107. coder;
  108. length;
  109. constructor(coder, length, localName) {
  110. const type = (coder.type + "[" + (length >= 0 ? length : "") + "]");
  111. const dynamic = (length === -1 || coder.dynamic);
  112. super("array", type, localName, dynamic);
  113. defineProperties(this, { coder, length });
  114. }
  115. defaultValue() {
  116. // Verifies the child coder is valid (even if the array is dynamic or 0-length)
  117. const defaultChild = this.coder.defaultValue();
  118. const result = [];
  119. for (let i = 0; i < this.length; i++) {
  120. result.push(defaultChild);
  121. }
  122. return result;
  123. }
  124. encode(writer, _value) {
  125. const value = Typed.dereference(_value, "array");
  126. if (!Array.isArray(value)) {
  127. this._throwError("expected array value", value);
  128. }
  129. let count = this.length;
  130. if (count === -1) {
  131. count = value.length;
  132. writer.writeValue(value.length);
  133. }
  134. assertArgumentCount(value.length, count, "coder array" + (this.localName ? (" " + this.localName) : ""));
  135. let coders = [];
  136. for (let i = 0; i < value.length; i++) {
  137. coders.push(this.coder);
  138. }
  139. return pack(writer, coders, value);
  140. }
  141. decode(reader) {
  142. let count = this.length;
  143. if (count === -1) {
  144. count = reader.readIndex();
  145. // Check that there is *roughly* enough data to ensure
  146. // stray random data is not being read as a length. Each
  147. // slot requires at least 32 bytes for their value (or 32
  148. // bytes as a link to the data). This could use a much
  149. // tighter bound, but we are erroring on the side of safety.
  150. assert(count * WordSize <= reader.dataLength, "insufficient data length", "BUFFER_OVERRUN", { buffer: reader.bytes, offset: count * WordSize, length: reader.dataLength });
  151. }
  152. let coders = [];
  153. for (let i = 0; i < count; i++) {
  154. coders.push(new AnonymousCoder(this.coder));
  155. }
  156. return unpack(reader, coders);
  157. }
  158. }
  159. //# sourceMappingURL=array.js.map