mod leaf;
mod math;
mod protected;
mod region;
mod token;
use rowan::GreenNode;
use crate::declarations::ResolvedDeclarations;
use crate::parser::core::{Parse, SyntaxError, parse_with_declarations_resolved};
use crate::parser::lexer::{LexConfig, ParseCtx, dtx_has_expl_signal};
use crate::syntax::SyntaxNode;
pub use crate::parser::edit::{Edit, apply_edits, diff_edit, try_apply_edits};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ReparseTier {
Token,
Verbatim,
Math,
Region,
}
#[derive(Debug, Clone)]
pub struct Reparsed {
pub green: GreenNode,
pub errors: Vec<SyntaxError>,
pub tier: ReparseTier,
}
#[derive(Debug, Clone, Copy)]
pub struct ReparseBase<'a> {
pub text: &'a str,
pub green: &'a GreenNode,
pub errors: &'a [SyntaxError],
pub ctx: &'a ParseCtx,
pub config: LexConfig,
pub implicit_expl: bool,
pub declared: &'a ResolvedDeclarations,
}
impl<'a> ReparseBase<'a> {
pub fn from_parts(
text: &'a str,
green: &'a GreenNode,
errors: &'a [SyntaxError],
ctx: &'a ParseCtx,
config: LexConfig,
declared: &'a ResolvedDeclarations,
) -> Self {
Self {
text,
green,
errors,
ctx,
config,
implicit_expl: implicit_expl_for(text, config),
declared,
}
}
pub fn syntax(&self) -> SyntaxNode {
SyntaxNode::new_root(self.green.clone())
}
}
fn implicit_expl_for(text: &str, config: LexConfig) -> bool {
config.dtx && dtx_has_expl_signal(text)
}
pub fn reparse(base: &ReparseBase<'_>, edit: &Edit, new_text: &str) -> Option<Reparsed> {
if !edit.fits(base.text) {
return None;
}
reparse_one(base, edit, new_text)
}
pub fn reparse_edits(base: &ReparseBase<'_>, edits: &[Edit], new_text: &str) -> Option<Reparsed> {
if edits.is_empty() {
return None;
}
let mut text = base.text.to_string();
let mut green = base.green.clone();
let mut errors = base.errors.to_vec();
let mut tier: Option<ReparseTier> = None;
for edit in edits {
if !edit.fits(&text) {
return None;
}
let next = edit.apply(&text);
let step = {
let step_base = ReparseBase::from_parts(
&text,
&green,
&errors,
base.ctx,
base.config,
base.declared,
);
reparse_one(&step_base, edit, &next)?
};
text = next;
green = step.green;
errors = step.errors;
tier = Some(tier.map_or(step.tier, |t| t.max(step.tier)));
}
if text != new_text {
return None;
}
Some(Reparsed {
green,
errors,
tier: tier?,
})
}
fn reparse_one(base: &ReparseBase<'_>, edit: &Edit, new_text: &str) -> Option<Reparsed> {
token::reparse_token(base, edit, new_text)
.or_else(|| protected::reparse_protected(base, edit, new_text))
.or_else(|| math::reparse_math(base, edit, new_text))
.or_else(|| region::reparse_region(base, edit, new_text))
}
fn finish(
green: GreenNode,
errors: Vec<SyntaxError>,
tier: ReparseTier,
base: &ReparseBase<'_>,
new_text: &str,
) -> Option<Reparsed> {
if !spans_its_text(&green, new_text) {
return None;
}
let out = Reparsed {
green,
errors,
tier,
};
assert_matches_full_parse(&out, base, new_text);
Some(out)
}
fn spans_its_text(green: &GreenNode, text: &str) -> bool {
usize::from(green.text_len()) == text.len()
}
#[doc(hidden)]
pub fn fingerprint(node: &SyntaxNode) -> String {
use std::fmt::Write as _;
let mut out = String::new();
for element in node.descendants_with_tokens() {
match element {
rowan::NodeOrToken::Node(n) => {
let _ = writeln!(out, "{:?}@{:?}", n.kind(), n.text_range());
}
rowan::NodeOrToken::Token(t) => {
let _ = writeln!(out, "{:?}@{:?} {:?}", t.kind(), t.text_range(), t.text());
}
}
}
out
}
#[cfg(debug_assertions)]
fn assert_matches_full_parse(result: &Reparsed, base: &ReparseBase<'_>, new_text: &str) {
let full = full_parse(base, new_text);
debug_assert_eq!(
fingerprint(&SyntaxNode::new_root(result.green.clone())),
fingerprint(&full.syntax()),
"reparse ({:?}) produced a different tree than a full parse",
result.tier,
);
debug_assert_eq!(
result.errors, full.errors,
"reparse ({:?}) produced different errors than a full parse",
result.tier,
);
}
#[cfg(not(debug_assertions))]
fn assert_matches_full_parse(_: &Reparsed, _: &ReparseBase<'_>, _: &str) {}
#[cfg_attr(not(debug_assertions), allow(dead_code))]
fn full_parse(base: &ReparseBase<'_>, text: &str) -> Parse {
parse_with_declarations_resolved(text, base.config, base.declared).0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::lexer::LatexFlavor;
fn base_of(text: &str) -> (Parse, ParseCtx, ResolvedDeclarations) {
let declared = ResolvedDeclarations::default();
let (parse, ctx) = parse_with_declarations_resolved(text, LatexFlavor::Document, &declared);
(parse, ctx, declared)
}
fn with_base<R>(text: &str, f: impl FnOnce(&ReparseBase<'_>) -> R) -> R {
let (parse, ctx, declared) = base_of(text);
f(&ReparseBase::from_parts(
text,
&parse.green,
&parse.errors,
&ctx,
LatexFlavor::Document.into(),
&declared,
))
}
fn edit(range: std::ops::Range<usize>, insert: &str) -> Edit {
Edit {
range,
insert: insert.to_string(),
}
}
#[test]
fn an_edit_outside_a_plain_leaf_falls_back() {
with_base("\\section{Hi}\n\nbody text\n", |base| {
let e = edit(8..8, "x");
assert!(reparse(base, &e, &e.apply(base.text)).is_none());
let e = edit(7..10, "zz");
assert!(reparse(base, &e, &e.apply(base.text)).is_none());
});
}
#[test]
fn an_edit_that_does_not_fit_the_base_is_refused() {
with_base("abc\n", |base| {
assert!(reparse(base, &edit(90..99, "x"), "abc\n").is_none());
assert!(reparse(base, &edit(1..1, "x"), "abc\n").is_none());
});
with_base("α\n", |base| {
assert!(reparse(base, &edit(1..1, "x"), "αx\n").is_none());
});
}
#[test]
fn an_empty_chain_is_refused() {
with_base("abc\n", |base| {
assert!(reparse_edits(base, &[], "abc\n").is_none());
});
}
#[test]
fn a_chain_that_lands_elsewhere_is_refused() {
with_base("abc\n", |base| {
assert!(reparse_edits(base, &[edit(0..0, "x")], "totally different").is_none());
});
}
#[test]
fn spans_its_text_measures_the_green_width() {
with_base("\\section{Hi}\n", |base| {
assert!(spans_its_text(base.green, base.text));
assert!(!spans_its_text(base.green, "\\section{Hi}"));
assert!(!spans_its_text(base.green, "\\section{Hi}\n\n"));
});
}
#[test]
fn finish_refuses_a_tree_that_does_not_span_its_text() {
with_base("\\section{Hi}\n", |base| {
let out = finish(
base.green.clone(),
base.errors.to_vec(),
ReparseTier::Token,
base,
"\\section{Hi}\n\n",
);
assert!(out.is_none());
});
}
#[test]
fn finish_accepts_an_identity_splice() {
with_base("\\section{Hi}\n\nbody\n", |base| {
let out = finish(
base.green.clone(),
base.errors.to_vec(),
ReparseTier::Token,
base,
base.text,
);
let out = out.expect("an identity splice matches a full parse");
assert_eq!(out.tier, ReparseTier::Token);
assert_eq!(&out.green, base.green);
});
}
#[test]
fn tiers_order_cheapest_first() {
assert!(ReparseTier::Token < ReparseTier::Verbatim);
assert!(ReparseTier::Verbatim < ReparseTier::Math);
assert!(ReparseTier::Math < ReparseTier::Region);
}
#[test]
fn fingerprint_separates_trees_that_differ_only_in_token_text() {
let a = crate::parser::parse("\\a{b}");
let b = crate::parser::parse("\\a{c}");
assert_ne!(fingerprint(&a.syntax()), fingerprint(&b.syntax()));
}
#[test]
fn fingerprint_agrees_with_itself_across_equal_parses() {
let a = crate::parser::parse("\\section{Hi}\n\nbody $x^2$ % c\n");
let b = crate::parser::parse("\\section{Hi}\n\nbody $x^2$ % c\n");
assert_eq!(fingerprint(&a.syntax()), fingerprint(&b.syntax()));
}
#[cfg(debug_assertions)]
mod oracle_self_tests {
use super::*;
#[test]
#[should_panic(expected = "different tree")]
fn the_oracle_rejects_a_wrong_tree() {
with_base("\\section{Hi}\n", |base| {
let wrong = crate::parser::parse("\\section{Ho}\n");
let _ = finish(
wrong.green,
base.errors.to_vec(),
ReparseTier::Token,
base,
base.text,
);
});
}
#[test]
#[should_panic(expected = "different errors")]
fn the_oracle_rejects_a_perturbed_error_vector() {
with_base("\\section{Hi}\n", |base| {
let mut errors = base.errors.to_vec();
errors.push(SyntaxError {
message: "invented".to_string(),
start: 0,
end: 1,
});
let _ = finish(
base.green.clone(),
errors,
ReparseTier::Token,
base,
base.text,
);
});
}
#[test]
#[should_panic(expected = "different errors")]
fn the_oracle_rejects_an_error_that_moved() {
let text = "\\begin{itemize}\n";
with_base(text, |base| {
assert!(
!base.errors.is_empty(),
"this fixture exists to carry an error"
);
let mut errors = base.errors.to_vec();
errors[0].start += 1;
let _ = finish(
base.green.clone(),
errors,
ReparseTier::Token,
base,
base.text,
);
});
}
}
}