From 1f0ae8f13b03111053d4db54fbe3a6457769ae08 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 1 Mar 2021 23:34:25 +0800 Subject: [PATCH] =?UTF-8?q?judge:=20=E9=9B=86=E6=88=90testlib=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=A4=E4=BA=92=E9=A2=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hydrojudge/files/testlib.h | 2866 ++++++++++++++++++ packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/cases.ts | 1 + packages/hydrojudge/src/checkers.ts | 9 +- packages/hydrojudge/src/judge/interactive.ts | 15 +- packages/hydrojudge/src/sandbox.ts | 45 +- packages/hydrojudge/src/testlib.ts | 24 + packages/hydrooj/package.json | 2 +- 8 files changed, 2933 insertions(+), 31 deletions(-) create mode 100644 packages/hydrojudge/files/testlib.h create mode 100644 packages/hydrojudge/src/testlib.ts diff --git a/packages/hydrojudge/files/testlib.h b/packages/hydrojudge/files/testlib.h new file mode 100644 index 000000000..26a3c5fdc --- /dev/null +++ b/packages/hydrojudge/files/testlib.h @@ -0,0 +1,2866 @@ +#ifndef _TESTLIB_H_ +#define _TESTLIB_H_ +#define VERSION "0.9.24-SNAPSHOT" +#ifdef _MSC_VER +#define _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NO_VA_START_VALIDATION +#endif +#define random __random_deprecated +#include +#include +#include +#include +#undef random +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (_WIN32 || __WIN32__ || _WIN64 || __WIN64__ || __CYGWIN__) +#if !defined(_MSC_VER) || _MSC_VER > 1400 +#define NOMINMAX 1 +#include +#else +#define WORD unsigned short +#include +#endif +#include +#define ON_WINDOWS +#if defined(_MSC_VER) && _MSC_VER > 1400 +#pragma warning(disable : 4127) +#pragma warning(disable : 4146) +#pragma warning(disable : 4458) +#endif +#else +#define WORD unsigned short +#include +#endif +#if defined(FOR_WINDOWS) && defined(FOR_LINUX) +#error Only one target system is allowed +#endif +#ifndef LLONG_MIN +#define LLONG_MIN (-9223372036854775807LL - 1) +#endif +#ifndef ULLONG_MAX +#define ULLONG_MAX (18446744073709551615) +#endif +#define LF ((char)10) +#define CR ((char)13) +#define TAB ((char)9) +#define SPACE ((char)' ') +#define EOFC (255) +#ifndef OK_EXIT_CODE +#ifdef CONTESTER +#define OK_EXIT_CODE 0xAC +#else +#define OK_EXIT_CODE 0 +#endif +#endif +#ifndef WA_EXIT_CODE +#ifdef EJUDGE +#define WA_EXIT_CODE 5 +#elif defined(CONTESTER) +#define WA_EXIT_CODE 0xAB +#else +#define WA_EXIT_CODE 1 +#endif +#endif +#ifndef PE_EXIT_CODE +#ifdef EJUDGE +#define PE_EXIT_CODE 4 +#elif defined(CONTESTER) +#define PE_EXIT_CODE 0xAA +#else +#define PE_EXIT_CODE 2 +#endif +#endif +#ifndef FAIL_EXIT_CODE +#ifdef EJUDGE +#define FAIL_EXIT_CODE 6 +#elif defined(CONTESTER) +#define FAIL_EXIT_CODE 0xA3 +#else +#define FAIL_EXIT_CODE 3 +#endif +#endif +#ifndef DIRT_EXIT_CODE +#ifdef EJUDGE +#define DIRT_EXIT_CODE 6 +#else +#define DIRT_EXIT_CODE 4 +#endif +#endif +#ifndef POINTS_EXIT_CODE +#define POINTS_EXIT_CODE 7 +#endif +#ifndef UNEXPECTED_EOF_EXIT_CODE +#define UNEXPECTED_EOF_EXIT_CODE 8 +#endif +#ifndef PC_BASE_EXIT_CODE +#ifdef TESTSYS +#define PC_BASE_EXIT_CODE 50 +#else +#define PC_BASE_EXIT_CODE 0 +#endif +#endif +#ifdef __GNUC__ +#define __TESTLIB_STATIC_ASSERT(condition) typedef void* __testlib_static_assert_type[(condition) ? 1 : -1] __attribute__((unused)) +#else +#define __TESTLIB_STATIC_ASSERT(condition) typedef void* __testlib_static_assert_type[(condition) ? 1 : -1] +#endif +#ifdef ON_WINDOWS +#define I64 "%I64d" +#define U64 "%I64u" +#else +#define I64 "%lld" +#define U64 "%llu" +#endif +#ifdef _MSC_VER +#define NORETURN __declspec(noreturn) +#elif defined __GNUC__ +#define NORETURN __attribute__((noreturn)) +#else +#define NORETURN +#endif +static char __testlib_format_buffer[16777216]; +static int __testlib_format_buffer_usage_count = 0; +#define FMT_TO_RESULT(fmt, cstr, result) \ + std::string result; \ + if (__testlib_format_buffer_usage_count != 0) \ + __testlib_fail("FMT_TO_RESULT::__testlib_format_buffer_usage_count != 0"); \ + __testlib_format_buffer_usage_count++; \ + va_list ap; \ + va_start(ap, fmt); \ + vsnprintf(__testlib_format_buffer, sizeof(__testlib_format_buffer), cstr, ap); \ + va_end(ap); \ + __testlib_format_buffer[sizeof(__testlib_format_buffer) - 1] = 0; \ + result = std::string(__testlib_format_buffer); \ + __testlib_format_buffer_usage_count--; +const long long __TESTLIB_LONGLONG_MAX = 9223372036854775807LL; +bool __testlib_hasTestCase; +int __testlib_testCase = -1; +void setTestCase(int testCase) { + __testlib_hasTestCase = true; + __testlib_testCase = testCase; +} +void unsetTestCase() { + __testlib_hasTestCase = false; + __testlib_testCase = -1; +} +NORETURN static void __testlib_fail(const std::string& message); +template +static inline T __testlib_abs(const T& x) { + return x > 0 ? x : -x; +} +template +static inline T __testlib_min(const T& a, const T& b) { + return a < b ? a : b; +} +template +static inline T __testlib_max(const T& a, const T& b) { + return a > b ? a : b; +} +static bool __testlib_prelimIsNaN(double r) { + volatile double ra = r; +#ifndef __BORLANDC__ + return ((ra != ra) == true) && ((ra == ra) == false) && ((1.0 > ra) == false) && ((1.0 < ra) == false); +#else return std::_isnan(ra); +#endif +} +static std::string removeDoubleTrailingZeroes(std::string value) { + while (!value.empty() && value[value.length() - 1] == '0' && value.find('.') != std::string::npos)value = value.substr(0, value.length() - 1); + return value + '0'; +} +#ifdef __GNUC__ +__attribute__((format(printf, 1, 2))) +#endif +std::string +format(const char* fmt, ...) { + FMT_TO_RESULT(fmt, fmt, result); + return result; +} +std::string format(const std::string fmt, ...) { + FMT_TO_RESULT(fmt, fmt.c_str(), result); + return result; +} +static std::string __testlib_part(const std::string& s); +static bool __testlib_isNaN(double r) { + __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long)); + volatile double ra = r; + long long llr1, llr2; + std::memcpy((void*)&llr1, (void*)&ra, sizeof(double)); + ra = -ra; + std::memcpy((void*)&llr2, (void*)&ra, sizeof(double)); + long long llnan = 0xFFF8000000000000LL; + return __testlib_prelimIsNaN(r) || llnan == llr1 || llnan == llr2; +} +static double __testlib_nan() { + __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long)); +#ifndef NAN + long long llnan = 0xFFF8000000000000LL; + double nan; + std::memcpy(&nan, &llnan, sizeof(double)); + return nan; +#else return NAN; +#endif +} +static bool __testlib_isInfinite(double r) { + volatile double ra = r; + return (ra > 1E300 || ra < -1E300); +} +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline bool +doubleCompare(double expected, double result, double MAX_DOUBLE_ERROR) { + if (__testlib_isNaN(expected)) { + return __testlib_isNaN(result); + } else if (__testlib_isInfinite(expected)) { + if (expected > 0) { + return result > 0 && __testlib_isInfinite(result); + } else { + return result < 0 && __testlib_isInfinite(result); + } + } else if (__testlib_isNaN(result) || __testlib_isInfinite(result)) { + return false; + } else if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR + 1E-15) { + return true; + } else { + double minv = __testlib_min(expected * (1.0 - MAX_DOUBLE_ERROR), + expected * (1.0 + MAX_DOUBLE_ERROR)); + double maxv = __testlib_max(expected * (1.0 - MAX_DOUBLE_ERROR), + expected * (1.0 + MAX_DOUBLE_ERROR)); + return result + 1E-15 >= minv && result <= maxv + 1E-15; + } +} +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline double +doubleDelta(double expected, double result) { + double absolute = __testlib_abs(result - expected); + if (__testlib_abs(expected) > 1E-9) { + double relative = __testlib_abs(absolute / expected); + return __testlib_min(absolute, relative); + } else return absolute; +} +#if !defined(_MSC_VER) || _MSC_VER < 1900 +#ifndef _fileno +#define _fileno(_stream) ((_stream)->_file) +#endif +#endif +#ifndef O_BINARY +static void __testlib_set_binary( +#ifdef __GNUC__ + __attribute__((unused)) +#endif + std::FILE* file) +#else +static void __testlib_set_binary(std::FILE* file) +#endif +{ +#ifdef O_BINARY + if (NULL != file) { +#ifndef __BORLANDC__ + _setmode(_fileno(file), O_BINARY); +#else setmode(fileno(file), O_BINARY); +#endif + } +#endif +} +class random_t; +class pattern { +public: + pattern(std::string s); + std::string next(random_t& rnd) const; + bool matches(const std::string& s) const; + std::string src() const; + +private: + bool matches(const std::string& s, size_t pos) const; + std::string s; + std::vector children; + std::vector chars; + int from; + int to; +}; +class random_t { +private: + unsigned long long seed; + static const unsigned long long multiplier; + static const unsigned long long addend; + static const unsigned long long mask; + static const int lim; + long long nextBits(int bits) { + if (bits <= 48) { + seed = (seed * multiplier + addend) & mask; + return (long long)(seed >> (48 - bits)); + } else { + if (bits > 63)__testlib_fail("random_t::nextBits(int bits): n must be less than 64"); + int lowerBitCount = (random_t::version == 0 ? 31 : 32); + long long left = (nextBits(31) << 32); + long long right = nextBits(lowerBitCount); + return left ^ right; + } + } + +public: + static int version; + random_t(): seed(3905348978240129619LL) { + } + void setSeed(int argc, char* argv[]) { + random_t p; + seed = 3905348978240129619LL; + for (int i = 1; i < argc; i++) { + std::size_t le = std::strlen(argv[i]); + for (std::size_t j = 0; j < le; j++)seed = seed * multiplier + (unsigned int)(argv[i][j]) + addend; + seed += multiplier / addend; + } + seed = seed & mask; + } + void setSeed(long long _seed) { + _seed = (_seed ^ multiplier) & mask; + seed = _seed; + } +#ifndef __BORLANDC__ + + std::string next(const std::string& ptrn) { + pattern p(ptrn); + return p.next(*this); + } +#else + + std::string next(std::string ptrn) { + pattern p(ptrn); + return p.next(*this); + } +#endif + + int next(int n) { + if (n <= 0)__testlib_fail("random_t::next(int n): n must be positive"); + if ((n & -n) == n)return (int)((n * (long long)nextBits(31)) >> 31); + const long long limit = INT_MAX / n * n; + long long bits; + do { + bits = nextBits(31); + } while (bits >= limit); + return int(bits % n); + } + unsigned int next(unsigned int n) { + if (n >= INT_MAX)__testlib_fail("random_t::next(unsigned int n): n must be less INT_MAX"); + return (unsigned int)next(int(n)); + } + long long next(long long n) { + if (n <= 0)__testlib_fail("random_t::next(long long n): n must be positive"); + const long long limit = __TESTLIB_LONGLONG_MAX / n * n; + long long bits; + do { + bits = nextBits(63); + } while (bits >= limit); + return bits % n; + } + unsigned long long next(unsigned long long n) { + if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX))__testlib_fail("random_t::next(unsigned long long n): n must be less LONGLONG_MAX"); + return (unsigned long long)next((long long)(n)); + } + long next(long n) { + return (long)next((long long)(n)); + } + unsigned long next(unsigned long n) { + if (n >= (unsigned long)(LONG_MAX))__testlib_fail("random_t::next(unsigned long n): n must be less LONG_MAX"); + return (unsigned long)next((unsigned long long)(n)); + } + int next(int from, int to) { + return int(next((long long)to - from + 1) + from); + } + unsigned int next(unsigned int from, unsigned int to) { + return (unsigned int)(next((long long)to - from + 1) + from); + } + long long next(long long from, long long to) { + return next(to - from + 1) + from; + } + unsigned long long next(unsigned long long from, unsigned long long to) { + if (from > to)__testlib_fail("random_t::next(unsigned long long from, unsigned long long to): from can't not exceed to"); + return next(to - from + 1) + from; + } + long next(long from, long to) { + return next(to - from + 1) + from; + } + unsigned long next(unsigned long from, unsigned long to) { + if (from > to)__testlib_fail("random_t::next(unsigned long from, unsigned long to): from can't not exceed to"); + return next(to - from + 1) + from; + } + double next() { + long long left = ((long long)(nextBits(26)) << 27); + long long right = nextBits(27); + return (double)(left + right) / (double)(1LL << 53); + } + double next(double n) { + return n * next(); + } + double next(double from, double to) { + if (from > to)__testlib_fail("random_t::next(double from, double to): from can't not exceed to"); + return next(to - from) + from; + } + template + typename Container::value_type any(const Container& c) { + size_t size = c.size(); + if (size <= 0)__testlib_fail("random_t::any(const Container& c): c.size() must be positive"); + return *(c.begin() + next(size)); + } + template + typename Iter::value_type any(const Iter& begin, const Iter& end) { + int size = int(end - begin); + if (size <= 0)__testlib_fail("random_t::any(const Iter& begin, const Iter& end): range must have positive length"); + return *(begin + next(size)); + } +#ifdef __GNUC__ + __attribute__((format(printf, 2, 3))) +#endif + std::string + next(const char* format, ...) { + FMT_TO_RESULT(format, format, ptrn); + return next(ptrn); + } + int wnext(int n, int type) { + if (n <= 0)__testlib_fail("random_t::wnext(int n, int type): n must be positive"); + if (abs(type) < random_t::lim) { + int result = next(n); + for (int i = 0; i < +type; i++)result = __testlib_max(result, next(n)); + for (int i = 0; i < -type; i++)result = __testlib_min(result, next(n)); + return result; + } else { + double p; + if (type > 0)p = std::pow(next() + 0.0, 1.0 / (type + 1)); + else p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); + return int(n * p); + } + } + long long wnext(long long n, int type) { + if (n <= 0)__testlib_fail("random_t::wnext(long long n, int type): n must be positive"); + if (abs(type) < random_t::lim) { + long long result = next(n); + for (int i = 0; i < +type; i++)result = __testlib_max(result, next(n)); + for (int i = 0; i < -type; i++)result = __testlib_min(result, next(n)); + return result; + } else { + double p; + if (type > 0)p = std::pow(next() + 0.0, 1.0 / (type + 1)); + else p = std::pow(next() + 0.0, -type + 1); + return __testlib_min(__testlib_max((long long)(double(n) * p), 0LL), n - 1LL); + } + } + double wnext(int type) { + if (abs(type) < random_t::lim) { + double result = next(); + for (int i = 0; i < +type; i++)result = __testlib_max(result, next()); + for (int i = 0; i < -type; i++)result = __testlib_min(result, next()); + return result; + } else { + double p; + if (type > 0)p = std::pow(next() + 0.0, 1.0 / (type + 1)); + else p = std::pow(next() + 0.0, -type + 1); + return p; + } + } + double wnext(double n, int type) { + if (n <= 0)__testlib_fail("random_t::wnext(double n, int type): n must be positive"); + if (abs(type) < random_t::lim) { + double result = next(); + for (int i = 0; i < +type; i++)result = __testlib_max(result, next()); + for (int i = 0; i < -type; i++)result = __testlib_min(result, next()); + return n * result; + } else { + double p; + if (type > 0)p = std::pow(next() + 0.0, 1.0 / (type + 1)); + else p = std::pow(next() + 0.0, -type + 1); + return n * p; + } + } + unsigned int wnext(unsigned int n, int type) { + if (n >= INT_MAX)__testlib_fail("random_t::wnext(unsigned int n, int type): n must be less INT_MAX"); + return (unsigned int)wnext(int(n), type); + } + unsigned long long wnext(unsigned long long n, int type) { + if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX))__testlib_fail("random_t::wnext(unsigned long long n, int type): n must be less LONGLONG_MAX"); + return (unsigned long long)wnext((long long)(n), type); + } + long wnext(long n, int type) { + return (long)wnext((long long)(n), type); + } + unsigned long wnext(unsigned long n, int type) { + if (n >= (unsigned long)(LONG_MAX))__testlib_fail("random_t::wnext(unsigned long n, int type): n must be less LONG_MAX"); + return (unsigned long)wnext((unsigned long long)(n), type); + } + int wnext(int from, int to, int type) { + if (from > to)__testlib_fail("random_t::wnext(int from, int to, int type): from can't not exceed to"); + return wnext(to - from + 1, type) + from; + } + int wnext(unsigned int from, unsigned int to, int type) { + if (from > to)__testlib_fail("random_t::wnext(unsigned int from, unsigned int to, int type): from can't not exceed to"); + return int(wnext(to - from + 1, type) + from); + } + long long wnext(long long from, long long to, int type) { + if (from > to)__testlib_fail("random_t::wnext(long long from, long long to, int type): from can't not exceed to"); + return wnext(to - from + 1, type) + from; + } + unsigned long long wnext(unsigned long long from, unsigned long long to, int type) { + if (from > to)__testlib_fail("random_t::wnext(unsigned long long from, unsigned long long to, int type): from can't not exceed to"); + return wnext(to - from + 1, type) + from; + } + long wnext(long from, long to, int type) { + if (from > to)__testlib_fail("random_t::wnext(long from, long to, int type): from can't not exceed to"); + return wnext(to - from + 1, type) + from; + } + unsigned long wnext(unsigned long from, unsigned long to, int type) { + if (from > to)__testlib_fail("random_t::wnext(unsigned long from, unsigned long to, int type): from can't not exceed to"); + return wnext(to - from + 1, type) + from; + } + double wnext(double from, double to, int type) { + if (from > to)__testlib_fail("random_t::wnext(double from, double to, int type): from can't not exceed to"); + return wnext(to - from, type) + from; + } + template + typename Container::value_type wany(const Container& c, int type) { + size_t size = c.size(); + if (size <= 0)__testlib_fail("random_t::wany(const Container& c, int type): c.size() must be positive"); + return *(c.begin() + wnext(size, type)); + } + template + typename Iter::value_type wany(const Iter& begin, const Iter& end, int type) { + int size = int(end - begin); + if (size <= 0)__testlib_fail("random_t::any(const Iter& begin, const Iter& end, int type): range must have positive length"); + return *(begin + wnext(size, type)); + } + template + std::vector perm(T size, E first) { + if (size <= 0)__testlib_fail("random_t::perm(T size, E first = 0): size must be positive"); + std::vector p(size); + for (T i = 0; i < size; i++)p[i] = first + i; + if (size > 1)for (T i = 1; i < size; i++)std::swap(p[i], p[next(i + 1)]); + return p; + } + template + std::vector perm(T size) { + return perm(size, T(0)); + } +}; +const int random_t::lim = 25; +const unsigned long long random_t::multiplier = 0x5DEECE66DLL; +const unsigned long long random_t::addend = 0xBLL; +const unsigned long long random_t::mask = (1LL << 48) - 1; +int random_t::version = -1; + +bool pattern::matches(const std::string& s) const { + return matches(s, 0); +} +static bool __pattern_isSlash(const std::string& s, size_t pos) { + return s[pos] == '\\'; +} +#ifdef __GNUC__ +__attribute__((pure)) +#endif +static bool +__pattern_isCommandChar(const std::string& s, size_t pos, char value) { + if (pos >= s.length())return false; + int slashes = 0; + int before = int(pos) - 1; + while (before >= 0 && s[before] == '\\')before--, slashes++; + return slashes % 2 == 0 && s[pos] == value; +} +static char __pattern_getChar(const std::string& s, size_t& pos) { + if (__pattern_isSlash(s, pos))pos += 2; + else pos++; + return s[pos - 1]; +} +#ifdef __GNUC__ +__attribute__((pure)) +#endif +static int +__pattern_greedyMatch(const std::string& s, size_t pos, const std::vector chars) { + int result = 0; + while (pos < s.length()) { + char c = s[pos++]; + if (!std::binary_search(chars.begin(), chars.end(), c))break; + else result++; + } + return result; +} +std::string pattern::src() const { + return s; +} +bool pattern::matches(const std::string& s, size_t pos) const { + std::string result; + if (to > 0) { + int size = __pattern_greedyMatch(s, pos, chars); + if (size < from)return false; + if (size > to)size = to; + pos += size; + } + if (children.size() > 0) { + for (size_t child = 0; child < children.size(); child++)if (children[child].matches(s, pos))return true; + return false; + } else return pos == s.length(); +} +std::string pattern::next(random_t& rnd) const { + std::string result; + result.reserve(20); + if (to == INT_MAX)__testlib_fail("pattern::next(random_t& rnd): can't process character '*' for generation"); + if (to > 0) { + int count = rnd.next(to - from + 1) + from; + for (int i = 0; i < count; i++)result += chars[rnd.next(int(chars.size()))]; + } + if (children.size() > 0) { + int child = rnd.next(int(children.size())); + result += children[child].next(rnd); + } + return result; +} +static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, int& to) { + if (pos >= s.length()) { + from = to = 1; + return; + } + if (__pattern_isCommandChar(s, pos, '{')) { + std::vector parts; + std::string part; + pos++; + while (pos < s.length() && !__pattern_isCommandChar(s, pos, '}')) { + if (__pattern_isCommandChar(s, pos, ','))parts.push_back(part), part = "", pos++; + else part += __pattern_getChar(s, pos); + } + if (part != "")parts.push_back(part); + if (!__pattern_isCommandChar(s, pos, '}'))__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + pos++; + if (parts.size() < 1 || parts.size() > 2)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + std::vector numbers; + for (size_t i = 0; i < parts.size(); i++) { + if (parts[i].length() == 0)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + int number; + if (std::sscanf(parts[i].c_str(), "%d", &number) != 1)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + numbers.push_back(number); + } + if (numbers.size() == 1)from = to = numbers[0]; + else from = numbers[0], to = numbers[1]; + if (from > to)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + } else { + if (__pattern_isCommandChar(s, pos, '?')) { + from = 0, to = 1, pos++; + return; + } + if (__pattern_isCommandChar(s, pos, '*')) { + from = 0, to = INT_MAX, pos++; + return; + } + if (__pattern_isCommandChar(s, pos, '+')) { + from = 1, to = INT_MAX, pos++; + return; + } + from = to = 1; + } +} +static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos) { + if (pos >= s.length())__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + std::vector result; + if (__pattern_isCommandChar(s, pos, '[')) { + pos++; + bool negative = __pattern_isCommandChar(s, pos, '^'); + char prev = 0; + while (pos < s.length() && !__pattern_isCommandChar(s, pos, ']')) { + if (__pattern_isCommandChar(s, pos, '-') && prev != 0) { + pos++; + if (pos + 1 == s.length() || __pattern_isCommandChar(s, pos, ']')) { + result.push_back(prev); + prev = '-'; + continue; + } + char next = __pattern_getChar(s, pos); + if (prev > next)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + for (char c = prev; c != next; c++)result.push_back(c); + result.push_back(next); + prev = 0; + } else { + if (prev != 0)result.push_back(prev); + prev = __pattern_getChar(s, pos); + } + } + if (prev != 0)result.push_back(prev); + if (!__pattern_isCommandChar(s, pos, ']'))__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + pos++; + if (negative) { + std::sort(result.begin(), result.end()); + std::vector actuals; + for (int code = 0; code < 255; code++) { + char c = char(code); + if (!std::binary_search(result.begin(), result.end(), c))actuals.push_back(c); + } + result = actuals; + } + std::sort(result.begin(), result.end()); + } else result.push_back(__pattern_getChar(s, pos)); + return result; +} +pattern::pattern(std::string s): s(s), from(0), to(0) { + std::string t; + for (size_t i = 0; i < s.length(); i++)if (!__pattern_isCommandChar(s, i, ' '))t += s[i]; + s = t; + int opened = 0; + int firstClose = -1; + std::vector seps; + for (size_t i = 0; i < s.length(); i++) { + if (__pattern_isCommandChar(s, i, '(')) { + opened++; + continue; + } + if (__pattern_isCommandChar(s, i, ')')) { + opened--; + if (opened == 0 && firstClose == -1)firstClose = int(i); + continue; + } + if (opened < 0)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + if (__pattern_isCommandChar(s, i, '|') && opened == 0)seps.push_back(int(i)); + } + if (opened != 0)__testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); + if (seps.size() == 0 && firstClose + 1 == (int)s.length()&& __pattern_isCommandChar(s, 0, '(') && __pattern_isCommandChar(s, s.length() - 1, ')')) { + children.push_back(pattern(s.substr(1, s.length() - 2))); + } else { + if (seps.size() > 0) { + seps.push_back(int(s.length())); + int last = 0; + for (size_t i = 0; i < seps.size(); i++) { + children.push_back(pattern(s.substr(last, seps[i] - last))); + last = seps[i] + 1; + } + } else { + size_t pos = 0; + chars = __pattern_scanCharSet(s, pos); + __pattern_scanCounts(s, pos, from, to); + if (pos < s.length())children.push_back(pattern(s.substr(pos))); + } + } +} + +template +inline bool isEof(C c) { + return c == EOFC; +} +template +inline bool isEoln(C c) { + return (c == LF || c == CR); +} +template +inline bool isBlanks(C c) { + return (c == LF || c == CR || c == SPACE || c == TAB); +} +inline std::string trim(const std::string& s) { + if (s.empty())return s; + int left = 0; + while (left < int(s.length()) && isBlanks(s[left]))left++; + if (left >= int(s.length()))return ""; + int right = int(s.length()) - 1; + while (right >= 0 && isBlanks(s[right]))right--; + if (right < 0)return ""; + return s.substr(left, right - left + 1); +} +enum TMode { + _input, + _output, + _answer +}; + +enum TResult { + _ok = 0, + _wa = 1, + _pe = 2, + _fail = 3, + _dirt = 4, + _points = 5, + _unexpected_eof = 8, + _partially = 16 +}; +enum TTestlibMode { + _unknown, + _checker, + _validator, + _generator, + _interactor +}; +#define _pc(exitCode) (TResult(_partially + (exitCode))) + +const std::string outcomes[] = { + "accepted", + "wrong-answer", + "presentation-error", + "fail", + "fail", +#ifndef PCMS2 + "points", +#else "relative-scoring", +#endif + "reserved", + "reserved", + "unexpected-eof", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "partially-correct" +}; +class InputStreamReader { +public: + virtual int curChar() = 0; + virtual int nextChar() = 0; + virtual void skipChar() = 0; + virtual void unreadChar(int c) = 0; + virtual std::string getName() = 0; + virtual bool eof() = 0; + virtual void close() = 0; + virtual int getLine() = 0; + virtual ~InputStreamReader() = 0; +}; +InputStreamReader::~InputStreamReader() { +} +class StringInputStreamReader : public InputStreamReader { +private: + std::string s; + size_t pos; + +public: + StringInputStreamReader(const std::string& content): s(content), pos(0) { + } + int curChar() { + if (pos >= s.length())return EOFC; + else return s[pos]; + } + int nextChar() { + if (pos >= s.length()) { + pos++; + return EOFC; + } else return s[pos++]; + } + void skipChar() { + pos++; + } + void unreadChar(int c) { + if (pos == 0)__testlib_fail("FileFileInputStreamReader::unreadChar(int): pos == 0."); + pos--; + if (pos < s.length())s[pos] = char(c); + } + std::string getName() { + return __testlib_part(s); + } + int getLine() { + return -1; + } + bool eof() { + return pos >= s.length(); + } + void close() { + } +}; +class FileInputStreamReader : public InputStreamReader { +private: + std::FILE* file; + std::string name; + int line; + std::vector undoChars; + inline int postprocessGetc(int getcResult) { + if (getcResult != EOF)return getcResult; + else return EOFC; + } + int getc(FILE* file) { + int c; + if (undoChars.empty())c = ::getc(file); + else { + c = undoChars.back(); + undoChars.pop_back(); + } + if (c == LF)line++; + return c; + } + int ungetc(int c) { + if (c == LF)line--; + undoChars.push_back(c); + return c; + } + +public: + FileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name), line(1) { + } + int curChar() { + if (feof(file))return EOFC; + else { + int c = getc(file); + ungetc(c); + return postprocessGetc(c); + } + } + int nextChar() { + if (feof(file))return EOFC; + else return postprocessGetc(getc(file)); + } + void skipChar() { + getc(file); + } + void unreadChar(int c) { + ungetc(c); + } + std::string getName() { + return name; + } + int getLine() { + return line; + } + bool eof() { + if (NULL == file || feof(file))return true; + else { + int c = nextChar(); + if (c == EOFC || (c == EOF && feof(file)))return true; + unreadChar(c); + return false; + } + } + void close() { + if (NULL != file) { + fclose(file); + file = NULL; + } + } +}; +class BufferedFileInputStreamReader : public InputStreamReader { +private: + static const size_t BUFFER_SIZE; + static const size_t MAX_UNREAD_COUNT; + std::FILE* file; + char* buffer; + bool* isEof; + int bufferPos; + size_t bufferSize; + std::string name; + int line; + bool refill() { + if (NULL == file)__testlib_fail("BufferedFileInputStreamReader: file == NULL (" + getName() + ")"); + if (bufferPos >= int(bufferSize)) { + size_t readSize = fread( + buffer + MAX_UNREAD_COUNT, + 1, + BUFFER_SIZE - MAX_UNREAD_COUNT, + file); + if (readSize < BUFFER_SIZE - MAX_UNREAD_COUNT + && ferror(file))__testlib_fail("BufferedFileInputStreamReader: unable to read (" + getName() + ")"); + bufferSize = MAX_UNREAD_COUNT + readSize; + bufferPos = int(MAX_UNREAD_COUNT); + std::memset(isEof + MAX_UNREAD_COUNT, 0, sizeof(isEof[0]) * readSize); + return readSize > 0; + } else return true; + } + char increment() { + char c; + if ((c = buffer[bufferPos++]) == LF)line++; + return c; + } +public: + BufferedFileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name), line(1) { + buffer = new char[BUFFER_SIZE]; + isEof = new bool[BUFFER_SIZE]; + bufferSize = MAX_UNREAD_COUNT; + bufferPos = int(MAX_UNREAD_COUNT); + } + ~BufferedFileInputStreamReader() { + if (NULL != buffer) { + delete[] buffer; + buffer = NULL; + } + if (NULL != isEof) { + delete[] isEof; + isEof = NULL; + } + } + int curChar() { + if (!refill())return EOFC; + return isEof[bufferPos] ? EOFC : buffer[bufferPos]; + } + int nextChar() { + if (!refill())return EOFC; + return isEof[bufferPos] ? EOFC : increment(); + } + void skipChar() { + increment(); + } + void unreadChar(int c) { + bufferPos--; + if (bufferPos < 0)__testlib_fail("BufferedFileInputStreamReader::unreadChar(int): bufferPos < 0"); + isEof[bufferPos] = (c == EOFC); + buffer[bufferPos] = char(c); + if (c == LF)line--; + } + std::string getName() { + return name; + } + int getLine() { + return line; + } + bool eof() { + return !refill() || EOFC == curChar(); + } + void close() { + if (NULL != file) { + fclose(file); + file = NULL; + } + } +}; +const size_t BufferedFileInputStreamReader::BUFFER_SIZE = 2000000; +const size_t BufferedFileInputStreamReader::MAX_UNREAD_COUNT = BufferedFileInputStreamReader::BUFFER_SIZE / 2; +struct InStream { + InStream(); + ~InStream(); + InStream(const InStream& baseStream, std::string content); + InputStreamReader* reader; + int lastLine; + std::string name; + TMode mode; + bool opened; + bool stdfile; + bool strict; + int wordReserveSize; + std::string _tmpReadToken; + int readManyIteration; + size_t maxFileSize; + size_t maxTokenLength; + size_t maxMessageLength; + void init(std::string fileName, TMode mode); + void init(std::FILE* f, TMode mode); + void skipBlanks(); + char curChar(); + void skipChar(); + char nextChar(); + char readChar(); + char readChar(char c); + char readSpace(); + void unreadChar(char c); + void reset(std::FILE* file = NULL); + bool eof(); + bool seekEof(); + bool eoln(); + bool seekEoln(); + void nextLine(); + std::string readWord(); + std::string readToken(); + std::string readWord(const std::string& ptrn, const std::string& variableName = ""); + std::string readWord(const pattern& p, const std::string& variableName = ""); + std::vector readWords(int size, const std::string& ptrn, const std::string& variablesName = "", int indexBase = 1); + std::vector readWords(int size, const pattern& p, const std::string& variablesName = "", int indexBase = 1); + std::vector readWords(int size, int indexBase = 1); + std::string readToken(const std::string& ptrn, const std::string& variableName = ""); + std::string readToken(const pattern& p, const std::string& variableName = ""); + std::vector readTokens(int size, const std::string& ptrn, const std::string& variablesName = "", int indexBase = 1); + std::vector readTokens(int size, const pattern& p, const std::string& variablesName = "", int indexBase = 1); + std::vector readTokens(int size, int indexBase = 1); + void readWordTo(std::string& result); + void readWordTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readTokenTo(std::string& result); + void readTokenTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + long long readLong(); + unsigned long long readUnsignedLong(); + int readInteger(); + int readInt(); + long long readLong(long long minv, long long maxv, const std::string& variableName = ""); + std::vector readLongs(int size, long long minv, long long maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readLongs(int size, int indexBase = 1); + unsigned long long readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName = ""); + std::vector readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readUnsignedLongs(int size, int indexBase = 1); + unsigned long long readLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName = ""); + std::vector readLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string& variablesName = "", int indexBase = 1); + int readInteger(int minv, int maxv, const std::string& variableName = ""); + int readInt(int minv, int maxv, const std::string& variableName = ""); + std::vector readIntegers(int size, int minv, int maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readIntegers(int size, int indexBase = 1); + std::vector readInts(int size, int minv, int maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readInts(int size, int indexBase = 1); + double readReal(); + double readDouble(); + double readReal(double minv, double maxv, const std::string& variableName = ""); + std::vector readReals(int size, double minv, double maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readReals(int size, int indexBase = 1); + double readDouble(double minv, double maxv, const std::string& variableName = ""); + std::vector readDoubles(int size, double minv, double maxv, const std::string& variablesName = "", int indexBase = 1); + std::vector readDoubles(int size, int indexBase = 1); + double readStrictReal(double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variableName = ""); + std::vector readStrictReals(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variablesName = "", int indexBase = 1); + double readStrictDouble(double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variableName = ""); + std::vector readStrictDoubles(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variablesName = "", int indexBase = 1); + std::string readString(); + std::vector readStrings(int size, int indexBase = 1); + void readStringTo(std::string& result); + std::string readString(const pattern& p, const std::string& variableName = ""); + std::string readString(const std::string& ptrn, const std::string& variableName = ""); + std::vector readStrings(int size, const pattern& p, const std::string& variableName = "", int indexBase = 1); + std::vector readStrings(int size, const std::string& ptrn, const std::string& variableName = "", int indexBase = 1); + void readStringTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + std::string readLine(); + std::vector readLines(int size, int indexBase = 1); + void readLineTo(std::string& result); + std::string readLine(const pattern& p, const std::string& variableName = ""); + std::string readLine(const std::string& ptrn, const std::string& variableName = ""); + std::vector readLines(int size, const pattern& p, const std::string& variableName = "", int indexBase = 1); + std::vector readLines(int size, const std::string& ptrn, const std::string& variableName = "", int indexBase = 1); + void readLineTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readEoln(); + void readEof(); + NORETURN void quit(TResult result, const char* msg); + NORETURN void quitf(TResult result, const char* msg, ...); + void quitif(bool condition, TResult result, const char* msg, ...); + NORETURN void quits(TResult result, std::string msg); +#ifdef __GNUC__ + __attribute__((format(printf, 3, 4))) +#endif + void + ensuref(bool cond, const char* format, ...); + void __testlib_ensure(bool cond, std::string message); + void close(); + const static int NO_INDEX = INT_MAX; + const static char OPEN_BRACKET = char(11); + const static char CLOSE_BRACKET = char(17); + const static WORD LightGray = 0x07; + const static WORD LightRed = 0x0c; + const static WORD LightCyan = 0x0b; + const static WORD LightGreen = 0x0a; + const static WORD LightYellow = 0x0e; + static void textColor(WORD color); + static void quitscr(WORD color, const char* msg); + static void quitscrS(WORD color, std::string msg); + void xmlSafeWrite(std::FILE* file, const char* msg); + +private: + InStream(const InStream&); + InStream& operator=(const InStream&); +}; +InStream inf; +InStream ouf; +InStream ans; +bool appesMode; +std::string resultName; +std::string checkerName = "untitled checker"; +random_t rnd; +TTestlibMode testlibMode = _unknown; +double __testlib_points = std::numeric_limits::infinity(); +struct ValidatorBoundsHit { + static const double EPS; + bool minHit; + bool maxHit; + ValidatorBoundsHit(bool minHit = false, bool maxHit = false): minHit(minHit), maxHit(maxHit) {}; + ValidatorBoundsHit merge(const ValidatorBoundsHit& validatorBoundsHit) { + return ValidatorBoundsHit( + __testlib_max(minHit, validatorBoundsHit.minHit), + __testlib_max(maxHit, validatorBoundsHit.maxHit)); + } +}; +const double ValidatorBoundsHit::EPS = 1E-12; +class Validator { +private: + std::string _testset; + std::string _group; + std::string _testOverviewLogFileName; + std::map _boundsHitByVariableName; + std::set _features; + std::set _hitFeatures; + bool isVariableNameBoundsAnalyzable(const std::string& variableName) { + for (size_t i = 0; i < variableName.length(); i++)if ((variableName[i] >= '0' && variableName[i] <= '9') || variableName[i] < ' ')return false; + return true; + } + bool isFeatureNameAnalyzable(const std::string& featureName) { + for (size_t i = 0; i < featureName.length(); i++)if (featureName[i] < ' ')return false; + return true; + } + +public: + Validator(): _testset("tests"), _group() { + } + std::string testset() const { + return _testset; + } + std::string group() const { + return _group; + } + std::string testOverviewLogFileName() const { + return _testOverviewLogFileName; + } + void setTestset(const char* const testset) { + _testset = testset; + } + void setGroup(const char* const group) { + _group = group; + } + void setTestOverviewLogFileName(const char* const testOverviewLogFileName) { + _testOverviewLogFileName = testOverviewLogFileName; + } + void addBoundsHit(const std::string& variableName, ValidatorBoundsHit boundsHit) { + if (isVariableNameBoundsAnalyzable(variableName)) { + _boundsHitByVariableName[variableName] + = boundsHit.merge(_boundsHitByVariableName[variableName]); + } + } + std::string getBoundsHitLog() { + std::string result; + for (std::map::iterator i = _boundsHitByVariableName.begin(); + i != _boundsHitByVariableName.end(); + i++) { + result += "\"" + i->first + "\":"; + if (i->second.minHit)result += " min-value-hit"; + if (i->second.maxHit)result += " max-value-hit"; + result += "\n"; + } + return result; + } + std::string getFeaturesLog() { + std::string result; + for (std::set::iterator i = _features.begin(); + i != _features.end(); + i++) { + result += "feature \"" + *i + "\":"; + if (_hitFeatures.count(*i))result += " hit"; + result += "\n"; + } + return result; + } + void writeTestOverviewLog() { + if (!_testOverviewLogFileName.empty()) { + std::string fileName(_testOverviewLogFileName); + _testOverviewLogFileName = ""; + FILE* testOverviewLogFile = fopen(fileName.c_str(), "w"); + if (NULL == testOverviewLogFile)__testlib_fail("Validator::writeTestOverviewLog: can't test overview log to (" + fileName + ")"); + fprintf(testOverviewLogFile, "%s%s", getBoundsHitLog().c_str(), getFeaturesLog().c_str()); + if (fclose(testOverviewLogFile))__testlib_fail("Validator::writeTestOverviewLog: can't close test overview log file (" + fileName + ")"); + } + } + void addFeature(const std::string& feature) { + if (_features.count(feature))__testlib_fail("Feature " + feature + " registered twice."); + if (!isFeatureNameAnalyzable(feature))__testlib_fail("Feature name '" + feature + "' contains restricted characters."); + _features.insert(feature); + } + void feature(const std::string& feature) { + if (!isFeatureNameAnalyzable(feature))__testlib_fail("Feature name '" + feature + "' contains restricted characters."); + if (!_features.count(feature))__testlib_fail("Feature " + feature + " didn't registered via addFeature(feature)."); + _hitFeatures.insert(feature); + } +} validator; +struct TestlibFinalizeGuard { + static bool alive; + int quitCount, readEofCount; + TestlibFinalizeGuard(): quitCount(0), readEofCount(0) { + } + ~TestlibFinalizeGuard() { + bool _alive = alive; + alive = false; + if (_alive) { + if (testlibMode == _checker && quitCount == 0)__testlib_fail("Checker must end with quit or quitf call."); + if (testlibMode == _validator && readEofCount == 0 && quitCount == 0)__testlib_fail("Validator must end with readEof call."); + } + validator.writeTestOverviewLog(); + } +}; +bool TestlibFinalizeGuard::alive = true; +TestlibFinalizeGuard testlibFinalizeGuard; +void disableFinalizeGuard() { + TestlibFinalizeGuard::alive = false; +} +std::fstream tout; +#if __cplusplus > 199711L || defined(_MSC_VER) +template +static std::string vtos(const T& t, std::true_type) { + if (t == 0)return "0"; + else { + T n(t); + bool negative = n < 0; + std::string s; + while (n != 0) { + T digit = n % 10; + if (digit < 0)digit = -digit; + s += char('0' + digit); + n /= 10; + } + std::reverse(s.begin(), s.end()); + return negative ? "-" + s : s; + } +} +template +static std::string vtos(const T& t, std::false_type) { + std::string s; + static std::stringstream ss; + ss.str(std::string()); + ss.clear(); + ss << t; + ss >> s; + return s; +} +template +static std::string vtos(const T& t) { + return vtos(t, std::is_integral()); +} +#else +template +static std::string vtos(const T& t) { + std::string s; + static std::stringstream ss; + ss.str(std::string()); + ss.clear(); + ss << t; + ss >> s; + return s; +} +#endif +template +static std::string toString(const T& t) { + return vtos(t); +} +InStream::InStream() { + reader = NULL; + lastLine = -1; + name = ""; + mode = _input; + strict = false; + stdfile = false; + wordReserveSize = 4; + readManyIteration = NO_INDEX; + maxFileSize = 128 * 1024 * 1024; + maxTokenLength = 32 * 1024 * 1024; + maxMessageLength = 32000; +} +InStream::InStream(const InStream& baseStream, std::string content) { + reader = new StringInputStreamReader(content); + lastLine = -1; + opened = true; + strict = baseStream.strict; + mode = baseStream.mode; + name = "based on " + baseStream.name; + readManyIteration = NO_INDEX; + maxFileSize = 128 * 1024 * 1024; + maxTokenLength = 32 * 1024 * 1024; + maxMessageLength = 32000; +} +InStream::~InStream() { + if (NULL != reader) { + reader->close(); + delete reader; + reader = NULL; + } +} +#ifdef __GNUC__ +__attribute__((const)) +#endif +int +resultExitCode(TResult r) { + if (r == _ok)return OK_EXIT_CODE; + if (r == _wa)return WA_EXIT_CODE; + if (r == _pe)return PE_EXIT_CODE; + if (r == _fail)return FAIL_EXIT_CODE; + if (r == _dirt)return DIRT_EXIT_CODE; + if (r == _points)return POINTS_EXIT_CODE; + if (r == _unexpected_eof) +#ifdef ENABLE_UNEXPECTED_EOF + return UNEXPECTED_EOF_EXIT_CODE; +#else return PE_EXIT_CODE; +#endif + if (r >= _partially)return PC_BASE_EXIT_CODE + (r - _partially); + return FAIL_EXIT_CODE; +} +void InStream::textColor( +#if !(defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER > 1400)) && defined(__GNUC__)__attribute__((unused)) +#endif + WORD color) { +#if defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER > 1400)HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(handle, color); +#endif +#if !defined(ON_WINDOWS) && defined(__GNUC__)if (isatty(2)) { + switch (color) { + case LightRed: + fprintf(stderr, "\033[1;31m"); + break; + case LightCyan: + fprintf(stderr, "\033[1;36m"); + break; + case LightGreen: + fprintf(stderr, "\033[1;32m"); + break; + case LightYellow: + fprintf(stderr, "\033[1;33m"); + break; + case LightGray: + default: + fprintf(stderr, "\033[0m"); + } + } +#endif +} +NORETURN void halt(int exitCode) { +#ifdef FOOTER + InStream::textColor(InStream::LightGray); + std::fprintf(stderr, "Checker: \"%s\"\n", checkerName.c_str()); + std::fprintf(stderr, "Exit code: %d\n", exitCode); + InStream::textColor(InStream::LightGray); +#endif + std::exit(exitCode); +} +static bool __testlib_shouldCheckDirt(TResult result) { + return result == _ok || result == _points || result >= _partially; +} +static std::string __testlib_appendMessage(const std::string& message, const std::string& extra) { + int openPos = -1, closePos = -1; + for (size_t i = 0; i < message.length(); i++) { + if (message[i] == InStream::OPEN_BRACKET) { + if (openPos == -1)openPos = i; + else openPos = INT_MAX; + } + if (message[i] == InStream::CLOSE_BRACKET) { + if (closePos == -1)closePos = i; + else closePos = INT_MAX; + } + } + if (openPos != -1 && openPos != INT_MAX + && closePos != -1 && closePos != INT_MAX + && openPos < closePos) { + size_t index = message.find(extra, openPos); + if (index == std::string::npos || int(index) >= closePos) { + std::string result(message); + result.insert(closePos, ", " + extra); + return result; + } + return message; + } + return message + " " + InStream::OPEN_BRACKET + extra + InStream::CLOSE_BRACKET; +} +static std::string __testlib_toPrintableMessage(const std::string& message) { + int openPos = -1, closePos = -1; + for (size_t i = 0; i < message.length(); i++) { + if (message[i] == InStream::OPEN_BRACKET) { + if (openPos == -1)openPos = i; + else openPos = INT_MAX; + } + if (message[i] == InStream::CLOSE_BRACKET) { + if (closePos == -1)closePos = i; + else closePos = INT_MAX; + } + } + if (openPos != -1 && openPos != INT_MAX + && closePos != -1 && closePos != INT_MAX + && openPos < closePos) { + std::string result(message); + result[openPos] = '('; + result[closePos] = ')'; + return result; + } + return message; +} +NORETURN void InStream::quit(TResult result, const char* msg) { + if (TestlibFinalizeGuard::alive)testlibFinalizeGuard.quitCount++; + std::string message(msg); + message = trim(message); + if (__testlib_hasTestCase) { + if (result != _ok)message = __testlib_appendMessage(message, "test case " + vtos(__testlib_testCase)); + else { + if (__testlib_testCase == 1)message = __testlib_appendMessage(message, vtos(__testlib_testCase) + " test case"); + else message = __testlib_appendMessage(message, vtos(__testlib_testCase) + " test cases"); + } + } + if (message.length() > maxMessageLength) { + std::string warn = "message length exceeds " + vtos(maxMessageLength)+ ", the message is truncated: "; + message = warn + message.substr(0, maxMessageLength - warn.length()); + } +#ifndef ENABLE_UNEXPECTED_EOF + if (result == _unexpected_eof)result = _pe; +#endif + if (mode != _output && result != _fail) { + if (mode == _input && testlibMode == _validator && lastLine != -1)quits(_fail, __testlib_appendMessage(__testlib_appendMessage(message, name), "line " + vtos(lastLine))); + else quits(_fail, __testlib_appendMessage(message, name)); + } + std::FILE* resultFile; + std::string errorName; + if (__testlib_shouldCheckDirt(result)) { + if (testlibMode != _interactor && !ouf.seekEof())quit(_dirt, "Extra information in the output file"); + } + int pctype = result - _partially; + bool isPartial = false; + switch (result) { + case _ok: + errorName = "ok "; + quitscrS(LightGreen, errorName); + break; + case _wa: + errorName = "wrong answer "; + quitscrS(LightRed, errorName); + break; + case _pe: + errorName = "wrong output format "; + quitscrS(LightRed, errorName); + break; + case _fail: + errorName = "FAIL "; + quitscrS(LightRed, errorName); + break; + case _dirt: + errorName = "wrong output format "; + quitscrS(LightCyan, errorName); + result = _pe; + break; + case _points: + errorName = "points "; + quitscrS(LightYellow, errorName); + break; + case _unexpected_eof: + errorName = "unexpected eof "; + quitscrS(LightCyan, errorName); + break; + default: + if (result >= _partially) { + errorName = format("partially correct (%d) ", pctype); + isPartial = true; + quitscrS(LightYellow, errorName); + } else quit(_fail, "What is the code ??? "); + } + if (resultName != "") { + resultFile = std::fopen(resultName.c_str(), "w"); + if (resultFile == NULL)quit(_fail, "Can not write to the result file"); + if (appesMode) { + std::fprintf(resultFile, ""); + if (isPartial)std::fprintf(resultFile, "", outcomes[(int)_partially].c_str(), pctype); + else { + if (result != _points)std::fprintf(resultFile, "", outcomes[(int)result].c_str()); + else { + if (__testlib_points == std::numeric_limits::infinity())quit(_fail, "Expected points, but infinity found"); + std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", __testlib_points)); + std::fprintf(resultFile, "", outcomes[(int)result].c_str(), stringPoints.c_str()); + } + } + xmlSafeWrite(resultFile, __testlib_toPrintableMessage(message).c_str()); + std::fprintf(resultFile, "\n"); + } else std::fprintf(resultFile, "%s", __testlib_toPrintableMessage(message).c_str()); + if (NULL == resultFile || fclose(resultFile) != 0)quit(_fail, "Can not write to the result file"); + } + quitscr(LightGray, __testlib_toPrintableMessage(message).c_str()); + std::fprintf(stderr, "\n"); + inf.close(); + ouf.close(); + ans.close(); + if (tout.is_open())tout.close(); + textColor(LightGray); + if (resultName != "")std::fprintf(stderr, "See file to check exit message\n"); + halt(resultExitCode(result)); +} +#ifdef __GNUC__ +__attribute__((format(printf, 3, 4))) +#endif +NORETURN void +InStream::quitf(TResult result, const char* msg, ...) { + FMT_TO_RESULT(msg, msg, message); + InStream::quit(result, message.c_str()); +} +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +void +InStream::quitif(bool condition, TResult result, const char* msg, ...) { + if (condition) { + FMT_TO_RESULT(msg, msg, message); + InStream::quit(result, message.c_str()); + } +} +NORETURN void InStream::quits(TResult result, std::string msg) { + InStream::quit(result, msg.c_str()); +} +void InStream::xmlSafeWrite(std::FILE* file, const char* msg) { + size_t lmsg = strlen(msg); + for (size_t i = 0; i < lmsg; i++) { + if (msg[i] == '&') { + std::fprintf(file, "%s", "&"); + continue; + } + if (msg[i] == '<') { + std::fprintf(file, "%s", "<"); + continue; + } + if (msg[i] == '>') { + std::fprintf(file, "%s", ">"); + continue; + } + if (msg[i] == '"') { + std::fprintf(file, "%s", """); + continue; + } + if (0 <= msg[i] && msg[i] <= 31) { + std::fprintf(file, "%c", '.'); + continue; + } + std::fprintf(file, "%c", msg[i]); + } +} +void InStream::quitscrS(WORD color, std::string msg) { + quitscr(color, msg.c_str()); +} +void InStream::quitscr(WORD color, const char* msg) { + if (resultName == "") { + textColor(color); + std::fprintf(stderr, "%s", msg); + textColor(LightGray); + } +} +void InStream::reset(std::FILE* file) { + if (opened && stdfile)quit(_fail, "Can't reset standard handle"); + if (opened)close(); + if (!stdfile)if (NULL == (file = std::fopen(name.c_str(), "rb"))) { + if (mode == _output)quits(_pe, std::string("Output file not found: \"") + name + "\""); + if (mode == _answer)quits(_fail, std::string("Answer file not found: \"") + name + "\""); + } + if (NULL != file) { + opened = true; + __testlib_set_binary(file); + if (stdfile)reader = new FileInputStreamReader(file, name); + else reader = new BufferedFileInputStreamReader(file, name); + } else { + opened = false; + reader = NULL; + } +} +void InStream::init(std::string fileName, TMode mode) { + opened = false; + name = fileName; + stdfile = false; + this->mode = mode; + std::ifstream stream; + stream.open(fileName.c_str(), std::ios::in); + if (stream.is_open()) { + std::streampos start = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streampos end = stream.tellg(); + size_t fileSize = size_t(end - start); + stream.close(); + if (fileSize > maxFileSize)quitf(_pe, "File size exceeds %d bytes, size is %d", int(maxFileSize), int(fileSize)); + } + reset(); +} +void InStream::init(std::FILE* f, TMode mode) { + opened = false; + name = "untitled"; + this->mode = mode; + if (f == stdin)name = "stdin", stdfile = true; + if (f == stdout)name = "stdout", stdfile = true; + if (f == stderr)name = "stderr", stdfile = true; + reset(f); +} +char InStream::curChar() { + return char(reader->curChar()); +} +char InStream::nextChar() { + return char(reader->nextChar()); +} +char InStream::readChar() { + return nextChar(); +} +char InStream::readChar(char c) { + lastLine = reader->getLine(); + char found = readChar(); + if (c != found) { + if (!isEoln(found))quit(_pe, ("Unexpected character '" + std::string(1, found) + "', but '" + std::string(1, c) + "' expected").c_str()); + else quit(_pe, ("Unexpected character " + ("#" + vtos(int(found))) + ", but '" + std::string(1, c) + "' expected").c_str()); + } + return found; +} +char InStream::readSpace() { + return readChar(' '); +} +void InStream::unreadChar(char c) { + reader->unreadChar(c); +} +void InStream::skipChar() { + reader->skipChar(); +} +void InStream::skipBlanks() { + while (isBlanks(reader->curChar()))reader->skipChar(); +} +std::string InStream::readWord() { + readWordTo(_tmpReadToken); + return _tmpReadToken; +} +void InStream::readWordTo(std::string& result) { + if (!strict)skipBlanks(); + lastLine = reader->getLine(); + int cur = reader->nextChar(); + if (cur == EOFC)quit(_unexpected_eof, "Unexpected end of file - token expected"); + if (isBlanks(cur))quit(_pe, "Unexpected white-space - token expected"); + result.clear(); + while (!(isBlanks(cur) || cur == EOFC)) { + result += char(cur); + if (result.length() > maxTokenLength)quitf(_pe, "Length of token exceeds %d, token is '%s...'", int(maxTokenLength), __testlib_part(result).c_str()); + cur = reader->nextChar(); + } + reader->unreadChar(cur); + if (result.length() == 0)quit(_unexpected_eof, "Unexpected end of file or white-space - token expected"); +} +std::string InStream::readToken() { + return readWord(); +} +void InStream::readTokenTo(std::string& result) { + readWordTo(result); +} +static std::string __testlib_part(const std::string& s) { + if (s.length() <= 64)return s; + else return s.substr(0, 30) + "..." + s.substr(s.length() - 31, 31); +} +#define __testlib_readMany(readMany, readOne, typeName, space) \ + if (size < 0) \ + quit(_fail, #readMany ": size should be non-negative."); \ + if (size > 100000000) \ + quit(_fail, #readMany ": size should be at most 100000000."); \ + \ + std::vector result(size); \ + readManyIteration = indexBase; \ + \ + for (int i = 0; i < size; i++) { \ + result[i] = readOne; \ + readManyIteration++; \ + if (strict && space && i + 1 < size) \ + readSpace(); \ + } \ + \ + readManyIteration = NO_INDEX; \ + return result; +std::string InStream::readWord(const pattern& p, const std::string& variableName) { + readWordTo(_tmpReadToken); + if (!p.matches(_tmpReadToken)) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Token \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + else quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Token element [index=" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + else quit(_wa, ("Token element " + variableName + "[" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } + } + return _tmpReadToken; +} +std::vector InStream::readWords(int size, const pattern& p, const std::string& variablesName, int indexBase) { + __testlib_readMany(readWords, readWord(p, variablesName), std::string, true); +} +std::vector InStream::readWords(int size, int indexBase) { + __testlib_readMany(readWords, readWord(), std::string, true); +} +std::string InStream::readWord(const std::string& ptrn, const std::string& variableName) { + return readWord(pattern(ptrn), variableName); +} +std::vector InStream::readWords(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) { + pattern p(ptrn); + __testlib_readMany(readWords, readWord(p, variablesName), std::string, true); +} +std::string InStream::readToken(const pattern& p, const std::string& variableName) { + return readWord(p, variableName); +} +std::vector InStream::readTokens(int size, const pattern& p, const std::string& variablesName, int indexBase) { + __testlib_readMany(readTokens, readToken(p, variablesName), std::string, true); +} +std::vector InStream::readTokens(int size, int indexBase) { + __testlib_readMany(readTokens, readToken(), std::string, true); +} +std::string InStream::readToken(const std::string& ptrn, const std::string& variableName) { + return readWord(ptrn, variableName); +} +std::vector InStream::readTokens(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) { + pattern p(ptrn); + __testlib_readMany(readTokens, readWord(p, variablesName), std::string, true); +} +void InStream::readWordTo(std::string& result, const pattern& p, const std::string& variableName) { + readWordTo(result); + if (!p.matches(result)) { + if (variableName.empty())quit(_wa, ("Token \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + else quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } +} +void InStream::readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName) { + return readWordTo(result, pattern(ptrn), variableName); +} +void InStream::readTokenTo(std::string& result, const pattern& p, const std::string& variableName) { + return readWordTo(result, p, variableName); +} +void InStream::readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName) { + return readWordTo(result, ptrn, variableName); +} +#ifdef __GNUC__ +__attribute__((pure)) +#endif +static inline bool +equals(long long integer, const char* s) { + if (integer == LLONG_MIN)return strcmp(s, "-9223372036854775808") == 0; + if (integer == 0LL)return strcmp(s, "0") == 0; + size_t length = strlen(s); + if (length == 0)return false; + if (integer < 0 && s[0] != '-')return false; + if (integer < 0)s++, length--, integer = -integer; + if (length == 0)return false; + while (integer > 0) { + int digit = int(integer % 10); + if (s[length - 1] != '0' + digit)return false; + length--; + integer /= 10; + } + return length == 0; +} +#ifdef __GNUC__ +__attribute__((pure)) +#endif +static inline bool +equals(unsigned long long integer, const char* s) { + if (integer == ULLONG_MAX)return strcmp(s, "18446744073709551615") == 0; + if (integer == 0ULL)return strcmp(s, "0") == 0; + size_t length = strlen(s); + if (length == 0)return false; + while (integer > 0) { + int digit = int(integer % 10); + if (s[length - 1] != '0' + digit)return false; + length--; + integer /= 10; + } + return length == 0; +} +static inline double stringToDouble(InStream& in, const char* buffer) { + double retval; + size_t length = strlen(buffer); + int minusCount = 0; + int plusCount = 0; + int decimalPointCount = 0; + int digitCount = 0; + int eCount = 0; + for (size_t i = 0; i < length; i++) { + if (('0' <= buffer[i] && buffer[i] <= '9') || buffer[i] == '.' + || buffer[i] == 'e' || buffer[i] == 'E' + || buffer[i] == '-' || buffer[i] == '+') { + if ('0' <= buffer[i] && buffer[i] <= '9')digitCount++; + if (buffer[i] == 'e' || buffer[i] == 'E')eCount++; + if (buffer[i] == '-')minusCount++; + if (buffer[i] == '+')plusCount++; + if (buffer[i] == '.')decimalPointCount++; + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } + if (digitCount == 0 || minusCount > 2 || plusCount > 2 || decimalPointCount > 1 || eCount > 1)in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + char* suffix = new char[length + 1]; + int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); + bool empty = strlen(suffix) == 0; + delete[] suffix; + if (scanned == 1 || (scanned == 2 && empty)) { + if (__testlib_isNaN(retval))in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + return retval; + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); +} +static inline double stringToStrictDouble(InStream& in, const char* buffer, int minAfterPointDigitCount, int maxAfterPointDigitCount) { + if (minAfterPointDigitCount < 0)in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be non-negative."); + if (minAfterPointDigitCount > maxAfterPointDigitCount)in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be less or equal to maxAfterPointDigitCount."); + double retval; + size_t length = strlen(buffer); + if (length == 0 || length > 1000)in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if (buffer[0] != '-' && (buffer[0] < '0' || buffer[0] > '9'))in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + int pointPos = -1; + for (size_t i = 1; i + 1 < length; i++) { + if (buffer[i] == '.') { + if (pointPos > -1)in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + pointPos = int(i); + } + if (buffer[i] != '.' && (buffer[i] < '0' || buffer[i] > '9'))in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } + if (buffer[length - 1] < '0' || buffer[length - 1] > '9')in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + int afterDigitsCount = (pointPos == -1 ? 0 : int(length) - pointPos - 1); + if (afterDigitsCount < minAfterPointDigitCount || afterDigitsCount > maxAfterPointDigitCount)in.quit(_pe, ("Expected strict double with number of digits after point in range [" + vtos(minAfterPointDigitCount) + "," + vtos(maxAfterPointDigitCount) + "], but \"" + __testlib_part(buffer) + "\" found").c_str()); + int firstDigitPos = -1; + for (size_t i = 0; i < length; i++)if (buffer[i] >= '0' && buffer[i] <= '9') { + firstDigitPos = int(i); + break; + } + if (firstDigitPos > 1 || firstDigitPos == -1)in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if (buffer[firstDigitPos] == '0' && firstDigitPos + 1 < int(length)&& buffer[firstDigitPos + 1] >= '0' && buffer[firstDigitPos + 1] <= '9')in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + char* suffix = new char[length + 1]; + int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); + bool empty = strlen(suffix) == 0; + delete[] suffix; + if (scanned == 1 || (scanned == 2 && empty)) { + if (__testlib_isNaN(retval) || __testlib_isInfinite(retval))in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if (buffer[0] == '-' && retval >= 0)in.quit(_pe, ("Redundant minus in \"" + __testlib_part(buffer) + "\" found").c_str()); + return retval; + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); +} +static inline long long stringToLongLong(InStream& in, const char* buffer) { + if (strcmp(buffer, "-9223372036854775808") == 0)return LLONG_MIN; + bool minus = false; + size_t length = strlen(buffer); + if (length > 1 && buffer[0] == '-')minus = true; + if (length > 20)in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + long long retval = 0LL; + int zeroes = 0; + int processingZeroes = true; + for (int i = (minus ? 1 : 0); i < int(length); i++) { + if (buffer[i] == '0' && processingZeroes)zeroes++; + else processingZeroes = false; + if (buffer[i] < '0' || buffer[i] > '9')in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + retval = retval * 10 + (buffer[i] - '0'); + } + if (retval < 0)in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if ((zeroes > 0 && (retval != 0 || minus)) || zeroes > 1)in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + retval = (minus ? -retval : +retval); + if (length < 19)return retval; + if (equals(retval, buffer))return retval; + else in.quit(_pe, ("Expected int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); +} +static inline unsigned long long stringToUnsignedLongLong(InStream& in, const char* buffer) { + size_t length = strlen(buffer); + if (length > 20)in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if (length > 1 && buffer[0] == '0')in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + unsigned long long retval = 0LL; + for (int i = 0; i < int(length); i++) { + if (buffer[i] < '0' || buffer[i] > '9')in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + retval = retval * 10 + (buffer[i] - '0'); + } + if (length < 19)return retval; + if (length == 20 && strcmp(buffer, "18446744073709551615") == 1)in.quit(_pe, ("Expected unsigned int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if (equals(retval, buffer))return retval; + else in.quit(_pe, ("Expected unsigned int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); +} +int InStream::readInteger() { + if (!strict && seekEof())quit(_unexpected_eof, "Unexpected end of file - int32 expected"); + readWordTo(_tmpReadToken); + long long value = stringToLongLong(*this, _tmpReadToken.c_str()); + if (value < INT_MIN || value > INT_MAX)quit(_pe, ("Expected int32, but \"" + __testlib_part(_tmpReadToken) + "\" found").c_str()); + return int(value); +} +long long InStream::readLong() { + if (!strict && seekEof())quit(_unexpected_eof, "Unexpected end of file - int64 expected"); + readWordTo(_tmpReadToken); + return stringToLongLong(*this, _tmpReadToken.c_str()); +} +unsigned long long InStream::readUnsignedLong() { + if (!strict && seekEof())quit(_unexpected_eof, "Unexpected end of file - int64 expected"); + readWordTo(_tmpReadToken); + return stringToUnsignedLongLong(*this, _tmpReadToken.c_str()); +} +long long InStream::readLong(long long minv, long long maxv, const std::string& variableName) { + long long result = readLong(); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } + } + if (strict && !variableName.empty())validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + return result; +} +std::vector InStream::readLongs(int size, long long minv, long long maxv, const std::string& variablesName, int indexBase) { + __testlib_readMany(readLongs, readLong(minv, maxv, variablesName), long long, true) +} std::vector InStream::readLongs(int size, int indexBase) { + __testlib_readMany(readLongs, readLong(), long long, true) +} +unsigned long long InStream::readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName) { + unsigned long long result = readUnsignedLong(); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Unsigned integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Unsigned integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Unsigned integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Unsigned integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } + } + if (strict && !variableName.empty())validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + return result; +} +std::vector InStream::readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string& variablesName, int indexBase) { + __testlib_readMany(readUnsignedLongs, readUnsignedLong(minv, maxv, variablesName), unsigned long long, true) +} std::vector InStream::readUnsignedLongs(int size, int indexBase) { + __testlib_readMany(readUnsignedLongs, readUnsignedLong(), unsigned long long, true) +} +unsigned long long InStream::readLong(unsigned long long minv, unsigned long long maxv, const std::string& variableName) { + return readUnsignedLong(minv, maxv, variableName); +} +int InStream::readInt() { + return readInteger(); +} +int InStream::readInt(int minv, int maxv, const std::string& variableName) { + int result = readInt(); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } + } + if (strict && !variableName.empty())validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + return result; +} +int InStream::readInteger(int minv, int maxv, const std::string& variableName) { + return readInt(minv, maxv, variableName); +} +std::vector InStream::readInts(int size, int minv, int maxv, const std::string& variablesName, int indexBase) { + __testlib_readMany(readInts, readInt(minv, maxv, variablesName), int, true) +} std::vector InStream::readInts(int size, int indexBase) { + __testlib_readMany(readInts, readInt(), int, true) +} std::vector InStream::readIntegers(int size, int minv, int maxv, const std::string& variablesName, int indexBase) { + __testlib_readMany(readIntegers, readInt(minv, maxv, variablesName), int, true) +} std::vector InStream::readIntegers(int size, int indexBase) { + __testlib_readMany(readIntegers, readInt(), int, true) +} +double InStream::readReal() { + if (!strict && seekEof())quit(_unexpected_eof, "Unexpected end of file - double expected"); + return stringToDouble(*this, readWord().c_str()); +} +double InStream::readDouble() { + return readReal(); +} +double InStream::readReal(double minv, double maxv, const std::string& variableName) { + double result = readReal(); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Double element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } + } + if (strict && !variableName.empty())validator.addBoundsHit(variableName, ValidatorBoundsHit(doubleDelta(minv, result) < ValidatorBoundsHit::EPS, doubleDelta(maxv, result) < ValidatorBoundsHit::EPS)); + return result; +} +std::vector InStream::readReals(int size, double minv, double maxv, const std::string& variablesName, int indexBase) { + __testlib_readMany(readReals, readReal(minv, maxv, variablesName), double, true) +} std::vector InStream::readReals(int size, int indexBase) { + __testlib_readMany(readReals, readReal(), double, true) +} +double InStream::readDouble(double minv, double maxv, const std::string& variableName) { + return readReal(minv, maxv, variableName); +} +std::vector InStream::readDoubles(int size, double minv, double maxv, const std::string& variablesName, int indexBase) { + __testlib_readMany(readDoubles, readDouble(minv, maxv, variablesName), double, true) +} std::vector InStream::readDoubles(int size, int indexBase) { + __testlib_readMany(readDoubles, readDouble(), double, true) +} +double InStream::readStrictReal(double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variableName) { + if (!strict && seekEof())quit(_unexpected_eof, "Unexpected end of file - strict double expected"); + double result = stringToStrictDouble(*this, readWord().c_str(), + minAfterPointDigitCount, maxAfterPointDigitCount); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Strict double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Strict double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Strict double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else quit(_wa, ("Strict double element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } + } + if (strict && !variableName.empty())validator.addBoundsHit(variableName, ValidatorBoundsHit(doubleDelta(minv, result) < ValidatorBoundsHit::EPS, doubleDelta(maxv, result) < ValidatorBoundsHit::EPS)); + return result; +} +std::vector InStream::readStrictReals(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variablesName, int indexBase) { + __testlib_readMany(readStrictReals, readStrictReal(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), double, true) +} +double InStream::readStrictDouble(double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variableName) { + return readStrictReal(minv, maxv, + minAfterPointDigitCount, maxAfterPointDigitCount, + variableName); +} +std::vector InStream::readStrictDoubles(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string& variablesName, int indexBase) { + __testlib_readMany(readStrictDoubles, readStrictDouble(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), double, true) +} +bool InStream::eof() { + if (!strict && NULL == reader)return true; + return reader->eof(); +} +bool InStream::seekEof() { + if (!strict && NULL == reader)return true; + skipBlanks(); + return eof(); +} +bool InStream::eoln() { + if (!strict && NULL == reader)return true; + int c = reader->nextChar(); + if (!strict) { + if (c == EOFC)return true; + if (c == CR) { + c = reader->nextChar(); + if (c != LF) { + reader->unreadChar(c); + reader->unreadChar(CR); + return false; + } else return true; + } + if (c == LF)return true; + reader->unreadChar(c); + return false; + } else { + bool returnCr = false; +#if (defined(ON_WINDOWS) && !defined(FOR_LINUX)) || defined(FOR_WINDOWS)if (c != CR) { + reader->unreadChar(c); + return false; + } else { + if (!returnCr)returnCr = true; + c = reader->nextChar(); + } +#endif + if (c != LF) { + reader->unreadChar(c); + if (returnCr)reader->unreadChar(CR); + return false; + } + return true; + } +} +void InStream::readEoln() { + lastLine = reader->getLine(); + if (!eoln())quit(_pe, "Expected EOLN"); +} +void InStream::readEof() { + lastLine = reader->getLine(); + if (!eof())quit(_pe, "Expected EOF"); + if (TestlibFinalizeGuard::alive && this == &inf)testlibFinalizeGuard.readEofCount++; +} +bool InStream::seekEoln() { + if (!strict && NULL == reader)return true; + int cur; + do { + cur = reader->nextChar(); + } while (cur == SPACE || cur == TAB); + reader->unreadChar(cur); + return eoln(); +} +void InStream::nextLine() { + readLine(); +} +void InStream::readStringTo(std::string& result) { + if (NULL == reader)quit(_pe, "Expected line"); + result.clear(); + for (;;) { + int cur = reader->curChar(); + if (cur == LF || cur == EOFC)break; + if (cur == CR) { + cur = reader->nextChar(); + if (reader->curChar() == LF) { + reader->unreadChar(cur); + break; + } + } + lastLine = reader->getLine(); + result += char(reader->nextChar()); + } + if (strict)readEoln(); + else eoln(); +} +std::string InStream::readString() { + readStringTo(_tmpReadToken); + return _tmpReadToken; +} +std::vector InStream::readStrings(int size, int indexBase) { + __testlib_readMany(readStrings, readString(), std::string, false) +} +void InStream::readStringTo(std::string& result, const pattern& p, const std::string& variableName) { + readStringTo(result); + if (!p.matches(result)) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty())quit(_wa, ("Line \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + else quit(_wa, ("Line [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } else { + if (variableName.empty())quit(_wa, ("Line element [index=" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + else quit(_wa, ("Line element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } + } +} +void InStream::readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName) { + readStringTo(result, pattern(ptrn), variableName); +} +std::string InStream::readString(const pattern& p, const std::string& variableName) { + readStringTo(_tmpReadToken, p, variableName); + return _tmpReadToken; +} +std::vector InStream::readStrings(int size, const pattern& p, const std::string& variablesName, int indexBase) { + __testlib_readMany(readStrings, readString(p, variablesName), std::string, false) +} std::string InStream::readString(const std::string& ptrn, const std::string& variableName) { + readStringTo(_tmpReadToken, ptrn, variableName); + return _tmpReadToken; +} +std::vector InStream::readStrings(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) { + pattern p(ptrn); + __testlib_readMany(readStrings, readString(p, variablesName), std::string, false) +} +void InStream::readLineTo(std::string& result) { + readStringTo(result); +} +std::string InStream::readLine() { + return readString(); +} +std::vector InStream::readLines(int size, int indexBase) { + __testlib_readMany(readLines, readString(), std::string, false) +} +void InStream::readLineTo(std::string& result, const pattern& p, const std::string& variableName) { + readStringTo(result, p, variableName); +} +void InStream::readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName) { + readStringTo(result, ptrn, variableName); +} +std::string InStream::readLine(const pattern& p, const std::string& variableName) { + return readString(p, variableName); +} +std::vector InStream::readLines(int size, const pattern& p, const std::string& variablesName, int indexBase) { + __testlib_readMany(readLines, readString(p, variablesName), std::string, false) +} std::string InStream::readLine(const std::string& ptrn, const std::string& variableName) { + return readString(ptrn, variableName); +} +std::vector InStream::readLines(int size, const std::string& ptrn, const std::string& variablesName, int indexBase) { + pattern p(ptrn); + __testlib_readMany(readLines, readString(p, variablesName), std::string, false) +} +#ifdef __GNUC__ +__attribute__((format(printf, 3, 4))) +#endif +void +InStream::ensuref(bool cond, const char* format, ...) { + if (!cond) { + FMT_TO_RESULT(format, format, message); + this->__testlib_ensure(cond, message); + } +} +void InStream::__testlib_ensure(bool cond, std::string message) { + if (!cond)this->quit(_wa, message.c_str()); +} +void InStream::close() { + if (NULL != reader) { + reader->close(); + delete reader; + reader = NULL; + } + opened = false; +} +NORETURN void quit(TResult result, const std::string& msg) { + ouf.quit(result, msg.c_str()); +} +NORETURN void quit(TResult result, const char* msg) { + ouf.quit(result, msg); +} +NORETURN void __testlib_quitp(double points, const char* message) { + __testlib_points = points; + std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", points)); + std::string quitMessage; + if (NULL == message || 0 == strlen(message))quitMessage = stringPoints; + else quitMessage = stringPoints + " " + message; + quit(_points, quitMessage.c_str()); +} +NORETURN void __testlib_quitp(int points, const char* message) { + __testlib_points = points; + std::string stringPoints = format("%d", points); + std::string quitMessage; + if (NULL == message || 0 == strlen(message))quitMessage = stringPoints; + else quitMessage = stringPoints + " " + message; + quit(_points, quitMessage.c_str()); +} +NORETURN void quitp(float points, const std::string& message = "") { + __testlib_quitp(double(points), message.c_str()); +} +NORETURN void quitp(double points, const std::string& message = "") { + __testlib_quitp(points, message.c_str()); +} +NORETURN void quitp(long double points, const std::string& message = "") { + __testlib_quitp(double(points), message.c_str()); +} +NORETURN void quitp(int points, const std::string& message = "") { + __testlib_quitp(points, message.c_str()); +} +template +#ifdef __GNUC__ +__attribute__((format(printf, 2, 3))) +#endif +NORETURN void +quitp(F points, const char* format, ...) { + FMT_TO_RESULT(format, format, message); + quitp(points, message); +} +#ifdef __GNUC__ +__attribute__((format(printf, 2, 3))) +#endif +NORETURN void +quitf(TResult result, const char* format, ...) { + FMT_TO_RESULT(format, format, message); + quit(result, message); +} +#ifdef __GNUC__ +__attribute__((format(printf, 3, 4))) +#endif +void +quitif(bool condition, TResult result, const char* format, ...) { + if (condition) { + FMT_TO_RESULT(format, format, message); + quit(result, message); + } +} +static void __testlib_ensuresPreconditions() { + __TESTLIB_STATIC_ASSERT(sizeof(int) == 4); + __TESTLIB_STATIC_ASSERT(INT_MAX == 2147483647); + __TESTLIB_STATIC_ASSERT(sizeof(long long) == 8); + __TESTLIB_STATIC_ASSERT(sizeof(double) == 8); + if (!__testlib_isNaN(+__testlib_nan()))quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'"); + if (!__testlib_isNaN(-__testlib_nan()))quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'"); +} +void registerGen(int argc, char* argv[], int randomGeneratorVersion) { + if (randomGeneratorVersion < 0 || randomGeneratorVersion > 1)quitf(_fail, "Random generator version is expected to be 0 or 1."); + random_t::version = randomGeneratorVersion; + __testlib_ensuresPreconditions(); + testlibMode = _generator; + __testlib_set_binary(stdin); + rnd.setSeed(argc, argv); +} +#ifdef USE_RND_AS_BEFORE_087 +void registerGen(int argc, char* argv[]) { + registerGen(argc, argv, 0); +} +#else +#ifdef __GNUC__ +#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 4)) +__attribute__((deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators."))) +#else +__attribute__((deprecated)) +#endif +#endif +#ifdef _MSC_VER +__declspec(deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators.")) +#endif + void registerGen(int argc, char* argv[]) { + std::fprintf(stderr, "Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators.\n\n"); + registerGen(argc, argv, 0); +} +#endif +void registerInteraction(int argc, char* argv[]) { + __testlib_ensuresPreconditions(); + testlibMode = _interactor; + __testlib_set_binary(stdin); + if (argc < 3 || argc > 6) { + quit(_fail, std::string("Program must be run with the following arguments: ") + std::string(" [ [ [<-appes>]]]") + "\nUse \"--help\" to get help information"); + } + if (argc <= 4) { + resultName = ""; + appesMode = false; + } +#ifndef EJUDGE + if (argc == 5) { + resultName = argv[4]; + appesMode = false; + } + if (argc == 6) { + resultName = argv[4]; + appesMode = true; + } +#endif + inf.init(argv[1], _input); + tout.open(argv[2], std::ios_base::out); + if (tout.fail() || !tout.is_open())quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'")); + ouf.init(stdin, _output); + if (argc >= 4)ans.init(argv[3], _answer); + else ans.name = "unopened answer stream"; +} +void registerValidation() { + __testlib_ensuresPreconditions(); + testlibMode = _validator; + __testlib_set_binary(stdin); + inf.init(stdin, _input); + inf.strict = true; +} +void registerValidation(int argc, char* argv[]) { + registerValidation(); + for (int i = 1; i < argc; i++) { + if (!strcmp("--testset", argv[i])) { + if (i + 1 < argc && strlen(argv[i + 1]) > 0)validator.setTestset(argv[++i]); + else quit(_fail, std::string("Validator must be run with the following arguments: ") + "[--testset testset] [--group group] [--testOverviewLogFileName fileName]"); + } + if (!strcmp("--group", argv[i])) { + if (i + 1 < argc)validator.setGroup(argv[++i]); + else quit(_fail, std::string("Validator must be run with the following arguments: ") + "[--testset testset] [--group group] [--testOverviewLogFileName fileName]"); + } + if (!strcmp("--testOverviewLogFileName", argv[i])) { + if (i + 1 < argc)validator.setTestOverviewLogFileName(argv[++i]); + else quit(_fail, std::string("Validator must be run with the following arguments: ") + "[--testset testset] [--group group] [--testOverviewLogFileName fileName]"); + } + } +} +void addFeature(const std::string& feature) { + if (testlibMode != _validator)quit(_fail, "Features are supported in validators only."); + validator.addFeature(feature); +} +void feature(const std::string& feature) { + if (testlibMode != _validator)quit(_fail, "Features are supported in validators only."); + validator.feature(feature); +} +void registerTestlibCmd(int argc, char* argv[]) { + __testlib_ensuresPreconditions(); + testlibMode = _checker; + __testlib_set_binary(stdin); + if (argc > 1 && !strcmp("--help", argv[1]))__testlib_help(); + if (argc < 4 || argc > 6) { + quit(_fail, std::string("Program must be run with the following arguments: ") + std::string(" [ [<-appes>]]") + "\nUse \"--help\" to get help information"); + } + if (argc == 4) { + resultName = ""; + appesMode = false; + } + if (argc == 5) { + resultName = argv[4]; + appesMode = false; + } + if (argc == 6) { + if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) { + quit(_fail, std::string("Program must be run with the following arguments: ") + " [ [<-appes>]]"); + } else { + resultName = argv[4]; + appesMode = true; + } + } + inf.init(argv[1], _input); + ouf.init(argv[2], _output); + ans.init(argv[3], _answer); +} +void registerTestlib(int argc, ...) { + if (argc < 3 || argc > 5)quit(_fail, std::string("Program must be run with the following arguments: ") + " [ [<-appes>]]"); + char** argv = new char*[argc + 1]; + va_list ap; + va_start(ap, argc); + argv[0] = NULL; + for (int i = 0; i < argc; i++) { + argv[i + 1] = va_arg(ap, char*); + } + va_end(ap); + registerTestlibCmd(argc + 1, argv); + delete[] argv; +} +static inline void __testlib_ensure(bool cond, const std::string& msg) { + if (!cond)quit(_fail, msg.c_str()); +} +#ifdef __GNUC__ +__attribute__((unused)) +#endif +static inline void +__testlib_ensure(bool cond, const char* msg) { + if (!cond)quit(_fail, msg); +} +#define ensure(cond) __testlib_ensure(cond, "Condition failed: \"" #cond "\"") +#ifdef __GNUC__ +__attribute__((format(printf, 2, 3))) +#endif +inline void +ensuref(bool cond, const char* format, ...) { + if (!cond) { + FMT_TO_RESULT(format, format, message); + __testlib_ensure(cond, message); + } +} +NORETURN static void __testlib_fail(const std::string& message) { + quitf(_fail, "%s", message.c_str()); +} +#ifdef __GNUC__ +__attribute__((format(printf, 1, 2))) +#endif +void +setName(const char* format, ...) { + FMT_TO_RESULT(format, format, name); + checkerName = name; +} +template +void shuffle(_RandomAccessIter __first, _RandomAccessIter __last) { + if (__first == __last) return; + for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)std::iter_swap(__i, __first + rnd.next(int(__i - __first) + 1)); +} +template +#if defined(__GNUC__) && !defined(__clang__) +__attribute__((error("Don't use random_shuffle(), use shuffle() instead"))) +#endif +void +random_shuffle(_RandomAccessIter, _RandomAccessIter) { + quitf(_fail, "Don't use random_shuffle(), use shuffle() instead"); +} +#ifdef __GLIBC__ +#define RAND_THROW_STATEMENT throw() +#else +#define RAND_THROW_STATEMENT +#endif +#if defined(__GNUC__) && !defined(__clang__) +__attribute__((error("Don't use rand(), use rnd.next() instead"))) +#endif +#ifdef _MSC_VER +#pragma warning(disable : 4273) +#endif +int +rand() RAND_THROW_STATEMENT { + quitf(_fail, "Don't use rand(), use rnd.next() instead"); +} +#if defined(__GNUC__) && !defined(__clang__) +__attribute__((error("Don't use srand(), you should use " + "'registerGen(argc, argv, 1);' to initialize generator seed " + "by hash code of the command line params. The third parameter " + "is randomGeneratorVersion (currently the latest is 1)."))) +#endif +#ifdef _MSC_VER +#pragma warning(disable : 4273) +#endif +void +srand(unsigned int seed) RAND_THROW_STATEMENT { + quitf(_fail, "Don't use srand(), you should use " + "'registerGen(argc, argv, 1);' to initialize generator seed " + "by hash code of the command line params. The third parameter " + "is randomGeneratorVersion (currently the latest is 1) [ignored seed=%d].", + seed); +} +void startTest(int test) { + const std::string testFileName = vtos(test); + if (NULL == freopen(testFileName.c_str(), "wt", stdout))__testlib_fail("Unable to write file '" + testFileName + "'"); +} +inline std::string upperCase(std::string s) { + for (size_t i = 0; i < s.length(); i++)if ('a' <= s[i] && s[i] <= 'z')s[i] = char(s[i] - 'a' + 'A'); + return s; +} +inline std::string lowerCase(std::string s) { + for (size_t i = 0; i < s.length(); i++)if ('A' <= s[i] && s[i] <= 'Z')s[i] = char(s[i] - 'A' + 'a'); + return s; +} +inline std::string compress(const std::string& s) { + return __testlib_part(s); +} +inline std::string englishEnding(int x) { + x %= 100; + if (x / 10 == 1)return "th"; + if (x % 10 == 1)return "st"; + if (x % 10 == 2)return "nd"; + if (x % 10 == 3)return "rd"; + return "th"; +} +template +std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separator) { + std::stringstream ss; + bool repeated = false; + for (_ForwardIterator i = first; i != last; i++) { + if (repeated)ss << separator; + else repeated = true; + ss << *i; + } + return ss.str(); +} +template +std::string join(_ForwardIterator first, _ForwardIterator last) { + return join(first, last, ' '); +} +template +std::string join(const _Collection& collection, _Separator separator) { + return join(collection.begin(), collection.end(), separator); +} +template +std::string join(const _Collection& collection) { + return join(collection, ' '); +} +std::vector split(const std::string& s, char separator) { + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++)if (s[i] == separator) { + result.push_back(item); + item = ""; + } else item += s[i]; + result.push_back(item); + return result; +} +std::vector split(const std::string& s, const std::string& separators) { + if (separators.empty())return std::vector(1, s); + std::vector isSeparator(256); + for (size_t i = 0; i < separators.size(); i++)isSeparator[(unsigned char)(separators[i])] = true; + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++)if (isSeparator[(unsigned char)(s[i])]) { + result.push_back(item); + item = ""; + } else item += s[i]; + result.push_back(item); + return result; +} +std::vector tokenize(const std::string& s, char separator) { + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++)if (s[i] == separator) { + if (!item.empty())result.push_back(item); + item = ""; + } else item += s[i]; + if (!item.empty())result.push_back(item); + return result; +} +std::vector tokenize(const std::string& s, const std::string& separators) { + if (separators.empty())return std::vector(1, s); + std::vector isSeparator(256); + for (size_t i = 0; i < separators.size(); i++)isSeparator[(unsigned char)(separators[i])] = true; + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++)if (isSeparator[(unsigned char)(s[i])]) { + if (!item.empty())result.push_back(item); + item = ""; + } else item += s[i]; + if (!item.empty())result.push_back(item); + return result; +} +NORETURN void __testlib_expectedButFound(TResult result, std::string expected, std::string found, const char* prepend) { + std::string message; + if (strlen(prepend) != 0)message = format("%s: expected '%s', but found '%s'", + compress(prepend).c_str(), compress(expected).c_str(), compress(found).c_str()); + else message = format("expected '%s', but found '%s'", + compress(expected).c_str(), compress(found).c_str()); + quit(result, message); +} +NORETURN void __testlib_expectedButFound(TResult result, double expected, double found, const char* prepend) { + std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected)); + std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found)); + __testlib_expectedButFound(result, expectedString, foundString, prepend); +} +template +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +NORETURN void +expectedButFound(TResult result, T expected, T found, const char* prependFormat = "", ...) { + FMT_TO_RESULT(prependFormat, prependFormat, prepend); + std::string expectedString = vtos(expected); + std::string foundString = vtos(found); + __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str()); +} +template <> +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +NORETURN void +expectedButFound(TResult result, std::string expected, std::string found, const char* prependFormat, ...) { + FMT_TO_RESULT(prependFormat, prependFormat, prepend); + __testlib_expectedButFound(result, expected, found, prepend.c_str()); +} +template <> +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +NORETURN void +expectedButFound(TResult result, double expected, double found, const char* prependFormat, ...) { + FMT_TO_RESULT(prependFormat, prependFormat, prepend); + std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected)); + std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found)); + __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str()); +} +template <> +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +NORETURN void +expectedButFound(TResult result, const char* expected, const char* found, const char* prependFormat, ...) { + FMT_TO_RESULT(prependFormat, prependFormat, prepend); + __testlib_expectedButFound(result, std::string(expected), std::string(found), prepend.c_str()); +} +template <> +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +NORETURN void +expectedButFound(TResult result, float expected, float found, const char* prependFormat, ...) { + FMT_TO_RESULT(prependFormat, prependFormat, prepend); + __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str()); +} +template <> +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +NORETURN void +expectedButFound(TResult result, long double expected, long double found, const char* prependFormat, ...) { + FMT_TO_RESULT(prependFormat, prependFormat, prepend); + __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str()); +} +#if __cplusplus > 199711L || defined(_MSC_VER) +template +struct is_iterable { + template + static char test(typename U::iterator* x); + template + static long test(U* x); + static const bool value = sizeof(test(0)) == 1; +}; +template +struct __testlib_enable_if { }; +template +struct __testlib_enable_if { typedef T type; }; +template +typename __testlib_enable_if::value, void>::type __testlib_print_one(const T& t) { + std::cout << t; +} +template +typename __testlib_enable_if::value, void>::type __testlib_print_one(const T& t) { + bool first = true; + for (typename T::const_iterator i = t.begin(); i != t.end(); i++) { + if (first)first = false; + else std::cout << " "; + std::cout << *i; + } +} +template <> +typename __testlib_enable_if::value, void>::type __testlib_print_one(const std::string& t) { + std::cout << t; +} +template +void __println_range(A begin, B end) { + bool first = true; + for (B i = B(begin); i != end; i++) { + if (first)first = false; + else std::cout << " "; + __testlib_print_one(*i); + } + std::cout << std::endl; +} +template +struct is_iterator { + static T makeT(); + typedef void* twoptrs[2]; + static twoptrs& test(...); + template + static typename R::iterator_category* test(R); + template + static void* test(R*); + static const bool value = sizeof(test(makeT())) == sizeof(void*); +}; +template +struct is_iterator::value>::type> { + static const bool value = false; +}; +template +typename __testlib_enable_if::value, void>::type println(const A& a, const B& b) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << std::endl; +} +template +typename __testlib_enable_if::value, void>::type println(const A& a, const B& b) { + __println_range(a, b); +} +template +void println(const A* a, const A* b) { + __println_range(a, b); +} +template <> +void println(const char* a, const char* b) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << std::endl; +} +template +void println(const T& x) { + __testlib_print_one(x); + std::cout << std::endl; +} +template +void println(const A& a, const B& b, const C& c) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << std::endl; +} +template +void println(const A& a, const B& b, const C& c, const D& d) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << std::endl; +} +template +void println(const A& a, const B& b, const C& c, const D& d, const E& e) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << " "; + __testlib_print_one(e); + std::cout << std::endl; +} +template +void println(const A& a, const B& b, const C& c, const D& d, const E& e, const F& f) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << " "; + __testlib_print_one(e); + std::cout << " "; + __testlib_print_one(f); + std::cout << std::endl; +} +template +void println(const A& a, const B& b, const C& c, const D& d, const E& e, const F& f, const G& g) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << " "; + __testlib_print_one(e); + std::cout << " "; + __testlib_print_one(f); + std::cout << " "; + __testlib_print_one(g); + std::cout << std::endl; +} +#endif +#endif \ No newline at end of file diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 2b04210bf..bbb40cb94 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.3.19", + "version": "2.4.0", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index ecb440b60..c301c8445 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -205,6 +205,7 @@ export async function readYamlCases(folder: string, cfg: Dictionary = {}, a const checkFile = ensureFile(folder); config.checker_type = cfg.checker_type || 'default'; if (cfg.checker) config.checker = checkFile(cfg.checker, 'Cannot find checker {0}.'); + if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.'); if (cfg.judge_extra_files) { if (typeof cfg.judge_extra_files === 'string') { config.judge_extra_files = [checkFile(cfg.judge_extra_files, 'Cannot find judge extra file {0}.')]; diff --git a/packages/hydrojudge/src/checkers.ts b/packages/hydrojudge/src/checkers.ts index 14b20cc24..9305d3ba0 100644 --- a/packages/hydrojudge/src/checkers.ts +++ b/packages/hydrojudge/src/checkers.ts @@ -1,6 +1,8 @@ /* eslint-disable no-template-curly-in-string */ import fs from 'fs-extra'; +import { resolve } from 'path'; import * as STATUS from './status'; +import { parse } from './testlib'; import { run } from './sandbox'; import { SystemError } from './error'; @@ -178,6 +180,7 @@ const checkers: Record = { async testlib(config) { const { stderr, status } = await run('${dir}/checker ${dir}/in ${dir}/user_out ${dir}/answer', { copyIn: { + 'testlib.h': { src: resolve(__dirname, '../files/testlib.h') }, in: { src: config.input }, user_out: { src: config.user_stdout }, answer: { src: config.output }, @@ -191,11 +194,7 @@ const checkers: Record = { message: '', }; } - return { - status: stderr.startsWith('ok ') ? STATUS.STATUS_ACCEPTED : STATUS.STATUS_WRONG_ANSWER, - score: stderr.startsWith('ok ') ? config.score : 0, - message: stderr.split(' ').splice(1).join(' '), - }; + return parse(stderr, config.score); }, }; diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 1c5cb1484..3cf3a7499 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -1,6 +1,8 @@ import Queue from 'p-queue'; import fs from 'fs-extra'; +import { resolve } from 'path'; import * as STATUS from '../status'; +import { parse } from '../testlib'; import { parseFilename } from '../utils'; import { run } from '../sandbox'; import compile from '../compile'; @@ -15,7 +17,9 @@ const Score = { function judgeCase(c) { return async (ctx, ctxSubtask) => { - ctx.executeInteractor.copyIn.stdans = { src: c.input }; + ctx.executeInteractor.copyIn.in = { src: c.input }; + ctx.executeInteractor.copyIn.out = { src: c.output }; + ctx.executeInteractor.copyIn['testlib.h'] = { src: resolve(__dirname, '../../files/testlib.h') }; const [{ code, time_usage_ms, memory_usage_kb }, resInteractor] = await run([ { execute: ctx.executeUser.execute.replace(/\$\{name\}/g, 'code'), @@ -23,7 +27,7 @@ function judgeCase(c) { time_limit_ms: ctxSubtask.subtask.time_limit_ms, memory_limit_mb: ctxSubtask.subtask.memory_limit_mb, }, { - execute: ctx.executeInteractor.execute.replace(/\$\{name\}/g, 'code'), + execute: `${ctx.executeInteractor.execute.replace(/\$\{name\}/g, 'interactor')} /w/in /w/tout`, copyIn: ctx.executeInteractor.copyIn, time_limit_ms: ctxSubtask.subtask.time_limit_ms * 2, memory_limit_mb: ctxSubtask.subtask.memory_limit_mb * 2, @@ -40,7 +44,12 @@ function judgeCase(c) { status = STATUS.STATUS_RUNTIME_ERROR; if (code < 32) message = signals[code]; else message = { message: 'Your program returned {0}.', params: [code] }; - } else[status, score, message] = resInteractor.files.stderr.split('\n'); + } else { + const result = parse(resInteractor.files.stderr, ctx.config.score); + status = result.status; + score = result.score; + message = result.message; + } ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); ctxSubtask.status = Math.max(ctxSubtask.status, status); ctx.total_time_usage_ms += time_usage_ms; diff --git a/packages/hydrojudge/src/sandbox.ts b/packages/hydrojudge/src/sandbox.ts index 8cf5d839a..030e9aa4a 100644 --- a/packages/hydrojudge/src/sandbox.ts +++ b/packages/hydrojudge/src/sandbox.ts @@ -46,6 +46,28 @@ function proc({ }; } +async function adaptResult(result, params) { + // FIXME: Signalled? + const ret: any = { + status: statusMap[result.status] || STATUS.STATUS_ACCEPTED, + time_usage_ms: result.time / 1000000, + memory_usage_kb: result.memory / 1024, + files: result.files, + code: result.exitStatus, + }; + if (ret.time_usage_ms >= (params.time_limit_ms || 16000)) { + ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED; + } + ret.files = result.files || {}; + ret.fileIds = result.fileIds || {}; + if (params.stdout) await fs.writeFile(params.stdout, ret.files.stdout || ''); + else ret.stdout = ret.files.stdout || ''; + if (params.stderr) await fs.writeFile(params.stderr, ret.files.stderr || ''); + else ret.stderr = ret.files.stderr || ''; + if (result.error) ret.error = result.error; + return ret; +} + export async function runMultiple(execute) { let res; try { @@ -70,7 +92,7 @@ export async function runMultiple(execute) { } catch (e) { throw new SystemError('Sandbox Error'); } - return res.data; + return await Promise.all(res.data.map((i) => adaptResult(i, {}))); } export async function del(fileId) { @@ -91,24 +113,5 @@ export async function run(execute, params?) { // FIXME request body larger than maxBodyLength limit throw new SystemError('Sandbox Error', e.message); } - // FIXME: Signalled? - const ret: any = { - status: statusMap[result.status] || STATUS.STATUS_ACCEPTED, - time_usage_ms: result.time / 1000000, - memory_usage_kb: result.memory / 1024, - files: result.files, - code: result.exitStatus, - }; - if (ret.time_usage_ms >= (params.time_limit_ms || 16000)) { - ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED; - } - result.files = result.files || {}; - if (params.stdout) await fs.writeFile(params.stdout, result.files.stdout || ''); - else ret.stdout = result.files.stdout || ''; - if (params.stderr) await fs.writeFile(params.stderr, result.files.stderr || ''); - else ret.stderr = result.files.stderr || ''; - if (result.error) ret.error = result.error; - ret.files = result.files; - ret.fileIds = result.fileIds || {}; - return ret; + return await adaptResult(result, params); } diff --git a/packages/hydrojudge/src/testlib.ts b/packages/hydrojudge/src/testlib.ts new file mode 100644 index 000000000..b414d5274 --- /dev/null +++ b/packages/hydrojudge/src/testlib.ts @@ -0,0 +1,24 @@ +import * as STATUS from './status'; + +export function parse(output: string, fullscore: number) { + let status = STATUS.STATUS_WRONG_ANSWER; + let score = 0; + let message = ''; + if (output.startsWith('ok ')) status = STATUS.STATUS_ACCEPTED; + else if (output.startsWith('wrong answer ')) { + message = output.split('wrong answer ')[1] || ''; + score = fullscore; + } else if (output.startsWith('wrong output format ')) { + message = `PE ${output.split('wrong output format ')[1] || ''}`; + } else if (output.startsWith('unexpected eof ')) { + message = `UE ${output.split('unexpected eof ')[1] || ''}`; + } else if (output.startsWith('partially correct ')) { + message = `PC ${output.split('partially correct ')[1] || ''}`; + const p = +output.split('partially correct (')[1].split(')')[0]; + score = Math.floor(fullscore * (p / 200)); + } else if (output.startsWith('FAIL ')) { + message = output.split('FAIL ')[1] || ''; + status = STATUS.STATUS_SYSTEM_ERROR; + } + return { status, score, message }; +} diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 76503425c..8089c787d 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.18.4", + "version": "2.18.5", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts",