Optimise DelimiterSyntax (dart-lang/markdown#492)

* Optimise DelimiterSyntax

* Indention!

* Update stats

* update stats

* better naming

Co-authored-by: Kevin Moore <kevmoo@google.com>
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart
index 712883f..5a7437a 100644
--- a/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart
+++ b/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart
@@ -4,6 +4,7 @@
 
 import '../ast.dart';
 import '../inline_parser.dart';
+import '../patterns.dart';
 import 'inline_syntax.dart';
 
 /// Matches syntax that has a pair of tags and becomes an element, like `*` for
@@ -185,7 +186,7 @@
 /// also be used by other extensions of [DelimiterSyntax].
 class DelimiterRun implements Delimiter {
   /// According to
-  /// [CommonMark](https://spec.commonmark.org/0.29/#punctuation-character):
+  /// [CommonMark](https://spec.commonmark.org/0.30/#unicode-punctuation-character):
   ///
   /// > A punctuation character is an ASCII punctuation character or anything in
   /// > the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or
@@ -193,8 +194,8 @@
   // This RegExp is inspired by
   // https://github.com/commonmark/commonmark.js/blob/1f7d09099c20d7861a674674a5a88733f55ff729/lib/inlines.js#L39.
   // I don't know if there is any way to simplify it or maintain it.
-  static final RegExp punctuation = RegExp('['
-      r'''!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~'''
+  static final unicodePunctuationPattern = RegExp('['
+      '$asciiPunctuationEscaped'
       r'\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE'
       r'\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E'
       r'\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E'
@@ -216,8 +217,12 @@
       r'\uFF5B\uFF5D\uFF5F-\uFF65'
       ']');
 
-  // TODO(srawlins): Unicode whitespace
-  static const whitespace = ' \t\r\n';
+  /// Unicode whitespace.
+  // See https://spec.commonmark.org/0.30/#unicode-whitespace-character.
+  // Unicode Zs: https://www.compart.com/en/unicode/category.
+  static const unicodeWhitespace = '\u0020\u0009\u000A\u000C\u000D'
+      '\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008'
+      '\u2009\u200A\u202F\u205F\u3000';
 
   @override
   Text node;
@@ -271,50 +276,46 @@
     required Text node,
     bool allowIntraWord = false,
   }) {
-    bool leftFlanking,
-        rightFlanking,
-        precededByPunctuation,
-        followedByPunctuation;
-    String preceding, following;
+    bool precededByWhitespace;
+    bool followedByWhitespace;
+    bool precededByPunctuation;
+    bool followedByPunctuation;
+
     if (runStart == 0) {
-      rightFlanking = false;
-      preceding = '\n';
+      precededByWhitespace = true;
+      precededByPunctuation = false;
     } else {
-      preceding = parser.source.substring(runStart - 1, runStart);
+      final preceding = parser.source.substring(runStart - 1, runStart);
+      precededByWhitespace = unicodeWhitespace.contains(preceding);
+      precededByPunctuation = !precededByWhitespace &&
+          unicodePunctuationPattern.hasMatch(preceding);
     }
-    precededByPunctuation = punctuation.hasMatch(preceding);
 
     if (runEnd == parser.source.length) {
-      leftFlanking = false;
-      following = '\n';
+      followedByWhitespace = true;
+      followedByPunctuation = false;
     } else {
-      following = parser.source.substring(runEnd, runEnd + 1);
-    }
-    followedByPunctuation = punctuation.hasMatch(following);
-
-    // http://spec.commonmark.org/0.30/#left-flanking-delimiter-run
-    if (whitespace.contains(following)) {
-      leftFlanking = false;
-    } else {
-      leftFlanking = !followedByPunctuation ||
-          whitespace.contains(preceding) ||
-          precededByPunctuation;
+      final following = parser.source.substring(runEnd, runEnd + 1);
+      followedByWhitespace = unicodeWhitespace.contains(following);
+      followedByPunctuation = !followedByWhitespace &&
+          unicodePunctuationPattern.hasMatch(following);
     }
 
-    // http://spec.commonmark.org/0.30/#right-flanking-delimiter-run
-    if (whitespace.contains(preceding)) {
-      rightFlanking = false;
-    } else {
-      rightFlanking = !precededByPunctuation ||
-          whitespace.contains(following) ||
-          followedByPunctuation;
-    }
+    // If it is a left-flanking delimiter run, see
+    // http://spec.commonmark.org/0.30/#left-flanking-delimiter-run.
+    final isLeftFlanking = !followedByWhitespace &&
+        (!followedByPunctuation ||
+            precededByWhitespace ||
+            precededByPunctuation);
 
-    if (!leftFlanking && !rightFlanking) {
-      // Could not parse a delimiter run.
-      return null;
-    }
+    // If it is a right-flanking delimiter run, see
+    // http://spec.commonmark.org/0.30/#right-flanking-delimiter-run.
+    final isRightFlanking = !precededByWhitespace &&
+        (!precededByPunctuation ||
+            followedByWhitespace ||
+            followedByPunctuation);
 
+    // Make sure the shorter delimiter takes precedence.
     tags.sort((a, b) => a.indicatorLength.compareTo(b.indicatorLength));
 
     return DelimiterRun._(
@@ -322,8 +323,8 @@
       char: parser.charAt(runStart),
       syntax: syntax,
       tags: tags,
-      isLeftFlanking: leftFlanking,
-      isRightFlanking: rightFlanking,
+      isLeftFlanking: isLeftFlanking,
+      isRightFlanking: isRightFlanking,
       isPrecededByPunctuation: precededByPunctuation,
       isFollowedByPunctuation: followedByPunctuation,
       allowIntraWord: allowIntraWord,
diff --git a/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit b/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit
index 514b651..b342fc3 100644
--- a/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit
+++ b/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit
@@ -13,7 +13,7 @@
 >>> Emphasis and strong emphasis - 353
 * a *
 <<<
-<p><em> a </em></p>
+<p>* a *</p>
 >>> Emphasis and strong emphasis - 354
 foo*bar*
 <<<
diff --git a/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit b/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit
index 3cde638..b6e8155 100644
--- a/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit
+++ b/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit
@@ -13,7 +13,7 @@
 >>> Emphasis and strong emphasis - 363
 * a *
 <<<
-<p><em> a </em></p>
+<p>* a *</p>
 >>> Emphasis and strong emphasis - 364
 foo*bar*
 <<<
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index a073b64..8ff5f7c 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -113,7 +113,7 @@
   "350": "strict",
   "351": "strict",
   "352": "strict",
-  "353": "fail",
+  "353": "strict",
   "354": "strict",
   "355": "strict",
   "356": "strict",
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
index 5935a21..f5fbe3b 100644
--- a/pkgs/markdown/tool/common_mark_stats.txt
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -4,7 +4,7 @@
    1 of    1 – 100.0%  Blank lines
   25 of   25 – 100.0%  Block quotes
   22 of   22 – 100.0%  Code spans
- 130 of  131 –  99.2%  Emphasis and strong emphasis
+ 131 of  131 – 100.0%  Emphasis and strong emphasis
   17 of   17 – 100.0%  Entity and numeric character references
   29 of   29 – 100.0%  Fenced code blocks
   15 of   15 – 100.0%  Hard line breaks
@@ -24,5 +24,5 @@
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 636 of  652 –  97.5%  TOTAL
- 617 of  636 –  97.0%  TOTAL Strict
+ 637 of  652 –  97.7%  TOTAL
+ 618 of  637 –  97.0%  TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
index f426ab8..820d869 100644
--- a/pkgs/markdown/tool/gfm_stats.json
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -129,7 +129,7 @@
   "360": "strict",
   "361": "strict",
   "362": "strict",
-  "363": "fail",
+  "363": "strict",
   "364": "strict",
   "365": "strict",
   "366": "strict",
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
index b460faa..dbeadd0 100644
--- a/pkgs/markdown/tool/gfm_stats.txt
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -6,7 +6,7 @@
   25 of   25 – 100.0%  Block quotes
   22 of   22 – 100.0%  Code spans
    0 of    1 –   0.0%  Disallowed Raw HTML (extension)
- 130 of  131 –  99.2%  Emphasis and strong emphasis
+ 131 of  131 – 100.0%  Emphasis and strong emphasis
   17 of   17 – 100.0%  Entity and numeric character references
   29 of   29 – 100.0%  Fenced code blocks
   15 of   15 – 100.0%  Hard line breaks
@@ -28,5 +28,5 @@
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 654 of  671 –  97.5%  TOTAL
- 634 of  654 –  96.9%  TOTAL Strict
+ 655 of  671 –  97.6%  TOTAL
+ 635 of  655 –  96.9%  TOTAL Strict