Skip to content

Commit d662f65

Browse files
author
Erin van der Veen
committed
Create the query only once
The query is then used between the formatting and the idempotence check. The TopiaryQuery struct helps keep the API clean.
1 parent f236f8b commit d662f65

File tree

6 files changed

+75
-40
lines changed

6 files changed

+75
-40
lines changed

topiary-cli/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{
2020
output::OutputFile,
2121
visualise::Visualisation,
2222
};
23-
use topiary::{formatter, Language, Operation, SupportedLanguage};
23+
use topiary::{formatter, Language, Operation, SupportedLanguage, TopiaryQuery};
2424

2525
#[derive(Parser, Debug)]
2626
#[command(author, version, about, long_about = None)]
@@ -130,7 +130,7 @@ async fn run() -> CLIResult<()> {
130130
language.query_file()?
131131
};
132132

133-
let query = (|| {
133+
let query_content = (|| {
134134
let mut reader = BufReader::new(File::open(&query_path)?);
135135
let mut contents = String::new();
136136
reader.read_to_string(&mut contents)?;
@@ -145,6 +145,7 @@ async fn run() -> CLIResult<()> {
145145
})?;
146146

147147
let grammar = language.grammar().await?;
148+
let query = TopiaryQuery::new(&grammar, &query_content)?;
148149

149150
let operation = if let Some(visualisation) = args.visualise {
150151
Operation::Visualise {

topiary-playground/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#[cfg(target_arch = "wasm32")]
2-
use topiary::{formatter, Configuration, FormatterResult, Operation};
2+
use topiary::{formatter, Configuration, FormatterResult, Operation, TopiaryQuery};
33
#[cfg(target_arch = "wasm32")]
44
use tree_sitter_facade::TreeSitter;
55
#[cfg(target_arch = "wasm32")]
@@ -41,7 +41,7 @@ pub async fn format(
4141
#[cfg(target_arch = "wasm32")]
4242
async fn format_inner(
4343
input: &str,
44-
query: &str,
44+
query_content: &str,
4545
language_name: &str,
4646
check_idempotence: bool,
4747
tolerate_parsing_errors: bool,
@@ -51,11 +51,12 @@ async fn format_inner(
5151
let configuration = Configuration::parse_default_configuration()?;
5252
let language = configuration.get_language(language_name)?;
5353
let grammar = language.grammar_wasm().await?;
54+
let query = TopiaryQuery::new(&grammar, query_content)?;
5455

5556
formatter(
5657
&mut input.as_bytes(),
5758
&mut output,
58-
query,
59+
&query,
5960
language,
6061
&grammar,
6162
Operation::Format {

topiary/benches/benchmark.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ use criterion::async_executor::FuturesExecutor;
22
use criterion::{criterion_group, criterion_main, Criterion};
33
use std::fs;
44
use std::io;
5-
use topiary::Configuration;
65
use topiary::{formatter, Operation};
6+
use topiary::{Configuration, TopiaryQuery};
77

88
async fn format() {
99
let input = fs::read_to_string("tests/samples/input/ocaml.ml").unwrap();
10-
let query = fs::read_to_string("../languages/ocaml.scm").unwrap();
10+
let query_content = fs::read_to_string("../languages/ocaml.scm").unwrap();
1111
let configuration = Configuration::parse_default_configuration().unwrap();
1212
let language = configuration.get_language("ocaml").unwrap();
1313
let grammar = language.grammar().await.unwrap();
14+
let query = TopiaryQuery::new(&grammar, &query_content).unwrap();
1415

1516
let mut input = input.as_bytes();
1617
let mut output = io::BufWriter::new(Vec::new());

topiary/src/lib.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub use crate::{
1919
configuration::{default_configuration_toml, Configuration},
2020
error::{FormatterError, IoError},
2121
language::{Language, SupportedLanguage},
22-
tree_sitter::{apply_query, SyntaxNode, Visualisation},
22+
tree_sitter::{apply_query, SyntaxNode, TopiaryQuery, Visualisation},
2323
};
2424

2525
mod atom_collection;
@@ -145,21 +145,22 @@ pub enum Operation {
145145
/// # tokio_test::block_on(async {
146146
/// use std::fs::File;
147147
/// use std::io::{BufReader, Read};
148-
/// use topiary::{formatter, Configuration, FormatterError, Operation};
148+
/// use topiary::{formatter, Configuration, FormatterError, TopiaryQuery, Operation};
149149
///
150150
/// let input = "[1,2]".to_string();
151151
/// let mut input = input.as_bytes();
152152
/// let mut output = Vec::new();
153153
/// let mut query_file = BufReader::new(File::open("../languages/json.scm").expect("query file"));
154-
/// let mut query = String::new();
155-
/// query_file.read_to_string(&mut query).expect("read query file");
154+
/// let mut query_content = String::new();
155+
/// query_file.read_to_string(&mut query_content).expect("read query file");
156156
///
157157
/// let config = Configuration::parse_default_configuration().unwrap();
158158
/// let language = config.get_language("json").unwrap();
159159
/// let grammar = language
160160
/// .grammar()
161161
/// .await
162162
/// .expect("grammar");
163+
/// let query = TopiaryQuery::new(&grammar, &query_content).unwrap();
163164
///
164165
/// match formatter(&mut input, &mut output, &query, &language, &grammar, Operation::Format{ skip_idempotence: false, tolerate_parsing_errors: false }) {
165166
/// Ok(()) => {
@@ -177,7 +178,7 @@ pub enum Operation {
177178
pub fn formatter(
178179
input: &mut impl io::Read,
179180
output: &mut impl io::Write,
180-
query: &str,
181+
query: &TopiaryQuery,
181182
language: &Language,
182183
grammar: &tree_sitter_facade::Language,
183184
operation: Operation,
@@ -196,6 +197,7 @@ pub fn formatter(
196197
} => {
197198
// All the work related to tree-sitter and the query is done here
198199
log::info!("Apply Tree-sitter query");
200+
199201
let mut atoms =
200202
tree_sitter::apply_query(&content, query, grammar, tolerate_parsing_errors, false)?;
201203

@@ -257,7 +259,7 @@ fn trim_whitespace(s: &str) -> String {
257259
/// `Err(FormatterError::Formatting(...))` if the formatting failed
258260
fn idempotence_check(
259261
content: &str,
260-
query: &str,
262+
query: &TopiaryQuery,
261263
language: &Language,
262264
grammar: &tree_sitter_facade::Language,
263265
tolerate_parsing_errors: bool,
@@ -310,23 +312,24 @@ mod tests {
310312

311313
use crate::{
312314
configuration::Configuration, error::FormatterError, formatter,
313-
test_utils::pretty_assert_eq, Operation,
315+
test_utils::pretty_assert_eq, Operation, TopiaryQuery,
314316
};
315317

316318
/// Attempt to parse invalid json, expecting a failure
317319
#[test(tokio::test)]
318320
async fn parsing_error_fails_formatting() {
319321
let mut input = r#"{"foo":{"bar"}}"#.as_bytes();
320322
let mut output = Vec::new();
321-
let query = "(#language! json)";
323+
let query_content = "(#language! json)";
322324
let configuration = Configuration::parse_default_configuration().unwrap();
323325
let language = configuration.get_language("json").unwrap();
324326
let grammar = language.grammar().await.unwrap();
327+
let query = TopiaryQuery::new(&grammar, query_content).unwrap();
325328

326329
match formatter(
327330
&mut input,
328331
&mut output,
329-
query,
332+
&query,
330333
language,
331334
&grammar,
332335
Operation::Format {
@@ -352,10 +355,11 @@ mod tests {
352355
let expected = "{ \"one\": {\"bar\" \"baz\"}, \"two\": \"bar\" }\n";
353356

354357
let mut output = Vec::new();
355-
let query = fs::read_to_string("../languages/json.scm").unwrap();
358+
let query_content = fs::read_to_string("../languages/json.scm").unwrap();
356359
let configuration = Configuration::parse_default_configuration().unwrap();
357360
let language = configuration.get_language("json").unwrap();
358361
let grammar = language.grammar().await.unwrap();
362+
let query = TopiaryQuery::new(&grammar, &query_content).unwrap();
359363

360364
formatter(
361365
&mut input,

topiary/src/tree_sitter.rs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,35 @@ struct Position {
2525
column: u32,
2626
}
2727

28+
/// Topiary often needs both the tree-sitter `Query` and the original content
29+
/// beloging to the file from which the query was parsed. This struct is a simple
30+
/// convenience wrapper that combines the `Query` with its original string.
31+
pub struct TopiaryQuery {
32+
pub query: Query,
33+
pub query_content: String,
34+
}
35+
36+
impl TopiaryQuery {
37+
/// Creates a new `TopiaryQuery` from a tree-sitter language/grammar and the
38+
/// contents of the query file.
39+
///
40+
/// # Errors
41+
///
42+
/// This function will return an error if tree-sitter failed to parse the query file.
43+
pub fn new(
44+
grammar: &tree_sitter_facade::Language,
45+
query_content: &str,
46+
) -> FormatterResult<TopiaryQuery> {
47+
let query = Query::new(grammar, query_content)
48+
.map_err(|e| FormatterError::Query("Error parsing query file".into(), Some(e)))?;
49+
50+
Ok(TopiaryQuery {
51+
query,
52+
query_content: query_content.to_owned(),
53+
})
54+
}
55+
}
56+
2857
impl From<Point> for Position {
2958
fn from(point: Point) -> Self {
3059
Self {
@@ -92,23 +121,21 @@ struct LocalQueryMatch<'a> {
92121
/// - A unknown capture name was encountered in the query.
93122
pub fn apply_query(
94123
input_content: &str,
95-
query_content: &str,
124+
query: &TopiaryQuery,
96125
grammar: &tree_sitter_facade::Language,
97126
tolerate_parsing_errors: bool,
98127
should_check_input_exhaustivity: bool,
99128
) -> FormatterResult<AtomCollection> {
100129
let (tree, grammar) = parse(input_content, grammar, tolerate_parsing_errors)?;
101130
let root = tree.root_node();
102131
let source = input_content.as_bytes();
103-
let query = Query::new(grammar, query_content)
104-
.map_err(|e| FormatterError::Query("Error parsing query file".into(), Some(e)))?;
105132

106133
// Match queries
107134
let mut cursor = QueryCursor::new();
108135
let mut matches: Vec<LocalQueryMatch> = Vec::new();
109-
let capture_names = query.capture_names();
136+
let capture_names = query.query.capture_names();
110137

111-
for query_match in query.matches(&root, source, &mut cursor) {
138+
for query_match in query.query.matches(&root, source, &mut cursor) {
112139
let local_captures: Vec<QueryCapture> = query_match.captures().collect();
113140

114141
matches.push(LocalQueryMatch {
@@ -119,14 +146,7 @@ pub fn apply_query(
119146

120147
if should_check_input_exhaustivity {
121148
let ref_match_count = matches.len();
122-
check_input_exhaustivity(
123-
ref_match_count,
124-
&query,
125-
query_content,
126-
grammar,
127-
&root,
128-
source,
129-
)?;
149+
check_input_exhaustivity(ref_match_count, query, grammar, &root, source)?;
130150
}
131151

132152
// Find the ids of all tree-sitter nodes that were identified as a leaf
@@ -152,7 +172,7 @@ pub fn apply_query(
152172

153173
let mut predicates = QueryPredicates::default();
154174

155-
for p in query.general_predicates(m.pattern_index) {
175+
for p in query.query.general_predicates(m.pattern_index) {
156176
predicates = handle_predicate(&p, &predicates)?;
157177
}
158178
check_predicates(&predicates)?;
@@ -362,13 +382,13 @@ fn check_predicates(predicates: &QueryPredicates) -> FormatterResult<()> {
362382
/// then that pattern originally matched nothing in the input.
363383
fn check_input_exhaustivity(
364384
ref_match_count: usize,
365-
original_query: &Query,
366-
query_content: &str,
385+
original_query: &TopiaryQuery,
367386
grammar: &tree_sitter_facade::Language,
368387
root: &Node,
369388
source: &[u8],
370389
) -> FormatterResult<()> {
371-
let pattern_count = original_query.pattern_count();
390+
let pattern_count = original_query.query.pattern_count();
391+
let query_content = &original_query.query_content;
372392
// This particular test avoids a SIGSEGV error that occurs when trying
373393
// to count the matches of an empty query (see #481)
374394
if pattern_count == 1 {
@@ -379,6 +399,9 @@ fn check_input_exhaustivity(
379399
}
380400
}
381401
for i in 0..pattern_count {
402+
// We don't need to use TopiaryQuery in this test since we have no need
403+
// for duplicate versions of the query_content string, instead we create the query
404+
// manually.
382405
let mut query = Query::new(grammar, query_content)
383406
.map_err(|e| FormatterError::Query("Error parsing query file".into(), Some(e)))?;
384407
query.disable_pattern(i);
@@ -401,8 +424,7 @@ fn check_input_exhaustivity(
401424
#[cfg(target_arch = "wasm32")]
402425
fn check_input_exhaustivity(
403426
_ref_match_count: usize,
404-
_original_query: &Query,
405-
_query_content: &str,
427+
_original_query: &TopiaryQuery,
406428
_grammar: &tree_sitter_facade::Language,
407429
_root: &Node,
408430
_source: &[u8],

topiary/tests/sample-tester.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use test_log::test;
77

88
use topiary::{
99
apply_query, formatter, test_utils::pretty_assert_eq, Configuration, FormatterError, Language,
10-
Operation,
10+
Operation, TopiaryQuery,
1111
};
1212

1313
#[test(tokio::test)]
@@ -31,10 +31,12 @@ async fn input_output_tester() {
3131

3232
let mut input = BufReader::new(fs::File::open(file.path()).unwrap());
3333
let mut output = Vec::new();
34-
let query = fs::read_to_string(language.query_file().unwrap()).unwrap();
34+
let query_content = fs::read_to_string(language.query_file().unwrap()).unwrap();
3535

3636
let grammar = language.grammar().await.unwrap();
3737

38+
let query = TopiaryQuery::new(&grammar, &query_content).unwrap();
39+
3840
info!(
3941
"Formatting file {} as {}.",
4042
file.path().display(),
@@ -78,10 +80,12 @@ async fn formatted_query_tester() {
7880

7981
let mut input = BufReader::new(fs::File::open(file.path()).unwrap());
8082
let mut output = Vec::new();
81-
let query = fs::read_to_string(language.query_file().unwrap()).unwrap();
83+
let query_content = fs::read_to_string(language.query_file().unwrap()).unwrap();
8284

8385
let grammar = language.grammar().await.unwrap();
8486

87+
let query = TopiaryQuery::new(&grammar, &query_content).unwrap();
88+
8589
formatter(
8690
&mut input,
8791
&mut output,
@@ -122,7 +126,9 @@ async fn exhaustive_query_tester() {
122126

123127
let grammar = language.grammar().await.unwrap();
124128

125-
apply_query(&input_content, &query_content, &grammar, false, true).unwrap_or_else(|e| {
129+
let query = TopiaryQuery::new(&grammar, &query_content).unwrap();
130+
131+
apply_query(&input_content, &query, &grammar, false, true).unwrap_or_else(|e| {
126132
if let FormatterError::PatternDoesNotMatch(_) = e {
127133
panic!("Found untested query in file {query_file:?}:\n{e}");
128134
} else {

0 commit comments

Comments
 (0)