include graph ancestor collection and merge-list creation

This commit is contained in:
Noah Santschi-Cooney 2020-07-19 23:58:13 +01:00
parent 346e9b0f9f
commit fa81620027
No known key found for this signature in database
GPG key ID: 3B22282472C8AE48
4 changed files with 165 additions and 44 deletions

View file

@ -1,5 +1,5 @@
import { inspect } from 'util'; import { inspect } from 'util'
import * as vscode from 'vscode'; import * as vscode from 'vscode'
export const lspOutputChannel = vscode.window.createOutputChannel('Minecraft Shaders Language Server') export const lspOutputChannel = vscode.window.createOutputChannel('Minecraft Shaders Language Server')

View file

@ -2,7 +2,10 @@ use petgraph::stable_graph::StableDiGraph;
use petgraph::stable_graph::NodeIndex; use petgraph::stable_graph::NodeIndex;
use petgraph::Direction; use petgraph::Direction;
use petgraph::stable_graph::EdgeIndex; use petgraph::stable_graph::EdgeIndex;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use super::IncludePosition; use super::IncludePosition;
/// Wraps a `StableDiGraph` with caching behaviour for node search by maintaining /// Wraps a `StableDiGraph` with caching behaviour for node search by maintaining
@ -39,18 +42,22 @@ impl CachedStableGraph {
// If the string is not in cache, O(n) search the graph (i know...) and then cache the NodeIndex // If the string is not in cache, O(n) search the graph (i know...) and then cache the NodeIndex
// for later // for later
let n = self.graph.node_indices().find(|n| self.graph[*n] == name_str); let n = self.graph.node_indices().find(|n| self.graph[*n] == name_str);
if n.is_some() { if let Some(n) = n {
self.cache.insert(name_str, n.unwrap()); self.cache.insert(name_str, n);
} }
n n
} }
} }
} }
/* pub fn get_node(&self, node: NodeIndex) -> &IncludePosition {
self.graph.node_weight(node).expect("node index not found in graph")
} */
pub fn remove_node(&mut self, name: impl Into<String>) { pub fn remove_node(&mut self, name: impl Into<String>) {
let idx = self.cache.remove(&name.into()); let idx = self.cache.remove(&name.into());
if idx.is_some() { if let Some(idx) = idx {
self.graph.remove_node(idx.unwrap()); self.graph.remove_node(idx);
} }
} }
@ -67,6 +74,10 @@ impl CachedStableGraph {
self.graph.add_edge(parent, child, IncludePosition{filepath: child_path, line, start, end}) self.graph.add_edge(parent, child, IncludePosition{filepath: child_path, line, start, end})
} }
pub fn edge_weights(&self, node: NodeIndex) -> Vec<IncludePosition> {
self.graph.edges(node).map(|e| e.weight().clone()).collect()
}
pub fn child_node_names(&self, node: NodeIndex) -> Vec<String> { pub fn child_node_names(&self, node: NodeIndex) -> Vec<String> {
self.graph.neighbors(node).map(|n| self.reverse_index.get(&n).unwrap().clone()).collect() self.graph.neighbors(node).map(|n| self.reverse_index.get(&n).unwrap().clone()).collect()
} }
@ -84,6 +95,38 @@ impl CachedStableGraph {
} }
pub fn get_include_meta(&self, node: NodeIndex) -> Vec<IncludePosition> { pub fn get_include_meta(&self, node: NodeIndex) -> Vec<IncludePosition> {
self.graph.edges(node).into_iter().map(|e| e.weight().clone()).collect() self.graph.edges(node).map(|e| e.weight().clone()).collect()
}
pub fn collect_root_ancestors(&self, node: NodeIndex) -> Vec<NodeIndex> {
let mut visited = HashSet::new();
//visited.insert(node);
self.get_root_ancestors(node, node, &mut visited)
}
fn get_root_ancestors(&self, initial: NodeIndex, node: NodeIndex, visited: &mut HashSet<NodeIndex>) -> Vec<NodeIndex> {
if visited.contains(&node) {
return vec![node];
} else if node == initial && !visited.is_empty() {
return vec![];
}
let parents = Rc::new(self.parent_node_indexes(node));
let mut collection = Vec::with_capacity(parents.len());
for ancestor in parents.as_ref() {
visited.insert(*ancestor);
}
for ancestor in parents.as_ref() {
let ancestors = self.parent_node_indexes(*ancestor);
if !ancestors.is_empty() {
collection.extend(self.get_root_ancestors(initial, *ancestor, visited));
} else {
collection.push(*ancestor);
}
}
collection
} }
} }

View file

@ -2,10 +2,12 @@ use rust_lsp::jsonrpc::{*, method_types::*};
use rust_lsp::lsp::*; use rust_lsp::lsp::*;
use rust_lsp::lsp_types::{*, notification::*}; use rust_lsp::lsp_types::{*, notification::*};
use walkdir; use petgraph::stable_graph::NodeIndex;
use walkdir::WalkDir;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::{HashMap, LinkedList};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::io::{stdin, stdout, BufRead, BufReader, Write}; use std::io::{stdin, stdout, BufRead, BufReader, Write};
@ -13,6 +15,8 @@ use std::ops::Add;
use std::process; use std::process;
use std::rc::Rc; use std::rc::Rc;
use core::cmp::{Ordering, PartialOrd, PartialEq, Ord, Eq};
use anyhow::Result; use anyhow::Result;
use chan::WaitGroup; use chan::WaitGroup;
@ -37,14 +41,14 @@ lazy_static! {
} }
#[allow(dead_code)] #[allow(dead_code)]
static INCLUDE_STR: &'static str = "#extension GL_GOOGLE_include_directive : require"; static INCLUDE_STR: &str = "#extension GL_GOOGLE_include_directive : require";
static SOURCE: &'static str = "mc-glsl"; static SOURCE: &str = "mc-glsl";
fn main() { fn main() {
let stdin = stdin(); let stdin = stdin();
let endpoint_output = LSPEndpoint::create_lsp_output_with_output_stream(|| stdout()); let endpoint_output = LSPEndpoint::create_lsp_output_with_output_stream(stdout);
let cache_graph = graph::CachedStableGraph::new(); let cache_graph = graph::CachedStableGraph::new();
@ -97,11 +101,11 @@ impl Configuration {
return false; return false;
} }
return true; true
} }
} }
#[derive(Clone)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct IncludePosition { pub struct IncludePosition {
filepath: String, filepath: String,
line: u64, line: u64,
@ -115,6 +119,26 @@ impl Display for IncludePosition {
} }
} }
impl PartialOrd for IncludePosition {
#[allow(clippy::comparison_chain)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.line < other.line { Some(Ordering::Less) }
else if self.line > other.line { Some(Ordering::Greater) }
else { Some(Ordering::Equal) }
}
}
impl Ord for IncludePosition {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
struct GLSLFile {
idx: petgraph::graph::NodeIndex,
includes: Vec<IncludePosition>,
}
impl MinecraftShaderLanguageServer { impl MinecraftShaderLanguageServer {
pub fn error_not_available<DATA>(data: DATA) -> MethodError<DATA> { pub fn error_not_available<DATA>(data: DATA) -> MethodError<DATA> {
let msg = "Functionality not implemented.".to_string(); let msg = "Functionality not implemented.".to_string();
@ -131,10 +155,10 @@ impl MinecraftShaderLanguageServer {
eprintln!("root of project is {}", root); eprintln!("root of project is {}", root);
// filter directories and files not ending in any of the 3 extensions // filter directories and files not ending in any of the 3 extensions
let file_iter = walkdir::WalkDir::new(root) let file_iter = WalkDir::new(root)
.into_iter() .into_iter()
.filter_map(|entry| { .filter_map(|entry| {
if !entry.is_ok() { if entry.is_err() {
return None; return None;
} }
@ -165,10 +189,7 @@ impl MinecraftShaderLanguageServer {
let idx = self.graph.borrow_mut().add_node(path.clone()); let idx = self.graph.borrow_mut().add_node(path.clone());
//eprintln!("adding {} with\n{:?}", path.clone(), includes); //eprintln!("adding {} with\n{:?}", path.clone(), includes);
struct GLSLFile {
idx: petgraph::graph::NodeIndex,
includes: Vec<IncludePosition>,
}
files.insert(path, GLSLFile { idx, includes }); files.insert(path, GLSLFile { idx, includes });
} }
@ -219,12 +240,12 @@ impl MinecraftShaderLanguageServer {
let start = u64::try_from(cap.start()).unwrap(); let start = u64::try_from(cap.start()).unwrap();
let end = u64::try_from(cap.end()).unwrap(); let end = u64::try_from(cap.end()).unwrap();
let mut path: String = cap.as_str().into(); let mut path: String = cap.as_str().into();
if !path.starts_with("/") { if !path.starts_with('/') {
path.insert(0, '/'); path.insert(0, '/');
} }
let full_include = String::from(root).add("/shaders").add(path.as_str()); let full_include = String::from(root).add("/shaders").add(path.as_str());
includes.push(IncludePosition { includes.push(IncludePosition {
filepath: full_include.clone(), filepath: full_include,
line: u64::try_from(line.0).unwrap(), line: u64::try_from(line.0).unwrap(),
start, start,
end, end,
@ -232,13 +253,38 @@ impl MinecraftShaderLanguageServer {
//eprintln!("{} includes {}", file, full_include); //eprintln!("{} includes {}", file, full_include);
}); });
return includes; includes
} }
pub fn lint(&self, source: impl Into<String>) -> Result<Vec<Diagnostic>> { pub fn lint(&self, uri: &str, source: impl Into<String>) -> Result<Vec<Diagnostic>> {
// get all top level ancestors of this file
let file_ancestors = match self.get_file_toplevel_ancestors(uri) {
Ok(opt) => match opt {
Some(ancestors) => ancestors,
None => vec![],
},
Err(e) => return Err(e),
};
let source: Rc<String> = Rc::new(source.into()); let source: Rc<String> = Rc::new(source.into());
eprintln!("ancestors for {}:\n{:?}", uri, file_ancestors.iter().map(|e| self.graph.borrow().graph.node_weight(*e).unwrap().clone()).collect::<Vec<String>>());
let merge_list: LinkedList<Box<&str>> = if file_ancestors.is_empty() {
let mut list = LinkedList::new();
list.push_back(Box::new(source.as_str()));
list
} else {
for root in file_ancestors {
self.generate_merge_list(root);
}
LinkedList::new()
};
// run merged source through validator
let stdout = self.invoke_validator(source.as_ref())?; let stdout = self.invoke_validator(source.as_ref())?;
let stdout = stdout.trim();
eprintln!("glslangValidator output: {}\n", stdout); eprintln!("glslangValidator output: {}\n", stdout);
@ -246,7 +292,7 @@ impl MinecraftShaderLanguageServer {
let source_lines: Vec<&str> = source.split('\n').collect(); let source_lines: Vec<&str> = source.split('\n').collect();
stdout.split('\n').into_iter().for_each(|line| { stdout.split('\n').for_each(|line| {
let diagnostic_capture = match RE_DIAGNOSTIC.captures(line) { let diagnostic_capture = match RE_DIAGNOSTIC.captures(line) {
Some(d) => d, Some(d) => d,
None => return None => return
@ -299,7 +345,32 @@ impl MinecraftShaderLanguageServer {
Ok(diagnostics) Ok(diagnostics)
} }
fn invoke_validator(&self, source: impl Into<String>) -> Result<String> { fn get_file_toplevel_ancestors(&self, uri: &str) -> Result<Option<Vec<petgraph::stable_graph::NodeIndex>>> {
let curr_node = match self.graph.borrow_mut().find_node(uri) {
Some(n) => n,
None => return Err(anyhow::format_err!("node not found")),
};
let roots = self.graph.borrow().collect_root_ancestors(curr_node);
if roots.is_empty() {
return Ok(None);
}
Ok(Some(roots))
}
fn generate_merge_list(&self, root: NodeIndex) -> LinkedList<Box<&str>> {
let mut merge_list = LinkedList::new();
let children = self.graph.borrow().child_node_indexes(root);
// include positions sorted by earliest in file
let mut all_edges: Vec<IncludePosition> = self.graph.borrow().edge_weights(root);
all_edges.sort();
eprintln!("include positions for {:?}: {:?} {:?}", self.graph.borrow().graph.node_weight(root).unwrap(), all_edges, children);
merge_list
}
fn invoke_validator(&self, source: &str) -> Result<String> {
let source: String = source.into(); let source: String = source.into();
eprintln!("validator bin path: {}", self.config.glslang_validator_path); eprintln!("validator bin path: {}", self.config.glslang_validator_path);
let cmd = process::Command::new(&self.config.glslang_validator_path) let cmd = process::Command::new(&self.config.glslang_validator_path)
@ -310,7 +381,7 @@ impl MinecraftShaderLanguageServer {
let mut child = cmd?;//.expect("glslangValidator failed to spawn"); let mut child = cmd?;//.expect("glslangValidator failed to spawn");
let stdin = child.stdin.as_mut().expect("no stdin handle found"); let stdin = child.stdin.as_mut().expect("no stdin handle found");
stdin.write(source.as_bytes())?;//.expect("failed to write to stdin"); stdin.write_all(source.as_bytes())?;//.expect("failed to write to stdin");
let output = child.wait_with_output()?;//.expect("expected output"); let output = child.wait_with_output()?;//.expect("expected output");
let stdout = String::from_utf8(output.stdout).unwrap(); let stdout = String::from_utf8(output.stdout).unwrap();
@ -364,8 +435,8 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
}, },
)); ));
let root_path = match params.root_uri { let root_path: String = match params.root_uri {
Some(uri) => String::from(uri.path()), Some(uri) => uri.path().into(),
None => { None => {
completable.complete(Err(MethodError { completable.complete(Err(MethodError {
code: 42069, code: 42069,
@ -385,7 +456,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
self.set_status("loading", "Building dependency graph...", "$(loading~spin)"); self.set_status("loading", "Building dependency graph...", "$(loading~spin)");
let root: String = match percent_decode_str(root_path.as_str()).decode_utf8() { let root: String = match percent_decode_str(root_path.as_ref()).decode_utf8() {
Ok(s) => s.into(), Ok(s) => s.into(),
Err(e) => { Err(e) => {
self.set_status("failed", format!("{}", e), "$(close)"); self.set_status("failed", format!("{}", e), "$(close)");
@ -433,9 +504,9 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
fn did_open_text_document(&mut self, params: DidOpenTextDocumentParams) { fn did_open_text_document(&mut self, params: DidOpenTextDocumentParams) {
eprintln!("opened doc {}", params.text_document.uri); eprintln!("opened doc {}", params.text_document.uri);
match self.lint(params.text_document.text) { match self.lint(params.text_document.uri.path(), params.text_document.text) {
Ok(diagnostics) => self.publish_diagnostic(diagnostics, params.text_document.uri, Some(params.text_document.version)), Ok(diagnostics) => self.publish_diagnostic(diagnostics, params.text_document.uri, None),
_ => return Err(e) => eprintln!("error linting: {}", e),
} }
} }
@ -450,14 +521,14 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
fn did_close_text_document(&mut self, _: DidCloseTextDocumentParams) {} fn did_close_text_document(&mut self, _: DidCloseTextDocumentParams) {}
fn did_save_text_document(&mut self, params: DidSaveTextDocumentParams) { fn did_save_text_document(&mut self, params: DidSaveTextDocumentParams) {
self.wait.wait(); eprintln!("saved doc {}", params.text_document.uri);
let path: String = percent_encoding::percent_decode_str(params.text_document.uri.path()).decode_utf8().unwrap().into(); let path: String = percent_encoding::percent_decode_str(params.text_document.uri.path()).decode_utf8().unwrap().into();
let file_content = std::fs::read(path).unwrap(); let file_content = std::fs::read(path).unwrap();
match self.lint(String::from_utf8(file_content).unwrap()) { match self.lint(params.text_document.uri.path(), String::from_utf8(file_content).unwrap()) {
Ok(diagnostics) => self.publish_diagnostic(diagnostics, params.text_document.uri, None), Ok(diagnostics) => self.publish_diagnostic(diagnostics, params.text_document.uri, None),
_ => return Err(e) => eprintln!("error linting: {}", e),
} }
} }
@ -485,7 +556,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
fn execute_command(&mut self, mut params: ExecuteCommandParams, completable: LSCompletable<WorkspaceEdit>) { fn execute_command(&mut self, mut params: ExecuteCommandParams, completable: LSCompletable<WorkspaceEdit>) {
params params
.arguments .arguments
.push(serde_json::Value::String(self.root.clone().unwrap())); .push(serde_json::Value::String(self.root.as_ref().unwrap().into()));
match self match self
.command_provider .command_provider
.as_ref() .as_ref()
@ -590,7 +661,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
Position::new(value.line, value.start), Position::new(value.line, value.start),
Position::new(value.line, value.end), Position::new(value.line, value.end),
), ),
target: Some(url.clone()), target: Some(url),
//tooltip: Some(url.path().to_string().strip_prefix(self.root.clone().unwrap().as_str()).unwrap().to_string()), //tooltip: Some(url.path().to_string().strip_prefix(self.root.clone().unwrap().as_str()).unwrap().to_string()),
tooltip: None, tooltip: None,
data: None, data: None,

View file

@ -4,7 +4,7 @@ use std::io::Result;
use tempdir::TempDir; use tempdir::TempDir;
use std::fs; use std::fs;
use fs_extra; use fs_extra::{dir, copy_items};
use jsonrpc_common::*; use jsonrpc_common::*;
use jsonrpc_response::*; use jsonrpc_response::*;
@ -16,9 +16,9 @@ struct StdoutNewline {
impl io::Write for StdoutNewline { impl io::Write for StdoutNewline {
fn write(&mut self, buf: &[u8]) -> Result<usize> { fn write(&mut self, buf: &[u8]) -> Result<usize> {
let res = self.s.write(buf); let res = self.s.write(buf);
if buf[buf.len()-1] == "}".as_bytes()[0] { if buf[buf.len()-1] == b"}"[0] {
#[allow(unused_variables)] #[allow(unused_variables)]
let res = self.s.write("\n\n".as_bytes()); let res = self.s.write(b"\n\n");
} }
res res
} }
@ -42,9 +42,9 @@ fn new_temp_server() -> MinecraftShaderLanguageServer {
} }
fn copy_files(files: &str, dest: &TempDir) { fn copy_files(files: &str, dest: &TempDir) {
let opts = &fs_extra::dir::CopyOptions::new(); let opts = &dir::CopyOptions::new();
let files = fs::read_dir(files).unwrap().map(|e| String::from(e.unwrap().path().as_os_str().to_str().unwrap())).collect::<Vec<String>>(); let files = fs::read_dir(files).unwrap().map(|e| String::from(e.unwrap().path().as_os_str().to_str().unwrap())).collect::<Vec<String>>();
fs_extra::copy_items(&files, dest.path().join("shaders"), opts).unwrap(); copy_items(&files, dest.path().join("shaders"), opts).unwrap();
} }
#[allow(deprecated)] #[allow(deprecated)]
@ -168,6 +168,13 @@ fn test_graph_two_connected_nodes() {
assert_eq!(parents.len(), 1); assert_eq!(parents.len(), 1);
assert_eq!(parents[0], idx1); assert_eq!(parents[0], idx1);
let ancestors = graph.collect_root_ancestors(idx2);
assert_eq!(ancestors.len(), 1);
assert_eq!(ancestors[0], idx1);
let ancestors = graph.collect_root_ancestors(idx1);
assert_eq!(ancestors.len(), 0);
graph.remove_node("sample"); graph.remove_node("sample");
assert_eq!(graph.graph.node_count(), 1); assert_eq!(graph.graph.node_count(), 1);
assert!(graph.find_node("sample").is_none()); assert!(graph.find_node("sample").is_none());