The following solution uses LuaLaTeX's capabilities to define a function, called allstars
, which converts instances of pairs of groups of asterisks -- ***...***
, **...**
, and *...*
-- into "traditional" LaTeX code: {\bfseries\itshape ...}
, {\bfseries ...}
, and {\itshape ...}
, respectively. The code assumes that asterisk-based markdown does not span linebreaks. The code does not make the *
"active" (in the TeX sense of the word). Thus, various complications caused by *
being active do not arise.
Using starred LaTeX macros (e.g., \section*
) and starred LaTeX environments (e.g., equation*
) is OK -- i.e., they will not crash the compilation -- if there are no other asterisks on the respective input lines. For extra generality, the code provides two macros -- named \markdownoff
and \markdownon
-- that turn the operation of the allstars
function off and on. The allstars
function is not activated by default -- it has to be turned on by executing \markdownon
.
The markdown directives can be nested, in the sense that an italic string can contain a bold italic substring and also that a bold string can contain a bold italic substring.
The allstars
function is assigned to LuaTeX's process_input_buffer
callback, making it operate during a very early stage of compilation (before TeX's "eyes" get to do any work). The function allstars
performs three "sweeps" or "passes" over each line of input with Lua's string.gsub
function. During the first pass, it searches for pairs of ***
characters; during the second, it looks for pairs of **
characters; and during the third and final pass, it looks for pairs of *
characters.
To fully understand how the allstars
function works, it is instructive to examine what happens if an input line contains instances of four or more consecutive asterisks (even though "regular" markdown shouldn't contain such instances). For instance, if an input line contains the seven-asterisk sequence
a*******b
and no further asterisks anywhere else on the input line, the output will be "ab": a single "" character between "a" and "b", but no italic or bold characters. What's going on? The first six asterisks in *******
are interpreted by the allstars
function as a pair of ***
strings, and *******
therefore gets converted to {\bfseries\itshape}
; they thus end up doing nothing at all, at least as far as TeX is concerned. The seventh asterisk, though, is not modified by the allstars
function and therefore gets typeset by TeX.
Or, consider the string
a*******b********c
(seven asterisks between "a" and "b" and eight asterisks between "b" and "c"). During the first string.gsub
pass, the input line is converted to
a{\bfseries\itshape}*b{\bfseries\itshape}**c
which is equivalent (for our purposes) to
a*b**c
The function's second pass does nothing because no pair of **
characters is present in the input line. The function's third pass finds a pair of *
characters and therefore converts the input line to
a{\itshape b}*c
and that's what gets typeset by the (Lua)TeX engine.
Finally, consider what happens if and input line consists of
\section*{A New* Hope}
(and no further asterisks). The third pass of the allstars
function finds a pair of single asterisks in this line of input and therefore converts it to
\section{\itshape {A New} Hope}
which is equivalent to
\section{\itshape A New Hope}
Thus, we get a numbered section, and the section header -- but not the section number -- is typeset in bold italics. Probably not what was expected, or intended... If you really need to typeset *
directly, you should issue \markdownoff
to suspend the operation of the allstars
function.

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{amsmath} % for 'equation*' environment
\usepackage{luacode,luatexbase}
\begin{luacode}
-- Use Lua captures to extract material affected by markdown
function allstars (line)
line = string.gsub( line, "(%*%*%*)(.-)(%*%*%*)", "{\\bfseries\\itshape %2}")
line = string.gsub( line, "(%*%*)(.-)(%*%*)", "{\\bfseries %2}" )
line = string.gsub( line, "(%*)(.-)(%*)", "{\\itshape %2}" )
return line
end
\end{luacode}
\newcommand\markdownon{%
\directlua{luatexbase.add_to_callback( "process_input_buffer", allstars, "allstars" )}}
\newcommand\markdownoff{%
\directlua{luatexbase.remove_from_callback( "process_input_buffer", "allstars" )}}
\begin{document}
\markdownon
normal
*italic*, **bold**, normal, *more italic*
**bold**, *italic*, **more bold**
***bold italic***, normal, ***more bold italic text***,
**bold text containing a *bold italic* substring**
*italic text with **bold italic** substring*
normal again
\markdownoff % not actually required, as each of the following lines contains exactly one asterisk
\medskip
an \texttt{equation*} environment:
\begin{equation*}
a = b
\end{equation*}
\markdownon
more **bold text with a *bold italic* substring**
\end{document}
\
form is more general, to see the difference try it with%
instead of*
:-)*
active will destroy any star form of macros and environments in LaTeX.