extern crate handlebars;
use crate::svg_generator::data::{ExtBranchData, ExternalEvent, ResourceAccessPoint_extract, Visualizable, VisualizationData, LINE_SPACE};
use crate::svg_generator::svg_frontend::{code_panel, timeline_panel};
use handlebars::Handlebars;
use serde::Serialize;
use std::collections::{BTreeMap, HashMap, HashSet};
use crate::svg_generator::svg_frontend::templates::*;
use log::info;
#[derive(Serialize)]
struct SvgData {
visualization_name: String,
css: String,
code: String,
diagram: String,
tl_id: String,
tl_width: i32,
height: i32,
}
fn sort_branch_external_events(b_data: & mut ExtBranchData) {
for (_, event_vec) in b_data.line_map.iter_mut() {
event_vec.sort_by(|a, b| {
ResourceAccessPoint_extract(a)
.1
.hash()
.cmp(&ResourceAccessPoint_extract(b).1.hash())
.then(
ResourceAccessPoint_extract(a)
.0
.hash()
.cmp(&ResourceAccessPoint_extract(b).0.hash()),
)
});
}
for (_, ev) in b_data.e_data.iter_mut() {
match ev {
ExternalEvent::Branch { branches, ..} => {
for branch in branches {
sort_branch_external_events(branch);
}
}
_ => {}
}
}
}
pub fn mutate_branch_lines(b_data: & mut ExtBranchData, l_map: & mut HashMap<usize, usize>, mut extra_lines: usize) -> usize {
let old_extra_lines = extra_lines;
let old_line_map = b_data.line_map.clone();
let mut new_line_map: BTreeMap<usize, Vec<ExternalEvent>> = BTreeMap::new();
let mut skippable_ev: HashSet<usize> = HashSet::new();
let mut i: usize = 0;
let size: usize = b_data.e_data.len();
while i < size {
let (l, e) = b_data.e_data.get_mut(i).unwrap();
let line_num = *l;
let new_line_num = *l + extra_lines;
if skippable_ev.contains(&e.get_id()) {
i = i + 1;
continue;
}
*l += extra_lines; match e {
ExternalEvent::Branch {branches, split_point, merge_point, branch_type, .. } => {
*split_point = *l; for (j, branch) in branches.iter_mut().enumerate(){ let (start, end) = branch_type.get_mut_start_end(j);
*start += extra_lines;
let b = mutate_branch_lines(branch, l_map, extra_lines);
extra_lines += b;
*end += extra_lines;
}
*merge_point += extra_lines;
}
_ => {
if e.is_arrow_ev() {
let res = old_line_map.get(&line_num).cloned();
let ex = match res {
Some(ev) => { for e in ev.clone() {
skippable_ev.insert(e.get_id()); let mut j = i;
while j < size { let (l, p_e) = b_data.e_data.get_mut(j).unwrap();
if p_e.get_id() == e.get_id(){
*l = new_line_num;
break;
}
j += 1;
}
}
let ev_len = ev.len() - 1;
new_line_map.insert(new_line_num, ev); l_map.insert(new_line_num, ev_len);
ev_len
},
None => {
0
}
};
extra_lines += ex;
}
}
}
i += 1;
}
b_data.line_map = new_line_map;
extra_lines - old_extra_lines
}
fn shift_event_lines<F: Fn(usize) -> usize>(ev: &mut ExternalEvent, shift: &F) {
if let ExternalEvent::Branch { branches, branch_type, split_point, merge_point, .. } = ev {
*split_point = shift(*split_point);
*merge_point = shift(*merge_point);
match branch_type {
crate::svg_generator::data::BranchType::If(_, v)
| crate::svg_generator::data::BranchType::Loop(_, v)
| crate::svg_generator::data::BranchType::Match(_, v) => {
for (s, e) in v.iter_mut() {
*s = shift(*s);
*e = shift(*e);
}
}
}
for branch in branches.iter_mut() {
for (line, sub) in branch.e_data.iter_mut() {
*line = shift(*line);
shift_event_lines(sub, shift);
}
let new_line_map: BTreeMap<usize, Vec<ExternalEvent>> = branch.line_map
.iter()
.map(|(k, v)| {
let mut v = v.clone();
for sub in v.iter_mut() { shift_event_lines(sub, shift); }
(shift(*k), v)
})
.collect();
branch.line_map = new_line_map;
}
}
}
pub fn render_svg(
annotated_src_str: &str,
source_rs_str: &str,
visualization_data: &mut VisualizationData,
) -> (String, String){
info!("preprocessed events : {:#?}", visualization_data.preprocess_external_events);
info!("ev_line_map: {:#?}", visualization_data.event_line_map);
let a_lines_orig: Vec<&str> = annotated_src_str.lines().collect();
let s_lines_orig: Vec<&str> = source_rs_str.lines().collect();
let mut sorted_fn_starts: Vec<usize> = visualization_data
.fn_start_lines
.values()
.copied()
.collect::<HashSet<_>>()
.into_iter()
.collect();
sorted_fn_starts.sort();
let mut needs_blank_at: Vec<usize> = Vec::new();
for &src_f in sorted_fn_starts.iter().skip(1) {
if src_f < 2 { continue; }
let prev = a_lines_orig.get(src_f - 2).map(|s| s.trim()).unwrap_or("");
if !prev.is_empty() {
needs_blank_at.push(src_f);
}
}
let new_a_str: String;
let new_s_str: String;
if needs_blank_at.is_empty() {
new_a_str = annotated_src_str.to_string();
new_s_str = source_rs_str.to_string();
} else {
let needs_set: HashSet<usize> = needs_blank_at.iter().copied().collect();
let mut new_a: Vec<String> = Vec::with_capacity(a_lines_orig.len() + needs_blank_at.len());
let mut new_s: Vec<String> = Vec::with_capacity(s_lines_orig.len() + needs_blank_at.len());
for (i, line) in a_lines_orig.iter().enumerate() {
if needs_set.contains(&(i + 1)) { new_a.push(String::new()); }
new_a.push((*line).to_string());
}
for (i, line) in s_lines_orig.iter().enumerate() {
if needs_set.contains(&(i + 1)) { new_s.push(String::new()); }
new_s.push((*line).to_string());
}
new_a_str = new_a.join("\n");
new_s_str = new_s.join("\n");
let blanks = needs_blank_at.clone();
let shift = |line: usize| -> usize {
line + blanks.iter().filter(|&&f| f <= line).count()
};
for v in visualization_data.fn_start_lines.values_mut() {
*v = shift(*v);
}
for (line, ev) in visualization_data.preprocess_external_events.iter_mut() {
*line = shift(*line);
shift_event_lines(ev, &shift);
}
let shifted_elm: BTreeMap<usize, Vec<ExternalEvent>> = visualization_data
.event_line_map
.iter()
.map(|(k, v)| {
let mut v = v.clone();
for sub in v.iter_mut() { shift_event_lines(sub, &shift); }
(shift(*k), v)
})
.collect();
visualization_data.event_line_map = shifted_elm;
}
let annotated_src_str: &str = new_a_str.as_str();
let source_rs_str: &str = new_s_str.as_str();
let mut i: usize = 0;
let size: usize = visualization_data.preprocess_external_events.len();
let mut event_line_map_replace: BTreeMap<usize, Vec<ExternalEvent>> = BTreeMap::new();
let mut extra_lines: usize = 0;
let mut extras_at_source_line: Vec<(usize, usize)> = Vec::new();
let mut skippable_ev: HashSet<usize> = HashSet::new();
let mut line_insertion_map: HashMap<usize, usize> = HashMap::new();
while i < size {
let (line_num, event) = visualization_data.preprocess_external_events.get_mut(i).unwrap();
let mut branch_line = 0;
if skippable_ev.contains(&event.get_id()) {
i += 1;
continue;
}
match event {
ExternalEvent::Branch { branches, split_point, merge_point, branch_type, .. } => {
*split_point += extra_lines;
branch_line = *line_num + extra_lines;
for (j, branch) in branches.iter_mut().enumerate() {
let (start, end) = branch_type.get_mut_start_end(j);
*start += extra_lines;
let b = mutate_branch_lines(branch, &mut line_insertion_map, extra_lines);
extra_lines += b;
*end += extra_lines;
}
*merge_point += extra_lines;
}
_ => {}
}
let line_num = *line_num;
let event = event.clone();
let final_line_num = if branch_line != 0 { branch_line } else {line_num + extra_lines};
if event.is_arrow_ev() {
let res = visualization_data.event_line_map.get(&line_num).cloned();
let ex = match res {
Some(ev) => { for e in ev.clone() { visualization_data.append_processed_external_event(e.clone(), final_line_num);
skippable_ev.insert(e.get_id()); }
let ev_len = ev.len() - 1;
event_line_map_replace.insert(final_line_num, ev); line_insertion_map.insert(final_line_num, ev_len);
ev_len
},
None => {
visualization_data.append_processed_external_event(event.clone(), final_line_num);
0
}
};
if ex > 0 {
extras_at_source_line.push((line_num, ex));
}
extra_lines += ex;
}
else {
visualization_data.append_processed_external_event(event.clone(), final_line_num);
}
i += 1;
}
info!("insert line map {:#?}", line_insertion_map);
visualization_data.external_events.sort_by(|(l, _), (l1, _)| l.cmp(l1));
info!("processed events {:#?}", visualization_data.external_events);
visualization_data.event_line_map = event_line_map_replace;
extras_at_source_line.sort_by_key(|(s, _)| *s);
for src_line in visualization_data.fn_start_lines.values_mut() {
let extras_before: usize = extras_at_source_line
.iter()
.take_while(|(s, _)| *s < *src_line)
.map(|(_, n)| *n)
.sum();
*src_line += extras_before;
}
for (_, event_vec) in &mut visualization_data.event_line_map {
event_vec.sort_by(|a, b| {
ResourceAccessPoint_extract(a)
.1
.hash()
.cmp(&ResourceAccessPoint_extract(b).1.hash())
.then(
ResourceAccessPoint_extract(a)
.0
.hash()
.cmp(&ResourceAccessPoint_extract(b).0.hash()),
)
});
}
for (_, e) in visualization_data.preprocess_external_events.iter_mut() {
match e {
ExternalEvent::Branch { branches, .. } => {
for b in branches.iter_mut() {
sort_branch_external_events(b);
}
}
_ => {}
}
}
info!("processed line map {:#?}", visualization_data.event_line_map);
let svg_code_template = CODE_PANEL_TEMPLATE;
let svg_timeline_template = TIMELINE_PANEL_TEMPLATE;
let css_string = CSS_TEMPLATE;
visualization_data.compute_states();
let a_lines = annotated_src_str.lines();
let s_lines = source_rs_str.lines();
let mut handlebars = Handlebars::new();
handlebars.register_escape_fn(handlebars::no_escape);
let code_svg_template = svg_code_template;
let tl_svg_template = svg_timeline_template;
assert!(handlebars
.register_template_string("code_svg_template", code_svg_template)
.is_ok());
assert!(handlebars
.register_template_string("timeline_svg_template", tl_svg_template)
.is_ok());
let mut max_x_space: i64 = 0;
let (output, line_of_code) =
code_panel::render_code_panel(a_lines, s_lines, &mut max_x_space, &visualization_data.event_line_map, &line_insertion_map);
let code_panel_string = output;
let num_lines = line_of_code;
let (timeline_panel_string, max_width) = timeline_panel::render_timeline_panel(visualization_data);
let code_panel_width = std::cmp::max(400, (max_x_space as i32) * 9 + 40);
let mut svg_data = SvgData {
visualization_name: "vis".to_owned(),
css: css_string.to_owned(),
code: code_panel_string,
diagram: timeline_panel_string,
tl_id: "tl_".to_owned() + "vis",
tl_width: code_panel_width,
height: (num_lines * LINE_SPACE as i32 + 80) + 50,
};
let final_code_svg_content = handlebars.render("code_svg_template", &svg_data).unwrap();
svg_data.tl_width = std::cmp::max(max_width, 200);
let final_timeline_svg_content = handlebars
.render("timeline_svg_template", &svg_data)
.unwrap();
(final_code_svg_content, final_timeline_svg_content)
}