1mod leaf;
40mod math;
41mod protected;
42mod region;
43mod token;
44
45use rowan::GreenNode;
46
47use crate::declarations::ResolvedDeclarations;
48use crate::parser::core::{Parse, SyntaxError, parse_with_declarations_resolved};
49use crate::parser::lexer::{LexConfig, ParseCtx, dtx_has_expl_signal};
50use crate::syntax::SyntaxNode;
51
52pub use crate::parser::edit::{Edit, apply_edits, diff_edit, try_apply_edits};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
61pub enum ReparseTier {
62 Token,
64 Verbatim,
67 Math,
70 Region,
72}
73
74#[derive(Debug, Clone)]
77pub struct Reparsed {
78 pub green: GreenNode,
79 pub errors: Vec<SyntaxError>,
80 pub tier: ReparseTier,
81}
82
83#[derive(Debug, Clone, Copy)]
91pub struct ReparseBase<'a> {
92 pub text: &'a str,
93 pub green: &'a GreenNode,
94 pub errors: &'a [SyntaxError],
95 pub ctx: &'a ParseCtx,
96 pub config: LexConfig,
97 pub implicit_expl: bool,
103 pub declared: &'a ResolvedDeclarations,
104}
105
106impl<'a> ReparseBase<'a> {
107 pub fn from_parts(
108 text: &'a str,
109 green: &'a GreenNode,
110 errors: &'a [SyntaxError],
111 ctx: &'a ParseCtx,
112 config: LexConfig,
113 declared: &'a ResolvedDeclarations,
114 ) -> Self {
115 Self {
116 text,
117 green,
118 errors,
119 ctx,
120 config,
121 implicit_expl: implicit_expl_for(text, config),
122 declared,
123 }
124 }
125
126 pub fn syntax(&self) -> SyntaxNode {
128 SyntaxNode::new_root(self.green.clone())
129 }
130}
131
132fn implicit_expl_for(text: &str, config: LexConfig) -> bool {
133 config.dtx && dtx_has_expl_signal(text)
134}
135
136pub fn reparse(base: &ReparseBase<'_>, edit: &Edit, new_text: &str) -> Option<Reparsed> {
140 if !edit.fits(base.text) {
144 return None;
145 }
146 reparse_one(base, edit, new_text)
147}
148
149pub fn reparse_edits(base: &ReparseBase<'_>, edits: &[Edit], new_text: &str) -> Option<Reparsed> {
156 if edits.is_empty() {
157 return None;
158 }
159
160 let mut text = base.text.to_string();
165 let mut green = base.green.clone();
166 let mut errors = base.errors.to_vec();
167 let mut tier: Option<ReparseTier> = None;
168
169 for edit in edits {
170 if !edit.fits(&text) {
171 return None;
172 }
173 let next = edit.apply(&text);
174 let step = {
175 let step_base = ReparseBase::from_parts(
176 &text,
177 &green,
178 &errors,
179 base.ctx,
180 base.config,
181 base.declared,
182 );
183 reparse_one(&step_base, edit, &next)?
184 };
185 text = next;
186 green = step.green;
187 errors = step.errors;
188 tier = Some(tier.map_or(step.tier, |t| t.max(step.tier)));
189 }
190
191 if text != new_text {
195 return None;
196 }
197
198 Some(Reparsed {
199 green,
200 errors,
201 tier: tier?,
202 })
203}
204
205fn reparse_one(base: &ReparseBase<'_>, edit: &Edit, new_text: &str) -> Option<Reparsed> {
210 token::reparse_token(base, edit, new_text)
211 .or_else(|| protected::reparse_protected(base, edit, new_text))
212 .or_else(|| math::reparse_math(base, edit, new_text))
213 .or_else(|| region::reparse_region(base, edit, new_text))
214}
215
216fn finish(
230 green: GreenNode,
231 errors: Vec<SyntaxError>,
232 tier: ReparseTier,
233 base: &ReparseBase<'_>,
234 new_text: &str,
235) -> Option<Reparsed> {
236 if !spans_its_text(&green, new_text) {
237 return None;
238 }
239 let out = Reparsed {
240 green,
241 errors,
242 tier,
243 };
244 assert_matches_full_parse(&out, base, new_text);
245 Some(out)
246}
247
248fn spans_its_text(green: &GreenNode, text: &str) -> bool {
250 usize::from(green.text_len()) == text.len()
251}
252
253#[doc(hidden)]
260pub fn fingerprint(node: &SyntaxNode) -> String {
261 use std::fmt::Write as _;
262
263 let mut out = String::new();
264 for element in node.descendants_with_tokens() {
265 match element {
266 rowan::NodeOrToken::Node(n) => {
267 let _ = writeln!(out, "{:?}@{:?}", n.kind(), n.text_range());
268 }
269 rowan::NodeOrToken::Token(t) => {
270 let _ = writeln!(out, "{:?}@{:?} {:?}", t.kind(), t.text_range(), t.text());
271 }
272 }
273 }
274 out
275}
276
277#[cfg(debug_assertions)]
284fn assert_matches_full_parse(result: &Reparsed, base: &ReparseBase<'_>, new_text: &str) {
285 let full = full_parse(base, new_text);
286 debug_assert_eq!(
287 fingerprint(&SyntaxNode::new_root(result.green.clone())),
288 fingerprint(&full.syntax()),
289 "reparse ({:?}) produced a different tree than a full parse",
290 result.tier,
291 );
292 debug_assert_eq!(
293 result.errors, full.errors,
294 "reparse ({:?}) produced different errors than a full parse",
295 result.tier,
296 );
297}
298
299#[cfg(not(debug_assertions))]
300fn assert_matches_full_parse(_: &Reparsed, _: &ReparseBase<'_>, _: &str) {}
301
302#[cfg_attr(not(debug_assertions), allow(dead_code))]
304fn full_parse(base: &ReparseBase<'_>, text: &str) -> Parse {
305 parse_with_declarations_resolved(text, base.config, base.declared).0
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::parser::lexer::LatexFlavor;
312
313 fn base_of(text: &str) -> (Parse, ParseCtx, ResolvedDeclarations) {
314 let declared = ResolvedDeclarations::default();
315 let (parse, ctx) = parse_with_declarations_resolved(text, LatexFlavor::Document, &declared);
316 (parse, ctx, declared)
317 }
318
319 fn with_base<R>(text: &str, f: impl FnOnce(&ReparseBase<'_>) -> R) -> R {
320 let (parse, ctx, declared) = base_of(text);
321 f(&ReparseBase::from_parts(
322 text,
323 &parse.green,
324 &parse.errors,
325 &ctx,
326 LatexFlavor::Document.into(),
327 &declared,
328 ))
329 }
330
331 fn edit(range: std::ops::Range<usize>, insert: &str) -> Edit {
332 Edit {
333 range,
334 insert: insert.to_string(),
335 }
336 }
337
338 #[test]
339 fn an_edit_outside_a_plain_leaf_falls_back() {
340 with_base("\\section{Hi}\n\nbody text\n", |base| {
341 let e = edit(8..8, "x");
342 assert!(reparse(base, &e, &e.apply(base.text)).is_none());
343 let e = edit(7..10, "zz");
344 assert!(reparse(base, &e, &e.apply(base.text)).is_none());
345 });
346 }
347
348 #[test]
349 fn an_edit_that_does_not_fit_the_base_is_refused() {
350 with_base("abc\n", |base| {
351 assert!(reparse(base, &edit(90..99, "x"), "abc\n").is_none());
352 assert!(reparse(base, &edit(1..1, "x"), "abc\n").is_none());
353 });
354 with_base("α\n", |base| {
355 assert!(reparse(base, &edit(1..1, "x"), "αx\n").is_none());
356 });
357 }
358
359 #[test]
360 fn an_empty_chain_is_refused() {
361 with_base("abc\n", |base| {
362 assert!(reparse_edits(base, &[], "abc\n").is_none());
363 });
364 }
365
366 #[test]
367 fn a_chain_that_lands_elsewhere_is_refused() {
368 with_base("abc\n", |base| {
369 assert!(reparse_edits(base, &[edit(0..0, "x")], "totally different").is_none());
370 });
371 }
372
373 #[test]
374 fn spans_its_text_measures_the_green_width() {
375 with_base("\\section{Hi}\n", |base| {
376 assert!(spans_its_text(base.green, base.text));
377 assert!(!spans_its_text(base.green, "\\section{Hi}"));
378 assert!(!spans_its_text(base.green, "\\section{Hi}\n\n"));
379 });
380 }
381
382 #[test]
383 fn finish_refuses_a_tree_that_does_not_span_its_text() {
384 with_base("\\section{Hi}\n", |base| {
385 let out = finish(
386 base.green.clone(),
387 base.errors.to_vec(),
388 ReparseTier::Token,
389 base,
390 "\\section{Hi}\n\n",
391 );
392 assert!(out.is_none());
393 });
394 }
395
396 #[test]
397 fn finish_accepts_an_identity_splice() {
398 with_base("\\section{Hi}\n\nbody\n", |base| {
399 let out = finish(
400 base.green.clone(),
401 base.errors.to_vec(),
402 ReparseTier::Token,
403 base,
404 base.text,
405 );
406 let out = out.expect("an identity splice matches a full parse");
407 assert_eq!(out.tier, ReparseTier::Token);
408 assert_eq!(&out.green, base.green);
409 });
410 }
411
412 #[test]
413 fn tiers_order_cheapest_first() {
414 assert!(ReparseTier::Token < ReparseTier::Verbatim);
415 assert!(ReparseTier::Verbatim < ReparseTier::Math);
416 assert!(ReparseTier::Math < ReparseTier::Region);
417 }
418
419 #[test]
420 fn fingerprint_separates_trees_that_differ_only_in_token_text() {
421 let a = crate::parser::parse("\\a{b}");
422 let b = crate::parser::parse("\\a{c}");
423 assert_ne!(fingerprint(&a.syntax()), fingerprint(&b.syntax()));
424 }
425
426 #[test]
427 fn fingerprint_agrees_with_itself_across_equal_parses() {
428 let a = crate::parser::parse("\\section{Hi}\n\nbody $x^2$ % c\n");
429 let b = crate::parser::parse("\\section{Hi}\n\nbody $x^2$ % c\n");
430 assert_eq!(fingerprint(&a.syntax()), fingerprint(&b.syntax()));
431 }
432
433 #[cfg(debug_assertions)]
434 mod oracle_self_tests {
435 use super::*;
436
437 #[test]
438 #[should_panic(expected = "different tree")]
439 fn the_oracle_rejects_a_wrong_tree() {
440 with_base("\\section{Hi}\n", |base| {
441 let wrong = crate::parser::parse("\\section{Ho}\n");
442 let _ = finish(
443 wrong.green,
444 base.errors.to_vec(),
445 ReparseTier::Token,
446 base,
447 base.text,
448 );
449 });
450 }
451
452 #[test]
453 #[should_panic(expected = "different errors")]
454 fn the_oracle_rejects_a_perturbed_error_vector() {
455 with_base("\\section{Hi}\n", |base| {
456 let mut errors = base.errors.to_vec();
457 errors.push(SyntaxError {
458 message: "invented".to_string(),
459 start: 0,
460 end: 1,
461 });
462 let _ = finish(
463 base.green.clone(),
464 errors,
465 ReparseTier::Token,
466 base,
467 base.text,
468 );
469 });
470 }
471
472 #[test]
473 #[should_panic(expected = "different errors")]
474 fn the_oracle_rejects_an_error_that_moved() {
475 let text = "\\begin{itemize}\n";
476 with_base(text, |base| {
477 assert!(
478 !base.errors.is_empty(),
479 "this fixture exists to carry an error"
480 );
481 let mut errors = base.errors.to_vec();
482 errors[0].start += 1;
483 let _ = finish(
484 base.green.clone(),
485 errors,
486 ReparseTier::Token,
487 base,
488 base.text,
489 );
490 });
491 }
492 }
493}