From 035c14c061d8636db218d631c61c52083cd84ca3 Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:02:38 +0200 Subject: [PATCH 01/15] fix exponential parsing of nested unclosed blocks --- src/parsing/parser.rs | 71 +++++++++++++++++++++++++++++++++++++++++ src/parsing/rule/mod.rs | 13 +++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index d9457395b..ae18e54eb 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -30,6 +30,7 @@ use crate::tree::{ }; use std::borrow::Cow; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use std::{mem, ptr}; @@ -79,6 +80,11 @@ pub struct Parser<'r, 't> { // overriding later ones. bibliographies: Rc>>, + // Failed block parses can be retried after an enclosing block falls back + // to text. Keep terminal failures so malformed nested blocks do not cause + // the same suffix to be parsed exponentially many times. + block_failures: Rc>>, + // Flags accepts_partial: AcceptsPartial, in_footnote: bool, // Whether we're currently inside [[footnote]] ... [[/footnote]]. @@ -115,6 +121,7 @@ impl<'r, 't> Parser<'r, 't> { code_blocks: make_shared_vec(), footnotes: make_shared_vec(), bibliographies: Rc::new(RefCell::new(BibliographyList::new())), + block_failures: Rc::new(RefCell::new(HashMap::new())), accepts_partial: AcceptsPartial::None, in_footnote: false, has_footnote_block: false, @@ -292,6 +299,26 @@ impl<'r, 't> Parser<'r, 't> { self.footnotes.borrow_mut().truncate(count); } + pub(crate) fn cached_block_failure(&self, rule: &'static str) -> Option { + let key = self.block_failure_key(rule); + self.block_failures + .borrow() + .get(&key) + .filter(|failure| self.depth <= failure.depth) + .map(|failure| failure.error.clone()) + } + + pub(crate) fn cache_block_failure(&mut self, rule: &'static str, error: &ParseError) { + let key = self.block_failure_key(rule); + self.block_failures.borrow_mut().insert( + key, + CachedBlockFailure { + error: error.clone(), + depth: self.depth, + }, + ); + } + #[cold] pub fn remove_footnotes(&mut self) -> Vec>> { mem::take(&mut self.footnotes.borrow_mut()) @@ -597,6 +624,50 @@ impl<'r, 't> Parser<'r, 't> { } } +/// State which affects whether a terminal block parse can be retried. +/// +/// The recursion depth is intentionally not part of this key. The cached +/// failure records it separately and is only reused at an equal or shallower +/// depth, so recursion-limit errors retain their original behavior. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +struct BlockFailureKey { + token: usize, + rule: &'static str, + accepts_partial: AcceptsPartial, + in_footnote: bool, + has_footnote_block: bool, + start_of_line: bool, + footnote_index: usize, + html_block_index: usize, + code_block_index: usize, + table_of_contents_index: usize, +} + +#[derive(Debug, Clone)] +struct CachedBlockFailure { + error: ParseError, + depth: usize, +} + +impl<'r, 't> Parser<'r, 't> { + fn block_failure_key(&self, rule: &'static str) -> BlockFailureKey { + let mutable_state = self.get_mutable_state(); + + BlockFailureKey { + token: self.current as *const ExtractedToken<'t> as usize, + rule, + accepts_partial: self.accepts_partial, + in_footnote: self.in_footnote, + has_footnote_block: self.has_footnote_block, + start_of_line: self.start_of_line, + footnote_index: mutable_state.footnote_index, + html_block_index: mutable_state.html_block_index, + code_block_index: mutable_state.code_block_index, + table_of_contents_index: mutable_state.table_of_contents_index, + } + } +} + /// This struct stores the state of the mutable fields in `Parser`. /// /// This way, on rule failure, we can revert to the state these diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index bf8cec682..dbfacef77 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -66,6 +66,11 @@ impl Rule { } } + let memoize_failure = matches!(self.name, "block" | "block-star"); + if memoize_failure && let Some(error) = parser.cached_block_failure(self.name) { + return Err(error); + } + // Fork parser and try running the rule. let parser_state = parser.get_mutable_state(); let mut sub_parser = parser.clone_with_rule(self); @@ -85,7 +90,13 @@ impl Rule { // // While normally discarding the subparser is sufficient, // some annoying mutable fields are - Err(_) => parser.reset_mutable_state(parser_state), + Err(ref error) => { + if memoize_failure && error.kind() == ParseErrorKind::EndOfInput { + parser.cache_block_failure(self.name, &error); + } + + parser.reset_mutable_state(parser_state); + } } result From ce3e624d79a161203158d7f669565f083d64ee05 Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:02:54 +0200 Subject: [PATCH 02/15] add test --- src/test/large.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/large.rs b/src/test/large.rs index 9a0fd1ce6..66ab9b6bb 100644 --- a/src/test/large.rs +++ b/src/test/large.rs @@ -70,6 +70,27 @@ fn recursion_depth() { assert_eq!(element, &Element::Text(input_cow)); } +/// Unclosed nested blocks used to retry the same suffix exponentially often. +#[test] +fn nested_unclosed_divs() { + const ITERATIONS: usize = 22; + + let page_info = PageInfo::dummy(); + let settings = WikitextSettings::from_mode(WikitextMode::Page, Layout::Wikidot); + + let mut input = String::new(); + for _ in 0..ITERATIONS { + input.push_str("[[div]]\n"); + } + + crate::preprocess(&mut input); + let tokens = crate::tokenize(&input); + let (tree, errors) = crate::parse(&tokens, &page_info, &settings).into(); + + assert_eq!(errors.len(), ITERATIONS * 3); + assert_eq!(tree.elements.len(), 1); +} + /// Test the parser's ability to process large bodies #[test] #[ignore = "slow test"] From 008c2adee1548d0dc7dd29cb7f43d3d67e029e0e Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:04:38 +0200 Subject: [PATCH 03/15] fix clippy --- src/parsing/rule/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index dbfacef77..bf77130b2 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -92,7 +92,7 @@ impl Rule { // some annoying mutable fields are Err(ref error) => { if memoize_failure && error.kind() == ParseErrorKind::EndOfInput { - parser.cache_block_failure(self.name, &error); + parser.cache_block_failure(self.name, error); } parser.reset_mutable_state(parser_state); From 66f30f6c2459da38a7da155669ce11208d25aae1 Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:30:08 +0200 Subject: [PATCH 04/15] include bibliography state in block failure cache key --- src/parsing/parser.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index ae18e54eb..f034336c0 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -637,6 +637,7 @@ struct BlockFailureKey { in_footnote: bool, has_footnote_block: bool, start_of_line: bool, + bibliography_index: usize, footnote_index: usize, html_block_index: usize, code_block_index: usize, @@ -660,6 +661,7 @@ impl<'r, 't> Parser<'r, 't> { in_footnote: self.in_footnote, has_footnote_block: self.has_footnote_block, start_of_line: self.start_of_line, + bibliography_index: self.bibliographies.borrow().next_index(), footnote_index: mutable_state.footnote_index, html_block_index: mutable_state.html_block_index, code_block_index: mutable_state.code_block_index, From 289b79e442a623fd21ac669a33cb1fb5ac52c635 Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:30:43 +0200 Subject: [PATCH 05/15] add test --- src/test/large.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/large.rs b/src/test/large.rs index 66ab9b6bb..dea17bd4c 100644 --- a/src/test/large.rs +++ b/src/test/large.rs @@ -91,6 +91,26 @@ fn nested_unclosed_divs() { assert_eq!(tree.elements.len(), 1); } +/// Failed nested blocks must not reuse a cache entry across bibliography state. +#[test] +fn nested_unclosed_blocks_preserve_bibliography_indices() { + let page_info = PageInfo::dummy(); + let settings = WikitextSettings::from_mode(WikitextMode::Page, Layout::Wikidot); + + let mut input = String::from( + "[[div]]\n[[div]]\n[[bibliography]]\n: foo : bar\n[[/bibliography]]\n", + ); + crate::preprocess(&mut input); + let tokens = crate::tokenize(&input); + let (tree, errors) = crate::parse(&tokens, &page_info, &settings).into(); + + assert_eq!(errors.len(), 6); + assert!(matches!( + tree.elements.last(), + Some(Element::BibliographyBlock { index: 3, .. }) + )); +} + /// Test the parser's ability to process large bodies #[test] #[ignore = "slow test"] From 209d91c5ab0dcd9b86fd8c6a6e2f113a4ce1df2e Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:34:35 +0200 Subject: [PATCH 06/15] cache block failures after rolling back parser state --- src/parsing/rule/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index bf77130b2..b7792943f 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -91,11 +91,11 @@ impl Rule { // While normally discarding the subparser is sufficient, // some annoying mutable fields are Err(ref error) => { + parser.reset_mutable_state(parser_state); + if memoize_failure && error.kind() == ParseErrorKind::EndOfInput { parser.cache_block_failure(self.name, error); } - - parser.reset_mutable_state(parser_state); } } From 0a43b4145e7f705ab12b3fba300d315bb81340f7 Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:36:28 +0200 Subject: [PATCH 07/15] add test --- src/parsing/parser.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index f034336c0..85bf79228 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -736,3 +736,28 @@ fn parser_newline_flag() { [true, true, false, true, false, true, false, false], ); } + +#[test] +fn block_failure_cache_uses_rolled_back_state() { + use super::consume::consume; + use crate::layout::Layout; + use crate::settings::WikitextMode; + + let page_info = PageInfo::dummy(); + let settings = WikitextSettings::from_mode(WikitextMode::Page, Layout::Wikidot); + let mut input = String::from("[[div]]\n[[code]]\ncode\n[[/code]]\n"); + + crate::preprocess(&mut input); + let tokens = crate::tokenize(&input); + let mut parser = Parser::new(&tokens, &page_info, &settings); + + parser.step().expect("expected the first input token"); + let _ = consume(&mut parser).expect("unclosed div should fall back to text"); + + let failures = parser.block_failures.borrow(); + let failure = failures + .keys() + .next() + .expect("expected a cached block failure"); + assert_eq!(failure.code_block_index, 0); +} From b9d9469177ad4890fceff838b707cd5e0647271f Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:56:35 +0200 Subject: [PATCH 08/15] fix bibliography rollback for failed block parses --- src/parsing/parser.rs | 8 +++++++- src/tree/bibliography.rs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index 85bf79228..e6df65622 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -223,6 +223,7 @@ impl<'r, 't> Parser<'r, 't> { html_block_index: self.html_blocks.borrow().len(), code_block_index: self.code_blocks.borrow().len(), table_of_contents_index: self.table_of_contents.borrow().len(), + bibliography_index: self.bibliographies.borrow().next_index(), } } @@ -236,6 +237,7 @@ impl<'r, 't> Parser<'r, 't> { html_block_index, code_block_index, table_of_contents_index, + bibliography_index, }: ParserMutableState, ) { self.footnotes.borrow_mut().truncate(footnote_index); @@ -244,6 +246,9 @@ impl<'r, 't> Parser<'r, 't> { self.table_of_contents .borrow_mut() .truncate(table_of_contents_index); + self.bibliographies + .borrow_mut() + .truncate(bibliography_index); } // Parse settings helpers @@ -661,7 +666,7 @@ impl<'r, 't> Parser<'r, 't> { in_footnote: self.in_footnote, has_footnote_block: self.has_footnote_block, start_of_line: self.start_of_line, - bibliography_index: self.bibliographies.borrow().next_index(), + bibliography_index: mutable_state.bibliography_index, footnote_index: mutable_state.footnote_index, html_block_index: mutable_state.html_block_index, code_block_index: mutable_state.code_block_index, @@ -687,6 +692,7 @@ pub struct ParserMutableState { html_block_index: usize, code_block_index: usize, table_of_contents_index: usize, + bibliography_index: usize, } #[inline] diff --git a/src/tree/bibliography.rs b/src/tree/bibliography.rs index 93c2a3805..eba1191f1 100644 --- a/src/tree/bibliography.rs +++ b/src/tree/bibliography.rs @@ -102,6 +102,10 @@ impl<'t> BibliographyList<'t> { self.0.append(&mut other.0); } + pub(crate) fn truncate(&mut self, length: usize) { + self.0.truncate(length); + } + #[inline] pub fn is_empty(&self) -> bool { self.0.is_empty() From d4dca9ae585080432564f27240879236440ba82b Mon Sep 17 00:00:00 2001 From: jsthope Date: Wed, 29 Jul 2026 12:56:51 +0200 Subject: [PATCH 09/15] fix bibliography rollback test --- src/test/large.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/test/large.rs b/src/test/large.rs index dea17bd4c..96ff1b771 100644 --- a/src/test/large.rs +++ b/src/test/large.rs @@ -91,24 +91,30 @@ fn nested_unclosed_divs() { assert_eq!(tree.elements.len(), 1); } -/// Failed nested blocks must not reuse a cache entry across bibliography state. +/// Failed nested blocks must roll back bibliography state before caching. #[test] fn nested_unclosed_blocks_preserve_bibliography_indices() { + const ITERATIONS: usize = 22; + let page_info = PageInfo::dummy(); let settings = WikitextSettings::from_mode(WikitextMode::Page, Layout::Wikidot); - let mut input = String::from( - "[[div]]\n[[div]]\n[[bibliography]]\n: foo : bar\n[[/bibliography]]\n", - ); + let mut input = String::new(); + for _ in 0..ITERATIONS { + input.push_str("[[div]]\n"); + } + input.push_str("[[bibliography]]\n: foo : bar\n[[/bibliography]]\n"); + crate::preprocess(&mut input); let tokens = crate::tokenize(&input); let (tree, errors) = crate::parse(&tokens, &page_info, &settings).into(); - assert_eq!(errors.len(), 6); + assert_eq!(errors.len(), ITERATIONS * 3); assert!(matches!( tree.elements.last(), - Some(Element::BibliographyBlock { index: 3, .. }) + Some(Element::BibliographyBlock { index: 0, .. }) )); + assert_eq!(tree.bibliographies.next_index(), 1); } /// Test the parser's ability to process large bodies From 077ee306abcd00ba1b4181caba3f5d80640a6f76 Mon Sep 17 00:00:00 2001 From: jsthope <68657498+jsthope@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:31:29 +0200 Subject: [PATCH 10/15] Update src/parsing/rule/mod.rs Co-authored-by: Emmie --- src/parsing/rule/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index b7792943f..af1740b0f 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -66,7 +66,7 @@ impl Rule { } } - let memoize_failure = matches!(self.name, "block" | "block-star"); + let memoize_on_failure = matches!(self.name, "block" | "block-star"); if memoize_failure && let Some(error) = parser.cached_block_failure(self.name) { return Err(error); } From 7653700cdfd51a46cd2f492c81a87156c4dad677 Mon Sep 17 00:00:00 2001 From: jsthope Date: Sat, 1 Aug 2026 07:33:10 +0200 Subject: [PATCH 11/15] refact to memoize_on_failure --- src/parsing/rule/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index af1740b0f..4ad46d1eb 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -67,7 +67,7 @@ impl Rule { } let memoize_on_failure = matches!(self.name, "block" | "block-star"); - if memoize_failure && let Some(error) = parser.cached_block_failure(self.name) { + if memoize_on_failure && let Some(error) = parser.cached_block_failure(self.name) { return Err(error); } @@ -93,7 +93,7 @@ impl Rule { Err(ref error) => { parser.reset_mutable_state(parser_state); - if memoize_failure && error.kind() == ParseErrorKind::EndOfInput { + if memoize_on_failure && error.kind() == ParseErrorKind::EndOfInput { parser.cache_block_failure(self.name, error); } } From c7d1d5a999e76d9ea41c9cf68a16ec447d3f10f3 Mon Sep 17 00:00:00 2001 From: jsthope Date: Sat, 1 Aug 2026 07:33:49 +0200 Subject: [PATCH 12/15] cargo fmt --- src/parsing/rule/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index 4ad46d1eb..669bff3f0 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -67,7 +67,8 @@ impl Rule { } let memoize_on_failure = matches!(self.name, "block" | "block-star"); - if memoize_on_failure && let Some(error) = parser.cached_block_failure(self.name) { + if memoize_on_failure && let Some(error) = parser.cached_block_failure(self.name) + { return Err(error); } From 6018e607386330b7a1b8ec8d9e22cf33a68332bb Mon Sep 17 00:00:00 2001 From: jsthope <68657498+jsthope@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:34:14 +0200 Subject: [PATCH 13/15] Update src/parsing/rule/mod.rs Co-authored-by: Emmie --- src/parsing/rule/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index 669bff3f0..2db676366 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -90,7 +90,7 @@ impl Rule { // Rule failed, ensure that any changes are rolled back. // // While normally discarding the subparser is sufficient, - // some annoying mutable fields are + // some annoying mutable fields must be manually reset. Err(ref error) => { parser.reset_mutable_state(parser_state); From 1a804600fba3d4de96342c27a0ba3b467cc6785f Mon Sep 17 00:00:00 2001 From: jsthope Date: Sat, 1 Aug 2026 07:56:06 +0200 Subject: [PATCH 14/15] add an assert for tree.elements.len() --- src/test/large.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/large.rs b/src/test/large.rs index 96ff1b771..21f1fc286 100644 --- a/src/test/large.rs +++ b/src/test/large.rs @@ -110,6 +110,7 @@ fn nested_unclosed_blocks_preserve_bibliography_indices() { let (tree, errors) = crate::parse(&tokens, &page_info, &settings).into(); assert_eq!(errors.len(), ITERATIONS * 3); + assert_eq!(tree.elements.len(), 2); assert!(matches!( tree.elements.last(), Some(Element::BibliographyBlock { index: 0, .. }) From 655243deafe724e349ce49974c8b92fbe4735d4b Mon Sep 17 00:00:00 2001 From: jsthope Date: Sat, 1 Aug 2026 07:59:45 +0200 Subject: [PATCH 15/15] improve block failure cache key handling --- src/parsing/parser.rs | 33 +++++++++++++++++++++++++++------ src/parsing/rule/mod.rs | 3 ++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index e6df65622..cd8e2aeb5 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -31,6 +31,7 @@ use crate::tree::{ use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; +use std::hash::{Hash, Hasher}; use std::rc::Rc; use std::{mem, ptr}; @@ -83,7 +84,7 @@ pub struct Parser<'r, 't> { // Failed block parses can be retried after an enclosing block falls back // to text. Keep terminal failures so malformed nested blocks do not cause // the same suffix to be parsed exponentially many times. - block_failures: Rc>>, + block_failures: Rc, CachedBlockFailure>>>, // Flags accepts_partial: AcceptsPartial, @@ -304,7 +305,10 @@ impl<'r, 't> Parser<'r, 't> { self.footnotes.borrow_mut().truncate(count); } - pub(crate) fn cached_block_failure(&self, rule: &'static str) -> Option { + pub(crate) fn get_cached_block_failure( + &self, + rule: &'static str, + ) -> Option { let key = self.block_failure_key(rule); self.block_failures .borrow() @@ -635,8 +639,8 @@ impl<'r, 't> Parser<'r, 't> { /// failure records it separately and is only reused at an equal or shallower /// depth, so recursion-limit errors retain their original behavior. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -struct BlockFailureKey { - token: usize, +struct BlockFailureKey<'t> { + token: ExtractedTokenId<'t>, rule: &'static str, accepts_partial: AcceptsPartial, in_footnote: bool, @@ -649,6 +653,23 @@ struct BlockFailureKey { table_of_contents_index: usize, } +#[derive(Debug, Copy, Clone)] +struct ExtractedTokenId<'t>(*const ExtractedToken<'t>); + +impl PartialEq for ExtractedTokenId<'_> { + fn eq(&self, other: &Self) -> bool { + ptr::eq(self.0, other.0) + } +} + +impl Eq for ExtractedTokenId<'_> {} + +impl Hash for ExtractedTokenId<'_> { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + #[derive(Debug, Clone)] struct CachedBlockFailure { error: ParseError, @@ -656,11 +677,11 @@ struct CachedBlockFailure { } impl<'r, 't> Parser<'r, 't> { - fn block_failure_key(&self, rule: &'static str) -> BlockFailureKey { + fn block_failure_key(&self, rule: &'static str) -> BlockFailureKey<'t> { let mutable_state = self.get_mutable_state(); BlockFailureKey { - token: self.current as *const ExtractedToken<'t> as usize, + token: ExtractedTokenId(self.current), rule, accepts_partial: self.accepts_partial, in_footnote: self.in_footnote, diff --git a/src/parsing/rule/mod.rs b/src/parsing/rule/mod.rs index 2db676366..5bc420458 100644 --- a/src/parsing/rule/mod.rs +++ b/src/parsing/rule/mod.rs @@ -67,7 +67,8 @@ impl Rule { } let memoize_on_failure = matches!(self.name, "block" | "block-star"); - if memoize_on_failure && let Some(error) = parser.cached_block_failure(self.name) + if memoize_on_failure + && let Some(error) = parser.get_cached_block_failure(self.name) { return Err(error); }