use std::collections::{BTreeMap, Bound};
use codespan::{ByteIndex, ByteOffset, ColumnIndex, Files, LineIndex, RawIndex, RawOffset};
use crate::model::Loc;
use std::cell::RefCell;
struct CodeWriterData {
emit_hook: Box<dyn Fn(&str) -> Option<String>>,
output: String,
indent: usize,
current_location: Loc,
output_location_map: BTreeMap<ByteIndex, Loc>,
label_map: BTreeMap<ByteIndex, ByteIndex>,
}
pub struct CodeWriter(RefCell<CodeWriterData>);
#[derive(Debug, Clone, Copy)]
pub struct CodeWriterLabel(ByteIndex);
impl CodeWriter {
pub fn new(loc: Loc) -> CodeWriter {
let zero = ByteIndex(0);
let mut output_location_map = BTreeMap::new();
output_location_map.insert(zero, loc.clone());
Self(RefCell::new(CodeWriterData {
emit_hook: Box::new(|_| None),
output: String::new(),
indent: 0,
current_location: loc,
output_location_map,
label_map: Default::default(),
}))
}
pub fn set_emit_hook<F>(&self, f: F)
where
F: Fn(&str) -> Option<String>,
F: 'static,
{
let mut data = self.0.borrow_mut();
data.emit_hook = Box::new(f)
}
pub fn create_label(&self) -> CodeWriterLabel {
let mut data = self.0.borrow_mut();
let index = ByteIndex(data.output.len() as RawIndex);
data.label_map.insert(index, index);
CodeWriterLabel(index)
}
pub fn insert_at_label(&self, label: CodeWriterLabel, s: &str) {
let mut data = self.0.borrow_mut();
let index = *data.label_map.get(&label.0).expect("label undefined");
let shift = ByteOffset(s.len() as RawOffset);
data.label_map = std::mem::take(&mut data.label_map)
.into_iter()
.map(|(i, j)| if j > index { (i, j + shift) } else { (i, j) })
.collect();
data.output_location_map = std::mem::take(&mut data.output_location_map)
.into_iter()
.map(|(i, loc)| {
if i > index {
(i + shift, loc)
} else {
(i, loc)
}
})
.collect();
data.output.insert_str(index.0 as usize, s);
}
pub fn process_result<T, F: FnMut(&str) -> T>(&self, mut f: F) -> T {
let data = self.0.borrow();
let output = data.output.as_str();
let mut j = output.trim_end().len();
if j < output.len() && output[j..].starts_with('\n') {
j += 1;
}
f(&output[0..j])
}
pub fn extract_result(&self) -> String {
let mut s = std::mem::take(&mut self.0.borrow_mut().output);
s.truncate(s.trim_end().len());
if !s.ends_with('\n') {
s.push('\n');
}
s
}
pub fn set_location(&self, loc: &Loc) {
let mut data = self.0.borrow_mut();
let code_at = ByteIndex(data.output.len() as u32);
if &data.current_location != loc {
data.output_location_map.insert(code_at, loc.clone());
data.current_location = loc.clone();
}
}
pub fn get_source_location(&self, output_index: ByteIndex) -> Option<Loc> {
let data = self.0.borrow();
if let Some(loc) = data
.output_location_map
.range((Bound::Unbounded, Bound::Included(&output_index)))
.next_back()
.map(|(_, v)| v)
{
return Some(loc.clone());
}
None
}
pub fn get_output_byte_index(&self, line: LineIndex, column: ColumnIndex) -> Option<ByteIndex> {
self.process_result(|s| {
let mut fmap = Files::new();
let id = fmap.add("dummy", s);
fmap.line_span(id, line).ok().map(|line_span| {
ByteIndex((line_span.start().to_usize() + column.to_usize()) as u32)
})
})
}
pub fn indent(&self) {
let mut data = self.0.borrow_mut();
data.indent += 4;
}
pub fn unindent(&self) {
let mut data = self.0.borrow_mut();
assert!(data.indent >= 4);
data.indent -= 4;
}
pub fn with_indent<F>(&self, mut f: F)
where
F: FnMut(),
{
self.indent();
f();
self.unindent();
}
pub fn emit(&self, s: &str) {
let rewritten = (*self.0.borrow().emit_hook)(s);
let s = if let Some(r) = &rewritten {
r.as_str()
} else {
s
};
let mut first = true;
let end_newl = s.ends_with('\n');
for l in s.lines() {
if first {
first = false
} else {
Self::trim_trailing_whitespace(&mut self.0.borrow_mut().output);
self.0.borrow_mut().output.push('\n');
}
self.emit_str(l)
}
if end_newl {
Self::trim_trailing_whitespace(&mut self.0.borrow_mut().output);
self.0.borrow_mut().output.push('\n');
}
}
fn trim_trailing_whitespace(s: &mut String) {
s.truncate(s.trim_end_matches(' ').len());
}
pub fn emit_line(&self, s: &str) {
self.emit(s.trim_end_matches(' '));
self.emit("\n");
}
fn emit_str(&self, s: &str) {
let mut data = self.0.borrow_mut();
if data.indent > 0 && (data.output.is_empty() || data.output.ends_with('\n')) {
let n = data.indent;
data.output.push_str(&" ".repeat(n));
}
data.output.push_str(s);
}
}
#[macro_export]
macro_rules! emit {
($target:expr, $s:expr) => (
$target.emit($s)
);
($target:expr, $s:expr, $($arg:expr),+ $(,)?) => (
$target.emit(&format!($s, $($arg),+))
)
}
#[macro_export]
macro_rules! emitln {
($target:expr) => (
$target.emit_line("")
);
($target:expr, $s:expr) => (
$target.emit_line($s)
);
($target:expr, $s:expr, $($arg:expr),+ $(,)?) => (
$target.emit_line(&format!($s, $($arg),+))
)
}