Skip to content

Commit 738a178

Browse files
author
Erin van der Veen
authored
Merge pull request #523 from tweag/erin/reuse-query-idempotence
Create the query only once
2 parents f236f8b + d662f65 commit 738a178

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)