lexicon

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}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
1/**
2 * A Markdown parser that converts Markdown syntax to HTML.
3 *
4 * Refactored for better readability and maintainability while preserving
5 * exact functionality.
6 *
7 * Features supported:
8 * - Headers (# to ######)
9 * - Paragraphs (simple text)
10 * - Unordered lists (* items)
11 * - Bold text (__text__)
12 * - Italic text (_text_)
13 * - Nested formatting
14 */
15class MarkdownParser {
16 /**
17 * Parses Markdown text and converts it to HTML.
18 * @param {string} markdown - The Markdown text to parse
19 * @returns {string} The resulting HTML
20 */
21 parse(markdown) {
22 const lines = markdown.split('\n');
23 let html = '';
24 let inList = false;
25
26 for (const line of lines) {
27 const result = this.parseLine(line, inList);
28 html += result.html;
29 inList = result.inList;
30 }
31
32 // Close any open list at the end
33 if (inList) {
34 html += '</ul>';
35 }
36
37 return html;
2938 }
30}
31
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;
39
40 /**
41 * Parses a single line of Markdown.
42 * @param {string} line - The line to parse
43 * @param {boolean} inList - Whether we're currently in a list
44 * @returns {{html: string, inList: boolean}} The parsed HTML and list state
45 */
46 parseLine(line, inList) {
47 // Try to parse as header
48 const headerResult = this.parseHeader(line);
49 if (headerResult !== null) {
50 // If we were in a list, close it before the header
51 const html = inList ? `</ul>${headerResult}` : headerResult;
52 return { html, inList: false };
3953 }
54
55 // Try to parse as list item
56 if (line.startsWith('*')) {
57 const listItemHtml = this.parseListItem(line);
58 if (inList) {
59 // Continue the existing list
60 return { html: listItemHtml, inList: true };
61 } else {
62 // Start a new list
63 return { html: `<ul>${listItemHtml}`, inList: true };
64 }
65 }
66
67 // Parse as paragraph
68 const paragraphHtml = this.parseParagraph(line);
69 // If we were in a list, close it before the paragraph
70 const html = inList ? `</ul>${paragraphHtml}` : paragraphHtml;
71 return { html, inList: false };
4072 }
41 if (count === 0 || count > 6) {
42 return [null, list];
73
74 /**
75 * Parses a header line.
76 * @param {string} line - The line to parse
77 * @returns {string|null} The HTML for the header, or null if not a header
78 */
79 parseHeader(line) {
80 let count = 0;
81 for (let i = 0; i < line.length; i++) {
82 if (line[i] === '#') {
83 count += 1;
84 } else {
85 break;
86 }
87 }
88
89 // Valid headers have 1-6 # characters
90 if (count === 0 || count > 6) {
91 return null;
92 }
93
94 const headerTag = `h${count}`;
95 const headerText = line.substring(count + 1);
96 return this.wrap(headerText, headerTag);
4397 }
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];
98
99 /**
100 * Parses a list item.
101 * @param {string} line - The line to parse
102 * @returns {string} The HTML for the list item
103 */
104 parseListItem(line) {
105 const itemText = line.substring(2); // Skip '* '
106 const parsedText = this.parseInlineFormatting(itemText);
107 return this.wrap(parsedText, 'li');
50108 }
51}
52
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 }
109
110 /**
111 * Parses a paragraph.
112 * @param {string} line - The line to parse
113 * @returns {string} The HTML for the paragraph
114 */
115 parseParagraph(line) {
116 const parsedText = this.parseInlineFormatting(line);
117 return this.wrap(parsedText, 'p');
61118 }
62 return [null, list];
63}
64
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
119
120 /**
121 * Parses inline formatting (__bold__ and _italic_).
122 * @param {string} text - The text to parse
123 * @returns {string} The text with inline formatting converted to HTML
124 */
125 parseInlineFormatting(text) {
126 // Parse bold (__text__ -> <strong>text</strong>)
127 let result = this.parseBold(text);
128 // Parse italic (_text_ -> <em>text</em>)
129 result = this.parseItalic(result);
130 return result;
70131 }
71}
72
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
132
133 /**
134 * Parses bold formatting (__text__ -> <strong>text</strong>).
135 * @param {string} text - The text to parse
136 * @returns {string} The text with bold formatting converted to HTML
137 */
138 parseBold(text) {
139 return this.parseInlineElement(text, '__', 'strong');
140 }
141
142 /**
143 * Parses italic formatting (_text_ -> <em>text</em>).
144 * @param {string} text - The text to parse
145 * @returns {string} The text with italic formatting converted to HTML
146 */
147 parseItalic(text) {
148 return this.parseInlineElement(text, '_', 'em');
77149 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
150
151 /**
152 * Parses an inline element with the specified delimiter.
153 * @param {string} text - The text to parse
154 * @param {string} delimiter - The delimiter to look for
155 * @param {string} tag - The HTML tag to wrap the content with
156 * @returns {string} The text with the inline element converted to HTML
157 */
158 parseInlineElement(text, delimiter, tag) {
159 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
160 const replacement = `<${tag}>$1</${tag}>`;
161 return text.replace(pattern, replacement);
80162 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
163
164 /**
165 * Wraps text with the specified HTML tag.
166 * @param {string} text - The text to wrap
167 * @param {string} tag - The HTML tag to wrap with
168 * @returns {string} The wrapped text
169 */
170 wrap(text, tag) {
171 return `<${tag}>${text}</${tag}>`;
83172 }
84 return [result, inListAfter];
85173}
86174
175// Create a singleton instance and export the parse function
176const parser = new MarkdownParser();
177
87178/**
88 * @param {string} markdown
89 * @returns {string}
179 * Parses Markdown text and converts it to HTML.
180 * @param {string} markdown - The Markdown text to parse
181 * @returns {string} The resulting HTML
90182 */
91183export 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 }
184 return parser.parse(markdown);
105185}
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.