Add cursor abstraction

This commit is contained in:
Yura Dupyn 2026-04-25 01:44:09 +02:00
parent f72575ae54
commit 85bc9b05e1
2 changed files with 83 additions and 0 deletions

View file

@ -28,6 +28,7 @@ export const UPPERCASE_F: CodePoint = char('F');
export const LOWERCASE_z: CodePoint = char('z');
export const UPPERCASE_Z: CodePoint = char('Z');
// === Predicates ===
export function isBetween(a: CodePoint, x: CodePoint, b: CodePoint): boolean {
return a <= x && x <= b;
@ -46,6 +47,17 @@ export function isAsciiAlphanumeric(x: CodePoint): boolean {
return isAsciiAlpha(x) || isDigit(x);
}
export function isAsciiWhitespace(cp: CodePoint): boolean {
return cp === SPACE
|| cp === TAB
|| cp === NEW_LINE
|| cp === CARRIAGE_RETURN;
}
export function isAsciiInlineWhitespace(cp: CodePoint): boolean {
return cp === SPACE || cp === TAB;
}
export type CodePointRef = {
char: CodePoint,
offset: StringIndex,
@ -366,6 +378,75 @@ export type SourceLocation = {
column: number; // 1-based
}
export class SourceCursor {
private index: CodePointIndex;
constructor(public readonly region: SourceRegion) {
this.index = region.span.start.index;
}
current(): CodePointIndex {
return this.index;
}
checkpoint(): CodePointIndex {
return this.index;
}
restore(index: CodePointIndex) {
this.index = index;
}
peek(): CodePoint | undefined {
if (this.index >= this.region.span.end.index) return undefined;
return this.region.codePointAt(this.index);
}
advance(): CodePoint | undefined {
const cp = this.peek();
if (cp === undefined) return undefined;
this.index += 1;
return cp;
}
isAtEnd(): boolean {
return this.index >= this.region.span.end.index;
}
spanFrom(start: CodePointIndex): CodePointSpan {
return rawSpan(start, this.index);
}
currentSpan(): CodePointSpan {
return this.isAtEnd()
? pointSpan(this.index)
: rawSpan(this.index, this.index + 1);
}
eofSpan(): CodePointSpan {
return pointSpan(this.region.span.end.index);
}
slice(span: CodePointSpan): string {
return this.region.slice(span);
}
moveToNextLineStart(): void {
const loc = this.region.source.getLocation(this.index);
const nextLine = loc.line + 1;
if (nextLine > this.region.span.end.line) {
this.index = this.region.span.end.index;
return;
}
const range = this.region.source.getLineRange(nextLine);
this.index = Math.min(range.start, this.region.span.end.index);
}
}
// === Rendering Utilities ===
export type LineView = {