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.
- package/bnf/data/module.f.d.ts +40 -1
- package/bnf/data/module.f.js +88 -3
- package/bnf/data/test.f.d.ts +3 -1
- package/bnf/data/test.f.js +109 -4
- package/crypto/hmac/module.f.js +9 -4
- package/crypto/sha2/module.f.d.ts +1 -1
- package/crypto/sign/module.f.d.ts +5 -0
- package/crypto/sign/module.f.js +53 -0
- package/crypto/sign/test.f.d.ts +2 -0
- package/crypto/sign/test.f.js +1 -0
- package/dev/test.f.js +1 -1
- package/dev/tf/all.test.d.ts +1 -0
- package/dev/tf/all.test.js +43 -0
- package/dev/{test → tf}/module.f.d.ts +7 -2
- package/dev/tf/module.f.js +117 -0
- package/dev/tf/test.f.d.ts +4 -0
- package/dev/tf/test.f.js +5 -0
- package/io/module.f.d.ts +9 -1
- package/io/virtual/module.f.js +3 -0
- package/issues/test.f.d.ts +6 -1
- package/issues/test.f.js +5 -13
- package/package.json +9 -7
- package/text/module.f.d.ts +0 -1
- package/text/module.f.js +4 -4
- package/text/sgr/module.f.d.ts +5 -0
- package/text/sgr/module.f.js +10 -1
- package/types/bit_vec/module.f.d.ts +1 -0
- package/types/bit_vec/module.f.js +1 -0
- package/dev/test/module.f.js +0 -95
- /package/dev/{test → tf}/module.d.ts +0 -0
- /package/dev/{test → tf}/module.js +0 -0
- /package/djs/{test → examples}/input.f.d.ts +0 -0
- /package/djs/{test → examples}/input.f.js +0 -0
- /package/djs/{test → examples}/m.f.d.ts +0 -0
- /package/djs/{test → examples}/m.f.js +0 -0
- /package/issues/demo/{test → sample}/test.f.js +0 -0
package/bnf/data/module.f.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
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
|
*/
|
package/bnf/data/module.f.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { todo } from "../../dev/module.f.js";
|
|
1
2
|
import { stringToCodePointList } from "../../text/utf16/module.f.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
*/
|
package/bnf/data/test.f.d.ts
CHANGED
package/bnf/data/test.f.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
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,
|
package/crypto/hmac/module.f.js
CHANGED
|
@@ -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 =>
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
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) //
|
|
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 @@
|
|
|
1
|
+
export default {};
|
package/dev/test.f.js
CHANGED
|
@@ -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> =
|
|
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];
|
package/dev/tf/test.f.js
ADDED
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:
|
|
91
|
+
readonly tryCatch: TryCatch;
|
|
84
92
|
readonly asyncTryCatch: <T>(f: () => Promise<T>) => Promise<Result<T, unknown>>;
|
|
85
93
|
};
|
|
86
94
|
/**
|
package/io/virtual/module.f.js
CHANGED
|
@@ -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: {
|
package/issues/test.f.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
2
|
literal: () => void;
|
|
3
3
|
ownProperty: {
|
|
4
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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
|
|
14
|
-
"test22": "tsc && node --experimental-strip-types
|
|
15
|
-
"test": "tsc && node
|
|
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/
|
|
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": "^
|
|
48
|
-
"typescript": "^
|
|
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
|
}
|
package/text/module.f.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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) =>
|
|
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(
|
|
26
|
+
export const msbUtf8ToString = (msbV) => codePointListToString(toCodePointList(u8List(msb)(msbV)));
|
package/text/sgr/module.f.d.ts
CHANGED
|
@@ -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 {};
|
package/text/sgr/module.f.js
CHANGED
|
@@ -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 =>
|
|
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;
|
package/dev/test/module.f.js
DELETED
|
@@ -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
|