awesome

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
1// Refactored Markdown Parser
2//
3// Improvements made:
4// 1. Created a centralized ParserState class to manage list state
5// 2. Consolidated parsing logic into clearer, single-responsibility functions
6// 3. Used more descriptive variable and function names
7// 4. Restructured control flow to be more linear and easier to follow
8// 5. Added detailed comments to explain the parsing logic
49
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}
10class ParserState {
11 constructor() {
12 this.inList = false;
13 }
1414
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
15 // Handle opening and closing of list tags
16 handleListTransition(isListItem) {
17 const wasInList = this.inList;
18 this.inList = isListItem;
19
20 if (wasInList && !isListItem) {
21 // Close list when exiting list context
22 return "</ul>";
23 } else if (!wasInList && isListItem) {
24 // Open list when entering list context
25 return "<ul>";
26 }
27 return "";
28 }
1729}
1830
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
31// Wrap text with HTML tags
32function wrapWithTag(text, tag) {
33 return `<${tag}>${text}</${tag}>`;
2134}
2235
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
36// Parse inline formatting (__ for strong, _ for emphasis)
37// Note: Using the same greedy regex pattern as the original implementation
38// to ensure exact behavior matching, including edge cases with overlapping patterns
39function parseInlineFormatting(text) {
40 // Parse strong formatting (__text__) first - using greedy matching like original
41 const strongPattern = /__(.+)__/g;
42 let result = text.replace(strongPattern, "<strong>$1</strong>");
43
44 // Parse emphasis formatting (_text_) after strong is handled - using greedy matching like original
45 const emphasisPattern = /_(.+)_/g;
46 result = result.replace(emphasisPattern, "<em>$1</em>");
47
48 return result;
3049}
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// Parse header elements (# Header 1, ## Header 2, etc.)
52function parseHeader(line, state) {
53 // Count leading # characters
54 let headerLevel = 0;
55 for (let i = 0; i < line.length; i++) {
56 if (line[i] === "#") {
57 headerLevel++;
3758 } else {
3859 break;
3960 }
4061 }
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];
62
63 // Valid headers have 1-6 # characters
64 if (headerLevel === 0 || headerLevel > 6) {
65 return null;
5066 }
67
68 // Extract header content (skip # characters and space)
69 const headerContent = line.substring(headerLevel + 1);
70 const headerTag = `h${headerLevel}`;
71 const headerHtml = wrapWithTag(headerContent, headerTag);
72
73 // If we were in a list, close it before the header
74 const listClosingTag = state.handleListTransition(false);
75 return listClosingTag + headerHtml;
5176}
5277
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 }
78// Parse list items (* item text)
79function parseListItem(line, state) {
80 if (!line.startsWith("* ")) {
81 return null;
6182 }
62 return [null, list];
83
84 // Extract list item content (skip "* " prefix)
85 const itemContent = line.substring(2);
86
87 // Parse inline formatting in the list item
88 const formattedContent = parseInlineFormatting(itemContent);
89
90 // Wrap in list item tags
91 const listItemHtml = wrapWithTag(formattedContent, "li");
92
93 // Handle list opening/closing tags
94 const listTag = state.handleListTransition(true);
95 return listTag + listItemHtml;
6396}
6497
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
98// Parse paragraph elements (default for lines that don't match other patterns)
99function parseParagraph(line, state) {
100 // Parse inline formatting in the paragraph
101 const formattedContent = parseInlineFormatting(line);
102
103 // Wrap in paragraph tags (unless we're in a list item, which is handled elsewhere)
104 const paragraphHtml = wrapWithTag(formattedContent, "p");
105
106 // If we were in a list, close it before the paragraph
107 const listClosingTag = state.handleListTransition(false);
108 return listClosingTag + paragraphHtml;
71109}
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// Parse a single line of markdown
112function parseLine(line, state) {
113 // Try to parse as header first
114 let result = parseHeader(line, state);
115 if (result !== null) {
116 return result;
80117 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
118
119 // Try to parse as list item
120 result = parseListItem(line, state);
121 if (result !== null) {
122 return result;
83123 }
84 return [result, inListAfter];
124
125 // Default to paragraph parsing
126 return parseParagraph(line, state);
85127}
86128
87129/**
88 * @param {string} markdown
89 * @returns {string}
130 * Parse markdown text and convert it to HTML
131 * @param {string} markdown - The markdown text to parse
132 * @returns {string} - The resulting HTML
90133 */
91134export 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;
135 const lines = markdown.split("\n");
136 const state = new ParserState();
137 let result = "";
138
139 // Process each line
140 for (const line of lines) {
141 result += parseLine(line, state);
99142 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
143
144 // Close any open list at the end
145 if (state.inList) {
146 result += "</ul>";
104147 }
148
149 return result;
105150}
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.