hope

Finished
1// Helper functions for HTML tag wrapping
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// Text formatting parsers
7function parseBold(markdown) {
8 const pattern = new RegExp(`__(.+)__`);
9 const replacement = `<strong>$1</strong>`;
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
13function parseItalic(markdown) {
14 const pattern = new RegExp(`_(.+)_`);
15 const replacement = `<em>$1</em>`;
16 return markdown.replace(pattern, replacement);
2117}
2218
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
19function parseTextFormatting(markdown) {
20 // Apply bold formatting first, then italic
21 return parseItalic(parseBold(markdown));
3022}
3123
32function parseHeader(markdown, list) {
24// Line type parsers
25function parseHeaderLine(line) {
26 // Count consecutive # characters at the start of the line
3327 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
28 for (let i = 0; i < line.length; i++) {
29 if (line[i] === '#') {
3630 count += 1;
3731 } else {
3832 break;
3933 }
4034 }
35
36 // Valid headers have 1-6 # characters
4137 if (count === 0 || count > 6) {
42 return [null, list];
38 return null;
4339 }
40
4441 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 }
42 // Take everything after the # characters (including any space)
43 const content = line.substring(count + 1);
44 return wrap(content, headerTag);
5145}
5246
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 }
47function parseListItemLine(line) {
48 // List items start with '* ' (asterisk followed by space)
49 if (line.startsWith('* ')) {
50 const content = line.substring(2);
51 const formattedContent = parseTextFormatting(content);
52 return wrap(formattedContent, 'li');
6153 }
62 return [null, list];
54 return null;
6355}
6456
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
57function parseParagraphLine(line) {
58 // Everything else is treated as a paragraph
59 const formattedContent = parseTextFormatting(line);
60 return wrap(formattedContent, 'p');
7161}
7262
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);
63// Main line parser that determines the type of line and processes it accordingly
64function parseLine(line, inList) {
65 // Try to parse as header
66 let result = parseHeaderLine(line);
67 if (result !== null) {
68 // If we were in a list, close it before the header
69 return [inList ? `</ul>${result}` : result, false];
8070 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
71
72 // Try to parse as list item
73 result = parseListItemLine(line);
74 if (result !== null) {
75 // If we weren't in a list, start one; otherwise continue the existing list
76 return [inList ? result : `<ul>${result}`, true];
8377 }
84 return [result, inListAfter];
78
79 // Parse as paragraph (default case)
80 result = parseParagraphLine(line);
81 // If we were in a list, close it before the paragraph
82 return [inList ? `</ul>${result}` : result, false];
8583}
8684
8785/**
88 * @param {string} markdown
89 * @returns {string}
86 * Parse Markdown text and convert it to HTML
87 * @param {string} markdown - The Markdown text to parse
88 * @returns {string} - The resulting HTML
9089 */
9190export function parse(markdown) {
91 // Handle empty input
92 if (markdown === '') {
93 return '<p></p>';
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);
98 let inList = false;
99
100 for (const line of lines) {
101 let [lineResult, newListState] = parseLine(line, inList);
97102 result += lineResult;
98 list = newList;
103 inList = newListState;
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.