functionalscript 0.32.4 → 0.33.0

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.
@@ -6,7 +6,7 @@
6
6
  import { todo } from "../../../asserts/module.f.js";
7
7
  import { join, parse } from "../../../path/module.f.js";
8
8
  import { utf8ToString } from "../../../text/module.f.js";
9
- import { isVec, length, maxLengthBytes, msb, vec } from "../../../types/bit_vec/module.f.js";
9
+ import { empty, length, maxLengthBytes, msb, vec } from "../../../types/bit_vec/module.f.js";
10
10
  import { error, ok } from "../../../types/result/module.f.js";
11
11
  import { run } from "../../mock/module.f.js";
12
12
  import { asBase, asNominal } from "../../memory/module.f.js";
@@ -28,7 +28,7 @@ const operation = (op) => {
28
28
  }
29
29
  const [first, ...rest] = path;
30
30
  const subDir = dir[first];
31
- if (typeof subDir !== 'object') {
31
+ if (typeof subDir !== 'object' || Array.isArray(subDir)) {
32
32
  return op(dir, path);
33
33
  }
34
34
  const [newSubDir, r] = f(subDir, rest);
@@ -67,11 +67,23 @@ const readFile = readOperation((dir, path) => {
67
67
  if (file === undefined) {
68
68
  return enoent;
69
69
  }
70
- // exists but is a directory — a real error, not a missing path
71
- if (!isVec(file)) {
70
+ if (!Array.isArray(file)) {
72
71
  return error(`'${path[0]}' is not a file`);
73
72
  }
74
- return ok(file);
73
+ const chunks = file;
74
+ const capBits = maxLengthBytes * 8n;
75
+ let result = empty;
76
+ for (const chunk of chunks) {
77
+ const chunkLen = length(chunk);
78
+ if (chunkLen === 0n) {
79
+ continue;
80
+ }
81
+ if (length(result) + chunkLen > capBits) {
82
+ return error(`File size exceeds maximum allowed size of ${maxLengthBytes} bytes`);
83
+ }
84
+ result = msb.concat(result)(chunk);
85
+ }
86
+ return ok(result);
75
87
  });
76
88
  const import_ = readOperation((dir, path) => {
77
89
  if (path.length !== 1) {
@@ -90,11 +102,10 @@ const writeFile = (payload) => operation((dir, path) => {
90
102
  }
91
103
  const [name] = path;
92
104
  const file = dir[name];
93
- // fail if the file is a directory
94
- if (file !== undefined && !isVec(file)) {
105
+ if (file !== undefined && !Array.isArray(file)) {
95
106
  return [dir, writeFileError];
96
107
  }
97
- dir = { ...dir, [name]: payload };
108
+ dir = { ...dir, [name]: [payload] };
98
109
  return [dir, okVoid];
99
110
  });
100
111
  const invalidPath = error('invalid path');
@@ -109,7 +120,7 @@ const readdir = (base, recursive) => readOperation((dir, path) => {
109
120
  if (content === undefined) {
110
121
  continue;
111
122
  }
112
- const isFile = typeof content !== 'object';
123
+ const isFile = Array.isArray(content) || typeof content !== 'object';
113
124
  result = [...result, { name, parentPath, isFile }];
114
125
  if (!isFile && recursive) {
115
126
  result = [...result, ...f(join(parentPath, name), content)];
@@ -137,7 +148,7 @@ const rm = operation((dir, path) => {
137
148
  if (entry === undefined) {
138
149
  return [dir, error('no such file')];
139
150
  }
140
- if (typeof entry === 'object') {
151
+ if (!Array.isArray(entry) && typeof entry === 'object') {
141
152
  return [dir, error('is a directory')];
142
153
  }
143
154
  const { [name]: _, ...rest } = dir;
@@ -158,7 +169,7 @@ const extractEntity = (dir, path) => {
158
169
  }
159
170
  const [first, ...rest] = path;
160
171
  const sub = dir[first];
161
- if (sub === undefined || isVec(sub) || typeof sub === 'function') {
172
+ if (sub === undefined || Array.isArray(sub) || typeof sub === 'function') {
162
173
  return [dir, enoent];
163
174
  }
164
175
  const [newSub, result] = extractEntity(sub, rest);
@@ -175,8 +186,8 @@ const insertEntityAt = (dir, path, entity) => {
175
186
  const [name] = path;
176
187
  const existing = dir[name];
177
188
  if (existing !== undefined) {
178
- const entityIsDir = typeof entity === 'object';
179
- const existingIsDir = typeof existing === 'object';
189
+ const entityIsDir = !Array.isArray(entity) && typeof entity === 'object';
190
+ const existingIsDir = !Array.isArray(existing) && typeof existing === 'object';
180
191
  if (entityIsDir && !existingIsDir) {
181
192
  return [dir, error(`cannot overwrite file '${name}' with a directory`)];
182
193
  }
@@ -198,7 +209,7 @@ const insertEntityAt = (dir, path, entity) => {
198
209
  if (sub === undefined) {
199
210
  return [dir, enoent];
200
211
  }
201
- if (isVec(sub) || typeof sub === 'function') {
212
+ if (Array.isArray(sub) || typeof sub === 'function') {
202
213
  return [dir, error('not a directory')];
203
214
  }
204
215
  const [newSub, result] = insertEntityAt(sub, rest, entity);
@@ -238,7 +249,7 @@ const readBytesOp = (path, offset, size) => readOperation((dir, p) => {
238
249
  if (file === undefined) {
239
250
  return enoent;
240
251
  }
241
- if (!isVec(file)) {
252
+ if (!Array.isArray(file)) {
242
253
  return error(`'${p[0]}' is not a file`);
243
254
  }
244
255
  if (!Number.isInteger(offset)) {
@@ -256,11 +267,28 @@ const readBytesOp = (path, offset, size) => readOperation((dir, p) => {
256
267
  if (BigInt(size) > maxLengthBytes) {
257
268
  return error(`Chunk size ${size} exceeds maximum allowed size of ${maxLengthBytes} bytes`);
258
269
  }
259
- const offsetBits = BigInt(offset) * 8n;
260
- const sizeBits = BigInt(size) * 8n;
261
- const remaining = msb.removeFront(offsetBits)(file);
262
- const actualSizeBits = sizeBits < length(remaining) ? sizeBits : length(remaining);
263
- return ok(vec(actualSizeBits)(msb.front(actualSizeBits)(remaining)));
270
+ const chunks = file;
271
+ let toSkip = BigInt(offset) * 8n;
272
+ let toRead = BigInt(size) * 8n;
273
+ let result = empty;
274
+ for (const chunk of chunks) {
275
+ if (toRead === 0n) {
276
+ break;
277
+ }
278
+ const chunkBits = length(chunk);
279
+ if (toSkip >= chunkBits) {
280
+ toSkip -= chunkBits;
281
+ continue;
282
+ }
283
+ const skipInChunk = toSkip;
284
+ const availableBits = chunkBits - skipInChunk;
285
+ const takeBits = availableBits <= toRead ? availableBits : toRead;
286
+ const taken = vec(takeBits)(msb.front(takeBits)(msb.removeFront(skipInChunk)(chunk)));
287
+ result = msb.concat(result)(taken);
288
+ toSkip = 0n;
289
+ toRead -= takeBits;
290
+ }
291
+ return ok(result);
264
292
  })(path);
265
293
  const map = {
266
294
  all: (...a) => state => {
@@ -330,3 +358,27 @@ const map = {
330
358
  },
331
359
  };
332
360
  export const virtual = run(map);
361
+ const testContext = { test: todo };
362
+ /**
363
+ * Safe, inert defaults for every {@link NodeProgramOptions} field, intended for
364
+ * proof files that need to call a program without owning the full literal.
365
+ *
366
+ * Proofs spread-override only what their test cares about:
367
+ *
368
+ * ```ts
369
+ * const opts: NodeProgramOptions = { ...defaultNodeProgramOptions, args }
370
+ * ```
371
+ *
372
+ * Future additions to `NodeProgramOptions` only need a default added here,
373
+ * keeping unrelated proof files from churning.
374
+ */
375
+ export const defaultNodeProgramOptions = {
376
+ args: [],
377
+ env: {},
378
+ home: '.',
379
+ std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
380
+ testContext,
381
+ bunTestContext: testContext,
382
+ playwrightTestContext: testContext,
383
+ engine: 'node',
384
+ };
@@ -19,9 +19,13 @@ export declare const proof: {
19
19
  renameOntoOwnAncestor: () => void;
20
20
  renameNonEmptyDirOverEmptyDir: () => void;
21
21
  renameEmptyDirOverNonEmptyDir: () => void;
22
+ renameFileOntoDirectory: () => void;
23
+ readFileTooLarge: () => void;
22
24
  readBytesNegativeSize: () => void;
23
25
  readBytesZeroSize: () => void;
24
26
  readBytesNegativeOffset: () => void;
25
27
  readBytesFractionalSize: () => void;
26
28
  readBytesFractionalOffset: () => void;
29
+ readBytesAcrossChunkBoundary: () => void;
30
+ largeFileReadBytes: () => void;
27
31
  };
@@ -1,11 +1,11 @@
1
1
  import { assertEq } from "../../../asserts/module.f.js";
2
2
  import { awaitIfPromise, fetch, rm, writeFile, readFile, readdir, import_, rename, readBytes } from "../module.f.js";
3
- import { vec8 } from "../../../types/bit_vec/module.f.js";
3
+ import { maxLengthBytes, vec, vec8 } from "../../../types/bit_vec/module.f.js";
4
4
  import { emptyState, virtual } from "./module.f.js";
5
5
  export const proof = {
6
6
  rm: {
7
7
  success: () => {
8
- const root = { 'a.txt': vec8(0x42n) };
8
+ const root = { 'a.txt': [vec8(0x42n)] };
9
9
  const [, result] = virtual({ ...emptyState, root })(rm('a.txt'));
10
10
  assertEq(result[0], 'ok');
11
11
  },
@@ -27,7 +27,7 @@ export const proof = {
27
27
  assertEq(result[0], 'error');
28
28
  },
29
29
  readdirRecursive: () => {
30
- const file = vec8(0x42n);
30
+ const file = [vec8(0x42n)];
31
31
  const sub = { 'file.txt': file };
32
32
  const outer = { 'sub': sub };
33
33
  const root = { 'mydir': outer };
@@ -65,7 +65,7 @@ export const proof = {
65
65
  },
66
66
  importNonModule: () => {
67
67
  // import_ on a Vec (not a JsModule) covers typeof entry !== 'function' branch
68
- const root = { 'module.f.ts': vec8(0x42n) };
68
+ const root = { 'module.f.ts': [vec8(0x42n)] };
69
69
  const [, result] = virtual({ ...emptyState, root })(import_('module.f.ts'));
70
70
  assertEq(result[0], 'error');
71
71
  },
@@ -78,62 +78,95 @@ export const proof = {
78
78
  },
79
79
  renameSamePath: () => {
80
80
  // rename('a', 'a') should succeed as a no-op, not reject
81
- const root = { 'a': vec8(0x42n) };
81
+ const root = { 'a': [vec8(0x42n)] };
82
82
  const [, result] = virtual({ ...emptyState, root })(rename('a', 'a'));
83
83
  assertEq(result[0], 'ok');
84
84
  },
85
85
  renameIntoOwnSubtree: () => {
86
86
  // rename('a', 'a/b') should fail (dst inside src's subtree)
87
- const root = { 'a': { 'b': vec8(0x42n) } };
87
+ const root = { 'a': { 'b': [vec8(0x42n)] } };
88
88
  const [, result] = virtual({ ...emptyState, root })(rename('a', 'a/b'));
89
89
  assertEq(result[0], 'error');
90
90
  },
91
91
  renameOntoOwnAncestor: () => {
92
92
  // rename('a/b', 'a') should fail (src inside dst's subtree)
93
- const root = { 'a': { 'b': vec8(0x42n) } };
93
+ const root = { 'a': { 'b': [vec8(0x42n)] } };
94
94
  const [, result] = virtual({ ...emptyState, root })(rename('a/b', 'a'));
95
95
  assertEq(result[0], 'error');
96
96
  },
97
97
  renameNonEmptyDirOverEmptyDir: () => {
98
98
  // rename a directory onto an empty directory should succeed
99
- const root = { 'src': { 'file': vec8(0x42n) }, 'dst': {} };
99
+ const root = { 'src': { 'file': [vec8(0x42n)] }, 'dst': {} };
100
100
  const [, result] = virtual({ ...emptyState, root })(rename('src', 'dst'));
101
101
  assertEq(result[0], 'ok');
102
102
  },
103
103
  renameEmptyDirOverNonEmptyDir: () => {
104
104
  // rename an empty directory onto a non-empty directory should fail
105
- const root = { 'src': {}, 'dst': { 'file': vec8(0x42n) } };
105
+ const root = { 'src': {}, 'dst': { 'file': [vec8(0x42n)] } };
106
106
  const [, result] = virtual({ ...emptyState, root })(rename('src', 'dst'));
107
107
  assertEq(result[0], 'error');
108
108
  },
109
+ renameFileOntoDirectory: () => {
110
+ // rename a file to a path that is already an existing directory should fail
111
+ const root = { 'myfile': [vec8(0x42n)], 'mydir': {} };
112
+ const [, result] = virtual({ ...emptyState, root })(rename('myfile', 'mydir'));
113
+ assertEq(result[0], 'error');
114
+ },
115
+ readFileTooLarge: () => {
116
+ // a file stored as two max-size chunks exceeds the limit; readFile must return an error
117
+ const chunk0 = vec(maxLengthBytes * 8n)(0n);
118
+ const chunk1 = vec(1n)(1n);
119
+ const root = { 'big': [chunk0, chunk1] };
120
+ const [, result] = virtual({ ...emptyState, root })(readFile('big'));
121
+ assertEq(result[0], 'error');
122
+ },
109
123
  readBytesNegativeSize: () => {
110
124
  // readBytes with negative size should fail
111
- const root = { 'file': vec8(0x42n) };
125
+ const root = { 'file': [vec8(0x42n)] };
112
126
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0, -1));
113
127
  assertEq(result[0], 'error');
114
128
  },
115
129
  readBytesZeroSize: () => {
116
130
  // readBytes with zero size should succeed and return empty vec
117
- const root = { 'file': vec8(0x42n) };
131
+ const root = { 'file': [vec8(0x42n)] };
118
132
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0, 0));
119
133
  assertEq(result[0], 'ok');
120
134
  },
121
135
  readBytesNegativeOffset: () => {
122
136
  // readBytes with negative offset should fail
123
- const root = { 'file': vec8(0x42n) };
137
+ const root = { 'file': [vec8(0x42n)] };
124
138
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', -1, 1));
125
139
  assertEq(result[0], 'error');
126
140
  },
127
141
  readBytesFractionalSize: () => {
128
142
  // readBytes with fractional size should fail rather than throw RangeError
129
- const root = { 'file': vec8(0x42n) };
143
+ const root = { 'file': [vec8(0x42n)] };
130
144
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0, 1.5));
131
145
  assertEq(result[0], 'error');
132
146
  },
133
147
  readBytesFractionalOffset: () => {
134
148
  // readBytes with fractional offset should fail rather than throw RangeError
135
- const root = { 'file': vec8(0x42n) };
149
+ const root = { 'file': [vec8(0x42n)] };
136
150
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0.5, 1));
137
151
  assertEq(result[0], 'error');
138
152
  },
153
+ readBytesAcrossChunkBoundary: () => {
154
+ // Two 128 KiB chunks; read 2 bytes spanning the boundary (last byte of chunk 0, first of chunk 1).
155
+ const chunkSize = Number(maxLengthBytes);
156
+ const chunk0 = vec(maxLengthBytes * 8n)(0xaan);
157
+ const chunk1 = vec(maxLengthBytes * 8n)(0xbbn);
158
+ const root = { 'big': [chunk0, chunk1] };
159
+ const [, result] = virtual({ ...emptyState, root })(readBytes('big', chunkSize - 1, 2));
160
+ assertEq(result[0], 'ok');
161
+ },
162
+ largeFileReadBytes: () => {
163
+ // A file stored as two 128 KiB chunks is larger than maxLengthBytes.
164
+ // readBytes within the second chunk (offset = 128 KiB, size = 1) should succeed.
165
+ const chunkSize = Number(maxLengthBytes);
166
+ const chunk0 = vec(maxLengthBytes * 8n)(0n);
167
+ const chunk1 = vec(maxLengthBytes * 8n)(0xffn);
168
+ const root = { 'large': [chunk0, chunk1] };
169
+ const [, result] = virtual({ ...emptyState, root })(readBytes('large', chunkSize, 1));
170
+ assertEq(result[0], 'ok');
171
+ },
139
172
  };
@@ -1,8 +1,7 @@
1
- import { pure } from "../effects/module.f.js";
2
1
  import { log } from "../effects/node/module.f.js";
3
- import { emptyState } from "../effects/node/virtual/module.f.js";
2
+ import { defaultNodeProgramOptions, emptyState } from "../effects/node/virtual/module.f.js";
4
3
  import { virtual } from "../effects/node/virtual/module.f.js";
5
- import { assert, assertEq, todo } from "../asserts/module.f.js";
4
+ import { assert, assertEq } from "../asserts/module.f.js";
6
5
  import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, registerModule, parseTestSet, defaultTest, } from "./module.f.js";
7
6
  import { run as mockRun } from "../effects/mock/module.f.js";
8
7
  import { shouldLoad } from "../dev/module.f.js";
@@ -13,16 +12,9 @@ const makeReporter = () => ({
13
12
  summary: (pass, fail, time) => writeEvent(['summary', pass, fail, time]),
14
13
  test: defaultTest,
15
14
  });
16
- const noopTestContext = { test: todo };
17
15
  const options = (initCwd, github = false) => ({
18
- args: [],
16
+ ...defaultNodeProgramOptions,
19
17
  env: { INIT_CWD: initCwd, ...(github ? { GITHUB_ACTIONS: 'true' } : {}) },
20
- home: '.',
21
- std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
22
- testContext: noopTestContext,
23
- bunTestContext: noopTestContext,
24
- playwrightTestContext: noopTestContext,
25
- engine: 'node',
26
18
  });
27
19
  const ok0 = () => ({ result: ['ok', undefined], duration: 0 });
28
20
  const fail0 = () => ({ result: ['error', 'oops'], duration: 0 });
@@ -5,12 +5,12 @@
5
5
  */
6
6
  import { compile } from "../djs/module.f.js";
7
7
  import { main as testMain } from "../emergent_testing/module.f.js";
8
- import { commands as casCommands } from "../cas/module.f.js";
8
+ import { commands as casCommands, fileCas } from "../cas/module.f.js";
9
9
  import { main as ciMain } from "../ci/module.f.js";
10
10
  import { import_ } from "../effects/node/module.f.js";
11
11
  import { dispatch } from "../cli/module.f.js";
12
12
  import { casMcpServer } from "../cas/mcp/module.f.js";
13
- import { cas, fileKvStore, toPath } from "../cas/module.f.js";
13
+ import { toPath } from "../cas/module.f.js";
14
14
  import { sha256 } from "../crypto/sha2/module.f.js";
15
15
  import { join } from "../path/module.f.js";
16
16
  import { pure } from "../effects/module.f.js";
@@ -34,7 +34,7 @@ const commands = [
34
34
  names: ['mcp', 'm'],
35
35
  description: 'Run an MCP server over stdio exposing the CAS as tools',
36
36
  handler: ({ home }) => {
37
- const c = cas(sha256)(fileKvStore(home));
37
+ const c = fileCas(sha256)(home);
38
38
  return casMcpServer(c, home, hash => join(home, toPath(hash))).step(() => pure(0));
39
39
  },
40
40
  },
package/fs/fjs/proof.f.js CHANGED
@@ -1,17 +1,8 @@
1
1
  import { assert, assertEq } from "../asserts/module.f.js";
2
2
  import { pure } from "../effects/module.f.js";
3
- import { emptyState, virtual } from "../effects/node/virtual/module.f.js";
3
+ import { defaultNodeProgramOptions, emptyState, virtual } from "../effects/node/virtual/module.f.js";
4
4
  import { main } from "./module.f.js";
5
- const makeOptions = (args) => ({
6
- args,
7
- env: {},
8
- home: '.',
9
- std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
10
- testContext: { test: async () => { } },
11
- bunTestContext: { test: async () => { } },
12
- playwrightTestContext: { test: async () => { } },
13
- engine: 'node',
14
- });
5
+ const makeOptions = (args) => ({ ...defaultNodeProgramOptions, args });
15
6
  const run = (root) => (args) => virtual({ ...emptyState, root })(main(makeOptions(args)));
16
7
  const appMain = ({ args }) => pure(args.length);
17
8
  export const proof = {
@@ -95,4 +95,9 @@ export type JsTokenWithMetadata = {
95
95
  type ErrorMessage = '" are missing' | 'unescaped character' | 'invalid hex value' | 'unexpected character' | 'invalid number' | 'invalid token' | '*\/ expected' | 'unterminated string literal' | 'eof';
96
96
  export declare const isKeywordToken: (token: JsToken) => boolean;
97
97
  export declare const tokenize: (input: List<number>) => (path: string) => List<JsTokenWithMetadata>;
98
+ export declare const proof: {
99
+ throw: {
100
+ unionConflict: () => void;
101
+ };
102
+ };
98
103
  export {};
@@ -27,6 +27,7 @@ leftSquareBracket, reverseSolidus, rightSquareBracket, lowLine,
27
27
  latinSmallLetterRange, latinSmallLetterA, latinSmallLetterB, latinSmallLetterE, latinSmallLetterF, latinSmallLetterN, latinSmallLetterR, latinSmallLetterT, latinSmallLetterU,
28
28
  //
29
29
  leftCurlyBracket, rightCurlyBracket, dollarSign } from "../../text/ascii/module.f.js";
30
+ import { todo } from "../../asserts/module.f.js";
30
31
  const { fromCharCode } = String;
31
32
  const rangeOneNine = range('19');
32
33
  const rangeSetNewLine = [
@@ -519,3 +520,15 @@ export const tokenize = input => path => {
519
520
  const scan = scanTokenize({ state: { kind: 'initial' }, metadata: { path, line: 1, column: 1 } });
520
521
  return flat(scan(flat([input /*satisfies List<CharCodeOrEof>*/, [null]])));
521
522
  };
523
+ export const proof = {
524
+ throw: {
525
+ // union throws when two distinct non-default handlers are merged for the same range;
526
+ // this path is unreachable through the public API (no overlapping ranges in practice).
527
+ unionConflict: () => {
528
+ const def = (_) => todo;
529
+ const a = (_) => todo;
530
+ const b = (_) => todo;
531
+ union(def)(a)(b);
532
+ }
533
+ }
534
+ };
@@ -4,7 +4,6 @@
4
4
  * @module
5
5
  */
6
6
  import { flat, reduce, empty } from "../../types/list/module.f.js";
7
- import {} from "../../types/object/module.f.js";
8
7
  import {} from "../../types/function/operator/module.f.js";
9
8
  const jsonStringify = JSON.stringify;
10
9
  /**
@@ -1,15 +1,6 @@
1
1
  import { fgRed, reset, createConsoleText, backspace, csiWrite } from "./module.f.js";
2
- import { virtual, emptyState } from "../../effects/node/virtual/module.f.js";
3
- const makeOptions = (isTTY) => ({
4
- args: [],
5
- env: {},
6
- home: '.',
7
- std: { stdout: { isTTY }, stderr: { isTTY } },
8
- testContext: { test: async () => { } },
9
- bunTestContext: { test: async () => { } },
10
- playwrightTestContext: { test: async () => { } },
11
- engine: 'node',
12
- });
2
+ import { virtual, emptyState, defaultNodeProgramOptions } from "../../effects/node/virtual/module.f.js";
3
+ const makeOptions = (isTTY) => ({ ...defaultNodeProgramOptions, std: { stdout: { isTTY }, stderr: { isTTY } } });
13
4
  export const proof = [
14
5
  () => {
15
6
  if (fgRed !== '\x1b[31m') {
@@ -156,24 +156,17 @@ export declare const xor: Reduce;
156
156
  export declare const divUp: (b: bigint) => Unary;
157
157
  export declare const roundUp: (b: bigint) => Unary;
158
158
  /**
159
- * Divides a bigint value up to the nearest multiple of 2^e (shift-based, power-of-two only).
159
+ * Converts a bit count to a byte count, rounding up divides by `8`.
160
160
  *
161
- * @param e - The exponent (divisor = 2^e).
162
- * @returns A function that rounds a value up by the given power of two.
163
- */
164
- export declare const divUpE2: (e: bigint) => Unary;
165
- /**
166
- * Rounds a bigint value up to the nearest multiple of 2^e (shift-based, power-of-two only).
167
- *
168
- * @param e - The exponent (multiple = 2^e).
169
- * @returns A function that rounds a value up to the nearest multiple of the given power of two.
170
- */
171
- export declare const roundUpE2: (e: bigint) => Unary;
172
- /**
173
- * Converts a bit count to a byte count, rounding up — divides by `8` (`2^3` bits per byte).
161
+ * Domain: non-negative inputs (bit/byte counts). On negative inputs `divUp`
162
+ * truncates toward zero rather than flooring toward −∞, so the rounding
163
+ * direction is only meaningful for `v >= 0n`.
174
164
  */
175
165
  export declare const divUp8: Unary;
176
166
  /**
177
- * Rounds a bit count up to the nearest whole byte — the nearest multiple of `8` (`2^3` bits per byte).
167
+ * Rounds a bit count up to the nearest whole byte — the nearest multiple of `8`.
168
+ *
169
+ * Domain: non-negative inputs (bit/byte counts). See `divUp8` for the
170
+ * truncate-vs-floor caveat on negative values.
178
171
  */
179
172
  export declare const roundUp8: Unary;
@@ -234,30 +234,17 @@ export const roundUp = (b) => {
234
234
  return v => d(v) * b;
235
235
  };
236
236
  /**
237
- * Divides a bigint value up to the nearest multiple of 2^e (shift-based, power-of-two only).
237
+ * Converts a bit count to a byte count, rounding up divides by `8`.
238
238
  *
239
- * @param e - The exponent (divisor = 2^e).
240
- * @returns A function that rounds a value up by the given power of two.
239
+ * Domain: non-negative inputs (bit/byte counts). On negative inputs `divUp`
240
+ * truncates toward zero rather than flooring toward −∞, so the rounding
241
+ * direction is only meaningful for `v >= 0n`.
241
242
  */
242
- export const divUpE2 = (e) => {
243
- const m = mask(e);
244
- return v => (v + m) >> e;
245
- };
243
+ export const divUp8 = divUp(8n);
246
244
  /**
247
- * Rounds a bigint value up to the nearest multiple of 2^e (shift-based, power-of-two only).
245
+ * Rounds a bit count up to the nearest whole byte the nearest multiple of `8`.
248
246
  *
249
- * @param e - The exponent (multiple = 2^e).
250
- * @returns A function that rounds a value up to the nearest multiple of the given power of two.
251
- */
252
- export const roundUpE2 = (e) => {
253
- const d = divUpE2(e);
254
- return v => d(v) << e;
255
- };
256
- /**
257
- * Converts a bit count to a byte count, rounding up — divides by `8` (`2^3` bits per byte).
258
- */
259
- export const divUp8 = divUpE2(3n);
260
- /**
261
- * Rounds a bit count up to the nearest whole byte — the nearest multiple of `8` (`2^3` bits per byte).
247
+ * Domain: non-negative inputs (bit/byte counts). See `divUp8` for the
248
+ * truncate-vs-floor caveat on negative values.
262
249
  */
263
- export const roundUp8 = roundUpE2(3n);
250
+ export const roundUp8 = roundUp(8n);
@@ -29,8 +29,6 @@ export declare const proof: {
29
29
  combination3: () => void;
30
30
  divUp: () => void;
31
31
  roundUp: () => void;
32
- divUpE2: () => void;
33
- roundUpE2: () => void;
34
32
  divUp8: () => void;
35
33
  roundUp8: () => void;
36
34
  xor: () => void;
@@ -1,4 +1,4 @@
1
- import { sum, abs, serialize, log2, bitLength, mask, combination, factorial, xor, multiple, product, divUp, roundUp, divUpE2, roundUpE2, divUp8, roundUp8 } from "./module.f.js";
1
+ import { sum, abs, serialize, log2, bitLength, mask, combination, factorial, xor, multiple, product, divUp, roundUp, divUp8, roundUp8 } from "./module.f.js";
2
2
  import { assertEq } from "../../asserts/module.f.js";
3
3
  import { min } from "../function/compare/module.f.js";
4
4
  const oldLog2 = (v) => {
@@ -463,32 +463,22 @@ export const proof = {
463
463
  assertEq(roundUp(8n)(15n), 16n);
464
464
  assertEq(roundUp(8n)(3n), 8n);
465
465
  },
466
- divUpE2: () => {
467
- assertEq(divUpE2(3n)(8n), 1n);
468
- assertEq(divUpE2(3n)(15n), 2n);
469
- assertEq(divUpE2(3n)(0n), 0n);
470
- assertEq(divUpE2(3n)(8n), 1n);
471
- assertEq(divUpE2(3n)(9n), 2n);
472
- },
473
- roundUpE2: () => {
474
- assertEq(roundUpE2(3n)(8n), 8n);
475
- assertEq(roundUpE2(3n)(15n), 16n);
476
- assertEq(roundUpE2(3n)(3n), 8n);
477
- assertEq(roundUpE2(3n)(0n), 0n);
478
- assertEq(roundUpE2(3n)(8n), 8n);
479
- assertEq(roundUpE2(3n)(9n), 16n);
480
- },
481
466
  divUp8: () => {
482
467
  assertEq(divUp8(0n), 0n);
483
468
  assertEq(divUp8(1n), 1n);
484
469
  assertEq(divUp8(8n), 1n);
470
+ assertEq(divUp8(8n), 1n);
485
471
  assertEq(divUp8(9n), 2n);
472
+ assertEq(divUp8(15n), 2n);
486
473
  },
487
474
  roundUp8: () => {
488
475
  assertEq(roundUp8(0n), 0n);
489
476
  assertEq(roundUp8(1n), 8n);
477
+ assertEq(roundUp8(3n), 8n);
478
+ assertEq(roundUp8(8n), 8n);
490
479
  assertEq(roundUp8(8n), 8n);
491
480
  assertEq(roundUp8(9n), 16n);
481
+ assertEq(roundUp8(15n), 16n);
492
482
  },
493
483
  xor: () => {
494
484
  assertEq(xor(5n)(3n), 6n);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.32.4",
3
+ "version": "0.33.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",