diff --git a/ark/regex/__tests__/regex.test.ts b/ark/regex/__tests__/regex.test.ts index 34597d0eea..894f6a6705 100644 --- a/ark/regex/__tests__/regex.test.ts +++ b/ark/regex/__tests__/regex.test.ts @@ -23,7 +23,10 @@ import { writeInvalidModifierMessage } from "arkregex/internal/group.ts" import type { next } from "arkregex/internal/parse.ts" -import { writeUnmatchedQuantifierError } from "arkregex/internal/quantify.ts" +import { + writeUnmatchedQuantifierError, + writeUnnaturalNumberQuantifierError +} from "arkregex/internal/quantify.ts" import { writeIncompleteReferenceError, writeMidAnchorError, @@ -362,6 +365,34 @@ contextualize(() => { writeUnmatchedQuantifierError("{2,3}?") ) }) + + it("leading zeroes", () => { + // @ts-expect-error + attest(() => regex("^a{002}$")).type.errors( + writeUnnaturalNumberQuantifierError("{002}") + ) + }) + + it("negative number", () => { + // @ts-expect-error + attest(() => regex("^a{-1}$")).type.errors( + writeUnnaturalNumberQuantifierError("{-1}") + ) + }) + + it("decimal number", () => { + // @ts-expect-error + attest(() => regex("^a{1.5}$")).type.errors( + writeUnnaturalNumberQuantifierError("{1.5}") + ) + }) + + it("spaced number", () => { + // @ts-expect-error + attest(() => regex("^a{ 1}$")).type.errors( + writeUnnaturalNumberQuantifierError("{ 1}") + ) + }) }) describe("character sets", () => { diff --git a/ark/regex/quantify.ts b/ark/regex/quantify.ts index 329c6506bb..7c4a0f98b6 100644 --- a/ark/regex/quantify.ts +++ b/ark/regex/quantify.ts @@ -1,4 +1,4 @@ -import type { Scanner } from "@ark/util" +import type { parseNonNegativeInteger, Scanner } from "@ark/util" import type { s, State } from "./state.ts" export type parseBuiltinQuantifier< @@ -39,27 +39,29 @@ type skipPossibleQuestionMark = type parsePossibleRangeString = 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: parseNonNegativeInteger + max: parseNonNegativeInteger unscanned: skipPossibleQuestionMark }> - : unscanned extends `${infer l extends number},}${infer next}` ? + : unscanned extends `${infer l extends `${number}`},}${infer next}` ? ParsedRange.from<{ - min: l + min: parseNonNegativeInteger max: null unscanned: skipPossibleQuestionMark }> - : unscanned extends `${infer l extends number}}${infer next}` ? + : unscanned extends `${infer l extends `${number}`}}${infer next}` ? ParsedRange.from<{ - min: l - max: l + min: parseNonNegativeInteger + max: parseNonNegativeInteger unscanned: skipPossibleQuestionMark }> : null +type parseQuantifier = + unscanned extends `${infer range}${parsed["unscanned"]}` ? `{${range}` : never export type parsePossibleRange< s extends State, unscanned extends string, @@ -67,11 +69,12 @@ export type parsePossibleRange< > = parsed extends ParsedRange ? s["root"] extends "" ? + s.error>> + : [parsed["min"], parsed["max"]] extends ( + [never, unknown] | [unknown, never] + ) ? s.error< - writeUnmatchedQuantifierError< - unscanned extends `${infer range}${parsed["unscanned"]}` ? `{${range}` - : never - > + writeUnnaturalNumberQuantifierError> > : s.pushQuantifier< s, @@ -149,3 +152,11 @@ export const writeUnmatchedQuantifierError = ( export type writeUnmatchedQuantifierError = `Quantifier ${quantifier} requires a preceding token` + +export const writeUnnaturalNumberQuantifierError = ( + quantifier: quantifier +): writeUnnaturalNumberQuantifierError => + `Quantifier ${quantifier} must use natural numbers` + +export type writeUnnaturalNumberQuantifierError = + `Quantifier ${quantifier} must use natural numbers`