impossible

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) {
11// Generic parser for markdown delimiters (bold, italic, etc.)
12function parseMarkdownDelimiter(markdown, delimiter, tag) {
1013 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
1114 const replacement = `<${tag}>$1</${tag}>`;
1215 return markdown.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text (using __ delimiter)
19function parseBold(markdown) {
20 return parseMarkdownDelimiter(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (using _ delimiter)
24function parseItalic(markdown) {
25 return parseMarkdownDelimiter(markdown, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
28// Parse regular text, applying inline formatting (bold, italic) and wrapping in paragraph tags if not in a list
29function parseText(markdown, inList) {
30 // Apply inline formatting (bold and italic)
31 const formattedText = parseItalic(parseBold(markdown));
32
33 // If we're inside a list, don't wrap in paragraph tags
34 if (inList) {
35 return formattedText;
2736 } else {
28 return wrap(parsedText, 'p');
37 // Otherwise, wrap the text in paragraph tags
38 return wrap(formattedText, 'p');
2939 }
3040}
3141
32function parseHeader(markdown, list) {
33 let count = 0;
42// Parse markdown headers (lines starting with #)
43function parseHeader(markdown, inList) {
44 // Count the number of # characters at the start
45 let headerLevel = 0;
3446 for (let i = 0; i < markdown.length; i++) {
3547 if (markdown[i] === '#') {
36 count += 1;
48 headerLevel += 1;
3749 } else {
3850 break;
3951 }
4052 }
41 if (count === 0 || count > 6) {
42 return [null, list];
53
54 // Valid headers have 1-6 # characters
55 if (headerLevel === 0 || headerLevel > 6) {
56 return [null, inList];
4357 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
58
59 // Create the appropriate header tag (h1, h2, etc.)
60 const headerTag = `h${headerLevel}`;
61 const headerContent = markdown.substring(headerLevel + 1).trim();
62 const headerHtml = wrap(headerContent, headerTag);
63
64 // If we were in a list, close it before the header
65 if (inList) {
4766 return [`</ul>${headerHtml}`, false];
4867 } else {
4968 return [headerHtml, false];
5069 }
5170}
5271
53function parseLineItem(markdown, list) {
72// Parse list items (lines starting with *)
73function parseLineItem(markdown, inList) {
74 // Check if this line is a list item
5475 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
76 // Extract the content (skip the '* ' prefix)
77 const itemContent = markdown.substring(2);
78
79 // Parse the content with inline formatting, without paragraph wrapping (since it's in a list)
80 const formattedContent = parseText(itemContent, true);
81
82 // Wrap the content in list item tags
83 const listItemHtml = wrap(formattedContent, 'li');
84
85 // If we're already in a list, just return the list item
86 // Otherwise, start a new unordered list
87 if (inList) {
88 return [listItemHtml, true];
5889 } else {
59 return [`<ul>${innerHtml}`, true];
90 return [`<ul>${listItemHtml}`, true];
6091 }
6192 }
62 return [null, list];
93
94 // Not a list item
95 return [null, inList];
6396}
6497
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
98// Parse regular paragraphs (fallback when not a header or list item)
99function parseParagraph(markdown, inList) {
100 // Parse the text with inline formatting and paragraph wrapping
101 const paragraphHtml = parseText(markdown, false);
102
103 // If we were in a list, close it before the paragraph
104 if (inList) {
105 return [`</ul>${paragraphHtml}`, false];
68106 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
107 return [paragraphHtml, false];
70108 }
71109}
72110
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
111// Parse a single line of markdown, determining its type and converting to HTML
112// Returns [htmlResult, inListAfter] tuple
113function parseLine(markdown, inList) {
114 // Skip empty lines entirely
115 if (markdown.trim() === '') {
116 // If we're in a list, close it when we encounter an empty line
117 if (inList) {
118 return ['</ul>', false];
119 }
120 // Otherwise, return empty string (no HTML for empty lines)
121 return ['', false];
122 }
123
124 // Try to parse as header first (highest priority)
125 let [result, newInList] = parseHeader(markdown, inList);
126
127 // If not a header, try to parse as list item
75128 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
129 [result, newInList] = parseLineItem(markdown, inList);
77130 }
131
132 // If not a header or list item, treat as paragraph
78133 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
134 [result, newInList] = parseParagraph(markdown, inList);
80135 }
136
137 // This should never happen since parseParagraph always returns a result
81138 if (result === null) {
82 throw new Error('Remove this line and implement the function');
139 throw new Error('Unable to parse markdown line');
83140 }
84 return [result, inListAfter];
141
142 return [result, newInList];
85143}
86144
87145/**
88 * @param {string} markdown
89 * @returns {string}
146 * Main markdown parser function
147 * Converts markdown text to HTML
148 * @param {string} markdown - The markdown text to parse
149 * @returns {string} The equivalent HTML
90150 */
91151export function parse(markdown) {
152 // Split the markdown into individual lines
92153 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
154
155 let htmlResult = '';
156 let inList = false; // Track whether we're currently inside a list
157
158 // Process each line sequentially
95159 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
160 const currentLine = lines[i];
161
162 // Parse the current line and get the HTML result and new list state
163 const [lineHtml, newInListState] = parseLine(currentLine, inList);
164
165 // Append the HTML for this line
166 htmlResult += lineHtml;
167
168 // Update the list state for the next iteration
169 inList = newInListState;
99170 }
100 if (list) {
101 return result + '</ul>';
171
172 // If we ended while still in a list, close it
173 if (inList) {
174 return htmlResult + '</ul>';
102175 } else {
103 return result;
176 return htmlResult;
104177 }
105178}
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.