texlive[68959] Master/texmf-dist: simplebnf (24nov23)

commits+karl at tug.org commits+karl at tug.org
Fri Nov 24 22:12:41 CET 2023


Revision: 68959
          https://tug.org/svn/texlive?view=revision&revision=68959
Author:   karl
Date:     2023-11-24 22:12:41 +0100 (Fri, 24 Nov 2023)
Log Message:
-----------
simplebnf (24nov23)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/simplebnf/simplebnf-doc.pdf
    trunk/Master/texmf-dist/doc/latex/simplebnf/simplebnf-doc.tex
    trunk/Master/texmf-dist/tex/latex/simplebnf/simplebnf.sty

Modified: trunk/Master/texmf-dist/doc/latex/simplebnf/simplebnf-doc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/simplebnf/simplebnf-doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/simplebnf/simplebnf-doc.tex	2023-11-24 21:12:32 UTC (rev 68958)
+++ trunk/Master/texmf-dist/doc/latex/simplebnf/simplebnf-doc.tex	2023-11-24 21:12:41 UTC (rev 68959)
@@ -1,18 +1,78 @@
-\documentclass[a4paper]{article}
+\documentclass[11pt]{article}
+\usepackage{geometry}
+\geometry{a4paper,top=1in,bottom=1in,left=1.2in,right=1.2in}
 
-\usepackage{simplebnf}
-\usepackage{hyperref}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Font setup %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
+% Works for pdfLaTeX, XeLaTeX, and LuaLaTeX
+\usepackage{libertine}
+
+% Math/Mono font setup
+\ExplSyntaxOn
+\bool_if:nTF { \sys_if_engine_xetex_p: || \sys_if_engine_luatex_p: }
+  {
+    % Load mathtools before unicode-math
+    \usepackage{mathtools}
+    \usepackage[
+      math-style=ISO,
+      warnings-off={mathtools-colon, mathtools-overbracket},
+    ]{unicode-math}
+    \setmathfont{LibertinusMath-Regular.otf}[Scale = MatchUppercase]
+    \setmathfont{Garamond-Math.otf}[
+      Scale = MatchUppercase,
+      range = {\Coloneq}
+    ]
+
+    \setmonofont{Inconsolatazi4}[
+      Extension = .otf,
+      UprightFont = *-Regular,
+      BoldFont = *-Bold,
+      Scale=MatchLowercase,
+      AutoFakeSlant = 0.2,
+    ]
+  }
+  {
+    \usepackage[libertine]{newtxmath}
+    \usepackage{inconsolata}
+  }
+\ExplSyntaxOff
+
+% Some fancy symbols (prevent being overwritten by math fonts)
+\AtBeginDocument{%
+  \usepackage[
+    only, fatsemi, fatslash, talloblong, rightarrowtriangle
+  ]{stmaryrd}%
+}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Presentations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
 \usepackage{tcolorbox}
 \tcbuselibrary{listings,breakable}
-\tcbset{listing engine=listings,colframe=black,colback=white,size=small}
+\tcbset{listing engine=listings, colframe=black, colback=white, size=small}
 
+\NewTCBListing { example } { !O{} !s }
+  {
+    sharp corners,
+    IfBooleanTF = { #2 }
+      { listing above text }
+      { listing side text },
+    fontlower = \small,
+    #1
+  }
+
+\NewTCBListing { listing } { !O{} }
+  { sharp corners, listing only, #1 }
+
+
 \NewDocumentEnvironment {exampleside} {}
-  { \tcblisting{listing side text,righthand width=.55\textwidth} }
+  { \tcblisting{listing side text,righthand width=.5\textwidth} }
   { \endtcblisting }
 
-\NewDocumentCommand \cmd { m } {\texttt{\textbackslash#1}}
-
 \NewDocumentEnvironment { presentcommand } { b }
   {%
     \vspace*{0.5\baselineskip}\noindent\fbox{%
@@ -22,84 +82,432 @@
   }
   { }
 
+
+\NewDocumentCommand \cmd { m } {\texttt{\char`\\#1}}
 \NewDocumentCommand \env { m m }
   {
     \texttt{%
-      \textbackslash begin\{#1\} \textrm{#2}%
-      \textbackslash end\{#1\}%
+      \char`\\begin\{#1\} \textrm{#2}%
+      \char`\\end\{#1\}%
     }%
   }
 
+
+\usepackage{tabularray}
+\UseTblrLibrary{booktabs}
+% \fakeverb
+\usepackage{codehigh}
+
+
+\newcommand*{\secref}[1]{\hyperref[{#1}]{section~\ref*{#1} -- \emph{\nameref*{#1}}}}
+\newcommand*{\subsecref}[1]{\hyperref[{#1}]{subsection~\ref*{#1} -- \emph{\nameref*{#1}}}}
+\newcommand*{\subsubsecref}[1]{\hyperref[{#1}]{\nameref*{#1}}}
+\newcommand*{\parref}[1]{\hyperref[{#1}]{\nameref*{#1}}}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% simplebnf %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\usepackage{simplebnf}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Miscellaneous %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\usepackage{hologo}
+\newcommand*{\XeLaTeX}{\hologo{XeLaTeX}}
+\newcommand*{\LuaLaTeX}{\hologo{LuaLaTeX}}
+
+% ``Make sure it comes *last* of your loaded packages"
+\usepackage[pdfborder={0 0 0}, colorlinks=true]{hyperref}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Metadata %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
 \title{%
   \textsf{simplebnf} --- A simple package to format Backus-Naur form%
-  \footnote{This file describes v0.3.2.}}
+  \footnote{This file describes v1.0.0.}}
 \author{Jay Lee\footnote{E-mail: %
   \href{mailto:jaeho.lee at snu.ac.kr}{\texttt{jaeho.lee at snu.ac.kr}}}}
-\date{2023/01/07}
+\date{2023/11/25}
 
+
 \begin{document}
 \maketitle
 
-This package provides a simple way to typeset grammars written in Backus-Naur form (BNF).
 
-\begin{presentcommand}
-  \cmd{SimpleBNFDefEq}
-\end{presentcommand}
-This command is used to typeset the definition symbol separate a nonterminal from its productions. It defaults to \SimpleBNFDefEq. It can be redefined using \verb|RenewDocumentCommand|.
+\vfill
+This package provides a simple way for typesetting grammars in Backus-Naur form (BNF).
+It features a flexible configuration system, allowing for the customization of the domain-specific language (DSL) used in typesetting the grammar.
+Additionally, the package comes with sensible defaults.
 
-\begin{presentcommand}
-  \cmd{SimpleBNFDefOr}
-\end{presentcommand}
-This command is used to typeset the separator symbol between productions. It defaults to \SimpleBNFDefOr. It can be redefined using \verb|RenewDocumentCommand|.
+Below is the metagrammar of the DSL as defined in this package, which is typeset using the package itself.\footnote{The code is shown in \nameref{sec:appendix}.}
+\begin{tcolorbox}[sharp corners]
+  \begin{center}
+    \begin{bnf}(
+      prod-delim = ;;;,
+      new-line-delim = !,
+      single-line-delim = ?,
+      comment = //,
+      relation = {:::=|:in:},
+      relation-sym-map =
+        {
+          {:::=} = $\Coloneqq$,
+          {:in:} = $\in$,
+        },
+    )[
+      colspec = lrcll,
+      column{2} = {mode=dmath},
+      column{4} = {mode=text, font=\ttfamily},
+    ]
+      G // Gramar :::=
+      ! $P$ // production
+      ! $P \fatsemi {}$ // production w/ a trailing delimiter
+      ! $P \fatsemi G$ // production sequence
+    ;;;
+      P // Production :::= $L \rightarrowtriangle R$
+    ;;;
+      L // LHS :::=
+      ! $v$ // metavariable
+      ! $v\!\fatslash\,c$ // annotated metavariable
+    ;;;
+      R // RHS :::=
+      ! $\talloblong$ // delimiter
+      ! $A \talloblong R$ // alternative sequence
+    ;;;
+      A // Alternative :::=
+      ! $f$ // syntactic form
+      ! $f\!\fatslash\,c$ // annotated syntactic form
+    ;;;
+      \fatsemi // Prod. delimiter :::=
+      ! ;; // default symbol
+      ! $\cdots$ // user-defined
+    ;;;
+      \rightarrowtriangle // Rule relation :::=
+      ! ::= // $\Coloneqq$
+      ! -> // $\to$
+      ! \texttt{\char`\\in} // $\in$
+      ! $\cdots$ // user-defined
+    ;;;
+      \fatslash // Annot. symbol :::=
+      ! : // default symbol
+      ! $\cdots$ // user-defined
+    ;;;
+      \talloblong // Alt. delimiter :::=
+      ! | // new-line delimiter
+      ! || // single-line delimiter
+      ! $\cdots$ // user-defined
+    ;;;
+      v, f, c :in: \textsf{\TeX{} tl} // valid \TeX{} token lists
+    ;;;
+    \end{bnf}
+  \end{center}
+\end{tcolorbox}
+\vfill
 
-\begin{presentcommand}
-  \cmd{SimpleBNFStretch}
-\end{presentcommand}
-This command is used to control the vertical spacing between consecutive rules.
-It defaults to 0.
-It can be redefined using \verb/Renewdocumentcommand/.
+\section{Tutorial for the impatient}\label{sec:tutorial}
+Typesetting a grammar is as simple as typing the BNF grammar in the \verb/bnf/ environment:
+\begin{example}[righthand width=.52\linewidth]
+\begin{center}
+\begin{bnf}
+  $\tau$ : \textsf{Type} ::=
+  | \texttt{num} : numbers
+  | \texttt{str} : strings
+  ;;
+  $e$ : \textsf{Expr} ::=
+  | $x$ : variable
+  | $n$ : numeral
+  | \texttt{$e$ + $e$} : addition
+  | \texttt{$e$ * $e$} : multiplication
+  | \texttt{$e$ \textasciicircum{} $e$} : concatenation
+  | \texttt{len($e$)} : length
+  | \texttt{let $x$ = $e_1$ in $e_2$} : definition
+\end{bnf}
+\end{center}
+\end{example}
 
-\begin{presentcommand}
-  \cmd{bnfexpr}
-\end{presentcommand}
-This command is used when typesetting the BNF nonterminal and productions. It defaults to a wrappers around \cmd{texttt}. It can be redefined to customized output using \verb|RenewDocumentCommand|.
+Typically, each column in the BNF grammar has the same font style.
+In the example above, the comments in the first column, e.g., \textsf{Type}, is typeset in sans-serif, and the second column in math mode, e.g., $\tau$.
+The column for the right-hand side is (mostly) typeset in typewriter font with a bit of non-terminals like $e$ typeset in math mode.
 
-\begin{presentcommand}
- \cmd{bnfannot}
-\end{presentcommand}
-This command is used when typesetting the annotations on nonterminals and productions. It defaults to a wrappers around \cmd{textit}. It can be redefined to customized output using \verb|RenewDocumentCommand|.
-
-\begin{presentcommand}
-  \env{bnfgrammar}{text}
-\end{presentcommand}
-can be used to typeset BNF grammars. The \textit{text} inside the environment should be formatted as:
-\begin{verbatim}
-  term1 ::= rhs1
+\textsf{simplebnf} provides a straightforward way to customize the font styles for each column\footnote{Note that you must provide a manual alignment specification with the key \texttt{colspec}.}:
+\begin{listing}
+\begin{bnf}[
+  colspec = {llcll},
+  column{1} = {font = \sffamily},
+  column{2} = {mode = dmath},
+  column{4} = {font = \ttfamily},
+]
+  \tau : Type ::=
+  | num : numbers
+  | str : strings
   ;;
-  term2 ::= rhs2
+  e : Expr ::=
+  | $x$ : variable
+  | $n$ : numeral
+  | $e$ + $e$ : addition
+  | $e$ * $e$ : multiplication
+  | $e$ \textasciicircum{} $e$ : concatenation
+  | len($e$) : length
+  | let $x$ = $e_1$ in $e_2$ : definition
+\end{bnf}
+\end{listing}
+If you find yourself using the same configuration repeatedly, you can fix the desired configuration using \cmd{SetBNFLayout}.
+Once set using \cmd{SetBNFLayout}, the configuration is applied to all subsequent \verb/bnf/ environments:
+\begin{listing}
+% Some where above...
+\SetBNFLayout{
+  colspec = {llcll},
+  column{1} = {font = \sffamily},
+  column{2} = {mode = dmath},
+  column{4} = {font = \ttfamily},
+}
+% ...
+\begin{bnf}
+  \tau : Type ::=
+  | num : numbers
+  | str : strings
   ;;
-  ...
-  termk ::= rhsk
-\end{verbatim}
-where each of the \textit{rhs} represents alternative syntactic forms of the \textit{term}. An annotation may accompany each alternative in which case the alternative must be separated from its annotation with a colon (\verb/:/). If you don't need annotations, simply omit the colons and annotations altogether. The alternatives themselves are separated using the pipe symbol (\verb/|/).
+  e : Expr ::=
+  | $x$ : variable
+  | $n$ : numeral
+  | $e$ + $e$ : addition
+  | $e$ * $e$ : multiplication
+  | $e$ \textasciicircum{} $e$ : concatenation
+  | len($e$) : length
+  | let $x$ = $e_1$ in $e_2$ : definition
+\end{bnf}
+\end{listing}
 
-A sample code and the result is shown below:
-\begin{exampleside}
-  \begin{bnfgrammar}
-    a \in \textit{Vars}
-    ;;
-    expr ::=
-      expr + term : sum
-    | term        : term
-    ;;
-    term ::=
-      term * a : product
-    | a        : variable
-  \end{bnfgrammar}
-\end{exampleside}
+Since these customizations are provided by the backend \textsf{tabularray}\footnote{\url{https://github.com/lvjr/tabularray}}, consult its documentation for more details.
+The corresponding command is \cmd{SetTblrInner}.
 
-Annotations can also be provided on left-hand sides, to label the nonterminal instead of a specific production.
-\begin{exampleside}
+For more advanced customization, such as changing default delimiters and symbols, continue to \secref{sec:config}.
+
+\section{Configuration}\label{sec:config}
+While the default configuration should be sufficient for most use cases, some grammars may require using different delimiters and symbols.
+For example, the language itself may contain \verb/->/ as a syntactic form, which conflicts with one of the default symbols for the rule relation.
+
+To this end, \textsf{simplebnf} provides a number of options to configure the DSL used to typeset the grammar.
+Some examples of customizable symbols include the delimiters for productions and alternatives, the relation symbol between the left-hand side and the right-hand side, the annotation symbol, and more.
+
+The default configuration is shown in \autoref{tab:config}.
+\begin{longtblr}[
+  caption = {Default key-values for the configuration of \textsf{simplebnf}.},
+  label = {tab:config}
+]{
+  colspec={ll},
+  row{2-Z}={font=\ttfamily}
+}
+  \toprule
+  Key & Default value \\
+  \midrule
+  prod-delim & ;;\\
+  new-line-delim & \fakeverb{\|}\\
+  single-line-delim & //\\
+  comment & :\\
+  relation & \fakeverb{{::=|->|:in:}}\\
+  relation-sym-map & {
+    \{\\
+    \quad\fakeverb{{::=} = {\ensuremath{\Coloneqq}},}\\
+    \quad\fakeverb{{->} = {\ensuremath{\to}},}\\
+    \quad\fakeverb{{:in:} = {\ensuremath{\in}},}\\
+    \}
+  }\\
+  or-sym & \fakeverb{$|$}\\
+  prod-sep & 2pt\\
+  row-sep & 0pt\\
+  \bottomrule
+\end{longtblr}
+
+The key-value list can be provided as an optional argument to the \verb/bnf/ environment, wrapped in \verb/()/.
+The optional key-value list for the layout---wrapped in \verb/[]/---should be provided after the configuration key-value list.
+See \subsecref{subsec:layout} for more details.
+
+An exmaple of customizing the configuration is shown below:
+\begin{example}*
+\begin{center}
+\begin{bnf}(
+  prod-delim = {--},
+  new-line-delim = {\&},
+  single-line-delim = {\&\&},
+  comment = {//},
+  relation-sym-map =
+    {
+      {->} = {\ensuremath{\hookrightarrow}},
+      {:in:} = {\ensuremath{\in}},
+    },
+  or-sym = {},
+)[
+  colspec = {llcll},
+  column{1} = {font = \sffamily},
+  column{2} = {mode = dmath},
+  column{4} = {font = \ttfamily},
+]
+  \tau // Type ->
+  & num // numbers
+  & str // strings
+  --
+  e // Expr ->
+  & $x$ // variable
+  & $n$ // numeral
+  & $e$ + $e$ // addition
+  & $e$ * $e$ // multiplication
+  & $e$ \textasciicircum{} $e$ // concatenation
+  & len($e$) // length
+  & let $x$ = $e_1$ in $e_2$ // definition
+\end{bnf}
+\end{center}
+\end{example}
+
+
+Furthermore, the configuration can be set globally using \cmd{SetBNFConfig}, similar to \cmd{SetBNFLayout}:
+\begin{listing}
+% Some where above...
+\SetBNFConfig{
+  prod-delim = {--},
+  new-line-delim = {\&},
+  single-line-delim = {\&\&},
+  comment = {//},
+  relation-sym-map =
+    {
+      {->} = {\ensuremath{\hookrightarrow}},
+      {:in:} = {\ensuremath{\in}},
+    },
+  or-sym = {},
+}
+% ...
+\begin{bnf}[
+  colspec = {llcll},
+  column{1} = {font = \sffamily},
+  column{2} = {mode = dmath},
+  column{4} = {font = \ttfamily},
+]
+  \tau // Type ->
+  & num // numbers
+  & str // strings
+  --
+  e // Expr ->
+  & $x$ // variable
+  & $n$ // numeral
+  & $e$ + $e$ // addition
+  & $e$ * $e$ // multiplication
+  & $e$ \textasciicircum{} $e$ // concatenation
+  & len($e$) // length
+  & let $x$ = $e_1$ in $e_2$ // definition
+\end{bnf}
+\end{listing}
+
+
+\subsection{Production delimiter}
+The default production delimiter \verb/prod-delim/ is \verb/;;/.
+This will separate different productions in the same grammar.
+
+\subsection{New-line alternative delimiter}
+The default new-line alternative delimiter \verb/new-line-delim/ is \verb/\|/, which will actually match verbatim \verb/\|/ in the grammar.
+This will separate different alternatives into different lines in the same production.
+
+\subsection{Single-line alternative delimiter}
+The default single-line alternative delimiter \verb/single-line-delim/ is \verb|//|.
+The difference between the single-line alternative delimiter and the new-line alternative delimiter is that the former will not add a new line after the delimiter.
+
+For consecutive alternatives delimited by the single-line alternative delimiter, only the last of them can be annotated.
+
+Note that the single-line alternative delimiter must not contain the new-line alternative delimiter as a substring.
+
+\subsection{Annotation delimiter}
+The default annotation delimiter \verb/comment/ is \verb/:/.
+This will separate the syntactic form and the annotation within the alternative.
+
+\subsection{Rule relations and the symbol map}
+\textsf{simplebnf} provides a \verb/::=/, \verb/->/, and \verb/:in:/ for the rule relation between the left-hand side and the right-hand side.
+Each of them is mapped to a symbol in the \verb/relation-sym-map/ key-value list; By default, they are mapped to $\Coloneqq$ (\verb/\ensuremath{\Coloneqq}/), $\to$ (\verb/\ensuremath{\to}/), and $\in$ (\verb/\ensuremath{\in}/), respectively.
+
+To provide a custom relation symbol, provide the desired symbol and the mapping to the \verb/relation-sym-map/ key-value list and to the \verb/relation/.
+
+\subsection{Or symbol}
+The default or symbol \verb/or-sym/ is \verb/$|$/.
+Probably the most common use case for this option is to remove the or symbol altogether by setting it to \verb/{}/.
+
+\subsection{Production separation}
+The default production separation \verb/prod-sep/ is \verb/2pt/.
+This will add a vertical space between the productions.
+
+\subsection{Row separation}
+The default row separation \verb/row-sep/ is \verb/0pt/.
+This will add a vertical space between the alternatives in the same production.
+
+\subsection{Layout}\label{subsec:layout}
+As mentioned in \secref{sec:tutorial}, the layout of the \verb/bnf/ environment can be customized per-enviroment using the key-value list in the optional argument wrapped in \verb/[]/.
+When provided, these should be provided after the configuration key-value list, which is wrapped in \verb/()/.
+
+The optional key-value list for the layout is passed to the backend \textsf{tabularray} package.
+
+An example of customizing the layout is shown below:
+\begin{example}[righthand width=.53\linewidth]
+\begin{center}
+\begin{bnf}(
+  prod-delim = {--},
+  or-sym = {},
+  prod-sep = 5pt,
+)[
+  colspec = {|[2pt, gray5]llcll},
+  column{1} = {font = \sffamily},
+  column{2} = {mode = dmath},
+  column{4} = {font = \ttfamily},
+  column{5} = {font = \itshape},
+  cell{9}{4} = {cmd = \fbox},
+  row{-} = {bg = azure9},
+]
+  \tau : Type ::=
+  | num : numbers
+  | str : strings
+  --
+  e : Expr ::=
+  | $x$ : variable
+  | $n$ : numeral
+  | $e$ + $e$ : addition
+  | $e$ * $e$ : multiplication
+  | $e$ \textasciicircum{} $e$ : concatenation
+  | len($e$) : length
+  | let $x$ = $e_1$ in $e_2$ : definition
+\end{bnf}
+\end{center}
+\end{example}
+
+
+\section{Caveats}
+The choices of delimiters and symbols should be carefully made, in order to avoid conflicts with the grammar of the target language and our meta-language, \TeX{}.
+
+Another subtlety to consider while customizing delimiters, is that certain delimiters not contain the others as a substring.
+For instance, as mentioned in \secref{sec:config}, the single-line alternative delimiter must not contain the new-line alternative delimiter as a substring.
+
+When using custom OpenType fonts with the \verb/unicode-math/ package in \XeLaTeX{} or \LuaLaTeX{}, beware of the fact that many fonts lack the \verb/\Coloneqq/ symbol, $\Coloneqq$.
+The easiest way to fix this is to actually use a font that provides the symbol, such as \verb/Garamond-Math/, which comes with the \TeX{}Live installation\footnote{\fakeverb{\Coloneq} is not a typo of \fakeverb{\Coloneqq}.}.
+\begin{listing}
+\setmathfont{Garamond-Math.otf}[
+  Scale = MatchUppercase,
+  range = {\Coloneq}
+]
+\end{listing}
+
+
+\section{The deprecated \texttt{bnfgrammar} environment}
+Prior to version 1.0.0, the package provided a sole environment \verb/bnfgrammar/ for typesetting grammars.
+While it is still available, it is now deprecated and will be removed in a future release.
+Using it will result in a deprecation warning.
+
+To migrate to the new \verb/bnf/ environment, simply replace the \verb/bnfgrammar/ environment with \verb/bnf/, and replace \verb/\in/ with \verb/:in:/ and \verb/||/ with \verb|//|.
+Moreover, the new environment does not center the grammar by default, so wrapping it in a \verb/center/ environment is necessary to reproduce the same output.
+While there will be some differences in the spacings and the font styles, it is an easy fix.
+See \secref{sec:tutorial} for more details.
+
+Below is an example of using the deprecated \verb/bnfgrammar/ environment, left here for historical reasons.
+\begin{example}
   \begin{bnfgrammar}
     a : Variables \in \textit{Vars}
     ;;
@@ -107,48 +515,71 @@
       expr + term
     | term
     ;;
-    term ::=
-      term * a
-    | a
+    term ::= term * a || a
   \end{bnfgrammar}
-\end{exampleside}
+\end{example}
 
-You can also provide an optional specification to the grammar environment, to redefine alignment or spacing.
-\begin{tcblisting}{text above listing}
-  \begin{bnfgrammar}[lr@{\hspace{4pt}}c@{\hspace{2pt}}ll]
-    a : Variables \in \textit{Vars}
-    ;;
-    expr ::=
-      expr + term : sum
-    | term        : term
-    ;;
-    term ::=
-      term * a : product
-    | a        : variable
-  \end{bnfgrammar}
-\end{tcblisting}
 
-If you want to typeset multiple productions on a single line, you can use double vertical bars by default.
-\begin{exampleside}
-  \begin{bnfgrammar}
-    a \in \textit{Vars}
-    ;;
-    expr ::= expr + term || term
-    ;;
-    term ::= term * a || a
-  \end{bnfgrammar}
-\end{exampleside}
+\section*{Appendix}\label{sec:appendix}
+The following is the code used to typeset the metagrammar of the \textsf{simplebnf} DSL in the first page.
+\begin{listing}[breakable]
+\begin{bnf}(
+  prod-delim = ;;;,
+  new-line-delim = !,
+  single-line-delim = ?,
+  comment = //,
+  relation = {:::=|:in:},
+  relation-sym-map =
+    {
+      {:::=} = $\Coloneqq$,
+      {:in:} = $\in$,
+    },
+)[
+  colspec = lrcll,
+  column{2} = {mode=dmath},
+  column{4} = {mode=text, font=\ttfamily},
+]
+  G // Gramar :::=
+  ! $P$ // production
+  ! $P \fatsemi {}$ // production w/ a trailing delimiter
+  ! $P \fatsemi G$ // production sequence
+;;;
+  P // Production :::= $L \rightarrowtriangle R$
+;;;
+  L // LHS :::=
+  ! $v$ // metavariable
+  ! $v\!\fatslash\,c$ // annotated metavariable
+;;;
+  R // RHS :::=
+  ! $\talloblong$ // delimiter
+  ! $A \talloblong R$ // alternative sequence
+;;;
+  A // Alternative :::=
+  ! $f$ // syntactic form
+  ! $f\!\fatslash\,c$ // annotated syntactic form
+;;;
+  \fatsemi // Prod. delimiter :::=
+  ! ;; // default symbol
+  ! $\cdots$ // user-defined
+;;;
+  \rightarrowtriangle // Rule relation :::=
+  ! ::= // $\Coloneqq$
+  ! -> // $\to$
+  ! \texttt{\char`\\in} // $\in$
+  ! $\cdots$ // user-defined
+;;;
+  \fatslash // Annot. symbol :::=
+  ! : // default symbol
+  ! $\cdots$ // user-defined
+;;;
+  \talloblong // Alt. delimiter :::=
+  ! | // new-line delimiter
+  ! || // single-line delimiter
+  ! $\cdots$ // user-defined
+;;;
+  v, f, c :in: \textsf{\TeX{} tl} // valid \TeX{} token lists
+;;;
+\end{bnf}
+\end{listing}
 
-The second and third optional arguments specify regular expressions for the line-breaking and non-breaking RHS seperators:
-\begin{tcblisting}{text above listing}
-  \begin{bnfgrammar}[llcll][\|\|][\|]
-    a \in \textit{Vars}
-    ;;
-    expr ::= expr + term | term
-    ;;
-    term ::= term * a
-    || a
-  \end{bnfgrammar}
-\end{tcblisting}
-
 \end{document}

Modified: trunk/Master/texmf-dist/tex/latex/simplebnf/simplebnf.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/simplebnf/simplebnf.sty	2023-11-24 21:12:32 UTC (rev 68958)
+++ trunk/Master/texmf-dist/tex/latex/simplebnf/simplebnf.sty	2023-11-24 21:12:41 UTC (rev 68959)
@@ -8,191 +8,474 @@
 %% Released under the MIT License.
 %% ---------------------------------------------------------------------------
 %%
+\NeedsTeXFormat{LaTeX2e}[2018-04-01]
+\providecommand\DeclareRelease[3]{}
+\providecommand\DeclareCurrentRelease[2]{}
+\DeclareRelease{0.3.2}{2023-01-07}{simplebnf-2023-01-07.sty}
+\DeclareCurrentRelease{}{2023-11-25}
+
 \RequirePackage{expl3,xparse}
-% mathtools is needed for the \Coloneqq simbol
+% The new backend is tabularray
+\RequirePackage{tabularray}
+% mathtools is needed for the \Coloneqq symbol
 \RequirePackage{mathtools}
+
 \ProvidesExplPackage
   {simplebnf}
-  {2023/01/07}
-  {0.3.2}
+  {2023-11-25}
+  {1.0.0}
   {A simple package to format Backus–Naur form}
 
-\cs_generate_variant:Nn \regex_split:nnNTF {nVNTF}
-\cs_generate_variant:Nn \regex_split:NnN {NVN}
 
-\NewDocumentCommand\SimpleBNFDefEq{}{\ensuremath{\Coloneqq}}
+\tl_new:N \l__simplebnf_prod_delim_tl
+\tl_new:N \l__simplebnf_new_line_delim_tl
+\tl_new:N \l__simplebnf_single_line_delim_tl
+\tl_new:N \l__simplebnf_comment_tl
+\tl_new:N \l__simplebnf_or_sym_tl
+\tl_new:N \l__simplebnf_relation_tl
+\dim_new:N \l__simplebnf_prod_sep_dim
+\dim_new:N \l__simplebnf_row_sep_dim
+\prop_new:N \l__simplebnf_relation_sym_map_prop
 
-\NewDocumentCommand\SimpleBNFDefOr{}{\ensuremath{|}}
+\keys_define:nn { simplebnf }
+  {
+    prod-delim .tl_set:N = \l__simplebnf_prod_delim_tl,
+    new-line-delim .tl_set:N = \l__simplebnf_new_line_delim_tl,
+    single-line-delim .tl_set:N = \l__simplebnf_single_line_delim_tl,
+    comment .tl_set:N = \l__simplebnf_comment_tl,
+    relation .tl_set:N = \l__simplebnf_relation_tl,
+    relation-sym-map .code:n =
+      { \prop_set_from_keyval:Nn \l__simplebnf_relation_sym_map_prop { #1 } },
+    or-sym .tl_set:N = \l__simplebnf_or_sym_tl,
+    prod-sep .dim_set:N = \l__simplebnf_prod_sep_dim,
+    row-sep .dim_set:N = \l__simplebnf_row_sep_dim,
+  }
 
-\NewDocumentCommand\SimpleBNFStretch{}{0em}
+% default key-values
+\keys_set:nn { simplebnf }
+  {
+    prod-delim = ;;,
+    new-line-delim = \|,
+    single-line-delim = //,
+    comment = :,
+    relation = {::=|->|:in:},
+    relation-sym-map =
+      {
+        {::=} = {\ensuremath{\Coloneqq}},
+        {->} = {\ensuremath{\to}},
+        {:in:} = {\ensuremath{\in}},
+      },
+    or-sym = $|$,
+    prod-sep = 2pt,
+    row-sep = 0pt,
+  }
 
-\seq_new:N \l__input_seq
-\seq_new:N \l__term_seq
-\tl_new:N \l__term_tl
-\tl_new:N \l__keypairs_tl
-\tl_new:N \l__table_tl
-\seq_new:N \l__keypairs_seq
-\bool_new:N \l__first_rhs
 
-\regex_new:N \g_simplebnf_rhs_newline_r
-\regex_new:N \g_simplebnf_rhs_nb_r
+% bnf environment is internally a tabularray environment
+\NewTblrEnviron{@simplebnf_tblr_env}
+\SetTblrInner[@simplebnf_tblr_env]{rowsep=\l__simplebnf_row_sep_dim}
 
-%% Typeset a single rhs of a production.
-%% \l__first_rhs = true  => `::=' already typeset
-%% \l__first_rhs = false => move to a newline and typeset `|'
-%% #1 - rhs : annot or rhs
-\cs_new:Nn \simplebnf_typeset_rhs:n
-{
-  \bool_if:NTF \l__first_rhs
-    {
-      \bool_set_false:N \l__first_rhs
-    }
-    {
-      \tl_put_right:Nn \l__table_tl { \\ && \SimpleBNFDefOr & }
-    }
 
-  \tl_set:Nn \l_tmpa_tl { #1 }
-  \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
-  \regex_replace_once:nnN { \s+$ } {} \l_tmpa_tl
-  \regex_split:nVNTF { : } \l_tmpa_tl \l_tmpa_seq
-    {
-      \seq_pop_left:NNT \l_tmpa_seq \l_tmpa_tl
-        {
-          \regex_replace_all:NnN \g_simplebnf_rhs_nb_r { \c{SimpleBNFDefOr} } \l_tmpa_tl
-          % Expand only the local temporary variable.
-          \tl_put_right:No \l__table_tl
-             {
-               \exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl} &
-             }
-        }
+% The main token list that stores the table.
+\tl_new:N \l__simplebnf_table_tl
 
-      \seq_pop_left:NNT \l_tmpa_seq \l_tmpb_tl
-        {
-          \regex_replace_once:nnN { ^\s+ } {} \l_tmpb_tl
-          \tl_put_right:No \l__table_tl
-            {
-              \exp_after:wN\bnfannot\exp_after:wN{\l_tmpb_tl}
-            }
-        }
-    }
-    {
-      \regex_replace_all:NnN \g_simplebnf_rhs_nb_r { \c{SimpleBNFDefOr} } \l_tmpa_tl
+\seq_new:N \l__simplebnf_lhs_rhs_pair_seq
+\tl_new:N \l__simplebnf_lhs_tl
+\tl_new:N \l__simplebnf_matched_relation_tl
+\tl_new:N \l__simplebnf_rhs_tl
 
-      \tl_put_right:No \l__table_tl
-         {
-           \exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl}
-         }
-    }
-}
+\seq_new:N \l__simplebnf_alternative_seq
+\bool_new:N \l__simplebnf_first_alternative_bool
 
+\regex_new:N \l__simplebnf_new_line_delim_regex
+\regex_new:N \l__simplebnf_single_line_delim_regex
+\regex_new:N \l__simplebnf_comment_regex
+\regex_new:N \l__simplebnf_prod_delim_regex
+\regex_new:N \l__simplebnf_relation_regex
+
+\cs_generate_variant:Nn \regex_split:NnNTF {NVNTF}
+\cs_generate_variant:Nn \regex_split:NnN {NVN}
+\cs_generate_variant:Nn \regex_set:Nn {NV}
+\cs_generate_variant:Nn \regex_replace_all:NnN {NVN}
+
+
+\msg_new:nnn { simplebnf } { lhs } { Malformed~LHS~\msg_line_context: }
+\msg_new:nnn { simplebnf } { alt } { Malformed~alternative~\msg_line_context: }
+\msg_new:nnn { simplebnf } { prod } { Malformed~production~\msg_line_context: }
+\msg_new:nnn { simplebnf } { unk-rel } { Unknown~relation~\msg_line_context: }
+\msg_new:nnn { simplebnf } { no-rel } { No~relation~found~\msg_line_context: }
+\msg_new:nnn { simplebnf } { dep }
+  {
+    Environment~bnfgrammar~is~deprecated.~
+    Consider~using~the~environment~bnf.~
+    Consult~the~documentation~for~more~information.
+  }
+
+
 %% Typeset a single lhs of a production.
 %% #1 - lhs : either term or (term : annotation)
-\cs_new:Nn \simplebnf_typeset_lhs:n
-{
-  \tl_set:Nx \l_tmpa_tl { #1 }
-  \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
-  \regex_replace_once:nnN { \s+$ } {} \l_tmpa_tl
+\cs_new:Nn \__simplebnf_typeset_lhs:n
+  {
+    \tl_set:No \l_tmpa_tl { #1 }
+    \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+    \regex_replace_once:nnN { \s+$ } {} \l_tmpa_tl
 
-  \regex_split:nVNTF { : } \l_tmpa_tl \l_tmpa_seq
-    {
-      \seq_pop_right:NN \l_tmpa_seq \l_tmpa_tl
-      \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
-      \tl_put_right:No \l__table_tl
-        {
-          \exp_after:wN\bnfannot\exp_after:wN{\l_tmpa_tl} &
-        }
-      \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
-      \tl_put_right:No \l__table_tl
-        {
-          \exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl}
-        }
-    }
-    {
-      \tl_put_right:No \l__table_tl
-        {
-          \exp_after:wN&\exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl}
-        }
-    }
-}
+    \regex_split:NVNTF \l__simplebnf_comment_regex \l_tmpa_tl \l_tmpa_seq
+      { % annotation
+        \seq_pop_right:NN \l_tmpa_seq \l_tmpa_tl
+        \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+        \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+        \tl_put_right:Nn \l__simplebnf_table_tl { & }
+        % metavariable
+        \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
+        \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+      }
+      { % not annotated
+        \tl_put_right:Nn \l__simplebnf_table_tl { & }
+        \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
+        \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+      }
 
-\NewDocumentCommand \bnfexpr { m } { \texttt { #1 } }
-\NewDocumentCommand \bnfannot { m } { \textit{ #1 } }
+    % There should be no more tokens in the sequence.
+    \seq_if_empty:NF \l_tmpa_seq
+      { \msg_error:nnn { simplebnf } { lhs } { #1 } }
+  }
 
+
+% Handle a single alternative (or single-line alternatives) of an RHS.
+\cs_new:Nn \__simplebnf_typeset_rhs:n
+  {
+    \tl_if_blank:nF { #1 }
+      {
+        \bool_if:NTF \l__simplebnf_first_alternative_bool
+          { \bool_set_false:N \l__simplebnf_first_alternative_bool }
+          {
+            \tl_put_right:Nn \l__simplebnf_table_tl
+              { \\ & & \l__simplebnf_or_sym_tl & }
+          }
+
+        \tl_set:Nn \l_tmpa_tl { #1 }
+        \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+        \regex_replace_once:nnN { \s+$ } {} \l_tmpa_tl
+
+        \regex_replace_all:NVN
+          \l__simplebnf_single_line_delim_regex
+          \l__simplebnf_or_sym_tl
+          \l_tmpa_tl
+
+        \regex_split:NVNTF \l__simplebnf_comment_regex \l_tmpa_tl \l_tmpa_seq
+          { % put an alternate syntax form
+            \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
+            \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+            \tl_put_right:Nn \l__simplebnf_table_tl { & }
+
+            % put an annotation
+            \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
+            \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+            \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+
+            % There should be no more tokens in the sequence.
+            \seq_if_empty:NF \l_tmpa_seq
+              { \msg_error:nnn { simplebnf } { alt } { #1 } }
+          }
+          { % no annotation
+            \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+          }
+      }
+  }
+
+
+% Handle a single production P
+\cs_new:Nn \__simplebnf_typeset_production:n
+  {
+    \regex_split:NnNTF
+      \l__simplebnf_relation_regex
+      { #1 }
+      \l__simplebnf_lhs_rhs_pair_seq
+      {
+        % LHS
+        \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_lhs_tl
+        % Rule relation
+        \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_matched_relation_tl
+        % RHS
+        \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_rhs_tl
+
+        \seq_if_empty:NF \l__simplebnf_lhs_rhs_pair_seq
+          { \msg_error:nnn { simplebnf } { prod } { #1 } }
+        % Build the LHS
+        \__simplebnf_typeset_lhs:n \l__simplebnf_lhs_tl
+
+        % Build the rule relation
+        \prop_get:NVNTF
+          \l__simplebnf_relation_sym_map_prop
+          \l__simplebnf_matched_relation_tl
+          \l_tmpa_tl
+          {
+            \tl_put_right:Nn \l__simplebnf_table_tl { & }
+            \tl_put_right:NV \l__simplebnf_table_tl \l_tmpa_tl
+            \tl_put_right:Nn \l__simplebnf_table_tl { & }
+          }
+          {
+            \msg_warning:nnn { simplebnf } { unk-rel } { #1 }
+            \tl_put_right:Nn \l__simplebnf_table_tl { & & }
+          }
+        % Build the RHS
+        %% \l__simplebnf_alternative_seq - (rhs:annot | rhs)...
+        \regex_split:NVN
+          \l__simplebnf_new_line_delim_regex
+          \l__simplebnf_rhs_tl
+          \l__simplebnf_alternative_seq
+
+        \bool_set_true:N \l__simplebnf_first_alternative_bool
+        \seq_map_function:NN \l__simplebnf_alternative_seq \__simplebnf_typeset_rhs:n
+      }
+      { \msg_error:nnn { simplebnf } { no-rel } { #1 } }
+  }
+
+
+% The main control sequence that builds the grammar G into \l__simplebnf_table_tl
+\cs_new:Nn \__simplebnf_build_grammar:n
+  {
+    \regex_set:NV \l__simplebnf_new_line_delim_regex \l__simplebnf_new_line_delim_tl
+    \regex_set:NV \l__simplebnf_single_line_delim_regex \l__simplebnf_single_line_delim_tl
+    \regex_set:NV \l__simplebnf_comment_regex \l__simplebnf_comment_tl
+    \regex_set:NV \l__simplebnf_prod_delim_regex \l__simplebnf_prod_delim_tl
+    \tl_put_left:Nn \l__simplebnf_relation_tl { ( }
+    \tl_put_right:Nn \l__simplebnf_relation_tl { ) }
+    \regex_set:NV \l__simplebnf_relation_regex \l__simplebnf_relation_tl
+
+    % \l_tmpa_seq contains the sequence of productions (P; ...).
+    \regex_split:NnN \l__simplebnf_prod_delim_regex { #1 } \l_tmpa_seq
+
+    % Is this the first production of this grammar?
+    \bool_set_true:N \l_tmpa_bool
+    \seq_map_inline:Nn \l_tmpa_seq
+      {
+        \tl_if_blank:nF { ##1 } % ignore empty productions
+          { % If not first, change row
+            \bool_if:NTF \l_tmpa_bool
+              { \bool_set_false:N \l_tmpa_bool }
+              {
+                \tl_put_right:Nn \l__simplebnf_table_tl { \\[\l__simplebnf_prod_sep_dim] }
+              }
+            \__simplebnf_typeset_production:n { ##1 }
+          }
+      }
+  }
+
+
 %% Typeset a BNF grammar.
-%% #1 - tabular specification (llcll)
-%% #2 - regexp for newline separator for rhses
-%% #2 - regexp for non-breaking separator for rhses
-%% #3 - grammar
-\NewDocumentEnvironment { bnfgrammar } { O{llcll} O{[^\|]\|[^\|]} O{\|\|} +b }
+%% #1 - key-value options for simplebnf
+%% #2 - tabularray specification (default: llcll)
+%% #3 - grammar body
+\NewDocumentEnvironment { bnf } { d() O{llcll} +b }
   {
-    \regex_gset:Nn \g_simplebnf_rhs_newline_r { #2 }
-    \regex_gset:Nn \g_simplebnf_rhs_nb_r { #3 }
+    \IfNoValueF { #1 }
+      { \keys_set:nn { simplebnf } { #1 } }
 
-    %% \l__input_seq is a list of term definitions.
-    \regex_split:nnN { ;; } { #4 } \l__input_seq
-    \begin{center}
-      \tl_set:Nn \l__table_tl
-        {
-          \begin{tabular}{#1}
-        }
+    \__simplebnf_build_grammar:n { #3 }
 
-    \bool_set_true:N \l_tmp_first_term % Is this the first term in this grammar?
-    \seq_map_inline:Nn \l__input_seq
+    \begin{@simplebnf_tblr_env}[expand=\l__simplebnf_table_tl]{#2}
+      \tl_use:N \l__simplebnf_table_tl
+    \end{@simplebnf_tblr_env}
+  }
+  { }
+
+
+\NewDocumentCommand \SetBNFLayout { m }
+  {
+    \SetTblrInner[@simplebnf_tblr_env]{#1}
+  }
+
+
+\NewDocumentCommand \SetBNFConfig { m }
+  {
+    \keys_set:nn { simplebnf } { #1 }
+  }
+
+
+% DEPRECATED APIs
+
+% Redefinable user-level variables
+\newcommand*\SimpleBNFDefEq{\ensuremath{\Coloneqq}}
+\newcommand*\SimpleBNFDefOr{\ensuremath{|}}
+\newcommand*\SimpleBNFStretch{0em}
+
+\cs_generate_variant:Nn \regex_split:nnNTF {nVNTF}
+
+%% Typeset a single lhs of a production.
+%% #1 - lhs : either term or (term : annotation)
+\cs_new:Nn \@dep__simplebnf_typeset_lhs:n
+  {
+    \tl_set:Nx \l_tmpa_tl { #1 }
+    \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+    \regex_replace_once:nnN { \s+$ } {} \l_tmpa_tl
+
+    \regex_split:nVNTF { : } \l_tmpa_tl \l_tmpa_seq
       {
-        %% If not-first, add newline
-        \bool_if:NTF \l_tmp_first_term
+        \seq_pop_right:NN \l_tmpa_seq \l_tmpa_tl
+        \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+        \tl_put_right:No \l__simplebnf_table_tl
           {
-            \bool_set_false:N \l_tmp_first_term
+            \exp_after:wN\bnfannot\exp_after:wN{\l_tmpa_tl} &
           }
+        \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
+        \tl_put_right:No \l__simplebnf_table_tl
           {
-            \tl_put_right:Nn \l__table_tl { \\[\SimpleBNFStretch] }
+            \exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl}
           }
+      }
+      {
+        \tl_put_right:No \l__simplebnf_table_tl
+          {
+            \exp_after:wN&\exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl}
+          }
+      }
+  }
 
-        \regex_split:nnNTF { ::= } { ##1 } \l__term_seq
-          % Parse a ::= definition
+
+%% Typeset a single rhs of a production.
+%% \l__simplebnf_first_alternative_bool = true  => `::=' already typeset
+%% \l__simplebnf_first_alternative_bool = false => move to a newline and typeset `|'
+%% #1 - rhs : annot or rhs
+\cs_new:Nn \@dep__simplebnf_typeset_rhs:n
+  {
+    \bool_if:NTF \l__simplebnf_first_alternative_bool
+      {
+        \bool_set_false:N \l__simplebnf_first_alternative_bool
+      }
+      {
+        \tl_put_right:Nn \l__simplebnf_table_tl { \\ && \SimpleBNFDefOr & }
+      }
+
+    \tl_set:Nn \l_tmpa_tl { #1 }
+    \regex_replace_once:nnN { ^\s+ } {} \l_tmpa_tl
+    \regex_replace_once:nnN { \s+$ } {} \l_tmpa_tl
+    \regex_split:nVNTF { : } \l_tmpa_tl \l_tmpa_seq
+      {
+        \seq_pop_left:NNT \l_tmpa_seq \l_tmpa_tl
           {
-            %% \l__term_seq    - (lhs, rhses)...
-            %% \l__term_tl     - lhs
-            %% \l__keypairs_tl - rhses
-            \seq_pop_left:NN \l__term_seq \l__term_tl
-            \seq_pop_left:NN \l__term_seq \l__keypairs_tl
+            \regex_replace_all:NnN \l__simplebnf_single_line_delim_regex { \c{SimpleBNFDefOr} } \l_tmpa_tl
+            % Expand only the local temporary variable.
+            \tl_put_right:No \l__simplebnf_table_tl
+              {
+                \exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl} &
+              }
+          }
 
-            \simplebnf_typeset_lhs:n{\l__term_tl}
-            \tl_put_right:Nn \l__table_tl
+        \seq_pop_left:NNT \l_tmpa_seq \l_tmpb_tl
+          {
+            \regex_replace_once:nnN { ^\s+ } {} \l_tmpb_tl
+            \tl_put_right:No \l__simplebnf_table_tl
               {
-                & \SimpleBNFDefEq &
+                \exp_after:wN\bnfannot\exp_after:wN{\l_tmpb_tl}
               }
-            %% \l__keypairs_seq - (rhs:annot | rhs)...
-            \regex_split:NVN \g_simplebnf_rhs_newline_r \l__keypairs_tl \l__keypairs_seq
+          }
+      }
+      {
+        \regex_replace_all:NnN \l__simplebnf_single_line_delim_regex { \c{SimpleBNFDefOr} } \l_tmpa_tl
 
-            \bool_set_true:N \l__first_rhs
-            \seq_map_function:NN \l__keypairs_seq \simplebnf_typeset_rhs:n
+        \tl_put_right:No \l__simplebnf_table_tl
+          {
+            \exp_after:wN\bnfexpr\exp_after:wN{\l_tmpa_tl}
           }
+      }
+  }
+
+\cs_new:Nn \@dep__simplebnf_typeset_production:n
+  {
+    \regex_split:nnNTF { ::= } { #1 } \l__simplebnf_lhs_rhs_pair_seq
+      % Parse a ::= definition
+      {
+        %% \l__simplebnf_lhs_rhs_pair_seq    - (lhs, rhses)...
+        %% \l__simplebnf_lhs_tl     - lhs
+        %% \l__simplebnf_rhs_tl - rhses
+        \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_lhs_tl
+        \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_rhs_tl
+
+        \@dep__simplebnf_typeset_lhs:n{\l__simplebnf_lhs_tl}
+        \tl_put_right:Nn \l__simplebnf_table_tl
           {
-            % Else, parse a \in declaration
-            \regex_split:nnNTF { \c{in} } { ##1 } \l__term_seq
+            & \SimpleBNFDefEq &
+          }
+        %% \l__simplebnf_alternative_seq - (rhs:annot | rhs)...
+        \regex_split:NVN \l__simplebnf_new_line_delim_regex \l__simplebnf_rhs_tl \l__simplebnf_alternative_seq
+
+        \bool_set_true:N \l__simplebnf_first_alternative_bool
+        \seq_map_function:NN \l__simplebnf_alternative_seq \@dep__simplebnf_typeset_rhs:n
+      }
+      {
+        % Else, parse a \in declaration (TODO: There is a huge room for generalization)
+        \regex_split:nnNTF { \c{in} } { #1 } \l__simplebnf_lhs_rhs_pair_seq
+          {
+            %% \l__simplebnf_lhs_rhs_pair_seq    - (lhs, rhses)...
+            %% \l__simplebnf_lhs_tl     - lhs
+            %% \l__simplebnf_rhs_tl - rhses
+            \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_lhs_tl
+            \seq_pop_left:NN \l__simplebnf_lhs_rhs_pair_seq \l__simplebnf_rhs_tl
+
+            \@dep__simplebnf_typeset_lhs:n{\l__simplebnf_lhs_tl}
+            \tl_put_right:Nn \l__simplebnf_table_tl
               {
-                %% \l__term_seq - (lhs, rhs)
-                \seq_pop_left:NN \l__term_seq \l_tmpa_tl
+                & $\in$ &
+              }
+            %% \l__simplebnf_alternative_seq - (rhs:annot | rhs)...
+            \regex_split:NVN \l__simplebnf_new_line_delim_regex \l__simplebnf_rhs_tl \l__simplebnf_alternative_seq
 
-                \simplebnf_typeset_lhs:n{\l_tmpa_tl}
-                \tl_put_right:Nn \l__table_tl
-                  {
-                    & $\in$ & $
-                  }
-                \seq_pop_left:NN \l__term_seq \l_tmpa_tl
-                \tl_put_right:NV \l__table_tl \l_tmpa_tl
-                \tl_put_right:Nn \l__table_tl
-                  {
-                    $ &
-                  }
-              }
-              { \msg_error:nn {simplebnf} { Could not parser ##1 } }
+            \bool_set_true:N \l__simplebnf_first_alternative_bool
+            \seq_map_function:NN \l__simplebnf_alternative_seq \@dep__simplebnf_typeset_rhs:n
           }
+          {
+            \msg_error:nnn { simplebnf } { error-message }
+              { Could~not~parse~#1 }
+          }
       }
+  }
 
-    \tl_put_right:Nn \l__table_tl { \end{tabular} }
-    \tl_use:N \l__table_tl
+
+\seq_new:N \l__input_seq
+\cs_new:Nn \@dep__simplebnf_typeset_grammar:nnn
+  {
+    \regex_set:Nn \l__simplebnf_new_line_delim_regex { #1 }
+    \regex_set:Nn \l__simplebnf_single_line_delim_regex { #2 }
+
+    %% \l__input_seq is a list of term definitions.
+    \regex_split:nnN { ;; } { #3 } \l__input_seq
+
+    \bool_set_true:N \l_tmp_first_term % Is this the first term in this grammar?
+    \seq_map_inline:Nn \l__input_seq
+      {
+        %% If not-first, add newline
+        \bool_if:NTF \l_tmp_first_term
+          {
+            \bool_set_false:N \l_tmp_first_term
+          }
+          {
+            \tl_put_right:Nn \l__simplebnf_table_tl { \\[\SimpleBNFStretch] }
+          }
+
+        \@dep__simplebnf_typeset_production:n { ##1 }
+      }
+  }
+
+
+\NewDocumentCommand \bnfexpr { m } { \texttt { #1 } }
+\NewDocumentCommand \bnfannot { m } { \textit { #1 } }
+
+
+%% Typeset a BNF grammar.
+%% #1 - tabular specification (llcll)
+%% #2 - regexp for newline separator for rhses
+%% #2 - regexp for non-breaking separator for rhses
+%% #3 - grammar
+\NewDocumentEnvironment { bnfgrammar } { O{llcll} O{[^\|]\|[^\|]} O{\|\|} +b }
+  {
+    \msg_warning:nn { simplebnf } { dep }
+    \begin{center}
+      \begin{tabular}{#1}
+        \@dep__simplebnf_typeset_grammar:nnn { #2 } { #3 } { #4 }
+        \tl_use:N \l__simplebnf_table_tl
+      \end{tabular}
     \end{center}
   }
   { }



More information about the tex-live-commits mailing list.