Alone

Finished
1// Helper functions for wrapping 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// Parser for inline formatting (__ for bold, _ for italic)
7function parseInlineFormatting(markdown) {
8 // Parse bold text (__text__ -> <strong>text</strong>)
9 // Use non-greedy matching to handle multiple bold segments
10 let result = markdown.replace(/__(.+?)__/g, '<strong>$1</strong>');
11 // Parse italic text (_text_ -> <em>text</em>)
12 // Use non-greedy matching and avoid matching within bold text
13 result = result.replace(/_(.+?)_/g, '<em>$1</em>');
14 return result;
715}
816
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}
14
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
30}
31
32function parseHeader(markdown, list) {
17// Parser for different block elements
18function parseHeader(markdown) {
19 // Count leading # characters
3320 let count = 0;
3421 for (let i = 0; i < markdown.length; i++) {
3522 if (markdown[i] === '#') {
3825 break;
3926 }
4027 }
28
29 // Valid headers have 1-6 # characters
4130 if (count === 0 || count > 6) {
42 return [null, list];
31 return null;
4332 }
33
4434 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 }
35 const headerContent = markdown.substring(count + 1);
36 return wrap(headerContent, headerTag);
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 }
39function parseListItem(markdown) {
40 if (markdown.startsWith('* ')) {
41 const itemContent = markdown.substring(2);
42 const formattedContent = parseInlineFormatting(itemContent);
43 return wrap(formattedContent, 'li');
6144 }
62 return [null, list];
45 return null;
6346}
6447
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
48function parseParagraph(markdown) {
49 const formattedContent = parseInlineFormatting(markdown);
50 return wrap(formattedContent, 'p');
7151}
7252
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
53// Main parsing logic
54function parseBlockElement(line, inList) {
55 // Try to parse as header
56 let result = parseHeader(line);
57 if (result !== null) {
58 // If we were in a list, close it before the header
59 if (inList) {
60 return { content: `</ul>${result}`, inList: false };
61 }
62 return { content: result, inList: false };
7763 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
64
65 // Try to parse as list item
66 result = parseListItem(line);
67 if (result !== null) {
68 if (inList) {
69 // Already in a list, just add the item
70 return { content: result, inList: true };
71 } else {
72 // Starting a new list
73 return { content: `<ul>${result}`, inList: true };
74 }
8075 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
76
77 // Parse as paragraph
78 result = parseParagraph(line);
79 // If we were in a list, close it before the paragraph
80 if (inList) {
81 return { content: `</ul>${result}`, inList: false };
8382 }
84 return [result, inListAfter];
83 return { content: result, inList: false };
8584}
8685
8786/**
8988 * @returns {string}
9089 */
9190export function parse(markdown) {
91 // Handle empty input
92 if (markdown === '') {
93 return '';
94 }
95
9296 const lines = markdown.split('\n');
9397 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;
98 let inList = false;
99
100 for (const line of lines) {
101 const parsed = parseBlockElement(line, inList);
102 result += parsed.content;
103 inList = parsed.inList;
99104 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
105
106 // Close any open list at the end
107 if (inList) {
108 result += '</ul>';
104109 }
110
111 return result;
105112}
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.