Repository: pulldown-cmark/pulldown-cmark
Pull request #1024 (2025-02-20 16:41:35)

Do not use the same lower limit for `^` and `~`
This fixes some inconsistent behavior between superscript, subscript, and emphasis.


Patch:
diff --git a/pulldown-cmark/specs/super_sub.txt b/pulldown-cmark/specs/super_sub.txt
index 9cd7b9f..05ec581 100644
--- a/pulldown-cmark/specs/super_sub.txt
+++ b/pulldown-cmark/specs/super_sub.txt
@@ -59,3 +59,26 @@ y=x^2^a+xb+c
 <p>H<sub>2</sub>O</p>
 <p>y=x<sup>2</sup>a+xb+c</p>
 ````````````````````````````````
+
+Superscript and subscript cannot group with a different delimiter count.
+
+```````````````````````````````` example_super_sub
+~foo~~
+
+^bar^^
+.
+<p>~foo~~</p>
+<p>^bar^^</p>
+````````````````````````````````
+
+The lower bound of superscript and subscript are separate.
+Emphasis example included for analogy.
+
+```````````````````````````````` example_super_sub
+~foo^~^bar~
+
+*foo_*_bar*
+.
+<p><sub>foo^</sub>^bar~</p>
+<p><em>foo_</em>_bar*</p>
+````````````````````````````````
diff --git a/pulldown-cmark/src/parse.rs b/pulldown-cmark/src/parse.rs
index e3d9f2a..a54709f 100644
--- a/pulldown-cmark/src/parse.rs
+++ b/pulldown-cmark/src/parse.rs
@@ -1548,7 +1548,7 @@ struct InlineStack {
     // a strikethrough delimiter will never match with any element
     // in the stack with index smaller than
     // `lower_bounds[InlineStack::TILDES]`.
-    lower_bounds: [usize; 9],
+    lower_bounds: [usize; 10],
 }
 
 impl InlineStack {
@@ -1560,6 +1560,7 @@ impl InlineStack {
     const ASTERISK_BASE: usize = 2;
     const TILDES: usize = 5;
     const UNDERSCORE_BASE: usize = 6;
+    const CIRCUMFLEXES: usize = 9;
 
     fn pop_all(&mut self, tree: &mut Tree<Item>) {
         for el in self.stack.drain(..) {
@@ -1569,7 +1570,7 @@ impl InlineStack {
                 };
             }
         }
-        self.lower_bounds = [0; 9];
+        self.lower_bounds = [0; 10];
     }
 
     fn get_lowerbound(&self, c: u8, count: usize, both: bool) -> usize {
@@ -1593,6 +1594,8 @@ impl InlineStack {
                     self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH],
                 )
             }
+        } else if c == b'^' {
+            self.lower_bounds[InlineStack::CIRCUMFLEXES]
         } else {
             self.lower_bounds[InlineStack::TILDES]
         }
@@ -1610,6 +1613,8 @@ impl InlineStack {
             if !both {
                 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH] = new_bound;
             }
+        } else if c == b'^' {
+            self.lower_bounds[InlineStack::CIRCUMFLEXES] = new_bound;
         } else {
             self.lower_bounds[InlineStack::TILDES] = new_bound;
         }
@@ -1637,7 +1642,7 @@ impl InlineStack {
             .cloned()
             .enumerate()
             .rfind(|(_, el)| {
-                if c == b'~' && run_length != el.run_length {
+                if (c == b'~' || c == b'^') && run_length != el.run_length {
                     return false;
                 }
                 el.c == c
@@ -1670,6 +1675,8 @@ impl InlineStack {
     fn push(&mut self, el: InlineEl) {
         if el.c == b'~' {
             self.trim_lower_bound(InlineStack::TILDES);
+        } else if el.c == b'^' {
+            self.trim_lower_bound(InlineStack::CIRCUMFLEXES);
         }
         self.stack.push(el)
     }


Test patch:
diff --git a/pulldown-cmark/tests/suite/super_sub.rs b/pulldown-cmark/tests/suite/super_sub.rs
index 6c2492b..8614ff3 100644
--- a/pulldown-cmark/tests/suite/super_sub.rs
+++ b/pulldown-cmark/tests/suite/super_sub.rs
@@ -85,3 +85,29 @@ y=x^2^a+xb+c
 
     test_markdown_html(original, expected, false, false, false, true, false, false);
 }
+
+#[test]
+fn super_sub_test_9() {
+    let original = r##"~foo~~
+
+^bar^^
+"##;
+    let expected = r##"<p>~foo~~</p>
+<p>^bar^^</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false, true, false, false);
+}
+
+#[test]
+fn super_sub_test_10() {
+    let original = r##"~foo^~^bar~
+
+*foo_*_bar*
+"##;
+    let expected = r##"<p><sub>foo^</sub>^bar~</p>
+<p><em>foo_</em>_bar*</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false, true, false, false);
+}