mirror of
https://github.com/SpaceManiac/SpacemanDMM.git
synced 2025-12-23 05:36:47 +00:00
Add go-to-def for local vars and their properties
This commit is contained in:
parent
3f8d225d6e
commit
ed49883cae
5 changed files with 70 additions and 32 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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(¶m.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,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue