hope

Finished
1// Helper function to wrap text in HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
6// Parse bold text (__text__ -> <strong>text</strong>)
7function parseBold(markdown) {
8 // Match double underscores with meaningful content
9 // Use non-greedy matching but ensure there's actual content
10 return markdown.replace(/__(.+?)__/g, '<strong>$1</strong>');
711}
812
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
13// Parse italic text (_text_ -> <em>text</em>)
14function parseItalic(markdown) {
15 // Match single underscores with meaningful content
16 // But avoid matching underscores that are part of other formatting
17 // Use word boundaries to be more precise about valid formatting
18 return markdown.replace(/\B_(\S[^_]*?\S)_\B/g, '<em>$1</em>');
1319}
1420
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
21// Parse inline formatting (bold and italic)
22function parseInlineFormatting(markdown) {
23 // Parse bold first, then italic to handle nested formatting correctly
24 return parseItalic(parseBold(markdown));
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) {
27// Parse text with appropriate wrapping
28function parseText(markdown, inList) {
29 const parsedText = parseInlineFormatting(markdown);
30 if (inList) {
2631 return parsedText;
2732 } else {
2833 return wrap(parsedText, 'p');
2934 }
3035}
3136
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
37 } else {
38 break;
39 }
37// Parse headers (# text -> <h1>text</h1>, ## text -> <h2>text</h2>, etc.)
38function parseHeader(markdown, inList) {
39 // Count leading hash characters
40 let hashCount = 0;
41 for (let i = 0; i < markdown.length && markdown[i] === '#'; i++) {
42 hashCount++;
4043 }
41 if (count === 0 || count > 6) {
42 return [null, list];
44
45 // Valid headers have 1-6 hashes followed by content
46 if (hashCount === 0 || hashCount > 6) {
47 return [null, inList];
4348 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
49
50 // Extract content after hashes and space
51 const content = markdown.substring(hashCount + 1);
52 const headerTag = `h${hashCount}`;
53 const headerHtml = wrap(content, headerTag);
54
55 // Close any open list before adding header
56 if (inList) {
4757 return [`</ul>${headerHtml}`, false];
4858 } else {
4959 return [headerHtml, false];
5060 }
5161}
5262
53function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
63// Parse list items (* item -> <li>item</li>)
64function parseLineItem(markdown, inList) {
65 // Only treat as list item if asterisk is followed by a space
66 if (markdown.startsWith('* ') || markdown.startsWith('*\t')) {
67 const content = markdown.substring(2); // Remove '* ' prefix
68 const innerHtml = wrap(parseText(content, true), 'li');
69
70 if (inList) {
5771 return [innerHtml, true];
5872 } else {
5973 return [`<ul>${innerHtml}`, true];
6074 }
6175 }
62 return [null, list];
76 return [null, inList];
6377}
6478
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
79// Parse regular paragraphs
80function parseParagraph(markdown, inList) {
81 if (inList) {
82 // Close the list before starting a new paragraph
6983 return [`</ul>${parseText(markdown, false)}`, false];
84 } else {
85 return [parseText(markdown, false), false];
7086 }
7187}
7288
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
89// Parse a single line of markdown, trying different parsers in order
90function parseLine(markdown, inList) {
91 // Try to parse as header first
92 let [result, newInList] = parseHeader(markdown, inList);
93
94 // If not a header, try to parse as list item
7895 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
96 [result, newInList] = parseLineItem(markdown, inList);
8097 }
98
99 // If not a list item, parse as paragraph
81100 if (result === null) {
82 throw new Error('Remove this line and implement the function');
101 [result, newInList] = parseParagraph(markdown, inList);
83102 }
84 return [result, inListAfter];
103
104 // At this point, result should never be null since parseParagraph always returns a result
105 return [result, newInList];
85106}
86107
87108/**
88 * @param {string} markdown
89 * @returns {string}
109 * Parse markdown string to HTML
110 * @param {string} markdown - The markdown string to parse
111 * @returns {string} - The resulting HTML
90112 */
91113export function parse(markdown) {
114 // Handle empty input
115 if (!markdown || markdown.trim() === '') {
116 return '';
117 }
118
92119 const lines = markdown.split('\n');
93120 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
121 let inList = false;
122
123 // Process each line
124 for (const line of lines) {
125 const [lineResult, newInList] = parseLine(line, inList);
97126 result += lineResult;
98 list = newList;
127 inList = newInList;
99128 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
129
130 // Close any open list at the end
131 if (inList) {
132 result += '</ul>';
104133 }
134
135 return result;
105136}
Test NameStatus
parsing italics
Fail
mixed normal, italics and bold text
Fail
with a little bit of everything
Fail
parses normal text as a paragraph
Pass
parsing 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 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.