jackson

Finished
22 return `<${tag}>${text}</${tag}>`;
33}
44
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
5// Parse bold text (__text__ -> <strong>text</strong>)
6function parseBoldText(markdown) {
7 const boldPattern = new RegExp('__' + '(.+)' + '__');
8 const boldReplacement = '<strong>$1</strong>';
9 return markdown.replace(boldPattern, boldReplacement);
710}
811
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
12// Parse italic text (_text_ -> <em>text</em>)
13function parseItalicText(markdown) {
14 const italicPattern = new RegExp('_' + '(.+)' + '_');
15 const italicReplacement = '<em>$1</em>';
16 return markdown.replace(italicPattern, italicReplacement);
1317}
1418
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
19// Parse both bold and italic formatting
20function parseInlineFormatting(markdown) {
21 // Parse bold first, then italic
22 const withBold = parseBoldText(markdown);
23 const withItalic = parseItalicText(withBold);
24 return withItalic;
1725}
1826
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27// Parse text with inline formatting and wrap in paragraph tags if not in a list
28function parseFormattedText(markdown, inList) {
29 const formattedText = parseInlineFormatting(markdown);
30 if (inList) {
31 // Inside a list, don't wrap in paragraph tags
32 return formattedText;
2733 } else {
28 return wrap(parsedText, 'p');
34 // Outside a list, wrap in paragraph tags
35 return wrap(formattedText, 'p');
2936 }
3037}
3138
32function parseHeader(markdown, list) {
33 let count = 0;
39// Parse markdown header (# Header -> <h1>Header</h1>)
40function parseHeaderLine(markdown, inList) {
41 // Count consecutive '#' characters at the start of the line
42 let hashCount = 0;
3443 for (let i = 0; i < markdown.length; i++) {
3544 if (markdown[i] === '#') {
36 count += 1;
45 hashCount++;
3746 } else {
3847 break;
3948 }
4049 }
41 if (count === 0 || count > 6) {
42 return [null, list];
50
51 // Valid headers have 1-6 '#' characters followed by a space
52 if (hashCount === 0 || hashCount > 6) {
53 return [null, inList];
4354 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
55
56 // Extract header text (skip '#' characters and the following space)
57 const headerText = markdown.substring(hashCount + 1);
58 const headerTag = `h${hashCount}`;
59 const headerHtml = wrap(headerText, headerTag);
60
61 // If we're in a list, close it before the header
62 if (inList) {
4763 return [`</ul>${headerHtml}`, false];
4864 } else {
4965 return [headerHtml, false];
5066 }
5167}
5268
53function parseLineItem(markdown, list) {
69// Parse list item (* item -> <li>item</li>)
70function parseListItemLine(markdown, inList) {
5471 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
72 // Extract list item text (skip '* ' prefix)
73 const itemText = markdown.substring(2);
74
75 // Parse inline formatting but don't wrap in paragraph tags (since we're in a list)
76 const formattedText = parseFormattedText(itemText, true);
77 const listItemHtml = wrap(formattedText, 'li');
78
79 // If we're already in a list, just return the list item
80 // Otherwise, start a new list
81 if (inList) {
82 return [listItemHtml, true];
5883 } else {
59 return [`<ul>${innerHtml}`, true];
84 return [`<ul>${listItemHtml}`, true];
6085 }
6186 }
62 return [null, list];
87
88 return [null, inList];
6389}
6490
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
91// Parse paragraph (fallback for regular text lines)
92function parseParagraphLine(markdown, inList) {
93 // Parse inline formatting and wrap in paragraph tags
94 const paragraphHtml = parseFormattedText(markdown, false);
95
96 // If we're in a list, close it before the paragraph
97 if (inList) {
98 return [`</ul>${paragraphHtml}`, false];
6899 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
100 return [paragraphHtml, false];
70101 }
71102}
72103
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
104// Parse a single line by trying different parsers in sequence
105function parseLine(markdown, inList) {
106 // Try to parse as header
107 let [result, newList] = parseHeaderLine(markdown, inList);
108
109 // If not a header, try to parse as list item
75110 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
111 [result, newList] = parseListItemLine(markdown, inList);
77112 }
113
114 // If not a list item, parse as paragraph (fallback)
78115 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
116 [result, newList] = parseParagraphLine(markdown, inList);
80117 }
118
119 // This should never happen as parseParagraphLine always returns a result
81120 if (result === null) {
82 throw new Error('Remove this line and implement the function');
121 throw new Error('Failed to parse line: ' + markdown);
83122 }
84 return [result, inListAfter];
123
124 return [result, newList];
85125}
86126
87127/**
88 * @param {string} markdown
89 * @returns {string}
128 * Parse markdown text and convert it to HTML
129 * @param {string} markdown - The markdown text to parse
130 * @returns {string} - The resulting HTML
90131 */
91132export function parse(markdown) {
92133 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
134 let htmlResult = '';
135 let inList = false;
136
137 // Process each line sequentially
138 for (const line of lines) {
139 const [lineHtml, newListState] = parseLine(line, inList);
140 htmlResult += lineHtml;
141 inList = newListState;
99142 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
143
144 // Close any open list at the end of the document
145 if (inList) {
146 htmlResult += '</ul>';
104147 }
148
149 return htmlResult;
105150}
Test NameStatus
parses normal text as a paragraph
Pass
parsing italics
Pass
parsing bold text
Pass
mixed normal, italics and bold text
Pass
with h1 header level
Pass
with h2 header level
Pass
with h3 header level
Pass
with h4 header level
Pass
with h5 header level
Pass
with h6 header level
Pass
with h7 header level
Pass
unordered lists
Pass
with a little bit of everything
Pass
with markdown symbols in the header text that should not be interpreted
Pass
with markdown symbols in the list item text that should not be interpreted
Pass
with markdown symbols in the paragraph text that should not be interpreted
Pass
unordered lists close properly with preceding and following lines
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.