xas

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
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}>`;
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}
1/**
2 * A Markdown parser that converts Markdown syntax to HTML
3 * Supports headers (# ## ### #### ##### ######), emphasis (__ for bold, _ for italic), and lists (* item)
4 */
5class MarkdownParser {
6 /**
7 * Wraps text with HTML tags
8 * @param {string} text - The text to wrap
9 * @param {string} tag - The HTML tag to use
10 * @returns {string} The wrapped text
11 */
12 wrap(text, tag) {
13 return `<${tag}>${text}</${tag}>`;
14 }
2215
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
16 /**
17 * Parses emphasis syntax (__ for bold, _ for italic) in text
18 * @param {string} markdown - The markdown text to parse
19 * @returns {string} The text with emphasis converted to HTML
20 */
21 parseEmphasis(markdown) {
22 // Parse bold (__text__ -> <strong>text</strong>)
23 let result = markdown.replace(/__(.+)__/g, '<strong>$1</strong>');
24 // Parse italic (_text_ -> <em>text</em>)
25 result = result.replace(/_(.+)_/g, '<em>$1</em>');
26 return result;
2927 }
30}
3128
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;
29 /**
30 * Parses a header line (# Header -> <h1>Header</h1>)
31 * @param {string} line - The line to parse as a header
32 * @returns {string|null} The HTML header or null if not a valid header
33 */
34 parseHeader(line) {
35 let count = 0;
36 for (let i = 0; i < line.length; i++) {
37 if (line[i] === '#') {
38 count += 1;
39 } else {
40 break;
41 }
3942 }
43
44 // Valid headers have 1-6 # characters
45 if (count === 0 || count > 6) {
46 return null;
47 }
48
49 const headerTag = `h${count}`;
50 const headerContent = line.substring(count + 1);
51 return this.wrap(headerContent, headerTag);
4052 }
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}
5253
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];
54 /**
55 * Parses a list item (* item -> <li>item</li>)
56 * @param {string} line - The line to parse as a list item
57 * @returns {string|null} The HTML list item or null if not a valid list item
58 */
59 parseListItem(line) {
60 if (line.startsWith('* ')) {
61 const itemContent = line.substring(2);
62 const parsedContent = this.parseEmphasis(itemContent);
63 return this.wrap(parsedContent, 'li');
6064 }
65 return null;
6166 }
62 return [null, list];
63}
6467
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
68 /**
69 * Parses a paragraph (regular text -> <p>text</p>)
70 * @param {string} line - The line to parse as a paragraph
71 * @returns {string} The HTML paragraph
72 */
73 parseParagraph(line) {
74 const parsedContent = this.parseEmphasis(line);
75 return this.wrap(parsedContent, 'p');
7076 }
71}
7277
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);
80 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
78 /**
79 * Parses a single line of markdown
80 * @param {string} line - The line to parse
81 * @param {boolean} inList - Whether we're currently in a list
82 * @returns {[string, boolean]} A tuple of [HTML result, new inList state]
83 */
84 parseLine(line, inList) {
85 // Try to parse as header
86 const header = this.parseHeader(line);
87 if (header !== null) {
88 if (inList) {
89 // Close the list before starting the header
90 return [`</ul>${header}`, false];
91 } else {
92 return [header, false];
93 }
94 }
95
96 // Try to parse as list item
97 const listItem = this.parseListItem(line);
98 if (listItem !== null) {
99 if (inList) {
100 // Continue the existing list
101 return [listItem, true];
102 } else {
103 // Start a new list
104 return [`<ul>${listItem}`, true];
105 }
106 }
107
108 // Parse as paragraph
109 const paragraph = this.parseParagraph(line);
110 if (inList) {
111 // Close the list before starting the paragraph
112 return [`</ul>${paragraph}`, false];
113 } else {
114 return [paragraph, false];
115 }
83116 }
84 return [result, inListAfter];
85117}
86118
87119/**
88 * @param {string} markdown
89 * @returns {string}
120 * Parses Markdown text and converts it to HTML
121 * @param {string} markdown - The Markdown text to parse
122 * @returns {string} The resulting HTML
90123 */
91124export function parse(markdown) {
125 const parser = new MarkdownParser();
92126 const lines = markdown.split('\n');
93127 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
128 let inList = false;
129
130 for (const line of lines) {
131 const [lineResult, newListState] = parser.parseLine(line, inList);
97132 result += lineResult;
98 list = newList;
133 inList = newListState;
99134 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
135
136 // Close any open list at the end
137 if (inList) {
138 result += '</ul>';
104139 }
140
141 return result;
105142}
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.