lalrpop/src/cli/filetext.rs

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(())
}
}