CSS

Saturday, November 27, 2021

ISO-2145 numbering in Plain TeX

I've been writing terse notes using something like the ISO-2145 standard. Could some TeX macros be written to support writing such notes? Of course!

Like amsart's subsections and subsubsections, I wanted my sections to be "runin style" (i.e., an unindented paragraph with a leading number, possibly with some label in bold, which is separated by vertical space from the previous paragraph). Following Knuth's WEB macros, I use \M to create a new unlabeled "section", and \N{label} to create a new labeled "section". These macros increment the section number, makes the section number (and label, if present) in bold, and does not indent the section's first paragraph.

But I wanted to allow arbitrary depths for "section numbers". What I did was treat the section number like a stack. When I wanted to work on a new subsection level, I call \M[1] (or, for labeled subsections, \N[1]{My label for the new subsection.}).

If I want to "ascend" back to the parent section, I call \M[-1] (and \N[-1]{...}) which will pop the section stack, then increment the top of the stack.

Should I want to move more than one level up or down, I simply call \M[n] (or \N[n]{...}) for any nonzero integer n. If called with n=0, then it's treated as \M (and \N{...}, resp.).

Retrospective

If I were to do this all over again, I would probably use different variable names (for example, I would probably use \chunkctr instead of \section for the counter name). But I just wanted to see if it was possible.

Also, this code could work in LaTeX, but needs some small modifications. (Well, mostly dealing with making \chunkctr a bona fide counter, so \refstepcounter could use its magic for referencing things properly.)

I guess if I wanted to be fully general, I would have some additional parameters to allow customizing the vertical spacing for separating the sections, the horizontal space between the section number and the next symbol, etc.

Code and Worked Example

\newcount\section
\section=0

% technically, sectionDepth is not needed, but it's to avoid popping off
% "too many sections"
\newcount\sectionDepth \sectionDepth=0

% \secprefix will be redefined to append the current section number
% followed by a period
\def\secprefix{}

% \pushSection simply appends "\the\section." to \secprefix's current value
\def\pushSection{
  \global\advance\sectionDepth by1
  \let\sectmp\secprefix
  \xdef\secprefix{\sectmp\the\section.}
}

% update \secprefix from "a.b.(...).c.d" to "a.b.(...).c"
% and set \section to "d" (as a number)
\def\popSection{
  \def\newPrefix{}
  \global\advance\sectionDepth by-1
  \def\updatePrefix##1.##2\endUpdate{
    \ifx\relax##2 % if we are at the last number
      % globally set \secprefix to be the leading (n-1) section numbers
      \xdef\secprefix{\newPrefix}
      % globally set \section to be the last section number from \secprefix
      \global\section=\number##1
    \else
      \xdef\newPrefix{\newPrefix##1.}
      \updatePrefix##2\endUpdate
    \fi}
  \ifx\secprefix\relax % if \secprefix is empty
  \else
    \expandafter\updatePrefix\secprefix\relax\endUpdate
  \fi
}

\def\thesection{\secprefix\the\section.}
\def\stepSec{\global\advance\section by1}

\def\downOne{
  \pushSection
  \global\section=0
}

\def\upOne{
  \popSection
}

\newcount\secIter \secIter=0

% \up ascends back to one of the parent sections, ascending #1 levels
\def\up#1{ % #1 is a negative integer
  \ifnum\sectionDepth>0%
    \secIter=#1%
    \multiply\secIter by-1
    % but \sectionDepth is a non-negative integer, so transform it
    \ifnum\secIter>\sectionDepth%
      \global\secIter=\sectionDepth
    \fi%
    \loop
      \ifnum\secIter>0
      \upOne
      \advance\secIter by-1
    \repeat%
  \fi%
}

% down appends #1 subsections to the given section number
\def\down#1{ % #1 is a positive integer
  \global\secIter=#1%
  \loop
    \ifnum\secIter>0
    \downOne
    \advance\secIter by-1
  \repeat%
}

% move up or down #1 layers, does nothing if #1 is zero
\def\adjustSection#1{\ifnum#1<0\up{#1}\else{\ifnum#1>0\down{#1}\fi}\fi}

% \M is used for unlabeled paragraphs
\def\Mlabel{\medbreak\noindent\ignorespaces%
  {\bf\thesection\ignorespaces}\enspace\ignorespaces}
\def\Mnone{\stepSec\Mlabel}
\def\Mmany[#1]{\adjustSection{#1}\Mnone}
\def\xM{\ifx\tmp[\expandafter\Mmany\else\Mnone\fi}
\def\M{\futurelet\tmp\xM}

% \N is used for labeled paragraphs
\def\Nlabel#1{\medbreak\noindent\ignorespaces%
  {\bf\thesection\ignorespaces\enspace#1\ignorespaces}\enspace\ignorespaces}
\def\Nnone#1{\stepSec\Nlabel{#1}}
\def\Nmany[#1]#2{\adjustSection{#1}\Nnone{#2}}
\def\xN{\ifx\tmp[\expandafter\Nmany\else\Nnone\fi}
\def\N{\futurelet\tmp\xN}

\M
This is a test to see if this works.

\M I hope it works.

\M[1]
And this should be \S{2.1}, I hope.

\N[1]{Claim:} This should be \S{2.1.1}.

\M[-1]
This should be \S{2.2}.

\M[3]
This should be \S{2.2.0.0.1}, I hope?

\N[-3]{Theorem.}%
This is \S3, yes?

\M[-1]
No, this is \S3.

\bye

Addendum: in LaTeX

It turns out to be a straightforward generalization to LaTeX:

\documentclass{article}
\usepackage{fullpage}
\newcounter{chunk}

\def\setSection#1{\setcounter{chunk}{#1}}
\def\stepSec{\refstepcounter{chunk}}

% technically, sectionDepth is not needed, but it's to avoid popping off
% "too many sections"
\newcount\sectionDepth \sectionDepth=0

% \secprefix will be redefined to append the current section number
% followed by a period
\def\secprefix{}
\renewcommand\thechunk{\secprefix\arabic{chunk}.}

% \pushSection simply appends "\arabic{chunk}." to \secprefix's current value
\def\pushSection{
  \global\advance\sectionDepth by1
  \let\sectmp\secprefix
  \xdef\secprefix{\sectmp\arabic{chunk}.}
}

% update \secprefix from "a.b.(...).c.d" to "a.b.(...).c"
% and set \chunk counter to "d" (as a number)
\def\popSection{
  \def\newPrefix{}
  \global\advance\sectionDepth by-1
  \def\updatePrefix##1.##2\endUpdate{
    \ifx\relax##2 % if we are at the last number
      % globally set \secprefix to be the leading (n-1) section numbers
      \xdef\secprefix{\newPrefix}
      % globally set \chunk to be the last section number from \secprefix
      \setSection{\number##1}
    \else
      \xdef\newPrefix{\newPrefix##1.}
      \updatePrefix##2\endUpdate
    \fi}
  \ifx\secprefix\relax % if \secprefix is empty
  \else
    \expandafter\updatePrefix\secprefix\relax\endUpdate
  \fi
}

\def\downOne{\pushSection\setSection{0}}

\def\upOne{\popSection}

\newcount\secIter \secIter=0

% \up ascends back to one of the parent sections, ascending #1 levels
\def\up#1{ % #1 is a negative integer
  \ifnum\sectionDepth>0%
    \secIter=#1%
    % but \sectionDepth is a non-negative integer, so transform it
    \multiply\secIter by-1
    \ifnum\secIter>\sectionDepth%
      \global\secIter=\sectionDepth
    \fi%
    \loop
      \ifnum\secIter>0
      \upOne
      \advance\secIter by-1
    \repeat%
  \fi%
}

% down appends #1 subsections to the given section number
\def\down#1{ % #1 is a positive integer
  \global\secIter=#1%
  \loop
    \ifnum\secIter>0
    \downOne
    \advance\secIter by-1
  \repeat%
}
\def\adjustSection#1{\ifnum#1<0\up{#1}\else{\ifnum#1>0\down{#1}\fi}\fi}

%% \M is used for unlabeled paragraphs
\newcommand\M[1][0]{\adjustSection{#1}\medbreak%
  \stepSec\noindent\ignorespaces%
  \textbf{\thechunk\ignorespaces}\enspace\ignorespaces}

%% \N is used for labeled paragraphs
\newcommand\N[2][0]{\adjustSection{#1}\medbreak%
  \stepSec\noindent\ignorespaces%
  \textbf{\thechunk\ignorespaces\enspace#2\ignorespaces}\enspace\ignorespaces}

\begin{document}

\M
This is a test to see if this works.

\M I hope it works.

\M[1]
And this should be \S{2.1}, I hope.

\N[1]{Claim:} This should be \S{2.1.1}.

\M[-1]
This should be \S{2.2}.

\M[3]
This should be \S{2.2.0.0.1}, I hope?

\N[-3]{Theorem.}%
This is \S3, yes?

\M[-1]
No, this is \S3.

\M[0] This should be \S4.

\N[0]{Corollary.} This is \S5?
\end{document}

No comments:

Post a Comment