diff options
Diffstat (limited to 'macros')
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 Binary files differindex dec82bce1e..74cb5048e3 100644 --- a/macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdf +++ b/macros/latex/contrib/babel-contrib/sorbian/lsorbian.pdf 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 Binary files differindex edba5ff080..e9f2cf5c02 100644 --- a/macros/latex/contrib/babel-contrib/sorbian/usorbian.pdf +++ b/macros/latex/contrib/babel-contrib/sorbian/usorbian.pdf 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 Binary files differindex 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 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 Binary files differindex c65765d5b9..3c28176a7d 100644 --- a/macros/luatex/generic/luakeys/luakeys.pdf +++ b/macros/luatex/generic/luakeys/luakeys-doc.pdf 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 Binary files differindex 49fee5afac..0318eda2cb 100644 --- a/macros/luatex/latex/pyluatex/pyluatex.pdf +++ b/macros/luatex/latex/pyluatex/pyluatex.pdf 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 Binary files differindex b2732c7816..3117f4b371 100644 --- a/macros/plain/contrib/reverxii/reverxii.pdf +++ b/macros/plain/contrib/reverxii/reverxii.pdf 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 |