couple

Finished
1// Utility 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}>`);
7}
8
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
6// Refactored: More descriptive function names for parsing markdown delimiters
7// Parses text wrapped in delimiter and replaces with corresponding HTML tag
8function parseWithDelimiter(markdown, delimiter, tag) {
9 const pattern = new RegExp(`${delimiter}(.+?)${delimiter}`);
1110 const replacement = `<${tag}>$1</${tag}>`;
1211 return markdown.replace(pattern, replacement);
1312}
1413
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
14// Parses bold text (wrapped in double underscores)
15function parseBold(markdown) {
16 return parseWithDelimiter(markdown, '__', 'strong');
1717}
1818
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
19// Parses italic text (wrapped in single underscore)
20function parseItalic(markdown) {
21 return parseWithDelimiter(markdown, '_', 'em');
2122}
2223
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
24// Parses inline formatting (bold and italic) and wraps text in paragraph tags if not in a list
25function parseText(markdown, inList) {
26 // Apply bold and italic formatting
27 const formattedText = parseItalic(parseBold(markdown));
28
29 // If we're inside a list, don't wrap in paragraph tags
30 if (inList) {
31 return formattedText;
2732 } else {
28 return wrap(parsedText, 'p');
33 return wrap(formattedText, 'p');
2934 }
3035}
3136
32function parseHeader(markdown, list) {
33 let count = 0;
37// Parses header markdown (#, ##, ###, etc.) and returns HTML
38function parseHeader(markdown, inList) {
39 // Count the number of consecutive # characters at the start
40 let headerLevel = 0;
3441 for (let i = 0; i < markdown.length; i++) {
3542 if (markdown[i] === '#') {
36 count += 1;
43 headerLevel++;
3744 } else {
3845 break;
3946 }
4047 }
41 if (count === 0 || count > 6) {
42 return [null, list];
48
49 // Valid headers have 1-6 # characters
50 if (headerLevel === 0 || headerLevel > 6) {
51 return [null, inList];
4352 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
53
54 // Create the header tag (h1, h2, etc.)
55 const headerTag = `h${headerLevel}`;
56 const headerContent = markdown.substring(headerLevel + 1);
57 const headerHtml = wrap(headerContent, headerTag);
58
59 // If we were in a list, close it before starting the header
60 if (inList) {
4761 return [`</ul>${headerHtml}`, false];
4862 } else {
4963 return [headerHtml, false];
5064 }
5165}
5266
53function parseLineItem(markdown, list) {
67// Parses list items (starting with *) and returns HTML
68function parseLineItem(markdown, inList) {
5469 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
70 // Remove the '* ' prefix and parse the remaining text
71 const itemContent = markdown.substring(2);
72 const formattedItem = parseText(itemContent, true);
73 const listItemHtml = wrap(formattedItem, 'li');
74
75 // If we're already in a list, just add the item
76 if (inList) {
77 return [listItemHtml, true];
5878 } else {
59 return [`<ul>${innerHtml}`, true];
79 // Start a new list with this item
80 return [`<ul>${listItemHtml}`, true];
6081 }
6182 }
62 return [null, list];
83
84 // Not a list item
85 return [null, inList];
6386}
6487
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
88// Parses regular paragraph text and returns HTML
89function parseParagraph(markdown, inList) {
90 const paragraphHtml = parseText(markdown, false);
91
92 // If we were in a list, close it before starting the paragraph
93 if (inList) {
94 return [`</ul>${paragraphHtml}`, false];
6895 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
96 return [paragraphHtml, false];
7097 }
7198}
7299
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
100// Parses a single line of markdown by trying different parsers in order
101// Returns [htmlResult, inListAfter] tuple
102function parseLine(markdown, inList) {
103 // Try to parse as header first
104 let [result, newInList] = parseHeader(markdown, inList);
105 if (result !== null) {
106 return [result, newInList];
77107 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
108
109 // Try to parse as list item
110 [result, newInList] = parseLineItem(markdown, inList);
111 if (result !== null) {
112 return [result, newInList];
80113 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
114
115 // Default to paragraph
116 [result, newInList] = parseParagraph(markdown, inList);
117 if (result !== null) {
118 return [result, newInList];
83119 }
84 return [result, inListAfter];
120
121 // This should never happen since parseParagraph always returns a result
122 throw new Error('Unable to parse markdown line');
85123}
86124
87125/**
88 * @param {string} markdown
89 * @returns {string}
126 * Main function that parses markdown text and returns HTML
127 * @param {string} markdown - The markdown text to parse
128 * @returns {string} - The resulting HTML
90129 */
91130export function parse(markdown) {
92131 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;
132 let htmlResult = '';
133 let inList = false;
134
135 // Process each line of markdown
136 for (const line of lines) {
137 const [lineHtml, newInListState] = parseLine(line, inList);
138 htmlResult += lineHtml;
139 inList = newInListState;
99140 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
141
142 // Close any unclosed list at the end
143 if (inList) {
144 htmlResult += '</ul>';
104145 }
146
147 return htmlResult;
105148}
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.