Pārlūkot izejas kodu

Readers now prioritized with compare methods instead of numbers

main
Rocketsoup 1 gadu atpakaļ
vecāks
revīzija
1f8c2c8285
3 mainītis faili ar 390 papildinājumiem un 184 dzēšanām
  1. 363
    178
      js/markdown.js
  2. 1
    1
      js/markdown.min.js
  3. 26
    5
      testjs.html

+ 363
- 178
js/markdown.js Parādīt failu

@@ -581,20 +581,23 @@ class MDState {
581 581
 	/** @type {MDState|null} */
582 582
 	#parent = null;
583 583
 
584
+	/** @type {MDConfig} */
585
+	config;
586
+
584 587
 	/**
585
-	 * Tuples of `priority:number` and `MDReader` sorted by `blockPriority`.
586
-	 * @type {Array}
588
+	 * Array of `MDReader`s sorted by block reading priority.
589
+	 * @type {MDReader[]}
587 590
 	 */
588 591
 	#readersByBlockPriority = [];
589 592
 
590 593
 	/**
591
-	 * Tuples of `priority:number` and `MDReader` sorted by `tokenizePriority`.
592
-	 * @type {Array}
594
+	 * Array of `MDReader`s sorted by tokenization priority.
595
+	 * @type {MDReader[]}
593 596
 	 */
594 597
 	#readersByTokenPriority = [];
595 598
 
596 599
 	/**
597
-	 * Tuples of `priority:number` and `MDReader` sorted by `substitutePriority`.
600
+	 * Tuples of `pass:number` and `MDReader` sorted substitution priority.
598 601
 	 * @type {Array}
599 602
 	 */
600 603
 	#readersBySubstitutePriority = [];
@@ -615,15 +618,18 @@ class MDState {
615 618
 
616 619
 	/**
617 620
 	 * @param {string[]} lines - lines of markdown text
618
-	 * @param {Array} readersByBlockPriority - tuple arrays of priority and MDReader
619
-	 * @param {Array} readersByTokenPriority - tuple arrays of priority and MDReader
621
+	 * @param {MDConfig} config
622
+	 * @param {MDReader[]} readersByBlockPriority
623
+	 * @param {MDReader[]} readersByTokenPriority
620 624
 	 * @param {Array} readersBySubstitutePriority - tuple arrays of priority and MDReader
621 625
 	 */
622 626
 	constructor(lines,
627
+		config=null,
623 628
 		readersByBlockPriority=null,
624 629
 		readersByTokenPriority=null,
625 630
 		readersBySubstitutePriority=null) {
626 631
 		this.#lines = lines;
632
+		this.config = config;
627 633
 		this.#readersByBlockPriority = readersByBlockPriority
628 634
 		this.#readersByTokenPriority = readersByTokenPriority
629 635
 		this.#readersBySubstitutePriority = readersBySubstitutePriority
@@ -639,6 +645,7 @@ class MDState {
639 645
 	copy(lines) {
640 646
 		let cp = new MDState(lines);
641 647
 		cp.#parent = this;
648
+		cp.config = this.config;
642 649
 		return cp;
643 650
 	}
644 651
 
@@ -698,8 +705,7 @@ class MDState {
698 705
 			this.p++;
699 706
 		}
700 707
 		if (!this.hasLines(1)) return null;
701
-		for (const tuple of this.root.#readersByBlockPriority) {
702
-			var reader = tuple[1];
708
+		for (const reader of this.root.#readersByBlockPriority) {
703 709
 			const startP = this.p;
704 710
 			const block = reader.readBlock(this);
705 711
 			if (block) {
@@ -759,11 +765,7 @@ class MDState {
759 765
 				continue;
760 766
 			}
761 767
 			var found = false;
762
-			for (const readerTuple of this.root.#readersByTokenPriority) {
763
-				/** @type {number} */
764
-				const priority = readerTuple[0];
765
-				/** @type {MDReader} */
766
-				const reader = readerTuple[1];
768
+			for (const reader of this.root.#readersByTokenPriority) {
767 769
 				const token = reader.readToken(this, remainder);
768 770
 				if (token === null) continue;
769 771
 				if (token === undefined) {
@@ -825,10 +827,10 @@ class MDState {
825 827
 			anyChanges = false;
826 828
 			for (const readerTuple of this.root.#readersBySubstitutePriority) {
827 829
 				/** @type {number} */
828
-				const priority = readerTuple[0];
830
+				const pass = readerTuple[0];
829 831
 				/** @type {MDReader} */
830 832
 				const reader = readerTuple[1];
831
-				const changed = reader.substituteTokens(this, priority, nodes);
833
+				const changed = reader.substituteTokens(this, pass, nodes);
832 834
 				if (!changed) continue;
833 835
 				anyChanges = true;
834 836
 				break;
@@ -905,76 +907,22 @@ class MDState {
905 907
  * any combination of these.
906 908
  * 1. **Blocks** - Processing an array of lines to find block-level structures,
907 909
  *    such as paragraphs, lists, tables, blockquotes, etc. and converting them
908
- *    into block-level `MDNode`s.
910
+ *    into block-level `MDNode`s. Override `readBlock`.
909 911
  * 2. **Inline tokens** - Carving up single lines of markdown into tokens for
910 912
  *    inline formatting, such as strong, emphasis, links, images, etc.
913
+ *    Override `readToken`.
911 914
  * 3. **Inline substitution** - Finding patterns of tokens and substituting them
912
- *    with `MDNode`s.
915
+ *    with `MDNode`s. Override `substituteTokens`. (`readToken` and
916
+ *   `substituteTokens` are usually overridden together.)
913 917
  * 
914
- * Each parsing phase consults each registered reader, checking if its
915
- * recognized syntax is located in a given place in the markdown. The order
916
- * the readers are tested is affected by priority values for each phase. This
917
- * allows syntactic ambiguities to be resolved in a consistent way (e.g.
918
- * `\*\*strong\*\*` and `\*emphasis\*` using similar syntax).
918
+ * Readers may have similar, ambiguous syntax (such as `**strong**` and
919
+ * `*emphasis*`) and need to process in a certain order. This can be done by
920
+ * overriding the `compare` methods to influence which readers to put before
921
+ * others in each phase. Furthermore, substitution can occur in multiple passes
922
+ * if necessary. These two mechanisms can be used to resolve ambiguities.
919 923
  */
920 924
 class MDReader {
921 925
 	/**
922
-	 * Block reading priority for this reader. Priority is a unitless relative
923
-	 * value used for sorting readers in the block reading process. Core readers
924
-	 * use a range of 0 to 100 but any value is permitted. Generally, more
925
-	 * distinctive, unambiguous block syntax should be prioritized first (low
926
-	 * priority number), while more general block syntax like paragraphs should
927
-	 * be prioritized last (high priority number). If the reader has no block-level
928
-	 * elements, can be set to `null` to skip during that phase.
929
-	 *
930
-	 * @type {number|null}
931
-	 */
932
-	get blockPriority() { return this.#blockPriority; };
933
-	#blockPriority;
934
-
935
-	/**
936
-	 * Inline tokenization priority for this reader. Priority is a unitless
937
-	 * relative value used for sorting readers in the tokenization process.
938
-	 * Core readers use a range of 0 to 100 but any value is permitted.
939
-	 * Generally, more distinctive, unambiguous token formats should be
940
-	 * prioritized first (low priority number), while more general, ambiguous
941
-	 * tokens should be prioritized last (high priority number). If the reader
942
-	 * has no inline-level elements, can be set to `null` to skip that phase.
943
-	 *
944
-	 * @type {number|null}
945
-	 */
946
-	get tokenizePriority() { return this.#tokenizePriority; };
947
-	#tokenizePriority;
948
-
949
-	/**
950
-	 * Inline token substitution priority/priorities for this reader. Priority
951
-	 * is a unitless relative value or values for sorting readers in the
952
-	 * tokenization process. Core readers use a range of 0 to 100 but any value
953
-	 * is permitted. Generally, more distinctive, unambiguous patterns should
954
-	 * be prioritized first (low priority number), while more general, ambiguous
955
-	 * patterns should be prioritized last (high priority number).
956
-	 * 
957
-	 * This property can be an array of numbers, in monotonically increasing
958
-	 * order. In that case, the same reader will occur 2 or more times in the
959
-	 * prioritization list. This can be useful when a reader handles multiple
960
-	 * patterns of differing priority. When `substituteTokens` is called, the
961
-	 * current priority is passed to differentiate which pass is being done.
962
-	 * 
963
-	 * If the reader has no inline-level elements, can be set to `null` to skip
964
-	 * that phase.
965
-	 *
966
-	 * @type {number|number[]|null}
967
-	 */
968
-	get substitutePriority() { return this.#substitutePriority; };
969
-	#substitutePriority;
970
-
971
-	constructor(blockPriority=100, tokenizePriority=100, substitutePriority=100) {
972
-		this.#blockPriority = blockPriority;
973
-		this.#tokenizePriority = tokenizePriority;
974
-		this.#substitutePriority = substitutePriority;
975
-	}
976
-
977
-	/**
978 926
 	 * Called before processing begins. `state.lines` is populated and the
979 927
 	 * line pointer `state.p` will be at `0`. Default implementation does nothing.
980 928
 	 *
@@ -1011,11 +959,11 @@ class MDReader {
1011 959
 	 * with one or more `MDNode` subclass instances.
1012 960
 	 * 
1013 961
 	 * @param {MDState} state
1014
-	 * @param {number} priority
962
+	 * @param {number} pass - what substitution pass this is, starting with 1
1015 963
 	 * @param {Array} tokens - mixed array of `MDToken` and `MDInlineNode` elements
1016 964
 	 * @returns {boolean} `true` if a substitution was performed, `false` if not
1017 965
 	 */
1018
-	substituteTokens(state, priority, tokens) { return false; }
966
+	substituteTokens(state, pass, tokens) { return false; }
1019 967
 
1020 968
 	/**
1021 969
 	 * Called after all parsing has completed. An array `blocks` is passed of all
@@ -1030,6 +978,160 @@ class MDReader {
1030 978
 	 * @param {MDBlockNode[]} blocks
1031 979
 	 */
1032 980
 	postProcess(state, blocks) {}
981
+
982
+	/**
983
+	 * @param {MDReader} other
984
+	 * @returns {number} -1 if this should be before other, 0 if the same or don't care, 1 if this should be after other
985
+	 */
986
+	compareBlockOrdering(other) {
987
+		return 0;
988
+	}
989
+
990
+	/**
991
+	 * @param {MDReader} other
992
+	 * @returns {number}
993
+	 */
994
+	compareTokenizeOrdering(other) {
995
+		return 0;
996
+	}
997
+
998
+	/**
999
+	 * @param {MDReader} other
1000
+	 * @param {number} pass
1001
+	 * @returns {number}
1002
+	 */
1003
+	compareSubstituteOrdering(other, pass) {
1004
+		return 0;
1005
+	}
1006
+
1007
+	get substitutionPassCount() { return 1; }
1008
+
1009
+	/**
1010
+	 * For sorting readers with ordering preferences. The `compare` methods
1011
+	 * don't have the properties of normal sorting compares so need to sort
1012
+	 * differently.
1013
+	 *
1014
+	 * @param {MDReader[]} arr - array to sort
1015
+	 * @param {function} compareFn - comparison function, taking two array element
1016
+	 *   arguments and returning -1, 0, or 1 for a < b, a == b, and a > b,
1017
+	 *   respectively
1018
+	 * @param {function} idFn - function for returning a unique hashable id for
1019
+	 *   the array element
1020
+	 * @returns {MDReader[]} sorted array
1021
+	 */
1022
+	static #kahnTopologicalSort(arr, compareFn, idFn) {
1023
+		const graph = {};
1024
+		const inDegrees = {};
1025
+		const valuesById = {};
1026
+	
1027
+		// Build the graph and compute in-degrees
1028
+		for (const elem of arr) {
1029
+			const id = idFn(elem);
1030
+			graph[id] = [];
1031
+			inDegrees[id] = 0;
1032
+			valuesById[id] = elem;
1033
+		}
1034
+	
1035
+		for (let i = 0; i < arr.length; i++) {
1036
+			const elemA = arr[i];
1037
+			const idA = idFn(elemA);
1038
+			for (let j = 0; j < arr.length; j++) {
1039
+				if (i === j) continue;
1040
+				const elemB = arr[j];
1041
+				const idB = idFn(elemB);
1042
+				const comparisonResult = compareFn(elemA, elemB);
1043
+				if (comparisonResult < 0) {
1044
+					graph[idA].push(idB);
1045
+					inDegrees[idB]++;
1046
+				} else if (comparisonResult > 0) {
1047
+					graph[idB].push(idA);
1048
+					inDegrees[idA]++;
1049
+				}
1050
+			}
1051
+		}
1052
+	
1053
+		// Initialize the queue with zero-inDegree nodes
1054
+		const queue = [];
1055
+		for (const elemId in inDegrees) {
1056
+			if (inDegrees[elemId] === 0) {
1057
+				queue.push(elemId);
1058
+			}
1059
+		}
1060
+	
1061
+		// Process the queue and build the topological order list
1062
+		const sorted = [];
1063
+		while (queue.length > 0) {
1064
+			const elemId = queue.shift();
1065
+			sorted.push(valuesById[elemId]);
1066
+			delete valuesById[elemId];
1067
+	
1068
+			for (const neighbor of graph[elemId]) {
1069
+				inDegrees[neighbor]--;
1070
+				if (inDegrees[neighbor] === 0) {
1071
+					queue.push(neighbor);
1072
+				}
1073
+			}
1074
+		}
1075
+		// Anything left over can go at the end. No ordering dependencies.
1076
+		for (const elemId in valuesById) {
1077
+			sorted.push(valuesById[elemId]);
1078
+		}
1079
+	
1080
+		return sorted;
1081
+	}
1082
+
1083
+	/**
1084
+	 * @param {MDReader[]} readers
1085
+	 * @returns {MDReader[]}
1086
+	 */
1087
+	static sortReaderForBlocks(readers) {
1088
+		const sorted = readers.slice();
1089
+		return MDReader.#kahnTopologicalSort(sorted, (a, b) => {
1090
+			return a.compareBlockOrdering(b);
1091
+			// if (ab != 0) return ab;
1092
+			// return -b.compareBlockOrdering(a);
1093
+		}, (elem) => elem.constructor.name);
1094
+	}
1095
+
1096
+	/**
1097
+	 * @param {MDReader[]} readers
1098
+	 * @returns {MDReader[]}
1099
+	 */
1100
+	static sortReadersForTokenizing(readers) {
1101
+		const sorted = readers.slice();
1102
+		return MDReader.#kahnTopologicalSort(sorted, (a, b) => {
1103
+			return a.compareTokenizeOrdering(b);
1104
+			// if (ab != 0) return ab;
1105
+			// return -b.compareTokenizeOrdering(a);
1106
+		}, (elem) => elem.constructor.name);
1107
+	}
1108
+
1109
+	/**
1110
+	 * @param {MDReader[]} readers
1111
+	 * @returns {MDReader[]}
1112
+	 */
1113
+	static sortReadersForSubstitution(readers) {
1114
+		var tuples = [];
1115
+		var maxPass = 1;
1116
+		for (const reader of readers) {
1117
+			const passCount = reader.substitutionPassCount;
1118
+			for (var pass = 1; pass <= passCount; pass++) {
1119
+				tuples.push([ pass, reader ]);
1120
+			}
1121
+			maxPass = Math.max(maxPass, pass);
1122
+		}
1123
+		var result = [];
1124
+		for (var pass = 1; pass <= maxPass; pass++) {
1125
+			var readersThisPass = tuples.filter((tup) => tup[0] == pass);
1126
+			const passResult = MDReader.#kahnTopologicalSort(readersThisPass, (a, b) => {
1127
+				const aReader = a[1];
1128
+				const bReader = b[1];
1129
+				return aReader.compareSubstituteOrdering(bReader, pass);
1130
+			}, (elem) => `${elem[1].constructor.name}:${elem[0]}`);
1131
+			result = result.concat(passResult);
1132
+		}
1133
+		return result;
1134
+	}
1033 1135
 }
1034 1136
 
1035 1137
 /**
@@ -1118,6 +1220,13 @@ class MDSubtextReader extends MDReader {
1118 1220
 		if (modifier) modifier.applyTo(block);
1119 1221
 		return block;
1120 1222
 	}
1223
+
1224
+	compareBlockOrdering(other) {
1225
+		if (other instanceof MDUnorderedListReader) {
1226
+			return -1;
1227
+		}
1228
+		return 0;
1229
+	}
1121 1230
 }
1122 1231
 
1123 1232
 /**
@@ -1410,6 +1519,13 @@ class MDHorizontalRuleReader extends MDReader {
1410 1519
 		}
1411 1520
 		return null;
1412 1521
 	}
1522
+
1523
+	compareBlockOrdering(other) {
1524
+		if (other instanceof MDUnorderedListReader) {
1525
+			return -1;
1526
+		}
1527
+		return 0;
1528
+	}
1413 1529
 }
1414 1530
 
1415 1531
 /**
@@ -1650,7 +1766,7 @@ class MDFootnoteReader extends MDReader {
1650 1766
 		return null;
1651 1767
 	}
1652 1768
 
1653
-	substituteTokens(state, priority, tokens) {
1769
+	substituteTokens(state, pass, tokens) {
1654 1770
 		var match;
1655 1771
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Footnote ])) {
1656 1772
 			let symbol = match.tokens[0].content;
@@ -1679,6 +1795,27 @@ class MDFootnoteReader extends MDReader {
1679 1795
 		if (Object.keys(state.footnotes).length == 0) return;
1680 1796
 		blocks.push(new MDFootnoteListNode());
1681 1797
 	}
1798
+
1799
+	compareBlockOrdering(other) {
1800
+		if (other instanceof MDLinkReader || other instanceof MDImageReader) {
1801
+			return -1;
1802
+		}
1803
+		return 0;
1804
+	}
1805
+
1806
+	compareTokenizeOrdering(other) {
1807
+		if (other instanceof MDLinkReader || other instanceof MDImageReader) {
1808
+			return -1;
1809
+		}
1810
+		return 0;
1811
+	}
1812
+
1813
+	compareSubstituteOrdering(other, pass) {
1814
+		if (other instanceof MDLinkReader || other instanceof MDImageReader) {
1815
+			return -1;
1816
+		}
1817
+		return 0;
1818
+	}
1682 1819
 }
1683 1820
 
1684 1821
 /**
@@ -1757,17 +1894,8 @@ class MDAbbreviationReader extends MDReader {
1757 1894
 
1758 1895
 /**
1759 1896
  * Block reader for simple paragraphs. Paragraphs are separated by a blank (or
1760
- * whitespace-only) line. This reader should be prioritized last since there
1761
- * is no distinguishing syntax.
1762
- * 
1763
- * Example:
1764
- * 
1765
- * > ```markdown
1766
- * > Lorem ipsum dolor
1767
- * > sit amet. This is all one paragraph.
1768
- * >
1769
- * > Beginning of a new paragraph.
1770
- * > ```
1897
+ * whitespace-only) line. This reader is prioritized after every other reader
1898
+ * since there is no distinguishing syntax.
1771 1899
  */
1772 1900
 class MDParagraphReader extends MDReader {
1773 1901
 	readBlock(state) {
@@ -1791,6 +1919,10 @@ class MDParagraphReader extends MDReader {
1791 1919
 		}
1792 1920
 		return null;
1793 1921
 	}
1922
+
1923
+	compareBlockOrdering(other) {
1924
+		return 1; // always dead last
1925
+	}
1794 1926
 }
1795 1927
 
1796 1928
 /**
@@ -1798,33 +1930,44 @@ class MDParagraphReader extends MDReader {
1798 1930
  * around some content.
1799 1931
  */
1800 1932
 class MDSimplePairInlineReader extends MDReader {
1933
+	get substitutionPassCount() { return 4; }
1934
+
1801 1935
 	/**
1802 1936
 	 * Attempts a substitution of a matched pair of delimiting token types.
1803 1937
 	 * If successful, the substitution is performed on `tokens` and `true` is
1804 1938
 	 * returned, otherwise `false` is returned and the array is untouched.
1805 1939
 	 * 
1806
-	 * If multiple `substitutePriority` values are specified, the first pass
1940
+	 * If `this.substitutionPassCount` is greater than 1, the first pass
1807 1941
 	 * will reject matches with the delimiting character inside the content
1808
-	 * tokens. If a single `substitutePriority` is given or a subsequent pass
1809
-	 * is performed with multiple values any contents will be accepted.
1942
+	 * tokens. If the reader uses a single pass or a subsequent pass is performed
1943
+	 * with multiple pass any contents will be accepted.
1810 1944
 	 * 
1811 1945
 	 * @param {MDState} state
1812
-	 * @param {number} priority
1946
+	 * @param {number} pass
1813 1947
 	 * @param {MDToken[]} tokens
1814 1948
 	 * @param {class} nodeClass
1815 1949
 	 * @param {MDTokenType} delimiter
1816 1950
 	 * @param {number} count - how many times the token is repeated to form the delimiter
1817 1951
 	 * @returns {boolean} `true` if substitution performed, `false` if not
1818 1952
 	 */
1819
-	attemptPair(state, priority, tokens, nodeClass, delimiter, count=1, plaintext=false) {
1953
+	attemptPair(state, pass, tokens, nodeClass, delimiter, count=1, plaintext=false) {
1954
+		// We do four passes. #1: doubles without inner tokens, #2: singles
1955
+		// without inner tokens, #3: doubles with paired inner tokens,
1956
+		// #4: singles with paired inner tokens
1957
+		if (count == 1 && pass != 2 && pass != 4) return;
1958
+		if (count == 2 && pass != 1 && pass != 3) return;
1820 1959
 		let delimiters = Array(count).fill(delimiter);
1821
-		let firstPassPriority = (this.substitutePriority instanceof Array) ? this.substitutePriority[0] : null;
1960
+		const isFirstOfMultiplePasses = this.substitutionPassCount > 1 && pass == 1;
1822 1961
 		let match = MDToken.findPairedTokens(tokens, delimiters, delimiters, function(content) {
1823 1962
 			const firstType = content[0] instanceof MDToken ? content[0].type : null;
1824 1963
 			const lastType = content[content.length - 1] instanceof MDToken ? content[content.length - 1].type : null;
1825 1964
 			if (firstType == MDTokenType.Whitespace) return false;
1826 1965
 			if (lastType == MDTokenType.Whitespace) return false;
1827
-			if (priority == firstPassPriority) {
1966
+			for (const token of content) {
1967
+				// Don't allow nesting
1968
+				if (token.constructor == nodeClass) return false;
1969
+			}
1970
+			if (isFirstOfMultiplePasses) {
1828 1971
 				var innerCount = 0;
1829 1972
 				for (let token of content) {
1830 1973
 					if (token instanceof MDToken && token.type == delimiter) innerCount++;
@@ -1849,11 +1992,18 @@ class MDEmphasisReader extends MDSimplePairInlineReader {
1849 1992
 		return null;
1850 1993
 	}
1851 1994
 
1852
-	substituteTokens(state, priority, tokens) {
1853
-		if (this.attemptPair(state, priority, tokens, MDEmphasisNode, MDTokenType.Asterisk)) return true;
1854
-		if (this.attemptPair(state, priority, tokens, MDEmphasisNode, MDTokenType.Underscore)) return true;
1995
+	substituteTokens(state, pass, tokens) {
1996
+		if (this.attemptPair(state, pass, tokens, MDEmphasisNode, MDTokenType.Asterisk)) return true;
1997
+		if (this.attemptPair(state, pass, tokens, MDEmphasisNode, MDTokenType.Underscore)) return true;
1855 1998
 		return false;
1856 1999
 	}
2000
+
2001
+	compareSubstituteOrdering(other, pass) {
2002
+		if (other instanceof MDStrongReader) {
2003
+			return 1;
2004
+		}
2005
+		return 0;
2006
+	}
1857 2007
 }
1858 2008
 
1859 2009
 class MDStrongReader extends MDSimplePairInlineReader {
@@ -1863,11 +2013,18 @@ class MDStrongReader extends MDSimplePairInlineReader {
1863 2013
 		return null;
1864 2014
 	}
1865 2015
 
1866
-	substituteTokens(state, priority, tokens) {
1867
-		if (this.attemptPair(state, priority, tokens, MDStrongNode, MDTokenType.Asterisk, 2)) return true;
1868
-		if (this.attemptPair(state, priority, tokens, MDStrongNode, MDTokenType.Underscore, 2)) return true;
2016
+	substituteTokens(state, pass, tokens) {
2017
+		if (this.attemptPair(state, pass, tokens, MDStrongNode, MDTokenType.Asterisk, 2)) return true;
2018
+		if (this.attemptPair(state, pass, tokens, MDStrongNode, MDTokenType.Underscore, 2)) return true;
1869 2019
 		return false;
1870 2020
 	}
2021
+
2022
+	compareSubstituteOrdering(other, pass) {
2023
+		if (other instanceof MDEmphasisReader) {
2024
+			return -1;
2025
+		}
2026
+		return 0;
2027
+	}
1871 2028
 }
1872 2029
 
1873 2030
 class MDStrikethroughReader extends MDSimplePairInlineReader {
@@ -1876,9 +2033,11 @@ class MDStrikethroughReader extends MDSimplePairInlineReader {
1876 2033
 		return null;
1877 2034
 	}
1878 2035
 
1879
-	substituteTokens(state, priority, tokens) {
1880
-		if (this.attemptPair(state, priority, tokens, MDStrikethroughNode, MDTokenType.Tilde, 2)) return true;
1881
-		if (this.attemptPair(state, priority, tokens, MDStrikethroughNode, MDTokenType.Tilde)) return true;
2036
+	substituteTokens(state, pass, tokens) {
2037
+		if (this.attemptPair(state, pass, tokens, MDStrikethroughNode, MDTokenType.Tilde, 2)) return true;
2038
+		if (state.config.strikethroughSingleTildeEnabled) {
2039
+			if (this.attemptPair(state, pass, tokens, MDStrikethroughNode, MDTokenType.Tilde)) return true;
2040
+		}
1882 2041
 		return false;
1883 2042
 	}
1884 2043
 }
@@ -1889,8 +2048,15 @@ class MDUnderlineReader extends MDSimplePairInlineReader {
1889 2048
 		return null;
1890 2049
 	}
1891 2050
 
1892
-	substituteTokens(state, priority, tokens) {
1893
-		return this.attemptPair(state, priority, tokens, MDUnderlineNode, MDTokenType.Underscore, 2);
2051
+	substituteTokens(state, pass, tokens) {
2052
+		return this.attemptPair(state, pass, tokens, MDUnderlineNode, MDTokenType.Underscore, 2);
2053
+	}
2054
+
2055
+	compareSubstituteOrdering(other, pass) {
2056
+		if (other instanceof MDStrongReader) {
2057
+			return -1;
2058
+		}
2059
+		return 0;
1894 2060
 	}
1895 2061
 }
1896 2062
 
@@ -1900,8 +2066,8 @@ class MDHighlightReader extends MDSimplePairInlineReader {
1900 2066
 		return null;
1901 2067
 	}
1902 2068
 
1903
-	substituteTokens(state, priority, tokens) {
1904
-		return this.attemptPair(state, priority, tokens, MDHighlightNode, MDTokenType.Equal, 2);
2069
+	substituteTokens(state, pass, tokens) {
2070
+		return this.attemptPair(state, pass, tokens, MDHighlightNode, MDTokenType.Equal, 2);
1905 2071
 	}
1906 2072
 }
1907 2073
 
@@ -1909,10 +2075,6 @@ class MDLinkReader extends MDReader {
1909 2075
 	static #simpleEmailRegex = new RegExp("^<(" + MDUtils.baseEmailRegex.source + ")>", "i");  // 1=email
1910 2076
 	static #simpleURLRegex = new RegExp("^<(" + MDUtils.baseURLRegex.source + ")>", "i");  // 1=URL
1911 2077
 
1912
-	constructor(tokenizePriority=0.0, substitutePriority=0.0) {
1913
-		super(tokenizePriority, substitutePriority);
1914
-	}
1915
-
1916 2078
 	readToken(state, line) {
1917 2079
 		var groups;
1918 2080
 		if (groups = MDUtils.tokenizeLabel(line)) {
@@ -1933,7 +2095,7 @@ class MDLinkReader extends MDReader {
1933 2095
 		return null;
1934 2096
 	}
1935 2097
 
1936
-	substituteTokens(state, priority, tokens) {
2098
+	substituteTokens(state, pass, tokens) {
1937 2099
 		var match;
1938 2100
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.URL ])) {
1939 2101
 			let text = match.tokens[0].content;
@@ -2009,7 +2171,7 @@ class MDReferencedLinkReader extends MDLinkReader {
2009 2171
 		return new MDNode([]); // empty
2010 2172
 	}
2011 2173
 
2012
-	substituteTokens(state, priority, tokens) {
2174
+	substituteTokens(state, pass, tokens) {
2013 2175
 		var match;
2014 2176
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.Label ])) {
2015 2177
 			let text = match.tokens[0].content;
@@ -2022,8 +2184,6 @@ class MDReferencedLinkReader extends MDLinkReader {
2022 2184
 }
2023 2185
 
2024 2186
 class MDImageReader extends MDLinkReader {
2025
-	substituteTokens(state, priority, tokens) {}
2026
-
2027 2187
 	readToken(state, line) {
2028 2188
 		const s = super.readToken(state, line);
2029 2189
 		if (s) return s;
@@ -2031,7 +2191,7 @@ class MDImageReader extends MDLinkReader {
2031 2191
 		return null;
2032 2192
 	}
2033 2193
 
2034
-	substituteTokens(state, priority, tokens) {
2194
+	substituteTokens(state, pass, tokens) {
2035 2195
 		var match;
2036 2196
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Bang, MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.URL ])) {
2037 2197
 			let alt = match.tokens[1].content;
@@ -2052,10 +2212,24 @@ class MDImageReader extends MDLinkReader {
2052 2212
 		}
2053 2213
 		return false;
2054 2214
 	}
2215
+
2216
+	compareSubstituteOrdering(other, pass) {
2217
+		if (other instanceof MDLinkReader) {
2218
+			return -1;
2219
+		}
2220
+		return 0;
2221
+	}
2055 2222
 }
2056 2223
 
2057 2224
 class MDReferencedImageReader extends MDReferencedLinkReader {
2058 2225
 	readBlock(state) { return null; }
2226
+
2227
+	compareSubstituteOrdering(other, pass) {
2228
+		if (other instanceof MDLinkReader) {
2229
+			return -1;
2230
+		}
2231
+		return 0;
2232
+	}
2059 2233
 }
2060 2234
 
2061 2235
 class MDCodeSpanReader extends MDSimplePairInlineReader {
@@ -2064,9 +2238,9 @@ class MDCodeSpanReader extends MDSimplePairInlineReader {
2064 2238
 		return null;
2065 2239
 	}
2066 2240
 
2067
-	substituteTokens(state, priority, tokens) {
2068
-		if (this.attemptPair(state, priority, tokens, MDCodeNode, MDTokenType.Backtick, 2, true)) return true;
2069
-		if (this.attemptPair(state, priority, tokens, MDCodeNode, MDTokenType.Backtick, 1, true)) return true;
2241
+	substituteTokens(state, pass, tokens) {
2242
+		if (this.attemptPair(state, pass, tokens, MDCodeNode, MDTokenType.Backtick, 2, true)) return true;
2243
+		if (this.attemptPair(state, pass, tokens, MDCodeNode, MDTokenType.Backtick, 1, true)) return true;
2070 2244
 	}
2071 2245
 }
2072 2246
 
@@ -2076,8 +2250,20 @@ class MDSubscriptReader extends MDSimplePairInlineReader {
2076 2250
 		return null;
2077 2251
 	}
2078 2252
 
2079
-	substituteTokens(state, priority, tokens) {
2080
-		return this.attemptPair(state, priority, tokens, MDSubscriptNode, MDTokenType.Tilde);
2253
+	preProcess(state) {
2254
+		// Causes a conflict
2255
+		state.config.strikethroughSingleTildeEnabled = false;
2256
+	}
2257
+
2258
+	substituteTokens(state, pass, tokens) {
2259
+		return this.attemptPair(state, pass, tokens, MDSubscriptNode, MDTokenType.Tilde);
2260
+	}
2261
+
2262
+	compareSubstituteOrdering(other, pass) {
2263
+		if (other instanceof MDStrikethroughReader) {
2264
+			return -1;
2265
+		}
2266
+		return 0;
2081 2267
 	}
2082 2268
 }
2083 2269
 
@@ -2087,8 +2273,8 @@ class MDSuperscriptReader extends MDSimplePairInlineReader {
2087 2273
 		return null;
2088 2274
 	}
2089 2275
 
2090
-	substituteTokens(state, priority, tokens) {
2091
-		return this.attemptPair(state, priority, tokens, MDSuperscriptNode, MDTokenType.Caret);
2276
+	substituteTokens(state, pass, tokens) {
2277
+		return this.attemptPair(state, pass, tokens, MDSuperscriptNode, MDTokenType.Caret);
2092 2278
 	}
2093 2279
 }
2094 2280
 
@@ -2099,7 +2285,7 @@ class MDHTMLTagReader extends MDReader {
2099 2285
 		return new MDToken(tag.original, MDTokenType.HTMLTag, null, null, tag)
2100 2286
 	}
2101 2287
 
2102
-	substituteTokens(state, priority, tokens) {
2288
+	substituteTokens(state, pass, tokens) {
2103 2289
 		var match;
2104 2290
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.HTMLTag ])) {
2105 2291
 			const tag = match.tokens[0].tag
@@ -2117,7 +2303,7 @@ class MDModifierReader extends MDReader {
2117 2303
 		return null;
2118 2304
 	}
2119 2305
 
2120
-	substituteTokens(state, priority, tokens) {
2306
+	substituteTokens(state, pass, tokens) {
2121 2307
 		// Modifiers are applied elsewhere, and if they're not it's fine if they're
2122 2308
 		// rendered as the original syntax.
2123 2309
 		return false;
@@ -3184,7 +3370,7 @@ class MDTagModifier {
3184 3370
 }
3185 3371
 
3186 3372
 class MDConfig {
3187
-
3373
+	strikethroughSingleTildeEnabled = true;
3188 3374
 }
3189 3375
 
3190 3376
 class Markdown {
@@ -3193,22 +3379,22 @@ class Markdown {
3193 3379
 	 * @type {MDReader[]}
3194 3380
 	 */
3195 3381
 	static standardReaders = [
3196
-		new MDUnderlinedHeaderReader(0),
3197
-		new MDHashHeaderReader(1),
3198
-		new MDBlockQuoteReader(3),
3199
-		new MDHorizontalRuleReader(9),  // prioritize before unordered list
3200
-		new MDUnorderedListReader(10),
3201
-		new MDOrderedListReader(11),
3202
-		new MDFencedCodeBlockReader(20),
3203
-		new MDIndentedCodeBlockReader(21),
3204
-		new MDParagraphReader(100),
3205
-
3206
-		new MDStrongReader(101, 10, [ 0, 2 ]),  // prioritize before emphasis (both use * and _)
3207
-		new MDEmphasisReader(101, 15, [ 5, 55 ]),
3208
-		new MDCodeSpanReader(101, 20, [ 10, 60 ]),
3209
-		new MDImageReader(101, 30, 15),  // prioritize before links
3210
-		new MDLinkReader(101, 35, 20),
3211
-		new MDHTMLTagReader(101, 80, 30),
3382
+		new MDUnderlinedHeaderReader(),
3383
+		new MDHashHeaderReader(),
3384
+		new MDBlockQuoteReader(),
3385
+		new MDHorizontalRuleReader(),
3386
+		new MDUnorderedListReader(),
3387
+		new MDOrderedListReader(),
3388
+		new MDFencedCodeBlockReader(),
3389
+		new MDIndentedCodeBlockReader(),
3390
+		new MDParagraphReader(),
3391
+
3392
+		new MDStrongReader(),
3393
+		new MDEmphasisReader(),
3394
+		new MDCodeSpanReader(),
3395
+		new MDImageReader(),
3396
+		new MDLinkReader(),
3397
+		new MDHTMLTagReader(),
3212 3398
 	];
3213 3399
 
3214 3400
 	/**
@@ -3217,20 +3403,20 @@ class Markdown {
3217 3403
 	 */
3218 3404
 	static allReaders = [
3219 3405
 		...this.standardReaders,
3220
-		new MDSubtextReader(2),
3221
-		new MDTableReader(40),
3222
-		new MDDefinitionListReader(50),
3223
-		new MDFootnoteReader(60, 5, 40),  // prioritize before links and images
3224
-		new MDAbbreviationReader(70),
3225
-
3226
-		new MDUnderlineReader(101, 5, [ 9, 11 ]),  // prioritize before emphasis and strong (both use _)
3227
-		// new MDSubscriptReader(101, 20, [ 11, 51 ]),  // prioritize before strikethrough (both use ~)
3228
-		new MDStrikethroughReader(101, 21, [ 12, 50 ]),
3229
-		new MDHighlightReader(101, 23, [ 13, 51 ]),
3230
-		new MDSuperscriptReader(101, 24, [ 14, 54 ]),
3231
-		new MDReferencedImageReader(91, 31, 16),
3232
-		new MDReferencedLinkReader(90, 36, 21),
3233
-		new MDModifierReader(101, 90, 45),
3406
+		new MDSubtextReader(),
3407
+		new MDTableReader(),
3408
+		new MDDefinitionListReader(),
3409
+		new MDFootnoteReader(),
3410
+		new MDAbbreviationReader(),
3411
+
3412
+		new MDUnderlineReader(),
3413
+		new MDSubscriptReader(),
3414
+		new MDStrikethroughReader(),
3415
+		new MDHighlightReader(),
3416
+		new MDSuperscriptReader(),
3417
+		new MDReferencedImageReader(),
3418
+		new MDReferencedLinkReader(),
3419
+		new MDModifierReader(),
3234 3420
 	];
3235 3421
 
3236 3422
 	/**
@@ -3243,34 +3429,32 @@ class Markdown {
3243 3429
 	 */
3244 3430
 	static completeParser = new Markdown(this.allReaders);
3245 3431
 
3432
+	/**
3433
+	 * @type {MDConfig}
3434
+	 */
3435
+	config;
3436
+
3246 3437
 	#readers;
3247 3438
 
3439
+	/** @type {MDReader[]} */
3248 3440
 	#readersByBlockPriority;
3441
+	/** @type {MDReader[]} */
3249 3442
 	#readersByTokenPriority;
3443
+	/** @type {Array} */
3250 3444
 	#readersBySubstitutePriority;
3251 3445
 
3252 3446
 	/**
3253 3447
 	 * Creates a Markdown parser with the given syntax readers.
3254 3448
 	 *
3255 3449
 	 * @param {MDReader[]} readers
3450
+	 * @param {MDConfig} config
3256 3451
 	 */
3257
-	constructor(readers=Markdown.allReaders) {
3452
+	constructor(readers=Markdown.allReaders, config=new MDConfig()) {
3258 3453
 		this.#readers = readers;
3259
-		const duplicateAndSort = function(priorityFn) {
3260
-			var result = [];
3261
-			for (const reader of readers) {
3262
-				const p = priorityFn(reader);
3263
-				const priorities = (p instanceof Array) ? p : [ p ];
3264
-				for (const priority of priorities) {
3265
-					result.push([priority, reader]);
3266
-				}
3267
-			}
3268
-			result.sort((a, b) => a[0] - b[0]);
3269
-			return result;
3270
-		}
3271
-		this.#readersByBlockPriority = duplicateAndSort((reader) => reader.blockPriority);
3272
-		this.#readersByTokenPriority = duplicateAndSort((reader) => reader.tokenizePriority);
3273
-		this.#readersBySubstitutePriority = duplicateAndSort((reader) => reader.substitutePriority);
3454
+		this.config = config;
3455
+		this.#readersByBlockPriority = MDReader.sortReaderForBlocks(readers);
3456
+		this.#readersByTokenPriority = MDReader.sortReadersForTokenizing(readers);
3457
+		this.#readersBySubstitutePriority = MDReader.sortReadersForSubstitution(readers);
3274 3458
 	}
3275 3459
 
3276 3460
 	/**
@@ -3282,6 +3466,7 @@ class Markdown {
3282 3466
 	toHTML(markdown) {
3283 3467
 		const lines = markdown.split(/(?:\n|\r|\r\n)/);
3284 3468
 		const state = new MDState(lines,
3469
+			this.config,
3285 3470
 			this.#readersByBlockPriority,
3286 3471
 			this.#readersByTokenPriority,
3287 3472
 			this.#readersBySubstitutePriority);

+ 1
- 1
js/markdown.min.js
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 26
- 5
testjs.html Parādīt failu

@@ -735,16 +735,37 @@
735 735
 					this.assertEqual(actual, expected);
736 736
 				}
737 737
 
738
-				test_strikethrough_single() {
739
-					let markdown = 'Lorem ~ipsum~ dolor';
738
+				test_strikethrough_double() {
739
+					let markdown = 'Lorem ~~ipsum~~ dolor';
740 740
 					let expected = 'Lorem <s>ipsum</s> dolor';
741 741
 					let actual = this.md(markdown);
742 742
 					this.assertEqual(actual, expected);
743 743
 				}
744 744
 
745
-				test_strikethrough_double() {
746
-					let markdown = 'Lorem ~~ipsum~~ dolor';
747
-					let expected = 'Lorem <s>ipsum</s> dolor';
745
+				test_subscript() {
746
+					let markdown = 'H~2~O';
747
+					let expected = 'H<sub>2</sub>O';
748
+					let actual = this.md(markdown);
749
+					this.assertEqual(actual, expected);
750
+				}
751
+
752
+				test_superscript() {
753
+					let markdown = 'E=mc^2^';
754
+					let expected = 'E=mc<sup>2</sup>';
755
+					let actual = this.md(markdown);
756
+					this.assertEqual(actual, expected);
757
+				}
758
+
759
+				test_highlight() {
760
+					let markdown = 'Lorem ==ipsum== dolor';
761
+					let expected = 'Lorem <mark>ipsum</mark> dolor';
762
+					let actual = this.md(markdown);
763
+					this.assertEqual(actual, expected);
764
+				}
765
+
766
+				test_underline() {
767
+					let markdown = 'Lorem __ipsum__ dolor';
768
+					let expected = 'Lorem <u>ipsum</u> dolor';
748 769
 					let actual = this.md(markdown);
749 770
 					this.assertEqual(actual, expected);
750 771
 				}

Notiek ielāde…
Atcelt
Saglabāt