import type { CodePoint, CodePointSpan, CodePointString, SourceCursor } from 'source-region'; export type TextMatch = | { tag: "match"; span: CodePointSpan; text: string } | { tag: "none" }; export namespace TextMatch { export function match(span: CodePointSpan, text: string): TextMatch { return { tag: "match", span, text }; } export function none(): TextMatch { return { tag: "none" }; } } export function consumeWhile( cursor: SourceCursor, predicate: (cp: CodePoint) => boolean, ): CodePointSpan { const start = cursor.checkpoint(); while (true) { const cp = cursor.peek(); if (cp === undefined || !predicate(cp)) break; cursor.advance(); } return cursor.spanFrom(start); } export function consumeWhile1( cursor: SourceCursor, predicate: (cp: CodePoint) => boolean, ): TextMatch { const start = cursor.checkpoint(); const span = consumeWhile(cursor, predicate); if (span.start === span.end) { cursor.restore(start); return TextMatch.none(); } return TextMatch.match(span, cursor.slice(span)); } export function skipWhile( cursor: SourceCursor, predicate: (cp: CodePoint) => boolean, ): CodePointSpan { return consumeWhile(cursor, predicate); } export function matchCodePointString( cursor: SourceCursor, pattern: CodePointString, ): TextMatch { const start = cursor.checkpoint(); for (const expected of pattern.codePoints) { if (cursor.peek() !== expected) { cursor.restore(start); return TextMatch.none(); } cursor.advance(); } const span = cursor.spanFrom(start); return TextMatch.match(span, cursor.slice(span)); }