User Guide
Advanced
Initializer

Initializer

The CPLib team maintains a set of initializers for mainstream platforms in the cplib-initializers (opens in a new tab) project. If your goal is solely to run your own programs on the target platform, you can visit the project repository to learn more about the details.

If you need to use CPLib on an evaluation platform that does not natively support it, the best way to use it is to implement a custom initializer. The following will use the example of writing a checker for the CMS (opens in a new tab) platform to demonstrate how to write a custom initializer.

The initializer needs to do three things:

  1. Read and parse the command line arguments.
  2. Set up the input/output streams and the Reader variable required by the program.
  3. Set up the reporter.

According to the CMS official documentation (opens in a new tab), the execution details of the checker on this platform are as follows:

  • The program is run using the command ./checker <inf> <ans> <ouf>.
  • When reporting, output a real number in the range [0,1][0,1] to stdout as the score, and output a single string translate:success, translate:wrong, or translate:partial to stderr, representing the result AC, WA, or PC, respectively. Custom messages are also allowed.
  • If no internal errors occur, the checker's return value must be 0.

Here is a sample implementation:

initializer.hpp
struct CmsReporter : cplib::checker::Reporter {
  using Report = cplib::checker::Report;
  using Status = Report::Status;
 
  auto report(const Report &report) -> int override {
    // Do not use std::cout or std::clog directly: need to prevent the behavior of the user code
    // setting the format for them from affecting the Reporter output
    std::ostream score_stream(std::cout.rdbuf());
    std::ostream status_stream(std::clog.rdbuf());
 
    score_stream << std::fixed << std::setprecision(9) << report.score << '\n';
 
    switch (report.status) {
      case Status::INTERNAL_ERROR:
        status_stream << "FAIL " << report.message << '\n';
        return 1;
      case Status::ACCEPTED:
        status_stream << (report.message.empty() ? "translate:success" : report.message) << '\n';
        break;
      case Status::WRONG_ANSWER:
        status_stream << (report.message.empty() ? "translate:wrong" : report.message) << '\n';
        break;
      case Status::PARTIALLY_CORRECT:
        status_stream << (report.message.empty() ? "translate:partial" : report.message) << '\n';
        break;
      default:
        status_stream << "FAIL invalid status\n";
        return 1;
    }
 
    return 0;
  }
};
 
struct CmsInitializer : cplib::checker::Initializer {
  auto init(std::string_view arg0, const std::vector<std::string> &args) -> void override {
    auto& state = this->state();
 
    // Set the reporter as early as possible to ensure that errors reported during init can also be
    // reported in the correct way.
    state.reporter = std::make_unique<CmsReporter>();
 
    if (args.size() != 3) {
      cplib::panic("Program must be run with the following arguments:\n  <inf> <ans> <ouf>");
    }
 
    const auto &inf = args[0];
    const auto &ouf = args[2];
    const auto &ans = args[1];
 
    set_inf_path(inf, cplib::var::Reader::TraceLevel::NONE);
    set_ouf_path(ouf, cplib::var::Reader::TraceLevel::NONE);
    set_ans_path(ans, cplib::var::Reader::TraceLevel::NONE);
  }
};

The Reporter needs to inherit from cplib::<work-mode>::Reporter and implement the auto report(const cplib::<work-mode>::Report &report) -> int function, where the return value represents the exit code.

The initializer needs to inherit from cplib::<work-mode>Initializer and implement the auto init(std::string_view arg0, const std::vector<std::string> &args) -> void function.

Let's focus on the implementation of CmsInitializer. It first implements the init function, where the parameter arg0 represents the first item of the command line arguments (i.e., the path to the program being called), and args represents the subsequent arguments (<inf> <ans> <ouf>).

First, it calls this->state() to get the cplib::checker::State object that needs to be initialized. Then it extracts the paths of the input and output files from the args parameter. In this example, since the command line arguments are relatively simple, they can be processed directly. However, if the command line arguments are more complex, you can use cplib::cmd_args::ParsedArgs to process them.

For example, if a platform requires the command line arguments to be in the format ./chk <inf> <ouf> <ans> --full-score={int} [--report-as-json], you can process them as follows:

auto parsed_args = cplib::cmd_args::ParsedArgs(args);
 
bool report_as_json = parsed_args.has_flag("report-as-json");
int full_score = cplib::var::i32("full-score", 0, std::nullopt)
                     .parse(parsed_args.vars.at("full-score"));
auto inf = parsed_args.ordered[0];
auto ouf = parsed_args.ordered[1];
auto ans = parsed_args.ordered[2];

ParsedArgs will divide all command line arguments into three categories:

  • var: In the form of --var=value or --var value.
  • flag: In the form of --flag.
  • ordered: Elements that do not belong to the first two categories are stored in ordered in the order they appear.

Of note is the use of the set_(inf|ouf|ans)_path functions. To facilitate the initialization of input/output streams and the Reader, CPLib provides a series of functions in the form of set_<name>_<source> for objects that inherit from cplib::checker::Initializer, where <name> represents the name of the Reader or output stream, and <source> is either fileno or path, representing the use of the fileno (file descriptor of stdio stream) (opens in a new tab) and local files as content, respectively.

For checkers, the available <name> values are as follows:

NameTypeDescription
infReaderInput file
oufReaderParticipant output file
ansReaderAnswer file

For interactors, the available <name> values are as follows:

NameTypeDescription
infReaderInput file
from_userReaderUser input
to_userostreamUser output

For validators, the available <name> values are as follows:

NameTypeDescription
infReaderInput file

If the <name> type is Reader, an additional parameter representing the Reader's trace level needs to be passed, the meaning of which is explained in the Trace chapter.

⚠️

Do not report errors by calling the cplib::panic function when implementing Reporter::report. This is because CPLib programs modify the panic implementation during initialization to call Reporter::report with INTERNAL_ERROR as the status, which will cause an infinite recursion.

⚠️

According to the LGPL license requirements, when you distribute software that uses CPLib, you need to indicate somewhere that the software uses CPLib, and that CPLib is licensed under the LGPL. Our recommended approach is to output relevant information when the command line argument contains --help. This tutorial has not been implemented to keep the code short. In actual projects, please be sure to strictly abide by the open source license.