\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{geometry}
\geometry{margin=2.6cm}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{booktabs}
\usepackage{hyperref}
\usepackage{enumitem}
\usepackage{parskip}
\usepackage{titlesec}

\definecolor{codebg}{RGB}{245,245,245}
\definecolor{codecomment}{RGB}{110,110,110}

\lstdefinelanguage{latexcs}{
  morecomment=[l]{\%},
  commentstyle=\color{codecomment}\itshape,
  basicstyle=\ttfamily\small,
  showstringspaces=false,
  breaklines=true,
}

\lstset{
  language=latexcs,
  backgroundcolor=\color{codebg},
  frame=single,
  rulecolor=\color{black!20},
  framesep=6pt,
  xleftmargin=8pt,
  xrightmargin=8pt,
}

\titleformat{\section}{\Large\bfseries}{\thesection}{0.6em}{}
\titleformat{\subsection}{\large\bfseries}{\thesubsection}{0.6em}{}

\hypersetup{colorlinks=true,linkcolor=blue!50!black,urlcolor=blue!50!black}

\newcommand{\pkg}[1]{\textsf{#1}}
\newcommand{\cs}[1]{\texttt{\textbackslash#1}}
\newcommand{\note}[1]{%
  \par\noindent\fbox{\begin{minipage}{0.95\linewidth}\small\textbf{Key point.} #1\end{minipage}}\par\vspace{4pt}
}
\newcommand{\warn}[1]{%
  \par\noindent\fbox{\begin{minipage}{0.95\linewidth}\small\textbf{Warning.} #1\end{minipage}}\par\vspace{4pt}
}

\title{\textsf{cssyntax} \\ \large A syntax core for readable command names}
\author{Pedagogical documentation -- v0.1.2}
\date{July 17, 2026}

\begin{document}
\maketitle
\tableofcontents

\section{Why this package}

In LaTeX, a command name can only be made of letters. As soon as you want
long, structured internal names -- for instance to make clear which module,
which variable type, and which precise function a command belongs to -- you
quickly run into unreadable names: \cs{lwnxvarboxexlabel} tells the eye
nothing, whereas \texttt{lw\_nx\_var\_box/ex\_label} tells a story (the
\texttt{lw} bundle, the \texttt{nx} module, a \emph{box}-type \emph{variable},
concerning the \emph{label} of an \emph{example}).

\pkg{cssyntax} does exactly one thing: it temporarily turns a few
punctuation characters (\texttt{\_}, \texttt{:}, \texttt{/}) into letters,
for as long as it takes to define or use this kind of structured name. It
also provides a handful of readable aliases for common TeX primitives, and
a small toolkit for writing packages: dynamic command definition, low-level
environment declaration, native and declarative conditional tests, and
position-sensitive lookahead tests for building variadic-argument macros.

This package has no domain of its own: it knows nothing about linguistics,
mathematics, or page layout. Any package author can use it to structure
their own internal command names.

\note{\pkg{cssyntax} is a tool \emph{for package authors}. An end user
composing a document never needs to type an \texttt{\_fun\_...} command
directly -- these names live in the internal code of packages that
\emph{use} \pkg{cssyntax}, not in the user's document.}

\section{Naming convention}

\begin{center}
\ttfamily container\_module\_type\_subtype/description
\end{center}

\texttt{:} and \texttt{/} are interchangeable separators, both always
active at the same time. A project usually picks one for its new code and
keeps the other only for compatibility with an already-published module.

\note{Digits must never appear in a name built with \pkg{cssyntax}. They
must stay recognisable as digits everywhere else (dimensions, counters,
arithmetic). Use words (\texttt{zero}, \texttt{one}, \texttt{two}\ldots)
to number a series of commands.}

\section{Getting started}

\begin{lstlisting}
\usepackage{cssyntax}

\csSyntaxOn
  % ... your structured command definitions ...
\csSyntaxOff
\end{lstlisting}

\cs{csSyntaxOn} activates \texttt{\_}, \texttt{:} and \texttt{/} as
letters. \cs{csSyntaxOff} restores their catcode. Neither command contains
any of the three characters it activates, so both remain usable before
the extended syntax is in effect. \cs{csSyntaxEnabled} / \cs{csSyntaxDisabled}
are identical aliases.

\warn{Always close an open scope with \cs{csSyntaxOff} before the end of
the file. A \texttt{\_} left active would break subscripts in math mode
(\texttt{\$x\_i\$}) throughout the rest of the document.}

\section{Definition primitives}

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Command & Equivalent to & Behaviour \\
\midrule
\texttt{\_fun\_define/cs} & \cs{def} & no expansion \\
\texttt{\_fun\_define/exp} & \cs{edef} & local expansion \\
\texttt{\_fun\_define/global} & \cs{gdef} & global, no expansion \\
\texttt{\_fun\_define/global\_exp} & \cs{xdef} & global, with expansion \\
\texttt{\_fun\_define/protected\_exp} & \cs{protected@edef} & protected expansion \\
\texttt{\_fun\_copy/cs} & \cs{let} & copies a meaning \\
\bottomrule
\end{tabular}
\end{center}

\note{\texttt{\_fun\_copy/cs}, \texttt{\_fun\_save/cs}, \texttt{\_fun\_use/cs},
\texttt{\_fun\_reset/cs} and \texttt{\_fun\_swap/cs} keep a raw \cs{let}
internally: they implement idempotent, repeatable operations, which the
package's own \cs{NewCommandCopy}-based alias declarations (see below)
would break if used in their place.}

\section{Expansion, introspection, and dynamic definition}

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Command & Equivalent to & \\
\midrule
\texttt{\_fun\_expand/after} & \cs{expandafter} & reorder expansion \\
\texttt{\_fun\_expand/prevent} & \cs{noexpand} & protect a token \\
\texttt{\_fun\_build/cs\{name\}} & \cs{csname}...\cs{endcsname} & build \& invoke \\
\texttt{\_fun\_get/cs\_string} & \cs{string} & printable form \\
\texttt{\_fun\_get/cs\_meaning} & \cs{meaning} & inspect definition \\
\bottomrule
\end{tabular}
\end{center}

\subsection{Dynamic command definition}

\texttt{\_fun\_build/cs} only \emph{invokes} an existing command built
from a string. \texttt{\_fun\_build/def} and \texttt{\_fun\_build/redef}
\emph{define} a new one instead.

\begin{lstlisting}
\csSyntaxOn
\_fun_build/def{lw_test_greet}{#1}{Hello, #1!}
\csSyntaxOff
\lw_test_greet{World} % -> "Hello, World!"
\end{lstlisting}

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Command & Wraps & Collision behaviour \\
\midrule
\texttt{\_fun\_build/def\{name\}<param-text>\{body\}} & \cs{cs\_new:cpn} & refuses if name exists \\
\texttt{\_fun\_build/redef\{name\}<param-text>\{body\}} & \cs{cs\_set:cpn} & silently overwrites \\
\bottomrule
\end{tabular}
\end{center}

\warn{The parameter-text argument must be passed \emph{without} extra
braces around it when forwarded into \cs{cs\_new:cpn}/\cs{cs\_set:cpn}
from inside a wrapper -- adding a brace group there raises
\emph{"Illegal parameter number"}.}

\note{\texttt{\_fun\_build/cs}'s underlying \cs{csname}...\cs{endcsname}
has a side effect worth knowing: if the name was never defined, it is
implicitly created as \cs{relax} rather than raising an error. A typo in
a dynamically-built name fails silently instead of loudly.}

\section{Groups}

TeX distinguishes a box-construction scope from a general-purpose
local-values scope.

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Command & Equivalent to & Use \\
\midrule
\texttt{\_fun\_enter/group} & \cs{begingroup} & general-purpose (default) \\
\texttt{\_fun\_exit/group} & \cs{endgroup} & \\
\texttt{\_fun\_enter/token\_group} & \cs{bgroup} & literal brace token, e.g.\ box delimiter \\
\texttt{\_fun\_exit/token\_group} & \cs{egroup} & \\
\bottomrule
\end{tabular}
\end{center}

\begin{lstlisting}
\csSyntaxOn
\hbox\_fun_enter/token_group some text\_fun_exit/token_group
\csSyntaxOff
\end{lstlisting}

\section{Generic helpers}

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Command & Role \\
\midrule
\texttt{\_fun\_save/cs\{name\}} & store a command's current definition \\
\texttt{\_fun\_use/cs\{name\}} & recall a stored definition \\
\texttt{\_fun\_reset/cs\{name\}} & reset to \cs{relax} \\
\texttt{\_fun\_swap/cs\{a\}\{b\}} & exchange two definitions \\
\texttt{\_fun\_pick/first, \_fun\_pick/second} & \cs{@firstoftwo} / \cs{@secondoftwo} \\
\texttt{\_fun\_gobble/one, \_fun\_gobble/two} & \cs{@gobble} / \cs{@gobbletwo} \\
\bottomrule
\end{tabular}
\end{center}

\section{Files}

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Command & Behaviour \\
\midrule
\texttt{\_fun\_load/\{file\}} & unconditional load (alias of \cs{input}) \\
\texttt{\_fun\_load/if\_exists\{file\}\{found\}\{not found\}} & conditional load with both branches \\
\bottomrule
\end{tabular}
\end{center}

\warn{\texttt{\_fun\_load/if\_exists} is built on \cs{IfFileExists}
combined with \cs{input}, \emph{not} on \cs{InputIfFileExists} --
the latter is a 2-argument kernel command with no built-in "not found"
branch; passing it a third argument leaves it dangling in the input
stream instead of raising an error.}

\texttt{\_fun\_enter/source} (alias \texttt{\_fun\_enter/input}) is a
no-op marker for symmetry. \texttt{\_fun\_exit/source} (alias
\texttt{\_fun\_exit/input}) is a genuine \cs{endinput}, scoped strictly
to the file it appears in.

\section{Low-level environment declaration}

Emulates \cs{begin}/\cs{end} at a low level, anywhere -- including the
preamble, where \cs{newenvironment}-based environments are often unsafe
because they assume the main vertical list already exists.

\begin{lstlisting}
\csSyntaxOn
\_fun_declare/env{myenv}{[enter] }{ [exit]}
\csSyntaxOff
\_fun_use/myenv{content} % -> "[enter] content [exit]"
\end{lstlisting}

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Command & Role \\
\midrule
\texttt{\_fun\_declare/env\{name\}\{enter\}\{exit\}} & no automatic grouping \\
\texttt{\_fun\_declare/auto\_group\{name\}\{enter\}\{exit\}} & wraps enter in \cs{begingroup}, exit in \cs{endgroup} \\
\bottomrule
\end{tabular}
\end{center}

Each declaration also generates a matching \texttt{\_fun\_use/name\{content\}}
that opens, typesets, and closes -- usable identically regardless of which
of the two declaration commands built it.

\section{Booleans}

Built on \pkg{xifthen}'s \cs{newboolean}/\cs{setboolean}/\cs{boolean}: a
name collision raises an explicit error instead of silently overwriting.

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Command & Role \\
\midrule
\texttt{\_fun\_declare/bool\{name\}} & create, initialised false \\
\texttt{\_fun\_set/bool\_true, \_fun\_set/bool\_false} & set the value \\
\bottomrule
\end{tabular}
\end{center}

\note{Every boolean lives under an internal \texttt{\_user\_bool/} prefix,
protecting against collisions with the rest of the document -- but not
against two different bundle modules picking the same short name.
Callers should still prefix their own module name, e.g.\
\texttt{\_fun\_declare/bool\{nx\_note\_forced\_margin\}}.}

\section{The \texttt{\_fun\_test/} family -- native TeX conditionals}

One command opens a test, a single common command closes it, whatever
kind of test was opened.

\begin{lstlisting}
\csSyntaxOn
\_fun_enter/test_bool{queue_done}
  \_fun_doif/true{Done.}
  \_fun_doif/false{One more word.}
\_fun_exit/test
\csSyntaxOff
\end{lstlisting}

\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
Opening & Equivalent to & \\
\midrule
\texttt{\_fun\_enter/test\_bool\{name\}} & reads a declared boolean & \\
\texttt{\_fun\_enter/test\_dim\{a\}\{rel\}\{b\}} & \cs{ifdim} & \texttt{rel}: \texttt{<},\texttt{=},\texttt{>} \\
\texttt{\_fun\_enter/test\_num\{a\}\{rel\}\{b\}} & \cs{ifnum} & \\
\texttt{\_fun\_enter/test\_cs\{a\}\{b\}} & \cs{ifx} & \\
\texttt{\_fun\_enter/test\_case\{n\}} & \cs{ifcase} & \\
\bottomrule
\end{tabular}
\end{center}

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Branch & Role \\
\midrule
\texttt{\_fun\_doif/true} & executed if true \\
\texttt{\_fun\_doif/false} (alias \texttt{/else}) & executed otherwise (a real \cs{else}) \\
\texttt{\_fun\_doif/case} & one branch of \cs{ifcase} (a real \cs{or}) \\
\texttt{\_fun\_exit/test} & closes any of the above (a real \cs{fi}) \\
\bottomrule
\end{tabular}
\end{center}

\warn{\texttt{\_fun\_doif/false}, \texttt{/case} and \texttt{\_fun\_exit/test}
are genuine aliases of \cs{else}, \cs{or}, \cs{fi} (built with a true
meaning-copy), not macros expanding to them -- TeX recognises these during
conditional-skipping by comparing meaning without expanding tokens it
scans past. \texttt{\_fun\_doif/case} therefore takes no index argument:
write \texttt{\_fun\_doif/case <code>} directly.}

\section{The \texttt{\_fun\_check/} family -- declarative tests}

These take their whole content -- subject plus named \texttt{true}/\texttt{else}
branches -- in one call. There is no matching \texttt{\_fun\_exit/check}.

\begin{lstlisting}
\csSyntaxOn
\_fun_check/pkg_loaded{hyperref}{
  true = {Loaded.},
  else = {Not loaded.}
}
\csSyntaxOff
\end{lstlisting}

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Command & Role \\
\midrule
\texttt{\_fun\_check/defined\{cs\}} & is \texttt{cs} defined? \\
\texttt{\_fun\_check/pkg\_loaded\{pkg\}} & is \texttt{pkg} loaded? \\
\texttt{\_fun\_check/streq\{a\}\{b\}} & are the strings equal? \\
\bottomrule
\end{tabular}
\end{center}

\subsection{Position-sensitive lookahead tests}

\texttt{\_fun\_check/if\_next\_char} and \texttt{\_fun\_check/if\_next\_group}
inspect whatever token comes right after their own call, instead of
testing a value already in hand -- the building block for macros that
accept a flexible, unbounded number of \texttt{\{group\}\{group\}...}
arguments.

\begin{lstlisting}
\csSyntaxOn
\_fun_check/if_next_group{
  true  = {,~\lw_test_list_sep},
  else  = {}
}
\csSyntaxOff
\end{lstlisting}

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Command & Tests \\
\midrule
\texttt{\_fun\_check/if\_next\_char\{char\}} & does this character follow? (wraps \cs{@ifnextchar}) \\
\texttt{\_fun\_check/if\_next\_group} & does a \texttt{\{...\}} group follow? (wraps \cs{peek\_catcode:NTF}) \\
\bottomrule
\end{tabular}
\end{center}

\warn{These two are the only members of \texttt{\_fun\_check/} that close
cssyntax's own syntax scope themselves, as the very first action of their
body. Do \emph{not} write \cs{csSyntaxOff} between the call and the
content being tested -- doing so inserts \cs{csSyntaxOff} itself as the
token being peeked at, hiding the real one. Both are defined with
\cs{cs\_new:cpn} while \cs{ExplSyntaxOn} is active at authoring time, not
merely invoked at run time: a literal space typed in an ordinary \cs{def}
body becomes a permanent catcode-10 token regardless of \cs{ExplSyntaxOn}
called later, and such a token inside \cs{peek\_catcode:NTF}'s own branch
was found to make it misreport its result. A second, independent finding:
\cs{keys\_set:nn} must never be called \emph{before} a
\cs{peek\_catcode:NTF} in the same expansion -- even an unrelated call
placed earlier is enough to break the peek. It is safe only when placed
textually inside the peek's own true/false branches.}

\section{Quick reference}

\begin{center}
\begin{tabular}{@{}ll@{}}
\toprule
Family & Look for \\
\midrule
Bootstrap & \cs{csSyntaxOn} / \cs{csSyntaxOff} \\
Definition & \texttt{\_fun\_define/*}, \texttt{\_fun\_copy/cs} \\
Expansion \& dynamic def. & \texttt{\_fun\_expand/*}, \texttt{\_fun\_build/cs|def|redef}, \texttt{\_fun\_get/*} \\
Groups & \texttt{\_fun\_enter|exit/group}, \texttt{\_fun\_enter|exit/token\_group} \\
Helpers & \texttt{\_fun\_save|use|reset|swap/cs}, \texttt{\_fun\_pick/*}, \texttt{\_fun\_gobble/*} \\
Files & \texttt{\_fun\_load/}, \texttt{\_fun\_load/if\_exists}, \texttt{\_fun\_enter|exit/source} \\
Environments & \texttt{\_fun\_declare/env|auto\_group}, generated \texttt{\_fun\_use/*} \\
Booleans & \texttt{\_fun\_declare/bool}, \texttt{\_fun\_set/bool\_true|false} \\
Native tests & \texttt{\_fun\_enter/test\_*} \ldots \texttt{\_fun\_doif/*} \ldots \texttt{\_fun\_exit/test} \\
Declarative tests & \texttt{\_fun\_check/defined|pkg\_loaded|streq} \\
Lookahead tests & \texttt{\_fun\_check/if\_next\_char|if\_next\_group} \\
\bottomrule
\end{tabular}
\end{center}

\end{document}
