Introduce SourceRegion

This commit is contained in:
Yura Dupyn 2026-04-06 17:17:30 +02:00
parent 9939f6c97f
commit a439f15c5f

View file

@ -97,6 +97,22 @@ export class SourceText {
return { index, line, column }; return { index, line, column };
} }
// Creates a SourceRegion from a Span.
makeRegion(span: Span): SourceRegion {
// Basic validation
if (span.start.index < 0 || span.end.index > this.length) {
throw new Error(`Span out of bounds: ${span.start.index}-${span.end.index} (length: ${this.length})`);
}
return new SourceRegion(this, span);
}
// Creates a SourceRegion covering the entire SourceText.
fullRegion(): SourceRegion {
const start = this.getLocation(0);
const end = this.getLocation(this.length);
return this.makeRegion({ start, end });
}
// Returns the full text of a specific line (1-based index) // Returns the full text of a specific line (1-based index)
getLineText(line: number): string { getLineText(line: number): string {
const lineIndex = line - 1; const lineIndex = line - 1;
@ -132,18 +148,29 @@ export function sourceText(s: string): SourceText {
} }
export class SourceRegion { export class SourceRegion {
// TODO constructor(
readonly sourceText: SourceText; public readonly source: SourceText,
readonly span: Span; public readonly span: Span
) {}
constructor(sourceText: SourceText, span: Span) { get length(): number {
this.sourceText = sourceText; return this.span.end.index - this.span.start.index;
// TODO: What about span validation? }
this.span = span;
toString(): string {
return this.source.sliceByCp(this.span.start.index, this.span.end.index);
}
// Creates a sub-region within this region.
// Validates that the new span is contained within the current region.
subRegion(span: Span): SourceRegion {
if (span.start.index < this.span.start.index || span.end.index > this.span.end.index) {
throw new Error(`Sub-region span ${span.start.index}-${span.end.index} is not within parent region ${this.span.start.index}-${this.span.end.index}`);
}
return this.source.makeRegion(span);
} }
} }
export type Span = { export type Span = {
start: SourceLocation; start: SourceLocation;
end: SourceLocation; end: SourceLocation;
@ -190,7 +217,12 @@ export type LineView = {
underline: string; // The literal "^^^" string for CLI usage underline: string; // The literal "^^^" string for CLI usage
}; };
export function renderSpan(text: SourceText, span: Span, contextLines = 1): LineView[] { export function renderRegion(region: SourceRegion, contextLines = 1): LineView[] {
return renderSpan(region, region.span, contextLines);
}
export function renderSpan(region: SourceRegion, span: Span, contextLines = 1): LineView[] {
const text = region.source;
const views: LineView[] = []; const views: LineView[] = [];
// Determine range of lines to show (including context) // Determine range of lines to show (including context)