diff options
author | Karl Berry <karl@freefriends.org> | 2017-01-05 22:56:40 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2017-01-05 22:56:40 +0000 |
commit | f78056943ffe36621ca1cf595657f324cda9b13f (patch) | |
tree | 99a8c163e460573cbc8d0e27f501edd20177d1d7 /Master/texmf-dist | |
parent | 34a0db48cc3c734ab21f489d2f710e74a3fda6af (diff) |
markdown (5jan17)
git-svn-id: svn://tug.org/texlive/trunk@42868 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist')
-rw-r--r-- | Master/texmf-dist/doc/generic/markdown/markdown.pdf | bin | 323225 -> 331142 bytes | |||
-rw-r--r-- | Master/texmf-dist/source/generic/markdown/markdown.dtx | 2359 | ||||
-rw-r--r-- | Master/texmf-dist/tex/context/third/markdown/t-markdown.tex | 30 | ||||
-rw-r--r-- | Master/texmf-dist/tex/generic/markdown/markdown.tex | 137 | ||||
-rw-r--r-- | Master/texmf-dist/tex/latex/markdown/markdown.sty | 42 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/markdown/markdown.lua | 1685 |
6 files changed, 2552 insertions, 1701 deletions
diff --git a/Master/texmf-dist/doc/generic/markdown/markdown.pdf b/Master/texmf-dist/doc/generic/markdown/markdown.pdf Binary files differindex 4431869aa1f..3ff429866fe 100644 --- a/Master/texmf-dist/doc/generic/markdown/markdown.pdf +++ b/Master/texmf-dist/doc/generic/markdown/markdown.pdf diff --git a/Master/texmf-dist/source/generic/markdown/markdown.dtx b/Master/texmf-dist/source/generic/markdown/markdown.dtx index 99e9fb04a34..1618ea62478 100644 --- a/Master/texmf-dist/source/generic/markdown/markdown.dtx +++ b/Master/texmf-dist/source/generic/markdown/markdown.dtx @@ -46,9 +46,9 @@ \hyperref[lua:#1]{\t`#1`}} \def\t`#1`{% Inline code \textcolor{spot}{\text{\texttt{#1}}}} -\newcommand\Optitem[2][]{% An option item definition +\newcommand\Optitem[2][]{\penalty -1000\relax % An option item definition \phantomsection\label{opt:#2}\optitem[#1]{#2}} -\newcommand\Valitem[2][]{% A value item definition +\newcommand\Valitem[2][]{\penalty -1000\relax % A value item definition \phantomsection\label{opt:#2}\valitem[#1]{#2}} \newcommand\Opt[1]{% An option / value item reference \hyperref[opt:#1]{\t`#1`}} @@ -147,10 +147,11 @@ % \fi % \begin{macrocode} local metadata = { - version = "2.2.2", + version = "2.3.0", 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; 2016 Vít Novotný", + copyright = "2009-2017 John MacFarlane, Hans Hagen; " .. + "2016-2017 Vít Novotný", license = "LPPL 1.3" } if not modules then modules = { } end @@ -237,7 +238,20 @@ local md5 = require("md5") % engine (see \cite[Section~3.3]{luatex16}). % % The plain \TeX{} part of the package also requires that either the Lua\TeX{} -% \m{directlua} primitive or the shell access file stream 18 is available. +% \m{directlua} primitive or the shell access file stream 18 is available in +% your \TeX{} engine. If only the shell access file stream is available in your +% \TeX{} engine (as is the case with \hologo{pdfTeX} and \Hologo{XeTeX}) or if +% you enforce the use of shell using the \m{markdownMode} macro, then note the +% following: +% \begin{itemize} +% \item Unless your \TeX{} engine is globally configured to enable shell +% access, you will need to provide the \t`-shell-escape` parameter to your +% engine when typesetting a document. +% \item You will need to avoid the use of the \t`-output-directory` \TeX{} +% parameter when typesetting a document. The parameter causes auxiliary +% files to be written to a specified output directory, but the shell will +% be executed in the current directory. Things will not work out. +% \end{itemize} % % \iffalse %</tex> @@ -370,6 +384,15 @@ defaultOptions.blankBeforeCodeFence = false defaultOptions.blankBeforeHeading = false % \end{macrocode} % +% \Optitem[false]{breakableBlockquotes}{\opt{true}, \opt{false}} +% \begin{valuelist} +% \item[true] A blank line separates block quotes. +% \item[false] Blank lines in the middle of a block quote are ignored. +% \end{valuelist} +% \begin{macrocode} +defaultOptions.breakableBlockquotes = false +% \end{macrocode} +% % \Valitem[.]{cacheDir}{directory} % The path to the directory containing auxiliary cache files. % @@ -456,6 +479,22 @@ defaultOptions.definitionLists = false % \begin{macrocode} defaultOptions.hashEnumerators = false % \end{macrocode} +% +% \Optitem[false]{html}{\opt{true}, \opt{false}} +% \begin{valuelist} +% \item[true] Enable the recognition of \textsc{html} tags, block +% elements, comments, \textsc{html} instructions, and +% entities in the input. Tags, block elements (along with +% contents), \textsc{html} instructions, and comments will +% be ignored and \textsc{html} entities will be replaced +% with the corresponding Unicode codepoints. +% \item[true] Disable the recognition of \textsc{html} markup. Any +% \textsc{html} markup in the input will be rendered as +% plain text. +% \end{valuelist} +% \begin{macrocode} +defaultOptions.html = false +% \end{macrocode} % % \Optitem[false]{hybrid}{\opt{true}, \opt{false}} % \begin{valuelist} @@ -600,8 +639,8 @@ defaultOptions.tightLists = true % \ref{sec:luaoptions}) used during the conversion from markdown to plain % \TeX{}, and for changing the way markdown the tokens are rendered. % \begin{macrocode} -\def\markdownLastModified{2016/12/09}% -\def\markdownVersion{2.2.2}% +\def\markdownLastModified{2017/01/05}% +\def\markdownVersion{2.3.0}% % \end{macrocode} % % The plain \TeX{} interface is implemented by the \t`markdown.tex` file that @@ -739,12 +778,14 @@ defaultOptions.tightLists = true \let\markdownOptionBlankBeforeBlockquote\undefined \let\markdownOptionBlankBeforeCodeFence\undefined \let\markdownOptionBlankBeforeHeading\undefined +\let\markdownOptionBreakableBlockquotes\undefined \let\markdownOptionCitations\undefined \let\markdownOptionCitationNbsps\undefined \let\markdownOptionDefinitionLists\undefined \let\markdownOptionFootnotes\undefined \let\markdownOptionFencedCode\undefined \let\markdownOptionHashEnumerators\undefined +\let\markdownOptionHtml\undefined \let\markdownOptionHybrid\undefined \let\markdownOptionInlineFootnotes\undefined \let\markdownOptionPreserveTabs\undefined @@ -1251,26 +1292,6 @@ defaultOptions.tightLists = true % error messages. % % \subsubsection{Miscellanea} -% The \mdef{markdownLuaRegisterIBCallback} and -% \mdef{markdownLuaUnregisterIBCallback} macros specify the Lua code for -% registering and unregistering a callback for changing the contents of the -% line input buffer before a \TeX{} engine that supports direct Lua access via -% the \m{directlua} macro starts looking at it. The first argument of the -% \m{markdownLuaRegisterIBCallback} macro corresponds to the callback function -% being registered. -% -% Local members defined within \m{markdownLuaRegisterIBCallback} are guaranteed -% to be visible from \m{markdownLuaUnregisterIBCallback} and the execution of -% the two macros alternates, so it is not necessary to consider the case, when -% one of the macros is called twice in a row. -% \begin{macrocode} -\def\markdownLuaRegisterIBCallback#1{% - local old_callback = callback.find("process_input_buffer") - callback.register("process_input_buffer", #1)}% -\def\markdownLuaUnregisterIBCallback{% - callback.register("process_input_buffer", old_callback)}% -% \end{macrocode} -% % The \mdef{markdownMakeOther} macro is used by the package, when a \TeX{} % engine that does not support direct Lua access is starting to buffer a text. % The plain \TeX{} implementation changes the category code of plain \TeX{} @@ -1331,7 +1352,12 @@ defaultOptions.tightLists = true \fi % \end{macrocode} % -% +% The following macros are no longer a part of the plain \TeX{} interface and +% are only defined for backwards compatibility: +% \begin{macrocode} +\def\markdownLuaRegisterIBCallback#1{\relax}% +\def\markdownLuaUnregisterIBCallback#1{\relax}% +% \end{macrocode} % \iffalse %</tex> %<*latex> @@ -1448,6 +1474,8 @@ defaultOptions.tightLists = true \def\markdownOptionBlankBeforeCodeFence{#1}}% \define@key{markdownOptions}{blankBeforeHeading}[true]{% \def\markdownOptionBlankBeforeHeading{#1}}% +\define@key{markdownOptions}{breakableBlockquotes}[true]{% + \def\markdownOptionBreakableBlockquotes{#1}}% \define@key{markdownOptions}{citations}[true]{% \def\markdownOptionCitations{#1}}% \define@key{markdownOptions}{citationNbsps}[true]{% @@ -1462,6 +1490,8 @@ defaultOptions.tightLists = true \def\markdownOptionFencedCode{#1}}% \define@key{markdownOptions}{hashEnumerators}[true]{% \def\markdownOptionHashEnumerators{#1}}% +\define@key{markdownOptions}{html}[true]{% + \def\markdownOptionHtml{#1}}% \define@key{markdownOptions}{hybrid}[true]{% \def\markdownOptionHybrid{#1}}% \define@key{markdownOptions}{inlineFootnotes}[true]{% @@ -1823,10 +1853,10 @@ local P, R, S, V, C, Cg, Cb, Cmt, Cc, Ct, B, Cs, any = % \end{macrocode} % % \subsubsection{Utility Functions} -% This section documents the utility functions used by the Lua code. These -% functions are encapsulated in the \t`util` object. The functions were -% originally located in the \t`lunamark/util.lua` file in the Lunamark Lua -% module. +% This section documents the utility functions used by the plain \TeX{} +% writer and the markdown reader. These functions are encapsulated in the +% \t`util` object. The functions were originally located in the +% \t`lunamark/util.lua` file in the Lunamark Lua module. % \begin{macrocode} local util = {} % \end{macrocode} @@ -2059,6 +2089,294 @@ function util.pathname(dir, file) end end % \end{macrocode} +% \subsubsection{\textsc{html} entities} +% This section documents the \textsc{html} entities recognized by the +% markdown reader. These functions are encapsulated in the \t`entities` +% object. The functions were originally located in the +% \t`lunamark/entities.lua` file in the Lunamark Lua module. +% \begin{macrocode} +local entities = {} + +local character_entities = { + ["quot"] = 0x0022, + ["amp"] = 0x0026, + ["apos"] = 0x0027, + ["lt"] = 0x003C, + ["gt"] = 0x003E, + ["nbsp"] = 160, + ["iexcl"] = 0x00A1, + ["cent"] = 0x00A2, + ["pound"] = 0x00A3, + ["curren"] = 0x00A4, + ["yen"] = 0x00A5, + ["brvbar"] = 0x00A6, + ["sect"] = 0x00A7, + ["uml"] = 0x00A8, + ["copy"] = 0x00A9, + ["ordf"] = 0x00AA, + ["laquo"] = 0x00AB, + ["not"] = 0x00AC, + ["shy"] = 173, + ["reg"] = 0x00AE, + ["macr"] = 0x00AF, + ["deg"] = 0x00B0, + ["plusmn"] = 0x00B1, + ["sup2"] = 0x00B2, + ["sup3"] = 0x00B3, + ["acute"] = 0x00B4, + ["micro"] = 0x00B5, + ["para"] = 0x00B6, + ["middot"] = 0x00B7, + ["cedil"] = 0x00B8, + ["sup1"] = 0x00B9, + ["ordm"] = 0x00BA, + ["raquo"] = 0x00BB, + ["frac14"] = 0x00BC, + ["frac12"] = 0x00BD, + ["frac34"] = 0x00BE, + ["iquest"] = 0x00BF, + ["Agrave"] = 0x00C0, + ["Aacute"] = 0x00C1, + ["Acirc"] = 0x00C2, + ["Atilde"] = 0x00C3, + ["Auml"] = 0x00C4, + ["Aring"] = 0x00C5, + ["AElig"] = 0x00C6, + ["Ccedil"] = 0x00C7, + ["Egrave"] = 0x00C8, + ["Eacute"] = 0x00C9, + ["Ecirc"] = 0x00CA, + ["Euml"] = 0x00CB, + ["Igrave"] = 0x00CC, + ["Iacute"] = 0x00CD, + ["Icirc"] = 0x00CE, + ["Iuml"] = 0x00CF, + ["ETH"] = 0x00D0, + ["Ntilde"] = 0x00D1, + ["Ograve"] = 0x00D2, + ["Oacute"] = 0x00D3, + ["Ocirc"] = 0x00D4, + ["Otilde"] = 0x00D5, + ["Ouml"] = 0x00D6, + ["times"] = 0x00D7, + ["Oslash"] = 0x00D8, + ["Ugrave"] = 0x00D9, + ["Uacute"] = 0x00DA, + ["Ucirc"] = 0x00DB, + ["Uuml"] = 0x00DC, + ["Yacute"] = 0x00DD, + ["THORN"] = 0x00DE, + ["szlig"] = 0x00DF, + ["agrave"] = 0x00E0, + ["aacute"] = 0x00E1, + ["acirc"] = 0x00E2, + ["atilde"] = 0x00E3, + ["auml"] = 0x00E4, + ["aring"] = 0x00E5, + ["aelig"] = 0x00E6, + ["ccedil"] = 0x00E7, + ["egrave"] = 0x00E8, + ["eacute"] = 0x00E9, + ["ecirc"] = 0x00EA, + ["euml"] = 0x00EB, + ["igrave"] = 0x00EC, + ["iacute"] = 0x00ED, + ["icirc"] = 0x00EE, + ["iuml"] = 0x00EF, + ["eth"] = 0x00F0, + ["ntilde"] = 0x00F1, + ["ograve"] = 0x00F2, + ["oacute"] = 0x00F3, + ["ocirc"] = 0x00F4, + ["otilde"] = 0x00F5, + ["ouml"] = 0x00F6, + ["divide"] = 0x00F7, + ["oslash"] = 0x00F8, + ["ugrave"] = 0x00F9, + ["uacute"] = 0x00FA, + ["ucirc"] = 0x00FB, + ["uuml"] = 0x00FC, + ["yacute"] = 0x00FD, + ["thorn"] = 0x00FE, + ["yuml"] = 0x00FF, + ["OElig"] = 0x0152, + ["oelig"] = 0x0153, + ["Scaron"] = 0x0160, + ["scaron"] = 0x0161, + ["Yuml"] = 0x0178, + ["fnof"] = 0x0192, + ["circ"] = 0x02C6, + ["tilde"] = 0x02DC, + ["Alpha"] = 0x0391, + ["Beta"] = 0x0392, + ["Gamma"] = 0x0393, + ["Delta"] = 0x0394, + ["Epsilon"] = 0x0395, + ["Zeta"] = 0x0396, + ["Eta"] = 0x0397, + ["Theta"] = 0x0398, + ["Iota"] = 0x0399, + ["Kappa"] = 0x039A, + ["Lambda"] = 0x039B, + ["Mu"] = 0x039C, + ["Nu"] = 0x039D, + ["Xi"] = 0x039E, + ["Omicron"] = 0x039F, + ["Pi"] = 0x03A0, + ["Rho"] = 0x03A1, + ["Sigma"] = 0x03A3, + ["Tau"] = 0x03A4, + ["Upsilon"] = 0x03A5, + ["Phi"] = 0x03A6, + ["Chi"] = 0x03A7, + ["Psi"] = 0x03A8, + ["Omega"] = 0x03A9, + ["alpha"] = 0x03B1, + ["beta"] = 0x03B2, + ["gamma"] = 0x03B3, + ["delta"] = 0x03B4, + ["epsilon"] = 0x03B5, + ["zeta"] = 0x03B6, + ["eta"] = 0x03B7, + ["theta"] = 0x03B8, + ["iota"] = 0x03B9, + ["kappa"] = 0x03BA, + ["lambda"] = 0x03BB, + ["mu"] = 0x03BC, + ["nu"] = 0x03BD, + ["xi"] = 0x03BE, + ["omicron"] = 0x03BF, + ["pi"] = 0x03C0, + ["rho"] = 0x03C1, + ["sigmaf"] = 0x03C2, + ["sigma"] = 0x03C3, + ["tau"] = 0x03C4, + ["upsilon"] = 0x03C5, + ["phi"] = 0x03C6, + ["chi"] = 0x03C7, + ["psi"] = 0x03C8, + ["omega"] = 0x03C9, + ["thetasym"] = 0x03D1, + ["upsih"] = 0x03D2, + ["piv"] = 0x03D6, + ["ensp"] = 0x2002, + ["emsp"] = 0x2003, + ["thinsp"] = 0x2009, + ["ndash"] = 0x2013, + ["mdash"] = 0x2014, + ["lsquo"] = 0x2018, + ["rsquo"] = 0x2019, + ["sbquo"] = 0x201A, + ["ldquo"] = 0x201C, + ["rdquo"] = 0x201D, + ["bdquo"] = 0x201E, + ["dagger"] = 0x2020, + ["Dagger"] = 0x2021, + ["bull"] = 0x2022, + ["hellip"] = 0x2026, + ["permil"] = 0x2030, + ["prime"] = 0x2032, + ["Prime"] = 0x2033, + ["lsaquo"] = 0x2039, + ["rsaquo"] = 0x203A, + ["oline"] = 0x203E, + ["frasl"] = 0x2044, + ["euro"] = 0x20AC, + ["image"] = 0x2111, + ["weierp"] = 0x2118, + ["real"] = 0x211C, + ["trade"] = 0x2122, + ["alefsym"] = 0x2135, + ["larr"] = 0x2190, + ["uarr"] = 0x2191, + ["rarr"] = 0x2192, + ["darr"] = 0x2193, + ["harr"] = 0x2194, + ["crarr"] = 0x21B5, + ["lArr"] = 0x21D0, + ["uArr"] = 0x21D1, + ["rArr"] = 0x21D2, + ["dArr"] = 0x21D3, + ["hArr"] = 0x21D4, + ["forall"] = 0x2200, + ["part"] = 0x2202, + ["exist"] = 0x2203, + ["empty"] = 0x2205, + ["nabla"] = 0x2207, + ["isin"] = 0x2208, + ["notin"] = 0x2209, + ["ni"] = 0x220B, + ["prod"] = 0x220F, + ["sum"] = 0x2211, + ["minus"] = 0x2212, + ["lowast"] = 0x2217, + ["radic"] = 0x221A, + ["prop"] = 0x221D, + ["infin"] = 0x221E, + ["ang"] = 0x2220, + ["and"] = 0x2227, + ["or"] = 0x2228, + ["cap"] = 0x2229, + ["cup"] = 0x222A, + ["int"] = 0x222B, + ["there4"] = 0x2234, + ["sim"] = 0x223C, + ["cong"] = 0x2245, + ["asymp"] = 0x2248, + ["ne"] = 0x2260, + ["equiv"] = 0x2261, + ["le"] = 0x2264, + ["ge"] = 0x2265, + ["sub"] = 0x2282, + ["sup"] = 0x2283, + ["nsub"] = 0x2284, + ["sube"] = 0x2286, + ["supe"] = 0x2287, + ["oplus"] = 0x2295, + ["otimes"] = 0x2297, + ["perp"] = 0x22A5, + ["sdot"] = 0x22C5, + ["lceil"] = 0x2308, + ["rceil"] = 0x2309, + ["lfloor"] = 0x230A, + ["rfloor"] = 0x230B, + ["lang"] = 0x27E8, + ["rang"] = 0x27E9, + ["loz"] = 0x25CA, + ["spades"] = 0x2660, + ["clubs"] = 0x2663, + ["hearts"] = 0x2665, + ["diams"] = 0x2666, +} +% \end{macrocode} +% +% Given a string \t`s` of decimal digits, the \luamdef{entities.dec_entity} +% returns the corresponding \textsc{utf}8-encoded Unicode codepoint. +% \begin{macrocode} +function entities.dec_entity(s) + return unicode.utf8.char(tonumber(s)) +end +% \end{macrocode} +% +% Given a string \t`s` of hexadecimal digits, the +% \luamdef{entities.hex_entity} returns the corresponding +% \textsc{utf}8-encoded Unicode codepoint. +% \begin{macrocode} +function entities.hex_entity(s) + return unicode.utf8.char(tonumber("0x"..s)) +end +% \end{macrocode} +% +% Given a character entity name \t`s` (like \t`ouml`), the +% \luamdef{entities.char_entity} returns the corresponding +% \textsc{utf}8-encoded Unicode codepoint. +% \begin{macrocode} +function entities.char_entity(s) + local n = character_entities[s] + return unicode.utf8.char(n) +end +% \end{macrocode} +% % \subsubsection{Plain \TeX{} Writer}\label{sec:texwriter} % This section documents the \luam{writer} object, which implements the % routines for producing the \TeX{} output. The object is an amalgamate of the @@ -2307,6 +2625,15 @@ function M.writer.new(options) end % \end{macrocode} % +% Define \luamdef{writer->inline_html} and \luamdef{writer->display_html} +% as functions that will transform an inline or block \textsc{html} element +% respectively to the output format, where \t`html` is the \textsc{html} +% input. +% \begin{macrocode} + function self.inline_html(html) return "" end + function self.display_html(html) return "" end +% \end{macrocode} +% % Define \luamdef{writer->definitionlist} as a function that will transform an % input definition list to the output format, where \t`items` is an array of % tables, each of the form \t`\{ term = t, definitions = defs \}`, where \t`t` @@ -2441,98 +2768,478 @@ function M.writer.new(options) return self end % \end{macrocode} -% \subsubsection{Generic \acro{peg} Patterns} -% These \acro{peg} patterns have been temporarily moved outside the -% \luam{reader.new} method, which is currently hitting the limit of 200 local -% variables. To resolve this issue, all the \acro{peg} patterns local to -% \luam{reader.new} will be moved to a static hash table at some point in the -% future. -% \begin{macrocode} -local percent = P("%") -local at = P("@") -local comma = P(",") -local asterisk = P("*") -local dash = P("-") -local plus = P("+") -local underscore = P("_") -local period = P(".") -local hash = P("#") -local ampersand = P("&") -local backtick = P("`") -local less = P("<") -local more = P(">") -local space = P(" ") -local squote = P("'") -local dquote = P('"') -local lparent = P("(") -local rparent = P(")") -local lbracket = P("[") -local rbracket = P("]") -local circumflex = P("^") -local slash = P("/") -local equal = P("=") -local colon = P(":") -local semicolon = P(";") -local exclamation = P("!") -local tilde = P("~") - -local digit = R("09") -local hexdigit = R("09","af","AF") -local letter = R("AZ","az") -local alphanumeric = R("AZ","az","09") -local keyword = letter * alphanumeric^0 -local internal_punctuation = S(":;,.#$%&-+?<>~/") - -local doubleasterisks = P("**") -local doubleunderscores = P("__") -local fourspaces = P(" ") - -local any = P(1) -local fail = any - 1 -local always = P("") - -local escapable = S("\\`*_{}[]()+_.!<>#-~:^@;") - -local anyescaped = P("\\") / "" * escapable - + any - -local tab = P("\t") -local spacechar = S("\t ") -local spacing = S(" \n\r\t") -local newline = P("\n") -local nonspacechar = any - spacing -local tightblocksep = P("\001") - -local specialchar = S("*_`&[]<!\\.@-^") - -local normalchar = any - - (specialchar + spacing + tightblocksep) -local optionalspace = spacechar^0 -local eof = - any -local nonindentspace = space^-3 * - spacechar -local indent = space^-3 * tab - + fourspaces / "" -local linechar = P(1 - newline) - -local blankline = optionalspace * newline / "\n" -local blanklines = blankline^0 -local skipblanklines = (optionalspace * newline)^0 -local indentedline = indent /"" * C(linechar^1 * newline^-1) -local optionallyindentedline = indent^-1 /"" * C(linechar^1 * newline^-1) -local sp = spacing^0 -local spnl = optionalspace * (newline * optionalspace)^-1 -local line = linechar^0 * newline - + linechar^1 * eof -local nonemptyline = line - blankline - -local chunk = line * (optionallyindentedline - blankline)^0 +% \subsubsection{Parsers} +% The \luamdef{parsers} hash table stores \acro{peg} patterns that are +% static and can be reused between different \luam{reader} objects. +% \begin{macrocode} +local parsers = {} +% \end{macrocode} +% \paragraph{Basic Parsers} +% \begin{macrocode} +parsers.percent = P("%") +parsers.at = P("@") +parsers.comma = P(",") +parsers.asterisk = P("*") +parsers.dash = P("-") +parsers.plus = P("+") +parsers.underscore = P("_") +parsers.period = P(".") +parsers.hash = P("#") +parsers.ampersand = P("&") +parsers.backtick = P("`") +parsers.less = P("<") +parsers.more = P(">") +parsers.space = P(" ") +parsers.squote = P("'") +parsers.dquote = P('"') +parsers.lparent = P("(") +parsers.rparent = P(")") +parsers.lbracket = P("[") +parsers.rbracket = P("]") +parsers.circumflex = P("^") +parsers.slash = P("/") +parsers.equal = P("=") +parsers.colon = P(":") +parsers.semicolon = P(";") +parsers.exclamation = P("!") +parsers.tilde = P("~") +parsers.tab = P("\t") +parsers.newline = P("\n") +parsers.tightblocksep = P("\001") + +parsers.digit = R("09") +parsers.hexdigit = R("09","af","AF") +parsers.letter = R("AZ","az") +parsers.alphanumeric = R("AZ","az","09") +parsers.keyword = parsers.letter + * parsers.alphanumeric^0 +parsers.internal_punctuation = S(":;,.#$%&-+?<>~/") + +parsers.doubleasterisks = P("**") +parsers.doubleunderscores = P("__") +parsers.fourspaces = P(" ") + +parsers.any = P(1) +parsers.fail = parsers.any - 1 + +parsers.escapable = S("\\`*_{}[]()+_.!<>#-~:^@;") +parsers.anyescaped = P("\\") / "" * parsers.escapable + + parsers.any + +parsers.spacechar = S("\t ") +parsers.spacing = S(" \n\r\t") +parsers.nonspacechar = parsers.any - parsers.spacing +parsers.optionalspace = parsers.spacechar^0 + +parsers.specialchar = S("*_`&[]<!\\.@-^") + +parsers.normalchar = parsers.any - (parsers.specialchar + + parsers.spacing + + parsers.tightblocksep) +parsers.eof = -parsers.any +parsers.nonindentspace = parsers.space^-3 * - parsers.spacechar +parsers.indent = parsers.space^-3 * parsers.tab + + parsers.fourspaces / "" +parsers.linechar = P(1 - parsers.newline) + +parsers.blankline = parsers.optionalspace + * parsers.newline / "\n" +parsers.blanklines = parsers.blankline^0 +parsers.skipblanklines = (parsers.optionalspace * parsers.newline)^0 +parsers.indentedline = parsers.indent /"" + * C(parsers.linechar^1 * parsers.newline^-1) +parsers.optionallyindentedline = parsers.indent^-1 /"" + * C(parsers.linechar^1 * parsers.newline^-1) +parsers.sp = parsers.spacing^0 +parsers.spnl = parsers.optionalspace + * (parsers.newline * parsers.optionalspace)^-1 +parsers.line = parsers.linechar^0 * parsers.newline + + parsers.linechar^1 * parsers.eof +parsers.nonemptyline = parsers.line - parsers.blankline + +parsers.chunk = parsers.line * (parsers.optionallyindentedline + - parsers.blankline)^0 -- block followed by 0 or more optionally -- indented blocks with first line indented. -local function indented_blocks(bl) +parsers.indented_blocks = function(bl) return Cs( bl - * (blankline^1 * indent * -blankline * bl)^0 - * (blankline^1 + eof) ) + * (parsers.blankline^1 * parsers.indent * -parsers.blankline * bl)^0 + * (parsers.blankline^1 + parsers.eof) ) +end +% \end{macrocode} +% \paragraph{Parsers Used for Markdown Lists} +% \begin{macrocode} +parsers.bulletchar = C(parsers.plus + parsers.asterisk + parsers.dash) + +parsers.bullet = ( parsers.bulletchar * #parsers.spacing + * (parsers.tab + parsers.space^-3) + + parsers.space * parsers.bulletchar * #parsers.spacing + * (parsers.tab + parsers.space^-2) + + parsers.space * parsers.space * parsers.bulletchar + * #parsers.spacing + * (parsers.tab + parsers.space^-1) + + parsers.space * parsers.space * parsers.space + * parsers.bulletchar * #parsers.spacing + ) +% \end{macrocode} +% \paragraph{Parsers Used for Markdown Code Spans} +% \begin{macrocode} +parsers.openticks = Cg(parsers.backtick^1, "ticks") + +local function captures_equal_length(s,i,a,b) + return #a == #b and i +end + +parsers.closeticks = parsers.space^-1 + * Cmt(C(parsers.backtick^1) + * Cb("ticks"), captures_equal_length) + +parsers.intickschar = (parsers.any - S(" \n\r`")) + + (parsers.newline * -parsers.blankline) + + (parsers.space - parsers.closeticks) + + (parsers.backtick^1 - parsers.closeticks) + +parsers.inticks = parsers.openticks * parsers.space^-1 + * C(parsers.intickschar^0) * parsers.closeticks +% \end{macrocode} +% \paragraph{Parsers Used for Fenced Code Blocks} +% \begin{macrocode} +local function captures_geq_length(s,i,a,b) + return #a >= #b and i +end + +parsers.infostring = (parsers.linechar - (parsers.backtick + + parsers.space^1 * (parsers.newline + parsers.eof)))^0 + +local fenceindent +parsers.fencehead = function(char) + return C(parsers.nonindentspace) / function(s) fenceindent = #s end + * Cg(char^3, "fencelength") + * parsers.optionalspace * C(parsers.infostring) + * parsers.optionalspace * (parsers.newline + parsers.eof) +end + +parsers.fencetail = function(char) + return parsers.nonindentspace + * Cmt(C(char^3) * Cb("fencelength"), captures_geq_length) + * parsers.optionalspace * (parsers.newline + parsers.eof) + + parsers.eof +end + +parsers.fencedline = function(char) + return C(parsers.line - parsers.fencetail(char)) + / function(s) + return s:gsub("^" .. string.rep(" ?", fenceindent), "") + end +end +% \end{macrocode} +% \paragraph{Parsers Used for Markdown Tags and Links} +% \begin{macrocode} +parsers.leader = parsers.space^-3 + +-- in balanced brackets, parentheses, quotes: +parsers.bracketed = P{ parsers.lbracket + * ((parsers.anyescaped - (parsers.lbracket + + parsers.rbracket + + parsers.blankline^2) + ) + V(1))^0 + * parsers.rbracket } + +parsers.inparens = P{ parsers.lparent + * ((parsers.anyescaped - (parsers.lparent + + parsers.rparent + + parsers.blankline^2) + ) + V(1))^0 + * parsers.rparent } + +parsers.squoted = P{ parsers.squote * parsers.alphanumeric + * ((parsers.anyescaped - (parsers.squote + + parsers.blankline^2) + ) + V(1))^0 + * parsers.squote } + +parsers.dquoted = P{ parsers.dquote * parsers.alphanumeric + * ((parsers.anyescaped - (parsers.dquote + + parsers.blankline^2) + ) + V(1))^0 + * parsers.dquote } + +-- bracketed 'tag' for markdown links, allowing nested brackets: +parsers.tag = parsers.lbracket + * Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped - (parsers.rbracket + + parsers.blankline^2)))^0) + * parsers.rbracket + +-- url for markdown links, allowing balanced parentheses: +parsers.url = parsers.less * Cs((parsers.anyescaped-parsers.more)^0) + * parsers.more + + Cs((parsers.inparens + (parsers.anyescaped + -parsers.spacing-parsers.rparent))^1) + +-- quoted text possibly with nested quotes: +parsers.title_s = parsers.squote * Cs(((parsers.anyescaped-parsers.squote) + + parsers.squoted)^0) + * parsers.squote + +parsers.title_d = parsers.dquote * Cs(((parsers.anyescaped-parsers.dquote) + + parsers.dquoted)^0) + * parsers.dquote + +parsers.title_p = parsers.lparent + * Cs((parsers.inparens + (parsers.anyescaped-parsers.rparent))^0) + * parsers.rparent + +parsers.title = parsers.title_d + parsers.title_s + parsers.title_p + +parsers.optionaltitle + = parsers.spnl * parsers.title * parsers.spacechar^0 + + Cc("") +% \end{macrocode} +% \paragraph{Parsers Used for Citations} +% \begin{macrocode} +parsers.citation_name = Cs(parsers.dash^-1) * parsers.at + * Cs(parsers.alphanumeric + * (parsers.alphanumeric + parsers.internal_punctuation + - parsers.comma - parsers.semicolon)^0) + +parsers.citation_body_prenote + = Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped + - (parsers.rbracket + parsers.blankline^2)) + - (parsers.spnl * parsers.dash^-1 * parsers.at))^0) + +parsers.citation_body_postnote + = Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped + - (parsers.rbracket + parsers.semicolon + + parsers.blankline^2)) + - (parsers.spnl * parsers.rbracket))^0) + +parsers.citation_body_chunk + = parsers.citation_body_prenote + * parsers.spnl * parsers.citation_name + * (parsers.comma * parsers.spnl)^-1 + * parsers.citation_body_postnote + +parsers.citation_body + = parsers.citation_body_chunk + * (parsers.semicolon * parsers.spnl + * parsers.citation_body_chunk)^0 + +parsers.citation_headless_body_postnote + = Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped + - (parsers.rbracket + parsers.at + + parsers.semicolon + parsers.blankline^2)) + - (parsers.spnl * parsers.rbracket))^0) + +parsers.citation_headless_body + = parsers.citation_headless_body_postnote + * (parsers.sp * parsers.semicolon * parsers.spnl + * parsers.citation_body_chunk)^0 +% \end{macrocode} +% \paragraph{Parsers Used for Footnotes} +% \begin{macrocode} +local function strip_first_char(s) + return s:sub(2) +end + +parsers.RawNoteRef = #(parsers.lbracket * parsers.circumflex) + * parsers.tag / strip_first_char +% \end{macrocode} +% \paragraph{Parsers Used for \textsc{html}} +% \begin{macrocode} +-- case-insensitive match (we assume s is lowercase). must be single byte encoding +parsers.keyword_exact = function(s) + local parser = P(0) + for i=1,#s do + local c = s:sub(i,i) + local m = c .. upper(c) + parser = parser * S(m) + end + return parser +end + +parsers.block_keyword = + parsers.keyword_exact("address") + parsers.keyword_exact("blockquote") + + parsers.keyword_exact("center") + parsers.keyword_exact("del") + + parsers.keyword_exact("dir") + parsers.keyword_exact("div") + + parsers.keyword_exact("p") + parsers.keyword_exact("pre") + + parsers.keyword_exact("li") + parsers.keyword_exact("ol") + + parsers.keyword_exact("ul") + parsers.keyword_exact("dl") + + parsers.keyword_exact("dd") + parsers.keyword_exact("form") + + parsers.keyword_exact("fieldset") + parsers.keyword_exact("isindex") + + parsers.keyword_exact("ins") + parsers.keyword_exact("menu") + + parsers.keyword_exact("noframes") + parsers.keyword_exact("frameset") + + parsers.keyword_exact("h1") + parsers.keyword_exact("h2") + + parsers.keyword_exact("h3") + parsers.keyword_exact("h4") + + parsers.keyword_exact("h5") + parsers.keyword_exact("h6") + + parsers.keyword_exact("hr") + parsers.keyword_exact("script") + + parsers.keyword_exact("noscript") + parsers.keyword_exact("table") + + parsers.keyword_exact("tbody") + parsers.keyword_exact("tfoot") + + parsers.keyword_exact("thead") + parsers.keyword_exact("th") + + parsers.keyword_exact("td") + parsers.keyword_exact("tr") + +-- There is no reason to support bad html, so we expect quoted attributes +parsers.htmlattributevalue + = parsers.squote * (parsers.any - (parsers.blankline + + parsers.squote))^0 + * parsers.squote + + parsers.dquote * (parsers.any - (parsers.blankline + + parsers.dquote))^0 + * parsers.dquote + +parsers.htmlattribute = parsers.spacing^1 + * (parsers.alphanumeric + S("_-"))^1 + * parsers.sp * parsers.equal * parsers.sp + * parsers.htmlattributevalue + +parsers.htmlcomment = P("<!--") * (parsers.any - P("-->"))^0 * P("-->") + +parsers.htmlinstruction = P("<?") * (parsers.any - P("?>" ))^0 * P("?>" ) + +parsers.openelt_any = parsers.less * parsers.keyword * parsers.htmlattribute^0 + * parsers.sp * parsers.more + +parsers.openelt_exact = function(s) + return parsers.less * parsers.sp * parsers.keyword_exact(s) + * parsers.htmlattribute^0 * parsers.sp * parsers.more +end + +parsers.openelt_block = parsers.sp * parsers.block_keyword + * parsers.htmlattribute^0 * parsers.sp * parsers.more + +parsers.closeelt_any = parsers.less * parsers.sp * parsers.slash + * parsers.keyword * parsers.sp * parsers.more + +parsers.closeelt_exact = function(s) + return parsers.less * parsers.sp * parsers.slash * parsers.keyword_exact(s) + * parsers.sp * parsers.more +end + +parsers.emptyelt_any = parsers.less * parsers.sp * parsers.keyword + * parsers.htmlattribute^0 * parsers.sp * parsers.slash + * parsers.more + +parsers.emptyelt_block = parsers.less * parsers.sp * parsers.block_keyword + * parsers.htmlattribute^0 * parsers.sp * parsers.slash + * parsers.more + +parsers.displaytext = (parsers.any - parsers.less)^1 + +-- return content between two matched HTML tags +parsers.in_matched = function(s) + return { parsers.openelt_exact(s) + * (V(1) + parsers.displaytext + + (parsers.less - parsers.closeelt_exact(s)))^0 + * parsers.closeelt_exact(s) } +end + +local function parse_matched_tags(s,pos) + local t = string.lower(lpeg.match(C(parsers.keyword),s,pos)) + return lpeg.match(parsers.in_matched(t),s,pos-1) +end + +parsers.in_matched_block_tags = parsers.less + * Cmt(#parsers.openelt_block, parse_matched_tags) + +parsers.displayhtml = parsers.htmlcomment + + parsers.emptyelt_block + + parsers.openelt_exact("hr") + + parsers.in_matched_block_tags + + parsers.htmlinstruction + +parsers.inlinehtml = parsers.emptyelt_any + + parsers.htmlcomment + + parsers.htmlinstruction + + parsers.openelt_any + + parsers.closeelt_any +% \end{macrocode} +% \paragraph{Parsers Used for \textsc{html} entities} +% \begin{macrocode} +parsers.hexentity = parsers.ampersand * parsers.hash * S("Xx") + * C(parsers.hexdigit^1) * parsers.semicolon +parsers.decentity = parsers.ampersand * parsers.hash + * C(parsers.digit^1) * parsers.semicolon +parsers.tagentity = parsers.ampersand * C(parsers.alphanumeric^1) + * parsers.semicolon +% \end{macrocode} +% \paragraph{Helpers for Links and References} +% \begin{macrocode} +-- parse a reference definition: [foo]: /bar "title" +parsers.define_reference_parser = parsers.leader * parsers.tag * parsers.colon + * parsers.spacechar^0 * parsers.url + * parsers.optionaltitle * parsers.blankline^1 +% \end{macrocode} +% \paragraph{Inline Elements} +% \begin{macrocode} +parsers.Inline = V("Inline") + +-- parse many p between starter and ender +parsers.between = function(p, starter, ender) + local ender2 = B(parsers.nonspacechar) * ender + return (starter * #parsers.nonspacechar * Ct(p * (p - ender2)^0) * ender2) +end + +parsers.urlchar = parsers.anyescaped - parsers.newline - parsers.more +% \end{macrocode} +% \paragraph{Block Elements} +% \begin{macrocode} +parsers.Block = V("Block") + +parsers.TildeFencedCode + = parsers.fencehead(parsers.tilde) + * Cs(parsers.fencedline(parsers.tilde)^0) + * parsers.fencetail(parsers.tilde) + +parsers.BacktickFencedCode + = parsers.fencehead(parsers.backtick) + * Cs(parsers.fencedline(parsers.backtick)^0) + * parsers.fencetail(parsers.backtick) + +parsers.lineof = function(c) + return (parsers.leader * (P(c) * parsers.optionalspace)^3 + * (parsers.newline * parsers.blankline^1 + + parsers.newline^-1 * parsers.eof)) +end +% \end{macrocode} +% \paragraph{Lists} +% \begin{macrocode} +parsers.defstartchar = S("~:") +parsers.defstart = ( parsers.defstartchar * #parsers.spacing + * (parsers.tab + parsers.space^-3) + + parsers.space * parsers.defstartchar * #parsers.spacing + * (parsers.tab + parsers.space^-2) + + parsers.space * parsers.space * parsers.defstartchar + * #parsers.spacing + * (parsers.tab + parsers.space^-1) + + parsers.space * parsers.space * parsers.space + * parsers.defstartchar * #parsers.spacing + ) + +parsers.dlchunk = Cs(parsers.line * (parsers.indentedline - parsers.blankline)^0) +% \end{macrocode} +% \paragraph{Headings} +% \begin{macrocode} +-- parse Atx heading start and return level +parsers.HeadingStart = #parsers.hash * C(parsers.hash^-6) + * -parsers.hash / length + +-- parse setext header ending and return level +parsers.HeadingLevel = parsers.equal^1 * Cc(1) + parsers.dash^1 * Cc(2) + +local function strip_atx_end(s) + return s:gsub("[#%s]*\n$","") end % \end{macrocode} % @@ -2570,7 +3277,7 @@ function M.reader.new(writer, options) return defaultOptions[key] end }) % \end{macrocode} % -% \paragraph{Top Level Helper Functions} +% \paragraph{Top-Level Helper Functions} % Define \luamdef{normalize_tag} as a function that normalizes a markdown % reference tag by lowercasing it, and by collapsing any adjacent whitespace % characters. @@ -2598,16 +3305,15 @@ function M.reader.new(writer, options) end end % \end{macrocode} -% \paragraph{Top Level Parsing Functions} +% +% The \luamdef{larsers} (as in ``local \luam{parsers}'') hash table stores +% \acro{peg} patterns that depend on the received \t`options`, which impedes +% their reuse between different \luam{reader} objects. +% \begin{macrocode} + local larsers = {} +% \end{macrocode} +% \paragraph{Top-Level Parser Functions} % \begin{macrocode} - local syntax - local blocks_toplevel - local blocks - local inlines - local inlines_no_link - local inlines_no_inline_note - local inlines_nbsp - local function create_parser(name, grammar) return function(str) local res = lpeg.match(grammar(), str) @@ -2619,197 +3325,106 @@ function M.reader.new(writer, options) end end - local parse_blocks = create_parser("parse_blocks", - function() return blocks end) - local parse_blocks_toplevel = create_parser("parse_blocks_toplevel", - function() return blocks_toplevel end) - local parse_inlines = create_parser("parse_inlines", - function() return inlines end) - local parse_inlines_no_link = create_parser("parse_inlines_no_link", - function() return inlines_no_link end) - local parse_inlines_no_inline_note = create_parser( - "parse_inlines_no_inline_note", - function() return inlines_no_inline_note end) - local parse_inlines_nbsp = create_parser("parse_inlines_nbsp", - function() return inlines_nbsp end) -% \end{macrocode} -% \paragraph{List \acro{peg} Patterns} -% \begin{macrocode} - local bulletchar = C(plus + asterisk + dash) - - local bullet = ( Cg(bulletchar, "bulletchar") * #spacing * (tab + space^-3) - + space * Cg(bulletchar, "bulletchar") * #spacing * (tab + space^-2) - + space * space * Cg(bulletchar, "bulletchar") * #spacing * (tab + space^-1) - + space * space * space * Cg(bulletchar, "bulletchar") * #spacing - ) - + local parse_blocks + = create_parser("parse_blocks", + function() + return larsers.blocks + end) + + local parse_blocks_toplevel + = create_parser("parse_blocks_toplevel", + function() + return larsers.blocks_toplevel + end) + + local parse_inlines + = create_parser("parse_inlines", + function() + return larsers.inlines + end) + + local parse_inlines_no_link + = create_parser("parse_inlines_no_link", + function() + return larsers.inlines_no_link + end) + + local parse_inlines_no_inline_note + = create_parser("parse_inlines_no_inline_note", + function() + return larsers.inlines_no_inline_note + end) + + local parse_inlines_nbsp + = create_parser("parse_inlines_nbsp", + function() + return larsers.inlines_nbsp + end) +% \end{macrocode} +% \paragraph{Parsers Used for Markdown Lists (local)} +% \begin{macrocode} if options.hashEnumerators then - dig = digit + hash + larsers.dig = parsers.digit + parsers.hash else - dig = digit + larsers.dig = parsers.digit end - local enumerator = C(dig^3 * period) * #spacing - + C(dig^2 * period) * #spacing * (tab + space^1) - + C(dig * period) * #spacing * (tab + space^-2) - + space * C(dig^2 * period) * #spacing - + space * C(dig * period) * #spacing * (tab + space^-1) - + space * space * C(dig^1 * period) * #spacing -% \end{macrocode} -% \paragraph{Code Span \acro{peg} Patterns} + larsers.enumerator = C(larsers.dig^3 * parsers.period) * #parsers.spacing + + C(larsers.dig^2 * parsers.period) * #parsers.spacing + * (parsers.tab + parsers.space^1) + + C(larsers.dig * parsers.period) * #parsers.spacing + * (parsers.tab + parsers.space^-2) + + parsers.space * C(larsers.dig^2 * parsers.period) + * #parsers.spacing + + parsers.space * C(larsers.dig * parsers.period) + * #parsers.spacing + * (parsers.tab + parsers.space^-1) + + parsers.space * parsers.space * C(larsers.dig^1 + * parsers.period) * #parsers.spacing +% \end{macrocode} +% \paragraph{Parsers Used for Blockquotes (local)} % \begin{macrocode} - local openticks = Cg(backtick^1, "ticks") - - local function captures_equal_length(s,i,a,b) - return #a == #b and i + -- strip off leading > and indents, and run through blocks + larsers.blockquote_body = ((parsers.leader * parsers.more * parsers.space^-1)/"" + * parsers.linechar^0 * parsers.newline)^1 + * (-(parsers.leader * parsers.more + + parsers.blankline) * parsers.linechar^1 + * parsers.newline)^0 + + if not options.breakableBlockquotes then + larsers.blockquote_body = larsers.blockquote_body + * (parsers.blankline^0 / "") end - - local closeticks = space^-1 * - Cmt(C(backtick^1) * Cb("ticks"), captures_equal_length) - - local intickschar = (any - S(" \n\r`")) - + (newline * -blankline) - + (space - closeticks) - + (backtick^1 - closeticks) - - local inticks = openticks * space^-1 * C(intickschar^0) * closeticks -% \paragraph{Fenced Code \acro{peg} Patterns} +% \end{macrocode} +% \paragraph{Parsers Used for Citations (local)} % \begin{macrocode} - local function captures_geq_length(s,i,a,b) - return #a >= #b and i - end - - local infostring = (linechar - (backtick + space^1 * (newline + eof)))^0 - - local fenceindent - local function fencehead(char) - return C(nonindentspace) / function(s) fenceindent = #s end - * Cg(char^3, "fencelength") - * optionalspace * C(infostring) * optionalspace - * (newline + eof) - end - - local function fencetail(char) - return nonindentspace - * Cmt(C(char^3) * Cb("fencelength"), - captures_geq_length) - * optionalspace * (newline + eof) - + eof - end + larsers.citations = function(text_cites, raw_cites) + local function normalize(str) + if str == "" then + str = nil + else + str = (options.citationNbsps and parse_inlines_nbsp or + parse_inlines)(str) + end + return str + end - local function fencedline(char) - return C(line - fencetail(char)) - / function(s) - return s:gsub("^" .. string.rep(" ?", - fenceindent), "") - end + local cites = {} + for i = 1,#raw_cites,4 do + cites[#cites+1] = { + prenote = normalize(raw_cites[i]), + suppress_author = raw_cites[i+1] == "-", + name = writer.string(raw_cites[i+2]), + postnote = normalize(raw_cites[i+3]), + } + end + return writer.citations(text_cites, cites) end % \end{macrocode} -% \paragraph{Tag \acro{peg} Patterns} -% \begin{macrocode} - local leader = space^-3 - - -- in balanced brackets, parentheses, quotes: - local bracketed = P{ lbracket - * ((anyescaped - (lbracket + rbracket - + blankline^2)) + V(1))^0 - * rbracket } - - local inparens = P{ lparent - * ((anyescaped - (lparent + rparent - + blankline^2)) + V(1))^0 - * rparent } - - local squoted = P{ squote * alphanumeric - * ((anyescaped - (squote + blankline^2)) - + V(1))^0 - * squote } - - local dquoted = P{ dquote * alphanumeric - * ((anyescaped - (dquote + blankline^2)) - + V(1))^0 - * dquote } - - -- bracketed 'tag' for markdown links, allowing nested brackets: - local tag = lbracket - * Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - (rbracket + blankline^2)))^0) - * rbracket - - -- url for markdown links, allowing balanced parentheses: - local url = less * Cs((anyescaped-more)^0) * more - + Cs((inparens + (anyescaped-spacing-rparent))^1) - - -- quoted text possibly with nested quotes: - local title_s = squote * Cs(((anyescaped-squote) + squoted)^0) * - squote - - local title_d = dquote * Cs(((anyescaped-dquote) + dquoted)^0) * - dquote - - local title_p = lparent - * Cs((inparens + (anyescaped-rparent))^0) - * rparent - - local title = title_d + title_s + title_p - - local optionaltitle = spnl * title * spacechar^0 - + Cc("") -% \end{macrocode} -% \paragraph{Citation \acro{peg} Patterns} -% \begin{macrocode} - local citation_name = Cs(dash^-1) * at - * Cs(alphanumeric - * (alphanumeric + internal_punctuation - - comma - semicolon)^0) - - local citation_body_prenote - = Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - - (rbracket + blankline^2)) - - (spnl * dash^-1 * at))^0) - - local citation_body_postnote - = Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - - (rbracket + semicolon + blankline^2)) - - (spnl * rbracket))^0) - - local citation_body_chunk - = citation_body_prenote - * spnl * citation_name - * (comma * spnl)^-1 - * citation_body_postnote - - local citation_body = citation_body_chunk - * (semicolon * spnl * citation_body_chunk)^0 - - local citation_headless_body_postnote - = Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - - (rbracket + at + semicolon + blankline^2)) - - (spnl * rbracket))^0) - - local citation_headless_body - = citation_headless_body_postnote - * (sp * semicolon * spnl * citation_body_chunk)^0 -% \end{macrocode} -% \paragraph{Footnote \acro{peg} Patterns} +% \paragraph{Parsers Used for Footnotes (local)} % \begin{macrocode} local rawnotes = {} - local function strip_first_char(s) - return s:sub(2) - end - -- like indirect_link local function lookup_note(ref) return function() @@ -2827,19 +3442,18 @@ function M.reader.new(writer, options) return "" end - local RawNoteRef = #(lbracket * circumflex) * tag / strip_first_char - - local NoteRef = RawNoteRef / lookup_note + larsers.NoteRef = parsers.RawNoteRef / lookup_note - local NoteBlock = leader * RawNoteRef * colon * spnl - * indented_blocks(chunk) / register_note + larsers.NoteBlock = parsers.leader * parsers.RawNoteRef * parsers.colon + * parsers.spnl * parsers.indented_blocks(parsers.chunk) + / register_note - local InlineNote = circumflex -- no notes inside notes - * (tag / parse_inlines_no_inline_note) - / writer.note + larsers.InlineNote = parsers.circumflex + * (parsers.tag / parse_inlines_no_inline_note) -- no notes inside notes + / writer.note % \end{macrocode} -% \paragraph{Link and Image \acro{peg} Patterns} +% \paragraph{Helpers for Links and References (local)} % \begin{macrocode} -- List of references defined in the document local references @@ -2850,10 +3464,6 @@ function M.reader.new(writer, options) return "" end - -- parse a reference definition: [foo]: /bar "title" - local define_reference_parser = - leader * tag * colon * spacechar^0 * url * optionaltitle * blankline^1 - -- lookup link reference and return either -- the link or nil and fallback text. local function lookup_reference(label,sps,tag) @@ -2904,273 +3514,247 @@ function M.reader.new(writer, options) end end % \end{macrocode} -% \paragraph{Spacing \acro{peg} Patterns} +% \paragraph{Inline Elements (local)} % \begin{macrocode} - local bqstart = more - local headerstart = hash - + (line * (equal^1 + dash^1) * optionalspace * newline) - local fencestart = fencehead(backtick) + fencehead(tilde) + larsers.Str = parsers.normalchar^1 / writer.string + + larsers.Symbol = (parsers.specialchar - parsers.tightblocksep) + / writer.string + + larsers.Ellipsis = P("...") / writer.ellipsis + + larsers.Smart = larsers.Ellipsis + + larsers.Code = parsers.inticks / writer.code if options.blankBeforeBlockquote then - bqstart = fail + larsers.bqstart = parsers.fail + else + larsers.bqstart = parsers.more end if options.blankBeforeHeading then - headerstart = fail + larsers.headerstart = parsers.fail + else + larsers.headerstart = parsers.hash + + (parsers.line * (parsers.equal^1 + parsers.dash^1) + * parsers.optionalspace * parsers.newline) end if not options.fencedCode or options.blankBeforeCodeFence then - fencestart = fail - end -% \end{macrocode} -% \paragraph{String \acro{peg} Rules} -% \begin{macrocode} - local Inline = V("Inline") - - local Str = normalchar^1 / writer.string - - local Symbol = (specialchar - tightblocksep) / writer.string -% \end{macrocode} -% \paragraph{Ellipsis \acro{peg} Rules} -% \begin{macrocode} - local Ellipsis = P("...") / writer.ellipsis - - local Smart = Ellipsis -% \end{macrocode} -% \paragraph{Inline Code Block \acro{peg} Rules} -% \begin{macrocode} - local Code = inticks / writer.code -% \end{macrocode} -% \paragraph{Spacing \acro{peg} Rules} -% \begin{macrocode} - local Endline = newline * -( -- newline, but not before... - blankline -- paragraph break - + tightblocksep -- nested list - + eof -- end of document - + bqstart - + headerstart - + fencestart - ) * spacechar^0 / writer.space - - local Space = spacechar^2 * Endline / writer.linebreak - + spacechar^1 * Endline^-1 * eof / "" - + spacechar^1 * Endline^-1 * optionalspace / writer.space - - local NonbreakingEndline - = newline * -( -- newline, but not before... - blankline -- paragraph break - + tightblocksep -- nested list - + eof -- end of document - + bqstart - + headerstart - + fencestart - ) * spacechar^0 / writer.nbsp - - local NonbreakingSpace - = spacechar^2 * Endline / writer.linebreak - + spacechar^1 * Endline^-1 * eof / "" - + spacechar^1 * Endline^-1 * optionalspace / writer.nbsp - - -- parse many p between starter and ender - local function between(p, starter, ender) - local ender2 = B(nonspacechar) * ender - return (starter * #nonspacechar * Ct(p * (p - ender2)^0) * ender2) + larsers.fencestart = parsers.fail + else + larsers.fencestart = parsers.fencehead(parsers.backtick) + + parsers.fencehead(parsers.tilde) end -% \end{macrocode} -% \paragraph{Emphasis \acro{peg} Rules} -% \begin{macrocode} - local Strong = ( between(Inline, doubleasterisks, doubleasterisks) - + between(Inline, doubleunderscores, doubleunderscores) - ) / writer.strong - - local Emph = ( between(Inline, asterisk, asterisk) - + between(Inline, underscore, underscore) - ) / writer.emphasis -% \end{macrocode} -% \paragraph{Link \acro{peg} Rules} -% \begin{macrocode} - local urlchar = anyescaped - newline - more - - local AutoLinkUrl = less - * C(alphanumeric^1 * P("://") * urlchar^1) - * more - / function(url) - return writer.link(writer.string(url), url) - end - - local AutoLinkEmail = less - * C((alphanumeric + S("-._+"))^1 * P("@") * urlchar^1) - * more - / function(email) - return writer.link(writer.string(email), - "mailto:"..email) - end - - local DirectLink = (tag / parse_inlines_no_link) -- no links inside links - * spnl - * lparent - * (url + Cc("")) -- link can be empty [foo]() - * optionaltitle - * rparent - / writer.link - local IndirectLink = tag * (C(spnl) * tag)^-1 / indirect_link + larsers.Endline = parsers.newline * -( -- newline, but not before... + parsers.blankline -- paragraph break + + parsers.tightblocksep -- nested list + + parsers.eof -- end of document + + larsers.bqstart + + larsers.headerstart + + larsers.fencestart + ) * parsers.spacechar^0 / writer.space + + larsers.Space = parsers.spacechar^2 * larsers.Endline / writer.linebreak + + parsers.spacechar^1 * larsers.Endline^-1 * parsers.eof / "" + + parsers.spacechar^1 * larsers.Endline^-1 + * parsers.optionalspace / writer.space + + larsers.NonbreakingEndline + = parsers.newline * -( -- newline, but not before... + parsers.blankline -- paragraph break + + parsers.tightblocksep -- nested list + + parsers.eof -- end of document + + larsers.bqstart + + larsers.headerstart + + larsers.fencestart + ) * parsers.spacechar^0 / writer.nbsp + + larsers.NonbreakingSpace + = parsers.spacechar^2 * larsers.Endline / writer.linebreak + + parsers.spacechar^1 * larsers.Endline^-1 * parsers.eof / "" + + parsers.spacechar^1 * larsers.Endline^-1 + * parsers.optionalspace / writer.nbsp + + larsers.Strong = ( parsers.between(parsers.Inline, parsers.doubleasterisks, + parsers.doubleasterisks) + + parsers.between(parsers.Inline, parsers.doubleunderscores, + parsers.doubleunderscores) + ) / writer.strong + + larsers.Emph = ( parsers.between(parsers.Inline, parsers.asterisk, + parsers.asterisk) + + parsers.between(parsers.Inline, parsers.underscore, + parsers.underscore) + ) / writer.emphasis + + larsers.AutoLinkUrl = parsers.less + * C(parsers.alphanumeric^1 * P("://") * parsers.urlchar^1) + * parsers.more + / function(url) + return writer.link(writer.string(url), url) + end + + larsers.AutoLinkEmail = parsers.less + * C((parsers.alphanumeric + S("-._+"))^1 + * P("@") * parsers.urlchar^1) + * parsers.more + / function(email) + return writer.link(writer.string(email), + "mailto:"..email) + end + + larsers.DirectLink = (parsers.tag / parse_inlines_no_link) -- no links inside links + * parsers.spnl + * parsers.lparent + * (parsers.url + Cc("")) -- link can be empty [foo]() + * parsers.optionaltitle + * parsers.rparent + / writer.link + + larsers.IndirectLink = parsers.tag * (C(parsers.spnl) * parsers.tag)^-1 + / indirect_link -- parse a link or image (direct or indirect) - local Link = DirectLink + IndirectLink -% \end{macrocode} -% \paragraph{Image \acro{peg} Rules} -% \begin{macrocode} - local DirectImage = exclamation - * (tag / parse_inlines) - * spnl - * lparent - * (url + Cc("")) -- link can be empty [foo]() - * optionaltitle - * rparent - / writer.image + larsers.Link = larsers.DirectLink + larsers.IndirectLink + + larsers.DirectImage = parsers.exclamation + * (parsers.tag / parse_inlines) + * parsers.spnl + * parsers.lparent + * (parsers.url + Cc("")) -- link can be empty [foo]() + * parsers.optionaltitle + * parsers.rparent + / writer.image + + larsers.IndirectImage = parsers.exclamation * parsers.tag + * (C(parsers.spnl) * parsers.tag)^-1 / indirect_image + + larsers.Image = larsers.DirectImage + larsers.IndirectImage + + larsers.TextCitations = Ct(Cc("") + * parsers.citation_name + * ((parsers.spnl + * parsers.lbracket + * parsers.citation_headless_body + * parsers.rbracket) + Cc(""))) + / function(raw_cites) + return larsers.citations(true, raw_cites) + end + + larsers.ParenthesizedCitations + = Ct(parsers.lbracket + * parsers.citation_body + * parsers.rbracket) + / function(raw_cites) + return larsers.citations(false, raw_cites) + end - local IndirectImage = exclamation * tag * (C(spnl) * tag)^-1 / - indirect_image + larsers.Citations = larsers.TextCitations + larsers.ParenthesizedCitations - local Image = DirectImage + IndirectImage -% \end{macrocode} -% \paragraph{Miscellaneous Inline \acro{peg} Rules} -% \begin{macrocode} -- avoid parsing long strings of * or _ as emph/strong - local UlOrStarLine = asterisk^4 + underscore^4 / writer.string + larsers.UlOrStarLine = parsers.asterisk^4 + parsers.underscore^4 + / writer.string - local EscapedChar = S("\\") * C(escapable) / writer.string -% \end{macrocode} -% \paragraph{Citations \acro{peg} Rules} -% \begin{macrocode} - local function citations(text_cites, raw_cites) - local function normalize(str) - if str == "" then - str = nil - else - str = (options.citationNbsps and parse_inlines_nbsp or - parse_inlines)(str) - end - return str - end - - local cites = {} - for i = 1,#raw_cites,4 do - cites[#cites+1] = { - prenote = normalize(raw_cites[i]), - suppress_author = raw_cites[i+1] == "-", - name = writer.string(raw_cites[i+2]), - postnote = normalize(raw_cites[i+3]), - } - end - return writer.citations(text_cites, cites) - end - - local TextCitations = Ct(Cc("") - * citation_name - * ((spnl - * lbracket - * citation_headless_body - * rbracket) + Cc(""))) / - function(raw_cites) - return citations(true, raw_cites) - end + larsers.EscapedChar = S("\\") * C(parsers.escapable) / writer.string - local ParenthesizedCitations - = Ct(lbracket - * citation_body - * rbracket) / - function(raw_cites) - return citations(false, raw_cites) - end - - local Citations = TextCitations + ParenthesizedCitations + larsers.InlineHtml = C(parsers.inlinehtml) / writer.inline_html + + larsers.HtmlEntity = parsers.hexentity / entities.hex_entity / writer.string + + parsers.decentity / entities.dec_entity / writer.string + + parsers.tagentity / entities.char_entity / writer.string % \end{macrocode} -% \paragraph{Code Block \acro{peg} Rules} +% \paragraph{Block Elements (local)} % \begin{macrocode} - local Block = V("Block") + larsers.DisplayHtml = C(parsers.displayhtml) + / expandtabs / writer.display_html - local Verbatim = Cs( (blanklines - * ((indentedline - blankline))^1)^1 + larsers.Verbatim = Cs( (parsers.blanklines + * ((parsers.indentedline - parsers.blankline))^1)^1 ) / expandtabs / writer.verbatim - - local TildeFencedCode - = fencehead(tilde) - * Cs(fencedline(tilde)^0) - * fencetail(tilde) - local BacktickFencedCode - = fencehead(backtick) - * Cs(fencedline(backtick)^0) - * fencetail(backtick) - - local FencedCode = (TildeFencedCode + BacktickFencedCode) + larsers.FencedCode = (parsers.TildeFencedCode + + parsers.BacktickFencedCode) / function(infostring, code) - return writer.fencedCode( - writer.string(infostring), - expandtabs(code)) + return writer.fencedCode(writer.string(infostring), + expandtabs(code)) end + + larsers.Blockquote = Cs(larsers.blockquote_body^1) + / parse_blocks_toplevel / writer.blockquote + + larsers.HorizontalRule = ( parsers.lineof(parsers.asterisk) + + parsers.lineof(parsers.dash) + + parsers.lineof(parsers.underscore) + ) / writer.hrule + + larsers.Reference = parsers.define_reference_parser / register_link + + larsers.Paragraph = parsers.nonindentspace * Ct(parsers.Inline^1) + * parsers.newline + * ( parsers.blankline^1 + + #parsers.hash + + #(parsers.leader * parsers.more * parsers.space^-1) + ) + / writer.paragraph + + larsers.ToplevelParagraph + = parsers.nonindentspace * Ct(parsers.Inline^1) + * ( parsers.newline + * ( parsers.blankline^1 + + #parsers.hash + + #(parsers.leader * parsers.more * parsers.space^-1) + + parsers.eof + ) + + parsers.eof ) + / writer.paragraph + + larsers.Plain = parsers.nonindentspace * Ct(parsers.Inline^1) + / writer.plain % \end{macrocode} -% \paragraph{Blockquote \acro{peg} Patterns} -% \begin{macrocode} - -- strip off leading > and indents, and run through blocks - local Blockquote = Cs(( - ((leader * more * space^-1)/"" * linechar^0 * newline)^1 - * (-blankline * linechar^1 * newline)^0 - * (blankline^0 / "") - )^1) / parse_blocks_toplevel / writer.blockquote - - local function lineof(c) - return (leader * (P(c) * optionalspace)^3 * (newline * blankline^1 - + newline^-1 * eof)) - end -% \end{macrocode} -% \paragraph{Horizontal Rule \acro{peg} Rules} -% \begin{macrocode} - local HorizontalRule = ( lineof(asterisk) - + lineof(dash) - + lineof(underscore) - ) / writer.hrule -% \end{macrocode} -% \paragraph{List \acro{peg} Rules} +% \paragraph{Lists (local)} % \begin{macrocode} - local starter = bullet + enumerator + larsers.starter = parsers.bullet + larsers.enumerator -- we use \001 as a separator between a tight list item and a -- nested list under it. - local NestedList = Cs((optionallyindentedline - starter)^1) - / function(a) return "\001"..a end + larsers.NestedList = Cs((parsers.optionallyindentedline + - larsers.starter)^1) + / function(a) return "\001"..a end - local ListBlockLine = optionallyindentedline - - blankline - (indent^-1 * starter) + larsers.ListBlockLine = parsers.optionallyindentedline + - parsers.blankline - (parsers.indent^-1 + * larsers.starter) - local ListBlock = line * ListBlockLine^0 + larsers.ListBlock = parsers.line * larsers.ListBlockLine^0 - local ListContinuationBlock = blanklines * (indent / "") * ListBlock + larsers.ListContinuationBlock = parsers.blanklines * (parsers.indent / "") + * larsers.ListBlock - local function TightListItem(starter) - return -HorizontalRule - * (Cs(starter / "" * ListBlock * NestedList^-1) / - parse_blocks) - * -(blanklines * indent) + larsers.TightListItem = function(starter) + return -larsers.HorizontalRule + * (Cs(starter / "" * larsers.ListBlock * larsers.NestedList^-1) + / parse_blocks) + * -(parsers.blanklines * parsers.indent) end - local function LooseListItem(starter) - return -HorizontalRule - * Cs( starter / "" * ListBlock * Cc("\n") - * (NestedList + ListContinuationBlock^0) - * (blanklines / "\n\n") + larsers.LooseListItem = function(starter) + return -larsers.HorizontalRule + * Cs( starter / "" * larsers.ListBlock * Cc("\n") + * (larsers.NestedList + larsers.ListContinuationBlock^0) + * (parsers.blanklines / "\n\n") ) / parse_blocks end - local BulletList = ( Ct(TightListItem(bullet)^1) - * Cc(true) * skipblanklines * -bullet - + Ct(LooseListItem(bullet)^1) - * Cc(false) * skipblanklines ) / - writer.bulletlist + larsers.BulletList = ( Ct(larsers.TightListItem(parsers.bullet)^1) * Cc(true) + * parsers.skipblanklines * -parsers.bullet + + Ct(larsers.LooseListItem(parsers.bullet)^1) * Cc(false) + * parsers.skipblanklines ) + / writer.bulletlist - local function orderedlist(items,tight,startNumber) + local function ordered_list(items,tight,startNumber) if options.startNumber then startNumber = tonumber(startNumber) or 1 -- fallback for '#' else @@ -3179,119 +3763,74 @@ function M.reader.new(writer, options) return writer.orderedlist(items,tight,startNumber) end - local OrderedList = Cg(enumerator, "listtype") * - ( Ct(TightListItem(Cb("listtype")) * - TightListItem(enumerator)^0) - * Cc(true) * skipblanklines * -enumerator - + Ct(LooseListItem(Cb("listtype")) * - LooseListItem(enumerator)^0) - * Cc(false) * skipblanklines - ) * Cb("listtype") / orderedlist - - local defstartchar = S("~:") - local defstart = ( defstartchar * #spacing * (tab + space^-3) - + space * defstartchar * #spacing * (tab + space^-2) - + space * space * defstartchar * #spacing * - (tab + space^-1) - + space * space * space * defstartchar * #spacing - ) - - local dlchunk = Cs(line * (indentedline - blankline)^0) + larsers.OrderedList = Cg(larsers.enumerator, "listtype") * + ( Ct(larsers.TightListItem(Cb("listtype")) + * larsers.TightListItem(larsers.enumerator)^0) + * Cc(true) * parsers.skipblanklines * -larsers.enumerator + + Ct(larsers.LooseListItem(Cb("listtype")) + * larsers.LooseListItem(larsers.enumerator)^0) + * Cc(false) * parsers.skipblanklines + ) * Cb("listtype") / ordered_list local function definition_list_item(term, defs, tight) return { term = parse_inlines(term), definitions = defs } end - local DefinitionListItemLoose = C(line) * skipblanklines - * Ct((defstart * - indented_blocks(dlchunk) / - parse_blocks_toplevel)^1) - * Cc(false) - / definition_list_item - - local DefinitionListItemTight = C(line) - * Ct((defstart * dlchunk / - parse_blocks)^1) - * Cc(true) - / definition_list_item - - local DefinitionList = ( Ct(DefinitionListItemLoose^1) * Cc(false) - + Ct(DefinitionListItemTight^1) - * (skipblanklines * - -DefinitionListItemLoose * Cc(true)) - ) / writer.definitionlist -% \end{macrocode} -% \paragraph{Blank Line \acro{peg} Rules} -% \begin{macrocode} - local Reference = define_reference_parser / register_link - local Blank = blankline / "" - + NoteBlock - + Reference - + (tightblocksep / "\n") -% \end{macrocode} -% \paragraph{Paragraph \acro{peg} Rules} -% \begin{macrocode} - local Paragraph = nonindentspace * Ct(Inline^1) * newline - * ( blankline^1 - + #hash - + #(leader * more * space^-1) - ) - / writer.paragraph + larsers.DefinitionListItemLoose = C(parsers.line) * parsers.skipblanklines + * Ct((parsers.defstart + * parsers.indented_blocks(parsers.dlchunk) + / parse_blocks_toplevel)^1) + * Cc(false) / definition_list_item - local ToplevelParagraph - = nonindentspace * Ct(Inline^1) * (newline - * ( blankline^1 - + #hash - + #(leader * more * space^-1) - + eof - ) - + eof ) - / writer.paragraph + larsers.DefinitionListItemTight = C(parsers.line) + * Ct((parsers.defstart * parsers.dlchunk + / parse_blocks)^1) + * Cc(true) / definition_list_item - local Plain = nonindentspace * Ct(Inline^1) / writer.plain + larsers.DefinitionList = ( Ct(larsers.DefinitionListItemLoose^1) * Cc(false) + + Ct(larsers.DefinitionListItemTight^1) + * (parsers.skipblanklines + * -larsers.DefinitionListItemLoose * Cc(true)) + ) / writer.definitionlist % \end{macrocode} -% \paragraph{Heading \acro{peg} Rules} +% \paragraph{Blank (local)} +% \begin{macrocode} + larsers.Blank = parsers.blankline / "" + + larsers.NoteBlock + + larsers.Reference + + (parsers.tightblocksep / "\n") +% \end{macrocode} +% \paragraph{Headings (local)} % \begin{macrocode} - -- parse Atx heading start and return level - local HeadingStart = #hash * C(hash^-6) * -hash / length - - -- parse setext header ending and return level - local HeadingLevel = equal^1 * Cc(1) + dash^1 * Cc(2) - - local function strip_atx_end(s) - return s:gsub("[#%s]*\n$","") - end - -- parse atx header - local AtxHeading = Cg(HeadingStart,"level") - * optionalspace - * (C(line) / strip_atx_end / parse_inlines) + larsers.AtxHeading = Cg(parsers.HeadingStart,"level") + * parsers.optionalspace + * (C(parsers.line) / strip_atx_end / parse_inlines) * Cb("level") / writer.heading -- parse setext header - local SetextHeading = #(line * S("=-")) - * Ct(line / parse_inlines) - * HeadingLevel - * optionalspace * newline - / writer.heading + larsers.SetextHeading = #(parsers.line * S("=-")) + * Ct(parsers.line / parse_inlines) + * parsers.HeadingLevel + * parsers.optionalspace * parsers.newline + / writer.heading - local Heading = AtxHeading + SetextHeading + larsers.Heading = larsers.AtxHeading + larsers.SetextHeading % \end{macrocode} -% \paragraph{Top Level \acro{peg} Specification} +% \paragraph{Syntax Specification} % \begin{macrocode} - syntax = + local syntax = { "Blocks", - Blocks = Blank^0 * - Block^-1 * - (Blank^0 / function() - return writer.interblocksep - end * Block)^0 * - Blank^0 * - eof, + Blocks = larsers.Blank^0 * parsers.Block^-1 + * (larsers.Blank^0 / function() + return writer.interblocksep + end + * parsers.Block)^0 + * larsers.Blank^0 * parsers.eof, - Blank = Blank, + Blank = larsers.Blank, Block = V("Blockquote") + V("Verbatim") @@ -3301,20 +3840,21 @@ function M.reader.new(writer, options) + V("OrderedList") + V("Heading") + V("DefinitionList") + + V("DisplayHtml") + V("Paragraph") + V("Plain"), - Blockquote = Blockquote, - Verbatim = Verbatim, - FencedCode = FencedCode, - HorizontalRule = HorizontalRule, - BulletList = BulletList, - OrderedList = OrderedList, - Heading = Heading, - DefinitionList = DefinitionList, - DisplayHtml = DisplayHtml, - Paragraph = Paragraph, - Plain = Plain, + Blockquote = larsers.Blockquote, + Verbatim = larsers.Verbatim, + FencedCode = larsers.FencedCode, + HorizontalRule = larsers.HorizontalRule, + BulletList = larsers.BulletList, + OrderedList = larsers.OrderedList, + Heading = larsers.Heading, + DefinitionList = larsers.DefinitionList, + DisplayHtml = larsers.DisplayHtml, + Paragraph = larsers.Paragraph, + Plain = larsers.Plain, Inline = V("Str") + V("Space") @@ -3330,78 +3870,86 @@ function M.reader.new(writer, options) + V("Code") + V("AutoLinkUrl") + V("AutoLinkEmail") + + V("InlineHtml") + + V("HtmlEntity") + V("EscapedChar") + V("Smart") + V("Symbol"), - Str = Str, - Space = Space, - Endline = Endline, - UlOrStarLine = UlOrStarLine, - Strong = Strong, - Emph = Emph, - InlineNote = InlineNote, - NoteRef = NoteRef, - Citations = Citations, - Link = Link, - Image = Image, - Code = Code, - AutoLinkUrl = AutoLinkUrl, - AutoLinkEmail = AutoLinkEmail, - InlineHtml = InlineHtml, - HtmlEntity = HtmlEntity, - EscapedChar = EscapedChar, - Smart = Smart, - Symbol = Symbol, + Str = larsers.Str, + Space = larsers.Space, + Endline = larsers.Endline, + UlOrStarLine = larsers.UlOrStarLine, + Strong = larsers.Strong, + Emph = larsers.Emph, + InlineNote = larsers.InlineNote, + NoteRef = larsers.NoteRef, + Citations = larsers.Citations, + Link = larsers.Link, + Image = larsers.Image, + Code = larsers.Code, + AutoLinkUrl = larsers.AutoLinkUrl, + AutoLinkEmail = larsers.AutoLinkEmail, + InlineHtml = larsers.InlineHtml, + HtmlEntity = larsers.HtmlEntity, + EscapedChar = larsers.EscapedChar, + Smart = larsers.Smart, + Symbol = larsers.Symbol, } if not options.definitionLists then - syntax.DefinitionList = fail + syntax.DefinitionList = parsers.fail end if not options.fencedCode then - syntax.FencedCode = fail + syntax.FencedCode = parsers.fail end if not options.citations then - syntax.Citations = fail + syntax.Citations = parsers.fail end if not options.footnotes then - syntax.NoteRef = fail + syntax.NoteRef = parsers.fail end if not options.inlineFootnotes then - syntax.InlineNote = fail + syntax.InlineNote = parsers.fail end if not options.smartEllipses then - syntax.Smart = fail + syntax.Smart = parsers.fail + end + + if not options.html then + syntax.DisplayHtml = parsers.fail + syntax.InlineHtml = parsers.fail + syntax.HtmlEntity = parsers.fail end local blocks_toplevel_t = util.table_copy(syntax) - blocks_toplevel_t.Paragraph = ToplevelParagraph - blocks_toplevel = Ct(blocks_toplevel_t) + blocks_toplevel_t.Paragraph = larsers.ToplevelParagraph + larsers.blocks_toplevel = Ct(blocks_toplevel_t) - blocks = Ct(syntax) + larsers.blocks = Ct(syntax) local inlines_t = util.table_copy(syntax) inlines_t[1] = "Inlines" - inlines_t.Inlines = Inline^0 * (spacing^0 * eof / "") - inlines = Ct(inlines_t) + inlines_t.Inlines = parsers.Inline^0 * (parsers.spacing^0 * parsers.eof / "") + larsers.inlines = Ct(inlines_t) local inlines_no_link_t = util.table_copy(inlines_t) - inlines_no_link_t.Link = fail - inlines_no_link = Ct(inlines_no_link_t) + inlines_no_link_t.Link = parsers.fail + larsers.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 = fail - inlines_no_inline_note = Ct(inlines_no_inline_note_t) + inlines_no_inline_note_t.InlineNote = parsers.fail + larsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) local inlines_nbsp_t = util.table_copy(inlines_t) - inlines_nbsp_t.Endline = NonbreakingEndline - inlines_nbsp_t.Space = NonbreakingSpace - inlines_nbsp = Ct(inlines_nbsp_t) + inlines_nbsp_t.Endline = larsers.NonbreakingEndline + inlines_nbsp_t.Space = larsers.NonbreakingSpace + larsers.inlines_nbsp = Ct(inlines_nbsp_t) % \end{macrocode} % % \paragraph{Exported Conversion Function} @@ -3516,7 +4064,7 @@ return M \def\markdownRendererDlEndPrototype{}% \def\markdownRendererDlEndTightPrototype{}% \def\markdownRendererEmphasisPrototype#1{{\it#1}}% -\def\markdownRendererStrongEmphasisPrototype#1{{\it#1}}% +\def\markdownRendererStrongEmphasisPrototype#1{{\bf#1}}% \def\markdownRendererBlockQuoteBeginPrototype{\par\begingroup\it}% \def\markdownRendererBlockQuoteEndPrototype{\endgroup\par}% \def\markdownRendererInputVerbatimPrototype#1{% @@ -3552,6 +4100,9 @@ return M \ifx\markdownOptionBlankBeforeHeading\undefined\else blankBeforeHeading = \markdownOptionBlankBeforeHeading, \fi +\ifx\markdownOptionBreakableBlockquotes\undefined\else + breakableBlockquotes = \markdownOptionBreakableBlockquotes, +\fi \ifx\markdownOptionCacheDir\undefined\else cacheDir = "\markdownOptionCacheDir", \fi @@ -3573,6 +4124,9 @@ return M \ifx\markdownOptionHashEnumerators\undefined\else hashEnumerators = \markdownOptionHashEnumerators, \fi +\ifx\markdownOptionHtml\undefined\else + html = \markdownOptionHtml, +\fi \ifx\markdownOptionHybrid\undefined\else hybrid = \markdownOptionHybrid, \fi @@ -3617,128 +4171,18 @@ local convert = md.new(\markdownLuaOptions) }% % \end{macrocode} % -% \subsubsection{Lua Shell Escape Bridge}\label{sec:luabridge} -% The following \TeX{} code is intended for \TeX{} engines that do not provide -% direct access to Lua, but expose the shell of the operating system. This -% corresponds to the \m{markdownMode} values of \t`0` and \t`1`. -% -% The \m{markdownLuaExecute} and \m{markdownReadAndConvert} macros defined here -% and in Section \ref{sec:directlua} are meant to be transparent to the -% remaining code. -% -% The package assumes that although the user is not using the Lua\TeX{} engine, -% their TeX distribution contains it, and uses shell access to produce and -% execute Lua scripts using the \TeX{}Lua interpreter (see -% \cite[Section~3.1.1]{luatex16}). -% \begin{macrocode} - -\ifnum\markdownMode<2\relax -\ifnum\markdownMode=0\relax - \markdownInfo{Using mode 0: Shell escape via write18}% -\else - \markdownInfo{Using mode 1: Shell escape via os.execute}% -\fi -% \end{macrocode} -% +% \subsubsection{Buffering Markdown Input} % The macro \mdef{markdownLuaExecuteFileStream} contains the number of the output % file stream that will be used to store the helper Lua script in the file named % \m{markdownOptionHelperScriptFileName} during the expansion of the macro -% \m{markdownLuaExecute}, and to store the markdown input in the file -% named \m{markdownOptionInputTempFileName} during the expansion of the macro +% \m{markdownLuaExecute} when the Lua shell escape bridge is in use, and to +% store the markdown input in the file named +% \m{markdownOptionInputTempFileName} during the expansion of the macro % \m{markdownReadAndConvert}. % \begin{macrocode} \csname newwrite\endcsname\markdownLuaExecuteFileStream % \end{macrocode} % -% The \mdef{markdownExecuteShellEscape} macro contains the numeric value indicating -% whether the shell access is enabled (\t`1`), disabled (\t`0`), or restricted -% (\t`2`). -% -% Inherit the value of the the \m{pdfshellescape} (Lua\TeX{}, \Hologo{pdfTeX}) -% or the \m{shellescape} (\Hologo{XeTeX}) commands. If neither of these -% commands is defined and Lua is available, attempt to access the -% \luam{status.shell_escape} configuration item. -% -% If you cannot detect, whether the shell access is enabled, act as if it were. -% \begin{macrocode} -\ifx\pdfshellescape\undefined - \ifx\shellescape\undefined - \ifnum\markdownMode=0\relax - \def\markdownExecuteShellEscape{1}% - \else - \def\markdownExecuteShellEscape{% - \directlua{tex.sprint(status.shell_escape or "1")}}% - \fi - \else - \let\markdownExecuteShellEscape\shellescape - \fi -\else - \let\markdownExecuteShellEscape\pdfshellescape -\fi -% \end{macrocode} -% -% The \mdef{markdownExecuteDirect} macro executes the code it has received as -% its first argument by writing it to the output file stream 18, if Lua is -% unavailable, or by using the Lua \luam{markdown.execute} method otherwise. -% \begin{macrocode} -\ifnum\markdownMode=0\relax - \def\markdownExecuteDirect#1{\immediate\write18{#1}}% -\else - \def\markdownExecuteDirect#1{% - \directlua{os.execute("\luaescapestring{#1}")}}% -\fi -% \end{macrocode} -% -% The \mdef{markdownExecute} macro is a wrapper on top of -% \m{markdownExecuteDirect} that checks the value of -% \m{markdownExecuteShellEscape} and prints an error message if the shell is -% inaccessible. -% \begin{macrocode} -\def\markdownExecute#1{% - \ifnum\markdownExecuteShellEscape=1\relax - \markdownExecuteDirect{#1}% - \else - \markdownError{I can not access the shell}{Either run the TeX - compiler with the --shell-escape or the --enable-write18 flag, - or set shell_escape=t in the texmf.cnf file}% - \fi}% -% \end{macrocode} -% -% The \mdef{markdownLuaExecute} macro executes the Lua code it has received as -% its first argument. The Lua code may not directly interact with the \TeX{} -% engine, but it can use the \luam{print} function in the same manner it -% would use the \luam{tex.print} method. -% \begin{macrocode} -\def\markdownLuaExecute#1{% -% \end{macrocode} -% Create the file \m{markdownOptionHelperScriptFileName} and fill it with the -% input Lua code prepended with \pkg{kpathsea} initialization, so that Lua -% modules from the \TeX{} distribution are available. -% \begin{macrocode} - \immediate\openout\markdownLuaExecuteFileStream=% - \markdownOptionHelperScriptFileName - \markdownInfo{Writing a helper Lua script to the file - "\markdownOptionHelperScriptFileName"}% - \immediate\write\markdownLuaExecuteFileStream{% - local kpse = require('kpse') - kpse.set_program_name('luatex') #1}% - \immediate\closeout\markdownLuaExecuteFileStream -% \end{macrocode} -% Execute the generated \m{markdownOptionHelperScriptFileName} Lua script using -% the \TeX{}Lua binary and store the output in the -% \m{markdownOptionOutputTempFileName} file. -% \begin{macrocode} - \markdownInfo{Executing a helper Lua script from the file - "\markdownOptionHelperScriptFileName" and storing the result in the - file "\markdownOptionOutputTempFileName"}% - \markdownExecute{texlua "\markdownOptionHelperScriptFileName" > - "\markdownOptionOutputTempFileName"}% -% \end{macrocode} -% \m{input} the generated \m{markdownOptionOutputTempFileName} file. -% \begin{macrocode} - \input\markdownOptionOutputTempFileName\relax}% -% \end{macrocode} -% % The \mdef{markdownReadAndConvertTab} macro contains the tab character literal. % \begin{macrocode} \begingroup @@ -3834,114 +4278,131 @@ local convert = md.new(\markdownLuaOptions) |endgroup % \end{macrocode} % -% \subsubsection{Direct Lua Access}\label{sec:directlua} -% The following \TeX{} code is intended for \TeX{} engines that provide -% direct access to Lua (Lua\TeX{}). The \m{markdownLuaExecute} and -% \m{markdownReadAndConvert} defined here and in Section \ref{sec:luabridge} -% are meant to be transparent to the remaining code. This corresponds to the -% \m{markdownMode} value of \t`2`. +% \subsubsection{Lua Shell Escape Bridge}\label{sec:luabridge} +% The following \TeX{} code is intended for \TeX{} engines that do not provide +% direct access to Lua, but expose the shell of the operating system. This +% corresponds to the \m{markdownMode} values of \t`0` and \t`1`. +% +% The \m{markdownLuaExecute} macro defined here and in Section +% \ref{sec:directlua} are meant to be indistinguishable to the remaining code. +% +% The package assumes that although the user is not using the Lua\TeX{} engine, +% their TeX distribution contains it, and uses shell access to produce and +% execute Lua scripts using the \TeX{}Lua interpreter (see +% \cite[Section~3.1.1]{luatex16}). % \begin{macrocode} +\ifnum\markdownMode<2\relax +\ifnum\markdownMode=0\relax + \markdownInfo{Using mode 0: Shell escape via write18}% \else -\markdownInfo{Using mode 2: Direct Lua access}% -% \end{macrocode} -% The direct Lua access version of the \m{markdownLuaExecute} macro is defined -% in terms of the \m{directlua} primitive. The \luam{print} function is set as -% an alias to the \m{tex.print} method in order to mimic the behaviour of the -% \m{markdownLuaExecute} definition from Section \ref{sec:luabridge}, -% \begin{macrocode} -\def\markdownLuaExecute#1{\directlua{local print = tex.print #1}}% + \markdownInfo{Using mode 1: Shell escape via os.execute}% +\fi % \end{macrocode} % -% In the definition of the direct Lua access version of the -% \m{markdownReadAndConvert} macro, we will be using the hash symbol -% (\t`\#`), the underscore symbol (\t`_`), the circumflex symbol (\t`\^`), the -% dollar sign (\t`\$`), the backslash symbol (\t`\textbackslash`), the percent -% sign (\t`\%`), and the braces (\t`\{\}`) as a part of the Lua syntax. -% \begin{macrocode} -\begingroup -% \end{macrocode} +% The \mdef{markdownExecuteShellEscape} macro contains the numeric value indicating +% whether the shell access is enabled (\t`1`), disabled (\t`0`), or restricted +% (\t`2`). +% +% Inherit the value of the the \m{pdfshellescape} (Lua\TeX{}, \Hologo{pdfTeX}) +% or the \m{shellescape} (\Hologo{XeTeX}) commands. If neither of these +% commands is defined and Lua is available, attempt to access the +% \luam{status.shell_escape} configuration item. % -% To this end, we will make the underscore symbol, the dollar sign, and -% circumflex symbols ordinary characters, -% \begin{macrocode} - \catcode`\_=12% - \catcode`\$=12% - \catcode`\^=12% -% \end{macrocode} -% swap the category code of the hash symbol with the slash symbol (\t`/`). -% \begin{macrocode} - \catcode`\/=6% - \catcode`\#=12% -% \end{macrocode} -% swap the category code of the percent sign with the at symbol (\t`@`). -% \begin{macrocode} - \catcode`\@=14% - \catcode`\%=12% -% \end{macrocode} -% swap the category code of the backslash symbol with the pipe symbol (\t`|`), -% \begin{macrocode} - \catcode`|=0@ - \catcode`\\=12@ -% \end{macrocode} -% Braces are a part of the plain \TeX{} syntax, but they are not removed during -% expansion, so we do not need to bother with changing their category codes. +% If you cannot detect, whether the shell access is enabled, act as if it were. % \begin{macrocode} - |gdef|markdownReadAndConvert/1/2{@ +\ifx\pdfshellescape\undefined + \ifx\shellescape\undefined + \ifnum\markdownMode=0\relax + \def\markdownExecuteShellEscape{1}% + \else + \def\markdownExecuteShellEscape{% + \directlua{tex.sprint(status.shell_escape or "1")}}% + \fi + \else + \let\markdownExecuteShellEscape\shellescape + \fi +\else + \let\markdownExecuteShellEscape\pdfshellescape +\fi % \end{macrocode} -% Make the \mdef{markdownReadAndConvertAfter} macro store the token sequence -% that will be inserted into the document after the ending token sequence has -% been found. +% +% The \mdef{markdownExecuteDirect} macro executes the code it has received as +% its first argument by writing it to the output file stream 18, if Lua is +% unavailable, or by using the Lua \luam{markdown.execute} method otherwise. % \begin{macrocode} - |def|markdownReadAndConvertAfter{/2}@ - |markdownInfo{Buffering markdown input and scanning for the - closing token sequence "/1"}@ - |directlua{@ +\ifnum\markdownMode=0\relax + \def\markdownExecuteDirect#1{\immediate\write18{#1}}% +\else + \def\markdownExecuteDirect#1{% + \directlua{os.execute("\luaescapestring{#1}")}}% +\fi % \end{macrocode} -% Set up an empty Lua table that will serve as our buffer. +% +% The \mdef{markdownExecute} macro is a wrapper on top of +% \m{markdownExecuteDirect} that checks the value of +% \m{markdownExecuteShellEscape} and prints an error message if the shell is +% inaccessible. % \begin{macrocode} - |markdownPrepare - local buffer = {} +\def\markdownExecute#1{% + \ifnum\markdownExecuteShellEscape=1\relax + \markdownExecuteDirect{#1}% + \else + \markdownError{I can not access the shell}{Either run the TeX + compiler with the --shell-escape or the --enable-write18 flag, + or set shell_escape=t in the texmf.cnf file}% + \fi}% % \end{macrocode} -% Create a regex that will match the ending input sequence. Escape any special -% regex characters (like a star inside \t`\textbackslash end\{markdown*\}`) -% inside the input. +% +% The \mdef{markdownLuaExecute} macro executes the Lua code it has received as +% its first argument. The Lua code may not directly interact with the \TeX{} +% engine, but it can use the \luam{print} function in the same manner it +% would use the \luam{tex.print} method. % \begin{macrocode} - local ending_sequence = "^.-" .. ([[/1]]):gsub( - "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") +\def\markdownLuaExecute#1{% % \end{macrocode} -% Register a callback that will notify you about new lines of input. +% Create the file \m{markdownOptionHelperScriptFileName} and fill it with the +% input Lua code prepended with \pkg{kpathsea} initialization, so that Lua +% modules from the \TeX{} distribution are available. % \begin{macrocode} - |markdownLuaRegisterIBCallback{function(line) + \immediate\openout\markdownLuaExecuteFileStream=% + \markdownOptionHelperScriptFileName + \markdownInfo{Writing a helper Lua script to the file + "\markdownOptionHelperScriptFileName"}% + \immediate\write\markdownLuaExecuteFileStream{% + local kpse = require('kpse') + kpse.set_program_name('luatex') #1}% + \immediate\closeout\markdownLuaExecuteFileStream % \end{macrocode} -% When the ending token sequence appears on a line, unregister the callback, -% convert the contents of our buffer from markdown to plain \TeX{}, and insert -% the result into the input line buffer of \TeX{}. +% Execute the generated \m{markdownOptionHelperScriptFileName} Lua script using +% the \TeX{}Lua binary and store the output in the +% \m{markdownOptionOutputTempFileName} file. % \begin{macrocode} - if line:match(ending_sequence) then - |markdownLuaUnregisterIBCallback; - local input = table.concat(buffer, "\n") .. "\n" - local output = convert(input) - return [[\markdownInfo{The ending token sequence was found}]] .. - output .. [[\markdownReadAndConvertAfter]] + \markdownInfo{Executing a helper Lua script from the file + "\markdownOptionHelperScriptFileName" and storing the result in the + file "\markdownOptionOutputTempFileName"}% + \markdownExecute{texlua "\markdownOptionHelperScriptFileName" > + "\markdownOptionOutputTempFileName"}% % \end{macrocode} -% When the ending token sequence does not appear on a line, store the line in -% our buffer, and insert either \m{fi}, if this is the first line of input, -% or an empty token list to the input line buffer of \TeX{}. +% \m{input} the generated \m{markdownOptionOutputTempFileName} file. % \begin{macrocode} - else - buffer[#buffer+1] = line - return [[\]] .. (#buffer == 1 and "fi" or "relax") - end - end}}@ + \input\markdownOptionOutputTempFileName\relax}% % \end{macrocode} -% Insert \m{iffalse} after the \m{markdownReadAndConvert} macro in order to -% consume the rest of the first line of input. +% +% \subsubsection{Direct Lua Access}\label{sec:directlua} +% The following \TeX{} code is intended for \TeX{} engines that provide +% direct access to Lua (Lua\TeX{}). The macro \m{markdownLuaExecute} defined +% here and in Section \ref{sec:luabridge} are meant to be indistinguishable to +% the remaining code. This corresponds to the \m{markdownMode} value of \t`2`. % \begin{macrocode} - |iffalse}@ +\else +\markdownInfo{Using mode 2: Direct Lua access}% % \end{macrocode} -% Reset the character categories back to the former state. +% The direct Lua access version of the \m{markdownLuaExecute} macro is defined +% in terms of the \m{directlua} primitive. The \luam{print} function is set as +% an alias to the \m{tex.print} method in order to mimic the behaviour of the +% \m{markdownLuaExecute} definition from Section \ref{sec:luabridge}, % \begin{macrocode} - |endgroup +\def\markdownLuaExecute#1{\directlua{local print = tex.print #1}}% \fi % \end{macrocode} % @@ -4128,12 +4589,6 @@ local convert = md.new(\markdownLuaOptions) dlItem = {\item[#1]}, dlEnd = {\end{description}}, emphasis = {\emph{#1}}, - strongEmphasis = {% - \ifx\alert\undefined - \textbf{\emph{#1}}% - \else % Beamer support - \alert{\emph{#1}}% - \fi}, blockQuoteBegin = {\begin{quotation}}, blockQuoteEnd = {\end{quotation}}, inputVerbatim = {\VerbatimInput{#1}}, @@ -4160,7 +4615,27 @@ local convert = md.new(\markdownLuaOptions) \fi}, horizontalRule = {\noindent\rule[0.5ex]{\linewidth}{1pt}}, footnote = {\footnote{#1}}}} - +% \end{macrocode} +% +% Support the nesting of strong emphasis. +% \begin{macrocode} +\newif\ifmarkdownLATEXStrongEmphasisNested +\markdownLATEXStrongEmphasisNestedfalse +\markdownSetup{rendererPrototypes={ + strongEmphasis = {% + \ifmarkdownLATEXStrongEmphasisNested + \markdownLATEXStrongEmphasisNestedfalse + \textmd{#1}% + \markdownLATEXStrongEmphasisNestedtrue + \else + \markdownLATEXStrongEmphasisNestedtrue + \textbf{#1}% + \markdownLATEXStrongEmphasisNestedfalse + \fi}}} +% \end{macrocode} +% +% Support \LaTeX{} document classes that do not provide chapters. +% \begin{macrocode} \ifx\chapter\undefined \markdownSetup{rendererPrototypes = { headingOne = {\section{#1}}, @@ -4235,37 +4710,6 @@ local convert = md.new(\markdownLuaOptions) % \end{macrocode} % % \subsubsection{Miscellanea} -% Unlike base Lua\TeX{}, which only allows for a single registered function per -% a callback (see \cite[Section~8.1]{luatex16}), the \Hologo{LaTeX2e} format -% disables the \luam{callback.register} method and exposes the -% \luam{luatexbase.add_to_callback} and \luam{luatexbase.remove_from_callback} -% methods that enable the user code to hook several functions on a single -% callback (see \cite[Section~73.4]{latex16}). -% -% To make our code function with the \Hologo{LaTeX2e} format, we need to -% redefine the \m{markdownLuaRegisterIBCallback} and -% \m{markdownLuaUnregisterIBCallback} macros accordingly. -% \begin{macrocode} -\let\markdownLuaRegisterIBCallbackPrevious - \markdownLuaRegisterIBCallback -\let\markdownLuaUnregisterIBCallbackPrevious - \markdownLuaUnregisterIBCallback -\renewcommand\markdownLuaRegisterIBCallback[1]{% - if luatexbase and luatexbase.add_to_callback then - luatexbase.add_to_callback("process_input_buffer", #1, % - "The markdown input processor") - else - \markdownLuaRegisterIBCallbackPrevious{#1} - end} -\renewcommand\markdownLuaUnregisterIBCallback{% - if luatexbase and luatexbase.add_to_callback then - luatexbase.remove_from_callback("process_input_buffer",% - "The markdown input processor") - else - \markdownLuaUnregisterIBCallbackPrevious; - end} -% \end{macrocode} -% % When buffering user input, we should disable the bytes with the high bit set, % since these are made active by the \pkg{inputenc} package. We will do this by % redefining the \m{markdownMakeOther} macro accordingly. The code is courtesy @@ -4293,54 +4737,25 @@ local convert = md.new(\markdownLuaOptions) % \begin{macrocode} \def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&% \do\#\do\^\do\_\do\%\do\~}% -% \end{macrocode} -% When there is no Lua support, then just load the plain \TeX{} implementation. -% \begin{macrocode} -\ifx\directlua\undefined - \input markdown -\else -% \end{macrocode} -% When there is Lua support, check if we can set the \t`process_input_buffer` -% Lua\TeX{} callback. -% \begin{macrocode} - \directlua{% - local function unescape(str) - return (str:gsub("|", string.char(92))) end - local old_callback = callback.find("process_input_buffer") - callback.register("process_input_buffer", function() end) - local new_callback = callback.find("process_input_buffer") -% \end{macrocode} -% -% If we can not, we are probably using ConTeXt Mark IV. In ConTeXt Mark IV, the -% \t`process_input_buffer` callback is currently frozen (inaccessible from the -% user code) and, due to the lack of available documentation, it is unclear to -% me how to emulate it. As a workaround, we will force the plain \TeX{} -% implementation to use the Lua shell escape bridge (see Section -% \ref{sec:luabridge}) by setting the \m{markdownMode} macro to the value of -% \t`1`. -% \begin{macrocode} - if new_callback == false then - tex.print(unescape([[|def|markdownMode{1}|input markdown]])) +\input markdown % \end{macrocode} % -% If we can set the \t`process_input_buffer` Lua\TeX{} callback, then just load -% the plain \TeX{} implementation. +% When buffering user input, we should disable the bytes with the high bit set, +% since these are made active by the \m{enableregime} macro. We will do this +% by redefining the \m{markdownMakeOther} macro accordingly. The code is +% courtesy of Scott Pakin, the creator of the \pkg{filecontents} LaTeX package. % \begin{macrocode} - else - callback.register("process_input_buffer", old_callback) - tex.print(unescape("|input markdown")) - end}% -\fi +\def\markdownMakeOther{% + \count0=128\relax + \loop + \catcode\count0=11\relax + \advance\count0 by 1\relax + \ifnum\count0<256\repeat % \end{macrocode} -% -% If the shell escape bridge is being used, define the \m{markdownMakeOther} -% macro, so that the pipe character (\t`|`) is inactive during the scanning. +% On top of that, make the pipe character (\t`|`) inactive during the scanning. % This is necessary, since the character is active in \Hologo{ConTeXt}. % \begin{macrocode} -\ifnum\markdownMode<2\relax - \def\markdownMakeOther{% - \catcode`|=12}% -\fi + \catcode`|=12}% % \end{macrocode} % % \subsubsection{Logging Facilities} @@ -4426,7 +4841,7 @@ local convert = md.new(\markdownLuaOptions) \def\markdownRendererDlEndTightPrototype{% \stopMarkdownConTeXtDlTightPrototype}% \def\markdownRendererEmphasisPrototype#1{{\em#1}}% -\def\markdownRendererStrongEmphasisPrototype#1{{\bf\em#1}}% +\def\markdownRendererStrongEmphasisPrototype#1{{\bf#1}}% \def\markdownRendererBlockQuoteBeginPrototype{\startquotation}% \def\markdownRendererBlockQuoteEndPrototype{\stopquotation}% \def\markdownRendererInputVerbatimPrototype#1{\typefile{#1}}% @@ -4436,7 +4851,7 @@ local convert = md.new(\markdownLuaOptions) \else % \end{macrocode} % The code fence infostring is used as a name from the \Hologo{ConTeXt} -% \m{definetyping} command. This allows the user to set up code highlighting +% \m{definetyping} macro. This allows the user to set up code highlighting % mapping as follows: % \begin{Verbatim} % % Map the `TEX` syntax highlighter to the `latex` infostring. diff --git a/Master/texmf-dist/tex/context/third/markdown/t-markdown.tex b/Master/texmf-dist/tex/context/third/markdown/t-markdown.tex index 4f90f3b8f8f..91e89c1dc20 100644 --- a/Master/texmf-dist/tex/context/third/markdown/t-markdown.tex +++ b/Master/texmf-dist/tex/context/third/markdown/t-markdown.tex @@ -51,26 +51,14 @@ \let\stopmarkdown\relax \def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&% \do\#\do\^\do\_\do\%\do\~}% -\ifx\directlua\undefined - \input markdown -\else - \directlua{% - local function unescape(str) - return (str:gsub("|", string.char(92))) end - local old_callback = callback.find("process_input_buffer") - callback.register("process_input_buffer", function() end) - local new_callback = callback.find("process_input_buffer") - if new_callback == false then - tex.print(unescape([[|def|markdownMode{1}|input markdown]])) - else - callback.register("process_input_buffer", old_callback) - tex.print(unescape("|input markdown")) - end}% -\fi -\ifnum\markdownMode<2\relax - \def\markdownMakeOther{% - \catcode`|=12}% -\fi +\input markdown +\def\markdownMakeOther{% + \count0=128\relax + \loop + \catcode\count0=11\relax + \advance\count0 by 1\relax + \ifnum\count0<256\repeat + \catcode`|=12}% \def\markdownInfo#1{\writestatus{markdown}{#1.}}% \def\markdownWarning#1{\writestatus{markdown\space warn}{#1.}}% \begingroup @@ -132,7 +120,7 @@ \def\markdownRendererDlEndTightPrototype{% \stopMarkdownConTeXtDlTightPrototype}% \def\markdownRendererEmphasisPrototype#1{{\em#1}}% -\def\markdownRendererStrongEmphasisPrototype#1{{\bf\em#1}}% +\def\markdownRendererStrongEmphasisPrototype#1{{\bf#1}}% \def\markdownRendererBlockQuoteBeginPrototype{\startquotation}% \def\markdownRendererBlockQuoteEndPrototype{\stopquotation}% \def\markdownRendererInputVerbatimPrototype#1{\typefile{#1}}% diff --git a/Master/texmf-dist/tex/generic/markdown/markdown.tex b/Master/texmf-dist/tex/generic/markdown/markdown.tex index c98168c110a..8de36017864 100644 --- a/Master/texmf-dist/tex/generic/markdown/markdown.tex +++ b/Master/texmf-dist/tex/generic/markdown/markdown.tex @@ -45,8 +45,8 @@ %% %% The names of the source files used are shown above. %% -\def\markdownLastModified{2016/12/09}% -\def\markdownVersion{2.2.2}% +\def\markdownLastModified{2017/01/05}% +\def\markdownVersion{2.3.0}% \let\markdownBegin\relax \let\markdownEnd\relax \let\markdownInput\relax @@ -57,12 +57,14 @@ \let\markdownOptionBlankBeforeBlockquote\undefined \let\markdownOptionBlankBeforeCodeFence\undefined \let\markdownOptionBlankBeforeHeading\undefined +\let\markdownOptionBreakableBlockquotes\undefined \let\markdownOptionCitations\undefined \let\markdownOptionCitationNbsps\undefined \let\markdownOptionDefinitionLists\undefined \let\markdownOptionFootnotes\undefined \let\markdownOptionFencedCode\undefined \let\markdownOptionHashEnumerators\undefined +\let\markdownOptionHtml\undefined \let\markdownOptionHybrid\undefined \let\markdownOptionInlineFootnotes\undefined \let\markdownOptionPreserveTabs\undefined @@ -237,11 +239,6 @@ \def\markdownInfo#1{}% \def\markdownWarning#1{}% \def\markdownError#1{}% -\def\markdownLuaRegisterIBCallback#1{% - local old_callback = callback.find("process_input_buffer") - callback.register("process_input_buffer", #1)}% -\def\markdownLuaUnregisterIBCallback{% - callback.register("process_input_buffer", old_callback)}% \let\markdownMakeOther\relax \let\markdownReadAndConvert\relax \begingroup @@ -257,6 +254,8 @@ \def\markdownMode{2}% \fi \fi +\def\markdownLuaRegisterIBCallback#1{\relax}% +\def\markdownLuaUnregisterIBCallback#1{\relax}% \def\markdownInfo#1{% \message{(l.\the\inputlineno) markdown.tex info: #1.}}% \def\markdownWarning#1{% @@ -304,7 +303,7 @@ \def\markdownRendererDlEndPrototype{}% \def\markdownRendererDlEndTightPrototype{}% \def\markdownRendererEmphasisPrototype#1{{\it#1}}% -\def\markdownRendererStrongEmphasisPrototype#1{{\it#1}}% +\def\markdownRendererStrongEmphasisPrototype#1{{\bf#1}}% \def\markdownRendererBlockQuoteBeginPrototype{\par\begingroup\it}% \def\markdownRendererBlockQuoteEndPrototype{\endgroup\par}% \def\markdownRendererInputVerbatimPrototype#1{% @@ -331,6 +330,9 @@ \ifx\markdownOptionBlankBeforeHeading\undefined\else blankBeforeHeading = \markdownOptionBlankBeforeHeading, \fi +\ifx\markdownOptionBreakableBlockquotes\undefined\else + breakableBlockquotes = \markdownOptionBreakableBlockquotes, +\fi \ifx\markdownOptionCacheDir\undefined\else cacheDir = "\markdownOptionCacheDir", \fi @@ -352,6 +354,9 @@ \ifx\markdownOptionHashEnumerators\undefined\else hashEnumerators = \markdownOptionHashEnumerators, \fi +\ifx\markdownOptionHtml\undefined\else + html = \markdownOptionHtml, +\fi \ifx\markdownOptionHybrid\undefined\else hybrid = \markdownOptionHybrid, \fi @@ -380,14 +385,53 @@ end local md = require("markdown") local convert = md.new(\markdownLuaOptions) }% - +\csname newwrite\endcsname\markdownLuaExecuteFileStream +\begingroup + \catcode`\^^I=12% + \gdef\markdownReadAndConvertTab{^^I}% +\endgroup +\begingroup + \catcode`\^^M=13% + \catcode`\^^I=13% + \catcode`|=0% + \catcode`\\=12% + |gdef|markdownReadAndConvert#1#2{% + |begingroup% + |immediate|openout|markdownLuaExecuteFileStream% + |markdownOptionInputTempFileName% + |markdownInfo{Buffering markdown input into the temporary % + input file "|markdownOptionInputTempFileName" and scanning % + for the closing token sequence "#1"}% + |def|do##1{|catcode`##1=12}|dospecials% + |catcode`| =12% + |markdownMakeOther% + |def|markdownReadAndConvertProcessLine##1#1##2#1##3|relax{% + |ifx|relax##3|relax% + |immediate|write|markdownLuaExecuteFileStream{##1}% + |else% + |def^^M{% + |markdownInfo{The ending token sequence was found}% + |immediate|closeout|markdownLuaExecuteFileStream% + |endgroup% + |markdownInput|markdownOptionInputTempFileName% + #2}% + |fi% + ^^M}% + |catcode`|^^I=13% + |def^^I{|markdownReadAndConvertTab}% + |catcode`|^^M=13% + |def^^M##1^^M{% + |def^^M####1^^M{% + |markdownReadAndConvertProcessLine####1#1#1|relax}% + ^^M}% + ^^M}% +|endgroup \ifnum\markdownMode<2\relax \ifnum\markdownMode=0\relax \markdownInfo{Using mode 0: Shell escape via write18}% \else \markdownInfo{Using mode 1: Shell escape via os.execute}% \fi -\csname newwrite\endcsname\markdownLuaExecuteFileStream \ifx\pdfshellescape\undefined \ifx\shellescape\undefined \ifnum\markdownMode=0\relax @@ -431,82 +475,9 @@ local convert = md.new(\markdownLuaOptions) \markdownExecute{texlua "\markdownOptionHelperScriptFileName" > "\markdownOptionOutputTempFileName"}% \input\markdownOptionOutputTempFileName\relax}% -\begingroup - \catcode`\^^I=12% - \gdef\markdownReadAndConvertTab{^^I}% -\endgroup -\begingroup - \catcode`\^^M=13% - \catcode`\^^I=13% - \catcode`|=0% - \catcode`\\=12% - |gdef|markdownReadAndConvert#1#2{% - |begingroup% - |immediate|openout|markdownLuaExecuteFileStream% - |markdownOptionInputTempFileName% - |markdownInfo{Buffering markdown input into the temporary % - input file "|markdownOptionInputTempFileName" and scanning % - for the closing token sequence "#1"}% - |def|do##1{|catcode`##1=12}|dospecials% - |catcode`| =12% - |markdownMakeOther% - |def|markdownReadAndConvertProcessLine##1#1##2#1##3|relax{% - |ifx|relax##3|relax% - |immediate|write|markdownLuaExecuteFileStream{##1}% - |else% - |def^^M{% - |markdownInfo{The ending token sequence was found}% - |immediate|closeout|markdownLuaExecuteFileStream% - |endgroup% - |markdownInput|markdownOptionInputTempFileName% - #2}% - |fi% - ^^M}% - |catcode`|^^I=13% - |def^^I{|markdownReadAndConvertTab}% - |catcode`|^^M=13% - |def^^M##1^^M{% - |def^^M####1^^M{% - |markdownReadAndConvertProcessLine####1#1#1|relax}% - ^^M}% - ^^M}% -|endgroup \else \markdownInfo{Using mode 2: Direct Lua access}% \def\markdownLuaExecute#1{\directlua{local print = tex.print #1}}% -\begingroup - \catcode`\_=12% - \catcode`\$=12% - \catcode`\^=12% - \catcode`\/=6% - \catcode`\#=12% - \catcode`\@=14% - \catcode`\%=12% - \catcode`|=0@ - \catcode`\\=12@ - |gdef|markdownReadAndConvert/1/2{@ - |def|markdownReadAndConvertAfter{/2}@ - |markdownInfo{Buffering markdown input and scanning for the - closing token sequence "/1"}@ - |directlua{@ - |markdownPrepare - local buffer = {} - local ending_sequence = "^.-" .. ([[/1]]):gsub( - "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") - |markdownLuaRegisterIBCallback{function(line) - if line:match(ending_sequence) then - |markdownLuaUnregisterIBCallback; - local input = table.concat(buffer, "\n") .. "\n" - local output = convert(input) - return [[\markdownInfo{The ending token sequence was found}]] .. - output .. [[\markdownReadAndConvertAfter]] - else - buffer[#buffer+1] = line - return [[\]] .. (#buffer == 1 and "fi" or "relax") - end - end}}@ - |iffalse}@ - |endgroup \fi \begingroup \catcode`|=0% diff --git a/Master/texmf-dist/tex/latex/markdown/markdown.sty b/Master/texmf-dist/tex/latex/markdown/markdown.sty index 97b42f5f766..94997e75fb8 100644 --- a/Master/texmf-dist/tex/latex/markdown/markdown.sty +++ b/Master/texmf-dist/tex/latex/markdown/markdown.sty @@ -63,6 +63,8 @@ \def\markdownOptionBlankBeforeCodeFence{#1}}% \define@key{markdownOptions}{blankBeforeHeading}[true]{% \def\markdownOptionBlankBeforeHeading{#1}}% +\define@key{markdownOptions}{breakableBlockquotes}[true]{% + \def\markdownOptionBreakableBlockquotes{#1}}% \define@key{markdownOptions}{citations}[true]{% \def\markdownOptionCitations{#1}}% \define@key{markdownOptions}{citationNbsps}[true]{% @@ -77,6 +79,8 @@ \def\markdownOptionFencedCode{#1}}% \define@key{markdownOptions}{hashEnumerators}[true]{% \def\markdownOptionHashEnumerators{#1}}% +\define@key{markdownOptions}{html}[true]{% + \def\markdownOptionHtml{#1}}% \define@key{markdownOptions}{hybrid}[true]{% \def\markdownOptionHybrid{#1}}% \define@key{markdownOptions}{inlineFootnotes}[true]{% @@ -403,12 +407,6 @@ dlItem = {\item[#1]}, dlEnd = {\end{description}}, emphasis = {\emph{#1}}, - strongEmphasis = {% - \ifx\alert\undefined - \textbf{\emph{#1}}% - \else % Beamer support - \alert{\emph{#1}}% - \fi}, blockQuoteBegin = {\begin{quotation}}, blockQuoteEnd = {\end{quotation}}, inputVerbatim = {\VerbatimInput{#1}}, @@ -428,7 +426,19 @@ \fi}, horizontalRule = {\noindent\rule[0.5ex]{\linewidth}{1pt}}, footnote = {\footnote{#1}}}} - +\newif\ifmarkdownLATEXStrongEmphasisNested +\markdownLATEXStrongEmphasisNestedfalse +\markdownSetup{rendererPrototypes={ + strongEmphasis = {% + \ifmarkdownLATEXStrongEmphasisNested + \markdownLATEXStrongEmphasisNestedfalse + \textmd{#1}% + \markdownLATEXStrongEmphasisNestedtrue + \else + \markdownLATEXStrongEmphasisNestedtrue + \textbf{#1}% + \markdownLATEXStrongEmphasisNestedfalse + \fi}}} \ifx\chapter\undefined \markdownSetup{rendererPrototypes = { headingOne = {\section{#1}}, @@ -491,24 +501,6 @@ \markdownLaTeXBibLaTeXTextCitations \expandafter{\expandafter}% \fi}}} -\let\markdownLuaRegisterIBCallbackPrevious - \markdownLuaRegisterIBCallback -\let\markdownLuaUnregisterIBCallbackPrevious - \markdownLuaUnregisterIBCallback -\renewcommand\markdownLuaRegisterIBCallback[1]{% - if luatexbase and luatexbase.add_to_callback then - luatexbase.add_to_callback("process_input_buffer", #1, % - "The markdown input processor") - else - \markdownLuaRegisterIBCallbackPrevious{#1} - end} -\renewcommand\markdownLuaUnregisterIBCallback{% - if luatexbase and luatexbase.add_to_callback then - luatexbase.remove_from_callback("process_input_buffer",% - "The markdown input processor") - else - \markdownLuaUnregisterIBCallbackPrevious; - end} \newcommand\markdownMakeOther{% \count0=128\relax \loop diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown.lua b/Master/texmf-dist/tex/luatex/markdown/markdown.lua index 06f2a2b6169..63e4effda43 100644 --- a/Master/texmf-dist/tex/luatex/markdown/markdown.lua +++ b/Master/texmf-dist/tex/luatex/markdown/markdown.lua @@ -58,10 +58,11 @@ -- those in the standard .ins files. -- local metadata = { - version = "2.2.2", + version = "2.3.0", 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; 2016 Vít Novotný", + copyright = "2009-2017 John MacFarlane, Hans Hagen; " .. + "2016-2017 Vít Novotný", license = "LPPL 1.3" } if not modules then modules = { } end @@ -74,11 +75,13 @@ local defaultOptions = {} defaultOptions.blankBeforeBlockquote = false defaultOptions.blankBeforeCodeFence = false defaultOptions.blankBeforeHeading = false +defaultOptions.breakableBlockquotes = false defaultOptions.cacheDir = "." defaultOptions.citationNbsps = true defaultOptions.citations = false defaultOptions.definitionLists = false defaultOptions.hashEnumerators = false +defaultOptions.html = false defaultOptions.hybrid = false defaultOptions.fencedCode = false defaultOptions.footnotes = false @@ -221,6 +224,269 @@ function util.pathname(dir, file) return dir .. "/" .. file end end +local entities = {} + +local character_entities = { + ["quot"] = 0x0022, + ["amp"] = 0x0026, + ["apos"] = 0x0027, + ["lt"] = 0x003C, + ["gt"] = 0x003E, + ["nbsp"] = 160, + ["iexcl"] = 0x00A1, + ["cent"] = 0x00A2, + ["pound"] = 0x00A3, + ["curren"] = 0x00A4, + ["yen"] = 0x00A5, + ["brvbar"] = 0x00A6, + ["sect"] = 0x00A7, + ["uml"] = 0x00A8, + ["copy"] = 0x00A9, + ["ordf"] = 0x00AA, + ["laquo"] = 0x00AB, + ["not"] = 0x00AC, + ["shy"] = 173, + ["reg"] = 0x00AE, + ["macr"] = 0x00AF, + ["deg"] = 0x00B0, + ["plusmn"] = 0x00B1, + ["sup2"] = 0x00B2, + ["sup3"] = 0x00B3, + ["acute"] = 0x00B4, + ["micro"] = 0x00B5, + ["para"] = 0x00B6, + ["middot"] = 0x00B7, + ["cedil"] = 0x00B8, + ["sup1"] = 0x00B9, + ["ordm"] = 0x00BA, + ["raquo"] = 0x00BB, + ["frac14"] = 0x00BC, + ["frac12"] = 0x00BD, + ["frac34"] = 0x00BE, + ["iquest"] = 0x00BF, + ["Agrave"] = 0x00C0, + ["Aacute"] = 0x00C1, + ["Acirc"] = 0x00C2, + ["Atilde"] = 0x00C3, + ["Auml"] = 0x00C4, + ["Aring"] = 0x00C5, + ["AElig"] = 0x00C6, + ["Ccedil"] = 0x00C7, + ["Egrave"] = 0x00C8, + ["Eacute"] = 0x00C9, + ["Ecirc"] = 0x00CA, + ["Euml"] = 0x00CB, + ["Igrave"] = 0x00CC, + ["Iacute"] = 0x00CD, + ["Icirc"] = 0x00CE, + ["Iuml"] = 0x00CF, + ["ETH"] = 0x00D0, + ["Ntilde"] = 0x00D1, + ["Ograve"] = 0x00D2, + ["Oacute"] = 0x00D3, + ["Ocirc"] = 0x00D4, + ["Otilde"] = 0x00D5, + ["Ouml"] = 0x00D6, + ["times"] = 0x00D7, + ["Oslash"] = 0x00D8, + ["Ugrave"] = 0x00D9, + ["Uacute"] = 0x00DA, + ["Ucirc"] = 0x00DB, + ["Uuml"] = 0x00DC, + ["Yacute"] = 0x00DD, + ["THORN"] = 0x00DE, + ["szlig"] = 0x00DF, + ["agrave"] = 0x00E0, + ["aacute"] = 0x00E1, + ["acirc"] = 0x00E2, + ["atilde"] = 0x00E3, + ["auml"] = 0x00E4, + ["aring"] = 0x00E5, + ["aelig"] = 0x00E6, + ["ccedil"] = 0x00E7, + ["egrave"] = 0x00E8, + ["eacute"] = 0x00E9, + ["ecirc"] = 0x00EA, + ["euml"] = 0x00EB, + ["igrave"] = 0x00EC, + ["iacute"] = 0x00ED, + ["icirc"] = 0x00EE, + ["iuml"] = 0x00EF, + ["eth"] = 0x00F0, + ["ntilde"] = 0x00F1, + ["ograve"] = 0x00F2, + ["oacute"] = 0x00F3, + ["ocirc"] = 0x00F4, + ["otilde"] = 0x00F5, + ["ouml"] = 0x00F6, + ["divide"] = 0x00F7, + ["oslash"] = 0x00F8, + ["ugrave"] = 0x00F9, + ["uacute"] = 0x00FA, + ["ucirc"] = 0x00FB, + ["uuml"] = 0x00FC, + ["yacute"] = 0x00FD, + ["thorn"] = 0x00FE, + ["yuml"] = 0x00FF, + ["OElig"] = 0x0152, + ["oelig"] = 0x0153, + ["Scaron"] = 0x0160, + ["scaron"] = 0x0161, + ["Yuml"] = 0x0178, + ["fnof"] = 0x0192, + ["circ"] = 0x02C6, + ["tilde"] = 0x02DC, + ["Alpha"] = 0x0391, + ["Beta"] = 0x0392, + ["Gamma"] = 0x0393, + ["Delta"] = 0x0394, + ["Epsilon"] = 0x0395, + ["Zeta"] = 0x0396, + ["Eta"] = 0x0397, + ["Theta"] = 0x0398, + ["Iota"] = 0x0399, + ["Kappa"] = 0x039A, + ["Lambda"] = 0x039B, + ["Mu"] = 0x039C, + ["Nu"] = 0x039D, + ["Xi"] = 0x039E, + ["Omicron"] = 0x039F, + ["Pi"] = 0x03A0, + ["Rho"] = 0x03A1, + ["Sigma"] = 0x03A3, + ["Tau"] = 0x03A4, + ["Upsilon"] = 0x03A5, + ["Phi"] = 0x03A6, + ["Chi"] = 0x03A7, + ["Psi"] = 0x03A8, + ["Omega"] = 0x03A9, + ["alpha"] = 0x03B1, + ["beta"] = 0x03B2, + ["gamma"] = 0x03B3, + ["delta"] = 0x03B4, + ["epsilon"] = 0x03B5, + ["zeta"] = 0x03B6, + ["eta"] = 0x03B7, + ["theta"] = 0x03B8, + ["iota"] = 0x03B9, + ["kappa"] = 0x03BA, + ["lambda"] = 0x03BB, + ["mu"] = 0x03BC, + ["nu"] = 0x03BD, + ["xi"] = 0x03BE, + ["omicron"] = 0x03BF, + ["pi"] = 0x03C0, + ["rho"] = 0x03C1, + ["sigmaf"] = 0x03C2, + ["sigma"] = 0x03C3, + ["tau"] = 0x03C4, + ["upsilon"] = 0x03C5, + ["phi"] = 0x03C6, + ["chi"] = 0x03C7, + ["psi"] = 0x03C8, + ["omega"] = 0x03C9, + ["thetasym"] = 0x03D1, + ["upsih"] = 0x03D2, + ["piv"] = 0x03D6, + ["ensp"] = 0x2002, + ["emsp"] = 0x2003, + ["thinsp"] = 0x2009, + ["ndash"] = 0x2013, + ["mdash"] = 0x2014, + ["lsquo"] = 0x2018, + ["rsquo"] = 0x2019, + ["sbquo"] = 0x201A, + ["ldquo"] = 0x201C, + ["rdquo"] = 0x201D, + ["bdquo"] = 0x201E, + ["dagger"] = 0x2020, + ["Dagger"] = 0x2021, + ["bull"] = 0x2022, + ["hellip"] = 0x2026, + ["permil"] = 0x2030, + ["prime"] = 0x2032, + ["Prime"] = 0x2033, + ["lsaquo"] = 0x2039, + ["rsaquo"] = 0x203A, + ["oline"] = 0x203E, + ["frasl"] = 0x2044, + ["euro"] = 0x20AC, + ["image"] = 0x2111, + ["weierp"] = 0x2118, + ["real"] = 0x211C, + ["trade"] = 0x2122, + ["alefsym"] = 0x2135, + ["larr"] = 0x2190, + ["uarr"] = 0x2191, + ["rarr"] = 0x2192, + ["darr"] = 0x2193, + ["harr"] = 0x2194, + ["crarr"] = 0x21B5, + ["lArr"] = 0x21D0, + ["uArr"] = 0x21D1, + ["rArr"] = 0x21D2, + ["dArr"] = 0x21D3, + ["hArr"] = 0x21D4, + ["forall"] = 0x2200, + ["part"] = 0x2202, + ["exist"] = 0x2203, + ["empty"] = 0x2205, + ["nabla"] = 0x2207, + ["isin"] = 0x2208, + ["notin"] = 0x2209, + ["ni"] = 0x220B, + ["prod"] = 0x220F, + ["sum"] = 0x2211, + ["minus"] = 0x2212, + ["lowast"] = 0x2217, + ["radic"] = 0x221A, + ["prop"] = 0x221D, + ["infin"] = 0x221E, + ["ang"] = 0x2220, + ["and"] = 0x2227, + ["or"] = 0x2228, + ["cap"] = 0x2229, + ["cup"] = 0x222A, + ["int"] = 0x222B, + ["there4"] = 0x2234, + ["sim"] = 0x223C, + ["cong"] = 0x2245, + ["asymp"] = 0x2248, + ["ne"] = 0x2260, + ["equiv"] = 0x2261, + ["le"] = 0x2264, + ["ge"] = 0x2265, + ["sub"] = 0x2282, + ["sup"] = 0x2283, + ["nsub"] = 0x2284, + ["sube"] = 0x2286, + ["supe"] = 0x2287, + ["oplus"] = 0x2295, + ["otimes"] = 0x2297, + ["perp"] = 0x22A5, + ["sdot"] = 0x22C5, + ["lceil"] = 0x2308, + ["rceil"] = 0x2309, + ["lfloor"] = 0x230A, + ["rfloor"] = 0x230B, + ["lang"] = 0x27E8, + ["rang"] = 0x27E9, + ["loz"] = 0x25CA, + ["spades"] = 0x2660, + ["clubs"] = 0x2663, + ["hearts"] = 0x2665, + ["diams"] = 0x2666, +} +function entities.dec_entity(s) + return unicode.utf8.char(tonumber(s)) +end +function entities.hex_entity(s) + return unicode.utf8.char(tonumber("0x"..s)) +end +function entities.char_entity(s) + local n = character_entities[s] + return unicode.utf8.char(n) +end M.writer = {} function M.writer.new(options) local self = {} @@ -335,6 +601,8 @@ function M.writer.new(options) "\n\\markdownRendererOlEnd "} end end + function self.inline_html(html) return "" end + function self.display_html(html) return "" end local function dlitem(term, defs) local retVal = {"\\markdownRendererDlItem{",term,"}"} for _, def in ipairs(defs) do @@ -410,91 +678,432 @@ function M.writer.new(options) return self end -local percent = P("%") -local at = P("@") -local comma = P(",") -local asterisk = P("*") -local dash = P("-") -local plus = P("+") -local underscore = P("_") -local period = P(".") -local hash = P("#") -local ampersand = P("&") -local backtick = P("`") -local less = P("<") -local more = P(">") -local space = P(" ") -local squote = P("'") -local dquote = P('"') -local lparent = P("(") -local rparent = P(")") -local lbracket = P("[") -local rbracket = P("]") -local circumflex = P("^") -local slash = P("/") -local equal = P("=") -local colon = P(":") -local semicolon = P(";") -local exclamation = P("!") -local tilde = P("~") - -local digit = R("09") -local hexdigit = R("09","af","AF") -local letter = R("AZ","az") -local alphanumeric = R("AZ","az","09") -local keyword = letter * alphanumeric^0 -local internal_punctuation = S(":;,.#$%&-+?<>~/") - -local doubleasterisks = P("**") -local doubleunderscores = P("__") -local fourspaces = P(" ") - -local any = P(1) -local fail = any - 1 -local always = P("") - -local escapable = S("\\`*_{}[]()+_.!<>#-~:^@;") - -local anyescaped = P("\\") / "" * escapable - + any - -local tab = P("\t") -local spacechar = S("\t ") -local spacing = S(" \n\r\t") -local newline = P("\n") -local nonspacechar = any - spacing -local tightblocksep = P("\001") - -local specialchar = S("*_`&[]<!\\.@-^") - -local normalchar = any - - (specialchar + spacing + tightblocksep) -local optionalspace = spacechar^0 -local eof = - any -local nonindentspace = space^-3 * - spacechar -local indent = space^-3 * tab - + fourspaces / "" -local linechar = P(1 - newline) - -local blankline = optionalspace * newline / "\n" -local blanklines = blankline^0 -local skipblanklines = (optionalspace * newline)^0 -local indentedline = indent /"" * C(linechar^1 * newline^-1) -local optionallyindentedline = indent^-1 /"" * C(linechar^1 * newline^-1) -local sp = spacing^0 -local spnl = optionalspace * (newline * optionalspace)^-1 -local line = linechar^0 * newline - + linechar^1 * eof -local nonemptyline = line - blankline - -local chunk = line * (optionallyindentedline - blankline)^0 +local parsers = {} +parsers.percent = P("%") +parsers.at = P("@") +parsers.comma = P(",") +parsers.asterisk = P("*") +parsers.dash = P("-") +parsers.plus = P("+") +parsers.underscore = P("_") +parsers.period = P(".") +parsers.hash = P("#") +parsers.ampersand = P("&") +parsers.backtick = P("`") +parsers.less = P("<") +parsers.more = P(">") +parsers.space = P(" ") +parsers.squote = P("'") +parsers.dquote = P('"') +parsers.lparent = P("(") +parsers.rparent = P(")") +parsers.lbracket = P("[") +parsers.rbracket = P("]") +parsers.circumflex = P("^") +parsers.slash = P("/") +parsers.equal = P("=") +parsers.colon = P(":") +parsers.semicolon = P(";") +parsers.exclamation = P("!") +parsers.tilde = P("~") +parsers.tab = P("\t") +parsers.newline = P("\n") +parsers.tightblocksep = P("\001") + +parsers.digit = R("09") +parsers.hexdigit = R("09","af","AF") +parsers.letter = R("AZ","az") +parsers.alphanumeric = R("AZ","az","09") +parsers.keyword = parsers.letter + * parsers.alphanumeric^0 +parsers.internal_punctuation = S(":;,.#$%&-+?<>~/") + +parsers.doubleasterisks = P("**") +parsers.doubleunderscores = P("__") +parsers.fourspaces = P(" ") + +parsers.any = P(1) +parsers.fail = parsers.any - 1 + +parsers.escapable = S("\\`*_{}[]()+_.!<>#-~:^@;") +parsers.anyescaped = P("\\") / "" * parsers.escapable + + parsers.any + +parsers.spacechar = S("\t ") +parsers.spacing = S(" \n\r\t") +parsers.nonspacechar = parsers.any - parsers.spacing +parsers.optionalspace = parsers.spacechar^0 + +parsers.specialchar = S("*_`&[]<!\\.@-^") + +parsers.normalchar = parsers.any - (parsers.specialchar + + parsers.spacing + + parsers.tightblocksep) +parsers.eof = -parsers.any +parsers.nonindentspace = parsers.space^-3 * - parsers.spacechar +parsers.indent = parsers.space^-3 * parsers.tab + + parsers.fourspaces / "" +parsers.linechar = P(1 - parsers.newline) + +parsers.blankline = parsers.optionalspace + * parsers.newline / "\n" +parsers.blanklines = parsers.blankline^0 +parsers.skipblanklines = (parsers.optionalspace * parsers.newline)^0 +parsers.indentedline = parsers.indent /"" + * C(parsers.linechar^1 * parsers.newline^-1) +parsers.optionallyindentedline = parsers.indent^-1 /"" + * C(parsers.linechar^1 * parsers.newline^-1) +parsers.sp = parsers.spacing^0 +parsers.spnl = parsers.optionalspace + * (parsers.newline * parsers.optionalspace)^-1 +parsers.line = parsers.linechar^0 * parsers.newline + + parsers.linechar^1 * parsers.eof +parsers.nonemptyline = parsers.line - parsers.blankline + +parsers.chunk = parsers.line * (parsers.optionallyindentedline + - parsers.blankline)^0 -- block followed by 0 or more optionally -- indented blocks with first line indented. -local function indented_blocks(bl) +parsers.indented_blocks = function(bl) return Cs( bl - * (blankline^1 * indent * -blankline * bl)^0 - * (blankline^1 + eof) ) + * (parsers.blankline^1 * parsers.indent * -parsers.blankline * bl)^0 + * (parsers.blankline^1 + parsers.eof) ) +end +parsers.bulletchar = C(parsers.plus + parsers.asterisk + parsers.dash) + +parsers.bullet = ( parsers.bulletchar * #parsers.spacing + * (parsers.tab + parsers.space^-3) + + parsers.space * parsers.bulletchar * #parsers.spacing + * (parsers.tab + parsers.space^-2) + + parsers.space * parsers.space * parsers.bulletchar + * #parsers.spacing + * (parsers.tab + parsers.space^-1) + + parsers.space * parsers.space * parsers.space + * parsers.bulletchar * #parsers.spacing + ) +parsers.openticks = Cg(parsers.backtick^1, "ticks") + +local function captures_equal_length(s,i,a,b) + return #a == #b and i +end + +parsers.closeticks = parsers.space^-1 + * Cmt(C(parsers.backtick^1) + * Cb("ticks"), captures_equal_length) + +parsers.intickschar = (parsers.any - S(" \n\r`")) + + (parsers.newline * -parsers.blankline) + + (parsers.space - parsers.closeticks) + + (parsers.backtick^1 - parsers.closeticks) + +parsers.inticks = parsers.openticks * parsers.space^-1 + * C(parsers.intickschar^0) * parsers.closeticks +local function captures_geq_length(s,i,a,b) + return #a >= #b and i +end + +parsers.infostring = (parsers.linechar - (parsers.backtick + + parsers.space^1 * (parsers.newline + parsers.eof)))^0 + +local fenceindent +parsers.fencehead = function(char) + return C(parsers.nonindentspace) / function(s) fenceindent = #s end + * Cg(char^3, "fencelength") + * parsers.optionalspace * C(parsers.infostring) + * parsers.optionalspace * (parsers.newline + parsers.eof) +end + +parsers.fencetail = function(char) + return parsers.nonindentspace + * Cmt(C(char^3) * Cb("fencelength"), captures_geq_length) + * parsers.optionalspace * (parsers.newline + parsers.eof) + + parsers.eof +end + +parsers.fencedline = function(char) + return C(parsers.line - parsers.fencetail(char)) + / function(s) + return s:gsub("^" .. string.rep(" ?", fenceindent), "") + end +end +parsers.leader = parsers.space^-3 + +-- in balanced brackets, parentheses, quotes: +parsers.bracketed = P{ parsers.lbracket + * ((parsers.anyescaped - (parsers.lbracket + + parsers.rbracket + + parsers.blankline^2) + ) + V(1))^0 + * parsers.rbracket } + +parsers.inparens = P{ parsers.lparent + * ((parsers.anyescaped - (parsers.lparent + + parsers.rparent + + parsers.blankline^2) + ) + V(1))^0 + * parsers.rparent } + +parsers.squoted = P{ parsers.squote * parsers.alphanumeric + * ((parsers.anyescaped - (parsers.squote + + parsers.blankline^2) + ) + V(1))^0 + * parsers.squote } + +parsers.dquoted = P{ parsers.dquote * parsers.alphanumeric + * ((parsers.anyescaped - (parsers.dquote + + parsers.blankline^2) + ) + V(1))^0 + * parsers.dquote } + +-- bracketed 'tag' for markdown links, allowing nested brackets: +parsers.tag = parsers.lbracket + * Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped - (parsers.rbracket + + parsers.blankline^2)))^0) + * parsers.rbracket + +-- url for markdown links, allowing balanced parentheses: +parsers.url = parsers.less * Cs((parsers.anyescaped-parsers.more)^0) + * parsers.more + + Cs((parsers.inparens + (parsers.anyescaped + -parsers.spacing-parsers.rparent))^1) + +-- quoted text possibly with nested quotes: +parsers.title_s = parsers.squote * Cs(((parsers.anyescaped-parsers.squote) + + parsers.squoted)^0) + * parsers.squote + +parsers.title_d = parsers.dquote * Cs(((parsers.anyescaped-parsers.dquote) + + parsers.dquoted)^0) + * parsers.dquote + +parsers.title_p = parsers.lparent + * Cs((parsers.inparens + (parsers.anyescaped-parsers.rparent))^0) + * parsers.rparent + +parsers.title = parsers.title_d + parsers.title_s + parsers.title_p + +parsers.optionaltitle + = parsers.spnl * parsers.title * parsers.spacechar^0 + + Cc("") +parsers.citation_name = Cs(parsers.dash^-1) * parsers.at + * Cs(parsers.alphanumeric + * (parsers.alphanumeric + parsers.internal_punctuation + - parsers.comma - parsers.semicolon)^0) + +parsers.citation_body_prenote + = Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped + - (parsers.rbracket + parsers.blankline^2)) + - (parsers.spnl * parsers.dash^-1 * parsers.at))^0) + +parsers.citation_body_postnote + = Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped + - (parsers.rbracket + parsers.semicolon + + parsers.blankline^2)) + - (parsers.spnl * parsers.rbracket))^0) + +parsers.citation_body_chunk + = parsers.citation_body_prenote + * parsers.spnl * parsers.citation_name + * (parsers.comma * parsers.spnl)^-1 + * parsers.citation_body_postnote + +parsers.citation_body + = parsers.citation_body_chunk + * (parsers.semicolon * parsers.spnl + * parsers.citation_body_chunk)^0 + +parsers.citation_headless_body_postnote + = Cs((parsers.alphanumeric^1 + + parsers.bracketed + + parsers.inticks + + (parsers.anyescaped + - (parsers.rbracket + parsers.at + + parsers.semicolon + parsers.blankline^2)) + - (parsers.spnl * parsers.rbracket))^0) + +parsers.citation_headless_body + = parsers.citation_headless_body_postnote + * (parsers.sp * parsers.semicolon * parsers.spnl + * parsers.citation_body_chunk)^0 +local function strip_first_char(s) + return s:sub(2) +end + +parsers.RawNoteRef = #(parsers.lbracket * parsers.circumflex) + * parsers.tag / strip_first_char +-- case-insensitive match (we assume s is lowercase). must be single byte encoding +parsers.keyword_exact = function(s) + local parser = P(0) + for i=1,#s do + local c = s:sub(i,i) + local m = c .. upper(c) + parser = parser * S(m) + end + return parser +end + +parsers.block_keyword = + parsers.keyword_exact("address") + parsers.keyword_exact("blockquote") + + parsers.keyword_exact("center") + parsers.keyword_exact("del") + + parsers.keyword_exact("dir") + parsers.keyword_exact("div") + + parsers.keyword_exact("p") + parsers.keyword_exact("pre") + + parsers.keyword_exact("li") + parsers.keyword_exact("ol") + + parsers.keyword_exact("ul") + parsers.keyword_exact("dl") + + parsers.keyword_exact("dd") + parsers.keyword_exact("form") + + parsers.keyword_exact("fieldset") + parsers.keyword_exact("isindex") + + parsers.keyword_exact("ins") + parsers.keyword_exact("menu") + + parsers.keyword_exact("noframes") + parsers.keyword_exact("frameset") + + parsers.keyword_exact("h1") + parsers.keyword_exact("h2") + + parsers.keyword_exact("h3") + parsers.keyword_exact("h4") + + parsers.keyword_exact("h5") + parsers.keyword_exact("h6") + + parsers.keyword_exact("hr") + parsers.keyword_exact("script") + + parsers.keyword_exact("noscript") + parsers.keyword_exact("table") + + parsers.keyword_exact("tbody") + parsers.keyword_exact("tfoot") + + parsers.keyword_exact("thead") + parsers.keyword_exact("th") + + parsers.keyword_exact("td") + parsers.keyword_exact("tr") + +-- There is no reason to support bad html, so we expect quoted attributes +parsers.htmlattributevalue + = parsers.squote * (parsers.any - (parsers.blankline + + parsers.squote))^0 + * parsers.squote + + parsers.dquote * (parsers.any - (parsers.blankline + + parsers.dquote))^0 + * parsers.dquote + +parsers.htmlattribute = parsers.spacing^1 + * (parsers.alphanumeric + S("_-"))^1 + * parsers.sp * parsers.equal * parsers.sp + * parsers.htmlattributevalue + +parsers.htmlcomment = P("<!--") * (parsers.any - P("-->"))^0 * P("-->") + +parsers.htmlinstruction = P("<?") * (parsers.any - P("?>" ))^0 * P("?>" ) + +parsers.openelt_any = parsers.less * parsers.keyword * parsers.htmlattribute^0 + * parsers.sp * parsers.more + +parsers.openelt_exact = function(s) + return parsers.less * parsers.sp * parsers.keyword_exact(s) + * parsers.htmlattribute^0 * parsers.sp * parsers.more +end + +parsers.openelt_block = parsers.sp * parsers.block_keyword + * parsers.htmlattribute^0 * parsers.sp * parsers.more + +parsers.closeelt_any = parsers.less * parsers.sp * parsers.slash + * parsers.keyword * parsers.sp * parsers.more + +parsers.closeelt_exact = function(s) + return parsers.less * parsers.sp * parsers.slash * parsers.keyword_exact(s) + * parsers.sp * parsers.more +end + +parsers.emptyelt_any = parsers.less * parsers.sp * parsers.keyword + * parsers.htmlattribute^0 * parsers.sp * parsers.slash + * parsers.more + +parsers.emptyelt_block = parsers.less * parsers.sp * parsers.block_keyword + * parsers.htmlattribute^0 * parsers.sp * parsers.slash + * parsers.more + +parsers.displaytext = (parsers.any - parsers.less)^1 + +-- return content between two matched HTML tags +parsers.in_matched = function(s) + return { parsers.openelt_exact(s) + * (V(1) + parsers.displaytext + + (parsers.less - parsers.closeelt_exact(s)))^0 + * parsers.closeelt_exact(s) } +end + +local function parse_matched_tags(s,pos) + local t = string.lower(lpeg.match(C(parsers.keyword),s,pos)) + return lpeg.match(parsers.in_matched(t),s,pos-1) +end + +parsers.in_matched_block_tags = parsers.less + * Cmt(#parsers.openelt_block, parse_matched_tags) + +parsers.displayhtml = parsers.htmlcomment + + parsers.emptyelt_block + + parsers.openelt_exact("hr") + + parsers.in_matched_block_tags + + parsers.htmlinstruction + +parsers.inlinehtml = parsers.emptyelt_any + + parsers.htmlcomment + + parsers.htmlinstruction + + parsers.openelt_any + + parsers.closeelt_any +parsers.hexentity = parsers.ampersand * parsers.hash * S("Xx") + * C(parsers.hexdigit^1) * parsers.semicolon +parsers.decentity = parsers.ampersand * parsers.hash + * C(parsers.digit^1) * parsers.semicolon +parsers.tagentity = parsers.ampersand * C(parsers.alphanumeric^1) + * parsers.semicolon +-- parse a reference definition: [foo]: /bar "title" +parsers.define_reference_parser = parsers.leader * parsers.tag * parsers.colon + * parsers.spacechar^0 * parsers.url + * parsers.optionaltitle * parsers.blankline^1 +parsers.Inline = V("Inline") + +-- parse many p between starter and ender +parsers.between = function(p, starter, ender) + local ender2 = B(parsers.nonspacechar) * ender + return (starter * #parsers.nonspacechar * Ct(p * (p - ender2)^0) * ender2) +end + +parsers.urlchar = parsers.anyescaped - parsers.newline - parsers.more +parsers.Block = V("Block") + +parsers.TildeFencedCode + = parsers.fencehead(parsers.tilde) + * Cs(parsers.fencedline(parsers.tilde)^0) + * parsers.fencetail(parsers.tilde) + +parsers.BacktickFencedCode + = parsers.fencehead(parsers.backtick) + * Cs(parsers.fencedline(parsers.backtick)^0) + * parsers.fencetail(parsers.backtick) + +parsers.lineof = function(c) + return (parsers.leader * (P(c) * parsers.optionalspace)^3 + * (parsers.newline * parsers.blankline^1 + + parsers.newline^-1 * parsers.eof)) +end +parsers.defstartchar = S("~:") +parsers.defstart = ( parsers.defstartchar * #parsers.spacing + * (parsers.tab + parsers.space^-3) + + parsers.space * parsers.defstartchar * #parsers.spacing + * (parsers.tab + parsers.space^-2) + + parsers.space * parsers.space * parsers.defstartchar + * #parsers.spacing + * (parsers.tab + parsers.space^-1) + + parsers.space * parsers.space * parsers.space + * parsers.defstartchar * #parsers.spacing + ) + +parsers.dlchunk = Cs(parsers.line * (parsers.indentedline - parsers.blankline)^0) +-- parse Atx heading start and return level +parsers.HeadingStart = #parsers.hash * C(parsers.hash^-6) + * -parsers.hash / length + +-- parse setext header ending and return level +parsers.HeadingLevel = parsers.equal^1 * Cc(1) + parsers.dash^1 * Cc(2) + +local function strip_atx_end(s) + return s:gsub("[#%s]*\n$","") end M.reader = {} function M.reader.new(writer, options) @@ -518,14 +1127,7 @@ function M.reader.new(writer, options) end end end - local syntax - local blocks_toplevel - local blocks - local inlines - local inlines_no_link - local inlines_no_inline_note - local inlines_nbsp - + local larsers = {} local function create_parser(name, grammar) return function(str) local res = lpeg.match(grammar(), str) @@ -537,180 +1139,94 @@ function M.reader.new(writer, options) end end - local parse_blocks = create_parser("parse_blocks", - function() return blocks end) - local parse_blocks_toplevel = create_parser("parse_blocks_toplevel", - function() return blocks_toplevel end) - local parse_inlines = create_parser("parse_inlines", - function() return inlines end) - local parse_inlines_no_link = create_parser("parse_inlines_no_link", - function() return inlines_no_link end) - local parse_inlines_no_inline_note = create_parser( - "parse_inlines_no_inline_note", - function() return inlines_no_inline_note end) - local parse_inlines_nbsp = create_parser("parse_inlines_nbsp", - function() return inlines_nbsp end) - local bulletchar = C(plus + asterisk + dash) - - local bullet = ( Cg(bulletchar, "bulletchar") * #spacing * (tab + space^-3) - + space * Cg(bulletchar, "bulletchar") * #spacing * (tab + space^-2) - + space * space * Cg(bulletchar, "bulletchar") * #spacing * (tab + space^-1) - + space * space * space * Cg(bulletchar, "bulletchar") * #spacing - ) - + local parse_blocks + = create_parser("parse_blocks", + function() + return larsers.blocks + end) + + local parse_blocks_toplevel + = create_parser("parse_blocks_toplevel", + function() + return larsers.blocks_toplevel + end) + + local parse_inlines + = create_parser("parse_inlines", + function() + return larsers.inlines + end) + + local parse_inlines_no_link + = create_parser("parse_inlines_no_link", + function() + return larsers.inlines_no_link + end) + + local parse_inlines_no_inline_note + = create_parser("parse_inlines_no_inline_note", + function() + return larsers.inlines_no_inline_note + end) + + local parse_inlines_nbsp + = create_parser("parse_inlines_nbsp", + function() + return larsers.inlines_nbsp + end) if options.hashEnumerators then - dig = digit + hash + larsers.dig = parsers.digit + parsers.hash else - dig = digit - end - - local enumerator = C(dig^3 * period) * #spacing - + C(dig^2 * period) * #spacing * (tab + space^1) - + C(dig * period) * #spacing * (tab + space^-2) - + space * C(dig^2 * period) * #spacing - + space * C(dig * period) * #spacing * (tab + space^-1) - + space * space * C(dig^1 * period) * #spacing - local openticks = Cg(backtick^1, "ticks") - - local function captures_equal_length(s,i,a,b) - return #a == #b and i - end - - local closeticks = space^-1 * - Cmt(C(backtick^1) * Cb("ticks"), captures_equal_length) - - local intickschar = (any - S(" \n\r`")) - + (newline * -blankline) - + (space - closeticks) - + (backtick^1 - closeticks) - - local inticks = openticks * space^-1 * C(intickschar^0) * closeticks - local function captures_geq_length(s,i,a,b) - return #a >= #b and i - end - - local infostring = (linechar - (backtick + space^1 * (newline + eof)))^0 - - local fenceindent - local function fencehead(char) - return C(nonindentspace) / function(s) fenceindent = #s end - * Cg(char^3, "fencelength") - * optionalspace * C(infostring) * optionalspace - * (newline + eof) - end + larsers.dig = parsers.digit + end + + larsers.enumerator = C(larsers.dig^3 * parsers.period) * #parsers.spacing + + C(larsers.dig^2 * parsers.period) * #parsers.spacing + * (parsers.tab + parsers.space^1) + + C(larsers.dig * parsers.period) * #parsers.spacing + * (parsers.tab + parsers.space^-2) + + parsers.space * C(larsers.dig^2 * parsers.period) + * #parsers.spacing + + parsers.space * C(larsers.dig * parsers.period) + * #parsers.spacing + * (parsers.tab + parsers.space^-1) + + parsers.space * parsers.space * C(larsers.dig^1 + * parsers.period) * #parsers.spacing + -- strip off leading > and indents, and run through blocks + larsers.blockquote_body = ((parsers.leader * parsers.more * parsers.space^-1)/"" + * parsers.linechar^0 * parsers.newline)^1 + * (-(parsers.leader * parsers.more + + parsers.blankline) * parsers.linechar^1 + * parsers.newline)^0 - local function fencetail(char) - return nonindentspace - * Cmt(C(char^3) * Cb("fencelength"), - captures_geq_length) - * optionalspace * (newline + eof) - + eof + if not options.breakableBlockquotes then + larsers.blockquote_body = larsers.blockquote_body + * (parsers.blankline^0 / "") end + larsers.citations = function(text_cites, raw_cites) + local function normalize(str) + if str == "" then + str = nil + else + str = (options.citationNbsps and parse_inlines_nbsp or + parse_inlines)(str) + end + return str + end - local function fencedline(char) - return C(line - fencetail(char)) - / function(s) - return s:gsub("^" .. string.rep(" ?", - fenceindent), "") - end + local cites = {} + for i = 1,#raw_cites,4 do + cites[#cites+1] = { + prenote = normalize(raw_cites[i]), + suppress_author = raw_cites[i+1] == "-", + name = writer.string(raw_cites[i+2]), + postnote = normalize(raw_cites[i+3]), + } + end + return writer.citations(text_cites, cites) end - local leader = space^-3 - - -- in balanced brackets, parentheses, quotes: - local bracketed = P{ lbracket - * ((anyescaped - (lbracket + rbracket - + blankline^2)) + V(1))^0 - * rbracket } - - local inparens = P{ lparent - * ((anyescaped - (lparent + rparent - + blankline^2)) + V(1))^0 - * rparent } - - local squoted = P{ squote * alphanumeric - * ((anyescaped - (squote + blankline^2)) - + V(1))^0 - * squote } - - local dquoted = P{ dquote * alphanumeric - * ((anyescaped - (dquote + blankline^2)) - + V(1))^0 - * dquote } - - -- bracketed 'tag' for markdown links, allowing nested brackets: - local tag = lbracket - * Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - (rbracket + blankline^2)))^0) - * rbracket - - -- url for markdown links, allowing balanced parentheses: - local url = less * Cs((anyescaped-more)^0) * more - + Cs((inparens + (anyescaped-spacing-rparent))^1) - - -- quoted text possibly with nested quotes: - local title_s = squote * Cs(((anyescaped-squote) + squoted)^0) * - squote - - local title_d = dquote * Cs(((anyescaped-dquote) + dquoted)^0) * - dquote - - local title_p = lparent - * Cs((inparens + (anyescaped-rparent))^0) - * rparent - - local title = title_d + title_s + title_p - - local optionaltitle = spnl * title * spacechar^0 - + Cc("") - local citation_name = Cs(dash^-1) * at - * Cs(alphanumeric - * (alphanumeric + internal_punctuation - - comma - semicolon)^0) - - local citation_body_prenote - = Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - - (rbracket + blankline^2)) - - (spnl * dash^-1 * at))^0) - - local citation_body_postnote - = Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - - (rbracket + semicolon + blankline^2)) - - (spnl * rbracket))^0) - - local citation_body_chunk - = citation_body_prenote - * spnl * citation_name - * (comma * spnl)^-1 - * citation_body_postnote - - local citation_body = citation_body_chunk - * (semicolon * spnl * citation_body_chunk)^0 - - local citation_headless_body_postnote - = Cs((alphanumeric^1 - + bracketed - + inticks - + (anyescaped - - (rbracket + at + semicolon + blankline^2)) - - (spnl * rbracket))^0) - - local citation_headless_body - = citation_headless_body_postnote - * (sp * semicolon * spnl * citation_body_chunk)^0 local rawnotes = {} - local function strip_first_char(s) - return s:sub(2) - end - -- like indirect_link local function lookup_note(ref) return function() @@ -728,16 +1244,15 @@ function M.reader.new(writer, options) return "" end - local RawNoteRef = #(lbracket * circumflex) * tag / strip_first_char + larsers.NoteRef = parsers.RawNoteRef / lookup_note - local NoteRef = RawNoteRef / lookup_note + larsers.NoteBlock = parsers.leader * parsers.RawNoteRef * parsers.colon + * parsers.spnl * parsers.indented_blocks(parsers.chunk) + / register_note - local NoteBlock = leader * RawNoteRef * colon * spnl - * indented_blocks(chunk) / register_note - - local InlineNote = circumflex -- no notes inside notes - * (tag / parse_inlines_no_inline_note) - / writer.note + larsers.InlineNote = parsers.circumflex + * (parsers.tag / parse_inlines_no_inline_note) -- no notes inside notes + / writer.note -- List of references defined in the document local references @@ -747,10 +1262,6 @@ function M.reader.new(writer, options) return "" end - -- parse a reference definition: [foo]: /bar "title" - local define_reference_parser = - leader * tag * colon * spacechar^0 * url * optionaltitle * blankline^1 - -- lookup link reference and return either -- the link or nil and fallback text. local function lookup_reference(label,sps,tag) @@ -800,232 +1311,239 @@ function M.reader.new(writer, options) end end end - local bqstart = more - local headerstart = hash - + (line * (equal^1 + dash^1) * optionalspace * newline) - local fencestart = fencehead(backtick) + fencehead(tilde) + larsers.Str = parsers.normalchar^1 / writer.string + + larsers.Symbol = (parsers.specialchar - parsers.tightblocksep) + / writer.string + + larsers.Ellipsis = P("...") / writer.ellipsis + + larsers.Smart = larsers.Ellipsis + + larsers.Code = parsers.inticks / writer.code if options.blankBeforeBlockquote then - bqstart = fail + larsers.bqstart = parsers.fail + else + larsers.bqstart = parsers.more end if options.blankBeforeHeading then - headerstart = fail + larsers.headerstart = parsers.fail + else + larsers.headerstart = parsers.hash + + (parsers.line * (parsers.equal^1 + parsers.dash^1) + * parsers.optionalspace * parsers.newline) end if not options.fencedCode or options.blankBeforeCodeFence then - fencestart = fail - end - local Inline = V("Inline") - - local Str = normalchar^1 / writer.string - - local Symbol = (specialchar - tightblocksep) / writer.string - local Ellipsis = P("...") / writer.ellipsis - - local Smart = Ellipsis - local Code = inticks / writer.code - local Endline = newline * -( -- newline, but not before... - blankline -- paragraph break - + tightblocksep -- nested list - + eof -- end of document - + bqstart - + headerstart - + fencestart - ) * spacechar^0 / writer.space - - local Space = spacechar^2 * Endline / writer.linebreak - + spacechar^1 * Endline^-1 * eof / "" - + spacechar^1 * Endline^-1 * optionalspace / writer.space - - local NonbreakingEndline - = newline * -( -- newline, but not before... - blankline -- paragraph break - + tightblocksep -- nested list - + eof -- end of document - + bqstart - + headerstart - + fencestart - ) * spacechar^0 / writer.nbsp - - local NonbreakingSpace - = spacechar^2 * Endline / writer.linebreak - + spacechar^1 * Endline^-1 * eof / "" - + spacechar^1 * Endline^-1 * optionalspace / writer.nbsp - - -- parse many p between starter and ender - local function between(p, starter, ender) - local ender2 = B(nonspacechar) * ender - return (starter * #nonspacechar * Ct(p * (p - ender2)^0) * ender2) - end - local Strong = ( between(Inline, doubleasterisks, doubleasterisks) - + between(Inline, doubleunderscores, doubleunderscores) - ) / writer.strong - - local Emph = ( between(Inline, asterisk, asterisk) - + between(Inline, underscore, underscore) - ) / writer.emphasis - local urlchar = anyescaped - newline - more - - local AutoLinkUrl = less - * C(alphanumeric^1 * P("://") * urlchar^1) - * more - / function(url) - return writer.link(writer.string(url), url) - end - - local AutoLinkEmail = less - * C((alphanumeric + S("-._+"))^1 * P("@") * urlchar^1) - * more - / function(email) - return writer.link(writer.string(email), - "mailto:"..email) - end - - local DirectLink = (tag / parse_inlines_no_link) -- no links inside links - * spnl - * lparent - * (url + Cc("")) -- link can be empty [foo]() - * optionaltitle - * rparent - / writer.link - - local IndirectLink = tag * (C(spnl) * tag)^-1 / indirect_link + larsers.fencestart = parsers.fail + else + larsers.fencestart = parsers.fencehead(parsers.backtick) + + parsers.fencehead(parsers.tilde) + end + + larsers.Endline = parsers.newline * -( -- newline, but not before... + parsers.blankline -- paragraph break + + parsers.tightblocksep -- nested list + + parsers.eof -- end of document + + larsers.bqstart + + larsers.headerstart + + larsers.fencestart + ) * parsers.spacechar^0 / writer.space + + larsers.Space = parsers.spacechar^2 * larsers.Endline / writer.linebreak + + parsers.spacechar^1 * larsers.Endline^-1 * parsers.eof / "" + + parsers.spacechar^1 * larsers.Endline^-1 + * parsers.optionalspace / writer.space + + larsers.NonbreakingEndline + = parsers.newline * -( -- newline, but not before... + parsers.blankline -- paragraph break + + parsers.tightblocksep -- nested list + + parsers.eof -- end of document + + larsers.bqstart + + larsers.headerstart + + larsers.fencestart + ) * parsers.spacechar^0 / writer.nbsp + + larsers.NonbreakingSpace + = parsers.spacechar^2 * larsers.Endline / writer.linebreak + + parsers.spacechar^1 * larsers.Endline^-1 * parsers.eof / "" + + parsers.spacechar^1 * larsers.Endline^-1 + * parsers.optionalspace / writer.nbsp + + larsers.Strong = ( parsers.between(parsers.Inline, parsers.doubleasterisks, + parsers.doubleasterisks) + + parsers.between(parsers.Inline, parsers.doubleunderscores, + parsers.doubleunderscores) + ) / writer.strong + + larsers.Emph = ( parsers.between(parsers.Inline, parsers.asterisk, + parsers.asterisk) + + parsers.between(parsers.Inline, parsers.underscore, + parsers.underscore) + ) / writer.emphasis + + larsers.AutoLinkUrl = parsers.less + * C(parsers.alphanumeric^1 * P("://") * parsers.urlchar^1) + * parsers.more + / function(url) + return writer.link(writer.string(url), url) + end + + larsers.AutoLinkEmail = parsers.less + * C((parsers.alphanumeric + S("-._+"))^1 + * P("@") * parsers.urlchar^1) + * parsers.more + / function(email) + return writer.link(writer.string(email), + "mailto:"..email) + end + + larsers.DirectLink = (parsers.tag / parse_inlines_no_link) -- no links inside links + * parsers.spnl + * parsers.lparent + * (parsers.url + Cc("")) -- link can be empty [foo]() + * parsers.optionaltitle + * parsers.rparent + / writer.link + + larsers.IndirectLink = parsers.tag * (C(parsers.spnl) * parsers.tag)^-1 + / indirect_link -- parse a link or image (direct or indirect) - local Link = DirectLink + IndirectLink - local DirectImage = exclamation - * (tag / parse_inlines) - * spnl - * lparent - * (url + Cc("")) -- link can be empty [foo]() - * optionaltitle - * rparent - / writer.image - - local IndirectImage = exclamation * tag * (C(spnl) * tag)^-1 / - indirect_image - - local Image = DirectImage + IndirectImage - -- avoid parsing long strings of * or _ as emph/strong - local UlOrStarLine = asterisk^4 + underscore^4 / writer.string + larsers.Link = larsers.DirectLink + larsers.IndirectLink + + larsers.DirectImage = parsers.exclamation + * (parsers.tag / parse_inlines) + * parsers.spnl + * parsers.lparent + * (parsers.url + Cc("")) -- link can be empty [foo]() + * parsers.optionaltitle + * parsers.rparent + / writer.image + + larsers.IndirectImage = parsers.exclamation * parsers.tag + * (C(parsers.spnl) * parsers.tag)^-1 / indirect_image + + larsers.Image = larsers.DirectImage + larsers.IndirectImage + + larsers.TextCitations = Ct(Cc("") + * parsers.citation_name + * ((parsers.spnl + * parsers.lbracket + * parsers.citation_headless_body + * parsers.rbracket) + Cc(""))) + / function(raw_cites) + return larsers.citations(true, raw_cites) + end + + larsers.ParenthesizedCitations + = Ct(parsers.lbracket + * parsers.citation_body + * parsers.rbracket) + / function(raw_cites) + return larsers.citations(false, raw_cites) + end + + larsers.Citations = larsers.TextCitations + larsers.ParenthesizedCitations - local EscapedChar = S("\\") * C(escapable) / writer.string - local function citations(text_cites, raw_cites) - local function normalize(str) - if str == "" then - str = nil - else - str = (options.citationNbsps and parse_inlines_nbsp or - parse_inlines)(str) - end - return str - end + -- avoid parsing long strings of * or _ as emph/strong + larsers.UlOrStarLine = parsers.asterisk^4 + parsers.underscore^4 + / writer.string - local cites = {} - for i = 1,#raw_cites,4 do - cites[#cites+1] = { - prenote = normalize(raw_cites[i]), - suppress_author = raw_cites[i+1] == "-", - name = writer.string(raw_cites[i+2]), - postnote = normalize(raw_cites[i+3]), - } - end - return writer.citations(text_cites, cites) - end + larsers.EscapedChar = S("\\") * C(parsers.escapable) / writer.string - local TextCitations = Ct(Cc("") - * citation_name - * ((spnl - * lbracket - * citation_headless_body - * rbracket) + Cc(""))) / - function(raw_cites) - return citations(true, raw_cites) - end - - local ParenthesizedCitations - = Ct(lbracket - * citation_body - * rbracket) / - function(raw_cites) - return citations(false, raw_cites) - end - - local Citations = TextCitations + ParenthesizedCitations - local Block = V("Block") - - local Verbatim = Cs( (blanklines - * ((indentedline - blankline))^1)^1 - ) / expandtabs / writer.verbatim + larsers.InlineHtml = C(parsers.inlinehtml) / writer.inline_html - local TildeFencedCode - = fencehead(tilde) - * Cs(fencedline(tilde)^0) - * fencetail(tilde) + larsers.HtmlEntity = parsers.hexentity / entities.hex_entity / writer.string + + parsers.decentity / entities.dec_entity / writer.string + + parsers.tagentity / entities.char_entity / writer.string + larsers.DisplayHtml = C(parsers.displayhtml) + / expandtabs / writer.display_html - local BacktickFencedCode - = fencehead(backtick) - * Cs(fencedline(backtick)^0) - * fencetail(backtick) + larsers.Verbatim = Cs( (parsers.blanklines + * ((parsers.indentedline - parsers.blankline))^1)^1 + ) / expandtabs / writer.verbatim - local FencedCode = (TildeFencedCode + BacktickFencedCode) + larsers.FencedCode = (parsers.TildeFencedCode + + parsers.BacktickFencedCode) / function(infostring, code) - return writer.fencedCode( - writer.string(infostring), - expandtabs(code)) + return writer.fencedCode(writer.string(infostring), + expandtabs(code)) end - -- strip off leading > and indents, and run through blocks - local Blockquote = Cs(( - ((leader * more * space^-1)/"" * linechar^0 * newline)^1 - * (-blankline * linechar^1 * newline)^0 - * (blankline^0 / "") - )^1) / parse_blocks_toplevel / writer.blockquote - - local function lineof(c) - return (leader * (P(c) * optionalspace)^3 * (newline * blankline^1 - + newline^-1 * eof)) - end - local HorizontalRule = ( lineof(asterisk) - + lineof(dash) - + lineof(underscore) - ) / writer.hrule - local starter = bullet + enumerator + + larsers.Blockquote = Cs(larsers.blockquote_body^1) + / parse_blocks_toplevel / writer.blockquote + + larsers.HorizontalRule = ( parsers.lineof(parsers.asterisk) + + parsers.lineof(parsers.dash) + + parsers.lineof(parsers.underscore) + ) / writer.hrule + + larsers.Reference = parsers.define_reference_parser / register_link + + larsers.Paragraph = parsers.nonindentspace * Ct(parsers.Inline^1) + * parsers.newline + * ( parsers.blankline^1 + + #parsers.hash + + #(parsers.leader * parsers.more * parsers.space^-1) + ) + / writer.paragraph + + larsers.ToplevelParagraph + = parsers.nonindentspace * Ct(parsers.Inline^1) + * ( parsers.newline + * ( parsers.blankline^1 + + #parsers.hash + + #(parsers.leader * parsers.more * parsers.space^-1) + + parsers.eof + ) + + parsers.eof ) + / writer.paragraph + + larsers.Plain = parsers.nonindentspace * Ct(parsers.Inline^1) + / writer.plain + larsers.starter = parsers.bullet + larsers.enumerator -- we use \001 as a separator between a tight list item and a -- nested list under it. - local NestedList = Cs((optionallyindentedline - starter)^1) - / function(a) return "\001"..a end + larsers.NestedList = Cs((parsers.optionallyindentedline + - larsers.starter)^1) + / function(a) return "\001"..a end - local ListBlockLine = optionallyindentedline - - blankline - (indent^-1 * starter) + larsers.ListBlockLine = parsers.optionallyindentedline + - parsers.blankline - (parsers.indent^-1 + * larsers.starter) - local ListBlock = line * ListBlockLine^0 + larsers.ListBlock = parsers.line * larsers.ListBlockLine^0 - local ListContinuationBlock = blanklines * (indent / "") * ListBlock + larsers.ListContinuationBlock = parsers.blanklines * (parsers.indent / "") + * larsers.ListBlock - local function TightListItem(starter) - return -HorizontalRule - * (Cs(starter / "" * ListBlock * NestedList^-1) / - parse_blocks) - * -(blanklines * indent) + larsers.TightListItem = function(starter) + return -larsers.HorizontalRule + * (Cs(starter / "" * larsers.ListBlock * larsers.NestedList^-1) + / parse_blocks) + * -(parsers.blanklines * parsers.indent) end - local function LooseListItem(starter) - return -HorizontalRule - * Cs( starter / "" * ListBlock * Cc("\n") - * (NestedList + ListContinuationBlock^0) - * (blanklines / "\n\n") + larsers.LooseListItem = function(starter) + return -larsers.HorizontalRule + * Cs( starter / "" * larsers.ListBlock * Cc("\n") + * (larsers.NestedList + larsers.ListContinuationBlock^0) + * (parsers.blanklines / "\n\n") ) / parse_blocks end - local BulletList = ( Ct(TightListItem(bullet)^1) - * Cc(true) * skipblanklines * -bullet - + Ct(LooseListItem(bullet)^1) - * Cc(false) * skipblanklines ) / - writer.bulletlist + larsers.BulletList = ( Ct(larsers.TightListItem(parsers.bullet)^1) * Cc(true) + * parsers.skipblanklines * -parsers.bullet + + Ct(larsers.LooseListItem(parsers.bullet)^1) * Cc(false) + * parsers.skipblanklines ) + / writer.bulletlist - local function orderedlist(items,tight,startNumber) + local function ordered_list(items,tight,startNumber) if options.startNumber then startNumber = tonumber(startNumber) or 1 -- fallback for '#' else @@ -1034,107 +1552,65 @@ function M.reader.new(writer, options) return writer.orderedlist(items,tight,startNumber) end - local OrderedList = Cg(enumerator, "listtype") * - ( Ct(TightListItem(Cb("listtype")) * - TightListItem(enumerator)^0) - * Cc(true) * skipblanklines * -enumerator - + Ct(LooseListItem(Cb("listtype")) * - LooseListItem(enumerator)^0) - * Cc(false) * skipblanklines - ) * Cb("listtype") / orderedlist - - local defstartchar = S("~:") - local defstart = ( defstartchar * #spacing * (tab + space^-3) - + space * defstartchar * #spacing * (tab + space^-2) - + space * space * defstartchar * #spacing * - (tab + space^-1) - + space * space * space * defstartchar * #spacing - ) - - local dlchunk = Cs(line * (indentedline - blankline)^0) + larsers.OrderedList = Cg(larsers.enumerator, "listtype") * + ( Ct(larsers.TightListItem(Cb("listtype")) + * larsers.TightListItem(larsers.enumerator)^0) + * Cc(true) * parsers.skipblanklines * -larsers.enumerator + + Ct(larsers.LooseListItem(Cb("listtype")) + * larsers.LooseListItem(larsers.enumerator)^0) + * Cc(false) * parsers.skipblanklines + ) * Cb("listtype") / ordered_list local function definition_list_item(term, defs, tight) return { term = parse_inlines(term), definitions = defs } end - local DefinitionListItemLoose = C(line) * skipblanklines - * Ct((defstart * - indented_blocks(dlchunk) / - parse_blocks_toplevel)^1) - * Cc(false) - / definition_list_item - - local DefinitionListItemTight = C(line) - * Ct((defstart * dlchunk / - parse_blocks)^1) - * Cc(true) - / definition_list_item - - local DefinitionList = ( Ct(DefinitionListItemLoose^1) * Cc(false) - + Ct(DefinitionListItemTight^1) - * (skipblanklines * - -DefinitionListItemLoose * Cc(true)) - ) / writer.definitionlist - local Reference = define_reference_parser / register_link - local Blank = blankline / "" - + NoteBlock - + Reference - + (tightblocksep / "\n") - local Paragraph = nonindentspace * Ct(Inline^1) * newline - * ( blankline^1 - + #hash - + #(leader * more * space^-1) - ) - / writer.paragraph - - local ToplevelParagraph - = nonindentspace * Ct(Inline^1) * (newline - * ( blankline^1 - + #hash - + #(leader * more * space^-1) - + eof - ) - + eof ) - / writer.paragraph - - local Plain = nonindentspace * Ct(Inline^1) / writer.plain - -- parse Atx heading start and return level - local HeadingStart = #hash * C(hash^-6) * -hash / length - - -- parse setext header ending and return level - local HeadingLevel = equal^1 * Cc(1) + dash^1 * Cc(2) - - local function strip_atx_end(s) - return s:gsub("[#%s]*\n$","") - end - + larsers.DefinitionListItemLoose = C(parsers.line) * parsers.skipblanklines + * Ct((parsers.defstart + * parsers.indented_blocks(parsers.dlchunk) + / parse_blocks_toplevel)^1) + * Cc(false) / definition_list_item + + larsers.DefinitionListItemTight = C(parsers.line) + * Ct((parsers.defstart * parsers.dlchunk + / parse_blocks)^1) + * Cc(true) / definition_list_item + + larsers.DefinitionList = ( Ct(larsers.DefinitionListItemLoose^1) * Cc(false) + + Ct(larsers.DefinitionListItemTight^1) + * (parsers.skipblanklines + * -larsers.DefinitionListItemLoose * Cc(true)) + ) / writer.definitionlist + larsers.Blank = parsers.blankline / "" + + larsers.NoteBlock + + larsers.Reference + + (parsers.tightblocksep / "\n") -- parse atx header - local AtxHeading = Cg(HeadingStart,"level") - * optionalspace - * (C(line) / strip_atx_end / parse_inlines) + larsers.AtxHeading = Cg(parsers.HeadingStart,"level") + * parsers.optionalspace + * (C(parsers.line) / strip_atx_end / parse_inlines) * Cb("level") / writer.heading -- parse setext header - local SetextHeading = #(line * S("=-")) - * Ct(line / parse_inlines) - * HeadingLevel - * optionalspace * newline - / writer.heading - - local Heading = AtxHeading + SetextHeading - syntax = + larsers.SetextHeading = #(parsers.line * S("=-")) + * Ct(parsers.line / parse_inlines) + * parsers.HeadingLevel + * parsers.optionalspace * parsers.newline + / writer.heading + + larsers.Heading = larsers.AtxHeading + larsers.SetextHeading + local syntax = { "Blocks", - Blocks = Blank^0 * - Block^-1 * - (Blank^0 / function() - return writer.interblocksep - end * Block)^0 * - Blank^0 * - eof, + Blocks = larsers.Blank^0 * parsers.Block^-1 + * (larsers.Blank^0 / function() + return writer.interblocksep + end + * parsers.Block)^0 + * larsers.Blank^0 * parsers.eof, - Blank = Blank, + Blank = larsers.Blank, Block = V("Blockquote") + V("Verbatim") @@ -1144,20 +1620,21 @@ function M.reader.new(writer, options) + V("OrderedList") + V("Heading") + V("DefinitionList") + + V("DisplayHtml") + V("Paragraph") + V("Plain"), - Blockquote = Blockquote, - Verbatim = Verbatim, - FencedCode = FencedCode, - HorizontalRule = HorizontalRule, - BulletList = BulletList, - OrderedList = OrderedList, - Heading = Heading, - DefinitionList = DefinitionList, - DisplayHtml = DisplayHtml, - Paragraph = Paragraph, - Plain = Plain, + Blockquote = larsers.Blockquote, + Verbatim = larsers.Verbatim, + FencedCode = larsers.FencedCode, + HorizontalRule = larsers.HorizontalRule, + BulletList = larsers.BulletList, + OrderedList = larsers.OrderedList, + Heading = larsers.Heading, + DefinitionList = larsers.DefinitionList, + DisplayHtml = larsers.DisplayHtml, + Paragraph = larsers.Paragraph, + Plain = larsers.Plain, Inline = V("Str") + V("Space") @@ -1173,78 +1650,86 @@ function M.reader.new(writer, options) + V("Code") + V("AutoLinkUrl") + V("AutoLinkEmail") + + V("InlineHtml") + + V("HtmlEntity") + V("EscapedChar") + V("Smart") + V("Symbol"), - Str = Str, - Space = Space, - Endline = Endline, - UlOrStarLine = UlOrStarLine, - Strong = Strong, - Emph = Emph, - InlineNote = InlineNote, - NoteRef = NoteRef, - Citations = Citations, - Link = Link, - Image = Image, - Code = Code, - AutoLinkUrl = AutoLinkUrl, - AutoLinkEmail = AutoLinkEmail, - InlineHtml = InlineHtml, - HtmlEntity = HtmlEntity, - EscapedChar = EscapedChar, - Smart = Smart, - Symbol = Symbol, + Str = larsers.Str, + Space = larsers.Space, + Endline = larsers.Endline, + UlOrStarLine = larsers.UlOrStarLine, + Strong = larsers.Strong, + Emph = larsers.Emph, + InlineNote = larsers.InlineNote, + NoteRef = larsers.NoteRef, + Citations = larsers.Citations, + Link = larsers.Link, + Image = larsers.Image, + Code = larsers.Code, + AutoLinkUrl = larsers.AutoLinkUrl, + AutoLinkEmail = larsers.AutoLinkEmail, + InlineHtml = larsers.InlineHtml, + HtmlEntity = larsers.HtmlEntity, + EscapedChar = larsers.EscapedChar, + Smart = larsers.Smart, + Symbol = larsers.Symbol, } if not options.definitionLists then - syntax.DefinitionList = fail + syntax.DefinitionList = parsers.fail end if not options.fencedCode then - syntax.FencedCode = fail + syntax.FencedCode = parsers.fail end if not options.citations then - syntax.Citations = fail + syntax.Citations = parsers.fail end if not options.footnotes then - syntax.NoteRef = fail + syntax.NoteRef = parsers.fail end if not options.inlineFootnotes then - syntax.InlineNote = fail + syntax.InlineNote = parsers.fail end if not options.smartEllipses then - syntax.Smart = fail + syntax.Smart = parsers.fail + end + + if not options.html then + syntax.DisplayHtml = parsers.fail + syntax.InlineHtml = parsers.fail + syntax.HtmlEntity = parsers.fail end local blocks_toplevel_t = util.table_copy(syntax) - blocks_toplevel_t.Paragraph = ToplevelParagraph - blocks_toplevel = Ct(blocks_toplevel_t) + blocks_toplevel_t.Paragraph = larsers.ToplevelParagraph + larsers.blocks_toplevel = Ct(blocks_toplevel_t) - blocks = Ct(syntax) + larsers.blocks = Ct(syntax) local inlines_t = util.table_copy(syntax) inlines_t[1] = "Inlines" - inlines_t.Inlines = Inline^0 * (spacing^0 * eof / "") - inlines = Ct(inlines_t) + inlines_t.Inlines = parsers.Inline^0 * (parsers.spacing^0 * parsers.eof / "") + larsers.inlines = Ct(inlines_t) local inlines_no_link_t = util.table_copy(inlines_t) - inlines_no_link_t.Link = fail - inlines_no_link = Ct(inlines_no_link_t) + inlines_no_link_t.Link = parsers.fail + larsers.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 = fail - inlines_no_inline_note = Ct(inlines_no_inline_note_t) + inlines_no_inline_note_t.InlineNote = parsers.fail + larsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) local inlines_nbsp_t = util.table_copy(inlines_t) - inlines_nbsp_t.Endline = NonbreakingEndline - inlines_nbsp_t.Space = NonbreakingSpace - inlines_nbsp = Ct(inlines_nbsp_t) + inlines_nbsp_t.Endline = larsers.NonbreakingEndline + inlines_nbsp_t.Space = larsers.NonbreakingSpace + larsers.inlines_nbsp = Ct(inlines_nbsp_t) function self.convert(input) references = {} local opt_string = {} |