diff --git a/readme.md b/readme.md index ed03e2d..930d8b2 100644 --- a/readme.md +++ b/readme.md @@ -106,7 +106,7 @@ smd.parser_end(parser) - [x] Ordered lists - [ ] `start` attr - [x] Task lists -- [ ] Nested lists +- [x] Nested lists - [x] Blockquotes - [ ] Tables - [ ] Html tags (e.g. `
`, ``, ``, ``, etc.) \ No newline at end of file diff --git a/smd.js b/smd.js index ddc2f4a..1f37370 100644 --- a/smd.js +++ b/smd.js @@ -146,9 +146,11 @@ export function attr_to_html_attr(type) { * @property {Any_Renderer} renderer - {@link Renderer} interface * @property {string } text - Text to be added to the last token in the next flush * @property {string } pending - Characters for identifying tokens - * @property {Token[] } tokens - Current token and it's parents (a slice of a tree) + * @property {Uint32Array } tokens - Current token and it's parents (a slice of a tree) * @property {number } len - Number of tokens in types without root * @property {Token } token - Last token in the tree + * @property {Uint8Array } spaces + * @property {number } spaces_pending * @property {0 | 1 } code_fence_body - For {@link Token.Code_Fence} parsing * @property {number } backticks_count * @property {number } blockquote_idx - For Blockquote parsing @@ -156,19 +158,22 @@ export function attr_to_html_attr(type) { * @property {number } hr_chars - For horizontal rule parsing * @property {boolean } could_be_url - For raw url parsing * @property {boolean } could_be_task - For checkbox parsing - * @property {number } spaces */ +const TOKEN_ARRAY_CAP = 32 + /** * Makes a new Parser object. * @param {Any_Renderer} renderer * @returns {Parser } */ export function parser(renderer) { + const tokens = new Uint32Array(TOKEN_ARRAY_CAP) + tokens[0] = DOCUMENT return { renderer : renderer, text : "", pending : "", - tokens : /**@type {*}*/([DOCUMENT,,,,,]), + tokens : tokens, len : 0, token : DOCUMENT, code_fence_body: 0, @@ -178,7 +183,8 @@ export function parser(renderer) { backticks_count: 0, could_be_url: false, could_be_task: false, - spaces : 0, + spaces : new Uint8Array(TOKEN_ARRAY_CAP), + spaces_pending: 0, } } @@ -208,7 +214,7 @@ export function parser_add_text(p) { export function parser_end_token(p) { console.assert(p.len > 0, "No nodes to end") p.len -= 1 - p.token = p.tokens[p.len] + p.token = /** @type {Token} */ (p.tokens[p.len]) p.renderer.end_token(p.renderer.data) } @@ -254,39 +260,49 @@ function parser_end_tokens_to_len(p, len) { * or continue the last one * @param {Parser } p * @param {Token } list_token + * @param {number } prefix_length * @returns {void } */ -function parser_add_list(p, list_token) { - let list_idx = parser_idx_of(p, ANY_LIST, p.blockquote_idx) - - if (list_idx === -1) { - parser_end_tokens_to_len(p, p.blockquote_idx) - parser_add_token(p, list_token) - } else { - while (true) { - if (p.spaces < 2) { - if (p.tokens[list_idx] === list_token) { - parser_end_tokens_to_len(p, list_idx) - } else { - parser_end_tokens_to_len(p, list_idx+1) - parser_add_token(p, list_token) - } - break +function parser_add_list(p, list_token, prefix_length) { + /* will create a new list inside the last item + if the amount of spaces is greater than the last one (with prefix) + 1. foo + - bar <- new nested ul + - baz <- new nested ul + 12. qux <- cannot be nested in "baz" or "bar", + so it's a new list in "foo" + */ + let list_idx = -1 + let item_idx = -1 + for (let i = p.blockquote_idx+1; i <= p.len; i += 1) { + if (p.tokens[i] & LIST_ITEM) { + if (p.tokens[i-1] & list_token) { + list_idx = i-1 } - const next_idx = parser_idx_of(p, ANY_LIST, list_idx+1) - if (next_idx === -1) { - parser_end_tokens_to_len(p, list_idx+1) - parser_add_token(p, list_token) + if (p.spaces_pending < p.spaces[i]) { + item_idx = -1 break } - list_idx = next_idx - p.spaces -= 2 + item_idx = i + } + } + + if (item_idx === -1) { + if (list_idx === -1) { + parser_end_tokens_to_len(p, p.blockquote_idx) + parser_add_token(p, list_token) + } else { + parser_end_tokens_to_len(p, list_idx) } + } else { + parser_end_tokens_to_len(p, item_idx) + parser_add_token(p, list_token) } parser_add_token(p, LIST_ITEM) + p.spaces[p.len] = p.spaces_pending + prefix_length p.pending = "" + p.spaces_pending = 0 p.could_be_task = true - p.spaces = 0 } /** @@ -367,11 +383,11 @@ export function parser_write(p, chunk) { continue case ' ': p.pending = char - p.spaces += 1 + p.spaces_pending += 1 continue case '\t': p.pending = char - p.spaces += 3 + p.spaces_pending += 3 continue case '>': { const next_blockquote_idx = parser_idx_of(p, BLOCKQUOTE, p.blockquote_idx+1) @@ -397,7 +413,7 @@ export function parser_write(p, chunk) { case '+': if (' ' !== char) break // fail - parser_add_list(p, LIST_UNORDERED) + parser_add_list(p, LIST_UNORDERED, 2) continue case '0': case '1': @@ -415,7 +431,7 @@ export function parser_write(p, chunk) { */ if ('.' === p.pending[p.pending.length-1]) { if (' ' === char) { - parser_add_list(p, LIST_ORDERED) + parser_add_list(p, LIST_ORDERED, p.pending.length+1) continue } } else { @@ -437,7 +453,7 @@ export function parser_write(p, chunk) { } /* Add a line break and continue in previous token */ - p.token = p.tokens[p.len] + p.token = /** @type {Token} */(p.tokens[p.len]) p.renderer.add_token(p.renderer.data, LINE_BREAK) p.renderer.end_token(p.renderer.data) p.pending = "" // reprocess whole pending in previous token @@ -543,7 +559,7 @@ export function parser_write(p, chunk) { if ('_' !== p.pending[0] && ' ' === p.pending[1] ) { - parser_add_list(p, LIST_UNORDERED) + parser_add_list(p, LIST_UNORDERED, 2) parser_write(p, pending_with_char.slice(2)) continue } @@ -604,7 +620,7 @@ export function parser_write(p, chunk) { case '+': if (' ' !== char) break // fail - parser_add_list(p, LIST_UNORDERED) + parser_add_list(p, LIST_UNORDERED, 2) continue /* List Ordered */ case '0': @@ -623,7 +639,7 @@ export function parser_write(p, chunk) { */ if ('.' === p.pending[p.pending.length-1]) { if (' ' === char) { - parser_add_list(p, LIST_ORDERED) + parser_add_list(p, LIST_ORDERED, p.pending.length+1) continue } } else { diff --git a/test.js b/test.js index 3757494..2a54416 100644 --- a/test.js +++ b/test.js @@ -1140,7 +1140,7 @@ for (const [c, token] of /** @type {const} */([ const list_name = token === smd.Token.List_Unordered ? "List Unordered" : "List Ordered" - const suffix = "; prefix='"+c+"'" + const suffix = "; prefix: "+c test_single_write(list_name + suffix, c+" foo", @@ -1290,43 +1290,85 @@ for (const [c, token] of /** @type {const} */([ }] ) - test_single_write(list_name + " nested ul" + suffix, - c+" a\n"+ - " * b", - [{ - type : token, - children: [{ - type : smd.Token.List_Item, - children: ["a", { - type : smd.Token.List_Unordered, - children: [{ - type : smd.Token.List_Item, - children: ["b"] + { + const indent = " ".repeat(c.length + 1) + test_single_write(list_name + " nested list" + suffix, + c+" a\n"+ + indent+c+" b", + [{ + type : token, + children: [{ + type : smd.Token.List_Item, + children: ["a", { + type : token, + children: [{ + type : smd.Token.List_Item, + children: ["b"] + }] }] }] }] - }] - ) + ) + } - test_single_write(list_name + " nested ul multiple items" + suffix, - c+" a\n"+ - " * b\n"+ - " * c\n", - [{ - type : token, - children: [{ - type : smd.Token.List_Item, - children: ["a", { - type : smd.Token.List_Unordered, - children: [{ - type : smd.Token.List_Item, - children: ["b"] - }, { - type : smd.Token.List_Item, - children: ["c"] + { + const indent = " ".repeat(c.length) + test_single_write(list_name + " failed nested list" + suffix, + c+" a\n"+ + indent+c+" b", + [{ + type : token, + children: [{ + type : smd.Token.List_Item, + children: ["a"] + }, { + type : smd.Token.List_Item, + children: ["b"] + }] + }] + ) + } + + { + const indent = " ".repeat(c.length + 1) + test_single_write(list_name + " nested ul multiple items" + suffix, + c+" a\n"+ + indent+"* b\n"+ + indent+"* c\n", + [{ + type : token, + children: [{ + type : smd.Token.List_Item, + children: ["a", { + type : smd.Token.List_Unordered, + children: [{ + type : smd.Token.List_Item, + children: ["b"] + }, { + type : smd.Token.List_Item, + children: ["c"] + }] }] }] }] - }] - ) + ) + } } + +test_single_write("Failed nesting of ul in ol", + "1. a\n"+ + " * b", + [{ + type : smd.Token.List_Ordered, + children: [{ + type : smd.Token.List_Item, + children: ["a"] + }] + }, { + type : smd.Token.List_Unordered, + children: [{ + type : smd.Token.List_Item, + children: ["b"] + }] + }] +) \ No newline at end of file