mirror of
https://github.com/typst/typst
synced 2025-05-16 18:15:29 +08:00
Improve span stability after incremental parsing
This commit is contained in:
parent
163c2e1aa2
commit
7a46a85d3e
@ -164,6 +164,16 @@ impl SyntaxNode {
|
||||
Repr::Error(node) => Arc::make_mut(node).error.span = span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the two syntax nodes are the same apart from spans.
|
||||
pub fn spanless_eq(&self, other: &Self) -> bool {
|
||||
match (&self.0, &other.0) {
|
||||
(Repr::Leaf(a), Repr::Leaf(b)) => a.spanless_eq(b),
|
||||
(Repr::Inner(a), Repr::Inner(b)) => a.spanless_eq(b),
|
||||
(Repr::Error(a), Repr::Error(b)) => a.spanless_eq(b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxNode {
|
||||
@ -326,6 +336,11 @@ impl LeafNode {
|
||||
fn len(&self) -> usize {
|
||||
self.text.len()
|
||||
}
|
||||
|
||||
/// Whether the two leaf nodes are the same apart from spans.
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.kind == other.kind && self.text == other.text
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LeafNode {
|
||||
@ -440,6 +455,20 @@ impl InnerNode {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether the two inner nodes are the same apart from spans.
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.kind == other.kind
|
||||
&& self.len == other.len
|
||||
&& self.descendants == other.descendants
|
||||
&& self.erroneous == other.erroneous
|
||||
&& self.children.len() == other.children.len()
|
||||
&& self
|
||||
.children
|
||||
.iter()
|
||||
.zip(&other.children)
|
||||
.all(|(a, b)| a.spanless_eq(b))
|
||||
}
|
||||
|
||||
/// Replaces a range of children with a replacement.
|
||||
///
|
||||
/// May have mutated the children if it returns `Err(_)`.
|
||||
@ -449,6 +478,30 @@ impl InnerNode {
|
||||
replacement: Vec<SyntaxNode>,
|
||||
) -> NumberingResult {
|
||||
let Some(id) = self.span.id() else { return Err(Unnumberable) };
|
||||
let mut replacement_range = 0..replacement.len();
|
||||
|
||||
// Trim off common prefix.
|
||||
while range.start < range.end
|
||||
&& replacement_range.start < replacement_range.end
|
||||
&& self.children[range.start]
|
||||
.spanless_eq(&replacement[replacement_range.start])
|
||||
{
|
||||
range.start += 1;
|
||||
replacement_range.start += 1;
|
||||
}
|
||||
|
||||
// Trim off common suffix.
|
||||
while range.start < range.end
|
||||
&& replacement_range.start < replacement_range.end
|
||||
&& self.children[range.end - 1]
|
||||
.spanless_eq(&replacement[replacement_range.end - 1])
|
||||
{
|
||||
range.end -= 1;
|
||||
replacement_range.end -= 1;
|
||||
}
|
||||
|
||||
let mut replacement_vec = replacement;
|
||||
let replacement = &replacement_vec[replacement_range.clone()];
|
||||
let superseded = &self.children[range.clone()];
|
||||
|
||||
// Compute the new byte length.
|
||||
@ -470,9 +523,9 @@ impl InnerNode {
|
||||
|| self.children[range.end..].iter().any(SyntaxNode::erroneous));
|
||||
|
||||
// Perform the replacement.
|
||||
let replacement_count = replacement.len();
|
||||
self.children.splice(range.clone(), replacement);
|
||||
range.end = range.start + replacement_count;
|
||||
self.children
|
||||
.splice(range.clone(), replacement_vec.drain(replacement_range.clone()));
|
||||
range.end = range.start + replacement_range.len();
|
||||
|
||||
// Renumber the new children. Retries until it works, taking
|
||||
// exponentially more children into account.
|
||||
@ -577,6 +630,11 @@ impl ErrorNode {
|
||||
fn hint(&mut self, hint: impl Into<EcoString>) {
|
||||
self.error.hints.push(hint.into());
|
||||
}
|
||||
|
||||
/// Whether the two leaf nodes are the same apart from spans.
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.text == other.text && self.error.spanless_eq(&other.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ErrorNode {
|
||||
@ -597,6 +655,13 @@ pub struct SyntaxError {
|
||||
pub hints: Vec<EcoString>,
|
||||
}
|
||||
|
||||
impl SyntaxError {
|
||||
/// Whether the two errors are the same apart from spans.
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.message == other.message && self.hints == other.hints
|
||||
}
|
||||
}
|
||||
|
||||
/// A syntax node in a context.
|
||||
///
|
||||
/// Knows its exact offset in the file and provides access to its
|
||||
|
@ -25,7 +25,7 @@ use typst::doc::{Document, Frame, FrameItem, Meta};
|
||||
use typst::eval::{eco_format, func, Bytes, Datetime, Library, NoneValue, Tracer, Value};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::geom::{Abs, Color, Smart};
|
||||
use typst::syntax::{FileId, PackageVersion, Source, Span, SyntaxNode, VirtualPath};
|
||||
use typst::syntax::{FileId, PackageVersion, Source, SyntaxNode, VirtualPath};
|
||||
use typst::{World, WorldExt};
|
||||
use typst_library::layout::{Margin, PageElem};
|
||||
use typst_library::text::{TextElem, TextSize};
|
||||
@ -779,7 +779,6 @@ fn test_reparse(
|
||||
];
|
||||
|
||||
let mut ok = true;
|
||||
|
||||
let mut apply = |replace: Range<usize>, with| {
|
||||
let mut incr_source = Source::detached(text);
|
||||
if incr_source.root().len() != text.len() {
|
||||
@ -795,18 +794,14 @@ fn test_reparse(
|
||||
|
||||
let edited_src = incr_source.text();
|
||||
let ref_source = Source::detached(edited_src);
|
||||
let mut ref_root = ref_source.root().clone();
|
||||
let mut incr_root = incr_source.root().clone();
|
||||
let ref_root = ref_source.root();
|
||||
let incr_root = incr_source.root();
|
||||
|
||||
// Ensures that the span numbering invariants hold.
|
||||
let spans_ok = test_spans(output, &ref_root) && test_spans(output, &incr_root);
|
||||
let spans_ok = test_spans(output, ref_root) && test_spans(output, incr_root);
|
||||
|
||||
// Remove all spans so that the comparison works out.
|
||||
let tree_ok = {
|
||||
ref_root.synthesize(Span::detached());
|
||||
incr_root.synthesize(Span::detached());
|
||||
ref_root == incr_root
|
||||
};
|
||||
// Ensure that the reference and incremental trees are the same.
|
||||
let tree_ok = ref_root.spanless_eq(incr_root);
|
||||
|
||||
if !tree_ok {
|
||||
writeln!(
|
||||
|
Loading…
x
Reference in New Issue
Block a user