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 * as vscode from 'vscode';
import { inspect } from 'util'
import * as vscode from 'vscode'
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::Direction;
use petgraph::stable_graph::EdgeIndex;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use super::IncludePosition;
/// 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
// for later
let n = self.graph.node_indices().find(|n| self.graph[*n] == name_str);
if n.is_some() {
self.cache.insert(name_str, n.unwrap());
if let Some(n) = n {
self.cache.insert(name_str, 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>) {
let idx = self.cache.remove(&name.into());
if idx.is_some() {
self.graph.remove_node(idx.unwrap());
if let Some(idx) = idx {
self.graph.remove_node(idx);
}
}
@ -67,6 +74,10 @@ impl CachedStableGraph {
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> {
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> {
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_types::{*, notification::*};
use walkdir;
use petgraph::stable_graph::NodeIndex;
use walkdir::WalkDir;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::{HashMap, LinkedList};
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::io::{stdin, stdout, BufRead, BufReader, Write};
@ -13,6 +15,8 @@ use std::ops::Add;
use std::process;
use std::rc::Rc;
use core::cmp::{Ordering, PartialOrd, PartialEq, Ord, Eq};
use anyhow::Result;
use chan::WaitGroup;
@ -37,14 +41,14 @@ lazy_static! {
}
#[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() {
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();
@ -97,11 +101,11 @@ impl Configuration {
return false;
}
return true;
true
}
}
#[derive(Clone)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct IncludePosition {
filepath: String,
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 {
pub fn error_not_available<DATA>(data: DATA) -> MethodError<DATA> {
let msg = "Functionality not implemented.".to_string();
@ -131,10 +155,10 @@ impl MinecraftShaderLanguageServer {
eprintln!("root of project is {}", root);
// 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()
.filter_map(|entry| {
if !entry.is_ok() {
if entry.is_err() {
return None;
}
@ -165,10 +189,7 @@ impl MinecraftShaderLanguageServer {
let idx = self.graph.borrow_mut().add_node(path.clone());
//eprintln!("adding {} with\n{:?}", path.clone(), includes);
struct GLSLFile {
idx: petgraph::graph::NodeIndex,
includes: Vec<IncludePosition>,
}
files.insert(path, GLSLFile { idx, includes });
}
@ -219,12 +240,12 @@ impl MinecraftShaderLanguageServer {
let start = u64::try_from(cap.start()).unwrap();
let end = u64::try_from(cap.end()).unwrap();
let mut path: String = cap.as_str().into();
if !path.starts_with("/") {
if !path.starts_with('/') {
path.insert(0, '/');
}
let full_include = String::from(root).add("/shaders").add(path.as_str());
includes.push(IncludePosition {
filepath: full_include.clone(),
filepath: full_include,
line: u64::try_from(line.0).unwrap(),
start,
end,
@ -232,13 +253,38 @@ impl MinecraftShaderLanguageServer {
//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());
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 = stdout.trim();
eprintln!("glslangValidator output: {}\n", stdout);
@ -246,7 +292,7 @@ impl MinecraftShaderLanguageServer {
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) {
Some(d) => d,
None => return
@ -299,7 +345,32 @@ impl MinecraftShaderLanguageServer {
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();
eprintln!("validator bin path: {}", 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 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 stdout = String::from_utf8(output.stdout).unwrap();
@ -364,8 +435,8 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
},
));
let root_path = match params.root_uri {
Some(uri) => String::from(uri.path()),
let root_path: String = match params.root_uri {
Some(uri) => uri.path().into(),
None => {
completable.complete(Err(MethodError {
code: 42069,
@ -385,7 +456,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
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(),
Err(e) => {
self.set_status("failed", format!("{}", e), "$(close)");
@ -433,9 +504,9 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
fn did_open_text_document(&mut self, params: DidOpenTextDocumentParams) {
eprintln!("opened doc {}", params.text_document.uri);
match self.lint(params.text_document.text) {
Ok(diagnostics) => self.publish_diagnostic(diagnostics, params.text_document.uri, Some(params.text_document.version)),
_ => return
match self.lint(params.text_document.uri.path(), params.text_document.text) {
Ok(diagnostics) => self.publish_diagnostic(diagnostics, params.text_document.uri, None),
Err(e) => eprintln!("error linting: {}", e),
}
}
@ -450,14 +521,14 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
fn did_close_text_document(&mut self, _: DidCloseTextDocumentParams) {}
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 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),
_ => 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>) {
params
.arguments
.push(serde_json::Value::String(self.root.clone().unwrap()));
.push(serde_json::Value::String(self.root.as_ref().unwrap().into()));
match self
.command_provider
.as_ref()
@ -590,7 +661,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
Position::new(value.line, value.start),
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: None,
data: None,

View file

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