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/__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/c/config.py b/tested/languages/c/config.py index cb7c8e6a..4ec853f1 100644 --- a/tested/languages/c/config.py +++ b/tested/languages/c/config.py @@ -6,6 +6,7 @@ from tested.datatypes import AllTypes from tested.dodona import AnnotateCode, Message from tested.features import Construct, TypeSupport +from tested.languages.c.generators import CGenerator from tested.languages.conventionalize import ( EXECUTION_PREFIX, Conventionable, @@ -103,7 +104,7 @@ def modify_solution(self, solution: Path): # First, check if we have a no-arg main function. # If so, replace it with a renamed main function that does have args. no_args = re.compile(r"(int|void)(\s+)main(\s*)\((\s*)\)(\s*{)") - replacement = r"int\2solution_main\3(\4int argc, char** argv)\5" + replacement = r"int\2solution_main\3(\4int argc, char* argv[])\5" contents, nr = re.subn(no_args, replacement, contents, count=1) if nr == 0: # There was no main function without arguments. Now we try a main @@ -140,25 +141,20 @@ def cleanup_stacktrace(self, stacktrace: str) -> str: def is_source_file(self, file: Path) -> bool: return file.suffix in (".c", ".h") - def generate_statement(self, statement: Statement) -> str: - from tested.languages.c import generators + def generator(self) -> CGenerator: + return CGenerator(self.file_extension()) - return generators.convert_statement(statement, full=True) + def generate_statement(self, statement: Statement) -> str: + return self.generator().convert_statement(statement, full=True) def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str: - from tested.languages.c import generators - - return generators.convert_execution_unit(execution_unit) + return self.generator().convert_execution_unit(execution_unit) def generate_selector(self, contexts: list[str]) -> str: - from tested.languages.c import generators - - return generators.convert_selector(contexts) + return self.generator().convert_selector(contexts) def generate_encoder(self, values: list[Value]) -> str: - from tested.languages.c import generators - - return generators.convert_encoder(values) + return self.generator().convert_encoder(values) def get_declaration_metadata(self) -> TypeDeclarationMetadata: return { diff --git a/tested/languages/c/generators.py b/tested/languages/c/generators.py index f2d23cf3..cc84d52f 100644 --- a/tested/languages/c/generators.py +++ b/tested/languages/c/generators.py @@ -35,144 +35,139 @@ from tested.testsuite import MainInput -def convert_arguments(arguments: list[Expression | NamedArgument]) -> str: - return ", ".join(convert_statement(cast(Expression, arg)) for arg in arguments) +class CGenerator: + def __init__(self, extension: str = "c"): + self.extension = extension + def convert_arguments(self, arguments: list[Expression | NamedArgument]) -> str: + return ", ".join( + self.convert_statement(cast(Expression, arg)) for arg in arguments + ) -def convert_value(value: Value) -> str: - # Handle some advanced types. - if value.type == AdvancedStringTypes.CHAR: - assert isinstance(value, StringType) - return f"(char) '" + value.data.replace("'", "\\'") + "'" - elif value.type == AdvancedNumericTypes.INT_16: - return f"((short) {value.data})" - elif value.type == AdvancedNumericTypes.U_INT_16: - return f"((unsigned short) {value.data})" - elif value.type == AdvancedNumericTypes.INT_64: - return f"{value.data}L" - elif value.type == AdvancedNumericTypes.U_INT_64: - return f"{value.data}UL" - elif value.type == AdvancedNumericTypes.U_INT_32: - return f"{value.data}U" - # Handle basic types - original = value - value = as_basic_type(value) - if value.type == BasicNumericTypes.INTEGER: - # Basic heuristic for long numbers - if (value.data > (2**31 - 1)) or (value.data < -(2**31)): + def convert_value(self, value: Value) -> str: + # Handle some advanced types. + if value.type == AdvancedStringTypes.CHAR: + assert isinstance(value, StringType) + return f"(char) '" + value.data.replace("'", "\\'") + "'" + elif value.type == AdvancedNumericTypes.INT_16: + return f"((short) {value.data})" + elif value.type == AdvancedNumericTypes.U_INT_16: + return f"((unsigned short) {value.data})" + elif value.type == AdvancedNumericTypes.INT_64: return f"{value.data}L" - else: - return str(value.data) - elif value.type == BasicNumericTypes.REAL: - suffix = "f" if original.type == AdvancedNumericTypes.SINGLE_PRECISION else "" - if not isinstance(value.data, SpecialNumbers): - return str(value.data) + suffix - elif value.data == SpecialNumbers.NOT_A_NUMBER: - return "nan" + suffix + '("")' - elif value.data == SpecialNumbers.POS_INFINITY: - if original.type == AdvancedNumericTypes.DOUBLE_PRECISION: - return "((double) INFINITY)" + elif value.type == AdvancedNumericTypes.U_INT_64: + return f"{value.data}UL" + elif value.type == AdvancedNumericTypes.U_INT_32: + return f"{value.data}U" + # Handle basic types + original = value + value = as_basic_type(value) + if value.type == BasicNumericTypes.INTEGER: + # Basic heuristic for long numbers + if (value.data > (2**31 - 1)) or (value.data < -(2**31)): + return f"{value.data}L" else: - return "INFINITY" - else: - assert SpecialNumbers.NEG_INFINITY - if original.type == AdvancedNumericTypes.DOUBLE_PRECISION: - return "((double) -INFINITY)" + return str(value.data) + elif value.type == BasicNumericTypes.REAL: + suffix = ( + "f" if original.type == AdvancedNumericTypes.SINGLE_PRECISION else "" + ) + if not isinstance(value.data, SpecialNumbers): + return str(value.data) + suffix + elif value.data == SpecialNumbers.NOT_A_NUMBER: + return "nan" + suffix + '("")' + elif value.data == SpecialNumbers.POS_INFINITY: + if original.type == AdvancedNumericTypes.DOUBLE_PRECISION: + return "((double) INFINITY)" + else: + return "INFINITY" else: - return "(-INFINITY)" - elif value.type == BasicStringTypes.TEXT: - return json.dumps(value.data) - elif value.type == BasicBooleanTypes.BOOLEAN: - return f"(bool) " + str(value.data).lower() - elif value.type == BasicNothingTypes.NOTHING: - return "NULL" - elif value.type == BasicStringTypes.UNKNOWN: - assert isinstance(value, StringType) - return convert_unknown_type(value) - raise AssertionError(f"Invalid literal: {value!r}") - - -def convert_function_call(function: FunctionCall) -> str: - result = function.name - if function.type != FunctionType.PROPERTY: - result += f"({convert_arguments(function.arguments)})" # pyright: ignore - return result - - -def convert_declaration(tp: AllTypes | VariableType) -> str: - if isinstance(tp, VariableType): - return tp.data - elif tp == AdvancedNumericTypes.BIG_INT: - return "long long" - elif tp == AdvancedNumericTypes.U_INT_64: - return "unsigned long" - elif tp == AdvancedNumericTypes.INT_64: - return "long" - elif tp == AdvancedNumericTypes.U_INT_32: - return "unsigned int" - elif tp == AdvancedNumericTypes.INT_32: - return "int" - elif tp == AdvancedNumericTypes.U_INT_16: - return "unsigned short int" - elif tp == AdvancedNumericTypes.INT_16: - return "short int" - elif tp == AdvancedNumericTypes.U_INT_8: - return "unsigned char" - elif tp == AdvancedNumericTypes.INT_8: - return "signed char" - elif tp == AdvancedNumericTypes.DOUBLE_EXTENDED: - return "long double" - elif tp == AdvancedNumericTypes.DOUBLE_PRECISION: - return "double" - elif tp == AdvancedNumericTypes.SINGLE_PRECISION: - return "float" - elif tp == AdvancedStringTypes.CHAR: - return "char" - basic = resolve_to_basic(tp) - if basic == BasicBooleanTypes.BOOLEAN: - return "bool" - elif basic == BasicStringTypes.TEXT: - return "char*" - elif basic == BasicNumericTypes.INTEGER: - return "long long" - elif basic == BasicNumericTypes.REAL: - return "double" - elif basic == BasicNothingTypes.NOTHING: - return "void" - raise AssertionError(f"Unknown type: {tp!r}") - - -def convert_statement(statement: Statement, full=False) -> str: - if isinstance(statement, Identifier): - return statement - elif isinstance(statement, FunctionCall): - return convert_function_call(statement) - elif isinstance(statement, Value): - return convert_value(statement) - elif isinstance(statement, VariableAssignment): - if full: - prefix = convert_declaration(statement.type) + " " - else: - prefix = "" - return ( - f"{prefix}{statement.variable} = " - f"{convert_statement(statement.expression)};" - ) - raise AssertionError(f"Unknown statement: {statement!r}") - - -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" + assert SpecialNumbers.NEG_INFINITY + if original.type == AdvancedNumericTypes.DOUBLE_PRECISION: + return "((double) -INFINITY)" + else: + return "(-INFINITY)" + elif value.type == BasicStringTypes.TEXT: + return json.dumps(value.data) + elif value.type == BasicBooleanTypes.BOOLEAN: + return f"(bool) " + str(value.data).lower() + elif value.type == BasicNothingTypes.NOTHING: + return "NULL" + elif value.type == BasicStringTypes.UNKNOWN: + assert isinstance(value, StringType) + return convert_unknown_type(value) + raise AssertionError(f"Invalid literal: {value!r}") + + def convert_function_call(self, function: FunctionCall) -> str: + result = function.name + if function.type != FunctionType.PROPERTY: + result += ( + f"({self.convert_arguments(function.arguments)})" # pyright: ignore + ) + return result + + def convert_declaration(self, tp: AllTypes | VariableType) -> str: + if isinstance(tp, VariableType): + return tp.data + elif tp == AdvancedNumericTypes.BIG_INT: + return "long long" + elif tp == AdvancedNumericTypes.U_INT_64: + return "unsigned long" + elif tp == AdvancedNumericTypes.INT_64: + return "long" + elif tp == AdvancedNumericTypes.U_INT_32: + return "unsigned int" + elif tp == AdvancedNumericTypes.INT_32: + return "int" + elif tp == AdvancedNumericTypes.U_INT_16: + return "unsigned short int" + elif tp == AdvancedNumericTypes.INT_16: + return "short int" + elif tp == AdvancedNumericTypes.U_INT_8: + return "unsigned char" + elif tp == AdvancedNumericTypes.INT_8: + return "signed char" + elif tp == AdvancedNumericTypes.DOUBLE_EXTENDED: + return "long double" + elif tp == AdvancedNumericTypes.DOUBLE_PRECISION: + return "double" + elif tp == AdvancedNumericTypes.SINGLE_PRECISION: + return "float" + elif tp == AdvancedStringTypes.CHAR: + return "char" + basic = resolve_to_basic(tp) + if basic == BasicBooleanTypes.BOOLEAN: + return "bool" + elif basic == BasicStringTypes.TEXT: + return "char*" + elif basic == BasicNumericTypes.INTEGER: + return "long long" + elif basic == BasicNumericTypes.REAL: + return "double" + elif basic == BasicNothingTypes.NOTHING: + return "void" + raise AssertionError(f"Unknown type: {tp!r}") + + def convert_statement(self, statement: Statement, full=False) -> str: + if isinstance(statement, Identifier): + return statement + elif isinstance(statement, FunctionCall): + return self.convert_function_call(statement) + elif isinstance(statement, Value): + return self.convert_value(statement) + elif isinstance(statement, VariableAssignment): + if full: + prefix = self.convert_declaration(statement.type) + " " + else: + prefix = "" + return ( + f"{prefix}{statement.variable} = " + 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] @@ -189,139 +184,163 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) # The method has a "void" return type, so don't wrap it. result += ( " " * 4 - + convert_statement(tc.input.unwrapped_input_statement()) + + self.convert_statement(tc.input.unwrapped_input_statement()) + ";\n" ) - result += " " * 4 + convert_statement(tc.input.no_value_call()) + ";\n" + result += ( + " " * 4 + self.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 - #include - - #include "values.h" - #include "{pu.submission_name}.c" - """ - - # Import functions - for name in pu.evaluator_names: - result += f'#include "{name}.c"\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) - """ + result += self.convert_statement(tc.input.input_statement()) + ";\n" + return result + + def generate_internal_context( + self, ctx: PreparedContext, pu: PreparedExecutionUnit + ) -> str: + result = f""" + {ctx.before} + + int exit_code; + """ - # 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)} + # Generate code for each testcase + tc: PreparedTestcase + for tc in ctx.testcases: + result += f"{pu.unit.name}_write_separator();\n" + result += self.convert_testcase(tc, pu) + + result += ctx.after + "\n" + result += "return exit_code;\n" + return result + + def define_write_funtions(self, pu: PreparedExecutionUnit) -> str: + return 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) """ - 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" + def convert_execution_unit(self, pu: PreparedExecutionUnit) -> str: + result = f""" + #include + #include + + #include "values.h" + #include "{pu.submission_name}.{self.extension}" + """ - 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 + # Import functions + for name in pu.evaluator_names: + result += f'#include "{name}.{self.extension}"\n' + result += self.define_write_funtions(pu) -def convert_selector(contexts: list[str]) -> str: - result = """ - #include - #include - - #define INCLUDED true - """ + # Generate code for each context. + ctx: PreparedContext + for i, ctx in enumerate(pu.contexts): + result += f""" + int {pu.unit.name}_context_{i}(void) {{ + {self.generate_internal_context(ctx, pu)} + }} + """ - for ctx in contexts: - result += f'#include "{ctx}.c"\n' + 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; + """ - result += """ - int main(int argc, const char* argv[]) { + 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" - if (argc < 1) { - fprintf(stderr, "No context selected."); - return -2; - } - - const char* name = argv[1]; - """ - for ctx in contexts: result += f""" - if (strcmp("{ctx}", name) == 0) {{ - return {ctx}(); - }} + 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(self, contexts: list[str]) -> str: + result = """ + #include + #include + + #define INCLUDED true """ - result += """ - fprintf(stderr, "Non-existing context '%s' selected.", name); - return -1; - } - """ - return result - + for ctx in contexts: + result += f""" + #if __has_include("{ctx}.{self.extension}") + #include "{ctx}.{self.extension}" + #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}.{self.extension}") + if (strcmp("{ctx}", name) == 0) {{ + return {ctx}(); + }} + #endif + """ + + result += """ + fprintf(stderr, "Non-existing context '%s' selected.", name); + return -1; + } + """ + return result -def convert_encoder(values: list[Value]) -> str: - result = """ -#include -#include -#include + def convert_encoder(self, values: list[Value]) -> str: + result = """ + #include + #include + #include -#include "values.h" + #include "values.h" -int main() { -""" - for value in values: - result += " " * 4 + f"write_value(stdout, {convert_value(value)});\n" - result += " " * 4 + 'printf("␞");\n' - result += "}\n" - return result + int main() { + """ + for value in values: + result += " " * 4 + f"write_value(stdout, {self.convert_value(value)});\n" + result += " " * 4 + 'printf("␞");\n' + result += "}\n" + return result diff --git a/tested/languages/cpp/config.py b/tested/languages/cpp/config.py new file mode 100644 index 00000000..7e3b36e5 --- /dev/null +++ b/tested/languages/cpp/config.py @@ -0,0 +1,76 @@ +from pathlib import Path + +from tested.datatypes import AllTypes +from tested.features import Construct, TypeSupport +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 + + +class CPP(C): + def initial_dependencies(self) -> list[str]: + return [ + "values.h", + "values.cpp", + "values.tpp", + "evaluation_result.h", + "evaluation_result.cpp", + ] + + def file_extension(self) -> str: + return "cpp" + + def naming_conventions(self) -> dict[Conventionable, NamingConventions]: + return { + "identifier": "camel_case", + "property": "camel_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, + Construct.HETEROGENEOUS_COLLECTIONS, + Construct.DEFAULT_PARAMETERS, + Construct.HETEROGENEOUS_ARGUMENTS, + } + + def datatype_support(self) -> dict[AllTypes, TypeSupport]: + return super().datatype_support() | { # type: ignore + "sequence": "supported", + "set": "supported", + "map": "supported", + "dictionary": "supported", + "object": "reduced", + "array": "supported", + "list": "supported", + "tuple": "supported", + } + + 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++17", + "-Wall", + "-O3" if self.config.options.compiler_optimizations else "-O0", + "evaluation_result.cpp", + "values.cpp", + main_file, + "-o", + result, + ], + [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..67b031b3 --- /dev/null +++ b/tested/languages/cpp/generators.py @@ -0,0 +1,221 @@ +from tested.datatypes import AllTypes, resolve_to_basic +from tested.datatypes.advanced import ( + AdvancedNumericTypes, + AdvancedObjectTypes, + AdvancedSequenceTypes, + AdvancedStringTypes, +) +from tested.datatypes.basic import ( + BasicNumericTypes, + BasicObjectTypes, + BasicSequenceTypes, + BasicStringTypes, + BasicTypes, +) +from tested.languages.c.generators import CGenerator +from tested.languages.preparation import ( + PreparedContext, + PreparedExecutionUnit, + PreparedFunctionCall, + PreparedTestcase, + PreparedTestcaseStatement, +) +from tested.serialisation import ( + FunctionCall, + FunctionType, + ObjectType, + PropertyAssignment, + SequenceType, + Statement, + Value, + 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_value(self, value: Value) -> str: + tp = value.type + basic = resolve_to_basic(tp) + if basic == BasicObjectTypes.MAP: + return ( + "{" + + ", ".join( + f"{self.convert_value(k), self.convert_value(v)}" + for k, v in value.data.items() + ) + + "}" + ) + elif basic == BasicSequenceTypes.SEQUENCE or basic == BasicSequenceTypes.SET: + return "{" + ", ".join(self.convert_value(v) for v in value.data) + "}" + elif basic == BasicStringTypes.TEXT: + return f'std::string("{value.data}")' + + return super().convert_value(value) + + def convert_declaration( + self, + tp: AllTypes | VariableType, + value: Statement | None = None, + subtype: WrappedAllTypes | None = None, + ) -> str: + if isinstance(tp, VariableType): + return tp.data + "*" + elif tp == AdvancedNumericTypes.BIG_INT: + return "std::intmax_t" + elif tp == AdvancedNumericTypes.U_INT_64: + return "std::uint64_t" + elif tp == AdvancedNumericTypes.INT_64: + return "std::int64_t" + elif tp == AdvancedNumericTypes.U_INT_32: + return "std::uint32_t" + elif tp == AdvancedNumericTypes.INT_32: + return "std::int32_t" + elif tp == AdvancedNumericTypes.U_INT_16: + return "std::uint16_t" + elif tp == AdvancedNumericTypes.INT_16: + return "std::int16_t" + elif tp == AdvancedNumericTypes.U_INT_8: + return "std::uint8_t" + elif tp == AdvancedNumericTypes.INT_8: + return "std::int8_t" + 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}>" + elif basic == BasicSequenceTypes.SEQUENCE: + subtype = self.convert_sequence_subtype(value, subtype) + return f"std::vector<{subtype}>" + elif basic == BasicStringTypes.TEXT: + return "std::string" + elif basic == BasicStringTypes.ANY: + return "std::any" + elif basic == BasicNumericTypes.INTEGER: + return "std::intmax_t" + + 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) + + # if the function has a namespace, that is not the root namespace we assume it is a method call + if ( + function.namespace + and not function.has_root_namespace + and not 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 outside the try block + 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" + + # catch exceptions and write them to the output + result += "try {" + "\n" + result += super().convert_testcase(tc, pu) + result += "\n} catch (std::exception_ptr e) {\n" + result += self.convert_statement(tc.exception_statement("e")) + ";\n" + result += "exit_code = 1;\n" + result += "}\n" + return result + + def define_write_funtions(self, pu: PreparedExecutionUnit) -> str: + result = super().define_write_funtions(pu) + + # add a write function for exceptions + result += f""" + #undef send_exception + #define send_exception(value) write_exception({pu.unit.name}_value_file, value) + """ + 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..98258706 --- /dev/null +++ b/tested/languages/cpp/templates/values.cpp @@ -0,0 +1,75 @@ +#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; +} + + + + +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 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; +} + + +// writes an exception to json as +// { "type" : "exception", "message" : "message", "stacktrace" : "stacktrace" } +void write_exception(FILE* out, std::exception_ptr e) +{ + // Stacktrace and exception messages is not easily available in C++ + string json = "{ \"type\" : \"exception\", \"message\" : \"\", \"stacktrace\" : \"\" }"; + fprintf(out, "%s", json.c_str()); +} \ No newline at end of file diff --git a/tested/languages/cpp/templates/values.h b/tested/languages/cpp/templates/values.h new file mode 100644 index 00000000..0a512e53 --- /dev/null +++ b/tested/languages/cpp/templates/values.h @@ -0,0 +1,41 @@ +#ifndef WRITER_VALUES_H +#define WRITER_VALUES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "evaluation_result.h" + +string escape(const string &buffer); + +// Function to write a value as a json object with type information to a file +template void write_value(FILE* out, const T& value); + +// Function to write evaluated results +void write_evaluated(FILE* out, EvaluationResult* result); + +// writes an exception to json as +// { "type" : "exception", "message" : "message", "stacktrace" : "stacktrace" } +void write_exception(FILE* out, std::exception_ptr e); + +// Include the implementation file for template functions +#include "values.tpp" + +#endif //WRITER_VALUES_H diff --git a/tested/languages/cpp/templates/values.tpp b/tested/languages/cpp/templates/values.tpp new file mode 100644 index 00000000..1fdc0087 --- /dev/null +++ b/tested/languages/cpp/templates/values.tpp @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +string to_json(const T& value); + + +template +string getTypeName(const T&) { + if (is_same::value) return "integer"; + if (is_same::value) return "double_precision"; + if (is_same::value) return "single_precision"; + if (is_same::value) return "char"; + if (is_same::value) return "string"; + if (is_same::value) return "boolean"; + if (is_same::value) return "null"; + if (is_same::value) return "int8"; + if (is_same::value) return "uint8"; + if (is_same::value) return "int16"; + if (is_same::value) return "uint16"; + if (is_same::value) return "int32"; + if (is_same::value) return "uint32"; + if (is_same::value) return "int64"; + if (is_same::value) return "uint64"; + if (is_same::value) return "double_extended"; + if (is_same::value) return "nothing"; + return "undefined"; +} + +// Specialization for vector +template +string getTypeName(const vector&) { + return "sequence"; +} + +// Specialization for set +template +string getTypeName(const set&) { + return "set"; +} + +// Specialization for map +template +string getTypeName(const map&) { + return "map"; +} + +// Specialization for array +template +string getTypeName(const array&) { + return "array"; +} + +// Specialization for list +template +string getTypeName(const list&) { + return "list"; +} + +// Specialization for tuple +template +string getTypeName(const tuple&) { + return "tuple"; +} + +template +string to_json_value(const T& value) { + if constexpr (is_same::value) { + return "\"" + escape(value) + "\""; + } else if constexpr (is_same::value) { + return "\"" + string(1, value) + "\""; + } else if constexpr (is_same::value) { + return value ? "true" : "false"; + } else if constexpr (is_same::value) { + return "null"; + } else if constexpr (is_same::value) { + return "\"" + string(value) + "\""; + } else if constexpr (is_same::value || is_same::value || is_same::value) { + ostringstream oss; + oss << value; + return oss.str(); + } else { + return to_string(value); + } +} + + +template +string to_json_value(const vector& vec) { + string result = "["; + for (const auto& item : vec) { + result += to_json(item) + ","; + } + if (!vec.empty()) { + result.pop_back(); // remove trailing comma + } + result += "]"; + return result; +} + +template +string to_json_value(const set& set) { + string result = "["; + for (const auto& item : set) { + result += to_json(item) + ","; + } + if (!set.empty()) { + result.pop_back(); // remove trailing comma + } + result += "]"; + return result; +} + +template +string to_json_value(const map& map) { + string result = "{"; + for (const auto& item : map) { + result += "\"" + to_json_value(item.first) + "\": " + to_json(item.second) + ","; + } + if (!map.empty()) { + result.pop_back(); // remove trailing comma + } + result += "}"; + return result; +} + +template +string to_json_value(const array& arr) { + string result = "["; + for (const auto& item : arr) { + result += to_json(item) + ","; + } + if (N > 0) { + result.pop_back(); // remove trailing comma + } + result += "]"; + return result; +} + +template +string to_json_value(const tuple& tup); + +template +typename enable_if::type +to_json_value_helper(const tuple& tup, string& result) {} + +template +typename enable_if::type +to_json_value_helper(const tuple& tup, string& result) { + result += to_json(get(tup)) + ","; + to_json_value_helper(tup, result); +} + +template +string to_json_value(const tuple& tup) { + string result = "["; + to_json_value_helper(tup, result); + if (sizeof...(Args) > 0) { + result.pop_back(); // remove trailing comma + } + result += "]"; + return result; +} + +template +string to_json(const T& value) { + string json = "{ \"type\" : \"" + getTypeName(value) + "\", \"data\" : " + to_json_value(value) + " }"; + return json; +} + +template void write_value(FILE* out, const T& value) +{ + string json = to_json(value); + fprintf(out, "%s", json.c_str()); +} \ No newline at end of file diff --git a/tested/manual.py b/tested/manual.py index eab988a1..adcbcf29 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-function" 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/correct.cpp"), judge=Path("."), workdir=Path("workdir"), - test_suite="two-specific.tson", + test_suite="expected_no_return_and_got_none.yaml", 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/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 diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.cpp b/tests/exercises/echo-function-additional-source-files/solution/correct.cpp new file mode 100644 index 00000000..0d5dc4e1 --- /dev/null +++ b/tests/exercises/echo-function-additional-source-files/solution/correct.cpp @@ -0,0 +1,8 @@ +#include "echo_cpp.h" +#include +using namespace std; + + +string echo(string content) { + return source_echo(content); +} diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo.cpp b/tests/exercises/echo-function-additional-source-files/workdir/echo.cpp new file mode 100644 index 00000000..5b55b69e --- /dev/null +++ b/tests/exercises/echo-function-additional-source-files/workdir/echo.cpp @@ -0,0 +1,6 @@ +#include "echo_cpp.h" +#include + +std::string source_echo(std::string content) { + return content; +} diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo_cpp.h b/tests/exercises/echo-function-additional-source-files/workdir/echo_cpp.h new file mode 100644 index 00000000..60a88fd8 --- /dev/null +++ b/tests/exercises/echo-function-additional-source-files/workdir/echo_cpp.h @@ -0,0 +1,8 @@ +#ifndef echo_cpp_h +#define echo_cpp_h + +std::string source_echo(std::string content); + +#include "echo.cpp" /* Should be imported in the header file to be able to compile, important for the teachers */ + +#endif diff --git a/tests/exercises/echo-function-file/solution/correct.cpp b/tests/exercises/echo-function-file/solution/correct.cpp new file mode 100644 index 00000000..3e722807 --- /dev/null +++ b/tests/exercises/echo-function-file/solution/correct.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +std::string trim(const std::string& str) { + std::string trimmed = str; + trimmed.erase(trimmed.begin(), std::find_if(trimmed.begin(), trimmed.end(), [](int ch) { + return !std::isspace(ch); + })); + trimmed.erase(std::find_if(trimmed.rbegin(), trimmed.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), trimmed.end()); + return trimmed; +} + +std::string echo_file(const std::string& filename) { + std::ifstream file(filename); + if (!file) { + return "Error: Failed to open file."; + } + + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + return trim(content); +} diff --git a/tests/exercises/echo-function/solution/correct.cpp b/tests/exercises/echo-function/solution/correct.cpp new file mode 100644 index 00000000..bec305d0 --- /dev/null +++ b/tests/exercises/echo-function/solution/correct.cpp @@ -0,0 +1,15 @@ +#include + +std::string echo(std::string content) { + return content; +} + + +void no_echo(std::string content) { + // Do nothing. +} + + +std::string to_string(int number) { + return std::to_string(number); +} 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/run-error.cpp b/tests/exercises/echo/solution/run-error.cpp new file mode 100644 index 00000000..04f1ee2e --- /dev/null +++ b/tests/exercises/echo/solution/run-error.cpp @@ -0,0 +1,20 @@ + +#include + +using namespace std; + +int main() { + int x = 1; + int y = 0; + throw runtime_error("Error"); + + string input; + + // Read a line from standard input + getline(cin, input); + + // Output the same line + cout << input << endl; + + return 0; +} \ No newline at end of file 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/exercises/global/solution/correct.cpp b/tests/exercises/global/solution/correct.cpp new file mode 100644 index 00000000..4af9f155 --- /dev/null +++ b/tests/exercises/global/solution/correct.cpp @@ -0,0 +1 @@ +#define GLOBAL_VAR std::string("GLOBAL") diff --git a/tests/exercises/isbn-list/solution/solution.cpp b/tests/exercises/isbn-list/solution/solution.cpp new file mode 100644 index 00000000..cf1eb785 --- /dev/null +++ b/tests/exercises/isbn-list/solution/solution.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +bool is_isbn10(const std::string &code) { + // Helper function for computing ISBN-10 check digit + auto check_digit = [](const std::string &code) -> char { + int check = 0; + for (size_t i = 0; i < 9; ++i) { + check += (i + 1) * (code[i] - '0'); + } + check %= 11; + return (check == 10) ? 'X' : (check + '0'); + }; + + // Check whether given code contains 10 characters + if (code.length() != 10) return false; + + // Check whether first nine characters of given code are digits + for (size_t i = 0; i < 9; ++i) { + if (!isdigit(code[i])) return false; + } + + // Check the check digit + return check_digit(code) == code[9]; +} + +bool is_isbn13(const std::string &code) { + // Helper function for computing ISBN-13 check digit + auto check_digit = [](const std::string &code) -> char { + int check = 0; + for (size_t i = 0; i < 12; ++i) { + check += ((i % 2 == 0) ? 1 : 3) * (code[i] - '0'); + } + check = (10 - (check % 10)) % 10; + return check + '0'; + }; + + // Check whether given code contains 13 characters + if (code.length() != 13) return false; + + // Check whether first twelve characters of given code are digits + for (size_t i = 0; i < 12; ++i) { + if (!isdigit(code[i])) return false; + } + + // Check the check digit + return check_digit(code) == code[12]; +} + +template +bool is_isbn(const T &code, bool isbn13 = true) { + return isbn13 ? is_isbn13(code) : is_isbn10(code); +} + + +template +std::vector are_isbn(const std::vector &codes) { + std::vector checks; + for (const auto &code : codes) { + if(code.type() != typeid(std::string)){ + checks.push_back(false); + continue; + } + std::string isbn = std::any_cast(code); + bool isbn13 = isbn.length() == 13; + + checks.push_back(is_isbn(code, isbn13)); + } + return checks; +} + +template +std::vector are_isbn(const std::vector &codes, bool isbn13) { + std::vector checks; + for (const auto &code : codes) { + checks.push_back(is_isbn(code, isbn13)); + } + return checks; +} diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-cpp.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-cpp.tson new file mode 100644 index 00000000..24406376 --- /dev/null +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-cpp.tson @@ -0,0 +1,89 @@ +{ + "tabs": [ + { + "name": "are_isbn", + "runs": [ + { + "contexts": [ + { + "before": { + "csharp": { + "data": "#include " + "data": "String Ex = () => {throw "An error occurred!";};" + } + }, + "testcases": [ + { + "input": { + "type": "sequence", + "variable": "codes01", + "expression": { + "type": "function", + "name": "ex", + "namespace": "", + "arguments": [] + } + } + }, + { + "input": { + "type": "function", + "name": "are_isbn", + "arguments": [ + "codes01" + ] + }, + "output": { + "result": { + "value": { + "data": [ + { + "data": false, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + } + ], + "type": "sequence" + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/tests/exercises/isbn/solution/solution.cpp b/tests/exercises/isbn/solution/solution.cpp new file mode 100644 index 00000000..71ab9f43 --- /dev/null +++ b/tests/exercises/isbn/solution/solution.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include + +bool is_isbn10_st(const std::string &code) { + // Helper function for computing ISBN-10 check digit + auto check_digit = [](const std::string &code) -> char { + int check = 0; + for (size_t i = 0; i < 9; ++i) { + check += (i + 1) * (code[i] - '0'); + } + check %= 11; + return (check == 10) ? 'X' : (check + '0'); + }; + + // Check whether given code contains 10 characters + if (code.length() != 10) return false; + + // Check whether first nine characters of given code are digits + for (size_t i = 0; i < 9; ++i) { + if (!isdigit(code[i])) return false; + } + + // Check the check digit + return check_digit(code) == code[9]; +} + +template +bool is_isbn10(const T &code) { + if(code.type() != typeid(std::string)) return false; + + return is_isbn10_st(std::any_cast(code)); +} + +bool is_isbn13_st(const std::string &code) { + // Helper function for computing ISBN-13 check digit + auto check_digit = [](const std::string &code) -> char { + int check = 0; + for (size_t i = 0; i < 12; ++i) { + check += ((i % 2 == 0) ? 1 : 3) * (code[i] - '0'); + } + check = (10 - (check % 10)) % 10; + return check + '0'; + }; + + // Check whether given code contains 13 characters + if (code.length() != 13) return false; + + // Check whether first twelve characters of given code are digits + for (size_t i = 0; i < 12; ++i) { + if (!isdigit(code[i])) return false; + } + + // Check the check digit + return check_digit(code) == code[12]; +} + +template +bool is_isbn13(const T &code) { + if(code.type() != typeid(std::string)) return false; + + return is_isbn13_st(std::any_cast(code)); +} + +template +bool is_isbn(const T &code, bool isbn13 = true) { + return isbn13 ? is_isbn13(code) : is_isbn10(code); +} + + +template +std::vector are_isbn(const std::vector &codes) { + std::vector checks; + for (const auto &code : codes) { + if(code.type() != typeid(std::string)){ + checks.push_back(false); + continue; + } + std::string isbn = std::any_cast(code); + bool isbn13 = isbn.length() == 13; + + checks.push_back(is_isbn(code, isbn13)); + } + return checks; +} + +template +std::vector are_isbn(const std::vector &codes, bool isbn13) { + std::vector checks; + for (const auto &code : codes) { + checks.push_back(is_isbn(code, isbn13)); + } + return checks; +} diff --git a/tests/exercises/lotto/solution/correct.cpp b/tests/exercises/lotto/solution/correct.cpp new file mode 100644 index 00000000..02f28f0a --- /dev/null +++ b/tests/exercises/lotto/solution/correct.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +std::string loterij(int aantal = 6, int maximum = 42) { + std::set getallen; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(1, maximum); + + while (getallen.size() < aantal) { + getallen.insert(dis(gen)); + } + + std::ostringstream oss; + for (auto num : getallen) { + oss << num << " - "; + } + + std::string result = oss.str(); + result = result.substr(0, result.length() - 3); // Remove the last " - " + return result; +} \ No newline at end of file diff --git a/tests/exercises/lotto/solution/wrong.cpp b/tests/exercises/lotto/solution/wrong.cpp new file mode 100644 index 00000000..9416e8db --- /dev/null +++ b/tests/exercises/lotto/solution/wrong.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include + +std::string loterij(int aantal = 6, int maximum = 42) { + std::set getallen; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(1, maximum); + + while (getallen.size() < aantal) { + getallen.insert(dis(gen)); + } + + std::ostringstream oss; + std::copy(getallen.rbegin(), getallen.rend(), std::ostream_iterator(oss, " - ")); + std::string result = oss.str(); + result = result.substr(0, result.length() - 3); // Remove the trailing " - " + return result; +} diff --git a/tests/exercises/objects/solution/correct.cpp b/tests/exercises/objects/solution/correct.cpp new file mode 100644 index 00000000..78017ec5 --- /dev/null +++ b/tests/exercises/objects/solution/correct.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +class EqualChecker { +public: + EqualChecker(int number) : number(number) {} + + bool check(int other) { + return other == number; + } + +private: + int number; +}; + +std::set> setTest() { + std::set> mySet; + mySet.insert({1, 2}); + mySet.insert({2, 3}); + return mySet; +} \ No newline at end of file diff --git a/tests/exercises/remove/evaluation/full.tson b/tests/exercises/remove/evaluation/full.tson index d74cb9f0..64d29df6 100644 --- a/tests/exercises/remove/evaluation/full.tson +++ b/tests/exercises/remove/evaluation/full.tson @@ -7,6 +7,33 @@ "contexts": [ { "testcases": [ + { + "input": { + "variable": "a_list", + "type": "sequence", + "expression": { + "type": "sequence", + "data": [ + { + "type": "integer", + "data": 0 + }, + { + "type": "integer", + "data": 1 + }, + { + "type": "integer", + "data": 1 + }, + { + "type": "integer", + "data": 2 + } + ] + } + } + }, { "output": { "result": { diff --git a/tests/exercises/remove/solution/correct.cpp b/tests/exercises/remove/solution/correct.cpp new file mode 100644 index 00000000..e90a808b --- /dev/null +++ b/tests/exercises/remove/solution/correct.cpp @@ -0,0 +1,13 @@ +#include +#include + +template +std::vector remove(const std::vector& l, const S& value) { + std::vector result; + for (const T& x : l) { + if (x != value) { + result.push_back(x); + } + } + return result; +} diff --git a/tests/exercises/remove/solution/wrong.cpp b/tests/exercises/remove/solution/wrong.cpp new file mode 100644 index 00000000..c662b845 --- /dev/null +++ b/tests/exercises/remove/solution/wrong.cpp @@ -0,0 +1,12 @@ +#include +#include +#include + +template +std::vector remove(std::vector& l, S v) { + auto it = std::find(l.begin(), l.end(), v); + if (it != l.end()) { + l.erase(it); + } + return l; +} diff --git a/tests/exercises/sum/solution/correct.cpp b/tests/exercises/sum/solution/correct.cpp new file mode 100644 index 00000000..f138c0e3 --- /dev/null +++ b/tests/exercises/sum/solution/correct.cpp @@ -0,0 +1,20 @@ +#include +#include + +int main(int argc, char* argv[]) { + int som = 0; + + for (int i = 1; i < argc; i++) { + int r = std::atoi(argv[i]); + if (r == 0 && argv[i][0] != '0') { + std::cerr << "som: ongeldige argumenten" << std::endl; + return 1; + } else { + som += r; + } + } + + std::cout << som << std::endl; + + return 0; +} \ No newline at end of file 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",