70 lines
1.6 KiB
TypeScript
70 lines
1.6 KiB
TypeScript
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));
|
|
}
|