Skip to content

Commit caad52a

Browse files
committed
add tree-sitter as main parsing option
Add the tree-sitter grammar parser and use it for the main LSP functions. Functions changed: - completion: completion now also accepts visible variables for completion, which we obtain with tree-sitter. - hover: now it searches inside the tree-sitter AST instead of the AST generated by the SimplicityHL compiler. Also added more hover options, such as types and variables. - goto-definition and references: also updated to search using the tree-sitter AST and to use tree-sitter’s query features for finding definitions and references. As we now use the AST provided by tree-sitter, the functions working with the compiler AST are removed, as is the `miniscript` crate. Added the `documentation` module, because all SimplicityHL function documentation was previously inside the `completion` module, which was confusing since this documentation is also used by other functions, such as `hover`.
1 parent 49f1186 commit caad52a

File tree

13 files changed

+916
-262
lines changed

13 files changed

+916
-262
lines changed

lsp/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ env_logger = "0.11.8"
2020
thiserror = "2.0.17"
2121

2222
ropey = "1.6.1"
23-
miniscript = "12"
2423
simplicityhl = { version = "0.3.0" }
2524
nom = "8.0.0"
2625
lazy_static = "1.5.0"
2726

27+
tree-sitter-simplicityhl = "0.1.3"
28+
tree-sitter = "0.25.10"
29+
2830
[lints.rust]
2931
unsafe_code = "deny"
3032
unused_variables = "warn"

lsp/src/backend.rs

Lines changed: 108 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,19 @@ use simplicityhl::{
2828
parse::ParseFromStr,
2929
};
3030

31+
use crate::completion::builtin::string_to_callname;
3132
use crate::completion::{self, CompletionProvider};
3233
use crate::error::LspError;
3334
use crate::function::Functions;
34-
use crate::utils::{
35-
find_all_references, find_function_name_range, find_related_call, get_call_span,
36-
get_comments_from_lines, position_to_span, span_contains, span_to_positions,
37-
};
35+
use crate::treesitter::parser::{self, AstContext};
36+
use crate::utils::{get_comments_from_lines, span_to_positions};
3837

39-
#[derive(Debug)]
4038
struct Document {
4139
functions: Functions,
40+
ast: AstContext,
4241
text: Rope,
4342
}
4443

45-
#[derive(Debug)]
4644
pub struct Backend {
4745
client: Client,
4846

@@ -176,7 +174,11 @@ impl LanguageServer for Backend {
176174

177175
let completions = self
178176
.completion_provider
179-
.process_completions(prefix, &doc.functions.functions_and_docs())
177+
.process_completions(
178+
prefix,
179+
&doc.functions.functions_and_docs(),
180+
&doc.ast.scopes.get_visible_variables(pos, false),
181+
)
180182
.map(CompletionResponse::Array);
181183

182184
Ok(completions)
@@ -189,22 +191,20 @@ impl LanguageServer for Backend {
189191
let doc = documents
190192
.get(uri)
191193
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
192-
let functions = doc.functions.functions();
193194

194195
let token_pos = params.text_document_position_params.position;
195-
196-
let token_span = position_to_span(token_pos)?;
197-
let Ok(Some(call)) = find_related_call(&functions, token_span) else {
196+
let Some(token) = doc
197+
.ast
198+
.get_token_on_position(&doc.text.to_string(), token_pos)
199+
else {
198200
return Ok(None);
199201
};
200202

201-
let call_span = get_call_span(call)?;
202-
let (start, end) = span_to_positions(&call_span)?;
203-
204-
let description = match call.name() {
205-
parse::CallName::Jet(jet) => {
203+
let token_text = token.text;
204+
let description = match token.token {
205+
parser::Token::Jet => {
206206
let element =
207-
simplicityhl::simplicity::jet::Elements::from_str(format!("{jet}").as_str())
207+
simplicityhl::simplicity::jet::Elements::from_str(token_text.as_str())
208208
.map_err(|err| LspError::ConversionFailed(err.to_string()))?;
209209

210210
let template = completion::jet::jet_to_template(element);
@@ -216,12 +216,12 @@ impl LanguageServer for Backend {
216216
template.description
217217
)
218218
}
219-
parse::CallName::Custom(func) => {
219+
parser::Token::Function => {
220220
let (function, function_doc) =
221221
doc.functions
222-
.get(func.as_inner())
222+
.get(&token_text)
223223
.ok_or(LspError::FunctionNotFound(format!(
224-
"Function {func} is not found"
224+
"Function {token_text} is not found"
225225
)))?;
226226

227227
let template = completion::function_to_template(function, function_doc);
@@ -233,8 +233,12 @@ impl LanguageServer for Backend {
233233
template.description
234234
)
235235
}
236-
other => {
237-
let Some(template) = completion::builtin::match_callname(other) else {
236+
parser::Token::BuiltinFunction => {
237+
let Some(callname) = string_to_callname(&token_text) else {
238+
return Ok(None);
239+
};
240+
241+
let Some(template) = completion::builtin::match_callname(&callname) else {
238242
return Ok(None);
239243
};
240244
format!(
@@ -245,14 +249,37 @@ impl LanguageServer for Backend {
245249
template.description
246250
)
247251
}
252+
parser::Token::Identifier => {
253+
let vars = doc.ast.scopes.get_visible_variables(token_pos, true);
254+
255+
let Some(definition) = vars.iter().find(|&var| var.name == token_text) else {
256+
return Ok(None);
257+
};
258+
259+
format!(
260+
"```simplicityhl\nlet {}: {}\n```",
261+
definition.name, definition.ty
262+
)
263+
}
264+
parser::Token::BuiltinAlias => {
265+
let Some(info) = crate::documentation::alias::type_info(&token_text) else {
266+
return Ok(None);
267+
};
268+
269+
format!(
270+
"```simplicityhl\ntype {} = {}\n```\n{}",
271+
token_text, info.0, info.1
272+
)
273+
}
274+
parser::Token::Alias => return Ok(None),
248275
};
249276

250277
Ok(Some(Hover {
251278
contents: tower_lsp_server::lsp_types::HoverContents::Markup(MarkupContent {
252279
kind: MarkupKind::Markdown,
253280
value: description,
254281
}),
255-
range: Some(Range { start, end }),
282+
range: Some(token.range),
256283
}))
257284
}
258285

@@ -266,46 +293,35 @@ impl LanguageServer for Backend {
266293
let doc = documents
267294
.get(uri)
268295
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
269-
let functions = doc.functions.functions();
270296

271-
let token_position = params.text_document_position_params.position;
272-
let token_span = position_to_span(token_position)?;
297+
let token_pos = params.text_document_position_params.position;
273298

274-
let Ok(Some(call)) = find_related_call(&functions, token_span) else {
275-
let Some(func) = functions
276-
.iter()
277-
.find(|func| span_contains(func.span(), &token_span))
278-
else {
279-
return Ok(None);
280-
};
281-
let range = find_function_name_range(func, &doc.text)?;
282-
283-
if token_position <= range.end && token_position >= range.start {
284-
return Ok(Some(GotoDefinitionResponse::from(Location::new(
285-
uri.clone(),
286-
range,
287-
))));
288-
}
299+
let Some(token) = doc
300+
.ast
301+
.get_token_on_position(&doc.text.to_string(), token_pos)
302+
else {
289303
return Ok(None);
290304
};
291305

292-
match call.name() {
293-
simplicityhl::parse::CallName::Custom(func) => {
294-
let function =
295-
doc.functions
296-
.get_func(func.as_inner())
297-
.ok_or(LspError::FunctionNotFound(format!(
298-
"Function {func} is not found"
299-
)))?;
300-
301-
let (start, end) = span_to_positions(function.as_ref())?;
302-
Ok(Some(GotoDefinitionResponse::from(Location::new(
303-
uri.clone(),
304-
Range::new(start, end),
305-
))))
306+
let location = match token.token {
307+
parser::Token::Function => doc
308+
.ast
309+
.get_function_definition(&doc.text.to_string(), &token.text)?,
310+
parser::Token::Alias => doc
311+
.ast
312+
.get_alias_definition(&doc.text.to_string(), &token.text)?,
313+
parser::Token::Identifier => doc.ast.get_identifier_definition(&token.text, token_pos),
314+
_ => {
315+
return Ok(None);
306316
}
307-
_ => Ok(None),
308-
}
317+
};
318+
319+
Ok(Some(GotoDefinitionResponse::Array(
320+
location
321+
.iter()
322+
.map(|&range| Location::new(uri.to_owned(), range))
323+
.collect(),
324+
)))
309325
}
310326

311327
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
@@ -315,53 +331,40 @@ impl LanguageServer for Backend {
315331
let doc = documents
316332
.get(uri)
317333
.ok_or(LspError::DocumentNotFound(uri.to_owned()))?;
318-
let functions = doc.functions.functions();
319-
320-
let token_position = params.text_document_position.position;
321-
322-
let token_span = position_to_span(token_position)?;
323-
324-
let call_name =
325-
find_related_call(&functions, token_span)?.map(simplicityhl::parse::Call::name);
326-
327-
match call_name {
328-
Some(parse::CallName::Custom(_)) | None => {}
329-
Some(name) => {
330-
return Ok(Some(
331-
find_all_references(&functions, name)?
332-
.iter()
333-
.map(|range| Location {
334-
range: *range,
335-
uri: uri.clone(),
336-
})
337-
.collect(),
338-
));
339-
}
340-
}
341334

342-
let Some(func) = functions.iter().find(|func| match call_name {
343-
Some(parse::CallName::Custom(name)) => func.name() == name,
344-
_ => span_contains(func.span(), &token_span),
345-
}) else {
335+
let token_pos = params.text_document_position.position;
336+
337+
let Some(token) = doc
338+
.ast
339+
.get_token_on_position(&doc.text.to_string(), token_pos)
340+
else {
346341
return Ok(None);
347342
};
348343

349-
let range = find_function_name_range(func, &doc.text)?;
350-
351-
if (token_position <= range.end && token_position >= range.start) || call_name.is_some() {
352-
Ok(Some(
353-
find_all_references(&functions, &parse::CallName::Custom(func.name().clone()))?
354-
.into_iter()
355-
.chain(std::iter::once(range))
356-
.map(|range| Location {
357-
range,
358-
uri: uri.clone(),
359-
})
360-
.collect(),
361-
))
362-
} else {
363-
Ok(None)
364-
}
344+
let token_text = token.text;
345+
346+
let ranges = match token.token {
347+
parser::Token::Identifier => {
348+
doc.ast.get_identifier_reference(&token_text, token_pos)?
349+
}
350+
parser::Token::Function => doc
351+
.ast
352+
.get_function_reference(&doc.text.to_string(), &token_text)?,
353+
parser::Token::Alias => doc
354+
.ast
355+
.get_alias_reference(&doc.text.to_string(), &token_text)?,
356+
357+
_ => {
358+
return Ok(None);
359+
}
360+
};
361+
362+
Ok(Some(
363+
ranges
364+
.iter()
365+
.map(|&range| Location::new(uri.to_owned(), range))
366+
.collect(),
367+
))
365368
}
366369
}
367370

@@ -421,10 +424,12 @@ impl Backend {
421424
}
422425

423426
/// Create [`Document`] using parsed program and code.
424-
fn create_document(program: &simplicityhl::parse::Program, text: &str) -> Document {
427+
fn create_document(program: &simplicityhl::parse::Program, text: &str) -> Option<Document> {
425428
let mut document = Document {
426429
functions: Functions::new(),
427430
text: Rope::from_str(text),
431+
432+
ast: AstContext::new(text)?,
428433
};
429434

430435
program
@@ -447,7 +452,7 @@ fn create_document(program: &simplicityhl::parse::Program, text: &str) -> Docume
447452
);
448453
});
449454

450-
document
455+
Some(document)
451456
}
452457

453458
/// Parse program using [`simplicityhl`] compiler and return [`RichError`],
@@ -460,7 +465,7 @@ fn parse_program(text: &str) -> (Option<RichError>, Option<Document>) {
460465

461466
(
462467
ast::Program::analyze(&program).with_file(text).err(),
463-
Some(create_document(&program, text)),
468+
create_document(&program, text),
464469
)
465470
}
466471

lsp/src/completion/builtin.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,29 @@ pub fn get_builtin_functions() -> Vec<FunctionTemplate> {
3434
functions.iter().filter_map(match_callname).collect()
3535
}
3636

37+
pub fn string_to_callname(text: &str) -> Option<CallName> {
38+
let ty = AliasedType::from(AliasName::from_str_unchecked("T"));
39+
let function_name = FunctionName::from_str_unchecked("fn");
40+
let some = NonZero::new(1)?;
41+
42+
let callname = match text {
43+
"unwrap_left" => CallName::UnwrapLeft(ty),
44+
"unwrap_right" => CallName::UnwrapRight(ty),
45+
"unwrap" => CallName::Unwrap,
46+
"is_none" => CallName::IsNone(ty),
47+
"assert!" => CallName::Assert,
48+
"panic!" => CallName::Panic,
49+
"dbg!" => CallName::Debug,
50+
"into" => CallName::TypeCast(ty),
51+
"fold" => CallName::Fold(function_name, NonZeroPow2Usize::TWO),
52+
"array_fold" => CallName::ArrayFold(function_name, some),
53+
"for_while" => CallName::ForWhile(function_name),
54+
_ => return None,
55+
};
56+
57+
Some(callname)
58+
}
59+
3760
/// Match [`simplicityhl::parse::CallName`] and return [`FunctionTemplate`]
3861
pub fn match_callname(call: &CallName) -> Option<FunctionTemplate> {
3962
let doc = builtin_documentation(call);

lsp/src/completion/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use tower_lsp_server::lsp_types::{
1313
use tokens::parse;
1414
use tokens::CompletionType;
1515

16+
use crate::treesitter::variable::VariableInfo;
17+
1618
/// Build and provide [`CompletionItem`] for jets and builtin functions.
1719
#[derive(Debug)]
1820
pub struct CompletionProvider {
@@ -87,6 +89,7 @@ impl CompletionProvider {
8789
&self,
8890
prefix: &str,
8991
functions: &[(&Function, &str)],
92+
variables: &[VariableInfo],
9093
) -> Option<Vec<CompletionItem>> {
9194
let completion_type = parse(prefix)?;
9295

@@ -125,6 +128,7 @@ impl CompletionProvider {
125128
_ => {
126129
let mut completions = CompletionProvider::get_function_completions(functions);
127130

131+
completions.extend(variables.iter().map(CompletionItem::from));
128132
completions.extend_from_slice(&self.builtin);
129133
completions.extend_from_slice(&self.modules);
130134

0 commit comments

Comments
 (0)