Erweiterbare, nur in TeX verfügbare Möglichkeit, ausgeglichene Tokenlisten zu vergleichen

Erweiterbare, nur in TeX verfügbare Möglichkeit, ausgeglichene Tokenlisten zu vergleichen

Gibt es ein (einigermaßen) effizientes Makro, das etwas Ähnliches tut wie \long\def\comparets#1#2{\def\aa{#1}\def\bb{#2}\ifx\aa\bb true\else false\fi} „except is expandable“ (d. h. es \newcomparets{<tokens1>}{<tokens2>}würde sich entweder in „true“ oder „false“ erweitern, einschließlich innerhalb von \edef)? Ich suche nach einer „reinen“ TeX-Lösung (d. h. ohne Erweiterungen wie e-TeX). Ich habe mir l3tlMakros angesehen, aber sie scheinen e-TeX zu verwenden. Die Lösung sollte mit beliebigen Token-Sequenzen funktionieren (einschließlich solcher, die verschiedene Arten von „lustigen Leerzeichen“ und Klammern und beliebige Steuersequenzen enthalten). Ich kann anscheinend keinen Weg finden, dies zu tun, ohne mehrere Durchläufe durchzuführen.

Antwort1

Ich bin nicht sicher, ob es in Ordnung ist, dies als Antwort auf meine eigene Frage zu posten, da es diese nicht wirklich beantwortet, aber ich werde es nicht als solche kennzeichnen (selbst wenn ich annehmen würde, dass ich das könnte), also werde ich es gerne als wahre Antwort kennzeichnen, wenn irgendjemand einen Geistesblitz hat, der das ursprüngliche Problem löst.

Nun zu den Makros. Ich entschuldige mich im Voraus für die Form, in der sie sich befinden. Sie wurden aus einer Vielzahl von Codes gezogen (und erneut umbenannt), die ich im Laufe der Jahre geschrieben habe, daher ist der Stil ein bisschen ... eklektisch, sagen wir mal. Vieles kann unten optimiert werden, aber das Problem der mehreren Durchläufe bleibt bestehen, wie ich später erklären werde. Wenn also jemand einen cleveren Trick hat, um es zu lösen, lassen Sie es mich bitte wissen.

Es gibt ein paar Einschränkungen:

1) Die eigentlichen Vergleichsmakros fehlen, es ist lediglich der Analyseteil vorhanden, der auf „präfixerweiterbare“ Weise (funktioniert z. B. mit dem \romannumeral-1Trick) eine Zeichenfolge mit Token der Kategorien 11 und 12 liefert, die genügend Informationen enthält, um jedes Token in der Sequenz hinsichtlich seiner Kategorie, seines Zeichencodes (sofern vorhanden), ob es sich um eine Klammer handelt, seines Zeichencodes usw. zu identifizieren. Solche Zeichenfolgen können bei Bedarf direkt verglichen werden.

2) Nun, 1) ist aus zwei Gründen eine Notlüge:

a) jedes Token, das als Parameter erfasst werden kann (also ein Nicht-Leerzeichen oder eine Nicht-Klammer), wird (erfasst und) einfach durch seine \meaning Zeichenfolge ersetzt, die in t ... e eingeschlossen ist (sowohl t als auch e sind Kategorie 11); beachten Sie, dass Token der Kategorie 10 mit einem anderen Zeichencode als 32 in diese Kategorie fallen (Wortspiel beabsichtigt). \yygrabtokenrawkann angepasst werden, um eine bessere Analyse zu ermöglichen (ein Muss, wenn das Ziel darin besteht, beliebige ausgeglichene Tokenlisten zu vergleichen, läuft aber auf ein paar sorgfältig geschriebene Bedingungen hinaus). Beachten Sie, dass einfach \stringauch nicht ausreicht, da \escapechar-1 sein kann.

b) der Rekursionsschritt der „obersten Ebene“ fehlt; das Hauptproblem hier sind Klammern mit dem Zeichencode 32; sie werden im allerletzten Schritt behandelt, wenn die Länge der Sequenz bekannt ist und man einfach \stringjede einzelne davon aufheben oder ihren herausfinden kann \meaning. Nun, nicht so schnell, denn wenn sie den Kategoriecode 32 haben, werden sowohl als auch \meaningin \stringnormale Leerzeichen umgewandelt (die \meaningendet mit zwei Leerzeichen, was auch nicht hilft), was ein Problem ist, \detokenize das erfunden wurde, um es zu beheben. Daher müssen wir entscheiden, wie wir sie erfassen. Die einzige Garantie, die der Code gibt, ist, dass jede öffnende Klammer korrekt als entweder Zeichencode 32 ( o1eoder c1e) oder Zeichencode ungleich 32 ( o2e, c2e) identifiziert wird. Der Code, der dies tut, bringt einige der folgenden schließenden Klammern (ihre Zeichencodes) durcheinander, um die Klammer sicher zu verwenden, sodass c2e„Markierungen“, die der ersten folgen, unzuverlässig sind (wenn jedoch ein weiteres o1e, o1eoder o2e gefunden wird, ist es eine Klammer mit Zeichencode 32). Die nächste Iteration kann die „entschlüsselten“ Klammern erfassen, ohne die nächste Klammer durcheinander zu bringen. Nach vielen weiteren Durchläufen (bis zu so vielen, wie es schließende Klammern gibt, leider) kann alles aufgelöst werden. Falls jemand Interesse hat, kann ich die Makros dazu fertigstellen. Nur wenn Knuth jeweils \meaningmit einem Punkt abschließt ...

3) Der Code verbringt viel Zeit mit der „Verbreitung der Erweiterung“. Eine typische Situation ist \somemacro{<long list of benign tokens>}{\string}; \stringhier muss erweitert werden, bevor irgendetwas anderes passieren kann, also \somemacroverbringt er viel Zeit mit dem Einfügen \expandaftervon s in das <long list ...>. Beachten Sie, dass das \romannumeralfehlschlägt, wenn das <long list ...>sehr lang ist, also hilft es nicht, alles als Ziffern zu codieren. Die Verwendung \csname <long ...>\endcsnameist möglich (mit einer \expandafterNachverfolgung), aber ich habe in diesem Fall Bedenken, die Hash-Tabelle von TeX zu verunreinigen.

Die Makros versuchen im ersten Durchgang „komische Leerzeichen“ zu identifizieren. Dies ist die einzige Verwendung für \meaningund \yymatchblankspace weiter unten. Man kann nur mit auskommen \string.

Am Ende ist ein Testfall für das Makro enthalten. Wenn ich etwas Dummes übersehen habe, bitte ich um Entschuldigung (wenn Joseph Wright und andere misstrauisch sind, bin ich es normalerweise auch).

BEARBEITEN:Darüber hinaus habe ich \longder Übersichtlichkeit halber vor jeder Definition das „“ weggelassen, was \paralles ruinieren würde.

Um weiter darauf einzugehenbieten bessere Analysenoben: Um pathologische Fälle (wie \escapechar=-1 \let\#=#) zu lösen, kann man entweder eine Reihe von Makros (eins (oder sogar zwei) pro Zeichen, wie \expandafter\def\csname match#\endcsname #1\##{...}% last '#' is \catcode 13) oder ein paar Makros mit einem \defed vorbereiten, das \def\maintest #1<a list of all active characters and single letter cs's>{...}die ganze schwere Arbeit erledigt (indem das „gegriffene“ Token rekursiv in das potenzielle „Trennzeichen“ eingefügt wird). Zwischenoptionen (Zeit gegen Platz tauschen) sind ebenfalls möglich. Was „das sind viele Makros“ betrifft, ist das natürlich ein Problem. Meine (unvollkommene) Meinung dazu ist: „Wenn man sich so viele \catcodeRegister leisten kann, kann man sich auch diese speziellen „Bedingungen“ leisten).

Ich habe Angst,AusbreitungsausbreitungDas oben erwähnte Problem ist einfach der Preis für die Rekursion in TeX. Dieses Problem kann etwas gemildert werden, indem (während des ersten Durchgangs) die Token mit \yysx ?where kodiert werden \def\yysx#1#2{\expandafter\space\expandafter\yysx\expandafter#1\romannumeral-1#2}. Auf diese Weise wird ein \romannumeral-1vor einer Liste von \yysx ?Einträgen die Erweiterung an das Ende der Liste „weitergeben“, während sie intakt bleibt.

Die 'Klammern-Nachbearbeitung' fühlt sich an wiesollenvermeidbar sein.

Abschließend wurde ich oft gefragt: „Warum kein e-TeX?“. Ich bin nicht sicher, ob dies der richtige Ort ist, um darüber zu diskutieren, aber ich habe (wahrscheinlich subjektive) Gründe, es zu vermeiden. Wenn jemand einen besseren Ort vorschlagen kann, um solche Präferenzen zu diskutieren, wäre ich dankbar.

% helper macros (to build test cases, etc); @ is a letter

\def\yyreplacestring#1\in#2\with#3{%
      \expandafter\def\expandafter\r@placestring\expandafter##\expandafter1\the#1##2\end{%
          \def\r@placestring{##2}% is this the string at the very end?
          \ifx\r@placestring\empty % then it is the one we inserted, report
              \errmessage{string <\the#1> not present in \the#2}% do not change the register if the string is not there
          \else % remove the extra copy of #1\end at the end
              \expandafter#2\expandafter\expandafter\expandafter
                  {\expandafter\r@plac@string\expandafter{\the#3}{##1}##2\end}%
      \fi}% end of \r@placestring definition
      \expandafter\def\expandafter\r@plac@string
          \expandafter##\expandafter1%
          \expandafter##\expandafter2%
          \expandafter##\expandafter3%
          \the#1\end{##2##1##3}%
      \expandafter\expandafter\expandafter\r@placestring\expandafter\the\expandafter#2\the#1\end
}

\newtoks\toksa
\newtoks\toksb
\newtoks\toksc
\newtoks\toksd

\def\yybreak#1#2\yycontinue{\fi#1}

\def\eatone#1{}
\def\eatonespace#1 {}
\def\identity#1{#1}
\def\yyfirstoftwo#1#2{#1}
\def\yysecondoftwo#1#2{#2}
\def\yysecondofthree#1#2#3{#2}
\def\yythirdofthree#1#2#3{#3}

% #1 -- `call stack'
% #2 -- remaining sequence
% #3 -- `parsed' sequence

\def\yypreparsetokensequenc@#1#2#3{%
    \yystringempty{#2}{#1{#3}}{\yypreparsetokensequen@@{#1}{#2}{#3}}%
}

\def\yypreparsetokensequen@@#1#2#3{% remaining sequence is nonempty
    \yystartsinbrace{#2}{\yydealwithbracedgroup{#1}{#2}{#3}}{\yypreparsetokensequ@n@@{#1}{#2}{#3}}%
}

\def\yydealwithbracedgroup#1#2#3{% the first token of the remaining sequence is a brace
    \iffalse{\fi\yydealwithbracedgro@p#2}{#1}{#3}%
}

\def\yydealwithbracedgro@p#1{%
    \yypreparsetokensequenc@{\yyrepackagesequence}{#1}{}%
}

% #1 -- parsed sequence
% this is a sequence to `propagate expansion' into the next parameter.
% the same can be achieved by packaging the whole sequence with a 
% \csname ... \endcsname pair and using a simple \expandafter
% maybe that would be a better idea ...

\def\yyrepackagesequence#1{%
    \yyrepackagesequenc@{}#1\end
}

% #1 -- `packaged' sequence (\expandafter\expandafter\expandafter ? ...)
% #2 -- the next category 12 character or \end

\def\yyrepackagesequenc@#1#2{%
    \ifx#2\end
        \yybreak{\yyrepackagesequ@nc@{#1\expandafter\expandafter\expandafter}}%
    \else
        \yybreak{\yyrepackagesequenc@{#1\expandafter\expandafter\expandafter#2}}%
    \yycontinue
}

% #1 -- `packaged' sequence (\expandafter\expandafter\expandafter ? ...)
% this macro is followed by the remainder of the original sequence with a so far
% unmatched right brace, the `call stack' and the parsed sequence.

\def\yyrepackagesequ@nc@#1{%
    \expandafter\expandafter\expandafter\yyrepackagesequ@nc@swap#1{\expandafter\eatone\string}%
}

% #1 -- parsed sequence without packaging

\def\yyrepackagesequ@nc@swap#1#{%
    \yyrepackagesequ@nc@sw@p{#1}%
}

% #1 -- parsed `inner' sequence
% #2 -- remainder of the original sequence
% #3 -- `call stack'
% #4 -- parsed sequence so far

\def\yyrepackagesequ@nc@sw@p#1#2#3#4{%
    \yypreparsetokensequenc@{#3}{#2}{#4[#1]}%
}

% `braced group' thread ends here

% #1 -- `call stack'
% #2 -- remaining sequence
% #3 -- `parsed' sequence

\def\yypreparsetokensequ@n@@#1#2#3{% the remaining group in #2 is nonempty and does not start with a brace
    \yystartsinspace{#2}{\yyconsumetruespace{#1}{#2}{#3}}{\yypreparsetokenseq@@n@@{#1}{#2}{#3}}%
}

\def\yyconsumetruespace#1#2#3{%
    \expandafter\yyconsumetruespac@swap\expandafter{\eatonespace#2}{#1}{#3.}%
}

\def\yyconsumetruespac@swap#1#2#3{%
    \yypreparsetokensequenc@{#2}{#1}{#3}%
}

% `group starting with a true (character code 32, category code 10) space' thread ends here

% #1 -- `call stack'
% #2 -- remaining sequence
% #3 -- `parsed' sequence

\def\yypreparsetokenseq@@n@@#1#2#3{% a nonempty group, that does not start with a brace or a true space
    \yymatchblankspace{#2}{\yyrescanblankspace{#2}{#1}{#3}}{\yypreparsetokens@q@@n@@{#1}{#2}{#3}}%
}

% #1 -- remaining sequence
% #2 -- `call stack'
% #3 -- `parsed' sequence

\def\yyrescanblankspace#1#2#3{%
    \expandafter\expandafter\expandafter
        \yyrescanblankspac@swap
    \expandafter\expandafter\expandafter{\expandafter\yynormalizeblankspac@\meaning#1}{#2}{#3*}%
}

\def\yyrescanblankspac@swap#1#2#3{%
    \yystartsinspace{#1}{%
        \expandafter\yyrescanblankspac@sw@p\expandafter{\eatonespace#1}{#2}{#3}%
    }{%
        \expandafter\yyrescanblankspac@sw@p\expandafter{\eatone#1}{#2}{#3}%
    }%
}

\def\yyrescanblankspac@sw@p#1#2#3{%
    \yypreparsetokensequenc@{#2}{#1}{#3}%
}

% `group starting with a blank space' ends here

% #1 -- `call stack'
% #2 -- remaining sequence
% #3 -- `parsed' sequence

\def\yypreparsetokens@q@@n@@#1#2#3{% nonempty group starting with a non blank, non brace token
    \expandafter\yypreparsetokens@q@@n@@swap\expandafter{\eatone#2}{#1}{#30}%
}

\def\yypreparsetokens@q@@n@@swap#1#2#3{%
    \yypreparsetokensequenc@{#2}{#1}{#3}%
}

% #1 -- string of category code 12 or 10 characters
% #2 -- string of category code 12 or 10 characters

\def\yycomparesimplestrings#1#2{%
    \yystringempty{#1}{%
        \yystringempty{#2}{\yyfirstoftwo}{\yysecondoftwo}%
    }{\yycomparesimplestrings@{#1}{#2}}%
}

\def\yycomparesimplestrings@#1#2{% the first string is nonempty
    \yystringempty{#2}{\yysecondoftwo}{\yycomparesimplestrings@@{#1}{#2}}%
}

\def\yycomparesimplestrings@@#1#2{% both strings are nonempty
    \yystartsinspace{#1}{%
        \yystartsinspace{#2}{\yyabsorbfirstspace{#1}{#2}}{\yysecondoftwo}%
    }{%
        \yystartsinspace{#2}{\yysecondoftwo}{\yyabsorbfirstnonspace{#1}{#2}}%
    }    
}

\def\yyabsorbfirstspace#1#2{%
    \expandafter\yyabsorbfirstspac@swap\expandafter{\eatonespace#1}{#2}%
}

\def\yyabsorbfirstspac@swap#1#2{%
     \expandafter\yyabsorbfirst@swap\expandafter{\eatonespace#2}{#1}%
}

\def\yyabsorbfirstnonspace#1#2{%
    \expandafter\yyabsorbfirstnonspac@swap\expandafter{\eatone#1}{#2}%
}

\def\yyabsorbfirstnonspac@swap#1#2{%
     \expandafter\yyabsorbfirst@swap\expandafter{\eatone#2}{#1}%
}

\def\yyabsorbfirst@swap#1#2{%
     \yycomparesimplestrings{#2}{#1}%
}

% `compare strings of category code 12' thread ends here

% #1 -- remaining parsed sequence
% #2 -- analysed sequence

\def\yyanalysetokens@#1#2{%
    \yystringempty{#1}{{#2}}%
        {\yyanalysetok@ns@#1\end{#2}}%
}

\def\yyanalysetok@ns@#1#2\end{%
    \ifx#1.%
        \expandafter\yyfirstoftwo
    \else
        \expandafter\yysecondoftwo
    \fi
    {\yygrabablank{#2}}%
    {%
        \ifx#1[% not a space, an opening brace
            \expandafter\yyfirstoftwo
        \else
            \expandafter\yysecondoftwo
        \fi
        {%
            \yydisableobrace{#2}%
        }{% 
            \ifx#1]% not a space, a closing brace
                \expandafter\yyfirstoftwo
            \else
                \expandafter\yysecondoftwo
            \fi
            {%
                \yydisablecbrace{#2}%
            }{% neither space nor brace
                \yygrabtokenraw{#2}%
            }%
        }%
    }%
}

% #1 -- remaining parsed sequence
% #2 -- analysed sequence
% #3 -- next token

\def\yygrabtokenraw#1#2#3{%
    \expandafter\yyanalysetokens@swap\expandafter{\meaning#3}{#1}{#2}%
}

\def\yyanalysetokens@swap#1#2#3{%
    \yyanalysetokens@{#2}{#3t#1e}%
}

\def\yygrabablank#1#2 {%
    \yyanalysetokens@{#1}{#2s0e}%
}

% #1 -- remaining parsed sequence
% #2 -- analysed sequence

\def\yydisablecbrace#1#2{%
    \yydisablecbrac@{}#1\relax#2\end
}


\def\yydisablecbrac@#1#2{%
    \ifx#2\end
        \yybreak{\yydisablecbrac@@{#1\expandafter\expandafter\expandafter}}%
    \else
        \yybreak{\yydisablecbrac@{#1\expandafter\expandafter\expandafter#2}}%
    \yycontinue
}

\def\yydisablecbrac@@#1{%
    \expandafter\expandafter\expandafter
        \yydisablecbrace@@@#1\end
    \expandafter\expandafter\expandafter
        {\iffalse}\fi\string
}

\def\yydisablecbrace@@@#1\relax#2\end#3{%
    \yystartsinspace{#3}%
        {\expandafter\yyanalysetok@nsswap\expandafter{\eatonespace#3}{#1}{#2c1e}}%
        {\expandafter\yyanalysetok@nsswap\expandafter{\eatone#3}{#1}{#2c2e}}%
}

\def\yyanalysetok@nsswap#1#2#3{%
    \iffalse{\fi\yyanalysetokens@{#2}{#3}#1}%
}

% #1 -- remaining parsed sequence
% #2 -- analysed sequence

\def\yydisableobrace#1#2{%
    \yydisableobrac@{}#1\relax#2\end
}


\def\yydisableobrac@#1#2{%
    \ifx#2\end
        \yybreak{\yydisableobrac@@{#1\expandafter\expandafter\expandafter}}%
    \else
        \yybreak{\yydisableobrac@{#1\expandafter\expandafter\expandafter#2}}%
    \yycontinue
}

\def\yydisableobrac@@#1{%
    \expandafter\expandafter\expandafter
        \yydisableobrace@@@#1\end
    \expandafter\expandafter\expandafter
        {\iffalse}\fi\string
}

\def\yydisableobrace@@@#1\relax#2\end#3{%
    \yystartsinspace{#3}%
        {\expandafter\yyanalysetok@nsswap\expandafter{\eatonespace#3}{#1}{#2o1e}}%
        {\expandafter\yyanalysetok@nsswap\expandafter{\eatone#3}{#1}{#2o2e}}%
}

\uccode`\ =`\-

% \dotspace expands into a character code `\-, category code 10 token (funny space)

\uppercase{\def\dotspace{ }}

\toksa\expandafter\expandafter\expandafter{\expandafter\meaning\dotspace}

\toksb{-}

\toksc{#2}

\toksd\toksa

\yyreplacestring\toksb\in\toksa\with\toksc

\toksc{}
\yyreplacestring\toksb\in\toksd\with\toksc

\expandafter\def\expandafter\yymatchblankspac@\expandafter#\expandafter1\the\toksd{%
    \yystringempty{#1}{\expandafter\yysecondofthree\expandafter{\string}}%
        {\expandafter\yythirdofthree\expandafter{\string}}%
}

\edef\yymatchblankspace#1{% is it \catcode 10 token?
    \noexpand\iffalse{\noexpand\fi
    \noexpand\expandafter
    \noexpand\yymatchblankspac@
    \noexpand\meaning#1\the\toksd}%
}

% the idea behind the sequence below is that a leading character of category code 10
% is replaced either by a character of category code 10 and charachter code 32 or a character
% of category code 12 and character code other than 32
% note that while it is tempting to replace the definition below by something that ends in
% ... blank space #2{ ... with the hope of absorbing the result of \meaning in one step,
% this will not give the desired result in case of an active character,
% say, `~' that had been \let to the normal blank space

\expandafter\def\expandafter\yynormalizeblankspac@\expandafter#\expandafter1\the\toksd{}

\def\yystartsinspace#1{% is it \charcode 32, \catcode 10 token?
    \iffalse{\fi\yystartsinspac@#1 }%
}

\def\yystartsinspac@#1 {%
    \yystringempty{#1}{\expandafter\yysecondofthree\expandafter{\string}}{\expandafter\yythirdofthree\expandafter{\string}}%
}

\def\yystartsinbrace#1{%
  \iffalse{{\fi
  \if!\yytoks@mpty#1}}!%
    \expandafter\yysecondoftwo
  \else
    \expandafter\yyfirstoftwo
  \fi
}

\def\yystringempty#1{%
  \iffalse{{{\fi
  \ifcase\yytoks@mpty#1}}\@ne}\z@
    \expandafter\yyfirstoftwo
  \else
    \expandafter\yysecondoftwo
  \fi
}

\def\yytoks@mpty{%
    \expandafter\eatone\expandafter{\expandafter{%
        \ifcase\expandafter1\expandafter}\expandafter}\expandafter\fi\string
}

%% test code begins here

%\tracingmacros=3
%\tracingonline=3

\catcode`\ =13\relax%
\def\actspace{ }%
\catcode`\ =10\relax%

\catcode`\.=13\relax%
\def\actdotspace{.}%
\catcode`\.=12\relax%

\edef\makefunkydotspace{\let\expandafter\noexpand\actdotspace= \dotspace}
\edef\makefunkyspace{\let\expandafter\noexpand\actspace= \space}

\makefunkyspace
\makefunkydotspace

\catcode`\<=1
\catcode`\>=2
\uccode`\<=32
\uccode`\>=32

% inside the following sequence, < and > will become braces with character code 32 (space),
% \actspace will expand into an active character with character code 32, that has been \let to a
% character code 32, category code 10 token (space)

\uppercase{\edef\temptest{{ } \space\space\dotspace\expandafter\noexpand\actspace\expandafter\noexpand\actdotspace{<> {{}{{ u o l k kk
    \end\noexpand\fi\noexpand\else\noexpand\iffalse{}} }}}}}

%\uppercase{\edef\temptest{\dotspace E <>}}

\show\temptest

\def\displaypreparse#1{%
    \expandafter\errmessage\expandafter{\romannumeral-1\yypreparsetokensequenc@{\yyanalysetokens@}{#1}{}{}#1}%
}

\expandafter\displaypreparse\expandafter{\temptest}

\end

verwandte Informationen