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`)
|
- `SourceLocation` is basically a smart 2D coordinate equivalent to `(line, col)` (but also tracks `CodePointIndex`)
|
||||||
- `Span` an interval determined by `start` and `end` SourceLocations
|
- `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
|
# 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
|
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 LOWERCASE_z: CodePoint = char('z');
|
||||||
export const UPPERCASE_Z: CodePoint = char('Z');
|
export const UPPERCASE_Z: CodePoint = char('Z');
|
||||||
|
|
||||||
|
// === Predicates ===
|
||||||
|
|
||||||
export function isBetween(a: CodePoint, x: CodePoint, b: CodePoint): boolean {
|
export function isBetween(a: CodePoint, x: CodePoint, b: CodePoint): boolean {
|
||||||
return a <= x && x <= b;
|
return a <= x && x <= b;
|
||||||
|
|
@ -46,6 +47,17 @@ export function isAsciiAlphanumeric(x: CodePoint): boolean {
|
||||||
return isAsciiAlpha(x) || isDigit(x);
|
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 = {
|
export type CodePointRef = {
|
||||||
char: CodePoint,
|
char: CodePoint,
|
||||||
offset: StringIndex,
|
offset: StringIndex,
|
||||||
|
|
@ -366,6 +378,75 @@ export type SourceLocation = {
|
||||||
column: number; // 1-based
|
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 ===
|
// === Rendering Utilities ===
|
||||||
|
|
||||||
export type LineView = {
|
export type LineView = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue