summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
Diffstat (limited to 'macros')
-rw-r--r--macros/latex/contrib/babel-contrib/sorbian/README.md21
-rw-r--r--macros/latex/contrib/babel-contrib/sorbian/lsorbian.dtx4
-rw-r--r--macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdfbin131086 -> 133496 bytes
-rw-r--r--macros/latex/contrib/babel-contrib/sorbian/usorbian.dtx4
-rw-r--r--macros/latex/contrib/babel-contrib/sorbian/usorbian.pdfbin140300 -> 142601 bytes
-rw-r--r--macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.dtx24
-rw-r--r--macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.pdfbin544676 -> 544782 bytes
-rw-r--r--macros/luatex/generic/luakeys/README.md22
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.sty6
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.tex4
-rw-r--r--macros/luatex/generic/luakeys/luakeys-doc.pdf (renamed from macros/luatex/generic/luakeys/luakeys.pdf)bin153582 -> 157648 bytes
-rw-r--r--macros/luatex/generic/luakeys/luakeys-doc.tex726
-rw-r--r--macros/luatex/generic/luakeys/luakeys.lua19
-rw-r--r--macros/luatex/generic/luakeys/luakeys.sty21
-rw-r--r--macros/luatex/generic/luakeys/luakeys.tex660
-rw-r--r--macros/luatex/latex/pyluatex/README.md10
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex-interpreter.py37
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.lua73
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.pdfbin79524 -> 82190 bytes
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.sty29
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.tex47
-rw-r--r--macros/plain/contrib/reverxii/README20
-rw-r--r--macros/plain/contrib/reverxii/README.md21
-rw-r--r--macros/plain/contrib/reverxii/reverxii.dtx379
-rw-r--r--macros/plain/contrib/reverxii/reverxii.pdfbin200023 -> 234070 bytes
-rw-r--r--macros/plain/contrib/reverxii/reverxii.tex373
26 files changed, 1383 insertions, 1117 deletions
diff --git a/macros/latex/contrib/babel-contrib/sorbian/README.md b/macros/latex/contrib/babel-contrib/sorbian/README.md
index dd9cd0ea1d..e088f4abf7 100644
--- a/macros/latex/contrib/babel-contrib/sorbian/README.md
+++ b/macros/latex/contrib/babel-contrib/sorbian/README.md
@@ -1,16 +1,15 @@
-# babel-sorbian – Babel support for Upper and Lower Sorbian
+Lower and Upper Sorbian v1.0j
+-----------------------------
-Version: 2021-02-06 v1.0i
+This is the babel style for Lower and Upper Sorbian.
-The package provides language definitions file for support of
-both Upper and Lower Sorbian, in babel.
-Some shortcuts are defined, as well as translations to the relevant
-language of standard “LaTeX names”.
+**This package is NOT being actively maintained, but bugs might
+got fixed if you report them. If you want to take over maintenance
+of this language style, please contact me at
+http://www.texnia.com/contact.html .**
-## Copyright (C)
-* 1994–2008 Eduard Werner and Johannes Braams
-
-## License
-LATEX Project Public License, version 1.3 or later.
+Changes v1.0j
+------------
+* Fix a typo (a minor change),
diff --git a/macros/latex/contrib/babel-contrib/sorbian/lsorbian.dtx b/macros/latex/contrib/babel-contrib/sorbian/lsorbian.dtx
index 24ac4b7854..47f05afafc 100644
--- a/macros/latex/contrib/babel-contrib/sorbian/lsorbian.dtx
+++ b/macros/latex/contrib/babel-contrib/sorbian/lsorbian.dtx
@@ -37,7 +37,7 @@
%<code>\ProvidesLanguage{lsorbian}
%\fi
%\ProvidesFile{lsorbian.dtx}
- [2021/02/06 v1.0i Lower Sorbian support from the babel system]
+ [2021/11/05 v1.0j Lower Sorbian support from the babel system]
%\iffalse
%% File `lsorbian.dtx'
%% Babel package for LaTeX version 2e
@@ -116,7 +116,7 @@
% \begin{macrocode}
\ifx\l@lowersorbian\@undefined
\ifx\l@lsorbian\@undefined
- \@nopatterns{Lsorbian}
+ \@nopatterns{lsorbian}
\adddialect\l@lsorbian\z@
\let\l@lowersorbian\l@lsorbian
\else
diff --git a/macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdf b/macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdf
index dec82bce1e..74cb5048e3 100644
--- a/macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdf
+++ b/macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdf
Binary files differ
diff --git a/macros/latex/contrib/babel-contrib/sorbian/usorbian.dtx b/macros/latex/contrib/babel-contrib/sorbian/usorbian.dtx
index 2349133b57..0fee7d90b6 100644
--- a/macros/latex/contrib/babel-contrib/sorbian/usorbian.dtx
+++ b/macros/latex/contrib/babel-contrib/sorbian/usorbian.dtx
@@ -35,7 +35,7 @@
%<code>\ProvidesLanguage{usorbian}
%\fi
%\ProvidesFile{usorbian.dtx}
- [2021/02/06 v1.0i Upper Sorbian support from the babel system]
+ [2021/11/05 v1.0j Upper Sorbian support from the babel system]
%\iffalse
%% File `usorbian.dtx'
%% Babel package for LaTeX version 2e
@@ -118,7 +118,7 @@
% \begin{macrocode}
\ifx\l@uppersorbian\@undefined
\ifx\l@usorbian\@undefined
- \@nopatterns{Usorbian}
+ \@nopatterns{usorbian}
\adddialect\l@usorbian\z@
\let\l@uppersorbian\l@usorbian
\else
diff --git a/macros/latex/contrib/babel-contrib/sorbian/usorbian.pdf b/macros/latex/contrib/babel-contrib/sorbian/usorbian.pdf
index edba5ff080..e9f2cf5c02 100644
--- a/macros/latex/contrib/babel-contrib/sorbian/usorbian.pdf
+++ b/macros/latex/contrib/babel-contrib/sorbian/usorbian.pdf
Binary files differ
diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.dtx b/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.dtx
index 06cf351b3c..19a324fb04 100644
--- a/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.dtx
+++ b/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.dtx
@@ -84,7 +84,7 @@ This work has the LPPL maintenance status "author-maintained".
%<spanish-lbx>\ProvidesFile{spanish-philosophy.lbx}
%<french-lbx>\ProvidesFile{french-philosophy.lbx}
%<*standard-bbx|classic-bbx|modern-bbx|verbose-bbx|classic-cbx|modern-cbx|verbose-cbx|italian-lbx|english-lbx|spanish-lbx|french-lbx>
-[2021/07/16 v1.9.8e A set of styles for biblatex]
+[2021/11/06 v1.9.8f A set of styles for biblatex]
%</standard-bbx|classic-bbx|modern-bbx|verbose-bbx|classic-cbx|modern-cbx|verbose-cbx|italian-lbx|english-lbx|spanish-lbx|french-lbx>
%<*driver>
\documentclass[12pt]{ltxdoc}
@@ -253,7 +253,7 @@ This work has the LPPL maintenance status "author-maintained".
%</driver>
% \fi
%
-% \CheckSum{4377}
+% \CheckSum{4376}
%
% \CharacterTable
% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
@@ -271,10 +271,11 @@ This work has the LPPL maintenance status "author-maintained".
% Grave accent \` Left brace \{ Vertical bar \|
% Right brace \} Tilde \~}
%
+% \changes{v1.9.8f}{2021/11/06}{Fixed a bug in the \bibtype{inbook} entries occurring when \bibfield{author} and \bibfield{bookauthor} are the same person.}
% \changes{v1.9.8e}{2021/07/16}{New option \opt{iviemph}. Updated documentation.}
% \changes{v1.9.8d}{2021/04/20}{Changed \texttt{crossrefdata} macro. Updated \file{italian-philosophy.lbx}.}
-% \changes{v1.9.8c}{2020/09/30}{Maintenance release. Corrected a bug in the related entries. Added \cmd{mkrelatedparens} and \cmd{mkrelatedbrackets}.}
-% \changes{v1.9.8b}{2020/05/19}{Maintenance release. Corrected a bug in \bibtype{incollection} and \bibtype{inproceeding} without editor.}
+% \changes{v1.9.8c}{2020/09/30}{Maintenance release. Fixed a bug in the related entries. Added \cmd{mkrelatedparens} and \cmd{mkrelatedbrackets}.}
+% \changes{v1.9.8b}{2020/05/19}{Maintenance release. Fixed a bug in \bibtype{incollection} and \bibtype{inproceeding} without editor.}
% \changes{v1.9.8a}{2018/03/03}{Maintenance release. Corrected missing commas in some \texttt{.lbx} files.}
% \changes{v1.9.8}{2017/11/29}{Maintenance release. Updated macros involving \texttt{extrayear} component.}
% \changes{v1.9.7}{2017/10/05}{Maintenance release. Fixed a bug in the \bibtype{set} bibliography driver.}
@@ -293,11 +294,11 @@ This work has the LPPL maintenance status "author-maintained".
% \changes{v1.3}{2015/10/09}{Maintenance release. Corrected a spurious space in article entries.}
% \changes{v1.2}{2015/09/19}{Maintenance release. New value \opt{superscript} for \opt{editionformat} option.}
% \changes{v1.1}{2015/06/13}{Maintenance release. Updated documentation.}
-% \changes{v1.0}{2015/03/31}{Corrected a bug in \opt{volumeformat} and \opt{volnumformat} options. Change value \opt{romanupp} to \opt{Roman} in \opt{volume format} and \opt{edition format}. Corrected bug in \opt{related format} options: now the related block is not preceded by semicolon when using values \opt{parens} and \opt{brackets} styles. §updated documentation}
-% \changes{v0.9i}{2015/03/14}{Corrected a bug with \opt{shorthandintro} option.}
+% \changes{v1.0}{2015/03/31}{Fixed a bug in \opt{volumeformat} and \opt{volnumformat} options. Change value \opt{romanupp} to \opt{Roman} in \opt{volume format} and \opt{edition format}. Corrected bug in \opt{related format} options: now the related block is not preceded by semicolon when using values \opt{parens} and \opt{brackets} styles. §updated documentation}
+% \changes{v0.9i}{2015/03/14}{Fixed a bug with \opt{shorthandintro} option.}
% \changes{v0.9h}{2015/01/14}{New option \opt{lowscauthors}. Corrected some bugs. Updated documentation.}
-% \changes{v0.9g}{2014/12/12}{Added localization module for spanish. Corrected a bug in \bibtype{inbook} and \bibtype{incollection} entries when using \bibfield{crossref} field. Updated documentation.}
-% \changes{v0.9f}{2014/03/28}{Updated documentation. Corrected a bug in the \opt{volnumformat} option.}
+% \changes{v0.9g}{2014/12/12}{Added localization module for spanish. Fixed a bug in \bibtype{inbook} and \bibtype{incollection} entries when using \bibfield{crossref} field. Updated documentation.}
+% \changes{v0.9f}{2014/03/28}{Updated documentation. Fixed a bug in the \opt{volnumformat} option.}
% \changes{v0.9e}{2014/02/12}{Maintenance release. Updated documentation.}
% \changes{v0.9d}{2013/11/13}{Maintenance release. Corrected some bugs.}
% \changes{v0.9c}{2013/10/15}{Maintenance release. Corrected some bugs.}
@@ -2320,7 +2321,8 @@ This work has the LPPL maintenance status "author-maintained".
\renewbibmacro*{bybookauthor}{%
\ifnamesequal{author}{bookauthor}%
{}%
- {\printnames[default]{bookauthor}}}
+ {\printnames[default]{bookauthor}%
+ \newunit}}
\newbibmacro*{xrefdata}{%
\iffieldundef{volume}
@@ -2683,7 +2685,7 @@ This work has the LPPL maintenance status "author-maintained".
\newbibmacro*{inbook:full}{%
\usebibmacro{bybookauthor}%
- \newunit\newblock
+ \newblock
\usebibmacro{maintitle+booktitle}%
\newunit\newblock
\usebibmacro{byeditor+others}%
@@ -3560,7 +3562,7 @@ This work has the LPPL maintenance status "author-maintained".
\renewbibmacro*{inbook:full}{%
\usebibmacro{bybookauthor}%
- \newunit\newblock
+ \newblock
\usebibmacro{maintitle+booktitle}%
\newunit\newblock
\usebibmacro{byeditor+others}%
diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.pdf
index d989cda6df..0d112a5e68 100644
--- a/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.pdf
+++ b/macros/latex/contrib/biblatex-contrib/biblatex-philosophy/biblatex-philosophy.pdf
Binary files differ
diff --git a/macros/luatex/generic/luakeys/README.md b/macros/luatex/generic/luakeys/README.md
index cb4c429d4c..8c2e5cf8db 100644
--- a/macros/luatex/generic/luakeys/README.md
+++ b/macros/luatex/generic/luakeys/README.md
@@ -50,3 +50,25 @@ or
```
make test
```
+
+### Release a new version
+
+Update copyright in:
+
+* LICENSE
+* luakeys-debug.sty
+* luakeys-debug.tex
+* luakeys.lua
+* luakeys.sty
+* luakeys.tex
+* README.md
+
+Update version in:
+
+* luakeys-doc.tex
+* luakeys-debug.sty
+* luakeys.sty
+
+Summarize the changes in the luakeys-doc.tex as changes.
+
+Create a new git tag `git tag -sa v0.3`. Prefix the version with “v”.
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.sty b/macros/luatex/generic/luakeys/luakeys-debug.sty
index a96d2d51c6..ca800bfad7 100644
--- a/macros/luatex/generic/luakeys/luakeys-debug.sty
+++ b/macros/luatex/generic/luakeys/luakeys-debug.sty
@@ -13,10 +13,10 @@
%
% The Current Maintainer of this work is Josef Friedrich.
%
-% This work consists of the files luakeys.lua, luakeys-debug.sty
-% and luakeys-debug.tex.
+% This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
+% luakeys-debug.sty and luakeys-debug.tex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys-debug}[2021/09/19 Debug package for luakeys.]
+\ProvidesPackage{luakeys-debug}[2021/11/05 v0.3 Debug package for luakeys.]
\input luakeys-debug.tex
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.tex b/macros/luatex/generic/luakeys/luakeys-debug.tex
index b63e67e050..a6cd7c85c2 100644
--- a/macros/luatex/generic/luakeys/luakeys-debug.tex
+++ b/macros/luatex/generic/luakeys/luakeys-debug.tex
@@ -13,8 +13,8 @@
%
% The Current Maintainer of this work is Josef Friedrich.
%
-% This work consists of the files luakeys.lua, luakeys-debug.sty
-% and luakeys-debug.tex.
+% This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
+% luakeys-debug.sty and luakeys-debug.tex.
\directlua{
luakeys = require('luakeys')
diff --git a/macros/luatex/generic/luakeys/luakeys.pdf b/macros/luatex/generic/luakeys/luakeys-doc.pdf
index c65765d5b9..3c28176a7d 100644
--- a/macros/luatex/generic/luakeys/luakeys.pdf
+++ b/macros/luatex/generic/luakeys/luakeys-doc.pdf
Binary files differ
diff --git a/macros/luatex/generic/luakeys/luakeys-doc.tex b/macros/luatex/generic/luakeys/luakeys-doc.tex
new file mode 100644
index 0000000000..b7e525fc4b
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys-doc.tex
@@ -0,0 +1,726 @@
+\documentclass{ltxdoc}
+
+\usepackage{hyperref}
+\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+
+\usepackage{mdframed}
+\usepackage{minted}
+\usepackage{luakeys-debug}
+\usepackage{multicol}
+\usepackage{luacode}
+\usepackage{syntax}
+\usemintedstyle{friendly}
+\BeforeBeginEnvironment{minted}{\begin{mdframed}}
+\AfterEndEnvironment{minted}{\end{mdframed}}
+\setminted{
+ breaklines=true,
+ fontsize=\footnotesize,
+}
+\def\lua#1{\mintinline{lua}|#1|}
+
+\begin{document}
+
+\providecommand*{\url}{\texttt}
+
+\title{The \textsf{luakeys} package}
+\author{%
+ Josef Friedrich\\%
+ \url{josef@friedrich.rocks}\\%
+ \href{https://github.com/Josef-Friedrich/luakeys}{github.com/Josef-Friedrich/luakeys}%
+}
+\date{v0.3 from 2021/11/05}
+
+\maketitle
+
+\vfill
+
+%\luakeysdebug{level1={level2={level3={dim=1cm,bool=true,num=-1e-03,str=lua}}}}
+
+\begin{minted}{lua}
+local luakeys = require('luakeys')
+local kv = luakeys.parse('level1={level2={level3={dim=1cm,bool=true,num=-1e-03,str=lua}}}')
+luakeys.print(kv)
+\end{minted}
+
+\noindent
+Result:
+
+\begin{center}
+\begin{minted}{lua}
+{
+ ['level1'] = {
+ ['level2'] = {
+ ['level3'] = {
+ ['dim'] = 1864679,
+ ['bool'] = true,
+ ['num'] = -0.001
+ ['str'] = 'lua',
+ }
+ }
+ }
+}
+\end{minted}
+\end{center}
+
+\vfill
+
+\strut
+
+\newpage
+
+\tableofcontents
+
+\newpage
+
+\section{Introduction}
+
+\noindent
+|luakeys| is a Lua module that can parse key-value options like the
+\TeX{} packages \href{https://www.ctan.org/pkg/keyval}{keyval},
+\href{https://www.ctan.org/pkg/kvsetkeys}{kvsetkeys},
+\href{https://www.ctan.org/pkg/kvoptions}{kvoptions},
+\href{https://www.ctan.org/pkg/xkeyval}{xkeyval},
+\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc. do. |luakeys|,
+however, accompilshes this task entirely, by using the Lua language and
+doesn’t rely on \TeX{}. Therefore this package can only be used with the
+\TeX{} engine Lua\TeX{}. Since |luakeys| uses
+\href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}, the parsing
+mechanism should be pretty robust.
+
+The TUGboat article
+\href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf}
+{“Implementing key–value input: An introduction” (Volume 30 (2009), No. 1)}
+by Joseph Wright and Christian Feuersänger gives a good overview of the
+available key-value packages.
+
+This package would not be possible without the article
+\href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf}
+{Parsing complex data formats in LuaTEX with LPEG (Volume 40 (2019), No. 2)}.
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Usage}
+
+%%
+%
+%%
+
+\subsection{Using the Lua module \texttt{luakeys.lua}}
+
+The core functionality of this package is realized in Lua. So you can
+use \texttt{luakeys} without using the wrapper \TeX{} files
+\texttt{luakeys.sty} and \texttt{luakeys.tex}.
+
+\begin{minted}{latex}
+\documentclass{article}
+\directlua{
+ luakeys = require('luakeys')
+}
+
+\newcommand{\helloworld}[2][]{
+ \directlua{
+ local keys = luakeys.parse('\luaescapestring{\unexpanded{#1}}')
+ luakeys.print(keys)
+ local marg = '#2'
+ tex.print(keys.greeting .. ', ' .. marg .. keys.punctuation)
+ }
+}
+\begin{document}
+\helloworld[greeting=hello,punctuation=!]{world}
+\end{document}
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{Using the Lua\LaTeX{} wrapper \texttt{luakeys.sty}}
+
+The supplied Lua\LaTeX{} file is quite small:
+
+\begin{minted}{latex}
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{luakeys}
+\directlua{luakeys = require('luakeys')}
+\end{minted}
+
+\noindent
+It loads the Lua module into the global variable \texttt{luakeys}.
+
+\begin{minted}{latex}
+\documentclass{article}
+\usepackage{luakeys}
+
+\begin{document}
+ \directlua{
+ local keys = luakeys.parse('one,two,three')
+ tex.print(keys[1])
+ tex.print(keys[2])
+ tex.print(keys[3])
+ }
+\end{document}
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{Using the plain Lua\TeX{} wrapper \texttt{luakeys.tex}}
+
+Even smaller is the file \texttt{luakeys.tex}. It consists of only one
+line:
+
+\begin{minted}{latex}
+\directlua{luakeys = require('luakeys')}
+\end{minted}
+
+\noindent
+It does the same as the Lua\LaTeX{} wrapper and loads the Lua module
+\texttt{luakeys.lua} into the global variable \texttt{luakeys}.
+
+\begin{minted}{latex}
+\input luakeys.tex
+
+\directlua{
+ local keys = luakeys.parse('one,two,three')
+ tex.print(keys[1])
+ tex.print(keys[2])
+ tex.print(keys[3])
+}
+\bye
+\end{minted}
+
+\section{Syntax of the recognized key-value format}
+
+%%
+%
+%%
+
+\subsection{A attempt to put the syntax into words}
+
+A key-value pair is definied by an equal sign (\texttt{key=value}).
+Several key-value pairs or values without keys are lined up with commas
+(\texttt{key=value,value}) and build a key-value list. Curly brackets
+can be used to create a recursive data structure of nested key-value
+lists (\texttt{level1=\{level2=\{key=value,value\}\}}).
+
+%%
+%
+%%
+
+\subsection{An (incomplete) attempt to put the syntax into the Extended Backus-Naur Form}
+
+\begin{grammar}
+<list> ::= <list-item> | <list-item> <list>
+
+<list-item> ::= ( <key-value-pair> | <value-without-key> ) [ `,' ]
+
+<list-container> ::== `{' <list> `}'
+
+<value> ::= <boolean>
+ \alt <dimension>
+ \alt <number>
+ \alt <string-quoted>
+ \alt <string-unquoted>
+
+<sign> ::= `-' | `+'
+
+<integer> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
+
+<unit> ::= `bp' | `BP'
+ \alt `cc' | `CC'
+ \alt `cm' | `CM'
+ \alt `dd' | `DD'
+ \alt `em' | `EM'
+ \alt `ex' | `EX'
+ \alt `in' | `IN'
+ \alt `mm' | `MM'
+ \alt `nc' | `NC'
+ \alt `nd' | `ND'
+ \alt `pc' | `PC'
+ \alt `pt' | `PT'
+ \alt `sp' | `SP'
+
+\end{grammar}
+
+... to be continued
+
+%%
+%
+%%
+
+\subsection{Recognized data types}
+
+\subsubsection{boolean}
+
+The strings \texttt{true}, \texttt{TRUE} and \texttt{True} are converted
+into Lua’s boolean type \lua{true}, the strings \texttt{false},
+\texttt{FALSE} and \texttt{False} into \lua{false}.
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ lower case true = true,
+ upper case true = TRUE,
+ title case true = True
+ lower case false = false,
+ upper case false = FALSE,
+ title case false = False,
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['lower case true'] = true,
+ ['upper case true'] = true,
+ ['title case true'] = true,
+ ['lower case false'] = false,
+ ['upper case false'] = false
+ ['title case false'] = false,
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsubsection{number}
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ num1 = 4,
+ num2 = -4,
+ num3 = 0.4,
+ num4 = 4.57e-3,
+ num5 = 0.3e12,
+ num6 = 5e+20
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['num1'] = 4,
+ ['num2'] = -4,
+ ['num3'] = 0.4,
+ ['num4'] = 0.00457,
+ ['num5'] = 300000000000.0,
+ ['num6'] = 5e+20
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsubsection{dimension}
+
+|luakeys| detects \TeX{} dimensions and automatically converts the
+dimensions into scaled points using the function \lua{tex.sp(dim)}. Use
+the option \lua{convert_dimensions} of the function
+\lua{parse(kv_string, options)} to disalbe the automatic conversion.
+
+\begin{minted}{lua}
+local result = parse('dim=1cm', {
+ convert_dimensions = false,
+})
+\end{minted}
+
+\noindent
+If you want to convert a scale point into a unit string you can use the module
+\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}{lualibs-util-dim.lua}.
+
+\begin{minted}{latex}
+\begin{luacode}
+require('lualibs')
+tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s'))
+\end{luacode}
+\end{minted}
+
+\begin{center}
+\begin{tabular}{rl}
+\textbf{Unit name} & \textbf{Description} \\\hline
+bp & big point \\
+cc & cicero \\
+cm & centimeter \\
+dd & didot \\
+em & horizontal measure of \emph{M} \\
+ex & vertical measure of \emph{x} \\
+in & inch \\
+mm & milimeter \\
+nc & new cicero \\
+nd & new didot \\
+pc & pica \\
+pt & point \\
+sp & scaledpoint \\
+\end{tabular}
+\end{center}
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ bp = 1bp,
+ cc = 1cc,
+ cm = 1cm,
+ dd = 1dd,
+ em = 1em,
+ ex = 1ex,
+ in = 1in,
+ mm = 1mm,
+ nc = 1nc,
+ nd = 1nd,
+ pc = 1pc,
+ pt = 1pt,
+ sp = 1sp,
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['bp'] = 65781,
+ ['cc'] = 841489,
+ ['cm'] = 1864679,
+ ['dd'] = 70124,
+ ['em'] = 655360,
+ ['ex'] = 282460,
+ ['in'] = 4736286,
+ ['mm'] = 186467,
+ ['nc'] = 839105,
+ ['nd'] = 69925,
+ ['pc'] = 786432,
+ ['pt'] = 65536,
+ ['sp'] = 1,
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\subsubsection{string}
+
+There are two ways to specify strings: With or without quotes. If the
+text have to contain commas or equal signs, then double quotation
+marks must be used.
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ without quotes = no commas and equal signs are allowed,
+ with double quotes = ", and = are allowed",
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['without quotes'] = 'no commas and equal signs are allowed',
+ ['with double quotes'] = ', and = are allowed',
+}
+\end{minted}
+\end{multicols}
+
+\subsubsection{Standalone values}
+
+Standalone values are values without a key. They are converted into an
+array. In Lua an array is a table with numeric indexes (The first index
+is 1).
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{one,two,three}
+\end{minted}
+\columnbreak
+\begin{minted}{lua}
+{ 'one', 'two', 'three' }
+\end{minted}
+
+\noindent
+is equivalent to
+
+\begin{minted}{lua}
+{
+ [1] = 'one',
+ [2] = 'two',
+ [3] = 'three',
+}
+\end{minted}
+\end{multicols}
+
+\noindent
+All recognized data types can be used as standalone values.
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{one,2,3cm}
+\end{minted}
+\columnbreak
+\begin{minted}{lua}
+{ 'one', 2, 5594039 }
+\end{minted}
+\end{multicols}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Exported functions of the Lua module \texttt{luakeys.lua}}
+
+To learn more about the individual functions (local functions), please
+read the \href{https://josef-friedrich.github.io/luakeys/}{source code
+documentation}, which was created with
+\href{http://stevedonovan.github.io/ldoc/}{LDoc}. The Lua module exports
+this functions:
+
+\begin{minted}{lua}
+local luakeys = require('luakeys')
+local parse = luakeys.parse
+local render = luakeys.render
+--local print = luakeys.print -- That would overwrite the built-in Lua function
+local save = luakeys.save
+local get = luakeys.get
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{parse(kv\_string, options)}: table}
+
+The function \lua{parse(input_string, options)} is the main method of
+this module. It parses a key-value string into a Lua table.
+
+\begin{minted}{latex}
+\newcommand{\mykeyvalcmd}[1][]{
+ \directlua{
+ result = luakeys.parse('#1')
+ luakeys.print(result)
+ }
+ #2
+}
+\mykeyvalcmd[one=1]{test}
+\end{minted}
+
+\noindent
+In plain \TeX:
+
+\begin{minted}{latex}
+\def\mykeyvalcommand#1{
+ \directlua{
+ result = luakeys.parse('#1')
+ luakeys.print(result)
+ }
+}
+\mykeyvalcmd{one=1}
+\end{minted}
+
+\noindent
+The function can be called with a options table. This two options are
+supported.
+
+\begin{minted}{lua}
+local result = parse('one,two,three', {
+ convert_dimensions = false,
+ unpack_single_array_value = false
+})
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{render(tbl)}: string}
+
+The function \lua{render(tbl)} reverses the function
+\lua{parse(kv_string)}. It takes a Lua table and converts this table
+into a key-value string. The resulting string usually has a different
+order as the input table.
+
+\begin{minted}{lua}
+result = luakeys.parse('one=1,two=2,tree=3,')
+print(luakeys.render(result))
+--- one=1,two=2,tree=3,
+--- or:
+--- two=2,one=1,tree=3,
+--- or:
+--- ...
+\end{minted}
+
+\noindent
+In Lua only tables with 1-based consecutive integer keys (a.k.a. array
+tables) can be parsed in order.
+
+\begin{minted}{lua}
+result = luakeys.parse('one,two,three')
+print(luakeys.render(result))
+--- one,two,three, (always)
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{print(tbl): void}}
+
+The function \lua{print(tbl)} pretty prints a Lua table to standard
+output (stdout). It is a utility function that can be used to debug and
+inspect the resulting Lua table of the function \lua{parse}. You have to
+compile your \TeX{} document in a console to see the terminal output.
+
+%\luakeysdebug{level1={level2={key=value}}}
+
+\begin{minted}{lua}
+result = luakeys.parse('level1={level2={key=value}}')
+luakeys.print(result)
+\end{minted}
+
+\noindent
+The output should look like this:
+
+\begin{minted}{md}
+{
+ ['level1'] = {
+ ['level2'] = {
+ ['key'] = 'value',
+ },
+}
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{save(identifier, result): void}}
+
+The function \lua{save(identifier, result)} saves a result (a
+table from a previous run of \lua{parse}) under an identifier.
+Therefore, it is not necessary to pollute the global namespace to
+store results for the later usage.
+
+%%
+%
+%%
+
+\subsection{\texttt{get(identifier): table}}
+
+The function \lua{get(identifier)} retrieves a saved result from the
+result store.
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Debug packages}
+
+Two small debug packages are included in |luakeys|. One debug package
+can be used in \LaTeX{} (luakeys-debug.sty) and one can be used in plain
+\TeX{} (luakeys-debug.tex). Both packages provide only one command:
+|\luakeysdebug{kv-string}|
+
+\begin{minted}{latex}
+\luakeysdebug{one,two,three}
+\end{minted}
+
+\noindent
+Then the following output should appear in the document:
+\bigskip
+
+\luakeysdebug{one,two,three}
+
+%%
+%
+%%
+
+\subsection{For plain \TeX: luakeys-debug.tex}
+
+An example of how to use the command in plain \TeX:
+
+\begin{minted}{latex}
+\input luakeys-debug.tex
+\luakeysdebug{one,two,three}
+\bye
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{For \LaTeX: luakeys-debug.sty}
+
+An example of how to use the command in \LaTeX:
+
+\begin{minted}{latex}
+\documentclass{article}
+\usepackage{luakeys-debug}
+\begin{document}
+\luakeysdebug[
+ unpack single array values=false,
+ convert dimensions=false
+]{one,two,three}
+\end{document}
+\end{minted}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Implementation}
+
+%%
+%
+%%
+
+\subsection{luakeys.lua}
+
+\inputminted[linenos=true]{lua}{luakeys.lua}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsection{luakeys-debug.tex}
+
+\inputminted[linenos=true]{latex}{luakeys-debug.tex}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsection{luakeys-debug.sty}
+
+\inputminted[linenos=true]{latex}{luakeys-debug.sty}
+
+\changes{v0.1}{2021/01/18}{Inital release}
+\changes{v0.2}{2021/09/19}{
+* Allow all recognized data types as keys
+* Allow TeX macros in the values
+* New public Lua functions: save(identifier, result), get(identifier)
+}
+\changes{v0.3}{2021/11/05}{
+* Add a LuaLaTeX wrapper “luakeys.sty”
+* Add a plain LuaTeX wrapper “luakeys.tex”
+* Rename the previous documentation file “luakeys.tex” to luakeys-doc.tex”
+}
+\pagebreak
+\PrintChanges
+\pagebreak
+\PrintIndex
+\end{document}
diff --git a/macros/luatex/generic/luakeys/luakeys.lua b/macros/luatex/generic/luakeys/luakeys.lua
index b026413800..05b3d94e1f 100644
--- a/macros/luatex/generic/luakeys/luakeys.lua
+++ b/macros/luatex/generic/luakeys/luakeys.lua
@@ -1,4 +1,4 @@
--- luakeys-debug.tex
+-- luakeys.lua
-- Copyright 2021 Josef Friedrich
--
-- This work may be distributed and/or modified under the
@@ -13,8 +13,8 @@
--
-- The Current Maintainer of this work is Josef Friedrich.
--
--- This work consists of the files luakeys.lua, luakeys-debug.sty
--- and luakeys-debug.tex.
+-- This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
+-- luakeys-debug.sty and luakeys-debug.tex.
--- A key-value parser written with Lpeg.
--
@@ -167,7 +167,11 @@ local function generate_parser(options)
lpeg.V('dimension') +
lpeg.V('number') +
lpeg.V('string_quoted') +
- lpeg.V('string_unquoted'),
+ lpeg.V('string_unquoted') +
+ lpeg.V('array'),
+
+ array =
+ ws('{') * lpeg.Ct((lpeg.V('value') * ws(',')^-1)^0) * ws('}'),
boolean =
boolean_true * lpeg.Cc(true) +
@@ -175,6 +179,9 @@ local function generate_parser(options)
dimension = build_dimension_pattern(),
+ number =
+ white_space^0 * (number / tonumber) * white_space^0,
+
string_quoted =
white_space^0 * lpeg.P('"') *
lpeg.C((lpeg.P('\\"') + 1 - lpeg.P('"'))^0) *
@@ -188,10 +195,6 @@ local function generate_parser(options)
white_space^0,
word_unquoted = (1 - white_space - lpeg.S('{},='))^1;
-
- number =
- white_space^0 * (number / tonumber) * white_space^0,
-
})
end
diff --git a/macros/luatex/generic/luakeys/luakeys.sty b/macros/luatex/generic/luakeys/luakeys.sty
new file mode 100644
index 0000000000..6717b85b57
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys.sty
@@ -0,0 +1,21 @@
+%% luakeys.sty
+%% Copyright 2021 Josef Friedrich
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2008/05/04 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Josef Friedrich.
+%
+% This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
+% luakeys-debug.sty and luakeys-debug.tex.
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{luakeys}[2021/11/05 v0.3 Parsing key-value options using Lua.]
+\directlua{luakeys = require('luakeys')}
diff --git a/macros/luatex/generic/luakeys/luakeys.tex b/macros/luatex/generic/luakeys/luakeys.tex
index fc465f46ae..68fc92d8ee 100644
--- a/macros/luatex/generic/luakeys/luakeys.tex
+++ b/macros/luatex/generic/luakeys/luakeys.tex
@@ -1,651 +1,19 @@
-\documentclass{ltxdoc}
-
-\usepackage{hyperref}
-\EnableCrossrefs
-\CodelineIndex
-\RecordChanges
-
-\usepackage{mdframed}
-\usepackage{minted}
-\usepackage{luakeys-debug}
-\usepackage{multicol}
-\usepackage{luacode}
-\usepackage{syntax}
-\usemintedstyle{friendly}
-\BeforeBeginEnvironment{minted}{\begin{mdframed}}
-\AfterEndEnvironment{minted}{\end{mdframed}}
-\setminted{
- breaklines=true,
- fontsize=\footnotesize,
-}
-\def\lua#1{\mintinline{lua}|#1|}
-
-\begin{document}
-
-\providecommand*{\url}{\texttt}
-
-\title{The \textsf{luakeys} package}
-\author{%
- Josef Friedrich\\%
- \url{josef@friedrich.rocks}\\%
- \href{https://github.com/Josef-Friedrich/luakeys}{github.com/Josef-Friedrich/luakeys}%
-}
-\date{v0.2 from 2021/09/19}
-
-\maketitle
-
-\vfill
-
-%\luakeysdebug{level1={level2={level3={dim=1cm,bool=true,num=-1e-03,str=lua}}}}
-
-\begin{minted}{lua}
-local luakeys = require('luakeys')
-local kv = luakeys.parse('level1={level2={level3={dim=1cm,bool=true,num=-1e-03,str=lua}}}')
-luakeys.print(kv)
-\end{minted}
-
-\noindent
-Result:
-
-\begin{center}
-\begin{minted}{lua}
-{
- ['level1'] = {
- ['level2'] = {
- ['level3'] = {
- ['dim'] = 1864679,
- ['bool'] = true,
- ['num'] = -0.001
- ['str'] = 'lua',
- }
- }
- }
-}
-\end{minted}
-\end{center}
-
-\vfill
-
-\strut
-
-\newpage
-
-\tableofcontents
-
-\newpage
-
-\section{Introduction}
-
-\noindent
-|luakeys| is a Lua module that can parse key-value options like the
-\TeX{} packages \href{https://www.ctan.org/pkg/keyval}{keyval},
-\href{https://www.ctan.org/pkg/kvsetkeys}{kvsetkeys},
-\href{https://www.ctan.org/pkg/kvoptions}{kvoptions},
-\href{https://www.ctan.org/pkg/xkeyval}{xkeyval},
-\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc. do. |luakeys|,
-however, accompilshes this task entirely, by using the Lua language and
-doesn’t rely on \TeX{}. Therefore this package can only be used with the
-\TeX{} engine Lua\TeX{}. Since |luakeys| uses
-\href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}, the parsing
-mechanism should be pretty robust.
-
-The TUGboat article
-\href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf}
-{“Implementing key–value input: An introduction” (Volume 30 (2009), No. 1)}
-by Joseph Wright and Christian Feuersänger gives a good overview of the
-available key-value packages.
-
-This package would not be possible without the article
-\href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf}
-{Parsing complex data formats in LuaTEX with LPEG (Volume 40 (2019), No. 2)}.
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Usage}
-
-\begin{minted}{latex}
-\documentclass{article}
-\directlua{
- luakeys = require('luakeys')
-}
-
-\newcommand{\helloworld}[2][]{
- \directlua{
- local keys = luakeys.parse('\luaescapestring{\unexpanded{#1}}')
- luakeys.print(keys)
- local marg = '#2'
- tex.print(keys.greeting .. ', ' .. marg .. keys.punctuation)
- }
-}
-\begin{document}
-\helloworld[greeting=hello,punctuation=!]{world}
-\end{document}
-\end{minted}
-
-\section{Syntax of the recognized key-value format}
-
-%%
-%
-%%
-
-\subsection{A attempt to put the syntax into words}
-
-A key-value pair is definied by an equal sign (\texttt{key=value}).
-Several key-value pairs or values without keys are lined up with commas
-(\texttt{key=value,value}) and build a key-value list. Curly brackets
-can be used to create a recursive data structure of nested key-value
-lists (\texttt{level1=\{level2=\{key=value,value\}\}}).
-
-%%
-%
-%%
-
-\subsection{An (incomplete) attempt to put the syntax into the Extended Backus-Naur Form}
-
-\begin{grammar}
-<list> ::= <list-item> | <list-item> <list>
-
-<list-item> ::= ( <key-value-pair> | <value-without-key> ) [ `,' ]
-
-<list-container> ::== `{' <list> `}'
-
-<value> ::= <boolean>
- \alt <dimension>
- \alt <number>
- \alt <string-quoted>
- \alt <string-unquoted>
-
-<sign> ::= `-' | `+'
-
-<integer> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
-
-<unit> ::= `bp' | `BP'
- \alt `cc' | `CC'
- \alt `cm' | `CM'
- \alt `dd' | `DD'
- \alt `em' | `EM'
- \alt `ex' | `EX'
- \alt `in' | `IN'
- \alt `mm' | `MM'
- \alt `nc' | `NC'
- \alt `nd' | `ND'
- \alt `pc' | `PC'
- \alt `pt' | `PT'
- \alt `sp' | `SP'
-
-\end{grammar}
-
-... to be continued
-
-%%
-%
-%%
-
-\subsection{Recognized data types}
-
-\subsubsection{boolean}
-
-The strings \texttt{true}, \texttt{TRUE} and \texttt{True} are converted
-into Lua’s boolean type \lua{true}, the strings \texttt{false},
-\texttt{FALSE} and \texttt{False} into \lua{false}.
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{
- lower case true = true,
- upper case true = TRUE,
- title case true = True
- lower case false = false,
- upper case false = FALSE,
- title case false = False,
-}
-\end{minted}
-\begin{minted}{lua}
-{
- ['lower case true'] = true,
- ['upper case true'] = true,
- ['title case true'] = true,
- ['lower case false'] = false,
- ['upper case false'] = false
- ['title case false'] = false,
-}
-\end{minted}
-\end{multicols}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsubsection{number}
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{
- num1 = 4,
- num2 = -4,
- num3 = 0.4,
- num4 = 4.57e-3,
- num5 = 0.3e12,
- num6 = 5e+20
-}
-\end{minted}
-\begin{minted}{lua}
-{
- ['num1'] = 4,
- ['num2'] = -4,
- ['num3'] = 0.4,
- ['num4'] = 0.00457,
- ['num5'] = 300000000000.0,
- ['num6'] = 5e+20
-}
-\end{minted}
-\end{multicols}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsubsection{dimension}
-
-|luakeys| detects \TeX{} dimensions and automatically converts the
-dimensions into scaled points using the function \lua{tex.sp(dim)}. Use
-the option \lua{convert_dimensions} of the function
-\lua{parse(kv_string, options)} to disalbe the automatic conversion.
-
-\begin{minted}{lua}
-local result = parse('dim=1cm', {
- convert_dimensions = false,
-})
-\end{minted}
-
-\noindent
-If you want to convert a scale point into a unit string you can use the module
-\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}{lualibs-util-dim.lua}.
-
-\begin{minted}{latex}
-\begin{luacode}
-require('lualibs')
-tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s'))
-\end{luacode}
-\end{minted}
-
-\begin{center}
-\begin{tabular}{rl}
-\textbf{Unit name} & \textbf{Description} \\\hline
-bp & big point \\
-cc & cicero \\
-cm & centimeter \\
-dd & didot \\
-em & horizontal measure of \emph{M} \\
-ex & vertical measure of \emph{x} \\
-in & inch \\
-mm & milimeter \\
-nc & new cicero \\
-nd & new didot \\
-pc & pica \\
-pt & point \\
-sp & scaledpoint \\
-\end{tabular}
-\end{center}
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{
- bp = 1bp,
- cc = 1cc,
- cm = 1cm,
- dd = 1dd,
- em = 1em,
- ex = 1ex,
- in = 1in,
- mm = 1mm,
- nc = 1nc,
- nd = 1nd,
- pc = 1pc,
- pt = 1pt,
- sp = 1sp,
-}
-\end{minted}
-\begin{minted}{lua}
-{
- ['bp'] = 65781,
- ['cc'] = 841489,
- ['cm'] = 1864679,
- ['dd'] = 70124,
- ['em'] = 655360,
- ['ex'] = 282460,
- ['in'] = 4736286,
- ['mm'] = 186467,
- ['nc'] = 839105,
- ['nd'] = 69925,
- ['pc'] = 786432,
- ['pt'] = 65536,
- ['sp'] = 1,
-}
-\end{minted}
-\end{multicols}
-
-%%
-%
-%%
-
-\subsubsection{string}
-
-There are two ways to specify strings: With or without quotes. If the
-text have to contain commas or equal signs, then double quotation
-marks must be used.
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{
- without quotes = no commas and equal signs are allowed,
- with double quotes = ", and = are allowed",
-}
-\end{minted}
-\begin{minted}{lua}
-{
- ['without quotes'] = 'no commas and equal signs are allowed',
- ['with double quotes'] = ', and = are allowed',
-}
-\end{minted}
-\end{multicols}
-
-\subsubsection{Standalone values}
-
-Standalone values are values without a key. They are converted into an
-array. In Lua an array is a table with numeric indexes (The first index
-is 1).
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{one,two,three}
-\end{minted}
-\columnbreak
-\begin{minted}{lua}
-{ 'one', 'two', 'three' }
-\end{minted}
-
-\noindent
-is equivalent to
-
-\begin{minted}{lua}
-{
- [1] = 'one',
- [2] = 'two',
- [3] = 'three',
-}
-\end{minted}
-\end{multicols}
-
-\noindent
-All recognized data types can be used as standalone values.
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{one,2,3cm}
-\end{minted}
-\columnbreak
-\begin{minted}{lua}
-{ 'one', 2, 5594039 }
-\end{minted}
-\end{multicols}
-
-%-----------------------------------------------------------------------
+%% luakeys.tex
+%% Copyright 2021 Josef Friedrich
%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Exported functions of the Lua module \texttt{luakeys.lua}}
-
-To learn more about the individual functions (local functions), please
-read the \href{https://josef-friedrich.github.io/luakeys/}{source code
-documentation}, which was created with
-\href{http://stevedonovan.github.io/ldoc/}{LDoc}. The Lua module exports
-this functions:
-
-\begin{minted}{lua}
-local luakeys = require('luakeys')
-local parse = luakeys.parse
-local render = luakeys.render
---local print = luakeys.print -- That would overwrite the built-in Lua function
-local save = luakeys.save
-local get = luakeys.get
-\end{minted}
-
-%%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2008/05/04 or later.
%
-%%
-
-\subsection{\texttt{parse(kv\_string, options)}: table}
-
-The function \lua{parse(input_string, options)} is the main method of
-this module. It parses a key-value string into a Lua table.
-
-\begin{minted}{latex}
-\newcommand{\mykeyvalcmd}[1][]{
- \directlua{
- result = luakeys.parse('#1')
- luakeys.print(result)
- }
- #2
-}
-\mykeyvalcmd[one=1]{test}
-\end{minted}
-
-\noindent
-In plain \TeX:
-
-\begin{minted}{latex}
-\def\mykeyvalcommand#1{
- \directlua{
- result = luakeys.parse('#1')
- luakeys.print(result)
- }
-}
-\mykeyvalcmd{one=1}
-\end{minted}
-
-\noindent
-The function can be called with a options table. This two options are
-supported.
-
-\begin{minted}{lua}
-local result = parse('one,two,three', {
- convert_dimensions = false,
- unpack_single_array_value = false
-})
-\end{minted}
-
-%%
+% This work has the LPPL maintenance status `maintained'.
%
-%%
-
-\subsection{\texttt{render(tbl)}: string}
-
-The function \lua{render(tbl)} reverses the function
-\lua{parse(kv_string)}. It takes a Lua table and converts this table
-into a key-value string. The resulting string usually has a different
-order as the input table.
-
-\begin{minted}{lua}
-result = luakeys.parse('one=1,two=2,tree=3,')
-print(luakeys.render(result))
---- one=1,two=2,tree=3,
---- or:
---- two=2,one=1,tree=3,
---- or:
---- ...
-\end{minted}
-
-\noindent
-In Lua only tables with 1-based consecutive integer keys (a.k.a. array
-tables) can be parsed in order.
-
-\begin{minted}{lua}
-result = luakeys.parse('one,two,three')
-print(luakeys.render(result))
---- one,two,three, (always)
-\end{minted}
-
-%%
-%
-%%
-
-\subsection{\texttt{print(tbl): void}}
-
-The function \lua{print(tbl)} pretty prints a Lua table to standard
-output (stdout). It is a utility function that can be used to debug and
-inspect the resulting Lua table of the function \lua{parse}. You have to
-compile your \TeX{} document in a console to see the terminal output.
-
-%\luakeysdebug{level1={level2={key=value}}}
-
-\begin{minted}{lua}
-result = luakeys.parse('level1={level2={key=value}}')
-luakeys.print(result)
-\end{minted}
-
-\noindent
-The output should look like this:
-
-\begin{minted}{md}
-{
- ['level1'] = {
- ['level2'] = {
- ['key'] = 'value',
- },
-}
-\end{minted}
-
-%%
+% The Current Maintainer of this work is Josef Friedrich.
%
-%%
-
-\subsection{\texttt{save(identifier, result): void}}
-
-The function \lua{save(identifier, result)} saves a result (a
-table from a previous run of \lua{parse}) under an identifier.
-Therefore, it is not necessary to pollute the global namespace to
-store results for the later usage.
-
-%%
-%
-%%
-
-\subsection{\texttt{get(identifier): table}}
-
-The function \lua{get(identifier)} retrieves a saved result from the
-result store.
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Debug packages}
-
-Two small debug packages are included in |luakeys|. One debug package
-can be used in \LaTeX{} (luakeys-debug.sty) and one can be used in plain
-\TeX{} (luakeys-debug.tex). Both packages provide only one command:
-|\luakeysdebug{kv-string}|
-
-\begin{minted}{latex}
-\luakeysdebug{one,two,three}
-\end{minted}
-
-\noindent
-Then the following output should appear in the document:
-\bigskip
-
-\luakeysdebug{one,two,three}
-
-%%
-%
-%%
-
-\subsection{For plain \TeX: luakeys-debug.tex}
-
-An example of how to use the command in plain \TeX:
-
-\begin{minted}{latex}
-\input luakeys-debug.tex
-\luakeysdebug{one,two,three}
-\bye
-\end{minted}
-
-%%
-%
-%%
-
-\subsection{For \LaTeX: luakeys-debug.sty}
-
-An example of how to use the command in \LaTeX:
-
-\begin{minted}{latex}
-\documentclass{article}
-\usepackage{luakeys-debug}
-\begin{document}
-\luakeysdebug[
- unpack single array values=false,
- convert dimensions=false
-]{one,two,three}
-\end{document}
-\end{minted}
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Implementation}
-
-%%
-%
-%%
-
-\subsection{luakeys.lua}
-
-\inputminted[linenos=true]{lua}{luakeys.lua}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsection{luakeys-debug.tex}
-
-\inputminted[linenos=true]{latex}{luakeys-debug.tex}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsection{luakeys-debug.sty}
-
-\inputminted[linenos=true]{latex}{luakeys-debug.sty}
+% This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
+% luakeys-debug.sty and luakeys-debug.tex.
-\changes{v0.1}{2021/01/18}{Inital release}
-\changes{v0.2}{2021/09/19}{
-* Allow all recognized data types as keys
-* Allow TeX macros in the values
-* New public Lua functions: save(identifier, result), get(identifier)
-}
-\pagebreak
-\PrintChanges
-\pagebreak
-\PrintIndex
-\end{document}
+\directlua{luakeys = require('luakeys')}
diff --git a/macros/luatex/latex/pyluatex/README.md b/macros/luatex/latex/pyluatex/README.md
index 3165301bd8..feb77a04bc 100644
--- a/macros/luatex/latex/pyluatex/README.md
+++ b/macros/luatex/latex/pyluatex/README.md
@@ -8,10 +8,6 @@ LaTeX documents must be compiled with LuaLaTeX for this to work.
## Example
1\. LaTeX document `example.tex`
-**Note:** PyLuaTeX starts Python 3 using the command `python3` by default.
-If `python3` does not start Python 3 on your system, find the correct command
-and replace `\usepackage{pyluatex}` with `\usepackage[executable={your python command}]{pyluatex}`.
-For example, `\usepackage[executable=python.exe]{pyluatex}`.
```latex
\documentclass{article}
@@ -36,11 +32,15 @@ $\sqrt{371} = \py{math.sqrt(371)}$
\randint{2}{5}
\end{document}
```
+**Note:** PyLuaTeX starts Python 3 using the command `python3` by default.
+If `python3` does not start Python 3 on your system, find the correct command
+and replace `\usepackage{pyluatex}` with `\usepackage[executable={your python command}]{pyluatex}`.
+For example, `\usepackage[executable=python.exe]{pyluatex}`.
+
2\. Compile using LuaLaTeX (shell escape is required)
```
lualatex -shell-escape example.tex
```
-
**Note:** Running LaTeX with the shell escape option enabled allows arbitrary code to be
executed. For this reason, it is recommended to compile trusted documents only.
diff --git a/macros/luatex/latex/pyluatex/pyluatex-interpreter.py b/macros/luatex/latex/pyluatex/pyluatex-interpreter.py
index 4a17b5f5b0..0e8b32035f 100644
--- a/macros/luatex/latex/pyluatex/pyluatex-interpreter.py
+++ b/macros/luatex/latex/pyluatex/pyluatex-interpreter.py
@@ -30,8 +30,36 @@ import socketserver
import json
import textwrap
from collections import defaultdict
+import re
class Interpreter(InteractiveInterpreter):
+ def execute_repl(self, code, ignore_errors):
+ self.success = True
+ output = ''
+ incomplete = False
+ for line in re.split('\r?\n', code):
+ output += ('... ' if incomplete else '>>> ') + line + '\n'
+ if incomplete:
+ buffer += '\n' + line
+ else:
+ buffer = line
+ with StringIO() as out, redirect_stdout(out), redirect_stderr(out):
+ try:
+ code_obj = compile_command(buffer)
+ if code_obj is not None:
+ incomplete = False
+ self.runcode(code_obj)
+ else:
+ incomplete = True
+ except:
+ incomplete = False
+ traceback.print_exc(limit=0)
+ self.success = False
+ output += out.getvalue()
+ if not ignore_errors and not self.success:
+ return False, output
+ return self.success, output
+
def execute(self, code):
with StringIO() as out, redirect_stdout(out), redirect_stderr(out):
self.success = True
@@ -61,7 +89,14 @@ class Handler(socketserver.StreamRequestHandler):
data = json.loads(data)
interpreter = interpreters[data['session']]
- success, output = interpreter.execute(textwrap.dedent(data['code']))
+ code = textwrap.dedent(data['code'])
+ if data['repl_mode']:
+ success, output = interpreter.execute_repl(
+ code,
+ data['ignore_errors']
+ )
+ else:
+ success, output = interpreter.execute(code)
response = { 'success': success, 'output': output }
self.wfile.write((json.dumps(response) + '\n').encode('utf-8'))
diff --git a/macros/luatex/latex/pyluatex/pyluatex.lua b/macros/luatex/latex/pyluatex/pyluatex.lua
index d7e27bc6cb..6e9c6abb63 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.lua
+++ b/macros/luatex/latex/pyluatex/pyluatex.lua
@@ -26,6 +26,7 @@ local json = require("pyluatex-json")
local socket = require("socket")
pyluatex = pyluatex or {
+ ignore_errors = false,
verbose = false,
session = "default"
}
@@ -38,10 +39,15 @@ local tcp = nil
local env_end = nil
local env_lines = nil
local parent_env = nil
+local env_repl_mode = false
local last_code = nil
local last_output = nil
+local function trim(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
local function err_cmd(message)
return "\\PackageError{PyLuaTeX}{" .. message .. "}{}"
end
@@ -94,7 +100,7 @@ local function split_lines(str)
return t
end
-function pyluatex.execute(code, auto_print, write)
+function pyluatex.execute(code, auto_print, write, repl_mode)
local full_code
if auto_print then
full_code = "print(str(" .. code .. "), end='')"
@@ -104,12 +110,19 @@ function pyluatex.execute(code, auto_print, write)
if pyluatex.verbose then log_input(full_code) end
- local success, output = request({ session = pyluatex.session, code = full_code })
+ local success, output = request(
+ {
+ session = pyluatex.session,
+ code = full_code,
+ repl_mode = repl_mode,
+ ignore_errors = pyluatex.ignore_errors
+ }
+ )
last_code = split_lines(code)
last_output = split_lines(output)
- if success then
- if pyluatex.verbose then log_output(output) end
+ if success or pyluatex.ignore_errors then
+ if pyluatex.verbose or not success then log_output(output) end
if write then
tex.print(last_output)
@@ -135,10 +148,14 @@ local function record_line(line)
local s, e = line:find(env_end, 1, true)
if s ~= nil then
luatexbase.remove_from_callback("process_input_buffer", "pyluatex_record_line")
- table.insert(env_lines, line:sub(1, s - 1))
+ local code_in_line = line:sub(1, s - 1)
+ if trim(code_in_line):len() > 0 then
+ -- only include this line if it contains non-whitespace characters
+ table.insert(env_lines, code_in_line)
+ end
local code = table.concat(env_lines, "\n")
- local success = pyluatex.execute(code, false, false)
- if success then
+ local success = pyluatex.execute(code, false, false, env_repl_mode)
+ if success or pyluatex.ignore_errors then
return line:sub(s)
else
return env_end .. err_cmd("Python error (see above)") .. line:sub(e + 1)
@@ -149,20 +166,14 @@ local function record_line(line)
end
end
-function pyluatex.record_env(quiet)
- local name
+function pyluatex.record_env(name, repl_mode)
if parent_env ~= nil then
name = parent_env
parent_env = nil
- else
- if quiet then
- name = "pythonq"
- else
- name = "python"
- end
end
env_end = "\\end{" .. name .. "}"
env_lines = {}
+ env_repl_mode = repl_mode
luatexbase.add_to_callback("process_input_buffer", record_line, "pyluatex_record_line")
end
@@ -172,12 +183,18 @@ function pyluatex.set_parent_env(name)
end
end
-function pyluatex.run_file(path, write)
+function pyluatex.run_file(path, write, repl_mode)
local f = io.open(path, "r")
if f then
local code = f:read("*a")
f:close()
- pyluatex.execute(code, false, write)
+ -- ignore trailing new line if present
+ if code:sub(-2) == "\r\n" then
+ code = code:sub(0, -3)
+ elseif code:sub(-1) == "\n" then
+ code = code:sub(0, -2)
+ end
+ pyluatex.execute(code, false, write, repl_mode)
else
tex.sprint(err_cmd("File not found: " .. path))
end
@@ -191,4 +208,26 @@ function pyluatex.get_last_output()
return last_output
end
+local function parse_bool(name, value)
+ if value == "true" then
+ return true
+ elseif value == "false" then
+ return false
+ else
+ tex.sprint(
+ err_cmd("Invalid value '" .. value .. "' for option " .. name)
+ )
+ end
+end
+
+function pyluatex.set_option(name, value)
+ if name == "ignoreerrors" then
+ pyluatex.ignore_errors = parse_bool(name, value)
+ elseif name == "verbose" then
+ pyluatex.verbose = parse_bool(name, value)
+ else
+ tex.sprint(err_cmd("Unknown option '" .. name .. "'"))
+ end
+end
+
return pyluatex
diff --git a/macros/luatex/latex/pyluatex/pyluatex.pdf b/macros/luatex/latex/pyluatex/pyluatex.pdf
index 49fee5afac..0318eda2cb 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.pdf
+++ b/macros/luatex/latex/pyluatex/pyluatex.pdf
Binary files differ
diff --git a/macros/luatex/latex/pyluatex/pyluatex.sty b/macros/luatex/latex/pyluatex/pyluatex.sty
index 81fa87ea82..ab015d9638 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.sty
+++ b/macros/luatex/latex/pyluatex/pyluatex.sty
@@ -9,7 +9,7 @@
%% version 2005/12/01 or later.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{pyluatex}[2021/08/07 v0.3.0 Execute Python code on the fly]
+\ProvidesPackage{pyluatex}[2021/11/05 v0.4.0 Execute Python code on the fly]
\RequirePackage{expl3}
\ExplSyntaxOn
@@ -25,19 +25,32 @@
\RequirePackage{kvoptions}
\DeclareStringOption[python3]{executable}
-\DeclareVoidOption{verbose}{\directlua{pyluatex.verbose = true}}
+\DeclareBoolOption{ignoreerrors}
+\DeclareBoolOption{verbose}
\ProcessKeyvalOptions*
+\ifpyluatex@ignoreerrors
+ \directlua{pyluatex.ignore_errors = true}
+\else
+ \directlua{pyluatex.ignore_errors = false}
+\fi
+\ifpyluatex@verbose
+ \directlua{pyluatex.verbose = true}
+\else
+ \directlua{pyluatex.verbose = false}
+\fi
\directlua{pyluatex.start([==[\pyluatex@executable]==])}
\newcommand*{\PyLTVerbatimEnv}{\directlua{pyluatex.set_parent_env([==[\@currenvir]==])}}
-\newenvironment{python}{\directlua{pyluatex.record_env(false)}}%
+\newenvironment{python}{\directlua{pyluatex.record_env("python", false)}}%
{\directlua{pyluatex.print_env()}}
-\newenvironment{pythonq}{\directlua{pyluatex.record_env(true)}}{}
+\newenvironment{pythonq}{\directlua{pyluatex.record_env("pythonq", false)}}{}
-\newcommand*{\pyluatex@inline}[3]{\directlua{pyluatex.execute([==[#1]==], #2, #3)}}
+\newenvironment{pythonrepl}{\directlua{pyluatex.record_env("pythonrepl", true)}}{}
+
+\newcommand*{\pyluatex@inline}[3]{\directlua{pyluatex.execute([==[#1]==], #2, #3, false)}}
\newcommand*{\py}[1]{\pyluatex@inline{#1}{true}{true}}
\newcommand*{\pyq}[1]{\pyluatex@inline{#1}{true}{false}}
@@ -46,8 +59,10 @@
\newcommand*{\pycq}[1]{\pyluatex@inline{#1}{false}{false}}
\newcommand*{\pysession}[1]{\directlua{pyluatex.session = [==[#1]==]}}
+\newcommand*{\pyoption}[2]{\directlua{pyluatex.set_option([==[#1]==], [==[#2]==])}}
-\newcommand*{\pyfile}[1]{\directlua{pyluatex.run_file([==[#1]==], true)}}
-\newcommand*{\pyfileq}[1]{\directlua{pyluatex.run_file([==[#1]==], false)}}
+\newcommand*{\pyfile}[1]{\directlua{pyluatex.run_file([==[#1]==], true, false)}}
+\newcommand*{\pyfileq}[1]{\directlua{pyluatex.run_file([==[#1]==], false, false)}}
+\newcommand*{\pyfilerepl}[1]{\directlua{pyluatex.run_file([==[#1]==], false, true)}}
\endinput
diff --git a/macros/luatex/latex/pyluatex/pyluatex.tex b/macros/luatex/latex/pyluatex/pyluatex.tex
index d6c0325350..86374249af 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.tex
+++ b/macros/luatex/latex/pyluatex/pyluatex.tex
@@ -15,7 +15,7 @@
\usepackage{url}
\title{The \emph{pyluatex} package}
\author{Tobias Enderle\\\url{https://github.com/tndrle/PyLuaTeX}}
-\date{v0.3.0 (2021/08/07)}
+\date{v0.4.0 (2021/11/05)}
\begin{document}
\maketitle
\raggedright
@@ -29,10 +29,6 @@ PyLuaTeX allows you to execute Python code and to include the resulting output i
\section{Example}
\begin{enumerate}
\item \LaTeX{} document \inlcode|example.tex|\\[0.5ex]
-\textbf{Note:} PyLuaTeX starts Python 3 using the command \inlcode|python3| by default.
-If \inlcode|python3| does not start Python 3 on your system, find the correct command
-and replace \inlcode|\usepackage{pyluatex}| with \inlcode|\usepackage[executable={your python command}]{pyluatex}|.
-For example, \inlcode|\usepackage[executable=python.exe]{pyluatex}|.
\begin{tcblisting}{breakable,listing only,
size=fbox,colframe=black!8,boxrule=3pt,colback=black!8}
\documentclass{article}
@@ -58,28 +54,35 @@ $\sqrt{371} = \py{math.sqrt(371)}$
\randint{2}{5}
\end{document}
\end{tcblisting}
+\textbf{Note:} PyLuaTeX starts Python 3 using the command \inlcode|python3| by default.
+If \inlcode|python3| does not start Python 3 on your system, find the correct command
+and replace \inlcode|\usepackage{pyluatex}| with \inlcode|\usepackage[executable={your python command}]{pyluatex}|.
+For example, \inlcode|\usepackage[executable=python.exe]{pyluatex}|.
\item Compile using Lua\LaTeX{} (shell escape is required)
\begin{tcblisting}{breakable,listing only,
size=fbox,colframe=black!8,boxrule=3pt,colback=black!8}
lualatex -shell-escape example.tex
\end{tcblisting}
-\end{enumerate}
\textbf{Note:} Running \LaTeX{} with the shell escape option enabled allows arbitrary code to be
executed. For this reason, it is recommended to compile trusted documents only.
-
+\end{enumerate}
\subsection{Further Examples}
The folder \inlcode|example| contains additional example documents:
\begin{itemize}
-\item \inlcode|readme-example.tex|\\[0.5ex]
- The example above
-\item \inlcode|sessions.tex|\\[0.5ex]
- Demonstrates the use of different Python sessions in a document
+\item \inlcode|beamer.tex|\\[0.5ex]
+ Demonstrates the use of PyLuaTeX environments and typesetting in \textit{BEAMER} presentations. In particular, the \inlcode|fragile| option for frames is highlighted.
\item \inlcode|data-visualization.tex|\\[0.5ex]
Demonstrates the visualization of data using \textit{pgfplots} and \textit{pandas}
\item \inlcode|matplotlib-external.tex|\\[0.5ex]
Demonstrates how \textit{matplotlib} plots can be generated and included in a document
\item \inlcode|matplotlib-pgf.tex|\\[0.5ex]
Demonstrates how \textit{matplotlib} plots can be generated and included in a document using \textit{PGF}
+\item \inlcode|readme-example.tex|\\[0.5ex]
+ The example above
+\item \inlcode|repl.tex|\\[0.5ex]
+ Demonstrates how a Python console/REPL can be run and typeset
+\item \inlcode|sessions.tex|\\[0.5ex]
+ Demonstrates the use of different Python sessions in a document
\item \inlcode|typesetting-example.tex|\\[0.5ex]
The code typesetting example below
\item \inlcode|typesetting-listings.tex|\\[0.5ex]
@@ -127,12 +130,19 @@ For an example, see the Typesetting Code section.
\subsection{Package Options}
\begin{itemize}
\item \inlcode|verbose|\\[0.5ex]
- If this option is enabled, Python input and output is written to the log file.\\[0.5ex]
+ If this option is set, Python input and output is written to the \LaTeX{} log file.\\[0.5ex]
\textit{Example:} \inlcode|\usepackage[verbose]{pyluatex}|
\item \inlcode|executable|\\[0.5ex]
Specifies the path to the Python executable. (default: \inlcode|python3|)\\[0.5ex]
\textit{Example:} \inlcode|\usepackage[executable=/usr/local/bin/python3]{pyluatex}|
+\item \inlcode|ignoreerrors|\\[0.5ex]
+ By default, PyLuaTeX aborts the compilation process when Python reports an error.
+ If the \inlcode|ignoreerrors| option is set, the compilation process is not aborted.\\[0.5ex]
+ \textit{Example:} \inlcode|\usepackage[ignoreerrors]{pyluatex}|
\end{itemize}
+Package options (except for \inlcode|executable|) can be changed in the document with the
+\inlcode|\pyoption| command, e.g. \inlcode|\pyoption{verbose}{true}| or \inlcode|\pyoption{ignoreerrors}{false}|.
+
\subsection{Macros}
\begin{itemize}
\item \inlcode|\py{code}|\\[0.5ex]
@@ -157,6 +167,10 @@ For an example, see the Typesetting Code section.
Selects \inlcode|session| as Python session for subsequent Python code.\\[0.5ex]
The session that is active at the beginning is \inlcode|default|.\\[0.5ex]
\textit{Example:} \inlcode|\pysession{main}|
+\item \inlcode|\pyoption{option}{value}|\\[0.5ex]
+ Assigns \inlcode|value| to the package option \inlcode|option| anywhere in the document. For more information consider
+ the Package Options section.\\[0.5ex]
+ \textit{Example:} \inlcode|\pyoption{verbose}{true}|
\end{itemize}
\subsection{Environments}
\begin{itemize}
@@ -175,8 +189,13 @@ For an example, see the Typesetting Code section.
\end{tcblisting}
\item \inlcode|pythonq|\\[0.5ex]
Same as the \inlcode|python| environment, but any output is suppressed.
+\item \inlcode|pythonrepl|\\[0.5ex]
+ Executes the provided block of Python code in an interactive console/REPL. Code and output are
+ stored together in the output buffer and can be typeset as explained in section
+ Typesetting Code or as shown in the example \inlcode|repl.tex| in the folder
+ \inlcode|example|.
\end{itemize}
-You can create your own environments based on the \inlcode|python| and \inlcode|pythonq| environments.
+You can create your own environments based on the \inlcode|python|, \inlcode|pythonq| and \inlcode|pythonrepl| environments.
However, since they are verbatim environments, you have to use the macro \inlcode|\PyLTVerbatimEnv|
in your environment definition, e.g.
\begin{tcblisting}{breakable,listing only,
@@ -249,6 +268,8 @@ Using a different code listings package like \textit{minted}, or typesetting inl
You can also define your own environments that combine Python code and typesetting.
See the \inlcode|typesetting-*.tex| examples in the \inlcode|example| folder.
+To emulate an interactive Python console/REPL, the \inlcode|pythonrepl| environment can be used.
+
\section{How It Works}
PyLuaTeX runs a Python \inlcode|InteractiveInterpreter|\footnote{\url{https://docs.python.org/3/library/code.html#code.InteractiveInterpreter}} (actually several if you use different sessions) in the background for on the fly code execution.
Python code from your \LaTeX{} file is sent to the background interpreter through a TCP socket.
diff --git a/macros/plain/contrib/reverxii/README b/macros/plain/contrib/reverxii/README
deleted file mode 100644
index f008369cb8..0000000000
--- a/macros/plain/contrib/reverxii/README
+++ /dev/null
@@ -1,20 +0,0 @@
-----------------------------------------------------------------
-reverxii --- Playing Reversi, in the spirit of xii.tex
-E-mail: blflatex@gmail.com
-Released under the LaTeX Project Public License v1.3c or later
-See http://www.latex-project.org/lppl.txt
-----------------------------------------------------------------
-
-The file reverxii.tex is a 938 character long TeX program which
-lets you play Reversi against your favorite typesetting engine.
-Play by running
-
- tex reverxii.tex
-
-in the command line. This will also produce a record of your
-game as reverxii.dvi. Typeset the documentation by running
-
- pdflatex reverxii.tex
-
-This is work in progress, all suggestions/comments/bug reports
-are welcome! \ No newline at end of file
diff --git a/macros/plain/contrib/reverxii/README.md b/macros/plain/contrib/reverxii/README.md
new file mode 100644
index 0000000000..77fba86111
--- /dev/null
+++ b/macros/plain/contrib/reverxii/README.md
@@ -0,0 +1,21 @@
+Playing Reversi, in the spirit of xii.tex
+=================================
+* E-mail: blflatex@gmail.com
+* Released under the LaTeX Project Public License v1.3c or later
+ See http://www.latex-project.org/lppl.txt
+
+The file `reverxii.tex` is a 983 character long (La)TeX program which
+lets you play Reversi against your favorite typesetting engine.
+Play by running the following command (or the same with `pdftex`, or
+`latex`, or `pdflatex` etc.)
+
+ tex reverxii.tex
+
+in the command line. This will also produce a record of your
+game as `reverxii.dvi`. On the other hand, typeset the documentation
+by running
+
+ pdflatex reverxii.dtx
+
+All suggestions/comments/bug reports are welcome!
+See https://github.com/blefloch/latex-reverxii
diff --git a/macros/plain/contrib/reverxii/reverxii.dtx b/macros/plain/contrib/reverxii/reverxii.dtx
new file mode 100644
index 0000000000..084afdc544
--- /dev/null
+++ b/macros/plain/contrib/reverxii/reverxii.dtx
@@ -0,0 +1,379 @@
+{\def\x{plain}\expandafter}\ifx\fmtname\x
+ \errmessage{Use LaTeX for the documentation reverxii.dtx. Play with reverxii.tex}
+\fi
+\documentclass{article}
+\usepackage{shortvrb,verbatim}
+\MakeShortVerb|
+\title{\texttt{reverxii}\\
+ Playing Reversi in \TeX{}}
+\author{Bruno Le Floch}
+\date{November 6, 2021}
+\newcommand{\cs}[1]{\texttt{\char`\\ #1}}
+\newcommand{\meta}[1]{$\langle \mathit{#1}\rangle$}
+\newcommand{\enquote}[1]{``#1''}
+\begin{document}
+\maketitle
+
+\begin{abstract}
+ \texttt{reverxii.tex} is a $983$ character long \TeX{} program which
+ lets you play Reversi against your favorite typestting engine. To
+ play, run plain \TeX{} or \LaTeX{} (either the dvi or the pdf
+ engines), on the command line, on the file \texttt{reverxii.tex}. In
+ most distributions, this can mean running \texttt{tex reverxii.tex} in
+ a terminal. To typeset the documentation, run \LaTeX{} on
+ \texttt{reverxii.dtx}, with any engine (\emph{e.g.}, \texttt{pdflatex
+ reverxii.dtx}).
+\end{abstract}
+
+\section{The code}
+
+Line breaks can be removed, and the stripped down code takes $983$
+characters.
+{\small \verbatiminput{reverxii.tex}}
+In fact, the first line and |\ifx\>\:1\fi| are only used to support
+\LaTeX{} and can be removed for plain \TeX{}. This reduces the code down to $938$ characters.
+
+To play a two-player game, change |1=P| to |0=P| near the end:
+this changes the computer player from $0$ to $1$, hence disabling it.
+
+\section{Explanation}
+
+\subsection{Some comments}
+
+The name \texttt{reverxii.tex} is of course a reference to the infamous
+\texttt{xii.tex}, also on CTAN, which typesets the lyrics of the
+\textit{Twelve days of Christmas} using code that very few can understand.
+In my case, I aimed for the shortest possible code, rather than the
+most obfuscated. In particular, I did not assign weird catcodes
+other than making most characters active.
+
+Since I was aiming for short code, the text presented to the player
+is concise, but hopefully enough to leave the game understandable
+and usable. Despite aiming for short code, I thought it would be
+neat to typeset a record of the game as it goes: after all, \TeX{}
+is a typesetting program. This used up around $38$ characters, putting
+me above the $900$ character line.
+
+One technique used to make the code shorter is to rename any primitive
+used more than once into a single active character. Also, counters
+holding the information about the board are accessed directly by number.
+
+A careful reader would notice that changing one of the remaining
+one-character control sequences to active characters (|$| is still %$
+unused) would gain one character. I didn't do it, because it messes
+up the code-highlighting of my editor |:)|. Of course, I chose the
+control sequences which are not active characters to be those appearing
+the least, only twice each.
+
+\subsubsection{On with the code!}
+
+In \LaTeXe{}, detected by comparing the spaces |\>| and~|\:| (only one
+of them is defined in plain \TeX{}), load a document class.
+\begin{verbatim}
+\ifx\>\:\documentclass{report}\fi
+\end{verbatim}
+We do not need \cs{begin}|{document}| because the text is eventually
+typeset inside an \cs{halign} table, which means \cs{everypar} (and its
+usual error) is never called.
+
+First set up the page dimensions. This would not be enough for pdf output.
+\begin{verbatim}
+\vsize5cm
+\hsize3cm
+\end{verbatim}
+
+Since plain \TeX{} does not provide the \cs{typeout} command, we are
+using \cs{message}, hence need to setup a new line character. It is
+arbitrarily chosen to be |*|, which will be made active and \cs{let}
+to \cs{cr}.
+\begin{verbatim}
+\newlinechar`*
+\end{verbatim}
+
+Then a first loop to make many characters active. The loop ends when
+reaching the trailing brace group: there we make spaces active, then
+redefine |~| for the next loop. We still have an annoying |13~| in the
+input stream. Introduce a short-hand for count registers whose number
+starts with |1| (or in fact |11| in \LaTeX{} given the test
+|\ifx\>\:1\fi|, see later for why we do this). Then remove |13| by
+setting |\count11| to |213| (this counter is used for allocation later
+on). The next loop is triggered by the |~| which we left.
+\begin{verbatim}
+\def~#1{\catcode`#113~}
+~IJKLMNOPQRSTUVWXYZjqz@.[|](/+^'";:?,_)!*
+{ 13\def~#1#2{\let#1#2~}\def+{\count\ifx\>\:1\fi1}+1=2}
+\end{verbatim}
+
+At this stage, |~| is defined to take two arguments, \cs{let} the
+first to the second, and repeat. As announced |*| becomes \cs{cr},
+so that it will be a new line both in messages and in alignments.
+We try to keep a relatively consistent naming scheme: opening
+conditionals are left delimiters, \cs{or} and \cs{else} are
+middle delimiters, and \cs{fi} is a right bracket. Other primitives
+which are used a lot are also given short names. The loop ends by
+redefining |~| to \cs{def}.
+\begin{verbatim}
+*\cr
+[\ifnum
+(\ifcase
+|\or
+/\else
+]\fi
+N\number
+@\advance
+X\expandafter
+!\global
+?\message
+~\def
+\end{verbatim}
+
+It is time to allocate counters. Unfortunately, \cs{newcount} is
+\cs{outer} in plain \TeX{}, so it is unpractical to use. We have
+defined |+| to be |\count1| in plain and |\count11| in \LaTeX{}. This
+will eventually let us access a table of $8\times 8$ counters |+11| to
+|+88|, which are thus |\count111| to |\count188| in plain, and
+|\count1111| to |\count1188| in \LaTeX{}. This is needed because plain
+\TeX{} without e\TeX{} extensions only has $256$ counters, while
+\LaTeX{} \emph{uses} some of these counters already so we must use the
+higher counters provided by the e\TeX{} extensions, always available in
+\LaTeX{}. The following text explains the plain \TeX{} case; in the
+other case just replace |\count1| by |\count11|.
+
+We have set |+1|,
+\emph{aka.} |\count11| to $213$, and will allocate counters
+starting from that number upwards. Repeatedly advance |+1|
+by $1$ and define the next character to be the counter number |+1|,
+then repeat. As always, the loop is interrupted by making it
+redefine the looping macro |.| to be a counter. We use the trailing
+dot to assign it the value $-1$, then assign a couple of counters:
+starting player, other player, and the initial position:
+the squares in rows and columns $4$ and $5$ are initially
+filled in a cross pattern.
+\begin{verbatim}
+.#1{@+1 1\countdef#1+1.}.IJKLPQRSTUVWYZ.-1P1T2+44P+55P+45T+54T
+\end{verbatim}
+Let us summarize which counters are used where:
+\begin{itemize}
+\item [P] is the current player ($1$ for |-| or $2$ for |0|);
+\item [T] is the other player;
+\item [Q] is the row number;
+\item [J] is the column number;
+\item [S] is the step size in the row direction;
+\item [K] is the step size in the column direction;
+\item [R] is the score difference, positive when $0$ is winning;
+\item [V] is used when computing the value of placing a piece at
+ the position (|Q|,|J|);
+\item [W] is the value of the best possible move according to the AI,
+ also used to end the game if neither player can move;
+\item [U] is the row number of the best move;
+\item [L] is the column number of the best move;
+\item [Y] is a boolean, true ($0$) most of the time,
+ it has to do with when we flip or not, but I don't
+ understand it now, help welcome;
+\item [Z] is a temporary counter, used locally to compute
+ how much a given cell matters (\emph{i.e.} corners are important),
+ and used globally as a boolean to indicate whether to flip pieces
+ or not.
+\end{itemize}
+
+The board is stored in counters |1|\meta{row}\meta{column}.
+An empty square is has the value $0$, a square for player |-|
+has value $1$, and player |0| corresponds to the value $2$.
+The macro |j| retrieves that value, assuming that the row and
+column numbers are stored as |Q| and |J|, and returns |.|,
+that is, $-1$, if outside the board. Recall that |[| is \cs{ifnum},
+|/| is \cs{else}, and |]| is \cs{fi}. When |Q| and |J| are
+within bounds, the value is retrieved by |^| as
+|\count1|\meta{row}\meta{column}. Note that |j| and |^| are
+only safe to use on the left-hand side of an \cs{ifnum} test
+(because of expansion issues), and that |^| can be used
+on the left-hand side of an assignment.
+\begin{verbatim}
+~j{[0<Q[9>Q[0<J[9>J^/.]/.]/.]/.]}
+~^{+NQNJ}
+\end{verbatim}
+
+We often need to loop over numbers from $1$ to $8$; here is a macro.
+\begin{verbatim}
+~:#1{#11#12#13#14#15#16#17#18}
+\end{verbatim}
+
+The macros to print the board, both to the dvi and to the console.
+|M| spews its argument as a \cs{message} (|?|), and directly typeset.
+This is used rather directly to print the first and last lines (|'|),
+which are simply alignment cells containing each number from $1$ to $8$,
+with some care given to spaces and new lines.
+The |_| macro receives a digit and the corresponding capital letter
+as arguments, and outputs that row of the board. First place the
+letter on the left of the board, then loop from $1$ to $8$,
+typesetting and \cs{message}ing a space, |-| or |0|, depending on
+the value of the relevant count register. Note the two spaces
+in the definition: the first ends the counter's number, the second
+is typeset (in plain \TeX{}, active spaces expand to a normal space).
+\begin{verbatim}
+~M#1{?{#1}#1}
+~'{M :{&M}&M{*}}
+~_#1#2{M#2:{\B#1}&M#2&M{*}}
+~\B#1#2{&M{(+#1#2 |-|0]}}
+\end{verbatim}
+
+The input is done by |q|, which queries the user until they give
+a correct input, so that |Q| and |J| are in the range $[1,8]$.
+Prompt the user with a small \cs{message}, giving an example
+of what move he could do (only true at the start, but the hope
+is that the player will understand what the input format is).
+The code that follows is similar to \LaTeXe{}'s \cs{@onelevel@sanitize}.
+We extract the two first characters from the the \cs{meaning}
+of the user's input (remember, |X| is \cs{expandafter}),
+as |#2| and |#3| of \cs{D}. Grab the character code of each,
+relative to the characters |@| (row) or |0| (column).
+The closing parenthesis is where most of the work is done.
+It sets |V| to the value of placing a piece in the cell
+$(|Q|,|J|)$, zero if the move does not flip any of the opponent's
+pieces, or if the cell is outside the board. After performing
+that calculation, if |V| is zero, the move was not valid,
+and we query the user again.
+\begin{verbatim}
+~q{?{Row and column? e.g. E6*}\read.to\EX\D\meaning\E ;}
+~\D#1->#2#3#4;{Q`#2@Q-`@J`#3@J-`0)(V?{Invalid move #2#3.}q]}
+\end{verbatim}
+
+So\ldots{} how do we compute the \enquote{value} of a move?
+It is automatically invalid if |j| does not return $0$:
+either the cell is occupied, or it is outside the board.
+Then for each of the $8$ directions around the cell,
+we count the number of pieces that are flipped in that
+direction. The direction is stored as two counters,
+|S| and |K|, each $\pm 1$ or $0$. We call |,| a first
+time to test whether flipping should happen in that direction,
+and, if it is (as indicated by the global value of |Z|),
+call it again to actually flip. The call to |,| must happen
+within a group, because it directly changes the row and column
+numbers |Q| and |J|, following which cell is being queried.
+\begin{verbatim}
+~){V0 (jS1z1z0z.S0z1z.S.z1z0z.]}
+~z#1{{K#1Z1{Y1,}(Z,]}}
+\end{verbatim}
+
+The macro |,| is recursive. At each step, move $(|Q|,|J|)$
+in the direction $(|S|,|K|)$. Then, if that cell (|j|)
+contains a piece belonging to the other player (|T|),
+do some stuff |(Y!^P!;2]O| and repeat. What is it that
+we do? Well, if the condition |Y| is met (I don't remember
+how that works), we set the current cell to belong to the
+player, globally, and change the score difference by $2$
+(see |;|), also globally. Then, we compute with |O| the value
+corresponding to the cell that we might be flipping (see |O|).
+
+Otherwise (|/|), if the cell (|j|) contains a piece from the
+current player (|P|), it means we have reached the end of a
+run of the form \meta{initial cell} \meta{opponent's pieces}
+\meta{own piece}, hence the \meta{opponent's pieces} should
+count as flipped if we place our piece in the \meta{initial
+ cell}. Until there, all changes to |V| were local, returning
+to the old value at the end of the group that |,| is contained
+in. Since the run in that direction was successful, we escape
+the value of |V| from the group with |\global V=V|. Under
+some conditions, we set the boolean |Z| to true, globally (|!Z0|),
+which triggers a second call to |,| with different setting,
+and actually flips the opponent's pieces.
+\begin{verbatim}
+~,{@QS@JK[j=T(Y!^P!;2]O,/[j=P!VV(I(Y/!Z0]]]]}
+\end{verbatim}
+
+I moved those |O| and |"| guys a little bit in this explanation,
+but not in the original implemenation, because it is hard
+to sync. We assign weights to each of the $64$ cells:
+\begin{center}
+ \halign{&\hfil#\hfil\cr
+ 9 & 1 & 6 & 6 & 6 & 6 & 1 & 9 \cr
+ 1 & 1 & 2 & 2 & 2 & 2 & 1 & 1 \cr
+ 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
+ 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
+ 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
+ 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
+ 1 & 1 & 2 & 2 & 2 & 2 & 1 & 1 \cr
+ 9 & 1 & 6 & 6 & 6 & 6 & 1 & 9 \cr}
+\end{center}
+All weights are positive, so that every move which flips a piece
+ends up with a positive overall value. The AI would be better if
+the places next to corners had a negative weight, but I would have
+too much code to rewrite for that to work. We really have three
+types of rows and three types of columns. Converting from |Q|
+or |J| is done by |"|, then we assemble the two as a number
+in the range $[0,8]$, and use another \cs{ifcase} construction
+to produce the weights.
+\begin{verbatim}
+~O{Z"Q\multiplyZ3@Z"J@V(Z9|1|6|1|1|2|6|2|4] }
+~"#1{(#1|0|1|2|2|2|2|1|0]}
+\end{verbatim}
+
+The counter |R| keeps track of the score difference,
+and is updated with |;2| (when flipping a piece) or |;1| (when
+adding a piece). The counter |R| should be \cs{advance}d (|@|)
+by a positive amount when the current player |P| is player |0|,
+and a negative amount for player |-|.
+\begin{verbatim}
+~;{@R(P|-]}
+\end{verbatim}
+
+After printing the board, we go through every cell and find the
+one with the highest value. The macro \cs{A}, does one row, hence
+|\:\A| does all the rows. Store the argument as the row number |Q|,
+then loop over columns. After setting the column number |J| to
+its argument, \cs{C} calls |)|, which as explained above computes
+the value of placing a piece there, throws away that case if it flips
+nothing, otherwise also counts the weight of the current cell. Then
+update the best value |W| and the best row |U| and column |L| if
+the new |V| is larger than |W|.
+\begin{verbatim}
+~\A#1{Q#1:\C}
+~\C#1{J#1)[0<VO[V>WWVUQLJ]]}
+\end{verbatim}
+
+We won't need |~| as \cs{def} anymore, so we reuse it as the main command.
+\begin{itemize}
+\item First exchange the players: set |P| equal to |T|, but first expand
+ the value of |P| after |T| to set that as well.
+\item Secondly, open an alignment, with a repeating preamble adding a space
+ at its end. Then message a new-line (we should be using |?| rather than
+ |M| here, I think) to keep a clean output. Afterwards, print the top line
+ with |'|, the eight lines of the bulk with |_|, and the bottom line,
+ which happens to end with |?{*}*|, \emph{i.e.}, ends with \cs{cr}:
+ we can thus close the alignment, and cause \TeX{} to output the page.
+\item After printing, it is time to check whether there is a move or not.
+ We don't want to flip pieces at this stage, hence set the boolean |I|
+ to false ($1$). And we reset the best value to $0$, unless it was
+ already $0$ (which means that the previous player had no available
+ move), in which case we set it to $-1$. Then loop over rows, finding
+ the best value (see \cs{A}). Reset the boolean |I| to be true.
+\item If a move was found in the previous step ($|W|>0$), either
+ use it if the player is the AI, or query the user. The various
+ booleans are set up in such a way as to do the flipping, so
+ calling |)| does it. Then also put a player's piece in the current
+ cell |^|, and increase the score difference by $1$.
+\item Finally, if neither player could move, declare the game ended,
+ give the score, and \cs{dump} the run (not \cs{end} because we want to
+ support both plain \TeX{} and \LaTeX{}). Otherwise, repeat.
+\end{itemize}
+Of course, after defining |~|, we call it. Let's play!
+\begin{verbatim}
+~~{
+ PXTXTNP
+ \halign{&## *M{*}'_1A_2B_3C_4D_5E_6F_7G_8H'}\vfil\break
+ I1 W(W./0] :\AI0
+ [0<W
+ [1=PQUJL/q]
+ )
+ ^P;1
+ ]
+ [.=W
+ ?{(RTie/ Player [0>R-/0] wins by N[0>R-]R].}
+ X\dump
+ ]
+ ~
+}
+~
+\end{verbatim}
+
+\end{document} \ No newline at end of file
diff --git a/macros/plain/contrib/reverxii/reverxii.pdf b/macros/plain/contrib/reverxii/reverxii.pdf
index b2732c7816..3117f4b371 100644
--- a/macros/plain/contrib/reverxii/reverxii.pdf
+++ b/macros/plain/contrib/reverxii/reverxii.pdf
Binary files differ
diff --git a/macros/plain/contrib/reverxii/reverxii.tex b/macros/plain/contrib/reverxii/reverxii.tex
index 7877c70ab1..85e5c4ea83 100644
--- a/macros/plain/contrib/reverxii/reverxii.tex
+++ b/macros/plain/contrib/reverxii/reverxii.tex
@@ -1,49 +1,8 @@
-% \iffalse
-\begingroup
- \def\tmp{plain}
- \expandafter
-\endgroup
-\csname if\ifx\tmp\fmtname false\else true\fi\endcsname
- \documentclass{article}
- \usepackage{shortvrb}
- \MakeShortVerb|
- \catcode`\%=9
- \input \jobname.tex
-\fi
-% \fi
-%
-% \title{\texttt{reverxii.tex}\\
-% Playing Reversi in \TeX{}}
-% \author{Bruno Le Floch}
-% \date{December 28, 2011}
-% \newcommand{\cs}[1]{\texttt{\char`\\ #1}}
-% \newcommand{\meta}[1]{$\langle \mathit{#1}\rangle$}
-% \newcommand{\enquote}[1]{``#1''}
-% \makeatletter
-% \def\verbatim{\@verbatim\frenchspacing\@vobeyspaces\catcode`\%=9 \@xverbatim}
-% \makeatother
-% \begin{document}
-% \maketitle
-%
-% \begin{abstract}
-% \texttt{reverxii.tex} is a $938$-byte long \TeX{} program
-% which lets you play Reversi against your favorite typestting engine.
-% To play, run plain \TeX{}, on the command line, in dvi-mode,
-% on the file \texttt{reverxii.tex}. In most distributions,
-% this should mean running \texttt{tex reverxii.tex} in a terminal.
-% To produce the documentation, run \LaTeX{} on the same file,
-% with any engine (\emph{e.g.}, \texttt{pdflatex reverxii.tex}).
-% \end{abstract}
-%
-% \section{The code}
-%
-% Line breaks can be removed, and the stripped down code takes $938$
-% characters.
-% \begin{verbatim}
+\ifx\>\:\documentclass{report}\fi
\vsize5cm\hsize3cm\newlinechar`*\def~#1{\catcode`#113~}
~IJKLMNOPQRSTUVWXYZjqz@.[|](/+^'";:?,_)!*{ 13\def~#1#2{\let#1#2~}
-\def+{\count1}+1=2}*\cr[\ifnum(\ifcase|\or/\else]\fiN\number@\advance
-X\expandafter!\global?\message~\def.#1{@+1 1\countdef#1+1.}
+\def+{\count\ifx\>\:1\fi1}+1=2}*\cr[\ifnum(\ifcase|\or/\else]\fiN\number
+@\advanceX\expandafter!\global?\message~\def.#1{@+1 1\countdef#1+1.}
.IJKLPQRSTUVWYZ.-1P1T2+44P+55P+45T+54T
~j{[0<Q[9>Q[0<J[9>J^/.]/.]/.]/.]}
~^{+NQNJ}~:#1{#11#12#13#14#15#16#17#18}~M#1{?{#1}#1}
@@ -56,328 +15,4 @@ X\expandafter!\global?\message~\def.#1{@+1 1\countdef#1+1.}
~O{Z"Q\multiplyZ3@Z"J@V(Z9|1|6|1|1|2|6|2|4] }~'{M :{&M}&M{*}}
~~{PXTXTNP\halign{&## *M{*}'_1A_2B_3C_4D_5E_6F_7G_8H'}\vfil\break
I1W(W./0] :\AI0 [0<W[1=PQUJL/q])^P;1][.=W?{(RTie/ Player [0>R-/0]
-wins by N[0>R-]R].}X\end]~}~
-% \end{verbatim}
-%
-% To play a two-player game, change |1=P| to |0=P| near the end:
-% this changes the computer player from $0$ to $1$, hence disabling it.
-%
-% \section{Explanation}
-%
-% \subsection{Some comments}
-%
-% The name \texttt{reverxii.tex} is of course a reference to the infamous
-% \texttt{xii.tex}, also on CTAN, which typesets the lyrics of the
-% \textit{Twelve days of Christmas} using code that very few can understand.
-% In my case, I aimed for the shortest possible code, rather than the
-% most obfuscated. In particular, I did not assign weird catcodes
-% other than making most characters active.
-%
-% Since I was aiming for short code, the text presented to the player
-% is concise, but hopefully enough to leave the game understandable
-% and usable. Despite aiming for short code, I thought it would be
-% neat to typeset a record of the game as it goes: after all, \TeX{}
-% is a typesetting program. This used up around $38$ characters, putting
-% me above the $900$ character line.
-%
-% One technique used to make the code shorter is to rename any primitive
-% used more than once into a single active character. Also, counters
-% holding the information about the board are accessed directly by number.
-%
-% A careful reader would notice that changing one of the remaining
-% one-character control sequences to active characters (|$| is still
-% unused) would gain one character. I didn't do it, because it messes
-% up the code-highlighting of my editor |:)|. Of course, I chose the
-% control sequences which are not active characters to be those appearing
-% the least, only twice each.
-%
-% \subsubsection{On with the code!}
-%
-% First set up the page dimensions. This would not be enough for pdf output.
-% \begin{verbatim}
-% \vsize5cm
-% \hsize3cm
-% \end{verbatim}
-%
-% Since plain \TeX{} does not provide the \cs{typeout} command, we are
-% using \cs{message}, hence need to setup a new line character. It is
-% arbitrarily chosen to be |*|, which will be made active and \cs{let}
-% to \cs{cr}.
-% \begin{verbatim}
-% \newlinechar`*
-% \end{verbatim}
-%
-% Then a first loop to make many characters active. The loop ends when
-% reaching the trailing brace group: there we make spaces active, then
-% redefine |~| for the next loop. We still have an annoying |13~| in
-% the input stream. Introduce a short-hand for count registers whose
-% number starts with |1|, then remove |13| by setting |\count11| to
-% |213| (this counter is used for allocation later on). The next loop
-% is triggered by the |~| which we left.
-% \begin{verbatim}
-% \def~#1{\catcode`#113~}
-% ~IJKLMNOPQRSTUVWXYZjqz@.[|](/+^'";:?,_)!*
-% { 13\def~#1#2{\let#1#2~}\def+{\count1}+1=2}
-% \end{verbatim}
-%
-% At this stage, |~| is defined to take two arguments, \cs{let} the
-% first to the second, and repeat. As announced |*| becomes \cs{cr},
-% so that it will be a new line both in messages and in alignments.
-% We try to keep a relatively consistent naming scheme: opening
-% conditionals are left delimiters, \cs{or} and \cs{else} are
-% middle delimiters, and \cs{fi} is a right bracket. Other primitives
-% which are used a lot are also given short names. The loop ends by
-% redefining |~| to \cs{def}.
-% \begin{verbatim}
-% *\cr
-% [\ifnum
-% (\ifcase
-% |\or
-% /\else
-% ]\fi
-% N\number
-% @\advance
-% X\expandafter
-% !\global
-% ?\message
-% ~\def
-% \end{verbatim}
-%
-% It is time to allocate counters. Unfortunately, \cs{newcount}
-% is \cs{outer}, so it is unpractical to use. We have set |+1|,
-% \emph{aka.} |\count11| to $213$, and will allocate counters
-% starting from that number upwards. Repeatedly advance |+1|
-% by $1$ and define the next character to be the counter number |+1|,
-% then repeat. As always, the loop is interrupted by making it
-% redefine the looping macro |.| to be a counter. We use the trailing
-% dot to assign it the value $-1$, then assign a couple of counters:
-% starting player, other player, and the initial position:
-% the squares in rows and columns $4$ and $5$ are initially
-% filled in a cross pattern.
-% \begin{verbatim}
-% .#1{@+1 1\countdef#1+1.}.IJKLPQRSTUVWYZ.-1P1T2+44P+55P+45T+54T
-% \end{verbatim}
-% Let us summarize which counters are used where:
-% \begin{itemize}
-% \item [P] is the current player ($1$ for |-| or $2$ for |0|);
-% \item [T] is the other player;
-% \item [Q] is the row number;
-% \item [J] is the column number;
-% \item [S] is the step size in the row direction;
-% \item [K] is the step size in the column direction;
-% \item [R] is the score difference, positive when $0$ is winning;
-% \item [V] is used when computing the value of placing a piece at
-% the position (|Q|,|J|);
-% \item [W] is the value of the best possible move according to the AI,
-% also used to end the game if neither player can move;
-% \item [U] is the row number of the best move;
-% \item [L] is the column number of the best move;
-% \item [Y] is a boolean, true ($0$) most of the time,
-% it has to do with when we flip or not, but I don't
-% understand it now, help welcome;
-% \item [Z] is a temporary counter, used locally to compute
-% how much a given cell matters (\emph{i.e.} corners are important),
-% and used globally as a boolean to indicate whether to flip pieces
-% or not.
-% \end{itemize}
-%
-% The board is stored in counters |1|\meta{row}\meta{column}.
-% An empty square is has the value $0$, a square for player |-|
-% has value $1$, and player |0| corresponds to the value $2$.
-% The macro |j| retrieves that value, assuming that the row and
-% column numbers are stored as |Q| and |J|, and returns |.|,
-% that is, $-1$, if outside the board. Recall that |[| is \cs{ifnum},
-% |/| is \cs{else}, and |]| is \cs{fi}. When |Q| and |J| are
-% within bounds, the value is retrieved by |^| as
-% |\count1|\meta{row}\meta{column}. Note that |j| and |^| are
-% only safe to use on the left-hand side of an \cs{ifnum} test
-% (because of expansion issues), and that |^| can be used
-% on the left-hand side of an assignment.
-% \begin{verbatim}
-% ~j{[0<Q[9>Q[0<J[9>J^/.]/.]/.]/.]}
-% ~^{+NQNJ}
-% \end{verbatim}
-%
-% We often need to loop over numbers from $1$ to $8$; here is a macro.
-% \begin{verbatim}
-% ~:#1{#11#12#13#14#15#16#17#18}
-% \end{verbatim}
-%
-% The macros to print the board, both to the dvi and to the console.
-% |M| spews its argument as a \cs{message} (|?|), and directly typeset.
-% This is used rather directly to print the first and last lines (|'|),
-% which are simply alignment cells containing each number from $1$ to $8$,
-% with some care given to spaces and new lines.
-% The |_| macro receives a digit and the corresponding capital letter
-% as arguments, and outputs that row of the board. First place the
-% letter on the left of the board, then loop from $1$ to $8$,
-% typesetting and \cs{message}ing a space, |-| or |0|, depending on
-% the value of the relevant count register. Note the two spaces
-% in the definition: the first ends the counter's number, the second
-% is typeset (in plain \TeX{}, active spaces expand to a normal space).
-% \begin{verbatim}
-% ~M#1{?{#1}#1}
-% ~'{M :{&M}&M{*}}
-% ~_#1#2{M#2:{\B#1}&M#2&M{*}}
-% ~\B#1#2{&M{(+#1#2 |-|0]}}
-% \end{verbatim}
-%
-% The input is done by |q|, which queries the user until they give
-% a correct input, so that |Q| and |J| are in the range $[1,8]$.
-% Prompt the user with a small \cs{message}, giving an example
-% of what move he could do (only true at the start, but the hope
-% is that the player will understand what the input format is).
-% The code that follows is similar to \LaTeXe{}'s \cs{@onelevel@sanitize}.
-% We extract the two first characters from the the \cs{meaning}
-% of the user's input (remember, |X| is \cs{expandafter}),
-% as |#2| and |#3| of \cs{D}. Grab the character code of each,
-% relative to the characters |@| (row) or |0| (column).
-% The closing parenthesis is where most of the work is done.
-% It sets |V| to the value of placing a piece in the cell
-% $(|Q|,|J|)$, zero if the move does not flip any of the opponent's
-% pieces, or if the cell is outside the board. After performing
-% that calculation, if |V| is zero, the move was not valid,
-% and we query the user again.
-% \begin{verbatim}
-% ~q{?{Row and column? e.g. E6*}\read.to\EX\D\meaning\E ;}
-% ~\D#1->#2#3#4;{Q`#2@Q-`@J`#3@J-`0)(V?{Invalid move #2#3.}q]}
-% \end{verbatim}
-%
-% So\ldots{} how do we compute the \enquote{value} of a move?
-% It is automatically invalid if |j| does not return $0$:
-% either the cell is occupied, or it is outside the board.
-% Then for each of the $8$ directions around the cell,
-% we count the number of pieces that are flipped in that
-% direction. The direction is stored as two counters,
-% |S| and |K|, each $\pm 1$ or $0$. We call |,| a first
-% time to test whether flipping should happen in that direction,
-% and, if it is (as indicated by the global value of |Z|),
-% call it again to actually flip. The call to |,| must happen
-% within a group, because it directly changes the row and column
-% numbers |Q| and |J|, following which cell is being queried.
-% \begin{verbatim}
-% ~){V0 (jS1z1z0z.S0z1z.S.z1z0z.]}
-% ~z#1{{K#1Z1{Y1,}(Z,]}}
-% \end{verbatim}
-%
-% The macro |,| is recursive. At each step, move $(|Q|,|J|)$
-% in the direction $(|S|,|K|)$. Then, if that cell (|j|)
-% contains a piece belonging to the other player (|T|),
-% do some stuff |(Y!^P!;2]O| and repeat. What is it that
-% we do? Well, if the condition |Y| is met (I don't remember
-% how that works), we set the current cell to belong to the
-% player, globally, and change the score difference by $2$
-% (see |;|), also globally. Then, we compute with |O| the value
-% corresponding to the cell that we might be flipping (see |O|).
-%
-% Otherwise (|/|), if the cell (|j|) contains a piece from the
-% current player (|P|), it means we have reached the end of a
-% run of the form \meta{initial cell} \meta{opponent's pieces}
-% \meta{own piece}, hence the \meta{opponent's pieces} should
-% count as flipped if we place our piece in the \meta{initial
-% cell}. Until there, all changes to |V| were local, returning
-% to the old value at the end of the group that |,| is contained
-% in. Since the run in that direction was successful, we escape
-% the value of |V| from the group with |\global V=V|. Under
-% some conditions, we set the boolean |Z| to true, globally (|!Z0|),
-% which triggers a second call to |,| with different setting,
-% and actually flips the opponent's pieces.
-% \begin{verbatim}
-% ~,{@QS@JK[j=T(Y!^P!;2]O,/[j=P!VV(I(Y/!Z0]]]]}
-% \end{verbatim}
-%
-% I moved those |O| and |"| guys a little bit in this explanation,
-% but not in the original implemenation, because it is hard
-% to sync. We assign weights to each of the $64$ cells:
-% \begin{center}
-% \halign{&\hfil#\hfil\cr
-% 9 & 1 & 6 & 6 & 6 & 6 & 1 & 9 \cr
-% 1 & 1 & 2 & 2 & 2 & 2 & 1 & 9 \cr
-% 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
-% 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
-% 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
-% 6 & 2 & 4 & 4 & 4 & 4 & 2 & 6 \cr
-% 1 & 1 & 2 & 2 & 2 & 2 & 1 & 9 \cr
-% 9 & 1 & 6 & 6 & 6 & 6 & 1 & 9 \cr}
-% \end{center}
-% All weights are positive, so that every move which flips a piece
-% ends up with a positive overall value. The AI would be better if
-% the places next to corners had a negative weight, but I would have
-% too much code to rewrite for that to work. We really have three
-% types of rows and three types of columns. Converting from |Q|
-% or |J| is done by |"|, then we assemble the two as a number
-% in the range $[0,8]$, and use another \cs{ifcase} construction
-% to produce the weights.
-% \begin{verbatim}
-% ~O{Z"Q\multiplyZ3@Z"J@V(Z9|1|6|1|1|2|6|2|4] }
-% ~"#1{(#1|0|1|2|2|2|2|1|0]}
-% \end{verbatim}
-%
-% The counter |R| keeps track of the score difference,
-% and is updated with |;2| (when flipping a piece) or |;1| (when
-% adding a piece). The counter |R| should be \cs{advance}d (|@|)
-% by a positive amount when the current player |P| is player |0|,
-% and a negative amount for player |-|.
-% \begin{verbatim}
-% ~;{@R(P|-]}
-% \end{verbatim}
-%
-% After printing the board, we go through every cell and find the
-% one with the highest value. The macro \cs{A}, does one row, hence
-% |\:\A| does all the rows. Store the argument as the row number |Q|,
-% then loop over columns. After setting the column number |J| to
-% its argument, \cs{C} calls |)|, which as explained above computes
-% the value of placing a piece there, throws away that case if it flips
-% nothing, otherwise also counts the weight of the current cell. Then
-% update the best value |W| and the best row |U| and column |L| if
-% the new |V| is larger than |W|.
-% \begin{verbatim}
-% ~\A#1{Q#1:\C}
-% ~\C#1{J#1)[0<VO[V>WWVUQLJ]]}
-% \end{verbatim}
-%
-% We won't need |~| as \cs{def} anymore, so we reuse it as the main command.
-% \begin{itemize}
-% \item First exchange the players: set |P| equal to |T|, but first expand
-% the value of |P| after |T| to set that as well.
-% \item Secondly, open an alignment, with a repeating preamble adding a space
-% at its end. Then message a new-line (we should be using |?| rather than
-% |M| here, I think) to keep a clean output. Afterwards, print the top line
-% with |'|, the eight lines of the bulk with |_|, and the bottom line,
-% which happens to end with |?{*}*|, \emph{i.e.}, ends with \cs{cr}:
-% we can thus close the alignment, and cause \TeX{} to output the page.
-% \item After printing, it is time to check whether there is a move or not.
-% We don't want to flip pieces at this stage, hence set the boolean |I|
-% to false ($1$). And we reset the best value to $0$, unless it was
-% already $0$ (which means that the previous player had no available
-% move), in which case we set it to $-1$. Then loop over rows, finding
-% the best value (see \cs{A}). Reset the boolean |I| to be true.
-% \item If a move was found in the previous step ($|W|>0$), either
-% use it if the player is the AI, or query the user. The various
-% booleans are set up in such a way as to do the flipping, so
-% calling |)| does it. Then also put a player's piece in the current
-% cell |^|, and increase the score difference by $1$.
-% \item Finally, if neither player could move, declare the game ended,
-% give the score, and \cs{end} the run. Otherwise, repeat.
-% \end{itemize}
-% Of course, after defining |~|, we call it. Let's play!
-% \begin{verbatim}
-% ~~{
-% PXTXTNP
-% \halign{&## *M{*}'_1A_2B_3C_4D_5E_6F_7G_8H'}\vfil\break
-% I1 W(W./0] :\AI0
-% [0<W
-% [1=PQUJL/q]
-% )
-% ^P;1
-% ]
-% [.=W
-% ?{(RTie/ Player [0>R-/0] wins by N[0>R-]R].}
-% X\end
-% ]
-% ~
-% }
-% ~
-% \end{verbatim}
-%
-% \end{document} \ No newline at end of file
+wins by N[0>R-]R].}X\dump]~}~ \ No newline at end of file