A.2.1

Finished
1// Helper 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}`);
11 const replacement = `<${tag}>$1</${tag}>`;
6// Parse bold text (double underscores)
7function parseBold(markdown) {
8 const pattern = /__(.+)__/g;
9 const replacement = '<strong>$1</strong>';
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
13// Parse italic text (single underscores)
14function parseItalic(markdown) {
15 const pattern = /_(.+)_/g;
16 const replacement = '<em>$1</em>';
17 return markdown.replace(pattern, replacement);
1718}
1819
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
20// Parse inline formatting (bold and italic)
21function parseInlineFormatting(markdown) {
22 return parseItalic(parseBold(markdown));
2123}
2224
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
25// Parse text with appropriate wrapper tags
26function parseText(markdown, isListItem = false) {
27 const parsedText = parseInlineFormatting(markdown);
28 // List items don't get wrapped in <p> tags
29 return isListItem ? parsedText : wrap(parsedText, 'p');
3030}
3131
32function parseHeader(markdown, list) {
33 let count = 0;
32// Parse header markdown (#, ##, ###, etc.)
33function parseHeader(markdown, currentlyInList) {
34 // Count consecutive # characters at the beginning
35 let headerLevel = 0;
3436 for (let i = 0; i < markdown.length; i++) {
3537 if (markdown[i] === '#') {
36 count += 1;
38 headerLevel++;
3739 } else {
3840 break;
3941 }
4042 }
41 if (count === 0 || count > 6) {
42 return [null, list];
43
44 // Valid headers are h1-h6 (1-6 # characters)
45 if (headerLevel === 0 || headerLevel > 6) {
46 return [null, currentlyInList];
4347 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
48
49 // Extract header content (skip # characters and the following space)
50 const headerContent = markdown.substring(headerLevel + 1);
51 const headerTag = `h${headerLevel}`;
52 const headerHtml = wrap(headerContent, headerTag);
53
54 // If we were in a list, close it before the header
55 if (currentlyInList) {
4756 return [`</ul>${headerHtml}`, false];
4857 } else {
4958 return [headerHtml, false];
5059 }
5160}
5261
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 }
62// Parse list items (lines starting with *)
63function parseListItem(markdown, currentlyInList) {
64 if (!markdown.startsWith('*')) {
65 return [null, currentlyInList];
66 }
67
68 // Extract list item content (skip '* ' prefix)
69 const itemContent = markdown.substring(2);
70 const itemHtml = wrap(parseText(itemContent, true), 'li');
71
72 // If we're already in a list, just add the item
73 // Otherwise, start a new list
74 if (currentlyInList) {
75 return [itemHtml, true];
76 } else {
77 return [`<ul>${itemHtml}`, true];
6178 }
62 return [null, list];
6379}
6480
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
81// Parse paragraph (default case when no other syntax matches)
82function parseParagraph(markdown, currentlyInList) {
83 const paragraphHtml = parseText(markdown, false);
84
85 // If we were in a list, close it before the paragraph
86 if (currentlyInList) {
87 return [`</ul>${paragraphHtml}`, false];
6888 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
89 return [paragraphHtml, false];
7090 }
7191}
7292
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
93// Parse a single line of markdown
94function parseLine(markdown, currentlyInList) {
95 // Try to parse as header first
96 let [result, newListState] = parseHeader(markdown, currentlyInList);
97 if (result !== null) {
98 return [result, newListState];
7799 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
100
101 // Try to parse as list item
102 [result, newListState] = parseListItem(markdown, currentlyInList);
103 if (result !== null) {
104 return [result, newListState];
80105 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
106
107 // Default to paragraph
108 [result, newListState] = parseParagraph(markdown, currentlyInList);
109 if (result !== null) {
110 return [result, newListState];
83111 }
84 return [result, inListAfter];
112
113 // This should never happen with valid input
114 throw new Error('Unable to parse line');
85115}
86116
87117/**
88 * @param {string} markdown
89 * @returns {string}
118 * Parse markdown text and convert it to HTML
119 * @param {string} markdown - The markdown text to parse
120 * @returns {string} - The resulting HTML
90121 */
91122export function parse(markdown) {
92123 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;
124 let htmlResult = '';
125 let inList = false;
126
127 // Process each line
128 for (const line of lines) {
129 const [lineHtml, newListState] = parseLine(line, inList);
130 htmlResult += lineHtml;
131 inList = newListState;
99132 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
133
134 // Close any open list at the end
135 if (inList) {
136 htmlResult += '</ul>';
104137 }
138
139 return htmlResult;
105140}
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.