Finished
1// Helper function to wrap text in HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
6// Helper function to check if text starts with a specific HTML tag
57function isTag(text, tag) {
68 return text.startsWith(`<${tag}>`);
79}
810
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11// Generic parser for emphasis markers (bold, italic)
12// Fixed: Use non-greedy matching and global flag to handle multiple instances
13function parseEmphasis(markdown, delimiter, tag) {
14 // Escape special regex characters in the delimiter
15 const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
16 // Use non-greedy quantifier (.+?) and global flag to match all instances
17 const pattern = new RegExp(`${escapedDelimiter}(.+?)${escapedDelimiter}`, 'g');
1118 const replacement = `<${tag}>$1</${tag}>`;
1219 return markdown.replace(pattern, replacement);
1320}
1421
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
22// Parse bold text using ** delimiter
23function parseBold(markdown) {
24 return parseEmphasis(markdown, '**', 'strong');
1725}
1826
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
27// Parse italic text using * delimiter
28function parseItalic(markdown) {
29 return parseEmphasis(markdown, '*', 'em');
2130}
2231
32// Parse text with emphasis formatting (bold and italic)
2333function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
34 // Apply emphasis parsing in correct order (bold first, then italic)
35 const parsedText = parseItalic(parseBold(markdown));
2536 if (list) {
2637 return parsedText;
2738 } else {
2940 }
3041}
3142
43// Parse markdown headers (#, ##, ###, etc.)
3244function parseHeader(markdown, list) {
45 // Count consecutive # characters at the beginning of the line
3346 let count = 0;
3447 for (let i = 0; i < markdown.length; i++) {
3548 if (markdown[i] === '#') {
3851 break;
3952 }
4053 }
54
55 // Validate header level (1-6)
4156 if (count === 0 || count > 6) {
4257 return [null, list];
4358 }
59
60 // Create appropriate header tag (h1, h2, etc.)
4461 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
62 const headerContent = markdown.substring(count + 1); // Skip # characters and space
63 const headerHtml = wrap(headerContent, headerTag);
64
65 // Handle list closure if we're currently in a list
4666 if (list) {
4767 return [`</ul>${headerHtml}`, false];
4868 } else {
5070 }
5171}
5272
73// Parse list items starting with * (must be followed by space)
5374function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
75 // Only treat as list item if it starts with "* " (asterisk followed by space)
76 // This prevents conflict with emphasis like "*italic*"
77 if (markdown.startsWith('* ')) {
78 // Extract list item content (skip "* ")
79 const itemContent = markdown.substring(2);
80 // Parse the content for emphasis and wrap in li tag
81 const listItemHtml = wrap(parseText(itemContent, true), 'li');
82
5683 if (list) {
57 return [innerHtml, true];
84 // Already in a list, just add the item
85 return [listItemHtml, true];
5886 } else {
59 return [`<ul>${innerHtml}`, true];
87 // Start a new list
88 return [`<ul>${listItemHtml}`, true];
6089 }
6190 }
6291 return [null, list];
6392}
6493
94// Parse regular paragraphs
6595function parseParagraph(markdown, list) {
96 const paragraphHtml = parseText(markdown, false);
97
6698 if (!list) {
67 return [parseText(markdown, false), false];
99 // Not in a list, just return the paragraph
100 return [paragraphHtml, false];
68101 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
102 // Close the list and start the paragraph
103 return [`</ul>${paragraphHtml}`, false];
70104 }
71105}
72106
107// Parse a single line of markdown
73108function parseLine(markdown, list) {
109 // Try parsing as header first
74110 let [result, inListAfter] = parseHeader(markdown, list);
111
112 // If not a header, try parsing as list item
75113 if (result === null) {
76114 [result, inListAfter] = parseLineItem(markdown, list);
77115 }
116
117 // If neither header nor list item, treat as paragraph
78118 if (result === null) {
79119 [result, inListAfter] = parseParagraph(markdown, list);
80120 }
121
122 // This should never happen with valid markdown
81123 if (result === null) {
82 throw new Error('Remove this line and implement the function');
124 throw new Error('Unable to parse markdown line: ' + markdown);
83125 }
126
84127 return [result, inListAfter];
85128}
86129
87130/**
88 * @param {string} markdown
89 * @returns {string}
131 * Parse markdown text and convert it to HTML
132 * @param {string} markdown - The markdown text to parse
133 * @returns {string} - The resulting HTML
90134 */
91135export function parse(markdown) {
136 // Split the markdown into individual lines
92137 const lines = markdown.split('\n');
93138 let result = '';
94 let list = false;
139 let list = false; // Track if we're currently inside a list
140
141 // Process each line of markdown
95142 for (let i = 0; i < lines.length; i++) {
96143 let [lineResult, newList] = parseLine(lines[i], list);
97144 result += lineResult;
98145 list = newList;
99146 }
147
148 // Close any open list at the end of the document
100149 if (list) {
101150 return result + '</ul>';
102151 } else {
Test NameStatus
parsing italics
Fail
parsing bold text
Fail
mixed normal, italics and bold text
Fail
with a little bit of everything
Fail
parses normal text as a paragraph
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.