future

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 */
4class MarkdownParser {
5 constructor() {
6 this.inList = false;
7 }
88
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}
9 /**
10 * Wraps text with HTML tags
11 * @param {string} text - The text to wrap
12 * @param {string} tag - The HTML tag to use
13 * @returns {string} The wrapped text
14 */
15 wrapText(text, tag) {
16 return `<${tag}>${text}</${tag}>`;
17 }
1418
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
19 /**
20 * Parses bold text (surrounded by double underscores)
21 * @param {string} markdown - The markdown text to parse
22 * @returns {string} The parsed HTML
23 */
24 parseBold(markdown) {
25 const pattern = new RegExp(`__([^_]+)__`);
26 const replacement = `<strong>$1</strong>`;
27 return markdown.replace(pattern, replacement);
28 }
1829
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
30 /**
31 * Parses italic text (surrounded by single underscores)
32 * @param {string} markdown - The markdown text to parse
33 * @returns {string} The parsed HTML
34 */
35 parseItalic(markdown) {
36 const pattern = new RegExp(`_([^_]+)_`);
37 const replacement = `<em>$1</em>`;
38 return markdown.replace(pattern, replacement);
39 }
2240
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
41 /**
42 * Parses inline formatting (bold and italic)
43 * @param {string} markdown - The markdown text to parse
44 * @returns {string} The parsed HTML
45 */
46 parseInlineFormatting(markdown) {
47 // Parse bold first, then italic
48 return this.parseItalic(this.parseBold(markdown));
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;
37 } else {
38 break;
51 /**
52 * Parses a header line
53 * @param {string} line - The line to parse
54 * @returns {string|null} The parsed HTML or null if not a header
55 */
56 parseHeader(line) {
57 // Count the number of # at the beginning of the line
58 const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
59
60 if (!headerMatch) {
61 return null;
3962 }
63
64 const level = headerMatch[1].length;
65 const content = headerMatch[2];
66 const formattedContent = this.parseInlineFormatting(content);
67
68 // Close list if we're in one
69 const prefix = this.inList ? '</ul>' : '';
70 this.inList = false;
71
72 return prefix + this.wrapText(formattedContent, `h${level}`);
4073 }
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}
5274
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];
75 /**
76 * Parses a list item
77 * @param {string} line - The line to parse
78 * @returns {string|null} The parsed HTML or null if not a list item
79 */
80 parseListItem(line) {
81 if (!line.startsWith('* ')) {
82 return null;
6083 }
84
85 const content = line.substring(2);
86 const formattedContent = this.parseInlineFormatting(content);
87 const listItem = this.wrapText(formattedContent, 'li');
88
89 // Open list if we're not already in one
90 const prefix = this.inList ? '' : '<ul>';
91 this.inList = true;
92
93 return prefix + listItem;
6194 }
62 return [null, list];
63}
6495
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
96 /**
97 * Parses a paragraph
98 * @param {string} line - The line to parse
99 * @returns {string} The parsed HTML
100 */
101 parseParagraph(line) {
102 const formattedContent = this.parseInlineFormatting(line);
103
104 // Close list if we're in one
105 const prefix = this.inList ? '</ul>' : '';
106 this.inList = false;
107
108 return prefix + this.wrapText(formattedContent, 'p');
70109 }
71}
72110
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
111 /**
112 * Parses a single line of markdown
113 * @param {string} line - The line to parse
114 * @returns {string} The parsed HTML
115 */
116 parseLine(line) {
117 // Try to parse as header
118 const headerResult = this.parseHeader(line);
119 if (headerResult !== null) {
120 return headerResult;
121 }
122
123 // Try to parse as list item
124 const listItemResult = this.parseListItem(line);
125 if (listItemResult !== null) {
126 return listItemResult;
127 }
128
129 // Parse as paragraph
130 return this.parseParagraph(line);
80131 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
132
133 /**
134 * Parses markdown text into HTML
135 * @param {string} markdown - The markdown text to parse
136 * @returns {string} The resulting HTML
137 */
138 parse(markdown) {
139 this.inList = false;
140 const lines = markdown.split('\n');
141 let result = '';
142
143 for (const line of lines) {
144 result += this.parseLine(line);
145 }
146
147 // Close any open list at the end
148 if (this.inList) {
149 result += '</ul>';
150 }
151
152 return result;
83153 }
84 return [result, inListAfter];
85154}
86155
87156/**
88 * @param {string} markdown
89 * @returns {string}
157 * Parses markdown text into HTML
158 * @param {string} markdown - The markdown text to parse
159 * @returns {string} The resulting HTML
90160 */
91161export 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 }
162 const parser = new MarkdownParser();
163 return parser.parse(markdown);
105164}
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.