Add cursor abstraction
This commit is contained in:
parent
f72575ae54
commit
85bc9b05e1
2 changed files with 83 additions and 0 deletions
|
|
@ -19,6 +19,8 @@ It also allows for Spatial Tracking or various sub-regions within the source. It
|
|||
- `SourceLocation` is basically a smart 2D coordinate equivalent to `(line, col)` (but also tracks `CodePointIndex`)
|
||||
- `Span` an interval determined by `start` and `end` SourceLocations
|
||||
|
||||
# Source Cursor
|
||||
- `SourceCursor` is a mutable cursor over `SourceRegion`. Primarily useful to build parsers on top of `SourceRegion`. It is line-aware.
|
||||
|
||||
# Rendering CLI Errors
|
||||
Secondary functionality is `function renderSpan(region: SourceRegion, span: Span, contextLines = 1): LineView[]` which is able to render spans of source-code as follows
|
||||
|
|
|
|||
81
src/index.ts
81
src/index.ts
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue