From 5cec4daefe6640b79c8b8d8b9986b0bb8a5f51e8 Mon Sep 17 00:00:00 2001 From: jorg-vr Date: Wed, 17 Jul 2024 15:03:00 +0200 Subject: [PATCH] Get first c++ judgements up and running --- tested/languages/__init__.py | 2 + tested/languages/cpp/config.py | 43 +++++ tested/languages/cpp/generators.py | 170 ++++++++++++++++ .../cpp/templates/evaluation_result.cpp | 49 +++++ .../cpp/templates/evaluation_result.h | 43 +++++ tested/languages/cpp/templates/values.cpp | 182 ++++++++++++++++++ tested/languages/cpp/templates/values.h | 73 +++++++ tested/manual.py | 8 +- tested/testsuite.py | 1 + tests/exercises/echo/solution/comp-error.cpp | 1 + tests/exercises/echo/solution/correct.cpp | 14 ++ tests/exercises/echo/solution/wrong.cpp | 7 + tests/language_markers.py | 1 + 13 files changed, 590 insertions(+), 4 deletions(-) create mode 100644 tested/languages/cpp/config.py create mode 100644 tested/languages/cpp/generators.py create mode 100644 tested/languages/cpp/templates/evaluation_result.cpp create mode 100644 tested/languages/cpp/templates/evaluation_result.h create mode 100644 tested/languages/cpp/templates/values.cpp create mode 100644 tested/languages/cpp/templates/values.h create mode 100644 tests/exercises/echo/solution/comp-error.cpp create mode 100644 tests/exercises/echo/solution/correct.cpp create mode 100644 tests/exercises/echo/solution/wrong.cpp diff --git a/tested/languages/__init__.py b/tested/languages/__init__.py index a045fb26..dc2522d8 100644 --- a/tested/languages/__init__.py +++ b/tested/languages/__init__.py @@ -11,6 +11,7 @@ from tested.languages.bash.config import Bash from tested.languages.c.config import C +from tested.languages.cpp.config import CPP from tested.languages.csharp.config import CSharp from tested.languages.haskell.config import Haskell from tested.languages.java.config import Java @@ -34,6 +35,7 @@ "python": Python, "runhaskell": RunHaskell, "csharp": CSharp, + "cpp": CPP, } diff --git a/tested/languages/cpp/config.py b/tested/languages/cpp/config.py new file mode 100644 index 00000000..9f7db54b --- /dev/null +++ b/tested/languages/cpp/config.py @@ -0,0 +1,43 @@ +from pathlib import Path +import re + +from tested.languages.c.config import C +from tested.languages.language import CallbackResult +from tested.languages.preparation import PreparedExecutionUnit +from tested.languages.utils import executable_name + + +class CPP(C): + def initial_dependencies(self) -> list[str]: + return ["values.h", "values.cpp", "evaluation_result.h", "evaluation_result.cpp"] + + def file_extension(self) -> str: + return "cpp" + + def compilation(self, files: list[str]) -> CallbackResult: + main_file = files[-1] + exec_file = Path(main_file).stem + result = executable_name(exec_file) + return ( + [ + "g++", + "-std=c++11", + "-Wall", + "-O3" if self.config.options.compiler_optimizations else "-O0", + "evaluation_result.cpp", + "values.cpp", + main_file, + "-o", + result, + ], + [result], + ) + + def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str: + from tested.languages.cpp import generators + return generators.convert_execution_unit(execution_unit) + + def generate_selector(self, contexts: list[str]) -> str: + from tested.languages.cpp import generators + + return generators.convert_selector(contexts) diff --git a/tested/languages/cpp/generators.py b/tested/languages/cpp/generators.py new file mode 100644 index 00000000..b5eba822 --- /dev/null +++ b/tested/languages/cpp/generators.py @@ -0,0 +1,170 @@ +import json + +from tested.languages.c.generators import convert_statement +from tested.languages.preparation import PreparedExecutionUnit, PreparedContext, \ + PreparedTestcase, PreparedTestcaseStatement +from tested.languages.utils import is_special_void_call +from tested.testsuite import MainInput + + +def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) -> str: + result = f""" + {ctx.before} + + int exit_code; + """ + + # Generate code for each testcase + tc: PreparedTestcase + for tc in ctx.testcases: + result += f"{pu.unit.name}_write_separator();\n" + + if tc.testcase.is_main_testcase(): + assert isinstance(tc.input, MainInput) + wrapped = [json.dumps(a) for a in tc.input.arguments] + result += f'string string_args[] = {{"{pu.submission_name}", ' + result += ", ".join(wrapped) + result += "};\n" + result += f""" + // convert string to char** + const int argc = {len(tc.input.arguments) + 1}; + char** args = new char*[argc]; + for (int i = 0; i < argc; i++) {{ + args[i] = new char[string_args[i].size() + 1]; + strcpy(args[i], string_args[i].c_str()); + }} + """ + result += ( + f"exit_code = solution_main(argc, args);\n" + ) + else: + assert isinstance(tc.input, PreparedTestcaseStatement) + result += "exit_code = 0;\n" + if is_special_void_call(tc.input, pu.language): + # The method has a "void" return type, so don't wrap it. + result += ( + " " * 4 + + convert_statement(tc.input.unwrapped_input_statement()) + + ";\n" + ) + result += " " * 4 + convert_statement(tc.input.no_value_call()) + ";\n" + else: + result += convert_statement(tc.input.input_statement()) + ";\n" + + result += ctx.after + "\n" + result += "return exit_code;\n" + return result + + +def convert_execution_unit(pu: PreparedExecutionUnit) -> str: + result = f""" + #include "values.h" + #include "{pu.submission_name}.cpp" + + using namespace std; + """ + + # Import functions + for name in pu.evaluator_names: + result += f'#include "{name}.cpp"\n' + + result += f""" + static FILE* {pu.unit.name}_value_file = NULL; + static FILE* {pu.unit.name}_exception_file = NULL; + + static void {pu.unit.name}_write_separator() {{ + fprintf({pu.unit.name}_value_file, "--{pu.testcase_separator_secret}-- SEP"); + fprintf({pu.unit.name}_exception_file, "--{pu.testcase_separator_secret}-- SEP"); + fprintf(stdout, "--{pu.testcase_separator_secret}-- SEP"); + fprintf(stderr, "--{pu.testcase_separator_secret}-- SEP"); + }} + + static void {pu.unit.name}_write_context_separator() {{ + fprintf({pu.unit.name}_value_file, "--{pu.context_separator_secret}-- SEP"); + fprintf({pu.unit.name}_exception_file, "--{pu.context_separator_secret}-- SEP"); + fprintf(stdout, "--{pu.context_separator_secret}-- SEP"); + fprintf(stderr, "--{pu.context_separator_secret}-- SEP"); + }} + + #undef send_value + #define send_value(value) write_value({pu.unit.name}_value_file, value) + + #undef send_specific_value + #define send_specific_value(value) write_evaluated({pu.unit.name}_value_file, value) + """ + + # Generate code for each context. + ctx: PreparedContext + for i, ctx in enumerate(pu.contexts): + result += f""" + int {pu.unit.name}_context_{i}(void) {{ + {_generate_internal_context(ctx, pu)} + }} + """ + + result += f""" + int {pu.unit.name}() {{ + {pu.unit.name}_value_file = fopen("{pu.value_file}", "w"); + {pu.unit.name}_exception_file = fopen("{pu.exception_file}", "w"); + int exit_code; + """ + + for i, ctx in enumerate(pu.contexts): + result += " " * 4 + f"{pu.unit.name}_write_context_separator();\n" + result += " " * 4 + f"exit_code = {pu.unit.name}_context_{i}();\n" + + result += f""" + fclose({pu.unit.name}_value_file); + fclose({pu.unit.name}_exception_file); + return exit_code; + }} + + #ifndef INCLUDED + int main() {{ + return {pu.unit.name}(); + }} + #endif + """ + return result + + +def convert_selector(contexts: list[str]) -> str: + result = """ + #include + #include + + #define INCLUDED true + """ + + for ctx in contexts: + result += f""" + #if __has_include("{ctx}.cpp") + #include "{ctx}.cpp" + #endif + """ + + result += """ + int main(int argc, const char* argv[]) { + + if (argc < 1) { + fprintf(stderr, "No context selected."); + return -2; + } + + const char* name = argv[1]; + """ + for ctx in contexts: + result += f""" + #if __has_include("{ctx}.cpp") + if (strcmp("{ctx}", name) == 0) {{ + return {ctx}(); + }} + #endif + """ + + result += """ + fprintf(stderr, "Non-existing context '%s' selected.", name); + return -1; + } + """ + return result diff --git a/tested/languages/cpp/templates/evaluation_result.cpp b/tested/languages/cpp/templates/evaluation_result.cpp new file mode 100644 index 00000000..a20802b4 --- /dev/null +++ b/tested/languages/cpp/templates/evaluation_result.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +#include "evaluation_result.h" + +// Define the Message constructor +Message::Message(const std::string &desc, const std::string &fmt, const std::string &perm) + : description(desc), format(fmt.empty() ? "text" : fmt), permission(perm) {} + +// Define the EvaluationResult constructor +EvaluationResult::EvaluationResult(size_t nrOfMessages) { + messages.reserve(nrOfMessages); +} + +// Define the EvaluationResult destructor +EvaluationResult::~EvaluationResult() { + for (auto message : messages) { + delete message; + } +} + +// Function to create an EvaluationResult object +EvaluationResult* create_result(size_t nrOfMessages) { + return new EvaluationResult(nrOfMessages); +} + +// Function to free an EvaluationResult object +void free_result(EvaluationResult *result) { + delete result; +} + +// Function to create a Message object +Message* create_message(const std::string &description, const std::string &format, const std::string &permission) { + return new Message(description, format, permission); +} + +// Function to free a Message object +void free_message(Message *message) { + delete message; +} + + + + + + + + diff --git a/tested/languages/cpp/templates/evaluation_result.h b/tested/languages/cpp/templates/evaluation_result.h new file mode 100644 index 00000000..c86f288f --- /dev/null +++ b/tested/languages/cpp/templates/evaluation_result.h @@ -0,0 +1,43 @@ +#ifndef EVALUATION_RESULT_H +#define EVALUATION_RESULT_H + +#include +#include +#include + +using namespace std; + +// Define the Message structure +struct Message { + string description; + string format; + string permission; + + Message(const string &desc, const string &fmt, const string &perm); +}; + +// Define the EvaluationResult structure +struct EvaluationResult { + vector messages; + bool result; + string readableExpected; + string readableActual; + + EvaluationResult(size_t nrOfMessages); + ~EvaluationResult(); +}; + + +// Function to create an EvaluationResult object +EvaluationResult* create_result(size_t nrOfMessages); + +// Function to free an EvaluationResult object +void free_result(EvaluationResult *result); + +// Function to create a Message object +Message* create_message(const string &description, const string &format, const string &permission); + +// Function to free a Message object +void free_message(Message *message); + +#endif //EVALUATION_RESULT_H diff --git a/tested/languages/cpp/templates/values.cpp b/tested/languages/cpp/templates/values.cpp new file mode 100644 index 00000000..db0aefe6 --- /dev/null +++ b/tested/languages/cpp/templates/values.cpp @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "values.h" + +using namespace std; + + +// Function to escape special characters in a string +string escape(const string &buffer) { + string dest; + dest.reserve(buffer.length() * 2); // Reserve enough space to avoid reallocations + const string esc_char = "\a\b\f\n\r\t\v\\\""; + const string essc_str = "abfnrtv\\\""; + + for (char ch : buffer) { + auto pos = esc_char.find(ch); + if (pos != string::npos) { + dest += '\\'; + dest += essc_str[pos]; + } else { + dest += ch; + } + } + return dest; +} + +// Format macro equivalent +#define FORMAT(name, x) "{\"type\": \"" name "\", \"data\":" x "}" + +// Function to write a formatted string to an output stream +void write_formatted(FILE* out, const char* format, ...) { + va_list args; + va_start(args, format); + vfprintf(out, format, args); + va_end(args); +} + +// Function to write boolean values +void write_bool(FILE* out, bool value) { + write_formatted(out, FORMAT("boolean", "%s"), value ? "true" : "false"); +} + +// Function to write char values +void write_char(FILE* out, char value) { + string buffer(1, value); + string result = escape(buffer); + write_formatted(out, FORMAT("char", "\"%s\""), result.c_str()); +} + +// Function to write signed char values +void write_signed_char(FILE* out, signed char value) { + write_formatted(out, FORMAT("int8", "%d"), static_cast(value)); +} + +// Function to write unsigned char values +void write_unsigned_char(FILE* out, unsigned char value) { + write_formatted(out, FORMAT("uint8", "%u"), static_cast(value)); +} + +// Function to write short int values +void write_sint(FILE* out, short int value) { + write_formatted(out, FORMAT("int16", "%d"), value); +} + +// Function to write unsigned short int values +void write_usint(FILE* out, unsigned short int value) { + write_formatted(out, FORMAT("uint16", "%u"), value); +} + +// Function to write int values +void write_int(FILE* out, int value) { + write_formatted(out, FORMAT("int32", "%d"), value); +} + +// Function to write unsigned int values +void write_uint(FILE* out, unsigned int value) { + write_formatted(out, FORMAT("uint32", "%u"), value); +} + +// Function to write long values +void write_long(FILE* out, long value) { + write_formatted(out, FORMAT("int64", "%ld"), value); +} + +// Function to write unsigned long values +void write_ulong(FILE* out, unsigned long value) { + write_formatted(out, FORMAT("uint64", "%lu"), value); +} + +// Function to write long long values +void write_llong(FILE* out, long long value) { + write_formatted(out, FORMAT("integer", "%lld"), value); +} + +// Function to write unsigned long long values +void write_ullong(FILE* out, unsigned long long value) { + write_formatted(out, FORMAT("bigint", "%llu"), value); +} + +// Function to write float values +void write_float(FILE* out, float value) { + if (isnan(value)) { + write_formatted(out, FORMAT("single_precision", "\"%s\""), "nan"); + } else if (isinf(value)) { + write_formatted(out, FORMAT("single_precision", "\"%s\""), value < 0 ? "-inf" : "inf"); + } else { + write_formatted(out, FORMAT("single_precision", "%f"), value); + } +} + +// Function to write double values +void write_double(FILE* out, double value) { + if (isnan(value)) { + write_formatted(out, FORMAT("double_precision", "\"%s\""), "nan"); + } else if (isinf(value)) { + write_formatted(out, FORMAT("double_precision", "\"%s\""), value < 0 ? "-inf" : "inf"); + } else { + write_formatted(out, FORMAT("double_precision", "%lf"), value); + } +} + +// Function to write long double values +void write_ldouble(FILE* out, long double value) { + if (isnan(value)) { + write_formatted(out, FORMAT("double_extended", "\"%s\""), "nan"); + } else if (isinf(value)) { + write_formatted(out, FORMAT("double_extended", "\"%s\""), value < 0 ? "-inf" : "inf"); + } else { + write_formatted(out, FORMAT("double_extended", "%Lf"), value); + } +} + +// Function to write string values +void write_string(FILE* out, const string &value) { + string result = escape(value); + write_formatted(out, FORMAT("text", "\"%s\""), result.c_str()); +} + +// Function to write unknown values +void write_unknown(FILE* out, void* value) { + write_formatted(out, FORMAT("unknown", "\"%s\""), "?"); +} + +// Function to write void values +void write_void(FILE* out, void* value) { + write_formatted(out, FORMAT("nothing", "\"%s\""), "null"); +} + +// Function to write evaluated results +void write_evaluated(FILE* out, EvaluationResult* result) { + string concatMessages; + + for (const auto& message : result->messages) { + string messageStr; + if (message->permission.empty()) { + messageStr = "{\"description\": \"" + escape(message->description) + "\", \"format\": \"" + escape(message->format) + "\"}"; + } else { + messageStr = "{\"description\": \"" + escape(message->description) + "\", \"format\": \"" + escape(message->format) + "\", \"permission\": \"" + escape(message->permission) + "\"}"; + } + if (!concatMessages.empty()) { + concatMessages += ","; + } + concatMessages += messageStr; + } + + string resultStr = result->result ? "true" : "false"; + write_formatted(out, "{" + "\"result\": %s, " + "\"readable_expected\": \"%s\", " + "\"readable_actual\": \"%s\", " + "\"messages\": [%s]" + "}", resultStr.c_str(), escape(result->readableExpected).c_str(), escape(result->readableActual).c_str(), concatMessages.c_str()); + + delete result; +} diff --git a/tested/languages/cpp/templates/values.h b/tested/languages/cpp/templates/values.h new file mode 100644 index 00000000..ab858d3e --- /dev/null +++ b/tested/languages/cpp/templates/values.h @@ -0,0 +1,73 @@ +#ifndef WRITER_VALUES_H +#define WRITER_VALUES_H + +#include +#include +#include +#include + +#include "evaluation_result.h" + +// Function to escape special characters in a string +std::string escape(const std::string &buffer); + +// Function to write a formatted string to an output stream +void write_formatted(FILE* out, const char* format, ...); + +// Function to write boolean values +void write_bool(FILE* out, bool value); + +// Function to write char values +void write_char(FILE* out, char value); + +// Function to write signed char values +void write_signed_char(FILE* out, signed char value); + +// Function to write unsigned char values +void write_unsigned_char(FILE* out, unsigned char value); + +// Function to write short int values +void write_sint(FILE* out, short int value); + +// Function to write unsigned short int values +void write_usint(FILE* out, unsigned short int value); + +// Function to write int values +void write_int(FILE* out, int value); + +// Function to write unsigned int values +void write_uint(FILE* out, unsigned int value); + +// Function to write long values +void write_long(FILE* out, long value); + +// Function to write unsigned long values +void write_ulong(FILE* out, unsigned long value); + +// Function to write long long values +void write_llong(FILE* out, long long value); + +// Function to write unsigned long long values +void write_ullong(FILE* out, unsigned long long value); + +// Function to write float values +void write_float(FILE* out, float value); + +// Function to write double values +void write_double(FILE* out, double value); + +// Function to write long double values +void write_ldouble(FILE* out, long double value); + +// Function to write string values +void write_string(FILE* out, const std::string &value); + +// Function to write unknown values +void write_unknown(FILE* out, void* value); + +// Function to write void values +void write_void(FILE* out, void* value); + +// Function to write evaluated results +void write_evaluated(FILE* out, EvaluationResult* result); +#endif //WRITER_VALUES_H diff --git a/tested/manual.py b/tested/manual.py index eab988a1..7f3707d1 100644 --- a/tested/manual.py +++ b/tested/manual.py @@ -13,7 +13,7 @@ from tested.main import run from tested.testsuite import SupportedLanguage -exercise_dir = "/home/niko/Ontwikkeling/universal-judge/tests/exercises/echo-function" +exercise_dir = "/home/jorg/Documents/universal-judge/tests/exercises/echo" def read_config() -> DodonaConfig: @@ -21,13 +21,13 @@ def read_config() -> DodonaConfig: return DodonaConfig( memory_limit=536870912, time_limit=60, - programming_language=SupportedLanguage("haskell"), + programming_language=SupportedLanguage("cpp"), natural_language="nl", resources=Path(exercise_dir, "evaluation"), - source=Path(exercise_dir, "solution/correct.hs"), + source=Path(exercise_dir, "solution/wrong.cpp"), judge=Path("."), workdir=Path("workdir"), - test_suite="two-specific.tson", + test_suite="two.tson", options=Options( linter=False, ), diff --git a/tested/testsuite.py b/tested/testsuite.py index 9d858f9f..1bae778b 100644 --- a/tested/testsuite.py +++ b/tested/testsuite.py @@ -76,6 +76,7 @@ class SupportedLanguage(StrEnum): PYTHON = auto() RUNHASKELL = auto() CSHARP = auto() + CPP = auto() LanguageMapping = dict[SupportedLanguage, str] diff --git a/tests/exercises/echo/solution/comp-error.cpp b/tests/exercises/echo/solution/comp-error.cpp new file mode 100644 index 00000000..62820109 --- /dev/null +++ b/tests/exercises/echo/solution/comp-error.cpp @@ -0,0 +1 @@ +mfzej àryhg çyh aiogharuio ghqgh \ No newline at end of file diff --git a/tests/exercises/echo/solution/correct.cpp b/tests/exercises/echo/solution/correct.cpp new file mode 100644 index 00000000..458d240a --- /dev/null +++ b/tests/exercises/echo/solution/correct.cpp @@ -0,0 +1,14 @@ +#include +#include + +int main() { + std::string input; + + // Read a line from standard input + std::getline(std::cin, input); + + // Output the same line + std::cout << input << std::endl; + + return 0; +} diff --git a/tests/exercises/echo/solution/wrong.cpp b/tests/exercises/echo/solution/wrong.cpp new file mode 100644 index 00000000..e33934fb --- /dev/null +++ b/tests/exercises/echo/solution/wrong.cpp @@ -0,0 +1,7 @@ +#include +using namespace std; + +int main () { + cout << "wrong"; + return EXIT_SUCCESS; +} diff --git a/tests/language_markers.py b/tests/language_markers.py index 3a2acb76..35465a8b 100644 --- a/tests/language_markers.py +++ b/tests/language_markers.py @@ -4,6 +4,7 @@ "python", "java", "c", + "cpp", "kotlin", "haskell", "csharp",