diff --git a/.gitignore b/.gitignore index fca52d7c..3bca26e7 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,9 @@ venv-*/ .venv/ node_modules/ result/ + +# Visual Studio Code +universal-judge.sln +.data/current/python +.data/current/python-packages +.vscode/ \ No newline at end of file diff --git a/tested/languages/c/generators.py b/tested/languages/c/generators.py index 8f39a1a8..5b581842 100644 --- a/tested/languages/c/generators.py +++ b/tested/languages/c/generators.py @@ -162,9 +162,35 @@ def convert_statement(self, statement: Statement, full=False) -> str: f"{self.convert_statement(statement.expression)};" ) raise AssertionError(f"Unknown statement: {statement!r}") + + def convert_testcase(self, tc: PreparedTestcase, pu: PreparedExecutionUnit) -> str: + result = "" + if tc.testcase.is_main_testcase(): + assert isinstance(tc.input, MainInput) + wrapped = [json.dumps(a) for a in tc.input.arguments] + result += f'char* args[] = {{"{pu.submission_name}", ' + result += ", ".join(wrapped) + result += "};\n" + result += ( + f"exit_code = solution_main({len(tc.input.arguments) + 1}, 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 + + self.convert_statement(tc.input.unwrapped_input_statement()) + + ";\n" + ) + result += " " * 4 + self.convert_statement(tc.input.no_value_call()) + ";\n" + else: + result += self.convert_statement(tc.input.input_statement()) + ";\n" + return result - def _generate_internal_context(self, ctx: PreparedContext, pu: PreparedExecutionUnit) -> str: + def generate_internal_context(self, ctx: PreparedContext, pu: PreparedExecutionUnit) -> str: result = f""" {ctx.before} @@ -175,30 +201,8 @@ def _generate_internal_context(self, ctx: PreparedContext, pu: PreparedExecution 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'char* args[] = {{"{pu.submission_name}", ' - result += ", ".join(wrapped) - result += "};\n" - result += ( - f"exit_code = solution_main({len(tc.input.arguments) + 1}, 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 - + self.convert_statement(tc.input.unwrapped_input_statement()) - + ";\n" - ) - result += " " * 4 + self.convert_statement(tc.input.no_value_call()) + ";\n" - else: - result += self.convert_statement(tc.input.input_statement()) + ";\n" - + result += self.convert_testcase(tc, pu) + result += ctx.after + "\n" result += "return exit_code;\n" return result @@ -247,7 +251,7 @@ def convert_execution_unit(self, pu: PreparedExecutionUnit) -> str: for i, ctx in enumerate(pu.contexts): result += f""" int {pu.unit.name}_context_{i}(void) {{ - {self._generate_internal_context(ctx, pu)} + {self.generate_internal_context(ctx, pu)} }} """ diff --git a/tested/languages/cpp/config.py b/tested/languages/cpp/config.py index 3c631ed1..457fa428 100644 --- a/tested/languages/cpp/config.py +++ b/tested/languages/cpp/config.py @@ -1,7 +1,9 @@ from pathlib import Path -import re +from tested.features import Construct from tested.languages.c.config import C +from tested.languages.conventionalize import Conventionable, NamingConventions +from tested.languages.cpp.generators import CPPGenerator from tested.languages.language import CallbackResult from tested.languages.utils import executable_name @@ -12,6 +14,22 @@ def initial_dependencies(self) -> list[str]: def file_extension(self) -> str: return "cpp" + + def naming_conventions(self) -> dict[Conventionable, NamingConventions]: + return { + "identifier": "pascal_case", + "property": "pascal_case", + "class": "pascal_case", + "global_identifier": "macro_case", + } + + def supported_constructs(self) -> set[Construct]: + return { + Construct.FUNCTION_CALLS, + Construct.ASSIGNMENTS, + Construct.GLOBAL_VARIABLES, + Construct.OBJECTS, + } def compilation(self, files: list[str]) -> CallbackResult: main_file = files[-1] @@ -32,3 +50,5 @@ def compilation(self, files: list[str]) -> CallbackResult: [result], ) + def generator(self) -> CPPGenerator: + return CPPGenerator(self.file_extension()) diff --git a/tested/languages/cpp/generators.py b/tested/languages/cpp/generators.py new file mode 100644 index 00000000..995b0ebc --- /dev/null +++ b/tested/languages/cpp/generators.py @@ -0,0 +1,123 @@ + +from tested.datatypes import AllTypes, resolve_to_basic +from tested.datatypes.advanced import AdvancedObjectTypes, AdvancedSequenceTypes, AdvancedStringTypes +from tested.datatypes.basic import BasicObjectTypes, BasicSequenceTypes, BasicTypes +from tested.languages.c.generators import CGenerator +from tested.languages.preparation import PreparedExecutionUnit, PreparedFunctionCall, PreparedTestcase, PreparedTestcaseStatement +from tested.serialisation import FunctionCall, FunctionType, ObjectType, PropertyAssignment, SequenceType, Statement, VariableAssignment, VariableType, WrappedAllTypes + + +class CPPGenerator(CGenerator): + def unpack_wrapped_types(self, type_or_types: WrappedAllTypes) -> tuple[AllTypes, WrappedAllTypes]: + if isinstance(type_or_types, tuple): + return type_or_types + return type_or_types, None + + def convert_sequence_subtype(self, value: Statement, subtype: AllTypes) -> str: + if value and isinstance(value, SequenceType): + # if the value is a sequence, we need to know the types of it's elements + type_or_types = value.get_content_type() + elif subtype: + # we might already have a subtype extracted from a previous recursive call + type_or_types = subtype + else: + # c++ has no default type such as Object in java, so we can't infer the type + return None + + tp, subtype = self.unpack_wrapped_types(type_or_types) + return self.convert_declaration(tp, None, subtype) + + def convert_map_subtypes(self, value: Statement, subtype: WrappedAllTypes) -> tuple[str, str] | None: + if isinstance(value, ObjectType): + key_type = value.get_key_type() + value_type = value.get_value_type() + elif subtype: + key_type, value_type = subtype + else: + return None + key_base_type, key_sub_type = self.extract_type_tuple(key_type) + value_base_type, value_sub_type = self.extract_type_tuple(value_type) + key_type_str = self.convert_declaration(key_base_type, None, key_sub_type) + value_type_str = self.convert_declaration(value_base_type, None, value_sub_type) + + return key_type_str, value_type_str + + + + def convert_declaration(self, tp: AllTypes | VariableType, + value: Statement | None = None, + subtype: WrappedAllTypes| None = None) -> str: + if isinstance(tp, VariableType): + return tp.data + "*" + if tp == AdvancedSequenceTypes.LIST: + subtype = self.convert_sequence_subtype(value, subtype) + return f"std::list<{subtype}>" + elif tp == AdvancedSequenceTypes.TUPLE: + # this method does not support tuples within sequences such as list> + # as value won't be defined in that case and we cant't infer the tuple's length + # we also don't support tuples with different types, as we can only extract one type + assert value is not None and isinstance(value, SequenceType) + tuple_length = len(value.data) + subtype = self.convert_sequence_subtype(value, subtype) + return f"std::tuple<{", ".join(subtype for _ in range(tuple_length))}>" + elif tp == AdvancedSequenceTypes.ARRAY: + subtype = self.convert_sequence_subtype(value, subtype) + return f"std::vector<{subtype}>" + elif tp == AdvancedStringTypes.STRING: + return "std::string" + + basic = resolve_to_basic(tp) + if basic == BasicObjectTypes.MAP: + key_type, value_type = self.convert_map_subtypes(value, subtype) + return f"std::map<{key_type}, {value_type}>" + elif basic == BasicSequenceTypes.SET: + subtype = self.convert_sequence_subtype(value, subtype) + return f"std::set<{subtype}>" + + return super().convert_declaration(tp) + + def convert_statement(self, statement: Statement, full=False) -> str: + # support for property assignments + if isinstance(statement, PropertyAssignment): + return ( + f"{self.convert_statement(statement.property)} = " + f"{self.convert_statement(statement.expression)};" + ) + # overwrite the default implementation for variable assignments to allow for + # object declarations + elif full and isinstance(statement, VariableAssignment): + + prefix = self.convert_declaration(statement.type, statement.expression) + return ( + f"{prefix} {statement.variable} = " + f"{self.convert_statement(statement.expression)}" + ) + + return super().convert_statement(statement, full) + + def convert_function_call(self, function: FunctionCall) -> str: + result = super().convert_function_call(function) + + # add the namespace to the function call if it is not a constructor + if function.namespace and not ( + (isinstance(function, PreparedFunctionCall) and function.has_root_namespace) + and function.type == FunctionType.CONSTRUCTOR + ): + result = self.convert_statement(function.namespace) + "->" + result + # add the new keyword to constructors + if function.type == FunctionType.CONSTRUCTOR: + result = "new " + result + return result + + def convert_testcase(self, tc: PreparedTestcase, pu: PreparedExecutionUnit) -> str: + result = "" + # Define variables before asignment + if ( not tc.testcase.is_main_testcase() + and isinstance(tc.input, PreparedTestcaseStatement) + and isinstance(tc.input.statement, VariableAssignment) + ): + prefix = self.convert_declaration(tc.input.statement.type, tc.input.statement.expression) + result += f"{prefix} {tc.input.statement.variable};\n" + + result += super().convert_testcase(tc, pu) + return result diff --git a/tested/languages/cpp/templates/values.cpp b/tested/languages/cpp/templates/values.cpp index db0aefe6..8d859000 100644 --- a/tested/languages/cpp/templates/values.cpp +++ b/tested/languages/cpp/templates/values.cpp @@ -31,10 +31,8 @@ string escape(const string &buffer) { 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); @@ -42,70 +40,57 @@ void write_formatted(FILE* out, const char* format, ...) { va_end(args); } -// Function to write boolean values -void write_bool(FILE* out, bool value) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(FILE* out, unsigned int value) { write_formatted(out, FORMAT("uint32", "%u"), value); } -// Function to write long values -void write_long(FILE* out, long value) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(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) { +void write_value(FILE* out, float value) { if (isnan(value)) { write_formatted(out, FORMAT("single_precision", "\"%s\""), "nan"); } else if (isinf(value)) { @@ -115,8 +100,7 @@ void write_float(FILE* out, float value) { } } -// Function to write double values -void write_double(FILE* out, double value) { +void write_value(FILE* out, double value) { if (isnan(value)) { write_formatted(out, FORMAT("double_precision", "\"%s\""), "nan"); } else if (isinf(value)) { @@ -126,8 +110,7 @@ void write_double(FILE* out, double value) { } } -// Function to write long double values -void write_ldouble(FILE* out, long double value) { +void write_value(FILE* out, long double value) { if (isnan(value)) { write_formatted(out, FORMAT("double_extended", "\"%s\""), "nan"); } else if (isinf(value)) { @@ -137,20 +120,18 @@ void write_ldouble(FILE* out, long double value) { } } -// Function to write string values -void write_string(FILE* out, const string &value) { +void write_value(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\""), "?"); +void write_value(FILE* out, void* value) { + write_formatted(out, FORMAT("nothing", "\"%s\""), "null"); } -// Function to write void values -void write_void(FILE* out, void* value) { - write_formatted(out, FORMAT("nothing", "\"%s\""), "null"); +template void write_value(FILE* out, T value) +{ + write_formatted(out, FORMAT("unknown", "%p"), "?"); } // Function to write evaluated results diff --git a/tested/languages/cpp/templates/values.h b/tested/languages/cpp/templates/values.h index ab858d3e..c208e1f8 100644 --- a/tested/languages/cpp/templates/values.h +++ b/tested/languages/cpp/templates/values.h @@ -14,59 +14,25 @@ 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); +template void write_value(FILE* out, T value); +void write_value(FILE* out, bool value); +void write_value(FILE* out, char value); +void write_value(FILE* out, signed char value); +void write_value(FILE* out, unsigned char value); +void write_value(FILE* out, short int value); +void write_value(FILE* out, unsigned short int value); +void write_value(FILE* out, int value); +void write_value(FILE* out, unsigned int value); +void write_value(FILE* out, long value); +void write_value(FILE* out, unsigned long value); +void write_value(FILE* out, long long value); +void write_value(FILE* out, unsigned long long value); +void write_value(FILE* out, float value); +void write_value(FILE* out, double value); +void write_value(FILE* out, long double value); +void write_value(FILE* out, const std::string &value); +void write_value(FILE* out, void* value); +void write_value(FILE* out, void* value); // Function to write evaluated results void write_evaluated(FILE* out, EvaluationResult* result); diff --git a/tested/manual.py b/tested/manual.py index ad1dc353..c3840793 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/jorg/Documents/universal-judge/tests/exercises/echo" +exercise_dir = "/home/jorg/Documents/universal-judge/tests/exercises/counter" def read_config() -> DodonaConfig: @@ -24,10 +24,10 @@ def read_config() -> DodonaConfig: programming_language=SupportedLanguage("cpp"), natural_language="nl", resources=Path(exercise_dir, "evaluation"), - source=Path(exercise_dir, "solution/run-error.cpp"), + source=Path(exercise_dir, "solution/solution.cpp"), judge=Path("."), workdir=Path("workdir"), - test_suite="two.tson", + test_suite="plan.yaml", options=Options( linter=False, ), diff --git a/tests/exercises/counter/solution/solution.cpp b/tests/exercises/counter/solution/solution.cpp new file mode 100644 index 00000000..b6b88fed --- /dev/null +++ b/tests/exercises/counter/solution/solution.cpp @@ -0,0 +1,19 @@ +class Counter { +private: + int counter; + +public: + // Constructor + Counter() : counter(0) {} + + // Method to add to the counter + Counter& add() { + counter++; + return *this; + } + + // Method to get the current counter value + int get() const { + return counter; + } +}; \ No newline at end of file