Finished
1function wrap(text, tag) {
1// Utility function to wrap text in HTML tags
2function wrapTextInTag(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6// Check if text starts with a specific HTML tag
7function startsWithTag(text, tag) {
68 return text.startsWith(`<${tag}>`);
79}
810
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
11// Generic parser for delimiter-based markdown (bold, italic, etc.)
12function parseDelimitedText(markdown, delimiter, htmlTag) {
13 const pattern = new RegExp(`${delimiter}(.+?)${delimiter}`);
14 const replacement = `<${htmlTag}>$1</${htmlTag}>`;
1215 return markdown.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text (using __ delimiter)
19function parseBoldText(markdown) {
20 return parseDelimitedText(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (using _ delimiter)
24function parseItalicText(markdown) {
25 return parseDelimitedText(markdown, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
28// Parse inline formatting (bold and italic) within text
29function parseInlineFormatting(markdown) {
30 return parseItalicText(parseBoldText(markdown));
3031}
3132
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 }
40 }
41 if (count === 0 || count > 6) {
42 return [null, list];
43 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
50 }
33// Parse text content with optional paragraph wrapping
34function parseTextContent(markdown, isListItem = false) {
35 const formattedText = parseInlineFormatting(markdown);
36 return isListItem ? formattedText : wrapTextInTag(formattedText, 'p');
5137}
5238
53function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
58 } else {
59 return [`<ul>${innerHtml}`, true];
60 }
39// Parse header lines (starting with # characters)
40function parseHeaderLine(markdown, isCurrentlyInList) {
41 // Count consecutive # characters at the start
42 const headerLevel = markdown.match(/^#+/)?.[0].length || 0;
43
44 // Return null if not a valid header (no # or more than 6)
45 if (headerLevel === 0 || headerLevel > 6) {
46 return { html: null, isInList: isCurrentlyInList };
6147 }
62 return [null, list];
48
49 // Extract header content (after # and space)
50 const headerContent = markdown.substring(headerLevel + 1);
51 const headerHtml = wrapTextInTag(headerContent, `h${headerLevel}`);
52
53 // Close list if we were in one
54 const listClosingTag = isCurrentlyInList ? '</ul>' : '';
55
56 return {
57 html: `${listClosingTag}${headerHtml}`,
58 isInList: false
59 };
6360}
6461
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
62// Parse list items (starting with *)
63function parseListItem(markdown, isCurrentlyInList) {
64 // Check if this is a list item
65 if (!markdown.startsWith('*')) {
66 return { html: null, isInList: isCurrentlyInList };
7067 }
68
69 // Extract list item content (after * and space)
70 const itemContent = markdown.substring(2);
71 const listItemHtml = wrapTextInTag(parseTextContent(itemContent, true), 'li');
72
73 // Open list if we weren't in one
74 const listOpeningTag = isCurrentlyInList ? '' : '<ul>';
75
76 return {
77 html: `${listOpeningTag}${listItemHtml}`,
78 isInList: true
79 };
80}
81
82// Parse regular paragraphs
83function parseParagraph(markdown, isCurrentlyInList) {
84 const paragraphHtml = parseTextContent(markdown, false);
85
86 // Close list if we were in one
87 const listClosingTag = isCurrentlyInList ? '</ul>' : '';
88
89 return {
90 html: `${listClosingTag}${paragraphHtml}`,
91 isInList: false
92 };
7193}
7294
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
95// Parse a single line of markdown, determining its type and converting to HTML
96function parseMarkdownLine(markdown, isCurrentlyInList) {
97 // Try to parse as header first
98 let parseResult = parseHeaderLine(markdown, isCurrentlyInList);
99
100 // If not a header, try to parse as list item
101 if (parseResult.html === null) {
102 parseResult = parseListItem(markdown, isCurrentlyInList);
77103 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
104
105 // If not a list item, parse as paragraph
106 if (parseResult.html === null) {
107 parseResult = parseParagraph(markdown, isCurrentlyInList);
80108 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
109
110 // At this point, we should have a valid result
111 if (parseResult.html === null) {
112 throw new Error('Unable to parse markdown line');
83113 }
84 return [result, inListAfter];
114
115 return parseResult;
85116}
86117
87118/**
88 * @param {string} markdown
89 * @returns {string}
119 * Parse markdown text and convert it to HTML
120 * @param {string} markdown - The markdown text to parse
121 * @returns {string} - The resulting HTML
90122 */
91123export function parse(markdown) {
92124 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;
125 let htmlResult = '';
126 let isCurrentlyInList = false;
127
128 // Process each line of markdown
129 for (const line of lines) {
130 const parseResult = parseMarkdownLine(line, isCurrentlyInList);
131 htmlResult += parseResult.html;
132 isCurrentlyInList = parseResult.isInList;
99133 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
134
135 // Close any remaining open list
136 if (isCurrentlyInList) {
137 htmlResult += '</ul>';
104138 }
139
140 return htmlResult;
105141}
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.