Skip to content
Merged
20 changes: 20 additions & 0 deletions ark/regex/__tests__/regex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ contextualize(() => {
attest<Regex<"" | "a", {}>>(S)
})

it("leading zeroes", () => {
const S = regex("^a{002}$")
attest<Regex<"aa">>(S)
})

it("falls back to literal for missing min", () => {
const r = regex("^a{,2}$")
attest<Regex<"a{,2}", {}>>(r).type.toString.snap('Regex<"a{,2}", {}>')
Expand All @@ -313,6 +318,21 @@ contextualize(() => {
)
})

it("falls back to literal for negative number", () => {
const S = regex("^a{-1}$")
attest<Regex<"a{-1}">>(S)
})

it("falls back to literal for decimal number", () => {
const S = regex("^a{1.5}$")
attest<Regex<`a{1${string}5}`>>(S)
})

it("falls back to literal for spaced number", () => {
const S = regex("^a{ 1}$")
attest<Regex<"a{ 1}">>(S)
})

it("${string} does not duplicate when quantified", () => {
const r = regex("^.{5,10}$")
attest<Regex<string, {}>>(r).type.toString.snap("Regex<string, {}>")
Expand Down
23 changes: 13 additions & 10 deletions ark/regex/quantify.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Scanner } from "@ark/util"
import type { parseNaturalNumber, Scanner } from "@ark/util"
import type { s, State } from "./state.ts"

export type parseBuiltinQuantifier<
Expand Down Expand Up @@ -31,31 +31,34 @@ type ParsedRange = {
}

declare namespace ParsedRange {
export type from<r extends ParsedRange> = r
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}`
`${infer l extends `${number}`},${infer r extends `${number}`}}${infer next}`
) ?
ParsedRange.from<{
min: l
max: r
min: parseNaturalNumber<l>
max: parseNaturalNumber<r>
unscanned: skipPossibleQuestionMark<next>
}>
: unscanned extends `${infer l extends number},}${infer next}` ?
: unscanned extends `${infer l extends `${number}`},}${infer next}` ?
ParsedRange.from<{
min: l
min: parseNaturalNumber<l>
max: null
unscanned: skipPossibleQuestionMark<next>
}>
: unscanned extends `${infer l extends number}}${infer next}` ?
: unscanned extends `${infer l extends `${number}`}}${infer next}` ?
ParsedRange.from<{
min: l
max: l
min: parseNaturalNumber<l>
max: parseNaturalNumber<l>
unscanned: skipPossibleQuestionMark<next>
}>
: null
Expand Down
8 changes: 8 additions & 0 deletions ark/util/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ export type parseInteger<token extends string> =
export type parseNonNegativeInteger<token extends string> =
token extends `-${string}` ? never : parseInteger<token>

export type parseNaturalNumber<token extends string> =
token extends `0${infer t extends `${number}`}` ? parseNaturalNumber<t>
: token extends `${infer n extends number}` ?
number extends n ?
never
: parseNonNegativeInteger<token>
: never

export type NumericParseOptions<errorOnFail extends boolean | string> = {
errorOnFail?: errorOnFail
strict?: boolean
Expand Down