Add go-to-def for local vars and their properties

This commit is contained in:
Tad Hardesty 2018-07-27 00:07:42 -07:00
parent 3f8d225d6e
commit ed49883cae
5 changed files with 70 additions and 32 deletions

1
Cargo.lock generated
View file

@ -144,6 +144,7 @@ dependencies = [
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"dreammaker 0.1.0",
"git2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"interval-tree 0.8.0",
"jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"languageserver-types 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
"petgraph 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -16,6 +16,7 @@ pub enum Annotation {
Variable(Vec<String>),
ProcHeader(Vec<String>),
ProcBody(Vec<String>),
LocalVarScope(VarType, String),
// local information about a specific token
UnscopedCall(String),

View file

@ -761,7 +761,8 @@ impl<'ctx, 'an, I> Parser<'ctx, 'an, I> where
/// Parse a block
fn block(&mut self) -> Status<Vec<Statement>> {
if let Some(()) = self.exact(Token::Punct(Punctuation::LBrace))? {
let mut vars = Vec::new();
let result = if let Some(()) = self.exact(Token::Punct(Punctuation::LBrace))? {
let mut statements = Vec::new();
loop {
if let Some(()) = self.exact(Token::Punct(Punctuation::RBrace))? {
@ -769,21 +770,25 @@ impl<'ctx, 'an, I> Parser<'ctx, 'an, I> where
} else if let Some(()) = self.exact(Token::Punct(Punctuation::Semicolon))? {
continue;
} else {
statements.push(require!(self.statement()));
statements.push(require!(self.statement(&mut vars)));
}
}
success(statements)
statements
} else if let Some(()) = self.exact(Token::Punct(Punctuation::Semicolon))? {
// empty blocks: proc/foo();
return success(Vec::new());
Vec::new()
} else {
// and one-line blocks: if(1) neat();
let statement = require!(self.statement());
return success(vec![statement]);
let statement = require!(self.statement(&mut vars));
vec![statement]
};
for (loc, var_type, name) in vars {
self.annotate(loc, || Annotation::LocalVarScope(var_type, name));
}
success(result)
}
fn statement(&mut self) -> Status<Statement> {
fn statement(&mut self, vars: &mut Vec<(Location, VarType, String)>) -> Status<Statement> {
// BLOCK STATEMENTS
if let Some(()) = self.exact_ident("if")? {
// statement :: 'if' '(' expression ')' block ('else' 'if' '(' expression ')' block)* ('else' block)?
@ -829,12 +834,12 @@ impl<'ctx, 'an, I> Parser<'ctx, 'an, I> where
// for (Var in Low to High)
// for (Var = Low to High)
require!(self.exact(Token::Punct(Punctuation::LParen)));
let init = self.simple_statement(true)?;
let init = self.simple_statement(true, vars)?;
if let Some(()) = self.comma_or_semicolon()? {
// three-pronged loop form ("for loop")
let test = self.expression()?;
require!(self.comma_or_semicolon());
let inc = self.simple_statement(false)?;
let inc = self.simple_statement(false, vars)?;
require!(self.exact(Token::Punct(Punctuation::RParen)));
success(Statement::ForLoop {
init: init.map(Box::new),
@ -934,14 +939,14 @@ impl<'ctx, 'an, I> Parser<'ctx, 'an, I> where
// TODO: warn on weird values for these
success(Statement::Setting(name, mode, value))
} else {
let result = leading!(self.simple_statement(false));
let result = leading!(self.simple_statement(false, vars));
require!(self.exact(Token::Punct(Punctuation::Semicolon)));
success(result)
}
}
// Single-line statements. Can appear in for loops. Followed by a semicolon.
fn simple_statement(&mut self, in_for: bool) -> Status<Statement> {
fn simple_statement(&mut self, in_for: bool, vars: &mut Vec<(Location, VarType, String)>) -> Status<Statement> {
if let Some(()) = self.exact_ident("var")? {
// statement :: 'var' type_path name ('=' value)
let type_path_start = self.location();
@ -959,6 +964,10 @@ impl<'ctx, 'an, I> Parser<'ctx, 'an, I> where
.set_severity(Severity::Warning));
}
if self.annotations.is_some() {
vars.push((self.location, var_type.clone(), name.clone()));
}
let value = if let Some(()) = self.exact(Token::Punct(Punctuation::Assign))? {
Some(require!(self.expression()))
} else {

View file

@ -15,6 +15,7 @@ serde_derive = "1.0.27"
jsonrpc-core = "8.0.1"
languageserver-types = "0.41"
dreammaker = { path = "../dreammaker" }
interval-tree = { path = "../interval-tree" }
petgraph = { version = "0.4.9", default-features = false }
[build-dependencies]

View file

@ -12,6 +12,7 @@ extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
extern crate petgraph;
extern crate interval_tree;
extern crate languageserver_types as langserver;
extern crate jsonrpc_core as jsonrpc;
extern crate dreammaker as dm;
@ -69,6 +70,8 @@ enum InitStatus {
ShuttingDown,
}
type Span = interval_tree::RangeInclusive<dm::Location>;
struct Engine<'a, R: 'a, W: 'a> {
read: &'a R,
write: &'a W,
@ -82,7 +85,7 @@ struct Engine<'a, R: 'a, W: 'a> {
preprocessor: Option<dm::preprocessor::Preprocessor<'a>>,
objtree: dm::objtree::ObjectTree,
annotations: HashMap<PathBuf, (FileId, Rc<AnnotationTree>)>,
annotations: HashMap<PathBuf, (FileId, FileId, Rc<AnnotationTree>)>,
}
impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
@ -241,7 +244,7 @@ impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
Ok(())
}
fn get_annotations(&mut self, path: &Path) -> Result<(FileId, Rc<AnnotationTree>), jsonrpc::Error> {
fn get_annotations(&mut self, path: &Path) -> Result<(FileId, FileId, Rc<AnnotationTree>), jsonrpc::Error> {
Ok(match self.annotations.entry(path.to_owned()) {
Entry::Occupied(o) => o.get().clone(),
Entry::Vacant(v) => {
@ -253,12 +256,12 @@ impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
Some(ref pp) => pp,
None => return Err(invalid_request("no preprocessor")),
};
let file_id = match self.context.get_file(&stripped) {
let real_file_id = match self.context.get_file(&stripped) {
Some(id) => id,
None => return Err(invalid_request(format!("unregistered: {}", stripped.display()))),
};
let context = Default::default();
let mut preprocessor = preprocessor.branch_at_file(file_id, &context);
let mut preprocessor = preprocessor.branch_at_file(real_file_id, &context);
let contents = self.docs.read(path).map_err(invalid_request)?;
let file_id = preprocessor.push_file(stripped.to_owned(), contents);
let indent = dm::indents::IndentProcessor::new(&context, preprocessor);
@ -268,7 +271,7 @@ impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
parser.annotate_to(&mut annotations);
parser.run();
}
v.insert((file_id, Rc::new(annotations))).clone()
v.insert((real_file_id, file_id, Rc::new(annotations))).clone()
}
})
}
@ -309,7 +312,19 @@ impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
(found, proc_name)
}
fn find_unscoped_var<'b>(&'b self, ty: Option<TypeRef<'b>>, proc_name: Option<&'b str>, var_name: &str) -> UnscopedVar<'b> {
fn find_unscoped_var<'b, I>(&'b self, iter: &I, ty: Option<TypeRef<'b>>, proc_name: Option<&'b str>, var_name: &str) -> UnscopedVar<'b>
where I: Iterator<Item=(Span, &'b Annotation)> + Clone
{
// local variables
for (span, annotation) in iter.clone() {
if let Annotation::LocalVarScope(var_type, name) = annotation {
if name == var_name {
return UnscopedVar::Local { loc: span.start, var_type }
}
}
}
// proc parameters
let ty = ty.unwrap_or(self.objtree.root());
if let Some(proc_name) = proc_name {
if let Some(proc) = ty.get().procs.get(proc_name) {
@ -320,6 +335,8 @@ impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
}
}
}
// type variables (implicit `src.` and `globals.`)
let mut next = Some(ty);
while let Some(ty) = next {
if let Some(var) = ty.get().vars.get(var_name) {
@ -330,26 +347,30 @@ impl<'a, R: io::RequestRead, W: io::ResponseWrite> Engine<'a, R, W> {
UnscopedVar::None
}
fn find_scoped_type<'b>(&'b self, mut next: Option<TypeRef<'b>>, proc_name: Option<&str>, priors: &[String]) -> Option<TypeRef<'b>> {
fn find_scoped_type<'b, I>(&'b self, iter: &I, priors: &[String]) -> Option<TypeRef<'b>>
where I: Iterator<Item=(Span, &'b Annotation)> + Clone
{
let (mut next, proc_name) = self.find_type_context(iter);
// find the first; check the global scope, parameters, and "src"
let mut iter = priors.iter();
let first = match iter.next() {
let mut priors = priors.iter();
let first = match priors.next() {
Some(i) => i,
None => return None,
};
if first != "src" {
next = match self.find_unscoped_var(next, proc_name, first) {
next = match self.find_unscoped_var(iter, next, proc_name, first) {
UnscopedVar::Parameter { param, .. } => self.objtree.type_by_path(&param.path),
UnscopedVar::Variable { ty, .. } => match ty.get_declaration(first) {
Some(decl) => self.objtree.type_by_path(&decl.var_type.type_path),
None => None,
}
},
UnscopedVar::Local { var_type, .. } => self.objtree.type_by_path(&var_type.type_path),
UnscopedVar::None => None,
};
}
// find the rest; only look on the type we've found
for var_name in iter {
for var_name in priors {
if let Some(current) = next.take() {
if let Some(decl) = current.get_declaration(var_name) {
next = self.objtree.type_by_path(&decl.var_type.type_path);
@ -529,7 +550,7 @@ handle_method_call! {
on HoverRequest(&mut self, params) {
let path = url_to_path(params.text_document.uri)?;
let (file_id, annotations) = self.get_annotations(&path)?;
let (_, file_id, annotations) = self.get_annotations(&path)?;
let location = dm::Location {
file: file_id,
line: params.position.line as u32 + 1,
@ -663,7 +684,7 @@ handle_method_call! {
on GotoDefinition(&mut self, params) {
let path = url_to_path(params.text_document.uri)?;
let (file_id, annotations) = self.get_annotations(&path)?;
let (real_file_id, file_id, annotations) = self.get_annotations(&path)?;
let location = dm::Location {
file: file_id,
line: params.position.line as u32 + 1,
@ -765,20 +786,22 @@ handle_method_call! {
if_annotation! { Annotation::UnscopedVar(var_name) in iter; {
let (ty, proc_name) = self.find_type_context(&iter);
match self.find_unscoped_var(ty, proc_name, var_name) {
match self.find_unscoped_var(&iter, ty, proc_name, var_name) {
UnscopedVar::Parameter { ty, proc, param } => {
results.push(self.convert_location(param.location, &ty.path, "/proc/", proc)?);
}
},
UnscopedVar::Variable { ty, var } => {
results.push(self.convert_location(var.value.location, &ty.path, "/var/", var_name)?);
}
},
UnscopedVar::Local { loc, .. } => {
results.push(self.convert_location(dm::Location { file: real_file_id, ..loc }, "", "", "")?);
},
UnscopedVar::None => {}
}
}}
if_annotation! { Annotation::ScopedCall(priors, proc_name) in iter; {
let (ty, proc_ctx) = self.find_type_context(&iter);
let mut next = self.find_scoped_type(ty, proc_ctx, priors);
let mut next = self.find_scoped_type(&iter, priors);
while let Some(ty) = next {
if ty.path.is_empty() { // root
break;
@ -792,8 +815,7 @@ handle_method_call! {
}}
if_annotation! { Annotation::ScopedVar(priors, var_name) in iter; {
let (ty, proc_ctx) = self.find_type_context(&iter);
let mut next = self.find_scoped_type(ty, proc_ctx, priors);
let mut next = self.find_scoped_type(&iter, priors);
while let Some(ty) = next {
if ty.path.is_empty() { // root
break;
@ -918,5 +940,9 @@ enum UnscopedVar<'a> {
ty: TypeRef<'a>,
var: &'a dm::objtree::TypeVar,
},
Local {
loc: dm::Location,
var_type: &'a dm::ast::VarType,
},
None,
}