Skip to content

Commit b705a9f

Browse files
authored
Merge pull request #532 from dodona-edu/feat/add-cpp
Add C++
2 parents 2e7d7b3 + bb33e5a commit b705a9f

File tree

64 files changed

+2040
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2040
-94
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,9 @@ venv-*/
8282
.venv/
8383
node_modules/
8484
result/
85+
86+
# Visual Studio Code
87+
universal-judge.sln
88+
.data/current/python
89+
.data/current/python-packages
90+
.vscode/

tested/dsl/schema-strict.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,8 @@
778778
"kotlin",
779779
"python",
780780
"runhaskell",
781-
"csharp"
781+
"csharp",
782+
"cpp"
782783
]
783784
},
784785
"message" : {

tested/dsl/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,8 @@
778778
"kotlin",
779779
"python",
780780
"runhaskell",
781-
"csharp"
781+
"csharp",
782+
"cpp"
782783
]
783784
},
784785
"message" : {

tested/languages/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from tested.languages.bash.config import Bash
1313
from tested.languages.c.config import C
14+
from tested.languages.cpp.config import CPP
1415
from tested.languages.csharp.config import CSharp
1516
from tested.languages.haskell.config import Haskell
1617
from tested.languages.java.config import Java
@@ -36,6 +37,7 @@
3637
"python": Python,
3738
"runhaskell": RunHaskell,
3839
"csharp": CSharp,
40+
"cpp": CPP,
3941
}
4042

4143

tested/languages/cpp/config.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import re
2+
from pathlib import Path
3+
4+
from tested.datatypes import AllTypes
5+
from tested.dodona import AnnotateCode, Message
6+
from tested.features import Construct, TypeSupport
7+
from tested.languages.conventionalize import (
8+
Conventionable,
9+
NamingConventions,
10+
submission_file,
11+
)
12+
from tested.languages.cpp.generators import CPPGenerator
13+
from tested.languages.language import (
14+
CallbackResult,
15+
Command,
16+
Language,
17+
TypeDeclarationMetadata,
18+
)
19+
from tested.languages.preparation import PreparedExecutionUnit
20+
from tested.languages.utils import executable_name
21+
from tested.serialisation import Statement, Value
22+
23+
24+
class CPP(Language):
25+
def initial_dependencies(self) -> list[str]:
26+
return [
27+
"values.h",
28+
"values.cpp",
29+
"values.tpp",
30+
"evaluation_result.h",
31+
"evaluation_result.cpp",
32+
]
33+
34+
def needs_selector(self):
35+
return True
36+
37+
def file_extension(self) -> str:
38+
return "cpp"
39+
40+
def comment(self, text: str) -> str:
41+
return f"// {text}"
42+
43+
def naming_conventions(self) -> dict[Conventionable, NamingConventions]:
44+
return {
45+
"identifier": "camel_case",
46+
"property": "camel_case",
47+
"class": "pascal_case",
48+
"global_identifier": "macro_case",
49+
}
50+
51+
def supported_constructs(self) -> set[Construct]:
52+
return {
53+
Construct.FUNCTION_CALLS,
54+
Construct.ASSIGNMENTS,
55+
Construct.GLOBAL_VARIABLES,
56+
Construct.OBJECTS,
57+
Construct.HETEROGENEOUS_COLLECTIONS,
58+
Construct.DEFAULT_PARAMETERS,
59+
Construct.HETEROGENEOUS_ARGUMENTS,
60+
Construct.EXCEPTIONS,
61+
}
62+
63+
def datatype_support(self) -> dict[AllTypes, TypeSupport]:
64+
return { # type: ignore
65+
"integer": "supported",
66+
"real": "supported",
67+
"char": "supported",
68+
"text": "supported",
69+
"string": "supported",
70+
"boolean": "supported",
71+
"nothing": "supported",
72+
"undefined": "reduced",
73+
"null": "reduced",
74+
"int8": "supported",
75+
"uint8": "supported",
76+
"int16": "supported",
77+
"uint16": "supported",
78+
"int32": "supported",
79+
"uint32": "supported",
80+
"int64": "supported",
81+
"uint64": "supported",
82+
"single_precision": "supported",
83+
"double_precision": "supported",
84+
"double_extended": "supported",
85+
"sequence": "supported",
86+
"set": "supported",
87+
"map": "supported",
88+
"dictionary": "supported",
89+
"object": "reduced",
90+
"array": "supported",
91+
"list": "supported",
92+
"tuple": "supported",
93+
}
94+
95+
def compilation(self, files: list[str]) -> CallbackResult:
96+
main_file = files[-1]
97+
exec_file = Path(main_file).stem
98+
result = executable_name(exec_file)
99+
assert self.config
100+
return (
101+
[
102+
"g++",
103+
"-std=c++20",
104+
"-Wall",
105+
"-O3" if self.config.options.compiler_optimizations else "-O0",
106+
"evaluation_result.cpp",
107+
"values.cpp",
108+
main_file,
109+
"-o",
110+
result,
111+
],
112+
[result],
113+
)
114+
115+
def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command:
116+
local_file = cwd / executable_name(Path(file).stem)
117+
return [str(local_file.absolute()), *arguments]
118+
119+
def modify_solution(self, solution: Path):
120+
with open(solution, "r") as file:
121+
contents = file.read()
122+
# We use regex to find the main function.
123+
# First, check if we have a no-arg main function.
124+
# If so, replace it with a renamed main function that does have args.
125+
no_args = re.compile(r"(int|void)(\s+)main(\s*)\((\s*)\)(\s*{)")
126+
replacement = r"int\2solution_main\3(\4int argc, char* argv[])\5"
127+
contents, nr = re.subn(no_args, replacement, contents, count=1)
128+
if nr == 0:
129+
# There was no main function without arguments. Now we try a main
130+
# function with arguments.
131+
with_args = re.compile(r"(int|void)(\s+)main(\s*)\((\s*)int")
132+
replacement = r"int\2solution_main\3(\4int"
133+
contents = re.sub(with_args, replacement, contents, count=1)
134+
with open(solution, "w") as file:
135+
header = "#pragma once\n\n"
136+
file.write(header + contents)
137+
138+
def linter(self, remaining: float) -> tuple[list[Message], list[AnnotateCode]]:
139+
# Import locally to prevent errors.
140+
from tested.languages.c import linter
141+
142+
assert self.config
143+
return linter.run_cppcheck(self.config.dodona, remaining, "c++")
144+
145+
def cleanup_stacktrace(self, stacktrace: str) -> str:
146+
result = ""
147+
in_student_file = False
148+
for line in stacktrace.splitlines(keepends=True):
149+
if submission_file(self) in line:
150+
in_student_file = True
151+
elif "|" not in line:
152+
in_student_file = False
153+
154+
if in_student_file:
155+
line = line.replace(submission_file(self), "<code>")
156+
result += line
157+
return result
158+
159+
def is_source_file(self, file: Path) -> bool:
160+
return file.suffix in (".cpp", ".h", ".tpp")
161+
162+
def generator(self) -> CPPGenerator:
163+
return CPPGenerator(self.file_extension())
164+
165+
def generate_statement(self, statement: Statement) -> str:
166+
return self.generator().convert_statement(statement)
167+
168+
def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str:
169+
return self.generator().convert_execution_unit(execution_unit)
170+
171+
def generate_selector(self, contexts: list[str]) -> str:
172+
return self.generator().convert_selector(contexts)
173+
174+
def generate_encoder(self, values: list[Value]) -> str:
175+
return self.generator().convert_encoder(values)
176+
177+
def get_declaration_metadata(self) -> TypeDeclarationMetadata:
178+
return {
179+
"names": { # type: ignore
180+
"integer": "int",
181+
"real": "double",
182+
"char": "char",
183+
"text": "std::string",
184+
"string": "std::string",
185+
"boolean": "bool",
186+
"nothing": "void",
187+
"undefined": "void",
188+
"int8": "std::int8_t",
189+
"uint8": "std::uint8_t",
190+
"int16": "std::int16_t",
191+
"uint16": "std::uint16_t",
192+
"int32": "std::int32_t",
193+
"uint32": "std::uint32_t",
194+
"int64": "std::int64_t",
195+
"uint64": "std::uint64_t",
196+
"bigint": "std::intmax_t",
197+
"single_precision": "float",
198+
"double_precision": "double",
199+
"any": "std::any",
200+
"sequence": "std::vector",
201+
"array": "std::vector",
202+
"list": "std::list",
203+
"tuple": "std::tuple",
204+
"set": "std::set",
205+
"map": "std::map",
206+
},
207+
"nested": ("<", ">"),
208+
"exception": "std::exception_ptr",
209+
}
210+
211+
def is_void_method(self, name: str) -> bool:
212+
assert self.config
213+
regex = rf"void\s+{name}"
214+
the_source = self.config.dodona.source.read_text()
215+
return re.search(regex, the_source) is not None

0 commit comments

Comments
 (0)