functionalscript 0.12.9 → 0.13.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.
package/ci/module.f.js CHANGED
@@ -69,7 +69,7 @@ const node = (version) => (extra) => basicNode(version)([
69
69
  const findTgz = (v) => v === 'windows' ? '(Get-ChildItem *.tgz).FullName' : './*.tgz';
70
70
  const playwrightVersion = '1.58.2';
71
71
  const playwrightAndVersion = `playwright@${playwrightVersion}`;
72
- const rustToolchain = '1.94.1';
72
+ const rustToolchain = '1.95.0';
73
73
  const toSteps = (m) => {
74
74
  const filter = (st) => m.flatMap((mt) => mt.type === st ? [mt.step] : []);
75
75
  const aptGet = m.flatMap(v => v.type === 'apt-get' ? [v.package] : []).join(' ');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.12.9",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -163,6 +163,7 @@ export type BitOrder = {
163
163
  readonly cmp: (a: Vec) => (b: Vec) => Sign;
164
164
  readonly unpackSplit: (len: bigint) => (u: Unpacked) => readonly [bigint, bigint];
165
165
  readonly unpackConcat: (a: Unpacked) => (b: Unpacked) => Unpacked;
166
+ readonly startsWith: (prefix: Vec) => (v: Vec) => boolean;
166
167
  };
167
168
  /**
168
169
  * Implements operations for handling vectors in a least-significant-bit (LSb) first order.
@@ -119,6 +119,13 @@ const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }
119
119
  const unpackConcat = (a) => (b) => ({
120
120
  length: a.length + b.length, uint: unpackConcatUint(a)(b)
121
121
  });
122
+ const popFront = len => {
123
+ const f = unpackPopFront(len);
124
+ return v => {
125
+ const [uint, u] = f(unpack(v));
126
+ return [uint, pack(u)];
127
+ };
128
+ };
122
129
  return {
123
130
  front,
124
131
  removeFront,
@@ -129,13 +136,7 @@ const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }
129
136
  },
130
137
  xor: op(norm)(xor),
131
138
  unpackPopFront,
132
- popFront: len => {
133
- const f = unpackPopFront(len);
134
- return v => {
135
- const [uint, u] = f(unpack(v));
136
- return [uint, pack(u)];
137
- };
138
- },
139
+ popFront,
139
140
  norm,
140
141
  cmp: a => b => {
141
142
  const au = unpack(a);
@@ -148,6 +149,10 @@ const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }
148
149
  },
149
150
  unpackSplit,
150
151
  unpackConcat,
152
+ startsWith: prefix => {
153
+ const { length: n, uint: u } = unpack(prefix);
154
+ return v => length(v) < n ? false : popFront(n)(v)[0] === u;
155
+ }
151
156
  };
152
157
  };
153
158
  const lsbUnpackConcatUint = ({ uint: a, length }) => ({ uint: b }) => (b << length) | a;
@@ -39,6 +39,11 @@ declare const _default: {
39
39
  repeat: () => void;
40
40
  lsbCmp: () => void;
41
41
  msbCmp: () => void;
42
+ startsWith: {
43
+ lsb: () => void;
44
+ msb: () => void;
45
+ emptyVec: () => void;
46
+ };
42
47
  u8ListToVec: () => () => void;
43
48
  u8ListUnaligned: () => void;
44
49
  chunkList: {
@@ -458,6 +458,29 @@ export default {
458
458
  c(vec(5n)(0x5n))(vec(4n)(0x5n))(-1); // 0b00101 < 0b0101_
459
459
  c(vec(4n)(0x5n))(vec(5n)(0xan))(-1); // 0b0101_ < 0b01010
460
460
  },
461
+ startsWith: {
462
+ // vector 0xF5 = 0b1111_0101 (8 bits)
463
+ // LSB reads from the low end: bits 0-3 = 0101 = 0x5, bits 4-7 = 1111 = 0xF
464
+ lsb: () => {
465
+ const v = vec(8n)(0xf5n);
466
+ assertEq(lsb.startsWith(vec(4n)(0x5n))(v), true); // low nibble matches
467
+ assertEq(lsb.startsWith(vec(4n)(0xfn))(v), false); // low nibble doesn't match
468
+ assertEq(lsb.startsWith(v)(vec(4n)(0x5n)), false); // prefix longer than vector
469
+ assertEq(lsb.startsWith(empty)(v), true); // empty prefix always matches
470
+ },
471
+ // MSB reads from the high end: bits 0-3 = 1111 = 0xF, bits 4-7 = 0101 = 0x5
472
+ msb: () => {
473
+ const v = vec(8n)(0xf5n);
474
+ assertEq(msb.startsWith(vec(4n)(0xfn))(v), true); // high nibble matches
475
+ assertEq(msb.startsWith(vec(4n)(0x5n))(v), false); // high nibble doesn't match
476
+ assertEq(msb.startsWith(v)(vec(4n)(0xfn)), false); // prefix longer than vector
477
+ assertEq(msb.startsWith(empty)(v), true); // empty prefix always matches
478
+ },
479
+ emptyVec: () => {
480
+ assertEq(lsb.startsWith(empty)(empty), true);
481
+ assertEq(msb.startsWith(empty)(empty), true);
482
+ },
483
+ },
461
484
  u8ListToVec: () => {
462
485
  // 131_072 is too much for Bun
463
486
  const x = u8ListToVec(msb)(listRepeat(0x12)(131_071));
@@ -41,9 +41,10 @@ export type Ts<T extends Type> = T extends () => infer I ? (I extends readonly [
41
41
  readonly [K in string]: Ts<E>;
42
42
  } : I extends readonly ['or', ...infer A extends readonly Type[]] ? Ts<A[number]> : never) : ConstTs<T>;
43
43
  /**
44
- * Converts an RTTI schema `Type` to its TypeScript type expression as a string.
44
+ * Creates a printer that converts an RTTI schema `Type` to its TypeScript type expression as a string.
45
45
  *
46
46
  * Mirrors the compile-time `Ts<T>` mapped type at runtime.
47
+ * Pass `true` to emit mutable (non-`readonly`) types.
47
48
  *
48
49
  * **Note:** recursive schemas (e.g. `const list = () => ['array', list] as const`)
49
50
  * will cause infinite recursion. Only acyclic schemas are supported.
@@ -53,14 +54,19 @@ export type Ts<T extends Type> = T extends () => infer I ? (I extends readonly [
53
54
  *
54
55
  * @example
55
56
  * ```ts
57
+ * const toTs = printer()
56
58
  * toTs(boolean) // 'boolean'
57
59
  * toTs(array(number)) // 'readonly(number)[]'
58
- * toTs(record(string)) // '{readonly[k in string]:string}'
60
+ * toTs(record(string)) // '{readonly[k:string]:string}'
59
61
  * toTs(or(string, number)) // 'string|number'
60
62
  * toTs(42) // '42'
61
63
  * toTs('hello') // '"hello"'
62
64
  * toTs([boolean, number]) // 'readonly[boolean,number]'
63
- * toTs({ x: string }) // '{readonly "x":string}'
65
+ * toTs({ x: string }) // '{readonly"x":string}'
66
+ *
67
+ * const toTsMut = printer(true)
68
+ * toTsMut(array(number)) // '(number)[]'
69
+ * toTsMut(record(string)) // '{[k:string]:string}'
64
70
  * ```
65
71
  */
66
- export declare const toTs: (rtti: Type) => string;
72
+ export declare const printer: (mut?: true) => (rtti: Type) => string;
@@ -7,15 +7,12 @@
7
7
  * The runtime `toTs` function mirrors `Ts<T>` at value level, returning a TypeScript
8
8
  * type expression string for a given RTTI schema.
9
9
  */
10
- import { primitive, tuple, struct, array, record, union } from "../../ts/module.f.js";
11
- /** Serialises a `Const` schema to its TypeScript type expression. */
12
- const constToTs = (rtti) => typeof rtti !== 'object' || rtti === null ? primitive(rtti) :
13
- rtti instanceof Array ? tuple(rtti.map(toTs)) :
14
- struct(Object.entries(rtti).map(([k, v]) => [k, toTs(v)]));
10
+ import { primitive, union, printer as tsPrinter } from "../../ts/module.f.js";
15
11
  /**
16
- * Converts an RTTI schema `Type` to its TypeScript type expression as a string.
12
+ * Creates a printer that converts an RTTI schema `Type` to its TypeScript type expression as a string.
17
13
  *
18
14
  * Mirrors the compile-time `Ts<T>` mapped type at runtime.
15
+ * Pass `true` to emit mutable (non-`readonly`) types.
19
16
  *
20
17
  * **Note:** recursive schemas (e.g. `const list = () => ['array', list] as const`)
21
18
  * will cause infinite recursion. Only acyclic schemas are supported.
@@ -25,26 +22,38 @@ const constToTs = (rtti) => typeof rtti !== 'object' || rtti === null ? primitiv
25
22
  *
26
23
  * @example
27
24
  * ```ts
25
+ * const toTs = printer()
28
26
  * toTs(boolean) // 'boolean'
29
27
  * toTs(array(number)) // 'readonly(number)[]'
30
- * toTs(record(string)) // '{readonly[k in string]:string}'
28
+ * toTs(record(string)) // '{readonly[k:string]:string}'
31
29
  * toTs(or(string, number)) // 'string|number'
32
30
  * toTs(42) // '42'
33
31
  * toTs('hello') // '"hello"'
34
32
  * toTs([boolean, number]) // 'readonly[boolean,number]'
35
- * toTs({ x: string }) // '{readonly "x":string}'
33
+ * toTs({ x: string }) // '{readonly"x":string}'
34
+ *
35
+ * const toTsMut = printer(true)
36
+ * toTsMut(array(number)) // '(number)[]'
37
+ * toTsMut(record(string)) // '{[k:string]:string}'
36
38
  * ```
37
39
  */
38
- export const toTs = (rtti) => {
39
- if (typeof rtti !== 'function') {
40
- return constToTs(rtti);
41
- }
42
- const [tag, ...rest] = rtti();
43
- switch (tag) {
44
- case 'const': return constToTs(rest[0]);
45
- case 'array': return array(toTs(rest[0]));
46
- case 'record': return record(toTs(rest[0]));
47
- case 'or': return union(rest.map(toTs));
48
- default: return tag; // tag0: 'boolean' | 'number' | 'string' | 'bigint' | 'unknown'
49
- }
40
+ export const printer = (mut) => {
41
+ const { tuple, struct, array, record } = tsPrinter(mut);
42
+ const constToTs = (rtti) => typeof rtti !== 'object' || rtti === null ? primitive(rtti) :
43
+ rtti instanceof Array ? tuple(rtti.map(toTs)) :
44
+ struct(Object.entries(rtti).map(([k, v]) => [k, toTs(v)]));
45
+ const toTs = (rtti) => {
46
+ if (typeof rtti !== 'function') {
47
+ return constToTs(rtti);
48
+ }
49
+ const [tag, ...rest] = rtti();
50
+ switch (tag) {
51
+ case 'const': return constToTs(rest[0]);
52
+ case 'array': return array(toTs(rest[0]));
53
+ case 'record': return record(toTs(rest[0]));
54
+ case 'or': return union(rest.map(toTs));
55
+ default: return tag; // tag0: 'boolean' | 'number' | 'string' | 'bigint' | 'unknown'
56
+ }
57
+ };
58
+ return toTs;
50
59
  };
@@ -49,5 +49,12 @@ declare const _default: {
49
49
  };
50
50
  never: () => void;
51
51
  option: () => void;
52
+ mut: {
53
+ array: () => void;
54
+ nestedArray: () => void;
55
+ record: () => void;
56
+ tuple: () => void;
57
+ struct: () => void;
58
+ };
52
59
  };
53
60
  export default _default;
@@ -1,5 +1,13 @@
1
- import { toTs } from "./module.f.js";
1
+ import { printer } from "./module.f.js";
2
2
  import { boolean, number, string, bigint, unknown, array, record, or, option, never } from "../module.f.js";
3
+ const toTs = printer();
4
+ const toTsMut = printer(true);
5
+ const eqMut = (rtti, expected) => {
6
+ const result = toTsMut(rtti);
7
+ if (result !== expected) {
8
+ throw `expected ${JSON.stringify(expected)}, got ${JSON.stringify(result)}`;
9
+ }
10
+ };
3
11
  const eq = (rtti, expected) => {
4
12
  const result = toTs(rtti);
5
13
  if (result !== expected) {
@@ -21,8 +29,8 @@ export default {
21
29
  union: () => eq(array(or(number, string)), 'readonly(number|string)[]'),
22
30
  },
23
31
  record: {
24
- primitive: () => eq(record(string), '{readonly[k in string]:string}'),
25
- nested: () => eq(record(record(number)), '{readonly[k in string]:{readonly[k in string]:number}}'),
32
+ primitive: () => eq(record(string), '{readonly[k:string]:string}'),
33
+ nested: () => eq(record(record(number)), '{readonly[k:string]:{readonly[k:string]:number}}'),
26
34
  },
27
35
  },
28
36
  const: {
@@ -57,4 +65,11 @@ export default {
57
65
  },
58
66
  never: () => eq(never, 'never'),
59
67
  option: () => eq(option(number), 'number|undefined'),
68
+ mut: {
69
+ array: () => eqMut(array(number), '(number)[]'),
70
+ nestedArray: () => eqMut(array(array(boolean)), '((boolean)[])[]'),
71
+ record: () => eqMut(record(string), '{[k:string]:string}'),
72
+ tuple: () => eqMut([12, true], '[12,true]'),
73
+ struct: () => eqMut({ a: number, b: string }, '{"a":number,"b":string}'),
74
+ },
60
75
  };
@@ -21,3 +21,4 @@ export declare const join: (_: string) => (input: List<string>) => string;
21
21
  export declare const concat: (input: List<string>) => string;
22
22
  export declare const repeat: (n: string) => (v: number) => string;
23
23
  export declare const cmp: (a: string) => (b: string) => Sign;
24
+ export declare const splitAt: (p: number) => (v: string) => readonly [string, string];
@@ -24,3 +24,4 @@ export const join = compose(joinOp)(reduce);
24
24
  export const concat = reduce(concatOp);
25
25
  export const repeat = v => compose(listRepeat(v))(concat);
26
26
  export const cmp = uCmp;
27
+ export const splitAt = (p) => (v) => [v.substring(0, p), v.substring(p)];
@@ -12,5 +12,12 @@ declare const _default: {
12
12
  };
13
13
  repeat: () => void;
14
14
  cmp: () => void;
15
+ splitAt: {
16
+ middle: () => void;
17
+ zero: () => void;
18
+ full: () => void;
19
+ beyond: () => void;
20
+ empty: () => void;
21
+ };
15
22
  };
16
23
  export default _default;
@@ -1,4 +1,4 @@
1
- import { join, concat, repeat, cmp } from "./module.f.js";
1
+ import { join, concat, repeat, cmp, splitAt } from "./module.f.js";
2
2
  import { repeat as repeatList } from "../list/module.f.js";
3
3
  export default {
4
4
  example: () => {
@@ -67,5 +67,52 @@ export default {
67
67
  if (result !== -1) {
68
68
  throw result;
69
69
  }
70
+ },
71
+ splitAt: {
72
+ middle: () => {
73
+ const [a, b] = splitAt(3)('hello');
74
+ if (a !== 'hel') {
75
+ throw a;
76
+ }
77
+ if (b !== 'lo') {
78
+ throw b;
79
+ }
80
+ },
81
+ zero: () => {
82
+ const [a, b] = splitAt(0)('hello');
83
+ if (a !== '') {
84
+ throw a;
85
+ }
86
+ if (b !== 'hello') {
87
+ throw b;
88
+ }
89
+ },
90
+ full: () => {
91
+ const [a, b] = splitAt(5)('hello');
92
+ if (a !== 'hello') {
93
+ throw a;
94
+ }
95
+ if (b !== '') {
96
+ throw b;
97
+ }
98
+ },
99
+ beyond: () => {
100
+ const [a, b] = splitAt(10)('hello');
101
+ if (a !== 'hello') {
102
+ throw a;
103
+ }
104
+ if (b !== '') {
105
+ throw b;
106
+ }
107
+ },
108
+ empty: () => {
109
+ const [a, b] = splitAt(0)('');
110
+ if (a !== '') {
111
+ throw a;
112
+ }
113
+ if (b !== '') {
114
+ throw b;
115
+ }
116
+ },
70
117
  }
71
118
  };
@@ -1,8 +1,17 @@
1
1
  export type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
2
2
  export type Assert<T extends true> = T;
3
- export declare const tuple: (types: readonly string[]) => string;
4
- export declare const struct: (fields: readonly (readonly [string, string])[]) => string;
5
- export declare const array: (type: string) => string;
6
- export declare const record: (type: string) => string;
3
+ /** Functions for emitting TypeScript type expression strings. */
4
+ export type Printer = {
5
+ /** Emits a tuple type: `readonly[A, B]` or `[A, B]` when mutable. */
6
+ readonly tuple: (types: readonly string[]) => string;
7
+ /** Emits an object type with named fields: `{readonly"k":T}` or `{"k":T}` when mutable. */
8
+ readonly struct: (fields: readonly (readonly [string, string])[]) => string;
9
+ /** Emits an array type: `readonly(T)[]` or `(T)[]` when mutable. */
10
+ readonly array: (type: string) => string;
11
+ /** Emits an index-signature record type: `{readonly[k:string]:T}` or `{[k:string]:T}` when mutable. */
12
+ readonly record: (type: string) => string;
13
+ };
14
+ /** Creates a `Printer`. Pass `true` to emit mutable (non-`readonly`) types. */
15
+ export declare const printer: (mut?: true) => Printer;
7
16
  export declare const primitive: (c: bigint | string | undefined | boolean | number | null) => string;
8
17
  export declare const union: (types: readonly string[]) => string;
@@ -1,9 +1,15 @@
1
1
  const complex = (open, close) => (i) => `${open}${i.join(',')}${close}`;
2
- export const tuple = complex('readonly[', ']');
3
2
  const structX = complex('{', '}');
4
- export const struct = (fields) => structX(fields.map(([k, v]) => `readonly${JSON.stringify(k)}:${v}`));
5
- export const array = (type) => `readonly(${type})[]`;
6
- export const record = (type) => structX([`readonly[k in string]:${type}`]);
3
+ /** Creates a `Printer`. Pass `true` to emit mutable (non-`readonly`) types. */
4
+ export const printer = (mut) => {
5
+ const ro = mut ? '' : 'readonly';
6
+ return {
7
+ tuple: (mut ? complex('[', ']') : complex('readonly[', ']')),
8
+ struct: (fields) => structX(fields.map(([k, v]) => `${ro}${JSON.stringify(k)}:${v}`)),
9
+ array: (type) => `${ro}(${type})[]`,
10
+ record: (type) => structX([`${ro}[k:string]:${type}`]),
11
+ };
12
+ };
7
13
  export const primitive = (c) => {
8
14
  if (c === null) {
9
15
  return 'null';