From 7c33483242d8e20f8f809e76ec9a3406db2dbfab Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Tue, 31 Jan 2023 03:01:43 +0000 Subject: CTAN sync 202301310301 --- macros/luatex/generic/lparse/README.md | 34 ++++ macros/luatex/generic/lparse/lparse-doc.tex | 205 +++++++++++++++++++ macros/luatex/generic/lparse/lparse.lua | 302 ++++++++++++++++++++++++++++ macros/luatex/generic/lparse/lparse.pdf | Bin 0 -> 108107 bytes macros/luatex/generic/lparse/lparse.sty | 22 ++ macros/luatex/generic/lparse/lparse.tex | 22 ++ 6 files changed, 585 insertions(+) create mode 100644 macros/luatex/generic/lparse/README.md create mode 100644 macros/luatex/generic/lparse/lparse-doc.tex create mode 100644 macros/luatex/generic/lparse/lparse.lua create mode 100644 macros/luatex/generic/lparse/lparse.pdf create mode 100644 macros/luatex/generic/lparse/lparse.sty create mode 100644 macros/luatex/generic/lparse/lparse.tex (limited to 'macros/luatex') diff --git a/macros/luatex/generic/lparse/README.md b/macros/luatex/generic/lparse/README.md new file mode 100644 index 0000000000..f6210b4923 --- /dev/null +++ b/macros/luatex/generic/lparse/README.md @@ -0,0 +1,34 @@ +# lparse + +Parse and scan macro arguments in `Lua` with `LuaTeX` using a `xparse` +like argument specification. + +The name lparse is derived from xparse. The x has been replaced by l +because this package only works with LuaTeX. l stands for Lua. Just as +with xparse, it is possible to use a special syntax consisting of single +letters to express the arguments of a macro. However, lparse is able to +read arguments regardless of the macro systemd used - whether LaTeX or +ConTeXt or even plain TeX. Of course, LuaTeX must always be used as the +engine. + +## Current version + +2023/01/29 v0.1.0 + +## License + +Copyright (C) 2023 by Josef Friedrich +------------------------------------------------------------------------ +This work may be distributed and/or modified under the conditions of +the LaTeX Project Public License, either version 1.3 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.3 or later is part of all distributions of LaTeX +version 2005/12/01 or later. + +## Maintainer + +Josef Friedrich diff --git a/macros/luatex/generic/lparse/lparse-doc.tex b/macros/luatex/generic/lparse/lparse-doc.tex new file mode 100644 index 0000000000..35c618f277 --- /dev/null +++ b/macros/luatex/generic/lparse/lparse-doc.tex @@ -0,0 +1,205 @@ +\documentclass{ltxdoc} + +\EnableCrossrefs +\CodelineIndex +\RecordChanges + +\usepackage{mdframed} +\usepackage{minted} +\usepackage{multicol} +\usepackage{luacode} +\usepackage{syntax} + +\usemintedstyle{friendly} +\BeforeBeginEnvironment{minted}{\begin{mdframed}} +\AfterEndEnvironment{minted}{\end{mdframed}} +\setminted{ + breaklines=true, + fontsize=\footnotesize, + style=manni, +} +\def\lua#1{\mintinline{lua}|#1|} +\def\latex#1{\mintinline{latex}|#1|} + +\NewDocumentCommand { \InputLatex } { O{} m } { + \begin{mdframed} + \inputminted[linenos=false,#1]{latex}{examples/#2} + \end{mdframed} +} + +\NewDocumentCommand { \InputLua } { O{} m } { + \begin{mdframed} + \inputminted[linenos=false,#1]{lua}{examples/#2} + \end{mdframed} +} + +\begin{document} + +\providecommand*{\url}{\texttt} + +\title{The \textsf{lparse} package} +\author{% + Josef Friedrich\\% + \url{josef@friedrich.rocks}\\% + \href{https://github.com/Josef-Friedrich/lparse} + {github.com/Josef-Friedrich/lparse}% +} +\date{0.1.0 from 2023/01/29} + +\maketitle + +\InputLatex[firstline=5,lastline=14]{title-page.tex} + +\newpage + +\tableofcontents + +\newpage + +% \section{Einführung} +\section{Introduction} + +% Der Name |lparse| ist von |xparse| abgeleitet. +The name |lparse| is derived from |xparse|. +% +% Das |x| wurde durch |l| ersetzt, da dieses Packet nur zusammen mit +% Lua\TeX{} funktioniert. +The |x| has been replaced by |l| because this package only works with Lua\TeX{}. +% +% |l| steht für \emph{Lua} +|l| stands for \emph{Lua}. +% +% Ebenso wie bei dem Paket |xparse| ist es möglich, die Argumente eines +% Makros mit Hilfe einer speziellen Syntax bestehend aus einzelnen +% Buchstaben zu beschreiben. +Just as with |xparse|, it is possible to use a special syntax consisting +of single letters to express the arguments of a macro. +% +% |lparse| ist jedoch im Stande unabhängig von der verwendeten +% Makrosammlung - ob LaTeX oder ConTeXt oder sogar Plain TeX - Argumente +% einzulesen. +However, |lparse| is able to read arguments regardless of the macro +systemd used - whether \LaTeX{} or Con\TeX{}t or even plain \TeX. +% +% Als Engine musst natürlich immer LuaTeX verwendet werden. +Of course, Lua\TeX{} must always be used as the engine. + +\subsection{Similar projects} + +% Für ConTeXt gibt es einen ähnlichen Argumenten scanner. +For Con\TeX{}t there is a similar argument scanner (see Con\TeX{}t Lua +Document +\href{http://www.pragma-ade.nl/general/manuals/cld-mkiv.pdf}{cld-mkiv}). +% +% Dieser Scanner ist in folgenden Dateien implementiert +This scanner is implemented in the following files: +\href{https://github.com/contextgarden/context/blob/main/tex/context/base/mkiv/toks-scn.lua}{toks-scn.lua} +\href{https://github.com/contextgarden/context/blob/main/tex/context/base/mkiv/toks-aux.lua}{toks-aux.lua} +\href{https://github.com/contextgarden/context/blob/main/tex/context/base/mkiv/toks-ini.lua}{toks-ini.lua} +% Con\TeX{}t scanner verwendet offenbar die Token Bibliothek des LuaTeX Nachfolgeprojekts luametaTeX +% +Con\TeX{}t scanner apparently uses the token library of the LuaTeX +successor project luameta\TeX: +\href{https://github.com/contextgarden/context/blob/main/source/luametatex/source/lua/lmttokenlib.c}{lmttokenlib.c} + +\section{Description of the argument specification} +% +% Folgende Listen, die die Argumenttypen beschreiben, entstammen den +% Handbücher usrguide bzw. xparse. +The following lists, which describe the individual argument types, are +taken from the manuals +\href{http://mirrors.ctan.org/macros/latex/base/usrguide.pdf}{usrguide} +and +\href{http://mirrors.ctan.org/macros/latex/contrib/l3packages/xparse.pdf}{xparse}. +% +% Die Beschreibungstexte der einzelnen Argumententypen wurden nur leicht angepasst. +The descriptive texts of the individual argument types have only been +slightly adjusted. +% +% Die noch nicht unterstützen Argumentypen sind eingeklammert. +The argument types that are not yet supported are bracketed. + +% /usr/local/texlive/texmf-dist/source/latex/l3packages/xparse/xparse.dtx +\begin{itemize} + \item[m] A standard mandatory argument, which can either be a single + token alone or multiple tokens surrounded by curly braces |{}|. + Regardless of the input, the argument will be passed to the + internal code without the outer braces. This is the \pkg{xparse} + type specifier for a normal \TeX{} argument. + \item[r] Given as \texttt{r}\meta{token1}\meta{token2}, this denotes a + \enquote{required} delimited argument, where the delimiters are + \meta{token1} and \meta{token2}. If the opening delimiter + \meta{token1} is missing, |nil| will be + returned after a suitable error. + \item[R] Given as \texttt{R}\meta{token1}\meta{token2}\marg{default}, + this is a \enquote{required} delimited argument as for~\texttt{r}, + but it has a user-definable recovery \meta{default} instead of + |nil|. + \item[v] Reads an argument \enquote{verbatim}, between the following + character and its next occurrence. + \item[(b)] Not implemented! Only suitable in the argument specification of an + environment, it denotes the body of the environment, between + |\begin|\marg{environment} and |\end|\marg{environment}. +\end{itemize} +The types which define optional arguments are: +\begin{itemize} + \item[o] A standard \LaTeX{} optional argument, surrounded with square + brackets, which will supply + |nil| if not given (as described later). + \item[d] Given as \texttt{d}\meta{token1}\meta{token2}, an optional + argument which is delimited by \meta{token1} and \meta{token2}. + As with \texttt{o}, if no + value is given |nil| is returned. + \item[O] Given as \texttt{O}\marg{default}, is like \texttt{o}, but + returns \meta{default} if no value is given. + \item[D] Given as \texttt{D}\meta{token1}\meta{token2}\marg{default}, + it is as for \texttt{d}, but returns \meta{default} if no value is given. + Internally, the \texttt{o}, \texttt{d} and \texttt{O} types are + short-cuts to an appropriated-constructed \texttt{D} type argument. + \item[s] An optional star, which will result in a value + |true| if a star is present and |false| + otherwise (as described later). + \item[t] An optional \meta{token}, which will result in a value + |true| if \meta{token} is present and |false| + otherwise. Given as \texttt{t}\meta{token}. + \item[(e)] Not implemented! Given as \texttt{e}\marg{tokens}, a set of optional + \emph{embellishments}, each of which requires a \emph{value}. + If an embellishment is not present, |-NoValue-| is returned. Each + embellishment gives one argument, ordered as for the list of + \meta{tokens} in the argument specification. All \meta{tokens} + must be distinct. \emph{This is an experimental type}. + \item[(E)] Not implemented! As for \texttt{e} but returns one or more \meta{defaults} + if values are not given: \texttt{E}\marg{tokens}\marg{defaults}. +\end{itemize} + +\clearpage + +\section{Implementation} + +%% +% +%% + +\subsection{lparse.lua} + +\inputminted[linenos=true]{lua}{lparse.lua} + +%% +% +%% + +\clearpage + +\subsection{lparse.tex} + +\inputminted[linenos=true]{latex}{lparse.tex} + +\clearpage + +\subsection{lparse.sty} + +\inputminted[linenos=true]{latex}{lparse.sty} + +\pagebreak +\PrintIndex +\end{document} diff --git a/macros/luatex/generic/lparse/lparse.lua b/macros/luatex/generic/lparse/lparse.lua new file mode 100644 index 0000000000..755cf2883f --- /dev/null +++ b/macros/luatex/generic/lparse/lparse.lua @@ -0,0 +1,302 @@ +-- lparse.lua +-- Copyright 2023 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 lparse.lua, lparse.tex, +-- and lparse.sty. +--- +if lpeg == nil then + lpeg = require('lpeg') +end + +--- +---@param spec string +---@return Argument[] +local function parse_spec(spec) + local V = lpeg.V + local P = lpeg.P + local Set = lpeg.S + local Range = lpeg.R + local CaptureFolding = lpeg.Cf + local CaptureTable = lpeg.Ct + local Cc = lpeg.Cc + local CaptureSimple = lpeg.C + + local function add_result(result, value) + if not result then + result = {} + end + table.insert(result, value) + return result + end + + local function collect_delims(a, b) + return { init_delim = a, end_delim = b } + end + + local function collect_token(a) + return { token = a } + end + + local function set_default(a) + return { default = a } + end + + local function combine(...) + local args = { ... } + + local output = {} + + for _, arg in ipairs(args) do + if type(arg) ~= 'table' then + arg = {} + end + + for key, value in pairs(arg) do + output[key] = value + end + + end + + return output + end + + local function ArgumentType(letter) + local function get_type(l) + return { argument_type = l } + end + return CaptureSimple(P(letter)) / get_type + end + + local T = ArgumentType + + local pattern = P({ + 'init', + init = V('whitespace') ^ 0 * + CaptureFolding(CaptureTable('') * V('list'), add_result), + + list = (V('arg') * V('whitespace') ^ 1) ^ 0 * V('arg') ^ -1, + + arg = V('m') + V('r') + V('R') + V('v') + V('o') + V('d') + V('O') + + V('D') + V('s') + V('t'), + + m = T('m') / combine, + + r = T('r') * V('delimiters') / combine, + + R = T('R') * V('delimiters') * V('default') / combine, + + v = T('v') * Cc({ verbatim = true }) / combine, + + o = T('o') * Cc({ optional = true }) / combine, + + d = T('d') * V('delimiters') * Cc({ optional = true }) / combine, + + O = T('O') * V('default') * Cc({ optional = true }) / combine, + + D = T('D') * V('delimiters') * V('default') * + Cc({ optional = true }) / combine, + + s = T('s') * Cc({ star = true }) / combine, + + t = T('t') * V('token') / combine, + + token = V('delimiter') / collect_token, + + delimiter = CaptureSimple(Range('!~')), + + delimiters = V('delimiter') * V('delimiter') / collect_delims, + + whitespace = Set(' \t\n\r'), + + default = P('{') * CaptureSimple((1 - P('}')) ^ 0) * P('}') / + set_default, + }) + + return pattern:match(spec) + +end + +--- +---Scan for an optional argument. +--- +---@param init_delim? string # The character that marks the beginning of an optional argument (by default `[`). +---@param end_delim? string # The character that marks the end of an optional argument (by default `]`). +--- +---@return string|nil # The string that was enclosed by the delimiters. The delimiters themselves are not returned. +local function scan_delimited(init_delim, end_delim) + if init_delim == nil then + init_delim = '[' + end + if end_delim == nil then + end_delim = ']' + end + + --- + ---@param t Token + --- + ---@return string + local function convert_token_to_string(t) + if t.index ~= nil then + return utf8.char(t.index) + else + return '\\' .. t.csname + end + end + + local delimiter_stack = 0 + + local function get_next_char() + local t = token.get_next() + local char = convert_token_to_string(t) + if char == init_delim then + delimiter_stack = delimiter_stack + 1 + end + + if char == end_delim then + delimiter_stack = delimiter_stack - 1 + end + return char, t + end + + local char, t = get_next_char() + + if t.cmdname == 'spacer' then + char, t = get_next_char() + end + + if char == init_delim then + local output = {} + + char, t = get_next_char() + + -- “while” better than “repeat ... until”: The end_delimiter is + -- included in the result output. + while not (char == end_delim and delimiter_stack == 0) do + table.insert(output, char) + char, t = get_next_char() + end + return table.concat(output, '') + else + token.put_next(t) + end +end + +---@class Argument +---@field argument_type? string +---@field optional? boolean +---@field init_delim? string +---@field end_delim? string +---@field dest? string +---@field star? boolean +---@field default? string +---@field verbatim? boolean +---@field token? string + +---@class Parser +---@field args Argument[] +---@field result any[] +local Parser = {} +---@private +Parser.__index = Parser + +function Parser:new(spec) + local parser = {} + setmetatable(parser, Parser) + parser.spec = spec + parser.args = parse_spec(spec) + parser.result = parser:parse(parser.args) + return parser +end + +---@return any[] +function Parser:parse() + local result = {} + local index = 1 + for _, arg in pairs(self.args) do + if arg.star then + -- s + result[index] = token.scan_keyword('*') + elseif arg.token then + -- t + result[index] = token.scan_keyword(arg.token) + elseif arg.optional then + -- o d O D + local oarg = scan_delimited(arg.init_delim, arg.end_delim) + if arg.default and oarg == nil then + oarg = arg.default + end + result[index] = oarg + elseif arg.init_delim and arg.end_delim then + -- r R + local oarg = scan_delimited(arg.init_delim, arg.end_delim) + if arg.default and oarg == nil then + oarg = arg.default + end + if oarg == nil then + tex.error('Missing required argument') + end + result[index] = oarg + else + -- m v + local marg = token.scan_argument(arg.verbatim ~= true) + if marg == nil then + tex.error('Missing required argument') + end + result[index] = marg + end + index = index + 1 + end + return result +end + +---@private +function Parser:set_result(...) + self.result = { ... } +end + +function Parser:assert(...) + local arguments = { ... } + for index, arg in ipairs(arguments) do + assert(self.result[index] == arg, string.format( + 'Argument at index %d doesn’t match: “%s” != “%s”', + index, self.result[index], arg)) + end +end + +--- +---@return string|boolean|nil ... +function Parser:export() + -- #self.arg: to get all elements of the result table, also elements + -- with nil values. + return table.unpack(self.result, 1, #self.args) +end + +function Parser:debug() + for index = 1, #self.args do + print(index, self.result[index]) + end +end + +---@return Parser +local function create_parser(spec) + return Parser:new(spec) +end + +local function scan(spec) + local parser = create_parser(spec) + return parser:export() +end + +return { Parser = create_parser, scan = scan, parse_spec = parse_spec } diff --git a/macros/luatex/generic/lparse/lparse.pdf b/macros/luatex/generic/lparse/lparse.pdf new file mode 100644 index 0000000000..c1afc32823 Binary files /dev/null and b/macros/luatex/generic/lparse/lparse.pdf differ diff --git a/macros/luatex/generic/lparse/lparse.sty b/macros/luatex/generic/lparse/lparse.sty new file mode 100644 index 0000000000..9d4fb2412f --- /dev/null +++ b/macros/luatex/generic/lparse/lparse.sty @@ -0,0 +1,22 @@ +%% lparse.sty +%% Copyright 2023 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 lparse.lua, lparse.tex, +% and lparse.sty. + +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{lparse}[2023/01/29 v0.1.0 Parse and scan macro arguments in Lua on LuaTeX using a xparse like argument specification] + +\input lparse.tex diff --git a/macros/luatex/generic/lparse/lparse.tex b/macros/luatex/generic/lparse/lparse.tex new file mode 100644 index 0000000000..7393d581e5 --- /dev/null +++ b/macros/luatex/generic/lparse/lparse.tex @@ -0,0 +1,22 @@ +%% lparse.tex +%% Copyright 2023 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 lparse.lua, lparse.tex, +% and lparse.sty. + +\directlua +{ + lparse = require('lparse') +} -- cgit v1.2.3