@@ -31,37 +31,40 @@ const orSchema = <P>(p: z.ZodSchema<P>) =>
31
31
export const formulaSchema = < P > ( p : z . ZodSchema < P > ) : z . ZodSchema < F < P > > =>
32
32
z . union ( [ atomSchema ( p ) , andSchema ( p ) , orSchema ( p ) ] )
33
33
34
- export interface Atom < P > {
34
+ export interface Atom < P , X = never > {
35
35
kind : 'atom'
36
36
property : P
37
- value : boolean
37
+ value : boolean | X
38
38
}
39
39
40
- export interface And < P > {
40
+ export interface And < P , X = never > {
41
41
kind : 'and'
42
- subs : Formula < P > [ ]
42
+ subs : Formula < P , X > [ ]
43
43
}
44
44
45
- export interface Or < P > {
45
+ export interface Or < P , X = never > {
46
46
kind : 'or'
47
- subs : Formula < P > [ ]
47
+ subs : Formula < P , X > [ ]
48
48
}
49
49
50
- export type Formula < P > = And < P > | Or < P > | Atom < P >
50
+ export type Formula < P , X = never > = And < P , X > | Or < P , X > | Atom < P , X >
51
51
52
- export function and < P > ( ...subs : Formula < P > [ ] ) : And < P > {
52
+ export function and < P , X = never > ( ...subs : Formula < P , X > [ ] ) : And < P , X > {
53
53
return { kind : 'and' , subs : subs }
54
54
}
55
55
56
- export function or < P > ( ...subs : Formula < P > [ ] ) : Or < P > {
56
+ export function or < P , X = never > ( ...subs : Formula < P , X > [ ] ) : Or < P , X > {
57
57
return { kind : 'or' , subs : subs }
58
58
}
59
59
60
- export function atom < P > ( p : P , v = true ) : Atom < P > {
61
- return { kind : 'atom' , property : p , value : v }
60
+ export function atom < P , X = never > (
61
+ property : P ,
62
+ value : boolean | X = true ,
63
+ ) : Atom < P , X > {
64
+ return { kind : 'atom' , property, value }
62
65
}
63
66
64
- export function properties < P > ( f : Formula < P > ) : Set < P > {
67
+ export function properties < P , X > ( f : Formula < P , X > ) : Set < P > {
65
68
switch ( f . kind ) {
66
69
case 'atom' :
67
70
return new Set ( [ f . property ] )
@@ -94,10 +97,10 @@ export function negate<P>(formula: Formula<P>): Formula<P> {
94
97
}
95
98
}
96
99
97
- export function map < P , Q > (
98
- func : ( p : Atom < P > ) => Atom < Q > ,
99
- formula : Formula < P > ,
100
- ) : Formula < Q > {
100
+ export function map < P , Q , X = never > (
101
+ func : ( p : Atom < P , X > ) => Atom < Q , X > ,
102
+ formula : Formula < P , X > ,
103
+ ) : Formula < Q , X > {
101
104
switch ( formula . kind ) {
102
105
case 'atom' :
103
106
return func ( formula )
@@ -109,32 +112,38 @@ export function map<P, Q>(
109
112
}
110
113
}
111
114
112
- export function mapProperty < P , Q > (
115
+ export function mapProperty < P , Q , X = never > (
113
116
func : ( p : P ) => Q ,
114
- formula : Formula < P > ,
115
- ) : Formula < Q > {
116
- function mapAtom ( a : Atom < P > ) : Atom < Q > {
117
+ formula : Formula < P , X > ,
118
+ ) : Formula < Q , X > {
119
+ function mapAtom ( a : Atom < P , X > ) : Atom < Q , X > {
117
120
return { ...a , property : func ( a . property ) }
118
121
}
119
- return map < P , Q > ( mapAtom , formula )
122
+ return map < P , Q , X > ( mapAtom , formula )
120
123
}
121
124
122
- export function compact < P > ( f : Formula < P | undefined > ) : Formula < P > | undefined {
123
- return properties ( f ) . has ( undefined ) ? undefined : ( f as Formula < P > )
125
+ export function compact < P , X > (
126
+ f : Formula < P | undefined , X > ,
127
+ ) : Formula < P , X > | undefined {
128
+ return properties ( f ) . has ( undefined ) ? undefined : ( f as Formula < P , X > )
124
129
}
125
130
126
- export function evaluate < T > (
127
- f : Formula < T > ,
131
+ export function evaluate < T , V extends boolean | null = boolean > (
132
+ f : Formula < T , V > ,
128
133
traits : Map < T , boolean > ,
129
134
) : boolean | undefined {
130
135
let result : boolean | undefined
131
136
132
137
switch ( f . kind ) {
133
138
case 'atom' :
134
- if ( traits . has ( f . property ) ) {
135
- return traits . get ( f . property ) === f . value
139
+ const known = traits . has ( f . property )
140
+ if ( f . value === null ) {
141
+ return ! known
136
142
}
137
- return undefined
143
+ if ( ! known ) {
144
+ return undefined
145
+ }
146
+ return traits . get ( f . property ) === f . value
138
147
case 'and' :
139
148
result = true // by default
140
149
f . subs . forEach ( sub => {
@@ -170,7 +179,7 @@ export function evaluate<T>(
170
179
}
171
180
}
172
181
173
- export function parse ( q ?: string ) : Formula < string > | undefined {
182
+ export function parse ( q ?: string ) : Formula < string , null > | undefined {
174
183
if ( ! q ) {
175
184
return
176
185
}
@@ -190,19 +199,19 @@ export function parse(q?: string): Formula<string> | undefined {
190
199
return fromJSON ( parsed as any )
191
200
}
192
201
193
- type Serialized =
202
+ type Serialized < X = never > =
194
203
| { and : Serialized [ ] }
195
204
| { or : Serialized [ ] }
196
- | { property : string ; value : boolean }
197
- | Record < string , boolean >
205
+ | { property : string ; value : boolean | X }
206
+ | Record < string , boolean | X >
198
207
199
- export function fromJSON ( json : Serialized ) : Formula < string > {
208
+ export function fromJSON ( json : Serialized ) : Formula < string , null > {
200
209
if ( 'and' in json && typeof json . and === 'object' ) {
201
- return and < string > ( ...json . and . map ( fromJSON ) )
210
+ return and < string , null > ( ...json . and . map ( fromJSON ) )
202
211
} else if ( 'or' in json && typeof json . or === 'object' ) {
203
- return or < string > ( ...json . or . map ( fromJSON ) )
212
+ return or < string , null > ( ...json . or . map ( fromJSON ) )
204
213
} else if ( 'property' in json && typeof json . property === 'string' ) {
205
- return atom < string > ( json . property , json . value )
214
+ return atom < string , null > ( json . property , json . value )
206
215
}
207
216
208
217
const entries = Object . entries ( json )
@@ -214,7 +223,7 @@ export function fromJSON(json: Serialized): Formula<string> {
214
223
throw `cannot cast object with non-boolean value`
215
224
}
216
225
217
- return atom < string > ( ...entries [ 0 ] )
226
+ return atom < string , null > ( ...entries [ 0 ] )
218
227
}
219
228
220
229
export function toJSON ( f : Formula < string > ) : Serialized {
0 commit comments