forked from arktypeio/arktype
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquantify.ts
More file actions
154 lines (139 loc) · 4.35 KB
/
Copy pathquantify.ts
File metadata and controls
154 lines (139 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import type { parseNaturalNumber, Scanner } from "@ark/util"
import type { s, State } from "./state.ts"
export type parseBuiltinQuantifier<
s extends State,
quantifier extends QuantifyingChar,
unscanned extends string
> =
s["root"] extends "" ? s.error<writeUnmatchedQuantifierError<quantifier>>
: quantifyBuiltin<
s,
quantifier,
unscanned extends Scanner.shift<"?", infer lazyUnscanned> ? lazyUnscanned
: unscanned
>
type quantifyBuiltin<
s extends State,
quantifier extends QuantifyingChar,
unscanned extends string
> =
quantifier extends "?" ? s.pushQuantifier<s, 0, 1, unscanned>
: quantifier extends "+" ? s.pushQuantifier<s, 1, null, unscanned>
: quantifier extends "*" ? s.pushQuantifier<s, 0, null, unscanned>
: never
type ParsedRange = {
min: number
max: number | null
unscanned: string
}
declare namespace ParsedRange {
export type from<r extends ParsedRange> =
r["min"] extends never ? null
: r["max"] extends never ? null
: r
}
type skipPossibleQuestionMark<unscanned extends string> =
unscanned extends `?${infer next}` ? next : unscanned
type parsePossibleRangeString<unscanned extends string> =
unscanned extends (
`${infer l extends `${number}`},${infer r extends `${number}`}}${infer next}`
) ?
ParsedRange.from<{
min: parseNaturalNumber<l>
max: parseNaturalNumber<r>
unscanned: skipPossibleQuestionMark<next>
}>
: unscanned extends `${infer l extends `${number}`},}${infer next}` ?
ParsedRange.from<{
min: parseNaturalNumber<l>
max: null
unscanned: skipPossibleQuestionMark<next>
}>
: unscanned extends `${infer l extends `${number}`}}${infer next}` ?
ParsedRange.from<{
min: parseNaturalNumber<l>
max: parseNaturalNumber<l>
unscanned: skipPossibleQuestionMark<next>
}>
: null
export type parsePossibleRange<
s extends State,
unscanned extends string,
parsed extends ParsedRange | null = parsePossibleRangeString<unscanned>
> =
parsed extends ParsedRange ?
s["root"] extends "" ?
s.error<
writeUnmatchedQuantifierError<
unscanned extends `${infer range}${parsed["unscanned"]}` ? `{${range}`
: never
>
>
: s.pushQuantifier<
s,
parsed["min"],
parsed["max"],
parsed["unscanned"] extends Scanner.shift<"?", infer lazyUnscanned> ?
lazyUnscanned
: parsed["unscanned"]
>
: s.shiftQuantifiable<s, "{", unscanned>
export type quantify<
pattern extends string,
min extends number,
max extends number | null
> = tryFastPath<pattern, min, max>
type tryFastPath<
pattern extends string,
min extends number,
max extends number | null
> =
max extends 0 ? ""
: // repeating string or `${bigint}` any number of times will not change the type
string extends pattern ? string
: `${bigint}` extends pattern ? `${bigint}`
: min extends 0 ?
max extends 1 ? "" | pattern
: max extends number ? loopFromZero<pattern, max, "", []>
: // max is null, all we can do is append ${string}
"" | `${pattern}${string}`
: loopUntilMin<pattern, min, max, "", []>
type loopFromZero<
base extends string,
max extends number,
acc extends string,
repetitions extends 1[]
> =
repetitions["length"] extends max ? acc
: loopFromZero<base, max, acc | `${acc}${base}`, [...repetitions, 1]>
type loopUntilMin<
base extends string,
min extends number,
max extends number | null,
acc extends string,
repetitions extends 1[]
> =
repetitions["length"] extends min ?
max extends number ? loopUntilMax<base, min, max, acc, repetitions>
: // don't need appendNonRedundant for these cases because if the pattern is
// something collapsible like `string` or `bigint, the fast path has
// already been taken
repetitions["length"] extends 0 ? acc | `${acc}${base}${string}`
: `${acc}${string}`
: loopUntilMin<base, min, max, `${acc}${base}`, [...repetitions, 1]>
type loopUntilMax<
base extends string,
min extends number,
max extends number,
acc extends string,
repetitions extends 1[]
> =
repetitions["length"] extends max ? acc
: loopUntilMax<base, min, max, acc | `${acc}${base}`, [...repetitions, 1]>
export type QuantifyingChar = "*" | "+" | "?"
export const writeUnmatchedQuantifierError = <quantifier extends string>(
quantifier: quantifier
): writeUnmatchedQuantifierError<quantifier> =>
`Quantifier ${quantifier} requires a preceding token`
export type writeUnmatchedQuantifierError<quantifier extends string> =
`Quantifier ${quantifier} requires a preceding token`