fix include merging for when a file imports another file more than once directly

This commit is contained in:
Noah Santschi-Cooney 2022-04-18 01:08:53 +01:00
parent 1529460a5c
commit fecc41168a
No known key found for this signature in database
GPG key ID: 3B22282472C8AE48
10 changed files with 526 additions and 380 deletions

View file

@ -51,7 +51,7 @@ impl VirtualMergedDocument {
for node in nodes {
let graph = self.graph.borrow();
let path = graph.get_node(node.0);
let path = graph.get_node(node.child);
if sources.contains_key(&path) {
continue;
@ -103,7 +103,7 @@ impl Invokeable for VirtualMergedDocument {
let mut source_mapper = SourceMapper::new(all_sources.len());
let graph = self.graph.borrow();
let view = merge_views::generate_merge_list(&tree, &all_sources, &graph, &mut source_mapper);
let view = merge_views::MergeViewBuilder::new(&tree, &all_sources, &graph, &mut source_mapper).build();
return Ok(serde_json::value::Value::String(view));
}
return Err(format_err!(

View file

@ -56,21 +56,21 @@ impl<'a> Iterator for Dfs<'a> {
fn next(&mut self) -> Option<Result<FilialTuple, error::CycleError>> {
let parent = self.cycle.last().map(|p| p.node);
if let Some(node) = self.stack.pop() {
if let Some(child) = self.stack.pop() {
self.cycle.push(VisitCount {
node,
children: self.graph.graph.edges(node).count(),
node: child,
children: self.graph.graph.edges(child).count(),
touch: 1,
});
let mut children = self.graph.child_node_indexes(node);
let mut children: Vec<NodeIndex> = self.graph.child_node_indexes(child).collect();
if !children.is_empty() {
// sort by line number in parent
children.sort_by(|x, y| {
let graph = &self.graph.graph;
let edge1 = graph.edge_weight(graph.find_edge(node, *x).unwrap()).unwrap();
let edge2 = graph.edge_weight(graph.find_edge(node, *y).unwrap()).unwrap();
let edge1 = graph.edge_weight(graph.find_edge(child, *x).unwrap()).unwrap();
let edge2 = graph.edge_weight(graph.find_edge(child, *y).unwrap()).unwrap();
edge2.line.cmp(&edge1.line)
});
@ -87,7 +87,7 @@ impl<'a> Iterator for Dfs<'a> {
self.reset_path_to_branch();
}
return Some(Ok((node, parent)));
return Some(Ok(FilialTuple { child, parent }));
}
None
}
@ -189,8 +189,8 @@ mod dfs_test {
collection.push(i.unwrap());
}
let nodes: Vec<NodeIndex> = collection.iter().map(|n| n.0).collect();
let parents: Vec<Option<NodeIndex>> = collection.iter().map(|n| n.1).collect();
let nodes: Vec<NodeIndex> = collection.iter().map(|n| n.child).collect();
let parents: Vec<Option<NodeIndex>> = collection.iter().map(|n| n.parent).collect();
// 0
// / \
// 1 2
@ -237,8 +237,8 @@ mod dfs_test {
collection.push(i.unwrap());
}
let nodes: Vec<NodeIndex> = collection.iter().map(|n| n.0).collect();
let parents: Vec<Option<NodeIndex>> = collection.iter().map(|n| n.1).collect();
let nodes: Vec<NodeIndex> = collection.iter().map(|n| n.child).collect();
let parents: Vec<Option<NodeIndex>> = collection.iter().map(|n| n.parent).collect();
// 0
// / \
// 1 2

View file

@ -1,6 +1,7 @@
use petgraph::stable_graph::EdgeIndex;
use petgraph::stable_graph::NodeIndex;
use petgraph::stable_graph::StableDiGraph;
use petgraph::visit::EdgeRef;
use petgraph::Direction;
use std::{
@ -53,20 +54,27 @@ impl CachedStableGraph {
}
}
// Returns the `PathBuf` for a given `NodeIndex`
pub fn get_node(&self, node: NodeIndex) -> PathBuf {
PathBuf::from_str(&self.graph[node]).unwrap()
}
pub fn get_edge_meta(&self, parent: NodeIndex, child: NodeIndex) -> &IncludePosition {
self.graph.edge_weight(self.graph.find_edge(parent, child).unwrap()).unwrap()
}
#[allow(dead_code)]
pub fn remove_node(&mut self, name: &Path) {
let idx = self.cache.remove(name);
if let Some(idx) = idx {
self.graph.remove_node(idx);
}
/// returns an iterator over all the `IncludePosition`'s between a parent and its child for all the positions
/// that the child may be imported into the parent, in order of import.
pub fn get_edge_metas(&self, parent: NodeIndex, child: NodeIndex) -> impl Iterator<Item = IncludePosition> + '_ {
let mut edges = self
.graph
.edges(parent)
.filter_map(move |edge| {
let target = self.graph.edge_endpoints(edge.id()).unwrap().1;
if target != child {
return None;
}
Some(self.graph[edge.id()])
})
.collect::<Vec<IncludePosition>>();
edges.sort_by(|x, y| x.line.cmp(&y.line));
edges.into_iter()
}
pub fn add_node(&mut self, name: &Path) -> NodeIndex {
@ -83,54 +91,24 @@ impl CachedStableGraph {
self.graph.add_edge(parent, child, meta)
}
pub fn remove_edge(&mut self, parent: NodeIndex, child: NodeIndex) {
let edge = self.graph.find_edge(parent, child).unwrap();
self.graph.remove_edge(edge);
}
#[allow(dead_code)]
pub fn edge_weights(&self, node: NodeIndex) -> Vec<IncludePosition> {
self.graph.edges(node).map(|e| e.weight().clone()).collect()
}
#[allow(dead_code)]
pub fn child_node_names(&self, node: NodeIndex) -> Vec<PathBuf> {
pub fn remove_edge(&mut self, parent: NodeIndex, child: NodeIndex, position: IncludePosition) {
self.graph
.neighbors(node)
.map(|n| self.reverse_index.get(&n).unwrap().clone())
.collect()
.edges(parent)
.find(|edge| self.graph.edge_endpoints(edge.id()).unwrap().1 == child && *edge.weight() == position)
.map(|edge| edge.id())
.and_then(|edge| self.graph.remove_edge(edge));
}
pub fn child_node_meta(&self, node: NodeIndex) -> Vec<(PathBuf, IncludePosition)> {
self.graph
.neighbors(node)
.map(|n| {
let edge = self.graph.find_edge(node, n).unwrap();
let edge_meta = self.graph.edge_weight(edge).unwrap();
return (self.reverse_index.get(&n).unwrap().clone(), edge_meta.clone());
})
.collect()
pub fn child_node_metas(&self, node: NodeIndex) -> impl Iterator<Item = (PathBuf, IncludePosition)> + '_ {
self.graph.neighbors(node).map(move |n| {
let edge = self.graph.find_edge(node, n).unwrap();
let edge_meta = self.graph.edge_weight(edge).unwrap();
return (self.reverse_index.get(&n).unwrap().clone(), *edge_meta);
})
}
pub fn child_node_indexes(&self, node: NodeIndex) -> Vec<NodeIndex> {
self.graph.neighbors(node).collect()
}
#[allow(dead_code)]
pub fn parent_node_names(&self, node: NodeIndex) -> Vec<PathBuf> {
self.graph
.neighbors_directed(node, Direction::Incoming)
.map(|n| self.reverse_index.get(&n).unwrap().clone())
.collect()
}
pub fn parent_node_indexes(&self, node: NodeIndex) -> Vec<NodeIndex> {
self.graph.neighbors_directed(node, Direction::Incoming).collect()
}
#[allow(dead_code)]
pub fn get_include_meta(&self, node: NodeIndex) -> Vec<IncludePosition> {
self.graph.edges(node).map(|e| e.weight().clone()).collect()
pub fn child_node_indexes(&self, node: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
self.graph.neighbors(node)
}
pub fn collect_root_ancestors(&self, node: NodeIndex) -> Vec<NodeIndex> {
@ -138,6 +116,11 @@ impl CachedStableGraph {
self.get_root_ancestors(node, node, &mut visited)
}
// TODO: impl Iterator
fn parent_node_indexes(&self, node: NodeIndex) -> Vec<NodeIndex> {
self.graph.neighbors_directed(node, Direction::Incoming).collect()
}
fn get_root_ancestors(&self, initial: NodeIndex, node: NodeIndex, visited: &mut HashSet<NodeIndex>) -> Vec<NodeIndex> {
if node == initial && !visited.is_empty() {
return vec![];
@ -163,10 +146,36 @@ impl CachedStableGraph {
}
}
#[cfg(test)]
impl CachedStableGraph {
fn parent_node_names(&self, node: NodeIndex) -> Vec<PathBuf> {
self.graph
.neighbors_directed(node, Direction::Incoming)
.map(|n| self.reverse_index.get(&n).unwrap().clone())
.collect()
}
fn child_node_names(&self, node: NodeIndex) -> Vec<PathBuf> {
self.graph
.neighbors(node)
.map(|n| self.reverse_index.get(&n).unwrap().clone())
.collect()
}
fn remove_node(&mut self, name: &Path) {
let idx = self.cache.remove(name);
if let Some(idx) = idx {
self.graph.remove_node(idx);
}
}
}
#[cfg(test)]
mod graph_test {
use std::path::PathBuf;
use petgraph::graph::NodeIndex;
use crate::{graph::CachedStableGraph, IncludePosition};
#[test]
@ -182,7 +191,7 @@ mod graph_test {
assert_eq!(children.len(), 1);
assert_eq!(children[0], Into::<PathBuf>::into("banana".to_string()));
let children = graph.child_node_indexes(idx1);
let children: Vec<NodeIndex> = graph.child_node_indexes(idx1).collect();
assert_eq!(children.len(), 1);
assert_eq!(children[0], idx2);
@ -207,10 +216,33 @@ mod graph_test {
graph.remove_node(&PathBuf::from("sample"));
assert_eq!(graph.graph.node_count(), 1);
assert!(graph.find_node(&PathBuf::from("sample")).is_none());
let neighbors = graph.child_node_names(idx2);
assert_eq!(neighbors.len(), 0);
}
#[test]
#[logging_macro::log_scope]
fn test_double_import() {
let mut graph = CachedStableGraph::new();
let idx0 = graph.add_node(&PathBuf::from("0"));
let idx1 = graph.add_node(&PathBuf::from("1"));
graph.add_edge(idx0, idx1, IncludePosition { line: 2, start: 0, end: 0 });
graph.add_edge(idx0, idx1, IncludePosition { line: 4, start: 0, end: 0 });
// 0
// / \
// 1 1
assert_eq!(2, graph.get_edge_metas(idx0, idx1).count());
let mut edge_metas = graph.get_edge_metas(idx0, idx1);
assert_eq!(Some(IncludePosition { line: 2, start: 0, end: 0 }), edge_metas.next());
assert_eq!(Some(IncludePosition { line: 4, start: 0, end: 0 }), edge_metas.next());
}
#[test]
#[logging_macro::log_scope]
fn test_collect_root_ancestors() {
@ -287,10 +319,8 @@ mod graph_test {
graph.add_edge(idx1, idx3, IncludePosition { line: 5, start: 0, end: 0 });
// 0
// |
// 1
// \
// 2 \
// \
// 2 1
// \ /
// 3

View file

@ -16,7 +16,6 @@ use url_norm::FromUrl;
use walkdir::WalkDir;
use std::collections::hash_map::RandomState;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::fmt::{Debug, Display, Formatter};
@ -60,9 +59,7 @@ mod url_norm;
mod test;
lazy_static! {
static ref RE_VERSION: Regex = Regex::new(r#"#version [\d]{3}"#).unwrap();
static ref RE_INCLUDE: Regex = Regex::new(r#"^(?:\s)*?(?:#include) "(.+)"\r?"#).unwrap();
static ref RE_INCLUDE_EXTENSION: Regex = Regex::new(r#"#extension GL_GOOGLE_include_directive ?: ?require"#).unwrap();
}
fn main() {
@ -119,10 +116,13 @@ pub struct MinecraftShaderLanguageServer {
log_guard: Option<slog_scope::GlobalLoggerGuard>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct IncludePosition {
// the 0-indexed line on which the include lives.
line: usize,
// the 0-indexed char offset defining the start of the include path string.
start: usize,
// the 0-indexed char offset defining the end of the include path string.
end: usize,
}
@ -178,8 +178,8 @@ impl MinecraftShaderLanguageServer {
None => return None,
};
// TODO: include user added extensions
if ext != "vsh" && ext != "fsh" && ext != "glsl" && ext != "inc" {
// TODO: include user added extensions with a set
if ext != "vsh" && ext != "fsh" && ext != "csh" && ext != "gsh" && ext != "glsl" && ext != "inc" {
return None;
}
@ -251,8 +251,8 @@ impl MinecraftShaderLanguageServer {
Some(n) => n,
};
let prev_children: HashSet<_, RandomState> = HashSet::from_iter(self.graph.borrow().child_node_meta(idx));
let new_children: HashSet<_, RandomState> = includes.iter().cloned().collect();
let prev_children: HashSet<_> = HashSet::from_iter(self.graph.borrow().child_node_metas(idx));
let new_children: HashSet<_> = includes.iter().cloned().collect();
let to_be_added = new_children.difference(&prev_children);
let to_be_removed = prev_children.difference(&new_children);
@ -265,7 +265,7 @@ impl MinecraftShaderLanguageServer {
for removal in to_be_removed {
let child = self.graph.borrow_mut().find_node(&removal.0).unwrap();
self.graph.borrow_mut().remove_edge(idx, child);
self.graph.borrow_mut().remove_edge(idx, child, removal.1);
}
for insertion in to_be_added {
@ -325,7 +325,10 @@ impl MinecraftShaderLanguageServer {
let view = {
let graph = self.graph.borrow();
merge_views::generate_merge_list(&tree, &all_sources, &graph, &mut source_mapper)
let merged_string = {
merge_views::MergeViewBuilder::new(&tree, &all_sources, &graph, &mut source_mapper).build()
};
merged_string
};
let root_path = self.graph.borrow().get_node(root);
@ -365,7 +368,7 @@ impl MinecraftShaderLanguageServer {
diagnostics.extend(diagnostics_parser.parse_diagnostics_output(stdout, uri, &source_mapper, &self.graph.borrow()));
} else {
let mut all_trees: Vec<(TreeType, Vec<(NodeIndex, Option<_>)>)> = Vec::new();
let mut all_trees: Vec<(TreeType, Vec<FilialTuple>)> = Vec::new();
for root in &file_ancestors {
let nodes = match self.get_dfs_for_node(*root) {
@ -408,10 +411,13 @@ impl MinecraftShaderLanguageServer {
let mut source_mapper = source_mapper::SourceMapper::new(all_sources.len());
let view = {
let graph = self.graph.borrow();
merge_views::generate_merge_list(&tree.1, &all_sources, &graph, &mut source_mapper)
let merged_string = {
merge_views::MergeViewBuilder::new(&tree.1, &all_sources, &graph, &mut source_mapper).build()
};
merged_string
};
let root_path = self.graph.borrow().get_node(tree.1[0].0);
let root_path = self.graph.borrow().get_node(tree.1.first().unwrap().child);
let stdout = match self.compile_shader_source(&view, tree.0, &root_path) {
Some(s) => s,
None => continue,
@ -451,7 +457,7 @@ impl MinecraftShaderLanguageServer {
for node in nodes {
let graph = self.graph.borrow();
let path = graph.get_node(node.0);
let path = graph.get_node(node.child);
if sources.contains_key(&path) {
continue;
@ -817,29 +823,30 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
.graph
.borrow()
.child_node_indexes(node)
.into_iter()
.filter_map(|child| {
.filter_map::<Vec<DocumentLink>, _>(|child| {
let graph = self.graph.borrow();
let value = graph.get_edge_meta(node, child);
let path = graph.get_node(child);
let url = match Url::from_file_path(&path) {
Ok(url) => url,
Err(e) => {
error!("error converting into url"; "path" => path.to_str().unwrap(), "error" => format!("{:?}", e));
return None;
}
};
Some(DocumentLink {
range: Range::new(
Position::new(u32::try_from(value.line).unwrap(), u32::try_from(value.start).unwrap()),
Position::new(u32::try_from(value.line).unwrap(), u32::try_from(value.end).unwrap()),
),
target: Some(url.clone()),
tooltip: Some(url.path().to_string()),
data: None,
})
graph.get_edge_metas(node, child).map(|value| {
let path = graph.get_node(child);
let url = match Url::from_file_path(&path) {
Ok(url) => url,
Err(e) => {
error!("error converting into url"; "path" => path.to_str().unwrap(), "error" => format!("{:?}", e));
return None;
}
};
Some(DocumentLink {
range: Range::new(
Position::new(u32::try_from(value.line).unwrap(), u32::try_from(value.start).unwrap()),
Position::new(u32::try_from(value.line).unwrap(), u32::try_from(value.end).unwrap()),
),
target: Some(url.clone()),
tooltip: Some(url.path().to_string()),
data: None,
})
}).collect()
})
.flatten()
.collect();
debug!("document link results";
"links" => format!("{:?}", edges.iter().map(|e| (e.range, e.target.as_ref().unwrap().path())).collect::<Vec<_>>()),

View file

@ -11,274 +11,335 @@ use petgraph::stable_graph::NodeIndex;
use crate::graph::CachedStableGraph;
use crate::source_mapper::SourceMapper;
use crate::IncludePosition;
/// FilialTuple represents a tuple with a child at index 0
/// and a parent at index 1. Parent can be nullable in the case of
/// the child being a top level node in the tree.
pub type FilialTuple = (NodeIndex, Option<NodeIndex>);
/// FilialTuple represents a tuple (not really) of a child and any legitimate
/// parent. Parent can be nullable in the case of the child being a top level
/// node in the tree.
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
pub struct FilialTuple {
pub child: NodeIndex,
pub parent: Option<NodeIndex>,
}
pub fn generate_merge_list<'a>(
nodes: &'a [FilialTuple], sources: &'a HashMap<PathBuf, String>, graph: &'a CachedStableGraph, source_mapper: &mut SourceMapper,
) -> String {
// contains additionally inserted lines such as #line and other directives, preamble defines etc
let mut extra_lines: Vec<String> = Vec::new();
extra_lines.reserve((nodes.len() * 2) + 2);
/// Merges the source strings according to the nodes comprising a tree of imports into a GLSL source string
/// that can be handed off to the GLSL compiler.
pub struct MergeViewBuilder<'a> {
nodes: &'a [FilialTuple],
nodes_peeker: Peekable<Iter<'a, FilialTuple>>,
// list of source code views onto the below sources
let mut merge_list: LinkedList<&'a str> = LinkedList::new();
sources: &'a HashMap<PathBuf, String>,
graph: &'a CachedStableGraph,
source_mapper: &'a mut SourceMapper,
// holds the offset into the child which has been added to the merge list for a parent.
// A child can have multiple parents for a given tree, hence we have to track it for
// a (child, parent) tuple instead of just the child.
let mut last_offset_set: HashMap<FilialTuple, usize> = HashMap::new();
let mut nodes_iter = nodes.iter().peekable();
// invariant: nodes_iter always has _at least_ one element. Can't save a not-file :B
let first = nodes_iter.next().unwrap().0;
let first_path = graph.get_node(first);
let first_source = sources.get(&first_path).unwrap();
// seed source_mapper with top-level file
source_mapper.get_num(first);
let version_line_offset = find_version_offset(first_source);
let version_char_offsets = char_offset_for_line(version_line_offset, first_source);
// add_preamble(
// version_line_offset,
// version_char_offsets.1,
// &first_path,
// first,
// first_source,
// &mut merge_list,
// &mut extra_lines,
// source_mapper,
// );
// last_offset_set.insert((first, None), version_char_offsets.1);
last_offset_set.insert((first, None), 0);
// stack to keep track of the depth first traversal
let mut stack = VecDeque::<NodeIndex>::new();
create_merge_views(
&mut nodes_iter,
&mut merge_list,
&mut last_offset_set,
graph,
sources,
&mut extra_lines,
&mut stack,
source_mapper,
);
// now we add a view of the remainder of the root file
let offset = *last_offset_set.get(&(first, None)).unwrap();
let len = first_source.len();
merge_list.push_back(&first_source[min(offset, len)..]);
let total_len = merge_list.iter().fold(0, |a, b| a + b.len());
let mut merged = String::with_capacity(total_len);
merged.extend(merge_list);
merged
// A child can have multiple parents for a given tree, and be included multiple times
// by the same parent, hence we have to track it for a ((child, parent), line) tuple
// instead of just the child or (child, parent).
last_offset_set: HashMap<FilialTuple, usize>,
// holds, for any given filial tuple, the iterator yielding all the positions at which the child
// is included into the parent in line-sorted order. This is necessary for files that are imported
// more than once into the same parent, so we can easily get the next include position.
parent_child_edge_iterator: HashMap<FilialTuple, Box<(dyn Iterator<Item = IncludePosition> + 'a)>>,
}
fn create_merge_views<'a>(
nodes: &mut Peekable<Iter<FilialTuple>>, merge_list: &mut LinkedList<&'a str>, last_offset_set: &mut HashMap<FilialTuple, usize>,
graph: &'a CachedStableGraph, sources: &'a HashMap<PathBuf, String>, extra_lines: &mut Vec<String>, stack: &mut VecDeque<NodeIndex>,
source_mapper: &mut SourceMapper,
) {
loop {
let n = match nodes.next() {
Some(n) => n,
None => return,
};
impl<'a> MergeViewBuilder<'a> {
pub fn new(
nodes: &'a [FilialTuple], sources: &'a HashMap<PathBuf, String>, graph: &'a CachedStableGraph, source_mapper: &'a mut SourceMapper,
) -> Self {
MergeViewBuilder {
nodes,
nodes_peeker: nodes.iter().peekable(),
sources,
graph,
source_mapper,
last_offset_set: HashMap::new(),
parent_child_edge_iterator: HashMap::new(),
}
}
// invariant: never None as only the first element in `nodes` should have a None, which is popped off in the calling function
let parent = n.1.unwrap();
let child = n.0;
let edge = graph.get_edge_meta(parent, child);
let parent_path = graph.get_node(parent).clone();
let child_path = graph.get_node(child).clone();
pub fn build(&mut self) -> String {
// contains additionally inserted lines such as #line and other directives, preamble defines etc
let mut extra_lines: Vec<String> = Vec::new();
extra_lines.reserve((self.nodes.len() * 2) + 2);
let parent_source = sources.get(&parent_path).unwrap();
let (char_for_line, char_following_line) = char_offset_for_line(edge.line, parent_source);
// list of source code views onto the below sources
let mut merge_list: LinkedList<&'a str> = LinkedList::new();
let offset = *last_offset_set
.insert((parent, stack.back().copied()), char_following_line)
.get_or_insert(0);
merge_list.push_back(&parent_source[offset..char_for_line]);
add_opening_line_directive(&child_path, child, merge_list, extra_lines, source_mapper);
// invariant: nodes_iter always has _at least_ one element. Can't save a not-file :B
let first = self.nodes_peeker.next().unwrap().child;
let first_path = self.graph.get_node(first);
let first_source = self.sources.get(&first_path).unwrap();
match nodes.peek() {
Some(next) => {
let next = *next;
// if the next pair's parent is not a child of the current pair, we dump the rest of this childs source
if next.1.unwrap() != child {
let child_source = sources.get(&child_path).unwrap();
// if ends in \n\n, we want to exclude the last \n for some reason. Ask optilad
let offset = {
match child_source.ends_with('\n') {
true => child_source.len() - 1,
false => child_source.len(),
// seed source_mapper with top-level file
self.source_mapper.get_num(first);
let version_line_offset = self.find_version_offset(first_source);
let _version_char_offsets = self.char_offset_for_line(version_line_offset, first_source);
// add_preamble(
// version_line_offset,
// version_char_offsets.1,
// &first_path,
// first,
// first_source,
// &mut merge_list,
// &mut extra_lines,
// source_mapper,
// );
// last_offset_set.insert((first, None), version_char_offsets.1);
self.last_offset_set.insert(
FilialTuple {
child: first,
parent: None,
},
0,
);
// stack to keep track of the depth first traversal
let mut stack = VecDeque::<NodeIndex>::new();
self.create_merge_views(&mut merge_list, &mut extra_lines, &mut stack);
// now we add a view of the remainder of the root file
let offset = *self
.last_offset_set
.get(&FilialTuple {
child: first,
parent: None,
})
.unwrap();
let len = first_source.len();
merge_list.push_back(&first_source[min(offset, len)..]);
let total_len = merge_list.iter().fold(0, |a, b| a + b.len());
let mut merged = String::with_capacity(total_len);
merged.extend(merge_list);
merged
}
fn create_merge_views(&mut self, merge_list: &mut LinkedList<&'a str>, extra_lines: &mut Vec<String>, stack: &mut VecDeque<NodeIndex>) {
loop {
let n = match self.nodes_peeker.next() {
Some(n) => n,
None => return,
};
// invariant: never None as only the first element in `nodes` should have a None, which is popped off in the calling function
let (parent, child) = (n.parent.unwrap(), n.child);
// gets the next include position for the filial tuple, seeding if this is the first time querying this tuple
let edge = self
.parent_child_edge_iterator
.entry(*n)
.or_insert_with(|| {
let edge_metas = self.graph.get_edge_metas(parent, child);
Box::new(edge_metas)
})
.next()
.unwrap();
let parent_path = self.graph.get_node(parent).clone();
let child_path = self.graph.get_node(child).clone();
let parent_source = self.sources.get(&parent_path).unwrap();
let (char_for_line, char_following_line) = self.char_offset_for_line(edge.line, parent_source);
let offset = *self
.last_offset_set
.insert(
FilialTuple {
child: parent,
parent: stack.back().copied(),
},
char_following_line,
)
.get_or_insert(0);
merge_list.push_back(&parent_source[offset..char_for_line]);
self.add_opening_line_directive(&child_path, child, merge_list, extra_lines);
match self.nodes_peeker.peek() {
Some(next) => {
let next = *next;
// if the next pair's parent is not a child of the current pair, we dump the rest of this childs source
if next.parent.unwrap() != child {
let child_source = self.sources.get(&child_path).unwrap();
// if ends in \n\n, we want to exclude the last \n for some reason. Ask optilad
let offset = {
match child_source.ends_with('\n') {
true => child_source.len() - 1,
false => child_source.len(),
}
};
merge_list.push_back(&child_source[..offset]);
self.last_offset_set.insert(
FilialTuple {
child,
parent: Some(parent),
},
0,
);
// +2 because edge.line is 0 indexed but #line is 1 indexed and references the *following* line
self.add_closing_line_directive(edge.line + 2, &parent_path, parent, merge_list, extra_lines);
// if the next pair's parent is not the current pair's parent, we need to bubble up
if stack.contains(&next.parent.unwrap()) {
return;
}
continue;
}
stack.push_back(parent);
self.create_merge_views(merge_list, extra_lines, stack);
stack.pop_back();
let offset = *self
.last_offset_set
.get(&FilialTuple {
child,
parent: Some(parent),
})
.unwrap();
let child_source = self.sources.get(&child_path).unwrap();
// this evaluates to false once the file contents have been exhausted aka offset = child_source.len() + 1
let end_offset = match child_source.ends_with('\n') {
true => 1, /* child_source.len()-1 */
false => 0, /* child_source.len() */
};
merge_list.push_back(&child_source[..offset]);
last_offset_set.insert((child, Some(parent)), 0);
if offset < child_source.len() - end_offset {
// if ends in \n\n, we want to exclude the last \n for some reason. Ask optilad
merge_list.push_back(&child_source[offset../* std::cmp::max( */child_source.len()-end_offset/* , offset) */]);
self.last_offset_set.insert(
FilialTuple {
child,
parent: Some(parent),
},
0,
);
}
// +2 because edge.line is 0 indexed but #line is 1 indexed and references the *following* line
add_closing_line_directive(edge.line + 2, &parent_path, parent, merge_list, extra_lines, source_mapper);
// if the next pair's parent is not the current pair's parent, we need to bubble up
if stack.contains(&next.1.unwrap()) {
self.add_closing_line_directive(edge.line + 2, &parent_path, parent, merge_list, extra_lines);
// we need to check the next item at the point of original return further down the callstack
if self.nodes_peeker.peek().is_some() && stack.contains(&self.nodes_peeker.peek().unwrap().parent.unwrap()) {
return;
}
continue;
}
stack.push_back(parent);
create_merge_views(
nodes,
merge_list,
last_offset_set,
graph,
sources,
extra_lines,
stack,
source_mapper,
);
stack.pop_back();
let offset = *last_offset_set.get(&(child, Some(parent))).unwrap();
let child_source = sources.get(&child_path).unwrap();
// this evaluates to false once the file contents have been exhausted aka offset = child_source.len() + 1
let end_offset = match child_source.ends_with('\n') {
true => 1, /* child_source.len()-1 */
false => 0, /* child_source.len() */
};
if offset < child_source.len() - end_offset {
None => {
let child_source = self.sources.get(&child_path).unwrap();
// if ends in \n\n, we want to exclude the last \n for some reason. Ask optilad
merge_list.push_back(&child_source[offset../* std::cmp::max( */child_source.len()-end_offset/* , offset) */]);
last_offset_set.insert((child, Some(parent)), 0);
let offset = match child_source.ends_with('\n') {
true => child_source.len() - 1,
false => child_source.len(),
};
merge_list.push_back(&child_source[..offset]);
self.last_offset_set.insert(
FilialTuple {
child,
parent: Some(parent),
},
0,
);
// +2 because edge.line is 0 indexed but #line is 1 indexed and references the *following* line
self.add_closing_line_directive(edge.line + 2, &parent_path, parent, merge_list, extra_lines);
}
// +2 because edge.line is 0 indexed but #line is 1 indexed and references the *following* line
add_closing_line_directive(edge.line + 2, &parent_path, parent, merge_list, extra_lines, source_mapper);
// we need to check the next item at the point of original return further down the callstack
if nodes.peek().is_some() && stack.contains(&nodes.peek().unwrap().1.unwrap()) {
return;
}
}
None => {
let child_source = sources.get(&child_path).unwrap();
// if ends in \n\n, we want to exclude the last \n for some reason. Ask optilad
let offset = match child_source.ends_with('\n') {
true => child_source.len() - 1,
false => child_source.len(),
};
merge_list.push_back(&child_source[..offset]);
last_offset_set.insert((child, Some(parent)), 0);
// +2 because edge.line is 0 indexed but #line is 1 indexed and references the *following* line
add_closing_line_directive(edge.line + 2, &parent_path, parent, merge_list, extra_lines, source_mapper);
}
}
}
}
// returns the character offset + 1 of the end of line number `line` and the character
// offset + 1 for the end of the line after the previous one
fn char_offset_for_line(line_num: usize, source: &str) -> (usize, usize) {
let mut char_for_line: usize = 0;
let mut char_following_line: usize = 0;
for (n, line) in source.lines().enumerate() {
if n == line_num {
char_following_line += line.len() + 1;
break;
// returns the character offset + 1 of the end of line number `line` and the character
// offset + 1 for the end of the line after the previous one
fn char_offset_for_line(&self, line_num: usize, source: &str) -> (usize, usize) {
let mut char_for_line: usize = 0;
let mut char_following_line: usize = 0;
for (n, line) in source.lines().enumerate() {
if n == line_num {
char_following_line += line.len() + 1;
break;
}
char_for_line += line.len() + 1;
char_following_line = char_for_line;
}
char_for_line += line.len() + 1;
char_following_line = char_for_line;
(char_for_line, char_following_line)
}
(char_for_line, char_following_line)
}
fn find_version_offset(source: &str) -> usize {
source
.lines()
.enumerate()
.find(|(_, line)| line.starts_with("#version "))
.map_or(0, |(i, _)| i)
}
fn find_version_offset(&self, source: &str) -> usize {
source
.lines()
.enumerate()
.find(|(_, line)| line.starts_with("#version "))
.map_or(0, |(i, _)| i)
}
// fn add_preamble<'a>(
// version_line_offset: usize, version_char_offset: usize, path: &Path, node: NodeIndex, source: &'a str,
// merge_list: &mut LinkedList<&'a str>, extra_lines: &mut Vec<String>, source_mapper: &mut SourceMapper,
// ) {
// // TODO: Optifine #define preabmle
// merge_list.push_back(&source[..version_char_offset]);
// let google_line_directive = format!(
// "#extension GL_GOOGLE_cpp_style_line_directive : enable\n#line {} {} // {}\n",
// // +2 because 0 indexed but #line is 1 indexed and references the *following* line
// version_line_offset + 2,
// source_mapper.get_num(node),
// path.to_str().unwrap().replace('\\', "\\\\"),
// );
// extra_lines.push(google_line_directive);
// unsafe_get_and_insert(merge_list, extra_lines);
// }
// fn add_preamble<'a>(
// version_line_offset: usize, version_char_offset: usize, path: &Path, node: NodeIndex, source: &'a str,
// merge_list: &mut LinkedList<&'a str>, extra_lines: &mut Vec<String>, source_mapper: &mut SourceMapper,
// ) {
// // TODO: Optifine #define preabmle
// merge_list.push_back(&source[..version_char_offset]);
// let google_line_directive = format!(
// "#extension GL_GOOGLE_cpp_style_line_directive : enable\n#line {} {} // {}\n",
// // +2 because 0 indexed but #line is 1 indexed and references the *following* line
// version_line_offset + 2,
// source_mapper.get_num(node),
// path.to_str().unwrap().replace('\\', "\\\\"),
// );
// extra_lines.push(google_line_directive);
// unsafe_get_and_insert(merge_list, extra_lines);
// }
fn add_opening_line_directive(
path: &Path, node: NodeIndex, merge_list: &mut LinkedList<&str>, extra_lines: &mut Vec<String>, source_mapper: &mut SourceMapper,
) {
let line_directive = format!(
"#line 1 {} // {}\n",
source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
);
extra_lines.push(line_directive);
unsafe_get_and_insert(merge_list, extra_lines);
}
fn add_opening_line_directive(
&mut self, path: &Path, node: NodeIndex, merge_list: &mut LinkedList<&str>, extra_lines: &mut Vec<String>,
) {
let line_directive = format!(
"#line 1 {} // {}\n",
self.source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
);
extra_lines.push(line_directive);
self.unsafe_get_and_insert(merge_list, extra_lines);
}
fn add_closing_line_directive(
line: usize, path: &Path, node: NodeIndex, merge_list: &mut LinkedList<&str>, extra_lines: &mut Vec<String>,
source_mapper: &mut SourceMapper,
) {
// Optifine doesn't seem to add a leading newline if the previous line was a #line directive
let line_directive = if let Some(l) = merge_list.back() {
if l.trim().starts_with("#line") {
format!(
"#line {} {} // {}\n",
line,
source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
)
fn add_closing_line_directive(
&mut self, line: usize, path: &Path, node: NodeIndex, merge_list: &mut LinkedList<&str>, extra_lines: &mut Vec<String>,
) {
// Optifine doesn't seem to add a leading newline if the previous line was a #line directive
let line_directive = if let Some(l) = merge_list.back() {
if l.trim().starts_with("#line") {
format!(
"#line {} {} // {}\n",
line,
self.source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
)
} else {
format!(
"\n#line {} {} // {}\n",
line,
self.source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
)
}
} else {
format!(
"\n#line {} {} // {}\n",
line,
source_mapper.get_num(node),
self.source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
)
};
extra_lines.push(line_directive);
self.unsafe_get_and_insert(merge_list, extra_lines);
}
fn unsafe_get_and_insert(&self, merge_list: &mut LinkedList<&str>, extra_lines: &[String]) {
// :^)
unsafe {
let vec_ptr_offset = extra_lines.as_ptr().add(extra_lines.len() - 1);
merge_list.push_back(&vec_ptr_offset.as_ref().unwrap()[..]);
}
} else {
format!(
"\n#line {} {} // {}\n",
line,
source_mapper.get_num(node),
path.to_str().unwrap().replace('\\', "\\\\")
)
};
extra_lines.push(line_directive);
unsafe_get_and_insert(merge_list, extra_lines);
}
fn unsafe_get_and_insert(merge_list: &mut LinkedList<&str>, extra_lines: &[String]) {
// :^)
unsafe {
let vec_ptr_offset = extra_lines.as_ptr().add(extra_lines.len() - 1);
merge_list.push_back(&vec_ptr_offset.as_ref().unwrap()[..]);
}
}
@ -287,7 +348,7 @@ mod merge_view_test {
use std::fs;
use std::path::PathBuf;
use crate::merge_views::generate_merge_list;
use crate::merge_views::MergeViewBuilder;
use crate::source_mapper::SourceMapper;
use crate::test::{copy_to_and_set_root, new_temp_server};
use crate::IncludePosition;
@ -300,16 +361,8 @@ mod merge_view_test {
let (_tmp_dir, tmp_path) = copy_to_and_set_root("./testdata/01", &mut server);
server.endpoint.request_shutdown();
let final_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{:?}/shaders/final.fsh", tmp_path).try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("final.fsh"));
let common_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{:?}/shaders/common.glsl", tmp_path).try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("common.glsl"));
let final_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("final.fsh"));
let common_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("common.glsl"));
server
.graph
@ -321,7 +374,7 @@ mod merge_view_test {
let graph_borrow = server.graph.borrow();
let mut source_mapper = SourceMapper::new(0);
let result = generate_merge_list(&nodes, &sources, &graph_borrow, &mut source_mapper);
let result = MergeViewBuilder::new(&nodes, &sources, &graph_borrow, &mut source_mapper).build();
let merge_file = tmp_path.join("shaders").join("final.fsh.merge");
@ -352,25 +405,18 @@ mod merge_view_test {
let (_tmp_dir, tmp_path) = copy_to_and_set_root("./testdata/02", &mut server);
server.endpoint.request_shutdown();
let final_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/{}", tmp_path, "final.fsh").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("final.fsh"));
let final_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("final.fsh"));
let test_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "test.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("test.glsl"));
let burger_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "burger.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("burger.glsl"));
let sample_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "sample.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("sample.glsl"));
server
@ -391,7 +437,7 @@ mod merge_view_test {
let graph_borrow = server.graph.borrow();
let mut source_mapper = SourceMapper::new(0);
let result = generate_merge_list(&nodes, &sources, &graph_borrow, &mut source_mapper);
let result = MergeViewBuilder::new(&nodes, &sources, &graph_borrow, &mut source_mapper).build();
let merge_file = tmp_path.join("shaders").join("final.fsh.merge");
@ -434,25 +480,18 @@ mod merge_view_test {
let (_tmp_dir, tmp_path) = copy_to_and_set_root("./testdata/03", &mut server);
server.endpoint.request_shutdown();
let final_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/{}", tmp_path, "final.fsh").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("final.fsh"));
let final_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("final.fsh"));
let test_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "test.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("test.glsl"));
let burger_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "burger.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("burger.glsl"));
let sample_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "sample.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("sample.glsl"));
server
@ -473,7 +512,7 @@ mod merge_view_test {
let graph_borrow = server.graph.borrow();
let mut source_mapper = SourceMapper::new(0);
let result = generate_merge_list(&nodes, &sources, &graph_borrow, &mut source_mapper);
let result = MergeViewBuilder::new(&nodes, &sources, &graph_borrow, &mut source_mapper).build();
let merge_file = tmp_path.join("shaders").join("final.fsh.merge");
@ -516,30 +555,22 @@ mod merge_view_test {
let (_tmp_dir, tmp_path) = copy_to_and_set_root("./testdata/04", &mut server);
server.endpoint.request_shutdown();
let final_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/{}", tmp_path, "final.fsh").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("final.fsh"));
let final_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("final.fsh"));
let utilities_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "utilities.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("utilities.glsl"));
let stuff1_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "stuff1.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("stuff1.glsl"));
let stuff2_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/utils/{}", tmp_path, "stuff2.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("utils").join("stuff2.glsl"));
let matrices_idx = server
.graph
.borrow_mut()
//.add_node(&format!("{}/shaders/lib/{}", tmp_path, "matrices.glsl").try_into().unwrap());
.add_node(&tmp_path.join("shaders").join("lib").join("matrices.glsl"));
server
@ -564,7 +595,7 @@ mod merge_view_test {
let graph_borrow = server.graph.borrow();
let mut source_mapper = SourceMapper::new(0);
let result = generate_merge_list(&nodes, &sources, &graph_borrow, &mut source_mapper);
let result = MergeViewBuilder::new(&nodes, &sources, &graph_borrow, &mut source_mapper).build();
let merge_file = tmp_path.join("shaders").join("final.fsh.merge");
@ -587,4 +618,49 @@ mod merge_view_test {
assert_eq!(result, truth);
}
#[test]
#[logging_macro::log_scope]
fn test_generate_merge_list_06() {
let mut server = new_temp_server(None);
let (_tmp_dir, tmp_path) = copy_to_and_set_root("./testdata/06", &mut server);
server.endpoint.request_shutdown();
let final_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("final.fsh"));
let test_idx = server.graph.borrow_mut().add_node(&tmp_path.join("shaders").join("test.glsl"));
server
.graph
.borrow_mut()
.add_edge(final_idx, test_idx, IncludePosition { line: 3, start: 0, end: 0 });
server
.graph
.borrow_mut()
.add_edge(final_idx, test_idx, IncludePosition { line: 5, start: 0, end: 0 });
let nodes = server.get_dfs_for_node(final_idx).unwrap();
let sources = server.load_sources(&nodes).unwrap();
let graph_borrow = server.graph.borrow();
let mut source_mapper = SourceMapper::new(0);
let result = MergeViewBuilder::new(&nodes, &sources, &graph_borrow, &mut source_mapper).build();
let merge_file = tmp_path.join("shaders").join("final.fsh.merge");
let mut truth = fs::read_to_string(merge_file).unwrap();
for file in &[
// PathBuf::new().join("final.fsh").to_str().unwrap(),
PathBuf::new().join("test.glsl").to_str().unwrap(),
PathBuf::new().join("final.fsh").to_str().unwrap(),
PathBuf::new().join("test.glsl").to_str().unwrap(),
PathBuf::new().join("final.fsh").to_str().unwrap(),
] {
let path = tmp_path.clone();
truth = truth.replacen("!!", &path.join("shaders").join(file).to_str().unwrap().replace('\\', "\\\\"), 1);
}
assert_eq!(result, truth);
}
}

View file

@ -28,7 +28,11 @@ impl SymbolName {
}
("struct_specifier", "field_declaration_list") => {
let struct_ident = node.child_by_field_name("name").unwrap();
fqname.push(format!("{}[{}]", struct_ident.utf8_text(source.as_bytes()).unwrap(), struct_ident.id()));
fqname.push(format!(
"{}[{}]",
struct_ident.utf8_text(source.as_bytes()).unwrap(),
struct_ident.id()
));
}
_ => (),
}

View file

@ -1,5 +1,5 @@
#version 120
#extension GL_GOOGLE_cpp_style_line_directive : enable
#line 2 "!!"
#line 1 "!!"

9
server/main/testdata/06/final.fsh vendored Normal file
View file

@ -0,0 +1,9 @@
#version 120
#ifdef BANANA
#include "test.glsl"
#else
#include "test.glsl"
#endif
void main() {}

17
server/main/testdata/06/final.fsh.merge vendored Normal file
View file

@ -0,0 +1,17 @@
#version 120
#ifdef BANANA
#line 1 1 // !!
int test() {
return 1;
}
#line 5 0 // !!
#else
#line 1 1 // !!
int test() {
return 1;
}
#line 7 0 // !!
#endif
void main() {}

3
server/main/testdata/06/test.glsl vendored Normal file
View file

@ -0,0 +1,3 @@
int test() {
return 1;
}