Introduce recognizers

This commit is contained in:
Yura Dupyn 2026-04-25 11:55:46 +02:00
parent a56020cd9f
commit bb9ca93f4e
4 changed files with 91 additions and 16 deletions

70
src/recognizers.ts Normal file
View file

@ -0,0 +1,70 @@
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));
}