awesome

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
1/**
2 * A Markdown parser that converts Markdown syntax to HTML.
3 * Supports headers, bold/italic text, and unordered lists.
4 */
5class MarkdownParser {
6 constructor() {
7 this.inList = false;
8 }
89
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}
10 /**
11 * Wraps text with HTML tags
12 * @param {string} text - Text to wrap
13 * @param {string} tag - HTML tag to use
14 * @returns {string} Wrapped HTML
15 */
16 wrap(text, tag) {
17 return `<${tag}>${text}</${tag}>`;
18 }
1419
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
20 /**
21 * Parses text with specified delimiter and converts to HTML tag
22 * @param {string} markdown - Markdown text to parse
23 * @param {string} delimiter - Delimiter to look for
24 * @param {string} tag - HTML tag to convert to
25 * @returns {string} Parsed HTML
26 */
27 parser(markdown, delimiter, tag) {
28 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
29 const replacement = `<${tag}>$1</${tag}>`;
30 return markdown.replace(pattern, replacement);
31 }
1832
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
33 /**
34 * Parses bold text (__text__ -> <strong>text</strong>)
35 * @param {string} markdown - Markdown text
36 * @returns {string} HTML with bold formatting
37 */
38 parseBold(markdown) {
39 return this.parser(markdown, '__', 'strong');
40 }
2241
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
42 /**
43 * Parses italic text (_text_ -> <em>text</em>)
44 * @param {string} markdown - Markdown text
45 * @returns {string} HTML with italic formatting
46 */
47 parseItalic(markdown) {
48 return this.parser(markdown, '_', 'em');
2949 }
30}
3150
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
51 /**
52 * Parses text formatting and wraps in paragraph tags if not in list
53 * @param {string} markdown - Markdown text
54 * @param {boolean} inList - Whether we're currently in a list
55 * @returns {string} Formatted HTML text
56 */
57 parseText(markdown, inList) {
58 const parsedText = this.parseItalic(this.parseBold(markdown));
59 if (inList) {
60 return parsedText;
3761 } else {
38 break;
62 return this.wrap(parsedText, 'p');
3963 }
4064 }
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 }
51}
5265
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];
66 /**
67 * Parses header markdown (# Header -> <h1>Header</h1>)
68 * @param {string} markdown - Markdown line
69 * @returns {string|null} HTML header or null if not a header
70 */
71 parseHeader(markdown) {
72 let count = 0;
73 for (let i = 0; i < markdown.length; i++) {
74 if (markdown[i] === '#') {
75 count += 1;
76 } else {
77 break;
78 }
6079 }
80
81 if (count === 0 || count > 6) {
82 return null;
83 }
84
85 const headerTag = `h${count}`;
86 const headerContent = markdown.substring(count + 1);
87 const headerHtml = this.wrap(headerContent, headerTag);
88
89 // Close list if we were in one
90 if (this.inList) {
91 this.inList = false;
92 return `</ul>${headerHtml}`;
93 }
94
95 return headerHtml;
6196 }
62 return [null, list];
63}
6497
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
98 /**
99 * Parses list item markdown (* item -> <li>item</li>)
100 * @param {string} markdown - Markdown line
101 * @returns {string|null} HTML list item or null if not a list item
102 */
103 parseListItem(markdown) {
104 if (markdown.startsWith('*')) {
105 const itemContent = markdown.substring(2);
106 const itemHtml = this.wrap(this.parseText(itemContent, true), 'li');
107
108 if (this.inList) {
109 return itemHtml;
110 } else {
111 this.inList = true;
112 return `<ul>${itemHtml}`;
113 }
114 }
115
116 return null;
70117 }
71}
72118
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
119 /**
120 * Parses paragraph markdown (plain text -> <p>text</p>)
121 * @param {string} markdown - Markdown line
122 * @returns {string} HTML paragraph
123 */
124 parseParagraph(markdown) {
125 // Close list if we were in one
126 if (this.inList) {
127 this.inList = false;
128 return `</ul>${this.parseText(markdown, false)}`;
129 }
130
131 return this.parseText(markdown, false);
77132 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
133
134 /**
135 * Parses a single line of markdown
136 * @param {string} markdown - A single line of markdown
137 * @returns {string} HTML output for the line
138 */
139 parseLine(markdown) {
140 // Try to parse as header first
141 let result = this.parseHeader(markdown);
142 if (result !== null) {
143 return result;
144 }
145
146 // Try to parse as list item
147 result = this.parseListItem(markdown);
148 if (result !== null) {
149 return result;
150 }
151
152 // Default to paragraph
153 return this.parseParagraph(markdown);
80154 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
155
156 /**
157 * Parses complete markdown text into HTML
158 * @param {string} markdown - Complete markdown text
159 * @returns {string} Complete HTML output
160 */
161 parse(markdown) {
162 const lines = markdown.split('\n');
163 let result = '';
164
165 for (const line of lines) {
166 result += this.parseLine(line);
167 }
168
169 // Close any open list at the end
170 if (this.inList) {
171 result += '</ul>';
172 }
173
174 return result;
83175 }
84 return [result, inListAfter];
85176}
86177
87178/**
88 * @param {string} markdown
89 * @returns {string}
179 * Parses markdown text and converts it to HTML
180 * @param {string} markdown - Markdown text to parse
181 * @returns {string} HTML representation of the markdown
90182 */
91183export function parse(markdown) {
92 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;
99 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
104 }
184 const parser = new MarkdownParser();
185 return parser.parse(markdown);
105186}
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.