relax

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
6// Generic parser function for inline formatting
97function parser(markdown, delimiter, tag) {
108 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
119 const replacement = `<${tag}>$1</${tag}>`;
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
13// Parse bold text (__text__ -> <strong>text</strong>)
14function parseBold(markdown) {
1615 return parser(markdown, '__', 'strong');
1716}
1817
19function parse_(markdown) {
18// Parse italic text (_text_ -> <em>text</em>)
19function parseItalic(markdown) {
2020 return parser(markdown, '_', 'em');
2121}
2222
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
23// Parse both bold and italic formatting
24// Note: Order matters - bold is parsed first to avoid conflicts
25function parseTextFormatting(markdown) {
26 return parseItalic(parseBold(markdown));
27}
28
29// Parse text with or without paragraph wrapper based on list state
30function parseText(markdown, inList) {
31 const formattedText = parseTextFormatting(markdown);
32 if (inList) {
33 // Inside a list, don't wrap in <p> tags
34 return formattedText;
2735 } else {
28 return wrap(parsedText, 'p');
36 // Outside a list, wrap in <p> tags
37 return wrap(formattedText, 'p');
2938 }
3039}
3140
32function parseHeader(markdown, list) {
33 let count = 0;
41// Parse header (# Header -> <h1>Header</h1>)
42// Returns [htmlResult, newListState] tuple
43function parseHeader(markdown, inList) {
44 // Count consecutive # characters at start of line
45 let hashCount = 0;
3446 for (let i = 0; i < markdown.length; i++) {
3547 if (markdown[i] === '#') {
36 count += 1;
48 hashCount++;
3749 } else {
3850 break;
3951 }
4052 }
41 if (count === 0 || count > 6) {
42 return [null, list];
53
54 // Headers must have 1-6 # characters
55 if (hashCount === 0 || hashCount > 6) {
56 return [null, inList];
4357 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
58
59 // Extract header content (everything after "# ")
60 const headerContent = markdown.substring(hashCount + 1);
61 const headerTag = `h${hashCount}`;
62 const headerHtml = wrap(headerContent, headerTag);
63
64 // If we're in a list, close it before the header
65 if (inList) {
4766 return [`</ul>${headerHtml}`, false];
4867 } else {
4968 return [headerHtml, false];
5069 }
5170}
5271
53function parseLineItem(markdown, list) {
72// Parse list item (* item -> <li>item</li>)
73// Returns [htmlResult, newListState] tuple
74function parseListItem(markdown, inList) {
75 // Check if line starts with asterisk
5476 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
77 // Extract item content (remove first 2 characters: "* ")
78 // Note: This also removes "*L" from "*List", which is the original behavior
79 const itemContent = markdown.substring(2);
80 const formattedItem = parseText(itemContent, true); // true = in list
81 const listItemHtml = wrap(formattedItem, 'li');
82
83 if (inList) {
84 // Continue existing list
85 return [listItemHtml, true];
5886 } else {
59 return [`<ul>${innerHtml}`, true];
87 // Start new list
88 return [`<ul>${listItemHtml}`, true];
6089 }
6190 }
62 return [null, list];
91
92 return [null, inList];
6393}
6494
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
95// Parse paragraph (default case for regular text)
96// Returns [htmlResult, newListState] tuple
97function parseParagraph(markdown, inList) {
98 const paragraphHtml = parseText(markdown, false); // false = not in list
99
100 // If we're in a list, close it before the paragraph
101 if (inList) {
102 return [`</ul>${paragraphHtml}`, false];
68103 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
104 return [paragraphHtml, false];
70105 }
71106}
72107
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
108// Parse a single line of markdown
109// Returns [htmlResult, newListState] tuple
110function parseLine(markdown, inList) {
111 // Try parsing as header first
112 let [result, newListState] = parseHeader(markdown, inList);
75113 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
114 // Try parsing as list item
115 [result, newListState] = parseListItem(markdown, newListState);
77116 }
78117 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
118 // Default to paragraph
119 [result, newListState] = parseParagraph(markdown, newListState);
80120 }
121
122 // This should never happen with valid input
81123 if (result === null) {
82124 throw new Error('Remove this line and implement the function');
83125 }
84 return [result, inListAfter];
126
127 return [result, newListState];
85128}
86129
87130/**
88 * @param {string} markdown
89 * @returns {string}
131 * Parse markdown text and convert it to HTML
132 * @param {string} markdown - The markdown text to parse
133 * @returns {string} - The resulting HTML
90134 */
91135export function parse(markdown) {
136 // Input validation - ensure we have a string
137 if (typeof markdown !== 'string') {
138 throw new Error('Input must be a string');
139 }
140
92141 const lines = markdown.split('\n');
93142 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
143 let inList = false;
144
145 // Process each line
146 for (const line of lines) {
147 let [lineResult, newListState] = parseLine(line, inList);
97148 result += lineResult;
98 list = newList;
149 inList = newListState;
99150 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
151
152 // Close any open list at the end of document
153 if (inList) {
154 result += '</ul>';
104155 }
156
157 return result;
105158}
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.