functionalscript 0.34.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,10 +23,11 @@
23
23
  */
24
24
  import { bitLength, divUp, mask, maxLength, xor } from "../bigint/module.f.js";
25
25
  import { flip, identity } from "../function/module.f.js";
26
- import { fold, iterable, map } from "../list/module.f.js";
26
+ import { iterable, map } from "../list/module.f.js";
27
27
  import { asBase, asNominal } from "../nominal/module.f.js";
28
28
  import { repeat as mRepeat } from "../monoid/module.f.js";
29
29
  import { cmp, max, min } from "../function/compare/module.f.js";
30
+ import { mapUnwrap } from "../nullable/module.f.js";
30
31
  /**
31
32
  * Maximum length of a bit vector in bits (1_048_576 = 0x10_0000).
32
33
  * This limit is enforced by Bun's `bigint` size constraint, the minimal limit
@@ -133,6 +134,64 @@ const op = (norm) => (op) => ap => bp => {
133
134
  const { a, b } = norm(au)(bu)(len);
134
135
  return vec(len)(op(a)(b));
135
136
  };
137
+ const unpackEmpty = { length: 0n, uint: 0n };
138
+ const listToVecOp = (unpackConcat) => ({
139
+ init: { len: 0n, stack: [] },
140
+ update: (v, { len, stack }) => {
141
+ len += v.length;
142
+ if (len > maxLength) {
143
+ return null;
144
+ }
145
+ let i = 0;
146
+ while (true) {
147
+ if (stack.length <= i) {
148
+ stack = [...stack, v];
149
+ break;
150
+ }
151
+ const old = stack[i];
152
+ if (old.length === 0n) {
153
+ stack = stack.toSpliced(i, 1, v);
154
+ break;
155
+ }
156
+ stack = stack.toSpliced(i, 1, unpackEmpty);
157
+ v = unpackConcat(old)(v);
158
+ i++;
159
+ }
160
+ return { len, stack };
161
+ },
162
+ end: ({ stack }) => pack(stack.reduce((p, c) => unpackConcat(c)(p), unpackEmpty))
163
+ });
164
+ /**
165
+ * Concatenates a list of unpacked vectors using a binary-counter accumulator,
166
+ * giving O(n log n) total `bigint` shifting work instead of the O(n²) of a
167
+ * naive left fold.
168
+ *
169
+ * Slot `i` of `result` holds an already-combined run of the most recent
170
+ * `2 ** i` elements. Each arriving element "carries" upward, merging only with
171
+ * runs of comparable size — exactly like incrementing a binary number — so
172
+ * every merge joins two runs of similar length. Left-to-right element order is
173
+ * preserved: `unpackConcat(old)(cur)` keeps the earlier run on the left, and
174
+ * the final reduce prepends higher (earlier) slots in front of accumulated
175
+ * later runs. An empty list yields `unpackEmpty`.
176
+ *
177
+ * This is the bit-vector analogue of a builder that accumulates appended pieces
178
+ * and materializes the combined result on demand, such as `StringBuilder`
179
+ * (Java, C#) or `strings.Builder` (Go).
180
+ */
181
+ const unpackListToVec = (unpackConcat) => {
182
+ const { init, update, end } = listToVecOp(unpackConcat);
183
+ return (list) => {
184
+ let result = init;
185
+ for (const e of iterable(list)) {
186
+ const candidate = update(e, result);
187
+ if (candidate === null) {
188
+ return null;
189
+ }
190
+ result = candidate;
191
+ }
192
+ return end(result);
193
+ };
194
+ };
136
195
  const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }) => {
137
196
  const unpackPopFront = (len) => {
138
197
  const m = mask(len);
@@ -142,8 +201,9 @@ const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }
142
201
  return [uint & m, { length: v.length - len, uint: rest }];
143
202
  };
144
203
  };
145
- const unpackConcat = (a) => (b) => ({
146
- length: a.length + b.length, uint: unpackConcatUint(a)(b)
204
+ const unpackConcat = a => b => ({
205
+ length: a.length + b.length,
206
+ uint: unpackConcatUint(a)(b)
147
207
  });
148
208
  const popFront = len => {
149
209
  const f = unpackPopFront(len);
@@ -157,11 +217,13 @@ const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }
157
217
  const bu = unpack(b);
158
218
  return pack(unpackConcat(au)(bu));
159
219
  };
220
+ const tryListToVec = (list) => unpackListToVec(unpackConcat)(map(unpack)(list));
160
221
  return {
161
222
  front,
162
223
  removeFront,
163
224
  concat,
164
- listToVec: fold(flip(concat))(empty),
225
+ tryListToVec,
226
+ listToVec: mapUnwrap(tryListToVec),
165
227
  xor: op(norm)(xor),
166
228
  unpackPopFront,
167
229
  popFront,
@@ -232,7 +294,16 @@ export const msb = bo({
232
294
  unpackSplit: len => ({ length, uint }) => [uint >> (length - len), uint],
233
295
  unpackConcatUint: flip(lsbUnpackConcatUint),
234
296
  });
235
- const unpackEmpty = { length: 0n, uint: 0n };
297
+ /**
298
+ * Converts a list of unsigned 8-bit integers to a bit vector using the provided
299
+ * bit order, like `u8ListToVec`, but returns `null` instead of throwing when the
300
+ * result would exceed `maxLength`.
301
+ *
302
+ * @param bo The bit order for the conversion
303
+ * @param list The list of unsigned 8-bit integers to be converted.
304
+ * @returns The resulting vector, or `null` if it would exceed `maxLength`.
305
+ */
306
+ export const tryU8ListToVec = ({ unpackConcat }) => (list) => unpackListToVec(unpackConcat)(map((b) => ({ length: 8n, uint: BigInt(b) }))(list));
236
307
  /**
237
308
  * Converts a list of unsigned 8-bit integers to a bit vector using the provided bit order.
238
309
  *
@@ -240,28 +311,7 @@ const unpackEmpty = { length: 0n, uint: 0n };
240
311
  * @param list The list of unsigned 8-bit integers to be converted.
241
312
  * @returns The resulting vector based on the provided bit order.
242
313
  */
243
- export const u8ListToVec = ({ unpackConcat }) => (list) => {
244
- let result = [];
245
- for (const b of iterable(list)) {
246
- let v = { length: 8n, uint: BigInt(b) };
247
- let i = 0;
248
- while (true) {
249
- if (result.length <= i) {
250
- result = [...result, v];
251
- break;
252
- }
253
- const old = result[i];
254
- if (old.length === 0n) {
255
- result = result.toSpliced(i, 1, v);
256
- break;
257
- }
258
- result = result.toSpliced(i, 1, unpackEmpty);
259
- v = unpackConcat(old)(v);
260
- i++;
261
- }
262
- }
263
- return pack(result.reduce((p, c) => unpackConcat(c)(p), unpackEmpty));
264
- };
314
+ export const u8ListToVec = (bo) => mapUnwrap(tryU8ListToVec(bo));
265
315
  const unpackChunkList = ({ unpackSplit }) => (n) => {
266
316
  const divUpN2 = divUp(n << 1n);
267
317
  return u => {
@@ -46,6 +46,14 @@ export declare const proof: {
46
46
  emptyVec: () => void;
47
47
  };
48
48
  u8ListToVec: () => () => void;
49
+ tryListToVecOverflow: () => void;
50
+ listToVecOverflow: {
51
+ throw: () => void;
52
+ };
53
+ u8ListToVecOverflow: {
54
+ try: () => void;
55
+ throw: () => void;
56
+ };
49
57
  u8ListUnaligned: () => void;
50
58
  chunkList: {
51
59
  empty: () => void;
@@ -1,6 +1,7 @@
1
+ import { assertEq } from "../../asserts/module.f.js";
1
2
  import { mask } from "../bigint/module.f.js";
2
3
  import { asBase, asNominal } from "../nominal/module.f.js";
3
- import { length, empty, uint, vec, lsb, msb, repeat, vec8, u8ListToVec, u8List, chunkList, fromSentinel } from "./module.f.js";
4
+ import { length, empty, uint, vec, lsb, msb, repeat, vec8, maxLength, u8ListToVec, tryU8ListToVec, u8List, chunkList, fromSentinel } from "./module.f.js";
4
5
  import { repeat as listRepeat, toArray } from "../list/module.f.js";
5
6
  const unsafeVec = (a) => asNominal(a);
6
7
  // 0x8 = 0b1000 = 0 + 8
@@ -11,11 +12,6 @@ const unsafeVec = (a) => asNominal(a);
11
12
  // 0xD = 0b1101 = 5 + 8
12
13
  // 0xE = 0b1110 = 6 + 8
13
14
  // 0xF = 0b1111 = 7 + 8
14
- const assertEq = (a, b) => {
15
- if (a !== b) {
16
- throw [a, b];
17
- }
18
- };
19
15
  const assertEq2 = ([a0, a1], [b0, b1]) => {
20
16
  assertEq(a0, b0);
21
17
  assertEq(a1, b1);
@@ -504,6 +500,28 @@ export const proof = {
504
500
  }
505
501
  };
506
502
  },
503
+ tryListToVecOverflow: () => {
504
+ const list = [vec(maxLength)(1n), vec(1n)(1n)];
505
+ assertEq(lsb.tryListToVec(list), null);
506
+ assertEq(msb.tryListToVec(list), null);
507
+ },
508
+ listToVecOverflow: {
509
+ // Same oversized input, but through the throwing `listToVec` wrapper.
510
+ throw: () => {
511
+ const list = { first: vec(maxLength + 1n)(1n), tail: null };
512
+ lsb.listToVec(list);
513
+ },
514
+ },
515
+ u8ListToVecOverflow: {
516
+ // 131_073 bytes is 8 bits past `maxLength`; same null/throw split as
517
+ // `tryListToVec`/`listToVec` above, exercised through the byte-list API.
518
+ try: () => {
519
+ assertEq(tryU8ListToVec(msb)(listRepeat(0x12)(131_073)), null);
520
+ },
521
+ throw: () => {
522
+ u8ListToVec(msb)(listRepeat(0x12)(131_073));
523
+ },
524
+ },
507
525
  u8ListUnaligned: () => {
508
526
  const x = vec(9n)(0x83n);
509
527
  const a = toArray(u8List(msb)(x));
@@ -1,8 +1,3 @@
1
- /**
2
- * Utilities for nullable (`null`/`undefined`) value handling.
3
- *
4
- * @module
5
- */
6
1
  import type { Option } from '../option/module.f.ts';
7
2
  export type Nullable<T> = T | null;
8
3
  export declare const map: <T, R>(f: (value: T) => R) => (value: Nullable<T>) => Nullable<R>;
@@ -15,3 +10,12 @@ export declare const toOption: <T>(value: Nullable<T>) => Option<T>;
15
10
  * property/index lookups) and FunctionalScript (which uses `null` for absence).
16
11
  */
17
12
  export declare const fromUndefined: <T>(value: T | undefined) => Nullable<T>;
13
+ /**
14
+ * Extracts the value from a `Nullable`, asserting that it is not `null`.
15
+ */
16
+ export declare const unwrap: <T>(value: Nullable<T>) => T;
17
+ /**
18
+ * Lifts a function that signals failure with `null` into one that asserts
19
+ * success instead, unwrapping the result.
20
+ */
21
+ export declare const mapUnwrap: <I, T>(f: (i: I) => Nullable<T>) => import("../function/module.f.ts").Func<I, T>;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Utilities for nullable (`null`/`undefined`) value handling.
3
+ *
4
+ * @module
5
+ */
6
+ import { assert } from "../../asserts/module.f.js";
7
+ import { fn } from "../function/module.f.js";
1
8
  export const map = f => value => value === null ? null : f(value);
2
9
  export const match = f => none => value => value === null ? none() : f(value);
3
10
  export const toOption = (value) => value === null ? [] : [value];
@@ -8,3 +15,15 @@ export const toOption = (value) => value === null ? [] : [value];
8
15
  * property/index lookups) and FunctionalScript (which uses `null` for absence).
9
16
  */
10
17
  export const fromUndefined = (value) => value === undefined ? null : value;
18
+ /**
19
+ * Extracts the value from a `Nullable`, asserting that it is not `null`.
20
+ */
21
+ export const unwrap = (value) => {
22
+ assert(value !== null);
23
+ return value;
24
+ };
25
+ /**
26
+ * Lifts a function that signals failure with `null` into one that asserts
27
+ * success instead, unwrapping the result.
28
+ */
29
+ export const mapUnwrap = (f) => fn(f).map(unwrap).result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.34.0",
3
+ "version": "0.35.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -44,7 +44,7 @@
44
44
  "homepage": "https://github.com/functionalscript/functionalscript#readme",
45
45
  "devDependencies": {
46
46
  "@playwright/test": "1.61.1",
47
- "@types/node": "26.0.1",
47
+ "@types/node": "26.1.0",
48
48
  "typescript": "6.0.3"
49
49
  }
50
50
  }