Расширяемый, стандартный TeX-способ сравнения сбалансированных списков токенов

Расширяемый, стандартный TeX-способ сравнения сбалансированных списков токенов

Есть ли (достаточно) эффективный макрос, который делает что-то похожее на \long\def\comparets#1#2{\def\aa{#1}\def\bb{#2}\ifx\aa\bb true\else false\fi} except is expandable (т. е. \newcomparets{<tokens1>}{<tokens2>}будет расширяться либо в 'true', либо в 'false', включая внутри \edef)? Я ищу решение 'чистого' TeX (т. е. без расширений, таких как e-TeX). Я посмотрел на l3tlмакросы, но они, похоже, используют e-TeX. Решение должно работать с произвольными последовательностями токенов (включая те, которые содержат различные разновидности 'смешных пробелов' и фигурных скобок и произвольные управляющие последовательности). Я не могу найти способ сделать это без выполнения нескольких проходов.

решение1

Я не уверен, что публикация этого сообщения в качестве ответа на мой собственный вопрос будет кошерной, поскольку на самом деле это не ответ на него, но я не буду отмечать это как таковое (даже если бы я мог это сделать), поэтому, если у кого-то возникнет вспышка вдохновения, которая решит исходную проблему, я с радостью отмечу это как истинный ответ.

Теперь о макросах. Заранее извиняюсь за то, в каком они виде. Они были вытащены (и переименованы) из множества кодов, которые я писал на протяжении многих лет, так что стиль немного... эклектичный, скажем так. Многое можно оптимизировать ниже, но проблема множественных проходов остается, как я объясню позже, так что если у кого-то есть хитрый трюк для ее решения, пожалуйста, дайте мне знать.

Есть несколько оговорок:

1) Фактические макросы сравнения отсутствуют, присутствует только аналитическая часть, которая выдает в «префиксно-расширяемом» виде (например, работает с трюком \romannumeral-1) строку токенов категорий 11 и 12, содержащую достаточно информации для идентификации каждого токена в последовательности с точки зрения его категории, кода символа, если таковой имеется, является ли он фигурной скобкой, его кода символа и т. д. При желании такие строки можно сравнивать напрямую.

2) Ну, 1) — это ложь во спасение по двум причинам:

a) любой токен, который может быть захвачен как параметр (т. е. не пробел, не фигурная скобка), (захватывается и) просто заменяется его \meaning строкой, заключенной в t ... e (и t, и e относятся к категории 11); обратите внимание, что токены категории 10 с кодом символа, отличным от 32, попадают в эту категорию (каламбур преднамеренный). \yygrabtokenrawможет быть скорректирован для обеспечения лучшего анализа (обязательно, если цель состоит в том, чтобы сравнить произвольные сбалансированные списки токенов, но просто сводится к нескольким тщательно написанным условным операторам). Обратите внимание, что просто \stringнедостаточно, так как \escapecharможет быть -1.

б) отсутствует шаг рекурсии «верхнего уровня»; главная проблема здесь — фигурные скобки с кодом символа 32; они решаются на самом последнем этапе, когда известна длина последовательности, и можно просто \stringкаждую из них взять или узнать их \meaning. Ну, не так быстро, потому что если у них есть код категории 32, оба \meaningи \stringпревратят их в обычные пробелы ( \meaningбудут заканчиваться двумя пробелами, что тоже не помогает), что является одной из проблем, которая \detokenize была придумана для исправления. Таким образом, нам нужно решить, как их захватить. Единственная гарантия, которую дает код, заключается в том, что каждая открывающая фигурная скобка будет правильно идентифицирована либо как код символа 32 ( o1eили c1e), либо как код символа, отличный от 32 ( o2e, c2e). Код, который делает это, портит некоторые из закрывающих фигурных скобок, которые следуют (их коды символов), чтобы безопасно использовать фигурную скобку, поэтому c2e«маркеры», следующие за первой, ненадежны (однако, если найден еще один o1e, o1eили , это будет фигурная скобка с кодом символа 32). o2eСледующая итерация может захватить скобки, которые «расшифровываются», не портя следующую скобку. После еще многих проходов (к сожалению, столько же, сколько закрывающих скобок), все может быть решено. Если кому-то интересно, я могу дописать макросы, чтобы сделать это. Только если Кнут заканчивал каждый \meaningточкой ...

3) Код тратит много времени на «распространение расширения». Типичная ситуация: \somemacro{<long list of benign tokens>}{\string}; \stringздесь необходимо расширить, прежде чем что-либо еще сможет произойти, поэтому \somemacroтратит много времени на вставку \expandafters в <long list ...>. Обратите внимание, что \romannumeralдаст сбой, если , <long list ...>очень длинный, поэтому кодирование всего в виде цифр не поможет. Использование \csname <long ...>\endcsnameвозможно (с \expandafterпоследующими действиями), но я беспокоюсь о загрязнении хэш-таблицы TeX в этом случае.

Макросы пытаются определить «смешные пробелы» в первом проходе, это единственное применение для \meaningи \yymatchblankspace ниже. Можно обойтись \stringтолько.

Тестовый случай для макроса включен в конце. Если я пропустил что-то глупое, мои извинения (когда Джозеф Райт и другие подозрительны, я тоже склонен быть подозрительным).

РЕДАКТИРОВАТЬ:Вдобавок ко всему, что может быть не так, я \longдля ясности опустил каждое определение, поэтому все \parиспортит.

Чтобы расширитьобеспечить лучший анализвыше: для разрешения патологических случаев (таких как \escapechar=-1 \let\#=#) можно подготовить либо кучу макросов (один (или даже два) на символ, например \expandafter\def\csname match#\endcsname #1\##{...}% last '#' is \catcode 13), либо несколько макросов с одним \defed, который будет \def\maintest #1<a list of all active characters and single letter cs's>{...}выполнять всю тяжелую работу (рекурсивно вставляя «захваченный» токен в потенциальный «разделитель»). Между ними также возможны варианты (обмен времени на пространство). Что касается «это много макросов», то это, конечно, вызывает беспокойство. Мое (несовершенное) мнение по этому поводу таково: «если кто-то может позволить себе столько \catcodeрегистров, он может позволить себе и эти специальные «условные операторы»).

Я боюсь, чтораспространение расширенияПроблема, упомянутая выше, — это просто цена выполнения рекурсии в TeX. Эту проблему можно несколько смягчить, кодируя (во время первого прохода) токены с помощью \yysx ?where \def\yysx#1#2{\expandafter\space\expandafter\yysx\expandafter#1\romannumeral-1#2}. Таким образом, a \romannumeral-1перед списком \yysx ?записей «передаст» расширение в конец списка, оставаясь нетронутым.

«Послеоперационная обработка брекетов» ощущается такдолженбыть избежаемым.

Наконец, меня много раз спрашивали: «Почему нет e-TeX?». Я не уверен, что это подходящее место для обсуждения, но у меня есть (вероятно, субъективные) причины избегать этого. Если кто-то может предложить лучшее место для обсуждения таких предпочтений, я буду признателен.

% 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

Связанный контент