diff options
author | Karl Berry <karl@freefriends.org> | 2022-10-01 19:49:44 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2022-10-01 19:49:44 +0000 |
commit | 90ce0701efdeb477c5832baea015d4c20b99d3ee (patch) | |
tree | 18315e142e806f8c634358c2750a56ba9514da8a /Master/texmf-dist/tex/luatex | |
parent | 0103a9d41473d542650463023e5ac4d4eb18c5ff (diff) |
markdown (1oct22)
git-svn-id: svn://tug.org/texlive/trunk@64570 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex')
-rw-r--r-- | Master/texmf-dist/tex/luatex/markdown/markdown.lua | 666 |
1 files changed, 379 insertions, 287 deletions
diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown.lua b/Master/texmf-dist/tex/luatex/markdown/markdown.lua index 79b875ea469..2081d0d3ff5 100644 --- a/Master/texmf-dist/tex/luatex/markdown/markdown.lua +++ b/Master/texmf-dist/tex/luatex/markdown/markdown.lua @@ -58,7 +58,7 @@ -- those in the standard .ins files. -- local metadata = { - version = "2.16.1-0-gf8a4bea", + version = "2.17.0-0-g6428569", comment = "A module for the conversion from markdown to plain TeX", author = "John MacFarlane, Hans Hagen, Vít Novotný", copyright = {"2009-2016 John MacFarlane, Hans Hagen", @@ -79,6 +79,38 @@ local unicode end)() local md5 = require("md5") local M = {metadata = metadata} +local walkable_syntax = { + Block = { + "Blockquote", + "Verbatim", + "HorizontalRule", + "BulletList", + "OrderedList", + "Heading", + "DisplayHtml", + "Paragraph", + "Plain", + }, + Inline = { + "Str", + "Space", + "Endline", + "UlOrStarLine", + "Strong", + "Emph", + "Link", + "Image", + "Code", + "AutoLinkUrl", + "AutoLinkEmail", + "AutoLinkRelativeReference", + "InlineHtml", + "HtmlEntity", + "EscapedChar", + "Smart", + "Symbol", + }, +} local defaultOptions = {} defaultOptions.cacheDir = "." defaultOptions.frozenCacheFileName = "frozenCache.tex" @@ -93,6 +125,9 @@ defaultOptions.contentBlocks = false defaultOptions.contentBlocksLanguageMap = "markdown-languages.json" defaultOptions.definitionLists = false defaultOptions.eagerCache = true +metadata.user_extension_api_version = 1 +metadata.grammar_version = 1 +defaultOptions.extensions = {} defaultOptions.expectJekyllData = false defaultOptions.fancyLists = false defaultOptions.fencedCode = false @@ -138,7 +173,7 @@ function util.cache(dir, string, salt, transform, suffix) local file = io.open(name, "r") if file == nil then -- If no cache entry exists, then create a new one. file = assert(io.open(name, "w"), - [[could not open file "]] .. name .. [[" for writing]]) + [[Could not open file "]] .. name .. [[" for writing]]) local result = string if transform ~= nil then result = transform(result) @@ -153,6 +188,20 @@ function util.table_copy(t) for k, v in pairs(t) do u[k] = v end return setmetatable(u, getmetatable(t)) end +util.lookup_files = (function() + local ran_ok, kpse = pcall(require, "kpse") + if ran_ok then + kpse.set_program_name("luatex") + else + kpse = { lookup = function(f, _) return f end } + end + + local function lookup_files(f, options) + return kpse.lookup(f, options) + end + + return lookup_files +end)() function util.expand_tabs_in_line(s, tabstop) local tab = tabstop or 4 local corr = 0 @@ -3097,7 +3146,7 @@ local function strip_atx_end(s) return s:gsub("[#%s]*\n$","") end M.reader = {} -function M.reader.new(writer, options, extensions) +function M.reader.new(writer, options) local self = {} self.writer = writer self.options = options @@ -3573,236 +3622,234 @@ function M.reader.new(writer, options, extensions) / writer.heading parsers.Heading = parsers.AtxHeading + parsers.SetextHeading - self.syntax = - { "Blocks", - - Blocks = ( V("ExpectedJekyllData") - * (V("Blank")^0 / writer.interblocksep) - )^-1 - * V("Blank")^0 - * V("Block")^-1 - * (V("Blank")^0 / writer.interblocksep - * V("Block"))^0 - * V("Blank")^0 * parsers.eof, - - Blank = parsers.Blank, - - UnexpectedJekyllData = parsers.fail, - ExpectedJekyllData = parsers.fail, - - Block = V("ContentBlock") - + V("UnexpectedJekyllData") - + V("Blockquote") - + V("PipeTable") - + V("Verbatim") - + V("FencedCode") - + V("HorizontalRule") - + V("BulletList") - + V("OrderedList") - + V("Heading") - + V("DefinitionList") - + V("DisplayHtml") - + V("Paragraph") - + V("Plain"), - - ContentBlock = parsers.fail, - Blockquote = parsers.Blockquote, - Verbatim = parsers.Verbatim, - FencedCode = parsers.fail, - HorizontalRule = parsers.HorizontalRule, - BulletList = parsers.BulletList, - OrderedList = parsers.OrderedList, - Heading = parsers.Heading, - DefinitionList = parsers.fail, - DisplayHtml = parsers.DisplayHtml, - Paragraph = parsers.Paragraph, - PipeTable = parsers.fail, - Plain = parsers.Plain, - EndlineExceptions = parsers.EndlineExceptions, - - Inline = V("Str") - + V("Space") - + V("Endline") - + V("UlOrStarLine") - + V("Strong") - + V("Emph") - + V("StrikeThrough") - + V("Superscript") - + V("Subscript") - + V("InlineNote") - + V("NoteRef") - + V("Citations") - + V("Link") - + V("Image") - + V("Code") - + V("AutoLinkUrl") - + V("AutoLinkEmail") - + V("AutoLinkRelativeReference") - + V("InlineHtml") - + V("HtmlEntity") - + V("EscapedChar") - + V("Smart") - + V("Symbol"), - - IndentedInline = V("Str") - + V("OptionalIndent") - + V("Endline") - + V("UlOrStarLine") - + V("Strong") - + V("Emph") - + V("StrikeThrough") - + V("Superscript") - + V("Subscript") - + V("InlineNote") - + V("NoteRef") - + V("Citations") - + V("Link") - + V("Image") - + V("Code") - + V("AutoLinkUrl") - + V("AutoLinkEmail") - + V("AutoLinkRelativeReference") - + V("InlineHtml") - + V("HtmlEntity") - + V("EscapedChar") - + V("Smart") - + V("Symbol"), - - Str = parsers.Str, - Space = parsers.Space, - OptionalIndent = parsers.OptionalIndent, - Endline = parsers.Endline, - UlOrStarLine = parsers.UlOrStarLine, - Strong = parsers.Strong, - Emph = parsers.Emph, - StrikeThrough = parsers.fail, - Superscript = parsers.fail, - Subscript = parsers.fail, - InlineNote = parsers.fail, - NoteRef = parsers.fail, - Citations = parsers.fail, - Link = parsers.Link, - Image = parsers.Image, - Code = parsers.Code, - AutoLinkUrl = parsers.AutoLinkUrl, - AutoLinkEmail = parsers.AutoLinkEmail, - AutoLinkRelativeReference - = parsers.AutoLinkRelativeReference, - InlineHtml = parsers.InlineHtml, - HtmlEntity = parsers.HtmlEntity, - EscapedChar = parsers.EscapedChar, - Smart = parsers.Smart, - Symbol = parsers.Symbol, - SpecialChar = parsers.fail, - } - local special_characters = {"*", "`", "[", "]", "<", "!", "\\"} - self.add_special_character = function(c) - table.insert(special_characters, c) - self.syntax.SpecialChar = S(table.concat(special_characters, "")) - end - self.syntax.SpecialChar = S(table.concat(special_characters, "")) - for _, extension in ipairs(extensions) do - extension.extend_writer(writer) - extension.extend_reader(self) - end + function self.finalize_grammar(extensions) + local walkable_syntax = (function(global_walkable_syntax) + local local_walkable_syntax = {} + for lhs, rule in pairs(global_walkable_syntax) do + local_walkable_syntax[lhs] = util.table_copy(rule) + end + return local_walkable_syntax + end)(walkable_syntax) + self.insert_pattern = function(selector, pattern) + local _, _, lhs, pos, rhs = selector:find("^(%a+)%s+([%a%s]+%a+)%s+(%a+)$") + assert(lhs ~= nil, + [[Expected selector in form "LHS (before|after|instead of) RHS", not "]] + .. selector .. [["]]) + assert(walkable_syntax[lhs] ~= nil, + [[Rule ]] .. lhs .. [[ -> ... does not exist in markdown grammar]]) + assert(pos == "before" or pos == "after" or pos == "instead of", + [[Expected positional specifier "before", "after", or "instead of", not "]] + .. pos .. [["]]) + local rule = walkable_syntax[lhs] + local index = nil + for current_index, current_rhs in ipairs(rule) do + if type(current_rhs) == "string" and current_rhs == rhs then + index = current_index + if pos == "after" then + index = index + 1 + end + break + end + end + assert(index ~= nil, + [[Rule ]] .. lhs .. [[ -> ]] .. rhs + .. [[ does not exist in markdown grammar]]) + if pos == "instead of" then + rule[index] = pattern + else + table.insert(rule, index, pattern) + end + end + local syntax = + { "Blocks", + + Blocks = ( V("ExpectedJekyllData") + * (V("Blank")^0 / writer.interblocksep) + )^-1 + * V("Blank")^0 + * V("Block")^-1 + * (V("Blank")^0 / writer.interblocksep + * V("Block"))^0 + * V("Blank")^0 * parsers.eof, + + ExpectedJekyllData = parsers.fail, + + Blank = parsers.Blank, + + Blockquote = parsers.Blockquote, + Verbatim = parsers.Verbatim, + HorizontalRule = parsers.HorizontalRule, + BulletList = parsers.BulletList, + OrderedList = parsers.OrderedList, + Heading = parsers.Heading, + DisplayHtml = parsers.DisplayHtml, + Paragraph = parsers.Paragraph, + Plain = parsers.Plain, + EndlineExceptions = parsers.EndlineExceptions, + + Str = parsers.Str, + Space = parsers.Space, + OptionalIndent = parsers.OptionalIndent, + Endline = parsers.Endline, + UlOrStarLine = parsers.UlOrStarLine, + Strong = parsers.Strong, + Emph = parsers.Emph, + Link = parsers.Link, + Image = parsers.Image, + Code = parsers.Code, + AutoLinkUrl = parsers.AutoLinkUrl, + AutoLinkEmail = parsers.AutoLinkEmail, + AutoLinkRelativeReference + = parsers.AutoLinkRelativeReference, + InlineHtml = parsers.InlineHtml, + HtmlEntity = parsers.HtmlEntity, + EscapedChar = parsers.EscapedChar, + Smart = parsers.Smart, + Symbol = parsers.Symbol, + SpecialChar = parsers.fail, + } + self.update_rule = function(rule_name, pattern) + assert(syntax[rule_name] ~= nil, + [[Rule ]] .. rule_name .. [[ -> ... does not exist in markdown grammar]]) + walkable_syntax[rule_name] = { pattern } + end + local special_characters = {} + self.add_special_character = function(c) + table.insert(special_characters, c) + syntax.SpecialChar = S(table.concat(special_characters, "")) + end - if options.underscores then - self.add_special_character("_") - end + self.add_special_character("*") + self.add_special_character("`") + self.add_special_character("[") + self.add_special_character("]") + self.add_special_character("<") + self.add_special_character("!") + self.add_special_character("\\") + for _, extension in ipairs(extensions) do + extension.extend_writer(writer) + extension.extend_reader(self) + end + walkable_syntax["IndentedInline"] = util.table_copy( + walkable_syntax["Inline"]) + self.insert_pattern( + "IndentedInline instead of Space", + "OptionalIndent") + for lhs, rule in pairs(walkable_syntax) do + syntax[lhs] = parsers.fail + for _, rhs in ipairs(rule) do + local pattern + if type(rhs) == "string" then + pattern = V(rhs) + else + pattern = rhs + end + syntax[lhs] = syntax[lhs] + pattern + end + end + if options.underscores then + self.add_special_character("_") + end - if not options.codeSpans then - self.syntax.Code = parsers.fail - end + if not options.codeSpans then + syntax.Code = parsers.fail + end - if not options.html then - self.syntax.DisplayHtml = parsers.fail - self.syntax.InlineHtml = parsers.fail - self.syntax.HtmlEntity = parsers.fail - else - self.add_special_character("&") - end + if not options.html then + syntax.DisplayHtml = parsers.fail + syntax.InlineHtml = parsers.fail + syntax.HtmlEntity = parsers.fail + else + self.add_special_character("&") + end - if options.preserveTabs then - options.stripIndent = false - end + if options.preserveTabs then + options.stripIndent = false + end - if not options.smartEllipses then - self.syntax.Smart = parsers.fail - else - self.add_special_character(".") - end + if not options.smartEllipses then + syntax.Smart = parsers.fail + else + self.add_special_character(".") + end - if not options.relativeReferences then - self.syntax.AutoLinkRelativeReference = parsers.fail - end + if not options.relativeReferences then + syntax.AutoLinkRelativeReference = parsers.fail + end - local blocks_nested_t = util.table_copy(self.syntax) - blocks_nested_t.ExpectedJekyllData = parsers.fail - parsers.blocks_nested = Ct(blocks_nested_t) - - parsers.blocks = Ct(self.syntax) - - local inlines_t = util.table_copy(self.syntax) - inlines_t[1] = "Inlines" - inlines_t.Inlines = parsers.Inline^0 * (parsers.spacing^0 * parsers.eof / "") - parsers.inlines = Ct(inlines_t) - - local inlines_no_link_t = util.table_copy(inlines_t) - inlines_no_link_t.Link = parsers.fail - parsers.inlines_no_link = Ct(inlines_no_link_t) - - local inlines_no_inline_note_t = util.table_copy(inlines_t) - inlines_no_inline_note_t.InlineNote = parsers.fail - parsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) - - local inlines_no_html_t = util.table_copy(inlines_t) - inlines_no_html_t.DisplayHtml = parsers.fail - inlines_no_html_t.InlineHtml = parsers.fail - inlines_no_html_t.HtmlEntity = parsers.fail - parsers.inlines_no_html = Ct(inlines_no_html_t) - - local inlines_nbsp_t = util.table_copy(inlines_t) - inlines_nbsp_t.Endline = parsers.NonbreakingEndline - inlines_nbsp_t.Space = parsers.NonbreakingSpace - parsers.inlines_nbsp = Ct(inlines_nbsp_t) - function self.convert(input) - references = {} - local opt_string = {} - for k,_ in pairs(defaultOptions) do - local v = options[k] - if k ~= "cacheDir" then - opt_string[#opt_string+1] = k .. "=" .. tostring(v) + local blocks_nested_t = util.table_copy(syntax) + blocks_nested_t.ExpectedJekyllData = parsers.fail + parsers.blocks_nested = Ct(blocks_nested_t) + + parsers.blocks = Ct(syntax) + + local inlines_t = util.table_copy(syntax) + inlines_t[1] = "Inlines" + inlines_t.Inlines = parsers.Inline^0 * (parsers.spacing^0 * parsers.eof / "") + parsers.inlines = Ct(inlines_t) + + local inlines_no_link_t = util.table_copy(inlines_t) + inlines_no_link_t.Link = parsers.fail + parsers.inlines_no_link = Ct(inlines_no_link_t) + + local inlines_no_inline_note_t = util.table_copy(inlines_t) + inlines_no_inline_note_t.InlineNote = parsers.fail + parsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) + + local inlines_no_html_t = util.table_copy(inlines_t) + inlines_no_html_t.DisplayHtml = parsers.fail + inlines_no_html_t.InlineHtml = parsers.fail + inlines_no_html_t.HtmlEntity = parsers.fail + parsers.inlines_no_html = Ct(inlines_no_html_t) + + local inlines_nbsp_t = util.table_copy(inlines_t) + inlines_nbsp_t.Endline = parsers.NonbreakingEndline + inlines_nbsp_t.Space = parsers.NonbreakingSpace + parsers.inlines_nbsp = Ct(inlines_nbsp_t) + return function(input) + references = {} + local opt_string = {} + for k, _ in pairs(defaultOptions) do + local v = options[k] + if type(v) == "table" then + for _, i in ipairs(v) do + opt_string[#opt_string+1] = k .. "=" .. tostring(i) + end + elseif k ~= "cacheDir" then + opt_string[#opt_string+1] = k .. "=" .. tostring(v) + end end - end - table.sort(opt_string) - local salt = table.concat(opt_string, ",") .. "," .. metadata.version - local output - local function convert(input) - local document = self.parser_functions.parse_blocks(input) - return util.rope_to_string(writer.document(document)) - end - if options.eagerCache or options.finalizeCache then - local name = util.cache(options.cacheDir, input, salt, convert, ".md" .. writer.suffix) - output = writer.pack(name) - else - output = convert(input) - end - if options.finalizeCache then - local file, mode - if options.frozenCacheCounter > 0 then - mode = "a" + table.sort(opt_string) + local salt = table.concat(opt_string, ",") .. "," .. metadata.version + local output + local function convert(input) + local document = self.parser_functions.parse_blocks(input) + return util.rope_to_string(writer.document(document)) + end + if options.eagerCache or options.finalizeCache then + local name = util.cache(options.cacheDir, input, salt, convert, + ".md" .. writer.suffix) + output = writer.pack(name) else - mode = "w" + output = convert(input) end - file = assert(io.open(options.frozenCacheFileName, mode), - [[could not open file "]] .. options.frozenCacheFileName - .. [[" for writing]]) - assert(file:write([[\expandafter\global\expandafter\def\csname ]] - .. [[markdownFrozenCache]] .. options.frozenCacheCounter - .. [[\endcsname{]] .. output .. [[}]] .. "\n")) - assert(file:close()) + if options.finalizeCache then + local file, mode + if options.frozenCacheCounter > 0 then + mode = "a" + else + mode = "w" + end + file = assert(io.open(options.frozenCacheFileName, mode), + [[Could not open file "]] .. options.frozenCacheFileName + .. [[" for writing]]) + assert(file:write([[\expandafter\global\expandafter\def\csname ]] + .. [[markdownFrozenCache]] .. options.frozenCacheCounter + .. [[\endcsname{]] .. output .. [[}]] .. "\n")) + assert(file:close()) + end + return output end - return output end return self end @@ -3838,7 +3885,6 @@ M.extensions.citations = function(citation_nbsps) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local citation_chars @@ -3946,7 +3992,7 @@ M.extensions.citations = function(citation_nbsps) local Citations = TextCitations + ParenthesizedCitations - syntax.Citations = Citations + self.insert_pattern("Inline after Emph", Citations) self.add_special_character("@") self.add_special_character("-") @@ -3955,21 +4001,14 @@ M.extensions.citations = function(citation_nbsps) end M.extensions.content_blocks = function(language_map) local languages_json = (function() - local ran_ok, kpse = pcall(require, "kpse") - if ran_ok then - kpse.set_program_name("luatex") - else - kpse = {lookup=function(filename, _) return filename end} - end local base, prev, curr - for _, filename in ipairs{kpse.lookup(language_map, { all=true })} do - local file = io.open(filename, "r") + for _, pathname in ipairs{util.lookup_files(language_map, { all=true })} do + local file = io.open(pathname, "r") if not file then goto continue end - local json = file:read("*all"):gsub('("[^\n]-"):','[%1]=') - curr = (function() - local _ENV={ json=json, load=load } -- luacheck: ignore _ENV - return load("return "..json)() - end)() + local input = assert(file:read("*a")) + assert(file:close()) + local json = input:gsub('("[^\n]-"):','[%1]=') + curr = load("_ENV = {}; return "..json)() if type(curr) == "table" then if base == nil then base = curr @@ -4009,7 +4048,6 @@ M.extensions.content_blocks = function(language_map) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local contentblock_tail @@ -4083,7 +4121,7 @@ M.extensions.content_blocks = function(language_map) * contentblock_tail / writer.contentblock - syntax.ContentBlock = ContentBlock + self.insert_pattern("Block before Blockquote", ContentBlock) end } end @@ -4116,7 +4154,6 @@ M.extensions.definition_lists = function(tight_lists) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local defstartchar = S("~:") @@ -4159,7 +4196,7 @@ M.extensions.definition_lists = function(tight_lists) * -DefinitionListItemLoose * Cc(true)) ) / writer.definitionlist - syntax.DefinitionList = DefinitionList + self.insert_pattern("Block after Heading", DefinitionList) end } end @@ -4176,7 +4213,6 @@ M.extensions.fenced_code = function(blank_before_code_fence) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local function captures_geq_length(_,i,a,b) @@ -4239,7 +4275,8 @@ M.extensions.fenced_code = function(blank_before_code_fence) self.expandtabs(code)) end - syntax.FencedCode = FencedCode + self.insert_pattern("Block after Verbatim", + FencedCode) local fencestart if blank_before_code_fence then @@ -4249,8 +4286,8 @@ M.extensions.fenced_code = function(blank_before_code_fence) + fencehead(parsers.tilde) end - parsers.EndlineExceptions = parsers.EndlineExceptions + fencestart - syntax.EndlineExceptions = parsers.EndlineExceptions + local EndlineExceptions = parsers.EndlineExceptions + fencestart + self.update_rule("EndlineExceptions", EndlineExceptions) self.add_special_character("~") end @@ -4265,9 +4302,16 @@ M.extensions.footnotes = function(footnotes, inline_footnotes) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer + if inline_footnotes then + local InlineNote + = parsers.circumflex + * (parsers.tag / self.parser_functions.parse_inlines_no_inline_note) + / writer.note + + self.insert_pattern("Inline after Emph", InlineNote) + end if footnotes then local function strip_first_char(s) return s:sub(2) @@ -4305,17 +4349,10 @@ M.extensions.footnotes = function(footnotes, inline_footnotes) * parsers.spnl * parsers.indented_blocks(parsers.chunk) / register_note - parsers.Blank = NoteBlock + parsers.Blank - syntax.Blank = parsers.Blank + local Blank = NoteBlock + parsers.Blank + self.update_rule("Blank", Blank) - syntax.NoteRef = NoteRef - end - if inline_footnotes then - local InlineNote - = parsers.circumflex - * (parsers.tag / self.parser_functions.parse_inlines_no_inline_note) - / writer.note - syntax.InlineNote = InlineNote + self.insert_pattern("Inline after Emph", NoteRef) end self.add_special_character("^") @@ -4327,7 +4364,6 @@ M.extensions.header_attributes = function() extend_writer = function() end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer parsers.AtxHeading = Cg(parsers.HeadingStart,"level") @@ -4372,8 +4408,8 @@ M.extensions.header_attributes = function() * parsers.newline / writer.heading - parsers.Heading = parsers.AtxHeading + parsers.SetextHeading - syntax.Heading = parsers.Heading + local Heading = parsers.AtxHeading + parsers.SetextHeading + self.update_rule("Heading", Heading) end } end @@ -4464,7 +4500,6 @@ M.extensions.jekyll_data = function(expect_jekyll_data) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local JekyllData @@ -4500,9 +4535,9 @@ M.extensions.jekyll_data = function(expect_jekyll_data) * JekyllData * (P("---") + P("..."))^-1 - syntax.UnexpectedJekyllData = UnexpectedJekyllData + self.insert_pattern("Block before Blockquote", UnexpectedJekyllData) if expect_jekyll_data then - syntax.ExpectedJekyllData = ExpectedJekyllData + self.update_rule("ExpectedJekyllData", ExpectedJekyllData) end end } @@ -4588,7 +4623,6 @@ M.extensions.pipe_tables = function(table_captions) end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local table_hline_separator = parsers.pipe + parsers.plus @@ -4643,7 +4677,7 @@ M.extensions.pipe_tables = function(table_captions) * table_caption^-1 / writer.table - syntax.PipeTable = PipeTable + self.insert_pattern("Block after Blockquote", PipeTable) end } end @@ -4655,7 +4689,6 @@ M.extensions.strike_through = function() end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local StrikeThrough = ( @@ -4663,7 +4696,7 @@ M.extensions.strike_through = function() parsers.doubletildes) ) / writer.strike_through - syntax.StrikeThrough = StrikeThrough + self.insert_pattern("Inline after Emph", StrikeThrough) self.add_special_character("~") end @@ -4677,14 +4710,13 @@ M.extensions.superscripts = function() end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local Superscript = ( parsers.between(parsers.Str, parsers.circumflex, parsers.circumflex) ) / writer.superscript - syntax.Superscript = Superscript + self.insert_pattern("Inline after Emph", Superscript) self.add_special_character("^") end @@ -4698,14 +4730,13 @@ M.extensions.subscripts = function() end end, extend_reader = function(self) local parsers = self.parsers - local syntax = self.syntax local writer = self.writer local Subscript = ( parsers.between(parsers.Str, parsers.tilde, parsers.tilde) ) / writer.subscript - syntax.Subscript = Subscript + self.insert_pattern("Inline after Emph", Subscript) self.add_special_character("~") end @@ -4748,7 +4779,6 @@ M.extensions.fancy_lists = function() end, extend_reader = function(self) local parsers = self.parsers local options = self.options - local syntax = self.syntax local writer = self.writer local label = parsers.dig + parsers.letter @@ -4864,8 +4894,7 @@ M.extensions.fancy_lists = function() * Cc(false) * parsers.skipblanklines ) * Cb("listtype") / fancylist - syntax.OrderedList = FancyList - + self.update_rule("OrderedList", FancyList) end } end @@ -4875,11 +4904,6 @@ function M.new(options) return defaultOptions[key] end }) local extensions = {} - if options.citations then - local citations_extension = M.extensions.citations(options.citationNbsps) - table.insert(extensions, citations_extension) - end - if options.contentBlocks then local content_blocks_extension = M.extensions.content_blocks( options.contentBlocksLanguageMap) @@ -4898,12 +4922,6 @@ function M.new(options) table.insert(extensions, fenced_code_extension) end - if options.footnotes or options.inlineFootnotes then - local footnotes_extension = M.extensions.footnotes( - options.footnotes, options.inlineFootnotes) - table.insert(extensions, footnotes_extension) - end - if options.headerAttributes then local header_attributes_extension = M.extensions.header_attributes() table.insert(extensions, header_attributes_extension) @@ -4936,15 +4954,89 @@ function M.new(options) table.insert(extensions, superscript_extension) end + if options.footnotes or options.inlineFootnotes then + local footnotes_extension = M.extensions.footnotes( + options.footnotes, options.inlineFootnotes) + table.insert(extensions, footnotes_extension) + end + + if options.citations then + local citations_extension = M.extensions.citations(options.citationNbsps) + table.insert(extensions, citations_extension) + end + if options.fancyLists then local fancy_lists_extension = M.extensions.fancy_lists() table.insert(extensions, fancy_lists_extension) end - + for _, user_extension_filename in ipairs(options.extensions) do + local user_extension = (function(filename) + local pathname = util.lookup_files(filename) + local input_file = assert(io.open(pathname, "r"), + [[Could not open user-defined syntax extension "]] + .. pathname .. [[" for reading]]) + local input = assert(input_file:read("*a")) + assert(input_file:close()) + local user_extension, err = load([[ + local sandbox = {} + setmetatable(sandbox, {__index = _G}) + _ENV = sandbox + ]] .. input)() + assert(user_extension, + [[Failed to compile user-defined syntax extension "]] + .. pathname .. [[": ]] .. (err or [[]])) + assert(user_extension.api_version ~= nil, + [[User-defined syntax extension "]] .. pathname + .. [[" does not specify mandatory field "api_version"]]) + assert(type(user_extension.api_version) == "number", + [[User-defined syntax extension "]] .. pathname + .. [[" specifies field "api_version" of type "]] + .. type(user_extension.api_version) + .. [[" but "number" was expected]]) + assert(user_extension.api_version == metadata.user_extension_api_version, + [[User-defined syntax extension "]] .. pathname + .. [[" uses syntax extension API version "]] + .. user_extension.api_version .. [[ but markdown.lua ]] + .. metadata.version .. [[ uses API version ]] + .. metadata.user_extension_api_version + .. [[, which is incompatible]]) + + assert(user_extension.grammar_version ~= nil, + [[User-defined syntax extension "]] .. pathname + .. [[" does not specify mandatory field "grammar_version"]]) + assert(type(user_extension.grammar_version) == "number", + [[User-defined syntax extension "]] .. pathname + .. [[" specifies field "grammar_version" of type "]] + .. type(user_extension.grammar_version) + .. [[" but "number" was expected]]) + assert(user_extension.grammar_version == metadata.grammar_version, + [[User-defined syntax extension "]] .. pathname + .. [[" uses grammar version "]] .. user_extension.grammar_version + .. [[ but markdown.lua ]] .. metadata.version + .. [[ uses grammar version ]] .. metadata.grammar_version + .. [[, which is incompatible]]) + + assert(user_extension.finalize_grammar ~= nil, + [[User-defined syntax extension "]] .. pathname + .. [[" does not specify mandatory "finalize_grammar" field]]) + assert(type(user_extension.finalize_grammar) == "function", + [[User-defined syntax extension "]] .. pathname + .. [[" specifies field "finalize_grammar" of type "]] + .. type(user_extension.finalize_grammar) + .. [[" but "function" was expected]]) + local extension = { + extend_reader = user_extension.finalize_grammar, + extend_writer = function() end, + } + return extension + end)(user_extension_filename) + table.insert(extensions, user_extension) + end local writer = M.writer.new(options) - local reader = M.reader.new(writer, options, extensions) + local reader = M.reader.new(writer, options) + local convert = reader.finalize_grammar(extensions) - return reader.convert + return convert end return M |