Files
hakorune/src/ast/span.rs

87 lines
2.6 KiB
Rust
Raw Normal View History

use std::fmt;
/// ソースコード位置情報 - エラー報告とデバッグの革命
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Span {
pub start: usize, // 開始位置(バイトオフセット)
pub end: usize, // 終了位置(バイトオフセット)
pub line: usize, // 行番号1から開始
pub column: usize, // 列番号1から開始
}
impl Span {
/// 新しいSpanを作成
pub fn new(start: usize, end: usize, line: usize, column: usize) -> Self {
Self {
start,
end,
line,
column,
}
}
/// デフォルトのSpan不明な位置
pub fn unknown() -> Self {
Self {
start: 0,
end: 0,
line: 1,
column: 1,
}
}
/// 2つのSpanを結合開始位置から終了位置まで
pub fn merge(&self, other: Span) -> Span {
Span {
start: self.start.min(other.start),
end: self.end.max(other.end),
line: self.line,
column: self.column,
}
}
/// ソースコードから該当箇所を抽出してエラー表示用文字列を生成
pub fn error_context(&self, source: &str) -> String {
let lines: Vec<&str> = source.lines().collect();
if self.line == 0 || self.line > lines.len() {
return format!("line {}, column {}", self.line, self.column);
}
let line_content = lines[self.line - 1];
let mut context = String::new();
// 行番号とソース行を表示
context.push_str(&format!(" |\n{:3} | {}\n", self.line, line_content));
// カーソル位置を表示(簡易版)
if self.column > 0 && self.column <= line_content.len() + 1 {
context.push_str(" | ");
for _ in 1..self.column {
context.push(' ');
}
let span_length = if self.end > self.start {
(self.end - self.start).min(line_content.len() - self.column + 1)
} else {
1
};
for _ in 0..span_length.max(1) {
context.push('^');
}
context.push('\n');
}
context
}
/// 位置情報の文字列表現
pub fn location_string(&self) -> String {
format!("line {}, column {}", self.line, self.column)
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "line {}, column {}", self.line, self.column)
}
}