functionalscript 0.6.10 → 0.7.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.
@@ -1,4 +1,6 @@
1
- import type { Rule as FRule } from '../module.f.ts';
1
+ import { type CodePoint } from '../../text/utf16/module.f.ts';
2
+ import { type RangeMapArray } from '../../types/range_map/module.f.ts';
3
+ import { type Rule as FRule } from '../module.f.ts';
2
4
  export type TerminalRange = number;
3
5
  export type Sequence = readonly string[];
4
6
  /** A variant of rule names. */
@@ -8,7 +10,44 @@ export type Variant = {
8
10
  export type Rule = Variant | Sequence | TerminalRange;
9
11
  /** The full grammar */
10
12
  export type RuleSet = Readonly<Record<string, Rule>>;
13
+ type EmptyTag = string | true | undefined;
14
+ type DispatchRule = {
15
+ readonly emptyTag: EmptyTag;
16
+ readonly rangeMap: Dispatch;
17
+ };
18
+ type Dispatch = RangeMapArray<DispatchResult>;
19
+ type DispatchResult = DispatchRuleCollection | null;
20
+ type DispatchRuleCollection = {
21
+ readonly tag: string | undefined;
22
+ readonly rules: DispatchRule[];
23
+ };
24
+ type DispatchMap = {
25
+ readonly [id in string]: DispatchRule;
26
+ };
27
+ /**
28
+ * Represents a parsed Abstract Syntax Tree (AST) sequence.
29
+ */
30
+ export type AstSequence = readonly (AstRule | CodePoint)[];
31
+ /**
32
+ * Represents a parsed AST rule, consisting of a rule name and its parsed sequence.
33
+ */
34
+ export type AstRule = readonly [string, AstSequence];
35
+ /**
36
+ * Represents the remaining input after a match attempt, or `null` if no match is possible.
37
+ */
38
+ export type Remainder = readonly CodePoint[] | null;
39
+ /**
40
+ * Represents the result of a match operation, including the parsed AST rule and the remainder of the input.
41
+ */
42
+ export type MatchResult = readonly [AstRule, Remainder];
43
+ /**
44
+ * Represents an LL(1) parser function for matching input against grammar rules.
45
+ */
46
+ export type Match = (name: string, s: readonly CodePoint[]) => MatchResult;
11
47
  export declare const toData: (fr: FRule) => readonly [RuleSet, string];
48
+ export declare const dispatchMap: (ruleSet: RuleSet) => DispatchMap;
49
+ export declare const parser: (fr: FRule) => Match;
50
+ export {};
12
51
  /**
13
52
  * Either `{ variantItem: id }` or `id`.
14
53
  */
@@ -1,5 +1,9 @@
1
+ import { todo } from "../../dev/module.f.js";
1
2
  import { stringToCodePointList } from "../../text/utf16/module.f.js";
2
- import { toArray } from "../../types/list/module.f.js";
3
+ import { strictEqual } from "../../types/function/operator/module.f.js";
4
+ import { map, toArray } from "../../types/list/module.f.js";
5
+ import { rangeMap } from "../../types/range_map/module.f.js";
6
+ import { oneEncode, rangeDecode, } from "../module.f.js";
3
7
  const { entries } = Object;
4
8
  const find = (map) => (fr) => {
5
9
  for (const [k, v] of entries(map)) {
@@ -40,10 +44,11 @@ const variant = (fr) => map => {
40
44
  }
41
45
  return [map, set, rule];
42
46
  };
47
+ const mapOneEncode = map(oneEncode);
43
48
  const data = (dr) => {
44
49
  switch (typeof dr) {
45
50
  case 'string': {
46
- return sequence(toArray(stringToCodePointList(dr)));
51
+ return sequence(toArray(mapOneEncode(stringToCodePointList(dr))));
47
52
  }
48
53
  case 'number':
49
54
  return m => [m, {}, dr];
@@ -72,7 +77,87 @@ export const toData = (fr) => {
72
77
  const [, ruleSet, id] = toDataAdd({})(fr);
73
78
  return [ruleSet, id];
74
79
  };
75
- // type Dispatch = RangeMapArray<string>;
80
+ const dispatchOp = rangeMap({
81
+ union: a => b => {
82
+ if (a === null) {
83
+ return b;
84
+ }
85
+ if (b === null) {
86
+ return a;
87
+ }
88
+ throw ['can not merge [', a, '][', b, ']'];
89
+ },
90
+ equal: strictEqual,
91
+ def: null,
92
+ });
93
+ export const dispatchMap = (ruleSet) => {
94
+ const addRuleToDispatch = (dr, rule) => {
95
+ if (dr === null)
96
+ return null;
97
+ return { tag: dr.tag, rules: [...dr.rules, rule] };
98
+ };
99
+ const addTagToDispatch = (dr, tag) => {
100
+ if (dr === null)
101
+ return null;
102
+ return { tag, rules: dr.rules };
103
+ };
104
+ const dispatchRule = (dm, name) => {
105
+ if (name in dm) {
106
+ return dm;
107
+ }
108
+ const rule = ruleSet[name];
109
+ if (typeof rule === 'number') {
110
+ const range = rangeDecode(rule);
111
+ const dispatch = dispatchOp.fromRange(range)({ tag: undefined, rules: [] });
112
+ const dr = { emptyTag: undefined, rangeMap: dispatch };
113
+ return { ...dm, [name]: dr };
114
+ }
115
+ else if (rule instanceof Array) {
116
+ let emptyTag = true;
117
+ let result = [];
118
+ for (const item of rule) {
119
+ dm = dispatchRule(dm, item);
120
+ const dr = dm[item];
121
+ if (emptyTag === true) {
122
+ result = toArray(dispatchOp.merge(result)(dr.rangeMap));
123
+ emptyTag = dr.emptyTag !== undefined ? true : undefined;
124
+ }
125
+ else {
126
+ result = result.map(x => [addRuleToDispatch(x[0], dr), x[1]]);
127
+ }
128
+ }
129
+ const dr = { emptyTag, rangeMap: result };
130
+ return { ...dm, [name]: dr };
131
+ }
132
+ else {
133
+ const entries = Object.entries(rule);
134
+ let result = [];
135
+ let emptyTag = undefined;
136
+ for (const [tag, item] of entries) {
137
+ dm = dispatchRule(dm, item);
138
+ const dr = dm[item];
139
+ if (dr.emptyTag !== undefined) {
140
+ emptyTag = tag;
141
+ }
142
+ else {
143
+ const d = dr.rangeMap.map(x => [addTagToDispatch(x[0], tag), x[1]]);
144
+ result = toArray(dispatchOp.merge(result)(d));
145
+ }
146
+ }
147
+ const dr = { emptyTag, rangeMap: result };
148
+ return { ...dm, [name]: dr };
149
+ }
150
+ };
151
+ let result = {};
152
+ for (const k in ruleSet) {
153
+ result = dispatchRule(result, k);
154
+ }
155
+ return result;
156
+ };
157
+ export const parser = (fr) => {
158
+ const data = toData(fr);
159
+ return todo();
160
+ };
76
161
  /**
77
162
  * Either `{ variantItem: id }` or `id`.
78
163
  */
@@ -1,5 +1,7 @@
1
1
  declare const _default: {
2
- toData: () => void;
2
+ toData: (() => void)[];
3
+ variantTest: () => void;
4
+ dispatch: (() => void)[];
3
5
  example: () => void;
4
6
  };
5
7
  export default _default;
@@ -1,10 +1,115 @@
1
+ import { stringify } from "../../json/module.f.js";
2
+ import { sort } from "../../types/object/module.f.js";
3
+ import { range } from "../module.f.js";
1
4
  import { classic, deterministic } from "../testlib.f.js";
2
- import { toData } from "./module.f.js";
5
+ import { dispatchMap, toData } from "./module.f.js";
3
6
  export default {
4
- toData: () => {
5
- const c = toData(classic());
6
- const d = toData(deterministic());
7
+ toData: [
8
+ () => {
9
+ const c = toData(classic());
10
+ const d = toData(deterministic());
11
+ },
12
+ () => {
13
+ const stringRule = 'true';
14
+ const result = stringify(sort)(toData(stringRule));
15
+ if (result != '[{"":["0","1","2","3"],"0":1946157172,"1":1912602738,"2":1962934389,"3":1694498917},""]') {
16
+ throw result;
17
+ }
18
+ },
19
+ () => {
20
+ const terminalRangeRule = range('AF');
21
+ const result = stringify(sort)(toData(terminalRangeRule));
22
+ if (result != '[{"":1090519110},""]') {
23
+ throw result;
24
+ } //1090519110 = 65 * 2^24 + 70
25
+ },
26
+ () => {
27
+ const sequenceRangeRule = [range('AF'), range('af')];
28
+ const result = stringify(sort)(toData(sequenceRangeRule));
29
+ if (result != '[{"":["0","1"],"0":1090519110,"1":1627390054},""]') {
30
+ throw result;
31
+ }
32
+ },
33
+ () => {
34
+ const lazyRule = () => 'true';
35
+ const result = stringify(sort)(toData(lazyRule));
36
+ if (result != '[{"":1946157172,"0":1912602738,"1":1962934389,"2":1694498917,"lazyRule":["","0","1","2"]},"lazyRule"]') {
37
+ throw result;
38
+ }
39
+ },
40
+ () => {
41
+ const varintRule = { true: 'true', false: 'false' };
42
+ const result = stringify(sort)(toData(varintRule));
43
+ const expected = '[{"":{"false":"5","true":"0"},"0":["1","2","3","4"],"1":1946157172,"2":1912602738,"3":1962934389,"4":1694498917,"5":["6","7","8","9","4"],"6":1711276134,"7":1627390049,"8":1811939436,"9":1929379955},""]';
44
+ if (result != expected) {
45
+ throw [result, expected];
46
+ }
47
+ },
48
+ () => {
49
+ const lazyRule = () => 'true';
50
+ const lazyRule0 = () => 'false';
51
+ const result = stringify(sort)(toData([lazyRule, lazyRule0]));
52
+ const expected = '[{"":["lazyRule","lazyRule0"],"0":1946157172,"1":1912602738,"2":1962934389,"3":1694498917,"4":1711276134,"5":1627390049,"6":1811939436,"7":1929379955,"lazyRule":["0","1","2","3"],"lazyRule0":["4","5","6","7","3"]},""]';
53
+ if (result != expected) {
54
+ throw [result, expected];
55
+ }
56
+ },
57
+ ],
58
+ variantTest: () => {
59
+ const varintRule = { a: 'a', b: 'b' };
60
+ const result = stringify(sort)(toData(varintRule));
61
+ if (result != '[{"":{"a":"0","b":"2"},"0":["1"],"1":1627390049,"2":["3"],"3":1644167266},""]') {
62
+ throw result;
63
+ }
7
64
  },
65
+ dispatch: [
66
+ () => {
67
+ const terminalRangeRule = range('AF');
68
+ const data = toData(terminalRangeRule);
69
+ const dm = dispatchMap(data[0]);
70
+ const result = JSON.stringify(dm);
71
+ if (result != '{"":{"rangeMap":[[null,64],[{"rules":[]},70]]}}') {
72
+ throw result;
73
+ }
74
+ },
75
+ () => {
76
+ const stringRule = 'AB';
77
+ const data = toData(stringRule);
78
+ const dm = dispatchMap(data[0]);
79
+ const result = JSON.stringify(dm);
80
+ if (result != '{"0":{"rangeMap":[[null,64],[{"rules":[]},65]]},"1":{"rangeMap":[[null,65],[{"rules":[]},66]]},"":{"rangeMap":[[null,64],[{"rules":[{"rangeMap":[[null,65],[{"rules":[]},66]]}]},65]]}}') {
81
+ throw result;
82
+ }
83
+ },
84
+ () => {
85
+ const emptyRule = '';
86
+ const data = toData(emptyRule);
87
+ const dm = dispatchMap(data[0]);
88
+ const result = JSON.stringify(dm);
89
+ if (result != '{"":{"emptyTag":true,"rangeMap":[]}}') {
90
+ throw result;
91
+ }
92
+ },
93
+ () => {
94
+ const variantRule = { 'a': range('AA'), 'b': range('BB') };
95
+ const data = toData(variantRule);
96
+ const dm = dispatchMap(data[0]);
97
+ const result = JSON.stringify(dm);
98
+ if (result != '{"0":{"rangeMap":[[null,64],[{"rules":[]},65]]},"1":{"rangeMap":[[null,65],[{"rules":[]},66]]},"":{"rangeMap":[[null,64],[{"tag":"a","rules":[]},65],[{"tag":"b","rules":[]},66]]}}') {
99
+ throw result;
100
+ }
101
+ },
102
+ () => {
103
+ const emptyRule = '';
104
+ const variantRule = { 'e': emptyRule, 'a': range('AA') };
105
+ const data = toData(variantRule);
106
+ const dm = dispatchMap(data[0]);
107
+ const result = JSON.stringify(dm);
108
+ if (result != '{"0":{"emptyTag":true,"rangeMap":[]},"1":{"rangeMap":[[null,64],[{"rules":[]},65]]},"":{"emptyTag":"e","rangeMap":[[null,64],[{"tag":"a","rules":[]},65]]}}') {
109
+ throw result;
110
+ }
111
+ },
112
+ ],
8
113
  example: () => {
9
114
  const grammar = {
10
115
  space: 0x000020_000020,
@@ -48,13 +48,18 @@ export const hmac = (sha2) => {
48
48
  const op = p(oPad);
49
49
  const c = computeSync(sha2);
50
50
  const vbl = vec(blockLength);
51
+ // a and b should have the same size
51
52
  const xor = (a) => (b) => vbl(a ^ b);
52
- return k => m => {
53
+ return k => {
53
54
  const k1 = length(k) > blockLength ? c([k]) : k;
54
55
  const k2 = concat(k1)(vec(blockLength - length(k1))(0n));
55
56
  const xk2 = xor(k2);
56
- const f = (p, msg) => c([xk2(p), msg]);
57
- const m1 = f(ip, m);
58
- return f(op, m1);
57
+ const f = (p) => {
58
+ const x = xk2(p);
59
+ return (msg) => c([x, msg]);
60
+ };
61
+ const fip = f(ip);
62
+ const fop = f(op);
63
+ return m => fop(fip(m));
59
64
  };
60
65
  };
@@ -37,7 +37,7 @@ export type Base = {
37
37
  * const s = msbUtf8("The quick brown fox jumps over the lazy dog.")
38
38
  * let state = sha224.init
39
39
  * state = sha224.append(state)(s)
40
- * const h = sha224.end(state) // 0x619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4cn
40
+ * const h = sha224.end(state) // 0x1_619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4cn
41
41
  * ```
42
42
  */
43
43
  export type Sha2 = {
@@ -0,0 +1,5 @@
1
+ import { type Vec } from '../../types/bit_vec/module.f.ts';
2
+ import { type Init } from '../secp/module.f.ts';
3
+ import type { Sha2 } from '../sha2/module.f.ts';
4
+ export declare const newPrivateKey: (i: Init) => (random: Vec) => Vec;
5
+ export declare const sign: (sha2: Sha2) => (curveInit: Init) => (privateKey: Vec) => (messageHash: Vec) => readonly [bigint, bigint];
@@ -0,0 +1,53 @@
1
+ import { listToVec, msb, uint, vec, vec8, length } from "../../types/bit_vec/module.f.js";
2
+ import { hmac } from "../hmac/module.f.js";
3
+ import { curve } from "../secp/module.f.js";
4
+ const concat = listToVec(msb);
5
+ const v00 = vec8(0x00n);
6
+ const v01 = vec8(0x01n);
7
+ /**
8
+ * The size of the result equals the size of the hash.
9
+ *
10
+ * @param sha2 SHA2 hash function
11
+ * @returns A function that accepts a private key, a message hash and returns `k`.
12
+ */
13
+ const createK = (sha2) => {
14
+ const h = hmac(sha2);
15
+ let vs = vec(sha2.hashLength);
16
+ let k0 = vs(0x00n);
17
+ let v0 = vs(0x01n);
18
+ return (privateKey) => (messageHash) => {
19
+ const pm = concat([privateKey, messageHash]);
20
+ let k = k0;
21
+ let v = v0;
22
+ k = h(k)(concat([v, v00, pm]));
23
+ v = h(k)(v);
24
+ k = h(k)(concat([v, v01, pm]));
25
+ v = h(k)(v);
26
+ return uint(h(k)(v));
27
+ };
28
+ };
29
+ export const newPrivateKey = (i) => (random) => {
30
+ const { nf } = curve(i);
31
+ if (length(nf.max) < length(random)) {
32
+ throw "need more random bits";
33
+ }
34
+ return uint(random) % nf.p;
35
+ };
36
+ export const sign = (sha2) => (curveInit) => (privateKey) => (messageHash) => {
37
+ const { mul, pf } = curve(curveInit);
38
+ // const curveVec = vec(length(pf.max))
39
+ //`k` is a unique for each `z` and secret.
40
+ const k = createK(sha2)(privateKey)(messageHash) % pf.p;
41
+ // `R = G * k`.
42
+ const rp = mul(curveInit.g)(k);
43
+ // `r = R.x`
44
+ const r = rp === null ? 0n : rp[0];
45
+ // `s = ((z + r * d) / k)`.
46
+ const d = uint(privateKey);
47
+ const z = uint(messageHash);
48
+ const rd = pf.mul(r)(d);
49
+ const zrd = pf.add(z)(rd);
50
+ const kn1 = pf.reciprocal(k);
51
+ const s = pf.mul(zrd)(kn1);
52
+ return [r, s];
53
+ };
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export default {};
package/dev/test.f.js CHANGED
@@ -18,7 +18,7 @@ export default {
18
18
  ctorUndefined: () => {
19
19
  /** @type {any} */
20
20
  const o = {
21
- constructor: void 0
21
+ constructor: undefined
22
22
  };
23
23
  const c = o['constructor'];
24
24
  //console.log(c)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import { io } from "../../io/module.js";
2
+ import { loadModuleMap } from "../module.f.js";
3
+ import { isTest, parseTestSet } from "./module.f.js";
4
+ import * as nodeTest from 'node:test';
5
+ const isBun = typeof Bun !== 'undefined';
6
+ const createFramework = (fw) => (prefix, f) => fw.test(prefix, t => f((name, v) => t.test(name, v)));
7
+ const createBunFramework = (fw) => (prefix, f) => f((name, v) => fw.test(`${prefix}: ${name}`, v));
8
+ const framework = isBun ? createBunFramework(nodeTest) : createFramework(nodeTest);
9
+ const parse = parseTestSet(io.tryCatch);
10
+ const scanModule = (x) => async (subTestRunner) => {
11
+ let subTests = [x];
12
+ while (true) {
13
+ const [first, ...rest] = subTests;
14
+ if (first === undefined) {
15
+ break;
16
+ }
17
+ subTests = rest;
18
+ //
19
+ const [name, value] = first;
20
+ const set = parse(value);
21
+ if (typeof set === 'function') {
22
+ await subTestRunner(name, () => {
23
+ const r = set();
24
+ subTests = [...subTests, [`${name}()`, r]];
25
+ });
26
+ }
27
+ else {
28
+ for (const [j, y] of set) {
29
+ const pr = `${name}/${j}`;
30
+ subTests = [...subTests, [pr, y]];
31
+ }
32
+ }
33
+ }
34
+ };
35
+ const run = async () => {
36
+ const x = await loadModuleMap(io);
37
+ for (const [i, v] of Object.entries(x)) {
38
+ if (isTest(i)) {
39
+ framework(i, scanModule(['', v.default]));
40
+ }
41
+ }
42
+ };
43
+ await run();
@@ -1,7 +1,8 @@
1
+ import { type CsiConsole } from '../../text/sgr/module.f.ts';
1
2
  import type * as Result from '../../types/result/module.f.ts';
2
- import type { Io, Performance } from '../../io/module.f.ts';
3
+ import type { Io, Performance, TryCatch } from '../../io/module.f.ts';
3
4
  import { type ModuleMap } from '../module.f.ts';
4
- type Log<T> = (v: string) => (state: T) => T;
5
+ type Log<T> = CsiConsole;
5
6
  type Measure<T> = <R>(f: () => R) => (state: T) => readonly [R, number, T];
6
7
  type Input<T> = {
7
8
  readonly moduleMap: ModuleMap;
@@ -12,6 +13,10 @@ type Input<T> = {
12
13
  readonly tryCatch: <R>(f: () => R) => Result.Result<R, unknown>;
13
14
  readonly env: (n: string) => string | undefined;
14
15
  };
16
+ export declare const isTest: (s: string) => boolean;
17
+ export type Test = () => unknown;
18
+ export type TestSet = Test | readonly (readonly [string, unknown])[];
19
+ export declare const parseTestSet: (t: TryCatch) => (x: unknown) => TestSet;
15
20
  export declare const test: <T>(input: Input<T>) => readonly [number, T];
16
21
  export declare const anyLog: (f: (s: string) => void) => (s: string) => <T>(state: T) => T;
17
22
  export declare const measure: (p: Performance) => <R>(f: () => R) => <T>(state: T) => readonly [R, number, T];
@@ -0,0 +1,117 @@
1
+ import { entries, fold } from "../../types/list/module.f.js";
2
+ import { reset, fgGreen, fgRed, bold, stdio, stderr } from "../../text/sgr/module.f.js";
3
+ import { env, loadModuleMap } from "../module.f.js";
4
+ export const isTest = (s) => s.endsWith('test.f.js') || s.endsWith('test.f.ts');
5
+ const addPass = (delta) => (ts) => ({ ...ts, time: ts.time + delta, pass: ts.pass + 1 });
6
+ const addFail = (delta) => (ts) => ({ ...ts, time: ts.time + delta, fail: ts.fail + 1 });
7
+ const timeFormat = (a) => {
8
+ const y = Math.round(a * 10_000).toString();
9
+ const yl = 5 - y.length;
10
+ const x = '0'.repeat(yl > 0 ? yl : 0) + y;
11
+ const s = x.length - 4;
12
+ const b = x.substring(0, s);
13
+ const e = x.substring(s);
14
+ return `${b}.${e} ms`;
15
+ };
16
+ export const parseTestSet = (t) => (x) => {
17
+ switch (typeof x) {
18
+ case 'function': {
19
+ if (x.length === 0) {
20
+ const xt = x;
21
+ if (xt.name !== 'throw') {
22
+ return xt;
23
+ }
24
+ // Usual tests throw on error, but if the function name is 'throw',
25
+ // then the test passes if it throws.
26
+ return () => {
27
+ const [tag, value] = t(xt);
28
+ if (tag === 'ok') {
29
+ throw value;
30
+ }
31
+ return value;
32
+ };
33
+ }
34
+ break;
35
+ }
36
+ case 'object': {
37
+ if (x !== null) {
38
+ return Object.entries(x);
39
+ }
40
+ break;
41
+ }
42
+ }
43
+ return [];
44
+ };
45
+ export const test = (input) => {
46
+ let { moduleMap, log, error, measure, tryCatch, env, state } = input;
47
+ const isGitHub = env('GITHUB_ACTION') !== undefined;
48
+ const parse = parseTestSet(tryCatch);
49
+ const f = ([k, v]) => {
50
+ const test = i => v => ([ts, state]) => {
51
+ const next = test(`${i}| `);
52
+ const set = parse(v);
53
+ if (typeof set === 'function') {
54
+ const [[s, r], delta, state0] = measure(() => tryCatch(set))(state);
55
+ state = state0;
56
+ if (s !== 'ok') {
57
+ ts = addFail(delta)(ts);
58
+ if (isGitHub) {
59
+ // https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions
60
+ // https://github.com/OndraM/ci-detector/blob/main/src/Ci/GitHubActions.php
61
+ error(`::error file=${k},line=1,title=[3]['a']()::${r}`);
62
+ }
63
+ else {
64
+ error(`${i}() ${fgRed}error${reset}, ${timeFormat(delta)}`);
65
+ error(`${fgRed}${r}${reset}`);
66
+ }
67
+ }
68
+ else {
69
+ ts = addPass(delta)(ts);
70
+ log(`${i}() ${fgGreen}ok${reset}, ${timeFormat(delta)}`);
71
+ [ts, state] = next(r)([ts, state]);
72
+ }
73
+ }
74
+ else {
75
+ const f = ([k, v]) => ([time, state]) => {
76
+ log(`${i}${k}:`);
77
+ [time, state] = next(v)([time, state]);
78
+ return [time, state];
79
+ };
80
+ [ts, state] = fold(f)([ts, state])(set);
81
+ }
82
+ return [ts, state];
83
+ };
84
+ return ([ts, state]) => {
85
+ if (isTest(k)) {
86
+ log(`testing ${k}`);
87
+ [ts, state] = test('| ')(v.default)([ts, state]);
88
+ }
89
+ return [ts, state];
90
+ };
91
+ };
92
+ let ts = { time: 0, pass: 0, fail: 0 };
93
+ [ts, state] = fold(f)([ts, state])(Object.entries(moduleMap));
94
+ const fgFail = ts.fail === 0 ? fgGreen : fgRed;
95
+ log(`${bold}Number of tests: pass: ${fgGreen}${ts.pass}${reset}${bold}, fail: ${fgFail}${ts.fail}${reset}${bold}, total: ${ts.pass + ts.fail}${reset}`);
96
+ log(`${bold}Time: ${timeFormat(ts.time)}${reset}`);
97
+ return [ts.fail !== 0 ? 1 : 0, state];
98
+ };
99
+ export const anyLog = (f) => (s) => (state) => {
100
+ f(s);
101
+ return state;
102
+ };
103
+ export const measure = (p) => (f) => (state) => {
104
+ const b = p.now();
105
+ const r = f();
106
+ const e = p.now();
107
+ return [r, e - b, state];
108
+ };
109
+ export const main = async (io) => test({
110
+ moduleMap: await loadModuleMap(io),
111
+ log: stdio(io), // anyLog(io.console.log),
112
+ error: stderr(io), // anyLog(io.console.error),
113
+ measure: measure(io.performance),
114
+ tryCatch: io.tryCatch,
115
+ env: env(io),
116
+ state: undefined,
117
+ })[0];
@@ -0,0 +1,4 @@
1
+ declare const _default: {
2
+ throw: () => never;
3
+ };
4
+ export default _default;
@@ -0,0 +1,5 @@
1
+ export default {
2
+ throw: () => {
3
+ throw [() => { }, () => { }];
4
+ }
5
+ };
package/io/module.f.d.ts CHANGED
@@ -25,6 +25,7 @@ export type MakeDirectoryOptions = {
25
25
  * @see https://nodejs.org/api/fs.html
26
26
  */
27
27
  export type Fs = {
28
+ readonly writeSync: (fd: number, s: string) => void;
28
29
  readonly writeFileSync: (file: string, data: string) => void;
29
30
  readonly readFileSync: (path: string, options: BufferEncoding) => string | null;
30
31
  readonly existsSync: (path: string) => boolean;
@@ -60,6 +61,10 @@ export type Module = {
60
61
  export type Performance = {
61
62
  readonly now: () => number;
62
63
  };
64
+ export type Writable = {
65
+ readonly fd: number;
66
+ readonly isTTY: boolean;
67
+ };
63
68
  /**
64
69
  * Node.js Process interface
65
70
  * @see https://nodejs.org/api/process.html
@@ -69,7 +74,10 @@ export type Process = {
69
74
  readonly env: Env;
70
75
  readonly exit: (code: number) => never;
71
76
  readonly cwd: () => string;
77
+ readonly stdout: Writable;
78
+ readonly stderr: Writable;
72
79
  };
80
+ export type TryCatch = <T>(f: () => T) => Result<T, unknown>;
73
81
  /**
74
82
  * Core IO operations interface providing access to system resources
75
83
  */
@@ -80,7 +88,7 @@ export type Io = {
80
88
  readonly asyncImport: (s: string) => Promise<Module>;
81
89
  readonly performance: Performance;
82
90
  readonly fetch: (url: string) => Promise<Response>;
83
- readonly tryCatch: <T>(f: () => T) => Result<T, unknown>;
91
+ readonly tryCatch: TryCatch;
84
92
  readonly asyncTryCatch: <T>(f: () => Promise<T>) => Promise<Result<T, unknown>>;
85
93
  };
86
94
  /**
@@ -5,6 +5,7 @@ export const createVirtualIo = (files) => ({
5
5
  error: (..._d) => { }
6
6
  },
7
7
  fs: {
8
+ writeSync: (fd, s) => { },
8
9
  writeFileSync: (_file, _data) => { },
9
10
  readFileSync: (path, _options) => { return at(path)(files); },
10
11
  existsSync: (path) => { return at(path)(files) !== null; },
@@ -22,6 +23,8 @@ export const createVirtualIo = (files) => ({
22
23
  env: {},
23
24
  exit: n => { throw n; },
24
25
  cwd: () => '',
26
+ stdout: { fd: 1, isTTY: false },
27
+ stderr: { fd: 2, isTTY: false },
25
28
  },
26
29
  asyncImport: () => Promise.reject(),
27
30
  performance: {
@@ -1,7 +1,12 @@
1
1
  declare const _default: {
2
2
  literal: () => void;
3
3
  ownProperty: {
4
- nullish: () => void;
4
+ null: {
5
+ throw: () => PropertyDescriptor | undefined;
6
+ };
7
+ undefined: {
8
+ throw: () => PropertyDescriptor | undefined;
9
+ };
5
10
  bool: () => void;
6
11
  array: () => void;
7
12
  object: {
package/issues/test.f.js CHANGED
@@ -6,19 +6,11 @@ export default {
6
6
  const m = '<html>Hello</html>';
7
7
  },
8
8
  ownProperty: {
9
- nullish: () => {
10
- /* // panic
11
- const v = Object.getOwnPropertyDescriptor(null, 0)
12
- if (v !== undefined) {
13
- throw v
14
- }
15
- */
16
- /* // panic
17
- const v = Object.getOwnPropertyDescriptor(undefined, 0)
18
- if (v !== undefined) {
19
- throw v
20
- }
21
- */
9
+ null: {
10
+ throw: () => Object.getOwnPropertyDescriptor(null, 0),
11
+ },
12
+ undefined: {
13
+ throw: () => Object.getOwnPropertyDescriptor(undefined, 0),
22
14
  },
23
15
  bool: () => {
24
16
  const v = at(true)('x');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.6.10",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -10,11 +10,12 @@
10
10
  "scripts": {
11
11
  "prepack": "tsc --NoEmit false",
12
12
  "git-clean": "git clean -xf",
13
- "test20": "npm run prepack && node ./dev/test/module.js",
14
- "test22": "tsc && node --experimental-strip-types ./dev/test/module.ts",
15
- "test": "tsc && node ./dev/test/module.ts",
13
+ "test20": "npm run prepack && node --test",
14
+ "test22": "tsc && node --test --experimental-strip-types",
15
+ "test": "tsc && node --test",
16
16
  "index": "node ./dev/index/module.ts",
17
17
  "fsc": "node ./fsc/module.ts",
18
+ "fst": "node ./dev/tf/module.ts",
18
19
  "update": "npm run index && npm install"
19
20
  },
20
21
  "engines": {
@@ -22,7 +23,7 @@
22
23
  },
23
24
  "bin": {
24
25
  "fsc": "fsc/module.js",
25
- "fst": "dev/test/module.js"
26
+ "fst": "dev/tf/module.js"
26
27
  },
27
28
  "repository": {
28
29
  "type": "git",
@@ -44,7 +45,8 @@
44
45
  },
45
46
  "homepage": "https://github.com/functionalscript/functionalscript#readme",
46
47
  "devDependencies": {
47
- "@types/node": "^22.15.30",
48
- "typescript": "^5.8.3"
48
+ "@types/node": "^24.2.0",
49
+ "@typescript/native-preview": "^7.0.0-dev.20250805.1",
50
+ "typescript": "^5.9.2"
49
51
  }
50
52
  }
@@ -5,7 +5,6 @@ type ItemArray = readonly Item[];
5
5
  type ItemThunk = () => List<Item>;
6
6
  export type Item = string | ItemArray | ItemThunk;
7
7
  export declare const flat: (indent: string) => (text: Block) => List<string>;
8
- export declare const curly: (type: string) => (name: string) => (body: Block) => Block;
9
8
  /**
10
9
  * Converts a string to an UTF-8, represented as an MSB first bit vector.
11
10
  *
package/text/module.f.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { msb, u8List, u8ListToVec } from "../types/bit_vec/module.f.js";
2
2
  import { flatMap } from "../types/list/module.f.js";
3
- import * as utf8 from "./utf8/module.f.js";
3
+ import { fromCodePointList, toCodePointList } from "./utf8/module.f.js";
4
4
  import { stringToCodePointList, codePointListToString } from "./utf16/module.f.js";
5
5
  export const flat = (indent) => {
6
6
  const f = (prefix) => {
@@ -9,18 +9,18 @@ export const flat = (indent) => {
9
9
  };
10
10
  return f('');
11
11
  };
12
- export const curly = (type) => (name) => (body) => [`${type} ${name}`, '{', body, '}'];
12
+ const u8ListToVecMsb = u8ListToVec(msb);
13
13
  /**
14
14
  * Converts a string to an UTF-8, represented as an MSB first bit vector.
15
15
  *
16
16
  * @param s The input string to be converted.
17
17
  * @returns The resulting UTF-8 bit vector, MSB first.
18
18
  */
19
- export const msbUtf8 = (s) => u8ListToVec(msb)(utf8.fromCodePointList(stringToCodePointList(s)));
19
+ export const msbUtf8 = (s) => u8ListToVecMsb(fromCodePointList(stringToCodePointList(s)));
20
20
  /**
21
21
  * Converts a UTF-8 bit vector with MSB first encoding to a string.
22
22
  *
23
23
  * @param msbV - The UTF-8 bit vector with MSB first encoding.
24
24
  * @returns The resulting string.
25
25
  */
26
- export const msbUtf8ToString = (msbV) => codePointListToString(utf8.toCodePointList(u8List(msb)(msbV)));
26
+ export const msbUtf8ToString = (msbV) => codePointListToString(toCodePointList(u8List(msb)(msbV)));
@@ -1,3 +1,4 @@
1
+ import type { Io, Writable } from "../../io/module.f.ts";
1
2
  export declare const backspace: string;
2
3
  type End = 'm';
3
4
  type Csi = (code: number | string) => string;
@@ -23,4 +24,8 @@ export type Stdout = {
23
24
  };
24
25
  export type WriteText = (text: string) => WriteText;
25
26
  export declare const createConsoleText: (stdout: Stdout) => WriteText;
27
+ export type CsiConsole = (s: string) => void;
28
+ export declare const console: ({ fs: { writeSync } }: Io) => (w: Writable) => CsiConsole;
29
+ export declare const stdio: (io: Io) => CsiConsole;
30
+ export declare const stderr: (io: Io) => CsiConsole;
26
31
  export {};
@@ -1,6 +1,7 @@
1
1
  // Co control codes
2
2
  // https://en.wikipedia.org/wiki/ANSI_escape_code#C0_control_codes
3
3
  export const backspace = '\x08';
4
+ const begin = '\x1b[';
4
5
  /**
5
6
  * Control Sequence Introducer (CSI) escape sequence.
6
7
  * https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
@@ -8,7 +9,7 @@ export const backspace = '\x08';
8
9
  * @param end - The final character that indicates the type of sequence.
9
10
  * @returns A function that takes a code (number or string) and returns the complete ANSI escape sequence.
10
11
  */
11
- export const csi = (end) => code => `\x1b[${code.toString()}${end}`;
12
+ export const csi = (end) => code => `${begin}${code.toString()}${end}`;
12
13
  /**
13
14
  * Specialization of CSI for Select Graphic Rendition (SGR) sequences.
14
15
  * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR
@@ -31,3 +32,11 @@ export const createConsoleText = (stdout) => {
31
32
  };
32
33
  return f('');
33
34
  };
35
+ export const console = ({ fs: { writeSync } }) => (w) => {
36
+ const { isTTY } = w;
37
+ return isTTY
38
+ ? (s) => writeSync(w.fd, s + '\n')
39
+ : (s) => writeSync(w.fd, s.replace(/\x1b\[[0-9;]*m/g, '') + '\n');
40
+ };
41
+ export const stdio = (io) => console(io)(io.process.stdout);
42
+ export const stderr = (io) => console(io)(io.process.stderr);
@@ -152,3 +152,4 @@ export declare const u8ListToVec: (bo: BitOrder) => (list: List<number>) => Vec;
152
152
  * @returns A thunk that produces a list of unsigned 8-bit integers.
153
153
  */
154
154
  export declare const u8List: ({ popFront }: BitOrder) => (v: Vec) => Thunk<number>;
155
+ export declare const listToVec: (bo: BitOrder) => (list: List<Vec>) => Vec;
@@ -136,3 +136,4 @@ export const u8List = ({ popFront }) => {
136
136
  };
137
137
  return f;
138
138
  };
139
+ export const listToVec = (bo) => fold(bo.concat)(empty);
@@ -1,95 +0,0 @@
1
- import { entries, fold } from "../../types/list/module.f.js";
2
- import { reset, fgGreen, fgRed, bold } from "../../text/sgr/module.f.js";
3
- import { env, loadModuleMap } from "../module.f.js";
4
- const isTest = (s) => s.endsWith('test.f.js') || s.endsWith('test.f.ts');
5
- const addPass = (delta) => (ts) => ({ ...ts, time: ts.time + delta, pass: ts.pass + 1 });
6
- const addFail = (delta) => (ts) => ({ ...ts, time: ts.time + delta, fail: ts.fail + 1 });
7
- const timeFormat = (a) => {
8
- const y = Math.round(a * 10_000).toString();
9
- const yl = 5 - y.length;
10
- const x = '0'.repeat(yl > 0 ? yl : 0) + y;
11
- const s = x.length - 4;
12
- const b = x.substring(0, s);
13
- const e = x.substring(s);
14
- return `${b}.${e} ms`;
15
- };
16
- export const test = (input) => {
17
- let { moduleMap, log, error, measure, tryCatch, env, state } = input;
18
- const isGitHub = env('GITHUB_ACTION') !== undefined;
19
- const f = ([k, v]) => {
20
- const test = i => v => ([ts, state]) => {
21
- const next = test(`${i}| `);
22
- switch (typeof v) {
23
- case 'function': {
24
- if (v.length === 0) {
25
- const [[s, r], delta, state0] = measure(() => tryCatch(v))(state);
26
- state = state0;
27
- // Usual tests throw on error, but if the function name is 'throw', then the test passes if it throws.
28
- if ((s === 'error') === (v.name !== 'throw')) {
29
- ts = addFail(delta)(ts);
30
- if (isGitHub) {
31
- // https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions
32
- // https://github.com/OndraM/ci-detector/blob/main/src/Ci/GitHubActions.php
33
- state = error(`::error file=${k},line=1,title=[3]['a']()::${r}`)(state);
34
- }
35
- else {
36
- state = error(`${i}() ${fgRed}error${reset}, ${timeFormat(delta)}`)(state);
37
- state = error(`${fgRed}${r}${reset}`)(state);
38
- }
39
- }
40
- else {
41
- ts = addPass(delta)(ts);
42
- state = log(`${i}() ${fgGreen}ok${reset}, ${timeFormat(delta)}`)(state);
43
- }
44
- [ts, state] = next(r)([ts, state]);
45
- }
46
- break;
47
- }
48
- case 'object': {
49
- if (v !== null) {
50
- const f = ([k, v]) => ([time, state]) => {
51
- state = log(`${i}${k}:`)(state);
52
- [time, state] = next(v)([time, state]);
53
- return [time, state];
54
- };
55
- [ts, state] = fold(f)([ts, state])(v instanceof Array ? entries(v) : Object.entries(v));
56
- }
57
- break;
58
- }
59
- }
60
- return [ts, state];
61
- };
62
- return ([ts, state]) => {
63
- if (isTest(k)) {
64
- state = log(`testing ${k}`)(state);
65
- [ts, state] = test('| ')(v.default)([ts, state]);
66
- }
67
- return [ts, state];
68
- };
69
- };
70
- let ts = { time: 0, pass: 0, fail: 0 };
71
- [ts, state] = fold(f)([ts, state])(Object.entries(moduleMap));
72
- const fgFail = ts.fail === 0 ? fgGreen : fgRed;
73
- state = log(`${bold}Number of tests: pass: ${fgGreen}${ts.pass}${reset}${bold}, fail: ${fgFail}${ts.fail}${reset}${bold}, total: ${ts.pass + ts.fail}${reset}`)(state);
74
- state = log(`${bold}Time: ${timeFormat(ts.time)}${reset}`)(state);
75
- return [ts.fail !== 0 ? 1 : 0, state];
76
- };
77
- export const anyLog = (f) => (s) => (state) => {
78
- f(s);
79
- return state;
80
- };
81
- export const measure = (p) => (f) => (state) => {
82
- const b = p.now();
83
- const r = f();
84
- const e = p.now();
85
- return [r, e - b, state];
86
- };
87
- export const main = async (io) => test({
88
- moduleMap: await loadModuleMap(io),
89
- log: anyLog(io.console.log),
90
- error: anyLog(io.console.error),
91
- measure: measure(io.performance),
92
- tryCatch: io.tryCatch,
93
- env: env(io),
94
- state: undefined,
95
- })[0];
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes