Я наткнулся на ss64.com, который хорошо помогает в написании пакетных сценариев, которые будет запускать интерпретатор команд Windows.
Однако мне не удалось найти хорошего объяснения грамматики пакетных сценариев, того, как вещи расширяются или не расширяются, и как избежать этого.
Вот примеры вопросов, которые мне не удалось решить:
- Как работает система котировок? Я сделал сценарий TinyPerl
(foreach $i (@ARGV) { print '*' . $i ; }
), скомпилировал его и назвал так:my_script.exe "a ""b"" c"
→ выход*a "b*c
my_script.exe """a b c"""
→ вывести это*"a*b*c"
- Как работает внутренняя
echo
команда? Что раскрывается внутри этой команды? - Почему я должен использовать
for [...] %%I
скрипты в файлах, аfor [...] %I
в интерактивных сессиях? - Что такое escape-символы и в каком контексте? Как избежать знака процента? Например, как я могу
%PROCESSOR_ARCHITECTURE%
буквально повторить эхо ? Я обнаружил, что этоecho.exe %""PROCESSOR_ARCHITECTURE%
работает, есть ли лучшее решение? - Как
%
совпадают пары ? Пример:set b=a
,echo %a %b% c%
→%a a c%
set a =b
,echo %a %b% c%
→bb c%
- Как обеспечить передачу переменной в команду как одного аргумента, если эта переменная когда-либо содержит двойные кавычки?
- Как сохраняются переменные при использовании
set
команды? Например, если я это сделаю,set a=a" b
тоecho.%a%
получуa" b
. Однако, если я используюecho.exe
из UnxUtils, я получаюa b
. Каким образом%a%
расширяется по-другому?
Спасибо за свет.
Ответы:
Мы проводили эксперименты по изучению грамматики пакетных скриптов. Мы также исследовали различия между пакетным режимом и режимом командной строки.
Пакетный анализатор строк:
Вот краткий обзор фаз в парсере строк командного файла:
Фаза 0) Читать строку:
Фаза 1) Процентное расширение:
Этап 2) Обработка специальных символов, токенизация и построение кэшированного командного блока: это сложный процесс, на который влияют такие вещи, как кавычки, специальные символы, разделители токенов и переходы курсора.
Фаза 3) Эхо проанализированных команд (команд) Только в том случае, если командный блок не начинается с
@
, а ECHO был включен в начале предыдущего шага.Фаза 4)
%X
Расширение переменной FOR : только если активна команда FOR и выполняются команды после DO.Фаза 5) Отложенное расширение: только если включено отложенное расширение
Фаза 5.3) Обработка трубы: только если команды находятся по обе стороны трубы.
Фаза 5.5) Выполнить перенаправление:
Фаза 6) Обработка CALL / удвоение символа каретки: только если токен команды CALL
Фаза 7) Выполнить: команда выполняется.
Вот подробности для каждого этапа:
Обратите внимание, что этапы, описанные ниже, являются лишь моделью того, как работает пакетный анализатор. Фактическое внутреннее устройство cmd.exe может не отражать эти фазы. Но эта модель эффективна при прогнозировании поведения пакетных сценариев.
Фаза 0) Прочитать строку: сначала прочитать строку ввода
<LF>
.<Ctrl-Z>
(0x1A) читается как<LF>
(LineFeed 0x0A)<Ctrl-Z>
обрабатываются как сами по себе - они не преобразуются в<LF>
Фаза 1) Процентное расширение:
%%
заменяется одиночным%
%*
,%1
,%2
и т.д.)%var%
, если var не существует, заменить его ничем<LF>
вне%var%
раскрытияЭтап 2) Обработка специальных символов, токенизация и построение кэшированного командного блока: это сложный процесс, на который влияют такие вещи, как кавычки, специальные символы, разделители токенов и переходы курсора. Ниже приводится приблизительное описание этого процесса.
Есть концепции, которые важны на этом этапе.
<space>
<tab>
;
,
=
<0x0B>
<0x0C>
и<0xFF>
последовательные разделители токенов обрабатываются как один - между разделителями токенов нет пустых токенов.
Следующие символы могут иметь особое значение на этом этапе, в зависимости от контекста:
<CR>
^
(
@
&
|
<
>
<LF>
<space>
<tab>
;
,
=
<0x0B>
<0x0C>
<0xFF>
Посмотрите на каждого символа слева направо:
<CR>
затем удалить его, как будто его никогда не было (за исключением странного поведения перенаправления )^
), следующий символ экранируется, а экранирующая вставка удаляется. Экранированные символы теряют все специальные значения (кроме<LF>
)."
), переключить флаг кавычки. Если флажок котировки активен, то особенными являются только"
и<LF>
. Все остальные символы теряют свое особое значение до тех пор, пока следующая цитата не отключит флаг кавычки. Невозможно избежать заключительной цитаты. Все цитируемые символы всегда находятся в одном токене.<LF>
всегда выключает флаг цитаты. Другое поведение зависит от контекста, но цитаты никогда не меняют поведения<LF>
.<LF>
<LF>
раздевается<LF>
, он рассматривается как литерал, что означает, что этот процесс не рекурсивный.<LF>
без скобок<LF>
удаляется, и анализ текущей строки прекращается.<LF>
в блоке FOR IN в скобках<LF>
превращается в<space>
<LF>
в командном блоке в скобках<LF>
преобразуется в<LF><space>
, и<space>
рассматривается как часть следующей строки командного блока.&
|
<
или>
, разделите строку в этой точке, чтобы обрабатывать каналы, конкатенацию команд и перенаправление.|
) каждая сторона представляет собой отдельную команду (или командный блок), которая получает особую обработку на этапе 5.3.&
,&&
или||
команда конкатенации, каждая сторона конкатенации рассматриваются в качестве отдельной команды.<
,<<
,>
или>>
перенаправления, положение перенаправления анализируется, временно удален, а затем добавляется к концу текущей команды. Предложение перенаправления состоит из необязательной цифры дескриптора файла, оператора перенаправления и маркера назначения перенаправления.@
, то@
имеет особое значение. (@
не является особенным ни в каком другом контексте)@
снимается.@
стоит перед открытием(
, то весь заключенный в скобки блок исключается из эхо-сигнала фазы 3.(
этом нет ничего особенного.(
, то запускает новый составной оператор и увеличивает счетчик скобок)
завершает составной оператор и уменьшает счетчик скобок.)
функции аналогичныREM
оператору, если за ним сразу следует разделитель токенов, специальный символ, новая строка или конец файла.^
(возможна конкатенация строк)@
были удалены и перенаправление перемещено в конец).(
действует как разделитель токена команды в дополнение к стандартным разделителям токена<LF>
как<space>
. После того, как предложение IN проанализировано, все токены объединяются вместе, чтобы сформировать один токен.^
, заканчивающим строку, то токен аргумента отбрасывается, а следующая строка анализируется и добавляется к REM. Это повторяется до тех пор, пока не будет больше одного токена или не останется последнего символа^
.:
, и это первый раунд фазы 2 (не перезапуск из-за CALL в фазе 6), то)
,<
,>
,&
и|
больше не имеют особого смысла. Вся оставшаяся часть строки считается частью метки «команда».^
продолжает оставаться особенным, что означает, что продолжение строки может использоваться для добавления следующей строки к метке.(
больше не имеет особого значения для первой команды, следующей за ярлыком « Неисполненное» .|
труба или&
,&&
или||
команда конкатенация на линии.Фаза 3) Эхо проанализированных команд (команд) Только в том случае, если командный блок не начинается с
@
, а ECHO был включен в начале предыдущего шага.Фаза 4)
%X
Расширение переменной FOR : только если активна команда FOR и выполняются команды после DO.%%X
в%X
. Командная строка имеет другие правила процентного расширения для фазы 1. Это причина того, что командные строки используют,%X
а пакетные файлы используют%%X
переменные FOR.~modifiers
не чувствительны к регистру.~modifiers
имеют приоритет над именами переменных. Если следующий за ним символ~
является и модификатором, и допустимым именем переменной FOR, и существует следующий символ, который является активным именем переменной FOR, то этот символ интерпретируется как модификатор.---- С этого момента каждая команда, указанная на этапе 2, обрабатывается отдельно.
---- Фазы с 5 по 7 завершаются для одной команды перед переходом к следующей.
Фаза 5) Отложенное расширение: только если отложенное расширение включено, команда не находится в блоке в скобках по обе стороны канала , и команда не является «голым» пакетным сценарием (имя сценария без круглых скобок, CALL, объединение команд, или труба).
!
. В противном случае токен не анализируется - это важно для^
персонажей. Если токен действительно содержит!
, просканируйте каждый символ слева направо:^
), следующий символ не имеет особого значения, сама каретка удаляется!
сворачиваются в один!
!
удаляются<CR>
или<LF>
)Фаза 5.3) Обработка конвейера : только если команды находятся по обе стороны канала.
Каждая сторона канала обрабатывается независимо и асинхронно.
%comspec% /S /D /c" commandBlock"
, поэтому командный блок получает фазовый перезапуск, но на этот раз в режиме командной строки.<LF>
у кого есть команда до и после, преобразуются в<space>&
. Остальные<LF>
раздеваются.Фаза 5.5) Выполнить перенаправление: теперь выполняется любое перенаправление, обнаруженное на этапе 2.
||
оно не используется .Фаза 6) Обработка CALL / удвоение каретки: только если токен команды - CALL, или если текст перед первым встречающимся стандартным разделителем токенов - CALL. Если CALL анализируется из более крупного токена команды, то неиспользованная часть добавляется к токену аргументов перед продолжением.
/?
. Если он найден где-либо в пределах жетонов, прервите фазу 6 и перейдите к фазе 7, где будет напечатана СПРАВКА для ЗВОНОК.CALL
, чтобы можно было сложить несколько вызовов CALL&
или|
(
@
IF
илиFOR
не признается в качестве внутренней или внешней команды.:
.:
, тоФаза 7 не выполняется для сценариев CALLed или меток:.
Фаза 7) Выполнить: команда выполняется.
+
/
[
]
<space>
<tab>
,
;
или=
Если предыдущий текст является внутренней командой, запомните эту команду
.
\
или:
Если предыдущий текст не является внутренней командой, то перейдите к 7.2.
Иначе предыдущий текст может быть внутренней командой. Запомните эту команду.
+
/
[
]
<space>
<tab>
,
;
или.=
Если предыдущий текст - это путь к существующему файлу, то перейти к 7.2.
Иначе выполнить запомненную внутреннюю команду.
/?
обнаружены. Большинство узнает,/?
появляется ли это где-нибудь в аргументах. Но некоторые команды, такие как ECHO и SET, выводят справку только в том случае, если первый токен аргумента начинается с/?
.set "name=content" ignored
-> значение =,content
тогда текст между первым знаком равенства и последней кавычкой используется в качестве содержимого (первая равная и последняя кавычки исключены). Текст после последней цитаты игнорируется. Если после знака равенства нет кавычек, то остальная часть строки используется как содержимое.
set name="content" not ignored
-> value =,"content" not ignored
тогда вся оставшаяся часть строки после равенства используется в качестве содержимого, включая все кавычки, которые могут присутствовать.
::
всегда будет приводить к ошибке, если только SUBST не используется для определения тома.::
Если SUBST используется для определения тома
::
, то том будет изменен, он не будет рассматриваться как метка.,
,;
,=
или+
затем перерыв команды лексемы при первом появлении<space>
,
;
или=
и предварять остаток к аргументу маркеров (ов).Если том не может быть найден, прервите операцию с ошибкой.
:
, то перейти к 7.4.Обратите внимание, что если токен метки начинается с
::
, то это не будет достигнуто, потому что предыдущий шаг будет прерван с ошибкой, если SUBST не используется для определения тома для::
.:
, то перейти к 7.4.Обратите внимание, что это редко достигается, потому что предыдущий шаг будет прерван с ошибкой, если токен команды не начинается с
::
, и SUBST используется для определения тома для::
, а весь токен команды является допустимым путем к внешней команде.:
.Правила 7.2 и 7.3 могут помешать метке достичь этой точки.
Парсер командной строки:
Работает аналогично BatchLine-Parser, за исключением:
Фаза 1) Процентное расширение:
%*
и%1
т. Д. Расширение аргумента%var%
остается без изменений.%%
. Если var = content,%%var%%
заменяется на%content%
.Фаза 3) Эхо проанализированных команд
Фаза 5) Отложенное расширение: только если отложенное расширение включено
!var!
остается без изменений.Фаза 7) Выполнить команду
::
Разбор целочисленных значений
Существует много разных контекстов, в которых cmd.exe анализирует целочисленные значения из строк, а правила несовместимы:
SET /A
IF
%var:~n,m%
(расширение переменной подстроки)FOR /F "TOKENS=n"
FOR /F "SKIP=n"
FOR /L %%A in (n1 n2 n3)
EXIT [/B] n
Подробную информацию об этих правилах можно найти в разделе «Правила анализа чисел CMD.EXE».
Для всех, кто желает улучшить правила синтаксического анализа cmd.exe, на форуме DosTips есть тема для обсуждения, где можно сообщить о проблемах и внести предложения.
Надеюсь, это поможет
Ян Эрик (jeb) - оригинальный автор и первооткрыватель фаз
Дэйв Бенхэм (dbenham) - много дополнительного контента и редактирования
источник
)
Действительно функция почти какREM
команда , когда счетчик скобки 0. Попробуйте оба из них из командной строки:) Ignore this
иecho OK & ) Ignore this
При вызове команды из командного окна токенизация аргументов командной строки не выполняется
cmd.exe
(иначе «оболочкой»). Чаще всего токенизация выполняется средой выполнения C / C ++ вновь сформированных процессов, но это не обязательно так - например, если новый процесс был написан не на C / C ++, или если новый процесс предпочитает игнорироватьargv
и обрабатывать необработанная командная строка для себя (например, с GetCommandLine ()). На уровне ОС Windows передает новым процессам командные строки без инициализации в виде единой строки. Это контрастирует с большинством оболочек * nix, где оболочка токенизирует аргументы согласованным и предсказуемым способом перед их передачей во вновь сформированный процесс. Все это означает, что вы можете столкнуться с совершенно различающимся поведением токенизации аргументов в разных программах в Windows, поскольку отдельные программы часто берут токенизацию аргументов в свои руки.Если это звучит как анархия, так оно и есть. Однако, поскольку большое количество программ Windows действительно используют среду выполнения Microsoft C / C ++
argv
, в целом может быть полезно понять, как MSVCRT токенизует аргументы. Вот отрывок:Пакетный язык Microsoft (
.bat
) не является исключением из этой анархической среды, и он разработал свои собственные уникальные правила для токенизации и экранирования. Также похоже, что командная строка cmd.exe выполняет некоторую предварительную обработку аргумента командной строки (в основном для подстановки переменных и экранирования) перед передачей аргумента новому выполняющемуся процессу. Вы можете узнать больше о низкоуровневых деталях пакетного языка и экранирования cmd в отличных ответах jeb и dbenham на этой странице.Давайте создадим простую утилиту командной строки на C и посмотрим, что она говорит о ваших тестовых примерах:
int main(int argc, char* argv[]) { int i; for (i = 0; i < argc; i++) { printf("argv[%d][%s]\n", i, argv[i]); } return 0; }
(Примечания: argv [0] всегда является именем исполняемого файла и опускается ниже для краткости. Проверено в Windows XP SP3. Скомпилировано с Visual Studio 2005.)
И несколько моих собственных тестов:
источник
[a "b" c]
могло бы стать[a "b] [c]
постобработкой.GetCommandLine
. Возможно, TinyPerl игнорирует argv и просто размечает необработанную командную строку по своим собственным правилам.Правила процентного расширения
Вот расширенное объяснение этапа 1 в ответе jeb (действительно как для пакетного режима, так и для режима командной строки).
Этап 1) Процентное расширение. Начиная слева, просканируйте каждый символ на предмет
%
или<LF>
. Если найден, то<LF>
)<LF>
то<LF>
вперед<CR>
)%
, поэтому перейдите к 1.1.%
) пропускается, если режим командной строки%
тозаменить
%%
на одиночный%
и продолжить сканирование.*
них включены и расширения команд, тогдаЗамените
%*
текстом всех аргументов командной строки (Замените ничем, если аргументов нет) и продолжите сканирование.<digit>
затемЗаменить
%<digit>
значением аргумента (заменить ничем, если не определено) и продолжить сканирование.~
и включены расширения команд, то<digit>
тогдаЗаменить
%~[modifiers]<digit>
на измененное значение аргумента (заменить ничем, если не определено или если указано, что модификатор $ PATH: не определен) и продолжить сканирование.Примечание: модификаторы нечувствительны к регистру и могут появляться несколько раз в любом порядке, кроме $ PATH: модификатор может появляться только один раз и должен быть последним модификатором перед
<digit>
посмотрите на следующую строку символов, разрыв до
%
или в конце буфера, и назовите их VAR (может быть пустой список)%
тозамените
%VAR%
на значение VAR и продолжите сканирование.Удалить
%VAR%
и продолжить сканированиепосмотрите на следующую строку символов, разрыв до
%
:
или в конце буфера, и назовите их VAR (может быть пустой список). Если перед VAR прерывается:
и последующий символ%
включается:
в качестве последнего символа в VAR и прерывается перед ним%
.%
тозамените
%VAR%
на значение VAR и продолжите сканирование.Удалить
%VAR%
и продолжить сканирование:
тоУдалить
%VAR:
и продолжить сканирование.~
то[integer][,[integer]]%
тозамените
%VAR:~[integer][,[integer]]%
на подстроку значения VAR (что может привести к пустой строке) и продолжить сканирование.=
или*=
топоиск Invalid переменного и заменить синтаксис вызывает фатальную ошибку: все разобраны команды прерываются, и пакетная обработка прерывает установку, если в пакетном режиме!
[*]search=[replace]%
, где поиск может включать любой набор символов, кроме=
, а замена может включать любой набор символов, кроме%
, затемзаменить
%VAR:[*]search=[replace]%
на значение VAR после выполнения поиска и замены (возможно, что приведет к пустой строке) и продолжить сканироватьУдалить
%
и продолжить сканирование, начиная со следующего символа после%
%
и продолжить сканирование, начиная со следующего символа после сохраненного интерлиньяжа.%
Приведенное выше помогает объяснить, почему эта партия
Дает такие результаты:
Примечание 1. Фаза 1 происходит до распознавания REM-выражений. Это очень важно, потому что это означает, что даже примечание может вызвать фатальную ошибку, если оно имеет недопустимый синтаксис раскрытия аргументов или недопустимый синтаксис поиска и замены переменных!
Примечание 2. Еще одно интересное следствие правил синтаксического анализа%: переменные, содержащие: в имени, могут быть определены, но они не могут быть расширены, если расширения команд не отключены. Есть одно исключение - имя переменной, содержащее одно двоеточие в конце, может быть расширено, пока включены расширения команд. Однако вы не можете выполнять подстроки или операции поиска и замены для имен переменных, заканчивающихся двоеточием. Пакетный файл ниже (любезно предоставлен jeb) демонстрирует это поведение.
Примечание 3. Интересный результат порядка правил синтаксического анализа, который Jeb изложил в своем сообщении: при выполнении поиска и замены с отложенным раскрытием специальные символы в терминах поиска и замены должны быть экранированы или заключены в кавычки. Но с процентным расширением ситуация иная - термин find нельзя экранировать (хотя его можно цитировать). Строка процентной замены может требовать или не требовать экранирования или кавычки, в зависимости от вашего намерения.
Правила отложенного расширения
Вот расширенное и более точное объяснение фазы 5 в ответе jeb (действительно как для пакетного режима, так и для режима командной строки)
Фаза 5) Отложенное расширение
Этот этап пропускается, если выполняется одно из следующих условий:
CALL
заключенным в скобки блоком, какой-либо формой объединения команд (&
,&&
или||
) или конвейером|
.Процесс отложенного расширения применяется к токенам независимо. У команды может быть несколько токенов:
for ... in(TOKEN) do
if defined TOKEN
if exists TOKEN
if errorlevel TOKEN
if cmdextversion TOKEN
if TOKEN comparison TOKEN
, Где сравнение является одним из==
,equ
,neq
,lss
,leq
,gtr
, илиgeq
Для токенов, которые не содержат
!
.Для каждого токена, который содержит хотя бы один
!
, просканируйте каждый символ слева направо на предмет^
или!
, и если он найден, то!
или^
^
то^
!
, топосмотрите на следующую строку символов, прервав ее перед
!
или<LF>
, и назовите их VAR (может быть пустой список)!
тозаменить
!VAR!
на значение VAR и продолжить сканирование.Удалить
!VAR!
и продолжить сканированиеПосмотрите на следующую строку символов, разбив перед тем
!
,:
или<LF>
, и называть их УАК (может быть пустой список). Если перед VAR прерывается:
и последующий символ!
включается:
в качестве последнего символа в VAR и прерывается перед!
!
тозаменить
!VAR!
на значение VAR и продолжить сканирование.Удалить
!VAR!
и продолжить сканирование:
тоУдалить
!VAR:
и продолжить сканирование.~
то[integer][,[integer]]!
то замените!VAR:~[integer][,[integer]]!
на подстроку значения VAR (что может привести к пустой строке) и продолжить сканирование.[*]search=[replace]!
, где поиск может включать любой набор символов, кроме=
, а замена может включать любой набор символов, кроме!
, тозаменить
!VAR:[*]search=[replace]!
на значение VAR после выполнения поиска и замены (возможно, в результате будет пустая строка) и продолжить сканирование!
иначе сохранить ведущий
!
!
источник
%definedVar:a=b%
vs%undefinedVar:a=b%
и%var:~0x17,-010%
форм%<digit>
,%*
или%~
. И поведение меняется для неопределенных переменных. Возможно, вам нужно открыть второй ответКак указывалось, командам передается вся строка аргументов в μSoft land, и они должны разобрать ее на отдельные аргументы для собственного использования. В этом нет согласованности между разными программами, и поэтому нет единого набора правил для описания этого процесса. Вам действительно нужно проверять каждый угол на предмет того, какую библиотеку C использует ваша программа.
Что касается системных
.bat
файлов, вот этот тест:c> type args.cmd @echo off echo cmdcmdline:[%cmdcmdline%] echo 0:[%0] echo *:[%*] set allargs=%* if not defined allargs goto :eof setlocal @rem Wot about a nice for loop? @rem Then we are in the land of delayedexpansion, !n!, call, etc. @rem Plays havoc with args like %t%, a"b etc. ugh! set n=1 :loop echo %n%:[%1] set /a n+=1 shift set param=%1 if defined param goto :loop endlocal
Теперь мы можем запустить несколько тестов. Посмотрим, сможете ли вы понять, что пытается сделать μSoft:
Пока все в порядке. (Я оставлю неинтересное
%cmdcmdline%
и впредь%0
.)Нет расширения имени файла.
Отсутствие удаления кавычек, хотя кавычки предотвращают разделение аргументов.
Последовательные двойные кавычки заставляют их терять любые специальные способности синтаксического анализа, которые они могли иметь. Пример @ Beniot:
Тест: как передать значение любой переменной окружения в качестве единственного аргумента (например, as
%1
) в файл bat?Разумный парсинг кажется навсегда сломанным.
Для вашего развлечения, попробуйте добавить разные
^
,\
,'
,&
(и с.) Символы в этих примерах.источник
t
этоa "b c
. У вас есть рецепт для получения этих 6 символов (a
, 2 × пространство"
,b
иc
) , чтобы выглядеть как%1
внутри.cmd
? Хотя мне нравится твое мышление.args "%t:"=""%"
довольно близко :-)У вас уже есть отличные ответы выше, но чтобы ответить на одну часть вашего вопроса:
Что там происходит, так это то, что, поскольку у вас есть пробел перед =, создается переменная, которая называется
%a<space>%
так, когда выecho %a %
правильно оцениваете какb
.Оставшаяся часть
b% c%
затем оценивается как простой текст + неопределенная переменная% c%
, которая должна отображаться как набранная, для меняecho %a %b% c%
возвращаетbb% c%
Я подозреваю, что возможность включать пробелы в имена переменных - это скорее оплошность, чем запланированная «особенность»
источник
изменить: см. принятый ответ, следующее неверно и объясняет только, как передать командную строку в TinyPerl.
Что касается цитат, у меня такое ощущение, что поведение следующее:
"
найден, начинается подстановка строк"
является, глобален"
найден:""
(например, тройка"
), то к строке добавляется двойная кавычка"
(например, двойная"
), то к строке добавляется двойная кавычка, и подстановка строки заканчивается"
, подстановка строк заканчиваетсяКороче говоря:
"a """ b "" c"""
состоит из двух строк:a " b "
иc"
"a""
,"a"""
и"a""""
все это одна и та же строка, если в конце строкиисточник
Обратите внимание, что Microsoft опубликовала исходный код своего Терминала. Он может работать аналогично командной строке в отношении синтаксического анализа. Может быть, кто-то заинтересован в тестировании правил обратного синтаксического анализа на соответствие правилам синтаксического анализа терминала.
Ссылка на исходный код.
источник