mirror of
https://github.com/fluencelabs/lalrpop
synced 2025-03-27 13:41:04 +00:00
111 lines
3.8 KiB
Rust
111 lines
3.8 KiB
Rust
use grammar::parse_tree as pt;
|
|
use std::fs::File;
|
|
use std::io::{self, Read, Write};
|
|
|
|
pub struct FileText {
|
|
path: String,
|
|
input_str: String,
|
|
newlines: Vec<usize>,
|
|
}
|
|
|
|
impl FileText {
|
|
pub fn from_stdin() -> io::Result<FileText> {
|
|
let mut input_str = String::new();
|
|
let stdin = io::stdin();
|
|
let mut stdin = stdin.lock();
|
|
try!(stdin.read_to_string(&mut input_str));
|
|
Ok(FileText::new(format!("<stdin>"), input_str))
|
|
}
|
|
|
|
pub fn from_path(path: String) -> io::Result<FileText> {
|
|
let mut input_str = String::new();
|
|
let mut f = try!(File::open(&path));
|
|
try!(f.read_to_string(&mut input_str));
|
|
Ok(FileText::new(path, input_str))
|
|
}
|
|
|
|
fn new(path: String, input_str: String) -> FileText {
|
|
let newline_indices: Vec<usize> =
|
|
input_str.as_bytes().iter()
|
|
.enumerate()
|
|
.filter(|&(_, &b)| b == ('\n' as u8))
|
|
.map(|(i, _)| i + 1) // store the first character in the line
|
|
.collect();
|
|
|
|
FileText { path: path, input_str: input_str, newlines: newline_indices }
|
|
}
|
|
|
|
pub fn text(&self) -> &String {
|
|
&self.input_str
|
|
}
|
|
|
|
pub fn span_str(&self, span: pt::Span) -> String {
|
|
let (start_line, start_col) = self.line_col(span.0);
|
|
let (end_line, end_col) = self.line_col(span.1);
|
|
format!("{}:{}:{}: {}:{}", self.path, start_line+1, start_col+1, end_line+1, end_col+1)
|
|
}
|
|
|
|
fn line_col(&self, pos: usize) -> (usize, usize) {
|
|
let num_lines = self.newlines.len();
|
|
let line =
|
|
(0..num_lines)
|
|
.filter(|&i| self.newlines[i] > pos)
|
|
.map(|i| i-1)
|
|
.next()
|
|
.unwrap();
|
|
|
|
// offset of the first character in `line`
|
|
let line_offset = self.newlines[line];
|
|
|
|
// find the column; use `saturating_sub` in case `pos` is the
|
|
// newline itself, which we'll call column 0
|
|
let col = pos - line_offset;
|
|
|
|
(line, col)
|
|
}
|
|
|
|
fn line_text(&self, line_num: usize) -> &str {
|
|
let start_offset = self.newlines[line_num];
|
|
if line_num == self.newlines.len() - 1 {
|
|
&self.input_str[start_offset..]
|
|
} else {
|
|
let end_offset = self.newlines[line_num + 1];
|
|
&self.input_str[start_offset..end_offset]
|
|
}
|
|
}
|
|
|
|
pub fn highlight(&self, span: pt::Span, out: &mut Write) -> io::Result<()> {
|
|
let (start_line, start_col) = self.line_col(span.0);
|
|
let (end_line, end_col) = self.line_col(span.1);
|
|
|
|
// (*) use `saturating_sub` since the start line could be the newline
|
|
// itself, in which case we'll call it column zero
|
|
|
|
// span is within one line:
|
|
if start_line == end_line {
|
|
let text = self.line_text(start_line);
|
|
try!(writeln!(out, " {}", text));
|
|
|
|
if end_col - start_col <= 1 {
|
|
try!(writeln!(out, " {0:1$}^", "", start_col));
|
|
} else {
|
|
let width = end_col - start_col;
|
|
try!(writeln!(out, " {0:1$}|{0:-2$}|",
|
|
"", start_col, width.saturating_sub(2)));
|
|
}
|
|
} else {
|
|
// span is across many lines, find the maximal width of any of those
|
|
let line_strs: Vec<_> = (start_line..end_line+1).map(|i| self.line_text(i)).collect();
|
|
let max_len = line_strs.iter().map(|l| l.len()).max().unwrap();
|
|
try!(writeln!(out, " {0:1$}|{0:-2$}-+", "", start_col, max_len - start_col));
|
|
for line in &line_strs[..line_strs.len()-1] {
|
|
try!(writeln!(out, "| {0:<1$} |", line, max_len));
|
|
}
|
|
try!(writeln!(out, "| {}", line_strs[line_strs.len()-1]));
|
|
try!(writeln!(out, "+-{0:-1$}", "", end_col));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|