Унифицированный язык скриптов генерации тестов
Унифицированный язык генерации тестов — язык, на котором должны быть написаны .d-файлы, описывающие процесс генерации тестов.
Уже реализованные вещи[править | править код]
Общая структура файла со скриптом генерации задач[править | править код]
- "Структурная единица" файла скрипта — строка.
- Переводы строк допускаются как в Windows, так и в Unix-кодировке. Допускается в одном файле смешение обеих кодировок.
- Символ tab эквивалентен пробелу.
- Несколько пробелов подряд эквивалентны одному пробелу.
- Пробелы в начале и в конце каждой строки игнорируются.
- Пустые строки (или ставшие пустыми с учетом предыдущих пунктов) игнорируются.
- Каждая оставшаяся строка является отдельной командой и состоит из разделенных пробелами имени команды и нуля или более ее аргументов.
- Имена всех файлов в скрипте указываются относительно каталога, в котором будет происходить генерация тестов (скрипт может считать, что это тот каталог, где лежит сам скрипт). Скрипт может полагать, что основной каталог задачи — это родительский (..) каталог относительно текущего, но не понятно, зачем это может быть надо скрипту.
Шаблоны имен файлов с тестами[править | править код]
Имена файлов с тестами нигде в скрипте генерации тестов не должны быть указаны явно. Вместо них должны использоваться шаблоны имен. Во время трансляции программа-транслятор поддерживает текущий номер теста и заменяет шаблоны имен на правильные строки с учетом текущего номера теста. Изначально текущий номер теста равен нулю. Программа-транслятор обрабатывает шаблоны в порядке необходимости (в случае наличия в скрипте циклов шаблоны будут раскрываться в том порядке, в котором будет идти выполнение строк скрипта с учетом циклов; в этом случае один и тот же шаблон может раскрываться несколько раз в разные строки в соответствии с текущим номером теста); в пределах одной строки шаблоны обрабатываются слева направо.
Конкретное соответствие между номерами тестов и именами файлов задается в файле daysper.ini.
Каждый шаблон имени состоит из трех символов, первый из которых — ^. Третий символ может иметь следующие значения:
- + — перед заменой этого шаблона номер текущего теста должен быть увеличен на единицу.
- * — ничего дополнительно делать не надо.
Второй символ может иметь следующие значения:
- t — шаблон должен быть заменен на имя input-файла текущего теста. Например, на 02.
- a — шаблон должен быть заменен на имя answer-файла текущего теста. Например, на 02.a.
Особое значение имеет последовательность ^^^. Она заменяется на одиночный символ ^, который уже не имеет значения начала шаблона.
Переменные[править | править код]
Именем переменной может быть любая строка, начинающаяся с символа $ и не содержащая пробелов и символов ^; рекомендуется, чтобы именем переменной была строка, начинающаяся на $ и далее содержащая только латинские буквы. В частности, в будущем обработка переменных, содержащих другие символы, может быть изменена.
На данный момент переменные реализованы лишь настолько, насколько они нужны для циклов; каждая переменная — это переменная некоторого цикла, которая последовательно принимает значения одно за другим. В частности, оператора присваивания нет. Таким образом, внутри каждого цикла определено значение переменной цикла (и равно текущему ее значению); если несколько циклов вложены, то внутри них определены значения нескольких переменных. После окончания цикла значение соответствующей переменной становится не определено (что эквивалентно отсутствию такой переменной).
Подстановка значений переменной[править | править код]
В любой аргумент любой команды может быть подставлено значение переменной. На данный момент правила подстановки таковы:
- если первый символ аргумента — $, то весь аргумент (независимо от того, состоит он только из латинских букв или нет) считается именем переменной и заменяется на ее значение, если оно определено, или оставляется как есть, если значение переменной не определено (т.е. если такой переменной нет в текущем scope).
- если первый символ не $, то:
- каждая подстрока вида {var} заменяется на значение переменной var или оставляется как есть, если такой переменной нет в текущем scope. (В частности, если строка var не начинается с символа $.) В кажестве значения var выбирается подстрока до ближайшей закрывающеся фигурной скобки.
- для каждого символа $ берется это символ и далее наидлиннейшая подстрока, содержащая только латинские буквы, и заменяется на значение переменной или оставляется как есть, если такая переменная не определена.
Пример:
echo $foo $foo.o a$foo.o {$foo}.o a{$f}oo.o
— здесь последовательно упоминаются переменные $foo, $foo.o, $foo, $foo, $f.
Если, например, на момент выполнения этой строчки определено лишь значение $foo=bar, то получится
echo bar $foo.o abar.o bar.o a{$f}oo.o
Взаимодействие с шаблонами имен тестов[править | править код]
Не определено. Т.е. если в имени переменной, в ее замещающем тексте и т.п. будет символ ^; а также если в замещающем тексте шаблона будут символы $ или {, то поведение daysper не определено.
Команды языка[править | править код]
Общие замечания[править | править код]
- При обработке части команд происходит проверка успешности выполнения.
- В команде run само имя команды может быть опущено, если это не вызывает неоднозначности. Т.е. если daysper встречает неизвестное имя команды, то предполагается, что это — команда run, а "неизвестное имя команды" на самом деле является первым параметром команды run. Например, команда gen 123 456 эквивалентна run gen 123 456.
Компиляция[править | править код]
- compile <source>
- Компиляция указанного файла. Язык программирования, на котором написан файл, определяется по расширению (суффиксу имени) файла. Соответствие между языками программирования и строками компиляции (включая основные параметры командной строки) определяется в файле daysper.ini. Полный список компиляторов также определяется файлом daysper.ini; рекомендуется, чтобы во всех установках daysper поддерживались как минимум следующие расширения (суффиксы имен файлов): .pas, .dpr, .c, .cpp, .java.
- source — имя (без расширения) файла с исходником. Программа-транслятор сама найдет файл с таким именем и каким найдется расширением и использует его. Если таких файлов несколько, то daysper будет использовать первый попавшийся, для которого можно определить компилятор.
- Более строгие правила работы команды compile в текущей версии daysper: для каждого суффикса-расширения, указанного в daysper.ini, команда compile ищет файл с именем, определяемым параметром source, и этим суффиксом-расширением. Команда выполняет компиляцию произвольного из найденных таким образом файлов. Если ни для одного суффикса-расширения файл не найден, выдается ошибка.
Запуск скомпилированного файла[править | править код]
- [run] <file> [parameters]
- Запускает файл, скомпилированный ранее командой compile, передавая ему в качестве параметров командной строки все параметры parameters.
- file — имя файла без расширения, который надо запустить. Запускать можно только программы, которые были ранее скомпилированы командой compile (т.е. ранее должны была быть команда compile с таким же именем в качестве параметра source)
- parameters — параметры, которые должны быть переданы программе.
- вывод программы может быть перенаправлен в файл синтаксисом >file и <file
- Само имя команды (run) может быть опущено, если это не вызывает неоднозначности.
Вывод на экран[править | править код]
- echo <text>
- Выводит на экран указанный текст
Копирование файлов[править | править код]
- copy <file1> <file2>
- Копирует файл.
Переименование файлов[править | править код]
- move <file1> <file2>
- Переименовывает файл.
Удаление файлов[править | править код]
- delete <file>
- Удаляет файл, если он существует.
Циклы for[править | править код]
Циклы for бывают трех видов: по множеству строк, по множеству имен файлов, и по множеству чисел. Любой цикл начинается с команды for и заканчивается командой endfor.
Общие замечания по циклам:
- Если значение переменной цикла уже определено, что будет в случае вложенных циклов по одной и той же переменной, daysper сигнализирует ошибку.
- Множество значений, которые должна будет принимать переменная, обязано быть непустым.
- Циклы интерпретируются программой-транслятором, т.е. в выходном cmd/sh-скрипте цикла не будет.
- Если в теле цикла встречаются шаблоны имен файлов с тестами, на каждой итерации цикла они будут раскрываться заново.
- Имя переменной цикла должно начинаться на $ (в соответствии с общей концепцией имен переменных)
См. примеры ниже.
- for <variable> in <values>
- endfor
- values — список разделенных проблеми значений, которые последовательно будет принимать переменная variable.
- for -f <variable> in <values>
- endfor
- values — список разделенных пробелами масок файлов. Каждая маска будет раскрыта в список файлов (не каталогов), удовлетворяющих маске, каждый такой список сортируется по алфавиту, после чего списки конкатенируются в порядке перечисления масок; переменная variable будет последовательно пробегать значения из полученного списка.
- Если какой-то маске не соответствует ни одного файла, она раскрывается в пустой список. Раскрытие масок происходит в момент трансляции этой строки, т.е. до реального запуска всего .d-скрипта и выполнения каких-либо действий. В случае вложенных циклов раскрытие масок внутреннего цикла будет происходить заново на каждой итерации внешнего цикла в рамках трансляции (т.е. если в маске внутреннего цикла используется переменная внешнего цикла, то на каждой итерации внешнего будет использоваться новое значение его переменной и маски внутреннего цикла будут раскрываться заново).
- for <variable> from <value1> to <value2> [format <format>]
- endfor
- value1 и value2 должны быть числами. Каждое число от value1 до value2 включительно будет преобразовано в строку командой sprintf в соответствии с форматом format (по умолчанию — %d), переменная variable будет последовательно пробегать соответствующие значения. См. примеры ниже.
Валидация тестов и генерация answer-файлов[править | править код]
Валидация тестов и автоматическая генерация answer-файлов, использующая решения жюри, не должна быть прописана в файле со скриптом. Она будет автоматически выполнена, т.е. при создании cmd/sh-скрипта генерации задачи будет добавлена валидация тестов и генерация answer-файлов.
А именно:
Валидация тестов[править | править код]
При генерации тестов код для валидации всех тестов вставляется в результирующий cmd/sh-скрипт автоматически, таким образом, все тесты автоматически валидируются.
Answer-файлы[править | править код]
Скрипт генерации может предусматривать создание некоторых answer-файлов без участия решения жюри (например, могут быть ручные answer-файлы на ручные тесты, или отдельные генераторы могут создавать также и answer-файлы в случаях, когда они создаются легко с учетом структуры теста, и т.п.); в таком случае скрипт генерации должен явно содержать команды для генерации этих файлов (и соответствующие шаблоны ^a+ или ^a*), в таком случае daysper не осуществляет особых действий по генерации answer-файлов для этих тестов.
Для всех же тестов, для которых не генерируются answer-файлы (т.е. отсутствуют шаблоны ^a+ или ^a*), в конец .cmd/.sh-файла создания задачи вставляется код генерации answer-файлов с использованием решения жюри. Конкретное решение жюри определяется параметром команды build или, по умолчанию, указывается в daysper.ini.
Примеры[править | править код]
run genrnd 2853 3 100 0 ^t+ ^a*
Запустить программу genrnd с шастью параметрами, первые четыре из которых — 2853 3 100 0, а пятый и шестой — имена input- и answer-файлов очередного теста (т.е. надо увеличить текущий тест на единицу, после чего имена input- и answer-файлов этого теста передать в качестве параметров программе).
genrnd 2853 3 100 0 ^t+ ^a*
То же самое.
copy 01.hand ^t+ copy 01.hand.a ^a*
скопировать файлы 01.hand и 01.hand.a в input- и answer-файлы очередного (т.е. следующего) теста.
for $i in a bb ccc echo a$i endfor
выводит на экран последовательно строки aa, abb, accc.
for $i in small large for -f $j in test?.$i copy $j ^t+ copy {$j}.a ^a* endfor endfor
последовательно копирует в очередные тесты сначала все файлы вида test?.small, потом все файлы вида test?.large; в соответствующие answer-файлы копирует файлы с теми же именами, к которым приписан суффикс .a.
for $i from 5 to 15 format %03d echo $i endfor
Выводит на экран последовательно 005, 006, 007, 008, 009, 010, 011, 012, 013, 014 и 015.
Планы на будущее[править | править код]
Общая структура файла со скриптом генерации задач[править | править код]
- Символ #, а также вся часть строки от него до конца, игнорируется (комментарии).
Шаблоны имен файлов с тестами[править | править код]
- Вторая буква шаблона n — шаблон должен быть заменен на номер теста. Например, на 2. Я пока не вижу применений у этого шаблона. Использовать его в качестве randseed нельзя, т.к. тогда нельзя будет безболезненно менять порядок тестов.
Команды языка[править | править код]
- Для каждой команды, которая создает новый файл (копирование, компиляция и т.д.): удаление старого файла с тем же именем, если он ес ть.
- Явное указание языка в команде compile. Явное указание компилятора в команде compile. Возможность указания параметров командной строки в команде compile. Возможность указания имени результирующего файла в команде compile (compile <source> [dest]). Более корректная работа в случае нескольких подходящих для компиляции файлов.
- (Я думаю, что надо рекомендовать все ключи компилятора, которые нужны, указывать в файле с исходниками (например, ключами компилятора); единственный случай, когда это вроде создает проблему — маленький стек по умолчанию стека в программах на C/C++ и невозможность его увеличить в исходнике (???), но тут просто надо в строке компиляции для всех C/C++ программ указывать стек, рекомендовав это в свойства программы-трансляторов.)
- Команда run: указание перенаправлений:
- redirections — перенаправления. Допускаются перенаправления следующих форматов:
- >file
- число>file
- <file
- тот же смысл, что и в операционной системе: перенаправление стандартных потоков ввода/вывода. Верно ли, что и в Windows, и в Linux они имеют один и тот же смысл и потом программа-транслятор может их рассматривать как аргументы и особо не обрабатывать?
- file1=file2
- Обозначает, что программа выводит свой результат работы в файл file1, а надо этот файл сохранить под именем file2. Программа-транслятор должна вставить после вызова программы команду переименования файла. Рекомендуется, чтобы перед вызовом программы была вставлена команда удаления файлов file1 и file2, а после вызова программы перед копирование — проверка, что файл file1 появился. Программа-транслятор должна поддерживать указание нескольких таких перенаправлений в одной строке.
- Допустить указание пути к файлу во всех командах
- Настройки скрипта
- Кавычки
- ' (начало/конец жёстких кавычек)
- " (начало/конец мягких кавычек)
- [] (функциональный префикс)
- ^ (начало шаблона)
- \ (экранирует)
- Префиксы для команд
[ignore-fail] run nya
- Переменные + циклы
- Идеи:
- Переменные будут. Причём нельзя использовать переменную до присвоения ей начального значения.
- У переменных есть типы. Пока понадобятся три: строки, массивы (строк), специальные (вроде исполняемого файла, что вернёт compile)
- Можно делать for как префикс к команде, умеющий перебрать элементы массива.
manualTests = find '??.manual' # похоже, что «=» — ещё один специальный символ. [for i $manualTests] copy $i ^t+ # почему именно такой синтаксис, неясно. Почему бы и нет?
Еще команды[править | править код]
- Вызов другого скрипта. include <file>
- Вызывает другой скрипт, написанный на этом же языке. Результат в точности эквивалентен тому, как если бы вместо этой команды в текущем файла присутствовало все содержимое файла file.
- file — имя вызываемого скрипта без расширения (требует стандартного расширения этим скриптам, но в любом случае его стоит придумать, наверное).
- Непонятно, почему без. Пусть будет имя файла полностью, как в c-style #include — burunduk3
- echo -n
- Вывод без перевода строки (если поддерживается системой)
- Вызов архиваторов.
- unrar <file>
- unzip <file>
- Распаковывает указанный файл.