@fncts/schema 0.0.5 → 0.0.6
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/AST.d.ts +528 -0
- package/ASTAnnotation.d.ts +82 -0
- package/ASTAnnotationMap.d.ts +14 -0
- package/Gen.d.ts +15 -0
- package/InvalidInterpretationError.d.ts +5 -0
- package/ParseError.d.ts +107 -0
- package/ParseResult.d.ts +24 -0
- package/Parser/api.d.ts +42 -0
- package/Parser/definition.d.ts +22 -0
- package/Parser/interpreter.d.ts +6 -0
- package/Parser.d.ts +2 -0
- package/Schema/api/conc.d.ts +18 -0
- package/Schema/api/hashMap.d.ts +21 -0
- package/Schema/api/immutableArray.d.ts +23 -0
- package/Schema/api/list.d.ts +23 -0
- package/Schema/api/maybe.d.ts +21 -0
- package/Schema/api.d.ts +243 -0
- package/Schema/definition.d.ts +29 -0
- package/Schema/derivations.d.ts +70 -0
- package/Schema.d.ts +2 -144
- package/_cjs/AST.cjs +1171 -0
- package/_cjs/AST.cjs.map +1 -0
- package/_cjs/ASTAnnotation.cjs +111 -0
- package/_cjs/ASTAnnotation.cjs.map +1 -0
- package/_cjs/ASTAnnotationMap.cjs +35 -0
- package/_cjs/ASTAnnotationMap.cjs.map +1 -0
- package/_cjs/Gen.cjs +185 -0
- package/_cjs/Gen.cjs.map +1 -0
- package/_cjs/InvalidInterpretationError.cjs +18 -0
- package/_cjs/InvalidInterpretationError.cjs.map +1 -0
- package/_cjs/ParseError.cjs +222 -0
- package/_cjs/ParseError.cjs.map +1 -0
- package/_cjs/{Decoder.cjs → ParseResult.cjs} +24 -22
- package/_cjs/ParseResult.cjs.map +1 -0
- package/_cjs/Parser/api.cjs +80 -0
- package/_cjs/Parser/api.cjs.map +1 -0
- package/_cjs/{Guard.cjs → Parser/definition.cjs} +17 -22
- package/_cjs/Parser/definition.cjs.map +1 -0
- package/_cjs/Parser/interpreter.cjs +409 -0
- package/_cjs/Parser/interpreter.cjs.map +1 -0
- package/_cjs/Parser.cjs +28 -0
- package/_cjs/Parser.cjs.map +1 -0
- package/_cjs/Schema/api/conc.cjs +84 -0
- package/_cjs/Schema/api/conc.cjs.map +1 -0
- package/_cjs/Schema/api/hashMap.cjs +161 -0
- package/_cjs/Schema/api/hashMap.cjs.map +1 -0
- package/_cjs/Schema/api/immutableArray.cjs +90 -0
- package/_cjs/Schema/api/immutableArray.cjs.map +1 -0
- package/_cjs/Schema/api/list.cjs +98 -0
- package/_cjs/Schema/api/list.cjs.map +1 -0
- package/_cjs/Schema/api/maybe.cjs +75 -0
- package/_cjs/Schema/api/maybe.cjs.map +1 -0
- package/_cjs/Schema/api.cjs +424 -0
- package/_cjs/Schema/api.cjs.map +1 -0
- package/_cjs/Schema/definition.cjs +26 -0
- package/_cjs/Schema/definition.cjs.map +1 -0
- package/_cjs/Schema/derivations.cjs +108 -0
- package/_cjs/Schema/derivations.cjs.map +1 -0
- package/_cjs/Schema.cjs +20 -237
- package/_cjs/Schema.cjs.map +1 -1
- package/_cjs/utils.cjs +52 -0
- package/_cjs/utils.cjs.map +1 -0
- package/_mjs/AST.mjs +1060 -0
- package/_mjs/AST.mjs.map +1 -0
- package/_mjs/ASTAnnotation.mjs +80 -0
- package/_mjs/ASTAnnotation.mjs.map +1 -0
- package/_mjs/ASTAnnotationMap.mjs +27 -0
- package/_mjs/ASTAnnotationMap.mjs.map +1 -0
- package/_mjs/Gen.mjs +176 -0
- package/_mjs/Gen.mjs.map +1 -0
- package/_mjs/InvalidInterpretationError.mjs +10 -0
- package/_mjs/InvalidInterpretationError.mjs.map +1 -0
- package/_mjs/ParseError.mjs +200 -0
- package/_mjs/ParseError.mjs.map +1 -0
- package/_mjs/ParseResult.mjs +21 -0
- package/_mjs/ParseResult.mjs.map +1 -0
- package/_mjs/Parser/api.mjs +67 -0
- package/_mjs/Parser/api.mjs.map +1 -0
- package/_mjs/Parser/definition.mjs +15 -0
- package/_mjs/Parser/definition.mjs.map +1 -0
- package/_mjs/Parser/interpreter.mjs +401 -0
- package/_mjs/Parser/interpreter.mjs.map +1 -0
- package/_mjs/Parser.mjs +4 -0
- package/_mjs/Parser.mjs.map +1 -0
- package/_mjs/Schema/api/conc.mjs +72 -0
- package/_mjs/Schema/api/conc.mjs.map +1 -0
- package/_mjs/Schema/api/hashMap.mjs +150 -0
- package/_mjs/Schema/api/hashMap.mjs.map +1 -0
- package/_mjs/Schema/api/immutableArray.mjs +79 -0
- package/_mjs/Schema/api/immutableArray.mjs.map +1 -0
- package/_mjs/Schema/api/list.mjs +87 -0
- package/_mjs/Schema/api/list.mjs.map +1 -0
- package/_mjs/Schema/api/maybe.mjs +64 -0
- package/_mjs/Schema/api/maybe.mjs.map +1 -0
- package/_mjs/Schema/api.mjs +367 -0
- package/_mjs/Schema/api.mjs.map +1 -0
- package/_mjs/Schema/definition.mjs +16 -0
- package/_mjs/Schema/definition.mjs.map +1 -0
- package/_mjs/Schema/derivations.mjs +94 -0
- package/_mjs/Schema/derivations.mjs.map +1 -0
- package/_mjs/Schema.mjs +3 -212
- package/_mjs/Schema.mjs.map +1 -1
- package/_mjs/utils.mjs +41 -0
- package/_mjs/utils.mjs.map +1 -0
- package/_src/AST.ts +1353 -0
- package/_src/ASTAnnotation.ts +98 -0
- package/_src/ASTAnnotationMap.ts +38 -0
- package/_src/Gen.ts +171 -0
- package/_src/InvalidInterpretationError.ts +6 -0
- package/_src/ParseError.ts +237 -0
- package/_src/ParseResult.ts +26 -0
- package/_src/Parser/api.ts +71 -0
- package/_src/Parser/definition.ts +24 -0
- package/_src/Parser/interpreter.ts +442 -0
- package/_src/Parser.ts +4 -0
- package/_src/Schema/api/conc.ts +78 -0
- package/_src/Schema/api/hashMap.ts +184 -0
- package/_src/Schema/api/immutableArray.ts +88 -0
- package/_src/Schema/api/list.ts +96 -0
- package/_src/Schema/api/maybe.ts +68 -0
- package/_src/Schema/api.ts +530 -0
- package/_src/Schema/definition.ts +32 -0
- package/_src/Schema/derivations.ts +185 -0
- package/_src/Schema.ts +4 -254
- package/_src/global.ts +53 -0
- package/_src/utils.ts +48 -0
- package/global.d.ts +52 -0
- package/package.json +2 -2
- package/utils.d.ts +8 -0
- package/Decoder.d.ts +0 -3
- package/Encoder.d.ts +0 -4
- package/Guard.d.ts +0 -3
- package/Schemable.d.ts +0 -39
- package/_cjs/Decoder.cjs.map +0 -1
- package/_cjs/Encoder.cjs +0 -45
- package/_cjs/Encoder.cjs.map +0 -1
- package/_cjs/Guard.cjs.map +0 -1
- package/_cjs/Schemable.cjs +0 -6
- package/_cjs/Schemable.cjs.map +0 -1
- package/_mjs/Decoder.mjs +0 -20
- package/_mjs/Decoder.mjs.map +0 -1
- package/_mjs/Encoder.mjs +0 -36
- package/_mjs/Encoder.mjs.map +0 -1
- package/_mjs/Guard.mjs +0 -20
- package/_mjs/Guard.mjs.map +0 -1
- package/_mjs/Schemable.mjs +0 -2
- package/_mjs/Schemable.mjs.map +0 -1
- package/_src/Decoder.ts +0 -20
- package/_src/Encoder.ts +0 -38
- package/_src/Guard.ts +0 -20
- package/_src/Schemable.ts +0 -46
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Validation } from "@fncts/base/data/Branded";
|
|
2
|
+
|
|
3
|
+
export const ASTAnnotationVariance = Symbol.for("fncts.schema.ASTAnnotation.Variance");
|
|
4
|
+
export type ASTAnnotationVariance = typeof ASTAnnotationVariance;
|
|
5
|
+
|
|
6
|
+
export const ASTAnnotationTypeId = Symbol.for("fncts.schema.ASTAnnotation");
|
|
7
|
+
export type ASTAnnotationTypeId = typeof ASTAnnotationTypeId;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @tsplus type fncts.schema.ASTAnnotation
|
|
11
|
+
* @tsplus companion fncts.schema.ASTAnnotationOps
|
|
12
|
+
*/
|
|
13
|
+
export class ASTAnnotation<V> implements Hashable, Equatable {
|
|
14
|
+
readonly [ASTAnnotationTypeId]: ASTAnnotationTypeId = ASTAnnotationTypeId;
|
|
15
|
+
declare [ASTAnnotationVariance]: {
|
|
16
|
+
readonly _V: (_: never) => V;
|
|
17
|
+
};
|
|
18
|
+
constructor(readonly tag: Tag<V>, readonly identifier: string, readonly combine: (v1: V, v2: V) => V) {}
|
|
19
|
+
get [Symbol.hash]() {
|
|
20
|
+
return Hashable.combine(Hashable.string(this.identifier), Hashable.unknown(this.tag));
|
|
21
|
+
}
|
|
22
|
+
[Symbol.equals](that: unknown) {
|
|
23
|
+
return isASTAnnotation(that) && Equatable.strictEquals(this.tag, that.tag) && this.identifier === that.identifier;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isASTAnnotation(u: unknown): u is ASTAnnotation<unknown> {
|
|
28
|
+
return isObject(u) && ASTAnnotationTypeId in u;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const TitleTag = Tag<string>();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @tsplus static fncts.schema.ASTAnnotationOps Title
|
|
35
|
+
*/
|
|
36
|
+
export const Title = new ASTAnnotation(TitleTag, "Title", (_, a) => a);
|
|
37
|
+
|
|
38
|
+
export const IdentifierTag = Tag<string>();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @tsplus static fncts.schema.ASTAnnotationOps Identifier
|
|
42
|
+
*/
|
|
43
|
+
export const Identifier = new ASTAnnotation(IdentifierTag, "Identifier", (_, a) => a);
|
|
44
|
+
|
|
45
|
+
export const DescriptionTag = Tag<string>();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @tsplus static fncts.schema.ASTAnnotationOps Description
|
|
49
|
+
*/
|
|
50
|
+
export const Description = new ASTAnnotation(DescriptionTag, "Description", (_, a) => a);
|
|
51
|
+
|
|
52
|
+
export const MessageTag = Tag<(_: unknown) => string>();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @tsplus static fncts.schema.ASTAnnotationOps Message
|
|
56
|
+
*/
|
|
57
|
+
export const Message = new ASTAnnotation(MessageTag, "Message", (_, a) => a);
|
|
58
|
+
|
|
59
|
+
export const BrandTag = Tag<Vector<Validation<any, any>>>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @tsplus static fncts.schema.ASTAnnotationOps Brand
|
|
63
|
+
*/
|
|
64
|
+
export const Brand = new ASTAnnotation(BrandTag, "Brand", (a, b) => a.concat(b));
|
|
65
|
+
|
|
66
|
+
export const OptionalTag = Tag<boolean>();
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @tsplus static fncts.schema.ASTAnnotationOps Optional
|
|
70
|
+
*/
|
|
71
|
+
export const Optional = new ASTAnnotation(OptionalTag, "Optional", (_, b) => b);
|
|
72
|
+
|
|
73
|
+
export const ParseOptionalTag = Tag<boolean>();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @tsplus static fncts.schema.ASTAnnotationOps ParseOptional
|
|
77
|
+
*/
|
|
78
|
+
export const ParseOptional = new ASTAnnotation(ParseOptionalTag, "ParseOptional", (_, b) => b);
|
|
79
|
+
|
|
80
|
+
export type Hook<A> = (...typeParameters: ReadonlyArray<A>) => A;
|
|
81
|
+
|
|
82
|
+
export function hook(handler: (...typeParameters: ReadonlyArray<any>) => any): Hook<any> {
|
|
83
|
+
return handler;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const ParserHookTag = Tag<Hook<any>>();
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @tsplus static fncts.schema.ASTAnnotationOps ParserHook
|
|
90
|
+
*/
|
|
91
|
+
export const ParserHook = new ASTAnnotation(ParserHookTag, "ParserHook", (_, b) => b);
|
|
92
|
+
|
|
93
|
+
export const GenHookTag = Tag<Hook<any>>();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @tsplus static fncts.schema.ASTAnnotationOps GenHook
|
|
97
|
+
*/
|
|
98
|
+
export const GenHook = new ASTAnnotation(GenHookTag, "GenHook", (_, b) => b);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ASTAnnotation } from "./ASTAnnotation.js";
|
|
2
|
+
|
|
3
|
+
export class ASTAnnotationMap {
|
|
4
|
+
constructor(readonly map: HashMap<ASTAnnotation<any>, any>) {}
|
|
5
|
+
combine(that: ASTAnnotationMap): ASTAnnotationMap {
|
|
6
|
+
return new ASTAnnotationMap(
|
|
7
|
+
Conc.from(this.map)
|
|
8
|
+
.concat(Conc.from(that.map))
|
|
9
|
+
.foldLeft(HashMap.empty(), (acc, [k, v]) =>
|
|
10
|
+
acc.set(
|
|
11
|
+
k,
|
|
12
|
+
acc.get(k).match(
|
|
13
|
+
() => v,
|
|
14
|
+
(_) => k.combine(_, v),
|
|
15
|
+
),
|
|
16
|
+
),
|
|
17
|
+
),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
get<V>(key: ASTAnnotation<V>): Maybe<V> {
|
|
21
|
+
return this.map.get(key);
|
|
22
|
+
}
|
|
23
|
+
private overwrite<V>(key: ASTAnnotation<V>, value: V): ASTAnnotationMap {
|
|
24
|
+
return new ASTAnnotationMap(this.map.set(key, value));
|
|
25
|
+
}
|
|
26
|
+
private update<V>(key: ASTAnnotation<V>, f: (v: Maybe<V>) => V): ASTAnnotationMap {
|
|
27
|
+
return this.overwrite(key, f(this.get(key)));
|
|
28
|
+
}
|
|
29
|
+
annotate<V>(key: ASTAnnotation<V>, value: V): ASTAnnotationMap {
|
|
30
|
+
return this.update(key, (v) =>
|
|
31
|
+
v.match(
|
|
32
|
+
() => value,
|
|
33
|
+
(v) => key.combine(v, value),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
static empty: ASTAnnotationMap = new ASTAnnotationMap(HashMap.empty());
|
|
38
|
+
}
|
package/_src/Gen.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { Hook } from "@fncts/schema/ASTAnnotation";
|
|
2
|
+
import type { Sized } from "@fncts/test/control/Sized";
|
|
3
|
+
|
|
4
|
+
import { ASTTag } from "@fncts/schema/AST";
|
|
5
|
+
import { ASTAnnotation } from "@fncts/schema/ASTAnnotation";
|
|
6
|
+
import { InvalidInterpretationError } from "@fncts/schema/InvalidInterpretationError";
|
|
7
|
+
import { memoize } from "@fncts/schema/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @tsplus getter fncts.schema.Schema genFrom
|
|
11
|
+
*/
|
|
12
|
+
export function genFrom<A>(self: Schema<A>): Gen<Sized, unknown> {
|
|
13
|
+
return go(self.ast.getFrom);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @tsplus getter fncts.schema.Schema genTo
|
|
18
|
+
*/
|
|
19
|
+
export function genTo<A>(self: Schema<A>): Gen<Sized, A> {
|
|
20
|
+
return go(self.ast);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getHook(ast: AST): Maybe<Hook<Gen<Sized, any>>> {
|
|
24
|
+
return ast.annotations.get(ASTAnnotation.GenHook);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function record<K extends PropertyKey, V>(
|
|
28
|
+
key: Gen<Sized, K>,
|
|
29
|
+
value: Gen<Sized, V>,
|
|
30
|
+
): Gen<Sized, { readonly [k in K]: V }> {
|
|
31
|
+
return Gen.tuple(key, value)
|
|
32
|
+
.arrayWith({ maxLength: 10 })
|
|
33
|
+
.map((tuples) => {
|
|
34
|
+
const out: { [k in K]: V } = {} as any;
|
|
35
|
+
for (const [k, v] of tuples) {
|
|
36
|
+
out[k] = v;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const go = memoize(function go(ast: AST): Gen<Sized, any> {
|
|
43
|
+
AST.concrete(ast);
|
|
44
|
+
switch (ast._tag) {
|
|
45
|
+
case ASTTag.Declaration:
|
|
46
|
+
return getHook(ast).match(
|
|
47
|
+
() => go(ast.type),
|
|
48
|
+
(hook) => hook(...ast.typeParameters.map(go)),
|
|
49
|
+
);
|
|
50
|
+
case ASTTag.Literal:
|
|
51
|
+
return Gen.constant(ast.literal);
|
|
52
|
+
case ASTTag.UniqueSymbol:
|
|
53
|
+
return Gen.constant(ast.symbol);
|
|
54
|
+
case ASTTag.UndefinedKeyword:
|
|
55
|
+
return Gen.constant(undefined);
|
|
56
|
+
case ASTTag.VoidKeyword:
|
|
57
|
+
return Gen.constant(undefined);
|
|
58
|
+
case ASTTag.NeverKeyword:
|
|
59
|
+
return Gen.fromIO(IO.haltNow(new InvalidInterpretationError("cannot build a Gen for `never`")));
|
|
60
|
+
case ASTTag.UnknownKeyword:
|
|
61
|
+
// TODO: Gen.anything
|
|
62
|
+
return Gen.constant("unknown");
|
|
63
|
+
case ASTTag.AnyKeyword:
|
|
64
|
+
// TODO: Gen.anything
|
|
65
|
+
return Gen.constant("any");
|
|
66
|
+
case ASTTag.StringKeyword:
|
|
67
|
+
return Gen.fullUnicodeString();
|
|
68
|
+
case ASTTag.NumberKeyword:
|
|
69
|
+
return Gen.float;
|
|
70
|
+
case ASTTag.BooleanKeyword:
|
|
71
|
+
return Gen.boolean;
|
|
72
|
+
case ASTTag.BigIntKeyword:
|
|
73
|
+
return Gen.bigInt;
|
|
74
|
+
case ASTTag.SymbolKeyword:
|
|
75
|
+
return Gen.fullUnicodeString().map((s) => Symbol.for(s));
|
|
76
|
+
case ASTTag.ObjectKeyword:
|
|
77
|
+
// TODO: Gen.anything
|
|
78
|
+
return Gen.constant({});
|
|
79
|
+
case ASTTag.TemplateLiteral: {
|
|
80
|
+
const components: Array<Gen<Sized, string>> = [Gen.constant(ast.head)];
|
|
81
|
+
for (const span of ast.spans) {
|
|
82
|
+
components.push(Gen.fullUnicodeString({ maxLength: 5 }));
|
|
83
|
+
components.push(Gen.constant(span.literal));
|
|
84
|
+
}
|
|
85
|
+
return Gen.tuple(...components).map((spans) => spans.join(""));
|
|
86
|
+
}
|
|
87
|
+
case ASTTag.Tuple: {
|
|
88
|
+
const elements = ast.elements.map((e) => go(e.type));
|
|
89
|
+
const rest = ast.rest.map((restElement) => restElement.map(go));
|
|
90
|
+
let output = Gen.tuple(...elements);
|
|
91
|
+
if (elements.length > 0 && rest.isNothing()) {
|
|
92
|
+
const firstOptionalIndex = ast.elements.findIndex((e) => e.isOptional);
|
|
93
|
+
if (firstOptionalIndex !== -1) {
|
|
94
|
+
output = output.flatMap((as) =>
|
|
95
|
+
Gen.intWith({ min: firstOptionalIndex, max: elements.length - 1 }).map((i) => as.slice(0, i)),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (rest.isJust()) {
|
|
100
|
+
const head = rest.value.unsafeHead!;
|
|
101
|
+
const tail = rest.value.tail;
|
|
102
|
+
output = output.flatMap((as) => head.arrayWith({ maxLength: 5 }).map((rest) => [...as, ...rest]));
|
|
103
|
+
for (let j = 0; j < tail.length; j++) {
|
|
104
|
+
output = output.flatMap((as) => tail[j]!.map((a) => [...as, a]));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return output as Gen<Sized, any>;
|
|
108
|
+
}
|
|
109
|
+
case ASTTag.TypeLiteral: {
|
|
110
|
+
const propertySignatureTypes = ast.propertySignatures.map((ps) => go(ps.type));
|
|
111
|
+
const indexSignatures = ast.indexSignatures.map((is) => [go(is.parameter), go(is.type)] as const);
|
|
112
|
+
const gens: any = {};
|
|
113
|
+
const requiredGens: Record<PropertyKey, Gen<any, any>> = {};
|
|
114
|
+
const optionalGens: Record<PropertyKey, Gen<any, any>> = {};
|
|
115
|
+
for (let i = 0; i < propertySignatureTypes.length; i++) {
|
|
116
|
+
const ps = ast.propertySignatures[i]!;
|
|
117
|
+
const name = ps.name;
|
|
118
|
+
if (!ps.isOptional) {
|
|
119
|
+
requiredGens[name] = propertySignatureTypes[i]!;
|
|
120
|
+
} else {
|
|
121
|
+
optionalGens[name] = propertySignatureTypes[i]!;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let output = Gen.struct(requiredGens).zipWith(Gen.partial(optionalGens), (a, b) => ({ ...a, ...b }));
|
|
125
|
+
for (let i = 0; i < indexSignatures.length; i++) {
|
|
126
|
+
const parameter = indexSignatures[i]![0]!;
|
|
127
|
+
const type = indexSignatures[i]![1]!;
|
|
128
|
+
output = output.flatMap((o) => {
|
|
129
|
+
return record(parameter, type).map((d) => ({ ...d, ...o }));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return output;
|
|
133
|
+
}
|
|
134
|
+
case ASTTag.Union: {
|
|
135
|
+
const types = ast.types.map(go);
|
|
136
|
+
return Gen.oneOf(...types) as Gen<Sized, any>;
|
|
137
|
+
}
|
|
138
|
+
case ASTTag.Lazy: {
|
|
139
|
+
return getHook(ast).match(
|
|
140
|
+
() => {
|
|
141
|
+
const f = () => go(ast.getAST());
|
|
142
|
+
const get = memoize<typeof f, Gen<any, any>>(f);
|
|
143
|
+
return Gen.defer(() => get(f));
|
|
144
|
+
},
|
|
145
|
+
(handler) => handler(),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
case ASTTag.Enum: {
|
|
149
|
+
if (ast.enums.length === 0) {
|
|
150
|
+
return Gen.fromIO(IO.haltNow(new InvalidInterpretationError("cannot build a Gen for an empty enum")));
|
|
151
|
+
}
|
|
152
|
+
return Gen.oneOf(...ast.enums.map(([_, value]) => Gen.constant(value))) as Gen<Sized, any>;
|
|
153
|
+
}
|
|
154
|
+
case ASTTag.Refinement: {
|
|
155
|
+
const from = go(ast.from);
|
|
156
|
+
return getHook(ast).match(
|
|
157
|
+
() => from.filter((a) => ast.decode(a).isRight()),
|
|
158
|
+
(handler) => handler(from),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
case ASTTag.Transform:
|
|
162
|
+
return go(ast.to);
|
|
163
|
+
case ASTTag.Validation: {
|
|
164
|
+
const from = go(ast.from);
|
|
165
|
+
return getHook(ast).match(
|
|
166
|
+
() => from.filter((a) => ast.validation.every((v) => v.validate(a))),
|
|
167
|
+
(handler) => handler(from),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const InvalidInterpretationErrorTypeId = Symbol.for("fncts.schema.InvalidInterpretationError");
|
|
2
|
+
export type InvalidInterpretationErrorTypeId = typeof InvalidInterpretationErrorTypeId;
|
|
3
|
+
|
|
4
|
+
export class InvalidInterpretationError extends Error {
|
|
5
|
+
readonly [InvalidInterpretationErrorTypeId]: InvalidInterpretationErrorTypeId = InvalidInterpretationErrorTypeId;
|
|
6
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { TemplateLiteral, TemplateLiteralSpan, Validation } from "@fncts/schema/AST";
|
|
2
|
+
|
|
3
|
+
import { showWithOptions } from "@fncts/base/data/Showable";
|
|
4
|
+
import { concrete } from "@fncts/schema/AST";
|
|
5
|
+
import { ASTTag } from "@fncts/schema/AST";
|
|
6
|
+
import { ASTAnnotation } from "@fncts/schema/ASTAnnotation";
|
|
7
|
+
|
|
8
|
+
export const enum ParseErrorTag {
|
|
9
|
+
Type,
|
|
10
|
+
Index,
|
|
11
|
+
Key,
|
|
12
|
+
Missing,
|
|
13
|
+
Unexpected,
|
|
14
|
+
UnionMember,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @tsplus type fncts.schema.ParseError
|
|
19
|
+
* @tsplus companion fncts.schema.ParseErrorOps
|
|
20
|
+
*/
|
|
21
|
+
export type ParseError =
|
|
22
|
+
| TypeError
|
|
23
|
+
| IndexError
|
|
24
|
+
| KeyError
|
|
25
|
+
| MissingError
|
|
26
|
+
| UnexpectedError
|
|
27
|
+
| UnexpectedError
|
|
28
|
+
| UnionMemberError;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @tsplus companion fncts.schema.ParseError.TypeError
|
|
32
|
+
*/
|
|
33
|
+
export class TypeError {
|
|
34
|
+
readonly _tag = ParseErrorTag.Type;
|
|
35
|
+
constructor(readonly expected: AST, readonly actual: unknown) {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @tsplus static fncts.schema.ParseError.TypeError __call
|
|
40
|
+
* @tsplus static fncts.schema.ParseErrorOps TypeError
|
|
41
|
+
*/
|
|
42
|
+
export function typeError(expected: AST, actual: unknown): ParseError {
|
|
43
|
+
return new TypeError(expected, actual);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @tsplus companion fncts.schema.ParseError.IndexError
|
|
48
|
+
*/
|
|
49
|
+
export class IndexError {
|
|
50
|
+
readonly _tag = ParseErrorTag.Index;
|
|
51
|
+
constructor(readonly index: number, readonly errors: Vector<ParseError>) {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @tsplus static fncts.schema.ParseError.IndexError __call
|
|
56
|
+
* @tsplus static fncts.schema.ParseErrorOps IndexError
|
|
57
|
+
*/
|
|
58
|
+
export function indexError(index: number, errors: Vector<ParseError>): ParseError {
|
|
59
|
+
return new IndexError(index, errors);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @tsplus companion fncts.schema.ParseError.KeyError
|
|
64
|
+
*/
|
|
65
|
+
export class KeyError {
|
|
66
|
+
readonly _tag = ParseErrorTag.Key;
|
|
67
|
+
constructor(readonly keyAST: AST, readonly key: any, readonly errors: Vector<ParseError>) {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @tsplus static fncts.schema.ParseError.IndexError __call
|
|
72
|
+
* @tsplus static fncts.schema.ParseErrorOps KeyError
|
|
73
|
+
*/
|
|
74
|
+
export function keyError(keyAST: AST, key: any, errors: Vector<ParseError>): ParseError {
|
|
75
|
+
return new KeyError(keyAST, key, errors);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @tsplus companion fncts.schema.ParseError.MissingError
|
|
80
|
+
*/
|
|
81
|
+
export class MissingError {
|
|
82
|
+
readonly _tag = ParseErrorTag.Missing;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @tsplus static fncts.schema.ParseErrorOps MissingError
|
|
87
|
+
*/
|
|
88
|
+
export const missingError = new MissingError();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @tsplus companion fncts.schema.ParseError.UnexpectedError
|
|
92
|
+
*/
|
|
93
|
+
export class UnexpectedError {
|
|
94
|
+
readonly _tag = ParseErrorTag.Unexpected;
|
|
95
|
+
constructor(readonly actual: unknown) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @tsplus static fncts.schema.ParseError.UnexpectedError __call
|
|
100
|
+
* @tsplus static fncts.schema.ParseErrorOps UnexpectedError
|
|
101
|
+
*/
|
|
102
|
+
export function unexpectedError(actual: unknown): ParseError {
|
|
103
|
+
return new UnexpectedError(actual);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @tsplus companion fncts.schema.ParseError.UnionMemberError
|
|
108
|
+
*/
|
|
109
|
+
export class UnionMemberError {
|
|
110
|
+
readonly _tag = ParseErrorTag.UnionMember;
|
|
111
|
+
constructor(readonly errors: Vector<ParseError>) {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @tsplus static fncts.schema.ParseError.UnionMemberError __call
|
|
116
|
+
* @tsplus static fncts.schema.ParseErrorOps UnionMemberError
|
|
117
|
+
*/
|
|
118
|
+
export function unionMemberError(errors: Vector<ParseError>): ParseError {
|
|
119
|
+
return new UnionMemberError(errors);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @tsplus static fncts.schema.ParseErrorOps format
|
|
124
|
+
*/
|
|
125
|
+
export function format(errors: Vector<ParseError>): string {
|
|
126
|
+
return RoseTree(`${errors.length} error(s) found`, errors.map(go)).draw;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function formatActual(actual: unknown): string {
|
|
130
|
+
return showWithOptions(actual, { colors: false });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatTemplateLiteralSpan(span: TemplateLiteralSpan): string {
|
|
134
|
+
switch (span.type._tag) {
|
|
135
|
+
case ASTTag.StringKeyword:
|
|
136
|
+
return "${string}";
|
|
137
|
+
case ASTTag.NumberKeyword:
|
|
138
|
+
return "${number}";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatTemplateLiteral(ast: TemplateLiteral): string {
|
|
143
|
+
return ast.head + ast.spans.map((span) => formatTemplateLiteralSpan(span) + span.literal).join("");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getExpected(ast: AST): Maybe<string> {
|
|
147
|
+
return ast.annotations
|
|
148
|
+
.get(ASTAnnotation.Identifier)
|
|
149
|
+
.orElse(ast.annotations.get(ASTAnnotation.Title))
|
|
150
|
+
.flatMap((title) =>
|
|
151
|
+
ast.annotations.get(ASTAnnotation.Description).match(
|
|
152
|
+
() => Just(title),
|
|
153
|
+
(description) => Just(`${title} (${description})`),
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getMissedBrands(ast: Validation): string {
|
|
159
|
+
return ast.validation.map((validation) => validation.name).join(" & ");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function formatExpected(ast: AST): string {
|
|
163
|
+
concrete(ast);
|
|
164
|
+
switch (ast._tag) {
|
|
165
|
+
case ASTTag.StringKeyword:
|
|
166
|
+
return getExpected(ast).getOrElse("string");
|
|
167
|
+
case ASTTag.NumberKeyword:
|
|
168
|
+
return getExpected(ast).getOrElse("number");
|
|
169
|
+
case ASTTag.BooleanKeyword:
|
|
170
|
+
return getExpected(ast).getOrElse("boolean");
|
|
171
|
+
case ASTTag.BigIntKeyword:
|
|
172
|
+
return getExpected(ast).getOrElse("bigint");
|
|
173
|
+
case ASTTag.UndefinedKeyword:
|
|
174
|
+
return getExpected(ast).getOrElse("undefined");
|
|
175
|
+
case ASTTag.SymbolKeyword:
|
|
176
|
+
return getExpected(ast).getOrElse("symbol");
|
|
177
|
+
case ASTTag.ObjectKeyword:
|
|
178
|
+
return getExpected(ast).getOrElse("object");
|
|
179
|
+
case ASTTag.AnyKeyword:
|
|
180
|
+
return getExpected(ast).getOrElse("any");
|
|
181
|
+
case ASTTag.UnknownKeyword:
|
|
182
|
+
return getExpected(ast).getOrElse("unknown");
|
|
183
|
+
case ASTTag.VoidKeyword:
|
|
184
|
+
return getExpected(ast).getOrElse("void");
|
|
185
|
+
case ASTTag.NeverKeyword:
|
|
186
|
+
return getExpected(ast).getOrElse("never");
|
|
187
|
+
case ASTTag.Literal:
|
|
188
|
+
return getExpected(ast).getOrElse(formatActual(ast.literal));
|
|
189
|
+
case ASTTag.UniqueSymbol:
|
|
190
|
+
return getExpected(ast).getOrElse(formatActual(ast.symbol));
|
|
191
|
+
case ASTTag.Union:
|
|
192
|
+
return ast.types.map(formatExpected).join(" or ");
|
|
193
|
+
case ASTTag.Refinement:
|
|
194
|
+
return getExpected(ast).getOrElse("refinement");
|
|
195
|
+
case ASTTag.TemplateLiteral:
|
|
196
|
+
return getExpected(ast).getOrElse(formatTemplateLiteral(ast));
|
|
197
|
+
case ASTTag.Tuple:
|
|
198
|
+
return getExpected(ast).getOrElse("tuple or array");
|
|
199
|
+
case ASTTag.TypeLiteral:
|
|
200
|
+
return getExpected(ast).getOrElse("type literal");
|
|
201
|
+
case ASTTag.Enum:
|
|
202
|
+
return getExpected(ast).getOrElse(ast.enums.map(([_, value]) => JSON.stringify(value)).join(" | "));
|
|
203
|
+
case ASTTag.Lazy:
|
|
204
|
+
return getExpected(ast).getOrElse("<anonymous lazy schema>");
|
|
205
|
+
case ASTTag.Declaration:
|
|
206
|
+
return getExpected(ast).getOrElse("<anonymous Declaration schema>");
|
|
207
|
+
case ASTTag.Transform:
|
|
208
|
+
return `a parsable value from ${formatExpected(ast.from)} to ${formatExpected(ast.to)}`;
|
|
209
|
+
case ASTTag.Validation:
|
|
210
|
+
return getExpected(ast).match(
|
|
211
|
+
() => getMissedBrands(ast),
|
|
212
|
+
(expected) => `${expected} with validation(s) ${getMissedBrands(ast)}`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function go(error: ParseError): RoseTree<string> {
|
|
218
|
+
switch (error._tag) {
|
|
219
|
+
case ParseErrorTag.Type:
|
|
220
|
+
return RoseTree(
|
|
221
|
+
error.expected.annotations
|
|
222
|
+
.get(ASTAnnotation.Message)
|
|
223
|
+
.map((f) => f(error.actual))
|
|
224
|
+
.getOrElse(`Expected ${formatExpected(error.expected)}, actual ${formatActual(error.actual)}`),
|
|
225
|
+
);
|
|
226
|
+
case ParseErrorTag.Index:
|
|
227
|
+
return RoseTree(`index ${error.index}`, error.errors.map(go));
|
|
228
|
+
case ParseErrorTag.Unexpected:
|
|
229
|
+
return RoseTree("is unexpected");
|
|
230
|
+
case ParseErrorTag.Key:
|
|
231
|
+
return RoseTree(`key ${formatActual(error.key)}`, error.errors.map(go));
|
|
232
|
+
case ParseErrorTag.Missing:
|
|
233
|
+
return RoseTree("is missing");
|
|
234
|
+
case ParseErrorTag.UnionMember:
|
|
235
|
+
return RoseTree("union member", error.errors.map(go));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tsplus type fncts.schema.ParseResult
|
|
3
|
+
* @tsplus companion fncts.schema.ParseResultOps
|
|
4
|
+
*/
|
|
5
|
+
export interface ParseResult<A> extends Either<Vector<ParseError>, A> {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @tsplus static fncts.schema.ParseResultOps succeed
|
|
9
|
+
*/
|
|
10
|
+
export function succeed<A>(value: A): ParseResult<A> {
|
|
11
|
+
return Either.right(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @tsplus static fncts.schema.ParseResultOps failures
|
|
16
|
+
*/
|
|
17
|
+
export function failures<A = never>(value: Vector<ParseError>): ParseResult<A> {
|
|
18
|
+
return Either.left(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @tsplus static fncts.schema.ParseResultOps fail
|
|
23
|
+
*/
|
|
24
|
+
export function fail<A = never>(value: ParseError): ParseResult<A> {
|
|
25
|
+
return Either.left(Vector(value));
|
|
26
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ParseOptions } from "../AST.js";
|
|
2
|
+
|
|
3
|
+
import { parserFor } from "./interpreter.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @tsplus getter fncts.schema.Schema decode
|
|
7
|
+
* @tsplus getter fncts.schema.Parser decode
|
|
8
|
+
*/
|
|
9
|
+
export function decode<A>(schema: Schema<A>): Parser<A> {
|
|
10
|
+
return parserFor(schema.ast);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @tsplus getter fncts.schema.Schema decodeMaybe
|
|
15
|
+
* @tsplus getter fncts.schema.Parser decodeMaybe
|
|
16
|
+
*/
|
|
17
|
+
export function decodeMaybe<A>(schema: Schema<A>): <A>(input: A, options?: ParseOptions) => Maybe<A> {
|
|
18
|
+
return parseMaybe(schema.ast.getTo);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @tsplus getter fncts.schema.Schema encode
|
|
23
|
+
* @tsplus getter fncts.schema.Parser encode
|
|
24
|
+
*/
|
|
25
|
+
export function encode<A>(schema: Schema<A>): <A>(input: A, options?: ParseOptions) => ParseResult<unknown> {
|
|
26
|
+
return parserFor(schema.ast.reverse);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @tsplus getter fncts.schema.Schema encodeMaybe
|
|
31
|
+
* @tsplus getter fncts.schema.Parser encodeMaybe
|
|
32
|
+
*/
|
|
33
|
+
export function encodeMaybe<A>(schema: Schema<A>): <A>(input: A, options?: ParseOptions) => Maybe<unknown> {
|
|
34
|
+
return parseMaybe(schema.ast.reverse);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @tsplus getter fncts.schema.Schema is
|
|
39
|
+
* @tsplus getter fncts.schema.Parser is
|
|
40
|
+
*/
|
|
41
|
+
export function is<A>(schema: Schema<A>) {
|
|
42
|
+
return (input: unknown, options?: ParseOptions): input is A => {
|
|
43
|
+
return parserFor(schema.ast)(input, options).isRight();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseMaybe(ast: AST) {
|
|
48
|
+
const parse = parserFor(ast);
|
|
49
|
+
return (input: unknown, options?: ParseOptions): Maybe<any> => {
|
|
50
|
+
return parse(input, options).toMaybe;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseOrThrow(ast: AST) {
|
|
55
|
+
const parser = parserFor(ast);
|
|
56
|
+
return (input: unknown, options?: ParseOptions) => {
|
|
57
|
+
return parser(input, options).match((errors) => {
|
|
58
|
+
throw new Error(ParseError.format(errors));
|
|
59
|
+
}, Function.identity);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @tsplus getter fncts.schema.Schema asserts
|
|
65
|
+
* @tsplus getter fncts.schema.Parser asserts
|
|
66
|
+
*/
|
|
67
|
+
export function asserts<A>(schema: Schema<A>) {
|
|
68
|
+
return (input: unknown, options?: ParseOptions): asserts input is A => {
|
|
69
|
+
parseOrThrow(schema.ast.getTo)(input, options);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ParseOptions } from "../AST.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @tsplus type fncts.schema.Parser
|
|
5
|
+
* @tsplus companion fncts.schema.ParserOps
|
|
6
|
+
* @tsplus no-inherit fncts.schema.Schema fncts.schema.SchemaOps
|
|
7
|
+
*/
|
|
8
|
+
export interface Parser<in out A> {
|
|
9
|
+
(input: unknown, options?: ParseOptions): ParseResult<A>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus static fncts.schema.ParserOps make
|
|
14
|
+
*/
|
|
15
|
+
export function make<A>(parse: Parser<A>): Parser<A> {
|
|
16
|
+
return parse;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @tsplus static fncts.schema.ParserOps fromRefinement
|
|
21
|
+
*/
|
|
22
|
+
export function fromRefinement<A>(ast: AST, refinement: Refinement<unknown, A>): Parser<A> {
|
|
23
|
+
return (u) => (refinement(u) ? ParseResult.succeed(u) : ParseResult.fail(ParseError.TypeError(ast, u)));
|
|
24
|
+
}
|